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:
1 parent
ecb8211694
commit
4caea21098
11 files changed
+1287
-3
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user