doxa-maps/internal/check-gospel-all.mjs
Doxa Maps 0b54b0354e Gospel checks: one dev-assets gate, and cover the gap it leaves
The gospel-only dev-assets check is gone in favour of the shared one, which is
better at the job: it tests the /__leaf/<bundle>/<profile> prefix — the one that
actually broke — across all seven map bundles, and confirms each leaf hands its
element the base it claims. The gospel version never looked at that prefix, so
keeping both would have meant two implementations and the weaker one still green
while the map was broken.

What the shared gate samples is the POOL, the files every map shares. It cannot
know this leaf also fetches gospel tables by name, so a table that stopped being
staged would leave it green and this map fetching a 404. That is the only gap
left, and this covers exactly it.

The wanted list is parsed out of resources-map.vue rather than written down,
which immediately earned itself: the hand-written list it replaces was already
two short — pray-index and gospel-link-verification were being fetched and were
not in it. A list maintained by hand is the field-list-goes-stale bug, and this
bundle has produced that three times in one day.

Poison-tested by unstaging a real table: FAIL, exit 1, then restored.
2026-09-25 23:50:31 +00:00

89 lines
4.3 KiB
JavaScript

/**
* check-gospel-all.mjs — everything this bundle owns, in one command.
*
* WHY THIS EXISTS. gospel-resources has ten gates of its own and every one of them was
* hand-run. That is how a dead bundle shipped: a duplicate module killed all five leaves,
* and the one gate I happened to run that day was pointed at the map. `check:no-orphan-gates`
* was right that they were all reachable — reachable is not the same as run.
*
* It is NOT wired into gates:pre/post on purpose. Three of these need a browser, the shared
* wave holds browser gates under load, and a bundle owner should not be able to slow every
* other agent's build by adding to a list they do not own. This is the bundle's own front
* door instead: one command, honest about what it could not measure.
*
* STATIC FIRST, THEN BROWSER. The static gates are sub-second and catch most drift, so they
* run first and a failure there stops the run before a browser is launched — there is no
* point spending a scarce browser slot to confirm a table is inconsistent.
*
* NOT MEASURED IS NOT PASS. A browser gate that cannot get a slot exits 75 and is reported
* as NOT MEASURED, never folded into the pass. Anything else would turn a busy machine into
* a green tick.
*/
import { spawnSync } from 'node:child_process'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const HERE = dirname(fileURLToPath(import.meta.url))
const REPO = resolve(HERE, '..')
const SLOT = resolve(HERE, 'browser-slot.sh')
const STATIC = [
['check:no-conflict-markers', 'nothing tracked is mid-merge'],
['check:no-conflict-markers:poison', '...and that check can fail'],
['check:coverage-consistent', 'the surfaces agree about who has what'],
['check:coverage-consistent:poison', '...and that check can fail'],
['check:surfaces-read-the-tier', 'one place decides "this group has nothing"'],
['check:stale-sweep-claim', 'no caveat outliving its truth'],
['check:click-sheet', 'the country sheet resolves, links are absolute'],
['check:snapshot-guard', 'no fetcher writes around the guard'],
['check:gospel-tables-staged', 'every asset the map fetches is in the shipped tree'],
]
const BROWSER = [
['check:leaves-mount', 'all five leaves actually mount'],
['check:leaves-mount:poison', '...and that check can fail'],
['check:composed-filters', 'the composite filters instead of navigating'],
['check:map-paints', 'the map puts pixels on the screen, not just features'],
['check:map-paints:poison', '...and that check can fail'],
]
const run = (script, viaSlot) => {
const cmd = viaSlot ? SLOT : 'bun'
const args = viaSlot ? ['bun', 'run', script] : ['run', script]
const r = spawnSync(cmd, args, { cwd: REPO, encoding: 'utf8' })
return { code: r.status ?? 1, out: `${r.stdout || ''}${r.stderr || ''}` }
}
const failed = []
const notMeasured = []
let passed = 0
for (const [script, what] of STATIC) {
const r = run(script, false)
if (r.code === 0) { passed++; console.log(` ok ${script.padEnd(34)} ${what}`) }
else { failed.push([script, r.out.trim().split('\n').slice(-3).join(' | ')]); console.log(` FAIL ${script.padEnd(34)} ${what}`) }
}
if (failed.length) {
console.error(`\nGOSPEL-ALL: FAIL — ${failed.length} static gate(s); not launching a browser to confirm it.`)
for (const [s, tail] of failed) console.error(` ${s}: ${tail}`)
process.exit(1)
}
for (const [script, what] of BROWSER) {
const r = run(script, true)
if (r.code === 0) { passed++; console.log(` ok ${script.padEnd(34)} ${what}`) }
else if (r.code === 75) { notMeasured.push(script); console.log(` -- ${script.padEnd(34)} NOT MEASURED (no browser slot)`) }
else { failed.push([script, r.out.trim().split('\n').slice(-3).join(' | ')]); console.log(` FAIL ${script.padEnd(34)} ${what}`) }
}
console.log('')
if (failed.length) {
console.error(`GOSPEL-ALL: FAIL — ${failed.length} gate(s) red, ${passed} green, ${notMeasured.length} not measured.`)
for (const [s, tail] of failed) console.error(` ${s}: ${tail}`)
process.exit(1)
}
if (notMeasured.length) {
console.log(`GOSPEL-ALL: INCOMPLETE — ${passed} green, ${notMeasured.length} NOT MEASURED (${notMeasured.join(', ')}).`)
console.log(' Not a pass. Re-run when a browser slot is free.')
process.exit(75)
}
console.log(`GOSPEL-ALL: PASS — ${passed} gate(s), static and browser.`)