refactor(session): drop the dead mutable SessionSummary

SessionSummary (updatedAt/title/firstPrompt) and SessionPersistence.update()
were dead state: zero production callers of update(), no production reader of
updatedAt/firstPrompt, and ACP's title comes from a tool-call presenter, not
storage. The live Session.header was already typed SessionHeader, so the
summary only ever existed in the persistence layer, written and read by nothing
but its own contract test.

Delete it entirely (no SessionMeta alias — SessionMeta collapses to
SessionHeader everywhere). This removes the JSONL .summary.json sidecar
machinery, the SQLite title/first_prompt/updated_at columns and per-append
updated_at bump, and the update() method from the abstract service and both
backends. SQLite SCHEMA_VERSION goes 1->2 and openDatabase now rejects any
non-current user_version (older or newer) — no migration, unreleased software.

Net -400 lines, and it erases the JSONL-sidecar-vs-SQLite-column durability
divergence that the upcoming write coordinator would otherwise have to model.

Records the decision in docs/rfc/implemented/2026-06-19-drop-mutable-session-summary.md
and migrates the 2026-06-14 session-persistence RFC's facts to current truth.
Adds a standalone AGENTS.md section "Tests document behavior, not golden truth"
(a passing test pins current behavior, not necessarily correct behavior) with
the summary-drop as its worked example, and reinforces the no-migration
pre-release stance.
This commit is contained in:
Tianyi Cui
2026-06-20 01:03:57 +08:00
parent 0561fb47b6
commit 815bac7de9
20 changed files with 163 additions and 518 deletions

View File

@@ -31,9 +31,7 @@ Plain class (not a Cordis Service). Create via `ctx.sessions.create()`.
### Metadata types (`types.ts`)
- `SessionHeader` — immutable, written once: `{ version, id, createdAt, cwd?, parentSession? }`.
- `SessionSummary` — mutable, updateable without touching the log: `{ updatedAt, title?, firstPrompt? }`.
- `SessionMeta = SessionHeader & SessionSummary` — owned here (beside `SessionId`) because `Session.header` is typed by it; persistence backends re-export these rather than own them (which would force a package cycle).
- `SessionHeader` — immutable session metadata, written once: `{ version, id, createdAt, cwd?, parentSession? }`. Owned here (beside `SessionId`) because `Session.header` is typed by it; persistence backends re-export it rather than own it (which would force a package cycle).
### Session event vocabulary (`types.ts`)
@@ -45,7 +43,7 @@ Also defines `TurnTriggerMap` and `TurnEndReasonMap` (merge-extensible sum types
### Extension points
- Persistence plugins: subscribe to `session/event` (write-behind) and drain on `session/flush` (awaited) and fiber dispose. A durable backend reads the log and reloads it into a live session; the metadata seam (`SessionHeader`/`SessionSummary`/`SessionMeta`, `session.header`) is what such a backend stores beside the log.
- Persistence plugins: subscribe to `session/event` (write-behind) and drain on `session/flush` (awaited) and fiber dispose. A durable backend reads the log and reloads it into a live session; the metadata seam (`SessionHeader`, `session.header`) is what such a backend stores beside the log.
- Replay/fork: `ctx.sessions.create(id, { seed })` seeds a new session with an existing event log.
### What is NOT here (TODO)

View File

@@ -30,29 +30,6 @@ export interface SessionHeader {
parentSession?: SessionId
}
/**
* Mutable session metadata — updateable without touching the append-only log.
* A persistence backend stores this beside the log (a sidecar file, a header
* row) and rewrites only it on update.
*/
export interface SessionSummary {
/** Unix epoch milliseconds of the last mutation (event append or update). */
updatedAt: number
/** Human-facing title (derived/edited), if any. */
title?: string
/** The first user prompt, cached for listing previews. */
firstPrompt?: string
}
/**
* Full session metadata: the immutable {@link SessionHeader} merged with the
* mutable {@link SessionSummary}. Owned here in `dsh-session` (beside
* {@link SessionId}) because `Session.header` is typed by it; the persistence
* package imports/re-exports these rather than owning them, which would force
* a package cycle.
*/
export type SessionMeta = SessionHeader & SessionSummary
/**
* Options for creating a {@link Session} via the store. `seed` replays/forks
* an existing event log; `meta` carries the caller-supplied storage fields the