--- id: engine-pg-lo-joint name: the pg-lo joint contribution — staged-put mechanics into the store-held tx status: completed depends_on: [] scope: moderate risk: high impact: component level: implementation tags: [wave-6, engines, pg-lo, gc] --- ## Description Land the **pg-lo engine's joint contribution** — the wave-5 deferred design point, recorded in `src/pg_lo.rs`'s scope boundary ("the *fleet* joint form (wave 6's store-held tx, ADR-012 §4/§5's ruling that pg-lo contributes LO writes + companion-row insert into it) is a wave-6 design point") and in `src/gc.rs`'s `EngineFleetState::joint_put` clause doc ("an engine-task contribution against this `JointTx` type"). Under the fleet consolidation posture (kv = postgres + large = pg-lo, same pg instance — admitted by `store-fleet-construction`'s predicate), a large entry's content reaches the **same store-held transaction** the pin row commits in: one tx over the shared instance — the pg-lo engine contributes its staged-put mechanics (chunked `lowrite` + companion-row insert) into it; the pin-row insert and the commit stay the store's (`pin_commit`, the postgres host's clause — landed wave 4, unchanged here). - **The one-tx ruling** (ADR-012 §4): the store core holds one transaction over the shared instance; the pg-lo engine contributes LO writes + companion-row insert; the pin-row insert is the store's. The engine sees pins only as rows; never as semantics (ADR-010 §2's correction of ADR-009's ownership statement — nothing of `EngineFleetState` lands on pg-lo itself; the contribution is mechanics the store core drives). - **The contribution's API shape** — the named design point wave 5 deferred. The engine's existing staged put holds its own tx (the held-tx posture: the staging cursor owns a pool checkout with an explicit session-level `BEGIN`); the joint form inverts the ownership — the store core's `PgJointTx` (the postgres engine's held checkout, wave 4's recorded shape) is the tx the LO writes ride. **Named design point — the contribution's API home**: decide the shape consistent with the seam's ownership rule (the store holds the tx; the engine contributes mechanics into it) — the natural shapes are a crate-internal pg-lo method taking `&mut PgJointTx` (the engine writes `lo_create`/`lowrite` + the companion-row insert through the held checkout) or a joint staging-cursor variant handed out against the held tx; decide, record, and keep the public trait surface untouched (the contribution is crate-internal, like every seam clause). The `JointTx` type is the postgres engine's `PgJointTx` — the consolidation posture's one shared instance is what makes the type shareable (ADR-012 §4's checkable clause; `store-fleet-construction` enforces it). - **The staged-put mechanics, re-shaped not re-written**: the 512 KiB `lowrite` knee (`LOWRITE_CHUNK`, the C2 citation), the CAS existence check first (a re-put commits an empty contribution, never `lo_create`), the companion-row insert, transactional LO creation (zero crash orphans — C6, structurally: the LO lives and dies with the store-held tx) — the engine's landed bodies carry; what changes is whose tx they run in. The engine-level held-tx staging cursor is **unchanged** (the non-fleet put path keeps its own tx; the wave-5 hygiene tests stay green untouched). - **Failure discipline**: every failure path inside the contribution leaves the store-held tx in a state the driver can terminate — the contribution never commits, never rolls back the store's tx unilaterally (the driver owns termination; amendment A1's discipline — the driver task owns the catch-propagate-terminate design; this task's duty is that the contribution's error paths compose with it: no partial LO state that outlives the tx's rollback, which transactional LO creation already guarantees). - **Contract tests** (engine-level, in `src/pg_lo.rs`'s test module, the established env-driven convention): the contribution writes LO content + the companion row into a `begin_joint_tx`-held tx and `pin_commit` publishes entry + pin row in one step (byte-exact round-trip after commit); a forced failure mid-contribution aborts the whole tx with zero residue (no LO pages, no companion row, no pin row, no kv row — the stage-hygiene invariant's tx form, the wave-4 joint-atomicity test's pg-lo edition); the CAS re-put form (a second contribution of the same key commits an empty contribution; the pin row upsert-renews); the 512 KiB knee rides (a multi-chunk contribution). **Tests need a live server** — the alkstore harness convention: env-driven (`ALKBLOBS_PG_*`), skip cleanly server-less, acceptance requires green against the harness server (`pglo-poc`, :15432, db `blobs`, `postgres`/`poc` — verified live 2026-10-12). Adopt the `SchemaJanitor`/per-test-database isolation posture from the start (amendment A2's lesson — the wave-4 leak is not re-learned). - **Scope boundary**: the store-core driver that calls this contribution on the fleet put path is `store-fleet-put`; this task ships the engine-side mechanics and their contract tests. The non-fleet pg-lo put path is untouched. ## Acceptance Criteria - [ ] The pg-lo joint contribution exists behind the crate-internal seam (the public trait surface untouched; nothing of `EngineFleetState` implemented on pg-lo itself) - [ ] The contribution's API shape decided and recorded (the store-held `PgJointTx` is the tx the LO writes ride; the ownership rule honored — the engine contributes, never owns) - [ ] One-tx publish: LO content + companion row + pin row + kv row commit in one step; byte-exact round-trip after `pin_commit` (contract-tested) - [ ] Zero residue on any failure path (no LO pages, no companion row, no pin row, no kv row — contract-tested with a forced failure); the contribution's error paths compose with the driver's termination discipline (A1) - [ ] The CAS re-put form (empty contribution, upsert-renewal) and the 512 KiB knee contract-tested - [ ] The non-fleet held-tx staging path and its wave-5 hygiene tests unchanged and green - [ ] `cargo test --features "pg-lo,postgres"` green server-less (skipping) and against the harness server at acceptance; `cargo clippy --all-targets -- -D warnings` and `cargo fmt --check` green (default + the pg-lo variants); `cargo doc --no-deps` clean ## References - src/pg_lo.rs (the scope boundary recording this task; the held-tx staging bodies this re-shapes; `LOWRITE_CHUNK`) - src/gc.rs (`EngineFleetState::joint_put`'s clause doc — the contribution's seam home; `JointTx`'s ownership rule) - src/postgres.rs (`PgJointTx` — the held-checkout tx type; the wave-4 recorded design points) - docs/architecture/decisions/012-pre-decomposition-consistency-rulings.md §4 (the one-tx ruling; the same-instance clause) - docs/architecture/decisions/010-backend-io-seams-and-fleet-state-home.md §2 (the ownership rule; the pg-lo correction) - docs/architecture/decisions/009-pg-lo-large-tier-engine.md (the engine shape the mechanics carry) - docs/research/poc-pglo-findings.md (C2 the knee, C6 zero orphans) - tasks/engine-pg-lo.md (the staged-put bodies this extends) - tasks/review-wave-4.md (amendment A1 — the drop discipline the contribution composes with) ## Notes > The contribution's API shape (the named design point) — **decided: the > joint staging-cursor variant**, not a whole-value method taking > `&mut PgJointTx`. Landed as `PgLoBackend::put_joint_staged(&self, tx: > &mut PgJointTx) -> PgLoJointStaging<'_>` in `src/pg_lo.rs` (gated > `feature = "postgres"` — `pg-lo` does not imply `postgres`, ADR-013 > §4; both pure builds compile). The deciding facts: (1) the large > tier's put contract is the streaming staging cursor > (`StagingCursor`/`ReadCursor`), and store-fleet-put routes to the > joint form "exactly as the non-fleet path routes to the staged form" > — stage-while-hashing, the key resolving at stream end — so a > whole-value method would force the driver to materialize every large > entry, regressing the memory posture; (2) the unknown-length flow's > derivation pass re-reads the staged artifact through the cursor's > read seam, the engine-level `PgLoStaging` shape — a whole-value > method cannot serve any of it; (3) the cursor is exactly "the staged > put with inverted ownership" — the task description's own framing — > the landed bodies re-homed, not re-written. Consequence recorded: > the CAS existence check lands at **publish** (the key resolves > there), the landed `PgLoStaging::publish` shape — a re-put publishes > an **empty contribution** (the staged LO unlinked *inside the > store-held tx*, so the committed tx carries nothing of the losing > put; zero residue; the driver's `pin_commit` then upsert-renews the > pin row), while a concurrent CAS miss surfaces the companion key's > unique violation (the landed puts' posture: check = fast path, PK = > hard guarantee). "Never `lo_create`" holds literally only in the > engine-level whole-value put — the cursor form's handout must create > the LO before the key exists — but the LO lives and dies with the > store-held tx (C6, structural), so a losing put leaves zero residue > either way (contract-tested: the re-put form's catalog stays at > exactly the first writer's pages). > > **Ownership rule honored, literally:** `PgLoJointStaging` borrows the > held checkout (`&'a mut PgJointTx`); it owns no checkout, opens no > tx, never COMMITs, never ROLLBACKs — including on `Drop` (no Drop > impl: the driver owns termination on every path, amendment A1). Its > failure paths (statement failures, the injected media failure after > the tail flush, the concurrent-PK race) return with the tx exactly > as the contribution found it — open, uncommitted, un-rolled-back — > composing with the driver's catch-propagate-terminate (A1) with no > partial LO state that outlives the termination's rollback > (transactional LO creation). The engine-level held-tx staging cursor > and the non-fleet put path are byte-unchanged (wave-5 hygiene tests > green untouched); nothing of `EngineFleetState` is implemented on > pg-lo; the public trait surface untouched. > > **Enabling affordance (minimal, for the contribution and the next > driver task):** `PgJointTx::client()` — a crate-internal accessor > returning `&Object` (the field stays private), documented as the > statement surface every joint contribution rides. The contract > tests' driver-side ROLLBACK (the forced-failure form) and the future > store-fleet-put driver's A1 termination design are the consumers; > the accessor is deliberately *not* an abort verb (A1: an explicit > abort verb would need a new ADR — the store core's driver issues its > termination through the exposed checkout; that design decision > remains the driver task's). > > **Tests** (`src/pg_lo.rs` `mod joint`, feature `postgres`-gated, the > env-driven convention, per-test dedicated databases from the start — > amendment A2's lesson honored; the probes ride the pg-lo engine's > pool over the kv host's tables: one instance, one schema): the > one-tx publish (kv host's `joint_put` row + LO content + companion > row through the joint cursor + `pin_commit`'s pin row, byte-exact > round-trips of both entries after commit, exactly one LO with > exactly its page count); the forced-failure zero-residue form > (injection after the tail flush, driver ROLLBACK, all four artifact > families absent); the CAS re-put form (empty contribution, first > writer wins, pin row upsert-renewed, zero losing-LO residue); the > 512 KiB knee (64 KiB appends aggregating into multi-`LOWRITE_CHUNK` > statements, boundary-crossing read-back, multi-chunk round-trip). ## Summary The pg-lo engine's joint contribution landed: under the consolidation posture (kv = postgres + large = pg-lo, same pg instance), a large entry's content reaches the store-held joint transaction — `put_joint_staged` hands the `PgLoJointStaging` staging cursor against the postgres engine's held `PgJointTx` checkout; the cursor rides the held tx for every statement (chunked 512 KiB `lowrite` aggregation, read-your-own-writes `lo_get` windows for the derivation pass, the publish-time CAS, the companion-row insert or the re-put's in-tx `lo_unlink` empty-contribution form, transactional LO creation), and the store's `pin_commit` — landed wave 4, unchanged — publishes entry + pin row and commits in one step. The ownership rule is honored structurally: the engine contributes mechanics into the tx it does not own, never committing or rolling it back on any path (A1's discipline; the `PgJointTx::client()` accessor is the checkout surface). The public trait surface is untouched; nothing of `EngineFleetState` exists on pg-lo; the engine-level held-tx staging cursor and the non-fleet put path are byte-unchanged. Verified: `cargo test` (default) green; `--features "pg-lo,postgres"` green server-less (skipping cleanly) and against the harness server (pglo-poc, :15432 — live, all four joint contract tests plus the full pg_lo and conformance suites green); `--all-features` green; clippy `-D warnings` green (default, postgres, pg-lo+postgres, all-features, and the pg-lo-pure builds `--no-default-features --features "pg-lo,mem"` and `--features "postgres,pg-lo"`); `cargo fmt --check` green; `cargo doc --no-deps` clean. The store-core driver that calls this contribution on the fleet put path is `store-fleet-put` (this task's scope boundary — recorded in the module doc and here).