Skip to content

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.
  1. Validate the subject, the resource, and the entire assignment collection.
  2. Keep the assignments whose scope covers the resource, using its ancestry.
  3. Evaluate deny rules across every applicable assignment. A match ends the request.
  4. Evaluate allow rules with their conditions and field constraints.
  5. Otherwise NOT_GRANTED.

Never throws for malformed runtime input. Returns a closed decision.

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);
}

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; // boolean

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.