feat(gates): gate-99 manifest-l10n-coverage — the check nothing was doing - #604
Merged
Conversation
…oing
A manifest string with no key in l10n/nl.json renders its ENGLISH source to a
Dutch user, and nothing reports it.
The l10n extractor scans .vue/.js/.ts for `t('<app>', '...')` calls. The
manifest is not source it reads; it is DATA THE RENDERER WALKS. CnAppNav
translates `menu[].label`, CnPageHeader a page's `title` and `description`,
CnWalkthrough a step's `title` / `body` / `task` — each through the app's own
translate function, each looking up a key the extractor never saw. The fallback
is the English source, silently.
`check:l10n-js` DOES NOT COVER THIS AND CANNOT. It compares l10n/nl.json to the
generated l10n/nl.js, and a string absent from BOTH is perfectly in sync.
Measured on buildiq: both files at 1,045 keys, both missing `Flow`, check
green.
WHY THIS EXISTS. A fleet sweep translated ~3,900 manifest strings across 17
apps and left every one at zero missing. Within hours two had regressed, not
from old debt but from the next PR that added a string:
keepiq #448 "Registered by", "Requested"
buildiq #485 "Flow"
Both merged green. Only humaniq had a check that would have failed, because
someone hand-wrote one for that app. Porting it sixteen times is sixteen
chances to miss one, so it goes here instead.
VERIFIED AGAINST THE REGRESSION IT EXISTS FOR, not a synthetic fixture:
buildiq at the regressed commit rc=1, and the finding reads
"FAIL no nl.json key: Flow"
hermiq (swept clean) rc=0 over 150 strings
dossiq (swept clean) rc=0 over 392 strings
manifest with no strings checked 0 -> na, never PASS
crashed checker SKIP(wiring) "UNVERIFIED", never PASS
Skips `{{placeholder}}` values: they are substituted at render time and
translating one breaks the template. humaniq declares five. Skips `_meta`,
which is per-fragment provenance, never rendered. Reads src/manifest.d/*.json
as well as src/manifest.json — the fragments are merged at runtime via
require.context and shillinq has 87 of them.
Asserts nl only. Dutch is what this fleet ships; demanding every European
locale would make the gate unpassable rather than useful.
test_gate_acceptance_matrix.sh failed this branch, and it was right to. A gate in the declared inventory with no planted/clean bundle is a gate that can stop working quietly, which is the exact property that suite exists to deny. Its own header makes the argument: the 2026-08-11 sweep found defects only in gates that had no repo-shaped fixture, and that is a selection effect rather than a coincidence. The two arms differ in ONE thing: whether l10n/nl.json carries a key for the manifest's `Flow` label. The manifests are byte-identical, so a gate grading on manifest size, on key count, or on anything but which strings are covered misgrades this pair. `Dashboard` is translated in BOTH arms deliberately. Without it the planted catalogue would be near-empty, and a gate that fired on "catalogue looks unpopulated" rather than on the specific uncovered string would pass the fixture for the wrong reason. The subject is `Flow`, not a count. This gate exists because the failure is silent — a manifest string with no key falls back to its English source, so the page renders English and nothing errors. "1 uncovered string" tells nobody which label a Dutch user is reading in English. The pair is a real regression, not an invention. buildiq #485 added a Flows settings section on 2026-08-27, putting `Flow` in the manifest with no nl.json key, and it merged green: check:l10n-js compares nl.json to nl.js, both were at 1,045 keys, both missing the string, perfectly in sync. keepiq #448 did the same hours earlier with `Registered by` and `Requested`. clean/ maps `Flow -> "Flow"` rather than inventing a Dutch word: decidiq, dossiq, openregister, keepiq, portaliq, pipelinq and filinq all carry that mapping, and a fixture whose passing state disagreed would encode the wrong rule. Verified locally: the matrix suite now passes 193 assertions, including [manifest-l10n-coverage/planted] gate-99 FAIL and NAMES 'Flow' [manifest-l10n-coverage/clean] gate-99 PASS
rubenvdlinde
added a commit
that referenced
this pull request
Aug 28, 2026
…615) Two different gates both declared number 99: 10818: # GATE 99 — manifest-l10n-coverage (#604, 2026-08-27) 10997: # GATE 99 — demo-data-coverage (#590, earlier) demo-data-coverage held 99 first and COVERED-ELSEWHERE.md records it there, so manifest-l10n-coverage is the one that moves. WHAT IT BROKE. The declared inventory is built with ... | sort -n -k1,1 -s | awk '!seen[$1]++' first declaration of each NUMBER wins — so one of the two gates was absent from the inventory entirely, and `_declared_n` counted 80 where 81 gates exist. The coverage line then printed arithmetic that cannot be true. From a real pipelinq run: COVERAGE: 72 of 80 declared gates reported a result (9 not applicable; 72 of 71 applicable gates ran) 72 of 71. That is the duplicate, surfacing as a number nobody can act on. It also made `test_gate99_demo_data_scope.sh` unpassable: it greps the runner's gate-99 output and received manifest-l10n-coverage's NOT APPLICABLE verdict on all three arms, so `Package invariants` has been red on main — masking any real regression in this repository behind a failure nobody could fix. Verified: - `bash -n` on the runner - test_gate99_demo_data_scope.sh: ALL PASS (was 3 FAILURES) — all four arms - test_gate_acceptance_matrix.sh: 193 passed, 0 failed - the fixtured-gate count moves 80 -> 81, which is the shadowed gate reappearing in the inventory - no duplicate gate number remains - demo-data-coverage's five call sites are untouched Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
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.
A manifest string with no key in
l10n/nl.jsonrenders its English source to a Dutch user, and nothing reports it.The l10n extractor scans
.vue/.js/.tsfort('<app>', '…')calls. The manifest is data the renderer walks, not source it reads:Each goes through the app's own translate function, each looks up a key the extractor never saw, and the fallback is silent.
check:l10n-jsdoes not cover this, and cannotIt compares
l10n/nl.jsonto the generatedl10n/nl.js. A string absent from both is perfectly in sync.Measured on buildiq: both files at 1,045 keys, both missing
Flow, check green.Why this exists
A fleet sweep translated ~3,900 manifest strings across 17 apps and left every one at zero missing. Within hours, two had regressed — not old debt, just the next PR that added a string:
Registered by,RequestedFlowBoth merged green. Only humaniq had a check that would have failed, because someone hand-wrote one for that app. Porting it sixteen times is sixteen chances to miss one — so it goes here instead.
Verified against the regression it exists for
Not a synthetic fixture:
FAIL no nl.json key: Flowchecked 0→ na, never PASSDeliberate exclusions
{{placeholder}}values — substituted at render time; translating one breaks the template. humaniq declares five._meta— per-fragment provenance, never rendered.src/manifest.d/*.jsonas well as the main manifest: fragments are merged at runtime viarequire.context, and shillinq has 87.nlonly. Dutch is what this fleet ships; demanding every European locale would make the gate unpassable rather than useful.Full-tree rather than diff-scoped, for the reason gates 84, 93, 94, 95 and 96 give: the string is either covered or it is not, and a diff-scoped version reports clean on every PR that does not happen to touch the manifest.