Skip to content

feat(gate-107): the same seven items at the bottom-left of every app - #668

Merged
rubenvdlinde merged 3 commits into
mainfrom
feat/gate-107-app-chrome
Sep 3, 2026
Merged

feat(gate-107): the same seven items at the bottom-left of every app#668
rubenvdlinde merged 3 commits into
mainfrom
feat/gate-107-app-chrome

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Implements ADR-114 (ConductionNL/hydra#643).

Why

Seven things belong at the bottom-left of every Conduction app. CnAppNav draws two itself (Personal settings, Admin settings, ADR-079). The other five are the app's own: Documentation, Store, Reports and Features & roadmap in footer, Flows in the settings foldout.

Measured 2026-09-03 across the 21 core apps, from the effective manifest: one app carries all seven, and it is dossiq. Reports is in 3 of 19, Store in 4 of 19.

Why a new gate, when four already read this menu

Every one of them is presence-blind, which is how fifteen apps ship no Store while every gate stays green.

gate what it does what it cannot ask
60 icon-vocabulary validates the icon on an entry that exists why there is no entry
62 store-plane / 63 settings-surface judge the names of entries that exist same
104 reports-one-page its own header: "does NOT require an app to HAVE a reports page" absent ⇒ NOT APPLICABLE
53 effective-manifest-crossref a declared entry resolves why it was never declared

navigation-budget (ADR-097 D8) was specified and never implemented.

How it decides

By page type, not by label. launchpad and humaniq ship i18n keys as labels (launchpad.menu.dashboards), dossiq's Reports entry has the id AnalyticsGroup, and a label may be translated. Four of the five items are resolved by following entry.route to a page and reading its type. Documentation has no page behind it and is matched on id/label as a documented exception.

The walk descends into children[]. ADR-110's own fleet audit walked only top-level entries, read integriq's nested Flows as absent, and had to withdraw the claim inside the ADR. The clean fixture arm nests Flows under a group for exactly that regression.

The ratchet (ADR-114 D8)

  • Blocking now: Documentation, Features & roadmap, Flows, any of the five in the wrong section, and a hand-rolled /settings/admin/<appId> link.
  • WARN: Store and Reports, until their rollouts land. Two constants at the top of the checker are the promotion, and they move both the presence and the placement rule, so no app is hard-failed for misplacing a Store while fifteen are merely warned for having none.
  • NOT APPLICABLE, never PASS, for an app with no pages. The test is the measurement, not a list of names: ADR-110 named planninq Tier-0 while it shipped pages: [], it now ships nine, and the exemption had quietly lapsed.
  • Diff-scoped: an unopened scope is na, not a pass.

Fixtures

Three rows, one per direction of the rule, because the driver grades a verdict plus one named subject. Store and Reports are present and correct in both arms deliberately, so promoting the ratchet later does not silently change what these rows prove. Anti-widening in the clean arm: a Flow runs index in main whose label contains "flow", and the real Flows entry nested inside a group.

Verification

test_gate_acceptance_matrix.sh: 232 passed, 0 failed, 80 of 87 gates fixtured, gate-107 among them. Run against the live fleet the gate reproduces the audit exactly, including integriq/hermiq/openregister's documented Flows-in-main exceptions and shillinq's hand-rolled admin link.

🤖 Generated with Claude Code


Update after running it against the live fleet

Two defects the corpus found before this merged, both of which would have shipped a gate that reds an app for being right:

  • The page type was not the only correct implementation. The first version tested page.type alone and called openregister's Features & roadmap MISSING. It is type: "custom" at /features-roadmap deliberately ("the built-in roadmap page is not adopted in this shell-swap change"), and shillinq's reports catalogue is custom for a reason gate-104's own header records. isSurface() now accepts the canonical route path as well as the type.
  • The footer order is relative, not absolute. openregister runs 1/2, pipelinq 160/200/230, keepiq 80/85 — all correctly sequenced. Hardcoding 90/92/95/100 would have red-flagged four apps and changed no pixel.

Both are fixture controls now: the clean arm's roadmap is a custom page at the canonical path (reverting isSurface reds it, verified 0 → 1 → 0), and the planted arm's Reports sits below Features & roadmap so the order row grades a real defect.

Also fixed: gate-107 was the only silent gate in the package on a repo with no manifest, which failed the entry-point invariant suite under --require-full-coverage (69/1) and the published-location install job. It now declares na out loud, the way gate-60 does. 70 passed, 0 failed.

Fleet state: all 19 manifest-driven apps are at findings=0, thematiq NOT APPLICABLE. The eight app PRs this gate depends on are merged. Acceptance matrix: 235 passed, 0 failed.

ADR-114. Measured 2026-09-03 across the 21 core apps: ONE carries the whole
chrome, and it is dossiq. Reports is in 3 of 19, Store in 4 of 19.

WHY A NEW GATE, WHEN FOUR ALREADY READ THIS MENU. Every one of them is
presence-blind. gate-60 validates the icon on an entry that exists. gates 62
and 63 judge the names of entries that exist. gate-104 says in its own header
that it does NOT require an app to HAVE a reports page and answers NOT
APPLICABLE when there is none. gate-53 checks a declared entry resolves, which
cannot ask why an entry was never declared. Fifteen apps ship no Store at all
and every one of those gates is green.

Detection is by PAGE TYPE, not by label: launchpad and humaniq ship i18n keys
as labels, dossiq's Reports entry has the id AnalyticsGroup, and a label may be
translated. Only Documentation has no page behind it and is matched on
id/label, which the checker documents as the exception.

The walk descends into children[]. ADR-110's own fleet audit walked only
top-level entries, read integriq's nested Flows as absent, and had to withdraw
the claim inside the ADR. The clean fixture arm nests Flows under a group for
exactly that regression.

RATCHET, per ADR-114 Decision 8. Documentation, Features & roadmap, Flows and
a hand-rolled Admin settings link block from day one. Store and Reports WARN
until their rollouts land; two constants at the top of the checker are the
promotion, and they move BOTH the presence and the placement rule so no app is
hard-failed for misplacing a Store while fifteen are merely warned for having
none.

NOT APPLICABLE, never PASS, for an app with no pages (ADR-040 Tier-0). The test
is the measurement, not a list: ADR-110 named planninq Tier-0 while it shipped
pages:[], it now ships nine, and the exemption had quietly lapsed.

Fixtures: three rows, one per direction of the rule, because the driver grades
a verdict plus ONE named subject. Store and Reports are present and correct in
BOTH arms on purpose, so promoting the ratchet later does not silently change
what these rows prove.

Acceptance matrix: 232 passed, 0 failed, 80 of 87 gates fixtured.
Conduction Release Bot added 2 commits September 3, 2026 11:58
The runner's coverage accounting treats a gate that simply never spoke as DID
NOT RUN, by design: silence counts AGAINST coverage, and a gate stops counting
only by declaring itself not-applicable out loud. gate-107 was guarded by a
bare `if [ -f src/manifest.json ]` with no else arm, so on a repo with no
manifest it was the only silent gate in the package.

Two CI jobs caught it, both correctly:

  - the entry-point invariant suite exited 98 under --require-full-coverage on
    a fixture where every gap is legitimately not-applicable (69 passed, 1
    failed)
  - the published-location install job: "73 not-applicable + 1 unrun gates are
    counted, but only 73 said so on their own line"

Adds the else arm gate-60 already has. Reproduced locally first (rc=98, gate-107
the only name in the DID NOT RUN list), then confirmed rc=0 after.

Entry-point tests: 70 passed, 0 failed.
…orce the ORDER not the numbers

Both found by pointing the gate at all 19 manifest-driven apps before merging
it. Either would have shipped a gate that reds an app for being right.

1. PAGE TYPE WAS NOT THE ONLY CORRECT IMPLEMENTATION. The first version tested
   `page.type` alone and called openregister's Features & roadmap MISSING. It
   is type:"custom" at /features-roadmap on purpose, and its manifest says so:
   "the built-in roadmap page is not adopted in this shell-swap change".
   shillinq's reports catalogue is custom for a reason gate-104's own header
   records. isSurface() now accepts the canonical route PATH as well as the
   type: the path is the half of the contract deep links and e2e specs already
   address, and gate-53 holds the menu-to-page join independently.

2. THE FOOTER ORDER IS RELATIVE. ADR-114's table names 90/92/95/100, but
   openregister runs 1 and 2, pipelinq 160/200/230 and keepiq 80 and 85, and
   every one of them is already in the right SEQUENCE. Hardcoding the numbers
   would red four apps whose footers read correctly and change no pixel. The
   gate checks the order the user can see; the numbers are what a NEW entry
   takes.

Fixtures carry both as controls. The clean arm's roadmap is type:"custom" at
the canonical path, so reverting isSurface() to a type-only test reds it
(verified: findings 0 -> 1 and back). The planted arm's Reports sits at 200,
below Features & roadmap, so the order row grades a real defect while
pipelinq's 160/200/230 still passes on the live fleet.

Fleet after the eight app PRs merged: 19 of 19 at findings=0, thematiq NOT
APPLICABLE. Acceptance matrix: 235 passed, 0 failed, all four app-chrome rows
graded on both arms.
@rubenvdlinde
rubenvdlinde merged commit b4622cb into main Sep 3, 2026
39 of 40 checks passed
@rubenvdlinde
rubenvdlinde deleted the feat/gate-107-app-chrome branch September 3, 2026 11:15
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