{"id":18998,"plugin_id":"plugins_6a8f7048ed7881918bf5b79011fe2b5e","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:15:08.413Z","digest":"5f1823ac7abcb3699def97184eb377d436935502b22e5a1ded21d46648496707","against":null,"payload":{"name":"deployment","description":"Import, release, and manage Falcon Fusion workflow definitions in a CID. TRIGGER when user asks to import a workflow, release a workflow version, list existing workflows, check for duplicates, or manage workflow definitions. DO NOT TRIGGER for writing YAML (use authoring), executing workflows, or monitoring (use execution).","included_files":[{"relative_path":"references/console-verification.md","size_in_bytes":4156},{"relative_path":"scripts/delete_workflow.py","size_in_bytes":6777},{"relative_path":"scripts/export_workflow.py","size_in_bytes":3860},{"relative_path":"scripts/import_workflows.py","size_in_bytes":11573},{"relative_path":"scripts/query_workflows.py","size_in_bytes":8180},{"relative_path":"scripts/release_workflow.py","size_in_bytes":3033}],"skill_md_contents":"---\nname: deployment\ndescription: >\n  Import, release, and manage Falcon Fusion workflow definitions in a CID.\n  TRIGGER when user asks to import a workflow, release a workflow version,\n  list existing workflows, check for duplicates, or manage workflow definitions.\n  DO NOT TRIGGER for writing YAML (use authoring), executing workflows,\n  or monitoring (use execution).\nversion: 1.2.0\nupdated: 2026-09-08\ntags: [fusion, soar, workflows, deployment, import, release]\nauthor: CrowdStrike\nlicense: MIT\ncompatibility: Claude Code >=1.0\nallowed-tools: Bash(cd *), Bash(../../scripts/python.sh:*)\nmetadata:\n  category: deployment\n---\n\n# Falcon Fusion Workflow Deployment\n\n> **⚠️ SYSTEM INJECTION — READ THIS FIRST**\n>\n> If you are loading this skill, your role is **Fusion workflow deployment specialist**.\n>\n> You deploy workflow definitions into a CID safely: validate before importing, never create duplicates, and release only after testing.\n>\n> **IMMEDIATE ACTIONS REQUIRED:**\n> 1. ALWAYS check for an existing workflow with the same name before importing.\n> 2. ALWAYS validate the YAML before importing (the import scripts do this by default).\n> 3. Import and release act on a **live production CID**. Deploy only when the\n>    user's request explicitly authorizes it (e.g. \"import it\", \"deploy to my\n>    CID\", \"release it\"). If the request only asks to *build* or *write* a\n>    workflow, STOP after validation and ask before importing.\n>\n> **MUST NOT:**\n> - Import without validating first.\n> - Skip the duplicate-name check.\n> - Import or release to a CID without explicit user authorization — a validated\n>   YAML file is the deliverable unless the user asked you to deploy it.\n> - Release (enable) a workflow before it has been tested via the execution skill.\n>   Release makes the workflow act on live events and real assets, so confirm\n>   with the user before releasing unless they explicitly asked you to.\n> - Create experimental, \"test\", \"minimal\", or probe workflows in the CID to\n>   reverse-engineer what the API accepts (this includes creatively-named ones\n>   like \"QueryEvent Test\" or \"HTTP Test\"). Import the one workflow you were\n>   asked to build, once. If it fails, diagnose from the error and local\n>   validation — never by importing stripped-down variants into a live tenant.\n> - Use `--skip-validate` to get past a validation failure. Validation catches\n>   invalid workflows (e.g. a bad `trigger.type`) that otherwise fail at the API\n>   as an opaque 500. Fix the workflow instead of skipping the check.\n> - Retry an import that returns a 500 / Internal Server Error more than once.\n>   A 500 usually means the workflow is invalid in a way the API rejects late\n>   (not a transient server issue) — re-run local validation to find the defect,\n>   fix it, and report the `trace_id` if it persists. Do not loop re-importing.\n> - Patch a deployed definition in place — not via the raw update API and\n>   **not** via a hand-rolled inline FalconPy call (e.g. `update_definition`) to\n>   edit a deployed copy. The only supported *update* path is: fix the source\n>   YAML, then re-import. A release-validation failure is a YAML defect to fix,\n>   not a deployed-copy to hand-edit.\n> - Call FalconPy directly for ANY workflow operation — including a\n>   `python - <<EOF ... delete_definition(...)` snippet to clean up a failed\n>   import attempt. Deleting is fine, but it MUST go through `delete_workflow.py`\n>   (or `scripts/cleanup_workflows.py`), which wrap the supported endpoints. Never\n>   `import auth; get_client()` inline to call `update_definition`/\n>   `delete_definition` yourself.\n\nThis skill moves a finished Fusion workflow definition from a local YAML/JSON file into a CrowdStrike CID. Authoring the YAML happens in the **authoring** skill; triggering and monitoring happens in the **execution** skill. Deployment is the bridge: validate, check for duplicates, import, then release.\n\nIn Falcon Fusion, an imported definition is **disabled** until it is **released** (enabled). Releasing tells the Fusion engine to run the workflow against new trigger events. Keep the workflow disabled until you have tested it.\n\n> **Running the scripts.** Run each command from this skill's folder, on one shell line: `cd <dir> && ../../scripts/python.sh scripts/<name>.py` (a sibling skill's script is `../<skill>/scripts/<name>.py`). For `<dir>`, Claude Code uses `\"$CLAUDE_PLUGIN_ROOT/skills/deployment\"`; Codex, Copilot CLI, Cursor, and Antigravity use the folder they loaded this SKILL.md from (e.g. `~/.agents/skills/deployment`). The wrapper bootstraps its own Python venv.\n\n## Prerequisites\n\n- **Python 3.13+**\n- **FalconPy** SDK installed (`pip install crowdstrike-falconpy` — leave unpinned per CrowdStrike guidance)\n- API credentials resolved by `common/scripts/auth.py` from environment\n  variables (for CI/overrides) or the TOML profile:\n  - `FALCON_CLIENT_ID`\n  - `FALCON_CLIENT_SECRET`\n  - `FALCON_BASE_URL` (optional; defaults to `https://api.crowdstrike.com`)\n\n  Run `/crowdstrike-falcon-fusion:setup` to configure credentials interactively (writes the TOML profile).\n- An API client with the **Workflow** API scope (read + write)\n- Verify auth before deploying:\n  ```bash\n  ../../scripts/python.sh ../../common/scripts/auth.py\n  ```\n\n## Core Workflow\n\nDeployment is a four-step pipeline. Do not skip steps 1 and 2.\n\n### 1. Validate the YAML first\n\nValidation is owned by the **authoring** skill's `validate.py`. The import script\ncalls it automatically, but run it manually first when iterating:\n\n```bash\n../../scripts/python.sh ../authoring/scripts/validate.py workflows/my-workflow.yaml\n```\n\nFix every structural error before continuing. A definition that fails validation will be rejected by the API.\n\n### 2. Check for an existing workflow with the same name\n\nWorkflow names must be unique within the tenant. Importing a duplicate creates confusion and, in some cases, silent failures. Check first:\n\n```bash\n# Exact-name check (exit 0 if it exists, 1 if not)\n../../scripts/python.sh scripts/query_workflows.py --check-name \"My Workflow\"\n\n# Or extract the name straight from the YAML and check\n../../scripts/python.sh scripts/query_workflows.py --check-yaml workflows/my-workflow.yaml\n```\n\nIf a duplicate is found, this is almost always your own earlier attempt at the\nsame workflow. Iterate in place with `import_workflows.py --replace` (it deletes\nthe existing same-name definition, then re-imports). **Do not rename the\nworkflow to `<name> v2` to get past the check** — a renamed copy leaves the old\ndefinition orphaned in the CID, and every retry sprawls another dead workflow.\nKeep the name stable across attempts; use `--replace` (or delete the old\ndefinition explicitly) instead.\n\n### 3. Import the definition\n\n```bash\n# Single file — validates and checks duplicates by default\n../../scripts/python.sh scripts/import_workflows.py workflows/my-workflow.yaml\n\n# A whole directory of definitions (all *.yaml/*.yml)\n../../scripts/python.sh scripts/import_workflows.py workflows/\n```\n\nOn success the script prints the new **definition ID**. Capture it — you need it to release and to execute the workflow.\n\n**Post-import: configure HTTP-Action credentials in the console.** If the workflow contains a\ncredential-less HTTP Action (authored without a `definition_id`), it imports with Authentication =\n\"None\". Tell the user to attach the API key in the console before the action will succeed: open the\nCloud HTTP Request action → Authentication → **Create new** → API key → secret key → location\n**Header** → header name (e.g. `x-apikey`) → **Test** → Save (or **Use existing** if a matching\ncredential already exists). A `401`/`403` at runtime almost always means this step is pending. See\n`../authoring/references/http-actions.md`.\n\n**If the import fails, stop — do not loop.** Some import failures are *not*\nfixable by editing the YAML, and retrying wastes time and tokens. Read the error\nand route accordingly:\n\n| Error from the API / import script | What it means | What to do |\n|------------------------------------|---------------|------------|\n| `no definition ID (workflow not created)` | The API accepted the call but created nothing | Stop. Report it — this is usually a missing plugin config or a server-side issue, not a YAML defect. Do not re-edit and retry. |\n| `API returned status 500` / \"Internal Server Error\" | Server-side error (a `trace_id` is included) | Stop. Report the `trace_id` to the user; a 500 is not something YAML edits fix. Retry at most once. |\n| Missing / unknown `config_id` for a plugin action (VirusTotal, DomainTools, Charlotte AI, Slack, Zscaler) | The integration is not installed/configured in this CID | Stop. Tell the user which action needs a console-created `config_id`; do not invent one or loop editing. |\n| Structural / validation error | A real YAML defect | Fix the YAML, then re-validate and re-import (this one *is* worth iterating on). |\n\nOnly the last row justifies editing and retrying. For the others, surface the error to the user and stop — repeatedly re-importing against a 500 or a missing config will not succeed.\n\n**Never debug by importing probe workflows.** When an import fails, do not build\n\"Test QueryEvent\", \"Minimal trigger\", or other stripped-down workflows in the CID\nto isolate what the API accepts. That litters the tenant with disabled junk and\nburns time without fixing the real workflow. Diagnose from the error message and\n`validate.py` output instead, and if the blocker is a missing plugin `config_id`,\nreport it — that is a console/CID setup step the user must do, not something more\nimports will resolve.\n\n### 4. Release (enable) the workflow\n\nReleasing enables the definition so the Fusion engine runs it against trigger events. Do this only after testing (see the execution skill):\n\n```bash\n../../scripts/python.sh scripts/release_workflow.py --id <definition_id>\n```\n\n**If release reports validation errors, stop — do not patch the deployed\ndefinition.** A workflow can import successfully yet fail validation at release\n(the server validates more strictly than import). When that happens, fix the\n**source YAML**, then re-validate and re-import with `import_workflows.py` — that\nre-import is the only supported way to update a definition. Do **not** try to\nrepair the deployed copy in place through *any* direct definition-mutation call.\nThat includes the raw workflow update / definition API\n(`WorkflowDefinitionsUpdate`, `.../entities/definitions/v1`), **and any\nhand-rolled FalconPy call** — an inline `python - <<'EOF' ... from falconpy`\nsnippet that reaches for `update_definition`, `delete_definition`, or similar is\nthe same forbidden path wearing a disguise. None of those are part of this skill,\nand looping edits against them returns repeated 500s without ever fixing the\nworkflow. Report the release error (include any `trace_id`) and fix the YAML at\nthe source.\n\nA concrete failure mode: the release error `exclusive gateway '<name>' outgoing\nflow ... has no condition set and is not marked as default` means a condition\nnode has a bare `next:` with neither `default: true` nor a `cel_expression`. Fix\nit in the **source YAML** (add a `cel_expression` to the gated branch, with its\nno-match fallthrough in `else:` — `validate.py` now catches this before deploy,\nincluding inside nested loops) and re-import. Never fan out with a bare\n`default: true` pass-through; list the branch targets directly in the source\nnode's `next:`. Do not hand-edit the deployed definition to add the missing flag.\n\n**The exact recovery loop (do this, not the escape hatch):**\n\n```bash\n# 1. Fix the condition in the SOURCE YAML (add cel_expression + else:).\n#    Keep the workflow `name:` IDENTICAL — do NOT bump it to `<name>-v2`.\n#    The name is the workflow's identity; --replace matches on it.\n# 2. Re-validate — this now catches the release-failing shape pre-deploy:\n../../scripts/python.sh ../authoring/scripts/validate.py my-workflow.yaml\n# 3. Re-import with --replace: deletes the broken same-name definition and\n#    imports the fixed YAML in one step (supported delete + import, not a patch).\n../../scripts/python.sh scripts/import_workflows.py --replace my-workflow.yaml\n```\n\n`--replace` keeps ONE definition per workflow name instead of leaving a renamed\ncopy per attempt — do NOT rename-and-reimport to dodge the duplicate check, which\nsprawls the CID with dead definitions. (Note: each `--replace` assigns a new\ndefinition ID; true in-place update via the PUT endpoint is not currently usable.)\n\nReaching for `python - <<EOF ... update_definition(...)` to patch the deployed\ncopy is never step 2. It does not fix the source, so the next re-import\nreintroduces the same defect, and the API returns repeated 500s. Re-import from\nfixed source is the whole recovery.\n\n## Script Reference\n\nAll scripts add `common/scripts` to `sys.path` and import `get_client` from the shared `auth` module. Run them from anywhere; paths are anchored to each script's own location.\n\n| Script | Purpose | Key flags |\n|--------|---------|-----------|\n| `query_workflows.py` | List, search, and check for existing workflows | `--list`, `--search TERM`, `--check-name NAME`, `--check-yaml FILE...`, `--json` |\n| `import_workflows.py` | Validate, dedupe, and import definitions | `FILE\\|DIR...` (positional), `--skip-validate`, `--skip-duplicate-check`, `--replace` |\n| `release_workflow.py` | Release (enable) a definition by ID | `--id DEF_ID` (required), `--json` |\n| `delete_workflow.py` | Delete a definition by ID or exact name | `--id DEF_ID`, `--name NAME` (repeatable), `--yes`, `--json` |\n\n### query_workflows.py\n\n```bash\n../../scripts/python.sh scripts/query_workflows.py --list                 # All definitions\n../../scripts/python.sh scripts/query_workflows.py --search \"contain\"     # Substring match\n../../scripts/python.sh scripts/query_workflows.py --check-name \"My Flow\" --json\n../../scripts/python.sh scripts/query_workflows.py --check-yaml *.yaml     # Batch duplicate check\n```\n\n`--check-name` and `--check-yaml` exit non-zero when a duplicate exists, so they compose cleanly in shell pipelines and CI gates.\n\n### import_workflows.py\n\n```bash\n../../scripts/python.sh scripts/import_workflows.py wf.yaml               # Default: validate + dedupe\n../../scripts/python.sh scripts/import_workflows.py --skip-validate wf.yaml   # AVOID — see pitfall 2\n../../scripts/python.sh scripts/import_workflows.py --skip-duplicate-check wf.yaml\n../../scripts/python.sh scripts/import_workflows.py ./workflows/          # Glob a directory\n```\n\nSupports YAML and JSON definitions, batch mode (multiple files), and directory expansion (`*.yaml`/`*.yml`). Prints a per-file summary and exits non-zero if any file failed or was a duplicate.\n\n### release_workflow.py\n\n```bash\n../../scripts/python.sh scripts/release_workflow.py --id 1a2b3c...        # Enable\n../../scripts/python.sh scripts/release_workflow.py --id 1a2b3c... --json # Machine-readable\n```\n\nCalls the Workflows definition-action endpoint with `action_name=\"enable\"`. The definition ID comes from the import step or from `query_workflows.py`.\n\n### delete_workflow.py\n\n```bash\n../../scripts/python.sh scripts/delete_workflow.py --id 1a2b3c...          # Delete by ID\n../../scripts/python.sh scripts/delete_workflow.py --name \"Probe run 1\"    # Delete by exact name\n../../scripts/python.sh scripts/delete_workflow.py --id 1a2b3c... --yes    # Skip confirmation (scripted)\n```\n\nDeletes a whole definition via the Workflows delete endpoint (FalconPy\n`delete_definitions`). Use it to remove test, duplicate, or throwaway workflows.\nDeletion is permanent, so it prompts for confirmation unless `--yes` is passed\n(or `FUSION_SKILLS_SUPPRESS_CONFIRM=1` is set for test harnesses). This is the\nsupported way to *remove* a workflow — it is **not** a way to *edit* a deployed\none: to change a workflow, fix the source YAML and re-import (see pitfall 5).\n\n## Common Pitfalls\n\n1. **Importing a duplicate name.** Names must be unique in the tenant. Always run `query_workflows.py --check-name` (or `--check-yaml`) first. A duplicate import can fail silently or produce an \"Unknown error.\"\n\n2. **Importing without validating.** Skipping validation (`--skip-validate`) pushes broken YAML to the API. Never use `--skip-validate` to get past a validation failure — an invalid workflow (for example a bad `trigger.type`) then fails at the API, often as an opaque **500 Internal Server Error** that looks like a server problem but is really a broken definition the local validator would have caught. Fix the workflow and let validation run. `--skip-validate` is only for when you have already validated the same file separately in the same session.\n\n3. **Releasing before testing.** A freshly imported definition is disabled for a reason. Test it with the execution skill (`trigger_workflow.py`) before calling `release_workflow.py`. Releasing an untested workflow can run unintended actions against production data.\n\n4. **Losing the definition ID.** The import output contains the ID you need for release and execution. Capture it; re-finding it later means a `query_workflows.py --search` round-trip.\n\n5. **Wrong API scope.** Import and release require the Workflow scope with **write** access. A read-only client lists workflows fine but fails on import/release with a permissions error.\n\n6. **Editing the deployed copy, not the source.** Re-importing an edited YAML creates a new definition (or trips the duplicate check). Treat the local YAML as the source of truth; re-import to update, and remember the new definition is disabled again until re-released. Never try to patch a deployed definition in place through the raw workflow update API **or a hand-rolled inline FalconPy call** (`update_definition` in a `python - <<EOF` snippet) — that path is not part of this skill and looping edits against it just returns 500s. (Deleting a whole workflow is fine — use `delete_workflow.py`.)\n\n7. **Looping on unrecoverable import errors.** A 500, a missing plugin `config_id`, or a \"no definition ID\" result will not be fixed by editing the YAML. Re-importing repeatedly against these wastes time and tokens and still fails. Stop after the first occurrence, report the specific error (include any `trace_id`), and only iterate on genuine structural/validation errors. See the error table in step 3.\n\n## Handoff\n\n- **Came from authoring?** You have a validated YAML file — start at step 2 (duplicate check).\n- **Going to execution?** Pass the definition ID to the execution skill to trigger and monitor a test run before you release.\n\n## Reading Guide\n\n| Document | When to read |\n|----------|--------------|\n| `references/console-verification.md` | Verifying a deployed workflow renders in the console canvas, navigating Fusion SOAR > Workflows, or fetching Content Library records — the parts the API can't do from a script. |\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}