Skip to content

The OpenRegister calls that only the fakes believed: saveObject's retired arg order and the id filter that matches nothing - #1686

Merged
rubenvdlinde merged 4 commits into
developmentfrom
fix/or-contract-drift-save-and-uuid-filters
Sep 2, 2026
Merged

The OpenRegister calls that only the fakes believed: saveObject's retired arg order and the id filter that matches nothing#1686
rubenvdlinde merged 4 commits into
developmentfrom
fix/or-contract-drift-save-and-uuid-filters

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What broke, live

The live-rig proof (DQ#1663) surfaced two green-CI/dead-runtime defect classes, both of the same family: the test fake agreed with the caller, so the suite could not fail.

1. saveObject in the retired argument order (29 sites, 4 were reported). OpenRegister's ObjectService::saveObject() takes the object FIRST (saveObject($object, $extend, $register, $schema, $uuid, …)). The reported four sites plus a sweep found 29 call sites still passing ($register, $schema, $object): the string register lands in the array|ObjectEntity $object slot and the call throws. Caught-and-logged as 'persist failed', which among other things meant no beslistermijn timer ever armed on a live pair. All 29 now use named arguments or the SearchesObjects::saveObjectAsArray() bridge, which gains a uuid pass-through for the update sites.

2. filters: ['id' => $uuid] matches nothing (7 sites, all reported). The current search resolves object identity only through the @self metadata block; a top-level id key addresses a schema property no schema declares and silently returns zero rows, so the parafering raise never found its voorstel and the StUF adapter never found its endpoint. All 7 now use the resolving get-by-id path: findObjectAsArray() (backed by ObjectService::find(), verified to resolve slugs and uuids on OR development) and a new StufRegisterAccess::findById() for the StUF wrapper.

3. The adjacent drift the honest fakes then exposed: find() returns an ObjectEntity, never an array, so an is_array() check after it is a dead path (the bezwaar register/resolve flows, termijn instance reads, dwangsom lookups). Fixed at the 12 sites the reworked fakes cover; roughly 20 more sites of this class in files outside this blast radius (Bezwaar/, Subsidie/, ZGW rules) are left for a follow-up sweep so this PR stays reviewable.

Tests that cannot lie again

  • FakeTermijnStore (shared by ~19 test files) now declares the REAL saveObject() signature, returns entity-shaped objects, throws DoesNotExistException from find(), and returns zero rows for a top-level id/uuid filter, exactly like live. Test seeding moved to an explicit seed() helper (49 sites).
  • The per-test fakes the change exposed (parafering, ZTC rules, seed data, CMMN counting store) were re-pinned the same way.
  • OrContractRegressionTest pins both contracts explicitly: the termijn persist works through the real signature, the retired positional order fatals, and a top-level id filter resolves nothing while the get-by-id path finds the row.

Also folded in

The live-journey e2e list assertion was pagination-blind (case list sorts identifier-asc, pages at 20, so the created case lands on the LAST page of any rig with >20 cases). It now uses the ?title= deep-link filter (CnIndexPage's route-query filter contract), with the dashboard-boot retry kept.

Verification

  • PHPUnit: 2732 tests green (was 2729; +3 contract pins).
  • php -l (1121 files), PHPCS, Psalm, PHPStan, PHPMD (both rulesets, per subdir, fresh pdepend cache): all clean.
  • eslint + prettier on the touched spec; playwright test --list collects 9 tests.
  • hydra-gates v1.11.0 --scope-to-diff: 42/42 applicable gates green.

🤖 Generated with Claude Code

… filters the fakes kept green

Two OpenRegister contract drifts shipped as green-CI/dead-runtime defects
(live-rig proof, DQ#1663):

- saveObject: 29 call sites still used the retired (register, schema,
  object) positional order; the real signature takes the object FIRST, so
  every one of them threw at runtime and, among other things, no
  beslistermijn timer ever armed. All 29 now go through named arguments
  or the SearchesObjects saveObjectAsArray bridge (which gains a uuid
  pass-through for update sites).

- searchObjects: 7 call sites filtered on a top-level 'id' key, which
  addresses a schema property no schema declares and silently matches
  zero rows (the parafering raise never found its voorstel). They now use
  the resolving get-by-id path (ObjectService::find via findObjectAsArray,
  plus a StufRegisterAccess::findById for the StUF wrapper).

- The adjacent shape drift the honest fakes then exposed: find() returns
  an ObjectEntity, never an array, so an is_array() check after it is a
  dead path. Fixed at the 12 sites the reworked fakes cover; the
  remaining sites of that class are catalogued for a follow-up.

Tests that cannot lie again: FakeTermijnStore (and the per-test fakes it
exposed) now declare the REAL saveObject signature - the retired order
fatals in tests the way it does live - and their searches resolve ids
only through @self/find, returning zero rows for top-level id filters
exactly like the live service. OrContractRegressionTest pins both
contracts explicitly.

Also folded in: the live-journey e2e list assertion was pagination-blind
(identifier-asc, 20 per page - the created case lands on the LAST page of
any lived-in rig); it now uses the ?title= deep-link filter.
…t-drift-save-and-uuid-filters

# Conflicts:
#	tests/e2e/case-flow-live-journeys.spec.ts
The merged repair test's fake declared saveObject without the uuid
parameter, so the trait's named-argument call errored and zero writes
landed.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 2ff246d

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-02 19:48 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit d8d3d75 into development Sep 2, 2026
49 checks passed
rubenvdlinde added a commit that referenced this pull request Sep 2, 2026
…tity-returning ': array' methods (#1694)

* fix(or-contract): finish the is_array-on-find sweep the honest fakes catalogued

PR #1686 fixed 12 sites of the class 'is_array() on the return of
ObjectService::find(), which returns an ObjectEntity and never an array'
and catalogued the rest for a follow-up. This is that follow-up, swept
repo-wide rather than trusting the catalogue:

- Bezwaar/* (15 find sites): AdvisoryCommitteeService, BeroepService,
  BezwaarCreationHook, DecisionService, HearingService and
  PanelIndependenceChecker all guarded find() results with is_array(),
  so every 'not found' guard fired on FOUND objects (committee referral,
  advice-request transitions, hearing attendance/minutes, beroep filing
  and cascade, decision publication) — the features always threw, or the
  dead true-branch silently skipped the work (panel independence author
  resolution, hearing case resolution).
- Subsidie/* (3 find sites): BeschikkingService.publish,
  SubsidieService.transitionAanvraag and VaststellingService.finalize
  answered 'niet gevonden' for records that exist.
- Singles (6): NoticeOfDefaultController.show (always 404),
  VoorstelBesluitController's IDOR gate (every voorstel unresolvable),
  DossierCompiler.compile (always 'Case not found'),
  CaseEmailRepository.findTemplate (always null),
  BeroepEscalationListener (dwingend derivation never ran), and
  InspectionChecklistService, whose get_object_vars() on an ObjectEntity
  read an empty array so the photo-required guard never fired.

All go through the SearchesObjects bridge (findObjectAsArray + null
check), as #1686 chose. The same files carried the adjacent drift #1686
named: ': array' methods returning saveObject()'s ObjectEntity — a
TypeError on every live call. 22 such sites (Bezwaar 11, Subsidie 11)
now go through saveObjectAsArray with an honest local fallback.

Deliberate non-sites, left alone: ZgwService/ZgwRulesBase/
ZgwZrcRulesService and the sharing/transfer services handle both shapes
via an explicit jsonSerialize() fallback, and
SelectionReassignmentService reads the entity via getObject().

The per-file fakes that kept this green are repinned to the real
contract (entity-shaped returns, DoesNotExistException on a miss, real
argument orders) in BezwaarCreationHookTest, VaststellingServiceTest,
DossierCompilerTest and VoorstelBesluitControllerContractTest, matching
the shared FakeTermijnStore.

PHPUnit 2771 green; php -l, PHPCS, Psalm, PHPStan, PHPMD (both
rulesets, per subdir, fresh pdepend cache) all clean.

* test(beroep): cover the repaired find/save contract, which the coverage ratchet demanded

The sweep added statements to files with zero unit coverage (the Bezwaar
services), and the changed-files coverage ratchet correctly read that as
'adding code without tests': 18.64% head vs 18.84% base, scoped to the
22 changed files. BeroepService was the largest untested surface (7
repaired sites), so it gets the contract pins: register() resolves an
EXISTING contested beslissing (pre-fix: always threw), a truly missing
one still refuses, addFileInspectionRequest() appends to the existing
beroep, and the reopen_objection cascade resolves the source bezwaar and
fires the beroep-reopen transition. All four run against the shared
entity-shaped FakeTermijnStore, so an is_array regression fails the way
it fails live.
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