docs(05-05): complete verification-gates plan
- BA9/BA10 + BA5 sentinel extension, five ?fixpair-raced capture legs (13/13), RP1/RP2 Worker privacy gates - two Rule-1 boot fixes (onLoad wiring + consult-after-load) documented - STATE.md and ROADMAP.md untouched (orchestrator-owned)
This commit is contained in:
@@ -0,0 +1,173 @@
|
|||||||
|
---
|
||||||
|
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=<pair-id> 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=<pair-id>` 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 `<threat_model>`: 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).
|
||||||
Reference in New Issue
Block a user