Mirror, don't migrate

Mirror, don’t migrate

When you need to evolve substrate, prefer adding a runtime mirror that reads from the canonical source and exposes a typed surface, over destructively replacing the canonical source with a new schema.

The mirror pattern keeps the old source authoritative through the transition, lets new consumers read from the typed runtime surface, and defers the cutover decision until evidence supports it.

What “mirror, don’t migrate” looks like

Why this exists

Two concrete examples from the platform’s recent history:

The migrate-destructively alternative — drop the old table, point all consumers at the new one in one PR — produces three predictable failure modes:

  1. Lost evidence. Operator data living in the old table is gone the moment the migration drops it. Recovery costs hours.
  2. Atomic-PR blast radius. Every consumer must change in the same PR, so review takes longer, rollback is harder, and one missed call site breaks the platform.
  3. No graceful fallback. If the new schema turns out to need a column that wasn’t anticipated, you have to migrate again on top of a half-functional substrate.

When mirroring is the wrong default

The contract

Before opening a PR that replaces an existing source of truth, ask:

  1. Who is the current canonical source? Name it.
  2. Why must I replace it rather than mirror it? Document the reason.
  3. What evidence do I have that the mirror is unworkable? A claim of “easier to just migrate” is not evidence.
  4. What’s the rollback path? If you can’t draw a one-line rollback for the destructive option, the mirror is the architecturally sound choice.

Anti-patterns

Spec references