# v0.72.0 — Frontier Durability & Catalog Correctness

> **Full details:** [v0.72.0.md-full.md](v0.72.0.md-full.md)

## What's New

v0.72.0 is the "Frontier Durability & Catalog Correctness" hardening release.
It resolves four correctness-critical findings from Assessment 14 (
PLAN_OVERALL_ASSESSMENT_14.md) that represented real data-loss or catalog-
corruption risk in production deployments.

### COR-001/REL-001/ARCH-001: Dead DUR-1 Tentative-Frontier Code Removed

The codebase contained a two-phase tentative-frontier design (DUR-1) with three
functions — `prepare_frontier`, `finalize_frontier_and_complete_refresh` (DUR-1
variant), and `reconcile_tentative_frontiers` — that had no call sites in
production code and contained invalid SQL in the recovery path. The scheduler
hot-path used the single-phase `store_frontier` but treated failure as log-only,
providing no durability guarantee.

Fix: the three dead DUR-1 functions are removed. The canonical path
(`store_frontier` / `store_frontier_and_complete_refresh`) is now the only
mechanism for advancing the frontier. All six scheduler refresh paths propagate
frontier-store failures using `?`, making them fatal. A frontier-store failure
aborts the entire refresh transaction, preventing partial commits. See
[ADR-004](../plans/adrs/ADR-004.md) for the full rationale.

### COR-002/API-001: Outbox `stream_table_oid` Stores the Correct OID

`pgtrickle.pgt_outbox_config.stream_table_oid` was being populated with
`pgt_id::oid` — an internal sequential counter cast to an OID type — instead of
`pgt_relid`, the actual `pg_class.oid` for the underlying table. This made all
outbox joins against `pg_class` silently wrong, and meant that outbox consumers
could not resolve the table from the stored OID.

Fix: all five write/read paths in `src/api/outbox.rs` now use `pgt_relid`
directly. The migration SQL updates any existing rows.

### COR-003: WAL Transition Handoff is Now Atomic

`complete_wal_transition` previously dropped the CDC trigger before updating the
catalog mode to `WAL`, creating a race window where concurrent writes could be
missed: the trigger was gone but the WAL reader had not yet taken over.

Fix: the update order is reversed and wrapped in a `pg_advisory_lock` —
catalog mode is updated to `WAL` first; the trigger is dropped second; the
advisory lock ensures only one concurrent call can execute the handoff for a
given stream table.

### COR-004: Replication Slot Creation Guarded Against XID Assignment

`create_replication_slot_pristine` would attempt to create a replication slot
in a transaction that already had an XID assigned (e.g. after any catalog write),
which causes PostgreSQL to error with "replication slot cannot be created in a
transaction that has performed writes". This was surfaced in rare test
environments and could occur in production during concurrent DDL.

Fix: the function now checks `GetCurrentTransactionIdIfAny()` before calling
the slot-creation primitive and returns an actionable `PgTrickleError` if the
caller is in a dirty transaction.

## Breaking Changes

None for users. Operators who inspect `pgt_outbox_config.stream_table_oid`
directly should note that the migration SQL corrects existing rows to hold the
real `pg_class` OID rather than the internal `pgt_id` counter.
