You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/docs/deployment/cli.mdx
+42Lines changed: 42 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -517,6 +517,48 @@ first. It never drops a table that is absent from your metadata, and on SQLite
517
517
it reconciles via a table rebuild (copy → swap) that preserves your data.
518
518
</Callout>
519
519
520
+
#### Data migrations
521
+
522
+
The commands above reconcile **schema**. A *data* migration rewrites rows, and
523
+
whether it is done is a fact about **your** database, not about the platform
524
+
version you installed — so each deployment runs it, and its result is recorded
525
+
where the data lives.
526
+
527
+
| Command | Description |
528
+
|---------|-------------|
529
+
|`os migrate files-to-references`| Convert legacy file-field values to `sys_file` references, verify the ownership ledger, and record the deployment's migration flag |
530
+
531
+
```bash
532
+
os migrate files-to-references # Dry run: full report, writes nothing
533
+
os migrate files-to-references --apply # Convert, verify, record the flag (prompts)
534
+
os migrate files-to-references --apply --yes --json # CI / scripts
535
+
os migrate files-to-references --object product # Restrict to one object (repeatable)
536
+
```
537
+
538
+
A `file` / `image` / `avatar` / `video` / `audio` field value is an opaque
539
+
`sys_file` id that the platform owns. Values written before that (an inline
540
+
`{url, name, …}` blob, or a URL naming this platform's own
541
+
`…/storage/files/:id` resolver) are converted in place; **external URLs are
542
+
reported, never re-hosted** — re-hosting third-party content is a licensing and
543
+
privacy decision, and the right fix is usually to model the field as a `url`
544
+
field instead.
545
+
546
+
The run then reconciles what records actually hold against what `sys_file`
547
+
records as each file's owner. Zero blocking discrepancies is what records the
548
+
flag — and that flag, not the version number, is what later enables features
549
+
that act irreversibly on migrated data (reclaiming the bytes of released files;
550
+
rejecting rather than warning about legacy media values). Never running it is
551
+
safe: files are simply retained forever.
552
+
553
+
Exit status is `0` only when the self-check passes, so CI can gate on it.
554
+
555
+
<Callouttype="warn">
556
+
A dry run writes **nothing** — not the conversions, and not the flag either,
557
+
even when the self-check would pass. `--apply` is the only writing mode. A
558
+
later run that *fails* its self-check clears the flag's verified state, so a
|**object**|`{ name: string; label?: string; pluralLabel?: string; description?: string; … }`| ✅ | Full object definition to create |
70
92
71
93
94
+
---
95
+
96
+
## DataMigrationFlag
97
+
98
+
Deployment-level record that a data migration ran here and its self-check passed — the evidence gate consumers read instead of the platform version
99
+
100
+
### Properties
101
+
102
+
| Property | Type | Required | Description |
103
+
| :--- | :--- | :--- | :--- |
104
+
|**id**|`string`| ✅ | Migration id (e.g. adr-0104-file-references) — one row per data migration |
105
+
|**last_run_at**|`string`| ✅ | When this migration last completed a gated (apply-mode) run on this deployment |
106
+
|**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 |
107
+
|**applied_at**|`string \| null`| optional | When the backfill last ran in apply mode (writes enabled) |
108
+
|**blocking**|`integer`| ✅ | Blocking discrepancies reported by the last self-check. The gate requires 0 |
109
+
|**advisory**|`integer`| optional | Advisory findings from the last run (external URLs, stale owners, …) — cost storage or need a modelling decision, never block the gate |
110
+
|**details**|`string`| optional | JSON-encoded counts from the last run, for diagnostics |
0 commit comments