--- phase: 08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl plan: 02 type: execute wave: 2 depends_on: - 08.1-01 files_modified: - routing/scripts/build-road-graph.mjs - routing/scripts/measure-route-cpu.mjs - routing/scripts/measure-optimality-gate.mjs - routing/scripts/measure-astar-oracle-sweep.mjs - scripts/lib/budget-gates.mjs - scripts/test/budget-audit.test.mjs - routing/test/rollout.test.mjs autonomous: true requirements: - D-01 - D-02 - D-03 - D-08 - D-09 - D-13 must_haves: truths: - "The production graph build runs CH ordering, contraction, and shortcut encoding and prints measured bytes, shortcut ratio, witness counts, and wall-clock without inventing a fixed payload ceiling." - "The existing optimality gate and 200-pair seeded Dijkstra-oracle sweep run unchanged against the ARG3 binary and report zero optimality violations." - "The local route-CPU gate prints the CH p95 against the unchanged 8 ms product budget and is the only SC-1 closing measurement." - "The drift guard is re-anchored to approximately two times the measured CH p95 only when the c3 class retires, and the constant plus its audit mirror move together." - "The Worker and upload path remain format-agnostic: one decode call, no version branch, no format token in the upload script." artifacts: - path: "routing/scripts/build-road-graph.mjs" provides: "Build-time CH preprocessing stage and measured ARG3 statistics print" - path: "routing/scripts/measure-route-cpu.mjs" provides: "Unchanged local 25-route and extended 100-route CH CPU measurement gates" - path: "scripts/lib/budget-gates.mjs" provides: "CPU verdict gate with D-03 drift-guard constant and CH regime wording" - path: "scripts/test/budget-audit.test.mjs" provides: "CPU3 mirror vector pinned to the CH verdict" key_links: - from: "routing/scripts/build-road-graph.mjs" to: "routing/src/contraction.ts" via: "contractGraph over the compacted directed edges, then encodeGraph ch block" pattern: "contractGraph|encodeGraph" - from: "routing/scripts/measure-route-cpu.mjs" to: "routing/src/astar.ts" via: "routeAstar now delegates to the CH query" pattern: "routeAstar|astar" - from: "scripts/lib/budget-gates.mjs" to: "scripts/test/budget-audit.test.mjs" via: "ROUTE_CPU_DRIFT_GUARD_MS and its CPU3 evidence mirror" pattern: "ROUTE_CPU_DRIFT_GUARD_MS|CPU3" --- Wire the ARG3 binary into the real build and measurement campaign, then close SC-1 from measured local evidence with an honest retire-or-shrink verdict. Purpose: The CH implementation is only useful if the production binary carries the hierarchy and the same evidence class that failed Phase 8 measures whether it succeeded. D-01 forbids retiring the c3 class on failing numbers; D-02 forbids substituting a deployed probe. Output: CH build stage, measured ARG3 build, re-run optimality/oracle/CPU gates, and the tightened or deliberately unchanged drift guard. @$HOME/.config/opencode/get-shit-done/workflows/execute-plan.md @$HOME/.config/opencode/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-CONTEXT.md @.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-RESEARCH.md @.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-PATTERNS.md @.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-VALIDATION.md @routing/scripts/build-road-graph.mjs @routing/scripts/measure-route-cpu.mjs @routing/scripts/measure-optimality-gate.mjs @routing/scripts/measure-astar-oracle-sweep.mjs @scripts/lib/budget-gates.mjs @scripts/test/budget-audit.test.mjs @routing/test/rollout.test.mjs Contracts consumed from Plan 01: ```typescript // routing/src/contraction.ts export function contractGraph(input: ContractionInput): ContractionResult; // ContractionResult.stats: { shortcutCount, ratioPerNode, witnessSearches, witnessBudgetHits } ``` Measurement constants to preserve or re-anchor: ```javascript // routing/scripts/measure-route-cpu.mjs const P95_BUDGET_MS = 8; // MUST remain exactly 8 and appear exactly once const ROUTE_COUNT = 25; const EXTENDED_PAIR_COUNT = 100; // routing/scripts/measure-optimality-gate.mjs const OPTIMALITY_EPSILON_PCT = 0.1; const REQUIRED_BASELINE_ARG_VERSION = 1; // the frozen record is immutable // routing/scripts/measure-astar-oracle-sweep.mjs const SWEEP_PAIR_COUNT = 200; const SWEEP_SEED = 0x08f1; const ORACLE_EPSILON_S = 1e-6; // scripts/lib/budget-gates.mjs export const P95_BUDGET_MS = 8; export const ROUTE_CPU_DRIFT_GUARD_MS = ; // only if c3 retires ``` task 1: replace the ALT build stage with measured CH preprocessing routing/scripts/build-road-graph.mjs - routing/scripts/build-road-graph.mjs - routing/src/contraction.ts - routing/src/graph.ts - routing/test/fixtures/build-mini-graph.mjs - .planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-RESEARCH.md - The build no longer imports landmark.ts and has no --landmarks option. - The build calls contractGraph and encodes the CH block through encodeGraph. - The build prints contracted node count, shortcut count, ratio per node, witness search count, witness budget hits, CH-section bytes, total bytes, and wall-clock. - The existing 1-80 MB envelope gate still fails before any write; no new fixed payload ceiling exists. Remove the `--landmarks` argument parsing, `LANDMARK_COUNT_MIN`, `LANDMARK_COUNT_MAX`, `NODES_PER_LANDMARK`, `LANDMARK_MIN_COVERAGE_FRACTION`, and `LANDMARK_COUNT_OVERRIDE_MAX` constants and their usage. Remove the ALT preprocessing block and replace it with one CH stage. Import `contractGraph` from `../src/contraction.ts`. Project the compacted directed edges into `{ from, to, seconds, flags }` and call `contractGraph({ nodeCount: cLat.length, edges })`. If `contractGraph` throws, fail with a message naming the stage and the thrown reason, then exit 1 before any write. Pass the resulting CH data to `encodeGraph` through the `ch:` input field, never a `landmarks:` field. Measure the build wall-clock with `process.hrtime.bigint()` around the CH stage and the encode. Print a `CH stats:` block containing exactly these labeled measurements: - `contracted nodes: ` - `shortcuts: ( per node)` - `witness searches: ( hit the settle budget)` - `chRank bytes: <4*N>` - `shortcut bytes: <12*S>` - `build wall-clock: ms` - `total binary: ( B)` Do not add an ARG3 byte ceiling. Keep the existing enforced `MIN_BINARY_BYTES` floor and `MAX_BINARY_BYTES` ceiling unchanged. Keep collect-then-write semantics: any gate failure exits 1 with the previous binary untouched. Update `--check ` so it prints CH facts instead of landmark facts: nodeCount, edgeCount, nameCount, maxSpeedKmh, bbox, bytes, chShortcutCount, shortcut count/ratio, and rank length. It must not import landmark.ts. Update the script header's usage text to remove `--landmarks` and describe the CH stage. node --check routing/scripts/build-road-graph.mjs && test "$(grep -v '^[[:space:]]*\*' routing/scripts/build-road-graph.mjs | grep -v '^[[:space:]]*//' | grep -c 'landmark\|--landmarks')" -eq 0 The landmark-absence gate uses `test "$(... grep -c ...)" -eq 0` rather than `grep -c ... || test $? -eq 1`. The `||` form is a no-op gate: `grep -c` exits 0 when it FINDS matches (it prints the count), so `|| test $? -eq 1` never fires and the overall command exits 0 even with every landmark token still present. The `test ... -eq 0` form fails when the count is non-zero — verified: it exits 1 against the current file (39 landmark matches) and exits 0 against a clean file. - `grep -v '^[[:space:]]*\*' routing/scripts/build-road-graph.mjs | grep -v '^[[:space:]]*//' | grep -c 'landmark'` equals 0. - `grep -v '^[[:space:]]*\*' routing/scripts/build-road-graph.mjs | grep -v '^[[:space:]]*//' | grep -c 'contractGraph'` is at least 1. - `grep -v '^[[:space:]]*\*' routing/scripts/build-road-graph.mjs | grep -v '^[[:space:]]*//' | grep -c 'build wall-clock\|witness searches\|shortcuts:'` is at least 1. - `node routing/scripts/build-road-graph.mjs --check routing/test/fixtures/mini-graph.bin` exits 0 and prints `chShortcutCount`. The production build emits ARG3 through the one CH module and prints every D-08/D-09 measurement without a payload ceiling. task 2: re-pin the existing gates for ARG3 and preserve the one-path rollout locks routing/scripts/measure-optimality-gate.mjs, routing/scripts/measure-astar-oracle-sweep.mjs, routing/scripts/measure-route-cpu.mjs, routing/test/rollout.test.mjs - routing/scripts/measure-optimality-gate.mjs - routing/scripts/measure-astar-oracle-sweep.mjs - routing/scripts/measure-route-cpu.mjs - routing/test/rollout.test.mjs - routing/src/astar.ts - .planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-VALIDATION.md - The optimality gate still rejects a baseline whose argVersion is not 1 and still mirrors the record's 0.1 percent epsilon. - The oracle sweep still runs 200 compared pairs with seed 0x08f1 and epsilon 1e-6, classifying SUBOPTIMAL, ORACLE-MISMATCH, and class breaks. - The CPU gate still measures 25 routes plus an optional 100-route extended run against P95_BUDGET_MS 8 and prints p95. - The upload script and Worker remain format-agnostic, and the ARG3 magic is added to their banned-token lists. In `routing/scripts/measure-optimality-gate.mjs`, keep `OPTIMALITY_EPSILON_PCT = 0.1` and `REQUIRED_BASELINE_ARG_VERSION = 1` unchanged. Change only the decode-failure message so it says the current binary must be ARG3 and names the rebuild command `node routing/scripts/build-road-graph.mjs`. Do not edit `routing/test/fixtures/alt-baseline.json`; it is immutable. In `routing/scripts/measure-astar-oracle-sweep.mjs`, keep `SWEEP_PAIR_COUNT = 200`, `SWEEP_SEED = 0x08f1`, `SWEEP_OVERSAMPLE_FACTOR = 3`, `SWEEP_MIN_PAIR_KM = 12`, and `ORACLE_EPSILON_S = 1e-6` unchanged. Update only the graph-missing and decode-failure wording to name ARG3. Keep the symmetric comparison and the three violation classes. In `routing/scripts/measure-route-cpu.mjs`, keep `P95_BUDGET_MS = 8`, `ROUTE_COUNT = 25`, `SAMPLE_SEED = 0x06d4`, `EXTENDED_PAIR_COUNT = 100`, and `EXTENDED_SEED = 0x08a1` unchanged. Update the LADDER HISTORY comment with a CH rung line once the measured CH numbers are available. Do not change the measurement logic or the warm-up law. The `P95_BUDGET_MS = 8` string must remain present exactly once in this file. In `routing/test/rollout.test.mjs`, add the ARG3 magic `0x41524733` to RO1's banned-token list and to RO2's banned-token list. Keep the Worker pinned to exactly one `decodeGraph` call, no `GRAPH_VERSION`, no graph magic, and no `=== 2`. Keep the upload script's banned-token list free of format awareness. npm run routing:test && test "$(grep -c 'P95_BUDGET_MS = 8' routing/scripts/measure-route-cpu.mjs)" -eq 1 && test "$(grep -cF 'const calls = code.match(/decodeGraph\(/g)' routing/test/rollout.test.mjs)" -eq 1 The rollout assertion uses `grep -cF` (fixed-string) on purpose. The BRE form `grep -c 'const calls = code.match(/decodeGraph\(/g)'` is a group-open and exits 2 with `grep: Unmatched ( or \(` — the gate would fail on a correct implementation. `grep -cF` treats the pattern as a literal string and returns 1 on the current file (the one decodeGraph call site in `routing/test/rollout.test.mjs`), so `-eq 1` is a real assertion. - `grep -c 'P95_BUDGET_MS = 8' routing/scripts/measure-route-cpu.mjs` equals 1. - `grep -c 'SWEEP_PAIR_COUNT = 200' routing/scripts/measure-astar-oracle-sweep.mjs` equals 1. - `grep -c 'SWEEP_SEED = 0x08f1' routing/scripts/measure-astar-oracle-sweep.mjs` equals 1. - `grep -c 'ORACLE_EPSILON_S = 1e-6' routing/scripts/measure-astar-oracle-sweep.mjs` equals 1. - `grep -c 'REQUIRED_BASELINE_ARG_VERSION = 1' routing/scripts/measure-optimality-gate.mjs` equals 1. - `grep -c '0x41524733' routing/test/rollout.test.mjs` is at least 2. - `npm run routing:test` exits 0. The D-13 gates remain unchanged in substance, the failure messages honestly name ARG3, and the Worker/upload one-path laws cover the new magic. task 3: run the maintainer measurement campaign and re-anchor the drift guard only on a green verdict scripts/lib/budget-gates.mjs, scripts/test/budget-audit.test.mjs - scripts/lib/budget-gates.mjs - scripts/test/budget-audit.test.mjs - routing/scripts/measure-route-cpu.mjs - routing/scripts/measure-optimality-gate.mjs - routing/scripts/measure-astar-oracle-sweep.mjs - docs/BUDGETS.md - docs/KNOWN-LIMITATIONS.md - The built ARG3 binary decodes and the optimality gate reports zero D03A and zero D03B violations. - The 200-pair oracle sweep reports 0 SUBOPTIMAL, 0 ORACLE-MISMATCH, and 0 class breaks. - The local CPU gate prints the CH p95 for both the 25-route and 100-route extended samples. - If measured CH p95 is below 8 ms, the drift guard becomes ceil(2 * measured p95) and the CPU3 mirror moves in the same commit. - If measured CH p95 is at or above 8 ms, the c3 class remains, the drift guard is not tightened, and the evidence says the class stays. Run the maintainer campaign in this exact order and preserve the printed output in the plan summary: 1. `node routing/scripts/build-road-graph.mjs` 2. `node routing/scripts/measure-optimality-gate.mjs` 3. `node routing/scripts/measure-astar-oracle-sweep.mjs` 4. `node routing/scripts/measure-route-cpu.mjs` 5. `node routing/scripts/measure-route-cpu.mjs --extended` 6. `node scripts/test/budget-audit.test.mjs` The local gate is the sole SC-1 evidence per D-02. Do not add a deployed-Worker CPU probe. Do not change `P95_BUDGET_MS = 8`; do not change `OPTIMALITY_EPSILON_PCT = 0.1`; do not change `ORACLE_EPSILON_S = 1e-6`; do not loosen any gate to manufacture a pass. Apply D-01/D-03 conditionally on the printed numbers: - If the 25-route CH p95 is strictly below 8 ms, the c3 class retires. Set `ROUTE_CPU_DRIFT_GUARD_MS` in `scripts/lib/budget-gates.mjs` to `Math.ceil(2 * measuredChP95Ms)` as a literal integer, and update its provenance comment to name the measured CH p95, the date, and the host. In the same commit, update the CPU3 vector in `scripts/test/budget-audit.test.mjs`: it must assert the CH verdict wording and must no longer require the literal `EXECUTED in Phase 8` phrase. Keep the `p95 ms`, `8 ms`, `10 ms free-plan ceiling`, and `GATE FAIL` assertions when the child still exits 1 for an over-budget regression. - If the 25-route CH p95 is at or above 8 ms, keep `ROUTE_CPU_DRIFT_GUARD_MS` at its current value and keep the c3 class. Rewrite only the budget-gates verdict prose so it names the executed-and-measured CH result and states that the class remains because retirement fires only on a passing measurement. Update the CPU3 mirror regex in the same commit to the new wording. In both branches, the three-branch verdict structure in `runCpuLadderGate` stays: uncharted regression beyond the drift guard, over-budget-within-band, and within-budget. Keep the honest NOT-RUN behavior for a missing binary. node routing/scripts/measure-optimality-gate.mjs && node routing/scripts/measure-astar-oracle-sweep.mjs && node scripts/test/budget-audit.test.mjs - `node routing/scripts/measure-optimality-gate.mjs` prints `GATE PASS` and `0 violation(s)` for both D03A and D03B. - `node routing/scripts/measure-astar-oracle-sweep.mjs` prints `GATE PASS` and `0 SUBOPTIMAL`, `0 ORACLE-MISMATCH`, `0 class-breaks`. - `node routing/scripts/measure-route-cpu.mjs` prints a `p95: ms` line; the number is recorded verbatim in the plan summary. - `grep -c 'P95_BUDGET_MS = 8' scripts/lib/budget-gates.mjs` equals 1. - `node scripts/test/budget-audit.test.mjs` exits 0. - The summary states explicitly whether the c3 class retired and, if it did, the exact new `ROUTE_CPU_DRIFT_GUARD_MS` value; if it did not, the summary states that the class remains. The measured CH verdict exists, the same local evidence class decides SC-1, and the drift guard plus its mirror move together only when the numbers justify retirement. ## Trust Boundaries | Boundary | Description | |----------|-------------| | Overpass response -> build pipeline | External map data enters the graph builder; malformed or hostile data must fail the build before a binary is written. | | Built ARG3 binary -> measurement gates | The gitignored local binary is the artifact under test; a corrupt or stale binary must make gates fail honestly rather than pass. | | Gate output -> budget verdict | Measured p95 and guard constants decide whether the c3 class retires; a mistaken or stale number could retire the class on failing evidence. | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-08.1-10 | Tampering / DoS | Overpass-derived graph entering contraction | mitigate | The existing build validates extracted ways, node ids, finite coordinates, and non-empty graphs before contraction; any `contractGraph` throw fails before collect-then-write, leaving the previous binary untouched. | | T-08.1-11 | Tampering | Stale or corrupt local binary passing a gate | mitigate | `decodeGraph` returns null for non-ARG3 bytes and every measurement script fails on null; the gates never substitute a missing measurement for a pass. | | T-08.1-12 | Repudiation | Retiring c3 on failing numbers | mitigate | D-01 is encoded in the conditional action: the guard is re-anchored only when the printed 25-route p95 is strictly below 8 ms; the measured value and verdict must be quoted in the plan summary and later record surfaces. | | T-08.1-13 | Tampering | Constant-mirror drift between guard and audit vector | mitigate | `ROUTE_CPU_DRIFT_GUARD_MS` and the CPU3 evidence mirror are changed in the same commit; CPU2 still pins `P95_BUDGET_MS = 8` exactly once. | | T-08.1-14 | DoS | Unbounded CH build consuming the maintainer machine | mitigate | Witness searches are bounded by distance and settle count; the build prints wall-clock and budget-hit counts; D-09 treats an over-run as an honest measurement, not a hidden failure. | | T-08.1-15 | DoS | Search CPU regression after the re-anchor | mitigate | The tightened drift guard is approximately 2x the measured CH p95, so a regression that consumes the headroom fails the route-cpu gate; the 8 ms product budget remains the binding pass bar. | Run the full suite and the maintainer campaign: ```bash npm run routing:test && npm test -w bot && npm run routing:typecheck && npm run bot:typecheck && npm run web:typecheck node scripts/test/budget-audit.test.mjs node routing/scripts/build-road-graph.mjs node routing/scripts/measure-optimality-gate.mjs node routing/scripts/measure-astar-oracle-sweep.mjs node routing/scripts/measure-route-cpu.mjs node routing/scripts/measure-route-cpu.mjs --extended ``` Expected: every suite green; optimality and oracle gates green; both CPU p95 values printed and recorded; the drift guard either tightened to ceil(2 x measured p95) with its mirror updated, or deliberately unchanged with the class remaining. - The real build produces ARG3 with measured CH statistics and no invented byte ceiling. - The D-03 optimality gate and 200-pair oracle sweep pass against the ARG3 binary without threshold changes. - The local CPU gate prints the CH p95 for both sample widths. - The c3 class is retired only when the measured p95 is below 8 ms; otherwise it remains with an honest record. - The drift guard and its audit mirror agree, and the Worker/upload path remains format-agnostic. After completion, create `.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-02-SUMMARY.md`