Skip to content

Commit 296a1d6

Browse files
committed
docs(qa): checklist item for the organization administrator's assignment boundary; changeset
Claude-Session: https://claude.ai/code/session_013WG7E4zFnUTehQ9UXwyQtT Co-authored-by: Claude <noreply@anthropic.com>
1 parent 78b7dee commit 296a1d6

2 files changed

Lines changed: 91 additions & 0 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/plugin-security': minor
3+
---
4+
5+
feat(plugin-security): organization administrators assign positions and permission sets to their own members, inside the organization boundary
6+
7+
Clause-②: no
8+
9+
ADR-0090 D12 (as amended for ADR-0131 D3) names three kinds of administrator. This release enforces the second one, the organization administrator: a holder of `organization_admin` or `organization_admin_no_bypass` for the active organization who is not a platform administrator.
10+
11+
- **What an organization administrator may now do.** Create and remove `sys_user_position` and `sys_user_permission_set` rows of its own organization, for that organization's members. Every permission set the row distributes must be inside the organization boundary: the granted set, or each set the assigned position carries (including the set of the position's own name). A set is inside when its `objects['*']` entry carries neither `viewAllRecords` nor `modifyAllRecords`, it has no `adminScope`, and every name in its `systemPermissions` is a capability declared `scope: 'org'`. `admin_full_access`, both organization-administrator sets and the three MCP agent ceilings are outside by name. An assignment row is never edited in place: remove it and create the one you mean. A row inserted with no organization lands in the caller's organization.
12+
- **What is refused, and how.** An out-of-boundary write answers `403 PERMISSION_DENIED`, and the message names the set and the condition it failed. The same refusal covers a row of another organization, removal of an organization-less row, a business unit of another organization, an edit in place and a filter delete. Assigning a user who is not a member of the organization is refused `400 VALIDATION_FAILED` as before, for every writer. So is assigning a built-in identity position (`platform_admin`, `org_owner`, `org_admin`, `org_member`). Organization membership rows stay platform-administrator-only.
13+
- **Overrides.** `organization_admin`, and so `organization_admin_no_bypass`, now carries `allowCreate` and `allowDelete` on both assignment tables. `allowEdit` stays `false`.
14+
- **The two doors.** `GET /security/my-delegable-scope` reports an organization administrator with `isTenantAdmin: false`, every unit of its organization as placeable, and only in-boundary positions as assignable. The walled `organization_admin` was reported as tenant-level before. Explaining another user's access now admits an organization administrator explaining a member of its own organization.
15+
- **Unchanged.** Platform administrators keep today's behaviour. Delegated (`adminScope`) administrators keep D12's subtree, allowlist and strict containment. Everyone else is denied as before. No spec key, no endpoint and no membership-model change.

‎docs/qa/platform-checklist/areas/access-security.json‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3997,6 +3997,82 @@
39973997
"ref": "#24632"
39983998
}
39993999
]
4000+
},
4001+
{
4002+
"id": "access-security.org-admin-assignment-boundary",
4003+
"title": "An organization administrator creates and removes its own members' position and permission-set assignments inside the organization boundary, in all three tenancy postures; an out-of-boundary set answers 403 naming the set and the condition",
4004+
"since": "v18",
4005+
"status": "active",
4006+
"revision": 1,
4007+
"priority": "P1",
4008+
"surface": "api",
4009+
"personas": [
4010+
"a platform administrator (admin_full_access)",
4011+
"an organization administrator under a walled posture (organization_admin)",
4012+
"an organization administrator under a wall-less posture (organization_admin_no_bypass)",
4013+
"a member (member_default)"
4014+
],
4015+
"fixtures": {
4016+
"app": "none — the real-stack pin boots ObjectQL over SQLite with the SecurityPlugin per posture",
4017+
"requires": [
4018+
"an in-boundary permission set (no objects['*'] superuser bit, no adminScope, only scope 'org' capabilities) and a position carrying it",
4019+
"a position carrying a mixed bundle (an in-boundary set plus admin_full_access)",
4020+
"two organizations, with the target user a member of the administrator's organization only"
4021+
]
4022+
},
4023+
"steps": [
4024+
"for each posture (single, group, isolated) and each persona, insert a sys_user_position and a sys_user_permission_set row for a member, with an in-boundary and an out-of-boundary set",
4025+
"for the same cells, delete a system-seeded row of the administrator's organization by id",
4026+
"read GET /security/my-delegable-scope and explain another member's access as each organization administrator"
4027+
],
4028+
"acceptance": [
4029+
{
4030+
"clause": "the three-posture matrix passes at the run subject sha (RUNNER rule 6: cite the pin's output, never re-derive by hand)",
4031+
"oracle": "test",
4032+
"verify": "run each file named in automated.ref with its package's vitest (`pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 FILE`, never through a bare `--`); every named file reports ≥1 test and 0 failed. A file that reports 0 tests or is not found is NOT a pass — it is a stale ref, fix the item",
4033+
"evidence": "the vitest summary line per pin file, with the package name and the subject sha"
4034+
},
4035+
{
4036+
"clause": "both organization-administrator variants: in-boundary insert and delete on both tables succeed (the inserted row is stored in the administrator's organization); out-of-boundary insert and delete answer 403 PERMISSION_DENIED and the message names the set and the failed condition",
4037+
"oracle": "test",
4038+
"verify": "the per-cell cases of org-admin-assignment-boundary.posture.test.ts, three postures",
4039+
"evidence": "the case names and their pass lines"
4040+
},
4041+
{
4042+
"clause": "the platform administrator is admitted in every cell (unchanged); the member is refused 403 in every cell (unchanged)",
4043+
"oracle": "test",
4044+
"verify": "the platform and member cells of the same file",
4045+
"evidence": "the case names and their pass lines"
4046+
},
4047+
{
4048+
"clause": "the delegable-scope report gives an organization administrator isTenantAdmin false and offers only in-boundary positions; explain admits it for a member of its organization and refuses it for anyone else",
4049+
"oracle": "test",
4050+
"verify": "the report/explain case of the same file, per posture",
4051+
"evidence": "the case names and their pass lines"
4052+
}
4053+
],
4054+
"negative": [
4055+
"an organization administrator's assignment of a member of another organization lands — FAIL (expected 400 VALIDATION_FAILED from the grant-holder membership hooks)",
4056+
"an organization administrator edits an assignment row in place — FAIL (expected 403)",
4057+
"an organization administrator assigns admin_full_access, an adminScope set, a platform-capability set, either organization-administrator set or an agent ceiling, to anyone including itself — FAIL (expected 403 naming the set)"
4058+
],
4059+
"automated": {
4060+
"kind": "e2e",
4061+
"ref": "packages/plugins/plugin-security/src/org-admin-assignment-boundary.posture.test.ts + packages/plugins/plugin-security/src/delegated-admin-gate.test.ts (the organization-administrator describe) + packages/plugins/plugin-security/src/objects/rbac-objects.test.ts (the boundary pin over the shipped sets)"
4062+
},
4063+
"source": [
4064+
"packages/plugins/plugin-security/src/delegated-admin-gate.ts#organizationAdminFailure",
4065+
"packages/plugins/plugin-security/src/objects/default-permission-sets.ts",
4066+
"docs/adr/0090-permission-model-v2-concept-convergence.md (D12, amended 2026-10-17)"
4067+
],
4068+
"history": [
4069+
{
4070+
"revision": 1,
4071+
"date": "2026-10-20",
4072+
"change": "initial — the organization administrator's assignment boundary (ADR-0090 D12 as amended 2026-10-17), run once on the PR head through the automated pins",
4073+
"ref": "#24136"
4074+
}
4075+
]
40004076
}
40014077
]
40024078
}

0 commit comments

Comments
 (0)