doxa-maps/internal/serve-strict.mjs
doxa-maps-88655c70 0f0bebd9c2 Serve locally the way production serves, so a bad URL fails here instead of live
bun run serve was python3 -m http.server, which resolves a directory request to
its index.html. The live static layer returns 404 for the same request. So a gate
could pass locally on a URL that does not work in production, and nothing local
could ever catch it.

That is not a harness quirk, it is a blind spot with a green light on it: every
defect inside the permissive gap passes locally by construction and can only be
found in production, which is the worst place to find it. It cost a false
"the publish failed, zero filters live" reading on a publish that was
byte-for-byte correct.

serve-strict.mjs returns 404 for a directory, exactly like production, with no
index fallback and no directory listing. It also refuses to read outside its
root. Loopback only, inherited from the script it replaces, whose own comment
records that the bind was once 0.0.0.0 and answered on the box's public IP while
a gate was running. The permissive server is kept as serve:permissive rather
than deleted, so the difference can be demonstrated.

It earned itself immediately: verify-shell-scroll held two bare-directory URLs
and failed the first time it ran against the strict server. Both now ask for
index.html, which is the form the manifest already ships for all 28 profiles,
and the gate passes again -- both wheel gestures moving the grid 1500px onto
screen.

Fixing the server rather than the URLs is the point. The repo was already
production-correct: 28 of 28 manifest URLs and 82 gate references use
index.html. What was missing was anything that could fail when one did not. A
rule you have to remember fails under time pressure; a server that returns 404
does not.
2026-09-23 03:09:25 +00:00

95 lines
5.1 KiB
JavaScript

#!/usr/bin/env node
/**
* serve-strict.mjs — serve public/ the way PRODUCTION serves it, not the way it is convenient.
*
* ★ THE DEFECT THIS EXISTS FOR, hit live 2026-09-23. `bun run serve` was
* `python3 -m http.server`, which resolves a DIRECTORY request to its index.html. The live
* nuxtbase.app static layer does NOT:
*
* /plugin-assets/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/ -> 404 live, 200 local
* /plugin-assets/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/index.html -> 200 both
*
* So every headless verification could pass on a URL that 404s in production, and the divergence
* was invisible until something was finally checked against the live site. It cost a false
* "the publish failed, zero filters live" reading on a publish that was byte-for-byte correct.
*
* ★ WHY FIX THE SERVER RATHER THAN THE URLS. The repo is already production-correct: the manifest
* ships 28/28 iframeUrl values ending in /index.html, and the gates use that form 82 times. Only
* ad-hoc probes and one gate ever used the bare-directory form. So the exposure was never really
* the URLs — it was that NOTHING LOCAL COULD CATCH a bad one. A local environment MORE PERMISSIVE
* than production is a blind spot with a green light on it: every defect inside the permissive gap
* passes locally by construction and can only be found in production, which is the worst place to
* find it. Making local strict converts that class from "discovered live" to "impossible to commit".
* A rule you have to REMEMBER fails under time pressure; a server that returns 404 does not.
*
* WHAT IT DOES, deliberately minimal:
* - serves files under public/ (or --directory) on 127.0.0.1 only;
* - a request for a DIRECTORY returns 404, exactly like the live layer — no index fallback,
* no redirect-to-index, no directory listing;
* - refuses to escape the served root (a .. traversal is 403, not a file read).
*
* LOOPBACK ONLY, inherited from the script this replaces: the bind was once 0.0.0.0, which
* published the built tree on every interface of whatever machine ran a gate — measured answering
* HTTP 200 on the box's PUBLIC IP while a browser gate was running. Override with HOST only if you
* deliberately want another host to reach it.
*
* Usage: node internal/serve-strict.mjs [--port 4173] [--directory public]
*/
import { createServer } from 'node:http'
import { createReadStream, statSync } from 'node:fs'
import { resolve, join, extname, normalize } from 'node:path'
import { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const argv = process.argv.slice(2)
const argOf = (name, dflt) => { const i = argv.indexOf(name); return i >= 0 && argv[i + 1] ? argv[i + 1] : dflt }
const PORT = Number(process.env.PORT || argOf('--port', '4173'))
const HOST = process.env.HOST || '127.0.0.1'
const DIR = resolve(ROOT, argOf('--directory', 'public'))
const TYPES = {
'.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8',
'.mjs': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8',
'.json': 'application/json; charset=utf-8', '.geojson': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
'.webp': 'image/webp', '.gif': 'image/gif', '.ico': 'image/x-icon', '.woff2': 'font/woff2',
'.csv': 'text/csv; charset=utf-8', '.pbf': 'application/x-protobuf', '.txt': 'text/plain; charset=utf-8',
}
const send = (res, code, body, headers = {}) => {
res.writeHead(code, { 'content-type': 'text/plain; charset=utf-8', ...headers })
res.end(body)
}
createServer((req, res) => {
let pathname
try { pathname = decodeURIComponent(new URL(req.url, 'http://x').pathname) } catch { return send(res, 400, 'bad request\n') }
// Never let a request escape the served root. normalize() collapses .. before we join.
const rel = normalize(pathname).replace(/^(\.\.[/\\])+/, '').replace(/^[/\\]+/, '')
const file = join(DIR, rel)
if (!file.startsWith(DIR)) return send(res, 403, 'forbidden\n')
let st
try { st = statSync(file) } catch { return send(res, 404, 'not found\n') }
// ★ THE WHOLE POINT. A directory is a 404 here because it is a 404 in production. Do not add an
// index fallback "for convenience" — the convenience is what hid a live defect for a full flight.
if (st.isDirectory()) {
return send(res, 404, 'not found — this server does not serve directory indexes, because the live\n' +
'static layer does not either. Request the file explicitly:\n' +
' ' + pathname.replace(/\/*$/, '/') + 'index.html\n')
}
res.writeHead(200, {
'content-type': TYPES[extname(file).toLowerCase()] || 'application/octet-stream',
'content-length': st.size,
'cache-control': 'no-cache',
})
createReadStream(file).pipe(res)
}).listen(PORT, HOST, () => {
console.log(`serve-strict: http://${HOST}:${PORT} -> ${DIR.replace(ROOT + '/', '')}`)
console.log(' directory requests return 404 (matching production). Ask for /index.html explicitly.')
})