From 67026b9483db298d3955c39ab87806951064ea36 Mon Sep 17 00:00:00 2001 From: araharah-executor Date: Mon, 26 Oct 2026 00:54:01 +0800 Subject: [PATCH] docs(08.1): fix plan task ownership and broken verify gates - 08.1-01 task 2: re-pin graph.test.mjs in-memory G-cases (G1/G3/G8/G9/G10/G11) and astar.test.mjs in-memory/ALT cases; suspend only committed-fixture G7 and A1/A2/A4/A5/A6 (the runner auto-discovers all suites and parses no argv, so task 2's verify could never exit 0 otherwise) - 08.1-01 task 3: own un-suspension of G7 and A1/A2/A4/A5/A6 after the ARG3 fixture regeneration - 08.1-01 task 1: drop the cosmetic --test-name-pattern flag (ignored by the hand-rolled runner; full suite runs instead) - 08.1-02 task 2: use grep -cF for the rollout decodeGraph call-site gate (BRE form exited 2 with 'Unmatched ( or \\(') - 08.1-02 task 1: replace the no-op '|| test $? -eq 1' landmark gate with a 'test "$(...)" -eq 0' count assertion that can actually fail --- .../08.1-01-PLAN.md | 71 ++++++++++++++++--- .../08.1-02-PLAN.md | 10 ++- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-01-PLAN.md b/.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-01-PLAN.md index da38b57..5b7efbf 100644 --- a/.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-01-PLAN.md +++ b/.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-01-PLAN.md @@ -222,7 +222,7 @@ sections: Determinism is mandatory: no PRNG, no `Date`, no Map iteration order, no reliance on object key order. Identical input arrays must yield identical output arrays. Do not use any external CH library and do not add a dependency. - npm run routing:test -- --test-name-pattern='C[1-7]' && npm run routing:typecheck + npm run routing:test && npm run routing:typecheck - `routing/src/contraction.ts` contains `export function contractGraph` and all four `CH_*` constants with the exact values 1, 60, 4, and 4. @@ -231,12 +231,15 @@ sections: - `npm run routing:typecheck` exits 0. - `grep -v '^[[:space:]]*\*' routing/src/contraction.ts | grep -v '^[[:space:]]*//' | grep -c 'Date\|Math.random'` equals 0. + + The routing runner (`routing/test/runner.mjs`) auto-discovers every `*.test.mjs` in `routing/test/` and parses NO argv — a `--test-name-pattern` flag is silently dropped and the FULL suite runs. This task's verify therefore runs the whole routing suite (82+ cases) rather than only the C-series. That is strictly stronger than a filtered run, and it must be green in full: at this point in the plan the only suites affected are the pre-existing ones plus the new `contraction.test.mjs`. + CH construction is deterministic, rank is a strict total order, shortcut weights and upwardness are proven, the overlay-only witness law is pinned, and the explosion tripwire fails loud. task 2: implement ARG3 codec persistence and the uphill bidirectional CH query - routing/src/graph.ts, routing/src/ch-query.ts, routing/src/astar.ts, routing/src/index.ts, routing/src/landmark.ts, routing/test/ch-query.test.mjs, routing/test/graph.test.mjs, routing/test/astar.test.mjs, routing/test/landmark.test.mjs + routing/src/graph.ts, routing/src/ch-query.ts, routing/src/astar.ts, routing/src/index.ts, routing/src/landmark.ts, routing/test/ch-query.test.mjs, routing/test/astar.test.mjs, routing/test/landmark.test.mjs, routing/test/graph.test.mjs - routing/src/graph.ts - routing/src/astar.ts @@ -253,10 +256,10 @@ sections: - Case Q3: the returned seconds equal a fresh sum over the returned original-edge path and never equal a sum of shortcut weights. - Case Q4: an unreachable pair returns null. - Case Q5: comment-stripped `astar.ts` contains none of `landmarkTo`, `landmarkFrom`, `cleanColumnsFor`, `LANDMARK_SENTINEL`; `routing/src/landmark.ts` and `routing/test/landmark.test.mjs` are absent; the index exports no landmark symbols. - - Case G1: ARG3 round-trips rank and shortcut CSR exactly through zero-copy views. - - Case G2: ARG1 bytes and ARG2 bytes both decode to null; a version-2 tamper decodes to null. - - Case G3: truncated, hostile-count, and `chShortcutCount = 0` buffers decode to null before any typed-array allocation. - - Case G4: the committed ARG3 mini-graph has 25 nodes, 80 edges, a rank of length 25, and at least one shortcut. + - Case G1: an in-memory ARG3 buffer round-trips rank and shortcut CSR exactly through zero-copy views. + - Case G2: in-memory ARG1 bytes and ARG2 bytes both decode to null; a version-2 tamper decodes to null. + - Case G3: in-memory truncated, hostile-count, and `chShortcutCount = 0` buffers decode to null before any typed-array allocation. + - The committed-fixture decode pin (G7) is SUSPENDED in this task and re-pinned in task 3, after the fixture is regenerated through the ARG3 encode path. G7 is the ONLY case in `graph.test.mjs` that reads a committed fixture; every other G-case is in-memory and is re-pinned HERE. Replace the ARG2 codec in `routing/src/graph.ts` with the ARG3 codec described in the interfaces block. Use `GRAPH_MAGIC = 0x41524733` and `GRAPH_VERSION = 3`. Keep `HEADER_SIZE = 72` so f64 views remain 8-byte aligned. Replace landmark header fields with `chShortcutCount` u32 @60 and reserved u32 @64/@68. Remove `landmarkCount`, `landmarkQuantumS`, `landmarkTo`, `landmarkFrom`, and `landmarkNodes` from `RoadGraph`, `LandmarkTablesInput`, and `GraphInput`; replace them with `ChTablesInput` and `GraphInput.ch`. @@ -271,7 +274,32 @@ sections: Update `routing/src/index.ts` to export the existing public API and additively export `contractGraph`, `ContractionInput`, `ContractionResult`, and the four `CH_*` constants if the query implementation needs them. Export no landmark symbols. - Delete `routing/src/landmark.ts` and `routing/test/landmark.test.mjs` per D-05. Re-pin `routing/test/graph.test.mjs` for ARG3 and re-point `routing/test/astar.test.mjs` A1-A8 at the CH-backed `routeAstar`; remove or fold the ALT-specific A9-A13 cases into the CH query suite rather than retaining landmark fixtures. + Delete `routing/src/landmark.ts` and `routing/test/landmark.test.mjs` per D-05. Add the ARG3 codec cases G1-G3 to `routing/test/ch-query.test.mjs` using in-memory encode/decode vectors only; do not read or assert the committed mini-graph in this task, because that fixture is still ARG2 until task 3 regenerates it. + + RE-PIN `routing/test/graph.test.mjs` IN THIS TASK. The codec flips to ARG3 here, so every in-memory G-case must be re-pinned here or the suite cannot go green. The routing runner auto-discovers ALL suites and parses NO argv, so `npm run routing:test` runs the whole `routing/test/` directory — there is no way to exclude `graph.test.mjs` from this task's verify. + + `routing/test/graph.test.mjs` re-pinning, case by case: + - `syntheticInput()` (currently around line 74) — replace the `landmarks: {...}` block with a `ch:` block built from `contractGraph` over the same 3 edges, or with a hand-built CH block (rank of length 3, `shortcutStart` of length 4, matching target/seconds/via arrays). The fixture must produce a graph with at least one shortcut so the round-trip is meaningful. + - G1 (around line 100) — assert the CH fields round-trip: `chRank` length equals nodeCount, `shortcutStart` length equals nodeCount+1, `shortcutTarget`/`shortcutSeconds`/`shortcutVia` lengths equal `chShortcutCount`, and the values match the input exactly. Keep the existing coord/target/flag/weight/name/bbox assertions unchanged. + - G2 (around line 126) — unchanged: tampered magic still decodes to null. + - G3 (around line 134) — keep the ARG1 fixture rejection. The version tamper must change: version 1 still decodes to null, but the "version 3 tamper" case must become a version 4 tamper (the new `GRAPH_VERSION` is 3, so a buffer stamped 4 must be refused). A version-2 tamper may be added as well. + - G4 (around line 150) — keep the truncated/empty/sub-header/hostile-count assertions. The hostile header must stamp `GRAPH_MAGIC` and the NEW `GRAPH_VERSION` (3). + - G5 (around line 171) — unchanged: zero-copy views over the input buffer. Add `chRank.buffer === buf` as a CH zero-copy assertion. + - G6 (around line 185) — unchanged: name pool behavior. + - G7 (around line 195) — SUSPEND in this task. This is the ONLY case that reads the committed `routing/test/fixtures/mini-graph.bin`, which is still ARG2 until task 3 regenerates it. Replace its body with a `test.skip`-style no-op that documents the deferral (for example a case named `G7 committed ARG3 fixture — SUSPENDED until task 3 regenerates mini-graph.bin` whose body asserts `true`). Do NOT delete the case and do NOT change the committed fixture in this task. Task 3 re-pins G7 in place. + - G8 (around line 237) — re-pin the constants: `GRAPH_MAGIC === 0x41524733` ('ARG3') and `GRAPH_VERSION === 3`. `UNNAMED_NAME_ID` stays `0xffffffff`. + - G9 (around line 244) — re-pin the table round-trip to the CH tables: assert `chRank` restores exactly, `shortcutStart` restores exactly, `shortcutTarget`/`shortcutSeconds`/`shortcutVia` restore exactly, `chShortcutCount` matches, and the CH arrays are zero-copy views over the input buffer. + - G10 (around line 260) — re-pin the encode-validation cases to CH inputs: a missing `ch` block, a `chRank` whose length differs from nodeCount, a `shortcutStart` whose length differs from nodeCount+1, a `shortcutTarget` whose length differs from `chShortcutCount`, a `shortcutVia` entry outside `[0, nodeCount)`, a non-finite `shortcutSeconds` value, and a `chShortcutCount` that disagrees with the arrays. Each must throw an `Error` whose message starts with `encodeGraph: `. + - G11 (around line 280) — re-pin the decode rejection: `chShortcutCount = 0` in the header decodes to null even when the byte size matches (byte 60 is now `chShortcutCount`, not `landmarkCount`), and a monster `chShortcutCount` on a tiny buffer decodes to null by arithmetic before any allocation. Recompute the "exactly-sized" buffer arithmetic for the ARG3 layout (the L=0 landmark layout no longer applies — the equivalent is a buffer whose byte length matches an ARG3 layout with `chShortcutCount = 0`). + - The file's header comment block (around lines 1-51) must be rewritten to describe the ARG3 layout (72-byte header, `chShortcutCount` u32 @60, reserved u32 @64/@68, the CH suffix of `chRank` ++ `shortcutStart` ++ `shortcutTarget` ++ `shortcutSeconds` ++ `shortcutVia`) and the G1-G11 case list. Remove every reference to the landmark tables and to `0x41524732`. + + RE-PIN `routing/test/astar.test.mjs` IN THIS TASK. Its cases split the same way: + - `loadMiniGraph()` (around line 92) reads the committed `routing/test/fixtures/mini-graph.bin`, which is still ARG2 in this task. The cases that use it are A1 (around line 187), A2 (around line 216), A4 (around line 274), A5 (around line 309), and A6 (around line 328). SUSPEND those five cases in this task with a documented `test.skip`-style no-op (the fixture is still ARG2 until task 3 regenerates it). Do NOT delete them and do NOT change the committed fixture in this task — task 3 re-pins them in place. + - A3 (around line 239) is in-memory: replace its `landmarks: {...}` block with a `ch:` block and keep the unreachable-pair law. + - A7 (around line 356) and A8 (around line 378) are structural: keep them, but extend A8 to assert the CH path (no `new Map(` in `astar.ts`/`ch-query.ts`). + - A9 (around line 478), A10 (around line 535), A12 (around line 401), and A13 (around line 612) are ALT-specific: per D-04/D-05 they are folded into the CH query suite (`routing/test/ch-query.test.mjs`) rather than retained. Their island fixture (around line 134) imports `../src/landmark.ts` and builds a `landmarks:` block — rewrite it to build a `ch:` block through `contractGraph`, and re-point the surviving assertions at the CH query. A11 (around line 561) asserts the ALT clean-column law in `astar.ts` source: replace it with the CH-law equivalent (the uphill rank filter exists in `ch-query.ts`, and `astar.ts` contains no landmark token). + + The committed-fixture ARG3 decode pin (the G4 case added in task 3) and the un-suspension of G7 / A1 / A2 / A4 / A5 / A6 all belong to task 3, after the fixture is regenerated through the ARG3 encode path and the ARG2 vector is preserved. npm run routing:test && npm run routing:typecheck @@ -281,19 +309,25 @@ sections: - `routing/src/ch-query.ts` exists and contains the upward-rank filter, the `peekKey()` stop rule, and shortcut unpacking through `via`. - `routing/src/astar.ts` comment-stripped code contains none of `landmarkTo`, `landmarkFrom`, `cleanColumnsFor`, `LANDMARK_SENTINEL`. - `routing/src/landmark.ts` and `routing/test/landmark.test.mjs` do not exist. + - `routing/test/graph.test.mjs` contains no `0x41524732`, no `landmarkCount`, and no `landmarkTo`/`landmarkFrom`/`landmarkQuantumS`; its G7 case is a documented suspended no-op. + - `routing/test/astar.test.mjs` contains no import of `../src/landmark.ts` and no `landmarks:` block; its A1/A2/A4/A5/A6 cases are documented suspended no-ops. - `npm run routing:test` exits 0 and `npm run routing:typecheck` exits 0. - `grep -v '^[[:space:]]*\*' routing/src/astar.ts | grep -v '^[[:space:]]*//' | grep -c 'landmarkTo\|landmarkFrom\|cleanColumnsFor\|LANDMARK_SENTINEL'` equals 0. + + The routing runner auto-discovers every `*.test.mjs` in `routing/test/` and parses NO argv — `npm run routing:test` is the WHOLE routing suite, and `graph.test.mjs` / `astar.test.mjs` cannot be excluded from it. That is why their in-memory cases are re-pinned in THIS task and only their committed-fixture-dependent cases are suspended (G7; A1/A2/A4/A5/A6) until task 3 regenerates `mini-graph.bin` as ARG3. + ARG3 is the sole binary format, the CH uphill query is the sole search path, ALT code and tables are gone, and reported seconds come from the unpacked original-edge path. task 3: rebuild all fixtures and bot graph builders through the ARG3 path - routing/test/fixtures/build-mini-graph.mjs, routing/test/fixtures/mini-graph.bin, routing/test/fixtures/mini-graph-arg2.bin, bot/test/webhook.test.mjs, bot/test/guidance-cron.test.mjs + routing/test/fixtures/build-mini-graph.mjs, routing/test/fixtures/mini-graph.bin, routing/test/fixtures/mini-graph-arg2.bin, routing/test/graph.test.mjs, routing/test/astar.test.mjs, bot/test/webhook.test.mjs, bot/test/guidance-cron.test.mjs - routing/test/fixtures/build-mini-graph.mjs - routing/test/fixtures/mini-graph.bin - routing/test/graph.test.mjs + - routing/test/astar.test.mjs - bot/test/webhook.test.mjs - bot/test/guidance-cron.test.mjs - routing/src/contraction.ts @@ -302,7 +336,10 @@ sections: - The committed mini-graph fixture decodes as ARG3 with 25 nodes, 80 edges, and a valid CH section. + - Case G4: the committed ARG3 mini-graph has 25 nodes, 80 edges, a rank of length 25, and at least one shortcut. - The preserved ARG2 fixture decodes to null. + - The G7 case in `routing/test/graph.test.mjs` is un-suspended and asserts the committed ARG3 mini-graph. + - The A1/A2/A4/A5/A6 cases in `routing/test/astar.test.mjs` are un-suspended and run green against the committed ARG3 mini-graph. - Every bot test graph encodes and decodes through the ARG3 `ch` block and produces the same route behavior as before. @@ -314,7 +351,11 @@ sections: Update `bot/test/guidance-cron.test.mjs`: replace the pinned-anchor 2-node graph builder with a `contractGraph` call and encode with `ch:`. The 2-node graph must contract successfully; no anchor pin is allowed. - Run `node routing/test/fixtures/build-mini-graph.mjs` to regenerate `mini-graph.bin` as ARG3 and confirm the printed node/edge/shortcut/byte counts. + Run `node routing/test/fixtures/build-mini-graph.mjs` to regenerate `mini-graph.bin` as ARG3 and confirm the printed node/edge/shortcut/byte counts. After regeneration succeeds and the ARG2 rejection vector is preserved, re-pin `routing/test/graph.test.mjs` for ARG3 and add the committed-fixture G4 case: it must read `routing/test/fixtures/mini-graph.bin`, assert ARG3 magic/version, 25 nodes, 80 edges, rank length 25, and at least one shortcut, and assert that `routing/test/fixtures/mini-graph-arg2.bin` decodes to null. Keep this graph.test.mjs G4 pin in this task so fixture regeneration, ARG2 preservation, and the committed-fixture assertion are owned atomically. + + UN-SUSPEND the committed-fixture cases that task 2 parked, now that `mini-graph.bin` is ARG3: + - `routing/test/graph.test.mjs` G7 — replace the suspended no-op with the real case: read `routing/test/fixtures/mini-graph.bin`, assert it decodes non-null, assert 25 nodes, 80 edges, 3 names, maxSpeed 60, exactly ONE oneway flag, the hand-built grid bbox, both speed classes present, `chRank` length 25, and at least one shortcut. Delete the ARG2 landmark assertions (landmarkCount, landmarkQuantumS, landmarkTo/landmarkFrom lengths, the anchor-cell loop). Also assert that `routing/test/fixtures/mini-graph-arg2.bin` decodes to null. + - `routing/test/astar.test.mjs` A1, A2, A4, A5, A6 — replace each suspended no-op with its real body. `loadMiniGraph()` now decodes an ARG3 graph; A1's 625-pair exhaustive Dijkstra equivalence, A2's oneway law, A4's `deriveSteps` L-path, A5's depart/arrange law, and A6's `classifyTurn` pins must all be restored and green. If any assertion depended on an ALT-only field, replace it with the CH equivalent (for example `chRank`/`chShortcutCount` instead of `landmarkCount`). node routing/test/fixtures/build-mini-graph.mjs && npm run routing:test && npm test -w bot && npm run bot:typecheck @@ -322,6 +363,9 @@ sections: - `routing/test/fixtures/mini-graph-arg2.bin` exists and `node -e "const fs=require('fs');const b=fs.readFileSync('routing/test/fixtures/mini-graph-arg2.bin');console.log(b.readUInt32LE(4))"` prints `2`. - `node -e "const fs=require('fs');const b=fs.readFileSync('routing/test/fixtures/mini-graph.bin');console.log(b.readUInt32LE(0).toString(16),b.readUInt32LE(4))"` prints `41524733 3`. + - `routing/test/graph.test.mjs` contains a G4 case that reads the committed ARG3 mini-graph, asserts 25 nodes, 80 edges, rank length 25, at least one shortcut, and asserts the preserved ARG2 fixture decodes to null. + - `routing/test/graph.test.mjs` contains no suspended/skipped case: grep for `skip`/`SUSPENDED` in that file returns 0 matches, and G7 asserts the committed ARG3 mini-graph. + - `routing/test/astar.test.mjs` contains no suspended/skipped case: grep for `skip`/`SUSPENDED` in that file returns 0 matches, and A1/A2/A4/A5/A6 run against the committed ARG3 mini-graph. - `grep -v '^[[:space:]]*\*' bot/test/webhook.test.mjs | grep -v '^[[:space:]]*//' | grep -c 'landmark'` equals 0. - `grep -v '^[[:space:]]*\*' bot/test/guidance-cron.test.mjs | grep -v '^[[:space:]]*//' | grep -c 'landmark'` equals 0. - `npm run routing:test`, `npm test -w bot`, and `npm run bot:typecheck` exit 0. @@ -370,7 +414,14 @@ grep -v '^[[:space:]]*\*' routing/src/astar.ts | grep -v '^[[:space:]]*//' | gre grep -c 'decodeGraph(' bot/src/routing/worker-router.ts ``` -Expected: routing and bot suites green; all typechecks green; structural ALT count 0; Worker decodeGraph count 1. +The committed-fixture cases suspended in task 2 (G7; A1/A2/A4/A5/A6) must be un-suspended by task 3 — no case in the plan may be left skipped at the end: + +```bash +grep -c 'SUSPENDED\|skip' routing/test/graph.test.mjs +grep -c 'SUSPENDED\|skip' routing/test/astar.test.mjs +``` + +Expected: routing and bot suites green; all typechecks green; structural ALT count 0; Worker decodeGraph count 1; both suspension counts 0. diff --git a/.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-02-PLAN.md b/.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-02-PLAN.md index 90440bc..b478610 100644 --- a/.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-02-PLAN.md +++ b/.planning/phases/08.1-contraction-hierarchies-routing-backend-v3-the-lever-that-cl/08.1-02-PLAN.md @@ -150,8 +150,11 @@ export const ROUTE_CPU_DRIFT_GUARD_MS = ; // only if Update the script header's usage text to remove `--landmarks` and describe the CH stage. - 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 + 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. @@ -188,8 +191,11 @@ export const ROUTE_CPU_DRIFT_GUARD_MS = ; // only if 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 -c 'const calls = code.match(/decodeGraph\(/g)' routing/test/rollout.test.mjs)" -eq 1 + 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.