Files
arah2/.planning/phases/06-turn-by-turn-guidance-live-driving/06-03-SUMMARY.md
T

171 lines
15 KiB
Markdown
Raw 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: 06-turn-by-turn-guidance-live-driving
plan: 03
subsystem: bot
tags: [kv, ttl-only, guidance-sessions, envelope, api-endpoints, bearer-claim, privacy, gate-repin, tdd, workspace-dep]
# Dependency graph
requires:
- phase: 05-routing-route-display
plan: 03
provides: the FROZEN GuidanceSessionEnvelope/GuidanceRouteSnapshot shape + GUIDANCE_SESSION_TTL_SECONDS + guidance:<id> key scheme (this plan implements, never redefines)
- phase: 05-routing-route-display
plan: 01
provides: decodePolyline6 — the pure codec validating POST body geometry server-side
- phase: 03-mini-app-shell-geolocation
plan: 01
provides: the Bearer-claim precedent (handleLocation) and getClaimByToken — the only identity source for /api/guidance
provides:
- "mintGuidanceSession/getGuidanceSession/deleteGuidanceSession — the TTL-only envelope service over the unchanged frozen shape (GE1/GE2/GE3 hold)"
- "POST/GET/DELETE /api/guidance — chat identity from the Bearer claim ONLY; body chat_id structurally ignored (T-06-02); cross-chat read/delete impossible (T-06-02b)"
- "MAX_GUIDANCE_BODY_CHARS = 8192 with the WR-01 pre/post-read double check"
- "RP2 re-pinned as an explicit allowlist: exactly @araharah/routing/src/polyline.ts may enter the Worker"
- "PG1 storageModules now covers kv/guidance-sessions.ts (same-line TTL law on the new write path)"
affects: [phase-6 guidance turn-card cron (plan 07 reads envelopes), GUID-05 web POST-on-minimize, GUID-06 resume, GUID-06 trip-end DELETE]
# Tech tracking
tech-stack:
added:
- "@araharah/routing:* in bot/package.json (workspace dep; only the pure polyline6 codec subpath is imported)"
patterns:
- "Types-frozen + service-beneath: behavior added BELOW an interface whose bytes never changed (GE2 runtime twin = exact-key checks in the defensive read)"
- "Gate re-pins ship with the invalidating change: GE4/GE7 (service live / consumers scoped) in 0cfa0bd, RP2 (codec-only allowlist) in 7875ed4"
- "Resolve-then-OWN endpoint posture: getGuidanceSession → envelope.chat_id === claim.chat_id, else an indistinguishable 404 (no existence oracle)"
key-files:
created: [] # zero new files — behavior landed inside existing modules per the frozen-contract plan
modified:
- bot/src/kv/guidance-sessions.ts
- bot/src/webhook.ts
- bot/package.json
- package-lock.json
- bot/test/guidance-envelope.test.mjs
- bot/test/webhook.test.mjs
- bot/test/privacy.test.mjs
- bot/test/route-privacy.test.mjs
key-decisions:
- "RP2 re-pin is an ALLOWLIST, not a lifted ban: every @araharah/routing specifier in bot/src must be EXACTLY the pure codec subpath — the routing stack (graph/A*/fixtures) stays web-side; only charset/decode validation crosses the boundary"
- "The envelope service REUSES the chat_sessions namespace under guidance:<id> keys — no second kv_namespaces entry (GE5 stands), no new Env field (GE6 stands); the binding is handed in structurally (the sessions.ts/location-pin.ts KvLike seam)"
- "validateSnapshot builds the snapshot field-by-field, so a body chat_id (or any extra property) is structurally dropped before mint — T-06-02 by construction, proven by the GW4 injection probe"
- "404 for absent, malformed, AND foreign ids — indistinguishable by design (T-06-02b, no existence oracle); GW7/GW10 additionally prove a foreign GET/DELETE never deletes"
patterns-established:
- "Guidance endpoint test block (GW1–GW11): claims minted through the REAL /api/session path (mintSession) against the Map-fake KV, write-count assertions made relative to the claim-mint baseline"
requirements-completed: [GUID-05] # server surface of GUID-05 (envelope mint/read/delete over HTTP); REQUIREMENTS.md tick left to the orchestrator
# Metrics
duration: ~8min (commits 05:02:28Z–05:06:25Z; resumed once — see Deviations)
completed: 2026-10-24
---
# Phase 6 Plan 3: Guidance Envelope Service + /api/guidance Endpoints Summary
**The Phase 5-frozen `GuidanceSessionEnvelope` goes LIVE (D-11's precondition): a TTL-only mint/get/delete service beneath the byte-identical 4-field shape, plus Bearer-claim-gated POST/GET/DELETE `/api/guidance` — chat identity from the claim only, every write TTL'd at 7200 s, corrupt records read as null, and the RP2 routing ban re-pinned to a codec-only allowlist.**
## Performance
- **Duration:** ~8 min (task commits 05:02:28Z → 05:06:25Z; session resumed once — see Deviations)
- **Started:** 2026-10-24T05:02:28Z (first task commit)
- **Completed:** 2026-10-24T05:06:25Z (last task commit); continuation verification + summary ~05:08Z
- **Tasks:** 2/2 (both TDD RED→GREEN)
- **Files modified:** 8 (zero created — the plan's whole point was to add behavior inside existing modules)
- **Suites:** bot 74 → 90 tests (5 GS behavioral + 11 GW endpoint), 90/90 green; `bot:typecheck` exit 0
## Accomplishments
- **The envelope service exists under the frozen shape** (task 1): `mintGuidanceSession` (crypto.randomUUID id, claim-derived chat_id, ONE put at `guidance:<id>` with same-line `expirationTtl: GUIDANCE_SESSION_TTL_SECONDS`), `getGuidanceSession` (UUID-shape guard BEFORE any KV touch — V5; try/catch parse; exact-key 4+6 field check + per-field type/finite/range validation mirroring the location-pin defensive read), `deleteGuidanceSession` (active trip-end sweep, D-14's server half; malformed id is a silent no-op). The envelope/snapshot interfaces are byte-identical to Phase 5 — GE1/GE2/GE3 pass unchanged.
- **The boundary gates re-pinned WITH the change** (0cfa0bd): GE4 flipped from the types-only behavior-token ban to "the service trio is exported"; GE7 flipped from a total consumer ban to "named imports of the service surface only" (mint/get/delete, the TTL constant, the two types — no default/namespace/side-effect end-run). GE5 (exactly ONE kv namespace) and GE6 (no Env guidance field) stand untouched because the service reuses `chat_sessions`.
- **`/api/guidance` is live over HTTP** (task 2): POST (401 → 413 → 400 → 201 classes; hand-rolled snapshot validation — slug `destinationId` per the search workspace's ENTRY_ID_PATTERN law, bounded `destinationName`, finite non-negative metrics, safe-integer `startedAtSec`, geometry that `decodePolyline6` decodes to ≥ 2 points) → `mintGuidanceSession(env.chat_sessions, claim.chat_id, snapshot)` → 201 `{id}`. GET returns the envelope only to the OWNING chat; DELETE removes the record actively. Non-POST/GET/DELETE → 405.
- **The threat register's mitigations are all code, all gate-proven**: T-06-02 (body `chat_id: 999` with claim chat 101 stores 101 — GW4 injection probe; snapshot built field-by-field so extras are structurally dropped), T-06-02b (foreign/absent → identical 404 `{error:"not_found"}`; the record SURVIVES a foreign GET/DELETE — GW7/GW10), T-06-04 (every log line is a literal category — `[guidance] rejected: unauthorized|malformed`; no ids, no coordinates, no payloads; TTL 7200 s on every write — PG1+GE4+GS1), DoS (MAX_GUIDANCE_BODY_CHARS 8192 with the pre/post-read double check — GW3 proves the lying-Content-Length backstop).
- **PG1 grew with the write path**: `kv/guidance-sessions.ts` joined `storageModules` in the same commit that added the `.put(` (the PATTERNS caution honored mechanically).
## Task Commits
1. **task 1: envelope service + GE4–GE7 re-pins + PG1 (TDD)**
- `0cfa0bd` test(06-03): re-pin GE4/GE7 to the live service + behavioral envelope suite + PG1 storage list (RED)
- `c87dc7d` feat(06-03): guidance envelope service — TTL-only mint/get/delete beneath the frozen shape (GREEN)
2. **task 2: /api/guidance POST + GET + DELETE (TDD)**
- `212aa57` test(06-03): guidance endpoint suite GW1-GW11 — 401/413/400 classes, claim-owned chat (T-06-02 injection probe), own-vs-foreign GET/DELETE, 405 (RED)
- `7875ed4` feat(06-03): /api/guidance POST/GET/DELETE — Bearer-claim gated, TTL-only service beneath (GREEN) — includes the RP2 re-pin + `@araharah/routing` workspace dep
## TDD Gate Compliance
- Task 1: RED `0cfa0bd` (GE4/GS1–GS5 fail on the absent service; GE1/GE2/GE3 pass unchanged; GE5–GE7 green-by-construction boundary guards, documented) → GREEN `c87dc7d` (90-suite predecessor green).
- Task 2: RED `212aa57` (GW1–GW7, GW9–GW11 fail on the missing route arm; GW8 green-by-construction — the catch-all already 404s; noted in the RED commit message) → GREEN `7875ed4`.
- Both RED→GREEN pairs verified in git log; no gate skipped.
## Files Created/Modified
- `bot/src/kv/guidance-sessions.ts` — frozen types untouched; the service added below them: `GuidanceSessionKv` seam, `ENVELOPE_ID_PATTERN` + exact-key tables (`ENVELOPE_KEYS`/`SNAPSHOT_KEYS`), mint/get/delete with the defensive-read field law (`isEnvelopeValid`). Module header now states the live-service law: shape frozen (GE2); the service owns every write; every write carries `expirationTtl: GUIDANCE_SESSION_TTL_SECONDS`.
- `bot/src/webhook.ts` — `/api/guidance` route arm + `handleGuidance` (method gate → Bearer claim → POST cap/validate/mint | GET/DELETE resolve-then-own), `MAX_GUIDANCE_BODY_CHARS = 8192`, `DESTINATION_ID_PATTERN` (mirrors `search/src/source.ts`), `MAX_DESTINATION_NAME_CHARS = 200`, `validateSnapshot` (field-by-field construction), category-only response helpers; header route table documents the new surface.
- `bot/test/guidance-envelope.test.mjs` — GE1–GE3 unchanged in meaning; GE4/GE7 re-pinned; GS1–GS5 behavioral (TTL'd mint, round-trip, 7-class defensive null read, UUID key-guard with a KV-untouched counter, active delete).
- `bot/test/webhook.test.mjs` — GW1–GW11 offline undici block; claims minted through the real `/api/session` path; injection probe; log-literal assertions.
- `bot/test/privacy.test.mjs` — PG1 `storageModules` + `kv/guidance-sessions.ts`.
- `bot/test/route-privacy.test.mjs` — RP2 re-pinned to the codec-only allowlist (header documents the 06-03 re-pin and why); RP1 unchanged.
- `bot/package.json` + root `package-lock.json` — `"@araharah/routing": "*"` workspace dep.
## Decisions Made
- **RP2 as an allowlist, not a lifted ban** — the plan required `import { decodePolyline6 } from "@araharah/routing/src/polyline.ts"` in webhook.ts, which the old RP2 (zero `@araharah/routing` tokens in bot/src) forbids. Per the gate re-pin law (PATTERNS: "the plan adding the routing import re-pins RP2, listed explicitly"), the GREEN commit narrows RP2 to: every routing specifier in bot/src must be EXACTLY the pure codec subpath. The routing stack (router/graph/A*/fixtures) still cannot enter the Worker; RP1 (no `/api/route` proxy) untouched.
- **Envelope storage reuses `chat_sessions`** — the frozen key scheme (`guidance:<id>`) lives in the existing namespace, so GE5/GE6 stay green without edits; no second namespace was created (R2 later is a different binding class).
- **`deleteGuidanceSession` UUID-guards like the read** — a malformed id constructs no KV key at all (silent no-op), closing arbitrary-key deletion symmetrically with the V5 read guard.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] RP2 gate re-pin (file not in `files_modified`)**
- **Found during:** task 2
- **Issue:** the plan's required codec import in `bot/src/webhook.ts` makes the existing RP2 gate (zero `@araharah/routing` specifiers in bot/src) permanently red — task 2's own verify (`npm test -w bot` green) is impossible without touching `bot/test/route-privacy.test.mjs`, which the plan's file list omitted.
- **Fix:** re-pinned RP2 to the codec-only allowlist in the same GREEN commit as the import, per the repo's explicit gate-re-pin law (PATTERNS "Gate re-pins ship WITH the change that invalidates them", which names RP2 and THIS kind of plan as the owner).
- **Files modified:** bot/test/route-privacy.test.mjs
- **Commit:** 7875ed4
**2. [Rule 1 - Bug] GW test write-count baselines corrected at GREEN**
- **Found during:** task 2 GREEN
- **Issue:** the RED fixtures minted claims via the real `mintSession` (2 KV writes) but several GW cases asserted absolute `ttlLog.length` (0 or 1) — a fixture-counting bug that would have made honest implementations fail.
- **Fix:** counts made relative to a `writesBefore` baseline captured after the claim mint; assertion strength unchanged (the guidance write is still proven singular, TTL'd, and correctly keyed).
- **Files modified:** bot/test/webhook.test.mjs
- **Commit:** 7875ed4
### Session Continuation Note
This plan executed across two executor sessions: the first landed all four task commits but its final working-tree writes flushed late (observed by the continuation agent as a tree that settled between checks). The continuation agent verified every gate post-flush (all four commits present, 90/90 green, typecheck clean, no untracked residue, no deletions in any plan commit) rather than re-executing. No work was redone or lost.
## Known Stubs
None — the service and all three endpoints are fully wired to the real KV binding (`env.chat_sessions`); nothing is mocked, placeholdered, or deferred. Plan 07's cron is the designed next consumer, not a missing piece of this plan.
## Threat Surface Scan
No surface beyond the plan's `<threat_model>`: one new endpoint family (`/api/guidance`, Bearer-gated — covered), one new KV write path (TTL-only — covered by PG1/GE4), no new bindings, no file access, no schema at a trust boundary. All four STRIDE mitigations are implemented and gate-proven (see Accomplishments). No threat flags.
## Issues Encountered
- Node lives under nvm in this environment — verification commands source `~/.nvm/nvm.sh` first (the 05-01/05-03 precedent). No project impact.
- STATE.md / ROADMAP.md / REQUIREMENTS.md intentionally untouched — orchestrator-owned writes after the wave completes (GUID-05 completion is recorded above for the orchestrator to tick).
## User Setup Required
None — no new secrets, no new wrangler bindings, no external services. The workspace dep links locally via npm workspaces; deploy needs nothing beyond the existing `chat_sessions` namespace.
## Next Phase Readiness
- **Plan 07 (cron turn-cards)**: reads envelopes through `getGuidanceSession`'s defensive validation (corrupt → null, never pushed); iterates `guidance:` keys; pushes to `envelope.chat_id` only.
- **Web plans (GUID-05/06 flows)**: POST on minimize (envelope in → `{id}` out), DELETE on trip end (D-14's client half already has its server twin), GET on resume.
- **Verifier hooks**: GE1–GE7, PG1, RP1/RP2, GW1–GW11, GS1–GS5 all pin the surface mechanically; the injection probe (GW4) is the regression canary for the claim-only identity law.
---
*Phase: 06-turn-by-turn-guidance-live-driving*
*Completed: 2026-10-24*
## Self-Check: PASSED
- All 8 modified files + the SUMMARY verified present on disk.
- All four plan commits verified in git log (0cfa0bd RED, c87dc7d GREEN, 212aa57 RED, 7875ed4 GREEN) — correct RED→GREEN interleaving per task.
- `npm test -w bot`: 90 passed, 0 failed (GE1–GE7 re-pinned + GS1–GS5 + GW1–GW11 + all 74 pre-existing). `npm run bot:typecheck`: exit 0.
- Acceptance greps re-run post-flush: `expirationTtl: GUIDANCE_SESSION_TTL_SECONDS` ×3 in the service module; `MAX_GUIDANCE_BODY_CHARS = 8192` at webhook.ts:225; all `[guidance]` log lines are literal categories (no interpolation).
- No deletions in any plan commit; no untracked non-planning files; STATE.md / ROADMAP.md / REQUIREMENTS.md untouched.