Wave-3 decomposition: SQLite engine tasks (open/opts, seam+tx, mechanisms, integration, review gate) + core value-constructor pre-work; plan synced for waves-1-2 review + ADR-023 follow-through
This commit is contained in:
1 parent
44637eea5b
commit
1269246faf
11 files changed
+883
-5
No files matched your search
@@ -0,0 +1,91 @@
|
||||
---
|
||||
id: sqlite-engine-queues
|
||||
name: SQLite engine — queues (`Queue`, claims, job handles, backoff curve)
|
||||
status: pending
|
||||
depends_on: [sqlite-engine-seam-tx]
|
||||
scope: moderate
|
||||
risk: medium
|
||||
impact: component
|
||||
level: implementation
|
||||
tags: [wave-3, sqlite-engine]
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Implement `Store::queue` and the full `Queue` + `JobHandle` traits
|
||||
over the substrate's re-derived queue ops (`enqueue`, `claim_batch`,
|
||||
`ack`, `ack_batch`, `heartbeat`, `retry`, `fail`, `cancel`, `get_job`,
|
||||
`sweep_expired` — wave 2's owned code):
|
||||
|
||||
- `queue(name, opts)` — validated constructor; the handle carries the
|
||||
`QueueOpts` stamps future enqueues resolve over (ADR-010 §3a).
|
||||
- `enqueue(payload, opts)` — the one handle-carrying shape: resolve
|
||||
`EnqueueOpts` over **this handle's opts** (delay-over-`run_at`
|
||||
precedence, relative-`expires` → absolute `expires_at`, per-call
|
||||
`max_attempts` override — ADR-020 §1–§3); one clock read per
|
||||
enqueue; `encode_payload` at the seam.
|
||||
- `claim_one` / `claim_batch` — through the writer slot (claims are
|
||||
writes); map the substrate's JSON row output into `Job` +
|
||||
boxed `JobHandle`s. **Extent guard (ADR-023 §2)**: `n <= 0` yields
|
||||
the empty `Vec` at the trait-impl entry, before the substrate —
|
||||
`claim_batch(-1)` must never claim the whole queue (the `LIMIT -1`
|
||||
dialect artifact dies here; the substrate passes `n` through
|
||||
contract-blind). `worker_id` non-empty (`InvalidName`).
|
||||
- `JobHandle` impl: `job()` (the row value), `ack`/`retry`/`fail`
|
||||
(one-shot, `self: Box<Self>`), `heartbeat(extend)` (repeatable,
|
||||
absolute reset). The uniform validity predicate is the substrate's;
|
||||
refusals are `Ok(false)`, never errors.
|
||||
- **The backoff curve — engine-side, this task**: `retry(err, None)`
|
||||
computes the equal-jitter exponential from the job's stamped
|
||||
`backoff_base_s`: `delay ∈ [base·2^(a−1)/2, base·2^(a−1)]`, capped
|
||||
at 1 hour (ADR-010 §3's pinned formula — the engine owns one
|
||||
implementation; wave 4 re-derives it identically and wave 5 pins
|
||||
equivalence). Jitter needs a randomness source — use the substrate's
|
||||
existing posture if one exists, else `std`-only (no `rand` dep
|
||||
without a decision; a hash-of-id-and-time fallback is acceptable if
|
||||
documented — flag the choice in Notes). `retry(err, Some(d))` passes
|
||||
`d` through.
|
||||
- `ack_batch(ids)` — per-id validity predicate, count returned.
|
||||
- `cancel(job_id)`, `get_job(job_id)` (sees dead rows — map
|
||||
`last_error`/`died_at`), `sweep_expired()` (no leader lock
|
||||
required).
|
||||
- Exhaustion dead-letters with the substrate's pinned default strings
|
||||
(`"max attempts exceeded"`; `fail(None)` → `"failed"`).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Full `Queue` + `JobHandle` impls; enqueue→claim→ack lifecycle
|
||||
works; stamps visible via `get_job` match the resolution rules
|
||||
- [ ] Extent guard: `claim_batch(n <= 0)` claims nothing (empty
|
||||
`Vec`, no error, never the whole queue) — pinned per ADR-023
|
||||
§2's backlog row
|
||||
- [ ] Exactly-once handout under concurrent claims (two workers, one
|
||||
row — the POC-pinned property, engine-tested here)
|
||||
- [ ] Backoff curve: `retry(err, None)` delays land in the pinned
|
||||
range (statistical test over many retries, tolerance-bounded);
|
||||
cap at 1 hour; `Some(d)` overrides
|
||||
- [ ] Validity predicate: lapsed-deadline ops refuse with `Ok(false)`
|
||||
(heartbeat, ack, retry, fail); reclaim consumes an attempt
|
||||
- [ ] Dead-letter: exhaustion moves to dead with the pinned default
|
||||
strings; `get_job` sees dead rows with `last_error`/`died_at`
|
||||
- [ ] `sweep_expired` moves expired rows (both states) + applies
|
||||
retention; returns the moved count
|
||||
- [ ] `cargo test -p alkstore-sqlite`, clippy `-D warnings`, fmt clean
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/core-contract.md (queues section)
|
||||
- docs/architecture/decisions/010-queue-semantics-depth.md §1–§5
|
||||
- docs/architecture/decisions/019-mechanism-handle-surfaces.md §2, §3
|
||||
- docs/architecture/decisions/020-enqueue-opt-semantics-and-bridges.md §1–§3
|
||||
- docs/architecture/decisions/023-fourth-review-round.md §2
|
||||
- docs/architecture/queues.md (Retry/backoff, Dead-letter)
|
||||
- alkstore-sqlite/src/substrate/queue_ops.rs
|
||||
|
||||
## Notes
|
||||
|
||||
> To be filled by implementation agent
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Reference in new issue
Block a user