@serfab/cadre-host 1.13.0 → 1.15.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 (352) hide show
  1. package/README.md +347 -423
  2. package/dist/bin/host.d.ts +6 -3
  3. package/dist/bin/host.js +332 -524
  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 +54 -0
  9. package/dist/bin/nat-output.js +138 -0
  10. package/dist/bin/nat-output.js.map +1 -0
  11. package/dist/bin/ui-access.d.ts +22 -0
  12. package/dist/bin/ui-access.js +44 -0
  13. package/dist/bin/ui-access.js.map +1 -0
  14. package/dist/hosted/claim-details.d.ts +66 -0
  15. package/dist/hosted/claim-details.js +60 -0
  16. package/dist/hosted/claim-details.js.map +1 -0
  17. package/dist/hosted/hosted-node-service.d.ts +325 -0
  18. package/dist/hosted/hosted-node-service.js +678 -0
  19. package/dist/hosted/hosted-node-service.js.map +1 -0
  20. package/dist/hosted/hosted-node-store.d.ts +40 -0
  21. package/dist/hosted/hosted-node-store.js +126 -0
  22. package/dist/hosted/hosted-node-store.js.map +1 -0
  23. package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
  24. package/dist/hosted/hosted-node-supervisor.js +396 -0
  25. package/dist/hosted/hosted-node-supervisor.js.map +1 -0
  26. package/dist/hosted/hosted-node-watcher.d.ts +95 -0
  27. package/dist/hosted/hosted-node-watcher.js +260 -0
  28. package/dist/hosted/hosted-node-watcher.js.map +1 -0
  29. package/dist/hosted/index.d.ts +16 -0
  30. package/dist/hosted/index.js +13 -0
  31. package/dist/hosted/index.js.map +1 -0
  32. package/dist/hosted/node-status.d.ts +51 -0
  33. package/dist/hosted/node-status.js +77 -0
  34. package/dist/hosted/node-status.js.map +1 -0
  35. package/dist/hosted/types.d.ts +141 -0
  36. package/dist/hosted/types.js +22 -0
  37. package/dist/hosted/types.js.map +1 -0
  38. package/dist/index.d.ts +12 -24
  39. package/dist/index.js +9 -19
  40. package/dist/index.js.map +1 -1
  41. package/dist/installer/config.d.ts +27 -34
  42. package/dist/installer/config.js +46 -15
  43. package/dist/installer/config.js.map +1 -1
  44. package/dist/installer/index.d.ts +9 -19
  45. package/dist/installer/index.js +21 -85
  46. package/dist/installer/index.js.map +1 -1
  47. package/dist/installer/paths.d.ts +0 -2
  48. package/dist/installer/paths.js +0 -4
  49. package/dist/installer/paths.js.map +1 -1
  50. package/dist/installer/wizard.d.ts +12 -9
  51. package/dist/installer/wizard.js +15 -14
  52. package/dist/installer/wizard.js.map +1 -1
  53. package/dist/nat/address-resolver.d.ts +18 -17
  54. package/dist/nat/address-resolver.js +79 -25
  55. package/dist/nat/address-resolver.js.map +1 -1
  56. package/dist/nat/address-watch.d.ts +37 -0
  57. package/dist/nat/address-watch.js +84 -0
  58. package/dist/nat/address-watch.js.map +1 -0
  59. package/dist/nat/ddns/duckdns.d.ts +2 -1
  60. package/dist/nat/ddns/duckdns.js +5 -1
  61. package/dist/nat/ddns/duckdns.js.map +1 -1
  62. package/dist/nat/ddns/index.d.ts +6 -0
  63. package/dist/nat/ddns/index.js.map +1 -1
  64. package/dist/nat/index.d.ts +13 -9
  65. package/dist/nat/index.js +7 -5
  66. package/dist/nat/index.js.map +1 -1
  67. package/dist/nat/nat-service.d.ts +220 -77
  68. package/dist/nat/nat-service.js +653 -239
  69. package/dist/nat/nat-service.js.map +1 -1
  70. package/dist/nat/nat-store.d.ts +12 -5
  71. package/dist/nat/nat-store.js +124 -29
  72. package/dist/nat/nat-store.js.map +1 -1
  73. package/dist/nat/network-mode.d.ts +19 -0
  74. package/dist/nat/network-mode.js +32 -0
  75. package/dist/nat/network-mode.js.map +1 -0
  76. package/dist/nat/port-mapper.d.ts +83 -79
  77. package/dist/nat/port-mapper.js +148 -189
  78. package/dist/nat/port-mapper.js.map +1 -1
  79. package/dist/nat/reachability.d.ts +32 -15
  80. package/dist/nat/reachability.js +110 -22
  81. package/dist/nat/reachability.js.map +1 -1
  82. package/dist/nat/types.d.ts +98 -27
  83. package/dist/nat/types.js +8 -5
  84. package/dist/nat/types.js.map +1 -1
  85. package/dist/orchestrator/host-process-orchestrator.d.ts +32 -74
  86. package/dist/orchestrator/host-process-orchestrator.js +84 -233
  87. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  88. package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
  89. package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
  90. package/dist/orchestrator/identity-file.js.map +1 -0
  91. package/dist/orchestrator/index.d.ts +2 -3
  92. package/dist/orchestrator/index.js +1 -1
  93. package/dist/orchestrator/index.js.map +1 -1
  94. package/dist/orchestrator/node-identity.d.ts +6 -6
  95. package/dist/orchestrator/node-identity.js +7 -7
  96. package/dist/orchestrator/node-identity.js.map +1 -1
  97. package/dist/orchestrator/port-allocator.d.ts +5 -6
  98. package/dist/orchestrator/port-allocator.js +9 -11
  99. package/dist/orchestrator/port-allocator.js.map +1 -1
  100. package/dist/orchestrator/state-store.d.ts +3 -5
  101. package/dist/orchestrator/state-store.js +2 -10
  102. package/dist/orchestrator/state-store.js.map +1 -1
  103. package/dist/orchestrator/types.d.ts +33 -22
  104. package/dist/orchestrator/types.js.map +1 -1
  105. package/dist/package-version.d.ts +1 -0
  106. package/dist/package-version.js +4 -0
  107. package/dist/package-version.js.map +1 -0
  108. package/dist/server/error-handler.d.ts +5 -25
  109. package/dist/server/error-handler.js +11 -74
  110. package/dist/server/error-handler.js.map +1 -1
  111. package/dist/server/events/types.d.ts +7 -11
  112. package/dist/server/events/types.js +2 -1
  113. package/dist/server/events/types.js.map +1 -1
  114. package/dist/server/index.d.ts +11 -37
  115. package/dist/server/index.js +30 -48
  116. package/dist/server/index.js.map +1 -1
  117. package/dist/server/routes/hosted-nodes.d.ts +28 -0
  118. package/dist/server/routes/hosted-nodes.js +68 -0
  119. package/dist/server/routes/hosted-nodes.js.map +1 -0
  120. package/dist/server/routes/nat.d.ts +4 -1
  121. package/dist/server/routes/nat.js +13 -2
  122. package/dist/server/routes/nat.js.map +1 -1
  123. package/dist/server/routes/nodes.d.ts +6 -20
  124. package/dist/server/routes/nodes.js +7 -86
  125. package/dist/server/routes/nodes.js.map +1 -1
  126. package/dist/server/routes/settings.d.ts +3 -7
  127. package/dist/server/routes/settings.js +18 -8
  128. package/dist/server/routes/settings.js.map +1 -1
  129. package/dist/server/routes/status.d.ts +2 -25
  130. package/dist/server/routes/status.js +3 -43
  131. package/dist/server/routes/status.js.map +1 -1
  132. package/dist/server/server.js +8 -8
  133. package/dist/server/settings-store.d.ts +2 -2
  134. package/dist/server/settings-store.js +2 -2
  135. package/dist/server/static.js +1 -2
  136. package/dist/server/static.js.map +1 -1
  137. package/dist/ui/assets/index-BHd8CWZA.css +1 -0
  138. package/dist/ui/assets/index-Ccr2GjPT.js +23 -0
  139. package/dist/ui/assets/index-Ccr2GjPT.js.map +1 -0
  140. package/dist/ui/index.html +2 -2
  141. package/dist/update/index.d.ts +2 -2
  142. package/dist/update/index.js +2 -2
  143. package/dist/update/store.d.ts +1 -1
  144. package/dist/update/store.js +1 -1
  145. package/dist/update/types.d.ts +1 -1
  146. package/dist/update/types.js +1 -1
  147. package/package.json +5 -7
  148. package/src/__tests__/cli-nat.smoke.test.ts +219 -212
  149. package/src/__tests__/cli.smoke.test.ts +2 -2
  150. package/src/__tests__/global-setup.ts +2 -3
  151. package/src/__tests__/orchestrator-claim-secret.test.ts +111 -0
  152. package/src/__tests__/orchestrator-env-scrub.test.ts +160 -163
  153. package/src/__tests__/orchestrator-handle-contract.ts +134 -134
  154. package/src/__tests__/orchestrator-node-identity.test.ts +177 -203
  155. package/src/__tests__/orchestrator-ports.test.ts +124 -143
  156. package/src/__tests__/orchestrator-push.test.ts +138 -146
  157. package/src/__tests__/orchestrator.test.ts +1017 -1063
  158. package/src/__tests__/ui-access.test.ts +30 -0
  159. package/src/bin/host.ts +397 -572
  160. package/src/bin/join-output.ts +120 -0
  161. package/src/bin/nat-output.ts +188 -0
  162. package/src/bin/ui-access.ts +50 -0
  163. package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +172 -173
  164. package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +43 -23
  165. package/src/hosted/__tests__/hosted-node-service.test.ts +708 -0
  166. package/src/hosted/__tests__/hosted-node-supervisor.test.ts +475 -0
  167. package/src/hosted/__tests__/test-invitation.ts +33 -0
  168. package/src/hosted/claim-details.ts +103 -0
  169. package/src/hosted/hosted-node-service.ts +814 -0
  170. package/src/hosted/hosted-node-store.ts +144 -0
  171. package/src/hosted/hosted-node-supervisor.ts +439 -0
  172. package/src/hosted/hosted-node-watcher.ts +288 -0
  173. package/src/hosted/index.ts +53 -0
  174. package/src/hosted/node-status.ts +97 -0
  175. package/src/hosted/types.ts +160 -0
  176. package/src/index.ts +72 -91
  177. package/src/installer/__tests__/config.test.ts +15 -40
  178. package/src/installer/__tests__/installer.smoke.test.ts +184 -263
  179. package/src/installer/__tests__/wizard.test.ts +25 -13
  180. package/src/installer/config.ts +201 -175
  181. package/src/installer/index.ts +252 -327
  182. package/src/installer/paths.ts +104 -109
  183. package/src/installer/wizard.ts +24 -26
  184. package/src/nat/__tests__/address-resolver.test.ts +107 -82
  185. package/src/nat/__tests__/nat-service.test.ts +608 -515
  186. package/src/nat/__tests__/nat-store.test.ts +49 -12
  187. package/src/nat/__tests__/network-mode.test.ts +38 -0
  188. package/src/nat/__tests__/port-mapper.test.ts +30 -191
  189. package/src/nat/__tests__/reachability.test.ts +96 -13
  190. package/src/nat/address-resolver.ts +94 -52
  191. package/src/nat/address-watch.ts +92 -0
  192. package/src/nat/ddns/duckdns.ts +5 -1
  193. package/src/nat/ddns/index.ts +6 -0
  194. package/src/nat/index.ts +33 -13
  195. package/src/nat/nat-service.ts +942 -501
  196. package/src/nat/nat-store.ts +126 -31
  197. package/src/nat/network-mode.ts +36 -0
  198. package/src/nat/port-mapper.ts +241 -329
  199. package/src/nat/reachability.ts +155 -34
  200. package/src/nat/types.ts +110 -37
  201. package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
  202. package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
  203. package/src/orchestrator/host-process-orchestrator.ts +1089 -1260
  204. package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
  205. package/src/orchestrator/index.ts +9 -10
  206. package/src/orchestrator/node-identity.ts +41 -41
  207. package/src/orchestrator/port-allocator.ts +71 -73
  208. package/src/orchestrator/state-store.ts +76 -90
  209. package/src/orchestrator/types.ts +34 -23
  210. package/src/package-version.ts +4 -0
  211. package/src/server/__tests__/bus.test.ts +38 -38
  212. package/src/server/__tests__/error-handler.test.ts +81 -118
  213. package/src/server/__tests__/fakes.ts +15 -18
  214. package/src/server/__tests__/hosted-nodes-route.test.ts +161 -0
  215. package/src/server/__tests__/nodes-route.test.ts +114 -283
  216. package/src/server/__tests__/publishers.test.ts +193 -180
  217. package/src/server/__tests__/server.smoke.test.ts +134 -223
  218. package/src/server/__tests__/settings-route.test.ts +210 -195
  219. package/src/server/__tests__/sse-route.test.ts +119 -119
  220. package/src/server/__tests__/status-route.test.ts +82 -121
  221. package/src/server/error-handler.ts +112 -178
  222. package/src/server/events/types.ts +20 -24
  223. package/src/server/index.ts +223 -267
  224. package/src/server/routes/hosted-nodes.ts +83 -0
  225. package/src/server/routes/nat.ts +75 -63
  226. package/src/server/routes/nodes.ts +96 -188
  227. package/src/server/routes/settings.ts +20 -15
  228. package/src/server/routes/status.ts +5 -67
  229. package/src/server/server.ts +8 -8
  230. package/src/server/settings-store.ts +2 -2
  231. package/src/server/static.ts +1 -2
  232. package/src/update/index.ts +2 -2
  233. package/src/update/store.ts +1 -1
  234. package/src/update/types.ts +1 -1
  235. package/dist/auth/duration.d.ts +0 -1
  236. package/dist/auth/duration.js +0 -32
  237. package/dist/auth/duration.js.map +0 -1
  238. package/dist/auth/index.d.ts +0 -11
  239. package/dist/auth/index.js +0 -9
  240. package/dist/auth/index.js.map +0 -1
  241. package/dist/auth/self-label.d.ts +0 -36
  242. package/dist/auth/self-label.js +0 -57
  243. package/dist/auth/self-label.js.map +0 -1
  244. package/dist/auth/trust-circle-store.d.ts +0 -26
  245. package/dist/auth/trust-circle-store.js +0 -129
  246. package/dist/auth/trust-circle-store.js.map +0 -1
  247. package/dist/auth/trust-circle.d.ts +0 -156
  248. package/dist/auth/trust-circle.js +0 -307
  249. package/dist/auth/trust-circle.js.map +0 -1
  250. package/dist/auth/types.d.ts +0 -84
  251. package/dist/auth/types.js +0 -24
  252. package/dist/auth/types.js.map +0 -1
  253. package/dist/donation/donation-service.d.ts +0 -389
  254. package/dist/donation/donation-service.js +0 -828
  255. package/dist/donation/donation-service.js.map +0 -1
  256. package/dist/donation/donation-store.d.ts +0 -51
  257. package/dist/donation/donation-store.js +0 -152
  258. package/dist/donation/donation-store.js.map +0 -1
  259. package/dist/donation/donation-supervisor.js +0 -364
  260. package/dist/donation/donation-supervisor.js.map +0 -1
  261. package/dist/donation/grant-service.d.ts +0 -80
  262. package/dist/donation/grant-service.js +0 -210
  263. package/dist/donation/grant-service.js.map +0 -1
  264. package/dist/donation/grant-store.d.ts +0 -38
  265. package/dist/donation/grant-store.js +0 -135
  266. package/dist/donation/grant-store.js.map +0 -1
  267. package/dist/donation/index.d.ts +0 -20
  268. package/dist/donation/index.js +0 -17
  269. package/dist/donation/index.js.map +0 -1
  270. package/dist/donation/types.d.ts +0 -224
  271. package/dist/donation/types.js +0 -40
  272. package/dist/donation/types.js.map +0 -1
  273. package/dist/installer/identity.js.map +0 -1
  274. package/dist/owner/index.d.ts +0 -11
  275. package/dist/owner/index.js +0 -11
  276. package/dist/owner/index.js.map +0 -1
  277. package/dist/owner/owner-node-client.d.ts +0 -110
  278. package/dist/owner/owner-node-client.js +0 -184
  279. package/dist/owner/owner-node-client.js.map +0 -1
  280. package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
  281. package/dist/server/routes/bootstrap-node-validation.js +0 -197
  282. package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
  283. package/dist/server/routes/grants-admin.d.ts +0 -29
  284. package/dist/server/routes/grants-admin.js +0 -58
  285. package/dist/server/routes/grants-admin.js.map +0 -1
  286. package/dist/server/routes/grants.d.ts +0 -28
  287. package/dist/server/routes/grants.js +0 -165
  288. package/dist/server/routes/grants.js.map +0 -1
  289. package/dist/server/routes/provision-request-validation.d.ts +0 -45
  290. package/dist/server/routes/provision-request-validation.js +0 -97
  291. package/dist/server/routes/provision-request-validation.js.map +0 -1
  292. package/dist/server/routes/strands.d.ts +0 -20
  293. package/dist/server/routes/strands.js +0 -38
  294. package/dist/server/routes/strands.js.map +0 -1
  295. package/dist/server/routes/trust-circle.d.ts +0 -17
  296. package/dist/server/routes/trust-circle.js +0 -35
  297. package/dist/server/routes/trust-circle.js.map +0 -1
  298. package/dist/strands/index.d.ts +0 -7
  299. package/dist/strands/index.js +0 -6
  300. package/dist/strands/index.js.map +0 -1
  301. package/dist/strands/strand-service.d.ts +0 -50
  302. package/dist/strands/strand-service.js +0 -103
  303. package/dist/strands/strand-service.js.map +0 -1
  304. package/dist/strands/types.d.ts +0 -93
  305. package/dist/strands/types.js +0 -23
  306. package/dist/strands/types.js.map +0 -1
  307. package/dist/ui/assets/index-C6nELNfC.js +0 -23
  308. package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
  309. package/dist/ui/assets/index-TtIYSeZO.css +0 -1
  310. package/src/__tests__/cli-invite.smoke.test.ts +0 -192
  311. package/src/__tests__/host-owner.smoke.test.ts +0 -123
  312. package/src/__tests__/orchestrator-owner.test.ts +0 -234
  313. package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
  314. package/src/auth/__tests__/duration.test.ts +0 -28
  315. package/src/auth/__tests__/self-label.test.ts +0 -85
  316. package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
  317. package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
  318. package/src/auth/__tests__/trust-circle.test.ts +0 -445
  319. package/src/auth/duration.ts +0 -33
  320. package/src/auth/index.ts +0 -21
  321. package/src/auth/self-label.ts +0 -74
  322. package/src/auth/trust-circle-store.ts +0 -155
  323. package/src/auth/trust-circle.ts +0 -395
  324. package/src/auth/types.ts +0 -99
  325. package/src/donation/__tests__/donation-service.test.ts +0 -1193
  326. package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
  327. package/src/donation/__tests__/grant-service.test.ts +0 -213
  328. package/src/donation/__tests__/grant-store.test.ts +0 -113
  329. package/src/donation/donation-service.ts +0 -1033
  330. package/src/donation/donation-store.ts +0 -176
  331. package/src/donation/donation-supervisor.ts +0 -411
  332. package/src/donation/grant-service.ts +0 -252
  333. package/src/donation/grant-store.ts +0 -152
  334. package/src/donation/index.ts +0 -65
  335. package/src/donation/types.ts +0 -260
  336. package/src/owner/__tests__/owner-node-client.test.ts +0 -194
  337. package/src/owner/index.ts +0 -12
  338. package/src/owner/owner-node-client.ts +0 -259
  339. package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
  340. package/src/server/__tests__/grants-admin-route.test.ts +0 -171
  341. package/src/server/__tests__/grants-route.test.ts +0 -394
  342. package/src/server/__tests__/strands-route.test.ts +0 -234
  343. package/src/server/routes/bootstrap-node-validation.ts +0 -206
  344. package/src/server/routes/grants-admin.ts +0 -74
  345. package/src/server/routes/grants.ts +0 -193
  346. package/src/server/routes/provision-request-validation.ts +0 -105
  347. package/src/server/routes/strands.ts +0 -54
  348. package/src/server/routes/trust-circle.ts +0 -49
  349. package/src/strands/__tests__/strand-service.test.ts +0 -165
  350. package/src/strands/index.ts +0 -14
  351. package/src/strands/strand-service.ts +0 -135
  352. package/src/strands/types.ts +0 -102
@@ -1,56 +1,56 @@
1
- /**
2
- * Identity persistence for cadre-host.
3
- *
4
- * The host's libp2p peer identity is an Ed25519 keypair generated at
5
- * install time. The protobuf-encoded private key is written to
6
- * `<dataDir>/identity.key` with mode 0600 on POSIX. `cadre-host start`
7
- * later loads it with `privateKeyFromProtobuf`.
8
- *
9
- * We persist the protobuf form (not raw bytes) because that's the format
10
- * libp2p's `privateKeyFromProtobuf` consumes directly and it preserves the
11
- * key type tag — future migrations to a different key algorithm don't
12
- * require a schema change here. Raw bytes carry no type tag AND no way to tell
13
- * a damaged file from a valid one: `privateKeyFromRaw` accepts any 64 bytes as
14
- * an Ed25519 key without checking that the public half matches the private, so
15
- * a truncated file would load as a *different* working identity.
16
- *
17
- * This is the repo-wide identity file format, not a cadre-host-local choice:
18
- * `cadre enroll create` writes exactly these bytes, and `cadre-cli`'s
19
- * `identity.keyFile` accepts nothing else (`config/loader.ts` → `loadIdentityKey`).
20
- */
21
-
22
- import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
23
- import { dirname } from 'node:path';
24
- import {
25
- generateKeyPair,
26
- privateKeyFromProtobuf,
27
- privateKeyToProtobuf,
28
- } from '@libp2p/crypto/keys';
29
- import { peerIdFromPrivateKey } from '@libp2p/peer-id';
30
- import type { PrivateKey } from '@libp2p/interface';
31
-
32
- export interface IdentityRecord {
33
- privateKey: PrivateKey;
34
- /** Stringified libp2p peer ID (`12D3Koo…`). */
35
- peerId: string;
36
- }
37
-
38
- /** Generate a fresh Ed25519 identity and persist it. Returns the in-memory record. */
39
- export async function generateIdentity(filePath: string): Promise<IdentityRecord> {
40
- const privateKey = await generateKeyPair('Ed25519');
41
- const bytes = privateKeyToProtobuf(privateKey);
42
- mkdirSync(dirname(filePath), { recursive: true });
43
- writeFileSync(filePath, bytes);
44
- // 0600 — owner read/write only. No-op on Windows.
45
- if (process.platform !== 'win32') {
46
- chmodSync(filePath, 0o600);
47
- }
48
- return { privateKey, peerId: peerIdFromPrivateKey(privateKey).toString() };
49
- }
50
-
51
- /** Load an existing identity file. Throws if it doesn't decode. */
52
- export function loadIdentity(filePath: string): IdentityRecord {
53
- const bytes = readFileSync(filePath);
54
- const privateKey = privateKeyFromProtobuf(new Uint8Array(bytes));
55
- return { privateKey, peerId: peerIdFromPrivateKey(privateKey).toString() };
56
- }
1
+ /**
2
+ * Identity persistence for the nodes cadre-host runs.
3
+ *
4
+ * Each managed node's libp2p peer identity is an Ed25519 keypair generated on
5
+ * its first spawn (`node-identity.ts`). The protobuf-encoded
6
+ * private key is written with mode 0600 on POSIX and loaded with
7
+ * `privateKeyFromProtobuf`.
8
+ *
9
+ * We persist the protobuf form (not raw bytes) because that's the format
10
+ * libp2p's `privateKeyFromProtobuf` consumes directly and it preserves the
11
+ * key type tag — future migrations to a different key algorithm don't
12
+ * require a schema change here. Raw bytes carry no type tag AND no way to tell
13
+ * a damaged file from a valid one: `privateKeyFromRaw` accepts any 64 bytes as
14
+ * an Ed25519 key without checking that the public half matches the private, so
15
+ * a truncated file would load as a *different* working identity.
16
+ *
17
+ * This is the repo-wide identity file format, not a cadre-host-local choice:
18
+ * `cadre enroll create` writes exactly these bytes, and `cadre-cli`'s
19
+ * `identity.keyFile` accepts nothing else (`config/loader.ts` → `loadIdentityKey`).
20
+ */
21
+
22
+ import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
23
+ import { dirname } from 'node:path';
24
+ import {
25
+ generateKeyPair,
26
+ privateKeyFromProtobuf,
27
+ privateKeyToProtobuf,
28
+ } from '@libp2p/crypto/keys';
29
+ import { peerIdFromPrivateKey } from '@libp2p/peer-id';
30
+ import type { PrivateKey } from '@libp2p/interface';
31
+
32
+ export interface IdentityRecord {
33
+ privateKey: PrivateKey;
34
+ /** Stringified libp2p peer ID (`12D3Koo…`). */
35
+ peerId: string;
36
+ }
37
+
38
+ /** Generate a fresh Ed25519 identity and persist it. Returns the in-memory record. */
39
+ export async function generateIdentity(filePath: string): Promise<IdentityRecord> {
40
+ const privateKey = await generateKeyPair('Ed25519');
41
+ const bytes = privateKeyToProtobuf(privateKey);
42
+ mkdirSync(dirname(filePath), { recursive: true });
43
+ writeFileSync(filePath, bytes);
44
+ // 0600 — owner read/write only. No-op on Windows.
45
+ if (process.platform !== 'win32') {
46
+ chmodSync(filePath, 0o600);
47
+ }
48
+ return { privateKey, peerId: peerIdFromPrivateKey(privateKey).toString() };
49
+ }
50
+
51
+ /** Load an existing identity file. Throws if it doesn't decode. */
52
+ export function loadIdentity(filePath: string): IdentityRecord {
53
+ const bytes = readFileSync(filePath);
54
+ const privateKey = privateKeyFromProtobuf(new Uint8Array(bytes));
55
+ return { privateKey, peerId: peerIdFromPrivateKey(privateKey).toString() };
56
+ }
@@ -1,10 +1,9 @@
1
- export { HostProcessOrchestrator, OWNER_CONTAINER_ID } from './host-process-orchestrator.js';
2
- export type { OwnerAdminEndpoint } from './host-process-orchestrator.js';
3
- export type {
4
- HostProcessConfig,
5
- ManagedNodeInfo,
6
- NodeStateListener,
7
- NodePorts,
8
- OwnerSpawnConfig,
9
- } from './types.js';
10
- export type { PersistedHandle } from './state-store.js';
1
+ export { HostProcessOrchestrator } from './host-process-orchestrator.js';
2
+ export type {
3
+ HostProcessConfig,
4
+ HostedSpawnRequest,
5
+ ManagedNodeInfo,
6
+ NodeStateListener,
7
+ NodePorts,
8
+ } from './types.js';
9
+ export type { PersistedHandle } from './state-store.js';
@@ -1,41 +1,41 @@
1
- /**
2
- * Per-node identity keys for orchestrator-managed children.
3
- *
4
- * A donated node joins a *foreign* cadre: the requester approves its peer id
5
- * and its seed nominates dial addresses keyed to that peer id. Both bind to the
6
- * identity, so a node that generated a fresh libp2p keypair per process would
7
- * become a stranger to the cadre that admitted it on every restart. It also
8
- * needs a stable on-disk home for its node-local stores — `cadre-cli start`
9
- * opens `FileBootstrapPeerStore` / `FileTrustedOwnerStore` /
10
- * `FileStrandNetworkStateStore` in the node's state directory, which defaults to
11
- * the directory holding the config file the orchestrator writes into the workdir.
12
- *
13
- * So every managed node gets its own `identity.key` inside its workdir, written
14
- * once and reused thereafter. The workdir is removed when the loan is
15
- * terminated (`removeContainer`), so nothing the node persisted outlives it.
16
- */
17
-
18
- import { existsSync } from 'node:fs';
19
- import { join } from 'node:path';
20
-
21
- import { generateIdentity, loadIdentity, type IdentityRecord } from '../installer/identity.js';
22
-
23
- const IDENTITY_FILE = 'identity.key';
24
-
25
- /** Path to a managed node's own protobuf identity key inside its workdir. */
26
- export function nodeIdentityPath(workdir: string): string {
27
- return join(workdir, IDENTITY_FILE);
28
- }
29
-
30
- /**
31
- * Ensure `<workdir>/identity.key` exists, generating an Ed25519 protobuf key on
32
- * first call and REUSING it on every later call. Reuse is the load-bearing
33
- * property: a re-spawn of the same container must land on the same peer id.
34
- */
35
- export async function ensureNodeIdentity(workdir: string): Promise<{ path: string; peerId: string }> {
36
- const path = nodeIdentityPath(workdir);
37
- const record: IdentityRecord = existsSync(path)
38
- ? loadIdentity(path)
39
- : await generateIdentity(path);
40
- return { path, peerId: record.peerId };
41
- }
1
+ /**
2
+ * Per-node identity keys for orchestrator-managed children.
3
+ *
4
+ * A hosted node joins the cadre whose owner claimed it: the claim named its peer
5
+ * id, and the owner's phone dials the addresses keyed to that peer id. Both bind
6
+ * to the identity, so a node that generated a fresh libp2p keypair per process
7
+ * would become a stranger to the cadre that claimed it on every restart. It also
8
+ * needs a stable on-disk home for its node-local stores — `cadre-cli start`
9
+ * opens `FileBootstrapPeerStore` / `FileTrustedOwnerStore` /
10
+ * `FileStrandNetworkStateStore` in the node's state directory, which defaults to
11
+ * the directory holding the config file the orchestrator writes into the workdir.
12
+ *
13
+ * So every managed node gets its own `identity.key` inside its workdir, written
14
+ * once and reused thereafter. The workdir is removed with the node
15
+ * (`removeContainer`), so nothing the node persisted outlives it.
16
+ */
17
+
18
+ import { existsSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+
21
+ import { generateIdentity, loadIdentity, type IdentityRecord } from './identity-file.js';
22
+
23
+ const IDENTITY_FILE = 'identity.key';
24
+
25
+ /** Path to a managed node's own protobuf identity key inside its workdir. */
26
+ export function nodeIdentityPath(workdir: string): string {
27
+ return join(workdir, IDENTITY_FILE);
28
+ }
29
+
30
+ /**
31
+ * Ensure `<workdir>/identity.key` exists, generating an Ed25519 protobuf key on
32
+ * first call and REUSING it on every later call. Reuse is the load-bearing
33
+ * property: a re-spawn of the same container must land on the same peer id.
34
+ */
35
+ export async function ensureNodeIdentity(workdir: string): Promise<{ path: string; peerId: string }> {
36
+ const path = nodeIdentityPath(workdir);
37
+ const record: IdentityRecord = existsSync(path)
38
+ ? loadIdentity(path)
39
+ : await generateIdentity(path);
40
+ return { path, peerId: record.peerId };
41
+ }
@@ -1,73 +1,71 @@
1
- import {
2
- allocatePortSet,
3
- releasePortSet,
4
- reservePortSet,
5
- type PortAllocator,
6
- } from '@serfab/cadre-provider';
7
-
8
- import type { NodePorts } from './types.js';
9
-
10
- /**
11
- * Allocation order for a node's port set. Fixed so that swapping a caller onto
12
- * {@link allocateNodePorts} does not shift the ports an existing deployment
13
- * already handed out — which is also why a key added later goes on the end
14
- * (`ws` came after the first four).
15
- */
16
- const NODE_PORT_KEYS = ['health', 'metrics', 'p2p', 'admin', 'ws'] as const satisfies readonly (keyof NodePorts)[];
17
-
18
- /**
19
- * Allocate a full NodePorts set atomically, in {@link NODE_PORT_KEYS} order.
20
- * `overrides` supply a port instead of allocating one (the owner node's p2p
21
- * port, which is fixed by NAT config; a re-spawn's previous ports, see
22
- * {@link reusedNodePorts}). Overrides are reserved before anything is allocated
23
- * and are trusted, not checked — see `allocatePortSet`.
24
- */
25
- export function allocateNodePorts(
26
- allocator: PortAllocator,
27
- overrides?: Partial<NodePorts>,
28
- ): NodePorts {
29
- return allocatePortSet(allocator, NODE_PORT_KEYS, overrides);
30
- }
31
-
32
- /**
33
- * Hold a node's whole port set against `allocator` — the inverse of
34
- * {@link releaseNodePorts}. A key the set lacks is skipped (see `markUsed`).
35
- */
36
- export function reserveNodePorts(allocator: PortAllocator, ports: NodePorts): void {
37
- reservePortSet(allocator, NODE_PORT_KEYS, ports);
38
- }
39
-
40
- /**
41
- * Give a node's whole port set back to `allocator`. A key the set lacks releases
42
- * nothing: deleting a value the used-set never held is a no-op.
43
- */
44
- export function releaseNodePorts(allocator: PortAllocator, ports: NodePorts): void {
45
- releasePortSet(allocator, NODE_PORT_KEYS, ports);
46
- }
47
-
48
- /**
49
- * The ports a re-spawn comes back on: the set the dropped handle held, as
50
- * {@link allocateNodePorts} overrides. `{}` when nothing was dropped (a first
51
- * spawn, or a handle lost along with `state.json`), so every port is allocated
52
- * fresh.
53
- *
54
- * Why a re-spawn must not simply take the lowest free ports: a requester with no
55
- * address of its own (a phone) reaches a lent node only by dialing the address it
56
- * was given, so a node that comes back on a different port is one that requester
57
- * can no longer find.
58
- *
59
- * A key the dropped handle lacks — `ws` on a handle persisted by an older build —
60
- * is left out, and so allocated fresh. When more than one handle was dropped (only
61
- * possible from a `state.json` that already held duplicates), the last one stored
62
- * wins and the others' ports stay released.
63
- */
64
- export function reusedNodePorts(dropped: readonly { ports: NodePorts }[]): Partial<NodePorts> {
65
- const reused: Partial<NodePorts> = {};
66
- const previous = dropped[dropped.length - 1]?.ports;
67
- if (!previous) return reused;
68
- for (const key of NODE_PORT_KEYS) {
69
- const port = previous[key];
70
- if (Number.isInteger(port)) reused[key] = port;
71
- }
72
- return reused;
73
- }
1
+ import {
2
+ allocatePortSet,
3
+ releasePortSet,
4
+ reservePortSet,
5
+ type PortAllocator,
6
+ } from '@serfab/cadre-provider';
7
+
8
+ import type { NodePorts } from './types.js';
9
+
10
+ /**
11
+ * Allocation order for a node's port set. A node already running keeps its
12
+ * ports whatever this order says ({@link reusedNodePorts}); the order decides
13
+ * only where a fresh allocation lands.
14
+ */
15
+ const NODE_PORT_KEYS = ['health', 'metrics', 'p2p', 'ws'] as const satisfies readonly (keyof NodePorts)[];
16
+
17
+ /**
18
+ * Allocate a full NodePorts set atomically, in {@link NODE_PORT_KEYS} order.
19
+ * `overrides` supply a port instead of allocating one (a re-spawn's previous
20
+ * ports, see {@link reusedNodePorts}). Overrides are reserved before anything is allocated
21
+ * and are trusted, not checked — see `allocatePortSet`.
22
+ */
23
+ export function allocateNodePorts(
24
+ allocator: PortAllocator,
25
+ overrides?: Partial<NodePorts>,
26
+ ): NodePorts {
27
+ return allocatePortSet(allocator, NODE_PORT_KEYS, overrides);
28
+ }
29
+
30
+ /**
31
+ * Hold a node's whole port set against `allocator` — the inverse of
32
+ * {@link releaseNodePorts}. A key the set lacks is skipped (see `markUsed`).
33
+ */
34
+ export function reserveNodePorts(allocator: PortAllocator, ports: NodePorts): void {
35
+ reservePortSet(allocator, NODE_PORT_KEYS, ports);
36
+ }
37
+
38
+ /**
39
+ * Give a node's whole port set back to `allocator`. A key the set lacks releases
40
+ * nothing: deleting a value the used-set never held is a no-op.
41
+ */
42
+ export function releaseNodePorts(allocator: PortAllocator, ports: NodePorts): void {
43
+ releasePortSet(allocator, NODE_PORT_KEYS, ports);
44
+ }
45
+
46
+ /**
47
+ * The ports a re-spawn comes back on: the set the dropped handle held, as
48
+ * {@link allocateNodePorts} overrides. `{}` when nothing was dropped (a first
49
+ * spawn, or a handle lost along with `state.json`), so every port is allocated
50
+ * fresh.
51
+ *
52
+ * Why a re-spawn must not simply take the lowest free ports: an owner with no
53
+ * address of its own (a phone) reaches a hosted node only by dialing the address
54
+ * it scanned, so a node that comes back on a different port is one that owner
55
+ * can no longer find.
56
+ *
57
+ * A key the dropped handle lacks — `ws` on a handle persisted by an older build —
58
+ * is left out, and so allocated fresh. When more than one handle was dropped (only
59
+ * possible from a `state.json` that already held duplicates), the last one stored
60
+ * wins and the others' ports stay released.
61
+ */
62
+ export function reusedNodePorts(dropped: readonly { ports: NodePorts }[]): Partial<NodePorts> {
63
+ const reused: Partial<NodePorts> = {};
64
+ const previous = dropped[dropped.length - 1]?.ports;
65
+ if (!previous) return reused;
66
+ for (const key of NODE_PORT_KEYS) {
67
+ const port = previous[key];
68
+ if (Number.isInteger(port)) reused[key] = port;
69
+ }
70
+ return reused;
71
+ }
@@ -1,90 +1,76 @@
1
- import { existsSync, readFileSync, renameSync, writeFileSync, mkdirSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
3
- import debug from 'debug';
4
-
5
- import type { OwnerSpawnConfig, NodePorts } from './types.js';
6
-
7
- const log = debug('cadre:host:state-store');
8
-
9
- /**
10
- * Persisted record for a single managed child. The orchestrator mirrors its
11
- * in-memory map to disk so that cadre-host can re-attach to surviving
12
- * children after a restart.
13
- */
14
- export interface PersistedHandle {
15
- containerId: string; // provider-assigned ID, passed in createContainer
16
- dockerId: string; // opaque token returned to callers (encoded "pid:token")
17
- pid: number;
18
- startupToken: string;
19
- workdir: string;
20
- ports: NodePorts;
21
- spawnedAt: string; // ISO timestamp
22
- partyId: string;
23
- profile: 'storage' | 'transaction';
24
- /** True for the admin's owner node. */
25
- owner?: boolean;
26
- }
27
-
28
- export interface PersistedState {
29
- version: 1;
30
- handles: PersistedHandle[];
31
- /** Spawn parameters for the owner node, so it can be re-spawned on demand. */
32
- ownerConfig?: OwnerSpawnConfig;
33
- }
34
-
35
- const STATE_VERSION = 1;
36
-
37
- /** JSON state file at `<rootDir>/state.json`, written atomically. */
38
- export class StateStore {
39
- private readonly path: string;
40
-
41
- constructor(rootDir: string) {
42
- mkdirSync(rootDir, { recursive: true });
43
- this.path = join(rootDir, 'state.json');
44
- }
45
-
46
- load(): PersistedState {
47
- if (!existsSync(this.path)) {
48
- return { version: STATE_VERSION, handles: [] };
49
- }
50
- try {
51
- const raw = readFileSync(this.path, 'utf8');
52
- const parsed = JSON.parse(raw) as PersistedState;
53
- if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.handles)) {
54
- log('state file at %s has unexpected shape; treating as empty', this.path);
55
- return { version: STATE_VERSION, handles: [] };
56
- }
57
- return {
58
- version: STATE_VERSION,
59
- handles: parsed.handles,
60
- ...(parsed.ownerConfig ? { ownerConfig: parsed.ownerConfig } : {}),
61
- };
62
- } catch (err) {
63
- // A corrupt state file is operationally significant — surviving children
64
- // may now be leaked to the OS without an in-memory handle. Make the
65
- // failure visible rather than silently returning empty.
66
- log('failed to load state file at %s: %o', this.path, err);
67
- return { version: STATE_VERSION, handles: [] };
68
- }
69
- }
70
-
71
- save(state: PersistedState): void {
72
- mkdirSync(dirname(this.path), { recursive: true });
73
- const payload = JSON.stringify(
74
- {
75
- version: STATE_VERSION,
76
- handles: state.handles,
77
- ...(state.ownerConfig ? { ownerConfig: state.ownerConfig } : {}),
78
- },
79
- null,
80
- 2,
81
- );
82
- const tmp = `${this.path}.tmp`;
83
- writeFileSync(tmp, payload, { encoding: 'utf8' });
84
- renameSync(tmp, this.path);
85
- }
86
-
87
- filePath(): string {
88
- return this.path;
89
- }
90
- }
1
+ import { existsSync, readFileSync, renameSync, writeFileSync, mkdirSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import debug from 'debug';
4
+
5
+ import type { NodePorts } from './types.js';
6
+
7
+ const log = debug('cadre:host:state-store');
8
+
9
+ /**
10
+ * Persisted record for a single managed child. The orchestrator mirrors its
11
+ * in-memory map to disk so that cadre-host can re-attach to surviving
12
+ * children after a restart.
13
+ */
14
+ export interface PersistedHandle {
15
+ containerId: string; // provider-assigned ID, passed in createContainer
16
+ dockerId: string; // opaque token returned to callers (encoded "pid:token")
17
+ pid: number;
18
+ startupToken: string;
19
+ workdir: string;
20
+ ports: NodePorts;
21
+ spawnedAt: string; // ISO timestamp
22
+ partyId: string;
23
+ profile: 'storage' | 'transaction';
24
+ /** The public addresses the child was started announcing. Absent in a file written before they existed. */
25
+ announcedAddrs?: string[];
26
+ }
27
+
28
+ export interface PersistedState {
29
+ version: 1;
30
+ handles: PersistedHandle[];
31
+ }
32
+
33
+ const STATE_VERSION = 1;
34
+
35
+ /** JSON state file at `<rootDir>/state.json`, written atomically. */
36
+ export class StateStore {
37
+ private readonly path: string;
38
+
39
+ constructor(rootDir: string) {
40
+ mkdirSync(rootDir, { recursive: true });
41
+ this.path = join(rootDir, 'state.json');
42
+ }
43
+
44
+ load(): PersistedState {
45
+ if (!existsSync(this.path)) {
46
+ return { version: STATE_VERSION, handles: [] };
47
+ }
48
+ try {
49
+ const raw = readFileSync(this.path, 'utf8');
50
+ const parsed = JSON.parse(raw) as PersistedState;
51
+ if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.handles)) {
52
+ log('state file at %s has unexpected shape; treating as empty', this.path);
53
+ return { version: STATE_VERSION, handles: [] };
54
+ }
55
+ return { version: STATE_VERSION, handles: parsed.handles };
56
+ } catch (err) {
57
+ // A corrupt state file is operationally significant — surviving children
58
+ // may now be leaked to the OS without an in-memory handle. Make the
59
+ // failure visible rather than silently returning empty.
60
+ log('failed to load state file at %s: %o', this.path, err);
61
+ return { version: STATE_VERSION, handles: [] };
62
+ }
63
+ }
64
+
65
+ save(state: PersistedState): void {
66
+ mkdirSync(dirname(this.path), { recursive: true });
67
+ const payload = JSON.stringify({ version: STATE_VERSION, handles: state.handles }, null, 2);
68
+ const tmp = `${this.path}.tmp`;
69
+ writeFileSync(tmp, payload, { encoding: 'utf8' });
70
+ renameSync(tmp, this.path);
71
+ }
72
+
73
+ filePath(): string {
74
+ return this.path;
75
+ }
76
+ }
@@ -4,6 +4,22 @@
4
4
 
5
5
  import type { ChildProcess } from 'node:child_process';
6
6
  import type { PushCredentials } from '@serfab/cadre-core';
7
+ import type { OrchestratorCreateRequest } from '@serfab/cadre-provider';
8
+
9
+ /**
10
+ * What `HostProcessOrchestrator.createContainer` takes: the shared create request plus how
11
+ * the node gets into its cadre — the claim secret of a node started waiting to be claimed
12
+ * (`cadre-cli start` with `CADRE_CLAIM_SECRET`), or the cadre invitation it redeems
13
+ * (`CADRE_INVITATION`). At most one of the two. Each is a credential and reaches the child
14
+ * through its environment only: never as an argument (an argument shows in the process
15
+ * list), never in `state.json`, never in a log line.
16
+ */
17
+ export type HostedSpawnRequest = OrchestratorCreateRequest & {
18
+ /** The node's one-time claim secret, base64url. Absent, the child starts with no claim. */
19
+ claimSecret?: string;
20
+ /** An encoded cadre invitation the child redeems right after it starts. */
21
+ invitation?: string;
22
+ };
7
23
 
8
24
  /** User-facing configuration for `HostProcessOrchestrator`. */
9
25
  export interface HostProcessConfig {
@@ -33,6 +49,15 @@ export interface HostProcessConfig {
33
49
  * resolver itself is responsible for rejecting a partial credential set.
34
50
  */
35
51
  pushResolver?: PushCredentialsResolver;
52
+ /**
53
+ * The public multiaddrs a node about to spawn should announce beside its listen
54
+ * addresses (`CADRE_APPEND_ANNOUNCE_ADDRS`), given its container id and the ports just
55
+ * allocated to it. Synchronous on purpose: both spawn paths allocate ports inside a
56
+ * window that must contain no `await` (see `restoreDroppedHandles`), and the addresses
57
+ * depend on those ports. A throw is logged and the node starts announcing nothing extra.
58
+ * Absent, no node announces anything beyond what libp2p reports.
59
+ */
60
+ announceAddrs?: (containerId: string, ports: NodePorts) => string[];
36
61
  }
37
62
 
38
63
  /**
@@ -43,7 +68,7 @@ export interface HostProcessConfig {
43
68
  export type PushCredentialsResolver = () => Promise<PushCredentials | undefined>;
44
69
 
45
70
  /**
46
- * Per-child allocated ports. `admin` carries the loopback admin channel (6.6).
71
+ * Per-child allocated ports.
47
72
  *
48
73
  * A handle read back from a `state.json` written by an older build lacks every key
49
74
  * added since, whatever this type says — the node-set helpers in `port-allocator.ts`
@@ -54,8 +79,6 @@ export interface NodePorts {
54
79
  metrics: number;
55
80
  /** libp2p TCP listener. */
56
81
  p2p: number;
57
- /** Loopback admin-channel port. Bound only by the owner node. */
58
- admin: number;
59
82
  /**
60
83
  * libp2p WebSocket listener. The one a phone dials: a phone's node has no TCP
61
84
  * transport, so the `p2p` port is unreachable to it.
@@ -74,8 +97,8 @@ export interface Handle {
74
97
  spawnedAt: string;
75
98
  partyId: string;
76
99
  profile: 'storage' | 'transaction';
77
- /** True for the admin's owner node (binds the admin channel). */
78
- owner?: boolean;
100
+ /** The public addresses the child was started announcing ({@link HostProcessConfig.announceAddrs}). */
101
+ announcedAddrs: string[];
79
102
  /** Live ChildProcess reference; absent after re-attach via init(). */
80
103
  child?: ChildProcess;
81
104
  /** Marked false when init() finds the PID dead or token mismatched. */
@@ -95,24 +118,12 @@ export interface ManagedNodeInfo {
95
118
  spawnedAt: string;
96
119
  workdir: string;
97
120
  ports: NodePorts;
98
- /** True for the admin's owner node. */
99
- owner?: boolean;
100
- }
101
-
102
- /**
103
- * Persisted spawn parameters for the admin's owner node. Stored alongside
104
- * the handles so an orchestrator restart can re-spawn the owner node from
105
- * `/api/nodes/:id/{start,restart}` without the caller re-supplying them.
106
- */
107
- export interface OwnerSpawnConfig {
108
- /** Absolute path to the host's libp2p protobuf identity (`identity.key`). */
109
- identityPath: string;
110
- /** Control-network partyId (the host's installId). */
111
- partyId: string;
112
- /** External libp2p port — mapped to the child's listen address. */
113
- libp2pPort: number;
114
- /** Node profile. Defaults to `storage` (the household node holds strands). */
115
- profile?: 'storage' | 'transaction';
121
+ /**
122
+ * The public addresses the node was started announcing. It learns them only at start,
123
+ * so `NatService` compares this with the node's current public addresses and asks for
124
+ * a restart when they differ.
125
+ */
126
+ announcedAddrs: string[];
116
127
  }
117
128
 
118
129
  /**
@@ -0,0 +1,4 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ // src/package-version.ts and dist/package-version.js both sit one level below package.json.
4
+ export const packageVersion = (createRequire(import.meta.url)('../package.json') as { version: string }).version;