Repository navigation
feat(plugin-security): organization administrators hold manage_connector_connections by default (ADR-0143 D5, ruling B) - #28441
Conversation
…ections by default (WIP) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxkKcPULsq3fo4JFAX
…ault holders of manage_connector_connections The shipped-set pin names admin_full_access and both organization-admin sets as the only holders and member_default / viewer_readonly as withheld; the no-bypass variant's list equals its parent's. The grant goldens gain the capability on every organization-admin envelope inside the organization (and the agent acting on its behalf), never outside it. The connection door admits the shipped organization-admin grant for its own organization and refuses the same person active in another one. Docs: connectors.mdx names organization admins as default holders. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxkKcPULsq3fo4JFAX
…mmit-trail pin carries manage_connector_connections Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxkKcPULsq3fo4JFAX
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 17 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 86aa22a26f303241465cf18f1831f8216b433d4a && git checkout 86aa22a26f303241465cf18f1831f8216b433d4a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5517005d3a53600300f5cb8974ae20f178b63132 29c358576aa95cc4463f0cf1b616609dd81a113f && git checkout -B drift-repro 5517005d3a53600300f5cb8974ae20f178b63132 && git merge --no-ff 29c358576aa95cc4463f0cf1b616609dd81a113f
node scripts/docs-audit/affected-docs.mjs --json 5517005d3a53600300f5cb8974ae20f178b63132
|
|
triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: ① Derived judgments
② Rulings cited
③ Narrowing / widening
④ Behaviour / flags
Implemented-by: |
|
domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc 维护者速读
席位意见:契约复审 PASS。代理人(agent)代组织管理员调用这扇门的问题不在裁定 B 范围内,已另交决裁清单(#435 第 1 项),不挡本 PR。可以直接落地。 席位核对ACCEPT · seat
|
Requested by Tesla · project thread
Part of #28427. This PR carries the code, pins, docs and changeset. The card's item 4 (one line in ADR-0143 D5 recording ruling B) is split out so that this PR is not held as Tier H; the line is handed to the seat as a patch, and the card stays open until it lands.
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
organization_admin,以及由它派生的organization_admin_no_bypass)默认拥有manage_connector_connections,组织的 owner/admin 一开始就能为本组织连接 OAuth2 连接器(DocuSign、Salesforce、Google 等),不再需要有人手工绑定权限集。actions:execute授权的 AI agent 会一起获得(沿用现有派生,平台管理员的 agent 今天已是如此),见 Acceptance notes。回滚即 revert 本 PR。packages/plugins/plugin-security/src/objects/default-permission-sets.ts的organization_admin.systemPermissions一行;objects/default-permission-sets.test.ts的两条持有者 pin;automation-connector-connection-door.test.ts的"本组织放行、他组织拒绝"用例。pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 src/objects/default-permission-sets.test.ts,预期全绿,持有者恰为admin_full_access、organization_admin、organization_admin_no_bypass三个。What changes
The grant.
organization_admin'ssystemPermissionsgainsmanage_connector_connections(default-permission-sets.ts). The set is declared once;deriveWallLessOrgAdminspreads the parent's declaration and drops only the wildcard superuser bits, soorganization_admin_no_bypasscarries the capability from that one line. It is never written a second time.auto-org-admin-grant.tsalready grants one of the two sets, per organization and by posture, to every member whose role containsowneroradmin, so those members hold the capability in that organization.Scope. The capability is
org-scoped. Core's resolver applies an organization-scoped grant only in the organization the grant row names (grantAppliesInTenantinresolve-authz-context.ts), and the connection door hands the service the caller's owntenantId. So an organization admin oforg_aconnectsorg_a's instances only. In an organization where they are a plain member they hold the member baseline, which does not carry the capability, and the door refuses them.Unchanged.
member_default,viewer_readonlyand every other shipped set hold the capability only by explicit grant. No other capability of any set moves. Nothing underpackages/specchanges.The agent golden.
builtin-positions.boot.test.tsandgrant-permission-set-name.equivalence.test.tsbuild the agent row from the organization admin's resolved envelope throughassembleExecutionContext(packages/core/src/security/assemble-execution-context.ts). For an OAuth agent withactions:execute(delegatesActions), that function passes the human'ssystemPermissionsthrough unchanged. The agent's golden gains the capability through that path only. No line names an agent.Docs.
content/docs/automation/connectors.mdx: the connect paragraph now says platform administrators and organization administrators hold the capability by default, and anyone else receives it through a permission set.Tests
objects/default-permission-sets.test.ts: the holders ofmanage_connector_connectionsacross the shipped sets are exactlyadmin_full_access,organization_admin,organization_admin_no_bypass.member_defaultandviewer_readonlyexist and do not hold it. A second pin asserts the no-bypass variant'ssystemPermissionsequal its parent's, so the capability rides the derivation and is not a second list.automation-connector-connection-door.test.ts(runtime), for each of GET / POST / DELETE / callback: an organization admin on the shipped grant is admitted with 200, and the service member is called once withtenantId: 'org_a'. The same person active inorg_b, holding the member baseline there, is refused with 403PERMISSION_DENIED, the message names the capability, and no service member runs. The transcribedORG_ADMIN_GRANTnow carries the capability.resolve-authz-grant-set-by-name.golden.test.ts,grant-readers-by-name.golden.test.ts,grant-permission-set-name.equivalence.test.tsandbuiltin-positions.boot.test.ts. Each gains the capability on the organization admin's envelope inside the organization, and on the agent acting for them. The resolver golden'snoOrganizationenvelope stayssystemPermissions: [].packages-capability-gate.test.ts: the commit-trail refusal case named "the organization admin's full grant" now lists the full grant. It is still refused, because that door asksstudio.access.Whole packages on
29c35857: plugin-securityTest Files 238 passed (238),Tests 5126 passed | 45 skipped (5171). runtimeTest Files 496 passed (496),Tests 8972 passed | 19 skipped (8991).Ablations
Both mutations went through
scripts/ablation-replace.mjs. The anchor landed on disk (count 1 → 0), and the file was restored with the blob equal to HEAD andgit diff HEADempty. Both suites import their subjects fromsrc, so no dist was involved.'manage_connector_connections'fromorganization_admin.systemPermissions(default-permission-sets.ts)refuseUngrantedConnectorConnectionAccesscall replaced byundefined,automation.ts)automation-connector-connection-door.test.ts -t 'refused in another'expected 200 to be 403on each verbThe derivation-equality pin stays green under the first mutation, as expected, since both variants lose the capability together. The holders pin is the one that reds there.
Acceptance notes
docs/adr/**Tier H. The line is a record of ruling B and changes no code path, so it ships separately rather than holding this PR. Patch for the seat:server-seat-1/r2026-10-31/out/28427-adr-0143-d5.patch. The landing of that PR closes the card.packages/speccomments ruling B makes false, left untouched by the seat's ruling (spec lane's line, handed over after this merges):packages/spec/src/security/capabilities.ts:103-110,packages/spec/src/identity/eval-user.zod.ts:166-169,packages/spec/src/identity/platform-admin-capabilities.test.ts:52-54. Each says the organization administrator holds the capability only by explicit grant.systemPermissions, and an OAuth agent withactions:executecarries its human's list. So an agent acting for an organization admin can now read, begin and revoke that organization's connection, as an agent acting for a platform admin already can onmain. Ruling B did not address agents. Whether the door should refuse an agent principal is a separate question for the seat.content/docs/permissions/permission-sets.mdx's summary row fororganization_adminnames a subset of its capabilities (it already leaves outmanage_mailboxes), so it is not edited here.claude/project-thread-jtzyzvis the card thread's platform-assigned branch; claim 6545275282 names it.Verification
Local, on
29c35857:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 92 families. Each was run with stdin from/dev/null, and its exit was captured by redirect before any pipe. All 92 exit 0. Three of them (check:skill-examples,check:dual-build-cjs-loads,check:i18n) first exited 3, PREREQUISITE NOT MET, and were re-run after a fullpnpm build.--ran:92 derived famil(ies) accounted for — 92 run, 0 NOT-MEASURED, exit 0. Scoped lint, the rootlintscript's eslint overpackages/plugins/plugin-security packages/runtimewith--no-inline-config: exit 0.pnpm --filter @objectstack/plugin-security --filter @objectstack/runtime run typecheck: exit 0.Generated by Claude Code