@opengeni/api-router 0.23.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/app.d.ts +3 -2
  2. package/dist/app.js +5 -3
  3. package/dist/auth/canonical-human-session-admission.d.ts +27 -0
  4. package/dist/browser-auth-broker.d.ts +20 -0
  5. package/dist/browser-controller-authority.d.ts +61 -0
  6. package/dist/browser-network-route.d.ts +8 -0
  7. package/dist/browser-state-authority.d.ts +27 -0
  8. package/dist/{chunk-T4T2PGU4.js → chunk-2PQMSRCB.js} +37891 -17463
  9. package/dist/chunk-2PQMSRCB.js.map +1 -0
  10. package/dist/codemode.d.ts +23 -0
  11. package/dist/controller-data-plane.d.ts +12 -0
  12. package/dist/editable-artifact-live-hints.d.ts +11 -0
  13. package/dist/editable-artifact-native-kernel.d.ts +37 -0
  14. package/dist/editable-artifact-office-import.d.ts +22 -0
  15. package/dist/editable-artifact-production.d.ts +29 -0
  16. package/dist/editable-artifact-websocket.d.ts +49 -0
  17. package/dist/editable-artifact-workspace-files.d.ts +23 -0
  18. package/dist/github-browser-flow.d.ts +6 -0
  19. package/dist/http/auth.d.ts +1 -1
  20. package/dist/http/cors.d.ts +1 -0
  21. package/dist/http/sse.d.ts +18 -1
  22. package/dist/index.d.ts +3 -2
  23. package/dist/index.js +1796 -22
  24. package/dist/index.js.map +1 -1
  25. package/dist/integrations/api-integrations.d.ts +24 -0
  26. package/dist/integrations/atlassian.d.ts +176 -0
  27. package/dist/integrations/fiken.d.ts +233 -0
  28. package/dist/integrations/github-skill-source.d.ts +5 -0
  29. package/dist/integrations/google-drive.d.ts +85 -0
  30. package/dist/integrations/oauth-client.d.ts +22 -1
  31. package/dist/integrations/provider-oauth.d.ts +17 -0
  32. package/dist/integrations/slack-bot.d.ts +175 -1
  33. package/dist/integrations/slack-interactions.d.ts +24 -6
  34. package/dist/integrations/social-api.d.ts +2 -1
  35. package/dist/interaction-holder-heartbeat.d.ts +17 -0
  36. package/dist/interaction-metrics.d.ts +11 -0
  37. package/dist/mcp/editable-artifact-query-schema.d.ts +4 -0
  38. package/dist/mcp/editable-artifacts.d.ts +13 -0
  39. package/dist/mcp/scheduled-task-view.d.ts +168 -0
  40. package/dist/mcp/server.d.ts +16 -4
  41. package/dist/memory-slack-delivery.d.ts +9 -0
  42. package/dist/model-catalog.d.ts +1 -0
  43. package/dist/routes/api-integrations.d.ts +8 -0
  44. package/dist/routes/browser-identities.d.ts +5 -0
  45. package/dist/routes/browser-sessions.d.ts +6 -0
  46. package/dist/routes/canonical-human-identities.d.ts +3 -0
  47. package/dist/routes/channels.d.ts +3 -0
  48. package/dist/routes/company-profile.d.ts +3 -0
  49. package/dist/routes/computer-sessions.d.ts +6 -0
  50. package/dist/routes/editable-artifacts.d.ts +44 -0
  51. package/dist/routes/integration-facets.d.ts +3 -0
  52. package/dist/routes/interaction-resources.d.ts +5 -0
  53. package/dist/routes/memory-slack-publications.d.ts +6 -0
  54. package/dist/routes/organization-memberships.d.ts +3 -0
  55. package/dist/routes/plugins.d.ts +8 -0
  56. package/dist/routes/sessions.d.ts +18 -2
  57. package/dist/routes/skills.d.ts +6 -0
  58. package/dist/routes/slack-task-policy.d.ts +3 -0
  59. package/dist/routes/supergrok.d.ts +3 -0
  60. package/dist/routes/video-generation.d.ts +3 -0
  61. package/dist/sandbox/channel-a.d.ts +62 -2
  62. package/dist/sandbox/machines.d.ts +2 -2
  63. package/dist/sandbox/metrics-ingestion.d.ts +6 -1
  64. package/dist/sandbox/viewer.d.ts +7 -3
  65. package/dist/slack-reaction-files.d.ts +27 -0
  66. package/dist/temporal-schedule-cleanup.d.ts +26 -0
  67. package/dist/transcription/providers/xai-subscription.d.ts +8 -0
  68. package/dist/xai-realtime.d.ts +26 -0
  69. package/dist/xai-subscription-auth.d.ts +23 -0
  70. package/package.json +20 -14
  71. package/src/app.ts +604 -64
  72. package/src/auth/canonical-human-session-admission.ts +116 -0
  73. package/src/auth/managed-auth.ts +101 -0
  74. package/src/browser-auth-broker.ts +155 -0
  75. package/src/browser-controller-authority.ts +186 -0
  76. package/src/browser-network-route.ts +34 -0
  77. package/src/browser-state-authority.ts +236 -0
  78. package/src/codemode.ts +186 -0
  79. package/src/controller-data-plane.ts +35 -0
  80. package/src/editable-artifact-live-hints.ts +64 -0
  81. package/src/editable-artifact-native-kernel.ts +659 -0
  82. package/src/editable-artifact-office-import.ts +230 -0
  83. package/src/editable-artifact-production.ts +419 -0
  84. package/src/editable-artifact-websocket.ts +311 -0
  85. package/src/editable-artifact-workspace-files.ts +186 -0
  86. package/src/github-browser-flow.ts +35 -6
  87. package/src/http/auth.ts +29 -9
  88. package/src/http/cors.ts +3 -0
  89. package/src/http/sse.ts +229 -5
  90. package/src/index.ts +119 -20
  91. package/src/integrations/api-integrations.ts +365 -0
  92. package/src/integrations/atlassian.ts +1621 -0
  93. package/src/integrations/fiken.ts +1230 -0
  94. package/src/integrations/github-skill-source.ts +142 -0
  95. package/src/integrations/google-drive.ts +1005 -64
  96. package/src/integrations/oauth-client.ts +123 -33
  97. package/src/integrations/provider-oauth.ts +756 -0
  98. package/src/integrations/slack-bot.ts +669 -11
  99. package/src/integrations/slack-interactions.ts +2063 -93
  100. package/src/integrations/social-api.ts +11 -0
  101. package/src/interaction-holder-heartbeat.ts +95 -0
  102. package/src/interaction-metrics.ts +159 -0
  103. package/src/mcp/documents.ts +146 -6
  104. package/src/mcp/editable-artifact-query-schema.ts +236 -0
  105. package/src/mcp/editable-artifacts.ts +448 -0
  106. package/src/mcp/scheduled-task-view.ts +34 -0
  107. package/src/mcp/server.ts +1756 -355
  108. package/src/memory-slack-delivery.ts +209 -0
  109. package/src/model-catalog.ts +19 -6
  110. package/src/routes/api-integrations.ts +410 -0
  111. package/src/routes/browser-identities.ts +160 -0
  112. package/src/routes/browser-sessions.ts +4482 -0
  113. package/src/routes/canonical-human-identities.ts +156 -0
  114. package/src/routes/channels.ts +90 -0
  115. package/src/routes/company-profile.ts +255 -0
  116. package/src/routes/computer-sessions.ts +1602 -0
  117. package/src/routes/connections.ts +421 -99
  118. package/src/routes/documents.ts +22 -3
  119. package/src/routes/editable-artifacts.ts +1159 -0
  120. package/src/routes/files.ts +106 -4
  121. package/src/routes/github.ts +18 -2
  122. package/src/routes/install.ts +7 -1
  123. package/src/routes/integration-facets.ts +258 -0
  124. package/src/routes/interaction-resources.ts +595 -0
  125. package/src/routes/memory-slack-publications.ts +216 -0
  126. package/src/routes/organization-memberships.ts +53 -0
  127. package/src/routes/packs.ts +437 -7
  128. package/src/routes/plugins.ts +752 -0
  129. package/src/routes/rigs.ts +77 -20
  130. package/src/routes/scheduled-tasks.ts +67 -40
  131. package/src/routes/sessions.ts +830 -278
  132. package/src/routes/skills.ts +256 -0
  133. package/src/routes/slack-task-policy.ts +115 -0
  134. package/src/routes/supergrok.ts +717 -0
  135. package/src/routes/transcription-recordings.ts +9 -2
  136. package/src/routes/transcriptions.ts +2 -1
  137. package/src/routes/video-generation.ts +162 -0
  138. package/src/routes/workspaces.ts +68 -28
  139. package/src/sandbox/channel-a.ts +750 -116
  140. package/src/sandbox/machines.ts +5 -5
  141. package/src/sandbox/metrics-ingestion.ts +121 -3
  142. package/src/sandbox/rematerialize.ts +35 -47
  143. package/src/sandbox/viewer.ts +39 -12
  144. package/src/slack-reaction-files.ts +181 -0
  145. package/src/temporal-schedule-cleanup.ts +135 -0
  146. package/src/transcription/providers/xai-subscription.ts +110 -0
  147. package/src/transcription/service.ts +17 -2
  148. package/src/xai-realtime.ts +216 -0
  149. package/src/xai-subscription-auth.ts +132 -0
  150. package/dist/chunk-T4T2PGU4.js.map +0 -1
  151. package/dist/mcp/toolspace.d.ts +0 -71
  152. package/src/mcp/toolspace.ts +0 -1190
@@ -0,0 +1,230 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ ArtifactOfficeSourceUnsupportedError,
4
+ prepareArtifactOfficeImport,
5
+ type ArtifactOfficeMimeType,
6
+ } from "@opengeni/artifact-tool/office-import";
7
+ import {
8
+ MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES,
9
+ EDITABLE_ARTIFACT_ORIGINAL_IMPORT_MAX_BYTES,
10
+ editableArtifactCausalFrontier,
11
+ editableArtifactContentHash,
12
+ editableArtifactReplicaId,
13
+ editableArtifactStateHash,
14
+ EditableArtifactOfficeImportError,
15
+ type EditableArtifactOfficeImportPort,
16
+ type EditableArtifactModality,
17
+ } from "@opengeni/core/editable-artifacts";
18
+ import { getFile } from "@opengeni/db";
19
+ import {
20
+ MAX_BOUNDED_OBJECT_CHUNK_BYTES,
21
+ type BoundedImmutableObjectWritePort,
22
+ type ObjectStorage,
23
+ } from "@opengeni/storage";
24
+ import type { Database } from "@opengeni/db";
25
+ import type { VerifiedNativeArtifactRuntimeBinding } from "./editable-artifact-native-kernel";
26
+
27
+ const SNAPSHOT_MIME = "application/vnd.opengeni.editable-artifact-snapshot" as const;
28
+
29
+ export type EditableArtifactOfficeImportAdapterDependencies = Readonly<{
30
+ db: Database;
31
+ objectStorage: ObjectStorage;
32
+ runtime: VerifiedNativeArtifactRuntimeBinding;
33
+ sourceObjects: BoundedImmutableObjectWritePort;
34
+ snapshotObjects: BoundedImmutableObjectWritePort;
35
+ readFile?: typeof getFile;
36
+ prepareOffice?: typeof prepareArtifactOfficeImport;
37
+ }>;
38
+
39
+ /** Trusted import boundary from one ready workspace file into native sequence-zero state. */
40
+ export class EditableArtifactOfficeImportAdapter implements EditableArtifactOfficeImportPort {
41
+ private readonly readFile: typeof getFile;
42
+
43
+ constructor(private readonly dependencies: EditableArtifactOfficeImportAdapterDependencies) {
44
+ this.readFile = dependencies.readFile ?? getFile;
45
+ }
46
+
47
+ async prepare(
48
+ input: Parameters<EditableArtifactOfficeImportPort["prepare"]>[0],
49
+ ): ReturnType<EditableArtifactOfficeImportPort["prepare"]> {
50
+ throwIfAborted(input.signal);
51
+ const file = await this.readFile(this.dependencies.db, input.scope.workspaceId, input.fileId);
52
+ if (!file || file.status !== "ready") {
53
+ throw new EditableArtifactOfficeImportError("invalid_source");
54
+ }
55
+ const expectedMimeType = officeMimeType(input.modality);
56
+ if (
57
+ file.contentType !== expectedMimeType ||
58
+ !officeFilenameMatches(file.filename, input.modality) ||
59
+ !Number.isSafeInteger(file.sizeBytes) ||
60
+ file.sizeBytes < 1 ||
61
+ file.sizeBytes > EDITABLE_ARTIFACT_ORIGINAL_IMPORT_MAX_BYTES ||
62
+ typeof file.sha256 !== "string" ||
63
+ !/^[0-9a-f]{64}$/u.test(file.sha256)
64
+ ) {
65
+ throw new EditableArtifactOfficeImportError("invalid_source");
66
+ }
67
+ const sourceBytes = await readVerifiedWorkspaceFile(
68
+ this.dependencies.objectStorage,
69
+ file,
70
+ input.signal,
71
+ );
72
+ const prepared = await prepareOfficeImport({
73
+ runtime: this.dependencies.runtime,
74
+ modality: input.modality,
75
+ filename: file.filename,
76
+ mimeType: expectedMimeType,
77
+ bytes: sourceBytes,
78
+ prepare: this.dependencies.prepareOffice ?? prepareArtifactOfficeImport,
79
+ });
80
+ const retainedSource = await this.dependencies.sourceObjects.write({
81
+ chunks: byteChunks(sourceBytes),
82
+ contentType: expectedMimeType,
83
+ maxBytes: EDITABLE_ARTIFACT_ORIGINAL_IMPORT_MAX_BYTES,
84
+ expectedByteSize: prepared.source.byteSize,
85
+ expectedContentHash: prepared.source.contentHash,
86
+ ...(input.signal ? { signal: input.signal } : {}),
87
+ });
88
+ const retainedSnapshot = await this.dependencies.snapshotObjects.write({
89
+ chunks: byteChunks(prepared.snapshot.bytes),
90
+ contentType: SNAPSHOT_MIME,
91
+ maxBytes: MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES,
92
+ expectedByteSize: prepared.snapshot.byteSize,
93
+ expectedContentHash: prepared.snapshot.contentHash,
94
+ ...(input.signal ? { signal: input.signal } : {}),
95
+ });
96
+ const common = {
97
+ modality: prepared.snapshot.modality,
98
+ blobReference: retainedSnapshot.opaqueReference,
99
+ byteSize: prepared.snapshot.byteSize,
100
+ contentHash: editableArtifactContentHash(prepared.snapshot.contentHash),
101
+ mimeType: SNAPSHOT_MIME,
102
+ coveredHeadSequence: 0 as const,
103
+ stateHash: editableArtifactStateHash(prepared.snapshot.stateHash),
104
+ modelSchemaVersion: prepared.snapshot.modelSchemaVersion,
105
+ kernelVersion: prepared.snapshot.kernelVersion,
106
+ };
107
+ return Object.freeze({
108
+ originalImport: Object.freeze({
109
+ fileId: file.id,
110
+ blobReference: retainedSource.opaqueReference,
111
+ byteSize: prepared.source.byteSize,
112
+ contentHash: editableArtifactContentHash(prepared.source.contentHash),
113
+ mimeType: expectedMimeType,
114
+ }),
115
+ snapshot:
116
+ prepared.snapshot.modality === "spreadsheet"
117
+ ? Object.freeze({
118
+ ...common,
119
+ modality: "spreadsheet" as const,
120
+ coveredCausalFrontier: editableArtifactCausalFrontier(
121
+ prepared.snapshot.coveredCausalFrontier.map((entry) => ({
122
+ replicaId: editableArtifactReplicaId(entry.replicaId),
123
+ counter: entry.counter,
124
+ })),
125
+ ),
126
+ operationProtocolVersion: prepared.snapshot.operationProtocolVersion,
127
+ crdtStateVersion: prepared.snapshot.crdtStateVersion,
128
+ })
129
+ : Object.freeze({
130
+ ...common,
131
+ modality: prepared.snapshot.modality,
132
+ nativeRevision: prepared.snapshot.nativeRevision,
133
+ }),
134
+ });
135
+ }
136
+ }
137
+
138
+ async function prepareOfficeImport(input: {
139
+ runtime: VerifiedNativeArtifactRuntimeBinding;
140
+ modality: EditableArtifactModality;
141
+ filename: string;
142
+ mimeType: ArtifactOfficeMimeType;
143
+ bytes: Uint8Array;
144
+ prepare: typeof prepareArtifactOfficeImport;
145
+ }) {
146
+ try {
147
+ return await input.prepare({
148
+ facade: input.runtime.facade,
149
+ modality: input.modality,
150
+ filename: input.filename,
151
+ mimeType: input.mimeType,
152
+ bytes: input.bytes,
153
+ expectedRuntimeTarget: input.runtime.location.target,
154
+ expectedKernelVersion: input.runtime.runtime.buildIdentity,
155
+ });
156
+ } catch (error) {
157
+ if (error instanceof ArtifactOfficeSourceUnsupportedError) {
158
+ throw new EditableArtifactOfficeImportError("unsupported_content");
159
+ }
160
+ throw error;
161
+ }
162
+ }
163
+
164
+ function officeMimeType(modality: EditableArtifactModality): ArtifactOfficeMimeType {
165
+ if (modality === "spreadsheet") {
166
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
167
+ }
168
+ if (modality === "document") {
169
+ return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
170
+ }
171
+ return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
172
+ }
173
+
174
+ function officeFilenameMatches(filename: string, modality: EditableArtifactModality): boolean {
175
+ const extension =
176
+ modality === "spreadsheet" ? ".xlsx" : modality === "document" ? ".docx" : ".pptx";
177
+ return filename.toLowerCase().endsWith(extension);
178
+ }
179
+
180
+ async function readVerifiedWorkspaceFile(
181
+ storage: ObjectStorage,
182
+ file: NonNullable<Awaited<ReturnType<typeof getFile>>>,
183
+ signal?: AbortSignal,
184
+ ): Promise<Uint8Array> {
185
+ throwIfAborted(signal);
186
+ const before = await storage.headFile(file);
187
+ if (
188
+ before.ContentLength !== file.sizeBytes ||
189
+ before.ContentType !== file.contentType ||
190
+ before.Metadata?.sha256 !== file.sha256 ||
191
+ typeof before.VersionToken !== "string" ||
192
+ before.VersionToken.length < 1
193
+ ) {
194
+ throw new EditableArtifactOfficeImportError("source_changed");
195
+ }
196
+ const bytes = new Uint8Array(file.sizeBytes);
197
+ const digest = createHash("sha256");
198
+ for (let start = 0; start < file.sizeBytes; start += MAX_BOUNDED_OBJECT_CHUNK_BYTES) {
199
+ throwIfAborted(signal);
200
+ const end = Math.min(file.sizeBytes, start + MAX_BOUNDED_OBJECT_CHUNK_BYTES) - 1;
201
+ const chunk = await storage.getFileRange(file, { start, end });
202
+ if (!chunk || chunk.byteLength !== end - start + 1) {
203
+ throw new EditableArtifactOfficeImportError("source_changed");
204
+ }
205
+ bytes.set(chunk, start);
206
+ digest.update(chunk);
207
+ }
208
+ const after = await storage.headFile(file);
209
+ const sha256 = digest.digest("hex");
210
+ if (
211
+ after.ContentLength !== before.ContentLength ||
212
+ after.ContentType !== before.ContentType ||
213
+ after.Metadata?.sha256 !== before.Metadata?.sha256 ||
214
+ after.VersionToken !== before.VersionToken ||
215
+ sha256 !== file.sha256
216
+ ) {
217
+ throw new EditableArtifactOfficeImportError("source_changed");
218
+ }
219
+ return bytes;
220
+ }
221
+
222
+ async function* byteChunks(bytes: Uint8Array): AsyncIterable<Uint8Array> {
223
+ for (let offset = 0; offset < bytes.byteLength; offset += MAX_BOUNDED_OBJECT_CHUNK_BYTES) {
224
+ yield bytes.subarray(offset, offset + MAX_BOUNDED_OBJECT_CHUNK_BYTES);
225
+ }
226
+ }
227
+
228
+ function throwIfAborted(signal?: AbortSignal): void {
229
+ if (signal?.aborted) throw signal.reason ?? new Error("Artifact import aborted");
230
+ }
@@ -0,0 +1,419 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import {
3
+ editableArtifactMaterializerCapabilitiesForRuntime,
4
+ type EditableArtifactNativeMaterializerCapabilities,
5
+ } from "@opengeni/artifact-tool/runtime/materializer";
6
+ import {
7
+ EditableArtifactDurableExportService,
8
+ EditableArtifactAgentApplication,
9
+ EDITABLE_ARTIFACT_EXPORT_MAX_DOWNLOAD_BYTES,
10
+ EditableArtifactCompactionPipeline,
11
+ EditableArtifactGenesisPipeline,
12
+ EditableArtifactService,
13
+ MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES,
14
+ ProductionEditableArtifactSnapshotVerifier,
15
+ SystemEditableArtifactClock,
16
+ editableArtifactLiveReadPortFromPostgres,
17
+ editableArtifactLiveTicketStorePortFromPostgres,
18
+ editableArtifactStorePortFromPostgres,
19
+ editableArtifactDurableExportStorePortFromPostgres,
20
+ ogatxEditableArtifactMutationIntentCodec,
21
+ editableArtifactId,
22
+ type EditableArtifactAuthorizationDecision,
23
+ type EditableArtifactAuthorizationPort,
24
+ type EditableArtifactAuthorizationRequest,
25
+ type EditableArtifactStableIdFactoryPort,
26
+ type EditableArtifactStableIdKind,
27
+ type EditableArtifactDurableExportIdFactoryPort,
28
+ type EditableArtifactExactSnapshotPort,
29
+ type EditableArtifactAgentAssociationPort,
30
+ type EditableArtifactMaterializationProfilePort,
31
+ } from "@opengeni/core/editable-artifacts";
32
+ import {
33
+ EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION,
34
+ EditableArtifactApplication,
35
+ EditableArtifactCompatibilityError,
36
+ EditableArtifactLiveServer,
37
+ SystemEditableArtifactLiveClock,
38
+ SystemEditableArtifactLiveScheduler,
39
+ WebCryptoEditableArtifactLiveTokens,
40
+ type EditableArtifactApplicationPort,
41
+ type EditableArtifactLiveAuthorizationInvalidationPort,
42
+ type EditableArtifactLiveCompatibilityPort,
43
+ } from "@opengeni/core/editable-artifact-live";
44
+ import {
45
+ PostgresEditableArtifactLiveReadStore,
46
+ PostgresEditableArtifactLiveTicketStore,
47
+ PostgresEditableArtifactStore,
48
+ listEditableArtifactIdsForSession,
49
+ touchEditableArtifactSessionLink,
50
+ dbSql,
51
+ withRlsContext,
52
+ type Database,
53
+ type PersistedEditableArtifactSnapshotBytesPort,
54
+ type PersistedEditableArtifactSnapshotMetadata,
55
+ } from "@opengeni/db";
56
+ import { PostgresEditableArtifactDurableExportStore } from "@opengeni/db/editable-artifact-durable-export";
57
+ import type { EventBus } from "@opengeni/events";
58
+ import {
59
+ MAX_BOUNDED_OBJECT_CHUNK_BYTES,
60
+ createObjectStorageBoundedPorts,
61
+ type BoundedObjectReadPort,
62
+ type ObjectStorage,
63
+ } from "@opengeni/storage";
64
+ import { EventBusEditableArtifactLiveHints } from "./editable-artifact-live-hints";
65
+ import {
66
+ NativeEditableArtifactKernelAdapter,
67
+ loadVerifiedNativeArtifactRuntimeBinding,
68
+ } from "./editable-artifact-native-kernel";
69
+ import { EditableArtifactOfficeImportAdapter } from "./editable-artifact-office-import";
70
+ import { EditableArtifactWorkspaceFileAdapter } from "./editable-artifact-workspace-files";
71
+
72
+ const INVALIDATION_POLL_MS = 1_000;
73
+ const TICKET_CLEANUP_MS = 30_000;
74
+
75
+ export type StandaloneEditableArtifactApplication = Readonly<{
76
+ application: EditableArtifactApplicationPort;
77
+ durableExports: EditableArtifactDurableExportService;
78
+ agent: EditableArtifactAgentApplication;
79
+ officeImports: EditableArtifactOfficeImportAdapter;
80
+ kernelVersion: string;
81
+ close(): void;
82
+ }>;
83
+
84
+ /**
85
+ * Standalone production composition. Every authority is durable: PostgreSQL
86
+ * owns admission/authorization/history, object storage owns immutable model
87
+ * bytes, and NATS carries wake-up hints only.
88
+ */
89
+ export async function createStandaloneEditableArtifactApplication(input: {
90
+ db: Database;
91
+ bus: EventBus;
92
+ objectStorage: ObjectStorage;
93
+ }): Promise<StandaloneEditableArtifactApplication> {
94
+ const boundedObjects = createObjectStorageBoundedPorts(input.objectStorage);
95
+ const materializationObjects = createObjectStorageBoundedPorts(input.objectStorage, {
96
+ keyPrefix: "editable-artifacts/materializations/v1/sha256/",
97
+ });
98
+ const importSourceObjects = createObjectStorageBoundedPorts(input.objectStorage, {
99
+ keyPrefix: "editable-artifacts/imports/v1/sha256/",
100
+ });
101
+ const runtimeBinding = await loadVerifiedNativeArtifactRuntimeBinding();
102
+ const runtime = runtimeBinding.runtime;
103
+ const kernel = new NativeEditableArtifactKernelAdapter(runtime, boundedObjects.read);
104
+ const persistedStore = new PostgresEditableArtifactStore(input.db);
105
+ const store = editableArtifactStorePortFromPostgres(persistedStore);
106
+ const authorization = new PostgresEditableArtifactAuthorization(input.db);
107
+ const ids = new RandomEditableArtifactStableIds();
108
+ const clock = new SystemEditableArtifactClock();
109
+ const snapshotVerifier = new ProductionEditableArtifactSnapshotVerifier({
110
+ objects: boundedObjects.read,
111
+ kernel,
112
+ });
113
+ const genesis = new EditableArtifactGenesisPipeline({
114
+ kernel,
115
+ objects: boundedObjects.write,
116
+ now: () => clock.now(),
117
+ });
118
+ const compaction = new EditableArtifactCompactionPipeline({
119
+ kernel,
120
+ objects: boundedObjects.write,
121
+ now: () => clock.now(),
122
+ });
123
+ const domain = new EditableArtifactService({
124
+ authorization,
125
+ kernel,
126
+ store,
127
+ intentCodec: ogatxEditableArtifactMutationIntentCodec,
128
+ clock,
129
+ ids,
130
+ snapshotVerifier,
131
+ genesis,
132
+ compaction,
133
+ });
134
+ const materializerCapabilities = editableArtifactMaterializerCapabilitiesForRuntime(
135
+ runtime,
136
+ runtimeBinding.location.artifactTool.packageVersion,
137
+ );
138
+ const durableExports = new EditableArtifactDurableExportService({
139
+ authorization,
140
+ exactSnapshots: Object.freeze({
141
+ ensure: async (request: Parameters<EditableArtifactExactSnapshotPort["ensure"]>[0]) =>
142
+ await domain.compactCurrentHead(request),
143
+ }),
144
+ store: editableArtifactDurableExportStorePortFromPostgres(
145
+ new PostgresEditableArtifactDurableExportStore(input.db),
146
+ ),
147
+ ids: new RandomEditableArtifactDurableExportIds(),
148
+ profiles: materializationProfiles(materializerCapabilities),
149
+ materializationObjects: materializationObjects.read,
150
+ });
151
+ const officeImports = new EditableArtifactOfficeImportAdapter({
152
+ db: input.db,
153
+ objectStorage: input.objectStorage,
154
+ runtime: runtimeBinding,
155
+ sourceObjects: importSourceObjects.write,
156
+ snapshotObjects: boundedObjects.write,
157
+ });
158
+ const agent = new EditableArtifactAgentApplication({
159
+ domain,
160
+ exports: durableExports,
161
+ associations: Object.freeze({
162
+ listArtifactIds: async ({ scope, sessionId, limit }) =>
163
+ (await listEditableArtifactIdsForSession(input.db, scope, sessionId, limit)).map(
164
+ editableArtifactId,
165
+ ),
166
+ touch: async ({ scope, sessionId, artifactId }) =>
167
+ await touchEditableArtifactSessionLink(input.db, scope, sessionId, artifactId),
168
+ } satisfies EditableArtifactAgentAssociationPort),
169
+ inspector: kernel,
170
+ officeImports,
171
+ workspaceFiles: new EditableArtifactWorkspaceFileAdapter({
172
+ db: input.db,
173
+ objectStorage: input.objectStorage,
174
+ durableExports,
175
+ }),
176
+ });
177
+ const ticketStore = new PostgresEditableArtifactLiveTicketStore(input.db);
178
+ const liveRead = new PostgresEditableArtifactLiveReadStore(input.db, {
179
+ snapshotBytes: snapshotBytesPort(boundedObjects.read),
180
+ });
181
+ const invalidations = new PollingEditableArtifactAuthorizationInvalidations(authorization);
182
+ const live = new EditableArtifactLiveServer({
183
+ authorization,
184
+ domain,
185
+ tickets: editableArtifactLiveTicketStorePortFromPostgres(ticketStore),
186
+ tokens: new WebCryptoEditableArtifactLiveTokens(),
187
+ clock: new SystemEditableArtifactLiveClock(),
188
+ scheduler: new SystemEditableArtifactLiveScheduler(),
189
+ read: editableArtifactLiveReadPortFromPostgres(liveRead),
190
+ hints: new EventBusEditableArtifactLiveHints(input.bus),
191
+ invalidations,
192
+ });
193
+ const compatibility: EditableArtifactLiveCompatibilityPort = Object.freeze({
194
+ assertCompatible(
195
+ request: Parameters<EditableArtifactLiveCompatibilityPort["assertCompatible"]>[0],
196
+ ) {
197
+ if (
198
+ request.protocolVersion !== EDITABLE_ARTIFACT_LIVE_PROTOCOL_VERSION ||
199
+ request.modelSchemaVersion !== 1 ||
200
+ request.kernelVersion !== runtime.buildIdentity
201
+ ) {
202
+ throw new EditableArtifactCompatibilityError();
203
+ }
204
+ },
205
+ });
206
+ const application = new EditableArtifactApplication({
207
+ domain,
208
+ live,
209
+ compatibility,
210
+ });
211
+ const cleanup = setInterval(() => {
212
+ void ticketStore.cleanupExpired().catch(() => undefined);
213
+ }, TICKET_CLEANUP_MS);
214
+ cleanup.unref?.();
215
+ return Object.freeze({
216
+ application,
217
+ durableExports,
218
+ agent,
219
+ officeImports,
220
+ kernelVersion: runtime.buildIdentity,
221
+ close() {
222
+ clearInterval(cleanup);
223
+ invalidations.close();
224
+ },
225
+ });
226
+ }
227
+
228
+ export class PostgresEditableArtifactAuthorization implements EditableArtifactAuthorizationPort {
229
+ constructor(private readonly db: Database) {}
230
+
231
+ async authorize(
232
+ request: EditableArtifactAuthorizationRequest,
233
+ ): Promise<EditableArtifactAuthorizationDecision> {
234
+ const actor = request.actor;
235
+ const rows = await withRlsContext(this.db, request.scope, async (tx) =>
236
+ rawRows<{ allowed: boolean; authorization_revision: number | string | bigint }>(
237
+ await tx.execute(dbSql`select *
238
+ from opengeni_private.authorize_editable_artifact_actor(
239
+ ${request.scope.accountId}::uuid,
240
+ ${request.scope.workspaceId}::uuid,
241
+ ${request.artifactId},
242
+ ${actor.kind},
243
+ ${actor.subjectId},
244
+ ${actor.kind === "agent" ? actor.sessionId : null},
245
+ ${actor.kind === "agent" ? actor.turnId : null},
246
+ ${actor.kind === "agent" ? actor.attemptId : null},
247
+ ${actor.kind === "agent" ? actor.generation : null},
248
+ ${actor.kind === "service" ? actor.service : null},
249
+ ${request.permission},
250
+ current_schema()
251
+ )`),
252
+ ),
253
+ );
254
+ if (rows.length !== 1) throw new Error("Editable artifact authorization returned no decision");
255
+ const revision = Number(rows[0]!.authorization_revision);
256
+ if (!Number.isSafeInteger(revision) || revision < 1) {
257
+ throw new Error("Editable artifact authorization returned an invalid revision");
258
+ }
259
+ return Object.freeze({ allowed: rows[0]!.allowed === true, revision });
260
+ }
261
+ }
262
+
263
+ class PollingEditableArtifactAuthorizationInvalidations implements EditableArtifactLiveAuthorizationInvalidationPort {
264
+ private readonly stops = new Set<() => void>();
265
+
266
+ constructor(private readonly authorization: EditableArtifactAuthorizationPort) {}
267
+
268
+ async subscribe(
269
+ input: Parameters<EditableArtifactLiveAuthorizationInvalidationPort["subscribe"]>[0],
270
+ ): Promise<() => void> {
271
+ let active = true;
272
+ let polling = false;
273
+ let baseline = await this.read(input);
274
+ const timer = setInterval(() => {
275
+ if (!active || polling) return;
276
+ polling = true;
277
+ void this.read(input)
278
+ .then((next) => {
279
+ if (!active) return;
280
+ if (next !== baseline) {
281
+ baseline = next;
282
+ input.onInvalidated();
283
+ }
284
+ })
285
+ .catch(() => {
286
+ if (active) input.onInvalidated();
287
+ })
288
+ .finally(() => {
289
+ polling = false;
290
+ });
291
+ }, INVALIDATION_POLL_MS);
292
+ timer.unref?.();
293
+ const stop = () => {
294
+ if (!active) return;
295
+ active = false;
296
+ clearInterval(timer);
297
+ this.stops.delete(stop);
298
+ };
299
+ this.stops.add(stop);
300
+ return stop;
301
+ }
302
+
303
+ close(): void {
304
+ for (const stop of [...this.stops]) stop();
305
+ }
306
+
307
+ private async read(
308
+ input: Parameters<EditableArtifactLiveAuthorizationInvalidationPort["subscribe"]>[0],
309
+ ): Promise<string> {
310
+ const decision = await this.authorization.authorize({
311
+ scope: input.scope,
312
+ artifactId: input.artifactId,
313
+ actor: input.actor,
314
+ permission: "read",
315
+ });
316
+ return `${decision.allowed ? 1 : 0}:${decision.revision}`;
317
+ }
318
+ }
319
+
320
+ class RandomEditableArtifactStableIds implements EditableArtifactStableIdFactoryPort {
321
+ next(_kind: EditableArtifactStableIdKind) {
322
+ return randomUUID().replaceAll("-", "") as ReturnType<
323
+ EditableArtifactStableIdFactoryPort["next"]
324
+ >;
325
+ }
326
+ }
327
+
328
+ class RandomEditableArtifactDurableExportIds implements EditableArtifactDurableExportIdFactoryPort {
329
+ next(_kind: "version" | "materialization_job" | "receipt"): string {
330
+ return randomUUID().replaceAll("-", "");
331
+ }
332
+ }
333
+
334
+ function materializationProfiles(
335
+ capabilities: EditableArtifactNativeMaterializerCapabilities,
336
+ ): EditableArtifactMaterializationProfilePort {
337
+ if (
338
+ capabilities.protocol !== "OGAMC001" ||
339
+ capabilities.runtimeKind !== "native" ||
340
+ capabilities.maxOutputBytes !== EDITABLE_ARTIFACT_EXPORT_MAX_DOWNLOAD_BYTES ||
341
+ capabilities.codecVersions["opengeni.xlsx"] === undefined ||
342
+ !capabilities.supportedModelSchemaVersions.includes(1) ||
343
+ !capabilities.supportedOperationProtocolVersions.includes(1) ||
344
+ !capabilities.supportedSnapshotProtocolVersions.includes(1)
345
+ ) {
346
+ throw new Error("Verified artifact materializer profile is incompatible");
347
+ }
348
+ const codecVersion = capabilities.codecVersions["opengeni.xlsx"];
349
+ return Object.freeze({
350
+ async resolve(input: Parameters<EditableArtifactMaterializationProfilePort["resolve"]>[0]) {
351
+ if (
352
+ input.modality !== "spreadsheet" ||
353
+ input.format !== "xlsx" ||
354
+ Object.keys(input.options).length !== 0
355
+ ) {
356
+ return null;
357
+ }
358
+ return Object.freeze({
359
+ modality: "spreadsheet" as const,
360
+ format: "xlsx" as const,
361
+ codecId: "opengeni.xlsx",
362
+ codecVersion,
363
+ kernelVersion: capabilities.kernelVersion,
364
+ fontRegistryHash: capabilities.fontRegistryHash,
365
+ policyHash: capabilities.policyHash,
366
+ normalizedOptions: "{}",
367
+ });
368
+ },
369
+ });
370
+ }
371
+
372
+ function snapshotBytesPort(
373
+ objects: BoundedObjectReadPort,
374
+ ): PersistedEditableArtifactSnapshotBytesPort {
375
+ return Object.freeze({
376
+ async readSnapshotBytes(snapshot: PersistedEditableArtifactSnapshotMetadata) {
377
+ const object = await objects.open({
378
+ opaqueReference: snapshot.blobReference,
379
+ maxBytes: MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES,
380
+ expectedByteSize: snapshot.byteSize,
381
+ });
382
+ try {
383
+ if (object.contentType !== undefined && object.contentType !== snapshot.mimeType) {
384
+ throw new Error("Editable artifact snapshot content type mismatch");
385
+ }
386
+ const bytes = new Uint8Array(snapshot.byteSize);
387
+ const digest = createHash("sha256");
388
+ let offset = 0;
389
+ for await (const chunk of object.chunks({
390
+ chunkBytes: MAX_BOUNDED_OBJECT_CHUNK_BYTES,
391
+ })) {
392
+ if (offset + chunk.byteLength > bytes.byteLength) {
393
+ throw new Error("Editable artifact snapshot exceeds its declared size");
394
+ }
395
+ bytes.set(chunk, offset);
396
+ digest.update(chunk);
397
+ offset += chunk.byteLength;
398
+ }
399
+ if (
400
+ offset !== bytes.byteLength ||
401
+ `sha256:${digest.digest("hex")}` !== snapshot.contentHash
402
+ ) {
403
+ throw new Error("Editable artifact snapshot digest or size mismatch");
404
+ }
405
+ await object.assertUnchanged();
406
+ return bytes;
407
+ } finally {
408
+ await object.close();
409
+ }
410
+ },
411
+ });
412
+ }
413
+
414
+ function rawRows<T extends Record<string, unknown>>(result: unknown): T[] {
415
+ if (Array.isArray(result)) return result as T[];
416
+ const rows = (result as { rows?: unknown } | null)?.rows;
417
+ if (Array.isArray(rows)) return rows as T[];
418
+ throw new Error("Unsupported database execute result shape");
419
+ }