# v0.91.0 — Lifecycle & Schema Evolution

> **Status:** Planned
> **Scope:** Very Large
> **User promise:** *"Production changes don't break my stream tables."*
> **Blocked by:** [v0.90.0](v0.90.0.md)
> **Renumbered:** previously v0.86.0.

## Theme

An extension that works perfectly until the first PostgreSQL major upgrade is
not production-ready. Neither is one that breaks when someone adds a column,
restores a backup, clones a database into staging, or renames a schema.

This release is dedicated entirely to making the boring, unavoidable operations
of a real production database boring for pg_trickle too. Nothing here is a
feature users will brag about; all of it is the reason they will still be
running pg_trickle in three years.

## Items

### LC-1: Safe defining-query replacement

Changing the defining query today means dropping and recreating. Instead,
`pgtrickle.alter_stream_table_query()` gains a classification and swap protocol:

1. Classify the change: **compatible** (same output schema and provably
   compatible incremental plan), **rebuildable** (output schema changes, or the
   plan changes such that existing state is invalid), or **rejected**
2. Compatible changes swap the plan atomically, keeping existing state and
   frontier — no repopulation, no visible gap
3. Rebuildable changes populate a shadow table, then swap under a brief lock,
   so readers never see a partially populated result
4. `pgtrickle.explain_alter('st', $$new query$$)` reports the classification and
   the estimated rebuild cost *before* the user runs it

> This is a function-API change, not new DDL grammar. Declarative
> `ALTER STREAM TABLE ... AS` was dropped from the product arc; the
> `pgtrickle.exec_stream_ddl()` shim is unchanged by this release.

### LC-2: Automatic handling of compatible source-schema changes

DDL event triggers detect source changes and act, controlled by
`pg_trickle.auto_evolve` (default `true` for additive changes only):

**Automatic:**
- `ADD COLUMN` — propagate when the defining query uses `SELECT *`; otherwise
  no-op
- `ADD CONSTRAINT`, `CREATE INDEX`, `DROP INDEX`, `SET STATISTICS`, `COMMENT` —
  no-op
- source table rename, schema move, ownership change — catalog references
  updated in place

**Suspend with a clear diagnosis:**
- `DROP COLUMN` used by the defining query
- `ALTER COLUMN TYPE` on a column used by the defining query
- replica identity or primary key removal on a source

Suspension is loud and actionable: `NOTIFY`, `health_check()` entry, an entry in
refresh history with reason `SCHEMA_INCOMPATIBLE`, and a `HINT` naming the exact
`pgtrickle.alter_stream_table_query()` call that would resolve it. A suspended
stream table stops refreshing but never serves silently wrong data.

### LC-3: Backup, restore and PITR

- `pg_dump`/`pg_restore` round-trip for every catalog object, building on the
  durable/derived catalog classification established in
  [v0.84.0](v0.84.0.md) CAT-81-5, with a restore-time consistency check
- documented and tested PITR behaviour: after a point-in-time restore, frontiers
  are validated against the restored WAL position, and stream tables whose
  frontier is ahead of the restored database are automatically marked for
  reinitialisation rather than diverging silently
- `pg_basebackup` and physical replica promotion covered by E2E tests
- restoring a dump into a database without the extension yields ordinary tables
  and a clear message, never a broken restore

### LC-4: Database cloning and environment copies

`CREATE DATABASE ... TEMPLATE`, dump/restore into staging, and logical copies
are first-class: cloned stream tables detect that they are a clone (database
identity changed), drop stale replication-slot bindings, and either resume with
fresh CDC or suspend with an explicit message. No clone ever consumes the
original's replication slots.

### LC-5: `pgtrickle.preflight_upgrade()`

One function a DBA runs before a PostgreSQL major upgrade or an extension
upgrade, which answers "is this safe?":

```sql
SELECT * FROM pgtrickle.preflight_upgrade();
```

```
 check                            | status | detail
----------------------------------+--------+-------------------------------------------
 extension version supported      | OK     | 0.91.0 → 1.0.0 upgrade path exists
 stream tables suspended          | WARN   | 1 suspended (customer_360) — resolve first
 in-flight refreshes              | OK     | none
 replication slots                | OK     | 3 slots, all active, max lag 1.2 MB
 change buffer backlog            | WARN   | orders backlog 2.1M rows — drain recommended
 catalog integrity                | OK     | 0 orphaned entries
 disk headroom for rebuild        | OK     | 41 GB free, 3.2 GB estimated
```

Backed by a documented drain-and-quiesce procedure
(`pgtrickle.quiesce()` / `pgtrickle.resume_all()`) so an upgrade window can be
entered cleanly.

### LC-6: PostgreSQL major upgrades and extension upgrades

- `pg_upgrade` across major versions covered end to end in CI, including a run
  with active stream tables and a non-empty change buffer
- extension upgrade E2E extended to cover every supported source version to the
  release under test, with the existing cutoff policy documented in place
- downgrade remains unsupported and is enforced with a clear error plus the
  rollback runbook shipped in v0.80.0

### LC-7: Automatic CDC repair

When capture breaks — dropped replication slot, dropped trigger, a source
restored out from under a stream table, WAL removed before it was consumed —
pg_trickle detects it, reports it precisely, and where safe repairs it
automatically by rebuilding capture and reinitialising affected stream tables.
Where automatic repair is unsafe, it suspends and names the manual step. The
existing repair tooling becomes the automatic path rather than a documented
procedure the user has to find.

## Exit criteria

- [ ] `pgtrickle.alter_stream_table_query()` supports compatible and
      rebuildable changes; `explain_alter()` classifies correctly for every DVM
      support matrix entry
- [ ] Additive source DDL flows through with no user action; destructive DDL
      suspends with a named remediation; no path produces silently wrong data
- [ ] PITR, `pg_basebackup`, replica promotion, `pg_dump`/`pg_restore` and
      database cloning each covered by an E2E test
- [ ] `preflight_upgrade()` shipped and referenced from the upgrade runbook
- [ ] `pg_upgrade` major-version test green with active stream tables and a
      non-empty change buffer
- [ ] CDC break-and-repair chaos test: every injected failure is either
      auto-repaired or suspended with an actionable message
