@nylorun/runtime 0.15.1-beta → 0.16.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 (220) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +33 -6
  3. package/dist/adapters/execution/restate.d.ts +9 -1
  4. package/dist/adapters/execution/restate.js +64 -5
  5. package/dist/adapters/replication/pgoutput.js +3 -1
  6. package/dist/adapters/sandbox/local.d.ts +10 -0
  7. package/dist/adapters/sandbox/local.js +195 -0
  8. package/dist/adapters/sandbox/virtual.js +36 -1
  9. package/dist/admin-openapi.json +37 -3
  10. package/dist/api/a2a/tasks.js +6 -1
  11. package/dist/api/ag-ui/history.js +6 -1
  12. package/dist/api/ag-ui/routes.js +5 -1
  13. package/dist/api/components.d.ts +2673 -655
  14. package/dist/api/components.js +17 -1
  15. package/dist/api/gate/keys.d.ts +4 -3
  16. package/dist/api/gate/routes.d.ts +19 -3
  17. package/dist/api/gate/routes.js +162 -53
  18. package/dist/api/http/app.js +4 -0
  19. package/dist/api/http/define.d.ts +10 -0
  20. package/dist/api/http/define.js +2 -1
  21. package/dist/api/http/routes/artifacts.d.ts +11 -0
  22. package/dist/api/http/routes/artifacts.js +518 -0
  23. package/dist/api/http/routes/sandboxes.d.ts +9 -0
  24. package/dist/api/http/routes/sandboxes.js +152 -0
  25. package/dist/api/http/routes/sessions.js +3 -1
  26. package/dist/api/http/routes/tenant.js +36 -2
  27. package/dist/artifacts/config.d.ts +23 -0
  28. package/dist/artifacts/config.js +27 -0
  29. package/dist/artifacts/export.d.ts +8 -0
  30. package/dist/artifacts/export.js +205 -0
  31. package/dist/artifacts/files.d.ts +36 -0
  32. package/dist/artifacts/files.js +45 -0
  33. package/dist/artifacts/folders.d.ts +60 -0
  34. package/dist/artifacts/folders.js +209 -0
  35. package/dist/artifacts/links.d.ts +28 -0
  36. package/dist/artifacts/links.js +92 -0
  37. package/dist/artifacts/media-types.d.ts +13 -0
  38. package/dist/artifacts/media-types.js +80 -0
  39. package/dist/artifacts/parts.d.ts +34 -0
  40. package/dist/artifacts/parts.js +38 -0
  41. package/dist/artifacts/service.d.ts +74 -0
  42. package/dist/artifacts/service.js +301 -0
  43. package/dist/artifacts/workspace.d.ts +60 -0
  44. package/dist/artifacts/workspace.js +57 -0
  45. package/dist/artifacts/zip.d.ts +10 -0
  46. package/dist/artifacts/zip.js +126 -0
  47. package/dist/blob/fs.d.ts +13 -0
  48. package/dist/blob/fs.js +194 -0
  49. package/dist/blob/index.d.ts +9 -0
  50. package/dist/blob/index.js +9 -0
  51. package/dist/blob/s3.d.ts +42 -0
  52. package/dist/blob/s3.js +366 -0
  53. package/dist/blob/types.d.ts +108 -0
  54. package/dist/blob/types.js +85 -0
  55. package/dist/core/sandbox-routes.d.ts +2 -2
  56. package/dist/core/sandbox-routes.js +4 -3
  57. package/dist/execution/memory.d.ts +11 -1
  58. package/dist/execution/memory.js +77 -3
  59. package/dist/execution/types.d.ts +54 -1
  60. package/dist/execution/types.js +31 -0
  61. package/dist/gates/contract.d.ts +14 -13
  62. package/dist/gates/contract.js +13 -12
  63. package/dist/gates/egress.d.ts +72 -0
  64. package/dist/gates/egress.js +319 -0
  65. package/dist/gates/handler.d.ts +3 -0
  66. package/dist/gates/handler.js +3 -2
  67. package/dist/gates/http-client.d.ts +3 -2
  68. package/dist/gates/http-client.js +17 -6
  69. package/dist/gates/in-process.d.ts +13 -1
  70. package/dist/gates/in-process.js +11 -0
  71. package/dist/gates/inflight.d.ts +23 -4
  72. package/dist/gates/inflight.js +18 -4
  73. package/dist/gates/model-gate.d.ts +6 -2
  74. package/dist/gates/tool-calls.d.ts +9 -5
  75. package/dist/gates/tool-calls.js +6 -6
  76. package/dist/gates/tool-client.d.ts +9 -2
  77. package/dist/gates/tool-client.js +56 -22
  78. package/dist/gates/tool-contract.d.ts +12 -7
  79. package/dist/gates/tool-contract.js +10 -4
  80. package/dist/gates/tool-gate.d.ts +1 -0
  81. package/dist/harness/calls.d.ts +52 -0
  82. package/dist/harness/calls.js +113 -0
  83. package/dist/harness/executors.d.ts +22 -0
  84. package/dist/harness/executors.js +96 -0
  85. package/dist/harness/main.d.ts +6 -0
  86. package/dist/harness/main.js +103 -0
  87. package/dist/harness/pod.d.ts +21 -0
  88. package/dist/harness/pod.js +119 -0
  89. package/dist/harness/run-tokens.d.ts +18 -0
  90. package/dist/harness/run-tokens.js +15 -0
  91. package/dist/harness/service.d.ts +46 -0
  92. package/dist/harness/service.js +179 -0
  93. package/dist/harness/ws-client.d.ts +43 -0
  94. package/dist/harness/ws-client.js +122 -0
  95. package/dist/harness/ws-port.d.ts +22 -0
  96. package/dist/harness/ws-port.js +78 -0
  97. package/dist/harness-api/in-process.d.ts +24 -0
  98. package/dist/harness-api/in-process.js +119 -0
  99. package/dist/harness-api/record.d.ts +44 -0
  100. package/dist/harness-api/record.js +250 -0
  101. package/dist/harness-api/renew.d.ts +37 -0
  102. package/dist/harness-api/renew.js +77 -0
  103. package/dist/harness-api/server.d.ts +104 -0
  104. package/dist/harness-api/server.js +408 -0
  105. package/dist/harness-api/start.d.ts +36 -0
  106. package/dist/harness-api/start.js +118 -0
  107. package/dist/harness-api/workspace.d.ts +53 -0
  108. package/dist/harness-api/workspace.js +201 -0
  109. package/dist/harness-api/ws-server.d.ts +34 -0
  110. package/dist/harness-api/ws-server.js +227 -0
  111. package/dist/host/app.js +23 -2
  112. package/dist/host/gates.d.ts +27 -2
  113. package/dist/host/gates.js +46 -1
  114. package/dist/host/http.d.ts +4 -1
  115. package/dist/host/http.js +6 -1
  116. package/dist/host/main.js +135 -27
  117. package/dist/host/stack-config.d.ts +101 -1
  118. package/dist/host/stack-config.js +199 -8
  119. package/dist/mcp/snapshot.d.ts +8 -1
  120. package/dist/mcp/snapshot.js +28 -0
  121. package/dist/media.d.ts +5 -24
  122. package/dist/media.js +5 -0
  123. package/dist/model/pi-model.d.ts +6 -2
  124. package/dist/model/pi-model.js +39 -22
  125. package/dist/node/index.d.ts +1 -1
  126. package/dist/node/index.js +1 -1
  127. package/dist/openapi.json +15245 -8850
  128. package/dist/record/index.d.ts +1 -0
  129. package/dist/record/index.js +1 -0
  130. package/dist/record/sandbox.d.ts +25 -0
  131. package/dist/record/sandbox.js +43 -0
  132. package/dist/sandbox/capability.d.ts +3 -0
  133. package/dist/sandbox/capability.js +10 -0
  134. package/dist/sandbox/egress-token.d.ts +26 -0
  135. package/dist/sandbox/egress-token.js +8 -0
  136. package/dist/sandbox/join.d.ts +37 -0
  137. package/dist/sandbox/join.js +165 -0
  138. package/dist/sandbox/manager.d.ts +99 -34
  139. package/dist/sandbox/manager.js +216 -84
  140. package/dist/sandbox/placement.d.ts +27 -0
  141. package/dist/sandbox/placement.js +44 -0
  142. package/dist/sandbox/pods/client.d.ts +90 -0
  143. package/dist/sandbox/pods/client.js +79 -0
  144. package/dist/sandbox/pods/lifecycle.d.ts +91 -0
  145. package/dist/sandbox/pods/lifecycle.js +178 -0
  146. package/dist/sandbox/pods/name.d.ts +1 -0
  147. package/dist/sandbox/pods/name.js +28 -0
  148. package/dist/sandbox/pods/network-gate.d.ts +25 -0
  149. package/dist/sandbox/pods/network-gate.js +71 -0
  150. package/dist/sandbox/pods/reconcile.d.ts +11 -0
  151. package/dist/sandbox/pods/reconcile.js +152 -0
  152. package/dist/sandbox/pods/spec.d.ts +45 -0
  153. package/dist/sandbox/pods/spec.js +62 -0
  154. package/dist/sandbox/records.d.ts +29 -0
  155. package/dist/sandbox/records.js +79 -0
  156. package/dist/sandbox/resolve.d.ts +5 -0
  157. package/dist/sandbox/resolve.js +3 -2
  158. package/dist/sandbox/session-sandbox.js +7 -5
  159. package/dist/sandbox/share.d.ts +11 -0
  160. package/dist/sandbox/share.js +10 -0
  161. package/dist/sandbox/tenant-config.d.ts +16 -1
  162. package/dist/sandbox/tenant-config.js +9 -0
  163. package/dist/sandbox/types.d.ts +29 -1
  164. package/dist/sandbox/types.js +13 -1
  165. package/dist/store/postgres/drizzle/0004_sandbox_resources.sql +20 -0
  166. package/dist/store/postgres/drizzle/0005_artifacts.sql +27 -0
  167. package/dist/store/postgres/drizzle/0006_folder_artifacts.sql +12 -0
  168. package/dist/store/postgres/drizzle/0007_sandbox_pods.sql +14 -0
  169. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  170. package/dist/store/postgres/record-writer.d.ts +6 -1
  171. package/dist/store/postgres/record-writer.js +20 -2
  172. package/dist/store/postgres/schema.d.ts +894 -0
  173. package/dist/store/postgres/schema.js +130 -2
  174. package/dist/store/postgres/store.js +289 -4
  175. package/dist/store/types.d.ts +143 -6
  176. package/dist/streams/control.d.ts +2 -0
  177. package/dist/streams/control.js +5 -0
  178. package/dist/streams/types.d.ts +10 -1
  179. package/dist/tenant/advance.js +218 -160
  180. package/dist/tenant/artifact-tool.d.ts +5 -0
  181. package/dist/tenant/artifact-tool.js +119 -0
  182. package/dist/tenant/commands.js +30 -5
  183. package/dist/tenant/context.d.ts +64 -5
  184. package/dist/tenant/effects.d.ts +19 -13
  185. package/dist/tenant/effects.js +5 -382
  186. package/dist/tenant/history.d.ts +13 -23
  187. package/dist/tenant/history.js +14 -62
  188. package/dist/tenant/host-token.d.ts +92 -0
  189. package/dist/tenant/host-token.js +179 -0
  190. package/dist/tenant/module.js +1 -0
  191. package/dist/tenant/paths.d.ts +2 -1
  192. package/dist/tenant/paths.js +3 -1
  193. package/dist/tenant/reset.d.ts +5 -2
  194. package/dist/tenant/reset.js +22 -2
  195. package/dist/tenant/run-grants.d.ts +49 -0
  196. package/dist/tenant/run-grants.js +62 -0
  197. package/dist/tenant/run-token.d.ts +70 -0
  198. package/dist/tenant/run-token.js +138 -0
  199. package/dist/tenant/runtime.d.ts +38 -0
  200. package/dist/tenant/runtime.js +120 -32
  201. package/dist/tenant/sandboxes.d.ts +119 -0
  202. package/dist/tenant/sandboxes.js +551 -0
  203. package/dist/tenant/session.d.ts +2 -13
  204. package/dist/tenant/session.js +4 -28
  205. package/dist/tenant/sessions.d.ts +5 -0
  206. package/dist/tenant/sessions.js +28 -1
  207. package/dist/tenant/status.d.ts +8 -3
  208. package/dist/tenant/status.js +5 -0
  209. package/dist/tenant/streams.d.ts +7 -0
  210. package/dist/tenant/streams.js +24 -1
  211. package/dist/tenant/sweep.js +37 -4
  212. package/dist/tenant/tokens.js +9 -2
  213. package/dist/tenant/types.d.ts +18 -1
  214. package/dist/tenant/worker.d.ts +11 -5
  215. package/dist/tenant/worker.js +12 -6
  216. package/dist/version.d.ts +1 -1
  217. package/dist/version.js +1 -1
  218. package/package.json +13 -10
  219. package/dist/adapters/media.d.ts +0 -24
  220. package/dist/adapters/media.js +0 -111
@@ -5,7 +5,7 @@
5
5
  * global registry.
6
6
  */
7
7
  import { z } from "zod";
8
- import { JwksSchema, CreateTokenRequestSchema, CreateTokenResponseSchema, AccessPolicyResponseSchema, PutAccessPolicyRequestSchema, SigningKeyListSchema, SigningKeyViewSchema, RotateSigningKeysRequestSchema, RevokeSigningKeyRequestSchema, ListPublishableKeysResponseSchema, PublishableKeySchema, CreatePublishableKeyRequestSchema, UpdatePublishableKeyRequestSchema, RevokeSubjectRequestSchema, RevokeSubjectResponseSchema, TenantStatusSchema, ResetTenantRequestSchema, ResetTenantResponseSchema, SeedTenantConfigRequestSchema, SeedTenantConfigResponseSchema, HostModelCatalogSchema, TenantSandboxViewSchema, ModelUsageTotalsSchema, ModelBudgetsSchema, PutModelBudgetsRequestSchema, PutTenantSandboxRequestSchema, ListProvidersResponseSchema, HostModelViewSchema, PutHostModelRequestSchema, SelectHostModelRequestSchema, CreateVaultRequestSchema, VaultInfoSchema, ListVaultsResponseSchema, CreateCredentialRequestSchema, CredentialInfoSchema, ListCredentialsResponseSchema, RotateCredentialRequestSchema, DeletedResponseSchema, ListAgentsResponseSchema, ListPublicAgentsResponseSchema, ListSessionsResponseSchema, LiveEventSchema, PutAgentRequestSchema, PutAgentResponseSchema, PutSessionRequestSchema, SessionItemsResponseSchema, SessionViewSchema, StreamClosedFrameSchema, AcceptedResponseSchema, ActionResultReceiptSchema, DeleteEndpointResponseSchema, DeliveryHeartbeatResponseSchema, ActionOutcomeSchema, EndpointPingResponseSchema, ListEndpointsResponseSchema, PutEndpointsRequestSchema, SandboxToolOutcomeSchema, SessionCommandSchema, AdminStatusSchema, HostShutdownResponseSchema, ProtocolRejectedResponseSchema, RejectedResponseSchema, EVENT_SCHEMAS, EVENT_TYPES, } from "@nylorun/core/contracts";
8
+ import { JwksSchema, CreateTokenRequestSchema, CreateTokenResponseSchema, AccessPolicyResponseSchema, PutAccessPolicyRequestSchema, SigningKeyListSchema, SigningKeyViewSchema, RotateSigningKeysRequestSchema, RevokeSigningKeyRequestSchema, ListPublishableKeysResponseSchema, PublishableKeySchema, CreatePublishableKeyRequestSchema, UpdatePublishableKeyRequestSchema, RevokeSubjectRequestSchema, RevokeSubjectResponseSchema, TenantStatusSchema, ResetTenantRequestSchema, ResetTenantResponseSchema, SeedTenantConfigRequestSchema, SeedTenantConfigResponseSchema, HostModelCatalogSchema, TenantSandboxViewSchema, ModelUsageTotalsSchema, ModelBudgetsSchema, PutModelBudgetsRequestSchema, PutTenantSandboxRequestSchema, ListProvidersResponseSchema, HostModelViewSchema, PutHostModelRequestSchema, SelectHostModelRequestSchema, CreateVaultRequestSchema, VaultInfoSchema, ListVaultsResponseSchema, CreateCredentialRequestSchema, CredentialInfoSchema, ListCredentialsResponseSchema, RotateCredentialRequestSchema, DeletedResponseSchema, ListAgentsResponseSchema, ListPublicAgentsResponseSchema, ListSessionsResponseSchema, LiveEventSchema, PutAgentRequestSchema, PutAgentResponseSchema, PutSessionRequestSchema, SessionItemsResponseSchema, SessionViewSchema, StreamClosedFrameSchema, AcceptedResponseSchema, ActionResultReceiptSchema, DeleteEndpointResponseSchema, DeliveryHeartbeatResponseSchema, ActionOutcomeSchema, EndpointPingResponseSchema, ListEndpointsResponseSchema, PutEndpointsRequestSchema, SandboxToolOutcomeSchema, PutSandboxRequestSchema, SandboxViewSchema, ListSandboxesResponseSchema, DeleteSandboxResponseSchema, ListSandboxEventsResponseSchema, ArtifactViewSchema, ArtifactVersionViewSchema, ListArtifactsResponseSchema, UploadArtifactResponseSchema, DeleteArtifactResponseSchema, CreateArtifactLinkRequestSchema, ArtifactLinkSchema, PutTenantArtifactsRequestSchema, TenantArtifactsViewSchema, ArtifactTreeSchema, ArtifactDiffSchema, SessionCommandSchema, AdminStatusSchema, HostShutdownResponseSchema, ProtocolRejectedResponseSchema, RejectedResponseSchema, EVENT_SCHEMAS, EVENT_TYPES, } from "@nylorun/core/contracts";
9
9
  function named(id, schema) {
10
10
  return schema.meta({ id });
11
11
  }
@@ -14,6 +14,22 @@ export const ProtocolRejected = named("ProtocolRejected", ProtocolRejectedRespon
14
14
  export const AdminStatus = named("AdminStatus", AdminStatusSchema);
15
15
  export const HostShutdownResponse = named("HostShutdownResponse", HostShutdownResponseSchema);
16
16
  export const SandboxToolOutcome = named("SandboxToolOutcome", SandboxToolOutcomeSchema);
17
+ export const PutSandboxRequest = named("PutSandboxRequest", PutSandboxRequestSchema);
18
+ export const SandboxView = named("SandboxView", SandboxViewSchema);
19
+ export const ListSandboxesResponse = named("ListSandboxesResponse", ListSandboxesResponseSchema);
20
+ export const DeleteSandboxResponse = named("DeleteSandboxResponse", DeleteSandboxResponseSchema);
21
+ export const ListSandboxEventsResponse = named("ListSandboxEventsResponse", ListSandboxEventsResponseSchema);
22
+ export const ArtifactVersionView = named("ArtifactVersionView", ArtifactVersionViewSchema);
23
+ export const ArtifactView = named("ArtifactView", ArtifactViewSchema);
24
+ export const ListArtifactsResponse = named("ListArtifactsResponse", ListArtifactsResponseSchema);
25
+ export const UploadArtifactResponse = named("UploadArtifactResponse", UploadArtifactResponseSchema);
26
+ export const DeleteArtifactResponse = named("DeleteArtifactResponse", DeleteArtifactResponseSchema);
27
+ export const CreateArtifactLinkRequest = named("CreateArtifactLinkRequest", CreateArtifactLinkRequestSchema);
28
+ export const ArtifactLink = named("ArtifactLink", ArtifactLinkSchema);
29
+ export const PutTenantArtifactsRequest = named("PutTenantArtifactsRequest", PutTenantArtifactsRequestSchema);
30
+ export const TenantArtifactsView = named("TenantArtifactsView", TenantArtifactsViewSchema);
31
+ export const ArtifactTree = named("ArtifactTree", ArtifactTreeSchema);
32
+ export const ArtifactDiff = named("ArtifactDiff", ArtifactDiffSchema);
17
33
  export const PutEndpointsRequest = named("PutEndpointsRequest", PutEndpointsRequestSchema);
18
34
  export const ListEndpointsResponse = named("ListEndpointsResponse", ListEndpointsResponseSchema);
19
35
  export const DeleteEndpointResponse = named("DeleteEndpointResponse", DeleteEndpointResponseSchema);
@@ -3,11 +3,12 @@
3
3
  * `Keys` per request, run with the vault key of the gateway's Tenant. Errors keep their status,
4
4
  * code and details. Logs the operation and its outcome, never an argument or a result.
5
5
  */
6
- import type { Hono, MiddlewareHandler } from "hono";
6
+ import type { Env, Hono, MiddlewareHandler } from "hono";
7
7
  import type { Logger } from "../../tenant/types.js";
8
8
  import { type Keys } from "../../keys/keys.js";
9
- export declare function mountKeysRoutes(app: Hono, options: {
10
- readonly authorized: MiddlewareHandler;
9
+ export declare function mountKeysRoutes<E extends Env>(app: Hono<E>, options: {
10
+ /** Core's credential: no run token reaches the keys (F5). */
11
+ readonly authorized: MiddlewareHandler<E>;
11
12
  /** The gateway's Tenant's keys; rejects when the Tenant is not ready. */
12
13
  readonly keys: () => Promise<Keys>;
13
14
  readonly logger: Logger;
@@ -6,9 +6,18 @@ import type { ToolCalls } from "../../gates/tool-calls.js";
6
6
  import type { Keys } from "../../keys/keys.js";
7
7
  import type { Logger } from "../../tenant/types.js";
8
8
  import { type OutboundPolicy } from "../../tenant/outbound.js";
9
+ import type { RunClaims, RunTokenVerdict } from "../../tenant/run-token.js";
10
+ /** How the gateway checks a run token: its signature and claims, then its lease (G4). */
11
+ export interface RunTokenCheck {
12
+ verify(raw: string): Promise<RunTokenVerdict>;
13
+ /** Why calls under `claims` are stale, or undefined while the lease is the session's. */
14
+ stale(claims: RunClaims): Promise<string | undefined>;
15
+ }
9
16
  export interface GatesAppOptions {
10
- /** The bearer every model call must present (`NYLORUN_GATES_TOKEN`). */
17
+ /** Core's credential (`NYLORUN_GATES_TOKEN`): keys, deliveries, MCP requests outside a run. */
11
18
  readonly token: string;
19
+ /** Checks run tokens (F5): the credential of model calls and a session's MCP requests. */
20
+ readonly runs: RunTokenCheck;
12
21
  /** Serves an authenticated, well-formed call. */
13
22
  readonly modelGate: ModelCallHandler;
14
23
  /** Keyed calls, which outlive their client (P1.2). */
@@ -23,7 +32,7 @@ export interface GatesAppOptions {
23
32
  readonly toolCalls?: ToolCalls;
24
33
  /** The keys service's operations (F4.2). Without it the keys route answers 404. */
25
34
  readonly keys?: () => Promise<Keys>;
26
- /** Logs the keys service's requests. */
35
+ /** Logs the keys service's requests and refused credentials. */
27
36
  readonly logger?: Logger;
28
37
  /**
29
38
  * How the gate may call Action endpoints (the gateway's own `NYLORUN_ENDPOINT_*`), for
@@ -31,4 +40,11 @@ export interface GatesAppOptions {
31
40
  */
32
41
  readonly delivery?: OutboundPolicy;
33
42
  }
34
- export declare function createGatesApp(options: GatesAppOptions): Hono;
43
+ /** The verified run token of a request on a run route; absent under core's credential. */
44
+ type GateEnv = {
45
+ Variables: {
46
+ run?: RunClaims;
47
+ };
48
+ };
49
+ export declare function createGatesApp(options: GatesAppOptions): Hono<GateEnv>;
50
+ export {};
@@ -1,45 +1,99 @@
1
1
  /**
2
2
  * The gates service's HTTP routes (blueprint §15, P1.1): the Model Gate's IR endpoint, the Tool
3
- * Gate's remote MCP and delivery routes (F4.1), and the health and readiness probes. Internal:
4
- * only the loop calls them, with the stack's gates token, so they are not in the published
5
- * OpenAPI documents. The wire formats are `gates/contract.ts` and `gates/tool-contract.ts`.
3
+ * Gate's remote MCP and delivery routes (F4.1), the keys service (F4.2), and the health and
4
+ * readiness probes. Internal, so they are not in the published OpenAPI documents. The wire
5
+ * formats are `gates/contract.ts` and `gates/tool-contract.ts`.
6
+ *
7
+ * Two credentials (F5, gate trust):
8
+ *
9
+ * | Route | Accepts | Scope from |
10
+ * | --- | --- | --- |
11
+ * | `POST /nylorun/v1/model-calls`, its cancel | run token | the token |
12
+ * | `POST /nylorun/v1/tool-calls`, its cancel; `mcp/connect`, `mcp/list`, `mcp/close` | run token, or core's | the token; the body only for core's |
13
+ * | `POST /nylorun/v1/keys/:operation` | core's | — |
14
+ * | `POST /nylorun/v1/deliveries` | core's | — |
15
+ *
16
+ * Core's credential is `NYLORUN_GATES_TOKEN`, which only the runtime container holds. A run
17
+ * token (`tenant/run-token.ts`) names one lease of one session's turn: a call under it must
18
+ * still be the session's (`409 run_stale` otherwise); a cancel need not, since a user cancel
19
+ * makes the token stale, but stops only its own session's call.
6
20
  */
7
21
  import { createHash } from "node:crypto";
8
22
  import { Hono } from "hono";
9
23
  import { bodyLimit } from "hono/body-limit";
10
24
  import { isTenantId } from "@nylorun/core/compatibility";
11
25
  import { MAX_MODEL_CALL_BYTES, MODEL_CALLS_PATH, ModelCallBodySchema, TENANT_HEADER, } from "../../gates/contract.js";
12
- import { InflightConflict } from "../../gates/inflight.js";
26
+ import { InflightConflict, InflightStale } from "../../gates/inflight.js";
13
27
  import { mountKeysRoutes } from "./keys.js";
14
28
  import { DELIVERIES_PATH, DELIVERY_HEADERS, DeliveryBodySchema, MAX_TOOL_BODY_BYTES, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, McpListBodySchema, McpServerBodySchema, TOOL_CALLS_PATH, ToolCallBodySchema, } from "../../gates/tool-contract.js";
15
29
  import { post } from "../../tenant/outbound.js";
30
+ import { looksLikeToken } from "../../tenant/jwt.js";
16
31
  import { adminKeyMatches } from "../../host/http.js";
17
32
  import { canonical } from "../../store/canonical.js";
18
33
  const invalid = (message) => ({
19
34
  error: { code: "invalid_request", message },
20
35
  });
36
+ const gateError = (code, message) => ({
37
+ error: { code, message },
38
+ });
21
39
  export function createGatesApp(options) {
22
40
  const app = new Hono();
23
41
  const maxSize = options.maxBodyBytes ?? MAX_MODEL_CALL_BYTES;
42
+ const logger = options.logger ?? { info() { }, warn() { }, error() { } };
24
43
  app.get("/health", (c) => c.json({ status: "ok" }));
25
44
  app.get("/ready", async (c) => (await options.ready()) ? c.json({ ready: true }) : c.json({ ready: false }, 503));
26
- const authorized = async (c, next) => {
27
- const presented = /^Bearer (.+)$/.exec(c.req.header("authorization") ?? "")?.[1];
28
- if (!adminKeyMatches(presented, options.token)) {
29
- const body = {
30
- error: {
31
- code: "gate_unauthorized",
32
- message: "Missing or wrong gates token; the caller and the gateway must share NYLORUN_GATES_TOKEN",
33
- },
34
- };
35
- return c.json(body, 401);
45
+ const bearerOf = (c) => /^Bearer (.+)$/.exec(c.req.header("authorization") ?? "")?.[1];
46
+ const unauthorized = (c, message) => c.json(gateError("gate_unauthorized", message), 401);
47
+ /** Core's credential, compared in constant time. */
48
+ const core = async (c, next) => {
49
+ if (!adminKeyMatches(bearerOf(c), options.token))
50
+ return unauthorized(c, "Missing or wrong credential: this route takes core's (NYLORUN_GATES_TOKEN)");
51
+ await next();
52
+ };
53
+ /**
54
+ * A run token, verified and put on the context; with `live`, its lease must still be the
55
+ * session's. With `orCore`, core's credential is accepted instead, and the route reads the
56
+ * session from the body.
57
+ */
58
+ const run = (accept) => async (c, next) => {
59
+ const presented = bearerOf(c);
60
+ // Core's credential is never JWT-shaped; a run token always is.
61
+ if (accept.orCore && presented !== undefined && !looksLikeToken(presented))
62
+ return core(c, next);
63
+ if (presented === undefined || !looksLikeToken(presented))
64
+ return unauthorized(c, "Missing or wrong credential: this route takes a run token");
65
+ const verdict = await options.runs.verify(presented);
66
+ if (!verdict.ok) {
67
+ logger.warn("gate_credential_refused", { route: c.req.path, reason: verdict.reason });
68
+ return unauthorized(c, "The run token is malformed, expired, revoked or not this Tenant's");
36
69
  }
70
+ if (accept.live) {
71
+ const stale = await options.runs.stale(verdict.claims);
72
+ if (stale) {
73
+ logger.warn("gate_run_stale", {
74
+ route: c.req.path,
75
+ session: verdict.claims.sessionId,
76
+ epoch: verdict.claims.epoch,
77
+ reason: stale,
78
+ });
79
+ return c.json(gateError("run_stale", `The run token's lease is over: ${stale}`), 409);
80
+ }
81
+ }
82
+ c.set("run", verdict.claims);
37
83
  await next();
38
84
  };
85
+ /**
86
+ * The Tenant a request is for: a run token's, which a `Nylorun-Tenant` header must then
87
+ * name; else the header's, when it is a Tenant id. `false` for a bad header.
88
+ */
39
89
  const tenantOf = (c) => {
40
90
  const tenantId = c.req.header(TENANT_HEADER);
41
- return tenantId !== undefined && !isTenantId(tenantId) ? false : tenantId;
91
+ const claims = c.get("run");
92
+ if (tenantId !== undefined && (!isTenantId(tenantId) || (claims && tenantId !== claims.tenantId)))
93
+ return false;
94
+ return claims?.tenantId ?? tenantId;
42
95
  };
96
+ const badTenant = (c) => c.json(invalid(`The ${TENANT_HEADER} header must name a Tenant, the run token's when there is one`), 400);
43
97
  const toolBodies = bodyLimit({
44
98
  maxSize: MAX_TOOL_BODY_BYTES,
45
99
  onError: (c) => c.json(invalid(`A request body may be at most ${MAX_TOOL_BODY_BYTES} bytes`), 400),
@@ -48,7 +102,7 @@ export function createGatesApp(options) {
48
102
  async function read(c, schema) {
49
103
  const tenantId = tenantOf(c);
50
104
  if (tenantId === false)
51
- return { ok: false, response: c.json(invalid(`The ${TENANT_HEADER} header must name a Tenant`), 400) };
105
+ return { ok: false, response: badTenant(c) };
52
106
  let raw;
53
107
  try {
54
108
  raw = await c.req.json();
@@ -64,61 +118,111 @@ export function createGatesApp(options) {
64
118
  };
65
119
  return { ok: true, tenantId, body: parsed.data };
66
120
  }
121
+ /**
122
+ * The MCP server a request names, in its session: a run token's session (a body naming
123
+ * another is refused), or, under core's credential, the body's.
124
+ */
125
+ function serverOf(c, server) {
126
+ const claims = c.get("run");
127
+ if (claims) {
128
+ if (server.sessionId !== undefined && server.sessionId !== claims.sessionId)
129
+ return {
130
+ ok: false,
131
+ response: c.json(gateError("gate_forbidden", "The run token is for another session"), 403),
132
+ };
133
+ return { ok: true, server: { ...server, sessionId: claims.sessionId } };
134
+ }
135
+ if (server.sessionId === undefined)
136
+ return {
137
+ ok: false,
138
+ response: c.json(invalid("server.sessionId is required with core's credential"), 400),
139
+ };
140
+ return { ok: true, server: server };
141
+ }
142
+ /** The run a keyed call belongs to, for the in-flight map (G4). */
143
+ const ownerOf = (c) => {
144
+ const claims = c.get("run");
145
+ return claims ? { sessionId: claims.sessionId, epoch: claims.epoch } : undefined;
146
+ };
147
+ /** 409 for a keyed re-send the in-flight map refused, or undefined. */
148
+ const refusedJoin = (c, error) => {
149
+ if (error instanceof InflightStale)
150
+ return c.json(gateError("run_stale", error.message), 409);
151
+ if (error instanceof InflightConflict)
152
+ return c.json(gateError("gate_conflict", error.message), 409);
153
+ return undefined;
154
+ };
67
155
  if (options.keys)
68
156
  mountKeysRoutes(app, {
69
- authorized,
157
+ authorized: core,
70
158
  keys: options.keys,
71
- logger: options.logger ?? { info() { }, warn() { }, error() { } },
159
+ logger,
72
160
  });
73
161
  const mcp = options.mcp;
74
162
  if (mcp) {
75
- app.post(MCP_CONNECT_PATH, authorized, toolBodies, async (c) => {
163
+ const scoped = run({ live: true, orCore: true });
164
+ app.post(MCP_CONNECT_PATH, scoped, toolBodies, async (c) => {
76
165
  const request = await read(c, McpServerBodySchema);
77
166
  if (!request.ok)
78
167
  return request.response;
79
- return c.json(await mcp.connect(request.tenantId, request.body.server));
168
+ const server = serverOf(c, request.body.server);
169
+ if (!server.ok)
170
+ return server.response;
171
+ return c.json(await mcp.connect(request.tenantId, server.server));
80
172
  });
81
- app.post(MCP_LIST_PATH, authorized, toolBodies, async (c) => {
173
+ app.post(MCP_LIST_PATH, scoped, toolBodies, async (c) => {
82
174
  const request = await read(c, McpListBodySchema);
83
175
  if (!request.ok)
84
176
  return request.response;
85
- const { server, cursor } = request.body;
86
- return c.json(await mcp.list(request.tenantId, server, cursor, c.req.raw.signal));
177
+ const server = serverOf(c, request.body.server);
178
+ if (!server.ok)
179
+ return server.response;
180
+ return c.json(await mcp.list(request.tenantId, server.server, request.body.cursor, c.req.raw.signal));
87
181
  });
88
- app.post(MCP_CLOSE_PATH, authorized, toolBodies, async (c) => {
182
+ // Closing a session's own connection needs no live lease: the gate reopens it on demand.
183
+ app.post(MCP_CLOSE_PATH, run({ live: false, orCore: true }), toolBodies, async (c) => {
89
184
  const request = await read(c, McpServerBodySchema);
90
185
  if (!request.ok)
91
186
  return request.response;
92
- await mcp.close(request.body.server);
187
+ const server = serverOf(c, request.body.server);
188
+ if (!server.ok)
189
+ return server.response;
190
+ await mcp.close(server.server);
93
191
  return c.body(null, 204);
94
192
  });
95
193
  const toolCalls = options.toolCalls;
96
- app.post(`${TOOL_CALLS_PATH}/:key/cancel`, authorized, (c) => {
194
+ app.post(`${TOOL_CALLS_PATH}/:key/cancel`, run({ live: false, orCore: true }), (c) => {
97
195
  if (tenantOf(c) === false)
98
- return c.json(invalid(`The ${TENANT_HEADER} header must name a Tenant`), 400);
99
- toolCalls?.cancel(c.req.param("key"));
196
+ return badTenant(c);
197
+ const owner = ownerOf(c);
198
+ if (toolCalls && !toolCalls.cancel(c.req.param("key"), owner?.sessionId))
199
+ return c.json(gateError("gate_forbidden", "The call is another session's"), 403);
100
200
  return c.body(null, 204);
101
201
  });
102
- app.post(TOOL_CALLS_PATH, authorized, toolBodies, async (c) => {
202
+ app.post(TOOL_CALLS_PATH, scoped, toolBodies, async (c) => {
103
203
  const request = await read(c, ToolCallBodySchema);
104
204
  if (!request.ok)
105
205
  return request.response;
206
+ const server = serverOf(c, request.body.server);
207
+ if (!server.ok)
208
+ return server.response;
209
+ const body = { ...request.body, server: server.server };
106
210
  const signal = c.req.raw.signal;
107
211
  const key = c.req.header("idempotency-key");
108
212
  try {
109
- // Keyed: the call runs under its own signal, once, and outlives this request (G3).
213
+ // Keyed: the call runs under its own signal, once, and outlives this request (G3). The
214
+ // hash covers the session, so a re-send joins only its own session's call.
110
215
  const answer = key && toolCalls
111
- ? await untilAborted(toolCalls.run(request.tenantId, key, createHash("sha256").update(canonical(request.body)).digest("hex"), request.body), signal)
112
- : await mcp.call(request.tenantId, request.body, signal);
216
+ ? await untilAborted(toolCalls.run(request.tenantId, key, createHash("sha256").update(canonical(body)).digest("hex"), body, ownerOf(c)), signal)
217
+ : await mcp.call(request.tenantId, body, signal);
113
218
  if (signal.aborted)
114
219
  return new Response(null, { status: 499 });
115
220
  return c.json(answer);
116
221
  }
117
222
  catch (error) {
118
- if (error instanceof InflightConflict) {
119
- const body = { error: { code: "gate_conflict", message: error.message } };
120
- return c.json(body, 409);
121
- }
223
+ const refused = refusedJoin(c, error);
224
+ if (refused)
225
+ return refused;
122
226
  if (signal.aborted)
123
227
  return new Response(null, { status: 499 });
124
228
  throw error;
@@ -127,7 +231,7 @@ export function createGatesApp(options) {
127
231
  }
128
232
  const policy = options.delivery;
129
233
  if (policy) {
130
- app.post(DELIVERIES_PATH, authorized, toolBodies, async (c) => {
234
+ app.post(DELIVERIES_PATH, core, toolBodies, async (c) => {
131
235
  const request = await read(c, DeliveryBodySchema);
132
236
  if (!request.ok)
133
237
  return request.response;
@@ -158,22 +262,22 @@ export function createGatesApp(options) {
158
262
  return c.json(answer);
159
263
  });
160
264
  }
161
- app.post(`${MODEL_CALLS_PATH}/:key/cancel`, authorized, (c) => {
265
+ app.post(`${MODEL_CALLS_PATH}/:key/cancel`, run({ live: false }), (c) => {
162
266
  // Optional, as on a call: the gate serves one Tenant, so the key alone names the call.
163
- const tenantId = c.req.header(TENANT_HEADER);
164
- if (tenantId !== undefined && !isTenantId(tenantId))
165
- return c.json(invalid(`The ${TENANT_HEADER} header must name a Tenant`), 400);
166
- options.inflight.cancel(c.req.param("key"));
267
+ if (tenantOf(c) === false)
268
+ return badTenant(c);
269
+ if (!options.inflight.cancel(c.req.param("key"), c.get("run").sessionId))
270
+ return c.json(gateError("gate_forbidden", "The call is another session's"), 403);
167
271
  return c.body(null, 204);
168
272
  });
169
- app.post(MODEL_CALLS_PATH, authorized, bodyLimit({
273
+ app.post(MODEL_CALLS_PATH, run({ live: true }), bodyLimit({
170
274
  maxSize,
171
275
  onError: (c) => c.json(invalid(`A model call body may be at most ${maxSize} bytes`), 400),
172
276
  }), async (c) => {
173
277
  // Optional: the gate serves its database's one Tenant, and refuses a call naming another.
174
- const tenantId = c.req.header(TENANT_HEADER);
175
- if (tenantId !== undefined && !isTenantId(tenantId))
176
- return c.json(invalid(`The ${TENANT_HEADER} header must name a Tenant`), 400);
278
+ const tenantId = tenantOf(c);
279
+ if (tenantId === false)
280
+ return badTenant(c);
177
281
  let raw;
178
282
  try {
179
283
  raw = await c.req.json();
@@ -184,9 +288,14 @@ export function createGatesApp(options) {
184
288
  const parsed = ModelCallBodySchema.safeParse(raw);
185
289
  if (!parsed.success)
186
290
  return c.json(invalid(`Invalid model call: ${parsed.error.issues[0]?.message ?? "malformed"}`), 400);
291
+ // Session, turn and agent come from the run token, never the body: they scope the
292
+ // ledger rows and the turn and agent caps.
293
+ const claims = c.get("run");
294
+ const scope = { sessionId: claims.sessionId, turnId: claims.turnId, agentId: claims.agentId };
187
295
  const { call, ...ids } = parsed.data;
188
296
  const request = {
189
297
  ...(tenantId === undefined ? {} : { tenantId }),
298
+ ...scope,
190
299
  ...ids,
191
300
  call: call,
192
301
  };
@@ -196,19 +305,19 @@ export function createGatesApp(options) {
196
305
  let outcome;
197
306
  if (key) {
198
307
  // Keyed: the call runs under its own signal and outlives this request (P1.2). The
199
- // gate serves one Tenant, so the effect id alone is the key.
200
- const hash = createHash("sha256").update(canonical(parsed.data)).digest("hex");
201
- outcome = await untilAborted(options.inflight.run(key, hash, (own) => options.modelGate.call(request, own)), signal);
308
+ // gate serves one Tenant, so the effect id alone is the key. The hash covers the
309
+ // token's scope, so the new owner's re-send after a takeover hashes the same.
310
+ const hash = createHash("sha256").update(canonical({ ...scope, ...parsed.data })).digest("hex");
311
+ outcome = await untilAborted(options.inflight.run(key, hash, (own) => options.modelGate.call(request, own), ownerOf(c)), signal);
202
312
  }
203
313
  else
204
314
  outcome = await options.modelGate.call(request, signal);
205
315
  return c.json({ outcome });
206
316
  }
207
317
  catch (error) {
208
- if (error instanceof InflightConflict) {
209
- const body = { error: { code: "gate_conflict", message: error.message } };
210
- return c.json(body, 409);
211
- }
318
+ const refused = refusedJoin(c, error);
319
+ if (refused)
320
+ return refused;
212
321
  // The caller went away (or cancelled): nobody reads the answer.
213
322
  if (signal.aborted)
214
323
  return new Response(null, { status: 499 });
@@ -8,6 +8,8 @@ import { jsonResponse, rejectionOf } from "./respond.js";
8
8
  import { accessRoutes } from "./routes/access.js";
9
9
  import { endpointRoutes } from "./routes/endpoints.js";
10
10
  import { actionRoutes } from "./routes/actions.js";
11
+ import { sandboxRoutes } from "./routes/sandboxes.js";
12
+ import { artifactRoutes } from "./routes/artifacts.js";
11
13
  import { sessionRoutes } from "./routes/sessions.js";
12
14
  import { tenantRoutes } from "./routes/tenant.js";
13
15
  import { vaultRoutes } from "./routes/vaults.js";
@@ -38,6 +40,8 @@ function build() {
38
40
  actionRoutes(api);
39
41
  endpointRoutes(api);
40
42
  sessionRoutes(api);
43
+ sandboxRoutes(api);
44
+ artifactRoutes(api);
41
45
  tenantRoutes(api);
42
46
  vaultRoutes(api);
43
47
  accessRoutes(api);
@@ -27,6 +27,16 @@ export interface RouteAccess {
27
27
  * served too. A credential that is sent is still checked, so a wrong one stays the opaque 404.
28
28
  */
29
29
  readonly anonymous?: boolean;
30
+ /**
31
+ * The body is the bytes of a file, of any media type (an artifact upload): the Host lets it
32
+ * through without `application/json`, and the handler streams it.
33
+ */
34
+ readonly bytes?: boolean;
35
+ /**
36
+ * Served without `Nylorun-Protocol` (a capability link, opened by a browser or an `<img>`):
37
+ * the Host checks the header only when it is sent.
38
+ */
39
+ readonly unversioned?: boolean;
30
40
  }
31
41
  /** The route's path segments, decoded, as the Tenant's checks read them. */
32
42
  export declare function pathSegments(incoming: IncomingMessage): string[];
@@ -68,8 +68,9 @@ function authenticated(access) {
68
68
  export function tenantRoute(api, access, route, handler) {
69
69
  const takes = (credential) => access.credentials.includes(credential);
70
70
  const schemes = [...new Set(access.credentials.map((credential) => SCHEMES[credential]))];
71
+ const protocol = z.string().meta({ description: `The protocol version, \`${PROTOCOL_VERSION}\`` });
71
72
  const headers = z.object({
72
- "Nylorun-Protocol": z.string().meta({ description: `The protocol version, \`${PROTOCOL_VERSION}\`` }),
73
+ "Nylorun-Protocol": access.unversioned ? protocol.optional() : protocol,
73
74
  ...(takes("subject")
74
75
  ? {
75
76
  "Nylorun-Subject": z
@@ -0,0 +1,11 @@
1
+ import type { OpenAPIHono } from "@hono/zod-openapi";
2
+ import type { TenantEnv } from "../app.js";
3
+ /**
4
+ * One `bytes=` range of a file of `size` bytes: what to send, `"unsatisfiable"`, or undefined
5
+ * to send the whole file (no header, several ranges, or one the Runtime ignores).
6
+ */
7
+ export declare function parseRange(header: string | undefined, size: number): {
8
+ start: number;
9
+ end: number;
10
+ } | "unsatisfiable" | undefined;
11
+ export declare function artifactRoutes(api: OpenAPIHono<TenantEnv>): void;