@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
@@ -0,0 +1,260 @@
1
+ import debug from 'debug';
2
+ import { decodeCadreInvitation } from '@serfab/cadre-core';
3
+ import { errorMessage, readWatchedNodeStatus, reportsClaim, reportsRemoval } from './node-status.js';
4
+ const log = debug('cadre:host:hosted-node-watcher');
5
+ /** How often an unclaimed node's `/status` is read for the claim, and a joining node's for its redemption. */
6
+ export const HOSTED_NODE_CLAIM_POLL_MS = 2000;
7
+ /** How often a joined node's `/status` is read for its connection count. */
8
+ export const HOSTED_NODE_CONNECTED_POLL_MS = 15000;
9
+ /** The `error` a node an owner removed from its cadre is recorded with. */
10
+ const REMOVED_BY_OWNER_ERROR = 'Removed from its cadre by an owner';
11
+ /** Statuses the watcher reads every tick: a node still on its way into its cadre. */
12
+ const PENDING_STATUSES = new Set(['unclaimed', 'joining']);
13
+ /**
14
+ * Follows each hosted node's `/status`: an `unclaimed` record becomes `joined` when
15
+ * the node reports a finished claim; a `joining` record becomes `joined` when a member
16
+ * accepted its invitation, or `error` when the redemption failed; a `joined`
17
+ * record's `connected` follows the node's control-connection count; and any of them
18
+ * becomes `error` when the node reports that an owner removed it from its cadre.
19
+ * Best-effort: a poll that fails is retried on the next tick, and nothing here throws
20
+ * out of the timer.
21
+ *
22
+ * One timer at the claim cadence drives all three: every tick polls every unclaimed and
23
+ * joining node, and each joined node every {@link HOSTED_NODE_CONNECTED_POLL_MS}, or at once when it
24
+ * was poked — the orchestrator's state change on a respawn pokes the node, and the
25
+ * poke stands until a poll succeeds, so a respawned child is noticed on its first
26
+ * answer rather than at the next 15 s mark. Polls are serialized on one promise
27
+ * tail so a timer pass and a poked pass cannot both write one record.
28
+ *
29
+ * NOTE: the 2 s timer ticks while nothing is unclaimed (one cached store read, no
30
+ * I/O per tick); if it ever shows up in a profile, stop it when no record is
31
+ * unclaimed and restart it from `poke`.
32
+ */
33
+ export class HostedNodeWatcher {
34
+ constructor(opts) {
35
+ this.tail = Promise.resolve();
36
+ /** When each joined node's `/status` last answered (ms since epoch). */
37
+ this.lastPolledAt = new Map();
38
+ /** Joined nodes to poll on the next pass regardless of cadence. */
39
+ this.due = new Set();
40
+ this.store = opts.store;
41
+ this.orchestrator = opts.orchestrator;
42
+ this.now = opts.now;
43
+ this.emit = opts.emit;
44
+ }
45
+ /** Start the timer and the state-change subscription, and poll once. Idempotent. */
46
+ start() {
47
+ if (this.timer)
48
+ return;
49
+ this.unsubscribe = this.orchestrator.onStateChange?.((info) => { this.poke(info.id); });
50
+ this.timer = setInterval(() => { this.poke(); }, HOSTED_NODE_CLAIM_POLL_MS);
51
+ this.timer.unref();
52
+ this.poke();
53
+ }
54
+ stop() {
55
+ if (this.timer) {
56
+ clearInterval(this.timer);
57
+ this.timer = undefined;
58
+ }
59
+ this.unsubscribe?.();
60
+ this.unsubscribe = undefined;
61
+ }
62
+ /** Ask for a pass now; `id` names a joined node to poll ahead of its cadence. Fire-and-forget. */
63
+ poke(id) {
64
+ if (id)
65
+ this.due.add(id);
66
+ void this.poll().catch((err) => { log('status poll failed: %s', errorMessage(err)); });
67
+ }
68
+ /** One pass over every unclaimed, joining and due joined record, serialized against the others. */
69
+ poll() {
70
+ const next = this.tail.then(() => this.pollOnce(), () => this.pollOnce());
71
+ this.tail = next.then(() => undefined, () => undefined);
72
+ return next;
73
+ }
74
+ async pollOnce() {
75
+ let records;
76
+ try {
77
+ records = this.store.list().filter((n) => PENDING_STATUSES.has(n.status) || n.status === 'joined');
78
+ }
79
+ catch (err) {
80
+ // A malformed hosted-nodes.json throws on every load; log rather than let it escape the timer.
81
+ log('poll could not list hosted nodes: %s', errorMessage(err));
82
+ return;
83
+ }
84
+ this.forgetRemoved(new Set(records.map((r) => r.id)));
85
+ for (const record of records) {
86
+ if (this.shouldPoll(record))
87
+ await this.pollOne(record);
88
+ }
89
+ }
90
+ /** Drop the cadence and poke bookkeeping of nodes no longer on record, so neither map grows with removals. */
91
+ forgetRemoved(live) {
92
+ for (const id of this.lastPolledAt.keys())
93
+ if (!live.has(id))
94
+ this.lastPolledAt.delete(id);
95
+ for (const id of this.due)
96
+ if (!live.has(id))
97
+ this.due.delete(id);
98
+ }
99
+ shouldPoll(record) {
100
+ if (PENDING_STATUSES.has(record.status) || this.due.has(record.id))
101
+ return true;
102
+ const last = this.lastPolledAt.get(record.id);
103
+ return last === undefined || this.now().getTime() - last >= HOSTED_NODE_CONNECTED_POLL_MS;
104
+ }
105
+ async pollOne(record) {
106
+ if (!record.statusEndpoint)
107
+ return;
108
+ let status;
109
+ try {
110
+ status = await readWatchedNodeStatus(record.statusEndpoint);
111
+ }
112
+ catch (err) {
113
+ log('hosted node %s did not answer /status (retried next tick): %s', record.id, errorMessage(err));
114
+ return;
115
+ }
116
+ this.lastPolledAt.set(record.id, this.now().getTime());
117
+ this.due.delete(record.id);
118
+ // Re-read: `record` predates the round-trip and `store.put` replaces the whole
119
+ // row, so writing that copy back would resurrect a node removed meanwhile, or
120
+ // undo a respawn's new handles. The store is synchronous, so this read-decide-
121
+ // write is atomic against the event loop as long as no `await` sneaks in.
122
+ const current = this.store.get(record.id);
123
+ if (!current || current.status !== record.status)
124
+ return;
125
+ if (reportsRemoval(status)) {
126
+ await this.applyRemoved(current, status);
127
+ }
128
+ else if (current.status === 'unclaimed') {
129
+ this.applyUnclaimed(current, status);
130
+ }
131
+ else if (current.status === 'joining') {
132
+ await this.applyJoining(current, status);
133
+ }
134
+ else {
135
+ this.applyJoined(current, status);
136
+ }
137
+ }
138
+ /** An unclaimed node that reports a finished claim is `joined`, with the party and owner the claim named. */
139
+ applyUnclaimed(current, status) {
140
+ if (!reportsClaim(status))
141
+ return;
142
+ this.store.put({
143
+ ...current,
144
+ status: 'joined',
145
+ partyId: status.node.partyId,
146
+ ownerKey: status.node.claimedBy,
147
+ peerId: current.peerId ?? status.peerId,
148
+ connected: status.node.connectionPaths.total > 0,
149
+ updatedAt: this.now().toISOString(),
150
+ });
151
+ log('hosted node %s was claimed by owner %s into cadre %s', current.id, status.node.claimedBy.slice(0, 8), status.node.partyId);
152
+ this.emit({ kind: 'claimed', id: current.id });
153
+ }
154
+ /**
155
+ * A joining node whose redemption settled: `joined` with the member that admitted it and
156
+ * the invitation's issuer as its owner, or `error` with the reason and whether Retry may
157
+ * help. A failed node's child is stopped after the write, as a give-up's is, and kept
158
+ * with its workdir for Retry; the write comes first so the exit the stop raises finds
159
+ * a record the supervisor leaves alone. Still `pending`: nothing.
160
+ */
161
+ async applyJoining(current, status) {
162
+ const invitation = status.node.invitation;
163
+ if (invitation?.state === 'accepted') {
164
+ const ownerKey = issuerOf(current);
165
+ this.store.put({
166
+ ...current,
167
+ status: 'joined',
168
+ ...(ownerKey ? { ownerKey } : {}),
169
+ ...(invitation.memberPeerId ? { memberPeerId: invitation.memberPeerId } : {}),
170
+ peerId: current.peerId ?? status.peerId,
171
+ connected: status.node.connectionPaths.total > 0,
172
+ updatedAt: this.now().toISOString(),
173
+ });
174
+ log('hosted node %s joined cadre %s at member %s', current.id, current.partyId, invitation.memberPeerId ?? '(unnamed)');
175
+ this.emit({ kind: 'joined', id: current.id });
176
+ return;
177
+ }
178
+ if (invitation?.state !== 'failed')
179
+ return;
180
+ const error = invitation.error ?? 'the invitation could not be redeemed';
181
+ this.store.put({
182
+ ...current,
183
+ status: 'error',
184
+ error,
185
+ retryable: invitation.retryable === true,
186
+ peerId: current.peerId ?? status.peerId,
187
+ updatedAt: this.now().toISOString(),
188
+ });
189
+ log('hosted node %s could not join cadre %s (%s): %s', current.id, current.partyId, invitation.retryable ? 'retryable' : 'final', error);
190
+ this.emit({ kind: 'changed', id: current.id });
191
+ await this.stopChild(current, 'its invitation failed');
192
+ }
193
+ /**
194
+ * A node that reports an owner removed it from its cadre: `error`, not retryable, its child
195
+ * stopped after the write as a failed invitation's is, and its workdir kept for Remove (or a
196
+ * claim node's Reset). The child had already stopped its node, and stays up only to report
197
+ * the removal, so stopping it loses nothing. The party and owner come from the report, for an
198
+ * `unclaimed` record whose claim this watcher never saw: its own party is the placeholder.
199
+ */
200
+ async applyRemoved(current, status) {
201
+ const partyId = status.node.partyId || current.partyId;
202
+ const ownerKey = current.ownerKey ?? status.node.claimedBy;
203
+ this.store.put({
204
+ ...current,
205
+ status: 'error',
206
+ partyId,
207
+ ...(ownerKey ? { ownerKey } : {}),
208
+ error: REMOVED_BY_OWNER_ERROR,
209
+ retryable: false,
210
+ removedByOwner: true,
211
+ updatedAt: this.now().toISOString(),
212
+ });
213
+ log('hosted node %s was removed from cadre %s by an owner', current.id, partyId);
214
+ this.emit({ kind: 'changed', id: current.id });
215
+ await this.stopChild(current, 'an owner removed it');
216
+ }
217
+ /** Stop a node's child after its record went `error`; a failed stop is logged, the record stands. */
218
+ async stopChild(current, why) {
219
+ if (!current.dockerId)
220
+ return;
221
+ try {
222
+ await this.orchestrator.stopContainer(current.dockerId);
223
+ }
224
+ catch (err) {
225
+ log('failed to stop hosted node %s after %s: %s', current.id, why, errorMessage(err));
226
+ }
227
+ }
228
+ /**
229
+ * A joined node's `connected` follows its control-connection count; published only on a
230
+ * change. Its `/status` keeps reporting the invitation for as long as the child that
231
+ * redeemed it runs, and whatever it says there is ignored: the node is a member, and the
232
+ * record says so.
233
+ */
234
+ applyJoined(current, status) {
235
+ const connected = status.node.connectionPaths.total > 0;
236
+ if (current.connected === connected)
237
+ return;
238
+ this.store.put({ ...current, connected, updatedAt: this.now().toISOString() });
239
+ log('hosted node %s is now %s', current.id, connected ? 'connected' : 'disconnected');
240
+ this.emit({ kind: 'changed', id: current.id });
241
+ }
242
+ }
243
+ /**
244
+ * The owner a joined invitation node is shown under: the issuer of its invitation. The
245
+ * record's bundle decoded at `join`, so this fails only on a hand-edited
246
+ * `hosted-nodes.json` or on a record written before invitations carried their
247
+ * `sereus-invite:1.` prefix; the node is a member either way, so the record says so without one.
248
+ */
249
+ function issuerOf(node) {
250
+ if (node.join.kind !== 'invitation')
251
+ return undefined;
252
+ try {
253
+ return decodeCadreInvitation(node.join.encoded).invite.issuerKey;
254
+ }
255
+ catch (err) {
256
+ log('hosted node %s carries an invitation that no longer decodes: %s', node.id, errorMessage(err));
257
+ return undefined;
258
+ }
259
+ }
260
+ //# sourceMappingURL=hosted-node-watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hosted-node-watcher.js","sourceRoot":"","sources":["../../src/hosted/hosted-node-watcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Hosted nodes: the host's "Join a cadre" action and what follows from it. See
3
+ * ./types.ts for the record, ./hosted-node-service.ts for the lifecycle,
4
+ * ./hosted-node-watcher.ts for following each node's `/status`, ./claim-details.ts for
5
+ * the QR payload, and ./hosted-node-supervisor.ts for the respawn invariant.
6
+ */
7
+ export { HostedNodeStore } from './hosted-node-store.js';
8
+ export { HostedNodeService, HOSTED_NODE_SPAWNING_TTL_MS, HOSTED_NODE_REAP_SWEEP_MS, } from './hosted-node-service.js';
9
+ export type { HostedNodeOrchestrator, HostedNodeServiceOptions, JoinOptions, RespawnOptions, RespawnResult, } from './hosted-node-service.js';
10
+ export type { ClaimDetails, HostedNodeAddressSource } from './claim-details.js';
11
+ export { HOSTED_NODE_CLAIM_POLL_MS, HOSTED_NODE_CONNECTED_POLL_MS, } from './hosted-node-watcher.js';
12
+ export { HostedNodeSupervisor, HOSTED_NODE_RESPAWN_BACKOFF_BASE_MS, HOSTED_NODE_RESPAWN_BACKOFF_MAX_MS, HOSTED_NODE_RESPAWN_MAX_ATTEMPTS, HOSTED_NODE_RESPAWN_HEALTHY_MS, HOSTED_NODE_RESPAWN_SWEEP_MS, } from './hosted-node-supervisor.js';
13
+ export type { HostedNodeSupervisorOptions, SupervisedOrchestrator, } from './hosted-node-supervisor.js';
14
+ export { PLACEHOLDER_PARTY } from './node-status.js';
15
+ export { HostedNodeError } from './types.js';
16
+ export type { HostedNode, HostedNodeChange, HostedNodeChangeKind, HostedNodeChangeListener, HostedNodeErrorCode, HostedNodeClaimJoin, HostedNodeFile, HostedNodeInvitationJoin, HostedNodeJoin, HostedNodeJoinView, HostedNodeStatus, HostedNodeView, } from './types.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Hosted nodes: the host's "Join a cadre" action and what follows from it. See
3
+ * ./types.ts for the record, ./hosted-node-service.ts for the lifecycle,
4
+ * ./hosted-node-watcher.ts for following each node's `/status`, ./claim-details.ts for
5
+ * the QR payload, and ./hosted-node-supervisor.ts for the respawn invariant.
6
+ */
7
+ export { HostedNodeStore } from './hosted-node-store.js';
8
+ export { HostedNodeService, HOSTED_NODE_SPAWNING_TTL_MS, HOSTED_NODE_REAP_SWEEP_MS, } from './hosted-node-service.js';
9
+ export { HOSTED_NODE_CLAIM_POLL_MS, HOSTED_NODE_CONNECTED_POLL_MS, } from './hosted-node-watcher.js';
10
+ export { HostedNodeSupervisor, HOSTED_NODE_RESPAWN_BACKOFF_BASE_MS, HOSTED_NODE_RESPAWN_BACKOFF_MAX_MS, HOSTED_NODE_RESPAWN_MAX_ATTEMPTS, HOSTED_NODE_RESPAWN_HEALTHY_MS, HOSTED_NODE_RESPAWN_SWEEP_MS, } from './hosted-node-supervisor.js';
11
+ export { PLACEHOLDER_PARTY } from './node-status.js';
12
+ export { HostedNodeError } from './types.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hosted/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EACL,iBAAiB,EACjB,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,0BAA0B,CAAC;AASlC,OAAO,EACL,yBAAyB,EACzB,6BAA6B,GAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,mCAAmC,EACnC,kCAAkC,EAClC,gCAAgC,EAChC,8BAA8B,EAC9B,4BAA4B,GAC7B,MAAM,6BAA6B,CAAC;AAKrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Reading a hosted child's `/status` — the one window the host has onto a node it
3
+ * runs: its peer id and addresses before the claim, the claim itself, the party
4
+ * and the owner key after it, and its removal by an owner. Shared by
5
+ * `HostedNodeService.claimDetails` and the status watcher.
6
+ */
7
+ import type { HealthStatus } from '@serfab/cadre-cli';
8
+ /** The party an unclaimed child's config names; the claim replaces it. */
9
+ export declare const PLACEHOLDER_PARTY = "unclaimed";
10
+ /** The `/status` URL for a spawn's `/health` URL (same server and port). */
11
+ export declare function statusUrlFor(healthEndpoint: string): string;
12
+ /** A `/status` answer from a node that has its peer identity. */
13
+ export type NodeStatus = HealthStatus & {
14
+ peerId: string;
15
+ };
16
+ /**
17
+ * A `/status` answer from a child an owner removed from its cadre. The child stopped its node
18
+ * and stays up only to report this, so it has no peer identity.
19
+ */
20
+ export type RemovedNodeStatus = HealthStatus & {
21
+ node: {
22
+ removed: NonNullable<HealthStatus['node']['removed']>;
23
+ };
24
+ };
25
+ /**
26
+ * The child's `/status`, fresh every call. Throws `node_unavailable` while the child
27
+ * does not answer, answers with an error, or has no peer identity yet — every one of
28
+ * which a caller retries.
29
+ */
30
+ export declare function readNodeStatus(statusEndpoint: string): Promise<NodeStatus>;
31
+ /**
32
+ * {@link readNodeStatus} for the watcher, which also has to hear a removal: a child reporting
33
+ * one is answered as it is rather than refused for having no peer identity.
34
+ */
35
+ export declare function readWatchedNodeStatus(statusEndpoint: string): Promise<NodeStatus | RemovedNodeStatus>;
36
+ /** Whether the child reports that an owner removed it from its cadre. */
37
+ export declare function reportsRemoval(status: HealthStatus): status is RemovedNodeStatus;
38
+ /**
39
+ * Whether `/status` reports a finished claim: a claim on record AND the party it
40
+ * named. `claim` flips to `claimed` the moment the node records the claim, but the
41
+ * node then restarts in-process into the claimed party, and until that restart
42
+ * finishes `partyId` is still the placeholder (or empty while the node object is
43
+ * being rebuilt). Writing `joined` on the flag alone would record the placeholder
44
+ * as the node's cadre.
45
+ */
46
+ export declare function reportsClaim(status: NodeStatus): status is NodeStatus & {
47
+ node: {
48
+ claimedBy: string;
49
+ };
50
+ };
51
+ export declare function errorMessage(err: unknown): string;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Reading a hosted child's `/status` — the one window the host has onto a node it
3
+ * runs: its peer id and addresses before the claim, the claim itself, the party
4
+ * and the owner key after it, and its removal by an owner. Shared by
5
+ * `HostedNodeService.claimDetails` and the status watcher.
6
+ */
7
+ import { HostedNodeError } from './types.js';
8
+ /** The party an unclaimed child's config names; the claim replaces it. */
9
+ export const PLACEHOLDER_PARTY = 'unclaimed';
10
+ /** The `/status` URL for a spawn's `/health` URL (same server and port). */
11
+ export function statusUrlFor(healthEndpoint) {
12
+ const url = new URL(healthEndpoint);
13
+ url.pathname = '/status';
14
+ return url.toString();
15
+ }
16
+ /**
17
+ * The child's `/status`, fresh every call. Throws `node_unavailable` while the child
18
+ * does not answer, answers with an error, or has no peer identity yet — every one of
19
+ * which a caller retries.
20
+ */
21
+ export async function readNodeStatus(statusEndpoint) {
22
+ return requirePeerIdentity(await fetchNodeStatus(statusEndpoint));
23
+ }
24
+ /**
25
+ * {@link readNodeStatus} for the watcher, which also has to hear a removal: a child reporting
26
+ * one is answered as it is rather than refused for having no peer identity.
27
+ */
28
+ export async function readWatchedNodeStatus(statusEndpoint) {
29
+ const status = await fetchNodeStatus(statusEndpoint);
30
+ return reportsRemoval(status) ? status : requirePeerIdentity(status);
31
+ }
32
+ /** Whether the child reports that an owner removed it from its cadre. */
33
+ export function reportsRemoval(status) {
34
+ return Boolean(status.node.removed);
35
+ }
36
+ async function fetchNodeStatus(statusEndpoint) {
37
+ let res;
38
+ try {
39
+ res = await fetch(statusEndpoint);
40
+ }
41
+ catch (err) {
42
+ throw new HostedNodeError('node_unavailable', `Hosted node unreachable: ${errorMessage(err)}`);
43
+ }
44
+ if (!res.ok) {
45
+ throw new HostedNodeError('node_unavailable', `Hosted node /status returned ${res.status}`);
46
+ }
47
+ try {
48
+ return (await res.json());
49
+ }
50
+ catch (err) {
51
+ throw new HostedNodeError('node_unavailable', `Hosted node /status is not JSON: ${errorMessage(err)}`);
52
+ }
53
+ }
54
+ function requirePeerIdentity(status) {
55
+ if (!status.peerId || !status.multiaddrs?.length) {
56
+ throw new HostedNodeError('node_unavailable', 'Hosted node has no peer identity yet');
57
+ }
58
+ return status;
59
+ }
60
+ /**
61
+ * Whether `/status` reports a finished claim: a claim on record AND the party it
62
+ * named. `claim` flips to `claimed` the moment the node records the claim, but the
63
+ * node then restarts in-process into the claimed party, and until that restart
64
+ * finishes `partyId` is still the placeholder (or empty while the node object is
65
+ * being rebuilt). Writing `joined` on the flag alone would record the placeholder
66
+ * as the node's cadre.
67
+ */
68
+ export function reportsClaim(status) {
69
+ return status.node.claim === 'claimed'
70
+ && status.node.claimedBy !== undefined
71
+ && status.node.partyId !== ''
72
+ && status.node.partyId !== PLACEHOLDER_PARTY;
73
+ }
74
+ export function errorMessage(err) {
75
+ return err instanceof Error ? err.message : String(err);
76
+ }
77
+ //# sourceMappingURL=node-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-status.js","sourceRoot":"","sources":["../../src/hosted/node-status.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAE7C,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,cAAsB;IACjD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,CAAC;IACpC,GAAG,CAAC,QAAQ,GAAG,SAAS,CAAC;IACzB,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC;AAWD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,cAAsB;IACzD,OAAO,mBAAmB,CAAC,MAAM,eAAe,CAAC,cAAc,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,cAAsB;IAChE,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,cAAc,CAAC,CAAC;IACrD,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;AACvE,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,cAAc,CAAC,MAAoB;IACjD,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,cAAsB;IACnD,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,eAAe,CAAC,kBAAkB,EAAE,4BAA4B,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjG,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,eAAe,CAAC,kBAAkB,EAAE,gCAAgC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAiB,CAAC;IAC5C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,eAAe,CAAC,kBAAkB,EAAE,oCAAoC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACzG,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,MAAoB;IAC/C,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;QACjD,MAAM,IAAI,eAAe,CAAC,kBAAkB,EAAE,sCAAsC,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,MAAoB,CAAC;AAC9B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAAkB;IAC7C,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS;WACjC,MAAM,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS;WACnC,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,EAAE;WAC1B,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,iBAAiB,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Hosted-node types for cadre-host.
3
+ *
4
+ * A **hosted node** is a child process the host runs for some cadre. It gets into its
5
+ * cadre one of two ways: the host starts it waiting to be claimed and shows its addresses
6
+ * and claim secret as a QR code, which the owner's phone scans to claim the node
7
+ * (`CadreNode.claimNode`); or the host starts it with a cadre invitation the owner's app
8
+ * minted, which the node redeems at a member of the cadre. Either way the host holds no
9
+ * owner key and is not an owner of any cadre its nodes serve.
10
+ *
11
+ * Records live in `<dataDir>/hosted-nodes.json` (`HostedNodeStore`). A record's id is also
12
+ * its orchestrator container id and the name of its working directory.
13
+ */
14
+ /**
15
+ * Lifecycle of one hosted node:
16
+ * spawning → the record is written, the child not yet spawned.
17
+ * unclaimed → (claim) the child is up with a claim secret, waiting for a phone to claim it.
18
+ * joining → (invitation) the child is up, its redemption of the invitation not yet settled.
19
+ * joined → in its cadre: claimed, or the invitation accepted by a member.
20
+ * error → the supervisor gave up on it, the stuck-`spawning` reap found it, the
21
+ * invitation was refused or no member could be reached (`retryable` says which),
22
+ * or an owner removed it from its cadre (`removedByOwner`).
23
+ *
24
+ * Removal deletes the row; there is no terminal "removed" status, and a node an owner removed
25
+ * from its cadre is, to the host, a node that failed and waits for the admin. An `error`
26
+ * record keeps its working directory (its identity key) until it is removed or reset.
27
+ */
28
+ export type HostedNodeStatus = 'spawning' | 'unclaimed' | 'joining' | 'joined' | 'error';
29
+ /**
30
+ * The node was started with a claim secret and waits for a phone to present it.
31
+ */
32
+ export interface HostedNodeClaimJoin {
33
+ kind: 'claim';
34
+ /** The node's one-time claim secret, base64url. Only `claimDetails` ever returns it. */
35
+ secret: string;
36
+ }
37
+ /**
38
+ * The node was started with a cadre invitation and redeems it at a member of the cadre.
39
+ * Kept for a respawn while `joining`: the child redeems again, and a member answers a node
40
+ * that already got in as accepted.
41
+ */
42
+ export interface HostedNodeInvitationJoin {
43
+ kind: 'invitation';
44
+ /**
45
+ * The bundle as the owner's app encoded it (`encodeCadreInvitation`). It carries the
46
+ * invitation's private key, so it is redacted like the claim secret.
47
+ *
48
+ * NOTE: kept on disk after the node joins, though nothing reads it then (a joined node is
49
+ * respawned without it). An invitation with uses left admits whoever reads it; if
50
+ * multi-use invitations get pasted into hosts, drop it at the watcher's `joined` write.
51
+ */
52
+ encoded: string;
53
+ }
54
+ /** How a node gets into its cadre. */
55
+ export type HostedNodeJoin = HostedNodeClaimJoin | HostedNodeInvitationJoin;
56
+ /** One hosted node, as persisted in `hosted-nodes.json`. */
57
+ export interface HostedNode {
58
+ /** `hn_<base64url of 12 random bytes>` — also the orchestrator container id and workdir name. */
59
+ id: string;
60
+ join: HostedNodeJoin;
61
+ /**
62
+ * A claim node: `unclaimed` (the placeholder party the child's config names) until the
63
+ * claim reports the party; the claimant's party after. An invitation node: the
64
+ * invitation's party from the start.
65
+ */
66
+ partyId: string;
67
+ /** Hosted nodes run the `storage` profile: they participate in strands and are dialable. */
68
+ profile: 'storage';
69
+ status: HostedNodeStatus;
70
+ /** Orchestrator handle (opaque `pid:token`), set once the child is spawned. */
71
+ dockerId?: string;
72
+ /** The child's loopback `/status` URL, from the spawn result. */
73
+ statusEndpoint?: string;
74
+ /** Read once from `/status`; the claim details need it before the claim. */
75
+ peerId?: string;
76
+ /**
77
+ * Set when the node joins: `/status.node.claimedBy` for a claim, the invitation's issuer
78
+ * key for an invitation. The UI shows its first 8 characters.
79
+ */
80
+ ownerKey?: string;
81
+ /** An invitation node, once joined: the member that admitted it, when its address named one. */
82
+ memberPeerId?: string;
83
+ /** From the last status poll: the node holds at least one control connection. */
84
+ connected?: boolean;
85
+ /**
86
+ * Respawn bookkeeping, owned by the supervisor's backoff. Absent until the first
87
+ * respawn attempt. Not secret — it rides along in `HostedNodeView`.
88
+ */
89
+ respawn?: {
90
+ attempts: number;
91
+ lastAttemptAt: string;
92
+ };
93
+ createdAt: string;
94
+ updatedAt: string;
95
+ /** Failure detail when `status === 'error'`. */
96
+ error?: string;
97
+ /**
98
+ * Set with `error` when an invitation failed: whether Retry may start the node again with
99
+ * the same invitation (no member could be reached), or the invitation itself was refused.
100
+ * False on a node an owner removed; absent on every other `error`.
101
+ */
102
+ retryable?: boolean;
103
+ /**
104
+ * Set with `error` when the node reported that an owner removed it from its cadre, so the
105
+ * UI words it as a removal rather than a failure to join or to stay up.
106
+ */
107
+ removedByOwner?: true;
108
+ }
109
+ /** `join` without its secret: what `list`, `get`, the routes and the events carry. */
110
+ export type HostedNodeJoinView = {
111
+ kind: HostedNodeJoin['kind'];
112
+ };
113
+ /** The wire shape of a hosted node: the record with the claim secret or the invitation stripped. */
114
+ export interface HostedNodeView extends Omit<HostedNode, 'join'> {
115
+ join: HostedNodeJoinView;
116
+ }
117
+ /** On-disk shape of `hosted-nodes.json`. Keyed by node id. */
118
+ export interface HostedNodeFile {
119
+ version: 1;
120
+ nodes: Record<string, Omit<HostedNode, 'id'>>;
121
+ }
122
+ /** Error codes thrown by the hosted-node service, mapped to HTTP status by `server/error-handler.ts`. */
123
+ export type HostedNodeErrorCode = 'not_found' | 'invalid_state' | 'invalid_request' | 'node_unavailable' | 'orchestrator_error' | 'storage_error';
124
+ /** Typed hosted-node error carrying a stable `code` for HTTP mapping. */
125
+ export declare class HostedNodeError extends Error {
126
+ readonly code: HostedNodeErrorCode;
127
+ constructor(code: HostedNodeErrorCode, message: string);
128
+ }
129
+ /**
130
+ * What changed about a hosted node: `added` (a join started the node), `claimed` (the
131
+ * watcher saw the claim), `joined` (the watcher saw a member accept the invitation),
132
+ * `removed`, or `changed` (anything else: a respawn, a liveness change, a refused
133
+ * invitation, a retry, a give-up). The server publishes each as the `hosted-nodes-changed`
134
+ * SSE event.
135
+ */
136
+ export type HostedNodeChangeKind = 'added' | 'claimed' | 'joined' | 'removed' | 'changed';
137
+ export interface HostedNodeChange {
138
+ kind: HostedNodeChangeKind;
139
+ id: string;
140
+ }
141
+ export type HostedNodeChangeListener = (change: HostedNodeChange) => void;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Hosted-node types for cadre-host.
3
+ *
4
+ * A **hosted node** is a child process the host runs for some cadre. It gets into its
5
+ * cadre one of two ways: the host starts it waiting to be claimed and shows its addresses
6
+ * and claim secret as a QR code, which the owner's phone scans to claim the node
7
+ * (`CadreNode.claimNode`); or the host starts it with a cadre invitation the owner's app
8
+ * minted, which the node redeems at a member of the cadre. Either way the host holds no
9
+ * owner key and is not an owner of any cadre its nodes serve.
10
+ *
11
+ * Records live in `<dataDir>/hosted-nodes.json` (`HostedNodeStore`). A record's id is also
12
+ * its orchestrator container id and the name of its working directory.
13
+ */
14
+ /** Typed hosted-node error carrying a stable `code` for HTTP mapping. */
15
+ export class HostedNodeError extends Error {
16
+ constructor(code, message) {
17
+ super(message);
18
+ this.name = 'HostedNodeError';
19
+ this.code = code;
20
+ }
21
+ }
22
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/hosted/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA0HH,yEAAyE;AACzE,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAGxC,YAAY,IAAyB,EAAE,OAAe;QACpD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}