--- phase: 05-routing-route-display plan: 01 subsystem: routing tags: [routing, fixture-backend, polyline6, encoded-polyline, routing-source, npm-workspaces, tdd, osrm] # Dependency graph requires: - phase: 04-destination-search-kl-gazetteer provides: the search/ workspace pattern (RoutingSource mirrors SearchSource: interface + backend ladder + lazy loader + hand-rolled runners) and the gazetteer destinationId namespace the route requests carry provides: - "@araharah/routing — the fifth buildless npm workspace (pure .ts + .json, zero runtime deps)" - "RoutingSource interface + full route model with D-07 day-one fields (RouteStep typed, RouteGeometry.cumulativeDistanceM)" - "polyline6 codec (decode/encode, 1e-6 grid, round-trip law, truncated-input refusal)" - "geo math: haversineM + deriveCumulativeDistanceM" - "createFixtureRoutingSource — backend #1 with the 200 m origin-tolerance matching law and honest-null pending semantics" - "committed EMPTY route-fixtures.json seed with ODbL header (schema + license surfaces ship now)" - "getPairOrigin / getPairs introspection for the corpus runner (05-02) and ?fixpair= harness knob (05-05)" - "three-environment loader proof (bare Node 24, esbuild, Vite-family) — L1–L3" affects: [05-02 fixtures corpus, 05-04 web route surface, 05-05 gates, phase-6 guidance] # Tech tracking tech-stack: added: [] # zero new dependencies — buildless TS consumed via workspace paths (search/ precedent) patterns: - "RoutingSource seam with documented backend ladder (fixture → wasm-router → valhalla), backend selected in code at the import site" - "polyline6 verbatim storage + pure codec with round-trip law as the corpus gate's free assertion" - "lazy memoized index at factory-closure scope; module scope holds only the static JSON import binding" - "defensive pair validation at index build (malformed → skipped → honest null, never a throw)" key-files: created: - routing/package.json - routing/tsconfig.json - routing/src/types.ts - routing/src/polyline.ts - routing/src/geo.ts - routing/src/fixture-source.ts - routing/src/index.ts - routing/src/data/route-fixtures.json - routing/test/runner.mjs - routing/test/harness.mjs - routing/test/contract.test.mjs - routing/test/polyline.test.mjs - routing/test/fixture-source.test.mjs - routing/test/loader-env.test.mjs modified: - package.json (workspaces + routing:test/typecheck/corpus delegates) - package-lock.json (routing workspace registered) key-decisions: - "polyline.ts is zero-import: it declares a structural PolyPoint instead of importing RoutePoint because the plan's acceptance gate greps polyline.ts for zero `from \"\"` occurrences; structural identity keeps full type compatibility" - "F8's positive half (getPairOrigin resolving a known pair) ships as a visible SKIP while the committed seed is empty — the lookup machinery is proven by F2's injected dataset; the positive half arms automatically when 05-02 populates the corpus (loader-env L2/L3 precedent)" - "RouteFixturesFile added to types.ts (beyond the plan's listed ten interfaces) because createFixtureRoutingSource(data?) needs a public parameter type; index.ts re-exports kept exactly per plan" patterns-established: - "RoutingSource ladder: interface header documents backends #2/#3 without shipping them (the SearchSource analog, D-04)" - "Null contract as contract documentation: 'router pending' grep-pinned by C3 so the semantics cannot silently drift" - "Three zero-laws gate (C4): comments stripped, then banned tokens (logging/storage/network) grepped across routing/src — grows with 05-02's script" requirements-completed: [RTNG-01] # Metrics duration: 14min completed: 2026-10-23 --- # Phase 5 Plan 1: Routing Workspace — RoutingSource Seam + Fixture Backend + polyline6 Codec Summary **`@araharah/routing` — the fifth buildless workspace landing the D-01 `RoutingSource` seam (fixture → wasm-router → valhalla ladder), the polyline6 codec proven against live OSRM geometry, 200 m-tolerance fixture matching with honest-null pending semantics, and a committed ODbL-headed empty fixture seed — 25/25 tests green in three environments.** ## Performance - **Duration:** 14 min - **Started:** 2026-10-23T20:50:09Z - **Completed:** 2026-10-23T21:04:00Z - **Tasks:** 3/3 - **Files modified:** 16 (14 created, 2 modified) ## Accomplishments - **The routing seam exists** (D-01): `RoutingSource` + the full route model (`RoutePoint` → `RouteResult`) with the D-07 day-one fields typed and grep-pinned by tests (`cumulativeDistanceM`, `steps: RouteStep[]`, the GUID-05/06 obligation note) — plans 05-02/05-04/05-05 and Phase 6 import these exact names. - **Backend #1 works end-to-end** (D-02/D-03): fixture matching law (destinationId equality + 200 m haversine origin tolerance), best-first option order, `steps: []` always (T-05-07), defensive malformed-pair skip, lazy memoized index (LAW 1), static JSON import (LAW 2), zero logging/storage/network (LAW 3). - **The polyline6 codec is proven against external ground truth** (RESEARCH Finding 1): 8 vector cases including the live OSRM demo geometry (decode byte-exact + full-string round-trip) and the truncated-varint refusal law (T-05-02). - **Three-environment loader proof** (Phase 4's top mechanical risk pattern): bare Node 24 static JSON import (L1), esbuild bundle inline marker `router.project-osrm.org` (L2), default source serving honest null from the committed seed (L3). - **Zero-dependency posture verified mechanically**: no `dependencies` field (C5), zero banned tokens in `routing/src` with comments stripped (C4), zero imports in the codec. ## Task Commits Each task followed RED→GREEN with atomic commits: 1. **task 1: workspace scaffold + type contracts + day-one fields** - `69b6a20` test(05-01): scaffold routing workspace with failing contract suite (RED) - `c92a955` feat(05-01): RoutingSource contract + day-one route model + fixture seed schema (GREEN) 2. **task 2: polyline6 codec (pure, zero-dependency)** - `9b66f90` test(05-01): add failing polyline6 codec vectors (P1-P8) (RED) - `4acb878` feat(05-01): polyline6 codec (pure, zero-dependency) (GREEN) 3. **task 3: geo math + fixture backend + lazy loader + public API + three-environment proof** - `0599ffe` test(05-01): add failing fixture-source + loader-env suites (F1-F8, L1-L3) (RED) - `3e0c5e3` feat(05-01): geo math + fixture RoutingSource backend + public API (GREEN) ## Files Created/Modified - `routing/src/types.ts` — the seam + route model; D-01 ladder and D-03 null contract in the header; D-07 day-one fields documented with the GUID-05/06 obligation verbatim; `RouteFixturesFile` dataset envelope - `routing/src/polyline.ts` — polyline6 codec; pure (zero imports), double-math varints (no int32 wrap), throws on truncated input - `routing/src/geo.ts` — `haversineM` (R = 6 371 000 m) + `deriveCumulativeDistanceM` ([0] === 0, non-decreasing) - `routing/src/fixture-source.ts` — backend #1: `ORIGIN_TOLERANCE_M = 200`, `createFixtureRoutingSource(data?)`, `getPairOrigin`, `getPairs`; slug guard first; lazy memoized index; defensive pair skip - `routing/src/index.ts` — named-export public surface; subpath-with-extension import law documented (bare `@araharah/routing` resolves to NOTHING) - `routing/src/data/route-fixtures.json` — committed EMPTY seed (`pairs: []`) with the ODbL `_license`/`_attribution`/`_notice` header and the `router.project-osrm.org` `_source` marker (T-05-06) - `routing/test/{runner,harness}.mjs` — zero-dependency runner, copied verbatim from search/test - `routing/test/contract.test.mjs` — C1–C6: seam shape, day-one field grep, null-contract doc grep, three zero-laws gate, zero-deps gate, ODbL header gate - `routing/test/polyline.test.mjs` — P1–P8: hand-derived + external OSRM vectors, round-trip law, malformed refusal - `routing/test/fixture-source.test.mjs` — F1–F8: matching law, tolerance boundary, slug guard, honest null, malformed skip, lazy-index source gate, getPairOrigin - `routing/test/loader-env.test.mjs` — L1–L3: bare Node, esbuild inline marker, default-source honest null - `routing/package.json` + `routing/tsconfig.json` — manifest + platform-neutral tsconfig (search/ shapes) - `package.json` (root) — `workspaces` + `routing:test`/`routing:typecheck`/`routing:corpus` delegates (corpus target lands with 05-02's script) - `package-lock.json` — routing workspace registered ## Decisions Made - **polyline.ts declares a structural `PolyPoint` instead of importing `RoutePoint`** — the plan's acceptance gate requires zero `from "` occurrences in the codec file, and a `import type` line would violate it; structural identity is fully type-compatible at every consumer. - **F8's positive half is a visible SKIP** while the committed seed is empty (its lookup target only exists after 05-02 populates the corpus). The negative/garbage halves are fully asserted now; the positive half arms automatically once pairs land. This mirrors the loader-env L2/L3 conditional-skip precedent. - **`RouteFixturesFile` exported from types.ts** (an 11th interface beyond the plan's listed ten) — `createFixtureRoutingSource(data?)` needs a public parameter type for tests and 05-02's corpus runner. `index.ts` re-exports kept exactly per plan. ## Deviations from Plan ### Auto-fixed Issues None — no bugs, no blocking issues. The three items above are judgment calls within the plan's stated laws, documented for the verifier. ## Known Stubs - `routing/src/data/route-fixtures.json` `pairs: []` — **intentional per plan** ("plan 05-02 populates it via the build-time script"). Until then the committed corpus serves honest nulls (F1/L3 pin this). Resolved by plan 05-02; no UI surface consumes fixtures before 05-04. ## Threat Surface Scan No new surface beyond the plan's ``: no network endpoints, no auth paths, no storage, no schema at trust boundaries. All six register entries (T-05-01/02/03/04/06/07) have implemented mitigations covered by gates C4, P7, F4, the in-code backend selection, C6, and F2 respectively. No threat flags. ## Issues Encountered - Node lives under nvm in this environment and non-interactive shells start without it — every verification command sourced `~/.nvm/nvm.sh` first. No project impact. ## User Setup Required None — no external service configuration required. (The OSRM demo server is touched only by 05-02's maintainer script, not by this plan.) ## Next Phase Readiness - **05-02 (fixtures corpus)**: imports `getPairs`/`getPairOrigin`/codec round-trip for the corpus gate; root delegate `routing:corpus` already points at `routing/scripts/route-corpus.mjs` (to be created); the committed seed schema is the write target. - **05-04 (web route surface)**: imports `createFixtureRoutingSource` + model types from `@araharah/routing/src/index.ts` (subpath-with-extension law documented in the module header). - **05-05 (gates)**: `?fixpair=` knob reads `getPairOrigin`; the C4 banned-token gate is designed to grow with the corpus script. --- *Phase: 05-routing-route-display* *Completed: 2026-10-23* ## Self-Check: PASSED - All 14 created files verified present on disk. - All 6 plan commits verified in git log (69b6a20, c92a955, 9b66f90, 4acb878, 0599ffe, 3e0c5e3). - `npm run routing:test`: 25 passed, 0 failed. `npm run routing:typecheck`: exit 0. - Root delegates verified from repo root; `workspaces` contains `routing`. - STATE.md and ROADMAP.md untouched (orchestrator-owned writes).