@cotal-ai/cli 0.51.0 → 0.52.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 +8 -0
- package/dist/command.js.map +1 -1
- package/dist/commands/agents.d.ts +41 -2
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +67 -18
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/clean.d.ts.map +1 -1
- package/dist/commands/clean.js +5 -1
- package/dist/commands/clean.js.map +1 -1
- package/dist/commands/console.d.ts.map +1 -1
- package/dist/commands/console.js +9 -1
- package/dist/commands/console.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +13 -8
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +38 -3
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +9 -8
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/spawn.d.ts.map +1 -1
- package/dist/commands/spawn.js +18 -2
- package/dist/commands/spawn.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +122 -35
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/up.d.ts +7 -0
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +104 -11
- package/dist/commands/up.js.map +1 -1
- package/dist/commands/update.d.ts +12 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +58 -5
- package/dist/commands/update.js.map +1 -1
- package/dist/console/app.d.ts +14 -6
- package/dist/console/app.d.ts.map +1 -1
- package/dist/console/app.js +308 -40
- package/dist/console/app.js.map +1 -1
- package/dist/console/commands.d.ts +29 -1
- package/dist/console/commands.d.ts.map +1 -1
- package/dist/console/commands.js +109 -13
- package/dist/console/commands.js.map +1 -1
- package/dist/console/control.d.ts +141 -0
- package/dist/console/control.d.ts.map +1 -0
- package/dist/console/control.js +189 -0
- package/dist/console/control.js.map +1 -0
- package/dist/console/mesh.d.ts +1 -1
- package/dist/console/mesh.d.ts.map +1 -1
- package/dist/console/root.d.ts +13 -1
- package/dist/console/root.d.ts.map +1 -1
- package/dist/console/root.js +25 -5
- package/dist/console/root.js.map +1 -1
- package/dist/console/ui/CommandPalette.d.ts +2 -1
- package/dist/console/ui/CommandPalette.d.ts.map +1 -1
- package/dist/console/ui/CommandPalette.js +3 -2
- package/dist/console/ui/CommandPalette.js.map +1 -1
- package/dist/console/ui/Confirm.d.ts +13 -4
- package/dist/console/ui/Confirm.d.ts.map +1 -1
- package/dist/console/ui/Confirm.js +13 -5
- package/dist/console/ui/Confirm.js.map +1 -1
- package/dist/console/ui/Detail.d.ts +8 -1
- package/dist/console/ui/Detail.d.ts.map +1 -1
- package/dist/console/ui/Detail.js +11 -4
- package/dist/console/ui/Detail.js.map +1 -1
- package/dist/console/ui/Help.d.ts.map +1 -1
- package/dist/console/ui/Help.js +3 -2
- package/dist/console/ui/Help.js.map +1 -1
- package/dist/console/ui/Roster.d.ts +9 -1
- package/dist/console/ui/Roster.d.ts.map +1 -1
- package/dist/console/ui/Roster.js +16 -5
- package/dist/console/ui/Roster.js.map +1 -1
- package/dist/console/ui/Sparkline.d.ts +13 -0
- package/dist/console/ui/Sparkline.d.ts.map +1 -0
- package/dist/console/ui/Sparkline.js +21 -0
- package/dist/console/ui/Sparkline.js.map +1 -0
- package/dist/console/ui/StatusBar.d.ts +4 -1
- package/dist/console/ui/StatusBar.d.ts.map +1 -1
- package/dist/console/ui/StatusBar.js +5 -3
- package/dist/console/ui/StatusBar.js.map +1 -1
- package/dist/console/ui/Tabs.d.ts +4 -2
- package/dist/console/ui/Tabs.d.ts.map +1 -1
- package/dist/console/ui/Tabs.js +5 -3
- package/dist/console/ui/Tabs.js.map +1 -1
- package/dist/console/ui/topo/Map.d.ts.map +1 -1
- package/dist/console/ui/topo/Map.js +34 -2
- package/dist/console/ui/topo/Map.js.map +1 -1
- package/dist/console/ui/topo/Matrix.d.ts.map +1 -1
- package/dist/console/ui/topo/Matrix.js +12 -3
- package/dist/console/ui/topo/Matrix.js.map +1 -1
- package/dist/console/ui/topo/Sequence.js +1 -1
- package/dist/console/ui/topo/Sequence.js.map +1 -1
- package/dist/console/ui/topo/Topo.d.ts +10 -2
- package/dist/console/ui/topo/Topo.d.ts.map +1 -1
- package/dist/console/ui/topo/Topo.js +6 -4
- package/dist/console/ui/topo/Topo.js.map +1 -1
- package/dist/console/ui/topo/model.d.ts +50 -2
- package/dist/console/ui/topo/model.d.ts.map +1 -1
- package/dist/console/ui/topo/model.js +69 -6
- package/dist/console/ui/topo/model.js.map +1 -1
- package/dist/lib/attach-client.d.ts.map +1 -1
- package/dist/lib/attach-client.js +7 -1
- package/dist/lib/attach-client.js.map +1 -1
- package/dist/lib/delivery-proc.d.ts +10 -2
- package/dist/lib/delivery-proc.d.ts.map +1 -1
- package/dist/lib/delivery-proc.js +7 -1
- package/dist/lib/delivery-proc.js.map +1 -1
- package/dist/view/mesh-view.d.ts +41 -1
- package/dist/view/mesh-view.d.ts.map +1 -1
- package/dist/view/mesh-view.js +107 -8
- package/dist/view/mesh-view.js.map +1 -1
- package/package.json +13 -6
package/dist/commands/up.js
CHANGED
|
@@ -35,6 +35,7 @@ export const upFlags = [
|
|
|
35
35
|
{ name: "host", type: "string", value: "<host>", description: "bind host override (an IP/hostname, not a URL; the URL is --server)" },
|
|
36
36
|
{ name: "space", type: "string", value: "<s>", description: "space name (default: the folder's)" },
|
|
37
37
|
{ name: "store-dir", type: "string", value: "<dir>", description: "JetStream store directory" },
|
|
38
|
+
{ name: "max-file-store", type: "string", value: "<bytes>", description: "JetStream file storage cap in bytes (default: nats-server's dynamic cap). Fixed at broker start; recorded and kept across `down --preserve-state`" },
|
|
38
39
|
{ name: "channels", type: "string", value: "<path>", description: "channel-registry seed file (JSON; default .cotal/channels.json)" },
|
|
39
40
|
{ name: "tls-cert", type: "string", value: "<file>", description: "serve the broker over TLS with this certificate (requires --tls-key)" },
|
|
40
41
|
{ name: "tls-key", type: "string", value: "<file>", description: "the private key for --tls-cert" },
|
|
@@ -54,6 +55,7 @@ export const upFlags = [
|
|
|
54
55
|
{ name: "detach", type: "boolean", description: "run in the background (stop with `cotal down`)" },
|
|
55
56
|
{ name: "runtime", type: "string", value: "<name>", description: "agent runtime for the mesh manager (default pty; extension runtimes are explicit-only, see `cotal runtimes`); with -f overrides the manifest's runtime" },
|
|
56
57
|
{ name: "max-sessions", type: "string", value: "<n>", description: "live-session ceiling for the mesh manager (default 64; one session per console pane, so size for agents × panes). Recorded and reused by later manager launches" },
|
|
58
|
+
{ name: "no-manager", type: "boolean", description: "broker-only boot: start the broker and, in auth mode, the delivery daemon, and no local manager (refuses on a mesh whose manager is live)" },
|
|
57
59
|
{ name: "file", type: "string", short: "f", value: "<cotal.yaml>", description: "launch a whole mesh from a manifest" },
|
|
58
60
|
{ name: "dry-run", type: "boolean", description: "with -f: print the plan, mutate nothing" },
|
|
59
61
|
];
|
|
@@ -145,6 +147,11 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
145
147
|
// read. A rotation is a stopped, fresh boot; a half-finished maintenance attempt is neither.
|
|
146
148
|
if (values["rotate-sys"] && (values.__restoreAttempt || values.__ordinaryResumeAttempt))
|
|
147
149
|
throw new Error("--rotate-sys cannot run during a restore/resume re-entry - finish or roll back the maintenance attempt, then `cotal down` and `cotal up --rotate-sys`");
|
|
150
|
+
// Same seam, same reason: a restore/resume re-entry needs the manager to commit the preserved
|
|
151
|
+
// inventory (completeResumeActivation asks it for resumePreserved/commitResume/finalizeResume), so
|
|
152
|
+
// a broker-only ask there is a contradiction to refuse, never a flag to drop mid-recovery.
|
|
153
|
+
if (values["no-manager"] && (values.__restoreAttempt || values.__ordinaryResumeAttempt))
|
|
154
|
+
throw new Error("--no-manager cannot run during a restore/resume re-entry - the resume needs a manager to commit the preserved inventory; finish or roll back the maintenance attempt first");
|
|
148
155
|
if (!values.__restoreAttempt && !values.__ordinaryResumeAttempt && !values.file) {
|
|
149
156
|
const root = cotalRoot();
|
|
150
157
|
// Same refusal for the AUTO-recovered journal, raised before the recovery is prepared and
|
|
@@ -152,6 +159,11 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
152
159
|
// `up` that has already begun adopting a maintenance attempt.
|
|
153
160
|
if (values["rotate-sys"] && readMaintenanceJournal(root))
|
|
154
161
|
throw new Error("--rotate-sys is refused while this root has a maintenance attempt to recover - finish or roll back that attempt (`cotal up` alone recovers it), then `cotal down` and `cotal up --rotate-sys`");
|
|
162
|
+
// Raised here for the stronger reason the journal itself carries: a pending maintenance attempt
|
|
163
|
+
// means the store is mid-recovery, and a broker-only boot over it is wrong with or without the
|
|
164
|
+
// manager half. The manager half is why the FLAG specifically cannot apply.
|
|
165
|
+
if (values["no-manager"] && readMaintenanceJournal(root))
|
|
166
|
+
throw new Error("--no-manager is refused while this root has a maintenance attempt to recover - a resume needs a manager to commit the preserved inventory; recover it (`cotal up` alone) or roll it back first");
|
|
155
167
|
const lock = acquireMaintenanceLock(root);
|
|
156
168
|
let pending;
|
|
157
169
|
let recoveredRestore;
|
|
@@ -205,6 +217,12 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
205
217
|
// Journal the CANONICAL identity-checked path, never the caller's spelling: a relative or
|
|
206
218
|
// symlinked spelling re-resolved later (other cwd, retargeted link) could open another store.
|
|
207
219
|
const resumeStore = journal.source.path;
|
|
220
|
+
// The cap is part of the preserved launch, like the store: a resume re-renders it, and a
|
|
221
|
+
// different --max-file-store is refused rather than silently resizing the preserved broker.
|
|
222
|
+
const resumeMaxFileStore = journaledMaxFileStore(launch.maxFileStore);
|
|
223
|
+
const requestedMaxFileStore = parsePositiveIntegerFlag("--max-file-store", values["max-file-store"]);
|
|
224
|
+
if (requestedMaxFileStore !== undefined && requestedMaxFileStore !== resumeMaxFileStore)
|
|
225
|
+
throw new Error(`--max-file-store ${requestedMaxFileStore} is not the preserved launch cap (${resumeMaxFileStore ?? "unset"}); ordinary up resumes exactly the preserved cap - omit --max-file-store`);
|
|
208
226
|
if (values.runtime && runtimes[0] && values.runtime !== runtimes[0])
|
|
209
227
|
throw new Error(`--runtime ${values.runtime} contradicts the preserved agent runtime ${runtimes[0]}; omit it to resume the same principals`);
|
|
210
228
|
const resumeRuntime = values.runtime ?? runtimes[0] ?? "pty";
|
|
@@ -219,6 +237,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
219
237
|
launch: {
|
|
220
238
|
server: resumeServer,
|
|
221
239
|
storeDir: resumeStore,
|
|
240
|
+
...(resumeMaxFileStore !== undefined ? { maxFileStore: resumeMaxFileStore } : {}),
|
|
222
241
|
runtime: resumeRuntime,
|
|
223
242
|
detached: Boolean(values.detach),
|
|
224
243
|
serverName,
|
|
@@ -238,6 +257,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
238
257
|
mode: journal.mode,
|
|
239
258
|
server: resumeServer,
|
|
240
259
|
storeDir: resumeStore,
|
|
260
|
+
...(resumeMaxFileStore !== undefined ? { maxFileStore: resumeMaxFileStore } : {}),
|
|
241
261
|
runtime: resumeRuntime,
|
|
242
262
|
detached: Boolean(values.detach),
|
|
243
263
|
inventory: resume.inventory,
|
|
@@ -251,6 +271,10 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
251
271
|
const resume = readMaintenanceResumeDocument(root, journal.resume);
|
|
252
272
|
const launch = journal.ordinaryResume.launch;
|
|
253
273
|
const attemptId = journal.ordinaryResume.attemptId;
|
|
274
|
+
const recoveredMaxFileStore = journaledMaxFileStore(launch.maxFileStore);
|
|
275
|
+
const requestedMaxFileStore = parsePositiveIntegerFlag("--max-file-store", values["max-file-store"]);
|
|
276
|
+
if (requestedMaxFileStore !== undefined && requestedMaxFileStore !== recoveredMaxFileStore)
|
|
277
|
+
throw new Error(`--max-file-store ${requestedMaxFileStore} is not the preserved launch cap (${recoveredMaxFileStore ?? "unset"}); resume attempt ${attemptId} re-renders exactly the preserved cap - omit --max-file-store`);
|
|
254
278
|
// Recovery re-asserts the source identity rather than trusting the journaled spelling.
|
|
255
279
|
assertStoreIdentity(journal.source);
|
|
256
280
|
// The bound listener decides re-entry: a live exact listener is ADOPTED, a provably dead
|
|
@@ -295,6 +319,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
295
319
|
mode: journal.mode,
|
|
296
320
|
server: typeof launch.server === "string" ? launch.server : DEFAULT_SERVER,
|
|
297
321
|
storeDir: journal.source.path,
|
|
322
|
+
...(recoveredMaxFileStore !== undefined ? { maxFileStore: recoveredMaxFileStore } : {}),
|
|
298
323
|
runtime: typeof launch.runtime === "string" ? launch.runtime : "pty",
|
|
299
324
|
detached: launch.detached === true,
|
|
300
325
|
inventory: resume.inventory,
|
|
@@ -353,6 +378,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
353
378
|
space: pending.space,
|
|
354
379
|
server: pending.server,
|
|
355
380
|
"store-dir": pending.storeDir,
|
|
381
|
+
"max-file-store": pending.maxFileStore === undefined ? undefined : String(pending.maxFileStore),
|
|
356
382
|
runtime: pending.runtime,
|
|
357
383
|
detach: pending.detached,
|
|
358
384
|
open: pending.mode === "open",
|
|
@@ -382,8 +408,22 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
382
408
|
if (values.idp && !wantUser && !values.file) {
|
|
383
409
|
throw new Error('--idp is for user-auth spaces; pair it with --user-auth, or set broker.auth: "user" in a manifest');
|
|
384
410
|
}
|
|
411
|
+
// BROKER-ONLY MODE (#1417). `--no-manager` names what it omits, and the two manager-shaping flags
|
|
412
|
+
// are refused beside it rather than silently dropped: a runtime selected or a ceiling recorded for
|
|
413
|
+
// a manager that will never start is a misunderstanding, and dropping it quietly is the exact
|
|
414
|
+
// silent-no-op shape this repo refuses everywhere else.
|
|
415
|
+
const noManager = Boolean(values["no-manager"]);
|
|
416
|
+
if (noManager && values.runtime)
|
|
417
|
+
throw new Error("--no-manager cannot be combined with --runtime - no manager starts, so there is no runtime to select; drop --runtime, or drop --no-manager");
|
|
418
|
+
if (noManager && values["max-sessions"])
|
|
419
|
+
throw new Error("--no-manager cannot be combined with --max-sessions - no manager starts, so there is no session ceiling to set; drop --max-sessions, or drop --no-manager");
|
|
385
420
|
const publicExchange = publicExchangeArgs(values, wantUser);
|
|
386
421
|
const maxSessions = parsePositiveIntegerFlag("--max-sessions", values["max-sessions"]);
|
|
422
|
+
const maxFileStore = parsePositiveIntegerFlag("--max-file-store", values["max-file-store"]);
|
|
423
|
+
// The manifest path renders its broker from the manifest, which has no store fields, so the flag
|
|
424
|
+
// would be accepted and never rendered. Refuse it rather than drop it.
|
|
425
|
+
if (maxFileStore !== undefined && values.file)
|
|
426
|
+
throw new Error("--max-file-store cannot be combined with --file/-f - a manifest launch does not render a store cap; drop --max-file-store, or start the mesh without -f");
|
|
387
427
|
// An open mesh has no operator, no system account, and no $SYS creds, so there is nothing to
|
|
388
428
|
// rotate; the request is a misunderstanding to name, never a silent no-op that reports success.
|
|
389
429
|
if (values["rotate-sys"] && values.open) {
|
|
@@ -473,6 +513,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
473
513
|
idp: values.idp,
|
|
474
514
|
rotateSys: values["rotate-sys"],
|
|
475
515
|
maxSessions,
|
|
516
|
+
noManager,
|
|
476
517
|
});
|
|
477
518
|
return;
|
|
478
519
|
}
|
|
@@ -491,6 +532,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
491
532
|
idp: values.idp,
|
|
492
533
|
rotateSys: values["rotate-sys"],
|
|
493
534
|
maxSessions,
|
|
535
|
+
noManager,
|
|
494
536
|
});
|
|
495
537
|
}
|
|
496
538
|
finally {
|
|
@@ -628,6 +670,13 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
628
670
|
console.error(c.red(`✗ mesh "${held.space}" is already running at ${server} - a running broker can't change its transport; \`cotal down\` it first, then \`cotal up --tls-cert <cert> --tls-key <key>\``));
|
|
629
671
|
process.exit(1);
|
|
630
672
|
}
|
|
673
|
+
// The store cap is fixed when nats-server reads its config, and nats-server refuses a reload
|
|
674
|
+
// from its dynamic cap to an explicit one. This branch starts nothing, so a different cap can
|
|
675
|
+
// only be refused; the same cap is a no-op.
|
|
676
|
+
if (maxFileStore !== undefined && maxFileStore !== held.maxFileStore) {
|
|
677
|
+
console.error(c.red(`✗ mesh "${held.space}" is already running at ${server} - a running broker can't change --max-file-store (it is fixed at start); \`cotal down\` it first, then \`cotal up --max-file-store ${maxFileStore}\``));
|
|
678
|
+
process.exit(1);
|
|
679
|
+
}
|
|
631
680
|
// `--max-sessions` is fixed when the manager starts. A refresh reuses a live manager as-is
|
|
632
681
|
// (`ensureManager` returns immediately), then used to persist the requested ceiling anyway.
|
|
633
682
|
// That printed `✓ already running` over an unchanged plane and left MeshEntry lying about it.
|
|
@@ -640,6 +689,18 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
640
689
|
process.exit(1);
|
|
641
690
|
}
|
|
642
691
|
}
|
|
692
|
+
// `--no-manager` on a refresh whose manager is LIVE is the same class of refusal: this branch
|
|
693
|
+
// starts nothing, so the flag cannot apply, and silently keeping the manager would answer a
|
|
694
|
+
// broker-only ask with a full stack while silently stopping it would kill agents this command
|
|
695
|
+
// was never asked to touch. Same shape as `--runtime`/`--max-sessions` above: refuse, name the
|
|
696
|
+
// exact remedy. A refresh of a mesh whose manager is NOT live simply proceeds broker-only.
|
|
697
|
+
if (noManager) {
|
|
698
|
+
const live = managerRecordState(undefined, undefined, held.space);
|
|
699
|
+
if (live.state === "alive") {
|
|
700
|
+
console.error(c.red(`✗ mesh "${held.space}" is already running at ${server} with a live manager - \`cotal up --no-manager\` will not keep or stop one; \`cotal down manager\` first, then \`cotal up --no-manager\``));
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
643
704
|
// Live INFO must agree with the recorded/requested transport. A bare refresh that greets
|
|
644
705
|
// "already running" over a plaintext listener while policy claims TLS is S5's second half —
|
|
645
706
|
// the refuse left a durable lie and this path used to reprint TLS success over cleartext.
|
|
@@ -751,6 +812,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
751
812
|
runtime: values.runtime,
|
|
752
813
|
attachHost: attachHostFor(held.space, values.host),
|
|
753
814
|
maxSessions: maxSessionsFor(held.space, maxSessions),
|
|
815
|
+
noManager,
|
|
754
816
|
});
|
|
755
817
|
if (!controlPlane)
|
|
756
818
|
process.exitCode = 1;
|
|
@@ -763,7 +825,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
763
825
|
// no transport decision to record — and `recordOurMesh` writes the entry whole, so omitting
|
|
764
826
|
// the field here would erase the requirement on every bare refresh, exactly the way dropping
|
|
765
827
|
// `attachHost` would silently demote the mesh to loopback.
|
|
766
|
-
recordOurMesh({ space: held.space, server, root, mode: held.mode, ...(held.tlsRequired !== undefined ? { tlsRequired: held.tlsRequired } : {}), ...(userAuth ? { userAuth } : {}), ...(heldAttachHost ? { attachHost: heldAttachHost } : {}), ...(heldMaxSessions !== undefined ? { maxSessions: heldMaxSessions } : {}), ts: new Date().toISOString() }, "refresh");
|
|
828
|
+
recordOurMesh({ space: held.space, server, root, mode: held.mode, ...(held.tlsRequired !== undefined ? { tlsRequired: held.tlsRequired } : {}), ...(userAuth ? { userAuth } : {}), ...(heldAttachHost ? { attachHost: heldAttachHost } : {}), ...(heldMaxSessions !== undefined ? { maxSessions: heldMaxSessions } : {}), ...(held.maxFileStore !== undefined ? { maxFileStore: held.maxFileStore } : {}), ts: new Date().toISOString() }, "refresh");
|
|
767
829
|
return;
|
|
768
830
|
}
|
|
769
831
|
const who = held ? `mesh "${held.space}" (${held.root})` : "a broker not started here";
|
|
@@ -795,6 +857,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
795
857
|
transport,
|
|
796
858
|
server,
|
|
797
859
|
storeDir: values["store-dir"],
|
|
860
|
+
maxFileStore,
|
|
798
861
|
space: values.space,
|
|
799
862
|
open: values.open,
|
|
800
863
|
userAuth: wantUser ? { idpUrl: values.idp } : undefined,
|
|
@@ -807,6 +870,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
807
870
|
host: values.host,
|
|
808
871
|
runtime: values.runtime,
|
|
809
872
|
maxSessions,
|
|
873
|
+
noManager,
|
|
810
874
|
resumeAttempt,
|
|
811
875
|
resumeCommitToken: restored?.managerCommit?.durableCommitToken ?? ordinaryAttempt?.managerCommit?.durableCommitToken,
|
|
812
876
|
...(restored ? {
|
|
@@ -862,14 +926,14 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
862
926
|
// Decided above the branch; the dial host is only settled here (a port collision may have moved
|
|
863
927
|
// the server, and the hostname is what the certificate has to match).
|
|
864
928
|
assertServesDialHost(transport, new URL(server).hostname);
|
|
865
|
-
const setup = useAuth ? await authSetup(storeDir, server, space, host, wantUser ? { idpUrl: values.idp } : undefined, transport, values["rotate-sys"]) : undefined;
|
|
929
|
+
const setup = useAuth ? await authSetup(storeDir, server, space, host, wantUser ? { idpUrl: values.idp } : undefined, transport, values["rotate-sys"], maxFileStore) : undefined;
|
|
866
930
|
const port = Number(new URL(server).port) || 4222;
|
|
867
931
|
const restored = resumeAttempt ? pendingRestores.get(resumeAttempt) : undefined;
|
|
868
932
|
// Both modes go through a RENDERER, never bare CLI flags. Open mode used to start from
|
|
869
933
|
// `-js -sd … -p … -a …`, which never called a renderer at all — so the required transport union
|
|
870
934
|
// protected the auth path and was silent on the open one, and a cert/key pair passed to an
|
|
871
935
|
// open-mode `up` would have been accepted while the listener came up in cleartext.
|
|
872
|
-
const confPath = setup ? setup.confPath : writeOpenBrokerConf(storeDir, { port, host, transport });
|
|
936
|
+
const confPath = setup ? setup.confPath : writeOpenBrokerConf(storeDir, { port, host, transport, maxFileStore });
|
|
873
937
|
const natsArgs = [
|
|
874
938
|
"-c", confPath,
|
|
875
939
|
...(restored ? ["--name", restored.serverName]
|
|
@@ -1001,6 +1065,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
1001
1065
|
// though the operator had chosen it.
|
|
1002
1066
|
...(effectiveAttachHost ? { attachHost: effectiveAttachHost } : {}),
|
|
1003
1067
|
...(effectiveMaxSessions !== undefined ? { maxSessions: effectiveMaxSessions } : {}),
|
|
1068
|
+
...(maxFileStore !== undefined ? { maxFileStore } : {}),
|
|
1004
1069
|
ts: new Date().toISOString(),
|
|
1005
1070
|
}, "started");
|
|
1006
1071
|
// Bring up the delivery daemon WITH the server (auth mode only — it self-gates on `.cotal/auth`).
|
|
@@ -1014,6 +1079,7 @@ async function runUp(args, inheritedLock, onAdopt) {
|
|
|
1014
1079
|
// is never a side effect of anything but the operator binding the mesh somewhere reachable.
|
|
1015
1080
|
attachHost: effectiveAttachHost,
|
|
1016
1081
|
maxSessions: effectiveMaxSessions,
|
|
1082
|
+
noManager,
|
|
1017
1083
|
resumeAttempt,
|
|
1018
1084
|
resumeCommitToken: restored?.managerCommit?.durableCommitToken ?? ordinaryAttempt?.managerCommit?.durableCommitToken,
|
|
1019
1085
|
wsPort: setup?.wsPort, // P2 item 6: the console session client's broker ws port
|
|
@@ -1365,6 +1431,7 @@ async function resumeProvenOrdinaryListener(pending, heldLock) {
|
|
|
1365
1431
|
...(svc.userAuth ? { userAuth: svc.userAuth } : {}),
|
|
1366
1432
|
...(adoptAttachHost ? { attachHost: adoptAttachHost } : {}),
|
|
1367
1433
|
...(adoptMaxSessions !== undefined ? { maxSessions: adoptMaxSessions } : {}),
|
|
1434
|
+
...(pending.maxFileStore !== undefined ? { maxFileStore: pending.maxFileStore } : {}),
|
|
1368
1435
|
ts: new Date().toISOString(),
|
|
1369
1436
|
}, "started");
|
|
1370
1437
|
const controlPlane = await startDeliveryWithBroker(pending.space, pending.server, adoptedTlsRequired(pending.root), {
|
|
@@ -1687,6 +1754,12 @@ async function upManifest(file, opts) {
|
|
|
1687
1754
|
const open = m.broker?.auth === false; // default is auth
|
|
1688
1755
|
const userAuth = m.broker?.auth === "user" ? { idpUrl: opts.idp ?? m.broker?.idp } : undefined; // flag > manifest
|
|
1689
1756
|
const runtime = m.runtime ?? "pty";
|
|
1757
|
+
// A manifest's agents are booted BY the manager `up -f` starts with the mesh, so `--no-manager`
|
|
1758
|
+
// against a manifest declaring agents asks for a boot that cannot deliver its own plan. Refused
|
|
1759
|
+
// before the dry-run print and before anything boots, with the two workable spellings named.
|
|
1760
|
+
if (opts.noManager && eff.agents.length)
|
|
1761
|
+
throw new Error(`--no-manager cannot launch this manifest: it declares ${eff.agents.length} agent(s), and a manifest's agents are booted by its manager (which --no-manager omits). Deploy it from a manager host with \`cotal spawn -f\`, or drop --no-manager.`);
|
|
1762
|
+
const effectiveNoManager = Boolean(opts.noManager);
|
|
1690
1763
|
// The open-mesh refusal must be RE-STATED here, not only against the CLI `--open` flag: on this
|
|
1691
1764
|
// path openness comes from the MANIFEST (`broker.auth: false`), which the flag-level guard cannot
|
|
1692
1765
|
// see. Without it, `cotal up -f open.yaml --rotate-sys` boots an open broker, exits 0 and rotates
|
|
@@ -1703,6 +1776,10 @@ async function upManifest(file, opts) {
|
|
|
1703
1776
|
if (opts.transport.kind === "tls-required")
|
|
1704
1777
|
announceTransport(opts.transport);
|
|
1705
1778
|
console.log(renderUpPlan(eff, server));
|
|
1779
|
+
// The omission is part of the plan (#1417): a dry run that lists a manager the real boot
|
|
1780
|
+
// will not start is the exact "green-lit a launch the applying command refuses" failure.
|
|
1781
|
+
if (effectiveNoManager)
|
|
1782
|
+
console.log(c.dim(" no manager will start on this host (--no-manager): broker, channels and, in auth mode, the delivery daemon come up, and no agents launch from here"));
|
|
1706
1783
|
return;
|
|
1707
1784
|
}
|
|
1708
1785
|
// Preflight an extension runtime before starting the broker. The detached manager re-execs this
|
|
@@ -1756,7 +1833,7 @@ async function upManifest(file, opts) {
|
|
|
1756
1833
|
try {
|
|
1757
1834
|
// `m.broker?.host`, not the defaulted `host`: same reason as the flag path — only a host the
|
|
1758
1835
|
// manifest actually declared is an exposure decision worth persisting.
|
|
1759
|
-
({ pid, controlPlane, authService } = await startMeshDetached({ transport: opts.transport, server, space: m.space, open, userAuth, rotateSys: opts.rotateSys, host: m.broker?.host, seed: manifestToChannels(eff), runtime, launch: specPath, maxSessions: opts.maxSessions }));
|
|
1836
|
+
({ pid, controlPlane, authService } = await startMeshDetached({ transport: opts.transport, server, space: m.space, open, userAuth, rotateSys: opts.rotateSys, host: m.broker?.host, seed: manifestToChannels(eff), runtime, launch: specPath, maxSessions: opts.maxSessions, noManager: effectiveNoManager }));
|
|
1760
1837
|
}
|
|
1761
1838
|
catch (e) {
|
|
1762
1839
|
console.error(c.red(`✗ ${e.message}`));
|
|
@@ -1767,7 +1844,12 @@ async function upManifest(file, opts) {
|
|
|
1767
1844
|
console.log(c.dim(` seeded ${m.channels.length} channel(s): ${m.channels.map((ch) => "#" + ch.name).join(", ")}`));
|
|
1768
1845
|
// Never claim a launch the control plane can't deliver: the manager carries the launch spec, so a
|
|
1769
1846
|
// degraded control plane (announced above) means the agents are NOT coming up.
|
|
1770
|
-
if (
|
|
1847
|
+
if (effectiveNoManager) {
|
|
1848
|
+
// Truthful summary for the broker-only boot (#1417): nothing was omitted by accident, and the
|
|
1849
|
+
// agents this manifest declares (none — a declaring manifest is refused above) launch elsewhere.
|
|
1850
|
+
console.log(c.dim(` broker-only (--no-manager): no manager started on this host - run \`cotal supervise --space ${m.space} --server ${server}\` from a manager host to manage agents`));
|
|
1851
|
+
}
|
|
1852
|
+
else if (controlPlane) {
|
|
1771
1853
|
// U6: on a user mesh the agents run under the OPERATOR's identity — say whose they are.
|
|
1772
1854
|
console.log(c.green(`✓ launching ${eff.agents.length} agent(s)`) + c.dim(` via manager (${runtime})${owner ? ` as you (owner ${owner})` : ""} - see ${managerLogDisplayPath(m.space)}`));
|
|
1773
1855
|
}
|
|
@@ -1780,7 +1862,7 @@ async function upManifest(file, opts) {
|
|
|
1780
1862
|
console.log("\n" + inherited);
|
|
1781
1863
|
if (eff.warnings.length)
|
|
1782
1864
|
console.log("\n" + renderWarnings(eff.warnings));
|
|
1783
|
-
console.log(c.dim(`\nWatch: \`cotal console --space ${m.space}\`
|
|
1865
|
+
console.log(c.dim(`\nWatch: \`cotal web\` (browser dashboard) or \`cotal console --space ${m.space}\` (terminal) · Tear down: \`cotal down\``));
|
|
1784
1866
|
// A declared-user-auth manifest whose auth service never became ready: the mesh is up + recorded
|
|
1785
1867
|
// (re-`cotal up` heals), but this launch did not deliver a usable identity plane — exit non-zero
|
|
1786
1868
|
// so CI/wrappers don't read success (the red consequence line printed at the failure above).
|
|
@@ -1885,11 +1967,11 @@ opts) {
|
|
|
1885
1967
|
// effective server is finally known — a manifest may name a different host than the flags did, and
|
|
1886
1968
|
// a certificate that is valid for one is not thereby valid for the other.
|
|
1887
1969
|
const transport = assertServesDialHost(opts.transport, new URL(server).hostname);
|
|
1888
|
-
const setup = useAuth ? await authSetup(storeDir, server, space, host, opts.userAuth, transport, opts.rotateSys) : undefined;
|
|
1970
|
+
const setup = useAuth ? await authSetup(storeDir, server, space, host, opts.userAuth, transport, opts.rotateSys, opts.maxFileStore) : undefined;
|
|
1889
1971
|
const port = Number(new URL(server).port) || 4222;
|
|
1890
1972
|
// Same rule as the foreground path: every route to a listener names its transport (see
|
|
1891
1973
|
// `writeOpenBrokerConf`). Detach must not be the mode where the fence quietly does not apply.
|
|
1892
|
-
const confPath = setup ? setup.confPath : writeOpenBrokerConf(storeDir, { port, host, transport });
|
|
1974
|
+
const confPath = setup ? setup.confPath : writeOpenBrokerConf(storeDir, { port, host, transport, maxFileStore: opts.maxFileStore });
|
|
1893
1975
|
const args = [
|
|
1894
1976
|
"-c", confPath,
|
|
1895
1977
|
...(opts.boundListener ? ["--name", opts.boundListener.serverName] : []),
|
|
@@ -1985,6 +2067,7 @@ opts) {
|
|
|
1985
2067
|
// Persist only a real decision — declared now, or carried forward — never the loopback default.
|
|
1986
2068
|
...(effectiveAttachHost ? { attachHost: effectiveAttachHost } : {}),
|
|
1987
2069
|
...(effectiveMaxSessions !== undefined ? { maxSessions: effectiveMaxSessions } : {}),
|
|
2070
|
+
...(opts.maxFileStore !== undefined ? { maxFileStore: opts.maxFileStore } : {}),
|
|
1988
2071
|
ts: new Date().toISOString(),
|
|
1989
2072
|
}, "started");
|
|
1990
2073
|
// Commit policy BEFORE delivery launch (S9). Listener is proved; refuse paths never reach here.
|
|
@@ -1997,6 +2080,7 @@ opts) {
|
|
|
1997
2080
|
// See the foreground path: the broker's bind address is what makes attach reachable off-box.
|
|
1998
2081
|
attachHost: effectiveAttachHost,
|
|
1999
2082
|
maxSessions: effectiveMaxSessions,
|
|
2083
|
+
noManager: opts.noManager,
|
|
2000
2084
|
resumeAttempt: opts.resumeAttempt,
|
|
2001
2085
|
resumeCommitToken: opts.resumeCommitToken,
|
|
2002
2086
|
wsPort: setup?.wsPort, // P2 item 6: the console session client's broker ws port
|
|
@@ -2121,6 +2205,15 @@ export function attachHostFor(space, explicit) {
|
|
|
2121
2205
|
export function maxSessionsFor(space, explicit) {
|
|
2122
2206
|
return explicit ?? findMesh(space)?.maxSessions;
|
|
2123
2207
|
}
|
|
2208
|
+
/** The store cap a preserved launch recorded: absent, or a positive integer byte count. Anything
|
|
2209
|
+
* else is a damaged journal, refused rather than rendered as the dynamic default. */
|
|
2210
|
+
function journaledMaxFileStore(value) {
|
|
2211
|
+
if (value === undefined)
|
|
2212
|
+
return undefined;
|
|
2213
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
|
|
2214
|
+
throw new Error(`the preserved launch records an invalid maxFileStore (${JSON.stringify(value)}); refusing to guess the broker's store cap`);
|
|
2215
|
+
return value;
|
|
2216
|
+
}
|
|
2124
2217
|
/** {@link recordOurMesh} under a name that says it is a test seam: the origin rule it enforces is
|
|
2125
2218
|
* asserted directly by the registry smoke, since reaching each branch through a full `up` would
|
|
2126
2219
|
* need a live broker per case. */
|
|
@@ -2509,7 +2602,7 @@ function validateRecorded(transport, dialHost, opts = {}) {
|
|
|
2509
2602
|
*/
|
|
2510
2603
|
function writeOpenBrokerConf(storeDir, opts) {
|
|
2511
2604
|
const confPath = resolve(storeDir, "..", "server-open.conf");
|
|
2512
|
-
writeFileSync(confPath, openServerConfig({ port: opts.port, host: opts.host, storeDir, transport: opts.transport }));
|
|
2605
|
+
writeFileSync(confPath, openServerConfig({ port: opts.port, host: opts.host, storeDir, maxFileStore: opts.maxFileStore, transport: opts.transport }));
|
|
2513
2606
|
return confPath;
|
|
2514
2607
|
}
|
|
2515
2608
|
async function authSetup(storeDir, server, space, host = "127.0.0.1", user,
|
|
@@ -2518,7 +2611,7 @@ async function authSetup(storeDir, server, space, host = "127.0.0.1", user,
|
|
|
2518
2611
|
// operator's `--tls-cert` sat validated and unused. A default here cannot be right, because the
|
|
2519
2612
|
// safe value and the common value are different values — every caller knows its transport, and
|
|
2520
2613
|
// the one that does not is the one that must not compile.
|
|
2521
|
-
transport, rotateSys = false) {
|
|
2614
|
+
transport, rotateSys = false, maxFileStore) {
|
|
2522
2615
|
const dir = authDir(cotalRoot()); // the broker config (server.conf) still lands under the FS auth dir
|
|
2523
2616
|
const store = workspaceSecretStore(cotalRoot());
|
|
2524
2617
|
let auth = await getSpaceAuth(store, space);
|
|
@@ -2627,7 +2720,7 @@ transport, rotateSys = false) {
|
|
|
2627
2720
|
: await resolveAuthProvider().preloadAccounts({ store, space: enabledSpace });
|
|
2628
2721
|
extraAccounts.push(...accounts);
|
|
2629
2722
|
}
|
|
2630
|
-
writeFileSync(confPath, serverConfig(auth, spaces, { transport, port, storeDir, host, wsPort, wsHost: host, extraAccounts }));
|
|
2723
|
+
writeFileSync(confPath, serverConfig(auth, spaces, { transport, port, storeDir, maxFileStore, host, wsPort, wsHost: host, extraAccounts }));
|
|
2631
2724
|
// Ephemeral setup cred: used only to probe reachability, pre-create the space streams/buckets
|
|
2632
2725
|
// (setupSpaceStreams) and seed the channel registry (seedChannelRegistry) — all within the
|
|
2633
2726
|
// enumerated `provisioner` scope. No broad `manager` residual for the up path.
|