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

+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