check:auto-engine asserts the rule that holds, and runs: it sat in no chain asserting the old one

Its case 2 expected a host token alone to draw Mapbox, the rule until 2026-09-28. It was in no
chain, so it never ran against the change that made that false, and nothing else measured the case
the change is about. The cases now:
  1 no token: MapLibre, zero Mapbox requests, painted, no prompt (unchanged)
  2 a host token alone: still MapLibre, painted, zero Mapbox requests
  3 ?provider=mapbox with that token: Mapbox, carrying that token
It joins gates:post beside check:leaf-engine-per-provider, together with check:engine-contract
(gospel's resources-map uses the engine it was asked for, or fails visibly), which was also in no
chain. Both are browser gates: NOT MEASURED outside the browser slot, measured in it.
This commit is contained in:
Doxa Maps 2026-09-29 10:33:45 +00:00
parent ba619b78bf
commit 10eaceedf2
2 changed files with 28 additions and 22 deletions

View file

@ -1,21 +1,28 @@
#!/usr/bin/env node
/**
* check-auto-engine — the default engine is 'auto': Mapbox WHEN a token is available, MapLibre otherwise.
* check-auto-engine — the default engine ('auto') is MapLibre, token or not; Mapbox only when asked for.
*
* WHY (2026-09-25, by project decision). The public repo carries no Mapbox token. With a default of
* 'mapbox', a keyless visitor got a map that could not draw and a paste-a-token prompt, and the only
* way out was a token in the repo. 'auto' asks the token ladder instead (useMapProvider.js), and the
* generated leaf asks the same ladder before it mounts (generate-tree.mjs, PROVIDER).
* WHY. The public repo carries no Mapbox token, so a default of 'mapbox' gave a keyless visitor a map
* that could not draw. From 2026-09-25 'auto' asked the token ladder (a token meant Mapbox); since
* 2026-09-28 it is MapLibre whether or not a token is present (useMapProvider.js, resolveMapProvider):
* holding a token is a capability, choosing Mapbox is a decision. A token still authenticates a map
* that asked for Mapbox, and place search uses one on either engine.
*
* ⚠ THIS GATE ASSERTED THE OLD RULE FOR A DAY AND NOBODY SAW: it sat in no chain, so its case 2
* (a host token draws Mapbox) never ran against the change that made it false. It is now in
* gates:post, where it is NOT MEASURED outside the browser slot and runs in it.
*
* THREE CASES, on one map leaf of THIS tree (or --dir), each in a fresh browser context:
* 1 NO TOKEN the map draws with MapLibre, makes ZERO requests to any *.mapbox.com host, and
* PAINTS (distinct colours on the canvas, not a blank rectangle).
* 2 HOST TOKEN the host sets window.MAP_TOKEN before the page runs: the map draws with Mapbox,
* and THAT token is the access_token on its requests to Mapbox.
* 3 EXPLICIT WINS the same host token, with ?provider=maplibre: MapLibre, zero Mapbox requests.
* The token in case 2 is a well-formed FAKE, built at run time; Mapbox requests that carry it are
* recorded and aborted, never sent. The engine is read from the canvas that drew
* (mapboxgl-canvas / maplibregl-canvas), not from which globals happen to exist.
* 1 NO TOKEN the map draws with MapLibre, makes ZERO requests to any *.mapbox.com host, and
* PAINTS (distinct colours on the canvas, not a blank rectangle).
* 2 HOST TOKEN ALONE the host sets window.MAP_TOKEN before the page runs and names no provider: the
* map STILL draws with MapLibre, paints, and makes ZERO Mapbox requests. This is
* the case the 09-28 rule exists for.
* 3 ASKED FOR MAPBOX the same host token with ?provider=mapbox: the map draws with Mapbox, and THAT
* token is the access_token on its requests to Mapbox.
* The token is a well-formed FAKE, built at run time; Mapbox requests that carry it are recorded and
* aborted, never sent. The engine is read from the canvas that drew (mapboxgl-canvas /
* maplibregl-canvas), not from which globals happen to exist.
*
* USAGE node internal/check-auto-engine.mjs [--dir <tree root>] [--leaf <bundle>/<profile>]
* Run through internal/browser-slot.sh.
@ -108,8 +115,8 @@ async function measure({ hostToken = '', query = '' }) {
const results = []
try {
results.push({ name: '1 no token', r: await measure({}) })
results.push({ name: '2 host token', r: await measure({ hostToken: FAKE_TOKEN }) })
results.push({ name: '3 explicit maplibre + token', r: await measure({ hostToken: FAKE_TOKEN, query: '?provider=maplibre' }) })
results.push({ name: '2 host token alone', r: await measure({ hostToken: FAKE_TOKEN }) })
results.push({ name: '3 provider mapbox + token', r: await measure({ hostToken: FAKE_TOKEN, query: '?provider=mapbox' }) })
} finally { await browser.close(); stop() }
let bad = 0, unmeasured = 0
@ -118,17 +125,16 @@ for (const { name, r } of results) {
if (r.err) { console.log(` ?? ${name.padEnd(30)} NOT MEASURED — ${r.err}`); unmeasured++; continue }
const withToken = r.mapbox.filter((u) => u.searchParams.has('access_token'))
const summary = `engine ${r.engine}, ${r.mapbox.length} Mapbox request(s)${r.prompt ? ', TOKEN PROMPT shown' : ''}`
if (name.startsWith('1')) {
if (name.startsWith('1') || name.startsWith('2')) {
// The same verdict for both: a token that is merely PRESENT must change nothing about the engine.
say(r.engine === 'maplibre' && r.mapbox.length === 0 && r.colours >= MIN_COLOURS && !r.prompt, name,
`${summary}, ${r.colours} colours (floor ${MIN_COLOURS}) — want maplibre, 0 Mapbox requests, painted, no prompt`)
} else if (name.startsWith('2')) {
} else {
const carries = withToken.length > 0 && withToken.every((u) => u.searchParams.get('access_token') === FAKE_TOKEN)
say(r.engine === 'mapbox' && carries, name,
`${summary}, ${withToken.length} carry a token${withToken.length && !carries ? ' — NOT the host token' : ''} — want mapbox, the host token on its requests`)
} else {
say(r.engine === 'maplibre' && r.mapbox.length === 0, name, `${summary} — want maplibre, 0 Mapbox requests`)
}
}
if (bad) { console.log(`AUTO-ENGINE: FAIL — ${bad} of 3 case(s) do not hold.`); process.exit(1) }
if (unmeasured) { console.log(`AUTO-ENGINE: NOT MEASURED — ${unmeasured} case(s) could not be judged.`); process.exit(75) }
console.log('AUTO-ENGINE: PASS — no token draws MapLibre keyless; a host token draws Mapbox with that token; an explicit provider wins.')
console.log('AUTO-ENGINE: PASS — the default draws MapLibre keyless with or without a host token; provider mapbox draws Mapbox with that token.')

View file

@ -327,7 +327,7 @@
"dev": "bun x vite",
"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: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: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: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: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: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",
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
@ -387,7 +387,7 @@
"check:a11y-button-names": "every VISIBLE button must have an accessible name (aria-label / aria-labelledby / visible text — a `title` is a TOOLTIP and does NOT count). Measures the RENDERED DOM through shadow roots: a source grep cannot answer this (132 `<button>` hits, most with visible text). Needs a browser, so NOT in build:unlocked and NOT in CI. ALL=1 sweeps every profile.",
"check:mobile-gating": "one phone flag per map (SPEC-MOBILE-FLAG): every <profile>.meta.json `mobile` is desktop-only or larger-screens; every flagged profile's leaf page carries its flag (and no leaf carries one its meta dropped); the Research Gallery derives its rules from the manifest, holds no id set, and mounts nothing on a phone before the flags land; dashboard-plus-grid stays desktop-only; the three notice sentences exist in all 11 locales and the desktop-only one names the map; ProfileLoader blocks, advises, and stays quiet inside the Gallery. --self-test plants one bad case per rule.",
"check:approx-pin-note": "people-group pins say they are localities, not towns or boundaries, in ONE shared wording (library/components/legends/ApproxPinNote.vue): the colocation index finds groups sharing a coordinate, the note names them only for a shared point, never prints a raw coordinate, and renders from each locale's catalogue. Headless.",
"check:auto-engine": "BROWSER. The default engine is auto: on one map leaf, no token draws with MapLibre and makes zero api.mapbox.com requests, a token draws with Mapbox, and an explicit provider wins. Under the fleet browser policy it is a rega run, not part of gates:post.",
"check:auto-engine": "BROWSER. The default engine (auto) is MapLibre, token or not: on one map leaf, no token AND a host token alone both draw with MapLibre, paint, and make zero api.mapbox.com requests; ?provider=mapbox with that token draws Mapbox and carries it. In gates:post, NOT MEASURED outside the browser slot.",
"check:no-new-driver-references": "shipped text (library, plugins, internal, docs, skills, README) neither quotes nor cites the Driver: a line that refers to the Driver and is not in internal/fixtures/driver-references-baseline.json FAILS. Forward-only: the baseline, keyed by file and line text with counts (not line numbers), may only shrink as owners sweep their areas (--shrink tidies it; it never grows). Matches the role word only, not a GPU or device driver. Node only.",
"check:plural-forms": "the three things a CLDR pluralRules map can silently break. (1) FORM COUNT: a locale must author as many forms as it has CLDR categories or the pick clamps onto the wrong one (ru 4, ar 6). (2) THE tStatic PATH: seven bundles install no vue-i18n and read through tStatic, which does NO plural selection, so a pipe key there reaches the page as the literal \"a | b | c\". (3) ZERO: a locale with an explicit CLDR zero category must author it. Written as the precondition for installing the map, and kept as the guard after.",
"check:backlog-refs": "a card may not depend on a card that does not exist. A dangling depends_on can never be done, so the card can never become ready and nothing says so — it reads as ordinary blocked work forever. FAILS only for depends_on on an OPEN card; a dangling parent or a stale link on a done card is REPORTED, because failing on filing debt teaches people to delete the reference rather than restore the card. Reads all three phase backlogs — references legitimately cross phases. SKIPs when _qa-cycle is absent.",