@nylorun/runtime 0.10.0-beta → 0.11.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 +46 -0
- package/README.md +53 -51
- package/dist/adapters/execution/restate.d.ts +130 -0
- package/dist/adapters/execution/restate.js +442 -0
- package/dist/adapters/sandbox/virtual.js +1 -1
- package/dist/adapters/streams/s2.d.ts +18 -0
- package/dist/adapters/streams/s2.js +361 -0
- package/dist/core/executors.d.ts +4 -1
- package/dist/core/executors.js +20 -1
- package/dist/core/flow-host.d.ts +93 -132
- package/dist/core/flow-host.js +282 -547
- package/dist/core/sandbox-routes.d.ts +8 -3
- package/dist/core/sandbox-routes.js +10 -10
- package/dist/execution/memory.d.ts +54 -0
- package/dist/execution/memory.js +217 -0
- package/dist/execution/types.d.ts +125 -0
- package/dist/execution/types.js +52 -0
- package/dist/host/config-for.d.ts +2 -1
- package/dist/host/config-for.js +3 -21
- package/dist/host/config.d.ts +0 -8
- package/dist/host/create-host.d.ts +36 -1
- package/dist/host/create-host.js +48 -24
- package/dist/host/execution.d.ts +61 -0
- package/dist/host/execution.js +69 -0
- package/dist/host/http.d.ts +4 -0
- package/dist/host/http.js +6 -0
- package/dist/host/index.d.ts +3 -1
- package/dist/host/index.js +2 -0
- package/dist/host/main.js +106 -47
- package/dist/host/stack-config.d.ts +72 -0
- package/dist/host/stack-config.js +193 -0
- package/dist/index.d.ts +1 -1
- package/dist/infra/database.d.ts +16 -0
- package/dist/infra/database.js +31 -0
- package/dist/infra/execution.d.ts +39 -0
- package/dist/infra/execution.js +68 -0
- package/dist/infra/index.d.ts +42 -0
- package/dist/infra/index.js +56 -0
- package/dist/infra/readiness.d.ts +33 -0
- package/dist/infra/readiness.js +53 -0
- package/dist/infra/streams.d.ts +17 -0
- package/dist/infra/streams.js +33 -0
- package/dist/infra/workers.d.ts +39 -0
- package/dist/infra/workers.js +31 -0
- package/dist/model/pi-model.d.ts +2 -2
- package/dist/model/pi-model.js +5 -5
- package/dist/sandbox/manager.d.ts +26 -8
- package/dist/sandbox/manager.js +95 -47
- package/dist/sandbox/select.d.ts +1 -1
- package/dist/sandbox/select.js +4 -5
- package/dist/sandbox/types.d.ts +2 -2
- package/dist/store/canonical.d.ts +5 -0
- package/dist/store/canonical.js +17 -0
- package/dist/store/cursor.d.ts +9 -0
- package/dist/store/cursor.js +18 -0
- package/dist/store/memory.d.ts +57 -0
- package/dist/store/memory.js +643 -0
- package/dist/store/ownership.d.ts +19 -0
- package/dist/store/ownership.js +33 -0
- package/dist/store/postgres/connect.d.ts +25 -0
- package/dist/store/postgres/connect.js +25 -0
- package/dist/store/postgres/locking.d.ts +16 -0
- package/dist/store/postgres/locking.js +22 -0
- package/dist/store/postgres/migrations/001_initial.d.ts +27 -0
- package/dist/store/postgres/migrations/001_initial.js +196 -0
- package/dist/store/postgres/migrations/index.d.ts +49 -0
- package/dist/store/postgres/migrations/index.js +75 -0
- package/dist/store/postgres/names.d.ts +11 -0
- package/dist/store/postgres/names.js +42 -0
- package/dist/store/postgres/store.d.ts +11 -0
- package/dist/store/postgres/store.js +790 -0
- package/dist/store/postgres/tenants.d.ts +90 -0
- package/dist/store/postgres/tenants.js +207 -0
- package/dist/store/types.d.ts +440 -0
- package/dist/store/types.js +10 -0
- package/dist/streams/basin.d.ts +23 -0
- package/dist/streams/basin.js +51 -0
- package/dist/streams/memory.d.ts +25 -0
- package/dist/streams/memory.js +124 -0
- package/dist/streams/relay.d.ts +37 -0
- package/dist/streams/relay.js +254 -0
- package/dist/streams/types.d.ts +115 -0
- package/dist/streams/types.js +86 -0
- package/dist/tenant/actions.d.ts +153 -0
- package/dist/tenant/actions.js +191 -0
- package/dist/tenant/advance.d.ts +15 -0
- package/dist/tenant/advance.js +439 -0
- package/dist/tenant/auth.d.ts +11 -0
- package/dist/tenant/auth.js +34 -0
- package/dist/tenant/commands.d.ts +3 -0
- package/dist/tenant/commands.js +384 -0
- package/dist/tenant/context.d.ts +140 -0
- package/dist/tenant/context.js +40 -0
- package/dist/tenant/effects.d.ts +20 -0
- package/dist/tenant/effects.js +539 -0
- package/dist/tenant/envelope.d.ts +0 -3
- package/dist/tenant/envelope.js +2 -34
- package/dist/tenant/ephemeral.d.ts +15 -4
- package/dist/tenant/ephemeral.js +106 -38
- package/dist/tenant/http.d.ts +32 -0
- package/dist/tenant/http.js +51 -0
- package/dist/tenant/live.d.ts +92 -0
- package/dist/tenant/live.js +290 -0
- package/dist/tenant/model-setting.d.ts +16 -0
- package/dist/tenant/model-setting.js +12 -0
- package/dist/tenant/module.d.ts +29 -8
- package/dist/tenant/module.js +162 -158
- package/dist/tenant/paths.d.ts +12 -8
- package/dist/tenant/paths.js +38 -12
- package/dist/tenant/principals.d.ts +9 -13
- package/dist/tenant/principals.js +31 -38
- package/dist/tenant/quarantine-error.d.ts +1 -1
- package/dist/tenant/quarantine-error.js +2 -8
- package/dist/tenant/quarantine.d.ts +0 -8
- package/dist/tenant/quarantine.js +8 -30
- package/dist/tenant/reset.d.ts +7 -6
- package/dist/tenant/reset.js +7 -51
- package/dist/tenant/routes-tenant.d.ts +12 -0
- package/dist/tenant/routes-tenant.js +126 -0
- package/dist/tenant/routes.d.ts +9 -0
- package/dist/tenant/routes.js +142 -0
- package/dist/tenant/runtime.d.ts +59 -58
- package/dist/tenant/runtime.js +243 -2201
- package/dist/tenant/scheduler.d.ts +25 -0
- package/dist/tenant/scheduler.js +84 -0
- package/dist/tenant/session.d.ts +60 -0
- package/dist/tenant/session.js +94 -0
- package/dist/tenant/sessions.d.ts +34 -0
- package/dist/tenant/sessions.js +149 -0
- package/dist/tenant/status.d.ts +13 -4
- package/dist/tenant/status.js +76 -41
- package/dist/tenant/store-memory.d.ts +9 -12
- package/dist/tenant/store-memory.js +79 -68
- package/dist/tenant/store-pg.d.ts +15 -0
- package/dist/tenant/store-pg.js +140 -0
- package/dist/tenant/streams.d.ts +126 -0
- package/dist/tenant/streams.js +419 -0
- package/dist/tenant/sweep.d.ts +15 -0
- package/dist/tenant/sweep.js +122 -0
- package/dist/tenant/types.d.ts +95 -15
- package/dist/tenant/types.js +23 -1
- package/dist/tenant/worker.d.ts +84 -0
- package/dist/tenant/worker.js +189 -0
- package/dist/vault/service.d.ts +37 -31
- package/dist/vault/service.js +439 -359
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -13
- package/dist/adapters/sandbox/microsandbox.d.ts +0 -2
- package/dist/adapters/sandbox/microsandbox.js +0 -238
- package/dist/core/store.d.ts +0 -32
- package/dist/core/store.js +0 -143
- package/dist/launcher/commands.d.ts +0 -24
- package/dist/launcher/commands.js +0 -260
- package/dist/launcher/ensure-config.d.ts +0 -16
- package/dist/launcher/ensure-config.js +0 -79
- package/dist/launcher/errors.d.ts +0 -12
- package/dist/launcher/errors.js +0 -22
- package/dist/launcher/host-config.d.ts +0 -17
- package/dist/launcher/host-config.js +0 -64
- package/dist/launcher/host-state.d.ts +0 -17
- package/dist/launcher/host-state.js +0 -61
- package/dist/launcher/lifecycle.d.ts +0 -66
- package/dist/launcher/lifecycle.js +0 -407
- package/dist/launcher/locks.d.ts +0 -26
- package/dist/launcher/locks.js +0 -117
- package/dist/launcher/logs.d.ts +0 -18
- package/dist/launcher/logs.js +0 -117
- package/dist/launcher/main.d.ts +0 -2
- package/dist/launcher/main.js +0 -66
- package/dist/launcher/output.d.ts +0 -11
- package/dist/launcher/output.js +0 -68
- package/dist/launcher/paths.d.ts +0 -20
- package/dist/launcher/paths.js +0 -36
- package/dist/launcher/probe.d.ts +0 -23
- package/dist/launcher/probe.js +0 -72
- package/dist/launcher/protocol.d.ts +0 -62
- package/dist/launcher/protocol.js +0 -1
- package/dist/launcher/schema-guard.d.ts +0 -6
- package/dist/launcher/schema-guard.js +0 -36
- package/dist/launcher/spawn.d.ts +0 -22
- package/dist/launcher/spawn.js +0 -85
- package/dist/launcher/status.d.ts +0 -6
- package/dist/launcher/status.js +0 -65
- package/dist/tenant/migration.d.ts +0 -16
- package/dist/tenant/migration.js +0 -116
- package/dist/tenant/schema.d.ts +0 -23
- package/dist/tenant/schema.js +0 -151
- package/dist/tenant/store-fs.d.ts +0 -16
- package/dist/tenant/store-fs.js +0 -205
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Tx } from "../store/types.js";
|
|
2
2
|
import type { BootstrapPrincipal } from "./types.js";
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
tokenHash: string;
|
|
7
|
-
idempotencyKey: string | null;
|
|
8
|
-
createdAt: string;
|
|
9
|
-
}
|
|
3
|
+
export type { PrincipalRow } from "../store/types.js";
|
|
4
|
+
/** Principal id of the Studio key derived from the admin key. */
|
|
5
|
+
export declare const STUDIO_PRINCIPAL_ID = "studio";
|
|
10
6
|
/**
|
|
11
|
-
* Writes the application principal and idempotency key in
|
|
7
|
+
* Writes the application principal and idempotency key in the caller's transaction, plus
|
|
8
|
+
* principal `studio` when the bootstrap carries a Studio credential hash.
|
|
12
9
|
* Exported for WS-B's `TenantStore.create` (A3).
|
|
13
10
|
*/
|
|
14
|
-
export declare function bootstrapPrincipal(
|
|
15
|
-
|
|
16
|
-
export declare function
|
|
17
|
-
export declare function isApplicationTokenHash(db: DatabaseSync, tokenHash: string): boolean;
|
|
11
|
+
export declare function bootstrapPrincipal(t: Tx, bootstrap: BootstrapPrincipal, now?: Date): Promise<void>;
|
|
12
|
+
/** Whether the stored bootstrap (and Studio) principals equal `bootstrap`. */
|
|
13
|
+
export declare function bootstrapPrincipalMatches(t: Tx, bootstrap: BootstrapPrincipal): Promise<boolean>;
|
|
@@ -1,44 +1,37 @@
|
|
|
1
|
+
/** Principal id of the Studio key derived from the admin key. */
|
|
2
|
+
export const STUDIO_PRINCIPAL_ID = "studio";
|
|
1
3
|
/**
|
|
2
|
-
* Writes the application principal and idempotency key in
|
|
4
|
+
* Writes the application principal and idempotency key in the caller's transaction, plus
|
|
5
|
+
* principal `studio` when the bootstrap carries a Studio credential hash.
|
|
3
6
|
* Exported for WS-B's `TenantStore.create` (A3).
|
|
4
7
|
*/
|
|
5
|
-
export function bootstrapPrincipal(
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
db.prepare(`INSERT INTO principals(id, role, token_hash, idempotency_key, created_at)
|
|
10
|
-
VALUES(?, 'application', ?, ?, ?)`).run(bootstrap.principalId, bootstrap.credentialHash, bootstrap.idempotencyKey, now);
|
|
11
|
-
db.exec("COMMIT");
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
db.exec("ROLLBACK");
|
|
15
|
-
throw error;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
export function findPrincipalByTokenHash(db, tokenHash) {
|
|
19
|
-
const row = db
|
|
20
|
-
.prepare(`SELECT id, role, token_hash, idempotency_key, created_at
|
|
21
|
-
FROM principals WHERE token_hash = ?`)
|
|
22
|
-
.get(tokenHash);
|
|
23
|
-
if (!row)
|
|
24
|
-
return undefined;
|
|
25
|
-
return {
|
|
26
|
-
id: String(row.id),
|
|
8
|
+
export async function bootstrapPrincipal(t, bootstrap, now = new Date()) {
|
|
9
|
+
const createdAt = now.toISOString();
|
|
10
|
+
await t.insertPrincipal({
|
|
11
|
+
id: bootstrap.principalId,
|
|
27
12
|
role: "application",
|
|
28
|
-
tokenHash:
|
|
29
|
-
idempotencyKey:
|
|
30
|
-
createdAt
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
13
|
+
tokenHash: bootstrap.credentialHash,
|
|
14
|
+
idempotencyKey: bootstrap.idempotencyKey,
|
|
15
|
+
createdAt,
|
|
16
|
+
});
|
|
17
|
+
if (bootstrap.studioCredentialHash)
|
|
18
|
+
await t.insertPrincipal({
|
|
19
|
+
id: STUDIO_PRINCIPAL_ID,
|
|
20
|
+
role: "application",
|
|
21
|
+
tokenHash: bootstrap.studioCredentialHash,
|
|
22
|
+
idempotencyKey: null,
|
|
23
|
+
createdAt,
|
|
24
|
+
});
|
|
38
25
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
26
|
+
/** Whether the stored bootstrap (and Studio) principals equal `bootstrap`. */
|
|
27
|
+
export async function bootstrapPrincipalMatches(t, bootstrap) {
|
|
28
|
+
const row = await t.principalById(bootstrap.principalId);
|
|
29
|
+
if (!row ||
|
|
30
|
+
row.role !== "application" ||
|
|
31
|
+
row.tokenHash !== bootstrap.credentialHash ||
|
|
32
|
+
row.idempotencyKey !== bootstrap.idempotencyKey)
|
|
33
|
+
return false;
|
|
34
|
+
const studio = await t.principalById(STUDIO_PRINCIPAL_ID);
|
|
35
|
+
return ((studio?.role === "application" ? studio.tokenHash : undefined) ===
|
|
36
|
+
bootstrap.studioCredentialHash);
|
|
44
37
|
}
|
|
@@ -2,5 +2,5 @@ import type { Quarantine } from "./types.js";
|
|
|
2
2
|
/** Typed quarantine raised while opening a Tenant Runtime (A7). */
|
|
3
3
|
export declare class QuarantineError extends Error {
|
|
4
4
|
readonly quarantine: Quarantine;
|
|
5
|
-
constructor(code: Quarantine["code"], message: string, repair?: string
|
|
5
|
+
constructor(code: Quarantine["code"], message: string, repair?: string);
|
|
6
6
|
}
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/** Typed quarantine raised while opening a Tenant Runtime (A7). */
|
|
2
2
|
export class QuarantineError extends Error {
|
|
3
3
|
quarantine;
|
|
4
|
-
constructor(code, message, repair = "
|
|
4
|
+
constructor(code, message, repair = "nylo tenant status") {
|
|
5
5
|
super(message);
|
|
6
6
|
this.name = "QuarantineError";
|
|
7
|
-
this.quarantine = {
|
|
8
|
-
code,
|
|
9
|
-
message,
|
|
10
|
-
repair,
|
|
11
|
-
...(extra?.lockPath === undefined ? {} : { lockPath: extra.lockPath }),
|
|
12
|
-
...(extra?.lockPid === undefined ? {} : { lockPid: extra.lockPid }),
|
|
13
|
-
};
|
|
7
|
+
this.quarantine = { code, message, repair };
|
|
14
8
|
}
|
|
15
9
|
}
|
|
@@ -3,8 +3,6 @@ import type { Quarantine } from "./types.js";
|
|
|
3
3
|
export declare class QuarantineError extends Error implements Quarantine {
|
|
4
4
|
readonly code: Quarantine["code"];
|
|
5
5
|
readonly repair: string;
|
|
6
|
-
readonly lockPath?: string;
|
|
7
|
-
readonly lockPid?: number;
|
|
8
6
|
constructor(fields: Quarantine);
|
|
9
7
|
toQuarantine(): Quarantine;
|
|
10
8
|
}
|
|
@@ -13,15 +11,9 @@ export declare function asQuarantine(error: unknown): Quarantine | undefined;
|
|
|
13
11
|
/** CLI-facing repair strings for every quarantine code (B9). */
|
|
14
12
|
export declare function repairFor(code: Quarantine["code"], detail?: {
|
|
15
13
|
tenantId?: string;
|
|
16
|
-
lockPath?: string;
|
|
17
|
-
lockPid?: number;
|
|
18
|
-
migrationPath?: string;
|
|
19
14
|
}): string;
|
|
20
15
|
export declare function quarantine(code: Quarantine["code"], message: string, detail?: {
|
|
21
16
|
tenantId?: string;
|
|
22
|
-
lockPath?: string;
|
|
23
|
-
lockPid?: number;
|
|
24
|
-
migrationPath?: string;
|
|
25
17
|
}): QuarantineError;
|
|
26
18
|
/** Conflict when create bootstrap material does not match an existing Tenant (HTTP 409). */
|
|
27
19
|
export declare class TenantConflictError extends Error {
|
|
@@ -2,26 +2,14 @@
|
|
|
2
2
|
export class QuarantineError extends Error {
|
|
3
3
|
code;
|
|
4
4
|
repair;
|
|
5
|
-
lockPath;
|
|
6
|
-
lockPid;
|
|
7
5
|
constructor(fields) {
|
|
8
6
|
super(fields.message);
|
|
9
7
|
this.name = "QuarantineError";
|
|
10
8
|
this.code = fields.code;
|
|
11
9
|
this.repair = fields.repair;
|
|
12
|
-
if (fields.lockPath !== undefined)
|
|
13
|
-
this.lockPath = fields.lockPath;
|
|
14
|
-
if (fields.lockPid !== undefined)
|
|
15
|
-
this.lockPid = fields.lockPid;
|
|
16
10
|
}
|
|
17
11
|
toQuarantine() {
|
|
18
|
-
return {
|
|
19
|
-
code: this.code,
|
|
20
|
-
message: this.message,
|
|
21
|
-
repair: this.repair,
|
|
22
|
-
...(this.lockPath !== undefined ? { lockPath: this.lockPath } : {}),
|
|
23
|
-
...(this.lockPid !== undefined ? { lockPid: this.lockPid } : {}),
|
|
24
|
-
};
|
|
12
|
+
return { code: this.code, message: this.message, repair: this.repair };
|
|
25
13
|
}
|
|
26
14
|
}
|
|
27
15
|
export function isQuarantineError(error) {
|
|
@@ -43,28 +31,20 @@ export function asQuarantine(error) {
|
|
|
43
31
|
export function repairFor(code, detail = {}) {
|
|
44
32
|
const id = detail.tenantId ? ` ${detail.tenantId}` : "";
|
|
45
33
|
switch (code) {
|
|
46
|
-
case "locked":
|
|
47
|
-
return (`nylorun tenant status${id}` +
|
|
48
|
-
(detail.lockPath !== undefined && detail.lockPid !== undefined
|
|
49
|
-
? ` — stop pid ${detail.lockPid} holding ${detail.lockPath}, or remove a stale lock`
|
|
50
|
-
: " — resolve the .runtime-lock conflict"));
|
|
51
34
|
case "kek-missing":
|
|
52
|
-
return `
|
|
35
|
+
return `nylo tenant status${id} — restore vault-kek for this Tenant (ciphertext cannot be opened without it)`;
|
|
53
36
|
case "corrupt":
|
|
54
|
-
return `
|
|
37
|
+
return `nylo tenant status${id} — restore the Tenant from backup`;
|
|
55
38
|
case "schema-too-new":
|
|
56
|
-
return `
|
|
57
|
-
`${id ? ` — Tenant${id} needs a newer Host` : " — Host schema is older than this Tenant"}`;
|
|
39
|
+
return `nylo tenant status${id} — the Tenant was migrated by a newer Runtime; run that version or newer`;
|
|
58
40
|
case "migration-failed":
|
|
59
|
-
return
|
|
60
|
-
? `restore ${detail.migrationPath} over the Tenant files, then nylorun tenant status${id}`
|
|
61
|
-
: `nylorun tenant status${id} — restore from .migration/ snapshot, then retry`;
|
|
41
|
+
return `nylo tenant status${id} — inspect the Runtime log for the failed migration, fix it, then restart the Runtime`;
|
|
62
42
|
case "envelope-invalid":
|
|
63
|
-
return `
|
|
43
|
+
return `nylo tenant status${id} — the Tenant envelope is missing or invalid; restore the Tenant from backup`;
|
|
64
44
|
case "open-timeout":
|
|
65
|
-
return `
|
|
45
|
+
return `nylo tenant status${id} — open timed out; inspect the Runtime log and Postgres, then retry`;
|
|
66
46
|
case "open-failed":
|
|
67
|
-
return `
|
|
47
|
+
return `nylo tenant status${id} — inspect Tenant logs and repair before the Host retries open`;
|
|
68
48
|
}
|
|
69
49
|
}
|
|
70
50
|
export function quarantine(code, message, detail = {}) {
|
|
@@ -72,8 +52,6 @@ export function quarantine(code, message, detail = {}) {
|
|
|
72
52
|
code,
|
|
73
53
|
message,
|
|
74
54
|
repair: repairFor(code, detail),
|
|
75
|
-
...(detail.lockPath !== undefined ? { lockPath: detail.lockPath } : {}),
|
|
76
|
-
...(detail.lockPid !== undefined ? { lockPid: detail.lockPid } : {}),
|
|
77
55
|
});
|
|
78
56
|
}
|
|
79
57
|
/** Conflict when create bootstrap material does not match an existing Tenant (HTTP 409). */
|
package/dist/tenant/reset.d.ts
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ResetScope, SessionStore } from "../store/types.js";
|
|
2
2
|
import type { ExecutorRegistry } from "../core/executors.js";
|
|
3
3
|
import type { SandboxManager } from "../sandbox/manager.js";
|
|
4
4
|
import type { TenantPaths } from "./types.js";
|
|
5
|
-
export type ResetScope
|
|
5
|
+
export type { ResetScope } from "../store/types.js";
|
|
6
6
|
export interface ResetTenantContext {
|
|
7
|
-
store:
|
|
7
|
+
store: SessionStore;
|
|
8
8
|
registry: ExecutorRegistry;
|
|
9
9
|
sandbox: SandboxManager;
|
|
10
10
|
paths: TenantPaths;
|
|
11
|
-
/** Clear in-memory session observers after
|
|
11
|
+
/** Clear in-memory session observers after the store wipe. */
|
|
12
12
|
clearSessionState: () => void;
|
|
13
13
|
/** Clear executor SSE streams when registrations are removed. */
|
|
14
14
|
clearExecutorStreams: () => void;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Reset Tenant durable state for `scope` (A17).
|
|
18
|
-
* Caller must have already drained (`activeWork`).
|
|
19
|
-
* transaction, then filesystem rename+delete for sandboxes /
|
|
18
|
+
* Caller must have already drained (`activeWork`). Deletes store state in one
|
|
19
|
+
* transaction (`Tx.reset`), then does the filesystem rename+delete for sandboxes /
|
|
20
|
+
* log as needed. The host vault (model credentials), principals and settings stay.
|
|
20
21
|
*/
|
|
21
22
|
export declare function resetTenant(ctx: ResetTenantContext, scope: ResetScope): Promise<void>;
|
package/dist/tenant/reset.js
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
const SESSION_TABLES = [
|
|
4
|
-
"sessions",
|
|
5
|
-
"commands",
|
|
6
|
-
"checkpoints",
|
|
7
|
-
"effects",
|
|
8
|
-
"actions",
|
|
9
|
-
];
|
|
10
3
|
/**
|
|
11
4
|
* Rename `dir` to a sibling, delete the sibling, recreate an empty `dir`.
|
|
12
5
|
* Failure before rename leaves the original intact; after rename the empty
|
|
@@ -35,41 +28,11 @@ function replaceDirectory(dir) {
|
|
|
35
28
|
throw error;
|
|
36
29
|
}
|
|
37
30
|
}
|
|
38
|
-
function clearSessionTables(store) {
|
|
39
|
-
for (const table of SESSION_TABLES) {
|
|
40
|
-
store.db.prepare(`DELETE FROM ${table}`).run();
|
|
41
|
-
}
|
|
42
|
-
store.db.prepare("DELETE FROM events").run();
|
|
43
|
-
}
|
|
44
|
-
function clearSandboxRows(store) {
|
|
45
|
-
store.db.prepare("DELETE FROM sandboxes").run();
|
|
46
|
-
}
|
|
47
|
-
function clearDefinitions(store) {
|
|
48
|
-
store.db.prepare("DELETE FROM definitions").run();
|
|
49
|
-
}
|
|
50
|
-
function clearExecutors(store, registry) {
|
|
51
|
-
for (const row of registry.list())
|
|
52
|
-
registry.remove(row.agentId);
|
|
53
|
-
store.db.prepare("DELETE FROM executors").run();
|
|
54
|
-
}
|
|
55
|
-
/** Delete user-scoped vaults; keep the host vault (model credentials). */
|
|
56
|
-
function clearUserVaults(store) {
|
|
57
|
-
const userVaults = store.db
|
|
58
|
-
.prepare(`SELECT id FROM vaults WHERE scope != 'host'`)
|
|
59
|
-
.all();
|
|
60
|
-
for (const vault of userVaults) {
|
|
61
|
-
store.db
|
|
62
|
-
.prepare(`DELETE FROM vault_credentials WHERE vault_id=?`)
|
|
63
|
-
.run(vault.id);
|
|
64
|
-
store.db.prepare(`DELETE FROM vaults WHERE id=?`).run(vault.id);
|
|
65
|
-
}
|
|
66
|
-
// Drop audit / idempotency rows that referenced user vaults only when all.
|
|
67
|
-
// Keep host-model idempotency so seed replay stays stable.
|
|
68
|
-
}
|
|
69
31
|
/**
|
|
70
32
|
* Reset Tenant durable state for `scope` (A17).
|
|
71
|
-
* Caller must have already drained (`activeWork`).
|
|
72
|
-
* transaction, then filesystem rename+delete for sandboxes /
|
|
33
|
+
* Caller must have already drained (`activeWork`). Deletes store state in one
|
|
34
|
+
* transaction (`Tx.reset`), then does the filesystem rename+delete for sandboxes /
|
|
35
|
+
* log as needed. The host vault (model credentials), principals and settings stay.
|
|
73
36
|
*/
|
|
74
37
|
export async function resetTenant(ctx, scope) {
|
|
75
38
|
const clearSessions = scope === "sessions" || scope === "all";
|
|
@@ -78,17 +41,10 @@ export async function resetTenant(ctx, scope) {
|
|
|
78
41
|
if (clearSandboxes) {
|
|
79
42
|
await ctx.sandbox.reconcile(() => false);
|
|
80
43
|
}
|
|
81
|
-
ctx.store.tx(() =>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
clearSandboxRows(ctx.store);
|
|
86
|
-
if (clearAll) {
|
|
87
|
-
clearDefinitions(ctx.store);
|
|
88
|
-
clearExecutors(ctx.store, ctx.registry);
|
|
89
|
-
clearUserVaults(ctx.store);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
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);
|
|
92
48
|
if (clearSessions)
|
|
93
49
|
ctx.clearSessionState();
|
|
94
50
|
if (clearAll)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant administration and vault routes: `/v1/tenant` (status, reset, config seed, host
|
|
3
|
+
* model and providers, sandbox report) and `/v1/vaults` (vaults and credentials).
|
|
4
|
+
* A reset that deletes sessions abandons their streams (a session created again gets a new
|
|
5
|
+
* incarnation) and collects them afterwards; it never deletes the Tenant's basin.
|
|
6
|
+
*
|
|
7
|
+
* Later waves: Wave 2 / X changes what reset clears once work leaves process memory.
|
|
8
|
+
*/
|
|
9
|
+
import type { IncomingMessage } from "node:http";
|
|
10
|
+
import type { AuthScope, TenantContext } from "./context.js";
|
|
11
|
+
export declare function dispatchTenant(ctx: TenantContext, scope: AuthScope, method: string | undefined, path: string[], request: IncomingMessage): Promise<unknown>;
|
|
12
|
+
export declare function dispatchVault(ctx: TenantContext, scope: AuthScope, method: string | undefined, path: string[], url: URL, request: IncomingMessage): Promise<unknown>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { CreateCredentialRequestSchema, CreateVaultRequestSchema, PutHostModelRequestSchema, ResetTenantRequestSchema, RotateCredentialRequestSchema, SeedTenantConfigRequestSchema, SelectHostModelRequestSchema, } from "@nylorun/core/contracts";
|
|
2
|
+
import { hostModelCatalog } from "../model/catalog.js";
|
|
3
|
+
import { resetTenant } from "./reset.js";
|
|
4
|
+
import { buildTenantStatus, seedTenantConfig } from "./status.js";
|
|
5
|
+
import { fail, readBody } from "./http.js";
|
|
6
|
+
import { clearExecutorStreams, clearObservers } from "./live.js";
|
|
7
|
+
import { requestStreamCollection, sessionStreamsAbandoned, streamsStatus, } from "./streams.js";
|
|
8
|
+
import { clearWork, drain } from "./scheduler.js";
|
|
9
|
+
import { usesFixtureModel } from "./model-setting.js";
|
|
10
|
+
export async function dispatchTenant(ctx, scope, method, path, request) {
|
|
11
|
+
const { vault } = ctx;
|
|
12
|
+
if (scope.kind !== "application") {
|
|
13
|
+
await vault.reject(path.join("/"));
|
|
14
|
+
fail(403, "Application credential required");
|
|
15
|
+
}
|
|
16
|
+
if (path.length === 2 && method === "GET")
|
|
17
|
+
return await buildTenantStatus({
|
|
18
|
+
envelope: ctx.envelope,
|
|
19
|
+
config: ctx.config,
|
|
20
|
+
store: ctx.store,
|
|
21
|
+
registry: ctx.registry,
|
|
22
|
+
vault,
|
|
23
|
+
sandbox: ctx.sandbox,
|
|
24
|
+
closing: ctx.closing || ctx.closed,
|
|
25
|
+
modelConfigured: !ctx.useVaultModel ||
|
|
26
|
+
(await ctx.store.tx((t) => usesFixtureModel(t))) ||
|
|
27
|
+
(await vault.getHostModel()).configured,
|
|
28
|
+
executorStreams: ctx.live.executorStreams,
|
|
29
|
+
...(ctx.stuckInvocations
|
|
30
|
+
? { stuckInvocations: ctx.stuckInvocations }
|
|
31
|
+
: {}),
|
|
32
|
+
streamsStatus: () => streamsStatus(ctx),
|
|
33
|
+
});
|
|
34
|
+
if (path[2] === "reset" && path.length === 3 && method === "POST") {
|
|
35
|
+
const body = ResetTenantRequestSchema.parse(await readBody(request));
|
|
36
|
+
await drain(ctx, body.activeWork, 30_000);
|
|
37
|
+
// The deleted sessions' streams are abandoned: a session created again with the same id,
|
|
38
|
+
// during or after the reset, gets a new incarnation and starts at sequence 0. The pending
|
|
39
|
+
// collection is recorded first, so a crash before it runs leaves it to the sweep.
|
|
40
|
+
const sessionsReset = body.scope !== "sandboxes";
|
|
41
|
+
if (sessionsReset)
|
|
42
|
+
await requestStreamCollection(ctx);
|
|
43
|
+
await resetTenant({
|
|
44
|
+
store: ctx.store,
|
|
45
|
+
registry: ctx.registry,
|
|
46
|
+
sandbox: ctx.sandbox,
|
|
47
|
+
paths: ctx.config.paths,
|
|
48
|
+
clearSessionState: () => {
|
|
49
|
+
clearWork(ctx);
|
|
50
|
+
clearObservers(ctx.live);
|
|
51
|
+
},
|
|
52
|
+
clearExecutorStreams: () => clearExecutorStreams(ctx.live),
|
|
53
|
+
}, body.scope);
|
|
54
|
+
if (sessionsReset)
|
|
55
|
+
sessionStreamsAbandoned(ctx);
|
|
56
|
+
// Reset leaves the Tenant open for new work.
|
|
57
|
+
ctx.closing = false;
|
|
58
|
+
return { ok: true };
|
|
59
|
+
}
|
|
60
|
+
if (path[2] === "config" &&
|
|
61
|
+
path[3] === "seed" &&
|
|
62
|
+
path.length === 4 &&
|
|
63
|
+
method === "PUT") {
|
|
64
|
+
const body = SeedTenantConfigRequestSchema.parse(await readBody(request));
|
|
65
|
+
return await seedTenantConfig({ store: ctx.store, vault }, body);
|
|
66
|
+
}
|
|
67
|
+
if (path[2] === "models" && path.length === 3 && method === "GET")
|
|
68
|
+
return hostModelCatalog();
|
|
69
|
+
if (path[2] === "sandbox" && path.length === 3 && method === "GET")
|
|
70
|
+
return ctx.sandbox.report();
|
|
71
|
+
if (path[2] === "providers" && path.length === 3 && method === "GET")
|
|
72
|
+
return vault.listHostProviders();
|
|
73
|
+
if (path[2] === "model" && path.length === 3 && method === "GET")
|
|
74
|
+
return vault.getHostModel();
|
|
75
|
+
if (path[2] === "model" && path.length === 3 && method === "PUT")
|
|
76
|
+
return vault.putHostModel(PutHostModelRequestSchema.parse(await readBody(request)));
|
|
77
|
+
if (path[2] === "model" &&
|
|
78
|
+
path[3] === "selection" &&
|
|
79
|
+
path.length === 4 &&
|
|
80
|
+
method === "PUT")
|
|
81
|
+
return vault.selectHostModel(SelectHostModelRequestSchema.parse(await readBody(request)));
|
|
82
|
+
fail(404, "Route not found");
|
|
83
|
+
}
|
|
84
|
+
export async function dispatchVault(ctx, scope, method, path, url, request) {
|
|
85
|
+
const { vault } = ctx;
|
|
86
|
+
if (scope.kind !== "application") {
|
|
87
|
+
await vault.reject(path.join("/"));
|
|
88
|
+
fail(403, "Application credential required");
|
|
89
|
+
}
|
|
90
|
+
if (path.length === 2 && method === "POST") {
|
|
91
|
+
const body = CreateVaultRequestSchema.parse(await readBody(request));
|
|
92
|
+
return vault.createVault(body);
|
|
93
|
+
}
|
|
94
|
+
if (path.length === 2 && method === "GET") {
|
|
95
|
+
const ownerUserId = url.searchParams.get("ownerUserId") ??
|
|
96
|
+
fail(400, "ownerUserId is required");
|
|
97
|
+
return { vaults: await vault.listVaults(ownerUserId) };
|
|
98
|
+
}
|
|
99
|
+
const vaultId = path[2];
|
|
100
|
+
if (!vaultId)
|
|
101
|
+
fail(404, "Vault not found");
|
|
102
|
+
if (path.length === 3 && method === "GET")
|
|
103
|
+
return vault.getVault(vaultId);
|
|
104
|
+
if (path.length === 3 && method === "DELETE")
|
|
105
|
+
return vault.deleteVault(vaultId);
|
|
106
|
+
if (path[3] !== "credentials")
|
|
107
|
+
fail(404, "Route not found");
|
|
108
|
+
if (path.length === 4 && method === "POST") {
|
|
109
|
+
const body = CreateCredentialRequestSchema.parse(await readBody(request));
|
|
110
|
+
return vault.createCredential(vaultId, body);
|
|
111
|
+
}
|
|
112
|
+
if (path.length === 4 && method === "GET")
|
|
113
|
+
return { credentials: await vault.listCredentials(vaultId) };
|
|
114
|
+
const credentialId = path[4];
|
|
115
|
+
if (!credentialId)
|
|
116
|
+
fail(404, "Credential not found");
|
|
117
|
+
if (path.length === 5 && method === "GET")
|
|
118
|
+
return vault.getCredential(vaultId, credentialId);
|
|
119
|
+
if (path.length === 5 && method === "POST") {
|
|
120
|
+
const body = RotateCredentialRequestSchema.parse(await readBody(request));
|
|
121
|
+
return vault.rotateCredential(vaultId, credentialId, body);
|
|
122
|
+
}
|
|
123
|
+
if (path.length === 5 && method === "DELETE")
|
|
124
|
+
return vault.deleteCredential(vaultId, credentialId);
|
|
125
|
+
fail(404, "Route not found");
|
|
126
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Tenant HTTP router: authenticates the bearer token, matches `/v1/...` routes and
|
|
3
|
+
* delegates to the service modules (commands, actions, sessions, live, routes-tenant). It
|
|
4
|
+
* maps typed errors to responses and owns no business logic of its own. History, SSE and
|
|
5
|
+
* executor work streams read Durable Streams (`live.ts`).
|
|
6
|
+
*/
|
|
7
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
|
+
import { type TenantContext } from "./context.js";
|
|
9
|
+
export declare function handle(ctx: TenantContext, request: IncomingMessage, response: ServerResponse, _url?: URL): Promise<void>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { PutAgentRequestSchema, PutSessionRequestSchema, RegisterExecutorsRequestSchema, SessionCommandSchema, } from "@nylorun/core/contracts";
|
|
2
|
+
import { VaultError } from "../vault/error.js";
|
|
3
|
+
import { SandboxRouteError, handleSessionSandboxTool, } from "../core/sandbox-routes.js";
|
|
4
|
+
import { loadSession, sessionOf } from "./context.js";
|
|
5
|
+
import { HttpError, OpaqueAuthError, fail, readBody, requestAborted, } from "./http.js";
|
|
6
|
+
import { authenticate, requireApplication } from "./auth.js";
|
|
7
|
+
import { command } from "./commands.js";
|
|
8
|
+
import { actionSandboxTool, deleteExecutor, listExecutors, listPendingActions, registerExecutors, sandboxRouteDeps, updateAction, } from "./actions.js";
|
|
9
|
+
import { listDefinitions, listSessions, putDefinition, putSession, sessionView, } from "./sessions.js";
|
|
10
|
+
import { readHistory, requestCursor, streamExecutorWork, streamSessionEvents, } from "./live.js";
|
|
11
|
+
import { dispatchTenant, dispatchVault } from "./routes-tenant.js";
|
|
12
|
+
export async function handle(ctx, request, response, _url) {
|
|
13
|
+
const json = (value, status = 200) => {
|
|
14
|
+
const payload = JSON.stringify(value);
|
|
15
|
+
response.writeHead(status, {
|
|
16
|
+
"content-type": "application/json",
|
|
17
|
+
"content-length": Buffer.byteLength(payload),
|
|
18
|
+
});
|
|
19
|
+
response.end(payload);
|
|
20
|
+
};
|
|
21
|
+
try {
|
|
22
|
+
const url = _url ?? new URL(request.url ?? "/", "http://runtime");
|
|
23
|
+
const path = url.pathname
|
|
24
|
+
.split("/")
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.map(decodeURIComponent);
|
|
27
|
+
const method = request.method;
|
|
28
|
+
const scope = await authenticate(ctx, request);
|
|
29
|
+
if (path[0] !== "v1")
|
|
30
|
+
fail(404, "Route not found");
|
|
31
|
+
if (path[1] === "executors" && path[2] === "connect" && method === "GET") {
|
|
32
|
+
if (scope.kind !== "executor")
|
|
33
|
+
return fail(403, "Executor credential required");
|
|
34
|
+
streamExecutorWork(ctx.live, request, response, scope.executor.tokenHash);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (path[1] === "actions") {
|
|
38
|
+
if (scope.kind !== "executor")
|
|
39
|
+
return fail(403, "Executor credential required");
|
|
40
|
+
if (path.length === 2 && method === "GET")
|
|
41
|
+
return json(await listPendingActions(ctx, scope.executor));
|
|
42
|
+
const actionId = path[2];
|
|
43
|
+
if (!actionId)
|
|
44
|
+
fail(404, "Action not found");
|
|
45
|
+
if (method === "POST" &&
|
|
46
|
+
path[3] === "sandbox" &&
|
|
47
|
+
path[4] &&
|
|
48
|
+
path.length === 5)
|
|
49
|
+
return json(await actionSandboxTool(ctx, scope, actionId, path[4], request));
|
|
50
|
+
const body = await readBody(request);
|
|
51
|
+
return json(await updateAction(ctx, scope, actionId, method, path[3], body));
|
|
52
|
+
}
|
|
53
|
+
if (path[1] === "sessions" &&
|
|
54
|
+
path[2] &&
|
|
55
|
+
path[3] === "commands" &&
|
|
56
|
+
method === "POST")
|
|
57
|
+
return json(await command(ctx, path[2], SessionCommandSchema.parse(await readBody(request)), scope));
|
|
58
|
+
if (path[1] === "vaults")
|
|
59
|
+
return json(await dispatchVault(ctx, scope, method, path, url, request));
|
|
60
|
+
if (path[1] === "tenant")
|
|
61
|
+
return json(await dispatchTenant(ctx, scope, method, path, request));
|
|
62
|
+
const principalId = requireApplication(scope);
|
|
63
|
+
if (path[1] === "executors" && path.length === 2 && method === "GET")
|
|
64
|
+
return json(listExecutors(ctx));
|
|
65
|
+
// The length guard matters: the connect branch above only matches GET, so without it a
|
|
66
|
+
// PUT to /v1/executors/connect would register an agent literally named "connect".
|
|
67
|
+
if (path[1] === "executors" && path.length === 2 && method === "PUT")
|
|
68
|
+
return json(await registerExecutors(ctx, principalId, RegisterExecutorsRequestSchema.parse(await readBody(request))));
|
|
69
|
+
if (path[1] === "executors" &&
|
|
70
|
+
path.length === 3 &&
|
|
71
|
+
path[2] &&
|
|
72
|
+
method === "DELETE")
|
|
73
|
+
return json(await deleteExecutor(ctx, path[2]));
|
|
74
|
+
if (path[1] === "agents" && path.length === 2 && method === "GET")
|
|
75
|
+
return json(await listDefinitions(ctx));
|
|
76
|
+
if (path[1] === "sessions" && path.length === 2 && method === "GET")
|
|
77
|
+
return json(await listSessions(ctx, url.searchParams.get("agentId")));
|
|
78
|
+
if (path[1] === "agents" &&
|
|
79
|
+
path[2] &&
|
|
80
|
+
path.length === 3 &&
|
|
81
|
+
method === "PUT")
|
|
82
|
+
return json(await putDefinition(ctx, path[2], PutAgentRequestSchema.parse(await readBody(request))));
|
|
83
|
+
if (path[1] === "sessions" && path[2]) {
|
|
84
|
+
const id = path[2];
|
|
85
|
+
if (method === "PUT" && path.length === 3) {
|
|
86
|
+
const body = PutSessionRequestSchema.parse(await readBody(request));
|
|
87
|
+
const session = await putSession(ctx, id, body);
|
|
88
|
+
return json(await ctx.store.tx((t) => sessionView(t, session)));
|
|
89
|
+
}
|
|
90
|
+
if (method === "GET" && path.length === 3)
|
|
91
|
+
return json(await ctx.store.tx(async (t) => sessionView(t, await sessionOf(t, id))));
|
|
92
|
+
// Every other session route needs the session to exist.
|
|
93
|
+
await loadSession(ctx, id);
|
|
94
|
+
const cursor = requestCursor(request, url);
|
|
95
|
+
if (method === "GET" && path[3] === "items")
|
|
96
|
+
return json(await readHistory(ctx, id, cursor, url.searchParams.get("agent") ?? undefined));
|
|
97
|
+
if (method === "GET" && path[3] === "events") {
|
|
98
|
+
await streamSessionEvents(ctx, request, response, id, cursor);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (method === "POST" &&
|
|
102
|
+
path[3] === "sandbox" &&
|
|
103
|
+
path[4] &&
|
|
104
|
+
path.length === 5) {
|
|
105
|
+
requireApplication(scope);
|
|
106
|
+
try {
|
|
107
|
+
const outcome = await handleSessionSandboxTool(sandboxRouteDeps(ctx), id, path[4], await readBody(request), requestAborted(request));
|
|
108
|
+
return json(outcome);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error instanceof SandboxRouteError)
|
|
112
|
+
fail(error.status, error.message);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
fail(404, "Route not found");
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (response.headersSent) {
|
|
121
|
+
response.end();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (error instanceof OpaqueAuthError) {
|
|
125
|
+
json(error.body, error.status);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const status = error instanceof HttpError ||
|
|
129
|
+
error instanceof VaultError ||
|
|
130
|
+
error instanceof SandboxRouteError
|
|
131
|
+
? error.status
|
|
132
|
+
: error?.name === "ZodError" ||
|
|
133
|
+
error?.message === "Invalid cursor"
|
|
134
|
+
? 400
|
|
135
|
+
: 500;
|
|
136
|
+
json({
|
|
137
|
+
status: "rejected",
|
|
138
|
+
code: status === 500 ? "internal_error" : "request_rejected",
|
|
139
|
+
message: status === 500 ? "Runtime request failed" : error.message,
|
|
140
|
+
}, status);
|
|
141
|
+
}
|
|
142
|
+
}
|