The root build files keep the reason and drop the speaker

package.json's gate descriptions, vite.config.js, the build config's
comment keys, capacitor.config.json and .gitignore named or quoted the
Driver in 13 lines. They sit outside check:no-new-driver-references'
areas, but they ship in the giveaway all the same, so they are reworded
the same way as the sweep. The two left are the reference gate's own
description, which has to say what it checks.
This commit is contained in:
Doxa Maps 2026-09-29 08:01:13 +00:00
parent 64a45967e6
commit f9c621c647
5 changed files with 15 additions and 15 deletions

2
.gitignore vendored
View file

@ -135,7 +135,7 @@ _qa-cycle/
# library/scripts/research/build-tabulator-tables.mjs). Guard against re-creation. # library/scripts/research/build-tabulator-tables.mjs). Guard against re-creation.
data-private/ data-private/
# ── public/ is the OUTPUT FOLDER + README.md only (Driver decision, BUILD-OUTPUT-IS-ONE-FOLDER, 2026-09-25) ── # ── public/ is the OUTPUT FOLDER + README.md only (decision BUILD-OUTPUT-IS-ONE-FOLDER, 2026-09-25) ──
# These rules used to live in public/.gitignore, which was itself a tracked file INSIDE the generated # These rules used to live in public/.gitignore, which was itself a tracked file INSIDE the generated
# folder (so `rm -rf public && bun run build` deleted it). Local-only files stay untracked: # folder (so `rm -rf public && bun run build` deleted it). Local-only files stay untracked:
/public/token.local.js /public/token.local.js

View file

@ -4,7 +4,7 @@
"//webDir": [ "//webDir": [
"webDir = public/<tree> \u2014 THE OUTPUT FOLDER, written by the build (internal/sync-app-shells.mjs keeps it", "webDir = public/<tree> \u2014 THE OUTPUT FOLDER, written by the build (internal/sync-app-shells.mjs keeps it",
"in step with the tree's name in vite.doxa-maps-build-config.json; do not edit it by hand).", "in step with the tree's name in vite.doxa-maps-build-config.json; do not edit it by hand).",
"public/ holds ONLY the output folder + README.md (Driver decision, BUILD-OUTPUT-IS-ONE-FOLDER). The folder is", "public/ holds ONLY the output folder + README.md (decision BUILD-OUTPUT-IS-ONE-FOLDER). The folder is",
"self-contained: its index.html is the front page (the Plugin Studio), <bundle>/ holds each plugin, and", "self-contained: its index.html is the front page (the Plugin Studio), <bundle>/ holds each plugin, and",
"assets/ carries the data, fonts and images the plugins fetch.", "assets/ carries the data, fonts and images the plugins fetch.",
"ONE build, THREE consumers, all reading THIS folder:", "ONE build, THREE consumers, all reading THIS folder:",

View file

@ -423,15 +423,15 @@
"check:embedder-keys": "regions-mfe forwards page-scoped config to its nested panes by an explicit key list (INHERITED_PAGE_KEYS). Asserts the documented embedder keys travel (dataSource, tokenUrl among them) AND that profile and instanceId do NOT: the host's would overwrite each pane's own and collapse both tabs into one view.", "check:embedder-keys": "regions-mfe forwards page-scoped config to its nested panes by an explicit key list (INHERITED_PAGE_KEYS). Asserts the documented embedder keys travel (dataSource, tokenUrl among them) AND that profile and instanceId do NOT: the host's would overwrite each pane's own and collapse both tabs into one view.",
"check:english-leaks": "A translated key must not render in English. rega's method: a string is a LEAK when the catalogue has a DIFFERENT value for the locale and the page still shows the English one - a render/wiring fault, not a missing translation. Third-party attributions, data names, placeholder-only strings and the prose this bundle deliberately keeps in English are all excluded, the last by simply not being in the catalogue. Scoped to the LEAF on purpose: six keys were reported leaking in the gallery and the leaf is clean in ar/zh/ru/hi - the gallery builds its iframe src with no lang parameter, so the embed never learns the locale. Embedder defect, not a bundle one.", "check:english-leaks": "A translated key must not render in English. rega's method: a string is a LEAK when the catalogue has a DIFFERENT value for the locale and the page still shows the English one - a render/wiring fault, not a missing translation. Third-party attributions, data names, placeholder-only strings and the prose this bundle deliberately keeps in English are all excluded, the last by simply not being in the catalogue. Scoped to the LEAF on purpose: six keys were reported leaking in the gallery and the leaf is clean in ar/zh/ru/hi - the gallery builds its iframe src with no lang parameter, so the embed never learns the locale. Embedder defect, not a bundle one.",
"check:experimental-shelf": "Every Experimental-folder bundle, leaf + Gallery tab, mapbox + maplibre: mounts with content, 0 errors / failed requests, maps load and DRAW their own data. Self-tests on a deliberately broken bundle first. Run through internal/browser-slot.sh.", "check:experimental-shelf": "Every Experimental-folder bundle, leaf + Gallery tab, mapbox + maplibre: mounts with content, 0 errors / failed requests, maps load and DRAW their own data. Self-tests on a deliberately broken bundle first. Run through internal/browser-slot.sh.",
"check:export-poster-prints": "the Simple Research 'Export poster' button opens the PRINT DIALOG on the poster in ONE click and downloads NOTHING (the Driver's rule, 2026-09-28): leaf AND Research Gallery, en AND ar; the printing document is the 1700x2200 poster on an 8.5in x 11in sheet; no download arrives; 0 page errors. window.print is stubbed, so this measures everything up to the dialog. Replaces check:export-one-click, which asserted the download this rule forbids. Run inside the shared browser slot. Exit 2 = SKIP, 75 = NOT MEASURED.", "check:export-poster-prints": "the Simple Research 'Export poster' button opens the PRINT DIALOG on the poster in ONE click and downloads NOTHING (the rule since 2026-09-28): leaf AND Research Gallery, en AND ar; the printing document is the 1700x2200 poster on an 8.5in x 11in sheet; no download arrives; 0 page errors. window.print is stubbed, so this measures everything up to the dialog. Replaces check:export-one-click, which asserted the download this rule forbids. Run inside the shared browser slot. Exit 2 = SKIP, 75 = NOT MEASURED.",
"check:export-poster-prints:maplibre": "the same four one-click prints on the keyless MapLibre engine (OpenFreeMap tiles): the hidden print map must be built with the engine that drew the screen, and the footer must credit the tiles actually printed.", "check:export-poster-prints:maplibre": "the same four one-click prints on the keyless MapLibre engine (OpenFreeMap tiles): the hidden print map must be built with the engine that drew the screen, and the footer must credit the tiles actually printed.",
"check:facet-cascade": "a child facet selection must not outlive its parent: clearing a parent facet must cascade-clear its descendants on EVERY clear path. Asserts on RESULT COUNTS, not DOM — an orphaned child silently ANDs the map to empty. run check:facet-cascade:self-test for the NEGATIVE test (reverts the cascade and requires this gate to FAIL).", "check:facet-cascade": "a child facet selection must not outlive its parent: clearing a parent facet must cascade-clear its descendants on EVERY clear path. Asserts on RESULT COUNTS, not DOM — an orphaned child silently ANDs the map to empty. run check:facet-cascade:self-test for the NEGATIVE test (reverts the cascade and requires this gate to FAIL).",
"check:facet-changes-map": "a facet selection must CHANGE THE DOTS — and the dots must SAY what they mean. Asserts (1) the rd-bubbles feature set changes by count AND identity, (2) every surviving dot satisfies the selected level, (3) while a prayer filter is active circle-color is a DATA-DRIVEN EXPRESSION reading peoplePraying, not a flat literal. Assertion 3 is the one that matters: the prayer filter ALREADY narrowed 2107->689 correctly while the Driver reported it broken, because every dot painted the same orange. Browser gate (needs `bun run dev` on 5173, or BASE_URL/PROBE_PATH at a served tree), so NOT in build:unlocked. run the `check:facet-changes-map:self-test` script for the NEGATIVE test.", "check:facet-changes-map": "a facet selection must CHANGE THE DOTS — and the dots must SAY what they mean. Asserts (1) the rd-bubbles feature set changes by count AND identity, (2) every surviving dot satisfies the selected level, (3) while a prayer filter is active circle-color is a DATA-DRIVEN EXPRESSION reading peoplePraying, not a flat literal. Assertion 3 is the one that matters: the prayer filter ALREADY narrowed 2107->689 correctly while it was reported broken, because every dot painted the same orange. Browser gate (needs `bun run dev` on 5173, or BASE_URL/PROBE_PATH at a served tree), so NOT in build:unlocked. run the `check:facet-changes-map:self-test` script for the NEGATIVE test.",
"check:facet-changes-map:built": "the SAME gate against the BUILT tree instead of the dev server. Measured 2026-09-24: the dev default is 1 PASS / 3 FAIL in four runs on a busy box (88 unbundled module requests vs the built leaf's 3), while the built tree passes deterministically. Use this when you need a trustworthy answer; use the dev default when checking uncommitted source.", "check:facet-changes-map:built": "the SAME gate against the BUILT tree instead of the dev server. Measured 2026-09-24: the dev default is 1 PASS / 3 FAIL in four runs on a busy box (88 unbundled module requests vs the built leaf's 3), while the built tree passes deterministically. Use this when you need a trustworthy answer; use the dev default when checking uncommitted source.",
"check:facet-changes-map:self-test": "reverts the fix IN THE LIVE PAGE and requires the gate to FAIL. SELF_TEST_MODE=colour (default, isolates the colour assertion — the reported bug) | props | both | frozen (blocks the source update, isolating the set assertion). All four modes verified caught.", "check:facet-changes-map:self-test": "reverts the fix IN THE LIVE PAGE and requires the gate to FAIL. SELF_TEST_MODE=colour (default, isolates the colour assertion — the reported bug) | props | both | frozen (blocks the source update, isolating the set assertion). All four modes verified caught.",
"check:facet-one-field": "ONE AXIS = ONE CONTROL-REACHABLE FACET. Asserts on the PARTITION each facet induces over the rows, NOT on which r.<field> it reads: three fields (religion, primary_language, rop1) are legitimately read by two facets each as tier-1/tier-2 pairs, so a gate on duplicate field READS would fail correct code. Scoped to facets a user can actually select (rail + dropdowns + charts), because a search-only projection sharing a partition is fine until it becomes a control. run check:facet-one-field:self-test for the NEGATIVE test.", "check:facet-one-field": "ONE AXIS = ONE CONTROL-REACHABLE FACET. Asserts on the PARTITION each facet induces over the rows, NOT on which r.<field> it reads: three fields (religion, primary_language, rop1) are legitimately read by two facets each as tier-1/tier-2 pairs, so a gate on duplicate field READS would fail correct code. Scoped to facets a user can actually select (rail + dropdowns + charts), because a search-only projection sharing a partition is fine until it becomes a control. run check:facet-one-field:self-test for the NEGATIVE test.",
"check:filter-multivalue": "SPEC-DASHBOARD-KIT D1: filterSet[key] may hold an array meaning OR-within-key, keys still AND together — and exactly ONE function answers 'does this row match'. The rule `a(r) === filterSet[k]` was written out SEVEN times (shell, facet rail x2, map panel, table, charts rail, CSV exporter); the moment it grew an array case, six copies would have kept comparing an array with === , matched nothing, and left the panels disagreeing about what is on screen — the facet rail printing counts for a scope the map and table no longer render. Runs the real bus (OR within, AND across, one-element array collapses to a scalar, EMPTY array clears rather than matching nothing, toggleFilterValue adds/removes/clears, cascade intact), greps the panels for surviving copies, round-trips the URL hash including a value containing a comma, and lifts the grid's valueMatches to prove both ends OR the same way. The list separator is `|` and not `,` because 120 of the 864 live language labels contain a comma. No browser.", "check:filter-multivalue": "SPEC-DASHBOARD-KIT D1: filterSet[key] may hold an array meaning OR-within-key, keys still AND together — and exactly ONE function answers 'does this row match'. The rule `a(r) === filterSet[k]` was written out SEVEN times (shell, facet rail x2, map panel, table, charts rail, CSV exporter); the moment it grew an array case, six copies would have kept comparing an array with === , matched nothing, and left the panels disagreeing about what is on screen — the facet rail printing counts for a scope the map and table no longer render. Runs the real bus (OR within, AND across, one-element array collapses to a scalar, EMPTY array clears rather than matching nothing, toggleFilterValue adds/removes/clears, cascade intact), greps the panels for surviving copies, round-trips the URL hash including a value containing a comma, and lifts the grid's valueMatches to prove both ends OR the same way. The list separator is `|` and not `,` because 120 of the 864 live language labels contain a comma. No browser.",
"check:fits-screen": "THE FITNESS-FOR-USE GATE (LL-the-user-found-three-bugs-my-gates-could-not-see): a green STATE suite (mounts/fetches/counts/paints) said nothing about whether the product is USABLE, and the Driver found five real defects in one sitting that no gate saw. Asserts 5 clauses on the built dashboard-plus-grid composite + research-dashboard leaf, real browser, live data: (A) the dashboard band fits ONE 1440x900 viewport and is shorter than its own tree-legend's scrollHeight; (B) the people-groups grid is still reachable by scrolling underneath it (a fix for A must not re-break this — AP-fixing-reachability-by-making-the-thing-taller); (C) a wheel over the map changes ZOOM and does not scroll the pane, a wheel below it scrolls and does not zoom — instrumented on the DISPATCHED EVENT (composedPath), not geometry, per LL-four-wrong-diagnoses-instrument-the-event-not-the-geometry; (D) the grid's live count ([data-table-count], the composite's documented single source of truth — show-total=false on the grid child is deliberate) is visible and CHANGES when a facet narrows it; (E) a 1440/1280/768/390 responsive sweep has no horizontal document overflow and no element clipped to zero height (.rd-td/.rd-th ellipsis excluded — expected). Spawns its own serve-strict on PORT (default 4391); SKIPs (exit 0) if the tree is not settled at START OR re-settled at END, or pray.doxa.life is unreachable — never reports a defect from a torn tree. Also best-effort probes any cap-5 shell the manifest declares (type nested-mfe-spa) by CONTENT-MATCHING a tab, never by hardcoded structure, so it survives the shell's own in-flight restructure without depending on it. Needs a built tree (`bun run build`). run `check:fits-screen:self-test` for the NEGATIVE test.", "check:fits-screen": "THE FITNESS-FOR-USE GATE (LL-the-user-found-three-bugs-my-gates-could-not-see): a green STATE suite (mounts/fetches/counts/paints) said nothing about whether the product is USABLE, and one sitting with the product found five real defects that no gate saw. Asserts 5 clauses on the built dashboard-plus-grid composite + research-dashboard leaf, real browser, live data: (A) the dashboard band fits ONE 1440x900 viewport and is shorter than its own tree-legend's scrollHeight; (B) the people-groups grid is still reachable by scrolling underneath it (a fix for A must not re-break this — AP-fixing-reachability-by-making-the-thing-taller); (C) a wheel over the map changes ZOOM and does not scroll the pane, a wheel below it scrolls and does not zoom — instrumented on the DISPATCHED EVENT (composedPath), not geometry, per LL-four-wrong-diagnoses-instrument-the-event-not-the-geometry; (D) the grid's live count ([data-table-count], the composite's documented single source of truth — show-total=false on the grid child is deliberate) is visible and CHANGES when a facet narrows it; (E) a 1440/1280/768/390 responsive sweep has no horizontal document overflow and no element clipped to zero height (.rd-td/.rd-th ellipsis excluded — expected). Spawns its own serve-strict on PORT (default 4391); SKIPs (exit 0) if the tree is not settled at START OR re-settled at END, or pray.doxa.life is unreachable — never reports a defect from a torn tree. Also best-effort probes any cap-5 shell the manifest declares (type nested-mfe-spa) by CONTENT-MATCHING a tab, never by hardcoded structure, so it survives the shell's own in-flight restructure without depending on it. Needs a built tree (`bun run build`). run `check:fits-screen:self-test` for the NEGATIVE test.",
"check:fits-screen:self-test": "FIVE regression modes, one per clause, each required to fire ITS OWN [A]/[B]/[C]/[D]/[E]-named failure: run clause a via THIS entry and b-e via `check:fits-screen:self-test:<b|c|d|e>`. a (default — forces the dashboard section past the viewport, the AP-fixing-reachability shape), b (n/a here — B is proven by A's own poison already trapping the grid below the fold; kept as a mode for a future independent poison), c (freezes map.scrollZoom enabled, the BUG-MAP-EATS-THE-WHEEL regression), d (blanks the live count element), e (forces the shell wider than the viewport at 1440). A gate that has only ever printed PASS is unproven.", "check:fits-screen:self-test": "FIVE regression modes, one per clause, each required to fire ITS OWN [A]/[B]/[C]/[D]/[E]-named failure: run clause a via THIS entry and b-e via `check:fits-screen:self-test:<b|c|d|e>`. a (default — forces the dashboard section past the viewport, the AP-fixing-reachability shape), b (n/a here — B is proven by A's own poison already trapping the grid below the fold; kept as a mode for a future independent poison), c (freezes map.scrollZoom enabled, the BUG-MAP-EATS-THE-WHEEL regression), d (blanks the live count element), e (forces the shell wider than the viewport at 1440). A gate that has only ever printed PASS is unproven.",
"check:fits-screen:self-test:b": "★ A DEDICATED ENTRY, not a mode appended at the call site. The parent script value ends in an inline # comment, so a mode appended by the caller lands INSIDE that comment and the shell drops it — the run then exits 0 in DEFAULT mode, indistinguishable from the mode being selected. A self-test is the proof a gate can fail, so a self-test whose mode cannot be chosen is a gate certifying itself. This entry hardcodes its mode, so nothing has to be appended. (Flag names are deliberately not spelled here: check:script-flags scans script VALUES, and naming one in prose reads as advertising it.)", "check:fits-screen:self-test:b": "★ A DEDICATED ENTRY, not a mode appended at the call site. The parent script value ends in an inline # comment, so a mode appended by the caller lands INSIDE that comment and the shell drops it — the run then exits 0 in DEFAULT mode, indistinguishable from the mode being selected. A self-test is the proof a gate can fail, so a self-test whose mode cannot be chosen is a gate certifying itself. This entry hardcodes its mode, so nothing has to be appended. (Flag names are deliberately not spelled here: check:script-flags scans script VALUES, and naming one in prose reads as advertising it.)",
"check:fits-screen:self-test:c": "★ A DEDICATED ENTRY, not a mode appended at the call site. The parent script value ends in an inline # comment, so a mode appended by the caller lands INSIDE that comment and the shell drops it — the run then exits 0 in DEFAULT mode, indistinguishable from the mode being selected. A self-test is the proof a gate can fail, so a self-test whose mode cannot be chosen is a gate certifying itself. This entry hardcodes its mode, so nothing has to be appended. (Flag names are deliberately not spelled here: check:script-flags scans script VALUES, and naming one in prose reads as advertising it.)", "check:fits-screen:self-test:c": "★ A DEDICATED ENTRY, not a mode appended at the call site. The parent script value ends in an inline # comment, so a mode appended by the caller lands INSIDE that comment and the shell drops it — the run then exits 0 in DEFAULT mode, indistinguishable from the mode being selected. A self-test is the proof a gate can fail, so a self-test whose mode cannot be chosen is a gate certifying itself. This entry hardcodes its mode, so nothing has to be appended. (Flag names are deliberately not spelled here: check:script-flags scans script VALUES, and naming one in prose reads as advertising it.)",
@ -448,11 +448,11 @@
"check:heartbeat": "the HEARTBEAT ZONE threshold (the choropleth's top bin) must come from config AND the shipped config must still render today's ramp. Two sides: (B) a configured threshold must MOVE the edge — catches re-hard-coding; (A2) dashboard.config.js's own map.* must still resolve to 5/20/60/150/Infinity — catches the shipped default silently changing. A2 was MISSING at first and heartbeatThreshold:999 shipped past this gate printing 'default ramp unchanged'. A grep for '150' cannot do either job: it false-positives on the legitimate default and false-negatives on a re-hard-coding at 200. run check:heartbeat:self-test for the NEGATIVE tests (3 cases).", "check:heartbeat": "the HEARTBEAT ZONE threshold (the choropleth's top bin) must come from config AND the shipped config must still render today's ramp. Two sides: (B) a configured threshold must MOVE the edge — catches re-hard-coding; (A2) dashboard.config.js's own map.* must still resolve to 5/20/60/150/Infinity — catches the shipped default silently changing. A2 was MISSING at first and heartbeatThreshold:999 shipped past this gate printing 'default ramp unchanged'. A grep for '150' cannot do either job: it false-positives on the legitimate default and false-negatives on a re-hard-coding at 200. run check:heartbeat:self-test for the NEGATIVE tests (3 cases).",
"check:heartbeat:self-test": "THREE regression cases, each required to fire ITS OWN assertion: [literal] resolver replaced by the old hard-coded array -> B; [shipped-default] dashboard.config.js heartbeatThreshold changed -> A2; [shipped-mode] heartbeatMode flipped to quantile -> A2. Poisons IN MEMORY, never the file. Testing that the FEATURE works is not testing that the REGRESSION is caught — this gate shipped with only the first and missed the second.", "check:heartbeat:self-test": "THREE regression cases, each required to fire ITS OWN assertion: [literal] resolver replaced by the old hard-coded array -> B; [shipped-default] dashboard.config.js heartbeatThreshold changed -> A2; [shipped-mode] heartbeatMode flipped to quantile -> A2. Poisons IN MEMORY, never the file. Testing that the FEATURE works is not testing that the REGRESSION is caught — this gate shipped with only the first and missed the second.",
"check:help-reachable": "every Experimental map with explainer text in doxa-maps-spa/src/experimentalHelp.js reaches it from its OWN \"?\" (a hosted HelpButton or useHostedHelp in its bar), or through a named control of its own (upg-neighbor's About); the app listens for doxa:help, marks its panes with data-doxa-help and has no top-centre button. Executes the composable on a fake DOM: doxa:help must bubble, be composed, and find the host across a shadow root. Six planted defects each caught. Node only, no browser.", "check:help-reachable": "every Experimental map with explainer text in doxa-maps-spa/src/experimentalHelp.js reaches it from its OWN \"?\" (a hosted HelpButton or useHostedHelp in its bar), or through a named control of its own (upg-neighbor's About); the app listens for doxa:help, marks its panes with data-doxa-help and has no top-centre button. Executes the composable on a fake DOM: doxa:help must bubble, be composed, and find the host across a shadow root. Six planted defects each caught. Node only, no browser.",
"check:hover-channel": "SPEC-DASHBOARD-KIT D3: the bus's third coordinated channel points at a record without committing to one. Hover looks like a weaker selection, and the cheap implementation is to put the key in selectionSet 'just while hovering' - every consequence of which is bad and none obvious at the call site: useSelectionCamera watches selectionVersion, so a hover would fly the camera at every mousemove (the resize-strobe class, on a photosensitive Driver); the CSV exporter reads selection > filter > all, so a hover would change what an export contains; the table's selection pill and the shell's live region would both narrate the pointer. Runs the real bus: hover holds its own ref, never enters the selection, never bumps the version, never touches the filters, a repeat emits once (hover fires on every mousemove across 2,107 rows), and the three channels clear independently. Does NOT check which panels light up - D3 says panels opt in, and the first opt-in belongs with a consumer that has a reason. No browser.", "check:hover-channel": "SPEC-DASHBOARD-KIT D3: the bus's third coordinated channel points at a record without committing to one. Hover looks like a weaker selection, and the cheap implementation is to put the key in selectionSet 'just while hovering' - every consequence of which is bad and none obvious at the call site: useSelectionCamera watches selectionVersion, so a hover would fly the camera at every mousemove (the resize-strobe class, and a reader may be photosensitive); the CSV exporter reads selection > filter > all, so a hover would change what an export contains; the table's selection pill and the shell's live region would both narrate the pointer. Runs the real bus: hover holds its own ref, never enters the selection, never bumps the version, never touches the filters, a repeat emits once (hover fires on every mousemove across 2,107 rows), and the three channels clear independently. Does NOT check which panels light up - D3 says panels opt in, and the first opt-in belongs with a consumer that has a reason. No browser.",
"check:host-locale-contract": "a HOST's profile-config.locale reaches every leaf: each leaf page is loaded with ONLY \"locale\":\"ar\" added to its profile-config (no ?lang=, the page keeps lang=\"en\") and must draw at least half the Arabic text the same page draws at ?lang=ar. regions-dashboard/regions-list is the positive control (NOT MEASURED if it does not enter Arabic). Guards ProfileLoader's inject('appI18n'): a bundle that never provides appI18n silently ignores the host. --dir <tree> for a before/after, --only a/b,c/d to narrow. Through browser-slot.sh.", "check:host-locale-contract": "a HOST's profile-config.locale reaches every leaf: each leaf page is loaded with ONLY \"locale\":\"ar\" added to its profile-config (no ?lang=, the page keeps lang=\"en\") and must draw at least half the Arabic text the same page draws at ?lang=ar. regions-dashboard/regions-list is the positive control (NOT MEASURED if it does not enter Arabic). Guards ProfileLoader's inject('appI18n'): a bundle that never provides appI18n silently ignores the host. --dir <tree> for a before/after, --only a/b,c/d to narrow. Through browser-slot.sh.",
"check:i18n-keys-exist": "every literal catalogue key named in code exists in library/i18n/locales/en/. A localized CALL SITE is not a localized STRING: the dashboard's five resize handles all read T('aria.resizeFiltersRail', ...) and look correct in review, while aria.json held eleven keys and not one was a resize key - so every handle announced itself in English in all eleven locales, and the fallback that makes a missing key degrade to readable English is exactly what kept it silent. No other gate asks this: i18n-parity is satisfied because the key is missing from ALL locales equally, and english-leaks and composite-locale-leaks both need the catalogue to HOLD the string before an appearance counts as a leak. Matches tStatic/$t/t and the house-pattern T( alias, all of which take a full dotted key; prefix helpers like L( and any concatenated or template-literal key are COUNTED as unresolved and never guessed at. Found 22 missing keys on its first run. ROUND 2, after go-int planted T('poison_key') behind `const T = useGiT()` and this gate reported the tree CLEAN -- the one failure a check is not forgiven, because a red gate gets fixed and a green one that should be red gets believed. THREE independent blindnesses stacked, and any one alone hid the key: the file never writes the word tStatic so the precondition skipped it before anything ran; useGiT is not tStatic so no alias was found; and the alias is PREFIXED (useOptionalT carries a namespace) while its call sites pass BARE keys, which the full-key pattern cannot match because it requires a dot. Now a translator that arrives from a COMPOSABLE is resolved like any other, with the namespace READ from the factory's one-line body rather than listed here -- useOptionalT/useOptionalI18n directly, plus any `export function useXxxT() { return useOptionalT('ns') }`, which is how useNeighborT and usePplrT are written. Because qualify() prefixes a bare key and leaves an already-qualified one alone, one binding registers as BOTH shapes. MEASURED before and after with the same poison on disk: the old version PASSES it, this one fails it as 'neighbor.zzz_not_a_real_key (via nt())'; coverage went 683 -> 743 full-key and 49 -> 56 prefixed call sites, so 67 sites were being scored clean without being read. A self-test now asserts all three fixes separately and exits 3 if any goes blind, because this gate guards goal (d) and had already been trusted once while lying. Empty-namespace bare keys stay outside the model and are COUNTED rather than assumed away; measured at zero today, since all five such sites pass dotted keys.", "check:i18n-keys-exist": "every literal catalogue key named in code exists in library/i18n/locales/en/. A localized CALL SITE is not a localized STRING: the dashboard's five resize handles all read T('aria.resizeFiltersRail', ...) and look correct in review, while aria.json held eleven keys and not one was a resize key - so every handle announced itself in English in all eleven locales, and the fallback that makes a missing key degrade to readable English is exactly what kept it silent. No other gate asks this: i18n-parity is satisfied because the key is missing from ALL locales equally, and english-leaks and composite-locale-leaks both need the catalogue to HOLD the string before an appearance counts as a leak. Matches tStatic/$t/t and the house-pattern T( alias, all of which take a full dotted key; prefix helpers like L( and any concatenated or template-literal key are COUNTED as unresolved and never guessed at. Found 22 missing keys on its first run. ROUND 2, after go-int planted T('poison_key') behind `const T = useGiT()` and this gate reported the tree CLEAN -- the one failure a check is not forgiven, because a red gate gets fixed and a green one that should be red gets believed. THREE independent blindnesses stacked, and any one alone hid the key: the file never writes the word tStatic so the precondition skipped it before anything ran; useGiT is not tStatic so no alias was found; and the alias is PREFIXED (useOptionalT carries a namespace) while its call sites pass BARE keys, which the full-key pattern cannot match because it requires a dot. Now a translator that arrives from a COMPOSABLE is resolved like any other, with the namespace READ from the factory's one-line body rather than listed here -- useOptionalT/useOptionalI18n directly, plus any `export function useXxxT() { return useOptionalT('ns') }`, which is how useNeighborT and usePplrT are written. Because qualify() prefixes a bare key and leaves an already-qualified one alone, one binding registers as BOTH shapes. MEASURED before and after with the same poison on disk: the old version PASSES it, this one fails it as 'neighbor.zzz_not_a_real_key (via nt())'; coverage went 683 -> 743 full-key and 49 -> 56 prefixed call sites, so 67 sites were being scored clean without being read. A self-test now asserts all three fixes separately and exits 3 if any goes blind, because this gate guards goal (d) and had already been trusted once while lying. Empty-namespace bare keys stay outside the model and are COUNTED rather than assumed away; measured at zero today, since all five such sites pass dotted keys.",
"check:i18n-never-translated": "a key PRESENT in all 11 catalogs but IDENTICAL in all of them is copied, not translated. check:i18n-parity asserts presence; presence and translatedness are different properties. Carries an allowlist for keys correctly identical everywhere (pure format strings, brand names, ISO designations) — each with a reason, so the list records decisions rather than silencing findings.", "check:i18n-never-translated": "a key PRESENT in all 11 catalogs but IDENTICAL in all of them is copied, not translated. check:i18n-parity asserts presence; presence and translatedness are different properties. Carries an allowlist for keys correctly identical everywhere (pure format strings, brand names, ISO designations) — each with a reason, so the list records decisions rather than silencing findings.",
"check:i18n-stopped-scope": "createAppI18n() must succeed while a STOPPED effect scope is current: the Driver's P0 (Plugin Studio gallery, every tab after the first threw 'SyntaxError: Unexpected error' from createI18n, because Vue 3.5 makes a new scope inactive under a stopped one). A bare createI18n() in the same scope must throw first, or the harness proves nothing. Also asserts a bundle's second provide('appI18n') raises no warning.", "check:i18n-stopped-scope": "createAppI18n() must succeed while a STOPPED effect scope is current: a P0 seen live (Plugin Studio gallery, every tab after the first threw 'SyntaxError: Unexpected error' from createI18n, because Vue 3.5 makes a new scope inactive under a stopped one). A bare createI18n() in the same scope must throw first, or the harness proves nothing. Also asserts a bundle's second provide('appI18n') raises no warning.",
"check:i18n-treeshake": "each built bundle carries every translation area its code asks for: every area-prefixed key literal in <bundle>.js belongs to an area the build included (the build narrows library/i18n's catalogue glob per bundle, internal/i18n-areas.mjs). Headless.", "check:i18n-treeshake": "each built bundle carries every translation area its code asks for: every area-prefixed key literal in <bundle>.js belongs to an area the build included (the build narrows library/i18n's catalogue glob per bundle, internal/i18n-areas.mjs). Headless.",
"check:instance-config-keys": "a plugin reads its instance config keys (show-search, show-filters) from profile-config in both spellings, with the prop winning. By the regions owner; mutation-tested. Headless; in gates:post.", "check:instance-config-keys": "a plugin reads its instance config keys (show-search, show-filters) from profile-config in both spellings, with the prop winning. By the regions owner; mutation-tested. Headless; in gates:post.",
"check:kit-options-reachable": "every option a shared kit composable ACCEPTS must be honoured somewhere in its body. Written because I nearly shipped one: kit landing 3 gave useSelectionCamera a `coords` option so a dashboard whose rows spell their coordinate differently could say so, and it was accepted in the signature and never passed to selectionBounds - a caller could set it, type-check clean, build clean and watch it do nothing. That is the worst shape a missing feature takes, because it READS as supported and nobody files a bug against a knob they assume they misused; this tree already paid for it once with a defineProps on a component the custom element never binds. It matters more for the kit than for one dashboard: the whole promise of SPEC-DASHBOARD-KIT is that a config produces a working dashboard, and a parameter that is merely present turns that promise into a guess. Strips comments from BOTH the signature and the body before searching - the first version was satisfied by the comment beside the fixed call site, and the second read comment prose as option names. Not a type checker: it catches a completely unused option, which is the failure invisible from outside.", "check:kit-options-reachable": "every option a shared kit composable ACCEPTS must be honoured somewhere in its body. Written because I nearly shipped one: kit landing 3 gave useSelectionCamera a `coords` option so a dashboard whose rows spell their coordinate differently could say so, and it was accepted in the signature and never passed to selectionBounds - a caller could set it, type-check clean, build clean and watch it do nothing. That is the worst shape a missing feature takes, because it READS as supported and nobody files a bug against a knob they assume they misused; this tree already paid for it once with a defineProps on a component the custom element never binds. It matters more for the kit than for one dashboard: the whole promise of SPEC-DASHBOARD-KIT is that a config produces a working dashboard, and a parameter that is merely present turns that promise into a guess. Strips comments from BOTH the signature and the body before searching - the first version was satisfied by the comment beside the fixed call site, and the second read comment prose as option names. Not a type checker: it catches a completely unused option, which is the failure invisible from outside.",
@ -522,7 +522,7 @@
"check:ported-globals-identical": "the DX Life base/_globals.scss rules that shadow-root components used to copy now come from one module (library/styles/portedGlobals.js), and the CSS each component actually INJECTS must be byte-identical to the pre-refactor baseline: it runs each component's setup with stubbed imports and hashes the real useShadowStyles argument, not the source text. Deduplication with a proof that no pixel can change. Node only, no browser.", "check:ported-globals-identical": "the DX Life base/_globals.scss rules that shadow-root components used to copy now come from one module (library/styles/portedGlobals.js), and the CSS each component actually INJECTS must be byte-identical to the pre-refactor baseline: it runs each component's setup with stubbed imports and hashes the real useShadowStyles argument, not the source text. Deduplication with a proof that no pixel can change. Node only, no browser.",
"check:pin-selection": "the shared source reader understands BOTH engines: mapbox-gl stores a GeoJSON source _data as the FeatureCollection, maplibre-gl wraps it as { geojson } (or { url } when remote). Reading only the Mapbox shape returned an EMPTY list on MapLibre — the keyless engine public builds use — which silently killed zoomToLegendRow camera fits on three maps and the research packet row selection, with no error because every caller no-ops on no features. Asserts both shapes, that a remote source returns [] AND warns, the serialize fallback, and that selectedPinIds agrees with the reader. Headless, no map. Exit 0 pass, 1 fail.", "check:pin-selection": "the shared source reader understands BOTH engines: mapbox-gl stores a GeoJSON source _data as the FeatureCollection, maplibre-gl wraps it as { geojson } (or { url } when remote). Reading only the Mapbox shape returned an EMPTY list on MapLibre — the keyless engine public builds use — which silently killed zoomToLegendRow camera fits on three maps and the research packet row selection, with no error because every caller no-ops on no features. Asserts both shapes, that a remote source returns [] AND warns, the serialize fallback, and that selectedPinIds agrees with the reader. Headless, no map. Exit 0 pass, 1 fail.",
"check:popup-href-escaped": "a URL interpolated into an HTML string is escaped before it lands in href= or src=. Written for RegionCountryMap's popup, which escaped every value except its two link URLs -- both built from joinBase(hostAttribute, apiSlug), so a quote in either escaped the attribute. Data URLs, blob URLs, encodeURIComponent and safeUrl() are recognised as safe; anything else needs an href-escaped-ok: reason. 6 self-tests. Source-only, headless.", "check:popup-href-escaped": "a URL interpolated into an HTML string is escaped before it lands in href= or src=. Written for RegionCountryMap's popup, which escaped every value except its two link URLs -- both built from joinBase(hostAttribute, apiSlug), so a quote in either escaped the attribute. Data URLs, blob URLs, encodeURIComponent and safeUrl() are recognised as safe; anything else needs an href-escaped-ok: reason. 6 self-tests. Source-only, headless.",
"check:poster-action-print": "a poster button opens the print dialog (Driver rule 2026-09-28: never a direct download). Two arms: PosterWall's posterAction defaults to 'print' and any consumer opting into 'download' carries a posterAction-download: comment saying why; and no shipped file hand-rolls an <a download> for a poster IMAGE (svg/png/webp/pdf) without first attempting to print, which the prop arm cannot see. Data exports (csv/txt/json) are out of scope. 6 self-tests. Source-only, headless.", "check:poster-action-print": "a poster button opens the print dialog (the rule since 2026-09-28: never a direct download). Two arms: PosterWall's posterAction defaults to 'print' and any consumer opting into 'download' carries a posterAction-download: comment saying why; and no shipped file hand-rolls an <a download> for a poster IMAGE (svg/png/webp/pdf) without first attempting to print, which the prop arm cannot see. Data exports (csv/txt/json) are out of scope. 6 self-tests. Source-only, headless.",
"check:print-frame-reclaim": "a print frame is kept until it has printed: openPrintFrame reclaims it 60 s after its load (not after its creation), at afterprint, or at a 10-minute ceiling, and printImageBlob revokes its object URL on the same clock. Fake timers; two-sided (the pre-fix copy fails). The Wall poster loads 132 MB of photos, and the old 60 s-from-creation clock removed the frame before it could print.", "check:print-frame-reclaim": "a print frame is kept until it has printed: openPrintFrame reclaims it 60 s after its load (not after its creation), at afterprint, or at a 10-minute ceiling, and printImageBlob revokes its object URL on the same clock. Fake timers; two-sided (the pre-fix copy fails). The Wall poster loads 132 MB of photos, and the old 60 s-from-creation clock removed the frame before it could print.",
"check:poster-attribution": "the poster footer is a LICENCE statement, so it must credit whoever actually served the tiles. posterDefaults ships '© Mapbox © OpenStreetMap', which under the fleet's MapLibre default credits a company that served nothing. Checked live 2026-09-28: the OpenFreeMap STYLE declares no attribution on either source — the credit is in the TileJSON, merged into the SOURCE object after load — so it is read from map.getSource(id), flattened from HTML, deduped, and an unreadable map falls back rather than printing a blank line. Headless; in gates:pre.", "check:poster-attribution": "the poster footer is a LICENCE statement, so it must credit whoever actually served the tiles. posterDefaults ships '© Mapbox © OpenStreetMap', which under the fleet's MapLibre default credits a company that served nothing. Checked live 2026-09-28: the OpenFreeMap STYLE declares no attribution on either source — the credit is in the TileJSON, merged into the SOURCE object after load — so it is read from map.getSource(id), flattened from HTML, deduped, and an unreadable map falls back rather than printing a blank line. Headless; in gates:pre.",
"check:poster-card-fits": "a poster card's print-options row must stay inside its own card. Reported by the repo agent off a 1440px screenshot: 27/27 cards overflowing by 110px, 36 neighbour intrusions. .pfs already had width:82%, so SOURCE READING SAID IT WAS FINE — flex items default to min-width:auto and refuse to shrink. Asserts BOTH no-overlap AND that the controls keep a usable box, because a row whose buttons collapsed to nothing would also \"not overflow\". Sweeps 390/768/1024/1440/1920px and asserts the row sits BELOW the preview too — measuring only left/right edges is how it passed a driver-visible regression. CARD_FIT_WIDTH overrides the list. SKIPs when nothing is served.", "check:poster-card-fits": "a poster card's print-options row must stay inside its own card. Reported by the repo agent off a 1440px screenshot: 27/27 cards overflowing by 110px, 36 neighbour intrusions. .pfs already had width:82%, so SOURCE READING SAID IT WAS FINE — flex items default to min-width:auto and refuse to shrink. Asserts BOTH no-overlap AND that the controls keep a usable box, because a row whose buttons collapsed to nothing would also \"not overflow\". Sweeps 390/768/1024/1440/1920px and asserts the row sits BELOW the preview too — measuring only left/right edges is how it passed a driver-visible regression. CARD_FIT_WIDTH overrides the list. SKIPs when nothing is served.",
@ -551,7 +551,7 @@
"check:select-zone-race": "each layer of the UPG-100 first-click select-zone fix, asserted separately so a regression names the layer it broke; four mutants fail by name. By the UPG-100 owner. Headless; in gates:pre.", "check:select-zone-race": "each layer of the UPG-100 first-click select-zone fix, asserted separately so a regression names the layer it broke; four mutants fail by name. By the UPG-100 owner. Headless; in gates:pre.",
"check:selection-kinds": "the UPG-100 selection kinds (by the UPG-100 owner, with d3ec8af7): each kind of selection the list offers resolves to the groups it names. Headless; in gates:pre.", "check:selection-kinds": "the UPG-100 selection kinds (by the UPG-100 owner, with d3ec8af7): each kind of selection the list offers resolves to the groups it names. Headless; in gates:pre.",
"check:selector-fields": "every UPG-100 selector's fields, read FROM the evaluator (not a table in this file), must be requested by every fetch that evaluates it. A missing field matches nothing and prints an EMPTY packet with no error: eight zones did, until the fetch carried wagf_region/wagf_block. Headless; in gates:pre. :self-test has 6 cases.", "check:selector-fields": "every UPG-100 selector's fields, read FROM the evaluator (not a table in this file), must be requested by every fetch that evaluates it. A missing field matches nothing and prints an EMPTY packet with no error: eight zones did, until the fetch carried wagf_region/wagf_block. Headless; in gates:pre. :self-test has 6 cases.",
"check:setup-order": "a line's POSITION in a setup block is not the MOMENT it runs, and two P0s from the Driver's console came from that gap. ARM A: `watch(..., { immediate: true })` runs during setup, so anything it reaches must be declared ABOVE it - research-map.vue threw `Cannot access '_lastFitKey' before initialization` and blanked the whole map, because the watcher called a HOISTED function 350 lines down whose own constants were still in their temporal dead zone. A hoisted function promises that its NAME resolves early, not that its BODY can run early, and that asymmetry is invisible from both ends. ARM B: a lifecycle registrar closes over Vue's current instance, which exists only while setup is on the stack, so `onMounted(async () => { await init(); onBeforeUnmount(clean) })` drops the registration silently - DashboardMapPanel.vue leaked a ResizeObserver on document.body plus a window listener, warning only, feature working, and research-map.vue had a second copy this gate found. The fix is never 'move the line': declare the handle at setup scope and tear it down in a hook registered before the first await. Its own false positives are the interesting part - it went 6 hits -> 1 over three rounds, and every one it dropped was prose or shadowing read as code: `onBeforeUnmount` inside a console.error STRING, `table` inside `new Error(`table not found (${r.status})`)` in four files, and a `const [pins, byCountry]` destructured INSIDE the function, shadowing a computed 220 lines away. So it blanks comments, string text and template text (keeping ${...}, which is code) and subtracts every name a span declares for itself. Self-tests on both shipped defects, both fixes, and all three decoys; exits 3 if any arm goes blind.", "check:setup-order": "a line's POSITION in a setup block is not the MOMENT it runs, and two P0s seen in a live console came from that gap. ARM A: `watch(..., { immediate: true })` runs during setup, so anything it reaches must be declared ABOVE it - research-map.vue threw `Cannot access '_lastFitKey' before initialization` and blanked the whole map, because the watcher called a HOISTED function 350 lines down whose own constants were still in their temporal dead zone. A hoisted function promises that its NAME resolves early, not that its BODY can run early, and that asymmetry is invisible from both ends. ARM B: a lifecycle registrar closes over Vue's current instance, which exists only while setup is on the stack, so `onMounted(async () => { await init(); onBeforeUnmount(clean) })` drops the registration silently - DashboardMapPanel.vue leaked a ResizeObserver on document.body plus a window listener, warning only, feature working, and research-map.vue had a second copy this gate found. The fix is never 'move the line': declare the handle at setup scope and tear it down in a hook registered before the first await. Its own false positives are the interesting part - it went 6 hits -> 1 over three rounds, and every one it dropped was prose or shadowing read as code: `onBeforeUnmount` inside a console.error STRING, `table` inside `new Error(`table not found (${r.status})`)` in four files, and a `const [pins, byCountry]` destructured INSIDE the function, shadowing a computed 220 lines away. So it blanks comments, string text and template text (keeping ${...}, which is code) and subtracts every name a span declares for itself. Self-tests on both shipped defects, both fixes, and all three decoys; exits 3 if any arm goes blind.",
"check:sfc-parse": "every .vue script block must parse as the BUILD parses it — catches the LIVE-CSS backtick that check:template-literal cannot see (it reads CSS comments only). 0 false positives on 83 files, where 3 lookahead heuristics scored 98 and 136.", "check:sfc-parse": "every .vue script block must parse as the BUILD parses it — catches the LIVE-CSS backtick that check:template-literal cannot see (it reads CSS comments only). 0 false positives on 83 files, where 3 lookahead heuristics scored 98 and 136.",
"check:shadow-css-parses": "the CSS we assemble as JS STRINGS must parse as CSS — brace balance and declarations stranded outside any rule. check:sfc-parse proves only that the JS parses, so it passed a stylesheet with 9 orphaned declarations and braces at 12 open / 21 close (REGLIB batch 1); nothing else in the tree validates CSS syntax. Reads every useShadowStyles() argument off the @babel/parser AST the build already uses, because three hand-rolled lexers each produced a confident FALSE RED: a /* CSS comment */ read as a declaration, an apostrophe in a .vue prose header opening a phantom string literal, and a regex literal containing a quote. 9 self-tests run inline on every invocation, including the exact batch-1 damage, so it cannot silently stop being able to fail.", "check:shadow-css-parses": "the CSS we assemble as JS STRINGS must parse as CSS — brace balance and declarations stranded outside any rule. check:sfc-parse proves only that the JS parses, so it passed a stylesheet with 9 orphaned declarations and braces at 12 open / 21 close (REGLIB batch 1); nothing else in the tree validates CSS syntax. Reads every useShadowStyles() argument off the @babel/parser AST the build already uses, because three hand-rolled lexers each produced a confident FALSE RED: a /* CSS comment */ read as a declaration, an apostrophe in a .vue prose header opening a phantom string literal, and a regex literal containing a quote. 9 self-tests run inline on every invocation, including the exact batch-1 damage, so it cannot silently stop being able to fail.",
"check:shadow-reduced-motion": "every animating library component honours prefers-reduced-motion: a page's own reset never crosses a shadow boundary, so useShadowStyles injects one per shadow root, and a component styled any other way must carry its own @media (prefers-reduced-motion: reduce) block. Self-test runs every time.", "check:shadow-reduced-motion": "every animating library component honours prefers-reduced-motion: a page's own reset never crosses a shadow boundary, so useShadowStyles injects one per shadow root, and a component styled any other way must carry its own @media (prefers-reduced-motion: reduce) block. Self-test runs every time.",
@ -566,8 +566,8 @@
"check:engine-contract": "the map must use the engine it was ASKED for or fail VISIBLY — never a substitute. resources-map read `engineGlobal(provider) || window.mapboxgl`; engineGlobal already defaults to mapbox, so that could only fire for provider 'maplibre' and then returned MAPBOX — a keyless page silently handed the engine that needs a token, which the profile then does not give it, so its tiles 401. Invisible to every other gate: the leaf mounts, renders and logs nothing. Six runtime measurements (2 providers x engine present / blocked / both blocked, CDN blocked by request routing the way a proxy or an offline kiosk does) The static arm was DELETED once check:no-silent-engine went into gates:post (main 23976fb0): two gates owning one source rule meant this leaf was checked twice and every other leaf once, and the narrower copy is the one that drifts. Mapbox arms inject a dummy MAP_TOKEN, because without one the element never mounts on a static tree and that is the environment, not the product. Engine attribution is read off the canvas class, not a debug handle. Browser gate. --poison names where the falsifiable source arm went rather than pretending to have one; the tree-wide gate self-tests with --self-test, not --poison.", "check:engine-contract": "the map must use the engine it was ASKED for or fail VISIBLY — never a substitute. resources-map read `engineGlobal(provider) || window.mapboxgl`; engineGlobal already defaults to mapbox, so that could only fire for provider 'maplibre' and then returned MAPBOX — a keyless page silently handed the engine that needs a token, which the profile then does not give it, so its tiles 401. Invisible to every other gate: the leaf mounts, renders and logs nothing. Six runtime measurements (2 providers x engine present / blocked / both blocked, CDN blocked by request routing the way a proxy or an offline kiosk does) The static arm was DELETED once check:no-silent-engine went into gates:post (main 23976fb0): two gates owning one source rule meant this leaf was checked twice and every other leaf once, and the narrower copy is the one that drifts. Mapbox arms inject a dummy MAP_TOKEN, because without one the element never mounts on a static tree and that is the environment, not the product. Engine attribution is read off the canvas class, not a debug handle. Browser gate. --poison names where the falsifiable source arm went rather than pretending to have one; the tree-wide gate self-tests with --self-test, not --poison.",
"check:link-coverage": "rule 4 of the brief — 'verify a URL resolves before shipping it' — as a gate instead of a habit. verify-gospel-links.mjs does the verifying; nothing asserted the record COVERS what is shipped, so the honest state of the corpus was a number someone had to go and measure. DISCOVERS the URLs by recursing the pin index for any http string rather than naming fields: every field list in this bundle has gone stale (ga and se shipped unscreened, the SUN block and the access table were each added after the fact), and the recursion found 16 links my own hand-rolled measurement had missed. Resolves each against THREE records — the verification table, translator-org-links.json (contacts, fetched by their own script) and sun-deaf-index.json (proved by content against a catch-all control, which is stronger than a status code) — and fails on any unrecorded link, any shipped link recorded BROKEN, and any 'unknown' on a target a status code CAN prove. The status-blind list holds one URL SHAPE, not a host: jesusfilm.org/watch/jesus/<slug> discriminates and 2,049 links depend on that, while /watch/?languageId= does not. Static, sub-second. Self-test: `bun run check:link-coverage:poison`.", "check:link-coverage": "rule 4 of the brief — 'verify a URL resolves before shipping it' — as a gate instead of a habit. verify-gospel-links.mjs does the verifying; nothing asserted the record COVERS what is shipped, so the honest state of the corpus was a number someone had to go and measure. DISCOVERS the URLs by recursing the pin index for any http string rather than naming fields: every field list in this bundle has gone stale (ga and se shipped unscreened, the SUN block and the access table were each added after the fact), and the recursion found 16 links my own hand-rolled measurement had missed. Resolves each against THREE records — the verification table, translator-org-links.json (contacts, fetched by their own script) and sun-deaf-index.json (proved by content against a catch-all control, which is stronger than a status code) — and fails on any unrecorded link, any shipped link recorded BROKEN, and any 'unknown' on a target a status code CAN prove. The status-blind list holds one URL SHAPE, not a host: jesusfilm.org/watch/jesus/<slug> discriminates and 2,049 links depend on that, while /watch/?languageId= does not. Static, sub-second. Self-test: `bun run check:link-coverage:poison`.",
"check:snapshot-guard": "every gospel-resources catalogue fetcher must write through scripts/_snapshot-guard.mjs, which refuses a write that loses more than a tenth of the previous snapshot. Written after a rate-limited FCBH run silently replaced 1,666 video scopes with 1,103. Static, sub-second. Self-test: `bun run check:snapshot-guard:poison` injects a raw write and the check must fail on it.", "check:snapshot-guard": "every gospel-resources catalogue fetcher must write through scripts/_snapshot-guard.mjs, which refuses a write that loses more than a tenth of the previous snapshot. Written after a rate-limited FCBH run silently replaced 1,666 video scopes with 1,103. Static, sub-second. Self-test: `bun run check:snapshot-guard:poison` injects a raw write and the check must fail on it.",
"check:spa-tab-order": "the cap-5 SPA must open on Big Picture and keep the Driver's order (Big Picture, UPG-100, Prayer Progress, Gospel Resources) — asserted as PRESENCE + tab-1 + RELATIVE order on the RENDERED rail, because the rail gained three sections and now renders 12 rows with others between them. ⚠ NOT WAVE-WIRED: it FAILS rather than skips when the SPA is unreachable, deliberately (\"a gate that cannot reach its subject has not passed, it has not run\") — correct standalone, but gates:post runs on checkouts with no server. Needs `bun run serve` on 4173.", "check:spa-tab-order": "the cap-5 SPA must open on Big Picture and keep the specified order (Big Picture, UPG-100, Prayer Progress, Gospel Resources) — asserted as PRESENCE + tab-1 + RELATIVE order on the RENDERED rail, because the rail gained three sections and now renders 12 rows with others between them. ⚠ NOT WAVE-WIRED: it FAILS rather than skips when the SPA is unreachable, deliberately (\"a gate that cannot reach its subject has not passed, it has not run\") — correct standalone, but gates:post runs on checkouts with no server. Needs `bun run serve` on 4173.",
"check:square-map-controls": "MapToolbar controls=\"square\" must be the ENGINE'S grouped card, not a square of ours. Reads node_modules/maplibre-gl/dist/maplibre-gl.css and compares our ported values against THAT FILE -- 4px corners, 0 0 0 2px rgba(0,0,0,.1) ring, 1px solid #ddd dividers -- because the look the Driver asked for is the engine default (DX-life adds NavigationControl and overrides nothing), so the target is published rather than a matter of taste. Asserts the three deliberate deviations: the engine's 29px is NOT adopted (touch target over pixel parity), there is NO overflow:hidden (it clips a focus ring; outer corners come from first/last child rounding, the engine's own mechanism), and a forced-colors block ships. Also asserts the BLAST RADIUS: round is the default and none of the nine MapToolbar consumers passes controls=, so no map changes -- opting one in turns it red. CSS assertions read the text passed to useShadowStyles, not the file, because the prop's doc comment quotes the engine's 29px and a checker that cannot tell a comment from a declaration finds the documentation it stands on.", "check:square-map-controls": "MapToolbar controls=\"square\" must be the ENGINE'S grouped card, not a square of ours. Reads node_modules/maplibre-gl/dist/maplibre-gl.css and compares our ported values against THAT FILE -- 4px corners, 0 0 0 2px rgba(0,0,0,.1) ring, 1px solid #ddd dividers -- because the look asked for is the engine default (DX-life adds NavigationControl and overrides nothing), so the target is published rather than a matter of taste. Asserts the three deliberate deviations: the engine's 29px is NOT adopted (touch target over pixel parity), there is NO overflow:hidden (it clips a focus ring; outer corners come from first/last child rounding, the engine's own mechanism), and a forced-colors block ships. Also asserts the BLAST RADIUS: round is the default and none of the nine MapToolbar consumers passes controls=, so no map changes -- opting one in turns it red. CSS assertions read the text passed to useShadowStyles, not the file, because the prop's doc comment quotes the engine's 29px and a checker that cannot tell a comment from a declaration finds the documentation it stands on.",
"check:stale-sweep-claim": "a caveat that has stopped being true is a wrong number. The gospel surfaces carry a paragraph saying which pools were searched; while the GRN sweep ran they truthfully said part of it was unread, the sweep finished, and the sentence stayed for four more commits. The DATA decides here: undecided gap codes mean the claim is allowed, all-decided means it must go, so it re-arms itself for the next slow pool. Does NOT flag a claim rendered under a v-if on the pending count — that form expires on its own. Self-test: `--poison`. Static, sub-second.", "check:stale-sweep-claim": "a caveat that has stopped being true is a wrong number. The gospel surfaces carry a paragraph saying which pools were searched; while the GRN sweep ran they truthfully said part of it was unread, the sweep finished, and the sentence stayed for four more commits. The DATA decides here: undecided gap codes mean the claim is allowed, all-decided means it must go, so it re-arms itself for the next slow pool. Does NOT flag a claim rendered under a v-if on the pending count — that form expires on its own. Self-test: `--poison`. Static, sub-second.",
"check:surfaces-read-the-tier": "a surface may READ the tier the pin index computed, never re-derive 'this group has nothing' from the raw resource fields. That hand-rolled field list went stale every time a source was added: 7 codes drawn as having nothing while the map showed them a link, then 28 when Scripture Earth landed. Static, sub-second. Self-test: `bun run check:surfaces-read-the-tier:poison` re-injects the old formula into every surface and each must be caught.", "check:surfaces-read-the-tier": "a surface may READ the tier the pin index computed, never re-derive 'this group has nothing' from the raw resource fields. That hand-rolled field list went stale every time a source was added: 7 codes drawn as having nothing while the map showed them a link, then 28 when Scripture Earth landed. Static, sub-second. Self-test: `bun run check:surfaces-read-the-tier:poison` re-injects the old formula into every surface and each must be caught.",
"check:tabs-localized": "a chosen locale must CHANGE the rendered tab text. check:i18n-parity compares catalogs to each OTHER, so it cannot tell a shipped translation from a dead one; tabs.json is reached only via a DYNAMIC key (SemanticTreeLegend.vue:40), invisible to any grep. Renders it and reads the result.", "check:tabs-localized": "a chosen locale must CHANGE the rendered tab text. check:i18n-parity compares catalogs to each OTHER, so it cannot tell a shipped translation from a dead one; tabs.json is reached only via a DYNAMIC key (SemanticTreeLegend.vue:40), invisible to any grep. Renders it and reads the result.",

View file

@ -184,7 +184,7 @@ export default defineConfig(({ command, mode }) => {
// port. `--host` on the command line still overrides this for a deliberate LAN session. // port. `--host` on the command line still overrides this for a deliberate LAN session.
// Local plugin backends (backend/, e.g. rudder-engine) are started by backendDevPlugin and reached // Local plugin backends (backend/, e.g. rudder-engine) are started by backendDevPlugin and reached
// through these same-origin routes, so a plugin calls /api/<name>/… in dev exactly as on desktop. // through these same-origin routes, so a plugin calls /api/<name>/… in dev exactly as on desktop.
// NOT WATCHING public/ (the Driver's first laptop run with DX_SYNC_REMOTE, 2026-09-28): a landing rewrites // NOT WATCHING public/ (the first laptop run with DX_SYNC_REMOTE, 2026-09-28): a landing rewrites
// dozens of built leaf index.html files there, and Vite answers each changed .html with a FULL page reload // dozens of built leaf index.html files there, and Vite answers each changed .html with a FULL page reload
// (measured: one edited leaf page -> one full-reload broadcast), so a pull fired a burst of reloads while // (measured: one edited leaf page -> one full-reload broadcast), so a pull fired a burst of reloads while
// git was still writing and the page went white. The dev server never serves public/ through its module // git was still writing and the page went white. The dev server never serves public/ through its module

View file

@ -31,8 +31,8 @@
"are byte-identical to data/posters/, so git stores each file once. check:poster-print-fallback asserts it." "are byte-identical to data/posters/, so git stores each file once. check:poster-print-fallback asserts it."
], ],
"//token": [ "//token": [
"THE PUBLIC MAPBOX KEY, BAKED ON PURPOSE (Driver ruling 2026-09-21): 'all maps can use the pk", "THE PUBLIC MAPBOX KEY, BAKED ON PURPOSE (ruled 2026-09-21): every map may use the pk from the",
"from the api, no problem \u2014 if they want to set a mapbox sk they can'. This is token-ladder", "API, and a host that wants to set a Mapbox sk can. This is token-ladder",
"RUNG 3 (see internal/generate-tree.mjs:595): rungs 1-3 are used DIRECTLY with no network call,", "RUNG 3 (see internal/generate-tree.mjs:595): rungs 1-3 are used DIRECTLY with no network call,",
"so baking it makes every map work out of the box on ANY host \u2014 including one with no", "so baking it makes every map work out of the box on ANY host \u2014 including one with no",
"/api/maps/token endpoint and no CORS on the fallback, which is exactly why maps on the live test host", "/api/maps/token endpoint and no CORS on the fallback, which is exactly why maps on the live test host",
@ -41,5 +41,5 @@
"than copying this literal around. A pk. is a PUBLIC key protected by URL restrictions, not by", "than copying this literal around. A pk. is a PUBLIC key protected by URL restrictions, not by",
"secrecy \u2014 never put an sk. here; check:public-hygiene still fails the build on sk./tk." "secrecy \u2014 never put an sk. here; check:public-hygiene still fails the build on sk./tk."
], ],
"//tokenUrl-2026-09-22": "EMPTIED 2026-09-22. It pointed at https://doxa.life/api/maps/token, which serves a URL-RESTRICTED token \u2014 tiles 403 from every origin except doxa.life, so the basemap rendered as an empty void while pins drew fine (the Driver's exact report: 'it's loading the pins, but it's not loading the actual underlying map'). That endpoint sits ABOVE the baked token in the ladder, so leaving it set would fetch the broken token and overwrite the good one. Measured: the baked token = 200 from no-Referer, the live test host AND localhost; the domain-restricted token = 403 from all three." "//tokenUrl-2026-09-22": "EMPTIED 2026-09-22. It pointed at https://doxa.life/api/maps/token, which serves a URL-RESTRICTED token \u2014 tiles 403 from every origin except doxa.life, so the basemap rendered as an empty void while pins drew fine (reported exactly so: the pins load, but not the underlying map). That endpoint sits ABOVE the baked token in the ladder, so leaving it set would fetch the broken token and overwrite the good one. Measured: the baked token = 200 from no-Referer, the live test host AND localhost; the domain-restricted token = 403 from all three."
} }