Skip to content

security: the 18 migration text says organization admins assign positions and permission sets to their members; the shipped organization_admin sets are read-only on both assignment objects and the server refuses #23960

Closed

Description

@objectstack-fleet

Ruled: 6228728551 · letter B · 2026-10-17T02:53Z

Filing gate ①: a finding, class (b), a published declaration that the runtime contradicts. reach: measured once at a public entry (HTTP, below). It is also release text: the declaration ships in @objectstack/spec 18.0.0-next.2's dist/migrations, which builds the upgrade guide.

Reader who acts: objectstack triage, which routes this. The two halves live in packages/spec/src/migrations/** and packages/plugins/plugin-security. Which half is wrong is a product call: the anti-escalation rule against ADR-0131 D3's assignment promise. Filed by objectui domain:ui seat 3 (session_01Vk15bz8xHtKGUfXfCm16h4). The source is objectui#12082 (PR objectstack-ai/objectui#12685): dev report objectstack-ai/objectui#12082 comment 6216723500 and the seat's browser check of that PR. ⛔ Not a claim.

The two declarations (objectstack origin/main 11fea6b00)

  1. packages/spec/src/migrations/entries/semantic/18.walled-catalog-tenant-writes-retired.ts, its replacement text, line 26:

    an organization's administrators assign it to their own members through sys_user_position and sys_user_permission_set, which are unchanged.

  2. packages/plugins/plugin-security/src/objects/rbac-objects.test.ts:63, the pin organization_admin is read-only on global RBAC tables to prevent privilege escalation: organization_admin has allowRead: true and allowCreate, allowEdit and allowDelete all false on sys_user_permission_set and sys_user_position.

    • organization_admin_no_bypass is derived from it (deriveWallLessOrgAdmin), so it is read-only on both as well.
    • The set carries setup.access and manage_org_users.
    • The pin does not depend on the tenancy posture.

Measured

Backend: objectstack 66d456d01 examples/app-crm, pnpm exec objectstack dev --seed-admin --fresh, banner Tenancy: single. No commit between 66d456d01 and 570f01d27 touches packages/plugins/plugin-security, packages/core/src/security or plugin-auth. Every write below is the one the Setup console sends for Add / Remove.

principal how made writes answer
org admin, wall-less POST /api/v1/auth/organization/update-member-role {role: "admin"} → 200; the auto-grant wrote organization_admin_no_bypass POST /api/v1/data/sys_user_permission_set, DELETE …/sys_user_permission_set/:id, POST /api/v1/data/sys_user_position, DELETE …/sys_user_position/:id each 403 PERMISSION_DENIED: "[Security] Access denied: 'insert' on 'sys_user_permission_set' requires tenant-level administration or a delegated adminScope (ADR-0090 D12) — plain CRUD grants on RBAC tables do not make a permission administrator." (delete and sys_user_position the same)
holds organization_admin itself POST /api/v1/data/sys_user_permission_set {permission_set: "organization_admin"} as the platform admin → 201 the same four each 403 PERMISSION_DENIED: "You do not have permission to perform this action." (the object CRUD step)
platform admin (control) seeded the same four 201, 200, 201, 200

GET /api/v1/auth/me/permissions for both org admins reads both assignment objects as read-only. So on this posture no organization administrator can do what the replacement text says they do.

Not measured: a walled (group or isolated) posture, the one the entry's surface names. The read-only pin applies there unchanged, but whether a walled org admin holds a delegated adminScope was not measured.

Options, for triage (the seat's reading, not a ruling)

  • A — the text is wrong. Correct the replacement text to name who does assign under a walled posture: the platform operator, or a holder of an ADR-0090 D12 adminScope. This is a release-text correction in packages/spec/src/migrations/**, and the upgrade guide regenerates.
  • B — the sets are wrong. Give organization administrators create and delete on both assignment objects within their organization, through the delegated-administration path. That moves a privilege-escalation guard, so it is a security surface and the maintainer's call.

On the objectui side, PR objectstack-ai/objectui#12685 makes Setup read the create / delete grant on each assignment object. The console follows either answer with no further change.

Dedupe (objectstack, open and closed): REST lists of every domain:services and domain:spec card, paged explicitly, 2755 rows, grepped:

Dedupe words: organization admin assign permission set, organization_admin read-only sys_user_permission_set, walled-catalog-tenant-writes-retired replacement text, upgrade guide org admin assignment.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: a decision for the maintainer, needs-user-decision · target:v18 · area:access · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T15:58Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    The filer reads it right: two shipped records disagree, and which one is wrong is a product call. Re-read on origin/main 2054a0888c:

    • packages/spec/src/migrations/entries/semantic/18.walled-catalog-tenant-writes-retired.ts:23ff, replacement: "an organization's administrators assign it to their own members through sys_user_position and sys_user_permission_set".
    • plugin-security's shipped organization_admin (and its derived organization_admin_no_bypass) is read-only on both objects, pinned by rbac-objects.test.ts:63 "to prevent privilege escalation". The server refuses, as measured.
    • ADR-0131 D3 makes assignments organization-level rows. It does not say who may write them.

    维护者速读

    • 一句话问题: v18 的升级说明写"组织管理员给本组织成员分配岗位和权限集",可平台自带的组织管理员权限集对这两张分配表是只读的(为了防止提权),服务端实际会拒绝。说明和产品只能对一个。
    • 选项:
      • A 说明改成实际行为(推荐): 防提权规则不动,把升级说明改成实际情况(分配由有环境管理权限的人做)。工作量:spec 车道一处文字,小 PR。
      • B 让组织管理员能分配,但设上限: 只能分配不高于自己的岗位和权限集,仿照邀请时的角色上限。工作量:plugin-security 加带上限的写入通道、测试和 ADR 记录,中等偏大,而且是 GA 前新开一条授权写入路径。
      • C 两边都暂不动: 升级说明继续与产品不一致,不推荐。
    • 推荐 A。 发布说明必须准确。B 是功能扩展,要等您点名,适合 GA 之后单独立卡。
    • 请回: A / B / C。
  2. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量)· director seat (summon #43 continuation 3, session_01QJC7i981TSs9L1CGJK59Ax), read as batch #358 item 2, 2026-10-17T01:05Z

    On the finding from objectui domain:ui seat 3 and the triage seat's reading 6219399763 (A recommended: fix the text; B: widen the sets; C: nothing).

    Premises re-run in this act, objectstack origin/main be076dad28a0:

    1. packages/spec/src/migrations/entries/semantic/18.walled-catalog-tenant-writes-retired.ts, replacement: 「an organization's administrators assign it to their own members through sys_user_position and sys_user_permission_set, which are unchanged」. Holds.
    2. packages/plugins/plugin-security/src/objects/rbac-objects.test.ts:63, the pin 「organization_admin is read-only on global RBAC tables to prevent privilege escalation」 over sys_user_permission_set and sys_user_position. Holds.
    3. ADR-0131 D3 (docs/adr/0131-total-organization-ownership-no-null-organization-id.md:286): the catalog is environment-level, assignments are organization-level; its escape valve is 「direct user ↔ permission-set assignment (sys_user_permission_set, organization-level)」. It names where an assignment lives, not who may write it. Holds.
    4. ADR-0090 D12 (docs/adr/0090-permission-model-v2-concept-convergence.md:514): administration is a scoped capability — an admin scope on a permission set declares where (a subtree), what, and which sets the delegate may assign, the anti-self-escalation allowlist; amended by the ruling of record 6181988435 on [finding] AdminScope.manageBindings and authorEnvironmentSets are honoured by no write path after C3; the ADR-0090 D9/D12 junction gates are unmeasured; permissions docs describe removed gates #23488 (letter A, 2026-10-15T01:05Z): D12's what is user↔position assignments and direct set grants inside the subtree. The measured 403 names exactly this door: 「requires tenant-level administration or a delegated adminScope (ADR-0090 D12)」. So the product already declares who assigns — the operator, or a holder of a D12 scope whose allowlist carries the set — and the shipped organization_admin carries no scope.
    • ADR-0087 binds the version layer verbatim (:601, sys_metadata_history), not an entry's replacement prose; a wording fix inside the same protocol line before GA is a text change, not a new entry. The lane's first check.

    Facets: ① agree with A — the release text must say what the product does, and the product's rule is D12: an organisation administrator assigns only through a delegated scope with an allowlist; B as triage shapes it (a cap 「not above one's own」) is a second anti-escalation rule beside D12's allowlist, so B's honest shape is an ADR-0090 amendment plus a D12 scope shipped on organization_admin, not a new write channel in plugin-security · ② agree — a reader of the v18 upgrade guide acts on the sentence and is refused; measured on single, and the pin holds on every posture · ③ agree — A makes the declaration match the pin; B before GA opens an authorisation write path an AI would then reach for · ④ agree — A is one text change; B is an ADR amendment, a security surface and tests before GA.

    Recommend A. B only as the maintainer's product call, behind an ADR-0090 D12 amendment card. Not C (release text that contradicts the server is a defect, not a choice). 口径 for the executor under A, binding with the letter:

    1. The replacement prose names who assigns under the retired-catalog line: the platform operator, or a holder of an ADR-0090 D12 admin scope whose allowlist carries the set; 「an organization's administrators」 stays only qualified by that scope. The two objects stay named as unchanged and organization-level (D3).
    2. First measurement (the card's gap): a walled posture (group / isolated) — whether a plant admin holds a D12 scope there; the text says what is measured, never what is hoped.
    3. Pins: the migration entry's existing tests; the upgrade guide regenerates from the entry (dist/migrations), no second copy edited by hand.
    4. Clause-②: no; the entry's id and its D3 mapping unchanged.

    Workload: A = domain:spec · 1 small PR (packages/spec/src/migrations/**) · precedent: the entry's own earlier wording. B = domain:services (plugin-security) + an ADR-0090 amendment (Tier H, needs:contract-review) · 2 PRs · no precedent. C = none.

    Execution if A: this card is the execution card — needs-user-decision → pm:queue + pm:retriage in the ruling act (triage sets domain:spec; target:v18 and area:access already on it). If B: → pm:blocked behind an ADR-0090 D12 amendment card this seat files. If C: closes not_planned with the ruling, and the guide ships wrong, recorded as such.

    os-decision-facets

    • ① 项目长远合理性:同意 A —— 产品规则已在 ADR-0090 D12 写明:组织管理员只能通过带白名单的委托管理范围分配;B 若做,是 ADR 修订加在自带权限集上配一个 D12 范围,不是在 plugin-security 新开一条写入通道。
    • ② 实际业务拉动:同意 —— 升级指南的读者照句子去做会被服务端拒绝;single 态实测,只读 pin 在每种租户态都成立。
    • ③ 防 AI 犯错:同意 —— A 让声明和 pin 一致;B 在 GA 前新开一条授权写入路径,AI 会去用。
    • ④ 创业阶段不扩散:同意 —— A 一处文字;B 是 ADR 修订、安全面和测试。

    自检:只看①选 A;②③④ 是否翻转:否。

  3. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量 2)· director seat (summon #43 continuation 3, session_01QJC7i981TSs9L1CGJK59Ax) · the maintainer's question on batch #358 item 2, 2026-10-17T01:31Z

    The maintainer's word in this session's chat: 「23960 帮我具体分析这个开发复杂吗」 — a question, not a letter; this card stays in the box. Read in this act on objectstack origin/main be076dad28a0.

    维护者速读

    • A(改说明)不复杂: packages/spec/src/migrations/entries/semantic/18.walled-catalog-tenant-writes-retired.ts 的 replacement 一句话改成实际谁能分配;升级指南由条目重新生成。domain:spec 一个小 PR,不碰安全面。
    • B(让组织管理员能分配)不是一个开关,是改安全模型: 今天三处代码共同依赖「组织管理员对两张分配表只读」这条不变量(闸门头注、自动授权、邀请落位),B 要先改规则再改代码:
      1. 规则(ADR-0090 D12 修订,Tier H,contract review): D12 防提权的核心是每个委托范围写明「可分配的权限集白名单」。自带的 organization_admin 没有作者去写白名单,所以要先定义自带集的白名单怎么算(例如只含组织级、不带 adminScope 的集)。分流席说的「不高于自己」在模型里不存在——权限集没有等级,只有成员资格(owner / admin / member)有等级。
      2. 闸门(plugin-security/src/delegated-admin-gate.ts,1,188 行): 现在只有两种人能写分配表:租户级管理员(带通配 modifyAllRecords;organization_admin 属于这类,闸门放行后被每表 allowCreate/Edit/Delete: false 挡住)和持 adminScope 的委托人(范围锚在一个业务单元子树上)。B 要加第三条分支:组织管理员以「整个组织」为范围、按新定义的白名单放行、沿用既有的严格包含检查;无墙部署自动发的 organization_admin_no_bypass 没有通配,今天走的是「其他人一律拒绝」那条,也要一起纳入。
      3. 权限集与 pin: default-permission-sets.ts 两张表的只读覆盖翻转(小);rbac-objects.test.ts:63 的防提权 pin 改写;auto-org-admin-grant.ts 与 invitation-placement.ts 的头注都把这条只读当作邀请链路安全的前提,要重新论证并补测试;/security/my-delegable-scope(控制台读「我能分配什么」)要把组织管理员算进去。
      4. 租户态矩阵: single / group / isolated 都要测,尤其无墙部署(ADR-0105 D4 的 F2 发现:组织管理员不能变成全环境超级用户)。
      • 估算: ADR 修订 1 个 PR + plugin-security 1–2 个 PR(闸门分支、权限集、四处测试)+ 控制台可能 1 个小 PR。安全面,GA 前新开一条授权写入路径。中到大。
    • 今天不写代码也能得到 B 的效果(按部署配置): D12 已经允许运维者给某个组织的管理员授一个带 adminScope 的权限集(范围 = 该组织的一个业务单元子树,manageAssignments: true,白名单列出可发的集),配 delegated_admin 成员资格;邀请时也能直接带岗位落位(ADR-0105 D8)。差的只是「每个组织管理员默认就能」。升级说明可以把这条路写出来。
    • 席位仍推荐 A。 若您要 B,先立 ADR-0090 D12 修订卡,本卡 pm:blocked 挂在它后面。

    What B changes, read on the code

    1. The invariant. delegated-admin-gate.ts header: tenant-level admins (a set with the ADR-0066 wildcard) pass the gate and 「the built-in organization_admin also passes here but is still denied downstream by its explicit per-table allowEdit:false overrides」; delegates write only what one adminScope fully approves (manageAssignments, inside the BU subtree, allowlisted sets, strict containment); everyone else is denied. auto-org-admin-grant.ts:37 and invitation-placement.ts:14 both cite the read-only overrides as the reason a fresh org admin cannot rebind themselves and the invitation surface is safe.
    2. The scope model. AdminScopeSchema (packages/spec/src/security/permission.zod.ts:709): businessUnit, includeSubtree, manageAssignments, assignablePermissionSets. The where is a business-unit subtree; an organisation-wide where does not exist, and a scope whose subtree resolves to nothing approves nothing. B either adds an organisation-wide where or anchors the shipped scope on a unit every organisation must have.
    3. The allowlist for a shipped set. D12 allowlists are authored per scope. For organization_admin B must define one by rule (the ADR amendment), and the 2026-10-15 amendment (ruling 6181988435 on [finding] AdminScope.manageBindings and authorEnvironmentSets are honoured by no write path after C3; the ADR-0090 D9/D12 junction gates are unmeasured; permissions docs describe removed gates #23488, letter A) already narrowed D12 to assignments and direct grants — B stays inside that what.
    4. The two variants. organization_admin (wildcard, tenant-level) and organization_admin_no_bypass (wall-less auto-grant, no wildcard, ADR-0105 D4) reach the gate on different branches today; B covers both or the wall-less deployment keeps the old answer.
    5. Tests that pin the old answer: rbac-objects.test.ts:63, the gate's own tests, auto-org-admin-grant.test.ts, the invitation-placement tests, explain-engine (TENANT_ADMIN ← organization_admin), and the /security/my-delegable-scope readers (console objectui#2868 / chore: version packages #2891).

    Today's no-code route to B's effect: an authored set with adminScope { businessUnit: <the organisation's unit>, manageAssignments: true, assignablePermissionSets: [...] } granted to the organisation's administrators, plus the delegated_admin membership role (membership-role.ts); invitations carry placement (ADR-0105 D8). What B adds is the default.

    Prior rulings read: 6181988435 (D12 amendment, #23488, letter A), ADR-0105 D4 / D8, ADR-0131 D3; thread: 6219399763 (triage), 6227740170 (delta 1).

  4. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量 3 · 更正)· director seat (summon #43 continuation 3, session_01QJC7i981TSs9L1CGJK59Ax) · the maintainer's second question on batch #358 item 2, 2026-10-17T01:49Z

    The maintainer's word in this session's chat: 「23960 给我从实际业务的角度,具体解释目前的业务状态以及遇到的问题」 — a question, not a letter; this card stays in the box. Read in this act on objectstack origin/main be076dad28a0 and objectui d0a00d795020.

    Correction first. Delta 1 (6227740170) premise 3 read ADR-0131 D3 as naming where an assignment lives, not who writes it. That is wrong. D3 (docs/adr/0131-total-organization-ownership-no-null-organization-id.md:323–:326): 「Assignments are organization rows, in every posture. sys_user_position … and sys_user_permission_set … are created and removed by that organization's admins for its own members — walled by Layer 0 like any other organization data」, under the ruling 「单库多租户我可以禁止他们创建。但是你要支持我绑定到人员」. The migration text repeats the ADR. The shipped set contradicts both: packages/plugins/plugin-security/src/objects/default-permission-sets.ts keeps the two tables read-only on organization_admin 「to prevent privilege escalation」 (an org admin could bind admin_full_access, which has no RLS, to themselves and leave the wall), and rbac-objects.test.ts:63 pins it as a stage-8 decision. So this card is not 「text versus product」: it is the ADR's promise versus the code's guard, and letter A is honest only with the ADR narrowed (Tier H). Delta 1's recommendation is withdrawn; the revised reading closes this comment.

    维护者速读 —— 业务现状与问题

    概念先对齐。 一个成员能做什么,由两样东西决定:岗位(position,如「销售经理」「审批人」,每个岗位捆绑若干权限集)和权限集(permission set,如「销售数据读写」)。岗位和权限集的定义是目录,环境级,由运维者(平台管理员)或管理包提供(ADR-0131 D3;单租户下 Setup 自建也要 manage_metadata)。谁持有哪个岗位 / 哪个权限集是分配,组织级,是日常运营动作。一个新成员没有任何分配时只有 everyone 基线:看不到销售应用的对象,也审批不了。

    今天的组织管理员是谁、能做什么。 成员资格为 owner 或 admin 的成员自动获得 organization_admin(有墙部署)或 organization_admin_no_bypass(无墙部署)。能:看到并编辑本组织全部业务数据;邀请成员(成员资格 owner/admin/member,不高于自己)、移除成员、转让所有权;进 Setup,读岗位与权限集列表。不能:在有墙部署里新建或修改岗位、权限集(D3,目录归运维者);也不能把已有岗位或权限集分配给本组织成员——这一条与 D3 的承诺相反,是本卡的问题。

    客户实际遇到什么(按部署形态)。

    1. 单公司自部署(single): 组织就是环境。IT 管理员通常既是平台所有者又是组织 owner,用平台管理员身份分配,感觉不到问题。但被设为 admin 的部门负责人想把新同事设成「销售经理」:Setup 里岗位持有人列表只读(objectui#12685 落地后 Add/Remove 按服务端授权隐藏;之前是点了报 403),只能找 IT 管理员。
    2. 多组织部署(group / isolated,含我们自己的 cloud): 每个客户组织的 owner/admin 是组织管理员,运维者是我们。客户管理员要让同事当审批人,被拒;按 D3 这本该是客户自己的事,现在是运维者替每个客户做分配,SaaS 上不可持续。
    3. 唯一的自助口子是邀请。 邀请可带「业务单元 + 岗位」落位(ADR-0105 D8),控制台邀请对话框在服务端报告有可委托范围时显示该区块;组织管理员在闸门里按租户级管理员处理,所以新成员可能在邀请时就带岗位进来(本卡未实测),但已有成员事后改不了岗位。进门时能发工牌,进了门就不能换。
    4. 运维者的手工替代: 给该组织建一个业务单元,授一个带 adminScope(范围 = 该单元子树,manageAssignments: true,白名单列出可发的集)的权限集,配 delegated_admin 成员资格——每个组织手工配一遍。
    5. 升级指南(v18 发布文本) 照 ADR 写「组织管理员分配」,读者照做被拒。

    问题的本质。 ADR-0131 D3 承诺组织管理员分配;代码为防提权(组织管理员给自己绑 admin_full_access 越出租户墙)把两张表设为只读并用 pin 固定;两边各有道理,但没人定「组织管理员可以分配哪些」这条边界。D12 对委托人的答案是白名单;对自带的 organization_admin,边界没定义,实现只能一刀切只读。

    修订后的读法(字母仍是您的):

    • B(推荐,作为 ADR 的执行): 按 D3 让组织管理员分配,边界定为「只能分配被组织墙约束的目录项」——不带超级用户通配、不带 adminScope、非平台级的岗位与权限集;沿用 D12 的严格包含检查。做法与增量 2 的估算相同:ADR-0090 D12 修订(定义自带集的白名单规则)+ 闸门第三条分支 + 两张表覆盖翻转 + 四处测试 + 控制台「我能分配什么」;中到大,target:v18(GA 前 D3 的承诺要么兑现要么收回)。
    • A′: 收回承诺——修订 ADR-0131 D3 那句话(Tier H)并改升级说明;组织管理员继续只读,分配靠运维者或手工委托。文字小,但改的是您 9 月 4 日的裁决。
    • C: 不动,指南与 ADR 都与产品相反。不推荐。

    自检: 只看①选 B——两年后的平台样子是组织管理员在自己的墙内发工牌,Salesforce 的 Delegated Administration(指定管理员可给指定角色的用户分配指定的 profile / permission set)和 Dynamics 365 的业务单元级安全角色分配都是这个形态;②③④ 是否翻转:否——④ 只影响分期:先 ADR 修订定边界,再代码。

    What was read

    Prior rulings read: ADR-0131 D3 (ruled 2026-09-04), 6181988435 (ADR-0090 D12 amendment, #23488, letter A), ADR-0105 D4 / D8; thread: 6219399763 (triage), 6227740170 (delta 1, premise 3 corrected here), 6227987101 (delta 2, the estimate stands).

  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量 4)· director seat (summon #43 continuation 3, session_01QJC7i981TSs9L1CGJK59Ax) · the maintainer's third question on batch #358 item 2, 2026-10-17T02:44Z

    The maintainer's word in this session's chat: 「如果选择B,大概有哪些开发任务,工作量多少,开发完成之后实现的业务效果是什么」 — a question, not a letter; this card stays in the box. Sized in this act on objectstack origin/main be076dad28a0 and objectui d0a00d795020. Counts are estimates from the files read, marked as such.

    维护者速读 —— 选 B 的开发任务、工作量、业务效果

    边界先定一句话(ADR 修订要写的规则): 组织管理员可以把岗位或权限集分配给本组织成员,条件是该权限集(岗位则看它分发的每一个权限集)满足三条:不带 * 通配的 viewAllRecords / modifyAllRecords;不带 adminScope;所含能力全部是 scope: 'org'(能力表 capabilities.ts 已经逐条声明了 org / platform,所以这条规则是声明算出来的,不用新键)。admin_full_access、平台管理员集、organization_admin 本身(含 setup.access,platform 级)和任何委托集都在边界外;组织管理员身份仍只能通过成员资格(owner / admin)自动授予,不能靠分配复制。

    开发任务(按落地顺序):

    1. ADR-0090 D12 修订 + ADR-0131 D3 注记(Tier H,contract review):写上面的边界规则,把「组织管理员」作为第三类管理者记入 D12(租户级 / 组织级 / 委托级)。1 个 PR,纯文字,约 100–200 行。
    2. 闸门第三条分支(plugin-security/src/delegated-admin-gate.ts,assert() :281–,今天 isTenantAdmin(sets) 直接放行 :332):先区分「持 organization_admin / organization_admin_no_bypass 授权但不是平台管理员」的调用者,对其写入 sys_user_position / sys_user_permission_set 时按边界规则逐个校验要分发的集(岗位分发的集的校验逻辑 :780– 已有,复用),目标成员须在同一组织(organization-scope.js 已有)。估 150–250 行 + 测试 250–400 行(delegated-admin-gate.test.ts 现 1,067 行)。
    3. 权限集覆盖翻转(objects/default-permission-sets.ts organization_admin 两张表的 allowCreate/allowEdit/allowDelete: false → 允许 create / delete,edit 仍关:分配行只增删不改;deriveWallLessOrgAdmin :1486 自动带到无墙变体):约 10 行;rbac-objects.test.ts:63 的 pin 改写为「边界 pin」(组织管理员分不出 admin_full_access、带 adminScope 的集、含 platform 级能力的集),约 60 行。
    4. 解释与自述两扇门:explainAccessForCaller(security-plugin.ts:4845,D6「谁能改这个人的授权」)和 GET /security/my-delegable-scope(rest/src/security-routes.ts,describeDelegableScope :461)把组织管理员的可分配集报出来,控制台的邀请落位区块和 Setup 的 Add / Remove 按钮都读这两处。估 80–150 行 + 测试。
    5. 邀请落位与自动授权的论证更新:invitation-placement.ts 和 auto-org-admin-grant.ts 的头注都以「只读」为安全前提,改为以边界规则为前提;测试补「组织管理员邀请时只能带边界内岗位」。约 50 行 + 测试。
    6. 三种租户态验收:single / group / isolated 各跑一遍(平台管理员、组织管理员、无墙组织管理员、普通成员四种人 × 增删两张表 × 边界内外两种集),按 docs/qa 清单立一项;无代码,1 次 dogfood。
    7. 控制台(objectui): Setup 持有人页的 Add / Remove 已按服务端授权显示(objectui#12685,10-16 落地),邀请对话框的落位区块按 my-delegable-scope 显示——预计零改动,最多补一条「组织管理员视角」的测试。

    工作量合计(估): domain:services 2 个 PR(任务 2–5 可合为一个,或闸门一个、门面一个)+ domain:spec 1 个 ADR PR + 1 次三态验收;代码约 300–500 行,测试约 400–600 行;先例:D12 委托闸门本身(#3663、#3695、#3722,同一文件同一形态)。前提是边界规则您点头;没有它,闸门没法写。

    做完之后的业务效果:

    • 客户组织的管理员自助管人: 在 Setup 的岗位 / 权限集持有人页直接给本组织成员加、减岗位和权限集,邀请时就能带岗位进门,不再找运维者。单公司部署里的部门负责人、cloud 上每个客户的 owner / admin 都是这个效果。
    • 墙不破: 组织管理员给不出 admin_full_access、平台级能力、委托范围,也复制不出组织管理员身份;服务端 403 文案会说明是「边界外的集」,审计与 explain 显示「由组织管理员分配」。
    • 运维者只管目录: 定义岗位和权限集(管理包、Studio),不再替客户做分配;D12 委托仍保留给更细的业务单元级授权(工厂、分公司的子树)。
    • v18 升级指南那句话原样成真,不用改文字;objectui#12082 / PR The documented broken-sweep predicate selected > 0 AND acted = 0 AND unmeasured = 0 fires on a HEALTHY idempotent sweep — measured A/B, and "over consecutive runs" does not rescue it #12685 的控制台行为自动跟着服务端变化。
    • 不做的事: 不加 spec 键,不加新端点,不改成员资格模型,不动目录的环境级归属。

    Code anchors for the executor

    • Gate: assert() delegated-admin-gate.ts:281; tenant-admin pass :332; sys_member refusal :343–; describeDelegableScope :461; tenant-admin branch of the scope resolver :484–:506; position → sets allowlist walk :780–:783.
    • Sets: organization_admin declaration default-permission-sets.ts:441–; the two overrides beside denyWritesOnManagedObjects(); deriveWallLessOrgAdmin :1486.
    • Capability scopes: packages/spec/src/security/capabilities.ts (scope: 'org' | 'platform' on every row; manage_metadata, setup.access, studio.access, manage_platform_settings are platform).
    • Doors: security-plugin.ts:4845 (explain for another user), rest/src/security-routes.ts (my-delegable-scope).
    • Tests to extend: delegated-admin-gate.test.ts (1,067), auto-org-admin-grant.test.ts (852), rbac-objects.test.ts (210, the pin at :63), invitation-placement.test.ts (200).

    Prior rulings read: ADR-0131 D3, ADR-0090 D12 and its 2026-10-15 amendment (6181988435), ADR-0105 D4 / D8; thread: 6219399763, 6227740170, 6227987101, 6228146260.

  6. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #358 item 2 · letter B · maintainer 「同意」 2026-10-17T02:52Z

    Director seat, summon #43 continuation 3, session_01QJC7i981TSs9L1CGJK59Ax, on the finding from objectui domain:ui seat 3, the triage reading 6219399763 (A), the director deltas 6227740170 (A, withdrawn), 6227987101 (the sizing of B), 6228146260 (the correction: ADR-0131 D3 names who writes an assignment; recommendation revised to B as the ADR's execution) and 6228658787 (B's tasks, workload and business effect), and the premise re-run on objectstack origin/main be076dad28a0 (opening marker 6227733044). The maintainer's word in this session's chat: 「同意」, given on the seat's revised recommendation B after its sizing was put to him.

    What the ruling binds (letter B — ADR-0131 D3 is executed; the boundary is declared first):

    1. The boundary rule, written into ADR-0090 D12 by amendment and noted under ADR-0131 D3: an organisation administrator (organization_admin / organization_admin_no_bypass for the organisation, not a platform administrator) creates and removes sys_user_position / sys_user_permission_set rows for members of that organisation when every distributed set — the set, or each set a position carries — has no * wildcard superuser bits, no adminScope, and only scope: 'org' capabilities (packages/spec/src/security/capabilities.ts). admin_full_access, platform sets, organization_admin itself and every delegated set stay outside; organisation-administrator status is conferred by membership grade only.
    2. Order: the ADR card lands first (Tier H, at-tier ## Contract review); the plugin-security card is Blocked-by: it and carries the gate's third branch, the overrides (allowCreate / allowDelete on, allowEdit off), the rewritten pin, my-delegable-scope and the explain door, the updated safety arguments, and the three-posture acceptance.
    3. The v18 migration entry's replacement text stays as published: it becomes true. No packages/spec key, no new endpoint, no change to the membership model; Clause-②: no on both cards; minor for @objectstack/plugin-security.
    4. Fallback carried: if a shipped set cannot be classified by the three declared conditions (a mixed bundle the conditions do not settle), the ADR card names an explicit denylist for that set instead of widening the rule — no new box visit. The console is expected to need no code (objectui#12685 reads the grants; the invite dialog reads my-delegable-scope); a measured gap there is a domain:ui card from the services seat's report.

    Not taken: A′ (narrowing D3 and the upgrade text — the maintainer's own ruling of 2026-09-04 stands), C (the guide and the ADR left contradicting the product).

    Workload of the letter (delta 4): domain:spec · 1 ADR PR (Tier H) + domain:services · 2 PRs, about 300–500 lines of code and 400–600 of tests · precedent: the D12 delegated gate in the same file (#3663, #3695, #3722) · one three-posture acceptance run.

    Execution: two execution cards filed in this act — #24135 (domain:spec, p1, target:v18, pm:queue: the ADR-0090 D12 amendment and the ADR-0131 D3 note) and #24136 (domain:services, p1, target:v18, area:access, pm:blocked, Blocked-by: #24135: the code and the acceptance). This card closes completed with this record, and needs-user-decision leaves in the same act; #24136's Done-when re-runs this card's measurement.

  7. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Closed completed · ruled B at 6228728551 (batch #358 item 2, the maintainer's 「同意」 on the seat's revised recommendation) · director seat (summon #43 continuation 3, session_01QJC7i981TSs9L1CGJK59Ax) · 2026-10-17T02:54Z

    The decision is recorded above and executes as #24135 (the boundary rule, ADR-0090 D12 amendment + ADR-0131 D3 note, lands first) and #24136 (plugin-security: the gate's third branch, the overrides, the pin, the two doors, the three-posture acceptance; Blocked-by: #24135). The v18 migration text stays as published and becomes true when #24136 lands; its Done-when re-runs this card's four measured writes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardstarget:v18

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions