Stopping a call
A rule carries a mode, and the mode is the only thing that changes between watching a run and stopping it. deny is the one to reach for first: the call is refused, and the reason goes back where the tool’s output would have gone.
const SEC_01 = Presets.requireBefore('restartService', 'queryMetrics', { id: 'SEC-01', description: 'restartService may not run before queryMetrics has returned', mode: 'deny',})What the agent gets back
Section titled “What the agent gets back”Instead of the tool’s result, an object it can read:
{ "blocked": true, "message": "blocked by SEC-01: restartService may not run before queryMetrics has returned", "violations": [{ "ruleId": "SEC-01", "seq": 0, "subformula": "O toolResult(queryMetrics)" }]}A model reads that, does the work the rule was asking for, and tries again. This is why deny composes with an agent in a way that throwing does not: an exception ends the run, while a refusal is information.
The refused call is not in the trace
Section titled “The refused call is not in the trace”Look at the transport in that run: seven events, and none of them is the refused restartService. A call that was refused never happened, so there is nothing to record — the only evidence is the decision the guard made, which is what the red path is drawn from.
That is worth holding onto, because it has a consequence for verifying a rule set: a run recorded while a rule was enforcing has nothing left in it for that rule to catch.
When to use something else
Section titled “When to use something else”| The agent could reasonably do something else instead | deny — this page |
| A person has to decide, and might take a while | suspend |
| The rule is new and has to earn trust | observe |
| Nothing downstream should happen and nobody will be asked | abort, covered in enforcement |