164 lines
11 KiB
Markdown
164 lines
11 KiB
Markdown
---
|
||
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. |