19 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 08-alt-landmark-preprocessing-routing-backend-v2 | 01 | execute | 1 |
|
true |
|
|
Purpose: every later plan (codec, ALT, campaign) compares against THIS committed record; measuring it after the codec change is impossible.
Output: routing/scripts/measure-optimality-baseline.mjs, committed routing/test/fixtures/alt-baseline.json, routing/test/fixtures/mini-graph-arg1.bin, measure-route-cpu.mjs --extended.
<execution_context> @$HOME/.config/opencode/get-shit-done/workflows/execute-plan.md @$HOME/.config/opencode/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/08-alt-landmark-preprocessing-routing-backend-v2/08-CONTEXT.md @.planning/phases/08-alt-landmark-preprocessing-routing-backend-v2/08-RESEARCH.md @.planning/phases/08-alt-landmark-preprocessing-routing-backend-v2/08-PATTERNS.md @.planning/phases/08-alt-landmark-preprocessing-routing-backend-v2/08-VALIDATION.mdFrom routing/scripts/measure-route-cpu.mjs (lines 50–64, 136–185):
const GRAPH_PATH = resolve(here, "..", "data", "kl-roadgraph.v1.bin");
const P95_BUDGET_MS = 8; // 20% margin under the 10 ms ceiling
const ROUTE_COUNT = 25;
const SAMPLE_SEED = 0x06d4;
// prints per-pair ` ${label}: ${ms.toFixed(3)} ms (${km})` then
// `p95: ${p95.toFixed(3)} ms (max ..., mean ..., n=...)` then `GATE PASS|FAIL: ...`
From scripts/lib/budget-gates.mjs runCpuLadderGate (lines 78–119) — the CLI contract that MUST keep matching:
out = execFileSync(process.execPath, [script], { ... }); // script run with ZERO args
const p95 = Number(out.match(/^p95: (\d+(?:\.\d+)?) ms/m)?.[1] ?? NaN);
const childGateLine = out.match(/^GATE (?:FAIL|PASS): .*$/m)?.[0] ?? null;
(The audit suite node scripts/test/budget-audit.test.mjs — 19/19 vectors — also regex-checks measure-route-cpu.mjs source for the measure-route-cpu and P95_BUDGET_MS = 8 tokens. Keep both names/constant verbatim.)
From routing/src (the pure surfaces the recorder reuses):
export function decodeGraph(buffer: ArrayBuffer): RoadGraph | null; // graph.ts — still ARG1 (v1) during this plan
export function snapToGraph(...); // snap.ts (mjs-wrapper in scripts)
export function routeAstar(graph, originNode, goalNode); // astar.ts — returns engineered seconds + nodes or null
graph.bbox: { minLat, minLng, maxLat, maxLng } — the order-INDEPENDENT pair-generation source (Pitfall 2).
Architecture (copy route-corpus.mjs): exported core + CLI guard (if (invoked === import.meta.url) main();), named constants at top with doc-comments, fail(message) → console.error + exit 1, collect-then-write (record written ONLY after all pairs measured).
Constants (named, no magic):
const GRAPH_PATH = resolve(here, "..", "data", "kl-roadgraph.v1.bin");
const RECORD_PATH = resolve(here, "..", "test", "fixtures", "alt-baseline.json");
const BASELINE_PAIR_COUNT = 100; // D-04: ~100 cross-metro worst-case pairs
const BASELINE_SEED = 0x08a1; // deterministic; the SAME seed --extended reuses
const MIN_PAIR_KM = 12; // straight-line endpoint floor → cross-metro class
Exported core:
export function generateCrossMetroPairs(bbox, { count, minPairKm, seed }) — mulberry32(seed) PRNG (copy the exact mulberry32 from measure-route-cpu.mjs lines 100–108); per candidate pair: draw two independent uniform points inside bbox (lat = minLat + rand()*(maxLat-minLat) etc. via the 1e-6-safe pattern); REJECT candidates whose haversine endpoint distance < minPairKm; label x${String(i).padStart(3,"0")}; emit { pairId, origin:{lat,lng} (rounded to 1e-6 integers /1e6 exact-decimal rule), destination:{lat,lng}, fromHintKm: straight-line km }. Deterministic: same bbox+seed ⇒ same pairs always.
export function measureBaselinePairs(graph, pairs, { snapToGraph, routeAstar, metersToKm }) — for each pair: snap both endpoints (honest snap-miss ⇒ verdict "unsnappable", recorded as seconds: null — a real measurement, never skipped silently); route with the CURRENT routeAstar (still haversine-potential at record time); record seconds as the JSON number routeAstar returns (f64 accumulation, A8-safe) or null for honest null; verdict = "reachable" if a route object returned, else "unreachable". Return per-pair rows.
main(): existsSync(GRAPH_PATH) guard (absent graph ⇒ fail-honest, print the build hint); decodeGraph (null ⇒ fail, "rebuild the binary"); generate + measure; print per-pair one line ( xNNN: <sec>s reachable|unreachable|unsnappable) and a summary (routed count, mean/max seconds); write RECORD only after all rows:
{
"recordedAt": "<ISO-8601>",
"tool": "routing/scripts/measure-optimality-baseline.mjs",
"purpose": "pre-ALT baseline for the D-03 optimality gate (Phase 8); measured BEFORE the ARG2 codec landed (Pitfall 1)",
"epsilon": { "name": "OPTIMALITY_EPSILON_PCT", "pct": 0.1 },
"graph": { "path": "routing/data/kl-roadgraph.v1.bin", "byteLength": <n>, "nodeCount": <n>, "edgeCount": <n>, "argVersion": 1 },
"pairRules": { "seed": "0x08a1", "count": 100, "minPairKm": 12 },
"pairs": [ { "pairId": "x000", "origin": {"lat": 3.12, "lng": 101.6}, "destination": {"lat": 3.19, "lng": 101.73}, "straightLineKm": 0.0, "seconds": 0.0, "verdict": "reachable" } ]
}
The epsilon block is DOCUMENTARY here; the gate script (Plan 08-05) asserts its own constant matches this record's value. Refuse to overwrite an existing record ("baseline already recorded — the record is immutable; delete by explicit git revert only"), fail-honest.
export PATH="$HOME/.nvm/versions/node/v24.18.0/bin:$PATH" && test -s routing/test/fixtures/alt-baseline.json && node -e "const r=require('./routing/test/fixtures/alt-baseline.json'); console.log('pairs:', r.pairs.length, 'verdicts:', JSON.stringify([...new Set(r.pairs.map(p=>p.verdict))]), 'epsilon:', r.epsilon.pct)"
<acceptance_criteria>
- routing/test/fixtures/alt-baseline.json committed; pairs.length === 100; every pair has BOTH endpoints as {lat, lng} objects (grep: "lat" present in the origin/destination fields; zero nodeIndex keys anywhere).
- epsilon.pct === 0.1 and graph.argVersion === 1 in the record.
- The recorder re-run refuses to overwrite (exit 1 with the immutable-record message).
- The CONTINGENCY GUARD: if routing/data/kl-roadgraph.v1.bin is absent OR decodes null (already ARG2 — should not happen in Wave 1), the script exits 1 with the honest build/rebuild message and NO record is written or mutated.
- Summary printed to stdout includes the routed-pair count (print-the-measurement law).
</acceptance_criteria>
The committed record pins 100 coordinate-identified pairs with pre-ALT seconds + verdict classes; reproducible without the old binary.
DEFAULT MODE (no flag): behaviour, stdout, exit codes byte-identical to today. Do not rename ROUTE_COUNT/SAMPLE_SEED/P95_BUDGET_MS; do not reorder prints; do not add any new output line to the default path. The only default-mode-visible change allowed: a comment block update in the header noting the D-04 extended flag exists (keeps the GPL line-citation in scripts/lib/source-gates.mjs from drifting semantically — keep lines 3–9's content intact; if adding header lines shifts them, VERIFY node scripts/test/budget-audit.test.mjs and scripts/test/privacy-audit.test.mjs still pass and adjust nothing else).
Pre-ALT, the --extended run EXPECTS an honest GATE FAIL (that is the c3 evidence); a FAIL exit is a valid measurement in Wave 1 — never paper over it.
export PATH="$HOME/.nvm/versions/node/v24.18.0/bin:$PATH" && node routing/scripts/measure-route-cpu.mjs --extended 2>&1 | grep -E "^p95: .* ms|^ x[0-9]{3}:|GATE (PASS|FAIL):" | head -6; node scripts/test/budget-audit.test.mjs 2>&1 | tail -1
<acceptance_criteria>
- node routing/scripts/measure-route-cpu.mjs --extended prints ≥ 90 xNNN: <ms> ms per-pair lines (snap-miss/unreachable pairs excepted, reported separately), a p95: <n> ms (...) line, and a GATE (PASS|FAIL): line mentioning "D-04 extended verdict" — exit 1 with GATE FAIL is an acceptable pre-ALT measurement.
- node routing/scripts/measure-route-cpu.mjs (no args) still prints the quadrant-sample p95: + GATE lines with unchanged regex shape (^p95: (\d+(?:\.\d+)?) ms).
- node scripts/test/budget-audit.test.mjs → final line BUDGET-AUDIT SUITE: 19/19 vectors ok.
- grep gate: grep -c "generateCrossMetroPairs" routing/scripts/measure-route-cpu.mjs ≥ 1 AND the extended path lives behind --extended (grep -c '"--extended"\|--extended' =≥ 1).
</acceptance_criteria>
D-04's extended verdict exists behind an opt-in flag; the audited zero-arg ladder contract is untouched and its suite is green.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| maintainer CLI → repo artifact | the recorder writes a committed artifact; garbage-in would poison the D-03 gate |
| graph binary → inpool measurement | the ARG1 binary is trusted-local today, but the recorder must fail-honest on absent/corrupt bytes |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-08-01 | Tampering/DoS | measure-optimality-baseline.mjs | mitigate | absent/corrupt/undecodable graph → fail() exit 1 before any measurement; decode null is never retried or papered over |
| T-08-02 | Tampering | routing/test/fixtures/alt-baseline.json | mitigate | record is immutable by construction (refuse-overwrite exit 1); committed — any tamper is visible in git history; the gate mirrors the epsilon constant so a record edit alone cannot fake equality |
| T-08-03 | Information disclosure | baseline/extended print lines | accept | prints carry route seconds + pair ids (x000) and km only — no coordinates beyond the committed record's public KL bbox endpoints, consistent with the category-only log law |
| T-08-04 | DoS | --extended runtime | accept | 100 routes × ~40 ms worst-case ≈ seconds, maintainer-time only; bounded by count/pairRules constants |
| </threat_model> |
<success_criteria>
The pre-ALT baseline is a committed, coordinate-pinned, epsilon-documented JSON artifact produced by a re-runnable tool, measured while old-code+old-binary is still in the tree (Pitfall 1 closed); the D-04 extended verdict exists behind --extended; budget-audit's CLI contract is proven intact by its own suite.
</success_criteria>