import { z } from 'zod'; import { type EntityRef, type EntityRow, type Page } from '@substrat-run/contracts'; import type { PageParams } from '@substrat-run/kernel'; /** * The conflict reasons this engine raises — its own vocabulary, narrowing the platform's * `conflict` code (#113). Exported so a vertical can branch on WHY a refusal happened * without importing this engine's types or matching on its prose; `as const` so a typo * is a compile error here rather than a slug nobody ever matches. * * Additive only, like every other engine surface: new reasons may appear, existing ones * do not change spelling. */ export declare const PROTOCOL_CONFLICT_REASONS: readonly ['already_open', 'already_signed', 'already_voided', 'attachment_mismatch', 'content_frozen', 'content_mismatch', 'no_bound_content', 'not_frozen', 'protocol_required', 'request_resolved', 'wrong_party', 'wrong_status', 'wrong_template_kind']; export type ProtocolConflictReason = (typeof PROTOCOL_CONFLICT_REASONS)[number]; import { protocolEntities } from './entities.js'; export { protocolEntities, protocolInstanceRow } from './entities.js'; /** * The row shapes this engine publishes, as Zod (#738). A vertical declaring an * operation that RETURNS one needs something to point at; before these it had * only TypeScript interfaces, which are enough to implement against and not * enough to declare against. */ export { protocolTemplateRow, protocolResponseRow, protocolSignatureRow, protocolSignatureRequestRow, } from './schemas.js'; /** * The input shapes, and the template-content vocabulary they are built from * (#738). Moved out of this file so `operations.ts` can declare against the * SAME Zod object the handler parses without importing the implementation — * see the header of `inputs.ts` for the cycle that forced it. * * Re-exported here in full: the package root is the engine's surface, and a * vertical that passed `defineTemplateInput` yesterday still imports it from * exactly where it did. */ export { protocolItem, checklistContent, documentContent, contentUnion, protocolTemplateContent, signatory, defineTemplateInput, instantiateProtocolInput, fillProtocolInput, bindDocumentInput, partyContact, signatureRequestParty, requestSignaturesInput, recordSignatureInput, declineSignatureInput, cancelSignatureRequestsInput, type ProtocolItem, type ChecklistContent, type DocumentContent, type ProtocolTemplateContent, type ProtocolTemplateContentInput, type Signatory, type DefineTemplateInput, type InstantiateProtocolInput, type FillProtocolInput, type BindDocumentInput, type PartyContact, type SignatureRequestParty, type RequestSignaturesInput, type RecordSignatureInput, type DeclineSignatureInput, type CancelSignatureRequestsInput, } from './inputs.js'; /** * The declared operation surface (#738) — what a vertical binds to its own URLs * with `defineEngineRoutes`, carrying this engine's real input and output * schemas rather than a restatement the vertical had to write. */ export { protocolOperations, PROTOCOL_PERMISSIONS } from './operations.js'; import { type BindDocumentInput, type CancelSignatureRequestsInput, type DeclineSignatureInput, type DefineTemplateInput, type FillProtocolInput, type InstantiateProtocolInput, type ProtocolTemplateContent, type RecordSignatureInput, type RequestSignaturesInput } from './inputs.js'; import { type GuardPredicate, type ModuleRegistration, type OperationContext } from '@substrat-run/kernel'; export declare const PROTOCOL_PERM: { create: string & z.$brand<"PermissionKey">; fill: string & z.$brand<"PermissionKey">; bind: string & z.$brand<"PermissionKey">; requestSignature: string & z.$brand<"PermissionKey">; recordSignature: string & z.$brand<"PermissionKey">; sign: string & z.$brand<"PermissionKey">; countersign: string & z.$brand<"PermissionKey">; read: string & z.$brand<"PermissionKey">; attach: string & z.$brand<"PermissionKey">; void: string & z.$brand<"PermissionKey">; }; export declare const protocolManifest: { id: string & z.$brand<"ModuleId">; version: string; kernelContract: string; permissions: { key: string & z.$brand<"PermissionKey">; description: string; }[]; events: { emits: { type: string; schemaVersion: number; }[]; consumes: { type: string; schemaVersion: number; }[]; }; migrations: { journalDir: string; compatibleFrom: string; }; attachmentTargets: { entityType: string; readPermission: string & z.$brand<"PermissionKey">; writePermission?: (string & z.$brand<"PermissionKey">) | undefined; }[]; entityRelations?: { entityType: string; parentType: string; }[] | undefined; guards?: { before: string; predicate: string; config: Record; }[] | undefined; schedules?: { operation: string; cadence: { everyMinutes: number; }; input?: Record | undefined; permissions: (string & z.$brand<"PermissionKey">)[]; }[] | undefined; withdraws?: string[] | undefined; entitlementKey: string; envSpec?: { key: string; label?: string | undefined; description: string; placeholder?: string | undefined; required: boolean; secret: boolean; default?: string | undefined; group?: string | undefined; }[] | undefined; ownerGrants?: (string & z.$brand<"PermissionKey">)[] | undefined; entitlements?: string[] | undefined; provides?: string[] | undefined; requires?: string[] | undefined; api?: string | undefined; searchables?: { entityType: string; fields: string[]; table?: string | undefined; idColumn?: string | undefined; tokenizer?: "prefix" | "substring" | undefined; }[] | undefined; lists?: { entityType: string; sortable: string[]; filterable?: string[] | undefined; table?: string | undefined; idColumn?: string | undefined; }[] | undefined; ui?: { routes?: { path: string; screen: string; permission: string & z.$brand<"PermissionKey">; }[] | undefined; nav?: { label: string; icon?: string | undefined; to: string; permission: string & z.$brand<"PermissionKey">; }[] | undefined; entityViews?: { entityType: string; view: string; }[] | undefined; widgets?: { slot: string; component: string; permission: string & z.$brand<"PermissionKey">; }[] | undefined; settingsPanels?: { label: string; component: string; permission: string & z.$brand<"PermissionKey">; }[] | undefined; } | undefined; }; export declare const protocolMigrations: { version: string; sql: string; }[]; export interface ProtocolTemplateRow { id: string; key: string; version: number; title: string; content_json: string; created_at: string; } /** * A protocol instance row — DERIVED from the entity registry (`entities.ts`) * rather than written beside it. The registry is what a vertical imports to get * the Zod schema for a declared operation's `output`, and two descriptions of * one row is how they come to disagree. */ export type ProtocolInstanceRow = EntityRow; export interface ProtocolResponseRow { id: string; instance_id: string; item_key: string; value_json: string; note: string | null; responded_by: string; responded_at: string; } export interface ProtocolSignatureRow { id: string; instance_id: string; /** The signatory reference: a `PrincipalId`, or an opaque `DataSubjectId`. */ signed_by: string; kind: 'primary' | 'counter'; method: string; content_hash: string; evidence_ref: string | null; signed_at: string; request_id: string | null; signatory_kind: 'principal' | 'external'; signatory_label: string | null; } export interface ProtocolSignatureRequestRow { id: string; instance_id: string; party_label: string; party_kind: 'principal' | 'external'; party_ref: string | null; signature_kind: 'primary' | 'counter'; method: string; /** #620: null on every row written before `0003-party-auth-level` — reads as `basic`. */ auth_level: 'basic' | 'strong' | null; /** * #687: the sealed delivery address, or null for a party that needs none (the * issuer) and for every row written before `0005-party-contact`. * * **Ciphertext, never plaintext** — the engine holds no readable address at * any point after the operation returns, which is what keeps a signatory's * contact out of this engine's erasure story entirely. */ contact_key_id: string | null; contact_ciphertext: string | null; status: 'pending' | 'signed' | 'declined' | 'expired' | 'cancelled'; content_hash: string; external_ref: string | null; resolved_note: string | null; requested_by: string; requested_at: string; resolved_at: string | null; } export declare function protocolContentHash(template: Pick, latest: Record, boundHash?: string | null): Promise; export declare function requireSigned(ctx: OperationContext, entity: EntityRef, templateKey: string): void; /** * The stronger form: signed AND counter-signed — the frozen content was * ACCEPTED by a second signatory (the customer at pickup). Invariant 3 already * guarantees a counter-signature can only exist on verified frozen content, so * the existence of the row is the whole check. */ export declare function requireCountersigned(ctx: OperationContext, entity: EntityRef, templateKey: string): void; /** * NOTE on multi-party: there is deliberately no `requireAllSigned`. In the * request-driven path an instance reaches `signed` only once EVERY requested * party has signed (see `recordSignature`), so "all parties signed" IS * `requireSigned` — a separate guard would read as though it checked something * stronger while checking the same thing. The multi-party question is answered * by the state machine, not by a second predicate. */ /** * Config for the `protocol/all-signed` predicate — parsed by the PREDICATE, not * by the kernel (the kernel keeps `config` opaque). `entityIdFrom` names the * field of the guarded operation's input that carries the entity id, which is * how one predicate serves any operation shape without the engine knowing the * vertical's vocabulary. */ export declare const allSignedGuardConfig: z.ZodObject<{ templateKey: z.ZodString; entityType: z.ZodString; entityIdFrom: z.ZodString; countersigned: z.ZodDefault; }, z.core.$strip>; export type AllSignedGuardConfig = z.input; /** The named predicate the kernel resolves for `predicate: 'protocol/all-signed'`. */ export declare const allSignedPredicate: GuardPredicate; /** * Templates version immutably: same key + new content = next version, the * old row is never touched. Editing a template never rewrites what a signed * document referred to. */ export declare function defineTemplate(ctx: OperationContext, rawInput: DefineTemplateInput): ProtocolTemplateRow; /** Latest version per key — the instantiation picker's list. */ /** * The latest version of every template, as a page (#811). * * Handler-composed deliberately: the `WHERE` is a correlated `MAX(version)` * subquery — one row per key, at its newest version — which is not something a * declared filter vocabulary composes, and a template is not a declared entity in * any case. Keyset over `key`, which is what the subquery groups by, so the * cursor is unique and the walk needs no tie-break. */ export declare function listTemplates(ctx: OperationContext, page: PageParams): Page; /** * Pins the latest template version at instantiation — forever (invariant 5). * One OPEN instance per (template, entity). Which entity types may carry * which protocols, and when, is vertical policy — enforced by the caller. */ export declare function instantiateProtocol(ctx: OperationContext, rawInput: InstantiateProtocolInput): ProtocolInstanceRow; export declare function fillProtocol(ctx: OperationContext, rawInput: FillProtocolInput): ProtocolResponseRow; /** * Bind (or re-bind) a document protocol's content while it is still open — * the document-kind counterpart of `fillProtocol`. Re-binding is the whole * point during negotiation: an avtal's price changes until it is sent out, and * each rebind moves the hash the signature will be taken over. * * Once frozen, this fails like any other write to frozen content. * * **Where the document and the hash are reconciled** (#711). `contentHash` is * what the signature attests to; `documentAttachmentId` is what the counterparty * reads while making it. Nothing downstream can tell whether they describe the * same thing — the connector sees bytes and a hash it cannot recompute, because * the recipe runs over the vertical's own rows. So the check belongs here, at the * moment both are named together, and it is the one this engine can actually * make: the attachment must exist and must be attached to THIS instance. That * refuses the accidents (a stale id, another instance's paperwork, an id from a * different scope) without pretending to verify a hash recipe the engine has * never been able to run. */ export declare function bindDocument(ctx: OperationContext, rawInput: BindDocumentInput): ProtocolInstanceRow; export interface RequestSignaturesResult { instance: ProtocolInstanceRow; contentHash: string; requests: ProtocolSignatureRequestRow[]; } /** * Freeze the content and ask named parties to sign it — the asynchronous * counterpart of `signProtocol`. * * This is the transition that closes the drift window: the instance leaves * `open` immediately, so nothing can fill or rebind it while it sits at the * provider. The hash is computed ONCE, here, and every signature that comes * back must match it. * * The engine dispatches nothing. It emits `protocol.signatures-requested` with * everything a connector needs (the hash, the parties, the method) and an * executor outside the scope makes the call — module code never touches the * network (boundary-lint R3). */ export declare function requestSignatures(ctx: OperationContext, rawInput: RequestSignaturesInput): Promise; export interface SignResult { instance: ProtocolInstanceRow; signature: ProtocolSignatureRow; } /** * Record a signature that happened OUTSIDE this system — the webhook's half. * * Everything `signProtocol` takes from ambient context, this takes as data: * the signatory is supplied (and may be an external person with no account), * the timestamp is the provider's, the method is the request's, and the * evidence reference points at the provider's sealed artifact. * * The last pending request resolving is what transitions the instance to * `signed` — which is the multi-party rule stated in code: an avtal is signed * when every requested party has signed it, not when the first one has. * * NOTE ON THE CALLER: there is no webhook ingress in the kernel yet, and no * inbound authority seam that would let a provider callback invoke a scope * operation (`ScopeHost.getScope` demands a `PrincipalId`; `ExecutorHandler` * has no return path into a scope). Until those land this is reachable only by * a principal holding `protocol:record-signature` — a key deliberately held by * no human role in any demo. */ export declare function recordSignature(ctx: OperationContext, rawInput: RecordSignatureInput): Promise; /** * A party refused, or the provider's window expired. The instance stays * `pending_signature` and therefore frozen — a refusal is not permission to * edit. Renegotiating means cancelling the request set explicitly, which is a * separate, permissioned, audited act. */ export declare function declineSignature(ctx: OperationContext, rawInput: DeclineSignatureInput): ProtocolSignatureRequestRow; /** * Withdraw an outstanding request set and thaw the instance — the * renegotiation path an avtal needs when a party declines or the price moves. * * Cancelling THAWS: status returns to `open` and the frozen hash is cleared, * so the next `requestSignatures` freezes fresh content at a fresh hash. * Signatures already collected are NOT removed — they are append-only history * attesting to content that really was frozen at the time — but they were * taken over the OLD hash, so they can never satisfy the new one. That is the * intended reading: a party who signed v1 has not signed v2. */ export declare function cancelSignatureRequests(ctx: OperationContext, rawInput: CancelSignatureRequestsInput): ProtocolInstanceRow; /** * In-app sign (engine-protocol.md §5): the authenticated principal signs, now; * integrity comes from the hash + immutability + the spine event. Freezing and * signing coincide here, which is sound precisely BECAUSE it is synchronous — * there is no window between what the signer saw and what was hashed. * * For an external provider flow (BankID via Scrive) use `requestSignatures` + * `recordSignature` instead: the signatory is not `ctx.principal`, the moment * is not now, and freezing must happen at dispatch rather than at signature. * Exactly ONE primary signature per instance — enforced by the open → signed * transition. */ export declare function signProtocol(ctx: OperationContext, input: { instanceId: string; }): Promise; /** * Counter-sign (invariant 3): a SECOND signature on the SAME frozen content — * the customer at pickup. Requires a signed instance; the content hash is * recomputed and must equal the frozen hash (frozen content, verified, never * assumed). One counter-signature per signatory; a signatory never * counter-signs what they primary-signed. */ export declare function countersignProtocol(ctx: OperationContext, input: { instanceId: string; }): Promise; /** Voiding, not deleting: a superseded protocol keeps its rows forever. */ export declare function voidProtocol(ctx: OperationContext, input: { instanceId: string; reason: string; }): ProtocolInstanceRow; export interface ProtocolDetail { instance: ProtocolInstanceRow; template: { key: string; version: number; title: string; content: ProtocolTemplateContent; }; responses: ProtocolResponseRow[]; latest: Record; signature: ProtocolSignatureRow | null; signatures: ProtocolSignatureRow[]; requests: ProtocolSignatureRequestRow[]; } export declare function getProtocol(ctx: OperationContext, instanceId: string): ProtocolDetail; export interface ProtocolSummary { instance: ProtocolInstanceRow; title: string; contentKind: ProtocolTemplateContent['kind']; answered: number; total: number; signedBy: string | null; signedAt: string | null; countersignedBy: string | null; countersignedAt: string | null; /** How many requested signatures are still outstanding. */ pendingSignatures: number; } /** * The protocols on one entity, as a page (#811). * * The walk is the kernel's — `entity_type` and `entity_id` are declared filters, * and the default sort is the ULID id, which is creation-ordered and therefore * the same sequence the old `ORDER BY rowid` produced. The per-row summary stays * here, and the page BOUNDS it: this used to load every response row of every * instance on the entity. */ export declare function listProtocolsForEntity(ctx: OperationContext, entity: EntityRef, page: PageParams): Page; export declare const protocolModule: ModuleRegistration; //# sourceMappingURL=index.d.ts.map