Explain a request

Open any recorded request in devtools and the Path tab shows the way it took through your code, in order: the rate limits it passed, how it was authenticated, the guards blocks run, the policy decision clause by clause with the file and line of the policy, the record lookup, validation, the handler, the service hooks your code overrides, the events it emitted and the error, if any. For a 403 or 404 it names the exact reason.

Agents get the same answer as text with the calm_explain MCP tool, so they stop guessing why something was denied:

PATCH /api/orders/66f1…c2 -> 404 NOT FOUND   as Grace Hopper (user)
  rateLimit "global"        calm/http/middleware.js                30 of 300 this minute
  auth session              calm/auth/index.js                     Grace Hopper (user)
  service load order 66f1…c2  calm/http/router.js                  found (within OrderService.scope)
✖ policy "owner | admin"    app/resources/order/order.resource.js:5
    owner: doc.buyer 66e0…9a is not user 66f3…11
    admin: user has roles [user]
    the user may not even read this order, so the id is not confirmed: 404
  service and hooks did not run

The steps#

KindRecorded whenSays
rateLimitA limiter counted the requestThe count and the window (14 of 300 this minute)
authAuthentication rananonymous, session, or the block whose credential matched (an API key, for example), and who
guardA block's calm.guard() ranThe block, and whether it passed, answered the request itself, or failed
policyThe route's policy was decidedEach token with its result and why, the file and line of the policy, and the status it led to
serviceA record was loaded for /:id routesWhether it was found within the service scope()
inputA route's input was validatedHow many fields, or which ones failed
handlerYour handler or the CRUD method ranThe function, its file and line, how long it took
hookA service hook you override ranOrderService.beforeCreate, in your service file
eventAn event was emittedThe name and how many listeners it has
errorThe request failedThe error code, message and status

Each step has the time since the request started, in milliseconds.

How a denial is explained#

Policies are evaluated token by token. In a traced request every token says what it decided: public allows everyone, authenticated needs a user, a role is checked against the user's roles, a function or named policy reports what it returned, and owner is settled once the record is loaded (doc.buyer 66e0…9a is not user 66f3…11). The last line says which status the decision led to, and why: 401 without a user, 403 when the user may read the record but not change it, 404 when the user may not even read it, so its id is not confirmed.

Your own steps#

trace() from #calm adds a step from your code or your block:

JavaScript
import { trace } from '#calm';

trace({ kind: 'guard', name: 'tenant', detail: `org ${org.slug}`, outcome: 'passed' });

A step is { kind, name, file?, line?, detail?, outcome? }, with outcome one of passed, denied, answered, failed, skipped. Outside a request, or with devtools off, trace() does nothing.

Cost#

Steps are only collected when devtools is enabled, which is never in production. The policy explanations are only built inside a traced request. Line numbers are read once at startup.