@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/http.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Tenant's typed rejections: what a route or the code it calls throws for the client to
|
|
3
|
+
* see (`api/http/respond.ts` answers it), and the opaque 404 for unknown credentials (D5).
|
|
4
|
+
*
|
|
5
|
+
* Later waves: stable; the streams seam (Wave 2 / Y) keeps using these helpers.
|
|
6
|
+
*/
|
|
1
7
|
/** D5 opaque failure for unknown/rejected credentials on Tenant routes. */
|
|
2
8
|
export const OPAQUE_NOT_FOUND = {
|
|
3
9
|
status: "rejected",
|
|
@@ -7,14 +13,18 @@ export const OPAQUE_NOT_FOUND = {
|
|
|
7
13
|
export class HttpError extends Error {
|
|
8
14
|
status;
|
|
9
15
|
rejection;
|
|
10
|
-
|
|
16
|
+
headers;
|
|
17
|
+
constructor(status, message, rejection = {},
|
|
18
|
+
/** Response headers the rejection carries, such as `retry-after` or `www-authenticate`. */
|
|
19
|
+
headers = {}) {
|
|
11
20
|
super(message);
|
|
12
21
|
this.status = status;
|
|
13
22
|
this.rejection = rejection;
|
|
23
|
+
this.headers = headers;
|
|
14
24
|
}
|
|
15
25
|
}
|
|
16
|
-
export const fail = (status, message, rejection) => {
|
|
17
|
-
throw new HttpError(status, message, rejection);
|
|
26
|
+
export const fail = (status, message, rejection, headers) => {
|
|
27
|
+
throw new HttpError(status, message, rejection, headers);
|
|
18
28
|
};
|
|
19
29
|
export class OpaqueAuthError extends Error {
|
|
20
30
|
status = 404;
|
|
@@ -27,27 +37,3 @@ export class OpaqueAuthError extends Error {
|
|
|
27
37
|
export const failOpaque = () => {
|
|
28
38
|
throw new OpaqueAuthError();
|
|
29
39
|
};
|
|
30
|
-
/** AbortSignal tied to the HTTP request being closed by the client. */
|
|
31
|
-
export function requestAborted(request) {
|
|
32
|
-
const controller = new AbortController();
|
|
33
|
-
request.on("close", () => {
|
|
34
|
-
if (!request.complete)
|
|
35
|
-
controller.abort();
|
|
36
|
-
});
|
|
37
|
-
return controller.signal;
|
|
38
|
-
}
|
|
39
|
-
/** Read a JSON request body, capped at 1 MiB. */
|
|
40
|
-
export async function readBody(request) {
|
|
41
|
-
let data = "";
|
|
42
|
-
for await (const chunk of request) {
|
|
43
|
-
data += chunk;
|
|
44
|
-
if (Buffer.byteLength(data) > 1024 * 1024)
|
|
45
|
-
fail(413, "Request too large");
|
|
46
|
-
}
|
|
47
|
-
try {
|
|
48
|
-
return JSON.parse(data);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return fail(400, "Invalid JSON");
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const MAX_TOKEN_BYTES = 4096;
|
|
2
|
+
export declare const CLOCK_TOLERANCE_SECONDS = 30;
|
|
3
|
+
/** True when `bearer` has the shape of a JWT; application keys never do. */
|
|
4
|
+
export declare function looksLikeToken(bearer: string): boolean;
|
|
5
|
+
/** The `typ` of a JWT-shaped bearer, or `undefined` when its header cannot be read. */
|
|
6
|
+
export declare function tokenType(raw: string): string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Why the header of a token of type `typ` is refused before any key is read, or its key id
|
|
9
|
+
* when it is not: `malformed` (size, shape, undecodable) or `header` (algorithm, type, key id,
|
|
10
|
+
* or a header that could name another key).
|
|
11
|
+
*/
|
|
12
|
+
export declare function tokenKeyId(raw: string, typ: string): {
|
|
13
|
+
kid: string;
|
|
14
|
+
} | {
|
|
15
|
+
refused: "malformed" | "header";
|
|
16
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What subject tokens and delivery tokens share: the Tenant's ES256 JWTs, recognised by shape,
|
|
3
|
+
* told apart by their `typ`, and refused before any key is read when the header is not one the
|
|
4
|
+
* Runtime issues.
|
|
5
|
+
*/
|
|
6
|
+
import { decodeProtectedHeader } from "jose";
|
|
7
|
+
import { SIGNING_KEY_ID_PATTERN } from "@nylorun/core/compatibility";
|
|
8
|
+
export const MAX_TOKEN_BYTES = 4096;
|
|
9
|
+
export const CLOCK_TOLERANCE_SECONDS = 30;
|
|
10
|
+
const JWT_SHAPE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
11
|
+
const FORBIDDEN_HEADERS = ["jku", "jwk", "x5u", "x5c", "x5t", "crit"];
|
|
12
|
+
/** True when `bearer` has the shape of a JWT; application keys never do. */
|
|
13
|
+
export function looksLikeToken(bearer) {
|
|
14
|
+
return JWT_SHAPE.test(bearer);
|
|
15
|
+
}
|
|
16
|
+
/** The `typ` of a JWT-shaped bearer, or `undefined` when its header cannot be read. */
|
|
17
|
+
export function tokenType(raw) {
|
|
18
|
+
if (Buffer.byteLength(raw) > MAX_TOKEN_BYTES || !JWT_SHAPE.test(raw))
|
|
19
|
+
return undefined;
|
|
20
|
+
try {
|
|
21
|
+
const typ = decodeProtectedHeader(raw).typ;
|
|
22
|
+
return typeof typ === "string" ? typ : undefined;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Why the header of a token of type `typ` is refused before any key is read, or its key id
|
|
30
|
+
* when it is not: `malformed` (size, shape, undecodable) or `header` (algorithm, type, key id,
|
|
31
|
+
* or a header that could name another key).
|
|
32
|
+
*/
|
|
33
|
+
export function tokenKeyId(raw, typ) {
|
|
34
|
+
if (Buffer.byteLength(raw) > MAX_TOKEN_BYTES || !JWT_SHAPE.test(raw))
|
|
35
|
+
return { refused: "malformed" };
|
|
36
|
+
let header;
|
|
37
|
+
try {
|
|
38
|
+
header = decodeProtectedHeader(raw);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return { refused: "malformed" };
|
|
42
|
+
}
|
|
43
|
+
if (header.alg !== "ES256" ||
|
|
44
|
+
header.typ !== typ ||
|
|
45
|
+
typeof header.kid !== "string" ||
|
|
46
|
+
!SIGNING_KEY_ID_PATTERN.test(header.kid) ||
|
|
47
|
+
FORBIDDEN_HEADERS.some((name) => name in header))
|
|
48
|
+
return { refused: "header" };
|
|
49
|
+
return { kid: header.kid };
|
|
50
|
+
}
|
package/dist/tenant/module.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StreamRelayStatus } from "@nylorun/core/contracts";
|
|
1
2
|
import { isQuarantineError, quarantine, TenantBusyError, TenantConflictError } from "./quarantine.js";
|
|
2
3
|
import { TenantNotFoundError, TenantUnavailableError, type Logger, type OpenTenantRuntime, type TenantConfig, type TenantModule, type TenantStore } from "./types.js";
|
|
3
4
|
export interface CreateTenantModuleOptions {
|
|
@@ -21,6 +22,8 @@ export interface CreateTenantModuleOptions {
|
|
|
21
22
|
* Durable Streams basin, its armed sweep). A failure is logged; the Tenant stays deleted.
|
|
22
23
|
*/
|
|
23
24
|
onDeleted?: (tenantId: string) => Promise<void>;
|
|
25
|
+
/** This process's stream relay, for the Host aggregate (a Host with S2 runs one). */
|
|
26
|
+
relayStatus?: () => Promise<StreamRelayStatus>;
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* Deep Tenant module (§8): discovery, create, resolve, list, delete and summarize over an
|
package/dist/tenant/module.js
CHANGED
|
@@ -7,7 +7,7 @@ import { TenantNotFoundError, TenantUnavailableError, } from "./types.js";
|
|
|
7
7
|
const OPEN_TIMEOUT_MS = 30_000;
|
|
8
8
|
function hasLiveWork(summary) {
|
|
9
9
|
return (summary.runningSessions > 0 ||
|
|
10
|
-
summary.
|
|
10
|
+
summary.inFlightDeliveries > 0 ||
|
|
11
11
|
summary.pendingActions > 0);
|
|
12
12
|
}
|
|
13
13
|
function toAdmin(id, entry, envelope) {
|
|
@@ -284,28 +284,22 @@ export function createTenantModule(options) {
|
|
|
284
284
|
},
|
|
285
285
|
async summarize() {
|
|
286
286
|
let runningSessions = 0;
|
|
287
|
-
let
|
|
287
|
+
let inFlightDeliveries = 0;
|
|
288
288
|
let pendingActions = 0;
|
|
289
289
|
let uncertainEffects = 0;
|
|
290
|
-
let outboxDepth;
|
|
291
|
-
let relayLagMs;
|
|
292
290
|
for (const s of await Promise.all(openEntries().map((e) => e.handle.summary()))) {
|
|
293
291
|
runningSessions += s.runningSessions;
|
|
294
|
-
|
|
292
|
+
inFlightDeliveries += s.inFlightDeliveries;
|
|
295
293
|
pendingActions += s.pendingActions;
|
|
296
294
|
uncertainEffects += s.uncertainEffects;
|
|
297
|
-
if (s.outboxDepth !== undefined)
|
|
298
|
-
outboxDepth = (outboxDepth ?? 0) + s.outboxDepth;
|
|
299
|
-
if (s.relayLagMs !== undefined)
|
|
300
|
-
relayLagMs = Math.max(relayLagMs ?? 0, s.relayLagMs);
|
|
301
295
|
}
|
|
296
|
+
const relay = await options.relayStatus?.().catch(() => undefined);
|
|
302
297
|
return {
|
|
303
298
|
runningSessions,
|
|
304
|
-
|
|
299
|
+
inFlightDeliveries,
|
|
305
300
|
pendingActions,
|
|
306
301
|
uncertainEffects,
|
|
307
|
-
...(
|
|
308
|
-
...(relayLagMs !== undefined ? { relayLagMs } : {}),
|
|
302
|
+
...(relay ? { relay } : {}),
|
|
309
303
|
};
|
|
310
304
|
},
|
|
311
305
|
async close() {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type IncomingHttpHeaders } from "node:http";
|
|
2
|
+
/** How the Host lets the Runtime call developer URLs (Host settings, `TenantConfig.delivery`). */
|
|
3
|
+
export interface OutboundPolicy {
|
|
4
|
+
/** `http` URLs as well as `https`. Default true (OSS). */
|
|
5
|
+
allowHttp?: boolean;
|
|
6
|
+
/** Private, loopback and link-local addresses. Default `allow` (OSS); Cloud refuses. */
|
|
7
|
+
privateAddresses?: "allow" | "refuse";
|
|
8
|
+
/**
|
|
9
|
+
* `docker-host`: the Runtime runs in the local stack's container, so `localhost`,
|
|
10
|
+
* `127.0.0.1` and `[::1]` mean the machine that runs Docker (`host.docker.internal`).
|
|
11
|
+
*/
|
|
12
|
+
loopback?: "docker-host";
|
|
13
|
+
}
|
|
14
|
+
export type OutboundResult = {
|
|
15
|
+
kind: "response";
|
|
16
|
+
status: number;
|
|
17
|
+
headers: IncomingHttpHeaders;
|
|
18
|
+
body: Buffer;
|
|
19
|
+
}
|
|
20
|
+
/** The answer was larger than `maxResponseBytes`. */
|
|
21
|
+
| {
|
|
22
|
+
kind: "too_large";
|
|
23
|
+
status: number;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "not_sent";
|
|
26
|
+
code: string;
|
|
27
|
+
message: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "lost";
|
|
30
|
+
code: string;
|
|
31
|
+
message: string;
|
|
32
|
+
};
|
|
33
|
+
/** The largest answer read from an endpoint. */
|
|
34
|
+
export declare const MAX_RESPONSE_BYTES: number;
|
|
35
|
+
/** True when `address` is private, loopback, link-local or unspecified (IPv4-mapped included). */
|
|
36
|
+
export declare function isPrivateAddress(address: string): boolean;
|
|
37
|
+
/** Why the Host refuses `url`, or `undefined` when it may be called. */
|
|
38
|
+
export declare function refusal(url: URL, policy: OutboundPolicy): string | undefined;
|
|
39
|
+
/** POSTs `body` to `url` under `policy`. Never throws; `signal` aborts the request. */
|
|
40
|
+
export declare function post(target: string, body: string, headers: Record<string, string>, options: {
|
|
41
|
+
policy: OutboundPolicy;
|
|
42
|
+
signal: AbortSignal;
|
|
43
|
+
maxResponseBytes?: number;
|
|
44
|
+
}): Promise<OutboundResult>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Runtime's requests to Action endpoints (design: Action endpoints §8.3): one POST, no
|
|
3
|
+
* redirects, a bounded answer, and the Host's address policy checked on the address actually
|
|
4
|
+
* connected to, so a DNS answer cannot steer a delivery somewhere the Host forbids.
|
|
5
|
+
*
|
|
6
|
+
* The result says whether anything reached the endpoint. A request that failed before its body
|
|
7
|
+
* was flushed (refused connection, unknown host, TLS failure, a refused address) was `not_sent`
|
|
8
|
+
* and is safe to send again, even for a tool; one that failed after (reset, timeout) is `lost`.
|
|
9
|
+
*/
|
|
10
|
+
import { lookup as dnsLookup } from "node:dns";
|
|
11
|
+
import { request as httpRequest } from "node:http";
|
|
12
|
+
import { request as httpsRequest } from "node:https";
|
|
13
|
+
import { BlockList, isIP } from "node:net";
|
|
14
|
+
/** The largest answer read from an endpoint. */
|
|
15
|
+
export const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
16
|
+
const PRIVATE = new BlockList();
|
|
17
|
+
for (const [network, prefix] of [
|
|
18
|
+
["0.0.0.0", 8],
|
|
19
|
+
["10.0.0.0", 8],
|
|
20
|
+
["100.64.0.0", 10],
|
|
21
|
+
["127.0.0.0", 8],
|
|
22
|
+
["169.254.0.0", 16],
|
|
23
|
+
["172.16.0.0", 12],
|
|
24
|
+
["192.168.0.0", 16],
|
|
25
|
+
])
|
|
26
|
+
PRIVATE.addSubnet(network, prefix, "ipv4");
|
|
27
|
+
for (const [network, prefix] of [
|
|
28
|
+
["::", 128],
|
|
29
|
+
["::1", 128],
|
|
30
|
+
["fc00::", 7],
|
|
31
|
+
["fe80::", 10],
|
|
32
|
+
])
|
|
33
|
+
PRIVATE.addSubnet(network, prefix, "ipv6");
|
|
34
|
+
/** True when `address` is private, loopback, link-local or unspecified (IPv4-mapped included). */
|
|
35
|
+
export function isPrivateAddress(address) {
|
|
36
|
+
const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(address);
|
|
37
|
+
if (mapped)
|
|
38
|
+
return PRIVATE.check(mapped[1], "ipv4");
|
|
39
|
+
const family = isIP(address);
|
|
40
|
+
if (family === 0)
|
|
41
|
+
return false;
|
|
42
|
+
return PRIVATE.check(address, family === 4 ? "ipv4" : "ipv6");
|
|
43
|
+
}
|
|
44
|
+
class RefusedAddress extends Error {
|
|
45
|
+
code = "ENDPOINT_ADDRESS_REFUSED";
|
|
46
|
+
}
|
|
47
|
+
/** Why the Host refuses `url`, or `undefined` when it may be called. */
|
|
48
|
+
export function refusal(url, policy) {
|
|
49
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && policy.allowHttp !== false))
|
|
50
|
+
return `This Runtime calls only ${policy.allowHttp === false ? "https" : "http and https"} endpoints`;
|
|
51
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
52
|
+
if (policy.privateAddresses === "refuse" && isIP(host) && isPrivateAddress(host))
|
|
53
|
+
return "This Runtime does not call private addresses; expose the endpoint through a public URL (a tunnel, for a local app)";
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/** POSTs `body` to `url` under `policy`. Never throws; `signal` aborts the request. */
|
|
57
|
+
export async function post(target, body, headers, options) {
|
|
58
|
+
const { policy, signal } = options;
|
|
59
|
+
const limit = options.maxResponseBytes ?? MAX_RESPONSE_BYTES;
|
|
60
|
+
const url = new URL(target);
|
|
61
|
+
const refused = refusal(url, policy);
|
|
62
|
+
if (refused)
|
|
63
|
+
return { kind: "not_sent", code: "ENDPOINT_ADDRESS_REFUSED", message: refused };
|
|
64
|
+
const originalHost = url.host;
|
|
65
|
+
if (policy.loopback === "docker-host" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))
|
|
66
|
+
url.hostname = "host.docker.internal";
|
|
67
|
+
const lookup = (hostname, lookupOptions, callback) => dnsLookup(hostname, { ...lookupOptions, all: true }, (error, addresses) => {
|
|
68
|
+
if (error)
|
|
69
|
+
return callback(error, []);
|
|
70
|
+
const allowed = policy.privateAddresses === "refuse"
|
|
71
|
+
? addresses.filter((a) => !isPrivateAddress(a.address))
|
|
72
|
+
: addresses;
|
|
73
|
+
if (allowed.length === 0)
|
|
74
|
+
return callback(new RefusedAddress(`${hostname} resolves only to private addresses, which this Runtime does not call`), []);
|
|
75
|
+
if (lookupOptions.all)
|
|
76
|
+
return callback(null, allowed);
|
|
77
|
+
return callback(null, allowed[0].address, allowed[0].family);
|
|
78
|
+
});
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
let sent = false;
|
|
81
|
+
let settled = false;
|
|
82
|
+
const finish = (result) => {
|
|
83
|
+
if (settled)
|
|
84
|
+
return;
|
|
85
|
+
settled = true;
|
|
86
|
+
resolve(result);
|
|
87
|
+
};
|
|
88
|
+
const failed = (error) => finish({
|
|
89
|
+
kind: sent ? "lost" : "not_sent",
|
|
90
|
+
code: signal.aborted ? "ABORTED" : (error.code ?? error.name),
|
|
91
|
+
message: signal.aborted ? "The delivery was aborted" : error.message,
|
|
92
|
+
});
|
|
93
|
+
const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(url, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: {
|
|
96
|
+
...headers,
|
|
97
|
+
host: originalHost,
|
|
98
|
+
"content-type": "application/json",
|
|
99
|
+
"content-length": String(Buffer.byteLength(body)),
|
|
100
|
+
},
|
|
101
|
+
lookup: lookup,
|
|
102
|
+
signal,
|
|
103
|
+
}, (response) => {
|
|
104
|
+
const status = response.statusCode ?? 0;
|
|
105
|
+
const chunks = [];
|
|
106
|
+
let size = 0;
|
|
107
|
+
response.on("data", (chunk) => {
|
|
108
|
+
size += chunk.length;
|
|
109
|
+
if (size > limit) {
|
|
110
|
+
// Settle first: destroying the answer reports an error of its own.
|
|
111
|
+
finish({ kind: "too_large", status });
|
|
112
|
+
response.destroy();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
chunks.push(chunk);
|
|
116
|
+
});
|
|
117
|
+
response.on("end", () => finish({ kind: "response", status, headers: response.headers, body: Buffer.concat(chunks) }));
|
|
118
|
+
response.on("error", failed);
|
|
119
|
+
response.on("aborted", () => failed(Object.assign(new Error("The answer was cut off"), { code: "ECONNRESET" })));
|
|
120
|
+
});
|
|
121
|
+
request.on("finish", () => {
|
|
122
|
+
sent = true;
|
|
123
|
+
});
|
|
124
|
+
request.on("error", failed);
|
|
125
|
+
request.end(body);
|
|
126
|
+
});
|
|
127
|
+
}
|
package/dist/tenant/pool.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/** Run async work over items with a fixed concurrency limit. */
|
|
2
|
-
export declare function mapPool<T, R>(items: readonly T[], concurrency: number, worker: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
3
1
|
export declare class TimeoutError extends Error {
|
|
4
2
|
constructor(message?: string);
|
|
5
3
|
}
|
package/dist/tenant/pool.js
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/** Run async work over items with a fixed concurrency limit. */
|
|
2
|
-
export async function mapPool(items, concurrency, worker) {
|
|
3
|
-
if (items.length === 0)
|
|
4
|
-
return [];
|
|
5
|
-
const limit = Math.max(1, Math.min(concurrency, items.length));
|
|
6
|
-
const results = new Array(items.length);
|
|
7
|
-
let next = 0;
|
|
8
|
-
async function run() {
|
|
9
|
-
for (;;) {
|
|
10
|
-
const index = next++;
|
|
11
|
-
if (index >= items.length)
|
|
12
|
-
return;
|
|
13
|
-
results[index] = await worker(items[index], index);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
await Promise.all(Array.from({ length: limit }, () => run()));
|
|
17
|
-
return results;
|
|
18
|
-
}
|
|
19
1
|
export class TimeoutError extends Error {
|
|
20
2
|
constructor(message = "operation timed out") {
|
|
21
3
|
super(message);
|
package/dist/tenant/reset.d.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import type { ResetScope, SessionStore } from "../store/types.js";
|
|
2
|
-
import type { ExecutorRegistry } from "../core/executors.js";
|
|
3
2
|
import type { SandboxManager } from "../sandbox/manager.js";
|
|
4
3
|
import type { TenantPaths } from "./types.js";
|
|
5
4
|
export type { ResetScope } from "../store/types.js";
|
|
6
5
|
export interface ResetTenantContext {
|
|
7
6
|
store: SessionStore;
|
|
8
|
-
registry: ExecutorRegistry;
|
|
9
7
|
sandbox: SandboxManager;
|
|
10
8
|
paths: TenantPaths;
|
|
11
9
|
/** Clear in-memory session observers after the store wipe. */
|
|
12
10
|
clearSessionState: () => void;
|
|
13
|
-
/** Clear executor SSE streams when registrations are removed. */
|
|
14
|
-
clearExecutorStreams: () => void;
|
|
15
11
|
}
|
|
16
12
|
/**
|
|
17
13
|
* Reset Tenant durable state for `scope` (A17).
|
package/dist/tenant/reset.js
CHANGED
|
@@ -42,13 +42,8 @@ export async function resetTenant(ctx, scope) {
|
|
|
42
42
|
await ctx.sandbox.reconcile(() => false);
|
|
43
43
|
}
|
|
44
44
|
await ctx.store.tx((t) => t.reset(scope));
|
|
45
|
-
if (clearAll)
|
|
46
|
-
for (const row of ctx.registry.list())
|
|
47
|
-
ctx.registry.remove(row.agentId);
|
|
48
45
|
if (clearSessions)
|
|
49
46
|
ctx.clearSessionState();
|
|
50
|
-
if (clearAll)
|
|
51
|
-
ctx.clearExecutorStreams();
|
|
52
47
|
if (clearSandboxes)
|
|
53
48
|
replaceDirectory(ctx.paths.sandboxes);
|
|
54
49
|
if (clearAll) {
|
package/dist/tenant/runtime.d.ts
CHANGED
|
@@ -1,25 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Composition root of an open Tenant: validates the config, opens the Session Store and the
|
|
3
|
-
* vault key, builds the services and the `TenantContext`, wires the seams, registers the
|
|
4
|
-
* Tenant's Worker handlers and arms its sweep, and implements `TenantHandle` (handle,
|
|
5
|
-
* summary, drain, close) by delegating to the Tenant modules.
|
|
6
|
-
*
|
|
7
|
-
* Seams wired here: `wireStreams()` connects the store's commits to Durable Streams (the
|
|
8
|
-
* relay, and the history, SSE, work and control readers); `wake` goes to the
|
|
9
|
-
* `DurableExecution`, whose handlers (`worker.ts`) call `advance` and `sweep`; `abortLocal`
|
|
10
|
-
* aborts an advance running on this process. The sweep also drains the outbox.
|
|
11
|
-
*
|
|
12
|
-
* Execution: the Host passes one `TenantExecution` for every Tenant it opens
|
|
13
|
-
* (`TenantOpenHooks.execution`); without one, the Tenant runs its own in-process
|
|
14
|
-
* `MemoryExecution`. No lock file: several processes may open a Tenant, and ownership of each
|
|
15
|
-
* session (§10.6) keeps its advances apart.
|
|
16
|
-
*/
|
|
17
|
-
import type { ServerResponse, IncomingMessage } from "node:http";
|
|
18
1
|
import type { TenantEnvelope } from "@nylorun/core/contracts";
|
|
19
2
|
import { type ModelProvider } from "../core/provider.js";
|
|
20
3
|
import { type AuthorizeResult } from "../vault/service.js";
|
|
21
4
|
import type { DurableStreams } from "../streams/types.js";
|
|
22
|
-
import type { OpenedTenant, TenantConfig, TenantHandle, TenantSummary } from "./types.js";
|
|
5
|
+
import type { NodeBindings, OpenedTenant, TenantConfig, TenantHandle, TenantSummary } from "./types.js";
|
|
23
6
|
import { type StreamsStatus } from "./streams.js";
|
|
24
7
|
import { type TenantExecution, type TenantWorker } from "./worker.js";
|
|
25
8
|
/** TENANTS-CCR: test/injection hooks until TenantConfig gains them. */
|
|
@@ -42,6 +25,13 @@ export type TenantOpenHooks = {
|
|
|
42
25
|
* survive a restart (tests, and a Host without S2).
|
|
43
26
|
*/
|
|
44
27
|
streams?: DurableStreams;
|
|
28
|
+
/**
|
|
29
|
+
* The Host's stream relay feeds `streams` from the record for every Tenant. Without it the
|
|
30
|
+
* Tenant relays its own commits.
|
|
31
|
+
*/
|
|
32
|
+
hostRelay?: boolean;
|
|
33
|
+
/** How long a retired stream basin is kept after a reset (tests). Default 60 s. */
|
|
34
|
+
retireGraceMs?: number;
|
|
45
35
|
} & OpenedTenant;
|
|
46
36
|
export declare class TenantRuntime implements TenantHandle {
|
|
47
37
|
private readonly ctx;
|
|
@@ -53,7 +43,7 @@ export declare class TenantRuntime implements TenantHandle {
|
|
|
53
43
|
private readonly closeGraceMs;
|
|
54
44
|
private constructor();
|
|
55
45
|
static open(config: TenantConfig, hooks: TenantOpenHooks): Promise<TenantRuntime>;
|
|
56
|
-
|
|
46
|
+
fetch(request: Request, node: NodeBindings): Promise<Response>;
|
|
57
47
|
authorize(sessionId: string, request: {
|
|
58
48
|
url: string;
|
|
59
49
|
serverName?: string;
|
|
@@ -67,7 +57,7 @@ export declare class TenantRuntime implements TenantHandle {
|
|
|
67
57
|
abortLocal(sessionId: string): void;
|
|
68
58
|
/**
|
|
69
59
|
* The Durable Streams seam's status for Tenant status and readiness: S2 reachability, the
|
|
70
|
-
* basin,
|
|
60
|
+
* basin, its generation and the Tenant's relay (`streamsStatus` in `tenant/streams.ts`).
|
|
71
61
|
*/
|
|
72
62
|
streamsStatus(): Promise<StreamsStatus>;
|
|
73
63
|
/** Adds a callback to the Tenant sweep. Returns a function that removes it. */
|