Files
alkblobs/tasks/engine-pg-lo-joint.md
T
glm-5.3-flash 76be7b0273 feat(pg-lo): the joint contribution — staged-put mechanics into the store-held tx (wave 6)
The wave-5 deferred design point, landed: under the consolidation
posture (kv = postgres + large = pg-lo, same pg instance), a large
entry's content reaches the store-held joint transaction the pin row
commits in (ADR-012 §4's one-tx ruling).

- The contribution's API home (the named design point) — decided: the
  joint staging-cursor variant. `PgLoBackend::put_joint_staged(&mut
  PgJointTx)` hands `PgLoJointStaging<'_>`, the staged put with
  inverted ownership: it borrows the held checkout, owns no checkout,
  opens no tx, never COMMITs, never ROLLBACKs (the driver owns
  termination, amendment A1) — no Drop, by design. The deciding facts:
  the large tier's put contract is the streaming cursor, the fleet put
  driver routes to the joint form as the non-fleet path routes to the
  staged form, and the unknown-length derivation pass reads back
  through the cursor's `ReadCursor` seam — none servable by a
  whole-value method.
- The mechanics are the landed bodies re-homed: the 512 KiB
  `lowrite` knee (LOWRITE_CHUNK aggregation of the store core's 64 KiB
  windows), read-your-own-writes `lo_get` windows, the publish-time
  CAS (the key resolves at publish — the staged put's named design
  point), the companion-row insert, transactional LO creation (C6:
  the LO lives and dies with the store-held tx). The re-put form
  publishes an empty contribution — the staged LO unlinked inside the
  store-held tx, zero residue; a concurrent CAS miss surfaces the
  companion key's unique violation (the landed puts' posture).
- Enabling affordance: `PgJointTx::client()` — the crate-internal
  statement surface every joint contribution (and the store-fleet-put
  driver's A1 termination design) rides through; deliberately not an
  abort verb.
- Crate-internal seam: the public trait surface untouched, nothing of
  `EngineFleetState` on pg-lo (ADR-010 §2's correction), the
  engine-level held-tx staging cursor and the non-fleet put path
  byte-unchanged. Gated `feature = "postgres"`; both pg-lo pure
  builds compile.
- Contract tests (`src/pg_lo.rs` mod joint, both engines on one
  dedicated database): one-tx publish (kv row + LO + companion row +
  pin row, byte-exact round-trips after `pin_commit`); forced failure
  mid-contribution → driver ROLLBACK → zero residue (no LO pages, no
  companion row, no pin row, no kv row); the CAS re-put form (empty
  contribution, upsert renewal); the 512 KiB knee (multi-chunk).

Verification: `cargo test` (default) green; `--features
"pg-lo,postgres"` green server-less (skipping) and against the harness
server (pglo-poc, :15432 — the four joint tests + full pg_lo +
conformance suites live); `--all-features` green; clippy `-D warnings`
green (default / postgres / pg-lo+postgres / all-features / the two
pg-lo pure builds); `cargo fmt --check` green; `cargo doc --no-deps`
clean.

The store-core driver that calls the contribution on the fleet put
path is store-fleet-put.
2026-10-12 15:15:27 +00:00

13 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
engine-pg-lo-joint the pg-lo joint contribution — staged-put mechanics into the store-held tx completed
moderate high component implementation
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).