Skip to content

Migrations ​

There are no hand-written migration files. The schema is code, versioned with @vN. Deploy a new schema head; each tenant applies it lazily on first touch.

Fingerprint apply-on-touch (Slice A) ​

Every tenant records the fingerprint of the last schema IR it applied (SchemaLog). On a data-touch path the engine compares that fingerprint to the deployed registry:

  1. Match → no-op (catalog-only check; no data-page reads).
  2. Differ → metadata-only apply: ensure @index B-trees exist, write the new fingerprint, persist. Studio's pending_migration clears after that first touch.

Idle tenants never pay. Adding a field with a default, growing an enum, or adding @index does not rewrite pages. Storage is dynamic; values are interpreted through the schema at read time.

Metadata vs @breaking ​

Most diffs are metadata-only. Destructive or narrowing changes require an explicit annotation plus a transform. The compiler refuses to infer data loss:

eel
schema Orders @v4 @breaking(from: @v3) {
  id: Id
  total: Money<usd>
} transform { drop region }
  • @breaking without transform { … } is a parse error.
  • Today transform supports drop <field> (old values stay in page history; reads ignore the dropped field because it is absent from the schema shape).
  • Slice A still treats the drop as metadata: fingerprint bumps, indexes are ensured, pages are not rewritten for the drop.

What compacting does / doesn't today ​

OperationToday
Lazy apply-on-touch (fingerprint + indexes)Yes: Slice A
eelgrass compact / Studio compactYes: rewrites live rows for a tenant (space reclaim)
History-compacting rewrite that purges dropped fields from page historyNot yet. See Status

Until history compaction for @breaking lands, dropped field bytes can remain in versioned page history even after the live schema no longer exposes them.

Drift vs migration ​

MigrationDrift
What movesThe declared schema (fingerprint)Stored values disagreeing with the declared type
How you see itpending_migration until first touchValues typed unknown at stage boundaries
Rewrite?Slice A never rewrites values to force conformanceNarrow with is / as: Types

The two are deliberately independent: migrating the schema does not coerce stored data into the new shape.

Pre-alpha. Local-first. Stdlib-only Rust engine. Tenant concerns shifted left into the database.