import { SubstratError, type Coverage, type Decision, type EntityRef, type Node, type PermissionKey, type CheckSubject, type PrincipalId } from '@substrat-run/contracts'; /** * The evaluation seam (D-16): the MODEL is kernel-owned, the evaluation engine * is an adapter — the built-in default is a constrained relationship-tuple * engine (design doc §4.2, plan D-23), OpenFGA-swappable behind this same * interface. Both must satisfy the same contract tests. * * `entity` narrows the check to one entity: evaluated as node-level first * (staff see everything in the scope), then via the declared parent-edge walk * against entity-narrowed grants (§4.2 rule 3). */ export interface PermissionChecker { /** * `subject` rather than `principal` since #97: a connection can hold a grant, * and must not be laundered through a principal to do it. Every existing * caller passes `{ kind: 'principal', id }` and behaves exactly as before. */ check(subject: CheckSubject, permission: PermissionKey, node: Node, entity?: EntityRef): Promise; /** * Does `subject` already hold every one of `required` at `node`? (K-21, * membership.md §5.1.) * * The bound that makes role assignment safe: *a principal may assign role `R` at node * `N` only if the assigner already holds every permission `R` carries at `N`.* Without * it the definition/assignment checkpoint protects nothing — an `admin` promoting * themselves to `owner` widens no role, calls no `defineRole`, and shows up in no diff. * * **One resolution, not N checks.** `check` answers about one permission and walks the * tuples to do it; asking it twenty times for a twenty-permission role walks them * twenty times, on every invite acceptance. An implementation resolves the subject's * effective set once and compares. * * **Narrowing-aware, and this is the load-bearing part.** Only authority held at the * NODE counts. An entity-narrowed grant (§4.2 rule 3) does not satisfy the bound for * the unnarrowed permission — otherwise narrowing launders into full authority by way * of assignment: share one work order with someone, and they could assign a role * carrying `workorder:read` over every work order there is. * * Membership still expands (rule 4): authority a subject holds through an org is * authority it holds, and can therefore confer. * * Returns which permissions are MISSING rather than a bare boolean, because the * refusal a person can act on names them. */ covers(subject: CheckSubject, required: readonly PermissionKey[], node: Node): Promise; } /** Convenience for the overwhelmingly common case. */ export declare const asPrincipal: (id: PrincipalId) => CheckSubject; /** * A refused check. The message constructor stays the public surface modules use * (`throw new PermissionDenied('…')` for their own policy denials). `assertAllowed` * additionally attaches the denied `permission` and the `node` it was checked at (K-35), * so the host can record the denial (actor, permission, where) without re-parsing the * message — and a plain message-only denial simply carries neither and is not recorded. */ export declare class PermissionDenied extends SubstratError { readonly permission?: PermissionKey; readonly node?: Node; constructor(message: string, detail?: { permission: PermissionKey; node: Node; }); } /** Throw unless the decision is an allow. The standard first line of an operation. */ export declare function assertAllowed(decision: Decision): asserts decision is Extract; /** Secure default: deny everything. Hosts require an explicit checker to allow anything. */ export declare const denyAllChecker: PermissionChecker; /** * Dev/test-only checker. The name is deliberately alarming: it grants every * permission to every principal via a synthetic self-granted proof tuple. * Never wire it into anything a tenant can reach. */ export declare const UNSAFE_allowAllChecker: PermissionChecker; //# sourceMappingURL=permission-checker.d.ts.map