docs(06-03): merge continuation-pass verification into the plan summary

- session-continuation note (late-flush observation + post-flush gate
  re-verification by the continuation agent)
- plan-agnostic phrasing for the future worker-router RP2 widening
This commit is contained in:
araharah-executor
2026-10-24 13:10:18 +08:00
parent 3f06fdc281
commit c96f186da6
@@ -2,160 +2,173 @@
phase: 06-turn-by-turn-guidance-live-driving phase: 06-turn-by-turn-guidance-live-driving
plan: 03 plan: 03
subsystem: bot subsystem: bot
tags: [kv, ttl-only, guidance-sessions, envelope, api-endpoints, bearer-claim, privacy, gate-repin, tdd, workspace-dep] tags: [kv, guidance-sessions, envelope-service, ttl, api-endpoints, bearer-claim, privacy, tdd, gate-repin]
# Dependency graph # Dependency graph
requires: requires:
- phase: 05-routing-route-display - phase: 05-routing-route-display
plan: 03 plan: 03
provides: the FROZEN GuidanceSessionEnvelope/GuidanceRouteSnapshot shape + GUIDANCE_SESSION_TTL_SECONDS + guidance:<id> key scheme (this plan implements, never redefines) provides: the FROZEN GuidanceSessionEnvelope/GuidanceRouteSnapshot shapes + GUIDANCE_SESSION_TTL_SECONDS + the guidance:<id> key scheme (implemented, never redefined — GE1/GE2/GE3 unchanged)
- phase: 05-routing-route-display - phase: 05-routing-route-display
plan: 01 plan: 01
provides: decodePolyline6 — the pure codec validating POST body geometry server-side provides: the polyline6 codec (decodePolyline6) the Worker now validates snapshot geometry with
- phase: 03-mini-app-shell-geolocation - phase: 02
plan: 01 provides: the Bearer-claim precedent (handleLocation posture), the WR-01 pre/post-read body-cap pattern, getClaimByToken
provides: the Bearer-claim precedent (handleLocation) and getClaimByToken — the only identity source for /api/guidance
provides: provides:
- "mintGuidanceSession/getGuidanceSession/deleteGuidanceSession — the TTL-only envelope service over the unchanged frozen shape (GE1/GE2/GE3 hold)" - "mintGuidanceSession / getGuidanceSession / deleteGuidanceSession — the TTL-only KV service over the frozen envelope (chat_sessions binding, guidance:<id> keys, 7200 s)"
- "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)" - "POST /api/guidance — mint a guidance session; chat_id from the Bearer claim ONLY (body chat_id structurally ignored, T-06-02); hand-rolled snapshot validation incl. polyline6 ≥2-point decode"
- "MAX_GUIDANCE_BODY_CHARS = 8192 with the WR-01 pre/post-read double check" - "GET /api/guidance?id=<uuid> — claim-owned envelope read; foreign/absent indistinguishable 404 (T-06-02b)"
- "RP2 re-pinned as an explicit allowlist: exactly @araharah/routing/src/polyline.ts may enter the Worker" - "DELETE /api/guidance?id=<uuid> — D-14's server half: the active trip-end KV sweep"
- "PG1 storageModules now covers kv/guidance-sessions.ts (same-line TTL law on the new write path)" - "RP2 re-pin — @araharah/routing allowlist in bot/src: exactly the polyline6 codec"
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] affects: [06 web guidance flows (POST on minimize), plan-07 cron (defensive reads + turn-card push), D-14 trip-end flow]
# Tech tracking # Tech tracking
tech-stack: tech-stack:
added: added: ["@araharah/routing:* (bot workspace dep — first routing import into the Worker, allowlisted to the codec)"]
- "@araharah/routing:* in bot/package.json (workspace dep; only the pure polyline6 codec subpath is imported)"
patterns: patterns:
- "Types-frozen + service-beneath: behavior added BELOW an interface whose bytes never changed (GE2 runtime twin = exact-key checks in the defensive read)" - "Defensive-read KV service: exact-key-set + type + finite/range validation on every read (the location-pin law extended to a 4+6-field nested record)"
- "Gate re-pins ship with the invalidating change: GE4/GE7 (service live / consumers scoped) in 0cfa0bd, RP2 (codec-only allowlist) in 7875ed4" - "UUID key-guard before any KV touch (V5: a malformed id never becomes arbitrary-key probing)"
- "Resolve-then-OWN endpoint posture: getGuidanceSession → envelope.chat_id === claim.chat_id, else an indistinguishable 404 (no existence oracle)" - "Resolve-then-own authorization for GET/DELETE (claim comparison AFTER read, BEFORE mutate)"
- "Gate re-pin in the same commit as the invalidating change (GE4/GE7 with task 1; RP2 with the routing import)"
key-files: key-files:
created: [] # zero new files — behavior landed inside existing modules per the frozen-contract plan created: []
modified: modified:
- bot/src/kv/guidance-sessions.ts - bot/src/kv/guidance-sessions.ts
- bot/src/webhook.ts - bot/src/webhook.ts
- bot/package.json - bot/package.json
- package-lock.json
- bot/test/guidance-envelope.test.mjs - bot/test/guidance-envelope.test.mjs
- bot/test/webhook.test.mjs - bot/test/webhook.test.mjs
- bot/test/privacy.test.mjs - bot/test/privacy.test.mjs
- bot/test/route-privacy.test.mjs - bot/test/route-privacy.test.mjs
- package-lock.json
key-decisions: 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" - "GE7's re-pin is a SHAPE gate, not a presence gate: bot/src MAY import the service but only via NAMED imports of the service surface (mint/get/delete, TTL const, the two types) — vacuous-green until the first consumer lands in this plan's webhook task, teeth grow with the consumer tree"
- "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)" - "RP2 re-pin scope kept minimal: exactly @araharah/routing/src/polyline.ts allowlisted; every other routing subpath still banned — plan 06-01 (engine A worker-router) widens it under its own responsibility per PATTERNS"
- "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" - "deleteGuidanceSession applies the same UUID guard as reads (silent no-op on malformed ids) — no key is ever constructed from unvalidated input, read or delete"
- "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" - "GET/DELETE 404 bodies are {error:'not_found'} JSON so foreign and absent stay byte-indistinguishable (no existence oracle, T-06-02b)"
- "validateSnapshot builds a NEW six-field object field-by-field — extra body properties (chat_id included) can never reach storage even if future validation relaxes"
patterns-established: 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" - "Fourth Bearer-claim arm copies the handleLocation posture verbatim (regex charset gate → getClaimByToken → category-only 401); guidance adds method dispatch POST/GET/DELETE under one path"
requirements-completed: [GUID-05] # server surface of GUID-05 (envelope mint/read/delete over HTTP); REQUIREMENTS.md tick left to the orchestrator requirements-completed: [GUID-05]
# Metrics # Metrics
duration: ~8min (commits 05:02:28Z–05:06:25Z; resumed once — see Deviations) duration: 12min
completed: 2026-10-24 completed: 2026-10-24
--- ---
# Phase 6 Plan 3: Guidance Envelope Service + /api/guidance Endpoints Summary # 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.** **The Phase 5-frozen `GuidanceSessionEnvelope` went LIVE (D-11's precondition): a TTL-only mint/get/delete KV service beneath the byte-identical shape, plus Bearer-gated POST/GET/DELETE on `/api/guidance` — chat identity from the claim only, every write TTL'd at 7200 s on its call line, GE4–GE7 and RP2 re-pinned in the same commits that changed their premises.**
## Performance ## Performance
- **Duration:** ~8 min (task commits 05:02:28Z → 05:06:25Z; session resumed once — see Deviations) - **Duration:** 12 min
- **Started:** 2026-10-24T05:02:28Z (first task commit) - **Started:** 2026-10-24T04:54:57Z
- **Completed:** 2026-10-24T05:06:25Z (last task commit); continuation verification + summary ~05:08Z - **Completed:** 2026-10-24T05:06:41Z
- **Tasks:** 2/2 (both TDD RED→GREEN) - **Tasks:** 2/2 (both TDD RED→GREEN)
- **Files modified:** 8 (zero created — the plan's whole point was to add behavior inside existing modules) - **Files modified:** 8 (6 bot workspace + root package-lock.json; zero files created — the service grew inside the frozen contract module)
- **Suites:** bot 74 → 90 tests (5 GS behavioral + 11 GW endpoint), 90/90 green; `bot:typecheck` exit 0
## Accomplishments ## 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 envelope service exists** (task 1): `mintGuidanceSession` (crypto.randomUUID id, claim-derived chat_id argument, ONE put at `guidance:<id>` with same-line `expirationTtl: GUIDANCE_SESSION_TTL_SECONDS`), `getGuidanceSession` (UUID-shape guard BEFORE any KV touch; exact 4-envelope + 6-snapshot key-set check; type + finite/range validation — a corrupt record reads as null and can never reach the plan-07 cron), `deleteGuidanceSession` (active sweep — D-14's server half). The frozen interfaces are byte-identical (GE2 green).
- **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`. - **The endpoints are live** (task 2): `POST /api/guidance` (WR-01 cap at `MAX_GUIDANCE_BODY_CHARS = 8192` with the pre/post-read double check; hand-rolled validation — slug `destinationId` per the gazetteer's own `ENTRY_ID_PATTERN`, bounded name, finite non-negative metrics, integer `startedAtSec`, geometry that `decodePolyline6` decodes to ≥ 2 points) → 201 `{id}`; `GET`/`DELETE` resolve-then-own (`envelope.chat_id === claim.chat_id` else an indistinguishable 404); other methods 405.
- **`/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 claim-only identity law is proven, not asserted** (T-06-02): GW4's injection probe posts `chat_id: 999` in the body alongside the claim's `101` — the stored envelope carries 101. T-06-02b: GW7/GW10 prove a foreign token can neither read nor delete another chat's record, and that the record survives the foreign attempt.
- **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). - **Every gate re-pinned WITH its invalidating change**: GE4 (types-only ban → service-export assert) and GE7 (total consumer ban → named-imports-of-service-surface) in task 1's RED; RP2 (total routing ban → polyline6-codec allowlist) in task 2's GREEN. GE1/GE2/GE3 unchanged and green; GE5 (exactly ONE kv namespace) and GE6 (no Env guidance field) held — the service reuses the existing `chat_sessions` binding.
- **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). - **PG1 now covers the new write path** (`kv/guidance-sessions.ts` in storageModules) — and proved itself immediately: it FAILED my first GREEN draft because the `put` had `expirationTtl` on a continuation line; the same-line law forced the fix before commit.
- **Full suite: 90 passed, 0 failed** (74 pre-existing + 5 GE re-pin/behavioral net-new + 11 GW); `bot:typecheck` exit 0.
## Task Commits ## Task Commits
1. **task 1: envelope service + GE4–GE7 re-pins + PG1 (TDD)** 1. **task 1: envelope service + GE4–GE7 re-pins + PG1**
- `0cfa0bd` test(06-03): re-pin GE4/GE7 to the live service + behavioral envelope suite + PG1 storage list (RED) - `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) - `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)** 2. **task 2: /api/guidance POST + GET + DELETE**
- `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) - `212aa57` test(06-03): guidance endpoint suite GW1-GW11 (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 - `7875ed4` feat(06-03): /api/guidance POST/GET/DELETE — Bearer-claim gated, TTL-only service beneath (GREEN)
**Plan metadata:** committed separately (docs, force-added — `.planning/` is gitignored).
## TDD Gate Compliance ## 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 1 RED `0cfa0bd`: GE4 + GS1–GS5 fail on the absent service (6 failures, right reasons); GE1–GE3 pass unchanged; GE5–GE7 green-by-construction (documented in the suite header).
- 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`. - Task 1 GREEN `c87dc7d`: 79/79 (PG1 caught the split-line TTL in the draft — fixed pre-commit).
- Both RED→GREEN pairs verified in git log; no gate skipped. - Task 2 RED `212aa57`: 10/11 GW fail on the absent endpoint; GW8 green-by-construction (the catch-all already 404s absent ids — its ownership teeth live in GW7/GW10).
- Task 2 GREEN `7875ed4`: 90/90 + typecheck exit 0.
- Git sequence: test→feat→test→feat — both RED and GREEN gates present for both tasks.
## Files Created/Modified ## 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/kv/guidance-sessions.ts` — header rewritten to the shape-frozen/service-owns-every-write law; `GuidanceSessionKv` seam (structural — no KvNamespace type token, no new binding), UUID pattern, exact-key constants, mint/get/delete + `isEnvelopeValid`.
- `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/src/webhook.ts` — `/api/guidance` route arm + `handleGuidance` (method dispatch, Bearer claim, WR-01 double check, mint/resolve-own/delete) + `validateSnapshot` + category-only response helpers; header route table updated.
- `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/package.json` + root `package-lock.json` — `@araharah/routing: "*"` workspace dep (linked via npm install).
- `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/guidance-envelope.test.mjs` — header rewritten (frozen vs re-pinned gate table); GE4/GE7 re-pinned; GS1–GS5 behavioral suite on a Map-fake KV with call-counting (proves the UUID guard never touches KV).
- `bot/test/privacy.test.mjs` — PG1 `storageModules` + `kv/guidance-sessions.ts`. - `bot/test/webhook.test.mjs` — GW1–GW11: 401 classes, 413 pre/post-read, 10 malformed classes with per-class category-log + zero-write asserts, happy-path 201 + TTL + injection probe + extra-field drop, own/foreign GET/DELETE, survival asserts, 405. Claims minted through the real `mintSession` path.
- `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/test/privacy.test.mjs` — PG1 storageModules += `kv/guidance-sessions.ts`.
- `bot/package.json` + root `package-lock.json` — `"@araharah/routing": "*"` workspace dep. - `bot/test/route-privacy.test.mjs` — RP2 re-pinned to the codec allowlist; RP1 unchanged.
## Decisions Made ## 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. - **GE7 as a shape gate (not a presence gate)** — the plan asked GE7 to assert consumers "MAY import" the service. A permission can't fail, so the gate enforces the permission's SHAPE: every `guidance-sessions` import in bot/src must be a named import of the service surface (default/namespace/side-effect forms fail via a specifier-count cross-check). Vacuous-green until the webhook import landed in task 2; documented in the gate.
- **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). - **RP2's allowlist stays minimal** — PATTERNS assigns the RP2 re-pin to "the plan adding the routing import"; that's this plan for the codec. Engine A's Worker-side router (`bot/src/routing/worker-router.ts`, a later phase-6 plan) widens the allowlist under its own responsibility. RP1 (no `/api/route` proxy) untouched — the guidance path name can't collide.
- **`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. - **Delete mirrors the read guard** — the plan's behavior list guards only `get`; delete gets the same UUID check so no KV key is ever built from unvalidated input on any path.
- **404 responses are JSON `{error:"not_found"}`** — matching the endpoint's JSON error vocabulary while keeping foreign/absent byte-identical.
## Deviations from Plan ## Deviations from Plan
### Auto-fixed Issues ### Auto-fixed Issues
**1. [Rule 3 - Blocking] RP2 gate re-pin (file not in `files_modified`)** **1. [Rule 3 - Blocking gate] RP2 re-pin in `bot/test/route-privacy.test.mjs`**
- **Found during:** task 2 - **Found during:** task 2 (planning — the conflict was visible at read-first time)
- **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. - **Issue:** the plan adds `@araharah/routing` to bot and imports `decodePolyline6` in `webhook.ts`, but `route-privacy.test.mjs` RP2 (Phase 5, 05-05) banned EVERY `@araharah/routing` specifier in bot/src — the plan's `files_modified` omitted the gate file, and GREEN would have shipped with a red suite.
- **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). - **Fix:** re-pinned RP2 to an explicit allowlist (exactly `@araharah/routing/src/polyline.ts`), shipping in the same commit as the import, per the plan's own gate-re-pin law and PATTERNS' explicit assignment of this re-pin to "the plan adding the routing import". Header rewritten to state the narrowed premise.
- **Files modified:** bot/test/route-privacy.test.mjs - **Files modified:** bot/test/route-privacy.test.mjs
- **Commit:** 7875ed4 - **Commit:** 7875ed4
**2. [Rule 1 - Bug] GW test write-count baselines corrected at GREEN** **2. [Rule 1 - Bug] PG1 caught a split-line `expirationTtl` in the task 1 GREEN draft**
- **Found during:** task 2 GREEN - **Found during:** task 1 GREEN verification
- **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. - **Issue:** the mint `put` wrapped `expirationTtl` onto its own line — legal TS, illegal under the same-line TTL law PG1 enforces (79 passed, 1 failed).
- **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). - **Fix:** single-line put with same-line `expirationTtl: GUIDANCE_SESSION_TTL_SECONDS`.
- **Files modified:** bot/src/kv/guidance-sessions.ts
- **Commit:** c87dc7d (fixed before the commit landed)
**3. [Rule 1 - Bug] GW3/GW4/GW5 counted the claim-mint's session writes as guidance writes**
- **Found during:** task 2 GREEN verification
- **Issue:** the tests mint claims through real `mintSession` (2 TTL writes) into the same fake KV, then asserted absolute `ttlLog` lengths — off by the claim-mint writes.
- **Fix:** snapshot `writesBefore` after claim minting; assert deltas.
- **Files modified:** bot/test/webhook.test.mjs - **Files modified:** bot/test/webhook.test.mjs
- **Commit:** 7875ed4 - **Commit:** 7875ed4 (fixed before the commit landed)
### Session Continuation Note ### 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. This plan's first executor session landed all four task commits plus this SUMMARY (371b2a0), but its final working-tree writes flushed late — the continuation agent observed the tree settling between checks (first `git status` showed a clean bot/ tree; the implementation, dep link, and re-pin appeared in stages). The continuation agent verified the flushed state end-to-end rather than re-executing: all four commits present with correct RED→GREEN interleaving, 90/90 green, typecheck exit 0, no deletions in any plan commit, single allowlisted routing specifier, no untracked residue, and the acceptance greps re-run post-flush. This SUMMARY is the merged record of both passes; no work was redone or lost.
## Known Stubs ## 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. None — every exported function is fully wired: the service writes/reads/deletes real KV records; the endpoints dispatch real traffic through the service. The cron consumer (plan 07) and the web POST caller (06-xx) are future consumers of THIS plan's surface, by design — not stubs.
## Threat Surface Scan ## 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. No new surface beyond the plan's `<threat_model>`: the three register entries (T-06-02 claim-only mint + unguessable ids; T-06-02b resolve-then-own 404s; T-06-04 category-only logs + TTL'd writes + active delete) are all implemented with gate coverage (GW4/GW7/GW10/GW1/GW3/GW5, GS1/GS4). The DoS row's 8192 cap with the pre/post-read double check is pinned by GW3. One boundary note, already gate-covered: `webhook.ts` now imports exactly one routing-workspace module (the pure polyline6 codec) — RP2's allowlist is the standing control, RP1's no-route-proxy ban is unchanged. No threat flags.
## Issues Encountered ## 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. - Pre-existing modified `.planning/**` files in the working tree (orchestrator-owned: PROJECT/REQUIREMENTS/ROADMAP/STATE/config + 06-01/06-07 PLANs) were left untouched and excluded from every commit. STATE.md and ROADMAP.md were not written (orchestrator-owned per dispatch).
- 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). - Node lives under nvm in this environment — verification commands sourced `~/.nvm/nvm.sh` first (the 05-01/05-03 precedent). No project impact.
## User Setup Required ## 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. None — no new bindings, no new Env fields, no deploy in this plan (wave 3/4 or close-out owns deploy). The endpoints reuse the existing `chat_sessions` KV namespace; the `@araharah/routing` link is a workspace-internal dependency resolved by `npm install`.
## Next Phase Readiness ## 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. - **Plan 06 (web flows):** POST the envelope on minimize (D-11 "envelope write timing" discretion honored — mint is one endpoint call); DELETE on trip end (D-14's client half now has its server counterpart).
- **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. - **Plan 07 (cron):** reads are defensively validated (`getGuidanceSession` null-classes); `guidance:<id>` keys + 7200 s TTL bound the scan surface; the cron NEVER needs to trust record shape.
- **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. - **GE gates:** GE1/GE2/GE3 remain the frozen-shape tripwire; GE5/GE6 hold until a plan legitimately adds a binding class or Env field; GE7 grows teeth with each new consumer import automatically.
- **RP2:** the Worker-side router plan owns the next allowlist widening.
--- ---
*Phase: 06-turn-by-turn-guidance-live-driving* *Phase: 06-turn-by-turn-guidance-live-driving*
@@ -163,8 +176,8 @@ None — no new secrets, no new wrangler bindings, no external services. The wor
## Self-Check: PASSED ## Self-Check: PASSED
- All 8 modified files + the SUMMARY verified present on disk. - All 6 modified bot files + package-lock.json verified in the four plan commits (git log + diff-filter=D count 0 — no accidental deletions).
- 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 + GS1–GS5 + GW1–GW11 + PG1/PG2 + RP1/RP2 + all 74 pre-existing). `npm run bot:typecheck`: exit 0.
- `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 verified: `expirationTtl: GUIDANCE_SESSION_TTL_SECONDS` in guidance-sessions.ts (×1 write + docs); `MAX_GUIDANCE_BODY_CHARS = 8192` in webhook.ts; all guidance `console.` lines are literal category strings (`\[guidance\] rejected: \w+`), zero `${…}` interpolation; exactly one `@araharah/routing` specifier in bot/src (the allowlisted codec).
- 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). - Injection probe (GW4): body `chat_id: 999` stored as claim's `101`.
- No deletions in any plan commit; no untracked non-planning files; STATE.md / ROADMAP.md / REQUIREMENTS.md untouched. - STATE.md and ROADMAP.md untouched (orchestrator-owned writes).