import { Request } from 'express'; import { DocumentFilter } from './document.js'; import './list.js'; import './memory.js'; /** * Policy-agnostic permission resolver. The core knows nothing about roles, * venues, or logbooks — it only calls these two methods. Implemented by a * consumer (e.g. client-agent's RoleResolver). */ interface PermissionResolver { /** Single-resource decision (write/byId/gate). `attrs` carries resource * attributes such as `{ category, venue }`. Returns true if allowed. */ can(userId: string, resource: string, action: string, attrs?: Record): Promise; /** List decision: the cross-user read scope for this user. `null` = * unrestricted (see everything). `[]` = no cross-user access (the caller's * own records only). A non-empty array is OR-ed together and unioned with * the caller's own records by the handler. */ listFilter(userId: string, resource: string): Promise; } type AuthzMode = "list" | "byId" | "fromBody" | "gate"; interface RouteRequirement { /** HTTP method, e.g. "GET" | "POST". */ method: string; /** Full express path incl. mount prefix, e.g. "/api/document/:id". */ path: string; resource: string; action: "read" | "write"; mode: AuthzMode; /** byId mode: load the target's attributes (e.g. its venue). Return null → * 404 (target missing). Return "skip" → not governed by authz (the handler's * own checks apply). Return attrs → run can(). */ loadAttrs?: (req: Request) => Promise | null | "skip">; /** fromBody mode: extract attributes from the body. Return "skip" → not * governed by authz. */ bodyAttrs?: (req: Request) => Record | "skip"; } interface AuthzConfig { resolver: PermissionResolver; /** Route → requirement bindings. Undeclared routes pass through. */ routes: RouteRequirement[]; } export type { AuthzConfig, AuthzMode, PermissionResolver, RouteRequirement };