Repository navigation
feat(spec,runtime): an organization-scoped manage_connector_connections capability gates the connector connection door (ADR-0143 D5) - #28400
Conversation
…tion-scoped manage_connector_connections capability ADR-0143 D5 delegates who may connect, reconnect or revoke a connector connection to a capability the server slice declares. Connecting a vendor account is an organization act (Q1 = A), so the door now asks a new org-scoped capability instead of the platform-scoped manage_metadata. - spec: PLATFORM_CAPABILITIES gains manage_connector_connections (org); ADMIN_FULL_ACCESS_CAPABILITIES carries it, no other shipped set does. - runtime: the four door members (status, begin, callback, revoke) ask it, fail-closed, only isSystem bypassing; route-ledger rows say so. - ui: the Setup capability picker labels it in all ten locales. - tests: both directions pinned (manage_metadata alone refused; the organization administrator's shipped grant refused, admitted with the capability), default-set pins updated. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibwTBxFYpcsutpHMRHe
📓 Docs Drift CheckThis PR changes 2 package(s): 16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 173 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 76ffb693d86fa805ee047cebfce7e1ead811e7e3 && git checkout 76ffb693d86fa805ee047cebfce7e1ead811e7e3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 125ad062d2eaf49687a89489766a284ba60aa618 3979c8fbbdd6fc3f31c7d875e4d8af1093844ad7 && git checkout -B drift-repro 125ad062d2eaf49687a89489766a284ba60aa618 && git merge --no-ff 3979c8fbbdd6fc3f31c7d875e4d8af1093844ad7
node scripts/docs-audit/affected-docs.mjs --json 125ad062d2eaf49687a89489766a284ba60aa618
|
|
domain:server#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
domain:server#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #28327 (the SF4 finding of #28317's review, record 6539266538: the S1 door gated on the platform-scoped ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Verdict reasoning. The capability is one well-formed org-scoped registry row, the four door members ask it alone and fail closed with the platform administrator the only default holder, the ledger, docs, picker and packs follow the declaration, the pins hold under three ablations, the one open question is a maintainer ruling the PR does not pre-empt, and every latest check on this head is green or skipped. Implemented-by: VERDICT: PASS |
|
domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc 维护者速读
席位意见:契约复审 PASS(6543044473),四个接口失败即拒、两向都有消融 pin;组织管理员默认授权是放宽,留给你在 S4 前裁,不挡本 PR。可以直接落地。 席位核对ACCEPT · seat
|
Requested by Tesla · project thread
Fixes #28327
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: updated here — content/docs/automation/connectors.mdx names the connect door's capability; no skill under skills/** teaches that door's gate
维护者速读
manage_connector_connections,连接器 OAuth2 连接的四个入口(查看状态、发起连接、回调、撤销)改为只认这个能力,不再认平台级的manage_metadata。admin_full_access)默认持有,行为不变;组织管理员默认不持有,需显式授予(不放宽任何人)。该入口今早才合并、尚未发版,没有已发布的调用方失去权限。回滚即还原本 PR。packages/runtime/src/domains/automation.ts(refuseUngrantedConnectorConnectionAccess)、packages/spec/src/security/capabilities.ts(新条目)、packages/runtime/src/domains/automation-connector-connection-door.test.ts(两个方向的钉子)。pnpm --filter @objectstack/runtime exec vitest run src/domains/automation-connector-connection-door.test.ts,预期 22 个测试全绿,其中"manage_metadataalone no longer admits"与"organization administrator on the shipped grant is refused"各 4 条。What changes
@objectstack/spec:PLATFORM_CAPABILITIESgainsmanage_connector_connections(label "Manage Connector Connections",scope: 'org'), the capability ADR-0143 D5 delegates to the server slice.ADMIN_FULL_ACCESS_CAPABILITIES(the eval fixtureadmin_full_accessis built from) carries it; no other shipped set does.@objectstack/runtime: the connection door's gate (refuseUngrantedConnectorConnectionAccess) asksCONNECTOR_CONNECTION_CAPABILITY=manage_connector_connectionsfor all four members:GET/POST/DELETE /automation/connectors/:name/connectionandGET /automation/connectors/oauth/callback. Fail-closed, onlyisSystembypassing, placed where it was (ahead of the service probe and the query checks). The refusal text names the new capability. The four route-ledger rows say so.@object-ui/fields,@object-ui/i18n): the capability picker's curated label set andcapability.label.manage_connector_connectionsinuseFieldTranslationand all ten locale packs (the spec-parity and locale-parity pins require both).content/docs/automation/connectors.mdxnamedmanage_metadatafor the connect calls; it now names the new capability and who holds it.28327-manage-connector-connections.md(minor on the four moved packages).Untouched, by the order's serial note: no provider and no
connector-credential-source.tsedit (#26702 S2 owns those). The credential door (/connectors/credential-refs) and its Setup nav entry keepmanage_metadata: they act on environment-level credentials, not on an organization's connection.Tests
packages/runtime/src/domains/automation-connector-connection-door.test.ts: the refusal pins name the new capability; new pins, each over all four members:manage_metadataalone, and the whole platform-only set (manage_metadata,studio.access,setup.access,manage_users,manage_platform_settings) without the new capability → 403PERMISSION_DENIED, no service member called.manage_org_users,setup.access,setup.write,manage_mailboxes) → 403, no member called; the same grant plusmanage_connector_connections→ 200 and the member called once with the caller's organization.isSystempasses without the capability; an absent capability list is refused.org-scoped capability.packages/spec/src/identity/platform-admin-capabilities.test.ts:ADMIN_FULL_ACCESS_CAPABILITIEScarries it and it is declaredorg.packages/plugins/plugin-security:default-permission-sets.test.tspinsadmin_full_accessas its only shipped holder; the platform-administrator envelopes in four golden/equivalence suites gain it (theTENANT_ADMINenvelopes stay without it).Acceptance notes
organization_admin/organization_admin_no_bypasswould let every organization owner or admin connect a vendor account where today they cannot: a widening of an access-control boundary, which is a human floor in this lane. Until ruled, an organization administrator connects only after an explicit grant. Ablation 3 below shows the pin that holds this.manage_metadata→manage_connector_connectionsmove narrows the door relative tomainfor a set that grantsmanage_metadatawithoutadmin_full_access. The door landed with feat(service-automation): serve oauth2 connector instances per dispatch, and connect an authorization_code instance per organization (ADR-0143 S1) #28317 (2026-10-31 08:28Z) and its changeset26702-connector-oauth2-connections.mdis still pending in.changeset/, so no published version served it: no released caller loses access, and theClause-②arm is declared on the protocol change (a new accepted capability name).Verification
Implementation commit
7354e08f4on baseorigin/main3ba520691; head3979c8fbbmergesorigin/mainac09e7b85(no overlapping file). After the merge: install, rebuild of the runtime closure,pnpm --filter @objectstack/spec check:generatedexit 0 (14 artifacts up to date), and the door, spec, five plugin-security and ui parity files re-run green (22 / 403 / 92 / 68 tests).Build and tests
pnpm --filter '@objectstack/runtime^...' --filter @objectstack/runtime build→ VERDICT command-exit 0.packages/runtime/src/domains/packages-capability-gate.test.ts→ Test Files 2 passed, Tests 101 passed; specsrc/identity/platform-admin-capabilities.test.ts src/security/→ Test Files 16 passed, Tests 403 passed.pnpm --filter @objectstack/plugin-security test→ Test Files 238 passed, Tests 5125 passed / 45 skipped.pnpm --filter @objectstack/runtime test→ Test Files 491 passed, Tests 7981 passed / 19 skipped.pnpm --filter @objectstack/runtime typecheck,pnpm --filter @objectstack/spec typecheck→ exit 0 (same lock run, VERDICT command-exit 0).ui/:vitest run packages/fields/src/widgets/CapabilityMultiSelectField packages/i18n/src/__tests__/all-locales-key-parity.test.ts packages/i18n/src/__tests__/en-zh-key-parity.test.ts→ Test Files 4 passed, Tests 68 passed.eslint --no-inline-configover the touched non-ui files → exit 0;ui/eslint over the touched ui files → exit 0 (3 pre-existing warnings inCapabilityMultiSelectField.tsx, none on changed lines).Ablations (each through scripts/ablation-replace.mjs, restored to the HEAD blob with an empty git diff HEAD)
The subjects are imported from source by relative path (
./automation.js,./default-permission-sets.js), so no dist rebuild sits between mutation and run.manage_metadataback beside the new capability in the gate → door test: 4 failed / 18 passed, exactly the four "manage_metadataalone no longer admits the caller" cases.return undefined) → door test: 13 failed / 9 passed: every refusal pin, everymanage_metadata-alone pin, every organization-administrator pin and the fail-closed pin.organization_adminshipped set → plugin-security: 4 failed / 28 passed: the "no other shipped set carries the connector connection capability" pin (expected [ 'admin_full_access', …(2) ] to deeply equal [ 'admin_full_access' ]) and the three per-postureTENANT_ADMINenvelope goldens.Derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat7354e08f4: 113 families derived, each run with stdin closed, exit codes recorded before any pipe.--ranreconciliation: 113 derived, 111 run, 2 NOT-MEASURED, 0 UNRUN.check-plugin-teardown-shape.mjs --self-test(fetched its pinned fixture commit),check:skill-examplesandpnpm --dir ui exec vitest run scripts/(built@objectstack/clientand@objectstack/client-react; then Test Files 169 passed / 2 skipped).pnpm check:dual-build-cjs-loads(needs every package'sdist/, i.e. a fullpnpm build) andpnpm check:i18n(needs the built CLI and its extract closure). No committed translation bundle names a capability, so neither reads a file this diff changes; both run in CI'sLint & Repo Gates.Generated by Claude Code