docs(control-plane): document versioned session_checkpoints schema (#660)

Adds the session_checkpoints section to the control-plane DB substrate
architecture doc: schema version and row identity, the column groups, the
public API surface, and the hard rules (redaction at write, reconcile
rather than blind restore, unknown live state is not a mismatch, drain
fails closed on an incomplete checkpoint, no transcript storage).

Satisfies acceptance criterion 1 of #660 ("Schema documented and
versioned") for the implementation added in 7e18dcc.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-24 14:57:44 -05:00
co-authored by Claude Opus 4.8
parent 87c30484aa
commit 42657b3b65
@@ -14,12 +14,13 @@
| Capability | Notes | | Capability | Notes |
|------------|--------| |------------|--------|
| Schema | `sessions`, `work_items`, `leases`, `assignments`, `terminal_locks`, `events`, `incident_links` | | Schema | `sessions`, `work_items`, `leases`, `assignments`, `terminal_locks`, `events`, `incident_links`, `session_checkpoints` |
| Atomic assign+lease | `ControlPlaneDB.assign_and_lease` — one `BEGIN IMMEDIATE` transaction | | Atomic assign+lease | `ControlPlaneDB.assign_and_lease` — one `BEGIN IMMEDIATE` transaction |
| Mutation gate | `require_valid_assignment` — live lease + allowed action + non-terminal work + non-stale head | | Mutation gate | `require_valid_assignment` — live lease + allowed action + non-terminal work + non-stale head |
| Heartbeat / release / expire | Lease lifecycle helpers | | Heartbeat / release / expire | Lease lifecycle helpers |
| Terminal-lock index | Routing signal for #600 (terminal path first) | | Terminal-lock index | Routing signal for #600 (terminal path first) |
| `incident_links` | Provider-neutral link model for #612**not** assignable work; scope keys NULL-safe | | `incident_links` | Provider-neutral link model for #612**not** assignable work; scope keys NULL-safe |
| `session_checkpoints` | Durable session resume state for #660 — redacted at write, reconciled (never restored) on boot |
## Hard rules (enforced in code) ## Hard rules (enforced in code)
@@ -92,6 +93,44 @@ Module: `incident_bridge.py` · MCP tools: `gitea_observability_*`
- Provider tokens never appear in issue bodies, links, or tool results. - Provider tokens never appear in issue bodies, links, or tool results.
- Allocator sees bridge work only after a Gitea issue exists. - Allocator sees bridge work only after a Gitea issue exists.
## Session checkpoints (#660)
Module: `control_plane_db.py` · Table: `session_checkpoints` · Parent **#655** · Soft-depends **#659** · Vision **#652** · Roadmap **#653**
Workflow state that lived only in MCP process memory or chat did not survive a restart, so session identity, stage, lease ownership, and the next valid action had to be reconstructed by hand. The `session_checkpoints` table makes that state durable.
### Schema version
Rows carry `checkpoint_schema_version` (currently **5**, tracking the module-level `SCHEMA_VERSION`), so a reader can tell which field set a record was written under. The row identity is
`UNIQUE (remote, org, repo, session_id, work_kind, work_number)` — one current-state row per session per work unit, upserted rather than appended. A session-level checkpoint that is not bound to an issue or PR uses the `work_number = 0` sentinel with an empty `work_kind`.
| Group | Columns |
|-------|---------|
| Identity | `session_id`, `provider_identity`, `role`, `remote`/`org`/`repo` |
| Work unit | `work_kind` (`issue`/`pr`/empty), `work_number`, `worktree_path`, `branch`, `head_sha` |
| Ownership | `capabilities` (JSON), `lease_id`, `assignment_id` |
| Progress | `workflow_stage`, `last_completed_action`, `current_operation`, `pending_mutation` (JSON) |
| Recovery | `evidence` (JSON), `blocker`, `next_valid_action`, `recovery_instructions` |
| Bookkeeping | `checkpoint_schema_version`, `status`, `created_at`, `updated_at` |
### API
| Method | Purpose |
|--------|---------|
| `write_session_checkpoint` | Upsert the current-state row; redacts, and optionally enforces drain completeness |
| `get_session_checkpoint` | Read one checkpoint by session + work unit |
| `list_session_checkpoints` | List checkpoints by session or by work unit |
| `reconcile_session_checkpoint` | Pure diagnosis of a stored checkpoint against live head/lease state |
| `checkpoint_completeness` | Pure — the drain-required fields missing from a record |
### Hard rules
1. **Redaction at write.** Free-text columns are redaction-filtered and JSON columns are redacted recursively before storage, so a pasted token or credential URL cannot land in a checkpoint. Secrets are never stored (#660 AC4).
2. **Reconcile, never blind-restore.** `reconcile_session_checkpoint` returns a diagnosis (`stale`, `head_mismatch`, `lease_mismatch`, `reconcile_action`) and the caller decides. A stored head that no longer matches live Git, or a lease that is gone or reassigned, marks the checkpoint stale (#660 AC3).
3. **Unknown live state is not a mismatch.** A `None` live input means "not checked" and never flags staleness on its own.
4. **Drain fails closed.** A write with `require_complete=True` refuses when any of `session_id`, `role`, `workflow_stage`, `next_valid_action`, `recovery_instructions` is missing or blank, so drain cannot complete on a checkpoint that could not resume.
5. **No transcript storage.** Checkpoints hold resume state, not conversation history.
## Non-goals (intentionally deferred) ## Non-goals (intentionally deferred)
- Full unsupervised watchdog auto-filing (prefer explicit reconcile first) - Full unsupervised watchdog auto-filing (prefer explicit reconcile first)