# Live status registry — design

audience: AI coding agents first.
plan: `docs/plans/2026-08-15-live-status-registry.md` (slices L1–L3 + queue mirror; L4 = WIP cap REJECTED by owner, NEVER build).
owner decision 2026-08-15: landing queue stays file-based and authoritative; its state is MIRRORED into the DB. NEVER rewrite the queue onto the DB; a collector outage MUST NEVER block a land or deploy.

## Architecture

One store, one write API, many writers, one reader surface.

- Store: the collector's existing SQLite (`collector/src/requests/requests-store.ts`) — extend, never a second DB.
- Write API: collector HTTP endpoints consumed by `od-requests` CLI verbs and by machinery hooks. All writes fail-open at every call site: timeout ≤ 800ms, failure logged to stderr, caller proceeds.
- Writers: intake/claim (exists), lander post-land path, deploy watcher, landq event mirror, liveness sweeper.
- Reader: `/requests` board cards (existing components; extend, no new primitives without approval).

## Data model (seams)

Extend `requests` rows + one new table:

```
requests: + last_state_at (iso), + landed_sha, + deployed_sha, + lost_at (iso|null)
receipt_trail (new): id, request_id, at, kind(claimed|progress|landed|deployed|failed|worker-lost|queue-event), line (owner-language, ≤120 chars), meta (json: sha, ticket, host, worker)
```

State machine on `requests.state`: `open → in_flight → landed → shipped`; `failed` and `worker-lost` are flags via trail + `lost_at`, not new states — the board renders them from the trail. Transitions are idempotent: re-stamping the same (kind, meta.sha|ticket) appends nothing.

## Components

**L1 — lifecycle API + CLI.**
- Collector endpoints: `POST /requests/:id/transition` `{kind, line?, meta?}` (validates kind, appends trail, updates row columns); `GET /requests/:id/trail`.
- `od-requests` verbs: `progress <id> <line...>`, `landed <id> --sha S`, `deployed <id> --sha S`, `failed <id> --reason R` (claim exists).
- Board card: trail's last line + timestamp on the face ("landed 4 min ago"); full trail on the card's detail view. Owner language only — no ticket ids/branch names on the face.

**L2 — machinery writes.**
- Lander: `finish-branch.sh` post-land path (the same seam that writes the delivery receipt) stamps `landed` with the merged sha, resolving the request row via the branch's claimed association (worktree/claim wiring from intake S4); no association → skip silently.
- Deploy watcher: `controller/src/deploy-watcher.ts` on a successful deploy stamps `deployed` on every request row whose `landed_sha` is an ancestor of the deployed sha and lacks `deployed_sha`.
- Both writes: fail-open, bounded timeout, never alter the land/deploy verdict.

**L3 — worker-lost sweeper.**
- Collector-side sweep (existing reconcile cadence): rows `in_flight` with worker claim → verify pid + pidStartTicks against `/proc` (same evidence rules as the session-ledger fix; remote hosts via the existing per-host probe). Dead → set `lost_at`, append `worker-lost` trail line. Card face shows "worker lost" state. A later claim/progress by a live worker clears `lost_at`.

**Queue mirror (owner decision).**
- The landq conductor (`finish-branch.sh`) already writes one JSON line per event to `.git/harness/landq/log`. A mirror tails/replays that log into the collector: each line becomes a `queue-event` trail entry on the associated request row (when resolvable) AND a row in the board's queue view (ticket, branch, gate class, queue depth, verdict). Mirror is one-directional, idempotent (keyed by ticket+event), crash-safe (persists its log offset), and its death never affects the queue. Implementation seam: a small watcher in the controller process on the existing reconcile loop — no new daemon.

## Error handling

Fail-open everywhere machinery writes; fail-visible on the board (a row that got `landed` but never `deployed` after N minutes renders its age honestly — no synthetic states). The deriver plan (`2026-08-15-plan-status-self-reconciling.md`) audits rows whose writer died; this design only writes what evidence supports — never fabricate.

## Testing

- Store/API: bun tests for transition validation, idempotent re-stamp, trail append.
- Lander stamp: finish-branch test-suite case — fake collector records `landed` with sha; collector down → land still succeeds.
- Watcher stamp: existing deploy-watcher test harness — ancestor logic both branches.
- Sweeper: kill a fake claiming pid, sweep marks worker-lost; live pid untouched.
- Mirror: replay a fixture landq log twice → no duplicate events.
- Acceptance (owner-visible, per plan): one real change walks claimed → landed → deployed on its card with no agent writing anything; a killed worker's card says "worker lost" within a sweep.

## Architecture decisions

- Queue-in-DB rewrite REJECTED (owner, 2026-08-15): mirror only. Queue files stay the operational truth.
- No new state enum values for failure modes: trail + flags, so existing board filters keep working.
- Mirror lives in the controller's existing loop, not a new daemon (deletion test: a separate daemon adds an install/health surface for zero isolation gain).
- L4 standing: WIP cap REJECTED — NEVER build, NEVER re-propose.
