Repository navigation
fix(cli): os dev's in-place organization-ownership upgrade names what stopped it, and the command that carries on - #26785
Conversation
… stopped it When the boot guard refuses a development database, os dev copies it and runs the ceremony in place. An apply that wrote no report ended on "The upgrade did not run to a report" with no cause whenever the ceremony's refusal was coloured (FORCE_COLOR), or the apply stopped with no refusal of its own (an uncaught error, a crash, a signal), or it left its reservation text in the report file. The upgrade now reads the ceremony's log with colour stripped, from where each run started, and prints the refusal in the ceremony's own words, or how the run ended and the end of its log. Every failed upgrade ends on the exact command that carries on from the plan it wrote. A real os dev test pins a database with a stale organization_id on sys_flow_credential and sys_secret starting after the automatic upgrade. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pVM4YkXVkL6VgUUhP
📓 Docs Drift Check15 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 32 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 019ac9de6ee0ebc2d45accebc388cded5d0acd76 && git checkout 019ac9de6ee0ebc2d45accebc388cded5d0acd76
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8b276385e6f1e2b94a4061160c148cfa56f81588 74e75e381c7f4f1d82ad9cbd941f1951f0840561 && git checkout -B drift-repro 8b276385e6f1e2b94a4061160c148cfa56f81588 && git merge --no-ff 74e75e381c7f4f1d82ad9cbd941f1951f0840561
node scripts/docs-audit/affected-docs.mjs --json 8b276385e6f1e2b94a4061160c148cfa56f81588 |
…efore it runs Seat ruling 6446501014 (option C): a stale organization_id the plan gives the column-drop fate is a column the plan accounts for, so the dev upgrade proceeds, but never silently. After the plan and before the apply, os dev prints per table how many stamped values the drop discards (the plan's fateCounts.rowsLosingTheirStamp) and names the copy that keeps them. Pinned by a unit case and by the real os dev fixture, whose sys_secret row is now stamped. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pVM4YkXVkL6VgUUhP
席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Requested by Leehom · project thread
Fixes #26615
Clause-②: no
Metadata-protocol: none
维护者速读
os dev遇到旧开发库被启动保护拒绝、就地升级又没跑完时,不再只说「The upgrade did not run to a report」。它会打印仪式自己的拒绝原文(终端强制彩色时也读得到),或者仪式是怎么结束的(退出码、信号)加日志末尾;最后给出一条能直接接着跑的完整命令(带数据库和它写好的计划文件)。os dev的就地升级读结果和打印的那一层;生产os migrate … --apply仪式本身一行没改,升级前的备份照旧保留。回滚即还原packages/cli/src/utils/dev-organization-ownership-upgrade.ts。packages/cli/src/utils/dev-organization-ownership-upgrade.ts(stopCause与readApplyOutcome)、packages/cli/test/dev-organization-ownership-stale-column.integration.test.ts、scripts/check-cli-test-child-env.mjs(名单加一行)。pnpm --filter @objectstack/cli exec vitest run src/utils/dev-organization-ownership-upgrade.test.ts,应见 13 条全绿,其中「a refusal printed in colour is read in the ceremony's own words」。What changes
os dev's in-place upgrade of a development database the organization-ownership boot guard refused (upgradeDevelopmentDatabase, ADR-0131 D10 development exception) now never ends without its cause:stripVTControlCharacters). Before, a terminal that forces colour (FORCE_COLOR) made chalk writeESC[31m ✗ Refused: …into the log, the reader'sstartsWith('✗')matched nothing, and the stop printed no cause. Measured:FORCE_COLOR=1 os migrate organization-ownership --dev --apply …writes exactly that byte shape to a file. The✗glyph is now the printer's alone, no longer doubled (✗ ✗ Refused).Error: …), a crash or a signal printed no✗line, so nothing was shown. Now the upgrade prints`os migrate organization-ownership --dev --apply` exited with code N(orwas ended by SIGNAL, orcould not be started), then the end of that run's own part of the log, without the kernel's timestamped INFO/DEBUG narration and without the plan run's output.--outfile before it boots, holding a reservation text. A run that stopped between the reservation and the report left that text, which the upgrade parsed as a report with no verdict and printed 「The upgrade stopped at a blocker (above)」 with nothing above. A file that did not parse threw out ofos dev. Both are now "no report", with the cause. A blocked report that names no blocker prints the log's cause too.organization_idthe plan gives the column-drop fate is a column the plan accounts for, so the upgrade proceeds, but before the apply runsos devprints one line per column-drop table whose retired column held non-null stamps: the table, how many stamped values the drop discards (the plan'sfateCounts.rowsLosingTheirStamp), and that the copy keeps them. A table with no stamps is not named. A column the plan gives no fate still refuses the plan.os migrate organization-ownership --dev --database-url URL --apply --plan-file PLAN --backup-taken, with the database the boot opened and the plan the upgrade wrote. The apply resumes from its checkpoint and prints the stop on the developer's terminal.The copy taken before the upgrade is kept exactly as before. The production ceremony (
os migrate organization-ownership --applywithout--dev) is unchanged: no file underpackages/cli/src/commands/migrate/orpackages/runtimeis touched.Tests
packages/cli/src/utils/dev-organization-ownership-upgrade.test.ts: six new cases (a coloured refusal, an uncaught error with kernel narration around it, a signal, the reservation text left behind, an unparseable report file, a blocked report with no blocker), and the existing cases read the un-doubled refusal and the exact command; a seventh (ruling C) pins that asys_secretcolumn drop discarding 3 stamps is announced exactly once before the apply, a drop with 0 stamps is not, and the upgrade completes. 13/13 (ablating therows > 0filter turns the C pin red: 1 failed | 12 passed).packages/cli/test/dev-organization-ownership-stale-column.integration.test.ts(new, integration tier, the card's third target): a freshos devcreates the database, the staging step givessys_flow_credentialandsys_secretthe injectedorganization_idcolumn and itsidx_TABLE_organization_idindex with unstamped rows, and deletes the marker (the 18.0 pre-release shape). A plainos devon it is refused, upgraded in place and starts (「server started on the upgraded database」). Both columns are gone, the marker is verified, the rows are kept, and the copy still has both columns. Thesys_secretrow is stamped with a realsys_organizationid, andos devprints its stamped-count line before "Upgraded the development database" and does not name the unstampedsys_flow_credential. 5/5. It joins the built-entrypoint census ofscripts/check-cli-test-child-env.mjs(seventeen → eighteen, with its reason).Red first: the new cases against the base source
The base
dev-organization-ownership-upgrade.ts(5cc31c7885), restored fromHEADafterwards (git diff HEADempty), driven by a copy of the test whose fake answers the base seam'snumber:The first two are the un-doubled glyph and the exact command; the other six are the swallowed causes.
Acceptance notes
.objectstack/data/backups/organization-ownership-2026-10-27T10-08-08-349Z) is on the reporter's machine. Five shapes were measured, and each completed the upgrade and started: (1) a one-object fixture onmain, both columns added, no marker; (2) the same with a shape-generation-0 birth marker; (3) HotCRMafdf9a0onmainwith the columns, the indexes, and stamped rows in both tables; (4) the same on the published 18.2.0 install; (5) an authentic HotCRM database written by HotCRM4f82b81(2026-10-16, platform18.0.0-next.2), then started by HotCRM on the published 18.2.0. Its plan carried exactlysys_flow_credentialandsys_secretas column-drop tables with the column,column-drops done {"dropped":2}. The test fixture stands in for the reporter's backup. With this change, runningos devon that backup prints the cause the ceremony hit. Theceremony.logbeside the backup already holds it.sys_flow_credentialandsys_secretcarry inventory fatecolumn-drop(organization-ownership-inventory.ts), and the dev-mode ceremony drops both columns through the drift door, index included. The new test pins it. No ceremony change was needed.sys_audit_logand thesys_metadatafamily are column-drop tables and always stamped.organization-ownership-inventory.ts(not touched here). Card cli:os migrateentry point for the plugin-audit key-material ledger-copy rewrite (the remaining half of #26640) #26687 has no PR yet.Verification
Local subset at the head named in the report
pnpm --filter @objectstack/cli exec vitest run --project unit: Test Files 339 passed (339), Tests 4932 passed (4932), at74e75e381c.pnpm --filter @objectstack/cli typecheck: exit 0 (check:test-typecheck: OK).vitest run --project integration test/dev-organization-ownership-stale-column.integration.test.ts: 5 passed.dispatch-gates.mjs --commands) was re-run at74e75e381cafter a fullpnpm buildand reconciled with--ran: 80 derived, 80 run, 0 NOT-MEASURED.check:dual-build-cjs-loadsandcheck:i18n-coverageboth exit 0. The full table is in the os-dev-report on the card (round 2: 6447810905).Human Minimum
packages/specholds no runtime logic. (not touched)main, one pull request.packages/spectouched: not touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code