@nylorun/runtime 0.15.2-beta → 0.17.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +68 -29
  3. package/dist/adapters/execution/restate.d.ts +9 -1
  4. package/dist/adapters/execution/restate.js +64 -5
  5. package/dist/adapters/sandbox/local.d.ts +10 -0
  6. package/dist/adapters/sandbox/local.js +195 -0
  7. package/dist/adapters/sandbox/virtual.js +36 -1
  8. package/dist/admin-openapi.json +341 -7
  9. package/dist/api/a2a/tasks.js +6 -1
  10. package/dist/api/ag-ui/endpoint.js +1 -2
  11. package/dist/api/ag-ui/history.js +6 -1
  12. package/dist/api/ag-ui/routes.d.ts +1 -1
  13. package/dist/api/ag-ui/routes.js +15 -13
  14. package/dist/api/components.d.ts +2667 -711
  15. package/dist/api/components.js +24 -11
  16. package/dist/api/gate/keys.d.ts +4 -3
  17. package/dist/api/gate/routes.d.ts +19 -3
  18. package/dist/api/gate/routes.js +162 -53
  19. package/dist/api/http/app.js +6 -0
  20. package/dist/api/http/define.d.ts +19 -12
  21. package/dist/api/http/define.js +13 -27
  22. package/dist/api/http/routes/access.d.ts +3 -19
  23. package/dist/api/http/routes/access.js +12 -172
  24. package/dist/api/http/routes/actions.js +0 -1
  25. package/dist/api/http/routes/artifacts.d.ts +11 -0
  26. package/dist/api/http/routes/artifacts.js +516 -0
  27. package/dist/api/http/routes/me.d.ts +13 -0
  28. package/dist/api/http/routes/me.js +45 -0
  29. package/dist/api/http/routes/sandboxes.d.ts +9 -0
  30. package/dist/api/http/routes/sandboxes.js +150 -0
  31. package/dist/api/http/routes/sessions.js +9 -13
  32. package/dist/api/http/routes/tenant.js +36 -2
  33. package/dist/api/http/routes/vaults.d.ts +12 -3
  34. package/dist/api/http/routes/vaults.js +122 -54
  35. package/dist/api/openapi.js +4 -10
  36. package/dist/artifacts/config.d.ts +23 -0
  37. package/dist/artifacts/config.js +27 -0
  38. package/dist/artifacts/export.d.ts +8 -0
  39. package/dist/artifacts/export.js +205 -0
  40. package/dist/artifacts/files.d.ts +36 -0
  41. package/dist/artifacts/files.js +45 -0
  42. package/dist/artifacts/folders.d.ts +60 -0
  43. package/dist/artifacts/folders.js +209 -0
  44. package/dist/artifacts/links.d.ts +28 -0
  45. package/dist/artifacts/links.js +92 -0
  46. package/dist/artifacts/media-types.d.ts +13 -0
  47. package/dist/artifacts/media-types.js +80 -0
  48. package/dist/artifacts/parts.d.ts +34 -0
  49. package/dist/artifacts/parts.js +38 -0
  50. package/dist/artifacts/service.d.ts +74 -0
  51. package/dist/artifacts/service.js +301 -0
  52. package/dist/artifacts/workspace.d.ts +60 -0
  53. package/dist/artifacts/workspace.js +57 -0
  54. package/dist/artifacts/zip.d.ts +10 -0
  55. package/dist/artifacts/zip.js +126 -0
  56. package/dist/blob/fs.d.ts +13 -0
  57. package/dist/blob/fs.js +194 -0
  58. package/dist/blob/index.d.ts +9 -0
  59. package/dist/blob/index.js +9 -0
  60. package/dist/blob/s3.d.ts +42 -0
  61. package/dist/blob/s3.js +366 -0
  62. package/dist/blob/types.d.ts +108 -0
  63. package/dist/blob/types.js +85 -0
  64. package/dist/core/sandbox-routes.d.ts +2 -2
  65. package/dist/core/sandbox-routes.js +4 -3
  66. package/dist/execution/memory.d.ts +11 -1
  67. package/dist/execution/memory.js +77 -3
  68. package/dist/execution/types.d.ts +54 -1
  69. package/dist/execution/types.js +31 -0
  70. package/dist/gates/contract.d.ts +14 -13
  71. package/dist/gates/contract.js +13 -12
  72. package/dist/gates/egress.d.ts +72 -0
  73. package/dist/gates/egress.js +319 -0
  74. package/dist/gates/handler.d.ts +3 -0
  75. package/dist/gates/handler.js +3 -2
  76. package/dist/gates/http-client.d.ts +3 -2
  77. package/dist/gates/http-client.js +17 -6
  78. package/dist/gates/in-process.d.ts +13 -1
  79. package/dist/gates/in-process.js +11 -0
  80. package/dist/gates/inflight.d.ts +23 -4
  81. package/dist/gates/inflight.js +18 -4
  82. package/dist/gates/mcp-handler.js +5 -1
  83. package/dist/gates/model-gate.d.ts +6 -2
  84. package/dist/gates/tenant-vaults.d.ts +18 -9
  85. package/dist/gates/tenant-vaults.js +17 -12
  86. package/dist/gates/tool-calls.d.ts +9 -5
  87. package/dist/gates/tool-calls.js +6 -6
  88. package/dist/gates/tool-client.d.ts +9 -2
  89. package/dist/gates/tool-client.js +56 -22
  90. package/dist/gates/tool-contract.d.ts +12 -7
  91. package/dist/gates/tool-contract.js +10 -4
  92. package/dist/gates/tool-gate.d.ts +1 -0
  93. package/dist/harness/calls.d.ts +52 -0
  94. package/dist/harness/calls.js +113 -0
  95. package/dist/harness/executors.d.ts +22 -0
  96. package/dist/harness/executors.js +96 -0
  97. package/dist/harness/main.d.ts +6 -0
  98. package/dist/harness/main.js +103 -0
  99. package/dist/harness/pod.d.ts +21 -0
  100. package/dist/harness/pod.js +119 -0
  101. package/dist/harness/run-tokens.d.ts +18 -0
  102. package/dist/harness/run-tokens.js +15 -0
  103. package/dist/harness/service.d.ts +46 -0
  104. package/dist/harness/service.js +179 -0
  105. package/dist/harness/ws-client.d.ts +43 -0
  106. package/dist/harness/ws-client.js +122 -0
  107. package/dist/harness/ws-port.d.ts +22 -0
  108. package/dist/harness/ws-port.js +78 -0
  109. package/dist/harness-api/in-process.d.ts +24 -0
  110. package/dist/harness-api/in-process.js +119 -0
  111. package/dist/harness-api/record.d.ts +44 -0
  112. package/dist/harness-api/record.js +250 -0
  113. package/dist/harness-api/renew.d.ts +37 -0
  114. package/dist/harness-api/renew.js +77 -0
  115. package/dist/harness-api/server.d.ts +104 -0
  116. package/dist/harness-api/server.js +408 -0
  117. package/dist/harness-api/start.d.ts +36 -0
  118. package/dist/harness-api/start.js +118 -0
  119. package/dist/harness-api/workspace.d.ts +53 -0
  120. package/dist/harness-api/workspace.js +201 -0
  121. package/dist/harness-api/ws-server.d.ts +34 -0
  122. package/dist/harness-api/ws-server.js +227 -0
  123. package/dist/host/admin-api.d.ts +12 -5
  124. package/dist/host/admin-api.js +72 -6
  125. package/dist/host/app.d.ts +0 -1
  126. package/dist/host/app.js +35 -24
  127. package/dist/host/config-for.d.ts +4 -0
  128. package/dist/host/config-for.js +2 -0
  129. package/dist/host/config.d.ts +0 -6
  130. package/dist/host/create-host.d.ts +3 -10
  131. package/dist/host/create-host.js +4 -1
  132. package/dist/host/gates.d.ts +27 -2
  133. package/dist/host/gates.js +52 -2
  134. package/dist/host/http.d.ts +4 -1
  135. package/dist/host/http.js +6 -1
  136. package/dist/host/main.js +167 -37
  137. package/dist/host/stack-config.d.ts +112 -9
  138. package/dist/host/stack-config.js +222 -20
  139. package/dist/index.d.ts +3 -0
  140. package/dist/index.js +2 -0
  141. package/dist/keys/client.js +9 -1
  142. package/dist/keys/keys.d.ts +12 -2
  143. package/dist/keys/keys.js +8 -0
  144. package/dist/mcp/connect.js +2 -1
  145. package/dist/mcp/pool.d.ts +1 -0
  146. package/dist/mcp/pool.js +1 -0
  147. package/dist/mcp/snapshot.d.ts +8 -1
  148. package/dist/mcp/snapshot.js +28 -0
  149. package/dist/media.d.ts +5 -24
  150. package/dist/media.js +5 -0
  151. package/dist/model/pi-model.d.ts +6 -2
  152. package/dist/model/pi-model.js +39 -22
  153. package/dist/node/index.d.ts +1 -1
  154. package/dist/node/index.js +1 -1
  155. package/dist/openapi.json +14084 -9426
  156. package/dist/record/index.d.ts +1 -0
  157. package/dist/record/index.js +1 -0
  158. package/dist/record/sandbox.d.ts +25 -0
  159. package/dist/record/sandbox.js +43 -0
  160. package/dist/sandbox/capability.d.ts +3 -0
  161. package/dist/sandbox/capability.js +10 -0
  162. package/dist/sandbox/egress-token.d.ts +26 -0
  163. package/dist/sandbox/egress-token.js +8 -0
  164. package/dist/sandbox/join.d.ts +37 -0
  165. package/dist/sandbox/join.js +165 -0
  166. package/dist/sandbox/manager.d.ts +99 -34
  167. package/dist/sandbox/manager.js +216 -84
  168. package/dist/sandbox/placement.d.ts +27 -0
  169. package/dist/sandbox/placement.js +44 -0
  170. package/dist/sandbox/pods/client.d.ts +90 -0
  171. package/dist/sandbox/pods/client.js +79 -0
  172. package/dist/sandbox/pods/lifecycle.d.ts +91 -0
  173. package/dist/sandbox/pods/lifecycle.js +178 -0
  174. package/dist/sandbox/pods/name.d.ts +1 -0
  175. package/dist/sandbox/pods/name.js +28 -0
  176. package/dist/sandbox/pods/network-gate.d.ts +25 -0
  177. package/dist/sandbox/pods/network-gate.js +71 -0
  178. package/dist/sandbox/pods/reconcile.d.ts +11 -0
  179. package/dist/sandbox/pods/reconcile.js +152 -0
  180. package/dist/sandbox/pods/spec.d.ts +45 -0
  181. package/dist/sandbox/pods/spec.js +62 -0
  182. package/dist/sandbox/records.d.ts +29 -0
  183. package/dist/sandbox/records.js +79 -0
  184. package/dist/sandbox/resolve.d.ts +5 -0
  185. package/dist/sandbox/resolve.js +3 -2
  186. package/dist/sandbox/session-sandbox.js +7 -5
  187. package/dist/sandbox/share.d.ts +11 -0
  188. package/dist/sandbox/share.js +10 -0
  189. package/dist/sandbox/tenant-config.d.ts +16 -1
  190. package/dist/sandbox/tenant-config.js +9 -0
  191. package/dist/sandbox/types.d.ts +29 -1
  192. package/dist/sandbox/types.js +13 -1
  193. package/dist/store/postgres/drizzle/0004_sandbox_resources.sql +20 -0
  194. package/dist/store/postgres/drizzle/0005_artifacts.sql +27 -0
  195. package/dist/store/postgres/drizzle/0006_folder_artifacts.sql +12 -0
  196. package/dist/store/postgres/drizzle/0007_sandbox_pods.sql +14 -0
  197. package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
  198. package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
  199. package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
  200. package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
  201. package/dist/store/postgres/record-writer.d.ts +6 -1
  202. package/dist/store/postgres/record-writer.js +20 -2
  203. package/dist/store/postgres/schema.d.ts +1168 -250
  204. package/dist/store/postgres/schema.js +174 -28
  205. package/dist/store/postgres/store.js +339 -103
  206. package/dist/store/postgres/tenant.d.ts +1 -1
  207. package/dist/store/postgres/tenant.js +2 -2
  208. package/dist/store/types.d.ts +163 -32
  209. package/dist/streams/control.d.ts +2 -5
  210. package/dist/streams/control.js +5 -8
  211. package/dist/streams/types.d.ts +8 -8
  212. package/dist/streams/types.js +1 -1
  213. package/dist/tenant/advance.js +218 -160
  214. package/dist/tenant/artifact-tool.d.ts +5 -0
  215. package/dist/tenant/artifact-tool.js +119 -0
  216. package/dist/tenant/auth.d.ts +13 -9
  217. package/dist/tenant/auth.js +25 -32
  218. package/dist/tenant/commands.js +31 -10
  219. package/dist/tenant/context.d.ts +83 -26
  220. package/dist/tenant/delivery-token.js +4 -4
  221. package/dist/tenant/effects.d.ts +24 -14
  222. package/dist/tenant/effects.js +10 -398
  223. package/dist/tenant/ephemeral.d.ts +14 -2
  224. package/dist/tenant/ephemeral.js +8 -1
  225. package/dist/tenant/history.d.ts +13 -23
  226. package/dist/tenant/history.js +14 -62
  227. package/dist/tenant/host-token.d.ts +92 -0
  228. package/dist/tenant/host-token.js +179 -0
  229. package/dist/tenant/identity-file.d.ts +75 -0
  230. package/dist/tenant/identity-file.js +202 -0
  231. package/dist/tenant/issuers.d.ts +48 -0
  232. package/dist/tenant/issuers.js +311 -0
  233. package/dist/tenant/jwt.js +3 -3
  234. package/dist/tenant/module.js +1 -0
  235. package/dist/tenant/operator-keys.d.ts +18 -0
  236. package/dist/tenant/operator-keys.js +55 -0
  237. package/dist/tenant/outbound.d.ts +30 -0
  238. package/dist/tenant/outbound.js +142 -10
  239. package/dist/tenant/paths.d.ts +2 -1
  240. package/dist/tenant/paths.js +3 -1
  241. package/dist/tenant/principals.d.ts +2 -11
  242. package/dist/tenant/principals.js +10 -30
  243. package/dist/tenant/reset.d.ts +5 -2
  244. package/dist/tenant/reset.js +22 -2
  245. package/dist/tenant/run-grants.d.ts +49 -0
  246. package/dist/tenant/run-grants.js +62 -0
  247. package/dist/tenant/run-token.d.ts +70 -0
  248. package/dist/tenant/run-token.js +138 -0
  249. package/dist/tenant/runtime.d.ts +42 -0
  250. package/dist/tenant/runtime.js +134 -33
  251. package/dist/tenant/sandboxes.d.ts +119 -0
  252. package/dist/tenant/sandboxes.js +551 -0
  253. package/dist/tenant/session-streams.d.ts +6 -10
  254. package/dist/tenant/session-streams.js +9 -27
  255. package/dist/tenant/session.d.ts +2 -13
  256. package/dist/tenant/session.js +4 -28
  257. package/dist/tenant/sessions.d.ts +6 -1
  258. package/dist/tenant/sessions.js +29 -2
  259. package/dist/tenant/signing-keys.d.ts +2 -1
  260. package/dist/tenant/signing-keys.js +2 -2
  261. package/dist/tenant/status.d.ts +8 -3
  262. package/dist/tenant/status.js +5 -0
  263. package/dist/tenant/streams.d.ts +8 -2
  264. package/dist/tenant/streams.js +25 -6
  265. package/dist/tenant/sweep.js +37 -4
  266. package/dist/tenant/types.d.ts +39 -1
  267. package/dist/tenant/worker.d.ts +11 -5
  268. package/dist/tenant/worker.js +12 -6
  269. package/dist/vault/crypto.d.ts +7 -0
  270. package/dist/vault/crypto.js +16 -0
  271. package/dist/vault/oauth.d.ts +55 -0
  272. package/dist/vault/oauth.js +186 -0
  273. package/dist/vault/service.d.ts +54 -7
  274. package/dist/vault/service.js +261 -17
  275. package/dist/vault/sources.d.ts +54 -0
  276. package/dist/vault/sources.js +185 -0
  277. package/dist/version.d.ts +1 -1
  278. package/dist/version.js +1 -1
  279. package/package.json +14 -10
  280. package/dist/adapters/media.d.ts +0 -24
  281. package/dist/adapters/media.js +0 -111
  282. package/dist/host/cors.d.ts +0 -27
  283. package/dist/host/cors.js +0 -64
  284. package/dist/tenant/access-policy.d.ts +0 -30
  285. package/dist/tenant/access-policy.js +0 -52
  286. package/dist/tenant/browser.d.ts +0 -15
  287. package/dist/tenant/browser.js +0 -39
  288. package/dist/tenant/subject-limits.d.ts +0 -16
  289. package/dist/tenant/subject-limits.js +0 -45
  290. package/dist/tenant/tokens.d.ts +0 -14
  291. package/dist/tenant/tokens.js +0 -180
@@ -1,16 +1,8 @@
1
- /**
2
- * Pure helpers over the session document: turn-manifest pins and variants, state rebasing,
3
- * the tools of an MCP snapshot, and the event target of an Action. No store access and no I/O.
4
- *
5
- * Later waves: stable; the async store conversion (Wave 1 / A) does not change these.
6
- */
7
- import { type DurableSessionTool, type HostEffect } from "@nylorun/harness/run";
8
1
  import type { Action } from "@nylorun/core/contracts";
9
- import { type AgentManifest, type JsonValue } from "@nylorun/core/define";
2
+ import type { AgentManifest, JsonValue } from "@nylorun/core/define";
10
3
  import { type TurnManifestStore } from "../core/turn-manifest.js";
11
- import type { McpSnapshot, McpToolRecord } from "../mcp/snapshot.js";
12
4
  import type { Session } from "./context.js";
13
- export declare function sessionToolsOf(snapshot: McpSnapshot | undefined): readonly DurableSessionTool[] | undefined;
5
+ export { manifestFor, sessionToolsOf } from "../mcp/snapshot.js";
14
6
  /** Turn-manifest store backed by the session's `variants` map (no schema change). */
15
7
  export declare function variantStore(session: Session): TurnManifestStore;
16
8
  /** Manifest this turn's checkpoint is pinned to (session pin or a stored variant). */
@@ -18,9 +10,6 @@ export declare function turnManifestOf(session: Session): AgentManifest;
18
10
  /** Wrap a linked agent turn so the Loop learns the turn's manifest (SD-I4 pin stays on session). */
19
11
  export declare function linkedAgentOutput(session: Session, output: JsonValue | undefined): JsonValue;
20
12
  export declare function rebaseSessionState(session: Session, turnHash: string): void;
21
- export declare function mcpToolOf(session: Session, request: Pick<HostEffect, "agent" | "capabilityId" | "toolName">): McpToolRecord | undefined;
22
- /** The manifest of the agent an effect belongs to: the root, or an agent it uses as a tool. */
23
- export declare function manifestFor(manifest: AgentManifest, agent: HostEffect["agent"]): AgentManifest | undefined;
24
13
  export declare function pinnedTool(manifest: AgentManifest, capabilityId?: string, toolName?: string): import("@nylorun/core/define").ToolManifest | undefined;
25
14
  /** What an action runs, for events: a tool name, or a hook point and its capabilities. */
26
15
  export declare function actionTarget(action: Action): {
@@ -1,29 +1,14 @@
1
1
  /**
2
2
  * Pure helpers over the session document: turn-manifest pins and variants, state rebasing,
3
- * the tools of an MCP snapshot, and the event target of an Action. No store access and no I/O.
3
+ * and the event target of an Action. No store access and no I/O. (The tools of an MCP snapshot
4
+ * live in `mcp/snapshot.ts`.)
4
5
  *
5
6
  * Later waves: stable; the async store conversion (Wave 1 / A) does not change these.
6
7
  */
7
- import { agentTurnValue, } from "@nylorun/harness/run";
8
- import { delegateManifest, } from "@nylorun/core/define";
8
+ import { agentTurnValue } from "@nylorun/harness/run";
9
9
  import { isWorkflowManifest } from "../core/flow-host.js";
10
10
  import { allowedManifestHashes, rebaseTurnState, } from "../core/turn-manifest.js";
11
- export function sessionToolsOf(snapshot) {
12
- if (!snapshot?.mcpTools.length)
13
- return undefined;
14
- return snapshot.mcpTools.map((tool) => ({
15
- ...(tool.agentId === undefined ? {} : { agentId: tool.agentId }),
16
- capabilityId: tool.capabilityId,
17
- name: tool.name,
18
- ...(tool.description === undefined
19
- ? {}
20
- : { description: tool.description }),
21
- inputSchema: tool.inputSchema,
22
- ...(tool.outputSchema === undefined
23
- ? {}
24
- : { outputSchema: tool.outputSchema }),
25
- }));
26
- }
11
+ export { manifestFor, sessionToolsOf } from "../mcp/snapshot.js";
27
12
  /** Turn-manifest store backed by the session's `variants` map (no schema change). */
28
13
  export function variantStore(session) {
29
14
  return {
@@ -61,15 +46,6 @@ export function rebaseSessionState(session, turnHash) {
61
46
  if (next !== session.state)
62
47
  session.state = next;
63
48
  }
64
- export function mcpToolOf(session, request) {
65
- return session.mcpSnapshot?.mcpTools.find((tool) => tool.agentId === request.agent?.id &&
66
- tool.capabilityId === request.capabilityId &&
67
- tool.name === request.toolName);
68
- }
69
- /** The manifest of the agent an effect belongs to: the root, or an agent it uses as a tool. */
70
- export function manifestFor(manifest, agent) {
71
- return agent ? delegateManifest(manifest, agent.id) : manifest;
72
- }
73
49
  export function pinnedTool(manifest, capabilityId, toolName) {
74
50
  const capability = manifest.capabilities.find((item) => item.id === capabilityId);
75
51
  return capability?.tools?.find((tool) => tool.name === toolName);
@@ -15,7 +15,7 @@ export declare function listDefinitions(ctx: TenantContext): Promise<{
15
15
  }[];
16
16
  }>;
17
17
  /**
18
- * The agents a subject token or publishable key may see: id, name and description only, never
18
+ * The agents a token caller may see: id, name and description only, never
19
19
  * instructions, tools or MCP servers, and only the agents allowed.
20
20
  */
21
21
  export declare function listAgentsPublic(ctx: TenantContext, agents: ReadonlySet<string> | "*"): Promise<{
@@ -59,6 +59,11 @@ export declare function putSession(ctx: TenantContext, id: string, body: PutSess
59
59
  * session): an existing one must have the same owner and agent, or it is a 404.
60
60
  */
61
61
  createOnly?: boolean;
62
+ /**
63
+ * The sandboxes the caller reaches (a token caller's grants, `sandboxGrantsOf`); undefined
64
+ * reaches every one. `sandbox: { id }` must name one of them.
65
+ */
66
+ sandboxGrants?: readonly string[];
62
67
  }): Promise<Session>;
63
68
  /** The session resource returned by `GET`/`PUT /v1/sessions/:id`. */
64
69
  export declare function sessionView(t: Tx, s: Session): Promise<unknown>;
@@ -12,6 +12,8 @@ import { resolveSandbox } from "../sandbox/resolve.js";
12
12
  import { withSandboxCapability } from "../sandbox/session-sandbox.js";
13
13
  import { declaredSandboxes, sandboxSpecOf, sessionSandboxSpec, } from "../sandbox/share.js";
14
14
  import { effectiveSandboxConfig, readSandboxConfig } from "../sandbox/tenant-config.js";
15
+ import { checkPlacement, requirePods } from "../sandbox/placement.js";
16
+ import { attachSandbox, recordAttachment } from "./sandboxes.js";
15
17
  import { sandboxLookup, } from "./context.js";
16
18
  import { fail } from "./http.js";
17
19
  /** A session's creation identity: everything but the request id and vault attachments. */
@@ -31,7 +33,7 @@ export async function listDefinitions(ctx) {
31
33
  };
32
34
  }
33
35
  /**
34
- * The agents a subject token or publishable key may see: id, name and description only, never
36
+ * The agents a token caller may see: id, name and description only, never
35
37
  * instructions, tools or MCP servers, and only the agents allowed.
36
38
  */
37
39
  export async function listAgentsPublic(ctx, agents) {
@@ -133,7 +135,14 @@ export async function putSession(ctx, id, body, access, options = {}) {
133
135
  }
134
136
  const definition = (await t.get("definitions", body.agentId)) ??
135
137
  fail(404, "Definition not found");
136
- const sandbox = await sessionSandbox(t, body, definition, { opaque });
138
+ const sandbox = await sessionSandbox(t, body, definition, {
139
+ opaque,
140
+ ...(options.sandboxGrants === undefined ? {} : { grants: options.sandboxGrants }),
141
+ });
142
+ // Placement (D38): where this session's harness may run, decided now and kept.
143
+ checkPlacement(await readSandboxConfig(t), sandbox.kind);
144
+ if (sandbox.kind === "pod")
145
+ await requirePods(ctx.pods, `Sandbox ${sandbox.sandboxId}`);
137
146
  const created = {
138
147
  id,
139
148
  agentId: body.agentId,
@@ -151,23 +160,40 @@ export async function putSession(ctx, id, body, access, options = {}) {
151
160
  ...(sandbox.sandboxOwnerId !== undefined
152
161
  ? { sandboxOwnerId: sandbox.sandboxOwnerId }
153
162
  : {}),
163
+ ...(sandbox.sandboxId !== undefined ? { sandboxId: sandbox.sandboxId } : {}),
154
164
  ...(sandbox.spec !== undefined
155
165
  ? { sandbox: sandbox.spec, sandboxSource: sandbox.source }
156
166
  : {}),
157
167
  };
158
168
  await t.put("sessions", id, created);
159
169
  await ctx.vault.recordAttachment(t, id, vaultIds);
170
+ if (sandbox.sandboxId !== undefined)
171
+ await recordAttachment(t, id, sandbox.sandboxId);
160
172
  return created;
161
173
  });
162
174
  }
163
175
  function isShare(value) {
164
176
  return typeof value === "object" && value !== null && "session" in value;
165
177
  }
178
+ function isAttach(value) {
179
+ return typeof value === "object" && value !== null && "id" in value;
180
+ }
166
181
  /** Decide a new session's sandbox and, for an agent, the manifest that carries its tools. */
167
182
  async function sessionSandbox(t, body, definition, options) {
168
183
  const request = body.sandbox;
169
184
  const declared = sandboxSpecOf(definition.manifest) !== undefined;
185
+ if (isAttach(request)) {
186
+ if (declared)
187
+ fail(400, `'${body.agentId}' declares its own sandbox with .sandbox(). Remove it from the agent to attach a sandbox.`);
188
+ const sandbox = await attachSandbox(t, request.id, options.grants);
189
+ // A pod's storage and lifecycle are the resource's, not the agent's sandbox manifest.
190
+ const { storage: _storage, lifecycle: _lifecycle, ...spec } = sandbox.spec;
191
+ return { ...(await pin(definition, spec, "sandbox")), sandboxId: sandbox.id, kind: sandbox.kind };
192
+ }
170
193
  if (isShare(request)) {
194
+ const owner = await t.get("sessions", request.session);
195
+ if (owner?.sandboxId !== undefined && (!options.opaque || owner.ownerUserId === body.ownerUserId))
196
+ fail(400, `Session ${request.session} is attached to sandbox ${owner.sandboxId}: open this session with sandbox: { id: "${owner.sandboxId}" }.`);
171
197
  const lookup = await sandboxLookup(t, request.session);
172
198
  const sandboxOwnerId = validateSandboxAttach(body, definition.manifest, lookup, {
173
199
  opaque: options.opaque,
@@ -240,6 +266,7 @@ export async function sessionView(t, s) {
240
266
  vaultIds: s.vaultIds ?? [],
241
267
  credentialSelections: s.credentialSelections ?? [],
242
268
  sandboxOwnerId: s.sandboxOwnerId ?? null,
269
+ ...(s.sandboxId === undefined ? {} : { sandboxId: s.sandboxId }),
243
270
  sandbox: s.sandbox ?? null,
244
271
  ...(s.sandboxSource ? { sandboxSource: s.sandboxSource } : {}),
245
272
  mcpSnapshot: s.mcpSnapshot ?? null,
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Tenant signing keys for subject tokens. Each key is an ES256 pair: the public JWK is stored
2
+ * Tenant signing keys for the tokens the Runtime signs itself (delivery tokens, capability
3
+ * links, run and host tokens). Each key is an ES256 pair: the public JWK is stored
3
4
  * as is, the PKCS#8 private key is sealed with the vault KEK (AAD bound to the Tenant and key
4
5
  * id), like a credential.
5
6
  *
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Tenant signing keys for subject tokens. Each key is an ES256 pair: the public JWK is stored
2
+ * Tenant signing keys for the tokens the Runtime signs itself (delivery tokens, capability
3
+ * links, run and host tokens). Each key is an ES256 pair: the public JWK is stored
3
4
  * as is, the PKCS#8 private key is sealed with the vault KEK (AAD bound to the Tenant and key
4
5
  * id), like a credential.
5
6
  *
@@ -117,7 +118,6 @@ export class SigningKeys {
117
118
  const [previous] = await t.signingKeys(["previous"]);
118
119
  if (previous && !force) {
119
120
  const retired = Date.parse(previous.retiredAt ?? previous.createdAt);
120
- // Delivery tokens may outlive the policy's subject tokens.
121
121
  const longest = Math.max(maxTtlSeconds, DELIVERY_TOKEN_MAX_TTL_SECONDS);
122
122
  const until = retired + longest * 1000 + ROTATION_GRACE_MS;
123
123
  if (until > now.getTime())
@@ -1,8 +1,9 @@
1
- import type { SeedTenantConfigRequest, SeedTenantConfigResponse, TenantStatus } from "@nylorun/core/contracts";
1
+ import type { HarnessStatus, SeedTenantConfigRequest, SeedTenantConfigResponse, TenantStatus } from "@nylorun/core/contracts";
2
2
  import type { SessionStore } from "../store/types.js";
3
3
  import type { VaultService } from "../vault/service.js";
4
- import type { SandboxManager } from "../sandbox/manager.js";
4
+ import type { WorkspacePort } from "../harness-api/workspace.js";
5
5
  import type { TenantConfig } from "./types.js";
6
+ import type { TenantContext } from "./context.js";
6
7
  import type { TenantEnvelope } from "@nylorun/core/contracts";
7
8
  import type { StuckInvocation } from "../execution/types.js";
8
9
  import type { StreamsStatus } from "./streams.js";
@@ -12,7 +13,7 @@ export interface TenantStatusContext {
12
13
  config: TenantConfig;
13
14
  store: SessionStore;
14
15
  vault: VaultService;
15
- sandbox: SandboxManager;
16
+ sandbox: Pick<WorkspacePort, "report">;
16
17
  closing: boolean;
17
18
  modelConfigured: boolean;
18
19
  /**
@@ -22,7 +23,11 @@ export interface TenantStatusContext {
22
23
  stuckInvocations?: () => Promise<StuckInvocation[]>;
23
24
  /** This Tenant's Durable Streams status (`streamsStatus` in `streams.ts`). */
24
25
  streamsStatus?: () => Promise<StreamsStatus>;
26
+ /** The Tenant's harnesses (F6.2). */
27
+ harness?: HarnessStatus;
25
28
  }
29
+ /** The Tenant's harnesses, for its status and the Host's. */
30
+ export declare function harnessStatusOf(ctx: Pick<TenantContext, "mcp" | "harness">): HarnessStatus;
26
31
  /** Build `TenantStatusSchema` with secrets redacted (A14). */
27
32
  export declare function buildTenantStatus(ctx: TenantStatusContext): Promise<TenantStatus>;
28
33
  /**
@@ -1,5 +1,9 @@
1
1
  import { FIXTURE_MODEL_SETTING, seedFixtureModel } from "./model-setting.js";
2
2
  import { SANDBOX_CONFIG_SETTING, writeSandboxConfig } from "../sandbox/tenant-config.js";
3
+ /** The Tenant's harnesses, for its status and the Host's. */
4
+ export function harnessStatusOf(ctx) {
5
+ return { mode: ctx.mcp ? "in-process" : "remote", ...ctx.harness.status() };
6
+ }
3
7
  /** How long status waits for the execution to list stuck invocations. */
4
8
  const STUCK_TIMEOUT_MS = 5000;
5
9
  /** `TenantStatus.execution`: paused and backing-off invocations, or why they are unknown. */
@@ -84,6 +88,7 @@ export async function buildTenantStatus(ctx) {
84
88
  backend: sandboxReport.backend,
85
89
  retained: counts.sandboxes,
86
90
  },
91
+ ...(ctx.harness ? { harness: ctx.harness } : {}),
87
92
  ...(execution ? { execution } : {}),
88
93
  ...(streams ? { streams } : {}),
89
94
  };
@@ -17,8 +17,8 @@
17
17
  * - **Readers.** History and session SSE read the session's stream
18
18
  * (`tenant/session-streams.ts`). This module runs one `tenant/control` reader per Tenant per
19
19
  * process, on the current basin, which calls `ctx.abortLocal` for each `session.cancel`,
20
- * checks the session streams for each `sessions.reset`, and ends a subject's streams for
21
- * each `subject.revoked`.
20
+ * passes an Action's outcome to a run held here for each `action.resolved` (F6.2), and
21
+ * checks the session streams for each `sessions.reset`.
22
22
  *
23
23
  * The caller passes the streams: the Host's S2 streams, or `MemoryStreams` for tests and a
24
24
  * local development Host (not durable).
@@ -78,6 +78,12 @@ export declare function wireStreams(ctx: TenantContext, options: WireStreamsOpti
78
78
  * effect), so failures are logged, never thrown.
79
79
  */
80
80
  export declare function signalSessionCancel(ctx: TenantContext, sessionId: string, turnId: string | null): void;
81
+ /**
82
+ * Appends `action.resolved` for an Action whose outcome just committed, so a run held for it
83
+ * by a harness of another process goes on (F6.2). Call it from `t.afterCommit`. A lost signal
84
+ * costs the hold's time only: the segment then ends as waiting and resumes by replay.
85
+ */
86
+ export declare function signalActionOutcome(ctx: TenantContext, sessionId: string, actionId: string): void;
81
87
  /**
82
88
  * After a reset deleted the Tenant's sessions and moved it to a new basin generation: tells
83
89
  * every process with the Tenant open (`sessions.reset` on the old basin's control stream),
@@ -1,8 +1,8 @@
1
1
  import { basinOf } from "../streams/basin.js";
2
- import { signalCancel, signalSessionsReset } from "../streams/control.js";
2
+ import { signalActionResolved, signalCancel, signalSessionsReset } from "../streams/control.js";
3
3
  import { createStreamRelay, } from "../streams/relay/core.js";
4
4
  import { CONTROL_STREAM, } from "../streams/types.js";
5
- import { checkSessionStreams, currentBasin, endSubjectStreams, sleep, } from "./session-streams.js";
5
+ import { checkSessionStreams, currentBasin, sleep, } from "./session-streams.js";
6
6
  const RETRY_MIN_MS = 100;
7
7
  const RETRY_MAX_MS = 2000;
8
8
  /** The longest wait between two `ensureTenant` attempts while the basin is being repaired. */
@@ -59,12 +59,12 @@ export async function wireStreams(ctx, options) {
59
59
  const signal = body;
60
60
  if (signal?.type === "session.cancel" && typeof signal.sessionId === "string")
61
61
  ctx.abortLocal(signal.sessionId, typeof signal.turnId === "string" ? signal.turnId : undefined);
62
+ else if (signal?.type === "action.resolved" &&
63
+ typeof signal.sessionId === "string" &&
64
+ typeof signal.actionId === "string")
65
+ void resolveHeld(ctx, signal.sessionId, signal.actionId).catch(report("held run resolution failed"));
62
66
  else if (signal?.type === "sessions.reset")
63
67
  void checkSessionStreams(ctx).catch(report("session stream check failed"));
64
- else if (signal?.type === "subject.revoked" &&
65
- typeof signal.subject === "string" &&
66
- typeof signal.epoch === "number")
67
- endSubjectStreams(ctx.sessionStreams, signal.subject, signal.epoch);
68
68
  }, report("control stream read failed; retrying"));
69
69
  // Signals appended from here on reach this process.
70
70
  await startControl();
@@ -171,6 +171,25 @@ export function signalSessionCancel(ctx, sessionId, turnId) {
171
171
  message: messageOf(error),
172
172
  }));
173
173
  }
174
+ /**
175
+ * Appends `action.resolved` for an Action whose outcome just committed, so a run held for it
176
+ * by a harness of another process goes on (F6.2). Call it from `t.afterCommit`. A lost signal
177
+ * costs the hold's time only: the segment then ends as waiting and resumes by replay.
178
+ */
179
+ export function signalActionOutcome(ctx, sessionId, actionId) {
180
+ const streams = ctx.sessionStreams.wiring?.streams;
181
+ if (!streams)
182
+ return;
183
+ void signalActionResolved(streams, currentBasin(ctx), sessionId, actionId).catch((error) => ctx.config.logger.warn("action resolved signal failed", { sessionId, message: messageOf(error) }));
184
+ }
185
+ /** Passes an Action's recorded outcome to the run of `sessionId` held here, if any. */
186
+ async function resolveHeld(ctx, sessionId, actionId) {
187
+ if (!ctx.harness.holds(sessionId))
188
+ return;
189
+ const effect = await ctx.store.tx((t) => t.get("effects", actionId));
190
+ if (effect?.status === "completed" && effect.outcome)
191
+ ctx.harness.resolved(sessionId, actionId, effect.outcome);
192
+ }
174
193
  /**
175
194
  * After a reset deleted the Tenant's sessions and moved it to a new basin generation: tells
176
195
  * every process with the Tenant open (`sessions.reset` on the old basin's control stream),
@@ -9,9 +9,10 @@
9
9
  * 2. **Orphaned sessions.** `running` or `runnable` sessions with no owner, or an owner whose
10
10
  * lease expired, are woken with reason `recover`: a wake lost between commit and send, or
11
11
  * a Worker that died mid-advance (the advance then takes over, §11.4).
12
- * 3. **Sandboxes.** Idle sandboxes are stopped, records of compute this process no longer
13
- * holds are marked stopped, and sandboxes whose session is gone are removed. Idle MCP
14
- * connections are closed.
12
+ * 3. **Sandboxes.** Idle sandboxes are stopped, records of compute no longer held are marked
13
+ * stopped, and sandboxes whose session or sandbox resource is gone are removed, by the
14
+ * workspace capability (`harness-api/workspace.ts`: here, or in the harness that serves
15
+ * workspaces). Idle MCP connections of the in-process harness are closed.
15
16
  * 4. **Hooks.** Callbacks registered with `ctx.onSweep`.
16
17
  * 5. **Deliveries.** Deliveries to Action endpoints whose deadline passed without an answer are
17
18
  * lost (`delivery.ts` `loseAction`), and pending Actions of agents with an endpoint are sent
@@ -38,9 +39,12 @@ export async function sweep(ctx) {
38
39
  () => ctx.sandbox.sweep({
39
40
  now: now.getTime(),
40
41
  sessionExists: async (id) => !!(await ctx.store.tx((t) => t.get("sessions", id))),
42
+ sandboxExists: async (id) => (await ctx.store.tx((t) => t.sandboxResource(id))) !== undefined,
41
43
  }),
42
44
  ],
43
- ["mcp", () => ctx.mcp.sweep()],
45
+ ["pods", () => reconcileStalePods(ctx, now)],
46
+ // A harness elsewhere closes its own idle MCP connections.
47
+ ["mcp", async () => ctx.mcp?.sweep()],
44
48
  ...[...ctx.sweepHooks].map((hook) => ["hook", hook]),
45
49
  ];
46
50
  let failure;
@@ -80,3 +84,32 @@ export async function wakeOrphanedSessions(ctx, now = new Date()) {
80
84
  await ctx.wake(id, { reason: "recover" });
81
85
  return ids;
82
86
  }
87
+ /** A pod sandbox in a change this long without a reconcile gets one from the sweep. */
88
+ const STALE_POD_MS = 60_000;
89
+ /**
90
+ * Pod sandboxes (F7.2) whose change has not finished (creating, deleting, an old volume still
91
+ * to delete, an expiry not yet recorded), or that should run but whose engine is not connected,
92
+ * and whose row has not moved for a minute: a reconcile the `Sandbox` object lost (a send
93
+ * dropped while the Tenant closed) is sent again, and a pod or volume deleted outside the
94
+ * Runtime is found.
95
+ */
96
+ async function reconcileStalePods(ctx, now) {
97
+ if (!ctx.pods)
98
+ return;
99
+ const stale = await ctx.store.tx(async (t) => (await t.listSandboxResources()).filter((row) => {
100
+ const pod = row.pod;
101
+ if (!pod || now.getTime() - Date.parse(row.updatedAt) < STALE_POD_MS)
102
+ return false;
103
+ const expiring = pod.expiresAt !== undefined && Date.parse(pod.expiresAt) <= now.getTime() && pod.observed !== "expired";
104
+ return (pod.desired === "deleted" ||
105
+ pod.retiring !== undefined ||
106
+ pod.observed === "creating" ||
107
+ pod.observed === "deleting" ||
108
+ expiring ||
109
+ (pod.desired === "suspended" && pod.observed !== "suspended") ||
110
+ // Running, but its engine is not connected: the pod may be gone, or its volume.
111
+ (pod.desired === "running" && pod.observed === "running" && !ctx.harness.hosting(row.id)));
112
+ }));
113
+ for (const row of stale)
114
+ await ctx.sandboxSignal(row.id, { kind: "reconcile" });
115
+ }
@@ -1,10 +1,15 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
- import type { HostAggregate, HostTenant, TenantEnvelope } from "@nylorun/core/contracts";
2
+ import type { HarnessStatus, HostAggregate, HostTenant, TenantEnvelope } from "@nylorun/core/contracts";
3
+ import type { HarnessChannel } from "@nylorun/core/harness-api";
3
4
  import type { FlowLimits } from "../core/limits.js";
5
+ import type { HarnessPeer } from "../harness-api/server.js";
6
+ import type { HostAuthority } from "../sandbox/join.js";
4
7
  import type { SessionStore } from "../store/types.js";
5
8
  import type { SandboxBackend } from "../sandbox/types.js";
6
9
  import type { TenantCause } from "./cause.js";
10
+ import type { OperatorKeys } from "./operator-keys.js";
7
11
  import type { OutboundPolicy } from "./outbound.js";
12
+ import type { ResolverConfig } from "../vault/sources.js";
8
13
  import type { TenantWorker } from "./worker.js";
9
14
  export type TenantMode = "shared" | "ephemeral" | "test";
10
15
  /**
@@ -19,6 +24,8 @@ export interface TenantPaths {
19
24
  tmp: string;
20
25
  sandboxes: string;
21
26
  pluginData: string;
27
+ /** The `fs` BlobStore's directory, used when the Host has no Object store. */
28
+ blobs: string;
22
29
  logs: string;
23
30
  log: string;
24
31
  }
@@ -48,6 +55,12 @@ export interface TenantConfig {
48
55
  steps?: number;
49
56
  ms?: number;
50
57
  };
58
+ /**
59
+ * How long a run waits in its lease for a pending Action's outcome (F6.2): the turn goes on
60
+ * at once when the Action endpoint answers in time, and ends its segment as waiting (resumed
61
+ * by replay) otherwise. Default 5 minutes; 0 never waits.
62
+ */
63
+ actionHoldMs?: number;
51
64
  /** Retries and timeouts for model calls (Model Calls §5, §6). Defaults in `piModel`. */
52
65
  modelCall?: import("../gates/model-gate.js").ModelCallSettings;
53
66
  childEnv: Readonly<Record<string, string>>;
@@ -70,6 +83,23 @@ export interface TenantConfig {
70
83
  */
71
84
  flowEnv?: Readonly<Record<string, string | undefined>>;
72
85
  vaultFetch?: typeof fetch;
86
+ /**
87
+ * The trusted issuers whose JWTs the Tenant API accepts (Host feature `trusted-issuers`), from
88
+ * the identity file (`NYLORUN_IDENTITY_FILE`). Absent: none.
89
+ */
90
+ issuers?: import("./issuers.js").TrustedIssuers;
91
+ /**
92
+ * The operator's credential resolver (F9 C1), asked for a person's MCP credential when the
93
+ * session's vaults hold none. Used where this process authorizes MCP calls itself (embedding,
94
+ * the ephemeral Runtime, tests); the gateway takes `NYLORUN_RESOLVER_*` instead.
95
+ */
96
+ resolver?: ResolverConfig;
97
+ /**
98
+ * The URL browsers reach this Tenant API at (`NYLORUN_PUBLIC_URL`, no trailing slash): the
99
+ * base of the MCP OAuth callback, `<publicUrl>/v1/oauth/callback` (F9 C2). Absent (embedding,
100
+ * tests): the origin the start request was sent to.
101
+ */
102
+ publicUrl?: string;
73
103
  logger: Logger;
74
104
  }
75
105
  export interface Logger {
@@ -84,6 +114,8 @@ export interface TenantSummary {
84
114
  inFlightDeliveries: number;
85
115
  pendingActions: number;
86
116
  uncertainEffects: number;
117
+ /** The Tenant's harnesses (F6.2). */
118
+ harness?: HarnessStatus;
87
119
  }
88
120
  /**
89
121
  * The Node request and response behind a `Request`, for what still writes to Node directly
@@ -107,6 +139,12 @@ export interface TenantHandle {
107
139
  /** Stop scheduling; wait for or cancel active turns. */
108
140
  drain(activeWork: "drain" | "cancel", timeoutMs?: number): Promise<void>;
109
141
  close(): Promise<void>;
142
+ /** Serves a harness's Harness API channel until it closes. Returns a function that detaches it. */
143
+ attachHarness?(channel: HarnessChannel, peer: HarnessPeer): () => void;
144
+ /** The Tenant's side of a sandbox pod's join (F7.2); absent without sandbox pods. */
145
+ hostAuthority?(): HostAuthority | undefined;
146
+ /** The Tenant's operator keys, for the Admin API (`/v1/admin/keys`, F9 I1). */
147
+ operatorKeys?(): OperatorKeys;
110
148
  }
111
149
  /**
112
150
  * The Host's Tenant for a request: open, or unavailable. `cause` says why when opening it
@@ -1,4 +1,6 @@
1
- import type { AdvanceResult, DeliverResult, DurableExecution, StuckInvocation, WorkerHandlers } from "../execution/types.js";
1
+ import type { AbortReason } from "@nylorun/core/harness-api";
2
+ import { RunAbort } from "@nylorun/harness/api";
3
+ import type { AdvanceResult, DeliverResult, SandboxResult, SandboxTrigger, DurableExecution, StuckInvocation, WorkerHandlers } from "../execution/types.js";
2
4
  import type { Logger } from "./types.js";
3
5
  /** This process's Worker id: the `owner` it writes on the sessions it advances (§10.6). */
4
6
  export declare const WORKER_ID: string;
@@ -11,10 +13,12 @@ export declare const DEFAULT_ADVANCE_DEADLINE_MS: number;
11
13
  /** Default time an aborted advance has to settle before it is abandoned. */
12
14
  export declare const DEFAULT_ADVANCE_GRACE_MS = 30000;
13
15
  /** Why an advance's signal aborted (see "Abort reasons" above). */
14
- export type AdvanceAbortKind = "cancel" | "shutdown" | "deadline" | "ownership.lost";
15
- /** The abort reason of an advance. */
16
- export declare class AdvanceAbort extends Error {
17
- readonly kind: AdvanceAbortKind;
16
+ export type AdvanceAbortKind = AbortReason;
17
+ /**
18
+ * The abort reason of an advance. A harness run's reason (`RunAbort`) has the same kinds, so the
19
+ * executors read either one.
20
+ */
21
+ export declare class AdvanceAbort extends RunAbort {
18
22
  readonly name: string;
19
23
  constructor(kind: AdvanceAbortKind, message: string, options?: {
20
24
  cause?: unknown;
@@ -40,6 +44,8 @@ export interface TenantWorker {
40
44
  sweep(): Promise<void>;
41
45
  /** Delivers one Action to its endpoint. Only infrastructure errors throw. */
42
46
  deliver?(actionId: string, signal: AbortSignal): Promise<DeliverResult>;
47
+ /** Reconciles one pod sandbox (F7.2). Only infrastructure errors throw. */
48
+ sandbox?(sandboxId: string, trigger: SandboxTrigger, signal: AbortSignal): Promise<SandboxResult>;
43
49
  }
44
50
  export interface TenantWorkersOptions {
45
51
  /** Finds or opens a Tenant that is not registered on this process. */
@@ -51,6 +51,7 @@
51
51
  */
52
52
  import { hostname } from "node:os";
53
53
  import { randomBytes } from "node:crypto";
54
+ import { RunAbort } from "@nylorun/harness/api";
54
55
  /** This process's Worker id: the `owner` it writes on the sessions it advances (§10.6). */
55
56
  export const WORKER_ID = `worker-${hostname()}-${process.pid}-${randomBytes(4).toString("hex")}`;
56
57
  /**
@@ -61,13 +62,14 @@ export const WORKER_ID = `worker-${hostname()}-${process.pid}-${randomBytes(4).t
61
62
  export const DEFAULT_ADVANCE_DEADLINE_MS = 50 * 60_000;
62
63
  /** Default time an aborted advance has to settle before it is abandoned. */
63
64
  export const DEFAULT_ADVANCE_GRACE_MS = 30_000;
64
- /** The abort reason of an advance. */
65
- export class AdvanceAbort extends Error {
66
- kind;
65
+ /**
66
+ * The abort reason of an advance. A harness run's reason (`RunAbort`) has the same kinds, so the
67
+ * executors read either one.
68
+ */
69
+ export class AdvanceAbort extends RunAbort {
67
70
  name = "AdvanceAbort";
68
71
  constructor(kind, message, options) {
69
- super(message, options);
70
- this.kind = kind;
72
+ super(kind, message, options);
71
73
  }
72
74
  }
73
75
  /** The abort reason of an advance that ran past its deadline. */
@@ -87,7 +89,7 @@ export class AdvanceDeadlineError extends AdvanceAbort {
87
89
  export function abortKind(signal) {
88
90
  if (!signal.aborted)
89
91
  return undefined;
90
- return signal.reason instanceof AdvanceAbort ? signal.reason.kind : "shutdown";
92
+ return signal.reason instanceof RunAbort ? signal.reason.kind : "shutdown";
91
93
  }
92
94
  /** The execution's abort reason as a `shutdown`, keeping its message. */
93
95
  function shutdownOf(reason) {
@@ -138,6 +140,10 @@ export class TenantWorkers {
138
140
  const worker = await this.find(tenantId);
139
141
  return worker?.deliver ? worker.deliver(actionId, signal) : DONE;
140
142
  },
143
+ sandbox: async (tenantId, sandboxId, trigger, signal) => {
144
+ const worker = await this.find(tenantId);
145
+ return worker?.sandbox ? worker.sandbox(sandboxId, trigger, signal) : {};
146
+ },
141
147
  };
142
148
  /** Runs one advance under the deadline and the grace period (see the module comment). */
143
149
  async bounded(tenantId, sessionId, worker, signal) {
@@ -10,3 +10,10 @@ export declare function encryptSecret(kek: Buffer, aad: Buffer, plaintext: Buffe
10
10
  kekId: string;
11
11
  };
12
12
  export declare function decryptSecret(kek: Buffer, aad: Buffer, nonce: Buffer, ciphertext: Buffer, wrappedDek: Buffer, expectedKekId: string): Buffer;
13
+ /**
14
+ * `encryptSecret` in one column: nonce, wrapped DEK, then ciphertext and tag. For rows that
15
+ * seal several short secrets under one `kekId` (`oauth_pending`, F9 C2).
16
+ */
17
+ export declare function sealBytes(kek: Buffer, aad: Buffer, plaintext: Buffer): Buffer;
18
+ /** Opens what `sealBytes` sealed; `VaultCryptoError` when it cannot. */
19
+ export declare function openBytes(kek: Buffer, aad: Buffer, sealed: Buffer, expectedKekId: string): Buffer;
@@ -78,3 +78,19 @@ function unwrapKey(kek, wrapped) {
78
78
  throw new VaultCryptoError();
79
79
  }
80
80
  }
81
+ /** The wrapped DEK's length: its nonce, the 32-byte key and the tag. */
82
+ const WRAPPED_DEK_BYTES = IV_BYTES + 32 + TAG_BYTES;
83
+ /**
84
+ * `encryptSecret` in one column: nonce, wrapped DEK, then ciphertext and tag. For rows that
85
+ * seal several short secrets under one `kekId` (`oauth_pending`, F9 C2).
86
+ */
87
+ export function sealBytes(kek, aad, plaintext) {
88
+ const sealed = encryptSecret(kek, aad, plaintext);
89
+ return Buffer.concat([sealed.nonce, sealed.wrappedDek, sealed.ciphertext]);
90
+ }
91
+ /** Opens what `sealBytes` sealed; `VaultCryptoError` when it cannot. */
92
+ export function openBytes(kek, aad, sealed, expectedKekId) {
93
+ if (sealed.length < IV_BYTES + WRAPPED_DEK_BYTES + TAG_BYTES)
94
+ throw new VaultCryptoError();
95
+ return decryptSecret(kek, aad, sealed.subarray(0, IV_BYTES), sealed.subarray(IV_BYTES + WRAPPED_DEK_BYTES), sealed.subarray(IV_BYTES, IV_BYTES + WRAPPED_DEK_BYTES), expectedKekId);
96
+ }