Federal Modernization Workbook
Chapter 04 — Access from Attributes and the Mover’s Residual Review
An automatic policy removes what its rule granted. It never removes what someone granted by hand, and a move is exactly when that access stops being right.
The code behind the guide’s Gate 4, “department access by automatic assignment; requests approved by the supervisor of record; residual access reviewed after a move.” You start with Learn’s procedure for an automatic assignment policy: a membership rule on user attributes, assignments created and removed as users enter and leave scope, a grace period, and Learn’s warnings about overlapping policies, the 15,000-identity limit and the memberOf quarantine. Then you use examples/hr-lifecycle for what Learn leaves to you: the policies as reviewed files, a lint of nine rules that evaluates each rule over a population to find overlap, a scan of the tenant’s policies for memberOf, and the mover’s residual review with the transfer-to-review interval. Lab 1 runs on a laptop with no tenant; Lab 2 uses a test tenant.
Jump to a slide (18)
Deck · Narrated deck (about 11 minutes, captions) · Script · Captions. Recorded on November 2, 2026 from the approved chapter.
Goal and audience
Goal. By the end of this chapter you can keep every access package assignment policy as a file a reviewer approves, catch the policy mistakes that silently drop or keep access before they reach the tenant, find the policies the November 3, 2026 change will quarantine, and hand the manager of record a sheet of exactly what a mover still holds outside the rules. For each rule you can say whether it is Learn’s contract, Learn’s warning, or the program’s design.
Audience. Developers who build the project’s access automation, the entitlement management owners, and the reviewers of access policy. You should read Python and JSON and know Microsoft Entra dynamic membership rules. You don’t need a tenant until Lab 2.
Why this gate needs code
The guide’s Step 4 assigns department packages by rule, one automatic policy per package, no memberOf, a grace period for movers, manager approval for requests and a review of the residual. Book 03, Chapter 10 holds the design: its rule patterns, its access packages and reviews and where dynamic membership stops; Chapter 08’s mover says what the move must start. Each rule is a property of a policy body or a fact about a user’s holdings, so code can check it every time instead of once.
Which cloud, for these reads
The tools read Microsoft Graph on the cloud --cloud names: graph.microsoft.com for GCC, graph.microsoft.us for GCC High, dod-graph.microsoft.us for DoD. Learn marks Create assignmentPolicies as available in US Government L4 and L5,1 and entitlement management, access reviews and dynamic groups as available in Azure Government; GCC uses the commercial service.2 One gap matters to this gate: administrative units with dynamic membership rules exist only in the global Azure cloud, not in Azure Government, so a GCC High tenant can’t scope administration by a rule-based unit; the guide’s interim path covers it.3
Start with Microsoft’s procedure
What Microsoft gives you
Learn’s Configure an automatic assignment policy for an access package page is the procedure.4 A policy carries a membership rule in the dynamic group syntax; entitlement management creates assignments for users who match and removes them for users who stop matching; a grace period can keep access for a while after a user leaves scope. Learn suggests one automatic policy per package, and supports more only if no user ever matches two at once or moves from one to another, “otherwise users can lose access.” It creates a hidden dynamic group per policy that nobody should modify or reuse, allows at most 15,000 identities in a policy’s rule, and needs Identity Governance Administrator: catalog owners and access package managers can’t create automatic policies. From November 3, 2026, a policy whose rule uses memberOf is quarantined: it stays, but adds and removes nothing until the operator is removed; Learn publishes a read-only script to find them.5
Learn’s Create assignmentPolicies page, example 3, gives the request: POST /identityGovernance/entitlementManagement/assignmentPolicies with allowedTargetScope specificDirectoryUsers, an attributeRuleMembers target holding the rule, automaticRequestSettings and the access package, with EntitlementManagement.ReadWrite.All.6 accessPackageAutomaticRequestSettings has three properties: requestAccessForAllowedTargets, removeAccessWhenTargetLeavesAllowedTargets and gracePeriodBeforeAccessRemoval.7 For request policies, requestorManager makes the requester’s manager the approver, and Learn says to include another approver in case the requester has no manager.8
What it leaves to you
Learn gives the portal steps and one request body. It doesn’t keep policies where a reviewer approves them, test a rule against the population before it goes live, find overlap between two policies, or say what a move leaves outside the rules. Each row is pinned by a test in tests/test_joiners_and_access.py.
| Learn, as published | Why it matters | What examples/hr-lifecycle adds |
Test |
|---|---|---|---|
| One example body | A policy written by hand in the portal isn’t reviewed or kept | One file per policy in config/access, the v1.0 body plus a _program block (tier, resource groups) |
test_the_shipped_policies_lint_clean |
memberOf policies quarantined from November 3, 2026; a script to find them |
A quarantined policy stops removing access without an error | AA-01 refuses memberOf in a file; memberof-scan runs the same test over the tenant’s export, for the pipeline |
test_the_bad_policies_raise_each_rule, test_memberof_scan_over_an_export |
| More than one automatic policy only if users never match two or move between them | A user in two policies can lose access | AA-02 evaluates each rule over a population: overlap is a Finding, two disjoint policies a Warn | test_the_bad_policies_raise_each_rule, test_disjoint_policies_on_one_package_warn_instead |
| At most 15,000 identities per policy | A rule over the limit doesn’t cover everyone | AA-03 from the declared or evaluated scope | test_scope_over_learns_limit |
| A rule on any user property | A rule on a department name breaks on a rename | AA-04 warns on a rule with no coded slot or worker type | test_the_bad_policies_raise_each_rule |
| Create and remove are checkboxes | A policy that creates but never removes keeps a mover’s access forever | AA-05 needs both | test_the_bad_policies_raise_each_rule |
| A grace period of any length | A long grace is a second job | AA-06 warns over 14 days (Design guidance) | test_the_bad_policies_raise_each_rule |
| — | A privileged role granted by rule is standing privilege | AA-09 refuses automatic assignment for tier T0, T1 or privileged packages | test_the_bad_policies_raise_each_rule |
requestorManager with another approver |
A request from someone with no manager waits forever | AA-07 needs the manager and a fallback | test_the_bad_policies_raise_each_rule |
| The rule syntax in the portal’s builder | A rule nobody evaluated grants something unexpected | A small evaluator for the subset the policies use, which refuses anything else | test_evaluator_handles_the_subset_and_refuses_the_rest |
| — | A move leaves requested and hand-granted access behind | residual classifies everything the mover holds and writes the review sheet |
test_residual_review_lists_requested_and_outside_package_access |
| — | Nobody measures how long the review took | The transfer-to-review interval, and RV-02 when it runs past ten days | test_a_completed_review_gives_the_interval |
The verdict. Keep Learn’s body and its checkboxes as they are, and follow its warnings as rules the code enforces. Add the population test, the export scan and the residual review, which Learn leaves to the operator.
The code: what examples/hr-lifecycle adds
Dynamic group rules as code, with a full parser, a lint and the shadow diff that shows a membership change before it happens, are Book 19, Chapter 04 (examples/dynamic-rules); this chapter’s evaluator handles only the subset its policies use and sends anything else there. The validation kit’s VK-DG-05 compares group counts with a snapshot taken before a test move, and VK-HR-03 reconciles counts per organization code with HR (the validation kit’s README). The mover workflow that removes the old package and notifies the manager is Chapter 03’s mover.json. Eligibility for privileged groups through PIM is Building Privileged Access, Chapter 05. This chapter adds python/access_assignment.py, the three policy files and their fixtures.
The policy files
config/access holds two automatic policies, one per department package, each on the organization code in extensionAttribute10 and the worker type, with a seven-day grace; and one request policy for infrastructure administrator eligibility, where ORG-210 employees may request and the manager approves, with a fallback. The request policy grants eligibility only: the role is activated through PIM, as the guide’s Gate 4, step 7 requires. Each file’s _program block holds its tier, the package’s name, the expected scope, and the groups the package grants, which the residual review uses.
The lint
| Rule | Severity | What it flags | Source |
|---|---|---|---|
| AA-01 | Finding | A rule that uses memberOf |
Learn: quarantined from November 3, 20269 |
| AA-02 | Finding; Warn | Two automatic policies on one package that a user matches at once (Finding); two disjoint ones (Warn) | Learn10 |
| AA-03 | Finding | More than 15,000 identities in scope | Learn11 |
| AA-04 | Warn | A rule on no coded slot or worker type | Design guidance, from Book 03, Chapter 10 |
| AA-05 | Finding | An automatic policy that doesn’t both create and remove | Design guidance; Learn’s defaults12 |
| AA-06 | Warn | A grace period over 14 days | Design guidance, from Book 04, Chapter 09 |
| AA-07 | Finding | A request policy without the manager as approver and a fallback | Learn13; the guide’s Gate 4, step 5 |
| AA-08 | Warn | A rule outside the evaluator’s subset | — (lint it with examples/dynamic-rules) |
| AA-09 | Finding | A tier T0, T1 or privileged package assigned by rule | The guide’s Gate 4, step 7; SCuBA MS.AAD.7.5v114 |
The evaluator reads -eq, -ne, -in, -startsWith, -and, -or, -not and parentheses over user.<property>, compares strings without regard to case, and raises an error for any other operator. The population file is a list of users with their coded attributes; a real run uses an export of the users in scope.
The mover’s residual review
residual takes the mover (old and new attributes, the effective date, and the review’s completion once it closes), the mover’s access package assignments, their group memberships and the policy files. For each assignment, it re-evaluates the automatic policy’s rule against the new attributes: kept by rule, or removed by rule at the end of the grace period, with the date. An assignment from any other policy is requested: review. For each group, a dynamic group is rule-managed, a group a held package grants is package-managed, and anything else is outside packages: review. The review items go to residual-review.csv for the manager of record; summary.json holds the transfer-to-review interval (PS-5’s metric) once the review closes, which Chapter 07 reads. The reads are task access-read:
| Call | Least-privileged permission | Delegated role |
|---|---|---|
| List assignment policies | EntitlementManagement.Read.All15 |
— |
| List the mover’s access package assignments | EntitlementManagement.Read.All16 |
— |
| List the mover’s group memberships, direct and transitive | User.Read.All (application)17 |
— |
The code reads transitiveMemberOf rather than memberOf on purpose: for another user’s direct memberships Learn’s least-privileged application permission is Directory.Read.All, while the transitive call needs only User.Read.All.1819
The tests
This chapter’s tests are the nine in the Chapter 04 block of tests/test_joiners_and_access.py: the evaluator, the shipped policies, each rule on the broken files, disjoint policies, the scope limit, the export scan, the residual’s classes, the completed review’s interval and the review sheet. The kit’s 57 tests pass under Python 3.10 or later with no package, tenant or network.
The commands below run against the repository’s policy files and fictional exports. They show what the code does, not that a tenant returns the same shapes. Review the files, test each rule against a real population export, and run Lab 2 in a test tenant, before a policy reaches production.
Lab 1: fixtures, no tenant
You need Python 3.10 or later. Work in examples/hr-lifecycle.
Every command in this lab, in order, from examples/hr-lifecycle. Each step below says what it does and what to look for.
# Step 1: List the reads and their permissions
python python/graph_export.py --task access-read --list
# Step 2: Lint the program's policies over the population
python python/access_assignment.py lint config/access --population tests/fixtures/ch04/population.json
# Step 3: Lint five broken policies
python python/access_assignment.py lint tests/fixtures/ch04/bad-access --population tests/fixtures/ch04/population.json
# Step 4: Scan the tenant's policies for memberOf
python python/access_assignment.py memberof-scan --policies tests/fixtures/ch04/assignment-policies.json
# Step 5: List a mover's residual
python python/access_assignment.py residual --mover tests/fixtures/ch04/mover.json --assignments tests/fixtures/ch04/mover-assignments.json \
--memberships tests/fixtures/ch04/mover-memberships.json --policies config/access --as-of 2026-11-01T12:00:00Z --cloud gcc --out out/ch04/residual
# Step 5: List a mover's residual
cat out/ch04/residual/residual-review.csv
# Step 6: Run the tests
python -m pytest tests -q
# After the steps
rm -rf out/ch04List the reads and their permissions:
python python/graph_export.py --task access-read --listTask access-read: EntitlementManagement.Read.All, User.Read.All GET /identityGovernance/entitlementManagement/assignmentPolicies [EntitlementManagement.Read.All] GET /identityGovernance/entitlementManagement/assignments [EntitlementManagement.Read.All] GET /users/{userId}/transitiveMemberOf/microsoft.graph.group [User.Read.All]What the terminal shows, run on the kit’s fixtures (the same output as text) Lint the program’s policies over the population:
python python/access_assignment.py lint config/access --population tests/fixtures/ch04/population.jsonPolicies: 3 files Lint: 0 Finding, 0 Warn, 0 InfoWhat the terminal shows, run on the kit’s fixtures (the same output as text) Lint five broken policies:
python python/access_assignment.py lint tests/fixtures/ch04/bad-access --population tests/fixtures/ch04/population.jsonPolicies: 5 files Lint: 5 Finding, 2 Warn, 0 Info AA-01 Finding: AUTO via group membership: rule uses memberOf; from November 3, 2026 the policy is quarantined and adds and removes nothing AA-02 Finding: auto-by-department.json, auto-second-policy.json: 2 users match more than one automatic policy on one package; they can lose access AA-05 Finding: AUTO infra admin eligibility: automatic policy doesn't both create and remove assignments AA-07 Finding: REQUEST with no fallback: request policy doesn't route to the manager with a fallback approver AA-09 Finding: AUTO infra admin eligibility: tier T1 access assigned by rule; privileged access is requested and activated through PIM AA-04 Warn: AUTO Program Ops by department name: rule reads no coded slot or worker type; write it on the codes HR owns AA-06 Warn: AUTO Program Ops by department name: grace period 30 days is over the program's 14What the terminal shows, run on the kit’s fixtures (the same output as text) The exit code is 2. The two Program Operations policies overlap on the two employees of ORG-100 in the population: one rule reads the department name, the other every employee.
Scan the tenant’s policies for
memberOf:python python/access_assignment.py memberof-scan --policies tests/fixtures/ch04/assignment-policies.jsonAssignment policies read: 4; using memberOf: 1 MS-01 Finding: Shared drives (legacy): AUTO via group membershipWhat the terminal shows, run on the kit’s fixtures (the same output as text) Learn publishes its own script for the same test, which runs against a tenant (Lab 2 runs both); this scan is the same test in the pipeline, over the export
graph_export.pywrites.List a mover’s residual:
python python/access_assignment.py residual --mover tests/fixtures/ch04/mover.json --assignments tests/fixtures/ch04/mover-assignments.json \ --memberships tests/fixtures/ch04/mover-memberships.json --policies config/access --as-of 2026-11-01T12:00:00Z --cloud gcc --out out/ch04/residualMover jordan.diaz@contoso.example: 8 items held, 3 to the residual review Residual: 1 Finding, 3 Warn, 0 Info Written to out/ch04/residual (residual-review.csv goes to the manager of record)What the terminal shows, run on the kit’s fixtures (the same output as text) Jordan moved from ORG-100 to ORG-210 on October 19.
residual.jsonshows the Program Operations baseline removed by rule after grace on October 26, the Infrastructure Services baseline kept by rule, the two packages’ app groups package-managed and the ORG-210 dynamic group rule-managed. The review sheet holds the other three:cat out/ch04/residual/residual-review.csvitem,kind,via,class Grants reporting workspace,access package,REQUEST Grants reporting,requested: review APP-Legacy-Timekeeping-Approvers,group,direct or nested assignment,outside packages: review SHARE-ProgramOps-Finance,group,direct or nested assignment,outside packages: reviewWhat the terminal shows, run on the kit’s fixtures (the same output as text) The Finding is RV-02: the review is still open 13 days after the move, past the program’s ten.
Run the tests:
python -m pytest tests -qWhat the terminal shows, run on the kit’s fixtures (the same output as text)
Expected result: 57 tests passing. Delete out/ch04 when you’re done:
rm -rf out/ch04Lab 2: test tenant
Not run by the authors. Use a test tenant in the worldwide or GCC cloud with Microsoft Entra ID Governance trial or test licenses, never production, two test access packages and three test users with coded attributes. Work in examples/hr-lifecycle. You’ll need my-mover.json: the moved test user, in the layout of tests/fixtures/ch04/mover.json. You’ll replace: <graph-token> (a bearer token for graph.microsoft.com issued to the reader app), <test-user-id> (the moved test user’s object ID), <tenant-id> (the test tenant’s ID), <test-package-id> (your test access package’s ID), <policy-id> (the ID of the policy step 2 creates), <before-run-id> (the run folder the validation kit wrote before the move), <now> (the current time, such as 2026-11-05T12:00:00Z), <review-completed> (when the test manager completed the review) and <later> (a time after the review’s interval).
Every command in this lab, in order, from examples/hr-lifecycle. Each step below says what it does and what to look for.
In a POSIX shell (Git Bash on Windows):
# Step 1: Export
export HRLC_GRAPH_TOKEN='<graph-token>'
python python/graph_export.py --task access-read --cloud gcc --param userId=<test-user-id> --out exports/access
python python/access_assignment.py memberof-scan --policies exports/access/assignment-policies.json
# Step 2: Create one policy
mkdir -p my-access
python -c "import json; d=json.load(open('config/access/auto-program-operations.json')); d['accessPackage']['id'] = '<test-package-id>'; d['automaticRequestSettings']['gracePeriodBeforeAccessRemoval'] = 'PT1H'; json.dump(d, open('my-access/auto-program-operations.json', 'w'), indent=2)"
python python/access_assignment.py lint my-access --population tests/fixtures/ch04/population.json
python -c "import json; d=json.load(open('my-access/auto-program-operations.json')); d.pop('_program'); json.dump(d, open('my-access-body.json', 'w'), indent=2)"In PowerShell 7:
# Step 2: Create one policy
Connect-MgGraph -Environment Global -TenantId <tenant-id> -Scopes EntitlementManagement.ReadWrite.All
New-MgEntitlementManagementAssignmentPolicy -BodyParameter (Get-Content my-access-body.json -Raw | ConvertFrom-Json -AsHashtable)
# Step 3: Move a test user
../validation-kit/Invoke-ValidationKit.ps1 -OutputPath out/ch04-vk-before -CheckId VK-DG-05
# Step 3: Move a test user
../validation-kit/Invoke-ValidationKit.ps1 -OutputPath out/ch04-vk-after -CheckId VK-DG-05 -InputPath out/ch04-vk-before/<before-run-id>/SnapshotIn a POSIX shell (Git Bash on Windows):
# Step 4: Run the residual
python python/graph_export.py --task access-read --cloud gcc --param userId=<test-user-id> --out exports/access-after
python python/access_assignment.py residual --mover my-mover.json --assignments exports/access-after/mover-assignments.json \
--memberships exports/access-after/mover-memberships.json --policies my-access --as-of <now> --cloud gcc --out out/ch04/residual-test
# Step 4: Run the residual
python -c "import json; p='my-mover.json'; d=json.load(open(p)); d['reviewCompletedDateTime'] = '<review-completed>'; json.dump(d, open(p, 'w'), indent=2)"
python python/access_assignment.py residual --mover my-mover.json --assignments exports/access-after/mover-assignments.json \
--memberships exports/access-after/mover-memberships.json --policies my-access --as-of <later> --cloud gcc --out out/ch04/residual-test-2In PowerShell 7:
# Step 5: Back it out
Remove-MgEntitlementManagementAssignmentPolicy -AccessPackageAssignmentPolicyId <policy-id>In a POSIX shell (Git Bash on Windows):
# Step 5: Back it out
rm -rf exports out/ch04 out/ch04-vk-before out/ch04-vk-after my-access my-access-body.jsonExport. Register a reader app with a certificate, grant
EntitlementManagement.Read.AllandUser.Read.Allwith admin consent, put a token inHRLC_GRAPH_TOKEN, export and scan:export HRLC_GRAPH_TOKEN='<graph-token>' python python/graph_export.py --task access-read --cloud gcc --param userId=<test-user-id> --out exports/access python python/access_assignment.py memberof-scan --policies exports/access/assignment-policies.jsonRun Learn’s read-only script beside it; the two lists must match.
Create one policy. The first two commands write a copy of
auto-program-operations.jsonpointed at your test package, with a grace period ofPT1Hfor the test; the third lints the copy, and the fourth writes its body without_program:mkdir -p my-access python -c "import json; d=json.load(open('config/access/auto-program-operations.json')); d['accessPackage']['id'] = '<test-package-id>'; d['automaticRequestSettings']['gracePeriodBeforeAccessRemoval'] = 'PT1H'; json.dump(d, open('my-access/auto-program-operations.json', 'w'), indent=2)" python python/access_assignment.py lint my-access --population tests/fixtures/ch04/population.json python -c "import json; d=json.load(open('my-access/auto-program-operations.json')); d.pop('_program'); json.dump(d, open('my-access-body.json', 'w'), indent=2)"Send it as an Identity Governance Administrator with
New-MgEntitlementManagementAssignmentPolicy,20 in PowerShell 7:Connect-MgGraph -Environment Global -TenantId <tenant-id> -Scopes EntitlementManagement.ReadWrite.All New-MgEntitlementManagementAssignmentPolicy -BodyParameter (Get-Content my-access-body.json -Raw | ConvertFrom-Json -AsHashtable)Wait several minutes, as Learn says, and confirm the matching test users receive assignments.
Move a test user. Take the validation kit’s snapshot first:
../validation-kit/Invoke-ValidationKit.ps1 -OutputPath out/ch04-vk-before -CheckId VK-DG-05Change the user’s organization code through the pipeline (never by hand: Chapter 01), wait for the grace period you set (
PT1Hin the test copy), and confirm the old assignment is removed and the new one created. Run VK-DG-05 against the snapshot you took before the move:../validation-kit/Invoke-ValidationKit.ps1 -OutputPath out/ch04-vk-after -CheckId VK-DG-05 -InputPath out/ch04-vk-before/<before-run-id>/SnapshotRun the residual. Export the user’s assignments and memberships again and run
residual:python python/graph_export.py --task access-read --cloud gcc --param userId=<test-user-id> --out exports/access-after python python/access_assignment.py residual --mover my-mover.json --assignments exports/access-after/mover-assignments.json \ --memberships exports/access-after/mover-memberships.json --policies my-access --as-of <now> --cloud gcc --out out/ch04/residual-testGive the sheet to the test manager. Record the review’s completion in the mover file, and run it again for the interval:
python -c "import json; p='my-mover.json'; d=json.load(open(p)); d['reviewCompletedDateTime'] = '<review-completed>'; json.dump(d, open(p, 'w'), indent=2)" python python/access_assignment.py residual --mover my-mover.json --assignments exports/access-after/mover-assignments.json \ --memberships exports/access-after/mover-memberships.json --policies my-access --as-of <later> --cloud gcc --out out/ch04/residual-test-2Back it out. Remove the test policy with
Remove-MgEntitlementManagementAssignmentPolicy,21 choosing in the portal whether its assignments are removed, as the guide’s rollback says:Remove-MgEntitlementManagementAssignmentPolicy -AccessPackageAssignmentPolicyId <policy-id>Restore the test user’s code through the pipeline; remove the reader app, its permissions and certificate; delete
exportsandout/ch04, with the lab’s policy copy and kit runs:rm -rf exports out/ch04 out/ch04-vk-before out/ch04-vk-after my-access my-access-body.json
Verified facts
| Item | Value | US Government clouds | Stage | Source |
|---|---|---|---|---|
| Automatic assignment policy | Rule on user attributes; assignments created and removed; grace period; one per package unless scopes never overlap; hidden dynamic group per policy; at most 15,000 identities; Identity Governance Administrator, not catalog owners or access package managers | — | GA | 22 |
memberOf in a policy rule |
Quarantined from November 3, 2026: no assignments added or removed until removed; read-only script | — | — | 23 |
| Create assignmentPolicies | POST /identityGovernance/entitlementManagement/assignmentPolicies; EntitlementManagement.ReadWrite.All; example 3 with attributeRuleMembers and automaticRequestSettings |
L4 and L5 | v1.0 | 24 |
accessPackageAutomaticRequestSettings |
requestAccessForAllowedTargets, removeAccessWhenTargetLeavesAllowedTargets, gracePeriodBeforeAccessRemoval |
— | v1.0 | 25 |
requestorManager |
The requester’s manager as approver, managerLevel 1 or 2; include another approver |
— | v1.0 | 26 |
| List assignments and policies | EntitlementManagement.Read.All (application, across catalogs) |
— | v1.0 | 27 |
| A user’s memberships, direct and transitive | User.Read.All (application) |
L4 and L5 | v1.0 | 28 |
| A user’s direct memberships | Directory.Read.All (application) |
— | v1.0 | 29 |
| Dynamic administrative units | Global Azure only, not Azure Government | Not available | — | 30 |
| Entitlement management, access reviews, PIM, dynamic groups | Available in Azure Government | Available | GA | 31 |
Places where Learn’s pages differ or stop, recorded so a later edit doesn’t loosen the code:
- The manager as approver.
requestorManageris documented for approval stages, with the instruction to add another approver; the code requires the fallback rather than treating it as optional.32 - Overlap. Learn says overlapping automatic policies on one package can lose access but gives no test for overlap. AA-02 evaluates the rules over a population, which finds overlap only among the users the population holds; a transition between policies over time can’t be seen from one population and stays a Warn.
Federal requirements and evidence
This chapter supports the guide’s Gate 4. The Gate 4 requirements table maps it to NIST SP 800-53 AC-2 (authorize access on organization-defined attributes, and account review), AC-6 (least privilege), AC-5 (separation of duties), PS-5 (personnel transfer) and CISA SCuBA MS.AAD.7.5v1 (highly privileged roles only through a privileged access management system, required under BOD 25-01); the federal framework crosswalk lists each source. What this code gives the ISSO:
| Requirement | KSI | What this code gives | What the ISSO keeps |
|---|---|---|---|
| AC-2 and AC-6, assignment on organization-defined attributes and least privilege, Moderate | KSI-IAM-JIT (AC-2); KSI-IAM-ELP, KSI-IAM-JIT (AC-6) | The reviewed policy files, the lint’s clean run and the hash of each body sent | The assignment policy exports with their rules |
AC-2, the memberOf quarantine |
KSI-IAM-JIT | The memberof-scan output |
Zero rows, as the guide’s Validate step asks |
| CISA SCuBA MS.AAD.7.5v1 (BOD 25-01 required) | KSI-IAM-JIT, as Book 04, Chapter 10 maps PIM | AA-09’s clean run: no privileged role reached by rule | The lint output beside the ScubaGear report |
| PS-5, personnel transfer, and AC-2 account review | KSI-IAM-ELP, KSI-IAM-JIT (PS-5) | Each mover’s residual sheet and the transfer-to-review interval | residual-review.csv, the manager’s decisions, and summary.json |
| AC-5, separation of duties | KSI-IAM-JIT | None: this code doesn’t read them | The separation-of-duties settings in the package configuration (Book 04, Chapter 09) |
Each scripted step in the labs, and each file the code writes, with the evidence it leaves:
| Lab step | What it runs or writes | Requirement | KSI | Evidence |
|---|---|---|---|---|
| Lab 1, step 1 | graph_export.py --task access-read --list |
AC-6, least privilege: GET calls only | KSI-IAM-ELP | The printed list, and config/calls.json as reviewed |
| Lab 1, steps 2 and 3 | access_assignment.py lint (AA-01 to AA-09) on the program’s policies and on the broken ones |
AC-2; AC-6; MS.AAD.7.5v1 (AA-09); CM-3(2), validated before merge | KSI-IAM-JIT; KSI-IAM-ELP; KSI-CMT-VTD | The clean run on config/access, filed with the change |
| Lab 1, step 4 | access_assignment.py memberof-scan |
AC-2 | KSI-IAM-JIT | using memberOf: 0 on the tenant’s export |
| Lab 1, step 5 | access_assignment.py residual, writing residual.json, residual-review.csv, summary.json and findings.json |
PS-5; AC-2 account review | KSI-IAM-ELP, KSI-IAM-JIT | out/ch04/residual/residual-review.csv for the manager; summary.json with the transfer-to-review interval |
| Lab 1, step 5 | manifest.json |
AU-9; SI-7 | KSI-MLA-OSM; KSI-SVC-VRI | A SHA-256 per file, written once |
| Lab 1, step 6 | python -m pytest tests -q |
CM-3(2) (design guidance) | KSI-CMT-VTD | 57 passed, kept with the change |
| Lab 1, clean-up | rm -rf out/ch04 |
None: fixture output isn’t evidence | — | — |
| Lab 2, step 1 | graph_export.py --task access-read, writing assignment-policies.json, mover-assignments.json, mover-memberships.json and export.json; memberof-scan |
AC-2; AC-6 for GET only | KSI-IAM-JIT; KSI-IAM-ELP | exports/access/export.json and the scan’s zero rows |
| Lab 2, step 2 | Copy the policy; lint; write my-access-body.json; New-MgEntitlementManagementAssignmentPolicy |
AC-2; AC-6; CM-3 for the change | KSI-IAM-JIT; KSI-IAM-ELP; KSI-CMT-LMC | The lint output, the body file, and the directory audit record of the create |
| Lab 2, step 3 | Invoke-ValidationKit.ps1 -CheckId VK-DG-05, before and after the move |
PS-5 | KSI-IAM-ELP, KSI-IAM-JIT | The kit’s run folders under out/ch04-vk-before and out/ch04-vk-after |
| Lab 2, step 4 | Export again; residual into out/ch04/residual-test; record the review’s completion; residual into out/ch04/residual-test-2 |
PS-5; AC-2 account review | KSI-IAM-ELP, KSI-IAM-JIT | Both folders’ residual-review.csv and summary.json, with their manifests |
| Lab 2, step 5 | Remove-MgEntitlementManagementAssignmentPolicy; rm -rf the lab files |
CM-3: the change backed out | KSI-CMT-LMC | The directory audit record of the removal |
KSIs are FedRAMP 20x indicators for cloud service providers: the agency authorizes its own system and lines this evidence up with what its providers report against them, and it doesn’t report KSIs itself (Book 00, Appendix B). Each KSI shown lists the row’s control among its related SP 800-53 controls on FedRAMP’s page;33 which file supports which indicator is the program’s mapping (design guidance). Controls the guide’s table doesn’t name (AU-9, CM-3, CM-3(2), SI-7) are in the Moderate baseline (Book 13, Appendix A; Book 14, Appendix A).
Dangers
| Danger | What happens | The guard |
|---|---|---|
memberOf in a rule after November 3, 2026 |
The policy is quarantined and stops removing access | AA-01 for files; memberof-scan for the tenant |
| Two automatic policies a user can match | The user can lose access | AA-02 over the population; one policy per package |
| A rule on a display name | A rename drops everyone from the package | AA-04 |
| A policy that never removes | A mover keeps the old department’s access | AA-05 |
| A long grace | Access outlives the handover | AA-06; zero for privileged access by design |
| Privileged access by rule | Standing privilege no one requested | AA-09; eligibility by request, activation through PIM |
| A request with no fallback approver | Requests from users without a manager wait to expire | AA-07 |
| Hand-granted access on a mover | It stays forever, because no rule granted it | residual and the review sheet; RV-02 when the review runs late |
| An edit to the hidden dynamic group | Entitlement management overwrites it, or the policy misbehaves | Never touched by the code; Learn says not to modify it |
| A rule the evaluator can’t read | A lint that pretends to understand it | The evaluator raises an error; AA-08 sends the rule to examples/dynamic-rules |
Checklist
Footnotes
Microsoft Learn, “Create assignmentPolicies,” national cloud deployments, permissions and example 3, https://learn.microsoft.com/graph/api/entitlementmanagement-post-assignmentpolicies?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “Microsoft Entra feature availability,” https://learn.microsoft.com/entra/identity/authentication/feature-availability (read November 1, 2026; carried from the HR-driven identity implementation guide’s verification log).↩︎
Microsoft Learn, “Manage users or devices for an administrative unit with rules for dynamic membership groups,” prerequisites, https://learn.microsoft.com/entra/identity/role-based-access-control/admin-units-members-dynamic (read November 1, 2026; carried from the HR-driven identity implementation guide’s verification log).↩︎
Microsoft Learn, “Configure an automatic assignment policy for an access package in entitlement management,” https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-auto-assignment-policy (read November 1, 2026).↩︎
Microsoft Learn, “Configure an automatic assignment policy for an access package in entitlement management,” https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-auto-assignment-policy (read November 1, 2026).↩︎
Microsoft Learn, “Create assignmentPolicies,” national cloud deployments, permissions and example 3, https://learn.microsoft.com/graph/api/entitlementmanagement-post-assignmentpolicies?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “accessPackageAutomaticRequestSettings resource type,” https://learn.microsoft.com/graph/api/resources/accesspackageautomaticrequestsettings?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “requestorManager resource type,” https://learn.microsoft.com/graph/api/resources/requestormanager?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “Configure an automatic assignment policy for an access package in entitlement management,” https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-auto-assignment-policy (read November 1, 2026).↩︎
Microsoft Learn, “Configure an automatic assignment policy for an access package in entitlement management,” https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-auto-assignment-policy (read November 1, 2026).↩︎
Microsoft Learn, “Configure an automatic assignment policy for an access package in entitlement management,” https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-auto-assignment-policy (read November 1, 2026).↩︎
Microsoft Learn, “Configure an automatic assignment policy for an access package in entitlement management,” https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-auto-assignment-policy (read November 1, 2026).↩︎
Microsoft Learn, “requestorManager resource type,” https://learn.microsoft.com/graph/api/resources/requestormanager?view=graph-rest-1.0 (read November 1, 2026).↩︎
Cybersecurity and Infrastructure Security Agency, “BOD 25-01: Implementing Secure Practices for Cloud Services Required Configurations,” MS.AAD.7.5v1, https://www.cisa.gov/resources-tools/services/bod-25-01-implementing-secure-practices-cloud-services-required-configurations (read November 1, 2026; carried from the HR-driven identity implementation guide’s verification log).↩︎
Microsoft Learn, “View, add, and remove assignments for an access package in entitlement management,” view assignments programmatically, https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-assignments, and “Change request settings for an access package in entitlement management,” create an access package assignment policy programmatically, https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-request-policy (read November 1, 2026).↩︎
Microsoft Learn, “View, add, and remove assignments for an access package in entitlement management,” view assignments programmatically, https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-assignments, and “Change request settings for an access package in entitlement management,” create an access package assignment policy programmatically, https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-request-policy (read November 1, 2026).↩︎
Microsoft Learn, “List a user’s memberships (direct and transitive),” permissions for another user’s memberships, https://learn.microsoft.com/graph/api/user-list-transitivememberof?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “List a user’s direct memberships,” permissions for another user’s direct memberships, https://learn.microsoft.com/graph/api/user-list-memberof?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “List a user’s memberships (direct and transitive),” permissions for another user’s memberships, https://learn.microsoft.com/graph/api/user-list-transitivememberof?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “New-MgEntitlementManagementAssignmentPolicy,” https://learn.microsoft.com/powershell/module/microsoft.graph.identity.governance/new-mgentitlementmanagementassignmentpolicy?view=graph-powershell-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “Remove-MgEntitlementManagementAssignmentPolicy,” https://learn.microsoft.com/powershell/module/microsoft.graph.identity.governance/remove-mgentitlementmanagementassignmentpolicy?view=graph-powershell-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “Configure an automatic assignment policy for an access package in entitlement management,” https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-auto-assignment-policy (read November 1, 2026).↩︎
Microsoft Learn, “Configure an automatic assignment policy for an access package in entitlement management,” https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-auto-assignment-policy (read November 1, 2026).↩︎
Microsoft Learn, “Create assignmentPolicies,” national cloud deployments, permissions and example 3, https://learn.microsoft.com/graph/api/entitlementmanagement-post-assignmentpolicies?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “accessPackageAutomaticRequestSettings resource type,” https://learn.microsoft.com/graph/api/resources/accesspackageautomaticrequestsettings?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “requestorManager resource type,” https://learn.microsoft.com/graph/api/resources/requestormanager?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “View, add, and remove assignments for an access package in entitlement management,” view assignments programmatically, https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-assignments, and “Change request settings for an access package in entitlement management,” create an access package assignment policy programmatically, https://learn.microsoft.com/entra/id-governance/entitlement-management-access-package-request-policy (read November 1, 2026).↩︎
Microsoft Learn, “List a user’s memberships (direct and transitive),” permissions for another user’s memberships, https://learn.microsoft.com/graph/api/user-list-transitivememberof?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “List a user’s direct memberships,” permissions for another user’s direct memberships, https://learn.microsoft.com/graph/api/user-list-memberof?view=graph-rest-1.0 (read November 1, 2026).↩︎
Microsoft Learn, “Manage users or devices for an administrative unit with rules for dynamic membership groups,” prerequisites, https://learn.microsoft.com/entra/identity/role-based-access-control/admin-units-members-dynamic (read November 1, 2026; carried from the HR-driven identity implementation guide’s verification log).↩︎
Microsoft Learn, “Microsoft Entra feature availability,” https://learn.microsoft.com/entra/identity/authentication/feature-availability (read November 1, 2026; carried from the HR-driven identity implementation guide’s verification log).↩︎
Microsoft Learn, “requestorManager resource type,” https://learn.microsoft.com/graph/api/resources/requestormanager?view=graph-rest-1.0 (read November 1, 2026).↩︎
FedRAMP, “Key Security Indicators,” FedRAMP Consolidated Rules for 2026, indicator titles and “Related SP 800-53 Controls,” including the mapping updates of October 13, 2026, https://www.fedramp.gov/2026/reference/key-security-indicators/ (read November 2, 2026; the indicator IDs are carried from Book 13, Appendix A and Book 14, Chapter 03).↩︎







