# v0.91.0 — Schema & Query Evolution: Release Contract

This document turns [v0.91.0.md](v0.91.0.md) into an implementation contract.
The release is admissible only when every lifecycle transition has one of the
three outcomes below: correct continuation, protected rebuild, or explicit
suspension. “Best effort” and silently stale output are not outcomes.

## Canonical implementation vocabulary

The implementation and tests use these exact names so the offline release gate
can check the contract without parsing Rust or SQL:

| Contract | Required name |
|---|---|
| Query decision | `AlterClassification` |
| Safe continuation | `Compatible` |
| Protected rebuild | `Rebuildable` |
| Fail-closed decision | `Rejected` |
| State proof | `AlterStateOracle` |
| Pre-mutation explanation | `explain_alter` |
| Stable suspension reason | `AlterReasonCode` |
| Atomic cutover | `atomic_swap_shadow_table` |
| Interrupted-build recovery | `resume_or_rollback_shadow_build` |

These names are intentionally narrower than generic terms such as
“compatible”, “rebuild”, or “atomic”. The gate checks the named symbols in the
owning files and checks the contract tests by exact test-function name.

## LC-1 — Defining-query replacement

`alter_stream_table_query()` must validate and classify the proposed query
before changing catalog, storage, dependencies, or frontier state.

### Classification rules

`AlterClassification::Compatible` requires a positive `AlterStateOracle` proof
that all four state components remain valid:

1. materialized result;
2. frontier;
3. row identities;
4. auxiliary DVM state.

If that proof is absent, the result is `Rebuildable` when a shadow candidate can
be built and swapped. A query is `Rejected` when validation, dependency, or
resource checks prevent either safe continuation or a protected rebuild.

The public `pgtrickle.explain_alter()` API reports the classification, reason
code, affected state, and estimated rebuild cost without mutation. Its result
must identify the exact stream table and defining query inputs.

### Protected rebuild

`Rebuildable` changes follow this sequence in one protected lifecycle:

1. record the build intent and old state;
2. create and populate shadow state;
3. validate the complete candidate, including row identities and auxiliary
   state;
4. call `atomic_swap_shadow_table` under the stream-table lifecycle lock;
5. commit the new query, dependencies, and frontier together;
6. remove shadow state only after successful cutover.

An interruption before cutover leaves the old result readable and resumable or
rolls back the shadow state. An interruption after cutover leaves the new
state complete. No partial shadow result is visible as the stream table.

Required deterministic tests:

- `test_explain_alter_classifies_compatible_without_mutation`
- `test_explain_alter_classifies_rebuildable_without_mutation`
- `test_explain_alter_rejects_without_mutation`
- `test_alter_query_proves_materialized_result_frontier_row_identity_auxiliary_state`
- `test_shadow_rebuild_atomic_swap_preserves_old_result_until_cutover`
- `test_shadow_rebuild_interruption_resumes_or_rolls_back`

## LC-2 — Source-schema evolution

DDL handling must use dependency identity, not names alone. A dropped and
recreated object with the same qualified name is a new dependency and must not
reuse the old proof.

The following transitions require deterministic tests:

- `test_schema_evolution_transactional_ddl_rollback`
- `test_schema_evolution_additive_column_continues_or_rebuilds`
- `test_schema_evolution_destructive_column_suspends_with_reason_code`
- `test_schema_evolution_rename_chain_preserves_or_suspends_explicitly`
- `test_schema_evolution_schema_move_and_owner_change`
- `test_schema_evolution_recreated_object_oid_is_not_reused`
- `test_schema_evolution_dependency_oid_change_invalidates_proof`
- `test_schema_evolution_refresh_concurrent_with_ddl`
- `test_schema_evolution_extension_upgrade_overlap`

Ambiguous or destructive changes set the stream table to an explicit suspended
state with an `AlterReasonCode`, expose that state through `health_check()`, and
include an actionable `reinitialize_stream_table` repair command. Transaction
rollback must leave the pre-DDL query, dependencies, materialized result,
frontier, row identities, and auxiliary state unchanged.

## Exit gate

- [x] Query decisions use `Compatible`, `Rebuildable`, or `Rejected`.
- [x] Compatibility is proved by `AlterStateOracle` for all four state parts.
- [x] Explanations are available through `explain_alter` before mutation.
- [x] Rebuilds use shadow state and `atomic_swap_shadow_table`.
- [x] Interrupted builds call or implement `resume_or_rollback_shadow_build`.
- [x] Suspensions use stable `AlterReasonCode` values and appear in
      `health_check()` with a repair command.
- [x] Every named deterministic transition test exists.

The offline gate is `python3 scripts/v0_91_release_gate.py`. It is deliberately
conservative: missing evidence fails the gate; generic prose or unrelated
symbols do not satisfy it.
