Jobs

Orchestrating multiple scenarios with shared pools and bindings.

A job coordinates multiple scenario workloads, each drawing from shared pools and running concurrently.

Structure

type: job
name: Detect IAM user brute force
pools:
  - id: iam-users
    type: pool
    csv:
      path: ./users.csv
workloads:
  - scenario: ./brute-force.yaml
    count: 10
    bindings:
      target_user: pool.iam-users.username

Name

The top-level name is the job's authored, customer-recognizable Test name — a short phrase describing the behavior the job exercises (for example, Detect anomalous S3 object exfiltration). It is display metadata: it never becomes a database identity, and it is not part of a Test case's identity digest or of Detection matching, so naming or renaming a job does not re-create its Test cases.

Author a name for every job — it is the primary way a Test is recognized throughout the product.

Pools

Pools provide reusable datasets — IP ranges, user lists, CSV files. They are declared at the job level and referenced in workload bindings via pool.<id> or pool.<id>.<field>.

Pool types:

TypeDescription
ip_rangeGenerates IPs from a CIDR block (max 1,048,576 IPs)
string_listA static list of string values
csvRows loaded from a CSV file

Bindings

Bindings map pool draws and generator outputs to scenario state variables. Each binding expression is evaluated and injected into the scenario's state before execution.

Bindings are re-evaluated on every iteration of a loop: workload (and on every combo of a matrix: workload), so a binding such as target_user: pool.iam-users draws a fresh user per iteration, and request_id: gen.uuid() produces a fresh UUID per iteration.

Job-level state is evaluated once at job start and is available to bindings via ref.* (for example account_id: ref.account_id); it stays stable across iterations.

Important: pool.* expressions are only valid in job state and workload bindings — they are rejected if used directly in scenario state or fields blocks.

Concurrency

Each workload runs in its own goroutine. The job runner forks a per-goroutine clock so concurrent workloads advance logical time independently. If any workload errors, the shared context is cancelled and sibling workloads stop.