doxa-maps/internal/check-mapbox-drew.mjs
Doxa Maps 496cd88091 Make NOT MEASURED name the blocked requests instead of guessing why
This gate reported a 403 as "tileset entitlement, not the embed" and printed
only the first blocked URL. Both halves were wrong.

The claim is disproved. Measured on this gate's own token: /v4/ streets-v8 and
country-boundaries both return 200 -- with no Referer, and from
research.nuxtbase.app, maps-dev.nuxtbase.app, doxa.life, 127.0.0.1 and
example.com. The token is unrestricted and entitled. The old conclusion rested
on three no-Referer probes, which cannot tell a URL-restricted token from a
missing entitlement; it named a cause those probes could not support. Whatever
403s is a different request -- a style, a sprite, glyphs, a composite -- or a
different token than the one measured.

Printing one URL is what hid that. A leaf blocked on forty tiles showed
whichever tile lost the race, so the tileset was never legible and a second
blocked endpoint was invisible behind the first.

NOT MEASURED now names every distinct blocked path, with tile coordinates
folded to {z}/{x}/{y} so one blocked tileset is one fact rather than forty, and
styles, sprites and glyphs stay separate -- which is exactly the class the
evidence now points at. It also names where the token came from (env var or the
leaf's own baked one), never its value: the query string is stripped before any
path is printed, asserted in the exercise below. The real-failure arm gets the
same treatment.

The gate asserts no cause any more. It reports what was blocked and lets the
reader judge.

Exercised on six URLs spanning two tilesets, a sprite and a glyph range: 6 ->
4 distinct facts, and a planted token value does not appear in the output.
2026-09-29 05:26:27 +00:00

204 lines
12 KiB
JavaScript

#!/usr/bin/env node
/**
* check-mapbox-drew — the Mapbox engine actually DREW, on every production leaf.
*
* ★ THE HOLE THIS FILLS (regions, RT8 goal b). Two gates already touch the engine and neither
* asserts this one thing:
* check-leaf-engine-per-provider proves MAPLIBRE purity (zero *.mapbox.com requests under
* ?provider=maplibre) and that a map still draws. It never forces
* Mapbox.
* check-provider-switch does load ?provider=mapbox — but with a FAKE token, so it proves
* the ENGINE LOADS, not that a single tile rendered. One leaf.
* So "Mapbox works" was never measured on the seven production maps. This measures it.
*
* ★ A SILENT FALLBACK IS THE FAILURE WORTH CATCHING. A leaf asked for Mapbox that quietly draws with
* MapLibre looks perfect in a screenshot and passes every paint check. So the ENGINE is read off the
* canvas that drew (.mapboxgl-canvas vs .maplibregl-canvas), never from which globals exist — a peer
* script that merely loaded Mapbox proves nothing.
*
* ★ AND A CANVAS AT THE RIGHT SIZE IS NOT A MAP. This repo learned that the hard way: a country map
* rendered fully blank while "a real GL canvas exists at 1200x780" passed. So drawing is asserted
* with queryRenderedFeatures() > 0 on the leaf's own rendered features, not by canvas geometry.
*
* ★ THREE EXIT STATES, BECAUSE TWO WOULD FORCE A LIE. When a tile endpoint 403s, the leaf draws
* nothing through no fault of its owner. Counting that as a failure would manufacture work for every
* owner; counting it as a pass would be false. It exits 75 NOT MEASURED instead, NAMING every
* distinct blocked request path and the token's source, so the next reader can tell which
* credential was in play without the gate ever printing it.
* ✗ AN EARLIER VERSION CALLED THIS "TILESET ENTITLEMENT" AND THAT WAS WRONG. Measured 2026-09-29 by
* rega on this gate's own token: /v4/ streets-v8 and country-boundaries both return 200, with no
* Referer and from research.nuxtbase.app, maps-dev.nuxtbase.app, doxa.life, 127.0.0.1 and
* example.com. The token is unrestricted and entitled. The old claim rested on three no-Referer
* probes, which cannot distinguish a URL-restricted token from a missing entitlement. Whatever 403s
* is a different request or a different token — which is exactly what the gate must now report
* instead of guessing.
*
* USAGE MAP_TOKEN=pk.… node internal/check-mapbox-drew.mjs [--dir <tree>] [--only a/b,c/d]
* Run through internal/browser-slot.sh — it needs the browser seat.
* @browser-slot-max-hold 1800
* EXIT 0 every measured leaf drew with Mapbox · 1 a real failure · 75 no token, or only
* entitlement 403s (nothing could be judged).
*/
import { chromium } from './lib/playwright-guarded.mjs'
import { createServer } from 'node:http'
import { spawn } from 'node:child_process'
import { readFileSync, existsSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { TREE_NAME } from './_tree-name.mjs'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const argv = process.argv.slice(2)
const arg = (k, d) => { const i = argv.indexOf(k); return i >= 0 && argv[i + 1] ? argv[i + 1] : d }
const DIR = resolve(arg('--dir', resolve(ROOT, 'public')))
const ONLY = arg('--only', '') ? new Set(arg('--only', '').split(',')) : null
const TOKEN = process.env.MAP_TOKEN || ''
// ── 3. NO REAL TOKEN -> NOT MEASURED, never a pass and never a fail ───────────────────────────
if (!/^pk\./.test(TOKEN)) {
console.log('MAPBOX-DREW: NOT MEASURED — set MAP_TOKEN to a real pk.… token. A fake token loads the'
+ ' engine and renders nothing, which is exactly the false green this gate exists to refuse.')
process.exit(75)
}
const manifestPath = join(DIR, TREE_NAME, 'manifest.json')
if (!existsSync(manifestPath)) { console.log(`MAPBOX-DREW: NOT MEASURED — no ${manifestPath}`); process.exit(75) }
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
// ── 2. TARGETS FROM THE MANIFEST, never hardcoded: the curated production tabs ─────────────────
// The seven are whatever doxa-maps-spa composes. Reading it here means adding a production tab
// cannot silently escape this gate.
const spa = (manifest.bundles || []).find((b) => b.bundle === 'doxa-maps-spa')
const composed = new Set(((spa?.composes) || []).map(String))
const leaves = []
// ★ isMap IS FALSE ON A COMPOSITE WHOSE CHILD HOLDS THE MAP, and skipping on it would have lost a
// production tab silently. research-map-plus-grid (Simple Research) is type nested-elements-mfe with
// isMap:false and composes ['doxa-research-map', 'people-groups-grid'] — the MAP is the child. So a
// composed tab counts as a map leaf if it is isMap ITSELF or it composes a profile that is. Measured:
// selecting on isMap alone found 6 of the 7 curated tabs.
const profileIsMap = new Map()
for (const b of manifest.bundles || []) for (const p of b.profiles || []) profileIsMap.set(`${b.bundle}/${p.profile}`, !!p.isMap)
const composesAMap = (b, p) => ((b.composes || []).some((ref) => {
const r = String(ref)
return profileIsMap.get(r) || [...profileIsMap.keys()].some((k) => k.startsWith(`${r}/`) && profileIsMap.get(k))
}))
for (const b of manifest.bundles || []) for (const p of b.profiles || []) {
const id = `${b.bundle}/${p.profile}`
if (!composed.has(id)) continue // only the curated tabs
if (!p.isMap && !composesAMap(b, p)) continue // …that draw a map, directly or via a child
if (ONLY && !ONLY.has(id)) continue
if (existsSync(join(DIR, TREE_NAME, id, 'index.html'))) leaves.push(id)
}
if (!leaves.length) { console.log('MAPBOX-DREW: NOT MEASURED — no composed map leaf matched'); process.exit(75) }
async function serve(dir) {
const port = await new Promise((ok) => { const s = createServer().listen(0, '127.0.0.1', () => { const n = s.address().port; s.close(() => ok(n)) }) })
const srv = spawn(process.execPath, [resolve(ROOT, 'internal/serve-strict.mjs'), '--port', String(port), '--directory', dir], { stdio: 'ignore' })
await new Promise((r) => setTimeout(r, 600))
return { port, stop: () => { try { srv.kill() } catch (_) {} } }
}
// Endpoints whose 403 is NOT the leaf owner's bug, so a leaf blocked on one is NOT MEASURED
// rather than FAILED.
//
// ✗ WHY THIS NO LONGER CLAIMS "ENTITLEMENT" (corrected 2026-09-29). It used to report a 403 here
// as "tileset, not the embed", on the strength of three no-Referer probes. MEASURED since, by rega,
// with the very token this gate runs on: /v4/ streets-v8 AND country-boundaries return 200 — with
// no Referer, and with research.nuxtbase.app, maps-dev.nuxtbase.app, doxa.life, 127.0.0.1 and
// example.com. The token is unrestricted and entitled, so whatever 403s here is a DIFFERENT
// request (a style, sprite, glyphs, a composite with terrain) or a different token than the one
// tested. Three no-Referer cases cannot tell a URL-restricted token from a missing entitlement,
// and the old comment stated a conclusion those cases could not support.
// So this list stays — a 403 on a tile endpoint still makes a leaf unjudgeable — but the gate now
// NAMES every distinct blocked path and where its token came from, and asserts no cause.
const ENTITLEMENT = [/\/v4\//, /country-boundaries/]
// Where the token came from, so a NOT MEASURED says which credential was in play. Never the value.
const TOKEN_SOURCE = process.env.MAP_TOKEN ? 'env MAP_TOKEN'
: process.env.MAPBOX_TOKEN ? 'env MAPBOX_TOKEN'
: 'none in env (the leaf used its own baked token, if any)'
// Distinct request paths, query dropped and TILE COORDINATES folded to {z}/{x}/{y}: one blocked
// tileset requested over 40 tiles is ONE fact, not forty, and a reader needs the tileset name, not
// which tile happened to be first. The query goes first, so a token can never reach the output.
const pathsOf = (urls) => [...new Set(urls.map((u) => String(u)
.replace(/\?.*/, '')
.replace(/\/\d+\/\d+\/\d+(?=(\.|@|$))/, '/{z}/{x}/{y}')))]
async function measure(page, url, provider) {
const errs = []
const forbidden = []
page.on('pageerror', (e) => errs.push(String(e.message || e)))
page.on('response', (r) => { if (r.status() === 403 && /mapbox\.com/.test(r.url())) forbidden.push(r.url()) })
await page.addInitScript((tk) => { window.MAP_TOKEN = tk }, TOKEN)
await page.goto(`${url}?provider=${provider}`, { waitUntil: 'load', timeout: 45000 })
// Give the engine a real chance to paint before judging; a slow tile server is not a failure.
const drew = await page.waitForFunction(() => {
const deep = (root, out = []) => {
for (const el of root.querySelectorAll('*')) {
if (el.tagName === 'CANVAS') out.push(el)
if (el.shadowRoot) deep(el.shadowRoot, out)
}
return out
}
const c = deep(document).find((x) => x.width > 0 && x.height > 0)
if (!c) return null
const engine = c.classList.contains('mapboxgl-canvas') ? 'mapbox'
: c.classList.contains('maplibregl-canvas') ? 'maplibre' : 'other'
// The map instance this repo already publishes for gates to read.
const m = window.__rcmMap || window.__rdashMap || null
let features = -1
try { if (m && m.queryRenderedFeatures) features = m.queryRenderedFeatures().length } catch (_) { features = -1 }
return { engine, features }
}, { timeout: 30000 }).then((h) => h.jsonValue()).catch(() => null)
return { drew, errs, forbidden }
}
const cur = await serve(DIR)
const browser = await chromium.launch({ args: ['--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist'] })
const base = `http://127.0.0.1:${cur.port}/${TREE_NAME}`
const fails = []
const notMeasured = []
const passes = []
for (const id of leaves) {
const page = await browser.newPage()
const { drew, errs, forbidden } = await measure(page, `${base}/${id}/`, 'mapbox')
await page.close()
const entitlement = forbidden.filter((u) => ENTITLEMENT.some((re) => re.test(u)))
const otherForbidden = forbidden.filter((u) => !ENTITLEMENT.some((re) => re.test(u)))
if (!drew) { notMeasured.push(`${id}: no sized canvas appeared within 30s`); continue }
// ── 4. THE ENGINE. A silent fallback is the failure worth catching. ──
if (drew.engine !== 'mapbox') { fails.push(`${id}: asked for ?provider=mapbox and the canvas that drew is ${drew.engine.toUpperCase()} — a silent fallback`); continue }
if (otherForbidden.length) { fails.push(`${id}: 403 on ${pathsOf(otherForbidden).join(' , ')} — not a tile endpoint [token: ${TOKEN_SOURCE}]`); continue }
// ── 5/6. DREW, or an entitlement 403 that makes it unjudgeable. ──
if (drew.features > 0) { passes.push(`${id}: mapbox canvas, ${drew.features} rendered feature(s)`); continue }
if (entitlement.length) { notMeasured.push(`${id}: 0 features; 403 on ${pathsOf(entitlement).join(' , ')} [token: ${TOKEN_SOURCE}]`); continue }
if (errs.length) { fails.push(`${id}: 0 rendered features, page error: ${errs[0].slice(0, 90)}`); continue }
fails.push(`${id}: mapbox canvas drew 0 rendered features, and no 403 explains it`)
}
// ── 8. THE NEGATIVE ARM. If the engine assertion cannot fail, a green above means nothing. ─────
let armOk = false
{
const page = await browser.newPage()
const { drew } = await measure(page, `${base}/${leaves[0]}/`, 'maplibre')
await page.close()
armOk = !!drew && drew.engine === 'maplibre'
}
if (!armOk) fails.push('NEGATIVE ARM BROKEN: ?provider=maplibre did not draw with MapLibre, so the engine'
+ ' check cannot distinguish the engines and every pass above is unfalsifiable')
await browser.close(); cur.stop()
for (const p of passes) console.log(` ✓ ${p}`)
for (const n of notMeasured) console.log(` · NOT MEASURED ${n}`)
for (const f of fails) console.error(` ✗ ${f}`)
// ── 9. THREE STATES ──
if (fails.length) { console.error(`MAPBOX-DREW: FAIL — ${fails.length} of ${leaves.length} leaf/leaves`); process.exit(1) }
if (!passes.length) { console.log(`MAPBOX-DREW: NOT MEASURED — nothing could be judged (${notMeasured.length} leaf/leaves blocked)`); process.exit(75) }
console.log(`MAPBOX-DREW: PASS — ${passes.length} of ${leaves.length} composed map leaf/leaves drew with Mapbox`
+ `${notMeasured.length ? `, ${notMeasured.length} not measurable on this token` : ''}; negative arm holds.`)