@opengeni/contracts 0.50.0 → 2.2.0-canary.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.
Files changed (113) hide show
  1. package/dist/agent-authored-durable-text.d.ts +64 -0
  2. package/dist/atlassian.d.ts +1 -0
  3. package/dist/atlassian.js +14 -13
  4. package/dist/{chunk-Y3MLSLB4.js → chunk-3SIZGL3O.js} +42 -30
  5. package/dist/chunk-3SIZGL3O.js.map +1 -0
  6. package/dist/{chunk-JBCIY7QD.js → chunk-7RMTKFJW.js} +45 -22
  7. package/dist/chunk-7RMTKFJW.js.map +1 -0
  8. package/dist/{chunk-Q6A7XT2N.js → chunk-7ZZ3R27P.js} +9598 -6021
  9. package/dist/chunk-7ZZ3R27P.js.map +1 -0
  10. package/dist/{chunk-5UDNP7AO.js → chunk-MRWYRAS5.js} +56 -8
  11. package/dist/chunk-MRWYRAS5.js.map +1 -0
  12. package/dist/{chunk-LW7OH6CS.js → chunk-N6GRVXAJ.js} +6 -5
  13. package/dist/chunk-N6GRVXAJ.js.map +1 -0
  14. package/dist/{chunk-TZ6YEYN4.js → chunk-NVJMJWLL.js} +3 -1
  15. package/dist/chunk-NVJMJWLL.js.map +1 -0
  16. package/dist/chunk-PX3VCKAU.js +29 -0
  17. package/dist/chunk-PX3VCKAU.js.map +1 -0
  18. package/dist/{chunk-LSB7GRLG.js → chunk-TXTQIYCV.js} +15 -11
  19. package/dist/chunk-TXTQIYCV.js.map +1 -0
  20. package/dist/{chunk-CADSX6UN.js → chunk-Y3KNXVTD.js} +2 -2
  21. package/dist/chunk-YBW5JSA3.js +251 -0
  22. package/dist/chunk-YBW5JSA3.js.map +1 -0
  23. package/dist/company-brain-governed-writes.d.ts +475 -0
  24. package/dist/company-brain.d.ts +398 -0
  25. package/dist/company-profile.d.ts +268 -0
  26. package/dist/connection-authority.d.ts +501 -0
  27. package/dist/connection-authority.js +364 -0
  28. package/dist/connection-authority.js.map +1 -0
  29. package/dist/connector-attachments.d.ts +95 -0
  30. package/dist/connector-attachments.js +51 -0
  31. package/dist/connector-attachments.js.map +1 -0
  32. package/dist/editable-artifact-codec-registry.d.ts +16 -7
  33. package/dist/editable-artifact-codec-registry.js +18 -8
  34. package/dist/editable-artifact-committed-transaction.d.ts +2 -2
  35. package/dist/editable-artifact-committed-transaction.js +2 -2
  36. package/dist/editable-artifact-live.d.ts +11 -12
  37. package/dist/editable-artifact-live.js +111 -46
  38. package/dist/editable-artifact-live.js.map +1 -1
  39. package/dist/editable-artifact-serialized-commit.d.ts +2 -1
  40. package/dist/editable-artifact-serialized-commit.js +9 -8
  41. package/dist/editable-artifact-versions.d.ts +20 -5
  42. package/dist/editable-artifact-versions.js +23 -5
  43. package/dist/editable-artifacts.d.ts +2 -0
  44. package/dist/editable-artifacts.js +51 -39
  45. package/dist/google-drive.d.ts +89 -1
  46. package/dist/google-drive.js +78 -17
  47. package/dist/google-drive.js.map +1 -1
  48. package/dist/governed-learning-activation.d.ts +154 -0
  49. package/dist/governed-learning-evaluator.d.ts +152 -0
  50. package/dist/index.d.ts +12395 -1426
  51. package/dist/index.js +837 -115
  52. package/dist/interaction.d.ts +68 -0
  53. package/dist/knowledge.d.ts +192 -0
  54. package/dist/organization-membership-lifecycle.d.ts +516 -0
  55. package/dist/permissions.d.ts +52 -0
  56. package/dist/personal-github.d.ts +319 -0
  57. package/dist/personal-github.js +209 -0
  58. package/dist/personal-github.js.map +1 -0
  59. package/dist/preference-registry.d.ts +17 -0
  60. package/dist/remember.d.ts +331 -0
  61. package/dist/sandbox-file-artifacts.d.ts +56 -0
  62. package/dist/sandbox-snapshots.d.ts +21 -0
  63. package/dist/slack-bot-scopes.d.ts +34 -14
  64. package/dist/slack-bot-scopes.js +5 -1
  65. package/dist/spreadsheet-artifact-commands.d.ts +2 -3
  66. package/dist/spreadsheet-artifact-commands.js +4 -2
  67. package/dist/spreadsheet-artifact-query.js +3 -2
  68. package/dist/task-notes.d.ts +42 -1
  69. package/dist/tool-result-spill.d.ts +16 -0
  70. package/dist/workspace-instruction-policies.d.ts +81 -0
  71. package/dist/workspace-learning-administration.d.ts +289 -0
  72. package/package.json +13 -1
  73. package/src/agent-authored-durable-text.ts +105 -0
  74. package/src/company-brain-governed-writes.ts +356 -0
  75. package/src/company-brain.ts +201 -0
  76. package/src/company-profile.ts +122 -1
  77. package/src/connection-authority.ts +413 -0
  78. package/src/connector-attachments.ts +327 -0
  79. package/src/editable-artifact-codec-registry.ts +55 -17
  80. package/src/editable-artifact-committed-transaction.ts +44 -31
  81. package/src/editable-artifact-live.ts +123 -57
  82. package/src/editable-artifact-serialized-commit.ts +2 -1
  83. package/src/editable-artifact-versions.ts +26 -5
  84. package/src/editable-artifacts.ts +2 -0
  85. package/src/google-drive.ts +76 -4
  86. package/src/governed-learning-activation.ts +191 -0
  87. package/src/governed-learning-evaluator.ts +176 -0
  88. package/src/index.ts +3182 -483
  89. package/src/interaction.ts +55 -1
  90. package/src/knowledge.ts +133 -6
  91. package/src/organization-membership-lifecycle.ts +330 -0
  92. package/src/permissions.ts +55 -0
  93. package/src/personal-github.ts +267 -0
  94. package/src/preference-registry.ts +17 -0
  95. package/src/remember.ts +242 -0
  96. package/src/sandbox-file-artifacts.ts +65 -0
  97. package/src/sandbox-snapshots.ts +78 -0
  98. package/src/slack-bot-scopes.ts +81 -9
  99. package/src/spreadsheet-artifact-commands.ts +12 -13
  100. package/src/task-notes.ts +14 -1
  101. package/src/tool-result-spill.ts +35 -0
  102. package/src/workspace-instruction-policies.ts +30 -0
  103. package/src/workspace-learning-administration.ts +88 -0
  104. package/dist/chunk-5UDNP7AO.js.map +0 -1
  105. package/dist/chunk-BVGIYW7D.js +0 -11
  106. package/dist/chunk-BVGIYW7D.js.map +0 -1
  107. package/dist/chunk-JBCIY7QD.js.map +0 -1
  108. package/dist/chunk-LSB7GRLG.js.map +0 -1
  109. package/dist/chunk-LW7OH6CS.js.map +0 -1
  110. package/dist/chunk-Q6A7XT2N.js.map +0 -1
  111. package/dist/chunk-TZ6YEYN4.js.map +0 -1
  112. package/dist/chunk-Y3MLSLB4.js.map +0 -1
  113. /package/dist/{chunk-CADSX6UN.js.map → chunk-Y3KNXVTD.js.map} +0 -0
@@ -0,0 +1,327 @@
1
+ import { z } from "zod";
2
+
3
+ export const CONNECTOR_ATTACHMENT_TRANSFER_VERSION = 1 as const;
4
+ export const CONNECTOR_ATTACHMENT_RECEIPT_VERSION = 1 as const;
5
+ export const CONNECTOR_ATTACHMENT_TRANSFER_META_KEY =
6
+ "opengeni/connector-attachment-transfers" as const;
7
+ export const CONNECTOR_ATTACHMENT_RECEIPT_META_KEY =
8
+ "opengeni/connector-attachment-receipts" as const;
9
+ export const CONNECTOR_ATTACHMENT_MAX_COUNT = 16;
10
+ // Must remain byte-for-byte aligned with Channel A's exact workspace importer.
11
+ export const CONNECTOR_ATTACHMENT_MAX_BYTES = 5_000_000_000;
12
+ export const CONNECTOR_ATTACHMENT_FILENAME_MAX_UTF8_BYTES = 255;
13
+ export const CONNECTOR_ATTACHMENT_MEDIA_TYPE_MAX_UTF8_BYTES = 256;
14
+ export const CONNECTOR_ATTACHMENT_PROVIDER_MAX_UTF8_BYTES = 128;
15
+ export const CONNECTOR_ATTACHMENT_PROVIDER_ID_MAX_UTF8_BYTES = 512;
16
+ export const CONNECTOR_ATTACHMENT_SOURCE_URL_MAX_UTF8_BYTES = 8 * 1024;
17
+ export const CONNECTOR_ATTACHMENT_SANDBOX_PATH_MAX_UTF8_BYTES = 2 * 1024;
18
+
19
+ const PROVIDER_PATTERN = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/u;
20
+ const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
21
+ const MEDIA_TYPE_PATTERN =
22
+ /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+(?:\s*;\s*[a-z0-9!#$&^_.+-]+=(?:[a-z0-9!#$&^_.+-]+|"[^"\r\n]*"))*$/iu;
23
+ const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u;
24
+ const HTTP_URL_REFERENCE_PATTERN = /(?:https?:)?\/\/[^\s]/iu;
25
+ const PORTABLE_FILENAME_FORBIDDEN_PATTERN = /[<>:"|?*]/u;
26
+ const PORTABLE_FILENAME_RESERVED_PATTERN = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu;
27
+ const textEncoder = new TextEncoder();
28
+
29
+ function utf8Bytes(value: string): number {
30
+ return textEncoder.encode(value).byteLength;
31
+ }
32
+
33
+ function boundedString(maxUtf8Bytes: number) {
34
+ return z.string().refine((value) => utf8Bytes(value) <= maxUtf8Bytes, {
35
+ message: `must be at most ${maxUtf8Bytes} UTF-8 bytes`,
36
+ });
37
+ }
38
+
39
+ function isHttpUrl(value: string): boolean {
40
+ try {
41
+ const parsed = new URL(value);
42
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ function containsHttpUrlReference(value: string): boolean {
49
+ return HTTP_URL_REFERENCE_PATTERN.test(value);
50
+ }
51
+
52
+ function isPrivateUrlCredentialQueryName(value: string): boolean {
53
+ const normalized = value.toLowerCase().replace(/[^a-z0-9]/gu, "");
54
+ return (
55
+ normalized === "sig" ||
56
+ normalized === "auth" ||
57
+ normalized.endsWith("signature") ||
58
+ normalized.endsWith("token") ||
59
+ normalized.endsWith("credential") ||
60
+ normalized.endsWith("secret") ||
61
+ normalized.endsWith("password") ||
62
+ normalized.endsWith("passcode") ||
63
+ normalized.endsWith("authorization") ||
64
+ normalized.endsWith("apikey") ||
65
+ normalized.endsWith("accesskey") ||
66
+ normalized.endsWith("securitytoken") ||
67
+ normalized.endsWith("sas") ||
68
+ normalized === "googleaccessid"
69
+ );
70
+ }
71
+
72
+ function privateUrlCredentialValues(value: string): string[] {
73
+ let parsed: URL;
74
+ try {
75
+ parsed = new URL(value);
76
+ } catch {
77
+ return [];
78
+ }
79
+ const credentials = new Set<string>();
80
+ for (const [name, credential] of parsed.searchParams) {
81
+ if (credential && isPrivateUrlCredentialQueryName(name)) credentials.add(credential);
82
+ }
83
+ for (const pair of parsed.search.slice(1).split("&")) {
84
+ if (!pair) continue;
85
+ const separator = pair.indexOf("=");
86
+ const rawName = separator === -1 ? pair : pair.slice(0, separator);
87
+ const rawCredential = separator === -1 ? "" : pair.slice(separator + 1);
88
+ let decodedName: string;
89
+ try {
90
+ decodedName = decodeURIComponent(rawName.replace(/\+/gu, " "));
91
+ } catch {
92
+ continue;
93
+ }
94
+ if (rawCredential && isPrivateUrlCredentialQueryName(decodedName)) {
95
+ credentials.add(rawCredential);
96
+ }
97
+ }
98
+ return [...credentials];
99
+ }
100
+
101
+ function publicMetadataContainsCredential(publicValue: string, credential: string): boolean {
102
+ return publicValue.includes(credential);
103
+ }
104
+
105
+ export const ConnectorAttachmentProviderIdentity = z
106
+ .object({
107
+ provider: boundedString(CONNECTOR_ATTACHMENT_PROVIDER_MAX_UTF8_BYTES).regex(PROVIDER_PATTERN),
108
+ kind: z.literal("attachment"),
109
+ value: boundedString(CONNECTOR_ATTACHMENT_PROVIDER_ID_MAX_UTF8_BYTES)
110
+ .min(1)
111
+ .refine((value) => !CONTROL_CHARACTER_PATTERN.test(value), {
112
+ message: "must not contain control characters",
113
+ })
114
+ .refine((value) => !isHttpUrl(value) && !containsHttpUrlReference(value), {
115
+ message: "must be an opaque provider attachment identity, not a URL",
116
+ }),
117
+ })
118
+ .strict();
119
+ export type ConnectorAttachmentProviderIdentity = z.infer<
120
+ typeof ConnectorAttachmentProviderIdentity
121
+ >;
122
+
123
+ export const ConnectorAttachmentFileName = boundedString(
124
+ CONNECTOR_ATTACHMENT_FILENAME_MAX_UTF8_BYTES,
125
+ )
126
+ .min(1)
127
+ .refine(
128
+ (value) =>
129
+ value !== "." &&
130
+ value !== ".." &&
131
+ value === value.trim() &&
132
+ !value.includes("/") &&
133
+ !value.includes("\\") &&
134
+ !CONTROL_CHARACTER_PATTERN.test(value) &&
135
+ !PORTABLE_FILENAME_FORBIDDEN_PATTERN.test(value) &&
136
+ !/[ .]$/u.test(value) &&
137
+ !PORTABLE_FILENAME_RESERVED_PATTERN.test(value),
138
+ { message: "must be a safe leaf filename" },
139
+ );
140
+
141
+ export const ConnectorAttachmentMediaType = boundedString(
142
+ CONNECTOR_ATTACHMENT_MEDIA_TYPE_MAX_UTF8_BYTES,
143
+ )
144
+ .regex(MEDIA_TYPE_PATTERN)
145
+ .refine((value) => !containsHttpUrlReference(value), {
146
+ message: "must not contain an HTTP URL reference",
147
+ });
148
+
149
+ export const ConnectorAttachmentSha256 = z.string().regex(SHA256_PATTERN);
150
+
151
+ export const ConnectorAttachmentPrivateSource = z
152
+ .object({
153
+ url: boundedString(CONNECTOR_ATTACHMENT_SOURCE_URL_MAX_UTF8_BYTES).superRefine(
154
+ (value, context) => {
155
+ let parsed: URL;
156
+ try {
157
+ parsed = new URL(value);
158
+ } catch {
159
+ context.addIssue({
160
+ code: "custom",
161
+ message: "must be an absolute URL",
162
+ });
163
+ return;
164
+ }
165
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
166
+ context.addIssue({ code: "custom", message: "must use http or https" });
167
+ }
168
+ if (parsed.username || parsed.password) {
169
+ context.addIssue({
170
+ code: "custom",
171
+ message: "must not contain URL userinfo",
172
+ });
173
+ }
174
+ if (parsed.hash) {
175
+ context.addIssue({
176
+ code: "custom",
177
+ message: "must not contain a URL fragment",
178
+ });
179
+ }
180
+ },
181
+ ),
182
+ expiresAt: z.iso.datetime({ offset: true }),
183
+ })
184
+ .strict();
185
+ export type ConnectorAttachmentPrivateSource = z.infer<typeof ConnectorAttachmentPrivateSource>;
186
+
187
+ export const ConnectorAttachmentTransfer = z
188
+ .object({
189
+ providerAttachmentId: ConnectorAttachmentProviderIdentity,
190
+ fileName: ConnectorAttachmentFileName,
191
+ mediaType: ConnectorAttachmentMediaType,
192
+ byteSize: z.number().int().min(0).max(CONNECTOR_ATTACHMENT_MAX_BYTES),
193
+ contentSha256: ConnectorAttachmentSha256,
194
+ source: ConnectorAttachmentPrivateSource,
195
+ })
196
+ .strict()
197
+ .superRefine((attachment, context) => {
198
+ const publicValues = [
199
+ attachment.providerAttachmentId.value,
200
+ attachment.fileName,
201
+ attachment.mediaType,
202
+ ];
203
+ for (const credential of privateUrlCredentialValues(attachment.source.url)) {
204
+ if (publicValues.some((value) => publicMetadataContainsCredential(value, credential))) {
205
+ context.addIssue({
206
+ code: "custom",
207
+ message: "private source credentials must not appear in public attachment metadata",
208
+ });
209
+ return;
210
+ }
211
+ }
212
+ });
213
+ export type ConnectorAttachmentTransfer = z.infer<typeof ConnectorAttachmentTransfer>;
214
+
215
+ export const ConnectorAttachmentTransferEnvelope = z
216
+ .object({
217
+ version: z.literal(CONNECTOR_ATTACHMENT_TRANSFER_VERSION),
218
+ attachments: z.array(ConnectorAttachmentTransfer).min(1).max(CONNECTOR_ATTACHMENT_MAX_COUNT),
219
+ })
220
+ .strict()
221
+ .superRefine((envelope, context) => {
222
+ const identities = new Set<string>();
223
+ for (const [index, attachment] of envelope.attachments.entries()) {
224
+ const identity = `${attachment.providerAttachmentId.provider}\0${attachment.providerAttachmentId.value}`;
225
+ if (identities.has(identity)) {
226
+ context.addIssue({
227
+ code: "custom",
228
+ message: "provider attachment identities must be unique",
229
+ path: ["attachments", index, "providerAttachmentId"],
230
+ });
231
+ }
232
+ identities.add(identity);
233
+ }
234
+ });
235
+ export type ConnectorAttachmentTransferEnvelope = z.infer<
236
+ typeof ConnectorAttachmentTransferEnvelope
237
+ >;
238
+
239
+ export const ConnectorAttachmentSandboxPath = boundedString(
240
+ CONNECTOR_ATTACHMENT_SANDBOX_PATH_MAX_UTF8_BYTES,
241
+ )
242
+ .min(1)
243
+ .refine(
244
+ (value) => {
245
+ if (
246
+ value !== value.trim() ||
247
+ value.startsWith("/") ||
248
+ /^[A-Za-z]:[\\/]/u.test(value) ||
249
+ value.includes("\\") ||
250
+ CONTROL_CHARACTER_PATTERN.test(value)
251
+ ) {
252
+ return false;
253
+ }
254
+ const segments = value.split("/");
255
+ if (
256
+ segments.length !== 5 ||
257
+ segments[0] !== ".opengeni" ||
258
+ segments[1] !== "connector-attachments" ||
259
+ !PROVIDER_PATTERN.test(segments[2] ?? "") ||
260
+ !/^[0-9a-f]{32}$/u.test(segments[3] ?? "") ||
261
+ !ConnectorAttachmentFileName.safeParse(segments[4]).success
262
+ ) {
263
+ return false;
264
+ }
265
+ return segments.every(
266
+ (segment) =>
267
+ segment.length > 0 &&
268
+ segment !== "." &&
269
+ segment !== ".." &&
270
+ !PORTABLE_FILENAME_FORBIDDEN_PATTERN.test(segment) &&
271
+ !/[ .]$/u.test(segment) &&
272
+ !PORTABLE_FILENAME_RESERVED_PATTERN.test(segment),
273
+ );
274
+ },
275
+ { message: "must be a confined connector attachment sandbox path" },
276
+ );
277
+
278
+ export const ConnectorAttachmentReceipt = z
279
+ .object({
280
+ providerAttachmentId: ConnectorAttachmentProviderIdentity,
281
+ fileName: ConnectorAttachmentFileName,
282
+ mediaType: ConnectorAttachmentMediaType,
283
+ byteSize: z.number().int().min(0).max(CONNECTOR_ATTACHMENT_MAX_BYTES),
284
+ contentSha256: ConnectorAttachmentSha256,
285
+ sandboxPath: ConnectorAttachmentSandboxPath,
286
+ })
287
+ .strict()
288
+ .superRefine((receipt, context) => {
289
+ const segments = receipt.sandboxPath.split("/");
290
+ if (segments[2] !== receipt.providerAttachmentId.provider) {
291
+ context.addIssue({
292
+ code: "custom",
293
+ message: "sandbox path provider must match the provider attachment identity",
294
+ path: ["sandboxPath"],
295
+ });
296
+ }
297
+ if (segments[4] !== receipt.fileName) {
298
+ context.addIssue({
299
+ code: "custom",
300
+ message: "sandbox path filename must match the attachment filename",
301
+ path: ["sandboxPath"],
302
+ });
303
+ }
304
+ });
305
+ export type ConnectorAttachmentReceipt = z.infer<typeof ConnectorAttachmentReceipt>;
306
+
307
+ export const ConnectorAttachmentReceiptEnvelope = z
308
+ .object({
309
+ version: z.literal(CONNECTOR_ATTACHMENT_RECEIPT_VERSION),
310
+ attachments: z.array(ConnectorAttachmentReceipt).min(1).max(CONNECTOR_ATTACHMENT_MAX_COUNT),
311
+ })
312
+ .strict();
313
+ export type ConnectorAttachmentReceiptEnvelope = z.infer<typeof ConnectorAttachmentReceiptEnvelope>;
314
+
315
+ export function parseConnectorAttachmentTransferEnvelope(
316
+ value: unknown,
317
+ options: { nowMs?: number } = {},
318
+ ): ConnectorAttachmentTransferEnvelope {
319
+ const envelope = ConnectorAttachmentTransferEnvelope.parse(value);
320
+ const nowMs = options.nowMs ?? Date.now();
321
+ for (const attachment of envelope.attachments) {
322
+ if (Date.parse(attachment.source.expiresAt) <= nowMs) {
323
+ throw new Error("connector attachment source is expired");
324
+ }
325
+ }
326
+ return envelope;
327
+ }
@@ -54,13 +54,24 @@ import {
54
54
  encodeSpreadsheetArtifactKernelQuery,
55
55
  } from "./spreadsheet-artifact-query";
56
56
  import {
57
- EDITABLE_ARTIFACT_MODEL_SCHEMA_VERSION,
58
- EDITABLE_ARTIFACT_SNAPSHOT_VERSION,
57
+ DOCUMENT_ARTIFACT_MODEL_SCHEMA_VERSION,
58
+ DOCUMENT_ARTIFACT_SNAPSHOT_VERSION,
59
+ EDITABLE_ARTIFACT_LIVE_WIRE_VERSION,
60
+ EDITABLE_ARTIFACT_SERIALIZED_COMMIT_VERSION,
61
+ PRESENTATION_ARTIFACT_MODEL_SCHEMA_VERSION,
62
+ PRESENTATION_ARTIFACT_SNAPSHOT_VERSION,
63
+ COMMITTED_TRANSACTION_PROTOCOL_VERSION,
64
+ SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION,
65
+ SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION,
59
66
  } from "./editable-artifact-versions";
60
67
 
61
68
  export {
62
- EDITABLE_ARTIFACT_MODEL_SCHEMA_VERSION,
63
- EDITABLE_ARTIFACT_SNAPSHOT_VERSION,
69
+ DOCUMENT_ARTIFACT_MODEL_SCHEMA_VERSION,
70
+ DOCUMENT_ARTIFACT_SNAPSHOT_VERSION,
71
+ PRESENTATION_ARTIFACT_MODEL_SCHEMA_VERSION,
72
+ PRESENTATION_ARTIFACT_SNAPSHOT_VERSION,
73
+ SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION,
74
+ SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION,
64
75
  } from "./editable-artifact-versions";
65
76
 
66
77
  export type EditableArtifactModality = "spreadsheet" | "document" | "presentation";
@@ -68,13 +79,22 @@ export type EditableArtifactConcurrencySemantics =
68
79
  | "causal-crdt-v1"
69
80
  | "authoritative-serialized-stale-base-v1";
70
81
 
82
+ export type CurrentEditableArtifactCompatibility = Readonly<{
83
+ modality: EditableArtifactModality;
84
+ liveProtocolVersion: number;
85
+ modelSchemaVersion: number;
86
+ snapshotVersion: number;
87
+ commandProtocolVersion: number;
88
+ committedTransactionProtocolVersion: number;
89
+ }>;
90
+
71
91
  export type EditableArtifactCodecDescriptor = Readonly<{
72
92
  modality: EditableArtifactModality;
73
- modelSchemaVersion: typeof EDITABLE_ARTIFACT_MODEL_SCHEMA_VERSION;
74
- snapshotVersion: typeof EDITABLE_ARTIFACT_SNAPSHOT_VERSION;
93
+ modelSchemaVersion: 1 | 2;
94
+ snapshotVersion: 1 | 2;
75
95
  command: Readonly<{
76
- magic: "OGASC001" | "OGADC001" | "OGAPC001";
77
- version: 1;
96
+ magic: "OGASC002" | "OGADC001" | "OGAPC001";
97
+ version: 1 | 2;
78
98
  maximumBytes: number;
79
99
  maximumCommands: number;
80
100
  encode: (value: unknown) => Uint8Array;
@@ -97,17 +117,17 @@ export type EditableArtifactCodecDescriptor = Readonly<{
97
117
  }>;
98
118
  concurrency: Readonly<{
99
119
  semantics: EditableArtifactConcurrencySemantics;
100
- collaborationEnvelope: "OGACO001" | null;
120
+ collaborationEnvelope: "OGACO002" | null;
101
121
  staleBaseMustBeRejected: boolean;
102
122
  }>;
103
123
  }>;
104
124
 
105
125
  const spreadsheet = Object.freeze({
106
126
  modality: "spreadsheet",
107
- modelSchemaVersion: 1,
108
- snapshotVersion: 1,
127
+ modelSchemaVersion: SPREADSHEET_ARTIFACT_MODEL_SCHEMA_VERSION,
128
+ snapshotVersion: SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION,
109
129
  command: Object.freeze({
110
- magic: "OGASC001",
130
+ magic: "OGASC002",
111
131
  version: SPREADSHEET_ARTIFACT_COMMAND_VERSION,
112
132
  maximumBytes: SPREADSHEET_ARTIFACT_COMMAND_MAX_BYTES,
113
133
  maximumCommands: SPREADSHEET_ARTIFACT_COMMAND_MAX_COMMANDS,
@@ -140,15 +160,15 @@ const spreadsheet = Object.freeze({
140
160
  }),
141
161
  concurrency: Object.freeze({
142
162
  semantics: "causal-crdt-v1",
143
- collaborationEnvelope: "OGACO001",
163
+ collaborationEnvelope: "OGACO002",
144
164
  staleBaseMustBeRejected: false,
145
165
  }),
146
166
  }) satisfies EditableArtifactCodecDescriptor;
147
167
 
148
168
  const document = Object.freeze({
149
169
  modality: "document",
150
- modelSchemaVersion: 1,
151
- snapshotVersion: 1,
170
+ modelSchemaVersion: DOCUMENT_ARTIFACT_MODEL_SCHEMA_VERSION,
171
+ snapshotVersion: DOCUMENT_ARTIFACT_SNAPSHOT_VERSION,
152
172
  command: Object.freeze({
153
173
  magic: "OGADC001",
154
174
  version: DOCUMENT_ARTIFACT_COMMAND_VERSION,
@@ -188,8 +208,8 @@ const document = Object.freeze({
188
208
 
189
209
  const presentation = Object.freeze({
190
210
  modality: "presentation",
191
- modelSchemaVersion: 1,
192
- snapshotVersion: 1,
211
+ modelSchemaVersion: PRESENTATION_ARTIFACT_MODEL_SCHEMA_VERSION,
212
+ snapshotVersion: PRESENTATION_ARTIFACT_SNAPSHOT_VERSION,
193
213
  command: Object.freeze({
194
214
  magic: "OGAPC001",
195
215
  version: PRESENTATION_ARTIFACT_COMMAND_VERSION,
@@ -233,6 +253,24 @@ export const EDITABLE_ARTIFACT_CODEC_REGISTRY: Readonly<
233
253
  Record<EditableArtifactModality, EditableArtifactCodecDescriptor>
234
254
  > = Object.freeze({ spreadsheet, document, presentation });
235
255
 
256
+ /** One exact current compatibility tuple per durable modality. */
257
+ export function currentEditableArtifactCompatibility(
258
+ modality: EditableArtifactModality,
259
+ ): CurrentEditableArtifactCompatibility {
260
+ const descriptor = EDITABLE_ARTIFACT_CODEC_REGISTRY[modality];
261
+ return Object.freeze({
262
+ modality,
263
+ liveProtocolVersion: EDITABLE_ARTIFACT_LIVE_WIRE_VERSION,
264
+ modelSchemaVersion: descriptor.modelSchemaVersion,
265
+ snapshotVersion: descriptor.snapshotVersion,
266
+ commandProtocolVersion: descriptor.command.version,
267
+ committedTransactionProtocolVersion:
268
+ modality === "spreadsheet"
269
+ ? COMMITTED_TRANSACTION_PROTOCOL_VERSION
270
+ : EDITABLE_ARTIFACT_SERIALIZED_COMMIT_VERSION,
271
+ });
272
+ }
273
+
236
274
  /**
237
275
  * Selects solely from the durable artifact modality plus persisted versions.
238
276
  * Modality intentionally never enters OGATX001: the durable artifact row is
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Strict, allocation-bounded inspection of canonical OGACO001 transactions.
2
+ * Strict, allocation-bounded inspection of canonical OGACO002 transactions.
3
3
  *
4
4
  * The Rust kernel remains the authority for applying operations. This decoder
5
5
  * validates the complete envelope and every typed operation so metadata after
@@ -15,7 +15,7 @@ export const MAX_COMMITTED_TRANSACTION_OPERATIONS = 4_096;
15
15
  export const MAX_COMMITTED_TRANSACTION_CAUSAL_REPLICAS = 100_000;
16
16
  export const MAX_COMMITTED_TRANSACTION_CELLS = 1_000_000;
17
17
 
18
- const MAGIC = new Uint8Array([0x4f, 0x47, 0x41, 0x43, 0x4f, 0x30, 0x30, 0x31]); // OGACO001
18
+ const MAGIC = new Uint8Array([0x4f, 0x47, 0x41, 0x43, 0x4f, 0x30, 0x30, 0x32]); // OGACO002
19
19
  const HEADER_BYTES = 24;
20
20
  const CHECKSUM_BYTES = 8;
21
21
  const HASH_BYTES = 32;
@@ -24,6 +24,7 @@ const MAX_OPERATION_STRING_BYTES = 4 * 1024 * 1024;
24
24
  const MAX_SAFE_U64_HIGH = 0x1f_ffff;
25
25
  const U32_SIZE = 0x1_0000_0000;
26
26
  const U32_MAX = 0xffff_ffff;
27
+ const MAX_DATE_MILLISECONDS = 8_640_000_000_000_000n;
27
28
  const FNV_OFFSET_LOW = 0x8422_2325;
28
29
  const FNV_OFFSET_HIGH = 0xcbf2_9ce4;
29
30
  const FNV_PRIME_LOW = 0x01b3;
@@ -47,7 +48,7 @@ export type CommittedTransactionSummary = Readonly<{
47
48
  }>;
48
49
 
49
50
  /**
50
- * Decodes only the metadata of one exact canonical OGACO001 byte envelope.
51
+ * Decodes only the metadata of one exact canonical OGACO002 byte envelope.
51
52
  *
52
53
  * This function is synchronous and never mutates or retains `bytes`. The
53
54
  * caller remains the owner of the exact canonical envelope.
@@ -55,38 +56,38 @@ export type CommittedTransactionSummary = Readonly<{
55
56
  export function decodeCommittedTransactionSummary(bytes: Uint8Array): CommittedTransactionSummary {
56
57
  assertInput(bytes);
57
58
  if (bytes.byteLength > MAX_COMMITTED_TRANSACTION_BYTES) {
58
- throw new RangeError("OGACO001 envelope exceeds its byte limit");
59
+ throw new RangeError("OGACO002 envelope exceeds its byte limit");
59
60
  }
60
61
  if (bytes.byteLength < HEADER_BYTES + CHECKSUM_BYTES) {
61
- throw new TypeError("truncated OGACO001 envelope");
62
+ throw new TypeError("truncated OGACO002 envelope");
62
63
  }
63
64
 
64
65
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
65
66
  for (let index = 0; index < MAGIC.byteLength; index += 1) {
66
- if (bytes[index] !== MAGIC[index]) throw new TypeError("invalid OGACO001 magic");
67
+ if (bytes[index] !== MAGIC[index]) throw new TypeError("invalid OGACO002 magic");
67
68
  }
68
69
 
69
70
  const version = view.getUint16(8, true);
70
71
  if (version !== COMMITTED_TRANSACTION_PROTOCOL_VERSION) {
71
- throw new TypeError(`unsupported OGACO001 version: ${version}`);
72
+ throw new TypeError(`unsupported OGACO002 version: ${version}`);
72
73
  }
73
74
  if (view.getUint16(10, true) !== 0) {
74
- throw new TypeError("reserved OGACO001 header bits are set");
75
+ throw new TypeError("reserved OGACO002 header bits are set");
75
76
  }
76
77
 
77
78
  const operationCount = view.getUint32(12, true);
78
79
  if (operationCount === 0 || operationCount > MAX_COMMITTED_TRANSACTION_OPERATIONS) {
79
- throw new RangeError("OGACO001 operation count is outside its canonical bounds");
80
+ throw new RangeError("OGACO002 operation count is outside its canonical bounds");
80
81
  }
81
82
 
82
- const payloadLength = safeU64At(view, 16, "OGACO001 payload length", false);
83
- const payloadEnd = checkedAdd(HEADER_BYTES, payloadLength, "OGACO001 payload length");
84
- const expectedLength = checkedAdd(payloadEnd, CHECKSUM_BYTES, "OGACO001 envelope length");
83
+ const payloadLength = safeU64At(view, 16, "OGACO002 payload length", false);
84
+ const payloadEnd = checkedAdd(HEADER_BYTES, payloadLength, "OGACO002 payload length");
85
+ const expectedLength = checkedAdd(payloadEnd, CHECKSUM_BYTES, "OGACO002 envelope length");
85
86
  if (bytes.byteLength !== expectedLength) {
86
87
  throw new TypeError(
87
88
  bytes.byteLength < expectedLength
88
- ? "truncated OGACO001 payload"
89
- : "trailing bytes after OGACO001 envelope",
89
+ ? "truncated OGACO002 payload"
90
+ : "trailing bytes after OGACO002 envelope",
90
91
  );
91
92
  }
92
93
 
@@ -95,7 +96,7 @@ export function decodeCommittedTransactionSummary(bytes: Uint8Array): CommittedT
95
96
  view.getUint32(payloadEnd, true) !== checksumLow ||
96
97
  view.getUint32(payloadEnd + 4, true) !== checksumHigh
97
98
  ) {
98
- throw new TypeError("OGACO001 checksum mismatch");
99
+ throw new TypeError("OGACO002 checksum mismatch");
99
100
  }
100
101
 
101
102
  const reader = new Reader(bytes, HEADER_BYTES, payloadEnd);
@@ -113,7 +114,7 @@ export function decodeCommittedTransactionSummary(bytes: Uint8Array): CommittedT
113
114
  for (let index = 0; index < operationCount; index += 1) {
114
115
  const operationId = reader.stableId(`operation ${index} id`);
115
116
  if (seenOperationIds.has(operationId)) {
116
- throw new TypeError("OGACO001 operation ids must be unique");
117
+ throw new TypeError("OGACO002 operation ids must be unique");
117
118
  }
118
119
  seenOperationIds.add(operationId);
119
120
  operationIds.push(operationId);
@@ -273,7 +274,7 @@ class Reader {
273
274
  const endRow = this.u32("clear range end row");
274
275
  const endColumn = this.u32("clear range end column");
275
276
  if (startRow > endRow || startColumn > endColumn) {
276
- throw new TypeError("OGACO001 clear range is not normalized");
277
+ throw new TypeError("OGACO002 clear range is not normalized");
277
278
  }
278
279
  return totalCells;
279
280
  }
@@ -281,7 +282,7 @@ class Reader {
281
282
  this.stableId("selective undo target");
282
283
  return totalCells;
283
284
  default:
284
- throw new TypeError(`invalid OGACO001 collaboration command tag: ${tag}`);
285
+ throw new TypeError(`invalid OGACO002 collaboration command tag: ${tag}`);
285
286
  }
286
287
  }
287
288
 
@@ -292,18 +293,18 @@ class Reader {
292
293
  const rows = this.u32("cell block rows");
293
294
  const columns = this.u32("cell block columns");
294
295
  if (rows === 0 || columns === 0) {
295
- throw new TypeError("OGACO001 cell block dimensions must be nonzero");
296
+ throw new TypeError("OGACO002 cell block dimensions must be nonzero");
296
297
  }
297
298
  if (anchorRow > U32_MAX - (rows - 1) || anchorColumn > U32_MAX - (columns - 1)) {
298
- throw new RangeError("OGACO001 cell block coordinates overflow u32");
299
+ throw new RangeError("OGACO002 cell block coordinates overflow u32");
299
300
  }
300
301
  if (rows > Math.floor(MAX_COMMITTED_TRANSACTION_CELLS / columns)) {
301
- throw new RangeError("OGACO001 cell block exceeds its cell limit");
302
+ throw new RangeError("OGACO002 cell block exceeds its cell limit");
302
303
  }
303
304
  const cellCount = rows * columns;
304
305
  const nextTotal = totalCells + cellCount;
305
306
  if (nextTotal > MAX_COMMITTED_TRANSACTION_CELLS) {
306
- throw new RangeError("OGACO001 transaction exceeds its cell limit");
307
+ throw new RangeError("OGACO002 transaction exceeds its cell limit");
307
308
  }
308
309
  for (let index = 0; index < cellCount; index += 1) this.skipCell();
309
310
  return nextTotal;
@@ -313,10 +314,11 @@ class Reader {
313
314
  const formulaTag = this.u8("cell formula tag");
314
315
  if (formulaTag === 1) {
315
316
  if (this.skipString("cell formula") === 0) {
316
- throw new TypeError("OGACO001 formula must not be empty");
317
+ throw new TypeError("OGACO002 formula must not be empty");
317
318
  }
319
+ return;
318
320
  } else if (formulaTag !== 0) {
319
- throw new TypeError(`invalid OGACO001 cell formula tag: ${formulaTag}`);
321
+ throw new TypeError(`invalid OGACO002 cell formula tag: ${formulaTag}`);
320
322
  }
321
323
 
322
324
  const valueTag = this.u8("cell value tag");
@@ -334,8 +336,11 @@ class Reader {
334
336
  case 5:
335
337
  this.skipFormulaError();
336
338
  return;
339
+ case 6:
340
+ this.skipDate();
341
+ return;
337
342
  default:
338
- throw new TypeError(`invalid OGACO001 cell value tag: ${valueTag}`);
343
+ throw new TypeError(`invalid OGACO002 cell value tag: ${valueTag}`);
339
344
  }
340
345
  }
341
346
 
@@ -344,10 +349,18 @@ class Reader {
344
349
  const low = this.#view.getUint32(offset, true);
345
350
  const high = this.#view.getUint32(offset + 4, true);
346
351
  if (low === 0 && high === 0x8000_0000) {
347
- throw new TypeError("OGACO001 cell number uses negative zero");
352
+ throw new TypeError("OGACO002 cell number uses negative zero");
348
353
  }
349
354
  if ((high & 0x7ff0_0000) === 0x7ff0_0000) {
350
- throw new TypeError("OGACO001 cell numbers must be finite");
355
+ throw new TypeError("OGACO002 cell numbers must be finite");
356
+ }
357
+ }
358
+
359
+ skipDate(): void {
360
+ const offset = this.reserve(8, "cell date");
361
+ const milliseconds = this.#view.getBigInt64(offset, true);
362
+ if (milliseconds < -MAX_DATE_MILLISECONDS || milliseconds > MAX_DATE_MILLISECONDS) {
363
+ throw new TypeError("OGACO002 cell date is outside the ECMAScript range");
351
364
  }
352
365
  }
353
366
 
@@ -358,7 +371,7 @@ class Reader {
358
371
  this.skipString("custom formula error");
359
372
  return;
360
373
  }
361
- throw new TypeError(`invalid OGACO001 formula error tag: ${tag}`);
374
+ throw new TypeError(`invalid OGACO002 formula error tag: ${tag}`);
362
375
  }
363
376
 
364
377
  skipGeneration(label: string): void {
@@ -377,16 +390,16 @@ class Reader {
377
390
  }
378
391
 
379
392
  done(): void {
380
- if (this.#offset !== this.#end) throw new TypeError("trailing bytes in OGACO001 payload");
393
+ if (this.#offset !== this.#end) throw new TypeError("trailing bytes in OGACO002 payload");
381
394
  }
382
395
  }
383
396
 
384
397
  function assertInput(bytes: Uint8Array): void {
385
398
  if (!(bytes instanceof Uint8Array)) {
386
- throw new TypeError("OGACO001 bytes must be a Uint8Array");
399
+ throw new TypeError("OGACO002 bytes must be a Uint8Array");
387
400
  }
388
401
  if (typeof SharedArrayBuffer !== "undefined" && bytes.buffer instanceof SharedArrayBuffer) {
389
- throw new TypeError("OGACO001 bytes must not use shared mutable memory");
402
+ throw new TypeError("OGACO002 bytes must not use shared mutable memory");
390
403
  }
391
404
  }
392
405