Repository navigation
feat(cli): os migrate plan lists the tenant columns still nullable, naming the voluntary ownership ceremony; the ceremony reports a NOT NULL shard truthfully - #25179
Conversation
…hard NOT NULL report Claude-Session: https://claude.ai/code/session_01RXxXFcq659RGJE6SsXAc3u Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RXxXFcq659RGJE6SsXAc3u Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RXxXFcq659RGJE6SsXAc3u Co-authored-by: Claude <noreply@anthropic.com>
…s advisory Claude-Session: https://claude.ai/code/session_01RXxXFcq659RGJE6SsXAc3u 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:
⛔ 8 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 535453a9c8c580a5aba191e2d5142bd6d2cb5b8d && git checkout 535453a9c8c580a5aba191e2d5142bd6d2cb5b8d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 72a302a9dde3347e15a971c5da28df205496a217 8ad60d9a9518ca6a65d48c4ce94397e40512cf97 && git checkout -B drift-repro 72a302a9dde3347e15a971c5da28df205496a217 && git merge --no-ff 8ad60d9a9518ca6a65d48c4ce94397e40512cf97
node scripts/docs-audit/affected-docs.mjs --json 72a302a9dde3347e15a971c5da28df205496a217
|
Contract reviewServed-tier: The face is the declaration face: ① Derived judgmentsRuling item 2: the advisory lives in
Population.
Residue marking against what the ceremony really does. Each
Ruling item 4: the stale shard sentences and the shard outcome.
The
Tests.
Prose (cli.mdx, upgrading.mdx, changeset), sentence by sentence against the diff.
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
Seat ACCEPT · epic PM Seat check
|
…migrate plan advisory Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQCyrGmMy6kwav9XCzAuQ9
Fixes #25134
Clause-②: yes (widening) —
os migrate plan --jsongains thenullableTenantColumnsfield, and the ceremony'snot-nullstep gains thealready-not-nulloutcome and thealreadyNotNullsummary count. No exit code, drift entry, op, refusal or accepted input moves.Metadata-protocol: none
Executes the maintainer's ruling 6328195425 (letter A, items 2 and 4) under the claim 6333411407. Dispatched by the epic seat (
epic:#15194,session_01Y5583bs2RDd53MH6kM1ca3). Branch fromorigin/mainab488d9b7d(carries #25133's fix, #25155).The claim expected
Clause-②: no. I re-declared it from the diff asyes (widening): two machine-read outputs gain a field and an outcome value. Nothing is narrowed, and the level stayspatch(Metadata-protocol: none).What changed
1.
os migrate plan: one advisory section (ruling item 2). New modulepackages/cli/src/utils/nullable-tenant-columns.ts, wired intopackages/cli/src/commands/migrate/plan.ts.os migrate organization-ownershipas the remedy. The section prints after the unmanaged-table section and before the drift verdict, so it shows on an "in sync" plan too. An 18.0.0-born store has no drift, so this matters.ceremonyOwnedNullability). It is read from the same source: each planned driver's tenant field per table (tenantFieldByTable), plus one catalog query per database for nullability. A rotation shard is read through its base.sys_sso_provideris never listed.sys_notification_subscription) is not listed either; the unmanaged-table section already reports it.changes, nothing tototal, and proposes no op.--jsonit is the separate fieldnullableTenantColumns:{ status: 'read', adr: 'ADR-0131 D1', remedy, columns: [{ table, column, database?, ceremony: 'tightens' | 'leaves', reason? }] }, or{ status: 'unreadable', adr, detail }.columns: [](read, none) cannot be confused with not read.ceremonyOwnedNullabilityis untouched (no driver file in the diff).Residue: marked apart, the dispatch's open question. I read 6323585273 §1 and marked residue, because that is both true and simple. Without the mark, a ceremonied store would be told to run the ceremony again for columns it never tightens. Each column carries
ceremony: 'tightens' | 'leaves', and aleavescolumn carries its reason. The reasons come from facts the plan already holds:rotationBaseOf): created before shards were created NOT NULL, and dropped when its period leaves the base's retention;driftApplyLimit('tighten_not_null')(the Turso remote face);notNullExempt, or themirror-deletionfate (sys_email_template).When no listed column is one the ceremony tightens, the footer says so and does not offer the ceremony. One case is deliberately not told apart: a table still holding a NULL row the ceremony cannot attribute. That would need a row count per table, and the ceremony's own plan already reports it.
2. The stale shard sentences (ruling item 4). In
packages/cli/src/utils/organization-ownership-not-null.ts, the docblock (:27-31) and the shard detail (:46) said each new shard is created nullable. That has not been true since 7b-0, and both are corrected.already-not-null: nothing to do, no note, counted in the step's newalreadyNotNullsummary key. This comes from the new optionalNotNullTarget.isNotNull, whichos migrate organization-ownershipwires to the same catalog read.unsupported, and its note now says it leaves with the base's retention.3. Docs.
content/docs/deployment/cli.mdx: a new "Tenant columns still nullable (advisory)" subsection in theos migrate plansection, with the human output (as printed on the fixture) and the--jsonshape.content/docs/upgrading.mdx: one paragraph in "Upgrading to 18.0".content/docs/releases/**,docs/adr/**, the shape generation, and any boot path.Measured on
fixture-18.0.0(HEAD'sos migrate plan, with the served environment'sOS_AUTH_SECRET)organization_id. The advisory lists 63 of them. The 2 not listed aresys_sso_provider(opt-out) andsys_notification_subscription(retired, undeclared).tightens, which matches the ceremony's measuredapplied 61.leaves:sys_activity__r20261020(the pre-7b-0 shard) andsys_email_template.leaves.not-nullstep reports HEAD's own shard asalreadyNotNull ≥ 1, neverunsupported.columns: []. Its only nullableorganization_idissys_sso_provider.Pins
fixture-18.0.0: lists every nullable tenant column and only those; residue marked; no drift entrypackages/qa/dogfood/test/upgrade-18x-harness.dogfood.test.ts, the 18.0.0 legalreadyNotNullutils/nullable-tenant-columns.test.ts;plan.nullable-tenant-columns.integration.test.ts--jsonshapeplan.nullable-tenant-columns.integration.test.ts;plan.apply-limit.test.ts(key set)plan.nullable-tenant-columns.integration.test.tschanges/totalbyte-equal to the fresh store's)plan.nullable-tenant-columns.integration.test.ts;utils/nullable-tenant-columns.test.ts(the driver's own drift scan)utils/organization-ownership-not-null.test.ts(real SQLite shard, both arms);organization-ownership-apply.generation-1.test.ts(step summary)The harness helpers
runSchemaPlanandrunOwnershipCeremonygain an optionalenv. The new legs passSERVED_ENV(anOS_AUTH_SECRET), as a productionos serveof the showcase must have. The existing legs are unchanged.Proof (head
8ad60d9a)plan.nullable-tenant-columns.integration.test.ts, which this diff adds to the integration tier.vitest run --project unitreported 321/322 files and 4754/4756 tests on the first run. The 2 red wereplan.apply-limit.test.tspinning the--jsonkey set, which this PR deliberately widens. The lists were updated, and the file plusplan.journal-mode-note.test.tsre-ran at 19/19. The rest of the integration tier is declared to CI (Test Core).tsc --noEmitandcheck:test-typecheck).--max-old-space-size=6144..tsfiles: exit 0.upgrade-18x-harness.dogfood.test.ts, both the 18.0 and the 18.0.0 legs): 15/15. Lock VERDICT command-exit 0.scripts/ablation-replace.mjs; the tool verified each landed on disk and was restored to blob == HEAD.total: red at thetotalassertion.process.exitCode = 1when the section prints: red at the human exit assertion.--jsonfield dropped: red at the fresh-store field.isNotNullcheck removed: red at thealready-not-nullpin.dist).node scripts/pm/dispatch-gates.mjs --commands: 100 families, each exit code captured before any pipe.--ranreconciliation: 100 accounted, 99 run, 1 NOT MEASURED. All 99 exit 0, after two re-runs:check-plugin-teardown-shape --self-testfirst exited 3 on the shallow clone; it passed after a fetch of its pinned commit.check:skill-examplesfirst exited 3 becauseclient-reactwas unbuilt; it passed after building it.check:dual-build-cjs-loadsexits 3 without a whole-monorepo build. It is declared to CI.8ad60d9a(check:type-check-coverage,check:type-check-debt,check:cross-package-test-inputs,check:test-source-alias,check:tier-file-adoption,check:durability-log-level, both changeset gates,check:nul-bytes,check:doc-authoring). All exit 0.check-governed-merges --branch origin/claude/issue-25134-migrate-plan-nullable-advisory: exit 0, NOT governed (0 of 15 paths), +926 / −33.pnpm dev:crm -- --freshreached ready, health 200.os serveofexamples/app-crmwithOS_AUTH_SECRETon a scratch database reached ready, health 200.os migrate planon that fresh store:nullableTenantColumns: { status: 'read', columns: [] }, nothing printed, "in sync", exit 0.os serve(OS_TENANCY_POSTURE=group,OS_AUTH_MEMBERSHIP_POLICY=invite-only,OS_PLATFORM_OWNER_EMAIL, a secret) of a scratch app declaring@objectstack/organizationsreached ready withOrganizationscomposed, health 200.os migrate planunder the same environment:columns: [], exit 0. The only nullableorganization_idissys_sso_provider.Acceptance notes
os migrate planrun withoutOS_AUTH_SECRETcomposes no auth family, asos servedoes. The advisory then cannot see the auth family's tenant tables (sys_member,sys_team,sys_user_permission_set, …), and the unmanaged-table section lists them as declared by nothing. Measured on the fixture: 52 listed without a secret, 63 with one. This is the existing composition rule for the whole plan, not the advisory's. The docs already say to run a migration with the deployment's environment.origin/mainmoved 6 commits since the branch point (72a302a9). None touches this diff's files or behaviour, andmerge-treeis clean. Not merged, per the post-PR merge rule; CI tests the merge ref.Generated by Claude Code