import type { VerifyTokenHook } from '@kokuin/capability'; import { type DIDMethodResolver } from '@kokuin/token'; import type { DocumentNode } from '@kubun/protocol'; import type { AccessLevel, StoredAccessRule } from '@kubun/store-graph'; /** * Minimal database interface required by the access control system. * Decoupled from KubunDB so any store implementation can provide these methods. */ export type AccessControlDB = { hasControllerAccessDefaults?(ownerDID: string): Promise; getUserModelAccessDefault(ownerDID: string, modelID: string, permissionType: 'read' | 'write'): Promise; isMemberOfAnyCircle(viewerDID: string, circleIDs: Array): Promise; isMemberOfAnyGroup(viewerDID: string, groupIDs: Array): Promise; /** * Return the interface model IDs implemented by `modelID`. * * Used by delegation resource enumeration so a capability granted on an * interface (e.g. `urn:kubun:model:`) authorizes mutations on * any concrete model that declares it. Returns an empty array for models * that implement nothing. */ getModelInterfaces(modelID: string): Promise>; /** * Optional per-leaf-capability verification hook invoked by * `checkCapability`. When set, capabilities whose `jti` has been revoked * cause the access check to fail closed. Left undefined on deployments * without a P2P store (e.g. light clients) where no revocation state exists. */ revocationChecker?: VerifyTokenHook; /** * Optional `did:kokuin:` method resolver passed to `checkCapability` as its * `methods`. When a capability chain is issued/subjected by a controller DID, * this resolves that DID to its current signing keys and deny set — so a * capability signed by a device key the controller has revoked is refused, * and a controller `iss`/`sub` resolves at all. Left undefined on deployments * without controller support, where a `did:kokuin:` issuer is unresolvable * and `checkCapability` fails closed. */ controllerResolver?: DIDMethodResolver; }; export type { AccessLevel }; export type AccessRule = { level: AccessLevel; allowedDIDs: Array | null; allowedCircles: Array | null; allowedGroups: Array | null; }; export type AccessPermissions = { read?: AccessRule; write?: AccessRule; }; export type DefaultAccessLevel = { read: AccessLevel; write: 'only_owner' | 'restricted'; }; export type AccessChecker = (doc: DocumentNode, permissionType: 'read' | 'write' | 'share') => Promise; /** * Parse and validate access permissions from document data. * * Ensures every returned rule carries the `allowedGroups` field (defaulting * to `null` when absent on input) and rejects rules with unknown `level` * tokens. */ export declare function parseDocumentAccessPermissions(data: unknown): AccessPermissions | null; /** * Validate that DIDs have the correct format. */ export declare function validateDIDs(dids: Array): void; /** * Validate that an identifier (circle or group ID) is a non-empty, * non-whitespace string. There is no canonical format helper for circle and * group IDs in `@kubun/id` (they are app-supplied opaque strings — `@kubun/id` * helpers cover content-addressed Kubun IDs only), so this check rejects only * obvious garbage. Downstream membership checks won't match if the format is * malformed beyond this — callers should treat malformed IDs as an empty * scope. */ export declare function validateID(id: string, kind: 'circle' | 'group'): void; /** * Apply {@link validateID} to every entry of an array. */ export declare function validateIDs(ids: Array, kind: 'circle' | 'group'): void; /** * Resolve the effective access rule for a document and permission type. * * Order of precedence: * 1. Document accessPermissions override * 2. User's model default from database * 3. Server configuration default * * Document overrides with `'anyone'` on a `write` permission are rejected * (anyone-write is not a valid configuration) and fall through to the next * tier. */ export declare function resolveAccessRule(document: DocumentNode, modelID: string, ownerDID: string, permissionType: 'read' | 'write', db: AccessControlDB, defaultAccessLevel: DefaultAccessLevel): Promise; export type DocumentDelegationResourcesParams = { document: Pick; interfaceIDs: ReadonlyArray; }; /** Capability resources that can authorize an action on a document, most specific first. */ export declare function documentDelegationResources(params: DocumentDelegationResourcesParams): Array; export type DocumentDelegationCandidatesParams = { tokens: ReadonlyArray; owner: string; permissionType: 'read' | 'write' | 'share'; resources: ReadonlyArray; }; /** Reject inapplicable independent grants without resolving or verifying their issuers. */ export declare function filterDocumentDelegationCandidates(params: DocumentDelegationCandidatesParams): Array; export type DocumentWriteDelegationParams = { issuer: string; owner: string; document: DocumentNode; tokens: Array; db: Pick; atTime: number; /** Skip independent-grant fallback when selecting a complete flat chain. */ chainOnly?: boolean; }; /** Use the document mutation capability check without access-rule shortcuts. */ export declare function checkDocumentWriteDelegation(params: DocumentWriteDelegationParams): Promise; export type ModelDelegationParams = { viewerDID: string; grantor: string; modelID: string; permissionType: 'share'; db: Pick; delegationTokens: Array; atTime?: number; }; /** * Whether a delegation covers every document of `grantor` in `modelID`: the document tier is * excluded, so a grant naming one document never stands for the whole model. */ export declare function checkModelDelegation(params: ModelDelegationParams): Promise; /** * Create an access checker function bound to specific viewer, delegation tokens, * database instance, and server default access level. */ export declare function createAccessChecker(params: { viewerDID: string | undefined; delegationTokens?: Array; db: AccessControlDB; defaultAccessLevel: DefaultAccessLevel; atTime?: number; }): AccessChecker;