@serfab/cadre-host 1.12.0 → 1.14.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 (337) hide show
  1. package/README.md +332 -423
  2. package/dist/bin/host.d.ts +6 -3
  3. package/dist/bin/host.js +301 -505
  4. package/dist/bin/host.js.map +1 -1
  5. package/dist/bin/join-output.d.ts +56 -0
  6. package/dist/bin/join-output.js +92 -0
  7. package/dist/bin/join-output.js.map +1 -0
  8. package/dist/bin/nat-output.d.ts +49 -0
  9. package/dist/bin/nat-output.js +127 -0
  10. package/dist/bin/nat-output.js.map +1 -0
  11. package/dist/hosted/claim-details.d.ts +44 -0
  12. package/dist/hosted/claim-details.js +66 -0
  13. package/dist/hosted/claim-details.js.map +1 -0
  14. package/dist/hosted/hosted-node-service.d.ts +318 -0
  15. package/dist/hosted/hosted-node-service.js +666 -0
  16. package/dist/hosted/hosted-node-service.js.map +1 -0
  17. package/dist/hosted/hosted-node-store.d.ts +40 -0
  18. package/dist/hosted/hosted-node-store.js +126 -0
  19. package/dist/hosted/hosted-node-store.js.map +1 -0
  20. package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
  21. package/dist/hosted/hosted-node-supervisor.js +396 -0
  22. package/dist/hosted/hosted-node-supervisor.js.map +1 -0
  23. package/dist/hosted/hosted-node-watcher.d.ts +83 -0
  24. package/dist/hosted/hosted-node-watcher.js +224 -0
  25. package/dist/hosted/hosted-node-watcher.js.map +1 -0
  26. package/dist/hosted/index.d.ts +16 -0
  27. package/dist/hosted/index.js +13 -0
  28. package/dist/hosted/index.js.map +1 -0
  29. package/dist/hosted/node-status.d.ts +35 -0
  30. package/dist/hosted/node-status.js +61 -0
  31. package/dist/hosted/node-status.js.map +1 -0
  32. package/dist/hosted/types.d.ts +134 -0
  33. package/dist/hosted/types.js +22 -0
  34. package/dist/hosted/types.js.map +1 -0
  35. package/dist/index.d.ts +12 -24
  36. package/dist/index.js +9 -19
  37. package/dist/index.js.map +1 -1
  38. package/dist/installer/config.d.ts +4 -34
  39. package/dist/installer/config.js +3 -18
  40. package/dist/installer/config.js.map +1 -1
  41. package/dist/installer/index.d.ts +7 -19
  42. package/dist/installer/index.js +14 -69
  43. package/dist/installer/index.js.map +1 -1
  44. package/dist/installer/paths.d.ts +0 -2
  45. package/dist/installer/paths.js +0 -4
  46. package/dist/installer/paths.js.map +1 -1
  47. package/dist/installer/wizard.d.ts +0 -7
  48. package/dist/installer/wizard.js +1 -8
  49. package/dist/installer/wizard.js.map +1 -1
  50. package/dist/nat/address-resolver.d.ts +18 -17
  51. package/dist/nat/address-resolver.js +79 -25
  52. package/dist/nat/address-resolver.js.map +1 -1
  53. package/dist/nat/address-watch.d.ts +37 -0
  54. package/dist/nat/address-watch.js +84 -0
  55. package/dist/nat/address-watch.js.map +1 -0
  56. package/dist/nat/ddns/duckdns.d.ts +2 -1
  57. package/dist/nat/ddns/duckdns.js +5 -1
  58. package/dist/nat/ddns/duckdns.js.map +1 -1
  59. package/dist/nat/ddns/index.d.ts +6 -0
  60. package/dist/nat/ddns/index.js.map +1 -1
  61. package/dist/nat/index.d.ts +12 -9
  62. package/dist/nat/index.js +6 -5
  63. package/dist/nat/index.js.map +1 -1
  64. package/dist/nat/nat-service.d.ts +203 -77
  65. package/dist/nat/nat-service.js +602 -237
  66. package/dist/nat/nat-service.js.map +1 -1
  67. package/dist/nat/nat-store.d.ts +12 -5
  68. package/dist/nat/nat-store.js +114 -30
  69. package/dist/nat/nat-store.js.map +1 -1
  70. package/dist/nat/port-mapper.d.ts +83 -79
  71. package/dist/nat/port-mapper.js +148 -189
  72. package/dist/nat/port-mapper.js.map +1 -1
  73. package/dist/nat/reachability.d.ts +31 -15
  74. package/dist/nat/reachability.js +97 -22
  75. package/dist/nat/reachability.js.map +1 -1
  76. package/dist/nat/types.d.ts +78 -27
  77. package/dist/nat/types.js +8 -5
  78. package/dist/nat/types.js.map +1 -1
  79. package/dist/orchestrator/host-process-orchestrator.d.ts +29 -71
  80. package/dist/orchestrator/host-process-orchestrator.js +78 -218
  81. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  82. package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
  83. package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
  84. package/dist/orchestrator/identity-file.js.map +1 -0
  85. package/dist/orchestrator/index.d.ts +2 -3
  86. package/dist/orchestrator/index.js +1 -1
  87. package/dist/orchestrator/index.js.map +1 -1
  88. package/dist/orchestrator/node-identity.d.ts +6 -6
  89. package/dist/orchestrator/node-identity.js +7 -7
  90. package/dist/orchestrator/node-identity.js.map +1 -1
  91. package/dist/orchestrator/port-allocator.d.ts +5 -6
  92. package/dist/orchestrator/port-allocator.js +9 -11
  93. package/dist/orchestrator/port-allocator.js.map +1 -1
  94. package/dist/orchestrator/state-store.d.ts +3 -5
  95. package/dist/orchestrator/state-store.js +2 -10
  96. package/dist/orchestrator/state-store.js.map +1 -1
  97. package/dist/orchestrator/types.d.ts +33 -22
  98. package/dist/orchestrator/types.js.map +1 -1
  99. package/dist/server/error-handler.d.ts +5 -25
  100. package/dist/server/error-handler.js +11 -74
  101. package/dist/server/error-handler.js.map +1 -1
  102. package/dist/server/events/types.d.ts +7 -11
  103. package/dist/server/events/types.js +2 -1
  104. package/dist/server/events/types.js.map +1 -1
  105. package/dist/server/index.d.ts +11 -37
  106. package/dist/server/index.js +30 -48
  107. package/dist/server/index.js.map +1 -1
  108. package/dist/server/routes/hosted-nodes.d.ts +28 -0
  109. package/dist/server/routes/hosted-nodes.js +68 -0
  110. package/dist/server/routes/hosted-nodes.js.map +1 -0
  111. package/dist/server/routes/nat.d.ts +4 -1
  112. package/dist/server/routes/nat.js +13 -2
  113. package/dist/server/routes/nat.js.map +1 -1
  114. package/dist/server/routes/nodes.d.ts +6 -20
  115. package/dist/server/routes/nodes.js +7 -86
  116. package/dist/server/routes/nodes.js.map +1 -1
  117. package/dist/server/routes/settings.d.ts +2 -7
  118. package/dist/server/routes/settings.js +3 -7
  119. package/dist/server/routes/settings.js.map +1 -1
  120. package/dist/server/routes/status.d.ts +2 -25
  121. package/dist/server/routes/status.js +1 -11
  122. package/dist/server/routes/status.js.map +1 -1
  123. package/dist/server/server.js +8 -8
  124. package/dist/server/settings-store.d.ts +2 -2
  125. package/dist/server/settings-store.js +2 -2
  126. package/dist/server/static.js +1 -2
  127. package/dist/server/static.js.map +1 -1
  128. package/dist/ui/assets/index-4FWuU8ye.js +19 -0
  129. package/dist/ui/assets/index-4FWuU8ye.js.map +1 -0
  130. package/dist/ui/assets/index-Di9Q4d01.css +1 -0
  131. package/dist/ui/index.html +2 -2
  132. package/dist/update/index.d.ts +2 -2
  133. package/dist/update/index.js +2 -2
  134. package/dist/update/store.d.ts +1 -1
  135. package/dist/update/store.js +1 -1
  136. package/dist/update/types.d.ts +1 -1
  137. package/dist/update/types.js +1 -1
  138. package/package.json +5 -6
  139. package/src/__tests__/cli-nat.smoke.test.ts +219 -212
  140. package/src/__tests__/cli.smoke.test.ts +1 -1
  141. package/src/__tests__/global-setup.ts +2 -3
  142. package/src/__tests__/orchestrator-claim-secret.test.ts +112 -0
  143. package/src/__tests__/orchestrator-handle-contract.ts +134 -134
  144. package/src/__tests__/orchestrator-node-identity.test.ts +182 -203
  145. package/src/__tests__/orchestrator-ports.test.ts +124 -143
  146. package/src/__tests__/orchestrator-push.test.ts +138 -146
  147. package/src/__tests__/orchestrator.test.ts +1019 -1063
  148. package/src/bin/host.ts +368 -553
  149. package/src/bin/join-output.ts +120 -0
  150. package/src/bin/nat-output.ts +177 -0
  151. package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +173 -173
  152. package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +265 -243
  153. package/src/hosted/__tests__/hosted-node-service.test.ts +609 -0
  154. package/src/hosted/__tests__/hosted-node-supervisor.test.ts +474 -0
  155. package/src/hosted/__tests__/test-invitation.ts +33 -0
  156. package/src/hosted/claim-details.ts +96 -0
  157. package/src/hosted/hosted-node-service.ts +795 -0
  158. package/src/hosted/hosted-node-store.ts +144 -0
  159. package/src/hosted/hosted-node-supervisor.ts +439 -0
  160. package/src/hosted/hosted-node-watcher.ts +250 -0
  161. package/src/hosted/index.ts +53 -0
  162. package/src/hosted/node-status.ts +71 -0
  163. package/src/hosted/types.ts +153 -0
  164. package/src/index.ts +72 -91
  165. package/src/installer/__tests__/config.test.ts +15 -40
  166. package/src/installer/__tests__/installer.smoke.test.ts +5 -86
  167. package/src/installer/__tests__/wizard.test.ts +56 -64
  168. package/src/installer/config.ts +135 -175
  169. package/src/installer/index.ts +15 -83
  170. package/src/installer/paths.ts +104 -109
  171. package/src/installer/wizard.ts +87 -103
  172. package/src/nat/__tests__/address-resolver.test.ts +107 -82
  173. package/src/nat/__tests__/nat-service.test.ts +433 -408
  174. package/src/nat/__tests__/nat-store.test.ts +127 -93
  175. package/src/nat/__tests__/port-mapper.test.ts +30 -191
  176. package/src/nat/__tests__/reachability.test.ts +96 -13
  177. package/src/nat/address-resolver.ts +94 -52
  178. package/src/nat/address-watch.ts +92 -0
  179. package/src/nat/ddns/duckdns.ts +5 -1
  180. package/src/nat/ddns/index.ts +6 -0
  181. package/src/nat/index.ts +87 -71
  182. package/src/nat/nat-service.ts +889 -501
  183. package/src/nat/nat-store.ts +237 -154
  184. package/src/nat/port-mapper.ts +241 -329
  185. package/src/nat/reachability.ts +134 -25
  186. package/src/nat/types.ts +209 -159
  187. package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
  188. package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
  189. package/src/orchestrator/host-process-orchestrator.ts +81 -243
  190. package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
  191. package/src/orchestrator/index.ts +9 -10
  192. package/src/orchestrator/node-identity.ts +41 -41
  193. package/src/orchestrator/port-allocator.ts +71 -73
  194. package/src/orchestrator/state-store.ts +76 -90
  195. package/src/orchestrator/types.ts +34 -23
  196. package/src/server/__tests__/bus.test.ts +38 -38
  197. package/src/server/__tests__/error-handler.test.ts +81 -118
  198. package/src/server/__tests__/fakes.ts +40 -44
  199. package/src/server/__tests__/hosted-nodes-route.test.ts +160 -0
  200. package/src/server/__tests__/nodes-route.test.ts +114 -283
  201. package/src/server/__tests__/publishers.test.ts +193 -180
  202. package/src/server/__tests__/server.smoke.test.ts +134 -223
  203. package/src/server/__tests__/settings-route.test.ts +179 -195
  204. package/src/server/__tests__/sse-route.test.ts +119 -119
  205. package/src/server/__tests__/status-route.test.ts +82 -121
  206. package/src/server/error-handler.ts +112 -178
  207. package/src/server/events/types.ts +20 -24
  208. package/src/server/index.ts +223 -267
  209. package/src/server/routes/hosted-nodes.ts +83 -0
  210. package/src/server/routes/nat.ts +75 -63
  211. package/src/server/routes/nodes.ts +96 -188
  212. package/src/server/routes/settings.ts +115 -123
  213. package/src/server/routes/status.ts +102 -134
  214. package/src/server/server.ts +8 -8
  215. package/src/server/settings-store.ts +2 -2
  216. package/src/server/static.ts +1 -2
  217. package/src/update/index.ts +2 -2
  218. package/src/update/store.ts +1 -1
  219. package/src/update/types.ts +1 -1
  220. package/dist/auth/duration.d.ts +0 -1
  221. package/dist/auth/duration.js +0 -32
  222. package/dist/auth/duration.js.map +0 -1
  223. package/dist/auth/index.d.ts +0 -11
  224. package/dist/auth/index.js +0 -9
  225. package/dist/auth/index.js.map +0 -1
  226. package/dist/auth/self-label.d.ts +0 -36
  227. package/dist/auth/self-label.js +0 -57
  228. package/dist/auth/self-label.js.map +0 -1
  229. package/dist/auth/trust-circle-store.d.ts +0 -26
  230. package/dist/auth/trust-circle-store.js +0 -129
  231. package/dist/auth/trust-circle-store.js.map +0 -1
  232. package/dist/auth/trust-circle.d.ts +0 -156
  233. package/dist/auth/trust-circle.js +0 -307
  234. package/dist/auth/trust-circle.js.map +0 -1
  235. package/dist/auth/types.d.ts +0 -84
  236. package/dist/auth/types.js +0 -24
  237. package/dist/auth/types.js.map +0 -1
  238. package/dist/donation/donation-service.d.ts +0 -389
  239. package/dist/donation/donation-service.js +0 -828
  240. package/dist/donation/donation-service.js.map +0 -1
  241. package/dist/donation/donation-store.d.ts +0 -51
  242. package/dist/donation/donation-store.js +0 -152
  243. package/dist/donation/donation-store.js.map +0 -1
  244. package/dist/donation/donation-supervisor.js +0 -364
  245. package/dist/donation/donation-supervisor.js.map +0 -1
  246. package/dist/donation/grant-service.d.ts +0 -80
  247. package/dist/donation/grant-service.js +0 -210
  248. package/dist/donation/grant-service.js.map +0 -1
  249. package/dist/donation/grant-store.d.ts +0 -38
  250. package/dist/donation/grant-store.js +0 -135
  251. package/dist/donation/grant-store.js.map +0 -1
  252. package/dist/donation/index.d.ts +0 -20
  253. package/dist/donation/index.js +0 -17
  254. package/dist/donation/index.js.map +0 -1
  255. package/dist/donation/types.d.ts +0 -224
  256. package/dist/donation/types.js +0 -40
  257. package/dist/donation/types.js.map +0 -1
  258. package/dist/installer/identity.js.map +0 -1
  259. package/dist/owner/index.d.ts +0 -11
  260. package/dist/owner/index.js +0 -11
  261. package/dist/owner/index.js.map +0 -1
  262. package/dist/owner/owner-node-client.d.ts +0 -110
  263. package/dist/owner/owner-node-client.js +0 -184
  264. package/dist/owner/owner-node-client.js.map +0 -1
  265. package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
  266. package/dist/server/routes/bootstrap-node-validation.js +0 -197
  267. package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
  268. package/dist/server/routes/grants-admin.d.ts +0 -29
  269. package/dist/server/routes/grants-admin.js +0 -58
  270. package/dist/server/routes/grants-admin.js.map +0 -1
  271. package/dist/server/routes/grants.d.ts +0 -28
  272. package/dist/server/routes/grants.js +0 -165
  273. package/dist/server/routes/grants.js.map +0 -1
  274. package/dist/server/routes/provision-request-validation.d.ts +0 -45
  275. package/dist/server/routes/provision-request-validation.js +0 -97
  276. package/dist/server/routes/provision-request-validation.js.map +0 -1
  277. package/dist/server/routes/strands.d.ts +0 -20
  278. package/dist/server/routes/strands.js +0 -38
  279. package/dist/server/routes/strands.js.map +0 -1
  280. package/dist/server/routes/trust-circle.d.ts +0 -17
  281. package/dist/server/routes/trust-circle.js +0 -35
  282. package/dist/server/routes/trust-circle.js.map +0 -1
  283. package/dist/strands/index.d.ts +0 -7
  284. package/dist/strands/index.js +0 -6
  285. package/dist/strands/index.js.map +0 -1
  286. package/dist/strands/strand-service.d.ts +0 -50
  287. package/dist/strands/strand-service.js +0 -103
  288. package/dist/strands/strand-service.js.map +0 -1
  289. package/dist/strands/types.d.ts +0 -93
  290. package/dist/strands/types.js +0 -23
  291. package/dist/strands/types.js.map +0 -1
  292. package/dist/ui/assets/index-C6nELNfC.js +0 -23
  293. package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
  294. package/dist/ui/assets/index-TtIYSeZO.css +0 -1
  295. package/src/__tests__/cli-invite.smoke.test.ts +0 -192
  296. package/src/__tests__/host-owner.smoke.test.ts +0 -123
  297. package/src/__tests__/orchestrator-owner.test.ts +0 -234
  298. package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
  299. package/src/auth/__tests__/duration.test.ts +0 -28
  300. package/src/auth/__tests__/self-label.test.ts +0 -85
  301. package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
  302. package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
  303. package/src/auth/__tests__/trust-circle.test.ts +0 -445
  304. package/src/auth/duration.ts +0 -33
  305. package/src/auth/index.ts +0 -21
  306. package/src/auth/self-label.ts +0 -74
  307. package/src/auth/trust-circle-store.ts +0 -155
  308. package/src/auth/trust-circle.ts +0 -395
  309. package/src/auth/types.ts +0 -99
  310. package/src/donation/__tests__/donation-service.test.ts +0 -1193
  311. package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
  312. package/src/donation/__tests__/grant-service.test.ts +0 -213
  313. package/src/donation/__tests__/grant-store.test.ts +0 -113
  314. package/src/donation/donation-service.ts +0 -1033
  315. package/src/donation/donation-store.ts +0 -176
  316. package/src/donation/donation-supervisor.ts +0 -411
  317. package/src/donation/grant-service.ts +0 -252
  318. package/src/donation/grant-store.ts +0 -152
  319. package/src/donation/index.ts +0 -65
  320. package/src/donation/types.ts +0 -260
  321. package/src/owner/__tests__/owner-node-client.test.ts +0 -194
  322. package/src/owner/index.ts +0 -12
  323. package/src/owner/owner-node-client.ts +0 -259
  324. package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
  325. package/src/server/__tests__/grants-admin-route.test.ts +0 -171
  326. package/src/server/__tests__/grants-route.test.ts +0 -394
  327. package/src/server/__tests__/strands-route.test.ts +0 -234
  328. package/src/server/routes/bootstrap-node-validation.ts +0 -206
  329. package/src/server/routes/grants-admin.ts +0 -74
  330. package/src/server/routes/grants.ts +0 -193
  331. package/src/server/routes/provision-request-validation.ts +0 -105
  332. package/src/server/routes/strands.ts +0 -54
  333. package/src/server/routes/trust-circle.ts +0 -49
  334. package/src/strands/__tests__/strand-service.test.ts +0 -165
  335. package/src/strands/index.ts +0 -14
  336. package/src/strands/strand-service.ts +0 -135
  337. package/src/strands/types.ts +0 -102
@@ -47,6 +47,18 @@ function scrubbedParentEnv() {
47
47
  }
48
48
  return env;
49
49
  }
50
+ /**
51
+ * The env carrying how the child gets into its cadre, and nowhere else (see
52
+ * `HostedSpawnRequest`): `CADRE_CLAIM_SECRET` makes `cadre-cli start` wait to be
53
+ * claimed, `CADRE_INVITATION` makes it redeem the invitation once it is up.
54
+ */
55
+ function joinEnv(request) {
56
+ if (request.claimSecret)
57
+ return { CADRE_CLAIM_SECRET: request.claimSecret };
58
+ if (request.invitation)
59
+ return { CADRE_INVITATION: request.invitation };
60
+ return undefined;
61
+ }
50
62
  /**
51
63
  * The libp2p listen addresses a managed child binds — its `CADRE_LISTEN_ADDRS`, one
52
64
  * entry per element: TCP on the `p2p` port and WebSocket on the `ws` port, both on
@@ -65,22 +77,17 @@ export function childListenAddrs(ports) {
65
77
  /**
66
78
  * Every TCP port a managed child binds, on the address it binds it to: health and
67
79
  * metrics (`cadre-cli`'s `HealthServer`) and the libp2p TCP and WebSocket listeners
68
- * ({@link childListenAddrs}) on every interface, plus the loopback admin channel —
69
- * for the owner node only, the one child started with `--admin-port`. A key the
70
- * set lacks (a handle persisted by an older build) is skipped.
80
+ * ({@link childListenAddrs}), all on every interface. A key the set lacks (a handle
81
+ * persisted by an older build) is skipped.
71
82
  */
72
- function childPortBindings(ports, owner) {
83
+ function childPortBindings(ports) {
73
84
  const bindings = [];
74
85
  for (const port of [ports.health, ports.metrics, ports.p2p, ports.ws]) {
75
86
  if (port !== undefined)
76
87
  bindings.push({ port, host: '0.0.0.0' });
77
88
  }
78
- if (owner && ports.admin !== undefined)
79
- bindings.push({ port: ports.admin, host: '127.0.0.1' });
80
89
  return bindings;
81
90
  }
82
- /** Fixed friendly id for the admin's owner node. */
83
- export const OWNER_CONTAINER_ID = 'owner';
84
91
  /**
85
92
  * Runs cadre nodes as plain Node child processes on the host.
86
93
  *
@@ -127,6 +134,7 @@ export class HostProcessOrchestrator {
127
134
  this.defaultMemoryLimit = cfg.defaultResources?.memoryLimit;
128
135
  this.cliEntrypoint = cfg.spawn?.entrypoint ?? resolveCadreCliBin();
129
136
  this.pushResolver = cfg.pushResolver;
137
+ this.announceAddrs = cfg.announceAddrs;
130
138
  log('HostProcessOrchestrator rootDir=%s cli=%s', this.rootDir, this.cliEntrypoint);
131
139
  }
132
140
  /**
@@ -136,7 +144,6 @@ export class HostProcessOrchestrator {
136
144
  */
137
145
  async init() {
138
146
  const state = this.stateStore.load();
139
- this.ownerConfig = state.ownerConfig;
140
147
  for (const persisted of state.handles) {
141
148
  const handle = {
142
149
  containerId: persisted.containerId,
@@ -148,7 +155,7 @@ export class HostProcessOrchestrator {
148
155
  spawnedAt: persisted.spawnedAt,
149
156
  partyId: persisted.partyId,
150
157
  profile: persisted.profile,
151
- ...(persisted.owner ? { owner: true } : {}),
158
+ announcedAddrs: persisted.announcedAddrs ?? [],
152
159
  alive: false,
153
160
  };
154
161
  handle.alive = isHandleLive(handle);
@@ -194,25 +201,32 @@ export class HostProcessOrchestrator {
194
201
  }
195
202
  /**
196
203
  * Register a state-change listener. Invoked when a handle's `alive`
197
- * transitions (child exit, manual stop). Returns an unsubscribe fn.
204
+ * transitions (create, child exit, manual stop) and once more, as `stopped`,
205
+ * when a handle is removed. Returns an unsubscribe fn.
198
206
  */
199
207
  onStateChange(listener) {
200
208
  this.stateListeners.add(listener);
201
209
  return () => { this.stateListeners.delete(listener); };
202
210
  }
203
211
  /**
204
- * Spawn a generic managed node — the donated-node path (the requester's cadre,
205
- * pinned to the *requester's* owner key). The node is given its own protobuf
206
- * identity key inside its workdir (`ensureNodeIdentity`, reused across
212
+ * Spawn a managed node — a hosted node for some cadre, started waiting to be
213
+ * claimed when the request carries a `claimSecret`, or redeeming the cadre
214
+ * invitation it carries as `invitation`. The node is given its own
215
+ * protobuf identity key inside its workdir (`ensureNodeIdentity`, reused across
207
216
  * re-spawns of the same containerId), which is what makes its peer id stable
208
217
  * across restarts AND what makes its node-local stores durable: `cadre-cli
209
218
  * start` opens the file-backed bootstrap-peer, trusted-owner and strand
210
219
  * network-state stores in the node's state directory, which defaults to the
211
220
  * directory holding the `cadre.json` written here — the same workdir.
212
- * Terminating the loan (`removeContainer`) deletes the workdir, so the key and
221
+ * Removing the node (`removeContainer`) deletes the workdir, so the key and
213
222
  * every store go with it.
214
223
  */
215
224
  async createContainer(request) {
225
+ // Before anything is allocated: `cadre-cli start` refuses the pair anyway, and a child
226
+ // that exits at once would only surface as a crash loop.
227
+ if (request.claimSecret && request.invitation) {
228
+ throw new Error(`container ${request.containerId}: a spawn carries a claim secret or an invitation, not both`);
229
+ }
216
230
  const workdir = this.workdirFor(request.containerId);
217
231
  // Sampled BEFORE `ensureNodeIdentity`, which brings the directory into
218
232
  // existence as a side effect of writing the key. Only a spawn that found
@@ -233,25 +247,14 @@ export class HostProcessOrchestrator {
233
247
  // node's worth of ports from a bounded range on every failed provision attempt.
234
248
  const identity = await ensureNodeIdentity(workdir);
235
249
  log('container %s identity peerId=%s', request.containerId, identity.peerId);
236
- // A node started with pinned owner keys belongs to a FOREIGN cadre (the
237
- // node-donation flow: it trusts the requester's owner key, not the host's).
238
- // The host's FCM/APNs credentials are minted for the host owner's own app
239
- // and are meaningless to a foreign cadre — so donated nodes get NO push
240
- // block. (Per-grantee push creds would be a future ticket; none in v1.)
241
- const foreignParty = (request.pinnedOwnerKeys?.length ?? 0) > 0;
242
250
  // Only storage-profile nodes participate in strands and thus fan out push
243
251
  // wakes — a transaction-only node need not carry credentials. Resolved
244
252
  // BEFORE the drop below, which keeps the drop → launch window synchronous
245
253
  // (see restoreDroppedHandles).
246
- const push = request.profile === 'storage' && !foreignParty ? await this.resolvePush() : undefined;
247
- // Pinned owner key(s) reach the child via CADRE_OWNER_KEYS (comma-separated);
248
- // `cadre-cli start` unions it into its cold-start pinnedKeyTrustPolicy so the
249
- // node will accept the foreign-authority-signed seed presented via POST /seed.
250
- const extraEnv = request.pinnedOwnerKeys?.length
251
- ? { CADRE_OWNER_KEYS: request.pinnedOwnerKeys.join(',') }
252
- : undefined;
254
+ const push = request.profile === 'storage' ? await this.resolvePush() : undefined;
255
+ const extraEnv = joinEnv(request);
253
256
  // The last `await` on this path: checked as close to the drop as it can be.
254
- await this.refuseRespawnOverLiveChild(request.containerId, {}, false);
257
+ await this.refuseRespawnOverLiveChild(request.containerId);
255
258
  // Nothing from here to the launch is `await`ed — which is what keeps
256
259
  // `restoreDroppedHandles`'s documented precondition true even though the
257
260
  // `try` now opens further up. Do not add an `await` past this line.
@@ -266,7 +269,6 @@ export class HostProcessOrchestrator {
266
269
  partyId: request.partyId,
267
270
  profile: request.profile,
268
271
  ports,
269
- owner: false,
270
272
  buildConfig: () => this.buildChildConfig(request, workdir, push),
271
273
  extraArgs: ['--identity-file', identity.path],
272
274
  ...(extraEnv ? { extraEnv } : {}),
@@ -294,7 +296,7 @@ export class HostProcessOrchestrator {
294
296
  }
295
297
  /**
296
298
  * Remove the working directory a spawn created for `containerId` when no
297
- * handle owns it — the cleanup of last resort for a donation record that
299
+ * handle owns it — the cleanup of last resort for a hosted-node record that
298
300
  * never got a `dockerId` (the host died between `ensureNodeIdentity` and the
299
301
  * handle write, so `createContainer`'s own unwind never ran). Returns whether
300
302
  * anything was removed.
@@ -303,17 +305,12 @@ export class HostProcessOrchestrator {
303
305
  * to await.
304
306
  */
305
307
  reclaimWorkdir(containerId) {
306
- // The admin's own node is never reclaimed by this route: its workdir is a
307
- // single fixed path reused by every restart and it holds the host's own
308
- // control-DB storage.
309
- if (containerId === OWNER_CONTAINER_ID)
310
- return false;
311
308
  // A live handle owns that directory, and `removeContainer` — which stops
312
309
  // the child first — is the only thing allowed to delete it.
313
310
  if (this.resolveDockerId(containerId))
314
311
  return false;
315
312
  const workdir = this.workdirFor(containerId);
316
- // Defence, not a live case: donation ids are `grn_<base64url>` and so can
313
+ // Defence, not a live case: hosted-node ids are `hn_<base64url>` and so can
317
314
  // hold neither a path separator nor a dot. A caller that ever passes
318
315
  // something else must not be able to walk out of `rootDir`.
319
316
  if (!workdir.startsWith(this.rootDir + sep)) {
@@ -376,120 +373,27 @@ export class HostProcessOrchestrator {
376
373
  }
377
374
  }
378
375
  /**
379
- * Ensure the admin's owner node is running, spawning it if absent or
380
- * dead. Idempotent: a second call with the node already alive returns the
381
- * existing handle without launching a second child. The spawn parameters
382
- * are persisted so a later `/api/nodes/:id/{start,restart}` (or an
383
- * orchestrator restart) can re-spawn it without re-supplying them.
384
- *
385
- * The owner node is spawned as `cadre-cli start --owner
386
- * --admin-port <p> --identity-file <identityPath>` so it carries the
387
- * host's libp2p identity, founds/joins the control network, and exposes the
388
- * loopback admin channel the manager delegates to.
376
+ * The public addresses the node about to spawn announces. A hook that throws costs
377
+ * the node its public addresses, never its start: it still binds and is reachable
378
+ * on its LAN addresses, and `NatService` restarts it once the addresses it should
379
+ * announce differ from these.
389
380
  */
390
- async ensureOwnerNode(cfg) {
391
- const config = cfg ?? this.ownerConfig;
392
- if (!config) {
393
- throw new Error('ensureOwnerNode: no owner config supplied and none persisted');
394
- }
395
- this.ownerConfig = config;
396
- const existing = this.findOwnerHandle();
397
- if (existing && isHandleLive(existing)) {
398
- this.persist();
399
- return toNodeInfo(existing);
400
- }
401
- const profile = config.profile ?? 'storage';
402
- // Re-resolve push credentials from the secret store on every (re-)spawn so a
403
- // restart picks up rotated keys and nothing raw is replayed from state.json.
404
- // Resolved BEFORE the drop below so the drop → launch window stays
405
- // synchronous (see restoreDroppedHandles).
406
- const push = await this.resolvePush();
407
- // The last `await` before the drop — same guard as in createContainer.
408
- await this.refuseRespawnOverLiveChild(OWNER_CONTAINER_ID, { p2p: config.libp2pPort }, true);
409
- // NOTE: the short-circuit above matches via `findOwnerHandle` (owner flag OR
410
- // containerId), this drop only by containerId. Equivalent today — every
411
- // owner handle is launched with OWNER_CONTAINER_ID. If an owner spawn ever
412
- // takes a different containerId, the two would disagree and the handle the
413
- // short-circuit found would survive here, leaking it and its ports.
414
- const dropped = this.dropStaleHandle(OWNER_CONTAINER_ID);
415
- let ports;
381
+ announceAddrsFor(containerId, ports) {
382
+ if (!this.announceAddrs)
383
+ return [];
416
384
  try {
417
- // The owner node must listen on the configured libp2p port so the NAT
418
- // mapping (external → internal) lands on it — hence the p2p override, which
419
- // wins over the dropped handle's. Every other port comes back as it was, for
420
- // the same reason as in createContainer.
421
- ports = allocateNodePorts(this.portAllocator, { ...reusedNodePorts(dropped), p2p: config.libp2pPort });
422
- const result = this.launchChild({
423
- containerId: OWNER_CONTAINER_ID,
424
- partyId: config.partyId,
425
- profile,
426
- ports,
427
- owner: true,
428
- buildConfig: (workdir) => this.buildOwnerChildConfig(config, profile, workdir, push),
429
- extraArgs: ['--owner', '--admin-port', String(ports.admin), '--identity-file', config.identityPath],
430
- });
431
- const handle = this.handles.get(result.dockerId);
432
- return toNodeInfo(handle);
385
+ return this.announceAddrs(containerId, ports);
433
386
  }
434
387
  catch (err) {
435
- // Release before restoring — see the same note in createContainer.
436
- if (ports)
437
- this.releasePorts(ports);
438
- this.restoreDroppedHandles(dropped);
439
- // Deliberately NOT `discardWorkdir` — the asymmetry with createContainer
440
- // is the point. `<rootDir>/owner` is a single fixed path reused by every
441
- // restart and holds the host's own control-DB storage, so it neither
442
- // grows without bound nor is safe to delete on a failed spawn.
443
- throw err;
444
- }
445
- }
446
- /** Endpoint + bearer for the owner node's admin channel, if spawned. */
447
- getOwnerAdminEndpoint() {
448
- const handle = this.findOwnerHandle();
449
- if (!handle)
450
- return undefined;
451
- return {
452
- baseUrl: `http://127.0.0.1:${handle.ports.admin}`,
453
- token: handle.startupToken,
454
- };
455
- }
456
- /** Whether spawn parameters for the owner node are known (persisted). */
457
- hasOwnerConfig() {
458
- return this.ownerConfig !== undefined;
459
- }
460
- /** Whether the given friendly/docker id refers to the owner node. */
461
- isOwnerNode(idOrDockerId) {
462
- if (idOrDockerId === OWNER_CONTAINER_ID)
463
- return true;
464
- const direct = this.handles.get(idOrDockerId);
465
- if (direct?.owner)
466
- return true;
467
- for (const h of this.handles.values()) {
468
- if (h.owner && h.containerId === idOrDockerId)
469
- return true;
470
- }
471
- return false;
472
- }
473
- /** Stop the owner node (if running). Used on graceful shutdown. */
474
- async stopOwnerNode() {
475
- const handle = this.findOwnerHandle();
476
- if (handle && isPidAlive(handle.pid)) {
477
- await this.stopContainer(handle.dockerId);
478
- }
479
- }
480
- /** Stop the owner node (if running), then re-spawn it from saved config. */
481
- async restartOwnerNode() {
482
- const handle = this.findOwnerHandle();
483
- if (handle && isPidAlive(handle.pid)) {
484
- await this.stopContainer(handle.dockerId);
388
+ log('announce hook failed for %s; it starts with no public addresses: %s', containerId, err.message);
389
+ return [];
485
390
  }
486
- return this.ensureOwnerNode();
487
391
  }
488
392
  /**
489
- * Core spawn mechanics shared by `createContainer` and `ensureOwnerNode`.
393
+ * Core spawn mechanics behind `createContainer`.
490
394
  *
491
395
  * The caller has already allocated `ports` and **owns their lifetime**: this
492
- * method never releases them, so every caller must release on any throw out
396
+ * method never releases them, so the caller must release on any throw out
493
397
  * of here (it has to unwind its handle drop on the same path anyway — see
494
398
  * `restoreDroppedHandles`).
495
399
  *
@@ -525,6 +429,7 @@ export class HostProcessOrchestrator {
525
429
  const memoryLimit = opts.memoryLimit ?? this.defaultMemoryLimit;
526
430
  const heapBytes = parseMemoryLimit(memoryLimit);
527
431
  const heapMB = heapBytes !== undefined ? Math.max(64, Math.floor(heapBytes / (1024 * 1024))) : undefined;
432
+ const announcedAddrs = this.announceAddrsFor(containerId, ports);
528
433
  const env = {
529
434
  // Scrubbed, not raw process.env: every CADRE_* config-override key is
530
435
  // stripped so a var set on the manager can't silently reconfigure every
@@ -537,19 +442,16 @@ export class HostProcessOrchestrator {
537
442
  CADRE_HEALTH_PORT: String(ports.health),
538
443
  CADRE_METRICS_PORT: String(ports.metrics),
539
444
  CADRE_LISTEN_ADDRS: childListenAddrs(ports).join(','),
540
- // NOTE: the scrub above drops every CADRE_* var and `extraEnv` is built only from
541
- // pinnedOwnerKeys, so a managed child advertises only the ports assigned to it here
542
- // — `CADRE_ANNOUNCE_ADDRS`/`CADRE_APPEND_ANNOUNCE_ADDRS` cannot reach it. Fine while
543
- // children are reached at those ports; if a host is ever fronted by a proxy or DNS
544
- // name, plumb an announce var through from host config. The TCP
545
- // and WebSocket ports are the CHILD'S CONTROL NODE's alone: each strand node the
546
- // child runs binds the same two entries with OS-assigned ports instead, since one
547
- // port cannot be held twice (`cadre-core/src/strand-network-config.ts`). A NAT
548
- // forward (the owner node's, via `NatService`) covers the TCP port only, so it
549
- // reaches the control node over TCP alone; a phone outside the LAN, which needs the
550
- // WebSocket port, and every strand node are reached through observed addresses only,
551
- // since no `CADRE_RELAY_ADDRS` is set here and a child therefore holds no relay
552
- // reservation.
445
+ // The append form, not `CADRE_ANNOUNCE_ADDRS`: that one replaces the advertised
446
+ // set and would drop the LAN addresses a phone at home dials. Every entry must be
447
+ // a valid multiaddr: cadre-core refuses to start on one that is not.
448
+ // The TCP and WebSocket ports are the CHILD'S CONTROL NODE's alone: each strand
449
+ // node the child runs binds the same two entries with OS-assigned ports instead,
450
+ // since one port cannot be held twice, and inherits neither announce field
451
+ // (`cadre-core/src/strand-network-config.ts`). Those ports are not mapped, and
452
+ // strand nodes are reached through observed addresses only, since no
453
+ // `CADRE_RELAY_ADDRS` is set here and a child therefore holds no relay reservation.
454
+ ...(announcedAddrs.length > 0 ? { CADRE_APPEND_ANNOUNCE_ADDRS: announcedAddrs.join(',') } : {}),
553
455
  CADRE_SEED_TOKEN: seedToken,
554
456
  // Pin each child's node-local state (trusted-owner anchor, retained
555
457
  // cold-start dial targets) to its OWN workdir. This is the same value the
@@ -612,7 +514,7 @@ export class HostProcessOrchestrator {
612
514
  spawnedAt: new Date().toISOString(),
613
515
  partyId: opts.partyId,
614
516
  profile: opts.profile,
615
- ...(opts.owner ? { owner: true } : {}),
517
+ announcedAddrs,
616
518
  child,
617
519
  alive: true,
618
520
  };
@@ -652,7 +554,7 @@ export class HostProcessOrchestrator {
652
554
  }
653
555
  /**
654
556
  * Refuse to re-spawn `containerId` while a previous child of it may still be
655
- * running. Both spawn paths call this as their last `await` before the handle
557
+ * running. `createContainer` calls this as its last `await` before the handle
656
558
  * drop, so a refusal happens while nothing has been released, no token rotated
657
559
  * and no child launched — the caller sees an ordinary failed spawn.
658
560
  *
@@ -670,9 +572,6 @@ export class HostProcessOrchestrator {
670
572
  * is already bound, refuse. The pid is never killed on that evidence — it may
671
573
  * not be ours.
672
574
  *
673
- * `overrides` are the ports the caller will override on top of the reused ones
674
- * (the owner node's configured p2p port); `owner` adds the admin channel.
675
- *
676
575
  * NOTE: the probe cannot see a re-attached child still loading its modules —
677
576
  * `cadre-cli start` writes its token and only then binds, so a child in that
678
577
  * window shows neither, and a re-spawn launched then races it for the ports.
@@ -687,14 +586,14 @@ export class HostProcessOrchestrator {
687
586
  * child is ever seen dying on its own port right after a host restart, probe by
688
587
  * connecting instead of binding.
689
588
  */
690
- async refuseRespawnOverLiveChild(containerId, overrides, owner) {
589
+ async refuseRespawnOverLiveChild(containerId) {
691
590
  const previous = this.handlesFor(containerId);
692
591
  if (previous.some(isHandleLive)) {
693
592
  throw new Error(`container ${containerId} is still running`);
694
593
  }
695
594
  if (!previous.some((h) => isPidAlive(h.pid)))
696
595
  return;
697
- const bindings = childPortBindings({ ...reusedNodePorts(previous), ...overrides }, owner);
596
+ const bindings = childPortBindings(reusedNodePorts(previous));
698
597
  try {
699
598
  await Promise.all(bindings.map(assertPortFree));
700
599
  }
@@ -709,9 +608,9 @@ export class HostProcessOrchestrator {
709
608
  /**
710
609
  * Drop any handle left over from a previous spawn of the same `containerId`
711
610
  * and release its ports. Handles are keyed by the per-spawn `dockerId`, so
712
- * without this a re-spawn (donated-node respawn) would strand the prior
611
+ * without this a re-spawn (a hosted node's respawn) would strand the prior
713
612
  * handle in the map forever, leaking its ports from a bounded range each
714
- * time. Mirrors the same cleanup in `ensureOwnerNode`.
613
+ * time.
715
614
  *
716
615
  * DO NOT delete the workdir: the identity key and node-local stores
717
616
  * (trusted-owner anchor, retained cold-start dial targets) live there and are
@@ -721,16 +620,16 @@ export class HostProcessOrchestrator {
721
620
  * Releasing the ports hands them straight back to the allocator, and the
722
621
  * re-spawn then takes those same ports as overrides (`reusedNodePorts`), so a
723
622
  * re-spawn while the *previous* child is still listening would bind-clash.
724
- * Callers must therefore never drop a live child, and both do so only after
623
+ * The caller must therefore never drop a live child, and does so only after
725
624
  * {@link refuseRespawnOverLiveChild} has passed. A caller that needs to replace
726
625
  * a live child must stop it first.
727
626
  *
728
627
  * **The drop is reversible.** It happens before the launch can fail, so the
729
- * dropped handles are returned and every caller restores them via
628
+ * dropped handles are returned and the caller restores them via
730
629
  * {@link restoreDroppedHandles} when the launch throws — otherwise the caller
731
630
  * would be left holding a dockerId this orchestrator no longer knows, and the
732
631
  * node's workdir could never be reclaimed. On a *successful* spawn the drop
733
- * stands: `DonationService.abandonRespawn` depends on the old handle being
632
+ * stands: `HostedNodeService.abandonRespawn` depends on the old handle being
734
633
  * gone by then.
735
634
  */
736
635
  dropStaleHandle(containerId) {
@@ -749,10 +648,10 @@ export class HostProcessOrchestrator {
749
648
  *
750
649
  * **Precondition: the drop → launch window is synchronous.** Restoring is
751
650
  * sound only because nothing can have taken those ports or run cleanup
752
- * against the handle in between — `launchChild` contains no `await` and both
753
- * callers resolve push credentials *before* their drop. `launchChild`'s half
651
+ * against the handle in between — `launchChild` contains no `await` and the
652
+ * caller resolves push credentials *before* its drop. `launchChild`'s half
754
653
  * is enforced: its declared return type is not a Promise, so adding an
755
- * `await` there is a compile error. The callers' half is not — an `await`
654
+ * `await` there is a compile error. The caller's half is not — an `await`
756
655
  * slipped between a drop and its launch silently breaks this.
757
656
  *
758
657
  * No `persist()` here: `state.json` is rewritten only by `launchChild` and
@@ -778,14 +677,6 @@ export class HostProcessOrchestrator {
778
677
  workdirFor(containerId) {
779
678
  return join(this.rootDir, containerId);
780
679
  }
781
- /** Locate the owner node's handle, if one has been spawned. */
782
- findOwnerHandle() {
783
- for (const h of this.handles.values()) {
784
- if (h.owner || h.containerId === OWNER_CONTAINER_ID)
785
- return h;
786
- }
787
- return undefined;
788
- }
789
680
  /**
790
681
  * Inverse of {@link releasePorts} — hold a handle's whole port set against
791
682
  * the allocator. Used both to rehydrate on `init` and to undo a drop whose
@@ -861,6 +752,11 @@ export class HostProcessOrchestrator {
861
752
  this.discardWorkdir(handle.workdir, { maxRetries: 20, retryDelay: 200 });
862
753
  this.handles.delete(dockerId);
863
754
  this.persist();
755
+ // Removal is a state change too: `NatService` releases the node's port
756
+ // mappings on it at once rather than at its next timer pass. A stopped
757
+ // event for a node no longer in `listNodes()` is how listeners tell a
758
+ // removal from a stop.
759
+ this.emitStateChange(handle);
864
760
  }
865
761
  /**
866
762
  * Whether the child `dockerId` names is running — see {@link isHandleLive}. An
@@ -939,46 +835,10 @@ export class HostProcessOrchestrator {
939
835
  cfg.push = push;
940
836
  return cfg;
941
837
  }
942
- /**
943
- * Child config for the **host's own personal cadre** owner node — the opt-in
944
- * "founder" persona spawned only when `ownCadre.enabled` (see
945
- * docs/cadre-host.md § Two roles: donor and founder). It founds/joins the
946
- * control network for the host's own `partyId` (no bootstrap peers — it is the
947
- * founding node of the host's *own* cadre) and carries the host identity via
948
- * `--identity-file` (passed as a spawn arg).
949
- *
950
- * This is NOT the node cadre-host donates to a requester. Donated nodes are
951
- * generic (`createContainer` / `buildChildConfig`): they join the
952
- * **requester's** cadre via bootstrap peers and pin the *requester's* owner
953
- * key — they never run a host genesis.
954
- */
955
- buildOwnerChildConfig(cfg, profile, workdir, push) {
956
- const config = {
957
- controlNetwork: {
958
- partyId: cfg.partyId,
959
- bootstrapNodes: [],
960
- },
961
- profile,
962
- strandFilter: 'all',
963
- };
964
- if (profile === 'storage') {
965
- config.storage = { type: 'file', path: join(workdir, 'storage') };
966
- }
967
- // The host-own-cadre owner/storage node participates in strands, so it owns
968
- // the push-wake fan-out when credentials are configured. (Donated nodes
969
- // belong to foreign cadres and get NO host push block — see the donor path.)
970
- // Written into cadre.json (same host trust boundary as the workdir's
971
- // control-DB); keys are never logged — only the redacted presence line in
972
- // resolvePush.
973
- if (push)
974
- config.push = push;
975
- return config;
976
- }
977
838
  persist() {
978
839
  const state = {
979
840
  version: 1,
980
841
  handles: [...this.handles.values()].map((h) => this.toPersisted(h)),
981
- ...(this.ownerConfig ? { ownerConfig: this.ownerConfig } : {}),
982
842
  };
983
843
  this.stateStore.save(state);
984
844
  }
@@ -1006,7 +866,7 @@ export class HostProcessOrchestrator {
1006
866
  spawnedAt: h.spawnedAt,
1007
867
  partyId: h.partyId,
1008
868
  profile: h.profile,
1009
- ...(h.owner ? { owner: true } : {}),
869
+ announcedAddrs: h.announcedAddrs,
1010
870
  };
1011
871
  }
1012
872
  }
@@ -1020,7 +880,7 @@ function toNodeInfo(h) {
1020
880
  spawnedAt: h.spawnedAt,
1021
881
  workdir: h.workdir,
1022
882
  ports: { ...h.ports },
1023
- ...(h.owner ? { owner: true } : {}),
883
+ announcedAddrs: [...h.announcedAddrs],
1024
884
  };
1025
885
  }
1026
886
  /**
@@ -1040,8 +900,8 @@ function resolveCadreCliBin() {
1040
900
  }
1041
901
  /**
1042
902
  * Whether the process a handle names is still the child it was launched as — the
1043
- * one liveness rule behind `init`, `isRunning`, `ensureOwnerNode`'s short-circuit
1044
- * and the pre-spawn guard, so they cannot drift apart.
903
+ * one liveness rule behind `init`, `isRunning` and the pre-spawn guard, so they
904
+ * cannot drift apart.
1045
905
  *
1046
906
  * A handle this process spawned carries its `ChildProcess`, and that answer is
1047
907
  * exact from the moment of spawn: no exit seen, and the pid still answering. (The