@nylorun/runtime 0.15.2-beta → 0.17.0-beta

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 (291) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +68 -29
  3. package/dist/adapters/execution/restate.d.ts +9 -1
  4. package/dist/adapters/execution/restate.js +64 -5
  5. package/dist/adapters/sandbox/local.d.ts +10 -0
  6. package/dist/adapters/sandbox/local.js +195 -0
  7. package/dist/adapters/sandbox/virtual.js +36 -1
  8. package/dist/admin-openapi.json +341 -7
  9. package/dist/api/a2a/tasks.js +6 -1
  10. package/dist/api/ag-ui/endpoint.js +1 -2
  11. package/dist/api/ag-ui/history.js +6 -1
  12. package/dist/api/ag-ui/routes.d.ts +1 -1
  13. package/dist/api/ag-ui/routes.js +15 -13
  14. package/dist/api/components.d.ts +2667 -711
  15. package/dist/api/components.js +24 -11
  16. package/dist/api/gate/keys.d.ts +4 -3
  17. package/dist/api/gate/routes.d.ts +19 -3
  18. package/dist/api/gate/routes.js +162 -53
  19. package/dist/api/http/app.js +6 -0
  20. package/dist/api/http/define.d.ts +19 -12
  21. package/dist/api/http/define.js +13 -27
  22. package/dist/api/http/routes/access.d.ts +3 -19
  23. package/dist/api/http/routes/access.js +12 -172
  24. package/dist/api/http/routes/actions.js +0 -1
  25. package/dist/api/http/routes/artifacts.d.ts +11 -0
  26. package/dist/api/http/routes/artifacts.js +516 -0
  27. package/dist/api/http/routes/me.d.ts +13 -0
  28. package/dist/api/http/routes/me.js +45 -0
  29. package/dist/api/http/routes/sandboxes.d.ts +9 -0
  30. package/dist/api/http/routes/sandboxes.js +150 -0
  31. package/dist/api/http/routes/sessions.js +9 -13
  32. package/dist/api/http/routes/tenant.js +36 -2
  33. package/dist/api/http/routes/vaults.d.ts +12 -3
  34. package/dist/api/http/routes/vaults.js +122 -54
  35. package/dist/api/openapi.js +4 -10
  36. package/dist/artifacts/config.d.ts +23 -0
  37. package/dist/artifacts/config.js +27 -0
  38. package/dist/artifacts/export.d.ts +8 -0
  39. package/dist/artifacts/export.js +205 -0
  40. package/dist/artifacts/files.d.ts +36 -0
  41. package/dist/artifacts/files.js +45 -0
  42. package/dist/artifacts/folders.d.ts +60 -0
  43. package/dist/artifacts/folders.js +209 -0
  44. package/dist/artifacts/links.d.ts +28 -0
  45. package/dist/artifacts/links.js +92 -0
  46. package/dist/artifacts/media-types.d.ts +13 -0
  47. package/dist/artifacts/media-types.js +80 -0
  48. package/dist/artifacts/parts.d.ts +34 -0
  49. package/dist/artifacts/parts.js +38 -0
  50. package/dist/artifacts/service.d.ts +74 -0
  51. package/dist/artifacts/service.js +301 -0
  52. package/dist/artifacts/workspace.d.ts +60 -0
  53. package/dist/artifacts/workspace.js +57 -0
  54. package/dist/artifacts/zip.d.ts +10 -0
  55. package/dist/artifacts/zip.js +126 -0
  56. package/dist/blob/fs.d.ts +13 -0
  57. package/dist/blob/fs.js +194 -0
  58. package/dist/blob/index.d.ts +9 -0
  59. package/dist/blob/index.js +9 -0
  60. package/dist/blob/s3.d.ts +42 -0
  61. package/dist/blob/s3.js +366 -0
  62. package/dist/blob/types.d.ts +108 -0
  63. package/dist/blob/types.js +85 -0
  64. package/dist/core/sandbox-routes.d.ts +2 -2
  65. package/dist/core/sandbox-routes.js +4 -3
  66. package/dist/execution/memory.d.ts +11 -1
  67. package/dist/execution/memory.js +77 -3
  68. package/dist/execution/types.d.ts +54 -1
  69. package/dist/execution/types.js +31 -0
  70. package/dist/gates/contract.d.ts +14 -13
  71. package/dist/gates/contract.js +13 -12
  72. package/dist/gates/egress.d.ts +72 -0
  73. package/dist/gates/egress.js +319 -0
  74. package/dist/gates/handler.d.ts +3 -0
  75. package/dist/gates/handler.js +3 -2
  76. package/dist/gates/http-client.d.ts +3 -2
  77. package/dist/gates/http-client.js +17 -6
  78. package/dist/gates/in-process.d.ts +13 -1
  79. package/dist/gates/in-process.js +11 -0
  80. package/dist/gates/inflight.d.ts +23 -4
  81. package/dist/gates/inflight.js +18 -4
  82. package/dist/gates/mcp-handler.js +5 -1
  83. package/dist/gates/model-gate.d.ts +6 -2
  84. package/dist/gates/tenant-vaults.d.ts +18 -9
  85. package/dist/gates/tenant-vaults.js +17 -12
  86. package/dist/gates/tool-calls.d.ts +9 -5
  87. package/dist/gates/tool-calls.js +6 -6
  88. package/dist/gates/tool-client.d.ts +9 -2
  89. package/dist/gates/tool-client.js +56 -22
  90. package/dist/gates/tool-contract.d.ts +12 -7
  91. package/dist/gates/tool-contract.js +10 -4
  92. package/dist/gates/tool-gate.d.ts +1 -0
  93. package/dist/harness/calls.d.ts +52 -0
  94. package/dist/harness/calls.js +113 -0
  95. package/dist/harness/executors.d.ts +22 -0
  96. package/dist/harness/executors.js +96 -0
  97. package/dist/harness/main.d.ts +6 -0
  98. package/dist/harness/main.js +103 -0
  99. package/dist/harness/pod.d.ts +21 -0
  100. package/dist/harness/pod.js +119 -0
  101. package/dist/harness/run-tokens.d.ts +18 -0
  102. package/dist/harness/run-tokens.js +15 -0
  103. package/dist/harness/service.d.ts +46 -0
  104. package/dist/harness/service.js +179 -0
  105. package/dist/harness/ws-client.d.ts +43 -0
  106. package/dist/harness/ws-client.js +122 -0
  107. package/dist/harness/ws-port.d.ts +22 -0
  108. package/dist/harness/ws-port.js +78 -0
  109. package/dist/harness-api/in-process.d.ts +24 -0
  110. package/dist/harness-api/in-process.js +119 -0
  111. package/dist/harness-api/record.d.ts +44 -0
  112. package/dist/harness-api/record.js +250 -0
  113. package/dist/harness-api/renew.d.ts +37 -0
  114. package/dist/harness-api/renew.js +77 -0
  115. package/dist/harness-api/server.d.ts +104 -0
  116. package/dist/harness-api/server.js +408 -0
  117. package/dist/harness-api/start.d.ts +36 -0
  118. package/dist/harness-api/start.js +118 -0
  119. package/dist/harness-api/workspace.d.ts +53 -0
  120. package/dist/harness-api/workspace.js +201 -0
  121. package/dist/harness-api/ws-server.d.ts +34 -0
  122. package/dist/harness-api/ws-server.js +227 -0
  123. package/dist/host/admin-api.d.ts +12 -5
  124. package/dist/host/admin-api.js +72 -6
  125. package/dist/host/app.d.ts +0 -1
  126. package/dist/host/app.js +35 -24
  127. package/dist/host/config-for.d.ts +4 -0
  128. package/dist/host/config-for.js +2 -0
  129. package/dist/host/config.d.ts +0 -6
  130. package/dist/host/create-host.d.ts +3 -10
  131. package/dist/host/create-host.js +4 -1
  132. package/dist/host/gates.d.ts +27 -2
  133. package/dist/host/gates.js +52 -2
  134. package/dist/host/http.d.ts +4 -1
  135. package/dist/host/http.js +6 -1
  136. package/dist/host/main.js +167 -37
  137. package/dist/host/stack-config.d.ts +112 -9
  138. package/dist/host/stack-config.js +222 -20
  139. package/dist/index.d.ts +3 -0
  140. package/dist/index.js +2 -0
  141. package/dist/keys/client.js +9 -1
  142. package/dist/keys/keys.d.ts +12 -2
  143. package/dist/keys/keys.js +8 -0
  144. package/dist/mcp/connect.js +2 -1
  145. package/dist/mcp/pool.d.ts +1 -0
  146. package/dist/mcp/pool.js +1 -0
  147. package/dist/mcp/snapshot.d.ts +8 -1
  148. package/dist/mcp/snapshot.js +28 -0
  149. package/dist/media.d.ts +5 -24
  150. package/dist/media.js +5 -0
  151. package/dist/model/pi-model.d.ts +6 -2
  152. package/dist/model/pi-model.js +39 -22
  153. package/dist/node/index.d.ts +1 -1
  154. package/dist/node/index.js +1 -1
  155. package/dist/openapi.json +14084 -9426
  156. package/dist/record/index.d.ts +1 -0
  157. package/dist/record/index.js +1 -0
  158. package/dist/record/sandbox.d.ts +25 -0
  159. package/dist/record/sandbox.js +43 -0
  160. package/dist/sandbox/capability.d.ts +3 -0
  161. package/dist/sandbox/capability.js +10 -0
  162. package/dist/sandbox/egress-token.d.ts +26 -0
  163. package/dist/sandbox/egress-token.js +8 -0
  164. package/dist/sandbox/join.d.ts +37 -0
  165. package/dist/sandbox/join.js +165 -0
  166. package/dist/sandbox/manager.d.ts +99 -34
  167. package/dist/sandbox/manager.js +216 -84
  168. package/dist/sandbox/placement.d.ts +27 -0
  169. package/dist/sandbox/placement.js +44 -0
  170. package/dist/sandbox/pods/client.d.ts +90 -0
  171. package/dist/sandbox/pods/client.js +79 -0
  172. package/dist/sandbox/pods/lifecycle.d.ts +91 -0
  173. package/dist/sandbox/pods/lifecycle.js +178 -0
  174. package/dist/sandbox/pods/name.d.ts +1 -0
  175. package/dist/sandbox/pods/name.js +28 -0
  176. package/dist/sandbox/pods/network-gate.d.ts +25 -0
  177. package/dist/sandbox/pods/network-gate.js +71 -0
  178. package/dist/sandbox/pods/reconcile.d.ts +11 -0
  179. package/dist/sandbox/pods/reconcile.js +152 -0
  180. package/dist/sandbox/pods/spec.d.ts +45 -0
  181. package/dist/sandbox/pods/spec.js +62 -0
  182. package/dist/sandbox/records.d.ts +29 -0
  183. package/dist/sandbox/records.js +79 -0
  184. package/dist/sandbox/resolve.d.ts +5 -0
  185. package/dist/sandbox/resolve.js +3 -2
  186. package/dist/sandbox/session-sandbox.js +7 -5
  187. package/dist/sandbox/share.d.ts +11 -0
  188. package/dist/sandbox/share.js +10 -0
  189. package/dist/sandbox/tenant-config.d.ts +16 -1
  190. package/dist/sandbox/tenant-config.js +9 -0
  191. package/dist/sandbox/types.d.ts +29 -1
  192. package/dist/sandbox/types.js +13 -1
  193. package/dist/store/postgres/drizzle/0004_sandbox_resources.sql +20 -0
  194. package/dist/store/postgres/drizzle/0005_artifacts.sql +27 -0
  195. package/dist/store/postgres/drizzle/0006_folder_artifacts.sql +12 -0
  196. package/dist/store/postgres/drizzle/0007_sandbox_pods.sql +14 -0
  197. package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
  198. package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
  199. package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
  200. package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
  201. package/dist/store/postgres/record-writer.d.ts +6 -1
  202. package/dist/store/postgres/record-writer.js +20 -2
  203. package/dist/store/postgres/schema.d.ts +1168 -250
  204. package/dist/store/postgres/schema.js +174 -28
  205. package/dist/store/postgres/store.js +339 -103
  206. package/dist/store/postgres/tenant.d.ts +1 -1
  207. package/dist/store/postgres/tenant.js +2 -2
  208. package/dist/store/types.d.ts +163 -32
  209. package/dist/streams/control.d.ts +2 -5
  210. package/dist/streams/control.js +5 -8
  211. package/dist/streams/types.d.ts +8 -8
  212. package/dist/streams/types.js +1 -1
  213. package/dist/tenant/advance.js +218 -160
  214. package/dist/tenant/artifact-tool.d.ts +5 -0
  215. package/dist/tenant/artifact-tool.js +119 -0
  216. package/dist/tenant/auth.d.ts +13 -9
  217. package/dist/tenant/auth.js +25 -32
  218. package/dist/tenant/commands.js +31 -10
  219. package/dist/tenant/context.d.ts +83 -26
  220. package/dist/tenant/delivery-token.js +4 -4
  221. package/dist/tenant/effects.d.ts +24 -14
  222. package/dist/tenant/effects.js +10 -398
  223. package/dist/tenant/ephemeral.d.ts +14 -2
  224. package/dist/tenant/ephemeral.js +8 -1
  225. package/dist/tenant/history.d.ts +13 -23
  226. package/dist/tenant/history.js +14 -62
  227. package/dist/tenant/host-token.d.ts +92 -0
  228. package/dist/tenant/host-token.js +179 -0
  229. package/dist/tenant/identity-file.d.ts +75 -0
  230. package/dist/tenant/identity-file.js +202 -0
  231. package/dist/tenant/issuers.d.ts +48 -0
  232. package/dist/tenant/issuers.js +311 -0
  233. package/dist/tenant/jwt.js +3 -3
  234. package/dist/tenant/module.js +1 -0
  235. package/dist/tenant/operator-keys.d.ts +18 -0
  236. package/dist/tenant/operator-keys.js +55 -0
  237. package/dist/tenant/outbound.d.ts +30 -0
  238. package/dist/tenant/outbound.js +142 -10
  239. package/dist/tenant/paths.d.ts +2 -1
  240. package/dist/tenant/paths.js +3 -1
  241. package/dist/tenant/principals.d.ts +2 -11
  242. package/dist/tenant/principals.js +10 -30
  243. package/dist/tenant/reset.d.ts +5 -2
  244. package/dist/tenant/reset.js +22 -2
  245. package/dist/tenant/run-grants.d.ts +49 -0
  246. package/dist/tenant/run-grants.js +62 -0
  247. package/dist/tenant/run-token.d.ts +70 -0
  248. package/dist/tenant/run-token.js +138 -0
  249. package/dist/tenant/runtime.d.ts +42 -0
  250. package/dist/tenant/runtime.js +134 -33
  251. package/dist/tenant/sandboxes.d.ts +119 -0
  252. package/dist/tenant/sandboxes.js +551 -0
  253. package/dist/tenant/session-streams.d.ts +6 -10
  254. package/dist/tenant/session-streams.js +9 -27
  255. package/dist/tenant/session.d.ts +2 -13
  256. package/dist/tenant/session.js +4 -28
  257. package/dist/tenant/sessions.d.ts +6 -1
  258. package/dist/tenant/sessions.js +29 -2
  259. package/dist/tenant/signing-keys.d.ts +2 -1
  260. package/dist/tenant/signing-keys.js +2 -2
  261. package/dist/tenant/status.d.ts +8 -3
  262. package/dist/tenant/status.js +5 -0
  263. package/dist/tenant/streams.d.ts +8 -2
  264. package/dist/tenant/streams.js +25 -6
  265. package/dist/tenant/sweep.js +37 -4
  266. package/dist/tenant/types.d.ts +39 -1
  267. package/dist/tenant/worker.d.ts +11 -5
  268. package/dist/tenant/worker.js +12 -6
  269. package/dist/vault/crypto.d.ts +7 -0
  270. package/dist/vault/crypto.js +16 -0
  271. package/dist/vault/oauth.d.ts +55 -0
  272. package/dist/vault/oauth.js +186 -0
  273. package/dist/vault/service.d.ts +54 -7
  274. package/dist/vault/service.js +261 -17
  275. package/dist/vault/sources.d.ts +54 -0
  276. package/dist/vault/sources.js +185 -0
  277. package/dist/version.d.ts +1 -1
  278. package/dist/version.js +1 -1
  279. package/package.json +14 -10
  280. package/dist/adapters/media.d.ts +0 -24
  281. package/dist/adapters/media.js +0 -111
  282. package/dist/host/cors.d.ts +0 -27
  283. package/dist/host/cors.js +0 -64
  284. package/dist/tenant/access-policy.d.ts +0 -30
  285. package/dist/tenant/access-policy.js +0 -52
  286. package/dist/tenant/browser.d.ts +0 -15
  287. package/dist/tenant/browser.js +0 -39
  288. package/dist/tenant/subject-limits.d.ts +0 -16
  289. package/dist/tenant/subject-limits.js +0 -45
  290. package/dist/tenant/tokens.d.ts +0 -14
  291. package/dist/tenant/tokens.js +0 -180
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A Tenant's artifact configuration, stored as Tenant setting `artifacts.config`: the largest
3
+ * file one upload may store and the most bytes every artifact version together may hold.
4
+ * Unset limits take the defaults (100 MiB and 10 GiB).
5
+ */
6
+ import { type TenantArtifactsConfig } from "@nylorun/core/contracts";
7
+ export declare const ARTIFACTS_CONFIG_SETTING = "artifacts.config";
8
+ export interface ArtifactLimits {
9
+ readonly fileBytes: number;
10
+ readonly totalBytes: number;
11
+ }
12
+ interface SettingsReader {
13
+ getSetting(key: string): Promise<string | undefined>;
14
+ }
15
+ interface SettingsWriter extends SettingsReader {
16
+ putSetting(key: string, value: string): Promise<void>;
17
+ }
18
+ /** The limits in force: the Tenant's, with the defaults for what it leaves unset. */
19
+ export declare function effectiveArtifactLimits(config: TenantArtifactsConfig): ArtifactLimits;
20
+ export declare function readArtifactsConfig(t: SettingsReader): Promise<TenantArtifactsConfig>;
21
+ export declare function readArtifactLimits(t: SettingsReader): Promise<ArtifactLimits>;
22
+ export declare function writeArtifactsConfig(t: SettingsWriter, config: TenantArtifactsConfig): Promise<void>;
23
+ export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A Tenant's artifact configuration, stored as Tenant setting `artifacts.config`: the largest
3
+ * file one upload may store and the most bytes every artifact version together may hold.
4
+ * Unset limits take the defaults (100 MiB and 10 GiB).
5
+ */
6
+ import { ARTIFACT_FILE_BYTES_DEFAULT, ARTIFACT_TOTAL_BYTES_DEFAULT, TenantArtifactsConfigSchema, } from "@nylorun/core/contracts";
7
+ export const ARTIFACTS_CONFIG_SETTING = "artifacts.config";
8
+ /** The limits in force: the Tenant's, with the defaults for what it leaves unset. */
9
+ export function effectiveArtifactLimits(config) {
10
+ return {
11
+ fileBytes: config.limits?.fileBytes ?? ARTIFACT_FILE_BYTES_DEFAULT,
12
+ totalBytes: config.limits?.totalBytes ?? ARTIFACT_TOTAL_BYTES_DEFAULT,
13
+ };
14
+ }
15
+ export async function readArtifactsConfig(t) {
16
+ const raw = await t.getSetting(ARTIFACTS_CONFIG_SETTING);
17
+ if (raw === undefined)
18
+ return {};
19
+ const parsed = TenantArtifactsConfigSchema.safeParse(JSON.parse(raw));
20
+ return parsed.success ? parsed.data : {};
21
+ }
22
+ export async function readArtifactLimits(t) {
23
+ return effectiveArtifactLimits(await readArtifactsConfig(t));
24
+ }
25
+ export async function writeArtifactsConfig(t, config) {
26
+ await t.putSetting(ARTIFACTS_CONFIG_SETTING, JSON.stringify(TenantArtifactsConfigSchema.parse(config)));
27
+ }
@@ -0,0 +1,8 @@
1
+ import { type TenantContext } from "../tenant/context.js";
2
+ /** The directory every sandbox's outputs are exported from. */
3
+ export declare const OUTPUTS_DIR = "/workspace/outputs";
4
+ /**
5
+ * Exports the outputs of turn `turnId` of session `sessionId`, if it completed and its sandbox
6
+ * has any. Never throws.
7
+ */
8
+ export declare function exportOutputs(ctx: TenantContext, sessionId: string, turnId: string | null, signal: AbortSignal): Promise<void>;
@@ -0,0 +1,205 @@
1
+ /**
2
+ * The turn-end export (F8.2): when an agent's turn completes, core reads `/workspace/outputs` of
3
+ * the session's sandbox through the `WorkspaceReader` seam and keeps it as a version of the
4
+ * session's folder artifact `outputs` (the first export creates it; later turns add versions,
5
+ * and a turn whose outputs did not change adds none).
6
+ *
7
+ * - **After the turn.** The advance calls `exportOutputs` once `settle` has committed the turn,
8
+ * outside its transaction and while it still holds the session's lease, so the next turn
9
+ * cannot change the workspace under it. Nothing here fails the turn: a sandbox or store
10
+ * failure is logged and recorded as `artifact.export.failed`.
11
+ * - **Claimed.** The listing and the bytes are what the holder of the workspace supplied: the
12
+ * version's source is `export` and its event says `claimed: true`.
13
+ * - **Bounded.** At most `EXPORT_MAX_FILES` files and `EXPORT_MAX_BYTES` bytes per export, each
14
+ * file within the Tenant's per-file limit, and the new bytes within its total; past any of
15
+ * them nothing is stored and `artifact.export.skipped` says why.
16
+ * - **Deduplicated.** Each file is stored content-addressed (`folders.ts`); a file the store
17
+ * already holds is not stored again.
18
+ *
19
+ * No sandbox, a sandbox never created, or an empty or missing `/workspace/outputs` exports
20
+ * nothing and records nothing. The package's declared outputs are left for later: the manifest
21
+ * has no `outputs` field yet.
22
+ */
23
+ import { posix } from "node:path";
24
+ import { EXPORT_MAX_BYTES, EXPORT_MAX_FILES, OUTPUTS_ARTIFACT_NAME, } from "@nylorun/core/contracts";
25
+ import { SANDBOX_WORKSPACE } from "@nylorun/core/define";
26
+ import { isWorkflowManifest } from "../core/flow-host.js";
27
+ import { sandboxWorkspaceOf } from "../sandbox/share.js";
28
+ import { SandboxFileTooLargeError } from "../sandbox/types.js";
29
+ import { sandboxLookup } from "../tenant/context.js";
30
+ import { readArtifactLimits } from "./config.js";
31
+ import { collectContent, commitFolderVersion, contentEpoch, contentKey, encodeManifest, FolderCommitRefused, sha256Hex, } from "./folders.js";
32
+ import { contentTypeFor } from "./media-types.js";
33
+ /** The directory every sandbox's outputs are exported from. */
34
+ export const OUTPUTS_DIR = `${SANDBOX_WORKSPACE}/outputs`;
35
+ /** Stops the export with `artifact.export.skipped`. */
36
+ class Skip extends Error {
37
+ reason;
38
+ limit;
39
+ path;
40
+ constructor(reason, message, limit, path) {
41
+ super(message);
42
+ this.reason = reason;
43
+ this.limit = limit;
44
+ this.path = path;
45
+ }
46
+ }
47
+ /**
48
+ * Exports the outputs of turn `turnId` of session `sessionId`, if it completed and its sandbox
49
+ * has any. Never throws.
50
+ */
51
+ export async function exportOutputs(ctx, sessionId, turnId, signal) {
52
+ try {
53
+ await runExport(ctx, sessionId, turnId, signal);
54
+ }
55
+ catch (error) {
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ if (signal.aborted) {
58
+ ctx.config.logger.info("outputs export stopped", { sessionId, message });
59
+ return;
60
+ }
61
+ const skip = error instanceof Skip
62
+ ? error
63
+ : error instanceof FolderCommitRefused && error.reason === "tenant_total"
64
+ ? new Skip("tenant_total", error.message, error.limit)
65
+ : undefined;
66
+ if (!skip)
67
+ ctx.config.logger.warn("outputs export failed", { sessionId, message });
68
+ try {
69
+ await ctx.store.tx(async (t) => {
70
+ if (skip)
71
+ await t.event(sessionId, turnId, "artifact.export.skipped", {
72
+ name: OUTPUTS_ARTIFACT_NAME,
73
+ reason: skip.reason,
74
+ message: skip.message,
75
+ ...(skip.limit === undefined ? {} : { limit: skip.limit }),
76
+ ...(skip.path === undefined ? {} : { path: skip.path }),
77
+ });
78
+ else
79
+ await t.event(sessionId, turnId, "artifact.export.failed", {
80
+ name: OUTPUTS_ARTIFACT_NAME,
81
+ message,
82
+ });
83
+ });
84
+ }
85
+ catch {
86
+ /* the session is gone (reset) or the store closed */
87
+ }
88
+ }
89
+ }
90
+ async function runExport(ctx, sessionId, turnId, signal) {
91
+ const found = await ctx.store.tx(async (t) => {
92
+ const s = await t.get("sessions", sessionId);
93
+ // Only a turn that completed, and only an agent's (a flow agent's sandbox is its agents').
94
+ if (!s || s.status !== "completed" || s.lastTurnId !== turnId || isWorkflowManifest(s.manifest))
95
+ return undefined;
96
+ const workspace = sandboxWorkspaceOf(s, await sandboxLookup(t, s.id));
97
+ const prior = (await t.listArtifacts({ sessionId })).find((row) => row.kind === "folder" && row.name === OUTPUTS_ARTIFACT_NAME);
98
+ const latest = prior ? await t.artifactVersion(prior.id, prior.latestVersion) : undefined;
99
+ return {
100
+ session: {
101
+ sessionId,
102
+ turnId,
103
+ ownerId: workspace.ownerId,
104
+ ...(workspace.sandboxId === undefined ? {} : { sandboxId: workspace.sandboxId }),
105
+ manifest: s.manifest,
106
+ },
107
+ limits: await readArtifactLimits(t),
108
+ priorId: prior?.id,
109
+ latestSha: latest?.sha256,
110
+ epoch: await contentEpoch(t),
111
+ };
112
+ });
113
+ if (!found)
114
+ return;
115
+ const { session, limits } = found;
116
+ const listing = await ctx.workspaces.list(session, OUTPUTS_DIR, {
117
+ maxEntries: EXPORT_MAX_FILES,
118
+ signal,
119
+ });
120
+ if (!listing || listing.entries.length === 0)
121
+ return;
122
+ if (listing.truncated)
123
+ throw new Skip("too_many_files", `${OUTPUTS_DIR} holds more than ${EXPORT_MAX_FILES} files`, EXPORT_MAX_FILES);
124
+ const large = listing.entries.find((entry) => entry.size > limits.fileBytes);
125
+ if (large)
126
+ throw tooLargeFile(large.path, large.size, limits.fileBytes);
127
+ const maxBytes = Math.min(EXPORT_MAX_BYTES, limits.totalBytes);
128
+ const listed = listing.entries.reduce((sum, entry) => sum + entry.size, 0);
129
+ if (listed > maxBytes)
130
+ throw tooLarge(listed, maxBytes);
131
+ // The files this export stored: removed again if it commits nothing, unless another version
132
+ // names them by then.
133
+ const put = [];
134
+ try {
135
+ await storeAndCommit(ctx, found, listing.entries, maxBytes, put, signal);
136
+ }
137
+ catch (error) {
138
+ if (put.length > 0)
139
+ await ctx.store
140
+ .tx(async (t) => {
141
+ await t.lockArtifactQuota();
142
+ await collectContent(ctx.blobs, t, put);
143
+ })
144
+ .catch(() => undefined);
145
+ throw error;
146
+ }
147
+ }
148
+ async function storeAndCommit(ctx, found, listed, maxBytes, put, signal) {
149
+ const { session, limits } = found;
150
+ const { sessionId, turnId } = session;
151
+ const entries = [];
152
+ const stored = new Set();
153
+ let read = 0;
154
+ for (const entry of listed) {
155
+ let bytes;
156
+ try {
157
+ bytes = await ctx.workspaces.readBytes(session, posix.join(OUTPUTS_DIR, entry.path), {
158
+ maxBytes: limits.fileBytes,
159
+ signal,
160
+ });
161
+ }
162
+ catch (error) {
163
+ if (error instanceof SandboxFileTooLargeError)
164
+ throw tooLargeFile(entry.path, error.size, limits.fileBytes);
165
+ throw error;
166
+ }
167
+ // Gone since the listing: the folder is what is there now.
168
+ if (bytes === undefined)
169
+ continue;
170
+ read += bytes.length;
171
+ if (read > maxBytes)
172
+ throw tooLarge(read, maxBytes);
173
+ const sha256 = sha256Hex(bytes);
174
+ const contentType = contentTypeFor(posix.basename(entry.path));
175
+ if (!stored.has(sha256)) {
176
+ stored.add(sha256);
177
+ const key = contentKey(sha256);
178
+ if (!(await ctx.blobs.head(key))) {
179
+ put.push(sha256);
180
+ await ctx.blobs.put(key, bytes, { contentType, signal });
181
+ }
182
+ }
183
+ entries.push({ path: entry.path, size: bytes.length, sha256, contentType });
184
+ }
185
+ // Every file vanished, or nothing changed since the last export: no new version.
186
+ if (entries.length === 0)
187
+ return;
188
+ if (found.latestSha !== undefined && sha256Hex(encodeManifest(entries)) === found.latestSha)
189
+ return;
190
+ await commitFolderVersion(ctx, {
191
+ sessionId,
192
+ turnId,
193
+ ...(found.priorId === undefined ? {} : { artifactId: found.priorId }),
194
+ name: OUTPUTS_ARTIFACT_NAME,
195
+ entries,
196
+ epoch: found.epoch,
197
+ totalBytes: limits.totalBytes,
198
+ });
199
+ }
200
+ function tooLargeFile(path, size, limit) {
201
+ return new Skip("file_too_large", `${OUTPUTS_DIR}/${path} is ${size} bytes; a file may hold at most ${limit}`, limit, path);
202
+ }
203
+ function tooLarge(bytes, limit) {
204
+ return new Skip("too_large", `${OUTPUTS_DIR} holds ${bytes} bytes; an export stores at most ${limit}`, limit);
205
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * How model-gate reads the files a prompt names (protocol 6): a media part's reference is an
3
+ * artifact version (`./parts.ts`), resolved here to its bytes from the Object store. The gate
4
+ * holds the store credential; the loop and the transcript only ever hold the reference.
5
+ *
6
+ * It replaces `MediaStore`: images are file artifacts, and their bytes reach the provider only in
7
+ * the request model-gate builds.
8
+ */
9
+ import type { JsonValue } from "@nylorun/core/define";
10
+ import type { BlobStore } from "../blob/index.js";
11
+ import type { SessionStore } from "../store/types.js";
12
+ /** A file a prompt names, with its bytes. */
13
+ export interface ResolvedFile {
14
+ readonly name: string;
15
+ /** The version's media type, as stored. */
16
+ readonly mediaType: string;
17
+ readonly bytes: Uint8Array;
18
+ }
19
+ /** Why a file cannot go to the model: the message is the outcome's, so it names no secret. */
20
+ export declare class FileUnavailableError extends Error {
21
+ constructor(message: string);
22
+ }
23
+ /** Resolves a media part's reference to the file's bytes, or throws `FileUnavailableError`. */
24
+ export type FileResolver = (reference: JsonValue) => Promise<ResolvedFile>;
25
+ /**
26
+ * Files from the Tenant's artifacts: `store` for the version's row, `blobs` for its bytes, for
27
+ * the call of session `sessionId`. An artifact of another session is refused (a Tenant-wide one
28
+ * is not), and without a session every file is: the check is never skipped.
29
+ */
30
+ export declare function artifactFiles(options: {
31
+ readonly store: SessionStore;
32
+ readonly blobs: BlobStore;
33
+ /** The session the call is for: the run token's, over HTTP. */
34
+ readonly sessionId: string | undefined;
35
+ readonly maxBytes?: number;
36
+ }): FileResolver;
@@ -0,0 +1,45 @@
1
+ import { MODEL_FILE_MAX_BYTES } from "./media-types.js";
2
+ import { fileReferenceOf } from "./parts.js";
3
+ /** Why a file cannot go to the model: the message is the outcome's, so it names no secret. */
4
+ export class FileUnavailableError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "FileUnavailableError";
8
+ }
9
+ }
10
+ /**
11
+ * Files from the Tenant's artifacts: `store` for the version's row, `blobs` for its bytes, for
12
+ * the call of session `sessionId`. An artifact of another session is refused (a Tenant-wide one
13
+ * is not), and without a session every file is: the check is never skipped.
14
+ */
15
+ export function artifactFiles(options) {
16
+ const maxBytes = options.maxBytes ?? MODEL_FILE_MAX_BYTES;
17
+ return async (value) => {
18
+ const reference = fileReferenceOf(value);
19
+ if (!reference)
20
+ throw new FileUnavailableError("Expected an artifact reference.");
21
+ const sessionId = options.sessionId;
22
+ if (typeof sessionId !== "string" || sessionId === "")
23
+ throw new FileUnavailableError("A model call reads files only for its own session.");
24
+ const found = await options.store.tx(async (t) => {
25
+ const artifact = await t.artifact(reference.artifactId);
26
+ if (!artifact)
27
+ return undefined;
28
+ const version = await t.artifactVersion(artifact.id, reference.version);
29
+ return version ? { artifact, version } : undefined;
30
+ });
31
+ if (!found ||
32
+ (found.artifact.sessionId !== null && found.artifact.sessionId !== sessionId))
33
+ throw new FileUnavailableError(`Artifact ${reference.artifactId} version ${reference.version} is not available.`);
34
+ const { artifact, version } = found;
35
+ if (artifact.kind !== "file")
36
+ throw new FileUnavailableError(`${artifact.name} is a folder; a model reads files only.`);
37
+ if (version.size > maxBytes)
38
+ throw new FileUnavailableError(`${artifact.name} is ${version.size} bytes; a model reads files of at most ${maxBytes} bytes.`);
39
+ const got = await options.blobs.get(version.blobKey);
40
+ if (!got)
41
+ throw new FileUnavailableError(`The bytes of ${artifact.name} are missing from the Object store.`);
42
+ const bytes = new Uint8Array(await new Response(got.body).arrayBuffer());
43
+ return { name: artifact.name, mediaType: version.contentType, bytes };
44
+ };
45
+ }
@@ -0,0 +1,60 @@
1
+ import type { ArtifactDiff, FolderEntry, FolderManifest } from "@nylorun/core/contracts";
2
+ import type { BlobStore } from "../blob/index.js";
3
+ import type { ArtifactRow, ArtifactVersionRow, Tx } from "../store/types.js";
4
+ import { type TenantContext } from "../tenant/context.js";
5
+ /** Where a folder file's bytes are: content-addressed by their SHA-256. */
6
+ export declare function contentKey(sha256: string): string;
7
+ export declare function sha256Hex(bytes: Uint8Array): string;
8
+ /** Entries in manifest order: by path, comparing UTF-16 code units. */
9
+ export declare function sortEntries(entries: readonly FolderEntry[]): FolderEntry[];
10
+ /** The manifest's bytes, as stored: deterministic for the same entries. */
11
+ export declare function encodeManifest(entries: readonly FolderEntry[]): Uint8Array;
12
+ /** A folder version's manifest, read from the Object store. */
13
+ export declare function readManifest(blobs: BlobStore, version: ArtifactVersionRow): Promise<FolderManifest>;
14
+ /** The entry at `path`, or the 404 of a missing file. */
15
+ export declare function entryAt(manifest: FolderManifest, path: string): FolderEntry;
16
+ /** What changed from `from` (absent: nothing) to `to`. */
17
+ export declare function diffManifests(artifactId: string, from: {
18
+ version: number;
19
+ manifest: FolderManifest;
20
+ } | undefined, to: {
21
+ version: number;
22
+ manifest: FolderManifest;
23
+ }): ArtifactDiff;
24
+ /** The Tenant's content epoch: moves whenever content-addressed files are deleted. */
25
+ export declare function contentEpoch(t: Tx): Promise<number>;
26
+ /**
27
+ * Deletes those of `shas` no committed folder version references any more, and moves the content
28
+ * epoch when it deletes any. Call it in a transaction holding the quota lock, after the rows that
29
+ * named them are gone: the deletes happen before the transaction commits, under the lock.
30
+ */
31
+ export declare function collectContent(blobs: BlobStore, t: Tx, shas: readonly string[]): Promise<number>;
32
+ /** The commit could not go ahead: a limit, or content removed while the export ran. */
33
+ export declare class FolderCommitRefused extends Error {
34
+ readonly reason: "tenant_total" | "content_gone" | "artifact_gone";
35
+ readonly limit?: number | undefined;
36
+ constructor(message: string, reason: "tenant_total" | "content_gone" | "artifact_gone", limit?: number | undefined);
37
+ }
38
+ export interface FolderVersionInput {
39
+ readonly sessionId: string;
40
+ readonly turnId: string | null;
41
+ /** A new version of this folder of the session; otherwise a new folder named `name`. */
42
+ readonly artifactId?: string;
43
+ readonly name: string;
44
+ /** Every file, each one's bytes already at `contentKey(sha256)`. */
45
+ readonly entries: readonly FolderEntry[];
46
+ /** The content epoch read before the first `head` of those bytes. */
47
+ readonly epoch: number;
48
+ /** The Tenant total in force. */
49
+ readonly totalBytes: number;
50
+ }
51
+ /**
52
+ * Commits a folder version whose files are already stored: its manifest to the Object store,
53
+ * then, in one transaction under the session and quota locks, the version row, its content rows
54
+ * and `artifact.created` or `artifact.version.created` (`claimed: true`, source `export`).
55
+ * Throws `FolderCommitRefused` when the Tenant total would be passed or a file is gone.
56
+ */
57
+ export declare function commitFolderVersion(ctx: TenantContext, input: FolderVersionInput): Promise<{
58
+ artifact: ArtifactRow;
59
+ version: ArtifactVersionRow;
60
+ }>;
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Folder artifacts (blueprint D35, F8.2). A folder version is a **manifest**: JSON
3
+ * `{ format, entries: [{ path, size, sha256, contentType }] }`, sorted by path, stored in the
4
+ * Object store under the version's `blobKey` like a file's bytes. Each file's bytes are stored
5
+ * once, content-addressed, at `blobs/sha256/<hex>`: a file that did not change between versions,
6
+ * or that two folders share, is stored and counted once.
7
+ *
8
+ * Which content-addressed files are in use is indexed in Postgres (`artifact_content`, a row per
9
+ * folder version and distinct hash), so deleting a folder removes only the files nothing else
10
+ * names, and the Tenant total counts each file once.
11
+ *
12
+ * **Collecting content safely.** A writer skips the `put` of a file the store already has
13
+ * (`head`), outside any lock, and a delete could remove that file before the writer commits. So:
14
+ * content is deleted only while holding the quota lock, and every such delete moves the Tenant's
15
+ * content epoch (setting `artifacts.content_epoch`) in the same transaction. A writer reads the
16
+ * epoch before its first `head`; at commit, under the quota lock, a file some committed version
17
+ * references is present (the invariant the lock keeps), and when the epoch moved it checks the
18
+ * others again and refuses to commit a version naming a file that is gone.
19
+ */
20
+ import { createHash, randomUUID } from "node:crypto";
21
+ import { FOLDER_CONTENT_TYPE, FOLDER_MANIFEST_FORMAT, FolderManifestSchema, } from "@nylorun/core/contracts";
22
+ import { newArtifactId } from "@nylorun/core/compatibility";
23
+ import { lockedSession } from "../tenant/context.js";
24
+ import { fail } from "../tenant/http.js";
25
+ const CONTENT_EPOCH_SETTING = "artifacts.content_epoch";
26
+ /** Where a folder file's bytes are: content-addressed by their SHA-256. */
27
+ export function contentKey(sha256) {
28
+ return `blobs/sha256/${sha256}`;
29
+ }
30
+ export function sha256Hex(bytes) {
31
+ return createHash("sha256").update(bytes).digest("hex");
32
+ }
33
+ /** Entries in manifest order: by path, comparing UTF-16 code units. */
34
+ export function sortEntries(entries) {
35
+ return [...entries].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
36
+ }
37
+ /** The manifest's bytes, as stored: deterministic for the same entries. */
38
+ export function encodeManifest(entries) {
39
+ const manifest = {
40
+ format: FOLDER_MANIFEST_FORMAT,
41
+ entries: sortEntries(entries).map(({ path, size, sha256, contentType }) => ({
42
+ path,
43
+ size,
44
+ sha256,
45
+ contentType,
46
+ })),
47
+ };
48
+ return new TextEncoder().encode(JSON.stringify(manifest));
49
+ }
50
+ /** A folder version's manifest, read from the Object store. */
51
+ export async function readManifest(blobs, version) {
52
+ const got = await blobs.get(version.blobKey);
53
+ if (!got)
54
+ return fail(404, "Artifact bytes not found");
55
+ const parsed = FolderManifestSchema.safeParse(JSON.parse(await new Response(got.body).text()));
56
+ if (!parsed.success)
57
+ throw new Error(`The manifest of ${version.artifactId} v${version.version} is malformed`);
58
+ return parsed.data;
59
+ }
60
+ /** The entry at `path`, or the 404 of a missing file. */
61
+ export function entryAt(manifest, path) {
62
+ return (manifest.entries.find((entry) => entry.path === path) ??
63
+ fail(404, `No file ${path} in this folder version`));
64
+ }
65
+ /** What changed from `from` (absent: nothing) to `to`. */
66
+ export function diffManifests(artifactId, from, to) {
67
+ const before = new Map((from?.manifest.entries ?? []).map((entry) => [entry.path, entry]));
68
+ const after = new Map(to.manifest.entries.map((entry) => [entry.path, entry]));
69
+ const added = [];
70
+ const changed = [];
71
+ for (const entry of to.manifest.entries) {
72
+ const prior = before.get(entry.path);
73
+ if (!prior)
74
+ added.push(entry);
75
+ else if (prior.sha256 !== entry.sha256 || prior.contentType !== entry.contentType)
76
+ changed.push({ path: entry.path, from: prior, to: entry });
77
+ }
78
+ const removed = (from?.manifest.entries ?? []).filter((entry) => !after.has(entry.path));
79
+ return {
80
+ artifactId,
81
+ ...(from === undefined ? {} : { from: from.version }),
82
+ to: to.version,
83
+ added,
84
+ removed,
85
+ changed,
86
+ };
87
+ }
88
+ /** The Tenant's content epoch: moves whenever content-addressed files are deleted. */
89
+ export async function contentEpoch(t) {
90
+ const raw = await t.getSetting(CONTENT_EPOCH_SETTING);
91
+ return raw === undefined ? 0 : Number(raw);
92
+ }
93
+ /**
94
+ * Deletes those of `shas` no committed folder version references any more, and moves the content
95
+ * epoch when it deletes any. Call it in a transaction holding the quota lock, after the rows that
96
+ * named them are gone: the deletes happen before the transaction commits, under the lock.
97
+ */
98
+ export async function collectContent(blobs, t, shas) {
99
+ if (shas.length === 0)
100
+ return 0;
101
+ const referenced = await t.referencedArtifactContent(shas);
102
+ const orphans = shas.filter((sha) => !referenced.has(sha));
103
+ if (orphans.length === 0)
104
+ return 0;
105
+ await t.putSetting(CONTENT_EPOCH_SETTING, String((await contentEpoch(t)) + 1));
106
+ for (const sha of orphans)
107
+ await blobs.delete(contentKey(sha));
108
+ return orphans.length;
109
+ }
110
+ /** The commit could not go ahead: a limit, or content removed while the export ran. */
111
+ export class FolderCommitRefused extends Error {
112
+ reason;
113
+ limit;
114
+ constructor(message, reason, limit) {
115
+ super(message);
116
+ this.reason = reason;
117
+ this.limit = limit;
118
+ this.name = "FolderCommitRefused";
119
+ }
120
+ }
121
+ /**
122
+ * Commits a folder version whose files are already stored: its manifest to the Object store,
123
+ * then, in one transaction under the session and quota locks, the version row, its content rows
124
+ * and `artifact.created` or `artifact.version.created` (`claimed: true`, source `export`).
125
+ * Throws `FolderCommitRefused` when the Tenant total would be passed or a file is gone.
126
+ */
127
+ export async function commitFolderVersion(ctx, input) {
128
+ const entries = sortEntries(input.entries);
129
+ const manifest = encodeManifest(entries);
130
+ const sha256 = sha256Hex(manifest);
131
+ const files = new Map();
132
+ for (const entry of entries)
133
+ files.set(entry.sha256, entry.size);
134
+ const size = entries.reduce((sum, entry) => sum + entry.size, 0);
135
+ const artifactId = input.artifactId ?? newArtifactId();
136
+ const key = `artifacts/${artifactId}/${randomUUID()}`;
137
+ await ctx.blobs.put(key, manifest, { contentType: FOLDER_CONTENT_TYPE });
138
+ const createdAt = new Date().toISOString();
139
+ try {
140
+ return await ctx.store.tx(async (t) => {
141
+ // The session first, then the quota, then the artifact (the module's lock order).
142
+ await lockedSession(t, input.sessionId);
143
+ await t.lockArtifactQuota();
144
+ const shas = [...files.keys()];
145
+ const referenced = await t.referencedArtifactContent(shas);
146
+ const fresh = shas.filter((sha) => !referenced.has(sha));
147
+ if ((await contentEpoch(t)) !== input.epoch)
148
+ for (const sha of fresh)
149
+ if (!(await ctx.blobs.head(contentKey(sha))))
150
+ throw new FolderCommitRefused("A file's bytes were removed while the export ran", "content_gone");
151
+ const added = fresh.reduce((sum, sha) => sum + files.get(sha), 0);
152
+ const total = await t.artifactBytes();
153
+ if (total + added > input.totalBytes)
154
+ throw new FolderCommitRefused(`The Tenant's artifacts may hold at most ${input.totalBytes} bytes; these outputs add ${added} to ${total}`, "tenant_total", input.totalBytes);
155
+ let row;
156
+ const version = {
157
+ artifactId,
158
+ version: 1,
159
+ blobKey: key,
160
+ size,
161
+ sha256,
162
+ contentType: FOLDER_CONTENT_TYPE,
163
+ source: "export",
164
+ createdAt,
165
+ };
166
+ if (input.artifactId !== undefined) {
167
+ const prior = await t.artifact(input.artifactId, { lock: true });
168
+ if (!prior || prior.kind !== "folder" || prior.sessionId !== input.sessionId)
169
+ throw new FolderCommitRefused("The folder was deleted while the export ran", "artifact_gone");
170
+ version.version = prior.latestVersion + 1;
171
+ await t.insertArtifactVersion(version);
172
+ row = { ...prior, latestVersion: version.version, contentType: FOLDER_CONTENT_TYPE, updatedAt: createdAt };
173
+ }
174
+ else {
175
+ row = {
176
+ id: artifactId,
177
+ kind: "folder",
178
+ name: input.name,
179
+ contentType: FOLDER_CONTENT_TYPE,
180
+ sessionId: input.sessionId,
181
+ latestVersion: 1,
182
+ labelsJson: null,
183
+ createdAt,
184
+ updatedAt: createdAt,
185
+ };
186
+ await t.insertArtifact(row, version);
187
+ }
188
+ await t.insertArtifactContent(artifactId, version.version, shas.map((sha) => ({ sha256: sha, size: files.get(sha) })));
189
+ await t.event(input.sessionId, input.turnId, version.version === 1 ? "artifact.created" : "artifact.version.created", {
190
+ artifactId,
191
+ kind: "folder",
192
+ name: row.name,
193
+ contentType: FOLDER_CONTENT_TYPE,
194
+ version: version.version,
195
+ size,
196
+ sha256,
197
+ source: "export",
198
+ fileCount: entries.length,
199
+ claimed: true,
200
+ });
201
+ return { artifact: row, version };
202
+ });
203
+ }
204
+ catch (error) {
205
+ // Nothing references the manifest: it was garbage from the start.
206
+ await ctx.blobs.delete(key).catch(() => undefined);
207
+ throw error;
208
+ }
209
+ }
@@ -0,0 +1,28 @@
1
+ import type { TenantContext } from "../tenant/context.js";
2
+ /** The audience of every capability link. */
3
+ export declare const ARTIFACT_LINK_AUDIENCE = "nylorun-artifact";
4
+ /** Where a capability link is served: `GET /v1/artifact-links/<token>`. */
5
+ export declare const ARTIFACT_LINK_PATH = "/v1/artifact-links";
6
+ export interface MintedArtifactLink {
7
+ path: string;
8
+ artifactId: string;
9
+ version: number;
10
+ /** The folder's file the link opens. */
11
+ file?: string;
12
+ expiresAt: string;
13
+ }
14
+ export declare function mintArtifactLink(ctx: TenantContext, link: {
15
+ artifactId: string;
16
+ version: number;
17
+ expiresIn?: number;
18
+ file?: string;
19
+ }): Promise<MintedArtifactLink>;
20
+ /**
21
+ * The artifact version a capability link opens. An expired link, or one whose key was revoked,
22
+ * is `401 token_expired`; anything else that does not verify is the opaque 404.
23
+ */
24
+ export declare function verifyArtifactLink(ctx: TenantContext, raw: string): Promise<{
25
+ artifactId: string;
26
+ version: number;
27
+ file?: string;
28
+ }>;