Repository navigation
Commit fa06169
fix(cli): os migrate apply names the birth marker, not a ceremony, when it lets an organization_id drop through (#25500)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuDWJsBGQpadCH8JR2xZQ5re)_
Fixes #25466
Clause-②: no
Metadata-protocol: none
## What changed
`os migrate apply --allow-destructive`, when the organization-ownership
drop gate lets an `organization_id` drop through, now says which row let
it through:
- **Birth marker** (`details.birth`, written by the boot guard on a
datastore created from empty): `organization_id on acme_note: this
database was created from empty (CREATED_AT, platform VERSION) and
carries the organization-ownership birth marker, so it holds no legacy
organization rows and the ceremony is not needed for it — the drop may
proceed.`
- **Ceremony run** (`details: { checkpoint }`): the same sentence as
before, `the organization-ownership ceremony verified (VERIFIED_AT) —
the drop may proceed.`
The distinction is read from the `flag` the gate's one marker read
(`gateReads.organizationOwnershipMarker`) already returned: no second
database read. The new pure helper `organizationOwnershipBirthOf(flag)`
in `packages/cli/src/utils/organization-ownership-drop-gate.ts` parses
`details.birth` with the spec's existing
`OrganizationOwnershipBirthMarkerSchema` (`@objectstack/spec/system`,
already exported). No new export from `packages/runtime` or
`packages/spec`.
Unchanged: the `--json` document (it prints no such line), exit codes,
the accept set (`isDataMigrationFlagVerified` still decides), and the
refusal branch. `plan.ts` prints no such line on a verified database, so
it is untouched.
## Discriminator: where it comes from
The runtime boot guard does not tell the two apart. It decides on the
shape generation alone (`organizationOwnershipShapeGenerationOf`, which
reads `details.birth ?? details.checkpoint`), and no exported reader
answers "birth or ceremony". The spec's docblock on
`ORGANIZATION_OWNERSHIP_MIGRATION_ID` defines the birth marker as the
row whose `details.birth` is an `OrganizationOwnershipBirthMarker`. The
ceremony's ledger write (`organization-ownership.ts`, `details: {
checkpoint }`) replaces the whole field, so a row carries one or the
other. The helper reads that declared key with the declared schema. If
`details.birth` is present but does not parse, the line still takes the
birth arm and names the row's `verified_at`, which the guard writes from
the same clock.
## Measured reproduction (real CLI, SQLite, harness copied from the
earlier tenant:false run)
Database created by `os migrate apply --yes` on an empty file. Its
`sys_migration` row is
`{"birth":{"shapeGeneration":1,"platformVersion":"18.0.0","createdAt":"…"}}`.
Then `acme_note` declares `systemFields: { tenant: false }` and `os
migrate apply --allow-destructive --yes` runs:
| leg | tree | exit | success line |
|---|---|---|---|
| before | `apply.ts` from base `ab510f42e7`, cli rebuilt | 0 | `✓
organization_id on acme_note: the organization-ownership ceremony
verified (2026-10-23T15:32:50.299Z) — the drop may proceed.` |
| after | `b58c1ed845` | 0 | `✓ organization_id on acme_note: this
database was created from empty (2026-10-23T15:32:05.034Z, platform
18.0.0) and carries the organization-ownership birth marker, so it holds
no legacy organization rows and the ceremony is not needed for it — the
drop may proceed.` |
In both legs the column is dropped (`pragma_table_info` has no
`organization_id` afterwards) and `Applied 1 change(s).` prints. With
`--json`, both legs exit 0 with the same key set. After normalising
timestamps and the database file name, the documents differ only in
`duration` (959 vs 907 ms). Neither prints the line. The before leg was
restored with `git checkout HEAD -- apply.ts`: `git diff HEAD` was
empty, the hash matched the HEAD blob, and cli was rebuilt (the marker
string is back in `dist/`).
## Tests
`packages/cli/src/commands/migrate/apply.organization-ownership-refusal.test.ts`
runs the real `MigrateApply.run` path, with the boot and the marker read
replaced. It adds three cases:
- a birth-marker row: exit 0, the drop is applied, and the line names
"created from empty", the creation time and "birth marker", and does not
contain "ceremony verified";
- a ceremony-run row (`details.checkpoint`): exit 0, and the line keeps
`the organization-ownership ceremony verified (VERIFIED_AT)`;
- `--json` for both rows: exit 0, no success line, and the two documents
are equal.
## Local gates (head `b58c1ed845`)
- `pnpm --workspace-concurrency=2 --filter '@objectstack/cli...' build`:
VERDICT command-exit 0
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2`: VERDICT command-exit 0, Test Files 326 passed (326),
Tests 4808 passed (4808)
- `pnpm --filter @objectstack/cli typecheck`: VERDICT command-exit 0
- `pnpm lint`: VERDICT command-exit 0
- `dispatch-gates --commands`: 67 derived. 65 ran with exit 0. 2 are NOT
MEASURED (exit 3, PREREQUISITE NOT MET): `check:dual-build-cjs-loads`
and `check:i18n-coverage`, because both need a whole-repo `pnpm build`
and only the cli closure was built here. CI runs them.
- `--ran` reconciliation: 67 derived, 65 run, 2 NOT-MEASURED (derived
from recorded exit 3), 0 UNRUN.
- The cli integration tier is left to CI: the diff touches no
integration-tier file and no spawn entry.
## Acceptance notes
- The ceremony arm is pinned at the unit layer only. Running a real
ceremony on SQLite to read its line was not part of this S card.
- The `sys_comment.reactions` sibling success line was checked and left
alone. Its marker (`sys-comment-reactions-to-records`) is written only
by `os migrate comment-reactions --apply`
(`comment-reactions-conversion.ts`) and is not creation-attested
(`CREATION_ATTESTED_MIGRATION_IDS` holds only file-references and
value-shapes), so a verified row there always comes from a real run.
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 5188e7f commit fa06169
4 files changed
Lines changed: 132 additions & 4 deletions
File tree
- .changeset
- packages/cli/src
- commands/migrate
- utils
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
182 | 182 | | |
183 | 183 | | |
184 | 184 | | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
185 | 259 | | |
186 | 260 | | |
187 | 261 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| |||
627 | 627 | | |
628 | 628 | | |
629 | 629 | | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
630 | 633 | | |
631 | | - | |
632 | | - | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
633 | 641 | | |
634 | 642 | | |
635 | 643 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
0 commit comments