Repository navigation
feat(cli)!: 18.0 guards — os migrate organization-ownership refuses, and os serve refuses a walled posture at boot - #24480
Conversation
Claude-Session: https://claude.ai/code/session_01Q4U6vXvHNpcDZr24aXeJo2 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4U6vXvHNpcDZr24aXeJo2 Co-authored-by: Claude <noreply@anthropic.com>
…nd walled postures; changeset Claude-Session: https://claude.ai/code/session_01Q4U6vXvHNpcDZr24aXeJo2 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 29 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 3bd4a20994cb28b8052abb5af100e05855f15863 && git checkout 3bd4a20994cb28b8052abb5af100e05855f15863
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 31ce45c071bbcb6e92a730ce19ebf30dcfa361cb cca354048165cb46cdbfb3d1a55401b636bcc1b1 && git checkout -B drift-repro 31ce45c071bbcb6e92a730ce19ebf30dcfa361cb && git merge --no-ff cca354048165cb46cdbfb3d1a55401b636bcc1b1
node scripts/docs-audit/affected-docs.mjs --json 31ce45c071bbcb6e92a730ce19ebf30dcfa361cb
|
Contract reviewServed-tier: Head resolved at review time: ① Derived judgments(a) The ceremony refusal — holds.
(b) The walled-boot refusal at
(c) (d) The 16 test-file import changes — hold. Each swaps (e) Docs — true and minimal. ② Semver level
③ Boundary flagsCheck-runs on the head at review time: 33 success, 5 skipped (opt-in / matrix placeholders), 2 in progress — The report's open questions:
The report's out-of-scope findings:
Obligation on the 18.x restore, so it is not lost: delete the subclass ( Implemented-by: VERDICT: PASS |
Part of #15212
Clause-②: yes (narrowing)
The 18.0 guards stage (plan amendment 14 on #15212, from ruling records 6259608309 and 6259698357 on #15194). 18.0 ships for fresh installs under the
singleposture only. This PR makes the two things 18.0 does not support refuse loudly. It deletes no ceremony code, and it adds no re-enable flag or environment escape hatch.What changes
os migrate organization-ownershiprefuses in 18.0, plan and apply alike.1with the standard ADR-0112 codeNOT_IMPLEMENTED. Under--jsonit prints{ error: 'plan_refused' | 'apply_refused', reason: 'upgrade-unavailable', code: 'NOT_IMPLEMENTED', detail }.OrganizationOwnershipCeremony. The registered default command is a subclass of it whoserun()refuses: same flags, same help, plus a description prefix. The 18.x release that carries the upgrade path deletes the subclass. The ceremony's ~45 existing in-process tests now import the ceremony class, a one-line import change in each of 16 files, so the 18.x ceremony keeps its coverage.organization-ownership-*.tsutils are untouched.os serverefuses a walled posture (group/isolated) at boot.process.exit(1)shape as an unrecognised posture: before a config is read, a plugin is mounted or anything is written.OS_MULTI_ORG_ENABLED=trueresolves toisolatedand is refused too.NOT_IMPLEMENTED, the 18.x release and the fixes:OS_TENANCY_POSTURE=single, or stay on 17.x.OS_ALLOW_DEGRADED_TENANCYdoes not lift it.os devandos startspawnserve, so this is every operator boot. Undersinglenothing changes.Where the walled refusal sits (measured)
os serve's posture gate (chosen)devandstartspawnserve)serveunderisolated— rewritten, see belowos dev/os startonlyos serveresolveTenancyPosture,createStandaloneStack,bootStack)wall-*files, verify posture tests, plugin-auth, core and driver testsThe kernel-level walled boots (
@objectstack/verify'sbootStack, the dogfood wall suites, the unit tests that setOS_TENANCY_POSTURE=isolated) never pass throughserve, and they keep working. The only walled boots this refusal breaks are the two nightly-tier CLI e2e files that spawnos serveunderisolated. Those tested the organizations runtime's host resolution and its mount-stage classification, which no 18.0 walled boot reaches. They keep their fixtures and now pin, over the real process, that the 18.0 refusal comes first: whatever the package state (installed in the app, absent, reachable only throughNODE_PATH, present-and-refusing) and with or withoutOS_ALLOW_DEGRADED_TENANCY=1, no organizations stage runs. The 18.x release restores their resolution cases from git history.The
os devautomatic ceremony (A1)os devruns the ceremony only when the boot guard refuses the database. That never happens on a fresh database: the guard passes a database with no retired column and no marker. When it does run, it spawns this same command, so it now gets the refusal. It prints the command's own refusal line, says nothing was changed, and keeps the copy it took. Noos devchange was needed.Docs
content/docs/deployment/cli.mdx(the command's section, its table row, andos dev's in-place upgrade section),tenancy-modes.mdx,environment-variables.mdx(theOS_TENANCY_POSTURErow) andself-hosting.mdx(walled first boot) each gain the sentence "18.0 supports freshsingleinstalls; upgrades and walled postures arrive in 18.x", only where the text would otherwise be false. Nothing undercontent/docs/releases/.Tests
packages/cli/src/commands/migrate/organization-ownership.release-refusal.test.ts(5 cases): plan and apply refuse withNOT_IMPLEMENTEDand exit 1; the plain output carries the code; nothing is touched (boot and occupancy stubs that throw, an unrecognised posture, a missing supplement and plan file, no db or out file created); the default class is a subclass of the ceremony with the same flags.packages/cli/src/commands/serve-walled-posture-refusal.test.ts(5 cases):singlegivesnull; every walled posture in the spec vocabulary is refused; theOS_MULTI_ORG_ENABLEDsource is named; and the realserve, run in-process underisolatedand underOS_MULTI_ORG_ENABLED=true, exits 1 with nothing announced (no config read).packages/qa/dogfood/test/organization-ownership-boot-refusal-order.dogfood.test.ts: new step 2a, in which the real CLI binary refuses withNOT_IMPLEMENTEDand the database columns are unchanged. Step 2's upgrade proof now runsOrganizationOwnershipCeremonyfrom the CLI's built module in its own process (oclif auto-transpile off, asbin/run.jsdoes), so the ceremony → boot-succeeds proof stays alive for 18.x.Readings on head
cca3540481(the measurements below were taken on9141352b, before the merge ofmain, which touched only unrelated cli tests; the ratchet families were re-run oncca3540481):typecheck: exit 0.serve-tenancy-posture-gate: 18 passed, 1 skipped (remote-face, which needssqld), 199 tests.OS_TEST_TIERS=nightly): 7/7.Ablations, each through
scripts/ablation-replace.mjs, mutation landed and restored withgit diff HEADempty:runreplaced by the ceremony's → 4/5 refusal cases red, the ceremony answeringposture-unresolved;walledPostureRefusal(tenancyPosture)call replaced bynull→ both in-process boot cases red;singleearly return deleted → thesinglecase red.Acceptance notes
organization-ownership-boot-guard.ts), left as is per the dispatch, still namesos migrate organization-ownershipas the remedy for a v17-shaped database. In 18.0 that command refuses, and its refusal says to stay on 17.x. The guard's prescription is therefore incomplete for 18.0. Whether to reword it is the seat's call: the dispatch said to leave the guard alone.os serve's unrecognised-posture FATAL still listsgroupandisolatedamong its fixes (fromTENANCY_POSTURE_FIX_HINTS). Following either now reaches the 18.0 refusal on the next boot, which is loud, so it is not touched here.createStandaloneStack) are not refused: they are not the operator'sosboot path, and refusing there would break the kernel-level walled suites.os doctorstill reports a walled posture without saying 18.0 refuses it.Generated by Claude Code