What it does
Before you ship a permission change, you want to know what it does. The simulator takes an agent (or a set of token claims), an action and a resource, and returns a decision with the reasoning behind it:allow, deny or needs_approval.
It only reads. It does not write audit rows, bump rate limit counters, or record spend, so you can run it as often as you like against production data.
Decision parity with authorize
The simulator is not a second implementation of the rules. Resource and action matching (findMatchingPermission) and constraint evaluation (inspectConstraints) live in policy/abac.ts, and the real authorize() path calls the same functions. A test runs the same fixtures through theauth.authorize() and the simulator and checks that they agree.
Three places differ on purpose:
- A
requireApprovalrule isdenyfromauthorize()butneeds_approvalfrom the simulator. - Budget is checked only by the simulator, and only when it can tell the action would not fit: a
costis supplied and it is larger than what is left.authorize()does not look at budgets. - An agent whose
expiresAthas passed but whose status is stillactivegets a warning step.authorize()by agent id looks at status only, so the simulator matches that and flags it instead.
Run it
{ success: true, data } or { success: false, error }, like the rest of the SDK.
Without a stored agent, pass token claims instead:
What the trace shows
Each step has astage, an outcome and a detail, plus structured data where it helps.
If the agent’s own rules deny the action, the simulator then tries delegation chains, in the same order
authorize() does.
What-if overrides
overrides changes the inputs for one run and never stores anything.
depth is above its maxDepth, or that has expired, is listed in the trace and ignored.
Matrices and effective permissions
simulateMany refuses grids over 2000 cells.
HTTP route
The route is off until you add the plugin. Nobody is an admin by default, so you have to say who is.POST /agents/:id/simulate needs a signed-in admin and accepts one of three bodies:
overrides. The single form returns { decision, allowed, reasons, trace }.
CLI
Sign in as an admin withtheauth login, then:
simulate exits 0 on allow and 2 on anything else, so you can use it in scripts. permissions prints a table of resource, action, source (own or delegation:<chain>) and decision.
Limits
- Time windows use the server’s local clock, the same as
authorize(). Passcontext.timestampto test another moment. - ReBAC relations and the unified policy engine’s cache are not part of the simulation yet. It covers the direct permission path that
theauth.authorize()uses.