Files

157 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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=<pairId> 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=<pairId>` 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 `<threat_model>`: 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=<pairId>` 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).