Skip to main content

Test action behavior before an agent runs

Action Tests are executable contracts for consequential tool calls. Each case supplies a proposed action, trusted invocation context, deterministic Source records, and the outcome ActionRail must return:

proposed action + trusted context + Source fixtures → allow | hold | block

The runner calls the same rule loader, policy evaluator, grounding matcher, and decision pipeline used during runtime enforcement. It does not maintain a simplified test-only interpretation of your rules.

That makes Action Tests useful for reviewing rule changes in the same way you review application code. A pull request can prove that a legitimate refund is still allowed, a high-value refund is still held, and a cross-customer refund is still blocked.

Create an action contract​

Keep runtime rules in actionrail.yaml and test cases in actionrail.cases.yaml. The test file has its own versioned schema:

actionrail.cases.yaml
schema_version: 1

cases:
- name: cross-customer refund is blocked
tool: issue_refund
args:
order_id: order-100
amount: 75
context:
customer_id: customer-2
expect: block
fixtures:
billing-production:
by_value:
order-100:
customer_id: customer-1
status: delivered

This case exercises a rule that grounds order_id through the billing-production Source and compares the returned customer_id with trusted context. The proposed order is real, but it belongs to another customer, so the required contract is block.

Run the contract​

From the directory containing both files:

actionrail test

Or pass explicit paths:

actionrail test config/actionrail.yaml \
--cases tests/actionrail.cases.yaml

The command prints each expected and actual outcome plus the local decision reasons:

ActionRail Action Tests

PASS owned delivered order is refundable
issue_refund: expected allow, got allow
PASS cross-customer refund is blocked
issue_refund: expected block, got block
PASS refund above the automatic limit is held
issue_refund: expected hold, got hold

3 passed · 0 failed

Exit code 0 means every contract passed. A mismatch returns 1; an invalid rule or Action Tests document returns 2. See Continuous integration for a ready-to-use CI pattern.

Fixtures model Sources, not adapters​

A fixture is keyed by the Source name referenced in the runtime rule. It provides the records that Source would return for an argument value:

fixtures:
billing-production:
by_value:
order-100:
customer_id: customer-1
status: delivered
order-404: null
  • an object represents one returned record;
  • a list represents multiple returned records;
  • null represents zero returned records;
  • default can define rows for values not listed under by_value.

The shared matcher still evaluates row-count conditions, field operators, trusted-context comparisons, cross-argument comparisons, and time conditions. Only the adapter I/O is replaced.

Fixtures are intentionally Source-oriented rather than Postgres-, MySQL-, HTTP-, or MCP-specific. The same contract remains valid if a team changes the adapter behind billing-production without changing the meaning of its rules.

Production Sources are off by default​

Normal Action Tests never construct or call the Sources declared in actionrail.yaml. Every Source referenced by the selected action must have a fixture. This gives pull-request CI three useful properties:

  • no production credentials are required;
  • tests are repeatable and fast;
  • a test command cannot unexpectedly query a customer system.

Use --live-sources only for an explicit integration-test stage:

actionrail test --live-sources

When enabled, a case may omit a fixture and use the configured Source instead. Fixtures still take precedence, so one suite can combine deterministic and live checks. Treat this option as real network and data-plane access.

Test hostile and incomplete proposals​

args and context describe the exact proposal being tested. They may omit a field intentionally. This is useful for proving fail-closed behavior when an agent omits a required value or authenticated context is unavailable:

- name: missing trusted customer is blocked
tool: issue_refund
args:
order_id: order-100
amount: 75
context: {}
expect: block
fixtures:
billing-production:
by_value:
order-100:
customer_id: customer-1
status: delivered

Recommended minimum coverage for each consequential action:

  1. one ordinary action that must be allowed;
  2. every policy boundary that must hold;
  3. wrong-owner, wrong-state, missing-record, and stale-record blocks;
  4. missing or malformed proposed arguments;
  5. missing trusted-context fields;
  6. Source outage behavior in a separate live or lower-level integration test.

Run the repository example​

An editable checkout includes a complete four-case contract:

actionrail test examples/action_tests/actionrail.yaml \
--cases examples/action_tests/actionrail.cases.yaml

It declares a production-style Postgres Source but uses fixtures, so the example requires neither Postgres nor credentials.

For every accepted field, continue to the Action Tests schema reference.