Repository navigation
cli: os migrate apply --allow-destructive drops organization_id from a host-declared platform-global object before the organization-ownership ceremony has verified, because the drop gate reads only the platform inventory #23684
Description
Filing gate ①, class (b). reach: measured at a public door by #23666's dev, on PR #23681's fixture at 06761db20: os migrate apply --database-url file:v17.db --allow-destructive --yes --json exited 0. It applied drop_column organization_id on sys_osgc_ledger and sys_osgc_held, two objects the host declares platform-global, with no organization-ownership marker on the database (os-dev-report 6194335616 on #23666, out-of-scope finding 1).
Filed by domain:cli seat 1 (#6024) · os-bill · session session_01FLwgbTSmGMrqsXkBDsiwfG, from that report. ⛔ Not a claim. Grade and lane are triage's.
Governing text
- The gate's own contract,
packages/cli/src/utils/organization-ownership-drop-gate.ts(header,origin/main): "So the drop oforganization_idon any table in the ceremony's inventory waits for the ceremony's verified marker … ⛔ No flag stands it down." The header names the harm as ADR-0131 D10 item 5's: "Applied ahead of the ceremony, that drop skips its promotion and report steps … the escape hatch D10 item 5 forbids." - ADR-0131 D10 (
docs/adr/0131-total-organization-ownership-no-null-organization-id.md:540): the ceremony is "Driven by the inventory the two censuses seed (59 platform + 28 example objects; cloud's supplement for cloud-declared ones)".
The gap
CEREMONY_TABLESis built fromORGANIZATION_OWNERSHIP_INVENTORYonly (organization-ownership-drop-gate.ts:29).ceremonyOrganizationColumnDrops(:37, called frompackages/cli/src/commands/migrate/apply.ts:495) therefore holds back no drop on an object a host declares platform-global (ADR-0131 D7) or names in its fate supplement.- It is pre-existing for a host whose engine is the CLI's own: that shape's drift already proposed the drop.
- PR fix(cli): os migrate organization-ownership --apply drops organization_id from an object the host declares platform-global (ADR-0131 D7) #23681 (cli: organization-ownership --apply column-drops refuses a host-declared platform-global object (ADR-0131 D7) whose fate is column-drop — "not proposed for dropping by the schema-drift ceremony" #23666) extends it to a host that brings its own engine plugin. That is the control-plane shape
schema-migrate.host-composition.integration.test.tsreproduces. That PR makes the ceremony's owncolumn-dropfate work for such a host, and with it, the drift now offers the same drop toos migrate apply --allow-destructive. - The drop is listed in the plan before it applies and needs the explicit flag. The gate's contract is that no flag stands it down.
Reader who acts
Triage grades it and routes it. The landing is packages/cli (domain:cli): the drop gate's table set. It should cover what the ceremony covers for this deployment (the platform inventory, the deployment's platform-global declaration, and the host's fate supplement). The design of that read is the claimant's.
- Seam:
spec:OrganizationOwnershipSupplementSchema→runtime:packages/cli/src/commands/migrate/apply.tsceremonyOrganizationColumnDrops. - Pin:
os migrate apply --allow-destructiveon a v17 file with a host-declared platform-global object refuses itsorganization_iddrop until the marker is verified. CONTROL: a non-organization_iddestructive change still applies.
Dedupe
MCP search_issues on objectstack-ai/objectstack, closed included:
os migrate apply allow-destructive drops organization_id on host-declared platform-global object before the organization-ownership ceremony marker drop gate: 37 hits, top 8 read. The nearest are cli: organization-ownership --apply column-drops refuses a host-declared platform-global object (ADR-0131 D7) whose fate is column-drop — "not proposed for dropping by the schema-drift ceremony" #23666 (open; this finding's parent, the ceremony's own drop) and organization-ownership column-drops never completes where no platform-global objects are declared: organization_id is still injected on the sys_metadata family and sys_flow_dispatch, so the drop is never proposed #23399 (closed; the platform-inventory drop never proposed). None is theapplygate's table set.organization-ownership drop gate CEREMONY_TABLES inventory host supplement ceremonyOrganizationColumnDrops: 4 hits, none this gap.
Dedupe words: allow-destructive organization_id host supplement; ceremonyOrganizationColumnDrops inventory; drop gate host-declared platform-global; organization-ownership-drop-gate CEREMONY_TABLES.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 15, 2026
Triage: first grade ·
bug·priority:p1·target:v18·domain:cli·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-15T14:58ZRead fresh.
os migrate apply --allow-destructiveapplieddrop_column organization_idon two host-declared platform-global objects (ADR-0131 D7) with no organization-ownership marker on the database. Measured by #23666's dev on PR #23681's fixture.organization-ownership-drop-gate.tsbuildsCEREMONY_TABLESfrom the platform inventory only (:29), so a host-declared or fate-supplement object is not held back.Grade p1 +
target:v18: a destructive step on v18's upgrade path that breaks its own gate. Applied ahead of the ceremony, the drop skips the promotion and report steps ADR-0131 D10 item 5 forbids skipping. 「发版本优先的都 p1」.Lane
domain:cli(packages/cli).Order: PR #23681 (#23666, open) widens this gap to hosts that bring their own engine plugin, and both touch the same ceremony family.
Scope:
apply --allow-destructiveholds back the drop on a host-declared platform-global object until the verified marker exists;findingleaves on grading.objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 20 (#23684:
os migrate apply --allow-destructiveholds back anorganization_iddrop on every table the organization-ownership ceremony owns for this deployment, not only the platform inventory, until the ceremony's verified marker exists) · 2026-10-15T15:05ZSession:
session_01Q9PZ5Pzqcus4woVvTZwWNUAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-23684-drop-gate-host-tablesWorktree:
objectstack-issue-23684Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)Scope:
batch3, by 「并发恢复3」. #23050 slice 4 is in flight, and this p1 takes a free slot ahead of the p2 #23686 (which orders after PR #23682).Order, read at this act:
6195145804orders this fix first, or in the same window as PR fix(cli): os migrate organization-ownership --apply drops organization_id from an object the host declares platform-global (ADR-0131 D7) #23681 (cli: organization-ownership --apply column-drops refuses a host-declared platform-global object (ADR-0131 D7) whose fate is column-drop — "not proposed for dropping by the schema-drift ceremony" #23666).main(eea76497d). That is why this card is taken now.File surface (read on
origin/maineea76497d; stop on a breach and explain it in the report):packages/cli/src/utils/organization-ownership-drop-gate.ts:CEREMONY_TABLES(:29) andceremonyOrganizationColumnDrops(:32). The table set becomes what the ceremony owns for this deployment, or a rule the dev measures to be equivalent and safer.packages/cli/src/commands/migrate/apply.ts: the gate's call site only (:495-:510), to hand it what the booted stack declares.organization-ownership-plan.ts,organization-ownership-inventory.ts,schema-migration-plugins.tsas PR fix(cli): os migrate organization-ownership --apply drops organization_id from an object the host declares platform-global (ADR-0131 D7) #23681 left it). ⛔ No second copy of that composition.packages/cli/src/, on PR fix(cli): os migrate organization-ownership --apply drops organization_id from an object the host declares platform-global (ADR-0131 D7) #23681's host-composition fixture where it fits..changeset/23684-*.md.packages/spec(OrganizationOwnershipSupplementSchemais read, not changed);os migrate applyflag without returningneeds_decisionfirst;sys_account.issuer,sys_comment.reactions).Container & model:
M(one gate's table set, its call site, and pins on a host-composed v17 fixture),mode:subagent,model: default tier. Ceiling:Clause-②: no (narrowing), as os migrate organization-ownership has no SQLite occupancy gate: run beside a process holding the file open, a later write by that process was lost (WAL and SHM deleted under it) #23400's ceremony gate shipped: a refusal the gate's own header ("⛔ No flag stands it down") and ADR-0131 D10 item 5 already state.Clause-②: no (narrowing)
Responsibility:
os migrate apply's organization-ownership drop gate (this lane) | the ceremony's inventory and the host's platform-global declarations (packages/cli, ADR-0131 D7/D10), read | every operator upgrading a v17 database whose host declares platform-global objectsThread-read: 6195145804
Serial constraints cleared:
packages/cli/src/utils/organization-ownership*,commands/migrate/orschema-migrat*(read at this act).Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 23684,
"status": "done",
"branch": "claude/issue-23684-drop-gate-host-tables",
"pr": "#23699",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (the dispatch's session; subagent = parent's)",
"premise_still_valid": true,
"summary": "The drop gate (packages/cli/src/utils/organization-ownership-drop-gate.ts) now holds back every organization_id drop the schema-drift report offers until the ceremony's marker is verified, not only the platform inventory's tables. CEREMONY_TABLES and the inventory import are gone; the header states the table set and why. The ceremony's plan meets every physical table that carries organization_id, and the drift can offer only a column that is physically there, so this is the ceremony's own table set with no second copy of its composition and no fate-supplement read. apply.ts is unchanged: its call site and the existing refusal (text, --json envelope refused_organization_ownership_unmigrated, exit 1, whole-run hold) are reused as they are. No new flag, nothing in packages/spec. The three card pins extend PR #23681's fixture file in place (red on base, green on the fix, ablation red on pin 1 only).",
"hypotheses": {
"a": "HOLDS (measured, primary database). organization-ownership-plan.ts (table loop from :1146 on c9810dd) plans every physical base table carrying organization_id: an inventory row, a --fate-supplement row, APPLICATION_OBJECT_FATE for any other table, or the uninventoried-platform-table refusal; a table without the column is counted outsideCeremony and skipped. The drift proposes drop_column only for a physically present column, so every proposed organization_id drop is on a table the ceremony plans; no v18 drift proposes one on a table the ceremony does not own (app objects take the application-object fate; cloud objects are inventory placeholder rows or supplement rows). The ceremony drops columns only through the drift (organization-ownership-column-drops.ts:38 keeps the drift's own drop_column entries), so every organization_id drop the ceremony could perform is held. The v18 boot refusal (packages/runtime/src/organization-ownership-boot-guard.ts:139 findV17ShapeTables) judges the same population: every registered object with systemFields.tenant false and no declared organization_id whose table carries the column, not the inventory. Stricter than the inventory and declarations in two places: an application object authoring systemFields tenant false over a table still carrying the column (the ceremony plans it, and the boot refusal refuses over it too), and the telemetry sibling datasource, which apply reconciles and the ceremony plan does not read. Corpus on c9810dd: the tenant opt-out pattern has 0 hits in examples//*.ts (26 object files); control, the same pattern over packages//*.object.ts: 14 files.",
"b": "NOT BUILT. The composed set (inventory plus the org-scoping declaration read at the call site) does not cover the fate supplement: it is a ceremony flag apply never sees, and a supplement object that opts out by its own metadata (not the deployment's declaration) has no other reading at apply, so covering it would need a new apply flag. Rule (a) covers it with no flag: the drift offers its drop exactly when the ceremony could drop it. So no needs_decision.",
"c": "USED. Pins extend packages/cli/src/utils/organization-ownership-platform-global-column-drop.integration.test.ts in place (a second describe), reusing its host composition (the host's lazy engine, an org-scoping provider declaring sys_osgc_ledger and sys_osgc_held platform-global, three host objects) and its v17 template database. The real os migrate apply --allow-destructive --yes --json, three cases."
},
"before_after": {
"rig": "the fixture above; base c9810dd = before (fix absent, pin present at 90c79b5), cf15fee = after (head 9b3d9d8 adds only the changeset)",
"rows": [
{
"column": "sys_osgc_ledger.organization_id (host-declared platform-global)",
"marker": "absent",
"before": "applied, exit 0",
"after": "held, exit 1, refused_organization_ownership_unmigrated"
},
{
"column": "sys_osgc_held.organization_id (host-declared platform-global)",
"marker": "absent",
"before": "applied, exit 0",
"after": "held (same refusal, tables [sys_osgc_held, sys_osgc_ledger])"
},
{
"column": "both declared objects",
"marker": "verified",
"before": "applied",
"after": "applied"
},
{
"column": "sys_osgc_audit.organization_id (not declared)",
"marker": "any",
"before": "not proposed, kept",
"after": "not proposed, kept"
},
{
"column": "sys_osgc_ledger.legacy_23684_note (another column, no organization_id drop in the run)",
"marker": "absent",
"before": "applied",
"after": "applied"
},
{
"column": "sys_platform_setting.organization_id (platform inventory, existing pins)",
"marker": "absent or unverified",
"before": "held",
"after": "held"
},
{
"column": "sys_platform_setting.organization_id",
"marker": "verified",
"before": "applied",
"after": "applied"
}
]
},
"tests": "Red leg, base c9810dd plus the pin (fix absent): pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 on the fixture file -t 'os migrate apply' gave 'Tests 1 failed | 2 passed | 3 skipped (6)', AssertionError: {"appliedOrganizationDrops":["sys_osgc_held","sys_osgc_ledger"]}: expected +0 to be 1 (controls green). Green leg on cf15fee: the fixture file plus src/commands/migrate/apply-organization-ownership-gate.integration.test.ts gave 'Test Files 2 passed (2) / Tests 11 passed (11)'. Ablation on cf15fee via node scripts/ablation-replace.mjs (WRAP mode, plus an own trap on EXIT INT TERM restoring with git checkout HEAD on the absolute path) putting the table set back to the platform inventory only: anchor x1 to x0, replacement x0 to x1, blob 8d8ebfc14096 to bea1fc2829b0, marker grep -c 0/1/0 (before/during/after); result 'Tests 1 failed | 7 passed | 3 skipped (11)', pin 1 red with the same message, the 2 new controls and the 5 existing gate cases green; restored blob == HEAD 8d8ebfc14096, git diff HEAD empty. No dist step: the gate is reached through relative src imports (test, ../commands/migrate/apply.js, ../../utils/organization-ownership-drop-gate.js). Typecheck: pnpm --filter @objectstack/cli typecheck exit 0 (tsc --noEmit over src, which holds the pin; check:test-typecheck OK). Unit tier: --project unit 290 passed + 2 PREREQUISITE ('packages/cli is not built'); after the CLI build, published-subpath-console.pin and published-subpath-hook-body.pin passed (29 tests): 292/292 files, 4325 passed + 29 skipped. Integration tier: only the two files above locally, the rest declared to CI. Head 9b3d9d8 differs from cf15fee only by .changeset/23684-drop-gate-host-tables.md (git diff --stat: 1 file, 21 insertions). Dialect: SQLite only.",
"gates": "Local, exit codes captured before any pipe. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 64 commands at 9b3d9d8; one pass on that head over the built workspace: all 64 exit 0; --ran: 'Run reconciliation — 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN'. An earlier pass on cf15fee (53 derived, pre-changeset) had check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity at exit 3 (PREREQUISITE NOT MET, workspace dist absent), superseded. pnpm lint (eslint . --no-inline-config) on 9b3d9d8: exit 0, no output; control eslint --format json over the two changed .ts files: 2 files, 0 errors, 0 warnings. check:dev-prereqs and check:skill-frame-freshness not run (no packages/spec or frame file touched). CI on PR head 9b3d9d8 at report time: 23 check-runs, 21 in_progress, 1 success, 1 skipped; required 'Governed Surface Queue Guard' in_progress; 'Lint & Repo Gates', 'TypeScript Type Check', 'Test Core', 'Build Core' not yet listed. in_progress, not a pass.",
"mcp_calls": "0",
"api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/23699/assignees [sunhaolin], read back 10167/10167 bytes identical; (2) comment = POST /repos//issues/23684/comments (this report). Plus git push (not REST). No label-write (none named; the changeset publishes, so no skip-changeset), no PR-body PATCH, no claim, no card-assignee write.",
"files_changed": [
".changeset/23684-drop-gate-host-tables.md",
"packages/cli/src/utils/organization-ownership-drop-gate.ts",
"packages/cli/src/utils/organization-ownership-platform-global-column-drop.integration.test.ts"
],
"deviations": [
"packages/cli/src/commands/migrate/apply.ts not touched: the claim allowed its call site, but the gate's signature and the refusal are reused unchanged, so no edit was needed.",
"The pins extend PR #23681's fixture file (a second describe) instead of a new file, to reuse its v17 template boot.",
"Tests ran on cf15fee; the final head 9b3d9d8 adds only the changeset file. The gate union was re-run in full on 9b3d9d8.",
"/verify (the objectui metadata-admin designer-preview browser check) not run before commits: the diff touches no objectui or designer surface.",
"Commits carry the model-free trailer pair (AGENTS.md and the role file), not the harness reminder's model-named Co-Authored-By; the PR footer is the role file's session-URL form, not the reminder's form."
],
"open_questions": [
{
"question": "The pending changeset .changeset/23429-v18-boot-refusal.md (same release, not yet versioned: pre.json lists no consumed changesets) says the gate refuses 'any table in the ceremony's inventory (the D7 tables and the sys_metadata family)' and 'Only an os migrate apply --allow-destructive run that would drop an inventory table's organization_id is refused'. After this PR the second sentence is false. It is outside this card's file surface, so it was not edited. Amend it here or elsewhere?",
"options": [
"A: a patch round on PR #23699 that widens the file surface by that one changeset and rewrites its two inventory-scoped phrases to the scope this PR ships (every organization_id drop the drift offers)",
"B: leave it for the release-time compile, with this changeset's FROM/TO superseding it",
"C: a separate docs-only PR before the release"
],
"recommendation": "A, because both changesets compile into the same release notes, the fix is two phrases in one file, and a patch round on this PR keeps the contradicting sentence from ever being published; B relies on a release editor noticing the contradiction."
}
],
"out_of_scope_findings": [
"class: b · reach: exception: release-text (the pending .changeset/23429-v18-boot-refusal.md compiles into the next release's CHANGELOG) · evidence: its sentence 'Only an os migrate apply --allow-destructive run that would drop an inventory table's organization_id is refused' becomes false once PR #23699 lands (a non-inventory organization_id drop is refused too; pinned on sys_osgc_ledger and sys_osgc_held). Seam: spec: none → runtime: packages/cli/src/utils/organization-ownership-drop-gate.ts ceremonyOrganizationColumnDrops | consumer: none · see open_questions[0] for the amendment · dedupe words: 23429 changeset inventory table only; v18-boot-refusal changeset drop gate scope; organization_id drop gate release note",
"carrier: none · noted, not filed (PR Acceptance notes): content/docs/deployment/cli.mdx :1000-:1006 says the organization_id refusal covers 'any table the organization-ownership ceremony covers' (still exact) and then 'These tables are the ADR-0131 D7 tables and the sys_metadata family', which names the platform's set only; outside this card's file surface",
"carrier: none · noted, not filed (PR Acceptance notes): the existing refusal sentence at packages/cli/src/commands/migrate/apply.ts:515 reads '..., which the organization-ownership ceremony owns that drop, and it has not verified ...' (a grammar slip in a runtime string), reused unchanged as the ruling asked"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
ACCEPT → PR #23699 comment
6197474781·TIER_DEFAULTrecord6197462406·domain:cliseat 2 · 2026-10-15T16:51ZGenerated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed: PR #23699 →
6074eafb63· 2026-10-15T17:19Zdomain:cliseat 2 (#22648) ·sunhaolin· sessionsession_01Q9PZ5Pzqcus4woVvTZwWNU· holder of claim6195305019, released by this landing. Review: this seat'sTIER_DEFAULTrecord6197462406(PASS) and ACCEPT6197474781on the PR (pointer on this card).merged: true,merged_at2026-10-15T17:18Z, through the merge queue (added_to_merge_queue2026-10-15T16:55Z)gh-readonly-queue/main/pr-23699-*git ls-remote, 0 refs)origin/main6074eafb63is an ancestor; one parent (30c33e3875); 3 files, +193 / −10, as reviewed at9b3d9d8dafFixes #23684onlydomain:clicards (read at this stamp), #23684 no longer among themState change: #23684 closed
completedby the merge;pm:dispatchedremoved in this act.os migrate apply --allow-destructivenow holds back everyorganization_iddrop the drift offers until the organization-ownership ceremony's marker is verified, the population the v18 boot guard refuses over.Release text:
.changeset/23429-v18-boot-refusal.md's "inventory only" sentences are false from this commit. They are amended in place by #23703 (row 3, unblocked by this landing), and that PR also aligns this PR's own changeset FROM if the two compile into one release.Generated by Claude Code