Skip to content

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.

Post-GA status (May 2026): Microsoft Agent 365 reached general availability on May 1, 2026 and Microsoft Entra Agent ID is generally available. The pre-GA "Frontier program enrollment" gate is replaced by Microsoft Agent 365 / Microsoft 365 E7 license assignment. Pre-flight gate PRE-06 and the §8 TRG-LICGAP-01 test retain their pre-GA names for backward compatibility; the underlying probe (HTTP 200 from /beta/agents) remains the correct observable because both the pre-GA Frontier enrollment and the post-GA license assignment manifest as the same API reachability state for the calling principal. TRG-LICGAP-01 was renamed to TRG-LICGAP-01 in issue #418.

Scope: Commercial M365 tenants with Microsoft Agent 365 or Microsoft 365 E7 licensing assigned to the operating admin and the in-scope sponsor / agent-owner users.

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: May 2026 against Microsoft Entra admin center build 2026.05.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.
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 §8.

Regulatory framing. This playbook helps meet recordkeeping, access-management, and oversight expectations under FINRA Rules 3110 and 4511, FINRA RN 24-09 / Rule 3110, SEC Rule 17a-4, SOX §404, GLBA §501(b), OCC Bulletin 2026-13 (formerly OCC Bulletin 2011-12), and FFIEC IT Examination Handbook (Information Security & Management). It does not by itself support every compliance obligation; organizations should verify findings against their own legal and regulatory obligations and tailor thresholds to their risk appetite.


§0 Pre-Test Prerequisites

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, §8)
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 §8

The Pester suites in §2–§8 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–§8 execute)

The bootstrap script Invoke-Agt226PreFlight.ps1 runs six pre-flight gates. Any FAIL halts the suite and emits a single evidence artifact preflight-FAILED-<runId>.json.

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
License gate PRE-04 Confirms tenant holds at least one Microsoft Agent 365 or Microsoft 365 E7 SKU (post-GA: SkuPartNumber -like 'Microsoft_Agent_365*' -or -like 'M365_E7*'). Pre-GA, this gate looked for a Microsoft_365_Copilot* SKU — verify exact SKU part numbers in your tenant via Get-MgSubscribedSku and update the regex if needed. HALT — Agent ID requires post-GA licensing
Agent ID API operability gate PRE-05 Confirms the /beta/agents endpoint is reachable to the calling principal (HTTP 200 vs 404/403). Pre-GA this gate also evidenced Frontier program enrollment; post-GA a 404/403 most likely indicates a license-coverage gap, RBAC gap, or feature non-availability. HALT with remediation pointer (license + RBAC)
Clock skew gate PRE-06 Compares local UTC to Date header from Graph response; aborts if drift > 60 seconds HALT

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 §8.


§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 (§8).

Namespace Evidences Criterion Section Cadence Owner
LICGAP C2.26-1 (Agent ID surface reachable — Microsoft Agent 365 / M365 E7 + Microsoft 365 Copilot license coverage) §8 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

Each namespace section in this document follows an identical structure:

  1. Criterion mapping — explicit pointer to which numbered criterion in Control 2.26 §8 is satisfied.
  2. Pre-conditions — what must already be true (e.g., access packages exist, Graph scopes granted).
  3. Pester suiteDescribe "AGT226-{NS}" { Context "Zone {1|2|3}" { It "..." } } with PS 7.4 / Pester 5.5 syntax.
  4. Sample passing JSON evidence record — exact shape that flows into the evidence pack.
  5. Sample failing JSON evidence record + remediation pointer — links to a numbered triage entry in §8.
  6. Examiner artifact — filename pattern, retention duration, signing policy.
  7. Zone 1 / Zone 2 / Zone 3 thresholds — pass/warn/fail bands per zone.
  8. 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 §8.

{
  "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 2026-13 (formerly 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 §8).

2.2 Pre-conditions

  • PRE-01 through PRE-06 from §0 returned PASS.
  • The current Graph context holds AgentIdentity.Read.All and User.Read.All delegated or application scopes.
  • The sister function Get-AllAgentIdentities from 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 {
    $script:Agents = Get-AllAgentIdentities
    $script:UnsponsoredZ23 = Get-AgentsWithoutSponsors -Zones 2,3
}

Describe "AGT226-SPONSOR" -Tag 'C2.26','SPONSOR' {

    Context "Zone 2" {
        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" {
        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" {
        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 §8.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 Bulletin 2026-13 (formerly 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 single FAIL record 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)" {
        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)" {
        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)" {
        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 (§8.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 Bulletin 2026-13 (formerly 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 = false or carries the employeeLeaveDateTime past 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 §8.5).

4.2 Pre-conditions

  • Lifecycle Workflows feature is licensed (Entra ID Governance SKU).
  • The reference workflow Agent Sponsor Departure Transfer exists and is in enabled state.
  • 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" {
        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" {
        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" {
        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 (§8.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 §8.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 Bulletin 2026-13 (formerly 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 Recertification is 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" {
        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" {
        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-RN-24-09","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 (§8.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 RN 24-09 SOX §404 OCC Bulletin 2026-13 (formerly 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)" {
        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)" {
        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 (§8.7): immediately remove the assignment via Remove-MgEntitlementManagementAssignmentRequest; open a Critical incident; capture root-cause and add to the next §8 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 Bulletin 2026-13 (formerly 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 on category eq 'AuditLogs' AND targetResources.type eq 'AgentIdentity' (or the equivalent type token in the active Graph schema). The Pester suite therefore tests:

  1. Entra Diagnostic Setting exists at the tenant level and emits the relevant categories.
  2. The SIEM has received at least one event matching the agent-identity correlation rule in the prior 24 hours.
  3. 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 exports AuditLogs, SignInLogs, IdentityRiskEvents, and EnrichedOffice365AuditLogs ().
  • A SIEM API endpoint and bearer token are available via $env:AGT226_SIEM_BASEURL and $env:AGT226_SIEM_TOKEN.
  • The SIEM correlation rule AgentID-Lifecycle-Events is 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" {
        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" {
        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 (§8.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 LICGAP — Agent ID Licensing & Blade Operability Verification

Namespace renamed — Issue #418

Pre-GA this namespace evidenced Microsoft Frontier program enrollment + Copilot licensing. Post-GA (May 2026) it evidences Microsoft Agent 365 / Microsoft 365 E7 license assignment plus Agent ID API surface operability. In issue #418: namespace renamed PREVIEWLICGAP; runbook ID renamed TRG-PREVIEW-01TRG-LICGAP-01; field renamed frontier_probe_statusagent_id_probe_status. Update any examiner pipelines that parse these keys.

8.1 Criterion mapping

This namespace evidences C2.26-1: the Entra Agent ID feature is enabled in the tenant. Post-GA, enablement requires two independent gates to be true at the time of verification:

  1. The tenant holds at least one Microsoft Agent 365 or Microsoft 365 E7 SKU assignment (the "license gate" already executed in PRE-04). Pre-GA this gate looked for a Microsoft_365_Copilot* SKU; post-GA the relevant SKUs are Agent 365 (standalone) or M365 E7 (suite). Verify exact SKU part numbers in your tenant via Get-MgSubscribedSku before adopting the patterns below.
  2. The Agent ID API surface is reachable to the calling principal (the "operability gate" already executed in PRE-05).

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.

8.2 Pre-conditions

  • PRE-04 (Agent 365 / M365 E7 license) and PRE-05 (Agent ID API operability) returned PASS.
  • Graph context holds AgentIdentity.Read.All.
  • Probe URL: https://graph.microsoft.com/beta/agents?$top=1.

8.3 Pester suite

Describe "AGT226-LICGAP" -Tag 'C2.26','LICGAP' {

    BeforeAll {
        # Post-GA: prefer Agent 365 / M365 E7 SKUs. Verify SkuPartNumber values
        # in your tenant via Get-MgSubscribedSku before relying on these patterns.
        $script:LicenseSkus = Get-MgSubscribedSku -All |
            Where-Object {
                $_.SkuPartNumber -like 'Microsoft_Agent_365*' -or
                $_.SkuPartNumber -like 'M365_E7*'             -or
                # transitional fallback for tenants still on pre-GA Copilot SKUs
                $_.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)" {
        It "tenant holds at least one Agent 365 / M365 E7 (or transitional Copilot) SKU" {
            $script:LicenseSkus.Count | Should -BeGreaterThan 0
        }
        It "at least one license is assigned (consumedUnits > 0)" {
            ($script:LicenseSkus | Measure-Object -Property ConsumedUnits -Sum).Sum |
                Should -BeGreaterThan 0
        }
    }

    Context "Agent ID API operability gate (C2.26-1b)" {
        It "the /beta/agents endpoint returns HTTP 200 (Agent ID surface reachable)" {
            $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)" {
        It "blade probe did not return 403 (RBAC or license-coverage misconfiguration)" {
            $script:ProbeError | Should -NotMatch '403'
        }
        It "blade probe did not return 404 (Agent ID surface not reachable to caller)" {
            $script:ProbeError | Should -NotMatch '404'
        }
    }
}

8.4 Sample passing record

{
  "control_id": "2.26",
  "namespace": "LICGAP",
  "criterion": "C2.26-1",
  "zone": "all",
  "subject_id": "tenant-feature-state",
  "subject_type": "feature_enablement",
  "status": "PASS",
  "assertion": "Agent 365 / M365 E7 licensed AND Agent ID API reachable",
  "observed_value": {
    "license_skus": ["Microsoft_Agent_365"],
    "license_consumed_units": 1500,
    "agent_id_probe_status": 200,
    "agents_endpoint_response": "ok",
    "agents_count_in_probe": 23
  },
  "regulator_mappings": ["SOX-404","OCC-2011-12"],
  "evidence_artifacts": ["licgap-state-AGT226-20260415-093012-a1b2c3d4.json"],
  "schema_version": "1.0"
}

Field-name carryover. agent_id_probe_status is retained as the field key for the HTTP status returned by the /beta/agents probe. The field name is preserved to keep examiner pipelines that already parse this key working across the GA cutover. Post-GA, a 200 indicates the Agent ID surface is reachable; a 403/404 indicates a license-coverage or RBAC gap, not a Frontier-enrollment gap.

8.5 Sample failing record

{
  "control_id": "2.26",
  "namespace": "LICGAP",
  "criterion": "C2.26-1",
  "status": "FAIL",
  "assertion": "Agent ID operability gate must return HTTP 200 from /beta/agents",
  "observed_value": {
    "license_skus": ["Microsoft_Agent_365"],
    "agent_id_probe_status": 404,
    "probe_error": "Resource not found: /beta/agents"
  },
  "remediation_ref": "TRG-LICGAP-01",
  "regulator_mappings": ["SOX-404","OCC-2011-12"],
  "schema_version": "1.0"
}

TRG-LICGAP-01 (§8.10): confirm Microsoft Agent 365 / M365 E7 license is assigned to the calling principal and the tenant has at least one consumed unit; verify the calling principal holds the required RBAC role (Entra Agent ID Admin or AI Administrator); halt deployment of any new Z2/Z3 agents while the gap persists; downgrade any Z2/Z3 agents created during the gap to Z1 and re-evaluate when access is restored. If license + RBAC are confirmed and the surface is still unreachable, raise a Microsoft support case via standard support channels.

8.6 Examiner artifact

Artifact Filename Retention
Feature-state snapshot licgap-state-<runId>.json 6 years
Copilot SKU list licgap-skus-<runId>.json 6 years
Agent ID API probe response (raw) agent-id-api-probe-<runId>.json 6 years

8.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

8.8 Regulator mapping

Test SOX §404 OCC Bulletin 2026-13 (formerly OCC 2011-12)
License gate ✓ control prerequisite ✓ tooling licensed for model use
Agent ID API surface gate ✓ feature in known state ✓ change-managed feature enablement
Blade probe ✓ control operable

§9 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-LICGAP.json
│   ├── 0002-SPONSOR-zone1.json
│   ├── 0003-SPONSOR-zone2.json
│   ├── ... (one file per evidence record)
├── pester/
│   ├── pester-LICGAP.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": ["LICGAP","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-LICGAP.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) 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:

  1. Names the control (2.26 — Entra Agent ID Identity Governance).
  2. States the run window (e.g., "Evidence collected for the 24-hour window ending 2026-04-15 09:30 UTC").
  3. Lists the eight Verification Criteria and the namespace each maps to.
  4. Provides a quick-glance table of criteria_coverage from manifest.json.
  5. Points the examiner to the relevant records/*.json and snapshots/*.json files for any FAIL.
  6. Includes the operator's name and contact email.

§10 Failure Triage Matrix

10.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-LICGAP-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

10.2 TRG-SPONSOR-01 — Z3 sponsor null or inactive

  • Trigger: §2 Pester suite reports a Z3 agent with sponsorObjectId == null or sponsor_active == false.
  • Containment: within 4 hours, suspend the agent identity (set accountEnabled = false via Update-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.

10.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-MgGroupMemberByRef or Remove-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.

10.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-AgentSponsorBulk with 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 via standard support channels.

10.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.

10.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) via Update-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 §8 packet and for the next 1.2 sponsor attestation cycle.

10.7 TRG-EXPIRY-01 — Stale grant (expired but still granting access)

  • Trigger: §6 reports a Z3 assignment past its expiredDateTime whose underlying group/role membership still resolves.
  • Containment: within 1 hour, force-remove via Remove-MgEntitlementManagementAssignmentRequest or, 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 §8 packet for board reporting.

10.8 TRG-SIEM-01 — SIEM retention < 6 years or not immutable

  • Trigger: §7 reports siem_retention_days < 2190 or siem_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.

10.9 TRG-LICGAP-01 — Agent ID licensing or operability gate FAIL

  • Trigger: §8 reports agent_id_probe_status of 403/404, or no Agent 365 / M365 E7 license consumed.
  • Containment: halt deployment of any new Z2/Z3 agents.
  • Remediation: confirm Microsoft Agent 365 or Microsoft 365 E7 license is assigned and consumed; verify the calling principal holds the Entra Agent ID Admin or AI Administrator role; if license + RBAC are confirmed and the surface is still unreachable, raise a Microsoft support case via standard support channels. Downgrade any Z2/Z3 agents created during the gap to Z1 and re-evaluate when access is restored.

10.10 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.


§11 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.json as upstream_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>.jsonagents[] 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 completedWithErrors in 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 §8.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/.


§12 Quarterly Attestation Runbook for the AI Governance Lead

12.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.

12.2 Inputs

The Lead assembles, in order:

  1. The 12 weekly evidence packs produced over the prior quarter (one per week from the standing Pester schedule).
  2. The 90 daily SPONSOR/EXPIRY snapshots (light-weight subsets of the full pack).
  3. The prior quarter's REVIEW campaign decision exports.
  4. The most recent 1.2 sponsor attestation (upstream dependency per §8.1).
  5. The 3.6 orphan registry delta over the quarter (downstream consumer per §8.2).
  6. Any incident tickets with severity Critical or High that traced back to a §8 triage entry.

12.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

12.4 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...=="
    }
  }
}

12.5 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:

  1. Mark criteria_attestation.<criterion>.status = "FAIL_OPEN".
  2. Attach the open ticket reference and projected remediation date.
  3. Sign the packet anyway (a "qualified attestation"); counter-signature still required.
  4. Notify the Board Risk Committee secretariat within 5 business days.
  5. 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.

12.6 Validation runbook

After signing, the Lead runs:

Test-Agt226QuarterlyAttestation -PacketPath "quarterly-attestation-Q1-2026.json"

which verifies:

  • All 8 criteria are present.
  • All evidence_pack_count values are ≥ the expected cadence (12 weekly for most; 1 for REVIEW; 90 for SPONSOR/EXPIRY daily snapshots).
  • All referenced remediation_refs resolve 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.


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

Updated: May 2026 | Version: v1.6.2 | UI Verification Status: Current