@cotal-ai/cli 0.16.0 → 0.18.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 (121) hide show
  1. package/cotal-skills/skills/team-topology/SKILL.md +1 -1
  2. package/dist/command.d.ts.map +1 -1
  3. package/dist/command.js +13 -0
  4. package/dist/command.js.map +1 -1
  5. package/dist/commands/agents.d.ts +15 -15
  6. package/dist/commands/agents.d.ts.map +1 -1
  7. package/dist/commands/agents.js +187 -77
  8. package/dist/commands/agents.js.map +1 -1
  9. package/dist/commands/clean.d.ts.map +1 -1
  10. package/dist/commands/clean.js +4 -3
  11. package/dist/commands/clean.js.map +1 -1
  12. package/dist/commands/describe.d.ts +72 -0
  13. package/dist/commands/describe.d.ts.map +1 -0
  14. package/dist/commands/describe.js +148 -0
  15. package/dist/commands/describe.js.map +1 -0
  16. package/dist/commands/doctor.d.ts.map +1 -1
  17. package/dist/commands/doctor.js +27 -8
  18. package/dist/commands/doctor.js.map +1 -1
  19. package/dist/commands/down-manifest.d.ts.map +1 -1
  20. package/dist/commands/down-manifest.js +38 -18
  21. package/dist/commands/down-manifest.js.map +1 -1
  22. package/dist/commands/down.d.ts +9 -0
  23. package/dist/commands/down.d.ts.map +1 -1
  24. package/dist/commands/down.js +38 -10
  25. package/dist/commands/down.js.map +1 -1
  26. package/dist/commands/ext.d.ts.map +1 -1
  27. package/dist/commands/ext.js +16 -10
  28. package/dist/commands/ext.js.map +1 -1
  29. package/dist/commands/join.d.ts.map +1 -1
  30. package/dist/commands/join.js +2 -4
  31. package/dist/commands/join.js.map +1 -1
  32. package/dist/commands/meshes-add.d.ts +17 -0
  33. package/dist/commands/meshes-add.d.ts.map +1 -1
  34. package/dist/commands/meshes-add.js +72 -4
  35. package/dist/commands/meshes-add.js.map +1 -1
  36. package/dist/commands/meshes-wizard.d.ts +3 -0
  37. package/dist/commands/meshes-wizard.d.ts.map +1 -1
  38. package/dist/commands/meshes-wizard.js +78 -6
  39. package/dist/commands/meshes-wizard.js.map +1 -1
  40. package/dist/commands/meshes.d.ts +4 -0
  41. package/dist/commands/meshes.d.ts.map +1 -1
  42. package/dist/commands/meshes.js +14 -4
  43. package/dist/commands/meshes.js.map +1 -1
  44. package/dist/commands/models.d.ts.map +1 -1
  45. package/dist/commands/models.js +2 -2
  46. package/dist/commands/models.js.map +1 -1
  47. package/dist/commands/spawn-manifest.d.ts.map +1 -1
  48. package/dist/commands/spawn-manifest.js +22 -12
  49. package/dist/commands/spawn-manifest.js.map +1 -1
  50. package/dist/commands/spawn.d.ts +5 -0
  51. package/dist/commands/spawn.d.ts.map +1 -1
  52. package/dist/commands/spawn.js +5 -3
  53. package/dist/commands/spawn.js.map +1 -1
  54. package/dist/commands/status.js +25 -5
  55. package/dist/commands/status.js.map +1 -1
  56. package/dist/commands/up.d.ts +17 -2
  57. package/dist/commands/up.d.ts.map +1 -1
  58. package/dist/commands/up.js +685 -50
  59. package/dist/commands/up.js.map +1 -1
  60. package/dist/console/app.d.ts +1 -1
  61. package/dist/console/app.d.ts.map +1 -1
  62. package/dist/console/app.js +16 -5
  63. package/dist/console/app.js.map +1 -1
  64. package/dist/console/commands.d.ts.map +1 -1
  65. package/dist/console/commands.js +5 -5
  66. package/dist/console/commands.js.map +1 -1
  67. package/dist/console/root.d.ts.map +1 -1
  68. package/dist/console/root.js +6 -1
  69. package/dist/console/root.js.map +1 -1
  70. package/dist/ext-loader.d.ts.map +1 -1
  71. package/dist/ext-loader.js +1 -0
  72. package/dist/ext-loader.js.map +1 -1
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +23 -0
  75. package/dist/index.js.map +1 -1
  76. package/dist/lib/attach-client.d.ts +37 -4
  77. package/dist/lib/attach-client.d.ts.map +1 -1
  78. package/dist/lib/attach-client.js +88 -18
  79. package/dist/lib/attach-client.js.map +1 -1
  80. package/dist/lib/auth-proc.d.ts +3 -1
  81. package/dist/lib/auth-proc.d.ts.map +1 -1
  82. package/dist/lib/auth-proc.js +15 -4
  83. package/dist/lib/auth-proc.js.map +1 -1
  84. package/dist/lib/connect.d.ts +1 -1
  85. package/dist/lib/connect.d.ts.map +1 -1
  86. package/dist/lib/connect.js +1 -1
  87. package/dist/lib/connect.js.map +1 -1
  88. package/dist/lib/control.d.ts +48 -12
  89. package/dist/lib/control.d.ts.map +1 -1
  90. package/dist/lib/control.js +258 -46
  91. package/dist/lib/control.js.map +1 -1
  92. package/dist/lib/delivery-proc.d.ts +19 -4
  93. package/dist/lib/delivery-proc.d.ts.map +1 -1
  94. package/dist/lib/delivery-proc.js +129 -43
  95. package/dist/lib/delivery-proc.js.map +1 -1
  96. package/dist/lib/join-target.d.ts +112 -0
  97. package/dist/lib/join-target.d.ts.map +1 -0
  98. package/dist/lib/join-target.js +130 -0
  99. package/dist/lib/join-target.js.map +1 -0
  100. package/dist/lib/manager-proc.d.ts +48 -5
  101. package/dist/lib/manager-proc.d.ts.map +1 -1
  102. package/dist/lib/manager-proc.js +131 -30
  103. package/dist/lib/manager-proc.js.map +1 -1
  104. package/dist/lib/manifest/ledger.d.ts +52 -8
  105. package/dist/lib/manifest/ledger.d.ts.map +1 -1
  106. package/dist/lib/manifest/ledger.js +77 -19
  107. package/dist/lib/manifest/ledger.js.map +1 -1
  108. package/dist/lib/manifest/live.d.ts +21 -16
  109. package/dist/lib/manifest/live.d.ts.map +1 -1
  110. package/dist/lib/manifest/live.js +33 -20
  111. package/dist/lib/manifest/live.js.map +1 -1
  112. package/dist/lib/restore.d.ts.map +1 -1
  113. package/dist/lib/restore.js +21 -3
  114. package/dist/lib/restore.js.map +1 -1
  115. package/dist/view/mesh-view.js +2 -2
  116. package/dist/view/mesh-view.js.map +1 -1
  117. package/package.json +4 -3
  118. package/dist/lib/pid.d.ts +0 -23
  119. package/dist/lib/pid.d.ts.map +0 -1
  120. package/dist/lib/pid.js +0 -39
  121. package/dist/lib/pid.js.map +0 -1
@@ -1,50 +1,77 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync, openSync, closeSync, writeFileSync, readFileSync, rmSync } from "node:fs";
3
3
  import { DEFAULT_SERVER, mintCreds, newIdentity, waitForDeliveryLease, } from "@cotal-ai/core";
4
- import { DELIVERY_CREDS_KEY, authDir, findCotalRoot, getSoleSpaceAuth, listSpaceAccounts, workspaceSecretStore } from "@cotal-ai/workspace";
4
+ import { DELIVERY_CREDS_KEY, authDir, findCotalRoot, getSoleSpaceAuth, listSpaceAccounts, parsePid, probeLiveness, workspaceSecretStore } from "@cotal-ai/workspace";
5
5
  import { selfArgv } from "./self-exec.js";
6
6
  import { resolveSpace } from "./status.js";
7
7
  import { cotalPath } from "./paths.js";
8
- import { ensureManager, managerHasDeliveryMarker, managerUp, stopManager } from "./manager-proc.js";
8
+ import { MANAGER_PID_PATH, ensureManager, managerHasDeliveryMarker, managerLiveness, stopManager } from "./manager-proc.js";
9
9
  const PID_PATH = () => cotalPath("delivery.pid");
10
10
  // The daemon's cred goes through the secret-store seam; the shared key (== the filename, so the
11
11
  // file stays `.cotal/delivery.creds`) comes from workspace — never a hand-copied literal.
12
12
  const credsStore = () => workspaceSecretStore(findCotalRoot());
13
- function alive(pid) {
14
- try {
15
- process.kill(pid, 0);
16
- return true;
17
- }
18
- catch {
19
- return false;
20
- }
21
- }
22
- /** True if the delivery daemon we started for this folder is still running (pid file + liveness). */
23
- export function deliveryUp() {
13
+ /** The recorded daemon's liveness, THREE-VALUED plus absent. See {@link managerLiveness} for why the
14
+ * boolean collapse is the defect: `unknown` is reachable on a real kernel (a seccomp
15
+ * `SECCOMP_RET_ERRNO` filter or an LSM policy answers `kill(pid, 0)` with an arbitrary errno and
16
+ * libuv preserves it), and both ways of folding it into a boolean fail silently. */
17
+ export function deliveryLiveness(probe = probeLiveness) {
24
18
  const p = PID_PATH();
25
19
  if (!existsSync(p))
26
- return false;
27
- const pid = Number(readFileSync(p, "utf8").trim());
28
- return Number.isFinite(pid) && alive(pid);
20
+ return "absent";
21
+ const raw = readFileSync(p, "utf8").trim();
22
+ if (raw === "")
23
+ return "absent";
24
+ const pid = parsePid(raw);
25
+ if (pid === undefined)
26
+ return "unattributable"; // see managerLiveness: never fold this into absent
27
+ return probe(pid);
28
+ }
29
+ /** True only if the daemon is PROVABLY running. Callers that ACT on the answer take
30
+ * {@link deliveryLiveness} instead; this cannot express "cannot tell". */
31
+ export function deliveryUp() {
32
+ return deliveryLiveness() === "alive";
29
33
  }
30
34
  /** True when this folder runs an authed mesh — the only mode with a delivery daemon (Plane-3 needs the
31
35
  * trusted reader; open dev mode is live-only). */
32
36
  function hasAuth() {
33
37
  return listSpaceAccounts(authDir(findCotalRoot())).length > 0;
34
38
  }
35
- /** True if an OLD (pre-delivery-daemon) Plane-3-hosting manager is live: a `manager.pid` that is alive
36
- * but carries no delivery-aware marker. Such a manager still calls `startPlane3` and would double-bind
37
- * `fanout`/`reader` against the new daemon. */
38
- function oldHostingManagerLive() {
39
- return managerUp() && !managerHasDeliveryMarker();
39
+ /** The cutover preflight's verdict, THREE-VALUED, because the boolean it replaced was read before the
40
+ * refusal boundary and therefore defeated it.
41
+ *
42
+ * `managerUp()` folds `unknown` to false, so an unattributable manager with no marker (which IS the
43
+ * old Plane-3-hosting shape) read as "no old manager", the preflight skipped, and `ensureDelivery`
44
+ * went on to mint a credential, write a pidfile and start a second daemon. Only afterwards did
45
+ * `ensureManager` throw. Everything the refusal was supposed to prevent had already happened, and
46
+ * the daemon's own lease cannot help: it is a delivery-daemon-only mechanism and can neither prove
47
+ * nor exclude an old manager still hosting Plane 3.
48
+ *
49
+ * A guard that runs after the work is not a guard, so this one reads the tri-state directly and the
50
+ * caller refuses BEFORE anything is minted, written or started. */
51
+ function oldHostingManagerVerdict(probe = probeLiveness) {
52
+ const state = managerLiveness(probe);
53
+ if (state === "unknown" || state === "unattributable")
54
+ return "indeterminate";
55
+ if (state !== "alive")
56
+ return "proceed"; // dead or absent: nothing is hosting Plane 3
57
+ return managerHasDeliveryMarker() ? "proceed" : "stop-it"; // alive: the marker decides
40
58
  }
41
59
  /** Cutover preflight — the FIRST action, BEFORE the daemon can bind: stop any old Plane-3-hosting
42
60
  * manager (live `manager.pid` without the delivery-aware marker) so it never double-binds the
43
61
  * daemon's durables. A delivery-aware (this-build) manager is left running. No-op on a fresh install. */
44
- export function stopOldHostingManagerIfPresent() {
45
- if (oldHostingManagerLive()) {
62
+ export async function stopOldHostingManagerIfPresent(probe = probeLiveness, signal) {
63
+ const verdict = oldHostingManagerVerdict(probe);
64
+ // FIRST action, before any mint/write/start, so the refusal actually fences the daemon.
65
+ if (verdict === "indeterminate")
66
+ throw new Error(`the recorded manager pid (${readFileSync(MANAGER_PID_PATH(), "utf8").trim()}) cannot be attributed, so the delivery cutover preflight cannot run: the kernel answered neither "running" nor "no such process" (a seccomp filter or LSM policy does this inside some sandboxes).\n` +
67
+ `Refusing before the daemon starts. If that manager is an old Plane-3-hosting one it is still bound to fanout/reader, and starting the daemon anyway would double-bind them; the daemon's own lease cannot detect that.\n` +
68
+ `NEXT: verify the process yourself (\`ps -p <pid>\`). If it is gone, remove \`.cotal/manager.pid\` and re-run. If it is running, stop it with \`cotal down\` first.`);
69
+ if (verdict === "stop-it") {
46
70
  console.error("• stopping an old Plane-3-hosting manager before starting the delivery daemon (cutover preflight)");
47
- stopManager();
71
+ // stopManager THROWS rather than reporting a stop it did not achieve (EPERM, or a process that
72
+ // outlived SIGTERM), so reaching the next line is the proof the old manager is gone. Letting that
73
+ // throw propagate is the point: the daemon must not start beside a manager still bound to Plane 3.
74
+ await stopManager(probe, signal);
48
75
  }
49
76
  }
50
77
  /** Start the delivery daemon detached (pid in `.cotal/delivery.pid`, output to `.cotal/delivery.log`),
@@ -60,6 +87,15 @@ export function startDeliveryDetached(o = {}) {
60
87
  o.space ?? resolveSpace(process.cwd()),
61
88
  "--server",
62
89
  o.server ?? DEFAULT_SERVER,
90
+ // Propagate the broker's transport decision. The daemon cannot derive it: it learns everything
91
+ // from argv by design - it is a pre-minted scoped-cred client, injectable behind a SecretStore,
92
+ // and making it read the machine-local mesh registry to pick a transport would couple a
93
+ // hosted-composable daemon to a workstation artifact. So the launcher, which DOES know, tells it.
94
+ //
95
+ // Without this the daemon connects plaintext-capable to a TLS broker and nothing looks wrong,
96
+ // because it still upgrades on the server's INFO. It holds a STANDING credential and reconnects
97
+ // unattended, so that exposure would repeat on every reconnect with nobody watching.
98
+ ...(o.tls ? ["--tls"] : []),
63
99
  ];
64
100
  // Internal child re-exec (the `up` that reached here already seeded); the delivery daemon does not
65
101
  // launch agents, so it skips the connector seed on boot (a direct `cotal deliver` still seeds).
@@ -74,10 +110,10 @@ export function startDeliveryDetached(o = {}) {
74
110
  * never double-binds. Mints a SCOPED `delivery` cred from the local signer ONCE, writes it to
75
111
  * `.cotal/delivery.creds` (0600), and launches the daemon WITHOUT signer access. Best-effort — callers
76
112
  * treat it as non-fatal (a missing daemon degrades durable delivery, never live). */
77
- export async function ensureDelivery(o = {}) {
113
+ export async function ensureDelivery(o = {}, probe = probeLiveness) {
78
114
  if (!hasAuth())
79
115
  return { running: false }; // open dev mode — no daemon, agents are live-only
80
- if (oldHostingManagerLive()) {
116
+ if (oldHostingManagerVerdict(probe) === "stop-it") {
81
117
  console.error("✗ delivery: an old Plane-3-hosting manager is still live (no delivery-aware marker). Refusing to start the daemon - run `cotal down` first, then retry.");
82
118
  return { running: false };
83
119
  }
@@ -90,7 +126,15 @@ export async function ensureDelivery(o = {}) {
90
126
  const creds = await mintCreds(auth, id, "delivery");
91
127
  const space = o.space ?? resolveSpace(process.cwd());
92
128
  const server = o.server ?? DEFAULT_SERVER;
93
- if (!deliveryUp()) {
129
+ const deliveryState = deliveryLiveness(probe);
130
+ // Same refusal as the manager: an unattributable pid must not be silently reused (a daemon
131
+ // reported running that is not there) nor silently replaced (two daemons on one fanout).
132
+ if (deliveryState === "unknown" || deliveryState === "unattributable")
133
+ throw new Error(`the recorded delivery daemon pid (${readFileSync(PID_PATH(), "utf8").trim()}) cannot be attributed: the kernel answered neither "running" nor "no such process".\n` +
134
+ `A seccomp filter or LSM policy that intercepts \`kill(pid, 0)\` does this, so it is expected inside some sandboxes and containers.\n` +
135
+ `Cotal will not guess: reusing it would report a daemon that is not there, and starting a second would put two daemons on one fanout.\n` +
136
+ `NEXT: verify the process yourself (\`ps -p <pid>\`). If it is gone, remove \`.cotal/delivery.pid\` and re-run. If it is running, use it or stop it.`);
137
+ if (deliveryState !== "alive") {
94
138
  // The store's put hardens `.cotal/` first (the cred is born under a private ACL, no race) and
95
139
  // lands it atomically — same path and bytes as before the seam.
96
140
  await credsStore().put(DELIVERY_CREDS_KEY, creds);
@@ -109,25 +153,67 @@ export async function ensureDelivery(o = {}) {
109
153
  * kill runs even if the creds delete fails (finally) — a delete error must never leave the daemon
110
154
  * alive to outlive the teardown and reattach to a restarted broker; the error still propagates
111
155
  * after the kill so the caller can surface it. */
112
- export async function stopDelivery() {
113
- try {
156
+ export async function stopDelivery(probe = probeLiveness, signal) {
157
+ const send = signal ?? ((pid, sig) => process.kill(pid, sig));
158
+ const p = PID_PATH();
159
+ // ORDER MATTERS, AND IT USED TO BE BACKWARDS. The credential was deleted FIRST, in a try whose
160
+ // finally then signalled and removed the pidfile unconditionally. Under a refused signal that left
161
+ // a LIVE daemon with no pidfile and no standing credential: still connected, still serving, and its
162
+ // reconnect and renewal source deleted out from under it, while the function returned success.
163
+ // Nothing is removed now until the process is proven gone.
164
+ // Once death is PROVEN, both records are stale and the pidfile goes first: a creds-delete failure
165
+ // (a blocked path, a store error) must still propagate loudly, but it must not leave behind a
166
+ // pidfile for a process that is definitely gone. delivery-teardown.smoke.ts pins exactly that
167
+ // combination, and caught this when the first version of this fix ordered them the other way.
168
+ const removeRecords = async () => {
169
+ rmSync(p, { force: true });
114
170
  await credsStore().delete(DELIVERY_CREDS_KEY);
171
+ };
172
+ if (!existsSync(p)) {
173
+ await credsStore().delete(DELIVERY_CREDS_KEY); // no pid recorded: no live daemon to strand
174
+ return;
175
+ }
176
+ const raw = readFileSync(p, "utf8").trim();
177
+ const pid = parsePid(raw);
178
+ if (pid === undefined) {
179
+ if (raw === "") {
180
+ await removeRecords(); // pre-protocol husk, nothing behind it
181
+ return;
182
+ }
183
+ // See the manager helper: unattributable content may front a live daemon, and clearing it here
184
+ // would also delete the standing credential of a process still using it.
185
+ throw new Error(`the delivery pidfile at ${p} is unattributable (${JSON.stringify(raw)}): it may still front a running daemon nobody can identify.\n` +
186
+ `Refusing to remove it or its standing credential, and refusing to report a clean stop.\n` +
187
+ `NEXT: find and stop that process, then remove the file by hand.`);
188
+ }
189
+ const before = probe(pid);
190
+ if (before === "dead") {
191
+ await removeRecords();
192
+ return;
193
+ }
194
+ if (before === "unknown")
195
+ throw new Error(`refusing to stop the delivery daemon (pid ${pid}): its liveness cannot be determined (a seccomp filter or LSM policy answers kill(pid,0) with an arbitrary errno).\n` +
196
+ `The pidfile and standing credential are LEFT IN PLACE: removing them would strand a daemon that may still be connected, with its renewal source gone.\n` +
197
+ `NEXT: verify with \`ps -p ${pid}\`, then stop it yourself or remove \`.cotal/delivery.pid\` if it is gone.`);
198
+ try {
199
+ send(pid, "SIGTERM");
115
200
  }
116
- finally {
117
- const p = PID_PATH();
118
- if (existsSync(p)) {
119
- const pid = Number(readFileSync(p, "utf8").trim());
120
- if (Number.isFinite(pid)) {
121
- try {
122
- process.kill(pid, "SIGTERM");
123
- }
124
- catch {
125
- /* already gone */
126
- }
127
- }
128
- rmSync(p);
201
+ catch (e) {
202
+ const code = e.code;
203
+ if (code === "ESRCH") {
204
+ await removeRecords(); // raced to exit between probe and signal
205
+ return;
129
206
  }
207
+ throw new Error(`refusing to stop the delivery daemon (pid ${pid}): the signal was rejected (${code ?? "unknown error"}).\n` +
208
+ `EPERM means it belongs to another user, so it is running and not ours to stop. The pidfile and standing credential are LEFT IN PLACE.\n` +
209
+ `NEXT: stop it as its owner, or remove \`.cotal/delivery.pid\` if you are certain it is gone.`);
130
210
  }
211
+ const deadline = Date.now() + 15_000;
212
+ while (probe(pid) !== "dead" && Date.now() < deadline)
213
+ await new Promise((r) => setTimeout(r, 100));
214
+ if (probe(pid) !== "dead")
215
+ throw new Error(`the delivery daemon (pid ${pid}) accepted SIGTERM but its death could not be confirmed; its pidfile and credential were preserved rather than recording a stop that did not happen.`);
216
+ await removeRecords();
131
217
  }
132
218
  /** Bring up the control plane in the correct cutover order: OLD-manager preflight → delivery daemon
133
219
  * (auth only, fails closed on a live old manager) → manager (lifecycle, writes the delivery-aware
@@ -135,7 +221,7 @@ export async function stopDelivery() {
135
221
  * first only to close the old-manager double-bind window and so freshly-spawned agents find the
136
222
  * `ctl.delivery` responder for their boot self-join (a miss honest-degrades to live-only). */
137
223
  export async function ensureControlPlane(o = {}) {
138
- stopOldHostingManagerIfPresent();
224
+ await stopOldHostingManagerIfPresent();
139
225
  await ensureDelivery(o);
140
226
  return ensureManager(o);
141
227
  }
@@ -1 +1 @@
1
- {"version":3,"file":"delivery-proc.js","sourceRoot":"","sources":["../../src/lib/delivery-proc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"delivery-proc.js","sourceRoot":"","sources":["../../src/lib/delivery-proc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Which broker addresses `cotal meshes add` may register.
3
+ *
4
+ * Registering a mesh is how a machine begins sending its agent credentials to a broker it does
5
+ * not run. NATS sends the initial INFO in plaintext and unauthenticated, so an on-path attacker
6
+ * can forge one that does not set `tls_required`, and a client that was never told to demand
7
+ * encryption puts its credentials in the CONNECT line for the attacker to read. The fence has to
8
+ * be client-side.
9
+ *
10
+ * The URL scheme is NOT that fence. Measured against `@nats-io/transport-node` 3.4.0 (the client
11
+ * this repo uses) pointed at a broker with no TLS configured at all:
12
+ *
13
+ * nats://host:port {} -> CONNECTED
14
+ * tls://host:port {} -> CONNECTED <- the scheme is cosmetic
15
+ * nats://host:port {tls:{}} -> REFUSED: server does not support 'tls'
16
+ *
17
+ * Only the explicit `tls` connect option makes the client demand TLS, and that option is the
18
+ * broker-TLS work's surface, not this one's. So this gates on the ADDRESS instead, in two steps.
19
+ * Step one, here: refuse public addresses, ordinary private ranges and every hostname outright,
20
+ * and refuse an overlay literal TOO unless the operator accepted it explicitly
21
+ * ({@link DialPolicy.allowUnencryptedOverlay}); accepted, it returns a {@link JoinTarget.residual}
22
+ * the caller prints and records as consent. Step two, once a record can carry TLS intent: the
23
+ * acceptance stops being needed because the transport is proven instead of promised.
24
+ *
25
+ * ONE deliberate limitation, stated rather than papered over: this classifies an ADDRESS, which
26
+ * is a guard against the obvious mistake (dialing a LAN or public address in the clear), not
27
+ * proof that the bytes are encrypted. An overlay address only rides an encrypted tunnel while the
28
+ * overlay is actually up on this machine; the same literal with the overlay down is just a CGNAT
29
+ * address.
30
+ *
31
+ * WHERE THIS GOES NEXT, so the next editor does not relax it for the wrong reason. Measured on
32
+ * this stack: the `tls` connect option verifies the certificate CHAIN AND THE HOSTNAME (Node
33
+ * defaults, `rejectUnauthorized: true`, servername from the URL), so a redirected name cannot
34
+ * complete the handshake and hostnames become safe — but only once something REQUIRES TLS. The
35
+ * relaxation is therefore a function of recorded strictness, not of time passing, and the agreed
36
+ * eventual shape is `classifyJoinTarget(url, { tlsRequired })` rather than a third verdict: one
37
+ * place keeps answering "may this machine send credentials to that address", with strictness as
38
+ * an input it cannot otherwise see. Two counterintuitive consequences worth keeping in view:
39
+ * the overlay ranges are the AWKWARD case under TLS (CGNAT space, no public CA will issue for it,
40
+ * verifying a literal needs an IP SAN, so it implies a private CA on every joiner), while the
41
+ * `<host>.ts.net` names refused below are the EASY case (publicly resolvable, publicly-trusted
42
+ * certs). Do not pre-relax: with nothing requiring TLS today, literals-only is the correct rule.
43
+ */
44
+ /** The address class a permitted target belongs to. This is a REACHABILITY allowlist: it says the
45
+ * address is one we are willing to consider, never that the connection is protected. What
46
+ * protects it is {@link DialPolicy.tlsRequired}, except on loopback where nothing leaves the
47
+ * machine to protect. */
48
+ export type JoinReach =
49
+ /** A loopback literal: the bytes never leave the machine. */
50
+ "loopback"
51
+ /** A private-overlay literal. Permitted TODAY without required TLS, carrying a
52
+ * {@link JoinTarget.residual} that says so; it becomes conditional on TLS in step two. The
53
+ * address alone is not a guarantee - see {@link DialPolicy.tlsRequired}. */
54
+ | "overlay";
55
+ export interface JoinTarget {
56
+ /** The normalized dial URL, port defaulted. */
57
+ server: string;
58
+ reach: JoinReach;
59
+ /**
60
+ * Set when the target is permitted but its protection is NOT guaranteed, carrying the sentence
61
+ * the operator needs to read. Exactly one case produces it today: an overlay literal dialed
62
+ * without required TLS, where the address class is right but the tunnel being up is an
63
+ * assumption rather than a fact.
64
+ *
65
+ * It is a returned value rather than a throw because by the time it is produced the operator has
66
+ * ALREADY accepted the risk, so the remaining job is to carry the sentence: the caller prints it
67
+ * as a second notice and records it on the entry as evidence of consent. The refusal for someone
68
+ * who has NOT accepted happens earlier, and is a throw.
69
+ */
70
+ residual?: string;
71
+ }
72
+ /** What the eventual connection will insist on, which the address alone cannot tell us. */
73
+ export interface DialPolicy {
74
+ /**
75
+ * Will the dial to this target REQUIRE TLS?
76
+ *
77
+ * This is the difference between an address and a guarantee, and getting it wrong was the
78
+ * defect this parameter exists to close. An overlay address is not proof the overlay transport
79
+ * is up: with the tunnel daemon stopped, `100.64.0.0/10` is ordinary CGNAT space, and hostile
80
+ * DHCP or routing can answer a dial to it. So the address class establishes only that we are
81
+ * willing to consider the target; requiring TLS is what makes it safe.
82
+ *
83
+ * There is no field on the mesh record to source this from yet — it arrives with the work that
84
+ * teaches the broker to serve TLS — so callers pass `false` today, and with it false an overlay
85
+ * literal is refused unless {@link DialPolicy.allowUnencryptedOverlay} says the operator
86
+ * accepted the tunnel dependency. When this can be `true` the acceptance stops being needed:
87
+ * the transport is then proven rather than promised, and the flag can go.
88
+ */
89
+ tlsRequired: boolean;
90
+ /**
91
+ * Did the operator EXPLICITLY accept the overlay's tunnel dependency?
92
+ *
93
+ * The default is `false` and the default refuses, because the previous shape — permit and print
94
+ * a warning — is not a fence. A warning is written to stderr, which a non-interactive caller
95
+ * need not read, and it was not persisted, so nothing repeated it at the dials that followed. A
96
+ * scripted registration therefore got the risk with none of the notice.
97
+ *
98
+ * A flag is the one form of notice a script cannot miss: without it the command fails, and the
99
+ * failure is in the exit code rather than in prose nobody reads. The acceptance is then recorded
100
+ * on the mesh entry, so a later dial can tell that a human agreed to this rather than inferring
101
+ * consent from an address.
102
+ */
103
+ allowUnencryptedOverlay: boolean;
104
+ }
105
+ /**
106
+ * Classify a registration target, or throw the reason it is refused.
107
+ *
108
+ * Refusal is the point: an unclassifiable target is one whose credentials would cross a network
109
+ * this build cannot encrypt, so it fails loud here rather than dialing and hoping.
110
+ */
111
+ export declare function classifyJoinTarget(raw: string, policy: DialPolicy): JoinTarget;
112
+ //# sourceMappingURL=join-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join-target.d.ts","sourceRoot":"","sources":["../../src/lib/join-target.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH;;;0BAG0B;AAC1B,MAAM,MAAM,SAAS;AACnB,6DAA6D;AAC3D,UAAU;AACZ;;6EAE6E;GAC3E,SAAS,CAAC;AAEd,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,CAAC;IACjB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,2FAA2F;AAC3F,MAAM,WAAW,UAAU;IACzB;;;;;;;;;;;;;;OAcG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;OAYG;IACH,uBAAuB,EAAE,OAAO,CAAC;CAClC;AAuCD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,UAAU,CA8C9E"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Which broker addresses `cotal meshes add` may register.
3
+ *
4
+ * Registering a mesh is how a machine begins sending its agent credentials to a broker it does
5
+ * not run. NATS sends the initial INFO in plaintext and unauthenticated, so an on-path attacker
6
+ * can forge one that does not set `tls_required`, and a client that was never told to demand
7
+ * encryption puts its credentials in the CONNECT line for the attacker to read. The fence has to
8
+ * be client-side.
9
+ *
10
+ * The URL scheme is NOT that fence. Measured against `@nats-io/transport-node` 3.4.0 (the client
11
+ * this repo uses) pointed at a broker with no TLS configured at all:
12
+ *
13
+ * nats://host:port {} -> CONNECTED
14
+ * tls://host:port {} -> CONNECTED <- the scheme is cosmetic
15
+ * nats://host:port {tls:{}} -> REFUSED: server does not support 'tls'
16
+ *
17
+ * Only the explicit `tls` connect option makes the client demand TLS, and that option is the
18
+ * broker-TLS work's surface, not this one's. So this gates on the ADDRESS instead, in two steps.
19
+ * Step one, here: refuse public addresses, ordinary private ranges and every hostname outright,
20
+ * and refuse an overlay literal TOO unless the operator accepted it explicitly
21
+ * ({@link DialPolicy.allowUnencryptedOverlay}); accepted, it returns a {@link JoinTarget.residual}
22
+ * the caller prints and records as consent. Step two, once a record can carry TLS intent: the
23
+ * acceptance stops being needed because the transport is proven instead of promised.
24
+ *
25
+ * ONE deliberate limitation, stated rather than papered over: this classifies an ADDRESS, which
26
+ * is a guard against the obvious mistake (dialing a LAN or public address in the clear), not
27
+ * proof that the bytes are encrypted. An overlay address only rides an encrypted tunnel while the
28
+ * overlay is actually up on this machine; the same literal with the overlay down is just a CGNAT
29
+ * address.
30
+ *
31
+ * WHERE THIS GOES NEXT, so the next editor does not relax it for the wrong reason. Measured on
32
+ * this stack: the `tls` connect option verifies the certificate CHAIN AND THE HOSTNAME (Node
33
+ * defaults, `rejectUnauthorized: true`, servername from the URL), so a redirected name cannot
34
+ * complete the handshake and hostnames become safe — but only once something REQUIRES TLS. The
35
+ * relaxation is therefore a function of recorded strictness, not of time passing, and the agreed
36
+ * eventual shape is `classifyJoinTarget(url, { tlsRequired })` rather than a third verdict: one
37
+ * place keeps answering "may this machine send credentials to that address", with strictness as
38
+ * an input it cannot otherwise see. Two counterintuitive consequences worth keeping in view:
39
+ * the overlay ranges are the AWKWARD case under TLS (CGNAT space, no public CA will issue for it,
40
+ * verifying a literal needs an IP SAN, so it implies a private CA on every joiner), while the
41
+ * `<host>.ts.net` names refused below are the EASY case (publicly resolvable, publicly-trusted
42
+ * certs). Do not pre-relax: with nothing requiring TLS today, literals-only is the correct rule.
43
+ */
44
+ /** The NATS default client port, used when the join URL omits one. */
45
+ const DEFAULT_PORT = 4222;
46
+ /** Loopback: `127.0.0.0/8` and `::1`. LITERALS ONLY, never `localhost`, because the whole point is
47
+ * a verdict that does not depend on a resolver an attacker could influence (a hosts-file entry or
48
+ * a poisoned lookup would otherwise turn "loopback" into any address at all). */
49
+ function isLoopbackLiteral(host) {
50
+ if (host === "::1" || host === "0:0:0:0:0:0:0:1")
51
+ return true;
52
+ const v4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
53
+ if (!v4)
54
+ return false;
55
+ const parts = v4.slice(1).map(Number);
56
+ if (parts.some((n) => n > 255))
57
+ return false;
58
+ return parts[0] === 127;
59
+ }
60
+ /** The private overlay's address space: `100.64.0.0/10` (the CGNAT range Tailscale assigns) and
61
+ * `fd7a:115c:a1e0::/48` (its ULA prefix). LITERALS ONLY, for the same reason as loopback: a
62
+ * MagicDNS name like `<host>.ts.net` is a resolver answer, and accepting it would let whoever
63
+ * answers that lookup redirect a plaintext credential-bearing dial anywhere they like. */
64
+ function isOverlayLiteral(host) {
65
+ const v4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
66
+ if (v4) {
67
+ const parts = v4.slice(1).map(Number);
68
+ if (parts.some((n) => n > 255))
69
+ return false;
70
+ // 100.64.0.0/10 == 100.64.0.0 through 100.127.255.255.
71
+ return parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127;
72
+ }
73
+ return /^fd7a:115c:a1e0:/i.test(host);
74
+ }
75
+ /** A URL's hostname with an IPv6 literal's brackets removed (`[::1]` -> `::1`). */
76
+ function bareHost(url) {
77
+ return url.hostname.startsWith("[") && url.hostname.endsWith("]")
78
+ ? url.hostname.slice(1, -1)
79
+ : url.hostname;
80
+ }
81
+ /**
82
+ * Classify a registration target, or throw the reason it is refused.
83
+ *
84
+ * Refusal is the point: an unclassifiable target is one whose credentials would cross a network
85
+ * this build cannot encrypt, so it fails loud here rather than dialing and hoping.
86
+ */
87
+ export function classifyJoinTarget(raw, policy) {
88
+ let url;
89
+ try {
90
+ url = new URL(raw);
91
+ }
92
+ catch {
93
+ throw new Error(`${JSON.stringify(raw)} is not a URL - pass a broker address like nats://100.64.0.1:4222`);
94
+ }
95
+ if (url.protocol !== "nats:" && url.protocol !== "tls:")
96
+ throw new Error(`${JSON.stringify(raw)} must be a nats:// or tls:// URL, not ${url.protocol}//`);
97
+ const host = bareHost(url);
98
+ if (!host)
99
+ throw new Error(`${JSON.stringify(raw)} has no host`);
100
+ const port = url.port || String(DEFAULT_PORT);
101
+ const server = `${url.protocol}//${url.hostname}:${port}`;
102
+ // Loopback is the one class that needs no transport guarantee: nothing leaves the machine, so
103
+ // there is nothing on a wire for anyone to sit on.
104
+ if (isLoopbackLiteral(host))
105
+ return { server, reach: "loopback" };
106
+ if (isOverlayLiteral(host)) {
107
+ if (policy.tlsRequired)
108
+ return { server, reach: "overlay" };
109
+ if (!policy.allowUnencryptedOverlay)
110
+ throw new Error(`${JSON.stringify(raw)} refused: ${host} is a private-overlay address, and this build cannot require TLS on the connection.\n` +
111
+ ` That is safe while the overlay tunnel is up, and NOT safe if it is down: the range is then ordinary carrier-grade\n` +
112
+ ` NAT, and whoever answers the dial receives the credentials this machine sends.\n` +
113
+ ` Only you can know whether the tunnel is up, so accept it explicitly with --allow-unencrypted-overlay, which records\n` +
114
+ ` your acceptance on the mesh entry. The flag goes away once the broker can be served over TLS.`);
115
+ return {
116
+ server,
117
+ reach: "overlay",
118
+ residual: `${host} is a private-overlay address, and this build cannot yet require TLS on the connection.\n` +
119
+ ` That is safe while the overlay tunnel is actually up: it authenticates both machines and encrypts the link.\n` +
120
+ ` It is NOT safe if the tunnel is down. That range is then ordinary carrier-grade NAT space, and whoever\n` +
121
+ ` answers the dial receives the credentials this machine will send. Keep the tunnel up, and expect this to\n` +
122
+ ` become a refusal once the broker can be served over TLS and the record can say TLS is required.`,
123
+ };
124
+ }
125
+ throw new Error(`${JSON.stringify(raw)} refused: this build cannot protect a connection to ${host}, and a machine that registers a mesh sends its agent credentials to that broker.\n` +
126
+ ` Only a loopback literal (127.0.0.0/8, ::1) or a private-overlay literal (100.64.0.0/10, fd7a:115c:a1e0::/48) may be registered. An overlay literal additionally needs --allow-unencrypted-overlay, which records that you accepted its tunnel dependency.\n` +
127
+ ` Ordinary private ranges are refused too: a cafe network is private, and private is not the same as yours.\n` +
128
+ ` A hostname is refused even when it resolves somewhere permitted - otherwise whoever answers the lookup chooses which machine receives the credentials. Pass the address itself.`);
129
+ }
130
+ //# sourceMappingURL=join-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join-target.js","sourceRoot":"","sources":["../../src/lib/join-target.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAkEH,sEAAsE;AACtE,MAAM,YAAY,GAAG,IAAI,CAAC;AAE1B;;kFAEkF;AAClF,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,iBAAiB;QAAE,OAAO,IAAI,CAAC;IAC9D,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;IACtE,IAAI,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IACtB,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;AAC1B,CAAC;AAED;;;2FAG2F;AAC3F,SAAS,gBAAgB,CAAC,IAAY;IACpC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;IACtE,IAAI,EAAE,EAAE,CAAC;QACP,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAC7C,uDAAuD;QACvD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IAC/D,CAAC;IACD,OAAO,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,mFAAmF;AACnF,SAAS,QAAQ,CAAC,GAAQ;IACxB,OAAO,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAC/D,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3B,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,MAAkB;IAChE,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,mEAAmE,CAAC,CAAC;IAC7G,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM;QACrD,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,yCAAyC,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC;IACnG,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,KAAK,GAAG,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;IAE1D,8FAA8F;IAC9F,mDAAmD;IACnD,IAAI,iBAAiB,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAElE,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,IAAI,MAAM,CAAC,WAAW;YAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAC5D,IAAI,CAAC,MAAM,CAAC,uBAAuB;YACjC,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,aAAa,IAAI,uFAAuF;gBAC5H,uHAAuH;gBACvH,oFAAoF;gBACpF,yHAAyH;gBACzH,iGAAiG,CACpG,CAAC;QACJ,OAAO;YACL,MAAM;YACN,KAAK,EAAE,SAAS;YAChB,QAAQ,EACN,GAAG,IAAI,2FAA2F;gBAClG,iHAAiH;gBACjH,4GAA4G;gBAC5G,8GAA8G;gBAC9G,mGAAmG;SACtG,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,uDAAuD,IAAI,qFAAqF;QACpK,+PAA+P;QAC/P,+GAA+G;QAC/G,mLAAmL,CACtL,CAAC;AACJ,CAAC"}
@@ -1,4 +1,19 @@
1
- /** True if the manager we started for this folder is still running (pid file + liveness). */
1
+ import { type LivenessProbe } from "@cotal-ai/workspace";
2
+ /** Exported so the delivery cutover preflight can NAME the pid it refused on: an error that says
3
+ * "cannot be attributed" without saying which pid is not actionable. */
4
+ export declare const MANAGER_PID_PATH: () => string;
5
+ /** The recorded manager's liveness, THREE-VALUED plus absent, because collapsing it to a boolean is
6
+ * what made this dangerous. Both collapses are silent and both are wrong:
7
+ * `!== "dead"` -> an `unknown` reports UP forever; no retry clears it and nothing starts.
8
+ * `=== "alive"` -> an `unknown` reports DOWN and a second manager launches onto a possibly-live one.
9
+ * `unknown` is REACHABLE on a real kernel, not just under a test shim: a Linux seccomp filter
10
+ * (`SECCOMP_RET_ERRNO`) or an LSM policy can return an arbitrary errno for `kill(pid, 0)` without
11
+ * executing it at all, and libuv preserves it. Proven with a live seccomp BPF filter, not by
12
+ * interposition. So the caller has to SEE the third state and refuse. */
13
+ export declare function managerLiveness(probe?: LivenessProbe): "alive" | "dead" | "unknown" | "absent" | "unattributable";
14
+ /** True only if the manager is PROVABLY running. `unknown` is not up, and callers that would ACT on
15
+ * that answer must use {@link managerLiveness} instead: this boolean cannot express the difference
16
+ * between "not running" and "cannot tell", and acting on the difference is the whole point. */
2
17
  export declare function managerUp(): boolean;
3
18
  /** True if the live manager carries a delivery-aware marker BOUND to its current pid (i.e. it's THIS
4
19
  * build, non-hosting). Fail-closed: the marker stores the pid it was written for, and this requires it
@@ -20,11 +35,20 @@ export declare function startManagerDetached(o?: {
20
35
  attachHost?: string;
21
36
  resumeAttempt?: string;
22
37
  resumeCommitToken?: string;
38
+ wsPort?: number;
23
39
  }): number;
24
40
  /** Make the control plane available: reuse a manager already running for this folder, else start
25
41
  * one detached. Best-effort — callers treat it as non-fatal. A caller that needs THE manager to
26
42
  * carry a runtime/launch spec (`up -f`) must stop any leftover manager first — a reused one is
27
43
  * taken as-is. */
44
+ /** Refuse to stand a manager up OVER a record we cannot read or attribute.
45
+ *
46
+ * Exported because `ensureManager` is not the only path that starts one: `cotal spawn -f` calls
47
+ * `startManagerDetached` directly after its own lease checks, and so skipped this entirely. A lease
48
+ * says nobody is ANSWERING; it does not say the recorded pid is dead. Overwriting an unknown or
49
+ * unattributable record is the same defect as deleting it, reached through a different verb, which
50
+ * is the third time that shape has appeared in this change. Any future starter calls this first. */
51
+ export declare function assertManagerRecordReplaceable(probe?: LivenessProbe): void;
28
52
  export declare function ensureManager(o?: {
29
53
  space?: string;
30
54
  server?: string;
@@ -34,10 +58,29 @@ export declare function ensureManager(o?: {
34
58
  attachHost?: string;
35
59
  resumeAttempt?: string;
36
60
  resumeCommitToken?: string;
37
- }): {
61
+ wsPort?: number;
62
+ }, probe?: LivenessProbe): {
38
63
  running: boolean;
39
64
  };
40
- /** Stop the detached (pty) manager if we started one. Used when switching to the cmux-tab manager
41
- * so the two don't both answer the control plane (queue-grouped requests would split between them). */
42
- export declare function stopManager(): void;
65
+ /** A signal, injectable for the same reason the probe is: `EPERM` from `kill` is producible only by
66
+ * another user's process or by kernel policy, so the branch that handles it is otherwise unreachable
67
+ * from a test. Production passes nothing. */
68
+ export type SignalFn = (pid: number, signal: NodeJS.Signals) => void;
69
+ /** What a stop actually achieved, because "void" let this function claim success it had not earned. */
70
+ export type StopVerdict = "stopped" | "already-gone";
71
+ /** Blocking sleep. DO NOT reintroduce this for death-waiting: it blocks the event loop, so a child
72
+ * of this process is never reaped, remains a signalable zombie, and reads `alive` forever. That
73
+ * turned `cotal up` + Ctrl-C into an exit-1 that left the broker running. Death-waits await. */
74
+ /** Stop the detached (pty) manager if we started one, and remove its records ONLY once it is gone.
75
+ *
76
+ * THIS USED TO CATCH EVERY SIGNAL FAILURE AS "already gone" AND DELETE THE RECORDS ANYWAY. That was
77
+ * survivable while `EPERM` was misread as dead, because the caller never got here: the cutover
78
+ * preflight skipped a manager it thought was not running. Resolving `EPERM` to `alive` (the fix this
79
+ * change exists for) makes the preflight recognise ANOTHER USER's live manager and call this, at
80
+ * which point the old code sent a signal it was not permitted to send, swallowed the refusal, and
81
+ * deleted the pidfile and marker of a process that was still running and still bound to Plane 3.
82
+ * A correct fix upstream reaching a latent destructive bug downstream is the worst shape available,
83
+ * so this refuses instead: records are removed only on proven death, never on a signal we could not
84
+ * send or a death we could not confirm. Found by review, with a kernel seccomp proof. */
85
+ export declare function stopManager(probe?: LivenessProbe, signal?: SignalFn | undefined): Promise<StopVerdict>;
43
86
  //# sourceMappingURL=manager-proc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"manager-proc.d.ts","sourceRoot":"","sources":["../../src/lib/manager-proc.ts"],"names":[],"mappings":"AAwBA,6FAA6F;AAC7F,wBAAgB,SAAS,IAAI,OAAO,CAKnC;AAED;;;;2CAI2C;AAC3C,wBAAgB,wBAAwB,IAAI,OAAO,CAOlD;AAED;;;;0FAI0F;AAC1F,wBAAgB,oBAAoB,CAClC,CAAC,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GACxK,MAAM,CAwCR;AAED;;;mBAGmB;AACnB,wBAAgB,aAAa,CAC3B,CAAC,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GACxK;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAItB;AAED;wGACwG;AACxG,wBAAgB,WAAW,IAAI,IAAI,CAalC"}
1
+ {"version":3,"file":"manager-proc.d.ts","sourceRoot":"","sources":["../../src/lib/manager-proc.ts"],"names":[],"mappings":"AAMA,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAElF;yEACyE;AACzE,eAAO,MAAM,gBAAgB,QAAO,MAAkC,CAAC;AASvE;;;;;;;0EAO0E;AAC1E,wBAAgB,eAAe,CAAC,KAAK,GAAE,aAA6B,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,gBAAgB,CAYhI;AAED;;gGAEgG;AAChG,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAID;;;;2CAI2C;AAC3C,wBAAgB,wBAAwB,IAAI,OAAO,CAOlD;AAED;;;;0FAI0F;AAC1F,wBAAgB,oBAAoB,CAClC,CAAC,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GACzL,MAAM,CA0CR;AAED;;;mBAGmB;AACnB;;;;;;qGAMqG;AACrG,wBAAgB,8BAA8B,CAAC,KAAK,GAAE,aAA6B,GAAG,IAAI,CAczF;AAED,wBAAgB,aAAa,CAC3B,CAAC,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,EAC1L,KAAK,GAAE,aAA6B,GACnC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAMtB;AAED;;8CAE8C;AAC9C,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC;AAErE,uGAAuG;AACvG,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,cAAc,CAAC;AAErD;;iGAEiG;AAGjG;;;;;;;;;;0FAU0F;AAC1F,wBAAsB,WAAW,CAAC,KAAK,GAAE,aAA6B,EAAE,MAAM,GAAE,QAAQ,GAAG,SAAqB,GAAG,OAAO,CAAC,WAAW,CAAC,CAsEtI"}