Skip to content

Operator Reference

Edictum's expression grammar supports 15 operators across five categories. Each leaf in a when expression uses exactly one operator applied to one selector.

For the full schema context, see the YAML Contract Reference.


Quick Reference

Operator Category Selector Type Value Type Semantics
exists Presence any boolean Field is present (true) or absent/null (false)
equals Equality scalar scalar Strict equality
not_equals Equality scalar scalar Strict inequality
in Membership scalar array Value appears in array
not_in Membership scalar array Value does not appear in array
contains String string string Substring match
contains_any String string array of strings Any element is a substring
starts_with String string string Field starts with value
ends_with String string string Field ends with value
matches String string string (regex) Regex search matches
matches_any String string array of strings Any regex matches
gt Numeric number number Greater than
gte Numeric number number Greater than or equal
lt Numeric number number Less than
lte Numeric number number Less than or equal

Presence

exists

Tests whether a field is present and non-null, or absent/null.

  • Value type: boolean (true or false)
  • Selector type: any
  • Behavior with true: passes when the field exists and is not null.
  • Behavior with false: passes when the field is missing or null.

This is the only operator that works on missing fields. All other operators evaluate to false when the field is absent.

# Deny if no ticket reference is attached to the principal
- id: require-ticket
  type: pre
  tool: deploy_service
  when:
    principal.ticket_ref: { exists: false }
  then:
    effect: deny
    message: "A ticket reference is required for deployments."
# Deny only when the optional 'force' flag is explicitly set
- id: block-force-flag
  type: pre
  tool: delete_resource
  when:
    args.force: { exists: true }
  then:
    effect: deny
    message: "The 'force' flag is not permitted."

Equality

equals

Strict equality comparison using Python's == operator.

  • Value type: any scalar (string, number, boolean)
  • Selector type: scalar
# Deny tool calls in the production environment
- id: block-production
  type: pre
  tool: "*"
  when:
    environment: { equals: production }
  then:
    effect: deny
    message: "Tool calls are disabled in production."
# Deny when a specific tool argument matches a value
- id: block-admin-database
  type: pre
  tool: query_database
  when:
    args.database: { equals: "admin" }
  then:
    effect: deny
    message: "Direct queries to the admin database are blocked."

not_equals

Strict inequality comparison using Python's != operator.

  • Value type: any scalar
  • Selector type: scalar
# Deny if the request is not targeting the staging environment
- id: staging-only
  type: pre
  tool: run_migration
  when:
    environment: { not_equals: staging }
  then:
    effect: deny
    message: "Migrations are only allowed in the staging environment."

Membership

in

Tests whether the selector's value appears in a provided list.

  • Value type: array (at least one element)
  • Selector type: scalar
# Allow only specific roles to use the deploy tool
# (deny everyone NOT in the list)
- id: deploy-role-gate
  type: pre
  tool: deploy_service
  when:
    principal.role: { not_in: [sre, admin, senior_engineer] }
  then:
    effect: deny
    message: "Your role does not have deploy permissions."
# Deny calls to known dangerous tools
- id: block-dangerous-tools
  type: pre
  tool: "*"
  when:
    tool.name: { in: [drop_database, truncate_table, format_disk] }
  then:
    effect: deny
    message: "Tool '{tool.name}' is permanently blocked."

not_in

Tests whether the selector's value does NOT appear in a provided list.

  • Value type: array (at least one element)
  • Selector type: scalar
# Only allow API calls to approved endpoints
- id: approved-endpoints-only
  type: pre
  tool: call_api
  when:
    args.endpoint: { not_in: ["/v1/users", "/v1/search", "/v1/health"] }
  then:
    effect: deny
    message: "Endpoint '{args.endpoint}' is not in the approved list."

String

String operators require the selector to resolve to a string. If the resolved value is not a string, the operator triggers a policy_error (fail-closed).

contains

Substring match. Passes when the operator value appears anywhere in the selector's string value.

  • Value type: string
  • Selector type: string
# Block reads of files with ".env" in the path
- id: block-env-reads
  type: pre
  tool: read_file
  when:
    args.path: { contains: ".env" }
  then:
    effect: deny
    message: "Reading .env files is not allowed."

contains_any

Passes when any element in the provided array is a substring of the selector's value. This is a convenience operator equivalent to an any block with multiple contains leaves.

  • Value type: array of strings (at least one element)
  • Selector type: string
# Block reads of multiple sensitive file patterns in one rule
- id: block-sensitive-reads
  type: pre
  tool: read_file
  when:
    args.path:
      contains_any: [".env", ".secret", "credentials", ".pem", "id_rsa", "kubeconfig"]
  then:
    effect: deny
    message: "Sensitive file '{args.path}' is blocked."
    tags: [secrets, dlp]

starts_with

Passes when the selector's string value starts with the operator value.

  • Value type: string
  • Selector type: string
# Block writes to absolute paths (enforce relative-only writes)
- id: block-absolute-writes
  type: pre
  tool: write_file
  when:
    args.path: { starts_with: / }
  then:
    effect: deny
    message: "Write to absolute path '{args.path}' blocked. Use relative paths."
    tags: [write-scope]

ends_with

Passes when the selector's string value ends with the operator value.

  • Value type: string
  • Selector type: string
# Warn when output is written to a log file
- id: warn-log-output
  type: post
  tool: write_file
  when:
    args.path: { ends_with: ".log" }
  then:
    effect: warn
    message: "Output written to log file '{args.path}'. Verify no sensitive data is logged."
    tags: [logging]

matches

Regular expression match using Python's re.search(). The pattern can match anywhere in the string (it is not anchored).

  • Value type: string (valid Python regex)
  • Selector type: string

Patterns are compiled once at policy load time. An invalid regex causes a validation error.

YAML tip: Always use single-quoted strings for regex patterns. In YAML, '\b' is a literal backslash-b (regex word boundary). Double-quoted "\b" is interpreted as a backspace character.

# Block recursive delete commands
- id: block-rm-rf
  type: pre
  tool: bash
  when:
    args.command: { matches: '\brm\s+(-rf?|--recursive)\b' }
  then:
    effect: deny
    message: "Recursive delete blocked: '{args.command}'."
    tags: [destructive]
# Warn on SSN patterns in tool output
- id: ssn-in-output
  type: post
  tool: "*"
  when:
    output.text: { matches: '\b\d{3}-\d{2}-\d{4}\b' }
  then:
    effect: warn
    message: "Possible SSN detected in output. Redact before using."
    tags: [pii]

matches_any

Passes when any regex pattern in the array matches the selector's value. Equivalent to an any block with multiple matches leaves, but more concise.

  • Value type: array of strings (valid Python regex patterns, at least one)
  • Selector type: string
# Detect multiple PII patterns in a single rule
- id: pii-detection
  type: post
  tool: "*"
  when:
    output.text:
      matches_any:
        - '\b\d{3}-\d{2}-\d{4}\b'                                        # US SSN
        - '\b[A-Z]{2}\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{0,2}\b'  # IBAN
        - '\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b'                   # credit card
  then:
    effect: warn
    message: "PII pattern detected in output. Redact before using."
    tags: [pii, compliance]

Numeric

Numeric operators require both the selector value and the operator value to be numbers (int or float). If the selector resolves to a non-numeric type, the operator triggers a policy_error (fail-closed).

gt

Greater than.

  • Value type: number
  • Selector type: number
# Deny requests with a batch size greater than 1000
- id: limit-batch-size
  type: pre
  tool: bulk_insert
  when:
    args.batch_size: { gt: 1000 }
  then:
    effect: deny
    message: "Batch size {args.batch_size} exceeds the maximum of 1000."

gte

Greater than or equal.

  • Value type: number
  • Selector type: number
# Deny requests where retry count is 5 or more
- id: limit-retries
  type: pre
  tool: call_api
  when:
    args.max_retries: { gte: 5 }
  then:
    effect: deny
    message: "Max retries of {args.max_retries} is too high. Use 4 or fewer."

lt

Less than.

  • Value type: number
  • Selector type: number
# Warn when the confidence score is below threshold
- id: low-confidence-warning
  type: post
  tool: classify_document
  when:
    args.min_confidence: { lt: 0.5 }
  then:
    effect: warn
    message: "Classification confidence threshold is below 0.5. Results may be unreliable."

lte

Less than or equal.

  • Value type: number
  • Selector type: number
# Deny requests with a timeout of 0 or negative
- id: block-zero-timeout
  type: pre
  tool: call_api
  when:
    args.timeout: { lte: 0 }
  then:
    effect: deny
    message: "Timeout must be a positive number. Got {args.timeout}."

Type Mismatch Behavior

When an operator receives a value of the wrong type (for example, contains applied to an integer, or gt applied to a string), the evaluation triggers a policy_error. This means:

  • The contract fires (fail-closed design).
  • The audit event includes policy_error: true.
  • For preconditions and session contracts, this results in a deny.
  • For postconditions, this results in a warn.

This behavior is intentional. If Edictum cannot evaluate a rule, it assumes the worst case and fires the rule rather than silently ignoring it.

Missing Field Behavior

When a selector references a field that does not exist:

  • The exists operator with false returns true (the field is indeed absent).
  • The exists operator with true returns false (the field is not present).
  • All other operators return false (the rule does not fire).

This means a rule like args.path: { contains: ".env" } will not fire if the tool call has no path argument. No error is raised.