@opengeni/sdk 0.46.0 → 0.52.1

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 (103) hide show
  1. package/README.md +94 -2
  2. package/dist/artifact-client.d.ts +20 -2
  3. package/dist/artifacts.d.ts +4 -0
  4. package/dist/artifacts.js +11 -0
  5. package/dist/artifacts.js.map +1 -0
  6. package/dist/chunk-FHI4DFIG.js +128 -0
  7. package/dist/chunk-FHI4DFIG.js.map +1 -0
  8. package/dist/chunk-FRJFNDIR.js +218 -0
  9. package/dist/chunk-FRJFNDIR.js.map +1 -0
  10. package/dist/{chunk-QKV5OCLJ.js → chunk-ILQZR5N6.js} +1380 -595
  11. package/dist/chunk-ILQZR5N6.js.map +1 -0
  12. package/dist/chunk-MZWTWOX6.js +661 -0
  13. package/dist/chunk-MZWTWOX6.js.map +1 -0
  14. package/dist/chunk-VWE2QIVO.js +1162 -0
  15. package/dist/chunk-VWE2QIVO.js.map +1 -0
  16. package/dist/chunk-VYCTL62C.js +230 -0
  17. package/dist/chunk-VYCTL62C.js.map +1 -0
  18. package/dist/client.d.ts +176 -20
  19. package/dist/codex-realtime-controller.d.ts +6 -6
  20. package/dist/codex-realtime-lifecycle.d.ts +1 -1
  21. package/dist/codex-realtime-v3.d.ts +3 -3
  22. package/dist/codex-realtime.d.ts +1 -1
  23. package/dist/company-profile.d.ts +100 -0
  24. package/dist/core.d.ts +5 -5
  25. package/dist/core.js +9 -4
  26. package/dist/desktop.d.ts +1 -1
  27. package/dist/editable-artifact-resources.d.ts +96 -0
  28. package/dist/editable-artifacts/browser-session.d.ts +35 -0
  29. package/dist/editable-artifacts/controller.d.ts +60 -0
  30. package/dist/editable-artifacts/errors.d.ts +23 -0
  31. package/dist/editable-artifacts/http-live-transport.d.ts +46 -0
  32. package/dist/editable-artifacts/index.d.ts +19 -0
  33. package/dist/editable-artifacts/pool.d.ts +15 -0
  34. package/dist/editable-artifacts/session.d.ts +96 -0
  35. package/dist/editable-artifacts/storage.d.ts +72 -0
  36. package/dist/editable-artifacts/types.d.ts +416 -0
  37. package/dist/editable-artifacts/worker/browser-client.d.ts +80 -0
  38. package/dist/editable-artifacts/worker/browser-entry.d.ts +6 -0
  39. package/dist/editable-artifacts/worker/kernel-adapter.d.ts +45 -0
  40. package/dist/editable-artifacts/worker/rpc-protocol.d.ts +92 -0
  41. package/dist/editable-artifacts/worker/runtime.d.ts +66 -0
  42. package/dist/editable-artifacts/worker/wire-codec.d.ts +52 -0
  43. package/dist/editable-artifacts-worker.d.ts +8 -0
  44. package/dist/editable-artifacts-worker.js +1708 -0
  45. package/dist/editable-artifacts-worker.js.map +1 -0
  46. package/dist/editable-artifacts.d.ts +6 -0
  47. package/dist/editable-artifacts.js +5954 -0
  48. package/dist/editable-artifacts.js.map +1 -0
  49. package/dist/gateway-realtime-transport.d.ts +1 -1
  50. package/dist/index.d.ts +41 -35
  51. package/dist/index.js +104 -68
  52. package/dist/index.js.map +1 -1
  53. package/dist/interaction.d.ts +885 -0
  54. package/dist/interaction.js +55 -0
  55. package/dist/interaction.js.map +1 -0
  56. package/dist/memory-slack-client.d.ts +13 -0
  57. package/dist/memory-slack-delivery.d.ts +99 -0
  58. package/dist/memory-slack.d.ts +4 -0
  59. package/dist/memory-slack.js +44 -0
  60. package/dist/memory-slack.js.map +1 -0
  61. package/dist/proxy.d.ts +3 -3
  62. package/dist/realtime.d.ts +12 -12
  63. package/dist/retained-artifacts.d.ts +12 -0
  64. package/dist/stream.d.ts +16 -1
  65. package/dist/terminal.d.ts +1 -1
  66. package/dist/transcription.d.ts +2 -2
  67. package/dist/types.d.ts +1203 -23
  68. package/dist/workspace-control-stream.d.ts +2 -2
  69. package/dist/workspace-state.d.ts +1 -1
  70. package/package.json +29 -3
  71. package/src/artifact-client.ts +129 -0
  72. package/src/artifacts.ts +17 -0
  73. package/src/client.ts +1801 -206
  74. package/src/company-profile.ts +92 -0
  75. package/src/editable-artifact-resources.ts +119 -0
  76. package/src/editable-artifacts/browser-session.ts +176 -0
  77. package/src/editable-artifacts/controller.ts +2632 -0
  78. package/src/editable-artifacts/errors.ts +52 -0
  79. package/src/editable-artifacts/http-live-transport.ts +1449 -0
  80. package/src/editable-artifacts/index.ts +175 -0
  81. package/src/editable-artifacts/pool.ts +81 -0
  82. package/src/editable-artifacts/session.ts +778 -0
  83. package/src/editable-artifacts/storage.ts +1718 -0
  84. package/src/editable-artifacts/types.ts +499 -0
  85. package/src/editable-artifacts/worker/browser-client.ts +1167 -0
  86. package/src/editable-artifacts/worker/browser-entry.ts +57 -0
  87. package/src/editable-artifacts/worker/kernel-adapter.ts +1039 -0
  88. package/src/editable-artifacts/worker/rpc-protocol.ts +622 -0
  89. package/src/editable-artifacts/worker/runtime.ts +1204 -0
  90. package/src/editable-artifacts/worker/wire-codec.ts +852 -0
  91. package/src/editable-artifacts-worker.ts +8 -0
  92. package/src/editable-artifacts.ts +6 -0
  93. package/src/index.ts +194 -0
  94. package/src/interaction.ts +1830 -0
  95. package/src/memory-slack-client.ts +71 -0
  96. package/src/memory-slack-delivery.ts +128 -0
  97. package/src/memory-slack.ts +19 -0
  98. package/src/retained-artifacts.ts +326 -0
  99. package/src/stream.ts +102 -4
  100. package/src/transcription.ts +2 -2
  101. package/src/types.ts +1430 -33
  102. package/src/workspace-control-stream.ts +15 -5
  103. package/dist/chunk-QKV5OCLJ.js.map +0 -1
@@ -0,0 +1,1204 @@
1
+ import {
2
+ EDITABLE_ARTIFACT_INTENT_MAX_BYTES,
3
+ EDITABLE_ARTIFACT_INTENT_VERSION,
4
+ assertCanonicalSpreadsheetArtifactCommandBytes,
5
+ decodeEditableArtifactMutationIntent,
6
+ hashEditableArtifactMutationIntent,
7
+ } from "@opengeni/contracts/editable-artifacts";
8
+ import { decodeCommittedTransactionSummary } from "@opengeni/contracts/editable-artifact-committed-transaction";
9
+ import { editableArtifactCodecFor } from "@opengeni/contracts/editable-artifact-codec-registry";
10
+ import { decodeEditableArtifactSerializedCommit } from "@opengeni/contracts/editable-artifact-serialized-commit";
11
+ import type {
12
+ EditableArtifactBlockedPending,
13
+ EditableArtifactCausalFrontier,
14
+ EditableArtifactCommittedTransaction,
15
+ EditableArtifactPendingTransaction,
16
+ } from "../types";
17
+ import type {
18
+ ArtifactWorkerKernelAdapter,
19
+ ArtifactWorkerKernelAdapterFactory,
20
+ ArtifactWorkerKernelSession,
21
+ } from "./kernel-adapter";
22
+ import { ArtifactWorkerPendingProjectionError } from "./kernel-adapter";
23
+ import {
24
+ ArtifactWorkerProtocolError,
25
+ ArtifactWorkerRpcKind,
26
+ decodeArtifactWorkerRpcMessage,
27
+ encodeArtifactWorkerRpcMessage,
28
+ transferListForArtifactWorkerRpcMessage,
29
+ type ArtifactWorkerRpcLimits,
30
+ type ArtifactWorkerRpcMessage,
31
+ } from "./rpc-protocol";
32
+ import {
33
+ decodeAuthorPendingMetadata,
34
+ decodeCommittedMetadata,
35
+ decodeInitialize,
36
+ decodePendingListMetadata,
37
+ decodeReconcileMetadata,
38
+ decodeSnapshotMetadata,
39
+ encodeErrorPayload,
40
+ encodePendingListMetadata,
41
+ encodeProjectionResponse,
42
+ encodeStateResponse,
43
+ requireMatchingHash,
44
+ sha256Hex,
45
+ type ArtifactWorkerAuthorPendingInput,
46
+ type ArtifactWorkerInitializeInput,
47
+ } from "./wire-codec";
48
+
49
+ export type ArtifactWorkerMessageEvent = { data: unknown };
50
+
51
+ export type ArtifactWorkerRuntimeEndpoint = {
52
+ addEventListener: (
53
+ type: "message",
54
+ listener: (event: ArtifactWorkerMessageEvent) => void,
55
+ ) => void;
56
+ removeEventListener: (
57
+ type: "message",
58
+ listener: (event: ArtifactWorkerMessageEvent) => void,
59
+ ) => void;
60
+ postMessage: (message: ArtifactWorkerRpcMessage, transfer: Transferable[]) => void;
61
+ };
62
+
63
+ export type InstallArtifactWorkerRuntimeOptions = {
64
+ endpoint: ArtifactWorkerRuntimeEndpoint;
65
+ loadAdapter: ArtifactWorkerKernelAdapterFactory;
66
+ rpcLimits?: Readonly<ArtifactWorkerRpcLimits>;
67
+ onProtocolError?: (error: ArtifactWorkerProtocolError) => void;
68
+ };
69
+
70
+ type InstalledStateCommon = {
71
+ artifactId: string;
72
+ sequence: number;
73
+ protocolVersion: number;
74
+ kernelVersion: string;
75
+ modelSchemaVersion: number;
76
+ stateHash: string;
77
+ };
78
+
79
+ type InstalledState = InstalledStateCommon &
80
+ (
81
+ | { modality: "spreadsheet"; causalFrontier: EditableArtifactCausalFrontier }
82
+ | { modality: "document" | "presentation"; nativeRevision: number }
83
+ );
84
+
85
+ const MAX_KNOWN_REQUESTS = 64;
86
+ const MAX_QUEUED_REQUEST_BYTES = 192 * 1024 * 1024;
87
+ const MAX_PENDING_AGGREGATE_BYTES = 64 * 1024 * 1024;
88
+
89
+ /** Owns all mutable artifact/WASM state inside one dedicated Worker. */
90
+ export class ArtifactWorkerRuntime {
91
+ private readonly endpoint: ArtifactWorkerRuntimeEndpoint;
92
+ private readonly loadAdapter: ArtifactWorkerKernelAdapterFactory;
93
+ private readonly rpcLimits: Readonly<ArtifactWorkerRpcLimits> | undefined;
94
+ private readonly onProtocolError: ((error: ArtifactWorkerProtocolError) => void) | undefined;
95
+ private readonly cancelled = new Set<number>();
96
+ private readonly knownRequests = new Map<number, { bytes: number; generation: number }>();
97
+ private queuedRequestBytes = 0;
98
+ private queue: Promise<void> = Promise.resolve();
99
+ private generation: number | null = null;
100
+ private initialization: ArtifactWorkerInitializeInput | null = null;
101
+ private adapter: ArtifactWorkerKernelAdapter | null = null;
102
+ private confirmed: ArtifactWorkerKernelSession | null = null;
103
+ private speculative: ArtifactWorkerKernelSession | null = null;
104
+ private installed: InstalledState | null = null;
105
+ private pendingKeys: readonly string[] = Object.freeze([]);
106
+ private blockedPending: readonly EditableArtifactBlockedPending[] = Object.freeze([]);
107
+ private disposed = false;
108
+
109
+ private readonly onMessage = (event: ArtifactWorkerMessageEvent): void => {
110
+ if (this.disposed) return;
111
+ let frame;
112
+ try {
113
+ frame = decodeArtifactWorkerRpcMessage(event.data, this.rpcLimits);
114
+ } catch (error) {
115
+ const failure = asProtocolError(error);
116
+ this.reportProtocolError(failure);
117
+ return;
118
+ }
119
+ if (frame.kind === ArtifactWorkerRpcKind.Cancel) {
120
+ if (
121
+ frame.flags === 0 &&
122
+ frame.metadata.byteLength === 0 &&
123
+ frame.segments.length === 0 &&
124
+ this.knownRequests.get(frame.requestId)?.generation === frame.generation
125
+ ) {
126
+ this.cancelled.add(frame.requestId);
127
+ }
128
+ return;
129
+ }
130
+ if (frame.flags !== 0) {
131
+ this.reportProtocolError(runtimeError("noncanonical", "unknown RPC flags are set"));
132
+ return;
133
+ }
134
+ if (this.knownRequests.has(frame.requestId)) {
135
+ this.reportProtocolError(runtimeError("duplicate_request", "RPC request id is duplicated"));
136
+ return;
137
+ }
138
+ const requestBytes = retainedFrameBytes(frame);
139
+ if (
140
+ this.knownRequests.size >= MAX_KNOWN_REQUESTS ||
141
+ this.queuedRequestBytes + requestBytes > MAX_QUEUED_REQUEST_BYTES
142
+ ) {
143
+ this.respondError(
144
+ frame.requestId,
145
+ runtimeError("request_limit", "artifact Worker request queue is full"),
146
+ frame.generation,
147
+ );
148
+ return;
149
+ }
150
+ this.knownRequests.set(frame.requestId, { bytes: requestBytes, generation: frame.generation });
151
+ this.queuedRequestBytes += requestBytes;
152
+ this.queue = this.queue
153
+ .then(() => this.dispatch(frame))
154
+ .catch((error: unknown) => this.reportProtocolError(asProtocolError(error)));
155
+ };
156
+
157
+ constructor(options: InstallArtifactWorkerRuntimeOptions) {
158
+ this.endpoint = options.endpoint;
159
+ this.loadAdapter = options.loadAdapter;
160
+ this.rpcLimits = options.rpcLimits;
161
+ this.onProtocolError = options.onProtocolError;
162
+ this.endpoint.addEventListener("message", this.onMessage);
163
+ }
164
+
165
+ async dispose(): Promise<void> {
166
+ if (this.disposed) return;
167
+ this.disposed = true;
168
+ this.endpoint.removeEventListener("message", this.onMessage);
169
+ await this.queue.catch(() => {});
170
+ this.releaseState();
171
+ this.disposeAdapter(this.adapter);
172
+ this.adapter = null;
173
+ this.cancelled.clear();
174
+ this.knownRequests.clear();
175
+ this.queuedRequestBytes = 0;
176
+ }
177
+
178
+ private async dispatch(frame: ReturnType<typeof decodeArtifactWorkerRpcMessage>): Promise<void> {
179
+ try {
180
+ if (frame.kind === ArtifactWorkerRpcKind.Initialize) {
181
+ await this.initialize(frame);
182
+ return;
183
+ }
184
+ this.requireInitialized(frame.generation);
185
+ this.throwIfCancelled(frame.requestId);
186
+ switch (frame.kind) {
187
+ case ArtifactWorkerRpcKind.Reset:
188
+ this.requireNoPayload(frame);
189
+ this.releaseState();
190
+ this.respond(frame.requestId);
191
+ return;
192
+ case ArtifactWorkerRpcKind.LoadSnapshot:
193
+ await this.loadSnapshot(frame);
194
+ return;
195
+ case ArtifactWorkerRpcKind.ApplyRecovered:
196
+ await this.applyRecovered(frame);
197
+ return;
198
+ case ArtifactWorkerRpcKind.ReconcileCommitted:
199
+ await this.reconcileCommitted(frame);
200
+ return;
201
+ case ArtifactWorkerRpcKind.ReplacePending:
202
+ await this.replacePending(frame);
203
+ return;
204
+ case ArtifactWorkerRpcKind.AuthorPending:
205
+ await this.authorPending(frame);
206
+ return;
207
+ case ArtifactWorkerRpcKind.QuerySpreadsheet:
208
+ this.queryArtifact(frame);
209
+ return;
210
+ case ArtifactWorkerRpcKind.Dispose:
211
+ this.requireNoPayload(frame);
212
+ this.releaseState();
213
+ this.disposeAdapter(this.adapter);
214
+ this.adapter = null;
215
+ this.respond(frame.requestId);
216
+ return;
217
+ default:
218
+ throw runtimeError("unknown_request", `worker cannot execute RPC kind ${frame.kind}`);
219
+ }
220
+ } catch (error) {
221
+ this.respondError(frame.requestId, error, frame.generation);
222
+ } finally {
223
+ this.cancelled.delete(frame.requestId);
224
+ const retainedBytes = this.knownRequests.get(frame.requestId)?.bytes ?? 0;
225
+ this.knownRequests.delete(frame.requestId);
226
+ this.queuedRequestBytes -= retainedBytes;
227
+ }
228
+ }
229
+
230
+ private async initialize(
231
+ frame: ReturnType<typeof decodeArtifactWorkerRpcMessage>,
232
+ ): Promise<void> {
233
+ if (this.generation !== null || this.adapter !== null) {
234
+ throw runtimeError("already_initialized", "artifact Worker is already initialized");
235
+ }
236
+ if (frame.segments.length !== 0)
237
+ throw runtimeError("invalid_segment", "initialize has no segments");
238
+ const initialization = decodeInitialize(frame.metadata);
239
+ this.throwIfCancelled(frame.requestId);
240
+ let adapter: ArtifactWorkerKernelAdapter | null = null;
241
+ try {
242
+ adapter = await this.loadAdapter(initialization);
243
+ this.throwIfCancelled(frame.requestId);
244
+ } catch (error) {
245
+ this.disposeAdapter(adapter);
246
+ throw error;
247
+ }
248
+ assertAdapterCompatibility(adapter, initialization);
249
+ this.generation = frame.generation;
250
+ this.initialization = initialization;
251
+ this.adapter = adapter;
252
+ this.respond(frame.requestId);
253
+ }
254
+
255
+ private async loadSnapshot(
256
+ frame: ReturnType<typeof decodeArtifactWorkerRpcMessage>,
257
+ ): Promise<void> {
258
+ const initialization = this.requireInitialization();
259
+ const adapter = this.requireAdapter();
260
+ if (frame.segments.length !== 1)
261
+ throw runtimeError("invalid_segment", "snapshot requires one segment");
262
+ const ownedBytes = requireBytesWithin(
263
+ frame.segments[0]!,
264
+ initialization.maximumSnapshotBytes,
265
+ "snapshot",
266
+ );
267
+ const snapshot = decodeSnapshotMetadata(frame.metadata, ownedBytes);
268
+ if (
269
+ snapshot.modality !== initialization.modality ||
270
+ snapshot.modality !== adapter.modality ||
271
+ (snapshot.modality === "spreadsheet" &&
272
+ snapshot.protocolVersion !== adapter.protocolVersion) ||
273
+ snapshot.kernelVersion !== adapter.kernelVersion ||
274
+ snapshot.modelSchemaVersion !== adapter.modelSchemaVersion
275
+ ) {
276
+ throw runtimeError(
277
+ "unsupported_protocol",
278
+ "snapshot protocol/kernel/schema is incompatible with the loaded collaboration kernel",
279
+ );
280
+ }
281
+ const digest = await sha256Hex(ownedBytes);
282
+ this.throwIfCancelled(frame.requestId);
283
+ requireMatchingHash(digest, snapshot.digest, "snapshot digest");
284
+ const canonical = requireBytesWithin(
285
+ adapter.canonicalizeSnapshot(ownedBytes),
286
+ initialization.maximumSnapshotBytes,
287
+ "canonical snapshot",
288
+ );
289
+ if (!bytesEqual(canonical, ownedBytes)) {
290
+ throw runtimeError(
291
+ "noncanonical_snapshot",
292
+ "server snapshot bytes are not canonical; refusing to persist a different model",
293
+ );
294
+ }
295
+ const nextConfirmed = adapter.open(canonical);
296
+ let nextSpeculative: ArtifactWorkerKernelSession | null = null;
297
+ let stateHash: string;
298
+ try {
299
+ stateHash = await nextConfirmed.stateHash();
300
+ this.throwIfCancelled(frame.requestId);
301
+ requireMatchingHash(stateHash, snapshot.stateHash, "snapshot state");
302
+ if (
303
+ snapshot.modality !== "spreadsheet" &&
304
+ nextConfirmed.nativeRevision() !== snapshot.nativeRevision
305
+ ) {
306
+ throw runtimeError(
307
+ "kernel_diverged",
308
+ "serialized snapshot native revision does not match the WASM session",
309
+ );
310
+ }
311
+ nextSpeculative = nextConfirmed.fork();
312
+ this.throwIfCancelled(frame.requestId);
313
+ } catch (error) {
314
+ this.disposeSession(nextConfirmed);
315
+ this.disposeSession(nextSpeculative);
316
+ throw error;
317
+ }
318
+ if (!nextSpeculative) throw runtimeError("kernel_failed", "kernel fork was not created");
319
+ this.installSessions(
320
+ nextConfirmed,
321
+ nextSpeculative,
322
+ snapshot.modality === "spreadsheet"
323
+ ? {
324
+ artifactId: snapshot.artifactId,
325
+ modality: "spreadsheet",
326
+ sequence: snapshot.sequence,
327
+ protocolVersion: snapshot.protocolVersion,
328
+ kernelVersion: snapshot.kernelVersion,
329
+ modelSchemaVersion: snapshot.modelSchemaVersion,
330
+ stateHash,
331
+ causalFrontier: snapshot.causalFrontier,
332
+ }
333
+ : {
334
+ artifactId: snapshot.artifactId,
335
+ modality: snapshot.modality,
336
+ sequence: snapshot.sequence,
337
+ protocolVersion: adapter.protocolVersion,
338
+ kernelVersion: snapshot.kernelVersion,
339
+ modelSchemaVersion: snapshot.modelSchemaVersion,
340
+ stateHash,
341
+ nativeRevision: snapshot.nativeRevision,
342
+ },
343
+ Object.freeze([]),
344
+ Object.freeze([]),
345
+ );
346
+ this.respond(frame.requestId, encodeStateResponse(stateHash, digest));
347
+ }
348
+
349
+ private async applyRecovered(
350
+ frame: ReturnType<typeof decodeArtifactWorkerRpcMessage>,
351
+ ): Promise<void> {
352
+ const initialization = this.requireInitialization();
353
+ if (frame.segments.length !== 1) {
354
+ throw runtimeError("invalid_segment", "recovered transaction requires one operation segment");
355
+ }
356
+ const committedTransactionBytes = requireBytesWithin(
357
+ frame.segments[0]!,
358
+ initialization.maximumCommittedTransactionBytes,
359
+ "committed transaction",
360
+ );
361
+ const transaction = decodeCommittedMetadata(frame.metadata, committedTransactionBytes);
362
+ const result = await this.buildCommittedCandidate(transaction, [], frame.requestId);
363
+ this.installCommittedCandidate(result, transaction, []);
364
+ this.respond(frame.requestId, encodeStateResponse(result.stateHash));
365
+ }
366
+
367
+ private async reconcileCommitted(
368
+ frame: ReturnType<typeof decodeArtifactWorkerRpcMessage>,
369
+ ): Promise<void> {
370
+ const initialization = this.requireInitialization();
371
+ const decoded = decodeReconcileMetadata(
372
+ frame.metadata,
373
+ frame.segments,
374
+ initialization.maximumPendingTransactions,
375
+ );
376
+ requireBytesWithin(
377
+ decoded.committed.committedTransactionBytes,
378
+ initialization.maximumCommittedTransactionBytes,
379
+ "committed transaction",
380
+ );
381
+ await this.validatePending(decoded.pending);
382
+ const result = await this.buildCommittedCandidate(
383
+ decoded.committed,
384
+ decoded.pending,
385
+ frame.requestId,
386
+ );
387
+ this.installCommittedCandidate(result, decoded.committed, decoded.pending);
388
+ this.respond(
389
+ frame.requestId,
390
+ encodeProjectionResponse(result.blockedPending, result.stateHash),
391
+ );
392
+ }
393
+
394
+ private async replacePending(
395
+ frame: ReturnType<typeof decodeArtifactWorkerRpcMessage>,
396
+ ): Promise<void> {
397
+ const initialization = this.requireInitialization();
398
+ const pending = decodePendingListMetadata(
399
+ frame.metadata,
400
+ frame.segments,
401
+ initialization.maximumPendingTransactions,
402
+ );
403
+ await this.validatePending(pending);
404
+ const keys = pending.map(pendingKey);
405
+ if (arraysEqual(keys, this.pendingKeys)) {
406
+ this.respond(frame.requestId, encodeProjectionResponse(this.blockedPending));
407
+ return;
408
+ }
409
+ const candidate = this.requireConfirmed().fork();
410
+ let blockedPending: readonly EditableArtifactBlockedPending[];
411
+ try {
412
+ blockedPending = this.applyPending(candidate, pending, frame.requestId);
413
+ this.throwIfCancelled(frame.requestId);
414
+ } catch (error) {
415
+ this.disposeSession(candidate);
416
+ throw error;
417
+ }
418
+ const previous = this.speculative;
419
+ this.speculative = candidate;
420
+ this.pendingKeys = Object.freeze(keys);
421
+ this.blockedPending = blockedPending;
422
+ this.disposeSession(previous);
423
+ this.respond(frame.requestId, encodeProjectionResponse(blockedPending));
424
+ }
425
+
426
+ private async authorPending(
427
+ frame: ReturnType<typeof decodeArtifactWorkerRpcMessage>,
428
+ ): Promise<void> {
429
+ const initialization = this.requireInitialization();
430
+ const adapter = this.requireAdapter();
431
+ if (frame.segments.length !== 1)
432
+ throw runtimeError("invalid_segment", "author requires one command segment");
433
+ const commandBytes = requireBytesWithin(
434
+ frame.segments[0]!,
435
+ initialization.maximumCommandBytes,
436
+ "pending command",
437
+ );
438
+ const input = decodeAuthorPendingMetadata(frame.metadata, commandBytes);
439
+ if (
440
+ input.modality !== initialization.modality ||
441
+ input.modality !== adapter.modality ||
442
+ input.protocolVersion !== adapter.protocolVersion ||
443
+ input.kernelVersion !== adapter.kernelVersion ||
444
+ input.modelSchemaVersion !== adapter.modelSchemaVersion ||
445
+ input.commandVersion !== adapter.commandVersion
446
+ ) {
447
+ throw runtimeError(
448
+ "unsupported_protocol",
449
+ "pending authoring versions are incompatible with the loaded kernel",
450
+ );
451
+ }
452
+ if (this.installed && input.artifactId !== this.installed.artifactId) {
453
+ throw runtimeError("artifact_mismatch", "pending transaction belongs to another artifact");
454
+ }
455
+ try {
456
+ if (input.modality === "spreadsheet") {
457
+ assertCanonicalSpreadsheetArtifactCommandBytes(commandBytes);
458
+ } else {
459
+ editableArtifactCodecFor({
460
+ durableModality: input.modality,
461
+ modelSchemaVersion: input.modelSchemaVersion,
462
+ commandProtocolVersion: input.commandVersion,
463
+ }).command.assertCanonical(commandBytes);
464
+ }
465
+ } catch (error) {
466
+ throw runtimeError(
467
+ "noncanonical_command",
468
+ `pending ${input.modality} command must be canonical before it receives an identity: ${
469
+ error instanceof Error ? error.message : "invalid command"
470
+ }`,
471
+ );
472
+ }
473
+ const canonicalCommand = commandBytes;
474
+ const authored = authorCanonicalPendingIntent(input, canonicalCommand);
475
+ const intentBytes = requireBytesWithin(
476
+ authored.bytes,
477
+ EDITABLE_ARTIFACT_INTENT_MAX_BYTES,
478
+ "OGATX001 intent",
479
+ );
480
+ this.throwIfCancelled(frame.requestId);
481
+ const commonPending = {
482
+ artifactId: input.artifactId,
483
+ clientTransactionId: input.clientTransactionId,
484
+ requestHash: authored.requestHash,
485
+ protocolVersion: input.protocolVersion,
486
+ modelSchemaVersion: input.modelSchemaVersion,
487
+ commandVersion: input.commandVersion,
488
+ replicaId: input.replicaId,
489
+ replicaCounter: input.replicaCounter,
490
+ previousLocalTransactionId: input.previousLocalTransactionId,
491
+ observedHeadSequence: input.observedHeadSequence,
492
+ commandBytes: canonicalCommand,
493
+ intentBytes,
494
+ createdAt: input.createdAt,
495
+ } as const;
496
+ let pending: EditableArtifactPendingTransaction;
497
+ if (input.modality === "spreadsheet") {
498
+ pending = Object.freeze({
499
+ ...commonPending,
500
+ modality: "spreadsheet",
501
+ causalBase: input.causalBase ?? [],
502
+ selectiveUndoTargets: input.selectiveUndoTargets ?? [],
503
+ });
504
+ } else {
505
+ const installed = this.requireInstalled();
506
+ if (installed.modality !== input.modality) {
507
+ throw runtimeError("modality_mismatch", "pending modality does not match installed state");
508
+ }
509
+ const expectedHeadSequence = installed.sequence + this.pendingKeys.length;
510
+ if (input.observedHeadSequence !== expectedHeadSequence) {
511
+ throw runtimeError(
512
+ "serialized_head_conflict",
513
+ "serialized pending command was not authored over the speculative head",
514
+ );
515
+ }
516
+ pending = Object.freeze({
517
+ ...commonPending,
518
+ modality: input.modality,
519
+ observedNativeRevision: this.requireSpeculative().nativeRevision(),
520
+ });
521
+ }
522
+ const metadata = encodePendingListMetadata([pending]);
523
+ // Both buffers are exclusively Worker-owned here: the command arrived by
524
+ // transfer and OGATX authoring allocated the intent. Return them without a
525
+ // second multi-megabyte copy.
526
+ const command = transferableOwnedBuffer(canonicalCommand);
527
+ const intent = transferableOwnedBuffer(intentBytes);
528
+ this.respond(frame.requestId, metadata, [command, intent]);
529
+ }
530
+
531
+ private queryArtifact(frame: ReturnType<typeof decodeArtifactWorkerRpcMessage>): void {
532
+ if (frame.metadata.byteLength !== 0 || frame.segments.length !== 1) {
533
+ throw runtimeError("invalid_segment", "artifact query requires one byte segment");
534
+ }
535
+ const initialization = this.requireInitialization();
536
+ const queryBytes = requireBytesWithin(
537
+ frame.segments[0]!,
538
+ initialization.maximumQueryBytes,
539
+ `${initialization.modality} query`,
540
+ );
541
+ const result = requireBytesWithin(
542
+ this.requireSpeculative().query(queryBytes),
543
+ initialization.maximumQueryResponseBytes,
544
+ `${initialization.modality} projection`,
545
+ );
546
+ this.throwIfCancelled(frame.requestId);
547
+ this.respond(frame.requestId, undefined, [transferableOwnedBuffer(result)]);
548
+ }
549
+
550
+ private async buildCommittedCandidate(
551
+ transaction: EditableArtifactCommittedTransaction,
552
+ remainingPending: readonly EditableArtifactPendingTransaction[],
553
+ requestId: number,
554
+ ): Promise<{
555
+ confirmed: ArtifactWorkerKernelSession;
556
+ speculative: ArtifactWorkerKernelSession;
557
+ stateHash: string;
558
+ blockedPending: readonly EditableArtifactBlockedPending[];
559
+ }> {
560
+ const installed = this.requireInstalled();
561
+ if (transaction.artifactId !== installed.artifactId) {
562
+ throw runtimeError("artifact_mismatch", "committed transaction belongs to another artifact");
563
+ }
564
+ if (transaction.modality !== installed.modality) {
565
+ throw runtimeError("modality_mismatch", "committed transaction modality is incompatible");
566
+ }
567
+ if (
568
+ transaction.modality === "spreadsheet" &&
569
+ transaction.protocolVersion !== installed.protocolVersion
570
+ )
571
+ throw runtimeError("unsupported_protocol", "committed transaction protocol is incompatible");
572
+ if (transaction.startSequence !== installed.sequence + 1) {
573
+ throw runtimeError("invalid_sequence", "committed transaction is not contiguous");
574
+ }
575
+ assertCommittedEnvelopeMatchesOuter(transaction);
576
+ if (transaction.modality === "spreadsheet" && installed.modality === "spreadsheet") {
577
+ requireFrontierAdvance(installed.causalFrontier, transaction.causalFrontier);
578
+ } else if (transaction.modality !== "spreadsheet" && installed.modality !== "spreadsheet") {
579
+ if (transaction.priorNativeRevision !== installed.nativeRevision) {
580
+ throw runtimeError("invalid_revision", "serialized commit prior revision is not current");
581
+ }
582
+ }
583
+ requireMatchingHash(installed.stateHash, transaction.priorStateHash, "transaction prior state");
584
+ const nextConfirmed = this.requireConfirmed().fork();
585
+ let nextSpeculative: ArtifactWorkerKernelSession | null = null;
586
+ try {
587
+ if (transaction.modality === "spreadsheet") {
588
+ nextConfirmed.applyCommitted(transaction.committedTransactionBytes);
589
+ } else {
590
+ const summary = decodeEditableArtifactSerializedCommit(
591
+ transaction.committedTransactionBytes,
592
+ transaction.modality,
593
+ );
594
+ const intent = decodeEditableArtifactMutationIntent(summary.intentBytes);
595
+ const nativeReceipt = nextConfirmed.applyCommands(intent.commandBytes);
596
+ if (!bytesEqual(nativeReceipt, summary.nativeReceiptBytes)) {
597
+ throw runtimeError(
598
+ "kernel_diverged",
599
+ "WASM native receipt differs from the exact durable OGAST receipt",
600
+ );
601
+ }
602
+ if (nextConfirmed.nativeRevision() !== transaction.nativeRevision) {
603
+ throw runtimeError(
604
+ "kernel_diverged",
605
+ "WASM native revision differs from the exact durable OGAST revision",
606
+ );
607
+ }
608
+ }
609
+ const stateHash = await nextConfirmed.stateHash();
610
+ this.throwIfCancelled(requestId);
611
+ requireMatchingHash(stateHash, transaction.stateHash, "committed state");
612
+ nextSpeculative = nextConfirmed.fork();
613
+ const blockedPending = this.applyPending(
614
+ nextSpeculative,
615
+ remainingPending,
616
+ requestId,
617
+ transaction.endSequence,
618
+ );
619
+ this.throwIfCancelled(requestId);
620
+ return {
621
+ confirmed: nextConfirmed,
622
+ speculative: nextSpeculative,
623
+ stateHash,
624
+ blockedPending,
625
+ };
626
+ } catch (error) {
627
+ this.disposeSession(nextConfirmed);
628
+ this.disposeSession(nextSpeculative);
629
+ throw error;
630
+ }
631
+ }
632
+
633
+ private installCommittedCandidate(
634
+ candidate: {
635
+ confirmed: ArtifactWorkerKernelSession;
636
+ speculative: ArtifactWorkerKernelSession;
637
+ stateHash: string;
638
+ blockedPending: readonly EditableArtifactBlockedPending[];
639
+ },
640
+ transaction: EditableArtifactCommittedTransaction,
641
+ pending: readonly EditableArtifactPendingTransaction[],
642
+ ): void {
643
+ const installed = this.requireInstalled();
644
+ this.installSessions(
645
+ candidate.confirmed,
646
+ candidate.speculative,
647
+ transaction.modality === "spreadsheet"
648
+ ? {
649
+ ...installed,
650
+ modality: "spreadsheet",
651
+ sequence: transaction.endSequence,
652
+ stateHash: candidate.stateHash,
653
+ causalFrontier: transaction.causalFrontier,
654
+ }
655
+ : {
656
+ ...installed,
657
+ modality: transaction.modality,
658
+ sequence: transaction.endSequence,
659
+ stateHash: candidate.stateHash,
660
+ nativeRevision: transaction.nativeRevision,
661
+ },
662
+ Object.freeze(pending.map(pendingKey)),
663
+ candidate.blockedPending,
664
+ );
665
+ }
666
+
667
+ private applyPending(
668
+ session: ArtifactWorkerKernelSession,
669
+ pending: readonly EditableArtifactPendingTransaction[],
670
+ requestId: number,
671
+ baseSequence = this.requireInstalled().sequence,
672
+ ): readonly EditableArtifactBlockedPending[] {
673
+ const blocked: EditableArtifactBlockedPending[] = [];
674
+ const blockedIds = new Set<string>();
675
+ for (const transaction of pending) {
676
+ this.throwIfCancelled(requestId);
677
+ if (
678
+ transaction.previousLocalTransactionId !== null &&
679
+ blockedIds.has(transaction.previousLocalTransactionId)
680
+ ) {
681
+ blockedIds.add(transaction.clientTransactionId);
682
+ blocked.push(
683
+ Object.freeze({
684
+ clientTransactionId: transaction.clientTransactionId,
685
+ code: "blocked_predecessor",
686
+ }),
687
+ );
688
+ continue;
689
+ }
690
+ try {
691
+ if (transaction.modality === "spreadsheet") {
692
+ session.applyPending(transaction.intentBytes);
693
+ } else {
694
+ if (
695
+ transaction.observedHeadSequence !== baseSequence ||
696
+ transaction.observedNativeRevision !== session.nativeRevision()
697
+ ) {
698
+ throw new ArtifactWorkerPendingProjectionError(
699
+ "serialized_head_conflict",
700
+ "serialized pending transaction is stale",
701
+ );
702
+ }
703
+ session.applyCommands(transaction.commandBytes);
704
+ baseSequence += 1;
705
+ }
706
+ } catch (error) {
707
+ if (!(error instanceof ArtifactWorkerPendingProjectionError)) throw error;
708
+ blockedIds.add(transaction.clientTransactionId);
709
+ blocked.push(
710
+ Object.freeze({ clientTransactionId: transaction.clientTransactionId, code: error.code }),
711
+ );
712
+ }
713
+ }
714
+ return Object.freeze(blocked);
715
+ }
716
+
717
+ private async validatePending(
718
+ pending: readonly EditableArtifactPendingTransaction[],
719
+ ): Promise<void> {
720
+ const initialization = this.requireInitialization();
721
+ const installed = this.requireInstalled();
722
+ const adapter = this.requireAdapter();
723
+ const ids = new Set<string>();
724
+ let aggregateBytes = 0;
725
+ for (const transaction of pending) {
726
+ if (transaction.artifactId !== installed.artifactId) {
727
+ throw runtimeError("artifact_mismatch", "pending transaction belongs to another artifact");
728
+ }
729
+ if (transaction.modality !== installed.modality) {
730
+ throw runtimeError("modality_mismatch", "pending transaction modality is incompatible");
731
+ }
732
+ if (
733
+ transaction.protocolVersion !== installed.protocolVersion ||
734
+ transaction.modelSchemaVersion !== installed.modelSchemaVersion ||
735
+ transaction.commandVersion !== adapter.commandVersion
736
+ ) {
737
+ throw runtimeError(
738
+ "unsupported_protocol",
739
+ "pending transaction versions are incompatible with confirmed state",
740
+ );
741
+ }
742
+ if (ids.has(transaction.clientTransactionId)) {
743
+ throw runtimeError("duplicate_transaction", "pending transaction id is duplicated");
744
+ }
745
+ ids.add(transaction.clientTransactionId);
746
+ requireBytesWithin(
747
+ transaction.commandBytes,
748
+ initialization.maximumCommandBytes,
749
+ "pending command",
750
+ );
751
+ requireBytesWithin(
752
+ transaction.intentBytes,
753
+ initialization.maximumIntentBytes,
754
+ "pending intent",
755
+ );
756
+ try {
757
+ if (transaction.modality === "spreadsheet") {
758
+ assertCanonicalSpreadsheetArtifactCommandBytes(transaction.commandBytes);
759
+ } else {
760
+ editableArtifactCodecFor({
761
+ durableModality: transaction.modality,
762
+ modelSchemaVersion: transaction.modelSchemaVersion,
763
+ commandProtocolVersion: transaction.commandVersion,
764
+ }).command.assertCanonical(transaction.commandBytes);
765
+ }
766
+ } catch (error) {
767
+ throw new ArtifactWorkerProtocolError(
768
+ "noncanonical_command",
769
+ `pending transaction contains noncanonical ${transaction.modality} command bytes`,
770
+ { cause: error },
771
+ );
772
+ }
773
+ verifyCanonicalPendingIntent(transaction);
774
+ aggregateBytes += transaction.commandBytes.byteLength + transaction.intentBytes.byteLength;
775
+ if (!Number.isSafeInteger(aggregateBytes) || aggregateBytes > MAX_PENDING_AGGREGATE_BYTES) {
776
+ throw runtimeError("limit_exceeded", "pending commands exceed their aggregate byte limit");
777
+ }
778
+ }
779
+ }
780
+
781
+ private installSessions(
782
+ confirmed: ArtifactWorkerKernelSession,
783
+ speculative: ArtifactWorkerKernelSession,
784
+ installed: InstalledState,
785
+ pendingKeys: readonly string[],
786
+ blockedPending: readonly EditableArtifactBlockedPending[],
787
+ ): void {
788
+ const previousConfirmed = this.confirmed;
789
+ const previousSpeculative = this.speculative;
790
+ this.confirmed = confirmed;
791
+ this.speculative = speculative;
792
+ this.installed = Object.freeze(installed);
793
+ this.pendingKeys = pendingKeys;
794
+ this.blockedPending = blockedPending;
795
+ this.disposeSession(previousConfirmed);
796
+ this.disposeSession(previousSpeculative);
797
+ }
798
+
799
+ private releaseState(): void {
800
+ const confirmed = this.confirmed;
801
+ const speculative = this.speculative;
802
+ this.confirmed = null;
803
+ this.speculative = null;
804
+ this.installed = null;
805
+ this.pendingKeys = Object.freeze([]);
806
+ this.blockedPending = Object.freeze([]);
807
+ this.disposeSession(confirmed);
808
+ this.disposeSession(speculative);
809
+ }
810
+
811
+ private disposeSession(session: ArtifactWorkerKernelSession | null): void {
812
+ if (!session) return;
813
+ try {
814
+ session.dispose();
815
+ } catch (error) {
816
+ this.reportProtocolError(asProtocolError(error));
817
+ }
818
+ }
819
+
820
+ private disposeAdapter(adapter: ArtifactWorkerKernelAdapter | null): void {
821
+ if (!adapter?.dispose) return;
822
+ try {
823
+ adapter.dispose();
824
+ } catch (error) {
825
+ this.reportProtocolError(asProtocolError(error));
826
+ }
827
+ }
828
+
829
+ private reportProtocolError(error: ArtifactWorkerProtocolError): void {
830
+ try {
831
+ this.onProtocolError?.(error);
832
+ } catch {
833
+ // Diagnostics must never acquire authority over Worker state or cleanup.
834
+ }
835
+ }
836
+
837
+ private requireNoPayload(frame: ReturnType<typeof decodeArtifactWorkerRpcMessage>): void {
838
+ if (frame.metadata.byteLength !== 0 || frame.segments.length !== 0) {
839
+ throw runtimeError("unexpected_payload", "RPC operation must not contain a payload");
840
+ }
841
+ }
842
+
843
+ private requireInitialized(generation: number): void {
844
+ if (this.generation === null || this.adapter === null || this.initialization === null) {
845
+ throw runtimeError("not_initialized", "artifact Worker is not initialized");
846
+ }
847
+ if (generation !== this.generation) {
848
+ throw runtimeError("stale_generation", "RPC belongs to a retired Worker generation");
849
+ }
850
+ }
851
+
852
+ private requireInitialization(): ArtifactWorkerInitializeInput {
853
+ if (!this.initialization)
854
+ throw runtimeError("not_initialized", "artifact Worker is not initialized");
855
+ return this.initialization;
856
+ }
857
+
858
+ private requireAdapter(): ArtifactWorkerKernelAdapter {
859
+ if (!this.adapter) throw runtimeError("not_initialized", "artifact Worker is not initialized");
860
+ return this.adapter;
861
+ }
862
+
863
+ private requireConfirmed(): ArtifactWorkerKernelSession {
864
+ if (!this.confirmed)
865
+ throw runtimeError("snapshot_required", "artifact Worker has no confirmed state");
866
+ return this.confirmed;
867
+ }
868
+
869
+ private requireSpeculative(): ArtifactWorkerKernelSession {
870
+ if (!this.speculative) {
871
+ throw runtimeError("snapshot_required", "artifact Worker has no speculative state");
872
+ }
873
+ return this.speculative;
874
+ }
875
+
876
+ private requireInstalled(): InstalledState {
877
+ if (!this.installed)
878
+ throw runtimeError("snapshot_required", "artifact Worker has no installed snapshot");
879
+ return this.installed;
880
+ }
881
+
882
+ private throwIfCancelled(requestId: number): void {
883
+ if (this.cancelled.has(requestId))
884
+ throw runtimeError("cancelled", "artifact Worker request was cancelled");
885
+ }
886
+
887
+ private respond(requestId: number, metadata?: Uint8Array, segments?: ArrayBuffer[]): void {
888
+ if (this.generation === null || this.cancelled.has(requestId)) return;
889
+ const response = encodeArtifactWorkerRpcMessage({
890
+ kind: ArtifactWorkerRpcKind.Response,
891
+ generation: this.generation,
892
+ requestId,
893
+ ...(metadata ? { metadata } : {}),
894
+ ...(segments ? { segments } : {}),
895
+ ...(this.rpcLimits ? { limits: this.rpcLimits } : {}),
896
+ });
897
+ this.endpoint.postMessage(response, transferListForArtifactWorkerRpcMessage(response));
898
+ }
899
+
900
+ private respondError(requestId: number, error: unknown, generation = this.generation): void {
901
+ if (generation === null || this.cancelled.has(requestId)) return;
902
+ const failure = asWorkerFailure(error);
903
+ const response = encodeArtifactWorkerRpcMessage({
904
+ kind: ArtifactWorkerRpcKind.Error,
905
+ generation,
906
+ requestId,
907
+ metadata: encodeErrorPayload(failure),
908
+ ...(this.rpcLimits ? { limits: this.rpcLimits } : {}),
909
+ });
910
+ this.endpoint.postMessage(response, transferListForArtifactWorkerRpcMessage(response));
911
+ }
912
+ }
913
+
914
+ function authorCanonicalPendingIntent(
915
+ input: ArtifactWorkerAuthorPendingInput,
916
+ canonicalCommand: Uint8Array,
917
+ ): { bytes: Uint8Array; requestHash: string } {
918
+ try {
919
+ return hashEditableArtifactMutationIntent(canonicalMutationIntent(input, canonicalCommand));
920
+ } catch (error) {
921
+ throw new ArtifactWorkerProtocolError(
922
+ "invalid_intent",
923
+ "pending transaction cannot be represented by canonical OGATX001",
924
+ { cause: error },
925
+ );
926
+ }
927
+ }
928
+
929
+ function transferableOwnedBuffer(bytes: Uint8Array): ArrayBuffer {
930
+ if (
931
+ bytes.buffer instanceof ArrayBuffer &&
932
+ bytes.byteOffset === 0 &&
933
+ bytes.byteLength === bytes.buffer.byteLength
934
+ ) {
935
+ return bytes.buffer;
936
+ }
937
+ return bytes.slice().buffer;
938
+ }
939
+
940
+ function verifyCanonicalPendingIntent(transaction: EditableArtifactPendingTransaction): void {
941
+ let expected: { bytes: Uint8Array; requestHash: string };
942
+ try {
943
+ expected = hashEditableArtifactMutationIntent(
944
+ canonicalMutationIntent(transaction, transaction.commandBytes),
945
+ );
946
+ } catch (error) {
947
+ throw new ArtifactWorkerProtocolError(
948
+ "intent_mismatch",
949
+ "pending transaction metadata is not valid canonical OGATX001 input",
950
+ { cause: error },
951
+ );
952
+ }
953
+ if (
954
+ expected.requestHash !== transaction.requestHash ||
955
+ !bytesEqual(expected.bytes, transaction.intentBytes)
956
+ ) {
957
+ throw runtimeError(
958
+ "intent_mismatch",
959
+ "pending transaction bytes do not bind its exact metadata and command",
960
+ );
961
+ }
962
+ }
963
+
964
+ function assertAdapterCompatibility(
965
+ adapter: ArtifactWorkerKernelAdapter,
966
+ initialization: ArtifactWorkerInitializeInput,
967
+ ): void {
968
+ if (adapter.modality !== initialization.modality) {
969
+ throw runtimeError("modality_mismatch", "loaded kernel modality does not match initialization");
970
+ }
971
+ if (
972
+ adapter.kernelVersion !== initialization.kernelVersion ||
973
+ adapter.protocolVersion !== initialization.protocolVersion ||
974
+ adapter.modelSchemaVersion !== initialization.modelSchemaVersion ||
975
+ adapter.commandVersion !== initialization.commandVersion
976
+ ) {
977
+ throw runtimeError(
978
+ "runtime_identity_mismatch",
979
+ "loaded kernel build/protocol/model identity does not match its package",
980
+ );
981
+ }
982
+ const limits = [
983
+ [adapter.maximumSnapshotBytes, initialization.maximumSnapshotBytes, "snapshot"],
984
+ [adapter.maximumCommandBytes, initialization.maximumCommandBytes, "command"],
985
+ [adapter.maximumIntentBytes, initialization.maximumIntentBytes, "intent"],
986
+ [
987
+ adapter.maximumCommittedTransactionBytes,
988
+ initialization.maximumCommittedTransactionBytes,
989
+ "committed transaction",
990
+ ],
991
+ [adapter.maximumQueryBytes, initialization.maximumQueryBytes, "query"],
992
+ [adapter.maximumQueryResponseBytes, initialization.maximumQueryResponseBytes, "query response"],
993
+ ] as const;
994
+ for (const [advertised, requested, label] of limits) {
995
+ if (advertised < requested) {
996
+ throw runtimeError(
997
+ "unsupported_protocol",
998
+ `loaded collaboration kernel ${label} limit is below the requested Worker limit`,
999
+ );
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+ /**
1005
+ * Outer delivery/index fields remain useful for storage and routing, but every
1006
+ * semantic field duplicated by OGACO must agree exactly before Rust sees it.
1007
+ */
1008
+ function assertCommittedEnvelopeMatchesOuter(
1009
+ transaction: EditableArtifactCommittedTransaction,
1010
+ ): void {
1011
+ if (transaction.modality !== "spreadsheet") {
1012
+ let summary: ReturnType<typeof decodeEditableArtifactSerializedCommit>;
1013
+ try {
1014
+ summary = decodeEditableArtifactSerializedCommit(
1015
+ transaction.committedTransactionBytes,
1016
+ transaction.modality,
1017
+ );
1018
+ } catch (error) {
1019
+ throw new ArtifactWorkerProtocolError(
1020
+ "invalid_committed_transaction",
1021
+ "committed transaction is not canonical OGAST001",
1022
+ { cause: error },
1023
+ );
1024
+ }
1025
+ if (
1026
+ summary.commitProtocolVersion !== transaction.commitProtocolVersion ||
1027
+ summary.transactionId !== transaction.transactionId ||
1028
+ summary.requestHash !== transaction.requestHash ||
1029
+ summary.parentHeadSequence + 1 !== transaction.startSequence ||
1030
+ summary.resultHeadSequence !== transaction.endSequence ||
1031
+ summary.priorNativeRevision !== transaction.priorNativeRevision ||
1032
+ summary.nativeReceipt.revision !== transaction.nativeRevision ||
1033
+ summary.priorStateHash !== transaction.priorStateHash ||
1034
+ summary.stateHash !== transaction.stateHash
1035
+ ) {
1036
+ throw runtimeError(
1037
+ "committed_metadata_mismatch",
1038
+ "outer committed metadata disagrees with its exact OGAST001 envelope",
1039
+ );
1040
+ }
1041
+ return;
1042
+ }
1043
+ let summary: ReturnType<typeof decodeCommittedTransactionSummary>;
1044
+ try {
1045
+ summary = decodeCommittedTransactionSummary(transaction.committedTransactionBytes);
1046
+ } catch (error) {
1047
+ throw new ArtifactWorkerProtocolError(
1048
+ "invalid_committed_transaction",
1049
+ "committed transaction is not canonical OGACO001",
1050
+ { cause: error },
1051
+ );
1052
+ }
1053
+ if (
1054
+ summary.operationProtocolVersion !== transaction.protocolVersion ||
1055
+ summary.transactionId !== transaction.transactionId ||
1056
+ summary.priorStateHash !== transaction.priorStateHash ||
1057
+ summary.stateHash !== transaction.stateHash ||
1058
+ !frontiersEqual(summary.resultingCausalFrontier, transaction.causalFrontier)
1059
+ ) {
1060
+ throw runtimeError(
1061
+ "committed_metadata_mismatch",
1062
+ "outer committed metadata disagrees with its exact OGACO001 envelope",
1063
+ );
1064
+ }
1065
+ }
1066
+
1067
+ function frontiersEqual(
1068
+ left: readonly Readonly<{ replicaId: string; counter: number }>[],
1069
+ right: EditableArtifactCausalFrontier,
1070
+ ): boolean {
1071
+ return (
1072
+ left.length === right.length &&
1073
+ left.every(
1074
+ (entry, index) =>
1075
+ entry.replicaId === right[index]?.replicaId && entry.counter === right[index]?.counter,
1076
+ )
1077
+ );
1078
+ }
1079
+
1080
+ function canonicalMutationIntent(
1081
+ input: ArtifactWorkerAuthorPendingInput | EditableArtifactPendingTransaction,
1082
+ commandBytes: Uint8Array,
1083
+ ) {
1084
+ return {
1085
+ envelopeVersion: EDITABLE_ARTIFACT_INTENT_VERSION,
1086
+ protocolVersion: input.protocolVersion,
1087
+ modelSchemaVersion: input.modelSchemaVersion,
1088
+ commandProtocolVersion: input.commandVersion,
1089
+ artifactId: input.artifactId,
1090
+ clientTransactionId: input.clientTransactionId,
1091
+ replicaId: input.replicaId,
1092
+ replicaCounter: input.replicaCounter,
1093
+ previousLocalTransactionId: input.previousLocalTransactionId,
1094
+ observedHeadSequence: input.observedHeadSequence,
1095
+ causalBase: input.modality === "spreadsheet" ? (input.causalBase ?? []) : [],
1096
+ selectiveUndoOperationIds:
1097
+ input.modality === "spreadsheet" ? (input.selectiveUndoTargets ?? []) : [],
1098
+ commandBytes,
1099
+ } as const;
1100
+ }
1101
+
1102
+ function retainedFrameBytes(frame: ReturnType<typeof decodeArtifactWorkerRpcMessage>): number {
1103
+ let bytes = frame.metadata.byteLength;
1104
+ for (const segment of frame.segments) bytes += segment.byteLength;
1105
+ if (!Number.isSafeInteger(bytes)) {
1106
+ throw runtimeError("size_overflow", "artifact Worker request byte count overflowed");
1107
+ }
1108
+ return bytes;
1109
+ }
1110
+
1111
+ function requireBytesWithin(bytes: Uint8Array, maximum: number, label: string): Uint8Array {
1112
+ if (!(bytes instanceof Uint8Array) || bytes.byteLength === 0) {
1113
+ throw runtimeError("invalid_bytes", `${label} must be non-empty bytes`);
1114
+ }
1115
+ if (bytes.byteLength > maximum)
1116
+ throw runtimeError("limit_exceeded", `${label} exceeds its byte limit`);
1117
+ return bytes;
1118
+ }
1119
+
1120
+ function pendingKey(transaction: EditableArtifactPendingTransaction): string {
1121
+ return `${transaction.clientTransactionId}\0${transaction.requestHash}`;
1122
+ }
1123
+
1124
+ function arraysEqual(left: readonly string[], right: readonly string[]): boolean {
1125
+ return left.length === right.length && left.every((value, index) => value === right[index]);
1126
+ }
1127
+
1128
+ function bytesEqual(left: Uint8Array, right: Uint8Array): boolean {
1129
+ if (left.byteLength !== right.byteLength) return false;
1130
+ for (let index = 0; index < left.byteLength; index += 1) {
1131
+ if (left[index] !== right[index]) return false;
1132
+ }
1133
+ return true;
1134
+ }
1135
+
1136
+ function requireFrontierAdvance(
1137
+ previous: EditableArtifactCausalFrontier,
1138
+ next: EditableArtifactCausalFrontier,
1139
+ ): void {
1140
+ const nextByReplica = new Map(next.map((entry) => [entry.replicaId, entry.counter]));
1141
+ for (const entry of previous) {
1142
+ if ((nextByReplica.get(entry.replicaId) ?? -1) < entry.counter) {
1143
+ throw runtimeError("invalid_frontier", "committed causal frontier regresses");
1144
+ }
1145
+ }
1146
+ }
1147
+
1148
+ type WorkerFailure = { code: string; message: string; retryable: boolean };
1149
+
1150
+ function asWorkerFailure(error: unknown): WorkerFailure {
1151
+ if (error instanceof ArtifactWorkerProtocolError) {
1152
+ return {
1153
+ code: normalizeErrorCode(error.code),
1154
+ message: sanitizeErrorMessage(error.message),
1155
+ retryable: false,
1156
+ };
1157
+ }
1158
+ if (error instanceof Error) {
1159
+ return {
1160
+ code: "kernel_failed",
1161
+ message: sanitizeErrorMessage(error.message || "artifact kernel failed"),
1162
+ retryable: false,
1163
+ };
1164
+ }
1165
+ return { code: "kernel_failed", message: "artifact kernel failed", retryable: false };
1166
+ }
1167
+
1168
+ function asProtocolError(error: unknown): ArtifactWorkerProtocolError {
1169
+ if (error instanceof ArtifactWorkerProtocolError) return error;
1170
+ return new ArtifactWorkerProtocolError(
1171
+ "runtime_failed",
1172
+ error instanceof Error ? error.message : "artifact Worker runtime failed",
1173
+ { cause: error },
1174
+ );
1175
+ }
1176
+
1177
+ function normalizeErrorCode(value: string): string {
1178
+ return /^[a-z][a-z0-9_]{0,127}$/.test(value) ? value : "kernel_failed";
1179
+ }
1180
+
1181
+ function sanitizeErrorMessage(value: string): string {
1182
+ let output = "";
1183
+ for (let index = 0; index < value.length && output.length < 2_000; index += 1) {
1184
+ const code = value.charCodeAt(index);
1185
+ if (code >= 0xd800 && code <= 0xdbff) {
1186
+ const following = value.charCodeAt(index + 1);
1187
+ if (following >= 0xdc00 && following <= 0xdfff) {
1188
+ output += value[index]! + value[index + 1]!;
1189
+ index += 1;
1190
+ } else {
1191
+ output += "\ufffd";
1192
+ }
1193
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
1194
+ output += "\ufffd";
1195
+ } else {
1196
+ output += value[index]!;
1197
+ }
1198
+ }
1199
+ return output || "artifact kernel failed";
1200
+ }
1201
+
1202
+ function runtimeError(code: string, message: string): ArtifactWorkerProtocolError {
1203
+ return new ArtifactWorkerProtocolError(code, message);
1204
+ }