docs(idm): complete delegation lifecycle guidance

This commit is contained in:
2026-08-19 22:16:18 +02:00
parent 2c6ee041b9
commit dc99a40384
3 changed files with 73 additions and 1 deletions
+28
View File
@@ -35,6 +35,34 @@ authority must clear it, or whether only the authority can initiate and grant
it. Recipient acceptance can be required or waived only by an explicit policy
with recorded provenance.
## Delegation, Substitution, And Acting In Place
These modes are explicit assignment sources; ordinary group membership never
creates them:
- A **delegated assignment** is a bounded substitution. For example, a registry
lead may delegate the same delegable function to a deputy until Friday. The
deputy acts as themself, and Access receives both the derived assignment ID
and its source assignment ID.
- An **acting-for assignment** is a bounded representation context. For example,
an assistant may select an active acting context for the represented function
holder. Access records the real account, the selected assignment, and the
represented account on the session and in audit evidence. No acting-for
authority is effective until that exact context is selected.
- A **direct assignment** is the holder's own function fact and has no source
assignment or represented account.
The Organizations function must permit the requested mode. The source and
derived assignments must belong to the same tenant and function, and the
source must be current and active. An actor also needs the assignment-write
scope; where a governance profile is enabled, Policy must authorize the
request/grant or an administrator must use the recorded emergency-override
path. Validity windows make substitutions expire automatically. Deactivation
revokes an assignment without deleting its provenance. IDM emits changed,
revoked, and expired lifecycle events and writes the normal assignment audit
record; effective-directory reads immediately exclude inactive, future,
expired, or source-invalid derived assignments.
## Grant Profiles
The first policy profiles are:
+4 -1
View File
@@ -475,7 +475,10 @@ manifest = ModuleManifest(
"function and unit are owned by Organizations. Source distinguishes "
"direct, delegated, acting-for, directory, governance, and system facts. "
"Delegation and acting-for require a valid source assignment and the "
"corresponding function permission. Subunit scope broadens the fact's "
"corresponding Organizations function permission. A delegate acts as themself; "
"acting-for additionally requires Access to select the exact representation "
"context before it contributes authority. Source and derived assignments must "
"remain current, active, tenant-local, and function-compatible. Subunit scope broadens the fact's "
"organizational reach. Deactivation and expiry preserve provenance while "
"removing the assignment from effective resolution. Governed request and "
"grant decisions retain actor, policy, workflow revision, comments, and "
+41
View File
@@ -99,6 +99,26 @@ class AssignmentWorkflowTests(unittest.TestCase):
),
)
def test_delegated_assignment_rejects_function_that_forbids_delegation(self) -> None:
base = assignment(id="source-1")
item = assignment(
id="assignment-2",
identity_id="identity-2",
account_id="account-2",
source="delegated",
delegated_from_assignment_id="source-1",
)
self.assert_invalid(
"This organization function does not allow delegation.",
lambda: validate_assignment_source_rules( # type: ignore[arg-type]
item,
function=function(delegable=False),
base=base,
account_linked_to_identity=lambda _identity_id, _account_id: False,
),
)
def test_acting_for_assignment_accepts_identity_linked_account(self) -> None:
base = assignment(id="source-1", identity_id="identity-1", account_id=None)
item = assignment(
@@ -136,6 +156,27 @@ class AssignmentWorkflowTests(unittest.TestCase):
),
)
def test_acting_for_assignment_rejects_function_that_forbids_representation(self) -> None:
base = assignment(id="source-1")
item = assignment(
id="assignment-2",
identity_id="identity-2",
account_id="acting-account",
source="acting_for",
delegated_from_assignment_id="source-1",
acting_for_account_id="account-1",
)
self.assert_invalid(
"This organization function does not allow acting in place.",
lambda: validate_assignment_source_rules( # type: ignore[arg-type]
item,
function=function(act_in_place_allowed=False),
base=base,
account_linked_to_identity=lambda _identity_id, _account_id: False,
),
)
def test_update_plan_does_not_mutate_until_applied(self) -> None:
item = assignment()