feat(gates): gate-96 manifest-copy-style — the voice rule, enforced - #581
Merged
Conversation
The Conduction voice already bans em-dashes. voice.md section 8 is explicit, and the `writing` skill's REVIEW mode uses a walkthrough `steps[0].body` em-dash as its worked example. The rule was never wrong. It shipped anyway. A sweep on 2026-08-26 found 126 violations across 12 apps: shillinq 47 · hrmq 15 · pipelinq 14 · opencatalogi 12 · decidesk 8 larpingapp 7 · openconnector 6 · scholiq 6 · docudesk 4 · hermiq 3 procest 2 · softwarecatalog 2 The one that surfaced it was a user opening dossiq and reading step 1 on screen: "a quick spin through case handling - we'll register a case". WHY A GATE WHEN THE RULE EXISTS. A skill is opt-in: it applies when an author chooses to load it, and manifest copy gets written by hand or by an agent that never invoked it. Nothing downstream reads the prose at all - `check:manifest` validates against a JSON Schema, and JSON Schema has no opinion about writing. So the rule lived in a document, the copy shipped past it, and the only detector was a human noticing. That is the shape a mechanical gate is for. NOT WALKTHROUGH-ONLY. My first sweep read only walkthrough steps and found 25. Reading every user-visible field found 126. The rest sit in setup-wizard steps, menu labels and widget empty states - "No open debtor invoices - everything is paid." A walkthrough-scoped checker would have called ten of those twelve apps clean. READS THE FRAGMENTS. `src/manifest.d/*.json` is merged into the manifest at runtime via require.context, so a checker that opens only `src/manifest.json` is blind to whatever they add. Eight fleet apps use them; shillinq has 87. Verified on real trees, all three exit paths: dossiq (2 known) rc=1, 2 findings, both the reported ones buildiq (clean) rc=0 a dir with no manifest rc=4, so the gate reports na, not a false pass crashed checker SKIP(wiring) "UNVERIFIED", never PASS That last one is the property worth having. `_helper_finished` requires the terminal "checked N manifest string(s)" line, so a checker that dies mid-run cannot report success over the findings it never reached. En-dashes between digits are allowed, per voice.md, so "2020-2024" passes while "RGS - Referentie GrootboekSchema" is caught. Backticked spans and URLs are excluded before the double-dash test, since `--flag` in an example is not prose. Full-tree rather than diff-scoped, for the reason gates 84, 93, 94 and 95 give: the violations are already in the tree, and a diff-scoped version reports clean on every PR that does not happen to touch the manifest.
The first version of this checker walked every key, which meant it read `_meta.description` — the per-fragment provenance block carrying spdx-license, spdx-copyright, change, adr and a note describing the fragment for developers. 38 shillinq fragments have one. That was 22 of shillinq's 47 findings, and every one of them was wrong. Worse than noise: acting on them would have rewritten build provenance as if it were user-facing prose, so the gate would go green while the tree got worse. A gate that manufactures work is not cheaper than no gate. Now skips any key beginning with `_`, which is the convention for internal blocks rather than a special case for this one name. Corrected fleet totals, measured after the change: before 126 across 12 apps after 97 across 12 apps shillinq 47 -> 24, scholiq 6 -> 1 The remaining findings are real. `CnPageHeader` renders a page's `config.description` under its title, so users currently read things like "REQ-REC-006: pre-close summary - matched count, unmatched GL/bank counts". Those are developer notes in a user-visible field, and the em-dash is the least of what is wrong with them. All three exit paths re-verified after the change: dossiq rc=1, buildiq rc=0, a directory with no manifest rc=4.
This was referenced Aug 26, 2026
Merged
Merged
rubenvdlinde
added a commit
to ConductionNL/opencatalogi
that referenced
this pull request
Aug 26, 2026
Found by gate-96 (manifest-copy-style, ConductionNL/.github#581). voice.md section 8: em-dashes are AI tells. All twelve were already genuine user copy, split between the six-step setup wizard and the getting-started tour. No internals leaked here, so the meaning is unchanged throughout and this is punctuation only. Two rewrites did more than swap a mark, because the dash was hiding a second sentence that deserved to be one: "Same as the 'Sync directories now' button in the admin settings - the periodic cron does this automatically over time, this step runs it once immediately." -> "Same as the 'Sync directories now' button in the admin settings. A scheduled job does this over time; this step runs it once, now." "cron" is also the wrong word for an admin reading a setup wizard. "A publication is a single thing you make open - a document, dataset or record." -> a colon, since what follows is a list in apposition, not a new clause. The schema-RBAC sentences kept their meaning intact. "OpenCatalogi has no separate 'publish' switch. Visibility is governed by schema-RBAC and the catalog's scope." is the sentence that stops someone hunting for a toggle that does not exist, and it is worth more than the dash it used to carry. Verified: gate-96 0 findings over 115 strings, check:manifest PASS (Ajv 0 errors), JSON re-parses.
rubenvdlinde
added a commit
to ConductionNL/shillinq
that referenced
this pull request
Aug 26, 2026
…tes (#1256) Found by gate-96 (manifest-copy-style, ConductionNL/.github#581). Two separate problems shared one detector. THE PUNCTUATION. voice.md section 8 bans em-dashes. Seven strings carried one, including the walkthrough's opening line and both dashboard empty states ("No open debtor invoices - everything is paid."). THE REAL PROBLEM. The other seventeen were internal developer notes sitting in a field users read. `CnPageHeader` renders a page's `config.description` underneath its title, so people opening these pages were shown: "Approve the submitted requisition. Server-side gated by BudgetBlocker::canCommit - budget availability or an override-mandate (REQ-REQ-003)." "REQ-REC-006: pre-close summary - matched count, unmatched GL/bank counts, total variance, and sign-off comment field." "Per-category tax summaries feeding the annual filing. Converted from the never-rendered type=report (no such page type is registered) to a plain index - the KPI dashboard variant needs a dedicated renderer first." That last one describes a refactor decision to a bookkeeper looking for their tax totals. Stripping the dash would have made all three PASS the gate while leaving them wrong, so they are rewritten as copy instead: "Approve the submitted request. Approval requires available budget, or a mandate that overrides it." "A summary before closing: how many items matched, how many are still unmatched on either side, and the total variance. Sign-off is required before this reconciliation can be verified." "Tax totals per category, ready for the annual filing." Requirement ids (REQ-*), class and method names, and internal chain/slice references are gone from every user-facing string. They remain where they belong, in `_meta` and in the specs. NOT TOUCHED: the `_meta.description` blocks. Those are per-fragment provenance (spdx, change, adr) and are never rendered. The gate's first version flagged 22 of them here, which is why it now skips underscore-prefixed keys. Verified: gate-96 reports 0 findings over 6,554 strings, `check:manifest` passes, and all 88 JSON files re-parse.
test_gate_empty_scope_never_passes.sh failed this gate, correctly: FAIL - gate(s) 96 reported PASS over a scope that excludes every planted defect - this is the .github#374 defect. Each of them printed the same word as a gate that read the whole tree and found it clean. That is exactly right. My first version returned exit 4 only when the repo ships no src/manifest.json at all. A manifest that exists but declares no user-visible string produced `checked 0` and exit 0, which the shell block turned into PASS - a gate announcing "clean" having inspected nothing. Now both shapes route to `_skip_empty_scope`, which says what was enumerated and states plainly that nothing was inspected so this is NOT a pass. The condition reads the checker's own `checked N` line rather than trusting the exit code, because the count is the measurement and the exit code is only a summary of it. The test message ends "do NOT add it to _ARM6_ALLOWED unless you can state what it computed." I did not. An allowlist would have made this green while leaving the gate unable to tell "no violations" from "no input", which is the whole failure this suite exists to prevent. Verified after the change: a manifest with pages:[] and menu:[] gives `checked 0` and takes the na branch; openbuild gives checked=49 findings=0 and still PASSES; hrmq (654) and pipelinq (600) still FAIL with their real counts. Empty scope and clean tree no longer print the same word.
rubenvdlinde
added a commit
to ConductionNL/pipelinq
that referenced
this pull request
Aug 26, 2026
Found by gate-96 (manifest-copy-style, ConductionNL/.github#581). voice.md section 8: em-dashes are AI tells, replaced with a period, a comma, or a colon. Unlike shillinq and humaniq, every string here was already real user copy. Nothing leaked internals, so this is punctuation only and the meaning is unchanged in all thirteen. They span the setup wizard, the whole getting-started tour, four empty states and one nav description. Each rewrite picked the mark the sentence actually wanted rather than swapping in a comma everywhere: period where the clause after the dash was a complete thought "Add the product or service you sell. We'll put it on the quotation later." colon where it introduces an explanation "a signable quotation IS the contract: one record, no duplication." commas where the dashes were a parenthetical pair "a small linked demo dataset of clients, leads, requests and contactmomenten, so lists ..." Verified: gate-96 0 findings over 600 strings, check:manifest PASS (51 pages, 25 menu items), both JSON files re-parse.
This was referenced Aug 26, 2026
rubenvdlinde
pushed a commit
that referenced
this pull request
Aug 26, 2026
…t untested TWO THINGS HAPPENED ON MAIN while this branch was open. 1. GATE NUMBER COLLISION. #581 landed `manifest-copy-style` on 96, which this branch had already claimed. Renumbered to 97 — in the runner, in the fixture's expect.conf and in the empty-scope suite's ARM 6 allowlist, all three, because a gate id is a citation key: it appears in COVERAGE lines, in `_ARM6_ALLOWED`, in expect.conf files and in PR bodies, and each is a pointer that only works while the number resolves to one thing. That is the same argument gate-95 (adr-number-collision) makes about ADR numbers, one merge earlier. 2. THE ACCEPTANCE RATCHET WENT RED ON MAIN, and not because of anything here. #581 added its gate to the runner with no fixture and no UNCOVERED.md row, so `test_gate_acceptance_matrix.sh` fails on a bare clone of main — verified on one before touching anything. In the suite's own words: "a gate can be added to the runner and never tested; this is that moment." The ratchet worked; nothing had closed the loop it opened. So this adds the missing bundle. Its two arms differ in two strings in two different FILES — an em-dash in `src/manifest.json`, a double-dash in a `src/manifest.d/` fragment — because fragments are merged at runtime and a checker reading only the base manifest would be blind to eight fleet apps' copy (shillinq alone has 87 fragments). A single-file fixture would have passed such a checker and taught us nothing. The clean arm is deliberately NOT dash-free: it keeps "Reporting covers 2020–2024", the en-dash-between-digits that voice.md permits. Without it, a checker that banned every dash outright would pass the clean arm and the rule's only exception would be untested. Acceptance matrix 190/190, 70 of 77 gates fixtured, ratchet intact. Empty-scope suite ALL PASS.
rubenvdlinde
added a commit
to ConductionNL/humaniq
that referenced
this pull request
Aug 26, 2026
…178) * fix(manifest): rewrite 15 user-visible strings that leaked internals Found by gate-96 (manifest-copy-style, ConductionNL/.github#581). The dashes are what the gate detects; they are not the interesting part. `CnPageHeader` renders a page's description under its title, so these were on screen for whoever opened the page: "Application pipeline (recruiting-ats-basic): new -> screening -> interview -> offer -> hired/rejected. The status filter is the MVP pipeline surface -- every stage is a filter value; a kanban board is an nc-vue widget follow-up (design D7)." "Submitted leave requests from your team awaiting approval or rejection (mss-team-scope) -- based on the managerUserId link maintained by HR/back office." "Run-to-run payroll mutation reports ... generated through the \"Mutation report\" action on a payroll run or through occ humaniq:payroll:mutations --persist." A manager approving leave does not need to know the field is called managerUserId, and telling them about an occ command they cannot run is worse than saying nothing. Rewritten as copy: "Applications as they move from new, through screening, interview and offer, to hired or rejected. Filter by status to see one stage at a time." "Leave requests from your team waiting for your decision. Your team is who reports to you according to the records HR maintains." "What changed between two payroll runs: who joined, who left, and how the total wage cost moved." Three carried real domain meaning that survived the rewrite rather than being cut: the loonbeslag floor ("never reduces net pay below the protected earnings floor"), the BHV standard (art. 15 asks for a judgement, not a headcount), and the stagiair/BBL distinction. Those are the sentences a user actually needs. The integrations page kept its most important sentence too: it RECORDS access, it does not grant it. Losing that would have been a security-relevant misreading. Design decision references, change slugs and occ invocations are gone from user-facing strings. `_meta` blocks are untouched: they are provenance, never rendered, and the gate skips them. Verified: gate-96 0 findings over 654 strings, check:manifest PASS (Ajv 0 errors, schema 2.23.0), all 12 JSON files re-parse. * fix(l10n): translate the 15 rewritten manifest strings The copy rewrite in the previous commit broke `check:l10n`, and the check was right to fail: humaniq's validate-l10n-parity.js asserts that EVERY manifest string has a key in both en.json and nl.json. Changing the English orphaned fifteen translations, so those pages would have rendered English to a Dutch user. That is exactly the bug this whole thread started from. Worth recording that humaniq ALREADY enforces this and dossiq does not. The fleet-wide gap I described earlier is real but not universal: this app checks manifest strings against the catalogues, which is why the regression surfaced here within minutes instead of being noticed on screen months later. Dutch written per string rather than machine-mapped, keeping the domain terms Dutch users expect: gebruikelijkloonregeling not "customary wage standard" beslagvrije voet the protected earnings floor RI&E / Arbowet art. 15 the BHV assessment basis leerarbeidsovereenkomst the BBL contract the page tells you is elsewhere TWK kept, it is the term on the payslip The old Dutch carried the same defects as the old English: em-dashes, and "(mss-team-scope) - gebaseerd op de managerUserId" verbatim in the translation. Those are gone on both sides now, not just in English. Then rebuilt l10n/*.js. `check:l10n-js` failed until that ran, and it is the check that matters: the .json is source, the .js is what the browser loads. A catalogue correct in JSON and stale in JS is a translation nobody receives. Verified: check:l10n PASS ("en/nl key sets are identical, en is identity-mapped, every manifest and t() string is covered, and no Dutch literal survives as a source key"), check:l10n-js PASS, check:manifest PASS, check:seed-refs PASS, check:schema-l10n PASS. 1841 -> 1856 keys per catalogue.
rubenvdlinde
added a commit
to ConductionNL/larpinq
that referenced
this pull request
Aug 26, 2026
* fix(manifest): drop 6 em-dashes from setup and tour copy Found by gate-96 (manifest-copy-style, ConductionNL/.github#581). Three setup-wizard steps and three tour steps. All were already real user copy, so the meaning is unchanged and this is punctuation only. One took a colon rather than a period, because what follows is an apposition and not a new clause: "Create the Larpinq register and schemas in OpenRegister: the data store for characters, players, abilities, skills, items ..." One had a parenthetical pair of dashes, which becomes commas: "Manage your live-action roleplay world, with characters, players, items and events all in one place." CHECKED, NOT ASSUMED: humaniq's sibling fix broke `check:l10n` there, because that app requires every manifest string to have an en/nl key and rewriting the English orphaned the Dutch. This app does not couple the two that way: test:l10n, check:l10n-js and check:schema-l10n all PASS unchanged. Verified: gate-96 0 findings over 220 strings, check:manifest PASS, test:l10n PASS, check:l10n-js PASS, check:schema-l10n PASS, prettier clean. * docs: correct the "prettier clean" claim in the previous commit The previous commit's verification list ends "prettier clean". That is wrong. `npx prettier --check src/manifest.json` WARNS on this file. I ran it, saw the warning, and wrote the opposite into the commit message. The correct statement, checked afterwards against the base blob rather than just my working copy: - src/manifest.json is prettier-dirty, and was ALREADY dirty on development before this branch existed. This change did not cause it. - It is out of CI's scope regardless: the `format` script is `prettier --check "**/*.{js,ts,vue,css,scss}"`, which matches no .json. Left as found rather than reformatting ~900 lines inside a copy-fix PR, which would bury six one-line changes in a whole-file reindent. An empty commit rather than an amend: the branch is already pushed, and the force-push guard is right that rewriting a shared branch is worse than an extra commit. Everything else in the previous message stands.
rubenvdlinde
added a commit
to ConductionNL/filinq
that referenced
this pull request
Aug 26, 2026
Found by gate-96 (manifest-copy-style, ConductionNL/.github#581). Three getting-started tour steps, all already real user copy, so the meaning is unchanged and this is punctuation only. The welcome step also loses a comma splice the dash was covering for. "we'll set up a template and a signing request together so you can see how the pieces fit. You'll add each record yourself." was two sentences pretending to be one; it now joins with "and" instead. CHECKED, NOT ASSUMED: humaniq's sibling fix broke `check:l10n` there, because that app requires every manifest string to carry an en/nl key and rewriting the English orphaned the Dutch. This app has no such coupling: test:l10n and check:schema-l10n both PASS unchanged. Verified: gate-96 0 findings over 50 strings, plus every frontend check this repo's CI actually runs (check:manifest, test:l10n, check:schema-l10n) PASS.
rubenvdlinde
added a commit
to ConductionNL/hermiq
that referenced
this pull request
Aug 26, 2026
Found by gate-96 (manifest-copy-style, ConductionNL/.github#581). All three take a colon rather than a period, because in each case what follows the dash is a list or an explanation of the clause before it, not a new thought: "Run metrics across your agents: success rate, latency and token usage." "Use New to create one: give it a prompt, pick a model, and add optional tools and skills." The Ollama step is the exception and takes a period, because "Agents can't run without a reachable model" is a consequence and deserves to land as its own sentence. It is the line that explains why the step is not optional. CHECKED, NOT ASSUMED: humaniq's sibling fix broke `check:l10n` there, because that app requires every manifest string to carry an en/nl key. This app has no such coupling. Verified: gate-96 0 findings over 150 strings, check:manifest PASS, test:l10n PASS, check:schema-l10n PASS.
rubenvdlinde
added a commit
to ConductionNL/integriq
that referenced
this pull request
Aug 26, 2026
Found by gate-96 (manifest-copy-style, ConductionNL/.github#581). All six are dashboard chart titles of the same shape: "Outgoing calls - daily" -> "Outgoing calls per day" "Outgoing calls - by hour" -> "Outgoing calls per hour" "Job executions - daily" -> "Job executions per day" "Job executions - by hour" -> "Job executions per hour" "Synchronization runs - daily" -> "Synchronization runs per day" "Synchronization runs - by hour"-> "Synchronization runs per hour" "per day" rather than "daily" so the pair reads consistently: "daily" and "by hour" were two different grammatical shapes for the same idea, and a chart title is read next to its sibling. CHECKED, NOT ASSUMED: humaniq's sibling fix broke `check:l10n` there, because that app requires every manifest string to carry an en/nl key. Here `check:l10n` also exits non-zero, but it does so IDENTICALLY on development, with zero missing-key findings on either side, so it is pre-existing and unrelated to these six strings. It is also not in this repo's frontend-checks list, which runs build, check:specs, test:l10n, format and check:schema-l10n. Verified: gate-96 0 findings over 229 strings, check:manifest PASS, test:l10n PASS, check:schema-l10n PASS.
rubenvdlinde
added a commit
to ConductionNL/stackiq
that referenced
this pull request
Aug 26, 2026
Found by gate-96 (manifest-copy-style, ConductionNL/.github#581). Two getting-started tour steps, both already real user copy. The second takes a colon rather than a period, because the clause after the dash explains what "the heart of your catalogue" means and belongs to the same sentence: "Organisations are the heart of your catalogue: they own the contracts, modules and compliance records you track." The welcome step also loses the comma splice the dash was hiding, joining with "and" instead of leaving two sentences fused. Verified: gate-96 0 findings over 153 strings, check:manifest PASS, test:l10n PASS, check:schema-l10n PASS.
rubenvdlinde
added a commit
to ConductionNL/learniq
that referenced
this pull request
Aug 26, 2026
Found by gate-96 (manifest-copy-style, ConductionNL/.github#581). The em-dash is what the gate detects; it is not what was wrong with the sentence. before: "No sub-competencies found - this is a leaf node (learning outcome)." after: "No sub-competencies. This is a learning outcome, the lowest level in the tree." "leaf node" is a data-structure term. A teacher looking at a competency that has nothing beneath it needs to know that is normal and expected, not that the record occupies a particular position in a graph. The parenthetical carried the only word that meant anything to them, so it is now the subject. "found" also goes: an empty state saying "none found" hints at a failed search, when the accurate statement is that there are none. Verified: gate-96 0 findings over 1,274 strings, check:manifest PASS, check:schema-l10n PASS.
rubenvdlinde
added a commit
to ConductionNL/decidiq
that referenced
this pull request
Aug 26, 2026
* fix(manifest): drop 8 em-dashes from user-visible copy Found by gate-96 (manifest-copy-style, ConductionNL/.github#581). Two tour steps, four sidebar-integration descriptions and two empty states. All were already real user copy, so the meaning is unchanged. Four of them said "surface on the body", which is our word for it and not the reader's. Those now say "appear on the page itself", paired with "stay in the sidebar" so the sentence actually tells you where to look: "External integrations linked to this meeting. The Action items board (Deck), Discussion (Talk), files and notes appear on the page itself; linked articles, tags and the audit trail stay in the sidebar." The empty states lost "the public Participation surface" for "the public Participation page", same reason. CHECKED, NOT ASSUMED: humaniq's sibling fix broke `check:l10n` because that app asserts every manifest string has an en/nl key, so rewriting the English orphaned its Dutch. decidiq is different, and I verified rather than guessing: `test:l10n` PASSES here, and `test:l10n:parity` fails identically on development and on this branch (+553 missing on both, none of them mine). That 553 is real pre-existing translation debt, but it is not in this repo's frontend-checks list and this change neither causes nor worsens it. Verified: gate-96 0 findings over 943 strings, check:manifest PASS, test:l10n PASS, check:l10n-js PASS, check:schema-l10n PASS. * test(e2e): match the rewritten integration-surface copy The copy fix in the previous commit changed this description from "... linked Emails, files and tasks surface on the body; notes, tags and the audit trail remain in the sidebar." to "... Linked emails, files and tasks appear on the page itself; notes, tags and the audit trail stay in the sidebar." and `integration-surfaces.spec.ts:267` asserted the old wording verbatim, so it failed. 1 failed, 141 passed. My change, correctly caught. Narrowed the pattern to `/linked emails, files and tasks/i` rather than re-pinning the new sentence in full. The assertion's job, per its own comment, is to prove the AGENDA-ITEM page's copy rendered and not another surface's. The discriminator is the LIST of surfaces, which really is unique per page: meeting -> Deck + Talk + files + notes dossier -> emails + Deck + files agenda -> emails + files + tasks The verb after it ("appear on the page itself") is shared by all three, so pinning it added no discriminating power while guaranteeing this test breaks again on the next copy edit. Verified both surviving patterns still match EXACTLY ONE description each, so the test can still fail if the wrong surface renders. A pattern that matched two would have gone green while testing nothing. Line 206's pattern needed no change: it is case-insensitive and stops before the words that moved, which is why only one of the two failed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Conduction voice already bans em-dashes.
voice.mdsection 8 is explicit, and thewritingskill's REVIEW mode uses a walkthroughsteps[0].bodyem-dash as its worked example. The rule was never wrong.It shipped anyway — 126 violations across 12 apps:
What surfaced it was a user opening dossiq and reading step 1 on screen: "a quick spin through case handling — we'll register a case".
Why a gate, when the rule already exists
A skill is opt-in. It applies when an author chooses to load it, and manifest copy gets written by hand or by an agent that never invoked it. Nothing downstream reads the prose at all:
check:manifestvalidates against a JSON Schema, and JSON Schema has no opinion about writing.So the rule lived in a document, the copy shipped past it, and the only detector was a human noticing on screen.
Not walkthrough-only — this is why the count is 126, not 25
My first sweep read only walkthrough steps and found 25. Reading every user-visible field found 126. The rest are in setup-wizard steps, menu labels and widget empty states:
A walkthrough-scoped checker would have called ten of those twelve apps clean.
Reads the fragments
src/manifest.d/*.jsonis merged into the manifest at runtime viarequire.context. A checker that opens onlysrc/manifest.jsonis blind to whatever they add. Eight fleet apps use fragments; shillinq has 87.Verified on real trees — all four paths
rc=1, 2 findings, both the reported onesrc=0rc=4→ gate reportsna, not a false passSKIP(wiring)"UNVERIFIED", never PASSThat last row is the property worth having.
_helper_finishedrequires the terminalchecked N manifest string(s)line, so a checker that dies mid-run cannot report success over findings it never reached.Deliberate exclusions
2020–2024passes whileRGS – Referentie GrootboekSchemais caught.--flagin an example is not prose.Full-tree rather than diff-scoped, for the reason gates 84, 93, 94 and 95 give: the violations are already in the tree, and a diff-scoped version reports clean on every PR that does not happen to touch the manifest.
Note for rollout: this gate will fail those 12 apps until their copy is fixed. Fixes are going out per app — dossiq is done (ConductionNL/dossiq#1373, 0 findings over 390 strings).