--- phase: 05-routing-route-display plan: 05 subsystem: verification-gates tags: [bundle-budget, build-artifact-gates, capture-harness, fixpair, state-tokens, privacy-gates, worker-boundary, playwright, tdd] # Dependency graph requires: - phase: 05-routing-route-display/01 provides: "getPairOrigin (the pinned-public-origin lookup the ?fixpair knob resolves through) and the routing workspace sources BA9 measures" - phase: 05-routing-route-display/02 provides: "the committed 15-pair corpus (BA10's pairId set; the runtime demoPair for the capture legs; national-museum as the RT4 pending probe) and the 16.6 KB fixture payload inside BA9's budget" - phase: 05-routing-route-display/04 provides: "the ARAHARAH-ROUTE state tokens, the .route-chip surface, the route/pending cards, and the production build carrying the route family — everything the five legs race and assert" provides: - "BA9 — the D-11 bundle-budget gate: summed gz of fixtures + routing/src + web/src/route ≤ 204800 B, measurement PRINTED every run (35,076 / 204,800 measured)" - "BA10 — pairId parity: every committed pairId must appear in the shipped JS assets (the BA7 analog; stale fixture-less bundles are unshippable)" - "BA5 extension — ROUTE_PROFILE_HARNESS_ONLY joins the harness-leak scan; the route-profiles module is provably dead-code-eliminated from web/dist" - "?fixpair= harness knob + web/src/mock/route-profiles.ts (harness-only pinned-origin lookup; pairId-only, never coordinates — T-05-14)" - "Five route capture legs (8 → 13 expected artifacts): shell-route-fixture / shell-route-alt / shell-route-pending / shell-route-dark / route-gallery-compare — all raced on ARAHARAH-ROUTE state tokens + DOM text assertions" - "RP1/RP2 in bot/test/route-privacy.test.mjs — the Worker-side no-route-proxy structural gates (D-05, T-05-16)" - "[Rule 1] main.ts boot fixes: opts.onLoad → installRouteLayers wired, and the route consult gated on map load (the line actually paints now)" affects: [phase-6-guidance (consumes the painted-route state machine + capture discipline), phase-verifier (SC3 evidence chain), future BA budget growth] # Tech tracking tech-stack: added: [] # zero new dependencies — node:zlib for BA9, playwright already present patterns: - "Budget gates print their measurement: a budget nobody sees is a hope, not a gate (BA9 console.log on every run)" - "State-token-raced captures: the leg races ARAHARAH-ROUTE, parses state=, then asserts DOM text — signals carry state, never coordinates (D-15 runtime half)" - "Harness knobs deliver committed public data only (?fixpair → getPairOrigin), never raw coordinates — the coordinate-in-URL surface stays closed (T-05-14)" - "Teeth-proof RED for gates over clean trees: plant a violating scratch file, watch the gates fail, remove, watch them pass" key-files: created: - web/src/mock/route-profiles.ts - bot/test/route-privacy.test.mjs modified: - web/test/build-artifact.test.mjs (BA9 + BA10 + BA5 sentinel + header inventory) - web/scripts/capture.mjs (ARAHARAH-ROUTE signal, waitMode route, interact hook, 5 legs, 13-artifact gate, composeCompare refactor) - web/index.html (?fixpair knob inside the __ARAHARAH_HARNESS__ block) - web/src/main.ts ([Rule 1] onLoad wiring + consult-after-load gate) - web/test/route-surface.test.mjs (RS2 boot-wiring regression gate) key-decisions: - "BA9 measures SOURCE gz as a stand-in for minified gz — conservative in the gate's favor (minified gzips smaller) and the 200 KiB ceiling carries order-of-magnitude headroom" - "The capture demo pair is selected AT RUNTIME from the committed corpus (find routes.length >= 2 ?? first) — never a hardcoded pair literal, so the legs track the dataset" - "mapLayersReady gate in main.ts resolves in a finally block — a route-layer install failure degrades to 'no route drawn' without wedging the consult (cards-only boots resolve immediately)" - "RP gates scan comment-stripped source with prefer-fail over-stripping (the no-storage.test.mjs pattern) so doc mentions can never whitewash a real proxy" patterns-established: - "Capture-leg contract: race the state token, assert state=, poll DOM text, theme-slot honesty check on themed legs, interact(page) hook for pre-shot interactions" - "Boot-wiring gates: RS gates over fakes cannot see a missing callback in the composition root — the new RS2 boot-wiring grep pins main.ts's onLoad wiring" requirements-completed: [RTNG-01, RTNG-02] # Metrics duration: 19min completed: 2026-10-23 --- # Phase 5 Plan 5: Verification Gates — BA9/BA10 + Route Capture Legs + Worker Privacy Gates Summary **The phase closes on mechanical proof: BA9 prints the route payload at 35,076 / 204,800 gz bytes and BA10 proves every committed pairId rides the shipped bundle, five new capture legs race the ARAHARAH-ROUTE state token through the new ?fixpair knob (13/13 non-blank, the orange line verified painted), and RP1/RP2 gate the Worker out of the routing path — the amended SC3's "bundle-size and type gates holding" is now read from gate output, not memory.** ## Performance - **Duration:** 19 min - **Started:** 2026-10-23T21:44:12Z - **Completed:** 2026-10-23T22:03:31Z - **Tasks:** 3/3 - **Files touched:** 7 (2 created, 5 modified) ## Accomplishments - **D-11 is a gate, not a hope**: BA9 sums `gzipSync` over the fixtures dataset + every `routing/src` *.ts + every `web/src/route` *.ts (production-shaped by construction), asserts ≤ 204800 bytes, and PRINTS `BA9 route bundle budget: 35076 / 204800 gz bytes` on every run — the failure message carries the measurement and the overshoot. BA10 (the BA7 analog) asserts every one of the 15 committed pairIds appears in the concatenated `web/dist/assets/*.js` — a stale bundle that lost the fixtures is unshippable. BA5's harness-leak scan gained `ROUTE_PROFILE_HARNESS_ONLY`; the production build provably excludes the module (0 occurrences in web/dist — the `if (__ARAHARAH_HARNESS__)` dynamic import dead-code-eliminates, verified by grep after build). - **The five route capture legs pass against the production-shaped harness bundle** (8 → 13 expected artifacts): fixture (state=drawn + destination name + MIN + 2000ms tile settle), alt (first `.route-chip` tap → `/ROUTE \d\/\d/` register — ROUTE 2/2, +1 MIN VS ROUTE 1 verified in the shot), pending (`state=pending` + "not wired up yet" — the honest D-03 card with the privacy clause, never a fabricated geometry), dark (PX-02 theme-slot honesty check — chrome re-slots, orange route + grayscale basemap Rams-constant), and the DSGN-02 route-gallery-compare beside `navigate_map_overview/screen.png`. All legs are data-driven: `demoPair` is picked at runtime from the committed corpus, and the destination name is read from the gazetteer at capture time. - **The ?fixpair knob closes the coordinate surface it opens**: `?fixpair=` resolves through `getRouteProfileOrigin` → `getPairOrigin` — the ONLY origin source is the committed public corpus (D-12); a `?lat/?lng` knob is deliberately absent (T-05-14). Unknown pairId keeps the default fix — the boot never wedges. - **D-05 is mechanically gated**: RP1 (zero `/api/route`, `registerRoute`, `routeProxy` in comment-stripped bot/src) and RP2 (zero `@araharah/routing` imports) — the Worker cannot route even accidentally. Teeth-proven: a planted violating file fails both gates; removed, the bot suite runs 74/0. - **Two real bugs found and fixed by the new legs** (the plan's stated purpose for them): see Deviations. ## Task Commits 1. **task 1: BA9 + BA10 + BA5 sentinel extension** (gate file over already-shipped artifacts — no failing RED exists; mechanism teeth proven by a bogus-pairId probe returning false on the real bundle) - `29ad451` test(05-05): BA9 bundle-budget + BA10 fixture-parity gates + BA5 harness-sentinel extension 2. **task 2: ?fixpair knob + five route capture legs** (strict RED→GREEN) - `c9b1128` test(05-05): add five route capture legs racing ARAHARAH-ROUTE (RED) — the drawn legs failed exactly as predicted (default fix outside the demo pair's 200 m tolerance → state=pending) - `af744e4` feat(05-05): ?fixpair harness knob + pinned-corpus origin lookup (GREEN) — includes the two Rule-1 boot fixes below 3. **task 3: Worker-side no-route-proxy gates** (gates over a clean tree; RED = planted-violation teeth proof) - `6302901` test(05-05): Worker no-route-proxy gates RP1+RP2 (D-05) ## Files Created/Modified - `web/test/build-artifact.test.mjs` — BA9 (measured, printed, budget-gated), BA10 (pairId parity with `pairs.length >= 10` real-dataset guard), BA5 token list + header inventory - `web/scripts/capture.mjs` — `ARAHARAH-ROUTE` in SIGNALS/seen-map/diagnostics; `waitMode: "route"` (30s race, state= parse, expectText DOM polls, PX-02 honesty check, 2s settle); `interact(page)` pre-shot hook; the five legs; `composeCompare` shared composer; 13-artifact missing/undersized/failures gate - `web/index.html` — `?fixpair` knob inside the harness block (dynamic import of the profile module; knob documented in the profile comment line) - `web/src/mock/route-profiles.ts` — HARNESS-ONLY module: sentinel + `getRouteProfileOrigin`; D-12 + BA-exclusion header - `web/src/main.ts` — [Rule 1] `bootMap(..., { onLoad: (m) => installRouteLayers(...) })` + the `mapLayersReady` gate ahead of the auto-consult - `web/test/route-surface.test.mjs` — RS2 boot-wiring gate (the composition-root wiring the fakes could not see) - `bot/test/route-privacy.test.mjs` — RP1/RP2 structural gates over bot/src ## Decisions Made - **BA9 approximates minified gz with source gz** — conservative in the gate's favor at these sizes; documented in the gate's comment per the plan. - **demoPair picked at runtime, destination name read from the gazetteer at capture time** — the legs track the dataset instead of hardcoding literals (the capture_contract's own snippet). - **mapLayersReady resolves in `finally`** — an install failure must degrade to "no route drawn", never wedge the consult (the RS4 exception-path law applied to the boot gate). - **RS2 boot-wiring gate added to the web suite** (a file outside the plan's `files_modified`) — Rule 1's "add a test if applicable" clause: the missing-callback bug was invisible to every fake-driven gate, so the wiring itself is now grep-pinned. ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] main.ts never passed bootMap's opts.onLoad — the route line never painted** - **Found during:** task 2 (first GREEN capture run: card + pin + camera all rendered, zero orange on the map) - **Issue:** `bootMap(document.getElementById("map")!)` was called without the onLoad callback, so `installRouteLayers` never ran in the real boot; `setRouteDrawn`'s `getSource(...)?.setData(...)` silently no-op'd. Invisible to RS3/RS4 (recording fakes) and RS2 (map.ts textual position only). - **Fix:** wire `onLoad: (m) => installRouteLayers(m as unknown as MapLike)`; add the RS2 boot-wiring gate pinning it. - **Files modified:** web/src/main.ts, web/test/route-surface.test.mjs - **Commit:** af744e4 **2. [Rule 1 - Bug] route consult raced the map load — the draw landed on sources that did not exist yet** - **Found during:** task 2 (after fix 1 the line STILL didn't paint; a headless probe showed `ARAHARAH-ROUTE state=drawn` firing BEFORE `RAMS-BASEMAP-READY layers=115`) - **Issue:** the harness session stubs resolve the location chain faster than WebGL/style init, so consult + setData ran before the load handler installed (empty) sources — the drawn data was discarded. The 05-04 assumption "the load hook precedes the location chain" is false under stubbed session latency (and plausible in production on fast networks). - **Fix:** `mapLayersReady` promise resolved in the load handler after `installRouteLayers` (finally-resolved — install failure degrades, never wedges; cards-only boots resolve immediately); the auto-consult awaits it. Probe after: READY precedes ROUTE, and the orange line paints (verified in all three drawn captures). - **Files modified:** web/src/main.ts - **Commit:** af744e4 ### Judgment calls within plan laws (not deviations) - Task 1 and task 3 gate artifacts that already hold (fixtures shipped in 05-02/05-04; the Worker tree is clean) — a failing RED run does not exist for them; both were teeth-proven instead (bogus-pairId probe for BA10's mechanism; planted violating file for RP1/RP2). Task 2's RED is genuine. - The plan's inline `resolve(REPOROOT, ..., "utf8")` snippet misplaced a paren; implemented with the intended semantics (utf8 read of the resolved path). ## TDD Gate Compliance - **task 2** is the plan's real RED→GREEN: `c9b1128` (legs fail: 3× state mismatch + missing gallery input, exit 1) → `af744e4` (13/13). The GREEN commit also carries the two Rule-1 fixes the RED run exposed. - **tasks 1 and 3** carry `tdd="true"` but gate already-true invariants (the plan frontmatter is `type: execute`, so no plan-level RED/GREEN ladder applies). Teeth were demonstrated without polluting the tree: BA10's `includes()` discriminates on the real bundle (bogus pairId absent); RP1/RP2 fail against a planted violation and pass on the clean tree. No fake RED commits were made. ## Known Stubs None. The `?fixpair` knob, the five legs, and both gate files are fully wired to committed data. (web/screenshots/* are regenerated gitignored artifacts by design — the capture harness's own contract.) ## Threat Surface Scan No new surface beyond the plan's ``: the knob accepts pairIds only and resolves them exclusively through the committed public corpus (T-05-14 mitigated, runtime-proven by the pending leg); BA9 gives T-05-12 its build-gate tier (35 KB measured vs 204,800 allowed); the BA5 sentinel extension enforces T-05-15 (0 sentinel occurrences in web/dist); RP1/RP2 enforce T-05-16 (0 banned tokens in bot/src). The capture signals carry state tokens only — no coordinates in any console line (D-15). No threat flags. ## Issues Encountered - Node lives under nvm; non-interactive shells need `source ~/.nvm/nvm.sh` first (all prior 05-xx plans noted the same). No project impact. - A block-comment in the first draft of route-privacy.test.mjs terminated early on a `*/` inside `bot/src/**/*.ts` — caught by the suite loader as a syntax error, fixed by rewording. No gate semantics changed. - Playwright's ESM resolution fails from /tmp scripts; the debug probe imported it by absolute path. The probe script lived outside the repo and was never committed. ## User Setup Required None — no external service configuration. The captures run against the committed corpus and local builds; tiles come from the existing OpenFreeMap runtime fetch. ## Next Phase Readiness - **Phase verification / the amended SC3 evidence chain is complete**: RT1–RT10 (corpus routes cleanly, corpus gate PASS) + BA9/BA10 (bundle-size gate, measurement printed) + the 05-01 C2 type gates + the pending capture (deliberate, designed) — all four clauses have a green instrument. - **Phase 6 (guidance)**: the route state machine is now visually verified against the production bundle; the painted-route boot ordering (consult-after-load) is the exact precondition guidance's turn-card flow inherits; the capture harness's state-token race pattern extends to guidance signals. - **Verifier hooks**: BA1–BA10, RS1–RS10 (+ boot-wiring), COPY gates, RT1–RT10, RP1/RP2, and the 13-leg capture gate are all mechanical. --- *Phase: 05-routing-route-display* *Completed: 2026-10-23* ## Self-Check: PASSED - All created files verified present on disk (web/src/mock/route-profiles.ts, bot/test/route-privacy.test.mjs); all modified files verified in the commits. - All 4 plan commits verified in git log: 29ad451, c9b1128, af744e4, 6302901 (task 2 strict RED→GREEN). - `npm test -w web`: 76 passed, 0 failed — output contains `PASS BA9`, `PASS BA10`, `BA9 route bundle budget: 35076 / 204800 gz bytes`. - `npm run web:capture`: exit 0, `CAPTURE OK — 13/13 non-blank captures`; the five new artifacts exist; the orange line visually verified in fixture/alt/dark shots; the pending card honest. - `npm test -w bot`: 74 passed, 0 failed — `PASS RP1`, `PASS RP2`; tree greps for `/api/route` and `@araharah/routing` in bot/src both return 0. - `npm run routing:test` 35/0; `npm run routing:corpus` PASS (gz 16,576 B; multi-route 8 / floor 4); web/bot/routing typechecks exit 0. - STATE.md and ROADMAP.md untouched (orchestrator-owned writes); only 05-05-SUMMARY.md added (force-add, .planning/ ignored). ## Phase close (orchestrator, 2026-10-24) All 5 plans complete; verification PASSED (05-VERIFICATION.md). Deployed live `e6a6811c`. Two real bugs the new capture legs caught (bootMap onLoad never wired; route consult racing map load) fixed + regression-gated in wave 3.