/** * Canon verb WIRE contract — `canon.verb-wire.v1`. * * The verb INTENT contract (`canon.verbs.v1`, verbContract.ts) is the * plaintext semantic input used locally by tools, MCP, SDK, and adapters. It * is NOT the remote wire schema. What crosses the network is this contract: * an authenticated public ENVELOPE (the routing/policy metadata the server * may read, authorize, and enforce on) plus a BODY that is either plaintext * JSON (temporary/plaintext conversations) or MLS ciphertext (encrypted * conversations). See docs/design/e2ee-mls.md §1-2. * * one developer-facing API ≠ one plaintext wire schema * * Server endpoints (/agent/verbs/*) implement THIS contract with the `json` * codec initially; MLS lands later as a codec swap, not an API change. In * `json` mode the server may additionally read `body.value` to execute * legacy behavior (validation, snapshot building); in `mls` mode the * envelope is everything it will ever see. * * The intent→wire projection (and its inverse, used by the server executor * in json mode) is defined here so every binding and the server agree on * exactly which intent fields are envelope (server-visible) vs body * (content). The split follows the 2026-07-12 plaintext audit: closed * enums, ids, deadlines, and turn-protocol keys are envelope; free text, * cards, prompts, questions, tool names/summaries, details, diffs, and * self-contexts are body. */ import { type CanonVerbName, type VerbNativeMetadata, type VerbSessionSelection } from './verbContract.js'; export declare const CANON_VERB_WIRE_SCHEMA_VERSION = "canon.verb-wire.v1"; export declare const CANON_VERB_WIRE_SCHEMA_ID = "https://canonmsg.com/schemas/canon.verb-wire.v1.json"; export type VerbWireBody = { encoding: 'json'; value: Record; } | { encoding: 'mls'; epoch: number; ciphertext: string; aadHash: string; }; /** Turn-protocol keys the server enforces on (subset of TurnMetadata). */ export interface VerbWireTurn { turnId?: string | null; turnSemantics?: 'progress' | 'turn_complete' | 'control'; deliveryIntent?: 'queue' | 'interrupt' | 'interleave' | 'stop'; replyBehavior?: 'allow_auto_reply' | 'suppress_auto_reply'; } /** * The authenticated public envelope. Every field is routing/policy metadata * the server needs to operate the network; none of it is message content. * Which fields are permitted/required varies per verb — see * VERB_WIRE_ENVELOPE_FIELDS and the per-verb projections. * * Documented leak decisions (see e2ee-mls.md §10): `mentions` stays * plaintext for routing + mention-piercing pushes (D3); `contactUserId` on * share_contact reveals who was shared (explicitly accepted for v1); * `native` carries only typed correlation ids. Paths, labels, prompts, summaries, and other free * text belong in the body or owner-local runtime state. */ export interface CanonVerbWireEnvelope { conversationId?: string; targetUserId?: string; canonContactId?: string; sourceConversationId?: string; contactUserId?: string; requestId?: string; responseUserId?: string; /** Absolute epoch-ms deadline. Bindings convert timeoutMs before the wire. */ expiresAt?: number; mode?: 'blocking' | 'detached'; kind?: 'clarify' | 'sudo' | 'secret'; sensitive?: boolean; allowSessionRule?: boolean; riskLevel?: 'normal' | 'destructive'; risk?: 'low' | 'normal' | 'high' | 'destructive'; category?: 'command' | 'file' | 'network' | 'browser' | 'mcp' | 'plugin' | 'canon' | 'tool'; sessionSelection?: VerbSessionSelection; idempotencyKey?: string; /** Target-message reference for react/forward — routing, not content. */ messageId?: string; replyTo?: string; replyToPosition?: number; mentions?: string[]; /** Group-membership routing ids (create_group) — distinct from mentions. */ memberIds?: string[]; turn?: VerbWireTurn; native?: VerbNativeMetadata; runtimeId?: string; limit?: number; } export interface CanonVerbWireRequest { wire: typeof CANON_VERB_WIRE_SCHEMA_VERSION; verb: CanonVerbName; envelope: CanonVerbWireEnvelope; body: VerbWireBody; } /** * Server-side acceptance result for the interactive verbs (request_input, * request_approval in blocking mode, request_card). The wire endpoint owns * create/validate/route; the runtime library completes the intent-level * result (submitted/allow/…) by consuming — a server cannot hold a * 30-minute-to-72-hour HTTP response open. */ export interface VerbWireAccepted { status: 'accepted'; requestId: string; expiresAt?: number; messageId?: string; responseUserId?: string; interactive?: boolean; } export interface CanonVerbWireResponse { wire: typeof CANON_VERB_WIRE_SCHEMA_VERSION; verb: CanonVerbName; /** * json codec: `value` is the verb's canonical result (send_to/check_ * approval/share_contact/reads) or VerbWireAccepted (interactive creates). * mls codec (future): content-bearing results become ciphertext. */ result: VerbWireBody; } export declare const VERB_WIRE_ENVELOPE_FIELDS: Record; export declare const CANON_VERB_WIRE_JSON_SCHEMA: { readonly $schema: "https://json-schema.org/draft/2020-12/schema"; readonly $id: "https://canonmsg.com/schemas/canon.verb-wire.v1.json"; readonly title: "Canon Verb Wire v1"; readonly description: string; readonly type: "object"; readonly required: readonly ["wire", "verb", "envelope", "body"]; readonly additionalProperties: false; readonly properties: { readonly wire: { readonly const: "canon.verb-wire.v1"; }; readonly verb: { readonly enum: readonly ["send_to", "request_input", "request_approval", "check_approval", "send_card", "request_card", "share_contact", "react", "forward", "create_group", "add_member", "remove_member", "leave_conversation", "list_contacts", "list_conversations", "no_reply"]; }; readonly envelope: { readonly $ref: "#/$defs/envelope"; }; readonly body: { readonly $ref: "#/$defs/body"; }; }; readonly $defs: { readonly body: { readonly description: string; readonly oneOf: readonly [{ readonly type: "object"; readonly required: readonly ["encoding", "value"]; readonly additionalProperties: false; readonly properties: { readonly encoding: { readonly const: "json"; }; readonly value: { readonly type: "object"; }; }; }, { readonly type: "object"; readonly required: readonly ["encoding", "epoch", "ciphertext", "aadHash"]; readonly additionalProperties: false; readonly properties: { readonly encoding: { readonly const: "mls"; }; readonly epoch: { readonly type: "integer"; readonly minimum: 0; }; readonly ciphertext: { readonly type: "string"; readonly minLength: 1; }; readonly aadHash: { readonly type: "string"; readonly minLength: 1; readonly description: "Hash binding the envelope (AAD) to the ciphertext."; }; }; }]; }; readonly envelope: { readonly type: "object"; readonly description: string; readonly additionalProperties: false; readonly properties: { readonly conversationId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$"; }; readonly targetUserId: { readonly type: "string"; readonly minLength: 1; }; readonly canonContactId: { readonly type: "string"; readonly minLength: 1; }; readonly sourceConversationId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$"; }; readonly contactUserId: { readonly type: "string"; readonly minLength: 1; }; readonly requestId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$"; }; readonly responseUserId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$"; }; readonly expiresAt: { readonly type: "integer"; readonly minimum: 0; }; readonly mode: { readonly enum: readonly ["blocking", "detached"]; }; readonly kind: { readonly enum: readonly ["clarify", "sudo", "secret"]; }; readonly sensitive: { readonly type: "boolean"; }; readonly allowSessionRule: { readonly type: "boolean"; }; readonly riskLevel: { readonly enum: readonly ["normal", "destructive"]; }; readonly risk: { readonly enum: readonly ["low", "normal", "high", "destructive"]; }; readonly category: { readonly enum: readonly ["command", "file", "network", "browser", "mcp", "plugin", "canon", "tool"]; }; readonly sessionSelection: { readonly oneOf: readonly [{ readonly type: "object"; readonly required: readonly ["mode"]; readonly additionalProperties: false; readonly properties: { readonly mode: { readonly enum: readonly ["new", "continue_latest", "continue_or_create"]; }; }; }, { readonly type: "object"; readonly required: readonly ["mode", "conversationId"]; readonly additionalProperties: false; readonly properties: { readonly mode: { readonly const: "specific"; }; readonly conversationId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$"; }; }; }]; }; readonly idempotencyKey: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$"; readonly not: { readonly pattern: "^(\\.{1,2}|__.*__)$"; }; }; readonly messageId: { readonly type: "string"; readonly minLength: 1; }; readonly replyTo: { readonly type: "string"; }; readonly replyToPosition: { readonly type: "integer"; }; readonly mentions: { readonly type: "array"; readonly items: { readonly type: "string"; }; readonly description: "Plaintext by decision D3 (routing + mention-piercing pushes)."; }; readonly memberIds: { readonly type: "array"; readonly items: { readonly type: "string"; }; readonly minItems: 1; readonly description: string; }; readonly turn: { readonly $ref: "#/$defs/turn"; }; readonly native: { readonly type: "object"; readonly description: string; readonly maxProperties: 24; readonly additionalProperties: false; readonly properties: { readonly runtime: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly method: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9_][A-Za-z0-9_./:\\-]{0,255}$"; readonly maxLength: 256; }; readonly requestId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly provider: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly origin: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly surface: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly threadId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly turnId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly runId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly itemId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly toolCallId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly approvalId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly pluginId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly sessionKey: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly nodeId: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; readonly handles: { readonly type: "object"; readonly maxProperties: 16; readonly propertyNames: { readonly pattern: "^[A-Za-z0-9_.:-]{1,80}$"; }; readonly additionalProperties: { readonly type: "string"; readonly pattern: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly maxLength: 256; }; }; }; }; readonly runtimeId: { readonly type: "string"; readonly maxLength: 128; }; readonly limit: { readonly type: "integer"; readonly minimum: 1; }; }; }; readonly turn: { readonly type: "object"; readonly additionalProperties: false; readonly properties: { readonly turnId: { readonly type: readonly ["string", "null"]; }; readonly turnSemantics: { readonly enum: readonly ["progress", "turn_complete", "control"]; }; readonly deliveryIntent: { readonly enum: readonly ["queue", "interrupt", "interleave", "stop"]; }; readonly replyBehavior: { readonly enum: readonly ["allow_auto_reply", "suppress_auto_reply"]; }; }; }; readonly accepted: { readonly type: "object"; readonly required: readonly ["status", "requestId"]; readonly additionalProperties: true; readonly properties: { readonly status: { readonly const: "accepted"; }; readonly requestId: { readonly type: "string"; }; readonly expiresAt: { readonly type: "integer"; }; readonly messageId: { readonly type: "string"; }; readonly responseUserId: { readonly type: "string"; }; readonly interactive: { readonly type: "boolean"; }; }; }; readonly response: { readonly type: "object"; readonly required: readonly ["wire", "verb", "result"]; readonly additionalProperties: true; readonly properties: { readonly wire: { readonly const: "canon.verb-wire.v1"; }; readonly verb: { readonly enum: readonly ["send_to", "request_input", "request_approval", "check_approval", "send_card", "request_card", "share_contact", "react", "forward", "create_group", "add_member", "remove_member", "leave_conversation", "list_contacts", "list_conversations", "no_reply"]; }; readonly result: { readonly $ref: "#/$defs/body"; }; }; }; }; }; export interface ProjectVerbIntentOptions { /** Clock for timeoutMs→expiresAt normalization. */ now: number; } /** * Project a plaintext verb intent (canon.verbs.v1 input) into its wire form * with the `json` codec: envelope = the routing/policy fields the server may * see; body.value = the content fields (what MLS later encrypts). * * The inverse is mergeVerbWireToIntent; round-tripping normalizes deadlines * to absolute expiresAt but is otherwise lossless. */ export declare function projectVerbIntentToWire(verb: CanonVerbName, intent: Record, options: ProjectVerbIntentOptions): CanonVerbWireRequest; /** * Reconstruct the plaintext verb intent from a json-codec wire request — the * server executor's half of the seam. Throws if the body is not json. */ export declare function mergeVerbWireToIntent(request: CanonVerbWireRequest): Record; /** * Envelope discipline check: no field outside the verb's allowed set, all * required fields present. Schema validation catches shape; this catches * per-verb field misuse (a strict-envelope server rejects on it). */ export declare function findVerbWireEnvelopeViolations(verb: CanonVerbName, envelope: CanonVerbWireEnvelope): string[];