Refuse to serve a half-built tree, so a gate cannot mistake one for a regression
A build removes public/doxa-maps before rebuilding it, so a browser gate running during one reads an empty tree and reports a confident wrong answer. verify-shell-scroll said it could not find the dashboard-plus-grid tab twice in ten minutes while a swarm rebuilt underneath it. That reads exactly like a real regression and cost three ticks of this flight. The server now answers 503 for anything under /doxa-maps/ while the manifest is absent, with a body naming the cause. A 503 rather than a 404 is deliberate: 404 means "this is not here", which a gate may reasonably report as a product defect, while 503 with a reason cannot be mistaken for a finding. Assets outside the bundle tree are unaffected. The check belongs in the server because all nine gates fetch through it. Guarding one chokepoint means none of them can misread a torn tree and none of them needs editing, where having every gate take the build lock is nine changes plus a rule every future gate has to remember. The aggregator already refused to start against a missing tree, but only at startup, and a full sweep runs for seventeen minutes. It also blamed a missing server for a 503, sending the reader to start one that was already running; it now names the real cause. The gap the server closes is the one that actually bit: a single gate run directly, at any moment, including mid-sweep. Verified by hiding the manifest in place: a leaf returns 503 with the explanation while /assets/ still returns 404, and both return to 200 when it is restored.
This commit is contained in:
parent
236d289cc7
commit
aa196db1d8
2 changed files with 45 additions and 1 deletions
|
|
@ -63,10 +63,39 @@ const send = (res, code, body, headers = {}) => {
|
|||
res.end(body)
|
||||
}
|
||||
|
||||
// ── REFUSE TO SERVE A TORN TREE (card BUILD-BROWSER-GATES-RACE-THE-BUILD-LOCK) ────────────────
|
||||
// `bun run clean` rm -rf's public/doxa-maps at the START of every build, so a browser gate running
|
||||
// during one reads an EMPTY tree and reports a confident wrong diagnosis. Measured: verify-shell-
|
||||
// scroll said "could not find the dashboard-plus-grid tab" twice in ten minutes while a swarm
|
||||
// rebuilt underneath it — a phantom failure that reads exactly like a real regression, and cost
|
||||
// three ticks of this flight.
|
||||
//
|
||||
// ★ THE CHECK BELONGS IN THE SERVER, NOT IN EACH GATE. All nine browser gates fetch through here.
|
||||
// Guarding one chokepoint means none of them can misread a torn tree and none of them needs
|
||||
// editing — where "have every gate acquire the build lock" is nine changes plus a rule every FUTURE
|
||||
// gate has to remember. Same reasoning as serving directories strictly: fix the thing they all pass
|
||||
// through, rather than asking nine callers to be careful.
|
||||
//
|
||||
// A 503 is deliberate, not a 404: 404 means "this is not here", which a gate may reasonably report
|
||||
// as a product defect. 503 with a named cause means "ask again later" and cannot be mistaken for a
|
||||
// finding. Static assets outside the bundle tree (assets/, __gallery/) are unaffected.
|
||||
const TREE = join(DIR, 'doxa-maps')
|
||||
const MANIFEST = join(TREE, 'manifest.json')
|
||||
function treeIsTorn() {
|
||||
try { return !statSync(MANIFEST).isFile() } catch { return true }
|
||||
}
|
||||
|
||||
createServer((req, res) => {
|
||||
let pathname
|
||||
try { pathname = decodeURIComponent(new URL(req.url, 'http://x').pathname) } catch { return send(res, 400, 'bad request\n') }
|
||||
|
||||
if (pathname.startsWith('/doxa-maps/') && treeIsTorn()) {
|
||||
return send(res, 503, 'tree is mid-build — public/doxa-maps/manifest.json is absent.\n' +
|
||||
'A build rm -rf\'s this tree at its start, so anything measured now is a phantom.\n' +
|
||||
'Wait for the build to finish and retry; do NOT record this as a product finding.\n',
|
||||
{ 'retry-after': '30' })
|
||||
}
|
||||
|
||||
// Never let a request escape the served root. normalize() collapses .. before we join.
|
||||
const rel = normalize(pathname).replace(/^(\.\.[/\\])+/, '').replace(/^[/\\]+/, '')
|
||||
const file = join(DIR, rel)
|
||||
|
|
|
|||
|
|
@ -100,10 +100,25 @@ if (bundleCount < 2) {
|
|||
// timeouts and looks like six broken gates.
|
||||
try {
|
||||
const r = await fetch(`${BASE}/doxa-maps/manifest.json`, { signal: AbortSignal.timeout(5000) })
|
||||
// ★ 503 IS A DIFFERENT DIAGNOSIS FROM "no server", and saying so is the whole point of the code
|
||||
// below. serve-strict.mjs returns 503 while public/doxa-maps/manifest.json is absent, because a
|
||||
// build rm -rf's that tree at its START — so a gate running then reads an EMPTY tree and reports
|
||||
// a confident wrong answer. Measured: verify-shell-scroll said "could not find the
|
||||
// dashboard-plus-grid tab" twice in ten minutes during a swarm rebuild, which reads exactly like
|
||||
// a real regression and cost three ticks. Without this branch the aggregator blamed a missing
|
||||
// server, sending the reader to start one that was already running.
|
||||
if (r.status === 503) {
|
||||
console.error('BROWSER-ALL: NOT MEASURED — the built tree is mid-build.')
|
||||
console.error(` ${BASE} answered 503: public/doxa-maps/manifest.json is absent, which is what a`)
|
||||
console.error(' build looks like from the outside (clean rm -rf\'s the tree before rebuilding it).')
|
||||
console.error(' Anything measured now is a PHANTOM. Wait for the build and re-run; do NOT record')
|
||||
console.error(' this as a product finding. Check with: bun run check:settled')
|
||||
process.exit(2)
|
||||
}
|
||||
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(' Start one first: bun run serve # serve-strict.mjs on 4173, --directory public')
|
||||
console.error(' Or point elsewhere: BASE_URL=http://host:port bun run verify:browser-all')
|
||||
process.exit(2)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue