Files
alkstore/docs/architecture/engine-sqlite.md
T

164 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: draft
last_updated: 2026-10-06
---
# 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)).
- 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.
## 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) |
## 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)),
and **OQ-08** (capability surface — none, by default ever;
[ADR-016](decisions/016-deployment-honesty.md)),
2026-10-05/06.