Decisions
Every check returns a decision object, never a bare boolean.
interface AuthorizationDecision { allowed: boolean; code: DecisionCode; reason: string; permission: string; policyVersion?: string; matched?: { effect: "allow" | "deny"; role: string; permission: string };}| Code | Meaning |
|---|---|
ALLOWED |
An allow rule matched and no deny did. |
EXPLICIT_DENY |
A deny rule matched. |
NOT_GRANTED |
No applicable allow rule matched. |
UNKNOWN_ROLE |
An assignment names a role the policy does not define. |
INVALID_ASSIGNMENT |
An assignment is malformed or its scope does not match its role. |
INVALID_SUBJECT |
The subject is malformed. |
RESOURCE_MISMATCH |
The resource does not fit the permission or the declared ancestry. |
INVALID_REQUEST |
The request is malformed, or evaluation itself faulted. |
Evaluation order
Section titled “Evaluation order”- Validate the subject, the resource, and the entire assignment collection.
- Keep the assignments whose scope covers the resource, using its ancestry.
- Evaluate deny rules across every applicable assignment. A match ends the request.
- Evaluate allow rules with their conditions and field constraints.
- Otherwise
NOT_GRANTED.
Never throws for malformed runtime input. Returns a closed decision.
enforce
Section titled “enforce”Returns the decision on allow and throws AuthorizationError on any denial. The error carries the decision.
import { AuthorizationError } from "rolebook";
try { authorization.enforce({ permission: "project:delete", resource: project });} catch (error) { if (error instanceof AuthorizationError) console.log(error.decision.code);}explain
Section titled “explain”Uses the same evaluator and adds a bounded, immutable trace of every rule evaluated. traceTruncated is true when entries were omitted after the 64-entry limit.
const explanation = authorization.explain({ permission: "project:update", resource: project });explanation.trace; // [{ outcome, effect, role, permission, reason }, ...]explanation.traceTruncated; // booleanFaults are closed, and reportable
Section titled “Faults are closed, and reportable”If evaluation itself throws, the caller gets INVALID_REQUEST. A fault must not reveal more than a malformed request does. That also makes an evaluator regression look like client error, so pass a reporter to tell them apart:
const authorizer = createAuthorizer(policy, { onInternalError: (error, { permission }) => logger.error({ err: error, permission }),});The reporter is diagnostics only. It never changes the decision, is never called for ordinary malformed input, and a reporter that throws is ignored.