Implementation plan: wave-based decomposition; waves 1-2 decomposed (11 tasks)

docs/plans/implementation.md records the wave structure (core ->
substrate fork || pg engine -> sqlite engine -> contract suite ->
release), the decided points (contract-suite layout = option (a),
engine-tests vs equivalence-suite split, no CI, mem-engine/fuzzing
deferrals surfaced), and the review-gate rhythm.

Wave 1 (foundations): workspace scaffold, core errors/validation,
value types, trait surface, contract-suite scaffold (+ADR-022),
review gate.
Wave 2 (substrate fork): fork scaffold/provenance, connection+watcher
port, queue-op re-derivation on contract v1, provenance/floor close,
review gate.
This commit is contained in:
glm-5.3-flash committed 2026-10-07 14:37:31 +00:00
1 parent 83767e880b
commit 49743c690e
12 files changed
+1009

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
---
status: draft
last_updated: 2026-10-07 (initial wave plan; waves 1–2 decomposed)
---
# alkstore — Implementation plan
Wave-based decomposition of the architecture
(`docs/architecture/`) into units of implementable work. This is a
deliberate deviation from the SDD process's decompose-everything-upfront
step: the architecture is large, early waves change the shape of later
ones (the fork's outcomes feed the SQLite engine tasks; the contract
suite's harness shape feeds both engines' verification work), and
decomposing only the next wave or two at a time keeps each session's
task set reviewable and lets later decompositions absorb earlier waves'
course corrections.
Rhythm: decompose a wave → implement it → review gate → decompose the
next wave. Task files live in `tasks/` (taskgraph-managed; frontmatter
carries the categorical estimates). Wave boundaries are also review
boundaries.
## The waves
Dependency logic in one line: core → (substrate fork ∥ postgres
engine) → sqlite engine → contract suite → release readiness. The
substrate fork is contract-blind (ADR-012 §2), so it needs only the
workspace scaffold from wave 1; the Postgres engine needs only the core
crate. Waves 2 and 4 are therefore independent of each other and could
run in either order (or in parallel, if agents are ever available in
parallel).
| Wave | Contents | Depends on | Status |
|---|---|---|---|
| [1](#wave-1--foundations) | Workspace scaffold; core crate (errors, value types, full trait surface); contract-suite scaffold | — | decomposed (`tasks/`) |
| [2](#wave-2--sqlite-substrate-fork) | honker-core fork into `alkstore-sqlite/src/substrate/`: port, deltas, provenance, test floor | wave 1 (workspace scaffold only) | decomposed (`tasks/`) |
| 3 | SQLite engine: connection architecture, re-derived queue ops on contract v1, scheduler/outbox, tx seam, SQLite backlog column | waves 1 + 2 | not yet decomposed |
| 4 | Postgres engine: schema bootstrap, pool/open, listener/forwarder, all mechanisms, tx seam, pg backlog column | wave 1 | not yet decomposed |
| 5 | Contract suite: the cross-engine equivalence properties (core-contract.md §Verification backlog), version-stamped per ADR-017 | waves 3 + 4 | not yet decomposed |
| 6 | Release readiness: crate docs, deployment matrix final pass, README (written last, honestly), publish prep; mem-engine and fuzzing decisions | wave 5 | not yet decomposed |
## Wave 1 — Foundations
The core crate is contract v1 in code: the error taxonomy (ADR-008 §5),
the value types (ADR-019 §3, ADR-020), the full trait surface
(ADR-008 §1–§3/§8, ADR-014, ADR-019, ADR-021), and the payload encoding
posture (ADR-020 §4). Nothing engine-specific lives here — no
resolution arithmetic, no SQL. The equal-jitter curve, opts-stamping
resolution, and boundary math are deliberately *not* core: ADR-012 §2
pins each engine as the owner of one implementation, with equivalence
pinned by the contract suite (wave 5).
The contract suite gets scaffolded now (not in wave 5) because its
harness shape — a `Store`-factory-parameterized property crate — is
easier to grow row by row as engines land than to retrofit onto two
finished engines. Wave 5 fills it with the cross-engine equivalence
rows; waves 3 and 4 adopt the harness for their own backlog columns.
## Wave 2 — SQLite substrate fork
The fork per ADR-011/012/013: port honker-core at `f4e53c6` into
`alkstore-sqlite/src/substrate/`, apply the three watcher port deltas
and the bootstrap re-keying, drop cron/experimental/cut-flag machinery,
re-own the table family as `__alkstore_*`, carry `PROVENANCE.md` and
the dual-license notice in-tree (ADR-018), and stand up the inherited
test suites as the floor. The substrate stays sync and contract-blind;
the engine layer that maps it onto the core contract is wave 3, not
here. Reviewability against the lineage (ADR-012 §3) is a property the
wave-2 review gate checks explicitly.
## Decided points
- **Contract-suite layout — option (a)**: a small internal
`alkstore-contract-suite` crate (not published) exposing property
tests parameterized over a `Store` factory; each engine crate takes
it as a dev-dependency. One normative owner per property, mirroring
ADR-012 §2's one-owner rule. This discharges ADR-017 §4.2's
"decided at implementation" deferral; recorded as ADR-022 by the
scaffold task.
- **Engine tests vs. contract suite**: each engine wave carries its own
mechanism tests (does the engine work); wave 5 carries the
cross-engine *equivalence* properties (do the engines agree). The
verification-backlog rows are mostly equivalence-shaped, so this
split keeps wave 5 from re-testing engine internals.
- **CI**: none, deliberately. CI and publishing are run manually
(self-hosted Gitea; supply-chain posture). No CI-wiring task exists;
the merge gates (`cargo test`, `cargo clippy --all-targets --
-D warnings`, `cargo fmt --check`) are run coordinator-side.
- **Mem engine** (ADR-001 §4) and **fuzzing adoption** (the
alksocks/alktty/alktunnels pattern): both are "decided at
implementation" deferrals, recorded here so they surface as explicit
decision points in wave 6 (or earlier if the test story demands the
mem engine sooner) rather than ambushing a later session.
## Review gates
Each wave ends in a review task (`review-wave-N`) before the next wave
decomposes. Specific gates:
- **Wave 1 review** — the trait surface is versioned contract surface
from the first release (ADR-017); a shape error found here is cheap,
found in wave 5 it is a migration. Review checks the code against the
pinned ADR text line by line.
- **Wave 2 review** — diff reviewability against the honker lineage
(ADR-012 §3's fidelity posture), provenance register completeness
(ADR-018), floor tests green.
- **Wave 3/4 reviews** — engine-vs-contract conformance; the backlog
columns each engine owns.
- **Wave 5 review** — the suite as compatibility instrument: every
backlog row present, version-stamped, green on both engines; this is
the gate that flips the engine specs to `stable`.
+76
View File
@@ -0,0 +1,76 @@
---
id: contract-suite-scaffold
name: Contract-suite crate scaffold + ADR-022 (suite layout decision)
status: pending
depends_on: [core-trait-surface]
scope: narrow
risk: low
impact: phase
level: implementation
tags: [wave-1, contract-suite]
---
## Description
Scaffold the contract suite as a fourth, internal, unpublished crate —
`alkstore-contract-suite` — per the layout decision recorded in
docs/plans/implementation.md (option (a)): property tests
parameterized over a `Store` factory, consumed by each engine crate as
a dev-dependency. This discharges ADR-017 §4.2's "layout decided at
implementation" deferral; the decision gets its own ADR (ADR-022) since
it shapes the pairing instrument ADR-017 §4 names.
The crate:
- Workspace member, `publish = false`, depends on `alkstore` only.
- Exposes a `StoreFactory` trait (or function alias — implementer's
choice, but one shape, documented): something that yields a fresh
`Box<dyn Store>`-shaped store over an isolated backing store (fresh
file / fresh schema), plus teardown. The exact factory shape may
evolve when engines adopt it (wave 3/4 feedback) — the ADR records
the layout decision, not a frozen API.
- A `tests/` harness that runs the property set against a supplied
factory, structured so each backlog row is one named, independently
runnable property with a version-stamp slot (ADR-017 §4.2: each row
version-stamped with the contract change that added it — a
doc-comment or attribute convention, decided here and used by every
later row).
- One exemplar property implemented end-to-end to prove the harness
works — the reserved-name/empty-name validation property (pure
contract, engine-independent, runnable against any factory): every
name-bearing entry point rejects empty with `InvalidName` and
reserved-prefix with `ReservedName`, on auto-commit and tx paths
alike. This row is version-stamped `ADR-008 §4`.
- Engine crates get the dev-dependency edge now (empty suite compiles
against them trivially once they have code; the edge existing early
means wave 3/4 tasks adopt the harness rather than debate it).
## Acceptance Criteria
- [ ] `alkstore-contract-suite` crate exists, `publish = false`,
depends on core only, compiles in the workspace
- [ ] Factory abstraction defined and documented; exemplar property
(name validation) green against a trivial in-crate mock store
- [ ] Version-stamp convention established and documented in the
crate (used by the exemplar row)
- [ ] ADR-022 written (`docs/architecture/decisions/022-contract-suite-layout.md`):
option (a) chosen, rationale (one normative owner per property,
ADR-012 §2 mirror), the factory-shape latitude stated, ADR-017
§4.2 deferral discharged; ADR index tables in
docs/architecture/README.md and overview.md updated
- [ ] Engine crate manifests carry the dev-dependency edge
- [ ] `cargo test` workspace-wide, clippy `-D warnings`, fmt clean
## References
- docs/architecture/decisions/017-contract-versioning.md §4.2
- docs/plans/implementation.md (Decided points)
- docs/architecture/core-contract.md §Verification backlog
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+72
View File
@@ -0,0 +1,72 @@
---
id: core-errors-and-validation
name: Core error taxonomy and name validation
status: pending
depends_on: [scaffold-workspace]
scope: narrow
risk: low
impact: project
level: implementation
tags: [wave-1, core]
---
## Description
Implement the core crate's error taxonomy and the name-validation
helpers every entry point shares.
**Error taxonomy** (ADR-008 §5, exact): one top-level `Error`,
`thiserror`-typed, `#[non_exhaustive]` (ADR-017 §3 — mechanically
enforces catch-all matching). Variants:
- `PayloadTooLarge { limit }` — universal variant; produced pg-side
only (the occurrence asymmetry is engine work, not core's — core
defines the variant and its doc text)
- `ReservedName { name }`
- `InvalidName { name }`
- `Closed`
- `Codec`
- `Database` — the opaque fallback; engine detail rides the source
chain (`#[source]`), no engine-specific variants ever minted
**Name validation** (ADR-008 §4): a shared validation helper applied at
every name-bearing entry point — non-empty (`InvalidName`) and
reserved-prefix rejection for the `__alkstore_` prefix
(`ReservedName`). The helper distinguishes the two name classes:
- Shared-namespace kinds (channels, streams, queues, outboxes, locks,
schedule names): non-empty + reserved-prefix rejected.
- Consumer-local identifier classes (stream-consumer names, `owner` on
`try_lock`, `worker_id` on claims): non-empty only — a leading
`__alkstore_` is legal (ADR-008 §4 third-round annotation).
Also the reserved-string constant: `__alkstore_listener_reconnected__`
(the one v1-reserved string, ADR-008 §4) — public in core so the pg
engine and the contract suite reference the same constant.
`Result<T>` alias in the crate's prelude shape. No panics; no
`unwrap()`/`expect()`.
## Acceptance Criteria
- [ ] `Error` with exactly the six v1 variants, `#[non_exhaustive]`,
`thiserror`-derived, `Database` carrying `#[source]`
- [ ] Validation helper(s) covering both name classes, unit-tested
(empty, whitespace-only, reserved-prefix, reserved string
itself, legal names)
- [ ] Reserved-string constant exported
- [ ] `cargo test -p alkstore`, clippy `-D warnings`, fmt clean
## References
- docs/architecture/decisions/008-contract-v1-pinning.md §4, §5
- docs/architecture/decisions/017-contract-versioning.md §3
- docs/architecture/core-contract.md §Errors, §Naming / reserved namespace
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+117
View File
@@ -0,0 +1,117 @@
---
id: core-trait-surface
name: Core trait surface (Store, TxHandle, mechanism handles, receivers)
status: pending
depends_on: [core-value-types]
scope: moderate
risk: medium
impact: project
level: implementation
tags: [wave-1, core]
---
## Description
Implement the core crate's full trait surface — contract v1 in code.
Every shape is pinned by ADR text; this task is faithful transcription,
and it is the surface both engines implement in waves 3–4, so shape
errors here are the expensive kind. The wave-1 review gate checks this
task line-by-line against the ADRs.
**`Store` trait** (ADR-008 §1/§6, ADR-019 §1, ADR-009 §1):
```text
begin_tx() -> Box<dyn TxHandle + Send>
notify(channel, payload)
listen(channel) -> Box<dyn WakeReceiver>
stream(name) -> Box<dyn StreamHandle>
queue(name, opts) -> Box<dyn Queue>
outbox(name) -> Box<dyn Outbox>
try_lock(name, owner, ttl) -> Option<Box<dyn Lock>>
schedule(name, spec, queue, payload, opts) -> Result<Schedule>
unschedule(name) -> bool
run_schedules(stop: StopToken) -> Result<()>
```
Constructors/options live in engine crates (ADR-008 §6) — core defines
only the trait. Name-bearing methods validate at the entry point
(shared helper from `core-errors-and-validation`); the trait's doc
contract states that obligation (engines must reject before any round
trip, on auto-commit and tx paths alike).
**`TxHandle` trait** (ADR-008 §2, ADR-014, ADR-015 §2, ADR-021 §1/§4):
`enqueue_tx`, `publish_tx`, `publish_with_key_tx`, `notify_tx`,
`save_offset_tx`, `get_job_tx`, `get_offset_tx`, `read_since_tx`,
`read_from_consumer_tx`, `outbox_enqueue_tx`, `commit(self:
Box<Self>) -> Result<()>`. Drop = rollback is an engine obligation
stated in the trait docs. No generic methods (object safety); payloads
cross as `serde_json::Value`; async methods use the boxed-future form
(family-standard async trait posture).
**Mechanism handles** (ADR-019 §1, exact method sets): `Queue { name,
enqueue, claim_one, claim_batch, ack_batch, cancel, get_job,
sweep_expired }`; `StreamHandle { name, publish, publish_with_key,
read_since, read_from_consumer, save_offset, get_offset, trim_to,
subscribe }`; `Outbox { name, enqueue, run_once }`; `Lock { name,
renew, release(self: Box<Self>) }`; `JobHandle { job(&self) -> &Job,
ack(self), retry(self, err, delay) -> bool, fail(self, err) -> bool,
heartbeat(self, extend) -> bool }` — the one-shot/repeatable split is
deliberate (heartbeat takes `&self`).
**Receivers** (ADR-008 §3/§8): `WakeReceiver { recv() ->
Option<Wake>, try_recv() -> Result<Option<Wake>>, recv_timeout(d) ->
Result<Option<Wake>> }` (timeout expiry = `Ok(None)`; `Err` carries
`Database`/`Closed` only); `EventReceiver { recv() ->
Option<Result<StreamEvent>>, try_recv() -> Result<Option<StreamEvent>>,
read_since(offset, limit) -> Result<Vec<StreamEvent>>,
save_offset(&mut self) -> Result<()>, offset() -> i64 }` — no
`recv_timeout` on `EventReceiver` (deliberate absence, ADR-008 §8
third-round annotation).
**`run_once` delivery callable** (ADR-019 §5): consumer-supplied async
callable taking `Box<dyn JobHandle>`, returning the boxed future of
`Result<()>`; object-safe encoding (named one-method trait object or
`&mut dyn FnMut` form — implementer's choice under the pinned
semantics).
`with_tx` (ADR-007, signature pinned 2026-10-07): `with_tx(f) ->
Result<()>` with `f: FnOnce(&mut dyn TxHandle) -> BoxedFuture<Result<()>>`
— `Ok` ⇒ commit, `Err`/drop/panic ⇒ rollback; non-generic, values
return through captured state, no handle escapes the closure. This
lives on the `Store` trait as a provided method (default body over
`begin_tx`).
All traits re-exported from `src/lib.rs`. Doc comments carry the
pinned semantics (guarantee-row references, validity predicate,
drop=rollback, no-claim_tx absence rationale).
## Acceptance Criteria
- [ ] Every trait above with the exact pinned method sets and
signatures; compiles as object-safe (`Box<dyn ...>` everywhere
the contract says)
- [ ] `with_tx` provided method present with the pinned signature
- [ ] A compile-probe test constructs `Box<dyn TxHandle>`,
`Box<dyn Queue>`, etc. from a trivial local impl, proving object
safety of the whole surface
- [ ] Trait docs state: entry-point validation obligation, drop =
rollback, the uniform handle-op validity predicate reference,
heartbeat's absolute-reset semantics, no `claim_tx` rationale
- [ ] `cargo test -p alkstore`, clippy `-D warnings`, fmt clean
## References
- docs/architecture/core-contract.md (the seam, mechanism contracts)
- docs/architecture/decisions/008-contract-v1-pinning.md §1–§3, §6, §8
- docs/architecture/decisions/007-transactional-seam.md
- docs/architecture/decisions/014-outbox-tx-enqueue.md
- docs/architecture/decisions/019-mechanism-handle-surfaces.md §1–§5
- docs/architecture/decisions/021-tx-reads-and-value-shape-fixes.md
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+95
View File
@@ -0,0 +1,95 @@
---
id: core-value-types
name: Core value types (opts, Job, Schedule, StreamEvent, Wake, StopToken)
status: pending
depends_on: [core-errors-and-validation]
scope: narrow
risk: low
impact: project
level: implementation
tags: [wave-1, core]
---
## Description
Implement the core crate's value types — everything the trait surface
(see `core-trait-surface`) takes and returns. Shapes are pinned; this
task invents nothing.
**Opts structs** (ADR-008 §1, ADR-010 §3/§3a, ADR-020 §1–§3; NOT
`#[non_exhaustive]` — ADR-017 §3, consumers construct them):
- `EnqueueOpts { delay: Option<i64>, run_at: Option<i64>, priority:
i64, max_attempts: Option<i64>, expires: Option<i64> }` — `Default`
gives all-`None`/priority 0. Field semantics (delay-over-run_at,
relative expires) are *engine-side resolution* — core carries only
the fields and their doc text.
- `QueueOpts { visibility_timeout_s: i64, max_attempts: i64,
backoff_base_s: i64, dead_letter_retention_s: Option<i64> }` —
`Default` = 300/3/5/None (ADR-010 §3).
- `ScheduleOpts { priority: i64, max_attempts: Option<i64>, expires:
Option<i64> }` — types identical to their `EnqueueOpts` twins.
**Consumer-read types** — `#[non_exhaustive]` (ADR-017 §3):
- `Job` — the full ADR-019 §3 field list: `id: i64, queue: String,
state: JobState, payload: Vec<u8>, priority: i64, run_at: i64,
attempts: i64, max_attempts: i64, worker_id: Option<String>,
claimed_at: Option<i64>, claim_expires_at: Option<i64>, created_at:
i64, expires_at: Option<i64>, visibility_timeout_s: i64,
backoff_base_s: i64, dead_letter_retention_s: Option<i64>,
last_error: Option<String>, died_at: Option<i64>` — plus
`payload_as<T>()` decoding stored bytes with `Codec` on failure
(ADR-020 §4).
- `JobState` enum: `Pending | Processing | Dead`.
- `Schedule { name: String, spec: String, queue: String, opts:
ScheduleOpts }` (ADR-019 §3).
- `StreamEvent { offset: i64, stream: String, key: Option<String>,
payload: Vec<u8>, created_at: i64 }` (ADR-015 §3) — plus
`payload_as<T>()`.
- `Wake { channel: String }` (ADR-008 §3).
**StopToken** (ADR-019 §4): `#[derive(Clone)]`, `cancel()` flips every
clone, `is_cancelled()`. Core-owned; the await mechanics are
engine-internal (tokio watch per engine) — core's token is the
contract-visible handle. Implement with a `std::sync::Arc<AtomicBool>`
or equivalent; engines may wrap their own watch channels around it.
**Payload encoding posture** (ADR-020 §4): the trait crosses
`serde_json::Value`; core provides the encode helper (serialize the
`Value` to bytes — the stored form) and the `payload_as<T>` decode
convenience used by `Job`/`StreamEvent`. `serde`/`serde_json` are
core's only dependencies.
All types re-exported from `src/lib.rs` (module-per-file under `src/`,
family standard). Doc comments on public API carry the pinned
semantics text (e.g. `heartbeat`'s absolute-reset note) — the docs are
part of the contract.
## Acceptance Criteria
- [ ] All types above with the exact pinned fields/types/derives;
`#[non_exhaustive]` on read types, absent on opts structs
- [ ] `Default` impls: `EnqueueOpts` (all-None/0), `QueueOpts`
(300/3/5/None), `ScheduleOpts`
- [ ] `payload_as<T>` round-trips a serde_json `Value` through encode →
decode; non-serializable target type yields `Codec`
- [ ] `StopToken`: cancel on a clone flips all clones; unit-tested
- [ ] `cargo test -p alkstore`, clippy `-D warnings`, fmt clean
## References
- docs/architecture/decisions/008-contract-v1-pinning.md §1, §3
- docs/architecture/decisions/010-queue-semantics-depth.md §3, §3a
- docs/architecture/decisions/015-streams-depth.md §3
- docs/architecture/decisions/017-contract-versioning.md §3
- docs/architecture/decisions/019-mechanism-handle-surfaces.md §3, §4
- docs/architecture/decisions/020-enqueue-opt-semantics-and-bridges.md §1–§4
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+102
View File
@@ -0,0 +1,102 @@
---
id: fork-port-connection-watcher
name: Fork port — connection architecture + watcher machinery
status: pending
depends_on: [fork-substrate-scaffold]
scope: broad
risk: medium
impact: component
level: implementation
tags: [wave-2, substrate]
---
## Description
Port the connection/watcher half of honker-core into
`alkstore-sqlite/src/substrate/` — the machinery the quality read
verified clean and ADR-011 §scope inherits near-verbatim. Source:
`/workspace/honker/honker-core/src/` @ `f4e53c6` (`lib.rs` ~4k lines,
`shm_watcher.rs`, `kernel_watcher.rs`).
**Port (the kept half, ADR-011 / quality-read §6):** the PRAGMA/WAL
open posture + `set_journal_mode_wal` retry logic; `Writer`; `Readers`;
the polling watcher + `SharedUpdateWatcher` + `WatcherDeathGuard` +
`stat_identity` dead-man's switch; the `in_savepoint` /
`UnwindUndo` mutation-discipline machinery; the REAL-coercion arg
helpers; the notify scalar + notifications table; stream functions;
lock functions.
**Port deltas (ADR-012 §4 — the only deliberate behavior changes):**
- W-1: bounded reconnect backoff in the watcher loop (no ~1000
open-attempts/sec on a vanished db file).
- W-2: watcher spawn becomes fallible; callers (the engine, wave 3)
surface the failure at open time.
- Dead-man's switch: the db-file-identity-change panic is replaced by
a deliberate watcher-fatal death (log the precise diagnostic, exit
through the ordinary death path); `WatcherDeathGuard`'s
death-closes-subscribers behavior unchanged.
**Drops (do not port):** `cron.rs`; the `kernel-watcher` /
`shm-fast-path` experimental watcher backends and their optional deps
(`notify`, `memmap2`, `libc`); the rate-limit and result tables; the
superseded queue functions (the queue half is re-derived in a separate
task).
**Table naming:** `_honker_*` → `__alkstore_*` across the ported
storage surface (ADR-011; ADR-010 §8 authorization). No online
migration machinery — fresh bootstrap only (ADR-012 §5).
**Fidelity posture (ADR-012 §3):** keep upstream's module structure and
internal function names for the kept half, including lineage tokens
(`Writer`, `run_poll_loop`, `in_savepoint`). Renames confined to the
table family and the hygiene deltas. The port is mechanical where
possible — the diff against the lineage must stay reviewable.
**Discipline deltas (family standard, ADR-012 §4):** no comments in
code (doc comments on the substrate's internal public surface fine),
no panics in library code (the dead-man's-switch delta is the big
one), no `unwrap()`/`expect()` outside tests. The substrate stays sync
— no tokio, no asyncification.
**Bootstrap re-keying (ADR-012 §5):** the fork owns the bootstrap;
append-column migrations stay; the duplicate-column race swallow is
re-keyed to `pragma_table_info` verification (present ⇒ benign race,
absent ⇒ propagate) — upstream's error-string matching is not
inherited.
**Tests (the floor, ADR-011):** inherit honker-core's suites for the
ported machinery — PRAGMA/WAL, watcher lifecycle + failure handling,
savepoint + multiprocess pressure — adapted to the new table names and
the three port deltas. These run as engine-crate tests (the substrate
is not separately testable through a public seam).
## Acceptance Criteria
- [ ] Kept machinery ported with upstream names/structure preserved;
diff vs. lineage reviewable (the wave-2 review checks this)
- [ ] Three watcher deltas applied; dead-man's switch exits without
panicking; `WatcherDeathGuard` still closes all subscribers
- [ ] Dropped machinery absent (no cron, no experimental watchers, no
rate-limit/result tables, no superseded queue functions)
- [ ] Table family is `__alkstore_*`; bootstrap fresh-only; race
swallow re-keyed to `pragma_table_info`
- [ ] Inherited test suites green (adapted); watcher-death-closes-
subscribers test present
- [ ] Substrate is sync (no tokio imports); no panics/`unwrap()` in
library code; clippy `-D warnings`, fmt clean
## References
- docs/architecture/decisions/011-sqlite-substrate-fork.md (scope register)
- docs/architecture/decisions/012-forked-substrate-design.md §3–§5
- docs/research/quality-read-honker-core.md §6 (the fork scope), §2 (watcher verdict)
- /workspace/honker @ f4e53c6 (the lineage)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+59
View File
@@ -0,0 +1,59 @@
---
id: fork-provenance-and-floor
name: Fork provenance register completion + inherited-test floor green
status: pending
depends_on: [fork-port-connection-watcher, fork-rederive-queue-ops]
scope: narrow
risk: low
impact: component
level: implementation
tags: [wave-2, substrate]
---
## Description
Close out the fork's bookkeeping and verify the test floor end-to-end
(ADR-018's adoption discipline; ADR-011's tests clause):
- **`PROVENANCE.md` delta register**: entries for every delta the port
actually made, category-tagged per ADR-018's categories — the three
watcher deltas (W-1, W-2, dead-man's switch), the table-family
rename, the bootstrap re-keying, the drops (recorded as
not-ported), the re-derivation (new code, contract-derived names),
and any mechanical adaptations the port surfaced that the plan
didn't anticipate. Each entry: what, why, the decision citation.
- **License/provenance cross-check**: the notice matches the actual
ported content; the register's revision citation (`f4e53c6`) matches
what was actually diffed.
- **Test floor**: run the full engine-crate test suite (inherited +
contract-property) as one gate; fix or explicitly record any
inherited test that the deltas legitimately changed (e.g. the
dead-man's-switch panic test becomes a watcher-fatal-death test).
- **Lineage-diff sanity pass**: produce (locally, not committed) the
diff of the substrate subtree vs. the honker-core sources at
`f4e53c6` and skim it for accidental divergence — anything not
covered by a register entry is either fixed or registered. This is
the wave-2 review's primary input.
## Acceptance Criteria
- [ ] `PROVENANCE.md` register complete: every delta categorized and
cited; no unregistered divergence from the lineage
- [ ] License notice accurate; revision citation verified
- [ ] Full substrate test floor green in one run; changed inherited
tests documented in the register
- [ ] Lineage diff skimmed; findings recorded
## References
- docs/architecture/decisions/018-provenance-register-and-cherry-picks.md
- docs/architecture/decisions/011-sqlite-substrate-fork.md (tests clause)
- docs/research/quality-read-honker-core.md §6
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+111
View File
@@ -0,0 +1,111 @@
---
id: fork-rederive-queue-ops
name: Fork re-derivation — queue ops on contract v1 (stamps, claim, sweep, get_job)
status: pending
depends_on: [fork-port-connection-watcher]
scope: broad
risk: high
impact: component
level: implementation
tags: [wave-2, substrate]
---
## Description
Re-derive the queue machinery in the substrate on contract v1 — the
new-code half of the fork (ADR-011's re-derivation list; quality-read
§6). This is the highest-risk task of wave 2: it is where ADR-010's
semantics depth becomes SQL, and where the upstream defect class (the
unreleased fix train's savepoint hardening, the `.ok()` error
swallows) must be *not* inherited.
**Re-derive (contract-derived names; the substrate API stays
contract-blind — primitives in, primitives out, ADR-012 §2):**
- **Enqueue + stamping** (ADR-010 §3a): job rows carry the
`QueueOpts` stamps (`visibility_timeout_s`, `max_attempts`,
`backoff_base_s`, `dead_letter_retention_s`) plus the `EnqueueOpts`
resolution inputs. The *resolution rules* (delay-over-run_at,
relative-expires, which stamps apply per call shape) are engine-layer
work (wave 3) — the substrate's enqueue takes already-resolved
primitive stamp values.
- **Single-statement claim** with per-row visibility from the job's
stamps: exactly-once handout under concurrency (POC-pinned shape),
`attempts += 1` per claim, claim ordering `priority DESC, ready-time
ASC, enqueue order`, claimant column stamped, `claimed_at` +
`claim_expires_at` set (ADR-019 §3's `Job` fields; ADR-021 §2).
- **Savepoint-guarded retry/fail/dead-letter** (ADR-010 §1–§4): the
DELETE→INSERT dead-letter moves are savepoint-hardened (the #133
defect class — a mid-flight error must strand the row in *neither*
table); retry computes nothing itself (the engine passes the
resolved delay in — the curve is engine-layer arithmetic per
ADR-012 §2); budget exhaustion and `fail` move to dead with
`last_error`/`died_at`; the pinned default strings (`"failed"`,
`"max attempts exceeded"`) are engine-layer (contract-derived
strings do not belong in the contract-blind substrate — pass them
in).
- **Both-states no-stranded-rows `sweep_expired`** (ADR-010 §5): moves
every past-`expires_at` row (pending and processing) to dead with
`last_error='expired'`, and enforces `dead_letter_retention_s`
deletion. Single-statement atomic per queue.
- **Dead-visible `get_job`** (ADR-010 §1): reads dead rows with
stamps, `claimed_at`, `last_error`, `died_at` — the zombie hole and
dead-visibility gap are the fork's motivating fixes; both land here.
- **Handle-op validity predicate support** (ADR-010 §2): the
ack/heartbeat/retry/fail transitions carry the uniform predicate
(row `processing` + caller's claim deadline unexpired) — false, not
error, on refusal; the ack-vs-reclaim race resolves atomically.
- **Scheduler tick over the new enqueue** with `@every` next-boundary
math (numeric, a few lines — cron machinery not ported): the
`__alkstore_scheduler_tasks` storage, boundary advance + fire
enqueue + row-locked advance, the 64-cap catch-up (ADR-009 §4). The
leadership-lock loop pattern is ported with the lock machinery; the
boundary math is the re-derivation.
**Schema:** the `__alkstore_*` job/dead/scheduler table family with the
stamp columns and `claimed_at` (ADR-012 §5 — the re-derivation adds
them); partial indexes matching the claim hot path, dead rows outside
it, single clock source (second-precision timestamps), per queues.md
§Namespaces.
**Tests:** the contract-property floor — no-stranded-rows, dead-visible
`get_job`, stamps immutability, claim exclusivity under concurrency,
the validity predicate's refusal boundaries, savepoint hardening
(mid-flight-error stranding test), scheduler boundary/catch-up math.
Plus the inherited queue-machinery tests adapted where applicable.
## Acceptance Criteria
- [ ] All re-derived ops above present with contract-blind primitive
APIs (no contract types, no error-taxonomy types in the
substrate)
- [ ] Savepoint hardening proven by test: a forced mid-flight error in
the dead-letter move strands no row in either table
- [ ] No `.ok()`-style error swallows (the D-class defects); all
substrate errors propagate typed
- [ ] Claim exclusivity under concurrent claims (multi-connection
test); reclaim consumes an attempt; validity predicate refuses
lapsed-deadline ops as values (false), not errors
- [ ] `sweep_expired` moves both states; retention deletion enforced;
no-stranded-rows property test green
- [ ] Scheduler: boundary advance + fire atomic under row lock;
64-cap catch-up; `@every` math unit-tested (s|m|h|d)
- [ ] `get_job` sees dead rows with full stamp/error fields
- [ ] `cargo test -p alkstore-sqlite`, clippy `-D warnings`, fmt clean
## References
- docs/architecture/decisions/010-queue-semantics-depth.md (§1–§6, §8)
- docs/architecture/decisions/009-scheduler-collapse.md §3–§4
- docs/architecture/decisions/012-forked-substrate-design.md §2, §5
- docs/research/quality-read-honker-core.md §3 (defect register), §6
- docs/research/reference-honker-machinery.md (the lineage mechanics, file/line cites)
- docs/architecture/queues.md (the semantics this realizes)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+60
View File
@@ -0,0 +1,60 @@
---
id: fork-substrate-scaffold
name: Fork scaffold — substrate subtree, provenance register, licenses
status: pending
depends_on: [scaffold-workspace]
scope: narrow
risk: low
impact: component
level: implementation
tags: [wave-2, substrate]
---
## Description
Create the fork's home and its provenance apparatus before any code
moves (ADR-011, ADR-012 §1 as amended by ADR-013, ADR-018):
- `alkstore-sqlite/src/substrate/` module subtree, wired into the
engine crate's module tree (`mod substrate;` — internal, never
re-exported through the engine's public API; ADR-012 §2's
contract-blind boundary starts structurally).
- `PROVENANCE.md` at the subtree root per ADR-018: upstream identity
(honker-core, checkout `/workspace/honker` @ `f4e53c6`, package
`node-v0.5.1-10-gf4e53c6`), license (MIT OR Apache-2.0), the fork
decision citation (ADR-011), and the empty delta register structure
(per-delta, category-tagged entries — the categories ADR-018 pins;
the port deltas of `fork-port-deltas` are the first entries).
- The dual-license notice carried in-tree (MIT OR Apache-2.0 text, per
ADR-012 §1's scaffold-time list).
- The substrate's `Cargo.toml`-independent posture: the subtree
compiles as engine-crate modules; its dependencies (rusqlite) come
from the engine crate's manifest.
This task moves no machinery — it establishes where the fork lives,
who it is, and how its lineage is tracked, so the port tasks land into
a finished frame.
## Acceptance Criteria
- [ ] `src/substrate/` exists, wired as an internal module, empty or
stub content only
- [ ] `PROVENANCE.md` complete per ADR-018 (identity, revision,
license, decision citation, delta-register structure)
- [ ] Dual-license notice in-tree
- [ ] Engine crate still compiles; clippy/fmt clean
## References
- docs/architecture/decisions/011-sqlite-substrate-fork.md
- docs/architecture/decisions/012-forked-substrate-design.md §1
- docs/architecture/decisions/013-fold-substrate-into-sqlite.md
- docs/architecture/decisions/018-provenance-register-and-cherry-picks.md
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+68
View File
@@ -0,0 +1,68 @@
---
id: review-wave-1
name: Review gate — wave 1 (core contract surface)
status: pending
depends_on: [core-trait-surface, contract-suite-scaffold]
scope: narrow
risk: low
impact: project
level: review
tags: [wave-1, review]
---
## Description
Review the wave-1 output before wave 2/3/4 work builds on it. The core
trait surface is versioned contract surface from the first release
(ADR-017) — a shape error found here is cheap; found in wave 5 it is a
migration. This review is line-by-line against the pinned ADR text,
not a general code review.
Check:
- **Trait surface vs. ADR text** — every method set and signature in
`core-trait-surface` against ADR-008 §1–§3/§8, ADR-014, ADR-019 §1–§5,
ADR-021: exact names, exact field types, the one-shot/repeatable
split (`heartbeat(&self)` vs `self: Box<Self>` ops), the
`recv_timeout`/no-`recv_timeout` asymmetry between the two receiver
traits, `with_tx`'s pinned signature.
- **Value types vs. ADR text** — `Job`'s full field list (including
`claimed_at`, ADR-021 §2), `StreamEvent`'s shape (ADR-015 §3), opts
structs' types and defaults (ADR-010 §3, ADR-020), the
`#[non_exhaustive]` split (read types yes, opts structs no —
ADR-017 §3).
- **Error taxonomy** — exactly six variants, no extras; `Database`
opaque with source chain; the pinning rule respected.
- **Validation** — both name classes correct (shared-namespace vs
consumer-local; ADR-008 §4 third-round annotation).
- **Family standards** — no panics, no `unwrap()`/`expect()` outside
tests, no comments in code (doc comments on public API fine),
module-per-file, re-exports from `lib.rs`.
- **Lean core** — no engine machinery, no resolution arithmetic, no
SQL-shaped anything in core (ADR-012 §2's boundary).
- **Suite scaffold** — ADR-022 exists and is coherent with ADR-017;
exemplar property green; stamp convention usable.
- **Gates** — `cargo test` (workspace), `cargo clippy --all-targets --
-D warnings`, `cargo fmt --check` all green.
## Acceptance Criteria
- [ ] Every divergence from pinned ADR text found and fixed (or an ADR
amendment proposed if the text itself is wrong)
- [ ] All gates green
- [ ] Findings recorded (task Summary or review notes)
## References
- docs/architecture/decisions/008-contract-v1-pinning.md
- docs/architecture/decisions/019-mechanism-handle-surfaces.md
- docs/architecture/decisions/021-tx-reads-and-value-shape-fixes.md
- docs/plans/implementation.md (Review gates)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+68
View File
@@ -0,0 +1,68 @@
---
id: review-wave-2
name: Review gate — wave 2 (substrate fork)
status: pending
depends_on: [fork-provenance-and-floor]
scope: narrow
risk: low
impact: component
level: review
tags: [wave-2, review]
---
## Description
Review the substrate fork before wave 3 builds the engine on it.
Primary lens: **diff reviewability against the lineage** (ADR-012 §3's
fidelity posture — the fork's economics rest on future cherry-picks
staying cheap) and **the defect class staying out** (the fork's
reason for existing).
Check:
- **Fidelity**: the kept half's module structure and internal names
match the lineage; renames confined to the table family and hygiene
deltas; the diff vs. `/workspace/honker` @ `f4e53c6` contains
nothing unregistered.
- **Port deltas**: W-1/W-2/dead-man's-switch applied as decided; no
other behavior changes smuggled in; W-4 (RW watcher connection)
deliberately kept.
- **Drops**: cron, experimental watchers + their optional deps,
rate-limit/result tables, superseded queue functions — absent, and
their absence registered.
- **Re-derivation vs. ADR-010**: stamps land per §3a; the claim
statement's ordering and visibility semantics match §1–§2; the
validity predicate is uniform across ack/heartbeat/retry/fail;
savepoint hardening real (test-read it, don't trust the test name);
sweep covers both states; the curve/boundary arithmetic is *not* in
the substrate (contract-blind boundary — ADR-012 §2).
- **Provenance**: register complete and accurate per ADR-018; license
notice correct.
- **Family standards** in the substrate: sync only, no panics, no
error swallows, no comments (doc comments fine).
- **Gates**: full engine-crate test suite, clippy `-D warnings`, fmt
clean.
## Acceptance Criteria
- [ ] Lineage diff reviewed; every divergence registered or fixed
- [ ] Re-derivation matches ADR-010's pinned semantics (spot-checked
against the ADR text, not just the tests)
- [ ] Provenance register accurate
- [ ] All gates green
- [ ] Findings recorded; wave 3 decomposition may proceed
## References
- docs/architecture/decisions/012-forked-substrate-design.md §3
- docs/architecture/decisions/010-queue-semantics-depth.md
- docs/architecture/decisions/018-provenance-register-and-cherry-picks.md
- docs/plans/implementation.md (Review gates)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+70
View File
@@ -0,0 +1,70 @@
---
id: scaffold-workspace
name: Cargo workspace scaffold (three crates per ADR-001)
status: pending
depends_on: []
scope: narrow
risk: low
impact: project
level: implementation
tags: [wave-1, workspace]
---
## Description
Create the Cargo workspace and the three crates of the ADR-001 split,
so every later task lands in an existing, compiling structure:
- `alkstore` — the core crate (contract surface only; no driver
dependencies, ever).
- `alkstore-sqlite` — engine crate stub; manifest carries the rusqlite
dependency (^0.40, `bundled` feature per ADR-003) and the core path
dependency. No code yet beyond an empty lib.
- `alkstore-postgres` — engine crate stub; manifest carries
tokio-postgres 0.7.x + deadpool-postgres 0.14.x (ADR-004) and the
core path dependency. No code yet beyond an empty lib.
Workspace-level: shared `[workspace.package]` fields (edition, license
MIT OR Apache-2.0, repository), `resolver = "2"` (or 3 per edition),
and a root `Cargo.toml` with `members`. Crate versions are `0.1.0`
placeholders — ADR-017 §1 pins the initial *release* at 1.0.0; that
bump is wave 6's release task, not this one.
The engines' manifests must pin the core dependency as a path
dependency with the version field set (the ADR-017 §4.1 manifest-pin
carrier) — `alkstore = { version = "0.1", path = "../alkstore" }`
shape, so the pin exists from the first commit.
Family standards apply from this commit onward: tokio async runtime,
`thiserror` errors, no panics in library code, no `unwrap()`/`expect()`
outside tests, no comments in code (doc comments on public API fine).
## Acceptance Criteria
- [ ] `cargo build` at the workspace root compiles all three crates
- [ ] `alkstore` has zero non-dev dependencies (serde/serde_json are
the allowed exception — ADR-020 §4 puts payload encoding in core)
- [ ] `alkstore-sqlite` depends only on `alkstore` + rusqlite;
`alkstore-postgres` only on `alkstore` + tokio-postgres +
deadpool-postgres (+ tokio)
- [ ] Engine manifests carry the versioned core path pin (ADR-017 §4.1
carrier 1)
- [ ] `cargo clippy --all-targets -- -D warnings` and
`cargo fmt --check` pass
- [ ] No CI wiring is added (manual CI per docs/plans/implementation.md)
## References
- docs/architecture/decisions/001-crate-split.md
- docs/architecture/decisions/003-sqlite-driver.md (rusqlite pin)
- docs/architecture/decisions/004-postgres-driver.md (pg driver pins)
- docs/architecture/decisions/017-contract-versioning.md §4.1
- docs/plans/implementation.md
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion