Repository navigation
feat(cli,plugin-sharing)!: an unattributable organization-less row blocks the ownership ceremony's certification (ADR-0131 C8 stage 8c-0) - #24403
Conversation
…ation Claude-Session: https://claude.ai/code/session_01BfSp42RND2f8bWLn2EC4w9 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfSp42RND2f8bWLn2EC4w9 Co-authored-by: Claude <noreply@anthropic.com>
…e rows Claude-Session: https://claude.ai/code/session_01BfSp42RND2f8bWLn2EC4w9 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfSp42RND2f8bWLn2EC4w9 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfSp42RND2f8bWLn2EC4w9 Co-authored-by: Claude <noreply@anthropic.com>
…0-unattributable-blocks
📓 Docs Drift CheckThis PR changes 2 package(s): 4 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 — 36 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 b81680e7800bee7f89bf58c3c0d4db10506a33eb && git checkout b81680e7800bee7f89bf58c3c0d4db10506a33eb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d79dc4883c5698cf05680a545758bdbba0d8a7a2 319935d6ece1da93e202f8bc5d3184c927bfa757 && git checkout -B drift-repro d79dc4883c5698cf05680a545758bdbba0d8a7a2 && git merge --no-ff 319935d6ece1da93e202f8bc5d3184c927bfa757
node scripts/docs-audit/affected-docs.mjs --json d79dc4883c5698cf05680a545758bdbba0d8a7a2
|
Contract reviewServed-tier: Rendered by an isolated at-tier reviewer subagent of the adopting seat ( Check-runs on the head (the gate verdicts): 41 runs, 36 ① Derived judgments(a) Ruling C, executed exactly — right.
(b) The
(c) The advisory DEPARTURE stays advisory — right. (d) (e) The A5 route (a) — right, minimal, graded, and the block is clean.
(f) The operator text (
② Semver level
③ Boundary flags
Non-blocking wording items (prose only; a
Implemented-by: VERDICT: PASS |
Part of #15212
Clause-②: yes (narrowing)
C8 stage 8c-0 (plan 6251466182 on #15212 and its amendments): it executes ruling C on decision #24352 (record 6251599171, verbatim choice: 「C 升级时拦住 (Recommended)」). #15212 stays open for 8b to 8d.
What changes
The ownership ceremony's post-check holds every unattributable row against certification (
packages/cli/src/utils/organization-ownership-apply.ts).TablePlan.unattributable(organization-ownership-plan.ts,walkNullRows): fate 4's reported rows (reasonfate 4: reported, never guessed), and a fate-3 row no anchor names under a wall (reasonno anchor names the owner,sys_file row X not found, and so on). The plan already classified them the same way. Only the apply's verdict counted them asadvisory, so the fix is in the verdict, not in the plan or the inventory.remainingFornow adds them to the table'sremaining, so the run'sblockingcounts them and thesys_migrationmarker stays unverified. A v18 server does not start on such a database: since fix(cli,runtime): the 17 → 18 ownership ceremony leaves no row without an organization on a single-posture database, its prescribed boot writes nothing before the guard refuses, and an unverified ceremony is not served #24325 an unverified ceremony is not served.held: { rows, remedy }in the report file. The terminal output names the table, its count and the remedy (UNATTRIBUTABLE_REMEDY): give the row its organization, or delete it by hand, then re-run--apply.advisorynow counts only two things. The first is an advisory departure's rows. The second is the reported rows of a fate-4 table whose column is not the tenancy anchor (InventoryEntry.notNullExempt:sys_sso_provider, the retiredsys_scim_provider). See the open question below.--apply's certification.An advisory DEPARTURE stays advisory (A1). It is a different population: a release whose own boot re-creates the rows, so no step of that release can remove them. The only member is the declared sharing-rule mirrors while
DECLARED_SHARING_RULE_SEEDER_RUNSistrue, and that has beenfalsesince stage 9. Blocking on it would block a database for rows its own release keeps writing, which no remedy can clear. The code path is unchanged.os dev's in-place upgrade reads the same verdict (A3) (dev-organization-ownership-upgrade.ts). Before this change it booted again onreport.completealone. A completed run whose post-check held rows then hit the boot guard's refusal a second time and endedos dev. Now it boots only oncomplete && blocking === 0, the marker the guard reads. Otherwise it prints each held table with its count and remedy, and the resume command.The declared-sharing-rule port answers SERVED names (A5, route (a)).
@objectstack/plugin-sharingexportstranslateDeclaredSharingRule(withDeclaredSharingRuleandDeclaredSharingRuleSkip). It is the existing, pure, unchanged translation enforcement serves a declared rule through.judgeDeclaredSharingRules), soDeclaredSharingRulesPort.rules()answers each declared name withrefused: nullor the reason.runDeclaredRuleMirrorDeletionrenames a mirror's grants only onto a served name. While grants still name a mirror of a refused declaration, the step blocks, naming the rule, its rows and the translation's reason, and writes nothing. A refused mirror that no grant names is still deleted.Operator text (A4).
content/docs/deployment/cli.mdxgains the verdict: what blocks, the remedy, that the marker means no organization-less row, and the not-the-tenancy-anchor exception. Theos devparagraph now says the upgrade also stops on post-check holds. Measured:install-modes.mdxholds no sentence about the ceremony's verdict (its only ADR-0131 mention is D6 install modes), so it is unchanged. Nothing undercontent/docs/releases/changed.The A5 route on the four axes
Clause-② (measured)
yes (narrowing), where the claim saidno (narrowing). Route (a) widens plugin-sharing's public surface by one export, atminor.--applyexits1and leaves the marker unverified where it used to certify.os devno longer boots on a complete-but-holding run. The mirror step blocks where it used to rename onto a refused rule.translateDeclaredSharingRuleis exported..changeset/15212-8c0-unattributable-rows-block.mdcarries each narrowing as FROM / TO / Fix, and the ADR-0087 dispositionnot-required (no-migration-prescription): no spec key, export, option or stored shape is renamed or removed, and the remedy is an operator data act the ledger has no reader for.check:adr-0087-registrationis green on it.Landing points outside the claim's file surface
packages/cli/vitest.config.ts: one anchored alias,@objectstack/plugin-sharingto its source.check:test-source-aliasreds without it, because the ceremony's tests now import plugin-sharing, and its registry is shrink-only.organization-ownership-inventory.tsandorganization-ownership-plan.tsare in the claim's surface and are unchanged. Measured (A2): both populations were already classified as one field. The verdict lived only in the apply.packages/plugins/plugin-sharing/src/declared-sharing-rules.tsis unchanged. The export is inindex.tsalone.Tests
On head
319935d6e(after mergingorigin/maindb4618b5c, which carries stage 8a):organization-ownership*test underpackages/cli/src/utilsandpackages/cli/src/commands/migrate, plusdev-organization-ownership-upgrade.test.ts: 22 files passed and 1 skipped by its own guard. 260 tests passed.@objectstack/clitypecheck(it compiles the tests) passed.@objectstack/plugin-sharingtypecheckandtestpassed (48 files, 1,139 tests).@objectstack/clivitest run --project unitpassed (311 files, 4,610 tests). The CLI's integration tier is declared to CI; the ceremony's own integration-tier files are in the first line above.dispatch-gates --commandsderived 101 families. All 101 ran, plus the 51 artifact-roster families, on this head.--ranreads 101 accounted for, 0 NOT-MEASURED. Four roster families need a PR context, so they refused locally (check-closing-target-claim,check-single-claim-paths,check-governed-queue-guard, andcheck-partof-closing-keywordwithout a body); CI runs them on this PR.check-partof-closing-keywordpassed locally on this body.blocking1, the ledger verdict{ complete: true, blocking: 1, advisory: 0 },heldonsys_saved_reportwith the remedy, and the row still present and still NULL.[true, 0, 0].isolated. It blocks, namessys_file row file_gone not found, and the row is untouched. Assigning its organization by hand certifies.[true, 0, 0].sys_sso_providerrow with no organization is reported, advisory 1, and certifies.judgeDeclaredSharingRulesover the real translator: served, untranslatable condition, dotted field recipient, match-all, and nameless.os dev: a complete run that holds rows does not boot, and names the table, count and remedy.scripts/ablation-replace.mjs, anchor hit 1 → 0, blobdcb8dd2b→074e0a99, then restored todcb8dd2b): withheldUnattributableforced to0, five pins inorganization-ownership-apply.test.tsgo red, the walled fate-3 pin and the acceptance fixture among them. Restored, all green.Acceptance notes
notNullExemptexception. Ruling C says "an unattributable organization-less row". Onsys_sso_provider(tenancy disabled; better-auth's provider relation) and the retiredsys_scim_provider(left in place untouched, by ruling), an organization-less row is legitimate. Neither table is tenant-scoped, so 8c's NULL-reader retirement does not read it. Blocking on these rows would force an operator to guess an owner or delete a legitimate SSO provider, which D10 fate 4 forbids. So they stay reported and advisory. If the seat reads the ruling as "every table", it is a one-line change inheldUnattributable, and its control pin flips.18.sharing-rule-declared-mirror-retired.tsmoved to stage 8e, per the claim.packages/spec/srcis untouched.🤖 Generated with Claude Code
https://claude.ai/code/session_01BfSp42RND2f8bWLn2EC4w9
Generated by Claude Code