Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ The skills are markdown instructions plus Python scripts — no plugin runtime i
1. **Read the SKILL.md** for the task you are doing (e.g., `skills/authoring/SKILL.md` to write a workflow).
2. **Run the scripts directly** with `python <skill>/scripts/<script>.py --help` to see flags. Any absolute or `~/.agents/skills/...` symlink path works — each script resolves its own location and bootstraps its managed Python venv, so `${CLAUDE_PLUGIN_ROOT}` (set only by Claude Code) is not required.
3. **Follow the discipline rules**, which are not optional:
- Always discover real action IDs with `action_search.py` — never guess. IDs are 32-char hex.
- Always discover real action IDs with `action_search.py` — never guess. IDs are opaque catalog identifiers.
- Never write `PLACEHOLDER_*` values. Resolve every ID before authoring.
- Every action needs a `version_constraint`: `~<major>` of its `semantic_version` (`~0` when it declares none).
- Validate after authoring (`validate.py`) and again as a deploy pre-flight.
Expand All @@ -88,7 +88,7 @@ Carry the artifacts forward: authoring produces a validated YAML file, deploymen

These rules apply to every task and are not optional:

- **Discover real action IDs — never invent them.** Run `skills/authoring/scripts/action_search.py` against the live activities catalog to resolve a real 32-char hex ID for every action. Guessing an ID, or shipping a `PLACEHOLDER_*` value, produces a workflow that fails to import or wires the wrong action into a response.
- **Discover real action IDs — never invent them.** Run `skills/authoring/scripts/action_search.py` against the live activities catalog to resolve a real ID for every action. Guessing an ID, or shipping a `PLACEHOLDER_*` value, produces a workflow that fails to import or wires the wrong action into a response.
- **Validate before deploy.** Run `skills/authoring/scripts/validate.py` after authoring, and again as a deploy pre-flight (`skills/deployment/scripts/import_workflows.py` does this by default). Catch schema, action-ID, and `version_constraint` errors locally instead of at the API.
- **Standalone-workflow-first.** Produce a standalone Fusion workflow that runs directly against a CID. Reach for a Foundry app (`manifest.yml`, UI, functions, collections) only when the request genuinely needs one — then route to `foundry-skills`.
- **Resolve credential config IDs; do not invent them.** HTTP Actions and plugin actions reference a `config_id` created in the Falcon console and specific to the CID. Discover an existing one or ask the user where to find it — a fabricated config ID fails at runtime.
Expand Down Expand Up @@ -147,7 +147,7 @@ Re-importing a name that already exists is flagged by the duplicate check; renam
Quality matters more than speed. Specifically:

- **Validate everything.** Run `validate.py` after authoring and rely on the import pre-flight; do not push unvalidated YAML to the API.
- **No placeholders.** Every action `id` must be a real 32-char hex value resolved via `action_search.py`. A `PLACEHOLDER_*` string in output YAML means a step was skipped — go back and resolve it.
- **No placeholders.** Every action `id` must be a real value resolved via `action_search.py`. A `PLACEHOLDER_*` string in output YAML means a step was skipped — go back and resolve it.
- **Test before declaring done.** A returned `definition_id` means imported, not working. Release it, trigger it with real parameters, and confirm the execution reached a terminal `Succeeded` state with `monitor_execution.py` before calling the task complete.
- **Read each skill's Common Pitfalls section** before working in that phase.

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
### Fixed

- **`validate.py` now flags `WorkflowCustomVariable.<name>` references to variables that nothing declares** — a release-only failure. A reference to a custom variable that no `CreateVariable` (or `UpdateVariable` setter) declares imports and validates cleanly, then fails at release with `property "..." contains unknown variable "WorkflowCustomVariable.<name>"`. The validator now collects declared variable names and reports an undeclared reference before you deploy.
- **`validate.py` no longer rejects valid action IDs that aren't 32-char hex.** The action-ID check assumed every ID was a 32-character hex string (or a `<hex>_<hex>` / `<hex>~<hex>` compound), but real catalog actions carry other shapes — a 26-character ULID joined to a hex id (custom IOC / API-integration actions), unequal compound halves (event query actions), and longer hex strings (RTR actions). Those imported fine yet were flagged as invalid locally. IDs are now treated as opaque catalog identifiers, so the check still rejects placeholders (UPPER_SNAKE tokens, punctuation, all-same-character) without blocking real IDs. Docs updated to describe action IDs as identifiers you look up rather than "32-char hex".

## [1.1.0] - 2026-08-19

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The skills enforce discipline to prevent common failures. When you catch yoursel

| Thought | Reality |
|---------|---------|
| "I'll write the YAML without searching actions" | STOP. Run `action_search.py` first — action IDs are 32-char hex, only discoverable via API |
| "I'll write the YAML without searching actions" | STOP. Run `action_search.py` first — action IDs are opaque catalog identifiers, only discoverable via API |
| "I'll use a placeholder ID for now" | NEVER. Resolve every action ID before writing YAML. No `PLACEHOLDER_*` values |
| "version_constraint is optional" | WRONG. Every action requires it: `~<major>` of its `semantic_version` (`~0` when none, e.g. Charlotte AI at `0.0.100`) |
| "Validation can wait until deploy" | NO. Authoring validates; deployment validates again as a pre-flight |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ hooks/ intent routing + cross-plugin advisories

A workflow goes from idea to running in five steps. The orchestrator coordinates them; here is the shape:

1. **Discover actions** — find real 32-char-hex action IDs from the live catalog:
1. **Discover actions** — find real action IDs from the live catalog:
```bash
python skills/authoring/scripts/action_search.py --search "contain"
```
Expand Down
8 changes: 4 additions & 4 deletions skills/authoring/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ metadata:
> connector-dependent and silently returns nothing on many tenants. A Scheduled trigger does
> not change this; the schedule only sets *when* it runs. (Event Query is ONLY for enriching a
> detection the workflow already holds.) See `references/event-query-vs-api.md`.
> 2. Resolve a real 32-char hex ID for **every** action BEFORE writing any YAML:
> 2. Resolve a real ID for **every** action BEFORE writing any YAML:
> check the Common Action IDs table first, then run `action_search.py --search`
> only for actions the table does not cover.
> 3. Run `trigger_search.py` to confirm the trigger type.
Expand Down Expand Up @@ -97,7 +97,7 @@ Follow these steps in order — do not skip discovery (steps 1–2).

### 1. Resolve action IDs (MANDATORY)

Every action needs a real 32-char hex `id` before you write any YAML. Resolve
Every action needs a real `id` from the catalog before you write any YAML. Resolve
them **table-first**: check the Common Action IDs table below, and only run
`action_search.py` for the actions it does not cover. Guessing an ID or shipping
a `PLACEHOLDER_*` is never acceptable — but a verified ID from the table is
Expand Down Expand Up @@ -146,7 +146,7 @@ biggest time sink.
../../scripts/python.sh scripts/action_search.py --details <action_id>
```

For each action you discover, record: `id` (32-char hex), `name`, input
For each action you discover, record: `id` (an opaque catalog identifier), `name`, input
fields/types, its `version_constraint` (nearly all have one), `class` if any,
and whether it is a plugin action (needs a `config_id`).

Expand Down Expand Up @@ -243,7 +243,7 @@ force an immediate refresh so newly shipped action types are never hidden.
| "I'll write the YAML, then fill in action IDs later." | STOP. Resolve every ID first — from the Common Action IDs table, or `action_search.py`. "Later" never happens — placeholders ship. |
| "I'll search for the Event Query / HTTP / Send email / Charlotte AI action." | DON'T. Those are in the Common Action IDs table — use the row directly. |
| "I'll run `action_search.py \"event query\"` to search." | WRONG FLAG. A bare term prints usage and finds nothing. Use `action_search.py --search \"event query\"`. |
| "I can guess the action ID format." | WRONG. IDs are 32-char hex, only discoverable via the table or the live API. |
| "I can guess the action ID format." | WRONG. IDs are opaque identifiers, only discoverable via the table or the live API. |
| "The template has `PLACEHOLDER_RAN_006`, I'll copy it." | NEVER. Templates are structural guides. Substitute a real value before saving. |
| "Validation can wait until deploy." | NO. Validate after authoring — `validate.py` catches PLACEHOLDERs, bad IDs, and schema errors locally. |
| "Only class-based actions need `version_constraint`." | WRONG. Not class-specific — nearly every action has a `version_constraint`. |
Expand Down
4 changes: 2 additions & 2 deletions skills/authoring/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ own automations. The structure follows the documented Fusion workflow YAML schem
a `trigger` that starts the workflow, `actions` (and `loops` / `conditions`) that do
the work, and `output_fields` that surface results to the caller.

**All action IDs are real values from the CrowdStrike platform.** The 32-character
hex IDs (such as `702d15788dbbffdf0b68d8e2f3599aa4` for Create variable) are global
**All action IDs are real values from the CrowdStrike platform.** These IDs
(such as `702d15788dbbffdf0b68d8e2f3599aa4` for Create variable) are global
and work across clouds. CrowdStrike-native actions import directly; third-party
actions (Slack, Zscaler, PAN NGFW) reference plugin instances that are specific to
your CID and need to be configured before the workflow will run.
Expand Down
2 changes: 1 addition & 1 deletion skills/authoring/references/http-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Every HTTP Action carries an `http_transaction` map under `properties`:

```yaml
MyRequest:
id: <32-char hex from action_search.py>
id: <action id from action_search.py>
class: Inline.HTTPRequest
name: Cloud HTTP Request - VirusTotal IP
version_constraint: ~1
Expand Down
2 changes: 1 addition & 1 deletion skills/authoring/references/yaml-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Each action is a named node with a unique label (PascalCase recommended).
```yaml
actions:
ContainDevice: # Node label — referenced by next/conditions
id: bec9fbeb... # 32-char hex from the action catalog (global, not per-CID)
id: bec9fbeb... # opaque id from the action catalog (global, not per-CID)
name: Contain device # Display label — defaults to the catalog name, but you can rename it freely (next:/conditions resolve by node key and id, not this label)
next: # Next node(s) to execute
- UpdateVariable
Expand Down
16 changes: 14 additions & 2 deletions skills/authoring/scripts/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,18 @@

REQUIRED_KEYS = {"name", "trigger"}
PLACEHOLDER_PATTERN = re.compile(r"PLACEHOLDER_[A-Z_]+")
ACTION_ID_PATTERN = re.compile(r"^[0-9a-f]{32}([_~][0-9a-f]{32})?$")
# An action id from the catalog is an opaque token: one or more segments of
# lowercase letters and digits, joined by '_' or '~'. Do NOT assume a fixed
# length or hex-only alphabet — real catalog ids vary by action type. Live
# examples from the platform:
# ad9b77de3da84531b79740e5b4076571 (32 hex, plain)
# 089012345678abcdef9012a3b4c5d6e7f8 (34 hex, RTR)
# 01gvk6e58p1815t6gz84000001_18df367939034f1bb97e336c5cd14de4 (26-char ULID _ 32 hex, custom IOC / API integration)
# 96915a3748b64a079458e21f1fdf4b8c_<longer second segment> (event query, unequal halves)
# The lowercase-only alphabet still rejects UPPER_SNAKE placeholders
# (PLACEHOLDER_*, VIRUSTOTAL_CONFIG_ID) and anything with punctuation or spaces,
# which is what this check is really guarding against.
ACTION_ID_PATTERN = re.compile(r"^[0-9a-z]+([_~][0-9a-z]+)*$")
# A real credential config id (HTTP action definition_id) is exactly 32 hex
# chars, e.g. 7227ab386bd646c18b27716e8fff8d26. Anything else — an
# ALL_CAPS_UNDERSCORE token or a PLACEHOLDER_* — is a placeholder that imports
Expand Down Expand Up @@ -278,7 +289,8 @@ def _validate_action(label, action, issues):
elif not ACTION_ID_PATTERN.match(str(action["id"])):
issues.append(
f"ERROR: Action '{label}' has invalid id '{action['id']}' "
f"(must be 32-char hex, or a compound plugin id '<hex>_<hex>' / '<hex>~<hex>')"
f"(not a catalog identifier — expected lowercase letters and digits, "
f"optionally joined by '_' or '~'. Run action_search.py to get the real ID)"
)
elif len(set(str(action["id"]))) == 1:
issues.append(
Expand Down
Loading