8.9 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||
|---|---|---|---|---|---|---|---|---|---|---|---|
| core-trait-surface | Core trait surface (Store, TxHandle, mechanism handles, receivers) | completed |
|
moderate | medium | project | implementation |
|
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):
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_txprovided 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_txrationale 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
- Async posture: the "desugared boxed-future form" (ADR-008 §2) is
realized as a named alias,
BoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>insrc/future.rs— noasync_traitmacro, no core dependency beyond the pinned posture (Send bounds the futures:begin_txreturnsBox<dyn TxHandle + Send>, so its futures must be equally portable). Sync methods (try_recv/save_offset/offseton receivers,job(&self),name(&self)) stay sync as pinned. with_txdyn encoding: the pinned signature is non-generic — aStoretrait object must stay object-safe — so the generic-Fform does not compile on the trait; the pinnedFnOnce(&mut dyn TxHandle) -> BoxedFuture<Result<()>>shape crosses dyn-encoded asBox<dyn FnOnce ...>, aliasedWithTxClosure<'a>(re-exported) to keep the complex type out of signatures. Values return through captured state; the provided default body commits onOk, drops (rolls back) onErr.- Delivery callable encoding (ADR-019 §5's implementer's choice):
the named one-method trait object —
trait Delivery { fn deliver(&mut self, Box<dyn JobHandle>) -> BoxedFuture<Result<()>>; }— withrun_once(worker_id, delivery: &mut dyn Delivery).&mut dyn FnMutcannot return boxed futures of borrowed data under object safety, so the named-trait form is the workable answer; the engine acks on the closure'sOkand retries on itsErr. - Fallibility: the ADR sketches elide
Result<>for brevity (ADR-008 §2's note: every fallible op returns the §5 taxonomy); entry points validating names must err, so constructors (stream/queue/outbox/listen) areResult-returning as isnotify(PayloadTooLarge);begin_txreturnsResult(SQLite writer-slot acquisition can fail); no-claim and no-lock outcomes stay values (Option), handle-op outcomes staybools per ADR-008 §5's act-differently rule. - Scheduler error variants (
InvalidSpec { spec },LeadershipLost— ADR-009 §6) joinedErrorhere percore-errors-and-validation's note (exactly-six list was that task's scope; their signatures pin in this task). Unit-tested. tokioadded as a dev-dependency only (test to drive the boxed-future probes) — runtime deps remain exactly serde/serde_json/thiserror (ADR-020 §4, ADR-001: no driver deps in core).Wake's#[non_exhaustive]prevented the probe's struct-literal construction — the probe re-exports or constructs nothing of the read types beyond the existing value-type tests; trait-object construction is the probe's whole point.- Every trait doc carries the pinned semantics text: entry-point
validation obligations (Store; TxHandle's own mirror — name-bearing
*_txmethods validate like their auto-commit counterparts), drop=rollback (TxHandle), no-claim_txrationale (Queue), the uniform validity predicate + absolute-reset heartbeat (JobHandle), monotone saves (StreamHandle/EventReceiver), terminal-close arms and the deliberate no-recv_timeoutabsence (receivers).
Summary
The core trait surface — contract v1 in code — is implemented and
re-exported from alkstore/src/lib.rs: BoxedFuture +
TxHandle (the eleven pinned methods incl. ADR-021 §1's four tx
reads, commit(self: Box<Self>), drop=rollback docs);
Store (the pinned method set + the with_tx provided method in the
pinned dyn-encoded signature, with commit/rollback dispositions —
unit-tested); mechanism handles Queue/JobHandle/StreamHandle/
Outbox (+ Delivery)/Lock with the exact ADR-019 §1/§3/§5 method
sets; receivers WakeReceiver (timeout expiry = Ok(None); Err =
Database/Closed only) and EventReceiver (no recv_timeout,
deliberate); Error::InvalidSpec/LeadershipLost added (ADR-009
§6). Object-safety compile probes construct every trait object from
trivial local impls (trait_objects_construct), and the with_tx
dispositions run against a probe store (with_tx_commit_and_ rollback_dispositions) — 23 tests green; cargo test -p alkstore,
workspace cargo build, clippy -D warnings (workspace), and
cargo fmt --check all clean.