Skip to content

refactor(ztc): the publish routes get their own controller, two helpers leave ZtcController - #3922

Merged
rubenvdlinde merged 13 commits into
developmentfrom
build/dq-l11-31
Oct 15, 2026
Merged

rubenvdlinde merged 13 commits into
developmentfrom
build/dq-l11-31

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Method-decomposition slice 8i, by measured complexity. Stacked on #3921 (Q-dossiq-L11-2; it shares .debt-baseline.json and the decomposition tasks with #3858): land after it.

What moves

ZtcController carried two class suppressions: 12 public methods against 10, and complexity 79 against 50 (measured on development without the suppressions).

  • The three publish routes go to their own controller, ZtcPublishController, with handlePublish() and publishedCopy(). Same URLs, verbs, rate limits and auth attributes; only the route names change (ztc#publish* to ztcPublish#publish* in appinfo/routes.php). It extends ZgwController, so the ZGW auth middleware still applies.
  • Zgw/ValidityDateFilter: the datumGeldigheid filter of a Catalogi list.
  • Zgw/ZtcTypeNameResolver: ztc-010m, an informatieobjecttype named by its omschrijving becomes its uuid.

Bodies are moved unchanged. After: 9 public methods, complexity under 50, both suppressions gone. phpmd.suppressWarnings 180 to 178 on top of #3858.

Tests

  • The publish contract test existed and moves with the routes (ZtcPublishControllerContractTest, 6 tests). ZgwRateLimitTest and ZgwAuthMiddlewareTest list the new controller, so its rate limits and its place under the middleware stay pinned.
  • New: ValidityDateFilterTest (both bounds inclusive, open bounds, re-indexed) and ZtcTypeNameResolverTest (name, then full text, outside the caller's access; and every case that leaves the body alone).

Seen, not changed

The ZTC publish routes run no business rule: the CT-02b publish guard (ZgwBusinessRulesService::guardCaseTypePublish, status types, a final status and a valid-from date) does not run on them, and they check no ZGW scope (the contract test documents the scope part). A zaaktype can therefore be published through /zaaktypen/{uuid}/publish without what the guard demands. Asked as Q-dossiq-L11-5; not a decomposition change.

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.
…4 (Q-dossiq-L11-2)

A ZGW delete answered 404 Not found on any failure: an unknown id, a store
that was down and a refused permission read the same, and so did a delete
that happened but whose notification failed. A show did the same, and an
update answered 400.

One place, StoreFailureAnswer behind ZgwService::storeFailureResponse(),
now sorts a store failure for every resource: DoesNotExistException or no
object is 404; OpenRegister's NotAuthorizedException or any failure with
code 403 is 403 permission_denied; anything else is 503 store-not-reached,
logged at error. ZgwService show, update and delete and the two BRC deletes
answer through it. A delete that happened answers 204 even when its
notification fails, and logs that at error. PATCH merges onto the object
the update already read instead of reading it a second time.

The cross-register writes of the Besluiten API stay best effort; a failure
is now logged at error with the rule, the resource and the objects.
REQ-ZBR-023.
The sync now logs a failed cross-register write at error with the rule,
the resource and the objects (Q-dossiq-L11-2); its own test still read the
old warning lines.
…rs leave ZtcController

Method-decomposition slice 8i. ZtcController carried two class suppressions:
12 public methods against 10, and complexity 79 against 50. The three publish
routes move to ZtcPublishController with handlePublish() and publishedCopy();
the URLs, verbs, rate limits and auth attributes are the same, only the route
names change to ztcPublish#. The datumGeldigheid filter becomes
Zgw/ValidityDateFilter and the ztc-010m name lookup Zgw/ZtcTypeNameResolver.
Bodies are moved unchanged. Both suppressions go; phpmd.suppressWarnings
180 to 178 (stacked on 8h).
@rubenvdlinde
rubenvdlinde merged commit dcad5ef 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