Gate 9 was accusing a working map: ask the engine, not the DOM

rega's gates:post run reported this gate failing its mapbox half — "NO map
constructed", 21 api.mapbox.com requests, 0 bars. Two theories followed and both
were wrong, mine included. rega then MEASURED the leaf on both token rungs: 1
Mapbox map, 2 GL canvases, 3 visible search inputs, 0 page errors, via the host
global AND via ?tk=. The product was never at fault. This gate's detection was.

THE MAP PROBE WALKED THE DOM FOR A CANVAS. That is a chain of assumptions — every
shadow root open, the canvas carrying the class I expected, existing by the time
I looked — and when any link breaks the probe reports ABSENCE, which is
indistinguishable from a broken product. It now hooks the engine's own
Map.prototype.setStyle through both globals, which cannot be wrong about the DOM
because it never looks at it. Both globals, because the leaf loads only the
engine its provider asks for; the property is defined with a setter so the hook
survives the engine script loading afterwards, and the whole thing is wrapped so
a hook can never be the reason a page fails. The canvas walk stays as a second
opinion, so an engine that stops routing through setStyle degrades to the old
behaviour rather than accusing the product again.

THE BAR SELECTOR MATCHED ONE CLASS. input.mapboxgl-ctrl-geocoder--input, and
nothing else — so it reported 0 bars on a page carrying three visible search
inputs. Whether the miss was the class or the shadow walk cannot be told from
here, and that is exactly the problem: a selector narrow enough that one wrong
assumption yields ZERO will accuse the product of being broken when it is only
wrong about a name. It now counts what a reader would call a search box.

Both probes are borrowed from rega's tokrung.mjs, which measured this leaf
correctly while this gate did not. Where a positive control exists that measures
correctly, copy the control.

IT STILL MEASURES BOTH TOKEN RUNGS, per studio. The ?tk= theory turned out to be
wrong, but the two cases are worth keeping on their own merit: the host global is
the rung an embedding partner reaches for, and if the two ever diverge the gate
now says which one failed instead of leaving a channel switch to look like a fix.
Rejected api.mapbox.com responses are recorded too, so "requests but no map" can
be told apart from a refused key — a 401 says the key, not the code.

THE LESSON, recorded in the header because this is the second time in two days
that a probe of mine accused a working product: a measurement that yields ZERO
when its own assumption is wrong will always blame the thing it is measuring.

Source only; this commit edits one gate and changes no product code. I cannot run
it — Playwright is rega's.
This commit is contained in:
Doxa Maps 2026-09-26 17:31:33 +00:00
parent 7f3435cc1b
commit 4f3a954b9a

View file

@ -48,18 +48,38 @@
* reads the URL first) — the engine is Mapbox, it simply has no key. A gate that fails a
* product for a key the gate did not provide is measuring its own environment.
* It now takes MAPBOX_TOKEN from the env (or vite.doxa-maps-build-config.json `token`, the
* same ladder check:experimental-shelf uses) and injects it as window.MAP_TOKEN before
* navigation, the way check:auto-engine and check:a11y-button-names do. With no token the
* same ladder check:experimental-shelf uses) and supplies it on BOTH documented rungs — the
* host global (window.MAP_TOKEN, injected before navigation the way check:auto-engine does)
* and the ?tk= URL param — as two separate cases. See the CASES block below for why two.
* With no token the
* mapbox half is NOT MEASURED — reported as a SKIP line so run-parallel prints ⊘ rather than
* counting it as a pass, because an always-taken skip is indistinguishable from an unwired
* gate unless the runner says so.
*
* ★ WHY THE MAP PROBE ASKS THE ENGINE AND NOT THE DOM (2026-09-26, the second correction in a
* day). This gate reported "NO map constructed" on a leaf where rega then measured 1 Mapbox map, 2
* GL canvases and 3 search inputs, on BOTH token rungs. The product was never at fault; the
* detection was. A DOM walk for a canvas is a chain of assumptions — every shadow root open, the
* canvas carrying the class expected, existing by the time it is looked for — and when any link
* breaks it reports ABSENCE, which is indistinguishable from a broken product. It now hooks the
* engine's own Map.prototype.setStyle through both globals, which cannot be wrong about the DOM
* because it never looks at it, and widens the bar selector to what a reader would call a search
* box. Both borrowed from rega's tokrung.mjs.
*
* THE LESSON, recorded because it is the second time in two days that a probe accused a working
* product: a measurement that yields ZERO when its own assumption is wrong will always blame the
* thing it is measuring. Where a positive control exists that measures correctly, copy the control.
*
* ASSERTIONS, on the built tree:
* maplibre ZERO requests to api.mapbox.com. (Measured with or without a token.)
* If a bar is present it must produce suggestions; if it is absent, that is the
* documented contract and is recorded as such.
* mapbox the bar is PRESENT and produces suggestions. Requires a token; without one this
* half is skipped, never failed. Mapbox traffic is expected here and not asserted on.
* mapbox TWICE, once per token rung: a map CONSTRUCTS, the bar is PRESENT, and typing
* produces suggestions. Requires a token; without one this half is skipped, never
* failed. Mapbox traffic is expected here and not asserted on — but rejected
* RESPONSES are recorded, because "21 requests and no map" is otherwise ambiguous
* between a token never seen, a token refused, and a map that failed for its own
* reasons, and those three have different owners.
*
* NEVER PRINTS THE TOKEN. Only its presence is reported.
*
@ -116,18 +136,71 @@ const browser = await chromium.launch({ args: ['--enable-unsafe-swiftshader', '-
const fails = []
const lines = []
try {
// The mapbox half needs a key. Measure maplibre either way — it is the headline claim and it is
// fully measurable keyless, which is the whole point of the engine.
const engines = TOKEN ? ['maplibre', 'mapbox'] : ['maplibre']
for (const engine of engines) {
// ★ THE MAPBOX HALF IS MEASURED ON BOTH TOKEN RUNGS, AND THAT IS A DELIBERATE EXPERIMENT.
// rega, gates:post at 1a761741 WITH a token: the mapbox half reported "NO map constructed", 21
// requests to api.mapbox.com, 0 bars — while rega's own probe of THIS leaf at
// ?provider=mapbox&tk=<same token> constructed a Mapbox map. The suggested cause was that the
// generated leaf overwrites the window.MAP_TOKEN this gate sets in addInitScript.
//
// IT DOES NOT. research-map/index.html reads it at line 70 (`HOST_TOKEN = window.MAP_TOKEN`) and
// every assignment after it — lines 71, 109, 150 — is guarded by `!window.MAP_TOKEN`, so a
// host-set value is read and never replaced. studio read the same lines independently. The
// hypothesis is the obvious one and the source does not support it.
//
// So rather than switch channels on a mechanism nobody can point at, the gate now measures BOTH:
// the host global that partners use, and the ?tk= URL rung. If both construct, the earlier
// failure was this gate's and is gone. If ONLY ?tk= constructs, that is a PRODUCT defect on the
// host-global rung — the one an embedding partner reaches for — and the gate will say so by
// name instead of leaving a one-line change to look like a fix.
//
// The maplibre case carries NO token by either route: resolveMapProvider's `auto` rung reads a
// token as "use Mapbox", and a present token is exactly the variable this gate holds at zero.
const CASES = [{ id: 'maplibre', provider: 'maplibre', rung: 'none' }]
if (TOKEN) CASES.push(
{ id: 'mapbox/window.MAP_TOKEN', provider: 'mapbox', rung: 'global' },
{ id: 'mapbox/?tk=', provider: 'mapbox', rung: 'url' },
)
for (const c of CASES) {
const engine = c.provider
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } })
const mapboxCalls = []
// ★ STATUSES TOO, not just counts. "21 requests and no map" is ambiguous between a token that
// was never seen, one that was REJECTED, and a map that failed for an unrelated reason — and
// the three have different owners. A 401 says the key, not the code.
const mapboxBad = []
page.on('request', (r) => { const u = r.url(); if (u.includes('api.mapbox.com')) mapboxCalls.push(u) })
// ★ ONLY under mapbox. Injecting a token into the maplibre run would change what is being
// measured: resolveMapProvider's `auto` rung reads a token as "use Mapbox", and even with an
// explicit ?provider=maplibre a present token is a variable this gate exists to hold at zero.
if (engine === 'mapbox') await page.addInitScript((tk) => { window.MAP_TOKEN = tk }, TOKEN)
await page.goto(`${LEAF}?provider=${engine}`, { waitUntil: 'domcontentloaded', timeout: 60000 })
page.on('response', (r) => { if (r.url().includes('api.mapbox.com') && r.status() >= 400) mapboxBad.push(r.status()) })
if (c.rung === 'global') await page.addInitScript((tk) => { window.MAP_TOKEN = tk }, TOKEN)
// ★ ASK THE ENGINE, NOT THE DOM, WHETHER A MAP EXISTS. See the header note; borrowed from
// rega's tokrung.mjs, which measured this leaf correctly while this gate did not. Both globals
// are hooked because the leaf loads only the engine its provider asks for.
await page.addInitScript(() => {
window.__mapsBuilt = { mapbox: 0, maplibre: 0 }
const hook = (name) => {
let real
Object.defineProperty(window, name, {
configurable: true,
get: () => real,
set: (gl) => {
real = gl
try {
const Ctor = gl && gl.Map
if (Ctor && !Ctor.__counted) {
Ctor.__counted = true
const setStyle = Ctor.prototype.setStyle
Ctor.prototype.setStyle = function (...a) {
window.__mapsBuilt[name === 'mapboxgl' ? 'mapbox' : 'maplibre']++
return setStyle.apply(this, a)
}
}
} catch (_) { /* a hook must never be the reason a page fails */ }
},
})
}
hook('mapboxgl'); hook('maplibregl')
})
const url = `${LEAF}?provider=${engine}` + (c.rung === 'url' ? `&tk=${encodeURIComponent(TOKEN)}` : '')
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 })
await page.waitForTimeout(20000)
const search = await page.evaluate(async () => {
@ -137,14 +210,26 @@ try {
if (!root || seen.has(root)) return
seen.add(root)
for (const el of root.querySelectorAll('*')) {
if (el.matches && el.matches('input.mapboxgl-ctrl-geocoder--input, input.maplibregl-ctrl-geocoder--input')) hits.push(el)
// ★ WIDER THAN THE PLUGIN'S OWN CLASS. This matched only
// input.mapboxgl-ctrl-geocoder--input and reported 0 bars on a leaf carrying three
// visible search inputs. Whether the miss was the class or the shadow walk cannot be told
// from here, and that is the point: a selector narrow enough that one wrong assumption
// yields ZERO accuses the product of being broken when it is only wrong about a name.
if (el.matches && el.matches(
'input.mapboxgl-ctrl-geocoder--input, input.maplibregl-ctrl-geocoder--input,'
+ ' .mapboxgl-ctrl-geocoder input, .maplibregl-ctrl-geocoder input,'
+ ' input[type="search"], input[placeholder*="Search" i]')) hits.push(el)
if (el.shadowRoot) walk(el.shadowRoot)
}
}
// Did an engine actually construct a map? A canvas is the one artefact both engines share,
// and it tells "the bar was removed" apart from "there was never a map to hang it on".
let mapReady = false
for (const root of seen) { if (root.querySelector && root.querySelector('canvas.mapboxgl-canvas, canvas.maplibregl-canvas, .mapboxgl-map canvas, .maplibregl-map canvas')) { mapReady = true; break } }
// The engine hook is the answer; the canvas walk stays only as a second opinion, so an engine
// that stops routing through setStyle degrades to the old behaviour instead of accusing the
// product.
const built = window.__mapsBuilt || {}
let mapReady = !!(built.mapbox || built.maplibre)
if (!mapReady) for (const root of seen) { if (root.querySelector && root.querySelector('canvas.mapboxgl-canvas, canvas.maplibregl-canvas, .mapboxgl-map canvas, .maplibregl-map canvas')) { mapReady = true; break } }
if (!hits.length) return { bars: 0, suggestions: 0, mapReady }
const input = hits[0]
input.focus()
@ -160,13 +245,14 @@ try {
})
await page.close()
lines.push(` · ${engine.padEnd(9)} api.mapbox.com requests: ${mapboxCalls.length} · search bars: ${search.bars} · suggestions for "Afar": ${search.suggestions}${engine === 'mapbox' ? ' · token: supplied' : ''}`)
const bad = mapboxBad.length ? ` · REJECTED responses: ${mapboxBad.length} (${[...new Set(mapboxBad)].join(', ')})` : ''
lines.push(` · ${c.id.padEnd(22)} api.mapbox.com requests: ${mapboxCalls.length} · map: ${search.mapReady ? 'yes' : 'NO'} · search bars: ${search.bars} · suggestions for "Afar": ${search.suggestions}${bad}`)
if (engine === 'maplibre') {
// THE HEADLINE. One request sends the visitor's IP to Mapbox and breaks the keyless promise,
// whatever the page looks like afterwards.
if (mapboxCalls.length) {
fails.push(`under maplibre the leaf made ${mapboxCalls.length} request(s) to api.mapbox.com:`)
fails.push(`[${c.id}] the leaf made ${mapboxCalls.length} request(s) to api.mapbox.com:`)
for (const u of [...new Set(mapboxCalls)].slice(0, 8)) fails.push(` ${u.slice(0, 150)}`)
}
// A bar is OPTIONAL here and its absence is the documented contract (see the header). What is
@ -182,11 +268,16 @@ try {
// `mapReady` separates "the bar was removed" from "the map never constructed", which are
// different bugs with the same symptom — and the second one is how this gate misread itself.
if (!search.bars) {
fails.push(search.mapReady
? 'under mapbox the map constructed but rendered NO search bar — hasGeocoder or the GeocoderComponent mount is broken.'
: 'under mapbox NO map constructed (so the bar\'s v-if could never fire). The token was supplied, so this is the product or the leaf, not a missing key.')
const rung = c.rung === 'global' ? 'the HOST GLOBAL rung (window.MAP_TOKEN, what an embedding partner sets)' : 'the ?tk= URL rung'
if (!search.mapReady) {
fails.push(mapboxBad.length
? `[${c.id}] NO map constructed and ${mapboxBad.length} api.mapbox.com response(s) came back ${[...new Set(mapboxBad)].join('/')} — the token reached Mapbox and was REFUSED. That is the key, not the code.`
: `[${c.id}] NO map constructed, via ${rung}, with a token supplied and no rejected responses. The leaf never overwrites a host-set window.MAP_TOKEN (index.html:70 reads it, :71/:109/:150 are all guarded), so if the other rung constructs and this one does not, this is a PRODUCT defect on the rung partners use — not this gate.`)
} else {
fails.push(`[${c.id}] the map constructed but rendered NO search bar — hasGeocoder or the GeocoderComponent mount is broken.`)
}
} else if (!search.suggestions) {
fails.push('under mapbox the search bar rendered but typing produced no suggestions.')
fails.push(`[${c.id}] the search bar rendered but typing produced no suggestions.`)
}
}
}
@ -210,4 +301,4 @@ if (!TOKEN) {
console.log(' Set MAPBOX_TOKEN in the environment, or `token` in vite.doxa-maps-build-config.json, to measure it.')
process.exit(0)
}
console.log(`${TAG}: PASS — MapLibre calls Mapbox zero times, and Mapbox still searches.`)
console.log(`${TAG}: PASS — MapLibre calls Mapbox zero times, and Mapbox searches on both token rungs.`)