import type { AuthorizationScope, EkklesiaDeviceId, EkklesiaUserId, PermissionId, } from "@ekklesia/contracts"; export interface ActiveGrantQuery { readonly userId: EkklesiaUserId; readonly permission: PermissionId; /** Absent means an unscoped check. An unscoped grant satisfies either an unscoped or scoped * check for the same permission; a scoped grant satisfies only the exact matching scope and * never satisfies an unscoped check. */ readonly scope?: AuthorizationScope; /** The instant to evaluate expiration against — injected so evaluation is deterministic and * testable rather than reading the wall clock inside the lookup itself. */ readonly now: Date; } /** * The only read dependency `createAuthorizationEvaluator` has for grant state. Narrow and * Postgres-independent by design: a test can supply a fake implementation, and the evaluator * itself never knows whether it is backed by `core/src/authorization/store.ts` or a test double. */ export interface AuthorizationGrantPort { hasActiveGrant(query: ActiveGrantQuery): Promise; } /** The only read dependency the evaluator has for platform-owner authority — deliberately a * separate, narrower port from grants, since owner authority is not represented as a grant. */ export interface AuthorizationPlatformOwnerPort { isPlatformOwner(userId: EkklesiaUserId): Promise; } /** * The evaluator's only read dependency for **device** authority. * * Separate from `AuthorizationGrantPort` because the two answer questions about different kinds of * subject, not because devices are a special case in the decision: `authorize` consults exactly one * of them per call, by principal kind, and neither can be reached without going through it. * * There is deliberately no device counterpart to `AuthorizationPlatformOwnerPort`. A device is * never a platform owner, so there is no port that could accidentally say it was. */ export interface AuthorizationDevicePort { hasActivePermission(deviceId: EkklesiaDeviceId, permission: PermissionId): Promise; }