feat(05-03): freeze guidance_sessions envelope as types-only contract (GREEN)

- GuidanceSessionEnvelope: EXACTLY id, chat_id, route_snapshot, created_at
  (SessionClaim field-freeze law — a 5th property means drift)
- GuidanceRouteSnapshot INLINE (D-07: one key, one TTL, ~2h): destination
  id/name, distanceM/durationS, polyline6 geometry (05-01 storage format),
  startedAtSec resume anchor
- GUIDANCE_SESSION_TTL_SECONDS = 7200 — every Phase 6 write carries it
- guidance:<id> key-scheme doc header; zero runtime behavior (no binding,
  no function, no import — GE4 pins it)

Note: header says 'No KV binding' rather than naming the KV type because
the acceptance gate requires that token to appear NOWHERE in the file,
comments included — same law, gate-clean wording.
This commit is contained in:
araharah-executor
2026-10-24 05:09:27 +08:00
parent 0b8671e9ca
commit d5c2bc0938
+58
View File
@@ -0,0 +1,58 @@
/*
* guidance-sessions.ts — the FROZEN Phase 6 contract, types-only (D-06/D-07).
* ------------------------------------------------------------------------
* THIS MODULE SHIPS NO BEHAVIOR. No KV binding, no Env field, no wrangler
* namespace, no runtime function — the envelope is designed here (ROADMAP
* §Phase 5 GUID-05/06 upstream obligation) so Phase 6 builds its service
* on a frozen shape. Phase 6's service owns every write; every write
* carries expirationTtl: GUIDANCE_SESSION_TTL_SECONDS (~2h) and the
* snapshot is INLINE (one key, one TTL) — nothing outlives 2h, nothing is
* reconstructable after expiry (D-07, the location-pin TTL precedent).
*
* Key scheme: guidance:<id> → JSON GuidanceSessionEnvelope
*/
/** ~2h (D-06) — the envelope's every future write carries THIS value as its
* expirationTtl (Phase 6's privacy grep gate extends the TTL-law to it). */
export const GUIDANCE_SESSION_TTL_SECONDS = 7200;
/**
* The route snapshot — INLINE in the envelope (D-07): one KV key, one TTL.
* Nothing outlives 2h; nothing is reconstructable after expiry. Scoped to
* what a chat turn-card needs (GUID-05): the selected route's headline data
* + its geometry for the map snapshot render + the trip-start anchor for
* progress math on resume (GUID-06).
*/
export interface GuidanceRouteSnapshot {
/** The gazetteer id of the trip's destination. */
destinationId: string;
/** The destination's display name (verbatim gazetteer name). */
destinationName: string;
/** Selected route summary (meters / seconds). */
distanceM: number;
durationS: number;
/** The SELECTED route's geometry, polyline6-encoded — the routing
* workspace's storage format (05-01), never a re-serialized array. */
geometry: string;
/** Trip start, seconds-since-epoch — the resume-progress anchor (GUID-06). */
startedAtSec: number;
}
/**
* The guidance_sessions envelope — Phase 6's frozen contract (D-06).
* EXACTLY these four fields: a 5th property means drift (the SessionClaim
* law, bot/src/kv/sessions.ts).
*
* Key scheme: guidance:<id> → JSON GuidanceSessionEnvelope
* Every write MUST carry expirationTtl: GUIDANCE_SESSION_TTL_SECONDS.
*/
export interface GuidanceSessionEnvelope {
/** Opaque session id — crypto.randomUUID() at Phase 6 mint (T-02-05 precedent). */
id: string;
/** The Telegram chat the turn-cards push to (GUID-05). */
chat_id: number;
/** The INLINE route snapshot (D-07) — never a reference or a second key. */
route_snapshot: GuidanceRouteSnapshot;
/** Envelope creation, seconds-since-epoch. */
created_at: number;
}