Flow Configuration¶
Power Automate flow specifications for the FINRA Supervision Workflow solution.
Flow Overview¶
| Flow | Trigger | Purpose |
|---|---|---|
| FSW-IngestFlaggedItems | Scheduled (15 min) | Poll Communication Compliance for flagged items |
| FSW-AssignmentFlow | Dataverse row created | Route items to supervisory principals |
| FSW-EscalationFlow | Scheduled (hourly) | Monitor SLA and escalate overdue items |
| FSW-ReviewComplete | Dataverse row updated | Log review completion, notify stakeholders |
FSW-IngestFlaggedItems¶
Polls Communication Compliance API for new flagged items and creates SupervisionQueue records.
Trigger¶
Recurrence - Frequency: Minute - Interval: 15 - Time zone: UTC - Concurrency Control: On (Degree of Parallelism: 1) — prevents duplicate queue entries if a run exceeds 15 minutes
Actions¶
1. Initialize variable: lastRunTime (from secure storage)
- First-run guard: If the Key Vault secret does not exist, initialize lastRunTime
to addHours(utcNow(), -1) so the first poll retrieves only the last hour of alerts
instead of failing with HTTP 400 or returning the entire backlog.
2. HTTP - Get Communication Compliance Alerts
Method: GET
URI: https://compliance.microsoft.com/api/SupervisoryReview/alerts
Headers:
Authorization: Bearer @{outputs('Get_Token')}
Queries:
$filter: createdDateTime gt @{variables('lastRunTime')} and status eq 'Active'
$top: 100
Note: Uses Purview Communication Compliance API, NOT Graph security/alerts_v2
(see docs/communication-compliance-setup.md for API access configuration)
> ⚠️ **API Risk:** The `compliance.microsoft.com/api/SupervisoryReview/alerts` endpoint
> is undocumented and not officially supported by Microsoft. It may change or be removed
> without notice. As a fallback, consider using `Connect-IPPSSession` with
> `Get-SupervisoryReviewPolicyV2` cmdlets for programmatic access.
Note: The $top=100 parameter prevents action execution limit errors when a
large alert backlog exists (e.g., post-outage or first run). Remaining
alerts are picked up in subsequent polling cycles.
Note: The status filter excludes dismissed or resolved alerts so that only
actionable items enter the supervision queue.
3. Get SupervisionConfig (all active rows)
Cache outside loop to avoid N+1 queries (matches EscalationFlow pattern at step 2.1)
4. Apply to each: Alert
4.1 Parse JSON - Extract alert details
4.2 Condition: Is AI Agent Related?
- Check if alert source contains 'CopilotStudio' or 'AgentBuilder'
4.3 If Yes:
4.3.1 HTTP - Get Agent Details
URI: <agent metadata source — see note below>
# NOTE: Power Automate has no single `get agent metadata` endpoint.
# Recommended sources of agent zone/tier:
# (a) `agent-registry-automation` Dataverse table `fsi_agentinventory` —
# List rows: filter `fsi_agentid eq '<agent.id>'` to get fsi_zone.
# (b) `environment-lifecycle-management` `fsi_environment` zone classification.
# (c) Static mapping table maintained by the supervision team.
# Use `Get rows` (Dataverse connector) to look up zone/tier; do not call
# api.powerplatform.com directly without a documented endpoint.
4.3.2 Look up matching config from cached SupervisionConfig
Filter (in-memory): fsi_zone eq @{agent.zone} and fsi_tier eq @{agent.tier}
4.3.3 Condition: Random sampling (Zone 1-2)
- If Zone 3 OR rand(1, 101) <= reviewPercent
# NOTE: Power Automate `rand(min, max)` upper bound is exclusive
# (per Microsoft docs). Use `rand(1, 101)` to get inclusive 1..100,
# otherwise reviewPercent=99 samples 100% (off-by-one).
4.3.4 Condition: Duplicate check
- Filter SupervisionQueue: fsi_sourceid eq @{alert.id}
- If count > 0, skip creation
4.3.5 Create SupervisionQueue row
- Queue Number: Auto
- Source Type: Communication Compliance
- Source ID: @{alert.id}
- Agent ID: @{agent.id}
- Agent Name: @{agent.displayName}
- Zone: @{agent.zone}
- Tier: @{agent.tier}
- Content Preview: @{if(empty(alert.content), '', substring(alert.content, 0, min(length(alert.content), 500)))}
- Flagged Reason: @{alert.policyName}
- State: Pending
- Queued Date: @{utcNow()}
- SLA Due: @{addHours(utcNow(), config.slaHours)}
5. Update lastRunTime in secure storage
Connection References¶
| Connection | Type | Purpose |
|---|---|---|
| Dataverse | Premium | Create queue records |
| HTTP with Microsoft Entra ID (preauthorized) | Premium | Purview Communication Compliance API |
| Azure Key Vault | Premium | Store polling state such as lastRunTime; do not store production client secrets when managed identity is available |
Error Handling¶
- On HTTP failure: Log to SupervisionLog with action "IngestError" and send real-time notification (Teams/Email) to Queue Managers so that persistent API unavailability is detected within minutes rather than waiting for the daily SLA report to surface the volume drop
- On Dataverse failure: Retry 3 times, then alert Queue Manager
- All errors: Continue processing remaining alerts
FSW-AssignmentFlow¶
Triggered when a new SupervisionQueue row is created. Assigns to appropriate supervisory principal.
Trigger¶
When a row is added (Dataverse) - Table: SupervisionQueue - Scope: Organization
Actions¶
1. Get SupervisionConfig
Filter: fsi_zone eq @{triggerBody().zone} and fsi_tier eq @{triggerBody().tier}
2. Condition: Has Default Principal?
2.1 If Yes:
- Assigned Principal = config.defaultPrincipal
2.2 If No:
- Get available supervisors (custom logic or round-robin)
- Assigned Principal = selected supervisor
3. Update SupervisionQueue
- Assigned Principal: @{assignedPrincipal}
- Owner: @{assignedPrincipal} (Dataverse Assign action — transfers record ownership
from the service principal so User-level privileges grant the supervisor access)
- State: Pending (unchanged — transitions to InReview when the supervisor opens the item; see note below)
4. Create SupervisionLog
- Queue Item: @{triggerBody().id}
- Action: Assigned
- `fsi_actor@odata.bind`: "/systemusers(<service-principal-app-user-guid>)" // resolve at flow design time; lookup column requires a valid systemuser GUID
- Timestamp: @{utcNow()}
- Details: Assigned to @{assignedPrincipal.fullname}
5. Send notification (Teams/Email)
- To: Assigned Principal
- Subject: New item requiring supervision review
- Body: Agent @{agentName}, Flagged for @{flaggedReason}
- Include: Deep link to queue item
Error Handling¶
- On Dataverse failure (SupervisionLog creation at step 4): Retry 3 times, then send notification to Queue Managers — a missing audit log entry compromises the FINRA 3110 evidence trail
- On notification failure (step 5): Log to SupervisionLog with action "NotificationError" and continue — assignment is still valid even if notification fails
- All errors: Log error details to SupervisionLog for auditability
Round-Robin Assignment Logic¶
For workload balancing when no default principal is configured:
1. Get all users with FSW Supervisor role
2. Get current queue counts per supervisor
- Filter: fsi_state in (1, 2)
- Group by: Assigned Principal
3. Select supervisor with lowest count
4. If tie, select randomly among tied supervisors
Pending → InReview Transition¶
The AssignmentFlow leaves items in Pending state after assignment. The transition to InReview (fsi_state = 2) must be implemented via one of:
- Model-driven app business rule: Set fsi_state to InReview when the supervisor opens the form (recommended — no additional flow required)
- Client-side JavaScript: On form load, update state if current user matches Assigned Principal and state is Pending
- Additional flow: Trigger on SupervisionQueue row update when a supervisor writes review notes or changes any review field
Without this transition, the EscalationFlow's filter on fsi_state in (1, 2) still captures these items, but the "My Queue" view filtering on InReview will not show newly assigned items until the state is updated.
Optional Teams/Outlook Approval Surface¶
The authoritative review record remains the Dataverse fsi_supervisionqueue row. If your deployment lets supervisors approve directly from Teams or Outlook, use current Power Automate Approvals and Adaptive Card patterns:
- Simple blocking path: Use Start and wait for an approval when the flow should pause until the supervisor responds. Capture the current Response, Responses Approver response, and Responses comments outputs.
ApproveandRejectcomparisons are case-sensitive. - Teams adaptive card path: Use Create an approval, post the adaptive card output to each supervisor with the Teams flow bot action, then use Wait for an approval before updating
fsi_reviewoutcome,fsi_reviewnotes,fsi_reviewedby, andfsi_revieweddate. - Parallel or dual-supervisor review: Use Approve/Reject - Everyone must approve or Custom Responses - Wait for all responses. Add a condition in each branch or after Wait for an approval that checks the response output and writes a SupervisionLog entry for every approver response.
- Outlook actionable approval emails: The built-in Approvals connector sends actionable emails. If you build a custom Outlook Actionable Message, the email body must be HTML with an
application/adaptivecard+jsonscript block, and production messages must include the provideroriginatorvalue. - Custom Teams cards: New custom cards should use Adaptive Cards schema 1.5+ with
Action.Execute, handle theadaptiveCard/actioninvoke (card.actionin some Teams SDKs) in a bot, includerefresh.userIdsfor Teams auto-refresh, and includeAction.Submitfallback for older Teams clients.
Minimal custom card shape for a bot-backed review action:
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.5",
"refresh": {
"action": { "type": "Action.Execute", "verb": "refreshReview" },
"userIds": ["<teams-user-mri>"]
},
"body": [
{ "type": "TextBlock", "text": "Supervision item @{queueNumber}", "weight": "Bolder" },
{ "type": "Input.Text", "id": "reviewNotes", "isMultiline": true }
],
"actions": [
{
"type": "Action.Execute",
"title": "Approve",
"verb": "approveSupervisionItem",
"data": { "queueItemId": "@{triggerBody().fsi_supervisionqueueid}" },
"fallback": { "type": "Action.Submit", "title": "Approve" }
}
]
}
Microsoft Learn references: Approvals actions, approval action differences, parallel approvals, Teams Universal Actions, and Outlook Actionable Messages.
FSW-EscalationFlow¶
Scheduled flow that monitors SLA compliance and escalates overdue items.
Trigger¶
Recurrence - Frequency: Hour - Interval: 1 - Time zone: UTC - Concurrency Control: On (Degree of Parallelism: 1) — prevents double-escalation notifications if a run exceeds 1 hour
Actions¶
1. List SupervisionQueue - Approaching SLA
Filter: fsi_state in (1, 2)
and fsi_sladue lt @{addHours(utcNow(), 2)}
and fsi_sladue gt @{utcNow()}
1.1 Apply to each: Send reminder
- Teams notification to Assigned Principal
- "Item @{queueNumber} SLA due in @{dateDiff} hours"
2. List SupervisionQueue - SLA Breached
Filter: fsi_state in (1, 2)
and fsi_sladue lt @{utcNow()}
2.1 Get SupervisionConfig (all rows, cached outside loop)
2.2 Apply to each: Check escalation threshold
2.2.1 Look up config from cached SupervisionConfig
2.2.2 Calculate hours since queued
hoursSinceQueued = dateDiff('Hour', queuedDate, utcNow())
2.2.3 Condition: hoursSinceQueued >= escalationHours?
If Yes:
- Update SupervisionQueue: State = Escalated
- Create SupervisionLog: Action = Escalated
- Reassign to config.escalationTo
- Notify escalation recipient
- Notify original assignee of escalation
If No:
- Send urgent reminder to Assigned Principal
- Create SupervisionLog: Action = SLABreached
3. Generate daily SLA report
- Count items by state
- Calculate SLA compliance %
- Send to Queue Managers
Error Handling¶
- On Dataverse failure (SupervisionLog creation at steps 2.2.3): Retry 3 times, then send alert to Queue Managers — missing escalation audit entries compromise the FINRA 3110 evidence trail
- On notification failure: Log to SupervisionLog with action "NotificationError" and continue — the state change and audit log are the critical path
- On SLA report generation failure (step 3): Send error notification to Queue Managers so the daily compliance summary is not silently lost
Escalation Notification Template¶
Subject: [ESCALATED] Supervision item requires immediate attention
Item: @{queueNumber}
Agent: @{agentName}
Zone: @{zone} | Tier: @{tier}
Flagged: @{flaggedReason}
Queued: @{queuedDate}
SLA Due: @{slaDue}
Original Assignee: @{originalAssignee}
This item has exceeded the escalation threshold and requires immediate review.
[Review Item] - Deep link
FSW-ReviewComplete¶
Triggered when a supervisor completes a review (State changes to Approved/Rejected/Escalated).
Trigger¶
When a row is modified (Dataverse) - Table: SupervisionQueue - Scope: Organization - Filter: State has changed AND State in (Approved, Rejected, Escalated)
Guard against automation-initiated triggers: The EscalationFlow (step 2.1.3) also sets State = Escalated during automated escalation. To prevent duplicate/conflicting audit log entries, Step 1 below must check whether the state change was initiated by a supervisor or by automation. Add a condition: if
triggerBody()?['_modifiedby_value']equals the service principal used by EscalationFlow, skip logging and exit the flow — EscalationFlow already logs its own escalation action.
Actions¶
1. Create SupervisionLog
- Queue Item: @{triggerBody().id}
- Action: Map reviewOutcome to action value (Approved=1→5, Rejected=2→6, Escalated=3→7 per dataverse-schema.md Action picklist)
- `fsi_actor@odata.bind`: "/systemusers(@{triggerBody().reviewedBy.id})" // requires reviewer Entra object ID resolved to systemuserid; do not pass display names to Lookup columns
- `fsi_queueitem@odata.bind`: "/fsi_supervisionqueues(@{triggerBody().queueItemId})" // lookup to the SupervisionQueue row
- Timestamp: @{utcNow()}
- Details: @{triggerBody().reviewNotes}
2. Condition: Outcome = Escalated?
2.1 If Yes:
- Get SupervisionConfig
- Reassign to escalationTo
- Reset SLA Due
- Update State to Pending
- Notify escalation recipient
2.2 If No (Approved or Rejected):
- Update State (already set by trigger)
- Close workflow
3. Optional: Notify requester/stakeholders
- If Rejected, may need follow-up action
4. Update metrics (increment counters for dashboard)
Error Handling¶
- On Dataverse failure (SupervisionLog creation at step 1): Retry 3 times, then send alert to Queue Managers — a missing review-completion audit entry is a critical gap in the FINRA 3110 supervision evidence trail
- On reassignment failure (step 2.1): Log to SupervisionLog with action "ReassignmentError", notify Queue Managers, and leave item in current state for manual intervention
- On notification failure (step 3): Log to SupervisionLog with action "NotificationError" and continue — the audit log is the critical path
Connection Security¶
Managed identity-first authentication¶
Production flows should use a dedicated managed identity where the connector path supports it:
- Enable a system-assigned managed identity for the Azure-hosted workflow component, or create a user-assigned managed identity for shared automation.
- Register the identity as a Dataverse application user and assign the FSW Admin security role.
- Assign approved Purview/Communication Compliance access to the connector identity where supported; the Compliance Administrator role is an Entra ID directory role, not a Graph API permission.
- Configure HTTP with Microsoft Entra ID (preauthorized) or a custom connector with managed identity OAuth instead of a stored client secret.
- Store polling state such as
FSW-LastRunTimein Key Vault. Client secrets are a legacy dev-only fallback and must not be the production path.
Least Privilege¶
| Flow | Required Permissions |
|---|---|
| IngestFlaggedItems | Purview: Compliance Administrator, Graph: User.Read.All |
| AssignmentFlow | Dataverse: FSW Admin role |
| EscalationFlow | Dataverse: FSW Admin role |
| ReviewComplete | Dataverse: FSW Admin role |
Environment Variables for Configurable Values¶
The following values are specified inline in the flow definitions above for clarity, but should be configured using Power Platform environment variables to support ALM promotion across dev/test/prod environments:
| Variable Name | Flow | Default | Description |
|---|---|---|---|
FSW_IngestPollingMinutes |
IngestFlaggedItems | 15 | Polling interval for Communication Compliance API |
FSW_EscalationCheckHours |
EscalationFlow | 1 | Interval for escalation threshold checks |
FSW_PowerPlatformApiBase |
IngestFlaggedItems | https://api.powerplatform.com |
Power Platform API base URL |
When deploying to a new environment, create these environment variables in the Power Platform admin center or include them in the solution's environmentvariabledefinition table. Flows should reference these variables instead of hardcoded values.
Testing Checklist¶
- IngestFlow creates queue items from test alerts
- AssignmentFlow routes to correct supervisor by zone/tier
- EscalationFlow sends reminders at correct thresholds
- EscalationFlow escalates after configured hours
- ReviewComplete logs all outcomes correctly
- Notifications delivered to correct recipients
- SLA calculations correct across time zones