@opengeni/core 0.21.2 → 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 (123) hide show
  1. package/dist/access/index.d.ts +9 -1
  2. package/dist/application/new-session-drafts.d.ts +1 -1
  3. package/dist/application/session-commands.d.ts +9 -4
  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 +35 -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/memory-slack-publication.d.ts +0 -2
  32. package/dist/domain/packs.d.ts +29 -1
  33. package/dist/domain/personal-connection-delegations.d.ts +9 -1
  34. package/dist/domain/scheduled-tasks.d.ts +27 -3
  35. package/dist/domain/session-tool-policy.d.ts +2 -2
  36. package/dist/domain/sessions.d.ts +44 -10
  37. package/dist/domain/skill-imports.d.ts +32 -0
  38. package/dist/domain/slack-publication-secret-safety.d.ts +8 -0
  39. package/dist/domain/timeline-annotations.d.ts +5 -0
  40. package/dist/domain/video-generation-capabilities.d.ts +9 -0
  41. package/dist/domain/video-generation.d.ts +47 -0
  42. package/dist/editable-artifact-live/application.d.ts +69 -0
  43. package/dist/editable-artifact-live/errors.d.ts +1 -0
  44. package/dist/editable-artifact-live/index.d.ts +8 -0
  45. package/dist/editable-artifact-live/outbox-dispatcher.d.ts +162 -0
  46. package/dist/editable-artifact-live/ports.d.ts +100 -0
  47. package/dist/editable-artifact-live/server.d.ts +71 -0
  48. package/dist/editable-artifact-live/ticket.d.ts +40 -0
  49. package/dist/editable-artifact-live/types.d.ts +213 -0
  50. package/dist/editable-artifact-live/wire.d.ts +34 -0
  51. package/dist/editable-artifact-live.d.ts +2 -0
  52. package/dist/editable-artifact-live.js +58 -0
  53. package/dist/editable-artifact-live.js.map +1 -0
  54. package/dist/editable-artifacts.d.ts +2 -0
  55. package/dist/editable-artifacts.js +179 -0
  56. package/dist/editable-artifacts.js.map +1 -0
  57. package/dist/index.d.ts +35 -25
  58. package/dist/index.js +3184 -652
  59. package/dist/index.js.map +1 -1
  60. package/dist/managed-session.d.ts +53 -0
  61. package/dist/rigs/index.d.ts +1 -1
  62. package/dist/rigs/provider-images.d.ts +31 -0
  63. package/dist/sandbox/fleet.d.ts +9 -1
  64. package/dist/sandbox/routing.d.ts +1 -0
  65. package/dist/session-authorization.d.ts +10 -1
  66. package/dist/workflow-wake-contract.d.ts +7 -0
  67. package/package.json +18 -10
  68. package/src/access/index.ts +43 -3
  69. package/src/application/new-session-drafts.ts +2 -0
  70. package/src/application/session-commands.ts +217 -116
  71. package/src/dependencies.ts +30 -2
  72. package/src/domain/capabilities.ts +672 -218
  73. package/src/domain/company-profile-durable-learning-adapter.ts +221 -0
  74. package/src/domain/durable-learning-slack-publication.ts +202 -0
  75. package/src/domain/editable-artifacts/agent-application.ts +687 -0
  76. package/src/domain/editable-artifacts/durable-export-postgres.ts +139 -0
  77. package/src/domain/editable-artifacts/durable-export.ts +734 -0
  78. package/src/domain/editable-artifacts/errors.ts +115 -0
  79. package/src/domain/editable-artifacts/hash.ts +131 -0
  80. package/src/domain/editable-artifacts/in-memory.ts +1662 -0
  81. package/src/domain/editable-artifacts/index.ts +14 -0
  82. package/src/domain/editable-artifacts/ports.ts +462 -0
  83. package/src/domain/editable-artifacts/postgres-adapters.ts +49 -0
  84. package/src/domain/editable-artifacts/service.ts +2402 -0
  85. package/src/domain/editable-artifacts/snapshot-compaction.ts +246 -0
  86. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +235 -0
  87. package/src/domain/editable-artifacts/snapshot-verifier-materialization.ts +74 -0
  88. package/src/domain/editable-artifacts/snapshot-verifier.ts +687 -0
  89. package/src/domain/editable-artifacts/types.ts +835 -0
  90. package/src/domain/insights.ts +136 -33
  91. package/src/domain/memory-slack-delivery.ts +204 -0
  92. package/src/domain/memory-slack-publication.ts +10 -19
  93. package/src/domain/packs.ts +266 -5
  94. package/src/domain/personal-connection-delegations.ts +97 -6
  95. package/src/domain/resources.ts +10 -20
  96. package/src/domain/scheduled-tasks.ts +365 -25
  97. package/src/domain/session-tool-policy.ts +8 -2
  98. package/src/domain/sessions.ts +388 -155
  99. package/src/domain/skill-imports.ts +350 -0
  100. package/src/domain/slack-publication-secret-safety.ts +42 -0
  101. package/src/domain/timeline-annotations.ts +204 -0
  102. package/src/domain/video-generation-capabilities.ts +69 -0
  103. package/src/domain/video-generation.ts +249 -0
  104. package/src/editable-artifact-live/application.ts +182 -0
  105. package/src/editable-artifact-live/errors.ts +4 -0
  106. package/src/editable-artifact-live/index.ts +8 -0
  107. package/src/editable-artifact-live/outbox-dispatcher.ts +916 -0
  108. package/src/editable-artifact-live/ports.ts +139 -0
  109. package/src/editable-artifact-live/server.ts +1707 -0
  110. package/src/editable-artifact-live/ticket.ts +217 -0
  111. package/src/editable-artifact-live/types.ts +286 -0
  112. package/src/editable-artifact-live/wire.ts +95 -0
  113. package/src/editable-artifact-live.ts +2 -0
  114. package/src/editable-artifacts.ts +2 -0
  115. package/src/index.ts +14 -0
  116. package/src/managed-auth-type.ts +4 -3
  117. package/src/managed-session.ts +36 -0
  118. package/src/rigs/index.ts +26 -19
  119. package/src/rigs/provider-images.ts +110 -0
  120. package/src/sandbox/fleet.ts +56 -1
  121. package/src/sandbox/routing.ts +32 -3
  122. package/src/session-authorization.ts +20 -0
  123. package/src/workflow-wake-contract.ts +8 -0
@@ -0,0 +1,1662 @@
1
+ import {
2
+ EditableArtifactIdempotencyConflictError,
3
+ EditableArtifactNotFoundError,
4
+ EditableArtifactOutboxLeaseConflictError,
5
+ } from "./errors";
6
+ import { decodeCommittedTransactionSummary } from "@opengeni/contracts/editable-artifact-committed-transaction";
7
+ import { decodeEditableArtifactSerializedCommit } from "@opengeni/contracts/editable-artifact-serialized-commit";
8
+ import type {
9
+ ClaimEditableArtifactLiveOutboxRequest,
10
+ CommitAppliedEditableArtifactTransaction,
11
+ CommitEditableArtifactSnapshot,
12
+ CreateEditableArtifactStoreRequest,
13
+ CreateEditableArtifactStoreResult,
14
+ EditableArtifactAuthorizationRevisionPort,
15
+ EditableArtifactScopeAuthorizationRevisionPort,
16
+ EditableArtifactClockPort,
17
+ EditableArtifactKernelState,
18
+ EditableArtifactStableIdFactoryPort,
19
+ EditableArtifactStableIdKind,
20
+ EditableArtifactStorePort,
21
+ EditableArtifactTransactionBasis,
22
+ EditableArtifactSnapshotUnitOfWorkPort,
23
+ ReadEditableArtifactTransactionBasisRequest,
24
+ ReadEditableArtifactAtAuthorizationRevisionResult,
25
+ TryCommitAppliedEditableArtifactTransactionRequest,
26
+ TryCommitAppliedEditableArtifactTransactionResult,
27
+ } from "./ports";
28
+ import {
29
+ assertBoundedArtifactTitle,
30
+ assertIsoTimestamp,
31
+ assertNonnegativeSafeInteger,
32
+ assertPositiveSafeInteger,
33
+ causalFrontiersEqual,
34
+ editableArtifactCausalFrontier,
35
+ editableArtifactClientTransactionId,
36
+ editableArtifactId,
37
+ editableArtifactOperationId,
38
+ editableArtifactOutboxId,
39
+ editableArtifactReceiptId,
40
+ editableArtifactRequestHash,
41
+ editableArtifactScope,
42
+ editableArtifactStateHash,
43
+ type EditableArtifact,
44
+ type CreateEditableArtifactResult,
45
+ type EditableArtifactCreationReceipt,
46
+ type EditableArtifactCommittedTransactionRecord,
47
+ type EditableArtifactId,
48
+ type EditableArtifactLifecycleState,
49
+ type EditableArtifactLiveOutboxRecord,
50
+ type EditableArtifactModality,
51
+ type EditableArtifactOperationId,
52
+ type EditableArtifactOperationRecord,
53
+ type EditableArtifactOutboxId,
54
+ type EditableArtifactOutboxRetryFailureCode,
55
+ type EditableArtifactOutboxDeadLetterFailureCode,
56
+ type EditableArtifactReceipt,
57
+ type EditableArtifactScope,
58
+ type EditableArtifactSequenceCheckpoint,
59
+ type EditableArtifactSnapshotId,
60
+ type EditableArtifactSnapshotMetadata,
61
+ type EditableArtifactStableId,
62
+ type EditableArtifactStateHash,
63
+ type EditableArtifactSpreadsheetCommittedTransactionRecord,
64
+ type EditableArtifactSerializedCommittedTransactionRecord,
65
+ type EditableArtifactSerializedSnapshotMetadata,
66
+ type EditableArtifactSpreadsheetSnapshotMetadata,
67
+ type EditableArtifactTransactionId,
68
+ } from "./types";
69
+
70
+ type ArtifactAggregate = {
71
+ artifact: EditableArtifact;
72
+ receipts: Map<string, EditableArtifactReceipt>;
73
+ transactions: Map<EditableArtifactTransactionId, EditableArtifactReceipt>;
74
+ committedTransactions: Map<
75
+ EditableArtifactTransactionId,
76
+ EditableArtifactCommittedTransactionRecord
77
+ >;
78
+ operations: Map<EditableArtifactOperationId, EditableArtifactOperationRecord>;
79
+ undoClaims: Map<EditableArtifactOperationId, EditableArtifactTransactionId>;
80
+ snapshots: Map<EditableArtifactSnapshotId, EditableArtifactSnapshotMetadata>;
81
+ checkpoints: Map<number, EditableArtifactSequenceCheckpoint>;
82
+ outbox: Map<EditableArtifactOutboxId, EditableArtifactLiveOutboxRecord>;
83
+ };
84
+
85
+ export type SeedInMemoryEditableArtifact = Readonly<{
86
+ scope: EditableArtifactScope;
87
+ artifactId: EditableArtifactId;
88
+ modality: EditableArtifactModality;
89
+ title: string;
90
+ stateHash: EditableArtifactStateHash;
91
+ lifecycle?: EditableArtifactLifecycleState;
92
+ authorizationRevision?: number;
93
+ /** Authoritative native revision for a pre-existing serialized artifact. */
94
+ nativeRevision?: number;
95
+ createdAt?: string;
96
+ }>;
97
+
98
+ /**
99
+ * Executable correctness reference for tests. It deliberately serializes and
100
+ * copies aggregate history; production runtimes must use the PostgreSQL CAS
101
+ * adapter rather than treating this O(history) store as a performance model.
102
+ */
103
+ export class InMemoryEditableArtifactStore
104
+ implements
105
+ EditableArtifactStorePort,
106
+ EditableArtifactAuthorizationRevisionPort,
107
+ EditableArtifactScopeAuthorizationRevisionPort
108
+ {
109
+ private aggregates = new Map<string, ArtifactAggregate>();
110
+ private creationReceipts = new Map<string, EditableArtifactCreationReceipt>();
111
+ private scopeCreateAuthorizationRevisions = new Map<string, number>();
112
+ private exclusiveTail: Promise<void> = Promise.resolve();
113
+
114
+ constructor(private readonly now: () => Date = () => new Date()) {}
115
+
116
+ async findArtifactCreation(
117
+ scopeInput: EditableArtifactScope,
118
+ operationKind: "create" | "import",
119
+ authorityKey: string,
120
+ idempotencyKeyInput: string,
121
+ ): Promise<CreateEditableArtifactResult | null> {
122
+ const scope = editableArtifactScope(scopeInput);
123
+ const idempotencyKey = editableArtifactClientTransactionId(idempotencyKeyInput);
124
+ const key = creationReceiptKey(scope, operationKind, authorityKey, idempotencyKey);
125
+ return this.exclusive(async () => {
126
+ const receipt = this.creationReceipts.get(key);
127
+ if (!receipt) return null;
128
+ const aggregate = this.aggregates.get(aggregateKey(scope, receipt.artifactId));
129
+ const snapshot = aggregate?.snapshots.get(receipt.genesisSnapshotId);
130
+ if (!aggregate || !snapshot) {
131
+ throw new Error("Artifact creation receipt is corrupt");
132
+ }
133
+ return Object.freeze({
134
+ artifact: cloneArtifact(aggregate.artifact),
135
+ genesisSnapshot: cloneSnapshot(snapshot),
136
+ creationReceipt: cloneCreationReceipt(receipt),
137
+ replayed: true,
138
+ });
139
+ });
140
+ }
141
+
142
+ async createArtifact(
143
+ input: CreateEditableArtifactStoreRequest,
144
+ ): Promise<CreateEditableArtifactStoreResult> {
145
+ const scope = editableArtifactScope(input.scope);
146
+ const artifactId = editableArtifactId(input.artifactId);
147
+ const receiptId = editableArtifactReceiptId(input.receiptId);
148
+ const idempotencyKey = editableArtifactClientTransactionId(input.idempotencyKey);
149
+ const requestHash = editableArtifactRequestHash(input.requestHash);
150
+ if (input.operationKind !== "create" && input.operationKind !== "import") {
151
+ throw new TypeError("Unknown editable artifact origin operation");
152
+ }
153
+ if ((input.operationKind === "import") !== Boolean(input.originalImport)) {
154
+ throw new TypeError("Editable artifact import source does not match its origin operation");
155
+ }
156
+ assertBoundedArtifactTitle(input.title);
157
+ assertPositiveSafeInteger(
158
+ input.expectedScopeAuthorizationRevision,
159
+ "expected scope create authorization revision",
160
+ );
161
+ assertPositiveSafeInteger(
162
+ input.initialArtifactAuthorizationRevision,
163
+ "initial artifact authorization revision",
164
+ );
165
+ const genesisSnapshot = cloneSnapshot(input.genesisSnapshot);
166
+ const outbox = cloneOutbox(input.outbox);
167
+ assertGenesisCreateRequest(scope, artifactId, input.operationKind, genesisSnapshot, outbox);
168
+ if (!(["spreadsheet", "presentation", "document"] as const).includes(input.modality)) {
169
+ throw new TypeError("Unknown editable artifact modality");
170
+ }
171
+ if (genesisSnapshot.modality !== input.modality) {
172
+ throw new TypeError("Genesis snapshot modality does not match artifact modality");
173
+ }
174
+ const creationKey = creationReceiptKey(
175
+ scope,
176
+ input.operationKind,
177
+ input.authorityKey,
178
+ idempotencyKey,
179
+ );
180
+ return this.exclusive(async () => {
181
+ const prior = this.creationReceipts.get(creationKey);
182
+ if (prior) {
183
+ if (prior.requestHash !== requestHash) {
184
+ throw new EditableArtifactIdempotencyConflictError();
185
+ }
186
+ const replay = this.aggregates.get(aggregateKey(scope, prior.artifactId));
187
+ if (!replay) throw new Error("Artifact creation receipt is corrupt");
188
+ const replaySnapshot = replay.snapshots.get(prior.genesisSnapshotId);
189
+ if (!replaySnapshot) {
190
+ throw new Error("Artifact creation receipt has no genesis snapshot");
191
+ }
192
+ return Object.freeze({
193
+ kind: "result" as const,
194
+ value: Object.freeze({
195
+ artifact: cloneArtifact(replay.artifact),
196
+ genesisSnapshot: cloneSnapshot(replaySnapshot),
197
+ creationReceipt: cloneCreationReceipt(prior),
198
+ replayed: true,
199
+ }),
200
+ });
201
+ }
202
+ const scopeKey = authorizationScopeKey(scope);
203
+ const currentScopeRevision = this.scopeCreateAuthorizationRevisions.get(scopeKey) ?? 1;
204
+ if (currentScopeRevision !== input.expectedScopeAuthorizationRevision) {
205
+ return Object.freeze({ kind: "authorization_stale" as const });
206
+ }
207
+ const key = aggregateKey(scope, artifactId);
208
+ if (this.aggregates.has(key)) {
209
+ throw new Error("Editable artifact identity already exists");
210
+ }
211
+ const commonArtifact = {
212
+ scope,
213
+ id: artifactId,
214
+ title: input.title,
215
+ lifecycle: "active",
216
+ authorizationRevision: input.initialArtifactAuthorizationRevision,
217
+ headSequence: 0,
218
+ stateHash: genesisSnapshot.stateHash,
219
+ currentSnapshotId: genesisSnapshot.snapshotId,
220
+ createdAt: genesisSnapshot.publishedAt,
221
+ updatedAt: genesisSnapshot.publishedAt,
222
+ } as const;
223
+ const artifact: EditableArtifact =
224
+ input.modality === "spreadsheet"
225
+ ? {
226
+ ...commonArtifact,
227
+ modality: "spreadsheet",
228
+ causalFrontier: editableArtifactCausalFrontier(
229
+ genesisSnapshot.modality === "spreadsheet"
230
+ ? genesisSnapshot.coveredCausalFrontier
231
+ : [],
232
+ ),
233
+ }
234
+ : { ...commonArtifact, modality: input.modality };
235
+ const creationReceipt: EditableArtifactCreationReceipt = Object.freeze({
236
+ receiptId,
237
+ scope,
238
+ artifactId,
239
+ operationKind: input.operationKind,
240
+ authorityKey: input.authorityKey,
241
+ idempotencyKey,
242
+ requestHash,
243
+ genesisSnapshotId: genesisSnapshot.snapshotId,
244
+ createdAt: genesisSnapshot.publishedAt,
245
+ });
246
+ this.aggregates.set(key, emptyAggregate(artifact, genesisSnapshot, outbox));
247
+ this.creationReceipts.set(creationKey, creationReceipt);
248
+ return Object.freeze({
249
+ kind: "result" as const,
250
+ value: Object.freeze({
251
+ artifact: cloneArtifact(artifact),
252
+ genesisSnapshot: cloneSnapshot(genesisSnapshot),
253
+ creationReceipt: cloneCreationReceipt(creationReceipt),
254
+ replayed: false,
255
+ }),
256
+ });
257
+ });
258
+ }
259
+
260
+ async readScopeCreateAuthorizationRevision(scope: EditableArtifactScope): Promise<number> {
261
+ const key = authorizationScopeKey(editableArtifactScope(scope));
262
+ return this.exclusive(async () => {
263
+ const revision = this.scopeCreateAuthorizationRevisions.get(key) ?? 1;
264
+ this.scopeCreateAuthorizationRevisions.set(key, revision);
265
+ return revision;
266
+ });
267
+ }
268
+
269
+ async advanceScopeCreateAuthorizationRevision(
270
+ scope: EditableArtifactScope,
271
+ expectedRevision: number,
272
+ nextRevision: number,
273
+ ): Promise<Readonly<{ applied: boolean; authorizationRevision: number }>> {
274
+ assertPositiveSafeInteger(expectedRevision, "expected scope create authorization revision");
275
+ assertPositiveSafeInteger(nextRevision, "next scope create authorization revision");
276
+ if (nextRevision <= expectedRevision) {
277
+ throw new TypeError(
278
+ "Next scope create authorization revision must exceed the expected revision",
279
+ );
280
+ }
281
+ const key = authorizationScopeKey(editableArtifactScope(scope));
282
+ return this.exclusive(async () => {
283
+ const current = this.scopeCreateAuthorizationRevisions.get(key) ?? 1;
284
+ if (current !== expectedRevision) {
285
+ return Object.freeze({
286
+ applied: false,
287
+ authorizationRevision: current,
288
+ });
289
+ }
290
+ this.scopeCreateAuthorizationRevisions.set(key, nextRevision);
291
+ return Object.freeze({
292
+ applied: true,
293
+ authorizationRevision: nextRevision,
294
+ });
295
+ });
296
+ }
297
+
298
+ async seedArtifact(input: SeedInMemoryEditableArtifact): Promise<EditableArtifact> {
299
+ const scope = editableArtifactScope(input.scope);
300
+ const artifactId = editableArtifactId(input.artifactId);
301
+ assertBoundedArtifactTitle(input.title);
302
+ const stateHash = editableArtifactStateHash(input.stateHash);
303
+ const createdAt = input.createdAt ?? new Date(0).toISOString();
304
+ assertIsoTimestamp(createdAt, "artifact creation timestamp");
305
+ if (!(["spreadsheet", "presentation", "document"] as const).includes(input.modality)) {
306
+ throw new TypeError("Unknown editable artifact modality");
307
+ }
308
+ const lifecycle = input.lifecycle ?? "active";
309
+ if (lifecycle !== "active" && lifecycle !== "archived") {
310
+ throw new TypeError("Unknown editable artifact lifecycle state");
311
+ }
312
+ const authorizationRevision = input.authorizationRevision ?? 1;
313
+ assertPositiveSafeInteger(authorizationRevision, "artifact authorization revision");
314
+ const title = input.title;
315
+ const modality = input.modality;
316
+ const nativeRevision = input.nativeRevision ?? 0;
317
+ assertNonnegativeSafeInteger(nativeRevision, "artifact native revision");
318
+ return this.exclusive(async () => {
319
+ const key = aggregateKey(scope, artifactId);
320
+ if (this.aggregates.has(key)) throw new Error("Editable artifact is already seeded");
321
+ const commonArtifact = {
322
+ scope,
323
+ id: artifactId,
324
+ title,
325
+ lifecycle,
326
+ authorizationRevision,
327
+ headSequence: 0,
328
+ stateHash,
329
+ currentSnapshotId: null,
330
+ createdAt,
331
+ updatedAt: createdAt,
332
+ } as const;
333
+ const artifact: EditableArtifact =
334
+ modality === "spreadsheet"
335
+ ? {
336
+ ...commonArtifact,
337
+ modality: "spreadsheet",
338
+ causalFrontier: editableArtifactCausalFrontier([]),
339
+ }
340
+ : { ...commonArtifact, modality };
341
+ this.aggregates.set(key, {
342
+ artifact,
343
+ receipts: new Map(),
344
+ transactions: new Map(),
345
+ committedTransactions: new Map(),
346
+ operations: new Map(),
347
+ undoClaims: new Map(),
348
+ snapshots: new Map(),
349
+ checkpoints: new Map([[0, checkpointAtGenesis(artifact, nativeRevision)]]),
350
+ outbox: new Map(),
351
+ });
352
+ return cloneArtifact(artifact);
353
+ });
354
+ }
355
+
356
+ async getArtifact(
357
+ scope: EditableArtifactScope,
358
+ artifactId: EditableArtifactId,
359
+ ): Promise<EditableArtifact | null> {
360
+ const key = aggregateKey(editableArtifactScope(scope), editableArtifactId(artifactId));
361
+ return this.exclusive(async () => {
362
+ const aggregate = this.aggregates.get(key);
363
+ return aggregate ? cloneArtifact(aggregate.artifact) : null;
364
+ });
365
+ }
366
+
367
+ async readArtifactAtAuthorizationRevision(
368
+ scope: EditableArtifactScope,
369
+ artifactId: EditableArtifactId,
370
+ expectedAuthorizationRevision: number,
371
+ ): Promise<ReadEditableArtifactAtAuthorizationRevisionResult> {
372
+ assertPositiveSafeInteger(
373
+ expectedAuthorizationRevision,
374
+ "expected artifact authorization revision",
375
+ );
376
+ const key = aggregateKey(editableArtifactScope(scope), editableArtifactId(artifactId));
377
+ return this.exclusive(async () => {
378
+ const aggregate = this.aggregates.get(key);
379
+ if (!aggregate) {
380
+ return Object.freeze({ kind: "result" as const, artifact: null });
381
+ }
382
+ if (aggregate.artifact.authorizationRevision !== expectedAuthorizationRevision) {
383
+ return Object.freeze({ kind: "authorization_stale" as const });
384
+ }
385
+ return Object.freeze({
386
+ kind: "result" as const,
387
+ artifact: cloneArtifact(aggregate.artifact),
388
+ });
389
+ });
390
+ }
391
+
392
+ /** Test/admin seam modeling the local policy invalidation transaction. */
393
+ async advanceAuthorizationRevision(
394
+ scope: EditableArtifactScope,
395
+ artifactId: EditableArtifactId,
396
+ expectedRevision: number,
397
+ nextRevision: number,
398
+ ): Promise<Readonly<{ applied: boolean; authorizationRevision: number }>> {
399
+ assertPositiveSafeInteger(expectedRevision, "expected artifact authorization revision");
400
+ assertPositiveSafeInteger(nextRevision, "next artifact authorization revision");
401
+ if (nextRevision <= expectedRevision) {
402
+ throw new TypeError("Next artifact authorization revision must exceed the expected revision");
403
+ }
404
+ const key = aggregateKey(editableArtifactScope(scope), editableArtifactId(artifactId));
405
+ return this.exclusive(async () => {
406
+ const aggregate = this.aggregates.get(key);
407
+ if (!aggregate) throw new EditableArtifactNotFoundError();
408
+ if (aggregate.artifact.authorizationRevision !== expectedRevision) {
409
+ return Object.freeze({
410
+ applied: false,
411
+ authorizationRevision: aggregate.artifact.authorizationRevision,
412
+ });
413
+ }
414
+ const updatedAt = authoritativeNow(this.now).toISOString();
415
+ aggregate.artifact = {
416
+ ...aggregate.artifact,
417
+ authorizationRevision: nextRevision,
418
+ updatedAt: laterIsoTimestamp(aggregate.artifact.updatedAt, updatedAt),
419
+ };
420
+ return Object.freeze({
421
+ applied: true,
422
+ authorizationRevision: nextRevision,
423
+ });
424
+ });
425
+ }
426
+
427
+ async readTransactionBasis(
428
+ scope: EditableArtifactScope,
429
+ artifactId: EditableArtifactId,
430
+ request: ReadEditableArtifactTransactionBasisRequest,
431
+ ): Promise<EditableArtifactTransactionBasis> {
432
+ const normalizedScope = editableArtifactScope(scope);
433
+ const normalizedArtifactId = editableArtifactId(artifactId);
434
+ const key = aggregateKey(normalizedScope, normalizedArtifactId);
435
+ return this.exclusive(async () => {
436
+ const aggregate = this.aggregates.get(key);
437
+ if (!aggregate) throw new EditableArtifactNotFoundError();
438
+ const priorReceipt = aggregate.receipts.get(
439
+ receiptKey(request.actorKey, request.clientTransactionId),
440
+ );
441
+ if (priorReceipt) {
442
+ return Object.freeze({
443
+ kind: "existing",
444
+ receipt: cloneReceipt(priorReceipt),
445
+ });
446
+ }
447
+ const predecessor = request.previousLocalTransactionId
448
+ ? aggregate.receipts.get(receiptKey(request.actorKey, request.previousLocalTransactionId))
449
+ : undefined;
450
+ const undoTargets: Extract<
451
+ EditableArtifactTransactionBasis,
452
+ { kind: "basis" }
453
+ >["undoTargets"] = Object.freeze(
454
+ request.selectiveUndoOperationIds.map((operationId) =>
455
+ Object.freeze({
456
+ operationId,
457
+ operation: aggregate.operations.has(operationId)
458
+ ? cloneOperation(aggregate.operations.get(operationId)!)
459
+ : null,
460
+ claimedBy: aggregate.undoClaims.get(operationId) ?? null,
461
+ }),
462
+ ),
463
+ );
464
+ const kernelState = kernelStateFromAggregate(aggregate);
465
+ return Object.freeze({
466
+ kind: "basis",
467
+ artifact: kernelState.artifact,
468
+ predecessor: predecessor ? cloneReceipt(predecessor) : null,
469
+ undoTargets,
470
+ kernelState,
471
+ });
472
+ });
473
+ }
474
+
475
+ async readCurrentKernelState(
476
+ scope: EditableArtifactScope,
477
+ artifactId: EditableArtifactId,
478
+ expectedAuthorizationRevision: number,
479
+ ): Promise<
480
+ | Readonly<{ kind: "basis"; state: EditableArtifactKernelState }>
481
+ | Readonly<{ kind: "authorization_stale" }>
482
+ > {
483
+ const normalizedScope = editableArtifactScope(scope);
484
+ const normalizedArtifactId = editableArtifactId(artifactId);
485
+ const key = aggregateKey(normalizedScope, normalizedArtifactId);
486
+ return this.exclusive(async () => {
487
+ const aggregate = this.aggregates.get(key);
488
+ if (!aggregate) throw new EditableArtifactNotFoundError();
489
+ if (aggregate.artifact.authorizationRevision !== expectedAuthorizationRevision) {
490
+ return Object.freeze({ kind: "authorization_stale" as const });
491
+ }
492
+ return Object.freeze({ kind: "basis" as const, state: kernelStateFromAggregate(aggregate) });
493
+ });
494
+ }
495
+
496
+ async tryCommitAppliedTransaction(
497
+ request: TryCommitAppliedEditableArtifactTransactionRequest,
498
+ ): Promise<TryCommitAppliedEditableArtifactTransactionResult> {
499
+ const scope = editableArtifactScope(request.scope);
500
+ const artifactId = editableArtifactId(request.artifactId);
501
+ const key = aggregateKey(scope, artifactId);
502
+ return this.exclusive(async () => {
503
+ const aggregate = this.aggregates.get(key);
504
+ if (!aggregate) throw new EditableArtifactNotFoundError();
505
+
506
+ // The idempotency winner is authoritative even when its commit changed
507
+ // the head before this candidate reached the CAS.
508
+ const prior = aggregate.receipts.get(
509
+ receiptKey(request.actorKey, request.clientTransactionId),
510
+ );
511
+ if (prior) {
512
+ if (prior.requestHash !== request.requestHash) {
513
+ throw new EditableArtifactIdempotencyConflictError();
514
+ }
515
+ return Object.freeze({
516
+ kind: "replayed",
517
+ receipt: cloneReceipt(prior),
518
+ });
519
+ }
520
+
521
+ const artifact = aggregate.artifact;
522
+ if (
523
+ artifact.lifecycle !== request.expectedLifecycle ||
524
+ artifact.authorizationRevision !== request.expectedAuthorizationRevision ||
525
+ artifact.headSequence !== request.expectedHeadSequence
526
+ ) {
527
+ return Object.freeze({ kind: "stale" });
528
+ }
529
+ if (!sameExpectedPredecessor(aggregate, request.expectedPredecessor)) {
530
+ return Object.freeze({ kind: "stale" });
531
+ }
532
+ if (
533
+ request.expectedUnclaimedUndoTargets.some((operationId) =>
534
+ aggregate.undoClaims.has(operationId),
535
+ )
536
+ ) {
537
+ return Object.freeze({ kind: "stale" });
538
+ }
539
+
540
+ const working = cloneAggregate(aggregate);
541
+ commitAppliedTransaction(working, request);
542
+ // No callback can retain `working` on this path; every caller-owned
543
+ // buffer was copied by commitAppliedTransaction.
544
+ this.aggregates.set(key, working);
545
+ return Object.freeze({
546
+ kind: "committed",
547
+ receipt: cloneReceipt(request.receipt),
548
+ });
549
+ });
550
+ }
551
+
552
+ async withSnapshotPublicationLock<T>(
553
+ scope: EditableArtifactScope,
554
+ artifactId: EditableArtifactId,
555
+ callback: (unitOfWork: EditableArtifactSnapshotUnitOfWorkPort) => Promise<T>,
556
+ ): Promise<T> {
557
+ const key = aggregateKey(editableArtifactScope(scope), editableArtifactId(artifactId));
558
+ return this.exclusive(async () => {
559
+ const aggregate = this.aggregates.get(key);
560
+ if (!aggregate) throw new EditableArtifactNotFoundError();
561
+ const working = cloneAggregate(aggregate);
562
+ const unitOfWork = new InMemoryEditableArtifactSnapshotUnitOfWork(working);
563
+ try {
564
+ const result = await callback(unitOfWork);
565
+ // Never install the aggregate still referenced by the callback's UoW.
566
+ this.aggregates.set(key, cloneAggregate(working));
567
+ return result;
568
+ } finally {
569
+ unitOfWork.close();
570
+ }
571
+ });
572
+ }
573
+
574
+ async claimLiveOutbox(
575
+ request: ClaimEditableArtifactLiveOutboxRequest,
576
+ ): Promise<readonly EditableArtifactLiveOutboxRecord[]> {
577
+ assertLeaseRequest(request);
578
+ const owner = request.owner;
579
+ const leaseDurationMs = request.leaseDurationMs;
580
+ const limit = request.limit;
581
+ return this.exclusive(async () => {
582
+ const nowMs = authoritativeNow(this.now).getTime();
583
+ const eligible = [...this.aggregates.values()]
584
+ .flatMap((aggregate) =>
585
+ [...aggregate.outbox.values()].map((record) => ({
586
+ aggregate,
587
+ record,
588
+ })),
589
+ )
590
+ .filter(({ record }) => {
591
+ if (record.state === "pending") {
592
+ return new Date(record.nextAttemptAt).getTime() <= nowMs;
593
+ }
594
+ if (record.state !== "publishing" || !record.leaseExpiresAt) return false;
595
+ return new Date(record.leaseExpiresAt).getTime() <= nowMs;
596
+ })
597
+ .sort(
598
+ (left, right) =>
599
+ compareText(left.record.createdAt, right.record.createdAt) ||
600
+ compareText(left.record.outboxId, right.record.outboxId),
601
+ )
602
+ .slice(0, limit);
603
+ const leaseExpiresAt = new Date(nowMs + leaseDurationMs).toISOString();
604
+ return Object.freeze(
605
+ eligible.map(({ aggregate, record }) => {
606
+ if (record.attemptCount >= Number.MAX_SAFE_INTEGER) {
607
+ throw new Error("Editable artifact outbox attempt counter exhausted");
608
+ }
609
+ const claimed: EditableArtifactLiveOutboxRecord = {
610
+ ...record,
611
+ state: "publishing",
612
+ attemptCount: record.attemptCount + 1,
613
+ leaseOwner: owner,
614
+ leaseExpiresAt,
615
+ };
616
+ aggregate.outbox.set(record.outboxId, claimed);
617
+ return cloneOutbox(claimed);
618
+ }),
619
+ );
620
+ });
621
+ }
622
+
623
+ async markLiveOutboxPublished(input: {
624
+ outboxId: EditableArtifactOutboxId;
625
+ owner: string;
626
+ attemptCount: number;
627
+ }): Promise<void> {
628
+ const outboxId = editableArtifactOutboxId(input.outboxId);
629
+ const owner = assertLeaseOwner(input.owner);
630
+ assertPositiveSafeInteger(input.attemptCount, "outbox attempt count");
631
+ await this.exclusive(async () => {
632
+ const publishedAt = authoritativeNow(this.now);
633
+ const publishedAtMs = publishedAt.getTime();
634
+ const publishedAtIso = publishedAt.toISOString();
635
+ const located = this.findOutbox(outboxId);
636
+ if (!located) throw new EditableArtifactOutboxLeaseConflictError();
637
+ const record = located.record;
638
+ if (record.state === "published") {
639
+ if (record.attemptCount === input.attemptCount) return;
640
+ throw new EditableArtifactOutboxLeaseConflictError();
641
+ }
642
+ if (
643
+ record.state !== "publishing" ||
644
+ record.leaseOwner !== owner ||
645
+ record.attemptCount !== input.attemptCount ||
646
+ !record.leaseExpiresAt ||
647
+ publishedAtMs >= new Date(record.leaseExpiresAt).getTime()
648
+ ) {
649
+ throw new EditableArtifactOutboxLeaseConflictError();
650
+ }
651
+ located.aggregate.outbox.set(outboxId, {
652
+ ...record,
653
+ state: "published",
654
+ leaseOwner: null,
655
+ leaseExpiresAt: null,
656
+ publishedAt: publishedAtIso,
657
+ });
658
+ });
659
+ }
660
+
661
+ async releaseLiveOutbox(input: {
662
+ outboxId: EditableArtifactOutboxId;
663
+ owner: string;
664
+ attemptCount: number;
665
+ }): Promise<void> {
666
+ const outboxId = editableArtifactOutboxId(input.outboxId);
667
+ const owner = assertLeaseOwner(input.owner);
668
+ assertPositiveSafeInteger(input.attemptCount, "outbox attempt count");
669
+ await this.exclusive(async () => {
670
+ const releasedAt = authoritativeNow(this.now).toISOString();
671
+ const located = this.findOutbox(outboxId);
672
+ if (!located) throw new EditableArtifactOutboxLeaseConflictError();
673
+ if (
674
+ (located.record.state === "published" ||
675
+ (located.record.state === "pending" &&
676
+ located.record.leaseOwner === null &&
677
+ located.record.leaseExpiresAt === null)) &&
678
+ located.record.attemptCount === input.attemptCount
679
+ ) {
680
+ return;
681
+ }
682
+ if (
683
+ located.record.state !== "publishing" ||
684
+ located.record.leaseOwner !== owner ||
685
+ located.record.attemptCount !== input.attemptCount
686
+ ) {
687
+ throw new EditableArtifactOutboxLeaseConflictError();
688
+ }
689
+ located.aggregate.outbox.set(outboxId, {
690
+ ...located.record,
691
+ state: "pending",
692
+ leaseOwner: null,
693
+ leaseExpiresAt: null,
694
+ nextAttemptAt: releasedAt,
695
+ });
696
+ });
697
+ }
698
+
699
+ async renewLiveOutbox(input: {
700
+ outboxId: EditableArtifactOutboxId;
701
+ owner: string;
702
+ attemptCount: number;
703
+ leaseDurationMs: number;
704
+ }): Promise<void> {
705
+ const outboxId = editableArtifactOutboxId(input.outboxId);
706
+ const owner = assertLeaseOwner(input.owner);
707
+ assertPositiveSafeInteger(input.attemptCount, "outbox attempt count");
708
+ assertLeaseDuration(input.leaseDurationMs);
709
+ await this.exclusive(async () => {
710
+ const nowMs = authoritativeNow(this.now).getTime();
711
+ const located = this.findOutbox(outboxId);
712
+ if (!located) throw new EditableArtifactOutboxLeaseConflictError();
713
+ assertActiveOutboxLease(located.record, owner, input.attemptCount, nowMs);
714
+ located.aggregate.outbox.set(outboxId, {
715
+ ...located.record,
716
+ leaseExpiresAt: new Date(nowMs + input.leaseDurationMs).toISOString(),
717
+ });
718
+ });
719
+ }
720
+
721
+ async retryLiveOutbox(input: {
722
+ outboxId: EditableArtifactOutboxId;
723
+ owner: string;
724
+ attemptCount: number;
725
+ retryDelayMs: number;
726
+ errorCode: EditableArtifactOutboxRetryFailureCode;
727
+ }): Promise<void> {
728
+ const outboxId = editableArtifactOutboxId(input.outboxId);
729
+ const owner = assertLeaseOwner(input.owner);
730
+ assertPositiveSafeInteger(input.attemptCount, "outbox attempt count");
731
+ assertRetryDelay(input.retryDelayMs);
732
+ assertRetryErrorCode(input.errorCode);
733
+ await this.exclusive(async () => {
734
+ const nowMs = authoritativeNow(this.now).getTime();
735
+ const located = this.findOutbox(outboxId);
736
+ if (!located) throw new EditableArtifactOutboxLeaseConflictError();
737
+ const record = located.record;
738
+ if (
739
+ record.state === "pending" &&
740
+ record.attemptCount === input.attemptCount &&
741
+ record.leaseOwner === null &&
742
+ record.leaseExpiresAt === null &&
743
+ record.lastErrorCode === input.errorCode
744
+ ) {
745
+ return;
746
+ }
747
+ assertActiveOutboxLease(record, owner, input.attemptCount, nowMs);
748
+ located.aggregate.outbox.set(outboxId, {
749
+ ...record,
750
+ state: "pending",
751
+ leaseOwner: null,
752
+ leaseExpiresAt: null,
753
+ nextAttemptAt: new Date(nowMs + input.retryDelayMs).toISOString(),
754
+ lastErrorCode: input.errorCode,
755
+ });
756
+ });
757
+ }
758
+
759
+ async deadLetterLiveOutbox(input: {
760
+ outboxId: EditableArtifactOutboxId;
761
+ owner: string;
762
+ attemptCount: number;
763
+ errorCode: EditableArtifactOutboxDeadLetterFailureCode;
764
+ }): Promise<void> {
765
+ const outboxId = editableArtifactOutboxId(input.outboxId);
766
+ const owner = assertLeaseOwner(input.owner);
767
+ assertPositiveSafeInteger(input.attemptCount, "outbox attempt count");
768
+ assertDeadLetterErrorCode(input.errorCode);
769
+ await this.exclusive(async () => {
770
+ const now = authoritativeNow(this.now);
771
+ const nowMs = now.getTime();
772
+ const located = this.findOutbox(outboxId);
773
+ if (!located) throw new EditableArtifactOutboxLeaseConflictError();
774
+ const record = located.record;
775
+ if (
776
+ record.state === "dead_lettered" &&
777
+ record.attemptCount === input.attemptCount &&
778
+ record.leaseOwner === null &&
779
+ record.leaseExpiresAt === null &&
780
+ record.lastErrorCode === input.errorCode
781
+ ) {
782
+ return;
783
+ }
784
+ assertActiveOutboxLease(record, owner, input.attemptCount, nowMs);
785
+ located.aggregate.outbox.set(outboxId, {
786
+ ...record,
787
+ state: "dead_lettered",
788
+ leaseOwner: null,
789
+ leaseExpiresAt: null,
790
+ lastErrorCode: input.errorCode,
791
+ deadLetteredAt: now.toISOString(),
792
+ });
793
+ });
794
+ }
795
+
796
+ async listReceipts(
797
+ scope: EditableArtifactScope,
798
+ artifactId: EditableArtifactId,
799
+ ): Promise<readonly EditableArtifactReceipt[]> {
800
+ return this.inspectAggregate(scope, artifactId, (aggregate) =>
801
+ Object.freeze(
802
+ [...aggregate.transactions.values()]
803
+ .sort((left, right) => left.sequenceStart - right.sequenceStart)
804
+ .map(cloneReceipt),
805
+ ),
806
+ );
807
+ }
808
+
809
+ async listOperations(
810
+ scope: EditableArtifactScope,
811
+ artifactId: EditableArtifactId,
812
+ ): Promise<readonly EditableArtifactOperationRecord[]> {
813
+ return this.inspectAggregate(scope, artifactId, (aggregate) =>
814
+ Object.freeze(
815
+ [...aggregate.operations.values()]
816
+ .sort((left, right) => left.sequence - right.sequence)
817
+ .map(cloneOperation),
818
+ ),
819
+ );
820
+ }
821
+
822
+ async listCommittedTransactions(
823
+ scope: EditableArtifactScope,
824
+ artifactId: EditableArtifactId,
825
+ ): Promise<readonly EditableArtifactCommittedTransactionRecord[]> {
826
+ return this.inspectAggregate(scope, artifactId, (aggregate) =>
827
+ Object.freeze(
828
+ [...aggregate.committedTransactions.values()]
829
+ .sort((left, right) => left.sequenceStart - right.sequenceStart)
830
+ .map(cloneCommittedTransaction),
831
+ ),
832
+ );
833
+ }
834
+
835
+ async listSnapshots(
836
+ scope: EditableArtifactScope,
837
+ artifactId: EditableArtifactId,
838
+ ): Promise<readonly EditableArtifactSnapshotMetadata[]> {
839
+ return this.inspectAggregate(scope, artifactId, (aggregate) =>
840
+ Object.freeze(
841
+ [...aggregate.snapshots.values()]
842
+ .sort((left, right) => left.coveredHeadSequence - right.coveredHeadSequence)
843
+ .map(cloneSnapshot),
844
+ ),
845
+ );
846
+ }
847
+
848
+ async listOutbox(): Promise<readonly EditableArtifactLiveOutboxRecord[]> {
849
+ return this.exclusive(async () =>
850
+ Object.freeze(
851
+ [...this.aggregates.values()]
852
+ .flatMap((aggregate) => [...aggregate.outbox.values()])
853
+ .sort(
854
+ (left, right) =>
855
+ compareText(left.createdAt, right.createdAt) ||
856
+ compareText(left.outboxId, right.outboxId),
857
+ )
858
+ .map(cloneOutbox),
859
+ ),
860
+ );
861
+ }
862
+
863
+ private async inspectAggregate<T>(
864
+ scope: EditableArtifactScope,
865
+ artifactId: EditableArtifactId,
866
+ inspect: (aggregate: ArtifactAggregate) => T,
867
+ ): Promise<T> {
868
+ const key = aggregateKey(editableArtifactScope(scope), editableArtifactId(artifactId));
869
+ return this.exclusive(async () => {
870
+ const aggregate = this.aggregates.get(key);
871
+ if (!aggregate) throw new EditableArtifactNotFoundError();
872
+ return inspect(aggregate);
873
+ });
874
+ }
875
+
876
+ private findOutbox(
877
+ outboxId: EditableArtifactOutboxId,
878
+ ): { aggregate: ArtifactAggregate; record: EditableArtifactLiveOutboxRecord } | undefined {
879
+ for (const aggregate of this.aggregates.values()) {
880
+ const record = aggregate.outbox.get(outboxId);
881
+ if (record) return { aggregate, record };
882
+ }
883
+ return undefined;
884
+ }
885
+
886
+ private async exclusive<T>(operation: () => Promise<T>): Promise<T> {
887
+ const prior = this.exclusiveTail;
888
+ let release!: () => void;
889
+ this.exclusiveTail = new Promise<void>((resolve) => {
890
+ release = resolve;
891
+ });
892
+ await prior;
893
+ try {
894
+ return await operation();
895
+ } finally {
896
+ release();
897
+ }
898
+ }
899
+ }
900
+
901
+ function commitAppliedTransaction(
902
+ aggregate: ArtifactAggregate,
903
+ input: CommitAppliedEditableArtifactTransaction,
904
+ ): void {
905
+ const artifact = aggregate.artifact;
906
+ if (input.expectedHeadSequence !== artifact.headSequence) {
907
+ throw new Error("In-memory artifact head changed before its CAS commit");
908
+ }
909
+ if (
910
+ input.receipt.modality !== artifact.modality ||
911
+ input.committedTransaction.modality !== artifact.modality ||
912
+ input.receipt.sequenceStart !== artifact.headSequence + 1
913
+ ) {
914
+ throw new Error("Committed transaction modality or interval is invalid");
915
+ }
916
+ if (
917
+ input.receipt.serverTransactionId !== input.serverTransactionId ||
918
+ input.operations.some(
919
+ (operation, index) =>
920
+ operation.serverTransactionId !== input.serverTransactionId ||
921
+ operation.sequence !== input.receipt.sequenceStart + index,
922
+ )
923
+ ) {
924
+ throw new Error("Committed operations are not bound to the transaction interval");
925
+ }
926
+ const committed = input.committedTransaction;
927
+ if (
928
+ committed.serverTransactionId !== input.serverTransactionId ||
929
+ committed.scope.accountId !== artifact.scope.accountId ||
930
+ committed.scope.workspaceId !== artifact.scope.workspaceId ||
931
+ committed.artifactId !== artifact.id ||
932
+ committed.requestHash !== input.receipt.requestHash ||
933
+ committed.sequenceStart !== input.receipt.sequenceStart ||
934
+ committed.sequenceEnd !== input.receipt.sequenceEnd ||
935
+ committed.priorStateHash !== artifact.stateHash ||
936
+ committed.stateHash !== input.receipt.stateHash ||
937
+ !(committed.committedTransactionBytes instanceof Uint8Array) ||
938
+ committed.committedTransactionBytes.byteLength < 1
939
+ ) {
940
+ throw new Error("Canonical committed transaction disagrees with its receipt");
941
+ }
942
+ if (
943
+ input.outbox.event.kind !== "transaction_committed" ||
944
+ input.outbox.event.modality !== artifact.modality ||
945
+ input.outbox.event.serverTransactionId !== input.serverTransactionId ||
946
+ input.outbox.event.sequenceStart !== input.receipt.sequenceStart ||
947
+ input.outbox.event.sequenceEnd !== input.receipt.sequenceEnd ||
948
+ input.outbox.event.stateHash !== input.receipt.stateHash
949
+ ) {
950
+ throw new Error("Transaction outbox projection is inconsistent");
951
+ }
952
+ if (artifact.modality === "spreadsheet") {
953
+ if (
954
+ input.receipt.modality !== "spreadsheet" ||
955
+ committed.modality !== "spreadsheet" ||
956
+ input.outbox.event.modality !== "spreadsheet" ||
957
+ input.operations.length !== input.receipt.operationCount ||
958
+ input.receipt.sequenceEnd !== artifact.headSequence + input.operations.length ||
959
+ committed.operationIds.length !== input.operations.length ||
960
+ committed.operationIds.some(
961
+ (operationId, index) => operationId !== input.operations[index]?.operationId,
962
+ ) ||
963
+ !causalFrontiersEqual(
964
+ committed.resultingCausalFrontier,
965
+ input.receipt.resultingCausalFrontier,
966
+ ) ||
967
+ input.outbox.event.operationProtocolVersion !== input.receipt.operationProtocolVersion
968
+ ) {
969
+ throw new Error("Spreadsheet commit disagrees with its CRDT receipt");
970
+ }
971
+ const summary = decodeCommittedTransactionSummary(committed.committedTransactionBytes);
972
+ if (
973
+ summary.transactionId !== input.serverTransactionId ||
974
+ summary.priorStateHash !== committed.priorStateHash ||
975
+ summary.stateHash !== committed.stateHash ||
976
+ summary.operationIds.length !== committed.operationIds.length
977
+ ) {
978
+ throw new Error("Spreadsheet OGACO metadata disagrees with its record");
979
+ }
980
+ } else {
981
+ if (
982
+ input.receipt.modality !== artifact.modality ||
983
+ committed.modality !== artifact.modality ||
984
+ input.outbox.event.modality !== artifact.modality ||
985
+ input.operations.length !== 0 ||
986
+ input.receipt.sequenceEnd !== input.receipt.sequenceStart ||
987
+ committed.sequenceEnd !== committed.sequenceStart ||
988
+ input.receipt.commitProtocolVersion !== committed.commitProtocolVersion ||
989
+ input.receipt.priorNativeRevision !== committed.priorNativeRevision ||
990
+ input.receipt.nativeRevision !== committed.nativeRevision ||
991
+ input.receipt.commandCount !== committed.commandCount ||
992
+ input.outbox.event.commitProtocolVersion !== input.receipt.commitProtocolVersion
993
+ ) {
994
+ throw new Error("Serialized commit disagrees with its authoritative receipt");
995
+ }
996
+ const summary = decodeEditableArtifactSerializedCommit(
997
+ committed.committedTransactionBytes,
998
+ artifact.modality,
999
+ );
1000
+ if (
1001
+ summary.transactionId !== input.serverTransactionId ||
1002
+ summary.parentHeadSequence !== artifact.headSequence ||
1003
+ summary.resultHeadSequence !== input.receipt.sequenceEnd ||
1004
+ summary.priorNativeRevision !== committed.priorNativeRevision ||
1005
+ summary.nativeReceipt.revision !== committed.nativeRevision ||
1006
+ summary.nativeReceipt.commandCount !== committed.commandCount ||
1007
+ summary.priorStateHash !== artifact.stateHash ||
1008
+ summary.stateHash !== input.receipt.stateHash ||
1009
+ summary.requestHash !== input.receipt.requestHash ||
1010
+ !sameBytes(summary.intentBytes, input.receipt.intentBytes) ||
1011
+ !sameBytes(summary.nativeReceiptBytes, committed.nativeReceiptBytes)
1012
+ ) {
1013
+ throw new Error("Serialized OGAST metadata disagrees with its record");
1014
+ }
1015
+ }
1016
+ const idempotencyKey = receiptKey(input.receipt.actorKey, input.receipt.clientTransactionId);
1017
+ if (
1018
+ aggregate.receipts.has(idempotencyKey) ||
1019
+ aggregate.transactions.has(input.serverTransactionId) ||
1020
+ aggregate.committedTransactions.has(input.serverTransactionId) ||
1021
+ aggregate.outbox.has(input.outbox.outboxId)
1022
+ ) {
1023
+ throw new Error("Duplicate transaction, receipt, or outbox identity");
1024
+ }
1025
+ for (const operation of input.operations) {
1026
+ if (aggregate.operations.has(operation.operationId)) {
1027
+ throw new Error(`Duplicate operation identity: ${operation.operationId}`);
1028
+ }
1029
+ }
1030
+ if (input.receipt.modality === "spreadsheet") {
1031
+ for (const target of input.receipt.selectiveUndoOperationIds) {
1032
+ if (aggregate.undoClaims.has(target)) {
1033
+ throw new Error(`Duplicate selective undo claim: ${target}`);
1034
+ }
1035
+ aggregate.undoClaims.set(target, input.serverTransactionId);
1036
+ }
1037
+ }
1038
+ aggregate.receipts.set(idempotencyKey, cloneReceipt(input.receipt));
1039
+ aggregate.transactions.set(input.serverTransactionId, cloneReceipt(input.receipt));
1040
+ aggregate.committedTransactions.set(
1041
+ input.serverTransactionId,
1042
+ cloneCommittedTransaction(input.committedTransaction),
1043
+ );
1044
+ for (const operation of input.operations) {
1045
+ aggregate.operations.set(operation.operationId, cloneOperation(operation));
1046
+ }
1047
+ aggregate.outbox.set(input.outbox.outboxId, cloneOutbox(input.outbox));
1048
+ if (artifact.modality === "spreadsheet") {
1049
+ if (input.receipt.modality !== "spreadsheet") {
1050
+ throw new Error("Spreadsheet artifact received a serialized receipt");
1051
+ }
1052
+ aggregate.artifact = {
1053
+ ...artifact,
1054
+ headSequence: input.receipt.sequenceEnd,
1055
+ causalFrontier: cloneFrontier(input.receipt.resultingCausalFrontier),
1056
+ stateHash: input.receipt.stateHash,
1057
+ updatedAt: laterIsoTimestamp(artifact.updatedAt, input.receipt.committedAt),
1058
+ };
1059
+ aggregate.checkpoints.set(input.receipt.sequenceEnd, {
1060
+ modality: "spreadsheet",
1061
+ headSequence: input.receipt.sequenceEnd,
1062
+ causalFrontier: cloneFrontier(input.receipt.resultingCausalFrontier),
1063
+ stateHash: input.receipt.stateHash,
1064
+ });
1065
+ } else {
1066
+ if (input.receipt.modality === "spreadsheet") {
1067
+ throw new Error("Serialized artifact received a spreadsheet receipt");
1068
+ }
1069
+ aggregate.artifact = {
1070
+ ...artifact,
1071
+ headSequence: input.receipt.sequenceEnd,
1072
+ stateHash: input.receipt.stateHash,
1073
+ updatedAt: laterIsoTimestamp(artifact.updatedAt, input.receipt.committedAt),
1074
+ };
1075
+ aggregate.checkpoints.set(input.receipt.sequenceEnd, {
1076
+ modality: artifact.modality,
1077
+ headSequence: input.receipt.sequenceEnd,
1078
+ nativeRevision: input.receipt.nativeRevision,
1079
+ stateHash: input.receipt.stateHash,
1080
+ });
1081
+ }
1082
+ }
1083
+
1084
+ class InMemoryEditableArtifactSnapshotUnitOfWork implements EditableArtifactSnapshotUnitOfWorkPort {
1085
+ private committed = false;
1086
+ private closed = false;
1087
+
1088
+ constructor(private readonly aggregate: ArtifactAggregate) {}
1089
+
1090
+ artifact(): EditableArtifact {
1091
+ this.assertOpen();
1092
+ return cloneArtifact(this.aggregate.artifact);
1093
+ }
1094
+
1095
+ async checkpoint(headSequence: number): Promise<EditableArtifactSequenceCheckpoint | null> {
1096
+ this.assertOpen();
1097
+ const checkpoint = this.aggregate.checkpoints.get(headSequence);
1098
+ return checkpoint ? cloneCheckpoint(checkpoint) : null;
1099
+ }
1100
+
1101
+ async findSnapshot(
1102
+ snapshotId: EditableArtifactSnapshotId,
1103
+ ): Promise<EditableArtifactSnapshotMetadata | null> {
1104
+ this.assertOpen();
1105
+ const snapshot = this.aggregate.snapshots.get(snapshotId);
1106
+ return snapshot ? cloneSnapshot(snapshot) : null;
1107
+ }
1108
+
1109
+ async commitSnapshot(
1110
+ input: CommitEditableArtifactSnapshot,
1111
+ ): Promise<Readonly<{ kind: "committed" }>> {
1112
+ this.assertUncommitted();
1113
+ if (
1114
+ input.authorizationPermission !== undefined &&
1115
+ input.authorizationPermission !== "manage" &&
1116
+ input.authorizationPermission !== "read" &&
1117
+ input.authorizationPermission !== "edit"
1118
+ ) {
1119
+ throw new TypeError("Snapshot authorization permission is invalid");
1120
+ }
1121
+ const artifact = this.aggregate.artifact;
1122
+ if (input.expectedCurrentSnapshotId !== artifact.currentSnapshotId) {
1123
+ throw new Error("In-memory current snapshot changed inside its transaction lock");
1124
+ }
1125
+ if (input.expectedAuthorizationRevision !== artifact.authorizationRevision) {
1126
+ throw new Error("In-memory artifact authorization changed before snapshot commit");
1127
+ }
1128
+ if (
1129
+ this.aggregate.snapshots.has(input.snapshot.snapshotId) ||
1130
+ this.aggregate.outbox.has(input.outbox.outboxId)
1131
+ ) {
1132
+ throw new Error("Duplicate snapshot or outbox identity");
1133
+ }
1134
+ const checkpoint = this.aggregate.checkpoints.get(input.snapshot.coveredHeadSequence);
1135
+ if (
1136
+ !checkpoint ||
1137
+ checkpoint.modality !== artifact.modality ||
1138
+ input.snapshot.modality !== artifact.modality ||
1139
+ checkpoint.stateHash !== input.snapshot.stateHash ||
1140
+ (input.snapshot.modality === "spreadsheet" &&
1141
+ (checkpoint.modality !== "spreadsheet" ||
1142
+ !causalFrontiersEqual(
1143
+ checkpoint.causalFrontier,
1144
+ input.snapshot.coveredCausalFrontier,
1145
+ ))) ||
1146
+ (input.snapshot.modality !== "spreadsheet" &&
1147
+ (checkpoint.modality === "spreadsheet" ||
1148
+ checkpoint.nativeRevision !== input.snapshot.nativeRevision))
1149
+ ) {
1150
+ throw new Error("Snapshot does not match an authoritative checkpoint");
1151
+ }
1152
+ this.aggregate.snapshots.set(input.snapshot.snapshotId, cloneSnapshot(input.snapshot));
1153
+ this.aggregate.outbox.set(input.outbox.outboxId, cloneOutbox(input.outbox));
1154
+ this.aggregate.artifact = {
1155
+ ...artifact,
1156
+ currentSnapshotId: input.snapshot.snapshotId,
1157
+ updatedAt: laterIsoTimestamp(artifact.updatedAt, input.snapshot.publishedAt),
1158
+ };
1159
+ this.committed = true;
1160
+ return Object.freeze({ kind: "committed" as const });
1161
+ }
1162
+
1163
+ private assertUncommitted(): void {
1164
+ this.assertOpen();
1165
+ if (this.committed) throw new Error("Artifact unit of work may commit exactly once");
1166
+ }
1167
+
1168
+ close(): void {
1169
+ this.closed = true;
1170
+ }
1171
+
1172
+ private assertOpen(): void {
1173
+ if (this.closed) {
1174
+ throw new Error("Artifact unit of work is no longer inside its lock");
1175
+ }
1176
+ }
1177
+ }
1178
+
1179
+ /** Deterministic persisted-namespace allocator for reference tests/adapters. */
1180
+ export class InMemoryEditableArtifactStableIdFactory implements EditableArtifactStableIdFactoryPort {
1181
+ private nextCounter: bigint;
1182
+ private readonly namespace: bigint;
1183
+
1184
+ constructor(namespace: bigint | string, initialCounter = 1n) {
1185
+ this.namespace =
1186
+ typeof namespace === "bigint" ? namespace : BigInt(`0x${namespace.replace(/^0x/, "")}`);
1187
+ if (this.namespace < 1n || this.namespace > 0xffff_ffff_ffff_ffffn) {
1188
+ throw new TypeError("Stable id namespace must be a nonzero unsigned 64-bit integer");
1189
+ }
1190
+ if (initialCounter < 1n || initialCounter > 0xffff_ffff_ffff_ffffn) {
1191
+ throw new TypeError("Stable id counter must be a nonzero unsigned 64-bit integer");
1192
+ }
1193
+ this.nextCounter = initialCounter;
1194
+ }
1195
+
1196
+ next(_kind: EditableArtifactStableIdKind): EditableArtifactStableId {
1197
+ if (this.nextCounter > 0xffff_ffff_ffff_ffffn) {
1198
+ throw new Error("Stable id namespace exhausted");
1199
+ }
1200
+ const value = `${this.namespace.toString(16).padStart(16, "0")}${this.nextCounter
1201
+ .toString(16)
1202
+ .padStart(16, "0")}`;
1203
+ this.nextCounter += 1n;
1204
+ return editableArtifactOperationId(value);
1205
+ }
1206
+ }
1207
+
1208
+ export class SystemEditableArtifactClock implements EditableArtifactClockPort {
1209
+ now(): Date {
1210
+ return new Date();
1211
+ }
1212
+ }
1213
+
1214
+ function aggregateKey(scope: EditableArtifactScope, artifactId: EditableArtifactId): string {
1215
+ return JSON.stringify([scope.accountId, scope.workspaceId, artifactId]);
1216
+ }
1217
+
1218
+ function authorizationScopeKey(scope: EditableArtifactScope): string {
1219
+ return JSON.stringify([scope.accountId, scope.workspaceId]);
1220
+ }
1221
+
1222
+ function creationReceiptKey(
1223
+ scope: EditableArtifactScope,
1224
+ operationKind: "create" | "import",
1225
+ authorityKey: string,
1226
+ idempotencyKey: string,
1227
+ ): string {
1228
+ return JSON.stringify([
1229
+ scope.accountId,
1230
+ scope.workspaceId,
1231
+ operationKind,
1232
+ authorityKey,
1233
+ idempotencyKey,
1234
+ ]);
1235
+ }
1236
+
1237
+ function emptyAggregate(
1238
+ artifact: EditableArtifact,
1239
+ genesisSnapshot: EditableArtifactSnapshotMetadata,
1240
+ outbox: EditableArtifactLiveOutboxRecord,
1241
+ ): ArtifactAggregate {
1242
+ return {
1243
+ artifact,
1244
+ receipts: new Map(),
1245
+ transactions: new Map(),
1246
+ committedTransactions: new Map(),
1247
+ operations: new Map(),
1248
+ undoClaims: new Map(),
1249
+ snapshots: new Map([[genesisSnapshot.snapshotId, cloneSnapshot(genesisSnapshot)]]),
1250
+ checkpoints: new Map([
1251
+ [
1252
+ 0,
1253
+ checkpointAtGenesis(
1254
+ artifact,
1255
+ genesisSnapshot.modality === "spreadsheet" ? 0 : genesisSnapshot.nativeRevision,
1256
+ ),
1257
+ ],
1258
+ ]),
1259
+ outbox: new Map([[outbox.outboxId, cloneOutbox(outbox)]]),
1260
+ };
1261
+ }
1262
+
1263
+ function assertGenesisCreateRequest(
1264
+ scope: EditableArtifactScope,
1265
+ artifactId: EditableArtifactId,
1266
+ operationKind: "create" | "import",
1267
+ snapshot: EditableArtifactSnapshotMetadata,
1268
+ outbox: EditableArtifactLiveOutboxRecord,
1269
+ ): void {
1270
+ assertIsoTimestamp(snapshot.verifiedAt, "genesis snapshot verification time");
1271
+ assertIsoTimestamp(snapshot.publishedAt, "genesis snapshot publication time");
1272
+ if (
1273
+ snapshot.scope.accountId !== scope.accountId ||
1274
+ snapshot.scope.workspaceId !== scope.workspaceId ||
1275
+ snapshot.artifactId !== artifactId ||
1276
+ snapshot.coveredHeadSequence !== 0 ||
1277
+ (operationKind === "create" &&
1278
+ snapshot.modality === "spreadsheet" &&
1279
+ snapshot.coveredCausalFrontier.length !== 0) ||
1280
+ Date.parse(snapshot.verifiedAt) > Date.parse(snapshot.publishedAt)
1281
+ ) {
1282
+ throw new TypeError(
1283
+ "Genesis snapshot is not the verified sequence-zero state for this artifact",
1284
+ );
1285
+ }
1286
+ if (
1287
+ outbox.state !== "pending" ||
1288
+ outbox.attemptCount !== 0 ||
1289
+ outbox.leaseOwner !== null ||
1290
+ outbox.leaseExpiresAt !== null ||
1291
+ outbox.nextAttemptAt !== snapshot.publishedAt ||
1292
+ outbox.lastErrorCode !== null ||
1293
+ outbox.publishedAt !== null ||
1294
+ outbox.deadLetteredAt !== null ||
1295
+ outbox.createdAt !== snapshot.publishedAt ||
1296
+ outbox.event.kind !== "snapshot_published" ||
1297
+ outbox.event.scope.accountId !== scope.accountId ||
1298
+ outbox.event.scope.workspaceId !== scope.workspaceId ||
1299
+ outbox.event.artifactId !== artifactId ||
1300
+ outbox.event.modality !== snapshot.modality ||
1301
+ outbox.event.snapshotId !== snapshot.snapshotId ||
1302
+ outbox.event.coveredHeadSequence !== 0 ||
1303
+ outbox.event.stateHash !== snapshot.stateHash ||
1304
+ (snapshot.modality === "spreadsheet" &&
1305
+ (outbox.event.modality !== "spreadsheet" ||
1306
+ outbox.event.operationProtocolVersion !== snapshot.operationProtocolVersion)) ||
1307
+ outbox.event.publishedAt !== snapshot.publishedAt
1308
+ ) {
1309
+ throw new TypeError("Genesis snapshot outbox projection is inconsistent");
1310
+ }
1311
+ }
1312
+
1313
+ function receiptKey(actorKey: string, clientTransactionId: string): string {
1314
+ return JSON.stringify([actorKey, clientTransactionId]);
1315
+ }
1316
+
1317
+ function sameExpectedPredecessor(
1318
+ aggregate: ArtifactAggregate,
1319
+ expected: TryCommitAppliedEditableArtifactTransactionRequest["expectedPredecessor"],
1320
+ ): boolean {
1321
+ if (expected === null) return true;
1322
+ const actual = aggregate.receipts.get(
1323
+ receiptKey(expected.actorKey, expected.clientTransactionId),
1324
+ );
1325
+ return (
1326
+ actual !== undefined &&
1327
+ actual.receiptId === expected.receiptId &&
1328
+ actual.serverTransactionId === expected.serverTransactionId &&
1329
+ actual.replicaId === expected.replicaId &&
1330
+ actual.replicaCounter === expected.replicaCounter
1331
+ );
1332
+ }
1333
+
1334
+ function assertLeaseRequest(request: ClaimEditableArtifactLiveOutboxRequest): void {
1335
+ assertLeaseOwner(request.owner);
1336
+ assertLeaseDuration(request.leaseDurationMs);
1337
+ assertPositiveSafeInteger(request.limit, "outbox claim limit");
1338
+ if (request.limit > 1_000) throw new TypeError("Outbox claim limit must not exceed 1000");
1339
+ }
1340
+
1341
+ function assertLeaseDuration(value: number): void {
1342
+ assertPositiveSafeInteger(value, "outbox lease duration");
1343
+ if (value > 24 * 60 * 60 * 1000) {
1344
+ throw new TypeError("Outbox lease duration must not exceed 24 hours");
1345
+ }
1346
+ }
1347
+
1348
+ function assertRetryDelay(value: number): void {
1349
+ assertPositiveSafeInteger(value, "outbox retry delay");
1350
+ if (value > 24 * 60 * 60 * 1000) {
1351
+ throw new TypeError("Outbox retry delay must not exceed 24 hours");
1352
+ }
1353
+ }
1354
+
1355
+ function assertActiveOutboxLease(
1356
+ record: EditableArtifactLiveOutboxRecord,
1357
+ owner: string,
1358
+ attemptCount: number,
1359
+ nowMs: number,
1360
+ ): void {
1361
+ if (
1362
+ record.state !== "publishing" ||
1363
+ record.leaseOwner !== owner ||
1364
+ record.attemptCount !== attemptCount ||
1365
+ !record.leaseExpiresAt ||
1366
+ nowMs >= Date.parse(record.leaseExpiresAt)
1367
+ ) {
1368
+ throw new EditableArtifactOutboxLeaseConflictError();
1369
+ }
1370
+ }
1371
+
1372
+ function assertRetryErrorCode(
1373
+ value: string,
1374
+ ): asserts value is EditableArtifactOutboxRetryFailureCode {
1375
+ if (
1376
+ value !== "broker_unavailable" &&
1377
+ value !== "broker_backpressure" &&
1378
+ value !== "publish_timeout"
1379
+ ) {
1380
+ throw new TypeError("Outbox retry error code is invalid");
1381
+ }
1382
+ }
1383
+
1384
+ function assertDeadLetterErrorCode(
1385
+ value: string,
1386
+ ): asserts value is EditableArtifactOutboxDeadLetterFailureCode {
1387
+ if (value !== "invalid_hint" && value !== "oversized_hint") {
1388
+ throw new TypeError("Outbox dead-letter error code is invalid");
1389
+ }
1390
+ }
1391
+
1392
+ function assertLeaseOwner(owner: string): string {
1393
+ if (owner.length < 1 || owner.length > 256) {
1394
+ throw new TypeError("Outbox lease owner must contain 1-256 characters");
1395
+ }
1396
+ return owner;
1397
+ }
1398
+
1399
+ function authoritativeNow(now: () => Date): Date {
1400
+ const value = now();
1401
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
1402
+ throw new TypeError("Editable artifact store clock returned an invalid date");
1403
+ }
1404
+ return new Date(value);
1405
+ }
1406
+
1407
+ function laterIsoTimestamp(left: string, right: string): string {
1408
+ return Date.parse(left) >= Date.parse(right) ? left : right;
1409
+ }
1410
+
1411
+ function cloneAggregate(aggregate: ArtifactAggregate): ArtifactAggregate {
1412
+ return {
1413
+ artifact: cloneArtifact(aggregate.artifact),
1414
+ receipts: new Map([...aggregate.receipts].map(([key, value]) => [key, cloneReceipt(value)])),
1415
+ transactions: new Map(
1416
+ [...aggregate.transactions].map(([key, value]) => [key, cloneReceipt(value)]),
1417
+ ),
1418
+ committedTransactions: new Map(
1419
+ [...aggregate.committedTransactions].map(([key, value]) => [
1420
+ key,
1421
+ cloneCommittedTransaction(value),
1422
+ ]),
1423
+ ),
1424
+ operations: new Map(
1425
+ [...aggregate.operations].map(([key, value]) => [key, cloneOperation(value)]),
1426
+ ),
1427
+ undoClaims: new Map(aggregate.undoClaims),
1428
+ snapshots: new Map([...aggregate.snapshots].map(([key, value]) => [key, cloneSnapshot(value)])),
1429
+ checkpoints: new Map(
1430
+ [...aggregate.checkpoints].map(([key, value]) => [key, cloneCheckpoint(value)]),
1431
+ ),
1432
+ outbox: new Map([...aggregate.outbox].map(([key, value]) => [key, cloneOutbox(value)])),
1433
+ };
1434
+ }
1435
+
1436
+ function kernelStateFromAggregate(aggregate: ArtifactAggregate): EditableArtifactKernelState {
1437
+ const snapshot = aggregate.artifact.currentSnapshotId
1438
+ ? (aggregate.snapshots.get(aggregate.artifact.currentSnapshotId) ?? null)
1439
+ : null;
1440
+ const artifact = cloneArtifact(aggregate.artifact);
1441
+ const covered = snapshot?.coveredHeadSequence ?? 0;
1442
+ const coveredCheckpoint = aggregate.checkpoints.get(covered);
1443
+ if (!coveredCheckpoint || coveredCheckpoint.modality !== artifact.modality) {
1444
+ throw new Error("Artifact replay base has no matching checkpoint");
1445
+ }
1446
+ const tail = [...aggregate.committedTransactions.values()]
1447
+ .filter((transaction) => transaction.sequenceEnd > covered)
1448
+ .sort((left, right) => left.sequenceStart - right.sequenceStart)
1449
+ .map(cloneCommittedTransaction);
1450
+ const tailByteSize = tail.reduce(
1451
+ (total, transaction) => total + transaction.committedTransactionBytes.byteLength,
1452
+ 0,
1453
+ );
1454
+ const common = {
1455
+ tailTransactionCount: tail.length,
1456
+ tailByteSize,
1457
+ } as const;
1458
+ if (artifact.modality === "spreadsheet") {
1459
+ return Object.freeze({
1460
+ ...common,
1461
+ modality: artifact.modality,
1462
+ artifact,
1463
+ snapshot: snapshot?.modality === "spreadsheet" ? cloneSnapshot(snapshot) : null,
1464
+ committedTransactionTail: Object.freeze(
1465
+ tail.map((transaction) => {
1466
+ if (transaction.modality !== "spreadsheet") {
1467
+ throw new Error("Spreadsheet artifact contains a cross-modality transaction");
1468
+ }
1469
+ return transaction;
1470
+ }),
1471
+ ),
1472
+ });
1473
+ }
1474
+ return Object.freeze({
1475
+ ...common,
1476
+ modality: artifact.modality,
1477
+ artifact,
1478
+ snapshot: snapshot && snapshot.modality !== "spreadsheet" ? cloneSnapshot(snapshot) : null,
1479
+ baseNativeRevision:
1480
+ coveredCheckpoint.modality === "spreadsheet"
1481
+ ? (() => {
1482
+ throw new Error("Serialized artifact has a spreadsheet replay checkpoint");
1483
+ })()
1484
+ : coveredCheckpoint.nativeRevision,
1485
+ committedTransactionTail: Object.freeze(
1486
+ tail.map((transaction) => {
1487
+ if (transaction.modality !== artifact.modality) {
1488
+ throw new Error("Serialized artifact contains a cross-modality transaction");
1489
+ }
1490
+ return transaction;
1491
+ }),
1492
+ ),
1493
+ });
1494
+ }
1495
+
1496
+ function cloneArtifact(artifact: EditableArtifact): EditableArtifact {
1497
+ if (artifact.modality !== "spreadsheet") {
1498
+ return Object.freeze({
1499
+ ...artifact,
1500
+ scope: Object.freeze({ ...artifact.scope }),
1501
+ });
1502
+ }
1503
+ return Object.freeze({
1504
+ ...artifact,
1505
+ scope: Object.freeze({ ...artifact.scope }),
1506
+ causalFrontier: cloneFrontier(artifact.causalFrontier),
1507
+ });
1508
+ }
1509
+
1510
+ function cloneCreationReceipt(
1511
+ receipt: EditableArtifactCreationReceipt,
1512
+ ): EditableArtifactCreationReceipt {
1513
+ return Object.freeze({
1514
+ ...receipt,
1515
+ scope: Object.freeze({ ...receipt.scope }),
1516
+ });
1517
+ }
1518
+
1519
+ function cloneReceipt(receipt: EditableArtifactReceipt): EditableArtifactReceipt {
1520
+ if (receipt.modality !== "spreadsheet") {
1521
+ return Object.freeze({
1522
+ ...receipt,
1523
+ scope: Object.freeze({ ...receipt.scope }),
1524
+ intentBytes: receipt.intentBytes.slice(),
1525
+ });
1526
+ }
1527
+ return Object.freeze({
1528
+ ...receipt,
1529
+ scope: Object.freeze({ ...receipt.scope }),
1530
+ intentBytes: receipt.intentBytes.slice(),
1531
+ causalBase: cloneFrontier(receipt.causalBase),
1532
+ resolvedCausalBase: cloneFrontier(receipt.resolvedCausalBase),
1533
+ resultingCausalFrontier: cloneFrontier(receipt.resultingCausalFrontier),
1534
+ selectiveUndoOperationIds: Object.freeze([...receipt.selectiveUndoOperationIds]),
1535
+ });
1536
+ }
1537
+
1538
+ function cloneOperation(
1539
+ operation: EditableArtifactOperationRecord,
1540
+ ): EditableArtifactOperationRecord {
1541
+ return Object.freeze({
1542
+ ...operation,
1543
+ scope: Object.freeze({ ...operation.scope }),
1544
+ dot: Object.freeze({ ...operation.dot }),
1545
+ });
1546
+ }
1547
+
1548
+ function cloneCommittedTransaction(
1549
+ transaction: EditableArtifactSpreadsheetCommittedTransactionRecord,
1550
+ ): EditableArtifactSpreadsheetCommittedTransactionRecord;
1551
+ function cloneCommittedTransaction(
1552
+ transaction: EditableArtifactSerializedCommittedTransactionRecord,
1553
+ ): EditableArtifactSerializedCommittedTransactionRecord;
1554
+ function cloneCommittedTransaction(
1555
+ transaction: EditableArtifactCommittedTransactionRecord,
1556
+ ): EditableArtifactCommittedTransactionRecord;
1557
+ function cloneCommittedTransaction(
1558
+ transaction: EditableArtifactCommittedTransactionRecord,
1559
+ ): EditableArtifactCommittedTransactionRecord {
1560
+ if (transaction.modality !== "spreadsheet") {
1561
+ return Object.freeze({
1562
+ ...transaction,
1563
+ scope: Object.freeze({ ...transaction.scope }),
1564
+ nativeReceiptBytes: transaction.nativeReceiptBytes.slice(),
1565
+ committedTransactionBytes: transaction.committedTransactionBytes.slice(),
1566
+ });
1567
+ }
1568
+ return Object.freeze({
1569
+ ...transaction,
1570
+ scope: Object.freeze({ ...transaction.scope }),
1571
+ dot: Object.freeze({ ...transaction.dot }),
1572
+ resolvedCausalBase: cloneFrontier(transaction.resolvedCausalBase),
1573
+ resultingCausalFrontier: cloneFrontier(transaction.resultingCausalFrontier),
1574
+ operationIds: Object.freeze([...transaction.operationIds]),
1575
+ committedTransactionBytes: transaction.committedTransactionBytes.slice(),
1576
+ });
1577
+ }
1578
+
1579
+ function cloneSnapshot(
1580
+ snapshot: EditableArtifactSpreadsheetSnapshotMetadata,
1581
+ ): EditableArtifactSpreadsheetSnapshotMetadata;
1582
+ function cloneSnapshot(
1583
+ snapshot: EditableArtifactSerializedSnapshotMetadata,
1584
+ ): EditableArtifactSerializedSnapshotMetadata;
1585
+ function cloneSnapshot(
1586
+ snapshot: EditableArtifactSnapshotMetadata,
1587
+ ): EditableArtifactSnapshotMetadata;
1588
+ function cloneSnapshot(
1589
+ snapshot: EditableArtifactSnapshotMetadata,
1590
+ ): EditableArtifactSnapshotMetadata {
1591
+ if (snapshot.modality !== "spreadsheet") {
1592
+ return Object.freeze({
1593
+ ...snapshot,
1594
+ scope: Object.freeze({ ...snapshot.scope }),
1595
+ });
1596
+ }
1597
+ return Object.freeze({
1598
+ ...snapshot,
1599
+ scope: Object.freeze({ ...snapshot.scope }),
1600
+ coveredCausalFrontier: cloneFrontier(snapshot.coveredCausalFrontier),
1601
+ });
1602
+ }
1603
+
1604
+ function cloneCheckpoint(
1605
+ checkpoint: EditableArtifactSequenceCheckpoint,
1606
+ ): EditableArtifactSequenceCheckpoint {
1607
+ if (checkpoint.modality !== "spreadsheet") {
1608
+ return Object.freeze({ ...checkpoint });
1609
+ }
1610
+ return Object.freeze({
1611
+ ...checkpoint,
1612
+ causalFrontier: cloneFrontier(checkpoint.causalFrontier),
1613
+ });
1614
+ }
1615
+
1616
+ function checkpointAtGenesis(
1617
+ artifact: EditableArtifact,
1618
+ nativeRevision = 0,
1619
+ ): EditableArtifactSequenceCheckpoint {
1620
+ return artifact.modality === "spreadsheet"
1621
+ ? Object.freeze({
1622
+ modality: "spreadsheet" as const,
1623
+ headSequence: 0,
1624
+ causalFrontier: editableArtifactCausalFrontier([]),
1625
+ stateHash: artifact.stateHash,
1626
+ })
1627
+ : Object.freeze({
1628
+ modality: artifact.modality,
1629
+ headSequence: 0,
1630
+ nativeRevision,
1631
+ stateHash: artifact.stateHash,
1632
+ });
1633
+ }
1634
+
1635
+ function sameBytes(left: Uint8Array, right: Uint8Array): boolean {
1636
+ if (left.byteLength !== right.byteLength) return false;
1637
+ let difference = 0;
1638
+ for (let index = 0; index < left.byteLength; index += 1) {
1639
+ difference |= left[index]! ^ right[index]!;
1640
+ }
1641
+ return difference === 0;
1642
+ }
1643
+
1644
+ function cloneOutbox(record: EditableArtifactLiveOutboxRecord): EditableArtifactLiveOutboxRecord {
1645
+ return Object.freeze({
1646
+ ...record,
1647
+ event: Object.freeze({
1648
+ ...record.event,
1649
+ scope: Object.freeze({ ...record.event.scope }),
1650
+ }),
1651
+ });
1652
+ }
1653
+
1654
+ function cloneFrontier<T extends readonly { replicaId: string; counter: number }[]>(
1655
+ frontier: T,
1656
+ ): T {
1657
+ return Object.freeze(frontier.map((entry) => Object.freeze({ ...entry }))) as unknown as T;
1658
+ }
1659
+
1660
+ function compareText(left: string, right: string): number {
1661
+ return left < right ? -1 : left > right ? 1 : 0;
1662
+ }