174 lines
9.1 KiB
Markdown
174 lines
9.1 KiB
Markdown
---
|
|
id: mem-engine-foundations
|
|
name: Mem engine — crate scaffold, `MemStore::new()`, guarded state core, wake routing
|
|
status: completed
|
|
depends_on: []
|
|
scope: moderate
|
|
risk: medium
|
|
impact: component
|
|
level: implementation
|
|
tags: [wave-6, mem-engine]
|
|
---
|
|
|
|
## Description
|
|
|
|
The mem engine's foundations (wave 6 — [ADR-024](../architecture/decisions/024-mem-engine.md)):
|
|
a new workspace member `alkstore-mem` and the state core every later
|
|
task builds on.
|
|
|
|
- **Crate + manifest**: workspace member per
|
|
[ADR-024](../architecture/decisions/024-mem-engine.md) §1 —
|
|
dependencies are `alkstore` (versioned path pin per
|
|
[ADR-017](../architecture/decisions/017-contract-versioning.md) §4.1,
|
|
matching the other engines' manifests) plus tokio restricted to the
|
|
**wasm-supported subset (sync + time only)**. No driver
|
|
dependencies. `thiserror` via core; no other crates.io surface.
|
|
- **Constructor**: `MemStore::new()` — engine-native, no connection
|
|
string, no file path (the one documented exception to the
|
|
core-contract open prose, amended by
|
|
[ADR-024](../architecture/decisions/024-mem-engine.md) §4). Each
|
|
`new()` yields a fully isolated instance sharing nothing (the suite
|
|
factory's isolation contract); teardown is drop. No `PgOpts`-equivalent
|
|
opts struct exists — nothing to configure (if a knob surfaces during
|
|
the wave, `MemOpts` with `..Default::default()` follows the family
|
|
opts pattern; not assumed now).
|
|
- **Guarded per-mechanism state core**: the store instance is the
|
|
state — per-mechanism structures (queues+jobs, dead letters,
|
|
streams+offsets, locks, schedules) behind the family's locking
|
|
discipline (parking_lot short-held internal locks per AGENTS
|
|
convention 3; tokio primitives for cross-await coordination).
|
|
Layout is module-per-file under `src/`, mirroring the other
|
|
engines. Subsequent mechanism tasks own their tables' shapes; this
|
|
task owns the shared scaffolding: the guarded-state container idiom,
|
|
name validation at entry (same `InvalidName`/`ReservedName` rules as
|
|
every other engine — reserved prefix `__alkstore_`, the one
|
|
reserved string `__alkstore_listener_reconnected__` unused here but
|
|
still rejected), and the wake-routing substrate (in-process channels;
|
|
a mechanism registers its commit-ordered wake source, receivers
|
|
attach per channel).
|
|
- **Wake substrate shape**: no batching boundary exists — wakes
|
|
deliver at the commit boundary directly to each open receiver,
|
|
per-subscriber FIFO by commit order (engine-mem.md §Architecture;
|
|
coalescing not applicable). The routing core lives here; the
|
|
`listen()`/`WakeReceiver` surface lives in `mem-engine-notify-listen`.
|
|
- **Clock posture**: every timestamp (unix-seconds stamps, visibility
|
|
deadlines, TTLs, scheduler boundaries) reads the process clock —
|
|
there is no server clock to defer to. Keep the clock behind one
|
|
internal accessor so tests can observe timestamps deterministically
|
|
the same way the SQL engines' row shapes do.
|
|
|
|
The `Store` trait impl itself is ADR-007-shaped and lands in
|
|
`mem-engine-seam-tx` (the tx overlay is the load-bearing half);
|
|
this task lands everything up to the trait boundary.
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [x] `alkstore-mem` is a workspace member compiling clean; manifest
|
|
dependency surface is core + tokio (sync + time) only
|
|
- [x] `MemStore::new()` constructs; two instances share nothing
|
|
(pinned by a unit test)
|
|
- [x] The guarded state core, validation rules, wake-routing
|
|
substrate, and clock accessor exist with unit pins
|
|
- [x] `cargo check --target wasm32-unknown-unknown -p alkstore-mem`
|
|
clean from this task onward (tokio subset discipline enforced
|
|
here, not retro-fitted)
|
|
- [x] Workspace gates green (`cargo build`, `cargo test`, clippy
|
|
`-D warnings`, fmt)
|
|
|
|
## References
|
|
|
|
- docs/architecture/decisions/024-mem-engine.md §1/§4
|
|
- docs/architecture/engine-mem.md §Identity and posture, §Architecture
|
|
- docs/architecture/core-contract.md §Naming / reserved namespace
|
|
- tasks/pg-engine-schema.md (the wave-4 rhythm this wave mirrors — its
|
|
"foundations first" slot)
|
|
|
|
## Notes
|
|
|
|
> Filled by implementation agent, 2026-10-10.
|
|
|
|
1. **parking_lot rejected — the ADR won the manifest.** The
|
|
description's prose named "parking_lot short-held internal locks
|
|
per AGENTS convention 3", but ADR-024 §1 and the acceptance
|
|
criterion pin the manifest to core + tokio (sync + time) only.
|
|
The guarded-state container uses plain `std::sync::Mutex`
|
|
(wasm-fine, no crates.io surface) behind a `Guard` trait whose
|
|
`guard()` folds poisoning into `into_inner` — structurally
|
|
unreachable anyway, since no guard is ever held across an `.await`
|
|
or user-provided code; tokio primitives (`mpsc` unbounded) are the
|
|
cross-await coordination. Downstream mechanism tasks follow this
|
|
idiom (`state::Guard`).
|
|
2. **Guarded-state shapes deliberately empty** — `state.rs` carries
|
|
the container plus named empty per-mechanism sections
|
|
(`QueueState` = jobs + dead letters, `StreamState` = log + offsets,
|
|
`LockState` = TTL registry, `ScheduleState` = schedule table);
|
|
each mechanism task owns its section's actual shape.
|
|
3. **Wake substrate API shape** (`wakes.rs`):
|
|
`WakeBus::attach(channel) -> WakeFeed` (one unbounded mpsc feed per
|
|
subscriber; attach lands under the registry guard, so
|
|
attach-after-commit no-replay is structural),
|
|
`fire_commit_wakes(channels)` is the commit-ordered wake source's
|
|
entry point (mechanisms fire at the commit boundary; the guard
|
|
makes bump + per-channel fanout one operation — per-subscriber FIFO
|
|
by commit order, never coalesced), `close()` is the engine-drop
|
|
path (MemStore's `Drop` calls it; senders live only in the bus, so
|
|
a drop disconnects every feed regardless of who holds one), and
|
|
`commit_watermark()` is a `cfg(test)` ordering observable only —
|
|
never contract surface. `WakeFeed::rx()` exposes the raw mpsc
|
|
receiver; the consumer-visible `WakeReceiver` bridge
|
|
(recv/try_recv/recv_timeout forms) is
|
|
`mem-engine-notify-listen`'s surface over this primitive.
|
|
4. **Clock** (`clock.rs`): one accessor `Clock::unix_now()` reads
|
|
`std::time::SystemTime` (process clock only, per engine-mem.md);
|
|
`freeze`/`advance`/`unfreeze` are the `cfg(test)` deterministic
|
|
observation seam. `MemStore::clock()` `cfg(test)` accessor.
|
|
5. **Validation carries no new rule** — `validation.rs` is the pin
|
|
module only (empty/whitespace-only → `InvalidName`, reserved
|
|
prefix → `ReservedName`, `RESERVED_LISTENER_RECONNECTED` rejected
|
|
even though unused here); every future mem entry point routes
|
|
through core's `validate_shared_name`/`validate_local_name`
|
|
directly, same as the SQL engines.
|
|
6. **Scaffold dead-code posture**: targeted `#[allow(dead_code)]` on
|
|
the not-yet-wired scaffold surfaces (the `wakes` module, the empty
|
|
`State` sections, `MemStore`'s `state`/`clock` fields,
|
|
`Clock::unix_now`) so clippy `-D warnings` stays green with the
|
|
gate before consumers exist — the mechanism tasks own using (and
|
|
thereby exhausting) these allows.
|
|
7. **No `MemStore::close()`** — ADR-024 §4 says teardown is drop;
|
|
there is no explicit close form (the sqlite/pg engines' `close` is
|
|
connection machinery). If `mem-engine-integration` wants one for
|
|
symmetry, that is the place to decide.
|
|
8. **tokio `time` feature is pinned but currently unexercised** — no
|
|
mem code reads tokio time yet (lock TTLs and scheduler boundaries
|
|
are their tasks' consumers); the manifest carries it per this
|
|
task's description so the wasm-subset discipline is enforced at
|
|
birth rather than widened later.
|
|
|
|
## Summary
|
|
|
|
> Filled on completion, 2026-10-10.
|
|
|
|
Landed `alkstore-mem` as the fourth workspace member — crate scaffold
|
|
+ manifest (deps: `alkstore` path pin per ADR-017 §4.1 + tokio
|
|
`sync`/`time` only; dev-deps tokio `rt`/`macros`), five modules
|
|
(`store`, `state`, `wakes`, `clock`, `validation`), re-exported from
|
|
`lib.rs` whose crate docs carry the ADR-016 posture statement
|
|
(honest-ephemeral, full contract v1, wasm32-compile-clean identity).
|
|
`MemStore::new()` implements the engine-native isolated-constructor
|
|
contract (ADR-024 §4): new guarded state core, wake bus, and clock
|
|
per open, teardown = drop driving `WakeBus::close()`. Unit pins:
|
|
16 tests — two-instances-share-nothing (Arc pointer identity +
|
|
behavioral: frozen clock on one instance leaves the other's process
|
|
time intact; a fire through one instance's bus never reaches the
|
|
other's feed), guarded-state section independence/relock, wake
|
|
routing (channel-scoped per-subscriber delivery + foreign-channel
|
|
silence, per-commit-notify never coalesced, watermark-ordered FIFO,
|
|
attach-after-commit no-replay, close disconnects every live feed +
|
|
inert post-close fire, dropped feed detaches), engine-drop closes
|
|
open feeds, clock determinism (freeze/advance/unfreeze), validation
|
|
pins (4). Verified: `cargo build`, workspace `cargo test` green (pg 124 lib / 25 suite /
|
|
9 schema, sqlite 191 lib / 25 suite, core 25, suite harness 3,
|
|
**mem 17**), clippy
|
|
`--all-targets -D warnings` clean, `cargo fmt --check` clean, and
|
|
`cargo check --target wasm32-unknown-unknown -p alkstore-mem` clean
|
|
— the wave-6 wasm gate starts green from this task onward. |