@nylorun/runtime 0.25.0-beta → 0.27.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 (326) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/README.md +26 -16
  3. package/dist/adapters/execution/restate.d.ts +15 -18
  4. package/dist/adapters/execution/restate.js +31 -27
  5. package/dist/adapters/replication/pgoutput.d.ts +1 -1
  6. package/dist/adapters/replication/pgoutput.js +3 -2
  7. package/dist/adapters/sandbox/local.js +8 -5
  8. package/dist/adapters/sandbox/virtual.js +2 -2
  9. package/dist/adapters/streams/s2.d.ts +1 -1
  10. package/dist/adapters/streams/s2.js +18 -14
  11. package/dist/api/a2a/card.js +1 -1
  12. package/dist/api/a2a/endpoint.js +3 -4
  13. package/dist/api/a2a/protocol.js +4 -0
  14. package/dist/api/a2a/routes.d.ts +2 -6
  15. package/dist/api/a2a/routes.js +12 -18
  16. package/dist/api/ag-ui/endpoint.js +2 -3
  17. package/dist/api/ag-ui/routes.js +4 -4
  18. package/dist/api/components.d.ts +326 -45
  19. package/dist/api/components.js +5 -1
  20. package/dist/api/gate/keys.js +7 -4
  21. package/dist/api/gate/routes.d.ts +6 -2
  22. package/dist/api/gate/routes.js +15 -11
  23. package/dist/api/http/app.d.ts +2 -2
  24. package/dist/api/http/app.js +2 -2
  25. package/dist/api/http/body.d.ts +15 -3
  26. package/dist/api/http/body.js +15 -7
  27. package/dist/api/http/define.d.ts +1 -1
  28. package/dist/api/http/respond.js +7 -3
  29. package/dist/api/http/routes/access.js +5 -4
  30. package/dist/api/http/routes/artifacts.js +19 -7
  31. package/dist/api/http/routes/coverage.js +1 -1
  32. package/dist/api/http/routes/environment-manifests.js +1 -1
  33. package/dist/api/http/routes/environments.js +6 -6
  34. package/dist/api/http/routes/files.d.ts +2 -1
  35. package/dist/api/http/routes/files.js +31 -5
  36. package/dist/api/http/routes/mcp.js +1 -1
  37. package/dist/api/http/routes/reads.d.ts +35 -0
  38. package/dist/api/http/routes/reads.js +50 -22
  39. package/dist/api/http/routes/sessions.js +12 -11
  40. package/dist/api/http/routes/tenant.d.ts +2 -2
  41. package/dist/api/http/routes/tenant.js +69 -16
  42. package/dist/api/inbound-files.d.ts +3 -9
  43. package/dist/api/inbound-files.js +18 -24
  44. package/dist/api/openapi.js +6 -2
  45. package/dist/artifacts/disk.d.ts +0 -4
  46. package/dist/artifacts/disk.js +77 -73
  47. package/dist/artifacts/files.d.ts +13 -4
  48. package/dist/artifacts/files.js +27 -9
  49. package/dist/artifacts/folder-upload.d.ts +75 -0
  50. package/dist/artifacts/folder-upload.js +263 -0
  51. package/dist/artifacts/folders.d.ts +14 -8
  52. package/dist/artifacts/folders.js +19 -11
  53. package/dist/artifacts/links.js +7 -6
  54. package/dist/artifacts/parts.d.ts +9 -14
  55. package/dist/artifacts/parts.js +11 -9
  56. package/dist/artifacts/pipe.d.ts +15 -0
  57. package/dist/artifacts/pipe.js +71 -0
  58. package/dist/artifacts/quota.d.ts +36 -0
  59. package/dist/artifacts/quota.js +44 -0
  60. package/dist/artifacts/service.d.ts +25 -1
  61. package/dist/artifacts/service.js +70 -25
  62. package/dist/blob/fs.js +2 -1
  63. package/dist/blob/s3.d.ts +6 -2
  64. package/dist/blob/s3.js +9 -7
  65. package/dist/blob/types.d.ts +1 -1
  66. package/dist/blob/types.js +1 -1
  67. package/dist/core/limits.d.ts +11 -35
  68. package/dist/core/limits.js +5 -73
  69. package/dist/core/sandbox-routes.js +0 -1
  70. package/dist/core/turn-manifest.d.ts +1 -2
  71. package/dist/core/turn-manifest.js +4 -28
  72. package/dist/execution/memory.d.ts +8 -5
  73. package/dist/execution/memory.js +8 -6
  74. package/dist/execution/types.d.ts +1 -6
  75. package/dist/execution/types.js +0 -5
  76. package/dist/gates/contract.d.ts +3 -2
  77. package/dist/gates/contract.js +1 -1
  78. package/dist/gates/egress-credentials.js +4 -3
  79. package/dist/gates/egress.d.ts +9 -9
  80. package/dist/gates/egress.js +19 -19
  81. package/dist/gates/fixture-model.js +13 -2
  82. package/dist/gates/handler.d.ts +1 -3
  83. package/dist/gates/handler.js +4 -3
  84. package/dist/gates/http-client.d.ts +4 -3
  85. package/dist/gates/http-client.js +5 -7
  86. package/dist/gates/http-tool.d.ts +24 -4
  87. package/dist/gates/http-tool.js +53 -17
  88. package/dist/gates/in-process.d.ts +2 -1
  89. package/dist/gates/in-process.js +17 -9
  90. package/dist/gates/inflight.d.ts +2 -2
  91. package/dist/gates/inflight.js +3 -2
  92. package/dist/gates/journal.js +5 -7
  93. package/dist/gates/mcp-handler.d.ts +4 -3
  94. package/dist/gates/mcp-handler.js +15 -5
  95. package/dist/gates/model-gate.d.ts +11 -2
  96. package/dist/gates/policy.d.ts +28 -5
  97. package/dist/gates/policy.js +60 -17
  98. package/dist/gates/provider-gate.d.ts +22 -0
  99. package/dist/gates/provider-gate.js +30 -0
  100. package/dist/gates/tool-calls.d.ts +1 -3
  101. package/dist/gates/tool-calls.js +2 -3
  102. package/dist/gates/tool-client.d.ts +1 -1
  103. package/dist/gates/tool-client.js +5 -6
  104. package/dist/gates/tool-contract.d.ts +8 -5
  105. package/dist/gates/tool-contract.js +1 -1
  106. package/dist/gates/tool-gate.d.ts +18 -7
  107. package/dist/gates/tool-gate.js +49 -7
  108. package/dist/harness/calls.d.ts +27 -25
  109. package/dist/harness/calls.js +40 -46
  110. package/dist/harness/executors.d.ts +10 -3
  111. package/dist/harness/executors.js +53 -23
  112. package/dist/harness/main.js +2 -2
  113. package/dist/harness/pod.d.ts +0 -2
  114. package/dist/harness/pod.js +7 -6
  115. package/dist/harness/run-tokens.d.ts +4 -1
  116. package/dist/harness/run-tokens.js +6 -2
  117. package/dist/harness/service.d.ts +17 -14
  118. package/dist/harness/service.js +21 -35
  119. package/dist/harness/setup.d.ts +0 -2
  120. package/dist/harness/setup.js +4 -9
  121. package/dist/harness/ws-client.d.ts +2 -2
  122. package/dist/harness/ws-client.js +7 -5
  123. package/dist/harness/ws-port.d.ts +3 -4
  124. package/dist/harness/ws-port.js +3 -4
  125. package/dist/harness-api/artifacts.d.ts +20 -0
  126. package/dist/harness-api/artifacts.js +478 -0
  127. package/dist/harness-api/in-process.d.ts +2 -2
  128. package/dist/harness-api/in-process.js +18 -16
  129. package/dist/harness-api/record.d.ts +10 -9
  130. package/dist/harness-api/record.js +102 -157
  131. package/dist/harness-api/renew.d.ts +1 -1
  132. package/dist/harness-api/renew.js +2 -2
  133. package/dist/harness-api/server.d.ts +1 -16
  134. package/dist/harness-api/server.js +82 -73
  135. package/dist/harness-api/start.d.ts +20 -2
  136. package/dist/harness-api/start.js +57 -18
  137. package/dist/harness-api/tool-journal.d.ts +15 -10
  138. package/dist/harness-api/tool-journal.js +26 -24
  139. package/dist/harness-api/workspace.d.ts +0 -4
  140. package/dist/harness-api/workspace.js +2 -45
  141. package/dist/harness-api/ws-server.js +10 -10
  142. package/dist/host/create-host.js +2 -3
  143. package/dist/host/execution.js +2 -3
  144. package/dist/host/gates.d.ts +2 -9
  145. package/dist/host/gates.js +15 -12
  146. package/dist/host/http.d.ts +0 -2
  147. package/dist/host/http.js +0 -27
  148. package/dist/host/main.js +10 -3
  149. package/dist/host/operate.js +12 -3
  150. package/dist/host/stack-config.d.ts +11 -0
  151. package/dist/host/stack-config.js +21 -1
  152. package/dist/index.d.ts +0 -1
  153. package/dist/index.js +0 -1
  154. package/dist/infra/index.js +2 -1
  155. package/dist/infra/readiness.d.ts +0 -2
  156. package/dist/infra/readiness.js +4 -3
  157. package/dist/keys/client.d.ts +1 -0
  158. package/dist/keys/client.js +2 -3
  159. package/dist/keys/contract.d.ts +1 -3
  160. package/dist/keys/contract.js +1 -3
  161. package/dist/keys/egress-ca.d.ts +0 -2
  162. package/dist/keys/egress-ca.js +2 -3
  163. package/dist/keys/keys.js +6 -1
  164. package/dist/keys/x509.d.ts +8 -4
  165. package/dist/keys/x509.js +14 -5
  166. package/dist/management-openapi.json +1961 -994
  167. package/dist/mcp/connect.d.ts +35 -26
  168. package/dist/mcp/connect.js +112 -141
  169. package/dist/mcp/pool.d.ts +27 -22
  170. package/dist/mcp/pool.js +34 -48
  171. package/dist/mcp/preview.d.ts +13 -6
  172. package/dist/mcp/preview.js +37 -20
  173. package/dist/mcp/snapshot.d.ts +35 -86
  174. package/dist/mcp/snapshot.js +93 -135
  175. package/dist/model/auth-store.js +5 -3
  176. package/dist/model/capabilities.d.ts +1 -1
  177. package/dist/model/catalog.d.ts +9 -36
  178. package/dist/model/catalog.js +6 -33
  179. package/dist/model/descriptor.d.ts +51 -0
  180. package/dist/model/descriptor.js +83 -0
  181. package/dist/model/models.d.ts +0 -3
  182. package/dist/model/models.js +3 -5
  183. package/dist/model/native-blocks.js +1 -1
  184. package/dist/model/pi-model.d.ts +16 -5
  185. package/dist/model/pi-model.js +86 -105
  186. package/dist/openapi.json +866 -1218
  187. package/dist/reads/cursor.js +3 -1
  188. package/dist/record/append.d.ts +34 -2
  189. package/dist/record/append.js +50 -1
  190. package/dist/record/environment.d.ts +2 -0
  191. package/dist/record/environment.js +2 -0
  192. package/dist/record/index.d.ts +1 -1
  193. package/dist/record/index.js +1 -1
  194. package/dist/redact.d.ts +1 -1
  195. package/dist/redact.js +3 -2
  196. package/dist/sandbox/egress-token.d.ts +3 -3
  197. package/dist/sandbox/egress-token.js +3 -3
  198. package/dist/sandbox/join.js +3 -2
  199. package/dist/sandbox/manager.d.ts +41 -41
  200. package/dist/sandbox/manager.js +112 -44
  201. package/dist/sandbox/placement.d.ts +1 -1
  202. package/dist/sandbox/placement.js +5 -8
  203. package/dist/sandbox/pods/client.d.ts +1 -10
  204. package/dist/sandbox/pods/client.js +8 -4
  205. package/dist/sandbox/pods/lifecycle.d.ts +19 -11
  206. package/dist/sandbox/pods/lifecycle.js +45 -23
  207. package/dist/sandbox/pods/network-gate.d.ts +4 -5
  208. package/dist/sandbox/pods/network-gate.js +7 -7
  209. package/dist/sandbox/pods/reconcile.js +4 -1
  210. package/dist/sandbox/pods/spec.js +8 -5
  211. package/dist/sandbox/policy.d.ts +7 -1
  212. package/dist/sandbox/policy.js +10 -3
  213. package/dist/sandbox/resolve.d.ts +8 -3
  214. package/dist/sandbox/resolve.js +12 -2
  215. package/dist/sandbox/setup.d.ts +3 -1
  216. package/dist/sandbox/setup.js +34 -11
  217. package/dist/sandbox/share.d.ts +5 -14
  218. package/dist/sandbox/share.js +5 -34
  219. package/dist/sandbox/skills.d.ts +7 -13
  220. package/dist/sandbox/skills.js +16 -31
  221. package/dist/sandbox/tenant-config.d.ts +2 -2
  222. package/dist/sandbox/tenant-config.js +4 -3
  223. package/dist/sandbox/tools.d.ts +7 -1
  224. package/dist/sandbox/tools.js +26 -26
  225. package/dist/sandbox/types.d.ts +10 -3
  226. package/dist/sandbox/types.js +9 -0
  227. package/dist/store/postgres/connect.d.ts +6 -5
  228. package/dist/store/postgres/connect.js +14 -11
  229. package/dist/store/postgres/control.js +13 -13
  230. package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
  231. package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
  232. package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
  233. package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
  234. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  235. package/dist/store/postgres/migrate.js +1 -1
  236. package/dist/store/postgres/reads.js +10 -2
  237. package/dist/store/postgres/schema.d.ts +19 -0
  238. package/dist/store/postgres/schema.js +7 -0
  239. package/dist/store/postgres/store.js +55 -26
  240. package/dist/store/types.d.ts +17 -7
  241. package/dist/streams/relay/core.d.ts +3 -3
  242. package/dist/streams/relay/core.js +9 -10
  243. package/dist/tenant/advance.js +21 -31
  244. package/dist/tenant/commands.js +9 -16
  245. package/dist/tenant/context.d.ts +14 -17
  246. package/dist/tenant/definition-files.d.ts +25 -4
  247. package/dist/tenant/definition-files.js +85 -47
  248. package/dist/tenant/effects.js +9 -36
  249. package/dist/tenant/environment-manifests.js +9 -1
  250. package/dist/tenant/environments.d.ts +0 -2
  251. package/dist/tenant/environments.js +54 -30
  252. package/dist/tenant/ephemeral.d.ts +1 -1
  253. package/dist/tenant/ephemeral.js +2 -1
  254. package/dist/tenant/history.d.ts +1 -1
  255. package/dist/tenant/history.js +1 -1
  256. package/dist/tenant/host-token.d.ts +3 -3
  257. package/dist/tenant/host-token.js +12 -11
  258. package/dist/tenant/http.d.ts +5 -8
  259. package/dist/tenant/http.js +9 -0
  260. package/dist/tenant/identity-file.d.ts +24 -1
  261. package/dist/tenant/identity-file.js +35 -7
  262. package/dist/tenant/issuers.d.ts +5 -5
  263. package/dist/tenant/issuers.js +31 -23
  264. package/dist/tenant/jwt.d.ts +8 -2
  265. package/dist/tenant/jwt.js +10 -2
  266. package/dist/tenant/limits.d.ts +90 -0
  267. package/dist/tenant/limits.js +175 -0
  268. package/dist/tenant/module.d.ts +4 -1
  269. package/dist/tenant/module.js +9 -8
  270. package/dist/tenant/outbound.d.ts +17 -19
  271. package/dist/tenant/outbound.js +18 -21
  272. package/dist/tenant/run-grants.js +3 -2
  273. package/dist/tenant/run-token.d.ts +0 -4
  274. package/dist/tenant/run-token.js +6 -11
  275. package/dist/tenant/runtime-limits.d.ts +24 -0
  276. package/dist/tenant/runtime-limits.js +27 -0
  277. package/dist/tenant/runtime.js +30 -43
  278. package/dist/tenant/session-streams.js +7 -6
  279. package/dist/tenant/session.d.ts +1 -1
  280. package/dist/tenant/session.js +1 -1
  281. package/dist/tenant/sessions.js +32 -29
  282. package/dist/tenant/signing-keys.d.ts +1 -1
  283. package/dist/tenant/signing-keys.js +5 -6
  284. package/dist/tenant/status.js +4 -3
  285. package/dist/tenant/streams.js +18 -20
  286. package/dist/tenant/sweep.d.ts +1 -21
  287. package/dist/tenant/sweep.js +39 -48
  288. package/dist/tenant/transcript.d.ts +70 -23
  289. package/dist/tenant/transcript.js +28 -22
  290. package/dist/tenant/types.d.ts +11 -16
  291. package/dist/tenant/worker.d.ts +2 -10
  292. package/dist/tenant/worker.js +5 -14
  293. package/dist/traces/ids.js +5 -1
  294. package/dist/traces/map.d.ts +9 -6
  295. package/dist/traces/map.js +9 -11
  296. package/dist/traces/otlp.d.ts +3 -3
  297. package/dist/traces/otlp.js +5 -3
  298. package/dist/traces/projector.d.ts +9 -4
  299. package/dist/traces/projector.js +13 -7
  300. package/dist/traces/read.js +6 -1
  301. package/dist/tuning.d.ts +383 -0
  302. package/dist/tuning.js +658 -0
  303. package/dist/vault/headers.js +4 -2
  304. package/dist/version.d.ts +1 -1
  305. package/dist/version.js +1 -1
  306. package/package.json +3 -3
  307. package/dist/artifacts/export.d.ts +0 -8
  308. package/dist/artifacts/export.js +0 -204
  309. package/dist/artifacts/media-types.d.ts +0 -30
  310. package/dist/artifacts/media-types.js +0 -137
  311. package/dist/artifacts/workspace.d.ts +0 -60
  312. package/dist/artifacts/workspace.js +0 -45
  313. package/dist/media.d.ts +0 -10
  314. package/dist/media.js +0 -58
  315. package/dist/sandbox/runtime-tools.d.ts +0 -9
  316. package/dist/sandbox/runtime-tools.js +0 -16
  317. package/dist/sandbox/session-sandbox.d.ts +0 -16
  318. package/dist/sandbox/session-sandbox.js +0 -77
  319. package/dist/tenant/artifact-tool.d.ts +0 -13
  320. package/dist/tenant/artifact-tool.js +0 -243
  321. package/dist/tenant/skill-tool.d.ts +0 -20
  322. package/dist/tenant/skill-tool.js +0 -67
  323. package/dist/tenant/tool-results.d.ts +0 -57
  324. package/dist/tenant/tool-results.js +0 -298
  325. package/dist/tenant/tool-search.d.ts +0 -39
  326. package/dist/tenant/tool-search.js +0 -103
@@ -3,15 +3,22 @@
3
3
  * today a skill's folder (`SkillManifest.files`). A client uploads each one once
4
4
  * (`PUT /v1/files/sha256:<hex>`); its bytes go to the Object store at
5
5
  * `definitions/sha256/<hex>`, and a `definition_files` row says the Tenant holds it. A
6
- * definition naming a file the Tenant does not hold is refused (`definition_files_missing`).
7
- * The Runtime reads them to serve the skill tools and to give a sandbox the skills' files.
6
+ * definition naming a file the Tenant does not hold is refused (`definition_files_missing`), and
7
+ * one whose skill's files hold more than the Tenant's skill limit together (`limit_exceeded`,
8
+ * `skills.config`, default 2 GiB). One file may be as large as a whole skill.
9
+ * The Runtime serves them to a run's harness (`definition.file`: our engine's skill tools read
10
+ * them) and gives a sandbox the skills' files.
8
11
  *
9
12
  * Files are never deleted here: `definition_file_uses` records which definition version names
10
13
  * which file, for a later sweep.
11
14
  */
12
- import { createHash } from "node:crypto";
13
- import { DEFINITION_FILE_MAX_BYTES, definitionFilesOf, isDefinitionFileHash, } from "@nylorun/core/contracts";
15
+ import { randomUUID } from "node:crypto";
16
+ import { definitionFilesOf, isDefinitionFileHash, skillsOf, } from "@nylorun/core/contracts";
17
+ import { BlobTooLargeError } from "../blob/index.js";
14
18
  import { fail } from "./http.js";
19
+ import { readSkillLimits } from "./limits.js";
20
+ /** Where an upload's bytes wait while their hash is checked; never read as a definition file. */
21
+ const STAGING_PREFIX = "definitions/uploads/";
15
22
  /** Where a definition file's bytes are in the Object store. */
16
23
  export function definitionFileKey(sha256) {
17
24
  return `definitions/sha256/${sha256.slice("sha256:".length)}`;
@@ -26,73 +33,104 @@ export async function hasDefinitionFile(ctx, file) {
26
33
  const sha256 = checkHash(file);
27
34
  return (await ctx.store.tx((t) => t.definitionFile(sha256))) !== undefined;
28
35
  }
29
- /** The body, at most `DEFINITION_FILE_MAX_BYTES`; past it, `413 limit_exceeded`. */
30
- async function readBody(body, declared) {
31
- const tooLarge = () => fail(413, `A definition file may hold at most ${DEFINITION_FILE_MAX_BYTES} bytes`, {
36
+ function tooLarge(limitBytes) {
37
+ return fail(413, `A definition file may hold at most ${limitBytes} bytes, the Tenant's limit for a whole skill`, {
32
38
  code: "limit_exceeded",
33
- details: { limitBytes: DEFINITION_FILE_MAX_BYTES },
39
+ details: { limitBytes },
34
40
  });
35
- if (declared !== undefined && declared > DEFINITION_FILE_MAX_BYTES) {
36
- await body?.cancel().catch(() => undefined);
37
- tooLarge();
38
- }
39
- if (body === null)
40
- return new Uint8Array();
41
- const chunks = [];
42
- let size = 0;
43
- const reader = body.getReader();
44
- for (;;) {
45
- const { done, value } = await reader.read();
46
- if (done)
47
- break;
48
- size += value.byteLength;
49
- if (size > DEFINITION_FILE_MAX_BYTES) {
50
- await reader.cancel().catch(() => undefined);
51
- tooLarge();
52
- }
53
- chunks.push(value);
54
- }
55
- return Buffer.concat(chunks);
56
41
  }
57
42
  /**
58
43
  * Stores the bytes of definition file `file`: `201` when they are new, `200` when the Tenant
59
- * holds them already (the body is not read). Bytes that do not hash to `file` are a `400`.
44
+ * holds them already (the body is not read). Bytes that do not hash to `file` are a `400`; more
45
+ * than the Tenant's skill limit (`skills.config`) a `413`. The body streams to a staging key and is copied to
46
+ * the file's key only once its hash is checked, so no file is held in memory and bytes of
47
+ * another hash never replace a file at its key.
60
48
  */
61
49
  export async function putDefinitionFile(ctx, file, request, signal) {
62
50
  const sha256 = checkHash(file);
63
- const held = await ctx.store.tx((t) => t.definitionFile(sha256));
51
+ const { held, maxBytes } = await ctx.store.tx(async (t) => ({
52
+ held: await t.definitionFile(sha256),
53
+ maxBytes: (await readSkillLimits(t)).totalBytes,
54
+ }));
64
55
  if (held) {
65
56
  await request.body?.cancel().catch(() => undefined);
66
57
  return { status: 200, view: { sha256, size: held.size } };
67
58
  }
68
- const bytes = await readBody(request.body, request.declaredBytes);
69
- const actual = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
70
- if (actual !== sha256)
71
- fail(400, `The body's SHA-256 is ${actual}, not ${sha256}: upload a file at its own hash`, {
72
- code: "invalid_request",
73
- details: { expected: sha256, actual },
74
- });
75
- // The key is the content's hash: a concurrent upload of the same file writes the same bytes.
76
- await ctx.blobs.put(definitionFileKey(sha256), bytes, {
59
+ if (request.declaredBytes !== undefined && request.declaredBytes > maxBytes) {
60
+ await request.body?.cancel().catch(() => undefined);
61
+ tooLarge(maxBytes);
62
+ }
63
+ const options = {
77
64
  ...(request.contentType === undefined ? {} : { contentType: request.contentType }),
78
65
  ...(signal ? { signal } : {}),
79
- });
66
+ };
67
+ const staging = `${STAGING_PREFIX}${randomUUID()}`;
68
+ let size;
69
+ try {
70
+ let staged;
71
+ try {
72
+ staged = await ctx.blobs.put(staging, request.body ?? new Uint8Array(), {
73
+ ...options,
74
+ maxBytes,
75
+ });
76
+ }
77
+ catch (error) {
78
+ if (error instanceof BlobTooLargeError)
79
+ tooLarge(maxBytes);
80
+ throw error;
81
+ }
82
+ const actual = `sha256:${staged.sha256}`;
83
+ if (actual !== sha256)
84
+ fail(400, `The body's SHA-256 is ${actual}, not ${sha256}: upload a file at its own hash`, {
85
+ code: "invalid_request",
86
+ details: { expected: sha256, actual },
87
+ });
88
+ const got = await ctx.blobs.get(staging, signal ? { signal } : {});
89
+ if (!got)
90
+ throw new Error(`The Object store lost the upload of ${sha256} at ${staging}`);
91
+ // The key is the content's hash: a concurrent upload of the same file writes the same bytes.
92
+ await ctx.blobs.put(definitionFileKey(sha256), got.body, options);
93
+ size = staged.size;
94
+ }
95
+ finally {
96
+ await ctx.blobs.delete(staging).catch(() => undefined);
97
+ }
80
98
  const inserted = await ctx.store.tx((t) => t.insertDefinitionFile({
81
99
  sha256,
82
100
  kind: "skill",
83
- size: bytes.byteLength,
101
+ size,
84
102
  contentType: request.contentType ?? null,
85
103
  createdAt: new Date().toISOString(),
86
104
  }));
87
- return { status: inserted ? 201 : 200, view: { sha256, size: bytes.byteLength } };
105
+ return { status: inserted ? 201 : 200, view: { sha256, size } };
88
106
  }
89
- /** The definition files `document` names that the Tenant does not hold, in order. */
90
- export async function missingDefinitionFiles(t, document) {
107
+ /**
108
+ * Why `document` cannot be stored for the files it names: the files the Tenant does not hold
109
+ * (`missing`), else each skill whose files hold more than `limitBytes` together (`oversized`).
110
+ * A file a skill names at two paths counts twice: a sandbox holds both.
111
+ */
112
+ export async function definitionFileIssues(t, document, limitBytes) {
91
113
  const named = [...definitionFilesOf(document)].sort();
92
114
  if (named.length === 0)
93
- return [];
115
+ return { missing: [], oversized: [] };
94
116
  const held = await t.heldDefinitionFiles(named);
95
- return named.filter((sha256) => !held.has(sha256));
117
+ const missing = named.filter((sha256) => !held.has(sha256));
118
+ if (missing.length > 0)
119
+ return { missing, oversized: [] };
120
+ const oversized = [];
121
+ for (const skill of skillsOf(document)) {
122
+ let bytes = 0;
123
+ for (const hash of Object.values(skill.files ?? {}))
124
+ if (typeof hash === "string")
125
+ bytes += held.get(hash) ?? 0;
126
+ if (bytes > limitBytes)
127
+ oversized.push({ name: String(skill.name), bytes });
128
+ }
129
+ return { missing, oversized };
130
+ }
131
+ /** A definition file's bytes as a stream, or undefined when the Object store does not have them. */
132
+ export async function openDefinitionFile(blobs, sha256, options = {}) {
133
+ return (await blobs.get(definitionFileKey(sha256), options))?.body;
96
134
  }
97
135
  /** A definition file's bytes, or undefined when the Object store does not have them. */
98
136
  export async function readDefinitionFile(blobs, sha256, signal) {
@@ -1,11 +1,10 @@
1
1
  import { embeddedAgent, flowDelegateManifest, hashManifest, } from "@nylorun/core/define";
2
2
  import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowToolEffect, isWorkflowManifest, linkedMessageInput, linkedMessageKey, linkedTurnEnd, settleAgentEffect, } from "../core/flow-host.js";
3
3
  import { mayDispatchMore } from "../core/limits.js";
4
+ import { readRuntimeLimits } from "./runtime-limits.js";
4
5
  import { sessionCredentials } from "../vault/sources.js";
5
6
  import { isRemoteMcpCall } from "../harness/calls.js";
6
7
  import { isHttpToolCall } from "../gates/http-tool.js";
7
- import { sandboxSpecOf } from "../sandbox/share.js";
8
- import { withRuntimeTools, withSandbox } from "../sandbox/session-sandbox.js";
9
8
  import { loadSession, ownedSession, sessionOf, } from "./context.js";
10
9
  import { fail } from "./http.js";
11
10
  import { turnManifestOf } from "./session.js";
@@ -41,7 +40,7 @@ export function recoversToolCalls(ctx) {
41
40
  }
42
41
  /** True when `request` calls a tool of a declared MCP server (every one is remote) or an HTTP tool. */
43
42
  export function isGateToolEffect(s, request) {
44
- return (isRemoteMcpCall({ rootManifest: s.manifest, mcpSnapshot: s.mcpSnapshot }, request) ||
43
+ return (isRemoteMcpCall({ rootManifest: s.manifest }, request) ||
45
44
  isHttpToolCall(s.manifest, request));
46
45
  }
47
46
  /**
@@ -50,7 +49,7 @@ export function isGateToolEffect(s, request) {
50
49
  * the running call or gets its outcome, instead of becoming `uncertain`.
51
50
  */
52
51
  export function recoversModelCalls(ctx) {
53
- return ctx.useVaultModel && ctx.modelGate.recovers === true;
52
+ return ctx.modelGate.recovers === true;
54
53
  }
55
54
  /** Journal and dispatch a new flow effect (agent or tool node). */
56
55
  export async function resolveNewFlowEffect(ctx, request, signal, lease) {
@@ -87,7 +86,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
87
86
  const active = await countActiveFlowWork(t, request.sessionId, request.turnId);
88
87
  // When the run journaled the effect (R7.1); a queued effect keeps it once dispatched.
89
88
  const startedAt = existing?.startedAt ?? new Date().toISOString();
90
- if (!mayDispatchMore(active, ctx.flowLimits)) {
89
+ if (!mayDispatchMore(active, await readRuntimeLimits(t))) {
91
90
  if (!existing || existing.status !== "queued")
92
91
  await t.put("effects", request.effectId, {
93
92
  request,
@@ -162,16 +161,14 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
162
161
  if (exists)
163
162
  return;
164
163
  const definition = leafDefinition(workflow, body, request);
165
- // The tree's Environment reaches every agent in it: its sandbox, with its tools, and its vault.
166
- const inherited = inheritedSandbox(workflow, definition);
167
- // Without one, the agent still gets its runtime block: read_artifact (R2b C11) and its skill tools.
168
- const pinned = inherited?.manifest ? inherited : runtimeTools(definition.manifest);
164
+ // The tree's Environment reaches every agent in it: its sandbox and its vault.
165
+ const sandbox = workflow.sandbox;
169
166
  const created = {
170
167
  id: agentSessionId,
171
168
  agentId: body.agentId,
172
169
  ownerUserId: workflow.ownerUserId,
173
- manifest: pinned?.manifest ?? definition.manifest,
174
- manifestHash: pinned?.manifestHash ?? definition.manifestHash,
170
+ manifest: definition.manifest,
171
+ manifestHash: definition.manifestHash,
175
172
  definitionHash: definition.manifestHash,
176
173
  implementationVersion: definition.implementationVersion,
177
174
  status: "idle",
@@ -191,7 +188,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
191
188
  ? {}
192
189
  : { environmentVersion: workflow.environmentVersion }),
193
190
  }),
194
- ...(inherited ? { sandbox: inherited.spec } : {}),
191
+ ...(sandbox === undefined ? {} : { sandbox }),
195
192
  };
196
193
  await t.put("sessions", agentSessionId, created);
197
194
  });
@@ -247,30 +244,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
247
244
  return { status: "pending" };
248
245
  });
249
246
  }
250
- /**
251
- * The sandbox a linked session inherits from its tree's owner when that sandbox was chosen at
252
- * open (pinned on the owner). A definition that declares its own sandbox keeps it, as before.
253
- */
254
- function inheritedSandbox(owner, definition) {
255
- const spec = owner?.sandbox;
256
- if (!spec || sandboxSpecOf(definition.manifest))
257
- return undefined;
258
- if (isWorkflowManifest(definition.manifest))
259
- return { spec };
260
- const sandboxed = withSandbox(definition.manifest, spec);
261
- if (!sandboxed.ok)
262
- throw new Error(sandboxed.message);
263
- return { spec, manifest: sandboxed.manifest, manifestHash: sandboxed.manifestHash };
264
- }
265
- /** A linked agent session's `runtime` block without a sandbox (R2b C11); undefined for a workflow. */
266
- function runtimeTools(manifest) {
267
- if (isWorkflowManifest(manifest))
268
- return undefined;
269
- const pinned = withRuntimeTools(manifest);
270
- if (!pinned.ok)
271
- throw new Error(pinned.message);
272
- return { manifest: pinned.manifest, manifestHash: pinned.manifestHash };
273
- }
274
247
  /**
275
248
  * The credential of an MCP server or HTTP tool request made on behalf of a session: from its
276
249
  * Environment's vault (`vault/sources.ts`).
@@ -18,6 +18,8 @@ import {} from "@nylorun/core/contracts";
18
18
  import { hashManifest } from "@nylorun/core/compatibility";
19
19
  import { canonical } from "../store/canonical.js";
20
20
  import { resolveRecipe } from "../sandbox/resolve.js";
21
+ import { limitIssues, manifestShapeIssues, readTenantLimits } from "./limits.js";
22
+ import { readRuntimeLimits } from "./runtime-limits.js";
21
23
  import { effectiveSandboxConfig, readSandboxConfig } from "../sandbox/tenant-config.js";
22
24
  import { actorOf, fileErrors } from "./environments.js";
23
25
  import { fail } from "./http.js";
@@ -32,6 +34,7 @@ export function manifestRecipeRequest(manifest) {
32
34
  ...(manifest.lifecycle === undefined ? {} : { lifecycle: manifest.lifecycle }),
33
35
  ...(manifest.setup === undefined ? {} : { setup: manifest.setup }),
34
36
  ...(manifest.files === undefined ? {} : { files: manifest.files }),
37
+ ...(manifest.limits === undefined ? {} : { limits: manifest.limits }),
35
38
  ...(manifest.secrets === undefined ? {} : { secrets: manifest.secrets }),
36
39
  };
37
40
  }
@@ -89,7 +92,12 @@ function warningsOf(ctx, manifest) {
89
92
  async function errorsOf(t, manifest) {
90
93
  const config = effectiveSandboxConfig(await readSandboxConfig(t));
91
94
  const resolved = resolveRecipe(manifest.kind ?? "virtual", manifestRecipeRequest(manifest), config);
92
- return [...(resolved.kind === "error" ? resolved.errors : []), ...(await fileErrors(t, manifest.files))];
95
+ return [
96
+ ...(resolved.kind === "error" ? resolved.errors : []),
97
+ ...limitIssues(manifest.limits, await readTenantLimits(t)),
98
+ ...manifestShapeIssues(manifest, await readRuntimeLimits(t)),
99
+ ...(await fileErrors(t, manifest.files)),
100
+ ];
93
101
  }
94
102
  export async function saveEnvironmentManifest(ctx, name, body, scope, options = {}) {
95
103
  const { requestId: _, ...manifest } = body;
@@ -116,8 +116,6 @@ export declare function newVolume(ctx: TenantContext, t: Tx, environment: Enviro
116
116
  };
117
117
  reason?: "restore" | "lost";
118
118
  }): Promise<number>;
119
- /** How long a pending seed holds the disk before another process may take it over. */
120
- export declare const SEED_LEASE_MS: number;
121
119
  /** `GET /v1/environments/{id}/events`: the Environment's lifecycle stream. */
122
120
  export declare function environmentEventsOf(ctx: TenantContext, id: string, scope: AuthScope, query?: EnvironmentEventsQuery): Promise<{
123
121
  events: {
@@ -54,8 +54,11 @@ import { SETUP_FAILED } from "../sandbox/pods/lifecycle.js";
54
54
  import { requirePods } from "../sandbox/placement.js";
55
55
  import { deleteDiskArtifact, forkSeedOf, runPendingSeed, snapshotEnvironment } from "../artifacts/disk.js";
56
56
  import { manifestChanges, manifestRecipeRequest } from "./environment-manifests.js";
57
+ import { labelIssues, limitIssues, limitsInForce, manifestShapeIssues, readTenantLimits, secretRequestIssues, } from "./limits.js";
58
+ import { readRuntimeLimits } from "./runtime-limits.js";
57
59
  import { harnessOf, sessionOf } from "./context.js";
58
- import { fail } from "./http.js";
60
+ import { fail, failRetryLater } from "./http.js";
61
+ import { hostTuning } from "../tuning.js";
59
62
  /** The Environment grants that limit `scope`, or undefined when it reaches every Environment. */
60
63
  export function environmentGrantsOf(scope) {
61
64
  return scope.kind === "token" ? (scope.environments ?? []) : undefined;
@@ -109,6 +112,7 @@ async function viewOf(ctx, t, environment, owner) {
109
112
  ...(environment.manifestName === undefined ? {} : { manifestName: environment.manifestName }),
110
113
  ...(environment.manifestVersion === undefined ? {} : { manifestVersion: environment.manifestVersion }),
111
114
  ...(environment.manifestHash === undefined ? {} : { manifestHash: environment.manifestHash }),
115
+ limits: limitsInForce(await readTenantLimits(t), environment.applied.limits),
112
116
  ...(environment.forkedFrom === undefined ? {} : { forkedFrom: environment.forkedFrom }),
113
117
  ...(slots === undefined ? {} : { slots }),
114
118
  version: environment.version,
@@ -173,6 +177,7 @@ const INLINE_FIELDS = [
173
177
  "lifecycle",
174
178
  "setup",
175
179
  "files",
180
+ "limits",
176
181
  "secrets",
177
182
  ];
178
183
  /** The hash of what an Environment runs, recorded when it is made. */
@@ -196,6 +201,7 @@ function recipeRequest(body, existing) {
196
201
  ...pick("lifecycle", body.lifecycle),
197
202
  ...pick("setup", body.setup),
198
203
  ...pick("files", body.files),
204
+ ...pick("limits", body.limits),
199
205
  ...pick("secrets", body.secrets),
200
206
  };
201
207
  }
@@ -305,16 +311,25 @@ export async function putEnvironment(ctx, id, body, scope, options = {}) {
305
311
  const view = await ctx.store.tx(async (t) => {
306
312
  const now = new Date().toISOString();
307
313
  const config = effectiveSandboxConfig(await readSandboxConfig(t));
308
- const resolve = (kind, request) => {
314
+ const tenantLimits = await readTenantLimits(t);
315
+ const runtimeLimits = await readRuntimeLimits(t);
316
+ // `limits` and the manifest's shape are checked against the Tenant's only when an Environment
317
+ // is made: a get-or-create on one made before the Tenant lowered a limit still answers, and
318
+ // the lower limit applies. Labels are checked whenever they are set.
319
+ const labels = labelIssues(body.labels, runtimeLimits);
320
+ if (labels.length > 0)
321
+ fail(400, labels.join(" "));
322
+ const resolve = (kind, request, creating) => {
309
323
  const resolved = resolveRecipe(kind, request, config);
310
- return resolved.kind === "error" ? fail(400, resolved.errors.join(" ")) : resolved.recipe;
324
+ const errors = [
325
+ ...(resolved.kind === "error" ? resolved.errors : []),
326
+ ...(creating ? [...limitIssues(request.limits, tenantLimits), ...manifestShapeIssues(request, runtimeLimits)] : []),
327
+ ];
328
+ return resolved.kind === "error" || errors.length > 0 ? fail(400, errors.join(" ")) : resolved.recipe;
311
329
  };
312
330
  const deleting = (row) => {
313
331
  if (row && !live(row))
314
- fail(409, `Environment ${id} is being deleted. Try again once it is gone.`, {
315
- code: "environment_busy",
316
- details: { retryAfterSeconds: 5 },
317
- }, { "retry-after": "5" });
332
+ failRetryLater(`Environment ${id} is being deleted. Try again once it is gone.`, "environment_busy");
318
333
  };
319
334
  const found = async (existing) => {
320
335
  if (fork !== undefined)
@@ -333,7 +348,7 @@ export async function putEnvironment(ctx, id, body, scope, options = {}) {
333
348
  immutable(["kind"], existing);
334
349
  if (body.storage !== undefined && body.storage !== existing.recipe.storage)
335
350
  immutable(["storage"], existing);
336
- const recipe = resolve(existing.kind, recipeRequest(body, existing));
351
+ const recipe = resolve(existing.kind, recipeRequest(body, existing), false);
337
352
  const differing = Object.keys({ ...recipe, ...existing.recipe })
338
353
  .filter((field) => canonical(recipe[field]) !== canonical(existing.recipe[field]))
339
354
  .sort();
@@ -363,7 +378,7 @@ export async function putEnvironment(ctx, id, body, scope, options = {}) {
363
378
  const kind = manifest ? (manifest.body.kind ?? "virtual") : (body.kind ?? "virtual");
364
379
  if (manifest && kind === "pod")
365
380
  await requirePods(ctx.pods, `Environment manifest ${manifest.name} is of kind pod, and a pod Environment`);
366
- const recipe = resolve(kind, manifest ? manifestRecipeRequest(manifest.body) : recipeRequest(body));
381
+ const recipe = resolve(kind, manifest ? manifestRecipeRequest(manifest.body) : recipeRequest(body), true);
367
382
  await checkFiles(t, recipe.files);
368
383
  const from = body.from === undefined ? undefined : await forkSource(t, scope, kind, body.from);
369
384
  // A fork onto other setup runs it on the written disk (Q17).
@@ -390,7 +405,7 @@ export async function putEnvironment(ctx, id, body, scope, options = {}) {
390
405
  kind: "seed",
391
406
  version: seed.version,
392
407
  startedAt: now,
393
- until: new Date(Date.now() + SEED_LEASE_MS).toISOString(),
408
+ until: new Date(Date.now() + hostTuning().tenant.seedLeaseMs).toISOString(),
394
409
  },
395
410
  }
396
411
  : {}),
@@ -461,6 +476,14 @@ export async function putEnvironment(ctx, id, body, scope, options = {}) {
461
476
  * accepted only when its binding is the slot's. Without slots, today's typed body.
462
477
  */
463
478
  export async function secretRequestFor(ctx, id, name, raw) {
479
+ const request = await parseSecretRequest(ctx, id, name, raw);
480
+ // Within the Tenant's Runtime limits `secretValueChars` and `secretHosts`, read for the write.
481
+ const issues = secretRequestIssues(request, await ctx.store.tx((t) => readRuntimeLimits(t)));
482
+ if (issues.length > 0)
483
+ fail(400, issues.join(" "));
484
+ return request;
485
+ }
486
+ async function parseSecretRequest(ctx, id, name, raw) {
464
487
  const environment = await ctx.store.tx((t) => liveEnvironment(t, id));
465
488
  const slots = environment?.recipe.secrets;
466
489
  if (!environment || slots === undefined)
@@ -538,8 +561,14 @@ export async function forkEnvironment(ctx, sourceId, body, scope) {
538
561
  : `Environment manifest ${name} has no version ${ref.version}.`, { code: "manifest_not_found" });
539
562
  const kind = manifest.body.kind ?? "virtual";
540
563
  const resolved = resolveRecipe(kind, manifestRecipeRequest(manifest.body), effectiveSandboxConfig(await readSandboxConfig(t)));
541
- if (resolved.kind === "error")
542
- return fail(400, resolved.errors.join(" "));
564
+ const errors = [
565
+ ...(resolved.kind === "error" ? resolved.errors : []),
566
+ ...limitIssues(manifest.body.limits, await readTenantLimits(t)),
567
+ ...manifestShapeIssues(manifest.body, await readRuntimeLimits(t)),
568
+ ...labelIssues(body.labels, await readRuntimeLimits(t)),
569
+ ];
570
+ if (resolved.kind === "error" || errors.length > 0)
571
+ return fail(400, errors.join(" "));
543
572
  const recipe = resolved.recipe;
544
573
  const asked = body.disk ?? "latest";
545
574
  const disk = kind === "none" || source.kind === "none" || asked === "empty"
@@ -666,19 +695,13 @@ export function diskHeld(environment, now = Date.now()) {
666
695
  /** Refused while a snapshot or restore copies the disk (a pending seed does not count). */
667
696
  export function refuseDiskCopy(environment) {
668
697
  if (diskHeld(environment) && environment.diskOp.kind !== "seed")
669
- fail(409, `Environment ${environment.id}'s disk is being copied (${environment.diskOp.kind}). Try again shortly.`, {
670
- code: "environment_busy",
671
- details: { retryAfterSeconds: 5 },
672
- }, { "retry-after": "5" });
698
+ failRetryLater(`Environment ${environment.id}'s disk is being copied (${environment.diskOp.kind}). Try again shortly.`, "environment_busy");
673
699
  }
674
700
  /** Refused while a session that names Environment `id` has a turn running. */
675
701
  async function refuseBusy(t, id) {
676
702
  const busy = (await t.sessionsOnEnvironment(id)).some((session) => session.activeTurnId !== null);
677
703
  if (busy)
678
- fail(409, `Environment ${id} has a turn running. Cancel it, or wait for it to end.`, {
679
- code: "environment_busy",
680
- details: { retryAfterSeconds: 5 },
681
- }, { "retry-after": "5" });
704
+ failRetryLater(`Environment ${id} has a turn running. Cancel it, or wait for it to end.`, "environment_busy");
682
705
  }
683
706
  /**
684
707
  * Deletes `environment` (locked, live) in `t`: its vault and credentials at once, its workspace
@@ -813,10 +836,7 @@ export async function resetEnvironment(ctx, id, scope) {
813
836
  refuseDiskCopy(environment);
814
837
  const pod = environment.pod;
815
838
  if (pod.retiring !== undefined)
816
- fail(409, `Environment ${id} is still deleting its previous volume. Try again shortly.`, {
817
- code: "environment_busy",
818
- details: { retryAfterSeconds: 5 },
819
- }, { "retry-after": "5" });
839
+ failRetryLater(`Environment ${id} is still deleting its previous volume. Try again shortly.`, "environment_busy");
820
840
  await newVolume(ctx, t, environment);
821
841
  return viewOf(ctx, t, (await t.environment(id)) ?? environment, subjectOf(scope));
822
842
  });
@@ -845,7 +865,7 @@ export async function newVolume(ctx, t, environment, options = {}) {
845
865
  kind: "seed",
846
866
  version: options.seed.version,
847
867
  startedAt: now,
848
- until: new Date(Date.now() + SEED_LEASE_MS).toISOString(),
868
+ until: new Date(Date.now() + hostTuning().tenant.seedLeaseMs).toISOString(),
849
869
  },
850
870
  }),
851
871
  }, now);
@@ -873,12 +893,13 @@ export async function newVolume(ctx, t, environment, options = {}) {
873
893
  await revokeHost(t, id, epoch);
874
894
  return volumeGen;
875
895
  }
876
- /** How long a pending seed holds the disk before another process may take it over. */
877
- export const SEED_LEASE_MS = 10 * 60_000;
878
896
  /** `GET /v1/environments/{id}/events`: the Environment's lifecycle stream. */
879
897
  export async function environmentEventsOf(ctx, id, scope, query = {}) {
880
898
  reachable(environmentGrantsOf(scope), id);
881
- return ctx.store.tx(async (t) => ({ events: await t.environmentEvents(id, query) }));
899
+ // One read returns at most the Tenant's Runtime limit `environmentEventItems`; `from` reads on.
900
+ return ctx.store.tx(async (t) => ({
901
+ events: await t.environmentEvents(id, { ...query, limit: (await readRuntimeLimits(t)).environmentEventItems }),
902
+ }));
882
903
  }
883
904
  /**
884
905
  * The owner guard (Q7): refuses a session of `ownerUserId` on an Environment guarded for
@@ -935,11 +956,14 @@ export async function checkEnvironmentTurn(t, session, scope, ctx) {
935
956
  code: "environment_owner_mismatch",
936
957
  });
937
958
  // The harness the turn would run on (R4.1, Q9): a pod about to wake runs its recipe's. A
938
- // session never moves to another harness: its state is that harness's.
959
+ // session never moves to another harness: its state is that harness's. An Environment's
960
+ // harness never changes since protocol 14, so this refuses a session whose Environment id was
961
+ // deleted and made again with another harness, and an Environment changed in place before
962
+ // protocol 14 whose recipe (a pod's, at its wake) names another harness than it applied.
939
963
  const waking = environment.pod !== undefined && (environment.pod.desired !== "running" || environment.pod.observed === "failed");
940
964
  const runs = waking ? environment.recipe.harness : environment.applied.harness;
941
965
  if (!isWorkflowManifest(session.manifest) && runs !== harnessOf(session))
942
- fail(409, `Environment ${id} now runs the ${runs} harness, and this session runs ${harnessOf(session)}: open a new session to run on it.`, {
966
+ fail(409, `Environment ${id} runs the ${runs} harness, and this session runs ${harnessOf(session)}: open a new session to run on it.`, {
943
967
  code: "harness_mismatch",
944
968
  details: { harness: harnessOf(session), environmentHarness: runs },
945
969
  });
@@ -31,7 +31,7 @@ export interface StartEphemeralRuntimeOptions {
31
31
  /** When true, close() leaves hostRoot on disk. */
32
32
  retainRoot?: boolean;
33
33
  /**
34
- * Trusted issuers whose JWTs the Tenant API accepts (Host feature `trusted-issuers`): the
34
+ * Trusted issuers whose JWTs the Runtime API accepts (Host feature `trusted-issuers`): the
35
35
  * issuers of an identity file (`parseIdentityFile`), or ones already built with
36
36
  * `createTrustedIssuers`. Default none.
37
37
  */
@@ -25,6 +25,7 @@ import { hostPaths, tenantPaths } from "./paths.js";
25
25
  import { hostPrincipals } from "./principals.js";
26
26
  import { openTenantRuntime } from "./runtime.js";
27
27
  import { createTrustedIssuers } from "./issuers.js";
28
+ import { hostTuning } from "../tuning.js";
28
29
  const nodeRequire = createRequire(import.meta.url);
29
30
  function coreVersion() {
30
31
  try {
@@ -127,7 +128,7 @@ export async function startEphemeralRuntime(options) {
127
128
  await host?.close();
128
129
  await module.close();
129
130
  await streams.close();
130
- await ownPool?.end({ timeout: 5 });
131
+ await ownPool?.end({ timeout: hostTuning().postgres.closeTimeoutSeconds });
131
132
  if (!options.retainRoot)
132
133
  rmSync(hostRoot, { recursive: true, force: true });
133
134
  };
@@ -16,7 +16,7 @@
16
16
  * transcript on the row too and checks the fold against it at every segment start.
17
17
  */
18
18
  import type { LiveEvent } from "@nylorun/core/contracts";
19
- export { CHUNK_BYTES, TranscriptFoldError, applyUpdate, applyUpdates, transcriptOf, transcriptUpdates, withTranscript, type TranscriptUpdate, } from "@nylorun/core/harness-api";
19
+ export { TranscriptFoldError, applyUpdate, applyUpdates, transcriptOf, transcriptUpdates, withTranscript, type TranscriptUpdate, } from "@nylorun/core/harness-api";
20
20
  /** Where a session's fold starts. */
21
21
  export interface SessionHistory {
22
22
  /** The fold reads the record from this seq: a snapshot at or before the turn's start. */
@@ -1,5 +1,5 @@
1
1
  import { applyUpdate, transcriptOf } from "@nylorun/core/harness-api";
2
- export { CHUNK_BYTES, TranscriptFoldError, applyUpdate, applyUpdates, transcriptOf, transcriptUpdates, withTranscript, } from "@nylorun/core/harness-api";
2
+ export { TranscriptFoldError, applyUpdate, applyUpdates, transcriptOf, transcriptUpdates, withTranscript, } from "@nylorun/core/harness-api";
3
3
  let shadow = false;
4
4
  /** Shadow mode: keep the transcript on the session row and check the fold against it. */
5
5
  export function transcriptShadow() {
@@ -5,8 +5,8 @@ export declare const HOST_TOKEN_TYP = "nylorun-host+jwt";
5
5
  export declare const HOST_TOKEN_AUD = "nylorun-harness-api";
6
6
  export declare const EGRESS_TOKEN_TYP = "nylorun-egress+jwt";
7
7
  export declare const EGRESS_TOKEN_AUD = "nylorun-egress";
8
- /** How long a host or egress token lives. The engine renews them before. */
9
- export declare const HOST_TOKEN_TTL_SECONDS: number;
8
+ /** How long a host or egress token lives (`keys.hostTokenTtlSeconds`). The engine renews them before. */
9
+ export declare const hostTokenTtlSeconds: () => number;
10
10
  /** What a verified host or egress token says. */
11
11
  export interface HostClaims {
12
12
  readonly tenantId: string;
@@ -37,7 +37,7 @@ export interface EgressTokenRequest {
37
37
  /** The host epoch the join set. */
38
38
  readonly epoch: number;
39
39
  readonly podUid: string;
40
- /** Seconds. Default and maximum `HOST_TOKEN_TTL_SECONDS`. */
40
+ /** Seconds. Default and maximum `keys.hostTokenTtlSeconds`. */
41
41
  readonly ttl?: number;
42
42
  }
43
43
  /**