Skip to content

docs(adr): ADR-0090 D12 names the organization administrator's assignment boundary; ADR-0131 D3 notes it - #24163

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-24135-adr-0090-d12-org-admin-boundary
Oct 17, 2026
Merged

os-zhuang merged 4 commits into
mainfrom
claude/issue-24135-adr-0090-d12-org-admin-boundary

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #24135
Clause-②: no

Summary

Executes the maintainer's ruling of record 6228728551 on #23960 (letter B, 「同意」): the boundary for what an organization administrator may assign is declared in the ADRs first, so the plugin-security card (#24136, Blocked-by: this one; it remains open) can enforce it.

  • ADR-0090 (docs/adr/0090-permission-model-v2-concept-convergence.md): one status-line entry (the ADR's own convention for amendments, beside the 2026-10-15 entry for ruling 6181988435) and one dated note under D12, Amended 2026-10-17 — the form D11's 2026-10-14 note uses. D12 now names three kinds of administrator (tenant-level, organization-level, delegated); the organization-level kind creates and removes sys_user_position / sys_user_permission_set rows for its own organization's members when every distributed set has no wildcard superuser bit, no adminScope and only scope: 'org' capabilities; admin_full_access, platform sets, organization_admin itself and every delegated set stay outside; organization-administrator status is conferred by membership grade only. Strict containment and the everyone / guest anchors are unchanged. Ruling item 4's denylist names the three D10 agent ceilings, which the three conditions mis-settle; the general case behind them is held for the maintainer's decision on [Decision] The organization administrator's assignment boundary: an assignable set with a wildcard read and no row scope reaches other organizations' SCIM rows in a deployment with no platform baseline #24164. The D12 text above the note is not rewritten.
  • ADR-0131 (docs/adr/0131-total-organization-ownership-no-null-organization-id.md): one dated note under D3's assignment paragraph, pointing at the D12 boundary as the rule that sentence runs under. It inherits the denylist by reference.
  • No code, no packages/spec key, no test, no migration text. The v18 entry's replacement text (18.walled-catalog-tenant-writes-retired) stays as published.

Patch round 1 (contract review 6232429140 on head b0173e29c, FAIL)

The review found precision 5 of the previous head wrong for this boundary: the three D10 agent ceilings pass the three conditions mechanically but are not wall-bounded on their own. Re-measured here before editing, on origin/main eda6f14b7b: mcp_agent_data_read is objects: { '*': { allowRead: true } } with no rowLevelSecurity (default-permission-sets.ts:1427–:1435); the seven SCIM projection objects are tenantField: null, reach: out in scripts/platform-object-tenancy-census.json; fallbackPermissionSet: null composes the human baseline to [] (app-default-permission-set.ts:40–:57). The head 61c8a5d34 therefore:

  1. moves mcp_agent_data_read, mcp_agent_data_write and mcp_agent_restricted (the last by kind: an agent ceiling is not a person's set) from "Inside" to "Outside (denylist)" in the D12 note's measured paragraph, with the reason — an agent ceiling's bound is the delegating principal's own grants (D10 rule 1), not the organization wall; held by a person as a direct grant, the two reading ceilings carry no row scope on the Layer-0-inert SCIM projection tables, and in a walled deployment with no platform baseline they read every organization's rows there, a read the granting organization administrator does not hold;
  2. replaces "names no set at this date" with "names those three sets", keeping the clause that a set shipped later that the conditions cannot settle joins the denylist by an amendment;
  3. adds one sentence: the general case — an authored set with a '*' read and no row scope on a Layer-0-inert readable object, in a deployment with no platform baseline — is not settled by the note and is held for the maintainer's decision on [Decision] The organization administrator's assignment boundary: an assignable set with a wildcard read and no row scope reaches other organizations' SCIM rows in a deployment with no platform baseline #24164. No fourth condition is written;
  4. tightens "refuses from every non-system writer" to "refuses from every writer": reserved_identity_position takes no provenance exemption (sys-user-position.object.ts:225–:265).

The rule paragraph of the note (the three conditions and the by-name exclusions) is unchanged; the denylist lives in the measured paragraph, as the review prescribed.

Mechanism assumptions, measured on origin/main eda6f14b7b

  • A1 — holds. ADR-0090 keeps amendments as dated · **Amended** (…) status-line entries (:4–:8), and a superseding change also gets a dated blockquote in the decision itself (D11's > **Amended 2026-10-14 …**; D3 / D5 / D8 carry > **Note (2026-10-14) …**). The 2026-10-15 D12 amendment is status-line only. This PR uses both: the status line, and an Amended 2026-10-17 blockquote at the end of D12 (D12 now at :515).
  • A2 — holds. ADR-0131 D3's assignment paragraph is :322–:328. ADR-0131 itself already uses > **Note (2026-10-14) — …** blockquotes (:66); the note follows that form. No status-line entry was added to ADR-0131: the note changes no D3 text (its inline 2026-10-10 withdrawal at :315 set the precedent of a note without a status entry).
  • A3 — holds, with the exact fields. packages/spec/src/security/capabilities.ts:33 declares PlatformCapability.scope: 'platform' | 'org', and every one of the 10 PLATFORM_CAPABILITIES rows sets it (platform: manage_users, manage_metadata, manage_platform_settings, setup.access, studio.access; org: manage_org_users, setup.write, manage_sharing, view_all_audit_log, manage_mailboxes). A package capability declares it through CapabilityDeclarationSchema.scope (:189), z.enum(['platform', 'org']).default('platform'). The superuser bits are ObjectPermissionSchema.viewAllRecords / modifyAllRecords on the objects['*'] entry; the delegation key is PermissionSetSchema.adminScope (permission.zod.ts:1022). All three conditions are readable from declarations alone.
  • A4 — measured; the conditions settle five of the eight shipped sets, and ruling item 4's denylist names the other three (the D10 agent ceilings). Table below. The shipped roster is the eight sets in packages/plugins/plugin-security/src/objects/default-permission-sets.ts, held equal to SHIPPED_PERMISSION_SET_NAMES (packages/cli/src/utils/organization-ownership-inventory.ts:215) by that file's test; a repo-wide search for PermissionSetSchema.parse( / definePermissionSet( outside tests finds no other shipped set (fixtures in packages/verify and packages/qa only).
  • A5 — holds. No heading was added (both notes are blockquotes), so no anchor moved; pnpm check:adr-anchors exits 0 on the final head.

Shipped permission sets against the three conditions

set (a) no * superuser bit (b) no adminScope (c) org capabilities only verdict
admin_full_access no — viewAllRecords + modifyAllRecords yes no — manage_users, manage_metadata, manage_platform_settings, setup.access, studio.access outside
organization_admin no — viewAllRecords + modifyAllRecords yes no — setup.access outside (also by name)
organization_admin_no_bypass yes — the derivation drops both bits yes no — setup.access outside (also by name)
member_default yes — no * entry at all yes yes — none inside
viewer_readonly yes — allowRead only yes yes — none inside (it carries the SCIM row scope itself)
mcp_agent_data_read yes — allowRead only, no row scope yes yes — none outside (denylist) — mis-settled
mcp_agent_data_write yes — CRUD bits only, no row scope yes yes — none outside (denylist) — mis-settled
mcp_agent_restricted yes — objects: {} yes yes — none outside (denylist) — by kind

Shipped positions: the four ADR-0068 D2 identity names (platform_admin, org_owner, org_admin, org_member) are refused on sys_user_position.position for every writer by the object validation reserved_identity_position (sys-user-position.object.ts:256, no provenance exemption), and the two anchors by the gate's unconditional anchor refusal (delegated-admin-gate.ts:298–:312). Neither is a question for this boundary; the note records them as unchanged.

Precisions the contract review should read

Each narrows; none widens the ruled rule.

  1. "Tenant-level" is the platform administrator alone. ADR-0095 names the organization administrator's posture rung TENANT_ADMIN; the note says in so many words that D12's "tenant-level" is the first kind only, so the two vocabularies cannot be read into each other.
  2. Condition (c) is fail-closed on an undeclared scope. A package declaration that omits scope defaults to 'platform', and a systemPermissions name no declaration carries is not an org capability.
  3. "Each set a position carries" includes the position-name fold. The resolver requests [...positions, ...explicitPermissionSets] (security-plugin.ts:6542), so a held position also grants the permission set of its own name; the note counts that set while the fold stands. Without this a position could carry an outside set past the boundary.
  4. Both organization-administrator variants are outside by name. organization_admin_no_bypass is outside by (c) today only because setup.access is platform; the note also excludes it by name, which is what "conferred by membership grade only" requires.
  5. The three D10 agent ceilings are outside, on ruling item 4's denylist. The conditions place them inside and so mis-settle them: an agent ceiling's bound is the delegating principal's own grants (D10 rule 1), not the organization wall. Held by a person as a direct grant there is no intersection, and the two reading ceilings carry no row scope on the seven Layer-0-inert SCIM projection tables; in a walled deployment with no platform baseline (fallbackPermissionSet: null) they read every organization's rows there, a read the granting organization administrator does not hold (its explicit per-object entries keep the _self / _none scope). mcp_agent_restricted is named by kind. The general case — any authored set with a '*' read and no row scope on a Layer-0-inert readable object, in a no-baseline deployment — is not settled by the conditions or by a shipped-set denylist; the note holds it for the maintainer's decision on [Decision] The organization administrator's assignment boundary: an assignable set with a wildcard read and no row scope reaches other organizations' SCIM rows in a deployment with no platform baseline #24164 and writes no fourth condition.
  6. Create and remove only — the ruling's allowCreate / allowDelete on, allowEdit off.

Gates (final head 61c8a5d34)

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 19 commands from the tree at 61c8a5d34 (change set: the two ADR files, +76 / -0 against the base; the same 19 on every head of this PR). Each ran on 61c8a5d34 with the exit code captured before any pipe:

  • node scripts/check-adr-links.mjs :: exit 0 — "769 relative link destination(s) under docs/adr/ resolve"; --self-test :: exit 0
  • node scripts/check-adr-symbol-anchors.mjs :: exit 0 — "2353 anchors across 142 records resolve"; --self-test :: exit 0
  • node scripts/check-ci-filter-parity.mjs :: exit 0
  • node scripts/check-closing-keyword-parity.mjs :: exit 0; --self-test :: exit 0
  • node scripts/check-comment-mask-corpus.mjs :: exit 0 — "9404 files, 0 disagree"
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 (its prerequisites @objectstack/formula and @objectstack/lint built first under the verify lock, pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2, VERDICT command-exit 0; on the first head the gate had answered exit 3, PREREQUISITE NOT MET, before that build)
  • pnpm check:adr-anchors :: exit 0 — "check-adr-anchors: OK (70 anchored file(s), every governing ADR still referenced; 136 decision number(s) …; 47869 citation(s) across 6347 file(s) resolve …)"
  • pnpm check:cross-package-test-inputs, check:doc-authoring, check:driver-memory-census, check:gitlink-declared, check:nul-bytes, check:pm-governed-merges, check:pm-prior-rulings, check:refd-timer-probe, check:watch-hint-literal :: exit 0 each

--ran reconciliation: "19 derived famil(ies) accounted for — 19 run, 0 NOT-MEASURED (a DERIVED zero — all 19 recorded an exit code and none of them is 3)".

node scripts/pm/check-governed-merges.mjs --branch claude/issue-24135-adr-0090-d12-org-admin-boundary (exit 3, the governed code): "GOVERNED — a human merge is the review record for this PR", "landing tier: H(人合)", docs/adr/** ×2, under the 3000-line threshold.

Everything else CI runs on this PR is CI's; nothing was re-run locally. No package builds or tests are owed: the diff touches no package.

Acceptance notes

Read-only inferences from the measurement above, not reproduced and not filed:

维护者速读(草稿)

改了什么

在权限模型 ADR-0090 的 D12(管理权限)里加了一条带日期的修订,并在 ADR-0131 D3 下加了一行注记,纯文字,不改代码、不加 spec 键。修订把管理者分成三类:租户级(平台管理员)、组织级(组织的 owner / admin,自动获得组织管理员权限集)、委托级(带 adminScope 的人)。组织级管理员可以给本组织成员增加或移除岗位与权限集,但只限"被组织墙约束住"的集:不带通配超级用户位、不带 adminScope、所含能力全部是组织级。admin_full_access、平台级集、组织管理员集本身、任何委托集都不能分;组织管理员身份只能由成员资格(owner / admin)获得,不能靠分配复制。本轮按席位复核意见补了一条:三个给 AI 代理用的 MCP 上限权限集列入裁决预留的黑名单,组织管理员不能把它们分给人。

为什么改

这是您 10 月 17 日对 #23960 的裁决 B(「同意」)的第一步。ADR-0131 D3 承诺"组织的管理员给本组织成员分配",但代码为了防止组织管理员给自己绑 admin_full_access 越过组织墙,把两张分配表设成了只读;缺的正是"组织管理员能分配哪些"这条边界。边界先写进 ADR,代码卡 #24136(被本卡阻塞)才能开工。实测出货的 8 个权限集:三条件能判定 5 个(2 个在内、3 个在外);剩下 3 个 MCP 代理上限集按三条件看似在内,但它们本来只配给 AI 代理、靠委托人自己的权限收窄,直接分给人时在"不配平台基线"的部署里能读到其他组织的 SCIM 身份数据,而分配它的组织管理员自己都读不到——所以按裁决第 4 条列入黑名单。

风险与代价(含回滚)

只改两份 ADR 的文字,今天的产品行为不变:两张分配表仍然只读,直到 #24136 落地。规则措辞上的几处精确化全部是收紧、没有放宽:未声明范围的能力按平台级算;岗位按名字带出的同名权限集也算"岗位携带的集";无墙部署用的 organization_admin_no_bypass 也按名字排除;三个 MCP 上限集列入黑名单。还有一个更一般的口子没有在本 ADR 里定:任何自建的"通配只读、没有行级范围"的权限集,在不配平台基线的部署里同样能碰到那几张没有组织列的 SCIM 表——是否加第四个条件,还是把保证写成"以平台基线为前提",已单独立成待您决策的 #24164,本 PR 不替您决定。回滚:直接 revert 本 PR,不涉及数据和发布。

席位意见

你要做的(一个动作)

席位复核通过后,在本 PR 上点 Approve。


Generated by Claude Code

…ment boundary; ADR-0131 D3 notes it

Ruling of record 6228728551 (letter B): an organization administrator creates
and removes sys_user_position / sys_user_permission_set rows for its own
members when every distributed set has no wildcard superuser bit, no
adminScope and only scope 'org' capabilities. D12 now names three kinds of
administrator (tenant-level, organization-level, delegated); strict
containment and the everyone / guest anchors are unchanged. The eight
shipped sets are classified in the note; none is left unsettled, so the
ruling's fallback denylist names no set.

Claude-Session: https://claude.ai/code/session_01HSDGREvqxQUt7K7pjHXMyC
Co-authored-by: Claude <noreply@anthropic.com>
The shipped organization_admin still keeps the assignment tables read-only
until the boundary is enforced; the ADR-0131 note names whose admins it means.

Claude-Session: https://claude.ai/code/session_01HSDGREvqxQUt7K7pjHXMyC
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Oct 17, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 17, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b0173e29c98b9047b26d9ae74a6f378343ec5393
Local-runs: none
Scope: docs/adr/0090-permission-model-v2-concept-convergence.md, docs/adr/0131-total-organization-ownership-no-null-organization-id.md + packages/spec/src/security/capabilities.ts, packages/spec/src/security/permission.zod.ts, packages/spec/src/identity/eval-user.zod.ts, packages/spec/src/ai/mcp.zod.ts, packages/spec/src/migrations/entries/semantic/18.walled-catalog-tenant-writes-retired.ts, packages/plugins/plugin-security/src/objects/default-permission-sets.ts, packages/plugins/plugin-security/src/objects/sys-user-position.object.ts, packages/plugins/plugin-security/src/delegated-admin-gate.ts, packages/plugins/plugin-security/src/security-plugin.ts, packages/plugins/plugin-security/src/permission-evaluator.ts, packages/plugins/plugin-security/src/app-default-permission-set.ts, packages/plugins/plugin-security/src/auto-org-admin-grant.ts, packages/platform-objects/src/identity/sys-scim-*.object.ts (seven), packages/cli/src/utils/organization-ownership-inventory.ts, scripts/platform-object-tenancy-census.json, docs/adr/0095 (rungs), docs/adr/0068 (D2), docs/adr/0105 (D4)

Inputs read: card #24135 body and both comments (claim 6232009241, os-dev report 6232264546); ruling 6228728551 on #23960 and the director deltas 6228146260, 6228658787; PR #24163 body, file list (2 files, +68/-0) and the net diff against main at the head; the 42 check-runs on the head. PR is draft, head repo = base repo, Tier H (docs/adr/**).

① Derived judgments

The ruled boundary (items 1, 3, 4) against the amended D12 text.

  • Item 1 is stated exactly, no wider: holder of organization_admin / organization_admin_no_bypass for one organization and not a platform administrator; creates and removes sys_user_position / sys_user_permission_set rows for that organization's members; every distributed set (the granted set, or each set the assigned position carries) meets (a) no objects['*'] viewAllRecords / modifyAllRecords, (b) no adminScope, (c) only scope: 'org' capabilities; admin_full_access, platform-capability sets, organization_admin in both variants and every delegated set outside; status by membership grade only (auto-org-admin-grant.ts). Strict containment and the anchors unchanged. The three kinds (tenant-level / organization-level / delegated) are the ones delta 4 named. The pre-existing D12 text is not rewritten; status-line entry plus dated blockquote is the ADR's own convention (D11's 2026-10-14 note is the precedent).
  • Item 3 is stated exactly: no spec key; the v18 entry's replacement text (18.walled-catalog-tenant-writes-retired.ts:23-:29, "an organization's administrators assign it to their own members through sys_user_position and sys_user_permission_set") stands and the note says it becomes true. Clause-②: no on the PR.
  • Item 4 is cited but applied wrongly — see the D10 ceilings below. The text says the fallback denylist "names no set at this date". It must name three.

The six "precisions" of the PR body.

  1. Tenant-level = the platform administrator alone, with the ADR-0095 TENANT_ADMIN name clash called out — right. ADR-0095 :183-:184 ranks PLATFORM_ADMIN above TENANT_ADMIN; eval-user.zod.ts names organization_admin as the TENANT_ADMIN rung's source; ADR-0068 D2 maps platform_admin to the unscoped admin_full_access grant. A vocabulary fix, neither wider nor narrower than delta 4's 「租户级(平台管理员)」.

  2. Condition (c) fail-closed on an undeclared scope — right and narrowing. CapabilityDeclarationSchema.scope is z.enum(['platform','org']).default('platform') (capabilities.ts:189); all 10 PLATFORM_CAPABILITIES rows carry scope (5 platform: manage_users, manage_metadata, manage_platform_settings, setup.access, studio.access; 5 org: manage_org_users, setup.write, manage_sharing, view_all_audit_log, manage_mailboxes); a name no declaration carries is not org.

  3. "Each set a position carries" includes the position-name fold — right and narrowing. security-plugin.ts:6542 requests [...positions, ...explicitPermissionSets], so a held position also resolves the set of its own name. Verified in the same read: the gate's setsBoundToPosition (delegated-admin-gate.ts:1086-:1099) reads only definition.permissionSets and does not see that set — the ADR text now obliges plugin-security: organisation admins assign positions and permission sets to their own members inside the D12 boundary — gate branch, overrides, explain and my-delegable-scope, three-posture acceptance (ruling B on #23960) #24136's third branch to count it (acceptance note 1 is true).

  4. Both organization-administrator variants outside by name — right and narrowing. deriveWallLessOrgAdmin (default-permission-sets.ts:1486) drops only the two superuser bits and carries systemPermissions: ['manage_org_users','setup.access','setup.write','manage_mailboxes'] over, so the variant is outside by (c) today (setup.access is platform) and by name whatever a scope later becomes — the by-name exclusion is what "conferred by membership grade only" requires.

  5. The three D10 agent ceilings inside by the conditions — wrong as a classification for this boundary. Mechanically the conditions place them inside (no superuser bit, no adminScope, no systemPermissions), but the ruling's premise is 「只能分配被组织墙约束的目录项」 and these sets are not wall-bounded on their own. Judged on the code:

    • mcp_agent_data_read is objects: { '*': { allowRead: true } } with no rowLevelSecurity (default-permission-sets.ts:1427-:1435); mcp_agent_data_write adds CRUD bits and denyWritesOnManagedObjects() (:1437-:1450), still no row scope. mcp.zod.ts:158-:160 declares them "pure capability ceilings — NO row-level security. All row/owner/tenant narrowing comes from the delegating user's own sets on the other side of the intersection" (D10 rule 1). Held by a person as a direct grant there is no intersection: sets merge most-permissively, so the ceiling is a floor.
    • resolveObjectPermission (permission-evaluator.ts:149-:160) applies a '*' wildcard to every non-private object. The seven SCIM projection objects (platform-objects/src/identity/sys-scim-*.object.ts) declare no access posture and no requiredPermissions, so the wildcard read reaches them.
    • Layer 0 is inert on all seven: scripts/platform-object-tenancy-census.json records tenantField: null, reach: out for sys_scim_user, sys_scim_subject, sys_scim_projection_grant, sys_scim_identity_tombstone, sys_scim_group, sys_scim_group_member, sys_scim_connection_binding; the member_default gives every authenticated member read on sys_scim_user / sys_scim_group with no row policy and no tenant column: one organization's IdP-provisioned users (emails, names) are readable from any other #20001 header (default-permission-sets.ts:105-:170) says the same.
    • Layer 1 starts null and stays null when no policy is collected (security-plugin.ts:7218-:7221); collectRLSPolicies (:8998-:9035) gathers policies across ALL held sets, so the only thing that bounds a wildcard reader on these tables is another held set's scimProjectionRowScope (_self / _none). That scope is spread into member_default, viewer_readonly and organization_admin (and its derived variant) — not into the ceilings.
    • The human baseline is member_default by default and [] under fallbackPermissionSet: null — "the one escape hatch", a supported deployment option (app-default-permission-set.ts:40-:57; security-plugin.ts:1505-:1514). The member_default gives every authenticated member read on sys_scim_user / sys_scim_group with no row policy and no tenant column: one organization's IdP-provisioned users (emails, names) are readable from any other #20001 header designs for exactly that deployment ("a deployment that composes no platform baseline resolves an org admin's sets without member_default").
    • The granting organization administrator itself does NOT hold that read: denyWritesOnManagedObjects() gives organization_admin an explicit per-object { allowRead: true } entry for every SCIM table, the explicit entry wins over the wildcard (resolveObjectPermission), so its viewAllRecords bypass does not reach them and the _self / _none scope applies — deliberately, "no org-admin admission" (:662-:665).
    • Nothing refuses a person being granted an MCP ceiling: the three names are referenced outside tests only in default-permission-sets.ts, explain-engine.ts (doc), mcp.zod.ts (scope mapping) and the CLI inventory; they resolve by name like any other set through sys_user_permission_set.

    Conclusion: on a walled posture (group / isolated) with fallbackPermissionSet: null, a member whom an organization administrator grants mcp_agent_data_read or mcp_agent_data_write reads every organization's SCIM projection rows — identity-provider records of other tenants — a read the grantor does not hold. That is a real cross-organization read path, and the amended text as written authorizes an organization administrator to open it. Under the default baseline the path is closed by member_default's carve-outs, but the ADR rule is deployment-independent and classifies the set inside categorically, and plugin-security: organisation admins assign positions and permission sets to their own members inside the D12 boundary — gate branch, overrides, explain and my-delegable-scope, three-posture acceptance (ruling B on #23960) #24136's gate will enforce the text as written. The dev's acceptance note ("Carrier: none", "not a classification question for this boundary") is refused.

    The instrument: ruling item 4's denylist is the right instrument for the three shipped ceilings, within the ruling and without a box visit — they are a shipped bundle the three conditions mis-settle, because their declared bound is another principal's grants (D10 rule 1), not the organization wall; the PR body's own precision 5 pre-names this instrument. The D12 note's measured paragraph must say the denylist names mcp_agent_data_read, mcp_agent_data_write and mcp_agent_restricted (the last by kind: an agent ceiling is not a person's set), with the reason above, and "names no set at this date" must go. The general class the same reasoning exposes — any authored set carrying a '*' read and no row scope on a Layer-0-inert readable object, in a no-baseline deployment — is NOT settled by the three conditions and no shipped-set denylist can settle it; whether the rule takes a fourth condition or states its guarantee as conditional on the platform baseline is a permission-boundary term only the maintainer can add. That question goes back to the maintainer (needs-user-decision, carried by the seat) before plugin-security: organisation admins assign positions and permission sets to their own members inside the D12 boundary — gate branch, overrides, explain and my-delegable-scope, three-posture acceptance (ruling B on #23960) #24136's gate ships; the ADR text must not decide it silently, and this record does not.

  6. Create and remove only — right; ruling item 2's allowCreate / allowDelete on, allowEdit off, restated as the right's shape.

The shipped-set table against the code (default-permission-sets.ts, eight sets, the roster held equal to SHIPPED_PERMISSION_SET_NAMES at organization-ownership-inventory.ts:215; no other non-test PermissionSetSchema.parse( / definePermissionSet( outside packages/spec, packages/qa and packages/verify fixtures):

  • admin_full_access — (a) no (ADMIN_FULL_ACCESS_CAPABILITIES: wildcard with both bits), (b) yes, (c) no (manage_users, manage_metadata, manage_platform_settings, setup.access, studio.access; it also carries four org capabilities, immaterial) — outside. Row right.
  • organization_admin — (a) no (:444-:450), (b) yes, (c) no (setup.access, :487) — outside, also by name. Row right.
  • organization_admin_no_bypass — (a) yes (derivation drops both bits), (b) yes, (c) no (setup.access) — outside, also by name. Row right.
  • member_default — (a) yes (no '*' entry, member_default's * wildcard object grant (C/R/E) union-merges into every org member — app-side explicit-allow object gates are erased on three axes #5491), (b) yes, (c) yes (no systemPermissions) — inside. Row right.
  • viewer_readonly — (a) yes (allowRead only), (b) yes, (c) yes — inside; it carries scimProjectionRowScope itself, so it is wall-bounded on its own. Row right.
  • mcp_agent_data_read, mcp_agent_data_write, mcp_agent_restricted — the three cells are mechanically right; the verdict column is wrong for this boundary (precision 5).

Other factual sentences of the new text, checked: PlatformCapability.scope at capabilities.ts:33 — true; PermissionSetSchema.adminScope at permission.zod.ts:1022 — true; superuser bits are ObjectPermissionSchema.viewAllRecords / modifyAllRecords (:485, :508) — true; isTenantAdmin is a wildcard modifyAllRecords holder reading the walled organization_admin as tenant-level and the variant as no administrator (delegated-admin-gate.ts:256-:263, :332) — true; the anchors are refused unconditionally on sys_user_position insert/update (:296-:310) — true; D9 binds anchors by admin confirmation, never a package — true; reserved_identity_position at sys-user-position.object.ts:256 — true, with one wording point: the validation "takes no provenance exemption" and refuses every writer, so "from every non-system writer" under-states it (true as written; tighten if the text is touched). "Assigns any set to anyone" for the tenant-level kind is the PLATFORM_ADMIN rung's reach on posture-permitting objects; acceptable. ADR-0131's note states the same three conditions and points at the D12 amendment — consistent with D12's text; it inherits the item-4 correction only by reference, no second edit needed.

② Semver level

ADR prose only; two files under docs/adr/**; no package source, no packages/spec key, no schema, no test, no migration text. skip-changeset is the right label and Clause-②: no the right declaration. Check Changeset is success on the head.

③ Boundary flags

Prescription for the next head: in the D12 note's measured paragraph, name the three D10 ceilings in the ruling's item-4 denylist with the reason (an agent ceiling's bound is the delegator's grants, not the wall; held by a person it carries no row scope on the Layer-0-inert projection tables), drop "names no set at this date", and move the three from "Inside" to "Outside (denylist)". Keep everything else. The seat files the general-class question for the maintainer in the same act.

Implemented-by: claude/issue-24135-adr-0090-d12-org-admin-boundary
Reviewed-by: session_01HSDGREvqxQUt7K7pjHXMyC

VERDICT: FAIL


Generated by Claude Code

…eilings

The contract review on the previous head found the three conditions
mis-settle the shipped agent ceilings: their bound is the delegating
principal's grants (D10 rule 1), not the organization wall, so held by a
person in a walled deployment with no platform baseline the reading ceilings
reach every organization's rows on the Layer-0-inert SCIM projection tables.
The ruling's fallback denylist now names mcp_agent_data_read,
mcp_agent_data_write and, by kind, mcp_agent_restricted. The general case
(an authored wildcard read with no row scope on a Layer-0-inert object, no
baseline) is held for a maintainer decision and not settled here. The
reserved identity names are refused from every writer.

Claude-Session: https://claude.ai/code/session_01HSDGREvqxQUt7K7pjHXMyC
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 61c8a5d34326e6c62468d2af9c01f9605bfdc3b7
Local-runs: none
Scope: docs/adr/0090-permission-model-v2-concept-convergence.md, docs/adr/0131-total-organization-ownership-no-null-organization-id.md + packages/spec/src/security/capabilities.ts, packages/spec/src/security/permission.zod.ts, packages/spec/src/identity/position.zod.ts, packages/spec/src/identity/eval-user.zod.ts, packages/spec/src/ai/mcp.zod.ts, packages/spec/src/migrations/entries/semantic/18.walled-catalog-tenant-writes-retired.ts, packages/plugins/plugin-security/src/objects/default-permission-sets.ts, packages/plugins/plugin-security/src/objects/sys-user-position.object.ts, packages/plugins/plugin-security/src/objects/reserved-identity-names.ts, packages/plugins/plugin-security/src/objects/rbac-objects.test.ts, packages/plugins/plugin-security/src/delegated-admin-gate.ts, packages/plugins/plugin-security/src/security-plugin.ts, packages/plugins/plugin-security/src/permission-evaluator.ts, packages/plugins/plugin-security/src/app-default-permission-set.ts, packages/plugins/plugin-security/src/auto-org-admin-grant.ts, packages/platform-objects/src/identity/sys-scim-*.object.ts (seven), packages/cli/src/utils/organization-ownership-inventory.ts, scripts/platform-object-tenancy-census.json, docs/adr/0068 (D2), docs/adr/0095 (rungs, D3), docs/adr/0105 (D4), ADR-0090 D9 / D10 / D11 / D12 as they stand at the head

Inputs read: card #24135 body and all three comments (claim 6232009241, os-dev report 6232264546, patch-round report 6232545368 with its pr_body_replacement); ruling 6228728551 on #23960 and the director deltas 6228146260, 6228658787; the previous record 6232429140 (FAIL on b0173e29c) and its prescription; decision card #24164 (body; no comments at read time); PR #24163 body (verified byte-equal to the patch-round report's pr_body_replacement), file list (2 files, +76 / -0) and the net diff against main at the head, plus this round's delta b0173e29c..61c8a5d34; the 46 check-runs on the head. PR is draft, head repo = base repo, mergeable_state: clean, labels documentation, size/s, skip-changeset. Tier H (docs/adr/**). Every judgment below is re-made on 61c8a5d34.

① Derived judgments

The ruled boundary (items 1, 3, 4) against the amended D12 text at this head.

  • Item 1 — stated exactly, no wider. Kind 2 of the note: a holder of organization_admin (walled posture) or organization_admin_no_bypass (wall-less posture, ADR-0105 D4 :183–:191) for one organization, not a platform administrator; creates and removes sys_user_position / sys_user_permission_set rows of that organization for its members; every distributed set (the granted set, or each set the assigned position carries) meets (a) no objects['*'] viewAllRecords / modifyAllRecords (permission.zod.ts:485, :508), (b) no adminScope (:1022), (c) only scope: 'org' capabilities (capabilities.ts:33, :189); admin_full_access, every platform-capability set, organization_admin in both variants and every delegated set outside; status conferred by membership grade only (auto-org-admin-grant.ts:4–:8, :272: an owner or admin sys_member role). Strict containment and the everyone / guest anchors unchanged. The three kinds — tenant-level, organization-level, delegated — are the ones delta 4 named 「租户级 / 组织级 / 委托级」. The pre-existing D12 text is not rewritten; status-line entry plus dated blockquote is the ADR's own convention (D11's 2026-10-14 note). The status-line entry (:9) summarises the rule and points at the note; it omits the denylist, which is right for a summary that says "See the note under D12".
  • Item 3 — stated exactly. No packages/** path in the diff; the v18 entry's replacement text (18.walled-catalog-tenant-writes-retired.ts:23–:29, "an organization's administrators assign it to their own members through sys_user_position and sys_user_permission_set, which are unchanged") stands and the note says it becomes true. Clause-②: no on the PR body.
  • Item 4 — now applied. The measured paragraph names ruling item 4's denylist: mcp_agent_data_read, mcp_agent_data_write and, by kind, mcp_agent_restricted; "names no set at this date" is gone, replaced by "therefore names those three sets"; the growth clause ("a set shipped later that the conditions cannot settle joins that denylist by an amendment here") is kept; Inside is now member_default and viewer_readonly alone.

The previous record's prescription, carried out exactly. This round's delta (b0173e29c..61c8a5d34) touches ADR-0090 only: the measured paragraph is rewritten as prescribed (the three ceilings moved from Inside to Outside (denylist), the reason given, "names no set at this date" dropped), one word in the Unchanged paragraph ("non-system" removed), and one sentence citing #24164. The status line, the rule paragraph (three conditions, position fold, by-name exclusions), kind 1, kind 3 and the ADR-0131 note are byte-identical to the previous head. "Keep everything else" held. The two changes beyond the prescription's three items are both ones the previous record itself asked for: the "every writer" tightening ("tighten if the text is touched") and the hand-off of the general class to the maintainer ("the ADR text must not decide it silently"); the text cites the decision card the seat filed.

The new reason sentence, against the code.

  • "An agent ceiling's bound is the delegating principal's own grants (D10 rule 1), not the organization wall" — D10 rule 1 (:407–:410, intersection never union); mcp.zod.ts:144–:160 ("pure capability ceilings — object CRUD bits only, NO row-level security. All row/owner/tenant narrowing comes from the delegating user's own sets"); the resolver gives an agent the restricted set as its floor and never the human baseline (security-plugin.ts:6551–:6558). True.
  • "The two reading ceilings" — mcp_agent_data_read is '*': { allowRead: true } and nothing else (default-permission-sets.ts:1427–:1435); mcp_agent_data_write is '*' with the four CRUD bits plus denyWritesOnManagedObjects(), which gives it an explicit { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false } entry on every BETTER_AUTH_MANAGED_OBJECTS name, the seven SCIM tables included (:59–:92, :1437–:1450); neither declares rowLevelSecurity. mcp_agent_restricted is objects: {} (:1452–:1459) and reads nothing. So exactly two of the three read, and "the two reading ceilings" is the right count — the dev's deviation 2 is right.
  • "Carry no row scope on the Layer-0-inert SCIM projection tables (no organization column)" — scripts/platform-object-tenancy-census.json records tenantField: null, reach: out for all seven (sys_scim_user, sys_scim_subject, sys_scim_projection_grant, sys_scim_identity_tombstone, sys_scim_group, sys_scim_group_member, sys_scim_connection_binding); the member_default gives every authenticated member read on sys_scim_user / sys_scim_group with no row policy and no tenant column: one organization's IdP-provisioned users (emails, names) are readable from any other #20001 header (default-permission-sets.ts:106–:170) says the same; the seven object files declare managedBy: 'better-auth' and no access posture or requiredPermissions, so the '*' read reaches them (resolveObjectPermission, permission-evaluator.ts:149–:160). True.
  • "In a walled deployment with no platform baseline they read every organization's rows there" — composeHumanBaselinePermissionSets(null) returns [] (app-default-permission-set.ts:40–:59, "null still means no baseline at all"); the resolver adds only this.baselinePermissionSets to a human request (security-plugin.ts:6573–:6578); in computeLayeredRlsFilter posturePermits is true for a better-auth-managed object and superuserBypass is false for a wildcard without the bits (:7167–:7175, superuserBypassSets permission-evaluator.ts:377–:392), so Layer 1 is collected from the held sets alone (collectRLSPolicies :8998–:9037), an empty collection compiles to null (:7219–:7221, :7421–:7437: the deny sentinel is set only when a policy was dropped for a missing field), and Layer 0 has no column to filter on. True.
  • "A read the granting organization administrator does not hold" — organization_admin resolves the seven tables through its explicit denyWritesOnManagedObjects() entries (:473), which carry no superuser bit; superuserBypassSets judges per object through resolveObjectPermission, where the explicit entry wins over the wildcard, so the wildcard's viewAllRecords does not skip Layer 1 there; scimProjectionRowScope() is spread into the set (:665) — _self on the four user-naming tables, _none (id == null) on the other three — with the comment "deliberately no org-admin admission — Layer 0 is inert on them" (:661–:664). The derived variant carries the same entries and policies (deriveWallLessOrgAdmin, :1486–:1505). True.
  • "The conditions place them inside and so mis-settle them" — mechanically true: no superuser bit, no adminScope, no systemPermissions on any of the three.
  • mcp_agent_restricted "by kind: an agent ceiling is not a person's set" — mcp.zod.ts:167–:171 declares it the no-object-access floor that keeps an agent's resolved list non-empty so enforcement fails closed; it grants a person nothing, so denylisting it costs nothing and keeps the D10 roster (MCP_AGENT_PERMISSION_SETS) whole. Within ruling item 4's instrument.

The general-case sentence, against #24164. The note says: "The general case — an authored set with a '*' read and no row scope on a Layer-0-inert readable object, in a deployment with no platform baseline — is not settled by this note: it is held for the maintainer's decision on #24164." #24164 (open, needs-user-decision, no comments) asks exactly that: 「以后自建的同形状集怎么办」, options A (a fourth declaration-checkable condition), B (the right conditional on the baseline), C (wall the tables), D (document); its 「已处理的部分」 is the three shipped ceilings on the item-4 denylist in this PR, which matches the text. The sentence writes no fourth condition, states no guarantee as conditional on the baseline, does not name an option, and does not say what a gate does with such a set meanwhile beyond the ruled three conditions — ruling B as the maintainer gave it, not a decision of the note. It holds the question without deciding it.

The reserved_identity_position wording change. "which sys_user_position.position refuses from every writer" — the object validation (sys-user-position.object.ts:250–:266) is a script validation with no isSystem or provenance carve-out, and the comment above it says so in words: "the refusal takes no provenance exemption: unlike sys_position, this object has no legitimate seeder to exempt" (:233–:243). The names are BUILTIN_IDENTITY_NAMES (eval-user.zod.ts:69–:74: platform_admin, org_owner, org_admin, org_member), ADR-0068 D2's four. "From every writer" is true and tighter than the previous "every non-system writer".

The six precisions of the PR body, re-checked.

  1. Tenant-level = the platform administrator alone — right. ADR-0095 :183–:184 ranks PLATFORM_ADMIN 3 above TENANT_ADMIN 2; :217 derives TENANT_ADMIN from the org-admin grants; eval-user.zod.ts:161–:189 names organization_admin and its variant as that rung's sources; ADR-0068 D2 maps platform_admin to the unscoped admin_full_access grant. Calling the clash out narrows nothing and widens nothing.
  2. Condition (c) fail-closed on an undeclared scope — right and narrowing. CapabilityDeclarationSchema.scope is z.enum(['platform', 'org']).default('platform') (capabilities.ts:189); defineCapability parses through that schema (:231–:233); the 10 PLATFORM_CAPABILITIES rows all carry scope — platform: manage_users, manage_metadata, manage_platform_settings, setup.access, studio.access; org: manage_org_users, setup.write, manage_sharing, view_all_audit_log, manage_mailboxes.
  3. "Each set a position carries" includes the position-name fold — right and narrowing. security-plugin.ts:6542 requests [...positions, ...explicitPermissionSets]; PositionSchema.permissionSets (position.zod.ts:106) is the declared list. Verified beside it: setsBoundToPosition (delegated-admin-gate.ts:1086–:1099) reads only definition.permissionSets — acceptance note 1 is true on the code.
  4. Both organization-administrator variants outside by name — right and narrowing. deriveWallLessOrgAdmin removes only viewAllRecords / modifyAllRecords from the '*' entry and carries systemPermissions: ['manage_org_users', 'setup.access', 'setup.write', 'manage_mailboxes'] over (:1486–:1505), so the variant is outside by (c) today (setup.access is platform) and by name whatever a scope later becomes.
  5. The three D10 agent ceilings outside on ruling item 4's denylist — right, as judged above. The general class is held for [Decision] The organization administrator's assignment boundary: an assignable set with a wildcard read and no row scope reaches other organizations' SCIM rows in a deployment with no platform baseline #24164 and no fourth condition is written.
  6. Create and remove only — right; ruling item 2's allowCreate / allowDelete on, allowEdit off, stated in the note as "The right is create and remove; an assignment row is not edited in place."

The classification table against the code (default-permission-sets.ts; eight sets = seven PermissionSetSchema.parse( literals at :392, :440, :675, :1233, :1427, :1437, :1452 plus the derived variant at :1493, equal to SHIPPED_PERMISSION_SET_NAMES, organization-ownership-inventory.ts:214–:217):

  • admin_full_access — (a) no (ADMIN_FULL_ACCESS_CAPABILITIES, eval-user.zod.ts:106–:158: '*' with both bits), (b) yes, (c) no (manage_users, manage_metadata, manage_platform_settings, setup.access, studio.access; four org capabilities beside them, immaterial) — outside. Row right.
  • organization_admin — (a) no (:444–:450), (b) yes, (c) no (setup.access, :487) — outside, also by name. Row right.
  • organization_admin_no_bypass — (a) yes (derivation drops both bits), (b) yes, (c) no (setup.access) — outside, also by name. Row right.
  • member_default — (a) yes (no '*' entry, :679–:680, member_default's * wildcard object grant (C/R/E) union-merges into every org member — app-side explicit-allow object gates are erased on three axes #5491), (b) yes, (c) yes (no systemPermissions) — inside. Row right.
  • viewer_readonly — (a) yes ('*' with allowRead only, :1236–:1242), (b) yes, (c) yes — inside; it spreads scimProjectionRowScope() itself (:1358), so it is wall-bounded on its own. Row right.
  • mcp_agent_data_read, mcp_agent_data_write — (a) yes, (b) yes, (c) yes; verdict outside (denylist), mis-settled. Rows right.
  • mcp_agent_restricted — (a) yes (objects: {}), (b) yes, (c) yes; verdict outside (denylist), by kind. Row right.

Shipped positions: the four identity names refused for every writer (above); the two anchors refused unconditionally on sys_user_position insert/update (delegated-admin-gate.ts:298–:312, "Applies to every caller, tenant admins included"). The note records both as unchanged — right.

Other factual sentences of the new text, checked. "The shipped organization_admin keeps both assignment tables read-only" — :474–:476, pinned by rbac-objects.test.ts:63–. "An unbounded one could bind admin_full_access, which the organization wall does not bound, to itself" — the anti-escalation comments :427–:431 and auto-org-admin-grant.ts:37–:42; admin_full_access resolves the PLATFORM_ADMIN posture that crosses Layer 0 on posture-permitting objects (ADR-0095 D3; security-plugin.ts:7176–:7202). Kind 1 "owns security-domain publishes (D7) and the everyone / guest anchors, and assigns any set to anyone" — the gate passes an isTenantAdmin caller to downstream CRUD (:332), whose wildcard grants every object; "any set" is about sets, and the positions nobody assigns (anchors, identity names) are named in the Unchanged paragraph. "ADR-0095 names the next kind's rung TENANT_ADMIN" — :184, :217. Kind 2's posture split — auto-org-admin-grant.ts:55–:79 (postureEnforcesWall), ADR-0105 D4 :189–:190. "No organization-level or delegated administrator binds [the anchors] and no caller assigns (D9)" — D9 :360–:381: a package suggests, the admin confirms; the gate refuses every assignment. "isTenantAdmin (a wildcard modifyAllRecords holder) reads the walled organization_admin as tenant-level today" — delegated-admin-gate.ts:256–:263, :332. "Of the eight permission sets … the three conditions settle five" — the roster above. All true.

ADR-0131 D3's note. Unchanged this round. It points at D12's 2026-10-17 amendment as the rule D3's sentence runs under and restates the three conditions as what an organization's admins assign. The restatement is a necessary condition — true of every set the boundary admits — and the denylist reaches D3 through "runs under that amendment's boundary", which is the note whole. Consistent; no second edit owed.

The dev's deviation 3 — is the pointer in the rule paragraph needed? No. The denylist is binding on #24136 as the text stands, for three reasons read on the head: (i) ruling item 4 itself makes the denylist part of the boundary ("the ADR card names an explicit denylist for that set instead of widening the rule"), and the note names the denylist's three members and its growth rule in the same dated amendment under D12 — a dated note is the ADR's rules text, and the "Measured on" heading frames the evidence, not the standing of the sentence "therefore names those three sets"; (ii) the rule paragraph's "Outside the boundary, by the conditions or by name:" list is not phrased as exhaustive and nothing in it admits a set the measured paragraph excludes, so the two paragraphs do not contradict; (iii) ADR-0131's note runs D3 under "that amendment's boundary", the whole note. The boundary is therefore stated exactly across the note: three conditions, by-name exclusions, item-4 denylist — the ruled boundary and nothing wider. The pointer would be a readability aid for #24136's implementer, not a correction; this record judges the head as it stands and does not require it. The seat can give #24136's implementer the same aid at no cost to this head by naming the denylist (and the position-name fold) in that card's dispatch text.

② Semver level

ADR prose only: two files under docs/adr/**, +76 / -0; no package source, no packages/spec key, no schema, no test, no migration text. skip-changeset is the right label and Clause-②: no the right declaration. Check Changeset is skipped on both of its instances on this head, which is what the label does.

③ Boundary flags

Implemented-by: claude/issue-24135-adr-0090-d12-org-admin-boundary
Reviewed-by: session_01HSDGREvqxQUt7K7pjHXMyC

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么

在权限模型 ADR-0090 的 D12(管理权限)里加了一条带日期的修订,并在 ADR-0131 D3 下加了一行注记。纯文字:不改代码,不加 spec 键。

修订把管理者分成三类:

  • 租户级(平台管理员);
  • 组织级(组织的 owner / admin,自动获得组织管理员权限集);
  • 委托级(带 adminScope 的人)。

组织级管理员可以给本组织成员增加或移除岗位与权限集,但只限「被组织墙约束住」的集,即同时满足三条:

  • 不带通配超级用户位;
  • 不带 adminScope;
  • 所含能力全部是组织级。

admin_full_access、平台级集、组织管理员集本身(两个变体)和任何委托集都不能分。组织管理员身份只能由成员资格(owner / admin)获得,不能靠分配复制。

另外,三个给 AI 代理用的 MCP 上限权限集列入您裁决第 4 条预留的黑名单,组织管理员不能把它们分给人。

为什么改

这是您 10 月 17 日对 #23960 裁决 B(「同意」)的第一步。ADR-0131 D3 承诺「组织的管理员给本组织成员分配」,但代码为了防止组织管理员给自己绑 admin_full_access 越过组织墙,把两张分配表设成了只读。缺的正是「组织管理员能分配哪些」这条边界。边界先写进 ADR,代码卡 #24136 才能照着实现。

实测出货的 8 个权限集,三条件能判定其中 5 个(2 个在内,3 个在外)。剩下 3 个 MCP 代理上限集,按三条件看似在内,但它们本是配给 AI 代理的:约束来自委托人自己的权限,不是组织墙。直接分给一个人时,在不配平台基线的部署里,它能读到其他组织的 SCIM 身份数据,而分配它的组织管理员自己都读不到。所以按裁决第 4 条把它们列入黑名单。

风险与代价(含回滚)

只改两份 ADR 的文字,今天的产品行为不变:两张分配表仍然只读,直到 #24136 落地。

措辞上的几处精确化全部是收紧,没有放宽:

  • 未声明范围的能力按平台级算;
  • 岗位按名字带出的同名权限集,也算「岗位携带的集」;
  • 无墙部署用的 organization_admin_no_bypass 也按名字排除;
  • 三个 MCP 上限集列入黑名单。

还有一个更一般的口子,本 ADR 没有定:任何自建的「通配只读、没有行级范围」的权限集,在不配平台基线的部署里,同样能碰到那几张没有组织列的 SCIM 表。是加第四个条件,还是把保证写成「以平台基线为前提」,已单独立成待您决策的 #24164,本 PR 不替您决定。

回滚:直接 revert 本 PR,不涉及数据和发布。

席位意见

建议批准。

你要做的(一个动作)

在 PR #24163 上点 Approve(os-zhuang 或 hotlong 任一账户)。批准后,席位负责翻 ready 并入队合并。

席位核对

ACCEPT · domain:spec seat 2 · huangyiirene · session session_01HSDGREvqxQUt7K7pjHXMyC · 2026-10-17T10:18Z · head 61c8a5d34326e6c62468d2af9c01f9605bfdc3b7

The seat's own reading, on top of the CONTRACT_REVIEW_TIER record 6232718234 (PASS on this head), which covers the whole diff: both files are governed rules text.

item reading
shape Draft, base main, head repo = base repo, mergeable_state clean. First line Fixes #24135; line-start Clause-②: no. No other closing keyword sits next to an issue number. Labels: documentation, size/s, skip-changeset.
file list 2 files, +76 / −0, both read in the diff: docs/adr/0090-permission-model-v2-concept-convergence.md (a status-line entry and one dated note under D12) and docs/adr/0131-total-organization-ownership-no-null-organization-id.md (one dated note under D3). No package, no packages/spec key, no migration text, no content/docs/releases/.
premise Ruling 6228728551 on #23960, letter B. The previous record's prescription (6232429140) was checked by the seat against the round delta b0173e29c..61c8a5d34, and the record confirms it was carried out exactly. The seat re-read three of the record's code claims at origin/main eda6f14b7b: mcp_agent_restricted is objects: {} (default-permission-sets.ts:1452–:1459); null "composes to []" (app-default-permission-set.ts:40); reserved_identity_position "takes no provenance exemption" (sys-user-position.object.ts:241–:243).
gates Check-runs on the head: 46, all completed, 35 success, 11 skipped, 0 failures. node scripts/pm/check-expected-skips.mjs --pr 24163: "11 skipped check-run(s), every one in the roster (exit 0)". The dev's 19 derived gate families all exit 0 on this head (--ran: 19 run, 0 NOT-MEASURED).
deviations accepted All as record 6232718234 answers them. Deviation 3 (no pointer from the rule paragraph to the denylist) is accepted: the denylist binds as the text stands. The seat carries the readability aid into #24136's dispatch instead.
out of scope Out-of-scope findings 1–3 (setsBoundToPosition blind to the position-name fold; isTenantAdmin's tenant-level reading of the walled set; docs/design/permission-model.md §10) go to #24136, and ride its dispatch order. Finding 4 (the general class) is #24164, in the decision box; #24136 carries Blocked-by: #24164 (6232459761).
landing Tier H: check-governed-merges.mjs --pr 24163 exits 3, "GOVERNED — a human merge is the review record for this PR", landing tier H. The PR stays draft and carries needs-user-decision; review is requested from os-zhuang and hotlong. On an APPROVED review from a GOVERNED_APPROVERS account, the seat runs the pre-landing checks, removes the label, flips ready and arms auto-merge. A person's direct merge is its own review record.

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 17, 2026 10:21
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 17, 2026
Merged via the queue into main with commit 7cda377 Oct 17, 2026
54 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-24135-adr-0090-d12-org-admin-boundary branch October 17, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants