Skip to content

fix(zgw): a reference or uniqueness read that fails refuses the write (Q-dossiq-L11-3) - #3920

Merged
rubenvdlinde merged 10 commits into
developmentfrom
build/dq-l11-29
Oct 15, 2026
Merged

rubenvdlinde merged 10 commits into
developmentfrom
build/dq-l11-29

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Coordinator ruling on Q-dossiq-L11-3, following decision 224 (guards refuse when unsure). Stacked on #3919 (citations), #3858 and #3854: land after them.

What changes

The store reads that every ZGW rule shares (Zgw/ZgwRuleLookups) answered a failed read as "nothing found":

  • Identification uniqueness (fieldPairExists): a search that failed counted the identificatie as free, so a duplicate could be stored.
  • Reference resolution (findObjectByField, findAllObjectsByField, findBySchemaKey): a zaaktype, informatieobjecttype or besluittype that could not be read counted as one that does not exist.

They now throw Zgw/LookupNotCompleted, naming what they were reading. ZgwBusinessRulesService::validate() catches it in one place and refuses with the answer decision 224 gave its guards: 503, code check-not-completed, no invalidParams, logged at error with the rule (catalogi/zaaktypen create) and what was read. This covers the entry point's own ZTC guards and every per-register rule.

What stays as it was:

  • An object the store does not hold (DoesNotExistException, or no object) is still "none".
  • A read OpenRegister refuses to the caller (NotAuthorizedException) is still "none": a rule cannot use a type the caller may not see, and that is not a check that could not run.
  • No store in the request, or an empty schema setting, still skips the lookup, as the ruling says.

The lookups lost their logger: it only logged the failures they swallowed.

Debt list

catch-return-null.allowlist.json: three sites left (findObjectByField, findAllObjectsByField, findBySchemaKey). Ceiling 254 to 251 on top of #3854's 256 to 254.

Spec

REQ-ZBR-022 in openspec/changes/guards-refuse-when-unsure, tasks section 5.

Seen, not fixed

ZgwZrcZaakinformatieobjectRules::isCaseTypeInformatieobjecttypeMissing() reads the store itself (not through the lookups) and answers "not missing" when its search fails. Same shape, outside this ruling's scope.

Verified (once, on the stack top 3bfc0b4, which holds #3858 and the four PRs above it)

Every composer check:strict stage once, one at a time in the lane's check worktree (exit codes in dq-l11-logs/r7/s31.rc):

  • lint 0, refusal-grounds snapshot 0, phpcs 0, psalm 0, phpstan 0.
  • phpmd 2: only the two BooleanArgumentFlag findings in lib/Service/Termijn/TermLetters.php (401, 425) that development carries since fix(letters): every date in a letter to the applicant reads the same way #3884/fix(portal): the Woo request form reads Dutch dates and names refused fields in Dutch #3895. Inherited.
  • PHPUnit --no-coverage: 8,676 tests, exit 0 (7 skipped, 1 warning). A first run found 3 reds of this stack (BrcCrossRegisterSyncTest still read the old warning lines), fixed in 28e4ad6, then the run above.
  • Mutations: the catch in ZgwBusinessRulesService, the throw in findBySchemaKey, the throw in fieldPairExists, the not-found branch, the not-allowed branch and the null check in handleDestroy each turn their tests red.
  • Not run locally: the touched test classes under pcov with fail-on-risky. The pcov image is gone and docker cannot pull, so the @covers/@uses of every touched test were checked by hand (each class a test can now execute is listed). CI is the check for that.
  • eslint and prettier on the 17 JS/Vue files the citation PR touches: 0. Vitest not run: comments only.
  • Hydra gates against refactor(transitions): the status engine asks two facades instead of fourteen collaborators #3858's head: 80 of 80 applicable ran, 8 failed, all inherited (22 and 53 on src/manifest.json, which this stack does not touch; 23, 43, 51, 55, 104, 107). Gates 5, 13, 16, 25 and 46 pass.
  • openspec validate guards-refuse-when-unsure and method-decomposition: valid.
  • Inherited: the debt ratchet reads red on development itself (phpmd.suppressWarnings 187 measured against 182) and eslint.inlineDisables 15 against 14.

No register change, so no info.version bump.

Decision 224. The case type delete check, drc-004 and drc-007 let a request
through when their lookup failed. They now refuse with 503
check-not-completed, name the rule, and log the rule and the object.
drc-003 gives the same answer instead of claiming a duplicate, and the
controller's relation check before a document delete (drc-008a) follows.

Two sites leave the catch-return-null list, ceiling 263 to 261.
… facades

method-decomposition slice 8h. The engine couples to 18 classes against 13
and takes 14 constructor arguments. The design gives it one facade over what
a case declares and one over what a move writes; the engine keeps the order
of the checks.
Characterisation before the StatusTransitionService split (method-decomposition
slice 8h), green on the single class: the order of the five refusals, the
conflict check, the one save of a closing move with the version it read, the
timer after the save, the record that cannot be written back, the free-form
refusals and the shape of the available transitions. Seven mutations of the
engine turn it red.
…fourteen collaborators

method-decomposition slice 8h. StatusTransitionService coupled to 18 classes
against 13 and took 14 constructor arguments. Transitions/CaseDeclarations
reads what a case declares about its status and its moves, and
Transitions/CaseMoveLedger holds what a move writes. The engine keeps the
decision and the order of the checks, takes nine arguments and couples to 12
classes. Both suppressions are gone; phpmd.suppressWarnings 183 to 181.

The public methods and their answers are unchanged. The characterisation
test and the five existing engine tests are green with only their
constructor call changed.
…ments

The change what-a-status-declares was archived on 7 October. 206 @SPEC lines in 42 files still cited its path under openspec/changes/, which no longer exists. They now cite openspec/specs/status-transition-engine/spec.md (REQ-SDC-01 to 05) and openspec/specs/doorlooptijd-dashboard/spec.md (REQ-SDW-01), where the requirements live. Two lines that became a repeat of the line above them are removed.
… (Q-dossiq-L11-3)

The store reads the ZGW rules share answered a failed read as nothing found:
a uniqueness search that failed counted the identificatie as free, and a
zaaktype, informatieobjecttype or besluittype that could not be read counted
as absent. They now throw LookupNotCompleted, and ZgwBusinessRulesService
refuses the request for it once, with the 503 check-not-completed answer of
decision 224, logged at error with the rule and what was read.

Not found, and a read OpenRegister refuses (NotAuthorizedException), still
read as none. No store in the request, or an empty schema setting, still
skips the lookup (the coordinator's ruling on the question).

The lookups lost their logger, which only logged the swallowed failures.
catch-return-null: three sites left, ceiling 254 to 251. REQ-ZBR-022.
@rubenvdlinde
rubenvdlinde merged commit fc36f80 into development Oct 15, 2026
44 of 50 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant