---
name: build-power-automate-flows
description: Creates, updates, debugs, and troubleshoots Power Automate cloud flows via the Dataverse MCP. Use when building new flows, editing existing flow definitions, fixing broken triggers, troubleshooting connection references, activating/deactivating flows, or working with flow templates. Covers clientdata structure, trigger configuration (filteringattributes vs filterexpression), connection reference setup, AI Builder actions, deployment/ALM patterns, and common flow gotchas. Part of the /project-standup pipeline (Phase 5).
argument-hint: "[environment-url] [solution-name] [optional: --project-dir DIR] [optional: --connection-ref logical-name]"
---

# Build Power Automate Cloud Flows

Create cloud flows via `dataverse_create_flow` using properly structured `clientdata` JSON templates.

## Arguments

- `$ARGUMENTS[0]` — Dataverse environment URL (e.g., `https://org209ecb48.crm.dynamics.com`)
- `$ARGUMENTS[1]` — Solution unique name to add flows to
- `--project-dir DIR` — Project runs directory containing `ado-work-items.json` and `build-specification.json`
- `--connection-ref` — Connection reference logical name to use (default: discover via `dataverse_list_connection_references`)

## Pre-Flight

Before building or modifying a cloud flow:
- Confirm the target environment and solution name
- If modifying an existing flow: `dataverse_get_flow` to retrieve current definition, then use `/review-component` if the flow is complex (10+ actions)
- If creating a new flow: verify the trigger table exists, verify all referenced columns exist via `dataverse_list_columns`
- Check existing connection references in the solution: `dataverse_list_connection_references` — reuse existing ones before creating new
- For connectors you haven't used before: `dataverse_list_connector_operations` to discover available actions and their parameters

## Guardrails

- **Connection references MUST exist before flow activation.** Create all needed connection references and wire them to connections BEFORE attempting `dataverse_activate_flow`. If activation fails, check connection ref wiring first — do NOT delete and recreate the flow.
- **`schemaVersion` goes at ROOT of clientdata**, not inside `properties`. This is the #1 cause of "flow saved but won't activate" issues.
- **Never delete a flow to fix an activation error.** Diagnose the root cause (usually missing connection refs or malformed trigger config). Use `/troubleshoot` if stuck.
- **`filteringattributes` is for automated triggers** (when a record field changes). Do NOT confuse with `filterexpression` (OData filter on which records trigger the flow).

## Pipeline Integration

When invoked as **Phase 5** of the `/project-standup` pipeline:

**Input:**
- Flow-related PBIs from `ado-work-items.json` (category: "Workflows" or items tagged as flow stories)
- Flow definitions from `build-specification.json` `flows` array

**Tandem ADO pattern** — same as Phase 4:
1. Read flow PBI from `ado-work-items.json` → get `work_item_id`
2. `wit_get_work_item(id)` → read Implementation Details for trigger/action specs
3. `wit_update_work_item(id, state: "Active")`
4. Create flow → activate flow → verify
5. `wit_update_work_item(id, state: "Closed")`
6. `wit_add_work_item_comment(id, "Flow created and activated: [flow name]")`
7. Update `ado-work-items.json` + append to `build-log.json`

---

## Prerequisites

- Dataverse environment accessible via MCP
- At least one unmanaged Dataverse connection reference in the environment
- Tables referenced by the flow must already exist

## Step 1: Connection Setup Pipeline

Before creating any flow, ensure the connection reference and underlying connection are properly wired.

### 1a. Check for existing connection reference

```
dataverse_list_connection_references(connector_filter: "commondataserviceforapps")
```

If a suitable connection reference exists (unmanaged, `is_managed: false`) with a `connection_id` set — use it. Skip to Step 2.

If a connection reference exists but `connection_id` is null — go to step 1c to wire a connection.

If no connection reference exists for the needed connector — create one:

### 1b. Create connection reference (if needed)

```
dataverse_create_connection_reference(
  display_name: "Project Dataverse Connection",
  connector_id: "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps",
  solution_name: "SolutionName"    // REQUIRED for auto-generated logical name (resolves publisher prefix)
)
```

The logical name is auto-generated with the solution's publisher prefix (e.g., `mcpt_ProjectDataverseConnection`). Provide `logical_name` explicitly to override.

### 1c. Wire a connection to the connection reference

**Check for existing valid connections:**
```
dataverse_list_connections(connector_filter: "commondataserviceforapps")
```

Select a connection where `is_valid: true` and `status: "Connected"`. Prefer:
1. Service account-owned connections over individual user connections
2. The most recently created connection if multiple valid ones exist

**If a valid connection exists:**
```
dataverse_share_connection(
  connector_id: "shared_commondataserviceforapps",
  connection_id: "<selected_connection_id>",
  principal_id: "<SP_object_id>",
  principal_type: "ServicePrincipal",
  role: "CanEdit"
)

dataverse_update_connection_reference(
  connection_ref_id: "<ref_id>",
  connection_id: "<selected_connection_id>"
)
```

**If NO valid connection exists (fresh environment):**

Connections cannot be created programmatically by the service principal (no Power Apps license). A user must create one manually:

1. Inform the user: "No [connector] connection exists in this environment. Please create one:"
   - Go to **make.powerapps.com** → select the target environment
   - Click **Connections** → **+ New connection** → select the connector → sign in
   - For production flows, sign in with a **service account** (not a personal account)
2. After user confirms creation, discover and wire it:
   ```
   dataverse_list_connections(connector_filter: "...")  // find the new connection
   dataverse_share_connection(...)                       // share with SP
   dataverse_update_connection_reference(...)            // wire it
   ```

### Connection Reference per Connector Type

Each distinct connector used in flows needs its own connection reference:
- Dataverse → `shared_commondataserviceforapps`
- SharePoint → `shared_sharepointonline`
- Outlook → `shared_office365`
- Teams → `shared_teams`
- Approvals → `shared_approvals`

One connection reference can be shared across multiple flows if they use the same connector.

## Step 2: Build Flows from ADO Stories

For each flow-related ADO story:
1. Read the story's Implementation Details from ADO (`wit_get_work_item`)
2. Match to the appropriate template below
3. Customize trigger entity, filter, and actions per the story's specs
4. Call `dataverse_create_flow` with the assembled `clientdata`
5. Call `dataverse_activate_flow` to turn it on
6. Update ADO story state

## ClientData Structure

Every flow's `clientdata` is a JSON string with this structure:

```json
{
  "properties": {
    "definition": {
      "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "$connections": { "defaultValue": {}, "type": "Object" },
        "$authentication": { "defaultValue": {}, "type": "SecureObject" }
      },
      "triggers": { ... },
      "actions": { ... },
      "outputs": {}
    },
    "connectionReferences": {
      "shared_commondataserviceforapps": {
        "runtimeSource": "embedded",
        "connection": {
          "connectionReferenceLogicalName": "YOUR_CONNECTION_REF_LOGICAL_NAME"
        },
        "api": {
          "name": "shared_commondataserviceforapps"
        }
      }
    }
  },
  "schemaVersion": "1.0.0.0"
}
```

**CRITICAL**:
- The `clientdata` parameter to `dataverse_create_flow` must be a **JSON string** (the tool will `JSON.parse()` it to validate, then store it). Always `JSON.stringify()` your object or ensure proper escaping.
- **`schemaVersion` MUST be at the ROOT level** (sibling of `properties`), NOT inside `properties`. Dataverse rejects clientdata without it. After creation, Dataverse moves it inside `properties` in the stored record — but creation requires it at the root.
- **`parameters` block** with `$connections` and `$authentication` is required inside `definition`.

---

## Flow Templates

### Template 1: Dataverse Row Modified Trigger → Update Row

**Use for:** "When [field] changes to [value], update [other fields]"

```json
{
  "properties": {
    "definition": {
      "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "$connections": { "defaultValue": {}, "type": "Object" },
        "$authentication": { "defaultValue": {}, "type": "SecureObject" }
      },
      "triggers": {
        "When_a_row_is_modified": {
          "type": "OpenApiConnectionWebhook",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataserviceforapps",
              "operationId": "SubscribeWebhookTrigger",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
            },
            "parameters": {
              "subscriptionRequest/message": 3,
              "subscriptionRequest/entityname": "TABLE_LOGICAL_NAME",
              "subscriptionRequest/scope": 4,
              "subscriptionRequest/filteringattributes": "COLUMN_LOGICAL_NAME",
              "subscriptionRequest/filterexpression": "COLUMN_LOGICAL_NAME eq VALUE"
            },
            "authentication": "@parameters('$authentication')"
          }
        }
      },
      "actions": {
        "Update_a_row": {
          "runAfter": {},
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataserviceforapps",
              "operationId": "UpdateRecord",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
            },
            "parameters": {
              "entityName": "TABLE_SET_NAME_PLURAL",
              "recordId": "@triggerOutputs()?['body/TABLE_PRIMARY_KEY']",
              "item/FIELD_TO_UPDATE": "NEW_VALUE"
            },
            "authentication": "@parameters('$authentication')"
          }
        }
      },
      "outputs": {}
    },
    "connectionReferences": {
      "shared_commondataserviceforapps": {
        "runtimeSource": "embedded",
        "connection": {
          "connectionReferenceLogicalName": "CONNECTION_REF_LOGICAL_NAME"
        },
        "api": {
          "name": "shared_commondataserviceforapps"
        }
      }
    }
  },
  "schemaVersion": "1.0.0.0"
}
```

**Parameter reference:**
- `subscriptionRequest/message`: 1=Create, 2=Delete, 3=Update, 4=Create+Update
- `subscriptionRequest/scope`: 1=User, 2=BusinessUnit, 3=ParentChildBU, 4=Organization
- `subscriptionRequest/filteringattributes`: comma-separated column logical names
- `subscriptionRequest/filterexpression`: OData filter (e.g., `statecode eq 0`)

**IMPORTANT: `filteringattributes` is ONLY valid on Update triggers (message=3) and Create+Update triggers (message=4).** On Create-only triggers (message=1), `filteringattributes` is invalid — it will be silently ignored or cause errors. To filter which records trigger a Create flow, use `filterexpression` (OData filter) instead.

### Template 2: Dataverse Row Created Trigger → Create Related Row

**Use for:** "When a [parent] is created, auto-create a [child] record"

```json
{
  "properties": {
    "definition": {
      "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "$connections": { "defaultValue": {}, "type": "Object" },
        "$authentication": { "defaultValue": {}, "type": "SecureObject" }
      },
      "triggers": {
        "When_a_row_is_created": {
          "type": "OpenApiConnectionWebhook",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataserviceforapps",
              "operationId": "SubscribeWebhookTrigger",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
            },
            "parameters": {
              "subscriptionRequest/message": 1,
              "subscriptionRequest/entityname": "PARENT_TABLE",
              "subscriptionRequest/scope": 4
            },
            "authentication": "@parameters('$authentication')"
          }
        }
      },
      "actions": {
        "Create_child_row": {
          "runAfter": {},
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataserviceforapps",
              "operationId": "CreateRecord",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
            },
            "parameters": {
              "entityName": "CHILD_TABLE_SET_NAME",
              "item/CHILD_NAME_FIELD": "@triggerOutputs()?['body/PARENT_NAME_FIELD']",
              "item/_PARENT_LOOKUP_FIELD_value": "@triggerOutputs()?['body/PARENT_PRIMARY_KEY']"
            },
            "authentication": "@parameters('$authentication')"
          }
        }
      },
      "outputs": {}
    },
    "connectionReferences": {
      "shared_commondataserviceforapps": {
        "runtimeSource": "embedded",
        "connection": {
          "connectionReferenceLogicalName": "CONNECTION_REF_LOGICAL_NAME"
        },
        "api": {
          "name": "shared_commondataserviceforapps"
        }
      }
    }
  },
  "schemaVersion": "1.0.0.0"
}
```

### Template 3: Scheduled Recurrence → Query → Loop → Action

**Use for:** "Every [interval], find records matching [criteria] and process them"

```json
{
  "properties": {
    "definition": {
      "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "$connections": { "defaultValue": {}, "type": "Object" },
        "$authentication": { "defaultValue": {}, "type": "SecureObject" }
      },
      "triggers": {
        "Recurrence": {
          "type": "Recurrence",
          "recurrence": {
            "frequency": "Day",
            "interval": 1,
            "startTime": "2026-01-01T06:00:00Z",
            "timeZone": "Eastern Standard Time"
          }
        }
      },
      "actions": {
        "List_rows": {
          "runAfter": {},
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataserviceforapps",
              "operationId": "ListRecords",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
            },
            "parameters": {
              "entityName": "TABLE_SET_NAME_PLURAL",
              "$filter": "ODATA_FILTER_EXPRESSION",
              "$top": 5000
            },
            "authentication": "@parameters('$authentication')"
          }
        },
        "Apply_to_each": {
          "runAfter": {
            "List_rows": ["Succeeded"]
          },
          "type": "Foreach",
          "foreach": "@outputs('List_rows')?['body/value']",
          "actions": {
            "Update_each_row": {
              "runAfter": {},
              "type": "OpenApiConnection",
              "inputs": {
                "host": {
                  "connectionName": "shared_commondataserviceforapps",
                  "operationId": "UpdateRecord",
                  "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
                },
                "parameters": {
                  "entityName": "TABLE_SET_NAME_PLURAL",
                  "recordId": "@items('Apply_to_each')?['TABLE_PRIMARY_KEY']",
                  "item/FIELD_TO_UPDATE": "NEW_VALUE"
                },
                "authentication": "@parameters('$authentication')"
              }
            }
          }
        }
      },
      "outputs": {}
    },
    "connectionReferences": {
      "shared_commondataserviceforapps": {
        "runtimeSource": "embedded",
        "connection": {
          "connectionReferenceLogicalName": "CONNECTION_REF_LOGICAL_NAME"
        },
        "api": {
          "name": "shared_commondataserviceforapps"
        }
      }
    }
  },
  "schemaVersion": "1.0.0.0"
}
```

**Recurrence frequency values:** `Second`, `Minute`, `Hour`, `Day`, `Week`, `Month`

### Template 4: Condition (If/Else) Pattern

Insert this into any template's `actions` to add branching logic:

```json
"Check_condition": {
  "runAfter": { "PREVIOUS_ACTION": ["Succeeded"] },
  "type": "If",
  "expression": {
    "equals": [
      "@triggerOutputs()?['body/FIELD_NAME']",
      "EXPECTED_VALUE"
    ]
  },
  "actions": {
    "If_true_action": {
      "runAfter": {},
      "type": "OpenApiConnection",
      "inputs": { ... }
    }
  },
  "else": {
    "actions": {
      "If_false_action": {
        "runAfter": {},
        "type": "OpenApiConnection",
        "inputs": { ... }
      }
    }
  }
}
```

**Expression operators:** `equals`, `not`, `greater`, `greaterOrEquals`, `less`, `lessOrEquals`, `contains`, `startsWith`, `endsWith`, `and`, `or`

### Template 5: Compose + HTTP (External API Call)

**Use for:** "Call an external API and process the response"

```json
"Compose_request": {
  "runAfter": {},
  "type": "Compose",
  "inputs": {
    "url": "https://api.example.com/endpoint",
    "body": {
      "key": "@triggerOutputs()?['body/FIELD_NAME']"
    }
  }
},
"HTTP_call": {
  "runAfter": { "Compose_request": ["Succeeded"] },
  "type": "Http",
  "inputs": {
    "method": "POST",
    "uri": "https://api.example.com/endpoint",
    "headers": {
      "Content-Type": "application/json"
    },
    "body": "@outputs('Compose_request')"
  }
}
```

---

## Common Dataverse Operations Reference

### operationId Values (Dataverse Connector)

| Operation | operationId | Notes |
|-----------|------------|-------|
| Create a row | `CreateRecord` | `entityName` = plural set name |
| Update a row | `UpdateRecord` | `entityName` + `recordId` |
| Delete a row | `DeleteRecord` | `entityName` + `recordId` |
| Get a row by ID | `GetItem` | `entityName` + `recordId` |
| List rows | `ListRecords` | `entityName` + `$filter` + `$top` |
| Perform bound action | `PerformBoundAction` | For custom actions |
| Perform unbound action | `PerformUnboundAction` | For global actions |
| Row created trigger | `SubscribeWebhookTrigger` | message=1 |
| Row updated trigger | `SubscribeWebhookTrigger` | message=3 |
| Row deleted trigger | `SubscribeWebhookTrigger` | message=2 |
| Row created/updated | `SubscribeWebhookTrigger` | message=4 |

### Discovering Operations for ANY Connector

Before building a flow action for any connector, query its available operations:

```
dataverse_list_connector_operations(
  connector_name: "shared_commondataserviceforapps",  // or shared_twilio, shared_teams, etc.
  operation_filter: "send",                            // optional filter
  include_parameters: "true"                           // include full parameter schemas
)
```

This returns all operationIds, summaries, HTTP methods, and parameter schemas from the connector's swagger. Works for every connector — Dataverse, SharePoint, Outlook, Teams, Twilio, custom connectors, etc.

To discover available connectors: `dataverse_list_connectors(name_filter: "twilio")`

**IMPORTANT:** Never guess operationIds. Always query the connector swagger first.

### AI Builder Actions

AI Builder actions are **virtual operations on the Dataverse connector** — they are NOT in the connector swagger. They use a special naming convention derived from AI Builder template records.

**OperationId pattern:** `aibuilderpredict_` + template `msdyn_uniquename` lowercased

**Discovery process:**
1. Query templates: `dataverse_query_records(entity_set: "msdyn_aitemplates", select: "msdyn_aitemplateid,msdyn_uniquename")`
2. Derive operationId: `aibuilderpredict_` + `msdyn_uniquename.toLowerCase()`
3. Read parameter schema: Get template record's `msdyn_rundataspecification` field for input/output definitions

**Parameter format:** Flat paths (NOT nested objects)
```json
"parameters": {
  "item/requestv2/language": "en",
  "item/requestv2/text": "@outputs('Get_Record')?['body/cr1a2_name']"
}
```

**Connection:** Uses the same Dataverse connection reference — no separate connector or connection needed.

**Common AI Builder operationIds:**

| Action | operationId |
|--------|------------|
| Analyze sentiment | `aibuilderpredict_sentimentanalysis` |
| Extract entities | `aibuilderpredict_entityextraction` |
| Extract key phrases | `aibuilderpredict_keyphraseextraction` |
| Detect language | `aibuilderpredict_languagedetection` |
| Classify text | `aibuilderpredict_textclassification` |
| Recognize text (OCR) | `aibuilderpredict_textrecognition` |
| Translate text | `aibuilderpredict_texttranslation` |
| Process documents | `aibuilderpredict_documentscanning` |
| Process invoices | `aibuilderpredict_invoiceprocessing` |
| Process receipts | `aibuilderpredict_receiptprocessing` |
| Read business cards | `aibuilderpredict_businesscard` |
| Read ID documents | `aibuilderpredict_identitydocument` |
| Detect objects | `aibuilderpredict_objectdetectionproposal` |
| Describe image | `aibuilderpredict_imagedescription` |
| Run GPT prompt | `aibuilderpredict_gptpromptengineering` |

**Example — Sentiment analysis action:**
```json
{
  "Analyze_Sentiment": {
    "type": "OpenApiConnection",
    "inputs": {
      "host": {
        "connectionName": "shared_commondataserviceforapps",
        "operationId": "aibuilderpredict_sentimentanalysis",
        "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
      },
      "parameters": {
        "item/requestv2/language": "en",
        "item/requestv2/text": "@outputs('Get_Record')?['body/fieldname']"
      },
      "authentication": "@parameters('$authentication')"
    },
    "runAfter": { "Get_Record": ["Succeeded"] }
  }
}
```

### Expression Reference

| Expression | Example | Notes |
|-----------|---------|-------|
| Trigger field | `@triggerOutputs()?['body/fieldname']` | Access trigger row fields |
| Loop item field | `@items('Apply_to_each')?['fieldname']` | Inside Foreach |
| Action output | `@outputs('Action_Name')?['body/fieldname']` | Previous action result |
| Concat | `@{concat(value1, '_', value2)}` | String concatenation |
| UTC now | `@utcNow()` | Current UTC datetime |
| Format date | `@formatDateTime(utcNow(), 'yyyy-MM-dd')` | Date formatting |
| Coalesce | `@coalesce(value1, 'default')` | First non-null |
| If expression | `@if(equals(value, 'X'), 'yes', 'no')` | Inline conditional |
| Int conversion | `@int(value)` | String to integer |
| Length | `@length(collection)` | Array/string length |

### Entity Set Names (Plural)

The `entityName` parameter in actions uses the **plural entity set name**, not the logical name:
- `mcpd_payment` → `mcpd_payments`
- `mcpd_batchheader` → `mcpd_batchheaders`
- `mcpd_remittance` → `mcpd_remittances`
- General rule: append `s` to the logical name (but check via `dataverse_get_table` if unsure)

### Primary Key Format

Primary keys follow: `{logical_name}id`
- `mcpd_payment` → `mcpd_paymentid`
- `mcpd_batchheader` → `mcpd_batchheaderid`

---

## Execution Workflow

```
1. Connection setup pipeline:
   a. Check for existing connection reference for needed connector(s)
   b. If none → create connection reference (with solution_name for dynamic prefix)
   c. Check for valid connection in environment (list_connections)
   d. If none → guide user to create manually in Power Apps UI
   e. Share connection with SP (share_connection)
   f. Wire connection to reference (update_connection_reference)

2. For each flow story from ADO:
   a. Read story Implementation Details
   b. Pick matching template
   c. Replace placeholders (table names, field names, filter values, connection ref logical name)
   d. JSON.stringify the completed template
   e. dataverse_create_flow(name, clientdata, solution_name)
   f. dataverse_activate_flow(flow_id) — pre-validates connection refs before activation
   g. Verify: dataverse_get_flow(flow_id) → check state=Activated
   h. Update ADO story → Closed
3. dataverse_publish_all after all flows created
```

## Deployment / ALM

When deploying flows across environments (e.g., dev → test → prod):

**What travels with the solution:**
- Flow definition (clientdata referencing connection refs by logical name)
- Connection reference records (logical name, connector_id, display_name)

**What does NOT travel:**
- `connectionid` on connection references (environment-specific, arrives as null)
- Actual connections (OAuth sessions, environment-scoped)

**Post-import steps (automated via MCP tools):**
```
1. list_connection_references → find refs with null connection_id
2. list_connections → find valid connections by connector type
3. For each unwired ref:
   a. Match to a valid connection by connector type
   b. share_connection → grant SP access
   c. update_connection_reference → wire connection_id
4. activate_flow for each flow in the solution
```

If the target environment has no connections for a required connector, a user must create one manually (one-time setup per connector per environment).

## Validation Checklist

After creating each flow:
- [ ] `dataverse_get_flow` returns state=Activated
- [ ] Trigger entity/field names match actual table schema
- [ ] Action entity set names are correct (plural)
- [ ] Connection reference logical name is valid
- [ ] Filter expressions use correct field values (especially picklist integer values, not labels)

## Lessons Learned

- `clientdata` is a **full replacement** on update — always get the current definition before modifying
- Flows are created in **Draft** state — must explicitly activate
- Activated flows **must be deactivated** before updating clientdata
- Picklist filters use **integer values** (e.g., `100000001`), not label strings (e.g., `"Trigger Flow"`)
- `filteringattributes` is comma-separated column logical names — typos here mean the trigger never fires
- Entity set names in actions are **plural** — using singular names causes silent failures
- `runAfter: {}` means "run immediately" (no dependencies) — the first action in a sequence uses this
- For chained actions, `runAfter: { "Previous_Action": ["Succeeded"] }` creates the sequence
- Connection references must exist and be **active** in the environment before the flow can activate
- Service principals CANNOT create connections (`InvalidUserPlan`) — connections require user accounts with Power Apps licenses
- Connection references arrive with `connectionid = null` after solution import — must be remapped per environment
- `activate_flow` now pre-validates connection references — blocks activation with diagnostics if any refs are unwired
- To share a connection with the SP: `share_connection` must be called BEFORE `update_connection_reference` — the Dataverse `PreValidateConnectionReferenceUpdate` plugin checks caller permissions on the connection
- Connection reference `logical_name` is the stable cross-environment identifier — flows reference it in `clientdata`, it stays the same across dev/test/prod
- `create_connection_reference` now resolves publisher prefix dynamically from `solution_name` — no hardcoded prefixes
- For non-Dataverse connectors, always use `list_connector_operations` to discover the correct operationId and parameter schema before building actions — never guess
- AI Builder actions use virtual operationIds (`aibuilderpredict_{templatename}`) that are NOT in the connector swagger — derive from `msdyn_aitemplates.msdyn_uniquename` lowercased
- AI Builder parameters use flat paths (`item/requestv2/text`) not nested objects — uses the same Dataverse connection reference
