# 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.