You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(authz): expose the caller's delegable scope — the read half of the delegated-admin gate (ADR-0090 D12 / ADR-0105 D8) (#3674)
* feat(authz): expose the caller's delegable scope — the read half of the delegated-admin gate (ADR-0090 D12 / ADR-0105 D8)
adminScope decided writes but could not be READ: assignablePermissionSets
lived only inside delegated-admin-gate.ts, so a UI offering 'place this
person in a unit, with these positions' (the D8 scoped-invitation form) had
no way to narrow its pickers — it would list the whole tree and let the user
discover the boundary by being refused, turning an authorization gate into a
validator.
ISecurityService.describeDelegableScope(callerContext), exposed as
GET /api/v1/security/my-delegable-scope and
client.security.describeDelegableScope():
- placeableBusinessUnitIds — subtrees where the caller may place people
- assignablePositions — positions whose every distributed set is allowlisted
(containment check included)
- scopes — held adminScopes with subtrees resolved, for attribution
- isTenantAdmin — unconstrained, everything enumerated so consumers render
ONE uniform picker
Computed by the same helpers the write path enforces with, so an option this
reports is one assert() accepts; a test asserts that agreement directly by
replaying every offered (unit, position) pair through the gate. It NARROWS —
the gate still decides.
Strictly self-scoped (no target-user parameter), so it discloses nothing
beyond the authority the caller already holds. Fail closed: unresolvable
scopes contribute nothing, no delegated authority yields empty lists, and a
deployment without plugin-security gets 501.
Verified: plugin-security 606, rest 401, client 175, spec 6677 — all green;
route-ledger conformance entry added (#3587); five grep guards + spec
check:docs clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FebXPaaGrLhGKw1LHPbpL
* docs(permissions): document the delegable-scope read surface on the delegated-administration page
The page described what the write gate enforces but had no answer to 'what
may I delegate?' — the question an admin surface must ask to narrow its
pickers. Adds the endpoint, the SDK call, the response shape, and the three
properties that matter: it narrows but does not decide, a position appears
only if every set it distributes is allowlisted, and it is strictly
self-scoped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FebXPaaGrLhGKw1LHPbpL
* chore(spec): regenerate the api-surface snapshot for the delegable-scope types
DelegableScope + DelegableAdminScope are additive (0 breaking, 2 added) —
the drift guard just needs the refreshed snapshot committed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FebXPaaGrLhGKw1LHPbpL
---------
Co-authored-by: Claude <noreply@anthropic.com>
note: "read half of the delegated-admin gate; strictly self-scoped (no target-user parameter), so a scoped-invitation picker can narrow to what the caller may actually delegate"},
0 commit comments