Compatibility-preserving migration tickets
Use expand/migrate/contract stages when a broad mechanical change cannot land in independent vertical slices.
These examples and illustrative results are independently authored teaching materials, not measured model results.
Use case
Rename shared timeout to timeoutMs across packages while old callers remain supported. Teaching dual fields with unequal values fail explicitly. Direct rename breaks remaining consumers, motivating expand→migrate→contract.
Mechanism
Expand with new/old forms and clear conflict rules; verify both. Inventory and migrate consumers in bounded batches dependent on expansion. Remove old support only after required consumers and compatibility/reversal checks complete. Declare an integration-only gate where independent green batches are impossible.
Bad example
Rename immediately, let packages fail until final integration and hide failures by removing consumers or silently ignoring old input.
Good example
Teaching expansion accepts timeoutMs/timeout, equal dual values and explicit unequal-value conflict; verify old callers. Migrate/check each inventoried package before contraction. Do not remove old support with remaining consumers. Where batches cannot pass independently, state the final integration gate rather than falsely claiming batch-green delivery.
Why the change matters
Coexistence creates a migration window and dependencies define deletion conditions. Conflict policy avoids guessing between forms; declared integration gates preserve honest stage limitations.
Observable expectation
Check old-only, new-only, equal dual and conflicting dual inputs. Remaining old consumers block contraction.
Verify final chain behavior; zero internal string matches does not establish external migration. No real migration is performed.
Limits
Dual-value/default/parsing rules are teaching choices. Real compatibility needs an explicit API contract plus data/external-consumer/release constraints; one staging pattern does not fit every broad change.