--- status: draft last_updated: 2026-10-08 (ADR-023 — fourth review round: plain-path open (URI flag dropped), 1 ms watcher default + SqliteOpts knob) --- # SQLite engine The `alkstore-sqlite` engine implements [core-contract.md](core-contract.md) on rusqlite over the [forked honker-core substrate](decisions/011-sqlite-substrate-fork.md) — carried in-tree as the engine crate's substrate module subtree ([ADR-013](decisions/013-fold-substrate-into-sqlite.md)). This spec records WHAT the engine is internally (its connection architecture, seam, and wake plumbing) — not code-level HOW. Decisions live in ADRs; per-contract obligations live in the core spec and are not restated here. ## Identity and posture - Single driver: rusqlite, riding the [forked honker-core substrate](decisions/011-sqlite-substrate-fork.md) — the forked lineage machinery carried in-tree as this crate's `src/substrate/` module subtree ([ADR-012](decisions/012-forked-substrate-design.md) design, [ADR-013](decisions/013-fold-substrate-into-sqlite.md) packaging) — with `bundled-sqlite` for hermetic builds. No `.so` runtime artifacts ([ADR-003](decisions/003-sqlite-driver.md) for the driver decision; ownership resolved by [ADR-011](decisions/011-sqlite-substrate-fork.md) — OQ-06's read fired ADR-005's fork trigger). - Sync machinery, async-facing trait: honker-core is sync (std threads, blocking iterators); the engine bridges at the trait seam per the family-standard posture (alktty REQ-TTY-01 precedent). Every engine call runs in `spawn_blocking`. - Single-host by nature: file-backed, one machine, NFS-two-writers unsupported (honker's honesty posture, inherited). See [deployment.md](deployment.md); the boundary's surface location is decided ([ADR-016](decisions/016-deployment-honesty.md)) — this crate's identity and docs state the posture; no runtime descriptor exists. ## Connection architecture - **Writer** — one dedicated connection; the only one permitted to write. Serializes all mutations (WAL single-writer, modeled honestly, not fought). - **Readers** — a small pool of read connections for lookups and claim/ack work. - **Watcher** — honker-core's `SharedUpdateWatcher`: a dedicated thread polling `PRAGMA data_version` at the default 1 ms cadence, fanning out to listeners, overtriggering on purpose (waking all subscribers per poll tick, even when several commits coalesced inside one tick — wake is a hint; consumers re-read indexed state, [ADR-006](decisions/006-wake-and-delivery-contract.md)). The 1 ms default is the shipping default — the POC's published wake-latency numbers were measured at it ([ADR-023](decisions/023-fourth-review-round.md) §4); the cadence is tunable via `SqliteOpts::poll_interval` ([ADR-008](decisions/008-contract-v1-pinning.md) §6's config split — engine opts carry cadence), the documented idle cost and tuning recipe in [deployment.md](deployment.md). - Each connection runs the substrate's bootstrap at open: pragmas (WAL, `synchronous=NORMAL`, busy timeout), `attach_notify`, the substrate's function attachments and schema bootstrap (the alknet-filesystem POC's wiring shape). Watcher spawn is fallible in the substrate ([ADR-012](decisions/012-forked-substrate-design.md) §4); open fails if the watcher cannot start. - **The `open` path argument is a plain filesystem path** ([ADR-023](decisions/023-fourth-review-round.md) §3): the substrate's `open_conn` drops the inherited `SQLITE_OPEN_URI` flag (a registered fork delta), so `?name=value` suffixes in the path are literal filenames — no URI interpretation, no connection- semantics mutation outside the constructor's opts (`:memory:` works without URI mode; URI-only features like shared-cache memory DBs re-enter with a consumer-inventory row). ## Mapping the contract | Contract piece | Engine realization | |---|---| | notify / listen | honker's notify functions inside the caller's tx; `listen()` bridges the watcher's fanout into a tokio receiver (one `spawn_blocking` thread per subscription doing `blocking_send`) | | streams | the forked substrate's stream machinery (inherited near-verbatim — the `__alkstore_stream` table's field set is already the contract's shape (its `topic` column carries the contract's `stream` field — the one name delta, ADR-012 §3's fidelity posture), the offset-ASC read path, keyed publish's nullable key column, monotone offset saves ([ADR-015](decisions/015-streams-depth.md)); explicit offset saves through the tx seam; `trim_to` is a `DELETE FROM … WHERE offset <= ?` inside the writer-slot lease) | | queues | owned queue machinery (forked substrate re-derived on [ADR-010](decisions/010-queue-semantics-depth.md) — stamps, no-stranded-rows sweep, dead-visible `get_job`); ADR-003's ride posture superseded on ownership by [ADR-011](decisions/011-sqlite-substrate-fork.md) | | named locks | the substrate's lock machinery (`lock_renew` carries the renew semantics the guarantee row pins; re-acquire does not refresh TTL — inherited deliberately, [ADR-012](decisions/012-forked-substrate-design.md) §4) | | scheduler / outbox | collapse shape ([ADR-009](decisions/009-scheduler-collapse.md)): owned scheduler storage (`__alkstore_scheduler_tasks`, forked substrate) carrying `@every` specs (cron machinery not ported); the leader loop pattern (TTL lock, heartbeat, exit-before-tick-on-loss); the leadership lock is `__alkstore_scheduler`; outbox = helper over queues with the derived backing-queue name ([ADR-008](decisions/008-contract-v1-pinning.md) §4) | | begin_tx | acquires the writer slot, opens `BEGIN IMMEDIATE`, returns the caller-held handle ([ADR-007](decisions/007-transactional-seam.md)) | | handle ops | each `*_tx` op round-trips `spawn_blocking` to the same connection (thread-affinity note in [ADR-007](decisions/007-transactional-seam.md)) | | commit/rollback | releases the writer slot | ## Obligations and constraints - **A long transaction parks the writer** (the slot lease is the honest model). Contract docs must surface this so consumers budget transactions ([ADR-007](decisions/007-transactional-seam.md) negative consequence). - Watcher failure handling is inherited: on watcher death, every subscriber's receiver closes (`WatcherDeathGuard` behavior) — consumers see the close, never a silent hang ([ADR-006](decisions/006-wake-and-delivery-contract.md)). - Wake coalescing: bursts inside one poll tick produce one wake; correctness is preserved by the re-read contract (POC-pinned: missed-wake stress with correct post-burst re-reads). - **Deployment note**: the lineage's rusqlite generation (^0.40.1) — this engine crate's own dependency — has a rustc floor ≥ 1.99; binaries linking this engine carry that requirement ([deployment.md](deployment.md) matrix). - The optimization path, if seam throughput ever demands it: a dedicated std-thread bridge (one thread owning the writer conn, ops over mpsc — measured ~2× the spawn_blocking shape at p50 in POC #1), or a raised watcher cadence for idle CPU. Neither is the default. ## What the fork question resolved The [quality read (OQ-06)](../research/quality-read-honker-core.md) — this engine's dependency gate — resolved 2026-10-05: ADR-005's fork trigger **fired** ([ADR-011](decisions/011-sqlite-substrate-fork.md)), and the fork's design is pinned by [ADR-012](decisions/012-forked-substrate-design.md): the substrate is a vendored contract-blind lineage module folded into this engine crate ([ADR-013](decisions/013-fold-substrate-into-sqlite.md)); the watcher/connection architecture this spec describes is inherited verbatim where kept ([ADR-003](decisions/003-sqlite-driver.md) unchanged in architecture), with the port deltas ADR-012 §4 decides (fallible watcher spawn, bounded reconnect backoff, panic-free death-closes-subscribers). The [evidence](../research/quality-read-honker-core.md): published 0.5.0's unreleased-fix-train defect class, plus ADR-010's queue depth requiring engine-owned queue SQL in any posture. The substrate's table family is `__alkstore_*` (ADR-010 §8's naming authorization). ## Design Decisions | ADR | Decision | Summary | |---|---|---| | [001](decisions/001-crate-split.md) | Crate split | single-driver engine crate | | [002](decisions/002-feature-scope.md) | Feature scope | which rows this engine serves | | [003](decisions/003-sqlite-driver.md) | Driver | rusqlite + the forked substrate lineage, bridged seam, inherited watcher — ownership resolved by 011 | | [005](decisions/005-dependency-ownership.md) | Ownership | published honker-core; fork trigger fired by OQ-06 (ADR-011) | | [006](decisions/006-wake-and-delivery-contract.md) | Wake contract | data_version watcher, coalescing, death-closes-receivers | | [007](decisions/007-transactional-seam.md) | Tx seam | writer-slot lease, `BEGIN IMMEDIATE`, `spawn_blocking` round-trips | | [008](decisions/008-contract-v1-pinning.md) | Contract v1 | pinned surface; outbox backing queue derived under the reserved prefix; wake payload transport unused (non-contract) | | [009](decisions/009-scheduler-collapse.md) | Scheduler collapse | `@every` specs; `__alkstore_scheduler` leadership; cron rejected | | [010](decisions/010-queue-semantics-depth.md) | Queue depth | semantics pinned; §5/§3a realization resolved by ADR-011 (owned code) | | [011](decisions/011-sqlite-substrate-fork.md) | Substrate fork | OQ-06's trigger fired; substrate owned, queue ops re-derived on contract v1 | | [012](decisions/012-forked-substrate-design.md) | Fork design | contract-blind substrate, fidelity posture, port deltas, panic-free watcher death | | [013](decisions/013-fold-substrate-into-sqlite.md) | Substrate packaging | folded into `alkstore-sqlite` (`src/substrate/`); no fourth crate | | [014](decisions/014-outbox-tx-enqueue.md) | Outbox tx enqueue | `outbox_enqueue_tx` on `TxHandle`; derivation engine-side through the writer-slot lease | | [015](decisions/015-streams-depth.md) | Streams depth | key = carried metadata (the inherited nullable column); global-FIFO ordering; `trim_to` as a writer-lease delete; event shape already the substrate's | | [016](decisions/016-deployment-honesty.md) | Deployment honesty | no runtime capability surface; single-host posture stated by crate identity + docs; this engine never produces `PayloadTooLarge` (pinned in the contract suite) | | [017](decisions/017-contract-versioning.md) | Contract versioning | engine pins core `alkstore = "1.y"` in its manifest; substrate cherry-picks are class-4 non-events; adoption duties per its §5 | | [018](decisions/018-provenance-register-and-cherry-picks.md) | Substrate provenance | `src/substrate/PROVENANCE.md` (identity, delta register — cherry-picks as tagged entries); cherry-picks recorded at adoption, non-versioning | | [019](decisions/019-mechanism-handle-surfaces.md) | Handle surfaces | boxed handle traits (`Queue`/`StreamHandle`/`Outbox`/`Lock`/`JobHandle`); `worker_id` stamps the row's claimant column; `StopToken` core-owned | | [020](decisions/020-enqueue-opt-semantics-and-bridges.md) | Enqueue options | delay-over-`run_at` in the substrate's re-derived enqueue; scheduler fires stamp from derived defaults; serde_json byte encoding | | [021](decisions/021-tx-reads-and-value-shape-fixes.md) | Third review round | tx-read methods route through the writer-slot lease; `Job.claimed_at` (the fork's claimed_at column, surfaced in `Job`); `schedule()` queue argument validated; drop = rollback releases the lease with `ROLLBACK` | | [023](decisions/023-fourth-review-round.md) | Fourth review round | `open` path is a plain filesystem path (URI flag dropped — registered fork delta); 1 ms watcher default stands, cadence carried onto `SqliteOpts::poll_interval`; `encode_payload` typed (`Codec`); numeric-argument domains pinned contract-side (extents clamp empty — enforced at this engine's trait-impl entry over the substrate) | ## Open Questions Open questions are tracked in [open-questions.md](open-questions.md). Key questions affecting this document: - **OQ-06**: honker-core quality read — fork-trigger assessment — **resolved** (2026-10-05, [ADR-011](decisions/011-sqlite-substrate-fork.md); trigger fired). - **OQ-12**: streams depth — **resolved** (2026-10-05, [ADR-015](decisions/015-streams-depth.md)): key = carried metadata; global-FIFO-by-offset ordering row; `StreamEvent` shape (honker's, `topic` → `stream` in the contract type — the substrate's column name stays per the ADR-012 §3 fidelity posture); `publish_with_key_tx` routes through the writer-slot lease; `trim_to` is a plain delete in the same lease. - **OQ-13**: transactional outbox enqueue shape — **resolved** (2026-10-05, [ADR-014](decisions/014-outbox-tx-enqueue.md)): `outbox_enqueue_tx(outbox, opts, payload)` on the `TxHandle` trait; the SQLite handle routes the op through the writer-slot lease ([ADR-007](decisions/007-transactional-seam.md)) into the forked substrate's `__alkstore_outbox:{name}` backing queue. Resolved: **OQ-09** (scheduler collapse — [ADR-009](decisions/009-scheduler-collapse.md)), **OQ-05** (queue semantics depth — [ADR-010](decisions/010-queue-semantics-depth.md)), **OQ-12** (streams depth — [ADR-015](decisions/015-streams-depth.md)), **OQ-08** (capability surface — none, by default ever; [ADR-016](decisions/016-deployment-honesty.md)), **OQ-10** (contract versioning — [ADR-017](decisions/017-contract-versioning.md)), and **OQ-11** (fork follow-through — the substrate's provenance register and the cherry-pick procedure; [ADR-018](decisions/018-provenance-register-and-cherry-picks.md)), 2026-10-05/06.