Files
arah2/routing/scripts/measure-optimality-gate.mjs
T
araharah-executor bcab4b9999 feat(08.1-02): re-pin measurement gates for ARG3 and extend the rollout locks (GREEN)
- The optimality gate's decode-failure message names ARG3 and the rebuild
  command (node routing/scripts/build-road-graph.mjs); the frozen D-03
  constants (OPTIMALITY_EPSILON_PCT = 0.1, REQUIRED_BASELINE_ARG_VERSION
  = 1) and the immutable alt-baseline.json record are untouched.
- The oracle sweep's graph-missing (SKIP) and decode-failure wording names
  the ARG3 binary; the seeded 200-pair symmetric-compare constants and the
  three violation classes are unchanged.
- RO1/RO2 ban the ARG3 magic 0x41524733 in the upload script's and the
  Worker's comment-stripped code: the one-path laws now cover the CURRENT
  format, not just the retired ones. The Worker keeps exactly one
  decodeGraph call, no GRAPH_VERSION, no magic, no === 2.
- measure-route-cpu.mjs is untouched here by design: its LADDER HISTORY
  gains the CH rung only once the measured numbers exist (task 3).
2026-10-26 07:48:28 +08:00

304 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/*
* measure-optimality-gate.mjs — the D-03 optimality gate (Phase 8 plan 08-05).
* ------------------------------------------------------------------------------
* A MAINTAINER-TIME REPO TOOL — NOT a runtime dependency; nothing in bot/src,
* web/src, or routing/src imports it (the route-corpus/measure-route-cpu law).
*
* Purpose: prove the ALT preprocessing (ARG2 + landmark steering) did not move
* route OPTIMALITY, by re-routing the SAME coordinate-pinned pairs the frozen
* pre-ALT record (Plan 08-01's routing/test/fixtures/alt-baseline.json)
* measured, and gating on:
* D03A — seconds equality: |post − pre| within ± OPTIMALITY_EPSILON_PCT % of
* pre (equal seconds pass; equal-cost TIES are fine — D-03);
* D03B — verdict-class preservation: reachable/unreachable stays the same
* per pair (unsnappable pairs that now snap are an INFO count, not a
* violation — D-03's letter gates the reachable/unreachable classes).
*
* WHAT THE GATE NEVER DOES: compare NODE PATHS. A different equal-cost path
* (a tie) is documented behavior under bidirectional search + ALT potentials —
* structurally invisible to this gate (no path read exists here; only seconds
* and verdict classes are compared).
*
* Inputs:
* - the COMMITTED record routing/test/fixtures/alt-baseline.json (IMMUTABLE
* — the recorder refuses overwrite; delete by explicit git revert only);
* - the LOCAL rebuilt binary routing/data/kl-roadgraph.v1.bin (ARG3 since
* Phase 08.1 — the storage NAME stays v1; the header version is the
* format truth).
*
* Constant-mirror law: this script's OPTIMALITY_EPSILON_PCT is asserted EQUAL
* to the record's epsilon block BEFORE any measurement — a record edit alone
* cannot fake a pass. Extract drift (node/edge count deltas vs the record) is
* a printed WARNING, never a violation, and NEVER a reason to re-record
* (Pitfall 1: the record is the anchor; mass failures escalate to the user).
*
* Echo law: pair ids + measurements ONLY (the route-corpus posture) — pair
* coordinates in the record are public bbox-derived values, never user data.
*
* Usage:
* node routing/scripts/measure-optimality-gate.mjs
*
* Exit codes: 0 GATE PASS (zero violations across both checks); 1 any
* violation, a mirror-assert failure, or an absent/undecodable input.
*/
import { existsSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { decodeGraph } from "../src/graph.ts";
import { snapToGraph as snap } from "../src/snap.ts";
import { routeAstar as astar } from "../src/astar.ts";
const here = dirname(fileURLToPath(import.meta.url)); // routing/scripts
/** The frozen pre-ALT record — the D-03 anchor (committed, immutable). */
const RECORD_PATH = resolve(here, "..", "test", "fixtures", "alt-baseline.json");
/** The current binary under test (same path law as measure-route-cpu.mjs). */
const GRAPH_PATH = resolve(here, "..", "data", "kl-roadgraph.v1.bin");
/**
* D-03's named epsilon (±0.1% of the pre-ALT seconds) — the MIRROR of the
* record's epsilon block. The main() assert below refuses to measure when
* the two disagree: the record cannot be edited to fake an epsilon, and this
* constant cannot drift from the record silently.
*/
const OPTIMALITY_EPSILON_PCT = 0.1;
/** The record must be the PRE-ALT baseline (Pitfall 1's guard, mirrored). */
const REQUIRED_BASELINE_ARG_VERSION = 1;
function fail(message) {
console.error(`measure-optimality-gate: ${message}`);
process.exit(1);
}
function toArrayBuffer(buf) {
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
}
/**
* The measurement core — exported so a suite can mirror the SAME numbers this
* CLI prints (one measurement, two reporters — the route-corpus split).
*
* For each record pair: snap both endpoints on the CURRENT graph, route with
* the CURRENT search, and classify:
* "pass" — seconds within epsilon (both routed) or the same null
* verdict class preserved (unreachable/unsnappable);
* "violation" — a D03A seconds break or a D03B reachable/unreachable class
* break;
* "re-snapped" — record-unsnappable that now snaps (INFO; counted, printed
* as a WARNING in the summary when > 0, never a violation);
* "unsnapped" — a record-routed pair whose snap now misses (a D03B class
* break — the pair no longer routes at all).
*
* @param graph decoded RoadGraph (the current ARG3 build)
* @param record parsed alt-baseline.json (the frozen pre-ALT record)
* @param opts { snap, astar } — the live modules (injected like the corpus core)
* @returns {{ checks: { D03A: check, D03B: check }, rows: Array, reSnapped: number, pass: boolean }}
*/
export function measureOptimality(graph, record, { snap, astar }) {
const checks = {
D03A: { id: "D03A", label: "seconds equality (±0.1%)", violations: [] },
D03B: { id: "D03B", label: "verdict class preserved", violations: [] },
};
const rows = [];
let reSnapped = 0;
for (const pair of record.pairs) {
const pre = pair.seconds; // number | null
const verdictPre = pair.verdict; // "reachable" | "unreachable" | "unsnappable"
const origin = snap(graph, pair.origin);
const goal = snap(graph, pair.destination);
let post = null;
let verdictPost = "unsnappable";
if (origin !== null && goal !== null) {
const result = astar(graph, origin.node, goal.node);
if (result === null) verdictPost = "unreachable";
else {
verdictPost = "reachable";
post = result.seconds;
}
}
let cls = "pass";
let deltaPct = null;
if (verdictPre === "unsnappable") {
if (verdictPost === "unsnappable") {
cls = "pass"; // unsnappable preserved — the honest null, stable
} else {
// The pair now snaps (+ routes or honestly reports unreachable):
// an INFO re-snap, counted and summary-WARNED, NOT a violation.
cls = "re-snapped";
reSnapped += 1;
}
} else if (verdictPost === "unsnappable") {
cls = "unsnapped";
checks.D03B.violations.push({
pairId: pair.pairId,
reason: `verdict class break: ${verdictPre} (pre-ALT record) -> unsnappable (post-ALT snap miss)`,
});
} else if (verdictPre !== verdictPost) {
cls = "violation";
checks.D03B.violations.push({
pairId: pair.pairId,
reason: `verdict class break: ${verdictPre} -> ${verdictPost}`,
});
} else if (verdictPost === "reachable") {
// Both sides routed — D03A seconds equality (paths are NEVER compared;
// equal-cost tie differences cannot produce a violation by construction).
deltaPct = ((post - pre) / pre) * 100;
if (Math.abs(deltaPct) > OPTIMALITY_EPSILON_PCT) {
cls = "violation";
checks.D03A.violations.push({
pairId: pair.pairId,
reason: `seconds drift ${pre.toFixed(1)}s -> ${post.toFixed(1)}s (Δ ${deltaPct.toFixed(3)}% beyond ±${OPTIMALITY_EPSILON_PCT}%)`,
});
}
}
// else: unreachable -> unreachable — class preserved, pass.
rows.push({ pairId: pair.pairId, pre, post, deltaPct, verdictPre, verdictPost, class: cls });
}
const pass =
checks.D03A.violations.length === 0 && checks.D03B.violations.length === 0;
return { checks, rows, reSnapped, pass };
}
/* ————— CLI reporter ————— */
function formatSeconds(v) {
return v === null ? "unsnappable" : `${v.toFixed(1)}s`;
}
function report(record, results, drift) {
const routed = results.rows.filter((r) => r.class === "pass" && r.pre !== null && r.post !== null);
const unreachPreserved = results.rows.filter(
(r) => r.verdictPre === "unreachable" && r.verdictPost === "unreachable",
).length;
const unsnapPreserved = results.rows.filter(
(r) => r.verdictPre === "unsnappable" && r.verdictPost === "unsnappable",
).length;
const maxAbsDelta = routed.reduce((m, r) => Math.max(m, Math.abs(r.deltaPct ?? 0)), 0);
console.log(`D-03 optimality gate — ${results.rows.length} record pairs vs the current binary`);
for (const row of results.rows) {
const delta = row.deltaPct === null ? "Δ —" : `Δ ${row.deltaPct >= 0 ? "+" : ""}${row.deltaPct.toFixed(3)}%`;
const tag =
row.class === "re-snapped"
? "re-snapped INFO"
: row.class === "unsnapped" || row.class === "violation"
? "FAIL"
: "pass";
const note = row.class === "re-snapped" ? " (re-snapped — no pre-ALT seconds to compare)" : "";
console.log(` ${row.pairId}: pre ${formatSeconds(row.pre)} post ${formatSeconds(row.post)} (${delta})${note} ${tag}`);
}
console.log(
`summary: ${results.rows.length} record pairs — ${routed.length} routed equal, ` +
`${unreachPreserved} unreachable preserved, ${unsnapPreserved} unsnappable preserved, ` +
`${results.reSnapped} re-snapped`,
);
for (const check of Object.values(results.checks)) {
console.log(` ${check.id} (${check.label}): ${check.violations.length} violation(s)`);
for (const v of check.violations) {
console.log(` ${v.pairId}: ${v.reason}`);
}
}
console.log(` max |Δ%| over routed pairs: ${maxAbsDelta.toFixed(3)}%`);
console.log(
` epsilon: ±${OPTIMALITY_EPSILON_PCT}% (OPTIMALITY_EPSILON_PCT — mirrored to the record's epsilon block: ${record.epsilon.pct}%)`,
);
if (drift !== null) {
console.log(
` extract drift vs the frozen record: nodes ${drift.nodes >= 0 ? "+" : ""}${drift.nodes}, edges ${drift.edges >= 0 ? "+" : ""}${drift.edges} — the seconds gate still applies`,
);
}
if (results.reSnapped > 0) {
console.log(
`WARNING: ${results.reSnapped} record-unsnappable pair(s) now snap on the current graph (re-snapped INFO rows above) — counted, not a D-03 violation`,
);
}
if (!results.pass) {
const a = results.checks.D03A.violations.length;
const b = results.checks.D03B.violations.length;
console.error(
`GATE FAIL: D-03 optimality — ${a} D03A + ${b} D03B violations across ${results.rows.length} record pairs. ` +
`The record is IMMUTABLE (Pitfall 1): suspect extract drift first (the WARNING counts above), then escalate to the user — never edit or re-record the baseline.`,
);
return false;
}
console.log(
`GATE PASS: D-03 optimality — ${routed.length}/${routed.length} routed pairs equal within ±${OPTIMALITY_EPSILON_PCT}%, verdict classes preserved ` +
`(${results.rows.length}/${results.rows.length} record pairs; ${unreachPreserved} unreachable + ${unsnapPreserved} unsnappable preserved, ${results.reSnapped} re-snapped)`,
);
return true;
}
function main() {
// Honest-refusal structure: EVERY guard below precedes any measurement call.
if (!existsSync(RECORD_PATH)) {
fail(`no baseline record at ${RECORD_PATH} — run measure-optimality-baseline first — the record is the D-03 anchor`);
}
if (!existsSync(GRAPH_PATH)) {
fail(`no graph at ${GRAPH_PATH} — build it first: node routing/scripts/build-road-graph.mjs`);
}
const record = JSON.parse(readFileSync(RECORD_PATH, "utf8"));
if (!record || typeof record !== "object" || !Array.isArray(record.pairs) || record.pairs.length === 0) {
fail(`${RECORD_PATH} has no pairs array — the record schema is the gate's input contract`);
}
if (!record.graph || typeof record.graph.argVersion !== "number") {
fail(`${RECORD_PATH} has no graph block — the record schema is the gate's input contract`);
}
// CONSTANT-MIRROR ASSERT (must precede any measurement): the gate's epsilon
// equals the record's epsilon block, by name and by value — a record edit
// alone cannot fake a pass.
if (record.epsilon?.name !== "OPTIMALITY_EPSILON_PCT" || record.epsilon?.pct !== OPTIMALITY_EPSILON_PCT) {
fail(
`epsilon mirror mismatch: the gate's OPTIMALITY_EPSILON_PCT is ${OPTIMALITY_EPSILON_PCT} but the record's epsilon block says ` +
`${JSON.stringify(record.epsilon ?? null)} — align the constant with the record's provenance (or git-revert the record edit); the record itself is immutable`,
);
}
if (record.graph.argVersion !== REQUIRED_BASELINE_ARG_VERSION) {
fail(
`the baseline must be the pre-ALT record (argVersion ${REQUIRED_BASELINE_ARG_VERSION}) — this record says argVersion ${record.graph.argVersion}`,
);
}
const graph = decodeGraph(toArrayBuffer(readFileSync(GRAPH_PATH)));
if (graph === null) {
fail(
`${GRAPH_PATH} does not decode (fail-honest null — the current binary must be ARG3) — rebuild it: node routing/scripts/build-road-graph.mjs`,
);
}
console.log(
`record: ${record.pairs.length} pairs recorded ${record.recordedAt} ` +
`(ARG${record.graph.argVersion}: ${record.graph.nodeCount} nodes / ${record.graph.edgeCount} edges, epsilon ±${record.epsilon.pct}%)`,
);
console.log(`current: ${graph.nodeCount} nodes / ${graph.edgeCount} edges (decoded from ${GRAPH_PATH})`);
let drift = null;
const nodeDrift = graph.nodeCount - record.graph.nodeCount;
const edgeDrift = graph.edgeCount - record.graph.edgeCount;
if (nodeDrift !== 0 || edgeDrift !== 0) {
drift = { nodes: nodeDrift, edges: edgeDrift };
console.log(
`WARNING: extract drift vs the frozen record — the seconds gate still applies (NEVER a reason to re-record; Pitfall 1)`,
);
}
const results = measureOptimality(graph, record, { snap, astar });
const pass = report(record, results, drift);
process.exit(pass ? 0 : 1);
}
// Run only when invoked directly — importing this module (an in-process
// mirror of the same numbers) must never execute the CLI (route-corpus law).
const invoked = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : "";
if (invoked === import.meta.url) {
main();
}