@nylorun/runtime 0.16.0-beta → 0.19.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 (256) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +105 -63
  3. package/dist/adapters/execution/restate.d.ts +1 -3
  4. package/dist/adapters/execution/restate.js +4 -31
  5. package/dist/adapters/replication/pgoutput.js +1 -1
  6. package/dist/adapters/sandbox/local.js +1 -1
  7. package/dist/adapters/sandbox/virtual.js +1 -1
  8. package/dist/api/ag-ui/endpoint.js +1 -2
  9. package/dist/api/ag-ui/routes.d.ts +1 -1
  10. package/dist/api/ag-ui/routes.js +11 -13
  11. package/dist/api/ag-ui/translate.d.ts +1 -3
  12. package/dist/api/ag-ui/translate.js +2 -17
  13. package/dist/api/components.d.ts +4783 -2588
  14. package/dist/api/components.js +19 -21
  15. package/dist/api/gate/routes.d.ts +7 -8
  16. package/dist/api/gate/routes.js +58 -52
  17. package/dist/api/http/app.d.ts +6 -0
  18. package/dist/api/http/app.js +20 -14
  19. package/dist/api/http/define.d.ts +22 -13
  20. package/dist/api/http/define.js +43 -39
  21. package/dist/api/http/routes/access.d.ts +3 -19
  22. package/dist/api/http/routes/access.js +25 -184
  23. package/dist/api/http/routes/artifacts.js +3 -7
  24. package/dist/api/http/routes/files.d.ts +8 -0
  25. package/dist/api/http/routes/files.js +77 -0
  26. package/dist/api/http/routes/keys.d.ts +10 -0
  27. package/dist/api/http/routes/keys.js +64 -0
  28. package/dist/api/http/routes/me.d.ts +13 -0
  29. package/dist/api/http/routes/me.js +48 -0
  30. package/dist/api/http/routes/reads.d.ts +66 -0
  31. package/dist/api/http/routes/reads.js +127 -0
  32. package/dist/api/http/routes/sandboxes.d.ts +2 -2
  33. package/dist/api/http/routes/sandboxes.js +8 -7
  34. package/dist/api/http/routes/sessions.js +31 -20
  35. package/dist/api/http/routes/tenant.js +15 -18
  36. package/dist/api/http/routes/vaults.d.ts +13 -3
  37. package/dist/api/http/routes/vaults.js +136 -64
  38. package/dist/api/openapi.d.ts +10 -11
  39. package/dist/api/openapi.js +292 -68
  40. package/dist/artifacts/links.js +6 -6
  41. package/dist/artifacts/service.js +1 -1
  42. package/dist/core/flow-host.d.ts +44 -23
  43. package/dist/core/flow-host.js +88 -106
  44. package/dist/core/provider.d.ts +4 -1
  45. package/dist/core/provider.js +6 -1
  46. package/dist/core/sandbox-routes.d.ts +0 -9
  47. package/dist/core/sandbox-routes.js +2 -21
  48. package/dist/execution/memory.d.ts +4 -5
  49. package/dist/execution/memory.js +11 -25
  50. package/dist/execution/types.d.ts +3 -33
  51. package/dist/execution/types.js +2 -12
  52. package/dist/gates/http-tool.d.ts +86 -0
  53. package/dist/gates/http-tool.js +197 -0
  54. package/dist/gates/mcp-handler.d.ts +3 -0
  55. package/dist/gates/mcp-handler.js +6 -4
  56. package/dist/gates/meter.d.ts +2 -2
  57. package/dist/gates/meter.js +6 -0
  58. package/dist/gates/tenant-vaults.d.ts +19 -14
  59. package/dist/gates/tenant-vaults.js +17 -12
  60. package/dist/gates/tool-calls.d.ts +7 -15
  61. package/dist/gates/tool-calls.js +4 -4
  62. package/dist/gates/tool-client.d.ts +1 -1
  63. package/dist/gates/tool-client.js +23 -47
  64. package/dist/gates/tool-contract.d.ts +27 -39
  65. package/dist/gates/tool-contract.js +30 -21
  66. package/dist/gates/tool-gate.d.ts +25 -24
  67. package/dist/gates/tool-gate.js +11 -7
  68. package/dist/harness/calls.d.ts +10 -4
  69. package/dist/harness/calls.js +15 -6
  70. package/dist/harness/executors.d.ts +1 -1
  71. package/dist/harness/executors.js +17 -12
  72. package/dist/harness/main.d.ts +2 -2
  73. package/dist/harness/main.js +5 -13
  74. package/dist/harness/service.d.ts +4 -5
  75. package/dist/harness/service.js +9 -2
  76. package/dist/harness-api/in-process.js +2 -2
  77. package/dist/harness-api/record.d.ts +6 -3
  78. package/dist/harness-api/record.js +65 -72
  79. package/dist/harness-api/server.d.ts +1 -9
  80. package/dist/harness-api/server.js +19 -17
  81. package/dist/harness-api/start.js +1 -5
  82. package/dist/harness-api/workspace.js +2 -3
  83. package/dist/harness-api/ws-server.js +1 -1
  84. package/dist/host/app.d.ts +2 -10
  85. package/dist/host/app.js +24 -44
  86. package/dist/host/config-for.d.ts +5 -5
  87. package/dist/host/config-for.js +2 -3
  88. package/dist/host/config.d.ts +0 -11
  89. package/dist/host/create-host.d.ts +3 -40
  90. package/dist/host/create-host.js +18 -91
  91. package/dist/host/environment.d.ts +0 -3
  92. package/dist/host/environment.js +0 -9
  93. package/dist/host/gates.d.ts +4 -1
  94. package/dist/host/gates.js +9 -3
  95. package/dist/host/http.d.ts +1 -2
  96. package/dist/host/http.js +1 -7
  97. package/dist/host/index.d.ts +2 -2
  98. package/dist/host/index.js +2 -2
  99. package/dist/host/main.js +55 -40
  100. package/dist/host/operate.d.ts +29 -0
  101. package/dist/host/operate.js +223 -0
  102. package/dist/host/stack-config.d.ts +23 -20
  103. package/dist/host/stack-config.js +28 -44
  104. package/dist/index.d.ts +4 -1
  105. package/dist/index.js +3 -1
  106. package/dist/infra/workers.d.ts +1 -1
  107. package/dist/keys/client.js +9 -1
  108. package/dist/keys/keys.d.ts +12 -2
  109. package/dist/keys/keys.js +10 -2
  110. package/dist/management-openapi.json +6491 -0
  111. package/dist/mcp/connect.d.ts +8 -4
  112. package/dist/mcp/connect.js +8 -27
  113. package/dist/mcp/pool.d.ts +6 -9
  114. package/dist/mcp/pool.js +8 -18
  115. package/dist/mcp/snapshot.d.ts +8 -2
  116. package/dist/mcp/snapshot.js +29 -2
  117. package/dist/media.d.ts +1 -1
  118. package/dist/media.js +1 -1
  119. package/dist/model/pi-model.js +3 -0
  120. package/dist/openapi.json +6228 -15615
  121. package/dist/reads/access.d.ts +4 -0
  122. package/dist/reads/access.js +12 -0
  123. package/dist/reads/cursor.d.ts +11 -0
  124. package/dist/reads/cursor.js +80 -0
  125. package/dist/reads/types.d.ts +25 -0
  126. package/dist/reads/types.js +1 -0
  127. package/dist/sandbox/manager.d.ts +16 -0
  128. package/dist/sandbox/manager.js +40 -0
  129. package/dist/sandbox/session-sandbox.js +13 -3
  130. package/dist/sandbox/skills.d.ts +13 -0
  131. package/dist/sandbox/skills.js +46 -0
  132. package/dist/sandbox/types.d.ts +2 -2
  133. package/dist/store/ownership.d.ts +9 -0
  134. package/dist/store/ownership.js +13 -0
  135. package/dist/store/postgres/connect.d.ts +2 -0
  136. package/dist/store/postgres/connect.js +29 -0
  137. package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
  138. package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
  139. package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
  140. package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
  141. package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
  142. package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
  143. package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
  144. package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
  145. package/dist/store/postgres/reads.d.ts +3 -0
  146. package/dist/store/postgres/reads.js +272 -0
  147. package/dist/store/postgres/schema.d.ts +423 -594
  148. package/dist/store/postgres/schema.js +84 -101
  149. package/dist/store/postgres/store.js +96 -248
  150. package/dist/store/postgres/tenant.d.ts +34 -2
  151. package/dist/store/postgres/tenant.js +16 -32
  152. package/dist/store/types.d.ts +44 -119
  153. package/dist/store/types.js +0 -1
  154. package/dist/streams/control.d.ts +0 -7
  155. package/dist/streams/control.js +0 -13
  156. package/dist/streams/types.d.ts +2 -20
  157. package/dist/streams/types.js +1 -1
  158. package/dist/tenant/advance.d.ts +3 -3
  159. package/dist/tenant/advance.js +10 -7
  160. package/dist/tenant/auth.d.ts +24 -15
  161. package/dist/tenant/auth.js +44 -55
  162. package/dist/tenant/cause.d.ts +3 -3
  163. package/dist/tenant/cause.js +1 -1
  164. package/dist/tenant/commands.d.ts +2 -18
  165. package/dist/tenant/commands.js +38 -179
  166. package/dist/tenant/context.d.ts +33 -45
  167. package/dist/tenant/definition-files.d.ts +24 -0
  168. package/dist/tenant/definition-files.js +103 -0
  169. package/dist/tenant/effects.d.ts +20 -18
  170. package/dist/tenant/effects.js +59 -133
  171. package/dist/tenant/ephemeral.d.ts +21 -8
  172. package/dist/tenant/ephemeral.js +11 -12
  173. package/dist/tenant/host-token.js +5 -5
  174. package/dist/tenant/identity-file.d.ts +75 -0
  175. package/dist/tenant/identity-file.js +202 -0
  176. package/dist/tenant/issuers.d.ts +48 -0
  177. package/dist/tenant/issuers.js +311 -0
  178. package/dist/tenant/jwt.js +3 -3
  179. package/dist/tenant/module.d.ts +1 -4
  180. package/dist/tenant/module.js +3 -12
  181. package/dist/tenant/operator-keys.d.ts +44 -0
  182. package/dist/tenant/operator-keys.js +87 -0
  183. package/dist/tenant/outbound.d.ts +40 -1
  184. package/dist/tenant/outbound.js +167 -18
  185. package/dist/tenant/paths.d.ts +1 -1
  186. package/dist/tenant/paths.js +1 -3
  187. package/dist/tenant/principals.d.ts +13 -13
  188. package/dist/tenant/principals.js +35 -36
  189. package/dist/tenant/run-token.js +6 -6
  190. package/dist/tenant/runtime.d.ts +5 -3
  191. package/dist/tenant/runtime.js +29 -15
  192. package/dist/tenant/sandboxes.d.ts +9 -6
  193. package/dist/tenant/sandboxes.js +16 -5
  194. package/dist/tenant/scheduler.d.ts +1 -5
  195. package/dist/tenant/scheduler.js +2 -5
  196. package/dist/tenant/session-streams.d.ts +8 -11
  197. package/dist/tenant/session-streams.js +48 -32
  198. package/dist/tenant/session.d.ts +8 -41
  199. package/dist/tenant/session.js +0 -37
  200. package/dist/tenant/sessions.d.ts +2 -2
  201. package/dist/tenant/sessions.js +17 -9
  202. package/dist/tenant/signing-keys.d.ts +3 -2
  203. package/dist/tenant/signing-keys.js +5 -5
  204. package/dist/tenant/skill-tool.d.ts +20 -0
  205. package/dist/tenant/skill-tool.js +72 -0
  206. package/dist/tenant/slim.d.ts +1 -1
  207. package/dist/tenant/slim.js +1 -1
  208. package/dist/tenant/status.js +7 -12
  209. package/dist/tenant/store-pg.d.ts +15 -0
  210. package/dist/tenant/store-pg.js +7 -3
  211. package/dist/tenant/streams.d.ts +3 -11
  212. package/dist/tenant/streams.js +2 -29
  213. package/dist/tenant/sweep.js +0 -5
  214. package/dist/tenant/transcript.d.ts +2 -2
  215. package/dist/tenant/transcript.js +2 -2
  216. package/dist/tenant/types.d.ts +33 -19
  217. package/dist/tenant/worker.d.ts +1 -3
  218. package/dist/tenant/worker.js +3 -8
  219. package/dist/vault/crypto.d.ts +7 -0
  220. package/dist/vault/crypto.js +16 -0
  221. package/dist/vault/oauth.d.ts +55 -0
  222. package/dist/vault/oauth.js +186 -0
  223. package/dist/vault/service.d.ts +54 -7
  224. package/dist/vault/service.js +261 -17
  225. package/dist/vault/sources.d.ts +60 -0
  226. package/dist/vault/sources.js +196 -0
  227. package/dist/version.d.ts +1 -1
  228. package/dist/version.js +1 -1
  229. package/package.json +5 -4
  230. package/dist/admin-openapi.json +0 -604
  231. package/dist/api/http/routes/actions.d.ts +0 -8
  232. package/dist/api/http/routes/actions.js +0 -76
  233. package/dist/api/http/routes/endpoints.d.ts +0 -7
  234. package/dist/api/http/routes/endpoints.js +0 -100
  235. package/dist/host/admin-api.d.ts +0 -23
  236. package/dist/host/admin-api.js +0 -74
  237. package/dist/host/cors.d.ts +0 -27
  238. package/dist/host/cors.js +0 -64
  239. package/dist/mcp/stdio.d.ts +0 -17
  240. package/dist/mcp/stdio.js +0 -82
  241. package/dist/tenant/access-policy.d.ts +0 -30
  242. package/dist/tenant/access-policy.js +0 -52
  243. package/dist/tenant/actions.d.ts +0 -14
  244. package/dist/tenant/actions.js +0 -29
  245. package/dist/tenant/browser.d.ts +0 -15
  246. package/dist/tenant/browser.js +0 -39
  247. package/dist/tenant/delivery-token.d.ts +0 -41
  248. package/dist/tenant/delivery-token.js +0 -104
  249. package/dist/tenant/delivery.d.ts +0 -62
  250. package/dist/tenant/delivery.js +0 -481
  251. package/dist/tenant/endpoints.d.ts +0 -67
  252. package/dist/tenant/endpoints.js +0 -90
  253. package/dist/tenant/subject-limits.d.ts +0 -16
  254. package/dist/tenant/subject-limits.js +0 -45
  255. package/dist/tenant/tokens.d.ts +0 -14
  256. package/dist/tenant/tokens.js +0 -187
@@ -1,67 +0,0 @@
1
- /**
2
- * Action endpoints (design: Action endpoints §4.2): where the Runtime delivers each agent's
3
- * Actions, registered with the application key.
4
- */
5
- import { type Endpoint, type PutEndpointsRequest } from "@nylorun/core/contracts";
6
- import type { EndpointRow } from "../store/types.js";
7
- import type { TenantContext } from "./context.js";
8
- /** An endpoint as the Tenant API answers it. */
9
- export declare function endpointView(row: EndpointRow): Endpoint;
10
- /** `GET /v1/endpoints`. */
11
- export declare function listEndpoints(ctx: TenantContext): Promise<{
12
- endpoints: {
13
- agentId: string;
14
- url: string;
15
- implementationVersion: string;
16
- manifestHash?: string | undefined;
17
- timeoutMs: number;
18
- maxConcurrent: number;
19
- health: {
20
- lastDeliveryAt?: string | undefined;
21
- lastSuccessAt?: string | undefined;
22
- lastError?: {
23
- code: string;
24
- message: string;
25
- } | undefined;
26
- consecutiveFailures: number;
27
- served?: {
28
- implementationVersion: string;
29
- manifestHash?: string | undefined;
30
- } | undefined;
31
- };
32
- updatedAt: string;
33
- }[];
34
- }>;
35
- /**
36
- * `PUT /v1/endpoints`: upsert the batch in one transaction, then send the Actions already
37
- * waiting for these agents. Answers every registered endpoint of the batch.
38
- */
39
- export declare function putEndpoints(ctx: TenantContext, principalId: string, body: PutEndpointsRequest): Promise<{
40
- endpoints: {
41
- agentId: string;
42
- url: string;
43
- implementationVersion: string;
44
- manifestHash?: string | undefined;
45
- timeoutMs: number;
46
- maxConcurrent: number;
47
- health: {
48
- lastDeliveryAt?: string | undefined;
49
- lastSuccessAt?: string | undefined;
50
- lastError?: {
51
- code: string;
52
- message: string;
53
- } | undefined;
54
- consecutiveFailures: number;
55
- served?: {
56
- implementationVersion: string;
57
- manifestHash?: string | undefined;
58
- } | undefined;
59
- };
60
- updatedAt: string;
61
- }[];
62
- }>;
63
- /** `DELETE /v1/endpoints/:agentId`. The agent's pending Actions wait for a new endpoint. */
64
- export declare function deleteEndpoint(ctx: TenantContext, agentId: string): Promise<{
65
- agentId: string;
66
- deleted: true;
67
- }>;
@@ -1,90 +0,0 @@
1
- /**
2
- * Action endpoints (design: Action endpoints §4.2): where the Runtime delivers each agent's
3
- * Actions, registered with the application key.
4
- */
5
- import { ENDPOINT_MAX_CONCURRENT_DEFAULT, ENDPOINT_TIMEOUT_DEFAULT_MS, } from "@nylorun/core/contracts";
6
- import { fail } from "./http.js";
7
- /** An endpoint as the Tenant API answers it. */
8
- export function endpointView(row) {
9
- const served = row.servedImplementationVersion === undefined
10
- ? undefined
11
- : {
12
- implementationVersion: row.servedImplementationVersion,
13
- ...(row.servedManifestHash === undefined
14
- ? {}
15
- : { manifestHash: row.servedManifestHash }),
16
- };
17
- return {
18
- agentId: row.agentId,
19
- url: row.url,
20
- implementationVersion: row.implementationVersion,
21
- ...(row.manifestHash === undefined ? {} : { manifestHash: row.manifestHash }),
22
- timeoutMs: row.timeoutMs,
23
- maxConcurrent: row.maxConcurrent,
24
- health: {
25
- ...(row.lastDeliveryAt === undefined ? {} : { lastDeliveryAt: row.lastDeliveryAt }),
26
- ...(row.lastSuccessAt === undefined ? {} : { lastSuccessAt: row.lastSuccessAt }),
27
- ...(row.lastErrorCode === undefined
28
- ? {}
29
- : {
30
- lastError: {
31
- code: row.lastErrorCode,
32
- message: row.lastErrorMessage ?? "",
33
- },
34
- }),
35
- consecutiveFailures: row.consecutiveFailures,
36
- ...(served ? { served } : {}),
37
- },
38
- updatedAt: row.updatedAt,
39
- };
40
- }
41
- /** `GET /v1/endpoints`. */
42
- export async function listEndpoints(ctx) {
43
- const rows = await ctx.store.tx((t) => t.listEndpoints());
44
- return { endpoints: rows.map(endpointView) };
45
- }
46
- /**
47
- * `PUT /v1/endpoints`: upsert the batch in one transaction, then send the Actions already
48
- * waiting for these agents. Answers every registered endpoint of the batch.
49
- */
50
- export async function putEndpoints(ctx, principalId, body) {
51
- const updatedAt = new Date().toISOString();
52
- const rows = await ctx.store.tx(async (t) => {
53
- const saved = [];
54
- for (const endpoint of body.endpoints) {
55
- await t.putEndpoint({
56
- agentId: endpoint.agentId,
57
- url: endpoint.url,
58
- implementationVersion: endpoint.implementationVersion,
59
- ...(endpoint.manifestHash === undefined
60
- ? {}
61
- : { manifestHash: endpoint.manifestHash }),
62
- timeoutMs: endpoint.timeoutMs ?? ENDPOINT_TIMEOUT_DEFAULT_MS,
63
- maxConcurrent: endpoint.maxConcurrent ?? ENDPOINT_MAX_CONCURRENT_DEFAULT,
64
- principalId,
65
- updatedAt,
66
- });
67
- saved.push((await t.getEndpoint(endpoint.agentId)));
68
- }
69
- return saved;
70
- });
71
- // Actions already waiting for these agents go to their endpoint now.
72
- const waiting = await ctx.store.tx(async (t) => {
73
- const actions = [];
74
- for (const endpoint of body.endpoints)
75
- actions.push(...(await t.pendingActions(endpoint.agentId)));
76
- return actions;
77
- });
78
- for (const action of waiting)
79
- await ctx.deliver(action.actionId);
80
- return { endpoints: rows.map(endpointView) };
81
- }
82
- /** `DELETE /v1/endpoints/:agentId`. The agent's pending Actions wait for a new endpoint. */
83
- export async function deleteEndpoint(ctx, agentId) {
84
- await ctx.store.tx(async (t) => {
85
- if (!(await t.getEndpoint(agentId)))
86
- fail(404, "Endpoint not found");
87
- await t.deleteEndpoint(agentId);
88
- });
89
- return { agentId, deleted: true };
90
- }
@@ -1,16 +0,0 @@
1
- /**
2
- * Subject limits (Host feature `subject-tokens`): a subject token's role may cap how many turns
3
- * its subject starts per hour and how many run at once. Every turn spends the Tenant's model
4
- * budget, so these ship with the first credential that reaches the Runtime without an app
5
- * server in between.
6
- *
7
- * Turns per hour is a token bucket per subject (capacity `turnsPerHour`, refilled
8
- * continuously): no hourly rows and no burst at a window boundary. Concurrent turns counts the
9
- * subject's sessions with work in flight; `paused` is not counted, so an unanswered approval
10
- * never locks a person out. Checked inside the command's transaction, after the session row
11
- * lock, so one subject's messages serialize on the bucket row.
12
- */
13
- import type { RoleLimits } from "@nylorun/core/contracts";
14
- import type { Tx } from "../store/types.js";
15
- /** Charges one turn to `subject`, or answers `429 limit_exceeded`. */
16
- export declare function chargeTurn(t: Tx, subject: string, limits: RoleLimits, now?: number): Promise<void>;
@@ -1,45 +0,0 @@
1
- import { fail } from "./http.js";
2
- /** Session statuses that hold a turn in flight. */
3
- const ACTIVE_STATUSES = ["runnable", "running", "waiting"];
4
- const CONCURRENT_RETRY_SECONDS = 5;
5
- /** Floating-point slack when a refill lands exactly on a whole turn. */
6
- const EPSILON = 1e-9;
7
- /** Charges one turn to `subject`, or answers `429 limit_exceeded`. */
8
- export async function chargeTurn(t, subject, limits, now = Date.now()) {
9
- if (limits.concurrentTurns !== undefined) {
10
- const active = await t.countOwnerSessions(subject, ACTIVE_STATUSES);
11
- if (active >= limits.concurrentTurns)
12
- fail(429, `At most ${limits.concurrentTurns} turns may run at once`, {
13
- code: "limit_exceeded",
14
- details: {
15
- limit: "concurrentTurns",
16
- retryAfterSeconds: CONCURRENT_RETRY_SECONDS,
17
- },
18
- }, { "retry-after": String(CONCURRENT_RETRY_SECONDS) });
19
- }
20
- const capacity = limits.turnsPerHour;
21
- if (capacity === undefined)
22
- return;
23
- const perMs = capacity / 3_600_000;
24
- const usage = await t.lockSubjectUsage({
25
- subject,
26
- turnTokens: capacity,
27
- refilledAt: new Date(now).toISOString(),
28
- });
29
- const elapsed = Math.max(0, now - Date.parse(usage.refilledAt));
30
- const tokens = Math.min(capacity, usage.turnTokens + elapsed * perMs);
31
- if (tokens < 1 - EPSILON) {
32
- // Rounded before the ceiling so floating-point dust never adds a second.
33
- const wait = Math.round(((1 - tokens) / perMs / 1000) * 1000) / 1000;
34
- const retryAfterSeconds = Math.max(1, Math.ceil(wait));
35
- fail(429, `At most ${capacity} turns per hour`, {
36
- code: "limit_exceeded",
37
- details: { limit: "turnsPerHour", retryAfterSeconds },
38
- }, { "retry-after": String(retryAfterSeconds) });
39
- }
40
- await t.putSubjectUsage({
41
- subject,
42
- turnTokens: Math.max(0, tokens - 1),
43
- refilledAt: new Date(now).toISOString(),
44
- });
45
- }
@@ -1,14 +0,0 @@
1
- import { type CreateTokenRequest, type CreateTokenResponse } from "@nylorun/core/contracts";
2
- import type { AuthScope, TenantContext } from "./context.js";
3
- export { looksLikeToken } from "./jwt.js";
4
- export declare function mintToken(ctx: TenantContext, body: CreateTokenRequest): Promise<CreateTokenResponse>;
5
- export type TokenScopeAuth = Extract<AuthScope, {
6
- kind: "token";
7
- }>;
8
- /**
9
- * Verifies a subject token. Returns the request's scope, answers `401 token_expired` for
10
- * the cases a new token fixes, and the opaque 404 for everything else.
11
- */
12
- export declare function verifySubjectToken(ctx: TenantContext, raw: string): Promise<TokenScopeAuth>;
13
- /** True when a token caller may reach `agentId`. Other callers always may. */
14
- export declare function mayUseAgent(scope: AuthScope, agentId: string): boolean;
@@ -1,187 +0,0 @@
1
- /**
2
- * Subject tokens (Host feature `subject-tokens`): ES256 JWTs minted by `POST /v1/tokens` with
3
- * an application key, for one subject and one role of the access policy.
4
- *
5
- * Verification order matters: everything a forger controls is checked before any signature
6
- * work, and nothing but the signature-verified cases reveals more than the opaque 404.
7
- * Expired tokens, a stale revocation epoch, a revoked key or a removed role answer
8
- * `401 token_expired`, so clients always react the same way: get a new token.
9
- */
10
- import { randomUUID } from "node:crypto";
11
- import { errors, jwtVerify } from "jose";
12
- import { isSandboxGrant, isSubject, SUBJECT_TOKEN_AUDIENCE, SUBJECT_TOKEN_TYPE, subjectTokenIssuer, TOKEN_SANDBOX_GRANTS_MAX, TOKEN_SCOPES, TOKEN_TTL_MIN_SECONDS, } from "@nylorun/core/contracts";
13
- import { agentAllowed, readPolicy, resolveRole } from "./access-policy.js";
14
- import { fail, failOpaque, HttpError } from "./http.js";
15
- import { CLOCK_TOLERANCE_SECONDS, tokenKeyId } from "./jwt.js";
16
- /** Allowed spread between `iat` and `exp` beyond the policy's longest lifetime. */
17
- const LIFETIME_SLACK_SECONDS = 60;
18
- export { looksLikeToken } from "./jwt.js";
19
- /** The 401 every "get a new token" case answers. The reason is logged, never returned. */
20
- function expired(ctx, reason, jti) {
21
- ctx.config.logger.warn("subject token refused", {
22
- reason,
23
- ...(jti ? { jti } : {}),
24
- });
25
- throw new HttpError(401, "The subject token has expired or was revoked", { code: "token_expired" }, { "www-authenticate": 'Bearer error="invalid_token"' });
26
- }
27
- function rejected(ctx, reason) {
28
- ctx.config.logger.warn("credential rejected", { reason });
29
- return failOpaque();
30
- }
31
- export async function mintToken(ctx, body) {
32
- const tenantId = ctx.config.tenantId;
33
- const { access, epoch, ttl } = await ctx.store.tx(async (t) => {
34
- const policy = await readPolicy(t);
35
- const role = Object.hasOwn(policy.roles, body.role)
36
- ? policy.roles[body.role]
37
- : undefined;
38
- if (!role)
39
- return fail(400, `Role ${body.role} is not in the access policy`, {
40
- code: "invalid_request",
41
- });
42
- const scopes = body.scopes ?? role.scopes;
43
- const outside = scopes.filter((scope) => !role.scopes.includes(scope));
44
- if (outside.length > 0)
45
- fail(400, `Role ${body.role} does not allow ${outside.join(", ")}`, {
46
- code: "invalid_request",
47
- });
48
- if (body.agents && role.agents !== "*") {
49
- const allowed = new Set(role.agents);
50
- const extra = body.agents.filter((agent) => !allowed.has(agent));
51
- if (extra.length > 0)
52
- fail(400, `Role ${body.role} does not allow agent ${extra.join(", ")}`, {
53
- code: "invalid_request",
54
- });
55
- }
56
- const maxTtl = policy.tokens.maxTtlSeconds;
57
- const ttl = Math.min(body.ttlSeconds ?? maxTtl, maxTtl);
58
- if (ttl < TOKEN_TTL_MIN_SECONDS)
59
- fail(400, `ttlSeconds must be at least ${TOKEN_TTL_MIN_SECONDS}`);
60
- return {
61
- access: resolveRole(policy, body.role, scopes, body.agents),
62
- epoch: await t.subjectEpoch(body.subject),
63
- ttl,
64
- };
65
- });
66
- const iat = Math.floor(Date.now() / 1000);
67
- const jti = randomUUID();
68
- const scopes = [...access.scopes];
69
- const claims = {
70
- tnt: tenantId,
71
- role: body.role,
72
- scp: scopes.join(" "),
73
- ...(body.agents ? { agt: [...body.agents] } : {}),
74
- ...(body.sandboxes?.length ? { sbx: [...new Set(body.sandboxes)] } : {}),
75
- epc: epoch,
76
- };
77
- // Signed by the keys service (F4.2): this process never holds the private key.
78
- const { token, keyId } = await ctx.keys.sign({
79
- typ: SUBJECT_TOKEN_TYPE,
80
- claims: {
81
- ...claims,
82
- iss: subjectTokenIssuer(tenantId),
83
- aud: SUBJECT_TOKEN_AUDIENCE,
84
- sub: body.subject,
85
- iat,
86
- exp: iat + ttl,
87
- jti,
88
- },
89
- });
90
- const signer = { id: keyId };
91
- ctx.config.logger.info("subject token minted", {
92
- jti,
93
- kid: signer.id,
94
- role: body.role,
95
- });
96
- return {
97
- token,
98
- expiresAt: new Date((iat + ttl) * 1000).toISOString(),
99
- subject: body.subject,
100
- role: body.role,
101
- scopes,
102
- agents: access.agents === "*" ? "*" : [...access.agents],
103
- ...(claims.sbx ? { sandboxes: [...claims.sbx] } : {}),
104
- keyId: signer.id,
105
- };
106
- }
107
- /**
108
- * Verifies a subject token. Returns the request's scope, answers `401 token_expired` for
109
- * the cases a new token fixes, and the opaque 404 for everything else.
110
- */
111
- export async function verifySubjectToken(ctx, raw) {
112
- const checked = tokenKeyId(raw, SUBJECT_TOKEN_TYPE);
113
- if ("refused" in checked)
114
- return rejected(ctx, checked.refused === "malformed" ? "token_malformed" : "token_header");
115
- const { kid } = checked;
116
- const { row, policy } = await ctx.store.tx(async (t) => ({
117
- row: await t.signingKey(kid),
118
- policy: await readPolicy(t),
119
- }));
120
- if (!row)
121
- return rejected(ctx, "token_key_unknown");
122
- const tenantId = ctx.config.tenantId;
123
- let claims;
124
- try {
125
- const verified = await jwtVerify(raw, await ctx.signingKeys.publicKey(row), {
126
- algorithms: ["ES256"],
127
- issuer: subjectTokenIssuer(tenantId),
128
- audience: SUBJECT_TOKEN_AUDIENCE,
129
- typ: SUBJECT_TOKEN_TYPE,
130
- clockTolerance: CLOCK_TOLERANCE_SECONDS,
131
- requiredClaims: ["sub", "iat", "exp", "jti"],
132
- });
133
- claims = verified.payload;
134
- }
135
- catch (error) {
136
- if (error instanceof errors.JWTExpired) {
137
- // The signature was valid; only now may the answer differ from a forgery.
138
- return expired(ctx, "expired");
139
- }
140
- return rejected(ctx, "token_invalid");
141
- }
142
- // The key was revoked after it signed: a new token uses the current key.
143
- if (row.state === "revoked")
144
- return expired(ctx, "key_revoked", claims.jti);
145
- const scopeNames = typeof claims.scp === "string" ? claims.scp.split(" ").filter(Boolean) : [];
146
- if (claims.tnt !== tenantId ||
147
- !isSubject(claims.sub) ||
148
- typeof claims.role !== "string" ||
149
- typeof claims.epc !== "number" ||
150
- !Number.isInteger(claims.epc) ||
151
- claims.exp - claims.iat >
152
- policy.tokens.maxTtlSeconds + LIFETIME_SLACK_SECONDS ||
153
- scopeNames.some((name) => !TOKEN_SCOPES.includes(name)) ||
154
- (claims.agt !== undefined &&
155
- (!Array.isArray(claims.agt) ||
156
- claims.agt.some((agent) => typeof agent !== "string"))) ||
157
- (claims.sbx !== undefined &&
158
- (!Array.isArray(claims.sbx) ||
159
- claims.sbx.length > TOKEN_SANDBOX_GRANTS_MAX ||
160
- claims.sbx.some((grant) => !isSandboxGrant(grant)))))
161
- return rejected(ctx, "token_claims");
162
- const epoch = await ctx.store.tx((t) => t.subjectEpoch(claims.sub));
163
- if (claims.epc !== epoch)
164
- return expired(ctx, "revoked", claims.jti);
165
- const access = resolveRole(policy, claims.role, scopeNames, claims.agt);
166
- if (!access)
167
- return expired(ctx, "role_removed", claims.jti);
168
- if (access.scopes.size === 0)
169
- return expired(ctx, "role_narrowed", claims.jti);
170
- return {
171
- kind: "token",
172
- subject: claims.sub,
173
- scopes: access.scopes,
174
- agents: access.agents,
175
- role: access.role,
176
- ...(access.limits ? { limits: access.limits } : {}),
177
- ...(claims.sbx ? { sandboxes: claims.sbx } : {}),
178
- epoch,
179
- expiresAt: claims.exp * 1000,
180
- tokenId: claims.jti,
181
- keyId: kid,
182
- };
183
- }
184
- /** True when a token caller may reach `agentId`. Other callers always may. */
185
- export function mayUseAgent(scope, agentId) {
186
- return scope.kind !== "token" || agentAllowed(scope.agents, agentId);
187
- }