Files
alkblobs/tasks/store-fleet-put.md
T
glm-5.3-flash 950af9ab0d feat(store): the fleet put path — the joint entry+pin tx driver and the pin guard's fleet form (wave 6)
- Under fleet: true, puts route through the joint entry+pin tx
  (store-fleet-put): kv tier via begin_joint_tx -> joint_put (CAS; a
  dedup re-put commits the pin row alone, upsert-renewing
  (key, owner)) -> pin_commit — entry + pin row in one step, verified
  against an independent connection; large = pg-lo rides the one-tx
  consolidation form (known- and unknown-length; the 512 KiB knee,
  the empty-contribution re-put form, read-your-own-writes
  derivation); large = local on declared shared media is
  pin-first, publish-second (the row-only joint tx, then the staged
  commit-rename; the orphan-pin crash window is the expiry
  machinery's).
- Amendment A1 landed: catch-propagate-terminate — every error path
  between begin_joint_tx and pin_commit terminates the held tx before
  propagating (no mid-flight PgJointTx returns dirty to the pool,
  PG-2); tested with a forced mid-driver failure (zero residue, the
  pool's idle-in-transaction count zero, a follow-up put clean).
- The pin guard's fleet form: the returned guard carries the row's
  TTL/3 renewal duty — a per-guard tokio task (spawned at the row's
  durability), silent on Err, ended by the never-resurrect
  Ok(false), aborted at every guard-life boundary (drop, batch
  contribution, conversion); PinRenewal gated feature postgres.
- KvEngine::Postgres holds Arc<PostgresBackend> so detached renewal
  tasks share the fleet host's engine; tokio gains the time feature
  for the cadence.
- Non-fleet put paths byte-unchanged (all existing suites green); a
  non-fleet test pins that no pin rows are ever committed.
- Tests: new postgres_fleet_put_tests (fresh schema + SchemaJanitor;
  kv joint atomicity both-or-neither, forced pin_commit failure,
  renewal cadence/drop/conversion stop, never-resurrect, batch
  release ends renewals, dedup upsert-renewal + by-row composition,
  local pin-first success + forced-failure forms, non-fleet
  non-event) and postgres_pglo_fleet_put_tests (fresh db +
  DatabaseJanitor; the A1 pg-lo failure form, the consolidation
  round-trip through the public put). Env-driven, skipping cleanly
  server-less.

Verified: cargo test (default) green; --features postgres and --features
"postgres,pg-lo" green server-less (skipping) and against the harness
server (pglo-poc, :15432); --all-features green live; pg-pure and
mem,pg-lo pure builds green; clippy -D warnings green (all combos);
cargo fmt --check green; cargo doc --no-deps clean (default; the gated
builds' pre-existing warnings unchanged). Schema/db residue 0.
2026-10-12 16:08:29 +00:00

17 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
store-fleet-put the fleet put path — the joint entry+pin tx driver and the pin guard's fleet form completed
store-fleet-construction
engine-pg-lo-joint
broad high phase implementation
wave-6
store
gc
fleet
put

Description

Route the put paths through the joint entry+pin transaction under fleet: true (ADR-008 §2; ADR-010 §2; ADR-012 §3/§4) — the store core holds the tx from begin_joint_tx to pin_commit, engines contribute mechanics, and publish + protection land in one step — plus the pin guard's fleet form: the TTL/3 renewal duty on the guard. The non-fleet put paths are byte-unchanged (the ADR-005 in-process protocol verbatim; every wave-2/3/4/5 put test stays green).

  • The joint-tx driver (the store core, src/store.rs): under fleet mode, a put whose entry lands on the kv engine (postgres) runs begin_joint_tx → joint_put (the engine's CAS row write inside the held tx) → pin_commit(tx, [PinRow { key, owner, expiry }]) — the pin row carries the store-instance id (store-fleet-construction's owner id) and expiry = now + TTL (the store resolves the expiry; ADR-012 §2's rename — the TTL lives with the store's renewal schedule). The public put signature is unchanged; the routing branches on the fleet flag, never on re-deriving from the engine set.
  • Amendment A1 — the joint-tx drop discipline (the wave-4 gate's owner lands here): the driver owes tx termination on every path from begin_joint_tx onward — the natural ? propagation between the driver's steps must never return a mid-flight PgJointTx to the pool (deadpool's Fast recycle never resets transaction state — PG-2's hazard). Named design point — the termination design: catch-propagate-terminate (every error path between begin and commit terminates the tx before propagating) per A1's recorded options — an explicit abort verb would need a new ADR (the seam shape is pinned; pin_commit's terminal-consume reading stays). Record the design; the zero-residue atomicity is already the engine clause's contract (wave 4) — the driver's duty is never abandoning the tx mid-flight.
  • The pin guard's fleet form (ADR-008 §2: "the pin guard renews its row every TTL/3"): the Pin guard returned by a fleet put carries the renewal duty — while the guard lives, its pin row's expiry moves forward every TTL/3 (via the engine's pin_renew(key, owner) — the never-resurrect semantics: an expired row is never revived; the guard's renewal failing after expiry is the delete-then-recover backstop's territory, ADR-008's expiry semantics verbatim). Named design points — the renewal lifecycle: the duty's mechanism (a per-guard tokio task on a TTL/3 interval vs a shared renewal sweep keyed off the guard registry — decide, record), the abort-on-drop discipline (a dropped/converted/batch-contributed guard stops its renewal — no leaked tasks, no renewal past the guard's life), and the failure posture (a failed renewal is logged-and-continued or silent — the row's expiry is the protection, a missed renewal degrades to the expiry semantics, never an error on the put path). The guard's in-process refcount machinery continues to function (instance-local bookkeeping — the batch's one-step release, the ordered-step conversion); the row is the cross-instance protection. Batch puts (put_in_batch): each member's joint tx commits its row; the batch's one-step release ends the members' renewals (the rows expire and the sweeper's maintenance step reaps them — store-fleet-sweep).
  • The dedup re-put under fleet: CAS dedup means no new entry — the joint tx commits the pin row alone (an empty joint_put contribution is the engine's recorded CAS form) and the row upsert-renews (key, owner) (the wave-4 recorded shape). A different instance's re-put is a different pin row — protection composes by row (ADR-010 §2's refcount-in-rows reading).
  • large = local on declared shared media — pin-first, publish-second (ADR-012 §4): the joint tx is unachievable across media, so the ordering is pinned: the pin row commits on the kv engine (a joint tx carrying only the pin row — begin_joint_tx → pin_commit without joint_put) before the stage file's commit-rename publishes the entry. The invariant survives (an entry becomes visible only after its pin row is durable — sweep arbitration can never observe a visible-unpinned entry); the crash window moves to the harmless side (an orphan pin row, reaped by the expiry machinery — no new recovery mechanism). The reverse ordering is rejected (ADR-012 §4) — do not implement it.
  • The pg-lo consolidation put (kv = postgres + large = pg-lo, same instance): the large entry's content reaches the store-held tx through the pg-lo engine's joint contribution (engine-pg-lo-joint) — LO writes + companion-row insert inside the tx, the pin-row insert + commit via pin_commit. One tx over the shared instance; the dispatch threshold routes to the joint form exactly as the non-fleet path routes to the staged form.
  • Unit tests (env-driven, skipping cleanly server-less; the harness server pglo-poc, :15432, db blobs, postgres/poc — verified live 2026-10-12): the kv joint put publishes entry + pin row atomically (an independent connection sees both or neither); the forced-failure zero-residue form (A1's discipline exercised — a mid-driver failure leaves no dirty pooled session); the guard's renewal moves the row's expiry (a short test TTL; the renewal cadence observed); the never-resurrect form (an expired row is not renewed back to life); the batch form (members' rows commit; batch release ends renewals); the dedup re-put's upsert-renewal; the local-shared-media pin-first ordering (the pin row is durable before the committed file exists); the pg-lo consolidation round-trip through the public put (both tiers, one tx — the byte-exact large-entry form); the non-fleet paths unchanged (the existing suites green).

Acceptance Criteria

  • Under fleet: true, kv-tier puts route through the joint tx (entry + pin row in one step; an independent connection never observes one without the other); under fleet: false the put paths are byte-unchanged (all existing suites green)
  • Amendment A1 landed: the driver terminates the tx on every path from begin_joint_tx onward (the termination design recorded; a forced mid-driver failure leaves zero residue and returns no dirty session to the pool)
  • The pin guard's fleet form: renewal every TTL/3 while the guard lives; drop/conversion/batch-contribution stops the renewal with no leaked tasks; a failed renewal never resurrects an expired row (the lifecycle design points decided and recorded)
  • The dedup re-put commits the pin row alone (CAS; upsert- renewal); a different instance's re-put composes protection by row
  • large = local fleet puts: pin-row-first, publish-second (contract-tested — the pin row is durable before the committed file exists; the orphan-pin crash window is the expiry machinery's)
  • The pg-lo consolidation put round-trips through the public put (one tx over the shared instance; byte-exact large-entry form)
  • cargo test (default) green; cargo test --features postgres and --features "postgres,pg-lo" green server-less (skipping) and against the harness server at acceptance; cargo clippy --all-targets -- -D warnings and cargo fmt --check green (default + the postgres/pg-lo variants); cargo doc --no-deps clean

References

  • src/store.rs (the put seam's two internal paths; the fleet declaration's flag from store-fleet-construction)
  • src/gc.rs (EngineFleetState — the clauses the driver calls; PinRow; the ownership rule)
  • src/postgres.rs (PgJointTx — PG-2's hazard doc; the wave-4 joint-clause bodies)
  • src/pg_lo.rs (the joint contribution from engine-pg-lo-joint)
  • docs/architecture/decisions/008-trait-size-probe-fleet-gc-and-large-engines.md §2 (pins are rows; the TTL/3 renewal; the expiry semantics)
  • docs/architecture/decisions/010-backend-io-seams-and-fleet-state-home.md §2 (the store holds the tx; the engine contributes mechanics), §4 (the guard-drops-at-response-time reading — the ops edition; the public-API guard lives as long as the caller holds it)
  • docs/architecture/decisions/012-pre-decomposition-consistency-rulings.md §3 (activation), §4 (the host; pin-first/publish-second for large=local; the one-tx consolidation)
  • docs/architecture/decisions/013-family-composition-reactive-boundary-and-feature-graph.md §1 (the renewal worker's cadence is embedder composition above the facade — the guard's own TTL/3 duty is the in-crate half)
  • tasks/review-wave-4.md (amendment A1 — this task is its owner)
  • tasks/store-fleet-construction.md, tasks/engine-pg-lo-joint.md (the declaration and the contribution this drives)

Notes

Landed 2026-10-12. Design decisions (each recorded in-code as a named design point on the fleet impl block of src/store.rs; all keyed off the landed ADR text):

  • A1's termination design — catch-propagate-terminate, no abort verb. The driver's steps between begin_joint_tx and pin_commit run inside one catch block (an async block's ? surface); any error terminates the held tx (a ROLLBACK through the PgJointTx::client() checkout — the wave-5 landed accessor) before propagating; pin_commit remains the terminal-consume step (its own failure paths roll back per the wave-4 clause contract). An explicit abort verb was rejected per A1's recorded options (it would need a new ADR; the seam shape is pinned). A ROLLBACK on a session with no active transaction (the clause bodies' having already rolled back) is a completed, warning-level no-op — the blanket termination is always safe. pin_commit-consuming txs on failure are the clause's own; the driver never re-uses a consumed tx.
  • The renewal duty's mechanism — a per-guard tokio task on a TTL/3 interval, holding an Arc<PostgresBackend> (the kv-tier enum's postgres payload moved to an Arc for this: the detached task outlives the Store and keeps the backend's pool exactly as long as the pin lives; PostgresBackend itself is not Clone — its lease checkout must not be). The shared-renewal-sweep keyed off a guard registry was the rejected alternative: it needs precisely the lifecycle notifications (mint/drop/convert/batch-contribute) that an abort handle gives for free. The task is spawned at the pin row's durability (after pin_commit, never at guard mint — a renewal arriving at a nonexistent row would read the not-yet-pinned window), on the cadence (ttl / 3).max(1 ms) (the 1 ms floor is the degenerate-tuning guard: a TTL below 3 ms would floor to a zero poll). The store resolves the expiry (now + ttl) at the joint tx's pin_commit; successive renewals ride the engine's pin_renew (the successor-time body reads the TTL the fleet constructor passed the engine — store-fleet-construction's wiring).
  • The failure posture: silent-and-continued on Err, ended on Ok(false). The row's expiry is the protection — a missed renewal degrades to ADR-008's expiry semantics, never an error on the put path (the crate carries no logging dependency; embedder visibility into renewals observes the row). Ok(false) — an expired or absent (key, owner) row — ends the duty: the engine never resurrects (wave 4's recorded semantics), so continuing would only churn; the duty ends, the delete-then-recover backstop's territory begins.
  • The abort-on-drop discipline: the duty is a PinRenewal (a JoinHandle wrapper, crate-internal, gated feature postgres) attached to the returned Pin; it is aborted by the guard's drop, by Batch's one-step release (the duty moves into the batch at Batch::add — a member's row keeps renewing while the batch lives), and by the ordered-step conversion (RegistryCore::convert pre-stops before its ManuallyDrop suppresses the guard's drop). No leaked tasks; no renewal past the guard's life. A task aborted mid-renewal is harmless (the clause's expiry update is idempotent).
  • The routing never re-derives from the engine set: every fleet branch keys off Store::fleet's presence first; the pg-lo tier's joint-vs-staged choice inside fleet mode rides the engine enum (the tier mechanics' selector, the same dispatch as every other fleet branch shape). The public put/put_in_batch signatures are unchanged; the non-fleet paths are byte-unchanged (all existing suites green untouched).
  • The kv joint form's mechanics: the in-process refcount mints first (unchanged bookkeeping), then begin_joint_tx → joint_put (the engine's CAS row write; a dedup re-put contributes nothing — the recorded CAS form; the row upsert-renews via pin_commit's ON CONFLICT (key, owner) upsert) → pin_commit(tx, [row]) — publish + protection in one step. The row's owner is the store-instance id; expiry = now + pin_ttl.
  • The consolidated (pg-lo) form: the tx begins, the tier's joint staging cursor streams the content inside it (the engine-level contract: the 512 KiB knee rides, the unknown-length form's buffer-to-threshold phase is put_unknown's verbatim shape and its derivation pass re-reads the artifact through the cursor's read-your-own-writes seam), the cursor's publish is the CAS contribution (a re-put: the empty-contribution form — the staged LO unlinked in-tx, zero residue), and pin_commit lands the row with the entry.
  • Test isolation: every store test takes a fresh engine-owned schema (the LO-free kv tier; SchemaJanitor) or a fresh dedicated database (the LO-bearing consolidation tests; DatabaseJanitor) with a janitor removing it in every exit path — amendment A2's lesson honored from the start. The A1 dirty-session observable is pg_stat_activity's idle in transaction% count asserted to zero (plus the follow-up put's clean round-trip and the zero-residue table assertions).

Summary

The fleet put path landed. Under fleet: true, put/put_in_batch (unchanged signatures) route: kv tier — the joint entry+pin tx (begin_joint_tx → the engine's CAS joint_put → pin_commit, publish + protection in one step; a dedup re-put commits the pin row alone, the row upsert-renewing (key, owner); a different instance's re-put mints a different row — protection composes by row); large = pg-lo (the consolidation posture) — one tx over the shared instance, the tier's joint staging cursor streaming the content inside the store-held tx (known- and unknown-length; the 512 KiB knee, the CAS publish, the empty-contribution re-put form, read-your-own-writes derivation passes), pin_commit closing; large = local (declared shared media) — pin-first, publish-second: a row-only joint tx commits the pin row before the staged commit-rename (the forced-publish-failure test proving the ordering: the pin row is durable with no committed file, the orphan reclaimed by the expiry machinery; never the reverse ordering). Amendment A1 landed as its recorded owner — catch-propagate-terminate: every error path between begin_joint_tx and pin_commit terminates the held tx before propagating (tested: a mid-driver contribution failure leaves zero residue and returns no dirty pooled session — pg_stat_activity's idle-in-transaction count zero — and the pool's follow-up put round-trips clean). The pin guard's fleet form landed: the returned guard carries the row's TTL/3 renewal duty (a per-guard tokio task, spawned at the row's durability, silent on Err, ended by the never-resurrect Ok(false), aborted at every guard-life boundary — drop, batch contribution, conversion). The in-process refcount machinery continues to function (instance-local bookkeeping, the batch's one-step release, the ordered-step conversion); the row is the cross-instance protection; nothing expires in non-fleet modes. Non-fleet put paths are byte-unchanged (all existing suites green untouched; a non-fleet test additionally pins that no pin rows are ever committed). Verified: cargo test (default) green; --features postgres and --features "postgres,pg-lo" green server-less (skipping cleanly) and against the harness server (pglo-poc, :15432 — live, all fleet-put tests green); --all-features and the pg-lo-pure --features mem,pg-lo builds green; clippy -D warnings green (default, mem, postgres, all-features, pg-pure, mem+pg-lo, and postgres+pg-lo); cargo fmt --check green; cargo doc --no-deps zero warnings in the default build (the gated builds' pre-existing warnings counted before and after — identical). The fleet sweep (store-fleet-sweep) holds the fleet mechanism's remaining store-core scope (the sweep lease, the SQL arbitration).