306 lines
20 KiB
Markdown
306 lines
20 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.config/opencode/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.config/opencode/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.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
|
|
</context>
|
|
|
|
<interfaces>
|
|
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 = <ceil(2 * measured CH p95)>; // only if c3 retires
|
|
```
|
|
</interfaces>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>task 1: replace the ALT build stage with measured CH preprocessing</name>
|
|
<files>routing/scripts/build-road-graph.mjs</files>
|
|
<read_first>
|
|
- 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
|
|
</read_first>
|
|
<behavior>
|
|
- 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.
|
|
</behavior>
|
|
<action>
|
|
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: <N>`
|
|
- `shortcuts: <S> (<ratio> per node)`
|
|
- `witness searches: <W> (<H> hit the settle budget)`
|
|
- `chRank bytes: <4*N>`
|
|
- `shortcut bytes: <12*S>`
|
|
- `build wall-clock: <ms> ms`
|
|
- `total binary: <MB> (<bytes> 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 <file>` 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.
|
|
</action>
|
|
<verify>
|
|
<automated>node --check routing/scripts/build-road-graph.mjs && grep -v '^[[:space:]]*\*' routing/scripts/build-road-graph.mjs | grep -v '^[[:space:]]*//' | grep -c 'landmark\|--landmarks' || test $? -eq 1</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `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`.
|
|
</acceptance_criteria>
|
|
<done>The production build emits ARG3 through the one CH module and prints every D-08/D-09 measurement without a payload ceiling.</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>task 2: re-pin the existing gates for ARG3 and preserve the one-path rollout locks</name>
|
|
<files>routing/scripts/measure-optimality-gate.mjs, routing/scripts/measure-astar-oracle-sweep.mjs, routing/scripts/measure-route-cpu.mjs, routing/test/rollout.test.mjs</files>
|
|
<read_first>
|
|
- 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
|
|
</read_first>
|
|
<behavior>
|
|
- 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.
|
|
</behavior>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<verify>
|
|
<automated>npm run routing:test && test "$(grep -c 'P95_BUDGET_MS = 8' routing/scripts/measure-route-cpu.mjs)" -eq 1 && test "$(grep -c 'const calls = code.match(/decodeGraph\(/g)' routing/test/rollout.test.mjs)" -eq 1</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `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.
|
|
</acceptance_criteria>
|
|
<done>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.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>task 3: run the maintainer measurement campaign and re-anchor the drift guard only on a green verdict</name>
|
|
<files>scripts/lib/budget-gates.mjs, scripts/test/budget-audit.test.mjs</files>
|
|
<read_first>
|
|
- 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
|
|
</read_first>
|
|
<behavior>
|
|
- 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.
|
|
</behavior>
|
|
<action>
|
|
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 <number> 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.
|
|
</action>
|
|
<verify>
|
|
<automated>node routing/scripts/measure-optimality-gate.mjs && node routing/scripts/measure-astar-oracle-sweep.mjs && node scripts/test/budget-audit.test.mjs</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `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: <number> 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.
|
|
</acceptance_criteria>
|
|
<done>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.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## 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. |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
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.
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- 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.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-02-SUMMARY.md`
|
|
</output>
|