Guard the map option a de-duplication can silently invert

check:gospel-whiteboard does not cover the adoption in the previous commit. I
poisoned GiMap into whiteboard:true — the exact regression that blanks the
production geographic map — and it still exited 0. It asserts chart geometry
(projection, radii, the bounded gap) and imports no component, so a call-site
option is invisible to it. Its pass was never evidence for this change.

check:board-map-options asserts what actually regresses: every
createWhiteboardMap call states `whiteboard` explicitly, and a geographic caller
(whiteboard:false) also states `boundaries`. A caller that inherits defaults
gets the whiteboard's, which are the inverse of a real map's.

Two-sided, because a check that cannot fail is not evidence: poisoned, it exits
1 naming GiMap.vue; restored, 0. Both negative controls run in the gate itself —
a call omitting the option must be judged non-compliant, and a compliant call
must not trip it, or the gate would just ban the helper.

Registered in all three places: script, gates:pre, //scripts. gates:pre is 77.
This commit is contained in:
Doxa Maps 2026-09-29 11:07:19 +00:00
parent c00b0b4b5d
commit 18e5dc8fdf
2 changed files with 85 additions and 3 deletions

View file

@ -0,0 +1,80 @@
#!/usr/bin/env node
/**
* check-board-map-options.mjs — the GEOGRAPHIC caller must state its map options, never inherit them.
*
* WHY THIS EXISTS. gospel-internet had its own copy of the map helper (src/boardEngine.js) whose
* defaults were whiteboard:false / boundaries:true / centre [60,20] / zoom 1.4 — the values a real map
* needs. The library's createWhiteboardMap was extracted for the WHITEBOARD case, so its defaults are
* the exact inverse: whiteboard:true / boundaries:false / [0,0] / zoom 1. Adopting the library while
* GiMap still relied on defaults would have hidden every basemap layer and dropped boundary
* attribution — a blank map in production, from a change that reads like a pure de-duplication.
*
* ★ WHY A NEW GATE RATHER THAN check:gospel-whiteboard. I poisoned GiMap into whiteboard mode and
* check:gospel-whiteboard still exited 0. It asserts chart GEOMETRY (projection, radii, the bounded
* gap) and never imports a component, so it cannot see a call-site option. A pass from a check that
* cannot fail is not evidence, so this asserts the thing that actually regresses.
*
* The rule: every createWhiteboardMap call must pass `whiteboard` EXPLICITLY. A whiteboard caller
* passing true is inheriting nothing; the geographic caller passing false is stating its intent. Either
* way the call reads the same against either module, which is what makes the de-duplication safe.
*/
import { readFileSync, readdirSync } from 'node:fs'
import { join } from 'node:path'
const DIR = 'plugin-designs/gospel-internet/src/components'
const CALL = /createWhiteboardMap\s*\(/g
const pass = []
const fail = []
const ok = (label, cond, detail = '') => (cond ? pass : fail).push(detail ? `${label} — ${detail}` : label)
/** The option object of a call, by brace matching from the call's opening paren. */
function callOptions(src, at) {
let depth = 0
let i = src.indexOf('(', at)
const start = i
for (; i < src.length; i++) {
const c = src[i]
if (c === '(') depth++
else if (c === ')') { depth--; if (depth === 0) break }
}
return src.slice(start, i + 1)
}
const files = readdirSync(DIR).filter((f) => f.endsWith('.vue'))
let calls = 0
for (const f of files) {
const src = readFileSync(join(DIR, f), 'utf8')
for (const m of src.matchAll(CALL)) {
calls++
const args = callOptions(src, m.index)
// `whiteboard:` must appear in the call itself, not be left to the module's default.
ok(`${f}: createWhiteboardMap states whiteboard: explicitly`, /\bwhiteboard\s*:/.test(args),
args.replace(/\s+/g, ' ').slice(0, 90))
// A geographic caller (whiteboard:false) must also state boundaries, or it silently loses
// boundary attribution to the library default of false.
if (/\bwhiteboard\s*:\s*false/.test(args)) {
ok(`${f}: the geographic caller states boundaries: explicitly`, /\bboundaries\s*:/.test(args))
}
}
}
ok('every call site was found and checked', calls >= 4, `${calls} call(s) across ${files.length} components`)
// NEG CONTROL: the matcher must actually be able to SEE a missing option, or every arm above is
// vacuous. Run it over a call that omits whiteboard and require it to be judged non-compliant.
const bad = "await createWhiteboardMap(el.value, cfg, { token: tk })"
ok('NEG CONTROL: a call omitting whiteboard: is detected',
!/\bwhiteboard\s*:/.test(callOptions(bad, bad.indexOf('createWhiteboardMap'))))
// And a compliant call must NOT trip it, or the gate would just ban the helper.
const good = "await createWhiteboardMap(el.value, cfg, { whiteboard: false, boundaries: true, token: tk })"
ok('NEG CONTROL: a compliant call passes',
/\bwhiteboard\s*:/.test(callOptions(good, good.indexOf('createWhiteboardMap'))))
for (const p of pass) console.log(` ✓ ${p}`)
for (const f of fail) console.log(` ✗ ${f}`)
if (fail.length) {
console.log(`\nBOARD-MAP-OPTIONS: FAIL — ${fail.length} of ${pass.length + fail.length}`)
process.exit(1)
}
console.log(`\nBOARD-MAP-OPTIONS: PASS — ${pass.length} assertions over ${calls} call sites`)

View file

@ -328,7 +328,7 @@
"fix:sri": "node internal/fix-sri.mjs",
"gallery": "node internal/generate-gallery.mjs",
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:snippet-engine check:snippet-engine:self-test",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:host-locale-fetch check:print-frame-reclaim",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:host-locale-fetch check:print-frame-reclaim",
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
"library-index": "node internal/generate-library-index.mjs",
@ -377,7 +377,8 @@
"check:people-group-records": "bun internal/check-people-group-records.mjs",
"check:poster-browser-equiv": "bun internal/check-poster-browser-equiv.mjs",
"check:poster-render-pinned": "bun internal/check-poster-render-pinned.mjs",
"pin:poster-render": "node library/scripts/posters/pin-poster-render.mjs"
"pin:poster-render": "node library/scripts/posters/pin-poster-render.mjs",
"check:board-map-options": "node internal/check-board-map-options.mjs"
},
"//scripts": {
"_": "What each script does. These notes used to sit inside the command strings as trailing \"# …\" comments, which made the shell discard any arguments passed after \"--\" (so a gate given --url silently measured its hardcoded default port — possibly another agent's server). Keep notes here, never in a command.",
@ -613,7 +614,8 @@
"check:people-group-records": "the records half of the print standard selects EXACTLY the set it is asked for, two-sided: a country selector returns that country and not another, an unknown selector returns nothing rather than every group on earth, one fetch per language serves every item, and a failed fetch is not cached so the next click retries.",
"check:poster-browser-equiv": "a map sheet GENERATED IN THE BROWSER (library/packet/posterSheets.js, the print standard: generate at the click) is byte-identical to the one the Node generator writes, given the same records: it runs the real generate.mjs for one UPG-100 zone (the map-only packet sheet, an India zone so the ADM1 path runs) and one country, draws both through the browser feed (the SERVED boundaries, data/zone-sheet.js, the library name table) and compares bytes. The drawing code is one factory (poster-render.js); this guards the FEED, which is what can still drift. NOT MEASURED without the generator's offline records cache.",
"check:poster-render-pinned": "the map-sheet generator a browser imports at print time (served at assets/lib/poster-render.js, not bundled) is integrity-pinned like every embed, because it executes on partner pages: the pin in library/packet/poster-render.integrity.js equals the sha384 of the source and of the served copy (a stale pin names `bun run pin:poster-render`), a TAMPERED copy is refused and its code never runs (a planted side effect stays unset), and the genuine copy loads and draws.",
"pin:poster-render": "rewrite library/packet/poster-render.integrity.js from library/packet/poster-render.js — run after any change to the generator; check:poster-render-pinned fails until it is."
"pin:poster-render": "rewrite library/packet/poster-render.integrity.js from library/packet/poster-render.js — run after any change to the generator; check:poster-render-pinned fails until it is.",
"check:board-map-options": "the GEOGRAPHIC map caller must state whiteboard/boundaries EXPLICITLY, never inherit them. library/maps/whiteboard-map.js defaults to whiteboard:true/boundaries:false (it was extracted for the whiteboard case), the inverse of what a real map needs, so a caller relying on defaults renders a blank basemap. check:gospel-whiteboard cannot see this — it asserts chart geometry and imports no component."
},
"devDependencies": {
"@capacitor/android": "^7.6.9",