docs: open Phase 1 — architecture spec set over the Phase 0 evidence

docs/architecture/ now exists: README index, overview, five component
specs (core-contract, engine-sqlite, engine-postgres, queues,
deployment), ADR-001..007 carrying the Phase 0 resolved decisions
(crate split, feature scope, per-engine drivers, dependency
ownership, wake contract, tx seam), and the centralized
open-questions tracker promotion: OQ-ST-01..08 mirror to OQ-01..08
one-to-one with statuses/resolutions carried; new Phase 1 questions
append (OQ-09 scheduler collapse, OQ-10 contract versioning).
Open Phase 1 work: OQ-04 contract pinning (high), OQ-05 queue
semantics depth (high), OQ-06 honker-core quality read (high;
fork-trigger gate), OQ-08 capability surface, OQ-09, OQ-10.

Erratum fixed in phase-0 OQ-ST-04 (thread-affinity friction is
SQLite-side, previously garbled as pg-side) and a stale scheduler-
boundary pointer corrected in consumer-inventory.md. Two review
passes run (findings: OQ-promotion numbering faithfulness, ADR
back-reference sync) — all critical/warning findings resolved.
This commit is contained in:
glm-5.3-flash committed 2026-10-04 18:13:10 +00:00
1 parent db73678090
commit 4391f6e879
17 files changed
+1812 -14

No files matched your search

@@ -0,0 +1,104 @@
# ADR-003: SQLite engine — rusqlite + published honker-core, bridged at the trait seam
## Status
Accepted
## Context
The SQLite engine's options were never a single "rusqlite vs sqlx"
axis (OQ-ST-03). Three distinct postures existed:
1. **honker-core on our own rusqlite connection** — we own
connection/schema/watcher wiring; honker supplies the SQL-function
machinery (`attach_notify`, `attach_honker_functions`,
`bootstrap_honker_schema`).
2. **honker-rs as the SQLite substrate** (`Database::open` — honker
holds its own connections; sync-only; transactions pin the
connection mutex).
3. **raw SQL over sqlx-sqlite with the honker loadable extension**
(`.so` built from published source, loaded per pool connection).
POC #1 (`docs/research/poc-sqlite-posture-findings.md`, ran 2026-10-04;
POC #2 is its pg twin) measured postures 1
and 3 end-to-end and settled the choice. Constraint facts that outlive
the comparison:
- rusqlite 0.40.x (what honker-core 0.5.0 pins) needs rustc ≥ 1.99
(`cfg_select!`) — a deployment note for any binary linking it.
- sqlx's `libsqlite3-sys` range collides with rusqlite 0.40's — mixed
rusqlite+sqlx binaries need a vendored one-line patch today. The
per-engine-crate split ([ADR-001]) keeps engine binaries
single-driver, dissolving this.
- The async-facing-trait + sync-bridge posture is family-standard
(alktty REQ-TTY-01; alkblobs store-api.md): bridge-at-the-seam is a
supported posture, not a workaround — which removes native-async's
main differentiator before any measurement.
## Decision
The SQLite engine uses **posture 1**: published `honker-core = 0.5`
as a library dependency over the crate's own rusqlite connections
(`bundled-sqlite` for hermetic builds).
- **Driver**: rusqlite, riding honker-core's pinned version.
- **Async seam**: bridged. The writer slot (`Writer`) +
reader pool (`Readers`) + every engine call in `spawn_blocking`.
A dedicated std-thread bridge (one thread owning the writer conn,
ops over mpsc) is the recorded optimization path if seam throughput
ever demands it — not the default.
- **Wake**: honker-core's `SharedUpdateWatcher` inherited (p50 ≈ 1.4 ms
at the default 1 ms `PRAGMA data_version` cadence, with
battle-tested failure handling — subscriber senders close on watcher
death, never silent-hang). A self-built thin watcher was benchmarked
only to confirm honker-core's is tighter (p50 1.40 vs 2.15 ms; max
29 vs 172 ms); no hybrid is warranted.
- **Transactions**: the engine opens `BEGIN IMMEDIATE` on the writer
slot and hands back a caller-held tx handle ([ADR-007]). The slot
lease models WAL's single-writer honestly: a long transaction parks
the writer — that is SQLite's shape, not an emulation artifact.
- **No `.so` runtime artifact, no vendored patches** in the engine
crate.
Rejected alternatives, briefly, for the record: posture 3 is *viable*
but pays ~2× p50 seam cost, a runtime dlopen artifact, wider
dependency tree, and sqlx 0.9 ergonomic warts (unsafe `extension()`,
`SqlSafeStr`) for no compensating advantage. Posture 2 (honker-rs)
was never measured because posture 1 dominates it on control with
comparable reuse.
## Consequences
**Positive**
- ~0.35 ms p50 transactional seam (measured), ~1.4 ms wake latency,
and honker-core's watcher failure-handling inherited for free.
- Static linkage: "open a path, get a store" needs no runtime
artifacts.
- The engine's job shrinks to wiring + the SQL surfaces honker doesn't
cover (queue semantics depth, OQ-ST-05's SQLite side rides honker's
machinery directly) + the async bridge.
**Negative**
- honker-core pins rusqlite ^0.40.1; version movement in honker-core
moves our rusqlite. A honker-core quality read is the recorded fork
trigger ([ADR-005], OQ-06).
- rustc ≥ 1.99 required by rusqlite 0.40.x — binaries linking this
engine carry that toolchain floor (deployment-matrix row,
[deployment.md](../deployment.md)).
- The sync bridge means engine ops pay a `spawn_blocking` hop — mostly
invisible next to SQLite write costs, but it shapes the tx-handle
design ([ADR-007]).
## References
- `docs/research/poc-sqlite-posture-findings.md` — the measurements.
- OQ-ST-03 (`docs/research/phase-0.md`) — resolution record.
- [ADR-001](001-crate-split.md) — why the engine crate is
single-driver.
- [ADR-004](004-postgres-driver.md) — the Postgres counterpart.
- [ADR-007](007-transactional-seam.md) — the tx-handle shape both
engines implement.
- [engine-sqlite.md](../engine-sqlite.md) — the engine spec this
decision defines.