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:
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;
nullrepresents zero returned records;defaultcan define rows for values not listed underby_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:
- one ordinary action that must be allowed;
- every policy boundary that must hold;
- wrong-owner, wrong-state, missing-record, and stale-record blocks;
- missing or malformed proposed arguments;
- missing trusted-context fields;
- 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.