@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,125 +1,181 @@
1
- import { type DdnsProviderInfo, type NatHandlers, type NatSettingsFile, type NatStatusSnapshot } from './types.js';
1
+ import { type DdnsProviderInfo, type ManualForwardPatch, type NatHandlers, type NatSettingsFile, type NatStatusSnapshot } from './types.js';
2
2
  import { NatStore } from './nat-store.js';
3
3
  import { ExternalIpDetector } from './external-ip.js';
4
4
  import { type PortMapper } from './port-mapper.js';
5
5
  import { type SecretsStore } from './secrets/index.js';
6
+ import { type NodeAddressesStaleListener } from './address-watch.js';
7
+ import type { ManagedNodeInfo, NodeStateListener } from '../orchestrator/types.js';
6
8
  /**
7
- * Minimal slice of CadreNode that NatService needs — defined as an interface
8
- * so tests can inject a mock without a real libp2p stack. Mirrors
9
- * `CadreNodeLike` in `auth/trust-circle.ts`.
10
- *
11
- * Both methods are async: in production they round-trip over the owner
12
- * node's loopback admin channel (`GET /admin/identity`, `GET /admin/multiaddrs`).
13
- * Synchronous test mocks can return a value directly (it's awaited).
9
+ * How long a node may sit `stopped` before its mappings are released. Longer
10
+ * than the hosted-node supervisor's whole respawn backoff (150 s), so a crash
11
+ * followed by a respawn keeps its mapping.
12
+ */
13
+ export declare const NAT_UNMAP_GRACE_MS: number;
14
+ /** The backstop reconcile cadence; state-change events trigger passes between ticks. */
15
+ export declare const NAT_RECONCILE_INTERVAL_MS = 60000;
16
+ /**
17
+ * How often the external IP is re-detected, and how often gateway discovery is
18
+ * retried while UPnP is on and no router has answered.
19
+ */
20
+ export declare const NAT_IP_REDETECT_INTERVAL_MS: number;
21
+ /**
22
+ * The orchestrator surface the service needs: the node list and cadre-host's
23
+ * own state-change subscription. `HostProcessOrchestrator` satisfies this; the
24
+ * hosted-node supervisor declares the same slice.
14
25
  */
15
- export interface CadreNodeLike {
16
- /** Returns this node's peer ID (libp2p) as a string. */
17
- getPeerId(): Promise<string> | string;
18
- /** Returns the current observed multiaddrs from libp2p. */
19
- getMultiaddrs(): Promise<string[]> | string[];
26
+ export interface NatNodeSource {
27
+ listNodes(): ManagedNodeInfo[];
28
+ onStateChange(listener: NodeStateListener): () => void;
20
29
  }
21
- /** Listener notified when the invite addresses may have changed. */
22
- export type AddressesChangedListener = (addresses: string[]) => void | Promise<void>;
30
+ /** Listener notified when the status snapshot changed in a way the UI should follow. */
31
+ export type NatChangeListener = (snapshot: NatStatusSnapshot) => void;
23
32
  export interface NatServiceOptions {
24
- /** Cadre-host root directory (same one orchestrator + trust-circle use). */
33
+ /** Cadre-host root directory (same one the orchestrator and the hosted-node store use). */
25
34
  rootDir: string;
26
- /** libp2p node — used for peer ID and the libp2p multiaddrs fallback. */
27
- cadreNode: CadreNodeLike;
35
+ /** Where the hosted nodes and their ports come from. */
36
+ nodeSource: NatNodeSource;
28
37
  /** Clock override for tests. */
29
38
  now?: () => Date;
30
39
  /** Fetch override for tests. */
31
40
  fetch?: typeof fetch;
32
41
  /** Test-only: stub the keytar/file-store. Default: createSecretsStore. */
33
42
  secretsStore?: SecretsStore;
34
- /** Test-only: stub the port mapper. Default: createDefaultPortMapper(). */
43
+ /** Test-only: stub the router. Default: `UpnpPortMapper`. */
35
44
  portMapper?: PortMapper;
36
45
  /** Test-only: stub the external-IP detector. */
37
46
  externalIpDetector?: ExternalIpDetector;
38
47
  /** Optional: re-use an existing NatStore (mostly for tests). */
39
48
  store?: NatStore;
40
- /** Initial-push retry: poll interval while the node is not-yet-ready. Default 250ms. */
41
- initialPushRetryMs?: number;
42
- /** Initial-push retry: total budget before giving up (best-effort). Default 15000ms. */
43
- initialPushTimeoutMs?: number;
49
+ /** Lease TTL asked of the router. Default 1 h. */
50
+ leaseTtlMs?: number;
44
51
  }
45
52
  /**
46
- * NatService — orchestrates port mapping, external-IP detection, DDNS updates,
47
- * and address resolution for invites.
53
+ * NatService — one mapping table for every node the host runs, keyed by node
54
+ * id, with a route per port (TCP and WebSocket); external-IP detection; DDNS;
55
+ * and the public addresses a node announces.
48
56
  *
49
57
  * Lifecycle:
50
- * - `start()`: load settings, install port mapping (if upnpEnabled), detect
51
- * external IP, kick off DDNS loop.
52
- * - `stop()`: release port mapping, stop DDNS timer.
53
- * - `getStatus()`: build a fresh status snapshot from internal state (cheap).
54
- * - `testReachability()`: re-run the IP + verify cycle and the heuristic.
55
- * - `getInviteAddresses()`: build the multiaddrs to embed in invites.
58
+ * - `start()`: load settings, discover the router (bounded), detect the
59
+ * external IP, start DDNS, then map the running nodes in the background.
60
+ * - `stop()`: clear timers only — mappings outlive the host process.
61
+ * - `getStatus()`: snapshot from cached state (cheap).
62
+ * - `publicAddressesFor(nodeId, ports)`: a node's public multiaddrs, also
63
+ * from cached state, predicting the identity mapping for a port that has
64
+ * no route yet.
65
+ * - `onNodeAddressesStale(listener)`: asked to restart a running node whose
66
+ * announced addresses no longer match `publicAddressesFor`, checked after
67
+ * every pass and every settings write (see `AddressWatch`).
56
68
  *
57
- * NatService is library + lifecycle only. The long-running process that owns
58
- * it is `cadre-host-local-ui` (see docs/cadre-host.md and the ticket header).
69
+ * One reconcile pass against the node source behind three triggers (start,
70
+ * node state change, a 1-minute timer), plus one renewal pass for every lease
71
+ * every `DEFAULT_REFRESH_MS`. Passes are serialized on one promise tail, so a
72
+ * state change during a timer pass can neither map a port twice nor unmap a
73
+ * port the other pass is mapping.
74
+ *
75
+ * NOTE: this file holds the settings/DDNS glue, the mapping table and the status
76
+ * assembly together (about 890 lines; the stale-address check lives in
77
+ * `address-watch.ts` for that reason); when the next capability lands here, move
78
+ * the mapping table (`NodeEntry` through `releaseUpnpRoutes`) into its own module.
59
79
  */
60
80
  export declare class NatService {
61
- private readonly cadreNode;
81
+ private readonly nodeSource;
62
82
  private readonly store;
63
83
  private readonly nowFn;
64
84
  private readonly fetchImpl;
85
+ private readonly portMapper;
86
+ private readonly leaseTtlMs;
87
+ private readonly secretsRootDir;
65
88
  private secretsStore;
66
- private portMapper;
67
- /** Caller-supplied detector — used as-is when present (skips the auto router-probe stitch). */
89
+ /** Caller-supplied detector — used as-is when present (skips the router-probe stitch). */
68
90
  private readonly injectedDetector;
69
- private portService;
91
+ private detector;
70
92
  private ddnsUpdater;
71
93
  private currentSettings;
94
+ private gateway;
72
95
  private latestIp;
73
96
  private lastTestedAt;
97
+ private readonly table;
74
98
  private started;
75
- /** Listeners notified when the invite addresses may have changed. */
76
- private readonly addressListeners;
77
- private readonly secretsRootDir;
78
- private readonly initialPushRetryMs;
79
- private readonly initialPushTimeoutMs;
99
+ private tail;
100
+ /** True while an event-triggered pass is queued and not yet started (crash storms coalesce into it). */
101
+ private eventPassQueued;
102
+ private timers;
103
+ private unsubscribe;
104
+ private readonly changeListeners;
105
+ private lastSignature;
106
+ private readonly addressWatch;
80
107
  constructor(opts: NatServiceOptions);
81
- /** Initial start: port mapping → IP detect → DDNS. Idempotent. */
108
+ /**
109
+ * Discover the router and detect the external IP (both awaited, both
110
+ * bounded), start DDNS, then map re-attached running nodes in the background
111
+ * so the management API comes up promptly. Idempotent.
112
+ */
82
113
  start(): Promise<void>;
83
- /** Release the port mapping and clear timers. Idempotent. */
114
+ /**
115
+ * Clear timers and release client resources. Idempotent.
116
+ *
117
+ * NOTE: deliberately unmaps nothing. Hosted children are detached and keep
118
+ * running across a host restart (an update restart included), so their
119
+ * mappings must outlive this process; they expire on their own within the
120
+ * lease TTL if the host stays down, and the next `start()` re-maps the
121
+ * re-attached nodes (re-mapping the same internal port is idempotent on the
122
+ * router).
123
+ */
84
124
  stop(): Promise<void>;
85
125
  /** Cheap read of the current snapshot. */
86
126
  getStatus(): NatStatusSnapshot;
87
- /** Re-run the IP probe + port-mapper verify, then return a fresh snapshot. */
88
- testReachability(): Promise<NatStatusSnapshot>;
127
+ /** Current settings (cheap read; for handlers to surface to UI). */
128
+ getSettings(): NatSettingsFile;
89
129
  /**
90
- * Build the multiaddrs to embed in invites. The peer ID and libp2p
91
- * fallback addresses are fetched over the owner node's admin channel; a
92
- * node-unavailable failure surfaces as `NatError('node_unavailable')`.
130
+ * The public multiaddrs for a node, from cached state. A port with no route
131
+ * yet is predicted to land on the identity mapping when UPnP is enabled and
132
+ * a gateway was discovered — callers ask at spawn time, before the mapping
133
+ * for a brand-new node exists. A port whose mapping attempt failed is not
134
+ * predicted.
93
135
  */
94
- getInviteAddresses(): Promise<string[]>;
136
+ publicAddressesFor(nodeId: string, ports: {
137
+ p2p: number;
138
+ ws?: number;
139
+ }): string[];
95
140
  /**
96
- * Register a listener fired when the invite addresses may have changed
97
- * (after `start`, `putSettings`, and `testReachability`). `cadre-host start`
98
- * wires this to `OwnerNodeClient.pushInviteAddresses`. Returns an
99
- * unsubscribe fn.
141
+ * Register a listener fired whenever a node's routes, the gateway, the
142
+ * external IP, the CGNAT flag, the UPnP toggle or the DDNS settings change.
143
+ * Returns an unsubscribe fn.
100
144
  */
101
- onAddressesChanged(listener: AddressesChangedListener): () => void;
145
+ onChange(listener: NatChangeListener): () => void;
102
146
  /**
103
- * Recompute invite addresses and notify listeners. Best-effort: when the
104
- * owner node is unreachable (or the build fails), the notification is
105
- * skipped — the push is retried on the next NAT event.
147
+ * Register a listener asked to restart a running node whose announced addresses
148
+ * (`ManagedNodeInfo.announcedAddrs`) differ from `publicAddressesFor` — at most
149
+ * once per node per `NAT_ADDRESS_RESTART_MIN_INTERVAL_MS`. Returns an unsubscribe fn.
106
150
  */
107
- private fireAddressesChanged;
108
- /** Deliver an address set to every registered listener (errors are logged, not thrown). */
109
- private notifyAddressListeners;
151
+ onNodeAddressesStale(listener: NodeAddressesStaleListener): () => void;
152
+ /** List available DDNS providers (UI listing for configuration). */
153
+ listDdnsProviders(): DdnsProviderInfo[];
110
154
  /**
111
- * Initial invite-address push. The owner node's admin channel is a freshly
112
- * spawned detached child that may not be bound yet (and may not have a libp2p
113
- * peer ID yet), so a single attempt races readiness and silently drops the first
114
- * NAT-resolved address set. Retry on `node_unavailable` until the node accepts it
115
- * or the bounded budget elapses (best-effort thereafter). Awaited by start() so
116
- * the management API — the invite-minting path — does not come up first.
155
+ * One reconcile pass against the node source: map running nodes, release
156
+ * nodes stopped past the grace, forget terminated ones. Serialized against
157
+ * every other pass, so a caller may get a queued pass rather than an
158
+ * immediate one.
117
159
  */
118
- private pushInitialAddresses;
119
- /** Current settings (cheap read; for handlers to surface to UI). */
120
- getSettings(): NatSettingsFile;
160
+ reconcile(): Promise<void>;
161
+ /**
162
+ * One renewal pass: re-request every UPnP-eligible mapping of every running
163
+ * node, failed ones included. Each renewal is isolated; one failure does not
164
+ * stop the pass. A router that answers with another external port updates
165
+ * the route.
166
+ */
167
+ renewMappings(): Promise<void>;
168
+ /**
169
+ * The 5-minute probe pass: while UPnP is on and no router has answered,
170
+ * search again and map the running nodes as soon as one does — a host that
171
+ * boots before its network, or a router that comes up later, is found here
172
+ * rather than at the next restart; then re-detect the external IP.
173
+ */
174
+ redetect(): Promise<void>;
175
+ /** Re-discover the router if needed, re-map everything, re-detect the IP, then return a fresh snapshot. */
176
+ testReachability(): Promise<NatStatusSnapshot>;
121
177
  /** Replace settings; persists to disk and reconfigures the runtime. */
122
- putSettings(patch: Partial<Omit<NatSettingsFile, 'version'>>): Promise<NatStatusSnapshot>;
178
+ putSettings(patch: Partial<Omit<NatSettingsFile, 'version' | 'forwards'>>): Promise<NatStatusSnapshot>;
123
179
  /**
124
180
  * Set DDNS configuration and persist secret values into the SecretsStore.
125
181
  * Throws if the provider is unknown or required secrets are missing.
@@ -130,14 +186,84 @@ export declare class NatService {
130
186
  config: Record<string, string>;
131
187
  externallyManaged?: boolean;
132
188
  }): Promise<NatStatusSnapshot>;
133
- /** List available DDNS providers (UI listing for configuration). */
134
- listDdnsProviders(): DdnsProviderInfo[];
135
- private startPortService;
189
+ /**
190
+ * Record the external ports the user forwarded by hand for one node (`null`
191
+ * clears a port). A port that became manual releases its UPnP mapping; a
192
+ * port whose entry was cleared is re-requested over UPnP.
193
+ */
194
+ putForward(nodeId: string, patch: ManualForwardPatch): Promise<NatStatusSnapshot>;
195
+ private enqueue;
196
+ /** Fire-and-forget pass for the timer / event / startup triggers. */
197
+ private sweep;
198
+ /**
199
+ * One pass per burst of events: an event while a pass is queued joins it,
200
+ * and an event during a running pass queues exactly one more, so a node that
201
+ * spawned after the running pass listed the nodes is mapped by the next pass
202
+ * rather than by the timer.
203
+ */
204
+ private queueEventPass;
205
+ private reconcileOnce;
206
+ /**
207
+ * A node no longer listed was terminated (`removeContainer`): release its
208
+ * mappings at once and drop its manual forward. A forward left behind for a
209
+ * node that vanished while the host was down goes the same way.
210
+ */
211
+ private forgetMissingNodes;
212
+ private reconcileNode;
213
+ /**
214
+ * Bring one node's routes in line with the settings: manual wins over UPnP;
215
+ * UPnP off releases every `upnp` route; with `mapMissing`, a port with no
216
+ * route and no recorded failure (or an expired lease) is requested.
217
+ */
218
+ private reconcileRoutes;
219
+ private renewOnce;
220
+ /**
221
+ * Ask the router for one port. A refusal records `error` on that port only.
222
+ *
223
+ * NOTE: a router that stops answering costs each attempt its 10 s timeout, so a
224
+ * pass over N mapped ports can take 10 s × N; passes are serialized, so they queue
225
+ * rather than overlap. If that ever shows, skip ports whose last failure was a
226
+ * timeout until the next renewal pass.
227
+ */
228
+ private mapRoute;
229
+ private unmapRoute;
230
+ private releaseUpnpRoutes;
231
+ private kindsOf;
232
+ private leaseExpired;
233
+ /** A UPnP route whose lease ran out reads as no route, keeping any recorded failure. */
234
+ private effectiveRoute;
235
+ private discoverGateway;
236
+ /**
237
+ * Detect the external IP. A detection that finds nothing (or throws), or that
238
+ * lost the public echo the previous one had, keeps the previous result: one
239
+ * failed probe must not drop every node's public address, and a router-only
240
+ * answer must not flip the CGNAT flag the two probes had agreed on.
241
+ */
136
242
  private detectIp;
137
- private disabledPortState;
243
+ private knowsLessThanPrevious;
244
+ private ipDetector;
245
+ /** The detected external IP for display and DDNS: the public probe's, else the router's. */
246
+ private detectedIp;
247
+ /** The IP used in `/ip4/` addresses: only a public IPv4 counts. */
248
+ private publicIpv4;
249
+ private describeNode;
250
+ private hostKnown;
251
+ private addressesFor;
252
+ private routeOrPrediction;
253
+ private ddnsStatus;
254
+ /**
255
+ * The end of every pass and every settings write: tell the change listeners
256
+ * when the snapshot moved, then ask for a restart of every running node whose
257
+ * announced addresses went stale. Running the check here rather than only on a
258
+ * change is what lets a difference deferred by the restart rate limit fire on the
259
+ * first timer pass after the limit ends.
260
+ */
261
+ private publish;
262
+ /** Fire the change listeners when the snapshot differs (timestamps aside) from the last one they saw. */
263
+ private notifyIfChanged;
138
264
  }
139
265
  /**
140
- * Wrap a NatService into the typed handler shape consumed by
141
- * `cadre-host-local-ui`. Mirrors `createTrustCircleHandlers`.
266
+ * Wrap a NatService into the typed handler shape consumed by the local UI
267
+ * server. Same shape as `createUpdateHandlers`.
142
268
  */
143
269
  export declare function createNatHandlers(service: NatService): NatHandlers;