docs(05-03): complete guidance-session contract freeze plan

This commit is contained in:
araharah-executor
2026-10-24 05:10:36 +08:00
parent d5c2bc0938
commit 48627d4102
@@ -0,0 +1,131 @@
---
phase: 05-routing-route-display
plan: 03
subsystem: bot
tags: [kv, types-only-contract, guidance-sessions, ttl, envelope, privacy, tdd, source-introspection]
# Dependency graph
requires:
- phase: 05-routing-route-display
plan: 01
provides: the polyline6 storage format the snapshot's geometry field cites by name (routing workspace, 05-01) and the RouteResult summary fields (distanceM/durationS) the snapshot headlines
- phase: 03-mini-app-shell-geolocation
provides: the SessionClaim field-freeze law (bot/src/kv/sessions.ts "a 5th property means drift") and the location-pin TTL precedent (bot/src/kv/location-pin.ts) D-07 aligns with
provides:
- "GuidanceSessionEnvelope — the frozen Phase 6 contract: EXACTLY [id, chat_id, route_snapshot, created_at]"
- "GuidanceRouteSnapshot — the INLINE route record (D-07): destinationId/Name, distanceM/durationS, polyline6 geometry, startedAtSec"
- "GUIDANCE_SESSION_TTL_SECONDS = 7200 — the ~2h TTL as a documented constant awaiting its Phase 6 consumer"
- "guidance:<id> key-scheme documentation in the module header"
- "GE1–GE7 — seven mechanical gates pinning the types-only boundary (field freeze, inline snapshot, TTL value, no runtime behavior, no new namespace, no Env field, no premature consumer)"
affects: [phase-6 guidance turn-card service, bot privacy gates]
# Tech tracking
tech-stack:
added: [] # zero dependencies — a types-only module
patterns:
- "Types-only frozen contract: interface + TTL constant + key-scheme doc, with source-introspection gates proving no runtime shadow (extends the privacy.test.mjs grep-gate family)"
- "Negative-space boundary gates: guards over files that must NOT change are green by construction and redden only on drift (05-01 C4/C5/C6 precedent)"
key-files:
created:
- bot/src/kv/guidance-sessions.ts
- bot/test/guidance-envelope.test.mjs
modified: [] # zero existing files touched
key-decisions:
- "Module header says 'No KV binding' instead of naming the KVNamespace type — the plan's own acceptance gate requires that token to appear NOWHERE in the file, comments included (grep -rc = 0); same law, gate-clean wording"
- "GE5–GE7 (no new namespace / no Env field / no consumer) are RED-green-by-construction boundary guards: they scan files that must NOT change, so they pass before and after this plan and redden only on future drift — documented in the suite header per the 05-01 C4/C5/C6 precedent"
patterns-established:
- "Contract-freeze gate naming: GE* gates live in the consuming workspace's test dir and lazy-import the contract module so a missing module is a per-case RED, not a suite load failure"
- "Phase-6 handoff note in gate doc-comments: when the service legitimately adds the binding/field/consumer, updating GE4–GE7 is THAT plan's responsibility"
requirements-completed: [] # plan frontmatter requirements: [] — the GUID-05/06 upstream obligation, not a tracked requirement ID
# Metrics
duration: 5min
completed: 2026-10-23
---
# Phase 5 Plan 3: Guidance-Session Contract Freeze (Types-Only) Summary
**The `guidance_sessions` envelope frozen as a types-only Phase 6 contract — 4-field envelope + inline 6-field route snapshot (D-07: one key, one TTL) + the 7200 s TTL constant + `guidance:<id>` key scheme, with zero runtime behavior and seven GE gates proving the boundary mechanically.**
## Performance
- **Duration:** 5 min
- **Started:** 2026-10-23T21:05:12Z
- **Completed:** 2026-10-23T21:09:35Z
- **Tasks:** 1/1 (TDD RED→GREEN)
- **Files modified:** 2 (both created; zero existing files touched)
## Accomplishments
- **The Phase 6 contract exists in code** (D-06): `GuidanceSessionEnvelope` (EXACTLY `id`, `chat_id`, `route_snapshot`, `created_at` — the SessionClaim field-freeze law) and `GuidanceRouteSnapshot` (EXACTLY `destinationId`, `destinationName`, `distanceM`, `durationS`, `geometry`, `startedAtSec`), the `guidance:<id>` key scheme, and `GUIDANCE_SESSION_TTL_SECONDS = 7200` — pinned by GE1/GE2 so drift reddens the suite.
- **D-07 is mechanical, not aspirational**: `route_snapshot: GuidanceRouteSnapshot` is typed INLINE (GE3), the tokens `snapshotRef`/`snapshot_ref` are banned across all of `bot/src`, and the TTL law means one key, one expiry — nothing outlives 2h, nothing is reconstructable after expiry.
- **The types-only boundary cannot leak this phase** (T-05-08): GE4 comment-strips the module and bans every behavior token (KV binding type, `.put(`, `.get(`, `.delete(`, function exports, imports); GE5 pins wrangler.jsonc to EXACTLY ONE kv namespace (`chat_sessions`); GE6 pins `Env` free of any guidance field; GE7 bans any `bot/src` import of the module — no runtime shadow, no premature consumer.
- **Zero existing bot files modified**: `wrangler.jsonc` and `config.ts` are untouched; the full bot suite is green at 72/72 (65 pre-existing + 7 GE gates) and `bot:typecheck` exits 0 (the module sits inside the bot tsconfig's `include: ["src"]`).
## Task Commits
Each gate of the TDD cycle was committed atomically:
1. **task 1: the frozen envelope types + the types-only gate suite**
- `0b8671e` test(05-03): add failing guidance-envelope types-only gates (GE1-GE7) (RED)
- `d5c2bc0` feat(05-03): freeze guidance_sessions envelope as types-only contract (GREEN)
**Plan metadata:** committed separately (docs, force-added — `.planning/` is gitignored)
## TDD Gate Compliance
- RED gate: `0b8671e` — GE1–GE4 fail on the absent module (ERR_MODULE_NOT_FOUND / ENOENT, verified as the exact failure modes); GE5–GE7 pass as documented green-by-construction boundary guards.
- GREEN gate: `d5c2bc0` — 72/72 pass, typecheck exit 0.
- REFACTOR gate: not needed — 58 lines of pure types + one constant; nothing to clean.
## Files Created/Modified
- `bot/src/kv/guidance-sessions.ts` — the frozen contract: module header (types-only law, D-06/D-07 citations, `guidance:<id>` key scheme), `GUIDANCE_SESSION_TTL_SECONDS = 7200`, `GuidanceRouteSnapshot` (6 fields), `GuidanceSessionEnvelope` (4 fields). No imports, no functions, no class.
- `bot/test/guidance-envelope.test.mjs` — GE1–GE7: TTL constant value; interface-body field-freeze regex (`^\s{2}\w+:` between `interface X {` and `\n}`); inline-snapshot type + repo-wide ref-token ban; comment-stripped behavior-token ban (the routing C4 `stripComments` pattern); comment-stripped JSON.parse of wrangler.jsonc; config.ts token ban; import-statement ban across the recursive `bot/src` tree.
## Decisions Made
- **Header wording: "No KV binding" instead of the KVNamespace type name** — the plan's `<interfaces>` header template contains that literal token, but the plan's own acceptance criterion requires `grep -rc 'KvNamespace'` to return 0 with "the word never appears" (comments included). The mechanical gate wins; the law stated is identical.
- **GE5–GE7 declared green-by-construction in the RED commit** — they are negative-space guards over files that must NOT change, so they cannot fail while the boundary holds (their RED state is vacuous). Mirrors 05-01's C4/C5/C6 zero-laws precedent; documented in both the suite header and the RED commit message.
## Deviations from Plan
None — plan executed exactly as written. The two items above are judgment calls inside the plan's own acceptance gates, documented for the verifier.
## Known Stubs
None — the module ships types + one constant by design (D-06); nothing flows to UI, nothing consumes it at runtime (GE7 pins that). Phase 6 builds the service on this frozen shape.
## Threat Surface Scan
No new surface beyond the plan's `<threat_model>`: no endpoints, no auth paths, no file access, no schema at a trust boundary — the module has zero runtime behavior. All three register entries have implemented, gate-covered mitigations: T-05-08 (premature persistence → GE4/GE5/GE6/GE7), T-05-08b (retention → GE1 + GE3 inline snapshot), T-05-08c (contract drift → GE2 exact field sets). No threat flags.
## Issues Encountered
- Pre-existing uncommitted working-tree changes to `bot/test/cari.test.mjs` and `bot/test/commands.test.mjs` (a prior session's register-commands scope-shadow fix pins) were present at start. Baseline verified green WITH them (65/65) before any of my commits; they were left untouched and excluded from every commit. Out of scope per the plan-boundary rule.
- Node lives under nvm in this environment — verification commands sourced `~/.nvm/nvm.sh` first (the 05-01 precedent). No project impact.
## User Setup Required
None — no external service configuration required. The module ships no behavior; nothing deploys.
## Next Phase Readiness
- **Phase 6 (backgrounded turn-card service)**: consumes `GuidanceSessionEnvelope` / `GuidanceRouteSnapshot` / `GUIDANCE_SESSION_TTL_SECONDS` / the `guidance:<id>` key scheme verbatim — the shape cannot have drifted (GE1/GE2 pin it). Phase 6's plan owns: the KV namespace binding + Env field (relaxing GE5/GE6), the consumer import (relaxing GE7), and extending the PG1 same-line-`expirationTtl` grep gate to `guidance:` writes.
- **Phase 5 remaining plans**: none depend on this module (depends_on: [], wave: 1); 05-04/05-05 web-side work is unaffected.
---
*Phase: 05-routing-route-display*
*Completed: 2026-10-23*
## Self-Check: PASSED
- All 2 created files verified present on disk (guidance-sessions.ts, guidance-envelope.test.mjs).
- Both plan commits verified in git log (0b8671e RED, d5c2bc0 GREEN).
- `npm test -w bot`: 72 passed, 0 failed (PASS GE1…GE7 + all 65 pre-existing cases). `npm run bot:typecheck`: exit 0.
- All seven acceptance greps pass on the module (envelope iface, snapshot iface, TTL const, key scheme, KvNamespace count = 0, inline snapshot).
- Zero existing files modified by this plan; STATE.md and ROADMAP.md untouched (orchestrator-owned writes).