@serfab/cadre-host 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/README.md +332 -423
  2. package/dist/bin/host.d.ts +6 -3
  3. package/dist/bin/host.js +301 -505
  4. package/dist/bin/host.js.map +1 -1
  5. package/dist/bin/join-output.d.ts +56 -0
  6. package/dist/bin/join-output.js +92 -0
  7. package/dist/bin/join-output.js.map +1 -0
  8. package/dist/bin/nat-output.d.ts +49 -0
  9. package/dist/bin/nat-output.js +127 -0
  10. package/dist/bin/nat-output.js.map +1 -0
  11. package/dist/hosted/claim-details.d.ts +44 -0
  12. package/dist/hosted/claim-details.js +66 -0
  13. package/dist/hosted/claim-details.js.map +1 -0
  14. package/dist/hosted/hosted-node-service.d.ts +318 -0
  15. package/dist/hosted/hosted-node-service.js +666 -0
  16. package/dist/hosted/hosted-node-service.js.map +1 -0
  17. package/dist/hosted/hosted-node-store.d.ts +40 -0
  18. package/dist/hosted/hosted-node-store.js +126 -0
  19. package/dist/hosted/hosted-node-store.js.map +1 -0
  20. package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
  21. package/dist/hosted/hosted-node-supervisor.js +396 -0
  22. package/dist/hosted/hosted-node-supervisor.js.map +1 -0
  23. package/dist/hosted/hosted-node-watcher.d.ts +83 -0
  24. package/dist/hosted/hosted-node-watcher.js +224 -0
  25. package/dist/hosted/hosted-node-watcher.js.map +1 -0
  26. package/dist/hosted/index.d.ts +16 -0
  27. package/dist/hosted/index.js +13 -0
  28. package/dist/hosted/index.js.map +1 -0
  29. package/dist/hosted/node-status.d.ts +35 -0
  30. package/dist/hosted/node-status.js +61 -0
  31. package/dist/hosted/node-status.js.map +1 -0
  32. package/dist/hosted/types.d.ts +134 -0
  33. package/dist/hosted/types.js +22 -0
  34. package/dist/hosted/types.js.map +1 -0
  35. package/dist/index.d.ts +12 -24
  36. package/dist/index.js +9 -19
  37. package/dist/index.js.map +1 -1
  38. package/dist/installer/config.d.ts +4 -34
  39. package/dist/installer/config.js +3 -18
  40. package/dist/installer/config.js.map +1 -1
  41. package/dist/installer/index.d.ts +7 -19
  42. package/dist/installer/index.js +14 -69
  43. package/dist/installer/index.js.map +1 -1
  44. package/dist/installer/paths.d.ts +0 -2
  45. package/dist/installer/paths.js +0 -4
  46. package/dist/installer/paths.js.map +1 -1
  47. package/dist/installer/wizard.d.ts +0 -7
  48. package/dist/installer/wizard.js +1 -8
  49. package/dist/installer/wizard.js.map +1 -1
  50. package/dist/nat/address-resolver.d.ts +18 -17
  51. package/dist/nat/address-resolver.js +79 -25
  52. package/dist/nat/address-resolver.js.map +1 -1
  53. package/dist/nat/address-watch.d.ts +37 -0
  54. package/dist/nat/address-watch.js +84 -0
  55. package/dist/nat/address-watch.js.map +1 -0
  56. package/dist/nat/ddns/duckdns.d.ts +2 -1
  57. package/dist/nat/ddns/duckdns.js +5 -1
  58. package/dist/nat/ddns/duckdns.js.map +1 -1
  59. package/dist/nat/ddns/index.d.ts +6 -0
  60. package/dist/nat/ddns/index.js.map +1 -1
  61. package/dist/nat/index.d.ts +12 -9
  62. package/dist/nat/index.js +6 -5
  63. package/dist/nat/index.js.map +1 -1
  64. package/dist/nat/nat-service.d.ts +203 -77
  65. package/dist/nat/nat-service.js +602 -237
  66. package/dist/nat/nat-service.js.map +1 -1
  67. package/dist/nat/nat-store.d.ts +12 -5
  68. package/dist/nat/nat-store.js +114 -30
  69. package/dist/nat/nat-store.js.map +1 -1
  70. package/dist/nat/port-mapper.d.ts +83 -79
  71. package/dist/nat/port-mapper.js +148 -189
  72. package/dist/nat/port-mapper.js.map +1 -1
  73. package/dist/nat/reachability.d.ts +31 -15
  74. package/dist/nat/reachability.js +97 -22
  75. package/dist/nat/reachability.js.map +1 -1
  76. package/dist/nat/types.d.ts +78 -27
  77. package/dist/nat/types.js +8 -5
  78. package/dist/nat/types.js.map +1 -1
  79. package/dist/orchestrator/host-process-orchestrator.d.ts +29 -71
  80. package/dist/orchestrator/host-process-orchestrator.js +78 -218
  81. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  82. package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
  83. package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
  84. package/dist/orchestrator/identity-file.js.map +1 -0
  85. package/dist/orchestrator/index.d.ts +2 -3
  86. package/dist/orchestrator/index.js +1 -1
  87. package/dist/orchestrator/index.js.map +1 -1
  88. package/dist/orchestrator/node-identity.d.ts +6 -6
  89. package/dist/orchestrator/node-identity.js +7 -7
  90. package/dist/orchestrator/node-identity.js.map +1 -1
  91. package/dist/orchestrator/port-allocator.d.ts +5 -6
  92. package/dist/orchestrator/port-allocator.js +9 -11
  93. package/dist/orchestrator/port-allocator.js.map +1 -1
  94. package/dist/orchestrator/state-store.d.ts +3 -5
  95. package/dist/orchestrator/state-store.js +2 -10
  96. package/dist/orchestrator/state-store.js.map +1 -1
  97. package/dist/orchestrator/types.d.ts +33 -22
  98. package/dist/orchestrator/types.js.map +1 -1
  99. package/dist/server/error-handler.d.ts +5 -25
  100. package/dist/server/error-handler.js +11 -74
  101. package/dist/server/error-handler.js.map +1 -1
  102. package/dist/server/events/types.d.ts +7 -11
  103. package/dist/server/events/types.js +2 -1
  104. package/dist/server/events/types.js.map +1 -1
  105. package/dist/server/index.d.ts +11 -37
  106. package/dist/server/index.js +30 -48
  107. package/dist/server/index.js.map +1 -1
  108. package/dist/server/routes/hosted-nodes.d.ts +28 -0
  109. package/dist/server/routes/hosted-nodes.js +68 -0
  110. package/dist/server/routes/hosted-nodes.js.map +1 -0
  111. package/dist/server/routes/nat.d.ts +4 -1
  112. package/dist/server/routes/nat.js +13 -2
  113. package/dist/server/routes/nat.js.map +1 -1
  114. package/dist/server/routes/nodes.d.ts +6 -20
  115. package/dist/server/routes/nodes.js +7 -86
  116. package/dist/server/routes/nodes.js.map +1 -1
  117. package/dist/server/routes/settings.d.ts +2 -7
  118. package/dist/server/routes/settings.js +3 -7
  119. package/dist/server/routes/settings.js.map +1 -1
  120. package/dist/server/routes/status.d.ts +2 -25
  121. package/dist/server/routes/status.js +1 -11
  122. package/dist/server/routes/status.js.map +1 -1
  123. package/dist/server/server.js +8 -8
  124. package/dist/server/settings-store.d.ts +2 -2
  125. package/dist/server/settings-store.js +2 -2
  126. package/dist/server/static.js +1 -2
  127. package/dist/server/static.js.map +1 -1
  128. package/dist/ui/assets/index-4FWuU8ye.js +19 -0
  129. package/dist/ui/assets/index-4FWuU8ye.js.map +1 -0
  130. package/dist/ui/assets/index-Di9Q4d01.css +1 -0
  131. package/dist/ui/index.html +2 -2
  132. package/dist/update/index.d.ts +2 -2
  133. package/dist/update/index.js +2 -2
  134. package/dist/update/store.d.ts +1 -1
  135. package/dist/update/store.js +1 -1
  136. package/dist/update/types.d.ts +1 -1
  137. package/dist/update/types.js +1 -1
  138. package/package.json +5 -6
  139. package/src/__tests__/cli-nat.smoke.test.ts +219 -212
  140. package/src/__tests__/cli.smoke.test.ts +1 -1
  141. package/src/__tests__/global-setup.ts +2 -3
  142. package/src/__tests__/orchestrator-claim-secret.test.ts +112 -0
  143. package/src/__tests__/orchestrator-handle-contract.ts +134 -134
  144. package/src/__tests__/orchestrator-node-identity.test.ts +182 -203
  145. package/src/__tests__/orchestrator-ports.test.ts +124 -143
  146. package/src/__tests__/orchestrator-push.test.ts +138 -146
  147. package/src/__tests__/orchestrator.test.ts +1019 -1063
  148. package/src/bin/host.ts +368 -553
  149. package/src/bin/join-output.ts +120 -0
  150. package/src/bin/nat-output.ts +177 -0
  151. package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +173 -173
  152. package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +265 -243
  153. package/src/hosted/__tests__/hosted-node-service.test.ts +609 -0
  154. package/src/hosted/__tests__/hosted-node-supervisor.test.ts +474 -0
  155. package/src/hosted/__tests__/test-invitation.ts +33 -0
  156. package/src/hosted/claim-details.ts +96 -0
  157. package/src/hosted/hosted-node-service.ts +795 -0
  158. package/src/hosted/hosted-node-store.ts +144 -0
  159. package/src/hosted/hosted-node-supervisor.ts +439 -0
  160. package/src/hosted/hosted-node-watcher.ts +250 -0
  161. package/src/hosted/index.ts +53 -0
  162. package/src/hosted/node-status.ts +71 -0
  163. package/src/hosted/types.ts +153 -0
  164. package/src/index.ts +72 -91
  165. package/src/installer/__tests__/config.test.ts +15 -40
  166. package/src/installer/__tests__/installer.smoke.test.ts +5 -86
  167. package/src/installer/__tests__/wizard.test.ts +56 -64
  168. package/src/installer/config.ts +135 -175
  169. package/src/installer/index.ts +15 -83
  170. package/src/installer/paths.ts +104 -109
  171. package/src/installer/wizard.ts +87 -103
  172. package/src/nat/__tests__/address-resolver.test.ts +107 -82
  173. package/src/nat/__tests__/nat-service.test.ts +433 -408
  174. package/src/nat/__tests__/nat-store.test.ts +127 -93
  175. package/src/nat/__tests__/port-mapper.test.ts +30 -191
  176. package/src/nat/__tests__/reachability.test.ts +96 -13
  177. package/src/nat/address-resolver.ts +94 -52
  178. package/src/nat/address-watch.ts +92 -0
  179. package/src/nat/ddns/duckdns.ts +5 -1
  180. package/src/nat/ddns/index.ts +6 -0
  181. package/src/nat/index.ts +87 -71
  182. package/src/nat/nat-service.ts +889 -501
  183. package/src/nat/nat-store.ts +237 -154
  184. package/src/nat/port-mapper.ts +241 -329
  185. package/src/nat/reachability.ts +134 -25
  186. package/src/nat/types.ts +209 -159
  187. package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
  188. package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
  189. package/src/orchestrator/host-process-orchestrator.ts +81 -243
  190. package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
  191. package/src/orchestrator/index.ts +9 -10
  192. package/src/orchestrator/node-identity.ts +41 -41
  193. package/src/orchestrator/port-allocator.ts +71 -73
  194. package/src/orchestrator/state-store.ts +76 -90
  195. package/src/orchestrator/types.ts +34 -23
  196. package/src/server/__tests__/bus.test.ts +38 -38
  197. package/src/server/__tests__/error-handler.test.ts +81 -118
  198. package/src/server/__tests__/fakes.ts +40 -44
  199. package/src/server/__tests__/hosted-nodes-route.test.ts +160 -0
  200. package/src/server/__tests__/nodes-route.test.ts +114 -283
  201. package/src/server/__tests__/publishers.test.ts +193 -180
  202. package/src/server/__tests__/server.smoke.test.ts +134 -223
  203. package/src/server/__tests__/settings-route.test.ts +179 -195
  204. package/src/server/__tests__/sse-route.test.ts +119 -119
  205. package/src/server/__tests__/status-route.test.ts +82 -121
  206. package/src/server/error-handler.ts +112 -178
  207. package/src/server/events/types.ts +20 -24
  208. package/src/server/index.ts +223 -267
  209. package/src/server/routes/hosted-nodes.ts +83 -0
  210. package/src/server/routes/nat.ts +75 -63
  211. package/src/server/routes/nodes.ts +96 -188
  212. package/src/server/routes/settings.ts +115 -123
  213. package/src/server/routes/status.ts +102 -134
  214. package/src/server/server.ts +8 -8
  215. package/src/server/settings-store.ts +2 -2
  216. package/src/server/static.ts +1 -2
  217. package/src/update/index.ts +2 -2
  218. package/src/update/store.ts +1 -1
  219. package/src/update/types.ts +1 -1
  220. package/dist/auth/duration.d.ts +0 -1
  221. package/dist/auth/duration.js +0 -32
  222. package/dist/auth/duration.js.map +0 -1
  223. package/dist/auth/index.d.ts +0 -11
  224. package/dist/auth/index.js +0 -9
  225. package/dist/auth/index.js.map +0 -1
  226. package/dist/auth/self-label.d.ts +0 -36
  227. package/dist/auth/self-label.js +0 -57
  228. package/dist/auth/self-label.js.map +0 -1
  229. package/dist/auth/trust-circle-store.d.ts +0 -26
  230. package/dist/auth/trust-circle-store.js +0 -129
  231. package/dist/auth/trust-circle-store.js.map +0 -1
  232. package/dist/auth/trust-circle.d.ts +0 -156
  233. package/dist/auth/trust-circle.js +0 -307
  234. package/dist/auth/trust-circle.js.map +0 -1
  235. package/dist/auth/types.d.ts +0 -84
  236. package/dist/auth/types.js +0 -24
  237. package/dist/auth/types.js.map +0 -1
  238. package/dist/donation/donation-service.d.ts +0 -389
  239. package/dist/donation/donation-service.js +0 -828
  240. package/dist/donation/donation-service.js.map +0 -1
  241. package/dist/donation/donation-store.d.ts +0 -51
  242. package/dist/donation/donation-store.js +0 -152
  243. package/dist/donation/donation-store.js.map +0 -1
  244. package/dist/donation/donation-supervisor.js +0 -364
  245. package/dist/donation/donation-supervisor.js.map +0 -1
  246. package/dist/donation/grant-service.d.ts +0 -80
  247. package/dist/donation/grant-service.js +0 -210
  248. package/dist/donation/grant-service.js.map +0 -1
  249. package/dist/donation/grant-store.d.ts +0 -38
  250. package/dist/donation/grant-store.js +0 -135
  251. package/dist/donation/grant-store.js.map +0 -1
  252. package/dist/donation/index.d.ts +0 -20
  253. package/dist/donation/index.js +0 -17
  254. package/dist/donation/index.js.map +0 -1
  255. package/dist/donation/types.d.ts +0 -224
  256. package/dist/donation/types.js +0 -40
  257. package/dist/donation/types.js.map +0 -1
  258. package/dist/installer/identity.js.map +0 -1
  259. package/dist/owner/index.d.ts +0 -11
  260. package/dist/owner/index.js +0 -11
  261. package/dist/owner/index.js.map +0 -1
  262. package/dist/owner/owner-node-client.d.ts +0 -110
  263. package/dist/owner/owner-node-client.js +0 -184
  264. package/dist/owner/owner-node-client.js.map +0 -1
  265. package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
  266. package/dist/server/routes/bootstrap-node-validation.js +0 -197
  267. package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
  268. package/dist/server/routes/grants-admin.d.ts +0 -29
  269. package/dist/server/routes/grants-admin.js +0 -58
  270. package/dist/server/routes/grants-admin.js.map +0 -1
  271. package/dist/server/routes/grants.d.ts +0 -28
  272. package/dist/server/routes/grants.js +0 -165
  273. package/dist/server/routes/grants.js.map +0 -1
  274. package/dist/server/routes/provision-request-validation.d.ts +0 -45
  275. package/dist/server/routes/provision-request-validation.js +0 -97
  276. package/dist/server/routes/provision-request-validation.js.map +0 -1
  277. package/dist/server/routes/strands.d.ts +0 -20
  278. package/dist/server/routes/strands.js +0 -38
  279. package/dist/server/routes/strands.js.map +0 -1
  280. package/dist/server/routes/trust-circle.d.ts +0 -17
  281. package/dist/server/routes/trust-circle.js +0 -35
  282. package/dist/server/routes/trust-circle.js.map +0 -1
  283. package/dist/strands/index.d.ts +0 -7
  284. package/dist/strands/index.js +0 -6
  285. package/dist/strands/index.js.map +0 -1
  286. package/dist/strands/strand-service.d.ts +0 -50
  287. package/dist/strands/strand-service.js +0 -103
  288. package/dist/strands/strand-service.js.map +0 -1
  289. package/dist/strands/types.d.ts +0 -93
  290. package/dist/strands/types.js +0 -23
  291. package/dist/strands/types.js.map +0 -1
  292. package/dist/ui/assets/index-C6nELNfC.js +0 -23
  293. package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
  294. package/dist/ui/assets/index-TtIYSeZO.css +0 -1
  295. package/src/__tests__/cli-invite.smoke.test.ts +0 -192
  296. package/src/__tests__/host-owner.smoke.test.ts +0 -123
  297. package/src/__tests__/orchestrator-owner.test.ts +0 -234
  298. package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
  299. package/src/auth/__tests__/duration.test.ts +0 -28
  300. package/src/auth/__tests__/self-label.test.ts +0 -85
  301. package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
  302. package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
  303. package/src/auth/__tests__/trust-circle.test.ts +0 -445
  304. package/src/auth/duration.ts +0 -33
  305. package/src/auth/index.ts +0 -21
  306. package/src/auth/self-label.ts +0 -74
  307. package/src/auth/trust-circle-store.ts +0 -155
  308. package/src/auth/trust-circle.ts +0 -395
  309. package/src/auth/types.ts +0 -99
  310. package/src/donation/__tests__/donation-service.test.ts +0 -1193
  311. package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
  312. package/src/donation/__tests__/grant-service.test.ts +0 -213
  313. package/src/donation/__tests__/grant-store.test.ts +0 -113
  314. package/src/donation/donation-service.ts +0 -1033
  315. package/src/donation/donation-store.ts +0 -176
  316. package/src/donation/donation-supervisor.ts +0 -411
  317. package/src/donation/grant-service.ts +0 -252
  318. package/src/donation/grant-store.ts +0 -152
  319. package/src/donation/index.ts +0 -65
  320. package/src/donation/types.ts +0 -260
  321. package/src/owner/__tests__/owner-node-client.test.ts +0 -194
  322. package/src/owner/index.ts +0 -12
  323. package/src/owner/owner-node-client.ts +0 -259
  324. package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
  325. package/src/server/__tests__/grants-admin-route.test.ts +0 -171
  326. package/src/server/__tests__/grants-route.test.ts +0 -394
  327. package/src/server/__tests__/strands-route.test.ts +0 -234
  328. package/src/server/routes/bootstrap-node-validation.ts +0 -206
  329. package/src/server/routes/grants-admin.ts +0 -74
  330. package/src/server/routes/grants.ts +0 -193
  331. package/src/server/routes/provision-request-validation.ts +0 -105
  332. package/src/server/routes/strands.ts +0 -54
  333. package/src/server/routes/trust-circle.ts +0 -49
  334. package/src/strands/__tests__/strand-service.test.ts +0 -165
  335. package/src/strands/index.ts +0 -14
  336. package/src/strands/strand-service.ts +0 -135
  337. package/src/strands/types.ts +0 -102
@@ -1,389 +0,0 @@
1
- import type { Orchestrator } from '@serfab/cadre-provider';
2
- import type { DonationStore } from './donation-store.js';
3
- import type { DonationStatus, DonationView, GrantValidator } from './types.js';
4
- /**
5
- * Default age after which a donation still stuck in `awaiting_seed` is
6
- * auto-terminated: the requester provisioned a node but never presented a seed
7
- * (it vanished, or abandoned the flow), leaving an orphaned child holding host
8
- * ports. 30 minutes.
9
- *
10
- * NOTE: not operator-configurable yet — a single module constant. If hosts need
11
- * to tune the grace period, promote this to `host.config.json`.
12
- */
13
- export declare const DONATION_AWAITING_SEED_TTL_MS: number;
14
- /**
15
- * Default age after which a donation still stuck in `provisioning` is
16
- * auto-terminated: the host wrote the row before starting the child, and a
17
- * crash/kill in that window (or during the spawn itself) leaves nothing to
18
- * advance it across a restart. A real spawn (identity key read/gen, port
19
- * allocation, process spawn) completes in well under a second normally, so 5
20
- * minutes is generously past "any plausible spawn" without risking a false
21
- * reap of one still genuinely in flight under heavy load.
22
- */
23
- export declare const DONATION_PROVISIONING_TTL_MS: number;
24
- /** How often the reap sweep runs while cadre-host is up. 5 minutes. */
25
- export declare const DONATION_REAP_SWEEP_MS: number;
26
- /** A grantee-driven request to provision one donated node. */
27
- export interface DonationProvisionRequest {
28
- /** The grant token that authorizes this donation (the quota key). */
29
- grantToken: string;
30
- /** The REQUESTER's cadre — the party the donated node joins. */
31
- partyId: string;
32
- /**
33
- * Requester control-network bootstrap multiaddrs (dialable, with `/p2p/`). Empty
34
- * means the requester dials the node itself: a phone has no address to give, so it
35
- * reaches the node at the WebSocket address {@link DonationService.getPeer} reports
36
- * rather than being dialed by it.
37
- */
38
- bootstrapNodes: string[];
39
- /**
40
- * The requester's owner public key(s), base64url. Pinned as cold-start
41
- * seed-trust anchors on the donated node so it accepts the requester-signed
42
- * seed presented in {@link DonationService.applySeed}. Without these the node's
43
- * node-local trusted-owner anchor holds no key for the requester's party, and
44
- * the default anchored policy rejects every seed.
45
- *
46
- * Shape-validated on the way in ({@link DonationService.provision}): every entry
47
- * must decode as a base64url 32-byte Ed25519 public key, the same rule the node
48
- * itself applies to `CADRE_OWNER_KEYS`. So everything downstream — the persisted
49
- * record, the child's environment, a later respawn replaying it — carries values
50
- * the node can actually start with. Curve membership is NOT checked (see
51
- * `requireEd25519PublicKeyB64`): a well-formed key naming the wrong signer still
52
- * fails at seed time, as any wrong key would.
53
- */
54
- ownerKeys: string[];
55
- /** Node profile; defaults to `storage` so the node participates and is dialable. */
56
- profile?: 'storage' | 'transaction';
57
- }
58
- /**
59
- * Outcome of {@link DonationService.applySeed}.
60
- *
61
- * - `seeded` — the node accepted the seed and the record now says so.
62
- * - `rejected` — the node refused the seed (its own seed-trust policy). Nothing
63
- * written.
64
- * - `abandoned` — the seed reached the node, but the loan ended while the
65
- * request was in flight. The ending wins: the record is left exactly as the
66
- * ending wrote it. `status` is the status that won, absent when the row is
67
- * gone entirely.
68
- */
69
- export type DonationSeedResult = {
70
- outcome: 'seeded';
71
- peersAdded: number;
72
- } | {
73
- outcome: 'rejected';
74
- error?: string;
75
- } | {
76
- outcome: 'abandoned';
77
- status?: DonationStatus;
78
- };
79
- /** Live peer identity of a donated node. */
80
- export interface DonationPeerInfo {
81
- peerId: string;
82
- multiaddrs: string[];
83
- }
84
- /**
85
- * Outcome of {@link DonationService.respawn}. Three distinct non-throwing ends,
86
- * which a bare `DonationView | undefined` conflated:
87
- *
88
- * - `respawned` — a new child is up and the record now names its handles.
89
- * - `not_respawnable` — the record predates persisted spawn inputs, so there is
90
- * nothing to replay. Permanent; no child was spawned.
91
- * - `abandoned` — the loan ended (or the record vanished) while the spawn was in
92
- * flight. The ending wins: the record is left exactly as the ending wrote it
93
- * and the new child is cleaned up. `status` is the status that won, absent
94
- * when the row is gone entirely.
95
- */
96
- export type RespawnResult = {
97
- outcome: 'respawned';
98
- donation: DonationView;
99
- } | {
100
- outcome: 'not_respawnable';
101
- } | {
102
- outcome: 'abandoned';
103
- status?: DonationStatus;
104
- };
105
- /**
106
- * Orchestrator capabilities the donation cleanup paths need beyond the base
107
- * `Orchestrator`. Both are optional — only `HostProcessOrchestrator` implements
108
- * them; absent on a test double or a future orchestrator, the cleanup degrades
109
- * to terminalizing the record with nothing to reclaim.
110
- */
111
- export interface DonationOrchestrator extends Orchestrator {
112
- /**
113
- * Resolve a spawn's friendly containerId (== a donation's id) back to its
114
- * current dockerId, so a stuck-`provisioning` reap can find and reclaim a
115
- * child that was actually spawned before the host died.
116
- */
117
- resolveDockerId?(containerId: string): string | undefined;
118
- /**
119
- * Remove the working directory a spawn created for `containerId` when no
120
- * handle owns it — the cleanup of last resort for a record that never got a
121
- * `dockerId`. Returns whether anything was removed. Refuses when a handle
122
- * still resolves (that child's directory belongs to `removeContainer`).
123
- */
124
- reclaimWorkdir?(containerId: string): boolean;
125
- }
126
- /** Constructor options. */
127
- export interface DonationServiceOptions {
128
- /** Orchestrator that spawns/stops the donated node child process. */
129
- orchestrator: DonationOrchestrator;
130
- /** Grant validator (identity/expiry/revocation + quota). */
131
- grants: GrantValidator;
132
- /** Persistent donation store (`donations.json`). */
133
- store: DonationStore;
134
- /** Clock override for tests. */
135
- now?: () => Date;
136
- }
137
- /**
138
- * DonationService — drives the donate-a-node lifecycle on behalf of an external
139
- * requester (a friend's phone). It is cadre-host's analogue of cadre-provider's
140
- * `ContainerService`, minus Docker/billing, plus the grant-token gate.
141
- *
142
- * The host contributes capacity only: it spawns a generic node into the
143
- * *requester's* cadre (`partyId`), pins the requester's owner key(s) so the node
144
- * will accept a requester-signed seed, and presents that seed to the node's
145
- * `POST /seed` — it never holds the requester's authority private key.
146
- *
147
- * Scope note: this class covers the service surface the node-donation
148
- * integration test drives directly (`provision` / `getPeer` / `applySeed` /
149
- * `terminate` / `get` / `list`). The grantee-facing HTTP routes, the `bin/host.ts`
150
- * wiring, and the stale-`awaiting_seed` reap sweep are owned by the
151
- * `2-donation-service` ticket and land alongside this file.
152
- */
153
- export declare class DonationService {
154
- private readonly orchestrator;
155
- private readonly grants;
156
- private readonly store;
157
- private readonly now;
158
- /**
159
- * Per-grant-token serialization tail. `provision` chains onto the prior
160
- * operation for the same token so the quota check and record-create are
161
- * atomic — two concurrent requests at `count = maxNodes - 1` can't both pass.
162
- * `terminateGrant` chains onto it too, so a revoke's teardown cannot miss a
163
- * record that a provision already queued for the grant is about to write.
164
- *
165
- * NOTE: one entry per distinct token, never evicted. Fine at household scale
166
- * (a handful of grants); if grant counts ever grow large, evict the tail once
167
- * it resolves.
168
- */
169
- private readonly grantTail;
170
- constructor(opts: DonationServiceOptions);
171
- /**
172
- * Provision a donated node for a validated grant. Serialized per grant token
173
- * so the quota check and record-create are atomic. On any orchestrator failure
174
- * the reserved resources are reclaimed and the record is marked `error`.
175
- *
176
- * The owner-key pins are shape-checked FIRST — ahead of the per-grant lock, the
177
- * quota check and the record write — so a malformed request costs nothing: no
178
- * waiting behind another provision, no grant quota slot burned, no workdir or
179
- * port taken, and no `error` record to explain later. Without it the request is
180
- * answered as provisioned and the child dies at boot, which is the one place the
181
- * caller cannot act on the typo.
182
- */
183
- provision(request: DonationProvisionRequest): Promise<DonationView>;
184
- private provisionLocked;
185
- /**
186
- * Mark a still-`provisioning` record as failed — and ONLY such a record. A row
187
- * that went `terminated` while the spawn was in flight is the borrower's own
188
- * ending and must not be rewritten as a host fault; a row that vanished must
189
- * not be recreated.
190
- *
191
- * Best-effort (mirroring {@link storeRespawnAttempt}): we are already unwinding a
192
- * provision failure, and a store error here must not mask the error being
193
- * reported to the caller.
194
- */
195
- private markProvisionFailed;
196
- /**
197
- * Live peer identity of a donated node, read fresh from its `/status` every
198
- * call (no cache) so a re-keyed identity / remapped multiaddr is never served
199
- * stale. Throws `peer_unavailable` while the node has no peer identity yet.
200
- */
201
- getPeer(id: string): Promise<DonationPeerInfo>;
202
- /**
203
- * Present the requester's phone-signed seed to the donated node's `POST /seed`,
204
- * authenticated with the host↔node `seedToken`. On success the donation moves
205
- * to `seeded`. The node still enforces its own seed-trust policy, so a seed
206
- * signed by a key the node did not pin comes back `rejected`.
207
- *
208
- * **An ending that lands mid-seed wins.** The record is re-read after the node
209
- * answers; if the loan ended (or the row vanished) while the request was in
210
- * flight, that write stands and we report `abandoned` rather than marking a
211
- * dead loan `seeded`. Unlike an abandoned respawn there is nothing to clean up
212
- * — the record named its `dockerId` the whole time, so the ending's own
213
- * `terminate` already stopped and reclaimed the child.
214
- */
215
- applySeed(id: string, encodedSeed: string): Promise<DonationSeedResult>;
216
- /**
217
- * Re-spawn a donated node that is no longer running, replaying the spawn
218
- * inputs persisted on the record. The node keeps its workdir — and with it its
219
- * identity key, trusted-owner anchor, and retained bootstrap peers — so it
220
- * comes back as the *same* peer and rejoins from its own durable node-local
221
- * stores. Only the host-side handles change: `dockerId`, `seedEndpoint`,
222
- * `seedToken` are written back fresh.
223
- *
224
- * **Status is deliberately unchanged.** A `seeded` node stays `seeded` (it
225
- * needs no re-seeding); an `awaiting_seed` node stays `awaiting_seed` and the
226
- * borrower's later seed goes to the new endpoint/token.
227
- *
228
- * **An ending that lands mid-spawn wins.** The record is re-read after the
229
- * orchestrator returns; if it went `terminated` (or `error`, or vanished)
230
- * while the child was starting, that write stands and the new child is
231
- * abandoned — see {@link abandonRespawn}. Both non-`respawned` outcomes are
232
- * skips, not failures, so a sweep over the store keeps going. Orchestrator
233
- * failures DO throw; the caller owns backoff/give-up.
234
- *
235
- * NOTE: not serialized. Two overlapping calls for the same id both spawn a
236
- * child, and the second spawn drops the first's orchestrator handle — leaving
237
- * an unmanaged process. A supervisor with more than one trigger (timer +
238
- * exit event) must not let its passes overlap on one id.
239
- */
240
- respawn(id: string): Promise<RespawnResult>;
241
- /**
242
- * Give up on a respawn whose loan ended while the child was starting: stop the
243
- * new child and, unless the record went `error`, reclaim it.
244
- *
245
- * Reclaim, not merely stop. The ending's own cleanup already ran and found
246
- * nothing: `HostProcessOrchestrator.createContainer` calls `dropStaleHandle`
247
- * *while* spawning, so by the time the concurrent `terminate` reached its
248
- * stop/reclaim the old handle was gone and both calls were swallowed as
249
- * best-effort no-ops. The new handle is now the only thing holding that
250
- * spawn's ports and workdir, so stopping alone would leak both. On a
251
- * `terminated` record the loan is over and the workdir (identity key +
252
- * node-local stores) goes with it — which is what `terminate` intended.
253
- *
254
- * `error` is the deliberate exception: `removeContainer` deletes the workdir,
255
- * and `DonationSupervisor.giveUp` keeps it on purpose so the identity key the
256
- * borrower's cadre approved survives. Since both spawns share one workdir
257
- * (`<rootDir>/<containerId>`), reclaiming here would delete exactly what
258
- * `giveUp` meant to keep.
259
- *
260
- * NOTE: that skipped reclaim leaks the new spawn's ports — the record
261
- * still names the *previous* `dockerId`, so a later `terminate` cleans up the
262
- * old handle, not this one. Unreachable today (`giveUp` is only called from
263
- * the supervisor's serialized pass, so it cannot overlap a respawn); if a
264
- * second `respawn` caller ever appears, write the new `dockerId` onto the
265
- * `error` record here so that later `terminate` reclaims the right child.
266
- */
267
- private abandonRespawn;
268
- /**
269
- * Terminate a donation: mark the record `terminated` FIRST, then stop + remove
270
- * the child process (kept in the store for audit, excluded from the live-node
271
- * tally).
272
- *
273
- * Order matters: stopping the child fires the orchestrator's `onStateChange`,
274
- * and a respawn supervisor listening there must already see a terminal record
275
- * — otherwise it observes "node gone, record still `seeded`" and resurrects a
276
- * loan the borrower just ended. The tradeoff: a crash between the write and
277
- * the stop leaves a `terminated` record with a live child (reaped on the next
278
- * host start), which is strictly better than resurrecting an ended loan.
279
- */
280
- terminate(id: string): Promise<void>;
281
- /**
282
- * Terminate every donation under one grant that is not already `terminated` —
283
- * the teardown behind an admin revoke. Returns the ids it terminated.
284
- *
285
- * Includes `error` records on purpose: the supervisor's give-up keeps such a
286
- * record's workdir (its identity key) for a later `terminate`, and once the
287
- * grant is revoked the grantee can no longer make that call, so this is the
288
- * last thing that can reclaim it.
289
- *
290
- * Serialized with `provision` on the same per-grant queue. The caller revokes
291
- * the grant first, and `provisionLocked` re-validates the grant inside that
292
- * queue, so a provision queued ahead of this call finishes and is then
293
- * terminated here, while one queued behind it is refused as revoked.
294
- *
295
- * Idempotent, and best-effort per record like the reap sweeps: a failed
296
- * terminate is logged and the loop continues, and running it again terminates
297
- * whatever is still left.
298
- */
299
- terminateGrant(token: string): Promise<string[]>;
300
- private terminateGrantLocked;
301
- /**
302
- * Auto-terminate donations still `awaiting_seed` past `ttlMs` — a requester
303
- * that provisioned a node but never presented a seed leaves an orphaned child
304
- * holding host ports. Run on a periodic sweep and once at startup for records
305
- * recovered from disk. Age is measured from the record's `updatedAt` (set when
306
- * it entered `awaiting_seed`). Best-effort per record: a failed terminate is
307
- * logged and the sweep continues. Returns the reaped donation ids.
308
- */
309
- reapStaleAwaitingSeed(ttlMs?: number): Promise<string[]>;
310
- /**
311
- * Auto-terminate donations stuck in `provisioning` past `ttlMs` — the record
312
- * is written before the child spawn starts, so a host crash/kill anywhere in
313
- * that window (including mid-spawn) leaves nothing to advance it across a
314
- * restart: there is no in-flight `provisionLocked` call left to reach its
315
- * post-spawn re-read. Run on the same periodic sweep and startup pass as
316
- * {@link reapStaleAwaitingSeed}. Age is measured from `updatedAt`, which for a
317
- * genuinely-stuck row equals `createdAt` — `provisionLocked` never touches a
318
- * `provisioning` record again except to advance it out of this predicate.
319
- * Best-effort per record: a failed reap is logged and the sweep continues.
320
- * Returns the reaped donation ids.
321
- */
322
- reapStaleProvisioning(ttlMs?: number): Promise<string[]>;
323
- /**
324
- * Terminalize a stuck-`provisioning` record as `error` and reclaim any child
325
- * the orchestrator can still find for it. Status is written FIRST (same
326
- * ordering rule as {@link terminate}): reclaiming fires `onStateChange`, and
327
- * anything listening must already see a terminal record.
328
- *
329
- * NOTE: the stop only reaches a child the orchestrator has a handle for.
330
- * `HostProcessOrchestrator.launchChild` spawns the process and persists the
331
- * handle in the same synchronous step, so the only child this misses is one
332
- * whose host died between the OS spawn and that write — an orphan *process*
333
- * this reap terminalizes the record for but cannot kill (its ports stay held
334
- * until a reboot). Its working directory is no longer out of reach, though:
335
- * with no handle to resolve we fall back to reclaiming by container name,
336
- * which also covers the far commoner case of a host that died before any
337
- * child existed at all. If the spawn→persist window ever widens (an async
338
- * step added between the two), the orchestrator still needs its own orphan
339
- * *process* sweep — a record that names no handle cannot get one after the
340
- * fact.
341
- */
342
- private reclaimStuckProvisioning;
343
- /** One donation (redacted), or undefined when unknown. */
344
- get(id: string): DonationView | undefined;
345
- /** All donations (redacted), optionally scoped to one grant token. */
346
- list(grantToken?: string): DonationView[];
347
- private requireDonation;
348
- /** Best-effort stop; logs but never throws (cleanup path). */
349
- private safeStop;
350
- /**
351
- * Persist a failed respawn's attempt counters — plus, when the spawn itself
352
- * succeeded and it was the record write that failed, the new child's handles.
353
- * Merged onto whatever is on disk now, never written wholesale: the caller's
354
- * copy predates the orchestrator round-trip and `store.put` replaces the whole
355
- * row, so writing it back would undo a `terminate` that landed while the spawn
356
- * was in flight and resurrect a loan the borrower just ended.
357
- *
358
- * `status` and `updatedAt` are deliberately untouched — the attempt did not
359
- * succeed, and `updatedAt` is what defers the stale-`awaiting_seed` reap.
360
- *
361
- * Recording `spawned` is what keeps the record pointing at the child that
362
- * actually exists: `createContainer` already dropped the handle the record
363
- * previously named, so leaving the old `dockerId` there would send every later
364
- * stop/reclaim at an id the orchestrator cannot resolve — stranding the node's
365
- * workdir on disk forever.
366
- *
367
- * Best-effort: we are already unwinding a respawn failure, and a store error
368
- * here must not mask it. Losing the write costs the caller one extra attempt
369
- * before backoff; the supervisor's next pass re-reads the record, finds the
370
- * named child not running, and respawns again — which drops whatever handle is
371
- * current, so the ports self-heal either way.
372
- */
373
- private storeRespawnAttempt;
374
- /** Best-effort remove; logs but never throws (cleanup path). */
375
- private safeReclaim;
376
- /**
377
- * Best-effort removal of the working directory of a donation whose spawn
378
- * never produced a `dockerId`; logs but never throws (cleanup path). A no-op
379
- * on an orchestrator without the capability, and on one that refuses because
380
- * a live handle still owns the directory.
381
- */
382
- private safeReclaimWorkdir;
383
- /**
384
- * Run `fn` after any in-flight provision or grant teardown for the same grant
385
- * token, so the quota check + record-create pair is atomic per grant and a
386
- * teardown sees every record a queued provision writes.
387
- */
388
- private serializeByGrant;
389
- }