@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.
- package/dist/command.d.ts.map +1 -1
- package/dist/command.js +4 -0
- package/dist/command.js.map +1 -1
- package/dist/commands/app-identity.d.ts +40 -0
- package/dist/commands/app-identity.d.ts.map +1 -0
- package/dist/commands/app-identity.js +188 -0
- package/dist/commands/app-identity.js.map +1 -0
- package/dist/commands/channels.d.ts.map +1 -1
- package/dist/commands/channels.js +3 -1
- package/dist/commands/channels.js.map +1 -1
- package/dist/commands/console.d.ts.map +1 -1
- package/dist/commands/console.js +6 -2
- package/dist/commands/console.js.map +1 -1
- package/dist/commands/endpoints.d.ts.map +1 -1
- package/dist/commands/endpoints.js +2 -3
- package/dist/commands/endpoints.js.map +1 -1
- package/dist/commands/join.d.ts.map +1 -1
- package/dist/commands/join.js +25 -3
- package/dist/commands/join.js.map +1 -1
- package/dist/commands/models.d.ts +5 -0
- package/dist/commands/models.d.ts.map +1 -1
- package/dist/commands/models.js +5 -3
- package/dist/commands/models.js.map +1 -1
- package/dist/commands/personas.js +2 -11
- package/dist/commands/personas.js.map +1 -1
- package/dist/commands/send.d.ts.map +1 -1
- package/dist/commands/send.js +13 -17
- package/dist/commands/send.js.map +1 -1
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +14 -1
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/spawn.d.ts.map +1 -1
- package/dist/commands/spawn.js +4 -38
- package/dist/commands/spawn.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +7 -11
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +51 -89
- package/dist/commands/up.js.map +1 -1
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +14 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/console/ui/Detail.d.ts.map +1 -1
- package/dist/console/ui/Detail.js +2 -2
- package/dist/console/ui/Detail.js.map +1 -1
- package/dist/console/ui/NeedsYou.d.ts.map +1 -1
- package/dist/console/ui/NeedsYou.js +2 -2
- package/dist/console/ui/NeedsYou.js.map +1 -1
- package/dist/console/ui/Roster.d.ts.map +1 -1
- package/dist/console/ui/Roster.js +2 -2
- package/dist/console/ui/Roster.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/control.d.ts +4 -4
- package/dist/lib/control.js +1 -1
- package/dist/lib/delivery-proc.d.ts.map +1 -1
- package/dist/lib/delivery-proc.js +31 -5
- package/dist/lib/delivery-proc.js.map +1 -1
- package/dist/lib/isolated-broker.d.ts.map +1 -1
- package/dist/lib/isolated-broker.js +2 -1
- package/dist/lib/isolated-broker.js.map +1 -1
- package/dist/lib/live-names.d.ts +13 -0
- package/dist/lib/live-names.d.ts.map +1 -0
- package/dist/lib/live-names.js +34 -0
- package/dist/lib/live-names.js.map +1 -0
- package/dist/lib/manager-proc.d.ts +4 -1
- package/dist/lib/manager-proc.d.ts.map +1 -1
- package/dist/lib/manager-proc.js +8 -6
- package/dist/lib/manager-proc.js.map +1 -1
- package/dist/lib/transient.d.ts +8 -2
- package/dist/lib/transient.d.ts.map +1 -1
- package/dist/lib/transient.js +12 -2
- package/dist/lib/transient.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +2 -2
- package/dist/render.js.map +1 -1
- package/dist/seed/reconcile.d.ts.map +1 -1
- package/dist/seed/reconcile.js +14 -22
- package/dist/seed/reconcile.js.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +2 -2
- package/dist/ui.js.map +1 -1
- package/package.json +4 -4
package/dist/commands/up.js
CHANGED
|
@@ -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
|
-
|
|
1367
|
-
|
|
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(
|
|
1370
|
-
if (proof.attemptId !==
|
|
1371
|
-
proof.serverNonce !==
|
|
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 !==
|
|
1374
|
-
throw new Error(
|
|
1375
|
-
const
|
|
1376
|
-
if (!
|
|
1377
|
-
|
|
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(
|
|
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(
|
|
1389
|
+
throw new Error(`${label} listener process ownership is not live and local`);
|
|
1387
1390
|
}
|
|
1388
|
-
const pidPath = join(
|
|
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(
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
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(
|
|
1407
|
-
const auth = await resumeControlAuth(
|
|
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:
|
|
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(
|
|
1417
|
+
throw new Error(`${label} authenticated to a foreign NATS server name/nonce`);
|
|
1416
1418
|
if (!nc.info.jetstream)
|
|
1417
|
-
throw new Error(
|
|
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
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
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;
|