--- id: mem-engine-queues name: Mem engine — queues (in-memory job table, ADR-010 state machine, engine-owned backoff curve) 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, queues] --- ## Description The queue mechanism: the [ADR-010](../architecture/decisions/010-queue-semantics-depth.md) state machine realized over an in-memory job table — all driver-free. - **`Queue` handle surface** ([ADR-019](../architecture/decisions/019-mechanism-handle-surfaces.md) §1): `enqueue` (auto-commit, resolving over the handle's opts), `claim_one`/`claim_batch` (extents: `n <= 0` claims nothing), `ack_batch` (per-id uniform predicate, lapsed/non-claimed silently not counted), `cancel` (unconditional delete), `get_job`, and the queue-scoped `sweep_expired` (no-stranded-rows: *every* past-expiry row in any state moves to dead + retention enforcement). - **Claim discipline**: exactly-once handout, `worker_id` claimant identity (non-empty), claim ordering priority DESC → ready-time → enqueue FIFO. Visibility expiry and reclaim — **reclaim consumes an attempt**; the dual-execution window is contract-honest (a worker past its deadline may complete its work but its ack will not land). Under a single engine instance the claim race is in-process — the guarded merge discipline must still make claim-vs-claim atomic and claim-vs-expiry deterministic at the row level. - **JobHandle ops** ([ADR-019](../architecture/decisions/019-mechanism-handle-surfaces.md) §3): `ack`/`retry`/`fail` one-shot terminal, `heartbeat(&self)` repeatable absolute-reset renewal; the uniform validity predicate (processing + unexpired deadline); deadline lapse refuses all ops, false not error. `retry(err, None)` computes the queue's **equal-jitter exponential delay — mem's own implementation** ([ADR-012](../architecture/decisions/012-forked-substrate-design.md) §2's one-owner rule, third owner; the SQLite engine's `resolution.rs` is reference, not a dependency); `retry(err, Some(d))` overrides verbatim; `fail` = immediate dead-letter with the engine-default strings (`"failed"` / `"max attempts exceeded"`). - **Opts stamping** ([ADR-020](../architecture/decisions/020-enqueue-opt-semantics-and-bridges.md)): resolution rules (delay-over-`run_at`, relative `expires`) are mem-owned (third copy); handle-enqueue stamps the handle's opts; the tx/no-handle shapes stamped plain 300/3/5/none in `mem-engine-seam-tx`. `get_job` shows resolved values. - **Dead letters**: move-to-dead storage, `get_job` sees dead rows (`last_error`/`died_at`), `dead_letter_retention_s` (None = forever) enforced by `sweep_expired`. No redrive API. - Timestamps and expiry checks are tokio-time/clock reads against the state core's clock accessor; expiry is *checked* at claim/renew boundaries (no ambient sweeper — `sweep_expired` is caller-driven, cadence recipe unchanged). ## Acceptance Criteria - [x] Full `Queue`/`JobHandle` surfaces over the in-memory job table - [x] The ADR-010 state machine properties unit-pinned: visibility reclaim consumes an attempt, dead-letter moves, no-stranded-rows sweep, handle-op validity predicate, late-heartbeat boundary - [x] Equal-jitter curve + opts resolution pinned to the identical numeric behavior the suite's equivalence rows assert (per the engine-side pin posture used for the cap legs) - [x] Numeric-domain guards (extents) at entry — pinned - [x] `cargo check --target wasm32-unknown-unknown -p alkstore-mem` clean; workspace gates green ## References - docs/architecture/queues.md; core-contract.md §queues - docs/architecture/decisions/010-queue-semantics-depth.md; 019-mechanism-handle-surfaces.md §1/§3; 020 §1–§3; 023 §2 - docs/architecture/engine-mem.md §Mapping the contract (queues row) - tasks/pg-engine-queues.md (the structural twin's task of record — the wave-4 rhythm and the resolution rule set) ## Notes > Filled by implementation agent, 2026-10-10. 1. **The implementation already landed in the seam task** — `mem-engine-seam-tx` Note 1 records that the mem engine's full `Store` surface (including the `MemQueueHandle`/`MemJobHandle` mechanism in `alkstore-mem/src/queue.rs` and mem's third-owner `resolution.rs` arithmetic) is all-or-nothing per trait impl, so this task found its mechanism *implemented*. Its work was the coverage reconciliation against the description's pin list plus the missing engine-side unit pins — the same disposition the notify/listen twin task recorded (`mem-engine-notify-listen` Notes 1–3). 2. **Coverage reconciliation**: already pinned by the seam task's suite in `queue.rs`/`resolution.rs`/`tx.rs` — the full lifecycle + stamp resolutions (300/3/5/none + per-job override, resolved values shown by `get_job`), claim ordering (priority DESC → ready-time ASC → id ASC), extent clamps (`n = 0`/`n < 0` → empty at the entry, before any effect), empty-`worker_id` rejection, the uniform validity predicate (lapse refuses all ops, refusals leave the row intact), late-heartbeat refusal + absolute-reset renewal (with `claimed_at`/`attempts` untouched), reclaim-consumes-an- attempt + the loser's ack refusing (the dual-execution honesty), retry/fail default strings (`"max attempts exceeded"` / `"failed"`) + caller strings via the exhaust path, dead-row `get_job` visibility with `last_error`/`died_at`, the both-states no-stranded-rows sweep + retention (incl. forever-`None`) + queue scoping, the tx/no-handle stamp shapes (plain 300/3/5/none, outbox 60/5/5 in `tx.rs`), and the resolution legs (delay-wins- over-`run_at`, `run_at` literal, neither→now, negative-delay total, relative-`expires`) + the base-5 curve range/cap/spread pins in `resolution.rs`. 3. **What this task added** — the six unpinned shapes: (a) `concurrent_claims_hand_out_work_exactly_once` (8 racing workers over 4 rows via spawned `claim_batch` handles: every row exactly once — the guarded-section claim-vs-claim atomicity pin the description's "claim race is in-process" bullet asks for); (b) `claim_vs_expiry_is_deterministic_at_the_row_boundary` (at `now == claim_expires_at`: no reclaim (`d < now` false), the holder's heartbeat/ack still valid (`d >= now`) — the two predicates' boundary coherence; one second past: reclaimable with its attempt consumed; the lapsed id silently uncounted by `ack_batch`); (c) `the_equal_jitter_curve_mirrors_the_suite_rows_ numeric_bounds` (base 2, `max_attempts` 7 — the suite row's own shape: three claim→`retry(None)` cycles asserting [1,2]/[2,4]/[4,8] read back through `get_job`'s `run_at`, exact under the frozen clock; plus the override legs `Some(5)` verbatim and `Some(0)` ready-now per the boundary-total rule) — the engine-side pin of AC3's "identical numeric behavior"; the cap leg stays pinned in `resolution.rs`'s base-5 tests per `suite-opts-backoff-rows`'s recorded disposition; (d) `backoff_base_two_lands_in_the_suite_ rows_exact_bounds` in `resolution.rs` — the same base-2 bounds at the arithmetic function itself; (e) `the_pre_claim_sweep_dead_ letters_exhausted_reclaimables` (the D-12 shape: a lapsed row with its budget spent claims nothing and dead-letters with the exhaust string at the *next* claim — never strands claimable); (f) `cancel_is_unconditional_across_states` (pending + processing deleted, row-less `Ok(false)`, the holder's post-cancel ack refusing — the not-an-interrupt honesty). 4. **No production code changed** — the seam task's realization was verified correct as-found; this task is tests only (+5 in `queue.rs`, +1 in `resolution.rs`, 97 mem tests total). One test-authoring note: `heartbeat`'s absolute reset moves the deadline (`now + extend`), so a boundary-reclaim leg staged after a heartbeat on the *same* row reads the shifted deadline — the boundary test stages its legs on fresh rows to keep each observation exact. ## Summary > Filled on completion, 2026-10-10. Completed the mem engine's queues task against the seam task's foundation: reconciled the description's pin list against `alkstore-mem/src/queue.rs` + `resolution.rs` + `tx.rs`'s existing suite (13 pre-existing queue mechanism tests + the resolution and tx stamp pins), found the full `Queue`/`JobHandle` surfaces and nearly all ADR-010 properties already pinned, and added the six gaps: the in-process claim-race atomicity pin (8 concurrent workers, exact-once handout over the guarded section), the claim-vs-expiry boundary determinism pin (the `d < now` / `d >= now` predicates' edge-second coherence + the lapsed `ack_batch` non-count), the engine-side mirror of the suite's `backoff_curve_equivalence` row (base 2 → [1,2]/[2,4]/ [4,8] end-to-end through claim→`retry(None)`→`get_job.run_at` with the `Some(5)`/`Some(0)` override legs, and the same bounds pinned at `backoff_delay_s` itself; the 1-hour cap stays in `resolution.rs`'s base-5 pins per the recorded suite disposition), the D-12 pre-claim exhausted-reclaimable dead-letter sweep, and the both-states unconditional `cancel`. Acceptance criteria: full surfaces ✓ (seam task, lifecycle test), ADR-010 properties ✓ (seam tests + the new boundary/reclaim/sweep pins), curve + opts resolution numeric equivalence ✓ (new base-2 pins), extent guards ✓ (seam clamp test), gates ✓. Verified: `cargo test -p alkstore-mem` 97/97 (91 + 6 new, new tests green solo ×3), workspace `cargo test` green (core 25, suite-harness 3, mem 97, pg 124+25+9, SQLite 191+25), `cargo clippy --all-targets -- -D warnings` clean, `cargo fmt --check` clean, `cargo check --target wasm32-unknown-unknown -p alkstore-mem` clean — the wave-6 wasm gate stays green.