Preview before signing

POST /api/preview answers what would this mandate permit? with no wallet, no key and no gas. It registers the terms on a simulated chain, fires a plan at requestAction, and returns what the guard did with each action.

curl -X POST https://<host>/api/preview \
      -H 'content-type: application/json' \
      -d '{"terms":{"maxAggregate":"1000000000000"},"actions":12}'

The preview is the contract, not a model of it: the same artifacts, the same evaluation order, flags from IntentRecorded and every figure read back from headroom. Halving clause 3.4 moves the hold from action 9 to action 5. Nothing settles, and nothing enters the evidence spine.

Searching what it permits

A preview answers a question somebody asked. POST /api/stress answers the one nobody asks before signing: not would this plan pass, but what is the most that comes out of a mandate written this way.

curl -X POST https://<host>/api/stress \
      -H 'content-type: application/json' \
      -d '{"terms":{"maxAggregate":"10000000000000","maxActions":16,"maxOffPrimaryBps":10000}}'

It takes the same bounds and runs a grid of strategies against them, each with a mandate and a window of its own, sizing every action to fit, routing a share of them off-primary, and retrying smaller when one is held, because a hold consumes no budget and is not a lockout. The report is the frontier: the most any strategy settled, the most it concentrated, which clause bound the most of them, and what the search covered.

Nothing here evaluates an invariant. Every verdict is requestAction on the same bytecode testnet runs, and the search is bounded twice, by a fixed grid and a wall clock, and reports what it covered rather than only what it found. A search that runs until it finds something always finds something.

The finding that matters most is the one a loose mandate produces, and a loose mandate stops nothing. Write ten million into clause 3.4, sixteen actions into 3.5 and a per-action cap of 250,000, and no strategy is ever held: the report says the window can never reach ten million at all, because sixteen actions at 250,000 is four million.

The aggregate an owner wrote as the most this may cost me in an hour is decorative, and clause 3.5 is the control. That is arithmetic over figures the contract returned, not a verdict: a mandate that permits four million is not a defect, it is a fact its owner is entitled to know at signing time and currently learns after an incident.

What the sandbox will do for you

Neither route takes a key, both do real work on a chain, and they share one queue — the simulated node has a single owner key, and two writers race on its nonce. So there is a ceiling, and it is stated rather than discovered: twelve searches and a hundred and twenty previews per connection per ten minutes, and one search at a time across the whole deployment.

A second search is refused with 429 and a retry-after rather than queued behind the first. Queueing is what turns ten requests into a minute of everybody else waiting, and “busy, come back in ten seconds” is the honest answer when the work is genuinely unavailable.

A revert is answered as a refusal too, in the contract’s own words: {"error":"the venue returned less than clause 2.5's floor, measured on what arrived"} under a 400. Both routes used to hand back the whole viem error — internal addresses, calldata and all — under a 500.