@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,246 @@
1
+ import { type BoundedImmutableObjectWritePort, BoundedObjectWriteError } from "@opengeni/storage";
2
+
3
+ import type { EditableArtifactCompactionPort, EditableArtifactKernelState } from "./ports";
4
+ import {
5
+ MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES,
6
+ EditableArtifactSnapshotVerificationError,
7
+ } from "./snapshot-verifier";
8
+ import {
9
+ assertBoundedKernelVersion,
10
+ causalFrontiersEqual,
11
+ editableArtifactCausalFrontier,
12
+ editableArtifactContentHash,
13
+ editableArtifactId,
14
+ editableArtifactScope,
15
+ editableArtifactSnapshotId,
16
+ editableArtifactStateHash,
17
+ type EditableArtifactCausalFrontier,
18
+ type EditableArtifactContentHash,
19
+ type EditableArtifactId,
20
+ type EditableArtifactModality,
21
+ type EditableArtifactScope,
22
+ type EditableArtifactStateHash,
23
+ type PublishEditableArtifactSnapshotRequest,
24
+ } from "./types";
25
+
26
+ const SNAPSHOT_MIME_TYPE = "application/vnd.opengeni.editable-artifact-snapshot" as const;
27
+
28
+ type AuthoritativeEditableArtifactCompactionKernelResultCommon = Readonly<{
29
+ scope: EditableArtifactScope;
30
+ artifactId: EditableArtifactId;
31
+ modality: EditableArtifactModality;
32
+ canonicalChunks: AsyncIterable<Uint8Array>;
33
+ canonicalByteSize: number;
34
+ canonicalContentHash: EditableArtifactContentHash;
35
+ stateHash: EditableArtifactStateHash;
36
+ coveredHeadSequence: number;
37
+ modelSchemaVersion: number;
38
+ kernelVersion: string;
39
+ }>;
40
+
41
+ export type AuthoritativeEditableArtifactCompactionKernelResult =
42
+ | (AuthoritativeEditableArtifactCompactionKernelResultCommon &
43
+ Readonly<{
44
+ modality: "spreadsheet";
45
+ coveredCausalFrontier: EditableArtifactCausalFrontier;
46
+ operationProtocolVersion: number;
47
+ crdtStateVersion: number;
48
+ }>)
49
+ | (AuthoritativeEditableArtifactCompactionKernelResultCommon &
50
+ Readonly<{
51
+ modality: "document" | "presentation";
52
+ nativeRevision: number;
53
+ }>);
54
+
55
+ /** Native authority that replays one detached durable basis without network access. */
56
+ export interface AuthoritativeEditableArtifactCompactionKernelPort {
57
+ createCanonicalSnapshot(input: {
58
+ state: EditableArtifactKernelState;
59
+ signal?: AbortSignal;
60
+ }): Promise<AuthoritativeEditableArtifactCompactionKernelResult>;
61
+ }
62
+
63
+ export type EditableArtifactCompactionPipelineDependencies = Readonly<{
64
+ kernel: AuthoritativeEditableArtifactCompactionKernelPort;
65
+ objects: BoundedImmutableObjectWritePort;
66
+ now: () => Date;
67
+ maxSnapshotBytes?: number;
68
+ }>;
69
+
70
+ /** Reconstruct, canonicalize, and upload an exact-head state-neutral snapshot. */
71
+ export class EditableArtifactCompactionPipeline implements EditableArtifactCompactionPort {
72
+ readonly #dependencies: Readonly<{
73
+ kernel: AuthoritativeEditableArtifactCompactionKernelPort;
74
+ objects: BoundedImmutableObjectWritePort;
75
+ now: () => Date;
76
+ maxSnapshotBytes: number;
77
+ }>;
78
+
79
+ constructor(dependencies: EditableArtifactCompactionPipelineDependencies) {
80
+ const maxSnapshotBytes = dependencies.maxSnapshotBytes ?? MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES;
81
+ if (
82
+ !Number.isSafeInteger(maxSnapshotBytes) ||
83
+ maxSnapshotBytes <= 0 ||
84
+ maxSnapshotBytes > MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES
85
+ ) {
86
+ throw new TypeError("Compaction snapshot limit is invalid");
87
+ }
88
+ this.#dependencies = Object.freeze({ ...dependencies, maxSnapshotBytes });
89
+ }
90
+
91
+ async prepare(
92
+ input: Parameters<EditableArtifactCompactionPort["prepare"]>[0],
93
+ ): Promise<PublishEditableArtifactSnapshotRequest> {
94
+ const scope = editableArtifactScope(input.scope);
95
+ const artifactId = editableArtifactId(input.artifactId);
96
+ const snapshotId = editableArtifactSnapshotId(input.snapshotId);
97
+ validateBasis(input.state, scope, artifactId);
98
+ throwIfCancelled(input.signal);
99
+ try {
100
+ const generated = await this.#dependencies.kernel.createCanonicalSnapshot({
101
+ state: input.state,
102
+ ...(input.signal ? { signal: input.signal } : {}),
103
+ });
104
+ validateGenerated(generated, input.state, scope, artifactId);
105
+ const stored = await this.#dependencies.objects.write({
106
+ chunks: generated.canonicalChunks,
107
+ contentType: SNAPSHOT_MIME_TYPE,
108
+ maxBytes: this.#dependencies.maxSnapshotBytes,
109
+ expectedByteSize: generated.canonicalByteSize,
110
+ expectedContentHash: generated.canonicalContentHash,
111
+ ...(input.signal ? { signal: input.signal } : {}),
112
+ });
113
+ const common = {
114
+ snapshotId,
115
+ blobReference: stored.opaqueReference,
116
+ byteSize: stored.byteSize,
117
+ contentHash: editableArtifactContentHash(stored.contentHash),
118
+ mimeType: SNAPSHOT_MIME_TYPE,
119
+ modality: generated.modality,
120
+ coveredHeadSequence: generated.coveredHeadSequence,
121
+ stateHash: generated.stateHash,
122
+ modelSchemaVersion: generated.modelSchemaVersion,
123
+ kernelVersion: generated.kernelVersion,
124
+ verifiedAt: canonicalTimestamp(this.#dependencies.now()),
125
+ } as const;
126
+ return generated.modality === "spreadsheet"
127
+ ? Object.freeze({
128
+ ...common,
129
+ modality: generated.modality,
130
+ coveredCausalFrontier: generated.coveredCausalFrontier,
131
+ operationProtocolVersion: generated.operationProtocolVersion,
132
+ crdtStateVersion: generated.crdtStateVersion,
133
+ })
134
+ : Object.freeze({
135
+ ...common,
136
+ modality: generated.modality,
137
+ nativeRevision: generated.nativeRevision,
138
+ });
139
+ } catch (error) {
140
+ if (
141
+ error instanceof EditableArtifactSnapshotVerificationError ||
142
+ error instanceof BoundedObjectWriteError
143
+ ) {
144
+ throw error;
145
+ }
146
+ if (input.signal?.aborted) {
147
+ throw new EditableArtifactSnapshotVerificationError("cancelled");
148
+ }
149
+ throw new EditableArtifactSnapshotVerificationError("kernel_rejected");
150
+ }
151
+ }
152
+ }
153
+
154
+ function validateBasis(
155
+ state: EditableArtifactKernelState,
156
+ scope: EditableArtifactScope,
157
+ artifactId: EditableArtifactId,
158
+ ): void {
159
+ if (
160
+ state.artifact.scope.accountId !== scope.accountId ||
161
+ state.artifact.scope.workspaceId !== scope.workspaceId ||
162
+ state.artifact.id !== artifactId ||
163
+ state.artifact.modality !== state.modality
164
+ ) {
165
+ throw new EditableArtifactSnapshotVerificationError("identity_mismatch");
166
+ }
167
+ for (const value of [state.tailTransactionCount, state.tailByteSize]) {
168
+ if (!Number.isSafeInteger(value) || value < 0) {
169
+ throw new EditableArtifactSnapshotVerificationError("coverage_mismatch");
170
+ }
171
+ }
172
+ }
173
+
174
+ function validateGenerated(
175
+ value: AuthoritativeEditableArtifactCompactionKernelResult,
176
+ state: EditableArtifactKernelState,
177
+ scope: EditableArtifactScope,
178
+ artifactId: EditableArtifactId,
179
+ ): void {
180
+ if (
181
+ value.scope.accountId !== scope.accountId ||
182
+ value.scope.workspaceId !== scope.workspaceId ||
183
+ value.artifactId !== artifactId ||
184
+ value.modality !== state.modality
185
+ ) {
186
+ throw new EditableArtifactSnapshotVerificationError("identity_mismatch");
187
+ }
188
+ if (
189
+ value.coveredHeadSequence !== state.artifact.headSequence ||
190
+ editableArtifactStateHash(value.stateHash) !== state.artifact.stateHash ||
191
+ (value.modality === "spreadsheet" &&
192
+ (state.modality !== "spreadsheet" ||
193
+ !causalFrontiersEqual(
194
+ editableArtifactCausalFrontier(value.coveredCausalFrontier),
195
+ state.artifact.causalFrontier,
196
+ ))) ||
197
+ (value.modality !== "spreadsheet" &&
198
+ (state.modality === "spreadsheet" || value.nativeRevision !== expectedNativeRevision(state)))
199
+ ) {
200
+ throw new EditableArtifactSnapshotVerificationError("coverage_mismatch");
201
+ }
202
+ if (
203
+ !Number.isSafeInteger(value.canonicalByteSize) ||
204
+ value.canonicalByteSize <= 0 ||
205
+ value.canonicalByteSize > MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES
206
+ ) {
207
+ throw new EditableArtifactSnapshotVerificationError("limit_exceeded");
208
+ }
209
+ editableArtifactContentHash(value.canonicalContentHash);
210
+ const versions =
211
+ value.modality === "spreadsheet"
212
+ ? [value.modelSchemaVersion, value.operationProtocolVersion, value.crdtStateVersion]
213
+ : [value.modelSchemaVersion];
214
+ if (versions.some((version) => !Number.isSafeInteger(version) || version <= 0)) {
215
+ throw new EditableArtifactSnapshotVerificationError("version_mismatch");
216
+ }
217
+ try {
218
+ assertBoundedKernelVersion(value.kernelVersion);
219
+ } catch {
220
+ throw new EditableArtifactSnapshotVerificationError("version_mismatch");
221
+ }
222
+ if (!value.canonicalChunks || typeof value.canonicalChunks[Symbol.asyncIterator] !== "function") {
223
+ throw new EditableArtifactSnapshotVerificationError("kernel_rejected");
224
+ }
225
+ }
226
+
227
+ function expectedNativeRevision(
228
+ state: Extract<EditableArtifactKernelState, { modality: "document" | "presentation" }>,
229
+ ): number {
230
+ return (
231
+ state.committedTransactionTail.at(-1)?.nativeRevision ??
232
+ state.snapshot?.nativeRevision ??
233
+ state.baseNativeRevision
234
+ );
235
+ }
236
+
237
+ function canonicalTimestamp(value: Date): string {
238
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
239
+ throw new TypeError("Compaction verification clock returned an invalid date");
240
+ }
241
+ return value.toISOString();
242
+ }
243
+
244
+ function throwIfCancelled(signal: AbortSignal | undefined): void {
245
+ if (signal?.aborted) throw new EditableArtifactSnapshotVerificationError("cancelled");
246
+ }
@@ -0,0 +1,235 @@
1
+ import { type BoundedImmutableObjectWritePort, BoundedObjectWriteError } from "@opengeni/storage";
2
+ import type { EditableArtifactGenesisPort } from "./ports";
3
+ import {
4
+ MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES,
5
+ EditableArtifactSnapshotVerificationError,
6
+ } from "./snapshot-verifier";
7
+ import {
8
+ editableArtifactCausalFrontier,
9
+ assertBoundedKernelVersion,
10
+ editableArtifactContentHash,
11
+ editableArtifactId,
12
+ editableArtifactScope,
13
+ editableArtifactSnapshotId,
14
+ editableArtifactStateHash,
15
+ type EditableArtifactCausalFrontier,
16
+ type EditableArtifactContentHash,
17
+ type EditableArtifactId,
18
+ type EditableArtifactModality,
19
+ type EditableArtifactScope,
20
+ type EditableArtifactStateHash,
21
+ type PublishEditableArtifactSnapshotRequest,
22
+ } from "./types";
23
+
24
+ const SNAPSHOT_MIME_TYPE = "application/vnd.opengeni.editable-artifact-snapshot" as const;
25
+
26
+ type AuthoritativeEditableArtifactGenesisKernelResultCommon = Readonly<{
27
+ scope: EditableArtifactScope;
28
+ artifactId: EditableArtifactId;
29
+ modality: EditableArtifactModality;
30
+ canonicalChunks: AsyncIterable<Uint8Array>;
31
+ canonicalByteSize: number;
32
+ canonicalContentHash: EditableArtifactContentHash;
33
+ stateHash: EditableArtifactStateHash;
34
+ coveredHeadSequence: 0;
35
+ modelSchemaVersion: number;
36
+ kernelVersion: string;
37
+ }>;
38
+
39
+ export type AuthoritativeEditableArtifactGenesisKernelResult =
40
+ | (AuthoritativeEditableArtifactGenesisKernelResultCommon &
41
+ Readonly<{
42
+ modality: "spreadsheet";
43
+ coveredCausalFrontier: EditableArtifactCausalFrontier;
44
+ operationProtocolVersion: number;
45
+ crdtStateVersion: number;
46
+ }>)
47
+ | (AuthoritativeEditableArtifactGenesisKernelResultCommon &
48
+ Readonly<{
49
+ modality: "document" | "presentation";
50
+ nativeRevision: 0;
51
+ }>);
52
+
53
+ /** Trusted no-network native-kernel subprocess; never an HTTP request adapter. */
54
+ export interface AuthoritativeEditableArtifactGenesisKernelPort {
55
+ createCanonicalEmptySnapshot(input: {
56
+ scope: EditableArtifactScope;
57
+ artifactId: EditableArtifactId;
58
+ modality: EditableArtifactModality;
59
+ signal?: AbortSignal;
60
+ }): Promise<AuthoritativeEditableArtifactGenesisKernelResult>;
61
+ }
62
+
63
+ export type EditableArtifactGenesisPipelineDependencies = Readonly<{
64
+ kernel: AuthoritativeEditableArtifactGenesisKernelPort;
65
+ objects: BoundedImmutableObjectWritePort;
66
+ now: () => Date;
67
+ maxSnapshotBytes?: number;
68
+ }>;
69
+
70
+ /**
71
+ * Creates the only legal sequence-zero candidate. The domain service passes
72
+ * the result through its unchanged snapshot verifier, then commits artifact +
73
+ * checkpoint-0 + snapshot + blob-ref + receipt + outbox atomically.
74
+ */
75
+ export class EditableArtifactGenesisPipeline implements EditableArtifactGenesisPort {
76
+ readonly #dependencies: Readonly<{
77
+ kernel: AuthoritativeEditableArtifactGenesisKernelPort;
78
+ objects: BoundedImmutableObjectWritePort;
79
+ now: () => Date;
80
+ maxSnapshotBytes: number;
81
+ }>;
82
+
83
+ constructor(dependencies: EditableArtifactGenesisPipelineDependencies) {
84
+ const maxSnapshotBytes = dependencies.maxSnapshotBytes ?? MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES;
85
+ if (
86
+ !Number.isSafeInteger(maxSnapshotBytes) ||
87
+ maxSnapshotBytes <= 0 ||
88
+ maxSnapshotBytes > MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES
89
+ ) {
90
+ throw new TypeError("Genesis snapshot limit is invalid");
91
+ }
92
+ this.#dependencies = Object.freeze({
93
+ kernel: dependencies.kernel,
94
+ objects: dependencies.objects,
95
+ now: dependencies.now,
96
+ maxSnapshotBytes,
97
+ });
98
+ }
99
+
100
+ async prepare(
101
+ input: Parameters<EditableArtifactGenesisPort["prepare"]>[0],
102
+ ): Promise<PublishEditableArtifactSnapshotRequest> {
103
+ const scope = editableArtifactScope(input.scope);
104
+ const artifactId = editableArtifactId(input.artifactId);
105
+ const snapshotId = editableArtifactSnapshotId(input.snapshotId);
106
+ if (!isModality(input.modality)) {
107
+ throw new TypeError("Genesis modality is invalid");
108
+ }
109
+ throwIfGenesisCancelled(input.signal);
110
+ try {
111
+ const generated = await this.#dependencies.kernel.createCanonicalEmptySnapshot({
112
+ scope,
113
+ artifactId,
114
+ modality: input.modality,
115
+ ...(input.signal ? { signal: input.signal } : {}),
116
+ });
117
+ validateGeneratedGenesis(generated, scope, artifactId, input.modality);
118
+ const stored = await this.#dependencies.objects.write({
119
+ chunks: generated.canonicalChunks,
120
+ contentType: SNAPSHOT_MIME_TYPE,
121
+ maxBytes: this.#dependencies.maxSnapshotBytes,
122
+ expectedByteSize: generated.canonicalByteSize,
123
+ expectedContentHash: generated.canonicalContentHash,
124
+ ...(input.signal ? { signal: input.signal } : {}),
125
+ });
126
+ const verifiedAt = canonicalTimestamp(this.#dependencies.now());
127
+ const common = {
128
+ snapshotId,
129
+ blobReference: stored.opaqueReference,
130
+ byteSize: stored.byteSize,
131
+ contentHash: editableArtifactContentHash(stored.contentHash),
132
+ mimeType: SNAPSHOT_MIME_TYPE,
133
+ modality: generated.modality,
134
+ coveredHeadSequence: 0,
135
+ stateHash: generated.stateHash,
136
+ modelSchemaVersion: generated.modelSchemaVersion,
137
+ kernelVersion: generated.kernelVersion,
138
+ verifiedAt,
139
+ } as const;
140
+ return generated.modality === "spreadsheet"
141
+ ? Object.freeze({
142
+ ...common,
143
+ modality: "spreadsheet" as const,
144
+ coveredCausalFrontier: editableArtifactCausalFrontier([]),
145
+ operationProtocolVersion: generated.operationProtocolVersion,
146
+ crdtStateVersion: generated.crdtStateVersion,
147
+ })
148
+ : Object.freeze({
149
+ ...common,
150
+ modality: generated.modality,
151
+ nativeRevision: generated.nativeRevision,
152
+ });
153
+ } catch (error) {
154
+ if (
155
+ error instanceof EditableArtifactSnapshotVerificationError ||
156
+ error instanceof BoundedObjectWriteError
157
+ ) {
158
+ throw error;
159
+ }
160
+ if (input.signal?.aborted) {
161
+ throw new EditableArtifactSnapshotVerificationError("cancelled");
162
+ }
163
+ // Native generation diagnostics may contain internal paths. Preserve no
164
+ // cause across this service boundary.
165
+ throw new EditableArtifactSnapshotVerificationError("kernel_rejected");
166
+ }
167
+ }
168
+ }
169
+
170
+ function validateGeneratedGenesis(
171
+ value: AuthoritativeEditableArtifactGenesisKernelResult,
172
+ scope: EditableArtifactScope,
173
+ artifactId: EditableArtifactId,
174
+ modality: EditableArtifactModality,
175
+ ): void {
176
+ if (
177
+ value.scope.accountId !== scope.accountId ||
178
+ value.scope.workspaceId !== scope.workspaceId ||
179
+ value.artifactId !== artifactId ||
180
+ value.modality !== modality
181
+ ) {
182
+ throw new EditableArtifactSnapshotVerificationError("identity_mismatch");
183
+ }
184
+ if (
185
+ value.coveredHeadSequence !== 0 ||
186
+ (value.modality === "spreadsheet"
187
+ ? editableArtifactCausalFrontier(value.coveredCausalFrontier).length !== 0
188
+ : value.nativeRevision !== 0)
189
+ ) {
190
+ throw new EditableArtifactSnapshotVerificationError("coverage_mismatch");
191
+ }
192
+ if (
193
+ !Number.isSafeInteger(value.canonicalByteSize) ||
194
+ value.canonicalByteSize <= 0 ||
195
+ value.canonicalByteSize > MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES
196
+ ) {
197
+ throw new EditableArtifactSnapshotVerificationError("limit_exceeded");
198
+ }
199
+ editableArtifactContentHash(value.canonicalContentHash);
200
+ editableArtifactStateHash(value.stateHash);
201
+ const versions =
202
+ value.modality === "spreadsheet"
203
+ ? [value.modelSchemaVersion, value.operationProtocolVersion, value.crdtStateVersion]
204
+ : [value.modelSchemaVersion];
205
+ for (const version of versions) {
206
+ if (!Number.isSafeInteger(version) || version <= 0) {
207
+ throw new EditableArtifactSnapshotVerificationError("version_mismatch");
208
+ }
209
+ }
210
+ try {
211
+ assertBoundedKernelVersion(value.kernelVersion);
212
+ } catch {
213
+ throw new EditableArtifactSnapshotVerificationError("version_mismatch");
214
+ }
215
+ if (!value.canonicalChunks || typeof value.canonicalChunks[Symbol.asyncIterator] !== "function") {
216
+ throw new EditableArtifactSnapshotVerificationError("kernel_rejected");
217
+ }
218
+ }
219
+
220
+ function canonicalTimestamp(value: Date): string {
221
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
222
+ throw new TypeError("Genesis verification clock returned an invalid date");
223
+ }
224
+ return value.toISOString();
225
+ }
226
+
227
+ function isModality(value: unknown): value is EditableArtifactModality {
228
+ return value === "spreadsheet" || value === "presentation" || value === "document";
229
+ }
230
+
231
+ function throwIfGenesisCancelled(signal: AbortSignal | undefined): void {
232
+ if (signal?.aborted) {
233
+ throw new EditableArtifactSnapshotVerificationError("cancelled");
234
+ }
235
+ }
@@ -0,0 +1,74 @@
1
+ import { createHash } from "node:crypto";
2
+ import { EDITABLE_ARTIFACT_KERNEL_VERSION_MAX_BYTES } from "@opengeni/contracts/editable-artifacts";
3
+
4
+ declare const editableArtifactMaterializationKeyBrand: unique symbol;
5
+
6
+ export type EditableArtifactMaterializationKey = string & {
7
+ readonly [editableArtifactMaterializationKeyBrand]: true;
8
+ };
9
+
10
+ export type EditableArtifactMaterializationKeyInput = Readonly<{
11
+ stateHash: string;
12
+ format: string;
13
+ /** SHA-256 of the canonical, normalized export options. */
14
+ optionsHash: string;
15
+ codecVersion: string;
16
+ kernelVersion: string;
17
+ fontRegistryHash: string;
18
+ policyHash: string;
19
+ }>;
20
+
21
+ const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
22
+ const FORMAT_PATTERN = /^[a-z0-9][a-z0-9.+_-]{0,63}$/;
23
+ const VERSION_PATTERN = /^[\x21-\x7e]+$/;
24
+
25
+ /**
26
+ * Complete materialization identity from docs/artifact-engine.md. Length
27
+ * framing prevents concatenation ambiguity and the output is safe as a DB
28
+ * idempotency key; it is deliberately not an object-storage key.
29
+ */
30
+ export function editableArtifactMaterializationKey(
31
+ input: EditableArtifactMaterializationKeyInput,
32
+ ): EditableArtifactMaterializationKey {
33
+ for (const [label, value] of [
34
+ ["state hash", input.stateHash],
35
+ ["options hash", input.optionsHash],
36
+ ["font registry hash", input.fontRegistryHash],
37
+ ["policy hash", input.policyHash],
38
+ ] as const) {
39
+ if (!SHA256_PATTERN.test(value)) {
40
+ throw new TypeError(`${label} must be a canonical SHA-256 digest`);
41
+ }
42
+ }
43
+ if (!FORMAT_PATTERN.test(input.format)) {
44
+ throw new TypeError("materialization format is invalid");
45
+ }
46
+ if (!VERSION_PATTERN.test(input.codecVersion) || input.codecVersion.length > 128) {
47
+ throw new TypeError("codec version is invalid");
48
+ }
49
+ if (
50
+ !VERSION_PATTERN.test(input.kernelVersion) ||
51
+ input.kernelVersion.length > EDITABLE_ARTIFACT_KERNEL_VERSION_MAX_BYTES
52
+ ) {
53
+ throw new TypeError("kernel version is invalid");
54
+ }
55
+ const fields = [
56
+ input.stateHash,
57
+ input.format,
58
+ input.optionsHash,
59
+ input.codecVersion,
60
+ input.kernelVersion,
61
+ input.fontRegistryHash,
62
+ input.policyHash,
63
+ ];
64
+ const digest = createHash("sha256");
65
+ digest.update("OpenGeni editable artifact materialization\0v1\0", "utf8");
66
+ for (const field of fields) {
67
+ const bytes = Buffer.from(field, "utf8");
68
+ const length = Buffer.allocUnsafe(4);
69
+ length.writeUInt32BE(bytes.byteLength);
70
+ digest.update(length);
71
+ digest.update(bytes);
72
+ }
73
+ return `materialization:v1:sha256:${digest.digest("hex")}` as EditableArtifactMaterializationKey;
74
+ }