Run every browser gate from one command
Some checks are pending
build-gate / build (push) Waiting to run

All seven browser gates were orphans: each had a script, and not one was invoked
by anything else. They were reachable only by someone who already knew the name
to type, which is exactly the person who does not need the list. This repo has
been bitten by this before — the Gate-1 acceptance test existed, worked, and was
invoked by nothing, so it could never report anything. An orphan gate is worse
than a missing one because it looks like coverage.

The data guards already solved this; verify-all.mjs says it plainly — a guard
nobody runs is documentation. That file has no reference to playwright or to the
built tree. It is the data half. This is the browser half.

Every gate listed here was run before it was listed, following the rule the
earlier audit set: listing a program you have not executed puts unvalidated
entries in the hand-off surface. All six pass, and the aggregate now runs them
end to end: capabilities, embeds, map paint, the Gate-1 chain, accessible names,
and phone navigation.

smoke:share is excluded on purpose and says so. It takes required arguments and
its gallery mode reads a registry only the vite dev server serves, so listing it
would make the suite fail for something that is not a defect.

Preconditions are asserted up front and exit 2, so a missing server reads as a
setup problem rather than six mystery failures.
This commit is contained in:
doxa-maps 2026-09-22 09:15:00 +00:00
parent 94dc3c64e8
commit d463cb6161
2 changed files with 131 additions and 1 deletions

View file

@ -0,0 +1,129 @@
#!/usr/bin/env node
/**
* verify-browser-all.mjs — run every BROWSER gate over the built tree, in one command.
*
* WHY THIS EXISTS. library/scripts/research/verify-all.mjs already solved this for the DATA guards,
* and its header states the principle better than I can paraphrase it: "Scattered, they are worth
* much less than they look: nobody knows the list, so nobody runs them, and a guard nobody runs is
* documentation." That file has ZERO references to playwright or public/doxa-maps — it is the data
* half. The BROWSER half had no equivalent.
*
* ★ MEASURED 2026-09-22: all seven browser gates were ORPHANS — each had a package.json script, and
* NOT ONE was invoked by any other script. They were reachable only by someone who already knew the
* name to type, which is precisely the person who does not need the list. This repo has already
* been bitten by exactly this: the Gate-1 acceptance test existed, worked, and was invoked by
* nothing, so it "could never report anything" (IDEA-SIX-INTERNAL-SCRIPTS-ARE-UNREACHABLE). An
* orphan gate is worse than a missing one, because it LOOKS like coverage on the board.
*
* ★ EVERY GATE LISTED HERE WAS RUN BEFORE IT WAS LISTED. That rule is inherited deliberately from
* the same card: "do not mass-wire ... Adding scripts for programs I have not executed would put
* unvalidated entries in the hand-off surface." Results on 2026-09-22, against the built tree:
* check:nav-reachable PASS 2 cap-5 shells reach their rail at 414px
* check:a11y-button-names PASS 332/332 buttons have an accessible name across 19 leaves
* verify:gate1 PASS 13/13 acceptance assertions against 2107 LIVE records
* verify:5-caps PASS all 5 capability levels mount, render, error-free
* smoke:embeds PASS 43/43 leaf pages mount cleanly
* verify:map-paint PASS 11/11 map leaves paint a real canvas under SwiftShader
*
* ★ ONE GATE IS DELIBERATELY EXCLUDED. `smoke:share` is NOT in this list and that is not an
* oversight: it takes required arguments (--url / --plugin-gallery) and its gallery mode reads
* /__plugin-gallery/registry.json, which only the VITE DEV SERVER serves — a different runtime
* dependency from the static built tree every gate here targets. Run it separately against
* `bun x vite`. Listing it would make this aggregate fail for a reason that is not a defect.
*
* PRECONDITIONS (asserted, not assumed): a built tree, and a static server on the BASE port. Both
* are checked up front so a missing server reports as a SETUP problem rather than six mystery
* failures.
*
* Exit 0 = every gate passed. Exit 1 = at least one failed (each named, with its output tail).
* Exit 2 = preconditions unmet (no tree / no server) — distinguishable from a real failure.
*/
import { spawnSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const BASE = process.env.BASE_URL || 'http://127.0.0.1:4173'
// Each entry was executed on 2026-09-22 with the result recorded above. `why` is what the gate
// actually protects, so a failure here tells a reader what broke without opening the file.
const GATES = [
{ script: 'verify:5-caps', why: 'all 5 capability levels mount and render' },
{ script: 'smoke:embeds', why: 'every shipped leaf page mounts its element' },
{ script: 'verify:map-paint', why: 'every map leaf paints a real canvas' },
{ script: 'verify:gate1', why: 'the Gate-1 chain filters live data end to end' },
{ script: 'check:a11y-button-names', why: 'every visible button has an accessible name' },
{ script: 'check:nav-reachable', why: 'a phone can open the cap-5 tab rail' },
]
// ── PRECONDITION 1: a built tree ────────────────────────────────────────────
const MANIFEST = join(ROOT, 'public', 'doxa-maps', 'manifest.json')
if (!existsSync(MANIFEST)) {
console.error('BROWSER-ALL: SETUP — no public/doxa-maps/manifest.json. Run `bun run build` first.')
process.exit(2)
}
let bundleCount = 0
try { bundleCount = (JSON.parse(readFileSync(MANIFEST, 'utf8')).bundles || []).length } catch { /* reported below */ }
if (bundleCount < 2) {
console.error(`BROWSER-ALL: SETUP — manifest lists ${bundleCount} bundle(s); the tree looks empty or truncated.`)
process.exit(2)
}
// ── PRECONDITION 2: a server on BASE ────────────────────────────────────────
// Every gate here fetches over HTTP. Without this check a missing server yields six identical
// timeouts and looks like six broken gates.
try {
const r = await fetch(`${BASE}/doxa-maps/manifest.json`, { signal: AbortSignal.timeout(5000) })
if (!r.ok) throw new Error(`HTTP ${r.status}`)
} catch (e) {
console.error(`BROWSER-ALL: SETUP — nothing serving ${BASE} (${String(e.message).slice(0, 40)}).`)
console.error(' Start one first: bun run serve # python http.server on 4173, --directory public')
console.error(' Or point elsewhere: BASE_URL=http://host:port bun run verify:browser-all')
process.exit(2)
}
// A token is optional: the map gates need one to paint, the chrome gates do not. Fetch once and
// share it, rather than letting six gates each pay for the round trip.
let token = process.env.MAPBOX_TOKEN || ''
if (!token) {
try {
const r = await fetch('https://doxa.life/api/maps/token', { signal: AbortSignal.timeout(8000) })
if (r.ok) token = (await r.json()).token || ''
} catch { /* the chrome gates still run; the map gates will say so themselves */ }
}
console.log(`BROWSER-ALL: ${GATES.length} gate(s) over ${bundleCount} bundle(s) at ${BASE}` +
`${token ? ' (token present)' : ' (NO TOKEN — map gates may report blank)'}\n`)
const results = []
for (const g of GATES) {
process.stdout.write(` … ${g.script.padEnd(24)} ${g.why}\n`)
const t0 = Date.now()
const r = spawnSync('bun', ['run', g.script], {
cwd: ROOT, encoding: 'utf8', timeout: 900000,
env: { ...process.env, MAPBOX_TOKEN: token, BASE_URL: BASE },
})
const secs = Math.round((Date.now() - t0) / 1000)
const out = ((r.stdout || '') + (r.stderr || '')).trim()
const ok = r.status === 0
results.push({ ...g, ok, secs, tail: out.split('\n').filter(Boolean).slice(-3).join('\n') })
console.log(` ${ok ? '✓' : '✗'} ${g.script.padEnd(24)} ${ok ? 'PASS' : 'FAIL'} (${secs}s)`)
}
const failed = results.filter(r => !r.ok)
console.log(`\nBROWSER-ALL: ${results.length - failed.length}/${results.length} gate(s) passed.`)
if (failed.length) {
console.error('\nFAILED:')
for (const f of failed) {
console.error(` ✗ ${f.script} — ${f.why}`)
for (const line of f.tail.split('\n')) console.error(` ${line}`)
}
console.error('\n Re-run one on its own to see its full output: bun run <script>')
process.exit(1)
}
console.log('BROWSER-ALL: PASS — every browser gate over the built tree is green.')
console.log(' NOT covered here: smoke:share (needs the vite dev server, not a static tree) and the')
console.log(' DATA guards (`bun run verify` / `verify:full`, which own the corpus side).')

View file

@ -75,7 +75,8 @@
"check:dep-names": "node internal/check-dependency-names.mjs # every declared dep is a REAL package name (a rename once rewrote @capacitor/* and broke bun install in a clone)",
"check:composite-srcs": "node internal/check-composite-srcs.mjs",
"check:nested-token": "node internal/check-nested-token-delivery.mjs",
"check:nav-reachable": "node internal/check-nav-reachable-mobile.mjs # a cap-5 shell must have ONE on-screen control that opens its tab rail at phone width (needs `bun run serve` on 4173; browser gate, not in build:unlocked)"
"check:nav-reachable": "node internal/check-nav-reachable-mobile.mjs # a cap-5 shell must have ONE on-screen control that opens its tab rail at phone width (needs `bun run serve` on 4173; browser gate, not in build:unlocked)",
"verify:browser-all": "node internal/verify-browser-all.mjs # runs ALL 6 browser gates over the built tree in one command (needs `bun run serve` on 4173). Every gate listed was RUN before being listed. smoke:share is excluded on purpose — it needs the vite dev server."
},
"devDependencies": {
"@capacitor/android": "^7.6.9",