Wave 4 decomposed: Postgres engine — 11 tasks (schema, open/opts, seam, forwarder, mechanisms, scheduler/outbox, integration, review gate); plan synced

This commit is contained in:
glm-5.3-flash committed 2026-10-08 22:49:00 +00:00
1 parent ecb8211694
commit 4caea21098
11 files changed
+1287 -3

No files matched your search

+129
View File
@@ -0,0 +1,129 @@
---
id: pg-engine-seam-tx
name: Postgres engine — `begin_tx`, `TxHandle` (pooled-object handle), all eleven `*_tx` methods
status: pending
depends_on: [pg-engine-open-opts]
scope: moderate
risk: high
impact: component
level: implementation
tags: [wave-4, postgres-engine, seam]
---
## Description
Implement the transactional seam — the pg arm of ADR-007, the shape
every other mechanism task rides, and the wave's highest-risk piece
(the wave-3 review's writer-slot findings show exactly the defect
class seam error arms breed):
- **No bridge, no `spawn_blocking`** — the pg handle holds the pooled
object directly (tokio-postgres `Client` is `Send + Sync`, POC #2
compile-probe; ADR-007's pg arm). `begin_tx`: pool checkout +
`BEGIN`, return the boxed `PgTxHandle` holding
`Option<deadpool::Object>`; ops are straight `.await`s; commit/
rollback return the object to the pool.
- **Drop = rollback** (ADR-021 §4): the handle's `Drop` issues
`ROLLBACK` and re-pools the object — panic-safe (no panic escapes
drop). A failed rollback's disposition is pinned (the object is
discarded, not re-pooled — its state is unknowable; the pool
accounting stays exact, deployment.md's ADR-021 note). Ops after
commit/consumption fail closed with `Error::Closed` (the wave-3
fail-closed guard shape).
- **All eleven `*_tx` methods, for real** (nothing stubs — the schema
exists, the SQL is this task's to write):
- Writes: `enqueue_tx` (stamps the derived plain-queue defaults
300/3/5/none — the no-handle-open shape, ADR-020 §3a),
`publish_tx`, `publish_with_key_tx` (empty-`Some`-key →
`InvalidName`, ADR-015 §2), `notify_tx` (`SELECT pg_notify($1,
$2)` inside the caller's tx — delivers at commit, native
commit-atomicity, ADR-007; the 8000-byte client-side check fires
here and on the auto-commit twin — typed `PayloadTooLarge` with
the limit in the variant, before any round trip, ADR-016 §5),
`save_offset_tx` (monotone upsert), `outbox_enqueue_tx` (derives
the `__alkstore_outbox:{name}` backing queue name engine-side,
stamps the outbox's 60/5/5 set — ADR-014 §1).
- Reads (ADR-021 §1, read-your-own-writes through the same held
tx): `get_job_tx` (queue-scoped — a found row's queue column
must match the argument, the wave-3 seam's scoping rule),
`get_offset_tx`, `read_since_tx`, `read_from_consumer_tx`
(offset ASC; the extent guard `limit <= 0` → empty `Vec` at
trait-impl entry, ADR-023 §2).
- `commit(self: Box<Self>)`: `COMMIT` + re-pool. A failed COMMIT
discards the object (unknowable state) — the wave-3 review's
finding (c), pre-empted here.
- **Name validation at every entry point** via core's `validation`
helpers (shared-namespace kinds vs consumer-local; ADR-008 §4) —
before any round trip.
- **Payload encoding**: core's fallible `encode_payload` at the seam;
`?` the `Codec` error (ADR-023 §1). Decode via the `#[doc(hidden)]`
`from_row` constructors; malformed rows → `Codec` (the posture
guard).
- **The engine-side arithmetic lives here first** (one owner per
formula, ADR-012 §2 — the pg engine's own `resolution.rs`, the
SQLite one is reference not dependency): `now` (the single clock
read — `now()` at the SQL layer or one `std::time` read; pick one,
document it), `plain_queue_default_stamps()` (300/3/5/none),
`outbox_default_stamps()` (60/5/5), `outbox_backing_queue_name()`
(the reserved-prefix derivation), `resolve_enqueue_opts`
(delay-over-`run_at`, relative-`expires`, one clock read per
enqueue). The auto-commit mechanism tasks reuse these directly —
same shape as the SQLite engine's `resolution.rs`.
- **`pg_notify` payload carriage**: the notify payload is the
serde_json serialization's UTF-8 text (≤ 8000 bytes checked
client-side). Large payloads do NOT ride a side-table in v1 — the
typed rejection is the contract's answer (ADR-016 §5's occurrence
asymmetry; the outbox shape is the documented large-payload
pattern, not engine machinery).
The mechanism tasks then add what the tx path deliberately lacks: the
auto-commit handles, the forwarder-driven receivers, the backoff
curve, and the scheduler runner.
## Acceptance Criteria
- [ ] `begin_tx` checks out + `BEGIN`; concurrent `begin_tx` draws
distinct pooled objects (pool-bounded); pool exhaustion blocks
(deadpool's acquire semantics) — tested
- [ ] All eleven `*_tx` methods implemented with entry-point
validation; writes commit-atomic, reads see the tx's own writes
(read committed, both directions POC-pinned); opts resolution
(delay-over-run_at, relative-expires, 300/3/5/none and 60/5/5
stamp sets) pinned by tests
- [ ] `commit` commits + re-pools; drop (without commit) rolls back +
re-pools — no-ghosts tested for job rows, stream events,
notifications (a listener hears nothing from a rolled-back
`notify_tx`), and offset saves
- [ ] Drop is panic-safe; failed COMMIT/ROLLBACK arms discard the
object (pool accounting exact); ops-after-consume → `Closed`
- [ ] `notify_tx`/`notify`'s 8000-byte check: typed
`PayloadTooLarge { limit: 8000 }` client-side, before any round
trip; ≤ 8000 crosses
- [ ] `with_tx` (core's provided method) works end-to-end on this
engine: Ok ⇒ commit, Err ⇒ rollback — tested
- [ ] `encode_payload`'s `Codec` error propagates; extent guard on the
tx stream reads; `get_job_tx` queue-scoped
- [ ] `cargo test -p alkstore-postgres` (harness server), clippy
`-D warnings`, fmt clean; gates green server-less
## References
- docs/architecture/decisions/007-transactional-seam.md (pg arm)
- docs/architecture/decisions/021-tx-reads-and-value-shape-fixes.md §1, §2, §4
- docs/architecture/decisions/014-outbox-tx-enqueue.md §1
- docs/architecture/decisions/020-enqueue-opt-semantics-and-bridges.md §1–§4
- docs/architecture/decisions/016-deployment-honesty.md §5
- docs/architecture/decisions/023-fourth-review-round.md §1, §2
- docs/architecture/engine-postgres.md (Mapping the contract: begin_tx, handle ops)
- docs/research/poc-pg-posture-findings.md (Sub-module T — the shape
(a) evidence, read-your-writes, notify-at-commit)
- alkstore/src/tx.rs (the trait to implement)
- alkstore-sqlite/src/tx.rs, resolution.rs (the structural twin)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion