# Flow Coverage Assessment (Salesforce Production Flows + Apex Metadata)

**Date:** 2026-04-23  
**Scope:** Assess whether the current NexusCRM Flow design/engine can carry the company’s production Salesforce Flows and (parts of) Apex-based behavior. Identify ceilings and hard blockers.

**Inputs:**
- Salesforce Flow metadata dump (not committed to this repo): `*.flow-meta.xml` exports (211 files)
- NexusCRM Flow design overview: `docs/design/flow/flow-system-overview.md`
- Flow engine gap analysis: `docs/design/flow/flow-engine-gap-analysis.md`
- PR #1786 direction (Lead Convert): comment `pull/1786#issuecomment-4300706443`
- Flow module issue clusters (umbrella): `issue #1825` (2026-04-23 triage)
- TiDB Tooling metadata tables: `apexclass`, `apextrigger` (queried using local `MetaDrivenPlatform4AI/.env`)

---

## 0. Provenance: where the `*.flow-meta.xml` names come from

This repo intentionally does **not** commit the production Salesforce Flow XML exports.
The flow file names referenced below (e.g. `Something.flow-meta.xml`) come from an internal
metadata export directory that is kept outside the repository.

That dump is intentionally kept out of the OSS-ish repo because:

- it can contain tenant-specific identifiers and business rules
- it can include proprietary Flow definitions and action names

### Reproducing the inventory locally

If you have the dump directory locally, you can reproduce the basic counts used by this
assessment with a one-liner:

```bash
FLOW_XML_DIR=/absolute/path/to/salesforce_flows python3 - <<'PY'
import os
from pathlib import Path
import re, collections

root_str = os.environ.get("FLOW_XML_DIR", "").strip()
if not root_str:
    raise SystemExit("FLOW_XML_DIR is required (path to a directory containing *.flow-meta.xml)")

root = Path(root_str)
if not root.exists() or not root.is_dir():
    raise SystemExit(f"FLOW_XML_DIR is not a directory: {root}")

files = sorted(root.glob("*.flow-meta.xml"))
proc = re.compile(r"<processType>([^<]+)</processType>")
c = collections.Counter()
for p in files:
    m = proc.search(p.read_text(errors="ignore"))
    c[m.group(1).strip() if m else ""] += 1
print("files:", len(files))
print("processType counts:")
for k,v in c.most_common():
    print(f"  {k or '(missing)'}: {v}")
PY
```

---

## 0.1 Representative XML examples (sanitized)

To avoid relying on non-committed XML, this section includes minimal, **synthetic** examples
showing the exact shapes that drive the “blocker vs migratable vs supported” decisions.

### Example A: Record-triggered Scheduled Path (hard blocker without `start.scheduledPaths`)

```xml
<Flow>
  <processType>AutoLaunchedFlow</processType>
  <start>
    <object>Lead</object>
    <triggerType>RecordAfterSave</triggerType>
    <recordTriggerType>Create</recordTriggerType>
    <scheduledPaths>
      <label>Run later</label>
      <offsetNumber>1</offsetNumber>
      <offsetUnit>Minutes</offsetUnit>
      <timeSource>RecordTriggerEvent</timeSource>
      <connector><targetReference>SomeAction</targetReference></connector>
    </scheduledPaths>
  </start>
</Flow>
```

### Example B: Workflow / Process Builder export (migratable; does not dispatch as-is)

```xml
<Flow>
  <processType>Workflow</processType>
  <!-- no <start> element -->
  <processMetadataValues>
    <name>ObjectType</name>
    <value><stringValue>Opportunity</stringValue></value>
  </processMetadataValues>
  <!-- record refs often appear as myVariable_current / myVariable_old -->
</Flow>
```

### Example C: Apex invocable action call (hard blocker without an Apex replacement strategy)

```xml
<Flow>
  <actionCalls>
    <actionType>apex</actionType>
    <actionName>SomeInvocableApexHandler</actionName>
    <inputParameters>
      <name>RecordIds</name>
      <value><elementReference>$Record.Id</elementReference></value>
    </inputParameters>
  </actionCalls>
</Flow>
```

### Example D: Approval-style submit action (hard blocker without `actionType=submit`)

```xml
<Flow>
  <actionCalls>
    <actionType>submit</actionType>
    <actionName>Submit_for_Approval</actionName>
  </actionCalls>
</Flow>
```

### Example E: TextTemplate usage (hard blocker unless templates are rendered)

```xml
<Flow>
  <textTemplates>
    <name>EmailBody</name>
    <text>Hi {!$Record.Owner.Name}, ...</text>
  </textTemplates>
</Flow>
```

## 1. Conclusions (TL;DR)

### 1.1 Can the 211 Salesforce Flows be implemented on the current Flow engine?

**Mostly yes for “backend automation” Flows (record-triggered + scheduled + invocable), but there are:**

- **23 hard blocker flows** (missing engine/action capabilities; cannot be made equivalent without extending the substrate)
- **53 legacy flows that require migration/rewriting** (otherwise they import but never trigger / never run)

**Hard blockers (23):**
- Screen Flows (need UI interaction / Screen handler): 8
- Record-triggered Scheduled Paths (`start.scheduledPaths`): 10
- TextTemplate rendering: 4
- `actionType=submit` (approval-like submission): 4
- `actionType=apex` (Apex invocable action): 3
- `actionType=customNotificationAction`: 1

**Requires migration/rewriting (53):**
- `processType=Workflow`: 48 (all have **no `<start>`**, typical Process Builder export)
- `processType=InvocableProcess`: 5 (all have **no `<start>`**; callable-only semantics)

> Note: this is a capability-level assessment. It does not depend on DB latency. Issues like TiDB Cloud RTT timeouts (e.g. #1817) affect verification methodology, not whether the semantic substrate exists.

### 1.2 How much Apex (ApexTrigger / ApexClass) can Flow cover?

Flow can cover a large class of Apex behavior—especially **trigger-style CRUD automation** (field normalization, validation, branching, loops, cross-object lookups, record create/update). But Flow does **not** cover these Apex classes without adding new substrate:

- External callouts / integrations (HTTP)
- Complex transaction semantics (savepoints/rollback, multi-phase error recovery)
- Approval submission (Approval.process / “submit”) unless `actionType=submit` exists
- Salesforce platform-only behaviors (e.g. `DMLOptions.assignmentRuleHeader`) unless NexusCRM provides a metadata-driven equivalent
- UI interactions (Screen Flow, Aura controller, Visualforce controller)

---

## 1.3 Crosswalk: how this assessment relates to issue #1825 (umbrella triage)

Issue #1825 groups ~60 open `module/flow` issues into clusters (A–L) and proposes PR “waves”.
This document complements it from a **production-metadata** viewpoint:

- #1825 is “issues-first” (root-cause + landing zone).
- this doc is “metadata-first” (what the imported/real flow definitions actually require).

The two should converge on the same substrate gaps. When they diverge, treat it as a signal:
either (a) the issue list is missing a production blocker, or (b) the metadata dump includes
flows that are not currently expected to be runnable in NexusCRM.

### Cluster-to-gap mapping (high-signal)

| #1825 cluster | What it covers | What we see in production metadata | Status from this doc |
|---|---|---|---|
| A (Trigger dispatch) | Trigger subscription + FlowDefinition trigger fields NULL | 48 `Workflow` + 5 `InvocableProcess` have no `<start>`; require migration so they can dispatch | **Requires migration** (53 flows) |
| B (Operator/filter eval) | booleanValue=false handling, start filters, etc. | Operators include `IsBlank` (32 uses), `IsNull` (390), `IsChanged` (42) | **Supported but correctness-sensitive** |
| C (AfterSave chain) | AfterSave deadlocks + follow-ups + tests | Many flows use RecordAfterSave + recordUpdate; correctness depends on tx model | **Supported but regression-prone** |
| D (Scheduled/Async) | scheduledPath failures + scheduled paths feature (#1464) | 10 flows use `start.scheduledPaths` | **Hard blocker** (10 flows) |
| E (Cross-object resolver) | multi-level + polymorphic reference resolution | 28 flows contain `__r.` segments | **Supported but needs hardening** |
| F (Missing engine features) | formulas, TextTemplate, `$Record__Prior`, etc. | 36 flows use `<formulas>`; 4 use TextTemplate; 1 uses PRIORVALUE; scheduledPaths present | **Mixed** (formulas OK; TextTemplate / scheduledPaths blockers) |
| G/H (Builder) | frontend bugs + missing UI features | Not in metadata dump per se, but blocks authoring/editing | **Blocks admin authoring** (not runtime) |
| I (Flow list/CRUD) | Tooling field mapping gaps | Needed for Flow management UX and fidelity | **Blocks UX/roundtrip**, not engine semantics |
| L (Tenant/config) | tenant-specific flow misconfig | Some flows clearly tenant-specific | Must remain within tenant quarantine rules |

### How #1825 “recommended waves” relate to production blockers

From a “what breaks real flows” angle, the biggest direct blockers in this metadata dump align to:

- **Wave 1 (A)**: required to make 53 legacy flows dispatch at all
- **Wave 2 (B1)**: correctness of boolean `false` impacts both in-memory and SOQL-builder paths
- **Wave 4 (E1)**: 2-level + polymorphic refs are common in non-trivial flows
- **Wave 5 (I1)**: without correct MasterLabel/trigger fields, the management UI lies even if the engine works

But **the metadata dump also reveals a substrate-level blocker that is “feature-sized”**:

- `start.scheduledPaths` (10 flows) — maps to #1825 D/#1464 (P0).

---

## 2. Salesforce Flow Inventory: capability stats (211 `*.flow-meta.xml`)

### 2.1 `processType` distribution (from `<processType>`)

- `AutoLaunchedFlow`: 149
- `Workflow`: 48 (**all without `<start>`**)
- `Flow`: 9 (8 of these contain Screen elements)
- `InvocableProcess`: 5 (**all without `<start>`**; typically invoked by other flows via `actionType=flow`)

### 2.2 High-signal element/action usage

This section only lists constructs that materially determine “engine completeness” and “ceiling”.

**(A) Mainstream constructs that are supported or largely supportable**
- Decision / Assignment / Loop / RecordLookup / RecordUpdate / RecordCreate: heavily used (core automation shape)
- Operators: `IsNull`, `IsBlank`, `IsChanged`, etc.
  - Caveat: there can still be path-specific semantic bugs (e.g. scheduled start-filter inversion for `IsBlank booleanValue=false`)
- Flow `<formulas>`: used by 36 flows
  - Formula functions observed include `IF/TEXT/LEN/FIND/RIGHT/TODAY/NOW/NOT/ADDMONTHS/INCLUDES/PRIORVALUE`
  - These are not considered blockers because the repo already contains a formula engine and Flow formula wiring.
- action types `emailAlert` / `emailSimple` / `flow`: frequently used; engine must continue to support these action dispatch paths and their metadata dependencies.

**(B) Missing constructs that currently block equivalence**
- Screen elements (8 flows)
- `start.scheduledPaths` (10 flows)
- `actionType=submit` (4 flows)
- `actionType=apex` (3 flows)
- TextTemplate resources (4 flows)
- `actionType=customNotificationAction` (1 flow)

---

## 3. Hard blocker flows (23) and why they block

> “Hard blocker” means: without changing business semantics, the current engine/action surface cannot execute these flows equivalently.

### 3.1 Screen flows (8)

The executor does not implement Screen element handling, so these are effectively “frontend-driven interactions” and cannot be completed end-to-end by the backend FlowExecutor.

- `Lead_Partner_Change_Owner_To_Sales.flow-meta.xml` (Screen + TextTemplate)
- `Opportunity_Partner_Attribution_Checkbox.flow-meta.xml`
- `Opportunity_Partner_Attribution_Checkbox_Show.flow-meta.xml`
- `POC_Approval_Submit_Flow.flow-meta.xml` (Screen + submit)
- `Partner_Lead_Reject.flow-meta.xml`
- `Quote_Fields_Update.flow-meta.xml`
- `Quote_Submit_for_Approval_New.flow-meta.xml` (Screen + submit)
- `Submit_for_Approval.flow-meta.xml` (Screen + submit)

### 3.2 Record-triggered scheduled paths (10)

If a flow’s Start has only scheduledPaths (no immediate connector), then without scheduledPaths support **nothing runs** (example: `lead.flow-meta.xml`).

- `Account_Related_Leads_Based_on_Website_Domain_Create.flow-meta.xml`
- `Account_Related_Leads_Based_on_Website_Domain_Update.flow-meta.xml`
- `BN_Account_Related_Leads_Based_on_Website_Domain.flow-meta.xml`
- `Cloud_Registration_Source_from_Org_to_Order.flow-meta.xml`
- `Collection_Amount_and_Payable_Amount_exc_tax.flow-meta.xml`
- `Collection_Amount_and_Payable_Amount_exc_tax_Update.flow-meta.xml`
- `LeadSendemail.flow-meta.xml`
- `Lead_Convert_Partner_Email_Alert.flow-meta.xml` (scheduledPaths + TextTemplate)
- `awsapn__APN_Synchronization_Failure_Notification.flow-meta.xml` (scheduledPaths + customNotificationAction)
- `lead.flow-meta.xml` (scheduledPaths + apex action)

### 3.3 `submit` action (4)

These flows typically map to “approval submission / work item submission” semantics in Salesforce. Without `actionType=submit` support, their meaning is missing.

- `POC_Approval_Submit_Flow.flow-meta.xml`
- `Partner_Account_CM_Approval.flow-meta.xml`
- `Quote_Submit_for_Approval_New.flow-meta.xml`
- `Submit_for_Approval.flow-meta.xml`

### 3.4 Apex invocable action (3)

These flows depend on `actionType=apex`, i.e. calling Apex invocable methods. NexusCRM does not execute Apex, so these must be rewritten to Flow-native record ops + decisions/loops/formulas, or replaced by a metadata-driven server action mechanism.

- `Lead_Assigenment_Rule_AutoMation.flow-meta.xml` → `LeadOwnerAssignmentTriggerHandler`
- `lead.flow-meta.xml` → `LeadOwnerAssignmentTriggerHandler`
- `Close_Won_Create_Renew_Opportunity.flow-meta.xml` → `FlowUtilController`

### 3.5 TextTemplate (4)

TextTemplates are commonly used to build dynamic email/notification content. Without template rendering, even if email sending works, the output is not equivalent.

- `Lead_Convert_Partner_Email_Alert.flow-meta.xml` (scheduledPaths + TextTemplate)
- `Lead_Partner_Change_Owner_To_Sales.flow-meta.xml` (Screen + TextTemplate)
- `Opportunity_stage_and_Elvance_score_Notification.flow-meta.xml`
- `Opportunity_stage_and_Elvance_score_Notification_Schedule.flow-meta.xml`

### 3.6 `customNotificationAction` (1)

- `awsapn__APN_Synchronization_Failure_Notification.flow-meta.xml` (customNotificationAction + scheduledPaths)

---

## 4. Legacy flows that can run after migration (53)

These are not “engine missing features”. The problem is that Salesforce Process Builder exports and InvocableProcess shapes do not match NexusCRM’s record-trigger metadata matching until they are migrated/rewritten.

### 4.1 Workflow (48)

Characteristic: `processType=Workflow` and **no `<start>`**.

Migration typically requires:
- convert to AutoLaunchedFlow
- generate Start/Trigger metadata
- rewrite `myVariable_current` / `myVariable_old` references to `$Record` / `$Record__Old`

### 4.2 InvocableProcess (5)

Characteristic: `processType=InvocableProcess` and **no `<start>`**.

These are usually callable-only subflows. Options:
- keep callable-only and ensure `actionType=flow` can invoke them, or
- convert to an AutoLaunchedFlow-style invocable flow (still invoked via invocable endpoint / subflow) with the minimum entry metadata needed for roundtrip correctness.

The 5 InvocableProcess flows in the dump:
- `Order_Expiration_Alert_15.flow-meta.xml`
- `Order_Expiration_Alert_30.flow-meta.xml`
- `Order_Expiration_Alert_60.flow-meta.xml`
- `Order_Expiration_Alert_7.flow-meta.xml`
- `Order_Expiration_Alert_90.flow-meta.xml`

---

## 5. What PR #1786 requires: Apex/bespoke handler → Flow-native

The PR #1786 comment proposes rebuilding Lead Convert as **seeded standard Flow + subflows**, dispatched by a generic `/actions/standard/:name` handler driven by `_System_StandardAction`.

That direction implies Flow’s ceiling must include:

1. **Metadata introspection from within Flow** (FieldDefinition / Field metadata) to derive anchors dynamically (e.g. fields like `Converted%Id`).
2. **Bulk/loop/decision/update expressiveness** to replace:
   - SOQL-string substrates
   - side registries for relinking children
   - hard-coded caps and bespoke permission flags
3. **Metadata-driven input schema** (no Go structs freezing LeadConvert shape).
4. **Defaults and RecordTypes from metadata**, not Go fallbacks.

Conclusion: this is feasible but it elevates Flow to a core substrate. Therefore the priority becomes closing “substrate gaps” (scheduled paths, submit, TextTemplate, input guards) rather than continuing to add bespoke handlers.

---

## 6. Apex metadata snapshot (from TiDB: `apexclass` / `apextrigger`)

### 6.1 Scale

- `apexclass`: 1539 rows
- `apextrigger`: 72 rows (covers 54 SObjects)

### 6.2 Apex invocable actions directly referenced by flows

Only two Apex invocable `actionName`s are referenced by Flow XML:

1) `LeadOwnerAssignmentTriggerHandler` (referenced by 2 flows)  
In Salesforce, it uses `Database.DMLOptions.assignmentRuleHeader.useDefaultRule=true` to apply the default assignment rule, then updates the lead.

2) `FlowUtilController` (referenced by 1 flow)  
Multi-object logic that reads Opportunity / Opportunity_Product__c / Order and creates/updates a renewal opportunity.

Both are `actionType=apex` blockers. To cover them, either:
- rewrite them into Flow-native metadata (record ops + decisions/loops/formulas), or
- introduce a metadata-driven “server action” mechanism (less Flow-native; still metadata-driven but not fully declarative).

### 6.3 How Lead Convert appears in Apex (in the org metadata)

There are multiple ApexClasses referencing `Database.convertLead`, e.g.:
- `LeadConvertTriggerHandler`
- `BN_CustomConvertLeadController`
- `LeadConvertController`
- `UpdateAccount`
- `APIBillingToSF`
- `POCTriggerHandler`

These typically include:
- SOQL lookups (Account/Contact/RecordType, etc.)
- DML updates (Account updates, RecordType changes, etc.)
- in some cases, approval-related calls

Conclusion: rebuilding Lead Convert as Flow-native can cover most of this logic, but it requires substrate capabilities like metadata-driven schema, metadata-derived defaults, and approval submission.

---

## 7. Recommended next steps (by blocker priority)

1) **Record-triggered Scheduled Paths (`start.scheduledPaths`)**  
Blocks 10 flows directly and is a major Salesforce-compatibility gap.

2) **`submit` action (approval submission)**  
Blocks 4 flows (all approval/screen-adjacent) and is also a key piece for Flow-native Lead Convert.

3) **TextTemplate rendering**  
Blocks 4 flows and is required for equivalence of email/notification content.

4) **Apex-action replacement strategy (requires a decision)**
   - A: fully Flow-native: rewrite `LeadOwnerAssignmentTriggerHandler` and `FlowUtilController` as Flow metadata
   - B: metadata-driven server actions: add an engine mechanism like `actionType=server` with metadata-defined inputs/outputs

5) **Screen Flow execution strategy (define the boundary)**
   - If backend must run Screen flows end-to-end, you need a UI runtime (out of current scope)
   - If Screen flows are only a frontend orchestration layer, split side effects into subflows/auto-launched flows and keep the backend focused on invocable execution + resume + validations

6) **Scheduler timezone policy**
Not necessarily a blocker, but a correctness risk: scheduled flows can run at the wrong wall-clock time if UTC/local comparisons are inconsistent.

---

## 8. Suggested tracking: add “coverage tags” to #1825 waves

If we want the issue tracker to reflect the production-metadata view, here’s a lightweight suggestion:

- For each #1825 wave PR, add a short note in the PR body:
  - which production features it unblocks (e.g. “unblocks Workflow migration dispatch”)
  - which hard-blocker category it reduces (scheduledPaths / submit / TextTemplate / apexAction / Screen)

This prevents a common failure mode: closing many issues while still leaving a small number of
production-critical flows blocked by one missing substrate feature.
