@canonmsg/backend-contracts 5.4.1 → 6.1.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"
@@ -130,6 +135,24 @@
130
135
  "durationMs": {
131
136
  "type": "number",
132
137
  "minimum": 0
138
+ },
139
+ "thumbnailUrl": {
140
+ "type": "string",
141
+ "minLength": 1
142
+ },
143
+ "processingStatus": {
144
+ "enum": [
145
+ "processing",
146
+ "ready",
147
+ "failed"
148
+ ]
149
+ },
150
+ "processingErrorCode": {
151
+ "enum": [
152
+ "invalid_video",
153
+ "video_limits_exceeded",
154
+ "video_processing_failed"
155
+ ]
133
156
  }
134
157
  }
135
158
  },
@@ -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,19 +83,44 @@ 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);
89
94
  const durationMs = normalizeAttachmentNumber(candidate.durationMs);
95
+ const processingStatus = candidate.processingStatus;
96
+ if (processingStatus !== undefined
97
+ && processingStatus !== 'processing'
98
+ && processingStatus !== 'ready'
99
+ && processingStatus !== 'failed') {
100
+ return null;
101
+ }
102
+ const processingErrorCode = candidate.processingErrorCode;
103
+ if (processingErrorCode !== undefined
104
+ && processingErrorCode !== 'invalid_video'
105
+ && processingErrorCode !== 'video_limits_exceeded'
106
+ && processingErrorCode !== 'video_processing_failed') {
107
+ return null;
108
+ }
90
109
  attachments.push({
91
110
  kind: candidate.kind,
92
111
  url: candidate.url,
112
+ ...(candidate.uploadId ? { uploadId: candidate.uploadId } : {}),
93
113
  ...(typeof candidate.mimeType === 'string' && candidate.mimeType ? { mimeType: candidate.mimeType } : {}),
94
114
  ...(typeof candidate.fileName === 'string' && candidate.fileName ? { fileName: candidate.fileName } : {}),
95
115
  ...(sizeBytes !== undefined ? { sizeBytes } : {}),
96
116
  ...(width !== undefined ? { width } : {}),
97
117
  ...(height !== undefined ? { height } : {}),
98
118
  ...(durationMs !== undefined ? { durationMs } : {}),
119
+ ...(typeof candidate.thumbnailUrl === 'string' && candidate.thumbnailUrl
120
+ ? { thumbnailUrl: candidate.thumbnailUrl }
121
+ : {}),
122
+ ...(processingStatus !== undefined ? { processingStatus } : {}),
123
+ ...(processingErrorCode !== undefined ? { processingErrorCode } : {}),
99
124
  });
100
125
  }
101
126
  return attachments.length > 0 ? attachments : [];
@@ -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,15 +93,26 @@ 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 },
102
108
  width: { type: 'number', minimum: 0 },
103
109
  height: { type: 'number', minimum: 0 },
104
110
  durationMs: { type: 'number', minimum: 0 },
111
+ thumbnailUrl: { type: 'string', minLength: 1 },
112
+ processingStatus: { enum: ['processing', 'ready', 'failed'] },
113
+ processingErrorCode: {
114
+ enum: ['invalid_video', 'video_limits_exceeded', 'video_processing_failed'],
115
+ },
105
116
  },
106
117
  };
107
118
  const turnMetadataDef = {
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
@@ -1,13 +1,28 @@
1
1
  export type MediaAttachmentKind = 'image' | 'audio' | 'video' | 'file';
2
+ /** Lifecycle for newly uploaded videos that are normalized server-side. */
3
+ export type VideoProcessingStatus = 'processing' | 'ready' | 'failed';
4
+ /** Stable, display-safe reasons for a terminal video processing failure. */
5
+ export type VideoProcessingErrorCode = 'invalid_video' | 'video_limits_exceeded' | 'video_processing_failed';
2
6
  export interface MediaAttachment {
3
7
  kind: MediaAttachmentKind;
4
8
  url: string;
9
+ /**
10
+ * Server-issued identity for a finalized resumable upload. The send path
11
+ * uses this to retain temporary canonical media atomically with the message.
12
+ * Legacy/base64 uploads intentionally omit it.
13
+ */
14
+ uploadId?: string;
5
15
  mimeType?: string;
6
16
  fileName?: string;
7
17
  sizeBytes?: number;
8
18
  width?: number;
9
19
  height?: number;
10
20
  durationMs?: number;
21
+ /** Server-generated poster for a normalized video. */
22
+ thumbnailUrl?: string;
23
+ /** Missing means a legacy attachment that is immediately playable. */
24
+ processingStatus?: VideoProcessingStatus;
25
+ processingErrorCode?: VideoProcessingErrorCode;
11
26
  }
12
27
  export declare function inferMediaAttachmentKind(mimeType: string): MediaAttachmentKind;
13
28
  export declare function getStoredFileExtension(input: {
package/dist/media.js CHANGED
@@ -75,19 +75,44 @@ 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);
81
86
  const durationMs = normalizeAttachmentNumber(candidate.durationMs);
87
+ const processingStatus = candidate.processingStatus;
88
+ if (processingStatus !== undefined
89
+ && processingStatus !== 'processing'
90
+ && processingStatus !== 'ready'
91
+ && processingStatus !== 'failed') {
92
+ return null;
93
+ }
94
+ const processingErrorCode = candidate.processingErrorCode;
95
+ if (processingErrorCode !== undefined
96
+ && processingErrorCode !== 'invalid_video'
97
+ && processingErrorCode !== 'video_limits_exceeded'
98
+ && processingErrorCode !== 'video_processing_failed') {
99
+ return null;
100
+ }
82
101
  attachments.push({
83
102
  kind: candidate.kind,
84
103
  url: candidate.url,
104
+ ...(candidate.uploadId ? { uploadId: candidate.uploadId } : {}),
85
105
  ...(typeof candidate.mimeType === 'string' && candidate.mimeType ? { mimeType: candidate.mimeType } : {}),
86
106
  ...(typeof candidate.fileName === 'string' && candidate.fileName ? { fileName: candidate.fileName } : {}),
87
107
  ...(sizeBytes !== undefined ? { sizeBytes } : {}),
88
108
  ...(width !== undefined ? { width } : {}),
89
109
  ...(height !== undefined ? { height } : {}),
90
110
  ...(durationMs !== undefined ? { durationMs } : {}),
111
+ ...(typeof candidate.thumbnailUrl === 'string' && candidate.thumbnailUrl
112
+ ? { thumbnailUrl: candidate.thumbnailUrl }
113
+ : {}),
114
+ ...(processingStatus !== undefined ? { processingStatus } : {}),
115
+ ...(processingErrorCode !== undefined ? { processingErrorCode } : {}),
91
116
  });
92
117
  }
93
118
  return attachments.length > 0 ? attachments : [];
@@ -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,14 +248,19 @@ 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;
254
258
  width?: number;
255
259
  height?: number;
256
260
  durationMs?: number;
261
+ thumbnailUrl?: string;
262
+ processingStatus?: 'processing' | 'ready' | 'failed';
263
+ processingErrorCode?: 'invalid_video' | 'video_limits_exceeded' | 'video_processing_failed';
257
264
  }
258
265
  /**
259
266
  * Well-known turn-protocol keys inside the free-form metadata envelope
@@ -846,6 +853,8 @@ export declare const CANON_VERB_LIMITS_ARTIFACT: {
846
853
  readonly workspaceOptionId: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$";
847
854
  readonly runtimeHandleKey: "^[A-Za-z0-9_.:-]{1,80}$";
848
855
  readonly runtimeControlId: "^[A-Za-z0-9_-]{1,80}$";
856
+ /** UUID issued by Canon's resumable-media session endpoint. */
857
+ 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
858
  };
850
859
  readonly byteSemantics: {
851
860
  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
  };
@@ -275,6 +280,16 @@ export declare const CANON_VERBS_JSON_SCHEMA: {
275
280
  readonly type: "number";
276
281
  readonly minimum: 0;
277
282
  };
283
+ readonly thumbnailUrl: {
284
+ readonly type: "string";
285
+ readonly minLength: 1;
286
+ };
287
+ readonly processingStatus: {
288
+ readonly enum: readonly ["processing", "ready", "failed"];
289
+ };
290
+ readonly processingErrorCode: {
291
+ readonly enum: readonly ["invalid_video", "video_limits_exceeded", "video_processing_failed"];
292
+ };
278
293
  };
279
294
  };
280
295
  readonly turnMetadata: {
@@ -88,15 +88,26 @@ 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 },
97
103
  width: { type: 'number', minimum: 0 },
98
104
  height: { type: 'number', minimum: 0 },
99
105
  durationMs: { type: 'number', minimum: 0 },
106
+ thumbnailUrl: { type: 'string', minLength: 1 },
107
+ processingStatus: { enum: ['processing', 'ready', 'failed'] },
108
+ processingErrorCode: {
109
+ enum: ['invalid_video', 'video_limits_exceeded', 'video_processing_failed'],
110
+ },
100
111
  },
101
112
  };
102
113
  const turnMetadataDef = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/backend-contracts",
3
- "version": "5.4.1",
3
+ "version": "6.1.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
- }