Skip to content

fix(cli): os migrate apply names the birth marker, not a ceremony, when it lets an organization_id drop through - #25500

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-25466-birth-marker-text
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-25466-birth-marker-text

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

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.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX

…id drop success line

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 23, 2026
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 3 documentable anchor(s).

10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/data-modeling/drivers.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/data-modeling/queries.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/deployment/cli.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/deployment/index.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/deployment/seed-tenancy-repair.mdx (via verified_at (literal, a string literal in organizationOwnershipBirthOf))
  • content/docs/deployment/self-hosting.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/kernel/services-checklist.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/protocol/kernel/lifecycle.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/upgrading.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))

⛔ 6 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-3.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/releases/v17/17-5.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/releases/v17/17-6.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/releases/v17/17-7.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/releases/v18/18-0.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/releases/v18/18-1.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 30 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 4c8f40a1961f75b84d5f2222554c471e035a3744 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 36077993ac4332f2eda7511b18a338e739a4f7f7 — the merge of head b3854a5fa405038954a269b05b5654051c2c07de into base 4c8f40a1961f75b84d5f2222554c471e035a3744, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 36077993ac4332f2eda7511b18a338e739a4f7f7 && git checkout 36077993ac4332f2eda7511b18a338e739a4f7f7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4c8f40a1961f75b84d5f2222554c471e035a3744 b3854a5fa405038954a269b05b5654051c2c07de && git checkout -B drift-repro 4c8f40a1961f75b84d5f2222554c471e035a3744 && git merge --no-ff b3854a5fa405038954a269b05b5654051c2c07de

node scripts/docs-audit/affected-docs.mjs --json 4c8f40a1961f75b84d5f2222554c471e035a3744

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 4c8f40a1961f75b84d5f2222554c471e035a3744 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

… --json documents

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:cli seat 1 (seat post #6024; lane read as domain:server) · os-sales · session session_01CxBKycaiUpA1yGx1XyoGqX · 2026-10-23T17:12Z · head b3854a5fa4

item reading
Shape 4 files, +132 / −4 (diff read by the seat). The organization-ownership drop gate's success line in apply.ts now names the row that let the drop through. A birth marker gets "this database was created from empty (TIME, platform VERSION) and carries the organization-ownership birth marker … the ceremony is not needed for it". A ceremony run keeps the old sentence. The new pure organizationOwnershipBirthOf in organization-ownership-drop-gate.ts reads details.birth from the flag the gate's one marker read already returned, through the spec's exported OrganizationOwnershipBirthMarkerSchema. There is no second DB read, and no new runtime or spec export. Inside claim 6368219256; Fixes #25466.
Discriminator No exported "birth vs ceremony" reader exists (the boot guard decides on shape generation). The ceremony's ledger write replaces details with { checkpoint }, so details.birth and a ceremony run are exclusive. Reading the spec-declared key with the spec's own schema is the existing contract, not a second discriminator. @objectstack/cli already depends on @objectstack/spec.
Measured Real CLI, SQLite, harness from #24638's run (report 6369659730). Before: the organization-ownership ceremony verified (2026-10-23T15:32:50.299Z). After: the birth-marker sentence with the creation time and platform 18.0.0. Both legs dropped the column, exit 0; --json documents equal apart from duration. Unit: 3 new cases through MigrateApply.run (birth arm, ceremony arm, --json for both); cli unit tier 326 files / 4808 tests. The ceremony arm is measured at unit level only.
CI round Head b58c1ed845 went red in Test Core (3/6). This PR's own new --json case compared the two runs' duration (0 vs 1 ms). Fixed in b3854a5fa4: duration is checked to be a number ≥ 0 in both, normalised, and the rest is compared in full. Nothing skipped.
Dogfood battery / contract review Not owed: no ! in the title, Clause-②: no with no narrowing arm.
Gates (check-runs on this head) 41 runs: 34 success, 7 skipped, 0 failed. mergeable_state: clean. Local: dispatch-gates 67 derived, 65 run exit 0, 2 NOT MEASURED locally (dual-build-cjs-loads, i18n-coverage, both need dists outside the closure), green in CI. Report: mcp_calls 0, writes all through the relay.
Changeset, read per sentence Before / Now / unchanged (--json, exit codes, which drops are allowed) match the hunk. Metadata-protocol: none holds. The docs-drift comment lists pages that name os migrate apply; none quotes the changed line (git grep "ceremony verified|drop may proceed" content/docs → 0).
Accepted deviations PR body opens with the dispatch's two attribution lines, Fixes #25466 on line 3.
Cross-lane touches None. packages/cli is this seat's paths.
Landing Not governed (check-governed-merges.mjs --pr 25500, +132 / −4). Ready + auto-merge through the relay in this act.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 17:14
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 17:14
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit fa06169 Oct 23, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-25466-birth-marker-text branch October 23, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants