Job Schema
Complete field reference for job configuration files.
Complete field reference for job configuration files. For conceptual overview, see job concepts.
IDE Integration
Add this directive to the top of your YAML file for autocomplete and validation:
# yaml-language-server: $schema=https://tracemill.dev/schemas/job.schema.jsonProperties
| Field | Type | Required | Description |
|---|---|---|---|
type | "job" | Yes | Discriminator. |
description | string | No | Free-text description. |
tags | string[] | No | Classification tags (unique values). |
mitre | object | No | MITRE ATT&CK mapping. Same structure as scenario mitre. |
detection | object | No | Default detection this job validates (Splunk jobs). Inherited by every workload with an expectation, unless that workload overrides it via expectation.detection. See detection. |
state | object | No | Job-level variables. See state. |
pools | object[] | No | Pool registry. See pools. |
workloads | object[] | Yes | Min 1. See workload. |
state
A map of variable names to ExprStr values, evaluated once before workloads start.
Unlike scenario state, pool.* expressions are allowed here. Job state is available to workload bindings via ref.*, but does not flow to scenarios implicitly — use explicit bindings (e.g. account_id: ref.account_id).
Object/array state: values are not opaque blobs: string leaves nested at any depth evaluate as ExprStr once at job start, matching top-level state semantics. pool.* is allowed at any depth (not just at the top level), and nested ref.* leaves participate in the same cycle / undefined-ref checks as top-level refs.
state:
account_id: "000000000000"
region: us-east-1
api_endpoint: "https://api.example.com:8443"State values may be overridden at invocation via the CLI's --set key=value flag (repeatable). Override keys must be declared in this state block — unknown keys are rejected before any workload runs. Override values are YAML-decoded into the same type set as the YAML state block, and string overrides follow the ExprStr grammar (so --set region=gen.aws_region() resolves at runtime). Nested string leaves inside an override map or list value follow the same ExprStr rules as declared nested job state.
pools
An array of pool entries. Each entry is either an inline pool definition or a reference to an external pool file. See pool schema for the full pool configuration.
Inline pool
Defines the pool directly in the job file. The type field is optional for inline pools.
pools:
- id: threat-ips
sampling:
mode: random
seed: 42
ip_range:
cidrs:
- 198.51.100.0/24External pool reference
Points to a standalone pool file by path or content ID. The job entry's id is used regardless of what id the file contains.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Pool ID for this job. Pattern: ^[a-zA-Z][a-zA-Z0-9_-]*$ |
description | string | No | |
path | string | Yes | Content ID (e.g. resources/pools/threat-ips) or file path (starting with ./, ../, /, or ~). |
pools:
- id: users
path: ./pools/users.yamlworkload
Each workload runs a scenario with optional concurrency, looping, and bindings.
| Field | Type | Required | Description |
|---|---|---|---|
scenario | string | Yes | Scenario to run: a content ID (e.g. scenarios/aws/cloudtrail/brute-force) or a file path (starting with ./, ../, /, or ~). |
concurrency | integer | No | Parallel goroutines running the scenario. Default: 1. Min: 1. |
loop | integer | "infinite" | No | Repetitions: 0 or 1 = run once, N = run N times, "infinite" or -1 = run until cancelled. Default: 0. Mutually exclusive with matrix. |
start_after | string | No | Delay before this workload starts, relative to job start. Duration format. |
eps | number | No | Events-per-second rate limit. 0 = unlimited. Min: 0. |
matrix | object | No | Parameterised axes. Each key is an axis name; the value is an inline list or pool.<id> reference. The scenario runs once per cartesian-product combination. Mutually exclusive with loop. |
bindings | object | No | State variable overrides as ExprStr values. Only explicit bindings flow to the scenario, overriding its state defaults. Can reference job state via ref.* and pools via pool.*. Re-evaluated on every loop: iteration and every matrix: combo, so gen.* and pool.* produce fresh values per iteration. |
fields | object | No | Static fields injected into every event from this workload. Event-level fields win on conflict. |
output | string | No | Output channel this workload delivers to. Omit to use the implicit default channel — keyed as the empty string "" in a run's output_bindings; an explicit output: default is a distinct channel keyed default. Ignored in CLI mode — all events go to the configured sink. Pattern: ^[a-zA-Z][a-zA-Z0-9_-]*$. |
expectation | object | No | What this workload asserts about detection behavior. A workload without one validates on delivery alone. See expectation. |
Example
workloads:
- scenario: ./scenarios/brute-force.yaml
concurrency: 2
bindings:
source_ip: pool.threat-ips
username: pool.target-usernames
account_id: ref.account_id
- scenario: ./scenarios/api-calls.yaml
loop: 10
eps: 50
start_after: 5s
fields:
environment: productionexpectation
Declares what a workload asserts about detection behavior. A workload without an expectation validates on delivery alone.
| Field | Type | Required | Description |
|---|---|---|---|
expected | "alert" | "none" | Yes | Whether the detection should fire (alert) or stay silent (none). A none control asserts a specific detection does not fire on benign input. |
summary | string | No | One-line description of what this workload validates. |
detection | object | No | The detection this workload validates. Overrides the job-level detection default for this workload. See detection. |
correlation | object | No | Map of result-field → value, tying job state to fields of the fired alert. Values are literals or ref.* job-state references (and ${ref...} interpolation of them); generator (gen.*) and pool (pool.*) expressions are rejected. |
mitre | object | No | Per-expectation MITRE mapping, supplementing the job-level block. |
workloads:
- scenario: scenarios/aws/iam/delete-policy
expectation:
expected: alert
summary: "IAM DeletePolicy removes a managed policy"detection
Records which detection a Splunk job validates — provenance linking the job to its detective control. It may appear at the job root (a default applied to every workload) and/or under a workload's expectation (overriding the default for that workload).
Resolution. For each workload that has an expectation, the effective detection is its own expectation.detection if present, otherwise the job-level detection. A workload with neither resolves to no detection — the engine permits that. The Tracemill content library additionally requires every jobs/splunk/** workload that has an expectation to resolve to a detection (enforced by library CI), so every Splunk validation job carries one in practice. Observation-only workloads (no expectation) carry no detection. The common case is a single job-level block shared by all workloads; per-workload detection is for jobs that validate different detections across workloads (e.g. a multi-stage campaign).
| Field | Type | Required | Description |
|---|---|---|---|
source | "escu" | "custom" | Yes | Provenance: escu = Splunk security_content (catalog-backed, the SIEM already operates it, enabled by name); custom = SPL carried inline. |
name | string | Yes | Detection name. For escu, the security_content name (the savedsearch is ESCU - <name> - Rule). |
id | string | When source: escu | Catalog identifier (security_content UUID) — the durable join key, stable across renames. Forbidden when source: custom. |
spl | string | When source: custom | The detection SPL, when no catalog entry backs it. Forbidden when source: escu. |
# Job-level default — the common case: all workloads validate one detection
detection:
source: escu
name: "AWS IAM Delete Policy"
id: ec3a9362-92fe-11eb-99d0-acde48001122
workloads:
- scenario: scenarios/aws/iam/delete-policy
expectation:
expected: alert
# Per-workload override — e.g. a campaign validating a different detection per stage
- scenario: scenarios/aws/iam/escalate
expectation:
expected: alert
detection:
source: custom
name: "Custom Escalation Rule"
spl: "| tstats count from datamodel=Cloud_Infrastructure by ..."