@canonmsg/backend-contracts 5.4.1 → 6.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.
@@ -0,0 +1 @@
1
+ export declare const MESSAGE_ADMISSION_REQUIRED_CODE: "MESSAGE_ADMISSION_REQUIRED";
@@ -0,0 +1 @@
1
+ export const MESSAGE_ADMISSION_REQUIRED_CODE = 'MESSAGE_ADMISSION_REQUIRED';
@@ -64,7 +64,8 @@
64
64
  "runtimeOptionValue": "^[A-Za-z0-9@_][A-Za-z0-9_.:/@+\\-]{0,255}$",
65
65
  "workspaceOptionId": "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$",
66
66
  "runtimeHandleKey": "^[A-Za-z0-9_.:-]{1,80}$",
67
- "runtimeControlId": "^[A-Za-z0-9_-]{1,80}$"
67
+ "runtimeControlId": "^[A-Za-z0-9_-]{1,80}$",
68
+ "resumableUploadId": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
68
69
  },
69
70
  "byteSemantics": {
70
71
  "send_to.text": "utf8_bytes<=messageTextBytes",
@@ -107,7 +107,12 @@
107
107
  "url": {
108
108
  "type": "string",
109
109
  "minLength": 1,
110
- "description": "Must come from /media/upload (Canon storage) or the GIF picker — server allowlist, fail-closed (functions/src/utils/mediaUploadSafety.ts)."
110
+ "description": "Must come from a canonical Canon upload/finalization response or the GIF picker — server allowlist, fail-closed (functions/src/utils/mediaUploadSafety.ts)."
111
+ },
112
+ "uploadId": {
113
+ "type": "string",
114
+ "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$",
115
+ "description": "Server-issued resumable identity. Preserve it from finalization so the durable message transaction can validate and retain initially temporary media."
111
116
  },
112
117
  "mimeType": {
113
118
  "type": "string"
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MESSAGE_ADMISSION_REQUIRED_CODE = void 0;
4
+ exports.MESSAGE_ADMISSION_REQUIRED_CODE = 'MESSAGE_ADMISSION_REQUIRED';
package/dist/cjs/index.js CHANGED
@@ -24,11 +24,11 @@ __exportStar(require("./turnProtocol.js"), exports);
24
24
  __exportStar(require("./agentBehaviorPolicy.js"), exports);
25
25
  __exportStar(require("./agentSearch.js"), exports);
26
26
  __exportStar(require("./contactRequest.js"), exports);
27
- __exportStar(require("./contactSources.js"), exports);
28
27
  __exportStar(require("./verbContract.js"), exports);
29
28
  __exportStar(require("./verbSchemas.js"), exports);
30
29
  __exportStar(require("./verbWire.js"), exports);
31
30
  __exportStar(require("./accessPolicy.js"), exports);
31
+ __exportStar(require("./apiErrors.js"), exports);
32
32
  __exportStar(require("./firestoreValues.js"), exports);
33
33
  __exportStar(require("./moderation.js"), exports);
34
34
  __exportStar(require("./selfContext.js"), exports);
package/dist/cjs/media.js CHANGED
@@ -83,6 +83,11 @@ function normalizeStoredAttachments(value) {
83
83
  if (typeof candidate.url !== 'string' || candidate.url.length === 0) {
84
84
  return null;
85
85
  }
86
+ if (candidate.uploadId !== undefined
87
+ && (typeof candidate.uploadId !== 'string'
88
+ || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(candidate.uploadId))) {
89
+ return null;
90
+ }
86
91
  const sizeBytes = normalizeAttachmentNumber(candidate.sizeBytes);
87
92
  const width = normalizeAttachmentNumber(candidate.width);
88
93
  const height = normalizeAttachmentNumber(candidate.height);
@@ -90,6 +95,7 @@ function normalizeStoredAttachments(value) {
90
95
  attachments.push({
91
96
  kind: candidate.kind,
92
97
  url: candidate.url,
98
+ ...(candidate.uploadId ? { uploadId: candidate.uploadId } : {}),
93
99
  ...(typeof candidate.mimeType === 'string' && candidate.mimeType ? { mimeType: candidate.mimeType } : {}),
94
100
  ...(typeof candidate.fileName === 'string' && candidate.fileName ? { fileName: candidate.fileName } : {}),
95
101
  ...(sizeBytes !== undefined ? { sizeBytes } : {}),
@@ -82,6 +82,8 @@ exports.VERB_ID_PATTERNS = {
82
82
  workspaceOptionId: '^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$',
83
83
  runtimeHandleKey: '^[A-Za-z0-9_.:-]{1,80}$',
84
84
  runtimeControlId: '^[A-Za-z0-9_-]{1,80}$',
85
+ /** UUID issued by Canon's resumable-media session endpoint. */
86
+ resumableUploadId: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$',
85
87
  };
86
88
  /**
87
89
  * Server-enforced limits, single-sourced. Each value cites its enforcement
@@ -93,9 +93,15 @@ const mediaAttachmentDef = {
93
93
  url: {
94
94
  type: 'string',
95
95
  minLength: 1,
96
- description: 'Must come from /media/upload (Canon storage) or the GIF picker — server '
96
+ description: 'Must come from a canonical Canon upload/finalization response or the GIF picker — server '
97
97
  + 'allowlist, fail-closed (functions/src/utils/mediaUploadSafety.ts).',
98
98
  },
99
+ uploadId: {
100
+ type: 'string',
101
+ pattern: verbContract_js_1.VERB_ID_PATTERNS.resumableUploadId,
102
+ description: 'Server-issued resumable identity. Preserve it from finalization so the durable message '
103
+ + 'transaction can validate and retain initially temporary media.',
104
+ },
99
105
  mimeType: { type: 'string' },
100
106
  fileName: { type: 'string' },
101
107
  sizeBytes: { type: 'number', minimum: 0 },
package/dist/index.d.ts CHANGED
@@ -8,11 +8,11 @@ export * from './turnProtocol.js';
8
8
  export * from './agentBehaviorPolicy.js';
9
9
  export * from './agentSearch.js';
10
10
  export * from './contactRequest.js';
11
- export * from './contactSources.js';
12
11
  export * from './verbContract.js';
13
12
  export * from './verbSchemas.js';
14
13
  export * from './verbWire.js';
15
14
  export * from './accessPolicy.js';
15
+ export * from './apiErrors.js';
16
16
  export * from './firestoreValues.js';
17
17
  export * from './moderation.js';
18
18
  export * from './selfContext.js';
package/dist/index.js CHANGED
@@ -8,11 +8,11 @@ export * from './turnProtocol.js';
8
8
  export * from './agentBehaviorPolicy.js';
9
9
  export * from './agentSearch.js';
10
10
  export * from './contactRequest.js';
11
- export * from './contactSources.js';
12
11
  export * from './verbContract.js';
13
12
  export * from './verbSchemas.js';
14
13
  export * from './verbWire.js';
15
14
  export * from './accessPolicy.js';
15
+ export * from './apiErrors.js';
16
16
  export * from './firestoreValues.js';
17
17
  export * from './moderation.js';
18
18
  export * from './selfContext.js';
package/dist/media.d.ts CHANGED
@@ -2,6 +2,12 @@ export type MediaAttachmentKind = 'image' | 'audio' | 'video' | 'file';
2
2
  export interface MediaAttachment {
3
3
  kind: MediaAttachmentKind;
4
4
  url: string;
5
+ /**
6
+ * Server-issued identity for a finalized resumable upload. The send path
7
+ * uses this to retain temporary canonical media atomically with the message.
8
+ * Legacy/base64 uploads intentionally omit it.
9
+ */
10
+ uploadId?: string;
5
11
  mimeType?: string;
6
12
  fileName?: string;
7
13
  sizeBytes?: number;
package/dist/media.js CHANGED
@@ -75,6 +75,11 @@ export function normalizeStoredAttachments(value) {
75
75
  if (typeof candidate.url !== 'string' || candidate.url.length === 0) {
76
76
  return null;
77
77
  }
78
+ if (candidate.uploadId !== undefined
79
+ && (typeof candidate.uploadId !== 'string'
80
+ || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(candidate.uploadId))) {
81
+ return null;
82
+ }
78
83
  const sizeBytes = normalizeAttachmentNumber(candidate.sizeBytes);
79
84
  const width = normalizeAttachmentNumber(candidate.width);
80
85
  const height = normalizeAttachmentNumber(candidate.height);
@@ -82,6 +87,7 @@ export function normalizeStoredAttachments(value) {
82
87
  attachments.push({
83
88
  kind: candidate.kind,
84
89
  url: candidate.url,
90
+ ...(candidate.uploadId ? { uploadId: candidate.uploadId } : {}),
85
91
  ...(typeof candidate.mimeType === 'string' && candidate.mimeType ? { mimeType: candidate.mimeType } : {}),
86
92
  ...(typeof candidate.fileName === 'string' && candidate.fileName ? { fileName: candidate.fileName } : {}),
87
93
  ...(sizeBytes !== undefined ? { sizeBytes } : {}),
@@ -77,6 +77,8 @@ export declare const VERB_ID_PATTERNS: {
77
77
  readonly workspaceOptionId: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$";
78
78
  readonly runtimeHandleKey: "^[A-Za-z0-9_.:-]{1,80}$";
79
79
  readonly runtimeControlId: "^[A-Za-z0-9_-]{1,80}$";
80
+ /** UUID issued by Canon's resumable-media session endpoint. */
81
+ readonly resumableUploadId: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$";
80
82
  };
81
83
  /**
82
84
  * Server-enforced limits, single-sourced. Each value cites its enforcement
@@ -246,8 +248,10 @@ export declare const VERB_NATIVE_METADATA_KEYS: readonly ["runtime", "method", "
246
248
  export declare function normalizeVerbNativeMetadata(value: unknown): VerbNativeMetadata | undefined;
247
249
  export interface VerbMediaAttachment {
248
250
  kind: 'image' | 'audio' | 'video' | 'file';
249
- /** Must come from /media/upload (Canon storage) or the GIF picker — server allowlisted. */
251
+ /** Must come from a canonical Canon upload/finalization response or the GIF picker. */
250
252
  url: string;
253
+ /** Server-issued resumable identity; preserve it so durable sends retain temporary media. */
254
+ uploadId?: string;
251
255
  mimeType?: string;
252
256
  fileName?: string;
253
257
  sizeBytes?: number;
@@ -846,6 +850,8 @@ export declare const CANON_VERB_LIMITS_ARTIFACT: {
846
850
  readonly workspaceOptionId: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$";
847
851
  readonly runtimeHandleKey: "^[A-Za-z0-9_.:-]{1,80}$";
848
852
  readonly runtimeControlId: "^[A-Za-z0-9_-]{1,80}$";
853
+ /** UUID issued by Canon's resumable-media session endpoint. */
854
+ readonly resumableUploadId: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$";
849
855
  };
850
856
  readonly byteSemantics: {
851
857
  readonly 'send_to.text': "utf8_bytes<=messageTextBytes";
@@ -76,6 +76,8 @@ export const VERB_ID_PATTERNS = {
76
76
  workspaceOptionId: '^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$',
77
77
  runtimeHandleKey: '^[A-Za-z0-9_.:-]{1,80}$',
78
78
  runtimeControlId: '^[A-Za-z0-9_-]{1,80}$',
79
+ /** UUID issued by Canon's resumable-media session endpoint. */
80
+ resumableUploadId: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$',
79
81
  };
80
82
  /**
81
83
  * Server-enforced limits, single-sourced. Each value cites its enforcement
@@ -253,6 +253,11 @@ export declare const CANON_VERBS_JSON_SCHEMA: {
253
253
  readonly minLength: 1;
254
254
  readonly description: string;
255
255
  };
256
+ readonly uploadId: {
257
+ readonly type: "string";
258
+ readonly pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$";
259
+ readonly description: string;
260
+ };
256
261
  readonly mimeType: {
257
262
  readonly type: "string";
258
263
  };
@@ -88,9 +88,15 @@ const mediaAttachmentDef = {
88
88
  url: {
89
89
  type: 'string',
90
90
  minLength: 1,
91
- description: 'Must come from /media/upload (Canon storage) or the GIF picker — server '
91
+ description: 'Must come from a canonical Canon upload/finalization response or the GIF picker — server '
92
92
  + 'allowlist, fail-closed (functions/src/utils/mediaUploadSafety.ts).',
93
93
  },
94
+ uploadId: {
95
+ type: 'string',
96
+ pattern: VERB_ID_PATTERNS.resumableUploadId,
97
+ description: 'Server-issued resumable identity. Preserve it from finalization so the durable message '
98
+ + 'transaction can validate and retain initially temporary media.',
99
+ },
94
100
  mimeType: { type: 'string' },
95
101
  fileName: { type: 'string' },
96
102
  sizeBytes: { type: 'number', minimum: 0 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/backend-contracts",
3
- "version": "5.4.1",
3
+ "version": "6.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.0",
46
+ "@canonmsg/rich-cards": "^0.10.2",
47
47
  "@types/node": "^22.0.0",
48
48
  "ajv": "^8.20.0",
49
49
  "typescript": "~5.7.0",
@@ -1,45 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ADMISSION_GRANTING_SOURCES = void 0;
4
- exports.isAdmissionGrantingContactSource = isAdmissionGrantingContactSource;
5
- /**
6
- * Sources that represent a user-initiated relationship which grants admission
7
- * past `inboundPolicy: 'approval-required'` and
8
- * `groupJoinPolicy: 'approval-required'`.
9
- *
10
- * - `contact_request`: an explicit approval flow.
11
- * - `direct_add`: the user added the other side directly.
12
- * - `link` / `qr`: out-of-band invitations that imply consent.
13
- * - `group`: established by being in a shared group.
14
- * - `phone_book`: included intentionally — if a human has a registered Canon
15
- * user in their device contacts, that is enough to start a human-human DM
16
- * without a separate Canon request.
17
- * - `open_inbound_message`: the unified source written when a delivered direct
18
- * interaction reaches an `inboundPolicy: 'open'` recipient. The recipient
19
- * consented to open inbound; the resulting mutual contact is real.
20
- *
21
- * Hard caps (`owner-only`, blocks, inactive agent) override admission grants
22
- * regardless of source — ordering lives in `functions/src/utils/access.ts`
23
- * (`evaluatePolicy` / `buildPolicyContext`).
24
- */
25
- const ADMISSION_GRANTING_CONTACT_SOURCES = [
26
- 'contact_request',
27
- 'direct_add',
28
- 'link',
29
- 'qr',
30
- 'group',
31
- 'phone_book',
32
- 'open_inbound_message',
33
- ];
34
- exports.ADMISSION_GRANTING_SOURCES = new Set(ADMISSION_GRANTING_CONTACT_SOURCES);
35
- /**
36
- * Returns true if a contact-doc source counts as an admission-granting
37
- * relationship. `null` / `undefined` (no doc, or doc with a missing source)
38
- * both return `false` — every contact doc must have an explicit source after
39
- * the cleanup.
40
- */
41
- function isAdmissionGrantingContactSource(source) {
42
- if (source === null || source === undefined)
43
- return false;
44
- return exports.ADMISSION_GRANTING_SOURCES.has(source);
45
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * Sources that represent a user-initiated relationship which grants admission
3
- * past `inboundPolicy: 'approval-required'` and
4
- * `groupJoinPolicy: 'approval-required'`.
5
- *
6
- * - `contact_request`: an explicit approval flow.
7
- * - `direct_add`: the user added the other side directly.
8
- * - `link` / `qr`: out-of-band invitations that imply consent.
9
- * - `group`: established by being in a shared group.
10
- * - `phone_book`: included intentionally — if a human has a registered Canon
11
- * user in their device contacts, that is enough to start a human-human DM
12
- * without a separate Canon request.
13
- * - `open_inbound_message`: the unified source written when a delivered direct
14
- * interaction reaches an `inboundPolicy: 'open'` recipient. The recipient
15
- * consented to open inbound; the resulting mutual contact is real.
16
- *
17
- * Hard caps (`owner-only`, blocks, inactive agent) override admission grants
18
- * regardless of source — ordering lives in `functions/src/utils/access.ts`
19
- * (`evaluatePolicy` / `buildPolicyContext`).
20
- */
21
- declare const ADMISSION_GRANTING_CONTACT_SOURCES: readonly ["contact_request", "direct_add", "link", "qr", "group", "phone_book", "open_inbound_message"];
22
- export type AdmissionGrantingContactSource = (typeof ADMISSION_GRANTING_CONTACT_SOURCES)[number];
23
- export declare const ADMISSION_GRANTING_SOURCES: ReadonlySet<AdmissionGrantingContactSource>;
24
- /**
25
- * Returns true if a contact-doc source counts as an admission-granting
26
- * relationship. `null` / `undefined` (no doc, or doc with a missing source)
27
- * both return `false` — every contact doc must have an explicit source after
28
- * the cleanup.
29
- */
30
- export declare function isAdmissionGrantingContactSource(source: string | null | undefined): boolean;
31
- export {};
@@ -1,41 +0,0 @@
1
- /**
2
- * Sources that represent a user-initiated relationship which grants admission
3
- * past `inboundPolicy: 'approval-required'` and
4
- * `groupJoinPolicy: 'approval-required'`.
5
- *
6
- * - `contact_request`: an explicit approval flow.
7
- * - `direct_add`: the user added the other side directly.
8
- * - `link` / `qr`: out-of-band invitations that imply consent.
9
- * - `group`: established by being in a shared group.
10
- * - `phone_book`: included intentionally — if a human has a registered Canon
11
- * user in their device contacts, that is enough to start a human-human DM
12
- * without a separate Canon request.
13
- * - `open_inbound_message`: the unified source written when a delivered direct
14
- * interaction reaches an `inboundPolicy: 'open'` recipient. The recipient
15
- * consented to open inbound; the resulting mutual contact is real.
16
- *
17
- * Hard caps (`owner-only`, blocks, inactive agent) override admission grants
18
- * regardless of source — ordering lives in `functions/src/utils/access.ts`
19
- * (`evaluatePolicy` / `buildPolicyContext`).
20
- */
21
- const ADMISSION_GRANTING_CONTACT_SOURCES = [
22
- 'contact_request',
23
- 'direct_add',
24
- 'link',
25
- 'qr',
26
- 'group',
27
- 'phone_book',
28
- 'open_inbound_message',
29
- ];
30
- export const ADMISSION_GRANTING_SOURCES = new Set(ADMISSION_GRANTING_CONTACT_SOURCES);
31
- /**
32
- * Returns true if a contact-doc source counts as an admission-granting
33
- * relationship. `null` / `undefined` (no doc, or doc with a missing source)
34
- * both return `false` — every contact doc must have an explicit source after
35
- * the cleanup.
36
- */
37
- export function isAdmissionGrantingContactSource(source) {
38
- if (source === null || source === undefined)
39
- return false;
40
- return ADMISSION_GRANTING_SOURCES.has(source);
41
- }