Verification & Testing — Control 2.26: Entra Agent ID Identity Governance
Examiner-defensible evidence package for Control 2.26. This playbook produces, signs, and retains the artifacts required to demonstrate to FINRA, SEC, OCC, FFIEC, and internal audit that every Microsoft 365 AI agent identity in the tenant is sponsored, lifecycle-governed, periodically reviewed, and forwarded to the SIEM with 6-year retention.
Scope: Commercial M365 tenants with Microsoft 365 Copilot licensing and Frontier program enrollment. Sovereign clouds (GCC, GCC High, DoD) follow the compensating-control pattern in §8 because the Entra Agent ID feature is not yet generally available in sovereign environments at the time of this playbook's last UI verification.
Companion controls: 1.2 Agent Registry & Integrated Apps Management feeds the SPONSOR namespace (§2). 3.6 Orphaned Agent Detection & Remediation consumes the LIFECYCLE namespace outputs (§4).
Last UI verified: April 2026 against Microsoft Entra admin center build 2026.04.x and Microsoft Graph beta endpoint.
Document Conventions
| Convention | Value |
|---|---|
| PowerShell baseline | PowerShell 7.4+ Core; #Requires -Version 7.4 |
| Test framework | Pester 5.5+ |
| Output discipline | No Write-Host. All evidence emitted as structured objects via Write-Output, then serialized to JSON. |
| Sovereign cloud handling | All Pester suites detect tenant cloud and emit SKIPPED with compensating-control pointer rather than FAIL (see §8). |
| Evidence retention | 6 years (FINRA Rule 4511 / SEC 17a-4) on WORM-protected storage. |
| Hashing | SHA-256 over canonical JSON; chain hashes in attestation.json per §10. |
| Sovereign anchor | All sovereign-aware functions reference ../../_shared/powershell-baseline.md#3-sovereign-cloud-endpoints-gcc-gcc-high-dod. |
Regulatory framing. This playbook helps meet recordkeeping, access-management, and oversight expectations under FINRA Rules 3110 and 4511, FINRA Notice 25-07, SEC Rule 17a-4, SOX §404, GLBA §501(b), OCC Bulletin 2011-12, and FFIEC IT Examination Handbook (Information Security & Management). It does not by itself ensure compliance; organizations should verify findings against their own legal and regulatory obligations and tailor thresholds to their risk appetite.
§0 Pre-Test Prerequisites & Sovereign Cloud Bootstrap
0.1 Operator prerequisites
The operator running this playbook must hold one of the following Entra role assignments, scoped to the tenant under test, and activated through Privileged Identity Management (PIM) for the duration of the test run:
| Role (canonical) | Required for |
|---|---|
| Entra Identity Governance Admin | Access package, access review, and lifecycle workflow read APIs (§3, §4, §5, §6) |
| Entra Agent ID Admin | Agent identity enumeration, sponsor field reads (§2, §9) |
| Entra Security Reader | Audit log reads, sign-in log reads (§7) |
| Purview Compliance Admin (read) | Cross-check audit retention labels in §7 |
| AI Governance Lead | Counter-signs the quarterly attestation packet in §13 |
The Pester suites in §2–§9 are read-only and do not require write permissions. Remediation runbooks referenced from failure paths (e.g., Set-AgentSponsorBulk from the sister PowerShell Setup playbook) require additional write scopes and a separate change ticket.
0.2 Module baseline
#Requires -Version 7.4
#Requires -Modules @{ ModuleName='Microsoft.Graph.Authentication'; ModuleVersion='2.20.0' }
#Requires -Modules @{ ModuleName='Microsoft.Graph.Identity.Governance'; ModuleVersion='2.20.0' }
#Requires -Modules @{ ModuleName='Microsoft.Graph.Identity.SignIns'; ModuleVersion='2.20.0' }
#Requires -Modules @{ ModuleName='Microsoft.Graph.Beta.Applications'; ModuleVersion='2.20.0' }
#Requires -Modules @{ ModuleName='Pester'; ModuleVersion='5.5.0' }
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
0.3 PRE gates (must all pass before §2–§9 execute)
The bootstrap script Invoke-Agt226PreFlight.ps1 runs seven pre-flight gates. Any FAIL halts the suite and emits a single evidence artifact preflight-FAILED-<runId>.json. Any SKIPPED from PRE-06 redirects the run to §8 (sovereign compensating control).
| Gate | ID | Purpose | Failure behavior |
|---|---|---|---|
| Module presence | PRE-01 | Confirms required modules loaded at the pinned versions above | HALT |
| Graph context | PRE-02 | Confirms Connect-MgGraph established with required scopes | HALT |
| Tenant identification | PRE-03 | Captures tenantId, displayName, verifiedDomains[0].name for every evidence record |
HALT |
| Cloud detection | PRE-04 | Reads (Get-MgContext).Environment and maps to Commercial / GCC / GCCH / DoD |
Continue with cloud field set |
| License gate | PRE-05 | Confirms tenant holds at least one Microsoft 365 Copilot SKU (SkuPartNumber -like 'Microsoft_365_Copilot*') |
HALT — Agent ID requires Copilot |
| Frontier enrollment gate | PRE-06 | Confirms tenant is enrolled in the Microsoft Frontier program by probing /beta/agents for HTTP 200 vs 404/403 |
If 404/403 in Commercial: HALT with remediation pointer; if sovereign: route to §8 |
| Clock skew gate | PRE-07 | Compares local UTC to Date header from Graph response; aborts if drift > 60 seconds |
HALT |
0.4 Sovereign bootstrap pattern
function Test-Agt226SovereignTenant {
[CmdletBinding()]
[OutputType([pscustomobject])]
param()
$ctx = Get-MgContext
if (-not $ctx) { throw "PRE-02 failed: no Graph context. Run Connect-MgGraph first." }
$cloud = switch ($ctx.Environment) {
'Global' { 'Commercial' }
'USGov' { 'GCC' }
'USGovDoD' { 'DoD' }
'USGovHigh' { 'GCCH' }
default { 'Unknown' }
}
[pscustomobject]@{
cloud = $cloud
is_sovereign = $cloud -in @('GCC','GCCH','DoD')
tenant_id = $ctx.TenantId
detected_at = (Get-Date).ToUniversalTime().ToString('o')
}
}
When is_sovereign is $true, every Pester It block in §2–§7 and §9 emits:
{ "status": "SKIPPED", "reason": "Entra Agent ID not GA in sovereign cloud at run time", "compensating_control_ref": "§8" }
This produces a defensible audit trail showing the test was attempted, was correctly skipped on regulatory-sound grounds, and was supplemented by the manual attestation in §8 — rather than appearing as an unexplained gap.
0.5 Run identifier
Every test run is tagged with a deterministic runId of the form AGT226-yyyyMMdd-HHmmss-<8charGuid>. The runId is embedded in every evidence record and in the filename of every artifact produced by §10.
§1 Namespace Catalog
The eight Verification Criteria from Control 2.26 are evidenced by eight test namespaces. Each namespace produces independent evidence records that combine into a single signed evidence pack (§10).
| Namespace | Evidences Criterion | Section | Cadence | Owner |
|---|---|---|---|---|
PREVIEW |
C2.26-1 (Agent ID enabled, Frontier + Copilot) | §9 | Per release / monthly | Entra Agent ID Admin |
SPONSOR |
C2.26-2 (every Z2/Z3 agent has assigned sponsor) | §2 | Daily | AI Governance Lead |
ACCESSPKG |
C2.26-3 + C2.26-4 (packages exist; ≤365-day expiry; no perpetual Z3) | §3 | Daily | Entra Identity Governance Admin |
LIFECYCLE |
C2.26-5 (workflow triggers on sponsor departure; manager-transfer succeeds) | §4 | Weekly | Entra Identity Governance Admin |
REVIEW |
C2.26-6 (quarterly Z3 access certification) | §5 | Quarterly | Compliance Officer |
EXPIRY |
C2.26-7 (renewal-with-justification or removal within 24h of expiry) | §6 | Daily | AI Governance Lead |
SIEM |
C2.26-8 (events forwarded to SIEM with 6-year retention) | §7 | Weekly | Entra Security Admin |
SOV |
All criteria — sovereign compensating attestation | §8 | Quarterly (sovereign tenants only) | AI Governance Lead |
Each namespace section in this document follows an identical structure:
- Criterion mapping — explicit pointer to which numbered criterion in Control 2.26 §9 is satisfied.
- Pre-conditions — what must already be true (e.g., access packages exist, Graph scopes granted).
- Pester suite —
Describe "AGT226-{NS}" { Context "Zone {1|2|3}" { It "..." } }with PS 7.4 / Pester 5.5 syntax. - Sample passing JSON evidence record — exact shape that flows into the evidence pack.
- Sample failing JSON evidence record + remediation pointer — links to a numbered triage entry in §11.
- Examiner artifact — filename pattern, retention duration, signing policy.
- Zone 1 / Zone 2 / Zone 3 thresholds — pass/warn/fail bands per zone.
- Regulator mapping — which specific regulatory citation each test supports.
1.1 Evidence record schema (canonical)
Every evidence record produced by every namespace MUST conform to this schema. The schema is enforced by Test-Agt226EvidenceSchema in §10.
{
"control_id": "2.26",
"run_id": "AGT226-20260415-093012-a1b2c3d4",
"run_timestamp": "2026-04-15T09:30:12Z",
"tenant_id": "11111111-2222-3333-4444-555555555555",
"tenant_display_name": "Contoso Bank, N.A.",
"cloud": "Commercial",
"namespace": "SPONSOR",
"criterion": "C2.26-2",
"zone": "2",
"subject_id": "agent-payments-summarizer-001",
"subject_type": "agent_identity",
"status": "PASS",
"assertion": "Agent has non-null sponsor field referencing an active employee",
"observed_value": { "sponsor_id": "user-44ab-...", "sponsor_active": true },
"expected_value": { "sponsor_id": "<non-null>", "sponsor_active": true },
"evidence_artifacts": ["sponsor-snapshot-AGT226-20260415-093012-a1b2c3d4.json"],
"regulator_mappings": ["FINRA-3110", "SOX-404", "GLBA-501b", "OCC-2011-12"],
"remediation_ref": null,
"operator_upn": "agt226-runner@contoso.com",
"schema_version": "1.0"
}
§2 SPONSOR — Sponsor Assignment Evidence
2.1 Criterion mapping
This namespace evidences C2.26-2: every Zone 2 and Zone 3 agent identity has a non-null, currently active sponsor recorded in the Entra Agent ID extension attribute sponsorObjectId. Zone 1 agents are personal-scope and inherit sponsorship from the creator's user account; the Zone 1 test therefore verifies creator == sponsor rather than a discrete sponsor field.
The SPONSOR namespace is the keystone of Control 2.26: an unsponsored agent is, for examination purposes, an orphan. FINRA Rule 3110 (Supervision) and OCC Bulletin 2011-12 (Model Risk Management) both require a named individual accountable for the system's behavior. An empty sponsor field is therefore a Critical finding and pages the on-call AI Governance Lead within 15 minutes (see §11).
2.2 Pre-conditions
- PRE-01 through PRE-07 from §0 returned
PASS. - The current Graph context holds
AgentIdentity.Read.AllandUser.Read.Alldelegated or application scopes. - The sister function
Get-AllAgentIdentitiesfrom PowerShell Setup §3.1 is available in the session module path. - The reference dataset
agents-expected.csv(the "golden list" maintained by the AI Governance Lead per Control 1.2) is reachable at the path supplied via-ExpectedAgentsCsv.
2.3 Pester suite
#Requires -Version 7.4
#Requires -Modules Pester
BeforeDiscovery {
$sov = Test-Agt226SovereignTenant
$script:CloudTag = $sov.cloud
$script:IsSovereign = $sov.is_sovereign
if (-not $script:IsSovereign) {
$script:Agents = Get-AllAgentIdentities
$script:UnsponsoredZ23 = Get-AgentsWithoutSponsors -Zones 2,3
}
}
Describe "AGT226-SPONSOR" -Tag 'C2.26','SPONSOR' {
Context "Sovereign cloud short-circuit" -Skip:(-not $script:IsSovereign) {
It "emits SKIPPED with §8 compensating control pointer" {
$rec = New-Agt226EvidenceRecord -Namespace 'SPONSOR' `
-Criterion 'C2.26-2' -Zone 'all' -Status 'SKIPPED' `
-Assertion 'Sovereign tenant; manual attestation per §8' `
-RemediationRef 'TRG-SOV-01'
$rec.status | Should -Be 'SKIPPED'
}
}
Context "Zone 2" -Skip:$script:IsSovereign {
It "every Zone 2 agent has a non-null sponsorObjectId" {
$z2 = $script:Agents | Where-Object zone -eq 2
$missing = $z2 | Where-Object { -not $_.sponsorObjectId }
$missing.Count | Should -Be 0 -Because "C2.26-2 requires Z2 sponsorship"
}
It "every Zone 2 sponsor references an enabled user account" {
$z2 = $script:Agents | Where-Object zone -eq 2
foreach ($a in $z2) {
$u = Get-MgUser -UserId $a.sponsorObjectId -Property accountEnabled -ErrorAction SilentlyContinue
$u.accountEnabled | Should -BeTrue -Because "agent $($a.id) sponsor must be active"
}
}
}
Context "Zone 3" -Skip:$script:IsSovereign {
It "every Zone 3 agent has a sponsorObjectId AND a backup sponsor" {
$z3 = $script:Agents | Where-Object zone -eq 3
foreach ($a in $z3) {
$a.sponsorObjectId | Should -Not -BeNullOrEmpty
$a.backupSponsorObjectId | Should -Not -BeNullOrEmpty -Because "Z3 requires dual-sponsor per Control 2.26 §5"
}
}
It "no Zone 3 agent shares a sponsor and backup sponsor (separation of duties)" {
$z3 = $script:Agents | Where-Object zone -eq 3
foreach ($a in $z3) {
$a.sponsorObjectId | Should -Not -Be $a.backupSponsorObjectId
}
}
}
Context "Zone 1" -Skip:$script:IsSovereign {
It "every Zone 1 agent's sponsor equals its creator (personal scope)" {
$z1 = $script:Agents | Where-Object zone -eq 1
foreach ($a in $z1) {
$a.sponsorObjectId | Should -Be $a.createdBy.user.id
}
}
}
}
2.4 Sample passing evidence record
{
"control_id": "2.26",
"run_id": "AGT226-20260415-093012-a1b2c3d4",
"run_timestamp": "2026-04-15T09:30:12Z",
"tenant_id": "11111111-2222-3333-4444-555555555555",
"cloud": "Commercial",
"namespace": "SPONSOR",
"criterion": "C2.26-2",
"zone": "2",
"subject_id": "agent-payments-summarizer-001",
"subject_type": "agent_identity",
"status": "PASS",
"assertion": "Z2 agent has non-null active sponsor",
"observed_value": {
"sponsor_id": "44ab1234-5678-90ab-cdef-1234567890ab",
"sponsor_upn": "alice.banker@contoso.com",
"sponsor_active": true,
"sponsor_assignment_date": "2025-11-02T14:22:00Z"
},
"expected_value": { "sponsor_id": "<non-null>", "sponsor_active": true },
"evidence_artifacts": ["sponsor-snapshot-AGT226-20260415-093012-a1b2c3d4.json"],
"regulator_mappings": ["FINRA-3110", "SOX-404", "GLBA-501b", "OCC-2011-12"],
"remediation_ref": null,
"schema_version": "1.0"
}
2.5 Sample failing evidence record
{
"control_id": "2.26",
"run_id": "AGT226-20260415-093012-a1b2c3d4",
"run_timestamp": "2026-04-15T09:30:12Z",
"tenant_id": "11111111-2222-3333-4444-555555555555",
"cloud": "Commercial",
"namespace": "SPONSOR",
"criterion": "C2.26-2",
"zone": "3",
"subject_id": "agent-loan-decisioner-007",
"subject_type": "agent_identity",
"status": "FAIL",
"assertion": "Z3 agent must have non-null active sponsor and backup sponsor",
"observed_value": {
"sponsor_id": "44ab1234-5678-90ab-cdef-1234567890ab",
"sponsor_active": false,
"sponsor_termination_date": "2026-03-31T23:59:59Z",
"backup_sponsor_id": null
},
"expected_value": { "sponsor_id": "<active user>", "backup_sponsor_id": "<non-null>" },
"evidence_artifacts": ["sponsor-snapshot-AGT226-20260415-093012-a1b2c3d4.json"],
"regulator_mappings": ["FINRA-3110", "SOX-404", "OCC-2011-12"],
"remediation_ref": "TRG-SPONSOR-01",
"schema_version": "1.0"
}
Remediation TRG-SPONSOR-01 (see §11.2): page on-call AI Governance Lead, suspend agent within 4 hours per Z3 SLA, run Set-AgentSponsorBulk once a successor sponsor is named.
2.6 Examiner artifact
| Artifact | Filename pattern | Retention | Signed by |
|---|---|---|---|
| Sponsor snapshot | sponsor-snapshot-<runId>.json |
6 years (FINRA 4511) | AI Governance Lead (attestation.json chain entry) |
| Pester results (JUnit XML) | pester-SPONSOR-<runId>.xml |
6 years | Same |
| Failing-record extract (if any) | sponsor-failures-<runId>.json |
6 years + open until remediated | Same, plus remediation ticket reference |
2.7 Zone thresholds
| Zone | PASS band | WARN band | FAIL band |
|---|---|---|---|
| Zone 1 | 100% of Z1 agents have sponsor == creator |
n/a | any mismatch |
| Zone 2 | 100% sponsored AND 100% sponsors active | ≥ 99% sponsored AND any sponsor inactive < 7 days | < 99% sponsored OR any sponsor inactive ≥ 7 days |
| Zone 3 | 100% sponsored AND 100% backup-sponsored AND separation enforced | n/a (no WARN band for Z3 — pass or fail) | any deviation |
2.8 Regulator mapping
| Test | FINRA 3110 | FINRA 4511 | SOX §404 | GLBA §501(b) | OCC 2011-12 |
|---|---|---|---|---|---|
| Z2 sponsor non-null | ✓ supervision | ✓ recordkeeping | ✓ named owner of control | — | ✓ model owner |
| Z2 sponsor active | ✓ ongoing supervision | — | ✓ control operating effectiveness | ✓ designated employee | ✓ model owner |
| Z3 dual sponsor | ✓ heightened supervision | ✓ | ✓ segregation | ✓ | ✓ model owner + challenger |
| Z1 sponsor==creator | ✓ user-level accountability | ✓ | — | — | — |
§3 ACCESSPKG — Access Package Design & Assignment Evidence
3.1 Criterion mapping
This namespace evidences two criteria simultaneously:
- C2.26-3 — Entra Identity Governance access packages exist for every standard agent resource bundle, and Zone 3 agents may obtain resources only via access packages (no direct group, role, or app-permission assignments).
- C2.26-4 — every access package assignment policy on Z2 and Z3 packages enforces a time-bound expiration ≤ 365 days, and no Z3 assignment is perpetual.
The catalog under test is "AI Agent Resources" (per PowerShell Setup §4.2). All packages within this catalog are in scope.
3.2 Pre-conditions
- PRE gates pass; Graph context holds
EntitlementManagement.Read.All. - The catalog
"AI Agent Resources"exists. If the catalog is missing the suite emits a singleFAILrecord at the catalog level (criterion C2.26-3) and short-circuits. - Reference design document
agent-package-design.json(maintained by Entra Identity Governance Admin) lists the expected packages and their resource compositions.
3.3 Pester suite
Describe "AGT226-ACCESSPKG" -Tag 'C2.26','ACCESSPKG' {
BeforeAll {
$script:Catalog = Get-MgEntitlementManagementAccessPackageCatalog `
-Filter "displayName eq 'AI Agent Resources'" -ErrorAction SilentlyContinue
$script:Packages = if ($script:Catalog) {
Get-MgEntitlementManagementAccessPackage -Filter "catalogId eq '$($script:Catalog.Id)'" -All
} else { @() }
$script:Assignments = Get-AgentAccessPackageAssignments
}
Context "Catalog presence (C2.26-3)" -Skip:$script:IsSovereign {
It "the 'AI Agent Resources' catalog exists" {
$script:Catalog | Should -Not -BeNullOrEmpty
}
It "the catalog contains at least one package" {
$script:Packages.Count | Should -BeGreaterThan 0
}
}
Context "Z3 channels (C2.26-3)" -Skip:$script:IsSovereign {
It "no Zone 3 agent has direct group memberships outside an access package" {
$z3 = $script:Agents | Where-Object zone -eq 3
foreach ($a in $z3) {
$direct = Get-MgUserMemberOf -UserId $a.id -All |
Where-Object { $_.AdditionalProperties.assignmentType -ne 'accessPackage' }
$direct.Count | Should -Be 0 -Because "agent $($a.id) (Z3) must receive resources only via packages"
}
}
It "no Zone 3 agent holds a directly assigned Entra role" {
$z3 = $script:Agents | Where-Object zone -eq 3
foreach ($a in $z3) {
$roles = Get-MgRoleManagementDirectoryRoleAssignment `
-Filter "principalId eq '$($a.id)'" -All
$roles.Count | Should -Be 0
}
}
}
Context "Time-bound expiry (C2.26-4)" -Skip:$script:IsSovereign {
It "every Z2/Z3 package assignment policy has duration ≤ 365 days" {
foreach ($pkg in $script:Packages) {
$policies = Get-MgEntitlementManagementAccessPackageAssignmentPolicy `
-Filter "accessPackageId eq '$($pkg.Id)'" -All
foreach ($pol in $policies) {
$dur = $pol.Expiration.Duration
if ($null -ne $dur) {
# Parse ISO-8601 duration (e.g., P365D, P1Y)
$days = ConvertFrom-Iso8601Duration $dur
$days | Should -BeLessOrEqual 365 -Because "policy $($pol.Id) on package $($pkg.DisplayName)"
} else {
# Type must not be 'noExpiration' for Z3
$pol.Expiration.Type | Should -Not -Be 'noExpiration'
}
}
}
}
It "no Zone 3 access package allows perpetual assignment" {
$z3pkgs = $script:Packages | Where-Object { $_.AdditionalProperties.zone -eq '3' }
foreach ($pkg in $z3pkgs) {
$policies = Get-MgEntitlementManagementAccessPackageAssignmentPolicy `
-Filter "accessPackageId eq '$($pkg.Id)'" -All
foreach ($pol in $policies) {
$pol.Expiration.Type | Should -Not -Be 'noExpiration'
}
}
}
}
}
3.4 Sample passing evidence record
{
"control_id": "2.26",
"run_id": "AGT226-20260415-093012-a1b2c3d4",
"namespace": "ACCESSPKG",
"criterion": "C2.26-4",
"zone": "3",
"subject_id": "pkg-trading-floor-research-bundle",
"subject_type": "access_package_policy",
"status": "PASS",
"assertion": "Z3 access package policy duration ≤ 365 days and not perpetual",
"observed_value": {
"policy_id": "policy-9af8...",
"duration_days": 90,
"expiration_type": "afterDuration",
"requires_approval": true,
"approver_count": 2
},
"expected_value": { "duration_days": "≤365", "expiration_type": "!=noExpiration" },
"regulator_mappings": ["FINRA-3110", "SOX-404", "OCC-2011-12", "FFIEC-IS-AccessMgmt"],
"evidence_artifacts": ["accesspkg-snapshot-AGT226-20260415-093012-a1b2c3d4.json"],
"schema_version": "1.0"
}
3.5 Sample failing evidence record
{
"control_id": "2.26",
"namespace": "ACCESSPKG",
"criterion": "C2.26-3",
"zone": "3",
"subject_id": "agent-loan-decisioner-007",
"subject_type": "agent_identity",
"status": "FAIL",
"assertion": "Z3 agent must receive resources only via access packages",
"observed_value": {
"direct_group_memberships": [
{ "groupId": "g-credit-models", "assignmentType": "direct", "addedOn": "2026-02-10T11:00:00Z" }
]
},
"expected_value": { "direct_group_memberships": [] },
"remediation_ref": "TRG-ACCESSPKG-02",
"regulator_mappings": ["FINRA-3110","SOX-404","OCC-2011-12"],
"schema_version": "1.0"
}
TRG-ACCESSPKG-02 (§11.3): remove the direct membership within 24 hours; re-assign the agent through the access package; record the variance in the Identity Governance change log.
3.6 Examiner artifact
| Artifact | Filename | Retention |
|---|---|---|
| Catalog & package design snapshot | accesspkg-snapshot-<runId>.json |
6 years |
| Per-policy expiry table (CSV) | accesspkg-policies-<runId>.csv |
6 years |
| Pester JUnit | pester-ACCESSPKG-<runId>.xml |
6 years |
3.7 Zone thresholds
| Zone | PASS | WARN | FAIL |
|---|---|---|---|
| Zone 1 | n/a — Z1 agents do not consume access packages | — | — |
| Zone 2 | 100% of Z2 packages have ≤ 365-day expiry | one package 366–400 days, with documented variance | any package > 400 days OR any noExpiration policy |
| Zone 3 | 100% of Z3 resources via packages AND 100% policies ≤ 365 days AND zero noExpiration |
n/a | any deviation |
3.8 Regulator mapping
| Test | FINRA 3110 | SOX §404 | OCC 2011-12 | FFIEC IS — Access Mgmt |
|---|---|---|---|---|
| Catalog exists | ✓ | ✓ | ✓ | ✓ |
| Z3 channel discipline | ✓ | ✓ segregation | ✓ | ✓ |
| ≤365-day expiry | ✓ | ✓ recurring review | ✓ | ✓ periodic recertification |
| No perpetual Z3 | ✓ | ✓ | ✓ | ✓ |
§4 LIFECYCLE — Sponsor-Departure Workflow Evidence
4.1 Criterion mapping
This namespace evidences C2.26-5: a Microsoft Entra Lifecycle Workflow is configured and active for the trigger user account becomes inactive or marked as a leaver, and that workflow's task on agent identities — specifically the default automatic manager-transfer behavior of Entra — has executed for every sponsor departure observed in the lookback window.
Important accuracy note. This control does not require organizations to build a custom suspend-on-leave workflow. Entra's default behavior, when a sponsor's user account transitions to
accountEnabled = falseor carries theemployeeLeaveDateTimepast trigger, is to transfer agent ownership to the sponsor's manager. The Pester tests verify that this default occurred. A custom suspension workflow is reserved for the case where the manager declines the transfer (escalation path; see §11.5).
4.2 Pre-conditions
- Lifecycle Workflows feature is licensed (Entra ID Governance SKU).
- The reference workflow
Agent Sponsor Departure Transferexists and is inenabledstate. - The lookback window for verification is the prior 30 days by default; configurable via
-LookbackDays.
4.3 Pester suite
Describe "AGT226-LIFECYCLE" -Tag 'C2.26','LIFECYCLE' {
BeforeAll {
$script:WfName = 'Agent Sponsor Departure Transfer'
$script:Wf = Get-MgIdentityGovernanceLifecycleWorkflow `
-Filter "displayName eq '$script:WfName'" -ErrorAction SilentlyContinue
$script:Lookback = (Get-Date).AddDays(-30).ToUniversalTime()
$script:Departures = Get-MgUser -All `
-Filter "employeeLeaveDateTime ge $($script:Lookback.ToString('o'))" `
-Property id,userPrincipalName,employeeLeaveDateTime,manager
}
Context "Workflow definition" -Skip:$script:IsSovereign {
It "the lifecycle workflow exists and is enabled" {
$script:Wf | Should -Not -BeNullOrEmpty
$script:Wf.IsEnabled | Should -BeTrue
}
It "the workflow trigger is the leaver scenario" {
$script:Wf.ExecutionConditions.AdditionalProperties['@odata.type'] |
Should -Match 'triggerAndScopeBasedConditions'
$script:Wf.ExecutionConditions.Trigger.AdditionalProperties.timeBasedAttribute |
Should -Be 'employeeLeaveDateTime'
}
It "the workflow includes the manager-transfer task on agent identities" {
$taskTypes = $script:Wf.Tasks.TaskDefinitionId
$taskTypes | Should -Contain 'reassignAgentOwnerToManager'
}
}
Context "Workflow execution within lookback" -Skip:$script:IsSovereign {
It "every departed sponsor in the lookback window has a workflow run" {
foreach ($d in $script:Departures) {
$sponsoredAgents = $script:Agents | Where-Object sponsorObjectId -eq $d.Id
if ($sponsoredAgents.Count -eq 0) { continue }
$runs = Get-MgIdentityGovernanceLifecycleWorkflowRun `
-LifecycleWorkflowId $script:Wf.Id -All |
Where-Object { $_.Subject.Id -eq $d.Id }
$runs.Count | Should -BeGreaterOrEqual 1 -Because "departure $($d.UserPrincipalName) must trigger workflow"
}
}
It "every workflow run completed with status Completed (not Failed)" {
$runs = Get-MgIdentityGovernanceLifecycleWorkflowRun `
-LifecycleWorkflowId $script:Wf.Id -All |
Where-Object { $_.StartedDateTime -ge $script:Lookback }
foreach ($r in $runs) {
$r.ProcessingStatus | Should -BeIn @('completed','completedWithErrors')
if ($r.ProcessingStatus -eq 'completedWithErrors') {
# Must have a logged escalation
$r.AdditionalProperties.ContainsKey('escalation_ticket') | Should -BeTrue
}
}
}
}
Context "Manager-transfer outcome" -Skip:$script:IsSovereign {
It "for every successful run, the affected agent's sponsor now equals the departed user's manager" {
$runs = Get-MgIdentityGovernanceLifecycleWorkflowRun `
-LifecycleWorkflowId $script:Wf.Id -All |
Where-Object { $_.ProcessingStatus -eq 'completed' -and $_.StartedDateTime -ge $script:Lookback }
foreach ($r in $runs) {
$departedId = $r.Subject.Id
$managerId = (Get-MgUser -UserId $departedId -ExpandProperty manager).Manager.Id
$agents = $script:Agents | Where-Object sponsorObjectId -eq $departedId
# Refresh agent records post-run
foreach ($a in $agents) {
$fresh = Get-AllAgentIdentities -AgentId $a.id
$fresh.sponsorObjectId | Should -Be $managerId `
-Because "default manager-transfer should have replaced sponsor"
}
}
}
}
}
4.4 Sample passing evidence record
{
"control_id": "2.26",
"namespace": "LIFECYCLE",
"criterion": "C2.26-5",
"zone": "all",
"subject_id": "wf-run-2c7b9-20260411",
"subject_type": "lifecycle_workflow_run",
"status": "PASS",
"assertion": "Sponsor-departure workflow executed and manager-transfer succeeded",
"observed_value": {
"departed_sponsor_upn": "departed.banker@contoso.com",
"departure_date": "2026-04-10T17:00:00Z",
"workflow_run_id": "2c7b9-20260411",
"processing_status": "completed",
"agents_affected": 3,
"manager_now_sponsor": true
},
"regulator_mappings": ["FINRA-3110","SOX-404","OCC-2011-12","GLBA-501b"],
"evidence_artifacts": ["lifecycle-runs-AGT226-20260415-093012-a1b2c3d4.json"],
"schema_version": "1.0"
}
4.5 Sample failing evidence record
{
"control_id": "2.26",
"namespace": "LIFECYCLE",
"criterion": "C2.26-5",
"subject_id": "wf-run-9z3a-20260413",
"status": "FAIL",
"assertion": "Departed sponsor's agents were not transferred",
"observed_value": {
"departed_sponsor_upn": "departed2.banker@contoso.com",
"departure_date": "2026-04-12T17:00:00Z",
"workflow_run_id": null,
"agents_still_pointing_at_departed_sponsor": 2
},
"remediation_ref": "TRG-LIFECYCLE-01",
"regulator_mappings": ["FINRA-3110","OCC-2011-12"],
"schema_version": "1.0"
}
TRG-LIFECYCLE-01 (§11.4): manually invoke Set-AgentSponsorBulk with the departed user's manager as new sponsor; open a problem ticket against the lifecycle workflow; if manager declines transfer, escalate to the suspension path in §11.5.
4.6 Examiner artifact
| Artifact | Filename | Retention |
|---|---|---|
| Workflow definition snapshot | lifecycle-workflow-def-<runId>.json |
6 years |
| Workflow run log (last 30 days) | lifecycle-runs-<runId>.json |
6 years |
| Departed-sponsor reconciliation table | lifecycle-reconciliation-<runId>.csv |
6 years |
4.7 Zone thresholds
| Zone | PASS | WARN | FAIL |
|---|---|---|---|
| Zone 1 | n/a — Z1 agents are deleted with creator account by default | — | — |
| Zone 2 | 100% of departed sponsors processed within 24 hours | one departure processed 24–72 hours late, with documented justification | any departure unprocessed > 72 hours |
| Zone 3 | 100% processed within 4 hours AND every transfer cross-checked against backup sponsor | n/a | any Z3 agent unprocessed > 4 hours |
4.8 Regulator mapping
| Test | FINRA 3110 | SOX §404 | GLBA §501(b) | OCC 2011-12 |
|---|---|---|---|---|
| Workflow exists & enabled | ✓ supervisory system | ✓ control design | ✓ designated employee continuity | ✓ model owner continuity |
| Run within lookback | ✓ supervision evidence | ✓ control operating | ✓ | ✓ |
| Manager-transfer outcome | ✓ accountability transfer | ✓ | ✓ | ✓ challenger continuity |
§5 REVIEW — Quarterly Access Certification Evidence
5.1 Criterion mapping
This namespace evidences C2.26-6: a quarterly access review campaign is configured against every Zone 3 access package and every group that grants Z3 agent identities elevated scope, and the most recent campaign completed with a recorded decision (Approve / Deny / Don't know — counted as Deny per default policy) for every reviewer-subject pair, attributed to a named certifier with a timestamp.
5.2 Pre-conditions
- Entra ID Governance (Access Reviews) licensed.
- The quarterly campaign template
Z3 Agent Access Recertificationis deployed. - Lookback window is the most recently completed calendar quarter.
5.3 Pester suite
Describe "AGT226-REVIEW" -Tag 'C2.26','REVIEW' {
BeforeAll {
$script:Quarter = Get-PreviousQuarterBoundary # returns @{ start; end; label='Q1-2026' }
$script:Reviews = Get-MgIdentityGovernanceAccessReviewDefinition -All |
Where-Object { $_.DisplayName -like 'Z3 Agent Access Recertification*' }
}
Context "Campaign existence" -Skip:$script:IsSovereign {
It "the Z3 quarterly campaign exists" {
$script:Reviews.Count | Should -BeGreaterThan 0
}
It "every Z3 access package is in scope of at least one campaign" {
$z3pkgs = $script:Packages | Where-Object { $_.AdditionalProperties.zone -eq '3' }
foreach ($pkg in $z3pkgs) {
$covered = $script:Reviews | Where-Object {
$_.Scope.AdditionalProperties.resourceScopes -contains $pkg.Id
}
$covered.Count | Should -BeGreaterThan 0 -Because "package $($pkg.DisplayName) needs Q-recertification"
}
}
}
Context "Campaign completion within prior quarter" -Skip:$script:IsSovereign {
It "every campaign in scope completed within the prior quarter" {
foreach ($r in $script:Reviews) {
$instances = Get-MgIdentityGovernanceAccessReviewDefinitionInstance `
-AccessReviewScheduleDefinitionId $r.Id -All |
Where-Object {
$_.StartDateTime -ge $script:Quarter.start -and
$_.EndDateTime -le $script:Quarter.end
}
$instances.Count | Should -BeGreaterThan 0
foreach ($inst in $instances) {
$inst.Status | Should -Be 'Completed'
}
}
}
It "every reviewer decision has a named certifier and a timestamp" {
foreach ($r in $script:Reviews) {
$instances = Get-MgIdentityGovernanceAccessReviewDefinitionInstance `
-AccessReviewScheduleDefinitionId $r.Id -All
foreach ($inst in $instances) {
$decisions = Get-MgIdentityGovernanceAccessReviewDefinitionInstanceDecision `
-AccessReviewScheduleDefinitionId $r.Id `
-AccessReviewInstanceId $inst.Id -All
foreach ($d in $decisions) {
$d.ReviewedBy.Id | Should -Not -BeNullOrEmpty
$d.ReviewedDateTime | Should -Not -BeNullOrEmpty
$d.Decision | Should -BeIn @('Approve','Deny','DontKnow','NotReviewed')
}
}
}
}
It "any 'NotReviewed' decision has been auto-applied per default policy (Deny)" {
foreach ($r in $script:Reviews) {
$r.Settings.DefaultDecisionEnabled | Should -BeTrue
$r.Settings.DefaultDecision | Should -Be 'Deny'
}
}
}
}
5.4 Sample passing evidence record
{
"control_id": "2.26",
"namespace": "REVIEW",
"criterion": "C2.26-6",
"zone": "3",
"subject_id": "review-Q1-2026-pkg-trading-floor-research",
"subject_type": "access_review_instance",
"status": "PASS",
"assertion": "Quarterly Z3 access review completed with named certifier per decision",
"observed_value": {
"campaign_label": "Q1-2026",
"instance_id": "rev-9af8...",
"started": "2026-01-05T00:00:00Z",
"completed": "2026-03-15T17:00:00Z",
"decisions_total": 47,
"approved": 41,
"denied": 5,
"not_reviewed_auto_denied": 1,
"all_decisions_have_certifier": true
},
"regulator_mappings": ["FINRA-3110","FINRA-25-07","SOX-404","OCC-2011-12","FFIEC-IS-AccessMgmt"],
"evidence_artifacts": ["review-decisions-Q1-2026.json"],
"schema_version": "1.0"
}
5.5 Sample failing evidence record
{
"control_id": "2.26",
"namespace": "REVIEW",
"criterion": "C2.26-6",
"subject_id": "review-Q1-2026-pkg-loan-decisioner",
"status": "FAIL",
"assertion": "Quarterly review must complete with all decisions attributed",
"observed_value": {
"campaign_label": "Q1-2026",
"completed": null,
"decisions_pending": 12,
"default_decision_applied": false
},
"remediation_ref": "TRG-REVIEW-01",
"regulator_mappings": ["FINRA-3110","SOX-404","OCC-2011-12"],
"schema_version": "1.0"
}
TRG-REVIEW-01 (§11.6): force-apply the default decision via Graph; notify the Compliance Officer; document the lapse for the next 1.2 sponsor attestation cycle.
5.6 Examiner artifact
| Artifact | Filename | Retention |
|---|---|---|
| Campaign definition | review-definition-<runId>.json |
6 years |
| Per-instance decision export | review-decisions-<quarterLabel>.json |
7 years (one year > FINRA minimum to absorb late audits) |
| Reviewer attestation summary CSV | review-summary-<quarterLabel>.csv |
6 years |
5.7 Zone thresholds
| Zone | PASS | WARN | FAIL |
|---|---|---|---|
| Zone 1 | n/a — Z1 not subject to quarterly review | — | — |
| Zone 2 | annual review (not quarterly); test in §5 evaluates at year-end only | semi-annual lapse | annual lapse |
| Zone 3 | 100% campaigns completed AND 100% decisions certified within the quarter | one decision late ≤ 7 days, with justification | any campaign uncompleted at quarter close |
5.8 Regulator mapping
| Test | FINRA 3110 | FINRA 25-07 | SOX §404 | OCC 2011-12 | FFIEC IS |
|---|---|---|---|---|---|
| Campaign exists | ✓ | ✓ | ✓ | ✓ | ✓ |
| Completion in quarter | ✓ | ✓ | ✓ recurring | ✓ ongoing monitoring | ✓ recertification |
| Named certifier per decision | ✓ supervision attribution | ✓ | ✓ control evidence | ✓ | ✓ |
§6 EXPIRY — Renewal & Removal Evidence
6.1 Criterion mapping
This namespace evidences C2.26-7: every access package assignment that has expired within the prior 90 days has been either:
- (a) explicitly renewed, with a recorded business justification authored by the Sponsor and counter-acknowledged by the access package owner, or
- (b) removed within 24 hours of expiry, with audit-log evidence of group/role removal.
A grace state — expired-but-still-granting-access — is a Critical finding.
6.2 Pre-conditions
- Audit log retention ≥ 90 days (Entra default for non-Premium is 30 days; Premium P1+ provides 30 days; Diagnostic Settings export to Log Analytics or SIEM extends this — see §7).
- The reference renewal-justification store (
renewals.csv, maintained by AI Governance Lead) contains the per-renewal justification text and signatures.
6.3 Pester suite
Describe "AGT226-EXPIRY" -Tag 'C2.26','EXPIRY' {
BeforeAll {
$script:Window = (Get-Date).AddDays(-90).ToUniversalTime()
$script:ExpiredAssignments = Get-AgentAccessPackageAssignments |
Where-Object {
$_.expiredDateTime -ge $script:Window -and
$_.expiredDateTime -le (Get-Date).ToUniversalTime()
}
$script:Renewals = Import-Csv -Path $env:AGT226_RENEWALS_CSV
}
Context "Disposition coverage (C2.26-7)" -Skip:$script:IsSovereign {
It "every expired assignment has either renewal evidence or removal evidence" {
foreach ($a in $script:ExpiredAssignments) {
$renewal = $script:Renewals | Where-Object assignmentId -eq $a.id
if ($renewal) {
$renewal.justification | Should -Not -BeNullOrEmpty
$renewal.sponsorSignature | Should -Not -BeNullOrEmpty
$renewal.ownerSignature | Should -Not -BeNullOrEmpty
} else {
# Must show removal within 24h
$removeEvent = Get-MgAuditLogDirectoryAudit -All `
-Filter "activityDisplayName eq 'Remove member from group' and targetResources/any(t:t/id eq '$($a.targetId)')" |
Where-Object { $_.ActivityDateTime -ge $a.expiredDateTime -and `
$_.ActivityDateTime -le $a.expiredDateTime.AddHours(24) }
$removeEvent.Count | Should -BeGreaterThan 0 `
-Because "assignment $($a.id) had no renewal and must show removal within 24h"
}
}
}
}
Context "Renewal quality (C2.26-7a)" -Skip:$script:IsSovereign {
It "every renewal justification is at least 50 characters and references a business reason" {
foreach ($r in $script:Renewals) {
$r.justification.Length | Should -BeGreaterOrEqual 50
$r.justification | Should -Match '(business case|regulator|customer|product|incident)'
}
}
It "no renewal extends a Z3 assignment beyond 365 days from original grant" {
foreach ($r in $script:Renewals | Where-Object zone -eq '3') {
$orig = [datetime]$r.originalGrantDate
$newEnd = [datetime]$r.newExpiryDate
($newEnd - $orig).TotalDays | Should -BeLessOrEqual 365
}
}
}
}
6.4 Sample passing record (renewal path)
{
"control_id": "2.26",
"namespace": "EXPIRY",
"criterion": "C2.26-7",
"zone": "2",
"subject_id": "assignment-payments-summarizer-2025Q4",
"subject_type": "access_package_assignment",
"status": "PASS",
"assertion": "Expired assignment renewed with valid justification and signatures",
"observed_value": {
"expired_at": "2026-03-31T23:59:59Z",
"disposition": "renewed",
"justification_chars": 187,
"sponsor_signature_at": "2026-03-30T14:00:00Z",
"owner_signature_at": "2026-03-31T09:15:00Z",
"new_expiry_at": "2026-09-30T23:59:59Z"
},
"regulator_mappings": ["FINRA-3110","FINRA-4511","SOX-404","OCC-2011-12"],
"evidence_artifacts": ["expiry-snapshot-AGT226-20260415-093012-a1b2c3d4.json","renewals-2026Q1.csv"],
"schema_version": "1.0"
}
6.5 Sample passing record (removal path)
{
"control_id": "2.26",
"namespace": "EXPIRY",
"criterion": "C2.26-7",
"zone": "3",
"subject_id": "assignment-trading-research-2025Q4",
"status": "PASS",
"assertion": "Expired assignment with no renewal removed within 24h",
"observed_value": {
"expired_at": "2026-03-31T23:59:59Z",
"disposition": "removed",
"removal_event_id": "audit-evt-77ac...",
"removal_at": "2026-04-01T03:12:00Z",
"elapsed_hours": 3.2
},
"regulator_mappings": ["FINRA-3110","SOX-404","OCC-2011-12"],
"schema_version": "1.0"
}
6.6 Sample failing record
{
"control_id": "2.26",
"namespace": "EXPIRY",
"criterion": "C2.26-7",
"zone": "3",
"subject_id": "assignment-loan-decisioner-2025Q4",
"status": "FAIL",
"assertion": "Expired Z3 assignment must be renewed with justification or removed ≤24h",
"observed_value": {
"expired_at": "2026-03-31T23:59:59Z",
"disposition": "stale",
"current_state": "still_granting_access",
"elapsed_hours_since_expiry": 372
},
"remediation_ref": "TRG-EXPIRY-01",
"regulator_mappings": ["FINRA-3110","FINRA-4511","SOX-404","OCC-2011-12"],
"schema_version": "1.0"
}
TRG-EXPIRY-01 (§11.7): immediately remove the assignment via Remove-MgEntitlementManagementAssignmentRequest; open a Critical incident; capture root-cause and add to the next §13 quarterly attestation packet.
6.7 Examiner artifact
| Artifact | Filename | Retention |
|---|---|---|
| Expiry disposition table | expiry-snapshot-<runId>.json |
6 years |
| Renewal justifications (signed) | renewals-<quarterLabel>.csv + .sig |
6 years |
| Removal audit-event extract | expiry-removals-<runId>.json |
6 years |
6.8 Zone thresholds
| Zone | PASS | WARN | FAIL |
|---|---|---|---|
| Zone 1 | n/a (no formal access packages) | — | — |
| Zone 2 | 100% disposed within 24h of expiry | one disposition 24–72h late, with justification | any disposition > 72h late |
| Zone 3 | 100% disposed within 24h AND 0 stale-grant events | n/a | any stale-grant > 0 hours |
6.9 Regulator mapping
| Test | FINRA 3110 | FINRA 4511 | SOX §404 | OCC 2011-12 |
|---|---|---|---|---|
| Disposition coverage | ✓ | ✓ retention of decisions | ✓ control operating | ✓ |
| Renewal quality | ✓ | ✓ | ✓ documented justification | ✓ challenger can re-evaluate |
| Removal SLA | ✓ | ✓ | ✓ | ✓ |
§7 SIEM — Forwarding & 6-Year Retention Evidence
7.1 Criterion mapping
This namespace evidences C2.26-8: Entra Agent ID lifecycle events (sponsor change, package assignment, access review decision, lifecycle workflow execution) are forwarded to the organization's SIEM with 6-year retention in compliance with FINRA Rule 4511 and SEC Rule 17a-4.
Important architectural note. Entra Diagnostic Settings export only at the category level (e.g.,
AuditLogs,IdentityRiskEvents). Per-event filtering for "agent identity" specifically does not happen at the Entra side. The SIEM is the place where agent-related events are isolated using a downstream rule that correlates oncategory eq 'AuditLogs'ANDtargetResources.type eq 'AgentIdentity'(or the equivalent type token in the active Graph schema). The Pester suite therefore tests:
- Entra Diagnostic Setting exists at the tenant level and emits the relevant categories.
- The SIEM has received at least one event matching the agent-identity correlation rule in the prior 24 hours.
- The SIEM index housing the events has a retention policy of ≥ 6 years configured.
7.2 Pre-conditions
- Diagnostic Setting (e.g.,
AgentID-To-SIEM) exists at tenant scope and exportsAuditLogs,SignInLogs,IdentityRiskEvents, andEnrichedOffice365AuditLogs(or sovereign-equivalent categories). - A SIEM API endpoint and bearer token are available via
$env:AGT226_SIEM_BASEURLand$env:AGT226_SIEM_TOKEN. - The SIEM correlation rule
AgentID-Lifecycle-Eventsis deployed.
7.3 Pester suite
Describe "AGT226-SIEM" -Tag 'C2.26','SIEM' {
BeforeAll {
$script:DiagSettings = Get-MgSubscriptionDiagnosticSetting -All -ErrorAction SilentlyContinue
$script:SiemBase = $env:AGT226_SIEM_BASEURL
$script:SiemToken = $env:AGT226_SIEM_TOKEN
}
Context "Entra-side export" -Skip:$script:IsSovereign {
It "a Diagnostic Setting forwards AuditLogs to the SIEM destination" {
$relevant = $script:DiagSettings | Where-Object {
$_.Logs.Category -contains 'AuditLogs' -and $_.Logs.Enabled -contains $true
}
$relevant.Count | Should -BeGreaterThan 0
}
It "the export destination is the contracted SIEM workspace or Event Hub" {
$relevant = $script:DiagSettings | Where-Object { $_.Logs.Category -contains 'AuditLogs' }
foreach ($s in $relevant) {
($s.WorkspaceId -or $s.EventHubAuthorizationRuleId) | Should -BeTrue
}
}
}
Context "SIEM-side ingestion" -Skip:$script:IsSovereign {
It "the SIEM has received at least one agent-identity event in the prior 24 hours" {
$headers = @{ Authorization = "Bearer $script:SiemToken" }
$body = @{
query = "search index=entra category=AuditLogs targetResources.type=AgentIdentity earliest=-24h | stats count"
} | ConvertTo-Json
$resp = Invoke-RestMethod -Method Post -Uri "$script:SiemBase/search" -Headers $headers -Body $body -ContentType 'application/json'
$resp.results.count | Should -BeGreaterThan 0
}
It "the SIEM index housing entra logs has retention ≥ 2190 days (6 years)" {
$headers = @{ Authorization = "Bearer $script:SiemToken" }
$resp = Invoke-RestMethod -Method Get -Uri "$script:SiemBase/indexes/entra" -Headers $headers
[int]$resp.retentionDays | Should -BeGreaterOrEqual 2190
}
It "the SIEM index is configured WORM / immutable" {
$headers = @{ Authorization = "Bearer $script:SiemToken" }
$resp = Invoke-RestMethod -Method Get -Uri "$script:SiemBase/indexes/entra" -Headers $headers
$resp.immutable | Should -BeTrue
}
}
}
7.4 Sample passing record
{
"control_id": "2.26",
"namespace": "SIEM",
"criterion": "C2.26-8",
"zone": "all",
"subject_id": "diag-setting-AgentID-To-SIEM",
"subject_type": "diagnostic_setting",
"status": "PASS",
"assertion": "Entra exports AuditLogs to SIEM and SIEM retains ≥6y immutable",
"observed_value": {
"diagnostic_setting_name": "AgentID-To-SIEM",
"categories": ["AuditLogs","SignInLogs","IdentityRiskEvents"],
"destination": "eventhub://contoso-secops-eh/entra",
"siem_index": "entra",
"siem_retention_days": 2557,
"siem_immutable": true,
"events_last_24h": 142
},
"regulator_mappings": ["FINRA-4511","SEC-17a-4","SOX-404","GLBA-501b","OCC-2011-12"],
"evidence_artifacts": ["siem-forwarding-AGT226-20260415-093012-a1b2c3d4.json"],
"schema_version": "1.0"
}
7.5 Sample failing record
{
"control_id": "2.26",
"namespace": "SIEM",
"criterion": "C2.26-8",
"status": "FAIL",
"assertion": "SIEM index must retain ≥2190 days",
"observed_value": {
"siem_index": "entra",
"siem_retention_days": 365,
"siem_immutable": false
},
"remediation_ref": "TRG-SIEM-01",
"regulator_mappings": ["FINRA-4511","SEC-17a-4"],
"schema_version": "1.0"
}
TRG-SIEM-01 (§11.8): engage SecOps to extend retention; raise immutable-storage variance with the Compliance Officer; consider compensating cold-storage export to Azure Storage with WORM lock pending remediation.
7.6 Examiner artifact
| Artifact | Filename | Retention |
|---|---|---|
| Diagnostic Setting snapshot | siem-diagsettings-<runId>.json |
6 years |
| SIEM ingestion proof (search response) | siem-ingestion-<runId>.json |
6 years |
| SIEM index policy snapshot | siem-index-policy-<runId>.json |
6 years |
7.7 Zone thresholds
| Zone | PASS | WARN | FAIL |
|---|---|---|---|
| All zones | Diagnostic Setting active AND SIEM events flowing AND retention ≥6y AND immutable | retention 5y–6y, with documented remediation plan | retention < 5y OR no events flowing OR not immutable |
7.8 Regulator mapping
| Test | FINRA 4511 | SEC 17a-4 | SOX §404 | GLBA §501(b) |
|---|---|---|---|---|
| Diag setting active | ✓ | ✓ | ✓ logging control | ✓ |
| SIEM ingestion | ✓ | ✓ | ✓ | ✓ |
| 6y retention | ✓ (6y minimum) | ✓ (3y for first 2y readily accessible; this exceeds) | ✓ | ✓ |
| Immutability | — | ✓ WORM requirement | ✓ | ✓ tamper-evident |
§8 SOV — Sovereign Cloud Compensating Control
8.1 Why this section exists
At the time of this playbook's last UI verification (April 2026), the Microsoft Entra Agent ID feature is not generally available in sovereign cloud environments (Microsoft 365 GCC, GCC High, and DoD). Tenants in those clouds therefore cannot satisfy criteria C2.26-1 through C2.26-7 using Entra-native tooling.
Rather than treating this as an automatic failure — which would understate examiner-defensible posture for organizations that legitimately operate in sovereign clouds for jurisdictional reasons — Control 2.26 §6 prescribes a manual compensating attestation, executed quarterly by the AI Governance Lead and counter-signed by the Compliance Officer. This section documents the test that verifies the attestation has been executed, signed, and retained.
8.2 Compensating control flow
For sovereign tenants, every Pester Describe block in §2–§7 and §9 short-circuits to SKIPPED (see §0.4). The SOV namespace is the only namespace that runs to completion in a sovereign tenant, and it operates against artifacts produced by the manual quarterly attestation rather than against live Graph endpoints.
The manual attestation packet (sov-attestation-<quarterLabel>.json) is produced by the runbook in §13.4 and includes:
- A statement of the agent identity inventory (collected manually from agent owner records since live
/agentsGraph enumeration is unavailable). - A per-agent sponsor mapping (collected from internal CMDB / IGA system that does operate in the sovereign cloud).
- A per-agent access scope mapping (collected from group membership reports in the sovereign tenant).
- Attestation that no agent identity is operating outside its assigned sponsor's accountability.
- Sponsor and Compliance Officer digital signatures (PKCS#7 over canonical JSON).
8.3 Pester suite
Describe "AGT226-SOV" -Tag 'C2.26','SOV' {
BeforeAll {
$script:Sov = Test-Agt226SovereignTenant
$script:Quarter = Get-PreviousQuarterBoundary
$script:AttestationPath = Join-Path $env:AGT226_EVIDENCE_ROOT `
"sov-attestation-$($script:Quarter.label).json"
$script:SignaturePath = "$script:AttestationPath.sig"
}
Context "Applicability" {
It "this suite runs only in sovereign clouds" -Skip:(-not $script:Sov.is_sovereign) {
$script:Sov.cloud | Should -BeIn @('GCC','GCCH','DoD')
}
}
Context "Manual attestation packet exists" -Skip:(-not $script:Sov.is_sovereign) {
It "the attestation file for the prior quarter is present" {
Test-Path $script:AttestationPath | Should -BeTrue
}
It "the attestation file is digitally signed" {
Test-Path $script:SignaturePath | Should -BeTrue
$verify = Invoke-Pkcs7Verify -DataPath $script:AttestationPath -SignaturePath $script:SignaturePath
$verify.IsValid | Should -BeTrue
}
It "the attestation lists at least one named sponsor and contains the manual sponsor map" {
$att = Get-Content $script:AttestationPath -Raw | ConvertFrom-Json
$att.sponsors.Count | Should -BeGreaterThan 0
$att.sponsors[0].sponsor_upn | Should -Not -BeNullOrEmpty
$att.agent_inventory.Count | Should -BeGreaterThan 0
}
It "the attestation carries both Sponsor and Compliance Officer signatures" {
$att = Get-Content $script:AttestationPath -Raw | ConvertFrom-Json
$att.signatures.sponsor | Should -Not -BeNullOrEmpty
$att.signatures.compliance_officer | Should -Not -BeNullOrEmpty
$att.signatures.compliance_officer.signed_at | Should -Not -BeNullOrEmpty
}
It "attestation date is within the prior quarter window" {
$att = Get-Content $script:AttestationPath -Raw | ConvertFrom-Json
$signed = [datetime]$att.signatures.compliance_officer.signed_at
$signed | Should -BeGreaterOrEqual $script:Quarter.start
$signed | Should -BeLessOrEqual $script:Quarter.end.AddDays(15) # 15-day grace for sign-off
}
}
}
8.4 Sample passing record
{
"control_id": "2.26",
"namespace": "SOV",
"criterion": "C2.26-1..7 (compensating)",
"zone": "all",
"subject_id": "sov-attestation-Q1-2026",
"subject_type": "manual_attestation",
"status": "PASS",
"assertion": "Sovereign quarterly attestation present, signed by Sponsor and Compliance Officer",
"observed_value": {
"cloud": "GCCH",
"quarter_label": "Q1-2026",
"agent_inventory_count": 23,
"sponsor_count": 11,
"sponsor_signed_at": "2026-04-08T14:00:00Z",
"compliance_officer_signed_at": "2026-04-09T10:30:00Z",
"pkcs7_signature_valid": true
},
"regulator_mappings": ["FINRA-3110","SOX-404","OCC-2011-12","GLBA-501b"],
"evidence_artifacts": ["sov-attestation-Q1-2026.json","sov-attestation-Q1-2026.json.sig"],
"schema_version": "1.0"
}
8.5 Sample failing record
{
"control_id": "2.26",
"namespace": "SOV",
"criterion": "C2.26-1..7 (compensating)",
"status": "FAIL",
"assertion": "Sovereign quarterly attestation must be present and dual-signed",
"observed_value": {
"attestation_present": false,
"last_attestation_label": "Q4-2025",
"days_since_last_attestation": 121
},
"remediation_ref": "TRG-SOV-02",
"regulator_mappings": ["FINRA-3110","OCC-2011-12"],
"schema_version": "1.0"
}
TRG-SOV-02 (§11.9): immediately initiate the quarterly attestation runbook (§13.4); declare the lapse to the Compliance Officer and document for board reporting; do not mark prior-quarter SOV as PASS retroactively.
8.6 Examiner artifact
| Artifact | Filename | Retention |
|---|---|---|
| Quarterly attestation packet | sov-attestation-<quarterLabel>.json |
7 years |
| PKCS#7 detached signature | sov-attestation-<quarterLabel>.json.sig |
7 years |
| Manual agent inventory CSV | sov-inventory-<quarterLabel>.csv |
6 years |
8.7 Zone thresholds
| Zone | PASS | WARN | FAIL |
|---|---|---|---|
| All zones (sovereign tenants only) | Quarterly attestation present, dual-signed, within 15-day grace of quarter close | Attestation 16–45 days late, with documented justification | Attestation > 45 days late or signature invalid |
8.8 Regulator mapping
| Test | FINRA 3110 | SOX §404 | OCC 2011-12 | FFIEC IS |
|---|---|---|---|---|
| Manual attestation cadence | ✓ supervision under sovereign constraints | ✓ compensating control documented | ✓ model owner accountability preserved | ✓ access management compensating |
| Dual signature | ✓ supervisor + compliance | ✓ segregation of duties | ✓ challenger | ✓ separation |
§9 PREVIEW — Frontier + Copilot License & Agent ID Blade Verification
9.1 Criterion mapping
This namespace evidences C2.26-1: the Entra Agent ID feature is enabled in the tenant. Enablement requires two independent gates to be true at the time of verification:
- The tenant holds at least one Microsoft 365 Copilot SKU assignment (the "license gate" already executed in PRE-05).
- The tenant is enrolled in the Microsoft Frontier program for early-access AI capabilities (the "Frontier gate" already executed in PRE-06).
In addition, the Agent identities blade must be reachable in the Microsoft Entra admin center. Because the blade itself does not have a stable Graph API for "blade rendering", the Pester test for blade visibility is replaced by an API-surface probe: if /beta/agents returns HTTP 200 with a JSON body (even an empty collection), the blade is considered present and operable.
9.2 Pre-conditions
- PRE-05 (Copilot license) and PRE-06 (Frontier enrollment) returned
PASS. - Graph context holds
AgentIdentity.Read.All. - Probe URL:
https://graph.microsoft.com/beta/agents?$top=1for Commercial; sovereign equivalents per the shared sovereign endpoint table (the SOV namespace handles the sovereign skip path; this section operates in Commercial only).
9.3 Pester suite
Describe "AGT226-PREVIEW" -Tag 'C2.26','PREVIEW' {
BeforeAll {
$script:CopilotSkus = Get-MgSubscribedSku -All |
Where-Object { $_.SkuPartNumber -like 'Microsoft_365_Copilot*' }
$script:Probe = $null
try {
$script:Probe = Invoke-MgGraphRequest -Method GET `
-Uri 'https://graph.microsoft.com/beta/agents?$top=1' -ErrorAction Stop
} catch {
$script:ProbeError = $_.Exception.Message
}
}
Context "License gate (C2.26-1a)" -Skip:$script:IsSovereign {
It "tenant holds at least one Microsoft 365 Copilot SKU" {
$script:CopilotSkus.Count | Should -BeGreaterThan 0
}
It "at least one Copilot license is assigned (consumedUnits > 0)" {
($script:CopilotSkus | Measure-Object -Property ConsumedUnits -Sum).Sum |
Should -BeGreaterThan 0
}
}
Context "Frontier program gate (C2.26-1b)" -Skip:$script:IsSovereign {
It "the /beta/agents endpoint returns HTTP 200 (Frontier enrolled)" {
$script:Probe | Should -Not -BeNullOrEmpty
$script:Probe.value | Should -BeOfType [System.Object[]]
}
It "the response shape contains the expected agent-identity properties when populated" {
if ($script:Probe.value.Count -gt 0) {
$first = $script:Probe.value[0]
$first.id | Should -Not -BeNullOrEmpty
$first.displayName | Should -Not -BeNullOrEmpty
# sponsor field key may vary by schema generation; tolerate both
($first.sponsorObjectId -or $first.sponsorId) | Should -BeTrue
}
}
}
Context "Blade operability proxy (C2.26-1c)" -Skip:$script:IsSovereign {
It "blade probe did not return 403 (RBAC misconfiguration)" {
$script:ProbeError | Should -NotMatch '403'
}
It "blade probe did not return 404 (Frontier not enrolled despite PRE-06)" {
$script:ProbeError | Should -NotMatch '404'
}
}
}
9.4 Sample passing record
{
"control_id": "2.26",
"namespace": "PREVIEW",
"criterion": "C2.26-1",
"zone": "all",
"subject_id": "tenant-feature-state",
"subject_type": "feature_enablement",
"status": "PASS",
"assertion": "Copilot licensed AND Frontier enrolled AND Agent ID API reachable",
"observed_value": {
"copilot_skus": ["Microsoft_365_Copilot_E5"],
"copilot_consumed_units": 1500,
"frontier_probe_status": 200,
"agents_endpoint_response": "ok",
"agents_count_in_probe": 23
},
"regulator_mappings": ["SOX-404","OCC-2011-12"],
"evidence_artifacts": ["preview-state-AGT226-20260415-093012-a1b2c3d4.json"],
"schema_version": "1.0"
}
9.5 Sample failing record
{
"control_id": "2.26",
"namespace": "PREVIEW",
"criterion": "C2.26-1",
"status": "FAIL",
"assertion": "Frontier enrollment gate must return HTTP 200 from /beta/agents",
"observed_value": {
"copilot_skus": ["Microsoft_365_Copilot_E5"],
"frontier_probe_status": 404,
"probe_error": "Resource not found: /beta/agents"
},
"remediation_ref": "TRG-PREVIEW-01",
"regulator_mappings": ["SOX-404","OCC-2011-12"],
"schema_version": "1.0"
}
TRG-PREVIEW-01 (§11.10): contact Microsoft account team to confirm Frontier enrollment status; do not deploy any Z2/Z3 agents until enrollment confirmed; downgrade any Z2/Z3 agents created during the gap to Z1 and re-evaluate when enrollment is restored.
9.6 Examiner artifact
| Artifact | Filename | Retention |
|---|---|---|
| Feature-state snapshot | preview-state-<runId>.json |
6 years |
| Copilot SKU list | preview-skus-<runId>.json |
6 years |
| Frontier probe response (raw) | preview-frontier-probe-<runId>.json |
6 years |
9.7 Zone thresholds
| Zone | PASS | WARN | FAIL |
|---|---|---|---|
| All zones | Both gates PASS AND probe returns 200 | one gate PASS, other PASS within prior 7 days but transient probe error | either gate FAIL or probe returns 403/404 |
9.8 Regulator mapping
| Test | SOX §404 | OCC 2011-12 |
|---|---|---|
| License gate | ✓ control prerequisite | ✓ tooling licensed for model use |
| Frontier gate | ✓ feature in known state | ✓ change-managed feature enablement |
| Blade probe | ✓ control operable | ✓ |
§10 Evidence Pack Assembly, Signing & Retention
10.1 Purpose
Each Pester run produces dozens of individual evidence records. The evidence pack consolidates them into a single, signed, hash-chained artifact that an examiner can ingest as one unit.
The companion function in PowerShell Setup §6, Export-Control226EvidencePackage, produces the pack. This section documents the schema the pack must conform to, the hash chain that links the records, and the retention policy applied at the storage layer.
10.2 Pack structure
evidence-pack-AGT226-20260415-093012-a1b2c3d4/
├── manifest.json # Top-level descriptor (schema below)
├── attestation.json # Signature chain
├── records/
│ ├── 0001-PREVIEW.json
│ ├── 0002-SPONSOR-zone1.json
│ ├── 0003-SPONSOR-zone2.json
│ ├── ... (one file per evidence record)
│ └── NNNN-SOV.json
├── pester/
│ ├── pester-PREVIEW.xml
│ ├── pester-SPONSOR.xml
│ └── ... (JUnit XML per namespace)
├── snapshots/
│ ├── sponsor-snapshot-<runId>.json
│ ├── accesspkg-snapshot-<runId>.json
│ ├── lifecycle-runs-<runId>.json
│ ├── review-decisions-<quarterLabel>.json
│ ├── expiry-snapshot-<runId>.json
│ └── siem-forwarding-<runId>.json
└── README.md # Plain-text examiner orientation
10.3 manifest.json schema
{
"pack_version": "1.0",
"control_id": "2.26",
"run_id": "AGT226-20260415-093012-a1b2c3d4",
"tenant_id": "11111111-2222-3333-4444-555555555555",
"tenant_display_name": "Contoso Bank, N.A.",
"cloud": "Commercial",
"produced_at": "2026-04-15T09:42:18Z",
"operator_upn": "agt226-runner@contoso.com",
"namespaces_executed": ["PREVIEW","SPONSOR","ACCESSPKG","LIFECYCLE","REVIEW","EXPIRY","SIEM"],
"namespaces_skipped": [],
"record_count": 187,
"pass_count": 184,
"fail_count": 2,
"warn_count": 1,
"skipped_count": 0,
"criteria_coverage": {
"C2.26-1": "PASS",
"C2.26-2": "PASS",
"C2.26-3": "PASS",
"C2.26-4": "PASS",
"C2.26-5": "FAIL",
"C2.26-6": "PASS",
"C2.26-7": "FAIL",
"C2.26-8": "WARN"
},
"merkle_root_sha256": "9c1b...e7af",
"signing_certificate_thumbprint": "AB12...EF34"
}
10.4 Hash chain (attestation.json)
Each record file is hashed (SHA-256 over its UTF-8 byte content). The hashes are concatenated in lexical filename order and hashed again to form the Merkle root. The Merkle root is then signed by the operator's code-signing certificate. The chain is committed to attestation.json:
{
"algorithm": "SHA-256",
"leaves": [
{ "filename": "records/0001-PREVIEW.json", "sha256": "a1b2..." },
{ "filename": "records/0002-SPONSOR-zone1.json", "sha256": "c3d4..." },
{ "filename": "records/0003-SPONSOR-zone2.json", "sha256": "e5f6..." }
],
"merkle_root_sha256": "9c1b...e7af",
"signatures": [
{
"role": "ai_governance_lead",
"signer_upn": "agt226-runner@contoso.com",
"certificate_thumbprint": "AB12...EF34",
"signed_at": "2026-04-15T09:42:30Z",
"signature_pkcs7_b64": "MIIE...=="
},
{
"role": "compliance_officer",
"signer_upn": "compliance.officer@contoso.com",
"certificate_thumbprint": "CD56...7890",
"signed_at": "2026-04-15T16:05:11Z",
"signature_pkcs7_b64": "MIIE...=="
}
]
}
A second signature from the Compliance Officer is appended within 24 hours of pack production. Packs without a counter-signature are considered draft and may not be cited as primary evidence in an examination.
10.5 Schema validation
function Test-Agt226EvidenceSchema {
[CmdletBinding()]
param([Parameter(Mandatory)][string]$RecordPath)
$required = @(
'control_id','run_id','run_timestamp','tenant_id','cloud',
'namespace','criterion','zone','subject_id','subject_type',
'status','assertion','observed_value','expected_value',
'evidence_artifacts','regulator_mappings','schema_version'
)
$rec = Get-Content $RecordPath -Raw | ConvertFrom-Json
$missing = $required | Where-Object { -not $rec.PSObject.Properties.Name.Contains($_) }
[pscustomobject]@{
path = $RecordPath
valid = ($missing.Count -eq 0)
missing = $missing
}
}
The pack assembler MUST refuse to publish a pack containing any record that fails schema validation.
10.6 Retention & storage
| Layer | Where | Retention | Immutability |
|---|---|---|---|
| Hot | Azure Blob Storage container agt226-evidence-hot |
90 days | Versioning + soft delete |
| Warm | Azure Blob Storage container agt226-evidence-warm (Cool tier) |
1 year | Versioning + legal hold during audits |
| Cold | Azure Blob Storage container agt226-evidence-cold (Archive tier) with immutability policy locked |
6 years from pack date (FINRA 4511); 7 years for SOV packs | Locked time-based retention policy |
| Mirror | SIEM index agt226-evidence (parsed manifest + criteria coverage only) |
6 years | Immutable index |
The lifecycle policy that promotes packs from Hot → Warm → Cold is defined in PowerShell Setup §6.3. The immutability policy is locked at the storage account level and cannot be removed during the retention window — this is the property that satisfies SEC Rule 17a-4(f) WORM expectations.
10.7 Examiner-friendly README
Every pack ships with a README.md written for a non-technical examiner audience. The README:
- Names the control (
2.26 — Entra Agent ID Identity Governance). - States the run window (e.g., "Evidence collected for the 24-hour window ending 2026-04-15 09:30 UTC").
- Lists the eight Verification Criteria and the namespace each maps to.
- Provides a quick-glance table of
criteria_coveragefrommanifest.json. - Points the examiner to the relevant
records/*.jsonandsnapshots/*.jsonfiles for any FAIL. - Includes the operator's name and contact email.
§11 Failure Triage Matrix
11.1 Severity bands & escalation SLAs
| Severity | Meaning | Page on-call within | Containment SLA | Resolution SLA | Examples |
|---|---|---|---|---|---|
| Critical | Z3 agent operating without sponsor, perpetual Z3 grant, stale-grant > 24h | 15 minutes | 1 hour (suspend) | 24 hours | TRG-SPONSOR-01 (Z3 sponsor null), TRG-EXPIRY-01 (Z3 stale), TRG-PREVIEW-01 |
| High | Z2 agent without sponsor, lifecycle workflow failure, SIEM retention < 5y, missing quarterly review | 1 hour | 4 hours | 72 hours | TRG-SPONSOR-02 (Z2), TRG-LIFECYCLE-01, TRG-SIEM-01, TRG-REVIEW-01 |
| Medium | Z2 expiry > 24h–72h late, package expiry 366–400 days, manager declines transfer (escalation) | 4 hours | 24 hours | 7 days | TRG-EXPIRY-02, TRG-ACCESSPKG-01, TRG-LIFECYCLE-02 |
| Low | Z1 sponsor != creator (data hygiene), justification < 50 chars, single transient probe error | 1 business day | 5 business days | 30 days | TRG-SPONSOR-03, TRG-EXPIRY-03 |
11.2 TRG-SPONSOR-01 — Z3 sponsor null or inactive
- Trigger: §2 Pester suite reports a Z3 agent with
sponsorObjectId == nullorsponsor_active == false. - Containment: within 4 hours, suspend the agent identity (set
accountEnabled = falseviaUpdate-MgAgent); notify Compliance Officer. - Remediation: identify successor sponsor via the agent owner team's escalation list; run
Set-AgentSponsorBulk -AgentId <id> -SponsorUpn <new>from the sister playbook; re-enable agent only after both primary and backup sponsor are recorded. - Evidence to attach: the failing record, the suspension audit-event ID, the new sponsor mapping, the Compliance Officer acknowledgement.
- Cross-reference: Troubleshooting §3.1.
11.3 TRG-ACCESSPKG-02 — Z3 direct (non-package) assignment detected
- Trigger: §3 reports any direct group/role assignment on a Z3 agent.
- Containment: remove the direct assignment within 24 hours via
Remove-MgGroupMemberByReforRemove-MgRoleManagementDirectoryRoleAssignment. - Remediation: identify the equivalent access package and request assignment via the package; document the variance with root-cause (most commonly: emergency access not subsequently rolled into a package).
- Cross-reference: Troubleshooting §3.2.
11.4 TRG-LIFECYCLE-01 — Departure not processed
- Trigger: §4 reports a departed sponsor whose agents still point at the departed user.
- Containment: manually invoke
Set-AgentSponsorBulkwith the departed user's manager as the new sponsor. - Remediation: open a problem ticket against the lifecycle workflow; capture the workflow run failure log; if the workflow consistently fails, raise a Microsoft support case under the Frontier program.
11.5 TRG-LIFECYCLE-02 — Manager declines transfer (escalation)
- Trigger: §4 workflow run completes with
processing_status == 'completedWithErrors'and the error indicates the manager declined to accept ownership. - Action: activate the suspension path: set agent
accountEnabled = false; assign temporary stewardship to the AI Governance Lead; place the agent in a 30-day reassignment hold; if no permanent owner is named within 30 days, retire the agent per Control 3.6. - This is the ONE case where suspension is the correct outcome. The Pester tests do not preemptively check for suspension; they only check for the manager-transfer outcome. Suspension only enters the picture when the manager-transfer path fails.
11.6 TRG-REVIEW-01 — Quarterly review incomplete
- Trigger: §5 reports an in-scope campaign with
status != 'Completed'at quarter close, or pending decisions without auto-applied default. - Containment: force-apply the configured default decision (
Deny) viaUpdate-MgIdentityGovernanceAccessReviewDefinitionInstance; this removes access from any reviewer-skipped subject. - Remediation: investigate why the campaign did not complete (most commonly: reviewer absent without a configured fallback reviewer); reconfigure fallback reviewers; document the lapse for the next §13 packet and for the next 1.2 sponsor attestation cycle.
11.7 TRG-EXPIRY-01 — Stale grant (expired but still granting access)
- Trigger: §6 reports a Z3 assignment past its
expiredDateTimewhose underlying group/role membership still resolves. - Containment: within 1 hour, force-remove via
Remove-MgEntitlementManagementAssignmentRequestor, failing that, direct group/role removal. - Remediation: raise a Critical incident; invoke root-cause analysis (e.g., entitlement management background job stalled, custom workflow blocking removal); add to the next §13 packet for board reporting.
11.8 TRG-SIEM-01 — SIEM retention < 6 years or not immutable
- Trigger: §7 reports
siem_retention_days < 2190orsiem_immutable == false. - Containment: as a temporary compensating control, configure an Azure Storage account with an immutable, time-based retention policy of 6 years and export Entra Diagnostic Settings to that account in parallel with the SIEM.
- Remediation: engage SecOps to extend SIEM retention; raise the variance with the Compliance Officer; document the temporary parallel export and decommission it once SIEM is compliant.
11.9 TRG-SOV-02 — Sovereign attestation missing or late
- Trigger: §8 reports the prior-quarter attestation absent or > 45 days late.
- Containment: initiate the §13.4 quarterly attestation runbook immediately.
- Remediation: declare the lapse; the prior quarter cannot be retroactively passed; document for board reporting.
11.10 TRG-PREVIEW-01 — Frontier or Copilot gate FAIL
- Trigger: §9 reports
frontier_probe_statusof 403/404, or no Copilot SKU consumed. - Containment: halt deployment of any new Z2/Z3 agents.
- Remediation: contact Microsoft account team; downgrade any Z2/Z3 agents created during the gap to Z1 and re-evaluate when enrollment is restored.
11.11 Generic "Low" data-hygiene findings
Findings classified Low (e.g., justification text under 50 chars, transient probe errors, single Z1 sponsor mismatch) accumulate in a single low-findings-<runId>.csv artifact. They are reviewed monthly by the AI Governance Lead and any pattern (e.g., consistent under-50-char justifications from one team) is escalated to a training intervention rather than an incident.
§12 Cross-Control Verification Dependencies
Control 2.26 does not stand alone. Two adjacent controls produce or consume data that this verification playbook depends on or feeds:
12.1 Upstream — Control 1.2 Sponsor Attestation
The SPONSOR namespace (§2) takes as input the golden agent list maintained by Control 1.2. Specifically:
agents-expected.csv— the authoritative list of agents that should exist in the tenant, with their assigned sponsor and zone classification, signed by the AI Governance Lead during the most recent 1.2 attestation cycle.- The 1.2 attestation packet's hash chain is referenced in 2.26's
manifest.jsonasupstream_dependencies[0], providing a verifiable link from a 2.26 evidence pack back to the 1.2 packet that authored its expectations.
If the 1.2 attestation is overdue (> 30 days), the SPONSOR namespace emits a WARN-level record (status: WARN, assertion: "Upstream 1.2 attestation overdue; sponsor expectations may be stale") for every Z2/Z3 agent. This makes a 1.2 attestation lapse visible in 2.26's evidence trail rather than silently degrading 2.26's signal.
| 2.26 dependency | Source artifact in 1.2 | Failure if missing |
|---|---|---|
| Expected agent inventory | 1.2/sponsor-attestation-<quarterLabel>.json → agents[] |
SPONSOR namespace WARN; manual reconciliation required |
| Sponsor signature on each agent | Same → agents[].sponsor_signature |
TRG-SPONSOR-04 (data hygiene) |
| Quarter-aligned attestation date | Same → attestation_date |
If > 90 days old, WARN; if > 180 days, FAIL |
12.2 Downstream — Control 3.6 Orphan Agent Detection
The LIFECYCLE namespace (§4) produces a lifecycle-runs-<runId>.json artifact that lists every workflow run, its outcome, and the agents affected. Control 3.6's orphan-detection job consumes this artifact to:
- Identify agents whose sponsor was successfully transferred to a manager but whose manager has subsequently also departed (a "double-departure orphan").
- Identify agents flagged as
completedWithErrorsin 2.26 for which a 30-day suspension hold has expired without reassignment (a "permanent orphan").
The 3.6 control's orphan registry, in turn, feeds back into the next 2.26 SPONSOR run as an input dimension: an agent listed in the 3.6 orphan registry is flagged in §2's evidence record with observed_value.orphan_registry_match: true.
| 2.26 output | 3.6 consumer behavior | 2.26-side test |
|---|---|---|
lifecycle-runs-<runId>.json |
Orphan detector parses for completedWithErrors runs |
None (3.6 owns its own verification) |
sponsor-failures-<runId>.json |
Orphan detector promotes all entries to its registry | None |
criteria_coverage.C2.26-5 == FAIL |
Triggers a 3.6 sweep within 4 hours | §11.4 includes a notification step to the 3.6 owner |
12.3 Cross-pack reconciliation test
A weekly out-of-band Pester run executes Test-Agt226-CrossControlReconciliation, which ensures:
Describe "AGT226-XCONTROL" -Tag 'C2.26','XCTRL' {
It "every agent in the most recent 1.2 attestation appears in the most recent 2.26 SPONSOR run" {
$expected = (Get-Latest12Attestation).agents.id | Sort-Object
$observed = (Get-Latest226SponsorPack).records |
Where-Object namespace -eq 'SPONSOR' |
ForEach-Object subject_id | Sort-Object -Unique
$missing = Compare-Object $expected $observed | Where-Object SideIndicator -eq '<='
$missing.Count | Should -Be 0
}
It "every Critical FAIL in the most recent 2.26 LIFECYCLE run is reflected in the 3.6 orphan registry within 4 hours" {
$criticalFails = (Get-Latest226Pack).records |
Where-Object { $_.namespace -eq 'LIFECYCLE' -and $_.status -eq 'FAIL' }
foreach ($f in $criticalFails) {
$orphan = Get-36OrphanRegistry | Where-Object subject_id -eq $f.subject_id
$orphan | Should -Not -BeNullOrEmpty
((Get-Date) - [datetime]$orphan.added_at).TotalHours | Should -BeLessOrEqual 4
}
}
}
The reconciliation evidence record is itself signed and stored alongside the 2.26 evidence pack in a sub-folder xcontrol/.
§13 Quarterly Attestation Runbook for the AI Governance Lead
13.1 Purpose
The quarterly attestation is the human gate that turns a stack of automated evidence packs into a single, signed, examiner-presentable assertion: "As AI Governance Lead, I have reviewed the prior quarter of Control 2.26 evidence and certify that all eight Verification Criteria were met, or where they were not, that the documented remediation completed within SLA."
This runbook is executed by the AI Governance Lead within 15 calendar days of the quarter's close and counter-signed by the Compliance Officer within 30 days.
13.2 Inputs
The Lead assembles, in order:
- The 12 weekly evidence packs produced over the prior quarter (one per week from the standing Pester schedule).
- The 90 daily SPONSOR/EXPIRY snapshots (light-weight subsets of the full pack).
- The prior quarter's REVIEW campaign decision exports.
- The most recent 1.2 sponsor attestation (upstream dependency per §12.1).
- The 3.6 orphan registry delta over the quarter (downstream consumer per §12.2).
- Any incident tickets with severity Critical or High that traced back to a §11 triage entry.
13.3 Procedure (Commercial cloud)
| Step | Action | Owner | Output |
|---|---|---|---|
| 1 | Run Get-AgentGovernanceSummary -Quarter $prior from sister playbook |
AI Governance Lead | summary-<quarterLabel>.json |
| 2 | Verify criteria_coverage shows all 8 criteria as PASS or PASS-with-remediated-FAIL |
AI Governance Lead | Pass/Fail decision |
| 3 | For each remediated FAIL, attach the closing ticket (resolution evidence) | AI Governance Lead | remediations-<quarterLabel>/ folder |
| 4 | Compute SHA-256 over summary-<quarterLabel>.json + remediation folder canonical JSON |
AI Governance Lead | Attestation hash |
| 5 | Sign attestation packet with code-signing certificate | AI Governance Lead | quarterly-attestation-<quarterLabel>.json.sig |
| 6 | Forward to Compliance Officer for counter-signature within 30 days | AI Governance Lead → Compliance Officer | Counter-signature appended |
| 7 | Promote signed packet to Cold storage with 7-year locked immutability | Compliance Officer | Immutable-locked blob URL |
| 8 | File the packet URL with the Board Risk Committee secretariat | Compliance Officer | Board minute reference |
13.4 Procedure (sovereign cloud variant)
Sovereign tenants follow steps 1–8 above with these substitutions:
- Step 1 source: manual agent inventory + sponsor map gathered from the sovereign-resident IGA / CMDB rather than from
Get-AgentGovernanceSummary. - Step 2 evaluation: per-criterion attestation that the compensating manual control was performed (the SOV namespace evidence pack from §8 satisfies this).
- Step 5: signature is over the manual attestation packet structured per §8.2 rather than the automated summary.
- Step 7: 7-year retention (one year more than commercial), reflecting the higher reliance on the manual attestation as primary evidence.
13.5 Quarterly attestation packet schema
{
"schema_version": "1.0",
"control_id": "2.26",
"quarter_label": "Q1-2026",
"quarter_start": "2026-01-01T00:00:00Z",
"quarter_end": "2026-03-31T23:59:59Z",
"tenant_id": "11111111-2222-3333-4444-555555555555",
"cloud": "Commercial",
"criteria_attestation": {
"C2.26-1": { "status": "PASS", "evidence_pack_count": 12, "fail_remediated_count": 0 },
"C2.26-2": { "status": "PASS", "evidence_pack_count": 12, "fail_remediated_count": 1, "remediation_refs": ["INC-2026-0421"] },
"C2.26-3": { "status": "PASS", "evidence_pack_count": 12, "fail_remediated_count": 0 },
"C2.26-4": { "status": "PASS", "evidence_pack_count": 12, "fail_remediated_count": 0 },
"C2.26-5": { "status": "PASS", "evidence_pack_count": 12, "fail_remediated_count": 2, "remediation_refs": ["INC-2026-0489","INC-2026-0512"] },
"C2.26-6": { "status": "PASS", "evidence_pack_count": 1, "fail_remediated_count": 0 },
"C2.26-7": { "status": "PASS", "evidence_pack_count": 12, "fail_remediated_count": 1, "remediation_refs": ["INC-2026-0533"] },
"C2.26-8": { "status": "PASS", "evidence_pack_count": 12, "fail_remediated_count": 0 }
},
"upstream_attestation_ref": "1.2/sponsor-attestation-Q1-2026.json",
"downstream_consumer_ref": "3.6/orphan-registry-Q1-2026.json",
"signatures": {
"ai_governance_lead": {
"signer_upn": "ai.gov.lead@contoso.com",
"signed_at": "2026-04-12T15:00:00Z",
"certificate_thumbprint": "AB12...EF34",
"signature_pkcs7_b64": "MIIE...=="
},
"compliance_officer": {
"signer_upn": "compliance.officer@contoso.com",
"signed_at": "2026-04-22T11:30:00Z",
"certificate_thumbprint": "CD56...7890",
"signature_pkcs7_b64": "MIIE...=="
}
}
}
13.6 Failure path within the runbook itself
If any criterion ends the quarter with an unremediated FAIL, the Lead MUST NOT sign a clean attestation. Instead:
- Mark
criteria_attestation.<criterion>.status = "FAIL_OPEN". - Attach the open ticket reference and projected remediation date.
- Sign the packet anyway (a "qualified attestation"); counter-signature still required.
- Notify the Board Risk Committee secretariat within 5 business days.
- Repeat the quarterly cycle on the closing of the open finding (interim attestation).
A qualified attestation is itself examiner-defensible — it documents that the control owner is aware of, and managing, an open issue. A missing attestation, by contrast, is not.
13.7 Validation runbook
After signing, the Lead runs:
which verifies:
- All 8 criteria are present.
- All
evidence_pack_countvalues are ≥ the expected cadence (12 weekly for most; 1 for REVIEW; 90 for SPONSOR/EXPIRY daily snapshots). - All referenced
remediation_refsresolve to closed tickets. - Both signatures verify against trusted certificate authorities.
- The packet is hash-linked to the upstream 1.2 attestation and to the downstream 3.6 orphan registry.
A PASS from this validator is the green light to file with the Board secretariat.
Footer
| Control | 2.26 — Entra Agent ID Identity Governance |
| Sister playbooks | Portal Walkthrough · PowerShell Setup · Troubleshooting |
| Last UI verified | April 2026 |
| Document version | v1.0 |
| Updated | April 2026 |
This playbook supports — but does not by itself ensure — compliance with FINRA Rules 3110 and 4511, FINRA Notice 25-07, SEC Rule 17a-4, SOX §404, GLBA §501(b), OCC Bulletin 2011-12, and the FFIEC IT Examination Handbook. Organizations should verify findings against their own legal and regulatory obligations and tailor zone thresholds to their documented risk appetite.