Contract-suite tx commit-atomicity rows (task suite-tx-commit-atomicity-rows): the N-5 panic-probe admission in properties.rs's module doc (spawned with_tx task joined via JoinError::is_panic — catch_unwind around an async closure cannot see the panic point across an await; post-panic assertions read-only until convergence, single-task drive, no race window; ADR-017 §2 class 4) and three version-stamped rows: outbox_enqueue_tx_commit_atomicity (rollback drops the backing-queue job with the business write — get_job_tx-gone + run_once claims nothing; commit makes the job claimable exactly when the business write commits, real delivery through the RecordingDelivery closure with job-id identity, derived __alkstore_outbox:mail queue, exact payload, 60/5/5 stamps, consumed-after-ack; ADR-014 §1, ADR-010 §3a, ADR-021 §4, ADR-007), publish_with_key_tx_commit_atomicity (rollback drops the keyed event with the business write, key round-tripping inside the tx; commit surfaces it to read_since and a post-commit subscriber attach with the key round-tripping; ADR-015 §2/§4, ADR-021 §4, ADR-007), and with_tx_panicking_closure_rolls_back (N-5's probe against real engines: the panicking closure writes all four kinds then panics mid-flight; panic surfaces via the join; no-ghost reads converge with begin_tx granting every iteration; pre-panic listener silence on the notified channel; ghost never claimable; fresh with_tx commits through the same seam — both engines green; ADR-007, ADR-021 §4) — wired into both engines' suite targets (SQLite tokio tests, pg harness_row!s). Dispositions recorded in Notes: the 60/5/5 stamp inspection rides the delivery handle's job() because get_job cannot target the reserved derived backing queue through the contract surface (exemplar row pins the rejection; engine-side raw-row probes stayed put), the panic row's notify leg is a windowed silence (SQLite's wake overtriggers on any commit — the fully cross-engine notify-ghost pin rides the engines' rollback-ghosts twins, the suite's drop-rollback row made the same call), the closure tail routes through a #[cold] mid_flight_panic -> Error helper (a bare Err(panic!()) tail trips unreachable_code under -D warnings), and the post-panic convergence loop is the suite-side bounded wait (state outcomes only, begin_tx doubling as the seam-still-grants probe). Verified: sqlite suite 19/19, pg suite 19/19 vs harness twice (postgres/poc@:15432) + solo re-runs of each new row per engine (determinism), workspace build/test green, clippy -D warnings, fmt clean
This commit is contained in:
1 parent
7f749ac673
commit
360e71e51e
5 files changed
+567
-16
No files matched your search
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: suite-tx-commit-atomicity-rows
|
||||
name: Contract-suite rows — outbox/keyed-publish tx commit-atomicity + with_tx panic probe
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: []
|
||||
scope: moderate
|
||||
risk: low
|
||||
@@ -53,15 +53,15 @@ Rows to add:
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Three rows exist, version-stamped (ADR-014 §1, ADR-015 §2,
|
||||
- [x] 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
|
||||
- [x] Rows wired into both engines' `contract_suite.rs` targets
|
||||
- [x] SQLite column green server-less; pg column green against the
|
||||
harness server
|
||||
- [ ] The panic probe passes on both engines (no writer-slot stranding,
|
||||
- [x] 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
|
||||
- [x] `cargo test -p alkstore-sqlite -p alkstore-postgres` green
|
||||
(pg rows skip cleanly server-less); clippy `-D warnings`; fmt clean
|
||||
|
||||
## References
|
||||
@@ -76,8 +76,84 @@ Rows to add:
|
||||
|
||||
## Notes
|
||||
|
||||
> To be filled by implementation agent
|
||||
Decisions of record beyond the task description:
|
||||
|
||||
- **The 60/5/5 stamp inspection rides the delivery handle's `job()`.**
|
||||
ADR-014 §1's backlog wording says the stamped opts are
|
||||
"`get_job`-visible", but the derived backing queue is engine-internal:
|
||||
it is reserved-prefixed, so no `get_job`/`get_job_tx` call can target
|
||||
it through the contract surface (the exemplar row itself pins
|
||||
`get_job_tx(reserved queue)` → `ReservedName`; the engine-side stamp
|
||||
tests use raw row probes, which the suite may not). The row therefore
|
||||
asserts the 60/5/5 set on the `Delivery` closure's `JobHandle`
|
||||
(`job()` — `visibility_timeout_s`/`max_attempts`/`backoff_base_s`/
|
||||
`dead_letter_retention_s`) — the only contract-visible inspection of
|
||||
backing-queue rows. The delivery payload-exactness and the
|
||||
`__alkstore_outbox:{name}` derived-name pin ride the same snapshot.
|
||||
- **The panic row's notify leg is a windowed silence, not a full
|
||||
cross-engine pin.** The row drives all four write kinds
|
||||
(job/event/notify/offset) through the panicking tx, but a rolled-back
|
||||
notify's no-residue is only observable as listener silence, and
|
||||
SQLite's wake overtriggers on *any* commit (the notify module's
|
||||
documented coalescing posture), so silence is asserted in a window
|
||||
that precedes every post-panic committed write — a delivery-shaped
|
||||
wake there is a genuine defect. The fully cross-engine notify-ghost
|
||||
pin rides the engines' own rollback-ghosts tests (SQLite's raw
|
||||
notify-count probe, pg's listener-silence twin); the suite's existing
|
||||
`drop_rollback_leaves_no_ghosts` row makes the same shape of call
|
||||
(drives the observable three kinds).
|
||||
- **The panic-probe closure's tail routes through a `#[cold]`
|
||||
helper typed `Error`** (`mid_flight_panic`) — a bare
|
||||
`Err(panic!(...))` tail is unreachable-call-shaped and trips the
|
||||
`unreachable_code` lint under `-D warnings`; `catch_unwind` around an
|
||||
async closure cannot see the panic point anyway (the mechanics hint),
|
||||
so the typed diverger costs nothing.
|
||||
- **The post-panic convergence loop is the suite-side bounded wait**
|
||||
for the rollback to land (pg's detached rollback task; SQLite's rolls
|
||||
back synchronously in the unwind): state-outcome assertions only,
|
||||
each iteration's `begin_tx` doubling as the seam-still-grants probe
|
||||
(the writer-slot/pool replenish property), the deadline a safety
|
||||
bound like `await_fires`' — no timing-value assertion.
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Landed the three tx-seam commit-atomicity rows in
|
||||
`alkstore-contract-suite/src/properties.rs`, each version-stamped per
|
||||
the suite convention:
|
||||
|
||||
- `outbox_enqueue_tx_commit_atomicity` — rollback drops the
|
||||
backing-queue job with the business write (business id `get_job_tx`-
|
||||
gone in a fresh tx; `run_once` afterwards claims nothing); commit
|
||||
makes the job claimable by `run_once` exactly when the business
|
||||
write commits (real delivery through the `RecordingDelivery`
|
||||
closure: job id identity, derived `__alkstore_outbox:mail` queue,
|
||||
exact payload, 60/5/5 stamps, consumed-after-ack) (ADR-014 §1,
|
||||
ADR-010 §3a, ADR-021 §4, ADR-007).
|
||||
- `publish_with_key_tx_commit_atomicity` — rollback drops the keyed
|
||||
event with the business write (both streams empty on fresh reads,
|
||||
key round-tripping inside the tx pre-drop); commit makes it visible
|
||||
to `read_since` and to a post-commit subscriber attach with the key
|
||||
round-tripping on both (ADR-015 §2/§4, ADR-021 §4, ADR-007).
|
||||
- `with_tx_panicking_closure_rolls_back` — N-5's probe against real
|
||||
engines for the first time: spawned-task `with_tx` closure writes
|
||||
enqueue/publish/notify/offset then panics mid-flight; the panic
|
||||
surfaces via `JoinError::is_panic()`; the no-ghost reads converge
|
||||
with `begin_tx` granting every iteration; the pre-panic listener
|
||||
hears nothing on the notified channel; the ghost never becomes
|
||||
claimable work; a fresh `with_tx` commits through the same seam —
|
||||
both engines green (no writer-slot stranding, no pooled-object
|
||||
leak) (ADR-007, ADR-021 §4).
|
||||
|
||||
Also: the module-doc determinism/orchestration note gained the panic
|
||||
probe's admission sentence (spawned-task mechanics, `catch_unwind`
|
||||
asymmetry — the runner-driving extension's sibling; ADR-017 §2 class
|
||||
4), `RecordingDelivery` + the `#[cold]` diverger are suite-local
|
||||
helpers, and the rows are exported from the suite lib and wired into
|
||||
`alkstore-sqlite/tests/contract_suite.rs` (three direct tokio tests)
|
||||
and `alkstore-postgres/tests/contract_suite.rs` (three
|
||||
`harness_row!`s).
|
||||
|
||||
Verified: sqlite suite 19/19; pg suite 19/19 vs the harness server
|
||||
(`pglo-poc` :15432, `postgres/poc`/`blobs`) twice consecutively, plus
|
||||
solo re-runs of each new row per engine (determinism); workspace
|
||||
build/test green; clippy `-D warnings` clean; fmt clean.
|
||||
Reference in new issue
Block a user