Build a migration
A migration maps vertices and edges in a source schema to a target schema. Build or derive the mapping, check it, then apply it to representative data.
Prerequisites
Two panproto schema JSON files and the schema CLI. The current CLI protocol resolver supports atproto. The TypeScript SDK can check a narrow explicit mapping between two protocol-tagged schema handles, with the qualifications in Translate across protocols.
Derive a mapping
Inspect the inferred span first:
schema auto-migrate schemas/v1.json schemas/v2.json
Review its coverage and vertex map. Then save the right leg as a migration:
schema auto-migrate schemas/v1.json schemas/v2.json --monic --json \
> migrations/v1-to-v2.json
--monic prevents two apex vertices from mapping to the same target vertex. Omit it only when the contraction is intentional and a separate value-level rule will combine the values.
The generated file maps the matched apex into v2, not necessarily all of v1. Use --total if partial coverage is unacceptable:
schema auto-migrate schemas/v1.json schemas/v2.json --total --json \
> migrations/v1-to-v2.json
Check the mapping
schema check \
--src schemas/v1.json \
--tgt schemas/v2.json \
--mapping migrations/v1-to-v2.json \
--typecheck
The existence check validates the migration against the source, target, and protocol theories. --typecheck also validates the induced GAT morphism. The command exits non-zero when either enabled check reports an error.
A passing check does not establish source-wide coverage. The mapping may omit unmatched source vertices, so retain the auto-migrate report or require --total when all source data must move.
Apply the migration
schema lift \
--migration migrations/v1-to-v2.json \
--src-schema schemas/v1.json \
--tgt-schema schemas/v2.json \
data/user.json > data/user-v2.json
The default direction is restrict and the default instance type is wtype. In this command, restrict reads a source-shaped record, follows the compiled source-to-target migration, retains the part represented by mapped vertices, and serializes that result with the target schema. It is not the categorical precomposition functor (\Delta_F), which has the opposite instance-level direction, from a target instance to a source instance. Both other modes also run source to target. sigma uses the total extension path. For functor instances, pi forms Cartesian products from the values associated with each target vertex (its fiber). For W-type instances, pi only accepts vertex-injective mappings and relabels the tree; it does not construct the general right Kan extension. The vocabulary in plain terms gives the categorical directions. The command infers the record root from the migration’s mapped source vertices; a mapping with no vertex entries cannot be applied.
Build a mapping in TypeScript
const builder = p
.migration(srcSchema, tgtSchema)
.map('user', 'user')
.map('user:name', 'user:display_name');
const report = p.checkExistence(srcSchema, tgtSchema, builder);
if (!report.valid) throw new Error(JSON.stringify(report.errors));
using migration = builder.compile();
const migrated = migration.liftJson(oldRecord, 'user');
MigrationBuilder supports vertex maps, edge maps, and contraction resolvers. Per-field expressions are not part of this builder; use the field-transform guide for the supported TypeScript lens-document route.
Classify compatibility separately
Migration validity and compatibility answer different questions. To classify the schema change for CI, run:
schema compat schemas/v1.json schemas/v2.json --protocol atproto
The command exits 0 when it finds no breaking change, 1 for a breaking change, and 2 for a usage or load error. Add --format json for machine-readable output.
Limitations
schema checkdoes not measure how much of the source schema is mapped.- Automatic alignment ranks candidates; it does not certify that the selected correspondence matches domain intent. Review every generated map.
- A vertex map cannot split one value across several targets or compute a new value. Those operations require field transforms.
schema lift --direction restrictis named afterpanproto-mig’s surviving-fragment operation. Do not identify it withpanproto-inst::adjunction::w_deltaorf_delta, which implement target-to-source precomposition.
See also
- Find a span between two schemas for coverage and search constraints.
- Apply field transforms for value-level rewrites.
- Migrations as morphisms for the model.
- CLI reference for all migration commands.