diff --git a/.planning/phases/05-routing-route-display/05-02-SUMMARY.md b/.planning/phases/05-routing-route-display/05-02-SUMMARY.md new file mode 100644 index 0000000..c4f23c3 --- /dev/null +++ b/.planning/phases/05-routing-route-display/05-02-SUMMARY.md @@ -0,0 +1,156 @@ +--- +phase: 05-routing-route-display +plan: 02 +subsystem: routing +tags: [routing, fixtures, osrm-demo, polyline6, corpus-gate, offline-gate, golden-pairs, tdd, odbl] + +# Dependency graph +requires: + - phase: 05-routing-route-display/01 + provides: the routing workspace — polyline6 codec (decode/encode round-trip law), haversineM, createFixtureRoutingSource/getPairOrigin/getPairs, the committed empty route-fixtures.json seed schema, the 200 m origin-tolerance matching law + - phase: 04-destination-search-kl-gazetteer + provides: the KL gazetteer (search/src/data/kl-gazetteer.json) — the destinationId namespace and the public landmark coordinates the D-12 proof keys off +provides: + - "committed 15-pair fixture dataset (routing/src/data/route-fixtures.json) — real OSRM-demo geometry, 1–3 verbatim polyline6 routes per pair, 8 pairs with ≥ 2 alternates" + - "routing/test/fixtures/golden-pairs.json — the hand-authored pinned corpus (pairIds = the ?fixpair= knob namespace for 05-05; klBbox, 100 KB gz budget, probe id, alternatives floor)" + - "routing/scripts/fetch-route-fixtures.mjs — the maintainer build-time tool (overpass-topup.mjs pattern): D-12 boundary header, pre-flight public-coordinate proof, mandatory UA, 1200 ms delay, collect-then-write, --only= repair merge" + - "routing/scripts/route-corpus.mjs — measurePairs() measurement core + CLI reporter; the RT1–RT10 offline tolerance gate" + - "routing/test/corpus.test.mjs — the in-suite Nyquist gate asserting the same measurements (npm run routing:test)" + - "root npm delegate routing:fixtures; routing workspace fixtures/corpus scripts" +affects: [05-04 web route surface, 05-05 gates (?fixpair= + BA9 budget), phase-6 guidance] + +# Tech tracking +tech-stack: + added: [] # zero new dependencies — node:zlib/node:fs built-ins + the existing workspace codec + patterns: + - "build-time fixture precompute: committed snapshot makes a free best-effort demo server safe (a policy change breaks the regeneration script, never the product)" + - "mechanical privacy proof: RT2b joins the committed dataset to the gazetteer within 1e-4 deg — 'public data between public places' enforced by test, not by promise" + - "one measurement, two reporters: exported measurePairs consumed by both the CLI and the in-suite gate (gazetteer-corpus.mjs precedent)" + - "collect-then-write regeneration: any per-pair hard failure leaves the committed file untouched; --only repairs one pair without churning fourteen" + +key-files: + created: + - routing/test/fixtures/golden-pairs.json + - routing/scripts/fetch-route-fixtures.mjs + - routing/scripts/route-corpus.mjs + - routing/test/corpus.test.mjs + modified: + - routing/src/data/route-fixtures.json (empty seed → 15 real OSRM-demo pairs) + - package.json (routing:fixtures delegate) + - routing/package.json (fixtures + corpus scripts) + - routing/test/loader-env.test.mjs (L3 armed: serves pair 1 / honest null at 300 m) + - routing/test/contract.test.mjs (C6 re-pinned from empty-seed to populated-corpus state) + +key-decisions: + - "C6 re-arm: 05-01's contract test pinned the seed as EMPTY with the message 'plan 05-02's build-time script populates it' — the same documented handoff as L3, re-pinned to the populated state (10..20 pairs) with the ODbL header assertions unchanged" + - "measurePairs takes an OPTIONAL 4th parameter (fixturesPath, default = the committed dataset URL relative to the script) so RT9 gzips the COMMITTED file per the RT9 spec while the plan's 3-arg call shape still works for both reporters" + - "alternatives=2 is best-effort at the demo server: 7 of 15 pairs returned a single route; 8 pairs carry 2 routes — comfortably above the RT10 floor of 4, and alternatives floor data is real, not padded" + - "the fetch script validates the ENTIRE corpus pre-flight (nothing leaves the machine unless every pair is proven public) rather than validating per-pair lazily — the strongest reading of D-12" + +requirements-completed: [RTNG-01, RTNG-02] + +# Metrics +duration: 9min +completed: 2026-10-23 +--- + +# Phase 5 Plan 2: Fixture Corpus — Golden Pairs + OSRM-Demo Precompute + Offline Tolerance Gate Summary + +**The fixture corpus is REAL: 15 pinned KL pairs precomputed from the public OSRM demo server (23 verbatim polyline6 routes, 8 with alternates), pinned behind the RT1–RT10 offline tolerance gate — CLI and in-suite reporters green, the D-12 "public data between public places" law mechanically proven against the gazetteer, 16.6 KB gzipped against the 100 KB early-warning budget.** + +## Performance + +- **Duration:** 9 min +- **Started:** 2026-10-23T21:11:43Z +- **Completed:** 2026-10-23T21:20:27Z +- **Tasks:** 2/2 +- **Files touched:** 9 (4 created, 5 modified) + +## Accomplishments + +- **The data product exists** (D-02): `routing/src/data/route-fixtures.json` carries 15 golden pairs — 5.2 km KL Tower→KL Sentral to 23.1 km Mid Valley→IOI City Mall — each with 1–3 best-first routes stored as VERBATIM polyline6 strings from the OSRM demo (`alternatives=2&overview=full&geometries=polyline6`), ODbL header fields preserved (T-05-06). The 05-01 empty seed is resolved. +- **The maintainer tool follows the repo-tool law** (D-12): `fetch-route-fixtures.mjs` documents the maintainer-machine boundary verbatim in its header, validates the whole corpus against the gazetteer BEFORE any request leaves the machine (origin ≡ landmark within 1e-4 deg, destination known), sends a mandatory descriptive User-Agent, spaces requests 1200 ms apart, retries once on network/5xx, and writes only on total success (`collect-then-write`); `--only=` repairs one pair without churning the rest. +- **The phase's primary acceptance instrument is armed** (amended SC3): `measurePairs()` performs RT1–RT10 fully OFFLINE — corpus size, schema + RT2b public-origin proof, route shape, probe exclusion, haversine↔summary band [0.85, 1.10] + bbox+0.05 deg, speed sanity [1.0, 30] m/s, codec round-trip per route, ODbL header surfaces, gz budget (measured **16,576 B** / 102,400 B), alternatives floor (**8** multi-route pairs / floor 4). The CLI (`npm run routing:corpus`) and the suite (`npm run routing:test`) assert the SAME numbers. +- **05-01's deferred gates armed themselves exactly as designed**: loader-env L3 now proves the default source serves pair 1 at its pinned origin and resolves honest null ~300 m away; F8's positive half runs its assertions now that pairs exist; C6 re-pinned from the empty-seed state to the populated corpus. +- **Suite state:** 35 passed, 0 failed (C1–C6, F1–F8, L1–L3, P1–P8, RT1–RT10); `npm run routing:typecheck` exit 0; zero `fetch(` occurrences in the gate (only the maintainer tool touches the network). + +## Task Commits + +Each task followed RED→GREEN with atomic commits: + +1. **task 1: pinned golden-pairs corpus + fetch script + populated committed dataset** + - `01b0ed6` test(05-02): tighten loader-env L3 to require pair 1 serving at pinned origin (RED) + - `4e89cff` feat(05-02): pinned golden-pair corpus + OSRM-demo fetch tool + populated committed dataset (GREEN) +2. **task 2: the offline corpus tolerance gate (CLI + in-suite Nyquist gate)** + - `63ef408` test(05-02): add failing offline corpus tolerance gate suite RT1-RT10 (RED) + - `6c0880b` feat(05-02): offline corpus tolerance gate — measurePairs core + CLI + in-suite RT1-RT10 (GREEN) + +## Files Created/Modified + +- `routing/test/fixtures/golden-pairs.json` — the 15-pair pinned corpus verbatim from the plan (pairId, originId, pinned origin + label, destinationId), plus klBbox, fixtureGzipBudgetBytes (102400), pendingProbeDestinationId (`national-museum`), alternativesFloorPairs (4) +- `routing/scripts/fetch-route-fixtures.mjs` — the build-time tool: pre-flight D-12 proof, UA `araharah-fixtures/1.0`, 1200 ms delay, one retry, hard-fail diagnostics naming the pair, 2-space JSON + trailing newline, header fields preserved from the committed file +- `routing/src/data/route-fixtures.json` — 15 pairs / 23 routes committed (was `pairs: []`) +- `routing/scripts/route-corpus.mjs` — `measurePairs(corpus, fixtures, gazetteer, fixturesPath?)` + CLI with per-check table, measured gz size, fixed escalation ladder (`--only` repair → regenerate → re-gate); imports decode/encode/haversine from `../src/index.ts`; direct-invocation guard +- `routing/test/corpus.test.mjs` — one `test()` per RT check asserting `violations.length === 0` with named messages; prints gz size + multi-route count so suite output and CLI output agree +- `routing/test/loader-env.test.mjs` — L3 armed (RED commit); header comment updated +- `routing/test/contract.test.mjs` — C6 re-pinned to populated corpus (10..20 pairs), ODbL assertions unchanged +- `package.json` — `"routing:fixtures"` delegate +- `routing/package.json` — `"fixtures"` + `"corpus"` scripts + +## Decisions Made + +- **C6 re-arm** (05-01 documented the handoff on the test itself: "the seed ships EMPTY — plan 05-02's build-time script populates it"): the empty-state pin became a populated-state pin (10..20 pairs); the ODbL header gate (T-05-06) is unchanged. Same category as the plan-ordered L3 update. +- **Optional 4th `fixturesPath` parameter on measurePairs** — RT9's spec gzips the committed file; the default (dataset URL relative to the script) keeps the plan's 3-arg signature valid at both call sites. +- **Whole-corpus pre-flight validation in the fetch script** — nothing is sent until every pair is proven public, the strongest reading of D-12's red line. +- **No padding of alternates** — the demo server returned 1 route for 7 pairs; the floor is met by 8 genuine multi-route pairs. Fabricating extra routes would poison RT5/RT7. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Stale state pin] C6 empty-seed assertion re-armed to the populated corpus** +- **Found during:** task 1 GREEN verification +- **Issue:** 05-01's `contract.test.mjs` C6 asserted `pairs.length === 0` ("the seed ships EMPTY — plan 05-02's build-time script populates it"), which the plan's task-1 acceptance ("05-01's suites pass against REAL data") supersedes; the plan explicitly ordered the analogous L3 update but C6's identical handoff pin needed the same treatment +- **Fix:** re-pinned C6 to assert 10..20 pairs; ODbL header assertions unchanged +- **Files modified:** routing/test/contract.test.mjs +- **Commit:** 4e89cff + +### Judgment calls within plan laws (not deviations) + +- `measurePairs`' optional 4th `fixturesPath` parameter (RT9 spec requires reading the committed file; default keeps the 3-arg shape) +- Fetch script validates the entire corpus pre-flight rather than per-pair lazily (D-12 strongest reading) + +## Known Stubs + +None. The 05-01 known stub (`route-fixtures.json` `pairs: []`) is **resolved** by this plan — the corpus is fully populated and gate-pinned. + +## Threat Surface Scan + +No new surface beyond the plan's ``: the only network touchpoint is the maintainer fetch tool itself (T-05-05b — the declared, documented boundary; the gate is grep-proven offline), the committed dataset ships public data only (RT2b mechanically proven), the gz budget gate gives the T-05-12 early warning (16,576 B / 102,400 B), and the ODbL surfaces are regeneration-proof (RT8). No threat flags. + +## Issues Encountered + +- Node lives under nvm in this environment; non-interactive shells start without it — every command sourced `~/.nvm/nvm.sh` first (05-01 precedent). No project impact. +- OSRM demo stayed reachable throughout; the plan's unreachable-server checkpoint path never triggered. + +## User Setup Required + +None — no external service configuration. The OSRM demo is touched only by the maintainer tool (`npm run routing:fixtures`), never by tests, CI, builds, or the runtime. + +## Next Phase Readiness + +- **05-04 (web route surface)**: imports `createFixtureRoutingSource` from `@araharah/routing/src/index.ts` — the committed corpus now serves 15 real pairs with best-first options, decoded geometry + cumulative distances, honest null for unknown origins (the "router pending" state). +- **05-05 (gates)**: `?fixpair=` values are exactly the 15 committed pairIds (`getPairOrigin` resolves each); BA9's 200 KB all-in budget has 16.6 KB of fixtures against it; the C4 banned-token gate can grow with the corpus script as planned. +- **Phase 6 (guidance)**: route snapshots can embed decoded option geometry with `cumulativeDistanceM` derived at decode — the D-07 fields are live data now, not just types. + +--- +*Phase: 05-routing-route-display* +*Completed: 2026-10-23* + +## Self-Check: PASSED + +- All 4 created files verified present on disk (golden-pairs.json, fetch-route-fixtures.mjs, route-corpus.mjs, corpus.test.mjs). +- All 4 plan commits verified in git log: 01b0ed6, 4e89cff, 63ef408, 6c0880b (RED→GREEN per task). +- `npm run routing:corpus` exit 0 (RT1–RT10 ok, gz 16576 B); `npm run routing:test` 35 passed, 0 failed; `npm run routing:typecheck` exit 0. +- Committed dataset: 15 pairs, ODbL-1.0 header, zero probe-id destinations. +- STATE.md and ROADMAP.md untouched (orchestrator-owned writes); only 05-02-SUMMARY.md added (force-add, .planning/ ignored).