feat(07-04): the budget-gates library — six measurement gates (T-07-18/19/20/22/23b)

- route-cpu: RE-RUNS routing/scripts/measure-route-cpu.mjs as a child process; p95 printed
  against the 8 ms product budget + 10 ms free ceiling; the child's GATE FAIL line carried
  verbatim; the accepted c3 band has a 200 ms drift guard — uncharted regression fails
- bundle-budget: BA9 route-payload gz sum + whole-app bundle ceiling (500000 gz bytes);
  Worker bundle honest NOT MEASURED when bot/dist is absent (never zero)
- kv-writes: 4 write sites inventoried by family, every write TTL-named (PG1); the cron
  zero-write law is source-proven — runGuidanceCronTick's body must contain no .put(/.delete(
- subrequests: per-request-class external-subrequest inventory derived from the route table;
  uninventoried classes FAIL; source-derived honesty sentence verbatim; never a fabricated zero
- cache-headers: reuses cloudflare-probes' runAssetHeaderProbe (imported, never forked)
- golden-corpus: RE-RUNS routing:corpus + search:corpus as child processes (SC4.a/SC4.b)
This commit is contained in:
araharah-executor
2026-10-25 04:59:27 +08:00
parent 160a173010
commit 9efb6e2832
+534
View File
@@ -0,0 +1,534 @@
#!/usr/bin/env node
/*
* budget-gates.mjs — the measurement gates for the consolidated budget audit
* (07-04, D-06).
* ---------------------------------------------------------------------------
* A MAINTAINER-TIME REPO TOOL — NOT a runtime dependency; nothing in bot/src,
* web/src, routing/src, or search/src imports it. The free plan gives a Worker
* 10 ms of CPU per request (Error 1102 beyond it) and 50 subrequests per
* request, so every budget here is MEASURED AND PRINTED, never hoped for
* ("budget gates print their measurement" — the BA9 precedent).
*
* Gate contract (07-RESEARCH Pattern 4): every gate is an async function
* returning { id, pass, evidence, violations? } — and every gate carries an
* honest NOT-RUN path that is NEVER a pass ({ pass:false, notRun:true }).
*
* The gates, in audit order:
* runCpuLadderGate() id: route-cpu — RE-RUNS routing/scripts/measure-route-cpu.mjs as a child process (never re-implemented)
* runBundleBudgetGate() id: bundle-budget — BA9's route-payload gzip sum + the whole-app bundle ceiling
* runKvWriteClassGate() id: kv-writes — the write-class inventory + the cron's ZERO-write law (source-proven)
* runSubrequestClassGate() id: subrequests — the per-request-class external-subrequest inventory (source-derived)
* runCacheHeaderGate() id: cache-headers — the LIVE header probe (re-used from cloudflare-probes.mjs)
* runGoldenCorpusGate() id: golden-corpus — RE-RUNS `routing:corpus` + `search:corpus` as child processes
*
* The route-cpu verdict law (the c3 class, decided in Phase 6 / 06-04): the
* full-sample ladder p95 (25 pairs incl. the 10 adversarial quadrant worst
* cases) MEASURED 38–93 ms across runs — over the 8 ms product budget, and
* the mitigation ladder was exhausted WITHIN the $0 law ((a) rejected on
* correctness — 12/25 pairs unreachable, (b) bidirectional A* shipped,
* (c) Workers Paid rejected on the $0 mandate). The DECIDED posture is the
* honest degradation: cross-metro CPU exhaust → Error 1102 → fail-honest
* null → the client's router-pending state; the committed upgrade lever is
* ALT landmark preprocessing (next milestone). This gate therefore checks
* the measured p95 against the DOCUMENTED accepted band (a 200 ms drift
* guard ≈ 2x the worst documented run): a p95 inside the band is the
* documented state (the record still prints it against the 8 ms budget with
* negative headroom — nothing is hidden); a p95 beyond it is UNCHARTED
* regression and FAILS.
*
* Test injectability (the privacy-audit suite precedent): every gate accepts
* opts-only path overrides; production callers pass nothing.
*/
import { spawnSync, execFileSync } from "node:child_process";
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { gzipSync } from "node:zlib";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { runAssetHeaderProbe } from "./cloudflare-probes.mjs";
const MODULE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
const REPO_ROOT = process.env.PRIVACY_AUDIT_ROOT ?? MODULE_ROOT;
/** The ladder's own budget constant — 8 ms, 20% under the 10 ms free ceiling. */
export const P95_BUDGET_MS = 8;
/** The BA9 route-payload ceiling (D-11): 200 KiB gz. */
export const ROUTE_PAYLOAD_BUDGET_BYTES = 204800;
/** The whole-app bundle ceiling (D-06's record row): 500 KB gz for ALL of web/dist/assets/*.js. */
export const APP_BUNDLE_BUDGET_BYTES = 500000;
/** The drift guard for the accepted c3 band: the documented accepted p95 tops
* at 93.6 ms (06-04's worst shared-box run); beyond 200 ms nothing the
* escalation examined applies — uncharted regression. */
export const ROUTE_CPU_DRIFT_GUARD_MS = 200;
/** A missing environment is the exit-2 class: pass:false + notRun:true. */
function notRun(id, reason) {
return { id, pass: false, notRun: true, evidence: reason };
}
/* ══════════════════════════ route-cpu ══════════════════════════ */
/**
* RE-RUN the existing A* ladder as a child process and gate its measurement.
* The child's stdout carries one ` label: <ms> ms (<km> km / ...)` line per
* pair, an optional `unreachable — honest null` marker per dead-end probe,
* and the summary `p95: <n> ms (max <n> ms, mean <n> ms, n=<k>)` line.
* opts: scriptPath, graphPath, ladderOutput (pre-captured child stdout —
* tests), cwd.
*/
export function runCpuLadderGate(opts = {}) {
const id = "route-cpu";
const script = opts.scriptPath ?? path.join(REPO_ROOT, "routing", "scripts", "measure-route-cpu.mjs");
const graphPath = opts.graphPath ?? path.join(REPO_ROOT, "routing", "data", "kl-roadgraph.v1.bin");
if (!existsSync(graphPath)) {
return notRun(id, "kl-roadgraph.v1.bin is absent — build it (node routing/scripts/build-road-graph.mjs) and re-run");
}
if (!existsSync(script)) return notRun(id, "routing/scripts/measure-route-cpu.mjs is absent — the ladder cannot be re-run");
let out;
if (opts.ladderOutput !== undefined) {
out = opts.ladderOutput; // test injection — the captured child stdout
} else {
try {
out = execFileSync(process.execPath, [script], {
cwd: opts.cwd ?? REPO_ROOT,
encoding: "utf8",
timeout: 120000,
maxBuffer: 1 << 24,
stdio: ["ignore", "pipe", "pipe"],
});
} catch (err) {
// The ladder exits 1 on its own gate failure — its stdout is still the
// measurement (the GATE FAIL line prints to stderr and is merged here:
// the mitigation-ladder text is part of the evidence).
if (err.stdout === undefined) return notRun(id, `the ladder could not run (${err.code === "ETIMEDOUT" ? "timed out" : "spawn failure"})`);
out = err.stdout + "\n" + (err.stderr ?? "");
}
}
// The child's own verdict lines — carried VERBATIM (the mitigation-ladder
// text is part of the evidence, per the plan).
const childP95Line = out.match(/^p95: .*$/m)?.[0] ?? null;
const childGateLine = out.match(/^GATE (?:FAIL|PASS): .*$/m)?.[0] ?? null;
const p95 = Number(out.match(/^p95: (\d+(?:\.\d+)?) ms/m)?.[1] ?? NaN);
if (!Number.isFinite(p95) || childP95Line === null) {
return notRun(id, "the ladder produced no parsable p95 line — the measurement is unavailable, never assumed");
}
const overBudget = p95 > P95_BUDGET_MS;
const uncharted = p95 > ROUTE_CPU_DRIFT_GUARD_MS;
// The verdict: inside the documented accepted c3 band = the documented
// state (see the header's verdict law); beyond the drift guard = FAIL.
const pass = !uncharted;
const verdict = uncharted
? `UNCHARTED REGRESSION — p95 beyond the ${ROUTE_CPU_DRIFT_GUARD_MS} ms drift guard (the documented accepted c3 band tops at ~93 ms)`
: overBudget
? `over the ${P95_BUDGET_MS} ms product budget — the DOCUMENTED, ACCEPTED c3 class (cross-metro honest degradation: 1102 → fail-honest null → router-pending; mitigation ladder exhausted within the $0 law; the ALT landmark lever is a next-milestone item)`
: `within the ${P95_BUDGET_MS} ms product budget`;
const evidence =
`p95 ${p95.toFixed(3)} ms <= 8 ms budget (10 ms free-plan ceiling) — NOT satisfied as stated: ` +
`${verdict}. Full-sample ladder (corpus + quadrant worst cases): ${childP95Line}. ` +
`Child's own gate line: ${childGateLine ?? "(none printed)"}. ` +
`LADDER HISTORY context is cited in docs/BUDGETS.md, not re-printed here.`;
return { id, pass, evidence, p95 };
}
/* ══════════════════════════ bundle-budget ══════════════════════════ */
/** The BA9 input set (web/test/build-artifact.test.mjs lines 204–209). */
function collectTsFiles(dir) {
const out = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) out.push(...collectTsFiles(full));
else if (entry.name.endsWith(".ts")) out.push(full);
}
return out;
}
/**
* The BA9 measurement, re-implemented in the library (the inputs, the gzip
* sum, the 204800 ceiling), PLUS the whole-app bundle measurement and the
* Worker bundle size (NOT MEASURED when bot/dist is absent — never zero).
* opts: distDir, routeDirs, routeFixturesFile, botDistDir.
*/
export function runBundleBudgetGate(opts = {}) {
const id = "bundle-budget";
const webRoot = opts.webRoot ?? path.join(REPO_ROOT, "web");
const distDir = opts.distDir ?? path.join(webRoot, "dist");
const routeDirs = opts.routeDirs ?? [
path.join(REPO_ROOT, "routing", "src"),
path.join(webRoot, "src", "route"),
path.join(webRoot, "src", "guidance"),
];
const routeFixturesFile = opts.routeFixturesFile ?? path.join(REPO_ROOT, "routing", "src", "data", "route-fixtures.json");
const measurements = [];
const violations = [];
// — BA9: the route payload's gz bytes (source gz ≈ conservative vs minified+gz).
if (!existsSync(routeFixturesFile)) return notRun(id, "route-fixtures.json is absent — the BA9 inputs are incomplete");
const missingDirs = routeDirs.filter((d) => !existsSync(d));
if (missingDirs.length > 0) return notRun(id, `route-payload input dir(s) absent: ${missingDirs.map((d) => path.relative(REPO_ROOT, d)).join(", ")}`);
const inputs = [routeFixturesFile, ...routeDirs.flatMap((dir) => collectTsFiles(dir))];
const routePayloadGz = inputs.reduce((sum, f) => sum + gzipSync(readFileSync(f)).length, 0);
measurements.push(`route bundle budget: ${routePayloadGz} / ${ROUTE_PAYLOAD_BUDGET_BYTES} gz bytes`);
if (routePayloadGz > ROUTE_PAYLOAD_BUDGET_BYTES) {
violations.push(`route payload is ${routePayloadGz} gz bytes — over the ${ROUTE_PAYLOAD_BUDGET_BYTES}-byte (200 KiB) D-11 budget by ${routePayloadGz - ROUTE_PAYLOAD_BUDGET_BYTES}; shrink the fixtures (simplified geometry) or the route code before shipping (T-05-12)`);
}
// — The whole-app bundle: every hashed JS asset in web/dist, gz-summed.
if (!existsSync(distDir)) return notRun(id, "web/dist is absent — run npm run build -w web and re-run");
const assetsDir = path.join(distDir, "assets");
if (!existsSync(assetsDir)) return notRun(id, "web/dist/assets is absent — a build without hashed assets is not the shipped shape");
const jsAssets = readdirSync(assetsDir).filter((f) => f.endsWith(".js"));
if (jsAssets.length === 0) return notRun(id, "web/dist/assets carries no .js file — the build is broken");
const appBundleGz = jsAssets.reduce((sum, f) => sum + gzipSync(readFileSync(path.join(assetsDir, f))).length, 0);
measurements.push(`app bundle budget: ${appBundleGz} / ${APP_BUNDLE_BUDGET_BYTES} gz bytes (${jsAssets.length} hashed js asset(s))`);
if (appBundleGz > APP_BUNDLE_BUDGET_BYTES) {
violations.push(`app bundle is ${appBundleGz} gz bytes — over the ${APP_BUNDLE_BUDGET_BYTES}-byte (500 KB gz) D-06 record ceiling by ${appBundleGz - APP_BUNDLE_BUDGET_BYTES}`);
}
// — The Worker bundle's uncompressed size (honest NOT MEASURED, never zero).
const botDistDir = opts.botDistDir ?? path.join(REPO_ROOT, "bot", "dist");
if (existsSync(botDistDir)) {
// A flat byte-sum over the directory's files (wrangler emits a bundle dir).
let workerBytesTotal = 0;
const walkSum = (dir) => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walkSum(full);
else workerBytesTotal += readFileSync(full).length;
}
};
walkSum(botDistDir);
measurements.push(`Worker bundle: ${workerBytesTotal} uncompressed bytes (bot/dist)`);
} else {
measurements.push("Worker bundle: NOT MEASURED — bot/dist is absent (wrangler builds in memory on deploy; the number is not fabricated)");
}
const evidence =
violations.length === 0
? `${measurements.join(" | ")}`
: `${measurements.join(" | ")} — ${violations.length} violation(s): ${violations.join("; ")}`;
return { id, pass: violations.length === 0, evidence, violations: violations.length };
}
/* ══════════════════════════ kv-writes ══════════════════════════ */
/** The key families (the KV storage contract — shared with cloudflare-probes). */
const KEY_FAMILIES = ["session:", "chat:", "loc:", "guidance:"];
/**
* The KV write-class inventory (T-07-18). Three laws:
* 1. every `.put(` call site in bot/src writes a key whose family is one of
* the four TTL-only families AND carries a NAMED TTL constant on the
* same line (the PG1 law, re-checked from the budget side);
* 2. the cron tick (`runGuidanceCronTick`'s body) contains NO `.put(` and
* NO `.delete(` — the zero-write law is SOURCE-PROVEN, not claimed;
* 3. the per-family TTLs are READ from the constants (never hard-coded).
* opts: botRoot (defaults REPO_ROOT/bot).
*/
export function runKvWriteClassGate(opts = {}) {
const id = "kv-writes";
const botRoot = opts.botRoot ?? path.join(REPO_ROOT, "bot");
const srcDir = path.join(botRoot, "src");
if (!existsSync(srcDir)) return notRun(id, "bot/src is absent — the write classes cannot be inventoried");
// — The write-class inventory: every .put( call site in bot/src.
const putSites = [];
const walk = (dir) => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walk(full);
else if (entry.name.endsWith(".ts")) {
const lines = readFileSync(full, "utf8").split("\n");
lines.forEach((line, i) => {
if (line.includes(".put(")) {
putSites.push({ file: path.relative(botRoot, full), line: i + 1, text: line.trim() });
}
});
}
}
};
walk(srcDir);
const classViolations = [];
const classRows = [];
// Family ownership by module (the key schemes are each module's own law,
// documented in their headers — sessions.ts owns session:/chat:, etc.).
// A .put( in a file OUTSIDE the owned modules must carry a family literal
// on its own line, or it is a new, unclassified write family.
const FILE_FAMILY = (rel) =>
rel.startsWith("src/kv/sessions.ts")
? "session: + chat:"
: rel.startsWith("src/kv/location-pin.ts")
? "loc:"
: rel.startsWith("src/kv/guidance-sessions.ts")
? "guidance:"
: null;
for (const site of putSites) {
const owned = FILE_FAMILY(site.file);
const literalFamily = KEY_FAMILIES.find((f) => site.text.includes(f));
const family = owned ?? literalFamily;
// The named-TTL law: the call line carries `expirationTtl: <CONSTANT>`.
const namedTtl = /expirationTtl:\s*[A-Z][A-Z0-9_]+/.test(site.text);
if (family === undefined || family === null) {
classViolations.push(`${site.file}:${site.line} — a .put( whose key family is not one of ${KEY_FAMILIES.join("/")} and whose module owns none`);
} else {
classRows.push(`${site.file}:${site.line} ${family}`);
}
if (!namedTtl) {
classViolations.push(`${site.file}:${site.line} — a .put( WITHOUT a NAMED TTL constant on the same line (PG1)`);
}
}
// — The TTL table, READ from the constants (never hard-coded).
const ttlRows = [];
const ttlConstants = [
["SESSION_TTL_SECONDS", "src/kv/sessions.ts", "session: + chat:"],
["PIN_TTL_SECONDS", "src/kv/location-pin.ts", "loc:"],
["GUIDANCE_SESSION_TTL_SECONDS", "src/kv/guidance-sessions.ts", "guidance:"],
];
for (const [name, file, families] of ttlConstants) {
const full = path.join(botRoot, file);
if (!existsSync(full)) {
ttlRows.push(`${name}: NOT FOUND (${file} absent)`);
classViolations.push(`${file} is absent — the ${name} constant cannot be read`);
continue;
}
const value = readFileSync(full, "utf8").match(new RegExp(`export const ${name} = (\\d+);`))?.[1];
if (value === undefined) {
ttlRows.push(`${name}: NOT FOUND in ${file}`);
classViolations.push(`${file} no longer exports ${name} — the TTL table cannot be read from source`);
} else {
ttlRows.push(`${name} = ${value} (${families})`);
}
}
// — THE CRON LAW: extract runGuidanceCronTick's body and count writes.
const webhookPath = path.join(srcDir, "webhook.ts");
let cronWriteCount = null;
if (!existsSync(webhookPath)) {
classViolations.push("bot/src/webhook.ts is absent — the cron zero-write law cannot be source-proven");
} else {
const src = readFileSync(webhookPath, "utf8");
const start = src.indexOf("async function runGuidanceCronTick(");
if (start === -1) {
classViolations.push("runGuidanceCronTick is absent from bot/src/webhook.ts — the cron law's subject is gone");
} else {
// A simple brace counter from the function's first { to its match.
let depth = 0;
let end = -1;
for (let i = src.indexOf("{", start); i < src.length; i++) {
if (src[i] === "{") depth += 1;
else if (src[i] === "}") {
depth -= 1;
if (depth === 0) {
end = i;
break;
}
}
}
if (end === -1) {
classViolations.push("runGuidanceCronTick's body did not balance — the extraction failed");
} else {
const body = src.slice(start, end + 1);
cronWriteCount = (body.match(/\.put\(/g) ?? []).length + (body.match(/\.delete\(/g) ?? []).length;
if (cronWriteCount !== 0) {
classViolations.push(`the cron tick performs ${cronWriteCount} KV write/delete call(s) — the free-plan zero-write law (T-07-18) is violated in runGuidanceCronTick`);
}
}
}
}
const cronEvidence =
cronWriteCount === null ? "cron write count: UNKNOWN (extraction failed)" : `cron write count: ${cronWriteCount} (runGuidanceCronTick scans for .put(/.delete( — zero; the tick lists, reads, and sends only)`;
const evidence =
classViolations.length === 0
? `${putSites.length} KV write site(s) [${[...new Set(classRows)].join("; ")}] — every write carries a NAMED TTL constant; ${ttlRows.join("; ")}; ${cronEvidence}`
: `${putSites.length} write site(s); ${cronEvidence}; ${classViolations.length} violation(s): ${classViolations.join("; ")}`;
return { id, pass: classViolations.length === 0, evidence, violations: classViolations.length };
}
/* ══════════════════════════ subrequests ══════════════════════════ */
/**
* The per-request-class external-subrequest inventory (T-07-23b).
*
* HONESTY REQUIREMENT: this gate is a SOURCE-DERIVED inventory, not a live
* Workers Logs metric. Each row's count is derived from the Worker's route
* table (bot/src/webhook.ts) and the code paths behind it; the live Workers
* Logs subrequest metric is NOT available outside --live and when it cannot
* be produced the row reads `not measured outside --live` — never a
* fabricated number.
*
* The derived facts (each with its source site):
* /telegram/webhook ≤ 2 per update — ctx.reply / replyWithPhoto with a
* text fallback (bot/src/bot/commands/start.ts);
* the count is update-dependent — not measured live
* /api/session 0 external — HMAC validation is local; KV ops are
* NOT subrequests (bot/src/webhook.ts handleSession)
* /api/location 0 external — KV reads only (handleLocation)
* /api/location-fallback 1 external — bot.api.sendMessage
* (bot/src/bot/handlers/location-fallback.ts)
* /api/guidance 0 external — KV only (handleGuidance)
* /api/route 0 external — the graph comes from the R2/KV
* binding, never fetched; the A* search is pure
* CPU (bot/src/routing/worker-router.ts)
* scheduled (cron tick) 1 per DUE envelope (api.sendPhoto,
* bot/src/webhook.ts) — the ONLY recurring
* external-subrequest class; the per-tick count
* depends on the number of live due envelopes —
* not measured
*
* KV operations are NOT subrequests — Cloudflare counts them against the
* separate KV-operations ceiling (1,000/invocation) — so they are reported
* by the kv-writes gate and this gate SAYS SO explicitly.
*
* The gate derives the route table FROM webhook.ts (`url.pathname === "…"`
* entries) and FAILS on any class the inventory does not cover — the table
* must follow the code, not the reverse.
* opts: webhookPath, plus per-class overrides for the fixture tests.
*/
export function runSubrequestClassGate(opts = {}) {
const id = "subrequests";
const webhookPath = opts.webhookPath ?? path.join(REPO_ROOT, "bot", "src", "webhook.ts");
if (!existsSync(webhookPath)) return notRun(id, "bot/src/webhook.ts is absent — the route table cannot be derived");
const src = readFileSync(webhookPath, "utf8");
// Derive the route classes from the Worker's own route table.
const routes = [...src.matchAll(/url\.pathname === "([^"]+)"/g)].map((m) => m[1]);
if (src.includes("scheduled(") || src.includes("runGuidanceCronTick")) routes.push("scheduled (cron tick)");
// The inventory — the source-derived facts, keyed by route class.
const INVENTORY = {
"/telegram/webhook": {
count: "≤ 2 per update (reply / photo-with-text-fallback — update-dependent)",
source: "bot/src/bot/commands/start.ts ctx.reply/replyWithPhoto",
},
"/api/session": { count: "0 external subrequests", source: "bot/src/webhook.ts handleSession — HMAC validation is local; KV ops are not subrequests" },
"/api/location": { count: "0 external subrequests", source: "bot/src/webhook.ts handleLocation — KV reads only" },
"/api/location-fallback": { count: "1 external subrequest (bot.api.sendMessage)", source: "bot/src/bot/handlers/location-fallback.ts" },
"/api/guidance": { count: "0 external subrequests", source: "bot/src/webhook.ts handleGuidance — KV only" },
"/api/route": { count: "0 external subrequests", source: "bot/src/routing/worker-router.ts — the graph comes from the R2/KV binding, never fetched; the A* search is pure CPU" },
"scheduled (cron tick)": {
count: "1 per DUE envelope (api.sendPhoto); the per-tick total depends on the number of live due envelopes — not measured",
source: "bot/src/webhook.ts runGuidanceCronTick — the ONLY recurring external-subrequest class",
},
};
const violations = [];
const rows = [];
for (const route of routes) {
const row = INVENTORY[route];
if (row === undefined) {
violations.push(`uninventoried request class: ${route} — the subrequest inventory must be updated beside the route table`);
continue;
}
rows.push(`${route} → ${row.count} [${row.source}]`);
}
// A documented inventory row whose route vanished from the table is drift too.
for (const route of Object.keys(INVENTORY)) {
if (!routes.includes(route)) violations.push(`inventoried class ${route} is no longer in the route table — update the inventory`);
}
// Re-verify the two MANDATORY derived facts from the source (they are the
// record's load-bearing rows — a silent regression would empty them).
const fallbackSrc = path.join(REPO_ROOT, "bot", "src", "bot", "handlers", "location-fallback.ts");
if (opts.verifyFacts !== false) {
if (existsSync(fallbackSrc) && !readFileSync(fallbackSrc, "utf8").includes("api.sendMessage")) {
violations.push("location-fallback no longer calls api.sendMessage — the inventory's 1-subrequest row is stale");
}
if (existsSync(webhookPath)) {
const tickStart = src.indexOf("async function runGuidanceCronTick(");
const routeIdx = src.indexOf('url.pathname === "/api/route"');
if (tickStart !== -1 && !src.slice(tickStart).includes("api.sendPhoto")) {
violations.push("the cron tick no longer sends via api.sendPhoto — the only-recurring-class fact is stale");
}
if (routeIdx !== -1 && /url\.pathname === "\/api\/route"[\s\S]{0,2000}?\bfetch\(/.test(src.slice(routeIdx, src.indexOf("return new Response(\"Not found\"")))) {
violations.push("/api/route now appears to fetch — its 0-external-subrequest fact is stale");
}
}
}
const table = rows.join(" | ");
const liveNote = "live Workers Logs subrequest metric: not measured outside --live (and not producible by this gate — see the record)";
const kvNote = "KV operations are NOT subrequests — they count against the separate KV-operations ceiling (1,000/invocation) and are tracked by the kv-writes gate";
const evidence =
violations.length === 0
? `source-derived inventory, not a live Workers Logs metric — ${routes.length} request class(es): ${table} | budget 50/request per class | ${liveNote} | ${kvNote}`
: `source-derived inventory; ${violations.length} violation(s): ${violations.join("; ")}`;
return { id, pass: violations.length === 0, evidence, violations: violations.length };
}
/* ══════════════════════════ cache-headers ══════════════════════════ */
/**
* The LIVE header probe — RE-USED from scripts/lib/cloudflare-probes.mjs
* (imported, never forked). Without a deployed origin the probe degrades to
* notRun. The probe quotes the observed cache-control / cf-cache-status
* VERBATIM (cf-cache-status is probabilistic per Cloudflare's own footnote —
* quoted, never interpreted as a guarantee).
* opts: baseUrl (default: the shared probe's own resolution — MINI_APP_URL /
* bot/wrangler.jsonc), forceMissingOrigin (tests).
*/
export function runCacheHeaderGate(opts = {}) {
const id = "cache-headers";
if (opts.forceMissingOrigin || opts.baseUrl === null) {
return notRun(id, "live cache-header probe not run — no deployed origin or credentials");
}
const result = opts.baseUrl !== undefined ? runAssetHeaderProbe({ baseUrl: opts.baseUrl }) : runAssetHeaderProbe();
// The shared probe carries id "asset-headers"; the budget audit's row is
// "cache-headers" — same measurement, one id per audit family.
return { ...result, id };
}
/* ══════════════════════════ golden-corpus ══════════════════════════ */
/**
* RE-RUN the golden route + search corpora as child processes (cwd = repo
* root) — RE-RUN, never re-implemented — so the ONE budget-audit command
* that produces the record also proves the corpora are green (SC4.a/SC4.b).
* opts: commands (Array<[id, argv]> overrides — tests).
*/
export function runGoldenCorpusGate(opts = {}) {
const id = "golden-corpus";
const commands =
opts.commands ?? [
["routing:corpus", ["npm", "run", "routing:corpus"]],
["search:corpus", ["npm", "run", "search:corpus"]],
];
const parts = [];
const failures = [];
for (const [corpusId, argv] of commands) {
let res;
try {
res = spawnSync(argv[0], argv.slice(1), {
cwd: opts.cwd ?? REPO_ROOT,
encoding: "utf8",
timeout: 300000,
maxBuffer: 1 << 26,
stdio: ["ignore", "pipe", "pipe"],
});
} catch (err) {
failures.push(`${corpusId} could not run (${err.code === "ETIMEDOUT" ? "timed out" : "spawn failure"})`);
continue;
}
if (res.status !== 0) {
const tail = ((res.stdout ?? "") + (res.stderr ?? "")).trim().split("\n").slice(-3).join(" | ");
failures.push(`${corpusId} exited ${res.status ?? "signal"} — its own failure output: ${tail}`);
} else {
parts.push(`${corpusId} exit 0`);
}
}
const evidence =
failures.length === 0
? `golden corpora re-run by THIS gate (child processes): ${parts.join("; ")} — SC4.a + SC4.b proven by the same command that produces the record`
: `${failures.join("; ")}`;
return { id, pass: failures.length === 0, evidence, violations: failures.length };
}