raxit
Capabilities

Permit / defer / deny / modify / step-up

The five decision outcomes: permit, defer, deny, modify, step-up.

Every governed tool call resolves to exactly one outcome. What you see at the terminal:

>>> agent calls calc({'expression': '21 * 2'})
<<< result: 42

[raxit PENDING] 'post_to_webhook' deferred — waiting for human approval (up to 120s).
  Token:   dt_1373bb3e0bcf139d
  Approve: raxit approvals approve dt_1373bb3e0bcf139d
  Deny:    raxit approvals deny dt_1373bb3e0bcf139d

<<< [raxit BLOCKED] 'post_to_webhook' denied (reason: deny.approval_expired). The operation was
    not executed. Details: raxit explain d867c63661454e8497369a08ebaec35d

The first call ran silently — that's permit. The second suspended and printed a token — that's defer. Left unapproved for 120s, it resolved to a hard [raxit BLOCKED] — that's deny.

The vocabulary is permit | defer | deny — one, not two

Cedar's native decision is binary: Allow or Deny. raxit needs a third state — a call that's neither permitted nor denied, but suspended pending a human. It produces that third state with a Cedar policy annotation, not a second policy language: a contributing permit can carry @effect("defer") or @effect("step_up"), and the engine reads that annotation to resolve the final decision. There is exactly one decision vocabulary end to end — permit | defer | deny — never a second "outcome/status" split.

The resolution chain

1. Authorize the normalized tool call with Cedar → (Decision, Diagnostics)
2. Cedar errors                      → deny   (fail closed, never fail open)
3. Decision == Deny (forbid matched, or nothing matched)
                                      → deny
4. Decision == Allow:
     contributing permit has @effect("step_up")  → defer  (STEP_UP)
     contributing permit has @effect("defer")     → defer  (DEFER)
     contributing permit has @effect("modify")     → permit + transform, or deny if the
                                                        transform doesn't parse
     otherwise                                     → permit

The five outcomes

security.yamlCedar annotationDecision.EffectDecision.TypeReason code
effect: allow(none)PermitALLOWpermit
effect: reviewdefer (step_up only with a when: guard)DeferDEFER (STEP_UP with when:)defer.policy (defer.step_up with when:)
effect: blockforbidDenyDENYdeny.default_or_forbid
modify: {...}modify + transformPermit (or Deny if transform invalid)MODIFY (or DENY)modify.policy (or deny.modify_error)
Cedar eval errorDenyDENYdeny.eval_error

effect: review is generic defer by default — step_up only with a when: guard. A plain effect: review rule (no when:) compiles to a Sensitive tool, @effect("defer"), reason defer.policy — the same generic DEFER/defer.policy type used for non-rule defers (budget, cost, and context-completeness suspensions — e.g. defer.budget_exceeded, defer.context_incomplete). Add a when: condition to a review rule and it compiles instead to @effect("step_up"), reason defer.step_up, Decision.Type == STEP_UP: "a step-up rule requires explicit human approval for this call." Both land in the same coarse defer bucket when you only need expect: defer in a test; use expect_type: STEP_UP vs expect_type: DEFER when you need to tell them apart.

modify is permit-with-transformation, not a fourth effect

- resource: send_email
  modify:
    redact: [body, subject]

A modify: rule still permits the call — the tool runs — but with the declared transform applied to the named argument paths first (redact, hash, or drop). If the transform can't be applied, the call fails closed to deny.modify_error rather than silently forwarding the original, untransformed arguments. See Rules & effects for the full modify: grammar.

Fail-closed at every step

Notice step 2 in the resolution chain above: a Cedar evaluation error is a deny, never a silent pass-through. This is one instance of a rule that holds everywhere in raxit — see Fail-closed & degrade-closed for the general principle.

On this page