Repository navigation
feat(cli): the organization-ownership ceremony carries the sys_metadata organization layer into the environment, and a choice deletes only the rows the reviewed plan listed (ADR-0131 D10, C7b S1) - #23452
Conversation
…customizations and carries form withdrawals; the form doors' organization read is deleted (ADR-0131 D10, C7b S1) WIP: tests not yet run. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
… the deleted read left (ADR-0131 D10, C7b S1) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…sen rows deleted after the chosen promotion is verified (ruling 6163114098 Q1 B, Q6 A; C7b S1) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
… lineages unique environment-wide before the column goes (seat answer Q2 A, C7b S1) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…the overlay key does (C7b S1) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
… cannot collide (C7b S1) The door numbers a save from history in its own scope, so the withdrawal carry, a chosen conflict's carry and the template promotion appended environment history at numbers history-renumber had just given an organization (measured on a seeded showcase database: event_seq 3 and (view, showcase_inquiry.contact, 3) each held twice after the run). - version-renumber, before the promotion: organization lineages laid out one above another, the environment's above them all, each overlay row's version with its own lineage. - sequence-renumber, just before column-drops: each organization's event range above the environment's, its commit ranges with it, as seat answer Q2 A reads; run on every apply, checkpoint or not. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…t a tracker number (C7b S1) A runtime string carries no tracker id (check:doc-authoring's prose-id leg). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
📓 Docs Drift CheckThis PR changes 1 package(s): 34 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 14 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c7c8314c695c4663ca011b588b14800b25dd975e && git checkout c7c8314c695c4663ca011b588b14800b25dd975e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 222293f60a7fff2779269b20bb4e5ce28a34c935 82bd7e0b803b57b01b8d66b880331d4b89e19e80 && git checkout -B drift-repro 222293f60a7fff2779269b20bb4e5ce28a34c935 && git merge --no-ff 82bd7e0b803b57b01b8d66b880331d4b89e19e80
node scripts/docs-audit/affected-docs.mjs --json 222293f60a7fff2779269b20bb4e5ce28a34c935
|
Contract reviewServed-tier: The head has not moved since the brief. Read: the net diff
① Derived judgmentsF1 · blocking: S1 makes the ceremony refuse on every 17.x database.
F2 · blocking: version lineages are not unique environment-wide after the run (Q2 A).
Why the pin and the dogfood missed it:
The PR body says "none of the ceremony's own environment saves … can land on a number an organization holds", and the changeset claims uniqueness. Both are false for versions. F3 · blocking: a conflict resolved to an organization row beside an environment row is neither fail-closed nor crash-safe (Q1 B).
The promotion (Q6 A, posture).
Conflicts (Q1 B).
The form-withdrawal carry. It runs under every posture, before the promotion, through the door, judged by
The history renumbering (Q2 A).
The deleted organization read on the form doors. No caller outside the tests is left, on
Tests.
Cross-stage: landing-order items (S4 is #23437, not landed). None is a FAIL for S1 by itself.
② Semver levelThe changeset grades
③ Boundary flagsFrom report 6165486835 (no open questions):
Unnamed deviations (the seat ratifies or rejects each):
Escalations:
Before the queue:
NOT MEASURED:
Seat adoption (2026-10-14T10:02Z). Adopted after an audit of the reviewer transcript: one Write, to this record only; 63 Bash calls, all reads (
Implemented-by: VERDICT: FAIL |
…versions unique by construction, one environment row per resolved key (C7b S1) - F1: the overlay-duplicates category groups by bundle_member only where the column exists (17.x databases never had it); the fixtures that hid this are back in the 17.x shape, with a pin. - F2: a promoted row takes its history with it (its organization's lineage of the item moved above the environment's numbers, then re-tagged), so every later environment save numbers above it; the late history-renumber step lays out every lineage still tagged to an organization before the column goes, and under single folds it. - F3: a chosen conflict ends with exactly one environment row defining the chosen body (the key's environment row takes it through the door, in place; with none, the chosen row is promoted in place), read back before one DELETE of the key's organization rows; a rerun accepts the same --metadata-choice, recorded in the checkpoint before the delete. - Seat ruling (Q6 A's text): under a wall a choice deletes nothing, and an unchosen conflict is reported, not blocking. - Seat ruling: the form doors keep their organization read; its deletion moves to S2. rest and metadata-protocol are back to main. - The changeset: cli minor, Clause-②: yes (widening). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…own range too (C7b S1) A 17.x database holds history's per-scope counters under unique indexes ((organization_id, event_seq), (organization_id, type, name, version)), checked row by row: an offset smaller than the lineage's own span landed a row on a sibling's number mid-statement (measured on a seeded showcase database). The test fixtures now carry those indexes. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
….0-next.1 shipped (C7b S1) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…b-promotions # Conflicts: # packages/cli/src/commands/migrate/organization-ownership.ts # packages/cli/src/utils/organization-ownership-apply.test.ts
… the reviewed plan listed (C7b S1, seat option A) One mechanism for both flags, on S4's ReviewedPlan: the plan file's metadataConflicts and templateConflicts are read into the reviewed plan, and before anything is written each chosen group must equal its apply-time group, or the apply refuses with plan-conflict-mismatch, naming the conflict, the rows added and the rows gone. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
… in place, not through the door (C7b S1) Measured on a seeded showcase database: the metadata door writes an environment customization of a packaged item into that package's slot, whatever the target, so a package-less environment row of the key never took the chosen body (the read-back caught it; nothing was deleted). The key's environment row now takes the body by one UPDATE, its version the chosen row's after the fold; a withdrawal the body keeps is still saved through the door, for its history and the package's slot. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…in place (C7b S1) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
Contract reviewServed-tier: A delta review against the FAIL record 6165835673 (head What was read:
No Check-runs on the head, all finished at 2026-10-14T16:03Z: 61 runs, 47 success, 12 skipped, 2 failure, none running.
① Derived judgmentsN1 · blocking: the history fold's
N2 · fix in this round: the in-place UPDATE skips what the door does to that same row (the dev's second dogfood fix,
F1 · resolved.
F2 · versions resolved; for
F3 · resolved and pinned.
The wall · pinned both ways.
The form doors' organization read: KEPT, byte-identical. No change under The binding (option A) · holds.
The merge with S4 · nothing dropped.
② Semver level
③ Boundary flagsThe dev's deviations, from the PR body:
Unnamed:
Kept from the baseline's non-blocking list: the conflict key ignores Corrections the PR needs:
Escalated, unchanged since the baseline: whether the No MongoDB path is owed (6171293009), and none is asked for. NOT MEASURED:
Before the queue:
Seat adoption (2026-10-14T16:04Z). Adopted after an audit of the reviewer transcript: its writes are this record and scratch files under the brief's directory; 73 Bash calls, all reads (
Implemented-by: VERDICT: FAIL |
…le history; the in-place write does what the door does; column-drops waits on verified uniqueness (C7b S1) - N1: each fold moves a lineage above the environment's numbers AND the same organization's other events and versions (17.x numbered event_seq per organization under a unique index); the base excludes the lineage itself, so a rerun moves nothing twice. Pinned from the review's smallest case, with a crash and a rerun. - N2: the in-place UPDATE asks the door's lock verdict first (the item read's editable; a locked item stays locked), keeps the row's stored _lock family, and restamps checksum with hashSpec. - The wall with an environment row (ruled as built): each organization row left is named in the report as remaining and blocking column-drops, with the remedy. - Uniqueness under a wall (ruled, on condition): history-renumber verifies event_seq and version uniqueness environment-wide on every apply, and column-drops runs only after that verification in the same apply. - validateMetadataChoices keys a conflict with its bundle member. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…ness gate and the remaining-row report (C7b S1) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
Contract reviewServed-tier: A scoped delta review against the FAIL record 6172666364 (head What was read:
Check-runs on the head, all finished by 2026-10-14T16:39Z: 47 runs, 39 success, 8 skipped, none failed, none running.
① Derived judgmentsN1 · resolved and pinned.
N2 ·
The wall report line · resolved and pinned.
The
The bundle member · resolved and pinned.
PR text · resolved.
Nothing the baseline judged right is broken:
② Semver level
③ Boundary flagsThe dev's deviations (6172990807):
Not named in the PR:
No MongoDB path is owed (6171293009), and none is asked for. NOT MEASURED:
Before the queue:
Seat adoption (2026-10-14T16:42Z). Adopted after an audit of the reviewer transcript: its writes are this record and scratch files under the brief's directory; 67 Bash calls, all reads (
Implemented-by: VERDICT: PASS |
…b-promotions # Conflicts: # packages/cli/src/utils/organization-ownership-apply.test.ts # packages/cli/src/utils/organization-ownership-plan.ts
…solution; the follow-up door save sends the row as written; history duplicates are named (C7b S1) - The door's lock verdict is asked once, before the history fold, the promotion and the in-place write: a refusal leaves nothing written. Pinned under a wall with a history table. - The door save after the in-place write sends the row as written, its stored lock family kept, so a rerun meets the same body. - history-renumber's refusal names the table, each item holding the duplicated number and the remedy; both messages pinned. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
Contract reviewServed-tier: A scoped review of the merge round after the PASS record 6173411667 (content at What was read:
Check-runs on the head, all finished by 2026-10-14T17:21Z: 47 runs, 39 success, 8 skipped, none failed, none running.
① Derived judgmentsThe merge · clean, both sides intact.
The lock check · before every write of the resolution.
The door save sends
The duplicate refusal · names everything owed, and is pinned.
The body's claim "a refused write leaves nothing written" is now true for a resolution.
Nothing the previous record judged right is broken. The fold base (N1), the checksum restamp, the stored ② Semver level
③ Boundary flagsThe dev's report (6173806262): it names no deviation and no open question.
Carried forward, unchanged, not re-judged:
No MongoDB path is owed (6171293009), and none is asked for. NOT MEASURED:
Before the queue:
Seat adoption (2026-10-14T17:23Z). Adopted after an audit of the reviewer transcript: its writes are this record and scratch files under the brief's directory; 55 Bash calls, all reads (
The optional findings do not block. None of them writes anything wrong: a failed lock lookup stops with nothing written. They are noted for the epic's follow-up, not built here. Implemented-by: VERDICT: PASS |
…issue-23429-metadata-family One conflict: packages/rest/src/meta-delete-drop-storage-other-rows.test.ts — main parameterised the seed (seedStoredRow(packageId)); S2 drops the retired organization_id key from it. Took main's shape without the key. Auto-merged files both sides touch (main = S1/S3/#23489, kept as merged): organization-ownership.ts keeps S2's template-lookup column guard (S2's own hunk); organization-ownership-plan.ts and -inventory.ts are main's (S1) byte-for-byte; protocol.ts keeps #23489's packagelessReadAddress beside S2's legacy-row filtering; rest-server.ts keeps S2's deletion of the form doors' organization read; generated translations and the engine-double ledger are regenerated after the merge. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ
Part of #15211 (stage S1 of C7b; the other stages are this card's S4, #23437, and the split cards #23429 and #23430)
Fixes #23398
Clause-②: yes (widening)
What
os migrate organization-ownership --applycarries thesys_metadataorganization layer into the environment (ADR-0131 D10; rulings A on #22011 and #22008; ruling 6163114098, Q1 B and Q6 A; seat answer Q2 A; review rulings in 6165835673).metadataConflicts, and--applygains--metadata-choice ROW_ID. That is the widening, so cli isminor. Nothing is removed. This PR changes only@objectstack/cli.metadata-promotion, after the authored conversion:Form withdrawals are carried first, under every posture, and fail-closed. Every view of that name the environment serves still open takes an organization's withdrawal, stored or code-served, in every package. It is saved through the metadata door into the view's own package, and any organization's withdrawal wins.
Under
single, an organization customization whose key no other row holds is promoted in place, with its history. This coversview,dashboard,report,translationandemail_template; the key is type, name, package and bundle member.A key held by more than one row is a conflict. Under
singleit blocks until--metadata-choice ROW_IDnames one row. The key then ends with exactly one environment row defining the chosen body:Any withdrawal another row of the key holds is kept on that body, and also saved through the door (its history, and the package's slot). The row is read back, the choice is recorded in the checkpoint, and only then are the key's organization rows deleted, in one statement, by id. A rerun after a crash anywhere in this accepts the same
--metadata-choiceand finishes.Under a wall: nothing is promoted unless named, and nothing is ever deleted (ruling 6163114098, Q6 A's text, applied by the seat). A conflict nobody chose for is reported, not blocking, and its rows hold the column.
event_seqand every(type, name, version)unique environment-wide. The door numbers a save from history in its own scope, so:history-renumber, runs just beforecolumn-drops, on every apply, even after a checkpoint. It lays out every lineage still tagged to an organization above the environment's: event ranges by one offset each, with their commit ranges; version lineages with the organization's rows.single, that history then becomes the environment's too.ReviewedPlan. The plan file'smetadataConflictsandtemplateConflictsare read into the reviewed plan. Before anything is written, each chosen conflict must hold exactly the rows the file listed for it, or the apply refuses withplan-conflict-mismatch, naming the conflict, the rows added and the rows gone.sys_metadata.bundle_memberfirst shipped in 18.0.0-next.0, and the ceremony boots with schema changes held. Soenvironment-overlay-duplicatesgroups bybundle_memberonly where the column exists; on a 17.x table the member reads as NULL, the overlay key's folded part.restandmetadata-protocolare back tomain.Merge round (review 6173411667 PASS at
d02b699d65; head82bd7e0b80, base:main222293f60, S3 merged)main, now carrying S3 (feat(plugin-security,cli,spec)!: retire sys_user_permission_set.permission_set_id; the grant key is unique per organization; the boot grant-name backfill is deleted (C7b S3) #23503), is merged throughos-regen-merge.sh.organization-ownership-plan.ts:TablePlangains bothmetadataConflictsand S3'sgrants, and both types are kept;organization-ownership-apply.test.ts: both sides' new describes are kept.runColumnDrops. The gate pin and the two column-drop doubles now use S3's{ table, column }port shape.the lock is asked before the history fold and the promotion: under a wall, a refusal leaves history and rows as they were, with a history table andisolated.the door save after the in-place write sends the row as written, its stored lock family kept.bodyreddens it.history-renumberkeeps blocking on a duplicate, by the seat's call. Its refusal names the table, each item holding the duplicated number (by row) and the remedy. Both messages are pinned: theevent_seqone verbatim, the version one by its parts.82bd7e0b80:.tsfiles: 0 errors, 0 warnings;mainmerges clean again.Delta round 2 (review 6172666364; head
d02b699d65)The seat's rulings, each fixed and pinned, each ablation red with the restore verified (blob == HEAD):
event_seqand versions) is based onmax(environment, the same organization's other items); excluding the lineage itself keeps a rerun idempotentN1: folding one item of an organization never lands on that organization's other events, on a rerun too(the review's smallest case, 17.x's unique indexes, a crash after the first fold's move, then the rerun)checksumwithhashSpecrestamps checksum from the written body_lockkeys_lockfamily is kept, whatever the chosen body carrieskeeps the lock family the environment row storeseditable(the save door's own verdict) before the write; a locked item blocks the conflict, nothing written or deleteda locked item stays lockedhistory-renumberverifiesevent_seqand(type, name, version)uniqueness environment-wide on every apply and blocks naming any duplicate;column-dropsrefuses unless that verification passed in the same applycolumn-drops never runs unless this apply's history-renumber verified uniqueness(two environment events sharing a number)validateMetadataChoicestwo conflicts of one item that differ only by bundle member are two conflicts, each chosen for917e0b8ccChecks on
d02b699d65:organization-ownership*, 8 files): 135/135. Narrowed lint of the 8 changed.tsfiles: 0 errors, 0 warnings.git merge-tree), so it is not merged.468b74a09d, CI's Test Core passed (all 6 shards). I stopped the local sweep there for this round: 58 of 65 gates had run, all exit 0.Verification of the previous round (head
468b74a09d, base:mainf9b421716d, S4 merged)Merge.
main, carrying S4, is merged throughscripts/pm/os-regen-merge.sh.main'sserved-catalogtest gains theviewsport, which serves no view.The binding (seat option A):
--metadata-choice: a row that joined the conflict since the review refuses the apply, naming it; nothing is written;a reviewed row gone since refuses too, and a plan file that lists no such conflict; and the unchanged group resolves--template-choice: a row that joined the conflict since the review refuses the apply, naming it; nothing is written; and the unchanged group resolvesreviewedPlanFromFilereads both lists, keyed and sortedThe review round (6165835673) keeps its pins and ablations:
plans a 17.x sys_metadata with no bundle_member column…singlefold removed → the two-organization pin; seq offset not wholly above → the organization-history pina write that does not land deletes nothingChecks:
organization-ownership*, 8 files): 129/129. Narrowed lint of the 8 changed.tsfiles: 0 errors, 0 warnings.dispatch-gates: the local sweep on that head was stopped at 58 of 65, all exit 0, for the next round. The interim headb0093a3337ran 65/65 at exit 0.osbinary built from this head. Oneobjectstack dev --seed-adminboot of the showcase, then stopped. I made the database a 17.x shape (bundle_memberdropped; history keeps 17.x's unique(organization_id, event_seq)and(organization_id, type, name, version)indexes), and planted a conflict onshowcase_inquiry.contact's package-less key: the organization's withdrawn copy (with only an organization history, 1–2) beside an open environment copy.env_copy,legacy_org_withdrawal).--apply --plan-file plan.json --metadata-choice legacy_org_withdrawalexits 1,plan-conflict-mismatch: reviewedenv_copy,legacy_org_withdrawal, current withjoined_later. The database file is byte-identical, with no ceremony row.env_copytook the chosen body in place, withdrawal kept; its version is 4, the foldedcorg2snapshot. The organization row is deleted, and the package's served copy carries the withdrawal. After therewriteremedy, the resume ranhistory-renumber: no history is left tagged to an organization, and no duplicate exists.column-dropsblocks onsys_metadata's declared column, which is S2's.event_seqandversion5, above everything, with no duplicate.event_seqoffset was smaller than a lineage's own span. 17.x's unique index refused it row by row. Fixed in917e0b8cc, and its base corrected in this round's N1.0162d47cd.Acceptance notes
single, when an organization row is chosen over the key's environment row, the environment row keeps its record and takes the chosen body in place, by one UPDATE, doing to the row what the door does (N2). Its version is the chosen row's after its lineage is folded. The chosen organization row is then deleted with the key's other organization rows; its body survives in the environment row. This is the "replace in place" the ruling offered: it keeps one environment row at every point, and any other order leaves zero or two.single, the history left tagged to an organization is folded by the late step. History rows keep their bodies and their order; onlyorganization_idand the numbers change.--applyshipped in@objectstack/cli@18.0.0-next.1. This PR adds to it (a plan key, a flag, two steps), henceminorandClause-②: yes (widening). It refuses no invocation that release could finish. A conflict now blocks the new step until it is chosen, but on such a database18.0.0-next.1's--applyalready stopped atcolumn-drops, which never dropssys_metadata's still-declared column. The changeset says so.--template-choicegap S4 left (6167752569). The changeset's example carries--plan-file.claude/issue-15211-c7b-promotions).state;singlefor a key an organization row holds. With the doors' organization read kept, no form is open meanwhile.rewriteblocks on the--seed-admingrantsadmin_full_accessandorganization_admin_no_bypass(6163356782; escalated in 6165835673). The dogfood removed them as the blocker's remedy says.🤖 Generated with Claude Code
https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH