@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,13 +1,14 @@
1
1
  /**
2
- * The Admin API (`/v1/admin/**`): the Host's status and shutdown, for a caller with the admin
3
- * key. A Host serves one Tenant, so there are no Tenant routes: the status names the Tenant
4
- * and, when it could not be opened, why (tenancy.md §9). The Host checks the listener, the
5
- * protocol and the admin key first (`app.ts`); what gets here is authorized. Routes are
6
- * declared once, for serving and for the OpenAPI document.
2
+ * The Admin API (`/v1/admin/**`): the Host's status and shutdown, and the Tenant's operator
3
+ * keys (F9 I1), for a caller with the admin key. A Host serves one Tenant, so there are no
4
+ * Tenant routes: the status names the Tenant and, when it could not be opened, why
5
+ * (tenancy.md §9). The Host checks the listener, the protocol and the admin key first
6
+ * (`app.ts`); what gets here is authorized. Routes are declared once, for serving and for the
7
+ * OpenAPI document.
7
8
  */
8
9
  import { OpenAPIHono } from "@hono/zod-openapi";
9
10
  import { z } from "zod";
10
- import { AdminStatus as AdminStatusBody, HostShutdownResponse, ProtocolRejected, Rejected, } from "../api/components.js";
11
+ import { AdminStatus as AdminStatusBody, DeleteOperatorKeyResponse, HostShutdownResponse, ListOperatorKeysResponse, PutOperatorKeyResponse, ProtocolRejected, Rejected, } from "../api/components.js";
11
12
  import { serveRoute } from "../api/route.js";
12
13
  import { jsonResponse, rejectedResponse } from "./http.js";
13
14
  const json = (schema, description) => ({
@@ -54,6 +55,68 @@ export function createAdminApi(options) {
54
55
  c.env.outgoing.once("finish", () => options.shutdown());
55
56
  return jsonResponse(200, { status: "shutting_down" });
56
57
  });
58
+ const keyId = z.object({
59
+ id: z.string().meta({ description: "The key's name: `^[a-z][a-z0-9-]{0,31}$`, never `studio`" }),
60
+ });
61
+ const keysOf = async () => (await options.operatorKeys?.()) ??
62
+ rejectedResponse(503, "request_rejected", "The Tenant is not open: see /v1/admin/status");
63
+ serveRoute(api, adminRoute({
64
+ method: "get",
65
+ path: "/v1/admin/keys",
66
+ summary: "List the Tenant's keys",
67
+ description: "Every application key of the Tenant by name, with its role and when it was issued. Keys themselves are never shown again.",
68
+ responses: {
69
+ 200: json(ListOperatorKeysResponse, "The keys, by id"),
70
+ 503: rejected("The Tenant is not open"),
71
+ },
72
+ }), async () => {
73
+ const keys = await keysOf();
74
+ if (keys instanceof Response)
75
+ return keys;
76
+ return jsonResponse(200, { keys: await keys.list() });
77
+ });
78
+ serveRoute(api, adminRoute({
79
+ method: "put",
80
+ path: "/v1/admin/keys/{id}",
81
+ summary: "Create or rotate a key",
82
+ description: "Issues a new key named `id` and returns it this once. A key of that name is replaced: the old one stops authenticating at once. `studio` is refused.",
83
+ request: { params: keyId },
84
+ responses: {
85
+ 200: json(PutOperatorKeyResponse, "The new key, shown once"),
86
+ 400: rejected("Not a key name, or `studio`"),
87
+ 503: rejected("The Tenant is not open"),
88
+ },
89
+ }), async (c) => {
90
+ const keys = await keysOf();
91
+ if (keys instanceof Response)
92
+ return keys;
93
+ const put = await keys.put(c.req.param("id") ?? "");
94
+ return "reason" in put ? refusedResponse(put) : jsonResponse(200, put);
95
+ });
96
+ serveRoute(api, adminRoute({
97
+ method: "delete",
98
+ path: "/v1/admin/keys/{id}",
99
+ summary: "Delete a key",
100
+ description: "The key stops authenticating at once. `studio` is refused.",
101
+ request: { params: keyId },
102
+ responses: {
103
+ 200: json(DeleteOperatorKeyResponse, "The key is deleted"),
104
+ 400: rejected("Not a key name, or `studio`"),
105
+ 404: rejected("No key of that name; or an unknown admin key, or the public listener"),
106
+ 503: rejected("The Tenant is not open"),
107
+ },
108
+ }), async (c) => {
109
+ const keys = await keysOf();
110
+ if (keys instanceof Response)
111
+ return keys;
112
+ const id = c.req.param("id") ?? "";
113
+ const deleted = await keys.delete(id);
114
+ if (typeof deleted !== "boolean")
115
+ return refusedResponse(deleted);
116
+ return deleted
117
+ ? jsonResponse(200, { id, deleted: true })
118
+ : rejectedResponse(404, "not_found", `No key ${id}`);
119
+ });
57
120
  serveRoute(api, adminRoute({
58
121
  method: "get",
59
122
  path: "/v1/admin/openapi.json",
@@ -72,3 +135,6 @@ export function createAdminApi(options) {
72
135
  function notFound() {
73
136
  return rejectedResponse(404, "not_found", "Route not found");
74
137
  }
138
+ function refusedResponse(refusal) {
139
+ return rejectedResponse(400, refusal.reason === "invalid" ? "invalid_request" : "request_rejected", refusal.message);
140
+ }
@@ -20,7 +20,6 @@ export interface HostAppOptions {
20
20
  adminKey: string;
21
21
  coreVersion: string;
22
22
  pid: number;
23
- browserAccess: boolean;
24
23
  readiness?: () => Promise<{
25
24
  ok: boolean;
26
25
  checks: Record<string, boolean>;
package/dist/host/app.js CHANGED
@@ -1,10 +1,9 @@
1
1
  import { Hono } from "hono";
2
2
  import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
3
- import { checkCompatibility, HOST_PROTOCOL, PROTOCOL_HEADER, PUBLISHABLE_KEY_HEADER, TENANT_HEADER, tenantOfPublishableKey, } from "@nylorun/core/compatibility";
3
+ import { checkCompatibility, HOST_PROTOCOL, PROTOCOL_HEADER, TENANT_HEADER, } from "@nylorun/core/compatibility";
4
4
  import { RUNTIME_VERSION } from "../version.js";
5
5
  import { findTenantRoute } from "../api/http/app.js";
6
6
  import { tenantDocument } from "../api/openapi.js";
7
- import { answerPreflight } from "./cors.js";
8
7
  import { adminKeyMatches, headerValue, isJsonContentType, jsonResponse, opaqueNotFoundResponse, protocolRejectedResponse, readBearer, pathnameIsLogged, redactRoutePath, rejectedResponse, requestHasBody, } from "./http.js";
9
8
  export function createHostApp(options) {
10
9
  const { module, logger } = options;
@@ -27,25 +26,26 @@ export function createHostApp(options) {
27
26
  }
28
27
  });
29
28
  app.use(async (c, next) => {
30
- const { incoming, outgoing, role } = c.env;
29
+ const { incoming, role } = c.env;
31
30
  if (headerValue(incoming, "origin") !== undefined) {
32
31
  const route = pathnameOf(incoming).split("/").filter(Boolean);
33
- // Only Tenant routes, and only when the operator allows browsers; the Tenant then
34
- // checks the publishable key and its origins before adding any CORS header.
32
+ // Only Tenant routes of the public listener; the Tenant then accepts only a trusted
33
+ // issuer's token from a browser. The operator listener never serves browsers.
35
34
  const tenantRoute = route[0] === "v1" && route[1] !== "admin";
36
- // The operator listener never serves browsers.
37
- if (!options.browserAccess || role === "operator" || !tenantRoute)
35
+ if (role === "operator" || !tenantRoute)
38
36
  return rejectedResponse(403, "origin_rejected", "Browser Origin headers are not accepted");
39
- if (incoming.method === "OPTIONS") {
40
- c.set("status", answerPreflight(incoming, outgoing, route, browserRoute));
41
- return RESPONSE_ALREADY_SENT;
42
- }
43
37
  }
38
+ // CORS is the operator's proxy's: an OPTIONS request gets no CORS header.
39
+ if (incoming.method === "OPTIONS")
40
+ return new Response(null, { status: 204, headers: { allow: ALLOWED_METHODS } });
44
41
  await next();
45
42
  });
46
43
  app.use(async (c, next) => {
47
44
  const { incoming } = c.env;
48
- if (requestHasBody(incoming) && !isJsonContentType(headerValue(incoming, "content-type")))
45
+ if (requestHasBody(incoming) &&
46
+ !isJsonContentType(headerValue(incoming, "content-type")) &&
47
+ // An artifact upload's body is the file itself, of any type.
48
+ tenantRouteOf(incoming)?.bytes !== true)
49
49
  return rejectedResponse(415, "unsupported_media_type", "Request bodies must use application/json");
50
50
  await next();
51
51
  });
@@ -93,14 +93,13 @@ export function createHostApp(options) {
93
93
  return opaqueNotFoundResponse();
94
94
  return await options.admin(c.req.raw, { incoming, outgoing });
95
95
  }
96
- // Tenant routes: key pattern → protocol → the Tenant → selection → the Tenant's routes.
97
- const keyHeader = headerValue(incoming, PUBLISHABLE_KEY_HEADER);
98
- const keyTenant = keyHeader === undefined ? undefined : tenantOfPublishableKey(keyHeader);
99
- if (keyHeader !== undefined && keyTenant === undefined)
100
- return rejectedResponse(400, "invalid_request", `${PUBLISHABLE_KEY_HEADER} header is malformed`);
96
+ // Tenant routes: protocol → the Tenant → selection → the Tenant's routes.
101
97
  const tenantHeader = headerValue(incoming, TENANT_HEADER);
102
98
  const named = tenantHeader === undefined || tenantHeader.trim() === "" ? undefined : tenantHeader.trim();
103
- if (!protocolAccepted(headerValue(incoming, PROTOCOL_HEADER)))
99
+ const protocol = headerValue(incoming, PROTOCOL_HEADER);
100
+ // A capability link is opened without the header; when one is sent, it is checked.
101
+ if (!protocolAccepted(protocol) &&
102
+ !(protocol === undefined && tenantRouteOf(incoming)?.unversioned === true))
104
103
  return protocolRejectedResponse();
105
104
  const resolution = await module.resolve();
106
105
  if (resolution.kind !== "open") {
@@ -113,9 +112,8 @@ export function createHostApp(options) {
113
112
  }
114
113
  const { handle } = resolution;
115
114
  const tenantId = handle.envelope.id;
116
- // A protocol 4 client names the Tenant; a publishable key carries one. Either naming
117
- // another Tenant (or a malformed header) reached the wrong installation.
118
- if ((named !== undefined && named !== tenantId) || (keyTenant !== undefined && keyTenant !== tenantId))
115
+ // A protocol 4 client names the Tenant: naming another reached the wrong installation.
116
+ if (named !== undefined && named !== tenantId)
119
117
  return opaqueNotFoundResponse();
120
118
  c.set("tenantId", tenantId);
121
119
  return await handle.fetch(c.req.raw, { incoming, outgoing });
@@ -148,9 +146,22 @@ export function createHostApp(options) {
148
146
  });
149
147
  return app;
150
148
  }
151
- /** A route a browser page may call, decided from the route alone (`api/http/define.ts`). */
152
- function browserRoute(method, segments) {
153
- return findTenantRoute(method, segments)?.browser === true;
149
+ /** The methods the Runtime serves, for an `OPTIONS` answer's `Allow`. */
150
+ const ALLOWED_METHODS = "GET, POST, PUT, DELETE, OPTIONS";
151
+ /** The Tenant route a request names, if any; a malformed path names none. */
152
+ function tenantRouteOf(incoming) {
153
+ const segments = [];
154
+ for (const segment of pathnameOf(incoming).split("/").filter(Boolean)) {
155
+ try {
156
+ segments.push(decodeURIComponent(segment));
157
+ }
158
+ catch {
159
+ return undefined;
160
+ }
161
+ }
162
+ if (segments[0] !== "v1" || segments[1] === "admin")
163
+ return undefined;
164
+ return findTenantRoute(incoming.method ?? "GET", segments);
154
165
  }
155
166
  /** The answer's status: a Response's, or the Node response's when it was written there. */
156
167
  function statusOf(c) {
@@ -16,4 +16,8 @@ export declare function configForFactory(options: {
16
16
  model?: TenantConfig["model"];
17
17
  /** How Tenants may call Action endpoints (`StackConfig.delivery`). */
18
18
  delivery?: TenantConfig["delivery"];
19
+ /** The trusted issuers of the identity file (`NYLORUN_IDENTITY_FILE`). */
20
+ issuers?: TenantConfig["issuers"];
21
+ /** `NYLORUN_PUBLIC_URL`: the base of the MCP OAuth callback (F9 C2). */
22
+ publicUrl?: string;
19
23
  }): (id: string) => TenantConfig;
@@ -17,6 +17,8 @@ export function configForFactory(options) {
17
17
  sandbox: { backend: sandboxBackend },
18
18
  model: options.model ?? { kind: "vault" },
19
19
  ...(options.delivery ? { delivery: options.delivery } : {}),
20
+ ...(options.issuers ? { issuers: options.issuers } : {}),
21
+ ...(options.publicUrl ? { publicUrl: options.publicUrl } : {}),
20
22
  childEnv: tenantChildEnvironment(baseline, options.hostConfig, tenant),
21
23
  logger: {
22
24
  info: (message, fields) => options.logger.info(message, { tenantId: id, ...fields }),
@@ -4,12 +4,6 @@ export interface HostConfigFile {
4
4
  host: string;
5
5
  port: number;
6
6
  allowNonLoopback?: boolean;
7
- /**
8
- * Whether browser requests (an `Origin` with a publishable key) may reach Tenant routes.
9
- * Default off for a Host started from host.json; container mode reads
10
- * `NYLORUN_BROWSER_ACCESS` instead.
11
- */
12
- browserAccess?: boolean;
13
7
  /**
14
8
  * The operator listener's port: the Admin API moves there (on `host`, loopback only), and
15
9
  * `port` serves the Tenant API alone. Absent: one port serves both.
@@ -24,14 +24,7 @@ export interface CreateHostOptions {
24
24
  */
25
25
  publicUrl?: string;
26
26
  /**
27
- * Browser access (Host feature `browser-access`): requests with an `Origin` may reach Tenant
28
- * routes, where the publishable key's origin allowlist decides. Off by default: every
29
- * `Origin` is `403 origin_rejected`, as before. Admin routes, `/health` and `/ready` refuse
30
- * `Origin` either way.
31
- */
32
- browserAccess?: boolean;
33
- /**
34
- * The operator listener: the Admin API (and the Tenant API, never with browser access) on
27
+ * The operator listener: the Admin API (and the Tenant API, never to browsers) on
35
28
  * its own address, kept off the network that reaches the Tenant API. When set, the main
36
29
  * listener is public and answers admin routes with the opaque 404. Absent: one listener
37
30
  * serves everything, as before.
@@ -66,8 +59,8 @@ export interface OperatorListen {
66
59
  allowedHosts?: readonly string[];
67
60
  }
68
61
  /**
69
- * What a listener serves. `combined`: everything (one port). `public`: the Tenant API, with
70
- * browser access when enabled; admin routes are the opaque 404. `operator`: the Admin API and
62
+ * What a listener serves. `combined`: everything (one port). `public`: the Tenant API, to
63
+ * servers and to browsers with a trusted issuer's token; admin routes are the opaque 404. `operator`: the Admin API and
71
64
  * the Tenant API, never to browsers.
72
65
  */
73
66
  export type ListenerRole = "combined" | "public" | "operator";
@@ -47,6 +47,10 @@ export function createHost(options) {
47
47
  status: adminStatusBody,
48
48
  shutdown: () => void close(),
49
49
  document: adminDocument,
50
+ operatorKeys: async () => {
51
+ const resolved = await module.resolve();
52
+ return resolved.kind === "open" ? resolved.handle.operatorKeys?.() : undefined;
53
+ },
50
54
  });
51
55
  const app = createHostApp({
52
56
  module,
@@ -55,7 +59,6 @@ export function createHost(options) {
55
59
  adminKey: credentials.adminKey,
56
60
  coreVersion,
57
61
  pid,
58
- browserAccess: options.browserAccess === true,
59
62
  ...(options.readiness ? { readiness: options.readiness } : {}),
60
63
  listening: () => Boolean(server?.listening) && (!operator || Boolean(operatorServer?.listening)),
61
64
  closing: () => closing,
@@ -3,7 +3,8 @@
3
3
  * the Tool Gate's routes (`api/gate/routes.ts`), behind the same `Host` check as the Runtime's
4
4
  * listeners.
5
5
  * It opens no Tenant runtime, no Restate endpoint and no stream relay, and runs no migration;
6
- * it needs only the Postgres pool and the Host's tenant directory (read-only).
6
+ * it needs only the Postgres pool and the Host's tenant directory (read-only). Run tokens (F5)
7
+ * verify against the Tenant's public keys and session rows in that database.
7
8
  *
8
9
  * Nothing is written while a model call runs, so the server's request timeout sits above the
9
10
  * gate's longest call. Closing stops accepting calls, lets running ones finish for up to
@@ -13,11 +14,13 @@ import { type Server } from "node:http";
13
14
  import { type InflightCallsOptions } from "../gates/inflight.js";
14
15
  import type { openMcpServer } from "../mcp/connect.js";
15
16
  import type { OutboundPolicy } from "../tenant/outbound.js";
17
+ import { type BlobStore } from "../blob/index.js";
16
18
  import type { ModelCallSettings } from "../gates/model-gate.js";
17
19
  import { type TenantVaults } from "../gates/tenant-vaults.js";
18
20
  import type { PostgresClient } from "../store/postgres/connect.js";
19
21
  import type { Logger } from "../tenant/types.js";
20
- import type { GatesConfig } from "./stack-config.js";
22
+ import type { EgressConfig, GatesConfig } from "./stack-config.js";
23
+ import { type EgressGate, type StartEgressGateOptions } from "../gates/egress.js";
21
24
  /** Above the gate's 600 s provider request timeout and the loop's 630 s client timeout. */
22
25
  export declare const GATES_REQUEST_TIMEOUT_MS = 660000;
23
26
  export interface StartGatesOptions {
@@ -48,6 +51,11 @@ export interface StartGatesOptions {
48
51
  * with the Tenant's vault key. The gateway is then not ready until the key file is there.
49
52
  */
50
53
  readonly keys?: boolean;
54
+ /**
55
+ * The Object store model-gate reads the files a prompt names from (protocol 6): the stack's
56
+ * `s3` store. Without one, the Tenant's `fs` store under the Host root, when there is one.
57
+ */
58
+ readonly blobs?: BlobStore;
51
59
  }
52
60
  export interface GatesServer {
53
61
  /** Where the gate listens, e.g. `http://0.0.0.0:4100`. */
@@ -56,3 +64,20 @@ export interface GatesServer {
56
64
  close(): Promise<void>;
57
65
  }
58
66
  export declare function startGates(options: StartGatesOptions): Promise<GatesServer>;
67
+ export interface StartEgressOptions {
68
+ readonly egress: EgressConfig;
69
+ readonly logger: Logger;
70
+ /** The pool for the Tenant's keys and sandboxes. Required unless `vaults` is given. */
71
+ readonly database?: PostgresClient;
72
+ /** The Host root. Required unless `vaults` is given. */
73
+ readonly hostRoot?: string;
74
+ /** Replaces the Postgres-backed Tenant (tests). */
75
+ readonly vaults?: TenantVaults;
76
+ /** Replaces how sandboxes are read, and the gate's limits and hooks (tests). */
77
+ readonly gate?: Partial<Omit<StartEgressGateOptions, "listen" | "logger" | "verify">>;
78
+ }
79
+ /**
80
+ * egress-gate (`--service egress`, F7.2): pods' CONNECT proxy, verifying egress tokens against the
81
+ * Tenant's public keys and reading its sandboxes from the same database as the gates.
82
+ */
83
+ export declare function startEgress(options: StartEgressOptions): Promise<EgressGate>;
@@ -3,7 +3,8 @@
3
3
  * the Tool Gate's routes (`api/gate/routes.ts`), behind the same `Host` check as the Runtime's
4
4
  * listeners.
5
5
  * It opens no Tenant runtime, no Restate endpoint and no stream relay, and runs no migration;
6
- * it needs only the Postgres pool and the Host's tenant directory (read-only).
6
+ * it needs only the Postgres pool and the Host's tenant directory (read-only). Run tokens (F5)
7
+ * verify against the Tenant's public keys and session rows in that database.
7
8
  *
8
9
  * Nothing is written while a model call runs, so the server's request timeout sits above the
9
10
  * gate's longest call. Closing stops accepting calls, lets running ones finish for up to
@@ -18,9 +19,13 @@ import { createMcpHandler, GATE_MCP_IDLE_MS } from "../gates/mcp-handler.js";
18
19
  import { createToolCalls } from "../gates/tool-calls.js";
19
20
  import { existsSync } from "node:fs";
20
21
  import { tenantPaths } from "../tenant/paths.js";
22
+ import { staleRun, verifyRunToken } from "../tenant/run-token.js";
23
+ import { createFsBlobStore } from "../blob/index.js";
21
24
  import { createTenantVaults } from "../gates/tenant-vaults.js";
22
25
  import { probeDatabase } from "../infra/database.js";
23
26
  import { bindListener, headerValue, isAllowedRequestHost, sendRejected } from "./http.js";
27
+ import { startEgressGate, storeEgressSandboxes } from "../gates/egress.js";
28
+ import { verifyEgressToken } from "../sandbox/egress-token.js";
24
29
  /** Above the gate's 600 s provider request timeout and the loop's 630 s client timeout. */
25
30
  export const GATES_REQUEST_TIMEOUT_MS = 660_000;
26
31
  export async function startGates(options) {
@@ -29,8 +34,17 @@ export async function startGates(options) {
29
34
  if (!vaults) {
30
35
  if (!database || options.hostRoot === undefined)
31
36
  throw new Error("startGates needs the Postgres pool and the Host root");
32
- vaults = createTenantVaults({ sql: database, hostRoot: options.hostRoot });
37
+ vaults = createTenantVaults({
38
+ sql: database,
39
+ hostRoot: options.hostRoot,
40
+ ...(gates.resolver ? { resolver: gates.resolver } : {}),
41
+ ...(options.delivery ? { delivery: options.delivery } : {}),
42
+ });
33
43
  }
44
+ const blobs = options.blobs ??
45
+ (options.hostRoot !== undefined
46
+ ? createFsBlobStore({ root: tenantPaths(options.hostRoot).blobs })
47
+ : undefined);
34
48
  const inflight = createInflightCalls(options.inflight);
35
49
  const mcpIdleMs = options.mcpIdleMs ?? GATE_MCP_IDLE_MS;
36
50
  const mcp = createMcpHandler({
@@ -49,13 +63,24 @@ export async function startGates(options) {
49
63
  sweep.unref();
50
64
  const prune = setInterval(() => void toolCalls.prune(), 60 * 60_000);
51
65
  prune.unref();
66
+ // Run tokens verify against the Tenant's public keys and session rows (F5): the same
67
+ // database the ledger and `tool_crossings` use.
68
+ const publicKeys = new Map();
52
69
  const app = createGatesApp({
53
70
  token: gates.token,
71
+ runs: {
72
+ async verify(raw) {
73
+ const vault = await vaults.open();
74
+ return verifyRunToken(vault.store, vault.tenantId, raw, publicKeys);
75
+ },
76
+ stale: async (claims) => staleRun((await vaults.open()).store, claims),
77
+ },
54
78
  inflight,
55
79
  modelGate: createModelCallHandler({
56
80
  vaults,
57
81
  logger,
58
82
  ...(options.settings ? { settings: options.settings } : {}),
83
+ ...(blobs ? { blobs } : {}),
59
84
  }),
60
85
  ready: async () => {
61
86
  // The keys service needs the vault key, which `nylorun start` writes; it never creates one.
@@ -125,3 +150,28 @@ export async function startGates(options) {
125
150
  },
126
151
  };
127
152
  }
153
+ /**
154
+ * egress-gate (`--service egress`, F7.2): pods' CONNECT proxy, verifying egress tokens against the
155
+ * Tenant's public keys and reading its sandboxes from the same database as the gates.
156
+ */
157
+ export async function startEgress(options) {
158
+ const { database } = options;
159
+ let vaults = options.vaults;
160
+ if (!vaults) {
161
+ if (!database || options.hostRoot === undefined)
162
+ throw new Error("startEgress needs the Postgres pool and the Host root");
163
+ vaults = createTenantVaults({ sql: database, hostRoot: options.hostRoot });
164
+ }
165
+ const tenant = vaults;
166
+ const publicKeys = new Map();
167
+ return startEgressGate({
168
+ listen: options.egress.listen,
169
+ logger: options.logger,
170
+ async verify(raw) {
171
+ const vault = await tenant.open();
172
+ return verifyEgressToken(vault.store, vault.tenantId, raw, publicKeys);
173
+ },
174
+ sandboxes: { live: async (sandboxId) => storeEgressSandboxes((await tenant.open()).store).live(sandboxId) },
175
+ ...options.gate,
176
+ });
177
+ }
@@ -43,7 +43,10 @@ export declare function isJsonContentType(value: string | undefined): boolean;
43
43
  export declare function requestHasBody(request: IncomingMessage): boolean;
44
44
  /** `/health` and `/ready` are polled; the request log leaves them out. */
45
45
  export declare function pathnameIsLogged(rawUrl: string | undefined): boolean;
46
- /** Redact path parameters after `/v1/sessions/` for Host logs. */
46
+ /**
47
+ * Redact path parameters after `/v1/sessions/` for Host logs, and a capability link's token
48
+ * (a credential) after `/v1/artifact-links/`.
49
+ */
47
50
  export declare function redactRoutePath(pathname: string): string;
48
51
  /** Constant-time comparison of SHA-256 digests of the presented and stored admin keys. */
49
52
  export declare function adminKeyMatches(presented: string | undefined, adminKey: string): boolean;
package/dist/host/http.js CHANGED
@@ -135,13 +135,18 @@ export function pathnameIsLogged(rawUrl) {
135
135
  const pathname = new URL(rawUrl, "http://runtime.local").pathname;
136
136
  return pathname !== "/health" && pathname !== "/ready";
137
137
  }
138
- /** Redact path parameters after `/v1/sessions/` for Host logs. */
138
+ /**
139
+ * Redact path parameters after `/v1/sessions/` for Host logs, and a capability link's token
140
+ * (a credential) after `/v1/artifact-links/`.
141
+ */
139
142
  export function redactRoutePath(pathname) {
140
143
  const parts = pathname.split("/");
141
144
  // ["", "v1", "sessions", "<id>", ...]
142
145
  if (parts[1] === "v1" && parts[2] === "sessions" && parts.length > 3) {
143
146
  return `/v1/sessions/:id${parts.length > 4 ? "/…" : ""}`;
144
147
  }
148
+ if (parts[1] === "v1" && parts[2] === "artifact-links" && parts.length > 3)
149
+ return "/v1/artifact-links/:token";
145
150
  return pathname;
146
151
  }
147
152
  /** Constant-time comparison of SHA-256 digests of the presented and stored admin keys. */