@lumenkey/cli 0.3.0 → 0.4.0-rc.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 (184) hide show
  1. package/README.md +51 -2
  2. package/dist/cli/copied.js +86 -85
  3. package/dist/cli/index.js +56 -44
  4. package/dist/cli/render.js +5 -1
  5. package/dist/core/asset-stream.d.ts +16 -0
  6. package/dist/core/asset-stream.js +64 -0
  7. package/dist/core/atomic-config-file.js +8 -7
  8. package/dist/core/chinese-messages.d.ts +4 -0
  9. package/dist/core/chinese-messages.js +907 -0
  10. package/dist/core/chinese-script.d.ts +7 -0
  11. package/dist/core/chinese-script.js +28 -0
  12. package/dist/core/client.js +2 -1
  13. package/dist/core/config-dir.js +2 -1
  14. package/dist/core/config.js +7 -6
  15. package/dist/core/download-root.d.ts +5 -0
  16. package/dist/core/download-root.js +64 -0
  17. package/dist/core/download.js +15 -12
  18. package/dist/core/generation-input.js +3 -2
  19. package/dist/core/generation.d.ts +8 -1
  20. package/dist/core/generation.js +13 -10
  21. package/dist/core/job-display.js +7 -6
  22. package/dist/core/locale.d.ts +2 -0
  23. package/dist/core/locale.js +172 -142
  24. package/dist/core/outcome.d.ts +6 -0
  25. package/dist/core/outcome.js +17 -4
  26. package/dist/core/output-path.d.ts +3 -1
  27. package/dist/core/output-path.js +5 -2
  28. package/dist/core/params.js +26 -25
  29. package/dist/core/profiles.js +5 -4
  30. package/dist/core/set-values.d.ts +0 -1
  31. package/dist/core/set-values.js +7 -7
  32. package/dist/core/submission-journal.js +43 -17
  33. package/dist/core/version.js +2 -3
  34. package/dist/mcp/server.js +30 -29
  35. package/dist/studio/asset-integrity-hash.d.ts +28 -0
  36. package/dist/studio/asset-integrity-hash.js +93 -0
  37. package/dist/studio/asset-integrity-queue.d.ts +24 -0
  38. package/dist/studio/asset-integrity-queue.js +94 -0
  39. package/dist/studio/asset-store.d.ts +52 -0
  40. package/dist/studio/asset-store.js +352 -0
  41. package/dist/studio/canvas/LICENSES.txt +1590 -0
  42. package/dist/studio/canvas/dependencies.json +862 -0
  43. package/dist/studio/canvas/director-source-manifest.json +47 -0
  44. package/dist/studio/canvas/editor.css +239 -0
  45. package/dist/studio/canvas/editor.js +4613 -0
  46. package/dist/studio/canvas/integration-source-manifest.json +384 -0
  47. package/dist/studio/canvas/source-manifest.json +240 -0
  48. package/dist/studio/canvas-store.d.ts +1254 -0
  49. package/dist/studio/canvas-store.js +205 -0
  50. package/dist/studio/capture-placement.d.ts +11 -0
  51. package/dist/studio/capture-placement.js +85 -0
  52. package/dist/studio/cloud-contract.d.ts +128 -0
  53. package/dist/studio/cloud-contract.js +229 -0
  54. package/dist/studio/cloud-service.d.ts +178 -0
  55. package/dist/studio/cloud-service.js +202 -0
  56. package/dist/studio/director-captures.d.ts +59 -0
  57. package/dist/studio/director-captures.js +141 -0
  58. package/dist/studio/director-contract.d.ts +1489 -0
  59. package/dist/studio/director-contract.js +288 -0
  60. package/dist/studio/director-png.d.ts +6 -0
  61. package/dist/studio/director-png.js +41 -0
  62. package/dist/studio/director-relations.d.ts +40 -0
  63. package/dist/studio/director-relations.js +103 -0
  64. package/dist/studio/document-authority.d.ts +7 -0
  65. package/dist/studio/document-authority.js +50 -0
  66. package/dist/studio/draft-store.d.ts +95 -0
  67. package/dist/studio/draft-store.js +100 -0
  68. package/dist/studio/errors.d.ts +85 -0
  69. package/dist/studio/errors.js +94 -0
  70. package/dist/studio/launch.d.ts +5 -0
  71. package/dist/studio/launch.js +31 -0
  72. package/dist/studio/media.d.ts +9 -0
  73. package/dist/studio/media.js +50 -0
  74. package/dist/studio/output-save-contract.d.ts +1321 -0
  75. package/dist/studio/output-save-contract.js +26 -0
  76. package/dist/studio/output-save-journal.d.ts +971 -0
  77. package/dist/studio/output-save-journal.js +287 -0
  78. package/dist/studio/output-save-recovery.d.ts +18 -0
  79. package/dist/studio/output-save-recovery.js +161 -0
  80. package/dist/studio/output-save-service.d.ts +436 -0
  81. package/dist/studio/output-save-service.js +251 -0
  82. package/dist/studio/output-save-worker.d.ts +40 -0
  83. package/dist/studio/output-save-worker.js +360 -0
  84. package/dist/studio/paths.d.ts +6 -0
  85. package/dist/studio/paths.js +68 -0
  86. package/dist/studio/run-journal.d.ts +993 -0
  87. package/dist/studio/run-journal.js +125 -0
  88. package/dist/studio/server.d.ts +17 -0
  89. package/dist/studio/server.js +440 -0
  90. package/dist/studio/session.d.ts +6 -0
  91. package/dist/studio/session.js +26 -0
  92. package/dist/studio/shell.d.ts +3 -0
  93. package/dist/studio/shell.js +23 -0
  94. package/dist/studio/style.d.ts +2 -0
  95. package/dist/studio/style.js +29 -0
  96. package/dist/studio/types.d.ts +26 -0
  97. package/dist/studio/types.js +2 -0
  98. package/dist/studio/web/app.d.ts +2 -0
  99. package/dist/studio/web/app.js +662 -0
  100. package/dist/studio/web/asset-controller.d.ts +37 -0
  101. package/dist/studio/web/asset-controller.js +191 -0
  102. package/dist/studio/web/asset-model.d.ts +29 -0
  103. package/dist/studio/web/asset-model.js +51 -0
  104. package/dist/studio/web/controller.d.ts +113 -0
  105. package/dist/studio/web/controller.js +401 -0
  106. package/dist/studio/web/messages.d.ts +2 -0
  107. package/dist/studio/web/messages.js +74 -0
  108. package/dist/studio/web/model.d.ts +75 -0
  109. package/dist/studio/web/model.js +182 -0
  110. package/dist/studio/web/save-controller.d.ts +85 -0
  111. package/dist/studio/web/save-controller.js +495 -0
  112. package/dist/studio/web/save-model.d.ts +63 -0
  113. package/dist/studio/web/save-model.js +64 -0
  114. package/dist/studio/web/save-queue.d.ts +7 -0
  115. package/dist/studio/web/save-queue.js +62 -0
  116. package/dist/studio/web/save-view.d.ts +9 -0
  117. package/dist/studio/web/save-view.js +19 -0
  118. package/dist/studio/writer-lock.d.ts +5 -0
  119. package/dist/studio/writer-lock.js +88 -0
  120. package/package.json +11 -5
  121. package/dist/cli/copied.d.ts.map +0 -1
  122. package/dist/cli/copied.js.map +0 -1
  123. package/dist/cli/index.d.ts.map +0 -1
  124. package/dist/cli/index.js.map +0 -1
  125. package/dist/cli/render.d.ts.map +0 -1
  126. package/dist/cli/render.js.map +0 -1
  127. package/dist/core/atomic-config-file.d.ts.map +0 -1
  128. package/dist/core/atomic-config-file.js.map +0 -1
  129. package/dist/core/chat.d.ts.map +0 -1
  130. package/dist/core/chat.js.map +0 -1
  131. package/dist/core/client.d.ts.map +0 -1
  132. package/dist/core/client.js.map +0 -1
  133. package/dist/core/config-dir.d.ts.map +0 -1
  134. package/dist/core/config-dir.js.map +0 -1
  135. package/dist/core/config-error.d.ts.map +0 -1
  136. package/dist/core/config-error.js.map +0 -1
  137. package/dist/core/config.d.ts.map +0 -1
  138. package/dist/core/config.js.map +0 -1
  139. package/dist/core/credit-cap.d.ts.map +0 -1
  140. package/dist/core/credit-cap.js.map +0 -1
  141. package/dist/core/download.d.ts.map +0 -1
  142. package/dist/core/download.js.map +0 -1
  143. package/dist/core/error-result.d.ts.map +0 -1
  144. package/dist/core/error-result.js.map +0 -1
  145. package/dist/core/explain.d.ts.map +0 -1
  146. package/dist/core/explain.js.map +0 -1
  147. package/dist/core/generation-input.d.ts.map +0 -1
  148. package/dist/core/generation-input.js.map +0 -1
  149. package/dist/core/generation-result.d.ts.map +0 -1
  150. package/dist/core/generation-result.js.map +0 -1
  151. package/dist/core/generation.d.ts.map +0 -1
  152. package/dist/core/generation.js.map +0 -1
  153. package/dist/core/job-display.d.ts.map +0 -1
  154. package/dist/core/job-display.js.map +0 -1
  155. package/dist/core/locale.d.ts.map +0 -1
  156. package/dist/core/locale.js.map +0 -1
  157. package/dist/core/origin.d.ts.map +0 -1
  158. package/dist/core/origin.js.map +0 -1
  159. package/dist/core/outcome.d.ts.map +0 -1
  160. package/dist/core/outcome.js.map +0 -1
  161. package/dist/core/output-path.d.ts.map +0 -1
  162. package/dist/core/output-path.js.map +0 -1
  163. package/dist/core/params.d.ts.map +0 -1
  164. package/dist/core/params.js.map +0 -1
  165. package/dist/core/poller.d.ts.map +0 -1
  166. package/dist/core/poller.js.map +0 -1
  167. package/dist/core/profiles.d.ts.map +0 -1
  168. package/dist/core/profiles.js.map +0 -1
  169. package/dist/core/set-values.d.ts.map +0 -1
  170. package/dist/core/set-values.js.map +0 -1
  171. package/dist/core/submission-journal.d.ts.map +0 -1
  172. package/dist/core/submission-journal.js.map +0 -1
  173. package/dist/core/types.d.ts.map +0 -1
  174. package/dist/core/types.js.map +0 -1
  175. package/dist/core/version.d.ts.map +0 -1
  176. package/dist/core/version.js.map +0 -1
  177. package/dist/core/wait.d.ts.map +0 -1
  178. package/dist/core/wait.js.map +0 -1
  179. package/dist/index.d.ts.map +0 -1
  180. package/dist/index.js.map +0 -1
  181. package/dist/mcp/render.d.ts.map +0 -1
  182. package/dist/mcp/render.js.map +0 -1
  183. package/dist/mcp/server.d.ts.map +0 -1
  184. package/dist/mcp/server.js.map +0 -1
@@ -0,0 +1,436 @@
1
+ import type { LumenKeyConfig } from '../core/types.js';
2
+ import type { StudioAssetStore } from './asset-store.js';
3
+ import { type SaveOperation } from './output-save-contract.js';
4
+ type SaveProgress = {
5
+ receivedBytes: number;
6
+ totalBytes: number | null;
7
+ };
8
+ export declare function saveHttpError(error: unknown, bind?: boolean): {
9
+ status: number;
10
+ body: {
11
+ error: {
12
+ retryable: boolean;
13
+ mayAlreadyBeSaved: boolean | null;
14
+ resumeDeadline?: string | undefined;
15
+ recovery?: import("../core/locale.js").UnknownRecovery | undefined;
16
+ submission?: import("../index.js").Submission | undefined;
17
+ httpStatus?: number | undefined;
18
+ code: string;
19
+ message: string;
20
+ type: string;
21
+ };
22
+ root?: null | undefined;
23
+ scope: {
24
+ rootId: string | null;
25
+ connectionId: string;
26
+ } | null;
27
+ save: {
28
+ progress: SaveProgress | null;
29
+ error: {
30
+ code: string;
31
+ message: string;
32
+ type: string;
33
+ retryable: boolean;
34
+ mayAlreadyBeSaved: boolean | null;
35
+ httpStatus?: number | undefined;
36
+ } | null;
37
+ createdAt: string;
38
+ state: "ACCEPTED" | "SAVING" | "SAVED" | "FAILED" | "RECOVERY_REQUIRED" | "BLOCKED";
39
+ jobId: string;
40
+ updatedAt: string;
41
+ revision: number;
42
+ runId: string;
43
+ retryable: boolean;
44
+ mayAlreadyBeSaved: boolean | null;
45
+ stage: "QUEUED" | "DOWNLOADING" | "FILE_READY" | "REGISTERED" | "LINKED" | "RECONCILING";
46
+ attempt: number;
47
+ facts: {
48
+ file: "MISSING" | "NOT_CHECKED" | "ABSENT" | "PARTIAL" | "UNSAFE" | "UNKNOWN" | "VERIFIED" | "MODIFIED";
49
+ registration: "ABSENT" | "UNKNOWN" | "PRESENT";
50
+ relation: "ABSENT" | "UNKNOWN" | "PRESENT";
51
+ };
52
+ asset: {
53
+ sha256: string;
54
+ byteLength: number;
55
+ assetId: string;
56
+ displayName: string;
57
+ mediaType: "image/png" | "image/jpeg" | "image/webp" | "video/mp4" | "video/webm";
58
+ relativePath: string;
59
+ absolutePath: string;
60
+ contentUrl: string;
61
+ } | null;
62
+ safeRetry: boolean;
63
+ checkedAt: string | null;
64
+ integrityCheck: "PENDING" | "IDLE";
65
+ operationId: string;
66
+ localOutputId: string;
67
+ gatewayAssetId: string;
68
+ } | null;
69
+ };
70
+ };
71
+ interface SaveWorker {
72
+ enqueue(operationId: string): void;
73
+ retry?(id: string, connectionId: string, revision: number): Promise<SaveOperation>;
74
+ suspend?(connectionId: string): void;
75
+ activate?(connectionId: string): void;
76
+ recover?(): Promise<void>;
77
+ progress?(id: string, revision: number, attempt: number): SaveProgress | null;
78
+ }
79
+ export declare function createOutputSaveService(options: {
80
+ store?: StudioAssetStore;
81
+ outputDir?: string;
82
+ loadConfig(): LumenKeyConfig | Promise<LumenKeyConfig>;
83
+ worker?: SaveWorker;
84
+ makeWorker?(): SaveWorker;
85
+ }): {
86
+ invalidate: () => void;
87
+ localScope: () => {
88
+ rootId: string | null;
89
+ connectionId: string;
90
+ } | null;
91
+ preflight: (bind?: boolean) => Promise<void>;
92
+ bind: () => Promise<{
93
+ scope: {
94
+ rootId: string | null;
95
+ connectionId: string;
96
+ };
97
+ root: {
98
+ absolutePath: string;
99
+ } | null;
100
+ code: string | null;
101
+ }>;
102
+ outputs: (runId: string) => Promise<{
103
+ scope: {
104
+ rootId: string | null;
105
+ connectionId: string;
106
+ };
107
+ revision: number;
108
+ items: {
109
+ save: {
110
+ progress: SaveProgress | null;
111
+ error: {
112
+ code: string;
113
+ message: string;
114
+ type: string;
115
+ retryable: boolean;
116
+ mayAlreadyBeSaved: boolean | null;
117
+ httpStatus?: number | undefined;
118
+ } | null;
119
+ createdAt: string;
120
+ state: "ACCEPTED" | "SAVING" | "SAVED" | "FAILED" | "RECOVERY_REQUIRED" | "BLOCKED";
121
+ jobId: string;
122
+ updatedAt: string;
123
+ revision: number;
124
+ runId: string;
125
+ retryable: boolean;
126
+ mayAlreadyBeSaved: boolean | null;
127
+ stage: "QUEUED" | "DOWNLOADING" | "FILE_READY" | "REGISTERED" | "LINKED" | "RECONCILING";
128
+ attempt: number;
129
+ facts: {
130
+ file: "MISSING" | "NOT_CHECKED" | "ABSENT" | "PARTIAL" | "UNSAFE" | "UNKNOWN" | "VERIFIED" | "MODIFIED";
131
+ registration: "ABSENT" | "UNKNOWN" | "PRESENT";
132
+ relation: "ABSENT" | "UNKNOWN" | "PRESENT";
133
+ };
134
+ asset: {
135
+ sha256: string;
136
+ byteLength: number;
137
+ assetId: string;
138
+ displayName: string;
139
+ mediaType: "image/png" | "image/jpeg" | "image/webp" | "video/mp4" | "video/webm";
140
+ relativePath: string;
141
+ absolutePath: string;
142
+ contentUrl: string;
143
+ } | null;
144
+ safeRetry: boolean;
145
+ checkedAt: string | null;
146
+ integrityCheck: "PENDING" | "IDLE";
147
+ operationId: string;
148
+ localOutputId: string;
149
+ gatewayAssetId: string;
150
+ } | null;
151
+ acceptance: "ACCEPTED" | "UNKNOWN" | "NEVER_ACCEPTED";
152
+ saveAllowed: boolean;
153
+ saveBlockedCode: string | null;
154
+ saveOperationId: string | null;
155
+ localOutputId: string | null;
156
+ runId: string;
157
+ jobId: string;
158
+ gatewayAssetId: string | null;
159
+ outputIndex: number | null;
160
+ displayOrdinal: number;
161
+ outputType: string | null;
162
+ mediaType: null;
163
+ identity: {
164
+ status: "UNVERIFIED";
165
+ reason: "LEGACY_AWAITING_FRESH_GET" | "JOB_UNAVAILABLE" | "NO_VERIFIABLE_OUTPUTS" | "IDENTITY_CONFLICT";
166
+ } | {
167
+ status: "VERIFIED";
168
+ reason: null;
169
+ };
170
+ }[];
171
+ }>;
172
+ output: (runId: string, localOutputId: string) => Promise<{
173
+ scope: {
174
+ rootId: string | null;
175
+ connectionId: string;
176
+ };
177
+ revision: number;
178
+ output: {
179
+ save: {
180
+ progress: SaveProgress | null;
181
+ error: {
182
+ code: string;
183
+ message: string;
184
+ type: string;
185
+ retryable: boolean;
186
+ mayAlreadyBeSaved: boolean | null;
187
+ httpStatus?: number | undefined;
188
+ } | null;
189
+ createdAt: string;
190
+ state: "ACCEPTED" | "SAVING" | "SAVED" | "FAILED" | "RECOVERY_REQUIRED" | "BLOCKED";
191
+ jobId: string;
192
+ updatedAt: string;
193
+ revision: number;
194
+ runId: string;
195
+ retryable: boolean;
196
+ mayAlreadyBeSaved: boolean | null;
197
+ stage: "QUEUED" | "DOWNLOADING" | "FILE_READY" | "REGISTERED" | "LINKED" | "RECONCILING";
198
+ attempt: number;
199
+ facts: {
200
+ file: "MISSING" | "NOT_CHECKED" | "ABSENT" | "PARTIAL" | "UNSAFE" | "UNKNOWN" | "VERIFIED" | "MODIFIED";
201
+ registration: "ABSENT" | "UNKNOWN" | "PRESENT";
202
+ relation: "ABSENT" | "UNKNOWN" | "PRESENT";
203
+ };
204
+ asset: {
205
+ sha256: string;
206
+ byteLength: number;
207
+ assetId: string;
208
+ displayName: string;
209
+ mediaType: "image/png" | "image/jpeg" | "image/webp" | "video/mp4" | "video/webm";
210
+ relativePath: string;
211
+ absolutePath: string;
212
+ contentUrl: string;
213
+ } | null;
214
+ safeRetry: boolean;
215
+ checkedAt: string | null;
216
+ integrityCheck: "PENDING" | "IDLE";
217
+ operationId: string;
218
+ localOutputId: string;
219
+ gatewayAssetId: string;
220
+ } | null;
221
+ acceptance: "ACCEPTED" | "UNKNOWN" | "NEVER_ACCEPTED";
222
+ saveAllowed: boolean;
223
+ saveBlockedCode: string | null;
224
+ saveOperationId: string | null;
225
+ localOutputId: string | null;
226
+ runId: string;
227
+ jobId: string;
228
+ gatewayAssetId: string | null;
229
+ outputIndex: number | null;
230
+ displayOrdinal: number;
231
+ outputType: string | null;
232
+ mediaType: null;
233
+ identity: {
234
+ status: "UNVERIFIED";
235
+ reason: "LEGACY_AWAITING_FRESH_GET" | "JOB_UNAVAILABLE" | "NO_VERIFIABLE_OUTPUTS" | "IDENTITY_CONFLICT";
236
+ } | {
237
+ status: "VERIFIED";
238
+ reason: null;
239
+ };
240
+ };
241
+ acceptance: "ACCEPTED" | "UNKNOWN" | "NEVER_ACCEPTED";
242
+ save: {
243
+ progress: SaveProgress | null;
244
+ error: {
245
+ code: string;
246
+ message: string;
247
+ type: string;
248
+ retryable: boolean;
249
+ mayAlreadyBeSaved: boolean | null;
250
+ httpStatus?: number | undefined;
251
+ } | null;
252
+ createdAt: string;
253
+ state: "ACCEPTED" | "SAVING" | "SAVED" | "FAILED" | "RECOVERY_REQUIRED" | "BLOCKED";
254
+ jobId: string;
255
+ updatedAt: string;
256
+ revision: number;
257
+ runId: string;
258
+ retryable: boolean;
259
+ mayAlreadyBeSaved: boolean | null;
260
+ stage: "QUEUED" | "DOWNLOADING" | "FILE_READY" | "REGISTERED" | "LINKED" | "RECONCILING";
261
+ attempt: number;
262
+ facts: {
263
+ file: "MISSING" | "NOT_CHECKED" | "ABSENT" | "PARTIAL" | "UNSAFE" | "UNKNOWN" | "VERIFIED" | "MODIFIED";
264
+ registration: "ABSENT" | "UNKNOWN" | "PRESENT";
265
+ relation: "ABSENT" | "UNKNOWN" | "PRESENT";
266
+ };
267
+ asset: {
268
+ sha256: string;
269
+ byteLength: number;
270
+ assetId: string;
271
+ displayName: string;
272
+ mediaType: "image/png" | "image/jpeg" | "image/webp" | "video/mp4" | "video/webm";
273
+ relativePath: string;
274
+ absolutePath: string;
275
+ contentUrl: string;
276
+ } | null;
277
+ safeRetry: boolean;
278
+ checkedAt: string | null;
279
+ integrityCheck: "PENDING" | "IDLE";
280
+ operationId: string;
281
+ localOutputId: string;
282
+ gatewayAssetId: string;
283
+ } | null;
284
+ }>;
285
+ accept: (runId: string, localOutputId: string, body: unknown) => Promise<{
286
+ status: number;
287
+ scope: {
288
+ rootId: string | null;
289
+ connectionId: string;
290
+ };
291
+ revision: number;
292
+ save: {
293
+ progress: SaveProgress | null;
294
+ error: {
295
+ code: string;
296
+ message: string;
297
+ type: string;
298
+ retryable: boolean;
299
+ mayAlreadyBeSaved: boolean | null;
300
+ httpStatus?: number | undefined;
301
+ } | null;
302
+ createdAt: string;
303
+ state: "ACCEPTED" | "SAVING" | "SAVED" | "FAILED" | "RECOVERY_REQUIRED" | "BLOCKED";
304
+ jobId: string;
305
+ updatedAt: string;
306
+ revision: number;
307
+ runId: string;
308
+ retryable: boolean;
309
+ mayAlreadyBeSaved: boolean | null;
310
+ stage: "QUEUED" | "DOWNLOADING" | "FILE_READY" | "REGISTERED" | "LINKED" | "RECONCILING";
311
+ attempt: number;
312
+ facts: {
313
+ file: "MISSING" | "NOT_CHECKED" | "ABSENT" | "PARTIAL" | "UNSAFE" | "UNKNOWN" | "VERIFIED" | "MODIFIED";
314
+ registration: "ABSENT" | "UNKNOWN" | "PRESENT";
315
+ relation: "ABSENT" | "UNKNOWN" | "PRESENT";
316
+ };
317
+ asset: {
318
+ sha256: string;
319
+ byteLength: number;
320
+ assetId: string;
321
+ displayName: string;
322
+ mediaType: "image/png" | "image/jpeg" | "image/webp" | "video/mp4" | "video/webm";
323
+ relativePath: string;
324
+ absolutePath: string;
325
+ contentUrl: string;
326
+ } | null;
327
+ safeRetry: boolean;
328
+ checkedAt: string | null;
329
+ integrityCheck: "PENDING" | "IDLE";
330
+ operationId: string;
331
+ localOutputId: string;
332
+ gatewayAssetId: string;
333
+ };
334
+ }>;
335
+ read: (id: string) => Promise<{
336
+ scope: {
337
+ rootId: string | null;
338
+ connectionId: string;
339
+ };
340
+ revision: number;
341
+ save: {
342
+ progress: SaveProgress | null;
343
+ error: {
344
+ code: string;
345
+ message: string;
346
+ type: string;
347
+ retryable: boolean;
348
+ mayAlreadyBeSaved: boolean | null;
349
+ httpStatus?: number | undefined;
350
+ } | null;
351
+ createdAt: string;
352
+ state: "ACCEPTED" | "SAVING" | "SAVED" | "FAILED" | "RECOVERY_REQUIRED" | "BLOCKED";
353
+ jobId: string;
354
+ updatedAt: string;
355
+ revision: number;
356
+ runId: string;
357
+ retryable: boolean;
358
+ mayAlreadyBeSaved: boolean | null;
359
+ stage: "QUEUED" | "DOWNLOADING" | "FILE_READY" | "REGISTERED" | "LINKED" | "RECONCILING";
360
+ attempt: number;
361
+ facts: {
362
+ file: "MISSING" | "NOT_CHECKED" | "ABSENT" | "PARTIAL" | "UNSAFE" | "UNKNOWN" | "VERIFIED" | "MODIFIED";
363
+ registration: "ABSENT" | "UNKNOWN" | "PRESENT";
364
+ relation: "ABSENT" | "UNKNOWN" | "PRESENT";
365
+ };
366
+ asset: {
367
+ sha256: string;
368
+ byteLength: number;
369
+ assetId: string;
370
+ displayName: string;
371
+ mediaType: "image/png" | "image/jpeg" | "image/webp" | "video/mp4" | "video/webm";
372
+ relativePath: string;
373
+ absolutePath: string;
374
+ contentUrl: string;
375
+ } | null;
376
+ safeRetry: boolean;
377
+ checkedAt: string | null;
378
+ integrityCheck: "PENDING" | "IDLE";
379
+ operationId: string;
380
+ localOutputId: string;
381
+ gatewayAssetId: string;
382
+ };
383
+ }>;
384
+ retry: (id: string, body: unknown) => Promise<{
385
+ status: number;
386
+ scope: {
387
+ rootId: string | null;
388
+ connectionId: string;
389
+ };
390
+ revision: number;
391
+ save: {
392
+ progress: SaveProgress | null;
393
+ error: {
394
+ code: string;
395
+ message: string;
396
+ type: string;
397
+ retryable: boolean;
398
+ mayAlreadyBeSaved: boolean | null;
399
+ httpStatus?: number | undefined;
400
+ } | null;
401
+ createdAt: string;
402
+ state: "ACCEPTED" | "SAVING" | "SAVED" | "FAILED" | "RECOVERY_REQUIRED" | "BLOCKED";
403
+ jobId: string;
404
+ updatedAt: string;
405
+ revision: number;
406
+ runId: string;
407
+ retryable: boolean;
408
+ mayAlreadyBeSaved: boolean | null;
409
+ stage: "QUEUED" | "DOWNLOADING" | "FILE_READY" | "REGISTERED" | "LINKED" | "RECONCILING";
410
+ attempt: number;
411
+ facts: {
412
+ file: "MISSING" | "NOT_CHECKED" | "ABSENT" | "PARTIAL" | "UNSAFE" | "UNKNOWN" | "VERIFIED" | "MODIFIED";
413
+ registration: "ABSENT" | "UNKNOWN" | "PRESENT";
414
+ relation: "ABSENT" | "UNKNOWN" | "PRESENT";
415
+ };
416
+ asset: {
417
+ sha256: string;
418
+ byteLength: number;
419
+ assetId: string;
420
+ displayName: string;
421
+ mediaType: "image/png" | "image/jpeg" | "image/webp" | "video/mp4" | "video/webm";
422
+ relativePath: string;
423
+ absolutePath: string;
424
+ contentUrl: string;
425
+ } | null;
426
+ safeRetry: boolean;
427
+ checkedAt: string | null;
428
+ integrityCheck: "PENDING" | "IDLE";
429
+ operationId: string;
430
+ localOutputId: string;
431
+ gatewayAssetId: string;
432
+ };
433
+ }>;
434
+ };
435
+ export {};
436
+ //# sourceMappingURL=output-save-service.d.ts.map
@@ -0,0 +1,251 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import path from 'node:path';
3
+ import { LumenKeyClient, LumenKeyError } from '../core/client.js';
4
+ import { ConfigError } from '../core/config-error.js';
5
+ import { normalizeGatewayOrigin } from '../core/origin.js';
6
+ import { resolveOutputPath } from '../core/output-path.js';
7
+ import { studioBindingDigest } from './cloud-service.js';
8
+ import { projectOutputViews, cloudHttpError } from './cloud-contract.js';
9
+ import { uuid } from './run-journal.js';
10
+ import { StudioError } from './errors.js';
11
+ import { saveOperation } from './output-save-contract.js';
12
+ import { z } from 'zod';
13
+ const receiptErrors = new WeakMap();
14
+ function projectSave(operation, progress = null) { return { ...structuredClone(operation.receipt), progress }; }
15
+ export function saveHttpError(error, bind = false) {
16
+ const projected = cloudHttpError(error);
17
+ const context = error instanceof Error ? receiptErrors.get(error) : undefined;
18
+ return { status: projected.status, body: { scope: context?.scope ?? null, save: context?.save ?? null, ...(bind ? { root: null } : {}), error: { ...projected.body.error, retryable: false, mayAlreadyBeSaved: context?.save.mayAlreadyBeSaved ?? null } } };
19
+ }
20
+ export function createOutputSaveService(options) {
21
+ let worker = options.worker;
22
+ const project = (op) => projectSave(op, worker?.progress?.(op.receipt.operationId, op.receipt.revision, op.receipt.attempt) ?? null);
23
+ const policy = Object.freeze({ configuredRoot: process.env.LUMENKEY_DOWNLOAD_ROOT });
24
+ const memory = new Map();
25
+ let epoch = 0;
26
+ let queue = Promise.resolve();
27
+ let active;
28
+ const invalidate = () => { epoch++; if (active)
29
+ worker?.suspend?.(active.scope.connectionId); active = undefined; };
30
+ function serial(fn) { const next = queue.then(fn); queue = next.catch(() => { }); return next; }
31
+ async function configuration() {
32
+ try {
33
+ const config = { ...await options.loadConfig() };
34
+ config.apiKey = config.apiKey?.trim();
35
+ if (!config.apiKey)
36
+ throw new StudioError('SAVE_CLOUD_CONFIG_REQUIRED', 409);
37
+ config.baseUrl = normalizeGatewayOrigin(config.baseUrl);
38
+ const client = new LumenKeyClient(config);
39
+ client.requireKey();
40
+ return studioBindingDigest(new URL(config.baseUrl).origin, config.apiKey);
41
+ }
42
+ catch (error) {
43
+ if (error instanceof ConfigError)
44
+ throw new LumenKeyError(error.code, 409);
45
+ throw new StudioError('SAVE_CLOUD_CONFIG_REQUIRED', 409);
46
+ }
47
+ }
48
+ function checkRoot() {
49
+ if (!options.outputDir)
50
+ return;
51
+ const raw = options.outputDir;
52
+ const absolute = path.resolve(raw);
53
+ if (!path.isAbsolute(raw) || /^[\\/]{2}/.test(raw) || absolute === path.parse(absolute).root || /^(?:[A-Za-z]:[\\/](?:Windows|Program Files(?: \(x86\))?|ProgramData)(?:[\\/]|$)|\/(?:etc|usr|sys|proc|dev|bin|sbin)(?:\/|$))/i.test(absolute))
54
+ throw new StudioError('SAVE_ROOT_NOT_ALLOWED', 403);
55
+ try {
56
+ resolveOutputPath(path.join(raw, 'LumenKey', 'assets'), 'absolute-only', undefined, policy);
57
+ }
58
+ catch {
59
+ throw new StudioError('SAVE_ROOT_NOT_ALLOWED', 403);
60
+ }
61
+ }
62
+ async function healthyAuthority() { await options.store?.saveAuthority.readIntegrity(); }
63
+ async function authorized() {
64
+ await healthyAuthority();
65
+ const bound = active;
66
+ const started = epoch;
67
+ let digest;
68
+ try {
69
+ digest = await configuration();
70
+ }
71
+ catch (error) {
72
+ invalidate();
73
+ throw error;
74
+ }
75
+ if (!bound)
76
+ throw new StudioError('SAVE_BINDING_REQUIRED', 403);
77
+ if (digest !== bound.digest || started !== epoch) {
78
+ invalidate();
79
+ throw new StudioError('SAVE_BINDING_REQUIRED', 403);
80
+ }
81
+ checkRoot();
82
+ const store = options.store;
83
+ if (!store)
84
+ throw new StudioError('SAVE_OUTPUT_ROOT_REQUIRED', 409);
85
+ const authority = await store.saveAuthority.read();
86
+ if (authority.rootId !== bound.scope.rootId || !authority.connections.some(c => c.connectionId === bound.scope.connectionId && c.bindingDigestRef === digest))
87
+ throw new StudioError('SAVE_BINDING_MISMATCH', 403);
88
+ if (started !== epoch)
89
+ throw new StudioError('SAVE_BINDING_REQUIRED', 403);
90
+ return { bound, authority, store, started };
91
+ }
92
+ function checkEpoch(access) { if (access.started !== epoch)
93
+ throw new StudioError('SAVE_BINDING_REQUIRED', 403); }
94
+ async function outputViews(access, runId) {
95
+ if (!uuid.safeParse(runId).success)
96
+ throw new StudioError('STUDIO_INPUT_INVALID');
97
+ const data = await access.store.runs.read();
98
+ const run = data.runs.find(r => r.runId === runId);
99
+ if (!run)
100
+ throw new StudioError('STUDIO_RUN_NOT_FOUND', 404);
101
+ if (run.binding !== access.bound.digest)
102
+ throw new StudioError('SAVE_BINDING_MISMATCH', 403);
103
+ checkEpoch(access);
104
+ const operations = access.authority.operations.filter(o => o.connectionId === access.bound.scope.connectionId && o.receipt.runId === runId);
105
+ const seen = new Set();
106
+ const views = projectOutputViews(run, { rootReady: true, authorityHealthy: true }).map((view, index) => {
107
+ // This index only pairs two projections of the SAME snapshot. Identity is
108
+ // matched exclusively by its gateway UUID, never by a historical ordinal.
109
+ const sourceId = run.job?.outputs[index]?.assetId;
110
+ const found = uuid.safeParse(sourceId).success ? operations.find(o => o.receipt.gatewayAssetId.toLowerCase() === sourceId.toLowerCase() && o.receipt.jobId === view.jobId) : undefined;
111
+ if (found)
112
+ seen.add(found.receipt.operationId);
113
+ return { ...view, ...(found ? { acceptance: 'ACCEPTED', saveAllowed: false, saveBlockedCode: view.saveBlockedCode ?? 'SAVE_OPERATION_EXISTS', saveOperationId: found.receipt.operationId } : {}), save: found ? project(found) : null };
114
+ });
115
+ for (const found of operations)
116
+ if (!seen.has(found.receipt.operationId)) {
117
+ const reason = run.job?.identityReason ?? 'NO_VERIFIABLE_OUTPUTS';
118
+ const type = run.job?.identityEvidence?.find(o => o.assetId.toLowerCase() === found.receipt.gatewayAssetId.toLowerCase())?.type ?? null;
119
+ views.push({ localOutputId: null, runId, jobId: found.receipt.jobId, gatewayAssetId: null, outputIndex: null, displayOrdinal: views.length + 1,
120
+ outputType: type, mediaType: null, identity: { status: 'UNVERIFIED', reason }, acceptance: 'ACCEPTED', saveAllowed: false,
121
+ saveBlockedCode: reason === 'IDENTITY_CONFLICT' ? 'SAVE_OUTPUT_IDENTITY_CONFLICT' : 'SAVE_OUTPUT_IDENTITY_UNVERIFIED', saveOperationId: found.receipt.operationId, save: project(found) });
122
+ }
123
+ return views;
124
+ }
125
+ async function selected(access, runId, localOutputId) {
126
+ if (!uuid.safeParse(localOutputId).success)
127
+ throw new StudioError('STUDIO_INPUT_INVALID');
128
+ const views = await outputViews(access, runId);
129
+ const found = views.find(v => v.localOutputId === localOutputId || v.save?.localOutputId === localOutputId);
130
+ if (!found)
131
+ throw new StudioError('SAVE_OUTPUT_IDENTITY_UNVERIFIED', 409);
132
+ return found;
133
+ }
134
+ function operation(access, id) {
135
+ if (!uuid.safeParse(id).success)
136
+ throw new StudioError('STUDIO_INPUT_INVALID');
137
+ const found = access.authority.operations.find(o => o.receipt.operationId === id);
138
+ if (!found)
139
+ throw new StudioError('SAVE_OPERATION_NOT_FOUND', 404);
140
+ if (found.connectionId !== access.bound.scope.connectionId)
141
+ throw new StudioError('SAVE_BINDING_MISMATCH', 403);
142
+ return found;
143
+ }
144
+ return {
145
+ invalidate,
146
+ localScope: () => active ? structuredClone(active.scope) : null,
147
+ preflight: (bind = false) => serial(async () => { if (bind) {
148
+ await healthyAuthority();
149
+ await configuration();
150
+ checkRoot();
151
+ }
152
+ else {
153
+ await authorized();
154
+ } }),
155
+ bind: () => serial(async () => {
156
+ invalidate();
157
+ await healthyAuthority();
158
+ const started = epoch;
159
+ const digest = await configuration();
160
+ checkRoot();
161
+ let scope;
162
+ let root = null;
163
+ if (options.store) {
164
+ const snapshot = await options.store.saveAuthority.bind(digest);
165
+ scope = { rootId: snapshot.rootId, connectionId: snapshot.connections.find(c => c.bindingDigestRef === digest).connectionId };
166
+ root = { absolutePath: snapshot.canonicalAssetsPath };
167
+ }
168
+ else {
169
+ let id = memory.get(digest);
170
+ if (!id) {
171
+ id = randomUUID();
172
+ memory.set(digest, id);
173
+ }
174
+ scope = { rootId: null, connectionId: id };
175
+ }
176
+ if (root && !worker)
177
+ worker = options.makeWorker?.();
178
+ await worker?.recover?.();
179
+ if (started !== epoch)
180
+ throw new StudioError('SAVE_BINDING_REQUIRED', 403);
181
+ worker?.activate?.(scope.connectionId);
182
+ active = { digest, scope };
183
+ return { scope, root, code: root ? null : 'SAVE_OUTPUT_ROOT_REQUIRED' };
184
+ }),
185
+ outputs: (runId) => serial(async () => {
186
+ const a = await authorized();
187
+ return { scope: a.bound.scope, revision: a.authority.revision, items: await outputViews(a, runId) };
188
+ }),
189
+ output: (runId, localOutputId) => serial(async () => {
190
+ let a = await authorized();
191
+ let output = await selected(a, runId, localOutputId);
192
+ if (output.save?.asset) {
193
+ await a.store.metadata(output.save.asset.assetId);
194
+ a = await authorized();
195
+ output = await selected(a, runId, localOutputId);
196
+ }
197
+ return { scope: a.bound.scope, revision: a.authority.revision, output, acceptance: output.acceptance, save: output.save };
198
+ }),
199
+ accept: (runId, localOutputId, body) => serial(async () => {
200
+ const a = await authorized();
201
+ const view = await selected(a, runId, localOutputId);
202
+ const request = z.object({ requestId: uuid }).strict().safeParse(body);
203
+ if (!request.success)
204
+ throw new StudioError('STUDIO_INPUT_INVALID');
205
+ if (!view.save && !view.saveAllowed)
206
+ throw new StudioError(view.saveBlockedCode, view.saveBlockedCode === 'SAVE_MEDIA_UNSUPPORTED' ? 415 : 409);
207
+ checkEpoch(a);
208
+ const accepted = await a.store.saveAuthority.accept({ connectionId: a.bound.scope.connectionId, requestId: request.data.requestId, localOutputId, runId, jobId: view.jobId, gatewayAssetId: view.gatewayAssetId ?? view.save.gatewayAssetId });
209
+ const latest = await a.store.saveAuthority.read();
210
+ worker?.enqueue(accepted.receipt.operationId);
211
+ return { status: ['ACCEPTED', 'SAVING'].includes(accepted.receipt.state) ? 202 : 200, scope: a.bound.scope, revision: latest.revision, save: project(accepted) };
212
+ }),
213
+ read: (id) => serial(async () => {
214
+ let a = await authorized();
215
+ let found = operation(a, id);
216
+ if (found.receipt.asset) {
217
+ await a.store.metadata(found.assetId);
218
+ a = await authorized();
219
+ found = operation(a, id);
220
+ }
221
+ return { scope: a.bound.scope, revision: a.authority.revision, save: project(found) };
222
+ }),
223
+ retry: (id, body) => serial(async () => {
224
+ const a = await authorized();
225
+ const found = operation(a, id);
226
+ const parsed = z.object({ expectedRevision: z.number().int().positive().safe() }).strict().safeParse(body);
227
+ if (!parsed.success)
228
+ throw new StudioError('STUDIO_INPUT_INVALID');
229
+ const reject = (code) => { const error = new StudioError(code, 409); receiptErrors.set(error, { scope: a.bound.scope, save: project(found) }); throw error; };
230
+ if (parsed.data.expectedRevision !== found.receipt.revision)
231
+ reject('SAVE_REVISION_CONFLICT');
232
+ if (!found.receipt.safeRetry || !['FAILED', 'RECOVERY_REQUIRED'].includes(found.receipt.state))
233
+ reject('SAVE_RETRY_NOT_ALLOWED');
234
+ checkEpoch(a);
235
+ let updated;
236
+ try {
237
+ updated = worker?.retry ? await worker.retry(id, a.bound.scope.connectionId, parsed.data.expectedRevision) : await a.store.saveAuthority.update(id, a.bound.scope.connectionId, parsed.data.expectedRevision, { state: 'SAVING', stage: 'RECONCILING', attempt: found.receipt.attempt + 1, safeRetry: false, error: null });
238
+ }
239
+ catch (error) {
240
+ if (error instanceof StudioError && error.code === 'SAVE_REVISION_CONFLICT' && 'operation' in error) {
241
+ const latest = saveOperation.safeParse(error.operation);
242
+ if (latest.success && latest.data.connectionId === a.bound.scope.connectionId && latest.data.receipt.operationId === id)
243
+ receiptErrors.set(error, { scope: a.bound.scope, save: project(latest.data) });
244
+ }
245
+ throw error;
246
+ }
247
+ return { status: updated.receipt.state === 'SAVED' ? 200 : 202, scope: a.bound.scope, revision: (await a.store.saveAuthority.read()).revision, save: project(updated) };
248
+ }),
249
+ };
250
+ }
251
+ //# sourceMappingURL=output-save-service.js.map