@cotal-ai/cli 0.85.0 → 0.87.0

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 (87) hide show
  1. package/dist/command.d.ts.map +1 -1
  2. package/dist/command.js +4 -0
  3. package/dist/command.js.map +1 -1
  4. package/dist/commands/app-identity.d.ts +40 -0
  5. package/dist/commands/app-identity.d.ts.map +1 -0
  6. package/dist/commands/app-identity.js +188 -0
  7. package/dist/commands/app-identity.js.map +1 -0
  8. package/dist/commands/channels.d.ts.map +1 -1
  9. package/dist/commands/channels.js +3 -1
  10. package/dist/commands/channels.js.map +1 -1
  11. package/dist/commands/console.d.ts.map +1 -1
  12. package/dist/commands/console.js +6 -2
  13. package/dist/commands/console.js.map +1 -1
  14. package/dist/commands/endpoints.d.ts.map +1 -1
  15. package/dist/commands/endpoints.js +2 -3
  16. package/dist/commands/endpoints.js.map +1 -1
  17. package/dist/commands/join.d.ts.map +1 -1
  18. package/dist/commands/join.js +25 -3
  19. package/dist/commands/join.js.map +1 -1
  20. package/dist/commands/models.d.ts +5 -0
  21. package/dist/commands/models.d.ts.map +1 -1
  22. package/dist/commands/models.js +5 -3
  23. package/dist/commands/models.js.map +1 -1
  24. package/dist/commands/personas.js +2 -11
  25. package/dist/commands/personas.js.map +1 -1
  26. package/dist/commands/send.d.ts.map +1 -1
  27. package/dist/commands/send.js +13 -17
  28. package/dist/commands/send.js.map +1 -1
  29. package/dist/commands/setup.d.ts +3 -0
  30. package/dist/commands/setup.d.ts.map +1 -1
  31. package/dist/commands/setup.js +14 -1
  32. package/dist/commands/setup.js.map +1 -1
  33. package/dist/commands/spawn.d.ts.map +1 -1
  34. package/dist/commands/spawn.js +4 -38
  35. package/dist/commands/spawn.js.map +1 -1
  36. package/dist/commands/status.d.ts.map +1 -1
  37. package/dist/commands/status.js +7 -11
  38. package/dist/commands/status.js.map +1 -1
  39. package/dist/commands/up.d.ts.map +1 -1
  40. package/dist/commands/up.js +51 -89
  41. package/dist/commands/up.js.map +1 -1
  42. package/dist/commands/update.d.ts +2 -0
  43. package/dist/commands/update.d.ts.map +1 -1
  44. package/dist/commands/update.js +14 -1
  45. package/dist/commands/update.js.map +1 -1
  46. package/dist/console/ui/Detail.d.ts.map +1 -1
  47. package/dist/console/ui/Detail.js +2 -2
  48. package/dist/console/ui/Detail.js.map +1 -1
  49. package/dist/console/ui/NeedsYou.d.ts.map +1 -1
  50. package/dist/console/ui/NeedsYou.js +2 -2
  51. package/dist/console/ui/NeedsYou.js.map +1 -1
  52. package/dist/console/ui/Roster.d.ts.map +1 -1
  53. package/dist/console/ui/Roster.js +2 -2
  54. package/dist/console/ui/Roster.js.map +1 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +12 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/lib/control.d.ts +4 -4
  59. package/dist/lib/control.js +1 -1
  60. package/dist/lib/delivery-proc.d.ts.map +1 -1
  61. package/dist/lib/delivery-proc.js +31 -5
  62. package/dist/lib/delivery-proc.js.map +1 -1
  63. package/dist/lib/isolated-broker.d.ts.map +1 -1
  64. package/dist/lib/isolated-broker.js +2 -1
  65. package/dist/lib/isolated-broker.js.map +1 -1
  66. package/dist/lib/live-names.d.ts +13 -0
  67. package/dist/lib/live-names.d.ts.map +1 -0
  68. package/dist/lib/live-names.js +34 -0
  69. package/dist/lib/live-names.js.map +1 -0
  70. package/dist/lib/manager-proc.d.ts +4 -1
  71. package/dist/lib/manager-proc.d.ts.map +1 -1
  72. package/dist/lib/manager-proc.js +8 -6
  73. package/dist/lib/manager-proc.js.map +1 -1
  74. package/dist/lib/transient.d.ts +8 -2
  75. package/dist/lib/transient.d.ts.map +1 -1
  76. package/dist/lib/transient.js +12 -2
  77. package/dist/lib/transient.js.map +1 -1
  78. package/dist/render.d.ts.map +1 -1
  79. package/dist/render.js +2 -2
  80. package/dist/render.js.map +1 -1
  81. package/dist/seed/reconcile.d.ts.map +1 -1
  82. package/dist/seed/reconcile.js +14 -22
  83. package/dist/seed/reconcile.js.map +1 -1
  84. package/dist/ui.d.ts.map +1 -1
  85. package/dist/ui.js +2 -2
  86. package/dist/ui.js.map +1 -1
  87. package/package.json +4 -4
@@ -20,7 +20,7 @@ import { detachedSystemdSupervisionWarning } from "../lib/systemd-supervision.js
20
20
  import { deliveryStoppedByDown, deliveryUp, ensureControlPlane, ensureDelivery, stopDelivery } from "../lib/delivery-proc.js";
21
21
  import { RESPONDER_UNBOUND_CONSEQUENCE } from "../lib/delivery-responder.js";
22
22
  import { displayCmd } from "../lib/self-exec.js";
23
- import { liveManagerWouldApplyMaxSessions, managerHasDeliveryMarker, managerLogDisplayPath, managerRecordState, managerUp, stopManager } from "../lib/manager-proc.js";
23
+ import { clearManagerArtifacts, liveManagerWouldApplyMaxSessions, managerHasDeliveryMarker, managerLogDisplayPath, managerRecordState, managerUp, stopManager } from "../lib/manager-proc.js";
24
24
  import { loadManifest } from "../lib/manifest/index.js";
25
25
  import { buildLaunchSpec, genRunId, manifestToChannels, preflightConnectors, writeLaunchSpec } from "../lib/manifest/apply.js";
26
26
  import { renderUpPlan, renderInherited, renderWarnings } from "../lib/manifest/render.js";
@@ -1034,6 +1034,7 @@ async function runUp(args, inheritedLock, onAdopt) {
1034
1034
  teardown = (async () => {
1035
1035
  try {
1036
1036
  await stopManager(space);
1037
+ clearManagerArtifacts(space);
1037
1038
  }
1038
1039
  catch (e) {
1039
1040
  console.error(c.red(`! teardown: ${e.message}`));
@@ -1058,7 +1059,7 @@ async function runUp(args, inheritedLock, onAdopt) {
1058
1059
  // that one holds, so wait for a Ctrl-C teardown already running instead of racing it.
1059
1060
  await teardown;
1060
1061
  await stopDelivery(space).catch((e) => console.error(`! delivery teardown: ${e.message}`));
1061
- await stopManager(space).catch((e) => console.error(`! manager teardown: ${e.message}`));
1062
+ await stopManager(space).then(() => clearManagerArtifacts(space)).catch((e) => console.error(`! manager teardown: ${e.message}`));
1062
1063
  await stopAuthService(space).catch((e) => console.error(`! auth teardown: ${e.message}`));
1063
1064
  // Unrecording (and the exit code below) both depend on WHY the broker is gone. `stopping` is
1064
1065
  // true only for the intended shutdown `stop()` drives (Ctrl-C, SIGTERM) — everything else here
@@ -1363,29 +1364,31 @@ function readNatsInfo(endpoint, timeoutMs = 2_000) {
1363
1364
  socket.on("close", () => finish(new Error("restore listener closed before NATS INFO")));
1364
1365
  });
1365
1366
  }
1366
- async function provePreparedRestoreListener(prepared) {
1367
- const proof = prepared.listenerProof;
1367
+ /** Prove a recovered attempt's live bound listener IS ours end-to-end before adoption: launch
1368
+ * binding, durable journal match, local pid + exact pidfile, store identity, mesh identity, the
1369
+ * raw NATS INFO server name, and an authenticated connect confirming name + JetStream. Each attempt
1370
+ * kind supplies only its journal match, which yields the store identity the journal recorded. */
1371
+ async function proveBoundListener(kind, attempt, proof, journalStore, store) {
1372
+ const label = `${kind} attempt ${attempt.attemptId}`;
1368
1373
  if (!proof)
1369
- throw new Error(`restore attempt ${prepared.attemptId} has no bound listener proof`);
1370
- if (proof.attemptId !== prepared.attemptId || proof.serverName !== prepared.serverName ||
1371
- proof.serverNonce !== prepared.serverNonce || proof.serverEndpoint !== prepared.server ||
1374
+ throw new Error(`${label} has no bound listener proof`);
1375
+ if (proof.attemptId !== attempt.attemptId || proof.serverName !== attempt.serverName ||
1376
+ proof.serverNonce !== attempt.serverNonce || proof.serverEndpoint !== attempt.server ||
1372
1377
  proof.serverName !== `${proof.attemptId}-${proof.serverNonce}` || !/^[0-9a-f]{32}$/.test(proof.serverNonce) ||
1373
- proof.processOwner.id !== `restore-listener-${proof.serverNonce}`)
1374
- throw new Error(`restore attempt ${prepared.attemptId} listener proof does not match its launch record`);
1375
- const journal = readMaintenanceJournal(prepared.root);
1376
- if (!journal || (journal.state !== "commit-intent" && journal.state !== "manager-committed" && journal.state !== "degraded" && journal.state !== "active") ||
1377
- journal.restore.attemptId !== prepared.attemptId || !journal.listenerProof ||
1378
- !sameRestoreListenerProof(journal.listenerProof, proof) || journal.launch.server !== proof.serverEndpoint)
1379
- throw new Error(`restore attempt ${prepared.attemptId} listener proof does not exactly match durable recovery state`);
1378
+ proof.processOwner.id !== `${kind}-listener-${proof.serverNonce}`)
1379
+ throw new Error(`${label} listener proof does not match its launch record`);
1380
+ const recorded = journalStore(proof);
1381
+ if (!recorded)
1382
+ throw new Error(`${label} listener proof does not exactly match durable recovery state`);
1380
1383
  if (proof.processOwner.host !== hostname())
1381
- throw new Error(`restore attempt ${prepared.attemptId} listener process ownership is not live and local`);
1384
+ throw new Error(`${label} listener process ownership is not live and local`);
1382
1385
  try {
1383
1386
  process.kill(proof.processOwner.pid, 0);
1384
1387
  }
1385
1388
  catch {
1386
- throw new Error(`restore attempt ${prepared.attemptId} listener process ownership is not live and local`);
1389
+ throw new Error(`${label} listener process ownership is not live and local`);
1387
1390
  }
1388
- const pidPath = join(prepared.root, ".cotal", "nats.pid");
1391
+ const pidPath = join(attempt.root, ".cotal", "nats.pid");
1389
1392
  let exactPidFile = false;
1390
1393
  try {
1391
1394
  const stat = lstatSync(pidPath);
@@ -1394,33 +1397,42 @@ async function provePreparedRestoreListener(prepared) {
1394
1397
  }
1395
1398
  catch { /* absent or unprovable */ }
1396
1399
  if (!exactPidFile)
1397
- throw new Error(`restore attempt ${prepared.attemptId} listener pidfile does not match its process proof`);
1398
- const target = readStoreIdentity(prepared.targetPath);
1399
- if (!sameStoreIdentity(target, proof.target) || !sameStoreIdentity(journal.restore.target, proof.target))
1400
- throw new Error(`restore attempt ${prepared.attemptId} target identity changed`);
1401
- const mesh = findMesh(prepared.space);
1402
- if (mesh && (mesh.server !== prepared.server || mesh.root !== prepared.root || mesh.mode !== prepared.mode))
1403
- throw new Error(`restore attempt ${prepared.attemptId} conflicts with the recorded mesh identity`);
1400
+ throw new Error(`${label} listener pidfile does not match its process proof`);
1401
+ if (!sameStoreIdentity(readStoreIdentity(store.path), proof.target) || !sameStoreIdentity(recorded, proof.target))
1402
+ throw new Error(`${label} ${store.changed}`);
1403
+ const mesh = findMesh(attempt.space);
1404
+ if (mesh && (mesh.server !== attempt.server || mesh.root !== attempt.root || mesh.mode !== attempt.mode))
1405
+ throw new Error(`${label} conflicts with the recorded mesh identity`);
1404
1406
  const info = await readNatsInfo(proof.serverEndpoint);
1405
1407
  if (info.server_name !== proof.serverName)
1406
- throw new Error(`restore attempt ${prepared.attemptId} reached a foreign NATS server name/nonce`);
1407
- const auth = await resumeControlAuth(prepared.root, prepared.mode);
1408
+ throw new Error(`${label} reached a foreign NATS server name/nonce`);
1409
+ const auth = await resumeControlAuth(attempt.root, attempt.mode);
1408
1410
  const nc = await connect({
1409
- servers: prepared.server,
1411
+ servers: attempt.server,
1410
1412
  ...standaloneConnectOpts({ ...auth, /* not yet wired to a recorded transport - see broker-policy/MeshEntry work */ tls: false }),
1411
1413
  maxReconnectAttempts: 0,
1412
1414
  });
1413
1415
  try {
1414
1416
  if (nc.info?.server_name !== proof.serverName)
1415
- throw new Error(`restore attempt ${prepared.attemptId} authenticated to a foreign NATS server name/nonce`);
1417
+ throw new Error(`${label} authenticated to a foreign NATS server name/nonce`);
1416
1418
  if (!nc.info.jetstream)
1417
- throw new Error(`restore attempt ${prepared.attemptId} listener has no JetStream`);
1419
+ throw new Error(`${label} listener has no JetStream`);
1418
1420
  }
1419
1421
  finally {
1420
1422
  await nc.drain().catch(() => { });
1421
1423
  }
1422
1424
  return proof;
1423
1425
  }
1426
+ function provePreparedRestoreListener(prepared) {
1427
+ return proveBoundListener("restore", prepared, prepared.listenerProof, (proof) => {
1428
+ const journal = readMaintenanceJournal(prepared.root);
1429
+ if (!journal || (journal.state !== "commit-intent" && journal.state !== "manager-committed" && journal.state !== "degraded" && journal.state !== "active") ||
1430
+ journal.restore.attemptId !== prepared.attemptId || !journal.listenerProof ||
1431
+ !sameRestoreListenerProof(journal.listenerProof, proof) || journal.launch.server !== proof.serverEndpoint)
1432
+ return undefined;
1433
+ return journal.restore.target;
1434
+ }, { path: prepared.targetPath, changed: "target identity changed" });
1435
+ }
1424
1436
  function bindSpawnedOrdinaryResumeListener(pending, pid, startedAt, heldLock) {
1425
1437
  if (!Number.isInteger(pid) || pid <= 0)
1426
1438
  throw new Error("resume listener spawn returned no pid");
@@ -1443,66 +1455,15 @@ function bindSpawnedOrdinaryResumeListener(pending, pid, startedAt, heldLock) {
1443
1455
  releaseMaintenanceLock(lock);
1444
1456
  }
1445
1457
  }
1446
- /** Prove the recovered attempt's live bound listener IS ours end-to-end before adoption: launch
1447
- * binding, durable journal match, local pid + exact pidfile, source identity, mesh identity, the
1448
- * raw NATS INFO server name, and an authenticated connect confirming name + JetStream. */
1449
- async function proveOrdinaryResumeListener(pending) {
1450
- const proof = pending.adoptProof;
1451
- if (!proof)
1452
- throw new Error(`resume attempt ${pending.attemptId} has no bound listener proof`);
1453
- if (proof.attemptId !== pending.attemptId || proof.serverName !== pending.serverName ||
1454
- proof.serverNonce !== pending.serverNonce || proof.serverEndpoint !== pending.server ||
1455
- proof.serverName !== `${proof.attemptId}-${proof.serverNonce}` || !/^[0-9a-f]{32}$/.test(proof.serverNonce) ||
1456
- proof.processOwner.id !== `resume-listener-${proof.serverNonce}`)
1457
- throw new Error(`resume attempt ${pending.attemptId} listener proof does not match its launch record`);
1458
- const journal = readMaintenanceJournal(pending.root);
1459
- if (!journal || !("ordinaryResume" in journal) || journal.ordinaryResume.attemptId !== pending.attemptId ||
1460
- !journal.listenerProof || !sameRestoreListenerProof(journal.listenerProof, proof) ||
1461
- journal.ordinaryResume.launch.server !== proof.serverEndpoint)
1462
- throw new Error(`resume attempt ${pending.attemptId} listener proof does not exactly match durable recovery state`);
1463
- if (proof.processOwner.host !== hostname())
1464
- throw new Error(`resume attempt ${pending.attemptId} listener process ownership is not live and local`);
1465
- try {
1466
- process.kill(proof.processOwner.pid, 0);
1467
- }
1468
- catch {
1469
- throw new Error(`resume attempt ${pending.attemptId} listener process ownership is not live and local`);
1470
- }
1471
- const pidPath = join(pending.root, ".cotal", "nats.pid");
1472
- let exactPidFile = false;
1473
- try {
1474
- const stat = lstatSync(pidPath);
1475
- exactPidFile = stat.isFile() && !stat.isSymbolicLink() &&
1476
- readFileSync(pidPath, "utf8") === String(proof.processOwner.pid);
1477
- }
1478
- catch { /* absent or unprovable */ }
1479
- if (!exactPidFile)
1480
- throw new Error(`resume attempt ${pending.attemptId} listener pidfile does not match its process proof`);
1481
- const source = readStoreIdentity(pending.storeDir);
1482
- if (!sameStoreIdentity(source, proof.target) || !sameStoreIdentity(journal.source, proof.target))
1483
- throw new Error(`resume attempt ${pending.attemptId} preserved source identity changed`);
1484
- const mesh = findMesh(pending.space);
1485
- if (mesh && (mesh.server !== pending.server || mesh.root !== pending.root || mesh.mode !== pending.mode))
1486
- throw new Error(`resume attempt ${pending.attemptId} conflicts with the recorded mesh identity`);
1487
- const info = await readNatsInfo(proof.serverEndpoint);
1488
- if (info.server_name !== proof.serverName)
1489
- throw new Error(`resume attempt ${pending.attemptId} reached a foreign NATS server name/nonce`);
1490
- const auth = await resumeControlAuth(pending.root, pending.mode);
1491
- const nc = await connect({
1492
- servers: pending.server,
1493
- ...standaloneConnectOpts({ ...auth, /* not yet wired to a recorded transport - see broker-policy/MeshEntry work */ tls: false }),
1494
- maxReconnectAttempts: 0,
1495
- });
1496
- try {
1497
- if (nc.info?.server_name !== proof.serverName)
1498
- throw new Error(`resume attempt ${pending.attemptId} authenticated to a foreign NATS server name/nonce`);
1499
- if (!nc.info.jetstream)
1500
- throw new Error(`resume attempt ${pending.attemptId} listener has no JetStream`);
1501
- }
1502
- finally {
1503
- await nc.drain().catch(() => { });
1504
- }
1505
- return proof;
1458
+ function proveOrdinaryResumeListener(pending) {
1459
+ return proveBoundListener("resume", pending, pending.adoptProof, (proof) => {
1460
+ const journal = readMaintenanceJournal(pending.root);
1461
+ if (!journal || !("ordinaryResume" in journal) || journal.ordinaryResume.attemptId !== pending.attemptId ||
1462
+ !journal.listenerProof || !sameRestoreListenerProof(journal.listenerProof, proof) ||
1463
+ journal.ordinaryResume.launch.server !== proof.serverEndpoint)
1464
+ return undefined;
1465
+ return journal.source;
1466
+ }, { path: pending.storeDir, changed: "preserved source identity changed" });
1506
1467
  }
1507
1468
  /** Light wire check for a freshly SPAWNED ordinary-resume listener: the greeted server must carry
1508
1469
  * the attempt's exact name/nonce (the full seven-point prove is for adopting a survivor). */
@@ -1962,6 +1923,7 @@ async function upManifest(file, opts) {
1962
1923
  // lives at this address) would win the fresh mesh's lease and the launch manager would refuse.
1963
1924
  // Stop it, so the manager started below WITH the launch spec is THE manager.
1964
1925
  await stopManager(m.space);
1926
+ clearManagerArtifacts(m.space);
1965
1927
  let pid;
1966
1928
  let controlPlane = false;
1967
1929
  let authService = true;