@canonmsg/backend-contracts 6.4.0 → 7.0.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/README.md CHANGED
@@ -16,7 +16,7 @@ Node.js 18+. Ships both ESM and CommonJS builds — Cloud Functions consume the
16
16
 
17
17
  ## What is in it
18
18
 
19
- **The verb contract.** `canon.verbs.v1` (`verbContract.ts`) is the plaintext *intent* schema — eighteen verbs, their input and result schemas, byte limits, and rate limits. `canon.verb-wire.v1` (`verbWire.ts`) is what actually crosses the network: a server-readable envelope plus a body that is either `{ encoding: 'json', value }` or `{ encoding: 'mls', … }`. `projectVerbIntentToWire` and `mergeVerbWireToIntent` are the two halves of that split, so bindings and the server never disagree about which fields are envelope and which are content.
19
+ **The verb contract.** `canon.verbs.v1` (`verbContract.ts`) is the plaintext *intent* schema — seventeen verbs, their input and result schemas, byte limits, and rate limits. `canon.verb-wire.v1` (`verbWire.ts`) is what actually crosses the network: a server-readable envelope plus a body that is either `{ encoding: 'json', value }` or `{ encoding: 'mls', … }`. `projectVerbIntentToWire` and `mergeVerbWireToIntent` are the two halves of that split, so bindings and the server never disagree about which fields are envelope and which are content.
20
20
 
21
21
  ```ts
22
22
  import {
@@ -33,7 +33,6 @@
33
33
  "list_contacts",
34
34
  "list_contact_requests",
35
35
  "list_conversations",
36
- "cancel_contact_request",
37
36
  "no_reply"
38
37
  ]
39
38
  },
@@ -428,10 +427,6 @@
428
427
  "null"
429
428
  ]
430
429
  },
431
- "sourceMessageId": {
432
- "type": "string",
433
- "minLength": 1
434
- },
435
430
  "turnSemantics": {
436
431
  "enum": [
437
432
  "progress",
@@ -513,7 +508,6 @@
513
508
  "list_contacts",
514
509
  "list_contact_requests",
515
510
  "list_conversations",
516
- "cancel_contact_request",
517
511
  "no_reply"
518
512
  ]
519
513
  },
@@ -1767,26 +1767,11 @@
1767
1767
  "list_contact_requests_input": {
1768
1768
  "type": "object",
1769
1769
  "additionalProperties": false,
1770
- "properties": {
1771
- "direction": {
1772
- "enum": [
1773
- "inbound",
1774
- "outbound"
1775
- ]
1776
- },
1777
- "includeResolved": {
1778
- "type": "boolean"
1779
- },
1780
- "limit": {
1781
- "type": "integer",
1782
- "minimum": 1,
1783
- "maximum": 100
1784
- }
1785
- }
1770
+ "properties": {}
1786
1771
  },
1787
1772
  "list_contact_requests_result": {
1788
1773
  "type": "object",
1789
- "description": "Contact requests, newest first, capped at 100. Defaults to pending inbound; requesters use direction=outbound with includeResolved=true to recover lifecycle state. Items are SerializedContactRequest (backend-contracts contactRequest.ts).",
1774
+ "description": "Pending inbound requests, newest first, capped at 100. Items are SerializedContactRequest (backend-contracts contactRequest.ts). Read-only awareness: approval routes to the owner; agents cannot approve/reject.",
1790
1775
  "required": [
1791
1776
  "requests"
1792
1777
  ],
@@ -1801,8 +1786,7 @@
1801
1786
  "requesterId",
1802
1787
  "targetId",
1803
1788
  "status",
1804
- "kind",
1805
- "phase"
1789
+ "kind"
1806
1790
  ],
1807
1791
  "additionalProperties": true,
1808
1792
  "properties": {
@@ -1823,8 +1807,7 @@
1823
1807
  "pending",
1824
1808
  "approved",
1825
1809
  "rejected",
1826
- "expired",
1827
- "cancelled"
1810
+ "expired"
1828
1811
  ]
1829
1812
  },
1830
1813
  "kind": {
@@ -1833,17 +1816,6 @@
1833
1816
  "group_invite"
1834
1817
  ]
1835
1818
  },
1836
- "phase": {
1837
- "enum": [
1838
- "awaiting_owner",
1839
- "starting",
1840
- "connected",
1841
- "rejected",
1842
- "expired",
1843
- "cancelled",
1844
- "failed"
1845
- ]
1846
- },
1847
1819
  "message": {
1848
1820
  "type": [
1849
1821
  "string",
@@ -1977,35 +1949,6 @@
1977
1949
  }
1978
1950
  }
1979
1951
  },
1980
- "cancel_contact_request_input": {
1981
- "type": "object",
1982
- "required": [
1983
- "requestId"
1984
- ],
1985
- "additionalProperties": false,
1986
- "properties": {
1987
- "requestId": {
1988
- "type": "string",
1989
- "pattern": "^[A-Za-z0-9_.:-]{1,160}$"
1990
- }
1991
- }
1992
- },
1993
- "cancel_contact_request_result": {
1994
- "type": "object",
1995
- "required": [
1996
- "status",
1997
- "requestId"
1998
- ],
1999
- "additionalProperties": true,
2000
- "properties": {
2001
- "status": {
2002
- "const": "cancelled"
2003
- },
2004
- "requestId": {
2005
- "type": "string"
2006
- }
2007
- }
2008
- },
2009
1952
  "no_reply_input": {
2010
1953
  "type": "object",
2011
1954
  "description": "End this turn without posting anything to the conversation. Nothing is rendered and no other member or agent is triggered.",
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readCommunicationRule = readCommunicationRule;
4
+ exports.parseDirectConversationSelection = parseDirectConversationSelection;
5
+ /** Missing or invalid policy never widens access. */
6
+ function readCommunicationRule(value) {
7
+ if (value === 'open' || value === 'approval-required' || value === 'closed') {
8
+ return value;
9
+ }
10
+ // Existing owner-only records have the same outsider-facing meaning as
11
+ // closed; the agent owner bypass remains a separate server-side rule.
12
+ if (value === 'owner-only')
13
+ return 'closed';
14
+ return 'approval-required';
15
+ }
16
+ function parseDirectConversationSelection(value) {
17
+ if (value == null)
18
+ return { mode: 'latest_or_new' };
19
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
20
+ throw new Error('selection must be an object');
21
+ }
22
+ const record = value;
23
+ if (record.mode === 'latest_or_new' || record.mode === 'new') {
24
+ return { mode: record.mode };
25
+ }
26
+ if (record.mode === 'specific') {
27
+ const conversationId = typeof record.conversationId === 'string'
28
+ ? record.conversationId.trim()
29
+ : '';
30
+ if (!conversationId)
31
+ throw new Error('selection.conversationId is required');
32
+ return { mode: 'specific', conversationId };
33
+ }
34
+ throw new Error('selection.mode is invalid');
35
+ }
@@ -1,29 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readContactRequestRequirements = readContactRequestRequirements;
4
3
  exports.readGroupInviteRequirements = readGroupInviteRequirements;
5
- exports.deriveContactRequestPhase = deriveContactRequestPhase;
6
4
  exports.serializeContactRequest = serializeContactRequest;
7
- function readContactRequestRequirements(value) {
5
+ function readGroupInviteRequirements(value) {
8
6
  const record = isRecord(value) ? value : null;
9
7
  if (typeof record?.policyApproval !== 'boolean'
10
8
  || typeof record.ownerSessionSetup !== 'boolean') {
11
- throw new TypeError('Invalid contact request requirements');
9
+ throw new TypeError('Invalid group invite requirements');
12
10
  }
13
11
  return {
14
12
  policyApproval: record.policyApproval,
15
13
  ownerSessionSetup: record.ownerSessionSetup,
16
14
  };
17
15
  }
18
- /** Backward-compatible parser retained for existing group invite clients. */
19
- function readGroupInviteRequirements(value) {
20
- try {
21
- return readContactRequestRequirements(value);
22
- }
23
- catch {
24
- throw new TypeError('Invalid group invite requirements');
25
- }
26
- }
27
16
  function isRecord(value) {
28
17
  return typeof value === 'object' && value !== null && !Array.isArray(value);
29
18
  }
@@ -41,36 +30,12 @@ function normalizeStatus(value) {
41
30
  if (value === 'pending'
42
31
  || value === 'approved'
43
32
  || value === 'rejected'
44
- || value === 'expired'
45
- || value === 'cancelled') {
33
+ || value === 'expired') {
46
34
  return value;
47
35
  }
48
36
  return 'pending';
49
37
  }
50
- function deriveContactRequestPhase(data) {
51
- const status = data.status;
52
- const openingStatus = data.openingMessageStatus;
53
- let phase;
54
- if (status === 'cancelled')
55
- phase = 'cancelled';
56
- else if (status === 'rejected')
57
- phase = 'rejected';
58
- else if (status === 'expired')
59
- phase = 'expired';
60
- else if (status === 'delivery_failed' || openingStatus === 'failed')
61
- phase = 'failed';
62
- else if (status === 'approved')
63
- phase = 'connected';
64
- else if (status === 'delivery_pending'
65
- || openingStatus === 'ready'
66
- || openingStatus === 'delivering'
67
- || openingStatus === 'retryable')
68
- phase = 'starting';
69
- else
70
- phase = 'awaiting_owner';
71
- return phase;
72
- }
73
- function serializeContactRequest(requestId, data, options = {}) {
38
+ function serializeContactRequest(requestId, data) {
74
39
  if (typeof data.approverId !== 'string' || data.approverId.length === 0) {
75
40
  return null;
76
41
  }
@@ -127,34 +92,20 @@ function serializeContactRequest(requestId, data, options = {}) {
127
92
  message: null,
128
93
  status: normalizeStatus(data.status),
129
94
  kind: kindValue,
130
- phase: deriveContactRequestPhase(data),
131
95
  createdAt: normalizeTimestamp(data.createdAt),
132
96
  resolvedAt: normalizeTimestamp(data.resolvedAt),
133
97
  expiresAt: normalizeTimestamp(data.expiresAt),
134
98
  };
135
- if (groupContext || kindValue === 'dm') {
99
+ if (groupContext) {
136
100
  let requirements;
137
101
  try {
138
- requirements = data.requirements === undefined && kindValue === 'dm'
139
- ? { policyApproval: true, ownerSessionSetup: false }
140
- : readContactRequestRequirements(data.requirements);
102
+ requirements = readGroupInviteRequirements(data.requirements);
141
103
  }
142
104
  catch {
143
105
  return null;
144
106
  }
145
- payload.requirements = requirements;
146
- }
147
- if (groupContext)
148
107
  payload.groupContext = groupContext;
149
- if (options.audience === 'requester') {
150
- if (typeof data.sourceConversationId === 'string' && data.sourceConversationId.length > 0) {
151
- payload.sourceConversationId = data.sourceConversationId;
152
- }
153
- if (payload.phase === 'connected'
154
- && typeof data.openingMessageConversationId === 'string'
155
- && data.openingMessageConversationId.length > 0) {
156
- payload.conversationId = data.openingMessageConversationId;
157
- }
108
+ payload.requirements = requirements;
158
109
  }
159
110
  if (data.targetUserType === 'ai_agent') {
160
111
  payload.targetOwnerId = data.targetOwnerId;
package/dist/cjs/index.js CHANGED
@@ -29,6 +29,7 @@ __exportStar(require("./verbContract.js"), exports);
29
29
  __exportStar(require("./verbSchemas.js"), exports);
30
30
  __exportStar(require("./verbWire.js"), exports);
31
31
  __exportStar(require("./accessPolicy.js"), exports);
32
+ __exportStar(require("./communication.js"), exports);
32
33
  __exportStar(require("./apiErrors.js"), exports);
33
34
  __exportStar(require("./firestoreValues.js"), exports);
34
35
  __exportStar(require("./moderation.js"), exports);
@@ -206,7 +206,6 @@ exports.CANON_VERB_NAMES = [
206
206
  'list_contacts',
207
207
  'list_contact_requests',
208
208
  'list_conversations',
209
- 'cancel_contact_request',
210
209
  'no_reply',
211
210
  ];
212
211
  exports.VERB_NATIVE_METADATA_KEYS = [
@@ -1004,17 +1004,13 @@ const list_contacts_result = {
1004
1004
  const list_contact_requests_input = {
1005
1005
  type: 'object',
1006
1006
  additionalProperties: false,
1007
- properties: {
1008
- direction: { enum: ['inbound', 'outbound'] },
1009
- includeResolved: { type: 'boolean' },
1010
- limit: { type: 'integer', minimum: 1, maximum: 100 },
1011
- },
1007
+ properties: {},
1012
1008
  };
1013
1009
  const list_contact_requests_result = {
1014
1010
  type: 'object',
1015
- description: 'Contact requests, newest first, capped at 100. Defaults to pending inbound; '
1016
- + 'requesters use direction=outbound with includeResolved=true to recover lifecycle state. '
1017
- + 'Items are SerializedContactRequest (backend-contracts contactRequest.ts).',
1011
+ description: 'Pending inbound requests, newest first, capped at 100. Items are '
1012
+ + 'SerializedContactRequest (backend-contracts contactRequest.ts). Read-only awareness: '
1013
+ + "approval routes to the owner; agents cannot approve/reject.",
1018
1014
  required: ['requests'],
1019
1015
  additionalProperties: true,
1020
1016
  properties: {
@@ -1022,26 +1018,15 @@ const list_contact_requests_result = {
1022
1018
  type: 'array',
1023
1019
  items: {
1024
1020
  type: 'object',
1025
- required: ['id', 'requesterId', 'targetId', 'status', 'kind', 'phase'],
1021
+ required: ['id', 'requesterId', 'targetId', 'status', 'kind'],
1026
1022
  additionalProperties: true,
1027
1023
  properties: {
1028
1024
  id: { type: 'string' },
1029
1025
  requesterId: { type: 'string' },
1030
1026
  requesterName: { type: 'string' },
1031
1027
  targetId: { type: 'string' },
1032
- status: { enum: ['pending', 'approved', 'rejected', 'expired', 'cancelled'] },
1028
+ status: { enum: ['pending', 'approved', 'rejected', 'expired'] },
1033
1029
  kind: { enum: ['dm', 'group_invite'] },
1034
- phase: {
1035
- enum: [
1036
- 'awaiting_owner',
1037
- 'starting',
1038
- 'connected',
1039
- 'rejected',
1040
- 'expired',
1041
- 'cancelled',
1042
- 'failed',
1043
- ],
1044
- },
1045
1030
  message: { type: ['string', 'null'] },
1046
1031
  createdAt: { type: ['string', 'null'] },
1047
1032
  expiresAt: { type: ['string', 'null'] },
@@ -1050,23 +1035,6 @@ const list_contact_requests_result = {
1050
1035
  },
1051
1036
  },
1052
1037
  };
1053
- const cancel_contact_request_input = {
1054
- type: 'object',
1055
- required: ['requestId'],
1056
- additionalProperties: false,
1057
- properties: {
1058
- requestId: { type: 'string', pattern: verbContract_js_1.VERB_ID_PATTERNS.runtimeId },
1059
- },
1060
- };
1061
- const cancel_contact_request_result = {
1062
- type: 'object',
1063
- required: ['status', 'requestId'],
1064
- additionalProperties: true,
1065
- properties: {
1066
- status: { const: 'cancelled' },
1067
- requestId: { type: 'string' },
1068
- },
1069
- };
1070
1038
  const list_conversations_input = {
1071
1039
  type: 'object',
1072
1040
  additionalProperties: false,
@@ -1206,8 +1174,6 @@ exports.CANON_VERBS_JSON_SCHEMA = {
1206
1174
  list_contact_requests_result,
1207
1175
  list_conversations_input,
1208
1176
  list_conversations_result,
1209
- cancel_contact_request_input,
1210
- cancel_contact_request_result,
1211
1177
  no_reply_input,
1212
1178
  no_reply_result,
1213
1179
  },
@@ -118,7 +118,6 @@ exports.VERB_WIRE_ENVELOPE_FIELDS = {
118
118
  list_contacts: { required: [], optional: [] },
119
119
  list_contact_requests: { required: [], optional: [] },
120
120
  list_conversations: { required: [], optional: ['limit'] },
121
- cancel_contact_request: { required: ['requestId'], optional: [] },
122
121
  no_reply: { required: [], optional: ['conversationId', 'messageId'] },
123
122
  };
124
123
  // ---------------------------------------------------------------------------
@@ -160,7 +159,6 @@ const wireTurnDef = {
160
159
  additionalProperties: false,
161
160
  properties: {
162
161
  turnId: { type: ['string', 'null'] },
163
- sourceMessageId: { type: 'string', minLength: 1 },
164
162
  turnSemantics: { enum: ['progress', 'turn_complete', 'control'] },
165
163
  deliveryIntent: { enum: ['queue', 'interrupt', 'interleave', 'stop'] },
166
164
  replyBehavior: { enum: ['allow_auto_reply', 'suppress_auto_reply'] },
@@ -286,13 +284,7 @@ exports.CANON_VERB_WIRE_JSON_SCHEMA = {
286
284
  // ---------------------------------------------------------------------------
287
285
  // Intent <-> wire projection (json codec)
288
286
  // ---------------------------------------------------------------------------
289
- const TURN_KEYS = [
290
- 'turnId',
291
- 'sourceMessageId',
292
- 'turnSemantics',
293
- 'deliveryIntent',
294
- 'replyBehavior',
295
- ];
287
+ const TURN_KEYS = ['turnId', 'turnSemantics', 'deliveryIntent', 'replyBehavior'];
296
288
  function splitTurnMetadata(metadata) {
297
289
  if (!metadata || typeof metadata !== 'object')
298
290
  return { turn: undefined, rest: undefined };
@@ -477,25 +469,14 @@ function projectVerbIntentToWire(verb, intent, options) {
477
469
  envelope = compact({ conversationId: input.conversationId });
478
470
  break;
479
471
  }
480
- case 'list_contacts': {
481
- break;
482
- }
472
+ case 'list_contacts':
483
473
  case 'list_contact_requests': {
484
- value = compact({
485
- direction: input.direction,
486
- includeResolved: input.includeResolved,
487
- limit: input.limit,
488
- });
489
474
  break;
490
475
  }
491
476
  case 'list_conversations': {
492
477
  envelope = compact({ limit: input.limit });
493
478
  break;
494
479
  }
495
- case 'cancel_contact_request': {
496
- envelope = compact({ requestId: input.requestId });
497
- break;
498
- }
499
480
  case 'no_reply': {
500
481
  envelope = compact({ conversationId: input.conversationId, messageId: input.messageId });
501
482
  value = compact({ reason: input.reason });
@@ -634,17 +615,10 @@ function mergeVerbWireToIntent(request) {
634
615
  case 'leave_conversation':
635
616
  return compact({ conversationId: envelope.conversationId });
636
617
  case 'list_contacts':
637
- return {};
638
618
  case 'list_contact_requests':
639
- return compact({
640
- direction: value.direction,
641
- includeResolved: value.includeResolved,
642
- limit: value.limit,
643
- });
619
+ return {};
644
620
  case 'list_conversations':
645
621
  return compact({ limit: envelope.limit });
646
- case 'cancel_contact_request':
647
- return compact({ requestId: envelope.requestId });
648
622
  case 'no_reply':
649
623
  return compact({ conversationId: envelope.conversationId, messageId: envelope.messageId, reason: value.reason });
650
624
  }
@@ -0,0 +1,91 @@
1
+ /** The deliberately small policy vocabulary for principal communication. */
2
+ export type CommunicationRule = 'open' | 'approval-required' | 'closed';
3
+ /** Missing or invalid policy never widens access. */
4
+ export declare function readCommunicationRule(value: unknown): CommunicationRule;
5
+ /** Canon conversation selection is principal-neutral; a conversation is a runtime session boundary. */
6
+ export type DirectConversationSelection = {
7
+ mode: 'latest_or_new';
8
+ } | {
9
+ mode: 'new';
10
+ } | {
11
+ mode: 'specific';
12
+ conversationId: string;
13
+ };
14
+ export type CommunicateInput = {
15
+ action: 'message_existing';
16
+ conversationId: string;
17
+ text: string;
18
+ messageId?: string;
19
+ } | {
20
+ action: 'start_direct';
21
+ principalId: string;
22
+ text: string;
23
+ selection?: DirectConversationSelection;
24
+ messageId?: string;
25
+ } | {
26
+ action: 'create_group';
27
+ name: string;
28
+ memberIds: string[];
29
+ } | {
30
+ action: 'forward_message';
31
+ sourceConversationId: string;
32
+ targetConversationId: string;
33
+ messageId: string;
34
+ text?: string;
35
+ };
36
+ export type CommunicateResult = {
37
+ status: 'messaged';
38
+ conversationId: string;
39
+ messageId: string;
40
+ created?: boolean;
41
+ reused?: boolean;
42
+ } | {
43
+ status: 'requested' | 'pending';
44
+ requestId: string;
45
+ } | {
46
+ status: 'created';
47
+ conversationId: string;
48
+ added: string[];
49
+ pending: Array<{
50
+ userId: string;
51
+ requestId: string;
52
+ requirements: {
53
+ policyApproval: boolean;
54
+ ownerSessionSetup: boolean;
55
+ };
56
+ }>;
57
+ skipped: Array<{
58
+ userId: string;
59
+ reason: string;
60
+ }>;
61
+ } | {
62
+ status: 'forwarded';
63
+ messageId: string;
64
+ targetConversationId: string;
65
+ } | {
66
+ status: 'blocked' | 'unavailable';
67
+ reason: string;
68
+ };
69
+ /** Principal-neutral app/SDK command. Humans may open a composer before sending. */
70
+ export interface StartDirectInput {
71
+ targetUserId: string;
72
+ opener?: {
73
+ text: string;
74
+ messageId?: string;
75
+ } | null;
76
+ selection?: DirectConversationSelection;
77
+ }
78
+ export type StartDirectResult = {
79
+ status: 'opened' | 'messaged';
80
+ conversationId: string;
81
+ messageId?: string;
82
+ created?: boolean;
83
+ reused?: boolean;
84
+ } | {
85
+ status: 'requested' | 'pending';
86
+ requestId: string;
87
+ } | {
88
+ status: 'blocked' | 'unavailable';
89
+ reason: string;
90
+ };
91
+ export declare function parseDirectConversationSelection(value: unknown): DirectConversationSelection;
@@ -0,0 +1,31 @@
1
+ /** Missing or invalid policy never widens access. */
2
+ export function readCommunicationRule(value) {
3
+ if (value === 'open' || value === 'approval-required' || value === 'closed') {
4
+ return value;
5
+ }
6
+ // Existing owner-only records have the same outsider-facing meaning as
7
+ // closed; the agent owner bypass remains a separate server-side rule.
8
+ if (value === 'owner-only')
9
+ return 'closed';
10
+ return 'approval-required';
11
+ }
12
+ export function parseDirectConversationSelection(value) {
13
+ if (value == null)
14
+ return { mode: 'latest_or_new' };
15
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
16
+ throw new Error('selection must be an object');
17
+ }
18
+ const record = value;
19
+ if (record.mode === 'latest_or_new' || record.mode === 'new') {
20
+ return { mode: record.mode };
21
+ }
22
+ if (record.mode === 'specific') {
23
+ const conversationId = typeof record.conversationId === 'string'
24
+ ? record.conversationId.trim()
25
+ : '';
26
+ if (!conversationId)
27
+ throw new Error('selection.conversationId is required');
28
+ return { mode: 'specific', conversationId };
29
+ }
30
+ throw new Error('selection.mode is invalid');
31
+ }
@@ -1,18 +1,12 @@
1
- export type SerializedContactRequestStatus = 'pending' | 'approved' | 'rejected' | 'expired' | 'cancelled';
2
- export type ContactRequestLifecyclePhase = 'awaiting_owner' | 'starting' | 'connected' | 'rejected' | 'expired' | 'cancelled' | 'failed';
1
+ export type SerializedContactRequestStatus = 'pending' | 'approved' | 'rejected' | 'expired';
3
2
  /**
4
- * Independent gates that must be satisfied before a pending contact request
5
- * can activate. DM introductions and group invites share this owner-review
6
- * vocabulary even though their activation pipelines remain separate.
3
+ * Independent gates that must be satisfied before a pending group invite can
4
+ * add its target.
7
5
  */
8
- export interface ContactRequestRequirements {
6
+ export interface GroupInviteRequirements {
9
7
  policyApproval: boolean;
10
8
  ownerSessionSetup: boolean;
11
9
  }
12
- /** Backward-compatible name retained for existing group invite clients. */
13
- export type GroupInviteRequirements = ContactRequestRequirements;
14
- export declare function readContactRequestRequirements(value: unknown): ContactRequestRequirements;
15
- /** Backward-compatible parser retained for existing group invite clients. */
16
10
  export declare function readGroupInviteRequirements(value: unknown): GroupInviteRequirements;
17
11
  export interface SerializedContactRequest {
18
12
  id: string;
@@ -33,18 +27,10 @@ export interface SerializedContactRequest {
33
27
  conversationId: string;
34
28
  groupName: string | null;
35
29
  };
36
- /** Present for group invites and setup/policy-aware DM introductions. */
37
- requirements?: ContactRequestRequirements;
38
- phase: ContactRequestLifecyclePhase;
39
- /** Requester-only source lane for an opener-backed introduction. */
40
- sourceConversationId?: string;
41
- /** Requester-only destination, present after an opener-backed DM connects. */
42
- conversationId?: string;
30
+ /** Present for group_invite payloads; omitted for DM requests. */
31
+ requirements?: GroupInviteRequirements;
43
32
  createdAt: string | null;
44
33
  resolvedAt?: string | null;
45
34
  expiresAt?: string | null;
46
35
  }
47
- export declare function deriveContactRequestPhase(data: Record<string, unknown>): ContactRequestLifecyclePhase;
48
- export declare function serializeContactRequest(requestId: string, data: Record<string, unknown>, options?: {
49
- audience?: 'inbound' | 'requester';
50
- }): SerializedContactRequest | null;
36
+ export declare function serializeContactRequest(requestId: string, data: Record<string, unknown>): SerializedContactRequest | null;
@@ -1,23 +1,14 @@
1
- export function readContactRequestRequirements(value) {
1
+ export function readGroupInviteRequirements(value) {
2
2
  const record = isRecord(value) ? value : null;
3
3
  if (typeof record?.policyApproval !== 'boolean'
4
4
  || typeof record.ownerSessionSetup !== 'boolean') {
5
- throw new TypeError('Invalid contact request requirements');
5
+ throw new TypeError('Invalid group invite requirements');
6
6
  }
7
7
  return {
8
8
  policyApproval: record.policyApproval,
9
9
  ownerSessionSetup: record.ownerSessionSetup,
10
10
  };
11
11
  }
12
- /** Backward-compatible parser retained for existing group invite clients. */
13
- export function readGroupInviteRequirements(value) {
14
- try {
15
- return readContactRequestRequirements(value);
16
- }
17
- catch {
18
- throw new TypeError('Invalid group invite requirements');
19
- }
20
- }
21
12
  function isRecord(value) {
22
13
  return typeof value === 'object' && value !== null && !Array.isArray(value);
23
14
  }
@@ -35,36 +26,12 @@ function normalizeStatus(value) {
35
26
  if (value === 'pending'
36
27
  || value === 'approved'
37
28
  || value === 'rejected'
38
- || value === 'expired'
39
- || value === 'cancelled') {
29
+ || value === 'expired') {
40
30
  return value;
41
31
  }
42
32
  return 'pending';
43
33
  }
44
- export function deriveContactRequestPhase(data) {
45
- const status = data.status;
46
- const openingStatus = data.openingMessageStatus;
47
- let phase;
48
- if (status === 'cancelled')
49
- phase = 'cancelled';
50
- else if (status === 'rejected')
51
- phase = 'rejected';
52
- else if (status === 'expired')
53
- phase = 'expired';
54
- else if (status === 'delivery_failed' || openingStatus === 'failed')
55
- phase = 'failed';
56
- else if (status === 'approved')
57
- phase = 'connected';
58
- else if (status === 'delivery_pending'
59
- || openingStatus === 'ready'
60
- || openingStatus === 'delivering'
61
- || openingStatus === 'retryable')
62
- phase = 'starting';
63
- else
64
- phase = 'awaiting_owner';
65
- return phase;
66
- }
67
- export function serializeContactRequest(requestId, data, options = {}) {
34
+ export function serializeContactRequest(requestId, data) {
68
35
  if (typeof data.approverId !== 'string' || data.approverId.length === 0) {
69
36
  return null;
70
37
  }
@@ -121,34 +88,20 @@ export function serializeContactRequest(requestId, data, options = {}) {
121
88
  message: null,
122
89
  status: normalizeStatus(data.status),
123
90
  kind: kindValue,
124
- phase: deriveContactRequestPhase(data),
125
91
  createdAt: normalizeTimestamp(data.createdAt),
126
92
  resolvedAt: normalizeTimestamp(data.resolvedAt),
127
93
  expiresAt: normalizeTimestamp(data.expiresAt),
128
94
  };
129
- if (groupContext || kindValue === 'dm') {
95
+ if (groupContext) {
130
96
  let requirements;
131
97
  try {
132
- requirements = data.requirements === undefined && kindValue === 'dm'
133
- ? { policyApproval: true, ownerSessionSetup: false }
134
- : readContactRequestRequirements(data.requirements);
98
+ requirements = readGroupInviteRequirements(data.requirements);
135
99
  }
136
100
  catch {
137
101
  return null;
138
102
  }
139
- payload.requirements = requirements;
140
- }
141
- if (groupContext)
142
103
  payload.groupContext = groupContext;
143
- if (options.audience === 'requester') {
144
- if (typeof data.sourceConversationId === 'string' && data.sourceConversationId.length > 0) {
145
- payload.sourceConversationId = data.sourceConversationId;
146
- }
147
- if (payload.phase === 'connected'
148
- && typeof data.openingMessageConversationId === 'string'
149
- && data.openingMessageConversationId.length > 0) {
150
- payload.conversationId = data.openingMessageConversationId;
151
- }
104
+ payload.requirements = requirements;
152
105
  }
153
106
  if (data.targetUserType === 'ai_agent') {
154
107
  payload.targetOwnerId = data.targetOwnerId;
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export * from './verbContract.js';
13
13
  export * from './verbSchemas.js';
14
14
  export * from './verbWire.js';
15
15
  export * from './accessPolicy.js';
16
+ export * from './communication.js';
16
17
  export * from './apiErrors.js';
17
18
  export * from './firestoreValues.js';
18
19
  export * from './moderation.js';
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ export * from './verbContract.js';
13
13
  export * from './verbSchemas.js';
14
14
  export * from './verbWire.js';
15
15
  export * from './accessPolicy.js';
16
+ export * from './communication.js';
16
17
  export * from './apiErrors.js';
17
18
  export * from './firestoreValues.js';
18
19
  export * from './moderation.js';
@@ -42,7 +42,7 @@
42
42
  * `findVerbByteLimitViolations()` (or equivalent checks from the emitted
43
43
  * canon-verbs.limits.json) after schema validation.
44
44
  */
45
- import type { ContactRequestRequirements } from './contactRequest.js';
45
+ import type { GroupInviteRequirements } from './contactRequest.js';
46
46
  /** Identifier for this contract document. */
47
47
  export declare const CANON_VERBS_SCHEMA_VERSION = "canon.verbs.v1";
48
48
  /** $id of the emitted JSON Schema bundle. */
@@ -184,7 +184,7 @@ export declare const VERB_RATE_LIMITS: {
184
184
  /** The one self-context type the platform accepts today. */
185
185
  export declare const SELF_CONTEXT_TYPE = "cross_session";
186
186
  /** Canonical verb names. */
187
- export declare const CANON_VERB_NAMES: 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", "cancel_contact_request", "no_reply"];
187
+ export declare const CANON_VERB_NAMES: 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", "no_reply"];
188
188
  export type CanonVerbName = (typeof CANON_VERB_NAMES)[number];
189
189
  /** Private note-to-self attached to a cross-conversation send. */
190
190
  export interface VerbSelfContext {
@@ -268,7 +268,6 @@ export interface VerbMediaAttachment {
268
268
  */
269
269
  export interface VerbTurnMetadata {
270
270
  turnId?: string | null;
271
- sourceMessageId?: string;
272
271
  turnSemantics?: 'progress' | 'turn_complete' | 'control';
273
272
  deliveryIntent?: 'queue' | 'interrupt' | 'interleave' | 'stop';
274
273
  replyBehavior?: 'allow_auto_reply' | 'suppress_auto_reply';
@@ -323,7 +322,7 @@ export type SendToResult = {
323
322
  selfContextId?: string;
324
323
  created?: boolean;
325
324
  reused?: boolean;
326
- sessionSelection?: VerbSessionSelection['mode'];
325
+ sessionSelection?: string;
327
326
  } | {
328
327
  status: 'requested';
329
328
  requestId: string | null;
@@ -614,7 +613,7 @@ export interface CreateGroupInput {
614
613
  export interface PendingGroupInviteResult {
615
614
  userId: string;
616
615
  requestId: string;
617
- requirements: ContactRequestRequirements;
616
+ requirements: GroupInviteRequirements;
618
617
  }
619
618
  export interface CreateGroupResult {
620
619
  status: 'created';
@@ -654,7 +653,7 @@ export type AddMemberResult = {
654
653
  } | {
655
654
  status: 'pending';
656
655
  requestId: string;
657
- requirements: ContactRequestRequirements;
656
+ requirements: GroupInviteRequirements;
658
657
  };
659
658
  export interface RemoveMemberInput {
660
659
  conversationId: string;
@@ -681,26 +680,11 @@ export interface VerbContact {
681
680
  export interface ListContactsResult {
682
681
  contacts: VerbContact[];
683
682
  }
684
- export type ContactRequestListDirection = 'inbound' | 'outbound';
685
- export interface ListContactRequestsInput {
686
- /** Defaults to inbound. Outbound is the reconnect/recovery surface. */
687
- direction?: ContactRequestListDirection;
688
- /** Include terminal and starting lifecycle states; default false. */
689
- includeResolved?: boolean;
690
- /** Newest-first result cap; default and maximum 100. */
691
- limit?: number;
692
- }
693
- /** Items are SerializedContactRequest (contactRequest.ts), newest first, capped at 100. */
683
+ export type ListContactRequestsInput = Record<string, never>;
684
+ /** Items are SerializedContactRequest (contactRequest.ts) — pending inbound only, newest first, capped at 100. */
694
685
  export interface ListContactRequestsResult {
695
686
  requests: unknown[];
696
687
  }
697
- export interface CancelContactRequestInput {
698
- requestId: string;
699
- }
700
- export interface CancelContactRequestResult {
701
- status: 'cancelled';
702
- requestId: string;
703
- }
704
688
  export interface ListConversationsInput {
705
689
  /** Optional client-side cap; the REST endpoint returns all memberships. */
706
690
  limit?: number;
@@ -200,7 +200,6 @@ export const CANON_VERB_NAMES = [
200
200
  'list_contacts',
201
201
  'list_contact_requests',
202
202
  'list_conversations',
203
- 'cancel_contact_request',
204
203
  'no_reply',
205
204
  ];
206
205
  export const VERB_NATIVE_METADATA_KEYS = [
@@ -1615,19 +1615,7 @@ export declare const CANON_VERBS_JSON_SCHEMA: {
1615
1615
  readonly list_contact_requests_input: {
1616
1616
  readonly type: "object";
1617
1617
  readonly additionalProperties: false;
1618
- readonly properties: {
1619
- readonly direction: {
1620
- readonly enum: readonly ["inbound", "outbound"];
1621
- };
1622
- readonly includeResolved: {
1623
- readonly type: "boolean";
1624
- };
1625
- readonly limit: {
1626
- readonly type: "integer";
1627
- readonly minimum: 1;
1628
- readonly maximum: 100;
1629
- };
1630
- };
1618
+ readonly properties: {};
1631
1619
  };
1632
1620
  readonly list_contact_requests_result: {
1633
1621
  readonly type: "object";
@@ -1639,7 +1627,7 @@ export declare const CANON_VERBS_JSON_SCHEMA: {
1639
1627
  readonly type: "array";
1640
1628
  readonly items: {
1641
1629
  readonly type: "object";
1642
- readonly required: readonly ["id", "requesterId", "targetId", "status", "kind", "phase"];
1630
+ readonly required: readonly ["id", "requesterId", "targetId", "status", "kind"];
1643
1631
  readonly additionalProperties: true;
1644
1632
  readonly properties: {
1645
1633
  readonly id: {
@@ -1655,14 +1643,11 @@ export declare const CANON_VERBS_JSON_SCHEMA: {
1655
1643
  readonly type: "string";
1656
1644
  };
1657
1645
  readonly status: {
1658
- readonly enum: readonly ["pending", "approved", "rejected", "expired", "cancelled"];
1646
+ readonly enum: readonly ["pending", "approved", "rejected", "expired"];
1659
1647
  };
1660
1648
  readonly kind: {
1661
1649
  readonly enum: readonly ["dm", "group_invite"];
1662
1650
  };
1663
- readonly phase: {
1664
- readonly enum: readonly ["awaiting_owner", "starting", "connected", "rejected", "expired", "cancelled", "failed"];
1665
- };
1666
1651
  readonly message: {
1667
1652
  readonly type: readonly ["string", "null"];
1668
1653
  };
@@ -1756,30 +1741,6 @@ export declare const CANON_VERBS_JSON_SCHEMA: {
1756
1741
  };
1757
1742
  };
1758
1743
  };
1759
- readonly cancel_contact_request_input: {
1760
- readonly type: "object";
1761
- readonly required: readonly ["requestId"];
1762
- readonly additionalProperties: false;
1763
- readonly properties: {
1764
- readonly requestId: {
1765
- readonly type: "string";
1766
- readonly pattern: "^[A-Za-z0-9_.:-]{1,160}$";
1767
- };
1768
- };
1769
- };
1770
- readonly cancel_contact_request_result: {
1771
- readonly type: "object";
1772
- readonly required: readonly ["status", "requestId"];
1773
- readonly additionalProperties: true;
1774
- readonly properties: {
1775
- readonly status: {
1776
- readonly const: "cancelled";
1777
- };
1778
- readonly requestId: {
1779
- readonly type: "string";
1780
- };
1781
- };
1782
- };
1783
1744
  readonly no_reply_input: {
1784
1745
  readonly type: "object";
1785
1746
  readonly description: string;
@@ -999,17 +999,13 @@ const list_contacts_result = {
999
999
  const list_contact_requests_input = {
1000
1000
  type: 'object',
1001
1001
  additionalProperties: false,
1002
- properties: {
1003
- direction: { enum: ['inbound', 'outbound'] },
1004
- includeResolved: { type: 'boolean' },
1005
- limit: { type: 'integer', minimum: 1, maximum: 100 },
1006
- },
1002
+ properties: {},
1007
1003
  };
1008
1004
  const list_contact_requests_result = {
1009
1005
  type: 'object',
1010
- description: 'Contact requests, newest first, capped at 100. Defaults to pending inbound; '
1011
- + 'requesters use direction=outbound with includeResolved=true to recover lifecycle state. '
1012
- + 'Items are SerializedContactRequest (backend-contracts contactRequest.ts).',
1006
+ description: 'Pending inbound requests, newest first, capped at 100. Items are '
1007
+ + 'SerializedContactRequest (backend-contracts contactRequest.ts). Read-only awareness: '
1008
+ + "approval routes to the owner; agents cannot approve/reject.",
1013
1009
  required: ['requests'],
1014
1010
  additionalProperties: true,
1015
1011
  properties: {
@@ -1017,26 +1013,15 @@ const list_contact_requests_result = {
1017
1013
  type: 'array',
1018
1014
  items: {
1019
1015
  type: 'object',
1020
- required: ['id', 'requesterId', 'targetId', 'status', 'kind', 'phase'],
1016
+ required: ['id', 'requesterId', 'targetId', 'status', 'kind'],
1021
1017
  additionalProperties: true,
1022
1018
  properties: {
1023
1019
  id: { type: 'string' },
1024
1020
  requesterId: { type: 'string' },
1025
1021
  requesterName: { type: 'string' },
1026
1022
  targetId: { type: 'string' },
1027
- status: { enum: ['pending', 'approved', 'rejected', 'expired', 'cancelled'] },
1023
+ status: { enum: ['pending', 'approved', 'rejected', 'expired'] },
1028
1024
  kind: { enum: ['dm', 'group_invite'] },
1029
- phase: {
1030
- enum: [
1031
- 'awaiting_owner',
1032
- 'starting',
1033
- 'connected',
1034
- 'rejected',
1035
- 'expired',
1036
- 'cancelled',
1037
- 'failed',
1038
- ],
1039
- },
1040
1025
  message: { type: ['string', 'null'] },
1041
1026
  createdAt: { type: ['string', 'null'] },
1042
1027
  expiresAt: { type: ['string', 'null'] },
@@ -1045,23 +1030,6 @@ const list_contact_requests_result = {
1045
1030
  },
1046
1031
  },
1047
1032
  };
1048
- const cancel_contact_request_input = {
1049
- type: 'object',
1050
- required: ['requestId'],
1051
- additionalProperties: false,
1052
- properties: {
1053
- requestId: { type: 'string', pattern: VERB_ID_PATTERNS.runtimeId },
1054
- },
1055
- };
1056
- const cancel_contact_request_result = {
1057
- type: 'object',
1058
- required: ['status', 'requestId'],
1059
- additionalProperties: true,
1060
- properties: {
1061
- status: { const: 'cancelled' },
1062
- requestId: { type: 'string' },
1063
- },
1064
- };
1065
1033
  const list_conversations_input = {
1066
1034
  type: 'object',
1067
1035
  additionalProperties: false,
@@ -1201,8 +1169,6 @@ export const CANON_VERBS_JSON_SCHEMA = {
1201
1169
  list_contact_requests_result,
1202
1170
  list_conversations_input,
1203
1171
  list_conversations_result,
1204
- cancel_contact_request_input,
1205
- cancel_contact_request_result,
1206
1172
  no_reply_input,
1207
1173
  no_reply_result,
1208
1174
  },
@@ -40,8 +40,6 @@ export type VerbWireBody = {
40
40
  /** Turn-protocol keys the server enforces on (subset of TurnMetadata). */
41
41
  export interface VerbWireTurn {
42
42
  turnId?: string | null;
43
- /** Trusted message that initiated a host-bound owner turn. */
44
- sourceMessageId?: string;
45
43
  turnSemantics?: 'progress' | 'turn_complete' | 'control';
46
44
  deliveryIntent?: 'queue' | 'interrupt' | 'interleave' | 'stop';
47
45
  replyBehavior?: 'allow_auto_reply' | 'suppress_auto_reply';
@@ -139,7 +137,7 @@ export declare const CANON_VERB_WIRE_JSON_SCHEMA: {
139
137
  readonly const: "canon.verb-wire.v1";
140
138
  };
141
139
  readonly verb: {
142
- 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", "cancel_contact_request", "no_reply"];
140
+ 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", "no_reply"];
143
141
  };
144
142
  readonly envelope: {
145
143
  readonly $ref: "#/$defs/envelope";
@@ -466,10 +464,6 @@ export declare const CANON_VERB_WIRE_JSON_SCHEMA: {
466
464
  readonly turnId: {
467
465
  readonly type: readonly ["string", "null"];
468
466
  };
469
- readonly sourceMessageId: {
470
- readonly type: "string";
471
- readonly minLength: 1;
472
- };
473
467
  readonly turnSemantics: {
474
468
  readonly enum: readonly ["progress", "turn_complete", "control"];
475
469
  };
@@ -515,7 +509,7 @@ export declare const CANON_VERB_WIRE_JSON_SCHEMA: {
515
509
  readonly const: "canon.verb-wire.v1";
516
510
  };
517
511
  readonly verb: {
518
- 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", "cancel_contact_request", "no_reply"];
512
+ 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", "no_reply"];
519
513
  };
520
514
  readonly result: {
521
515
  readonly $ref: "#/$defs/body";
package/dist/verbWire.js CHANGED
@@ -112,7 +112,6 @@ export const VERB_WIRE_ENVELOPE_FIELDS = {
112
112
  list_contacts: { required: [], optional: [] },
113
113
  list_contact_requests: { required: [], optional: [] },
114
114
  list_conversations: { required: [], optional: ['limit'] },
115
- cancel_contact_request: { required: ['requestId'], optional: [] },
116
115
  no_reply: { required: [], optional: ['conversationId', 'messageId'] },
117
116
  };
118
117
  // ---------------------------------------------------------------------------
@@ -154,7 +153,6 @@ const wireTurnDef = {
154
153
  additionalProperties: false,
155
154
  properties: {
156
155
  turnId: { type: ['string', 'null'] },
157
- sourceMessageId: { type: 'string', minLength: 1 },
158
156
  turnSemantics: { enum: ['progress', 'turn_complete', 'control'] },
159
157
  deliveryIntent: { enum: ['queue', 'interrupt', 'interleave', 'stop'] },
160
158
  replyBehavior: { enum: ['allow_auto_reply', 'suppress_auto_reply'] },
@@ -280,13 +278,7 @@ export const CANON_VERB_WIRE_JSON_SCHEMA = {
280
278
  // ---------------------------------------------------------------------------
281
279
  // Intent <-> wire projection (json codec)
282
280
  // ---------------------------------------------------------------------------
283
- const TURN_KEYS = [
284
- 'turnId',
285
- 'sourceMessageId',
286
- 'turnSemantics',
287
- 'deliveryIntent',
288
- 'replyBehavior',
289
- ];
281
+ const TURN_KEYS = ['turnId', 'turnSemantics', 'deliveryIntent', 'replyBehavior'];
290
282
  function splitTurnMetadata(metadata) {
291
283
  if (!metadata || typeof metadata !== 'object')
292
284
  return { turn: undefined, rest: undefined };
@@ -471,25 +463,14 @@ export function projectVerbIntentToWire(verb, intent, options) {
471
463
  envelope = compact({ conversationId: input.conversationId });
472
464
  break;
473
465
  }
474
- case 'list_contacts': {
475
- break;
476
- }
466
+ case 'list_contacts':
477
467
  case 'list_contact_requests': {
478
- value = compact({
479
- direction: input.direction,
480
- includeResolved: input.includeResolved,
481
- limit: input.limit,
482
- });
483
468
  break;
484
469
  }
485
470
  case 'list_conversations': {
486
471
  envelope = compact({ limit: input.limit });
487
472
  break;
488
473
  }
489
- case 'cancel_contact_request': {
490
- envelope = compact({ requestId: input.requestId });
491
- break;
492
- }
493
474
  case 'no_reply': {
494
475
  envelope = compact({ conversationId: input.conversationId, messageId: input.messageId });
495
476
  value = compact({ reason: input.reason });
@@ -628,17 +609,10 @@ export function mergeVerbWireToIntent(request) {
628
609
  case 'leave_conversation':
629
610
  return compact({ conversationId: envelope.conversationId });
630
611
  case 'list_contacts':
631
- return {};
632
612
  case 'list_contact_requests':
633
- return compact({
634
- direction: value.direction,
635
- includeResolved: value.includeResolved,
636
- limit: value.limit,
637
- });
613
+ return {};
638
614
  case 'list_conversations':
639
615
  return compact({ limit: envelope.limit });
640
- case 'cancel_contact_request':
641
- return compact({ requestId: envelope.requestId });
642
616
  case 'no_reply':
643
617
  return compact({ conversationId: envelope.conversationId, messageId: envelope.messageId, reason: value.reason });
644
618
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/backend-contracts",
3
- "version": "6.4.0",
3
+ "version": "7.0.0",
4
4
  "description": "Canon backend contract helpers shared by Functions and stream-service",
5
5
  "type": "module",
6
6
  "main": "dist/cjs/index.js",
@@ -43,7 +43,7 @@
43
43
  "access": "public"
44
44
  },
45
45
  "devDependencies": {
46
- "@canonmsg/rich-cards": "^0.10.2",
46
+ "@canonmsg/rich-cards": "^0.10.3",
47
47
  "@types/node": "^22.0.0",
48
48
  "ajv": "^8.20.0",
49
49
  "typescript": "~5.7.0",