Skip to main content

What it does

theauth.explain(agentId, action, resource, options?) mirrors theauth.authorize() and returns the reasoning instead of just the verdict. It answers questions like “which rule let this through?”, “which delegation carried it?” and “why was this denied?”. It only reads. It writes no audit rows, does not increment rate limit counters, does not spend approvals, and never caches.
The call returns a Result. A denial is a successful explanation (decision: "deny"). A database failure returns EXPLAIN_LOAD_FAILED, and empty input returns EXPLAIN_INVALID_INPUT.

Result

Near misses

When no grant matches, matchedGrant is absent and nearMisses lists up to three grants that almost allowed the call, best first. Own grants and delegated grants are both considered. A grant that matched on resource and action but failed a constraint is reported as matchedGrant instead, so nearMisses stays empty then.
Near misses are computed from the same permission lists, with no extra queries and no side effects. To turn explain into CI checks, see policy testing.

Semantics

explain() follows authorize(): the first matching rule wins, own permissions are tried before delegated ones, and when both deny the own verdict stands. When a delegated rule matched but failed a constraint, matchedGrant and constraints show that rule and reason says so, even though the code stays the one authorize() returns. A rule with requireApproval reports decision: "escalate" and code approval_required. Explain cannot spend an approval. The unified policy.evaluate() engine (deny overrides, risk, taint) is not explained yet. The engine option is reserved for it and only accepts "authorize".

Options

Hooks

Hooks can call external services, so explain() skips beforeAuthorize unless you pass runHooks: true. When skipped, hook.configured is true and hook.evaluated is false, which tells you the live answer may differ. With runHooks: true the hook runs through the same timeout and fail-closed handling as authorize(). It receives dryRun: true, so a hook that writes records or notifies someone should check that flag and stay quiet. A deny, escalate, timeout or error from the hook is reflected in decision, code and hook.
Last modified on October 18, 2026