---
name: fluent-workflow-analyzer
description: Analyze Fluent Commerce workflow JSON definitions. Map status graphs, trace event chains, detect orphaned rulesets, validate trigger coverage, and generate dependency reports. Triggers on "analyze workflow", "workflow analysis", "workflow graph", "workflow dependencies", "workflow review".
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
argument-hint: <workflow-file-or-name> [--deep] [--mermaid] [--explain-rules] [--output <path>]
cursor-tier: manual
---

# Workflow Analyzer

Analyze existing Fluent Commerce workflow definitions to understand structure, trace event chains, detect issues, and generate comprehensive reports.

## Ownership Boundary

This skill owns workflow **analysis and visualization** — dependency mapping, Mermaid diagrams, process flow classification, connection topology, and deep structural assessment.

**Structural validation** (pass/fail gate with labeled checks S01-X06) is owned by `/fluent-workflow-validate`. Run validation first, then use this skill for deeper analysis.

Other skills:
- Workflow creation and editing: `/fluent-workflow-builder`
- Operational workflow commands: `/fluent-workflow`
- Runtime event tracing: `/fluent-trace`
- Structural validation (hard gate): `/fluent-workflow-validate`

## Pre-flight: Load Domain Model

Before analyzing, read the entity hierarchy:

```
knowledge/platform/domain-model.md
```

Use it to:
- Identify root vs sub-entity types within a workflow (e.g., ORDER workflow contains FULFILMENT, ARTICLE, WAVE sub-entities)
- Validate that rulesets reference correct entity types per the hierarchy table
- Annotate which entity types in a multi-entity workflow are orchestrated together vs need separate workflows

## Pre-Check: Load Source Analysis

Before analyzing, check if `/fluent-custom-code` analysis artifacts exist:

```
accounts/<PROFILE>/analysis/code/source-map.json
accounts/<PROFILE>/analysis/code/workflow-rule-map.json
```

If found, use them to:
- Annotate workflow graph nodes with rule descriptions from `source-map.json`
- Identify which rules are custom vs core (custom rules appear in `source-map.json`)
- Report test coverage per rule from `source-map.json` → `modules[].rules[].hasTest`

If not found, analyze workflow structure only (no source-level annotations). Treat artifact-gate failures as "not found" (for example missing required files, missing hashes, or blocking `missingSources` in `constraints.json`).

## When to Use

- Understanding an unfamiliar workflow before modifying it
- Auditing a workflow for structural issues (orphans, dead-ends, unreachable statuses)
- Mapping the complete event chain from entity creation to terminal states
- Generating Mermaid diagrams for workflow walkthroughs and handovers
- Explaining which rules/rulesets run, what each consumes, and what each emits
- Generating documentation for a workflow's state machine
- Pre-deployment validation beyond basic JSON syntax
- Comparing workflow complexity across entity types

## Analysis Framework

### Step 1: Load and Parse

Load the workflow JSON (from file or download via `/fluent-workflow`):

```
1. Read the workflow JSON file
2. Extract metadata: name, version, entityType, entitySubtype, retailerId
3. Separate statuses by entityType (ORDER vs FULFILMENT vs ARTICLE etc.)
4. Index rulesets by name and type
```

### Step 2: Build Status Graph

Map all status transitions by tracing `SetState` rules:

```
For each ruleset:
  1. Find all SetState rules → extract target status
  2. Find all triggers → extract source statuses
  3. If no triggers → ruleset fires on named event (ruleset name = event name)
  4. Map: source_status → [event_name] → target_status
```

**Output format — Status Transition Table:**

```
Source Status          Event/Ruleset              Target Status
-------------------------------------------------------------
(new entity)          CREATE                     CREATED
CREATED               ProcessOrder               ON_VALIDATION
ON_VALIDATION         ConfirmValidation          IN_PROGRESS
IN_PROGRESS           OrderComplete              COMPLETE
BOOKED                OrderCancel                CANCELLED
```

### Step 3: Trace Event Chains

Map cascading events — when one ruleset fires `SendEvent`, trace to the receiving ruleset:

```
For each ruleset:
  For each SendEvent rule:
    1. Extract eventName prop
    2. Find matching ruleset (name == eventName)
    3. Record chain: RulesetA -> SendEvent(X) -> RulesetX
    4. Recurse into RulesetX
```

**Output format — Event Chain:**

```
CREATE
  +-> SendEvent("CheckOrderCoordinates")
       +-> CheckOrderCoordinates
            |-> ForwardIfOrderCoordinatesPresent -> SendEvent("ProcessOrder")
            |    +-> ProcessOrder
            |         +-> SetState(ON_VALIDATION)
            |         +-> SendEvent("FindAndCreateOrderFulfilment")
            +-> ForwardIfOrderCoordinatesNotPresent -> SendEvent("ResolveOrderCoordinates")
                 +-> ResolveOrderCoordinates
                      +-> SendEvent("UpdateOrderCoordinates")
```

### 3A: Event-to-Ruleset Mapping Heuristics

Use these heuristics when mapping runtime events back to workflow intent:

1. **Direct name match (highest confidence)**
   `event.name == ruleset.name` on same entity type/subtype.
2. **Status-trigger fallback**
   If no name match, map by `triggers[].status` against entity status at event time.
3. **Entity scope check**
   Validate `context.entityType` and `context.rootEntityType` alignment before concluding ownership.
4. **SendEvent bridge inference**
   For downstream events, map `SendEvent(eventName)` producer ruleset -> target ruleset named `eventName`.
5. **Audit evidence confirmation**
   Confirm inferred mapping with `ORCHESTRATION_AUDIT` evidence (`category=ruleSet|rule|ACTION|exception`) from `/fluent-trace`.

Confidence levels:
- **Confirmed:** name match + audit evidence
- **Strong inference:** status-trigger + consistent audit timeline
- **Weak inference:** no audit evidence; mark explicitly and avoid hard conclusions

### Step 3B: Mermaid Diagram Output (--mermaid)

When `--mermaid` is requested, generate copy-pasteable Mermaid diagram blocks. Generate **all four diagram types** by default; the user can request a subset. **Validate all blocks against `knowledge/mermaid-syntax-rules.md` rules before output** (no colons in free text, no unicode arrows, quoted special-char labels).

> **Diagram validation:** All Mermaid blocks must pass `knowledge/mermaid-syntax-rules.md` rules before writing to the plan file.

> **Mandatory validation:** Before emitting any Mermaid diagram, validate syntax via `knowledge/mermaid-syntax-rules.md`. State names must not contain spaces or special characters in `stateDiagram-v2`.

#### Diagram 1 — Status State Machine (`stateDiagram-v2`)

Generate one state diagram **per entity type** in the workflow (ORDER, FULFILMENT_CHOICE, FULFILMENT, etc.).

**Generation algorithm:**
```
For each entity type in the workflow:
  1. Collect all statuses for this entity type from statuses[]
  2. Identify DONE-category statuses (terminal states)
  3. For each ruleset scoped to this entity type:
     a. Find trigger status (from triggers[].status) -> this is the source state
     b. Find all SetState rules -> extract target status
     c. Edge = source -> target, labeled with ruleset name
  4. For rulesets with NO trigger status (event-name match):
     a. Infer source status from the event chain (which ruleset sends this event, what status it runs in)
     b. If ambiguous, label edge with "via {eventName}"
  5. [*] -> CREATED for the initial transition
  6. Terminal states -> [*] for DONE-category statuses
```

**Styling rules:**
- Use `classDef terminal fill:#d4edda` for completed states, `fill:#f8d7da` for cancelled/failed
- Add notes for statuses that have ScheduleEvent rules (timeouts)
- Group subtypes separately if the workflow has subtype-specific rulesets

**Example — ORDER entity:**
```mermaid
stateDiagram-v2
    [*] --> CREATED: CREATE
    CREATED --> ON_VALIDATION: ProcessOrder
    ON_VALIDATION --> IN_PROGRESS: ConfirmValidation
    ON_VALIDATION --> IN_PROGRESS: ValidationGraceExpired
    IN_PROGRESS --> SHIPPED: NotifyFulfilmentShipped
    SHIPPED --> COMPLETED: NotifyFulfilmentDelivered
    IN_PROGRESS --> COMPLETED: NotifyFulfilmentCollected
    CREATED --> CANCELLED: CancelOrder
    ON_VALIDATION --> CANCELLED: CancelOrder
    COMPLETED --> [*]
    CANCELLED --> [*]

    note right of ON_VALIDATION: ScheduleEvent: ValidationGraceExpired (30s)
```

**Example — FULFILMENT entity (with subtype branching):**
```mermaid
stateDiagram-v2
    [*] --> CREATED: CREATE
    CREATED --> READY: ProcessFulfilment
    READY --> PICKPACK: ConfirmPick
    PICKPACK --> SHIPPED: ConfirmShipment
    PICKPACK --> READY_FOR_PICKUP: ReadyForPickup [CC_PFS only]
    SHIPPED --> DELIVERED: ConfirmDelivery
    READY_FOR_PICKUP --> COLLECTED: ConfirmCollection [CC_PFS only]
    DELIVERED --> [*]
    COLLECTED --> [*]
```

#### Diagram 2 — Event Chain Flowchart (`flowchart TD`)

Shows how events cascade through rulesets. Use subgraphs to group by entity type.

**Generation algorithm:**
```
For each ruleset:
  1. Create a node with shape based on trigger type
  2. For each rule in the ruleset:
     a. SendEvent -> event node (circle) -> edge to target ruleset
     b. SetState -> state node (trapezoid)
  3. Group by entity type using subgraph blocks
  4. Cross-entity bridges use dashed arrows with label
```

**Node shapes:**
- `["name"]` — status-triggered ruleset (rectangle)
- `[/"name"/]` — event-triggered ruleset (parallelogram)
- `(("eventName"))` — emitted event (circle)
- `[/STATUS/]` — status change (trapezoid)
- `{{"condition"}}` — gate/decision rule (hexagon)

**Example — Combined flowchart:**
```mermaid
flowchart TD
    subgraph ORDER
        CREATE_RS["CREATE<br/><small>trigger: new entity</small>"]
        CREATE_RS --> evt_validate(("ValidateOrder"))
        evt_validate --> VALIDATE_RS[/"ValidateOrder"/]
        VALIDATE_RS --> state_onval[/ON_VALIDATION/]

        CONFIRM_VAL[/"ConfirmValidation"/]
        CONFIRM_VAL --> evt_process(("ProcessOrder"))
        evt_process --> PROCESS_RS[/"ProcessOrder"/]
        PROCESS_RS --> state_inprog[/IN_PROGRESS/]
        PROCESS_RS --> evt_route(("RouteFulfilmentChoice"))
    end

    subgraph FULFILMENT_CHOICE
        ROUTE_FC[/"RouteFulfilmentChoice"/]
        evt_route --> ROUTE_FC
        ROUTE_FC --> evt_hd(("ProcessHDFulfilmentChoice"))
        ROUTE_FC --> evt_cc(("ProcessCCFulfilmentChoice"))
        evt_hd --> HD_FC[/"ProcessHDFulfilmentChoice"/]
        evt_cc --> CC_FC[/"ProcessCCFulfilmentChoice"/]
    end

    subgraph FULFILMENT
        HD_FC -.->|CreateFulfilment| FUL_CREATE["CREATE"]
        CC_FC -.->|CreateFulfilment| FUL_CREATE
        FUL_CREATE --> FUL_READY[/READY/]
        CONFIRM_PICK[/"ConfirmPick"/] --> FUL_PICKPACK[/PICKPACK/]
        CONFIRM_SHIP[/"ConfirmShipment"/] --> FUL_SHIPPED[/SHIPPED/]
    end

    style HD_FC fill:#fff3cd,stroke:#856404
    style CC_FC fill:#fff3cd,stroke:#856404
```

#### Diagram 3 — Cross-Entity Lifecycle (`flowchart LR`)

Shows the full lifecycle across ALL entity types on a single horizontal timeline. This is the "big picture" diagram for stakeholder walkthroughs and handovers.

**Generation algorithm:**
```
1. ORDER statuses as the main spine (left to right)
2. At each status where child entities are created or events cross entities:
   a. Branch down to FULFILMENT_CHOICE statuses
   b. Branch further down to FULFILMENT statuses
3. Cross-entity events shown as dashed arrows
4. User actions (events with userActions config) shown with bold borders
5. Webhooks shown as external callout nodes
```

**Example:**
```mermaid
flowchart LR
    subgraph Order
        O_CREATED([CREATED]) -->|ProcessOrder| O_ONVAL([ON_VALIDATION])
        O_ONVAL -->|ConfirmValidation| O_INPROG([IN_PROGRESS])
        O_INPROG -->|all fulfilments done| O_SHIPPED([SHIPPED])
        O_SHIPPED -->|all delivered| O_COMPLETE([COMPLETED])
    end

    subgraph FulfilmentChoice
        O_INPROG -.->|RouteFulfilmentChoice| FC_CREATED([FC:CREATED])
        FC_CREATED -->|ProcessHD/CC| FC_ASSIGNED([FC:ASSIGNED])
    end

    subgraph Fulfilment
        FC_ASSIGNED -.->|CreateFulfilment| FUL_CREATE([FUL:CREATED])
        FUL_CREATE --> FUL_READY([READY])
        FUL_READY -->|ConfirmPick| FUL_PICK([PICKPACK])
        FUL_PICK -->|Ship| FUL_SHIPPED([SHIPPED])
        FUL_SHIPPED -->|Deliver| FUL_DELIVERED([DELIVERED])
        FUL_PICK -->|CC path| FUL_RFP([READY_FOR_PICKUP])
        FUL_RFP -->|Collect| FUL_COLLECTED([COLLECTED])
    end

    FUL_SHIPPED -.->|NotifyShipped| O_SHIPPED
    FUL_DELIVERED -.->|NotifyDelivered| O_COMPLETE

    style O_COMPLETE fill:#d4edda
    style FUL_DELIVERED fill:#d4edda
    style FUL_COLLECTED fill:#d4edda
```

#### Diagram 4 — Rule Dependency Graph (`flowchart TD`)

Shows what each rule in a ruleset consumes (settings, event attributes, entity fields) and produces (state changes, events, mutations). Use `--deep` to generate for all rulesets, otherwise generate for a specific ruleset on request.

**Generation algorithm:**
```
For a specific ruleset:
  1. For each rule, look up metadata via plugin.list (see Step 3D)
  2. From rule props, identify: settings referenced, event attributes consumed
  3. From rule metadata, identify: events produced, mutations performed
  4. Create nodes: settings (cylinder), events (circle), states (trapezoid), rules (rectangle)
  5. Draw edges: setting --> rule --> event/state
```

**Example — single ruleset:**
```mermaid
flowchart TD
    setting_wh[("webhook.order.created")] --> rule_webhook["SendWebhookWithDynamicAttributes"]
    rule_webhook --> ext_call>"External webhook POST"]
    rule_upsert["UpsertAttribute"] --> attr["statusHistory attribute"]
    rule_setState["SetState"] --> state[/ON_VALIDATION/]
    rule_schedule["ScheduleEvent"] --> timer["ValidationGraceExpired (30s)"]
    rule_sendEvt["SendEvent"] --> evt(("ValidateOrder"))

    style setting_wh fill:#e2e3f1
    style ext_call fill:#fff3cd
    style timer fill:#fce4ec
```

### Step 3C: Rule Execution Narrative (--explain-rules)

When `--explain-rules` is requested, produce a detailed execution narrative per ruleset. This answers "what happens when this event fires?" in plain language with verified data.

#### Rule Lookup Strategy (MANDATORY — never guess what a rule does)

To explain a rule, you MUST look it up. Use these sources in priority order:

**1. Live rule registry — `plugin.list` (covers ALL deployed rules):**
```
plugin.list({ name: "RuleShortName" })
```
Returns: description, parameters (with types/defaults), accepted entity types, produced events, exceptions. Extract short name from `ACCOUNT.bundle.RuleName` -> search for `RuleName`.

**2. Custom rule source code (if cloned under SOURCE/backend/):**
```
accounts/<PROFILE>/SOURCE/backend/<repo>/src/main/java/.../<RuleName>.java
```
Look for `@RuleInfo`, `@ParamString`/`@ParamInteger` annotations, `run()` method logic, GraphQL queries/mutations, `sendEvent()` calls.

**3. Reference module JARs (for core/order/fulfilment module rules):**
If `plugin.list` returns a description but you need implementation details for a reference module rule (core, order, fulfilment, commonv2, globalinventory), the user can provide the deployed JAR. Decompile it to inspect the rule's actual logic:
```
# Get JAR from Fluent module artifacts or ask user to supply it
# Decompile with cfr, procyon, or IntelliJ's built-in decompiler
java -jar cfr.jar <module>.jar --outputdir decompiled/
# Then search for the rule class — use the Glob tool with pattern
# decompiled/**/*RuleName* instead of shell find for cross-platform compatibility
```

This is useful when `plugin.list` gives you "what" but you need "how" — the actual algorithm, edge cases, and failure modes. Store decompiled output under `accounts/<PROFILE>/SOURCE/backend/.decompiled/<jar-basename>/`.

**4. Cached analysis artifacts (from `/fluent-custom-code`):**
```
accounts/<PROFILE>/analysis/code/source-map.json -> modules[].rules[]
```

**5. Name pattern inference (LAST RESORT — always label as inferred):**
Only if all above fail. Infer from name: `*SetState` -> state change, `*SendEvent` -> emit event, etc. Always mark as `(inferred from name — not verified)`.

#### Narrative Format

For each ruleset, produce:

**1. Header:**
```
### [RulesetName] — {entityType} @ {triggerStatus or "event: {eventName}"}
{One-sentence business summary of what this ruleset accomplishes}
```

**2. Rules table (descriptions from plugin.list, NOT invented):**

| # | Rule | Description | Props | Reads | Produces |
|---|------|-------------|-------|-------|----------|
| 1 | `ACCT.bundle.RuleName` | *{from plugin.list}* | `prop=value` | event.attr.X | SetState(STATUS) |
| 2 | `ACCT.core.SendEvent` | *Sends named event* | `eventName=Foo` | — | Event: Foo |

**3. Data flow summary:**
```
Inputs:  event.attributes.items, setting:fulfilment.pick.config
Actions: Update item quantities, transition to PICKPACK
Outputs: SendEvent(RequestShipment), SendEvent(ReadyForPickup)
```

**4. Failure modes (from rule metadata + props):**
```
- Missing setting "fulfilment.pick.config" -> SettingNotFoundException
- Event attribute "items" empty -> rule skips (no error)
- Entity not in expected status -> ruleset no-match (silent skip)
```

#### Full Walkthrough Example

```markdown
### CREATE — ORDER @ new entity
Initializes a new order: ensures product variants exist, records status history,
fires webhook to external system, and routes to validation.

| # | Rule | Description | Props | Produces |
|---|------|-------------|-------|----------|
| 1 | `*.CreateMissingVariantProducts` | Creates variants that don't exist for order items | `productCatalogueRef=PC:MASTER:2` | Product mutations |
| 2 | `*.UpdateStatusHistory` | Records status transition in entity attributes | — | Attribute upsert |
| 3 | `*.SendWebhookWithDynamicAttributes` | POSTs to URL from settings | `settingName=webhook.order.created` | HTTP POST |
| 4 | `*.SendEvent` | Sends named event | `eventName=ValidateOrder` | Event: ValidateOrder |

Data flow: New order -> product variants ensured -> status logged -> webhook fired -> ValidateOrder emitted.
```

### Step 3D: Rule Resolution (Understanding What a Rule Does)

When you encounter a rule in a workflow and need to understand its behavior, follow this resolution chain in order:

#### 1. Parse the Rule Name

Rule names follow `<ACCOUNT>.<MODULE>.<RuleName>`. The module segment tells you the origin:

| Module | Origin | Example |
|--------|--------|---------|
| `core` | Fluent platform core module | `ACCT.core.SetState`, `ACCT.core.SendEvent` |
| `order`, `fulfilment` | Fluent reference modules | `ACCT.order.SendEventForAllFulfilments` |
| `commonv2`, `globalinventory` | Fluent shared/inventory modules | `ACCT.globalinventory.ValidateIncomingProduct` |
| `reporting` | Fluent reporting module | `ACCT.reporting.SetState` |
| Any other name | **Custom extension module** | `ACCT.myextensions.CreateFulfilmentFromSourcingLocation` |

#### 2. Query the Live Rule Registry (`plugin.list`)

The `plugin.list` MCP tool returns the live registry of every rule deployed on the platform. This is the fastest way to understand any rule:

```
plugin.list({ name: "CreateFulfilmentFromSourcingLocation" })
```

**Returns:**
- `description` — what the rule does in plain language
- `parameters[]` — configurable props (`name`, `description`, `type`) that map to the `props` object in workflow JSON
- `ruleInfo.accepts[]` — entity types the rule works with
- `ruleInfo.produces[]` — events the rule can emit
- `ruleInfo.ruleException[]` — exceptions it can throw

**Example response (live from MY_PROFILE):**
```json
{
  "name": "CreateFulfilmentFromSourcingLocation",
  "description": "Creates a Fulfilment at a location dynamically sourced from a configurable JSON path.",
  "parameters": [
    { "name": "sourcingLocationPath", "description": "JSON path to extract location ref", "type": "STRING" },
    { "name": "fulfilmentType", "description": "Fulfilment type (default: HD)", "type": "STRING" },
    { "name": "fulfilmentRefPrefix", "description": "Static fallback prefix (default: FUL)", "type": "STRING" }
  ]
}
```

This tells you: the `props` in the workflow JSON (`sourcingLocationPath`, `fulfilmentType`, etc.) map directly to these parameters.

#### 3. Read Source Code or Decompiled JAR

For **any** rule where you need implementation details (not just the description from `plugin.list`), look at the actual code:

**Custom extension modules** — original source:
```
accounts/<PROFILE>/SOURCE/backend/<repo>/src/main/java/.../CreateFulfilmentFromSourcingLocation.java
```

**Reference modules** (core, order, fulfilment, commonv2, globalinventory) — decompiled from JAR:
```
accounts/<PROFILE>/SOURCE/backend/.decompiled/<jar-basename>/com/fluentcommerce/rule/.../SetState.java
```

Reference modules ship as JARs. If the user has placed the JAR under `accounts/<PROFILE>/SOURCE/backend/`, `/fluent-connect` or `/fluent-custom-code` will decompile it. If no decompiled source exists yet, ask the user to provide the module JAR:

> I need to see the implementation of `<ACCOUNT>.order.SendEventForAllFulfilments`.
> This is a reference module rule — the source isn't open, but if you have the JAR
> (e.g., `fc-module-order-<version>.jar`), place it under `accounts/<PROFILE>/SOURCE/backend/`
> and I can decompile it.

**What to look for in source or decompiled code:**
- `@RuleInfo` annotation — description, accepts, produces (mirrors `plugin.list`)
- `@ParamString` / `@ParamInteger` annotations — rule parameters
- `run()` method — the actual execution logic
- GraphQL queries/mutations — what entity data the rule reads or writes
- `context.action().sendEvent()` calls — events it emits at runtime
- Exception patterns — what causes failures

**Confidence by source type:**

| Source | Confidence | Editable? |
|--------|-----------|-----------|
| Original repo source | High | Yes |
| Decompiled JAR | Medium (synthetic var names, missing comments) | No (read-only) |
| `plugin.list` only | Description-level (no implementation detail) | N/A |

Use `/fluent-custom-code` to generate a pre-computed analysis (`source-map.json`) that indexes all rules — both custom and decompiled — with descriptions, parameters, test coverage, and complexity metrics.

#### 4. Check Cached Analysis Artifacts

If `/fluent-custom-code` has been run:

- `source-map.json` -> `modules[].rules[]` — every rule with `className`, `description`, `parameters`, `hasTest`
- `workflow-rule-map.json` — which rules are used in which workflows and rulesets
- `constraints.json` — extension risks and recommendations

#### Resolution Priority

```
plugin.list (live, always available, covers ALL deployed rules)
  | need implementation details?
Original source (custom rules with repos cloned to SOURCE/backend/)
  | no source repo?
Decompiled JAR (reference or custom modules, under SOURCE/backend/.decompiled/)
  | no JAR available?
Ask user to provide JAR -> place under SOURCE/backend/ -> decompile
  | need pre-computed batch analysis?
source-map.json (from /fluent-custom-code, covers both source + decompiled)
  | rule not found anywhere?
Likely undeployed or from a module not installed on this account
```

### Step 4: Detect Issues

Run these checks against the parsed workflow:

#### 4.1 Orphaned Rulesets
Rulesets that can never be reached:
- No status trigger AND
- No other ruleset sends an event matching this ruleset's name

```
For each ruleset R:
  if R has no status triggers:
    if no other ruleset contains SendEvent(R.name):
      WARN: Orphaned ruleset "{R.name}" — never triggered
```

#### 4.2 Dead-End Statuses
Statuses with no outgoing transitions (no ruleset triggers from them):

```
For each status S:
  if no ruleset has trigger.status == S.name:
    if S.category != "DONE":
      WARN: Dead-end status "{S.name}" (category={S.category}) — no transitions out
```

Note: DONE-category statuses are expected terminal states and should NOT be flagged.

#### 4.3 Unreachable Statuses
Statuses that no `SetState` rule targets:

```
For each status S:
  if no ruleset contains SetState(S.name):
    if S.name != "CREATED":  # CREATED is set by platform on entity creation
      WARN: Unreachable status "{S.name}" — no SetState targets it
```

#### 4.4 Missing SendEvent Targets
Events fired by `SendEvent` that have no matching ruleset:

```
For each SendEvent(eventName):
  if no ruleset.name == eventName:
    WARN: SendEvent("{eventName}") has no matching ruleset
```

Note: Some events are intentionally sent to child/parent entities or external systems. Cross-reference with entity types.

#### 4.5 Same-Name Ruleset Collision Analysis
```
For each pair of rulesets (R1, R2) where R1.name == R2.name and R1.type == R2.type:
  key = (type, subtype-or-*, name)

  if key matches and trigger statuses overlap:
    HIGH: Ambiguous same-name rulesets with overlapping trigger scopes
  else if key matches and trigger statuses do not overlap:
    MEDIUM: Same-name multi-trigger pattern (often intentional, review for clarity)
  else if subtype differs:
    INFO: Same-name subtype-partitioned rulesets (usually intentional)
```

Notes:
- Same name with different entity types (ORDER vs FULFILMENT) is normal.
- Same name + same type is not automatically an error; subtype and trigger overlap decide severity.

#### 4.6 Trigger Conflicts
Multiple rulesets with the same status trigger for the same entity type:

```
For each status S:
  rulesets = all rulesets with trigger.status == S AND same type
  if len(rulesets) > 1:
    WARN: Multiple rulesets trigger on status "{S}": {ruleset names}
    (Only the first match fires — order matters)
```

#### 4.7 Rule Pattern Detection

Detect rule types, integration touchpoints, and setting dependencies by static analysis of rule names and props:

**Direct event-name match:**
- Ruleset name = expected event name (primary matching mechanism)
- Example: Ruleset `ConfirmValidation` fires when event `ConfirmValidation` arrives

**Status-trigger match:**
- Rulesets with `triggers: [{ "status": "BOOKED" }]` fire when the entity enters that status
- Check for conflicts: multiple rulesets triggering on the same status (only first match fires)

**Cross-entity event mapping:**
- `SendEvent` rules in one entity type often target rulesets in a different entity type
- Example: ORDER ruleset sends `CreateFulfilment` -> FULFILMENT entity's `CREATE` ruleset fires
- Track: source entity type + event name -> target entity type + matching ruleset

**Rule pattern detection for status transitions:**
```
Status transition rules (use endsWith for namespaced matching):
  - *SetState -> sets entity status
  - *ChangeState -> state change rule
  - *ChangeStateGQL -> GraphQL-based state change
Example: "ACCOUNT.core.SetState" matches via endsWith("SetState")
```

**Webhook pattern detection:**
```
Rules whose names contain (case-insensitive):
  webhook, hook, callback, notify, notification, trigger, publish
Or whose props reference webhook setting names.
Mark these rulesets as integration touchpoints.
```

**Setting dependency detection:**
```
Rules whose props contain keys matching:
  setting, config, param, parameter, option, preference
Extract the setting name from the prop value.
Cross-reference with /fluent-settings to verify settings exist.
```

#### 4.8 Settings Validation (Live Cross-Reference)

Verify that all settings referenced by workflow rule props actually exist in the live environment. Missing settings are the #1 cause of silent rule failures — the event processes as SUCCESS but the rule does nothing, leaving entities stuck.

##### Extraction

Parse all rulesets and extract setting references from rule props:

```
For each ruleset:
  For each rule in ruleset.rules:
    For each prop key/value in rule.props:
      If key matches: setting, settingName, settingRef, settingsKey,
                      webhookSettingName, configSetting, configName
        → Record: { settingName: value, ruleset, rule, propKey }

    If rule.name endsWith "SendWebhook" or "SendWebhookWithDynamicAttributes":
      → Mark as webhook setting (expect LOB valueType with "url" field)

    If rule.name endsWith "UpdateEntityFromSetting" or "GetSettingValue":
      → Mark as config setting
```

Additionally, query `plugin.list` for each rule to discover settings that aren't obvious from prop names:

```
plugin.list({ name: "<RuleShortName>" })
→ Check parameters[].description for mentions of "setting" or "config"
→ Cross-reference parameter names with the rule's props to identify the actual setting name
```

##### Validation

For each extracted setting reference, query the live environment:

```graphql
{
  settings(first: 1, name: ["<SETTING_NAME>"], context: "RETAILER") {
    edges {
      node { id name value lobValue valueType context contextId }
    }
  }
}
```

Check:
1. **Exists** — edges array is non-empty
2. **Value type** — webhook settings should be LOB; simple config should be STRING/BOOLEAN
3. **Value content** — LOB webhook settings should contain a `url` field in `lobValue`
4. **Context scope** — setting context matches rule expectation (usually RETAILER)
5. **Non-empty** — `value` or `lobValue` is not null/empty

##### Report

```
=== Settings Validation: ORDER::HD (v1.50) ===

Ruleset                    Rule                                Setting Ref                  Status
──────────────────────────────────────────────────────────────────────────────────────────────────────
OrderCreatedWebhook        *.SendWebhookWithDynamicAttributes  webhook.order.created        OK (LOB)
OrderCancelledWebhook      *.SendWebhookWithDynamicAttributes  webhook.order.cancelled      OK (LOB)
FulfilmentShippedNotify    *.SendWebhookWithDynamicAttributes  webhook.fulfilment.shipped   MISSING
UpdateOrderConfig          *.UpdateEntityFromSetting           order.update.mapping         MISSING
ConsignmentCreate          *.GetSettingValue                   consignment.prefix           OK (STRING)
CarrierRouting             *.GetSettingValue                   carrier.hd.config            TYPE_MISMATCH
  → Expected: LOB (JSON config), Actual: STRING

Summary: 3/6 valid, 2 MISSING, 1 TYPE_MISMATCH
Action: Create missing settings before go-live → /fluent-settings
```

##### Severity

| Status | Severity | Impact |
|--------|----------|--------|
| OK | None | Setting will be read correctly |
| MISSING | HIGH | Rule will fail silently — entity gets stuck |
| TYPE_MISMATCH | MEDIUM | Rule may read empty value (e.g., reads `lobValue` but type is STRING) |
| EMPTY_VALUE | LOW | Setting exists but value is null/empty — rule behavior depends on implementation |

##### Orphaned Settings Detection

Optionally, compare all live settings against all setting references extracted from workflows:

```
For each setting in the environment:
  If setting.name does NOT appear in any workflow rule prop:
    → ORPHANED (exists but no rule uses it — candidate for cleanup)
```

Report orphaned settings separately. These are low priority but indicate configuration drift.

#### 4.9 Rule Props Audit (Runtime vs Static)

Compare rule props as defined in the workflow JSON against the actual props observed at runtime via ORCHESTRATION_AUDIT events. Drift between static config and runtime evidence indicates workflow version mismatches, environment-specific overrides, or deployment issues.

##### Static Props Extraction

From the workflow JSON, extract all rule props for each ruleset:

```
For each ruleset:
  For each rule in ruleset.rules:
    Record: { rulesetName, ruleName, props: { key: value, ... } }
```

##### Runtime Props Extraction

Query ORCHESTRATION_AUDIT events with `category=rule` for the target entity:

```
event.list({
  "eventType": "ORCHESTRATION_AUDIT",
  "context.rootEntityRef": "<ENTITY_REF>",
  "count": 200
})
```

Filter results where `category == "rule"`. Each rule audit event contains:
- `name` — the rule's short name (e.g., `SendWebhookWithDynamicAttributes`)
- `ruleSet` — the ruleset it belongs to (e.g., `OrderCreatedWebhook`)
- `props` — the actual props object as passed by the workflow engine at execution time

##### Comparison

```
For each rule execution from runtime:
  1. Find the matching rule in the static workflow JSON (by ruleSet + rule name)
  2. Compare static props vs runtime props:
     - MATCH: all keys and values identical
     - EXTRA_PROP: runtime has a prop not in static JSON (injected by platform or module)
     - MISSING_PROP: static JSON has a prop not seen at runtime (stripped or defaulted)
     - VALUE_DRIFT: same key, different value (version mismatch or override)
  3. Flag VALUE_DRIFT as HIGH — this means the deployed workflow differs from the JSON file
```

##### Report Format

```
=== Rule Props Audit: ORDER::HD ===

Ruleset                Rule                                   Prop          Static Value          Runtime Value      Status
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
OrderCreatedWebhook    *.SendWebhookWithDynamicAttributes     setting       webhook.order.created webhook.order.created MATCH
ProcessOrder           *.SetState                             status        ON_VALIDATION         ON_VALIDATION       MATCH
ConfirmShipment        *.SendWebhookWithDynamicAttributes     setting       webhook.ful.shipped   webhook.shipped     VALUE_DRIFT
CreateFulfilment       *.CreateFulfilmentFromSourcingLocation  fulfilmentType HD                   HD                  MATCH
CreateFulfilment       *.CreateFulfilmentFromSourcingLocation  maxRetries    (not in JSON)         3                   EXTRA_PROP

Summary: 4/5 MATCH, 0 MISSING, 1 VALUE_DRIFT, 1 EXTRA_PROP
Action: VALUE_DRIFT on "ConfirmShipment" setting prop → re-download workflow or check deployment version
```

##### Severity

| Status | Severity | Meaning |
|--------|----------|---------|
| MATCH | None | Static and runtime agree |
| VALUE_DRIFT | HIGH | Deployed workflow differs from local JSON — re-download or re-deploy |
| MISSING_PROP | MEDIUM | Prop exists in JSON but was not observed — rule may not have fired, or platform stripped it |
| EXTRA_PROP | LOW | Platform or module injected a default prop not in the JSON — usually harmless |

##### Integration

- Use runtime props data from `/fluent-trace` Step 8A–8F event extraction
- Feed VALUE_DRIFT findings into `/fluent-workflow` for re-download
- Feed MISSING_PROP findings into `/fluent-trace` for investigation of why the rule didn't fire

### Step 5: Generate Report

Produce a structured analysis report:

```
=== Workflow Analysis: ORDER::HD (v1.50) ===

Metadata:
  Entity Type: ORDER
  Entity Subtype: HD
  Retailer ID: 2
  Total Statuses: 29 (ORDER=8, FULFILMENT=11, ARTICLE=5, CONSIGNMENT=5)
  Total Rulesets: 61

Status Graph (ORDER):
  CREATED -> ON_VALIDATION -> BOOKED -> PICK_PACK -> AWAITING_COURIER_COLLECTION -> COMPLETE
                                                                                 -> CANCELLED
                                                                                 -> ESCALATED

Entity Type Breakdown:
  ORDER:      8 statuses, 12 rulesets
  FULFILMENT: 11 statuses, 20 rulesets
  ARTICLE:    5 statuses, 15 rulesets
  CONSIGNMENT: 5 statuses, 14 rulesets

Issues Found:
  [WARN] Orphaned ruleset "UpsertEventAttributes" — no trigger, no SendEvent targets it
  [WARN] Dead-end status "ESCALATED" is category DONE — OK (terminal)
  [INFO] 4 cross-entity SendEvent calls (ORDER -> FULFILMENT events)

Event Chain Depth:
  Longest chain: CREATE -> ... -> OrderComplete (12 hops)
  Deepest cascade: ConfirmValidation triggers 8 downstream rulesets

Rule Module Usage:
  <ACCOUNT>.core: 45 usages (SetState, SendEvent, ScheduleEvent, SendWebhook)
  <ACCOUNT>.order: 38 usages (domain-specific rules)
  <ACCOUNT>.metrics: 20 usages (UpdateStatusHistory, ComputeMetrics)

Process Flow Classification:
  CREATE (1):            CREATE
  USER_ACTION (3):       CancelOrder, ConfirmPick, ConfirmShipment
  INTEGRATION_EVENT (2): PaymentCallback, InventoryUpdate
  SCHEDULED_EVENT (1):   ValidationGraceExpired
  CROSS_WORKFLOW (4):    CreateFulfilment, NotifyFulfilmentShipped, ...
  INTERNAL (51):         ProcessOrder, ConfirmValidation, ...
```

### Step 5A: Process Flow Classification

Classify each ruleset by its role in the business process. This reveals how work enters and flows through the system.

| Flow Type | Detection Rule | Priority |
|-----------|---------------|----------|
| **CREATE** | Ruleset name is exactly `CREATE` | 1 (highest) |
| **USER_ACTION** | Ruleset has `userActions` defined (UI button triggers) | 2 |
| **SCHEDULED_EVENT** | Ruleset is the target of a `ScheduleEvent` rule (has delay/schedule props) | 3 |
| **CROSS_WORKFLOW** | Ruleset receives events from a different workflow type (cross-workflow incoming connection) | 4 |
| **INTEGRATION_EVENT** | Ruleset has no internal or cross-workflow incoming connections (external API/webhook entry point) | 5 |
| **INTERNAL** | All other rulesets — triggered internally via `SendEvent` chains or status triggers | 6 (default) |

**Classification algorithm:**
```
For each ruleset R:
  if R.name == "CREATE" -> CREATE
  else if R.userActions is non-empty -> USER_ACTION
  else if any other ruleset has ScheduleEvent rule with eventName == R.name -> SCHEDULED_EVENT
  else if R has cross-workflow incoming connections -> CROSS_WORKFLOW
  else if R has no incoming connections from within same workflow -> INTEGRATION_EVENT
  else -> INTERNAL
```

**Why this matters:**
- **CREATE** — entity generation rate and initialization quality
- **USER_ACTION** — manual intervention points (bottleneck candidates)
- **SCHEDULED_EVENT** — time-dependent automation (monitor for stale timers)
- **CROSS_WORKFLOW** — inter-workflow dependencies (failure propagation risk)
- **INTEGRATION_EVENT** — external system entry points (webhook health)
- **INTERNAL** — core orchestration logic (event chain depth)

### Step 5B: Strict User Action Contract Audit (MANDATORY)

When a ruleset is classified as `USER_ACTION` (has non-empty `userActions`), validate it against the strict contract. **Any CRITICAL failure means the button will be invisible in the UI — flag prominently.**

| # | Check | Requirement | Severity | Invisible if fails? |
|---|-------|-------------|----------|---------------------|
| UA-A1 | eventName present | `userActions[].eventName` exists and is non-empty string | **CRITICAL** | **YES** — button silently invisible |
| UA-A2 | Ruleset subtype | Ruleset has `subtype` field matching workflow `entitySubtype` | **CRITICAL** | **YES** — button silently invisible |
| UA-A3 | Placement | `userActions` exists at ruleset level (not inside rule props or rules array) | **CRITICAL** | **YES** — silently ignored |
| UA-A4 | Context is array | `context` is an array `[...]` not an object `{...}` | **CRITICAL** | **YES** — runtime error |
| UA-A5 | Context completeness | Every `context[]` entry has ALL 4 fields: `label`, `type`, `modules`, `confirm` | HIGH | Partial — may be invisible or broken |
| UA-A6 | Type value | `context[].type` is exactly `"PRIMARY"` or `"SECONDARY"` (uppercase) | HIGH | YES — invalid type = invisible |
| UA-A7 | Modules non-empty | `context[].modules` is non-empty array | HIGH | YES — no app shows the button |
| UA-A8 | Modules valid values | Module names match the target app's `orchestrationAlias` (NOT manifest setting suffix): `"adminconsole"` (OMS App — manifest is `fc.mystique.manifest.oms`), `"servicepoint"` (Store App — manifest is `fc.mystique.manifest.store`), or custom app's alias. **`"oms"` and `"store"` are NOT valid — both apps use legacy `orchestrationAlias` overrides** | HIGH | YES in target app — wrong app name = wrong app |
| UA-A9 | Confirm is boolean | `context[].confirm` is `true`/`false` (boolean), not `"true"` (string) | MEDIUM | Confirmation dialog broken |
| UA-A10 | Attribute source safe | `attributes[].source` is empty `""` or `"settings.<key>"` (lowercase `settings.` — capital `"Settings."` silently fails). `"OVERRIDE"` works on v4.80+ but safe default is `""`. If `source` starts with `"settings."`: referenced setting MUST have `{ "active": [...] }` wrapper and retailer context must match workflow. | LOW | No — `"OVERRIDE"` works on v4.80+. Original breakage was missing `subtype`, not attribute values |
| UA-A11 | Attribute defaultValue safe | `attributes[].defaultValue` is empty `""` or literal value. `"{{me.username}}"` works on v4.80+ but safe default is `""`. | LOW | No — template expressions work on v4.80+. Original breakage was missing `subtype`. |
| UA-A12 | Attribute shape | `attributes[]` entries include `name`, `type`, `mandatory` | MEDIUM | Dialog broken |
| UA-A13 | Schema discipline | No undocumented keys in user action objects | LOW | No |

**Report format for user action issues:**

For each failing check, report:
```
⚠️ USER ACTION INVISIBLE: [RulesetName] at status [STATUS]
   Issue: [UA-XX] [description]
   Impact: Button "[label]" will NOT appear in [app] UI
   Fix: [specific fix instruction]
```

Group all user action issues in a dedicated **"User Action Audit"** section of the analysis report — do not bury them in general findings.

**Parity check (MANDATORY — not just --deep mode):**

1. If MCP is available, run `workflow.transitions` for each entity type + subtype in the workflow that has user actions.
2. Compare the Transition API response `userActions[]` against the workflow JSON definition.
3. If the workflow JSON has user actions but the Transition API returns empty:
   - Flag as **CRITICAL**: "User action defined in workflow but NOT returned by Transition API — button will be invisible"
   - Run the diagnostic checklist (UA-A1 through UA-A11) to identify the root cause
4. If Transition API returns actions not in the workflow JSON, flag as INFO (may be from a different workflow version).

For canonical contract definitions and remediation, use `/fluent-workflow-builder` and `/fluent-transition-api`.

## Cross-Workflow Connection Analysis

### Connection Types

| Type | Definition | Detection |
|------|-----------|-----------|
| **Internal** | Events within the same workflow (ruleset A -> SendEvent -> ruleset B within ORDER::HD) | SendEvent target entityType matches workflow's own entityType |
| **Cross-Entity** | Events targeting child entities (ORDER workflow -> SendEvent -> FULFILMENT entity) | SendEvent target entityType differs from workflow's entityType |
| **Cross-Workflow** | Events bridging workflow types (ORDER::HD -> FULFILMENT::HD_WH) | Target flexType differs from source flexType; creates new execution thread |

### Detection Pattern

For each SendEvent rule in the workflow:
1. Extract target `entityType` from rule props or event attributes
2. Compare with the workflow's own `entityType`
3. If same -> **internal connection**
4. If different -> **cross-entity connection**
5. Look up target workflow by flexType (entityType::subtype) -> if found, map the bridge -> **cross-workflow connection**
6. If target workflow not found -> potential **orphan connection** (event will get NO_MATCH)

### Cross-Workflow Bridge Report

When analyzing workflows, report bridges between them:

```
ORDER::HD -> FULFILMENT::HD_WH
  Bridge event: CreateFulfilment (sent by CreateFulfilmentFromSourcingLocation rule)
  Source ruleset: ProcessHDFulfilmentChoice
  Target workflow: FULFILMENT::HD_WH (CREATE ruleset)
  Thread boundary: YES (different execution context)
```

## Deep Analysis Mode (--deep)

When `--deep` is specified, additionally:

1. **Cross-workflow analysis** — Download ALL workflows for the retailer and check for cross-entity event consistency (e.g., ORDER workflow sends events that FULFILMENT workflow expects)
2. **Rule module inventory** — List all unique rule names used, grouped by module
3. **User action audit** — List all rulesets with `userActions` defined, map to UI button labels
4. **Attribute flow** — Track attributes set by rules across the event chain
5. **Webhook mapping** — List all SendWebhook rules and their target settings

## Integration with Other Skills

| Task | Skill / Tool |
|------|-------------|
| Download workflow to analyze | `/fluent-workflow` |
| Connection-centric dependency mapping | `/fluent-connection-analysis` |
| Understand what a rule does (live registry) | `plugin.list` MCP tool |
| Read custom rule source code | `/fluent-custom-code` |
| Decompile reference module JARs | `/fluent-custom-code` or manual `cfr`/`procyon` |
| Fix issues found by analyzer | `/fluent-workflow-builder` |
| Deploy fixed workflow | `/fluent-module-deploy` or `/fluent-workflow` |
| Test after deployment | `/fluent-e2e-test` |
| Trace runtime failures | `/fluent-trace` |
| Query available user actions | `/fluent-transition-api` |
| Check rule settings dependencies | `/fluent-settings` |
| Audit/fix missing settings | `/fluent-settings` (remediation for settings gaps found by analyzer) |
| Validate Mermaid diagrams | `knowledge/mermaid-syntax-rules.md` (knowledge file for diagram syntax validation) |

## Learning Capture

This is an analysis skill — follow the **Learning Capture Protocol** (see CLAUDE.md Cross-Skill Conventions). After workflow analysis, when the user confirms or corrects findings about status graph behavior, ruleset intent, or trigger patterns, write the confirmed learning to the `implementation-learnings.md` auto-memory file under the relevant account heading.

## Tips

- **Always download the latest version** before analyzing. Prefer MCP `workflow.list({ retailerId, outputDir: "accounts/<PROFILE>/workflows/<RETAILER>" })` — downloads all workflows to disk in one call, returns only summary metadata (keeps large JSON out of context). On macOS/Linux, `fluent workflow download -w all` also works. On Windows, the CLI fails on `::` filenames — use MCP `workflow.list` with `outputDir` or `workflow.get` with `outputFile`.
- **Check for `workflow-file-map.json`** in the workflow directory. If missing, generate it before analysis:
  ```bash
  cd accounts/<PROFILE>/workflows/<RETAILER_REF>/
  node -e "const fs=require('fs');const f=fs.readdirSync('.').filter(x=>x.endsWith('.json')&&x!=='workflow-context.json'&&x!=='workflow-file-map.json');const m={};f.forEach(x=>m[x.replace('.json','').replace(/__/g,'::')]=x);fs.writeFileSync('workflow-file-map.json',JSON.stringify(m,null,2));console.log(Object.keys(m).length+' entries')"
  ```
  This resolves `ORDER::HD` to `ORDER__HD.json` for downstream processing.
- **Composite workflows** contain multiple entity types (ORDER + FULFILMENT + ARTICLE + CONSIGNMENT) — analyze each entity type's status graph separately
- **SendEvent across entity types** is normal — ORDER rulesets often send events to FULFILMENT entities and vice versa
- **Rule names reveal module ownership** — `<ACCOUNT>.core.*` are platform module rules, `<ACCOUNT>.order.*` are from the order module, custom modules use their own prefix
- **Gate rulesets** (e.g., `SendEventOnVerifyingAllFcStatus`) wait for child entities before advancing parent — trace these carefully
- **Never guess what a rule does** — always use `plugin.list` first, then source/decompiled code if you need implementation details
