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

+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