doxa-maps/internal/check-no-silent-engine-substitution.mjs
Doxa Maps ee0bf338d1 My own gate was accusing the one agent who fixed the defect
check:no-silent-engine reported gospel-resources/resources-map.vue as the last remaining offender at
main 9df086e5. It is not, and has not been since their landing: line 1513 reads
`const gl = engineGlobal(grProvider)` with no `||` at all. I relayed the red to dx-shepherd as fact
before looking at the line, and the line was PROSE.

★ WHAT IT WAS MATCHING. gospel documented what they removed, in a /* */ block whose second line begins
with a BACKTICK:
      `engineGlobal(grProvider) || window.mapboxgl`. engineGlobal already defaults to mapbox, so
The skip asked only whether a line STARTS with //, * or /*. That is not what a comment is: it misses
every CONTINUATION line of a block comment whose text begins with something else. So the gate now
tracks block-comment state instead, and the --self-test carries gospel's exact shape as a regression
case — plus its inverse, a real offender after a closed block, because fixing a false positive by
skipping MORE is only correct if the true positives still fail. Both assert in the same run.

★ AND THIS IS THE WORST FAILURE MODE A GATE CAN HAVE, which is why it gets a commit of its own rather
than a quiet one-line patch. It told the one agent who did the right thing that they had done the wrong
thing, and the cheapest way to make it green was to DELETE the explanation — so the gate actively
attacked the reasoning it exists to protect. This file's own header quotes the bad pattern for exactly
that reason; the skip was there from the start and was simply too shallow to cover the form someone
else actually used.

⚠ AND IT MADE A STALE CARD LOOK LIVE. The offender's line number moved 1490 -> 1505 -> 1509 across
three landings purely because their file grew, so each re-read produced a plausible new number for a
defect that had already been fixed. A card that names file:line ages into a false claim silently; the
gate has to be the source of truth, and it was lying too.

Tree-wide result now: 6 files call engineGlobal(), PASS — every one keeps the engine its provider asked
for. The gate is finally green on a green tree, which means it can be wired into gates:post. That wiring
is shared config (rule 5B) so it is NOT in this commit: prepared for studio separately.
2026-09-27 01:59:11 +00:00

216 lines
12 KiB
JavaScript

#!/usr/bin/env node
/**
* check-no-silent-engine-substitution — a profile must never fall back to a DIFFERENT map engine than
* the one its provider asked for.
*
* THE DEFECT, found by regions' C3 read and routed by dx-shepherd (END-SHEP-C3C, 2026-09-26):
*
* const gl = engineGlobal(provider) || window.mapboxgl
*
* That `||` looks like ordinary defensive coding and is not. engineGlobal() ALREADY resolves mapbox's own
* global for the mapbox case — useMapInstance.js:115 is
* `ENGINES[provider]?.global || ENGINES.mapbox.global`, which falls back to mapbox's NAME only for an
* UNKNOWN provider name, never for a missing engine. So the expression can fire in exactly ONE situation:
* the page asked for 'maplibre' and MapLibre failed to load. It then hands back MAPBOX — the token-gated
* engine — to a page that explicitly asked for the keyless one, breaking the keyless contract
* research-map.instances.json documents.
*
* ★ AND THE SUBSTITUTE IS BROKEN BY CONSTRUCTION, which is what makes this worth a gate rather than a
* one-line fix. These profiles gate `gl.accessToken = token` on `if (!isMaplibre)`, so a maplibre request
* that fell through to Mapbox got Mapbox WITHOUT a token: 401s on every tile instead of a legible failure.
* A fallback that cannot produce a working map even in principle is strictly worse than no fallback,
* because it converts a diagnosable error into a mysterious one.
*
* ⚠ WHY THE THREE EXISTING ENGINE GATES DID NOT CATCH IT, and why this is not a duplicate of them:
* check-leaf-engine-per-provider — a leaf loads the engine its provider asks for
* check-provider-switch — the page-level switch reaches a raw-built map
* check-auto-engine — 'auto' resolves to Mapbox with a token, MapLibre without
* All three assert the HAPPY PATH: what happens when engines load. This bug only appears when one does
* NOT load, so all three were green for as long as it existed. The property here is the negative one —
* "when the requested engine is absent, no other engine is substituted" — and nothing asserted it.
*
* ⚠ IT IS A SOURCE CHECK, ON PURPOSE. Reproducing it at the rendered layer means serving a leaf with
* MapLibre deliberately blocked, which is a browser run for a defect whose whole signature is one
* expression. The source is the authoritative place: the fallback either appears in the file or it does
* not. A browser gate here would cost a slot to measure something a grep decides.
*
* Usage: node internal/check-no-silent-engine-substitution.mjs
* node internal/check-no-silent-engine-substitution.mjs --self-test
*/
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const HERE = dirname(fileURLToPath(import.meta.url))
const REPO = process.env.DOXA_REPO || resolve(HERE, '..')
const ROOT = join(REPO, 'plugin-designs')
// ⚠ FLAGS FILTERED FROM THE POSITIONAL READ. A bare `--self-test` was otherwise taken as a path, which
// has bitten this repo three times (--self-test read as a leaf, --emit read as a baseUrl, --poison read
// as a packet).
const SELF_TEST = process.argv.includes('--self-test')
// The substitution to refuse: engineGlobal(...) OR-ed with a specific engine's global. Any of these
// named globals on the right of a `||` after engineGlobal is a cross-engine fallback by definition —
// engineGlobal already returns the RIGHT global for the requested provider, so the only thing an `||`
// can add is a DIFFERENT engine.
const ENGINE_GLOBALS = ['mapboxgl', 'maplibregl']
const PATTERN = new RegExp(
String.raw`engineGlobal\s*\([^)]*\)\s*\|\|\s*(?:window\.)?(${ENGINE_GLOBALS.join('|')})\b`,
'g',
)
function scan(text) {
const hits = []
const lines = text.split('\n')
// ⚠ BLOCK-COMMENT STATE, not a per-line prefix test. The first version asked only whether a line
// STARTS with //, * or /*, and that is not what a comment is: it misses every CONTINUATION line of a
// /* … */ block whose text happens to begin with something else.
//
// IT COST A FALSE ACCUSATION, 2026-09-27, and the accusation was aimed at the correct fix. gospel
// fixed this defect in resources-map.vue — `const gl = engineGlobal(grProvider)`, no `||` — and
// documented what they removed in a /* */ block whose second line begins with a BACKTICK:
// `engineGlobal(grProvider) || window.mapboxgl`. engineGlobal already defaults to mapbox, so
// The prefix test saw no comment marker, matched the quoted pattern, and reported gospel as the last
// remaining offender at main 9df086e5. I relayed that to dx-shepherd as fact and it was wrong: the
// line number had also moved three times (1490 -> 1505 -> 1509) purely because their file grew, which
// is what made a stale card look like a live defect.
// ★ A GATE THAT FAILS ON THE EXPLANATION OF A FIX IS WORSE THAN NO GATE. It tells the one person who
// did the right thing that they did the wrong thing, and the cheapest way to make it green is to
// DELETE the explanation — so the gate actively destroys the reasoning it should protect. This file's
// own header quotes the pattern for the same reason, which is why the skip existed at all; it was
// just too shallow to cover the form another agent actually used.
let inBlock = false
lines.forEach((line, i) => {
const t = line.trim()
const wasInBlock = inBlock
// Track /* and */ on this line so the NEXT line knows where it stands. Deliberately simple: a
// block marker inside a string literal would fool it, and the alternative is a JS parser for a
// one-expression grep. The failure mode of over-skipping is a missed offender, which the
// --self-test guards against by keeping real forms in the MUST-MATCH list.
const opens = (line.match(/\/\*/g) || []).length
const closes = (line.match(/\*\//g) || []).length
if (opens > closes) inBlock = true
else if (closes > opens) inBlock = false
if (wasInBlock || t.startsWith('//') || t.startsWith('*') || t.startsWith('/*')) return
PATTERN.lastIndex = 0
let m
while ((m = PATTERN.exec(line)) !== null) hits.push({ line: i + 1, engine: m[1], text: t.slice(0, 110) })
})
return hits
}
if (SELF_TEST) {
// ★ PROVE THE PATTERN FIRES BEFORE TRUSTING A GREEN RUN. A source gate that has never matched anything
// is indistinguishable from a broken regex, and this one's whole value is that it matches.
const should = [
'const gl = engineGlobal(upgProvider) || window.mapboxgl',
' const gl = engineGlobal(p)||maplibregl',
'let gl = engineGlobal( provider ) || window.maplibregl;',
]
const shouldNot = [
'const gl = engineGlobal(upgProvider)',
'// const gl = engineGlobal(p) || window.mapboxgl <- the removed fallback',
'const gl = engineGlobal(p) || null',
'const token = mapboxToken?.value || profileConfig?.value?.tk || \'\'',
]
// ★ MULTI-LINE CASES, because the single-line list above cannot express the bug that actually
// happened: a CONTINUATION line of a /* */ block. Each entry is scanned as one text so the
// block-comment state machine is what is under test, not the prefix check.
const shouldNotMultiline = [
// gospel's real fix at resources-map.vue, verbatim in shape — the line that was falsely accused
// begins with a BACKTICK, so no prefix test could ever have skipped it.
[` /* ⚠ NEVER SWITCH ENGINES SILENTLY. This used to read
\`engineGlobal(grProvider) || window.mapboxgl\`. engineGlobal already defaults to mapbox, so
the fallback could only ever fire for provider 'maplibre'. */
const gl = engineGlobal(grProvider)`, 'a /* */ block quoting the removed fallback, then the correct fix'],
// and the inverse: a real offender AFTER a block comment closes must still be caught, or the fix
// above would have traded a false positive for a false negative.
[` /* explanation that mentions engineGlobal(p) || window.mapboxgl and then ends */
const gl = engineGlobal(p) || window.mapboxgl`, null],
]
let bad = 0
for (const s of should) {
const n = scan(s).length
console.log(` ${n ? 'MATCHED ' : 'MISSED '} ${s.trim().slice(0, 62)}`)
if (!n) bad++
}
for (const s of shouldNot) {
const n = scan(s).length
console.log(` ${n ? 'FALSE+ ' : 'ignored '} ${s.trim().slice(0, 62)}`)
if (n) bad++
}
// The multi-line pairs: a label means "must be IGNORED and here is why", null means "must MATCH".
// Both directions are asserted together on purpose — fixing the false positive by skipping more is
// only correct if a real offender after the block still fails.
for (const [text, why] of shouldNotMultiline) {
const n = scan(text).length
const wantHit = why === null
const ok = wantHit ? n > 0 : n === 0
if (!ok) bad++
console.log(` ${ok ? (wantHit ? 'MATCHED ' : 'ignored ') : (wantHit ? 'MISSED ' : 'FALSE+ ')} `
+ `[multi-line] ${wantHit ? 'a real offender AFTER a closed block comment' : why}`)
}
if (bad) {
console.error(`\nNO-SILENT-ENGINE-SUBSTITUTION --self-test: FAIL — ${bad} case(s) wrong.`)
process.exit(1)
}
console.log('\nNO-SILENT-ENGINE-SUBSTITUTION --self-test: PASS — the pattern matches every real form '
+ 'and ignores comments, a null fallback and an unrelated token chain.')
process.exit(0)
}
if (!existsSync(ROOT)) {
console.log(`NO-SILENT-ENGINE-SUBSTITUTION: NOT MEASURED — no plugin-designs tree at ${ROOT}`)
process.exit(75)
}
const findings = []
let scanned = 0
const walk = (dir) => {
for (const e of readdirSync(dir, { withFileTypes: true })) {
const p = join(dir, e.name)
if (e.isDirectory()) {
if (e.name === 'node_modules' || e.name === 'dist' || e.name.startsWith('.')) continue
walk(p)
} else if (/\.(vue|js|mjs)$/.test(e.name)) {
let text
try { text = readFileSync(p, 'utf8') } catch { continue }
if (!text.includes('engineGlobal')) continue
scanned++
for (const h of scan(text)) findings.push({ file: p.slice(REPO.length + 1), ...h })
}
}
}
walk(ROOT)
console.log('NO-SILENT-ENGINE-SUBSTITUTION')
console.log(` ${scanned} file(s) call engineGlobal()`)
// ⚠ REFUSE TO JUDGE rather than pass vacuously. If nothing in the tree calls engineGlobal at all, the
// pattern had nothing to match and a green result would mean "I found no files", not "the code is clean".
if (!scanned) {
console.log('NO-SILENT-ENGINE-SUBSTITUTION: NOT MEASURED — no file calls engineGlobal(), so there was '
+ 'nothing to check. Exit 75 rather than 0: "found nothing" is not "found nothing wrong".')
process.exit(75)
}
if (findings.length) {
console.error(`\nNO-SILENT-ENGINE-SUBSTITUTION: FAIL — ${findings.length} silent engine substitution(s).`)
for (const f of findings) {
console.error(` ✗ ${f.file}:${f.line} falls back to ${f.engine}`)
console.error(` ${f.text}`)
}
console.error('\n engineGlobal() ALREADY returns the right global for the requested provider, so an `||`')
console.error(' after it can only supply a DIFFERENT engine — and it fires only when the page asked for')
console.error(' maplibre and maplibre failed to load. A page that asked for the KEYLESS engine then')
console.error(' receives the TOKEN-GATED one, and because accessToken is gated on !isMaplibre it')
console.error(' receives it WITHOUT a token: 401s instead of a legible error.')
console.error('\n FIX: delete the fallback. Keep the requested engine, and let the profile\'s existing')
console.error(' !gl branch fail visibly — console.warn plus the map\'s error/empty state, never a blank,')
console.error(' and never another engine. Name the engine in the message so a partner can tell an')
console.error(' honoured request from a silent downgrade.')
process.exit(1)
}
console.log('NO-SILENT-ENGINE-SUBSTITUTION: PASS — every engineGlobal() call keeps the engine its provider '
+ 'asked for.')