Repository navigation
fix(cli): os migrate plan names the ownership ceremony, not --allow-destructive, for the organization_id drops apply refuses - #24954
Conversation
…zation_id drops apply refuses Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
…ps, with verified and no-drop controls Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
… one module load for both Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
…ny hold Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 30 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 4920f390e1b5c18e7921ac038bb41fd438b5df86 && git checkout 4920f390e1b5c18e7921ac038bb41fd438b5df86
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 865d2c8cf4accaaa5d1885d512f694e4abdce440 003e41ab5135d4db35636dcae23c5ab7b1d2d343 && git checkout -B drift-repro 865d2c8cf4accaaa5d1885d512f694e4abdce440 && git merge --no-ff 003e41ab5135d4db35636dcae23c5ab7b1d2d343
node scripts/docs-audit/affected-docs.mjs --json 865d2c8cf4accaaa5d1885d512f694e4abdce440
|
席位核对 · ACCEPT
Accepted deviations:
Out-of-scope dispositions (one line each):
Landing: through the merge queue by this seat once every check on this head reads Generated by Claude Code |
Fixes #24925
Clause-②: no
os migrate planno longer offers--allow-destructivefor theorganization_iddrops thatos migrate applyrefuses ahead of the organization-ownership ceremony (ADR-0131 D10 item 5). On a database the ceremony has not verified, each such drop is still listed and counted as drift. Its line names the ceremony as the remedy, and the footer names the ceremony's two commands. A verified database, or one with noorganization_iddrop, gets the plan it got before, byte for byte. The--jsondocument, the exit code andos migrate applyare unchanged.What changed
packages/cli/src/commands/migrate/plan.tsceremonyHeldDropsanswers which entries are held. It uses the two functionsapply's drop gate asks:ceremonyOrganizationColumnDrops, asked one entry at a time, andreadOrganizationOwnershipMarker, read through the stack'sobjectqlservice asapplyreads it. There is no second inventory and no table list. The marker is read only when such a drop is listed, and never under--json.--allow-destructivesentence (driftFindingWithoutRemedy, the seam[#23925]added), then:a drop the organization-ownership ceremony owns, and it has not verified on this database: the ceremony drops it itself (see below).Apply with: os migrate applyprints only when that command has non-destructive or additive work. It never adds--allow-destructive.os migrate organization-ownership --out PLAN, thenos migrate organization-ownership --apply --plan-file PLAN --backup-taken, spelled asapply's refusal spells them.[#23925]apply limit keeps its own warning, and its count excludes held drops.packages/cli/src/commands/migrate/plan.apply-limit.test.tsutils/one-shot-exit.ts: the file's 12 cases took 24 to 75 ms each after the first, on a shared box..changeset/24925-plan-names-ownership-ceremony.md:@objectstack/clipatch.One deviation from the suggested route, measured
The suggested route kept offering
--allow-destructivefor the other destructive entries. Butapply's gate refuses the whole--allow-destructiverun while anyorganization_iddrop is in it. Case 1c ofapply-organization-ownership-gate.integration.test.tspins that, and step 1 below shows it again: an unrelatedsys_sso_provider.created_bydrop was not applied either. Soapplywould perform none of the destructive changes. The footer offers the flag for none of them, and names the rest as waiting for the ceremony.Measured
Setup:
create-objectstack@17.7.0(npm), booted once on 17.7.0 withobjectstack dev --seed-admin. That leaves 43 tables carryingorganization_id, no ownership marker,sys_comment.reactionspresent and 0 comments.feb09db14(= main), and then this branch. It ran from a minimal project directory withOS_AUTH_SECRETset, so the auth family composes, against copies of that SQLite file.os migrate apply --allow-destructive --yesexits 1 at the reactions gate first, as in the card's walk.os migrate comment-reactions --apply --yes(Converted 0 reaction(s) … recorded as verified),os migrate apply --allow-destructive --yesexits 1:✗ Refusing this --allow-destructive run: it would drop organization_id from sys_job, sys_job_queue, sys_metadata, sys_metadata_commit, sys_metadata_history, sys_migration, sys_migration_journal, sys_secret, a drop the organization-ownership ceremony owns, and the ceremony has not verified on this database. … Nothing in this run was applied.organization_idin both;sys_job,sys_metadataandsys_secretkeep it;sys_sso_provider.created_byis still there.28 change(s): 6 safe, 0 needs-confirm, 22 destructive. Eachorganization_iddrop reads… (orphaned) — "os migrate apply --allow-destructive" to drop it., the footer readsApply with: os migrate apply (add --allow-destructive for drops / tightenings), and the ceremony is never named.sys_migrationtable (a copy withDROP TABLE sys_migration). The branch's plan exits 0 and lists the 7 remaining drops as held. It prints no WARN or ERROR line beyond the 3 boot WARN lines the held copy prints too.Byte for byte. Main's
plan.tsand this branch's, each built on the same tree, ran over the same four databases. Log timestamps and the duration line were normalised before comparing.sys_migrationtableorganization_iddriftPins: red on main, then green; one ablation
Pins in
plan.apply-limit.test.ts.bootSchemaStackis stubbed, and the marker read is the realreadOrganizationOwnershipMarkerover a fake engine.sys_migrationtable, where the read throws and the plan answers "not verified"; nothingapplycan do before the ceremony, so there is no apply line; and beside a driver's declared apply limit.--json. The document is unchanged, and no marker is read.organization_iddrop gives today's plan, and the marker is never read.plan.tsatfeb09db14(main), tree only, restored afterreturn marker.verified ? …changed toreturn true ? …(every database treated as verified), viascripts/ablation-replace.mjs03cb80830b10equals HEAD's, andgit diff HEADis empty.The subject resolves through relative source imports (
./plan.js), not through a package'sexports, so nodist/preflight applies.Local verification at
003e41ab51pnpm --filter @objectstack/cli typecheckexits 0, test layer included:check:test-typecheck: OK.pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2exits 0:Test Files 315 passed (315),Tests 4650 passed (4650).integrationtier is left to CI: this diff touches no integration file, spawn entry or driver boot path.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 67 derived commands, plus the 11 declared WIDE-population families. All 78 exit 0.check:dual-build-cjs-loadsandcheck:i18n-coverageprinted PREREQUISITE NOT MET and COULD NOT MEASURE until the unbuilt packages they read were built.check:route-envelopehit an ENOENT on a temporary tsup file my concurrent build was writing.--ran:✓ dispatch-gates --ran: 67 derived famil(ies) accounted for — 67 run, 0 NOT-MEASURED (a DERIVED zero — all 67 recorded an exit code and none of them is 3).pnpm lint(eslint . --no-inline-config) on this head exits 0 with no findings printed.Acceptance notes
--out, then--apply … --backup-taken) stops at itsrewritestep:sys_user_permission_set has no permission_set column, so no grant can name its set. Boot this release once against the database (its schema sync adds the column), then re-run.os migrate applywithout the flag (exit 0, 16 tables created or extended, 6 changes applied), the same ceremony passesrewrite.os serveboot adds that column too.Apply with: os migrate applyline above the ceremony line is a sanctioned first step, not a detour. In this minimal composition the ceremony then stopped atcolumn-dropsonsys_flow_credential, an object my measurement project does not declare. That is an artefact of the measurement set-up, not of this change.[schema-drift]lines indriver-sqlstill say"os migrate apply --allow-destructive" to drop itfor these columns. Measured:os serveon a 17.x copy exits 1 after 22[schema-drift]lines; the boot guard's refusal that follows names the ceremony.os migrate applywithout the flag still prints22 destructive change(s) will be SKIPPED (re-run with --allow-destructive to include them).andSkipped 22 destructive change(s) (re-run with --allow-destructive to include them)., which is a re-run its own drop gate refuses (apply.ts).--allow-destructiveforsys_comment.reactionsbeforeos migrate comment-reactionshas run, thoughapplyrefuses that drop too. Its predicate is inline inapply.ts, with no exported function to share, so it is not folded in here.plan.tsand, separately, inapply.ts's refusal (held off). No shared constant was introduced.outdated).apply's drop gate, and so this plan, ask onlyisDataMigrationFlagVerified. The plan followsapplyhere by construction.Generated by Claude Code