Migration
Migration protocol schemas
Migration protocol — the two kinds of migration, kept apart on purpose.
A schema migration (ChangeSet + its atomic operations) reshapes the
physical database to match metadata: add a field, change a type, create an
object, run SQL. It is derivable from the metadata and applied by
os migrate plan / os migrate apply.
A data migration rewrites the rows themselves, and whether it is done is
a property of ONE DEPLOYMENT's database rather than of the installed code
version — so it cannot be expressed as a ChangeSet, and its completion cannot
be inferred from a release. DataMigrationFlag is the per-deployment record
that one ran here and its self-check passed; consumers that would act
irreversibly on migrated data gate on the flag instead of the version.
Source: packages/spec/src/system/migration.zod.ts
TypeScript Usage
import { AddFieldOperation, ChangeSetSchema, CreateObjectOperation, DataMigrationFlagSchema, DeleteObjectOperation, ExecuteSqlOperation, MigrationDependencySchema, MigrationJournalEventSchema, MigrationOperationSchema, ModifyFieldOperation, RemoveFieldOperation, RenameObjectOperation } from '@objectstack/spec/system';
import type { ChangeSet, DataMigrationFlag, DeleteObjectOperation, ExecuteSqlOperation, MigrationDependency, MigrationJournalEvent, MigrationOperation, ModifyFieldOperation, RemoveFieldOperation, RenameObjectOperation } from '@objectstack/spec/system';
// Validate data
const result = AddFieldOperation.parse(data);AddFieldOperation
Add a new field to an existing object
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'add_field' | ✅ | |
| objectName | string | ✅ | Target object name |
| fieldName | string | ✅ | Name of the field to add |
| field | { name?: string; label?: string; type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | … +42 more>; description?: string; … } | ✅ | Full field definition to add |
ChangeSet
A versioned set of atomic schema migration operations
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | ✅ | Unique identifier for this change set |
| name | string | ✅ | Human readable name for the migration |
| description | string | optional | Detailed description of what this migration does |
| author | string | optional | Author who created this migration |
| createdAt | string | optional | ISO 8601 timestamp when the migration was created |
| dependencies | { migrationId: string; package?: string }[] | optional | Migrations that must run before this one |
| operations | ({ type: 'add_field'; objectName: string; fieldName: string; field: object } | { type: 'modify_field'; objectName: string; fieldName: string; changes: Record<string, any> } | { type: 'remove_field'; objectName: string; fieldName: string } | { type: 'create_object'; object: object } | … +3 more)[] | ✅ | Ordered list of atomic migration operations |
| rollback | ({ type: 'add_field'; objectName: string; fieldName: string; field: object } | { type: 'modify_field'; objectName: string; fieldName: string; changes: Record<string, any> } | { type: 'remove_field'; objectName: string; fieldName: string } | { type: 'create_object'; object: object } | … +3 more)[] | optional | Operations to reverse this migration |
CreateObjectOperation
Create a new object
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'create_object' | ✅ | |
| object | { name: string; label?: string; pluralLabel?: string; description?: string; … } | ✅ | Full object definition to create |
DataMigrationFlag
Deployment-level record that a data migration ran here and its self-check passed — the evidence gate consumers read instead of the platform version
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | ✅ | Migration id (e.g. adr-0104-file-references) — one row per data migration |
| last_run_at | string | ✅ | When this migration last completed a gated (apply-mode) run on this deployment |
| verified_at | string | null | optional | When the self-check last PASSED. Null/absent until it does — and cleared again by a later failing run, so a regression closes the gate |
| applied_at | string | null | optional | When the backfill last ran in apply mode (writes enabled) |
| blocking | integer | ✅ | Blocking discrepancies reported by the last self-check. The gate requires 0 |
| advisory | integer | optional | Advisory findings from the last run (external URLs, stale owners, …) — cost storage or need a modelling decision, never block the gate |
| details | string | optional | JSON-encoded counts from the last run, for diagnostics |
| deviation_observed_at | string | null | optional | When this deployment last ADMITTED a value the verified contract rejects, via an OS_ALLOW_LAX_* escape hatch. Does not clear verified_at — it withdraws the irreversible half of what the certificate authorises (#4797) |
| deviation_detail | string | null | optional | JSON-encoded first counterexample behind deviation_observed_at (object, field, type, parse issue), for diagnostics |
DeleteObjectOperation
Delete an existing object
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'delete_object' | ✅ | |
| objectName | string | ✅ | Name of the object to delete |
ExecuteSqlOperation
Execute a raw SQL statement
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'execute_sql' | ✅ | |
| sql | string | ✅ | Raw SQL statement to execute |
| description | string | optional | Human-readable description of the SQL |
MigrationDependency
Dependency reference to another migration that must run first
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| migrationId | string | ✅ | ID of the migration this depends on |
| package | string | optional | Package that owns the dependency migration |
MigrationJournalEvent
One event in a migration run journal — the durable trace that lets a killed run be resumed forward or compensated back, with rows proving which
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| run_id | string | ✅ | Identifies one run. Rows are keyed (run_id, seq) |
| seq | integer | ✅ | Monotonic per-run sequence. Ordering authority — wall-clock timestamps can tie or skew |
| kind | Enum<'run_started' | 'chunk_started' | 'chunk_done' | 'compensated' | 'run_done' | 'run_failed'> | ✅ | Event kind |
| migration_id | string | optional | The named migration this run belongs to, when it has one — joins to sys_migration.id |
| plan_hash | string | optional | On run_started: hash of the plan shape. A resume whose plan hash differs REFUSES rather than resuming a changed plan against an old journal |
| chunk_index | integer | optional | On chunk_started / chunk_done / compensated: the run-global chunk index |
| attempt | integer | optional | Which attempt produced this event. attempt > 1 means a prior outcome was unknown and the callback was asked to recheck by natural key |
| detail | string | optional | JSON-encoded payload — the chunk plan on run_started, the error on run_failed / a failed compensation |
| created_at | string | optional | Wall-clock stamp, for humans. Never the ordering authority — that is seq |
MigrationOperation
Union Options
This schema accepts one of the following structures:
Option 1
Add a new field to an existing object
Type: add_field
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'add_field' | ✅ | |
| objectName | string | ✅ | Target object name |
| fieldName | string | ✅ | Name of the field to add |
| field | { name?: string; label?: string; type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | … +42 more>; description?: string; … } | ✅ | Full field definition to add |
Option 2
Modify properties of an existing field
Type: modify_field
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'modify_field' | ✅ | |
| objectName | string | ✅ | Target object name |
| fieldName | string | ✅ | Name of the field to modify |
| changes | Record<string, any> | ✅ | Partial field definition updates |
Option 3
Remove a field from an existing object
Type: remove_field
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'remove_field' | ✅ | |
| objectName | string | ✅ | Target object name |
| fieldName | string | ✅ | Name of the field to remove |
Option 4
Create a new object
Type: create_object
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'create_object' | ✅ | |
| object | { name: string; label?: string; pluralLabel?: string; description?: string; … } | ✅ | Full object definition to create |
Option 5
Rename an existing object
Type: rename_object
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'rename_object' | ✅ | |
| oldName | string | ✅ | Current object name |
| newName | string | ✅ | New object name |
Option 6
Delete an existing object
Type: delete_object
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'delete_object' | ✅ | |
| objectName | string | ✅ | Name of the object to delete |
Option 7
Execute a raw SQL statement
Type: execute_sql
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'execute_sql' | ✅ | |
| sql | string | ✅ | Raw SQL statement to execute |
| description | string | optional | Human-readable description of the SQL |
ModifyFieldOperation
Modify properties of an existing field
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'modify_field' | ✅ | |
| objectName | string | ✅ | Target object name |
| fieldName | string | ✅ | Name of the field to modify |
| changes | Record<string, any> | ✅ | Partial field definition updates |
RemoveFieldOperation
Remove a field from an existing object
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'remove_field' | ✅ | |
| objectName | string | ✅ | Target object name |
| fieldName | string | ✅ | Name of the field to remove |
RenameObjectOperation
Rename an existing object
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | 'rename_object' | ✅ | |
| oldName | string | ✅ | Current object name |
| newName | string | ✅ | New object name |