# v0.58.0 — Security & Correctness Hardening

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

## What's New

v0.58.0 is the first release of the Assessment-Driven Hardening Arc, driven
by the findings in the v0.57.0 overall assessment
([plans/PLAN_OVERALL_ASSESSMENT_12.md](../plans/PLAN_OVERALL_ASSESSMENT_12.md)).
It addresses all HIGH-severity security and correctness findings before any
feature work resumes.

### SEC-1/2: Ownership Checks for Outbox and Publication APIs (S-1, S-2)

`attach_outbox()`, `detach_outbox()`, `attach_embedding_outbox()`,
`stream_table_to_publication()`, and `drop_stream_table_publication()` now call
`check_stream_table_ownership()` immediately after resolving the stream table
metadata — the same guard used by every other mutating API.  Previously, any
role with `EXECUTE` on the `pgtrickle` schema could attach a pg_tide outbox
or create a logical-replication publication for a stream table owned by a
different role.

### COR-1: Multi-Column NOT IN + NULL Row Handling (C-1)

The v0.55.0 multi-column `IN` rewrite now correctly handles `NULL` elements
in the row constructor and `NULL`-producing columns in the subquery's target
list.  When any left-side element is a `NULL` constant, or when the rewrite
would produce an anti-join that cannot faithfully represent `NOT IN` semantics
(SQL UNKNOWN propagation), the DVM parser falls back to the original
subquery-based execution path and emits a diagnostic note.

### COR-2: Recursive-CTE Depth Guard in DIFFERENTIAL Mode (C-2)

The `ivm_recursive_max_depth` GUC is now applied consistently to both
DIFFERENTIAL (`ChangeBuffer`) and IMMEDIATE (`TransitionTable`) modes.
Previously only IMMEDIATE mode enforced the depth limit; a pathological
recursive CTE definition in DIFFERENTIAL mode could loop until WAL or
temp-buffer limits were exceeded.

### COR-3: WAL Decoder TOCTOU Advisory Lock (C-3)

`is_slot_suitable_for_wal_transition()` and `poll_wal_changes()` are now
executed inside the same SPI transaction, holding a `pg_advisory_xact_lock`
keyed on the slot OID.  This eliminates the race window where a concurrent
`pg_drop_replication_slot()` could invalidate the slot between the eligibility
check and the first WAL consumption.

### COR-4: Compact-Buffer Lock Contention Is Now Observable (C-4)

`compact_change_buffer()` no longer returns `Ok(0)` when it fails to acquire
the advisory lock.  It now returns `CompactionResult::Contended`, increments
a new shared-memory counter `pg_trickle_cdc_compact_contended_total`, and
logs at `debug!` level.  The counter is exposed via the Prometheus `/metrics`
endpoint so operators can detect persistent contention.

### SEC-3: DDL Hook Escalates on SPI Failure (S-3)

`handle_alter_table()` in `src/hooks.rs` no longer silently returns when
`find_view_downstream_pgt_ids()` fails with an SPI error.  It now retries
once and, on repeated failure, raises an `error!()` so the originating DDL
statement itself fails with a clear message.  This prevents a catalog-query
outage from leaving downstream stream tables invisibly stale.

### SEC-4: Schema Identifier Quoted in CDC Buffer Names (S-4)

`buffer_qualified_name_for_oid()` now uses `sql_builder::qualified()` instead
of `format!("{change_schema}.{base}")`, ensuring the schema identifier is
properly quoted and safe against unusual schema names.
