@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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant signing keys for subject tokens. Each key is an ES256 pair: the public JWK is stored
|
|
3
|
+
* as is, the PKCS#8 private key is sealed with the vault KEK (AAD bound to the Tenant and key
|
|
4
|
+
* id), like a credential.
|
|
5
|
+
*
|
|
6
|
+
* States: `standby` (published, not used yet), `current` (signs), `previous` (verifies only),
|
|
7
|
+
* `revoked`. The first use creates `current` and `standby`. Rotating moves standby → current →
|
|
8
|
+
* previous → revoked and makes a new standby; it is refused while the key it would revoke may
|
|
9
|
+
* still verify live tokens, so a rotation never signs anyone out (`force` skips that guard).
|
|
10
|
+
*
|
|
11
|
+
* Key states are read from the database on every use, so a revocation applies at once on
|
|
12
|
+
* every node. Only imported key objects are cached: public keys by id (immutable) and the
|
|
13
|
+
* current private key.
|
|
14
|
+
*/
|
|
15
|
+
import { importJWK } from "jose";
|
|
16
|
+
import type { PublicJwk, SigningKeyView } from "@nylorun/core/contracts";
|
|
17
|
+
import type { SigningKeyRow, Tx } from "../store/types.js";
|
|
18
|
+
type KeyLike = Awaited<ReturnType<typeof importJWK>>;
|
|
19
|
+
export interface SigningKeysOptions {
|
|
20
|
+
tenantId: string;
|
|
21
|
+
/** The vault KEK, created on first use. Resolve it before opening a transaction. */
|
|
22
|
+
kek(): Buffer;
|
|
23
|
+
}
|
|
24
|
+
export interface CurrentKey {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly key: KeyLike;
|
|
27
|
+
}
|
|
28
|
+
export declare class SigningKeys {
|
|
29
|
+
private readonly options;
|
|
30
|
+
private readonly publicKeys;
|
|
31
|
+
private current;
|
|
32
|
+
constructor(options: SigningKeysOptions);
|
|
33
|
+
kek(): Buffer;
|
|
34
|
+
private aad;
|
|
35
|
+
/** A new sealed key row in `state`. In-memory crypto only; safe inside a transaction. */
|
|
36
|
+
private create;
|
|
37
|
+
/** The current key row, creating `current` and `standby` if the Tenant has none. */
|
|
38
|
+
ensure(t: Tx, kek: Buffer): Promise<SigningKeyRow>;
|
|
39
|
+
/** The private key of `row` (the current key), imported once per key id. */
|
|
40
|
+
privateKey(row: SigningKeyRow, kek: Buffer): Promise<CurrentKey>;
|
|
41
|
+
/** The public key of `row`, imported once per key id. */
|
|
42
|
+
publicKey(row: SigningKeyRow): Promise<KeyLike>;
|
|
43
|
+
/**
|
|
44
|
+
* Rotates: previous → revoked, current → previous, standby → current, new standby. Refused
|
|
45
|
+
* with 409 while the previous key may still verify live tokens (retired less than the
|
|
46
|
+
* longest token lifetime, `maxTtlSeconds` or a delivery token's, plus a minute ago), unless
|
|
47
|
+
* `force`.
|
|
48
|
+
*/
|
|
49
|
+
rotate(t: Tx, kek: Buffer, maxTtlSeconds: number, force: boolean): Promise<SigningKeyRow[]>;
|
|
50
|
+
/** Revokes a `previous` or `standby` key. The current key must be rotated first. */
|
|
51
|
+
revoke(t: Tx, id: string): Promise<SigningKeyRow>;
|
|
52
|
+
}
|
|
53
|
+
export declare function publicJwk(row: SigningKeyRow): PublicJwk;
|
|
54
|
+
export declare function signingKeyView(row: SigningKeyRow): SigningKeyView;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant signing keys for subject tokens. Each key is an ES256 pair: the public JWK is stored
|
|
3
|
+
* as is, the PKCS#8 private key is sealed with the vault KEK (AAD bound to the Tenant and key
|
|
4
|
+
* id), like a credential.
|
|
5
|
+
*
|
|
6
|
+
* States: `standby` (published, not used yet), `current` (signs), `previous` (verifies only),
|
|
7
|
+
* `revoked`. The first use creates `current` and `standby`. Rotating moves standby → current →
|
|
8
|
+
* previous → revoked and makes a new standby; it is refused while the key it would revoke may
|
|
9
|
+
* still verify live tokens, so a rotation never signs anyone out (`force` skips that guard).
|
|
10
|
+
*
|
|
11
|
+
* Key states are read from the database on every use, so a revocation applies at once on
|
|
12
|
+
* every node. Only imported key objects are cached: public keys by id (immutable) and the
|
|
13
|
+
* current private key.
|
|
14
|
+
*/
|
|
15
|
+
import { exportJWK, exportPKCS8, generateKeyPair, importJWK, importPKCS8, } from "jose";
|
|
16
|
+
import { newSigningKeyId, SIGNING_KEY_ID_PATTERN, } from "@nylorun/core/compatibility";
|
|
17
|
+
import { DELIVERY_TOKEN_MAX_TTL_SECONDS } from "@nylorun/core/contracts";
|
|
18
|
+
import { decryptSecret, encryptSecret } from "../vault/crypto.js";
|
|
19
|
+
import { fail } from "./http.js";
|
|
20
|
+
const ALG = "ES256";
|
|
21
|
+
/** Grace added to the longest token lifetime before a previous key may be revoked. */
|
|
22
|
+
const ROTATION_GRACE_MS = 60_000;
|
|
23
|
+
export class SigningKeys {
|
|
24
|
+
options;
|
|
25
|
+
publicKeys = new Map();
|
|
26
|
+
current;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.options = options;
|
|
29
|
+
}
|
|
30
|
+
kek() {
|
|
31
|
+
return this.options.kek();
|
|
32
|
+
}
|
|
33
|
+
aad(id) {
|
|
34
|
+
return Buffer.from(`nylorun.signing-key:${this.options.tenantId}:${id}`);
|
|
35
|
+
}
|
|
36
|
+
/** A new sealed key row in `state`. In-memory crypto only; safe inside a transaction. */
|
|
37
|
+
async create(kek, state, now) {
|
|
38
|
+
const id = newSigningKeyId();
|
|
39
|
+
const pair = await generateKeyPair(ALG, { extractable: true });
|
|
40
|
+
const jwk = await exportJWK(pair.publicKey);
|
|
41
|
+
const pkcs8 = await exportPKCS8(pair.privateKey);
|
|
42
|
+
const sealed = encryptSecret(kek, this.aad(id), Buffer.from(pkcs8, "utf8"));
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
state,
|
|
46
|
+
alg: ALG,
|
|
47
|
+
publicJwk: JSON.stringify({
|
|
48
|
+
kty: jwk.kty,
|
|
49
|
+
crv: jwk.crv,
|
|
50
|
+
x: jwk.x,
|
|
51
|
+
y: jwk.y,
|
|
52
|
+
}),
|
|
53
|
+
createdAt: now,
|
|
54
|
+
activatedAt: state === "current" ? now : null,
|
|
55
|
+
retiredAt: null,
|
|
56
|
+
revokedAt: null,
|
|
57
|
+
kekId: sealed.kekId,
|
|
58
|
+
nonce: sealed.nonce,
|
|
59
|
+
ciphertext: sealed.ciphertext,
|
|
60
|
+
wrappedDek: sealed.wrappedDek,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** The current key row, creating `current` and `standby` if the Tenant has none. */
|
|
64
|
+
async ensure(t, kek) {
|
|
65
|
+
let live = await t.signingKeys(["standby", "current"]);
|
|
66
|
+
if (live.length < 2) {
|
|
67
|
+
// Concurrent first uses would both create keys: lock, then read what the winner made.
|
|
68
|
+
await t.lockSigningKeys();
|
|
69
|
+
live = await t.signingKeys(["standby", "current"]);
|
|
70
|
+
}
|
|
71
|
+
const now = new Date().toISOString();
|
|
72
|
+
let current = live.find((row) => row.state === "current");
|
|
73
|
+
if (!current) {
|
|
74
|
+
const standby = live.find((row) => row.state === "standby");
|
|
75
|
+
if (standby) {
|
|
76
|
+
await t.setSigningKeyState(standby.id, "standby", "current", now);
|
|
77
|
+
current = { ...standby, state: "current", activatedAt: now };
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
current = await this.create(kek, "current", now);
|
|
81
|
+
await t.insertSigningKey(current);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!live.some((row) => row.state === "standby" && row.id !== current.id))
|
|
85
|
+
await t.insertSigningKey(await this.create(kek, "standby", now));
|
|
86
|
+
return current;
|
|
87
|
+
}
|
|
88
|
+
/** The private key of `row` (the current key), imported once per key id. */
|
|
89
|
+
async privateKey(row, kek) {
|
|
90
|
+
if (this.current?.id === row.id)
|
|
91
|
+
return this.current;
|
|
92
|
+
const pkcs8 = decryptSecret(kek, this.aad(row.id), Buffer.from(row.nonce), Buffer.from(row.ciphertext), Buffer.from(row.wrappedDek), row.kekId).toString("utf8");
|
|
93
|
+
const key = await importPKCS8(pkcs8, ALG);
|
|
94
|
+
this.current = { id: row.id, key };
|
|
95
|
+
return this.current;
|
|
96
|
+
}
|
|
97
|
+
/** The public key of `row`, imported once per key id. */
|
|
98
|
+
async publicKey(row) {
|
|
99
|
+
const cached = this.publicKeys.get(row.id);
|
|
100
|
+
if (cached)
|
|
101
|
+
return cached;
|
|
102
|
+
const key = await importJWK({ ...JSON.parse(row.publicJwk), alg: ALG }, ALG);
|
|
103
|
+
this.publicKeys.set(row.id, key);
|
|
104
|
+
return key;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Rotates: previous → revoked, current → previous, standby → current, new standby. Refused
|
|
108
|
+
* with 409 while the previous key may still verify live tokens (retired less than the
|
|
109
|
+
* longest token lifetime, `maxTtlSeconds` or a delivery token's, plus a minute ago), unless
|
|
110
|
+
* `force`.
|
|
111
|
+
*/
|
|
112
|
+
async rotate(t, kek, maxTtlSeconds, force) {
|
|
113
|
+
await t.lockSigningKeys();
|
|
114
|
+
await this.ensure(t, kek);
|
|
115
|
+
const now = new Date();
|
|
116
|
+
const at = now.toISOString();
|
|
117
|
+
const [previous] = await t.signingKeys(["previous"]);
|
|
118
|
+
if (previous && !force) {
|
|
119
|
+
const retired = Date.parse(previous.retiredAt ?? previous.createdAt);
|
|
120
|
+
// Delivery tokens may outlive the policy's subject tokens.
|
|
121
|
+
const longest = Math.max(maxTtlSeconds, DELIVERY_TOKEN_MAX_TTL_SECONDS);
|
|
122
|
+
const until = retired + longest * 1000 + ROTATION_GRACE_MS;
|
|
123
|
+
if (until > now.getTime())
|
|
124
|
+
fail(409, "The previous key may still verify live tokens", {
|
|
125
|
+
code: "request_rejected",
|
|
126
|
+
details: { retryAfterSeconds: Math.ceil((until - now.getTime()) / 1000) },
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (previous)
|
|
130
|
+
await t.setSigningKeyState(previous.id, "previous", "revoked", at);
|
|
131
|
+
const [current] = await t.signingKeys(["current"]);
|
|
132
|
+
const [standby] = await t.signingKeys(["standby"]);
|
|
133
|
+
if (current)
|
|
134
|
+
await t.setSigningKeyState(current.id, "current", "previous", at);
|
|
135
|
+
if (standby)
|
|
136
|
+
await t.setSigningKeyState(standby.id, "standby", "current", at);
|
|
137
|
+
else
|
|
138
|
+
await t.insertSigningKey(await this.create(kek, "current", at));
|
|
139
|
+
await t.insertSigningKey(await this.create(kek, "standby", at));
|
|
140
|
+
return t.signingKeys();
|
|
141
|
+
}
|
|
142
|
+
/** Revokes a `previous` or `standby` key. The current key must be rotated first. */
|
|
143
|
+
async revoke(t, id) {
|
|
144
|
+
const row = SIGNING_KEY_ID_PATTERN.test(id)
|
|
145
|
+
? await t.signingKey(id)
|
|
146
|
+
: undefined;
|
|
147
|
+
if (!row)
|
|
148
|
+
return fail(404, "Signing key not found");
|
|
149
|
+
if (row.state === "revoked")
|
|
150
|
+
return row;
|
|
151
|
+
if (row.state === "current")
|
|
152
|
+
return fail(409, "Rotate before revoking the current key");
|
|
153
|
+
const at = new Date().toISOString();
|
|
154
|
+
await t.setSigningKeyState(row.id, row.state, "revoked", at);
|
|
155
|
+
return { ...row, state: "revoked", revokedAt: at };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export function publicJwk(row) {
|
|
159
|
+
return { ...JSON.parse(row.publicJwk), kid: row.id, alg: ALG, use: "sig" };
|
|
160
|
+
}
|
|
161
|
+
export function signingKeyView(row) {
|
|
162
|
+
return {
|
|
163
|
+
id: row.id,
|
|
164
|
+
state: row.state,
|
|
165
|
+
alg: row.alg,
|
|
166
|
+
publicKey: publicJwk(row),
|
|
167
|
+
createdAt: row.createdAt,
|
|
168
|
+
activatedAt: row.activatedAt,
|
|
169
|
+
retiredAt: row.retiredAt,
|
|
170
|
+
revokedAt: row.revokedAt,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Tx } from "../store/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Slim the completed model effects of a turn that ended (Model Calls §9): drop the prompt
|
|
4
|
+
* and the answer, which the transcript already holds, and keep the identity and status.
|
|
5
|
+
* Replay only ever reads the effects of a running segment. Tool, flow and uncertain rows
|
|
6
|
+
* are never touched: completed actions return their receipts, and status lists uncertain work.
|
|
7
|
+
*/
|
|
8
|
+
export declare function slimModelEffects(t: Tx, sessionId: string, turnId: string | null): Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slim the completed model effects of a turn that ended (Model Calls §9): drop the prompt
|
|
3
|
+
* and the answer, which the transcript already holds, and keep the identity and status.
|
|
4
|
+
* Replay only ever reads the effects of a running segment. Tool, flow and uncertain rows
|
|
5
|
+
* are never touched: completed actions return their receipts, and status lists uncertain work.
|
|
6
|
+
*/
|
|
7
|
+
export async function slimModelEffects(t, sessionId, turnId) {
|
|
8
|
+
if (!turnId)
|
|
9
|
+
return;
|
|
10
|
+
for (const effect of await t.effectsForTurn(sessionId, turnId, ["completed"])) {
|
|
11
|
+
if (effect.request?.kind !== "model" || effect.slimmed)
|
|
12
|
+
continue;
|
|
13
|
+
const { input: _input, ...request } = effect.request;
|
|
14
|
+
await t.put("effects", request.effectId, {
|
|
15
|
+
...effect,
|
|
16
|
+
request,
|
|
17
|
+
outcome: {},
|
|
18
|
+
slimmed: true,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/tenant/status.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { SeedTenantConfigRequest, SeedTenantConfigResponse, TenantStatus } from "@nylorun/core/contracts";
|
|
2
2
|
import type { SessionStore } from "../store/types.js";
|
|
3
|
-
import type { ExecutorRegistry } from "../core/executors.js";
|
|
4
3
|
import type { VaultService } from "../vault/service.js";
|
|
5
4
|
import type { SandboxManager } from "../sandbox/manager.js";
|
|
6
5
|
import type { TenantConfig } from "./types.js";
|
|
@@ -11,12 +10,10 @@ export interface TenantStatusContext {
|
|
|
11
10
|
envelope: TenantEnvelope;
|
|
12
11
|
config: TenantConfig;
|
|
13
12
|
store: SessionStore;
|
|
14
|
-
registry: ExecutorRegistry;
|
|
15
13
|
vault: VaultService;
|
|
16
14
|
sandbox: SandboxManager;
|
|
17
15
|
closing: boolean;
|
|
18
16
|
modelConfigured: boolean;
|
|
19
|
-
executorStreams: Map<string, Set<unknown>>;
|
|
20
17
|
/**
|
|
21
18
|
* This Tenant's execution invocations that need an operator (`TenantExecution`). Absent
|
|
22
19
|
* when the execution cannot report them.
|
package/dist/tenant/status.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FIXTURE_MODEL_SETTING, seedFixtureModel } from "./model-setting.js";
|
|
2
|
+
import { SANDBOX_CONFIG_SETTING, writeSandboxConfig } from "../sandbox/tenant-config.js";
|
|
2
3
|
/** How long status waits for the execution to list stuck invocations. */
|
|
3
4
|
const STUCK_TIMEOUT_MS = 5000;
|
|
4
5
|
/** `TenantStatus.execution`: paused and backing-off invocations, or why they are unknown. */
|
|
@@ -54,17 +55,13 @@ export async function buildTenantStatus(ctx) {
|
|
|
54
55
|
const definitionIds = new Set(definitions
|
|
55
56
|
.map((d) => d.manifest?.id)
|
|
56
57
|
.filter((id) => typeof id === "string"));
|
|
57
|
-
const
|
|
58
|
-
const agentIds = new Set([...definitionIds, ...
|
|
59
|
-
const agents = [...agentIds].sort().map((agentId) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
connected: !!record &&
|
|
65
|
-
(ctx.executorStreams.get(record.tokenHash)?.size ?? 0) > 0,
|
|
66
|
-
};
|
|
67
|
-
});
|
|
58
|
+
const endpointIds = new Set((await ctx.store.tx((t) => t.listEndpoints())).map((e) => e.agentId));
|
|
59
|
+
const agentIds = new Set([...definitionIds, ...endpointIds]);
|
|
60
|
+
const agents = [...agentIds].sort().map((agentId) => ({
|
|
61
|
+
agentId,
|
|
62
|
+
registered: definitionIds.has(agentId),
|
|
63
|
+
endpoint: endpointIds.has(agentId),
|
|
64
|
+
}));
|
|
68
65
|
return {
|
|
69
66
|
tenant: ctx.envelope,
|
|
70
67
|
path: ctx.config.paths.root,
|
|
@@ -72,7 +69,7 @@ export async function buildTenantStatus(ctx) {
|
|
|
72
69
|
store: health.schemaVersion > 0,
|
|
73
70
|
scheduler: !ctx.closing,
|
|
74
71
|
model: ctx.modelConfigured || modelView.configured,
|
|
75
|
-
|
|
72
|
+
endpoints: true,
|
|
76
73
|
schema: health.ok,
|
|
77
74
|
},
|
|
78
75
|
model: modelView,
|
|
@@ -108,6 +105,16 @@ export async function seedTenantConfig(ctx, body) {
|
|
|
108
105
|
});
|
|
109
106
|
(inserted ? applied : kept).push("sandbox.backend");
|
|
110
107
|
}
|
|
108
|
+
const sandboxConfig = body.sandbox?.config;
|
|
109
|
+
if (sandboxConfig) {
|
|
110
|
+
const inserted = await ctx.store.tx(async (t) => {
|
|
111
|
+
if ((await t.getSetting(SANDBOX_CONFIG_SETTING)) !== undefined)
|
|
112
|
+
return false;
|
|
113
|
+
await writeSandboxConfig(t, sandboxConfig);
|
|
114
|
+
return true;
|
|
115
|
+
});
|
|
116
|
+
(inserted ? applied : kept).push(SANDBOX_CONFIG_SETTING);
|
|
117
|
+
}
|
|
111
118
|
if (body.fixtureModel) {
|
|
112
119
|
const inserted = await ctx.store.tx((t) => seedFixtureModel(t));
|
|
113
120
|
(inserted ? applied : kept).push(FIXTURE_MODEL_SETTING);
|
package/dist/tenant/streams.d.ts
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Durable Streams seam of an open Tenant (Durable Streams §7–§9), wired by one
|
|
3
|
+
* `wireStreams(ctx, …)` call in the composition root:
|
|
4
|
+
*
|
|
5
|
+
* - **Record.** Every event is written to the record in its transaction (`Tx.event`). The
|
|
6
|
+
* stream relay (`streams/relay/`) feeds the Tenant's basin from it. On a Host with S2, one
|
|
7
|
+
* process-wide relay reads every Tenant's record over logical replication (`host/main.ts`);
|
|
8
|
+
* otherwise (tests, a Host without S2) this module runs a relay of the Tenant's own commits,
|
|
9
|
+
* which reconciles the record with the streams when it starts.
|
|
10
|
+
* - **Basin generations.** The Tenant's session streams live in its current basin generation
|
|
11
|
+
* (`streams/basin.ts`). A reset moves it to the next one (`tenantReset`): the old basin gets
|
|
12
|
+
* a `sessions.reset` signal, every process moves its readers, and the old basin is deleted
|
|
13
|
+
* after a grace period (again when the Tenant opens, until it is gone).
|
|
14
|
+
* - **Basin.** The current basin is created with the Tenant (`createTenantStreams`) and
|
|
15
|
+
* checked when it opens. A missing basin (S2 down at creation, a crash in between) is
|
|
16
|
+
* repaired on first use: a failed `ensureTenant` retries in the background with backoff,
|
|
17
|
+
* and `streamsStatus` reports the basin's state.
|
|
18
|
+
* - **Readers.** History and session SSE read the session's stream
|
|
19
|
+
* (`tenant/session-streams.ts`). This module runs one `tenant/control` reader per Tenant per
|
|
20
|
+
* process, on the current basin, which calls `ctx.abortLocal` for each `session.cancel`,
|
|
21
|
+
* checks the session streams for each `sessions.reset`, and ends a subject's streams for
|
|
22
|
+
* each `subject.revoked`.
|
|
23
|
+
*
|
|
24
|
+
* The caller passes the streams: the Host's S2 streams, or `MemoryStreams` for tests and a
|
|
25
|
+
* local development Host (not durable).
|
|
26
|
+
*/
|
|
1
27
|
import type { SessionStore } from "../store/types.js";
|
|
2
|
-
import { type
|
|
28
|
+
import { type StreamRelay, type StreamRelayStatus } from "../streams/relay/core.js";
|
|
3
29
|
import { type DurableStreams } from "../streams/types.js";
|
|
4
30
|
import type { TenantContext } from "./context.js";
|
|
5
31
|
export interface WireStreamsOptions {
|
|
@@ -8,10 +34,15 @@ export interface WireStreamsOptions {
|
|
|
8
34
|
streams: DurableStreams;
|
|
9
35
|
/** Close `streams` with the wiring (streams created for this Tenant alone). */
|
|
10
36
|
ownsStreams?: boolean;
|
|
11
|
-
/**
|
|
12
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The Host runs the stream relay for every Tenant (logical replication). Otherwise the
|
|
39
|
+
* Tenant relays its own commits.
|
|
40
|
+
*/
|
|
41
|
+
hostRelay?: boolean;
|
|
42
|
+
/** How long a retired basin is kept for readers still on it. Default 60 s. */
|
|
43
|
+
retireGraceMs?: number;
|
|
13
44
|
}
|
|
14
|
-
/** The state of the Tenant's basin as this process last saw it. */
|
|
45
|
+
/** The state of the Tenant's current basin as this process last saw it. */
|
|
15
46
|
export interface BasinStatus {
|
|
16
47
|
/** `ensureTenant` succeeded and no append has failed since. */
|
|
17
48
|
ready: boolean;
|
|
@@ -20,41 +51,27 @@ export interface BasinStatus {
|
|
|
20
51
|
/** The last failure's message, cleared once the basin is ready. */
|
|
21
52
|
lastError: string | null;
|
|
22
53
|
}
|
|
23
|
-
/**
|
|
24
|
-
export interface CollectResult {
|
|
25
|
-
/** Abandoned session streams deleted. */
|
|
26
|
-
deleted: number;
|
|
27
|
-
/** Session streams of existing sessions, kept. */
|
|
28
|
-
kept: number;
|
|
29
|
-
/** Deletions that failed; the collection stays pending. */
|
|
30
|
-
failed: number;
|
|
31
|
-
}
|
|
32
|
-
/** The handles `wireStreams` returns (also kept on `ctx.live.wiring`). */
|
|
54
|
+
/** The handles `wireStreams` returns (also kept on `ctx.sessionStreams.wiring`). */
|
|
33
55
|
export interface StreamsWiring {
|
|
34
56
|
readonly streams: DurableStreams;
|
|
35
|
-
|
|
36
|
-
|
|
57
|
+
/** The Tenant's own relay; undefined when the Host relays. */
|
|
58
|
+
readonly relay: StreamRelay | undefined;
|
|
59
|
+
/** The current basin's state (`streamsStatus`). */
|
|
37
60
|
basin(): BasinStatus;
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*/
|
|
44
|
-
collect(): Promise<CollectResult | undefined>;
|
|
45
|
-
/** Stops the readers, waits for in-flight relays, and closes what `wireStreams` created. */
|
|
61
|
+
/** Moves this process's readers and control reader to basin generation `generation`. */
|
|
62
|
+
moveTo(generation: number): void;
|
|
63
|
+
/** Deletes the basins of retired generations after the grace period. */
|
|
64
|
+
retire(generations: readonly number[]): void;
|
|
65
|
+
/** Stops the readers and the relay, and closes what `wireStreams` created. */
|
|
46
66
|
close(): Promise<void>;
|
|
47
67
|
}
|
|
48
|
-
/** The Tenant setting that holds a pending collection's token ("" when none is pending). */
|
|
49
|
-
export declare const COLLECT_SETTING = "streams.collect";
|
|
50
68
|
/**
|
|
51
|
-
* Wires the Tenant to Durable Streams:
|
|
52
|
-
*
|
|
53
|
-
*
|
|
69
|
+
* Wires the Tenant to Durable Streams: reads its basin generation, checks the basin, starts
|
|
70
|
+
* the Tenant's relay (unless the Host relays), the control reader and the retired basins'
|
|
71
|
+
* deletion, and records the handles on `ctx.sessionStreams.wiring`. Call it once, after
|
|
72
|
+
* `ctx` is built and before the Tenant serves requests.
|
|
54
73
|
*/
|
|
55
74
|
export declare function wireStreams(ctx: TenantContext, options: WireStreamsOptions): Promise<StreamsWiring>;
|
|
56
|
-
/** The Tenant sweep's outbox recovery: appends leftover rows, at most `limit`. */
|
|
57
|
-
export declare function drainOutbox(ctx: TenantContext, limit?: number): Promise<number>;
|
|
58
75
|
/**
|
|
59
76
|
* Appends `session.cancel` for `sessionId` to `tenant/control`, so the process running its
|
|
60
77
|
* advance aborts it. Call it from `t.afterCommit` after a cancel commits. A lost signal costs
|
|
@@ -63,49 +80,25 @@ export declare function drainOutbox(ctx: TenantContext, limit?: number): Promise
|
|
|
63
80
|
*/
|
|
64
81
|
export declare function signalSessionCancel(ctx: TenantContext, sessionId: string): void;
|
|
65
82
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* After a reset deleted sessions: tells every process with the Tenant open to check its
|
|
72
|
-
* session feeds (`sessions.reset`), and deletes the abandoned streams now, best effort.
|
|
73
|
-
* Failures are logged; the sweep retries the collection.
|
|
74
|
-
*/
|
|
75
|
-
export declare function sessionStreamsAbandoned(ctx: TenantContext): void;
|
|
76
|
-
/**
|
|
77
|
-
* Deletes the Tenant's session streams that no session uses any more: streams of deleted
|
|
78
|
-
* sessions and of earlier incarnations. Runs only while a collection is pending
|
|
79
|
-
* (`requestStreamCollection`), and clears it once every deletion succeeded, unless another
|
|
80
|
-
* reset requested a new one meanwhile.
|
|
81
|
-
*
|
|
82
|
-
* Streams are listed before their sessions are read, so a session created meanwhile keeps
|
|
83
|
-
* its stream: its row committed before the relay created the stream.
|
|
83
|
+
* After a reset deleted the Tenant's sessions and moved it to a new basin generation: tells
|
|
84
|
+
* every process with the Tenant open (`sessions.reset` on the old basin's control stream),
|
|
85
|
+
* moves this process's readers, and deletes the old basin after the grace period. Failures
|
|
86
|
+
* are logged; the periodic check and the next open finish the job.
|
|
84
87
|
*/
|
|
85
|
-
export declare function
|
|
88
|
+
export declare function tenantReset(ctx: TenantContext): Promise<void>;
|
|
86
89
|
/** Tenant status of the streams seam (for `GET /v1/tenant` and the Admin API). */
|
|
87
90
|
export interface StreamsStatus {
|
|
88
91
|
/** The streams' service answered a probe (`probeStreams`) within the timeout. */
|
|
89
92
|
reachable: boolean;
|
|
90
93
|
basin: BasinStatus;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
oldestAgeMs: number | null;
|
|
96
|
-
};
|
|
97
|
-
/** How far the relay is behind: the oldest unrelayed event's age, 0 when none is waiting. */
|
|
98
|
-
relayLagMs: number;
|
|
99
|
-
/** Abandoned session streams are waiting to be deleted. */
|
|
100
|
-
collectionPending: boolean;
|
|
94
|
+
/** The basin generation session streams are in. */
|
|
95
|
+
generation: number;
|
|
96
|
+
/** The Tenant's own relay; null when the Host's relay serves it. */
|
|
97
|
+
relay: StreamRelayStatus | null;
|
|
101
98
|
}
|
|
102
|
-
/**
|
|
103
|
-
* The streams seam's status for an open Tenant: S2 reachability, the basin, outbox depth and
|
|
104
|
-
* relay lag. Reads the Session Store and probes the streams (at most `probeTimeoutMs`).
|
|
105
|
-
*/
|
|
99
|
+
/** The streams seam's status for an open Tenant: S2 reachability, the basin and the relay. */
|
|
106
100
|
export declare function streamsStatus(ctx: TenantContext, options?: {
|
|
107
101
|
probeTimeoutMs?: number;
|
|
108
|
-
now?: () => number;
|
|
109
102
|
}): Promise<StreamsStatus>;
|
|
110
103
|
/**
|
|
111
104
|
* Creates a new Tenant's basin. Call it when the Tenant is created, after its schema commits;
|
|
@@ -116,8 +109,8 @@ export declare function createTenantStreams(streams: DurableStreams, tenantId: s
|
|
|
116
109
|
attempts?: number;
|
|
117
110
|
}): Promise<void>;
|
|
118
111
|
/**
|
|
119
|
-
* Deletes a Tenant
|
|
120
|
-
*
|
|
112
|
+
* Deletes every basin of a Tenant and every stream in them. Call it when the Tenant is
|
|
113
|
+
* deleted. Idempotent; retries transient failures, then throws.
|
|
121
114
|
*/
|
|
122
115
|
export declare function deleteTenantStreams(streams: DurableStreams, tenantId: string, options?: {
|
|
123
116
|
attempts?: number;
|