- 2/2 tasks TDD RED->GREEN: envelope service (0cfa0bd/c87dc7d) + /api/guidance endpoints (212aa57/7875ed4) - GE4-GE7 + RP2 re-pinned with their invalidating changes; GE1-GE3 frozen - 90/90 bot tests green; typecheck clean
15 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | duration | completed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-turn-by-turn-guidance-live-driving | 03 | bot |
|
|
|
|
|
|
|
|
|
12min | 2026-10-24 |
Phase 6 Plan 3: Guidance Envelope Service + /api/guidance Endpoints Summary
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
- Duration: 12 min
- Started: 2026-10-24T04:54:57Z
- Completed: 2026-10-24T05:06:41Z
- Tasks: 2/2 (both TDD RED→GREEN)
- Files modified: 8 (6 bot workspace + root package-lock.json; zero files created — the service grew inside the frozen contract module)
Accomplishments
- The envelope service exists (task 1):
mintGuidanceSession(crypto.randomUUID id, claim-derived chat_id argument, ONE put atguidance:<id>with same-lineexpirationTtl: 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 endpoints are live (task 2):
POST /api/guidance(WR-01 cap atMAX_GUIDANCE_BODY_CHARS = 8192with the pre/post-read double check; hand-rolled validation — slugdestinationIdper the gazetteer's ownENTRY_ID_PATTERN, bounded name, finite non-negative metrics, integerstartedAtSec, geometry thatdecodePolyline6decodes to ≥ 2 points) → 201{id};GET/DELETEresolve-then-own (envelope.chat_id === claim.chat_idelse an indistinguishable 404); other methods 405. - The claim-only identity law is proven, not asserted (T-06-02): GW4's injection probe posts
chat_id: 999in the body alongside the claim's101— 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. - 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_sessionsbinding. - PG1 now covers the new write path (
kv/guidance-sessions.tsin storageModules) — and proved itself immediately: it FAILED my first GREEN draft because theputhadexpirationTtlon 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:typecheckexit 0.
Task Commits
- task 1: envelope service + GE4–GE7 re-pins + PG1
0cfa0bdtest(06-03): re-pin GE4/GE7 to the live service + behavioral envelope suite + PG1 storage list (RED)c87dc7dfeat(06-03): guidance envelope service — TTL-only mint/get/delete beneath the frozen shape (GREEN)
- task 2: /api/guidance POST + GET + DELETE
212aa57test(06-03): guidance endpoint suite GW1-GW11 (RED)7875ed4feat(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
- 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 1 GREEN
c87dc7d: 79/79 (PG1 caught the split-line TTL in the draft — fixed pre-commit). - 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
bot/src/kv/guidance-sessions.ts— header rewritten to the shape-frozen/service-owns-every-write law;GuidanceSessionKvseam (structural — no KvNamespace type token, no new binding), UUID pattern, exact-key constants, mint/get/delete +isEnvelopeValid.bot/src/webhook.ts—/api/guidanceroute arm +handleGuidance(method dispatch, Bearer claim, WR-01 double check, mint/resolve-own/delete) +validateSnapshot+ category-only response helpers; header route table updated.bot/package.json+ rootpackage-lock.json—@araharah/routing: "*"workspace dep (linked via npm install).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/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 realmintSessionpath.bot/test/privacy.test.mjs— PG1 storageModules +=kv/guidance-sessions.ts.bot/test/route-privacy.test.mjs— RP2 re-pinned to the codec allowlist; RP1 unchanged.
Decisions Made
- 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-sessionsimport 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. - 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-router(06-01) widens the allowlist under its own plan. RP1 (no/api/routeproxy) untouched — the guidance path name can't collide. - 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
Auto-fixed Issues
1. [Rule 3 - Blocking gate] RP2 re-pin in bot/test/route-privacy.test.mjs
- Found during: task 2 (planning — the conflict was visible at read-first time)
- Issue: the plan adds
@araharah/routingto bot and importsdecodePolyline6inwebhook.ts, butroute-privacy.test.mjsRP2 (Phase 5, 05-05) banned EVERY@araharah/routingspecifier in bot/src — the plan'sfiles_modifiedomitted the gate file, and GREEN would have shipped with a red suite. - 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
- Commit:
7875ed4
2. [Rule 1 - Bug] PG1 caught a split-line expirationTtl in the task 1 GREEN draft
- Found during: task 1 GREEN verification
- Issue: the mint
putwrappedexpirationTtlonto its own line — legal TS, illegal under the same-line TTL law PG1 enforces (79 passed, 1 failed). - 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 absolutettlLoglengths — off by the claim-mint writes. - Fix: snapshot
writesBeforeafter claim minting; assert deltas. - Files modified: bot/test/webhook.test.mjs
- Commit:
7875ed4(fixed before the commit landed)
Known Stubs
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
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
- 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). - Node lives under nvm in this environment — verification commands sourced
~/.nvm/nvm.shfirst (the 05-01/05-03 precedent). No project impact.
User Setup Required
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
- 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).
- Plan 07 (cron): reads are defensively validated (
getGuidanceSessionnull-classes);guidance:<id>keys + 7200 s TTL bound the scan surface; the cron NEVER needs to trust record shape. - 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: engine A's worker-router plan (06-01) owns the next allowlist widening.
Phase: 06-turn-by-turn-guidance-live-driving Completed: 2026-10-24
Self-Check: PASSED
- All 6 modified bot files + package-lock.json verified in the four plan commits (git log + diff-filter=D count 0 — no accidental deletions).
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.- Acceptance greps verified:
expirationTtl: GUIDANCE_SESSION_TTL_SECONDSin guidance-sessions.ts (×1 write + docs);MAX_GUIDANCE_BODY_CHARS = 8192in webhook.ts; all guidanceconsole.lines are literal category strings (\[guidance\] rejected: \w+), zero${…}interpolation; exactly one@araharah/routingspecifier in bot/src (the allowlisted codec). - Injection probe (GW4): body
chat_id: 999stored as claim's101. - STATE.md and ROADMAP.md untouched (orchestrator-owned writes).