---
name: fluent-workflow-deploy
description: Strict anti-hallucination workflow deployment protocol. Uses verified Fluent CLI capabilities, hard preflight gates, and explicit fallback upload paths. Triggers on "deploy workflow", "upload workflow", "push workflow", "workflow deploy".
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
argument-hint: <workflow-file> [--profile <name>] [--retailer <ref>] [--dry-run]
cursor-tier: manual
planning-gate: mandatory
---

# Workflow Deployer (Strict)

Deploy Fluent Commerce workflow definitions with a verification-first protocol that prevents invented commands and enforces safe upload behavior.

## CRITICAL: No `fluent workflow upload` Command

The Fluent CLI has **NO `workflow upload` command**. The only workflow subcommands are:

```
fluent workflow download    # Download workflow JSON
fluent workflow list        # List workflows for a retailer
fluent workflow merge       # Merge fragment into workflow (auto-uploads, buggy)

# MCP alternatives (fluent-mcp-extn):
workflow.get               # Fetch specific workflow by entityType + entitySubtype (works even when CLI returns 401)
workflow.list              # List all workflows for a retailer (paginated, deduped to latest)
```

**To upload a workflow via CLI, you MUST use `fluent module install`** — package the workflow JSON into a temporary module directory and install it. This is not a workaround; it is the designed mechanism. See [Strategy 1](#strategy-1-primary-cli-fluent-module-install) below.

## Pre-flight: Feedback Check

Before starting, check for past learnings from this skill:

1. Check if `accounts/<PROFILE>/feedback/fluent-workflow-deploy.jsonl` exists
2. If yes, read last 20 records
3. Filter to: same entity type/subtype if known, FAILURE or USER_CORRECTED outcomes, confidence = CONFIRMED or PROMOTED
4. Extract top 3 relevant learnings — use as internal "WATCH OUT" guidance during execution
5. Do NOT show raw feedback to the user — silently adjust approach based on past issues

## Non-Negotiable Guardrails

1. Never invent workflow CLI commands or flags.
2. Never claim `fluent workflow upload` exists.
3. Hard-forbidden commands:
   - `fluent workflow upload`
   - `fluent workflow deploy`
   - `fluent workflow push`
   - `fluent workflow apply`
4. If custom rules in the workflow are not present in `plugin.list`, stop deployment and deploy the module first.
5. If `fluent workflow merge` fails with `token:undefined`, do not retry blindly; use fallback upload strategy.

## Planning Gate

### Pre-flight Chain (Three-Level)

Before deploying any workflow, run the three-level pre-flight check:

1. **Spec check:** If `accounts/<PROFILE>/features/<slug>/status.json` exists for this work:
   - Check `spec: "APPROVED"` — If missing, BLOCK: `→ BLOCKED: PREREQ_MISSING — Run /fluent-use-case-discover first`
   - Check `plan: "APPROVED"` — If missing, BLOCK: `→ BLOCKED: PLAN_REQUIRED — Run /fluent-feature-plan first`
   - If both approved, proceed to deployment steps
2. **Standalone deployment** (no feature context):
   - Search `accounts/<PROFILE>/plans/` or `accounts/<PROFILE>/tasks/` for a plan file with `Status: APPROVED`
   - If approved plan found, proceed to deployment steps
   - If no approved plan, write a new plan below

**Before deploying any workflow, write a plan using the template from `PLAN_TEMPLATE.md` in the `fluent-feature-plan` skill.**

**Workflow-deploy specific emphasis -- ensure these are covered:**

1. **Business Context (Section 1)** -- what change this workflow update delivers
2. **Scope (Section 2)** -- workflow name (e.g., `ORDER::HD`), current version -> new version
3. **Rulesets (Section 6)** -- new/modified/removed rulesets with Source classification
4. **Rules Inventory (Section 7)** -- confirm all referenced rules exist in `plugin.list`
5. **Deployment Sequence (Section 17)** -- module deploy BEFORE workflow deploy if new custom rules are referenced
6. **Rollback Plan (Section 18)** -- previous workflow version to restore

**Write the plan to:** `accounts/<PROFILE>/plans/<YYYY-MM-DD>-workflow-deploy-<slug>.md`. Set `Status: PENDING`.

Present the full plan content to the user and wait for approval before uploading. On approval, update the file to `Status: APPROVED`. If the user says "just do it", proceed directly (still write the file for audit trail).

## Ownership Boundary

This skill owns the deployment execution pipeline (validate -> upload -> verify -> rollback).

Workflow structure and editing are owned by `/fluent-workflow-builder`.
Workflow listing and download are owned by `/fluent-workflow`.
Workflow analysis is owned by `/fluent-workflow-analyzer`.

## Module-First Deployment — Check Before Using This Skill

**If the workflow is part of a module** (exists in `resources/workflows/` within a module directory), deploy the entire module via `/fluent-module-deploy` using `fluent module install` instead. Module install deploys workflows, settings, and rules as a single atomic unit with proper versioning.

**Use this skill only for standalone workflow uploads** — workflows not bundled in a module, hotfixes to individual workflows, or when module install is not applicable.

| Scenario | Correct Approach |
|----------|-----------------|
| Workflow is in `resources/workflows/` of a module | `/fluent-module-deploy` → `fluent module install` |
| Workflow changed along with Java rules | `/fluent-build` → `/fluent-module-deploy` (module bundles everything) |
| Standalone workflow JSON not in any module | This skill (`/fluent-workflow-deploy`) |
| Hotfix to a single workflow in production | This skill (targeted upload) |
| Module install excluded workflows (`--exclude workflows`) | This skill (deploy workflows separately) |

## Handoff Protocol

### Signals emitted by this skill

| Signal | Condition | Example |
|--------|-----------|---------|
| `-> READY: <path>` | Workflow deployed successfully | `-> READY: Workflow ORDER::HD v12 deployed to MY_RETAILER` |
| `-> NEXT: /fluent-<skill>` | Ready for E2E testing | `-> NEXT: /fluent-e2e-test` |
| `-> BLOCKED: <reason>` | Deployment blocked | `-> BLOCKED: DEPLOYMENT_BLOCKED — Pre-deploy check verdict is BLOCKED` |
| `-> SKIP: <reason>` | Workflow already at target version | `-> SKIP: Workflow ORDER::HD already at v12` |

### Error codes

| Code | Condition | Recovery |
|------|-----------|----------|
| `PLAN_REQUIRED` | Deployment attempted without approved plan | Run `/fluent-feature-plan` first |
| `DEPLOYMENT_BLOCKED` | Pre-deploy check BLOCKED or rule references missing | Resolve blocking gates or deploy module first |
| `PREREQ_MISSING` | No pre-deploy report or rule not registered | Run `/fluent-pre-deploy-check`; deploy module via `/fluent-module-deploy` |
| `ENV_UNREACHABLE` | Cannot connect to Fluent environment | Check credentials via `config.validate` |

## When to Use

- Uploading a standalone workflow definition not bundled in a module
- Deploying after `fluent workflow merge` fails to upload (known `token:undefined` bug)
- Deploying workflows excluded during module install (`--exclude workflows`)
- Automating workflow deployment in CI/CD pipelines
- Rolling back to a previous workflow version

## Deployment Pipeline

### Step 0: Verify CLI Truth Source

Run these commands before any deployment action:

```bash
fluent --version
fluent workflow --help
fluent workflow merge --help
```

Use only verified subcommands from `fluent workflow --help`:

- `fluent workflow list`
- `fluent workflow download`
- `fluent workflow merge`

**MCP alternatives** (fluent-mcp-extn): `workflow.get` (fetch by entityType+entitySubtype), `workflow.list` (all workflows). Use these when CLI returns 401 or on Windows where `::` filenames fail.

If CLI is not found, fall back to MCP `workflow.upload` or REST API (Strategy 2/3).

If a requested command is not shown in the help output, treat it as invalid and stop.

### Step 1: Pre-flight Checks

Before uploading, run **`/fluent-workflow-validate`** on the workflow JSON file. This is the single validation gate — it covers schema compliance (S01-S10), status graph integrity (G01-G10), trigger routing (T01-T10), rule references (R01-R09), user actions (U01-U10), and cross-entity checks (X01-X06).

**If validation returns FAIL (any CRITICAL or HIGH issues), STOP. Do not proceed to upload.**

If `/fluent-workflow-validate` was already run (e.g., by `/fluent-workflow-builder` post-build hook) and the file has not changed since, skip re-running and use the cached result.

Additionally verify:
7. **Validate user actions (BLOCKING — do NOT deploy if any fail)** -- for every ruleset that has `userActions`:
   - `eventName` MUST be present — **MANDATORY per official Fluent docs**. Missing `eventName` = button silently invisible. This is a **deploy blocker**.
   - Ruleset MUST have `subtype` matching workflow `entitySubtype` (without it, buttons are silently invisible -- #1 cause of missing UI buttons). This is a **deploy blocker**.
   - Every `context[]` entry MUST have all 4 fields: `label`, `type`, `modules`, `confirm`
   - `modules` MUST be non-empty array matching each app's `orchestrationAlias` (`"adminconsole"` for OMS App, `"servicepoint"` for Store App, or custom)
   - **OMS App uses `"adminconsole"` (NOT `"oms"`)** — the OMS App manifest (`fc.mystique.manifest.oms`) has `orchestrationAlias: "adminconsole"`. **Store App uses `"servicepoint"` (NOT `"store"`)** — the Store App manifest (`fc.mystique.manifest.store`) has `orchestrationAlias: "servicepoint"`.
   - `confirm` MUST be boolean (not string `"true"`)
   - `context` MUST be an array `[...]` not an object `{...}`
   - If `attributes` has entries: `source` MUST be empty `""` or `"settings.<key>"` (**lowercase** `settings.` — capital `"Settings."` silently fails). Referenced setting value MUST have `{ "active": [...] }` wrapper (plain array silently fails). `"OVERRIDE"` works on v4.80+. `defaultValue` MUST be empty `""` or literal (`"{{me.username}}"` works on v4.80+).
   - **If ANY of the first two checks fail, STOP deployment and report:** *"User action on ruleset [name] will be invisible in the UI. Fix: add `eventName` / add `subtype` before deploying."*

### Step 1.05: Major Version Impact Check (MANDATORY)

Compare the workflow version being deployed against the currently deployed version. If the **major version** has changed (e.g., 99.x → 100.0), this has significant behavioral impact:

**Detection:**
1. Read the `version` field from the workflow JSON being deployed
2. Fetch the currently deployed version via `workflow.get` or `fluent workflow list`
3. Parse both as `<major>.<minor>` — compare major components

**If major version increased:**

> **MAJOR VERSION WARNING**
> Deploying `<WORKFLOW_NAME>` from v`<old>` → v`<new>` is a **major version bump**.
>
> **Impact on existing entities:**
> - All entities created before this deploy stay on major `<old_major>` (latest minor of that major)
> - They will **never** execute against v`<new>` rulesets — even for future events
> - Only **newly created** entities will use major `<new_major>`
>
> **Is this intentional?**
> - If YES (breaking changes, post go-live protection): proceed
> - If NO (you want all entities to use the new logic): change the version to `<old_major>.<old_minor + 1>` (minor bump instead)

**Present this warning to the user and wait for confirmation before proceeding.** A minor bump applies changes to all existing entities immediately; a major bump isolates new entities only. Choosing wrong can leave in-flight orders on stale logic or break them with incompatible changes.

**If minor version increased (or first deployment):** Proceed without warning — minor bumps are backward-compatible and apply to all entities on that major version immediately.

### Step 1.1: Large workflow payload gate (MANDATORY)

Before upload, keep workflow changes file-based and reviewable:

1. Save the workflow JSON to a local file under `.tmp/` or `accounts/<PROFILE>/workflows/<RETAILER_REF>/`.
2. Apply edits locally and keep a backup copy of the previous version.
3. Prefer Strategy 1 (`fluent module install --include workflows`) for large workflows.
4. Avoid pasting large workflow bodies directly into ad-hoc MCP/tool payloads when a file-based deploy path is available.

### Step 1.5: Pre-Deploy Check Gate

**This gate is MANDATORY. Do not skip.**

1. Search `accounts/<PROFILE>/analysis/pre-deploy/` for `<MODULE>-<VERSION>.checklist.json`.
2. **If found and `overallResult == "BLOCKED"`**: **STOP. Do NOT proceed.** List all blocking gates from the report. Require explicit user override ("deploy anyway") to continue past a BLOCKED verdict.
3. **If found and `overallResult == "READY"`**: Proceed -- pre-deploy validation passed.
4. **If no report found**: WARN the user: "No pre-deploy-check report found for this module version. Run `/fluent-pre-deploy-check` first for comprehensive validation." Only proceed after explicit user confirmation ("deploy without validation").

### Step 1.75: Output Validation (Automated)

**The implementation critique protocol lives at: `knowledge/platform/implementation-critique-protocol.md`.** Select the **Workflow JSON Rubric** (WF-1 through WF-10) and follow the protocol exactly.

Before uploading, run the adversarial validation loop (max 2 rounds) on the workflow JSON. The critic validates: eventName presence, subtype match, modules correctness, version increment, trigger coverage, status categories, user action attribute contracts, confirm boolean type, context type case, and attribute default safety.

**This is the last automated check before the workflow goes live.** Do not skip.

### Step 2: Upload (Strategy Selection)

Try upload strategies in order. **Always use Strategy 1 when the CLI is available.**

#### Strategy 1 (Primary): CLI `fluent module install`

**This is the correct and reliable way to upload workflows via CLI.** The Fluent CLI has no `workflow upload` command -- you package the workflow into a temporary module directory and install it.

**Step-by-step procedure:**

```bash
# 1. Create temporary module directory (NO resources/ prefix -- CLI rejects it)
DEPLOY_DIR=$(mktemp -d)
mkdir -p "$DEPLOY_DIR/assets/workflows"

# 2. Copy workflow JSON (CLI converts __ to :: in workflow name automatically)
cp <workflow-file>.json "$DEPLOY_DIR/assets/workflows/"

# 3. Create minimal module.json at module ROOT (REQUIRED)
echo '{"_schema":"1.0.0","name":"workflow-deploy-tmp","version":"1.0.0"}' > "$DEPLOY_DIR/module.json"

# 4. Create empty module.config.json at module ROOT (REQUIRED)
echo '{}' > "$DEPLOY_DIR/module.config.json"

# 5. Deploy -- ONLY workflows, with --force to bypass version check
fluent module install "$DEPLOY_DIR" \
  -p <profile> \
  -r <retailer> \
  --include workflows \
  --force

# 6. Clean up
rm -rf "$DEPLOY_DIR"
```

**With config token resolution** (for workflows with `[[token.name]]` placeholders):

```bash
# Create config with PREFIXED keys (REQUIRED -- unscoped keys are silently ignored)
cat > "$DEPLOY_DIR/module.config.json" <<'CONF'
{
  "default:retailerId": "2",
  "default:networkRef": "BASE_2",
  "default:virtualCatalogueRef": "BASE:2"
}
CONF

fluent module install "$DEPLOY_DIR" \
  -p <profile> \
  -r <retailer> \
  --config "$DEPLOY_DIR/module.config.json" \
  --include workflows \
  --force
```

**Key flags:**
- `--include workflows` -- installs ONLY workflows from the module, skips everything else
- `--force` -- bypasses version check (temp modules always use version `1.0.0`)
- `--config` -- resolves `[[token]]` placeholders in workflow JSON before upload

**CRITICAL: Config key prefix requirement.** Config keys MUST use a prefix (`default:`, `workflow:`, `workflow:<type>:<subtype>:`). Unscoped keys like `"retailerId": "2"` are **silently ignored** -- the CLI reports "missing placeholder keys" even though the key exists in the file. Use `"default:retailerId": "2"` for keys that apply to all assets. See CLI Reference § Config Prefix System for full rules.

**Module directory structure (verified against CLI v2.0.0 -- must be exact):**

```
<deploy-dir>/
├── module.json              # {"_schema":"1.0.0","name":"<any>","version":"1.0.0"}
├── module.config.json       # {} (empty if no tokens) or {"default:key":"val"} (MUST use prefix)
└── assets/
    └── workflows/
        └── <WORKFLOW>.json  # The workflow JSON to upload
```

**CRITICAL:** Do NOT use a `resources/` wrapper directory. The CLI validates that `assets/`, `module.json`, and `module.config.json` exist at the module root directory. A `resources/` prefix causes `Invalid Module` error.

**IMPORTANT notes:**
- The CLI converts `__` in filenames to `::` for the workflow name (e.g., `ORDER__HD.json` becomes workflow `ORDER::HD`)
- The `--force` flag is required because the temp module always uses version `1.0.0`
- Multiple workflows can be placed in `assets/workflows/` to deploy several at once
- The `module.json` `name` field can be anything -- it is a throwaway wrapper
- Config tokens (`[[key]]`) are resolved by `--config` at deploy time, NOT stored in the uploaded workflow

#### Strategy 2 (Fallback): MCP `workflow.upload` Tool

If CLI is unavailable, use the MCP extension tool. This calls REST API `POST /api/v4.1/workflow/{retailerId}`:

```
workflow.upload({
  workflow: <workflow-json-object>,
  dryRun: true,     // Validate without deploying
  validate: true    // Run structure checks
})
```

1. First run with `dryRun: true` to validate
2. If validation passes, run with `dryRun: false` to deploy
3. Response includes the new version number

**Advantages:** Built-in validation, dryRun mode, no temp directory needed.

**When this fails:** MCP server not configured, connection issues, or SDK client lacks `request()` method.

#### Strategy 3 (Last Resort): REST API Direct Upload

If both CLI and MCP are unavailable:

```bash
# Extract credentials from CLI profile
PROFILE_DIR="$HOME/.fluentcommerce/<profile>"
BASE_URL=$(python3 -c "import json; print(json.load(open('$PROFILE_DIR/profile.json'))['baseUrl'])")
CLIENT_SECRET=$(python3 -c "import json; print(json.load(open('$PROFILE_DIR/profile.json'))['clientSecret'])")
ACCOUNT_ID=$(python3 -c "import json; print(json.load(open('$PROFILE_DIR/profile.json'))['id'])")

# Get retailer credentials
RETAILER_FILE="$PROFILE_DIR/retailer.<retailer-ref>.json"
USERNAME=$(python3 -c "import json; d=json.load(open('$RETAILER_FILE')); print(d['defaultUser']['username'])")
PASSWORD=$(python3 -c "import json; d=json.load(open('$RETAILER_FILE')); print(d['defaultUser']['password'])")

# Authenticate (retailer-scoped token required -- account-level tokens return 403)
TOKEN=$(curl -s -X POST "$BASE_URL/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=$USERNAME&password=$PASSWORD&client_id=$ACCOUNT_ID&client_secret=$CLIENT_SECRET&grant_type=password&scope=api" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

# Upload via POST (not PUT)
curl -s -X POST "$BASE_URL/api/v4.1/workflow/<retailerId>" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -H "fluent.account: $ACCOUNT_ID" \
  -d @<workflow-file>
```

**Success response:**
```json
{
  "entityId": "2-ORDER::HD::1.51",
  "eventStatus": "COMPLETE"
}
```

**Error responses:**

| HTTP Code | Cause | Fix |
|-----------|-------|-----|
| 401 | Token expired or invalid | Re-authenticate |
| 403 | Account-level token, not retailer-scoped | Use retailer user credentials |
| 400 | Invalid workflow JSON | Check structure with `/fluent-workflow-builder` |
| 409 | Version conflict | Bump version number |
| 500 | Server error | Retry after 5 seconds |

#### NOT a Strategy: `fluent workflow merge`

`fluent workflow merge` is for merging fragments, NOT for standalone upload:

```bash
fluent workflow merge <fragment> <target-workflow-name> -p <profile> -r <retailer>
```

It merges the fragment into the target and then auto-uploads. Known issues:
- `token:undefined` bug causes the upload step to fail silently
- Cannot upload a standalone workflow -- it always requires a fragment AND a target
- If the merge succeeds but upload fails, the merged file is saved locally as `<filename>.merged.json` -- re-upload via Strategy 1 or 2

**Do NOT use `fluent workflow merge` as a workflow upload mechanism.**

### Step 3: Verify Deployment

```bash
fluent workflow list -p <profile> -r <retailer>
```

Confirm the version number matches the uploaded version.

Additionally, download and diff to confirm content:

```bash
fluent workflow download -p <profile> -r <retailer> -w <name> -o /tmp/
# OR via MCP with outputFile (keeps large workflow out of context):
# workflow.get({ entityType: "ORDER", entitySubtype: "HD", retailerId: "<id>", outputFile: "/tmp/ORDER-HD.json" })
```

Compare ruleset count and status count between uploaded and downloaded versions.

### Step 3.5: Post-Deploy User Action Verification (MANDATORY when workflow has userActions)

If the deployed workflow contains any rulesets with `userActions`, verify they are actually visible:

1. **Transition API check** — For each entity type + subtype that has user actions, query:
   ```
   workflow.transitions type=<ENTITY_TYPE> subtype=<SUBTYPE> retailerId=<ID> module=adminconsole flexType=<WORKFLOW_NAME> flexVersion=<deployed_version>
   ```
   **THREE MANDATORY parameters** — omitting ANY one silently returns empty results (no error):
   - **`module`** — **REQUIRED.** Must be a specific app name. `"all"` returns NOTHING.
     - For OMS App buttons: use `module: "adminconsole"` (NOT `"oms"` — the OMS App's module alias, legacy alias)
     - For Store App buttons: use `module: "servicepoint"` (Store App's `orchestrationAlias` — manifest setting is `fc.mystique.manifest.store`)
   - **`flexType`** — **REQUIRED.** Workflow identifier (e.g., `"ORDER::HD"`). For child entities (CREDIT_MEMO, FULFILMENT), BOTH the parent workflow name (e.g., `"BILLING_ACCOUNT::CUSTOMER"`) and the standalone child workflow name (e.g., `"CREDIT_MEMO::APPEASEMENT"`) work — use the matching `flexVersion` for whichever you choose. To mirror what the UI does, use the parent workflow (the UI reads `workflowRef`, which for child entities = parent)
   - **`flexVersion`** — **REQUIRED.** Include the exact deployed version number. After rapid deploys, the Transition API may cache a stale version. Always specify the version you just deployed.
2. **Verify non-empty** — The response `userActions[]` must contain the expected actions with correct `eventName`, `context`, and `attributes`.
3. **If empty** — Run the diagnostic checklist from `/fluent-workflow-builder` (V2a through V2g) to identify the root cause. Do NOT assume the deployment succeeded just because the upload returned success.
   **Diagnostic reference — if Transition API returns empty `userActions[]`:**
   1. Verify ALL THREE mandatory parameters are present: `module`, `flexType`, `flexVersion` — omitting any one silently returns empty
   2. Verify `module` is a specific name (`"adminconsole"` for the OMS App) — `"all"` returns NOTHING
   3. Verify `flexType` — the Transition API accepts both the parent workflow name and standalone child workflow name (with matching `flexVersion`). The UI uses the entity's `workflowRef` (for child entities = parent workflow)
   4. Verify `flexVersion` is the exact deployed version number
   5. Verify ruleset has `subtype` matching entity subtype
   6. Verify `eventName` is set on the user action
   7. Run `/fluent-workflow-builder` diagnostic checklist (checks UA01-UA19)
4. **Browser verification (RECOMMENDED when Chrome DevTools MCP available):**
   a. Navigate to the entity detail page using `navigate_page` with `initScript` set to the `__fcTest` helper IIFE (see `fluent-ui-test/BROWSER_OPTIMIZATIONS.md` "FAST PATTERN") — auto-injects helper, no separate step
   b. Single `evaluate_script` with `batch()` — verify page + check button in ONE call:
      ```javascript
      () => JSON.stringify(window.__fcTest.batch([
        { action: 'pageSummary' },
        { action: 'findButton', args: ['BUTTON_LABEL'] },
        { action: 'highlight', args: ['BUTTON_LABEL', 'VERIFIED'] }
      ]))
      ```
   c. If found: `take_screenshot` for evidence (optional — 3rd MCP call max)
5. **Report to user** — Always tell the user: "I verified the user action [label] is [visible/NOT visible] via the Transition API at status [status]." Never skip this report.

### Step 4: Post-Deploy Validation (Optional)

Send a test event to verify the workflow processes correctly:

1. Find or create a test entity in a suitable status
2. `event.build` -- preview the test event payload
3. `event.send` (dryRun: true) -- validate without sending
4. `event.send` (dryRun: false) -- send real event
5. `event.list` -- confirm event processed (eventStatus: COMPLETE/SUCCESS)
6. `graphql.query` -- verify entity status changed as expected

Use `/fluent-e2e-test` for comprehensive post-deploy testing.

## Module Dependency Check

**Critical:** If the workflow references custom rules (rules with `<ACCOUNT>.<context>.<RuleName>` pattern rather than `FLUENTRETAIL.*`), verify the module containing those rules is deployed:

```
plugin.list({ name: "<RuleName>" })
```

If any referenced rule is not found in the registry, the module must be deployed first via `/fluent-module-deploy`. Deploying a workflow that references unregistered rules causes NO_MATCH events at runtime.

**Recommended deployment order:**
1. Deploy module (registers rules) -- `/fluent-module-deploy`
2. Deploy workflows (references rules) -- `/fluent-workflow-deploy`
3. Create/update settings (referenced by rules) -- `/fluent-settings`
4. Run E2E test -- `/fluent-e2e-test`

## Dry Run Mode

With `--dry-run`, the deployer performs all steps except the actual upload:

```
1. Parse and validate workflow JSON         done
2. Check current deployed version           done
3. Verify version bump                      done
4. Check rule references exist              done
5. Upload workflow                          SKIPPED
6. Report what WOULD be deployed
```

## Rollback

To rollback to a previous version:

1. Download the previous version:
   ```bash
   fluent workflow download -p <profile> -r <retailer> -w <name> -o /tmp/
   # OR via MCP: workflow.get({ entityType: "<TYPE>", entitySubtype: "<SUB>", retailerId: "<id>", version: "<prev>", outputFile: "/tmp/<TYPE>-<SUB>.json" })
   ```

2. Edit the downloaded JSON to bump the version number (must be higher than current)
3. Upload via Strategy 1 (CLI `fluent module install`) or Strategy 2 (MCP `workflow.upload`)

Note: Fluent does not support downgrading version numbers. To "rollback," you upload the old content with a new (higher) version number.

For failed deployments that need reversal, follow the rollback steps above (download previous content, bump version, re-upload).

## CI/CD Workflow Deployment

In CI/CD pipelines, the assembly pattern is the standard approach:

```bash
# Build a per-retailer temp module from source workflows
DEPLOY="/tmp/deploy-$RETAILER"
mkdir -p "$DEPLOY/assets/workflows"
cp workflows/*.json "$DEPLOY/assets/workflows/"
echo '{"_schema":"1.0.0","name":"workflow-deploy","version":"1.0.0"}' > "$DEPLOY/module.json"
echo '{}' > "$DEPLOY/module.config.json"

fluent module install "$DEPLOY" \
  -p deploy \
  -r "$RETAILER" \
  --config "$CONFIG_FILE" \
  --include workflows \
  --force

rm -rf "$DEPLOY"
```

See the CI/CD documentation for wave-based rollout patterns with `--config` token resolution for per-country workflow variants.

## Integration with Other Skills

| Task | Skill |
|------|-------|
| Analyze workflow before deploying | `/fluent-workflow-analyzer` |
| Edit workflow JSON | `/fluent-workflow-builder` |
| Download current workflow | `/fluent-workflow` |
| Deploy module (rules must exist first) | `/fluent-module-deploy` |
| Pre-deployment validation | `/fluent-pre-deploy-check` |
| Test after deployment | `/fluent-e2e-test` |
| Debug deployment failures | `/fluent-trace` |
| Revert failed deployment | Manual rollback via this skill's rollback steps |

## Session Tracking

When invoked, log the following to the session tracking protocol (consumed by `/fluent-session`):

**On entry:**
```json
{ "skill": "<this-skill-name>", "timestamp": "<ISO-8601>", "arguments": { "<key>": "<value>", "...": "..." } }
```

**On exit:**
```json
{ "skill": "<this-skill-name>", "outcome": "<completed|failed|skipped>", "changesProduced": ["<seq-numbers>"], "toolCallsProduced": ["<seq-numbers>"], "nextRecommended": "<from-handoff-section>" }
```

All MCP tool calls made during execution should include `"skill": "<this-skill-name>"` in their tracking record for attribution.

### Update status.json gates

If a feature context exists (`accounts/<PROFILE>/features/<slug>/status.json`):
- On **successful deploy**: set `deploy: "DEPLOYED"` and advance `status` to `"DEPLOYED"` (if currently `IN_PROGRESS`, `BUILT`, or `APPROVED`)
- On **deploy failure**: set `deploy: "FAILED"` (do NOT advance `status`)
- Always update `updated` timestamp

## Tips

- **Always backup the current version** before deploying -- download with `fluent workflow download` or MCP `workflow.get`
- **Version must always increase** -- Fluent rejects same or lower version numbers
- **Use retailer-scoped tokens** for REST API -- Account-level tokens get 403 on workflow API
- **Check the `eventStatus`** in the upload response -- `COMPLETE` means success
- **The `entityId` in the response** follows format `<retailerId>-<workflowName>::<version>`
- **Deploy module before workflow** if the workflow references new custom rules
- **Windows users**: Colons in workflow names (e.g., `ORDER::HD`) crash file downloads on Windows -- use WSL or Linux
- **There is NO `fluent workflow upload` command** -- use `fluent module install --include workflows --force` instead

## Post-Execution: Feedback Capture

After completing this skill (whether success or failure), write a feedback record:

1. **Classify outcome:** `SUCCESS` (deployed), `PARTIAL_SUCCESS` (deployed after retry), `FAILURE` (deploy failed), `BLOCKED` (pre-deploy check failed), `USER_CORRECTED` (user overrode deploy method)
2. **Build record:** Create a `feedback-record-v1` JSON object with:
   - `skill`: `"fluent-workflow-deploy"`
   - `feature`: feature slug if applicable
   - `entityType` / `entitySubtype`: from the deployed workflow
   - `phases`: trace of each phase (pre-flight, backup, validate, deploy, verify) with PASS/FAIL/SKIP
   - `learnings`: any gotchas discovered (version conflicts, REST method issues, auth failures)
   - `userCorrections`: any corrections the user provided
3. **Redact secrets:** Strip tokens, auth headers. Keep workflow names, versions, retailer refs.
4. **Append** one JSON line to `accounts/<PROFILE>/feedback/fluent-workflow-deploy.jsonl`
5. **Update** `accounts/<PROFILE>/feedback/index.json` counters (create with `mkdir -p` if missing)

**Skip capture if:** Dry-run mode was used (no actual deployment happened).

## Known Pitfalls
<!-- feedback-promoted: managed section, updated by feedback loop -->

- **CLI workflowlog merge strips eventName** — After deploying via CLI, the workflowlog merge drops eventName from userActions[]. The Transition API still works (platform infers from ruleset name), but workflow JSON downloads will show eventName as missing.
- **Stale local files after MCP/REST deploy** — MCP workflow.upload and REST POST bypass the workflowlog. CLI downloads after such deploys return stale versions. Fix: fluent workflowlog delete all positions, then redeploy via CLI.
- **TRANSITION_ATTRIBUTE_INVALID on workflow deploy** — Platform server validates each `userActions[].attributes[].name` against `@EventAttribute` annotations declared by rules in the same ruleset. All rules treated equally regardless of plugin. If no rule has matching @EventAttribute → rejected. Fix: ensure a rule in the ruleset declares @EventAttribute for each attribute name (add annotations to existing rule or create new one), or deploy via REST POST/MCP to bypass. (Confirmed 2026-02-28)
- **Workflowlog poisoning on failed deploy** — CLI writes workflowlog entries even when deploy FAILS. Subsequent retries may conflict. Fix: `fluent workflowlog delete` all stale positions, then retry. (Confirmed 2026-02-28)
- **Config keys silently ignored without prefix** — `module.config.json` keys MUST use `default:`, `workflow:`, or `setting:` prefix. Unscoped keys (e.g., `"retailerId": "2"`) are silently ignored — CLI reports "missing placeholder keys" even though the key is in the file. Fix: use `"default:retailerId": "2"`. (Confirmed 2026-03-08)
- **Deploy from `resources/` directory causes 404** — CLI expects `assets/`, `module.json`, `module.config.json` at the module root. Using a source repo's `resources/` dir creates `resources/assets/workflows/` path which fails. Always use a fresh temp directory with flat layout. (Confirmed 2026-03-08)

<!-- end feedback-promoted -->
