134 lines
6.8 KiB
Markdown
134 lines
6.8 KiB
Markdown
---
|
|
id: mem-engine-streams
|
|
name: Mem engine — streams (in-memory log, per-consumer offsets, trim)
|
|
status: completed
|
|
depends_on: [mem-engine-seam-tx, mem-engine-notify-listen]
|
|
scope: moderate
|
|
risk: medium
|
|
impact: component
|
|
level: implementation
|
|
tags: [wave-6, mem-engine, streams]
|
|
---
|
|
|
|
## Description
|
|
|
|
The streams mechanism over an in-memory event log + per-consumer
|
|
offset table (engine-mem.md §Mapping the contract, streams row):
|
|
|
|
- **`StreamHandle` surface** ([ADR-019](../architecture/decisions/019-mechanism-handle-surfaces.md)
|
|
§2): `publish` / `publish_with_key` (present key must be non-empty
|
|
→ `InvalidName`), `read_since` / `read_from_consumer` (cursor reads,
|
|
`offset ASC`, extents clamp empty per
|
|
[ADR-023](../architecture/decisions/023-fourth-review-round.md) §2),
|
|
`save_offset` / `get_offset` (monotone saves — below-checkpoint
|
|
saves are silent no-ops, `Err` arm carries `Database` only),
|
|
`trim_to` (delete `offset <= horizon`, exact-boundary, returns
|
|
deleted count, negative horizon deletes nothing idempotently,
|
|
**wakes nothing**, surviving rows keep offsets, never renumbered),
|
|
and `subscribe` (durable consumption: attach, read to tail, resume
|
|
from saved offset; wake-driven with re-read — the wake substrate
|
|
delivers at commit; explicit saves only, no auto-checkpoint).
|
|
- **Ordering**: engine-assigned monotonically increasing per-stream
|
|
offsets, global FIFO by offset — the ADR-015 ordering row, mem's own
|
|
counter (per-stream; absolute values need not equal the SQL engines'
|
|
— the equivalence rows pin relative order).
|
|
- **`StreamEvent` shape** exactly as pinned
|
|
([ADR-015](../architecture/decisions/015-streams-depth.md) §3):
|
|
`{ offset, stream, key, payload, created_at }` — `created_at` is
|
|
unix-seconds at publish, informational, from the clock accessor.
|
|
Reads from a trimmed-away region resume at the horizon's first
|
|
remaining row.
|
|
- **Keyed publish rides the tx overlay** (staged `publish_with_key_tx`
|
|
landed in `mem-engine-seam-tx`); this task's non-tx forms, receiver,
|
|
and trim live here.
|
|
- **Receiver close arms** ([ADR-021](../architecture/decisions/021-tx-reads-and-value-shape-fixes.md)
|
|
§5): `recv()`'s `Err` carries `Database` only (payloads cross raw;
|
|
`Codec` at `payload_as<T>`); close is terminal — the one close path
|
|
is engine drop (the notify-listen task's close arm, realized
|
|
identically); no reconnection concept (the pg stays-open-across-
|
|
reconnect row is absent from mem's column).
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [x] Full `StreamHandle` surface over the in-memory log +
|
|
per-consumer offsets
|
|
- [x] The ADR-015 properties unit-pinned: global FIFO, immutable
|
|
never-renumbered offsets (gaps legal post-trim), exact-boundary
|
|
trim semantics (reads resume at horizon; saved sub-horizon
|
|
offsets stay valid; trim wakes nothing), monotone save
|
|
composition (direct + receiver forms interleave)
|
|
- [x] Key round-trips exactly (`None` vs `Some`); empty-`Some`-key
|
|
rejected at every entry point
|
|
- [x] Extent/offset numeric-domain guards at entry — pinned
|
|
- [x] `cargo check --target wasm32-unknown-unknown -p alkstore-mem`
|
|
clean; workspace gates green
|
|
|
|
## References
|
|
|
|
- docs/architecture/core-contract.md §streams
|
|
- docs/architecture/decisions/015-streams-depth.md;
|
|
019-mechanism-handle-surfaces.md §2/§6; 021 §5; 023 §2
|
|
- docs/architecture/engine-mem.md §Mapping the contract (streams row)
|
|
- tasks/pg-engine-streams.md (the structural twin's task of record)
|
|
|
|
## Notes
|
|
|
|
> Filled by implementation agent, 2026-10-10.
|
|
|
|
1. **The implementation already landed in the seam task** — the same
|
|
all-or-nothing trait-impl disposition the notify-listen, queues,
|
|
and locks twins recorded: the `MemStreamHandle`/receiver mechanism
|
|
in `alkstore-mem/src/stream.rs` (log + offsets + trim + subscribe
|
|
+ the tx-staged keyed publish) was implemented with the seam's
|
|
whole-engine scope; this task found its mechanism *implemented*.
|
|
2. **Coverage reconciliation**: already pinned by the seam suite in
|
|
`stream.rs` — the round-trip FIFO row (offsets monotone, offset-ASC
|
|
reads, `created_at` from the clock, key `None` vs `Some` on the
|
|
yielded events), the empty/whitespace-`Some`-key rejection at the
|
|
auto-commit and `publish_with_key_tx` entry points, the read
|
|
extent clamps (`limit <= 0` → empty), the monotone-save row
|
|
(absent consumer 0, below/equal saves silent no-ops, forward saves
|
|
land), the exact-boundary trim row (`offset <= horizon`,
|
|
survivors keep offsets, negative horizon deletes nothing,
|
|
idempotent), the subscriber row (attach replays from the saved
|
|
checkpoint, wake-driven delivery, the receiver `save_offset()` form
|
|
composing with the direct form, the pre-yield save no-op), the
|
|
trim-wakes-nothing row (silent timeout with an open subscriber),
|
|
and the engine-drop close arms (terminal `Err(Closed)` on
|
|
`try_recv`, terminal `None` on `recv`, never reopens).
|
|
3. **What this task added** — the two unpinned shapes: (a)
|
|
`read_from_consumer_reads_the_cursor_and_clamps_the_extent` — the
|
|
surface test the seam suite lacked for the consumer-cursor read
|
|
(absent consumer reads from the head, checkpoint-forward resume,
|
|
tail reads empty, extent clamps, empty-consumer-name
|
|
`InvalidName`); (b) `saved_sub_horizon_offsets_survive_trim_and_
|
|
consumer_reads_resume_at_the_horizon` — the ADR-015 trim property
|
|
the trim row only half-pinned: the sub-horizon checkpoint is
|
|
untouched by trim (offsets never renumbered) and consumer reads
|
|
resume at the horizon's first remaining row.
|
|
4. **No production code changed** — verified correct as-found; tests
|
|
only (+2 in `stream.rs`, 99 mem tests total). One observation the
|
|
new rows recorded: a cursor read resumes at the first *surviving*
|
|
row after the checkpoint and continues to the tail (the pinned
|
|
resume-at-horizon semantic is a lower-bound guarantee, not a
|
|
one-row read).
|
|
|
|
## Summary
|
|
|
|
> Filled on completion, 2026-10-10.
|
|
|
|
Completed the mem engine's streams task against the seam task's
|
|
foundation: reconciled the description's pin list against
|
|
`alkstore-mem/src/stream.rs`'s existing suite (11 pre-existing
|
|
mechanism tests), found the `StreamHandle` surface and nearly all
|
|
ADR-015 properties already pinned, and added the two gaps — the
|
|
`read_from_consumer` cursor-read surface (head/cursor/tail/extent/
|
|
name-validation legs) and the sub-horizon-checkpoint-survives-trim +
|
|
resume-at-horizon row. Acceptance criteria: full surface ✓ (seam
|
|
task + the new cursor row), ADR-015 properties ✓ (seam rows + the new
|
|
trim row), key round-trip/empty-key rejection ✓ (seam rows), extent
|
|
guards ✓ (seam row + the new consumer-read clamp legs), gates ✓.
|
|
Verified: `cargo test -p alkstore-mem` 99/99 (97 + 2 new, new tests
|
|
green solo), workspace `cargo test` green, `cargo clippy --all-targets
|
|
-- -D warnings` clean, `cargo fmt --check` clean,
|
|
`cargo check --target wasm32-unknown-unknown -p alkstore-mem` clean. |