Files
araharah-executor c96f186da6 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
2026-10-24 13:10:18 +08:00

16 KiB
Raw Permalink Blame History

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
kv
guidance-sessions
envelope-service
ttl
api-endpoints
bearer-claim
privacy
tdd
gate-repin
phase plan provides
05-routing-route-display 03 the FROZEN GuidanceSessionEnvelope/GuidanceRouteSnapshot shapes + GUIDANCE_SESSION_TTL_SECONDS + the guidance:<id> key scheme (implemented, never redefined — GE1/GE2/GE3 unchanged)
phase plan provides
05-routing-route-display 01 the polyline6 codec (decodePolyline6) the Worker now validates snapshot geometry with
phase provides
02 the Bearer-claim precedent (handleLocation posture), the WR-01 pre/post-read body-cap pattern, getClaimByToken
mintGuidanceSession / getGuidanceSession / deleteGuidanceSession — the TTL-only KV service over the frozen envelope (chat_sessions binding, guidance:<id> keys, 7200 s)
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
GET /api/guidance?id=<uuid> — claim-owned envelope read; foreign/absent indistinguishable 404 (T-06-02b)
DELETE /api/guidance?id=<uuid> — D-14's server half: the active trip-end KV sweep
RP2 re-pin — @araharah/routing allowlist in bot/src: exactly the polyline6 codec
06 web guidance flows (POST on minimize)
plan-07 cron (defensive reads + turn-card push)
D-14 trip-end flow
added patterns
@araharah/routing:* (bot workspace dep — first routing import into the Worker, allowlisted to the codec)
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)
UUID key-guard before any KV touch (V5: a malformed id never becomes arbitrary-key probing)
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)
created modified
bot/src/kv/guidance-sessions.ts
bot/src/webhook.ts
bot/package.json
bot/test/guidance-envelope.test.mjs
bot/test/webhook.test.mjs
bot/test/privacy.test.mjs
bot/test/route-privacy.test.mjs
package-lock.json
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
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
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
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
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
GUID-05
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 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 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.
  • 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.
  • 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 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

  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)
    • 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
    • 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)

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; 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 dispatch, Bearer claim, WR-01 double check, mint/resolve-own/delete) + validateSnapshot + category-only response helpers; header route table updated.
  • bot/package.json + root package-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 real mintSession path.
  • 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-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.
  • 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.
  • 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/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 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 put wrapped expirationTtl onto 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 absolute ttlLog lengths — off by the claim-mint writes.
  • Fix: snapshot writesBefore after claim minting; assert deltas.
  • Files modified: bot/test/webhook.test.mjs
  • Commit: 7875ed4 (fixed before the commit landed)

Session Continuation Note

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

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.sh first (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 (getGuidanceSession null-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: the Worker-side router plan 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_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).
  • Injection probe (GW4): body chat_id: 999 stored as claim's 101.
  • STATE.md and ROADMAP.md untouched (orchestrator-owned writes).