@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
@@ -22,19 +22,32 @@ import { resolveMessageManifest } from "../core/turn-manifest.js";
22
22
  import { canonical } from "../store/canonical.js";
23
23
  import { lockedSession, } from "./context.js";
24
24
  import { fail } from "./http.js";
25
+ import { settleToolRow } from "../harness-api/tool-journal.js";
25
26
  import { accessOf } from "./auth.js";
26
- import { checkSandboxTurn } from "./sandboxes.js";
27
- import { rebaseSessionState, turnManifestOf, variantStore } from "./session.js";
27
+ import { checkEnvironmentTurn } from "./environments.js";
28
+ import { turnManifestOf, variantStore } from "./session.js";
28
29
  import { slimModelEffects } from "./slim.js";
29
30
  import { resolveMessageParts } from "../artifacts/parts.js";
31
+ import { currentModelInputs } from "../model/catalog.js";
32
+ import { endTurn, resumeTurn, startTurn } from "./turn-timing.js";
30
33
  /** A command's identity for idempotency: everything but the request id. */
31
34
  const semantic = (value) => {
32
35
  const { requestId: _, ...body } = value;
33
36
  return canonical(body);
34
37
  };
35
- export async function command(ctx, id, input, scope) {
38
+ export async function command(ctx, id, input, scope,
39
+ /**
40
+ * The W3C trace context the request carried (R7.4): a `message` records it on its
41
+ * `command.message`, and the turn joins the caller's trace. Not part of the command's identity.
42
+ */
43
+ options = {}) {
36
44
  const { store } = ctx;
37
45
  const cascadeCancelIds = [];
46
+ // What the Tenant's model reads, for a message that names a file (protocol 12): read before
47
+ // the session's transaction. Undefined without a model in the vault: nothing is refused.
48
+ const inputs = input.type === "message" && "parts" in input && input.parts.some((part) => part.type === "file")
49
+ ? await currentModelInputs(ctx)
50
+ : undefined;
38
51
  const response = await store.tx(async (t) => {
39
52
  const s = await lockedSession(t, id, accessOf(scope));
40
53
  const command = input;
@@ -53,6 +66,8 @@ export async function command(ctx, id, input, scope) {
53
66
  return existing.response;
54
67
  }
55
68
  let event;
69
+ // A tool call the Tool Gate held, which this `approve` answers (R5 step 2).
70
+ let held;
56
71
  // The turn a `cancel` ended, if one was active.
57
72
  let cancelledTurnId = null;
58
73
  if (command.type === "cancel") {
@@ -73,15 +88,24 @@ export async function command(ctx, id, input, scope) {
73
88
  turnId: cancelledTurnId,
74
89
  });
75
90
  if (cancelledTurnId !== null)
76
- for (const effect of await t.effectsForTurn(id, cancelledTurnId, ["invoking"])) {
91
+ for (const effect of await t.effectsForTurn(id, cancelledTurnId, ["invoking", "held"])) {
92
+ // A tool call ends `cancelled` with its `tool.completed` (R5.1); it may have run.
93
+ if (effect.request.kind === "tool") {
94
+ await settleToolRow(t, s, effect, {
95
+ kind: "failed",
96
+ code: "tool.cancelled",
97
+ message: "The turn was cancelled while this call ran; it may have run.",
98
+ }, { cancelled: true });
99
+ continue;
100
+ }
77
101
  effect.status = "uncertain";
78
102
  effect.error =
79
103
  "Turn cancelled before model outcome was durably recorded";
80
104
  await t.put("effects", effect.request.effectId, effect);
81
105
  }
82
- event = await t.event(id, cancelledTurnId, "turn.cancelled", {
83
- reason: command.reason,
84
- });
106
+ // The cancelled turn's span (R7.1): its duration, agent and totals.
107
+ const ended = cancelledTurnId !== null ? await endTurn(t, s, cancelledTurnId) : {};
108
+ event = await t.event(id, cancelledTurnId, "turn.cancelled", { reason: command.reason, ...ended }, commandWriter(scope));
85
109
  await slimModelEffects(t, id, cancelledTurnId);
86
110
  // The process running the advance aborts it on this signal (control.ts), sent at commit.
87
111
  await t.signal({
@@ -110,21 +134,43 @@ export async function command(ctx, id, input, scope) {
110
134
  }
111
135
  }
112
136
  else {
137
+ // A session opened before protocol 11 (migration 0020) or 13 (migration 0024) keeps its
138
+ // history but takes no turn.
139
+ if (s.closedReason !== undefined)
140
+ fail(409, s.closedReason === "protocol_11"
141
+ ? "This session was opened before protocol 11: open a new session that names an Environment."
142
+ : "This session was opened before protocol 13, on a manifest the Runtime no longer runs: register the agent again (manifest 7) and open a new session.", { code: "session_closed" });
113
143
  if (command.type === "message") {
114
144
  if (!["idle", "completed", "failed", "cancelled"].includes(s.status))
115
145
  fail(409, "Session has active or unresolved work");
116
146
  // Every turn start on a sandbox: it exists, the token's grants reach it, and no other
117
147
  // session's turn holds it.
118
- await checkSandboxTurn(t, s, scope, ctx);
148
+ await checkEnvironmentTurn(t, s, scope, ctx);
119
149
  s.turnStartState = s.state;
120
150
  // A new turn folds from the latest snapshot: a cancel or failure only reverts this turn.
121
151
  if (s.history?.snapshot !== undefined)
122
152
  s.history = { ...s.history, from: s.history.snapshot };
123
153
  s.activeTurnId = randomUUID();
124
- // File parts name artifacts the caller may read; each is pinned to a version now.
154
+ startTurn(s);
155
+ if (options.trace)
156
+ s.turnTrace = options.trace;
157
+ else
158
+ delete s.turnTrace;
159
+ // File parts name artifacts the caller may read; each is pinned to a version now. An
160
+ // agent's model must read each one (a workflow passes them on as data).
125
161
  const parts = "parts" in command
126
- ? await resolveMessageParts(t, command.parts, id, accessOf(scope))
162
+ ? await resolveMessageParts(t, command.parts, id, accessOf(scope), isWorkflowManifest(s.manifest) ? undefined : inputs)
127
163
  : undefined;
164
+ if (parts && parts.refused.length > 0)
165
+ fail(422, `The model does not read ${parts.refused
166
+ .map((part) => `${part.name} (${part.reason})`)
167
+ .join(", ")}.`, { code: "attachment_unsupported", details: { parts: parts.refused } });
168
+ // The files the session's messages named: `read_artifact` reads them again (protocol 12).
169
+ if (parts) {
170
+ const named = parts.pinned.flatMap((part) => (part.type === "file" ? [part.artifactId] : []));
171
+ if (named.length > 0)
172
+ s.fileArtifacts = [...new Set([...(s.fileArtifacts ?? []), ...named])];
173
+ }
128
174
  const data = "data" in command ? command.data : undefined;
129
175
  if (isWorkflowManifest(s.manifest)) {
130
176
  const input = "content" in command
@@ -151,7 +197,6 @@ export async function command(ctx, id, input, scope) {
151
197
  fail(400, resolved.message);
152
198
  }
153
199
  else {
154
- rebaseSessionState(s, resolved.hash);
155
200
  s.checkpoint = createDurableCheckpoint({
156
201
  manifest: resolved.manifest,
157
202
  sessionId: id,
@@ -169,6 +214,25 @@ export async function command(ctx, id, input, scope) {
169
214
  }
170
215
  }
171
216
  }
217
+ else if (command.type === "approve" &&
218
+ (held = await heldCall(t, s, command.interactionId)) !== undefined) {
219
+ // A tool call the Tool Gate held for approval (R5 step 2). Approved: the replay sends it
220
+ // again and the gate runs it. Denied: it fails to the model as `tool.denied`, unsent.
221
+ if (s.status !== "paused")
222
+ fail(409, "Session is not awaiting a response");
223
+ if (command.approved) {
224
+ held.status = "invoking";
225
+ const by = principalOf(scope);
226
+ held.approval = { approved: true, at: new Date().toISOString(), ...(by ? { by } : {}) };
227
+ await t.put("effects", held.request.effectId, held);
228
+ }
229
+ else
230
+ await settleToolRow(t, s, held, {
231
+ kind: "failed",
232
+ code: "tool.denied",
233
+ message: "A person denied this call; it was not sent.",
234
+ });
235
+ }
172
236
  else if (isWorkflowManifest(s.manifest)) {
173
237
  // Approvals owned by linked agent sessions must be answered there (WF-R52).
174
238
  const conflict = await foreignInteractionConflict({
@@ -221,10 +285,13 @@ export async function command(ctx, id, input, scope) {
221
285
  segment: (s.checkpoint?.segment ?? 0) + 1,
222
286
  });
223
287
  }
288
+ // A paused turn goes on: its wait for a person is not its running time (R7.1).
289
+ resumeTurn(s);
224
290
  s.status = "runnable";
225
291
  s.waits = undefined;
226
- // A flow answers each interaction in the same segment: the interaction names the cause.
227
- const cause = command.type !== "message" && isWorkflowManifest(s.manifest)
292
+ // A flow answers each interaction in the same segment, as a held tool call's approval is
293
+ // answered (R5 step 2): the interaction names the cause.
294
+ const cause = command.type !== "message" && (isWorkflowManifest(s.manifest) || held !== undefined)
228
295
  ? command.interactionId
229
296
  : s.checkpoint.segment;
230
297
  const commandWake = {
@@ -232,7 +299,7 @@ export async function command(ctx, id, input, scope) {
232
299
  dedupeKey: `${command.type}:${s.activeTurnId}:${cause}`,
233
300
  };
234
301
  await t.wake(id, commandWake);
235
- event = await t.event(id, s.activeTurnId, `command.${command.type}`, command);
302
+ event = await t.event(id, s.activeTurnId, `command.${command.type}`, command.type === "message" && options.trace ? { ...command, trace: options.trace } : command, commandWriter(scope));
236
303
  }
237
304
  await t.put("sessions", id, s);
238
305
  // Direct cancel of a linked agent fails that node with agent.cancelled (WF-R54). The agent
@@ -272,3 +339,37 @@ export async function command(ctx, id, input, scope) {
272
339
  }
273
340
  return response;
274
341
  }
342
+ /** Core's own commands (R4.9): the flow host's, and a drain's cancels. */
343
+ const CORE_PRINCIPALS = { "flow-host": "flow-host", drain: "core" };
344
+ /**
345
+ * Who sent a command (R4.9), from its credential and never its body: a client over the Runtime
346
+ * API (an application, or a person through it or a token), or core itself.
347
+ */
348
+ export function commandWriter(scope) {
349
+ if (scope.kind === "application" && CORE_PRINCIPALS[scope.principalId] !== undefined)
350
+ return {
351
+ source: { kind: "core", id: "runtime" },
352
+ principal: { kind: CORE_PRINCIPALS[scope.principalId], id: scope.principalId },
353
+ };
354
+ const principal = scope.kind === "subject" || scope.kind === "token"
355
+ ? { kind: "subject", id: scope.subject }
356
+ : { kind: "application", id: scope.kind === "anonymous" ? "anonymous" : scope.principalId };
357
+ return { source: { kind: "client", id: principal.id }, principal };
358
+ }
359
+ /** The held tool call of the session's active turn that `interactionId` names (R5 step 2). */
360
+ async function heldCall(t, s, interactionId) {
361
+ const row = await t.get("effects", interactionId);
362
+ if (row?.status !== "held")
363
+ return undefined;
364
+ if (row.request.sessionId !== s.id || row.request.turnId !== s.activeTurnId)
365
+ return undefined;
366
+ return row;
367
+ }
368
+ /** Who answered: the principal (its subject, when it acts for one), or the token's subject. */
369
+ function principalOf(scope) {
370
+ if (scope.kind === "token")
371
+ return `${scope.issuer}:${scope.subject}`;
372
+ if (scope.kind === "subject")
373
+ return `${scope.principalId}:${scope.subject}`;
374
+ return "principalId" in scope ? scope.principalId : undefined;
375
+ }
@@ -18,7 +18,6 @@ import type { SessionHistory } from "./history.js";
18
18
  import type { IssuerScope, SubjectScope, TenantEnvelope } from "@nylorun/core/contracts";
19
19
  import type { DurableCheckpoint, FlowCheckpoint } from "@nylorun/harness/run";
20
20
  import type { AgentManifest, JsonValue, SandboxManifest } from "@nylorun/core/define";
21
- import type { CredentialSelection } from "@nylorun/core/contracts";
22
21
  import type { OutboxRequest, SessionStore, Tx } from "../store/types.js";
23
22
  import type { FlowLimits } from "../core/limits.js";
24
23
  import type { ModelProvider } from "../core/provider.js";
@@ -40,6 +39,8 @@ import type { SigningKeys } from "./signing-keys.js";
40
39
  import type { BlobStore } from "../blob/index.js";
41
40
  import type { WorkspaceReader } from "../artifacts/workspace.js";
42
41
  import type { RunGrants } from "./run-grants.js";
42
+ import type { TraceOptions } from "../traces/options.js";
43
+ import type { TraceContext } from "../traces/ids.js";
43
44
  export interface Session {
44
45
  id: string;
45
46
  agentId: string;
@@ -49,7 +50,7 @@ export interface Session {
49
50
  manifestHash: string;
50
51
  /**
51
52
  * The hash of the definition the session was opened from; `manifestHash` differs when the
52
- * Runtime pinned its own capabilities beside it (R2b C11). Absent on older sessions.
53
+ * Runtime pinned its `runtime` block on it (R2b C11). Absent on older sessions.
53
54
  */
54
55
  definitionHash?: string;
55
56
  /** Validated turn-manifest variants, keyed by hash (loops.md §4.2). */
@@ -74,26 +75,55 @@ export interface Session {
74
75
  * turn it started (`linkedTurnEnd` in `core/flow-host.ts`).
75
76
  */
76
77
  lastTurnId?: string;
78
+ /** When the active turn started (R7.1, `turn-timing.ts`); absent on a turn started before. */
79
+ turnStartedAt?: string;
80
+ /** How long the active turn has waited paused for a person, in ms (R7.1). */
81
+ pausedMs?: number;
82
+ /** Since when the active turn waits paused (R7.1); absent while it runs. */
83
+ pausedAt?: string;
84
+ /**
85
+ * The trace context the active turn's message arrived with (R7.4): its trace is the caller's,
86
+ * and the context core hands on (to the harness, tools and sandbox commands) is in it.
87
+ */
88
+ turnTrace?: TraceContext;
77
89
  creation: unknown;
78
- vaultIds?: readonly string[];
79
- credentialSelections?: readonly CredentialSelection[];
80
90
  mcpSnapshot?: McpSnapshot;
81
91
  mcpDiagnostics?: readonly McpDiagnostic[];
82
- /** Session id that keys the shared sandbox; absent means this session owns it. */
83
- sandboxOwnerId?: string;
84
92
  /**
85
- * The sandbox resource the session is attached to (`sandbox: { id }`): its workspace is the
86
- * sandbox's, shared with every session attached to it, and it outlives the session.
93
+ * The Environment the session names (`environment`, protocol 11): its sandbox is the
94
+ * session's (the Environment's id keys the workspace) and its vault holds the session's
95
+ * credentials. It outlives the session.
96
+ */
97
+ environmentId?: string;
98
+ /** The Environment's recipe version when the session opened (shown, never enforced). */
99
+ environmentVersion?: number;
100
+ /**
101
+ * The harness that runs the session's turns (R4.1), pinned when it opened: its Environment's
102
+ * harness, else `nylorun`. Absent on a session opened before R4: ours (`harnessOf`).
87
103
  */
88
- sandboxId?: string;
104
+ harness?: string;
89
105
  /**
90
- * The sandbox chosen when the session was opened, resolved against the Tenant's limits, or
91
- * inherited from the session it shares with. Absent when the session has no sandbox or its
92
- * definition declares one (`.sandbox()`).
106
+ * A flow agent's linked session: the session whose turn opened the flow tree. Its sessions
107
+ * share the tree's Environment, and their turns nest in the root's, so they do not hold the
108
+ * Environment against each other (`checkEnvironmentTurn`).
109
+ */
110
+ flowRootId?: string;
111
+ /**
112
+ * The Environment's sandbox spec when the session opened (kinds `virtual` and `pod`).
113
+ * Absent without an Environment, or for kind `none`.
93
114
  */
94
115
  sandbox?: SandboxManifest;
95
- /** Where `sandbox` came from: `sandbox` is a sandbox resource's spec. */
96
- sandboxSource?: "default" | "inline" | "shared" | "sandbox";
116
+ /**
117
+ * Why the session takes no new turn: `protocol_11` for a session opened before protocol 11
118
+ * (migration 0020), `protocol_13` for one opened before protocol 13 (migration 0024). Its
119
+ * history stays readable.
120
+ */
121
+ closedReason?: string;
122
+ /**
123
+ * The artifacts the session's messages named (protocol 12): `read_artifact` reads these
124
+ * Tenant-wide ones beside the session's own. Absent until a message names a file.
125
+ */
126
+ fileArtifacts?: string[];
97
127
  }
98
128
  export type AuthScope = {
99
129
  kind: "application";
@@ -110,7 +140,7 @@ export type AuthScope = {
110
140
  }
111
141
  /**
112
142
  * A trusted issuer's token (Host feature `trusted-issuers`, F9-D12): one subject, with the
113
- * issuer's scopes, agents and sandbox grants, until `expiresAt` (ms). Only its expiry ends it.
143
+ * issuer's scopes, agents and Environment grants, until `expiresAt` (ms). Only its expiry ends it.
114
144
  */
115
145
  | {
116
146
  kind: "token";
@@ -120,8 +150,8 @@ export type AuthScope = {
120
150
  /** The issuer's scopes the token holds, which may add `studio`. */
121
151
  scopes: ReadonlySet<IssuerScope>;
122
152
  agents: ReadonlySet<string> | "*";
123
- /** The sandboxes it reaches: exact ids or `p/*` prefixes. Absent or empty reaches none. */
124
- sandboxes?: readonly string[];
153
+ /** The Environments it reaches: exact ids or `p/*` prefixes. Absent or empty reaches none. */
154
+ environments?: readonly string[];
125
155
  expiresAt: number;
126
156
  /** The token's `jti`, or a hash of it. */
127
157
  tokenId: string;
@@ -151,7 +181,7 @@ export interface TenantContext {
151
181
  */
152
182
  readonly mcp?: McpPool;
153
183
  /**
154
- * The workspace capability (F6.2): the Tenant's SandboxManager with an in-process harness,
184
+ * The workspace feature (F6.2): the Tenant's SandboxManager with an in-process harness,
155
185
  * else the harness that serves workspaces (`harness-api/workspace.ts`).
156
186
  */
157
187
  readonly sandbox: WorkspacePort;
@@ -183,6 +213,11 @@ export interface TenantContext {
183
213
  * this process's `sandbox` today; over the Harness API's `workspace.read` from F6.2.
184
214
  */
185
215
  readonly workspaces: WorkspaceReader;
216
+ /**
217
+ * How the trace route (R7.5) makes spans: the resource and the content the installation exports
218
+ * (R7.2). Absent: `service.name` `nylorun`, no content.
219
+ */
220
+ readonly traces?: TraceOptions;
186
221
  /**
187
222
  * The run token of each session an advance of this process owns (F5): the credential the
188
223
  * HTTP gate clients present for that session's model and MCP calls (`run-grants.ts`).
@@ -232,7 +267,7 @@ export interface TenantContext {
232
267
  abortLocal(sessionId: string, turnId?: string): void;
233
268
  /**
234
269
  * Sandbox pods (F7.2): the sandboxes service and the Runtime image pods copy the engine from.
235
- * Absent without a cluster (`nylorun sandbox enable`): kind `pod` is `sandbox_unavailable`.
270
+ * Absent without a cluster (`nylorun local sandbox enable`): kind `pod` is `environment_unavailable`.
236
271
  */
237
272
  readonly pods?: TenantPods;
238
273
  /**
@@ -275,14 +310,11 @@ export interface Lease {
275
310
  export declare function ownedSession(t: Tx, lease: Lease, id: string): Promise<Session>;
276
311
  /** The session read in its own transaction, or a 404. */
277
312
  export declare function loadSession(ctx: TenantContext, id: string, access?: SessionAccess): Promise<Session>;
278
- /**
279
- * A synchronous lookup over `start` and every session its `sandboxOwnerId` chain names, for
280
- * the sandbox-sharing helpers, which walk that chain synchronously.
281
- */
282
- export declare function sandboxLookup(t: Tx, start: string | undefined): Promise<(id: string) => Session | undefined>;
283
313
  /** The sandboxes service a Tenant drives its pod sandboxes with (`TenantContext.pods`). */
284
314
  export interface TenantPods {
285
315
  readonly client: SandboxesClient;
286
316
  /** The Runtime image the pods copy the engine from (`NYLORUN_SANDBOX_HARNESS_IMAGE`). */
287
317
  readonly harnessImage: string;
288
318
  }
319
+ /** The harness a session runs (R4.1): the one it pinned, else ours. */
320
+ export declare function harnessOf(session: Pick<Session, "harness">): string;
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_HARNESS } from "@nylorun/core/contracts";
1
2
  import { fail } from "./http.js";
2
3
  /**
3
4
  * `access`, when set, limits the request to the owner's sessions of the allowed agents:
@@ -34,19 +35,7 @@ export async function ownedSession(t, lease, id) {
34
35
  export function loadSession(ctx, id, access) {
35
36
  return ctx.store.tx((t) => sessionOf(t, id, access));
36
37
  }
37
- /**
38
- * A synchronous lookup over `start` and every session its `sandboxOwnerId` chain names, for
39
- * the sandbox-sharing helpers, which walk that chain synchronously.
40
- */
41
- export async function sandboxLookup(t, start) {
42
- const found = new Map();
43
- let id = start;
44
- while (id && !found.has(id)) {
45
- const session = await t.get("sessions", id);
46
- if (!session)
47
- break;
48
- found.set(id, session);
49
- id = session.sandboxOwnerId;
50
- }
51
- return (sid) => found.get(sid);
38
+ /** The harness a session runs (R4.1): the one it pinned, else ours. */
39
+ export function harnessOf(session) {
40
+ return session.harness ?? DEFAULT_HARNESS;
52
41
  }
@@ -42,6 +42,6 @@ export declare function recoversModelCalls(ctx: TenantContext): boolean;
42
42
  export declare function resolveNewFlowEffect(ctx: TenantContext, request: HostEffect, signal: AbortSignal, lease: Lease): Promise<EffectResolution>;
43
43
  /**
44
44
  * The credential of an MCP server or HTTP tool request made on behalf of a session: from its
45
- * attached vaults (`vault/sources.ts`).
45
+ * Environment's vault (`vault/sources.ts`).
46
46
  */
47
47
  export declare function authorize(ctx: TenantContext, sessionId: string, request: McpCredentialRequest): Promise<AuthorizeResult>;
@@ -4,9 +4,9 @@ import { mayDispatchMore } from "../core/limits.js";
4
4
  import { sessionCredentials } from "../vault/sources.js";
5
5
  import { isRemoteMcpCall } from "../harness/calls.js";
6
6
  import { isHttpToolCall } from "../gates/http-tool.js";
7
- import { owningSandboxSessionId, sandboxSpecOf, sessionSandboxSpec, } from "../sandbox/share.js";
8
- import { withPlatformTools, withSandboxCapability } from "../sandbox/session-sandbox.js";
9
- import { loadSession, ownedSession, sandboxLookup, sessionOf, } from "./context.js";
7
+ import { sandboxSpecOf } from "../sandbox/share.js";
8
+ import { withRuntimeTools, withSandbox } from "../sandbox/session-sandbox.js";
9
+ import { loadSession, ownedSession, sessionOf, } from "./context.js";
10
10
  import { fail } from "./http.js";
11
11
  import { turnManifestOf } from "./session.js";
12
12
  import { command } from "./commands.js";
@@ -85,11 +85,14 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
85
85
  }
86
86
  }
87
87
  const active = await countActiveFlowWork(t, request.sessionId, request.turnId);
88
+ // When the run journaled the effect (R7.1); a queued effect keeps it once dispatched.
89
+ const startedAt = existing?.startedAt ?? new Date().toISOString();
88
90
  if (!mayDispatchMore(active, ctx.flowLimits)) {
89
91
  if (!existing || existing.status !== "queued")
90
92
  await t.put("effects", request.effectId, {
91
93
  request,
92
94
  status: "queued",
95
+ startedAt,
93
96
  });
94
97
  return resolved({ status: "pending" });
95
98
  }
@@ -101,19 +104,32 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
101
104
  value: {
102
105
  kind: "failed",
103
106
  code: "tool.unavailable",
104
- message: `The tool stage '${request.key ?? request.path ?? ""}' runs your code, and the Runtime runs no code of yours during a session`,
107
+ message: `The tool stage '${request.key ?? request.path ?? ""}' runs your code, but the Runtime runs agents from their manifests alone`,
105
108
  },
106
109
  };
107
- await t.put("effects", request.effectId, { request, status: "completed", outcome });
108
- await t.event(workflow.id, workflow.activeTurnId, "node.started", {
110
+ await t.put("effects", request.effectId, {
111
+ request,
112
+ status: "completed",
113
+ outcome,
114
+ startedAt,
115
+ settledAt: startedAt,
116
+ });
117
+ const node = {
109
118
  path: request.path,
110
119
  kind: "tool",
111
120
  key: request.key,
112
121
  ...(request.iterations !== undefined ? { iterations: request.iterations } : {}),
122
+ };
123
+ await t.event(workflow.id, workflow.activeTurnId, "node.started", node);
124
+ // Nothing ran (R7.1): the stage's outcome row says so.
125
+ await t.event(workflow.id, workflow.activeTurnId, "node.completed", {
126
+ ...node,
127
+ effectId: request.effectId,
128
+ status: "failed",
113
129
  });
114
130
  return resolved({ status: "completed", outcome });
115
131
  }
116
- return { kind: "agent", workflow };
132
+ return { kind: "agent", workflow, startedAt };
117
133
  });
118
134
  if (step.kind === "resolved")
119
135
  return step.resolution;
@@ -130,6 +146,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
130
146
  request,
131
147
  status: "pending",
132
148
  agentSessionId,
149
+ startedAt: step.startedAt,
133
150
  });
134
151
  await t.put("links", agentSessionId, {
135
152
  workflowSessionId: workflow.id,
@@ -145,14 +162,10 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
145
162
  if (exists)
146
163
  return;
147
164
  const definition = leafDefinition(workflow, body, request);
148
- const lookup = await sandboxLookup(t, workflow.sandboxOwnerId);
149
- const sandboxOwnerId = sessionSandboxSpec(workflow) || workflow.sandboxOwnerId
150
- ? owningSandboxSessionId(workflow, lookup)
151
- : undefined;
152
- // A sandbox chosen when the tree was opened reaches every agent in it, with its tools.
153
- const inherited = inheritedSandbox(sandboxOwnerId === undefined ? undefined : lookup(sandboxOwnerId) ?? workflow, definition);
154
- // Without one, an agent with MCP or HTTP tools still reads its stored results (R2b C11).
155
- const pinned = inherited?.manifest ? inherited : platformTools(definition.manifest);
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);
156
169
  const created = {
157
170
  id: agentSessionId,
158
171
  agentId: body.agentId,
@@ -167,14 +180,18 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
167
180
  requestId: `flow-put-${request.effectId}`,
168
181
  agentId: body.agentId,
169
182
  ownerUserId: workflow.ownerUserId,
170
- ...(sandboxOwnerId ? { sandbox: { session: sandboxOwnerId } } : {}),
183
+ ...(workflow.environmentId === undefined ? {} : { environment: workflow.environmentId }),
171
184
  },
172
- vaultIds: workflow.vaultIds,
173
- credentialSelections: workflow.credentialSelections,
174
- ...(sandboxOwnerId ? { sandboxOwnerId } : {}),
175
- ...(inherited
176
- ? { sandbox: inherited.spec, sandboxSource: "shared" }
177
- : {}),
185
+ flowRootId: workflow.flowRootId ?? workflow.id,
186
+ ...(workflow.environmentId === undefined
187
+ ? {}
188
+ : {
189
+ environmentId: workflow.environmentId,
190
+ ...(workflow.environmentVersion === undefined
191
+ ? {}
192
+ : { environmentVersion: workflow.environmentVersion }),
193
+ }),
194
+ ...(inherited ? { sandbox: inherited.spec } : {}),
178
195
  };
179
196
  await t.put("sessions", agentSessionId, created);
180
197
  });
@@ -240,25 +257,23 @@ function inheritedSandbox(owner, definition) {
240
257
  return undefined;
241
258
  if (isWorkflowManifest(definition.manifest))
242
259
  return { spec };
243
- const sandboxed = withSandboxCapability(definition.manifest, spec);
260
+ const sandboxed = withSandbox(definition.manifest, spec);
244
261
  if (!sandboxed.ok)
245
262
  throw new Error(sandboxed.message);
246
263
  return { spec, manifest: sandboxed.manifest, manifestHash: sandboxed.manifestHash };
247
264
  }
248
- /** A linked agent session's `read_artifact` without a sandbox (R2b C11); undefined for none. */
249
- function platformTools(manifest) {
265
+ /** A linked agent session's `runtime` block without a sandbox (R2b C11); undefined for a workflow. */
266
+ function runtimeTools(manifest) {
250
267
  if (isWorkflowManifest(manifest))
251
268
  return undefined;
252
- const pinned = withPlatformTools(manifest);
253
- if (pinned === undefined)
254
- return undefined;
269
+ const pinned = withRuntimeTools(manifest);
255
270
  if (!pinned.ok)
256
271
  throw new Error(pinned.message);
257
272
  return { manifest: pinned.manifest, manifestHash: pinned.manifestHash };
258
273
  }
259
274
  /**
260
275
  * The credential of an MCP server or HTTP tool request made on behalf of a session: from its
261
- * attached vaults (`vault/sources.ts`).
276
+ * Environment's vault (`vault/sources.ts`).
262
277
  */
263
278
  export async function authorize(ctx, sessionId, request) {
264
279
  const s = await loadSession(ctx, sessionId);
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Environment manifests (protocol 14, D63): `/v1/environment-manifests/{name}`, named and
3
+ * versioned definitions of a kind of Environment, saved like agents.
4
+ *
5
+ * - **Versions.** A save whose manifest hashes the same as the latest answers it unchanged;
6
+ * any other writes the next version. A version is never changed or deleted alone.
7
+ * - **No Environment changes.** Saving a version reaches no Environment: an Environment is made
8
+ * from one version and keeps it for life (`putEnvironment`). The plan counts those behind.
9
+ * - **Plan and drift.** `?dryRun=true` answers what a save would do and saves nothing: the
10
+ * changes from the latest, every reason the Runtime would refuse it (the Tenant's limits,
11
+ * missing artifacts), warnings, and how many Environments stay on older versions.
12
+ * `If-Match: <version>` refuses (`412 manifest_changed`) a save whose latest version moved
13
+ * since the client read it; `0` asks for a manifest that does not exist yet.
14
+ * - **Every client is equal** (Q12): a version records who saved it (`savedBy`), and nothing is
15
+ * read-only by where it was saved from.
16
+ */
17
+ import { type EnvironmentManifest, type EnvironmentManifestPlan, type EnvironmentManifestVersions, type EnvironmentManifestView, type PutEnvironmentManifestRequest, type SaveEnvironmentManifestResponse } from "@nylorun/core/contracts";
18
+ import { type RecipeRequest } from "../sandbox/resolve.js";
19
+ import type { AuthScope, TenantContext } from "./context.js";
20
+ /** What an environment manifest asks of an Environment's recipe. */
21
+ export declare function manifestRecipeRequest(manifest: EnvironmentManifest): RecipeRequest;
22
+ /** The hash a manifest's version records: equal manifests hash the same. */
23
+ export declare function environmentManifestHash(manifest: EnvironmentManifest): string;
24
+ /** Each field that differs between `before` and `after`, by path; arrays compare whole. */
25
+ export declare function manifestChanges(before: unknown, after: unknown, path?: string): {
26
+ path: string;
27
+ from?: unknown;
28
+ to?: unknown;
29
+ }[];
30
+ /**
31
+ * `PUT /v1/environment-manifests/{name}`: saves `body` as manifest `name`'s next version, or
32
+ * answers the latest unchanged; with `dryRun`, the plan of that save and nothing saved.
33
+ */
34
+ export declare function saveEnvironmentManifest(ctx: TenantContext, name: string, body: PutEnvironmentManifestRequest, scope: AuthScope, options: {
35
+ dryRun: true;
36
+ ifMatch?: number;
37
+ }): Promise<EnvironmentManifestPlan>;
38
+ export declare function saveEnvironmentManifest(ctx: TenantContext, name: string, body: PutEnvironmentManifestRequest, scope: AuthScope, options?: {
39
+ dryRun?: false;
40
+ ifMatch?: number;
41
+ }): Promise<SaveEnvironmentManifestResponse>;
42
+ /** `GET /v1/environment-manifests/{name}` (the latest) and `.../versions/{n}`. */
43
+ export declare function getEnvironmentManifest(ctx: TenantContext, name: string, version?: number): Promise<EnvironmentManifestView>;
44
+ /** `GET /v1/environment-manifests`: each manifest's latest version. */
45
+ export declare function listEnvironmentManifests(ctx: TenantContext): Promise<{
46
+ manifests: EnvironmentManifestView[];
47
+ }>;
48
+ /** `GET /v1/environment-manifests/{name}/versions`: every version, newest first. */
49
+ export declare function environmentManifestVersions(ctx: TenantContext, name: string): Promise<EnvironmentManifestVersions>;
50
+ /**
51
+ * `DELETE /v1/environment-manifests/{name}`: deletes every version, refused while any Environment
52
+ * was made from one (`409 manifest_in_use`): the Environment names the version it runs.
53
+ */
54
+ export declare function deleteEnvironmentManifest(ctx: TenantContext, name: string): Promise<{
55
+ name: string;
56
+ deleted: boolean;
57
+ }>;