/** * Durable engine-status store — the WRITE half of the catalog emission * (`dsh_status_emissions`, written by the pre-step) plus the per-session * workflow-selection CONTROL record (D4) the picker reads and commits. * * Flushing that emission no longer has a plugin-side reader: the panel renders * the store-backed `readPanelSnapshot` (`src/panel/read-panel-snapshot.ts`), and * a test that verifies the write reads the row back through the engine's own * verb (`readDshStatusEmission`). The row itself stays — the MODEL-visible * catalog is anchored by it. * * Every piece of state lives in the control-root `store.db` through the * engine's typed synchronous host-state domain operations * (`packages/engine/src/host-state-store.ts`): one transaction per operation, * SQLite's own `BEGIN IMMEDIATE` serializing concurrent writers. There is no * plugin-owned file, no directory lock, and no shared read-modify-write * envelope: * * - an emission is UPSERTed as this session's single newest row * (`dsh_status_emissions`) — the depth every reader ever consumed; * - a binding commit UPSERTs this session's row (`dsh_session_bindings`), * merging the exclusion floor by max and preserving an omitted preference. * * ADDRESSING (HARD): both records are keyed by the durable * `session.header.id` AND the authoritative cwd, so a read must be addressed * by both. A session id alone cannot address a row — the picker's reader * therefore passes the cwd it resolved SERVER-side. * * WRITE RULE (HARD): a store that cannot be written is never guessed at. The * emission returns `{kind:'degraded', reason}` (the advisory path logs it and * never aborts the step it observes); the binding commit reports its own * degraded reason. * * KEYING (HARD): the stored maps are SQL rows keyed by * `(session_id, cwd)`. The session id is caller-chosen on the host's * `session.create` (a branded string with no shape validation), but SQLite * binds it as an ordinary parameter — an id equal to an `Object.prototype` * member is a value, never a lookup of an inherited member. * * @module @mstar-harness/dsh/engine-status-store */ import { type DshSelectedGeneration, type ExecutionBinding } from '@mstar-harness/engine'; export type EngineStatusSnapshotWrite = { readonly kind: 'written'; } | { readonly kind: 'degraded'; readonly reason: string; }; /** * One write request — the emission site's payload plus its session identity. * The stored `at` is the EMISSION time: the emission site supplies the ISO time * it captured when the row was emitted, and a persist retried on a later step * repeats that same time, so the freshness footer's `at` never claims the * retry as a new emission. Omitting `emittedAt` (a direct write) lets the store * stamp its own transaction time. */ export interface EngineStatusSnapshotWriteInput { /** The session whose catalog row this payload is (`session.header.id`). */ readonly sessionId: string; /** The session workspace the payload was resolved for (`session.header.cwd`). */ readonly cwd: string; /** The agent turn the row was emitted for. */ readonly turn: number; /** The exact payload object handed to the step messages. */ readonly payload: object; /** The selected authority generation of the emission's selection, when bound. */ readonly selectedGeneration?: DshSelectedGeneration; /** * ISO time of the EMISSION this write records. A retried persist passes the * original emission time so the stored row keeps naming when the model saw * the payload; omitted means "stamp this write" (a direct store write). */ readonly emittedAt?: string; } /** * One session's durable workflow-selection CONTROL record (D4): the active * workflow this session picked plus the durable no-backfill floor. It is never * part of an emitted payload and is keyed by the durable `session.header.id`. */ export interface WorkflowSessionBinding { /** The authoritative session cwd the record was written for (exact-match anchor). */ readonly cwd: string; /** The session's chosen ACTIVE workflow id; absent = no explicit pick yet. */ readonly selectedWorkflowId?: string; readonly executionBinding?: ExecutionBinding | null; /** * Durable exclusion floor: ledger rows at or below this sequence were * intentionally skipped while the session was unbound and must never be * backfilled. An absent record means floor 0 (no exclusion history). */ readonly excludedBeforeSeq: number; } /** One binding read outcome. */ export type WorkflowSessionBindingRead = { readonly kind: 'ok'; readonly binding?: WorkflowSessionBinding; } | { readonly kind: 'unavailable'; readonly reason: string; }; /** One binding read-modify-write request (a picker commit or a floor advance). */ export interface WorkflowSessionBindingUpdate { /** The chosen active workflow id; omitted preserves the stored preference. */ readonly selectedWorkflowId?: string; /** Adopt or clear the current execution authority witness. */ readonly executionBinding?: ExecutionBinding | null; /** The exclusion floor to merge (by max) into the stored record. */ readonly excludedBeforeSeq: number; /** The selected authority generation of the picker's selection, when bound. */ readonly selectedGeneration?: DshSelectedGeneration; } /** Binding write outcome: written, or the degraded reason (never throws for store faults). */ export type WorkflowSessionBindingWrite = { readonly kind: 'written'; } | { readonly kind: 'degraded'; readonly reason: string; }; /** * Persist one emission as this session's single newest row. * * Contained by design: a store that cannot be written (no ACTIVE authority, a * schema drift, a busy store, a changed selected generation) returns the * explicit `{kind:'degraded', reason}` the advisory emission path logs — it * never aborts the step it observes, and it never fabricates a successful * write. * @param harnessDir - the resolved `{HARNESS_DIR}` (null when none resolved). * @param input - the emission's session identity + payload. * @returns written, or the degraded reason. */ export declare function writeEngineStatusSnapshot(harnessDir: string | null, input: EngineStatusSnapshotWriteInput): EngineStatusSnapshotWrite; /** * Read one session's durable workflow-selection binding. * * The answers are deliberately distinct: `{kind:'ok'}` WITHOUT a `binding` * means there is nothing recorded for this session — the caller proceeds * unbound. `{kind:'unavailable', reason}` means the record cannot be trusted * (`cwd-mismatch`, a store refusal carrying its own recovery text) — the caller * must NOT treat that as "no pick" and attribute the session anywhere. * @param harnessDir - the resolved `{HARNESS_DIR}`. * @param sessionId - the durable `session.header.id` (the picker key). * @param cwd - the authoritative session cwd (exact match against the record). */ export declare function readWorkflowSessionBinding(harnessDir: string, sessionId: string, cwd: string): WorkflowSessionBindingRead; /** * Durably record one session's workflow-selection binding (the D4 picker * commit / exclusion-floor advance) in ONE store transaction: the stored * preference is preserved when the update omits one, the exclusion floor * merges by max (an older observation never walks the window back over rows * already excluded), and a record whose stored cwd is not this session's cwd * refuses `cwd-mismatch`. * @param harnessDir - the resolved `{HARNESS_DIR}`. * @param sessionId - the durable `session.header.id`. * @param cwd - the authoritative session cwd (must match any stored record). * @param update - the preference (optional) and the exclusion floor. * @returns written, or the degraded reason (never throws for store faults). */ export declare function updateWorkflowSessionBinding(harnessDir: string, sessionId: string, cwd: string, update: WorkflowSessionBindingUpdate): WorkflowSessionBindingWrite;