Repository navigation
feat(documents): collected mail keeps its headers, a mail parties report, private review records - #3763
Merged
Conversation
… verdict woo-review-triage section 1: wooDocumentReview, wooTriageRule and wooReviewBatch schemas (87-woo-review.json, register 0.20.23); only an in-scope document needs a verdict and an unmarked one is always outstanding; POST relevance and GET summary on the Woo case; a repair step marks documents assessed before this change in scope so they do not block their case. (unit tests written, run pending a slot)
…h install has no assessments
# Conflicts: # appinfo/info.xml # tests/schemas/schema-version-digests.json
…rmed only when its term runs WooRequestIntake::receive() takes opencatalogi's answer keys (portal-form and opencatalogi origins, subjectRef optional for those two), writes the Woo case directly (decision 179: no intake object in between), counts the term from when the requester sent it, and reads the term back: armed only when a statutory instance with an engine timer ends on the case deadline; otherwise not-armed with an internal timeline entry, or unavailable with nothing written. PortalContributionProvider::receiveWooRequest() has opencatalogi's signature. Register 0.20.26 (wooRequest origin enum, phone and address).
…chable
formerReferences (list of {application, reference}) on the case schema, an
exact search declaration, register 0.20.27.
OpenCatalogiWooCase maps every status to its stage (received, in_progress, awaiting_clarification, decided, withdrawn), carries start, requester, channel, old reference, extension count and end date, on opencatalogi's own request shape as fixture. First part of takes-over 4.2.
…who gets a task for it
POST /api/cases/{id}/woo/batches takes a list of documents or a filter on
the rule that marked them, refuses a document already in another open
batch (409, naming the batch), writes the batch on each review and hands
the reviewer an engine task. GET lists the batches with assessed of total.
Also: WooThroughputReport gets its row in the date-arithmetic audit that
development's EveryTermOnTheCalendarTest now requires.
…throughput tally has its audit row
…d the viewer reports the pages seen
Review depth per document type (every page, or a sample drawn from a seed
recorded on the review). POST .../documents/{documentRef}/pages-seen
appends each displayed page with the reviewer and time; the first page
count a review hears sets its required pages from the recorded depth.
…seen every required page A verdict on an in-scope document with unseen required pages is refused in bulkAssess and, for direct OpenRegister writes, by WooPagesSeenGuard on the object creating and updating events. assembleDecision() and publish() answer 409 naming the documents and pages.
OneDateWritePathTest refused the private date parser in WooReceivedAnswers. The receipt moment, the start date, the decided date and the due date now go through CaseDateNormaliser, in the tenant's time zone, the one rule for what a date is.
…on 182 generalisation)
…and a recall sample gets its own batch (wip)
…at any case type switches on
Decision 182: procedures are configuration, code is generic.
- Classes move to OCA\Dossiq\Review and are named by what they do:
DocumentRelevance, ReviewSummary, ReviewBatches, ReviewDepth, PagesSeen,
StoppingRules, ReviewConfiguration; DocumentReviewController,
PagesSeenGuard, StoppingRuleGuard, CreateDocumentReviews,
ReviewListenerRegistrar.
- Schemas documentReview, triageRule, reviewBatch, stoppingRule and
recallSample (fragments 87-document-review, 88-review-recall); routes
under /api/cases/{id}/review/...
- The case type declares documentReview {enabled, reviewDepth}; the review
depth is read from the case type, and the Woo-verzoek case type switches
the review on.
… declaration Decision 182: the opencatalogi Woo request import becomes the generic case-record-import capability (CaseRecordImport, RecordCaseMapping, RecordImportStore, occ dossiq:case:import-records) and the generic term carry-over (TermCarryOver). The Woo part is the recordImports declaration on the Woo case type. caseType 1.17.0, register 0.20.28.
… former references
… kept Row 10.8 of woo-request-takes-over-from-opencatalogi: a Woo request received through the portal form was refused acknowledgement_no_address, because its requester's address lives only in wooRequest.verzoekerEmail. The acknowledgement declaration takes addressFields (generic); the Woo case type declares its path.
# Conflicts: # appinfo/info.xml
…g rule and samples, the batches and what is open Board DqWooReview (design-system #191), generic per decision 182: DocumentReviewPanel (registry type document-review), RecallSampleDialog, ReviewBatchDialog, and the pagesSeenReporter a review viewer calls as pages come into view. The summary answers enabled and unseen. e2e document-review.spec.ts cites every document-review scenario (not run, decision 139).
# Conflicts: # appinfo/info.xml # tests/schemas/schema-version-digests.json
…now the import's catches casesBySource answered [] when the existing cases could not be read, and the import then wrote a half-finished case a second time. It now answers null and the run stops with the failure listed. The case-record-import catch sites are classified in the catch-return-null allowlist and CaseRecordImport's period-to-days conversion has its row in the date-arithmetic audit (verdict neither); ServiceCatchReturnsNullTest and EveryTermOnTheCalendarTest were red on these since #3632.
…rn-null allowlist find() is a read miss counted as refused; service() is the degradation that makes the reconciler answer unavailable. ServiceCatchReturnsNullTest was red on these since #3660.
… StringIncrement)
…he answer's int types
# Conflicts: # appinfo/routes.php # lib/Settings/dossiq_register.json # lib/Woo/WooRequestForm.php # lib/Woo/WooRequestIntake.php # tests/schemas/schema-version-digests.json
…now the import's catches casesBySource answered [] when the existing cases could not be read, and the import then wrote a half-finished case a second time. It now answers null and the run stops with the failure listed. The case-record-import catch sites are classified in the catch-return-null allowlist and CaseRecordImport's period-to-days conversion has its row in the date-arithmetic audit (verdict neither); ServiceCatchReturnsNullTest and EveryTermOnTheCalendarTest were red on these since #3632.
… StringIncrement)
…he answer's int types
# Conflicts: # appinfo/routes.php # tests/Unit/Architecture/catch-return-null.allowlist.json
Conflicts: SchemaSlugMap (both), WOODocumentAssessmentService (relevance and reviewer access both injected; excluded documents leave the case before the relevance read), its test (both; reviewer access passed by name), woo-verzoek case type 1.3.1 over both, digests as the union (generator agrees), info.xml.
…cess reconciled after a batch BatchSelection takes a batch's documents by the review's rule and by the provenance fields of CaseDocumentProvenance (custodian, sourceSystem, source); an unknown field is still refused. ReviewBatches::create() and createSampleBatch() reconcile the case's reviewer access once the batch is stored (a failed reconcile is logged and answered, the batch stands). CaseDocumentProvenance::mailOf() reads provenance.mail for the triage rules.
…ce system, mail and text TriageRules keeps a case's triageRule objects and applies the active ones in order to every unmarked document; the first match marks it with its effect and name, and a reviewer's marking is never touched. TriageConditions checks the eight conditions at save and matches them; custodian and sourceSystem read the provenance, senderDomain and the mail date provenance.mail, the text conditions the text OpenRegister already extracted (DocumentText). A created rule reconciles the case's reviewer access. TriageRuleController routes list, create, change, delete and apply. The Review tab gets the rules section of board DqWooReview and TriageRuleDialog; the batch dialog takes documents by custodian, source system or rule.
… from the configuration TriageFacts gathers what a rule reads about a document, so TriageRules stays under the complexity limit. ReviewConfiguration::depthOfDocument() gives a review its depth and required pages, and the review controller reaches the pages seen through the reviews, so neither class needs a coupling suppression. Prettier and eslint over the touched Vue files.
…ge turned into a type error The test passed a PagesSeen as a seventh constructor argument the service never declared; PHP ignored it until #3660 added reviewerAccess in that position.
…and message id The gather add now writes through a generic CaseFileCollector, which adds provenance.mail for a collected e-mail. MailHeaderReader reads the headers through OpenRegister's EML parser into bare lower-cased addresses and marks a mail whose headers cannot be read as unreadable (woo-review-reports 3.1, REQ-WRR-003). provenance.mail is declared on informatieobject 1.4.2.
…ecipient and domain
MailPartiesReport counts a case's collected mail from provenance.mail, for
any case type, with unreadable mail counted apart and a CSV beside it.
GET /api/cases/{id}/reports/mail-parties needs the parties switch and read
access to the case (woo-review-reports 4.1, REQ-WRR-004; spec amended to
the generic route, decision 182).
… in read(), not swallowed as null
… through the case type documentReview, reviewBatch, triageRule, wooExclusion, wooSearchPlan and wooCollectionQuery carry the private authorization block (no hierarchy), and the woo-verzoek case type lists them under reviewerAccess.records so CaseReviewerAccess grants the people on the case read on them (woo-review-triage 6.1, 6.2 configuration; REQ-WRT-006). A generic test holds every listed record schema private.
…ncile the case documentReview and the three corpus records stay public until their writers reconcile: a private record nobody granted reads as absent to everyone but its author.
rubenvdlinde
requested review from
Rem-Dam,
SudoThijn,
WilcoLouwerse,
bbrands02,
remko48 and
rjzondervan
as code owners
October 11, 2026 21:20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #3733 (which stacks on #3663 and #3660; merge train 2, #3742, is landing those). Review the last four commits; the rest is the stack.
What this builds
woo-review-reports 3.1: a collected mail keeps its header fields (REQ-WRR-003). Generic, per decision 182:
MailHeaderReaderreads.emlheaders through OpenRegister's parser (TextExtractionService::parseEmlStructured; no new dependency in dossiq) into bare, lower-cased addresses. An.msg, a parse failure, a missing parser or a mail without a sender is stored as{unreadable: true}.CaseFileCollectorwrites collected bytes as a document on a case, with provenance, for any case type. It addsprovenance.mailwhen the file is a mail, by extension or by the source's MIME type. The gather add now calls it. That keepsWooGatherAddunder phpmd's coupling limit.provenance.mailis declared oninformatieobject1.4.2.woo-review-reports 4.1: a mail parties report (REQ-WRR-004). Generic:
MailPartiesReportcounts a case's collected mail per sender, per recipient and per domain. Each count is per mail. Unreadable mail is counted separately, and a CSV comes with the report. The route is GET/api/cases/{id}/reports/mail-partieson a newMailPartiesReportController, with#[NoAdminRequired]. The parties switch andCaseAccessGuardare checked in the method body. The spec now uses this generic route. The parties half of the reports screen is not in this PR.woo-review-triage 6.1 and 6.2, configuration.
reviewBatchandtriageRulenow carry the private authorization block, with no hierarchy, and the woo-verzoek case type (1.3.2) lists them underreviewerAccess.records. Their creators already reconcile the case.documentReview,wooExclusion,wooSearchPlanandwooCollectionQuerystay public for now. Their writers do not reconcile yet, so making them private would hide each record from everyone except its author. The per-record grant table needs a genericreadersoption onCaseReviewerAccess, which L9 is restructuring at the moment. tasks.md lists what is still open.Verified (targeted, slot rule / decision 149: CI is the full check)
tests/Unit/{Architecture,AppInfo,Woo,Service/Zaakdossier,Controller}: 1619 tests, exit 0.tests/Unit/{Settings,Service/Access,Review}: 415 tests OK. Settings again after the last commit: 309 OK..emlfixture and is validated against the real merged schema withRealSchemaValidator.test:l10n,check:l10n-jsandcheck:schema-l10nexit 0 (11 keys added in en and nl).composer check:strictand the hydra gates (token budget). development is not merged in yet, because train 2 is still open.Inherited: the debt-ratchet red on #3733 (+2 phpmd suppressions on WOODocumentAssessmentService) belongs to that PR. A train-2 agent is splitting that class, and this PR does not touch it.