Skip to content

fix: gate hygiene sweep — gate-29/46/51/53/54/58 - #776

Merged
rubenvdlinde merged 6 commits into
developmentfrom
greenday/procest-cfg
Aug 9, 2026
Merged

fix: gate hygiene sweep — gate-29/46/51/53/54/58#776
rubenvdlinde merged 6 commits into
developmentfrom
greenday/procest-cfg

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Summary

Fixes manifest/register-JSON/spec-anchor/e2e-hygiene gate findings on procest per the fleet gate-hygiene sweep (gate package 48c88ba1e0d049f8f38538c33e790d3e603c55d0).

  • gate-29 gitignore-then-commit: .phpunit.result.cache was both gitignored and tracked. Removed from the index.
  • gate-46 spec-anchor-existence: fixed all 4 dangling @spec targets (15 findings) — see gate-by-gate notes below.
  • gate-51 schema-property-titles: added real, CMMN-grounded descriptions to the 23 schema properties that were missing one (procest_register.json federated-activity/casetransfer audit trails, 70-cmmn-case-model.json plan-item/sentry structure).
  • gate-53 effective-manifest-crossref: fixed a genuine bug (SubstitutionMenu wrongly treated as a duplicate of the admin-only SubstitutionAdminMenu — they are different pages for different roles); deleted 8 dead removals entries that matched no menu item at all. 8 findings remain intentionally red — see below.
  • gate-54 relation-dialect: fixed case.decisions's $ref casing (verified against decidesk's real register: schema key Decision, slug decision — the $ref must address the slug) and added the missing $ref/x-external-register to case.vergunningaanvraagRef (verified against openregister/lib/Settings/dso_register.json). 2 findings remain intentionally red — see below.
  • gate-58 e2e-networkidle: replaced all 3 waitForLoadState('networkidle') calls with waitUntil: 'domcontentloaded' + deterministic element waits (reusing the repo's own established waitForContentReady technique from tests/e2e/visual/_visual-helpers.ts).
  • gate-52 custom-widget-ratchet: investigated, not a real defect — see verdict below.

gate-53 — per-removal decisions

src/menu-layout.json#removals originally had 17 entries. Verified with check_manifest_crossref.js directly (not just the wrapper script) before and after every edit.

Fixed (1):

  • SubstitutionMenu → SubstitutionSettingsrestored. Commit 01c1a8905 removed it as a "duplicate" of SubstitutionAdminMenu, but the two pages are genuinely different: SubstitutionSettings (/substitution) is the self-service page every user manages their own substitutions on; SubstitutionAdmin (/substitution-admin) is the coordinator-only console listing all substitutions. A non-coordinator user had no menu path to their own self-service page at all. Confirmed via each page's own manifest _note.

Deleted as dead cruft (8) — matched no merged menu entry at all (removal '<id>' matches no merged menu entry (stale removal), WARN-only, per direct tool output): BezwaarDecisions, BezwaarAdviceRequests, Subsidies, BesluitvormingGroup, LeverancierDashboard, MijnZaken, MijnNotificaties, Inspecties.

Left red, intentionally (8)BezwaarBeroepGroup, Bezwaren, Beroepen, SubsidiesGroup, CaseMap, Voorstellen, Advice, BesluitvormingAgenda. Each is a deliberate, extensively documented retirement across two sequential architecture changes, both still live and working:

  1. case-type-navigation (commit d6824aa20, openspec/changes/case-type-navigation/) replaced the standalone Bezwaar/Beroep/Subsidie nav with case-type children resolved onto the Cases page's own config.folderSidebar (verified present in src/manifest.json), and gave Cases a viewModes: [...,"map"] + mapConfig that supersedes the standalone CaseMap leaf.
  2. consume-decidesk-besluitvorming-leaf (commit d2df51b8b) replaced the standalone Besluitvorming nav (Voorstellen/Advice/Agenda) with a BesluitvormingLeafTab sidebar tab on CaseDetail (verified wired in src/registry.js).

Both replacements are real, shipped, and menu-reachable via Cases/CaseDetail — but gate-53's removals-invariant check only walks the static .menu tree (collectMenuRoutes) and has no way to see a runtime folderSidebar filter, a page viewMode, or a per-object sidebar tab as "reachability". I deliberately did not:

  • restore these as static top-level entries — that would reintroduce exactly the anti-pattern each change explicitly rejected in its own design doc ("Static per-case-type menu entries... rejected: freezes the taxonomy and drifts on every case-type add/rename"; "Keeping a parallel Besluitvorming nav in procest duplicates what now lives on the case detail");
  • delete the underlying routes — both changes state an explicit ADR-044 "hard invariant" to keep them deep-link/e2e routable.

I documented this in src/menu-layout.json#_meta.removalsCoverageNote (plain prose, not a gate suppression — it doesn't affect any check) so the next reader doesn't have to re-derive it.

WARN, verified legitimate, no change: widget case-kpis-hours on CaseDetail references register shillinq schema UrenRegistratie. shillinq is a real, separate Conduction app (a time-registration system) — this is a genuine, working cross-app OpenRegister integration the gate can't statically verify from a single-repo scope, not a defect.

gate-54 — verified against the real external registers

I have local checkouts of decidesk, pipelinq and openregister available and read their actual register JSON directly rather than guessing:

  • caseType.productsOrServices$ref: "product" — verified against pipelinq/lib/Settings/pipelinq_register.json: schema key and slug are both product. This is already correct and matches ADR-003 verbatim. Left unchanged, left red — this is gate-54's _global_schema_keys() only ever collecting the current app's own register files; a genuinely-external cross-app slug can never resolve from procest's side. Not a defect.
  • case.decisions$ref: "Decision"fixed to "decision". Verified against decidesk/lib/Settings/decidesk_register.json: the schema's JSON key is Decision but its slug is decision, and $ref resolves against the slug (check_relation_dialect.py's own documented reasoning: "a $ref is resolved against the slug, not against the key"). Now resolves.
  • case.vergunningaanvraagRef → added "$ref": "vergunningaanvraag" + "x-external-register": "dso". Verified against openregister/lib/Settings/dso_register.json: schema key and slug are both vergunningaanvraag, hosted in openregister's own dso mock register (dso_register.json, matching the pipelinq_register.json/decidesk_register.json file-name-implies-register-name convention already used by the other two x-external-register values in this file). Still red for the same single-repo-scope reason as productsOrServices — genuinely correct, gate structurally cannot verify a cross-register slug.

gate-52 verdict: artefact, not a real defect

Measured directly: full-repo run → gate-52 PASS. --scope-to-diff --base <repo-root-commit>base=0 head=3 delta=+3, FAIL. The ratchet is diffing the entire repository's history back to its root commit — every custom widget that has ever existed in the repo reads as "+3 growth" against a base of zero, because the root commit predates all of them. This is an artefact of the base ref choice (root commit), not evidence this PR added anything. No code change made.

Can-fail proofs

For gate-29, gate-46, gate-51, gate-53, gate-54 and gate-58 I reverted one representative fix, re-ran the relevant checker directly, confirmed the finding reappeared, then reapplied the fix and reconfirmed clean. (gate-52 had no code change, so no proof needed there.)

Test plan

  • check_spec_anchors.py — 0 findings across all touched files (was 15)
  • check_schema_property_meta.py — 0 findings (was 23)
  • check_manifest_crossref.js against the freshly-built effective manifest — 8 documented findings remain (was 9), gate-22-equivalent schema validation still PASS
  • check_relation_dialect.py — 2 documented findings remain (was 3)
  • check_js_call_sites.py --rule networkidle — 0 findings (was 3)
  • git ls-files | grep phpunit.result.cache — empty
  • Full run-hydra-gates.sh before/after comparison, no regressions on previously-PASS gates

…commit)

The file is gitignored but was still tracked from before the ignore rule
landed. Untrack it so the gate's tracked-file-behind-an-ignore-rule check
passes.
…chor-existence)

- woo-publication design.md: add a resolvable (Fallback) anchor tag to the
  "Fallback: catalog discovery is best-effort" heading
- DigidSamlAdapterInterface / LogDigidSamlAdapter: retarget from the
  archived zaakportaal-01-schema-foundation change to the canonical
  zaakportaal-mijngemeente spec's DigiD/eHerkenning requirement
- processMiningApi.js / processMiningShaping.js: retarget from the
  now-archived process-mining-bottlenecks change tasks to its synced
  canonical spec
- RedactionAssistDialog.vue / DocumentAssessmentTable.vue: retarget the 10
  woo-llm-anonymisation change-task references to the canonical spec's
  human-review requirement (the one still-valid #task-4-1 reference is
  left untouched)
…-property-titles)

23 schema properties across procest_register.json (caseFederatedActivity
entries, casetransfer custodyAuditTrail) and 70-cmmn-case-model.json (CMMN
plan-item / sentry structure: type, name, description, entry/exit criteria
id/standardEvent/caseFileEvent/field/operator/value) were missing a
human-friendly description. Added real, CMMN/domain-grounded descriptions
rather than placeholder text.
…lation-dialect)

- case.decisions: $ref "Decision" -> "decision". Verified against
  decidesk's real lib/Settings/decidesk_register.json: the schema's JSON
  key is "Decision" but its declared `slug` is "decision", and a $ref
  resolves against the slug (per the gate's own documented reasoning).
- case.vergunningaanvraagRef: was relation-shaped (format:uuid + a
  relation description) but had no $ref at all. Added
  $ref:"vergunningaanvraag" + x-external-register:"dso", verified against
  openregister/lib/Settings/dso_register.json (schema key and slug are
  both "vergunningaanvraag", hosted in openregister's own "dso" mock
  register) and matching the file-name-implies-register-name convention
  already used by the pipelinq/decidesk external refs in this file.

caseType.productsOrServices's $ref:"product" is left unchanged — verified
against pipelinq's real register (key and slug both "product", matching
ADR-003 verbatim). That finding and the (now correct but still
externally-unresolvable) vergunningaanvraagRef finding remain red: the
checker's schema-key candidate set is built only from the current app's
own register files, so a genuinely-external cross-app/cross-register slug
can never resolve from procest's side. Not a defect.
…ate-53 effective-manifest-crossref)

- SubstitutionMenu (route /substitution) was wrongly removed as a
  "duplicate" of SubstitutionAdminMenu (/substitution-admin) in 01c1a89.
  They are different pages for different roles: SubstitutionSettings is
  the self-service page every user manages their own substitutions on;
  SubstitutionAdmin is the coordinator-only console listing all
  substitutions. Non-coordinators had no menu path to their own
  self-service page. Restored.
- Deleted 8 removals entries that matched no merged menu entry at all
  (dead cruft from earlier portal/inspection retirements):
  BezwaarDecisions, BezwaarAdviceRequests, Subsidies, BesluitvormingGroup,
  LeverancierDashboard, MijnZaken, MijnNotificaties, Inspecties.

8 findings remain intentionally red (BezwaarBeroepGroup, Bezwaren,
Beroepen, SubsidiesGroup, CaseMap, Voorstellen, Advice,
BesluitvormingAgenda) — each is a deliberate, documented retirement
(case-type-navigation commit d6824aa; consume-decidesk-besluitvorming-leaf
commit d2df51b) superseded by a runtime surface (Cases' folderSidebar +
map viewMode, CaseDetail's BesluitvormingLeafTab) that this gate's static
.menu-tree walk cannot see. Restoring them as static entries would
reintroduce the exact anti-pattern both changes explicitly rejected;
deleting their routes would violate each change's own ADR-044 hard
invariant to keep them deep-link/e2e routable. Documented in
_meta.removalsCoverageNote for the next reader.
… e2e-networkidle)

networkidle never settles on Nextcloud (NC's notification poll keeps a
request in flight for the whole session), so all 3 waits silently burned
their entire timeout budget behind a .catch(() => {}). Replaced with
waitUntil:'domcontentloaded' + explicit element waits:
- deelzaak-case-email.spec.ts: the goto is immediately followed by real
  URL/text assertions with their own timeouts, so the networkidle wait
  was redundant on top of them.
- docs-screenshots.spec.ts (2 sites): ported the app's own established
  technique from tests/e2e/visual/_visual-helpers.ts's
  waitForContentReady() — wait on the actual content region (main /
  .app-content / #content-vue, or #content/.section for the NC core
  admin-settings page), then let any loading spinner clear.
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/procest @ 00003a2

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 ✅ 552/552
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-09 15:57 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 25d0a86 into development Aug 9, 2026
29 of 32 checks passed
@rubenvdlinde
rubenvdlinde deleted the greenday/procest-cfg branch August 9, 2026 19:56
rubenvdlinde added a commit that referenced this pull request Aug 9, 2026
PR #776 added "x-external-register" next to three cross-register $refs as part
of a gate-54 fix. Measured, it is inert in both directions:

  - OpenRegister does not read it: no occurrence of x-external-register
    anywhere under openregister/lib/.
  - gate-54 does not read it either: check_relation_dialect.py resolves a $ref
    against _global_schema_keys(), the union of schema KEYS and declared SLUGS
    across *register*.json and register.d/*.json in the same settings dir. There
    is no external-register branch.

Proof it changed nothing: gate-54 reports the same 2 findings with and without
the key.

The 'decision' $ref in that commit was a REAL fix and is untouched — "Decision"
-> "decision" resolves against decidesk's declared slug. Only the no-op key is
removed; an unread key introduced under a 'fix(relations)' subject reads like a
mechanism and is not one.

The remaining 2 findings STAY RED, honestly:
caseType.productsOrServices -> 'product' (Pipelinq's product register) and
case.vergunningaanvraagRef -> 'vergunningaanvraag' (the DSO register) both name
schemas owned by another app. Neither is in procest's 85-schema register set, so
the gate cannot resolve them and has no dialect that would let it. Both
properties are live — 6 and 7 code references respectively — so deleting them
would break working relations, and adding foreign schemas to procest's own
register would misstate ownership. Each property's description already documents
the owning register. This needs a cross-register $ref dialect in the gate, not a
change here.
rubenvdlinde added a commit that referenced this pull request Aug 9, 2026
…laced

Found while verifying the gate-53 rationale merged in #776, which states that
the standalone Besluitvorming nav (Voorstellen / Advice / Agenda) was 'replaced
with a BesluitvormingLeafTab sidebar tab on CaseDetail' and that the replacement
is 'live, wired, and menu-reachable'. The first half was true; the second was
not.

Measured:
  - src/components/tabs/BesluitvormingLeafTab.vue exists.
  - src/registry.js:412 registers it, in the same shape as CaseNotesTab.
  - CaseDetail.config.sidebar.tabs listed audit, version-history, notes,
    sharing, email — and nothing else.

So the component was resolvable by name and rendered by nobody. The
consume-decidesk-besluitvorming-leaf change removed three menu entries and
registered a tab it never placed: users lost the nav and gained no replacement.
Being in the registry is availability, not placement — the manifest decides what
renders, and it did not mention this tab.

The component's own header says it is meant to be surfaced 'through the
 path', which is exactly the wiring added here, and it
already handles decidesk being absent by rendering a quiet unavailable notice
rather than a broken tab. Icon 'Gavel' is registered in src/icons.js, so gate-60
stays green (28 manifests, 0 failures).

This does NOT move gate-53: still the same 8 findings, because that gate walks
the static .menu tree and a per-object sidebar tab is not in it. The point is the
lost surface, not the gate — the gate is what led me to look.
rubenvdlinde added a commit that referenced this pull request Aug 9, 2026
* fix(a11y): gate-32 semantic-controls — add keyboard equivalents to click targets

Non-semantic elements (div/li/a) with @click but no role/tabindex/keyboard
handler are unreachable and inoperable for keyboard and screen-reader users
(WCAG 2.1.1 / 4.1.2). Adds role="button" tabindex="0" plus real
@keydown.enter / @keydown.space handlers that call the same action as the
click handler — not decorative attributes.

Modal/dialog backdrop overlays (click-outside-to-close) get the same
treatment so the dismiss action has a keyboard equivalent.

The six "View all" widget footer links move from a bare <a @click.prevent>
to a real <a :href> pointing at the actual destination URL (computed via
generateUrl), which is both a genuinely better fix (native middle-click /
open-in-new-tab support) and clears the gate since a real href exempts an
<a> from the semantic-controls check.

TaskCreateDialog.vue additionally gets an aria-label on its icon-only close
button (gate-39 button-name, same file).

gate-32: 40 findings -> 0.

* fix(a11y): gate-39 button-name — add accessible names to icon-only buttons

Icon-only NcButton/button elements with no aria-label/title/text content
announce as just "button" to screen readers (WCAG 2.2 AA SC 4.1.2). Adds a
translated aria-label naming the actual action ("Edit {name}", "Delete
{name}", "Remove guard", "Close step configuration", etc.) rather than a
generic label, using the row's own name field for specificity where one
is available.

gate-39: 24 findings -> 0.

* fix(a11y): gate-34 window-confirm — replace window.confirm() with CnConfirmDialog

Native window.confirm() calls break Nextcloud theming and are inaccessible
to assistive tech (they bypass the app's dialog stack entirely). Replaces
all three call sites with @conduction/nextcloud-vue's shared CnConfirmDialog
(NcDialog-wrapped, its own file in the design-system package — importing it
does not trigger gate-13 modal-isolation since no <NcModal>/<NcDialog> tag
is written inline in these components).

Each site now opens the dialog on the original trigger, performs the delete
in a `@confirm` handler, and reports success/failure back via setResult()
instead of a bare console.error / silent catch.

These three files also carry their gate-43 table-headers (<th scope=>) and,
for DeelzaakList.vue, gate-45 prefers-reduced-motion fixes — committed
together since they land in the same file.

gate-34: 3 findings -> 0.

* fix(a11y): gate-43 table-headers — add scope= to <th> elements

<th> without a scope declaration leaves screen readers unable to associate
data cells with their column/row header (WCAG 2.2 AA SC 1.3.1). Adds
scope="col" to header-row <th> cells and scope="row" to the row-header
<th> in LhsMatrixAdmin.vue's severity/behavior matrix table.

ComplaintAnalyticsDashboard.vue's "By category" table had no <th> at all
(rule=table-without-th) — adds a real <thead> naming the two actual
columns (Category, Count) instead of a header-less table.

Self-closed spacer/action <th /> columns (row-actions menus, drag handles)
are left alone — they carry no accessible name so scope= on them is inert,
per the gate's own exemption for headers with no name.

gate-43: 27 findings -> 0.

* fix(a11y): gate-45 prefers-reduced-motion — add reduced-motion fallbacks

<style> blocks with a transition/animation and no
@media (prefers-reduced-motion: reduce) override force motion on users who
have asked the OS to minimise it (WCAG 2.2 SC 2.3.3). Adds a media query
per affected selector that sets transition/animation: none — a real
override, not an empty block satisfying the gate's text match.

Skeleton-loading shimmer keyframe animations and hover/click transitions
both get the treatment. The remaining ~17 files flagged by this gate had
their reduced-motion fix land in an earlier commit because they were also
touched for gate-32/39/43 in the same file.

gate-45: 25 findings -> 0.

* fix(a11y): gate-55 detail-page-discipline — use a registered manifest icon

AiOversightDetail's data widget declared icon "RobotOutline", which is not
in the shared icon registry (nextcloud-vue CnWidgetGrid/widgetIcons.js) and
renders the '?' fallback per ADR-062 rule 8. Swaps it for "ShieldCheckOutline"
— already in the registry and semantically fitting for an AI-oversight /
human-review audit entry (EU AI Act Art. 14 evidence).

gate-55: 1 finding -> 0.

The gate-16 spec-coverage regression this branch introduced (8 new frontend
methods/computed properties added while fixing gate-34/32 lacked @SPEC tags)
was fixed in the earlier gate-32/gate-34 commits by giving each new method
the same @SPEC tag as its sibling methods in the same file.

* fix(l10n): add the 14 strings the a11y pass introduced, EN and NL

The accessible names and the CnConfirmDialog copy added for gate-39/gate-34
were new source strings, and neither l10n file had them. That failed two CI
jobs the gate suite does not see — 'l10n coverage (en.json)' and
'quality / Frontend Check (test:l10n)' — and would have shipped raw English
keys to a Dutch-locale user.

en.json extracted with the repo's own 'test:l10n:write'; nl.json translated by
hand. 'Remove guard' is 'Voorwaarde verwijderen' rather than a literal
'bewaker' — in the transition editor a guard is a precondition on the
transition, not a person.

node tests/l10n/check-l10n.js now reports both halves OK; before this commit it
reported 14 keys missing from en.json and then 4 missing Dutch translations.

* fix(spec): retarget the signalering-widgets @SPEC tag at its canonical spec

All 36 tags pointed at openspec/changes/retrofit-2026-05-24-signalering-widgets/tasks.md,
a change directory that was archived to
openspec/changes/archive/2026-05-31-retrofit-2026-05-24-signalering-widgets.
The target has not existed for months, which is why gate-46 reports them as
unresolved. Retargeted at openspec/specs/signalering-widgets/spec.md — the
canonical home, which exists — per the rule that a @SPEC tag names a spec, never
a change dir.

30 of the 36 predate this branch; the a11y pass added 6 more by copying the
surrounding docblock. Diff-scoping means touching these files makes all of them
this PR's to answer for, and CI reported exactly that: 10 unresolved findings
from 1 distinct target.

Measured, not assumed: after this commit no @SPEC target under src/ named
signalering-widgets is missing from disk.

⚠️ NOT fixed here, and not this PR's: 48 OTHER @SPEC targets under src/ are also
missing — mostly archived retrofit-2026-05-* change dirs, plus two that name a
canonical spec which was never written
(openspec/specs/process-mining-bottlenecks/spec.md,
openspec/specs/realtime-updates-ui/spec.md). They are diff-scoped out today and
will surface on the next PR that touches each file. That is a separate sweep,
and the two missing canonical specs need writing, not retargeting.

* fix(register): drop x-external-register — nothing reads it

PR #776 added "x-external-register" next to three cross-register $refs as part
of a gate-54 fix. Measured, it is inert in both directions:

  - OpenRegister does not read it: no occurrence of x-external-register
    anywhere under openregister/lib/.
  - gate-54 does not read it either: check_relation_dialect.py resolves a $ref
    against _global_schema_keys(), the union of schema KEYS and declared SLUGS
    across *register*.json and register.d/*.json in the same settings dir. There
    is no external-register branch.

Proof it changed nothing: gate-54 reports the same 2 findings with and without
the key.

The 'decision' $ref in that commit was a REAL fix and is untouched — "Decision"
-> "decision" resolves against decidesk's declared slug. Only the no-op key is
removed; an unread key introduced under a 'fix(relations)' subject reads like a
mechanism and is not one.

The remaining 2 findings STAY RED, honestly:
caseType.productsOrServices -> 'product' (Pipelinq's product register) and
case.vergunningaanvraagRef -> 'vergunningaanvraag' (the DSO register) both name
schemas owned by another app. Neither is in procest's 85-schema register set, so
the gate cannot resolve them and has no dialect that would let it. Both
properties are live — 6 and 7 code references respectively — so deleting them
would break working relations, and adding foreign schemas to procest's own
register would misstate ownership. Each property's description already documents
the owning register. This needs a cross-register $ref dialect in the gate, not a
change here.

* fix(case): wire BesluitvormingLeafTab — it was registered but never placed

Found while verifying the gate-53 rationale merged in #776, which states that
the standalone Besluitvorming nav (Voorstellen / Advice / Agenda) was 'replaced
with a BesluitvormingLeafTab sidebar tab on CaseDetail' and that the replacement
is 'live, wired, and menu-reachable'. The first half was true; the second was
not.

Measured:
  - src/components/tabs/BesluitvormingLeafTab.vue exists.
  - src/registry.js:412 registers it, in the same shape as CaseNotesTab.
  - CaseDetail.config.sidebar.tabs listed audit, version-history, notes,
    sharing, email — and nothing else.

So the component was resolvable by name and rendered by nobody. The
consume-decidesk-besluitvorming-leaf change removed three menu entries and
registered a tab it never placed: users lost the nav and gained no replacement.
Being in the registry is availability, not placement — the manifest decides what
renders, and it did not mention this tab.

The component's own header says it is meant to be surfaced 'through the
 path', which is exactly the wiring added here, and it
already handles decidesk being absent by rendering a quiet unavailable notice
rather than a broken tab. Icon 'Gavel' is registered in src/icons.js, so gate-60
stays green (28 manifests, 0 failures).

This does NOT move gate-53: still the same 8 findings, because that gate walks
the static .menu tree and a per-object sidebar tab is not in it. The point is the
lost surface, not the gate — the gate is what led me to look.

* fix(register): express the two cross-app relations in the x-external-register dialect (gate-54 2 -> 0)

.github#286 landed after my earlier commit and adds exactly the notion that was
missing: check_relation_dialect.py now has _is_external_ref(), so a property
owned by another app's register is expressible.

Verified against a fresh clone of ConductionNL/.github@main, package
365fa31d09a26f980e6dc76cb0800575ef005a4e — `_is_external_ref` exists at
lib/check_relation_dialect.py:341. It did NOT exist at 651e5c5, which is why my
previous commit removed the key as inert. That measurement was correct at the
time and is now superseded.

The dialect is `x-external-register: <app>` on the property, carrying the BARE
identifier, and NO $ref — the checker still reports a property that declares
both, because OpenRegister resolves $ref within one register set and can never
reach another app's schema:

  caseType.productsOrServices  x-external-register: pipelinq, items lose $ref
  case.vergunningaanvraagRef   x-external-register: dso,      loses $ref

case.decisions is deliberately NOT given the key: `decision` is one of procest's
own 85 schemas and resolves locally, so it is not a cross-app reference. Adding
the key there would now be a finding rather than a fix.

Bidirectional proof, same checker and invocation both ways:
  as committed                              -> 0 findings
  $ref put back on vergunningaanvraagRef    -> "$ref 'vergunningaanvraag' does
                                               not resolve to a schema key in
                                               the register set (case-exact)"
  restored                                  -> 0 findings

Both properties stay live — 6 and 7 code references — and their descriptions
already name the owning register.
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