@cotal-ai/cli 0.60.0 → 0.62.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 (41) hide show
  1. package/dist/commands/describe.d.ts.map +1 -1
  2. package/dist/commands/describe.js +4 -4
  3. package/dist/commands/describe.js.map +1 -1
  4. package/dist/commands/down.d.ts +3 -0
  5. package/dist/commands/down.d.ts.map +1 -1
  6. package/dist/commands/down.js +24 -18
  7. package/dist/commands/down.js.map +1 -1
  8. package/dist/commands/meshes.d.ts +4 -0
  9. package/dist/commands/meshes.d.ts.map +1 -1
  10. package/dist/commands/meshes.js +45 -9
  11. package/dist/commands/meshes.js.map +1 -1
  12. package/dist/commands/spawn.d.ts.map +1 -1
  13. package/dist/commands/spawn.js +36 -8
  14. package/dist/commands/spawn.js.map +1 -1
  15. package/dist/commands/status.d.ts.map +1 -1
  16. package/dist/commands/status.js +3 -2
  17. package/dist/commands/status.js.map +1 -1
  18. package/dist/commands/up.d.ts.map +1 -1
  19. package/dist/commands/up.js +70 -4
  20. package/dist/commands/up.js.map +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/lib/control.d.ts +13 -1
  24. package/dist/lib/control.d.ts.map +1 -1
  25. package/dist/lib/control.js +38 -3
  26. package/dist/lib/control.js.map +1 -1
  27. package/dist/lib/delivery-proc.d.ts +10 -1
  28. package/dist/lib/delivery-proc.d.ts.map +1 -1
  29. package/dist/lib/delivery-proc.js +19 -0
  30. package/dist/lib/delivery-proc.js.map +1 -1
  31. package/dist/lib/teardown-spare.d.ts +7 -3
  32. package/dist/lib/teardown-spare.d.ts.map +1 -1
  33. package/dist/lib/teardown-spare.js +22 -10
  34. package/dist/lib/teardown-spare.js.map +1 -1
  35. package/dist/seed/reconcile.d.ts.map +1 -1
  36. package/dist/seed/reconcile.js +4 -1
  37. package/dist/seed/reconcile.js.map +1 -1
  38. package/dist/seed/store.d.ts.map +1 -1
  39. package/dist/seed/store.js +5 -7
  40. package/dist/seed/store.js.map +1 -1
  41. package/package.json +4 -4
@@ -4,7 +4,7 @@ import { createConnection, createServer } from "node:net";
4
4
  import { hostname } from "node:os";
5
5
  import { mkdirSync, writeFileSync, readFileSync, existsSync, openSync, statSync, readSync, closeSync, lstatSync, rmSync, realpathSync, readdirSync, } from "node:fs";
6
6
  import { dirname, join, resolve } from "node:path";
7
- import { isReachable, DEFAULT_SERVER, createSpaceAuth, serverConfig, openServerConfig, validateTlsMaterial, mintCreds, mintLifecycleUid, DEV_OWNER, mintConnectionEvictorCreds, mintMembershipObserverCreds, newIdentity, setupSpaceStreams, reconcileSpaceTtls, ttlBuckets, standaloneConnectOpts, requireBrokerFloor, parseServerVersion, presenceBucket, seedChannelRegistry, ensureDefaultDeliveryClass, mkSecretDir, writeSecretFile, } from "@cotal-ai/core";
7
+ import { isReachable, DEFAULT_SERVER, createSpaceAuth, serverConfig, openServerConfig, validateTlsMaterial, mintCreds, mintLifecycleUid, DEV_OWNER, mintConnectionEvictorCreds, mintMembershipObserverCreds, newIdentity, setupSpaceStreams, reconcileSpaceTtls, ttlBuckets, standaloneConnectOpts, requireBrokerFloor, parseServerVersion, presenceBucket, seedChannelRegistry, ensureDefaultDeliveryClass, LEASE_TTL_MS, mkSecretDir, writeSecretFile, } from "@cotal-ai/core";
8
8
  import { connect } from "@nats-io/transport-node";
9
9
  import { jetstreamManager, StorageType } from "@nats-io/jetstream";
10
10
  import { Kvm } from "@nats-io/kv";
@@ -17,7 +17,7 @@ import { resolveNatsServer } from "../lib/nats-bin.js";
17
17
  import { cotalPath, cotalRoot } from "../lib/paths.js";
18
18
  import { renderDetachedSummary } from "../lib/up-report.js";
19
19
  import { detachedSystemdSupervisionWarning } from "../lib/systemd-supervision.js";
20
- import { deliveryUp, ensureControlPlane, stopDelivery } from "../lib/delivery-proc.js";
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
23
  import { liveManagerWouldApplyMaxSessions, managerHasDeliveryMarker, managerLogDisplayPath, managerRecordState, managerUp, stopManager } from "../lib/manager-proc.js";
@@ -26,6 +26,7 @@ import { loadManifest } from "../lib/manifest/index.js";
26
26
  import { buildLaunchSpec, genRunId, manifestToChannels, preflightConnectors, writeLaunchSpec } from "../lib/manifest/apply.js";
27
27
  import { renderUpPlan, renderInherited, renderWarnings } from "../lib/manifest/render.js";
28
28
  import { failManifest } from "./topology.js";
29
+ import { reserveStop } from "./down.js";
29
30
  import { extensionNames, preflightRuntime } from "../ext-loader.js";
30
31
  import { completingFlagValue } from "../lib/completion.js";
31
32
  import { askManager } from "../lib/control.js";
@@ -1047,8 +1048,11 @@ async function runUp(args, inheritedLock, onAdopt) {
1047
1048
  void (async () => {
1048
1049
  const managerContext = { root: cotalRoot(), space };
1049
1050
  let spared;
1051
+ let spareSeats;
1050
1052
  let legacyManagerSpareUnverified = false;
1051
1053
  const managerPidPath = localProcessPath(MANAGER_PIDFILE, managerContext);
1054
+ const stopMarker = `${managerPidPath}.stopping`;
1055
+ let reserved = false;
1052
1056
  if (existsSync(managerPidPath)) {
1053
1057
  const pin = verifyIdentityPin(managerPidPath);
1054
1058
  if (pin.kind === "legacy")
@@ -1056,7 +1060,12 @@ async function runUp(args, inheritedLock, onAdopt) {
1056
1060
  else if (pin.kind === "match") {
1057
1061
  spared = await listManagerSeatsForSpare(managerContext);
1058
1062
  try {
1059
- assertManagerCanSpare(managerContext, undefined, pin.record);
1063
+ // Taken before the capability read and held until the manager is gone, as bare `down`
1064
+ // holds it, so a concurrent `cotal down` can neither stop this manager nor arm a reap
1065
+ // while this stop is in flight.
1066
+ reserveStop("manager", stopMarker);
1067
+ reserved = true;
1068
+ spareSeats = assertManagerCanSpare(managerContext, undefined, pin.record);
1060
1069
  }
1061
1070
  catch (e) {
1062
1071
  // THE CAPABILITY ASSERT IS THE SIGNAL GATE, the same rule bare `down` enforces inside
@@ -1065,6 +1074,8 @@ async function runUp(args, inheritedLock, onAdopt) {
1065
1074
  // release the latch so the stack keeps running in the foreground; the operator ends it
1066
1075
  // with `cotal down --with-agents` from another terminal, and the broker-exit handler
1067
1076
  // below already ends `up` when the broker goes.
1077
+ if (reserved)
1078
+ rmSync(stopMarker, { force: true });
1068
1079
  console.error(c.red(`! teardown: ${e.message}`));
1069
1080
  console.error(c.red(`the stack is still running; to take managed agents with it, run: cotal down --with-agents`));
1070
1081
  stopping = false;
@@ -1078,11 +1089,15 @@ async function runUp(args, inheritedLock, onAdopt) {
1078
1089
  if (legacyManagerSpareUnverified)
1079
1090
  printLegacyManagerSpareUncertainty();
1080
1091
  else if (spared)
1081
- printSparedAgents(spared);
1092
+ printSparedAgents(spared, spareSeats);
1082
1093
  }
1083
1094
  catch (e) {
1084
1095
  console.error(`! manager teardown: ${e.message}`);
1085
1096
  }
1097
+ finally {
1098
+ if (reserved)
1099
+ rmSync(stopMarker, { force: true });
1100
+ }
1086
1101
  await stopAuthService(space).catch((e) => console.error(`! auth teardown: ${e.message}`));
1087
1102
  child.kill("SIGTERM");
1088
1103
  })();
@@ -1186,11 +1201,62 @@ async function runUp(args, inheritedLock, onAdopt) {
1186
1201
  storeDir,
1187
1202
  ts: new Date().toISOString(),
1188
1203
  }, "started");
1204
+ // A DAEMON THAT DIES UNDER THIS RUNNING BROKER IS RESTARTED HERE (#2469). It exits on its own once
1205
+ // it cannot reach the broker, and a starved host makes a running broker look unreachable, so it can
1206
+ // end while this broker serves on. Nothing else brings it back, and every static retirement then
1207
+ // fails on the ctl.delivery-admin rail and holds its name until an operator re-runs `cotal up`. A
1208
+ // clean exit or SIGTERM/SIGINT is a deliberate stop (`cotal down delivery`) and stays stopped, and so
1209
+ // is the SIGKILL `down` escalates to when a starved daemon outlives its grace, also when either ends a
1210
+ // replacement this loop is still waiting on: that attempt then fails, and retrying it would undo the
1211
+ // stop. A failed attempt waits out the lease TTL, the longest a dead holder's lease blocks its
1212
+ // replacement. No daemon is alive during that wait for `down` to signal, so a stop there shows only as
1213
+ // the dead daemon's record going away. Recovery is announced only once the responder is bound.
1214
+ const meshServing = () => !stopping && child.exitCode === null && child.signalCode === null;
1215
+ let restartingDelivery = false;
1216
+ let deliveryStopped = false;
1217
+ const onDeliveryExit = (code, signal) => {
1218
+ if (code === 0 || signal === "SIGTERM" || signal === "SIGINT" || (signal !== null && deliveryStoppedByDown(space))) {
1219
+ deliveryStopped = true;
1220
+ return;
1221
+ }
1222
+ if (restartingDelivery || !meshServing())
1223
+ return;
1224
+ restartingDelivery = true;
1225
+ console.error(c.yellow(`! delivery daemon exited (${signal ? `signal ${signal}` : `code ${code}`}) while nats-server is running - restarting it`));
1226
+ void (async () => {
1227
+ while (meshServing()) {
1228
+ let failure;
1229
+ try {
1230
+ const ensured = await ensureDelivery({ space, server, tls: transport.kind === "tls-required", onDeliveryExit });
1231
+ // An abnormal exit during this attempt was dropped above, so the record decides.
1232
+ if (deliveryUp(space)) {
1233
+ // An unbound responder was reported by ensureDelivery, and this daemon's exit re-arms the restart.
1234
+ if (ensured.responderBound)
1235
+ console.error(c.green(`✓ delivery daemon running again${ensured.pid !== undefined ? ` (pid ${ensured.pid})` : ""}`));
1236
+ break;
1237
+ }
1238
+ failure = "no delivery daemon is running after the attempt";
1239
+ }
1240
+ catch (e) {
1241
+ failure = e.message;
1242
+ }
1243
+ if (deliveryStopped)
1244
+ break;
1245
+ console.error(c.yellow(`! delivery restart failed, retrying in ${LEASE_TTL_MS / 1000}s: ${failure}`));
1246
+ const recorded = !deliveryStoppedByDown(space);
1247
+ await new Promise((r) => setTimeout(r, LEASE_TTL_MS));
1248
+ if (deliveryStopped || (recorded && deliveryStoppedByDown(space)))
1249
+ break;
1250
+ }
1251
+ restartingDelivery = false;
1252
+ })();
1253
+ };
1189
1254
  // Bring up the delivery daemon WITH the server (auth mode only — it self-gates on `.cotal/auth`).
1190
1255
  // It is part of the server, so `cotal up` starts it by default; open dev mode has no daemon.
1191
1256
  // Class-2 credential renewal is NOT wired here: the MANAGER is the renewal owner (it is resident
1192
1257
  // in every mesh mode — foreground, --detach, refresh — where this foreground process is not).
1193
1258
  const controlPlane = await startDeliveryWithBroker(space, server, transport.kind === "tls-required", {
1259
+ onDeliveryExit,
1194
1260
  runtime: values.runtime,
1195
1261
  // The address the broker was bound to. This is what lets `cotal attach` reach this manager
1196
1262
  // from another machine; without it the attach face stays loopback-only, so exposing terminals