@cotal-ai/cli 0.61.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.
- package/dist/commands/describe.d.ts.map +1 -1
- package/dist/commands/describe.js +4 -4
- package/dist/commands/describe.js.map +1 -1
- package/dist/commands/down.d.ts +3 -0
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +24 -18
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/meshes.d.ts +4 -0
- package/dist/commands/meshes.d.ts.map +1 -1
- package/dist/commands/meshes.js +45 -9
- package/dist/commands/meshes.js.map +1 -1
- package/dist/commands/spawn.d.ts.map +1 -1
- package/dist/commands/spawn.js +36 -8
- package/dist/commands/spawn.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +3 -2
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +70 -4
- package/dist/commands/up.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/control.d.ts +13 -1
- package/dist/lib/control.d.ts.map +1 -1
- package/dist/lib/control.js +38 -3
- package/dist/lib/control.js.map +1 -1
- package/dist/lib/delivery-proc.d.ts +10 -1
- package/dist/lib/delivery-proc.d.ts.map +1 -1
- package/dist/lib/delivery-proc.js +19 -0
- package/dist/lib/delivery-proc.js.map +1 -1
- package/dist/lib/teardown-spare.d.ts +7 -3
- package/dist/lib/teardown-spare.d.ts.map +1 -1
- package/dist/lib/teardown-spare.js +22 -10
- package/dist/lib/teardown-spare.js.map +1 -1
- package/dist/seed/reconcile.d.ts.map +1 -1
- package/dist/seed/reconcile.js +4 -1
- package/dist/seed/reconcile.js.map +1 -1
- package/dist/seed/store.d.ts.map +1 -1
- package/dist/seed/store.js +5 -7
- package/dist/seed/store.js.map +1 -1
- package/package.json +4 -4
package/dist/commands/up.js
CHANGED
|
@@ -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
|
-
|
|
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
|