decompose wave 5 — contract suite: audit-first split of the verification backlog (engines' columns already discharge most rows; map in review-wave-5's appendix), seven mechanism-grouped suite-row tasks (queue depth, scheduler, tx commit-atomicity, streams, locks, wakes, opts/backoff equivalence), two engine-side hardening tasks (sqlite commit-error arm, pg F-1 defense-in-depth), and the review-wave-5 gate that flips the engine specs to stable

This commit is contained in:
glm-5.3-flash committed 2026-10-10 05:37:53 +00:00
1 parent 9a00fb8a7e
commit 250511d480
11 files changed
+911 -2

No files matched your search

+71 -2
View File
@@ -1,6 +1,6 @@
---
status: draft
last_updated: 2026-10-09 (pg-fix-tx-wake landed — the second HIGH fix, retiring F-1's engine arm; wave 5's remaining gate is the fix batch's review gate)
last_updated: 2026-10-10 (wave-4 fix batch landed and gate-verified — review-wave-4-fixes passed; wave 5 decomposed)
---
# alkstore — Implementation plan
@@ -36,7 +36,7 @@ parallel).
| [2](#wave-2--sqlite-substrate-fork) | honker-core fork into `alkstore-sqlite/src/substrate/`: port, deltas, provenance, test floor | wave 1 (workspace scaffold only) | implemented + reviewed (2026-10-08) |
| [3](#wave-3--sqlite-engine) | SQLite engine: connection architecture, re-derived queue ops on contract v1, scheduler/outbox, tx seam, SQLite backlog column | waves 1 + 2 | implemented + reviewed (2026-10-08) |
| 4 | [Postgres engine](#wave-4--postgres-engine): schema bootstrap, pool/open, listener/forwarder, all mechanisms, tx seam, pg backlog column | wave 1 | implemented + reviewed (2026-10-08) |
| 5 | Contract suite: the cross-engine equivalence properties (core-contract.md §Verification backlog), version-stamped per ADR-017 | waves 3 + 4 | not yet decomposed |
| 5 | [Contract suite](#wave-5--contract-suite): the cross-engine equivalence properties (core-contract.md §Verification backlog), version-stamped per ADR-017 | waves 3 + 4 | decomposed (2026-10-10) |
| 6 | Release readiness: crate docs, deployment matrix final pass, README (written last, honestly), publish prep; mem-engine and fuzzing decisions | wave 5 | not yet decomposed |
## Wave 1 — Foundations
@@ -181,6 +181,69 @@ natural realization — the mechanism name *is* the wake channel
streams), legal by construction under the shared namespace — with
the reserved reconnect-wake channel staying the only reserved string.
## Wave 5 — Contract suite
Filling the scaffolded suite (`alkstore-contract-suite`) with the
remaining verification-backlog rows and running the compatibility
gate. Waves 3 and 4 each landed a nine/ten-row backlog column; the
wave-5 decomposition started from an audit of what those columns
already discharge versus what the backlog still lacks.
**Already discharged by the engines' columns** (the wave-5 review
gate verifies each claim by reading the pinning row, not by trusting
the audit): name validation (the exemplar, incl. `schedule()`'s
queue-argument validation), the numeric-domain rows (extents +
durations), `encode_payload` typed failure + round-trip, the
`PayloadTooLarge` occurrence asymmetry (both engine-scoped arms),
drop-=-rollback no-ghosts, in-tx read-your-own-writes (which also
discharges the `save_offset_tx` exactly-once backlog row across
itself and the drop-rollback row), receiver close/save arms + monotone
composition (both columns), and — pinned engine-side rather than as
suite rows, per the sqlite-engine-integration task's scoping call —
plain-path SQLite open and the watcher-cadence knob
(`open_tests.rs`; SQLite-scoped backlog rows).
**Wave 5's task set** (the gaps):
- Queue depth: the job-handle validity predicate, the ADR-010 depth
properties (reclaim-eats-attempt, dead-letter moves, the
no-stranded-rows sweep), and M-1's retention-failure pin
(`suite-queue-depth-rows`).
- Scheduler: boundary fires, bounded catch-up, leadership discipline —
with a documented extension of the suite's determinism posture to
admit runner-driving rows (`suite-scheduler-rows`).
- Tx seam: `outbox_enqueue_tx` and `publish_with_key_tx`
commit-atomicity, plus N-5's `with_tx` panic-disposition probe —
the panic path pinned against real engines for the first time
(`suite-tx-commit-atomicity-rows`).
- Streams: the cross-engine ordering-equivalence row and the full
`trim_to` semantics row (`suite-stream-rows`).
- Locks: TTL/expiry re-acquisition and the concurrent-`try_lock`
loser posture — the SQLite busy-path is the open question the row
exists to answer (`suite-lock-rows`).
- Wakes: the pinned `WakeReceiver` shapes, tolerance-bounded
(`suite-wake-rows`).
- Arithmetic equivalence: the backoff-curve row and the two
wall-clock-adjacent opts-resolution legs the existing row deferred
to this wave (`suite-opts-backoff-rows`).
- Engine-side hardening riding the window: the wave-3 review's
deferred SQLite commit-error-arm coverage (`sqlite-commit-error-arm`)
and F-1's defense-in-depth candidates — explicitly not load-bearing
since `pg-fix-tx-wake` retired the flake's root cause
(`pg-suite-infra-hardening`).
- The review gate (`review-wave-5`): the backlog discharge audit,
stamp verification, green-on-both-engines, the parked-disposition
audit — and the gate that flips `engine-sqlite.md` /
`engine-postgres.md` to `stable`.
Judgment calls the decomposition parked in task Notes (the review
gate audits each): where the M-1 retention-failure pin, the
beyond-64-cap skip-forward leg, and the backoff 1-hour-cap leg land
when the public surface cannot express them (engine-side is the
expected answer, recorded per case); whether the scheduler rows
demand the pg fire-wake parity the wave-4 fix-batch gate recorded;
and what the SQLite lock busy-path actually returns under contention.
## Decided points
- **Contract-suite layout — option (a)**: a small internal
@@ -261,6 +324,12 @@ decomposes. Specific gates:
`tasks/review-wave-4.md`) and making `tx_publishes_compose_with_the_handle`
deterministic (20 consecutive solo runs green); the remaining fixes
ride the wave-4 fix-batch decomposition below.
- **Wave 5 decomposition** (2026-10-10) — audit-first: the engines'
backlog columns already discharge most of the §Verification backlog
(map recorded in `tasks/review-wave-5.md`'s appendix for the gate to
verify); the wave fills the gaps with seven suite-row tasks grouped
by mechanism, two engine-side hardening tasks, and the gate. See the
Wave 5 section.
- **Wave 4 decomposition** (2026-10-08) — shaped as wave 3's
structural twin; wave-3 outcomes absorbed (constructors exist;
arithmetic re-owned per engine; per-engine `@every` parser;
+63
View File
@@ -0,0 +1,63 @@
---
id: pg-suite-infra-hardening
name: Postgres test-infra hardening — F-1's defense-in-depth candidates
status: pending
depends_on: []
scope: single
risk: low
impact: isolated
level: implementation
tags: [wave-5, postgres-engine, tests]
---
## Description
Land the defense-in-depth test-infra hardening the wave-4 review
recorded for F-1 (`tasks/review-wave-4.md` Notes) — **explicitly not
load-bearing since `pg-fix-tx-wake` retired F-1's engine-side root
cause** (the compose test is deterministic, 20/20 solo runs green at
the fix-batch gate). This task exists because wave 5 was the named
home ("wave 5's suite hardening, or a wave-5-adjacent test-infra
task") and the candidates are cheap; if the wave-5 window gets tight,
cancelling this task loses nothing but insurance.
Work (the review's candidate dispositions, cheapest first):
- **(a) Harden `must_recv_event`'s deadline** — replace the 20 ms
polling loop with a parked `recv().await` under a timeout wrapper;
the deadline assert still bounds the property, the poll-loop's
wake-subscription interaction surface goes away.
- **(b) A `LOG_LAGGED`-style diagnostic counter on the bridge** —
discriminates "wake never arrived" from "wake arrived, re-drain
missed" if a wake-shaped flake ever recurs. Land only if it is a
small, honest addition (a test-observation counter, `#[cfg(test)]`-
gated or eprintln-postured per the house test style); skip and
record if it wants more machinery than that.
Candidate (c) (leave the skip-posture as-is) is the do-nothing arm —
adopted by default for anything not landed.
## Acceptance Criteria
- [ ] `must_recv_event` parks on `recv().await` under a timeout
wrapper (no polling loop); the pg stream tests stay green
against the harness server (a full stream-module run)
- [ ] Candidate (b) either landed small or recorded as skipped with
the reason
- [ ] `cargo test -p alkstore-postgres` green (server-less skips
clean); clippy `-D warnings`; fmt clean
## References
- tasks/review-wave-4.md (F-1's Notes — the candidate dispositions)
- tasks/review-wave-4-fixes.md (F-1 retired; defense-in-depth posture)
- tasks/pg-fix-tx-wake.md (the engine-side fix that made this
non-load-bearing)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+127
View File
@@ -0,0 +1,127 @@
---
id: review-wave-5
name: Wave 5 review gate — the suite as compatibility instrument
status: pending
depends_on: [suite-queue-depth-rows, suite-scheduler-rows, suite-tx-commit-atomicity-rows, suite-stream-rows, suite-lock-rows, suite-wake-rows, suite-opts-backoff-rows, sqlite-commit-error-arm, pg-suite-infra-hardening]
scope: broad
risk: low
impact: project
level: review
tags: [wave-5, review-gate]
---
## Description
The wave-5 review gate (docs/plans/implementation.md §Review gates):
"the suite as compatibility instrument — every backlog row present,
version-stamped, green on both engines; this is the gate that flips
the engine specs to `stable`."
Primary lenses:
1. **Backlog audit — every row present.** Walk
core-contract.md §Verification backlog item by item against the
suite (and the engine-side pins for the SQLite-scoped rows) and
record the discharge map. The decomposition's audit (below) is the
starting point — verify each "discharged" claim by reading the
pinning row/test, not by trusting the map.
2. **Version stamps.** Every row's `Contract stamp:` items cite ADR §
per `version_stamp.rs`'s convention; stamps accumulate on
amendment (the extended `enqueue_opts_resolution` row is the
amendment case to check); greppable via `STAMP_MARKER`. Cross-check
against ADR-017 §2's no-silent-change discipline: every stamp's
cited ADR § actually pins the behavior tested.
3. **Green on both engines.** The SQLite column green server-less;
the pg column green against the harness server (twice, the
wave-4 gate's posture). Workspace gates green
(build/test/clippy/fmt).
4. **Dispositions audited.** The decomposition parked several
judgment calls in task Notes — verify each was made and recorded:
the M-1 retention-failure pin's location (suite row vs engine-side),
the beyond-64 skip-forward leg's location, the backoff cap leg's
location, the scheduler fire-wake parity call, the lock-row
divergence call (if any), the SQLite reconnect-success test
taken/not-taken.
5. **Conformance spot-checks.** The rows assert what the ADR text
pins (no weaker, no stronger — a row pinning beyond the ADR text
is a suite defect: it would fail a correct engine); the
determinism posture holds (tolerance-bounded state assertions
only, no timing-value asserts, no two-task races) including the
scheduler rows' documented posture extension; no duplication
between rows' legs.
6. **Flip the engine specs to `stable`** — `engine-sqlite.md` and
`engine-postgres.md` frontmatter `status: draft` → `status:
stable`, with dated `last_updated` annotations citing this gate.
Update `docs/plans/implementation.md`: the wave table's wave-5
status, a review-rounds entry for this gate.
## Acceptance Criteria
- [ ] Backlog discharge map recorded (every §Verification backlog item
→ its pinning row/test or an explicit gap with a disposition)
- [ ] Every row version-stamped per convention; amendment stamps
accumulated; stamps verified against the cited ADR text
- [ ] Suite green on both engines (SQLite server-less; pg vs harness,
two consecutive full runs); workspace gates green
- [ ] All parked dispositions from the wave-5 tasks audited and
recorded
- [ ] Engine specs flipped to `stable` with dated annotations;
implementation.md updated (wave table + review rounds)
- [ ] Findings recorded; wave 6 decomposition may proceed
## References
- docs/plans/implementation.md §Review gates (wave 5), §The waves
- docs/architecture/core-contract.md §Verification backlog
- docs/architecture/decisions/017-contract-versioning.md §4.2
- docs/architecture/decisions/022-contract-suite-layout.md
- alkstore-contract-suite/src/version_stamp.rs (the stamp convention)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
## Appendix — the decomposition's backlog audit (verify, don't trust)
Discharged by existing rows (waves 1/3/4 — verify each):
| Backlog item | Discharged by |
|---|---|
| Name validation at every entry point (ADR-008 §4) | `name_validation_rejects_empty_and_reserved` (exemplar) |
| `schedule()` queue-argument validation (ADR-021 §3) | same exemplar row |
| Numeric-domain semantics (ADR-023 §2) | `extent_clamp_semantics` + `duration_refusal_on_non_positive_ttl` |
| `encode_payload` typed failure + round-trip (ADR-023 §1, ADR-020 §4) | `payload_round_trip_stores_exact_encoding` |
| `PayloadTooLarge` occurrence asymmetry (ADR-016 §5) | `payload_too_large_never_produced_on_sqlite` + `payload_too_large_produced_on_pg` |
| Drop = rollback no-ghosts (ADR-021 §4) | `drop_rollback_leaves_no_ghosts` |
| In-tx read-your-own-writes (ADR-021 §1) | `in_tx_reads_see_own_writes` |
| `save_offset_tx` exactly-once-within-a-business-tx shape | discharged across `in_tx_reads_see_own_writes` (own-save visibility) + `drop_rollback_leaves_no_ghosts` (the save never lands on rollback) — verify the combination genuinely covers the backlog row's claim |
| Receiver error/close arms + save-offset monotone composition (ADR-021 §5, ADR-019 §6) | `receiver_close_and_save_arms` (both columns; the pg reconnect-stays-open leg pins engine-side — verify that pin exists) |
| Plain-path SQLite open (ADR-023 §3) | engine-side: `uri_shaped_open_path_is_a_literal_filename` (`open_tests.rs`) — SQLite-scoped backlog row per the sqlite-engine-integration task |
| Watcher cadence default + knob (ADR-023 §4) | engine-side: `poll_interval_flows_to_the_watcher_config` (`open_tests.rs`) — same scoping |
Added by wave-5 tasks (verify presence + stamps):
| Backlog item | Wave-5 task |
|---|---|
| Job-handle validity predicate (ADR-010 §2) | `suite-queue-depth-rows` |
| ADR-010 depth: reclaim-eats-attempt, dead-letter, no-stranded-rows sweep + M-1 retention pin | `suite-queue-depth-rows` |
| Scheduler boundary/catch-up/leadership (ADR-009) | `suite-scheduler-rows` |
| `outbox_enqueue_tx` commit-atomicity (ADR-014) | `suite-tx-commit-atomicity-rows` |
| `publish_with_key_tx` commit-atomicity (ADR-015 §2) | `suite-tx-commit-atomicity-rows` |
| `with_tx` panic disposition (N-5) | `suite-tx-commit-atomicity-rows` |
| Cross-engine stream ordering equivalence (ADR-015 §3/§4) | `suite-stream-rows` |
| `trim_to` full semantics (ADR-015 §5) | `suite-stream-rows` |
| Lock TTL/expiry re-acquisition + concurrent loser (ADR-008 §7) | `suite-lock-rows` |
| Wake semantics under `WakeReceiver` shapes (ADR-008 §3) | `suite-wake-rows` |
| Backoff-curve equivalence (ADR-010 §3) + deferred opts clock legs (ADR-020) | `suite-opts-backoff-rows` |
Engine-side hardening riding the wave-5 window:
| Item | Task |
|---|---|
| SQLite commit-error-arm coverage (wave-3 review deferral) | `sqlite-commit-error-arm` |
| F-1 defense-in-depth (pg test-infra; not load-bearing) | `pg-suite-infra-hardening` |
+68
View File
@@ -0,0 +1,68 @@
---
id: sqlite-commit-error-arm
name: SQLite engine — commit-error-arm coverage (wave-3 review's deferred test)
status: pending
depends_on: []
scope: narrow
risk: medium
impact: isolated
level: implementation
tags: [wave-5, sqlite-engine, tests]
---
## Description
Land the wave-3 review gate's deferred coverage item
(`tasks/review-wave-3.md` Notes: "Coverage of the commit-error arm —
no test induces a failing `COMMIT` ... natural home is wave 5's suite
hardening"): a test that drives a failing `COMMIT` through the SQLite
engine's `commit` path and pins the fix the review verified by
code-read — the writer slot is replenished via the `reopen` closure on
the failed `COMMIT` (no stranding), the error surfaces as the opaque
`Database`, and the store remains fully usable afterward (subsequent
`begin_tx`/auto-commit ops work).
The injection mechanism is this task's to find — candidates: a
`PRAGMA max_page_count` squeeze forcing `SQLITE_FULL` on a
space-consuming commit, or a `cfg(test)` fault seam in the seam layer
if the PRAGMA route cannot hit the COMMIT arm deterministically. The
wave-4 fix batch's `cfg(test)` config-seam precedent
(`pg-fix-forwarder-reconnect`) is the house pattern if a seam is
needed. Prefer the least-invasive mechanism that deterministically
reaches the arm; document the choice.
If the same session has cheap room for it, the waves-1–2 review's
optional add — the watcher reconnect-*success* path test (upstream's
W-1 test drives only the failure path; "a reconnect success test would
require a file appearing mid-run") — rides here **only if** the
engine kept the current watcher shape and the test is genuinely cheap;
otherwise leave it recorded as not-taken in Notes (it was a
suggestion, not an order).
## Acceptance Criteria
- [ ] A test induces a failing `COMMIT` on the SQLite engine and pins:
error surfaces (`Database`), writer slot replenished (a
subsequent `begin_tx` succeeds), no partial-commit residue
- [ ] The injection mechanism documented in Notes (PRAGMA vs seam, and
why)
- [ ] The watcher reconnect-success test either landed or explicitly
recorded as not-taken with the reason
- [ ] `cargo test -p alkstore-sqlite` green server-less; clippy
`-D warnings`; fmt clean
## References
- tasks/review-wave-3.md (Notes: the deferred commit-error-arm coverage)
- tasks/sqlite-engine-seam-tx.md (the `commit` replenish fix the test pins)
- tasks/pg-fix-forwarder-reconnect.md (the `cfg(test)` seam precedent)
- docs/reviews/001-waves-1-2-general-review.md §2 (the watcher
reconnect-success suggestion)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+78
View File
@@ -0,0 +1,78 @@
---
id: suite-lock-rows
name: Contract-suite rows — lock TTL/expiry re-acquisition + concurrent try_lock loser
status: pending
depends_on: []
scope: narrow
risk: medium
impact: phase
level: implementation
tags: [wave-5, contract-suite, locks]
---
## Description
Add the named-locks contract rows to
`alkstore-contract-suite/src/properties.rs` and wire them into both
engines' suite targets, discharging two backlog rows
(core-contract.md §Verification backlog): "Named-lock TTL/expiry
re-acquisition on SQLite" (pinned on pg by the POC's lock probe; on
SQLite it rests on the forked substrate's lock machinery — the suite
pins both engines to identical behavior) and "Concurrent `try_lock`
loser/error behavior on SQLite" (the substrate's busy-path under lock
contention is the thing to pin — the pg side returns cleanly).
Rows to add:
- **`lock_ttl_expiry_and_reacquisition`** — the ADR-008 §7 guarantee
row: mutual exclusion bounded by TTL + renewal. Pin: a held lock
excludes a second acquirer (`None`); after TTL expiry exclusion
lapses *silently* (no revocation event, no error — a second owner
acquires); the original holder's post-expiry `renew` is refused
(`false` — the deadline-lapse refusal, ADR-010 §2's uniform
predicate shape on the lock handle); `release` on the second owner
frees the name for a third acquirer. Short TTL (1 s) + tolerance
sleeps — sequential single-store, fits the determinism posture.
- **`concurrent_try_lock_loser_is_a_value`** — contention posture:
with a lock held, contender `try_lock` calls return the clean
no-work value (`None`) on **both** engines — never a `Database`
error, never a busy-throw surfacing (the SQLite substrate's
busy-path is the risk site; the pg side is the reference behavior).
Drive several contenders sequentially against one held lock, and a
release-then-contend cycle proving the loser path leaves no state
that blocks a later acquire.
Risk note: what the SQLite busy-path *actually* returns under
contention is the open question this row exists to answer — if it
surfaces `Database` where pg returns `None`, that is a real
conformance finding: stop, record it in Notes, and raise it (an
engine fix or an ADR-level asymmetry call) rather than pinning the
divergence silently.
## Acceptance Criteria
- [ ] Two rows exist, version-stamped (ADR-008 §7, ADR-019 §1,
ADR-023 §2 as applicable)
- [ ] Rows wired into both engines' `contract_suite.rs` targets
- [ ] SQLite column green server-less; pg column green against the
harness server
- [ ] Any engine divergence found is recorded in Notes with its
disposition (fix or documented asymmetry) — not pinned silently
- [ ] `cargo test -p alkstore-sqlite -p alkstore-postgres` green
(pg rows skip cleanly server-less); clippy `-D warnings`; fmt clean
## References
- docs/architecture/core-contract.md §Verification backlog (lock
TTL/expiry re-acquisition; concurrent try_lock loser)
- docs/architecture/decisions/008-contract-v1-pinning.md §7
- docs/architecture/decisions/019-mechanism-handle-surfaces.md §1
- docs/architecture/engine-sqlite.md (the substrate's lock machinery)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+81
View File
@@ -0,0 +1,81 @@
---
id: suite-opts-backoff-rows
name: Contract-suite rows — backoff-curve equivalence + the deferred enqueue-opts clock legs
status: pending
depends_on: []
scope: narrow
risk: low
impact: phase
level: implementation
tags: [wave-5, contract-suite, queues]
---
## Description
Complete the arithmetic-equivalence backlog rows
(core-contract.md §Verification backlog): "Backoff-curve and
stamp-resolution equivalence across engines" and the two
wall-clock-adjacent legs the existing `enqueue_opts_resolution` row
explicitly deferred ("The `run_at`-alone-literal and
neither-field-resolves-to-now legs ... pin with wave 5's equivalence
row").
Work:
- **Extend `enqueue_opts_resolution` in place** (one owner per
property — the row's own doc comment names this wave-5 completion;
stamps accumulate per the convention): the `run_at`-alone literal
leg (a future `run_at` is the row's ready time verbatim; a past
`run_at` resolves to ready-now — the boundary-total rule, ADR-023
§2) and the neither-field leg (ready now — tolerance-bounded
proximity, never a tight timing assert).
- **Add `backoff_curve_equivalence`** — the equal-jitter exponential
curve (ADR-010 §3): `delay ∈ [base·2^(a−1)/2, base·2^(a−1)]` per
attempt, uniform jitter over the lower half — *the range, not a
jitter label, is the definition*. Drive claim → `retry(err, None)`
cycles on a queue with a small `backoff_base_s` stamp (2 s keeps
the integer-second row resolution honest: ranges [1,2], [2,4],
[4,8] across the first attempts; the inter-attempt waits stay
tolerance-bounded and sequential), reading the computed delay back
via `get_job`'s `run_at` minus now, asserting range membership per
attempt on both engines — identical bounds is the equivalence pin
(ADR-012 §2: engine-side arithmetic, suite-pinned identical
outputs). Include the explicit-delay override leg
(`retry(err, Some(d))` honored — a `Some(0)` resolves ready-now per
the boundary-total rule). **The 1-hour cap leg is not
suite-pinnable** (reaching capped attempts requires waiting out
minute-scale delays); it stays pinned engine-side (both engines'
unit tests already pin the curve against the ADR text — the
wave-3/4 reviews verified it) — record that disposition in Notes
for the review gate.
## Acceptance Criteria
- [ ] `enqueue_opts_resolution` extended with both clock legs; its
doc comment's deferral note updated (stamps accumulate)
- [ ] `backoff_curve_equivalence` exists, version-stamped (ADR-010 §3,
ADR-020 §1–§3, ADR-012 §2, ADR-023 §2)
- [ ] Rows wired into both engines' `contract_suite.rs` targets
- [ ] SQLite column green server-less; pg column green against the
harness server
- [ ] The cap-leg disposition recorded in Notes
- [ ] `cargo test -p alkstore-sqlite -p alkstore-postgres` green
(pg rows skip cleanly server-less); clippy `-D warnings`; fmt clean
## References
- docs/architecture/core-contract.md §Verification backlog (backoff-curve
and stamp-resolution equivalence; enqueue-opts resolution equivalence)
- docs/architecture/decisions/010-queue-semantics-depth.md §3
- docs/architecture/decisions/020-enqueue-opt-semantics-and-bridges.md §1–§3
- docs/architecture/decisions/012-forked-substrate-design.md §2
- alkstore-contract-suite/src/properties.rs (`enqueue_opts_resolution`'s
deferral note)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+94
View File
@@ -0,0 +1,94 @@
---
id: suite-queue-depth-rows
name: Contract-suite rows — job-handle validity predicate + ADR-010 queue depth
status: pending
depends_on: []
scope: moderate
risk: medium
impact: phase
level: implementation
tags: [wave-5, contract-suite, queues]
---
## Description
Add the queue-depth contract rows to
`alkstore-contract-suite/src/properties.rs` and wire them into both
engines' suite targets (`alkstore-sqlite/tests/contract_suite.rs`
directly; `alkstore-postgres/tests/contract_suite.rs` via the
`harness_row!` macro), discharging the backlog rows "Job-handle
validity predicate on both engines" and the ADR-010 depth properties
(core-contract.md §Verification backlog). One owner per property
(ADR-012 §2 mirror): the rows are engine-independent, run against both
factories, version-stamped per the crate's convention.
Rows to add:
- **`job_handle_validity_predicate`** — the uniform predicate
(ADR-010 §2): an op succeeds only while the row is `processing` and
the claim deadline is unexpired. Pin: the late-heartbeat boundary
(heartbeat refused exactly when the deadline has lapsed — a short
`visibility_timeout_s` stamp plus a tolerance sleep past it; the
sleep-then-assert-state shape is sequential single-store and fits
the suite's determinism posture), the post-lapse ack refusal
(at-least-once: the worker's ack does not land after lapse),
`ack_batch` applying the predicate per id (lapsed or non-claimed ids
silently not counted — the returned count reflects only live
acks), `retry`'s refusal being `false` with the row untouched, and
the engine-default strings landing identically (`fail(None)` →
`"failed"`, exhaustion → `"max attempts exceeded"`).
- **`queue_depth_reclaim_and_dead_letter`** — the ADR-010 depth
properties: a visibility reclaim consumes an attempt (expired claim
→ reclaim → `attempts` incremented, `claimed_at` refreshed),
exhaustion moves the row to dead storage (`get_job` sees the dead
row with `last_error`/`died_at`), and `cancel` is an unconditional
delete (not an interrupt).
- **`sweep_no_stranded_rows`** — `sweep_expired()` moves *every*
past-expiry row (any state — pending-expired, processing-lapsed,
dead-past-retention) and enforces dead-letter retention; the
no-stranded-rows property (ADR-010 §5). Include the M-1 follow-up
pin here (the waves-1–2 general review's suggested wave-5 add): the
sweep's retention half rolling back with the sweep on failure —
**with an explicit disposition**: if a retention-DELETE failure is
not injectable through the contract surface (it likely is not —
the failure is storage-level), pin that leg engine-side
(SQLite substrate test seam) and record the disposition in this
task's Notes for the review gate to audit. The plan ordered "M-1's
retention-failure test → wave 5's contract-suite rows"; where the
pin actually lands is the implementer's honest call, documented.
Timing posture: short stamps (1 s visibility) + tolerance sleeps
(bounded well above the transition's resolution), asserting state
outcomes never timing values. No two-task races.
## Acceptance Criteria
- [ ] Three rows exist in `properties.rs`, `pub async fn(&dyn
StoreFactory)`, version-stamped (ADR-010 §2/§5, ADR-019 §3,
ADR-008 §5 as applicable; stamps cite ADR § per the
`version_stamp.rs` convention)
- [ ] Rows wired into both engines' `contract_suite.rs` targets
- [ ] SQLite column green server-less; pg column green against the
harness server
- [ ] The M-1 retention-failure pin's location decided and recorded in
Notes (suite row or engine-side, with the reason)
- [ ] `cargo test -p alkstore-sqlite -p alkstore-postgres` green
(pg rows skip cleanly server-less); clippy `-D warnings`; fmt clean
## References
- docs/architecture/core-contract.md §Verification backlog (job-handle
validity predicate; ADR-010 depth properties)
- docs/architecture/decisions/010-queue-semantics-depth.md §2/§5
- docs/architecture/decisions/019-mechanism-handle-surfaces.md §3
- docs/reviews/001-waves-1-2-general-review.md (M-1, §4's suggested adds)
- tasks/sqlite-engine-integration.md (the wave-5 lesson: rows are
mock-blind until a real single-writer engine runs them)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+93
View File
@@ -0,0 +1,93 @@
---
id: suite-scheduler-rows
name: Contract-suite rows — scheduler boundary/catch-up/leadership (+ determinism-posture extension)
status: pending
depends_on: []
scope: moderate
risk: medium
impact: phase
level: implementation
tags: [wave-5, contract-suite, scheduler]
---
## Description
Add the scheduler contract rows to
`alkstore-contract-suite/src/properties.rs` and wire them into both
engines' suite targets, discharging the backlog row "Scheduler + depth
properties on Postgres" (core-contract.md §Verification backlog): the
tick machinery — boundary advance, bounded catch-up, leadership-loss
discipline — pinned engine-uniform (SQLite inherits the substrate's
test-pinned implementation; pg re-derived; the suite pins identical
*behavior*).
**Posture extension first** (test-side, ADR-017 §2 class 4): the
suite's determinism statement (`properties.rs` module doc) currently
says runner spawning stays engine-test-side. The backlog explicitly
demands these rows in the suite, so amend the module doc to admit
**runner-driving rows**: a spawned `run_schedules(stop)` task with a
`StopToken`, assertions on state outcomes (jobs fired, leadership
returned) with tolerances bounded well above the boundary resolution —
never timing-value assertions, never two-task race windows. Document
the extension where the posture is stated.
Rows to add:
- **`scheduler_boundary_fires`** — register `@every 1s`, run the
runner briefly, stop: boundary fires land as ordinary claimable jobs
stamped with the `ScheduleOpts`/derived-default resolution
(ADR-009 §3, ADR-020 §3); no double-fire per boundary while one
leader runs (fire + boundary-advance commit atomically).
- **`scheduler_bounded_catchup`** — register, delay before starting
the runner (several elapsed boundaries), then run: missed boundaries
replay boundary-by-boundary (at-least-once per elapsed boundary),
the fired count bounded by the 64-cap (a multi-second downtime stays
far below it — the ≤64 catch-up leg is the suite-pinnable one).
**The beyond-cap skip-forward leg needs >64 elapsed boundaries
(~65+ s) and row-backdating the public surface does not express —
pin it engine-side where the schedule row's `next_fire_at` is
directly manipulable (both engines' scheduler tests), and record
that disposition in Notes for the review gate.**
- **`scheduler_leadership_discipline`** — two concurrent runners:
exactly one leader (fires not duplicated), the loser surfaces
`LeadershipLost` (ADR-009 §6) rather than silently co-ticking;
a stopped runner's clean-stop return shape per ADR-019 §4.
**Disposition note from the wave-4 fix-batch gate** (recorded, not a
defect): the pg tick's in-tx enqueue fires issue no `pg_notify` wake
(SQLite's watcher wakes on those commits) — a cross-engine
wake-latency-parity gap the re-poll safety net covers for correctness.
If this row's shape demands the parity, the one-call `wake_tx` inside
the tick's tx rides **this** task; otherwise record the row's
latency-posture in Notes and leave the engines as they are.
## Acceptance Criteria
- [ ] The determinism-posture extension documented in
`properties.rs`'s module doc (runner-driving rows admitted,
tolerance-bounded state assertions only)
- [ ] Three rows exist, version-stamped (ADR-009 §3/§4/§6, ADR-019 §4,
ADR-020 §3 as applicable)
- [ ] Rows wired into both engines' `contract_suite.rs` targets
- [ ] SQLite column green server-less; pg column green against the
harness server
- [ ] The skip-forward-leg disposition and the fire-wake-parity
disposition recorded in Notes
- [ ] `cargo test -p alkstore-sqlite -p alkstore-postgres` green
(pg rows skip cleanly server-less); clippy `-D warnings`; fmt clean
## References
- docs/architecture/core-contract.md §Verification backlog (scheduler
+ depth properties on Postgres)
- docs/architecture/decisions/009-scheduler-collapse.md §3/§4/§6
- docs/architecture/decisions/019-mechanism-handle-surfaces.md §4
- tasks/review-wave-4-fixes.md (the fire-wake-parity recorded-for-later note)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+80
View File
@@ -0,0 +1,80 @@
---
id: suite-stream-rows
name: Contract-suite rows — cross-engine stream ordering equivalence + trim_to semantics
status: pending
depends_on: []
scope: moderate
risk: low
impact: phase
level: implementation
tags: [wave-5, contract-suite, streams]
---
## Description
Add the streams contract rows to
`alkstore-contract-suite/src/properties.rs` and wire them into both
engines' suite targets, discharging two backlog rows
(core-contract.md §Verification backlog): "Cross-engine stream
equivalence" (ADR-015 §3/§4) and "`trim_to` semantics on both
engines" (ADR-015 §5 — the legs the existing `extent_clamp_semantics`
row does not carry).
Rows to add:
- **`stream_ordering_equivalence`** — the ordering guarantee row:
a publish sequence with keyed/unkeyed interleavings yields `offset
ASC` global FIFO per stream — same publish order → same read order
on `read_since`, `read_from_consumer`, and a subscriber's attach
drain alike; offsets are strictly increasing per stream (per-stream
relative order — absolute offset values are explicitly *not*
cross-pinned: pg bigserial vs SQLite AUTOINCREMENT); `key`
round-trips exactly (`None` stays `None`, `Some` stays `Some`, on
every read form); `stream` carries the stream name and `created_at`
is unix-seconds-at-publish (informational — tolerance-bounded
proximity to now, never an ordering assertion). The single-event
key/payload round-trip already pins in
`payload_round_trip_stores_exact_encoding` — this row owns the
*sequence/ordering* property.
- **`trim_to_semantics`** — the full ADR-015 §5 row: exact-boundary
trim (`offset <= horizon` — the horizon's own row deletes,
horizon+1 survives), surviving rows keep their offsets (gaps legal,
never renumbered — the negative-horizon/immutability legs already
pin in `extent_clamp_semantics`; this row owns the exact-boundary
and resume legs), a read from a trimmed-away region resumes at the
trim horizon's first remaining row, a saved offset below the horizon
stays a valid position marker (`get_offset` returns it;
`read_from_consumer` resumes at the horizon), trim emits no
dedicated wake and no notify (a pre-attached listener idles across
the trim — tolerance-bounded absence), and a subscriber with a
saved checkpoint never loses its place across a trim (its next read
continues from the horizon, not from a renumbered past).
## Acceptance Criteria
- [ ] Two rows exist, version-stamped (ADR-015 §3/§4/§5, ADR-019 §6
as applicable)
- [ ] Rows wired into both engines' `contract_suite.rs` targets
- [ ] SQLite column green server-less; pg column green against the
harness server
- [ ] No duplication with the existing rows' legs (cross-reference in
each row's doc comment which row owns which leg)
- [ ] `cargo test -p alkstore-sqlite -p alkstore-postgres` green
(pg rows skip cleanly server-less); clippy `-D warnings`; fmt clean
## References
- docs/architecture/core-contract.md §Verification backlog (cross-engine
stream equivalence; trim_to semantics)
- docs/architecture/decisions/015-streams-depth.md §3/§4/§5
- docs/architecture/decisions/019-mechanism-handle-surfaces.md §6
- alkstore-contract-suite/src/properties.rs (the existing rows whose
legs this task complements)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+83
View File
@@ -0,0 +1,83 @@
---
id: suite-tx-commit-atomicity-rows
name: Contract-suite rows — outbox/keyed-publish tx commit-atomicity + with_tx panic probe
status: pending
depends_on: []
scope: moderate
risk: low
impact: phase
level: implementation
tags: [wave-5, contract-suite, tx-seam]
---
## Description
Add the tx-seam commit-atomicity rows to
`alkstore-contract-suite/src/properties.rs` and wire them into both
engines' suite targets, discharging three backlog rows
(core-contract.md §Verification backlog): "`outbox_enqueue_tx`
commit-atomicity on both engines" (ADR-014), "`publish_with_key_tx`
commit-atomicity on both engines" (ADR-015 §2), and the N-5
`with_tx` panic-disposition probe (the waves-1–2 general review's
ordered wave-5 add — the panic path "can only be fully pinned once a
real engine's handle exists").
Rows to add:
- **`outbox_enqueue_tx_commit_atomicity`** — rollback drops the
backing-queue job row together with the business write (no ghost
job; `run_once` afterwards claims nothing); commit makes the job
claimable by `run_once` exactly when the business write commits
(drive a real delivery through the `Delivery` closure); the stamped
opts are the outbox's derived 60/5/5 set, `get_job`-visible and
identical on both engines (ADR-014 §1, ADR-010 §3a). The
reserved/empty outbox-name validation legs already pin in the
exemplar row — do not duplicate them.
- **`publish_with_key_tx_commit_atomicity`** — rollback drops the
keyed event row with the business write (no ghost event); commit
makes it visible to `read_since` (and a subscriber attach); the key
round-trips on the committed event. The empty-`Some`-key
`InvalidName` leg already pins in the exemplar — do not duplicate.
- **`with_tx_panicking_closure_rolls_back`** — a closure that panics
mid-flight ⇒ rollback with no residue (the uniform no-ghosts list:
job/event/notify/offset — ADR-021 §4's drop = rollback through the
panic path), and the store remains usable afterward (the SQLite
writer slot replenished — the wave-3 stranding fix's property, now
pinned at contract level). Mechanics hint: the panic crosses an
await inside `with_tx`'s own future, so drive it via
`tokio::spawn` + `JoinError::is_panic()` (a `catch_unwind` around an
async closure does not see the panic point); assert the panic
surfaced *and* the post-panic state. This is N-5's probe against
real engines for the first time — the SQLite handle-on-lease Drop
impl and the pg pooled-object discard arm must both survive it.
## Acceptance Criteria
- [ ] Three rows exist, version-stamped (ADR-014 §1, ADR-015 §2,
ADR-021 §4, ADR-007 as applicable)
- [ ] Rows wired into both engines' `contract_suite.rs` targets
- [ ] SQLite column green server-less; pg column green against the
harness server
- [ ] The panic probe passes on both engines (no writer-slot stranding,
no pooled-object leak) — if an engine fails here, that is a real
defect: stop, record, fix engine-side before completing the task
- [ ] `cargo test -p alkstore-sqlite -p alkstore-postgres` green
(pg rows skip cleanly server-less); clippy `-D warnings`; fmt clean
## References
- docs/architecture/core-contract.md §Verification backlog (outbox tx
commit-atomicity; keyed-publish tx commit-atomicity)
- docs/architecture/decisions/014-outbox-tx-enqueue.md §1
- docs/architecture/decisions/015-streams-depth.md §2
- docs/architecture/decisions/021-tx-reads-and-value-shape-fixes.md §4
- docs/reviews/001-waves-1-2-general-review.md (N-5)
- tasks/sqlite-engine-seam-tx.md (the Drop impl N-5's probe exercises)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion
+73
View File
@@ -0,0 +1,73 @@
---
id: suite-wake-rows
name: Contract-suite row — wake semantics under the pinned WakeReceiver shapes
status: pending
depends_on: []
scope: narrow
risk: low
impact: phase
level: implementation
tags: [wave-5, contract-suite, notify]
---
## Description
Add the wake-semantics contract row to
`alkstore-contract-suite/src/properties.rs` and wire it into both
engines' suite targets, discharging the backlog row "Wake semantics
under `WakeReceiver` shapes" (core-contract.md §Verification
backlog): the POCs verified wake delivery/coalescing through their own
probe types; the pinned `Wake { channel }` / recv forms (ADR-008 §3)
need the suite's own property on both engines.
Row to add:
- **`wake_receiver_shapes`** — the wake contract's pinnable core,
tolerance-bounded (wakes are best-effort hints — the row asserts
state outcomes, never delivery counts or latencies):
- a pre-attached listener receives a wake after a committed
`notify` on its channel (`recv`/`try_recv`/`recv_timeout` forms
all exercised; the wake's `channel` field matches the listened
channel — the one piece of semantic content a wake carries);
- a burst of notifies yields *at least one* wake — never an exact
per-notify count (coalescing is the documented engine asymmetry:
possibly coalesced on SQLite, per-notify on pg; the row pins the
floor, not the shape);
- a listener attached *after* a commit never sees that commit's
notify (no-replay — `recv_timeout` idles; the pg no-replay hole
and SQLite's burst coalescing are both legal under this pin);
- wakes from a *different* channel do not arrive on this listener
(channel-scoped delivery — the invalidation-key property caching
subscribers rely on).
The failure-surface arms (watcher death → `recv() -> None` on SQLite;
the synthetic reconnect-wake on pg) are the engine-differing close
arms — already pinned engine-side and in the receiver-arms row's
disposal leg; this row does not re-pin them (cross-reference in the
doc comment).
## Acceptance Criteria
- [ ] The row exists, version-stamped (ADR-006, ADR-008 §3)
- [ ] Wired into both engines' `contract_suite.rs` targets
- [ ] SQLite column green server-less; pg column green against the
harness server
- [ ] The row's doc comment cross-references the close-arm pins it
deliberately does not duplicate
- [ ] `cargo test -p alkstore-sqlite -p alkstore-postgres` green
(pg rows skip cleanly server-less); clippy `-D warnings`; fmt clean
## References
- docs/architecture/core-contract.md §Verification backlog (wake
semantics under WakeReceiver shapes); §notify/listen
- docs/architecture/decisions/006-wake-and-delivery-contract.md
- docs/architecture/decisions/008-contract-v1-pinning.md §3
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion