--- id: engine-sqlite-window name: sqlite engine-state window family — the durable single-node delete window status: completed depends_on: [engine-sqlite] scope: narrow risk: low impact: component level: implementation tags: [wave-3, engines, sqlite, gc] --- ## Description Implement the engine-state companion seam's **window family** on the sqlite backend (`src/sqlite.rs` or its seam module): the `EngineWindowState` impl per ADR-010 §2 **as reduced by ADR-012 §5** — the window family only. The pin clauses and the sweep-lock body are **dropped dead machinery** for sqlite (ADR-012 §5's table: pin clauses **no**, sweep-lock clause **no** — the single-flighter is the in-process lock; a sqlite fleet is a recorded non-option, ADR-010 §3). Nothing of `EngineFleetState` may exist on this engine. - **`ensure_gc_schema`** — creates **`gc_window` only** (`gc_window (key BLOB PRIMARY KEY, visible_after INTEGER)`; the timestamp encoding — `SystemTime` ↔ integer, sub-second precision for the test tunings — is a recorded design point). Never `gc_pins`; the embedder's liveness table is never created here (ADR-008's schema-ownership rule — the sweep reads it and never owns it). Called at store construction by the store core (`store-sqlite-construction` wires the call); the clause body is engine-side. - **`window_stage`** — stage candidates into the durable window, **idempotent per `(key, deadline)`** (`INSERT OR IGNORE` semantics — the body's discipline; the store stages each candidate once per sweep, and a re-stage of the same key at a new deadline updates the row). Virgin store: tolerated (the table exists from construction; a no-tables-yet database stages lazily or errors nothing — the Backend trait's engine posture carries here). - **`window_take(now)`** — take overdue candidates (`visible_after <= now`): the rows **leave the window table and return as keys** for the calling sweep to re-arbitrate and delete — the executor is the next sweep (ADR-012 §6.7). Empty when nothing is overdue; virgin store: empty. The take is one atomic step (selected rows removed and returned together — a row is never handed out twice). - **Contract tests** per the applicability row (ADR-012 §5's table is the contract-test matrix): the schema creates the window table only (assert `gc_pins` does not exist); stage idempotence per `(key, deadline)` incl. the re-stage-at-new-deadline form; take returns exactly the overdue set, removes it, leaves future rows; empty/virgin shapes; the timestamp encoding's boundary (`visible_after == now` is overdue — `<=` per the seam doc). The store core drives these clauses (`store-sweep-durable-window`); this task ships the clauses and their engine-level contract tests. ## Acceptance Criteria - [x] The sqlite backend implements `EngineWindowState` — the window family only; no pin-clause or sweep-lock body exists anywhere in the sqlite engine - [x] `ensure_gc_schema` creates `gc_window` only; `gc_pins` is never created by sqlite (contract-tested) - [x] `window_stage` is idempotent per `(key, deadline)`; a re-stage at a new deadline updates the row - [x] `window_take` returns exactly the overdue set (`<= now` boundary), removes it atomically, leaves future rows; empty on none/virgin - [x] Clause contract tests green; `cargo test` (default) green; `cargo clippy --all-targets -- -D warnings` and `cargo fmt --check` green ## References - src/gc.rs (`EngineWindowState` — the seam contract and its clause docs) - docs/architecture/decisions/010-backend-io-seams-and-fleet-state.md §2 - docs/architecture/decisions/012-pre-decomposition-consistency-rulings.md §5 (the applicability table; the sqlite reduction), §6.7 (the executor) - docs/architecture/decisions/008-trait-size-probe-fleet-gc-and-large-engines.md §2 (the durable window's semantics) - docs/architecture/decisions/005-pooled-cas-and-gc-mechanism.md (the delete-window protocol) ## Notes - The `EngineWindowState` impl lives in `src/sqlite.rs` after the `Backend` impl, as `impl EngineWindowState for SqliteBackend`. The pin clauses and the sweep-lock body are absent by construction — nothing of `EngineFleetState` is implementable or referenced on this engine (ADR-012 §5's sqlite row: the single-flighter is the in-process sweep lease on `GcHome`, a sqlite fleet is a recorded non-option). - **Timestamp encoding (recorded design point):** `visible_after` travels as a signed i64 nanosecond count from the Unix epoch — sub-second precision preserved for the test tunings, negatives for pre-epoch instants, full i64 range. Helpers `time_to_nanos` / `nanos_to_time` sit beside `size_u64` in `src/sqlite.rs`. - **Staging discipline:** one `prepare_cached` upsert per row — `INSERT … ON CONFLICT(key) DO UPDATE SET visible_after = excluded.visible_after`. The row's primary key is the window candidate's key, so a same-deadline re-stage is idempotent (one row, unchanged value) and a new-deadline re-stage updates the row rather than duplicating it. `INSERT OR IGNORE` alone would have made the new-deadline update a no-op, so the upsert carries both clauses of the idempotence contract. - **Take atomicity:** `DELETE FROM gc_window WHERE visible_after <= ? RETURNING key` — one statement on the single pooled connection, so selection and removal are one atomic step (a row is never handed out twice); `RETURNING` order is unspecified, so the returned keys are sorted for determinism. - **Virgin-store tolerance split per the sqlite engine's recorded posture:** `window_take` is a read — a no-tables-yet database maps to empty via the existing `is_no_such_table` tolerance; `window_stage` is a write — a tableless database surfaces the engine error rather than silently creating schema (the constructor-vs-lazy design point: store construction always wires `ensure_gc_schema`, so the table exists whenever the engine is in store service; the lazily staged option was rejected as contradicting that recorded point). - Schema timing unchanged from the pool table's recorded design point: the backend constructor never creates `gc_window` itself — the store construction call (`store-sqlite-construction`) wires `ensure_gc_schema`, which is idempotent and creates `gc_window` only. The clause-level tests use a `new_window_backend()` fixture that mirrors that wiring. - The boundary contract (`visible_after <= now`, inclusive) is contract-tested, including the nanosecond slack row (visible at `now + 1ns` stays). ## Summary The sqlite engine-state seam's window family landed: `impl EngineWindowState for SqliteBackend` in `src/sqlite.rs` — `ensure_gc_schema` (creates `gc_window` only, never `gc_pins`, contract-tested through `sqlite_master`), `window_stage` (key-keyed upsert; idempotent per `(key, deadline)`, new-deadline re-stage updates the row), and `window_take` (`DELETE … RETURNING` — one atomic overdue take with the `<= now` boundary, future rows left in place). Signed-nano timestamp encoding recorded; virgin-store tolerance split read/write per the sqlite engine's posture. Six clause contract tests added (schema window-only, staging idempotence + deadline update, overdue-exact take + future rows left, the `<=` boundary with nanosecond slack, and both virgin shapes). Verification: `cargo test` green (58 lib + 5 integration), `cargo clippy --all-targets -- -D warnings` green, `cargo fmt --check` green.