Files
alkstore/tasks/review-wave-3.md
T
glm-5.3-flash 060f262e62 ADR-024 — the mem engine adopted as the family's third engine (wave 6; release readiness renumbered to wave 7)
Row-first gate: consumer-inventory.md gains an engine-tier section — the mem-engine need recorded operator-authority, dated 2026-10-10 (wasm32 sandboxing across the alk protocol family + downstream ffi/napi/python-adapter economics; rusqlite/tokio-postgres structurally out on wasm, so mem is the only triad member the sandbox can carry).

ADR-024 (decisions/024-mem-engine.md) pins the decision set: (1) separate `alkstore-mem` crate — discharges and amends ADR-001 §4 (superseded in part: full contract-v1 engine, not an "implementation convenience"; annotation on ADR-001 §4 + Status, class-1 window still open); (2) honest-ephemeral posture — no durability, single-process, ephemeral by design, never fleet-valid (ADR-010 §3's shared-pool predicate fails structurally), riding ADR-016's carriers unchanged (identity + docs + matrix); (3) full contract v1 tier with the asymmetry classification — `PayloadTooLarge` never produced (SQLite arm), no reconnection concept (reconnect-wake/watcher rows absent from mem's per-engine column), receiver close at engine drop, wake coalescing N/A; (4) `MemStore::new()` engine-native constructor — the one documented exception to the open-prose, core-contract.md Store concept amended; shared-instance (two opens → one engine) rejected with the ADR-016 §4-style re-entry gate (consumer-inventory row), register stays closed; (5) wasm32 — compile-clean for wasm32-unknown-unknown plus the suite's mem column green on host under a current-thread runtime flavor are wave-6 acceptance items; on-target suite execution scoped out with a named collapse condition (wasm-bindgen-test adapter crate) — a scope decision, not a pending-client hedge; (6) mem is not a core dev-dependency — core's doc examples stay mock-based, ADR-001 §4's doctest posture declines.

engine-mem.md (draft): the in-process mapping — guarded per-mechanism state (no pool, no schema bootstrap, no forwarder, no spawn_blocking seam), the tx overlay (staged *_tx effects merged atomically at commit; read-your-own-writes easy, drop-=-rollback trivial; wake-at-commit structural — the seam pg needed pg-fix-tx-wake for, mem pins from birth via the existing tx-commit-atomicity rows), ADR-012 §2 third owner of the curve/opts/@every arithmetic (three-way equivalence, ADR-022's StoreFactory reused verbatim — born pinned).

deployment.md: host-semantics row (single-process, ephemeral, never fleet-valid, wasm32-clean), mem connection-budget subsection (none — nothing to budget), durability-knobs row (the honest row is the absence), toolchain row (wasm32 subset + current-thread-clean, acceptance-cited). README/overview: engine-mem.md row, ADR-024 row, family table entry (alkstore-mem, no driver deps), current-state updated to the implementation phase with engine specs' stable statuses reflected.

implementation.md: wave table gains wave 6 (mem engine, depends waves 1 + 5) and renumbers release readiness to wave 7 (fuzzing decision — the one remaining decided-at-implementation deferral — rides wave 7); wave-6 section records the born-pinned posture, acceptance items, the one suite-harness pre-task (past_stamp_sleep blocking sleep → async sleep, review 003's properties.rs:1683 note), and the window riders (review 003 Finding 1: the pg transient-fault seam, MEDIUM, rides wave 6; Findings 2–3 follow their park into wave 7's pre-release); wave-6 review gate defined; Decided points updated (mem discharged, fuzzing renumbered); stale wave-6 references swept across review docs and the landed tasks' notes (release-readiness items renumbered or annotated).

Docs-only change; verified by cross-reference audit (all ADR/OQ/anchor references resolve, residual wave-6 mentions are correct under the new numbering or carry dated annotations).
2026-10-10 09:42:40 +00:00

231 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: review-wave-3
name: Review gate — wave 3 (SQLite engine)
status: completed
depends_on: [sqlite-engine-integration]
scope: moderate
risk: low
impact: phase
level: review
tags: [wave-3, review]
---
## Description
Review the SQLite engine before wave 4 (or 5) decomposes. Primary
lens per the plan's review gates: **engine-vs-contract conformance** —
the engine crate now carries the first real consumer surface, so this
is the gate that checks the code against the pinned contract text
line by line (the wave-1 gate's discipline, now against an
implementation that must *honor* the surface, not just declare it).
Check:
- **Contract conformance**: every trait method's behavior against
core-contract.md's per-mechanism text — validation at entry points
(auto-commit AND tx paths), error arms (`Database`-only receivers,
`Codec` at payload seams, `LeadershipLost` vs clean stop), value
shapes (`Job`/`StreamEvent` field-for-field), the no-work-is-a-value
vocabulary (`None`/`false`/empty-`Vec` where pinned, never errors).
- **ADR-023 follow-through**: the extent guards actually sit at the
trait-impl entry (not substrate-side patches); duration guards on
both lock call sites; plain-path open holds; `poll_interval` wired;
`encode_payload` consumed fallibly at every call site.
- **Seam integrity**: drop = rollback under panic; writer-slot lease
released on every path (commit, rollback, drop, error);
`spawn_blocking` used consistently (no blocking call inside async
context, no connection moved across await).
- **Substrate boundary**: still contract-blind (no `alkstore::` under
`src/substrate/`); the lint removal didn't leave dead ported code
behind; any new divergence registered in PROVENANCE.md.
- **Backoff curve**: the engine-side formula matches ADR-010 §3's
pinned range and cap exactly (wave 4 re-derives it; wave 5 pins
equivalence — this gate checks the one implementation against the
text).
- **Backlog column**: the engine's suite rows present, stamped, green.
- **Family standards**: no panics, no unwrap/expect outside tests, no
inline comments (doc comments fine), module-per-file.
- **Gates**: full workspace build/test/clippy/fmt.
## Acceptance Criteria
- [ ] Contract conformance spot-checked against the spec text (not
just the tests)
- [ ] ADR-023's five backlog rows verified implemented as decided
- [ ] Seam/lease/rollback paths verified (code-read, not test-name-trust)
- [ ] Substrate boundary + provenance register still clean
- [ ] All gates green
- [ ] Findings recorded; wave 4/5 decomposition may proceed
## References
- docs/plans/implementation.md (Review gates)
- docs/architecture/core-contract.md
- docs/architecture/engine-sqlite.md
- docs/architecture/decisions/023-fourth-review-round.md
- docs/reviews/001-waves-1-2-general-review.md (the posture this gate inherits)
## Notes
> To be filled by implementation agent
Two findings, both fixed inline (2026-10-08). Neither needed a new
session or scope decision:
- **W-1 (boundary) — `alkstore::Error` referenced inside
`src/substrate/mod.rs`**: `open_writer_connection` (the tx handle's
`reopen` helper) lived in the substrate subtree and mapped into the
contract taxonomy there — a violation of ADR-012 §2's
contract-blind boundary ("no `alkstore` core types", as the
substrate's own mod.rs doc text states; the wave-3 review
checklist's grep for `alkstore::` under `src/substrate/` caught
it). The machinery files (`schema.rs`/`ops.rs`/`queue_ops.rs`/
`watcher.rs`) were always clean — the violation was a wave-3
engine-layer helper that had been placed in the wrong file. Fixed:
the helper moved to `alkstore-sqlite/src/seam.rs` (the substrate's
one existing mapping site; identical body). No register entry —
nothing lineage-side changed.
- **W-2 (seam defect) — the writer slot stranded on three error
arms**: the task checklist's "writer-slot lease released on every
path" read found `Writer::acquire`d connections **dropped without
`release()`/replenish** on (a) `seam.rs::with_writer`'s op-error
arm, (b) `tx.rs::begin`'s failed `BEGIN IMMEDIATE` arm, and (c)
`tx.rs::commit`'s failed `COMMIT` arm. `Writer` holds exactly one
connection and has no self-replenish, so the first failed
auto-commit op (e.g. a busy timeout expiry on any notify/enqueue/
claim) would park *every* subsequent writer op forever on the
condvar — a store-wide livelock from one transient storage failure.
Fixed: (a) releases unconditionally on both arms (the failed op
didn't corrupt the connection — a refused statement leaves it
healthy; the release is safe); (b) releases on the begin-error arm
(the no-transaction connection is reusable); (c) drops the failed
connection (its state is unknowable — exactly the Drop path's
posture) and replenishes the slot via the existing `reopen`
closure, the same machinery the cancellation-path `ConnLease` and
the failed-rollback Drop already use. Regression tests added:
`failed_notify_releases_the_writer_slot` (notify_tests — trigger-
induced storage failure, then the slot proves grantable),
`failed_begin_releases_the_writer_slot` (tx_tests — SQLITE_BUSY
begin at a shortened busy timeout, then `begin_tx` succeeds).
Verified clean otherwise — spot-check record:
- Contract conformance (code-read against the spec text, per the
acceptance criterion): entry-point validation on every name-bearing
method, auto-commit AND tx paths (tx.rs does all eleven
`validate_shared_name`/`validate_local_name` at entry, before any
round trip); `InvalidName` for empty/whitespace-only, `ReservedName`
for prefixed, matching core's `validate_name`. Duration guards on
**both** lock call sites (`try_lock`, `SqliteLockHandle::renew` —
`ttl <= 0` → opaque `Database`, detail in the source chain, the
substrate guard's message preserved there). Extent guards at
trait-impl entry on `claim_batch` (`n <= 0` → empty `Vec`), both
auto-commit stream reads, both tx stream reads, and
`EventReceiver::read_since` — never reaching the substrate's
`LIMIT ?`. Boundary args unguarded-and-total (`trim_to` passes
through; a negative horizon deletes nothing idempotently).
`Error::Closed` on already-consumed handles; `Codec` at every
payload/decode seam; `recv()`'s `Err` remapped to `Database`-only
(`database_only`, stream.rs). No-work vocabulary held:
`Ok(None)`, `Ok(false)`, empty `Vec` where pinned.
- Value shapes: `Job::from_row` field set matches ADR-021 §2's struct
(claimed_at included); `StreamEvent::from_row` matches ADR-015 §3
(the `topic` column carries `stream`, the one registered delta);
dead-visible `get_job` carries `last_error`/`died_at`.
- ADR-023 follow-through: all five rows present in
`alkstore-contract-suite/src/properties.rs` with their contract
stamps and green in `alkstore-sqlite/tests/contract_suite.rs` (10
tests); extent guards/duration guards/`poll_interval`/plain-path
open/`encode_payload` fallibility verified at their sites (see
above); `encode_payload` consumed fallibly at all eight call sites,
no silent fallback.
- Backoff curve (queue.rs `backoff_delay_s`): equal-jitter over
`[base·2^(a−1)/2, base·2^(a−1)]` integerized inclusive, capped at
3600 s, attempt index = the row's post-claim count — matches
ADR-010 §3's pinned form exactly.
- Scheduler: tick fires + boundary advance in one `BEGIN IMMEDIATE`
(writer serialization as the row lock); leadership loss returns
`Err(LeadershipLost)` **before** ticking (top-of-loop renew, and
exit-before-tick also holds at start — a refused acquire is
`LeadershipLost`); the sleep renews the lease across itself; stop
token releases the lock cleanly; 64-cap catch-up + skip-forward
inherited (D-12).
- Seam integrity code-read: drop = rollback issues `ROLLBACK` and
releases the slot (failed rollback → connection dropped + slot
replenished via `reopen` — the same posture commit now uses);
panic-through-drop reaches the same path; the mid-op-future
cancellation path (ConnLease Drop) replenishes; `spawn_blocking`
used consistently — the only blocking-in-async exception is
`EventReceiver::save_offset`, a sync *trait signature* taking the
slot in-line, documented at the site (ADR-007's honest lease
posture; not a violation).
- Substrate boundary: no `alkstore::` reference remains under
`src/substrate/` (post-W-1-fix grep; also `grep -r "allow("`
clean — the wave-3 lint removals hold); PROVENANCE.md's
D-29..D-36 entries cover the wave-3 deltas (verified against the
code: URI flag dropped, `open_conn_bootstrapped`/D-30,
`ack_batch` worker-less/D-31, the five cuts D-32..D-36 all
genuinely absent); no unregistered divergence found in the
lineage-adjacent reads.
- Family standards: no panics/unwrap/expect in library code (only
`#[cfg(test)]` modules); inline `//` comments exist only at the
correctness-constraint sites the AGENTS.md exception names
(backoff curve's range pin, the extent guard, the leader loop's
one-owner-of-the-loss-decision note, `run_once`'s retry-string
posture — all non-obvious behavior notes, acceptable);
module-per-file holds.
- One doc-vs-code nit, absorbed in the W-2 fix rather than a
finding: `seam.rs`'s `with_writer` doc comment said "release on
both arms" — the code did not do that; now it does.
Deferred (documented, not fixed — each is small but not
inline-clean, or already owned by a later wave):
- **`Readers::acquire`'s `close()` path can drop a busy reader
connection while held** (schema.rs — `close()` clears the pool
but does not track outstanding acquisitions; a reader acquired
before `close()` and released after it is discarded by
`release()`'s closed check — correct, no leak; but a reader
connection *still in use* across store-close keeps running on a
closed file until its op completes — harmless in practice, the op
finishes on an open connection that is then dropped). Noted for
release readiness's close-path review (now wave 7); no consumer-visible defect.
- **Coverage of the commit-error arm**: no test induces a failing
`COMMIT` (needs SQLITE_FULL/IOERR-class injection; the code fix
is pinned by code-read + the shared `reopen` machinery being
exercised by the cancellation tests). Recorded here; natural
home is wave 5's suite hardening.
## Summary
> What landed, verified how:
**Review gate passed: 2 findings, both fixed inline; all gates green.**
- **Fixed — substrate boundary violation**: `open_writer_connection`
moved from `src/substrate/mod.rs` (which referenced
`alkstore::Error`, breaking ADR-012 §2's contract-blind boundary) to
`src/seam.rs`, the engine's one mapping site. Substrate grep for
`alkstore::` now clean.
- **Fixed — writer-slot stranding on error arms** (the
store-wide-livelock defect): `with_writer` now releases the slot on
the op-error arm; `begin` releases on a failed `BEGIN IMMEDIATE`;
`commit` replenishes via the existing `reopen` closure on a failed
`COMMIT`. Two regression tests pin the property end-to-end
(`failed_notify_releases_the_writer_slot`,
`failed_begin_releases_the_writer_slot`).
- **Verified clean**: contract conformance (entry-point validation,
error arms, value shapes, no-work vocabulary — full code-read
record in Notes), ADR-023's five follow-through items + backlog
rows (10 tests, stamped, green), backoff curve matches ADR-010 §3,
scheduler tick atomicity + exit-before-tick-on-loss, seam
integrity (drop = rollback incl. panic path, cancellation
replenish), substrate boundary + PROVENANCE D-29..D-36 accurate,
family standards.
- **Gates**: `cargo build`, `cargo test` (25 core + 188 sqlite + 10
suite + 3 harness), `cargo clippy --all-targets -- -D warnings`,
`cargo fmt --check` — all green after the fixes.
- **Recorded for later waves**: the reader-pool close-path note and
the commit-error-arm coverage gap (Notes; wave 5/6 natural homes).
Wave 4/5 decomposition may proceed.