Verification & Testing — Control 2.1: Managed Environments
Examiner-defensible evidence package for Control 2.1. This playbook produces, signs, and retains the artifacts required to demonstrate to FINRA, SEC, OCC, FFIEC, NYDFS, and internal audit that every Power Platform environment in scope of US financial-services governance is correctly enrolled as a Microsoft Power Platform Managed Environment (
protectionLevel = Standard), is licensed at the prescribed entitlement floor, has zone-appropriate sharing limits, solution-checker enforcement, IP firewall, IP-bound cookies, Customer Lockbox, Customer-Managed Keys, Tenant Isolation, environment routing, governance-console integration, and segregation of duties — and that the evidentiary chain for each of those configurations is reproducible, hash-chained, and retained on WORM-protected storage for the FINRA Rule 4511 / SEC Rule 17a-4 six-year horizon.Scope: All Power Platform environments classified Zone 2 (Team) or Zone 3 (Enterprise). Zone 1 (Personal Productivity / developer / individual maker) environments are tested only for clean delineation from Zone 2/3 (TC-1) and for routing-rule placement (TC-14); they are not in scope for Managed-Environments enablement. Sovereign clouds (GCC, GCC High, DoD, China 21Vianet) follow the compensating-control patterns in TC-8, TC-15, and TC-18.
Companion controls: 1.4 Advanced Connector Policies, 1.5 DLP and Sensitivity Labels, 1.7 Comprehensive Audit Logging, 1.20 Network Isolation, 2.2 Environment Groups, 2.3 Change Management, 2.8 Access Control & SoD, 2.14 Training & Awareness, 2.15 Environment Routing, 2.25 Agent 365 Admin Center Governance Console, 3.1 Agent Inventory & Metadata, 3.6 Orphaned Agent Detection, 3.9 Microsoft Sentinel Integration.
Last UI verified: April 2026 against Power Platform Admin Center (PPAC) build 2026.04.x, the Microsoft 365 admin center governance pages of the same build, the Microsoft Agent 365 Admin Center release of build 2026.04.x, and the
Microsoft.PowerApps.Administration.PowerShellmodule v2.0.193 /Microsoft.Graphv2.25.0 / Pester 5.5.0.
Non-Substitution — Managed Environments are not a substitute for human supervision
Managed Environments provide the technical guardrails and evidentiary surface that make FSI governance operable. They do not substitute for, and must not be claimed to substitute for:
- The registered-principal supervisory designation required by FINRA Rule 3110. The solution-checker enforcement gate, sharing-limit enforcement, and weekly digest are evidentiary inputs to a Series-24 supervisor's review; they are not themselves the review. See Control 2.12.
- The books-and-records retention obligation under FINRA Rule 4511 and SEC Rule 17a-4(f). The PPAC weekly usage-insights digest is operational telemetry. Records-scope artifacts (audit logs, attestations, change-management approvals) are governed by Control 1.7 and Control 3.1. The retention column in the §3 evidence-capture master table distinguishes operational vs records-scope artifacts; do not commingle them.
- The independent model validation required by OCC Bulletin 2011-12 and Federal Reserve SR 11-7. A Managed Environment toggle does not validate a model. See Control 2.6.
- The Written Supervisory Procedures (WSPs) that document who reviews what, when, and how. Examiners hold the firm to its WSPs; this playbook produces the evidence those WSPs reference.
- The SOX 302 / 404 management certification of internal controls over financial reporting. The §4 quarterly attestation supports — but does not replace — management's certification.
Treat Managed Environments as the enforcement substrate that makes the human-and-process controls above auditable. Where a TC below uses language like "blocks" or "enforces", read it as "produces the technical signal that the firm''s WSP designates as the gating control"; the gate is the WSP, not the toggle.
Sovereign Cloud Availability — feature gaps that change evidence patterns
The following Managed Environments features have material gaps in sovereign clouds at the time of this playbook''s last UI verification. Sovereign tenants must apply the compensating-control substitutions in TC-8, TC-15, and TC-18; the §3 evidence-capture master table flags affected artifacts with a SOV-SUB annotation:
| Feature | Commercial | GCC | GCC High | DoD | China (21Vianet) |
|---|---|---|---|---|---|
| Managed Environments enablement | GA | GA | GA | GA | Available |
| Sharing limits per resource family | GA | GA | GA | GA | Available |
| Solution checker enforcement | GA | GA | GA | GA | Available |
| IP firewall + AuditOnly mode | GA | GA | GA | GA | Verify per Learn — IP firewall |
| IP-based cookie binding | GA | GA | GA | GA | Verify |
| Customer Lockbox | GA | GA | GA | Limited tier coverage | Not available |
| Customer-Managed Keys (CMK) | GA — service coverage varies | Partial — verify per service | Partial | Partial | Partial |
| Weekly usage-insights digest | GA | Not available | Not available | Not available | Not available |
| Tenant Isolation | GA | GA | GA | GA | Available |
| Environment routing | GA | GA | GA | GA | Available |
| Microsoft Agent 365 Admin Center governance console | GA (May 2026) | Not at parity | Not at parity | Not at parity | Not available |
Re-verify sovereign-cloud parity quarterly via the Microsoft 365 Government roadmap and the linked Learn pages, and update the §3 evidence-capture master table with any change in availability before the next quarterly evidence pack is sealed.
Document Conventions
| Convention | Value |
|---|---|
| PowerShell baseline (read-only Graph + Agent 365) | PowerShell 7.4+ Core; #Requires -Version 7.4 |
| PowerShell baseline (Power Platform admin module) | Windows PowerShell 5.1; Microsoft.PowerApps.Administration.PowerShell does not load on PowerShell 7+ for admin scenarios as of this writing — see the sister PowerShell Setup. |
| Test framework | Pester 5.5+. All assertions use Should with -Because clauses for examiner traceability. |
| Output discipline | No Write-Host in evidence emission. All evidence emitted as structured [pscustomobject] instances, then serialized with ConvertTo-Json -Depth 8 to evidence files. |
| Sovereign cloud handling | Pester suites detect cloud and emit SKIPPED records routed to TC-18 rather than FAIL. |
| Evidence retention — records scope | Six (6) years on WORM-protected storage for any artifact the firm''s WSPs designate as a books-and-records artifact. Aligns with FINRA 4511 / SEC 17a-4(f). Distinguished from operational scope in §3. |
| Evidence retention — operational scope | Three (3) years on tamper-evident storage for operational telemetry the firm has documented as out-of-scope of supervisory recordkeeping (e.g., the weekly digest itself, license-consumption snapshots used for capacity planning). |
| Hashing | SHA-256 over canonical JSON; chained leaf hashes plus a Merkle root in attestation.json (see §4.3). |
| Sovereign anchor | Sovereign-aware functions reference ../../_shared/powershell-baseline.md. |
| Run identifier | Every test run is tagged ME-yyyyMMdd-HHmmss-<8charGuid> (e.g., ME-20260415-093012-a1b2c3d4) and embedded in every evidence record and artifact filename. |
| Canonical role names | Per docs/reference/role-catalog.md. The canonical short names used in this playbook are Power Platform Admin, Entra Global Admin, AI Administrator, Purview Compliance Admin, Entra Security Admin, Entra Global Reader, AI Governance Lead, Compliance Officer, Information Security Officer, Internal Audit Lead, Technology Risk Manager, Change Management Lead. |
protectionLevel semantics |
protectionLevel = "Standard" ⇒ Managed Environment ENABLED. protectionLevel = "Basic" (or absent) ⇒ DISABLED / unmanaged. Earlier versions of these playbooks had this inverted; pre-April-2026 evidence likely reports the opposite of reality and must be re-collected. |
This playbook helps meet recordkeeping, supervision, change-management, access-management, and oversight expectations under FINRA Rules 3110 and 4511, FINRA Regulatory Notice 25-07, SEC Rules 17a-3 / 17a-4, SOX §§ 302 / 404, GLBA § 501(b), OCC Bulletin 2011-12, Federal Reserve SR 11-7, NYDFS 23 NYCRR 500.06, and the FFIEC IT Examination Handbook. It is one component of a defensible AI / low-code governance program; it does not replace a registered-principal designation, written supervisory procedures, model risk management practices, or the firm''s own legal review.
§0 Pre-Test Prerequisites & Sovereign Cloud Bootstrap
0.1 Operator role prerequisites
The operator running this playbook must hold one of the following role assignments, scoped to the tenant under test, and activated through Microsoft Entra Privileged Identity Management (PIM) for the duration of the test run. Read-only assertions throughout TC-1 — TC-20 are deliberately separated from any write actions; the Pester suites in this playbook do not perform remediation. Remediation runbooks referenced from failure paths live in the sister PowerShell Setup and Troubleshooting playbooks and require their own change tickets and write scopes.
| Role (canonical) | Required for | PIM activation window |
|---|---|---|
| Power Platform Admin | Tenant-scope reads against PPAC governance configuration (TC-1 through TC-17); Get-AdminPowerAppEnvironment, Get-AdminPowerAppEnvironmentGovernanceConfiguration; emergency execution of remediation runbooks |
4 hours, just-in-time, ticketed |
| Entra Global Admin | Tenant-scope reads where Power Platform Admin is insufficient (TC-3 license-consumption Graph endpoint, TC-11 Lockbox tenant-tier read, TC-13 Tenant Isolation tenant-level read) | 2 hours, JIT, ticketed |
| AI Administrator | Reads against Microsoft Agent 365 Admin Center inventory + governance template assignments (TC-15) | 4 hours, JIT |
| Purview Compliance Admin | Read access to the audit-log substitute data path used in sovereign clouds (TC-8 SOV substitution; TC-18) and to Purview retention labels referenced from the §3 evidence-capture master table | 4 hours |
| Entra Security Admin | Reads against the Microsoft Sentinel workspace used for sovereign substitution (TC-8, TC-18) and against Conditional Access policies that gate IP firewall coverage (TC-9) | 4 hours |
| Entra Global Reader | Witness role for the dual-control evidence-pack signing pattern in §4.3 | 4 hours |
| AI Governance Lead | Counter-signs the quarterly attestation packet in §4; reviews the per-environment owner field in the §3 master table; signs the §4 evidence pack as primary signatory | Standing; quarterly recertification per Control 2.8 |
| Compliance Officer | Counter-signs the quarterly attestation packet in §4; signs Zone 3 sharing-limit and CMK variances; signs the TC-18 sovereign quarterly attestation; produces the examiner-facing evidence pack in §5 | Standing |
| Information Security Officer | Reviews IP firewall AuditOnly-to-Enforce promotion (TC-9), IP-cookie-binding posture (TC-10), Lockbox tier (TC-11), CMK exclusion narrative (TC-12), Tenant Isolation posture (TC-13) | Standing |
| Internal Audit Lead | Receives the §4 evidence pack and integrates it into the firm''s SOX 404 internal-controls-over-financial-reporting (ICFR) workpapers; signs the §4 annual self-assessment (TC-19) | Standing |
| Technology Risk Manager | Receives the §4 evidence pack and integrates it into the firm''s technology-risk reporting per OCC 2011-12 / SR 11-7 | Standing |
| Change Management Lead | Reconciles solution-checker exception register entries (TC-6), routing rule deployments (TC-14), and Lockbox approval responses (TC-11) with change tickets per Control 2.3 | Standing |
Least privilege. No operator should hold Entra Global Admin persistently. Day-to-day verification work in TC-1 through TC-17 is performed under Power Platform Admin with Entra Global Reader as the witness role. Entra Global Admin is reserved for tenant-level reads that the Power Platform admin module does not surface, and is operated under PIM with just-in-time activation and a ticketed justification.
0.2 Module baseline
Pin to specific module versions to keep evidence packs reproducible across machines and over time. Re-validate against newer module versions before promoting them to the standing schedule.
# Read-only Graph operations (TC-3 license consumption, TC-15 Agent 365, TC-18 sovereign substitute)
#Requires -Version 7.4
#Requires -Modules @{ ModuleName='Microsoft.Graph.Authentication'; ModuleVersion='2.25.0' }
#Requires -Modules @{ ModuleName='Microsoft.Graph.Identity.DirectoryManagement'; ModuleVersion='2.25.0' }
#Requires -Modules @{ ModuleName='Microsoft.Graph.Reports'; ModuleVersion='2.25.0' }
#Requires -Modules @{ ModuleName='Microsoft.Graph.Beta.Applications'; ModuleVersion='2.25.0' }
#Requires -Modules @{ ModuleName='Pester'; ModuleVersion='5.5.0' }
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
# Power Platform admin reads (TC-1, TC-2, TC-4 — TC-14, TC-16, TC-17). Windows PowerShell 5.1.
#Requires -PSEdition Desktop
#Requires -Version 5.1
#Requires -Modules @{ ModuleName='Microsoft.PowerApps.Administration.PowerShell'; ModuleVersion='2.0.193' }
#Requires -Modules @{ ModuleName='Microsoft.PowerApps.PowerShell'; ModuleVersion='1.0.34' }
$ErrorActionPreference = 'Stop'
Why two shells? The Power Platform admin module does not load on PowerShell 7+ for admin scenarios as of this writing. Graph-based assertions (TC-3, TC-15, TC-18) run on PowerShell 7.4 with
Microsoft.Graphv2.25; PPAC-surfaced assertions (everything else) run on Windows PowerShell 5.1. The §4 evidence-pack assembler reconciles JSON outputs from both shells. The sister PowerShell Setup §1 documents the cross-shell hand-off pattern.
0.3 PRE gates (must all pass before TC-1 — TC-20 execute)
The bootstrap script Invoke-Me21PreFlight.ps1 (in the sister PowerShell Setup playbook) runs nine 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 TC-18 (sovereign compensating control).
| Gate | ID | Purpose | Failure behavior |
|---|---|---|---|
| Module presence | PRE-01 | Confirms required modules loaded at the pinned versions in §0.2 | HALT |
| Power Platform context | PRE-02 | Confirms Add-PowerAppsAccount -Endpoint <cloud> established and that Get-AdminPowerAppEnvironment returns at least one environment |
HALT |
| Graph context | PRE-03 | Confirms Connect-MgGraph established with required scopes (Directory.Read.All, Reports.Read.All, Application.Read.All, User.Read.All, AuditLog.Read.All, AgentGovernance.Read.All on commercial) |
HALT |
| Tenant identification | PRE-04 | Captures tenantId, displayName, primary verified domain for every evidence record |
HALT |
| Cloud detection | PRE-05 | Reads (Get-MgContext).Environment and the -Endpoint parameter passed to Add-PowerAppsAccount; maps to Commercial / GCC / GCCH / DoD / China; verifies the two agree |
HALT on disagreement |
| Sovereign route | PRE-06 | If cloud ∈ {GCC, GCCH, DoD, China}, sets $script:isSovereign = $true; downstream Skip flags route SOV-affected TCs to compensating substitutes |
Continue with cloud field set; sovereign clouds route to TC-18 |
| License gate | PRE-07 | Confirms tenant holds at least one Power Platform-bearing SKU (Power Apps Premium, Power Automate Premium, Copilot Studio, or Dynamics 365 with Power Platform usage rights). Surfaces the June 2026 enforcement banner — see TC-3. | HALT on absent entitlement; WARN on PAYG-only |
| Clock skew gate | PRE-08 | Compares local UTC to the Date header from a Graph response; aborts if drift exceeds 60 seconds |
HALT — clock skew invalidates timestamp evidence for FINRA 4511 / SEC 17a-4 |
| Evidence root writeable | PRE-09 | Confirms $env:ME21_EVIDENCE_ROOT exists, is writeable, and resolves to a path under WORM-eligible storage (validated by checking the parent storage account''s immutability policy where applicable) |
HALT |
0.4 Sovereign bootstrap pattern
function Test-Me21SovereignTenant {
[CmdletBinding()]
[OutputType([pscustomobject])]
param(
[Parameter(Mandatory)]
[ValidateSet('prod','usgov','usgovhigh','dod','china')]
[string] $PowerPlatformEndpoint
)
$cloud = switch ($PowerPlatformEndpoint) {
'prod' { 'Commercial' }
'usgov' { 'GCC' }
'usgovhigh' { 'GCCH' }
'dod' { 'DoD' }
'china' { 'China' }
}
[pscustomobject]@{
cloud = $cloud
is_sovereign = $cloud -in @('GCC','GCCH','DoD','China')
endpoint = $PowerPlatformEndpoint
detected_at = (Get-Date).ToUniversalTime().ToString('o')
endpoint_ref = '../../_shared/powershell-baseline.md#3-sovereign-cloud-endpoints-gcc-gcc-high-dod'
}
}
When is_sovereign is $true, the affected It blocks in TC-8, TC-15, and (where called out) TC-9, TC-11, TC-12 emit a SKIPPED record with a pointer to the TC-18 substitute, rather than a FAIL:
{
"status": "SKIPPED",
"reason": "Feature not at parity in sovereign cloud at run time",
"compensating_control_ref": "TC-18 sovereign substitute namespace",
"next_check_due": "2026-07-15T00:00:00Z"
}
This produces an examiner-defensible audit trail showing the test was attempted, was correctly skipped on regulatory-sound grounds, and was supplemented by the manual attestation in TC-18 — rather than appearing as an unexplained gap. The next_check_due date is set to the start of the following quarter so the sovereign roadmap re-verification cadence is itself evidenced.
0.5 Run identifier and evidence root
function New-Me21RunId {
$ts = (Get-Date).ToUniversalTime().ToString('yyyyMMdd-HHmmss')
$guid = ([guid]::NewGuid().ToString('N')).Substring(0,8)
"ME-$ts-$guid"
}
$script:RunId = New-Me21RunId
$script:RunTimestamp = (Get-Date).ToUniversalTime().ToString('o')
$script:EvidenceRoot = Join-Path $env:ME21_EVIDENCE_ROOT $script:RunId
New-Item -Path $script:EvidenceRoot -ItemType Directory -Force | Out-Null
Every evidence record produced in TC-1 through TC-20 is written under $script:EvidenceRoot and the runId is embedded in the filename of every artifact assembled into the §4 evidence pack.
0.6 Evidence record schema (canonical)
Every evidence record produced by every TC MUST conform to this schema. The schema is enforced by Test-Me21EvidenceSchema in §4.5; the pack assembler refuses to publish a pack containing any record that fails schema validation.
{
"control_id": "2.1",
"run_id": "ME-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",
"zone": "3",
"test_case": "TC-3",
"subject_id": "00000000-aaaa-bbbb-cccc-111111111111",
"subject_type": "environment",
"status": "PASS",
"assertion": "Every active maker in the Z3 environment holds a Power Platform Premium-tier entitlement",
"observed_value": { "non_compliant_makers": 0, "sample_size": 247 },
"expected_value": { "non_compliant_makers": 0 },
"evidence_artifacts": ["license-consumption-ME-20260415-093012-a1b2c3d4.json"],
"regulator_mappings": ["FINRA-4511","SEC-17a-4","SOX-404","OCC-2011-12","FFIEC-MGMT"],
"remediation_ref": null,
"operator_upn": "me21-runner@contoso.com",
"schema_version": "1.0"
}
0.7 Regulator mapping vocabulary
| Token | Citation |
|---|---|
FINRA-3110 |
FINRA Rule 3110 (Supervision) |
FINRA-4511 |
FINRA Rule 4511 (Books and Records — General Requirements) |
FINRA-25-07 |
FINRA Regulatory Notice 25-07 (AI Tools — RFC, contextual) |
SEC-17a-3 |
SEC Rule 17a-3 (Records to be Made) |
SEC-17a-4 |
SEC Rule 17a-4 (Records Retention) |
SOX-302 |
Sarbanes-Oxley Section 302 (Management Certification) |
SOX-404 |
Sarbanes-Oxley Section 404 (ICFR / Internal Controls) |
GLBA-501b |
Gramm-Leach-Bliley Act §501(b) (Safeguards Rule) |
OCC-2011-12 |
OCC Bulletin 2011-12 (Technology Risk Management / MRM) |
SR-11-7 |
Federal Reserve SR Letter 11-7 (Model Risk Management) |
CFTC-1.31 |
CFTC Regulation 1.31 (Recordkeeping) — contextual for FCM/SD scope |
NYDFS-500-06 |
NYDFS 23 NYCRR 500.06 (Audit Trail) |
FFIEC-IS |
FFIEC IT Examination Handbook — Information Security |
FFIEC-MGMT |
FFIEC IT Examination Handbook — Management |
No FINRA Rule 3110 substitution. Wherever
FINRA-3110appears in a regulator-mapping table in TC-1 through TC-20, it indicates that the control element supports the firm''s Rule 3110 supervisory obligation by providing reviewable evidence of admin oversight. It does not indicate that any technical guardrail substitutes for the firm''s obligation to designate an appropriately registered principal where Rule 3110 requires that designation.
§1 Test Case Format
Each test case below has exactly five subsections, in this order:
- Setup — pre-conditions, reference data, role activation, environment fixture identification.
- Steps — numbered, deterministic actions. Mix of PPAC navigation steps and PowerShell snippets. Where KQL is used (Sentinel-backed substitutes), it is presented in a fenced
kustoblock. - Expected — the assertion that must hold for
status = PASS. PASS / WARN / FAIL bands per zone where applicable. - Evidence Capture — the exact filename pattern, content shape, retention scope (operational vs records-scope), and the §3 master-table row that this TC populates.
- Remediation — the specific runbook reference (file + section) in the sister Portal Walkthrough, PowerShell Setup, or Troubleshooting playbooks. Remediation is never performed by this playbook.
Cross-cutting pattern for every TC: emit a single evidence record (the §0.6 schema) per It block; serialize to <test-case>-<runId>.json; append the SHA-256 leaf hash to the run-level manifest.ndjson; and let §4 chain leaves into the Merkle root.
§2 Test Cases
TC-1 — All Zone-2/3 environments are Managed; Zone-1 personal/dev clearly delineated
Criterion mapped: Verification Criterion 1 (PPAC card shows Managed Environment: Yes) for every Zone-2/3 environment, plus zone-classification hygiene per Control 2.2.
Setup
- PRE-01 through PRE-09 passed.
- Operator activated as Power Platform Admin under PIM with a justification referencing
ME21-TC-01-<runId>. - Reference data: the firm''s zone-classification register (
zone-register.json, maintained by the AI Governance Lead per Control 2.2) is available at$env:ME21_ZONE_REGISTER. This file enumerates every environment ID with its assigned zone (1,2, or3), owner UPN, and creation justification. Zone classification is the firm''s assertion; this TC verifies enablement against that assertion. - The Power Platform tenant has at least one environment of each in-scope zone (Z1 personal/dev, Z2 team, Z3 enterprise).
Steps
- Sign into PPAC at the cloud-appropriate URL (Commercial; GCC
https://gcc.admin.powerplatform.microsoft.us; GCC Highhttps://high.admin.powerplatform.microsoft.us; DoDhttps://admin.appsplatform.us; Chinahttps://admin.powerplatform.partner.microsoftonline.cn). - Navigate Manage → Environments.
- In the environment grid, sort by Managed environment column. Capture a full-page screenshot.
-
Run the inventory snapshot:
Add-PowerAppsAccount -Endpoint $Endpoint $register = Get-Content $env:ME21_ZONE_REGISTER | ConvertFrom-Json $envs = Get-AdminPowerAppEnvironment | Select-Object DisplayName, EnvironmentName, EnvironmentType, Location, @{n='ProtectionLevel'; e={ $_.Properties.protectionLevel }}, @{n='IsManaged'; e={ $_.Properties.protectionLevel -eq 'Standard' }}, @{n='HasDataverse'; e={ [bool]$_.Properties.linkedEnvironmentMetadata }} $joined = $envs | ForEach-Object { $rec = $_ $zone = ($register.environments | Where-Object id -eq $rec.EnvironmentName).zone [pscustomobject]@{ EnvironmentId = $rec.EnvironmentName DisplayName = $rec.DisplayName Zone = $zone EnvironmentType = $rec.EnvironmentType Region = $rec.Location IsManaged = $rec.IsManaged HasDataverse = $rec.HasDataverse ProtectionLevel = $rec.ProtectionLevel ZoneRegistered = [bool]$zone } } $joined | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc01-inventory-$($script:RunId).json") -Encoding utf8 -
Compute the four assertion sets:
$z23Unmanaged = $joined | Where-Object { $_.Zone -in '2','3' -and -not $_.IsManaged } $z1Managed = $joined | Where-Object { $_.Zone -eq '1' -and $_.IsManaged } $unregistered = $joined | Where-Object { -not $_.ZoneRegistered } $z23NoDataverse = $joined | Where-Object { $_.Zone -in '2','3' -and -not $_.HasDataverse } -
Emit one evidence record per assertion set; PASS only if all four sets are empty.
Expected
- Every environment classified Z2 or Z3 in
zone-register.jsonhasprotectionLevel = "Standard"(IsManaged = $true). - Every environment classified Z1 has
protectionLevel = "Basic"(or absent) — Z1 environments must not carry Managed-Environments overhead, both for licensing posture (TC-3) and for the supervisor clarity that examiners expect. - Every environment in PPAC has a corresponding entry in
zone-register.json(no orphan environments). - Every Z2/Z3 environment has Dataverse provisioned.
| Zone | PASS | WARN | FAIL |
|---|---|---|---|
| Z1 | All Z1 envs IsManaged = $false; all are present in register |
1–2 Z1 envs are IsManaged (likely accidental enablement) | ≥3 Z1 envs IsManaged, OR any Z1 env unregistered |
| Z2 | All Z2 envs IsManaged + HasDataverse + registered | Any single env missing one of those three | ≥1 Z2 env unmanaged or missing Dataverse |
| Z3 | All Z3 envs IsManaged + HasDataverse + registered + sharing-limit configured (cross-checked in TC-5) | n/a | ≥1 Z3 env unmanaged or missing Dataverse |
Evidence Capture
- Filename:
tc01-inventory-<runId>.jsonandtc01-evidence-<runId>.ndjson(one record per assertion set). - Storage:
$script:EvidenceRoot/tc01/. - Retention: records scope (6 years, WORM) — the inventory establishes the universe of supervised environments and is therefore a books-and-records artifact under FINRA 4511 / SEC 17a-4(f). Mirror to the firm''s WORM bucket via the §4.4 sealing job.
- §3 master-table row: row 1 (
tc01-inventory).
Remediation
- If any Z2/Z3 environment is unmanaged: see PowerShell Setup — Bootstrap Script — Enable Managed Environments. Open a Control 2.3 change ticket before running.
- If a Z1 environment is unexpectedly Managed: see PowerShell Setup — Disable (rollback). Confirm with the environment owner that the Z1 classification is correct before downgrading.
- If an environment is unregistered: file with the AI Governance Lead via the Control 2.2 environment-registration intake; do not auto-classify.
- If a Z2/Z3 environment lacks Dataverse: Troubleshooting §3 — Dataverse provisioning prerequisites. Provisioning Dataverse in a populated environment is a high-impact change — require CAB approval.
TC-2 — Pipeline target environments are Managed Environments — proactive enablement evidence
Criterion mapped: Pipelines that promote solutions toward Z2/Z3 environments must target Managed environments only. This is a proactive assertion: the firm should be able to show that the pipeline metadata enforces the rule, not merely that the current targets happen to comply.
Setup
- PRE gates passed.
- Operator activated as Power Platform Admin with Pipeline Admin read.
- Reference data: pipeline definitions are stored in the Power Platform pipelines host environment; the host environment ID is at
$env:ME21_PIPELINE_HOST. - The firm''s pipeline policy (per Control 2.3) declares: "No deployment stage may target an unmanaged environment outside Z1."
Steps
-
Enumerate every pipeline target stage and its environment binding:
# Pipelines surface via the Dataverse `deploymentpipeline`, `deploymentstage`, # and `deploymentenvironment` tables in the pipeline host environment. Add-PowerAppsAccount -Endpoint $Endpoint $hostEnv = $env:ME21_PIPELINE_HOST # Use the WebAPI (the admin module does not expose pipeline tables directly). $token = (Get-AdminPowerAppEnvironment -EnvironmentName $hostEnv).Properties.linkedEnvironmentMetadata.instanceApiUrl $stages = Invoke-RestMethod -Uri "$token/api/data/v9.2/deploymentstages?`$select=deploymentstageid,name,_targetenvironmentid_value" ` -Headers @{ Authorization = "Bearer $((Get-PowerAppsAccessToken).accessToken)" } $report = foreach ($s in $stages.value) { $targetEnvId = $s._targetenvironmentid_value $targetEnv = Get-AdminPowerAppEnvironment -EnvironmentName $targetEnvId -ErrorAction SilentlyContinue [pscustomobject]@{ StageId = $s.deploymentstageid StageName = $s.name TargetEnvId = $targetEnvId TargetEnvName = $targetEnv.DisplayName TargetIsManaged = ($targetEnv.Properties.protectionLevel -eq 'Standard') TargetZone = ($register.environments | Where-Object id -eq $targetEnvId).zone } } $report | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc02-pipeline-targets-$($script:RunId).json") -Encoding utf8 -
Emit a
FAILrecord for any target whereTargetZone -in '2','3'ANDTargetIsManaged -eq $false. - Emit a
WARNrecord for any target whereTargetZoneis empty (target not in zone register). - Capture a PPAC screenshot: Pipelines host environment → Pipelines table → the deployment pipeline → Stages view, showing each stage''s target environment.
Expected
- 100% of stages whose target is a Z2/Z3 environment have
TargetIsManaged = $true. - 100% of stages have a target that is present in the zone register.
- Z1 targets (sandbox / scratch) are tolerated unmanaged; this is by design and explicitly documented in the firm''s pipeline policy.
Evidence Capture
- Filename:
tc02-pipeline-targets-<runId>.jsonplustc02-pipeline-stages-<runId>.png. - Retention: records scope (6 years) — pipeline-target configuration is change-control evidence under SEC 17a-3 / SOX 404.
- §3 master-table row: row 2 (
tc02-pipeline-targets).
Remediation
- Re-target the offending stage to a Managed environment via PPAC Pipelines → Stage → Edit. Open a Control 2.3 change ticket. Do not auto-rebind without ticket; pipelines control production releases and a silent rebind is itself a high-severity finding.
- See Troubleshooting §6 — Pipeline target binding for the rebinding sequence and the pre-flight check that prevents promoting a pre-Managed-Environment artifact into a Managed target without re-running solution checker.
TC-3 — Licensing coverage audit complete and current — June 2026 enforcement readiness
Criterion mapped: Verification Criterion 10 (license-entitlement coverage verified for every active maker). Per Microsoft Learn — Managed Environment Licensing, every active maker / user in a Managed Environment must hold one of: Power Apps Premium, Power Automate Premium, a Copilot Studio license that includes Power Platform Premium, or a Dynamics 365 license with Power Platform usage rights. Pay-as-you-go (PAYG) is not a substitute for the floor.
June 2026 licensing-enforcement timeline
Microsoft is rolling out end-user license-compliance notifications for Managed Environments starting June 2026. Non-compliant users will see in-product banners, and PPAC will surface a tenant-level compliance summary. The firm should be in a state where the compliance summary reads zero non-compliant makers per Z2/Z3 environment before June 2026, so that the rollout produces no operational surprise. This TC produces the evidence that the firm has either reached that state or has an explicit remediation plan with a CAB-approved completion date prior to June 2026. Re-run weekly between April 2026 and August 2026.
Setup
- PRE gates passed.
- Operator activated as Power Platform Admin + Entra Global Reader (the Graph reads in step 4 require Global Reader scope).
- Reference data: the firm''s license catalog (
license-catalog.json, per Control 2.4) at$env:ME21_LICENSE_CATALOGenumerates which SKUs satisfy the Managed-Environments floor.
Steps
-
Capture the PPAC license-consumption page screenshot: Resources → License consumption (preview). Save as
tc03-license-consumption-ppac-<runId>.png. -
Pull the per-environment, per-maker license posture via the admin module:
Add-PowerAppsAccount -Endpoint $Endpoint $envs = Get-AdminPowerAppEnvironment | Where-Object { ($register.environments | Where-Object id -eq $_.EnvironmentName).zone -in '2','3' } $allEnvs = $envs | ForEach-Object { $env = $_ $env | ConvertTo-Json -Depth 8 } $allEnvs | Out-File (Join-Path $script:EvidenceRoot "tc03-env-snapshot-$($script:RunId).json") -Encoding utf8 -
Pull the tenant-wide subscribed-SKU list via Graph (this is the canonical entitlement source — the PPAC view is a derived projection):
Connect-MgGraph -Scopes 'Directory.Read.All','Reports.Read.All' -NoWelcome $skus = Get-MgSubscribedSku -All | Select-Object SkuId, SkuPartNumber, ConsumedUnits, @{n='Enabled'; e={ $_.PrepaidUnits.Enabled }}, @{n='ServicePlans'; e={ $_.ServicePlans }} $skus | ConvertTo-Json -Depth 8 | Out-File (Join-Path $script:EvidenceRoot "tc03-tenant-skus-$($script:RunId).json") -Encoding utf8 -
Pull active makers per environment and join against
license-catalog.json:$catalog = Get-Content $env:ME21_LICENSE_CATALOG | ConvertFrom-Json $eligibleSkus = $catalog.skus | Where-Object satisfiesManagedEnv -eq $true | ForEach-Object partNumber $perEnv = foreach ($env in $envs) { $makers = Get-AdminPowerAppCdsDatabaseLanguages -EnvironmentName $env.EnvironmentName -ErrorAction SilentlyContinue # placeholder for maker-list call # In production, use the Dataverse `systemuser` query; see PowerShell Setup §6 for the canonical helper. $makerList = Get-Me21EnvironmentMaker -EnvironmentName $env.EnvironmentName $nonCompliant = foreach ($m in $makerList) { $userSkus = (Get-MgUserLicenseDetail -UserId $m.UserPrincipalName -ErrorAction SilentlyContinue).SkuPartNumber $isOk = $userSkus | Where-Object { $eligibleSkus -contains $_ } if (-not $isOk) { $m } } [pscustomobject]@{ EnvironmentId = $env.EnvironmentName EnvironmentName = $env.DisplayName Zone = ($register.environments | Where-Object id -eq $env.EnvironmentName).zone ActiveMakerCount = $makerList.Count NonCompliantCount = $nonCompliant.Count NonCompliantUPNs = @($nonCompliant.UserPrincipalName) CapturedAt = (Get-Date).ToUniversalTime().ToString('o') } } $perEnv | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc03-license-coverage-$($script:RunId).json") -Encoding utf8 -
Emit
Get-AdminPowerAppEnvironment | ConvertTo-Json -Depth 8as the canonical environment snapshot per the user''s evidence requirement; the §4 packer hashes it as a leaf. -
Compute the PAYG-alone universe (handed off to TC-4):
$paygOnly = $perEnv | Where-Object { # Maker has only PAYG SKUs and no Premium-tier subscription. $_.NonCompliantUPNs.Count -gt 0 -and ($_.NonCompliantUPNs | ForEach-Object { Get-MgUserLicenseDetail -UserId $_ }).SkuPartNumber -contains 'POWERAPPS_PER_APP_PAYG' } $paygOnly | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc03-payg-handoff-tc04-$($script:RunId).json") -Encoding utf8
Expected
NonCompliantCount = 0for every Z2/Z3 environment.- The PPAC license-consumption page lists no banner indicating non-compliant makers.
- The firm''s remediation plan (if any non-compliant makers exist) has a CAB-approved completion date prior to June 2026.
| Zone | PASS | WARN | FAIL |
|---|---|---|---|
| Z2 | NonCompliantCount = 0 | NonCompliantCount = 1–2 with ticketed remediation due before June 2026 | ≥3 non-compliant or no remediation ticket |
| Z3 | NonCompliantCount = 0 | n/a (Z3 has zero tolerance) | NonCompliantCount ≥ 1 |
Evidence Capture
- Files:
tc03-license-consumption-ppac-<runId>.png,tc03-env-snapshot-<runId>.json,tc03-tenant-skus-<runId>.json,tc03-license-coverage-<runId>.json,tc03-payg-handoff-tc04-<runId>.json. - Retention: records scope (6 years) — license entitlement is a foundational input to the supervisory framework and to the SOX 404 ICFR perimeter.
- §3 master-table rows: rows 3a–3e.
Remediation
- Assign the appropriate Premium SKU to the non-compliant maker via Microsoft 365 admin center → Users → Active users → {user} → Licenses and apps. Open a ticket per the firm''s licensing-request workflow and reference the TC-3 evidence record ID.
- If the maker is no longer active, off-board per the firm''s leaver process and re-run TC-3 to confirm removal from the active-maker enumeration.
- See Troubleshooting §4 — License-entitlement remediation for the bulk-assignment script and the rollback path.
TC-4 — PAYG-only makers identified and remediation tickets opened
Criterion mapped: PAYG meter usage is not a substitute for a Premium-tier entitlement floor in a Managed Environment. This TC accepts the PAYG handoff from TC-3 step 6 and produces a ticketed-remediation evidence record.
Setup
- TC-3 has run and produced
tc03-payg-handoff-tc04-<runId>.json. - Operator activated as Power Platform Admin + Entra Global Reader.
- The firm''s ITSM ticketing API is available at
$env:ME21_ITSM_APIwith a service-principal credential that can read ticket status (read-only — TC-4 does not open tickets, it verifies tickets exist).
Steps
-
Load the handoff:
-
For each environment in the handoff, query the ITSM API for an open ticket whose
customField.evidenceRefreferences TC-3 for that maker:$rows = foreach ($e in $handoff) { foreach ($upn in $e.NonCompliantUPNs) { $tickets = Invoke-RestMethod -Uri "$env:ME21_ITSM_API/api/tickets?subject=$upn&status=open" ` -Headers $itsmHeaders [pscustomobject]@{ EnvironmentId = $e.EnvironmentId Maker = $upn TicketCount = $tickets.value.Count TicketIds = @($tickets.value.id) DueDates = @($tickets.value.dueDate) AllDueBeforeJun2026 = (-not ($tickets.value.dueDate | Where-Object { [datetime]$_ -ge [datetime]'2026-06-01' })) } } } $rows | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc04-payg-remediation-tickets-$($script:RunId).json") -Encoding utf8 -
Emit one record per maker; PASS only if every PAYG-only maker has at least one open ticket with
dueDate < 2026-06-01.
Expected
- Every PAYG-only maker has an open ITSM ticket with a CAB-approved due date prior to June 2026.
- No maker is in PAYG-only status without a ticket.
Evidence Capture
- Filename:
tc04-payg-remediation-tickets-<runId>.json. - Retention: records scope (6 years) — supports SOX 404 ICFR remediation evidence.
- §3 master-table row: row 4.
Remediation
- Where a PAYG-only maker has no ticket: open one via the firm''s standard intake. The ticket''s justification field must reference this TC-4 evidence record ID.
- Where the ticket''s due date is post-June 2026: escalate to the AI Governance Lead and Compliance Officer for explicit risk acceptance, signed and stored in the §4 evidence pack with regulator-mapping
OCC-2011-12,FFIEC-MGMT. - See Troubleshooting §4 — License-entitlement remediation.
TC-5 — Sharing limits per resource family enforced; standalone cloud flows have DLP coverage
Criterion mapped: Verification Criterion 4 (sharing-limit enforcement). Per-environment sharing limits exist for canvas apps, Copilot Studio agents, and Power Pages; standalone cloud flows are governed via DLP per Control 1.5 — they are not governed by a per-environment sharing-limit slider.
Setup
- PRE gates passed.
- Operator activated as Power Platform Admin.
-
Reference data: zone-policy table (
zone-policy.json) declares the firm''s per-zone sharing-limit ceilings:Zone Canvas app share limit (#users) Copilot Studio agent share limit Power Pages share limit "Share with security group" allowed? Z2 ≤ 50 ≤ 25 ≤ 50 Yes (group ≤ 100 members) Z3 ≤ 25 ≤ 10 ≤ 25 No (named users only)
Steps
-
Pull governance configuration per Z2/Z3 environment:
$rows = foreach ($env in $envs) { $g = Get-AdminPowerAppEnvironmentGovernanceConfiguration -EnvironmentName $env.EnvironmentName $zone = ($register.environments | Where-Object id -eq $env.EnvironmentName).zone $policy = $zonePolicy.zones."$zone" [pscustomobject]@{ EnvironmentId = $env.EnvironmentName Zone = $zone CanvasShareLimit = $g.canvasAppSharing.shareWithMaxUsers CanvasShareGroupAllowed = $g.canvasAppSharing.shareWithSecurityGroupAllowed CopilotShareLimit = $g.copilotStudio.shareWithMaxUsers PagesShareLimit = $g.powerPages.shareWithMaxUsers CompliantCanvas = ($g.canvasAppSharing.shareWithMaxUsers -le $policy.canvasShareLimit) CompliantCopilot = ($g.copilotStudio.shareWithMaxUsers -le $policy.copilotShareLimit) CompliantPages = ($g.powerPages.shareWithMaxUsers -le $policy.pagesShareLimit) CompliantGroup = (-not $policy.shareWithGroup -implies (-not $g.canvasAppSharing.shareWithSecurityGroupAllowed)) } } $rows | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc05-sharing-limits-$($script:RunId).json") -Encoding utf8 -
Confirm DLP coverage for standalone cloud flows by referencing the Control 1.5 evidence pack''s most recent run:
$dlpRef = Get-Content $env:ME21_CTRL15_LATEST | ConvertFrom-Json $dlpRef | Where-Object { $_.test_case -eq '15-DLP-COVERAGE' -and $_.status -ne 'PASS' } | ForEach-Object { [pscustomobject]@{ EnvironmentId = $_.subject_id Status = 'WARN' Reason = 'TC-5 cannot pass because Control 1.5 most-recent run reports DLP gap for standalone cloud flows in this environment' XRef = '../../../controls/pillar-1-security/1.5-data-loss-prevention-dlp-and-sensitivity-labels.md' } }
Expected
- All Z2/Z3 environments have
Compliant*flags$true. - The most recent Control 1.5 evidence pack reports
PASSfor DLP coverage of every Z2/Z3 environment.
Evidence Capture
- Filename:
tc05-sharing-limits-<runId>.json. - Retention: records scope (6 years) for Z3, operational (3 years) for Z2.
- §3 master-table row: row 5.
Remediation
- Tighten the offending sharing-limit slider via PPAC Environments → {env} → Settings → Privacy + security → Sharing limits. Open a Control 2.3 change ticket.
- For DLP gaps, follow Control 1.5''s remediation runbook; do not attempt to remediate at the Managed-Environments layer.
TC-6 — Solution checker on Block for Z3, Warn or Block for Z2; exception register reconciled
Criterion mapped: Verification Criterion 5 (solution-checker enforcement). Z3 environments enforce Block on import for high-severity findings; Z2 environments may operate at Warn or Block per the firm''s WSP. All exceptions (e.g., temporary downgrades from Block to Warn for an emergency import) live in an exception register reconciled to change tickets.
Setup
- PRE gates passed; Power Platform Admin activated.
- Reference data: exception register (
solution-checker-exceptions.csv) at$env:ME21_SC_EXCEPTIONS.
Steps
-
Read enforcement level per environment:
$rows = foreach ($env in $envs) { $g = Get-AdminPowerAppEnvironmentGovernanceConfiguration -EnvironmentName $env.EnvironmentName $zone = ($register.environments | Where-Object id -eq $env.EnvironmentName).zone [pscustomobject]@{ EnvironmentId = $env.EnvironmentName Zone = $zone SolutionChecker = $g.solutionChecker.enforcementLevel # None | Warn | Block CompliantPolicy = switch ($zone) { '3' { $g.solutionChecker.enforcementLevel -eq 'Block' } '2' { $g.solutionChecker.enforcementLevel -in @('Warn','Block') } default { $true } } } } $rows | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc06-solution-checker-$($script:RunId).json") -Encoding utf8 -
Reconcile the exception register: every row in
solution-checker-exceptions.csvmust reference a Control 2.3 change ticket whosestatus = approvedand whoseeffectiveUntil <= today + 30 days(exceptions are time-bounded).
Expected
- All Z3 environments at
Block; all Z2 environments atWarnorBlock. - Exception register has zero stale entries.
Evidence Capture
- Filename:
tc06-solution-checker-<runId>.json+tc06-exception-register-snapshot-<runId>.csv. - Retention: records scope (6 years) — change-control evidence.
- §3 master-table row: row 6.
Remediation
- Re-enable Block via PPAC Environments → {env} → Settings → Product → Features → Solution checker enforcement.
- Expire stale exceptions via the Control 2.3 change ticket workflow.
- See Portal Walkthrough — Solution checker enforcement.
TC-7 — Maker welcome content present, free of sensitive data, and CMK-exclusion documented
Criterion mapped: Verification Criterion 6 (maker welcome content links to WSPs and training). Every Managed Environment surfaces a maker-welcome banner. Per Control 2.14 the banner must link to the firm''s WSPs and training catalog; per the CMK service coverage matrix the welcome content is excluded from CMK encryption.
Maker welcome content is excluded from CMK encryption
Welcome content (banner text, link captions, link URLs) is stored in a configuration store that is not within the CMK encryption boundary. Display names, environment descriptions, connection metadata, solution-checker results, and Microsoft Agent 365 audit metadata are also excluded. The firm''s CMK exclusion narrative (TC-12) must enumerate every excluded surface; this TC verifies the welcome-content surface specifically. The corollary is that the welcome content must not contain customer data, NPI, MNPI, PII, or anything else that the firm''s data-classification policy would not permit on a non-CMK-encrypted surface.
Setup
- PRE gates passed; Power Platform Admin activated.
- Reference data: training-catalog URL (
$env:ME21_TRAINING_CATALOG), WSP URL ($env:ME21_WSP_URL), and approved welcome-content template (welcome-content-template.json).
Steps
-
Read welcome content per environment:
$rows = foreach ($env in $envs) { $g = Get-AdminPowerAppEnvironmentGovernanceConfiguration -EnvironmentName $env.EnvironmentName $w = $g.makerWelcomeContent [pscustomobject]@{ EnvironmentId = $env.EnvironmentName HasWelcome = [bool]$w.message LinksToWsp = $w.message -match [regex]::Escape($env:ME21_WSP_URL) LinksToTraining = $w.message -match [regex]::Escape($env:ME21_TRAINING_CATALOG) MessageLen = ($w.message | Measure-Object -Character).Characters MessageSample = if ($w.message) { $w.message.Substring(0,[Math]::Min(200,$w.message.Length)) } else { $null } } } $rows | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc07-welcome-content-$($script:RunId).json") -Encoding utf8 -
DLP-style scan welcome content for sensitive-data patterns (SSN, account-number patterns, MNPI watchwords). Use the firm''s sensitive-data scanner (Purview Information Protection classifier) — do not use a homegrown regex without compliance sign-off:
-
Verify the CMK exclusion narrative (
cmk-exclusion-narrative.md) listsmaker_welcome_contentand is signed by the Compliance Officer within the last 12 months. (Cross-check with TC-12.)
Expected
- 100% of Z2/Z3 environments have welcome content; content links to both WSPs and training.
- 0 sensitive-data hits in the DLP scan.
- CMK exclusion narrative present, signed, and within 12 months.
Evidence Capture
- Filenames:
tc07-welcome-content-<runId>.json,tc07-welcome-dlp-scan-<runId>.json, plus a copy ofcmk-exclusion-narrative.md. - Retention: records scope (6 years).
- §3 master-table row: row 7.
Remediation
- Re-set the welcome content via PPAC Environments → {env} → Settings → Product → Features → Welcome content.
- If sensitive-data hits found: redact and re-publish; open a Control 2.14 incident ticket and notify the Information Security Officer per the firm''s data-leakage protocol.
- See Portal Walkthrough — Maker welcome content.
TC-8 — Weekly usage-insights digest current (commercial); sovereign substitute via Graph + Purview + Sentinel
Criterion mapped: Verification Criterion 7 (usage telemetry available to supervisors). On commercial cloud, the PPAC weekly usage-insights digest is the canonical source. Sovereign clouds do not offer the weekly digest at this time; the firm must operate a compensating control assembled from Microsoft Graph reports, Purview unified audit logs (per Control 1.7 and Control 3.1), and Microsoft Sentinel queries (per Control 3.9).
Setup — commercial path
- PRE gates passed; cloud =
Commercial; Power Platform Admin activated. - Reference data: digest delivery list (
$env:ME21_DIGEST_RECIPIENTS) — which Power Platform Admins are configured to receive the weekly digest.
Setup — sovereign path
- PRE gates passed; cloud ∈ {GCC, GCCH, DoD, China}.
- Operator activated as Purview Compliance Admin + Entra Security Admin.
- The firm''s Sentinel workspace ID is at
$env:ME21_SENTINEL_WS.
Steps — commercial
- PPAC Environments → Analytics → Usage insights: confirm a digest dated within the last 7 days and capture screenshot.
- Confirm digest recipients via Settings → Notifications → Weekly digest matches the reference list.
Steps — sovereign
-
Pull the equivalent activity surface via the Graph audit-log endpoint:
Connect-MgGraph -Scopes 'AuditLog.Read.All','Directory.Read.All' -NoWelcome $since = (Get-Date).AddDays(-7).ToUniversalTime().ToString('o') $events = Get-MgAuditLogDirectoryAudit -Filter "activityDateTime ge $since and category eq 'PowerPlatform'" -All $events | ConvertTo-Json -Depth 8 | Out-File (Join-Path $script:EvidenceRoot "tc08-graph-audit-$($script:RunId).json") -Encoding utf8 -
Run the Sentinel KQL substitute query against the workspace where Purview audit is forwarded:
// TC-8 sovereign substitute — weekly Power Platform admin activity summary AuditLogs | where TimeGenerated >= ago(7d) | where LoggedByService in ("Power Apps","Power Automate","Microsoft Power Platform","Copilot Studio","Microsoft Agent 365") | extend EnvironmentId = tostring(parse_json(AdditionalDetails)[0].value) | summarize EventCount = count(), ActiveMakers = dcount(InitiatedBy.user.userPrincipalName), FirstEvent = min(TimeGenerated), LastEvent = max(TimeGenerated) by EnvironmentId, OperationName, ResultType | order by EventCount desc -
Emit a
PASSonly if both data paths return non-empty results and the Compliance Officer has signed the weekly compensating-control attestation (tc08-compensating-attestation-<runId>.json) within the last 7 days.
Expected
- Commercial: PPAC digest exists within the last 7 days; recipient list matches.
- Sovereign: Graph + Sentinel return data; Compliance Officer attestation signed within 7 days.
Evidence Capture
- Commercial files:
tc08-digest-screenshot-<runId>.png,tc08-recipients-<runId>.json. - Sovereign files:
tc08-graph-audit-<runId>.json,tc08-sentinel-query-<runId>.kql,tc08-sentinel-results-<runId>.json,tc08-compensating-attestation-<runId>.json. - Retention: records scope (6 years) — supervisory-evidence input.
- §3 master-table rows: row 8 (commercial), row 8-SOV (sovereign).
Remediation
- Commercial digest gap: re-enable in Settings → Notifications; verify mailbox routing for digest recipients.
- Sovereign substitute gap: rerun Control 3.9 §4 — Sentinel ingestion verification and Control 1.7 §5 — Purview ingestion verification.
TC-9 — IP firewall configured; AuditOnly ≤ 4 weeks then Enforce; reverse-proxy IPs included
Criterion mapped: Verification Criterion 8 (IP firewall present). Per the Microsoft Learn IP firewall guidance, AuditOnly mode is intended as a time-bounded discovery phase — typically ≤ 4 weeks — before promotion to Enforce. The allow-list must include any reverse-proxy egress IPs the firm uses (CDN, WAF, ZTNA exit nodes).
Setup
- PRE gates passed; Power Platform Admin + Information Security Officer (consulted, not operator).
- Reference data: corporate egress IP catalog (
egress-ips.json) maintained by the network team.
Steps
-
Read IP firewall posture per environment:
$rows = foreach ($env in $envs) { $g = Get-AdminPowerAppEnvironmentGovernanceConfiguration -EnvironmentName $env.EnvironmentName $f = $g.ipFirewall [pscustomobject]@{ EnvironmentId = $env.EnvironmentName Zone = ($register.environments | Where-Object id -eq $env.EnvironmentName).zone FirewallEnabled = [bool]$f.enabled Mode = $f.mode # AuditOnly | Enforce AuditOnlySince = $f.auditOnlyEffectiveDate AuditOnlyAgeDays = if ($f.auditOnlyEffectiveDate) { ((Get-Date) - [datetime]$f.auditOnlyEffectiveDate).Days } else { $null } AllowList = $f.allowedIpRangeList } } $rows | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc09-ip-firewall-$($script:RunId).json") -Encoding utf8 -
Verify reverse-proxy egress IPs are present in every Z3 environment''s allow-list:
$egress = Get-Content $env:ME21_EGRESS_IPS | ConvertFrom-Json foreach ($r in $rows | Where-Object Zone -eq '3') { $missing = $egress.proxyEgressRanges | Where-Object { $r.AllowList -notcontains $_ } if ($missing) { [pscustomobject]@{ EnvironmentId = $r.EnvironmentId Status = 'FAIL' MissingRanges = $missing } | ConvertTo-Json | Add-Content (Join-Path $script:EvidenceRoot "tc09-egress-gap-$($script:RunId).ndjson") } } -
Where
Mode = AuditOnlyandAuditOnlyAgeDays > 28, emit aWARN(≤ 56 days) orFAIL(> 56 days) record citing the 4-week guidance.
Expected
- All Z3 environments:
FirewallEnabled = $true,Mode = Enforce, all reverse-proxy egress ranges present. - All Z2 environments:
FirewallEnabled = $true; AuditOnly tolerated for ≤ 4 weeks.
Evidence Capture
- Files:
tc09-ip-firewall-<runId>.json,tc09-egress-gap-<runId>.ndjson(if any). - Retention: records scope (6 years) for Z3, operational (3 years) for Z2.
- §3 master-table row: row 9.
Remediation
- Promote AuditOnly → Enforce via PPAC Environments → {env} → Settings → Privacy + security → IP firewall. Open a Control 2.3 change ticket.
- Add missing reverse-proxy ranges to the allow-list before promotion.
- See Portal Walkthrough — IP firewall.
TC-10 — IP-based cookie binding enabled
Criterion mapped: Verification Criterion 9. IP-bound session cookies prevent token replay from a different IP than the one that established the session.
Setup
- PRE gates passed; Power Platform Admin activated.
Steps
-
Read posture per environment:
$rows = foreach ($env in $envs) { $g = Get-AdminPowerAppEnvironmentGovernanceConfiguration -EnvironmentName $env.EnvironmentName [pscustomobject]@{ EnvironmentId = $env.EnvironmentName Zone = ($register.environments | Where-Object id -eq $env.EnvironmentName).zone IpCookieBindingOn = [bool]$g.ipCookieBinding.enabled } } $rows | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc10-ip-cookie-binding-$($script:RunId).json") -Encoding utf8 -
Capture PPAC screenshot Environments → {env} → Settings → Privacy + security → IP cookie binding.
Expected
- All Z2/Z3:
IpCookieBindingOn = $true.
Evidence Capture
- Filename:
tc10-ip-cookie-binding-<runId>.json+ screenshot. - Retention: records scope (6 years).
- §3 master-table row: row 10.
Remediation
- Enable via PPAC; open Control 2.3 ticket. Re-test mobile + VPN-roaming workflows because IP-cookie binding causes session re-prompts when the user''s public IP changes.
- See Troubleshooting §7 — IP-cookie binding session loss.
TC-11 — Customer Lockbox enabled; tier verified; 8-hour approval runbook tested
Criterion mapped: Verification Criterion 11. Microsoft Customer Lockbox provides documented, time-bound, tenant-administrator approval over Microsoft engineer access. Lockbox tier coverage varies by sovereign cloud (see top admonition).
Setup
- PRE gates passed.
- Operator: Power Platform Admin (read configuration); Entra Global Admin (read tenant-tier Lockbox enrollment via M365 admin center under PIM); Compliance Officer (counter-signs the approval-runbook walkthrough).
- Reference data: Lockbox approver list (
lockbox-approvers.json) — Power Platform Admins and Entra Global Admins designated to receive Lockbox approval requests, with on-call rotation. - Sovereign-aware: Lockbox is not available in China 21Vianet at the time of this writing; route to TC-18.
Steps
- Read tenant-level Lockbox enrollment via Microsoft 365 admin center → Settings → Org settings → Security & privacy → Customer Lockbox. Capture screenshot.
-
Read per-environment Lockbox posture:
$rows = foreach ($env in $envs) { $g = Get-AdminPowerAppEnvironmentGovernanceConfiguration -EnvironmentName $env.EnvironmentName [pscustomobject]@{ EnvironmentId = $env.EnvironmentName Zone = ($register.environments | Where-Object id -eq $env.EnvironmentName).zone LockboxEnabled = [bool]$g.customerLockbox.enabled } } $rows | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc11-lockbox-$($script:RunId).json") -Encoding utf8 -
Verify the approval-runbook tabletop has been exercised within the last quarter: locate the most recent
lockbox-tabletop-<date>.mdin the firm''s evidence repository; confirm the on-call approver responded within the 8-hour SLA in the simulated request. -
Confirm the SIEM forwards
LockboxApproval*events from the Microsoft 365 audit log into Sentinel (per Control 3.9):
Expected
- Tenant-level Lockbox enrolled at the appropriate tier for the firm''s subscription.
- All Z3 environments:
LockboxEnabled = $true. - Tabletop exercised within last 90 days; approver responded within 8-hour SLA.
Evidence Capture
- Files:
tc11-lockbox-<runId>.json,tc11-lockbox-tier-screenshot-<runId>.png,tc11-tabletop-pointer-<runId>.json(a record pointing to the tabletop evidence file with its SHA-256 hash). - Retention: records scope (6 years).
- §3 master-table row: row 11.
Remediation
- Enable Lockbox per-environment via PPAC Environments → {env} → Settings → Privacy + security → Customer Lockbox. Tenant-level enrollment requires a Microsoft 365 admin center change.
- If tabletop is stale, schedule a fresh exercise via the firm''s incident-response calendar.
- See Portal Walkthrough — Customer Lockbox.
TC-12 — CMK Enterprise Policy on Z3; exclusion narrative reviewed and signed
Criterion mapped: Verification Criterion 12. CMK protects Dataverse-resident data; per the CMK service coverage matrix some surfaces are out of scope. The firm''s CMK exclusion narrative must enumerate every excluded surface, be signed by the Compliance Officer + Internal Audit Lead, and be re-attested annually.
Setup
- PRE gates passed.
- Operator: Power Platform Admin (read CMK Enterprise Policy assignment); Entra Global Reader (read Key Vault metadata); Compliance Officer + Internal Audit Lead (counter-sign exclusion narrative).
Steps
-
Pull CMK Enterprise Policy assignments per Z3 environment:
$rows = foreach ($env in $envs | Where-Object { ($register.environments | Where-Object id -eq $_.EnvironmentName).zone -eq '3' }) { $g = Get-AdminPowerAppEnvironmentGovernanceConfiguration -EnvironmentName $env.EnvironmentName $cmk = $g.encryption.customerManagedKey [pscustomobject]@{ EnvironmentId = $env.EnvironmentName CmkPolicyAssigned = [bool]$cmk.enterprisePolicyId EnterprisePolicyId = $cmk.enterprisePolicyId KeyVaultUri = $cmk.keyVaultUri KeyName = $cmk.keyName KeyVersion = $cmk.keyVersion LastRotation = $cmk.lastRotationDate RotationDueWithin90 = if ($cmk.lastRotationDate) { ((Get-Date) - [datetime]$cmk.lastRotationDate).Days -le 365 } else { $false } } } $rows | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc12-cmk-$($script:RunId).json") -Encoding utf8 -
Locate
cmk-exclusion-narrative.md. Verify it enumerates at minimum these excluded surfaces (per current Microsoft documentation): - Maker welcome content (TC-7).
- Solution-checker scan results.
- Environment / app / agent display names and descriptions.
- Connection metadata (connector display names, references, but not credentials, which are stored separately and protected by the connection store).
- Microsoft Agent 365 audit metadata.
- Telemetry forwarded to Microsoft for service operations.
- Verify the narrative is signed by the Compliance Officer and the Internal Audit Lead within the last 12 months. Capture signature timestamps.
Expected
- All Z3 environments have a CMK Enterprise Policy assigned and a key version current within 365 days.
- Exclusion narrative present, current, dual-signed.
Evidence Capture
- Files:
tc12-cmk-<runId>.json, signed copy ofcmk-exclusion-narrative.md,tc12-narrative-signature-<runId>.json. - Retention: records scope (6 years); the narrative itself is records-scope under SOX 404.
- §3 master-table row: row 12.
Remediation
- Assign CMK via PPAC Environments → {env} → Settings → Encryption → Customer-Managed Key. CMK assignment requires Key Vault prerequisites; see Portal Walkthrough — CMK assignment.
- Re-sign exclusion narrative annually; route via the firm''s e-signature workflow.
TC-13 — Tenant Isolation configured both directions; Entra ID-auth scope caveat noted; Azure DevOps DLP exception documented
Criterion mapped: Verification Criterion 3 (cross-tenant isolation). Tenant Isolation is set at PPAC → Security → Identity and access at tenant level — not inside per-environment Privacy + security. Tenant Isolation governs Power Platform connectors that authenticate via Entra ID; connectors that use other auth (e.g., Azure DevOps PAT) are out of scope and must be governed by DLP.
Setup
- PRE gates passed.
- Operator: Power Platform Admin + Entra Global Reader.
- Reference data: tenant allow-list (
tenant-isolation-allowlist.json) — partner / counterparty / regulator tenants for which inbound or outbound flow is explicitly permitted.
Steps
- PPAC Security → Identity and access → Tenant isolation. Capture screenshot.
-
Read posture via PowerShell (admin module exposes via REST helper):
-
Verify both inbound and outbound default =
Block; allow-list matches reference. - Verify the Azure DevOps connector exception is documented in the firm''s DLP variance register (Control 1.4 / 1.5) — Azure DevOps PAT-based auth is not governed by Tenant Isolation.
Expected
- Default inbound =
Block; default outbound =Block. - Allow-list = reference list; no drift.
- Azure DevOps DLP variance entry present and signed.
Evidence Capture
- Files:
tc13-tenant-isolation-<runId>.json+ screenshot + variance pointer. - Retention: records scope (6 years).
- §3 master-table row: row 13.
Remediation
- Reset defaults via PPAC; open Control 2.3 ticket.
- Add or remove allow-list entries per the firm''s vendor-management workflow.
TC-14 — Environment routing rules configured; rules govern type/zone, not region
Criterion mapped: Verification Criterion 2 (auto-provisioning of personal envs at Managed posture). Environment routing rules redirect new personal-environment provisioning to a designated Managed environment. Region is still selected at provisioning time per the user''s home-region assignment; routing does not override region.
Setup
- PRE gates passed; Power Platform Admin activated.
- Reference data: routing-rule policy (
routing-rules.json) — the firm''s expected rule set (e.g., "Route new personal environments for users in the Investment Bank line-of-business to the IB Z2 environment").
Steps
- PPAC Settings → Environments → Environment routing. Capture screenshot of rules table.
-
Read rules via PowerShell:
-
Diff against the reference policy; emit
FAILfor any discrepancy.
Expected
- Live rules match
routing-rules.jsonexactly. - No rule attempts to override region.
Evidence Capture
- File:
tc14-routing-rules-<runId>.json+ screenshot. - Retention: records scope (6 years) — change-control evidence under Control 2.15.
- §3 master-table row: row 14.
Remediation
- Re-create or amend rules via PPAC; open Control 2.15 change ticket.
- See Control 2.15 for the routing-rule lifecycle.
TC-15 — Microsoft Agent 365 / M365 admin center governance console — Copilot Studio shared agents in catalog
Criterion mapped: Cross-reference to Control 2.25. Every Copilot Studio agent shared from a Z2/Z3 environment must appear in the Microsoft Agent 365 catalog with correct owner, environment, and policy assignment. Sovereign clouds: not at parity — route to TC-18.
Setup
- PRE gates passed; cloud =
Commercial. - Operator: AI Administrator (read Agent 365 catalog) + Entra Global Reader (witness).
- Sovereign route: emit
SKIPPEDand route to TC-18.
Steps
-
Read Agent 365 inventory via Graph beta:
-
Cross-join with Copilot Studio agent enumeration from each Z2/Z3 environment (use the helper from Control 2.25 verification):
-
Emit
FAILfor any non-empty$missing.
Expected
- Every shared Copilot Studio agent in Z2/Z3 environments appears in Agent 365 catalog with owner/environment/policy assignment populated.
- For sovereign clouds:
SKIPPEDrecord with TC-18 substitute pointer.
Evidence Capture
- Files:
tc15-agent365-inventory-<runId>.json,tc15-catalog-gap-<runId>.json. - Retention: records scope (6 years) — agent inventory is books-and-records under FINRA 4511 / SEC 17a-4(f).
- §3 master-table row: row 15.
Remediation
- Re-trigger Agent 365 catalog sync via the Control 2.25 §3 — Catalog reconciliation runbook.
TC-16 — Maker / Approver / Admin SoD enforced via Entra security groups
Criterion mapped: Cross-reference to Control 2.8. The same Entra principal must not simultaneously hold maker, approver, and admin rights for the same scope.
Setup
- PRE gates passed.
- Operator: Entra Global Reader + Power Platform Admin.
- Reference data: SoD matrix (
sod-matrix.json) — for each environment, which security groups carry maker / approver / admin roles.
Steps
-
For each Z2/Z3 environment, enumerate group memberships and intersect:
foreach ($e in $envs | Where-Object { ($register.environments | Where-Object id -eq $_.EnvironmentName).zone -in '2','3' }) { $sod = $sodMatrix.environments | Where-Object id -eq $e.EnvironmentName $makers = (Get-MgGroupMember -GroupId $sod.makerGroupId -All).AdditionalProperties.userPrincipalName $approvers = (Get-MgGroupMember -GroupId $sod.approverGroupId -All).AdditionalProperties.userPrincipalName $admins = (Get-MgGroupMember -GroupId $sod.adminGroupId -All).AdditionalProperties.userPrincipalName $violations = @() $violations += $makers | Where-Object { $_ -in $approvers } $violations += $makers | Where-Object { $_ -in $admins } $violations += $approvers | Where-Object { $_ -in $admins } [pscustomobject]@{ EnvironmentId = $e.EnvironmentName MakerCount = $makers.Count ApproverCount = $approvers.Count AdminCount = $admins.Count Violations = $violations | Select-Object -Unique } | ConvertTo-Json -Depth 6 | Add-Content (Join-Path $script:EvidenceRoot "tc16-sod-$($script:RunId).ndjson") }
Expected
- Zero violations across all Z2/Z3 environments.
Evidence Capture
- File:
tc16-sod-<runId>.ndjson. - Retention: records scope (6 years) — SoD evidence is SOX 404 ICFR perimeter.
- §3 master-table row: row 16.
Remediation
- Remove the offending principal from the conflicting role; route via the firm''s access-recertification workflow per Control 2.8.
TC-17 — Inactive resource view feeds orphan detection (Control 3.6)
Criterion mapped: Cross-reference to Control 3.6. The Managed Environments Inactive resources view surfaces apps, flows, and agents not used in the configured window. This TC verifies the view is configured and that its output is consumed by the orphan-detection pipeline.
Setup
- PRE gates passed; Power Platform Admin activated.
Steps
- PPAC Environments → {env} → Resources → Inactive. Capture screenshot of configured threshold (e.g., 90 days).
-
Pull inactive-resource snapshot:
$rows = foreach ($e in $envs) { $inactive = Get-AdminPowerAppEnvironmentInactiveResources -EnvironmentName $e.EnvironmentName [pscustomobject]@{ EnvironmentId = $e.EnvironmentName ThresholdDays = $inactive.thresholdDays InactiveCount = $inactive.resources.Count ResourceTypes = ($inactive.resources | Group-Object resourceType | Select-Object Name, Count) } } $rows | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc17-inactive-$($script:RunId).json") -Encoding utf8 -
Cross-check that the most recent Control 3.6 evidence pack consumed this snapshot within the last 7 days.
Expected
- All Z2/Z3:
ThresholdDaysmatches firm policy (typically 90). - Snapshot consumed by Control 3.6 pipeline within 7 days.
Evidence Capture
- File:
tc17-inactive-<runId>.json+ screenshot. - Retention: operational (3 years) — telemetry feed.
- §3 master-table row: row 17.
Remediation
- Adjust threshold via PPAC if drifted from policy; open Control 3.6 ticket if pipeline consumption gap.
TC-18 — Sovereign cloud compensating-control exercise; Agent 365 parity gap documented
Criterion mapped: Sovereign substitute for TC-8, TC-15, and any other SOV-skipped TCs. This TC produces a single composite attestation that the firm''s sovereign tenant is governed by the documented compensating controls and that the Compliance Officer + AI Governance Lead have reviewed within the quarter.
Setup
- PRE gates passed; cloud ∈ {GCC, GCCH, DoD, China}.
- Operator: Purview Compliance Admin + Entra Security Admin; counter-signed by Compliance Officer + AI Governance Lead.
Steps
-
Enumerate every TC in this run that emitted a
SKIPPEDdue to sovereign: -
For each skipped TC, confirm the substitute artifact exists in this run (e.g.,
tc08-sentinel-results-<runId>.jsonfor TC-8 sovereign). - Document the Microsoft Agent 365 parity gap: Agent 365 governance console is not at parity in sovereign clouds. The compensating evidence is:
- Graph beta agent enumeration (where available in the sovereign cloud — limited at this writing).
- Purview unified-audit-log forwarded events for
Microsoft Power Platform/Copilot Studio. - Sentinel KQL summary per TC-8 sovereign path.
-
Compose the quarterly sovereign attestation:
$attest = [pscustomobject]@{ control_id = '2.1' run_id = $script:RunId cloud = $cloud skipped_test_cases = $skipped.test_case | Select-Object -Unique substitute_artifacts_present = $true agent365_parity_gap = 'Documented; substituted via Graph + Purview + Sentinel' signed_by = @('Compliance Officer','AI Governance Lead') signed_at = (Get-Date).ToUniversalTime().ToString('o') next_review_due = (Get-Date).AddDays(90).ToUniversalTime().ToString('o') regulator_mappings = @('FINRA-3110','FINRA-4511','SEC-17a-4','SOX-404','GLBA-501b','OCC-2011-12','SR-11-7','NYDFS-500-06','FFIEC-IS','FFIEC-MGMT') } $attest | ConvertTo-Json -Depth 6 | Out-File (Join-Path $script:EvidenceRoot "tc18-sovereign-attestation-$($script:RunId).json") -Encoding utf8
Expected
- Every
SKIPPEDrecord has a corresponding substitute artifact. - Attestation signed by Compliance Officer + AI Governance Lead within the current quarter.
Evidence Capture
- File:
tc18-sovereign-attestation-<runId>.json. - Retention: records scope (6 years).
- §3 master-table row: row 18.
Remediation
- Missing substitute artifacts: rerun the affected TC''s sovereign path.
- Stale attestation: route via e-signature; do not commingle with operator-only evidence.
TC-19 — Annual SOX 404 self-assessment to Audit Committee
Criterion mapped: SOX § 404 management-assertion support. Once per fiscal year, the AI Governance Lead, Compliance Officer, Information Security Officer, and Internal Audit Lead jointly produce a self-assessment of Control 2.1 effectiveness for delivery to the firm''s Audit Committee.
Setup
- All four quarterly evidence packs from the prior fiscal year are sealed and available.
- Operator: AI Governance Lead (primary author).
Steps
-
Aggregate the four quarterly packs:
-
Compute annual KPIs:
- Total environments under management.
- PASS rate per TC, per quarter.
- Median time-to-remediate for FAIL records.
- Open exceptions at fiscal year end.
- Sovereign substitution count.
- Author
tc19-annual-self-assessment-FY<yyyy>.mdfrom the firm''s template. - Route for signature: AI Governance Lead → Compliance Officer → Information Security Officer → Internal Audit Lead → Audit Committee acknowledgement.
Expected
- Self-assessment delivered to Audit Committee within 90 days of fiscal year end.
- All four signatures captured prior to delivery.
Evidence Capture
- File:
tc19-annual-self-assessment-FY<yyyy>.md+ signature manifest. - Retention: records scope (6 years).
- §3 master-table row: row 19.
Remediation
- Missed signature window: escalate to General Counsel; document the cause and the corrective action in the next quarterly pack.
TC-20 — Examination-ready evidence pack pull-test
Criterion mapped: A regulator (FINRA, SEC, OCC, NYDFS, FFIEC) can request evidence with short notice (often ≤ 7 business days). This TC is a quarterly fire drill: the Compliance Officer requests a focused pack from a randomly chosen prior-quarter run; the Power Platform Admin and AI Governance Lead must produce the focused pack within 4 business hours.
Setup
- A sealed §4 pack from any prior quarter exists.
- Operator: Compliance Officer (requester) + Power Platform Admin (producer) + AI Governance Lead (witness).
Steps
- Compliance Officer selects a random prior-quarter pack and identifies a focus area (e.g., "TC-3 + TC-12 evidence for the IB Z3 environment").
-
Power Platform Admin extracts the focused subset using the
Get-Me21EvidenceSlicehelper: -
AI Governance Lead verifies the slice''s Merkle proof against the original pack''s root hash and signs the verification record.
- Capture wall-clock time from request to delivery.
Expected
- Slice produced and verified within 4 business hours.
- Merkle proof valid.
Evidence Capture
- Files:
tc20-pulltest-slice-<runId>/*.json,tc20-pulltest-timing-<runId>.json,tc20-pulltest-merkle-proof-<runId>.json. - Retention: records scope (6 years) — examiner-readiness evidence.
- §3 master-table row: row 20.
Remediation
- Missed SLA: root-cause in the next quarterly attestation; escalate to AI Governance Lead and Internal Audit Lead.
§3 Evidence-Capture Master Table
This table is the canonical mapping from each TC to the evidence artifact(s) it produces, the retention scope under Control 1.7, and the regulator citations that justify the retention. Sovereign-affected rows are marked SOV-SUB.
| Row | TC | Artifact filename pattern | Retention scope | Retention period | Regulator mapping |
|---|---|---|---|---|---|
| 1 | TC-1 | tc01-inventory-<runId>.json |
Records | 6 years (WORM) | FINRA-4511, SEC-17a-3, SOX-404, FFIEC-MGMT |
| 2 | TC-2 | tc02-pipeline-targets-<runId>.json, tc02-pipeline-stages-<runId>.png |
Records | 6 years | SEC-17a-3, SOX-404, OCC-2011-12 |
| 3a | TC-3 | tc03-license-consumption-ppac-<runId>.png |
Records | 6 years | FINRA-4511, SOX-404 |
| 3b | TC-3 | tc03-env-snapshot-<runId>.json |
Records | 6 years | FINRA-4511, SEC-17a-4 |
| 3c | TC-3 | tc03-tenant-skus-<runId>.json |
Records | 6 years | SOX-404, FFIEC-MGMT |
| 3d | TC-3 | tc03-license-coverage-<runId>.json |
Records | 6 years | FINRA-4511, SOX-404, OCC-2011-12 |
| 3e | TC-3 | tc03-payg-handoff-tc04-<runId>.json |
Operational | 3 years | SOX-404 |
| 4 | TC-4 | tc04-payg-remediation-tickets-<runId>.json |
Records | 6 years | SOX-404, OCC-2011-12 |
| 5 | TC-5 | tc05-sharing-limits-<runId>.json |
Records (Z3) / Operational (Z2) | 6 / 3 years | FINRA-3110, GLBA-501b, NYDFS-500-06 |
| 6 | TC-6 | tc06-solution-checker-<runId>.json, tc06-exception-register-snapshot-<runId>.csv |
Records | 6 years | SOX-404, OCC-2011-12, FFIEC-MGMT |
| 7 | TC-7 | tc07-welcome-content-<runId>.json, tc07-welcome-dlp-scan-<runId>.json, cmk-exclusion-narrative.md |
Records | 6 years | FINRA-3110, GLBA-501b |
| 8 | TC-8 (Commercial) | tc08-digest-screenshot-<runId>.png, tc08-recipients-<runId>.json |
Records | 6 years | FINRA-3110, FINRA-4511 |
| 8-SOV | TC-8 (Sovereign) — SOV-SUB |
tc08-graph-audit-<runId>.json, tc08-sentinel-query-<runId>.kql, tc08-sentinel-results-<runId>.json, tc08-compensating-attestation-<runId>.json |
Records | 6 years | FINRA-3110, FINRA-4511, NYDFS-500-06 |
| 9 | TC-9 | tc09-ip-firewall-<runId>.json, tc09-egress-gap-<runId>.ndjson |
Records (Z3) / Operational (Z2) | 6 / 3 years | GLBA-501b, NYDFS-500-06, FFIEC-IS |
| 10 | TC-10 | tc10-ip-cookie-binding-<runId>.json + screenshot |
Records | 6 years | GLBA-501b, NYDFS-500-06, FFIEC-IS |
| 11 | TC-11 | tc11-lockbox-<runId>.json, tc11-lockbox-tier-screenshot-<runId>.png, tc11-tabletop-pointer-<runId>.json |
Records | 6 years | GLBA-501b, NYDFS-500-06, OCC-2011-12 |
| 12 | TC-12 | tc12-cmk-<runId>.json, signed cmk-exclusion-narrative.md, tc12-narrative-signature-<runId>.json |
Records | 6 years | SOX-404, GLBA-501b, OCC-2011-12 |
| 13 | TC-13 | tc13-tenant-isolation-<runId>.json + screenshot + variance pointer |
Records | 6 years | GLBA-501b, NYDFS-500-06, FFIEC-IS |
| 14 | TC-14 | tc14-routing-rules-<runId>.json + screenshot |
Records | 6 years | SEC-17a-3, SOX-404, FFIEC-MGMT |
| 15 | TC-15 | tc15-agent365-inventory-<runId>.json, tc15-catalog-gap-<runId>.json |
Records | 6 years | FINRA-4511, SEC-17a-4, FINRA-25-07 |
| 16 | TC-16 | tc16-sod-<runId>.ndjson |
Records | 6 years | SOX-404, OCC-2011-12, FFIEC-MGMT |
| 17 | TC-17 | tc17-inactive-<runId>.json + screenshot |
Operational | 3 years | FFIEC-MGMT |
| 18 | TC-18 | tc18-sovereign-attestation-<runId>.json |
Records | 6 years | FINRA-3110, FINRA-4511, SOX-404, GLBA-501b, OCC-2011-12, NYDFS-500-06, FFIEC-IS, FFIEC-MGMT |
| 19 | TC-19 | tc19-annual-self-assessment-FY<yyyy>.md + signature manifest |
Records | 6 years | SOX-302, SOX-404, FINRA-3110, FFIEC-MGMT |
| 20 | TC-20 | tc20-pulltest-slice-<runId>/*.json, tc20-pulltest-timing-<runId>.json, tc20-pulltest-merkle-proof-<runId>.json |
Records | 6 years | FINRA-4511, SEC-17a-4, OCC-2011-12 |
WORM placement. Records-scope artifacts mirror to the firm''s WORM-protected blob storage account (Azure Blob with immutability policy in
Lockedmode) within 24 hours of pack sealing. Operational-scope artifacts live in the firm''s standard tamper-evident log store. The §4.4 sealing job emits aworm-mirror-<runId>.jsonmanifest confirming each records-scope artifact''s WORM URL and immutability-policy retention period.
§4 Evidence Pack Assembly
4.1 Pack structure
pack-<runId>.zip
├── attestation.json # Merkle root, signatures, regulator mapping summary
├── manifest.ndjson # one line per evidence artifact (path + SHA-256 leaf)
├── preflight-<runId>.json # PRE-01..PRE-09 results
├── tc01/ ... tc20/ # one folder per TC
└── README.md # pack overview, regulator mapping, signature block
4.2 Per-TC pack contents
Each tc<NN>/ folder contains:
- The artifacts named in §3.
- A
tc<NN>-evidence-<runId>.ndjsonfile listing every evidence record emitted for that TC, conforming to the §0.6 schema. - Where applicable, a
tc<NN>-screenshots/subfolder.
4.3 Merkle tree and signing
function New-Me21EvidencePack {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $EvidenceRoot,
[Parameter(Mandatory)] [string] $RunId,
[Parameter(Mandatory)] [string] $PrimarySignerUpn,
[Parameter(Mandatory)] [string] $WitnessSignerUpn,
[Parameter(Mandatory)] [string] $OutputDir
)
# 1. enumerate artifacts (deterministic order)
$files = Get-ChildItem -Path $EvidenceRoot -Recurse -File |
Sort-Object FullName
# 2. leaf hashes
$manifest = foreach ($f in $files) {
$hash = (Get-FileHash -Algorithm SHA256 -Path $f.FullName).Hash
[pscustomobject]@{
path = $f.FullName.Substring($EvidenceRoot.Length).TrimStart('\','/')
size = $f.Length
sha256 = $hash
}
}
$manifest | ConvertTo-Json -Depth 4 -Compress |
Set-Content (Join-Path $EvidenceRoot 'manifest.ndjson') -Encoding utf8
# 3. Merkle root (binary tree, SHA-256, hex lowercase, deterministic order)
$leaves = $manifest.sha256
while ($leaves.Count -gt 1) {
$next = @()
for ($i = 0; $i -lt $leaves.Count; $i += 2) {
$left = $leaves[$i]
$right = if ($i + 1 -lt $leaves.Count) { $leaves[$i + 1] } else { $left }
$bytes = [System.Text.Encoding]::ASCII.GetBytes($left + $right)
$next += ([System.BitConverter]::ToString(
[System.Security.Cryptography.SHA256]::Create().ComputeHash($bytes)
).Replace('-','').ToLowerInvariant())
}
$leaves = $next
}
$merkleRoot = $leaves[0]
# 4. attestation document (dual signature)
$attest = [pscustomobject]@{
control_id = '2.1'
run_id = $RunId
merkle_root = $merkleRoot
leaf_count = $manifest.Count
primary_signer = $PrimarySignerUpn
witness_signer = $WitnessSignerUpn
signed_at = (Get-Date).ToUniversalTime().ToString('o')
regulator_mapping_summary = @(
'FINRA-3110','FINRA-4511','FINRA-25-07',
'SEC-17a-3','SEC-17a-4',
'SOX-302','SOX-404',
'GLBA-501b','OCC-2011-12','SR-11-7',
'CFTC-1.31','NYDFS-500-06','FFIEC-IS','FFIEC-MGMT'
)
}
$attest | ConvertTo-Json -Depth 6 |
Set-Content (Join-Path $EvidenceRoot 'attestation.json') -Encoding utf8
# 5. zip and emit
Compress-Archive -Path "$EvidenceRoot\*" `
-DestinationPath (Join-Path $OutputDir "pack-$RunId.zip") -Force
}
Dual control. The pack assembler refuses to seal a pack where
PrimarySignerUpn -eq $WitnessSignerUpn. The primary signer is the AI Governance Lead (or delegate); the witness is the Entra Global Reader designated for the run.
4.4 WORM mirroring
Records-scope artifacts mirror to the firm''s WORM blob container within 24 hours of pack-<runId>.zip creation. The mirroring job emits worm-mirror-<runId>.json:
{
"run_id": "ME-20260415-093012-a1b2c3d4",
"mirrored_at": "2026-04-16T09:30:12Z",
"container": "https://contosogovevidence.blob.core.windows.net/me21-records",
"immutability_policy": { "mode": "Locked", "period_years": 6 },
"artifacts": [ { "path": "tc01-inventory-...json", "blob_url": "...", "etag": "...", "sha256": "..." } ]
}
4.5 Schema validation
function Test-Me21EvidenceSchema {
param([Parameter(Mandatory)] [pscustomobject] $Record)
$required = @('control_id','run_id','run_timestamp','tenant_id','tenant_display_name',
'cloud','test_case','status','assertion','regulator_mappings','schema_version')
$missing = $required | Where-Object { -not $Record.PSObject.Properties.Name.Contains($_) }
if ($missing) { throw "Schema violation: missing fields $($missing -join ', ')" }
if ($Record.status -notin @('PASS','WARN','FAIL','SKIPPED')) { throw "Invalid status: $($Record.status)" }
if ($Record.regulator_mappings.Count -eq 0) { throw "Empty regulator_mappings" }
return $true
}
4.6 Quarterly attestation packet
Each calendar quarter, the AI Governance Lead and Compliance Officer co-sign attestation-quarterly-FY<yy>-Q<n>.md summarising:
- Number of runs in the quarter (target: weekly = 13/quarter).
- PASS rate per TC, per zone.
- All FAIL records and their remediation status.
- All WARN records aged > 30 days.
- All sovereign
SKIPPEDrecords and the corresponding TC-18 attestations. - Carry-forward exceptions from prior quarters.
The packet attaches the four most-recent monthly pack ZIPs and is delivered to: AI Governance Lead, Compliance Officer, Information Security Officer, Internal Audit Lead, Technology Risk Manager.
§5 Examiner-Facing Evidence Pack Production (TC-20 pull-test substrate)
When a regulator (FINRA, SEC, OCC, NYDFS, FFIEC) requests evidence on short notice, the Compliance Officer is the firm''s single point of contact. The evidence-pack production sequence, as exercised quarterly via TC-20:
- Receipt and triage. Compliance Officer receives the request, identifies which TCs and which environments are in scope, and opens a request ticket referencing the regulator name, exam ID, and SLA.
- Slice extraction. Power Platform Admin runs
Get-Me21EvidenceSliceagainst the relevant pack(s). - Merkle proof. AI Governance Lead generates a Merkle inclusion proof for each slice element against the original pack''s root hash.
- Redaction review. Compliance Officer + General Counsel review the slice for any out-of-scope data; redactions are themselves logged.
- Delivery. Compliance Officer delivers via the firm''s regulator-portal-of-record (e.g., FINRA Gateway, SEC EDGAR / Edgar Online, OCC examiner portal), with the Merkle root and slice proof in the cover memo.
- Post-delivery. Pack delivery, recipient acknowledgement, and any follow-up requests are appended to the firm''s examiner-correspondence log under Control 1.7.
Pre-staged narratives. The firm maintains, alongside this playbook, pre-approved narratives for the most-likely examiner questions:
- "How does the firm ensure every Power Platform environment hosting customer-facing or trade-impacting workloads is on the Managed Environments tier?" — answer based on TC-1, TC-2, TC-14.
- "How does the firm supervise maker activity in production environments?" — answer based on TC-5, TC-6, TC-8, TC-16.
- "What encryption protections apply to data at rest in your production Power Platform environments, and what are the documented exclusions?" — answer based on TC-12 and the CMK exclusion narrative.
- "How does the firm prevent cross-tenant data exfiltration via Power Platform connectors?" — answer based on TC-13 plus Control 1.4 / 1.5.
- "How does the firm govern AI agents shared from Copilot Studio at scale?" — answer based on TC-15 and Control 2.25; sovereign clouds answer via TC-18.
- "What is the firm''s evidence-retention scheme for AI-governance artifacts, and how does it map to FINRA Rule 4511 / SEC Rule 17a-4?" — answer based on §3 master table and Control 1.7.
§6 Pester Suite — Reference Skeleton
#Requires -Modules @{ ModuleName='Pester'; ModuleVersion='5.5.0' }
BeforeAll {
. $PSScriptRoot/Invoke-Me21PreFlight.ps1
Invoke-Me21PreFlight -PowerPlatformEndpoint $env:ME21_ENDPOINT
}
Describe 'Control 2.1 — Managed Environments' -Tag 'me21' {
Context 'TC-1 — Z2/Z3 environments are Managed' {
It 'has zero unmanaged Z2/Z3 environments' {
$z23Unmanaged.Count | Should -Be 0 -Because 'TC-1 PASS requires zero unmanaged Z2/Z3 envs'
}
}
Context 'TC-3 — Licensing coverage' {
It 'has zero non-compliant makers in any Z3 environment' {
($perEnv | Where-Object Zone -eq '3' | Measure-Object NonCompliantCount -Sum).Sum |
Should -Be 0 -Because 'Z3 has zero tolerance for non-compliant makers'
}
}
# ... TC-2, TC-4 .. TC-20 follow the same pattern
}
The full Pester suite (Invoke-Me21Tests.ps1) lives in the sister PowerShell Setup playbook, §10.
Cross-References
- Control: 2.1 Managed Environments
- Sister playbooks: Portal Walkthrough | PowerShell Setup | Troubleshooting
- Companion controls: 1.4 · 1.5 · 1.7 · 1.20 · 2.2 · 2.3 · 2.8 · 2.14 · 2.15 · 2.25 · 3.1 · 3.6 · 3.9
Back to Control 2.1 | Portal Walkthrough | PowerShell Setup | Troubleshooting
Updated: April 2026 | Version: v1.4.0 | UI Verification Status: Current