@nylorun/runtime 0.25.0-beta → 0.27.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/README.md +26 -16
  3. package/dist/adapters/execution/restate.d.ts +15 -18
  4. package/dist/adapters/execution/restate.js +31 -27
  5. package/dist/adapters/replication/pgoutput.d.ts +1 -1
  6. package/dist/adapters/replication/pgoutput.js +3 -2
  7. package/dist/adapters/sandbox/local.js +8 -5
  8. package/dist/adapters/sandbox/virtual.js +2 -2
  9. package/dist/adapters/streams/s2.d.ts +1 -1
  10. package/dist/adapters/streams/s2.js +18 -14
  11. package/dist/api/a2a/card.js +1 -1
  12. package/dist/api/a2a/endpoint.js +3 -4
  13. package/dist/api/a2a/protocol.js +4 -0
  14. package/dist/api/a2a/routes.d.ts +2 -6
  15. package/dist/api/a2a/routes.js +12 -18
  16. package/dist/api/ag-ui/endpoint.js +2 -3
  17. package/dist/api/ag-ui/routes.js +4 -4
  18. package/dist/api/components.d.ts +326 -45
  19. package/dist/api/components.js +5 -1
  20. package/dist/api/gate/keys.js +7 -4
  21. package/dist/api/gate/routes.d.ts +6 -2
  22. package/dist/api/gate/routes.js +15 -11
  23. package/dist/api/http/app.d.ts +2 -2
  24. package/dist/api/http/app.js +2 -2
  25. package/dist/api/http/body.d.ts +15 -3
  26. package/dist/api/http/body.js +15 -7
  27. package/dist/api/http/define.d.ts +1 -1
  28. package/dist/api/http/respond.js +7 -3
  29. package/dist/api/http/routes/access.js +5 -4
  30. package/dist/api/http/routes/artifacts.js +19 -7
  31. package/dist/api/http/routes/coverage.js +1 -1
  32. package/dist/api/http/routes/environment-manifests.js +1 -1
  33. package/dist/api/http/routes/environments.js +6 -6
  34. package/dist/api/http/routes/files.d.ts +2 -1
  35. package/dist/api/http/routes/files.js +31 -5
  36. package/dist/api/http/routes/mcp.js +1 -1
  37. package/dist/api/http/routes/reads.d.ts +35 -0
  38. package/dist/api/http/routes/reads.js +50 -22
  39. package/dist/api/http/routes/sessions.js +12 -11
  40. package/dist/api/http/routes/tenant.d.ts +2 -2
  41. package/dist/api/http/routes/tenant.js +69 -16
  42. package/dist/api/inbound-files.d.ts +3 -9
  43. package/dist/api/inbound-files.js +18 -24
  44. package/dist/api/openapi.js +6 -2
  45. package/dist/artifacts/disk.d.ts +0 -4
  46. package/dist/artifacts/disk.js +77 -73
  47. package/dist/artifacts/files.d.ts +13 -4
  48. package/dist/artifacts/files.js +27 -9
  49. package/dist/artifacts/folder-upload.d.ts +75 -0
  50. package/dist/artifacts/folder-upload.js +263 -0
  51. package/dist/artifacts/folders.d.ts +14 -8
  52. package/dist/artifacts/folders.js +19 -11
  53. package/dist/artifacts/links.js +7 -6
  54. package/dist/artifacts/parts.d.ts +9 -14
  55. package/dist/artifacts/parts.js +11 -9
  56. package/dist/artifacts/pipe.d.ts +15 -0
  57. package/dist/artifacts/pipe.js +71 -0
  58. package/dist/artifacts/quota.d.ts +36 -0
  59. package/dist/artifacts/quota.js +44 -0
  60. package/dist/artifacts/service.d.ts +25 -1
  61. package/dist/artifacts/service.js +70 -25
  62. package/dist/blob/fs.js +2 -1
  63. package/dist/blob/s3.d.ts +6 -2
  64. package/dist/blob/s3.js +9 -7
  65. package/dist/blob/types.d.ts +1 -1
  66. package/dist/blob/types.js +1 -1
  67. package/dist/core/limits.d.ts +11 -35
  68. package/dist/core/limits.js +5 -73
  69. package/dist/core/sandbox-routes.js +0 -1
  70. package/dist/core/turn-manifest.d.ts +1 -2
  71. package/dist/core/turn-manifest.js +4 -28
  72. package/dist/execution/memory.d.ts +8 -5
  73. package/dist/execution/memory.js +8 -6
  74. package/dist/execution/types.d.ts +1 -6
  75. package/dist/execution/types.js +0 -5
  76. package/dist/gates/contract.d.ts +3 -2
  77. package/dist/gates/contract.js +1 -1
  78. package/dist/gates/egress-credentials.js +4 -3
  79. package/dist/gates/egress.d.ts +9 -9
  80. package/dist/gates/egress.js +19 -19
  81. package/dist/gates/fixture-model.js +13 -2
  82. package/dist/gates/handler.d.ts +1 -3
  83. package/dist/gates/handler.js +4 -3
  84. package/dist/gates/http-client.d.ts +4 -3
  85. package/dist/gates/http-client.js +5 -7
  86. package/dist/gates/http-tool.d.ts +24 -4
  87. package/dist/gates/http-tool.js +53 -17
  88. package/dist/gates/in-process.d.ts +2 -1
  89. package/dist/gates/in-process.js +17 -9
  90. package/dist/gates/inflight.d.ts +2 -2
  91. package/dist/gates/inflight.js +3 -2
  92. package/dist/gates/journal.js +5 -7
  93. package/dist/gates/mcp-handler.d.ts +4 -3
  94. package/dist/gates/mcp-handler.js +15 -5
  95. package/dist/gates/model-gate.d.ts +11 -2
  96. package/dist/gates/policy.d.ts +28 -5
  97. package/dist/gates/policy.js +60 -17
  98. package/dist/gates/provider-gate.d.ts +22 -0
  99. package/dist/gates/provider-gate.js +30 -0
  100. package/dist/gates/tool-calls.d.ts +1 -3
  101. package/dist/gates/tool-calls.js +2 -3
  102. package/dist/gates/tool-client.d.ts +1 -1
  103. package/dist/gates/tool-client.js +5 -6
  104. package/dist/gates/tool-contract.d.ts +8 -5
  105. package/dist/gates/tool-contract.js +1 -1
  106. package/dist/gates/tool-gate.d.ts +18 -7
  107. package/dist/gates/tool-gate.js +49 -7
  108. package/dist/harness/calls.d.ts +27 -25
  109. package/dist/harness/calls.js +40 -46
  110. package/dist/harness/executors.d.ts +10 -3
  111. package/dist/harness/executors.js +53 -23
  112. package/dist/harness/main.js +2 -2
  113. package/dist/harness/pod.d.ts +0 -2
  114. package/dist/harness/pod.js +7 -6
  115. package/dist/harness/run-tokens.d.ts +4 -1
  116. package/dist/harness/run-tokens.js +6 -2
  117. package/dist/harness/service.d.ts +17 -14
  118. package/dist/harness/service.js +21 -35
  119. package/dist/harness/setup.d.ts +0 -2
  120. package/dist/harness/setup.js +4 -9
  121. package/dist/harness/ws-client.d.ts +2 -2
  122. package/dist/harness/ws-client.js +7 -5
  123. package/dist/harness/ws-port.d.ts +3 -4
  124. package/dist/harness/ws-port.js +3 -4
  125. package/dist/harness-api/artifacts.d.ts +20 -0
  126. package/dist/harness-api/artifacts.js +478 -0
  127. package/dist/harness-api/in-process.d.ts +2 -2
  128. package/dist/harness-api/in-process.js +18 -16
  129. package/dist/harness-api/record.d.ts +10 -9
  130. package/dist/harness-api/record.js +102 -157
  131. package/dist/harness-api/renew.d.ts +1 -1
  132. package/dist/harness-api/renew.js +2 -2
  133. package/dist/harness-api/server.d.ts +1 -16
  134. package/dist/harness-api/server.js +82 -73
  135. package/dist/harness-api/start.d.ts +20 -2
  136. package/dist/harness-api/start.js +57 -18
  137. package/dist/harness-api/tool-journal.d.ts +15 -10
  138. package/dist/harness-api/tool-journal.js +26 -24
  139. package/dist/harness-api/workspace.d.ts +0 -4
  140. package/dist/harness-api/workspace.js +2 -45
  141. package/dist/harness-api/ws-server.js +10 -10
  142. package/dist/host/create-host.js +2 -3
  143. package/dist/host/execution.js +2 -3
  144. package/dist/host/gates.d.ts +2 -9
  145. package/dist/host/gates.js +15 -12
  146. package/dist/host/http.d.ts +0 -2
  147. package/dist/host/http.js +0 -27
  148. package/dist/host/main.js +10 -3
  149. package/dist/host/operate.js +12 -3
  150. package/dist/host/stack-config.d.ts +11 -0
  151. package/dist/host/stack-config.js +21 -1
  152. package/dist/index.d.ts +0 -1
  153. package/dist/index.js +0 -1
  154. package/dist/infra/index.js +2 -1
  155. package/dist/infra/readiness.d.ts +0 -2
  156. package/dist/infra/readiness.js +4 -3
  157. package/dist/keys/client.d.ts +1 -0
  158. package/dist/keys/client.js +2 -3
  159. package/dist/keys/contract.d.ts +1 -3
  160. package/dist/keys/contract.js +1 -3
  161. package/dist/keys/egress-ca.d.ts +0 -2
  162. package/dist/keys/egress-ca.js +2 -3
  163. package/dist/keys/keys.js +6 -1
  164. package/dist/keys/x509.d.ts +8 -4
  165. package/dist/keys/x509.js +14 -5
  166. package/dist/management-openapi.json +1961 -994
  167. package/dist/mcp/connect.d.ts +35 -26
  168. package/dist/mcp/connect.js +112 -141
  169. package/dist/mcp/pool.d.ts +27 -22
  170. package/dist/mcp/pool.js +34 -48
  171. package/dist/mcp/preview.d.ts +13 -6
  172. package/dist/mcp/preview.js +37 -20
  173. package/dist/mcp/snapshot.d.ts +35 -86
  174. package/dist/mcp/snapshot.js +93 -135
  175. package/dist/model/auth-store.js +5 -3
  176. package/dist/model/capabilities.d.ts +1 -1
  177. package/dist/model/catalog.d.ts +9 -36
  178. package/dist/model/catalog.js +6 -33
  179. package/dist/model/descriptor.d.ts +51 -0
  180. package/dist/model/descriptor.js +83 -0
  181. package/dist/model/models.d.ts +0 -3
  182. package/dist/model/models.js +3 -5
  183. package/dist/model/native-blocks.js +1 -1
  184. package/dist/model/pi-model.d.ts +16 -5
  185. package/dist/model/pi-model.js +86 -105
  186. package/dist/openapi.json +866 -1218
  187. package/dist/reads/cursor.js +3 -1
  188. package/dist/record/append.d.ts +34 -2
  189. package/dist/record/append.js +50 -1
  190. package/dist/record/environment.d.ts +2 -0
  191. package/dist/record/environment.js +2 -0
  192. package/dist/record/index.d.ts +1 -1
  193. package/dist/record/index.js +1 -1
  194. package/dist/redact.d.ts +1 -1
  195. package/dist/redact.js +3 -2
  196. package/dist/sandbox/egress-token.d.ts +3 -3
  197. package/dist/sandbox/egress-token.js +3 -3
  198. package/dist/sandbox/join.js +3 -2
  199. package/dist/sandbox/manager.d.ts +41 -41
  200. package/dist/sandbox/manager.js +112 -44
  201. package/dist/sandbox/placement.d.ts +1 -1
  202. package/dist/sandbox/placement.js +5 -8
  203. package/dist/sandbox/pods/client.d.ts +1 -10
  204. package/dist/sandbox/pods/client.js +8 -4
  205. package/dist/sandbox/pods/lifecycle.d.ts +19 -11
  206. package/dist/sandbox/pods/lifecycle.js +45 -23
  207. package/dist/sandbox/pods/network-gate.d.ts +4 -5
  208. package/dist/sandbox/pods/network-gate.js +7 -7
  209. package/dist/sandbox/pods/reconcile.js +4 -1
  210. package/dist/sandbox/pods/spec.js +8 -5
  211. package/dist/sandbox/policy.d.ts +7 -1
  212. package/dist/sandbox/policy.js +10 -3
  213. package/dist/sandbox/resolve.d.ts +8 -3
  214. package/dist/sandbox/resolve.js +12 -2
  215. package/dist/sandbox/setup.d.ts +3 -1
  216. package/dist/sandbox/setup.js +34 -11
  217. package/dist/sandbox/share.d.ts +5 -14
  218. package/dist/sandbox/share.js +5 -34
  219. package/dist/sandbox/skills.d.ts +7 -13
  220. package/dist/sandbox/skills.js +16 -31
  221. package/dist/sandbox/tenant-config.d.ts +2 -2
  222. package/dist/sandbox/tenant-config.js +4 -3
  223. package/dist/sandbox/tools.d.ts +7 -1
  224. package/dist/sandbox/tools.js +26 -26
  225. package/dist/sandbox/types.d.ts +10 -3
  226. package/dist/sandbox/types.js +9 -0
  227. package/dist/store/postgres/connect.d.ts +6 -5
  228. package/dist/store/postgres/connect.js +14 -11
  229. package/dist/store/postgres/control.js +13 -13
  230. package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
  231. package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
  232. package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
  233. package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
  234. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  235. package/dist/store/postgres/migrate.js +1 -1
  236. package/dist/store/postgres/reads.js +10 -2
  237. package/dist/store/postgres/schema.d.ts +19 -0
  238. package/dist/store/postgres/schema.js +7 -0
  239. package/dist/store/postgres/store.js +55 -26
  240. package/dist/store/types.d.ts +17 -7
  241. package/dist/streams/relay/core.d.ts +3 -3
  242. package/dist/streams/relay/core.js +9 -10
  243. package/dist/tenant/advance.js +21 -31
  244. package/dist/tenant/commands.js +9 -16
  245. package/dist/tenant/context.d.ts +14 -17
  246. package/dist/tenant/definition-files.d.ts +25 -4
  247. package/dist/tenant/definition-files.js +85 -47
  248. package/dist/tenant/effects.js +9 -36
  249. package/dist/tenant/environment-manifests.js +9 -1
  250. package/dist/tenant/environments.d.ts +0 -2
  251. package/dist/tenant/environments.js +54 -30
  252. package/dist/tenant/ephemeral.d.ts +1 -1
  253. package/dist/tenant/ephemeral.js +2 -1
  254. package/dist/tenant/history.d.ts +1 -1
  255. package/dist/tenant/history.js +1 -1
  256. package/dist/tenant/host-token.d.ts +3 -3
  257. package/dist/tenant/host-token.js +12 -11
  258. package/dist/tenant/http.d.ts +5 -8
  259. package/dist/tenant/http.js +9 -0
  260. package/dist/tenant/identity-file.d.ts +24 -1
  261. package/dist/tenant/identity-file.js +35 -7
  262. package/dist/tenant/issuers.d.ts +5 -5
  263. package/dist/tenant/issuers.js +31 -23
  264. package/dist/tenant/jwt.d.ts +8 -2
  265. package/dist/tenant/jwt.js +10 -2
  266. package/dist/tenant/limits.d.ts +90 -0
  267. package/dist/tenant/limits.js +175 -0
  268. package/dist/tenant/module.d.ts +4 -1
  269. package/dist/tenant/module.js +9 -8
  270. package/dist/tenant/outbound.d.ts +17 -19
  271. package/dist/tenant/outbound.js +18 -21
  272. package/dist/tenant/run-grants.js +3 -2
  273. package/dist/tenant/run-token.d.ts +0 -4
  274. package/dist/tenant/run-token.js +6 -11
  275. package/dist/tenant/runtime-limits.d.ts +24 -0
  276. package/dist/tenant/runtime-limits.js +27 -0
  277. package/dist/tenant/runtime.js +30 -43
  278. package/dist/tenant/session-streams.js +7 -6
  279. package/dist/tenant/session.d.ts +1 -1
  280. package/dist/tenant/session.js +1 -1
  281. package/dist/tenant/sessions.js +32 -29
  282. package/dist/tenant/signing-keys.d.ts +1 -1
  283. package/dist/tenant/signing-keys.js +5 -6
  284. package/dist/tenant/status.js +4 -3
  285. package/dist/tenant/streams.js +18 -20
  286. package/dist/tenant/sweep.d.ts +1 -21
  287. package/dist/tenant/sweep.js +39 -48
  288. package/dist/tenant/transcript.d.ts +70 -23
  289. package/dist/tenant/transcript.js +28 -22
  290. package/dist/tenant/types.d.ts +11 -16
  291. package/dist/tenant/worker.d.ts +2 -10
  292. package/dist/tenant/worker.js +5 -14
  293. package/dist/traces/ids.js +5 -1
  294. package/dist/traces/map.d.ts +9 -6
  295. package/dist/traces/map.js +9 -11
  296. package/dist/traces/otlp.d.ts +3 -3
  297. package/dist/traces/otlp.js +5 -3
  298. package/dist/traces/projector.d.ts +9 -4
  299. package/dist/traces/projector.js +13 -7
  300. package/dist/traces/read.js +6 -1
  301. package/dist/tuning.d.ts +383 -0
  302. package/dist/tuning.js +658 -0
  303. package/dist/vault/headers.js +4 -2
  304. package/dist/version.d.ts +1 -1
  305. package/dist/version.js +1 -1
  306. package/package.json +3 -3
  307. package/dist/artifacts/export.d.ts +0 -8
  308. package/dist/artifacts/export.js +0 -204
  309. package/dist/artifacts/media-types.d.ts +0 -30
  310. package/dist/artifacts/media-types.js +0 -137
  311. package/dist/artifacts/workspace.d.ts +0 -60
  312. package/dist/artifacts/workspace.js +0 -45
  313. package/dist/media.d.ts +0 -10
  314. package/dist/media.js +0 -58
  315. package/dist/sandbox/runtime-tools.d.ts +0 -9
  316. package/dist/sandbox/runtime-tools.js +0 -16
  317. package/dist/sandbox/session-sandbox.d.ts +0 -16
  318. package/dist/sandbox/session-sandbox.js +0 -77
  319. package/dist/tenant/artifact-tool.d.ts +0 -13
  320. package/dist/tenant/artifact-tool.js +0 -243
  321. package/dist/tenant/skill-tool.d.ts +0 -20
  322. package/dist/tenant/skill-tool.js +0 -67
  323. package/dist/tenant/tool-results.d.ts +0 -57
  324. package/dist/tenant/tool-results.js +0 -298
  325. package/dist/tenant/tool-search.d.ts +0 -39
  326. package/dist/tenant/tool-search.js +0 -103
@@ -0,0 +1,478 @@
1
+ /**
2
+ * The artifact seam of the Harness API (protocol 15): a harness reads and writes the artifacts
3
+ * of the run it holds, and nothing else. Our engine's `read_artifact` and `save_artifact` run in
4
+ * the harness over it; any harness may use it.
5
+ *
6
+ * - **Scope.** The run's session (`run.grant.sessionId`), never a parameter: agents used as tools
7
+ * share it. A read follows the read rule (`sessionMayRead`): the session's own artifacts and the
8
+ * Tenant-wide ones its messages named. Anything else, missing or not, is the same `not_found`.
9
+ * A write makes an artifact of the session, or a new version of one of its own file artifacts.
10
+ * - **Chunks.** No answer or chunk carries more than the run's `limits.chunkBytes`: a read is
11
+ * clamped to it, a chunk past it is `limit_exceeded`.
12
+ * - **Uploads stream.** `begin` starts the upload (`uploadArtifact`) with a body that takes each
13
+ * chunk as it comes: a chunk is answered once the store took it, so nothing is held whole, and
14
+ * the version commits in Postgres on `commit` as any upload does. The key of `begin` makes the
15
+ * write idempotent within the session (`writeKeyOf`, `artifact_versions.write_key`), and its
16
+ * `effectId` a tool call of the run's turn, whose call id `artifact.created` carries: core reads
17
+ * it from the journal, so the harness only names the call.
18
+ * - **Ends.** `abort`, the run's end and core's abort of the advance stop an upload: nothing
19
+ * commits.
20
+ * - **Folders.** `begin` with `kind: "folder"` writes a folder version of the session instead
21
+ * (`artifacts/folder-upload.ts`): file by file, each checked against its hash, committed with
22
+ * dedupe, and not added when it equals the folder's latest version.
23
+ */
24
+ import { randomUUID } from "node:crypto";
25
+ import { HarnessApiError } from "@nylorun/core/harness-api";
26
+ import { limitExceededMessage, normalizeContentType } from "@nylorun/core/contracts";
27
+ import { isArtifactId } from "@nylorun/core/compatibility";
28
+ import { BlobRangeError } from "../blob/index.js";
29
+ import { sessionMayRead } from "../artifacts/files.js";
30
+ import { artifactQuota, artifactUsage, environmentOfSession } from "../artifacts/quota.js";
31
+ import { artifactRoom, checkArtifactName, uploadArtifact, writeKeyOf, } from "../artifacts/service.js";
32
+ import { FolderUpload } from "../artifacts/folder-upload.js";
33
+ import { contentEpoch } from "../artifacts/folders.js";
34
+ import { HttpError } from "../tenant/http.js";
35
+ import { toolIds } from "../tenant/transcript.js";
36
+ import { abortKind } from "../tenant/worker.js";
37
+ import { Pipe } from "../artifacts/pipe.js";
38
+ const notOurs = (artifactId) => new HarnessApiError("not_found", `Artifact ${artifactId} is not an artifact of this session.`);
39
+ export function createArtifactSeam(ctx) {
40
+ const uploads = new Map();
41
+ const folders = new Map();
42
+ const folderOf = (scope, uploadId) => folders.get(scope.runId)?.get(uploadId);
43
+ const forgetFolder = (runId, uploadId) => {
44
+ const open = folders.get(runId);
45
+ open?.delete(uploadId);
46
+ if (open?.size === 0)
47
+ folders.delete(runId);
48
+ };
49
+ /** Opens a folder version of the session (`kind: "folder"`). */
50
+ const beginFolder = async (scope, params) => {
51
+ stopped(scope.runSignal);
52
+ const { sessionId } = scope;
53
+ harnessError(() => checkArtifactName(params.name));
54
+ const writeKey = writeKeyOf(sessionId, params.key);
55
+ const begun = await ctx.store.tx(async (t) => {
56
+ // A key written before answers its version: a harness repeating a write stores nothing.
57
+ const known = await t.artifactVersionByWriteKey(writeKey);
58
+ if (known) {
59
+ const row = await t.artifact(known.artifactId);
60
+ if (row)
61
+ return { written: metaOf(row, known) };
62
+ }
63
+ const session = await t.get("sessions", sessionId);
64
+ if (!session)
65
+ throw new HarnessApiError("run_not_held", `Session ${sessionId} is gone`);
66
+ let prior;
67
+ if (params.artifactId !== undefined) {
68
+ prior = isArtifactId(params.artifactId) ? await t.artifact(params.artifactId) : undefined;
69
+ if (!prior || prior.kind !== "folder" || prior.sessionId !== sessionId)
70
+ throw notOurs(params.artifactId);
71
+ }
72
+ else if (params.sameName)
73
+ prior = (await t.listArtifacts({ sessionId }))
74
+ .filter((row) => row.kind === "folder" && row.name === params.name)
75
+ .sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0))[0];
76
+ const latest = prior ? await t.artifactVersion(prior.id, prior.latestVersion) : undefined;
77
+ return {
78
+ upload: new FolderUpload(ctx, {
79
+ sessionId,
80
+ turnId: scope.turnId,
81
+ name: params.name,
82
+ ...(prior ? { prior: { row: prior, ...(latest ? { latest } : {}) } } : {}),
83
+ quota: await artifactQuota(t, await environmentOfSession(t, sessionId)),
84
+ epoch: await contentEpoch(t),
85
+ writeKey,
86
+ limits: scope.limits,
87
+ }),
88
+ };
89
+ });
90
+ if ("written" in begun)
91
+ return { status: "written", artifact: begun.written, created: false };
92
+ const upload = begun.upload;
93
+ const open = folders.get(scope.runId) ?? new Map();
94
+ folders.set(scope.runId, open);
95
+ open.set(upload.id, upload);
96
+ const end = () => {
97
+ forgetFolder(scope.runId, upload.id);
98
+ void upload.abort("the run stopped");
99
+ };
100
+ if (scope.runSignal.aborted)
101
+ end();
102
+ else
103
+ scope.runSignal.addEventListener("abort", end, { once: true });
104
+ return {
105
+ status: "open",
106
+ uploadId: upload.id,
107
+ maxBytes: upload.maxBytes,
108
+ maxFileBytes: upload.maxFileBytes,
109
+ maxEntries: upload.maxEntries,
110
+ received: 0,
111
+ };
112
+ };
113
+ /** One request of an open folder upload; a refusal ends the upload. */
114
+ const inFolder = async (scope, upload, step) => {
115
+ try {
116
+ return await step();
117
+ }
118
+ catch (error) {
119
+ forgetFolder(scope.runId, upload.id);
120
+ await upload.abort("a request of it was refused");
121
+ throw toHarnessError(error);
122
+ }
123
+ };
124
+ const folderAnswer = (upload, received) => ({
125
+ status: "open",
126
+ uploadId: upload.id,
127
+ maxBytes: upload.maxBytes,
128
+ maxFileBytes: upload.maxFileBytes,
129
+ maxEntries: upload.maxEntries,
130
+ received,
131
+ });
132
+ const stop = (upload, reason) => {
133
+ if (!upload.controller.signal.aborted)
134
+ upload.controller.abort(new Error(reason));
135
+ upload.pipe.fail(new HarnessApiError("invalid", `The upload ended: ${reason}`));
136
+ };
137
+ const forget = (runId, uploadId) => {
138
+ const open = uploads.get(runId);
139
+ open?.delete(uploadId);
140
+ if (open?.size === 0)
141
+ uploads.delete(runId);
142
+ };
143
+ const uploadOf = (scope, uploadId) => {
144
+ const upload = uploads.get(scope.runId)?.get(uploadId);
145
+ if (!upload)
146
+ throw new HarnessApiError("invalid", `No upload ${uploadId} is open for this run`);
147
+ return upload;
148
+ };
149
+ const begin = async (scope, params, signal) => {
150
+ stopped(scope.runSignal);
151
+ const { sessionId, limits } = scope;
152
+ const contentType = params.contentType === undefined ? undefined : normalizeContentType(params.contentType);
153
+ if (params.contentType !== undefined && contentType === undefined)
154
+ throw new HarnessApiError("invalid", `${params.contentType} is not a media type`);
155
+ if (params.artifactId === undefined)
156
+ harnessError(() => checkArtifactName(params.name));
157
+ const writeKey = writeKeyOf(sessionId, params.key);
158
+ const begun = await ctx.store.tx(async (t) => {
159
+ // A key written before answers its version: a harness repeating a write stores nothing.
160
+ const known = await t.artifactVersionByWriteKey(writeKey);
161
+ if (known) {
162
+ const row = await t.artifact(known.artifactId);
163
+ if (row)
164
+ return { written: metaOf(row, known) };
165
+ }
166
+ const session = await t.get("sessions", sessionId);
167
+ if (!session)
168
+ throw new HarnessApiError("run_not_held", `Session ${sessionId} is gone`);
169
+ if (params.artifactId !== undefined) {
170
+ const prior = isArtifactId(params.artifactId) ? await t.artifact(params.artifactId) : undefined;
171
+ // A new version only of a file artifact of this session.
172
+ if (!prior || prior.kind !== "file" || prior.sessionId !== sessionId)
173
+ throw notOurs(params.artifactId);
174
+ }
175
+ let callId;
176
+ if (params.effectId !== undefined) {
177
+ const row = await t.get("effects", params.effectId);
178
+ const request = row?.request;
179
+ if (!request ||
180
+ request.kind !== "tool" ||
181
+ request.sessionId !== sessionId ||
182
+ request.turnId !== scope.turnId)
183
+ throw new HarnessApiError("invalid", `Effect ${params.effectId} is not a tool call of this run's turn`);
184
+ const ids = toolIds(request.context);
185
+ if ("callId" in ids)
186
+ callId = ids.callId;
187
+ }
188
+ const quota = await artifactQuota(t, await environmentOfSession(t, sessionId));
189
+ return {
190
+ owner: session.ownerUserId,
191
+ callId,
192
+ room: artifactRoom(quota, await artifactUsage(t, quota)),
193
+ };
194
+ });
195
+ if (begun.written)
196
+ return { status: "written", artifact: begun.written, created: false };
197
+ const refusal = begun.room.refusal(params.size);
198
+ if (refusal)
199
+ throw new HarnessApiError("limit_exceeded", `${refusal.message} (limitBytes ${refusal.limitBytes})`);
200
+ const input = {
201
+ ...(params.artifactId === undefined ? { name: params.name } : { artifactId: params.artifactId }),
202
+ ...(contentType === undefined ? {} : { contentType }),
203
+ sessionId,
204
+ source: "engine",
205
+ turnId: scope.turnId,
206
+ ...(begun.callId === undefined ? {} : { callId: begun.callId }),
207
+ writeKey,
208
+ };
209
+ // Our engine saves into its own session only: the session's owner reaches it.
210
+ const access = { owner: begun.owner };
211
+ if (params.base64 !== undefined) {
212
+ const bytes = chunkOf(params.base64, limits);
213
+ const over = begun.room.refusal(bytes.byteLength);
214
+ if (over)
215
+ throw new HarnessApiError("limit_exceeded", `${over.message} (limitBytes ${over.limitBytes})`);
216
+ const saved = await harnessErrorOf(uploadArtifact(ctx, { ...input, declaredBytes: bytes.byteLength }, bytes, access, signal));
217
+ return written(saved);
218
+ }
219
+ const controller = new AbortController();
220
+ const pipe = new Pipe();
221
+ const upload = {
222
+ id: randomUUID(),
223
+ maxBytes: begun.room.maxBytes,
224
+ received: 0,
225
+ pipe,
226
+ controller,
227
+ done: uploadArtifact(ctx, { ...input, ...(params.size === undefined ? {} : { declaredBytes: params.size }) }, pipe, access, controller.signal),
228
+ };
229
+ // A failed upload fails the chunk waiting on it, and the next request; no rejection is lost.
230
+ upload.done.catch((error) => pipe.fail(error));
231
+ const open = uploads.get(scope.runId) ?? new Map();
232
+ uploads.set(scope.runId, open);
233
+ open.set(upload.id, upload);
234
+ // Core's abort of the advance (a cancel, a lost lease) stops the run's uploads.
235
+ if (scope.runSignal.aborted)
236
+ stop(upload, "the run stopped");
237
+ else
238
+ scope.runSignal.addEventListener("abort", () => stop(upload, "the run stopped"), { once: true });
239
+ return { status: "open", uploadId: upload.id, maxBytes: upload.maxBytes, received: 0 };
240
+ };
241
+ const chunk = async (scope, params) => {
242
+ const upload = uploadOf(scope, params.uploadId);
243
+ const open = () => ({ status: "open", uploadId: upload.id, maxBytes: upload.maxBytes, received: upload.received });
244
+ const bytes = chunkOf(params.base64, scope.limits);
245
+ // A repeat of the last chunk (its answer was lost) is acknowledged, not taken again.
246
+ if (upload.last &&
247
+ params.offset === upload.last.offset &&
248
+ bytes.byteLength === upload.last.length &&
249
+ params.offset + bytes.byteLength === upload.received)
250
+ return open();
251
+ if (params.offset !== upload.received)
252
+ throw new HarnessApiError("invalid", `The chunk starts at ${params.offset}; the upload has ${upload.received} bytes, so the next chunk starts there`);
253
+ if (upload.received + bytes.byteLength > upload.maxBytes) {
254
+ const message = `The upload passes the ${upload.maxBytes} bytes it may hold (limitBytes ${upload.maxBytes})`;
255
+ stop(upload, message);
256
+ forget(scope.runId, upload.id);
257
+ await upload.done.catch(() => undefined);
258
+ throw new HarnessApiError("limit_exceeded", message);
259
+ }
260
+ // Taken now, so a repeat sent while this one is written is acknowledged too.
261
+ upload.received += bytes.byteLength;
262
+ upload.last = { offset: params.offset, length: bytes.byteLength };
263
+ try {
264
+ await upload.pipe.push(bytes);
265
+ }
266
+ catch (error) {
267
+ forget(scope.runId, upload.id);
268
+ // The upload's own refusal (a limit, the session gone) says more than the pipe's.
269
+ throw toHarnessError(await upload.done.then(() => error, (failure) => failure));
270
+ }
271
+ return open();
272
+ };
273
+ const commit = async (scope, params) => {
274
+ const upload = uploadOf(scope, params.uploadId);
275
+ stopped(scope.runSignal);
276
+ upload.pipe.close();
277
+ try {
278
+ return written(await upload.done);
279
+ }
280
+ catch (error) {
281
+ throw toHarnessError(error);
282
+ }
283
+ finally {
284
+ forget(scope.runId, upload.id);
285
+ }
286
+ };
287
+ return {
288
+ async read(scope, params, signal) {
289
+ const { artifactId } = params;
290
+ const found = await ctx.store.tx(async (t) => {
291
+ if (!isArtifactId(artifactId))
292
+ return undefined;
293
+ const artifact = await t.artifact(artifactId);
294
+ if (!artifact)
295
+ return undefined;
296
+ const session = await t.get("sessions", scope.sessionId);
297
+ if (!sessionMayRead(session, artifact))
298
+ return undefined;
299
+ return { artifact, version: await t.artifactVersion(artifact.id, params.version ?? artifact.latestVersion) };
300
+ });
301
+ if (!found)
302
+ throw notOurs(artifactId);
303
+ const { artifact, version } = found;
304
+ if (!version)
305
+ throw new HarnessApiError("not_found", `Artifact ${artifactId} has no version ${params.version}.`);
306
+ const meta = metaOf(artifact, version);
307
+ const offset = params.offset ?? 0;
308
+ // A folder's bytes are its files: the seam answers its metadata only.
309
+ if (artifact.kind !== "file")
310
+ return { artifact: meta, offset, base64: "", eof: true };
311
+ if (offset > version.size)
312
+ throw new HarnessApiError("invalid", `Offset ${offset} is past the end of ${artifact.name}, which is ${version.size} bytes.`);
313
+ const { chunkBytes } = scope.limits;
314
+ const end = Math.min(offset + Math.min(params.length ?? chunkBytes, chunkBytes), version.size);
315
+ if (end === offset)
316
+ return { artifact: meta, offset, base64: "", eof: offset === version.size };
317
+ let bytes;
318
+ try {
319
+ const got = await ctx.blobs.get(version.blobKey, { range: { start: offset, end: end - 1 }, signal });
320
+ if (!got)
321
+ throw new HarnessApiError("unavailable", `The bytes of ${artifact.name} are missing from the Object store`);
322
+ bytes = new Uint8Array(await new Response(got.body).arrayBuffer());
323
+ }
324
+ catch (error) {
325
+ if (!(error instanceof BlobRangeError))
326
+ throw error;
327
+ bytes = new Uint8Array();
328
+ }
329
+ return {
330
+ artifact: meta,
331
+ offset,
332
+ base64: Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64"),
333
+ eof: offset + bytes.byteLength >= version.size,
334
+ };
335
+ },
336
+ async write(scope, params, signal) {
337
+ switch (params.op) {
338
+ case "begin":
339
+ return "kind" in params ? beginFolder(scope, params) : begin(scope, params, signal);
340
+ case "file": {
341
+ const folder = folderOf(scope, params.uploadId);
342
+ if (!folder)
343
+ throw new HarnessApiError("invalid", `No folder upload ${params.uploadId} is open for this run`);
344
+ return folderAnswer(folder, await inFolder(scope, folder, () => folder.file(params, signal)));
345
+ }
346
+ case "chunk": {
347
+ const folder = folderOf(scope, params.uploadId);
348
+ if (folder)
349
+ return folderAnswer(folder, await inFolder(scope, folder, () => folder.chunk(params)));
350
+ return chunk(scope, params);
351
+ }
352
+ case "commit": {
353
+ const folder = folderOf(scope, params.uploadId);
354
+ if (!folder)
355
+ return commit(scope, params);
356
+ stopped(scope.runSignal);
357
+ const done = await inFolder(scope, folder, () => folder.commit(signal));
358
+ forgetFolder(scope.runId, folder.id);
359
+ return done.kind === "refused"
360
+ ? { status: "refused", reason: done.reason, message: done.message, limit: done.limit }
361
+ : {
362
+ status: "written",
363
+ artifact: metaOf(done.row, done.version),
364
+ created: done.created,
365
+ ...(done.unchanged ? { unchanged: true } : {}),
366
+ };
367
+ }
368
+ case "abort": {
369
+ const folder = folderOf(scope, params.uploadId);
370
+ if (folder) {
371
+ forgetFolder(scope.runId, folder.id);
372
+ await folder.abort("the harness aborted it");
373
+ return { status: "aborted" };
374
+ }
375
+ const upload = uploads.get(scope.runId)?.get(params.uploadId);
376
+ if (upload) {
377
+ stop(upload, "the harness aborted it");
378
+ forget(scope.runId, upload.id);
379
+ await upload.done.catch(() => undefined);
380
+ }
381
+ return { status: "aborted" };
382
+ }
383
+ }
384
+ },
385
+ endRun(runId) {
386
+ const openFolders = folders.get(runId);
387
+ folders.delete(runId);
388
+ for (const folder of openFolders?.values() ?? [])
389
+ void folder.abort("the run ended");
390
+ const open = uploads.get(runId);
391
+ if (!open)
392
+ return;
393
+ uploads.delete(runId);
394
+ for (const upload of open.values())
395
+ stop(upload, "the run ended");
396
+ },
397
+ };
398
+ }
399
+ /** What the seam shows of an artifact's version. */
400
+ function metaOf(artifact, version) {
401
+ return {
402
+ artifactId: artifact.id,
403
+ kind: artifact.kind,
404
+ name: artifact.name,
405
+ version: version.version,
406
+ contentType: version.contentType,
407
+ size: version.size,
408
+ sha256: version.sha256,
409
+ tenantWide: artifact.sessionId === null,
410
+ };
411
+ }
412
+ function written(saved) {
413
+ return {
414
+ status: "written",
415
+ artifact: {
416
+ artifactId: saved.artifact.artifactId,
417
+ kind: saved.artifact.kind,
418
+ name: saved.artifact.name,
419
+ version: saved.version.version,
420
+ contentType: saved.version.contentType,
421
+ size: saved.version.size,
422
+ sha256: saved.version.sha256,
423
+ tenantWide: saved.artifact.sessionId === undefined,
424
+ },
425
+ created: saved.created,
426
+ };
427
+ }
428
+ /** One chunk's bytes, at most the run's `chunkBytes`. */
429
+ function chunkOf(base64, limits) {
430
+ const bytes = Buffer.from(base64, "base64");
431
+ if (bytes.byteLength > limits.chunkBytes)
432
+ throw new HarnessApiError("limit_exceeded", limitExceededMessage("The chunk", bytes.byteLength, "chunkBytes", limits.chunkBytes));
433
+ return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
434
+ }
435
+ /** A stopped run starts no write. */
436
+ function stopped(signal) {
437
+ const kind = abortKind(signal);
438
+ if (kind === undefined)
439
+ return;
440
+ if (kind === "cancel")
441
+ throw new HarnessApiError("turn_cancelled", "Turn cancelled");
442
+ if (kind === "ownership.lost")
443
+ throw new HarnessApiError("ownership_lost", "The session's lease was lost");
444
+ throw new HarnessApiError("unavailable", "The run is stopping");
445
+ }
446
+ /**
447
+ * An upload's refusal as the harness hears it: missing or not the session's is `not_found`, past a
448
+ * limit `limit_exceeded`, any other refusal `invalid`. Anything else is the Runtime's own error.
449
+ */
450
+ function toHarnessError(error) {
451
+ if (error instanceof HarnessApiError || !(error instanceof HttpError))
452
+ return error;
453
+ if (error.status === 404)
454
+ return new HarnessApiError("not_found", error.message);
455
+ if (error.status === 413) {
456
+ const limit = error.rejection.details?.limitBytes;
457
+ return new HarnessApiError("limit_exceeded", typeof limit === "number" ? `${error.message} (limitBytes ${limit})` : error.message);
458
+ }
459
+ if (error.status === 400 || error.status === 409)
460
+ return new HarnessApiError("invalid", error.message);
461
+ return error;
462
+ }
463
+ function harnessError(call) {
464
+ try {
465
+ return call();
466
+ }
467
+ catch (error) {
468
+ throw toHarnessError(error);
469
+ }
470
+ }
471
+ async function harnessErrorOf(promise) {
472
+ try {
473
+ return await promise;
474
+ }
475
+ catch (error) {
476
+ throw toHarnessError(error);
477
+ }
478
+ }
@@ -16,8 +16,8 @@ export declare function startInProcessHarness(ctx: TenantContext, tools: {
16
16
  }, options?: MemoryPortsOptions): Promise<InProcessHarness>;
17
17
  /**
18
18
  * A harness service over a loopback WebSocket (tests): what `--service harness` runs, in this
19
- * process. Its workspaces live under the Tenant's `sandboxes` directory. Remote MCP servers go
20
- * through the Tenant's Tool Gate when it opens them, else they are authorized in this process.
19
+ * process. Its workspaces live under the Tenant's `sandboxes` directory, and its MCP servers
20
+ * open through the Tenant's Tool Gate.
21
21
  */
22
22
  export declare function startLoopbackHarness(ctx: TenantContext, options?: {
23
23
  sandboxBackends?: readonly SandboxBackend[];
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * The Tenant's own harness (D37): `@nylorun/harness/api` in this process, attached to the
3
3
  * Tenant's Harness API server over a memory channel, with the Tenant's model gate, MCP pool
4
- * and SandboxManager as its executors. `json` sends every frame through JSON and validates it,
5
- * as a socket will (tests).
4
+ * and SandboxManager as its executors. It uses the same seams and gates as a remote one
5
+ * (protocol 15): every model call goes through the Model Gate, every MCP server opens through
6
+ * the Tool Gate, and skill bytes come over `definition.file`. `json` sends every frame through
7
+ * JSON and validates it, as a socket will (tests).
6
8
  *
7
9
  * `startLoopbackHarness` (tests, `harness: "ws"`) runs a harness service in this process instead:
8
10
  * over a WebSocket to a Harness API listener on 127.0.0.1, with its own MCP pool and
@@ -10,31 +12,37 @@
10
12
  */
11
13
  import { randomBytes } from "node:crypto";
12
14
  import { DEFAULT_HARNESS, memoryChannels } from "@nylorun/core/harness-api";
15
+ import { sandboxToolLimitsOf } from "@nylorun/harness";
13
16
  import { createHarness } from "@nylorun/harness/api";
14
17
  import { harnessExecutors } from "../harness/executors.js";
15
- import { startHarnessService } from "../harness/service.js";
16
- import { authorize } from "../tenant/effects.js";
18
+ import { engineOptionsOf, startHarnessService } from "../harness/service.js";
17
19
  import { startHarnessListener } from "./ws-server.js";
20
+ import { hostTuning } from "../tuning.js";
18
21
  /** Where the Tenant's model calls go. */
19
22
  export function modelRouteOf(ctx) {
20
23
  return {
21
24
  tenantId: ctx.config.tenantId,
22
25
  modelGate: ctx.modelGate,
23
- modelProvider: ctx.modelProvider,
24
- useVaultModel: ctx.useVaultModel,
25
26
  };
26
27
  }
27
28
  export async function startInProcessHarness(ctx, tools, options = {}) {
28
29
  const channels = memoryChannels(options);
29
30
  const detach = ctx.harness.attach(channels.core, { name: "in-process" });
31
+ // Our engine's settings from the Host tuning (`engine`), and one configuration of the sandbox
32
+ // tools: what our engine's schemas name, and what runs them.
33
+ const engine = engineOptionsOf(hostTuning().engine);
34
+ const sandboxTools = sandboxToolLimitsOf(engine.sandboxTools);
30
35
  const harness = createHarness({
36
+ ...engine,
31
37
  channel: channels.harness,
32
38
  executors: harnessExecutors({
33
39
  model: modelRouteOf(ctx),
34
40
  toolGate: ctx.toolGate,
35
41
  mcp: tools.mcp,
36
42
  sandbox: () => tools.sandbox,
43
+ sandboxTools,
37
44
  }),
45
+ sandboxTools,
38
46
  logger: ctx.config.logger,
39
47
  // Our harness: its hello names our harness id (R4.1).
40
48
  name: DEFAULT_HARNESS,
@@ -50,8 +58,8 @@ export async function startInProcessHarness(ctx, tools, options = {}) {
50
58
  }
51
59
  /**
52
60
  * A harness service over a loopback WebSocket (tests): what `--service harness` runs, in this
53
- * process. Its workspaces live under the Tenant's `sandboxes` directory. Remote MCP servers go
54
- * through the Tenant's Tool Gate when it opens them, else they are authorized in this process.
61
+ * process. Its workspaces live under the Tenant's `sandboxes` directory, and its MCP servers
62
+ * open through the Tenant's Tool Gate.
55
63
  */
56
64
  export async function startLoopbackHarness(ctx, options = {}) {
57
65
  const token = randomBytes(32).toString("hex");
@@ -69,24 +77,18 @@ export async function startLoopbackHarness(ctx, options = {}) {
69
77
  token,
70
78
  paths: { sandboxes: ctx.config.paths.sandboxes },
71
79
  modelGate: route.modelGate,
72
- modelProvider: route.modelProvider,
73
- useVaultModel: route.useVaultModel,
74
80
  toolGate: ctx.toolGate,
75
- ...(ctx.toolGate.openMcp
76
- ? {}
77
- : { authorize: (sessionId, request) => authorize(ctx, sessionId, request) }),
78
- ...(ctx.config.delivery ? { delivery: ctx.config.delivery } : {}),
79
81
  ...(options.sandboxBackends ? { sandboxBackends: options.sandboxBackends } : {}),
80
82
  logger: quietLogger(ctx),
81
83
  ephemeral: ctx.config.mode === "ephemeral",
82
- backoff: { minMs: 50, maxMs: 500 },
84
+ backoff: hostTuning().harnessApi.loopbackBackoff,
83
85
  });
84
86
  let timer;
85
87
  try {
86
88
  await Promise.race([
87
89
  service.client.ready,
88
90
  new Promise((_, reject) => {
89
- timer = setTimeout(() => reject(new Error("The loopback harness did not connect")), 10_000);
91
+ timer = setTimeout(() => reject(new Error("The loopback harness did not connect")), hostTuning().harnessApi.loopbackConnectTimeoutMs);
90
92
  }),
91
93
  ]);
92
94
  }
@@ -1,13 +1,16 @@
1
1
  /**
2
2
  * The Record seam of the Harness API: core's journal for the effects of a run. `recordIntent`
3
3
  * journals an effect before anything runs it, and either answers it from the journal, hands it
4
- * to core's own executors (flow work, delegation, `save_artifact`, `read_artifact`,
5
- * `tool_search` and the skill tools), tells the harness to `execute` it (model calls; MCP, HTTP
6
- * and sandbox tools, a deferred MCP tool's `tool_call` among them, whose effect names the tool;
7
- * a flow's HTTP stages and HTTP verifiers), or fails a tool the Runtime cannot run (one that
8
- * would run the developer's code, R2 M6). `recordOutcome` records what the harness's call returned, an
9
- * agent's MCP or HTTP tool result first shaped to fit (R2b C11). Both run under the advance's
10
- * lease: every write is epoch-checked (`ownedSession`), and a lost epoch writes nothing.
4
+ * to core's own executors (flow work, delegation), tells the harness to `execute` it (model calls;
5
+ * a flow's HTTP stages and HTTP verifiers; an agent's tool call whose target the session has,
6
+ * protocol 15: an `mcp` tool it discovered and the turn leaves enabled, by `{server, tool}`; an
7
+ * `http` tool it declares; a `sandbox` operation when it has a sandbox; a `harness` tool), or
8
+ * fails a tool call it cannot run (no target, as an author's code tool has, R2 M6, or one the
9
+ * session lacks). A tool call is routed by its target, never by its name. `recordOutcome`
10
+ * records what the harness's call returned, as the harness reported it: one record entry, refused
11
+ * past the Runtime limit `recordEntryBytes` (`limit_exceeded`), since the harness shapes tool
12
+ * results, not core. Both run under the advance's lease: every write is epoch-checked
13
+ * (`ownedSession`), and a lost epoch writes nothing.
11
14
  *
12
15
  * Rows store the request's hash (`requestHash`) and, for a model call, the request without its
13
16
  * prompt: drift is a hash compare. Rows written before carry their full request, hashed on read.
@@ -27,8 +30,6 @@ export interface RecordScope {
27
30
  readonly ctx: TenantContext;
28
31
  readonly lease: Lease;
29
32
  readonly signal: AbortSignal;
30
- /** The tools the run's harness declared it runs itself (`hello` `features.tools`, R4.2), by name. */
31
- readonly nativeTools?: ReadonlySet<string>;
32
33
  /**
33
34
  * Who reported what this scope records (R4.9): the run's harness, so the events derived from
34
35
  * its intents and outcomes are its claim. Absent, core recorded what it saw itself.