ADR-009: scheduler collapses into queues — schedule()/unschedule()/ run_schedules (opt-in, no ambient timers), @every-only v1 grammar (dissolves honker's local-TZ cron brittleness), boundary guarantee row (at-least-once per boundary, fixed 64-cap catch-up with skip-forward, row-locked fire tx as engine-generic no-double-fire floor), __alkstore_scheduler leadership lock, InvalidSpec + LeadershipLost taxonomy additions. ADR-010: queue depth pinned engine-uniformly — three-state machine (pending/processing/dead) with delete-on-ack, get_job sees dead rows, heartbeat = renewal with late-heartbeat refusal, reclaim-consumes- an-attempt stated as contract text, equal-jitter exponential backoff (range definitionally pinned, 1 h cap), QueueOpts stamped onto job rows at enqueue (no per-queue registry), move-to-dead dead-letter with retention-sweep support and no redrive API, sweep_expired carries the no-stranded-rows property (fixes honker's expired- processing zombie hole — SQLite-side realization rides OQ-06 as a concrete fork candidate), one engine-owned pg schema, queues are rows not tables, result-storage cut-flag stands. Also: full honker-machinery and pgboss-rs reference reads persisted (docs/research/reference-*.md — the honker defect list pre-stages the OQ-06 quality read), queues.md rewritten from design-space frame to resolved-depth spec, core-contract/engines/README/overview/deployment/ ADR-002 propagated.
6.8 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-10-05 |
SQLite engine
The alkstore-sqlite engine implements
core-contract.md on rusqlite + published
honker-core. 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
honker-core's pin),
bundled-sqlitefor hermetic builds. No.soruntime artifacts, no vendored patches (ADR-003, ADR-001). - 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.
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 pollingPRAGMA data_versionat 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). - Each connection runs honker's bootstrap at open: pragmas
(WAL,
synchronous=NORMAL, busy timeout),attach_notify,attach_honker_functions,bootstrap_honker_schema(the alknet-filesystem POC's wiring shape).
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 | honker's stream machinery; explicit offset saves through the tx seam |
| queues | honker's queue functions (ADR-002); semantics depth per ADR-010 — ride + pin, except two contract properties honker's machinery doesn't provide (the no-stranded-rows sweep_expired, dead-row get_job visibility — OQ-06's concrete fork candidates) |
| named locks | honker's lock machinery |
| scheduler / outbox | collapse shape (ADR-009): honker's scheduler machinery (_honker_scheduler_tasks) carries @every specs (its cron boundary machinery unused — cron strings are contract-rejected); honker's 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 §4) |
| begin_tx | acquires the writer slot, opens BEGIN IMMEDIATE, returns the caller-held handle (ADR-007) |
| handle ops | each *_tx op round-trips spawn_blocking to the same connection (thread-affinity note in ADR-007) |
| 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 negative consequence).
- Watcher failure handling is inherited: on watcher death, every
subscriber's receiver closes (
WatcherDeathGuardbehavior) — consumers see the close, never a silent hang (ADR-006). - 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: honker-core 0.5 pins rusqlite ^0.40.1, whose rustc floor is ≥ 1.99; binaries linking this engine carry that requirement (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 rides on the fork question
Nearly all of honker-core's surface is consumed (Writer/Readers/ SharedUpdateWatcher/attach_*). The quality read (OQ-06) is this engine's only open dependency gate: if it names a defect or an upstream-unwon't change, the fork posture (ADR-005) fires and this engine's substrate becomes owned code. Until then, published-library consumption stands.
Design Decisions
| ADR | Decision | Summary |
|---|---|---|
| 001 | Crate split | single-driver engine crate |
| 002 | Feature scope | which rows this engine serves |
| 003 | Driver | rusqlite + honker-core, bridged seam, inherited watcher |
| 005 | Ownership | published honker-core; named fork triggers |
| 006 | Wake contract | data_version watcher, coalescing, death-closes-receivers |
| 007 | Tx seam | writer-slot lease, BEGIN IMMEDIATE, spawn_blocking round-trips |
| 008 | Contract v1 | pinned surface; outbox backing queue derived under the reserved prefix; wake payload transport unused (non-contract) |
| 009 | Scheduler collapse | honker scheduler machinery with @every specs; __alkstore_scheduler leadership; cron machinery unused |
| 010 | Queue depth | ride + pin over honker's functions; §5/§3a properties are OQ-06 fork candidates |
Open Questions
Open questions are tracked in open-questions.md. Key questions affecting this document:
- OQ-06: honker-core quality read — fork-trigger assessment (open); now carrying two concrete candidates from the queue-depth design (no-stranded-rows sweep, dead-row visibility — complement vs fork is the read's call)
Resolved: OQ-09 (scheduler collapse — ADR-009) and OQ-05 (queue semantics depth — ADR-010), 2026-10-05.