@nylorun/runtime 0.12.0-beta → 0.14.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.
- package/CHANGELOG.md +361 -0
- package/README.md +69 -15
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +38 -8
- package/dist/adapters/replication/pgoutput.d.ts +16 -0
- package/dist/adapters/replication/pgoutput.js +169 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +1071 -0
- package/dist/api/a2a/card.d.ts +15 -0
- package/dist/api/a2a/card.js +24 -0
- package/dist/api/a2a/endpoint.d.ts +8 -0
- package/dist/api/a2a/endpoint.js +81 -0
- package/dist/api/a2a/protocol.d.ts +144 -0
- package/dist/api/a2a/protocol.js +188 -0
- package/dist/api/a2a/routes.d.ts +19 -0
- package/dist/api/a2a/routes.js +289 -0
- package/dist/api/a2a/tasks.d.ts +50 -0
- package/dist/api/a2a/tasks.js +186 -0
- package/dist/api/ag-ui/endpoint.d.ts +8 -0
- package/dist/api/ag-ui/endpoint.js +126 -0
- package/dist/api/ag-ui/history.d.ts +7 -0
- package/dist/api/ag-ui/history.js +60 -0
- package/dist/api/ag-ui/routes.d.ts +32 -0
- package/dist/api/ag-ui/routes.js +305 -0
- package/dist/api/ag-ui/session-id.d.ts +1 -0
- package/dist/api/ag-ui/session-id.js +12 -0
- package/dist/api/ag-ui/sse.d.ts +9 -0
- package/dist/api/ag-ui/sse.js +7 -0
- package/dist/api/ag-ui/translate.d.ts +38 -0
- package/dist/api/ag-ui/translate.js +222 -0
- package/dist/api/components.d.ts +7825 -0
- package/dist/api/components.js +83 -0
- package/dist/api/http/app.d.ts +18 -0
- package/dist/api/http/app.js +58 -0
- package/dist/api/http/body.d.ts +4 -0
- package/dist/api/http/body.js +37 -0
- package/dist/api/http/define.d.ts +39 -0
- package/dist/api/http/define.js +130 -0
- package/dist/api/http/respond.d.ts +9 -0
- package/dist/api/http/respond.js +44 -0
- package/dist/api/http/routes/access.d.ts +24 -0
- package/dist/api/http/routes/access.js +259 -0
- package/dist/api/http/routes/actions.d.ts +8 -0
- package/dist/api/http/routes/actions.js +76 -0
- package/dist/api/http/routes/endpoints.d.ts +7 -0
- package/dist/api/http/routes/endpoints.js +100 -0
- package/dist/api/http/routes/sessions.d.ts +3 -0
- package/dist/api/http/routes/sessions.js +205 -0
- package/dist/api/http/routes/tenant.d.ts +10 -0
- package/dist/api/http/routes/tenant.js +172 -0
- package/dist/api/http/routes/vaults.d.ts +8 -0
- package/dist/api/http/routes/vaults.js +167 -0
- package/dist/api/openapi.d.ts +19 -0
- package/dist/api/openapi.js +138 -0
- package/dist/api/route.d.ts +12 -0
- package/dist/api/route.js +4 -0
- package/dist/core/bearer.d.ts +8 -0
- package/dist/core/bearer.js +11 -0
- package/dist/core/flow-host.d.ts +7 -15
- package/dist/core/flow-host.js +19 -41
- package/dist/core/sandbox-routes.d.ts +12 -7
- package/dist/core/sandbox-routes.js +24 -25
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +33 -13
- package/dist/execution/types.d.ts +38 -6
- package/dist/execution/types.js +12 -3
- package/dist/host/admin-api.d.ts +22 -0
- package/dist/host/admin-api.js +178 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +185 -0
- package/dist/host/config-for.d.ts +2 -0
- package/dist/host/config-for.js +1 -0
- package/dist/host/config.d.ts +11 -0
- package/dist/host/cors.d.ts +27 -0
- package/dist/host/cors.js +64 -0
- package/dist/host/create-host.d.ts +31 -3
- package/dist/host/create-host.js +168 -327
- package/dist/host/http.d.ts +14 -3
- package/dist/host/http.js +64 -23
- package/dist/host/main.js +55 -4
- package/dist/host/stack-config.d.ts +20 -0
- package/dist/host/stack-config.js +55 -0
- package/dist/mcp/pool.d.ts +19 -1
- package/dist/mcp/pool.js +60 -10
- package/dist/model/catalog.d.ts +2 -10
- package/dist/model/classify.d.ts +13 -0
- package/dist/model/classify.js +61 -0
- package/dist/model/models.d.ts +10 -1
- package/dist/model/models.js +14 -6
- package/dist/model/pi-model.d.ts +17 -2
- package/dist/model/pi-model.js +366 -222
- package/dist/openapi.json +25696 -0
- package/dist/sandbox/manager.d.ts +13 -1
- package/dist/sandbox/manager.js +8 -11
- package/dist/sandbox/resolve.d.ts +33 -0
- package/dist/sandbox/resolve.js +81 -0
- package/dist/sandbox/session-sandbox.d.ts +11 -0
- package/dist/sandbox/session-sandbox.js +51 -0
- package/dist/sandbox/share.d.ts +14 -2
- package/dist/sandbox/share.js +33 -3
- package/dist/sandbox/tenant-config.d.ts +37 -0
- package/dist/sandbox/tenant-config.js +51 -0
- package/dist/sandbox/tools.d.ts +7 -9
- package/dist/sandbox/tools.js +11 -3
- package/dist/sandbox/types.d.ts +8 -2
- package/dist/store/event.d.ts +19 -0
- package/dist/store/event.js +49 -0
- package/dist/store/memory.d.ts +20 -8
- package/dist/store/memory.js +265 -100
- package/dist/store/postgres/connect.d.ts +13 -0
- package/dist/store/postgres/connect.js +5 -0
- package/dist/store/postgres/migrations/003_subject_tokens.d.ts +8 -0
- package/dist/store/postgres/migrations/003_subject_tokens.js +42 -0
- package/dist/store/postgres/migrations/004_publishable_keys.d.ts +6 -0
- package/dist/store/postgres/migrations/004_publishable_keys.js +18 -0
- package/dist/store/postgres/migrations/005_action_endpoints.d.ts +6 -0
- package/dist/store/postgres/migrations/005_action_endpoints.js +32 -0
- package/dist/store/postgres/migrations/006_drop_executors.d.ts +11 -0
- package/dist/store/postgres/migrations/006_drop_executors.js +32 -0
- package/dist/store/postgres/migrations/007_event_time.d.ts +7 -0
- package/dist/store/postgres/migrations/007_event_time.js +14 -0
- package/dist/store/postgres/migrations/008_durable_streams.d.ts +13 -0
- package/dist/store/postgres/migrations/008_durable_streams.js +35 -0
- package/dist/store/postgres/migrations/index.d.ts +5 -1
- package/dist/store/postgres/migrations/index.js +22 -2
- package/dist/store/postgres/migrations/shared/index.d.ts +31 -0
- package/dist/store/postgres/migrations/shared/index.js +92 -0
- package/dist/store/postgres/record.d.ts +11 -0
- package/dist/store/postgres/record.js +64 -0
- package/dist/store/postgres/store.js +337 -95
- package/dist/store/postgres/tenants.js +14 -0
- package/dist/store/types.d.ts +176 -81
- package/dist/streams/basin.d.ts +18 -1
- package/dist/streams/basin.js +34 -2
- package/dist/streams/control.d.ts +18 -0
- package/dist/streams/control.js +29 -0
- package/dist/streams/memory.d.ts +3 -1
- package/dist/streams/memory.js +14 -8
- package/dist/streams/relay/core.d.ts +35 -0
- package/dist/streams/relay/core.js +366 -0
- package/dist/streams/relay/memory.d.ts +69 -0
- package/dist/streams/relay/memory.js +176 -0
- package/dist/streams/relay/types.d.ts +77 -0
- package/dist/streams/relay/types.js +7 -0
- package/dist/streams/types.d.ts +66 -36
- package/dist/streams/types.js +18 -53
- package/dist/tenant/access-policy.d.ts +30 -0
- package/dist/tenant/access-policy.js +52 -0
- package/dist/tenant/actions.d.ts +10 -149
- package/dist/tenant/actions.js +5 -167
- package/dist/tenant/advance.js +32 -0
- package/dist/tenant/auth.d.ts +19 -17
- package/dist/tenant/auth.js +90 -93
- package/dist/tenant/browser.d.ts +15 -0
- package/dist/tenant/browser.js +39 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +121 -129
- package/dist/tenant/context.d.ts +75 -18
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +105 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +476 -0
- package/dist/tenant/effects.d.ts +5 -0
- package/dist/tenant/effects.js +138 -48
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +6 -0
- package/dist/tenant/ephemeral.js +60 -36
- package/dist/tenant/http.d.ts +8 -9
- package/dist/tenant/http.js +13 -27
- package/dist/tenant/jwt.d.ts +16 -0
- package/dist/tenant/jwt.js +50 -0
- package/dist/tenant/module.d.ts +3 -0
- package/dist/tenant/module.js +6 -12
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/pool.d.ts +0 -2
- package/dist/tenant/pool.js +0 -18
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +10 -20
- package/dist/tenant/runtime.js +59 -42
- package/dist/tenant/scheduler.d.ts +7 -2
- package/dist/tenant/scheduler.js +7 -3
- package/dist/tenant/session-streams.d.ts +125 -0
- package/dist/tenant/session-streams.js +399 -0
- package/dist/tenant/sessions.d.ts +28 -4
- package/dist/tenant/sessions.js +118 -24
- package/dist/tenant/signing-keys.d.ts +55 -0
- package/dist/tenant/signing-keys.js +172 -0
- package/dist/tenant/slim.d.ts +8 -0
- package/dist/tenant/slim.js +21 -0
- package/dist/tenant/status.d.ts +0 -3
- package/dist/tenant/status.js +19 -12
- package/dist/tenant/streams.d.ts +60 -67
- package/dist/tenant/streams.js +140 -208
- package/dist/tenant/subject-limits.d.ts +16 -0
- package/dist/tenant/subject-limits.js +45 -0
- package/dist/tenant/sweep.d.ts +1 -10
- package/dist/tenant/sweep.js +29 -69
- package/dist/tenant/tokens.d.ts +14 -0
- package/dist/tenant/tokens.js +178 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +29 -7
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +9 -3
- package/dist/vault/service.d.ts +18 -2
- package/dist/vault/service.js +57 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +20 -8
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/routes-tenant.d.ts +0 -12
- package/dist/tenant/routes-tenant.js +0 -137
- package/dist/tenant/routes.d.ts +0 -9
- package/dist/tenant/routes.js +0 -151
package/dist/tenant/actions.js
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Action service and the executor registry: discover pending Actions, claim, heartbeat,
|
|
3
|
-
* claim-scoped sandbox tool calls, and executor registration, listing and removal.
|
|
4
|
-
*
|
|
5
|
-
* A claim or heartbeat locks the Action's session and commits in one transaction; the
|
|
6
|
-
* registry is updated only after the executors table commits.
|
|
7
|
-
*
|
|
8
|
-
* Later waves: Wave 2 / Y reads the executor `connected` flag from the work stream instead
|
|
9
|
-
* of `executorStreams`.
|
|
10
|
-
*/
|
|
11
|
-
import { randomUUID } from "node:crypto";
|
|
12
|
-
import { ActionClaimRequestSchema, ActionHeartbeatRequestSchema, } from "@nylorun/core/contracts";
|
|
13
|
-
import { assertExecutorCredential, hashToken, } from "../core/executors.js";
|
|
14
1
|
import { SandboxRouteError, handleActionSandboxTool, } from "../core/sandbox-routes.js";
|
|
15
|
-
import { lockedSession, sandboxLookup, sessionOf, } from "./context.js";
|
|
16
|
-
import { fail, readBody, requestAborted } from "./http.js";
|
|
17
2
|
import { scoped } from "./auth.js";
|
|
18
|
-
import {
|
|
3
|
+
import { sandboxLookup, sessionOf } from "./context.js";
|
|
4
|
+
import { fail } from "./http.js";
|
|
19
5
|
/** Session and Action lookups for the sandbox route handlers. */
|
|
20
6
|
export function sandboxRouteDeps(ctx) {
|
|
21
7
|
return {
|
|
@@ -27,19 +13,13 @@ export function sandboxRouteDeps(ctx) {
|
|
|
27
13
|
getAction: (id) => ctx.store.tx((t) => t.get("actions", id)),
|
|
28
14
|
};
|
|
29
15
|
}
|
|
30
|
-
/** `
|
|
31
|
-
export async function
|
|
32
|
-
return {
|
|
33
|
-
actions: await ctx.store.tx((t) => t.pendingActions(executor.agentId)),
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
/** `POST /v1/actions/:id/sandbox/:tool`: a claim-scoped sandbox tool call. */
|
|
37
|
-
export async function actionSandboxTool(ctx, scope, actionId, toolName, request) {
|
|
16
|
+
/** `POST /v1/actions/:id/sandbox/:tool`: a sandbox tool call for the Action being delivered. */
|
|
17
|
+
export async function actionSandboxTool(ctx, scope, actionId, toolName, body, signal) {
|
|
38
18
|
const action = (await ctx.store.tx((t) => t.get("actions", actionId))) ??
|
|
39
19
|
fail(404, "Action not found");
|
|
40
20
|
scoped(scope, action);
|
|
41
21
|
try {
|
|
42
|
-
return await handleActionSandboxTool(sandboxRouteDeps(ctx), actionId, toolName, await
|
|
22
|
+
return await handleActionSandboxTool(sandboxRouteDeps(ctx), actionId, toolName, await body(), signal, { generation: scope.generation });
|
|
43
23
|
}
|
|
44
24
|
catch (error) {
|
|
45
25
|
if (error instanceof SandboxRouteError)
|
|
@@ -47,145 +27,3 @@ export async function actionSandboxTool(ctx, scope, actionId, toolName, request)
|
|
|
47
27
|
throw error;
|
|
48
28
|
}
|
|
49
29
|
}
|
|
50
|
-
/** `POST /v1/actions/:id/claim` and `/heartbeat`; anything else under the Action is a 404. */
|
|
51
|
-
export function updateAction(ctx, scope, actionId, method, operation, body) {
|
|
52
|
-
return ctx.store.tx(async (t) => {
|
|
53
|
-
const found = (await t.get("actions", actionId)) ??
|
|
54
|
-
fail(404, "Action not found");
|
|
55
|
-
scoped(scope, found);
|
|
56
|
-
const s = await lockedSession(t, found.sessionId);
|
|
57
|
-
// Read again under the session lock.
|
|
58
|
-
const action = (await t.get("actions", actionId)) ??
|
|
59
|
-
fail(404, "Action not found");
|
|
60
|
-
if (method === "POST" && operation === "claim") {
|
|
61
|
-
ActionClaimRequestSchema.parse(body);
|
|
62
|
-
if (action.status !== "pending" ||
|
|
63
|
-
s.status === "cancelled" ||
|
|
64
|
-
s.activeTurnId !== action.turnId)
|
|
65
|
-
fail(409, "Action unavailable");
|
|
66
|
-
action.status = "claimed";
|
|
67
|
-
action.generation++;
|
|
68
|
-
action.claimId = randomUUID();
|
|
69
|
-
action.leaseExpiresAt = new Date(Date.now() + (ctx.config.leaseMs ?? 30000)).toISOString();
|
|
70
|
-
await t.put("actions", actionId, action);
|
|
71
|
-
await t.event(action.sessionId, action.turnId, "action.claimed", {
|
|
72
|
-
actionId,
|
|
73
|
-
generation: action.generation,
|
|
74
|
-
...(action.agent ? { agent: action.agent } : {}),
|
|
75
|
-
});
|
|
76
|
-
return {
|
|
77
|
-
action,
|
|
78
|
-
claimId: action.claimId,
|
|
79
|
-
generation: action.generation,
|
|
80
|
-
leaseExpiresAt: action.leaseExpiresAt,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
if (method === "POST" && operation === "heartbeat") {
|
|
84
|
-
const beat = ActionHeartbeatRequestSchema.parse(body);
|
|
85
|
-
if (s.activeTurnId !== action.turnId ||
|
|
86
|
-
action.status !== "claimed" ||
|
|
87
|
-
action.claimId !== beat.claimId ||
|
|
88
|
-
action.generation !== beat.generation ||
|
|
89
|
-
Date.parse(action.leaseExpiresAt) <= Date.now())
|
|
90
|
-
fail(409, "Stale or expired claim");
|
|
91
|
-
action.leaseExpiresAt = new Date(Date.now() + (ctx.config.leaseMs ?? 30000)).toISOString();
|
|
92
|
-
await t.put("actions", actionId, action);
|
|
93
|
-
return { leaseExpiresAt: action.leaseExpiresAt };
|
|
94
|
-
}
|
|
95
|
-
return fail(404, "Route not found");
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
/** `GET /v1/executors`. */
|
|
99
|
-
export function listExecutors(ctx) {
|
|
100
|
-
return {
|
|
101
|
-
executors: ctx.registry.list().map((e) => ({
|
|
102
|
-
agentId: e.agentId,
|
|
103
|
-
implementationVersion: e.implementationVersion,
|
|
104
|
-
...(e.manifestHash === undefined ? {} : { manifestHash: e.manifestHash }),
|
|
105
|
-
connected: executorConnected(ctx.live, e.tokenHash),
|
|
106
|
-
updatedAt: e.updatedAt,
|
|
107
|
-
})),
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
/** `PUT /v1/executors`: persist the batch, then update the registry and end rotated streams. */
|
|
111
|
-
export async function registerExecutors(ctx, principalId, body) {
|
|
112
|
-
const { store, registry } = ctx;
|
|
113
|
-
// Credential rules are shared with startup validation, which throws plainly; on the
|
|
114
|
-
// wire a rejected registration is a bad request, not a server fault.
|
|
115
|
-
const applicationHashes = await store.tx((t) => t.applicationTokenHashes());
|
|
116
|
-
try {
|
|
117
|
-
for (const executor of body.executors)
|
|
118
|
-
assertExecutorCredential(executor, applicationHashes);
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
fail(400, error instanceof Error ? error.message : String(error));
|
|
122
|
-
}
|
|
123
|
-
if (new Set(body.executors.map((e) => e.agentId)).size !==
|
|
124
|
-
body.executors.length ||
|
|
125
|
-
new Set(body.executors.map((e) => e.token)).size !== body.executors.length)
|
|
126
|
-
fail(400, "Executor registrations must be unique per agent and token");
|
|
127
|
-
const updatedAt = new Date().toISOString();
|
|
128
|
-
const records = body.executors.map((executor) => ({
|
|
129
|
-
agentId: executor.agentId,
|
|
130
|
-
implementationVersion: executor.implementationVersion,
|
|
131
|
-
...(executor.manifestHash === undefined
|
|
132
|
-
? {}
|
|
133
|
-
: { manifestHash: executor.manifestHash }),
|
|
134
|
-
tokenHash: hashToken(executor.token),
|
|
135
|
-
persisted: true,
|
|
136
|
-
updatedAt,
|
|
137
|
-
principalId,
|
|
138
|
-
}));
|
|
139
|
-
for (const record of records) {
|
|
140
|
-
const collision = registry.find(record.tokenHash);
|
|
141
|
-
if (collision && collision.agentId !== record.agentId)
|
|
142
|
-
fail(409, "Executor tokens must be unique");
|
|
143
|
-
// A token hash may not appear in both principals and executors (D3).
|
|
144
|
-
if (applicationHashes.includes(record.tokenHash))
|
|
145
|
-
fail(400, "Executor tokens require independent credentials and an agent id");
|
|
146
|
-
}
|
|
147
|
-
// Persist the whole batch first; the in-memory registry must never run ahead of the store.
|
|
148
|
-
await store.tx(async (t) => {
|
|
149
|
-
for (const record of records)
|
|
150
|
-
await t.putExecutor({
|
|
151
|
-
agentId: record.agentId,
|
|
152
|
-
tokenHash: record.tokenHash,
|
|
153
|
-
implementationVersion: record.implementationVersion,
|
|
154
|
-
...(record.manifestHash === undefined
|
|
155
|
-
? {}
|
|
156
|
-
: { manifestHash: record.manifestHash }),
|
|
157
|
-
principalId: record.principalId,
|
|
158
|
-
updatedAt,
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
const results = records.map((record) => {
|
|
162
|
-
const previous = registry.get(record.agentId);
|
|
163
|
-
const { rotated, previousHash } = registry.upsert(record);
|
|
164
|
-
if (rotated && previousHash)
|
|
165
|
-
endExecutorStreams(ctx.live, previousHash);
|
|
166
|
-
const replacedByDifferent = previous?.principalId !== undefined &&
|
|
167
|
-
previous.principalId !== principalId;
|
|
168
|
-
if (replacedByDifferent)
|
|
169
|
-
ctx.config.logger.warn("executor registration replaced by different application credential", {
|
|
170
|
-
agentId: record.agentId,
|
|
171
|
-
previousPrincipalId: previous.principalId,
|
|
172
|
-
});
|
|
173
|
-
return {
|
|
174
|
-
agentId: record.agentId,
|
|
175
|
-
implementationVersion: record.implementationVersion,
|
|
176
|
-
rotated,
|
|
177
|
-
...(replacedByDifferent
|
|
178
|
-
? { replacedBy: "different-credential" }
|
|
179
|
-
: {}),
|
|
180
|
-
};
|
|
181
|
-
});
|
|
182
|
-
return { executors: results };
|
|
183
|
-
}
|
|
184
|
-
/** `DELETE /v1/executors/:agentId`: forget the executor and end its streams. */
|
|
185
|
-
export async function deleteExecutor(ctx, agentId) {
|
|
186
|
-
const existing = ctx.registry.get(agentId) ?? fail(404, "Executor not found");
|
|
187
|
-
await ctx.store.tx((t) => t.deleteExecutor(agentId));
|
|
188
|
-
ctx.registry.remove(agentId);
|
|
189
|
-
endExecutorStreams(ctx.live, existing.tokenHash);
|
|
190
|
-
return { agentId, deleted: true };
|
|
191
|
-
}
|
package/dist/tenant/advance.js
CHANGED
|
@@ -35,6 +35,7 @@ import { aggregateWaits, cancelSiblingWork, isWorkflowManifest, wakeLinkedWorkfl
|
|
|
35
35
|
import { isOwnershipLost, ownedTx } from "../store/ownership.js";
|
|
36
36
|
import { linkedAgentOutput, rebaseSessionState, sessionToolsOf, turnManifestOf, } from "./session.js";
|
|
37
37
|
import { prepareMcp, resolveEffect } from "./effects.js";
|
|
38
|
+
import { slimModelEffects } from "./slim.js";
|
|
38
39
|
import { command } from "./commands.js";
|
|
39
40
|
import { usesFixtureModel } from "./model-setting.js";
|
|
40
41
|
import { toolFixtureModel } from "../core/provider.js";
|
|
@@ -224,6 +225,9 @@ function startHeartbeat(ctx, lease, controller) {
|
|
|
224
225
|
},
|
|
225
226
|
};
|
|
226
227
|
}
|
|
228
|
+
/** Segment rollover defaults (Model Calls §10), well inside the advance deadline. */
|
|
229
|
+
const ROLLOVER_STEPS = 50;
|
|
230
|
+
const ROLLOVER_MS = 20 * 60_000;
|
|
227
231
|
/** Runs one segment and settles it. Throws only `ownership.lost` and infrastructure errors. */
|
|
228
232
|
async function runSegment(ctx, lease, started, signal) {
|
|
229
233
|
const id = lease.sessionId;
|
|
@@ -258,6 +262,10 @@ async function runSegment(ctx, lease, started, signal) {
|
|
|
258
262
|
signal,
|
|
259
263
|
sessionTools: sessionToolsOf(current.mcpSnapshot),
|
|
260
264
|
host,
|
|
265
|
+
yieldAfter: {
|
|
266
|
+
steps: ctx.config.rollover?.steps ?? ROLLOVER_STEPS,
|
|
267
|
+
ms: ctx.config.rollover?.ms ?? ROLLOVER_MS,
|
|
268
|
+
},
|
|
261
269
|
});
|
|
262
270
|
// The engine turns an abort into a `cancelled` (agents) or `failed` (workflows) result;
|
|
263
271
|
// only a user cancel may settle that, and it already did.
|
|
@@ -337,6 +345,28 @@ async function settle(ctx, lease, s, result) {
|
|
|
337
345
|
await t.put("sessions", id, current);
|
|
338
346
|
return siblings;
|
|
339
347
|
}
|
|
348
|
+
if (result.status === "yielded") {
|
|
349
|
+
// The turn goes on in a new segment: same turn, next checkpoint, woken right away.
|
|
350
|
+
const finished = result.checkpoint;
|
|
351
|
+
current.state = result.result.state;
|
|
352
|
+
current.checkpoint = {
|
|
353
|
+
...finished,
|
|
354
|
+
segment: finished.segment + 1,
|
|
355
|
+
input: { kind: "continue" },
|
|
356
|
+
state: current.state,
|
|
357
|
+
};
|
|
358
|
+
current.status = "runnable";
|
|
359
|
+
current.waits = undefined;
|
|
360
|
+
await t.put("checkpoints", JSON.stringify([id, s.activeTurnId, finished.segment]), { checkpoint: finished, status: "yielded" });
|
|
361
|
+
await slimModelEffects(t, id, s.activeTurnId);
|
|
362
|
+
await t.put("sessions", id, current);
|
|
363
|
+
const segment = finished.segment + 1;
|
|
364
|
+
t.afterCommit(() => ctx.wake(id, {
|
|
365
|
+
reason: "rollover",
|
|
366
|
+
dedupeKey: `rollover:${s.activeTurnId}:${segment}`,
|
|
367
|
+
}));
|
|
368
|
+
return siblings;
|
|
369
|
+
}
|
|
340
370
|
const flow = isWorkflowManifest(current.manifest);
|
|
341
371
|
if (!flow) {
|
|
342
372
|
current.state =
|
|
@@ -370,6 +400,7 @@ async function settle(ctx, lease, s, result) {
|
|
|
370
400
|
current.activeTurnId = null;
|
|
371
401
|
if (s.activeTurnId)
|
|
372
402
|
current.lastTurnId = s.activeTurnId;
|
|
403
|
+
await slimModelEffects(t, id, s.activeTurnId);
|
|
373
404
|
}
|
|
374
405
|
const type = `turn.${result.status}`;
|
|
375
406
|
const payload = result.status === "completed"
|
|
@@ -430,6 +461,7 @@ async function settleFailure(ctx, lease, s, error) {
|
|
|
430
461
|
current.error = error instanceof Error ? error.message : String(error);
|
|
431
462
|
const payload = { message: current.error };
|
|
432
463
|
await t.event(id, current.activeTurnId, "turn.failed", payload);
|
|
464
|
+
await slimModelEffects(t, id, current.activeTurnId);
|
|
433
465
|
if (current.activeTurnId)
|
|
434
466
|
current.lastTurnId = current.activeTurnId;
|
|
435
467
|
current.activeTurnId = null;
|
package/dist/tenant/auth.d.ts
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tenant request authentication and authorization: a bearer token resolves to an application
|
|
3
|
-
* principal or a
|
|
4
|
-
* principal may act for a subject (`Nylorun-Subject`, `Nylorun-Scopes`);
|
|
5
|
-
*
|
|
3
|
+
* principal, a subject token or a delivery token; anything else is the opaque 404 (D5).
|
|
4
|
+
* An application principal may act for a subject (`Nylorun-Subject`, `Nylorun-Scopes`); a
|
|
5
|
+
* subject token names its subject itself. `authorize` limits both to the routes their scopes
|
|
6
|
+
* allow, decided from the route alone.
|
|
6
7
|
*/
|
|
7
8
|
import type { IncomingMessage } from "node:http";
|
|
8
9
|
import { type Action, type SubjectScope } from "@nylorun/core/contracts";
|
|
9
|
-
import type { AuthScope, TenantContext } from "./context.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export type RouteAccess = readonly SubjectScope[] | "never";
|
|
10
|
+
import type { AuthScope, SessionAccess, TenantContext } from "./context.js";
|
|
11
|
+
import type { BrowserClient } from "./browser.js";
|
|
12
|
+
export declare function authenticate(ctx: TenantContext, request: IncomingMessage, client?: BrowserClient): Promise<AuthScope>;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* What a subject needs for a route: any one of the scopes, nothing grants it (`never`), or
|
|
15
|
+
* any caller may (`any`, public data such as the JWKS). Each route declares it
|
|
16
|
+
* (`api/http/define.ts`).
|
|
17
17
|
*/
|
|
18
|
-
export
|
|
18
|
+
export type SubjectAccess = readonly SubjectScope[] | "never" | "any";
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* know is a 404 for a subject: new routes stay closed until they are added here. Application
|
|
23
|
-
* and executor scopes pass through to the checks each route makes today.
|
|
20
|
+
* A subject, subject token or publishable key reaches a route only with one of the scopes it
|
|
21
|
+
* declares: `403 scope_required`, decided from the route alone before anything is read.
|
|
24
22
|
*/
|
|
25
|
-
export declare function
|
|
23
|
+
export declare function requireScopes(scope: AuthScope, access: SubjectAccess): void;
|
|
24
|
+
export declare function accessOf(scope: AuthScope): SessionAccess | undefined;
|
|
26
25
|
/** The subject whose sessions and vaults the request is limited to, if it acts for one. */
|
|
27
26
|
export declare function ownerOf(scope: AuthScope): string | undefined;
|
|
28
27
|
/** The application principal behind the request, acting for a subject or not. */
|
|
29
28
|
export declare function requirePrincipal(scope: AuthScope): string;
|
|
30
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* The caller may act on `action`: only the delivery token minted for it. Whether its generation
|
|
31
|
+
* is still the Action's is checked by each callback.
|
|
32
|
+
*/
|
|
31
33
|
export declare function scoped(scope: AuthScope, action: Action): void;
|
|
32
34
|
export declare function requireApplication(scope: AuthScope): string;
|
package/dist/tenant/auth.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { SCOPES_HEADER, SUBJECT_HEADER, } from "@nylorun/core/compatibility";
|
|
2
|
-
import { parseSubjectHeaders, } from "@nylorun/core/contracts";
|
|
3
|
-
import { hashToken } from "../core/
|
|
2
|
+
import { DELIVERY_TOKEN_TYPE, parseSubjectHeaders, } from "@nylorun/core/contracts";
|
|
3
|
+
import { hashToken } from "../core/bearer.js";
|
|
4
4
|
import { fail, failOpaque } from "./http.js";
|
|
5
|
+
import { looksLikeToken, verifySubjectToken } from "./tokens.js";
|
|
6
|
+
import { verifyDeliveryToken } from "./delivery-token.js";
|
|
7
|
+
import { tokenType } from "./jwt.js";
|
|
8
|
+
import { readPolicy } from "./access-policy.js";
|
|
5
9
|
const SUBJECT_INVALID = { code: "subject_invalid" };
|
|
6
10
|
/** One header's value, or `undefined`; a header sent twice is refused. */
|
|
7
11
|
function singleHeader(request, name) {
|
|
@@ -14,19 +18,56 @@ function singleHeader(request, name) {
|
|
|
14
18
|
return fail(400, `${name} must be sent once`, SUBJECT_INVALID);
|
|
15
19
|
return values[0];
|
|
16
20
|
}
|
|
17
|
-
export async function authenticate(ctx, request) {
|
|
21
|
+
export async function authenticate(ctx, request, client) {
|
|
18
22
|
const header = request.headers.authorization;
|
|
19
23
|
const token = header?.startsWith("Bearer ") ? header.slice(7) : "";
|
|
20
24
|
if (!token || !header?.startsWith("Bearer ")) {
|
|
25
|
+
// A publishable key alone: what the policy grants `anon` (nothing by default).
|
|
26
|
+
if (client && header === undefined) {
|
|
27
|
+
const { anon } = await ctx.store.tx((t) => readPolicy(t));
|
|
28
|
+
return {
|
|
29
|
+
kind: "publishable",
|
|
30
|
+
keyId: client.keyId,
|
|
31
|
+
scopes: new Set(anon.scopes),
|
|
32
|
+
agents: anon.agents === "*" ? "*" : new Set(anon.agents),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
21
35
|
ctx.config.logger.warn("credential rejected", {
|
|
22
36
|
reason: "missing_bearer",
|
|
23
37
|
});
|
|
24
38
|
return failOpaque();
|
|
25
39
|
}
|
|
40
|
+
if (looksLikeToken(token) && tokenType(token) === DELIVERY_TOKEN_TYPE) {
|
|
41
|
+
// Delivery tokens come back from the application's server, never from a browser.
|
|
42
|
+
if (request.headers.origin !== undefined)
|
|
43
|
+
fail(403, "Delivery tokens are not accepted from browsers", { code: "origin_rejected" });
|
|
44
|
+
const scope = await verifyDeliveryToken(ctx, token);
|
|
45
|
+
if (singleHeader(request, SUBJECT_HEADER) !== undefined ||
|
|
46
|
+
singleHeader(request, SCOPES_HEADER) !== undefined)
|
|
47
|
+
fail(403, "A delivery token cannot act for a subject");
|
|
48
|
+
return scope;
|
|
49
|
+
}
|
|
50
|
+
if (looksLikeToken(token)) {
|
|
51
|
+
const scope = await verifySubjectToken(ctx, token);
|
|
52
|
+
// Read only after verification, so an unknown token still sees the opaque 404.
|
|
53
|
+
if (singleHeader(request, SUBJECT_HEADER) !== undefined ||
|
|
54
|
+
singleHeader(request, SCOPES_HEADER) !== undefined)
|
|
55
|
+
fail(403, "A subject token cannot act for another subject");
|
|
56
|
+
return scope;
|
|
57
|
+
}
|
|
58
|
+
// Application keys never come from a browser or a shipped app: refused before they are even
|
|
59
|
+
// looked up.
|
|
60
|
+
if (request.headers.origin !== undefined)
|
|
61
|
+
fail(403, "Application keys are not accepted from browsers", {
|
|
62
|
+
code: "origin_rejected",
|
|
63
|
+
});
|
|
64
|
+
if (client)
|
|
65
|
+
fail(400, "Nylorun-Key is for browser and mobile clients, not with a Tenant key", {
|
|
66
|
+
code: "invalid_request",
|
|
67
|
+
});
|
|
26
68
|
const tokenHash = hashToken(token);
|
|
27
69
|
const principal = await ctx.store.tx((t) => t.principalByTokenHash(tokenHash));
|
|
28
|
-
|
|
29
|
-
if (!principal && !executor) {
|
|
70
|
+
if (!principal) {
|
|
30
71
|
ctx.config.logger.warn("credential rejected", {
|
|
31
72
|
reason: "unknown_token",
|
|
32
73
|
});
|
|
@@ -35,11 +76,6 @@ export async function authenticate(ctx, request) {
|
|
|
35
76
|
// Read only after authentication, so an unknown caller sees the opaque 404 either way.
|
|
36
77
|
const subject = singleHeader(request, SUBJECT_HEADER);
|
|
37
78
|
const scopes = singleHeader(request, SCOPES_HEADER);
|
|
38
|
-
if (executor) {
|
|
39
|
-
if (subject !== undefined || scopes !== undefined)
|
|
40
|
-
fail(403, "An executor credential cannot act for a subject");
|
|
41
|
-
return { kind: "executor", executor };
|
|
42
|
-
}
|
|
43
79
|
if (subject === undefined) {
|
|
44
80
|
if (scopes !== undefined)
|
|
45
81
|
fail(400, `${SCOPES_HEADER} requires ${SUBJECT_HEADER}`, SUBJECT_INVALID);
|
|
@@ -55,87 +91,17 @@ export async function authenticate(ctx, request) {
|
|
|
55
91
|
scopes: parsed.scopes,
|
|
56
92
|
};
|
|
57
93
|
}
|
|
58
|
-
const SESSIONS = ["sessions:own"];
|
|
59
|
-
const VAULTS = ["vaults:own"];
|
|
60
|
-
const SETTINGS = ["tenant:settings"];
|
|
61
94
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* executors, actions and the sandbox tool routes.
|
|
95
|
+
* A subject, subject token or publishable key reaches a route only with one of the scopes it
|
|
96
|
+
* declares: `403 scope_required`, decided from the route alone before anything is read.
|
|
65
97
|
*/
|
|
66
|
-
export function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
case "executors":
|
|
73
|
-
case "actions":
|
|
74
|
-
return "never";
|
|
75
|
-
case "agents":
|
|
76
|
-
if (n === 2 && method === "GET")
|
|
77
|
-
return ["agents:read", "agents:write"];
|
|
78
|
-
if (n === 3 && method === "PUT")
|
|
79
|
-
return ["agents:write"];
|
|
80
|
-
return undefined;
|
|
81
|
-
case "sessions":
|
|
82
|
-
if (n === 2 && method === "GET")
|
|
83
|
-
return SESSIONS;
|
|
84
|
-
if (n === 3 && (method === "GET" || method === "PUT"))
|
|
85
|
-
return SESSIONS;
|
|
86
|
-
if (n === 4 && method === "GET" && (sub === "items" || sub === "events"))
|
|
87
|
-
return SESSIONS;
|
|
88
|
-
if (n === 4 && method === "POST" && sub === "commands")
|
|
89
|
-
return SESSIONS;
|
|
90
|
-
if (n === 5 && method === "POST" && sub === "sandbox")
|
|
91
|
-
return "never";
|
|
92
|
-
return undefined;
|
|
93
|
-
case "vaults":
|
|
94
|
-
if (n === 2)
|
|
95
|
-
return method === "GET" || method === "POST" ? VAULTS : undefined;
|
|
96
|
-
if (n === 3)
|
|
97
|
-
return method === "GET" || method === "DELETE" ? VAULTS : undefined;
|
|
98
|
-
if (sub !== "credentials")
|
|
99
|
-
return undefined;
|
|
100
|
-
if (n === 4)
|
|
101
|
-
return method === "GET" || method === "POST" ? VAULTS : undefined;
|
|
102
|
-
if (n === 5)
|
|
103
|
-
return method === "GET" || method === "POST" || method === "DELETE"
|
|
104
|
-
? VAULTS
|
|
105
|
-
: undefined;
|
|
106
|
-
return undefined;
|
|
107
|
-
case "tenant":
|
|
108
|
-
if (n === 2 && method === "GET")
|
|
109
|
-
return SETTINGS;
|
|
110
|
-
if (n === 3 && id === "reset" && method === "POST")
|
|
111
|
-
return "never";
|
|
112
|
-
if (n === 4 && id === "config" && sub === "seed" && method === "PUT")
|
|
113
|
-
return "never";
|
|
114
|
-
if (n === 3 && method === "GET" && (id === "models" || id === "providers"))
|
|
115
|
-
return ["tenant:settings", "agents:write"];
|
|
116
|
-
if (n === 3 && method === "GET" && id === "sandbox")
|
|
117
|
-
return SETTINGS;
|
|
118
|
-
if (n === 3 && id === "model" && (method === "GET" || method === "PUT"))
|
|
119
|
-
return SETTINGS;
|
|
120
|
-
if (n === 4 && id === "model" && sub === "selection" && method === "PUT")
|
|
121
|
-
return SETTINGS;
|
|
122
|
-
return undefined;
|
|
123
|
-
default:
|
|
124
|
-
return undefined;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Limits a subject to the routes its scopes allow. Decided from the route alone, before any
|
|
129
|
-
* lookup, so a refusal reveals nothing about which resources exist. A route the table does not
|
|
130
|
-
* know is a 404 for a subject: new routes stay closed until they are added here. Application
|
|
131
|
-
* and executor scopes pass through to the checks each route makes today.
|
|
132
|
-
*/
|
|
133
|
-
export function authorize(scope, method, path) {
|
|
134
|
-
if (scope.kind !== "subject")
|
|
98
|
+
export function requireScopes(scope, access) {
|
|
99
|
+
if (scope.kind !== "subject" &&
|
|
100
|
+
scope.kind !== "token" &&
|
|
101
|
+
scope.kind !== "publishable")
|
|
102
|
+
return;
|
|
103
|
+
if (access === "any")
|
|
135
104
|
return;
|
|
136
|
-
const access = routeAccess(method, path);
|
|
137
|
-
if (access === undefined)
|
|
138
|
-
fail(404, "Route not found");
|
|
139
105
|
if (access === "never")
|
|
140
106
|
fail(403, "This route is not available when acting for a subject", {
|
|
141
107
|
code: "scope_required",
|
|
@@ -147,20 +113,51 @@ export function authorize(scope, method, path) {
|
|
|
147
113
|
details: { scopes: access },
|
|
148
114
|
});
|
|
149
115
|
}
|
|
116
|
+
export function accessOf(scope) {
|
|
117
|
+
switch (scope.kind) {
|
|
118
|
+
case "application":
|
|
119
|
+
return undefined;
|
|
120
|
+
case "subject":
|
|
121
|
+
return { owner: scope.subject };
|
|
122
|
+
case "token":
|
|
123
|
+
return {
|
|
124
|
+
owner: scope.subject,
|
|
125
|
+
...(scope.agents === "*" ? {} : { agents: scope.agents }),
|
|
126
|
+
};
|
|
127
|
+
// A publishable key alone owns nothing: no session or vault is ever reachable.
|
|
128
|
+
case "publishable":
|
|
129
|
+
// Nor does a request with no credential, on a route that serves public data.
|
|
130
|
+
case "anonymous":
|
|
131
|
+
return fail(404, "Not found");
|
|
132
|
+
// A delivery token reaches its Action's callbacks, never a session or vault.
|
|
133
|
+
case "delivery":
|
|
134
|
+
return fail(404, "Not found");
|
|
135
|
+
default: {
|
|
136
|
+
const unknown = scope;
|
|
137
|
+
return fail(404, `Unknown credential ${String(unknown.kind)}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
150
141
|
/** The subject whose sessions and vaults the request is limited to, if it acts for one. */
|
|
151
142
|
export function ownerOf(scope) {
|
|
152
|
-
return scope
|
|
143
|
+
return accessOf(scope)?.owner;
|
|
153
144
|
}
|
|
154
145
|
/** The application principal behind the request, acting for a subject or not. */
|
|
155
146
|
export function requirePrincipal(scope) {
|
|
156
|
-
if (scope.kind
|
|
147
|
+
if (scope.kind === "application" || scope.kind === "subject")
|
|
157
148
|
return scope.principalId;
|
|
158
149
|
return fail(403, "Application credential required");
|
|
159
150
|
}
|
|
160
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* The caller may act on `action`: only the delivery token minted for it. Whether its generation
|
|
153
|
+
* is still the Action's is checked by each callback.
|
|
154
|
+
*/
|
|
161
155
|
export function scoped(scope, action) {
|
|
162
|
-
if (scope.kind
|
|
163
|
-
|
|
156
|
+
if (scope.kind === "delivery" &&
|
|
157
|
+
scope.actionId === action.actionId &&
|
|
158
|
+
scope.agentId === action.agentId)
|
|
159
|
+
return;
|
|
160
|
+
fail(403, "This delivery token is for another Action");
|
|
164
161
|
}
|
|
165
162
|
export function requireApplication(scope) {
|
|
166
163
|
if (scope.kind === "application")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which client app a request comes from (Host feature `browser-access`), decided before the
|
|
3
|
+
* bearer is looked at. `Nylorun-Key` names a publishable key of this Tenant; an `Origin` must
|
|
4
|
+
* be on that key's allowlist, and only then does the response carry CORS headers. The
|
|
5
|
+
* allowlist protects browsers from other sites' pages; it is not authentication, since a
|
|
6
|
+
* non-browser can send any `Origin`: tokens authorize.
|
|
7
|
+
*/
|
|
8
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
9
|
+
import type { TenantContext } from "./context.js";
|
|
10
|
+
/** The client app of a request: its publishable key, and the origin when from a browser. */
|
|
11
|
+
export interface BrowserClient {
|
|
12
|
+
readonly keyId: string;
|
|
13
|
+
readonly origin?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function identifyClient(ctx: TenantContext, request: IncomingMessage, response: ServerResponse): Promise<BrowserClient | undefined>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { PUBLISHABLE_KEY_HEADER } from "@nylorun/core/compatibility";
|
|
2
|
+
import { originAllowed } from "@nylorun/core/contracts";
|
|
3
|
+
import { browserOrigin, setCorsHeaders } from "../host/cors.js";
|
|
4
|
+
import { fail, failOpaque } from "./http.js";
|
|
5
|
+
function single(request, name) {
|
|
6
|
+
const raw = request.headers[name.toLowerCase()];
|
|
7
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
8
|
+
}
|
|
9
|
+
export async function identifyClient(ctx, request, response) {
|
|
10
|
+
const key = single(request, PUBLISHABLE_KEY_HEADER);
|
|
11
|
+
const hasOrigin = request.headers.origin !== undefined;
|
|
12
|
+
if (key === undefined) {
|
|
13
|
+
if (hasOrigin)
|
|
14
|
+
fail(403, `Browser requests need ${PUBLISHABLE_KEY_HEADER}`, {
|
|
15
|
+
code: "origin_rejected",
|
|
16
|
+
});
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const row = await ctx.store.tx((t) => t.publishableKeyByKey(key));
|
|
20
|
+
if (!row || row.revokedAt !== null) {
|
|
21
|
+
ctx.config.logger.warn("credential rejected", {
|
|
22
|
+
reason: "publishable_key_rejected",
|
|
23
|
+
});
|
|
24
|
+
return failOpaque();
|
|
25
|
+
}
|
|
26
|
+
if (!hasOrigin)
|
|
27
|
+
return { keyId: row.id };
|
|
28
|
+
const origin = browserOrigin(request);
|
|
29
|
+
const origins = JSON.parse(row.originsJson);
|
|
30
|
+
if (origin === undefined || !originAllowed(origins, origin)) {
|
|
31
|
+
ctx.config.logger.warn("credential rejected", {
|
|
32
|
+
reason: "origin_not_allowed",
|
|
33
|
+
keyId: row.id,
|
|
34
|
+
});
|
|
35
|
+
return failOpaque();
|
|
36
|
+
}
|
|
37
|
+
setCorsHeaders(response, origin);
|
|
38
|
+
return { keyId: row.id, origin };
|
|
39
|
+
}
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
-
import type { SessionCommand } from "@nylorun/core/contracts";
|
|
2
|
-
import
|
|
1
|
+
import type { Action, ActionOutcome, LiveEvent, SessionCommand } from "@nylorun/core/contracts";
|
|
2
|
+
import type { Tx } from "../store/types.js";
|
|
3
|
+
import { type AuthScope, type Session, type TenantContext } from "./context.js";
|
|
4
|
+
/** `outcome`, or a failed one when a tool's output does not match its stored output schema. */
|
|
5
|
+
export declare function acceptedOutcome(action: Action, outcome: ActionOutcome): ActionOutcome;
|
|
3
6
|
export declare function command(ctx: TenantContext, id: string, input: SessionCommand, scope: AuthScope): Promise<unknown>;
|
|
7
|
+
/**
|
|
8
|
+
* Completes a delivered Action with `outcome`, in the caller's transaction, which
|
|
9
|
+
* holds the lock of the Action's session `s`: the Action and its effect, the
|
|
10
|
+
* `action.completed` event (and `loop.verified` or `loop.decided`), the wake that resumes the
|
|
11
|
+
* turn, and queued workflow effects. The Action deliverer and the background-result callback
|
|
12
|
+
* both record outcomes here. It sets `s.status`; the caller writes `s`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function recordActionOutcome(t: Tx, ctx: TenantContext, s: Session, action: Action, received: ActionOutcome, options?: {
|
|
15
|
+
requestId?: string;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
event: LiveEvent;
|
|
18
|
+
receipt: Record<string, unknown>;
|
|
19
|
+
}>;
|