Guards

Reference for all guard kinds that control when transitions can fire.

Guards are preconditions on transitions. Every guard in a transition’s guards array must pass before the transition fires. If any guard fails, the gateway rejects the submission with GUARD_REJECTED and the workflow state doesn’t change.

You declare guards in YAML – no code needed. They evaluate at submit time, before the executor runs.

Guard kinds

Kind Purpose
permission Principal must hold a named permission
role Principal must hold a named role
expr Binary predicate on workflow context or input
evidence Requires specific evidence artifacts from earlier in the workflow
guidance_acknowledged Passes iff gateway.describe was called for the named subject and the recorded body-hash matches the current snapshot
script_acknowledged Like guidance_acknowledged, but for a script in the script library
all_of All sub-guards must pass
any_of At least one sub-guard must pass
not Inverts a sub-guard

jsonpath is accepted as a deprecated alias of expr (it logs a warning – use expr).

permission

Checks that the submitting principal has a specific permission. Useful for RBAC on sensitive transitions.

guards:
  - kind: permission
    permission: workflow.approve

The principal’s permissions come from whatever identity layer you’ve wired in. If the principal doesn’t have workflow.approve, the transition is rejected.

role

Checks that the principal holds a specific role. Similar to permission but operates on role names.

guards:
  - kind: role
    role: manager

expr

A binary predicate evaluated against the workflow’s context, input, or arguments. This is the most flexible guard – you can check any value in the workflow state.

guards:
  - kind: expr
    expr: "$.context.fmeca.maxResidualRpn <= 80"

Format

Every expression follows this pattern:

$.path.to.value <operator> <operand>

Operators

Operator Meaning
== Equal
!= Not equal
> Greater than
< Less than
>= Greater than or equal
<= Less than or equal
starts_with String prefix match
contains String substring match

Path resolution

Paths use dot notation starting with $:

Prefix Resolves to
$.context.* The workflow instance’s accumulated context
$.workflow.input.* The input passed when the workflow was started
$.arguments.* The arguments passed to the current transition
$.workflow.id The workflow instance ID
$.workflow.state The current state name
$.workflow.version The definition version

Array indexing uses brackets: $.context.items[0].name

An unset $.context.* slot fails fast with GUARD_UNSET_SLOT rather than evaluating to false. Unset $.arguments.* and $.workflow.input.* paths coalesce to null instead.

Operands

Operands can be:

Examples

# Simple value check
guards:
  - kind: expr
    expr: "$.context.policyOk == true"

# Numeric comparison
guards:
  - kind: expr
    expr: "$.context.riskScore <= 80"

# String comparison
guards:
  - kind: expr
    expr: "$.context.status == \"approved\""

# Check workflow input
guards:
  - kind: expr
    expr: "$.workflow.input.amount > 1000"

# Check transition arguments
guards:
  - kind: expr
    expr: "$.arguments.confirmed == true"

# Boolean negation
guards:
  - kind: expr
    expr: "$.context.requiresFinance == false"

evidence

Requires that specific evidence artifacts have been recorded earlier in the workflow. Evidence is accumulated as executors run – for example, the human executor records evidence when a human acts.

guards:
  - kind: evidence
    requires:
      - tests_passed
      - acceptance_criteria_met

All listed evidence kinds must be present. This is useful for defense-in-depth: even if state ordering allows a transition, the evidence guard ensures the prerequisite actions actually happened.

You can also require a count of evidence records:

guards:
  - kind: evidence
    requires:
      - { kind: human_request, count: 2 }

This is how you implement quorum approvals – require two human approvals before proceeding.

guidance_acknowledged

Passes only if the principal called gateway.describe for the named subject and the body-hash recorded at that time still matches the current snapshot’s hash for that subject. Editing the guidance body flips the hash and invalidates the acknowledgment, forcing a re-read.

guards:
  - kind: guidance_acknowledged
    subject: deploy.runbook

Use this to require that the model has actually read a piece of guidance before a transition fires. If the subject isn’t present in the workflow’s snapshot, the guard fails loudly with GUIDANCE_SUBJECT_UNKNOWN.

script_acknowledged

The same shape as guidance_acknowledged, but operates on the script library snapshot rather than guidance. Use it to require that an operator or critic called gateway.describe on a sensitive script before the workflow may invoke it.

guards:
  - kind: script_acknowledged
    subject: deploy.production.rollout

Editing the script body flips its hash and invalidates the acknowledgment, forcing re-review.

all_of

All sub-guards must pass. This is the default behavior when you list multiple guards in an array, but all_of makes it explicit and lets you nest it inside any_of or not.

guards:
  - kind: all_of
    guards:
      - { kind: role, role: manager }
      - { kind: expr, expr: "$.context.amount <= 5000" }

any_of

At least one sub-guard must pass. Useful for “either/or” conditions.

guards:
  - kind: any_of
    guards:
      - { kind: role, role: manager }
      - { kind: role, role: finance }

This means: managers or finance can take this transition.

You can combine any_of with other guards for complex policies:

guards:
  # The submitter must be a manager OR finance...
  - kind: any_of
    guards:
      - { kind: role, role: manager }
      - { kind: role, role: finance }
  # ...AND the amount must be under the limit
  - kind: expr
    expr: "$.context.amount <= 10000"

not

Inverts a sub-guard. The transition fires only if the inner guard fails.

guards:
  - kind: not
    guard:
      kind: role
      role: intern

This means: anyone except interns can take this transition.

Combine with expr to negate conditions:

guards:
  - kind: not
    guard:
      kind: expr
      expr: "$.context.flaggedForReview == true"

Composing guards

Since all_of, any_of, and not can nest, you can express arbitrarily complex policies:

guards:
  - kind: all_of
    guards:
      # Must be a manager or admin
      - kind: any_of
        guards:
          - { kind: role, role: manager }
          - { kind: role, role: admin }
      # Must NOT be flagged for conflict of interest
      - kind: not
        guard:
          kind: expr
          expr: "$.context.conflictOfInterest == true"
      # Must have evidence of prior review
      - kind: evidence
        requires: [peer_review]

linkFilter and guards

When a workflow or state sets linkFilter: byGuards, the gateway evaluates each transition’s guards silently against the current state before building the response. Only transitions whose guards would pass appear as links. This means the model only sees what it can actually do – no dead-end links.

states:
  manager_review:
    linkFilter: byGuards
    transitions:
      approve:
        target: approved
        guards:
          - { kind: expr, expr: "$.context.riskScore <= 80" }
      escalate:
        target: executive_review
        guards:
          - { kind: expr, expr: "$.context.riskScore > 80" }

If riskScore is 50, the model only sees the “approve” link. If it’s 90, it only sees “escalate.” No wasted round trips on transitions that would fail.