@nylorun/runtime 0.8.0-beta → 0.10.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 (130) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +96 -25
  3. package/dist/adapters/media.d.ts +2 -2
  4. package/dist/adapters/media.js +2 -2
  5. package/dist/adapters/observe.d.ts +2 -1
  6. package/dist/adapters/observe.js +2 -3
  7. package/dist/assets.d.ts +2 -2
  8. package/dist/assets.js +5 -3
  9. package/dist/core/executors.d.ts +11 -2
  10. package/dist/core/executors.js +20 -4
  11. package/dist/core/flow-host.d.ts +264 -0
  12. package/dist/core/flow-host.js +783 -0
  13. package/dist/core/limits.d.ts +40 -0
  14. package/dist/core/limits.js +76 -0
  15. package/dist/core/runtime.d.ts +5 -90
  16. package/dist/core/runtime.js +3 -1418
  17. package/dist/core/sandbox-routes.d.ts +35 -0
  18. package/dist/core/sandbox-routes.js +106 -0
  19. package/dist/core/store.d.ts +5 -2
  20. package/dist/core/store.js +20 -72
  21. package/dist/core/turn-manifest.d.ts +50 -0
  22. package/dist/core/turn-manifest.js +66 -0
  23. package/dist/host/config-for.d.ts +16 -0
  24. package/dist/host/config-for.js +45 -0
  25. package/dist/host/config.d.ts +24 -0
  26. package/dist/host/create-host.d.ts +21 -0
  27. package/dist/host/create-host.js +396 -0
  28. package/dist/host/environment.d.ts +13 -0
  29. package/dist/host/environment.js +53 -0
  30. package/dist/host/http.d.ts +35 -0
  31. package/dist/host/http.js +136 -0
  32. package/dist/host/index.d.ts +6 -0
  33. package/dist/host/index.js +5 -0
  34. package/dist/host/logger.d.ts +3 -0
  35. package/dist/host/logger.js +18 -0
  36. package/dist/host/main.d.ts +3 -0
  37. package/dist/host/main.js +142 -0
  38. package/dist/index.d.ts +7 -2
  39. package/dist/index.js +4 -2
  40. package/dist/launcher/commands.d.ts +24 -0
  41. package/dist/launcher/commands.js +260 -0
  42. package/dist/launcher/ensure-config.d.ts +16 -0
  43. package/dist/launcher/ensure-config.js +79 -0
  44. package/dist/launcher/errors.d.ts +12 -0
  45. package/dist/launcher/errors.js +22 -0
  46. package/dist/launcher/host-config.d.ts +17 -0
  47. package/dist/launcher/host-config.js +64 -0
  48. package/dist/launcher/host-state.d.ts +17 -0
  49. package/dist/launcher/host-state.js +61 -0
  50. package/dist/launcher/lifecycle.d.ts +66 -0
  51. package/dist/launcher/lifecycle.js +407 -0
  52. package/dist/launcher/locks.d.ts +26 -0
  53. package/dist/launcher/locks.js +117 -0
  54. package/dist/launcher/logs.d.ts +18 -0
  55. package/dist/launcher/logs.js +117 -0
  56. package/dist/launcher/main.d.ts +2 -0
  57. package/dist/launcher/main.js +66 -0
  58. package/dist/launcher/output.d.ts +11 -0
  59. package/dist/launcher/output.js +68 -0
  60. package/dist/launcher/paths.d.ts +20 -0
  61. package/dist/launcher/paths.js +36 -0
  62. package/dist/launcher/probe.d.ts +23 -0
  63. package/dist/launcher/probe.js +72 -0
  64. package/dist/launcher/protocol.d.ts +62 -0
  65. package/dist/launcher/protocol.js +1 -0
  66. package/dist/launcher/schema-guard.d.ts +6 -0
  67. package/dist/launcher/schema-guard.js +36 -0
  68. package/dist/launcher/spawn.d.ts +22 -0
  69. package/dist/launcher/spawn.js +85 -0
  70. package/dist/launcher/status.d.ts +6 -0
  71. package/dist/launcher/status.js +65 -0
  72. package/dist/mcp/connect.d.ts +1 -0
  73. package/dist/mcp/connect.js +2 -0
  74. package/dist/mcp/pool.d.ts +16 -2
  75. package/dist/mcp/pool.js +54 -19
  76. package/dist/mcp/snapshot.d.ts +4 -0
  77. package/dist/mcp/stdio.d.ts +2 -0
  78. package/dist/mcp/stdio.js +3 -1
  79. package/dist/model/auth-store.d.ts +1 -1
  80. package/dist/model/auth-store.js +2 -2
  81. package/dist/model/models.d.ts +4 -0
  82. package/dist/model/models.js +22 -26
  83. package/dist/model/pi-model.js +1 -1
  84. package/dist/model/settings.d.ts +5 -1
  85. package/dist/model/settings.js +6 -2
  86. package/dist/sandbox/select.d.ts +4 -3
  87. package/dist/sandbox/select.js +15 -9
  88. package/dist/sandbox/share.d.ts +23 -0
  89. package/dist/sandbox/share.js +58 -0
  90. package/dist/tenant/envelope.d.ts +10 -0
  91. package/dist/tenant/envelope.js +54 -0
  92. package/dist/tenant/ephemeral.d.ts +31 -0
  93. package/dist/tenant/ephemeral.js +156 -0
  94. package/dist/tenant/logger.d.ts +9 -0
  95. package/dist/tenant/logger.js +25 -0
  96. package/dist/tenant/migration.d.ts +16 -0
  97. package/dist/tenant/migration.js +116 -0
  98. package/dist/tenant/module.d.ts +17 -0
  99. package/dist/tenant/module.js +315 -0
  100. package/dist/tenant/paths.d.ts +24 -0
  101. package/dist/tenant/paths.js +65 -0
  102. package/dist/tenant/pool.d.ts +10 -0
  103. package/dist/tenant/pool.js +54 -0
  104. package/dist/tenant/principals.d.ts +17 -0
  105. package/dist/tenant/principals.js +44 -0
  106. package/dist/tenant/quarantine-error.d.ts +6 -0
  107. package/dist/tenant/quarantine-error.js +15 -0
  108. package/dist/tenant/quarantine.d.ts +35 -0
  109. package/dist/tenant/quarantine.js +94 -0
  110. package/dist/tenant/reset.d.ts +21 -0
  111. package/dist/tenant/reset.js +125 -0
  112. package/dist/tenant/runtime.d.ts +80 -0
  113. package/dist/tenant/runtime.js +2256 -0
  114. package/dist/tenant/schema.d.ts +23 -0
  115. package/dist/tenant/schema.js +151 -0
  116. package/dist/tenant/status.d.ts +28 -0
  117. package/dist/tenant/status.js +95 -0
  118. package/dist/tenant/store-fs.d.ts +16 -0
  119. package/dist/tenant/store-fs.js +205 -0
  120. package/dist/tenant/store-memory.d.ts +19 -0
  121. package/dist/tenant/store-memory.js +100 -0
  122. package/dist/tenant/types.d.ts +125 -0
  123. package/dist/tenant/types.js +1 -0
  124. package/dist/vault/kek.d.ts +5 -2
  125. package/dist/vault/kek.js +7 -10
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. package/package.json +9 -6
  129. package/dist/core/main.js +0 -35
  130. /package/dist/{core/main.d.ts → host/config.js} +0 -0
@@ -0,0 +1,54 @@
1
+ import { readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { TenantEnvelopeSchema, } from "@nylorun/core/contracts";
4
+ import { quarantine } from "./quarantine.js";
5
+ export function parseEnvelope(raw, expectedId) {
6
+ const parsed = TenantEnvelopeSchema.safeParse(raw);
7
+ if (!parsed.success) {
8
+ throw quarantine("envelope-invalid", "tenant.json is missing or not a valid Tenant envelope", expectedId ? { tenantId: expectedId } : {});
9
+ }
10
+ if (expectedId !== undefined && parsed.data.id !== expectedId) {
11
+ throw quarantine("envelope-invalid", `tenant.json id ${parsed.data.id} does not match directory ${expectedId}`, { tenantId: expectedId });
12
+ }
13
+ return parsed.data;
14
+ }
15
+ export function readEnvelopeFile(path, expectedId) {
16
+ let text;
17
+ try {
18
+ text = readFileSync(path, "utf8");
19
+ }
20
+ catch (error) {
21
+ const code = error.code;
22
+ if (code === "ENOENT") {
23
+ throw quarantine("envelope-invalid", "tenant.json is missing", expectedId ? { tenantId: expectedId } : {});
24
+ }
25
+ throw quarantine("corrupt", "tenant.json could not be read", expectedId ? { tenantId: expectedId } : {});
26
+ }
27
+ let json;
28
+ try {
29
+ json = JSON.parse(text);
30
+ }
31
+ catch {
32
+ throw quarantine("envelope-invalid", "tenant.json is not valid JSON", expectedId ? { tenantId: expectedId } : {});
33
+ }
34
+ return parseEnvelope(json, expectedId);
35
+ }
36
+ /** Write envelope via temp sibling then rename (atomic on the same filesystem). */
37
+ export function writeEnvelopeFile(envelopePath, envelope) {
38
+ const validated = parseEnvelope(envelope, envelope.id);
39
+ const tmp = join(envelopePath + `.tmp-${process.pid}-${Date.now()}`);
40
+ writeFileSync(tmp, `${JSON.stringify(validated, null, 2)}\n`, {
41
+ mode: 0o600,
42
+ });
43
+ renameSync(tmp, envelopePath);
44
+ }
45
+ export function envelopeNow(input, now = new Date()) {
46
+ const iso = now.toISOString();
47
+ return {
48
+ id: input.id,
49
+ name: input.name,
50
+ createdAt: iso,
51
+ updatedAt: iso,
52
+ schemaVersion: input.schemaVersion,
53
+ };
54
+ }
@@ -0,0 +1,31 @@
1
+ import type { Logger, TenantModelConfig } from "./types.js";
2
+ export interface StartEphemeralRuntimeOptions {
3
+ /** Absolute Host root. Caller creates any temporary directory. */
4
+ hostRoot: string;
5
+ tenantId?: string;
6
+ name?: string;
7
+ applicationKey?: string;
8
+ adminKey?: string;
9
+ principalId?: string;
10
+ /** Allowlisted baseline for childEnv (e.g. PATH). Never read from ambient here. */
11
+ baseline?: Readonly<Record<string, string>>;
12
+ model?: TenantModelConfig;
13
+ sandboxBackend?: "auto" | "microsandbox" | "virtual";
14
+ /** When true, close() leaves hostRoot on disk. */
15
+ retainRoot?: boolean;
16
+ logger?: Logger;
17
+ }
18
+ export interface EphemeralRuntime {
19
+ url: string;
20
+ tenantId: string;
21
+ applicationKey: string;
22
+ adminKey: string;
23
+ principalId: string;
24
+ hostRoot: string;
25
+ close(): Promise<void>;
26
+ }
27
+ /**
28
+ * Private Host on port 0 with one Tenant (D9 / A19).
29
+ * Exported from `@nylorun/runtime/core`.
30
+ */
31
+ export declare function startEphemeralRuntime(options: StartEphemeralRuntimeOptions): Promise<EphemeralRuntime>;
@@ -0,0 +1,156 @@
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync, } from "node:fs";
2
+ import { randomBytes } from "node:crypto";
3
+ import { createRequire } from "node:module";
4
+ import { DatabaseSync } from "node:sqlite";
5
+ import { newTenantId } from "@nylorun/core/compatibility";
6
+ import { hashToken, mintBearerToken } from "../core/executors.js";
7
+ import { createHost } from "../host/create-host.js";
8
+ import { createKekFile } from "../vault/kek.js";
9
+ import { createTenantModule } from "./module.js";
10
+ import { createFsTenantStore } from "./store-fs.js";
11
+ import { bootstrapPrincipal } from "./principals.js";
12
+ import { createTenantLogger } from "./logger.js";
13
+ import { hostPaths, tenantPaths } from "./paths.js";
14
+ import { openTenantRuntime } from "./runtime.js";
15
+ import { migrateTenantDatabase, readTenantConfig } from "./schema.js";
16
+ const nodeRequire = createRequire(import.meta.url);
17
+ function coreVersion() {
18
+ try {
19
+ return nodeRequire("@nylorun/core/package.json").version;
20
+ }
21
+ catch {
22
+ return "unknown";
23
+ }
24
+ }
25
+ function newHostId() {
26
+ return `host_${newTenantId().slice(3)}`;
27
+ }
28
+ /**
29
+ * Private Host on port 0 with one Tenant (D9 / A19).
30
+ * Exported from `@nylorun/runtime/core`.
31
+ */
32
+ export async function startEphemeralRuntime(options) {
33
+ const hostRoot = options.hostRoot;
34
+ const paths = hostPaths(hostRoot);
35
+ mkdirSync(paths.home, { recursive: true });
36
+ mkdirSync(paths.tmp, { recursive: true });
37
+ mkdirSync(paths.tenants, { recursive: true });
38
+ mkdirSync(paths.trash, { recursive: true });
39
+ const hostId = newHostId();
40
+ const adminKey = options.adminKey ?? randomBytes(32).toString("hex");
41
+ const hostConfig = {
42
+ hostId,
43
+ host: "127.0.0.1",
44
+ port: 0,
45
+ };
46
+ const credentials = { adminKey };
47
+ writeFileSync(paths.config, `${JSON.stringify(hostConfig, null, 2)}\n`, {
48
+ mode: 0o600,
49
+ });
50
+ writeFileSync(paths.credentials, `${JSON.stringify(credentials, null, 2)}\n`, { mode: 0o600 });
51
+ const logger = options.logger ??
52
+ {
53
+ info() { },
54
+ warn() { },
55
+ error() { },
56
+ };
57
+ const baseline = options.baseline ?? {};
58
+ const model = options.model ?? { kind: "scripted", output: "ok" };
59
+ const configFor = (tenantId) => {
60
+ const tenant = tenantPaths(hostRoot, tenantId);
61
+ let sandboxBackend = options.sandboxBackend ?? "virtual";
62
+ if (existsSync(tenant.database)) {
63
+ try {
64
+ const db = new DatabaseSync(tenant.database, { readOnly: true });
65
+ try {
66
+ const seeded = readTenantConfig(db).sandboxBackend;
67
+ if (seeded)
68
+ sandboxBackend = seeded;
69
+ }
70
+ finally {
71
+ db.close();
72
+ }
73
+ }
74
+ catch {
75
+ /* unreadable — keep default */
76
+ }
77
+ }
78
+ return {
79
+ tenantId,
80
+ mode: "ephemeral",
81
+ paths: tenant,
82
+ sandbox: { backend: sandboxBackend },
83
+ model,
84
+ childEnv: Object.freeze({
85
+ ...baseline,
86
+ HOME: tenant.home,
87
+ TMPDIR: tenant.tmp,
88
+ }),
89
+ logger: createTenantLogger({
90
+ tenantId,
91
+ logPath: tenant.log,
92
+ }),
93
+ };
94
+ };
95
+ const openRuntime = (config) => openTenantRuntime(config, { createKekIfMissing: true });
96
+ const store = createFsTenantStore({
97
+ hostRoot,
98
+ openRuntime,
99
+ configFor,
100
+ logger,
101
+ writeBootstrap: (db, bootstrap) => {
102
+ migrateTenantDatabase(db);
103
+ bootstrapPrincipal(db, bootstrap);
104
+ },
105
+ });
106
+ const module = createTenantModule({
107
+ hostRoot,
108
+ store,
109
+ openRuntime,
110
+ configFor,
111
+ logger,
112
+ });
113
+ await module.start();
114
+ const tenantId = options.tenantId ?? newTenantId();
115
+ const applicationKey = options.applicationKey ?? mintBearerToken();
116
+ const principalId = options.principalId ?? `principal_${randomBytes(8).toString("hex")}`;
117
+ const credentialHash = hashToken(applicationKey);
118
+ await module.create({
119
+ tenantId,
120
+ name: options.name ?? "ephemeral",
121
+ principalId,
122
+ credentialHash,
123
+ idempotencyKey: `ephemeral-${tenantId}`,
124
+ });
125
+ // KEK for first vault write; openTenantRuntime also creates when hooks allow.
126
+ const kekPath = tenantPaths(hostRoot, tenantId).kek;
127
+ if (!existsSync(kekPath))
128
+ createKekFile(kekPath);
129
+ const host = createHost({
130
+ hostRoot,
131
+ module,
132
+ config: hostConfig,
133
+ credentials,
134
+ logger,
135
+ coreVersion: coreVersion(),
136
+ });
137
+ await host.listen();
138
+ let closed = false;
139
+ return {
140
+ url: host.url,
141
+ tenantId,
142
+ applicationKey,
143
+ adminKey,
144
+ principalId,
145
+ hostRoot,
146
+ async close() {
147
+ if (closed)
148
+ return;
149
+ closed = true;
150
+ await host.close();
151
+ await module.close();
152
+ if (!options.retainRoot)
153
+ rmSync(hostRoot, { recursive: true, force: true });
154
+ },
155
+ };
156
+ }
@@ -0,0 +1,9 @@
1
+ import type { Logger } from "./types.js";
2
+ /**
3
+ * Tenant logger (D12): JSON lines to `logs/tenant.log` with `tenantId` on every record.
4
+ * Never writes to the process stdout/stderr APIs.
5
+ */
6
+ export declare function createTenantLogger(options: {
7
+ tenantId: string;
8
+ logPath: string;
9
+ }): Logger;
@@ -0,0 +1,25 @@
1
+ import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ /**
4
+ * Tenant logger (D12): JSON lines to `logs/tenant.log` with `tenantId` on every record.
5
+ * Never writes to the process stdout/stderr APIs.
6
+ */
7
+ export function createTenantLogger(options) {
8
+ const { tenantId, logPath } = options;
9
+ mkdirSync(dirname(logPath), { recursive: true });
10
+ const write = (level, message, fields) => {
11
+ const record = {
12
+ ts: new Date().toISOString(),
13
+ level,
14
+ tenantId,
15
+ message,
16
+ ...(fields ?? {}),
17
+ };
18
+ appendFileSync(logPath, `${JSON.stringify(record)}\n`);
19
+ };
20
+ return {
21
+ info: (message, fields) => write("info", message, fields),
22
+ warn: (message, fields) => write("warn", message, fields),
23
+ error: (message, fields) => write("error", message, fields),
24
+ };
25
+ }
@@ -0,0 +1,16 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import type { TenantPaths } from "./types.js";
3
+ import type { TenantEnvelope } from "@nylorun/core/contracts";
4
+ export interface MigrationHooks {
5
+ schemaVersionOf?: (db: DatabaseSync) => number;
6
+ migrateTenantDatabase?: (db: DatabaseSync) => {
7
+ from: number;
8
+ to: number;
9
+ };
10
+ targetVersion?: number;
11
+ }
12
+ /**
13
+ * Checkpoint WAL, snapshot database + envelope + KEK, migrate, update envelope.
14
+ * On failure restore the snapshot, keep diagnostics, and throw migration-failed.
15
+ */
16
+ export declare function migrateTenantWithSnapshot(paths: TenantPaths, envelope: TenantEnvelope, hooks?: MigrationHooks, now?: Date): TenantEnvelope;
@@ -0,0 +1,116 @@
1
+ import { copyFileSync, existsSync, mkdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { TENANT_SCHEMA_VERSION, migrateTenantDatabase as defaultMigrate, schemaVersionOf as defaultSchemaVersionOf, } from "./schema.js";
5
+ import { writeEnvelopeFile } from "./envelope.js";
6
+ import { quarantine } from "./quarantine.js";
7
+ function stamp(now) {
8
+ return now.toISOString().replaceAll(":", "-");
9
+ }
10
+ function copyIfPresent(from, to) {
11
+ if (existsSync(from))
12
+ copyFileSync(from, to);
13
+ }
14
+ /**
15
+ * Checkpoint WAL, snapshot database + envelope + KEK, migrate, update envelope.
16
+ * On failure restore the snapshot, keep diagnostics, and throw migration-failed.
17
+ */
18
+ export function migrateTenantWithSnapshot(paths, envelope, hooks = {}, now = new Date()) {
19
+ const schemaVersionOf = hooks.schemaVersionOf ?? defaultSchemaVersionOf;
20
+ const migrateTenantDatabase = hooks.migrateTenantDatabase ?? defaultMigrate;
21
+ const target = hooks.targetVersion ?? TENANT_SCHEMA_VERSION;
22
+ const db = new DatabaseSync(paths.database);
23
+ let from;
24
+ try {
25
+ from = schemaVersionOf(db);
26
+ }
27
+ catch (error) {
28
+ db.close();
29
+ throw error;
30
+ }
31
+ if (from > target) {
32
+ db.close();
33
+ throw quarantine("schema-too-new", `Tenant schema version ${from} is newer than Host ${target}`, { tenantId: envelope.id });
34
+ }
35
+ if (from === target) {
36
+ db.close();
37
+ return envelope;
38
+ }
39
+ const dirName = `${from}-${target}-${stamp(now)}`;
40
+ mkdirSync(paths.migration, { recursive: true });
41
+ const snapshotDir = join(paths.migration, dirName);
42
+ mkdirSync(snapshotDir, { recursive: true });
43
+ try {
44
+ // Checkpoint WAL so the database file is self-contained (invariant 7).
45
+ db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
46
+ db.close();
47
+ copyIfPresent(paths.database, join(snapshotDir, "tenant.sqlite"));
48
+ copyIfPresent(paths.envelope, join(snapshotDir, "tenant.json"));
49
+ copyIfPresent(paths.kek, join(snapshotDir, "vault-kek"));
50
+ // Side-car WAL/SHM if present.
51
+ copyIfPresent(`${paths.database}-wal`, join(snapshotDir, "tenant.sqlite-wal"));
52
+ copyIfPresent(`${paths.database}-shm`, join(snapshotDir, "tenant.sqlite-shm"));
53
+ const migrating = new DatabaseSync(paths.database);
54
+ let result;
55
+ try {
56
+ migrating.exec("BEGIN IMMEDIATE");
57
+ result = migrateTenantDatabase(migrating);
58
+ migrating.exec("COMMIT");
59
+ }
60
+ catch (error) {
61
+ try {
62
+ migrating.exec("ROLLBACK");
63
+ }
64
+ catch {
65
+ /* ignore */
66
+ }
67
+ migrating.close();
68
+ throw error;
69
+ }
70
+ migrating.close();
71
+ const updated = {
72
+ ...envelope,
73
+ schemaVersion: result.to,
74
+ updatedAt: now.toISOString(),
75
+ };
76
+ writeEnvelopeFile(paths.envelope, updated);
77
+ return updated;
78
+ }
79
+ catch (error) {
80
+ restoreSnapshot(paths, snapshotDir);
81
+ writeFileSync(join(snapshotDir, "failure.json"), `${JSON.stringify({
82
+ at: now.toISOString(),
83
+ message: error instanceof Error ? error.message : String(error),
84
+ }, null, 2)}\n`);
85
+ throw quarantine("migration-failed", error instanceof Error ? error.message : "migration failed", { tenantId: envelope.id, migrationPath: snapshotDir });
86
+ }
87
+ }
88
+ function restoreSnapshot(paths, snapshotDir) {
89
+ const dbSnap = join(snapshotDir, "tenant.sqlite");
90
+ const envSnap = join(snapshotDir, "tenant.json");
91
+ const kekSnap = join(snapshotDir, "vault-kek");
92
+ if (existsSync(dbSnap)) {
93
+ // Replace via temp rename so a partial copy cannot leave a half file.
94
+ const tmp = `${paths.database}.restore-tmp`;
95
+ copyFileSync(dbSnap, tmp);
96
+ renameSync(tmp, paths.database);
97
+ }
98
+ if (existsSync(envSnap)) {
99
+ const tmp = `${paths.envelope}.restore-tmp`;
100
+ copyFileSync(envSnap, tmp);
101
+ renameSync(tmp, paths.envelope);
102
+ }
103
+ if (existsSync(kekSnap)) {
104
+ const tmp = `${paths.kek}.restore-tmp`;
105
+ copyFileSync(kekSnap, tmp);
106
+ renameSync(tmp, paths.kek);
107
+ }
108
+ for (const side of ["-wal", "-shm"]) {
109
+ const snap = join(snapshotDir, `tenant.sqlite${side}`);
110
+ const dest = `${paths.database}${side}`;
111
+ if (existsSync(snap))
112
+ copyFileSync(snap, dest);
113
+ else if (existsSync(dest))
114
+ rmSync(dest, { force: true });
115
+ }
116
+ }
@@ -0,0 +1,17 @@
1
+ import { isQuarantineError, quarantine, TenantBusyError, TenantConflictError } from "./quarantine.js";
2
+ import type { Logger, OpenTenantRuntime, TenantConfig, TenantModule, TenantStore } from "./types.js";
3
+ export interface CreateTenantModuleOptions {
4
+ hostRoot: string;
5
+ store: TenantStore;
6
+ openRuntime: OpenTenantRuntime;
7
+ configFor: (tenantId: string) => TenantConfig;
8
+ logger: Logger;
9
+ openConcurrency?: number;
10
+ openTimeoutMs?: number;
11
+ }
12
+ /**
13
+ * Deep Tenant module (§8). Discovery, create, resolve, list, delete and
14
+ * summarize. Storage and Tenant Runtime are injected so WS-B tests without WS-A.
15
+ */
16
+ export declare function createTenantModule(options: CreateTenantModuleOptions): TenantModule;
17
+ export { TenantBusyError, TenantConflictError, isQuarantineError, quarantine, };