--- id: store-postgres-construction name: postgres constructor wiring — the fleet-capable store shapes status: completed depends_on: [engine-postgres, engine-postgres-fleet] scope: narrow risk: low impact: component level: implementation tags: [wave-4, store, postgres, construction] --- ## Description Slot postgres into the facade's engine representation — the slotting-in pattern `src/store.rs`'s module doc pinned ("adding an enum variant and a named constructor is slotting in"): - **`KvEngine::Postgres`** variant behind `#[cfg(feature = "postgres")]` + the dispatch arms (`put`/`get`/`has`/`size`/ `delete`/`list`). The kv cursor dispatch slots the postgres materializing body into **`KvCursor`** (the recorded sub-enum design point: "postgres (wave 4) slots one arm in here" — the store.rs doc note is updated to the landed state). - **Named constructors**, feature-gated on `postgres` (default-off — they construct under `--features postgres`): - **dual-tier** `kv = postgres + large = local` — the fleet-capable-kv production shape (non-fleet here; see below); fallible (config parse, connect, schema bootstrap, the local root creation). Size-threshold dispatch at the constructor-tunable threshold as in the sqlite shape. - **kv-only(postgres)** — no large tier; `threshold` is the put-time rejection point (ADR-012 §6.2; the rejection machinery is the put seam's, already landed — only the constructor slots in). - Both take the **connection settings + engine opts** (recorded design point: the DSN form — the tokio-postgres `Config`-parseable string, the alkstore `open(config, PgOpts)` pin adapted to the named-constructor shape — and the opts shape carrying schema name, pool size, `synchronous_commit`; engine-configuration concerns ride the constructor parameters, never invented defaults) and the **delete-window delay** parameter (`Duration`, default `DEFAULT_DELETE_WINDOW_DELAY`, `0` the legal degenerate tuning — the sqlite constructors' signature shape). - **Non-fleet posture** — these constructors are implicitly `fleet: false` (ADR-012 §3): the ADR-005 in-process protocol applies verbatim regardless of engine; pins are in-process guards, the sweep lease is the in-process lock, and none of the `EngineFleetState` clauses run. **The `fleet: true` declaration is wave 6** (the fleet-validity predicate + constructor declarations) — no fleet parameter is added here; wave 6 slots it in without reshaping. Recorded in the constructor docs so the wave-6 slotting is a non-event. - **`ensure_gc_schema` at construction** — the store-core call (engine-neutral; the postgres arm creates `gc_pins` + `gc_window` per the fleet host's applicability row). A construction failure is `Err(Error::Io)` per the fallible-constructor pattern. - **The one-window-host rule's store-core posture** — already recorded (the durable window hosts on the kv engine when it is SQL-backed); the postgres arm slots in under it with no reshaping. Verify the recorded posture's wording covers postgres (it names "sqlite or postgres" already — ADR-012 §5) and that the form branch keys off the kv-engine enum, never a flag. - **Stale scope notes updated** — the engine-representation doc's "postgres (wave 4)" forward references, the `KvCursor` design-point note, and any `#![allow(dead_code)]` scope notes whose scope changes (the batch-delete helper's note stays — its consumer is still wave 6). - **Constructor unit tests**: the modes construct and introspect correctly (`mode()`/`threshold()`), `ensure_gc_schema` ran (both GC tables exist in the fresh schema), the window-delay default is the pinned constant, kv-only rejection wiring unchanged, the dual-tier shape round-trips end-to-end (a smoke put/get through both tiers). Env-driven, skipping cleanly server-less (the engine tests' convention). - **The pg-pure build** — `cargo check --no-default-features --features postgres` (and the tests target under it): ADR-013 §4's supported non-default configuration first becomes constructible here; verify it compiles clean. ## Acceptance Criteria - [x] `KvEngine::Postgres` slots in; all six kv ops dispatch; the kv cursor body slots into `KvCursor` (the design-point note updated to the landed state) - [x] `dual_tier_postgres_local` + `kv_only_postgres` constructors exist behind feature `postgres`, take connection settings + engine opts + threshold + window delay, fallible where I/O is fallible - [x] `ensure_gc_schema` runs at construction (both GC tables in the fresh schema); the window-delay shipped default is 5 min (pinned constant) and constructor-tunable; `0` legal - [x] The non-fleet posture recorded (in-process protocol verbatim; the `fleet: true` declaration is wave 6, slotting in without reshaping) - [x] The one-window-host posture verified to cover postgres; the form branch keys off the kv-engine enum - [x] The pg-pure build (`--no-default-features --features postgres`) compiles clean - [x] `cargo test` (default) green; `cargo test --features postgres` green server-less (skipping); `cargo clippy --all-targets -- -D warnings` and `cargo fmt --check` green (default + `--features postgres`) ## References - src/store.rs (the engine-representation design point; the constructor modes; the slotting-in pattern; the `KvCursor` sub-enum design point) - docs/architecture/decisions/011-vocabulary-tiers-engines-and-fleet-state.md §4 (modes; the constructor table) - docs/architecture/decisions/012-pre-decomposition-consistency-rulings.md §3 (fleet activation is a declaration — wave 6), §5 (the one-window-host rule), §6.3 (dual-tier shapes) - docs/architecture/decisions/008-trait-size-probe-fleet-gc-and-large-engines.md §2 (the window delay, default 5 min) - docs/architecture/decisions/013-family-composition-reactive-boundary-and-feature-graph.md §4 (the feature graph; the pg-pure build) - tasks/engine-postgres.md, tasks/engine-postgres-fleet.md (the engine + seam bodies this wires) - tasks/store-sqlite-construction.md (the structural twin's recorded design points) ## Notes > Implemented in `src/store.rs` + `src/postgres.rs` + `src/lib.rs` > (wave 4, following `store-sqlite-construction` — the structural > twin — point for point). Recorded design points, as resolved: > > - **Opts on the public surface** — `PgOpts` went `pub` (and is > re-exported from `lib.rs` behind the feature gate): the > constructors' engine-opts parameter is on the public constructor > signature, so the type must be nameable there (the alkstore > `open(config, PgOpts)` pin adapted to the named-constructor shape; > the connection settings remain the separate DSN-string argument). > - **`KvCursor` slotting** — postgres's materializing body slotted > into the recorded sub-enum exactly as predicted: one variant, two > `ReadCursor` impl arms, no reshaping of `AnyCursor`/`Blob`. The > store.rs design-point note reads landed. > - **`ensure_gc_schema`** rides the engine-neutral `KvEngine` call — > the postgres arm forwards to the `EngineWindowState` clause (the > fleet host's both-tables body: `gc_pins` + `gc_window` in the > fresh schema), and both constructors dispatch through it (the > store core calls the kv engine, never a typed postgres path). > Construction failure is `Err(Error::Io)` per the fallible- > constructor pattern (`?` throughout). > - **The sweep's form branch** — the durable-form body was extracted > into `sweep_window_durable` (behavior > unchanged for sqlite, whose arm now delegates); both SQL-backed > arms call it, so the form branch still keys off the kv-engine > enum, never a flag, and wave 5's pg-lo dormancy slots in without > reshaping. The one-window-host posture's wording now names both > SQL-backed hosts (module doc + the sweep doc). > - **Non-fleet posture** — both constructors' docs carry the > ADR-012 §3 recording verbatim: implicitly `fleet: false`, the > ADR-005 in-process protocol applies regardless of engine (in- > process pins, in-process sweep lease, no `EngineFleetState` clause > runs), and the `fleet: true` declaration is wave 6, slotting in > without reshaping — no fleet parameter carried. > - **Signature shape** — `dual_tier_postgres_local(config, opts, > root, threshold, window_delay)` / `kv_only_postgres(config, opts, > threshold, window_delay)`: connection settings first (the > tokio-postgres `Config`-parseable DSN string), engine opts second, > then the sqlite twins' remaining parameters (root/threshold/ > window delay). The DSN is `&str` — `PostgresBackend::new` parses; > no invented defaults anywhere. > - **Clippy notes** — `KvEngine` needed a scoped > `#[allow(clippy::large_enum_variant)]` under the postgres > configs (the payloads are structurally dissimilar — one `Arc` vs > the pooled-connection posture inline — and all dispatch rides the > enum by reference, so the size difference costs nothing; recorded > on the enum doc). The store-construction `table_exists` probe > (pg_tables, schema-scoped) mirrors the sqlite engine's > `#[cfg(test)]` instrument. > - **Tests (5 new, env-driven, skipping server-less)** — dual-tier > construction + routing + both-GC-tables schema check; kv-only > construction + rejection wiring (immediate, overflow, zero > residue, at-threshold lands); the window-delay default/tunings/ > zero; the dual-tier round-trip through both tiers with tier- > placement assertions; and a durable-form sweep smoke (zero-delay: > first sweep stages and executes nothing — the inline form would > delete in-sweep, so this is the form-branch-is-postgres proof — > the second sweep executes the overdue set re-arbitrated at > execution time). The test-side `KvEngine` matches in the existing > test modules gained `Postgres` arms for `--all-features` > exhaustiveness. > - Stale scope notes updated to the landed state: the engine- > representation doc, the `KvCursor` design point, the fleet- > declaration paragraph, the one-window-host paragraph, the > postgres module doc's forward reference — and `gc.rs`'s > companion-seam note (the fleet family now "waits for wave 6", its > clause bodies landed with wave 4). The batch-delete helper's > scope note stays (its consumer is still wave 6). ## Summary > Completed 2026-10-12 (wave 4). Postgres slotted into the facade: > `KvEngine::Postgres` with all six kv-op dispatch arms, the > `KvCursor::Postgres` materializing body, the `ensure_gc_schema` > engine-neutral call creating `gc_pins` + `gc_window` in the fresh > schema at construction, and the two named feature-gated > constructors (`dual_tier_postgres_local`, `kv_only_postgres`) taking > the DSN connection settings + `PgOpts` (now public and re-exported) + > threshold + the constructor-tunable delete-window delay > (`DEFAULT_DELETE_WINDOW_DELAY` pinned at 5 min, `0` legal) — both > implicitly non-fleet per ADR-012 §3 with wave 6's declaration > recorded as a non-reshaping slotting. The sweep's durable form runs > on the postgres host through the kv-engine-enum-keyed branch > (`sweep_window_durable`, shared with sqlite's arm). Verification: > `cargo test` 69/19/3 ok (default), `cargo test --features postgres` > 102/19/3 ok live and green server-less, `cargo test > --no-default-features --features postgres` (the pg-pure build, ADR- > 013 §4's first constructible configuration) 75/2/3 ok server-less, > `cargo test --all-features` 157/54/4 ok, clippy `-D warnings` green > on default + postgres + pg-pure + all-features, `cargo fmt --check` > green, `cargo doc --no-deps` clean.