Repository navigation
feat(cli): os migrate organization-ownership --apply executes only the reviewed plan file (ADR-0131 D10, C7b S4) - #23437
Conversation
…, C7b S4) --apply requires --plan-file and refuses, before any write, when the inventory digest it computes or the database differs from the reviewed plan's; a checkpoint under another digest is superseded and reported. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…n is the seat's call (C7b S4) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
📓 Docs Drift CheckThis PR changes 2 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 148 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 4ff2526dd03ba3ff39ca2465aa75579edb894914 && git checkout 4ff2526dd03ba3ff39ca2465aa75579edb894914
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 394f65e891f0ac6c6be708fb807740d5cb446520 d7ef4bd6d72fa79e16feb86a0417b56a5a8bda1c && git checkout -B drift-repro 394f65e891f0ac6c6be708fb807740d5cb446520 && git merge --no-ff d7ef4bd6d72fa79e16feb86a0417b56a5a8bda1c
node scripts/docs-audit/affected-docs.mjs --json 394f65e891f0ac6c6be708fb807740d5cb446520
|
…e (seat answer Q-S4 A, C7b S4) `--apply` now refuses without the reviewed plan file, so the example the compiled CHANGELOG prints names it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
Contract reviewServed-tier: Read: the net diff ① Derived judgmentsF1 (must fix): a test on
At this head, CI agrees. Remedy: give both cases a plan-shaped F2 (must fix, or the seat rules the narrower claim): the posture changes what the apply does, and it is neither in the digest nor compared.
Other inputs outside the digest:
Unreviewed plans: what is refused, and when.
Checkpoint under another digest. A checkpoint is reused only when its digest equals the current digest, and after the refusal the current digest is the reviewed one. A checkpoint under another digest yields The refusal. It names the file and both digests (or both database labels), says that nothing was written, and gives the remedy (plan again with the same flags, review, pass the new file). Reason codes are stable, the Do the tests fail when the comparison is removed?
What on
② Semver level
③ Boundary flags
Seat adoption (2026-10-14T09:39Z). Adopted after an audit of the reviewer transcript: one Write, to this record only; 70 Bash calls, all reads (
Implemented-by: VERDICT: FAIL |
…ture; main's --apply callers hand in a plan file (C7b S4 review) - F2: the plan file's recorded posture is read and compared; plan-posture-mismatch names both postures and the remedy. - F1: the fate-supplement integration cases pass a plan file. - F3: the one-shot family's --apply write mode runs the plan first and hands its file in, so the seed-write and key-material pins reach the apply's boot again; a mode declaring preceding runs must reach its own write. - F4: #23280's pending note's example carries --plan-file (seat answer Q-S4 A, extended); #22937's note and the module doc say the plan file comes before the backup question. - The command's mapping of a post-boot plan refusal is pinned. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
Contract reviewServed-tier: Delta review against the FAIL record 6165431301 (head ① Derived judgmentsF1, as ruled: fixed. In F5 (must fix):
Nothing else invokes this command's
Remedy: merge F2: fixed.
F3: fixed.
F4 and the riders, as worded: correct. As a remedy: overtaken (see ②).
Carried to S1 (#23452), not a FAIL here:
② Semver levelF6 (must fix, seat ruling): the premise behind
What follows:
F4 re-opened by F6.
③ Boundary flags
Seat adoption (2026-10-14T12:06Z). Adopted after an audit of the reviewer transcript: one Write, to this record only; 60 Bash calls, all reads (
Implemented-by: VERDICT: FAIL |
… are (seat ruling, C7b S4 delta review) Both notes shipped in 18.0.0-next.1; this PR's own note states the refused invocation and its replacement instead. Q-S4 A is withdrawn. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…e note is minor with its BREAKING banner (C7b S4 delta review) - F5: organization-ownership-not-declared runs the plan first and passes its file; organization-ownership-apply.served-catalog passes the reviewedPlan its run computes. - F6: --apply shipped in 18.0.0-next.1, so refusing its bare invocation is a narrowing and --plan-file a widening: minor, Clause-②: yes (widening), a BREAKING banner with the refused invocations and their replacement, and the ADR-0087 disposition. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…edger (C7b S4 delta review) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
Contract reviewServed-tier: Third-round delta review against the FAIL record 6167948246 (head Unchanged since the baseline, not re-judged. Of the old net diff's code and tests, six files carry the same added and removed lines against their new base: the command, ① Derived judgmentsF5: fixed.
F6: fixed.
F4: fixed. The rider: met.
The merge breaks no earlier "right" finding.
The refusal order is still: ② Semver level
③ Boundary flags
Seat adoption (2026-10-14T14:35Z). Adopted after an audit of the reviewer transcript: its writes are this record and scratch files under the brief's directory; 71 Bash calls, all reads (
The three ③ nits do not block; the PR title's missing Implemented-by: VERDICT: PASS |
Part of #15211 (stage S4 of C7b; the other stages are this card's S1 and the split cards #23429 and #23430)
Clause-②: yes (widening)
What
os migrate organization-ownership --applyexecutes only the plan the operator reviewed (ADR-0131 D10). It compares the reviewed plan's inventory digest, tenancy posture and database. Which rows a--template-choice(or S1's--metadata-choice) deletes is not yet bound to the plan file: #23452 carries that binding, on top of this PR once it is onmain.BREAKING:
--applyshipped in@objectstack/cli@18.0.0-next.1, and its bare invocation--apply --backup-taken(with or without--fate-supplement) is now refused,plan-file-required. The replacement is a plan run, then--apply --backup-taken --plan-file plan.json. The changeset carries the banner, the FROM → TO table, and an ADR-0087 ledger entry (cli-organization-ownership-apply-plan-file-required).--applyrequires--plan-file FILE, the plan file a plan run wrote. Before it writes anything, it refuses:plan-digest-mismatchwhen the inventory digest it computes differs from the file's, naming both digests and the remedy (plan again with the same flags, review, pass the new file);plan-posture-mismatchwhen the run resolves another tenancy posture (OS_TENANCY_POSTURE/OS_MULTI_ORG_ENABLED) than the one the plan was computed under, naming both. The posture decides which rows attribution gives an owner and which it reports, and whether authored sets and positions are converted;plan-database-mismatchwhen the file names another database.plan-file-required(no flag) andplan-file-unusable(the file is not a plan, lacks its digest, posture or database, or cannot be read).restartedFrom, besidereviewedPlan.Why
Measured by spec seat 3 (6158876765). A plan made with fate supplement A could be applied with supplement B, which ran B's fates unreviewed, including a fate-2 deletion A never listed. The plan run writes no row (it is read-only), so the only place a plan can record its digest is the plan file. The apply never read that file.
Verification (head
d7ef4bd6d7, base:maincb9841aa25, merged)Delta round (review 6167948246, seat rulings):
mainis merged; it did not conflict, and GitHub reported the PR dirty. The two testsmainadded get a reviewed plan.organization-ownership-not-declared.integration.test.tsruns the plan first and passes its file.organization-ownership-apply.served-catalog.integration.test.tspasses thereviewedPlanits run computes.minorwithClause-②: yes (widening), a BREAKING banner, a FROM → TO table and the ADR-0087 disposition.not-required (no-migration-prescription)beside a FROM → TO prescription, so the bare invocation's retirement is registered as a D3 semantic entry,18.cli-organization-ownership-apply-plan-file-required;@objectstack/specgetspatch.minor, and the narrowing is carried by the banner and the disposition, not the level.mainno longer touches.changeset/pre/.Earlier rounds (review 6165431301; the code is unchanged since): F1 (fate-supplement plan files), F2 (
plan-posture-mismatch), F3 (the one-shot family reaches the apply's boot), and the pinned post-boot refusal mapping.Checks, on the merged head:
The whole cli integration tier,
vitest run --project integrationin 4 shards under the verify lock: 1,111 passed, 0 failed, 2 skipped. Both skips are in files this PR does not touch:preview-read-only.integration.test.ts(a cell that needsOS_TEST_POSTGRES_URL) andtest/migrate-meta-default-range.test.ts(a skip in the source).The two new tests and the ceremony suites, run after the cli's dependency closure was rebuilt on the merged tree: 8 files, 89/89. Before that rebuild,
served-catalogfailed 3 cases on dists older than fix(cli,plugin-security): os migrate organization-ownership --apply resolves the built-in positions and the everyone baseline #23433, which the rebuild fixed.cli typecheck: exit 0. spec
src/migrations: 209/209, with the registry regenerated (it is generated at install and untracked).check:upgrade-guideandcheck:spec-changes: exit 0.dispatch-gates --ran: 91 derived, 91 run, all exit 0, 0 NOT MEASURED. That includescheck-adr-0087-registrationandcheck-empty-changeset, which is green again now that the consumed notes are untouched.Narrowed lint of the 9 changed
.tsfiles: 0 errors, 0 warnings.Reverse verification (
ablation-replace, each restore verified blob == HEAD; earlier rounds, code unchanged since):ReviewedPlanRefusalmapping removedrefuseUnreviewedPlana no-opDogfood (first round,
bc3525a9cf), with the realosbinary on a seeded showcase SQLite database: no plan file is refused; a tampered digest is refused, naming both, with no ceremony row written; the reviewed file runs.Acceptance notes
--plan-file, not the stage plan's--plan FILE. The ADR already names the bare read-only mode--plan.os migrate organization-ownership --applyand E5's pure row→definition mappers in core (drafts from main; the --apply PR lands after #15204 stage 8) #22937 and v18 upgrade ceremony refuses every cloud database: the inventory gives cloud-provided tables no fate on the premise that cloud stays on v17 — a host needs to declare fates for its own objects #23280 are left as they are. Both shipped in 18.0.0-next.1 and are consumed into.changeset/pre/; seat answer Q-S4 A is withdrawn (6167948246). This PR's own note states that their bare invocation is now refused, and gives the replacement.--template-choicegap (the apply deletes against groups recomputed at apply time, not against the plan file'stemplateConflicts) is carried by 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. It builds one mechanism for both choice flags on this PR'sReviewedPlan, after this PR is onmain.--applystops atrewrite: the grants--seed-adminwrites (admin_full_access,organization_admin_no_bypass) name sets the ceremony's security catalog does not resolve. This diff touches neither that step nor the catalog composition. It is reported on the card for the ceremony family (6163356782).Generated by Claude Code