loftur

Lifecycles

Schema lifecycle: expand / contract

Your content schema — models and fields — evolves on its own loop. Additive changes are free. Destructive changes are gated by one rule: you may not remove schema the published site still queries.

Expand is always safe

create_model and create_field are additive DDL — a new column, auto-migrated. A new field is simply invisible to the queries your published site already runs, so expanding never breaks anything. (Adding a required field to a model with existing rows needs a default_value, which backfills the column.)

Contract is gated by the footprint

The footprint

Every published version stores a footprint — a snapshot of exactly what GraphQL surface the site uses: the Type.field pairs, type names, and root fields its documents reference. It's computed by walking your queries against the live schema at publish time.

The guard

Destructive schema ops — delete_model, delete_field, and breaking update_field/update_model (a rename or a field retype) — are checked against the published footprint before they run. If the live site still names the thing, the op is refused.

Blocked by the migration guard: "The published site (version v7) still queries field Post.subtitle." — you're trying to contract something still in use.

Non-breaking updates (validators, labels, hints, appearance) always pass — they don't change the GraphQL surface, so the footprint is unaffected.

The safe sequence

When you do need to remove or rename a field, the guard enforces expand-before- contract. Do it in this order and nothing ever breaks:

1. EXPAND        create the replacement field
2. BACKFILL      copy data into it
3. UPDATE CODE   point the site's queries at the new field, then publish_site
                 (this snapshots a new footprint that no longer names the old field)
4. CONTRACT      delete the old field — now allowed

Step 3 is the pivot: publishing new site code atomically updates the contract. Until you publish, the old footprint protects the live site; after you publish, the old field is free to drop.

This is the one place the schema loop and the site-code loop are coupled — and it's a one-way constraint, only at contract time. See how the loops stay decoupled.