@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,15 +1,17 @@
1
1
  /**
2
2
  * Tenant request authentication and authorization: a bearer token resolves to an application
3
- * principal, a subject token or a delivery token; anything else is the opaque 404 (D5).
4
- * An application principal may act for a subject (`Nylorun-Subject`, `Nylorun-Scopes`); a
5
- * subject token names its subject itself. `authorize` limits both to the routes their scopes
6
- * allow, decided from the route alone.
3
+ * principal, a trusted issuer's token or a delivery token; anything else is the opaque 404
4
+ * (D5). An application principal may act for a subject (`Nylorun-Subject`, `Nylorun-Scopes`);
5
+ * an issuer token names its subject itself. `requireScopes` limits them to the routes their
6
+ * scopes allow, decided from the route alone.
7
+ *
8
+ * Browsers: a request with `Origin` may carry an issuer token (CORS is the operator's proxy's),
9
+ * never an application key or a delivery token, which are server secrets.
7
10
  */
8
11
  import type { IncomingMessage } from "node:http";
9
12
  import { type Action, type SubjectScope } from "@nylorun/core/contracts";
10
13
  import type { AuthScope, SessionAccess, TenantContext } from "./context.js";
11
- import type { BrowserClient } from "./browser.js";
12
- export declare function authenticate(ctx: TenantContext, request: IncomingMessage, client?: BrowserClient): Promise<AuthScope>;
14
+ export declare function authenticate(ctx: TenantContext, request: IncomingMessage): Promise<AuthScope>;
13
15
  /**
14
16
  * What a subject needs for a route: any one of the scopes, nothing grants it (`never`), or
15
17
  * any caller may (`any`, public data such as the JWKS). Each route declares it
@@ -17,12 +19,12 @@ export declare function authenticate(ctx: TenantContext, request: IncomingMessag
17
19
  */
18
20
  export type SubjectAccess = readonly SubjectScope[] | "never" | "any";
19
21
  /**
20
- * A subject, subject token or publishable key reaches a route only with one of the scopes it
21
- * declares: `403 scope_required`, decided from the route alone before anything is read.
22
+ * A subject or a token caller reaches a route only with one of the scopes it declares:
23
+ * `403 scope_required`, decided from the route alone before anything is read.
22
24
  */
23
25
  export declare function requireScopes(scope: AuthScope, access: SubjectAccess): void;
24
26
  export declare function accessOf(scope: AuthScope): SessionAccess | undefined;
25
- /** The subject whose sessions and vaults the request is limited to, if it acts for one. */
27
+ /** The subject whose sessions the request is limited to, if it acts for one. */
26
28
  export declare function ownerOf(scope: AuthScope): string | undefined;
27
29
  /** The application principal behind the request, acting for a subject or not. */
28
30
  export declare function requirePrincipal(scope: AuthScope): string;
@@ -31,4 +33,6 @@ export declare function requirePrincipal(scope: AuthScope): string;
31
33
  * is still the Action's is checked by each callback.
32
34
  */
33
35
  export declare function scoped(scope: AuthScope, action: Action): void;
36
+ /** True when a token caller may reach `agentId` (its issuer's allowlist). Other callers always may. */
37
+ export declare function mayUseAgent(scope: AuthScope, agentId: string): boolean;
34
38
  export declare function requireApplication(scope: AuthScope): string;
@@ -2,10 +2,9 @@ import { SCOPES_HEADER, SUBJECT_HEADER, } from "@nylorun/core/compatibility";
2
2
  import { DELIVERY_TOKEN_TYPE, parseSubjectHeaders, } from "@nylorun/core/contracts";
3
3
  import { hashToken } from "../core/bearer.js";
4
4
  import { fail, failOpaque } from "./http.js";
5
- import { looksLikeToken, verifySubjectToken } from "./tokens.js";
6
5
  import { verifyDeliveryToken } from "./delivery-token.js";
7
- import { tokenType } from "./jwt.js";
8
- import { readPolicy } from "./access-policy.js";
6
+ import { verifyIssuerToken } from "./issuers.js";
7
+ import { looksLikeToken, tokenType } from "./jwt.js";
9
8
  const SUBJECT_INVALID = { code: "subject_invalid" };
10
9
  /** One header's value, or `undefined`; a header sent twice is refused. */
11
10
  function singleHeader(request, name) {
@@ -18,20 +17,10 @@ function singleHeader(request, name) {
18
17
  return fail(400, `${name} must be sent once`, SUBJECT_INVALID);
19
18
  return values[0];
20
19
  }
21
- export async function authenticate(ctx, request, client) {
20
+ export async function authenticate(ctx, request) {
22
21
  const header = request.headers.authorization;
23
22
  const token = header?.startsWith("Bearer ") ? header.slice(7) : "";
24
23
  if (!token || !header?.startsWith("Bearer ")) {
25
- // A publishable key alone: what the policy grants `anon` (nothing by default).
26
- if (client && header === undefined) {
27
- const { anon } = await ctx.store.tx((t) => readPolicy(t));
28
- return {
29
- kind: "publishable",
30
- keyId: client.keyId,
31
- scopes: new Set(anon.scopes),
32
- agents: anon.agents === "*" ? "*" : new Set(anon.agents),
33
- };
34
- }
35
24
  ctx.config.logger.warn("credential rejected", {
36
25
  reason: "missing_bearer",
37
26
  });
@@ -47,24 +36,27 @@ export async function authenticate(ctx, request, client) {
47
36
  fail(403, "A delivery token cannot act for a subject");
48
37
  return scope;
49
38
  }
50
- if (looksLikeToken(token)) {
51
- const scope = await verifySubjectToken(ctx, token);
52
- // Read only after verification, so an unknown token still sees the opaque 404.
39
+ // A trusted issuer's token (Host feature `trusted-issuers`), from a server or a browser: its
40
+ // unverified `iss` names an issuer of the identity file, and only that issuer verifies it.
41
+ const issuer = ctx.config.issuers?.claimed(token);
42
+ if (issuer) {
43
+ const scope = await verifyIssuerToken(ctx, issuer, token);
53
44
  if (singleHeader(request, SUBJECT_HEADER) !== undefined ||
54
45
  singleHeader(request, SCOPES_HEADER) !== undefined)
55
- fail(403, "A subject token cannot act for another subject");
46
+ fail(403, "An issuer token cannot act for another subject");
56
47
  return scope;
57
48
  }
49
+ // Any other JWT: no issuer of this Host signed it (subject tokens are gone, protocol 7).
50
+ if (looksLikeToken(token)) {
51
+ ctx.config.logger.warn("credential rejected", { reason: "token_unknown_issuer" });
52
+ return failOpaque();
53
+ }
58
54
  // Application keys never come from a browser or a shipped app: refused before they are even
59
55
  // looked up.
60
56
  if (request.headers.origin !== undefined)
61
57
  fail(403, "Application keys are not accepted from browsers", {
62
58
  code: "origin_rejected",
63
59
  });
64
- if (client)
65
- fail(400, "Nylorun-Key is for browser and mobile clients, not with a Tenant key", {
66
- code: "invalid_request",
67
- });
68
60
  const tokenHash = hashToken(token);
69
61
  const principal = await ctx.store.tx((t) => t.principalByTokenHash(tokenHash));
70
62
  if (!principal) {
@@ -92,13 +84,11 @@ export async function authenticate(ctx, request, client) {
92
84
  };
93
85
  }
94
86
  /**
95
- * A subject, subject token or publishable key reaches a route only with one of the scopes it
96
- * declares: `403 scope_required`, decided from the route alone before anything is read.
87
+ * A subject or a token caller reaches a route only with one of the scopes it declares:
88
+ * `403 scope_required`, decided from the route alone before anything is read.
97
89
  */
98
90
  export function requireScopes(scope, access) {
99
- if (scope.kind !== "subject" &&
100
- scope.kind !== "token" &&
101
- scope.kind !== "publishable")
91
+ if (scope.kind !== "subject" && scope.kind !== "token")
102
92
  return;
103
93
  if (access === "any")
104
94
  return;
@@ -107,7 +97,8 @@ export function requireScopes(scope, access) {
107
97
  code: "scope_required",
108
98
  details: { scopes: [] },
109
99
  });
110
- if (!access.some((name) => scope.scopes.has(name)))
100
+ const held = scope.scopes;
101
+ if (!access.some((name) => held.has(name)))
111
102
  fail(403, `Scope ${access.join(" or ")} required`, {
112
103
  code: "scope_required",
113
104
  details: { scopes: access },
@@ -124,9 +115,7 @@ export function accessOf(scope) {
124
115
  owner: scope.subject,
125
116
  ...(scope.agents === "*" ? {} : { agents: scope.agents }),
126
117
  };
127
- // A publishable key alone owns nothing: no session or vault is ever reachable.
128
- case "publishable":
129
- // Nor does a request with no credential, on a route that serves public data.
118
+ // A request with no credential, on a route that serves public data, owns nothing.
130
119
  case "anonymous":
131
120
  return fail(404, "Not found");
132
121
  // A delivery token reaches its Action's callbacks, never a session or vault.
@@ -138,7 +127,7 @@ export function accessOf(scope) {
138
127
  }
139
128
  }
140
129
  }
141
- /** The subject whose sessions and vaults the request is limited to, if it acts for one. */
130
+ /** The subject whose sessions the request is limited to, if it acts for one. */
142
131
  export function ownerOf(scope) {
143
132
  return accessOf(scope)?.owner;
144
133
  }
@@ -159,6 +148,10 @@ export function scoped(scope, action) {
159
148
  return;
160
149
  fail(403, "This delivery token is for another Action");
161
150
  }
151
+ /** True when a token caller may reach `agentId` (its issuer's allowlist). Other callers always may. */
152
+ export function mayUseAgent(scope, agentId) {
153
+ return scope.kind !== "token" || scope.agents === "*" || scope.agents.has(agentId);
154
+ }
162
155
  export function requireApplication(scope) {
163
156
  if (scope.kind === "application")
164
157
  return scope.principalId;
@@ -24,11 +24,12 @@ import { canonical } from "../store/canonical.js";
24
24
  import { lockedSession, } from "./context.js";
25
25
  import { fail } from "./http.js";
26
26
  import { accessOf } from "./auth.js";
27
- import { chargeTurn } from "./subject-limits.js";
27
+ import { checkSandboxTurn } from "./sandboxes.js";
28
28
  import { actionTarget, rebaseSessionState, turnManifestOf, variantStore, } from "./session.js";
29
- import { signalSessionCancel } from "./streams.js";
29
+ import { signalActionOutcome, signalSessionCancel } from "./streams.js";
30
30
  import { toolIds } from "./transcript.js";
31
31
  import { slimModelEffects } from "./slim.js";
32
+ import { resolveMessageParts } from "../artifacts/parts.js";
32
33
  /** `outcome`, or a failed one when a tool's output does not match its stored output schema. */
33
34
  export function acceptedOutcome(action, outcome) {
34
35
  if (action.kind !== "tool" || !action.outputSchema)
@@ -95,7 +96,7 @@ export async function command(ctx, id, input, scope) {
95
96
  if (scope.kind === "token" &&
96
97
  command.type === "message" &&
97
98
  command.manifest !== undefined)
98
- fail(403, "A subject token cannot send message.manifest", {
99
+ fail(403, "A token caller cannot send message.manifest", {
99
100
  code: "scope_required",
100
101
  });
101
102
  const key = commandKey(id, command.idempotencyKey);
@@ -178,16 +179,25 @@ export async function command(ctx, id, input, scope) {
178
179
  if (command.type === "message") {
179
180
  if (!["idle", "completed", "failed", "cancelled"].includes(s.status))
180
181
  fail(409, "Session has active or unresolved work");
181
- // After the replay check above, so a retried message is never charged twice.
182
- if (scope.kind === "token" && scope.limits)
183
- await chargeTurn(t, scope.subject, scope.limits);
182
+ // Every turn start on a sandbox: it exists, the token's grants reach it, and no other
183
+ // session's turn holds it.
184
+ await checkSandboxTurn(t, s, scope, ctx);
184
185
  s.turnStartState = s.state;
185
186
  // A new turn folds from the latest snapshot: a cancel or failure only reverts this turn.
186
187
  if (s.history?.snapshot !== undefined)
187
188
  s.history = { ...s.history, from: s.history.snapshot };
188
189
  s.activeTurnId = randomUUID();
190
+ // File parts name artifacts the caller may read; each is pinned to a version now.
191
+ const parts = "parts" in command
192
+ ? await resolveMessageParts(t, command.parts, id, accessOf(scope))
193
+ : undefined;
194
+ const data = "data" in command ? command.data : undefined;
189
195
  if (isWorkflowManifest(s.manifest)) {
190
- const input = "content" in command ? command.content : command.data;
196
+ const input = "content" in command
197
+ ? command.content
198
+ : parts
199
+ ? { parts: parts.pinned }
200
+ : data;
191
201
  s.checkpoint = createFlowCheckpoint({
192
202
  manifest: s.manifest,
193
203
  sessionId: id,
@@ -214,9 +224,11 @@ export async function command(ctx, id, input, scope) {
214
224
  turnId: s.activeTurnId,
215
225
  input: "content" in command
216
226
  ? command.content
217
- : typeof command.data === "string"
218
- ? command.data
219
- : JSON.stringify(command.data),
227
+ : parts
228
+ ? { content: parts.engine }
229
+ : typeof data === "string"
230
+ ? data
231
+ : JSON.stringify(data),
220
232
  state: s.state,
221
233
  info: s.info,
222
234
  });
@@ -334,6 +346,15 @@ export async function recordActionOutcome(t, ctx, s, action, received, options =
334
346
  dedupeKey: `action_result:${action.turnId}:${action.actionId}:${action.generation}`,
335
347
  };
336
348
  t.afterCommit(() => ctx.wake(s.id, resultWake));
349
+ // A run held while the Action was pending goes on with it in the same lease (F6.2): here, or
350
+ // on the process whose harness holds it. The wake finds the session settled, or resumes it
351
+ // by replay when the run had already ended.
352
+ t.afterCommit(() => {
353
+ if (ctx.harness.holds(s.id))
354
+ ctx.harness.resolved(s.id, action.actionId, outcome);
355
+ else
356
+ signalActionOutcome(ctx, s.id, action.actionId);
357
+ });
337
358
  const event = await t.event(s.id, s.activeTurnId, "action.completed", {
338
359
  actionId: action.actionId,
339
360
  ...actionTarget(action),
@@ -14,7 +14,7 @@
14
14
  * under ownership (§10.6); `abortLocal` aborts an advance running on this process.
15
15
  */
16
16
  import type { SessionHistory } from "./history.js";
17
- import type { RoleLimits, SubjectScope, TenantEnvelope } from "@nylorun/core/contracts";
17
+ import type { IssuerScope, SubjectScope, TenantEnvelope } from "@nylorun/core/contracts";
18
18
  import type { DurableCheckpoint, FlowCheckpoint } from "@nylorun/harness/run";
19
19
  import type { AgentManifest, JsonValue, SandboxManifest } from "@nylorun/core/define";
20
20
  import type { CredentialSelection } from "@nylorun/core/contracts";
@@ -25,14 +25,21 @@ import type { ModelGate } from "../gates/model-gate.js";
25
25
  import type { ToolGate } from "../gates/tool-gate.js";
26
26
  import type { Keys } from "../keys/keys.js";
27
27
  import type { VaultService } from "../vault/service.js";
28
+ import type { CredentialSources } from "../vault/sources.js";
28
29
  import type { McpPool } from "../mcp/pool.js";
29
30
  import type { McpDiagnostic, McpSnapshot } from "../mcp/snapshot.js";
30
- import type { SandboxManager } from "../sandbox/manager.js";
31
+ import type { HarnessApiServer } from "../harness-api/server.js";
32
+ import type { WorkspacePort } from "../harness-api/workspace.js";
33
+ import type { SandboxesClient } from "../sandbox/pods/client.js";
34
+ import type { SandboxSignal } from "../execution/types.js";
31
35
  import type { TenantConfig } from "./types.js";
32
36
  import type { SessionStreams } from "./session-streams.js";
33
37
  import type { StuckInvocation, Wake } from "../execution/types.js";
34
38
  import type { WorkState } from "./scheduler.js";
35
39
  import type { SigningKeys } from "./signing-keys.js";
40
+ import type { BlobStore } from "../blob/index.js";
41
+ import type { WorkspaceReader } from "../artifacts/workspace.js";
42
+ import type { RunGrants } from "./run-grants.js";
36
43
  export interface Session {
37
44
  id: string;
38
45
  agentId: string;
@@ -70,34 +77,43 @@ export interface Session {
70
77
  mcpDiagnostics?: readonly McpDiagnostic[];
71
78
  /** Session id that keys the shared sandbox; absent means this session owns it. */
72
79
  sandboxOwnerId?: string;
80
+ /**
81
+ * The sandbox resource the session is attached to (`sandbox: { id }`): its workspace is the
82
+ * sandbox's, shared with every session attached to it, and it outlives the session.
83
+ */
84
+ sandboxId?: string;
73
85
  /**
74
86
  * The sandbox chosen when the session was opened, resolved against the Tenant's limits, or
75
87
  * inherited from the session it shares with. Absent when the session has no sandbox or its
76
88
  * definition declares one (`.sandbox()`).
77
89
  */
78
90
  sandbox?: SandboxManifest;
79
- /** Where `sandbox` came from. */
80
- sandboxSource?: "default" | "inline" | "shared";
91
+ /** Where `sandbox` came from: `sandbox` is a sandbox resource's spec. */
92
+ sandboxSource?: "default" | "inline" | "shared" | "sandbox";
81
93
  }
82
94
  export type AuthScope = {
83
95
  kind: "application";
84
96
  principalId: string;
85
97
  }
86
98
  /**
87
- * A subject token (Host feature `subject-tokens`): one subject, the role's scopes and agents
88
- * narrowed by the token, until `expiresAt` (ms) or the subject's epoch moves past `epoch`.
99
+ * A trusted issuer's token (Host feature `trusted-issuers`, F9-D12): one subject, with the
100
+ * issuer's scopes, agents and sandbox grants, until `expiresAt` (ms). Only its expiry ends it.
89
101
  */
90
102
  | {
91
103
  kind: "token";
104
+ /** The identity file's issuer that signed it. */
105
+ issuer: string;
92
106
  subject: string;
93
- scopes: ReadonlySet<SubjectScope>;
107
+ /** The issuer's scopes the token holds, which may add `studio`. */
108
+ scopes: ReadonlySet<IssuerScope>;
94
109
  agents: ReadonlySet<string> | "*";
95
- role: string;
96
- limits?: RoleLimits;
97
- epoch: number;
110
+ /** The sandboxes it reaches: exact ids or `p/*` prefixes. Absent or empty reaches none. */
111
+ sandboxes?: readonly string[];
98
112
  expiresAt: number;
113
+ /** The token's `jti`, or a hash of it. */
99
114
  tokenId: string;
100
- keyId: string;
115
+ /** The issuer key's `kid`, when it has one. */
116
+ keyId?: string;
101
117
  }
102
118
  /** An application principal acting for `subject` (`Nylorun-Subject`), narrowed to `scopes`. */
103
119
  | {
@@ -122,24 +138,27 @@ export type AuthScope = {
122
138
  expiresAt: number;
123
139
  tokenId: string;
124
140
  keyId: string;
125
- }
126
- /**
127
- * A publishable key with no bearer (Host feature `browser-access`): what the policy grants
128
- * `anon`, at most `agents:read`. It owns no session or vault.
129
- */
130
- | {
131
- kind: "publishable";
132
- keyId: string;
133
- scopes: ReadonlySet<SubjectScope>;
134
- agents: ReadonlySet<string> | "*";
135
141
  };
136
142
  export interface TenantContext {
137
143
  readonly config: TenantConfig;
138
144
  readonly envelope: TenantEnvelope;
139
145
  readonly store: SessionStore;
140
146
  readonly vault: VaultService;
141
- readonly mcp: McpPool;
142
- readonly sandbox: SandboxManager;
147
+ /**
148
+ * A session's MCP credentials, for the in-process MCP pool: its attached vaults, then the
149
+ * operator's credential resolver (`TenantConfig.resolver`, F9 C1).
150
+ */
151
+ readonly credentials: CredentialSources;
152
+ /**
153
+ * The MCP pool of the Tenant's in-process harness. Absent when harnesses run elsewhere
154
+ * (`NYLORUN_HARNESS=remote`): each keeps its own (F6.2).
155
+ */
156
+ readonly mcp?: McpPool;
157
+ /**
158
+ * The workspace capability (F6.2): the Tenant's SandboxManager with an in-process harness,
159
+ * else the harness that serves workspaces (`harness-api/workspace.ts`).
160
+ */
161
+ readonly sandbox: WorkspacePort;
143
162
  readonly flowLimits: FlowLimits;
144
163
  readonly modelProvider: ModelProvider;
145
164
  /** True when the model comes from the Tenant vault selection, served by `modelGate`. */
@@ -157,6 +176,28 @@ export interface TenantContext {
157
176
  * `signingKeys`' private key.
158
177
  */
159
178
  readonly keys: Keys;
179
+ /**
180
+ * The Object store's `BlobStore` seam (D35): the Host's `s3` store (RustFS in the local
181
+ * stack), or the `fs` store under `paths.blobs` without one. Bytes only; Postgres holds what
182
+ * they mean, and a blob counts once a committed row names its key.
183
+ */
184
+ readonly blobs: BlobStore;
185
+ /**
186
+ * How core reads a session's sandbox workspace at turn end, to export its outputs (F8.2): over
187
+ * this process's `sandbox` today; over the Harness API's `workspace.read` from F6.2.
188
+ */
189
+ readonly workspaces: WorkspaceReader;
190
+ /**
191
+ * The run token of each session an advance of this process owns (F5): the credential the
192
+ * HTTP gate clients present for that session's model and MCP calls (`run-grants.ts`).
193
+ * Absent when the gates run in this process, which needs no token (G6).
194
+ */
195
+ readonly runGrants?: RunGrants;
196
+ /**
197
+ * The Harness API server (D37): advances offer their segments here, and the Tenant's
198
+ * harnesses (the in-process one, or attached ones) run them.
199
+ */
200
+ readonly harness: HarnessApiServer;
160
201
  /** Set by drain/close; reset clears it again. Stops new advances. */
161
202
  closing: boolean;
162
203
  /** Set once close has finished releasing resources. */
@@ -165,7 +206,7 @@ export interface TenantContext {
165
206
  readonly work: WorkState;
166
207
  /** Live delivery over Durable Streams: one `SessionStream` per observed session, and the streams wiring. */
167
208
  readonly sessionStreams: SessionStreams;
168
- /** The Tenant's signing keys for subject tokens. */
209
+ /** The Tenant's signing keys: delivery tokens, capability links, run and host tokens. */
169
210
  readonly signingKeys: SigningKeys;
170
211
  /** The Worker id this process writes as session `owner` (§10.6). */
171
212
  readonly workerId: string;
@@ -187,6 +228,16 @@ export interface TenantContext {
187
228
  * from `t.afterCommit`. Dropped while the Tenant is closing; the sweep re-sends it.
188
229
  */
189
230
  deliver(actionId: string): Promise<void>;
231
+ /**
232
+ * Sandbox pods (F7.2): the sandboxes service and the Runtime image pods copy the engine from.
233
+ * Absent without a cluster (`nylorun sandbox enable`): kind `pod` is `sandbox_unavailable`.
234
+ */
235
+ readonly pods?: TenantPods;
236
+ /**
237
+ * Seam: reconcile a pod sandbox, or arm one of its timers (`DurableExecution.sandbox`). Call
238
+ * it from `t.afterCommit`. Dropped while the Tenant is closing; the sweep re-sends reconciles.
239
+ */
240
+ sandboxSignal(sandboxId: string, signal: SandboxSignal): Promise<void>;
190
241
  /** This Tenant's execution invocations that need an operator, for Tenant status. */
191
242
  readonly stuckInvocations?: () => Promise<StuckInvocation[]>;
192
243
  /** Callbacks the Tenant sweep runs after its own steps (`sweep.ts`). */
@@ -195,8 +246,8 @@ export interface TenantContext {
195
246
  onSweep(hook: () => Promise<void>): () => void;
196
247
  }
197
248
  /**
198
- * What a request acting for a person may reach: that person's sessions and vaults, and, for a
199
- * subject token, only sessions of the agents its role allows. Undefined means the whole Tenant.
249
+ * What a request acting for a person may reach: that person's sessions, and, for a token
250
+ * caller, only sessions of the agents its issuer allows. Undefined means the whole Tenant.
200
251
  */
201
252
  export interface SessionAccess {
202
253
  readonly owner: string;
@@ -225,3 +276,9 @@ export declare function loadSession(ctx: TenantContext, id: string, access?: Ses
225
276
  * the sandbox-sharing helpers, which walk that chain synchronously.
226
277
  */
227
278
  export declare function sandboxLookup(t: Tx, start: string | undefined): Promise<(id: string) => Session | undefined>;
279
+ /** The sandboxes service a Tenant drives its pod sandboxes with (`TenantContext.pods`). */
280
+ export interface TenantPods {
281
+ readonly client: SandboxesClient;
282
+ /** The Runtime image the pods copy the engine from (`NYLORUN_SANDBOX_HARNESS_IMAGE`). */
283
+ readonly harnessImage: string;
284
+ }
@@ -5,13 +5,13 @@
5
5
  * Tenant's public keys, and comes back as the bearer of that Action's callbacks (a background
6
6
  * result, heartbeats, sandbox tools).
7
7
  *
8
- * A token lives at most `DELIVERY_TOKEN_MAX_TTL_SECONDS`, the subject-token maximum, and key
8
+ * A token lives at most `DELIVERY_TOKEN_MAX_TTL_SECONDS` (`TOKEN_TTL_MAX_SECONDS`), and key
9
9
  * rotation waits at least that long before it revokes the previous key
10
10
  * (`signing-keys.ts`), so a rotation never revokes a live token.
11
11
  */
12
12
  import { createHash, randomUUID } from "node:crypto";
13
13
  import { errors, jwtVerify } from "jose";
14
- import { DELIVERY_TOKEN_MAX_TTL_SECONDS, DELIVERY_TOKEN_TYPE, subjectTokenIssuer, } from "@nylorun/core/contracts";
14
+ import { DELIVERY_TOKEN_MAX_TTL_SECONDS, DELIVERY_TOKEN_TYPE, tenantTokenIssuer, } from "@nylorun/core/contracts";
15
15
  import { failOpaque, HttpError } from "./http.js";
16
16
  import { CLOCK_TOLERANCE_SECONDS, tokenKeyId } from "./jwt.js";
17
17
  /** base64url SHA-256 of a request body: the token's `bdy` claim. */
@@ -34,7 +34,7 @@ export async function mintDeliveryToken(ctx, options) {
34
34
  agt: delivery.agentId,
35
35
  gen: delivery.kind === "action" ? delivery.generation : 0,
36
36
  bdy: bodyHash(options.body),
37
- iss: subjectTokenIssuer(ctx.config.tenantId),
37
+ iss: tenantTokenIssuer(ctx.config.tenantId),
38
38
  aud: options.audience,
39
39
  sub: delivery.kind === "action" ? delivery.actionId : "ping",
40
40
  iat,
@@ -62,7 +62,7 @@ export async function verifyDeliveryToken(ctx, raw) {
62
62
  try {
63
63
  const verified = await jwtVerify(raw, await ctx.signingKeys.publicKey(row), {
64
64
  algorithms: ["ES256"],
65
- issuer: subjectTokenIssuer(ctx.config.tenantId),
65
+ issuer: tenantTokenIssuer(ctx.config.tenantId),
66
66
  typ: DELIVERY_TOKEN_TYPE,
67
67
  clockTolerance: CLOCK_TOLERANCE_SECONDS,
68
68
  requiredClaims: ["sub", "aud", "iat", "exp", "jti"],
@@ -1,18 +1,27 @@
1
+ /**
2
+ * Core's side of effects: `resolveNewFlowEffect` journals and dispatches new workflow effects
3
+ * (linked agent sessions, tool nodes, fn, verify); the takeover helpers say which calls their
4
+ * gate recovers. Also vault authorization for MCP servers. The journal of a run's effects is
5
+ * `harness-api/record.ts`; a harness readies the session's MCP servers itself (`session.mcp`).
6
+ *
7
+ * Every journal write runs in a transaction that locks the effect's session first and checks
8
+ * the advance's ownership epoch (`ownedSession`): after another Worker takes over, the next
9
+ * write throws `ownership.lost` and nothing is written. The model, MCP, sandbox and vault
10
+ * calls run between transactions, never inside one.
11
+ */
12
+ import type { ActionOutcome } from "@nylorun/core/contracts";
1
13
  import type { EffectResolution, HostEffect } from "@nylorun/harness/run";
14
+ import type { Tx } from "../store/types.js";
2
15
  import type { AuthorizeResult } from "../vault/service.js";
3
16
  import { type Lease, type Session, type TenantContext } from "./context.js";
4
- import type { ModelProvider } from "../core/provider.js";
5
- /** What one advance's segment decides for all its effects. */
6
- export interface SegmentOptions {
7
- /** Overrides the Tenant's model (the fixture-model Tenant setting, `model-setting.ts`). */
8
- model?: ModelProvider;
9
- }
10
17
  /**
11
- * Call the model for one effect. A provider failure comes back as a failure outcome
12
- * (Model Calls §6), whichever provider serves the call; only an abort throws, and the
13
- * advance decides what the abort means.
18
+ * The outcome of a flow `agent` effect once the linked turn it started ended, or undefined
19
+ * while it has not. The linked session may still show an earlier iteration's turn.
14
20
  */
15
- export declare function invokeModel(ctx: TenantContext, request: HostEffect, signal: AbortSignal, model?: ModelProvider): Promise<unknown>;
21
+ export declare function linkedOutcome(t: Tx, effect: {
22
+ request: HostEffect;
23
+ agentSessionId?: string;
24
+ }, agent: Session | undefined): Promise<ActionOutcome | undefined>;
16
25
  /**
17
26
  * True when this Tenant's remote MCP calls outlive the process that sent them (the gates
18
27
  * service, F4.1 G3): after a takeover or a shutdown, the journaled call is re-sent and joins
@@ -27,13 +36,14 @@ export declare function isRemoteMcpEffect(s: Session, request: HostEffect): bool
27
36
  * the running call or gets its outcome, instead of becoming `uncertain`.
28
37
  */
29
38
  export declare function recoversModelCalls(ctx: TenantContext): boolean;
30
- export declare function resolveEffect(ctx: TenantContext, request: HostEffect, signal: AbortSignal, lease: Lease, segment?: SegmentOptions): Promise<EffectResolution>;
31
39
  /** Journal and dispatch a new flow effect (agent / tool node / fn / verify). */
32
40
  export declare function resolveNewFlowEffect(ctx: TenantContext, request: HostEffect, signal: AbortSignal, lease: Lease): Promise<EffectResolution>;
33
- /** Discover the session's MCP tools once, or reconnect and refresh diagnostics. */
34
- export declare function prepareMcp(ctx: TenantContext, lease: Lease, signal: AbortSignal): Promise<void>;
35
- /** Vault authorization for an MCP server request made on behalf of a session. */
41
+ /**
42
+ * The credential of an MCP server request made on behalf of a session: from its attached vaults,
43
+ * else the operator's credential resolver (`vault/sources.ts`).
44
+ */
36
45
  export declare function authorize(ctx: TenantContext, sessionId: string, request: {
37
46
  url: string;
38
47
  serverName?: string;
48
+ agentId?: string;
39
49
  }): Promise<AuthorizeResult>;