Repository navigation
feat(plugin-sharing,plugin-approvals,metadata-protocol)!: 18.x step 7 part 7a-3 — the organization-less readers of sharing rules, approval units and the seed loader retire - #24828
Conversation
…r retired; webhook activation door Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…he 18.0 dual read; door pins for webhook Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…asserts no row and a dispatch Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…the checklist, the webhooks guide and the showcase; changeset Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…tcher's switch read Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…posture builds no wall, as its 18.0 seeded copy stamped with the Default Organization was Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…red writes; the declared-webhook double is drivable by the limit gate Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…does not implement Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…authors; checklist and admitsOrganization docblock say a declared webhook is deployment-wide under single Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…0g-webhooks-registry
…the fixture's customized declared-webhook mirror Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…n fail on a double delivery Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…0g-webhooks-registry # Conflicts: # content/docs/permissions/tenant-audit-census.mdx # docs/audits/2026-08-tenant-audit-write-call-sites.counts.md
…0g-webhooks-registry
…0g-webhooks-registry # Conflicts: # content/docs/permissions/tenant-audit-census.mdx # docs/audits/2026-08-tenant-audit-write-call-sites.counts.md
…-registry' into claude/issue-24581-ceremony-open
…NOT NULL door end to end
…he ceremony, then boot; boot-refusal-order step 2 back on the binary
…he checklist; changeset
…ap, docs and strictness-ledger artifacts
…el — the cipher names no organization Claude-Session: https://claude.ai/code/session_014UATXWqAXqkaZkbG92ox6V Co-authored-by: Claude <noreply@anthropic.com>
…sys_flow_credential leave reach) Claude-Session: https://claude.ai/code/session_014UATXWqAXqkaZkbG92ox6V Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 4 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 144 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 78ccb8e203143571a69c68b0988aecd3e33bd9a6 && git checkout 78ccb8e203143571a69c68b0988aecd3e33bd9a6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7c2eab929c6b724672849c5b295fa5e2f35d04c0 236d39daf47ce4b06c9e8757ccbe695af1ca1e19 && git checkout -B drift-repro 7c2eab929c6b724672849c5b295fa5e2f35d04c0 && git merge --no-ff 236d39daf47ce4b06c9e8757ccbe695af1ca1e19
node scripts/docs-audit/affected-docs.mjs --json 7c2eab929c6b724672849c5b295fa5e2f35d04c0
|
Contract reviewServed-tier: Read-only review of PR #24828 (18.x step 7, part 7a-3 of #24583): diff ① Derived judgments1. The shape (D2, ruling A) — holds.
2.
3. The lean-embedding key — robust, not a leak.
4. Readers — hold.
5. Wire change — the console needs no change.
6. Tests — pins rewritten, none weakened; the ablation is well-formed.
7. Overlap with PR #24825 (7a-1) on 8. Semver and Clause-② — correct. AGENTS.md §3: ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
… marker; an org-less row's delete by an org admin is pinned a no-op Claude-Session: https://claude.ai/code/session_01XFEb4fSDbe4fK94S3x5FKX Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFEb4fSDbe4fK94S3x5FKX Co-authored-by: Claude <noreply@anthropic.com>
…3-sharing-null-readers
…o driver NULL arm, declared rules deployment-wide by marker Claude-Session: https://claude.ai/code/session_01XFEb4fSDbe4fK94S3x5FKX Co-authored-by: Claude <noreply@anthropic.com>
Contract review (delta after PASS at 357025d)Served-tier: CONTRACT_REVIEW_TIER Read-only delta review of PR #24828 at its new head, in a detached worktree, against the PASS record at ① Derived judgments1. R1 —
2. 3. R2 — pinned, both the row and its grants, with something to lose. 4. R4 — reconciled, holds 7a-1, keeps 7a-3, weakens nothing.
5. Merge hygiene — clean. 6. CI on ② Semver level
③ Boundary flags
Implemented-by: claude/issue-24583-7a3-sharing-null-readers VERDICT: PASS |
Part of #24583 — step 7, part 7a-3 (claim 6285134534; governing text: execution record 6285122667, ADR-0131 D2 and D8). The other step-7 parts (7a-1 driver, 7a-2 authz, 7b-*, 7c webhooks) are not in this PR, and #24583 remains open.
Clause-②: yes (narrowing): a declared sharing rule served under
singlegains thedeploymentWide: truekey on the wire in place oforganization_id: null(widening); the sharing-rule admin reads, the approval unit screen and the scoped seed pre-load stop reading rows with no organization (narrowing). A declared rule keeps applying deployment-wide; nothing an author declares is refused.What changes
plugin-sharing
sharing-rule-service.tssingleprojection carries a typed marker,deploymentWide: true(DeploymentWideSharingRule,isDeploymentWideSharingRule), and noorganization_idkey. This is the shape of the declared webhooks'deploymentWide(ruling A, 6278609968). The scope list holds aDEPLOYMENT_WIDE_SCOPEsymbol in place ofnull.criteriaContextloses its NULL branch. It answers one of three named cases:single);no-organization-object), which also carries no tenant.null: a stored row with no organization sweeps nothing and grants nothing, and the service logs it once aterrorwith the remedy.single: one deployment-wide pass, and each grant is stamped with its record's organization.adminOrgScopeandfindRuleRowByNameread the caller's own organization only.assertCanDeletePlatformGlobalRuleis deleted, together with its call.sweepOrphanedRuleGrantsandruleGrantScopefollow the marker.plugin-approvals
ApprovalService.businessUnitOrgScope: a strict equality. The ceremony attributessys_business_unit, and a fresh install stamps it.metadata-protocol
seed-loader.ts: theunownedHoldersread is deleted, together with itsrefusedecision and the refusal text #24681 had rewritten. A scoped load issues noorganization_id IS NULLread on any path.Pins rewritten to the new rule (no assertion weakened):
rule-criteria-org-scope.test.ts(real SqlDriver):single: deployment-wide sweep;isolated: per-organization sweep;sharing-rule.test.ts:getRule's by-id branch has no org filter — an org admin can read, evaluate and DELETE another organization's sharing rule (and its grants) #7761 / plugin-sharing: amanage_sharingholder whose session has no ACTIVE organization reads every tenant's sharing rules (adminOrgScope falls open) #8158 blocks: own-organization only;approval-service.test.tsandbusiness-unit-member-org-screen.test.ts(fixtures re-stamped to the request's organization).seed-loader-null-organization-holder.test.tsand its runtime real-driver twin (no NULL read on any of the three readers).Premise measured first (step 7's "after certification")
Every reader retired here was dead on all three trees:
organization_idceremoniedFixtureover the 18.0 fixture), after the ceremony and again after a HEAD bootsingleinstall (showcase--fresh)sys_business_unit5 rows, all stamped)isolatedinstall, real organizations runtimeOne live path remains. It is kept, not deleted: the lean embedding with no organization object, which still writes NULL rows by design. It keeps the deployment-wide sweep, keyed on the composition fact (
sys_organizationnot registered), never on a row's NULL. The productionos serveboot withoutOS_AUTH_SECRETis that same composition today; 7b-3 makes it refuse.Proof
Final head
357025dc97. The boots, harness and suites ran on62227f5632, which differs from it only bymain's README commit.organization_idrows in any tenant table;share_key_account_qualified_contacts_with_managersmaterialised a stamped grant.upgrade-18x-harness.dogfood.test.ts+organization-ownership-boot-refusal-order.dogfood.test.tspassed, together with the two sharing dogfood files: 33/33. This run includes the ceremonied fixture, and the v17 refusal fired before the ceremony.isolatedos servewith@objectstack/organizations(invite-only membership, owner email set), on both heads: health 200, sign-in 200, 0 error lines, 0 NULL rows.GET /sharing/rulesserves both declared rules as per-organization projections, with no marker. The grant is stamped.serve-organizations-host-resolution+serve-organizations-mount-failure,OS_TEST_TIERS=nightly): 7/7.dispatch-gates --commandson the 12 code paths derived 75 commands. All 75 ran, and every exit code was captured before any pipe.check-system-context-censuswas red:content/docs/permissions/system-context.mdxstill cited the deletedassertCanDeletePlatformGlobalRule. The citation was dropped, and the re-run exited 0.check-plugin-teardown-shape --self-testwas a shallow-clone artefact: its pinned fixture commit was absent. It exited 0 once that commit was fetched.packages/runtime/src/standalone-stack-seeder-declaration-copy.test.ts: the served declared shape now carries the marker.sharing-rule-positions-name-authority.test.ts: retitled. Its delete case is refused by the scope gate, not by the retired guard.return SYSTEM_CTXfor every org-less rule) turned exactly the two predicted cases red ("a STORED rule with NO organization …", whole-rule and per-record), and the other 6 stayed green. Restore was proven by blob == HEAD and an emptygit diff HEAD.Acceptance notes
main'srecipient-membership-recompute.ts(ruleReadsRowsOf, landed in0b7286ff) still readsrule.organization_id == nullas "reads every row". With the marker the deployment-wide projection has noorganization_id, so it is still counted as affected, which is the right answer. The spelling is a NULL-shaped reader outside this part's file surface.sharing-plugin.ts'sresolveDeclaredRuleScopesstill spellssingleas[null].setDeclaredRuleScopestranslates it at that one door, so no NULL travels past it. Retyping the plugin side is outside the claimed surface.approval-service.tsteamIsProvablyOutsideOrgkeeps its "no organization ⇒ leave routing" limb (a team screen). It is not in this part's surface, and its doc cross-reference was corrected.rule-criteria-org-scope.test.tsstill expectsdeal_p1through the driver's NULL record arm. That is 7a-1's arm and 7a-1's pin. Separately, an org-less RECORD matched by two walled projections aborts the second pass withRECORD_NOT_FOUND. That case cannot occur on a certified database, and it goes away with 7a-1.content/docs/permissions/system-context.mdx, row 40: the deleted guard's citation, which its census gate requires) and pins inpackages/runtimeandpackages/qa/dogfood. It touches nodocs/adr/**and nocontent/docs/releases/.null-organization-holdernames:scripts/engine-double-contract.pinned.jsonregisters them by path.Generated by Claude Code