@opengeni/core 0.21.10 → 0.24.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 (116) hide show
  1. package/dist/access/index.d.ts +1 -1
  2. package/dist/application/new-session-drafts.d.ts +1 -1
  3. package/dist/application/session-commands.d.ts +2 -2
  4. package/dist/billing/limits.d.ts +1 -1
  5. package/dist/chunk-6WKPWE5S.js +2380 -0
  6. package/dist/chunk-6WKPWE5S.js.map +1 -0
  7. package/dist/chunk-JJU6I5XD.js +2713 -0
  8. package/dist/chunk-JJU6I5XD.js.map +1 -0
  9. package/dist/chunk-NYPMQ7NO.js +2270 -0
  10. package/dist/chunk-NYPMQ7NO.js.map +1 -0
  11. package/dist/dependencies.d.ts +26 -6
  12. package/dist/domain/capabilities.d.ts +28 -3
  13. package/dist/domain/company-profile-durable-learning-adapter.d.ts +39 -0
  14. package/dist/domain/durable-learning-slack-publication.d.ts +72 -0
  15. package/dist/domain/editable-artifacts/agent-application.d.ts +192 -0
  16. package/dist/domain/editable-artifacts/durable-export-postgres.d.ts +4 -0
  17. package/dist/domain/editable-artifacts/durable-export.d.ts +212 -0
  18. package/dist/domain/editable-artifacts/errors.d.ts +47 -0
  19. package/dist/domain/editable-artifacts/hash.d.ts +11 -0
  20. package/dist/domain/editable-artifacts/in-memory.d.ts +99 -0
  21. package/dist/domain/editable-artifacts/index.d.ts +14 -0
  22. package/dist/domain/editable-artifacts/ports.d.ts +341 -0
  23. package/dist/domain/editable-artifacts/postgres-adapters.d.ts +13 -0
  24. package/dist/domain/editable-artifacts/service.d.ts +105 -0
  25. package/dist/domain/editable-artifacts/snapshot-compaction.d.ts +44 -0
  26. package/dist/domain/editable-artifacts/snapshot-verifier-genesis.d.ts +50 -0
  27. package/dist/domain/editable-artifacts/snapshot-verifier-materialization.d.ts +21 -0
  28. package/dist/domain/editable-artifacts/snapshot-verifier.d.ts +118 -0
  29. package/dist/domain/editable-artifacts/types.d.ts +410 -0
  30. package/dist/domain/memory-slack-delivery.d.ts +19 -0
  31. package/dist/domain/packs.d.ts +29 -1
  32. package/dist/domain/personal-connection-delegations.d.ts +9 -1
  33. package/dist/domain/scheduled-tasks.d.ts +2 -2
  34. package/dist/domain/session-tool-policy.d.ts +2 -2
  35. package/dist/domain/sessions.d.ts +14 -6
  36. package/dist/domain/skill-imports.d.ts +32 -0
  37. package/dist/domain/slack-publication-secret-safety.d.ts +8 -0
  38. package/dist/domain/timeline-annotations.d.ts +5 -0
  39. package/dist/domain/video-generation-capabilities.d.ts +9 -0
  40. package/dist/domain/video-generation.d.ts +47 -0
  41. package/dist/editable-artifact-live/application.d.ts +69 -0
  42. package/dist/editable-artifact-live/errors.d.ts +1 -0
  43. package/dist/editable-artifact-live/index.d.ts +8 -0
  44. package/dist/editable-artifact-live/outbox-dispatcher.d.ts +162 -0
  45. package/dist/editable-artifact-live/ports.d.ts +100 -0
  46. package/dist/editable-artifact-live/server.d.ts +71 -0
  47. package/dist/editable-artifact-live/ticket.d.ts +40 -0
  48. package/dist/editable-artifact-live/types.d.ts +213 -0
  49. package/dist/editable-artifact-live/wire.d.ts +34 -0
  50. package/dist/editable-artifact-live.d.ts +2 -0
  51. package/dist/editable-artifact-live.js +58 -0
  52. package/dist/editable-artifact-live.js.map +1 -0
  53. package/dist/editable-artifacts.d.ts +2 -0
  54. package/dist/editable-artifacts.js +179 -0
  55. package/dist/editable-artifacts.js.map +1 -0
  56. package/dist/index.d.ts +35 -26
  57. package/dist/index.js +2792 -609
  58. package/dist/index.js.map +1 -1
  59. package/dist/managed-session.d.ts +1 -1
  60. package/dist/rigs/index.d.ts +1 -1
  61. package/dist/rigs/provider-images.d.ts +31 -0
  62. package/dist/sandbox/routing.d.ts +1 -0
  63. package/dist/session-authorization.d.ts +1 -1
  64. package/dist/workflow-wake-contract.d.ts +7 -0
  65. package/package.json +18 -10
  66. package/src/application/new-session-drafts.ts +2 -0
  67. package/src/application/session-commands.ts +188 -107
  68. package/src/dependencies.ts +30 -2
  69. package/src/domain/capabilities.ts +604 -220
  70. package/src/domain/company-profile-durable-learning-adapter.ts +221 -0
  71. package/src/domain/durable-learning-slack-publication.ts +202 -0
  72. package/src/domain/editable-artifacts/agent-application.ts +687 -0
  73. package/src/domain/editable-artifacts/durable-export-postgres.ts +139 -0
  74. package/src/domain/editable-artifacts/durable-export.ts +734 -0
  75. package/src/domain/editable-artifacts/errors.ts +115 -0
  76. package/src/domain/editable-artifacts/hash.ts +131 -0
  77. package/src/domain/editable-artifacts/in-memory.ts +1662 -0
  78. package/src/domain/editable-artifacts/index.ts +14 -0
  79. package/src/domain/editable-artifacts/ports.ts +462 -0
  80. package/src/domain/editable-artifacts/postgres-adapters.ts +49 -0
  81. package/src/domain/editable-artifacts/service.ts +2402 -0
  82. package/src/domain/editable-artifacts/snapshot-compaction.ts +246 -0
  83. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +235 -0
  84. package/src/domain/editable-artifacts/snapshot-verifier-materialization.ts +74 -0
  85. package/src/domain/editable-artifacts/snapshot-verifier.ts +687 -0
  86. package/src/domain/editable-artifacts/types.ts +835 -0
  87. package/src/domain/insights.ts +136 -33
  88. package/src/domain/memory-slack-delivery.ts +204 -0
  89. package/src/domain/memory-slack-publication.ts +5 -2
  90. package/src/domain/packs.ts +266 -5
  91. package/src/domain/personal-connection-delegations.ts +97 -6
  92. package/src/domain/scheduled-tasks.ts +155 -29
  93. package/src/domain/session-tool-policy.ts +8 -2
  94. package/src/domain/sessions.ts +274 -131
  95. package/src/domain/skill-imports.ts +350 -0
  96. package/src/domain/slack-publication-secret-safety.ts +42 -0
  97. package/src/domain/timeline-annotations.ts +204 -0
  98. package/src/domain/video-generation-capabilities.ts +69 -0
  99. package/src/domain/video-generation.ts +249 -0
  100. package/src/editable-artifact-live/application.ts +182 -0
  101. package/src/editable-artifact-live/errors.ts +4 -0
  102. package/src/editable-artifact-live/index.ts +8 -0
  103. package/src/editable-artifact-live/outbox-dispatcher.ts +916 -0
  104. package/src/editable-artifact-live/ports.ts +139 -0
  105. package/src/editable-artifact-live/server.ts +1707 -0
  106. package/src/editable-artifact-live/ticket.ts +217 -0
  107. package/src/editable-artifact-live/types.ts +286 -0
  108. package/src/editable-artifact-live/wire.ts +95 -0
  109. package/src/editable-artifact-live.ts +2 -0
  110. package/src/editable-artifacts.ts +2 -0
  111. package/src/index.ts +13 -0
  112. package/src/rigs/index.ts +26 -19
  113. package/src/rigs/provider-images.ts +110 -0
  114. package/src/sandbox/routing.ts +6 -3
  115. package/src/session-authorization.ts +3 -0
  116. package/src/workflow-wake-contract.ts +8 -0
@@ -0,0 +1,2270 @@
1
+ import {
2
+ EDITABLE_ARTIFACT_MAX_COMMITTED_TRANSACTION_BYTES,
3
+ assertIsoTimestamp,
4
+ assertNonnegativeSafeInteger,
5
+ assertPositiveSafeInteger,
6
+ editableArtifactActorKey,
7
+ editableArtifactCausalFrontier,
8
+ editableArtifactClientTransactionId,
9
+ editableArtifactId,
10
+ editableArtifactOutboxId,
11
+ editableArtifactRequestHash,
12
+ editableArtifactScope,
13
+ editableArtifactSnapshotId,
14
+ editableArtifactStateHash,
15
+ editableArtifactTransactionId,
16
+ validateEditableArtifactActor
17
+ } from "./chunk-JJU6I5XD.js";
18
+
19
+ // src/editable-artifact-live/application.ts
20
+ import { EDITABLE_ARTIFACT_KERNEL_VERSION_MAX_BYTES } from "@opengeni/contracts/editable-artifacts";
21
+ var EditableArtifactCompatibilityError = class extends Error {
22
+ code = "unsupported_protocol";
23
+ constructor(message = "Editable artifact runtime is incompatible") {
24
+ super(message);
25
+ this.name = "EditableArtifactCompatibilityError";
26
+ }
27
+ };
28
+ var EditableArtifactApplication = class {
29
+ constructor(dependencies) {
30
+ this.dependencies = dependencies;
31
+ }
32
+ async createArtifact(input) {
33
+ const scope = editableArtifactScope(input.scope);
34
+ validateEditableArtifactActor(input.actor);
35
+ const result = await this.dependencies.domain.createArtifact({
36
+ scope,
37
+ actor: Object.freeze({ ...input.actor }),
38
+ request: {
39
+ idempotencyKey: editableArtifactClientTransactionId(input.idempotencyKey),
40
+ modality: input.modality,
41
+ title: input.title
42
+ },
43
+ ...input.signal ? { signal: input.signal } : {}
44
+ });
45
+ return result.artifact;
46
+ }
47
+ async importArtifact(input) {
48
+ const scope = editableArtifactScope(input.scope);
49
+ validateEditableArtifactActor(input.actor);
50
+ const result = await this.dependencies.domain.importArtifact({
51
+ scope,
52
+ actor: Object.freeze({ ...input.actor }),
53
+ request: {
54
+ idempotencyKey: editableArtifactClientTransactionId(input.idempotencyKey),
55
+ modality: input.modality,
56
+ title: input.title,
57
+ originalImport: input.originalImport,
58
+ snapshot: input.snapshot
59
+ },
60
+ ...input.signal ? { signal: input.signal } : {}
61
+ });
62
+ return result.artifact;
63
+ }
64
+ async readArtifact(input) {
65
+ const scope = editableArtifactScope(input.scope);
66
+ const artifactId = editableArtifactId(input.artifactId);
67
+ validateEditableArtifactActor(input.actor);
68
+ return await this.dependencies.domain.getArtifact({
69
+ scope,
70
+ artifactId,
71
+ actor: Object.freeze({ ...input.actor })
72
+ });
73
+ }
74
+ async mintLiveTicket(input) {
75
+ const artifact = await this.readArtifact(input);
76
+ await this.dependencies.compatibility.assertCompatible({
77
+ artifact,
78
+ protocolVersion: positiveInteger(input.protocolVersion, "protocolVersion"),
79
+ kernelVersion: boundedVersion(input.kernelVersion, "kernelVersion"),
80
+ modelSchemaVersion: positiveInteger(input.modelSchemaVersion, "modelSchemaVersion")
81
+ });
82
+ return await this.dependencies.live.mintTicket({
83
+ scope: artifact.scope,
84
+ artifactId: artifact.id,
85
+ modality: artifact.modality,
86
+ actor: input.actor,
87
+ allowEdit: input.allowEdit
88
+ });
89
+ }
90
+ openLive(input) {
91
+ return this.dependencies.live.openLive(input);
92
+ }
93
+ };
94
+ function positiveInteger(value, label) {
95
+ if (!Number.isSafeInteger(value) || value < 1) {
96
+ throw new TypeError(`${label} must be a positive safe integer`);
97
+ }
98
+ return value;
99
+ }
100
+ function boundedVersion(value, label) {
101
+ const bytes = new TextEncoder().encode(value).byteLength;
102
+ if (bytes < 1 || bytes > EDITABLE_ARTIFACT_KERNEL_VERSION_MAX_BYTES || value.trim() !== value) {
103
+ throw new TypeError(`${label} is malformed`);
104
+ }
105
+ return value;
106
+ }
107
+
108
+ // src/editable-artifact-live/errors.ts
109
+ import {
110
+ EditableArtifactLiveError
111
+ } from "@opengeni/contracts/editable-artifact-live";
112
+
113
+ // src/editable-artifact-live/outbox-dispatcher.ts
114
+ var textEncoder = new TextEncoder();
115
+ var EDITABLE_ARTIFACT_HINT_MAX_BYTES = 16 * 1024;
116
+ var EDITABLE_ARTIFACT_HINT_SUBJECT_PREFIX = "editable_artifacts.v1";
117
+ var DEFAULTS = Object.freeze({
118
+ batchSize: 32,
119
+ concurrency: 8,
120
+ leaseDurationMs: 3e4,
121
+ leaseRenewIntervalMs: 1e4,
122
+ pollIntervalMs: 500,
123
+ publishTimeoutMs: 5e3,
124
+ retryBaseMs: 500,
125
+ retryMaxMs: 6e4,
126
+ retryJitterRatio: 0.2,
127
+ claimBackoffBaseMs: 500,
128
+ claimBackoffMaxMs: 3e4,
129
+ maxHintBytes: EDITABLE_ARTIFACT_HINT_MAX_BYTES
130
+ });
131
+ var EditableArtifactLiveOutboxDispatcher = class {
132
+ constructor(dependencies, options) {
133
+ this.dependencies = dependencies;
134
+ this.options = normalizeOptions(options);
135
+ }
136
+ options;
137
+ activeDispatch = null;
138
+ /**
139
+ * Long-running resilient loop. Abort stops new claims; an already-claimed
140
+ * bounded batch drains before this promise resolves.
141
+ */
142
+ async run(signal) {
143
+ let consecutiveClaimFailures = 0;
144
+ while (!signal.aborted) {
145
+ const summary = await this.dispatchOnce();
146
+ if (summary.claimFailed) consecutiveClaimFailures += 1;
147
+ else consecutiveClaimFailures = 0;
148
+ if (signal.aborted) break;
149
+ const delay = summary.claimFailed ? exponentialDelay(
150
+ consecutiveClaimFailures,
151
+ this.options.claimBackoffBaseMs,
152
+ this.options.claimBackoffMaxMs,
153
+ this.options.retryJitterRatio,
154
+ this.dependencies.random
155
+ ) : summary.claimed === 0 ? this.options.pollIntervalMs : 0;
156
+ if (delay > 0 && !await sleepUntilNextPoll(this.dependencies.scheduler, delay, signal)) {
157
+ break;
158
+ }
159
+ }
160
+ await this.drain();
161
+ }
162
+ /** Concurrent callers share one bounded claim/dispatch pass. */
163
+ dispatchOnce() {
164
+ if (this.activeDispatch) return this.activeDispatch;
165
+ const dispatch = this.dispatchBatch();
166
+ this.activeDispatch = dispatch;
167
+ void dispatch.then(
168
+ () => {
169
+ if (this.activeDispatch === dispatch) this.activeDispatch = null;
170
+ },
171
+ () => {
172
+ if (this.activeDispatch === dispatch) this.activeDispatch = null;
173
+ }
174
+ );
175
+ return dispatch;
176
+ }
177
+ async drain() {
178
+ await this.activeDispatch;
179
+ }
180
+ async dispatchBatch() {
181
+ const summary = mutableSummary();
182
+ let records;
183
+ try {
184
+ records = await this.dependencies.store.claimLiveOutbox({
185
+ owner: this.options.owner,
186
+ leaseDurationMs: this.options.leaseDurationMs,
187
+ limit: this.options.batchSize
188
+ });
189
+ if (!Array.isArray(records) || records.length > this.options.batchSize) {
190
+ throw new TypeError("Outbox store returned an invalid claim batch");
191
+ }
192
+ } catch (error) {
193
+ summary.claimFailed = true;
194
+ this.metric("claim_failed");
195
+ this.warn("Editable artifact outbox claim failed", "claim_failed", error);
196
+ return freezeSummary(summary);
197
+ }
198
+ summary.claimed = records.length;
199
+ if (records.length > 0) this.metric("claimed", records.length);
200
+ let cursor = 0;
201
+ const worker = async () => {
202
+ for (; ; ) {
203
+ const index = cursor;
204
+ cursor += 1;
205
+ const record = records[index];
206
+ if (!record) return;
207
+ await this.dispatchRecord(record, summary);
208
+ }
209
+ };
210
+ await Promise.all(
211
+ Array.from(
212
+ { length: Math.min(records.length, this.options.concurrency) },
213
+ async () => await worker()
214
+ )
215
+ );
216
+ return freezeSummary(summary);
217
+ }
218
+ async dispatchRecord(record, summary) {
219
+ let outboxId;
220
+ try {
221
+ outboxId = validateClaimedRecord(record, this.options.owner);
222
+ } catch (error) {
223
+ await this.deadLetter(record, "invalid_hint", summary, error);
224
+ return;
225
+ }
226
+ let encoded;
227
+ try {
228
+ encoded = encodeEditableArtifactBrokerHint(record, this.options.maxHintBytes);
229
+ } catch (error) {
230
+ await this.deadLetter(
231
+ record,
232
+ error instanceof EditableArtifactHintEncodingError && error.code === "oversized_hint" ? "oversized_hint" : "invalid_hint",
233
+ summary,
234
+ error
235
+ );
236
+ return;
237
+ }
238
+ const publishAbort = new AbortController();
239
+ const renewalAbort = new AbortController();
240
+ let leaseLost = false;
241
+ const renewal = this.renewWhilePublishing(record, renewalAbort.signal, publishAbort, () => {
242
+ leaseLost = true;
243
+ }).catch((error) => {
244
+ leaseLost = true;
245
+ publishAbort.abort();
246
+ this.warn("Editable artifact outbox renewal loop failed", "lease_lost", error);
247
+ });
248
+ let publishError = null;
249
+ let timedOut = false;
250
+ const startedAt = this.dependencies.clock.now().getTime();
251
+ try {
252
+ await withTimeout(
253
+ this.dependencies.broker.publish({
254
+ subject: encoded.subject,
255
+ payload: encoded.payload.slice(),
256
+ signal: publishAbort.signal
257
+ }),
258
+ this.options.publishTimeoutMs,
259
+ this.dependencies.scheduler,
260
+ publishAbort,
261
+ () => {
262
+ timedOut = true;
263
+ }
264
+ );
265
+ } catch (error) {
266
+ publishError = error;
267
+ } finally {
268
+ renewalAbort.abort();
269
+ await renewal;
270
+ this.observePublish(timedOut ? "timed_out" : publishError ? "failed" : "acked", startedAt);
271
+ }
272
+ if (leaseLost) {
273
+ summary.leaseLost += 1;
274
+ this.metric("lease_lost");
275
+ return;
276
+ }
277
+ if (publishError) {
278
+ const errorCode = timedOut ? "publish_timeout" : brokerErrorCode(publishError);
279
+ await this.retry(record, errorCode, summary, publishError);
280
+ return;
281
+ }
282
+ try {
283
+ await this.dependencies.store.renewLiveOutbox({
284
+ outboxId,
285
+ owner: this.options.owner,
286
+ attemptCount: record.attemptCount,
287
+ leaseDurationMs: this.options.leaseDurationMs
288
+ });
289
+ } catch (error) {
290
+ summary.leaseLost += 1;
291
+ this.metric("lease_lost");
292
+ this.warn("Editable artifact outbox lease was lost after broker ACK", "lease_lost", error);
293
+ return;
294
+ }
295
+ try {
296
+ await this.dependencies.store.markLiveOutboxPublished({
297
+ outboxId,
298
+ owner: this.options.owner,
299
+ attemptCount: record.attemptCount
300
+ });
301
+ summary.published += 1;
302
+ this.metric("published");
303
+ } catch (error) {
304
+ summary.markFailed += 1;
305
+ this.metric("mark_failed");
306
+ this.warn("Editable artifact outbox publish mark failed", "mark_failed", error);
307
+ }
308
+ }
309
+ async renewWhilePublishing(record, signal, publishAbort, onLeaseLost) {
310
+ for (; ; ) {
311
+ if (!await sleepUntilNextPoll(
312
+ this.dependencies.scheduler,
313
+ this.options.leaseRenewIntervalMs,
314
+ signal
315
+ )) {
316
+ return;
317
+ }
318
+ try {
319
+ await this.dependencies.store.renewLiveOutbox({
320
+ outboxId: editableArtifactOutboxId(record.outboxId),
321
+ owner: this.options.owner,
322
+ attemptCount: record.attemptCount,
323
+ leaseDurationMs: this.options.leaseDurationMs
324
+ });
325
+ } catch (error) {
326
+ onLeaseLost();
327
+ publishAbort.abort();
328
+ this.warn("Editable artifact outbox lease renewal failed", "lease_lost", error);
329
+ return;
330
+ }
331
+ }
332
+ }
333
+ async retry(record, errorCode, summary, error) {
334
+ const retryDelayMs = exponentialDelay(
335
+ record.attemptCount,
336
+ this.options.retryBaseMs,
337
+ this.options.retryMaxMs,
338
+ this.options.retryJitterRatio,
339
+ this.dependencies.random
340
+ );
341
+ try {
342
+ await this.dependencies.store.retryLiveOutbox({
343
+ outboxId: editableArtifactOutboxId(record.outboxId),
344
+ owner: this.options.owner,
345
+ attemptCount: record.attemptCount,
346
+ retryDelayMs,
347
+ errorCode
348
+ });
349
+ summary.retried += 1;
350
+ this.metric("retried");
351
+ } catch (retryError) {
352
+ this.metric("retry_store_failed");
353
+ this.warn(
354
+ "Editable artifact outbox retry persistence failed",
355
+ "retry_store_failed",
356
+ retryError
357
+ );
358
+ }
359
+ this.warn("Editable artifact hint publish failed", errorCode, error);
360
+ }
361
+ async deadLetter(record, errorCode, summary, error) {
362
+ try {
363
+ await this.dependencies.store.deadLetterLiveOutbox({
364
+ outboxId: editableArtifactOutboxId(record.outboxId),
365
+ owner: this.options.owner,
366
+ attemptCount: record.attemptCount,
367
+ errorCode
368
+ });
369
+ summary.deadLettered += 1;
370
+ this.metric("dead_lettered");
371
+ } catch (deadLetterError) {
372
+ this.metric("dead_letter_store_failed");
373
+ this.warn(
374
+ "Editable artifact outbox dead-letter persistence failed",
375
+ "dead_letter_store_failed",
376
+ deadLetterError
377
+ );
378
+ }
379
+ this.warn("Editable artifact outbox contains an undeliverable hint", errorCode, error);
380
+ }
381
+ metric(outcome, count = 1) {
382
+ try {
383
+ this.dependencies.metrics?.increment(outcome, count);
384
+ } catch {
385
+ }
386
+ }
387
+ observePublish(outcome, startedAt) {
388
+ let elapsed = 0;
389
+ const now = this.dependencies.clock.now().getTime();
390
+ if (Number.isFinite(now) && Number.isFinite(startedAt)) elapsed = Math.max(0, now - startedAt);
391
+ try {
392
+ this.dependencies.metrics?.observePublishSeconds(outcome, elapsed / 1e3);
393
+ } catch {
394
+ }
395
+ }
396
+ warn(message, code, error) {
397
+ try {
398
+ this.dependencies.logger?.warn(message, {
399
+ code,
400
+ errorClass: error instanceof Error ? error.name : "UnknownError"
401
+ });
402
+ } catch {
403
+ }
404
+ }
405
+ };
406
+ var EditableArtifactHintEncodingError = class extends Error {
407
+ constructor(code, message) {
408
+ super(message);
409
+ this.code = code;
410
+ this.name = "EditableArtifactHintEncodingError";
411
+ }
412
+ };
413
+ function encodeEditableArtifactBrokerHint(record, maxBytes = EDITABLE_ARTIFACT_HINT_MAX_BYTES) {
414
+ assertPositiveSafeInteger(maxBytes, "maximum broker hint bytes");
415
+ let hint;
416
+ try {
417
+ const event = record.event;
418
+ const scope = editableArtifactScope(event.scope);
419
+ const artifactId = editableArtifactId(event.artifactId);
420
+ const stateHash = editableArtifactStateHash(event.stateHash);
421
+ if (event.schemaVersion !== 1) throw new TypeError("Unsupported artifact hint schema");
422
+ if (event.kind === "transaction_committed") {
423
+ assertPositiveSafeInteger(event.sequenceEnd, "hint head sequence");
424
+ const common = {
425
+ kind: "head_advanced",
426
+ schemaVersion: 1,
427
+ scope,
428
+ artifactId,
429
+ modality: event.modality,
430
+ headSequence: event.sequenceEnd,
431
+ stateHash
432
+ };
433
+ if (event.modality === "spreadsheet") {
434
+ assertPositiveSafeInteger(
435
+ event.operationProtocolVersion,
436
+ "hint operation protocol version"
437
+ );
438
+ hint = Object.freeze({
439
+ ...common,
440
+ modality: "spreadsheet",
441
+ operationProtocolVersion: event.operationProtocolVersion
442
+ });
443
+ } else {
444
+ assertPositiveSafeInteger(event.commitProtocolVersion, "hint commit protocol version");
445
+ hint = Object.freeze({
446
+ ...common,
447
+ modality: event.modality,
448
+ commitProtocolVersion: event.commitProtocolVersion
449
+ });
450
+ }
451
+ } else if (event.kind === "snapshot_published") {
452
+ assertNonnegativeSafeInteger(event.coveredHeadSequence, "snapshot hint head sequence");
453
+ const common = {
454
+ kind: "snapshot_available",
455
+ schemaVersion: 1,
456
+ scope,
457
+ artifactId,
458
+ modality: event.modality,
459
+ headSequence: event.coveredHeadSequence,
460
+ stateHash,
461
+ snapshotId: editableArtifactSnapshotId(event.snapshotId)
462
+ };
463
+ if (event.modality === "spreadsheet") {
464
+ assertPositiveSafeInteger(
465
+ event.operationProtocolVersion,
466
+ "hint operation protocol version"
467
+ );
468
+ hint = Object.freeze({
469
+ ...common,
470
+ modality: "spreadsheet",
471
+ operationProtocolVersion: event.operationProtocolVersion
472
+ });
473
+ } else {
474
+ hint = Object.freeze({ ...common, modality: event.modality });
475
+ }
476
+ } else {
477
+ throw new TypeError("Unknown editable artifact outbox event");
478
+ }
479
+ const payload = textEncoder.encode(JSON.stringify(hint));
480
+ if (payload.byteLength > maxBytes) {
481
+ throw new EditableArtifactHintEncodingError(
482
+ "oversized_hint",
483
+ "Editable artifact broker hint exceeds its byte limit"
484
+ );
485
+ }
486
+ return Object.freeze({
487
+ subject: editableArtifactLiveHintSubject(scope, artifactId),
488
+ payload,
489
+ hint
490
+ });
491
+ } catch (error) {
492
+ if (error instanceof EditableArtifactHintEncodingError) throw error;
493
+ throw new EditableArtifactHintEncodingError(
494
+ "invalid_hint",
495
+ "Editable artifact outbox event cannot be projected to a broker hint"
496
+ );
497
+ }
498
+ }
499
+ function editableArtifactLiveHintSubject(scope, artifactIdInput) {
500
+ const normalized = editableArtifactScope(scope);
501
+ const artifactId = editableArtifactId(artifactIdInput);
502
+ const subject = [
503
+ EDITABLE_ARTIFACT_HINT_SUBJECT_PREFIX,
504
+ base64Url(textEncoder.encode(normalized.accountId)),
505
+ base64Url(textEncoder.encode(normalized.workspaceId)),
506
+ artifactId
507
+ ].join(".");
508
+ if (textEncoder.encode(subject).byteLength > 1024) {
509
+ throw new EditableArtifactHintEncodingError(
510
+ "invalid_hint",
511
+ "Editable artifact hint subject exceeds its byte limit"
512
+ );
513
+ }
514
+ return subject;
515
+ }
516
+ function decodeEditableArtifactBrokerHint(payload, expected, maxBytes = EDITABLE_ARTIFACT_HINT_MAX_BYTES) {
517
+ if (!(payload instanceof Uint8Array) || payload.byteLength < 2 || payload.byteLength > maxBytes) {
518
+ throw new EditableArtifactHintEncodingError(
519
+ "oversized_hint",
520
+ "Editable artifact broker hint exceeds its byte limit"
521
+ );
522
+ }
523
+ try {
524
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(payload);
525
+ const value = JSON.parse(text);
526
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
527
+ throw new TypeError("hint must be an object");
528
+ }
529
+ const record = value;
530
+ if (JSON.stringify(record) !== text) throw new TypeError("hint must be canonical JSON");
531
+ const kind = record.kind;
532
+ const modality = record.modality;
533
+ if (modality !== "spreadsheet" && modality !== "document" && modality !== "presentation") {
534
+ throw new TypeError("hint modality invalid");
535
+ }
536
+ const keys = Object.keys(record);
537
+ const expectedKeys = kind === "head_advanced" ? modality === "spreadsheet" ? [
538
+ "kind",
539
+ "schemaVersion",
540
+ "scope",
541
+ "artifactId",
542
+ "modality",
543
+ "headSequence",
544
+ "stateHash",
545
+ "operationProtocolVersion"
546
+ ] : [
547
+ "kind",
548
+ "schemaVersion",
549
+ "scope",
550
+ "artifactId",
551
+ "modality",
552
+ "headSequence",
553
+ "stateHash",
554
+ "commitProtocolVersion"
555
+ ] : modality === "spreadsheet" ? [
556
+ "kind",
557
+ "schemaVersion",
558
+ "scope",
559
+ "artifactId",
560
+ "modality",
561
+ "headSequence",
562
+ "stateHash",
563
+ "snapshotId",
564
+ "operationProtocolVersion"
565
+ ] : [
566
+ "kind",
567
+ "schemaVersion",
568
+ "scope",
569
+ "artifactId",
570
+ "modality",
571
+ "headSequence",
572
+ "stateHash",
573
+ "snapshotId"
574
+ ];
575
+ if (keys.length !== expectedKeys.length || keys.some((key, index) => key !== expectedKeys[index])) {
576
+ throw new TypeError("hint fields invalid");
577
+ }
578
+ if (record.schemaVersion !== 1) throw new TypeError("hint schema invalid");
579
+ const scope = editableArtifactScope(record.scope);
580
+ const artifactId = editableArtifactId(record.artifactId);
581
+ if (scope.accountId !== expected.scope.accountId || scope.workspaceId !== expected.scope.workspaceId || artifactId !== editableArtifactId(expected.artifactId)) {
582
+ throw new TypeError("hint authority mismatch");
583
+ }
584
+ assertNonnegativeSafeInteger(record.headSequence, "hint head sequence");
585
+ if (modality === "spreadsheet") {
586
+ assertPositiveSafeInteger(
587
+ record.operationProtocolVersion,
588
+ "hint operation protocol version"
589
+ );
590
+ } else if (kind === "head_advanced") {
591
+ assertPositiveSafeInteger(
592
+ record.commitProtocolVersion,
593
+ "hint commit protocol version"
594
+ );
595
+ }
596
+ editableArtifactStateHash(record.stateHash);
597
+ if (kind === "head_advanced") return Object.freeze(record);
598
+ if (kind === "snapshot_available") {
599
+ editableArtifactSnapshotId(record.snapshotId);
600
+ return Object.freeze(record);
601
+ }
602
+ throw new TypeError("hint kind invalid");
603
+ } catch (error) {
604
+ if (error instanceof EditableArtifactHintEncodingError) throw error;
605
+ throw new EditableArtifactHintEncodingError(
606
+ "invalid_hint",
607
+ "Editable artifact broker hint is invalid"
608
+ );
609
+ }
610
+ }
611
+ var MathEditableArtifactOutboxRandom = class {
612
+ unit() {
613
+ return Math.random();
614
+ }
615
+ };
616
+ function validateClaimedRecord(record, owner) {
617
+ const outboxId = editableArtifactOutboxId(record.outboxId);
618
+ if (record.state !== "publishing") throw new TypeError("Claimed outbox row is not publishing");
619
+ if (record.leaseOwner !== owner) throw new TypeError("Claimed outbox row has another owner");
620
+ assertPositiveSafeInteger(record.attemptCount, "outbox attempt count");
621
+ if (record.leaseExpiresAt === null) throw new TypeError("Claimed outbox row has no lease expiry");
622
+ return outboxId;
623
+ }
624
+ function normalizeOptions(options) {
625
+ const owner = boundedOwner(options.owner);
626
+ const normalized = {
627
+ owner,
628
+ batchSize: options.batchSize ?? DEFAULTS.batchSize,
629
+ concurrency: options.concurrency ?? DEFAULTS.concurrency,
630
+ leaseDurationMs: options.leaseDurationMs ?? DEFAULTS.leaseDurationMs,
631
+ leaseRenewIntervalMs: options.leaseRenewIntervalMs ?? DEFAULTS.leaseRenewIntervalMs,
632
+ pollIntervalMs: options.pollIntervalMs ?? DEFAULTS.pollIntervalMs,
633
+ publishTimeoutMs: options.publishTimeoutMs ?? DEFAULTS.publishTimeoutMs,
634
+ retryBaseMs: options.retryBaseMs ?? DEFAULTS.retryBaseMs,
635
+ retryMaxMs: options.retryMaxMs ?? DEFAULTS.retryMaxMs,
636
+ retryJitterRatio: options.retryJitterRatio ?? DEFAULTS.retryJitterRatio,
637
+ claimBackoffBaseMs: options.claimBackoffBaseMs ?? DEFAULTS.claimBackoffBaseMs,
638
+ claimBackoffMaxMs: options.claimBackoffMaxMs ?? DEFAULTS.claimBackoffMaxMs,
639
+ maxHintBytes: options.maxHintBytes ?? DEFAULTS.maxHintBytes
640
+ };
641
+ for (const [label, value] of Object.entries(normalized)) {
642
+ if (label === "owner" || label === "retryJitterRatio") continue;
643
+ if (typeof value !== "number") throw new TypeError(`${label} must be numeric`);
644
+ assertPositiveSafeInteger(value, label);
645
+ }
646
+ if (normalized.batchSize > 256) throw new TypeError("outbox batch size exceeds 256");
647
+ if (normalized.concurrency > normalized.batchSize)
648
+ throw new TypeError("outbox concurrency exceeds batch size");
649
+ if (normalized.leaseDurationMs < 5e3 || normalized.leaseDurationMs > 3e5)
650
+ throw new TypeError("outbox lease duration must be 5-300 seconds");
651
+ if (normalized.leaseRenewIntervalMs * 2 >= normalized.leaseDurationMs)
652
+ throw new TypeError("outbox lease renewal interval must be less than half the lease");
653
+ if (normalized.publishTimeoutMs >= normalized.leaseDurationMs)
654
+ throw new TypeError("outbox publish timeout must be shorter than the lease");
655
+ if (normalized.retryBaseMs > normalized.retryMaxMs)
656
+ throw new TypeError("outbox retry base exceeds retry maximum");
657
+ if (normalized.claimBackoffBaseMs > normalized.claimBackoffMaxMs)
658
+ throw new TypeError("outbox claim backoff base exceeds its maximum");
659
+ if (!Number.isFinite(normalized.retryJitterRatio) || normalized.retryJitterRatio < 0 || normalized.retryJitterRatio > 1) {
660
+ throw new TypeError("outbox retry jitter ratio must be between zero and one");
661
+ }
662
+ if (normalized.maxHintBytes > EDITABLE_ARTIFACT_HINT_MAX_BYTES)
663
+ throw new TypeError("outbox hint byte limit exceeds the protocol maximum");
664
+ return Object.freeze(normalized);
665
+ }
666
+ function boundedOwner(value) {
667
+ const bytes = textEncoder.encode(value).byteLength;
668
+ if (bytes < 1 || bytes > 200 || value.trim() !== value || !/^[A-Za-z0-9._:-]+$/u.test(value)) {
669
+ throw new TypeError("outbox owner is malformed");
670
+ }
671
+ return value;
672
+ }
673
+ function exponentialDelay(attempt, base, maximum, jitterRatio, random) {
674
+ const exponent = Math.min(30, Math.max(0, attempt - 1));
675
+ const withoutJitter = Math.min(maximum, base * 2 ** exponent);
676
+ const sample = random.unit();
677
+ const unit = Number.isFinite(sample) && sample >= 0 && sample < 1 ? sample : 0.5;
678
+ const jitter = Math.floor(withoutJitter * jitterRatio * unit);
679
+ return Math.min(maximum, withoutJitter + jitter);
680
+ }
681
+ async function withTimeout(operation, timeoutMs, scheduler, operationAbort, onTimeout) {
682
+ const timeoutAbort = new AbortController();
683
+ try {
684
+ await Promise.race([
685
+ operation,
686
+ scheduler.sleep(timeoutMs, timeoutAbort.signal).then(() => {
687
+ onTimeout();
688
+ operationAbort.abort();
689
+ throw new Error("Editable artifact broker publish timed out");
690
+ })
691
+ ]);
692
+ } finally {
693
+ timeoutAbort.abort();
694
+ }
695
+ }
696
+ async function sleepUntilNextPoll(scheduler, milliseconds, signal) {
697
+ try {
698
+ await scheduler.sleep(milliseconds, signal);
699
+ return true;
700
+ } catch (error) {
701
+ if (signal.aborted) return false;
702
+ throw error;
703
+ }
704
+ }
705
+ function brokerErrorCode(error) {
706
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "broker_backpressure") {
707
+ return "broker_backpressure";
708
+ }
709
+ return "broker_unavailable";
710
+ }
711
+ function mutableSummary() {
712
+ return {
713
+ claimed: 0,
714
+ published: 0,
715
+ retried: 0,
716
+ deadLettered: 0,
717
+ leaseLost: 0,
718
+ markFailed: 0,
719
+ claimFailed: false
720
+ };
721
+ }
722
+ function freezeSummary(summary) {
723
+ return Object.freeze({ ...summary });
724
+ }
725
+ function base64Url(bytes) {
726
+ let binary = "";
727
+ for (const byte of bytes) binary += String.fromCharCode(byte);
728
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
729
+ }
730
+
731
+ // src/editable-artifact-live/server.ts
732
+ import {
733
+ EDITABLE_ARTIFACT_COMMAND_MAX_BYTES,
734
+ EDITABLE_ARTIFACT_INTENT_MAX_BYTES,
735
+ EDITABLE_ARTIFACT_KERNEL_VERSION_MAX_BYTES as EDITABLE_ARTIFACT_KERNEL_VERSION_MAX_BYTES2,
736
+ EDITABLE_ARTIFACT_PRODUCT_MAX_SNAPSHOT_BYTES
737
+ } from "@opengeni/contracts/editable-artifacts";
738
+ import { decodeCommittedTransactionSummary } from "@opengeni/contracts/editable-artifact-committed-transaction";
739
+ import { decodeEditableArtifactSerializedCommit } from "@opengeni/contracts/editable-artifact-serialized-commit";
740
+
741
+ // src/editable-artifact-live/types.ts
742
+ var EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION = 1;
743
+
744
+ // src/editable-artifact-live/ticket.ts
745
+ var DEFAULT_TTL_MS = 3e4;
746
+ var MAX_TTL_MS = 6e4;
747
+ var MAX_TOKEN_BYTES = 4096;
748
+ var EditableArtifactLiveTicketAuthority = class {
749
+ constructor(dependencies) {
750
+ this.dependencies = dependencies;
751
+ this.ttlMs = dependencies.ttlMs ?? DEFAULT_TTL_MS;
752
+ if (!Number.isSafeInteger(this.ttlMs) || this.ttlMs < 1e3 || this.ttlMs > MAX_TTL_MS) {
753
+ throw new TypeError(`ticket ttl must be 1000-${MAX_TTL_MS} milliseconds`);
754
+ }
755
+ this.protocolVersion = dependencies.protocolVersion ?? EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION;
756
+ if (!Number.isSafeInteger(this.protocolVersion) || this.protocolVersion < 1) {
757
+ throw new TypeError("protocol version must be a positive safe integer");
758
+ }
759
+ }
760
+ ttlMs;
761
+ protocolVersion;
762
+ async mint(input) {
763
+ const scope = editableArtifactScope(input.scope);
764
+ const artifactId = editableArtifactId(input.artifactId);
765
+ const modality = assertModality(input.modality);
766
+ validateEditableArtifactActor(input.actor);
767
+ if (typeof input.allowEdit !== "boolean") {
768
+ throw new TypeError("ticket allowEdit must be boolean");
769
+ }
770
+ const actor = Object.freeze({ ...input.actor });
771
+ const decision = await this.dependencies.authorization.authorize({
772
+ scope,
773
+ artifactId,
774
+ actor,
775
+ permission: "read"
776
+ });
777
+ if (!decision.allowed) {
778
+ throw new EditableArtifactLiveError(
779
+ "permission_changed",
780
+ "Editable artifact read permission denied"
781
+ );
782
+ }
783
+ const token = this.dependencies.tokens.randomOpaqueToken();
784
+ assertToken(token);
785
+ const tokenDigest = await this.dependencies.tokens.digestOpaqueToken(token);
786
+ assertDigest(tokenDigest);
787
+ const issuedAtDate = this.dependencies.clock.now();
788
+ if (!Number.isFinite(issuedAtDate.getTime()))
789
+ throw new TypeError("clock returned an invalid date");
790
+ const issuedAt = issuedAtDate.toISOString();
791
+ const expiresAt = new Date(issuedAtDate.getTime() + this.ttlMs).toISOString();
792
+ const record = Object.freeze({
793
+ tokenDigest,
794
+ scope,
795
+ artifactId,
796
+ modality,
797
+ actor,
798
+ allowEdit: input.allowEdit,
799
+ protocolVersion: this.protocolVersion,
800
+ issuedAt,
801
+ expiresAt
802
+ });
803
+ await this.dependencies.tickets.put(record);
804
+ return Object.freeze({
805
+ artifactId,
806
+ modality,
807
+ replicaId: actor.replicaId,
808
+ token,
809
+ expiresAt,
810
+ protocolVersion: this.protocolVersion
811
+ });
812
+ }
813
+ async consume(input) {
814
+ assertToken(input.token);
815
+ const artifactId = editableArtifactId(input.artifactId);
816
+ const digest = await this.dependencies.tokens.digestOpaqueToken(input.token);
817
+ assertDigest(digest);
818
+ const record = await this.dependencies.tickets.consume(digest);
819
+ if (!record) {
820
+ throw new EditableArtifactLiveError(
821
+ "ticket_replayed",
822
+ "Live ticket is invalid or was already consumed"
823
+ );
824
+ }
825
+ validateTicketRecord(record);
826
+ if (Date.parse(record.expiresAt) <= this.dependencies.clock.now().getTime()) {
827
+ throw new EditableArtifactLiveError("ticket_expired", "Live ticket has expired", {
828
+ retryable: true
829
+ });
830
+ }
831
+ if (record.artifactId !== artifactId) {
832
+ throw new EditableArtifactLiveError("invalid_ticket", "Live ticket scope does not match");
833
+ }
834
+ if (record.protocolVersion !== input.protocolVersion || input.protocolVersion !== this.protocolVersion) {
835
+ throw new EditableArtifactLiveError(
836
+ "protocol_mismatch",
837
+ "Editable artifact live protocol is unsupported"
838
+ );
839
+ }
840
+ return Object.freeze({
841
+ ...record,
842
+ scope: editableArtifactScope(record.scope),
843
+ actor: Object.freeze({ ...record.actor })
844
+ });
845
+ }
846
+ };
847
+ var InMemoryEditableArtifactLiveTicketStore = class {
848
+ records = /* @__PURE__ */ new Map();
849
+ async put(record) {
850
+ if (this.records.has(record.tokenDigest)) throw new Error("Live ticket digest collision");
851
+ this.records.set(record.tokenDigest, record);
852
+ }
853
+ async consume(tokenDigest) {
854
+ const record = this.records.get(tokenDigest) ?? null;
855
+ if (record) this.records.delete(tokenDigest);
856
+ return record;
857
+ }
858
+ };
859
+ var WebCryptoEditableArtifactLiveTokens = class {
860
+ randomOpaqueToken() {
861
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
862
+ let binary = "";
863
+ for (const byte of bytes) binary += String.fromCharCode(byte);
864
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
865
+ }
866
+ async digestOpaqueToken(token) {
867
+ const digest = new Uint8Array(
868
+ await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token))
869
+ );
870
+ return `sha256:${[...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
871
+ }
872
+ };
873
+ function validateTicketRecord(record) {
874
+ assertDigest(record.tokenDigest);
875
+ editableArtifactScope(record.scope);
876
+ editableArtifactId(record.artifactId);
877
+ assertModality(record.modality);
878
+ validateEditableArtifactActor(record.actor);
879
+ if (typeof record.allowEdit !== "boolean") {
880
+ throw new TypeError("ticket allowEdit is invalid");
881
+ }
882
+ assertIsoTimestamp(record.issuedAt, "ticket issuedAt");
883
+ assertIsoTimestamp(record.expiresAt, "ticket expiresAt");
884
+ }
885
+ function assertModality(value) {
886
+ if (value !== "document" && value !== "spreadsheet" && value !== "presentation") {
887
+ throw new TypeError("ticket modality is invalid");
888
+ }
889
+ return value;
890
+ }
891
+ function assertToken(token) {
892
+ const bytes = new TextEncoder().encode(token).byteLength;
893
+ if (bytes < 32 || bytes > MAX_TOKEN_BYTES || !/^[A-Za-z0-9._~-]+$/u.test(token)) {
894
+ throw new EditableArtifactLiveError("invalid_ticket", "Live ticket is malformed");
895
+ }
896
+ }
897
+ function assertDigest(value) {
898
+ if (!/^sha256:[0-9a-f]{64}$/u.test(value)) throw new TypeError("ticket digest is malformed");
899
+ }
900
+
901
+ // src/editable-artifact-live/server.ts
902
+ var textEncoder2 = new TextEncoder();
903
+ var strictDecoder = new TextDecoder("utf-8", { fatal: true });
904
+ var DEFAULT_MAX_CLIENT_FRAME_BYTES = 5 * 1024 * 1024 + 64 * 1024;
905
+ var DEFAULT_MAX_OUTBOUND_FRAME_BYTES = 8 * 1024 * 1024 + 64 * 1024;
906
+ var DEFAULT_MAX_SNAPSHOT_BYTES = EDITABLE_ARTIFACT_PRODUCT_MAX_SNAPSHOT_BYTES;
907
+ var DEFAULT_SNAPSHOT_CHUNK_BYTES = 256 * 1024;
908
+ var DEFAULT_MAX_IN_FLIGHT_TRANSACTIONS = 256;
909
+ var DEFAULT_MAX_IN_FLIGHT_BYTES = 32 * 1024 * 1024;
910
+ var DEFAULT_REPLAY_PAGE_TRANSACTIONS = 256;
911
+ var DEFAULT_REPLAY_PAGE_BYTES = 32 * 1024 * 1024;
912
+ var DEFAULT_MAX_SOCKET_BUFFERED_BYTES = 16 * 1024 * 1024;
913
+ var DEFAULT_MAX_INBOUND_QUEUE_FRAMES = 64;
914
+ var DEFAULT_MAX_INBOUND_QUEUE_BYTES = 12 * 1024 * 1024;
915
+ var DEFAULT_RECONCILE_INTERVAL_MS = 5e3;
916
+ var DEFAULT_REAUTHORIZE_INTERVAL_MS = 15e3;
917
+ var DEFAULT_ACK_TIMEOUT_MS = 3e4;
918
+ var DEFAULT_SEND_TIMEOUT_MS = 1e4;
919
+ var MAX_IDENTIFIER_BYTES = 512;
920
+ var MAX_CAUSAL_ACTORS = 1024;
921
+ var EditableArtifactLiveServer = class {
922
+ constructor(dependencies, options = {}) {
923
+ this.dependencies = dependencies;
924
+ this.options = normalizeOptions2(options);
925
+ this.ticketAuthority = new EditableArtifactLiveTicketAuthority({
926
+ authorization: dependencies.authorization,
927
+ tickets: dependencies.tickets,
928
+ tokens: dependencies.tokens,
929
+ clock: dependencies.clock,
930
+ protocolVersion: this.options.protocolVersion,
931
+ ...this.options.ticketTtlMs === void 0 ? {} : { ttlMs: this.options.ticketTtlMs }
932
+ });
933
+ }
934
+ options;
935
+ ticketAuthority;
936
+ mintTicket(input) {
937
+ return this.ticketAuthority.mint(input);
938
+ }
939
+ async openLive(input) {
940
+ const artifactId = editableArtifactId(input.artifactId);
941
+ const ticket = await this.ticketAuthority.consume({
942
+ token: input.token,
943
+ artifactId,
944
+ protocolVersion: input.protocolVersion
945
+ });
946
+ validateResume(input.resume, ticket.modality);
947
+ const streamEpoch = `live_${this.dependencies.tokens.randomOpaqueToken()}`;
948
+ boundedString(streamEpoch, "stream epoch", MAX_IDENTIFIER_BYTES);
949
+ const session = new EditableArtifactLiveSessionImpl({
950
+ dependencies: this.dependencies,
951
+ options: this.options,
952
+ ticket,
953
+ streamEpoch,
954
+ resume: input.resume,
955
+ sink: input.sink,
956
+ ...input.signal === void 0 ? {} : { parentSignal: input.signal }
957
+ });
958
+ await session.start();
959
+ return session;
960
+ }
961
+ };
962
+ var EditableArtifactLiveSessionImpl = class {
963
+ artifactId;
964
+ modality;
965
+ streamEpoch;
966
+ closed;
967
+ dependencies;
968
+ options;
969
+ ticket;
970
+ resume;
971
+ sink;
972
+ abort = new AbortController();
973
+ boundaries = /* @__PURE__ */ new Map();
974
+ inFlight = /* @__PURE__ */ new Map();
975
+ resolveClosed;
976
+ workTail = Promise.resolve();
977
+ releaseHints = null;
978
+ releaseInvalidations = null;
979
+ detachParent = null;
980
+ maintenance = null;
981
+ closedValue = null;
982
+ bootstrapping = true;
983
+ reconcileRequested = false;
984
+ reconcileScheduled = false;
985
+ reauthorizationRequested = false;
986
+ reauthorizationScheduled = false;
987
+ sentSequence = 0;
988
+ sentStateHash = null;
989
+ ackedSequence = 0;
990
+ ackedStateHash = null;
991
+ targetHead = 0;
992
+ inFlightBytes = 0;
993
+ oldestInFlightAt = 0;
994
+ writable = false;
995
+ authorizationRevision = 0;
996
+ queuedInboundFrames = 0;
997
+ queuedInboundBytes = 0;
998
+ constructor(input) {
999
+ this.dependencies = input.dependencies;
1000
+ this.options = input.options;
1001
+ this.ticket = input.ticket;
1002
+ this.artifactId = input.ticket.artifactId;
1003
+ this.modality = input.ticket.modality;
1004
+ this.streamEpoch = input.streamEpoch;
1005
+ this.resume = input.resume;
1006
+ this.sink = input.sink;
1007
+ this.closed = new Promise((resolve) => {
1008
+ this.resolveClosed = resolve;
1009
+ });
1010
+ if (input.parentSignal) {
1011
+ const close = () => void this.close("closed");
1012
+ if (input.parentSignal.aborted) close();
1013
+ else {
1014
+ input.parentSignal.addEventListener("abort", close, { once: true });
1015
+ this.detachParent = () => input.parentSignal.removeEventListener("abort", close);
1016
+ }
1017
+ }
1018
+ }
1019
+ async start() {
1020
+ try {
1021
+ this.assertOpen();
1022
+ this.authorizationRevision = await this.requireRead();
1023
+ this.assertOpen();
1024
+ const releaseHints = await this.dependencies.hints.subscribe({
1025
+ scope: this.ticket.scope,
1026
+ artifactId: this.artifactId,
1027
+ onHint: () => this.requestReconcile(),
1028
+ onReconnect: () => this.requestReconcile()
1029
+ });
1030
+ if (this.closedValue) {
1031
+ safeRelease(releaseHints);
1032
+ this.assertOpen();
1033
+ }
1034
+ this.releaseHints = releaseHints;
1035
+ const releaseInvalidations = await this.dependencies.invalidations.subscribe({
1036
+ scope: this.ticket.scope,
1037
+ artifactId: this.artifactId,
1038
+ actor: this.ticket.actor,
1039
+ onInvalidated: () => this.requestReauthorization()
1040
+ });
1041
+ if (this.closedValue) {
1042
+ safeRelease(releaseInvalidations);
1043
+ this.assertOpen();
1044
+ }
1045
+ this.releaseInvalidations = releaseInvalidations;
1046
+ this.authorizationRevision = Math.max(this.authorizationRevision, await this.requireRead());
1047
+ this.assertOpen();
1048
+ let editDecision = await this.checkPermission("edit");
1049
+ if (editDecision.revision > this.authorizationRevision) {
1050
+ this.authorizationRevision = editDecision.revision;
1051
+ await this.requireRead();
1052
+ } else if (editDecision.revision < this.authorizationRevision) {
1053
+ editDecision = await this.checkPermission("edit");
1054
+ }
1055
+ this.writable = editDecision.revision === this.authorizationRevision && editDecision.allowed;
1056
+ this.assertOpen();
1057
+ const bootstrap = await this.dependencies.read.readBootstrap({
1058
+ scope: this.ticket.scope,
1059
+ artifactId: this.artifactId,
1060
+ resume: this.resume,
1061
+ protocolVersion: this.options.protocolVersion
1062
+ });
1063
+ this.assertOpen();
1064
+ validateBootstrap(bootstrap, this.artifactId, this.modality, this.resume, this.options);
1065
+ this.targetHead = bootstrap.headSequence;
1066
+ await this.send({
1067
+ type: "open",
1068
+ protocolVersion: this.options.protocolVersion,
1069
+ artifactId: this.artifactId,
1070
+ streamEpoch: this.streamEpoch,
1071
+ modality: this.modality,
1072
+ writable: this.writable,
1073
+ headSequence: bootstrap.headSequence,
1074
+ minimumReplaySequence: bootstrap.minimumReplaySequence,
1075
+ maxClientFrameBytes: this.options.maxClientFrameBytes,
1076
+ maxCommandBytes: Math.min(
1077
+ EDITABLE_ARTIFACT_COMMAND_MAX_BYTES,
1078
+ Math.max(0, this.options.maxClientFrameBytes - 64 * 1024)
1079
+ ),
1080
+ maxIntentBytes: Math.min(
1081
+ EDITABLE_ARTIFACT_INTENT_MAX_BYTES,
1082
+ Math.max(0, this.options.maxClientFrameBytes - 64 * 1024)
1083
+ ),
1084
+ maxCommittedTransactionBytes: Math.min(
1085
+ EDITABLE_ARTIFACT_MAX_COMMITTED_TRANSACTION_BYTES,
1086
+ Math.max(0, this.options.maxOutboundFrameBytes - 64 * 1024)
1087
+ ),
1088
+ maxSnapshotBytes: this.options.maxSnapshotBytes,
1089
+ maxInFlightTransactions: this.options.maxInFlightTransactions,
1090
+ maxInFlightBytes: this.options.maxInFlightBytes
1091
+ });
1092
+ await this.applyBootstrap(bootstrap);
1093
+ await this.reconcileFromDurableHead();
1094
+ await this.sendBarrier();
1095
+ this.bootstrapping = false;
1096
+ if (this.reconcileRequested) this.requestReconcile();
1097
+ this.maintenance = this.runMaintenance().catch((error) => {
1098
+ if (!this.abort.signal.aborted) void this.fail(error);
1099
+ });
1100
+ } catch (error) {
1101
+ await this.fail(error);
1102
+ throw error;
1103
+ }
1104
+ }
1105
+ receive(bytes) {
1106
+ if (this.closedValue)
1107
+ return Promise.reject(new EditableArtifactLiveError("closed", "Live session is closed"));
1108
+ if (!(bytes instanceof Uint8Array)) {
1109
+ return Promise.reject(new TypeError("Live frame must be Uint8Array"));
1110
+ }
1111
+ if (bytes.byteLength > this.options.maxClientFrameBytes) {
1112
+ const error = new EditableArtifactLiveError(
1113
+ "oversized_frame",
1114
+ "Client live frame exceeds the byte limit"
1115
+ );
1116
+ void this.fail(error);
1117
+ return Promise.reject(error);
1118
+ }
1119
+ let frame;
1120
+ try {
1121
+ frame = decodeClientFrame(
1122
+ bytes,
1123
+ this.artifactId,
1124
+ this.streamEpoch,
1125
+ this.options.protocolVersion
1126
+ );
1127
+ } catch (error) {
1128
+ const failure = error instanceof EditableArtifactLiveError ? error : new EditableArtifactLiveError("invalid_frame", "Client live frame is malformed", {
1129
+ cause: error
1130
+ });
1131
+ void this.fail(failure);
1132
+ return Promise.reject(failure);
1133
+ }
1134
+ return this.enqueueInbound(bytes.byteLength, async () => {
1135
+ await this.applyAcknowledgement(frame);
1136
+ });
1137
+ }
1138
+ acknowledge(frame) {
1139
+ if (this.closedValue) {
1140
+ return Promise.reject(new EditableArtifactLiveError("closed", "Live session is closed"));
1141
+ }
1142
+ let normalized;
1143
+ try {
1144
+ normalized = validateAppliedFrame(
1145
+ frame,
1146
+ this.artifactId,
1147
+ this.streamEpoch,
1148
+ this.options.protocolVersion
1149
+ );
1150
+ } catch (error) {
1151
+ const failure = error instanceof EditableArtifactLiveError ? error : new EditableArtifactLiveError("invalid_frame", "Client live ACK is malformed", {
1152
+ cause: error
1153
+ });
1154
+ void this.fail(failure);
1155
+ return Promise.reject(failure);
1156
+ }
1157
+ return this.enqueueInbound(256, async () => {
1158
+ await this.applyAcknowledgement(normalized);
1159
+ });
1160
+ }
1161
+ submitIntent(input) {
1162
+ if (this.closedValue) {
1163
+ return Promise.reject(new EditableArtifactLiveError("closed", "Live session is closed"));
1164
+ }
1165
+ if (input.protocolVersion !== this.options.protocolVersion) {
1166
+ const error = new EditableArtifactLiveError(
1167
+ "protocol_mismatch",
1168
+ "Mutation protocol mismatch"
1169
+ );
1170
+ void this.fail(error);
1171
+ return Promise.reject(error);
1172
+ }
1173
+ let inputArtifactId;
1174
+ try {
1175
+ inputArtifactId = editableArtifactId(input.artifactId);
1176
+ } catch (cause) {
1177
+ const error = new EditableArtifactLiveError(
1178
+ "invalid_frame",
1179
+ "Mutation artifact id is malformed",
1180
+ { cause }
1181
+ );
1182
+ void this.fail(error);
1183
+ return Promise.reject(error);
1184
+ }
1185
+ if (inputArtifactId !== this.artifactId) {
1186
+ const error = new EditableArtifactLiveError("invalid_frame", "Mutation artifact mismatch");
1187
+ void this.fail(error);
1188
+ return Promise.reject(error);
1189
+ }
1190
+ if (input.streamEpoch !== this.streamEpoch) {
1191
+ const error = new EditableArtifactLiveError(
1192
+ "stale_epoch",
1193
+ "Mutation belongs to a stale stream epoch"
1194
+ );
1195
+ void this.fail(error);
1196
+ return Promise.reject(error);
1197
+ }
1198
+ try {
1199
+ editableArtifactRequestHash(input.requestHash);
1200
+ } catch (cause) {
1201
+ const error = new EditableArtifactLiveError(
1202
+ "invalid_frame",
1203
+ "Mutation request hash is malformed",
1204
+ { cause }
1205
+ );
1206
+ return Promise.reject(error);
1207
+ }
1208
+ if (!(input.intentBytes instanceof Uint8Array) || input.intentBytes.byteLength === 0 || input.intentBytes.byteLength > this.options.maxClientFrameBytes) {
1209
+ const error = new EditableArtifactLiveError(
1210
+ "oversized_frame",
1211
+ "Mutation intent exceeds the byte limit"
1212
+ );
1213
+ void this.fail(error);
1214
+ return Promise.reject(error);
1215
+ }
1216
+ return this.enqueueInbound(
1217
+ input.intentBytes.byteLength,
1218
+ () => this.applyMutation(input),
1219
+ false
1220
+ );
1221
+ }
1222
+ reconcileNow() {
1223
+ return this.enqueue(() => this.reconcileFromDurableHead());
1224
+ }
1225
+ reauthorizeNow() {
1226
+ return this.enqueue(() => this.reauthorize());
1227
+ }
1228
+ async close(reason = "closed") {
1229
+ if (this.closedValue) return;
1230
+ const close = closeValue(reason);
1231
+ this.closedValue = close;
1232
+ this.abort.abort();
1233
+ safeRelease(this.releaseHints);
1234
+ this.releaseHints = null;
1235
+ safeRelease(this.releaseInvalidations);
1236
+ this.releaseInvalidations = null;
1237
+ safeRelease(this.detachParent);
1238
+ this.detachParent = null;
1239
+ try {
1240
+ this.sink.close(close);
1241
+ } catch {
1242
+ }
1243
+ this.resolveClosed(close);
1244
+ }
1245
+ assertOpen() {
1246
+ if (this.closedValue) {
1247
+ throw new EditableArtifactLiveError("closed", "Live session is closed");
1248
+ }
1249
+ }
1250
+ enqueue(task) {
1251
+ const result = this.workTail.then(async () => {
1252
+ if (this.closedValue) {
1253
+ throw new EditableArtifactLiveError("closed", "Live session is closed");
1254
+ }
1255
+ return await task();
1256
+ });
1257
+ this.workTail = result.then(
1258
+ () => void 0,
1259
+ (error) => this.fail(error)
1260
+ );
1261
+ return result;
1262
+ }
1263
+ enqueueInbound(bytes, task, fatal = true) {
1264
+ if (this.queuedInboundFrames + 1 > this.options.maxInboundQueueFrames || this.queuedInboundBytes + bytes > this.options.maxInboundQueueBytes) {
1265
+ const error = new EditableArtifactLiveError(
1266
+ "slow_client",
1267
+ "Live inbound work queue is full",
1268
+ { requiresSnapshot: true }
1269
+ );
1270
+ void this.fail(error);
1271
+ return Promise.reject(error);
1272
+ }
1273
+ this.queuedInboundFrames += 1;
1274
+ this.queuedInboundBytes += bytes;
1275
+ const run = async () => {
1276
+ try {
1277
+ return await task();
1278
+ } finally {
1279
+ this.queuedInboundFrames -= 1;
1280
+ this.queuedInboundBytes -= bytes;
1281
+ }
1282
+ };
1283
+ if (fatal) return this.enqueue(run);
1284
+ const result = this.workTail.then(async () => {
1285
+ if (this.closedValue) {
1286
+ throw new EditableArtifactLiveError("closed", "Live session is closed");
1287
+ }
1288
+ return await run();
1289
+ });
1290
+ this.workTail = result.then(
1291
+ () => void 0,
1292
+ () => void 0
1293
+ );
1294
+ return result;
1295
+ }
1296
+ requestReconcile() {
1297
+ if (this.closedValue) return;
1298
+ this.reconcileRequested = true;
1299
+ if (this.bootstrapping || this.reconcileScheduled) return;
1300
+ this.reconcileScheduled = true;
1301
+ void this.enqueue(async () => {
1302
+ try {
1303
+ while (this.reconcileRequested && !this.closedValue) {
1304
+ this.reconcileRequested = false;
1305
+ await this.reconcileFromDurableHead();
1306
+ }
1307
+ } finally {
1308
+ this.reconcileScheduled = false;
1309
+ if (this.reconcileRequested && !this.closedValue) this.requestReconcile();
1310
+ }
1311
+ }).catch(() => void 0);
1312
+ }
1313
+ requestReauthorization() {
1314
+ if (this.closedValue) return;
1315
+ this.reauthorizationRequested = true;
1316
+ if (this.reauthorizationScheduled) return;
1317
+ this.reauthorizationScheduled = true;
1318
+ void this.enqueue(async () => {
1319
+ try {
1320
+ while (this.reauthorizationRequested && !this.closedValue) {
1321
+ this.reauthorizationRequested = false;
1322
+ await this.reauthorize();
1323
+ }
1324
+ } finally {
1325
+ this.reauthorizationScheduled = false;
1326
+ if (this.reauthorizationRequested && !this.closedValue) {
1327
+ this.requestReauthorization();
1328
+ }
1329
+ }
1330
+ }).catch(() => void 0);
1331
+ }
1332
+ async applyBootstrap(bootstrap) {
1333
+ if (bootstrap.resumeAccepted) {
1334
+ this.sentSequence = bootstrap.resumeSequence;
1335
+ this.ackedSequence = bootstrap.resumeSequence;
1336
+ this.sentStateHash = bootstrap.resumeStateHash;
1337
+ this.ackedStateHash = bootstrap.resumeStateHash;
1338
+ this.boundaries.set(bootstrap.resumeSequence, bootstrap.resumeStateHash);
1339
+ } else if (bootstrap.snapshot) {
1340
+ await this.sendSnapshot(bootstrap.snapshot);
1341
+ this.sentSequence = bootstrap.snapshot.sequence;
1342
+ this.sentStateHash = bootstrap.snapshot.stateHash;
1343
+ this.boundaries.set(bootstrap.snapshot.sequence, bootstrap.snapshot.stateHash);
1344
+ if (bootstrap.snapshot.sequence === 0) {
1345
+ this.ackedStateHash = bootstrap.snapshot.stateHash;
1346
+ }
1347
+ } else {
1348
+ await this.requireResync("retention_gap", bootstrap.headSequence);
1349
+ return;
1350
+ }
1351
+ if (this.sentSequence + 1 < bootstrap.minimumReplaySequence) {
1352
+ await this.requireResync("retention_gap", bootstrap.headSequence);
1353
+ return;
1354
+ }
1355
+ await this.pumpThrough(bootstrap.headSequence);
1356
+ if (!this.closedValue && this.sentSequence === bootstrap.headSequence && this.sentStateHash !== bootstrap.stateHash) {
1357
+ await this.requireResync("durable_gap", bootstrap.headSequence);
1358
+ }
1359
+ }
1360
+ async sendSnapshot(snapshot) {
1361
+ if (snapshot.bytes.byteLength > this.options.maxSnapshotBytes) {
1362
+ await this.requireResync("oversized_frame", snapshot.sequence);
1363
+ return;
1364
+ }
1365
+ const snapshotBytes = snapshot.bytes;
1366
+ if (snapshot.modality !== this.modality) {
1367
+ throw new EditableArtifactLiveError(
1368
+ "durable_gap",
1369
+ "Snapshot modality differs from its durable ticket",
1370
+ { requiresSnapshot: true }
1371
+ );
1372
+ }
1373
+ const causalFrontier = snapshot.modality === "spreadsheet" ? validateFrontier(snapshot.causalFrontier, "snapshot causal frontier") : null;
1374
+ const totalBytes = snapshotBytes.byteLength;
1375
+ if (totalBytes === 0)
1376
+ throw new EditableArtifactLiveError("durable_gap", "Snapshot is empty", {
1377
+ requiresSnapshot: true
1378
+ });
1379
+ for (let offset = 0; offset < totalBytes; offset += this.options.snapshotChunkBytes) {
1380
+ const bytes = snapshotBytes.slice(
1381
+ offset,
1382
+ Math.min(offset + this.options.snapshotChunkBytes, totalBytes)
1383
+ );
1384
+ const common = {
1385
+ type: "snapshot",
1386
+ protocolVersion: this.options.protocolVersion,
1387
+ artifactId: this.artifactId,
1388
+ streamEpoch: this.streamEpoch,
1389
+ sequence: snapshot.sequence,
1390
+ stateHash: snapshot.stateHash,
1391
+ digest: snapshot.digest,
1392
+ kernelVersion: snapshot.kernelVersion,
1393
+ modelSchemaVersion: snapshot.modelSchemaVersion,
1394
+ offset,
1395
+ totalBytes,
1396
+ final: offset + bytes.byteLength === totalBytes,
1397
+ bytes
1398
+ };
1399
+ await this.send(
1400
+ snapshot.modality === "spreadsheet" ? { ...common, modality: snapshot.modality, causalFrontier } : {
1401
+ ...common,
1402
+ modality: snapshot.modality,
1403
+ nativeRevision: snapshot.nativeRevision
1404
+ }
1405
+ );
1406
+ }
1407
+ }
1408
+ async reconcileFromDurableHead() {
1409
+ if (this.closedValue) return;
1410
+ const head = await this.dependencies.read.readHead(this.ticket.scope, this.artifactId);
1411
+ validateHead(head, this.modality);
1412
+ if (head.headSequence < this.sentSequence) {
1413
+ await this.requireResync("durable_gap", head.headSequence);
1414
+ return;
1415
+ }
1416
+ this.targetHead = head.headSequence;
1417
+ if (this.sentSequence + 1 < head.minimumReplaySequence) {
1418
+ await this.requireResync("retention_gap", head.headSequence);
1419
+ return;
1420
+ }
1421
+ await this.pumpThrough(head.headSequence);
1422
+ if (!this.closedValue && this.sentSequence === head.headSequence && this.sentStateHash !== head.stateHash) {
1423
+ await this.requireResync("durable_gap", head.headSequence);
1424
+ return;
1425
+ }
1426
+ if (!this.closedValue) {
1427
+ await this.send({
1428
+ type: "watermark",
1429
+ protocolVersion: this.options.protocolVersion,
1430
+ artifactId: this.artifactId,
1431
+ streamEpoch: this.streamEpoch,
1432
+ headSequence: head.headSequence
1433
+ });
1434
+ }
1435
+ }
1436
+ async pumpThrough(through) {
1437
+ assertNonnegativeSafeInteger(through, "target head sequence");
1438
+ while (!this.closedValue && this.sentSequence < through) {
1439
+ if (this.inFlight.size >= this.options.maxInFlightTransactions) return;
1440
+ const page = await this.dependencies.read.readTransactions({
1441
+ scope: this.ticket.scope,
1442
+ artifactId: this.artifactId,
1443
+ after: this.sentSequence,
1444
+ through,
1445
+ maxCount: this.options.replayPageTransactions,
1446
+ maxBytes: this.options.replayPageBytes
1447
+ });
1448
+ validatePage(
1449
+ page,
1450
+ this.sentSequence,
1451
+ through,
1452
+ this.options.replayPageTransactions,
1453
+ this.options.replayPageBytes
1454
+ );
1455
+ if (this.sentSequence + 1 < page.minimumReplaySequence) {
1456
+ await this.requireResync("retention_gap", page.headSequence);
1457
+ return;
1458
+ }
1459
+ if (page.transactions.length === 0) {
1460
+ await this.requireResync("durable_gap", page.headSequence);
1461
+ return;
1462
+ }
1463
+ let progressed = false;
1464
+ for (const transaction of page.transactions) {
1465
+ validateTransaction(
1466
+ transaction,
1467
+ this.artifactId,
1468
+ this.modality,
1469
+ Math.min(
1470
+ this.options.replayPageBytes,
1471
+ Math.max(0, this.options.maxOutboundFrameBytes - 64 * 1024)
1472
+ )
1473
+ );
1474
+ if (transaction.endSequence > through) {
1475
+ await this.requireResync("durable_gap", page.headSequence);
1476
+ return;
1477
+ }
1478
+ if (transaction.startSequence !== this.sentSequence + 1) {
1479
+ await this.requireResync("durable_gap", page.headSequence);
1480
+ return;
1481
+ }
1482
+ if (this.sentStateHash && transaction.priorStateHash !== this.sentStateHash) {
1483
+ await this.requireResync("durable_gap", page.headSequence);
1484
+ return;
1485
+ }
1486
+ const ownedTransaction = ownTransaction(transaction);
1487
+ const frameBytes = estimateFrameBytes({
1488
+ type: "transaction",
1489
+ protocolVersion: this.options.protocolVersion,
1490
+ artifactId: this.artifactId,
1491
+ streamEpoch: this.streamEpoch,
1492
+ transaction: ownedTransaction
1493
+ });
1494
+ if (frameBytes > this.options.maxOutboundFrameBytes || frameBytes > this.options.maxInFlightBytes) {
1495
+ await this.requireResync("oversized_frame", page.headSequence);
1496
+ return;
1497
+ }
1498
+ if (this.inFlight.size >= this.options.maxInFlightTransactions || this.inFlightBytes + frameBytes > this.options.maxInFlightBytes) {
1499
+ return;
1500
+ }
1501
+ await this.send({
1502
+ type: "transaction",
1503
+ protocolVersion: this.options.protocolVersion,
1504
+ artifactId: this.artifactId,
1505
+ streamEpoch: this.streamEpoch,
1506
+ transaction: ownedTransaction
1507
+ });
1508
+ this.sentSequence = ownedTransaction.endSequence;
1509
+ this.sentStateHash = ownedTransaction.stateHash;
1510
+ this.boundaries.set(ownedTransaction.endSequence, ownedTransaction.stateHash);
1511
+ const sentAt = this.dependencies.clock.now().getTime();
1512
+ this.inFlight.set(ownedTransaction.endSequence, {
1513
+ bytes: frameBytes,
1514
+ stateHash: ownedTransaction.stateHash,
1515
+ sentAt
1516
+ });
1517
+ this.inFlightBytes += frameBytes;
1518
+ if (this.oldestInFlightAt === 0) this.oldestInFlightAt = sentAt;
1519
+ progressed = true;
1520
+ if (this.sentSequence >= through) break;
1521
+ }
1522
+ if (!progressed) return;
1523
+ }
1524
+ }
1525
+ async applyAcknowledgement(frame) {
1526
+ if (frame.sequence < this.ackedSequence || frame.sequence > this.sentSequence) {
1527
+ await this.requireResync("invalid_ack", this.targetHead);
1528
+ return;
1529
+ }
1530
+ const expected = frame.sequence === this.ackedSequence ? this.ackedStateHash : this.boundaries.get(frame.sequence);
1531
+ if (!expected || expected !== frame.stateHash) {
1532
+ await this.requireResync("invalid_ack", this.targetHead);
1533
+ return;
1534
+ }
1535
+ if (frame.sequence > this.ackedSequence) {
1536
+ await this.dependencies.read.acknowledgeReplica({
1537
+ scope: this.ticket.scope,
1538
+ artifactId: this.artifactId,
1539
+ replicaId: this.ticket.actor.replicaId,
1540
+ actorKey: editableArtifactActorKey(this.ticket.actor),
1541
+ streamEpoch: this.streamEpoch,
1542
+ sequence: frame.sequence,
1543
+ stateHash: frame.stateHash
1544
+ });
1545
+ this.ackedSequence = frame.sequence;
1546
+ this.ackedStateHash = frame.stateHash;
1547
+ for (const [sequence, inFlight] of this.inFlight) {
1548
+ if (sequence <= frame.sequence) {
1549
+ this.inFlight.delete(sequence);
1550
+ this.inFlightBytes -= inFlight.bytes;
1551
+ }
1552
+ }
1553
+ for (const sequence of this.boundaries.keys()) {
1554
+ if (sequence < frame.sequence) this.boundaries.delete(sequence);
1555
+ }
1556
+ this.oldestInFlightAt = this.inFlight.size === 0 ? 0 : Math.min(...[...this.inFlight.values()].map((value) => value.sentAt));
1557
+ }
1558
+ await this.send({
1559
+ type: "applied",
1560
+ protocolVersion: this.options.protocolVersion,
1561
+ artifactId: this.artifactId,
1562
+ streamEpoch: this.streamEpoch,
1563
+ sequence: frame.sequence,
1564
+ stateHash: frame.stateHash
1565
+ });
1566
+ await this.reconcileFromDurableHead();
1567
+ }
1568
+ async applyMutation(frame) {
1569
+ await this.reauthorize();
1570
+ if (this.closedValue) {
1571
+ throw new EditableArtifactLiveError(
1572
+ "permission_changed",
1573
+ "Editable artifact read permission changed"
1574
+ );
1575
+ }
1576
+ if (!this.writable) {
1577
+ throw new EditableArtifactLiveError(
1578
+ "permission_changed",
1579
+ "Editable artifact edit permission denied"
1580
+ );
1581
+ }
1582
+ let result;
1583
+ try {
1584
+ result = await this.dependencies.domain.applyTransaction({
1585
+ scope: this.ticket.scope,
1586
+ artifactId: this.artifactId,
1587
+ actor: this.ticket.actor,
1588
+ request: {
1589
+ intentBytes: frame.intentBytes.slice(),
1590
+ requestHash: frame.requestHash
1591
+ }
1592
+ });
1593
+ } catch (error) {
1594
+ await this.reauthorize();
1595
+ throw error;
1596
+ }
1597
+ const transaction = await this.dependencies.read.readCommittedTransaction({
1598
+ scope: this.ticket.scope,
1599
+ artifactId: this.artifactId,
1600
+ transactionId: result.receipt.serverTransactionId
1601
+ });
1602
+ if (!transaction || result.receipt.modality !== this.modality || transaction.modality !== this.modality || transaction.startSequence !== result.receipt.sequenceStart || transaction.endSequence !== result.receipt.sequenceEnd || transaction.transactionId !== result.receipt.serverTransactionId || result.receipt.requestHash !== frame.requestHash || transaction.requestHash !== result.receipt.requestHash || transaction.stateHash !== result.receipt.stateHash) {
1603
+ await this.requireResync("durable_gap", this.targetHead);
1604
+ throw new EditableArtifactLiveError(
1605
+ "durable_gap",
1606
+ "Committed mutation is unavailable from durable replay",
1607
+ { requiresSnapshot: true }
1608
+ );
1609
+ }
1610
+ validateTransaction(
1611
+ transaction,
1612
+ this.artifactId,
1613
+ this.modality,
1614
+ Math.min(
1615
+ this.options.replayPageBytes,
1616
+ Math.max(0, this.options.maxOutboundFrameBytes - 64 * 1024)
1617
+ )
1618
+ );
1619
+ const ownedTransaction = ownTransaction(transaction);
1620
+ await this.reconcileFromDurableHead();
1621
+ return Object.freeze({
1622
+ clientTransactionId: result.receipt.clientTransactionId,
1623
+ requestHash: result.receipt.requestHash,
1624
+ transaction: ownedTransaction
1625
+ });
1626
+ }
1627
+ async reauthorize() {
1628
+ let readDecision = await this.checkPermission("read");
1629
+ let editDecision = await this.checkPermission("edit");
1630
+ const targetRevision = Math.max(
1631
+ this.authorizationRevision,
1632
+ readDecision.revision,
1633
+ editDecision.revision
1634
+ );
1635
+ if (readDecision.revision < targetRevision) {
1636
+ readDecision = await this.checkPermission("read");
1637
+ }
1638
+ if (editDecision.revision < targetRevision) {
1639
+ editDecision = await this.checkPermission("edit");
1640
+ }
1641
+ const coherentRevision = Math.max(targetRevision, readDecision.revision, editDecision.revision);
1642
+ if (readDecision.revision !== coherentRevision) {
1643
+ await this.close("permission_changed");
1644
+ return;
1645
+ }
1646
+ if (!readDecision.allowed) {
1647
+ await this.close("permission_changed");
1648
+ return;
1649
+ }
1650
+ const nextWritable = editDecision.revision === coherentRevision && editDecision.allowed;
1651
+ const revisionChanged = coherentRevision !== this.authorizationRevision;
1652
+ this.authorizationRevision = coherentRevision;
1653
+ if (nextWritable !== this.writable || revisionChanged) {
1654
+ this.writable = nextWritable;
1655
+ await this.sendAuthorization();
1656
+ }
1657
+ }
1658
+ async sendAuthorization() {
1659
+ await this.send({
1660
+ type: "authorizationChanged",
1661
+ protocolVersion: this.options.protocolVersion,
1662
+ artifactId: this.artifactId,
1663
+ streamEpoch: this.streamEpoch,
1664
+ writable: this.writable
1665
+ });
1666
+ }
1667
+ async requireRead() {
1668
+ const decision = await this.checkPermission("read");
1669
+ if (!decision.allowed || decision.revision < this.authorizationRevision) {
1670
+ throw new EditableArtifactLiveError(
1671
+ "permission_changed",
1672
+ "Editable artifact read permission denied"
1673
+ );
1674
+ }
1675
+ return decision.revision;
1676
+ }
1677
+ async checkPermission(permission) {
1678
+ const decision = await this.dependencies.authorization.authorize({
1679
+ scope: this.ticket.scope,
1680
+ artifactId: this.artifactId,
1681
+ actor: this.ticket.actor,
1682
+ permission
1683
+ });
1684
+ if (permission === "edit" && !this.ticket.allowEdit) {
1685
+ return Object.freeze({ ...decision, allowed: false });
1686
+ }
1687
+ return decision;
1688
+ }
1689
+ async sendBarrier() {
1690
+ if (!this.sentStateHash)
1691
+ throw new EditableArtifactLiveError("durable_gap", "Bootstrap has no state boundary");
1692
+ await this.send({
1693
+ type: "barrier",
1694
+ protocolVersion: this.options.protocolVersion,
1695
+ artifactId: this.artifactId,
1696
+ streamEpoch: this.streamEpoch,
1697
+ sequence: this.sentSequence,
1698
+ stateHash: this.sentStateHash
1699
+ });
1700
+ }
1701
+ async send(frame) {
1702
+ if (this.closedValue) return;
1703
+ const bytes = estimateFrameBytes(frame);
1704
+ if (bytes > this.options.maxOutboundFrameBytes) {
1705
+ throw new EditableArtifactLiveError(
1706
+ "oversized_frame",
1707
+ "Server live frame exceeds the byte limit",
1708
+ { requiresSnapshot: true }
1709
+ );
1710
+ }
1711
+ const buffered = this.sink.bufferedBytes();
1712
+ if (!Number.isSafeInteger(buffered) || buffered < 0 || buffered + bytes > this.options.maxSocketBufferedBytes) {
1713
+ throw new EditableArtifactLiveError("slow_client", "Live socket send buffer is full", {
1714
+ retryable: true,
1715
+ requiresSnapshot: true
1716
+ });
1717
+ }
1718
+ const timeout = new AbortController();
1719
+ try {
1720
+ await Promise.race([
1721
+ this.sink.send(frame),
1722
+ this.dependencies.scheduler.sleep(this.options.sendTimeoutMs, timeout.signal).then(() => {
1723
+ throw new EditableArtifactLiveError("slow_client", "Live socket write timed out", {
1724
+ retryable: true,
1725
+ requiresSnapshot: true
1726
+ });
1727
+ })
1728
+ ]);
1729
+ } finally {
1730
+ timeout.abort();
1731
+ }
1732
+ }
1733
+ async requireResync(reason, headSequence) {
1734
+ if (this.closedValue) return;
1735
+ try {
1736
+ await this.send({
1737
+ type: "resyncRequired",
1738
+ protocolVersion: this.options.protocolVersion,
1739
+ artifactId: this.artifactId,
1740
+ streamEpoch: this.streamEpoch,
1741
+ reason,
1742
+ headSequence
1743
+ });
1744
+ } catch {
1745
+ }
1746
+ await this.close(reason);
1747
+ }
1748
+ async runMaintenance() {
1749
+ let nextReconcile = this.dependencies.clock.now().getTime() + this.options.reconcileIntervalMs;
1750
+ let nextAuthorization = this.dependencies.clock.now().getTime() + this.options.reauthorizeIntervalMs;
1751
+ const tick = Math.max(
1752
+ 10,
1753
+ Math.min(
1754
+ this.options.reconcileIntervalMs,
1755
+ this.options.reauthorizeIntervalMs,
1756
+ this.options.ackTimeoutMs
1757
+ )
1758
+ );
1759
+ while (!this.abort.signal.aborted) {
1760
+ await this.dependencies.scheduler.sleep(tick, this.abort.signal);
1761
+ if (this.abort.signal.aborted || this.closedValue) return;
1762
+ const now = this.dependencies.clock.now().getTime();
1763
+ if (this.oldestInFlightAt !== 0 && now - this.oldestInFlightAt >= this.options.ackTimeoutMs) {
1764
+ await this.enqueue(() => this.requireResync("slow_client", this.targetHead));
1765
+ return;
1766
+ }
1767
+ if (now >= nextAuthorization) {
1768
+ await this.reauthorizeNow();
1769
+ nextAuthorization = now + this.options.reauthorizeIntervalMs;
1770
+ }
1771
+ if (this.closedValue) return;
1772
+ if (now >= nextReconcile) {
1773
+ await this.reconcileNow();
1774
+ nextReconcile = now + this.options.reconcileIntervalMs;
1775
+ }
1776
+ }
1777
+ }
1778
+ async fail(error) {
1779
+ if (this.closedValue) return;
1780
+ if (error instanceof EditableArtifactLiveError) {
1781
+ if (error.requiresSnapshot && ["durable_gap", "retention_gap", "invalid_ack", "slow_client", "oversized_frame"].includes(
1782
+ error.code
1783
+ )) {
1784
+ await this.requireResync(
1785
+ error.code,
1786
+ this.targetHead
1787
+ );
1788
+ return;
1789
+ }
1790
+ await this.close(error.code === "invalid_ticket" ? "transport_error" : error.code);
1791
+ return;
1792
+ }
1793
+ await this.close("transport_error");
1794
+ }
1795
+ };
1796
+ function decodeClientFrame(bytes, artifactId, streamEpoch, protocolVersion) {
1797
+ let value;
1798
+ try {
1799
+ value = JSON.parse(strictDecoder.decode(bytes));
1800
+ } catch (cause) {
1801
+ throw new EditableArtifactLiveError(
1802
+ "invalid_frame",
1803
+ "Client live frame is not valid UTF-8 JSON",
1804
+ { cause }
1805
+ );
1806
+ }
1807
+ const frame = plainRecord(value, "client frame");
1808
+ const type = requiredString(frame, "type", 32);
1809
+ const actualProtocol = requiredSafeInteger(frame, "protocolVersion");
1810
+ const actualArtifact = editableArtifactId(requiredString(frame, "artifactId", 64));
1811
+ const actualEpoch = requiredString(frame, "streamEpoch", MAX_IDENTIFIER_BYTES);
1812
+ if (actualProtocol !== protocolVersion)
1813
+ throw new EditableArtifactLiveError("protocol_mismatch", "Client frame protocol mismatch");
1814
+ if (actualArtifact !== artifactId)
1815
+ throw new EditableArtifactLiveError("invalid_frame", "Client frame artifact mismatch");
1816
+ if (actualEpoch !== streamEpoch)
1817
+ throw new EditableArtifactLiveError(
1818
+ "stale_epoch",
1819
+ "Client frame belongs to a stale stream epoch"
1820
+ );
1821
+ if (type === "applied") {
1822
+ rejectUnknown(frame, [
1823
+ "type",
1824
+ "protocolVersion",
1825
+ "artifactId",
1826
+ "streamEpoch",
1827
+ "sequence",
1828
+ "stateHash"
1829
+ ]);
1830
+ return validateAppliedFrame(
1831
+ Object.freeze({
1832
+ type,
1833
+ protocolVersion,
1834
+ artifactId,
1835
+ streamEpoch,
1836
+ sequence: requiredSafeInteger(frame, "sequence"),
1837
+ stateHash: editableArtifactStateHash(requiredString(frame, "stateHash", 80))
1838
+ }),
1839
+ artifactId,
1840
+ streamEpoch,
1841
+ protocolVersion
1842
+ );
1843
+ }
1844
+ throw new EditableArtifactLiveError("invalid_frame", `Unknown client live frame: ${type}`);
1845
+ }
1846
+ function validateAppliedFrame(frame, artifactId, streamEpoch, protocolVersion) {
1847
+ if (frame.type !== "applied") {
1848
+ throw new EditableArtifactLiveError("invalid_frame", "Client frame is not an apply ACK");
1849
+ }
1850
+ if (frame.protocolVersion !== protocolVersion) {
1851
+ throw new EditableArtifactLiveError("protocol_mismatch", "Client frame protocol mismatch");
1852
+ }
1853
+ if (editableArtifactId(frame.artifactId) !== artifactId) {
1854
+ throw new EditableArtifactLiveError("invalid_frame", "Client frame artifact mismatch");
1855
+ }
1856
+ boundedString(frame.streamEpoch, "stream epoch", MAX_IDENTIFIER_BYTES);
1857
+ if (frame.streamEpoch !== streamEpoch) {
1858
+ throw new EditableArtifactLiveError(
1859
+ "stale_epoch",
1860
+ "Client frame belongs to a stale stream epoch"
1861
+ );
1862
+ }
1863
+ assertNonnegativeSafeInteger(frame.sequence, "applied sequence");
1864
+ return Object.freeze({
1865
+ type: "applied",
1866
+ protocolVersion,
1867
+ artifactId,
1868
+ streamEpoch,
1869
+ sequence: frame.sequence,
1870
+ stateHash: editableArtifactStateHash(frame.stateHash)
1871
+ });
1872
+ }
1873
+ function validateResume(resume, durableModality) {
1874
+ const resumeModality = resume.modality ?? "spreadsheet";
1875
+ if (resumeModality !== durableModality) {
1876
+ throw new EditableArtifactLiveError(
1877
+ "invalid_frame",
1878
+ "Resume modality differs from the durable artifact"
1879
+ );
1880
+ }
1881
+ if (resume.localCursor !== null) assertNonnegativeSafeInteger(resume.localCursor, "local cursor");
1882
+ if (resume.localStateHash !== null) editableArtifactStateHash(resume.localStateHash);
1883
+ if (resumeModality === "spreadsheet") {
1884
+ if (!("localCausalFrontier" in resume)) {
1885
+ throw new TypeError("spreadsheet resume requires a causal frontier");
1886
+ }
1887
+ validateFrontier(resume.localCausalFrontier, "local causal frontier");
1888
+ } else if ("localNativeRevision" in resume && resume.localNativeRevision !== null) {
1889
+ assertNonnegativeSafeInteger(resume.localNativeRevision, "local native revision");
1890
+ } else if (!("localNativeRevision" in resume)) {
1891
+ throw new TypeError("serialized resume requires a native revision");
1892
+ }
1893
+ if (typeof resume.requireSnapshot !== "boolean")
1894
+ throw new TypeError("requireSnapshot must be boolean");
1895
+ if (resume.localCursor === null !== (resume.localStateHash === null)) {
1896
+ throw new TypeError("resume cursor and state hash must both be null or present");
1897
+ }
1898
+ if (resumeModality !== "spreadsheet" && "localNativeRevision" in resume && resume.localCursor === null !== (resume.localNativeRevision === null)) {
1899
+ throw new TypeError(
1900
+ "serialized resume cursor and native revision must both be null or present"
1901
+ );
1902
+ }
1903
+ }
1904
+ function validateBootstrap(bootstrap, artifactId, durableModality, resume, options) {
1905
+ validateHead(bootstrap, durableModality);
1906
+ assertNonnegativeSafeInteger(bootstrap.resumeSequence, "bootstrap resume sequence");
1907
+ editableArtifactStateHash(bootstrap.resumeStateHash);
1908
+ if (typeof bootstrap.resumeAccepted !== "boolean")
1909
+ throw new TypeError("resumeAccepted must be boolean");
1910
+ if (bootstrap.resumeSequence > bootstrap.headSequence)
1911
+ throw new TypeError("bootstrap resume is beyond head");
1912
+ if (bootstrap.resumeAccepted && (resume.requireSnapshot || resume.localCursor === null || resume.localStateHash === null || bootstrap.resumeSequence !== resume.localCursor || bootstrap.resumeStateHash !== resume.localStateHash)) {
1913
+ throw new TypeError("bootstrap accepted a different or unverified resume boundary");
1914
+ }
1915
+ if (bootstrap.snapshot) {
1916
+ const snapshot = bootstrap.snapshot;
1917
+ if (snapshot.modality !== durableModality) throw new TypeError("snapshot modality mismatch");
1918
+ if (snapshot.artifactId !== artifactId) throw new TypeError("snapshot artifact mismatch");
1919
+ assertNonnegativeSafeInteger(snapshot.sequence, "snapshot sequence");
1920
+ if (snapshot.sequence > bootstrap.headSequence) throw new TypeError("snapshot is beyond head");
1921
+ editableArtifactStateHash(snapshot.stateHash);
1922
+ if (snapshot.modality === "spreadsheet") {
1923
+ validateFrontier(snapshot.causalFrontier, "snapshot causal frontier");
1924
+ assertPositiveSafeInteger(
1925
+ snapshot.operationProtocolVersion,
1926
+ "snapshot operation protocol version"
1927
+ );
1928
+ } else {
1929
+ assertNonnegativeSafeInteger(snapshot.nativeRevision, "snapshot native revision");
1930
+ }
1931
+ if (!/^sha256:[0-9a-f]{64}$/u.test(snapshot.digest))
1932
+ throw new TypeError("snapshot digest is malformed");
1933
+ assertPositiveSafeInteger(snapshot.modelSchemaVersion, "snapshot model schema version");
1934
+ boundedString(
1935
+ snapshot.kernelVersion,
1936
+ "snapshot kernel version",
1937
+ EDITABLE_ARTIFACT_KERNEL_VERSION_MAX_BYTES2
1938
+ );
1939
+ if (!(snapshot.bytes instanceof Uint8Array) || snapshot.bytes.byteLength > options.maxSnapshotBytes) {
1940
+ throw new EditableArtifactLiveError("oversized_frame", "Snapshot bytes exceed live limit", {
1941
+ requiresSnapshot: true
1942
+ });
1943
+ }
1944
+ }
1945
+ }
1946
+ function validateHead(head, durableModality) {
1947
+ if (head.modality !== durableModality) throw new TypeError("durable live head modality mismatch");
1948
+ assertNonnegativeSafeInteger(head.headSequence, "head sequence");
1949
+ assertNonnegativeSafeInteger(head.minimumReplaySequence, "minimum replay sequence");
1950
+ if (head.minimumReplaySequence > head.headSequence + 1)
1951
+ throw new TypeError("minimum replay sequence is beyond head");
1952
+ editableArtifactStateHash(head.stateHash);
1953
+ if (head.modality === "spreadsheet") {
1954
+ validateFrontier(head.causalFrontier, "head causal frontier");
1955
+ } else {
1956
+ assertNonnegativeSafeInteger(head.nativeRevision, "head native revision");
1957
+ }
1958
+ }
1959
+ function validatePage(page, after, through, maxCount, maxBytes) {
1960
+ if (!Array.isArray(page.transactions)) throw new TypeError("transaction page is malformed");
1961
+ assertNonnegativeSafeInteger(page.headSequence, "page head sequence");
1962
+ assertNonnegativeSafeInteger(page.minimumReplaySequence, "page minimum replay sequence");
1963
+ if (page.headSequence < through || after > through)
1964
+ throw new TypeError("transaction page does not cover requested target");
1965
+ if (page.transactions.length > maxCount) {
1966
+ throw new EditableArtifactLiveError(
1967
+ "oversized_frame",
1968
+ "Durable transaction page exceeds the count limit",
1969
+ { requiresSnapshot: true }
1970
+ );
1971
+ }
1972
+ let bytes = 0;
1973
+ for (const transaction of page.transactions) {
1974
+ bytes += transaction.committedTransactionBytes.byteLength;
1975
+ if (!Number.isSafeInteger(bytes) || bytes > maxBytes) {
1976
+ throw new EditableArtifactLiveError(
1977
+ "oversized_frame",
1978
+ "Durable transaction page exceeds the byte limit",
1979
+ { requiresSnapshot: true }
1980
+ );
1981
+ }
1982
+ }
1983
+ }
1984
+ function validateTransaction(transaction, artifactId, durableModality, maxBytes) {
1985
+ if (transaction.artifactId !== artifactId) throw new TypeError("transaction artifact mismatch");
1986
+ if (transaction.modality !== durableModality)
1987
+ throw new TypeError("transaction modality differs from the durable artifact");
1988
+ editableArtifactTransactionId(transaction.transactionId);
1989
+ assertPositiveSafeInteger(transaction.startSequence, "transaction start sequence");
1990
+ assertPositiveSafeInteger(transaction.endSequence, "transaction end sequence");
1991
+ if (transaction.endSequence < transaction.startSequence)
1992
+ throw new TypeError("transaction sequence interval is inverted");
1993
+ editableArtifactRequestHash(transaction.requestHash);
1994
+ editableArtifactStateHash(transaction.priorStateHash);
1995
+ editableArtifactStateHash(transaction.stateHash);
1996
+ if (transaction.modality === "spreadsheet") {
1997
+ assertPositiveSafeInteger(
1998
+ transaction.operationProtocolVersion,
1999
+ "transaction operation protocol version"
2000
+ );
2001
+ validateFrontier(transaction.causalFrontier, "transaction causal frontier");
2002
+ } else {
2003
+ assertPositiveSafeInteger(
2004
+ transaction.commitProtocolVersion,
2005
+ "transaction commit protocol version"
2006
+ );
2007
+ assertNonnegativeSafeInteger(
2008
+ transaction.priorNativeRevision,
2009
+ "transaction prior native revision"
2010
+ );
2011
+ assertNonnegativeSafeInteger(transaction.nativeRevision, "transaction native revision");
2012
+ }
2013
+ if (!(transaction.committedTransactionBytes instanceof Uint8Array) || transaction.committedTransactionBytes.byteLength === 0 || transaction.committedTransactionBytes.byteLength > maxBytes) {
2014
+ throw new EditableArtifactLiveError(
2015
+ "oversized_frame",
2016
+ "Canonical transaction bytes exceed live limit",
2017
+ { requiresSnapshot: true }
2018
+ );
2019
+ }
2020
+ try {
2021
+ if (transaction.modality === "spreadsheet") {
2022
+ const summary = decodeCommittedTransactionSummary(transaction.committedTransactionBytes);
2023
+ if (summary.transactionId !== transaction.transactionId || summary.priorStateHash !== transaction.priorStateHash || summary.stateHash !== transaction.stateHash || summary.operationProtocolVersion !== transaction.operationProtocolVersion || !sameFrontier(summary.resultingCausalFrontier, transaction.causalFrontier)) {
2024
+ throw new TypeError("spreadsheet transaction projection mismatch");
2025
+ }
2026
+ } else {
2027
+ const summary = decodeEditableArtifactSerializedCommit(
2028
+ transaction.committedTransactionBytes,
2029
+ transaction.modality
2030
+ );
2031
+ if (summary.transactionId !== transaction.transactionId || summary.requestHash !== transaction.requestHash || summary.parentHeadSequence !== transaction.startSequence - 1 || summary.resultHeadSequence !== transaction.endSequence || summary.priorStateHash !== transaction.priorStateHash || summary.stateHash !== transaction.stateHash || summary.priorNativeRevision !== transaction.priorNativeRevision || summary.nativeReceipt.revision !== transaction.nativeRevision || summary.commitProtocolVersion !== transaction.commitProtocolVersion) {
2032
+ throw new TypeError("serialized transaction projection mismatch");
2033
+ }
2034
+ }
2035
+ } catch (cause) {
2036
+ throw new EditableArtifactLiveError(
2037
+ "durable_gap",
2038
+ "Canonical transaction bytes disagree with durable live metadata",
2039
+ { requiresSnapshot: true, cause }
2040
+ );
2041
+ }
2042
+ }
2043
+ function estimateFrameBytes(frame) {
2044
+ if (frame.type === "snapshot") {
2045
+ const { bytes, ...metadata } = frame;
2046
+ return textEncoder2.encode(JSON.stringify(metadata)).byteLength + bytes.byteLength + 128;
2047
+ }
2048
+ if (frame.type === "transaction") {
2049
+ const { committedTransactionBytes, ...transaction } = frame.transaction;
2050
+ return textEncoder2.encode(JSON.stringify({ ...frame, transaction })).byteLength + committedTransactionBytes.byteLength + 128;
2051
+ }
2052
+ return textEncoder2.encode(JSON.stringify(frame)).byteLength;
2053
+ }
2054
+ function validateFrontier(frontier, label) {
2055
+ if (!Array.isArray(frontier) || frontier.length > MAX_CAUSAL_ACTORS) {
2056
+ throw new EditableArtifactLiveError(
2057
+ "oversized_frame",
2058
+ `${label} exceeds ${MAX_CAUSAL_ACTORS} actors`,
2059
+ { requiresSnapshot: true }
2060
+ );
2061
+ }
2062
+ const normalized = editableArtifactCausalFrontier(frontier);
2063
+ if (normalized.length > MAX_CAUSAL_ACTORS) {
2064
+ throw new EditableArtifactLiveError(
2065
+ "oversized_frame",
2066
+ `${label} exceeds ${MAX_CAUSAL_ACTORS} actors`,
2067
+ { requiresSnapshot: true }
2068
+ );
2069
+ }
2070
+ return normalized;
2071
+ }
2072
+ function sameFrontier(left, right) {
2073
+ return left.length === right.length && left.every(
2074
+ (entry, index) => entry.replicaId === right[index]?.replicaId && entry.counter === right[index]?.counter
2075
+ );
2076
+ }
2077
+ function ownTransaction(transaction) {
2078
+ return transaction.modality === "spreadsheet" ? Object.freeze({
2079
+ ...transaction,
2080
+ causalFrontier: validateFrontier(transaction.causalFrontier, "transaction causal frontier"),
2081
+ committedTransactionBytes: transaction.committedTransactionBytes.slice()
2082
+ }) : Object.freeze({
2083
+ ...transaction,
2084
+ committedTransactionBytes: transaction.committedTransactionBytes.slice()
2085
+ });
2086
+ }
2087
+ function plainRecord(value, label) {
2088
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2089
+ throw new EditableArtifactLiveError("invalid_frame", `${label} must be an object`);
2090
+ }
2091
+ const prototype = Object.getPrototypeOf(value);
2092
+ if (prototype !== Object.prototype && prototype !== null) {
2093
+ throw new EditableArtifactLiveError("invalid_frame", `${label} must be a plain object`);
2094
+ }
2095
+ return value;
2096
+ }
2097
+ function requiredString(record, key, maxBytes) {
2098
+ const value = record[key];
2099
+ if (typeof value !== "string")
2100
+ throw new EditableArtifactLiveError("invalid_frame", `${key} must be a string`);
2101
+ boundedString(value, key, maxBytes);
2102
+ return value;
2103
+ }
2104
+ function requiredSafeInteger(record, key) {
2105
+ const value = record[key];
2106
+ if (!Number.isSafeInteger(value))
2107
+ throw new EditableArtifactLiveError("invalid_frame", `${key} must be a safe integer`);
2108
+ return value;
2109
+ }
2110
+ function rejectUnknown(record, allowed) {
2111
+ const allowedSet = new Set(allowed);
2112
+ const unknown = Object.keys(record).find((key) => !allowedSet.has(key));
2113
+ if (unknown)
2114
+ throw new EditableArtifactLiveError(
2115
+ "invalid_frame",
2116
+ `Unknown client frame property: ${unknown}`
2117
+ );
2118
+ }
2119
+ function boundedString(value, label, maxBytes) {
2120
+ const bytes = textEncoder2.encode(value).byteLength;
2121
+ if (bytes < 1 || bytes > maxBytes || value.trim() !== value)
2122
+ throw new TypeError(`${label} is malformed`);
2123
+ }
2124
+ function normalizeOptions2(options) {
2125
+ const normalized = {
2126
+ protocolVersion: options.protocolVersion ?? EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION,
2127
+ maxClientFrameBytes: options.maxClientFrameBytes ?? DEFAULT_MAX_CLIENT_FRAME_BYTES,
2128
+ maxOutboundFrameBytes: options.maxOutboundFrameBytes ?? DEFAULT_MAX_OUTBOUND_FRAME_BYTES,
2129
+ maxSnapshotBytes: options.maxSnapshotBytes ?? DEFAULT_MAX_SNAPSHOT_BYTES,
2130
+ snapshotChunkBytes: options.snapshotChunkBytes ?? DEFAULT_SNAPSHOT_CHUNK_BYTES,
2131
+ maxInFlightTransactions: options.maxInFlightTransactions ?? DEFAULT_MAX_IN_FLIGHT_TRANSACTIONS,
2132
+ maxInFlightBytes: options.maxInFlightBytes ?? DEFAULT_MAX_IN_FLIGHT_BYTES,
2133
+ replayPageTransactions: options.replayPageTransactions ?? DEFAULT_REPLAY_PAGE_TRANSACTIONS,
2134
+ replayPageBytes: options.replayPageBytes ?? DEFAULT_REPLAY_PAGE_BYTES,
2135
+ maxSocketBufferedBytes: options.maxSocketBufferedBytes ?? DEFAULT_MAX_SOCKET_BUFFERED_BYTES,
2136
+ maxInboundQueueFrames: options.maxInboundQueueFrames ?? DEFAULT_MAX_INBOUND_QUEUE_FRAMES,
2137
+ maxInboundQueueBytes: options.maxInboundQueueBytes ?? DEFAULT_MAX_INBOUND_QUEUE_BYTES,
2138
+ reconcileIntervalMs: options.reconcileIntervalMs ?? DEFAULT_RECONCILE_INTERVAL_MS,
2139
+ reauthorizeIntervalMs: options.reauthorizeIntervalMs ?? DEFAULT_REAUTHORIZE_INTERVAL_MS,
2140
+ ackTimeoutMs: options.ackTimeoutMs ?? DEFAULT_ACK_TIMEOUT_MS,
2141
+ sendTimeoutMs: options.sendTimeoutMs ?? DEFAULT_SEND_TIMEOUT_MS,
2142
+ ...options.ticketTtlMs === void 0 ? {} : { ticketTtlMs: options.ticketTtlMs }
2143
+ };
2144
+ for (const [label, value] of Object.entries(normalized)) {
2145
+ if (label === "ticketTtlMs") continue;
2146
+ assertPositiveSafeInteger(value, label);
2147
+ }
2148
+ if (normalized.maxSnapshotBytes > EDITABLE_ARTIFACT_PRODUCT_MAX_SNAPSHOT_BYTES) {
2149
+ throw new TypeError("maxSnapshotBytes exceeds the browser-compatible product limit");
2150
+ }
2151
+ if (normalized.snapshotChunkBytes + 1024 > normalized.maxOutboundFrameBytes) {
2152
+ throw new TypeError("snapshot chunk exceeds maximum outbound frame");
2153
+ }
2154
+ if (normalized.replayPageBytes < normalized.maxOutboundFrameBytes) {
2155
+ throw new TypeError("replay page bytes must cover one maximum outbound frame");
2156
+ }
2157
+ return Object.freeze(normalized);
2158
+ }
2159
+ function closeValue(reason) {
2160
+ return Object.freeze({
2161
+ reason,
2162
+ retryable: ["ticket_expired", "permission_changed", "slow_client", "transport_error"].includes(
2163
+ reason
2164
+ ),
2165
+ requiresSnapshot: [
2166
+ "invalid_ack",
2167
+ "retention_gap",
2168
+ "durable_gap",
2169
+ "slow_client",
2170
+ "oversized_frame"
2171
+ ].includes(reason)
2172
+ });
2173
+ }
2174
+ function safeRelease(release) {
2175
+ try {
2176
+ release?.();
2177
+ } catch {
2178
+ }
2179
+ }
2180
+ var SystemEditableArtifactLiveClock = class {
2181
+ now() {
2182
+ return /* @__PURE__ */ new Date();
2183
+ }
2184
+ };
2185
+ var SystemEditableArtifactLiveScheduler = class {
2186
+ sleep(milliseconds, signal) {
2187
+ return new Promise((resolve, reject) => {
2188
+ if (signal.aborted) {
2189
+ reject(signal.reason ?? new Error("aborted"));
2190
+ return;
2191
+ }
2192
+ const timer = setTimeout(resolve, milliseconds);
2193
+ signal.addEventListener(
2194
+ "abort",
2195
+ () => {
2196
+ clearTimeout(timer);
2197
+ reject(signal.reason ?? new Error("aborted"));
2198
+ },
2199
+ { once: true }
2200
+ );
2201
+ });
2202
+ }
2203
+ };
2204
+
2205
+ // src/editable-artifact-live/wire.ts
2206
+ import {
2207
+ EDITABLE_ARTIFACT_LIVE_WIRE_VERSION,
2208
+ decodeEditableArtifactLiveClientWireFrame as decodeContractClientFrame,
2209
+ decodeEditableArtifactLiveServerWireFrame,
2210
+ encodeEditableArtifactLiveAppliedWireFrame as encodeContractAppliedFrame,
2211
+ encodeEditableArtifactLiveMutationWireFrame as encodeContractMutationFrame,
2212
+ encodeEditableArtifactLiveOpenWireFrame as encodeContractOpenFrame,
2213
+ encodeEditableArtifactLiveServerWireFrame as encodeContractServerFrame,
2214
+ inspectEditableArtifactLiveWireEnvelope
2215
+ } from "@opengeni/contracts/editable-artifact-live";
2216
+ function decodeEditableArtifactLiveClientWireFrame(bytes) {
2217
+ return decodeContractClientFrame(bytes);
2218
+ }
2219
+ function encodeEditableArtifactLiveOpenWireFrame(frame) {
2220
+ return encodeContractOpenFrame(frame);
2221
+ }
2222
+ function encodeEditableArtifactLiveAppliedWireFrame(frame) {
2223
+ return encodeContractAppliedFrame(frame);
2224
+ }
2225
+ function encodeEditableArtifactLiveMutationWireFrame(frame) {
2226
+ return encodeContractMutationFrame(frame);
2227
+ }
2228
+ function encodeEditableArtifactLiveServerWireFrame(frame) {
2229
+ if (frame.type === "transaction" && frame.transaction.modality === "spreadsheet") {
2230
+ const { operationProtocolVersion, modality: _modality, ...transaction } = frame.transaction;
2231
+ return encodeContractServerFrame({
2232
+ ...frame,
2233
+ transaction: {
2234
+ ...transaction,
2235
+ protocolVersion: operationProtocolVersion
2236
+ }
2237
+ });
2238
+ }
2239
+ return encodeContractServerFrame(frame);
2240
+ }
2241
+
2242
+ export {
2243
+ EditableArtifactCompatibilityError,
2244
+ EditableArtifactApplication,
2245
+ EditableArtifactLiveError,
2246
+ EDITABLE_ARTIFACT_HINT_MAX_BYTES,
2247
+ EDITABLE_ARTIFACT_HINT_SUBJECT_PREFIX,
2248
+ EditableArtifactLiveOutboxDispatcher,
2249
+ EditableArtifactHintEncodingError,
2250
+ encodeEditableArtifactBrokerHint,
2251
+ editableArtifactLiveHintSubject,
2252
+ decodeEditableArtifactBrokerHint,
2253
+ MathEditableArtifactOutboxRandom,
2254
+ EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION,
2255
+ EditableArtifactLiveTicketAuthority,
2256
+ InMemoryEditableArtifactLiveTicketStore,
2257
+ WebCryptoEditableArtifactLiveTokens,
2258
+ EditableArtifactLiveServer,
2259
+ SystemEditableArtifactLiveClock,
2260
+ SystemEditableArtifactLiveScheduler,
2261
+ EDITABLE_ARTIFACT_LIVE_WIRE_VERSION,
2262
+ decodeEditableArtifactLiveServerWireFrame,
2263
+ inspectEditableArtifactLiveWireEnvelope,
2264
+ decodeEditableArtifactLiveClientWireFrame,
2265
+ encodeEditableArtifactLiveOpenWireFrame,
2266
+ encodeEditableArtifactLiveAppliedWireFrame,
2267
+ encodeEditableArtifactLiveMutationWireFrame,
2268
+ encodeEditableArtifactLiveServerWireFrame
2269
+ };
2270
+ //# sourceMappingURL=chunk-NYPMQ7NO.js.map