feat(authz): expose the caller's delegable scope — the read half of the delegated-admin gate (ADR-0090 D12 / ADR-0105 D8) - #3674
Merged
Conversation
…he 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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 4 package(s): 111 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…elegated-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
…ope 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
os-zhuang
marked this pull request as ready for review
July 27, 2026 14:18
This was referenced Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prerequisite for the D8 scoped-invitation UX (cloud#874), found while scoping it. Follows #3663 (the D8 engine). Tracking: #3541.
The gap
adminScopedecided writes but could not be read:assignablePermissionSetslived only insidedelegated-admin-gate.tsandresolveHeldScopesis private. So a form offering "place this person in a unit, with these positions" had no way to narrow its pickers — it would list the whole BU tree and every position, and the user would discover the boundary only by being refused. That turns an authorization gate into a validator and makes the boundary invisible until it bites.What
ISecurityService.describeDelegableScope(callerContext)— exposed asGET /api/v1/security/my-delegable-scopeandclient.security.describeDelegableScope():placeableBusinessUnitIdsmanageAssignments)assignablePositionsscopesadminScopes with subtrees resolved, for attribution in a UIisTenantAdminComputed by the same
resolveHeldScopes/setsBoundToPosition/ containment helpers the write path enforces with, so an option this reports is oneassert()accepts — a test asserts that agreement directly by replaying every offered(unit, position)pair through the gate, and then checks that a withheld position really is refused, so the narrowing is not cosmetic. It NARROWS; the gate still decides.Why it's safe to expose
Strictly self-scoped — no target-user parameter, by design. The caller's own resolved sets are the only input, so it discloses nothing beyond the authority they already hold, and there's no "describe someone else's authority" surface to authorize (unlike
explain, which has one and gates it onmanage_users/coveringadminScope).Fail closed throughout: unresolvable scopes contribute nothing, a caller with no delegated authority gets empty lists, a degraded start with no gate returns empty, and a deployment without
@objectstack/plugin-securitygets 501. Audience anchors (everyone/guest) never appear — they're implicit and unassignable (ADR-0090 D9).Verification
sdkdisposition is honest.check:docsclean; changeset included (minor ×4).Next
The cloud D8 issuance UX consumes this: BU/position pickers narrowed to the issuer's actual authority, over the gated seam from #3663.
🤖 Generated with Claude Code
https://claude.ai/code/session_015FebXPaaGrLhGKw1LHPbpL
Generated by Claude Code