@nylorun/runtime 0.23.0-beta → 0.25.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 (301) hide show
  1. package/CHANGELOG.md +252 -0
  2. package/README.md +87 -28
  3. package/dist/adapters/execution/restate.js +1 -1
  4. package/dist/adapters/replication/pgoutput.d.ts +2 -0
  5. package/dist/adapters/replication/pgoutput.js +6 -3
  6. package/dist/adapters/sandbox/local.d.ts +9 -1
  7. package/dist/adapters/sandbox/local.js +16 -4
  8. package/dist/adapters/sandbox/virtual.js +4 -1
  9. package/dist/api/a2a/card.d.ts +11 -1
  10. package/dist/api/a2a/card.js +26 -2
  11. package/dist/api/a2a/endpoint.js +9 -3
  12. package/dist/api/a2a/protocol.d.ts +25 -2
  13. package/dist/api/a2a/protocol.js +20 -3
  14. package/dist/api/a2a/routes.d.ts +6 -1
  15. package/dist/api/a2a/routes.js +67 -9
  16. package/dist/api/a2a/tasks.js +2 -1
  17. package/dist/api/ag-ui/endpoint.js +8 -3
  18. package/dist/api/ag-ui/history.d.ts +6 -1
  19. package/dist/api/ag-ui/history.js +39 -3
  20. package/dist/api/ag-ui/routes.d.ts +4 -1
  21. package/dist/api/ag-ui/routes.js +63 -20
  22. package/dist/api/ag-ui/translate.js +3 -2
  23. package/dist/api/components.d.ts +5327 -1091
  24. package/dist/api/components.js +40 -16
  25. package/dist/api/gate/keys.d.ts +2 -1
  26. package/dist/api/gate/keys.js +14 -1
  27. package/dist/api/gate/routes.d.ts +6 -0
  28. package/dist/api/gate/routes.js +52 -3
  29. package/dist/api/http/app.js +17 -6
  30. package/dist/api/http/body.d.ts +3 -3
  31. package/dist/api/http/body.js +7 -7
  32. package/dist/api/http/define.d.ts +6 -0
  33. package/dist/api/http/define.js +13 -1
  34. package/dist/api/http/respond.js +1 -1
  35. package/dist/api/http/routes/artifacts.js +35 -8
  36. package/dist/api/http/routes/coverage.d.ts +9 -0
  37. package/dist/api/http/routes/coverage.js +34 -0
  38. package/dist/api/http/routes/environment-manifests.d.ts +9 -0
  39. package/dist/api/http/routes/environment-manifests.js +118 -0
  40. package/dist/api/http/routes/environments.d.ts +11 -0
  41. package/dist/api/http/routes/environments.js +332 -0
  42. package/dist/api/http/routes/mcp.d.ts +1 -1
  43. package/dist/api/http/routes/mcp.js +2 -2
  44. package/dist/api/http/routes/me.js +1 -1
  45. package/dist/api/http/routes/reads.d.ts +94 -9
  46. package/dist/api/http/routes/reads.js +54 -5
  47. package/dist/api/http/routes/sessions.js +24 -9
  48. package/dist/api/http/routes/tenant.js +65 -15
  49. package/dist/api/inbound-files.d.ts +65 -0
  50. package/dist/api/inbound-files.js +115 -0
  51. package/dist/api/openapi.js +48 -28
  52. package/dist/artifacts/disk.d.ts +59 -0
  53. package/dist/artifacts/disk.js +569 -0
  54. package/dist/artifacts/export.d.ts +1 -1
  55. package/dist/artifacts/export.js +2 -3
  56. package/dist/artifacts/folders.d.ts +6 -3
  57. package/dist/artifacts/folders.js +17 -12
  58. package/dist/artifacts/media-types.d.ts +15 -0
  59. package/dist/artifacts/media-types.js +56 -4
  60. package/dist/artifacts/parts.d.ts +12 -3
  61. package/dist/artifacts/parts.js +15 -4
  62. package/dist/artifacts/service.d.ts +2 -1
  63. package/dist/artifacts/service.js +6 -1
  64. package/dist/artifacts/workspace.d.ts +5 -5
  65. package/dist/artifacts/workspace.js +7 -19
  66. package/dist/core/flow-host.d.ts +3 -4
  67. package/dist/core/flow-host.js +5 -2
  68. package/dist/core/provider.d.ts +3 -2
  69. package/dist/core/provider.js +5 -37
  70. package/dist/core/sandbox-routes.d.ts +2 -21
  71. package/dist/core/sandbox-routes.js +11 -44
  72. package/dist/core/turn-manifest.d.ts +3 -2
  73. package/dist/core/turn-manifest.js +29 -5
  74. package/dist/execution/types.d.ts +1 -1
  75. package/dist/gates/contract.d.ts +3 -24
  76. package/dist/gates/contract.js +1 -36
  77. package/dist/gates/egress-credentials.d.ts +9 -0
  78. package/dist/gates/egress-credentials.js +11 -4
  79. package/dist/gates/egress.d.ts +22 -1
  80. package/dist/gates/egress.js +116 -10
  81. package/dist/gates/fixture-model.d.ts +28 -0
  82. package/dist/gates/fixture-model.js +92 -0
  83. package/dist/gates/handler.js +4 -4
  84. package/dist/gates/http-tool.d.ts +9 -6
  85. package/dist/gates/http-tool.js +11 -17
  86. package/dist/gates/in-process.d.ts +5 -0
  87. package/dist/gates/in-process.js +12 -1
  88. package/dist/gates/journal.d.ts +30 -0
  89. package/dist/gates/journal.js +51 -0
  90. package/dist/gates/mcp-handler.d.ts +4 -0
  91. package/dist/gates/mcp-handler.js +7 -4
  92. package/dist/gates/meter.d.ts +10 -2
  93. package/dist/gates/meter.js +20 -5
  94. package/dist/gates/policy.d.ts +49 -0
  95. package/dist/gates/policy.js +132 -0
  96. package/dist/gates/tenant-vaults.d.ts +7 -7
  97. package/dist/gates/tenant-vaults.js +5 -5
  98. package/dist/gates/tool-calls.d.ts +10 -5
  99. package/dist/gates/tool-calls.js +28 -10
  100. package/dist/gates/tool-client.d.ts +8 -0
  101. package/dist/gates/tool-client.js +27 -11
  102. package/dist/gates/tool-contract.d.ts +11 -69
  103. package/dist/gates/tool-contract.js +1 -85
  104. package/dist/gates/tool-gate.d.ts +1 -1
  105. package/dist/harness/calls.d.ts +14 -7
  106. package/dist/harness/calls.js +30 -24
  107. package/dist/harness/executors.js +12 -9
  108. package/dist/harness/main.js +24 -5
  109. package/dist/harness/pod.d.ts +39 -0
  110. package/dist/harness/pod.js +46 -0
  111. package/dist/harness/service.d.ts +0 -1
  112. package/dist/harness/service.js +17 -9
  113. package/dist/harness/setup.d.ts +29 -0
  114. package/dist/harness/setup.js +174 -0
  115. package/dist/harness/ws-client.js +3 -2
  116. package/dist/harness/ws-port.d.ts +1 -5
  117. package/dist/harness/ws-port.js +1 -5
  118. package/dist/harness-api/in-process.js +3 -3
  119. package/dist/harness-api/record.d.ts +15 -1
  120. package/dist/harness-api/record.js +180 -50
  121. package/dist/harness-api/redact.d.ts +3 -0
  122. package/dist/harness-api/redact.js +45 -0
  123. package/dist/harness-api/server.d.ts +35 -4
  124. package/dist/harness-api/server.js +130 -17
  125. package/dist/harness-api/start.d.ts +10 -3
  126. package/dist/harness-api/start.js +40 -10
  127. package/dist/harness-api/tool-journal.d.ts +105 -0
  128. package/dist/harness-api/tool-journal.js +132 -0
  129. package/dist/harness-api/tool-status.d.ts +13 -0
  130. package/dist/harness-api/tool-status.js +26 -0
  131. package/dist/harness-api/workspace.d.ts +12 -7
  132. package/dist/harness-api/workspace.js +47 -25
  133. package/dist/harness-api/ws-server.d.ts +11 -3
  134. package/dist/harness-api/ws-server.js +94 -15
  135. package/dist/host/gates.d.ts +2 -1
  136. package/dist/host/gates.js +10 -2
  137. package/dist/host/main.js +54 -5
  138. package/dist/host/operate.js +67 -5
  139. package/dist/host/stack-config.d.ts +64 -0
  140. package/dist/host/stack-config.js +62 -2
  141. package/dist/keys/client.js +4 -1
  142. package/dist/keys/keys.d.ts +13 -6
  143. package/dist/keys/keys.js +9 -8
  144. package/dist/management-openapi.json +1264 -2171
  145. package/dist/mcp/connect.d.ts +26 -22
  146. package/dist/mcp/connect.js +30 -34
  147. package/dist/mcp/pool.d.ts +3 -6
  148. package/dist/mcp/pool.js +14 -28
  149. package/dist/mcp/preview.d.ts +3 -3
  150. package/dist/mcp/preview.js +10 -21
  151. package/dist/mcp/snapshot.d.ts +16 -24
  152. package/dist/mcp/snapshot.js +33 -45
  153. package/dist/model/capabilities.d.ts +26 -0
  154. package/dist/model/capabilities.js +44 -0
  155. package/dist/model/catalog.d.ts +34 -3
  156. package/dist/model/catalog.js +38 -2
  157. package/dist/model/models.d.ts +2 -0
  158. package/dist/model/models.js +1 -1
  159. package/dist/model/native-blocks.d.ts +35 -0
  160. package/dist/model/native-blocks.js +170 -0
  161. package/dist/model/pi-model.js +114 -94
  162. package/dist/openapi.json +12822 -3943
  163. package/dist/reads/types.d.ts +16 -3
  164. package/dist/record/append.d.ts +3 -1
  165. package/dist/record/append.js +1 -0
  166. package/dist/record/envelope.d.ts +5 -1
  167. package/dist/record/envelope.js +7 -3
  168. package/dist/record/environment.d.ts +25 -0
  169. package/dist/record/environment.js +44 -0
  170. package/dist/record/index.d.ts +1 -1
  171. package/dist/record/index.js +1 -1
  172. package/dist/sandbox/disk-fs.d.ts +95 -0
  173. package/dist/sandbox/disk-fs.js +206 -0
  174. package/dist/sandbox/join.d.ts +17 -18
  175. package/dist/sandbox/join.js +46 -15
  176. package/dist/sandbox/manager.d.ts +47 -21
  177. package/dist/sandbox/manager.js +162 -107
  178. package/dist/sandbox/placement.d.ts +14 -13
  179. package/dist/sandbox/placement.js +31 -14
  180. package/dist/sandbox/pods/client.d.ts +9 -2
  181. package/dist/sandbox/pods/lifecycle.d.ts +36 -27
  182. package/dist/sandbox/pods/lifecycle.js +29 -26
  183. package/dist/sandbox/pods/reconcile.js +18 -12
  184. package/dist/sandbox/pods/spec.d.ts +15 -13
  185. package/dist/sandbox/pods/spec.js +17 -20
  186. package/dist/sandbox/records.d.ts +2 -2
  187. package/dist/sandbox/records.js +3 -3
  188. package/dist/sandbox/resolve.d.ts +37 -27
  189. package/dist/sandbox/resolve.js +73 -41
  190. package/dist/sandbox/runtime-tools.d.ts +9 -0
  191. package/dist/sandbox/runtime-tools.js +16 -0
  192. package/dist/sandbox/session-sandbox.d.ts +4 -6
  193. package/dist/sandbox/session-sandbox.js +47 -94
  194. package/dist/sandbox/setup.d.ts +64 -0
  195. package/dist/sandbox/setup.js +160 -0
  196. package/dist/sandbox/share.d.ts +21 -26
  197. package/dist/sandbox/share.js +36 -63
  198. package/dist/sandbox/skills.d.ts +8 -6
  199. package/dist/sandbox/skills.js +24 -23
  200. package/dist/sandbox/tenant-config.d.ts +12 -14
  201. package/dist/sandbox/tenant-config.js +12 -17
  202. package/dist/sandbox/types.d.ts +6 -0
  203. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  204. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  205. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  206. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  207. package/dist/store/postgres/drizzle/0024_flat_manifest.sql +7 -0
  208. package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
  209. package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
  210. package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
  211. package/dist/store/postgres/drizzle/meta/_journal.json +56 -0
  212. package/dist/store/postgres/reads.js +123 -17
  213. package/dist/store/postgres/record-writer.d.ts +4 -4
  214. package/dist/store/postgres/record-writer.js +9 -9
  215. package/dist/store/postgres/record.d.ts +7 -0
  216. package/dist/store/postgres/record.js +23 -2
  217. package/dist/store/postgres/schema.d.ts +1126 -736
  218. package/dist/store/postgres/schema.js +136 -73
  219. package/dist/store/postgres/store.js +195 -67
  220. package/dist/store/types.d.ts +165 -50
  221. package/dist/tenant/advance.d.ts +7 -6
  222. package/dist/tenant/advance.js +118 -42
  223. package/dist/tenant/artifact-tool.d.ts +1 -1
  224. package/dist/tenant/artifact-tool.js +46 -26
  225. package/dist/tenant/commands.d.ts +15 -2
  226. package/dist/tenant/commands.js +115 -14
  227. package/dist/tenant/context.d.ts +56 -24
  228. package/dist/tenant/context.js +4 -15
  229. package/dist/tenant/effects.d.ts +1 -1
  230. package/dist/tenant/effects.js +44 -29
  231. package/dist/tenant/environment-manifests.d.ts +57 -0
  232. package/dist/tenant/environment-manifests.js +200 -0
  233. package/dist/tenant/environments.d.ts +174 -0
  234. package/dist/tenant/environments.js +1044 -0
  235. package/dist/tenant/harness-tokens.d.ts +21 -0
  236. package/dist/tenant/harness-tokens.js +61 -0
  237. package/dist/tenant/host-token.d.ts +1 -1
  238. package/dist/tenant/host-token.js +2 -4
  239. package/dist/tenant/identity-file.d.ts +4 -4
  240. package/dist/tenant/identity-file.js +17 -8
  241. package/dist/tenant/issuers.js +3 -3
  242. package/dist/tenant/model-setting.d.ts +14 -0
  243. package/dist/tenant/model-setting.js +51 -0
  244. package/dist/tenant/principals.js +1 -1
  245. package/dist/tenant/reset.js +3 -3
  246. package/dist/tenant/runtime.d.ts +6 -1
  247. package/dist/tenant/runtime.js +7 -1
  248. package/dist/tenant/session.d.ts +3 -3
  249. package/dist/tenant/session.js +0 -14
  250. package/dist/tenant/sessions.d.ts +18 -14
  251. package/dist/tenant/sessions.js +99 -143
  252. package/dist/tenant/signing-keys.d.ts +5 -1
  253. package/dist/tenant/skill-tool.d.ts +3 -3
  254. package/dist/tenant/skill-tool.js +6 -11
  255. package/dist/tenant/status.js +3 -3
  256. package/dist/tenant/sweep.js +13 -7
  257. package/dist/tenant/tool-results.js +1 -1
  258. package/dist/tenant/tool-search.d.ts +7 -11
  259. package/dist/tenant/tool-search.js +8 -5
  260. package/dist/tenant/transcript.d.ts +245 -36
  261. package/dist/tenant/transcript.js +64 -12
  262. package/dist/tenant/turn-timing.d.ts +43 -0
  263. package/dist/tenant/turn-timing.js +39 -0
  264. package/dist/tenant/types.d.ts +2 -0
  265. package/dist/traces/ids.d.ts +41 -0
  266. package/dist/traces/ids.js +81 -0
  267. package/dist/traces/key.d.ts +6 -0
  268. package/dist/traces/key.js +37 -0
  269. package/dist/traces/map.d.ts +113 -0
  270. package/dist/traces/map.js +289 -0
  271. package/dist/traces/options.d.ts +7 -0
  272. package/dist/traces/options.js +1 -0
  273. package/dist/traces/otlp.d.ts +42 -0
  274. package/dist/traces/otlp.js +84 -0
  275. package/dist/traces/projector.d.ts +58 -0
  276. package/dist/traces/projector.js +200 -0
  277. package/dist/traces/read.d.ts +23 -0
  278. package/dist/traces/read.js +16 -0
  279. package/dist/vault/coverage.d.ts +26 -0
  280. package/dist/vault/coverage.js +62 -0
  281. package/dist/vault/error.d.ts +11 -1
  282. package/dist/vault/error.js +5 -1
  283. package/dist/vault/host-model.d.ts +4 -1
  284. package/dist/vault/host-model.js +10 -2
  285. package/dist/vault/service.d.ts +92 -48
  286. package/dist/vault/service.js +462 -376
  287. package/dist/vault/sources.d.ts +5 -16
  288. package/dist/vault/sources.js +6 -8
  289. package/dist/version.d.ts +1 -1
  290. package/dist/version.js +2 -2
  291. package/package.json +13 -13
  292. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  293. package/dist/api/http/routes/sandboxes.js +0 -153
  294. package/dist/api/http/routes/vaults.d.ts +0 -10
  295. package/dist/api/http/routes/vaults.js +0 -139
  296. package/dist/record/sandbox.d.ts +0 -25
  297. package/dist/record/sandbox.js +0 -43
  298. package/dist/sandbox/capability.d.ts +0 -3
  299. package/dist/sandbox/capability.js +0 -10
  300. package/dist/tenant/sandboxes.d.ts +0 -123
  301. package/dist/tenant/sandboxes.js +0 -566
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A pod sandbox's lifecycle (blueprint D34, D36), as one pure decision: from the row, the
2
+ * A pod Environment's lifecycle (blueprint D34, D36), as one pure decision: from the row, the
3
3
  * Tenant's lifecycle settings, the Sandbox's status as the sandboxes service sees it and the
4
4
  * clock, what to ask of the service, what to write to the row, which events to record, and
5
5
  * when to look again. `reconcile.ts` gathers the facts, carries the decision out and runs it
@@ -12,42 +12,51 @@
12
12
  * - **Every change** the Runtime asks for bumps `rev`; the `PUT` carries `opId =
13
13
  * <name>.r<rev>`, so a repeated apply is a no-op at the service, and a Sandbox whose `opId`
14
14
  * is not the row's is applied again.
15
- * - **Idle** (`trigger: idle`): no turn on an attached session, and none ended within the
16
- * Tenant's `limits.idle` → `Suspended` (`sandbox.suspended`, reason `idle`). The volume is
17
- * kept; the next turn start asks for `Running` again.
18
- * - **TTL**: the Sandbox carries `shutdownTime = createdAt + lifecycle.ttl` and the Tenant's
19
- * `onExpiry` as its `shutdownPolicy`. Once it passes, the sandbox is `expired`
20
- * (`sandbox.expired`), its host epoch moves (its tokens stop working) and turns are refused
21
- * (`sandbox_expired`); with `onExpiry: delete` it is deleted. A `PUT` with a longer TTL moves
22
- * `expiresAt`, and the next apply revives it with the later `shutdownTime`.
15
+ * - **Idle** (`trigger: idle`): no turn on a session that names it, and none ended within its
16
+ * `lifecycle.idle` (at most the Tenant's `limits.idle`) → `Suspended` (`environment.suspended`,
17
+ * reason `idle`). The volume is kept; the next turn start asks for `Running` again.
23
18
  * - **Lost**: an incarnation that has run (a pod joined) whose Sandbox is gone or whose volume
24
- * is missing is `lost` (`sandbox.lost`): its host epoch moves, its Sandbox is deleted, and
25
- * only a reset brings the sandbox back. agent-sandbox recreates a deleted claim empty, so the
26
- * engine's join also checks the id it keeps on the volume (`join.ts`): a new volume is lost
27
- * too. A pod replaced on the same volume is not lost: the new pod's join bumps the epoch
28
- * (`sandbox.relaunched`).
19
+ * is missing is `lost` (`environment.lost`): its host epoch moves, its Sandbox is deleted, and
20
+ * only a reset brings the Environment back. agent-sandbox recreates a deleted claim empty, so
21
+ * the engine's join also checks the id it keeps on the volume (`join.ts`): a new volume is
22
+ * lost too. A pod replaced on the same volume is not lost: the new pod's join bumps the epoch
23
+ * (`environment.relaunched`).
29
24
  * - **Delete** (`desired: deleted`): the Sandbox is deleted, and the row once it is gone.
30
- * - **Reset** (`tenant/sandboxes.ts`) bumps the volume generation: a new name, so a new
25
+ * - **Reset** (`tenant/environments.ts`) bumps the volume generation: a new name, so a new
31
26
  * Sandbox and volume; the old name is `retiring` and deleted here.
27
+ *
28
+ * - **Setup** (R2d step 2): a failed setup (`sandbox/setup.ts`) leaves the pod `failed` with a
29
+ * reason that starts `setup:`, even while the pod is ready, until a turn start asks it to run.
30
+ * - **Image** (Q14): the first ready pod's workload image is recorded by digest (`imageDigest`),
31
+ * and every later apply runs that digest, so a stop, relaunch or reset never pulls another
32
+ * image under the same tag. An image id with no repository (a locally loaded image) pins nothing.
33
+ *
34
+ * An unused Environment is deleted by the Tenant sweep (`lifecycle.deleteAfter`), not here.
32
35
  */
33
- import type { SandboxEventPayload, SandboxEventType } from "@nylorun/core/contracts";
34
- import type { SandboxPodPatch, SandboxPodState, SandboxResource } from "../../store/types.js";
36
+ import type { EnvironmentEventPayload, EnvironmentEventType } from "@nylorun/core/contracts";
37
+ import type { Environment, SandboxPodPatch, SandboxPodState } from "../../store/types.js";
35
38
  import type { PodStatus } from "./client.js";
39
+ /**
40
+ * The pullable reference of the image id a container status names (`repo@sha256:…`), or
41
+ * undefined for one without a repository, such as an image loaded onto the node.
42
+ */
43
+ export declare function imageDigestOf(imageID: string | undefined): string | undefined;
44
+ /** A setup failure's `reason` starts with this (`sandbox/setup.ts`); it is sticky. */
45
+ export declare const SETUP_FAILED = "setup:";
36
46
  /** A pod not ready this long after it was asked to run has failed. */
37
47
  export declare const READY_DEADLINE_MS: number;
38
48
  /** How soon to look again while the pod changes. */
39
49
  export declare const POLL_MS = 2000;
40
50
  /** How soon to look again when the sandboxes service does not answer. */
41
51
  export declare const UNREACHABLE_RETRY_MS = 5000;
42
- /** The Tenant's lifecycle settings (D36), read each time a decision is made. */
52
+ /** The lifecycle settings (D36), read each time a decision is made. */
43
53
  export interface PodLifecycleConfig {
44
- readonly onExpiry: "retain" | "delete";
45
54
  readonly idleMs: number;
46
55
  }
47
- /** Why the decision is made: a reconcile, or one of the sandbox's timers firing. */
48
- export type PodTrigger = "reconcile" | "idle" | "ttl";
49
- export type PodTimer = "idle" | "ttl";
50
- export type PodRow = SandboxResource & {
56
+ /** Why the decision is made: a reconcile, or the idle timer firing. */
57
+ export type PodTrigger = "reconcile" | "idle";
58
+ export type PodTimer = "idle";
59
+ export type PodRow = Environment & {
51
60
  readonly pod: SandboxPodState;
52
61
  };
53
62
  export interface PodFacts {
@@ -57,14 +66,14 @@ export interface PodFacts {
57
66
  readonly status: PodStatus | undefined;
58
67
  /** The retiring Sandbox's status, when the row names one. */
59
68
  readonly retiring?: PodStatus | undefined;
60
- /** A session attached to the sandbox has a turn running. */
69
+ /** A session that names the Environment has a turn running. */
61
70
  readonly busy: boolean;
62
71
  readonly now: number;
63
72
  readonly trigger: PodTrigger;
64
73
  }
65
- export interface PodEvent<T extends SandboxEventType = SandboxEventType> {
74
+ export interface PodEvent<T extends EnvironmentEventType = EnvironmentEventType> {
66
75
  readonly type: T;
67
- readonly payload: SandboxEventPayload<T>;
76
+ readonly payload: EnvironmentEventPayload<T>;
68
77
  }
69
78
  export interface PodDecision {
70
79
  /** Apply the row's spec (after `patch`) in this mode; `rotateJoin` mints a new join token. */
@@ -76,7 +85,7 @@ export interface PodDecision {
76
85
  readonly delete: readonly string[];
77
86
  readonly patch: SandboxPodPatch;
78
87
  readonly events: readonly PodEvent[];
79
- /** The Sandbox is gone and the sandbox deleted: remove the row. */
88
+ /** The Sandbox is gone and the Environment deleted: remove the row. */
80
89
  readonly removeRow?: true;
81
90
  /** Look again this soon. */
82
91
  readonly retryAfterMs?: number;
@@ -1,3 +1,16 @@
1
+ /**
2
+ * The pullable reference of the image id a container status names (`repo@sha256:…`), or
3
+ * undefined for one without a repository, such as an image loaded onto the node.
4
+ */
5
+ export function imageDigestOf(imageID) {
6
+ const id = imageID?.replace(/^docker-pullable:\/\//, "");
7
+ if (!id)
8
+ return undefined;
9
+ const at = id.indexOf("@sha256:");
10
+ return at > 0 && /^[0-9a-f]{64}$/.test(id.slice(at + 8)) ? id : undefined;
11
+ }
12
+ /** A setup failure's `reason` starts with this (`sandbox/setup.ts`); it is sticky. */
13
+ export const SETUP_FAILED = "setup:";
1
14
  /** A pod not ready this long after it was asked to run has failed. */
2
15
  export const READY_DEADLINE_MS = 5 * 60_000;
3
16
  /** How soon to look again while the pod changes. */
@@ -67,24 +80,6 @@ export function decide(facts) {
67
80
  again(POLL_MS);
68
81
  return done();
69
82
  }
70
- const expiresAt = pod.expiresAt === undefined ? undefined : Date.parse(pod.expiresAt);
71
- if (expiresAt !== undefined && now >= expiresAt) {
72
- if (pod.observed !== "expired") {
73
- patch.observed = "expired";
74
- patch.hostEpoch = pod.hostEpoch + 1;
75
- events.push(event("sandbox.expired", { onExpiry: facts.config.onExpiry }));
76
- }
77
- if (facts.config.onExpiry === "delete") {
78
- patch.desired = "deleted";
79
- patch.rev = pod.rev + 1;
80
- if (status.exists && !status.deleting)
81
- deletes.push(pod.k8sName);
82
- again(POLL_MS);
83
- }
84
- return done();
85
- }
86
- if (expiresAt !== undefined)
87
- arm.push({ timer: "ttl", at: expiresAt });
88
83
  let desired = pod.desired;
89
84
  let rev = pod.rev;
90
85
  if (facts.trigger === "idle" && desired === "running" && !facts.busy) {
@@ -96,7 +91,7 @@ export function decide(facts) {
96
91
  patch.desired = desired;
97
92
  patch.rev = rev;
98
93
  patch.hostEpoch = pod.hostEpoch + 1;
99
- events.push(event("sandbox.suspended", { reason: "idle" }));
94
+ events.push(event("environment.suspended", { reason: "idle" }));
100
95
  }
101
96
  else
102
97
  arm.push({ timer: "idle", at: due });
@@ -104,14 +99,14 @@ export function decide(facts) {
104
99
  // An incarnation a pod has joined, whose Sandbox or volume is gone: lost.
105
100
  if (pod.podUid !== undefined && (!status.exists || status.deleting || status.volume === "missing")) {
106
101
  const reason = !status.exists
107
- ? "The sandbox's pod and volume were deleted outside the Runtime"
102
+ ? "The Environment's pod and volume were deleted outside the Runtime"
108
103
  : status.deleting
109
- ? "The sandbox is being deleted outside the Runtime"
110
- : "The sandbox's volume is gone";
104
+ ? "The Environment's pod is being deleted outside the Runtime"
105
+ : "The Environment's volume is gone";
111
106
  patch.observed = "lost";
112
107
  patch.reason = reason;
113
108
  patch.hostEpoch = (patch.hostEpoch ?? pod.hostEpoch) + 1;
114
- events.push(event("sandbox.lost", { reason }));
109
+ events.push(event("environment.lost", { reason }));
115
110
  if (status.exists && !status.deleting)
116
111
  deletes.push(pod.k8sName);
117
112
  if (status.exists)
@@ -119,7 +114,7 @@ export function decide(facts) {
119
114
  return done();
120
115
  }
121
116
  if (status.deleting) {
122
- // The previous incarnation of this name is still going (a sandbox deleted and created again).
117
+ // The previous incarnation of this name is still going (an Environment deleted and created again).
123
118
  again(POLL_MS);
124
119
  return done();
125
120
  }
@@ -135,7 +130,7 @@ export function decide(facts) {
135
130
  patch.startedAt = new Date(now).toISOString();
136
131
  patch.reason = null;
137
132
  }
138
- // A pod started by this apply (a create, a resume) is the sandbox's first, not a relaunch.
133
+ // A pod started by this apply (a create, a resume) is the Environment's first, not a relaunch.
139
134
  if (desired === "running" && pod.podUid !== undefined)
140
135
  patch.podUid = null;
141
136
  again(POLL_MS);
@@ -144,11 +139,19 @@ export function decide(facts) {
144
139
  });
145
140
  }
146
141
  if (desired === "running") {
142
+ // A failed setup (R2d step 2) stays failed until a turn start asks the pod to run again,
143
+ // though the pod itself is ready: its engine takes no turn meanwhile.
144
+ if (pod.observed === "failed" && pod.reason?.startsWith(SETUP_FAILED))
145
+ return done();
147
146
  if (status.ready) {
148
147
  if (pod.observed !== "running") {
149
148
  patch.observed = "running";
150
149
  patch.reason = null;
151
150
  }
151
+ // The image it runs, pinned by digest from now on (Q14).
152
+ const digest = pod.imageDigest === undefined ? imageDigestOf(status.imageID) : undefined;
153
+ if (digest !== undefined)
154
+ patch.imageDigest = digest;
152
155
  return done();
153
156
  }
154
157
  const startedAt = Date.parse(pod.startedAt ?? row.updatedAt);
@@ -159,7 +162,7 @@ export function decide(facts) {
159
162
  `The pod was not ready within ${READY_DEADLINE_MS / 60_000} minutes`;
160
163
  patch.observed = "failed";
161
164
  patch.reason = reason;
162
- events.push(event("sandbox.failed", { reason }));
165
+ events.push(event("environment.failed", { reason }));
163
166
  return done();
164
167
  }
165
168
  if (pod.observed !== "creating")
@@ -15,8 +15,10 @@
15
15
  * infrastructure errors throw; Restate retries those.
16
16
  */
17
17
  import { randomBytes } from "node:crypto";
18
- import { readSandboxConfig } from "../tenant-config.js";
18
+ import { effectiveSandboxConfig, readSandboxConfig } from "../tenant-config.js";
19
+ import { podSandboxSpecOf } from "../resolve.js";
19
20
  import { sha256Hex } from "../join.js";
21
+ import { recoverLostVolume } from "../../artifacts/disk.js";
20
22
  import { decide } from "./lifecycle.js";
21
23
  import { podLifecycleConfig, podSpecOf } from "./spec.js";
22
24
  export async function reconcileSandbox(ctx, sandboxId, trigger) {
@@ -24,11 +26,11 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
24
26
  if (!pods || ctx.closed)
25
27
  return {};
26
28
  const read = await ctx.store.tx(async (t) => {
27
- const row = await t.sandboxResource(sandboxId);
29
+ const row = await t.environment(sandboxId);
28
30
  if (!row || row.kind !== "pod" || !row.pod)
29
31
  return undefined;
30
32
  const config = await readSandboxConfig(t);
31
- const busy = (await t.sessionsOnSandbox(sandboxId)).some((session) => session.activeTurnId != null);
33
+ const busy = (await t.sessionsOnEnvironment(sandboxId)).some((session) => session.activeTurnId != null);
32
34
  return { row: row, config, busy };
33
35
  });
34
36
  if (!read)
@@ -47,7 +49,7 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
47
49
  return undefined;
48
50
  }
49
51
  };
50
- const lifecycle = podLifecycleConfig(config);
52
+ const lifecycle = podLifecycleConfig(config, row.recipe);
51
53
  const decision = decide({
52
54
  row,
53
55
  config: lifecycle,
@@ -69,10 +71,10 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
69
71
  joinToken = randomBytes(32).toString("base64url");
70
72
  rev += 1;
71
73
  const recorded = await ctx.store.tx(async (t) => {
72
- const current = await t.sandboxResource(sandboxId, { lock: true });
74
+ const current = await t.environment(sandboxId, { lock: true });
73
75
  if (!unchanged(current?.pod))
74
76
  return false;
75
- await t.updateSandboxPod(sandboxId, { joinTokenHash: sha256Hex(joinToken), rev }, new Date().toISOString());
77
+ await t.updateEnvironmentPod(sandboxId, { joinTokenHash: sha256Hex(joinToken), rev }, new Date().toISOString());
76
78
  return true;
77
79
  });
78
80
  if (!recorded)
@@ -95,12 +97,13 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
95
97
  }
96
98
  if (decision.apply) {
97
99
  const spec = podSpecOf({
98
- sandboxId,
99
- spec: row.spec,
100
+ environmentId: sandboxId,
101
+ spec: podSandboxSpecOf(row.applied, effectiveSandboxConfig(config).limits.idle),
100
102
  pod: { ...row.pod, rev },
101
103
  config,
102
104
  mode: decision.apply.mode,
103
105
  harnessImage: pods.harnessImage,
106
+ harness: row.applied.harness,
104
107
  ...(joinToken ? { joinToken } : {}),
105
108
  });
106
109
  try {
@@ -117,13 +120,13 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
117
120
  }
118
121
  }
119
122
  const written = await ctx.store.tx(async (t) => {
120
- const current = await t.sandboxResource(sandboxId, { lock: true });
123
+ const current = await t.environment(sandboxId, { lock: true });
121
124
  if (!current?.pod)
122
125
  return { kind: "gone" };
123
126
  if (!unchanged(current.pod))
124
127
  return { kind: "changed" };
125
128
  if (decision.removeRow) {
126
- await t.deleteSandboxResource(sandboxId);
129
+ await t.deleteEnvironment(sandboxId);
127
130
  return { kind: "removed" };
128
131
  }
129
132
  const epochMoved = patch.hostEpoch !== undefined && patch.hostEpoch !== null;
@@ -132,9 +135,9 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
132
135
  ...(epochMoved ? { hostEpoch: current.pod.hostEpoch + 1 } : {}),
133
136
  };
134
137
  if (Object.keys(finalPatch).length > 0)
135
- await t.updateSandboxPod(sandboxId, finalPatch, new Date().toISOString());
138
+ await t.updateEnvironmentPod(sandboxId, finalPatch, new Date().toISOString());
136
139
  for (const item of decision.events)
137
- await t.sandboxEvent(sandboxId, item.type, item.payload);
140
+ await t.environmentEvent(sandboxId, item.type, item.payload);
138
141
  if (epochMoved)
139
142
  await t.signal({ type: "host.revoked", sandboxId, epoch: finalPatch.hostEpoch });
140
143
  return { kind: "written" };
@@ -145,6 +148,9 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
145
148
  await ctx.sandbox.removeSandbox(sandboxId).catch(() => undefined);
146
149
  return {};
147
150
  }
151
+ // A lost volume with a disk version starts again from the latest one (R2d step 3).
152
+ if (patch.observed === "lost" && (await recoverLostVolume(ctx, sandboxId).catch(() => false)))
153
+ return { retryAfterMs: 0 };
148
154
  return {
149
155
  ...(decision.retryAfterMs === undefined ? {} : { retryAfterMs: decision.retryAfterMs }),
150
156
  ...(decision.arm.length > 0 ? { arm: decision.arm } : {}),
@@ -1,10 +1,11 @@
1
1
  /**
2
- * The `PUT /v1/pods/{name}` body of a pod sandbox: its resolved spec, the Tenant's lifecycle
2
+ * The `PUT /v1/pods/{name}` body of a pod Environment: its applied recipe, the Tenant's lifecycle
3
3
  * settings (D36, read at every apply) and the incarnation's fences. The pod shape itself is
4
4
  * rendered only by the sandboxes service.
5
5
  */
6
6
  import { type SandboxManifest } from "@nylorun/core/define";
7
- import type { TenantSandboxConfig } from "@nylorun/core/contracts";
7
+ import { type EnvironmentRecipe } from "@nylorun/core/contracts";
8
+ import type { TenantSandboxConfig } from "../tenant-config.js";
8
9
  import type { SandboxPodState } from "../../store/types.js";
9
10
  import type { PodSpec } from "./client.js";
10
11
  import { type PodLifecycleConfig } from "./lifecycle.js";
@@ -14,32 +15,33 @@ export declare const DEFAULT_STOP_GRACE_SECONDS = 10;
14
15
  export declare const DEFAULT_STORAGE_GIB = 5;
15
16
  /** Where the engine in the pod keeps its records, plugin data and home (on the volume). */
16
17
  export declare const POD_HARNESS_ROOT = "/harness";
17
- /** What a pod sandbox's spec holds beyond a virtual one's. */
18
+ /** What a pod's spec holds beyond a virtual sandbox's. */
18
19
  export type PodSandboxSpec = SandboxManifest & {
19
20
  readonly storage?: string;
20
- readonly lifecycle?: {
21
- readonly ttl?: string;
22
- };
23
21
  };
24
- /** The Tenant's lifecycle settings with their defaults. */
25
- export declare function podLifecycleConfig(config: TenantSandboxConfig): PodLifecycleConfig & {
22
+ /**
23
+ * The lifecycle settings with their defaults: the Tenant's (D36), and the Environment's
24
+ * `lifecycle.idle` within the Tenant's `limits.idle` when `recipe` is given.
25
+ */
26
+ export declare function podLifecycleConfig(config: TenantSandboxConfig, recipe?: Pick<EnvironmentRecipe, "lifecycle">): PodLifecycleConfig & {
26
27
  readonly stopGraceSeconds: number;
27
28
  };
28
29
  /** The volume's size in GiB. */
29
30
  export declare function storageGiBOf(spec: PodSandboxSpec): number;
30
- /** The Tenant's default image, when its default sandbox names one. */
31
- export declare function tenantDefaultImage(config: TenantSandboxConfig): string | undefined;
32
31
  export interface PodSpecInput {
33
- readonly sandboxId: string;
32
+ readonly environmentId: string;
34
33
  readonly spec: PodSandboxSpec;
35
34
  readonly pod: SandboxPodState;
36
35
  readonly config: TenantSandboxConfig;
37
36
  readonly mode: "Running" | "Suspended";
38
37
  /** The Runtime image the engine is copied from. */
39
38
  readonly harnessImage: string;
39
+ /**
40
+ * The harness the pod runs (R4.4): ours, copied in from `harnessImage`, or a custom one, which
41
+ * is the workload image itself and runs as its entrypoint.
42
+ */
43
+ readonly harness?: string;
40
44
  /** The join token, when this apply rotates it. */
41
45
  readonly joinToken?: string;
42
46
  }
43
47
  export declare function podSpecOf(input: PodSpecInput): PodSpec;
44
- /** When a sandbox with this TTL, created at `createdAt`, expires. */
45
- export declare function expiresAtOf(createdAt: string, ttl: string | undefined): string | undefined;
@@ -1,9 +1,10 @@
1
1
  /**
2
- * The `PUT /v1/pods/{name}` body of a pod sandbox: its resolved spec, the Tenant's lifecycle
2
+ * The `PUT /v1/pods/{name}` body of a pod Environment: its applied recipe, the Tenant's lifecycle
3
3
  * settings (D36, read at every apply) and the incarnation's fences. The pod shape itself is
4
4
  * rendered only by the sandboxes service.
5
5
  */
6
6
  import { parseSandboxDuration, parseSandboxSize } from "@nylorun/core/define";
7
+ import { DEFAULT_HARNESS } from "@nylorun/core/contracts";
7
8
  import { memoryMiBOf } from "../policy.js";
8
9
  import { opIdOf } from "./lifecycle.js";
9
10
  /** The pod's grace period to stop when the Tenant sets none (D34). */
@@ -12,12 +13,16 @@ export const DEFAULT_STOP_GRACE_SECONDS = 10;
12
13
  export const DEFAULT_STORAGE_GIB = 5;
13
14
  /** Where the engine in the pod keeps its records, plugin data and home (on the volume). */
14
15
  export const POD_HARNESS_ROOT = "/harness";
15
- /** The Tenant's lifecycle settings with their defaults. */
16
- export function podLifecycleConfig(config) {
16
+ /**
17
+ * The lifecycle settings with their defaults: the Tenant's (D36), and the Environment's
18
+ * `lifecycle.idle` within the Tenant's `limits.idle` when `recipe` is given.
19
+ */
20
+ export function podLifecycleConfig(config, recipe) {
17
21
  const stopGrace = config.lifecycle?.stopGrace;
22
+ const tenantIdle = parseSandboxDuration(config.limits?.idle ?? "15m") ?? 15 * 60_000;
23
+ const own = recipe?.lifecycle.idle === undefined ? undefined : parseSandboxDuration(recipe.lifecycle.idle);
18
24
  return {
19
- onExpiry: config.lifecycle?.onExpiry ?? "retain",
20
- idleMs: parseSandboxDuration(config.limits?.idle ?? "15m") ?? 15 * 60_000,
25
+ idleMs: own === undefined ? tenantIdle : Math.min(own, tenantIdle),
21
26
  stopGraceSeconds: stopGrace === undefined
22
27
  ? DEFAULT_STOP_GRACE_SECONDS
23
28
  : Math.max(1, Math.min(30, Math.round((parseSandboxDuration(stopGrace) ?? 10_000) / 1000))),
@@ -28,35 +33,27 @@ export function storageGiBOf(spec) {
28
33
  const bytes = spec.storage === undefined ? undefined : parseSandboxSize(spec.storage);
29
34
  return bytes === undefined ? DEFAULT_STORAGE_GIB : Math.max(1, Math.ceil(bytes / 1024 ** 3));
30
35
  }
31
- /** The Tenant's default image, when its default sandbox names one. */
32
- export function tenantDefaultImage(config) {
33
- const fallback = config.default;
34
- return typeof fallback === "object" ? fallback.image : undefined;
35
- }
36
36
  export function podSpecOf(input) {
37
37
  const { spec, pod, config } = input;
38
38
  const lifecycle = podLifecycleConfig(config);
39
- const image = spec.image ?? tenantDefaultImage(config);
39
+ // Pinned by digest once a pod ran it (Q14).
40
+ const image = pod.imageDigest ?? spec.image;
41
+ const ours = (input.harness ?? DEFAULT_HARNESS) === DEFAULT_HARNESS;
40
42
  return {
41
43
  opId: opIdOf(pod),
42
44
  mode: input.mode,
43
45
  ...(image ? { image } : {}),
44
- harnessImage: input.harnessImage,
46
+ // A custom harness is its image: the pod runs the image's entrypoint, nothing copied in.
47
+ ...(ours ? { harnessImage: input.harnessImage } : { entrypoint: "image" }),
45
48
  cpus: spec.resources?.cpus ?? 1,
46
49
  memoryMiB: memoryMiBOf(spec),
47
50
  storageGiB: storageGiBOf(spec),
48
51
  stopGraceSeconds: lifecycle.stopGraceSeconds,
49
- ...(pod.expiresAt ? { shutdownTime: new Date(pod.expiresAt).toISOString().replace(/\.\d{3}Z$/, "Z") } : {}),
50
- shutdownPolicy: lifecycle.onExpiry === "delete" ? "Delete" : "Retain",
52
+ shutdownPolicy: "Retain",
51
53
  env: {
52
- NYLORUN_SANDBOX_ID: input.sandboxId,
54
+ NYLORUN_SANDBOX_ID: input.environmentId,
53
55
  NYLORUN_HARNESS_ROOT: POD_HARNESS_ROOT,
54
56
  },
55
57
  ...(input.joinToken ? { joinToken: input.joinToken } : {}),
56
58
  };
57
59
  }
58
- /** When a sandbox with this TTL, created at `createdAt`, expires. */
59
- export function expiresAtOf(createdAt, ttl) {
60
- const ms = ttl === undefined ? undefined : parseSandboxDuration(ttl);
61
- return ms === undefined ? undefined : new Date(Date.parse(createdAt) + ms).toISOString();
62
- }
@@ -16,10 +16,10 @@ export declare function workspacePrefix(scope: string): string;
16
16
  /** The key of session `sessionId`'s own workspace. */
17
17
  export declare function sessionWorkspaceKey(scope: string, sessionId: string): string;
18
18
  /**
19
- * The key of sandbox resource `sandboxId`'s workspace: apart from every session key (16 hex
19
+ * The key of sandbox resource `environmentId`'s workspace: apart from every session key (16 hex
20
20
  * characters after the prefix). The virtual backend keeps it in `<sandboxes>/<key>/workspace`.
21
21
  */
22
- export declare function sandboxWorkspaceKey(scope: string, sandboxId: string): string;
22
+ export declare function sandboxWorkspaceKey(scope: string, environmentId: string): string;
23
23
  /** The records in the Tenant's `sandboxes` table. */
24
24
  export declare function storeSandboxRecords(store: SessionStore): SandboxRecords;
25
25
  /**
@@ -21,11 +21,11 @@ export function sessionWorkspaceKey(scope, sessionId) {
21
21
  return workspacePrefix(scope) + createHash("sha256").update(sessionId).digest("hex").slice(0, 16);
22
22
  }
23
23
  /**
24
- * The key of sandbox resource `sandboxId`'s workspace: apart from every session key (16 hex
24
+ * The key of sandbox resource `environmentId`'s workspace: apart from every session key (16 hex
25
25
  * characters after the prefix). The virtual backend keeps it in `<sandboxes>/<key>/workspace`.
26
26
  */
27
- export function sandboxWorkspaceKey(scope, sandboxId) {
28
- return `${workspacePrefix(scope)}sbx-${createHash("sha256").update(sandboxId).digest("hex").slice(0, 16)}`;
27
+ export function sandboxWorkspaceKey(scope, environmentId) {
28
+ return `${workspacePrefix(scope)}sbx-${createHash("sha256").update(environmentId).digest("hex").slice(0, 16)}`;
29
29
  }
30
30
  /** The records in the Tenant's `sandboxes` table. */
31
31
  export function storeSandboxRecords(store) {
@@ -1,37 +1,47 @@
1
1
  /**
2
- * Resolves the sandbox a session asks for when it is opened against its Tenant's configuration.
3
- * Pure: the caller supplies the configuration and who is asking. Every problem is
4
- * reported, not only the first, so one response lists all of them.
2
+ * Resolves an Environment's recipe (`PUT /v1/environments/{id}`) against its Tenant's limits,
3
+ * and the sandbox spec a recipe gives a session's pinned manifest (its `runtime` block) and the pod. Pure: the
4
+ * caller supplies the configuration. Every problem is reported, not only the first, so one
5
+ * response lists all of them.
5
6
  */
6
- import type { SandboxInlineRequest } from "@nylorun/core/contracts";
7
- import type { SandboxManifest } from "@nylorun/core/define";
7
+ import type { EnvironmentFile, EnvironmentKind, EnvironmentLifecycle, EnvironmentRecipe, EnvironmentSecretSlots, EnvironmentSetupCommand } from "@nylorun/core/contracts";
8
+ import { type SandboxManifest } from "@nylorun/core/define";
9
+ import type { PodSandboxSpec } from "./pods/spec.js";
8
10
  import { type EffectiveSandboxConfig } from "./tenant-config.js";
9
- export type SandboxSource = "default" | "inline";
10
- export type SandboxResolution = {
11
- readonly kind: "none";
12
- } | {
13
- readonly kind: "sandbox";
14
- readonly spec: SandboxManifest;
15
- readonly source: SandboxSource;
11
+ /** What a `PUT` asks of the recipe: the request's fields, over the existing recipe's. */
12
+ export interface RecipeRequest {
13
+ readonly image?: string;
14
+ readonly resources?: {
15
+ readonly cpus?: number;
16
+ readonly memory?: string;
17
+ };
18
+ readonly storage?: string;
19
+ readonly network?: {
20
+ readonly allowedHosts?: readonly string[];
21
+ };
22
+ readonly harness?: string;
23
+ readonly lifecycle?: EnvironmentLifecycle;
24
+ readonly setup?: readonly EnvironmentSetupCommand[];
25
+ readonly files?: readonly EnvironmentFile[];
26
+ /** The secret slots (D64): passed through for every kind. */
27
+ readonly secrets?: EnvironmentSecretSlots;
28
+ }
29
+ export type RecipeResolution = {
30
+ readonly kind: "recipe";
31
+ readonly recipe: EnvironmentRecipe;
16
32
  } | {
17
33
  readonly kind: "error";
18
- readonly status: 400 | 403;
19
34
  readonly errors: readonly string[];
20
35
  };
21
- export interface ResolveSandboxInput {
22
- /** `PutSessionRequest.sandbox` other than `{ session }`, which the caller handles. */
23
- readonly request: false | SandboxInlineRequest | undefined;
24
- readonly config: EffectiveSandboxConfig;
25
- /** The request acts for a subject (`Nylorun-Subject`) rather than as the application. */
26
- readonly actingForSubject: boolean;
27
- /**
28
- * The sandbox's kind. Default `virtual`, which runs no images and allows exact host names
29
- * only; a `pod` (F7.2) takes an image and `*.suffix` hosts.
30
- */
31
- readonly kind?: "virtual" | "pod";
32
- }
33
- export declare const SUBJECT_INLINE_MESSAGE = "Defining a sandbox needs an application key. Use the Tenant default, or open the session from the application backend.";
34
- export declare function resolveSandbox(input: ResolveSandboxInput): SandboxResolution;
36
+ /** The recipe `request` asks for an Environment of `kind`, or every reason it cannot have it. */
37
+ export declare function resolveRecipe(kind: EnvironmentKind, request: RecipeRequest, config: EffectiveSandboxConfig): RecipeResolution;
38
+ /**
39
+ * The sandbox spec of a recipe: what a session's `runtime` block pins and the virtual
40
+ * workspace runs. Idle is the Environment's `lifecycle.idle`, else the Tenant's.
41
+ */
42
+ export declare function sandboxSpecOf(recipe: EnvironmentRecipe, tenantIdle: string): SandboxManifest;
43
+ /** The pod's spec of a recipe: its sandbox spec and its volume's size. */
44
+ export declare function podSandboxSpecOf(recipe: EnvironmentRecipe, tenantIdle: string): PodSandboxSpec;
35
45
  /** Whether `host` (a name or `*.suffix` pattern) is inside one of the ceiling's patterns. */
36
46
  export declare function withinCeiling(host: string, ceiling: readonly string[]): boolean;
37
47
  /** Problems with a Tenant configuration, checked when an operator saves it. */