@canonmsg/backend-contracts 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canon-verb-wire.schema.json +367 -0
- package/dist/canon-verbs.limits.json +70 -0
- package/dist/canon-verbs.schema.json +1744 -0
- package/dist/cjs/contactRequest.js +4 -1
- package/dist/cjs/environment.js +29 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/verbContract.js +288 -0
- package/dist/cjs/verbSchemas.js +1144 -0
- package/dist/cjs/verbWire.js +634 -0
- package/dist/contactRequest.d.ts +1 -0
- package/dist/contactRequest.js +4 -1
- package/dist/environment.d.ts +25 -0
- package/dist/environment.js +25 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/verbContract.d.ts +760 -0
- package/dist/verbContract.js +283 -0
- package/dist/verbSchemas.d.ts +1460 -0
- package/dist/verbSchemas.js +1139 -0
- package/dist/verbWire.d.ts +405 -0
- package/dist/verbWire.js +628 -0
- package/package.json +8 -3
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canon verb WIRE contract — `canon.verb-wire.v1`.
|
|
3
|
+
*
|
|
4
|
+
* The verb INTENT contract (`canon.verbs.v1`, verbContract.ts) is the
|
|
5
|
+
* plaintext semantic input used locally by tools, MCP, SDK, and adapters. It
|
|
6
|
+
* is NOT the remote wire schema. What crosses the network is this contract:
|
|
7
|
+
* an authenticated public ENVELOPE (the routing/policy metadata the server
|
|
8
|
+
* may read, authorize, and enforce on) plus a BODY that is either plaintext
|
|
9
|
+
* JSON (temporary/plaintext conversations) or MLS ciphertext (encrypted
|
|
10
|
+
* conversations). See docs/design/e2ee-mls.md §1-2.
|
|
11
|
+
*
|
|
12
|
+
* one developer-facing API ≠ one plaintext wire schema
|
|
13
|
+
*
|
|
14
|
+
* Server endpoints (/agent/verbs/*) implement THIS contract with the `json`
|
|
15
|
+
* codec initially; MLS lands later as a codec swap, not an API change. In
|
|
16
|
+
* `json` mode the server may additionally read `body.value` to execute
|
|
17
|
+
* legacy behavior (validation, snapshot building); in `mls` mode the
|
|
18
|
+
* envelope is everything it will ever see.
|
|
19
|
+
*
|
|
20
|
+
* The intent→wire projection (and its inverse, used by the server executor
|
|
21
|
+
* in json mode) is defined here so every binding and the server agree on
|
|
22
|
+
* exactly which intent fields are envelope (server-visible) vs body
|
|
23
|
+
* (content). The split follows the 2026-07-12 plaintext audit: closed
|
|
24
|
+
* enums, ids, deadlines, and turn-protocol keys are envelope; free text,
|
|
25
|
+
* cards, prompts, questions, tool names/summaries, details, diffs, and
|
|
26
|
+
* self-contexts are body.
|
|
27
|
+
*/
|
|
28
|
+
import { type CanonVerbName, type VerbSessionSelection } from './verbContract.js';
|
|
29
|
+
export declare const CANON_VERB_WIRE_SCHEMA_VERSION = "canon.verb-wire.v1";
|
|
30
|
+
export declare const CANON_VERB_WIRE_SCHEMA_ID = "https://canonmsg.com/schemas/canon.verb-wire.v1.json";
|
|
31
|
+
export type VerbWireBody = {
|
|
32
|
+
encoding: 'json';
|
|
33
|
+
value: Record<string, unknown>;
|
|
34
|
+
} | {
|
|
35
|
+
encoding: 'mls';
|
|
36
|
+
epoch: number;
|
|
37
|
+
ciphertext: string;
|
|
38
|
+
aadHash: string;
|
|
39
|
+
};
|
|
40
|
+
/** Turn-protocol keys the server enforces on (subset of TurnMetadata). */
|
|
41
|
+
export interface VerbWireTurn {
|
|
42
|
+
turnId?: string | null;
|
|
43
|
+
turnSemantics?: 'progress' | 'turn_complete' | 'control';
|
|
44
|
+
deliveryIntent?: 'queue' | 'interrupt' | 'interleave' | 'stop';
|
|
45
|
+
replyBehavior?: 'allow_auto_reply' | 'suppress_auto_reply';
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The authenticated public envelope. Every field is routing/policy metadata
|
|
49
|
+
* the server needs to operate the network; none of it is message content.
|
|
50
|
+
* Which fields are permitted/required varies per verb — see
|
|
51
|
+
* VERB_WIRE_ENVELOPE_FIELDS and the per-verb projections.
|
|
52
|
+
*
|
|
53
|
+
* Documented leak decisions (see e2ee-mls.md §10): `mentions` stays
|
|
54
|
+
* plaintext for routing + mention-piercing pushes (D3); `contactUserId` on
|
|
55
|
+
* share_contact reveals who was shared (explicitly accepted for v1);
|
|
56
|
+
* `sessionConfig` may carry workspace ids — borderline, revisit with the
|
|
57
|
+
* session-configuration encryption decision.
|
|
58
|
+
*/
|
|
59
|
+
export interface CanonVerbWireEnvelope {
|
|
60
|
+
conversationId?: string;
|
|
61
|
+
targetUserId?: string;
|
|
62
|
+
canonContactId?: string;
|
|
63
|
+
sourceConversationId?: string;
|
|
64
|
+
contactUserId?: string;
|
|
65
|
+
requestId?: string;
|
|
66
|
+
responseUserId?: string;
|
|
67
|
+
/** Absolute epoch-ms deadline. Bindings convert timeoutMs before the wire. */
|
|
68
|
+
expiresAt?: number;
|
|
69
|
+
mode?: 'blocking' | 'detached';
|
|
70
|
+
kind?: 'clarify' | 'sudo' | 'secret';
|
|
71
|
+
sensitive?: boolean;
|
|
72
|
+
allowSessionRule?: boolean;
|
|
73
|
+
riskLevel?: 'normal' | 'destructive';
|
|
74
|
+
risk?: 'low' | 'normal' | 'high' | 'destructive';
|
|
75
|
+
category?: 'command' | 'file' | 'network' | 'browser' | 'mcp' | 'plugin' | 'canon' | 'tool';
|
|
76
|
+
sessionSelection?: VerbSessionSelection;
|
|
77
|
+
sessionConfig?: Record<string, unknown> | null;
|
|
78
|
+
idempotencyKey?: string;
|
|
79
|
+
/** Target-message reference for react/forward — routing, not content. */
|
|
80
|
+
messageId?: string;
|
|
81
|
+
replyTo?: string;
|
|
82
|
+
replyToPosition?: number;
|
|
83
|
+
mentions?: string[];
|
|
84
|
+
/** Group-membership routing ids (create_group) — distinct from mentions. */
|
|
85
|
+
memberIds?: string[];
|
|
86
|
+
turn?: VerbWireTurn;
|
|
87
|
+
native?: Record<string, unknown>;
|
|
88
|
+
runtimeId?: string;
|
|
89
|
+
limit?: number;
|
|
90
|
+
}
|
|
91
|
+
export interface CanonVerbWireRequest {
|
|
92
|
+
wire: typeof CANON_VERB_WIRE_SCHEMA_VERSION;
|
|
93
|
+
verb: CanonVerbName;
|
|
94
|
+
envelope: CanonVerbWireEnvelope;
|
|
95
|
+
body: VerbWireBody;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Server-side acceptance result for the interactive verbs (request_input,
|
|
99
|
+
* request_approval in blocking mode, request_card). The wire endpoint owns
|
|
100
|
+
* create/validate/route; the runtime library completes the intent-level
|
|
101
|
+
* result (submitted/allow/…) by consuming — a server cannot hold a
|
|
102
|
+
* 30-minute-to-72-hour HTTP response open.
|
|
103
|
+
*/
|
|
104
|
+
export interface VerbWireAccepted {
|
|
105
|
+
status: 'accepted';
|
|
106
|
+
requestId: string;
|
|
107
|
+
expiresAt?: number;
|
|
108
|
+
messageId?: string;
|
|
109
|
+
responseUserId?: string;
|
|
110
|
+
interactive?: boolean;
|
|
111
|
+
}
|
|
112
|
+
export interface CanonVerbWireResponse {
|
|
113
|
+
wire: typeof CANON_VERB_WIRE_SCHEMA_VERSION;
|
|
114
|
+
verb: CanonVerbName;
|
|
115
|
+
/**
|
|
116
|
+
* json codec: `value` is the verb's canonical result (send_to/check_
|
|
117
|
+
* approval/share_contact/reads) or VerbWireAccepted (interactive creates).
|
|
118
|
+
* mls codec (future): content-bearing results become ciphertext.
|
|
119
|
+
*/
|
|
120
|
+
result: VerbWireBody;
|
|
121
|
+
}
|
|
122
|
+
export declare const VERB_WIRE_ENVELOPE_FIELDS: Record<CanonVerbName, {
|
|
123
|
+
required: readonly string[];
|
|
124
|
+
optional: readonly string[];
|
|
125
|
+
}>;
|
|
126
|
+
export declare const CANON_VERB_WIRE_JSON_SCHEMA: {
|
|
127
|
+
readonly $schema: "https://json-schema.org/draft/2020-12/schema";
|
|
128
|
+
readonly $id: "https://canonmsg.com/schemas/canon.verb-wire.v1.json";
|
|
129
|
+
readonly title: "Canon Verb Wire v1";
|
|
130
|
+
readonly description: string;
|
|
131
|
+
readonly type: "object";
|
|
132
|
+
readonly required: readonly ["wire", "verb", "envelope", "body"];
|
|
133
|
+
readonly additionalProperties: false;
|
|
134
|
+
readonly properties: {
|
|
135
|
+
readonly wire: {
|
|
136
|
+
readonly const: "canon.verb-wire.v1";
|
|
137
|
+
};
|
|
138
|
+
readonly verb: {
|
|
139
|
+
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_contact_requests", "list_conversations"];
|
|
140
|
+
};
|
|
141
|
+
readonly envelope: {
|
|
142
|
+
readonly $ref: "#/$defs/envelope";
|
|
143
|
+
};
|
|
144
|
+
readonly body: {
|
|
145
|
+
readonly $ref: "#/$defs/body";
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
readonly $defs: {
|
|
149
|
+
readonly body: {
|
|
150
|
+
readonly description: string;
|
|
151
|
+
readonly oneOf: readonly [{
|
|
152
|
+
readonly type: "object";
|
|
153
|
+
readonly required: readonly ["encoding", "value"];
|
|
154
|
+
readonly additionalProperties: false;
|
|
155
|
+
readonly properties: {
|
|
156
|
+
readonly encoding: {
|
|
157
|
+
readonly const: "json";
|
|
158
|
+
};
|
|
159
|
+
readonly value: {
|
|
160
|
+
readonly type: "object";
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
}, {
|
|
164
|
+
readonly type: "object";
|
|
165
|
+
readonly required: readonly ["encoding", "epoch", "ciphertext", "aadHash"];
|
|
166
|
+
readonly additionalProperties: false;
|
|
167
|
+
readonly properties: {
|
|
168
|
+
readonly encoding: {
|
|
169
|
+
readonly const: "mls";
|
|
170
|
+
};
|
|
171
|
+
readonly epoch: {
|
|
172
|
+
readonly type: "integer";
|
|
173
|
+
readonly minimum: 0;
|
|
174
|
+
};
|
|
175
|
+
readonly ciphertext: {
|
|
176
|
+
readonly type: "string";
|
|
177
|
+
readonly minLength: 1;
|
|
178
|
+
};
|
|
179
|
+
readonly aadHash: {
|
|
180
|
+
readonly type: "string";
|
|
181
|
+
readonly minLength: 1;
|
|
182
|
+
readonly description: "Hash binding the envelope (AAD) to the ciphertext.";
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
}];
|
|
186
|
+
};
|
|
187
|
+
readonly envelope: {
|
|
188
|
+
readonly type: "object";
|
|
189
|
+
readonly description: string;
|
|
190
|
+
readonly additionalProperties: false;
|
|
191
|
+
readonly properties: {
|
|
192
|
+
readonly conversationId: {
|
|
193
|
+
readonly type: "string";
|
|
194
|
+
readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$";
|
|
195
|
+
};
|
|
196
|
+
readonly targetUserId: {
|
|
197
|
+
readonly type: "string";
|
|
198
|
+
readonly minLength: 1;
|
|
199
|
+
};
|
|
200
|
+
readonly canonContactId: {
|
|
201
|
+
readonly type: "string";
|
|
202
|
+
readonly minLength: 1;
|
|
203
|
+
};
|
|
204
|
+
readonly sourceConversationId: {
|
|
205
|
+
readonly type: "string";
|
|
206
|
+
readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$";
|
|
207
|
+
};
|
|
208
|
+
readonly contactUserId: {
|
|
209
|
+
readonly type: "string";
|
|
210
|
+
readonly minLength: 1;
|
|
211
|
+
};
|
|
212
|
+
readonly requestId: {
|
|
213
|
+
readonly type: "string";
|
|
214
|
+
readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$";
|
|
215
|
+
};
|
|
216
|
+
readonly responseUserId: {
|
|
217
|
+
readonly type: "string";
|
|
218
|
+
readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$";
|
|
219
|
+
};
|
|
220
|
+
readonly expiresAt: {
|
|
221
|
+
readonly type: "integer";
|
|
222
|
+
readonly minimum: 0;
|
|
223
|
+
};
|
|
224
|
+
readonly mode: {
|
|
225
|
+
readonly enum: readonly ["blocking", "detached"];
|
|
226
|
+
};
|
|
227
|
+
readonly kind: {
|
|
228
|
+
readonly enum: readonly ["clarify", "sudo", "secret"];
|
|
229
|
+
};
|
|
230
|
+
readonly sensitive: {
|
|
231
|
+
readonly type: "boolean";
|
|
232
|
+
};
|
|
233
|
+
readonly allowSessionRule: {
|
|
234
|
+
readonly type: "boolean";
|
|
235
|
+
};
|
|
236
|
+
readonly riskLevel: {
|
|
237
|
+
readonly enum: readonly ["normal", "destructive"];
|
|
238
|
+
};
|
|
239
|
+
readonly risk: {
|
|
240
|
+
readonly enum: readonly ["low", "normal", "high", "destructive"];
|
|
241
|
+
};
|
|
242
|
+
readonly category: {
|
|
243
|
+
readonly enum: readonly ["command", "file", "network", "browser", "mcp", "plugin", "canon", "tool"];
|
|
244
|
+
};
|
|
245
|
+
readonly sessionSelection: {
|
|
246
|
+
readonly oneOf: readonly [{
|
|
247
|
+
readonly type: "object";
|
|
248
|
+
readonly required: readonly ["mode"];
|
|
249
|
+
readonly additionalProperties: false;
|
|
250
|
+
readonly properties: {
|
|
251
|
+
readonly mode: {
|
|
252
|
+
readonly enum: readonly ["new", "continue_latest", "continue_or_create"];
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
}, {
|
|
256
|
+
readonly type: "object";
|
|
257
|
+
readonly required: readonly ["mode", "conversationId"];
|
|
258
|
+
readonly additionalProperties: false;
|
|
259
|
+
readonly properties: {
|
|
260
|
+
readonly mode: {
|
|
261
|
+
readonly const: "specific";
|
|
262
|
+
};
|
|
263
|
+
readonly conversationId: {
|
|
264
|
+
readonly type: "string";
|
|
265
|
+
readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$";
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
}];
|
|
269
|
+
};
|
|
270
|
+
readonly sessionConfig: {
|
|
271
|
+
readonly type: readonly ["object", "null"];
|
|
272
|
+
};
|
|
273
|
+
readonly idempotencyKey: {
|
|
274
|
+
readonly type: "string";
|
|
275
|
+
readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$";
|
|
276
|
+
readonly not: {
|
|
277
|
+
readonly pattern: "^(\\.{1,2}|__.*__)$";
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
readonly messageId: {
|
|
281
|
+
readonly type: "string";
|
|
282
|
+
readonly minLength: 1;
|
|
283
|
+
};
|
|
284
|
+
readonly replyTo: {
|
|
285
|
+
readonly type: "string";
|
|
286
|
+
};
|
|
287
|
+
readonly replyToPosition: {
|
|
288
|
+
readonly type: "integer";
|
|
289
|
+
};
|
|
290
|
+
readonly mentions: {
|
|
291
|
+
readonly type: "array";
|
|
292
|
+
readonly items: {
|
|
293
|
+
readonly type: "string";
|
|
294
|
+
};
|
|
295
|
+
readonly description: "Plaintext by decision D3 (routing + mention-piercing pushes).";
|
|
296
|
+
};
|
|
297
|
+
readonly memberIds: {
|
|
298
|
+
readonly type: "array";
|
|
299
|
+
readonly items: {
|
|
300
|
+
readonly type: "string";
|
|
301
|
+
};
|
|
302
|
+
readonly minItems: 1;
|
|
303
|
+
readonly description: string;
|
|
304
|
+
};
|
|
305
|
+
readonly turn: {
|
|
306
|
+
readonly $ref: "#/$defs/turn";
|
|
307
|
+
};
|
|
308
|
+
readonly native: {
|
|
309
|
+
readonly type: "object";
|
|
310
|
+
};
|
|
311
|
+
readonly runtimeId: {
|
|
312
|
+
readonly type: "string";
|
|
313
|
+
readonly maxLength: 128;
|
|
314
|
+
};
|
|
315
|
+
readonly limit: {
|
|
316
|
+
readonly type: "integer";
|
|
317
|
+
readonly minimum: 1;
|
|
318
|
+
};
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
readonly turn: {
|
|
322
|
+
readonly type: "object";
|
|
323
|
+
readonly additionalProperties: false;
|
|
324
|
+
readonly properties: {
|
|
325
|
+
readonly turnId: {
|
|
326
|
+
readonly type: readonly ["string", "null"];
|
|
327
|
+
};
|
|
328
|
+
readonly turnSemantics: {
|
|
329
|
+
readonly enum: readonly ["progress", "turn_complete", "control"];
|
|
330
|
+
};
|
|
331
|
+
readonly deliveryIntent: {
|
|
332
|
+
readonly enum: readonly ["queue", "interrupt", "interleave", "stop"];
|
|
333
|
+
};
|
|
334
|
+
readonly replyBehavior: {
|
|
335
|
+
readonly enum: readonly ["allow_auto_reply", "suppress_auto_reply"];
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
readonly accepted: {
|
|
340
|
+
readonly type: "object";
|
|
341
|
+
readonly required: readonly ["status", "requestId"];
|
|
342
|
+
readonly additionalProperties: true;
|
|
343
|
+
readonly properties: {
|
|
344
|
+
readonly status: {
|
|
345
|
+
readonly const: "accepted";
|
|
346
|
+
};
|
|
347
|
+
readonly requestId: {
|
|
348
|
+
readonly type: "string";
|
|
349
|
+
};
|
|
350
|
+
readonly expiresAt: {
|
|
351
|
+
readonly type: "integer";
|
|
352
|
+
};
|
|
353
|
+
readonly messageId: {
|
|
354
|
+
readonly type: "string";
|
|
355
|
+
};
|
|
356
|
+
readonly responseUserId: {
|
|
357
|
+
readonly type: "string";
|
|
358
|
+
};
|
|
359
|
+
readonly interactive: {
|
|
360
|
+
readonly type: "boolean";
|
|
361
|
+
};
|
|
362
|
+
};
|
|
363
|
+
};
|
|
364
|
+
readonly response: {
|
|
365
|
+
readonly type: "object";
|
|
366
|
+
readonly required: readonly ["wire", "verb", "result"];
|
|
367
|
+
readonly additionalProperties: true;
|
|
368
|
+
readonly properties: {
|
|
369
|
+
readonly wire: {
|
|
370
|
+
readonly const: "canon.verb-wire.v1";
|
|
371
|
+
};
|
|
372
|
+
readonly verb: {
|
|
373
|
+
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_contact_requests", "list_conversations"];
|
|
374
|
+
};
|
|
375
|
+
readonly result: {
|
|
376
|
+
readonly $ref: "#/$defs/body";
|
|
377
|
+
};
|
|
378
|
+
};
|
|
379
|
+
};
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
export interface ProjectVerbIntentOptions {
|
|
383
|
+
/** Clock for timeoutMs→expiresAt normalization. */
|
|
384
|
+
now: number;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Project a plaintext verb intent (canon.verbs.v1 input) into its wire form
|
|
388
|
+
* with the `json` codec: envelope = the routing/policy fields the server may
|
|
389
|
+
* see; body.value = the content fields (what MLS later encrypts).
|
|
390
|
+
*
|
|
391
|
+
* The inverse is mergeVerbWireToIntent; round-tripping normalizes deadlines
|
|
392
|
+
* to absolute expiresAt but is otherwise lossless.
|
|
393
|
+
*/
|
|
394
|
+
export declare function projectVerbIntentToWire(verb: CanonVerbName, intent: Record<string, unknown>, options: ProjectVerbIntentOptions): CanonVerbWireRequest;
|
|
395
|
+
/**
|
|
396
|
+
* Reconstruct the plaintext verb intent from a json-codec wire request — the
|
|
397
|
+
* server executor's half of the seam. Throws if the body is not json.
|
|
398
|
+
*/
|
|
399
|
+
export declare function mergeVerbWireToIntent(request: CanonVerbWireRequest): Record<string, unknown>;
|
|
400
|
+
/**
|
|
401
|
+
* Envelope discipline check: no field outside the verb's allowed set, all
|
|
402
|
+
* required fields present. Schema validation catches shape; this catches
|
|
403
|
+
* per-verb field misuse (a strict-envelope server rejects on it).
|
|
404
|
+
*/
|
|
405
|
+
export declare function findVerbWireEnvelopeViolations(verb: CanonVerbName, envelope: CanonVerbWireEnvelope): string[];
|