import { type BoundedUnbind, type CapabilityId, type Coverage, type DomainEventInput, type EntityRef, type PermissionKey, type PrincipalId } from '@substrat-run/contracts'; import type { ScopedSql } from './scope-host.js'; /** * A BINDING shape (#1686): a record names its own person in a declared column, and the person * it names holds the record's declared shape there. What makes a customer invite a binding * rather than a delegation: the link names WHICH record, the reviewed registry says WHAT the * person bound to it holds, and the minter vouches for the record with an ordinary key. * * Three writes, each one statement set shared by both adapters: * - {@link bindIn}: inside a `become` exchange's transaction, once {@link judgeBind} said yes and * the use is taken — the column set to the claimant, the shape granted (marker and keys), and * `entity.holder-bound` emitted. * - {@link unbindEntityHolderBounded}: the column cleared, and every holder of the shape on the * record released from the SPINE — their marker and everything they hold there tombstoned (K-21). * - {@link captureBoundOf} / {@link releaseBound}: the erasure's half — on every type that binds or * ever bound here, the holders on the erased record and the erased person's own markers, read * before any module erasure runs and released after the hooks whether or not a row survived. * * People already bound keep the shape until they are released — an unbind (while the shape still * binds), an erasure, or the retirement of its keys — whatever a later version declares. * * Read from the scope's record of its reconciled registry (`shape-bind-*` tuples), never from the * vertical's code: a scope not yet reconciled with a binding declaration binds nothing. */ interface EntityBinding { entityType: string; table: string; idColumn: string; principalColumn: string; /** * Who holds this on a record may bind its person with a link, and unbind them — null once a * version dropped `bind` and kept the shape: nobody may, and an erasure still releases. */ permission: PermissionKey | null; /** The shape's current keys — what a claim gives. */ keys: PermissionKey[]; /** Every key of the shape, current and retired — what an unbind takes back. */ every: PermissionKey[]; } /** The bind key's refusal, in the shape every bounded verb answers a missing key with. */ declare const lacking: (permission: PermissionKey) => { ok: false; coverage: Extract; }; /** * May `entity` be bound by a link its minter vouches for, now? The scope's recorded binding for * the type, the record present, its column naming nobody, nobody holding its shape there on the * spine (`bind-record-held`: unbind them first), and the minter holding the binding's * permission on it (`allowed`, the checker's `check` for the minter). The same judgement at mint * and at the exchange, so a link whose minter has since lost the key, or left, is refused. */ export declare function judgeBind(sql: ScopedSql, allowed: (permission: PermissionKey, entity: EntityRef) => Promise, entity: EntityRef): Promise<{ ok: true; binding: EntityBinding; } | { ok: false; refused: 'bind-not-declared' | 'bind-record-missing' | 'bind-record-ambiguous' | 'bind-record-bound' | 'bind-record-held'; } | ReturnType>; /** * The claim's write, inside the exchange's transaction once {@link judgeBind} has said yes there * and the use is taken: the record's column set to `principal` — only while it still names nobody — * the shape's current keys given with its marker, and `entity.holder-bound` emitted with the * capability as actor. Nothing can have moved since the judgement inside one transaction, so a * column that no longer reads empty is a fault: thrown, so the use rolls back with it. */ export declare function bindIn(sql: ScopedSql, binding: EntityBinding, entity: EntityRef, principal: PrincipalId, link: { capabilityId: CapabilityId; mintedBy: PrincipalId; }, emit: (event: DomainEventInput) => void): void; /** The pseudo-operation an unbind's spine event names, on `capabilities.mint-become`'s pattern. */ export declare const ENTITY_UNBIND_OPERATION = "entities.unbind"; /** * A principal's unbind of `entity` (#1686) — the lever beside a binding invite, run by a host in one * scope task and transaction. Bounded here: the caller must hold the binding's key on the record * (`allowed`, the checker's `check` for the caller). Clears the record's column (every row with * its id) and releases every holder of the shape on it from the spine — whoever a link bound, * whatever the column says now, and a holder given the shape before the column was written — * with one `entity.holder-unbound` event each through `emit`, whose actor the host stamps as the * caller. `unbound` names everyone released; a refusal writes nothing. */ export declare function unbindEntityHolderBounded(deps: { sql: ScopedSql; now: string; allowed: (permission: PermissionKey, entity: EntityRef) => Promise; emit: (event: DomainEventInput) => void; }, entity: EntityRef): Promise; /** What an erasure releases, read before any of it runs. */ export interface ErasureBindings { /** Shape holders to release: on a record that IS the subject (any holder), or the subject's own markers. */ holders: { object: string; only?: string; }[]; /** Rows whose column names someone the erasure reaches, to clear where they survive. */ columns: { binding: EntityBinding; entityId: string; principal: string; }[]; } /** * Every binding the erasure of `subjectId` reaches, by the spine and by the column: on each type the * scope binds or has ever bound (`shape-bound`) — the id matched against every such type, as a * module's declared erasure matches its subject columns whatever the type, so ids that are not * ULIDs (an integer, say) can collide across types — the record that IS the subject (every holder on it) and every record the subject * holds the shape on (their own marker), plus the rows whose column names the subject or that are * the subject. Read BEFORE any declared blank, delete or hook runs, so a row a module's erasure * deletes is still known. A scope that binds nothing pays the one read of its shape records. */ export declare function captureBoundOf(sql: ScopedSql, subjectId: string): ErasureBindings; /** * The erasure's unbind, after the module hooks: each captured column cleared where the row survived * and still names that person, and each captured holder released from the spine whether or not * its row survived. No event: an event naming the erased subject would be a new copy of what is * being erased. */ export declare function releaseBound(sql: ScopedSql, captured: ErasureBindings, now: string): void; export {}; //# sourceMappingURL=entity-binding.d.ts.map