@nylorun/runtime 0.10.0-beta → 0.12.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +53 -51
  3. package/dist/adapters/execution/restate.d.ts +130 -0
  4. package/dist/adapters/execution/restate.js +442 -0
  5. package/dist/adapters/sandbox/virtual.js +1 -1
  6. package/dist/adapters/streams/s2.d.ts +18 -0
  7. package/dist/adapters/streams/s2.js +361 -0
  8. package/dist/core/executors.d.ts +4 -1
  9. package/dist/core/executors.js +20 -1
  10. package/dist/core/flow-host.d.ts +93 -132
  11. package/dist/core/flow-host.js +282 -547
  12. package/dist/core/sandbox-routes.d.ts +13 -4
  13. package/dist/core/sandbox-routes.js +14 -12
  14. package/dist/execution/memory.d.ts +54 -0
  15. package/dist/execution/memory.js +217 -0
  16. package/dist/execution/types.d.ts +125 -0
  17. package/dist/execution/types.js +52 -0
  18. package/dist/host/config-for.d.ts +2 -1
  19. package/dist/host/config-for.js +3 -21
  20. package/dist/host/config.d.ts +0 -8
  21. package/dist/host/create-host.d.ts +36 -1
  22. package/dist/host/create-host.js +51 -24
  23. package/dist/host/execution.d.ts +61 -0
  24. package/dist/host/execution.js +69 -0
  25. package/dist/host/http.d.ts +4 -0
  26. package/dist/host/http.js +6 -0
  27. package/dist/host/index.d.ts +3 -1
  28. package/dist/host/index.js +2 -0
  29. package/dist/host/main.js +106 -47
  30. package/dist/host/stack-config.d.ts +72 -0
  31. package/dist/host/stack-config.js +193 -0
  32. package/dist/index.d.ts +1 -1
  33. package/dist/infra/database.d.ts +16 -0
  34. package/dist/infra/database.js +31 -0
  35. package/dist/infra/execution.d.ts +39 -0
  36. package/dist/infra/execution.js +68 -0
  37. package/dist/infra/index.d.ts +42 -0
  38. package/dist/infra/index.js +56 -0
  39. package/dist/infra/readiness.d.ts +33 -0
  40. package/dist/infra/readiness.js +53 -0
  41. package/dist/infra/streams.d.ts +17 -0
  42. package/dist/infra/streams.js +33 -0
  43. package/dist/infra/workers.d.ts +39 -0
  44. package/dist/infra/workers.js +31 -0
  45. package/dist/model/pi-model.d.ts +2 -2
  46. package/dist/model/pi-model.js +5 -5
  47. package/dist/sandbox/manager.d.ts +26 -8
  48. package/dist/sandbox/manager.js +95 -47
  49. package/dist/sandbox/select.d.ts +1 -1
  50. package/dist/sandbox/select.js +4 -5
  51. package/dist/sandbox/types.d.ts +2 -2
  52. package/dist/store/canonical.d.ts +5 -0
  53. package/dist/store/canonical.js +17 -0
  54. package/dist/store/cursor.d.ts +9 -0
  55. package/dist/store/cursor.js +18 -0
  56. package/dist/store/memory.d.ts +57 -0
  57. package/dist/store/memory.js +645 -0
  58. package/dist/store/ownership.d.ts +19 -0
  59. package/dist/store/ownership.js +33 -0
  60. package/dist/store/postgres/connect.d.ts +25 -0
  61. package/dist/store/postgres/connect.js +25 -0
  62. package/dist/store/postgres/locking.d.ts +16 -0
  63. package/dist/store/postgres/locking.js +22 -0
  64. package/dist/store/postgres/migrations/001_initial.d.ts +27 -0
  65. package/dist/store/postgres/migrations/001_initial.js +196 -0
  66. package/dist/store/postgres/migrations/002_session_owner.d.ts +6 -0
  67. package/dist/store/postgres/migrations/002_session_owner.js +13 -0
  68. package/dist/store/postgres/migrations/index.d.ts +49 -0
  69. package/dist/store/postgres/migrations/index.js +76 -0
  70. package/dist/store/postgres/names.d.ts +11 -0
  71. package/dist/store/postgres/names.js +42 -0
  72. package/dist/store/postgres/store.d.ts +11 -0
  73. package/dist/store/postgres/store.js +797 -0
  74. package/dist/store/postgres/tenants.d.ts +90 -0
  75. package/dist/store/postgres/tenants.js +214 -0
  76. package/dist/store/types.d.ts +442 -0
  77. package/dist/store/types.js +10 -0
  78. package/dist/streams/basin.d.ts +23 -0
  79. package/dist/streams/basin.js +51 -0
  80. package/dist/streams/memory.d.ts +25 -0
  81. package/dist/streams/memory.js +124 -0
  82. package/dist/streams/relay.d.ts +37 -0
  83. package/dist/streams/relay.js +254 -0
  84. package/dist/streams/types.d.ts +115 -0
  85. package/dist/streams/types.js +86 -0
  86. package/dist/tenant/actions.d.ts +153 -0
  87. package/dist/tenant/actions.js +191 -0
  88. package/dist/tenant/advance.d.ts +15 -0
  89. package/dist/tenant/advance.js +439 -0
  90. package/dist/tenant/auth.d.ts +32 -0
  91. package/dist/tenant/auth.js +169 -0
  92. package/dist/tenant/commands.d.ts +3 -0
  93. package/dist/tenant/commands.js +393 -0
  94. package/dist/tenant/context.d.ts +147 -0
  95. package/dist/tenant/context.js +48 -0
  96. package/dist/tenant/effects.d.ts +20 -0
  97. package/dist/tenant/effects.js +564 -0
  98. package/dist/tenant/envelope.d.ts +0 -3
  99. package/dist/tenant/envelope.js +2 -34
  100. package/dist/tenant/ephemeral.d.ts +15 -4
  101. package/dist/tenant/ephemeral.js +106 -38
  102. package/dist/tenant/http.d.ts +38 -0
  103. package/dist/tenant/http.js +53 -0
  104. package/dist/tenant/live.d.ts +92 -0
  105. package/dist/tenant/live.js +290 -0
  106. package/dist/tenant/model-setting.d.ts +16 -0
  107. package/dist/tenant/model-setting.js +12 -0
  108. package/dist/tenant/module.d.ts +29 -8
  109. package/dist/tenant/module.js +165 -158
  110. package/dist/tenant/paths.d.ts +12 -8
  111. package/dist/tenant/paths.js +38 -12
  112. package/dist/tenant/principals.d.ts +12 -13
  113. package/dist/tenant/principals.js +50 -38
  114. package/dist/tenant/quarantine-error.d.ts +1 -1
  115. package/dist/tenant/quarantine-error.js +2 -8
  116. package/dist/tenant/quarantine.d.ts +0 -8
  117. package/dist/tenant/quarantine.js +8 -30
  118. package/dist/tenant/reset.d.ts +7 -6
  119. package/dist/tenant/reset.js +7 -51
  120. package/dist/tenant/routes-tenant.d.ts +12 -0
  121. package/dist/tenant/routes-tenant.js +137 -0
  122. package/dist/tenant/routes.d.ts +9 -0
  123. package/dist/tenant/routes.js +151 -0
  124. package/dist/tenant/runtime.d.ts +59 -58
  125. package/dist/tenant/runtime.js +243 -2201
  126. package/dist/tenant/scheduler.d.ts +25 -0
  127. package/dist/tenant/scheduler.js +84 -0
  128. package/dist/tenant/session.d.ts +60 -0
  129. package/dist/tenant/session.js +94 -0
  130. package/dist/tenant/sessions.d.ts +40 -0
  131. package/dist/tenant/sessions.js +163 -0
  132. package/dist/tenant/status.d.ts +13 -4
  133. package/dist/tenant/status.js +76 -41
  134. package/dist/tenant/store-memory.d.ts +9 -12
  135. package/dist/tenant/store-memory.js +79 -68
  136. package/dist/tenant/store-pg.d.ts +15 -0
  137. package/dist/tenant/store-pg.js +140 -0
  138. package/dist/tenant/streams.d.ts +126 -0
  139. package/dist/tenant/streams.js +419 -0
  140. package/dist/tenant/sweep.d.ts +15 -0
  141. package/dist/tenant/sweep.js +122 -0
  142. package/dist/tenant/transcript.d.ts +104 -0
  143. package/dist/tenant/transcript.js +58 -0
  144. package/dist/tenant/types.d.ts +101 -15
  145. package/dist/tenant/types.js +23 -1
  146. package/dist/tenant/worker.d.ts +84 -0
  147. package/dist/tenant/worker.js +189 -0
  148. package/dist/vault/service.d.ts +44 -31
  149. package/dist/vault/service.js +454 -360
  150. package/dist/version.d.ts +1 -1
  151. package/dist/version.js +1 -1
  152. package/package.json +15 -14
  153. package/dist/adapters/sandbox/microsandbox.d.ts +0 -2
  154. package/dist/adapters/sandbox/microsandbox.js +0 -238
  155. package/dist/core/store.d.ts +0 -32
  156. package/dist/core/store.js +0 -143
  157. package/dist/launcher/commands.d.ts +0 -24
  158. package/dist/launcher/commands.js +0 -260
  159. package/dist/launcher/ensure-config.d.ts +0 -16
  160. package/dist/launcher/ensure-config.js +0 -79
  161. package/dist/launcher/errors.d.ts +0 -12
  162. package/dist/launcher/errors.js +0 -22
  163. package/dist/launcher/host-config.d.ts +0 -17
  164. package/dist/launcher/host-config.js +0 -64
  165. package/dist/launcher/host-state.d.ts +0 -17
  166. package/dist/launcher/host-state.js +0 -61
  167. package/dist/launcher/lifecycle.d.ts +0 -66
  168. package/dist/launcher/lifecycle.js +0 -407
  169. package/dist/launcher/locks.d.ts +0 -26
  170. package/dist/launcher/locks.js +0 -117
  171. package/dist/launcher/logs.d.ts +0 -18
  172. package/dist/launcher/logs.js +0 -117
  173. package/dist/launcher/main.d.ts +0 -2
  174. package/dist/launcher/main.js +0 -66
  175. package/dist/launcher/output.d.ts +0 -11
  176. package/dist/launcher/output.js +0 -68
  177. package/dist/launcher/paths.d.ts +0 -20
  178. package/dist/launcher/paths.js +0 -36
  179. package/dist/launcher/probe.d.ts +0 -23
  180. package/dist/launcher/probe.js +0 -72
  181. package/dist/launcher/protocol.d.ts +0 -62
  182. package/dist/launcher/protocol.js +0 -1
  183. package/dist/launcher/schema-guard.d.ts +0 -6
  184. package/dist/launcher/schema-guard.js +0 -36
  185. package/dist/launcher/spawn.d.ts +0 -22
  186. package/dist/launcher/spawn.js +0 -85
  187. package/dist/launcher/status.d.ts +0 -6
  188. package/dist/launcher/status.js +0 -65
  189. package/dist/tenant/migration.d.ts +0 -16
  190. package/dist/tenant/migration.js +0 -116
  191. package/dist/tenant/schema.d.ts +0 -23
  192. package/dist/tenant/schema.js +0 -151
  193. package/dist/tenant/store-fs.d.ts +0 -16
  194. package/dist/tenant/store-fs.js +0 -205
@@ -1,17 +1,16 @@
1
- import type { DatabaseSync } from "node:sqlite";
1
+ import type { Tx } from "../store/types.js";
2
2
  import type { BootstrapPrincipal } from "./types.js";
3
- export interface PrincipalRow {
4
- id: string;
5
- role: "application";
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 one transaction.
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(db: DatabaseSync, bootstrap: BootstrapPrincipal): void;
15
- export declare function findPrincipalByTokenHash(db: DatabaseSync, tokenHash: string): PrincipalRow | undefined;
16
- export declare function applicationTokenHashes(db: DatabaseSync): readonly string[];
17
- export declare function isApplicationTokenHash(db: DatabaseSync, tokenHash: string): boolean;
11
+ export declare function bootstrapPrincipal(t: Tx, bootstrap: BootstrapPrincipal, now?: Date): Promise<void>;
12
+ /**
13
+ * Whether the stored bootstrap, Studio and derived principals equal `bootstrap`. A retried
14
+ * create names the same derived principals, so each must be stored with its hash.
15
+ */
16
+ export declare function bootstrapPrincipalMatches(t: Tx, bootstrap: BootstrapPrincipal): Promise<boolean>;
@@ -1,44 +1,56 @@
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 one transaction.
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(db, bootstrap) {
6
- const now = new Date().toISOString();
7
- db.exec("BEGIN IMMEDIATE");
8
- try {
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: String(row.token_hash),
29
- idempotencyKey: row.idempotency_key == null ? null : String(row.idempotency_key),
30
- createdAt: String(row.created_at),
31
- };
32
- }
33
- export function applicationTokenHashes(db) {
34
- return db
35
- .prepare(`SELECT token_hash FROM principals WHERE role = 'application'`)
36
- .all()
37
- .map((row) => String(row.token_hash));
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
+ });
25
+ for (const principal of bootstrap.derivedPrincipals ?? [])
26
+ await t.insertPrincipal({
27
+ id: principal.id,
28
+ role: "application",
29
+ tokenHash: principal.credentialHash,
30
+ idempotencyKey: null,
31
+ createdAt,
32
+ });
38
33
  }
39
- export function isApplicationTokenHash(db, tokenHash) {
40
- const row = db
41
- .prepare(`SELECT 1 AS ok FROM principals WHERE role = 'application' AND token_hash = ?`)
42
- .get(tokenHash);
43
- return !!row;
34
+ /**
35
+ * Whether the stored bootstrap, Studio and derived principals equal `bootstrap`. A retried
36
+ * create names the same derived principals, so each must be stored with its hash.
37
+ */
38
+ export async function bootstrapPrincipalMatches(t, bootstrap) {
39
+ const row = await t.principalById(bootstrap.principalId);
40
+ if (!row ||
41
+ row.role !== "application" ||
42
+ row.tokenHash !== bootstrap.credentialHash ||
43
+ row.idempotencyKey !== bootstrap.idempotencyKey)
44
+ return false;
45
+ const studio = await t.principalById(STUDIO_PRINCIPAL_ID);
46
+ if ((studio?.role === "application" ? studio.tokenHash : undefined) !==
47
+ bootstrap.studioCredentialHash)
48
+ return false;
49
+ for (const principal of bootstrap.derivedPrincipals ?? []) {
50
+ const stored = await t.principalById(principal.id);
51
+ if (stored?.role !== "application" ||
52
+ stored.tokenHash !== principal.credentialHash)
53
+ return false;
54
+ }
55
+ return true;
44
56
  }
@@ -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, extra?: Pick<Quarantine, "lockPath" | "lockPid">);
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 = "nylorun tenant status", extra) {
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 `nylorun tenant status${id} — restore vault-kek for this Tenant (ciphertext cannot be opened without it)`;
35
+ return `nylo tenant status${id} — restore vault-kek for this Tenant (ciphertext cannot be opened without it)`;
53
36
  case "corrupt":
54
- return `nylorun tenant status${id} — repair or restore the Tenant directory from backup`;
37
+ return `nylo tenant status${id} — restore the Tenant from backup`;
55
38
  case "schema-too-new":
56
- return `nylorun runtime restart` +
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 detail.migrationPath
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 `nylorun tenant status${id} — fix or replace tenant.json so its id matches the directory name`;
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 `nylorun tenant status${id} — open timed out; inspect locks and SQLite, then retry`;
45
+ return `nylo tenant status${id} — open timed out; inspect the Runtime log and Postgres, then retry`;
66
46
  case "open-failed":
67
- return `nylorun tenant status${id} — inspect Tenant logs and repair before the Host retries open`;
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). */
@@ -1,21 +1,22 @@
1
- import type { Store } from "../core/store.js";
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 = "sessions" | "sandboxes" | "all";
5
+ export type { ResetScope } from "../store/types.js";
6
6
  export interface ResetTenantContext {
7
- store: Store;
7
+ store: SessionStore;
8
8
  registry: ExecutorRegistry;
9
9
  sandbox: SandboxManager;
10
10
  paths: TenantPaths;
11
- /** Clear in-memory session observers after DB wipe. */
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`). Runs SQLite work in one
19
- * transaction, then filesystem rename+delete for sandboxes / log as needed.
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>;
@@ -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`). Runs SQLite work in one
72
- * transaction, then filesystem rename+delete for sandboxes / log as needed.
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
- if (clearSessions)
83
- clearSessionTables(ctx.store);
84
- if (clearSandboxes)
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,137 @@
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 { ownerOf } from "./auth.js";
6
+ import { fail, readBody } from "./http.js";
7
+ import { clearExecutorStreams, clearObservers } from "./live.js";
8
+ import { requestStreamCollection, sessionStreamsAbandoned, streamsStatus, } from "./streams.js";
9
+ import { clearWork, drain } from "./scheduler.js";
10
+ import { usesFixtureModel } from "./model-setting.js";
11
+ export async function dispatchTenant(ctx, scope, method, path, request) {
12
+ const { vault } = ctx;
13
+ // A subject reaches only the routes `authorize` allowed its scopes.
14
+ if (scope.kind === "executor") {
15
+ await vault.reject(path.join("/"));
16
+ fail(403, "Application credential required");
17
+ }
18
+ if (path.length === 2 && method === "GET")
19
+ return await buildTenantStatus({
20
+ envelope: ctx.envelope,
21
+ config: ctx.config,
22
+ store: ctx.store,
23
+ registry: ctx.registry,
24
+ vault,
25
+ sandbox: ctx.sandbox,
26
+ closing: ctx.closing || ctx.closed,
27
+ modelConfigured: !ctx.useVaultModel ||
28
+ (await ctx.store.tx((t) => usesFixtureModel(t))) ||
29
+ (await vault.getHostModel()).configured,
30
+ executorStreams: ctx.live.executorStreams,
31
+ ...(ctx.stuckInvocations
32
+ ? { stuckInvocations: ctx.stuckInvocations }
33
+ : {}),
34
+ streamsStatus: () => streamsStatus(ctx),
35
+ });
36
+ if (path[2] === "reset" && path.length === 3 && method === "POST") {
37
+ const body = ResetTenantRequestSchema.parse(await readBody(request));
38
+ await drain(ctx, body.activeWork, 30_000);
39
+ // The deleted sessions' streams are abandoned: a session created again with the same id,
40
+ // during or after the reset, gets a new incarnation and starts at sequence 0. The pending
41
+ // collection is recorded first, so a crash before it runs leaves it to the sweep.
42
+ const sessionsReset = body.scope !== "sandboxes";
43
+ if (sessionsReset)
44
+ await requestStreamCollection(ctx);
45
+ await resetTenant({
46
+ store: ctx.store,
47
+ registry: ctx.registry,
48
+ sandbox: ctx.sandbox,
49
+ paths: ctx.config.paths,
50
+ clearSessionState: () => {
51
+ clearWork(ctx);
52
+ clearObservers(ctx.live);
53
+ },
54
+ clearExecutorStreams: () => clearExecutorStreams(ctx.live),
55
+ }, body.scope);
56
+ if (sessionsReset)
57
+ sessionStreamsAbandoned(ctx);
58
+ // Reset leaves the Tenant open for new work.
59
+ ctx.closing = false;
60
+ return { ok: true };
61
+ }
62
+ if (path[2] === "config" &&
63
+ path[3] === "seed" &&
64
+ path.length === 4 &&
65
+ method === "PUT") {
66
+ const body = SeedTenantConfigRequestSchema.parse(await readBody(request));
67
+ return await seedTenantConfig({ store: ctx.store, vault }, body);
68
+ }
69
+ if (path[2] === "models" && path.length === 3 && method === "GET")
70
+ return hostModelCatalog();
71
+ if (path[2] === "sandbox" && path.length === 3 && method === "GET")
72
+ return ctx.sandbox.report();
73
+ if (path[2] === "providers" && path.length === 3 && method === "GET")
74
+ return vault.listHostProviders();
75
+ if (path[2] === "model" && path.length === 3 && method === "GET")
76
+ return vault.getHostModel();
77
+ if (path[2] === "model" && path.length === 3 && method === "PUT")
78
+ return vault.putHostModel(PutHostModelRequestSchema.parse(await readBody(request)));
79
+ if (path[2] === "model" &&
80
+ path[3] === "selection" &&
81
+ path.length === 4 &&
82
+ method === "PUT")
83
+ return vault.selectHostModel(SelectHostModelRequestSchema.parse(await readBody(request)));
84
+ fail(404, "Route not found");
85
+ }
86
+ export async function dispatchVault(ctx, scope, method, path, url, request) {
87
+ const { vault } = ctx;
88
+ if (scope.kind === "executor") {
89
+ await vault.reject(path.join("/"));
90
+ fail(403, "Application credential required");
91
+ }
92
+ // Acting for a subject: only the subject's own vaults, and never on another's behalf.
93
+ const owner = ownerOf(scope);
94
+ if (path.length === 2 && method === "POST") {
95
+ const body = CreateVaultRequestSchema.parse(await readBody(request));
96
+ if (owner !== undefined && body.ownerUserId !== owner)
97
+ fail(403, "ownerUserId must be the subject");
98
+ return vault.createVault(body);
99
+ }
100
+ if (path.length === 2 && method === "GET") {
101
+ const ownerUserId = url.searchParams.get("ownerUserId") ??
102
+ owner ??
103
+ fail(400, "ownerUserId is required");
104
+ if (owner !== undefined && ownerUserId !== owner)
105
+ fail(403, "ownerUserId must be the subject");
106
+ return { vaults: await vault.listVaults(ownerUserId) };
107
+ }
108
+ const vaultId = path[2];
109
+ if (!vaultId)
110
+ fail(404, "Vault not found");
111
+ if (owner !== undefined)
112
+ await vault.assertOwner(vaultId, owner);
113
+ if (path.length === 3 && method === "GET")
114
+ return vault.getVault(vaultId);
115
+ if (path.length === 3 && method === "DELETE")
116
+ return vault.deleteVault(vaultId);
117
+ if (path[3] !== "credentials")
118
+ fail(404, "Route not found");
119
+ if (path.length === 4 && method === "POST") {
120
+ const body = CreateCredentialRequestSchema.parse(await readBody(request));
121
+ return vault.createCredential(vaultId, body);
122
+ }
123
+ if (path.length === 4 && method === "GET")
124
+ return { credentials: await vault.listCredentials(vaultId) };
125
+ const credentialId = path[4];
126
+ if (!credentialId)
127
+ fail(404, "Credential not found");
128
+ if (path.length === 5 && method === "GET")
129
+ return vault.getCredential(vaultId, credentialId);
130
+ if (path.length === 5 && method === "POST") {
131
+ const body = RotateCredentialRequestSchema.parse(await readBody(request));
132
+ return vault.rotateCredential(vaultId, credentialId, body);
133
+ }
134
+ if (path.length === 5 && method === "DELETE")
135
+ return vault.deleteCredential(vaultId, credentialId);
136
+ fail(404, "Route not found");
137
+ }
@@ -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>;