Sharing a table does not automatically make two migrations conflict. Stable snapshot identities let Ship reason about the actual entities each operation reads or changes.
Independent columns
Suppose A adds Users.timezone while B adds Users.marketing_opt_in, both from S:
A = add Column(End.Users, .timezone)
B = add Column(End.Users, .marketing_opt_in)
The inherited Users table has the same hidden TableId in both branches. Each new column has a distinct deterministic identity. Applying either addition first leaves the other operation valid:
apply(B, apply(A, S)) == apply(A, apply(B, S)) == M
Integration emits the same multi-parent checkpoint as the different-table case:
unify parents = [A, B], to = U
The original migration files remain the executable intent. The unify source is only the proof-backed checkpoint:
unify AppDb(
parents = [snapshot("A"), snapshot("B")],
to = snapshot("U"),
) {}
The same addition on both branches
If both branches add the same column with the same definition from the same Start, deterministic entity identity and normalized snapshot content expose them as duplicate transitions. Ship refuses to discard either filename silently because a database may already record that exact migration ID. Remove the unpublished duplicate explicitly; the surviving transition is applied only once.
If the definitions differ, the shared identity exposes the disagreement:
A: Users.timezone: String
B: Users.timezone: Option(String)
Git may select one declaration text, but Ship still compares both branch states. Reaching the selected End requires an explicit nullability transition from the other branch; if that transition needs data intent, generation produces a todo or conflict rather than calling the branches independent.
Rename plus another change
Stable lineage also connects operations across names:
A: rename Users.full_name -> Users.display_name
B: set_not_null Users.full_name
A's rename preserves the column ID, so Ship can explain the overlap precisely. The original SQL programs do not commute, however: running B after A would still name full_name. Automatic unification therefore stops rather than pretending that an empty checkpoint makes the order safe.
Reconcile the overlap with an ordinary migration based on one head, then unify the resulting compatible leaves. Future operation transport may safely rewrite B to target display_name, but that is distinct from merely joining graph ancestry.
Compatibility is about typed entity and dependency effects, not whether two files mention the same table name.