Repository navigation
feat(access): a case's supervisors decide who changes another reviewer's decision - #3761
Merged
Merged
Conversation
…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.
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)
… 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
…es get catalogue keys (check:schema-l10n)
…y for any case type CaseDocumentProvenance reads custodian, sourceSystem and the other provenance fields of a case's documents, from the record or from this case's join, and selects documents by them (REQ-DPV-001/002). The collection report reads through it. The provenance fragment's text names no procedure (decision 182); informatieobject 1.4.1, zaakinformatieobject 1.2.1, register 0.20.29.
A case type declares under reviewerAccess the records it keeps private to the case, where its reviewers are named, the supervising group and whether reviewers see each other's records. CaseReviewerAccess computes who reads each record (the assignee, the reviewers unless hidden) and who may change it (its author, a supervisor, an administrator); ObjectReadGrants applies it through OpenRegister's per-object sharing and owning group, read bit only, refusals counted. The assessment writer refuses a change to another reviewer's assessment and reconciles after a create; an administrator route reconciles every declaring case once. The Woo case type declares it for its assessments, which become private (decision 182: replaces the planned WooAssessmentAccess). Register 0.20.30, wooDocumentAssessment 1.2.0, Woo case type object 1.3.0.
…filinq A case type names under documentDrafts.<purpose> the filinq template (by slug and namespace), the format, the document type and the data the draft renders. CaseDraftData builds that data from the declaration (fields by dot path with value maps, related records as numbered rows, summaries over a list field); CaseDocumentDrafts asks filinq through its published DocumentGenerationRequestedEvent and files what filinq stored on the case. No filinq is filinq-missing, filinq's error is the reason, nothing is filed without a file. The Woo case type declares its decision and inventory drafts (decision 182: replaces the planned WooDecisionDrafts); assembleDecision answers both under draft and inventoryDraft. Register 0.20.31, Woo case type object 1.4.0.
…re of nothing is no figure Parity suite tests/Unit/Service/Term/StatutoryTermParityTest.php replays opencatalogi's StatutoryTermTest and WooRequestServiceTest scenarios through the extend endpoint, ask/receive and the quarterly report endpoint. The report counted running terms in the share and answered 0.0 with nothing decided. TermOutcome now classifies every term as met, missed, running or suspended (a late decision is missed), and the share is met / (met + missed), null when nothing is decided. The dashboard shows a dash for null, and the quarterly widget reads the keys the report actually sends.
…a supervisor REQ-CRA-005: CaseReassignGuardListener refuses an assignee change on a case whose case type keeps records private (and that has some) unless a supervisor or an administrator makes it, and reconciles the read grants after an accepted change. Generic: any case type that declares reviewerAccess. REQ-CRA-003 on the redaction half: saveRedactionProposal, which proposing and reviewing both write through, refuses another reviewer's assessment with 403 owned-by-another-reviewer; the controller answers the rule instead of a 400. REQ-WSA-004 backend: getOutstanding reads assessments unscoped, so a document with an assessment the caller cannot read counts as assessed. The UI state waits on a board (design backlog DqBeoordelingAfgeschermd). The unscoped reads of CaseReviewerAccess move to CaseRecordLookup to keep the class under the complexity ceiling.
…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: # lib/Service/DeadlineReportingService.php # lib/Service/Termijn/TermOutcome.php # tests/Unit/Service/DeadlineReportingServiceTest.php # tests/Unit/Service/Termijn/TermOutcomeTest.php
# Conflicts: # lib/AppInfo/Registrar/ObjectListenerRegistrar.php
…r's decision The document schema declares x-openregister-review (decider ownership, dossiq-coordinators, hideOthers false) and the key joins SCHEMA_ANNOTATION_KEYS so it reaches existing instances. A generic listener answers OpenRegister's DecisionChangeRequestedEvent through the case the object belongs to (REQ-CRA-006, woo-request-scoped-access 5.2, decision 197). Stacked on openregister#4583.
rubenvdlinde
requested review from
Rem-Dam,
SudoThijn,
WilcoLouwerse,
bbrands02,
remko48 and
rjzondervan
as code owners
October 11, 2026 21:07
…hers CaseDecisionAccess::maySee() answers OpenRegister's DecisionVisibilityRequestedEvent (openregister#4592, REQ-DEC-003) through the same listener, so a case-type supervisor outside the document schema's groups sees hidden decisions on their cases. A failed read keeps the decision hidden (REQ-CRA-007). Assisted-by: Claude Code
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.
What
woo-request-scoped-access 5.2, built generic (decision 182) on the decision-object model (decision 197).
documentschema declaresx-openregister-review {ownership: decider, supervisors: [dossiq-coordinators], hideOthers: false}in a new fragment,lib/Settings/register.d/89-document-review-ownership.json(document 1.2.0, register 0.20.32, digests regenerated).hideOthersmatches the Woo case type'sreviewerAccess.hideOthers, and a test keeps the two equal.x-openregister-reviewjoinsSchemaSlugMap::SCHEMA_ANNOTATION_KEYS, so the block reaches an instance that imported the document schema before it existed. Without it, OpenRegister drops the key in silence.specs/case-reviewer-access:CaseDecisionAccessmaps the object a decision is on to its case (the case itself, or every case that links it as a case document).CaseDecisionAccessListeneranswers OpenRegister's vetoableDecisionChangeRequestedEvent: on a case whose case type declares reviewer access, only that case type's supervising group or an administrator may change another reviewer's decision (case-supervisor-required). One refusing case refuses. When no guarded case is involved, OpenRegister's answer stays as it was. A read that throws refuses. The listener is registered inCaseAccessListenerRegistrar.tests/Stubs/OpenRegister/Event/DecisionChangeRequestedEvent.phpis a declaration-only stub of the event's getters (bootstrap include plus psalm stub; phpstan already scans that directory).Round 9: case-type supervisors see hidden decisions (REQ-CRA-007)
CaseDecisionAccess::maySee()and the sameCaseDecisionAccessListenernow answer OpenRegister'sDecisionVisibilityRequestedEvent(openregister#4592, REQ-DEC-003). A supervisor of every guarded case the object belongs to sees a decision hidden underhideOthers. Anyone else gets no answer, so the decision stays hidden. A read that throws also leaves it hidden. Registered inCaseAccessListenerRegistrar; declaration-only stubtests/Stubs/OpenRegister/Event/DecisionVisibilityRequestedEvent.php.Stack and dependencies
DecisionChangeRequestedEvent) and, for REQ-CRA-007, #4592 (DecisionVisibilityRequestedEvent). Without it the declaration is inert and nothing dispatches the event. Archive the change only after #4583 lands.redactionfolder for 5.2 (live pass, decision 139).Verified