@lifeaitools/clauth 1.30.23 → 1.30.24

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 (40) hide show
  1. package/.clauth-skill/SKILL.md +111 -111
  2. package/README.md +25 -0
  3. package/cli/api.classify.test.js +75 -75
  4. package/cli/assets/codevelop/launcher-active.cmd.template +20 -20
  5. package/cli/assets/codevelop/launcher-static.cmd.template +7 -7
  6. package/cli/assets/codevelop/windows-terminal.profiles.json +48 -48
  7. package/cli/assets/watchdog.ps1 +42 -42
  8. package/cli/commands/agent-cron.js +396 -396
  9. package/cli/commands/agent-pool.js +1962 -1962
  10. package/cli/commands/codevelop.js +1190 -1190
  11. package/cli/commands/doctor.js +302 -302
  12. package/cli/commands/install.js +10 -10
  13. package/cli/commands/invite.js +175 -175
  14. package/cli/commands/join.js +179 -179
  15. package/cli/commands/npm.js +182 -182
  16. package/cli/commands/scrub.js +327 -327
  17. package/cli/commands/scrub.test.js +115 -115
  18. package/cli/commands/serve.js +41 -95
  19. package/cli/commands/watchdog.js +209 -209
  20. package/cli/conf-path.js +21 -21
  21. package/cli/enrollment-script.js +82 -82
  22. package/cli/fingerprint.js +143 -143
  23. package/cli/index.js +1053 -1053
  24. package/cli/lib/fs-git.js +282 -282
  25. package/cli/recovery.js +101 -101
  26. package/cli/studio-debug.js +1095 -1095
  27. package/cli/supervisor-registry.js +594 -589
  28. package/cli/supervisor-registry.test.js +397 -397
  29. package/cli/supervisor-ui.test.js +5 -83
  30. package/cli/watchdog-registry.js +209 -209
  31. package/cli/watchdog-registry.test.js +89 -89
  32. package/install.ps1 +21 -21
  33. package/package.json +2 -2
  34. package/scripts/bin/bootstrap-linux +0 -0
  35. package/scripts/bin/bootstrap-macos +0 -0
  36. package/scripts/bin/bootstrap-win.exe +0 -0
  37. package/supabase/migrations/001_clauth_schema.sql +12 -12
  38. package/supabase/migrations/003_clauth_config.sql +13 -13
  39. package/supabase/migrations/003_machine_enrollments.sql +39 -39
  40. package/cli/served-script-syntax.test.mjs +0 -54
@@ -1,397 +1,397 @@
1
- import assert from "node:assert/strict";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import test from "node:test";
6
-
7
- import {
8
- addTunnelRoute,
9
- discoverPlugins,
10
- getClauthPm2Home,
11
- listPlugins,
12
- listSurfaces,
13
- reconcileSurfaceHealth,
14
- runPluginAction,
15
- runSurfaceAction,
16
- removeTunnelRoute,
17
- setPluginEnabled,
18
- supervisorHealth,
19
- validatePluginManifest,
20
- } from "./supervisor-registry.js";
21
- import { isLoopbackAddress, supervisorLogDto, supervisorRequiresWriteToken } from "./commands/serve.js";
22
-
23
- function withTempSupervisor(fn) {
24
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-supervisor-"));
25
- const oldDir = process.env.CLAUTH_SUPERVISOR_DIR;
26
- const oldManaged = process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
27
- const oldUser = process.env.CLAUTH_USER_PLUGIN_ROOTS;
28
- const oldPm2 = process.env.CLAUTH_PM2_HOME;
29
- process.env.CLAUTH_SUPERVISOR_DIR = root;
30
- delete process.env.CLAUTH_PM2_HOME;
31
- try {
32
- return fn(root);
33
- } finally {
34
- if (oldDir === undefined) delete process.env.CLAUTH_SUPERVISOR_DIR;
35
- else process.env.CLAUTH_SUPERVISOR_DIR = oldDir;
36
- if (oldManaged === undefined) delete process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
37
- else process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = oldManaged;
38
- if (oldUser === undefined) delete process.env.CLAUTH_USER_PLUGIN_ROOTS;
39
- else process.env.CLAUTH_USER_PLUGIN_ROOTS = oldUser;
40
- if (oldPm2 === undefined) delete process.env.CLAUTH_PM2_HOME;
41
- else process.env.CLAUTH_PM2_HOME = oldPm2;
42
- fs.rmSync(root, { recursive: true, force: true });
43
- }
44
- }
45
-
46
- function writePlugin(root, source, id, manifest) {
47
- const dir = path.join(root, source, id);
48
- fs.mkdirSync(dir, { recursive: true });
49
- fs.writeFileSync(path.join(dir, "clauth-plugin.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
50
- return dir;
51
- }
52
-
53
- function baseManifest(id, overrides = {}) {
54
- return {
55
- schema: "lifeai.plugin.v1",
56
- id,
57
- version: "1.0.0",
58
- publisher: "LIFEAI",
59
- credentials: [{ name: `${id}-secret`, key_type: "secret", description: "fixture key" }],
60
- surfaces: [{
61
- id: `${id}-surface`,
62
- destination: "local/clauth/pm2",
63
- lifecycle_owner: "clauth",
64
- port: 39111,
65
- health: "/health",
66
- restart: ["node", "--version"],
67
- }],
68
- test: { command: ["node", "--version"], port: "auto", health: "/health", selfTest: [["node", "--version"]] },
69
- documentation: {
70
- architecture: "docs/systems/example/ARCHITECTURE.md",
71
- operator_guide: "docs/systems/example/OPERATE.md",
72
- tool_reference: ".claude/context/mcp-endpoint-design.md",
73
- },
74
- ...overrides,
75
- };
76
- }
77
-
78
- test("validatePluginManifest accepts LIFEAI plugin contract with isolated test context", () => {
79
- const plugin = validatePluginManifest(baseManifest("regen-media-local"), "clauth-plugin.json");
80
- assert.equal(plugin.schema, "lifeai.plugin.v1");
81
- assert.equal(plugin.test.port, "auto");
82
- assert.equal(plugin.surfaces[0].destination, "local/clauth/pm2");
83
- assert.equal(plugin.surfaces[0].lifecycle_owner, "clauth");
84
- assert.equal(plugin.surfaces[0].health, "http://127.0.0.1:39111/health");
85
- assert.equal(plugin.documentation.architecture, "docs/systems/example/ARCHITECTURE.md");
86
- assert.equal(plugin.documentation.operator_guide, "docs/systems/example/OPERATE.md");
87
- });
88
-
89
- test("validatePluginManifest accepts empty test command arrays from the v1 template", () => {
90
- const plugin = validatePluginManifest(baseManifest("empty-test-command", {
91
- test: { command: [], port: "auto", health: "/health", selfTest: [] },
92
- }), "clauth-plugin.json");
93
- assert.deepEqual(plugin.test.command, []);
94
- assert.equal(plugin.test.port, "auto");
95
- });
96
-
97
- test("validatePluginManifest rejects invalid manifests and non-local health URLs", () => {
98
- assert.throws(() => validatePluginManifest({ ...baseManifest("bad"), schema: "bad" }), /schema/);
99
- assert.throws(() => validatePluginManifest(baseManifest("bad", {
100
- surfaces: [{ id: "bad", health: "https://example.com/health" }],
101
- })), /localhost-only/);
102
- assert.throws(() => validatePluginManifest(baseManifest("bad-docs", {
103
- documentation: { architecture: "../outside.md" },
104
- })), /repository-relative/);
105
- assert.throws(() => validatePluginManifest(baseManifest("bad-docs", {
106
- documentation: { operator_guide: "docs/guide.md" },
107
- })), /documentation.architecture is required/);
108
- assert.throws(() => validatePluginManifest(baseManifest("bad-docs", {
109
- documentation: { architecture: "https://example.com/architecture.md" },
110
- })), /repository-relative/);
111
- });
112
-
113
- test("discovery merges managed and user roots without silent managed-id shadowing", () => withTempSupervisor((root) => {
114
- const managed = path.join(root, "managed");
115
- const user = path.join(root, "user");
116
- process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
117
- process.env.CLAUTH_USER_PLUGIN_ROOTS = user;
118
- writePlugin(root, "managed", "regen-media-local", baseManifest("regen-media-local"));
119
- writePlugin(root, "user", "regen-media-local", baseManifest("regen-media-local", { version: "2.0.0" }));
120
-
121
- const result = discoverPlugins();
122
- const managedPlugin = result.plugins.find((plugin) => plugin.id === "regen-media-local" && plugin.source === "managed");
123
- const invalid = result.plugins.find((plugin) => plugin.id === "regen-media-local" && plugin.source === "user");
124
- assert.equal(managedPlugin.state, "awaiting_enable");
125
- assert.equal(invalid.state, "manifest_invalid");
126
- assert.match(invalid.error, /shadow/);
127
- }));
128
-
129
- test("trusted managed core plugins auto-enable while user plugins remain opt-in", () => withTempSupervisor((root) => {
130
- const managed = path.join(root, "managed");
131
- const user = path.join(root, "user");
132
- process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
133
- process.env.CLAUTH_USER_PLUGIN_ROOTS = user;
134
- writePlugin(root, "managed", "core-mcp", baseManifest("core-mcp", { core: true, enable_default: true }));
135
- writePlugin(root, "user", "user-mcp", baseManifest("user-mcp", { core: true, enable_default: true }));
136
-
137
- const result = discoverPlugins();
138
- const core = result.plugins.find((plugin) => plugin.id === "core-mcp");
139
- const userPlugin = result.plugins.find((plugin) => plugin.id === "user-mcp");
140
- assert.equal(core.enabled, true);
141
- assert.equal(core.state, "current");
142
- assert.equal(result.events.some((event) => event.kind === "core_plugin_auto_enabled" && event.plugin_id === "core-mcp"), true);
143
- assert.equal(userPlugin.enabled, false);
144
- assert.equal(userPlugin.state, "awaiting_enable");
145
- }));
146
-
147
- test("plugin test marks a private candidate and never creates a public route", () => withTempSupervisor((root) => {
148
- const managed = path.join(root, "managed");
149
- process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
150
- process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
151
- writePlugin(root, "managed", "regen-media-local", baseManifest("regen-media-local"));
152
- discoverPlugins();
153
-
154
- const enabled = setPluginEnabled("regen-media-local", true);
155
- assert.equal(enabled.resulting_state.enabled, true);
156
- const receipt = runPluginAction("regen-media-local", "test");
157
- assert.equal(receipt.resulting_state.state, "candidate_testing");
158
- assert.equal(receipt.resulting_state.public_route, false);
159
- assert.equal(listPlugins().find((plugin) => plugin.id === "regen-media-local").candidate.public_route, false);
160
- }));
161
-
162
- test("surface actions use dedicated clauth PM2 home and keep CodeFlow observe-only", () => withTempSupervisor((root) => {
163
- const managed = path.join(root, "managed");
164
- process.env.REGEN_ROOT = "C:/Dev/regen-root";
165
- process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
166
- process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
167
- writePlugin(root, "managed", "demo", baseManifest("demo", {
168
- surfaces: [{
169
- id: "demo-surface",
170
- name: "Demo surface",
171
- health: "http://127.0.0.1:3333/health",
172
- cwd: "$REGEN_ROOT/mcp-servers/regen-media",
173
- restart: [process.execPath, "--version"],
174
- }],
175
- }));
176
- writePlugin(root, "managed", "codeflow", baseManifest("codeflow", {
177
- surfaces: [{ id: "codeflow-mcp", lifecycle_owner: "clauth", destination: "local/clauth/pm2", restart: ["node", "--version"] }],
178
- }));
179
- discoverPlugins();
180
- const receipt = runSurfaceAction("demo:demo-surface", "restart");
181
- assert.equal(receipt.resulting_state.ok, true);
182
- assert.match(receipt.resulting_state.evidence[0], /CLAUTH_PM2_HOME/);
183
- assert.equal(receipt.prior_state.cwd.replace(/\\/g, "/"), "C:/Dev/regen-root/mcp-servers/regen-media");
184
- assert.equal(getClauthPm2Home(), path.join(root, "pm2-home"));
185
-
186
- const codeflow = runSurfaceAction("codeflow:codeflow-mcp", "restart");
187
- assert.equal(codeflow.resulting_state.ok, false);
188
- assert.equal(codeflow.resulting_state.reason, "codeflow_self_owned");
189
- assert.equal(listSurfaces().length, 2);
190
- assert.equal(supervisorHealth().surfaces, 2);
191
- }));
192
-
193
- test("reconcile falls back to the declared start command when restart reports a missing process", () => withTempSupervisor((root) => {
194
- const managed = path.join(root, "managed");
195
- process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
196
- process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
197
- writePlugin(root, "managed", "fallback-demo", baseManifest("fallback-demo", {
198
- core: true,
199
- enable_default: true,
200
- surfaces: [{
201
- id: "primary",
202
- destination: "local/clauth/pm2",
203
- lifecycle_owner: "clauth",
204
- port: 39114,
205
- health: "/health",
206
- start: [process.execPath, "--version"],
207
- restart: [process.execPath, "-e", "process.exit(1)"],
208
- }],
209
- }));
210
- discoverPlugins();
211
- const receipt = runSurfaceAction("fallback-demo:primary", "reconcile");
212
- assert.equal(receipt.resulting_state.ok, true);
213
- assert.equal(receipt.resulting_state.evidence.includes("reconcile_start_fallback=true"), true);
214
- }));
215
-
216
- test("surface promote and rollback never fall through to restart commands", () => withTempSupervisor((root) => {
217
- const managed = path.join(root, "managed");
218
- process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
219
- process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
220
- writePlugin(root, "managed", "demo", baseManifest("demo", {
221
- surfaces: [{
222
- id: "demo-surface",
223
- name: "Demo surface",
224
- health: "http://127.0.0.1:3333/health",
225
- restart: [process.execPath, "--version"],
226
- }],
227
- }));
228
- discoverPlugins();
229
-
230
- for (const action of ["promote", "rollback"]) {
231
- const receipt = runSurfaceAction("demo:demo-surface", action);
232
- assert.equal(receipt.resulting_state.ok, false);
233
- assert.equal(receipt.resulting_state.state, "unsupported_surface_action");
234
- assert.equal(receipt.resulting_state.evidence[0], "surface action did not execute a process command");
235
- }
236
- }));
237
-
238
- test("health reconciliation marks a failed clauth surface and repairs it through reconcile", async () => {
239
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-supervisor-health-"));
240
- const oldDir = process.env.CLAUTH_SUPERVISOR_DIR;
241
- const oldManaged = process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
242
- const oldUser = process.env.CLAUTH_USER_PLUGIN_ROOTS;
243
- process.env.CLAUTH_SUPERVISOR_DIR = root;
244
- process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
245
- process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
246
- try {
247
- writePlugin(root, "managed", "health-demo", baseManifest("health-demo", {
248
- core: true,
249
- enable_default: true,
250
- surfaces: [{
251
- id: "primary",
252
- destination: "local/clauth/pm2",
253
- lifecycle_owner: "clauth",
254
- port: 39111,
255
- health: "/health",
256
- restart: [process.execPath, "--version"],
257
- }],
258
- }));
259
- discoverPlugins();
260
- let healthCalls = 0;
261
- const result = await reconcileSurfaceHealth({
262
- fetchImpl: async () => ({ ok: ++healthCalls > 1, status: 503 }),
263
- });
264
- assert.equal(result.inspected[0].surface_id, "health-demo:primary");
265
- assert.equal(result.inspected[0].state, "reconciled");
266
- const surface = listSurfaces().find((item) => item.plugin_id === "health-demo");
267
- assert.equal(surface.state, "current");
268
- assert.ok(surface.last_reconcile_operation_id);
269
- assert.equal(healthCalls, 2);
270
- } finally {
271
- if (oldDir === undefined) delete process.env.CLAUTH_SUPERVISOR_DIR;
272
- else process.env.CLAUTH_SUPERVISOR_DIR = oldDir;
273
- if (oldManaged === undefined) delete process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
274
- else process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = oldManaged;
275
- if (oldUser === undefined) delete process.env.CLAUTH_USER_PLUGIN_ROOTS;
276
- else process.env.CLAUTH_USER_PLUGIN_ROOTS = oldUser;
277
- fs.rmSync(root, { recursive: true, force: true });
278
- }
279
- });
280
-
281
- test("health reconciliation does not claim current when a successful command leaves health down", async () => {
282
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-supervisor-post-health-"));
283
- const oldDir = process.env.CLAUTH_SUPERVISOR_DIR;
284
- const oldManaged = process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
285
- const oldUser = process.env.CLAUTH_USER_PLUGIN_ROOTS;
286
- process.env.CLAUTH_SUPERVISOR_DIR = root;
287
- process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
288
- process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
289
- try {
290
- writePlugin(root, "managed", "post-health-demo", baseManifest("post-health-demo", {
291
- core: true,
292
- enable_default: true,
293
- surfaces: [{ id: "primary", destination: "local/clauth/pm2", lifecycle_owner: "clauth", port: 39113, health: "/health", restart: [process.execPath, "--version"] }],
294
- }));
295
- discoverPlugins();
296
- const result = await reconcileSurfaceHealth({ fetchImpl: async () => ({ ok: false, status: 503 }) });
297
- assert.equal(result.inspected[0].state, "reconcile_failed");
298
- assert.equal(listSurfaces()[0].state, "unavailable");
299
- assert.equal(listSurfaces()[0].last_health_ok, false);
300
- } finally {
301
- if (oldDir === undefined) delete process.env.CLAUTH_SUPERVISOR_DIR;
302
- else process.env.CLAUTH_SUPERVISOR_DIR = oldDir;
303
- if (oldManaged === undefined) delete process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
304
- else process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = oldManaged;
305
- if (oldUser === undefined) delete process.env.CLAUTH_USER_PLUGIN_ROOTS;
306
- else process.env.CLAUTH_USER_PLUGIN_ROOTS = oldUser;
307
- fs.rmSync(root, { recursive: true, force: true });
308
- }
309
- });
310
-
311
- test("health reconciliation never restarts external or plugin-owned surfaces", async () => {
312
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-supervisor-observe-"));
313
- const oldDir = process.env.CLAUTH_SUPERVISOR_DIR;
314
- const oldManaged = process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
315
- const oldUser = process.env.CLAUTH_USER_PLUGIN_ROOTS;
316
- process.env.CLAUTH_SUPERVISOR_DIR = root;
317
- process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
318
- process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
319
- try {
320
- writePlugin(root, "managed", "external-demo", baseManifest("external-demo", {
321
- core: true,
322
- enable_default: true,
323
- destination: "vultr/clauth/pm2",
324
- lifecycle_owner: "external",
325
- surfaces: [{ id: "primary", destination: "vultr/clauth/pm2", lifecycle_owner: "external", port: 39112, health: "/health" }],
326
- }));
327
- discoverPlugins();
328
- const result = await reconcileSurfaceHealth({ fetchImpl: async () => ({ ok: false, status: 503 }) });
329
- assert.deepEqual(result.inspected, []);
330
- assert.equal(listSurfaces()[0].state, "current");
331
- } finally {
332
- if (oldDir === undefined) delete process.env.CLAUTH_SUPERVISOR_DIR;
333
- else process.env.CLAUTH_SUPERVISOR_DIR = oldDir;
334
- if (oldManaged === undefined) delete process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
335
- else process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = oldManaged;
336
- if (oldUser === undefined) delete process.env.CLAUTH_USER_PLUGIN_ROOTS;
337
- else process.env.CLAUTH_USER_PLUGIN_ROOTS = oldUser;
338
- fs.rmSync(root, { recursive: true, force: true });
339
- }
340
- });
341
-
342
- test("supervisor write-token policy is temporarily relaxed only for the localhost supervisor port", () => {
343
- assert.equal(supervisorRequiresWriteToken(52439, {}), false);
344
- assert.equal(supervisorRequiresWriteToken(52439, { CLAUTH_SUPERVISOR_REQUIRE_WRITE_TOKEN: "1" }), true);
345
- assert.equal(supervisorRequiresWriteToken(52437, {}), true);
346
- assert.equal(isLoopbackAddress("127.0.0.1"), true);
347
- assert.equal(isLoopbackAddress("::1"), true);
348
- assert.equal(isLoopbackAddress("::ffff:127.0.0.1"), true);
349
- assert.equal(isLoopbackAddress("192.168.1.25"), false);
350
- });
351
-
352
- test("supervisor log DTO strips raw operation payloads", () => {
353
- const dto = supervisorLogDto({
354
- ts: "2026-07-30T00:00:00.000Z",
355
- kind: "operation",
356
- operationId: "op-secret",
357
- actor: "localhost",
358
- action: "restart",
359
- target: { surface_id: "demo", command: ["node", "--token=secret"] },
360
- prior_state: { start: ["node", "--token=secret"], cwd: "C:/secrets" },
361
- resulting_state: {
362
- ok: false,
363
- state: "operation_failed",
364
- stderr: "SECRET=abc123",
365
- evidence: ["CLAUTH_PM2_HOME=C:/safe"],
366
- },
367
- stderr: "SECRET=abc123",
368
- completed_at: "2026-07-30T00:00:01.000Z",
369
- });
370
-
371
- const json = JSON.stringify(dto);
372
- assert.equal(dto.operationId, "op-secret");
373
- assert.equal(dto.target.surface_id, "demo");
374
- assert.equal(dto.resulting_state.state, "operation_failed");
375
- assert.equal(json.includes("prior_state"), false);
376
- assert.equal(json.includes("stderr"), false);
377
- assert.equal(json.includes("SECRET"), false);
378
- assert.equal(json.includes("--token"), false);
379
- });
380
-
381
- test("tunnel route add and remove produce reversible operation receipts", () => withTempSupervisor((root) => {
382
- process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
383
- process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
384
- discoverPlugins();
385
-
386
- const added = addTunnelRoute("cf-main", {
387
- id: "route-1",
388
- hostname: "media.example.test",
389
- service_url: "http://127.0.0.1:3120",
390
- });
391
- assert.equal(added.resulting_state.ok, true);
392
- assert.equal(added.resulting_state.state, "route_recorded");
393
-
394
- const removed = removeTunnelRoute("cf-main", "route-1");
395
- assert.equal(removed.resulting_state.ok, true);
396
- assert.equal(removed.resulting_state.state, "route_removed");
397
- }));
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import {
8
+ addTunnelRoute,
9
+ discoverPlugins,
10
+ getClauthPm2Home,
11
+ listPlugins,
12
+ listSurfaces,
13
+ reconcileSurfaceHealth,
14
+ runPluginAction,
15
+ runSurfaceAction,
16
+ removeTunnelRoute,
17
+ setPluginEnabled,
18
+ supervisorHealth,
19
+ validatePluginManifest,
20
+ } from "./supervisor-registry.js";
21
+ import { isLoopbackAddress, supervisorLogDto, supervisorRequiresWriteToken } from "./commands/serve.js";
22
+
23
+ function withTempSupervisor(fn) {
24
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-supervisor-"));
25
+ const oldDir = process.env.CLAUTH_SUPERVISOR_DIR;
26
+ const oldManaged = process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
27
+ const oldUser = process.env.CLAUTH_USER_PLUGIN_ROOTS;
28
+ const oldPm2 = process.env.CLAUTH_PM2_HOME;
29
+ process.env.CLAUTH_SUPERVISOR_DIR = root;
30
+ delete process.env.CLAUTH_PM2_HOME;
31
+ try {
32
+ return fn(root);
33
+ } finally {
34
+ if (oldDir === undefined) delete process.env.CLAUTH_SUPERVISOR_DIR;
35
+ else process.env.CLAUTH_SUPERVISOR_DIR = oldDir;
36
+ if (oldManaged === undefined) delete process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
37
+ else process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = oldManaged;
38
+ if (oldUser === undefined) delete process.env.CLAUTH_USER_PLUGIN_ROOTS;
39
+ else process.env.CLAUTH_USER_PLUGIN_ROOTS = oldUser;
40
+ if (oldPm2 === undefined) delete process.env.CLAUTH_PM2_HOME;
41
+ else process.env.CLAUTH_PM2_HOME = oldPm2;
42
+ fs.rmSync(root, { recursive: true, force: true });
43
+ }
44
+ }
45
+
46
+ function writePlugin(root, source, id, manifest) {
47
+ const dir = path.join(root, source, id);
48
+ fs.mkdirSync(dir, { recursive: true });
49
+ fs.writeFileSync(path.join(dir, "clauth-plugin.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
50
+ return dir;
51
+ }
52
+
53
+ function baseManifest(id, overrides = {}) {
54
+ return {
55
+ schema: "lifeai.plugin.v1",
56
+ id,
57
+ version: "1.0.0",
58
+ publisher: "LIFEAI",
59
+ credentials: [{ name: `${id}-secret`, key_type: "secret", description: "fixture key" }],
60
+ surfaces: [{
61
+ id: `${id}-surface`,
62
+ destination: "local/clauth/pm2",
63
+ lifecycle_owner: "clauth",
64
+ port: 39111,
65
+ health: "/health",
66
+ restart: ["node", "--version"],
67
+ }],
68
+ test: { command: ["node", "--version"], port: "auto", health: "/health", selfTest: [["node", "--version"]] },
69
+ documentation: {
70
+ architecture: "docs/systems/example/ARCHITECTURE.md",
71
+ operator_guide: "docs/systems/example/OPERATE.md",
72
+ tool_reference: ".claude/context/mcp-endpoint-design.md",
73
+ },
74
+ ...overrides,
75
+ };
76
+ }
77
+
78
+ test("validatePluginManifest accepts LIFEAI plugin contract with isolated test context", () => {
79
+ const plugin = validatePluginManifest(baseManifest("regen-media-local"), "clauth-plugin.json");
80
+ assert.equal(plugin.schema, "lifeai.plugin.v1");
81
+ assert.equal(plugin.test.port, "auto");
82
+ assert.equal(plugin.surfaces[0].destination, "local/clauth/pm2");
83
+ assert.equal(plugin.surfaces[0].lifecycle_owner, "clauth");
84
+ assert.equal(plugin.surfaces[0].health, "http://127.0.0.1:39111/health");
85
+ assert.equal(plugin.documentation.architecture, "docs/systems/example/ARCHITECTURE.md");
86
+ assert.equal(plugin.documentation.operator_guide, "docs/systems/example/OPERATE.md");
87
+ });
88
+
89
+ test("validatePluginManifest accepts empty test command arrays from the v1 template", () => {
90
+ const plugin = validatePluginManifest(baseManifest("empty-test-command", {
91
+ test: { command: [], port: "auto", health: "/health", selfTest: [] },
92
+ }), "clauth-plugin.json");
93
+ assert.deepEqual(plugin.test.command, []);
94
+ assert.equal(plugin.test.port, "auto");
95
+ });
96
+
97
+ test("validatePluginManifest rejects invalid manifests and non-local health URLs", () => {
98
+ assert.throws(() => validatePluginManifest({ ...baseManifest("bad"), schema: "bad" }), /schema/);
99
+ assert.throws(() => validatePluginManifest(baseManifest("bad", {
100
+ surfaces: [{ id: "bad", health: "https://example.com/health" }],
101
+ })), /localhost-only/);
102
+ assert.throws(() => validatePluginManifest(baseManifest("bad-docs", {
103
+ documentation: { architecture: "../outside.md" },
104
+ })), /repository-relative/);
105
+ assert.throws(() => validatePluginManifest(baseManifest("bad-docs", {
106
+ documentation: { operator_guide: "docs/guide.md" },
107
+ })), /documentation.architecture is required/);
108
+ assert.throws(() => validatePluginManifest(baseManifest("bad-docs", {
109
+ documentation: { architecture: "https://example.com/architecture.md" },
110
+ })), /repository-relative/);
111
+ });
112
+
113
+ test("discovery merges managed and user roots without silent managed-id shadowing", () => withTempSupervisor((root) => {
114
+ const managed = path.join(root, "managed");
115
+ const user = path.join(root, "user");
116
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
117
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = user;
118
+ writePlugin(root, "managed", "regen-media-local", baseManifest("regen-media-local"));
119
+ writePlugin(root, "user", "regen-media-local", baseManifest("regen-media-local", { version: "2.0.0" }));
120
+
121
+ const result = discoverPlugins();
122
+ const managedPlugin = result.plugins.find((plugin) => plugin.id === "regen-media-local" && plugin.source === "managed");
123
+ const invalid = result.plugins.find((plugin) => plugin.id === "regen-media-local" && plugin.source === "user");
124
+ assert.equal(managedPlugin.state, "awaiting_enable");
125
+ assert.equal(invalid.state, "manifest_invalid");
126
+ assert.match(invalid.error, /shadow/);
127
+ }));
128
+
129
+ test("trusted managed core plugins auto-enable while user plugins remain opt-in", () => withTempSupervisor((root) => {
130
+ const managed = path.join(root, "managed");
131
+ const user = path.join(root, "user");
132
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
133
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = user;
134
+ writePlugin(root, "managed", "core-mcp", baseManifest("core-mcp", { core: true, enable_default: true }));
135
+ writePlugin(root, "user", "user-mcp", baseManifest("user-mcp", { core: true, enable_default: true }));
136
+
137
+ const result = discoverPlugins();
138
+ const core = result.plugins.find((plugin) => plugin.id === "core-mcp");
139
+ const userPlugin = result.plugins.find((plugin) => plugin.id === "user-mcp");
140
+ assert.equal(core.enabled, true);
141
+ assert.equal(core.state, "current");
142
+ assert.equal(result.events.some((event) => event.kind === "core_plugin_auto_enabled" && event.plugin_id === "core-mcp"), true);
143
+ assert.equal(userPlugin.enabled, false);
144
+ assert.equal(userPlugin.state, "awaiting_enable");
145
+ }));
146
+
147
+ test("plugin test marks a private candidate and never creates a public route", () => withTempSupervisor((root) => {
148
+ const managed = path.join(root, "managed");
149
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
150
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
151
+ writePlugin(root, "managed", "regen-media-local", baseManifest("regen-media-local"));
152
+ discoverPlugins();
153
+
154
+ const enabled = setPluginEnabled("regen-media-local", true);
155
+ assert.equal(enabled.resulting_state.enabled, true);
156
+ const receipt = runPluginAction("regen-media-local", "test");
157
+ assert.equal(receipt.resulting_state.state, "candidate_testing");
158
+ assert.equal(receipt.resulting_state.public_route, false);
159
+ assert.equal(listPlugins().find((plugin) => plugin.id === "regen-media-local").candidate.public_route, false);
160
+ }));
161
+
162
+ test("surface actions use dedicated clauth PM2 home and keep CodeFlow observe-only", () => withTempSupervisor((root) => {
163
+ const managed = path.join(root, "managed");
164
+ process.env.REGEN_ROOT = "C:/Dev/regen-root";
165
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
166
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
167
+ writePlugin(root, "managed", "demo", baseManifest("demo", {
168
+ surfaces: [{
169
+ id: "demo-surface",
170
+ name: "Demo surface",
171
+ health: "http://127.0.0.1:3333/health",
172
+ cwd: "$REGEN_ROOT/mcp-servers/regen-media",
173
+ restart: [process.execPath, "--version"],
174
+ }],
175
+ }));
176
+ writePlugin(root, "managed", "codeflow", baseManifest("codeflow", {
177
+ surfaces: [{ id: "codeflow-mcp", lifecycle_owner: "clauth", destination: "local/clauth/pm2", restart: ["node", "--version"] }],
178
+ }));
179
+ discoverPlugins();
180
+ const receipt = runSurfaceAction("demo:demo-surface", "restart");
181
+ assert.equal(receipt.resulting_state.ok, true);
182
+ assert.match(receipt.resulting_state.evidence[0], /CLAUTH_PM2_HOME/);
183
+ assert.equal(receipt.prior_state.cwd.replace(/\\/g, "/"), "C:/Dev/regen-root/mcp-servers/regen-media");
184
+ assert.equal(getClauthPm2Home(), path.join(root, "pm2-home"));
185
+
186
+ const codeflow = runSurfaceAction("codeflow:codeflow-mcp", "restart");
187
+ assert.equal(codeflow.resulting_state.ok, false);
188
+ assert.equal(codeflow.resulting_state.reason, "codeflow_self_owned");
189
+ assert.equal(listSurfaces().length, 2);
190
+ assert.equal(supervisorHealth().surfaces, 2);
191
+ }));
192
+
193
+ test("reconcile falls back to the declared start command when restart reports a missing process", () => withTempSupervisor((root) => {
194
+ const managed = path.join(root, "managed");
195
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
196
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
197
+ writePlugin(root, "managed", "fallback-demo", baseManifest("fallback-demo", {
198
+ core: true,
199
+ enable_default: true,
200
+ surfaces: [{
201
+ id: "primary",
202
+ destination: "local/clauth/pm2",
203
+ lifecycle_owner: "clauth",
204
+ port: 39114,
205
+ health: "/health",
206
+ start: [process.execPath, "--version"],
207
+ restart: [process.execPath, "-e", "process.exit(1)"],
208
+ }],
209
+ }));
210
+ discoverPlugins();
211
+ const receipt = runSurfaceAction("fallback-demo:primary", "reconcile");
212
+ assert.equal(receipt.resulting_state.ok, true);
213
+ assert.equal(receipt.resulting_state.evidence.includes("reconcile_start_fallback=true"), true);
214
+ }));
215
+
216
+ test("surface promote and rollback never fall through to restart commands", () => withTempSupervisor((root) => {
217
+ const managed = path.join(root, "managed");
218
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = managed;
219
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
220
+ writePlugin(root, "managed", "demo", baseManifest("demo", {
221
+ surfaces: [{
222
+ id: "demo-surface",
223
+ name: "Demo surface",
224
+ health: "http://127.0.0.1:3333/health",
225
+ restart: [process.execPath, "--version"],
226
+ }],
227
+ }));
228
+ discoverPlugins();
229
+
230
+ for (const action of ["promote", "rollback"]) {
231
+ const receipt = runSurfaceAction("demo:demo-surface", action);
232
+ assert.equal(receipt.resulting_state.ok, false);
233
+ assert.equal(receipt.resulting_state.state, "unsupported_surface_action");
234
+ assert.equal(receipt.resulting_state.evidence[0], "surface action did not execute a process command");
235
+ }
236
+ }));
237
+
238
+ test("health reconciliation marks a failed clauth surface and repairs it through reconcile", async () => {
239
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-supervisor-health-"));
240
+ const oldDir = process.env.CLAUTH_SUPERVISOR_DIR;
241
+ const oldManaged = process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
242
+ const oldUser = process.env.CLAUTH_USER_PLUGIN_ROOTS;
243
+ process.env.CLAUTH_SUPERVISOR_DIR = root;
244
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
245
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
246
+ try {
247
+ writePlugin(root, "managed", "health-demo", baseManifest("health-demo", {
248
+ core: true,
249
+ enable_default: true,
250
+ surfaces: [{
251
+ id: "primary",
252
+ destination: "local/clauth/pm2",
253
+ lifecycle_owner: "clauth",
254
+ port: 39111,
255
+ health: "/health",
256
+ restart: [process.execPath, "--version"],
257
+ }],
258
+ }));
259
+ discoverPlugins();
260
+ let healthCalls = 0;
261
+ const result = await reconcileSurfaceHealth({
262
+ fetchImpl: async () => ({ ok: ++healthCalls > 1, status: 503 }),
263
+ });
264
+ assert.equal(result.inspected[0].surface_id, "health-demo:primary");
265
+ assert.equal(result.inspected[0].state, "reconciled");
266
+ const surface = listSurfaces().find((item) => item.plugin_id === "health-demo");
267
+ assert.equal(surface.state, "current");
268
+ assert.ok(surface.last_reconcile_operation_id);
269
+ assert.equal(healthCalls, 2);
270
+ } finally {
271
+ if (oldDir === undefined) delete process.env.CLAUTH_SUPERVISOR_DIR;
272
+ else process.env.CLAUTH_SUPERVISOR_DIR = oldDir;
273
+ if (oldManaged === undefined) delete process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
274
+ else process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = oldManaged;
275
+ if (oldUser === undefined) delete process.env.CLAUTH_USER_PLUGIN_ROOTS;
276
+ else process.env.CLAUTH_USER_PLUGIN_ROOTS = oldUser;
277
+ fs.rmSync(root, { recursive: true, force: true });
278
+ }
279
+ });
280
+
281
+ test("health reconciliation does not claim current when a successful command leaves health down", async () => {
282
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-supervisor-post-health-"));
283
+ const oldDir = process.env.CLAUTH_SUPERVISOR_DIR;
284
+ const oldManaged = process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
285
+ const oldUser = process.env.CLAUTH_USER_PLUGIN_ROOTS;
286
+ process.env.CLAUTH_SUPERVISOR_DIR = root;
287
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
288
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
289
+ try {
290
+ writePlugin(root, "managed", "post-health-demo", baseManifest("post-health-demo", {
291
+ core: true,
292
+ enable_default: true,
293
+ surfaces: [{ id: "primary", destination: "local/clauth/pm2", lifecycle_owner: "clauth", port: 39113, health: "/health", restart: [process.execPath, "--version"] }],
294
+ }));
295
+ discoverPlugins();
296
+ const result = await reconcileSurfaceHealth({ fetchImpl: async () => ({ ok: false, status: 503 }) });
297
+ assert.equal(result.inspected[0].state, "reconcile_failed");
298
+ assert.equal(listSurfaces()[0].state, "unavailable");
299
+ assert.equal(listSurfaces()[0].last_health_ok, false);
300
+ } finally {
301
+ if (oldDir === undefined) delete process.env.CLAUTH_SUPERVISOR_DIR;
302
+ else process.env.CLAUTH_SUPERVISOR_DIR = oldDir;
303
+ if (oldManaged === undefined) delete process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
304
+ else process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = oldManaged;
305
+ if (oldUser === undefined) delete process.env.CLAUTH_USER_PLUGIN_ROOTS;
306
+ else process.env.CLAUTH_USER_PLUGIN_ROOTS = oldUser;
307
+ fs.rmSync(root, { recursive: true, force: true });
308
+ }
309
+ });
310
+
311
+ test("health reconciliation never restarts external or plugin-owned surfaces", async () => {
312
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-supervisor-observe-"));
313
+ const oldDir = process.env.CLAUTH_SUPERVISOR_DIR;
314
+ const oldManaged = process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
315
+ const oldUser = process.env.CLAUTH_USER_PLUGIN_ROOTS;
316
+ process.env.CLAUTH_SUPERVISOR_DIR = root;
317
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
318
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
319
+ try {
320
+ writePlugin(root, "managed", "external-demo", baseManifest("external-demo", {
321
+ core: true,
322
+ enable_default: true,
323
+ destination: "vultr/clauth/pm2",
324
+ lifecycle_owner: "external",
325
+ surfaces: [{ id: "primary", destination: "vultr/clauth/pm2", lifecycle_owner: "external", port: 39112, health: "/health" }],
326
+ }));
327
+ discoverPlugins();
328
+ const result = await reconcileSurfaceHealth({ fetchImpl: async () => ({ ok: false, status: 503 }) });
329
+ assert.deepEqual(result.inspected, []);
330
+ assert.equal(listSurfaces()[0].state, "current");
331
+ } finally {
332
+ if (oldDir === undefined) delete process.env.CLAUTH_SUPERVISOR_DIR;
333
+ else process.env.CLAUTH_SUPERVISOR_DIR = oldDir;
334
+ if (oldManaged === undefined) delete process.env.CLAUTH_MANAGED_PLUGIN_ROOTS;
335
+ else process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = oldManaged;
336
+ if (oldUser === undefined) delete process.env.CLAUTH_USER_PLUGIN_ROOTS;
337
+ else process.env.CLAUTH_USER_PLUGIN_ROOTS = oldUser;
338
+ fs.rmSync(root, { recursive: true, force: true });
339
+ }
340
+ });
341
+
342
+ test("supervisor write-token policy is temporarily relaxed only for the localhost supervisor port", () => {
343
+ assert.equal(supervisorRequiresWriteToken(52439, {}), false);
344
+ assert.equal(supervisorRequiresWriteToken(52439, { CLAUTH_SUPERVISOR_REQUIRE_WRITE_TOKEN: "1" }), true);
345
+ assert.equal(supervisorRequiresWriteToken(52437, {}), true);
346
+ assert.equal(isLoopbackAddress("127.0.0.1"), true);
347
+ assert.equal(isLoopbackAddress("::1"), true);
348
+ assert.equal(isLoopbackAddress("::ffff:127.0.0.1"), true);
349
+ assert.equal(isLoopbackAddress("192.168.1.25"), false);
350
+ });
351
+
352
+ test("supervisor log DTO strips raw operation payloads", () => {
353
+ const dto = supervisorLogDto({
354
+ ts: "2026-07-30T00:00:00.000Z",
355
+ kind: "operation",
356
+ operationId: "op-secret",
357
+ actor: "localhost",
358
+ action: "restart",
359
+ target: { surface_id: "demo", command: ["node", "--token=secret"] },
360
+ prior_state: { start: ["node", "--token=secret"], cwd: "C:/secrets" },
361
+ resulting_state: {
362
+ ok: false,
363
+ state: "operation_failed",
364
+ stderr: "SECRET=abc123",
365
+ evidence: ["CLAUTH_PM2_HOME=C:/safe"],
366
+ },
367
+ stderr: "SECRET=abc123",
368
+ completed_at: "2026-07-30T00:00:01.000Z",
369
+ });
370
+
371
+ const json = JSON.stringify(dto);
372
+ assert.equal(dto.operationId, "op-secret");
373
+ assert.equal(dto.target.surface_id, "demo");
374
+ assert.equal(dto.resulting_state.state, "operation_failed");
375
+ assert.equal(json.includes("prior_state"), false);
376
+ assert.equal(json.includes("stderr"), false);
377
+ assert.equal(json.includes("SECRET"), false);
378
+ assert.equal(json.includes("--token"), false);
379
+ });
380
+
381
+ test("tunnel route add and remove produce reversible operation receipts", () => withTempSupervisor((root) => {
382
+ process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
383
+ process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
384
+ discoverPlugins();
385
+
386
+ const added = addTunnelRoute("cf-main", {
387
+ id: "route-1",
388
+ hostname: "media.example.test",
389
+ service_url: "http://127.0.0.1:3120",
390
+ });
391
+ assert.equal(added.resulting_state.ok, true);
392
+ assert.equal(added.resulting_state.state, "route_recorded");
393
+
394
+ const removed = removeTunnelRoute("cf-main", "route-1");
395
+ assert.equal(removed.resulting_state.ok, true);
396
+ assert.equal(removed.resulting_state.state, "route_removed");
397
+ }));