3.6 KiB
3.6 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||
|---|---|---|---|---|---|---|---|---|---|---|---|
| sqlite-engine-streams | SQLite engine — streams (`StreamHandle`, reads, subscribe, trim) | pending |
|
moderate | medium | component | implementation |
|
Description
Implement Store::stream and the full StreamHandle trait over the
substrate's stream ops (stream_publish, stream_read_since,
stream_save_offset, stream_get_offset — inherited near-verbatim;
the topic column carries the contract's stream field, the one name
delta, mapped at this layer):
stream(name)— validated constructor returning the boxed handle.publish/publish_with_key— auto-commit publishes through the writer slot;encode_payloadat the seam (Codecpropagates); return the assigned offset.read_since(offset, limit)/read_from_consumer(consumer, limit)— reader-pool reads, offset ASC (global FIFO); map rows intoStreamEventvia the core constructor task'sfrom_row. Extent guard (ADR-023 §2):limit <= 0yields the emptyVecat the trait-impl entry, before the substrate — theLIMIT -1dialect artifact dies here (the substrate passeslimitthrough contract-blind; the guard is the engine's obligation).save_offset(consumer, offset)/get_offset(consumer)— monotone saves (substrate's upsert already refuses regressions silently);get_offsetof an absent consumer = 0.trim_to(horizon)—DELETE FROM __alkstore_stream WHERE topic = ? AND offset <= ?inside the writer-slot lease; boundary argument is total (ADR-023 §2: a negative horizon deletes nothing, idempotently — no guard, pass through); returns the deleted count.subscribe(consumer)— the durable receiver: attach, read to tail from the stored offset, then wake-driven re-reads off the watcher fanout (same bridge shape aslisten, but yieldingResult<StreamEvent>per theEventReceivertrait —ErrcarriesDatabaseonly;None= closed on watcher death, terminal). Receiver-formsave_offset()saves the last-yielded event's offset through the same monotone op (ADR-019 §6);offset()reports the receiver's position.- Consumer identifiers (
consumer) take the non-empty rule only; stream names the shared-namespace rules.
Acceptance Criteria
- Full
StreamHandleimpl; publish/read/save/get/trim/subscribe all work against a temp-file store - Extent guard:
read_since/read_from_consumerwithlimit <= 0return the emptyVec(never the whole stream) — pinned per ADR-023 §2's backlog row trim_toexact-boundary (<=), negative horizon deletes nothing, surviving offsets never renumbered- Monotone saves: regression saves are silent no-ops; direct and receiver forms compose without regression
subscribereplays from the stored offset, then delivers post-attach events wake-driven; watcher death closes it (None, terminal);recv()'sErrcarriesDatabaseonly- Key round-trips exactly (
NonevsSome);streamfield carries the contract name (nottopic) cargo test -p alkstore-sqlite, clippy-D warnings, fmt clean
References
- docs/architecture/core-contract.md (streams section)
- docs/architecture/decisions/015-streams-depth.md
- docs/architecture/decisions/019-mechanism-handle-surfaces.md §6
- docs/architecture/decisions/023-fourth-review-round.md §2
- docs/architecture/engine-sqlite.md (Mapping the contract: streams)
- alkstore-sqlite/src/substrate/ops.rs (stream_* fns)
Notes
To be filled by implementation agent
Summary
To be filled on completion