Repository navigation
feat(core,plugin-security)!: 18.x step 7 part 7a-2 — a grant applies only in its own organization; the organization-less grant readers retire - #24872
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>
Contract review (re-review after FAIL at 21b1726)Served-tier: In-seat delta review of PR #24872 (18.x step 7, part 7a-2). The FAIL at ① Derived judgments
② Semver levelUnchanged: ③ Boundary flags
Implemented-by: VERDICT: PASS |
维护者速读 · 请批准 · epic 席
|
…2-authz-null-readers # Conflicts: # packages/plugins/plugin-security/src/delegated-admin-gate.ts # packages/plugins/plugin-security/src/organization-scope.ts
|
Seat delta check · epic PM Why it changed. The merge queue removed this PR at 2026-10-20T14:04Z. 7z (#24880, Delta, measured:
The review stands. The |
Part of #24583 — step 7, part 7a-2: the organization-less readers of the authorization resolver and plugin-security retire. The card stays open for the other step-7 parts (7a-1, 7a-3, 7b-1, 7b-2, 7b-3, 7c).
Clause-②: yes (narrowing) — measured: a stored grant row with no
organization_idno longer applies in any resolution, and no longer anchors platform standing.What changes
@objectstack/core—resolve-authz-context.tsgrantAppliesInTenant(org, tenantId)is noworg !== null && org === tenantId. Asys_user_position(§4) orsys_user_permission_set(§6) row applies only in its own organization; with no active organization, no stored grant applies. §4 now reads the row throughgrantOrganizationOf, so a blank value is normalized to null like everywhere else.grantOrganizationOfstays as the normalizer;null(NULL, absent or blank) confers nothing.grantAnchorsPlatformStandingloses its organization-less arm: undersingle, only the Default Organization'sadmin_full_accessgrant anchorsplatform_admin. That is the grant the first-user promotion already writes. It still applies in every organization, or with none active. Walled postures are unchanged (no grant anchors there).mayApply/applyingGrantskeep their shape and now read the narrowed rule; their docblocks are corrected.@objectstack/plugin-securitydelegated-admin-gate.ts:holdingTakesEffectIncounts a holding only when it belongs to the caller's active organization. That is exactly what the resolver grants, so an organization-less holding, or a caller with no active organization, can no longer self-delegate. The org-admin removal of an organization-less row no longer claims the row "applies in every organization".bootstrap-platform-admin.ts:isAnchorHumanHoldermatches the Default Organization's grant only. Leg A asks once, for the Default Organization's id asorganization_id, never fornull. When no Default Organization can be named (a wall, or none to name), the holder scan reads no grant at all. The walled "no owner email" error loses its sentence about a legacy unscoped grant row, which can no longer be found.organization-scope.ts:resolveOwnOrganizationRowreturns{ own }; the residue half is gone.grant-permission-set-name.ts: the dead exportgrantOrganizationOfis deleted. It was not re-exported from the package and had no importer.Two text corrections this change made false (a declared extension of the claim's file surface, text only):
core/src/security/admin-standing-surface.ts: the module docblock and thesys_user_permission_setreason no longer name the organization-less row as an anchor.runtime/src/domains/automation.ts: undersingle, the run-lifecycle refusal named "an unscopedadmin_full_accessgrant" as the remedy. It now names the Default Organization's grant.Pins and fixtures. Every pin that asserted the retired reading is rewritten to the new rule, each with a positive control in the same test. Every fixture that relied on an organization-less grant to exercise something else is re-stamped into an organization (with the resolution run in it) or into the Default Organization. No assertion is loosened, no test is skipped, and no test is deleted. These live in 16 packages, because the readers' pins reach that far: core, plugin-security, plugin-auth, runtime, rest, cloud-connection, service-datasource, service-settings, service-automation, plugin-hono-server, plugin-approvals and plugin-sharing (test files only), and dogfood. Five suites gained a new negative pin, "an organization-less grant confers / anchors nothing".
Premise check — the retired readers are dead after certification
singleinstall (pnpm dev -- --fresh, merged head)sys_user_permission_set/sys_user_positionrows with NULL or blankorganization_idadmin_full_accessgrant is owned by the Default Organizationisolated)os serve, real Organizations runtimeexpectCertified: zero NULLorganization_idrows; the fixture's admin then signs in and readsno-organization-object)os serveregisters SecurityPlugin only inside the AuthPlugin block (which shipssys_organization); no example, template oros initoutput composes plugin-security without it. In such an embedding, grants stay organization-less and now confer nothing (the changeset says so).Verification (head
21b172681:origin/mainat81120caacmerged in, carrying #2468112d14765c, 7a-17c2eab929, 7cbd6dab009, 7a-37cacad534and 7b-3df8bb40a0)The full set below ran on
a62067ad4(main with #24681 and 7a-1). The two later merges (7c, 7a-3, 7b-3 and the commits around them) were re-checked on the packages they touch: plugin-sharing, plugin-approvals, plugin-auth, runtime, service-settings, plugin-hono-server and CLI unit, the merged sharing and settings dogfood files, speccheck:generated, R1, R4 live, and the harness. All green on21b172681.singleboot, random portplatform_admin, 0 error linesupgrade-18x-harness+organization-ownership-boot-refusal-orderdogfoodisolatedos serve, throwaway app declaring the real@objectstack/organizationsOrganizationsmounted, discovery 200, 0 error lines; a non-owner admin resolveseveryoneonlyOS_TEST_TIERS=nightly)--project unit4635 — 0 failedshowcase-crud-persona-matrix: its probe picked a paid invoice. That is order-dependent and unrelated, it passed alone, and main fixed it in08d201a80)dispatch-gates --commandscheck-plugin-teardown-shape --self-testneeded its pinned commit fetched into this shallow clone, then exit 0)runtime/test-typecheck-debt.jsonwas re-recorded: one pinned TS7053 signature was renamed by a fixture's added table; the count is unchangedcheck-platform-checklist --changed-from origin/mainReverse verification (ablation), core resolver, source path (core's suite imports its own
src):grantAppliesInTenantback toorganizationId === null || …→ 8 failed / 145 passed across the three resolver suites. Exactly the organization-less and no-tenant pins went red. Restore proven by blob hash.grantAnchorsPlatformStandinggiven back itsnullarm → 5 failed / 170 passed. Exactly the anchor pins went red. Restore proven by blob hash.Acceptance notes
check-governed-merges --branch: 3,341). Almost all of it is test fixtures across the 16 packages that pin these readers. No governed path is touched, but the size rule applies: this lands by an authorized approval or the maintainer's merge.plugin-auth/src/last-admin-guard.tsanchorOwnerTest(about line 1028) still counts an organization-lessadmin_full_accessgrant as a platform-admin holder;plugin-auth/src/ensure-default-organization.ts(about lines 505–509) still asksorganization_id: nullwhen it picks the Default Organization's owner;plugin-security/src/explain-engine.tscollectGrantProvenancereads holdings with no organization filter, so it can annotate a row the resolver no longer applies.On a certified or fresh database none of them is reachable, because no such row exists.
assertResolvableAdminScope(sharing-rule-service.ts) is no longer reachable over HTTP by any grant. Its comment still describes the old reading.OS_TENANCY_POSTURE, while cloud-connection's install door reads it from thetenancyservice. The organization-less grant used to mask a disagreement between them.service-automation/src/sys-user-default-row-scope.integration.test.tsbinds its'*'reader to theeveryoneanchor, because its "no organisation" legs cannot be reached by a stored grant any more.service-datasource/src/__tests__/admin-routes-tenancy-posture-admission.test.ts§4–§7 now pin that an organization-less API key passes the posture but holds nothing (403 rather than the posture's 401).维护者速读(草稿)
admin_full_access授权。os serve)不受影响。改动大(3,341 行,几乎全是测试夹具改戳),回滚即 revert 本 PR。Generated by Claude Code