Skip to content

feat(gates): gate-99 manifest-l10n-coverage — the check nothing was doing - #604

Merged
rubenvdlinde merged 2 commits into
mainfrom
feat/gate-99-manifest-l10n-coverage
Aug 27, 2026
Merged

feat(gates): gate-99 manifest-l10n-coverage — the check nothing was doing#604
rubenvdlinde merged 2 commits into
mainfrom
feat/gate-99-manifest-l10n-coverage

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

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 data the renderer walks, not source it reads:

CnAppNav        →  menu[].label
CnPageHeader    →  a page's title and description
CnWalkthrough   →  a step's title / body / task

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-js does not cover this, and cannot

It compares l10n/nl.json to the generated l10n/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:

app PR strings
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:

case result
buildiq at the regressed commit rc=1, 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 0na, never PASS
crashed checker SKIP(wiring) "UNVERIFIED", never PASS

Deliberate exclusions

  • {{placeholder}} values — substituted at render time; translating one breaks the template. humaniq declares five.
  • _meta — per-fragment provenance, never rendered.
  • Reads src/manifest.d/*.json as well as the main manifest: fragments are merged at runtime via require.context, and shillinq has 87.
  • Asserts nl only. 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.

…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
rubenvdlinde merged commit 22e53a4 into main Aug 27, 2026
39 checks passed
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>
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