Skip to content

fix(offline): planned inspections carried no checklist template ref — found by a real gate-24 parity check - #743

Merged
rubenvdlinde merged 1 commit into
developmentfrom
fix/gate-24-integration-parity
Aug 5, 2026
Merged

fix(offline): planned inspections carried no checklist template ref — found by a real gate-24 parity check#743
rubenvdlinde merged 1 commit into
developmentfrom
fix/gate-24-integration-parity

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

The defect this found

procest overrides the nc-vue field-inspection leaf in src/main.js with its own offlineConfig, but shipped no scripts/check-integration-parity.sh, so hydra gate-24 said, at the v1.3.0 pin:

[gate-24] integration-parity: SKIPPED (structural) — no scripts/check-integration-parity.sh,
but this repo DOES register integration leaves (lib/ LeafDescriptor: 0, src/ registerIntegration: 1).
server↔JS leaf parity (ADR-066 Decisions 4/7 …) is UNVERIFIED

Writing the check found a real defect on its first run.

offlineConfig.templateRefField is 'checklistTemplateRef', and nc-vue's CnFieldInspectionCard reads it off the planned item:

const templateRef = item?.[cfg.templateRefField]
this.activeTemplate = templateRef ? await getCachedObject(...) : null

The planned item is a fieldInspection, and this repo's fieldInspection schema declared no checklistTemplateRef property — only checklistResult had one. So templateRef was always undefined, activeTemplate always null, and answers always {}:

an inspector opening a planned inspection offline got an EMPTY checklist, every time, with no error anywhere.

The registration and the schema had drifted and nothing correlated them. src/main.js says the mapping "mirrors DailySyncService exactly" — there is no such class in this repo; the mapping was written against the design doc, where checklistTemplateRef sits on the result, not on the inspection.

The fix

Declare checklistTemplateRef on the fieldInspection schema — the template the inspector must complete for that inspection, read off the planned item by the leaf and copied onto the resulting checklistResult. Additive and optional (not added to required), so existing objects stay valid.

The check

Self-contained on purpose. The canonical Node check in @conduction/nextcloud-vue validates that library's built-ins, its ADR-066 cross-reference is WARN-only and self-disables in a repo with no new LeafDescriptor( (procest has none — its leaf is a builtin override, so it would have stayed silent here), and its scripts/ dir is not published to npm, so the historic wrapper shape resolves nothing in CI and exits 0 having checked nothing. Every way this one can fail to check exits non-zero with a named reason.

R1 complete render pair for the declared renderMode (mount+unmount / tab+widget)
R2 server↔JS id correlation both ways — phantom leaf / orphan registration
R3 renderMode agreement across layers under a shared id
R4 metadata agreement: label, icon, group, requiredApp, referenceType, surfaces
R5 a spread-inherited identity must come from the leaf-owning package
R6 an offlineConfig must name schemas/properties this repo actually declares

Output after the fix:

✓ integration parity: 0 server leaf face(s), 1 JS registration(s), 18 OpenRegister schema
  declaration file(s) — all rules pass (assertions run per rule: R1:0 R2:0 R3:0 R4:0 R5:1 R6:7)

The per-rule counts are printed so "verified" and "there was nothing to verify" are distinguishable. A run where every rule has zero subject matter exits 1.

Positive control — it has been seen to FAIL

break result
the defect itself, before the schema fix exit 1 — [R6 offlineConfig] leaf offlineConfig.templateRefField = "checklistTemplateRef" … is not a property of schema "fieldInspection"
titleField 'caseRef''caseReference' exit 1 — R6 names the property and lists the declared ones
...fieldInspectionIntegration → an unimported binding exit 1 — [R5 spread-source] … the spread of an undefined binding registers a leaf with no id

All restored; the check passes on the fixed tree.

gate-24, measured with the v1.3.0 script

before [gate-24] integration-parity: SKIPPED (structural) — … is UNVERIFIED
after [gate-24] integration-parity: PASS

It passes because the checker ran and returned 0 — the gate only reaches _pass 24 inside if [ -f scripts/check-integration-parity.sh ], after executing it.

…gate-24 parity check

procest registers the `field-inspection` OpenRegister leaf in `src/main.js`,
overriding the nc-vue builtin with its own `offlineConfig`, but shipped no
`scripts/check-integration-parity.sh`, so hydra gate-24 reported:

  [gate-24] integration-parity: SKIPPED (structural) — ... server↔JS leaf
  parity (ADR-066 Decisions 4/7 ...) is UNVERIFIED

Writing the check found a REAL defect on its first run.

THE DEFECT. `offlineConfig.templateRefField` is 'checklistTemplateRef', and
nc-vue's CnFieldInspectionCard reads it off the PLANNED item:

    const templateRef = item?.[cfg.templateRefField]
    this.activeTemplate = templateRef ? await getCachedObject(...) : null

The planned item is a `fieldInspection`, and this repo's `fieldInspection`
schema declared no `checklistTemplateRef` property — only `checklistResult` had
one. So `templateRef` was always undefined, `activeTemplate` always null, and
`answers` always `{}`: an inspector opening a planned inspection offline got an
EMPTY checklist, every time, with no error anywhere. The registration and the
schema had drifted and nothing correlated them. (`src/main.js` says the mapping
"mirrors DailySyncService exactly" — no such class exists in this repo; the
mapping was written against the design doc, where checklistTemplateRef sits on
the RESULT.)

THE FIX. Declare `checklistTemplateRef` on the `fieldInspection` schema — the
template the inspector must complete for that inspection, read off the planned
item by the leaf and copied onto the resulting `checklistResult`. Additive and
optional (not added to `required`), so existing objects stay valid.

THE CHECK. Self-contained on purpose: the canonical Node check in
@conduction/nextcloud-vue validates that library's own built-ins, its ADR-066
cross-reference is WARN-only and self-disables in a repo with no
`new LeafDescriptor(` (procest has none — its leaf is a builtin override, so it
would have stayed silent here), and its `scripts/` dir is not published to npm.
Every way this one can fail to check exits non-zero with a named reason.

Rules, all hard (ADR-019 AD-11/AD-13, ADR-066 decisions 4 and 7):
  R1 complete render pair for the declared renderMode (mount+unmount / tab+widget)
  R2 server↔JS id correlation, both ways (phantom leaf / orphan registration)
  R3 renderMode agreement across layers under a shared id
  R4 metadata agreement (label, icon, group, requiredApp, referenceType, surfaces)
  R5 a spread-inherited identity must come from the leaf-owning package
  R6 an offlineConfig must name schemas/properties this repo actually declares

procest exercises R5 (1) and R6 (7) — the counts are printed, so "verified" and
"nothing to verify" are distinguishable in the log. A run where every rule has
zero subject matter fails rather than passes.

Positive control (proved it can fail, then restored):
  * the defect itself — before the schema fix the check exited 1 with
    "[R6 offlineConfig] leaf offlineConfig.templateRefField =
     "checklistTemplateRef" ... is not a property of schema "fieldInspection""
  * titleField drift — 'caseRef' -> 'caseReference' => exit 1, R6 names it
  * spread drift — `...fieldInspectionIntegration` -> an unimported binding
    => exit 1, "[R5 spread-source] ... the spread of an undefined binding
       registers a leaf with no id"
All restored; the check passes on the fixed tree.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/procest @ b3ff470

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
composer ✅ 100/100
npm ✅ 550/550
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-05 19:10 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Status: gate-24 is fixed and PASSing. This PR is blocked by unrelated pre-existing debt.

Hydra Gates on this PR: 1 gate failed, and it is not this one.

[gate-24] integration-parity: PASS
[gate-53] effective-manifest-crossref: FAIL — 9 cross-reference failure(s)
[hydra-gates] COVERAGE: 59 of 63 declared gates reported a result (59 of 59 applicable gates ran)

gate-24 went from SKIPPED (structural) — … UNVERIFIED to PASS, and it passes because the checker ran and returned 0.

Why gate-53 fires

All 9 blocking findings are in src/menu-layout.json, and none of them touch this PR's subject:

at /menu-layout/removals/0:  removal 'SubstitutionMenu' orphans route 'SubstitutionSettings'
at /menu-layout/removals/1:  removal 'BezwaarBeroepGroup' orphans route 'BezwaarBeroepOverview'
at /menu-layout/removals/2:  removal 'Bezwaren' orphans route 'Bezwaren'
at /menu-layout/removals/3:  removal 'Beroepen' orphans route 'Beroepen'
at /menu-layout/removals/6:  removal 'SubsidiesGroup' orphans route 'Subsidies'
at /menu-layout/removals/8:  removal 'CaseMap' orphans route 'CaseMap'
at /menu-layout/removals/10: removal 'Voorstellen' orphans route 'Voorstellen'
at /menu-layout/removals/11: removal 'Advice' orphans route 'Advice'
at /menu-layout/removals/12: removal 'BesluitvormingAgenda' orphans route 'AgendaCompiler'
   — no surviving menu entry reaches it (ADR-044 no-functionality-loss)

They are pre-existing, and measurably independent of this diff. Running gate-53's own helper over this tree with the schema change reverted yields the identical finding count:

node build_effective_manifest.js && node check_manifest_crossref.js | grep -c 'orphans route'
  with the schema change:    10
  without the schema change: 10

They block only because of how gate-53 scopes itself. From manifest_diff_scope.py:

if "register" in rel and rel.endswith(".json") and rel.startswith("lib/"):
    # A register edit can invalidate a register/schema slug referenced
    # from ANY page. There is no per-page answer to be had.
    print("ALL")

so any edit to any lib/**register**.json unscopes gate-53 to the whole manifest and every legacy finding blocks. Under a normal diff scope these same 9 are reported as PRE-EXISTING … (ADR-020, not blocking).

The consequence is bigger than this PR: procest currently cannot accept ANY register/schema edit until those 9 are resolved.

The decision needed (not mine to take)

src/menu-layout.json's own _meta states the opposite intent to the rule that is failing:

removals: leaf menu-entry ids retired as duplicate navigation — their PAGES stay routable for deep links and e2e specs.

ADR-044's removals invariant says a removal must not leave a route no surviving menu entry reaches. procest's navigation design says exactly that is deliberate. Resolving it means one of:

  1. re-link each of the 9 routes into the menu (undoes a deliberate de-duplication of the nav), or
  2. delete the 9 pages (Bezwaren, Beroepen, Subsidies, CaseMap, Voorstellen, Advice, AgendaCompiler, SubstitutionSettings, BezwaarBeroepOverview), or
  3. amend ADR-044 to recognise a declared deep-link-only page — the crossref checker has no such affordance today.

Each is a product decision about nine live features. I am not taking it unilaterally, and I have not added a waiver, a baseline, a continue-on-error or a weakened assertion — the gate is right that those routes are unreachable from the menu.

Tracked as #744.

What is NOT in question

The defect this PR fixes is real and independent of all of the above: offlineConfig.templateRefField names checklistTemplateRef, CnFieldInspectionCard reads it off the planned fieldInspection, and that schema had no such property — so the offline checklist resolved to null and every planned inspection opened empty.

@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Verified and merged — the red Hydra Gates tick is proven pre-existing and unrelated

Checked tonight before merging. Recording the evidence because this PR is being merged over a red check, and that only holds if the red is shown to be someone else's.

1. The gate-53 failure is not this PR's

CI reported:

[gate-53] diff scope is INDETERMINATE for this PR (new/untracked manifest input, or a register JSON changed) — every finding blocks.
[gate-53] effective-manifest-crossref: FAIL — 9 cross-reference failure(s) in the effective manifest

The detail log is not uploaded by the job, so I reproduced gate-53 locally at the pinned logic, on two worktrees, with the same commands:

node lib/build_effective_manifest.js --app-dir . --out em.json
node lib/check_manifest_crossref.js  --app-dir . --manifest em.json

PR head 90af023c — 9 blocking findings.
development base 0053e9f2 — 9 blocking findings.

diff of the two finding sets: identical, zero lines of difference. All nine are /menu-layout/removals/*:

at /menu-layout/removals/0:  removal 'SubstitutionMenu'     orphans route 'SubstitutionSettings'
at /menu-layout/removals/1:  removal 'BezwaarBeroepGroup'   orphans route 'BezwaarBeroepOverview'
at /menu-layout/removals/2:  removal 'Bezwaren'             orphans route 'Bezwaren'
at /menu-layout/removals/3:  removal 'Beroepen'             orphans route 'Beroepen'
at /menu-layout/removals/6:  removal 'SubsidiesGroup'       orphans route 'Subsidies'
at /menu-layout/removals/8:  removal 'CaseMap'              orphans route 'CaseMap'
at /menu-layout/removals/10: removal 'Voorstellen'          orphans route 'Voorstellen'
at /menu-layout/removals/11: removal 'Advice'               orphans route 'Advice'
at /menu-layout/removals/12: removal 'BesluitvormingAgenda' orphans route 'AgendaCompiler'

None touch fieldInspection, the offline register, or anything in this diff. This PR did not create them — it only made them block, by touching a lib/Settings/*register*.json, which flips gate-53's scope to ALL. On a narrow scope the same nine are classified PRE-EXISTING and pass, which is why development shows gate-53 as PASS today.

2. The new check is live, not a green ornament

The PR's claim is that check-integration-parity.sh genuinely verifies something. A passing check is evidence about the check until it has been shown it can fail, so I mutated the fix out and re-ran it.

Baseline, unmodified branch — exit 0:

✓ integration parity: 0 server leaf face(s), 1 JS registration(s), 18 OpenRegister schema
  declaration file(s) — all rules pass (assertions run per rule: R1:0 R2:0 R3:0 R4:0 R5:1 R6:7)

With checklistTemplateRef removed from the fieldInspection schema — exit 1:

✗ [R6 offlineConfig] leaf offlineConfig.templateRefField = "checklistTemplateRef" (src/main.js)
  is not a property of schema "fieldInspection" as declared in lib/Settings/** — the leaf would
  filter/display on a property that does not exist. Declared properties: caseRef, completedAt,
  deviceId, gpsLocation, inspectorRef, offlineCreatedAt, scheduledAt, startedAt, status, syncedAt

Exit code moved 0 → 1, with the offending field named and the actual declared properties listed. Mutation reverted; the tree is back to the reviewed state. The check detects precisely the defect the PR says it found.

3. Merging this makes development greener, not redder

development's Hydra Gates job is red right now, and its reason is this PR's subject:

[hydra-gates] 58 GATE(S) GREEN — but 1 of 59 APPLICABLE gates DID NOT RUN (named above).
[hydra-gates] GATES THAT DID NOT RUN: 24

gate-24 could not run because scripts/check-integration-parity.sh did not exist, and hydra-gates-require-full-coverage is set — so the absent check fails the job. This PR ships that script. The nine gate-53 findings stay classified PRE-EXISTING under the push-side scope, exactly as they are today.

Left for a separate decision

Those nine orphaned routes are a real latent defect in their own right — nine routes with no surviving menu entry reaching them (ADR-044 no-functionality-loss). They are not this PR's to fix and each one needs a call on whether the route should be re-linked or deleted. Raised separately rather than smuggled in here.

Merging.

@rubenvdlinde
rubenvdlinde merged commit f6c896e into development Aug 5, 2026
33 of 35 checks passed
@rubenvdlinde
rubenvdlinde deleted the fix/gate-24-integration-parity branch August 5, 2026 19:51
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