@serfab/cadre-host 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/README.md +347 -423
  2. package/dist/bin/host.d.ts +6 -3
  3. package/dist/bin/host.js +332 -524
  4. package/dist/bin/host.js.map +1 -1
  5. package/dist/bin/join-output.d.ts +56 -0
  6. package/dist/bin/join-output.js +92 -0
  7. package/dist/bin/join-output.js.map +1 -0
  8. package/dist/bin/nat-output.d.ts +54 -0
  9. package/dist/bin/nat-output.js +138 -0
  10. package/dist/bin/nat-output.js.map +1 -0
  11. package/dist/bin/ui-access.d.ts +22 -0
  12. package/dist/bin/ui-access.js +44 -0
  13. package/dist/bin/ui-access.js.map +1 -0
  14. package/dist/hosted/claim-details.d.ts +66 -0
  15. package/dist/hosted/claim-details.js +60 -0
  16. package/dist/hosted/claim-details.js.map +1 -0
  17. package/dist/hosted/hosted-node-service.d.ts +325 -0
  18. package/dist/hosted/hosted-node-service.js +678 -0
  19. package/dist/hosted/hosted-node-service.js.map +1 -0
  20. package/dist/hosted/hosted-node-store.d.ts +40 -0
  21. package/dist/hosted/hosted-node-store.js +126 -0
  22. package/dist/hosted/hosted-node-store.js.map +1 -0
  23. package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
  24. package/dist/hosted/hosted-node-supervisor.js +396 -0
  25. package/dist/hosted/hosted-node-supervisor.js.map +1 -0
  26. package/dist/hosted/hosted-node-watcher.d.ts +95 -0
  27. package/dist/hosted/hosted-node-watcher.js +260 -0
  28. package/dist/hosted/hosted-node-watcher.js.map +1 -0
  29. package/dist/hosted/index.d.ts +16 -0
  30. package/dist/hosted/index.js +13 -0
  31. package/dist/hosted/index.js.map +1 -0
  32. package/dist/hosted/node-status.d.ts +51 -0
  33. package/dist/hosted/node-status.js +77 -0
  34. package/dist/hosted/node-status.js.map +1 -0
  35. package/dist/hosted/types.d.ts +141 -0
  36. package/dist/hosted/types.js +22 -0
  37. package/dist/hosted/types.js.map +1 -0
  38. package/dist/index.d.ts +12 -24
  39. package/dist/index.js +9 -19
  40. package/dist/index.js.map +1 -1
  41. package/dist/installer/config.d.ts +27 -34
  42. package/dist/installer/config.js +46 -15
  43. package/dist/installer/config.js.map +1 -1
  44. package/dist/installer/index.d.ts +9 -19
  45. package/dist/installer/index.js +21 -85
  46. package/dist/installer/index.js.map +1 -1
  47. package/dist/installer/paths.d.ts +0 -2
  48. package/dist/installer/paths.js +0 -4
  49. package/dist/installer/paths.js.map +1 -1
  50. package/dist/installer/wizard.d.ts +12 -9
  51. package/dist/installer/wizard.js +15 -14
  52. package/dist/installer/wizard.js.map +1 -1
  53. package/dist/nat/address-resolver.d.ts +18 -17
  54. package/dist/nat/address-resolver.js +79 -25
  55. package/dist/nat/address-resolver.js.map +1 -1
  56. package/dist/nat/address-watch.d.ts +37 -0
  57. package/dist/nat/address-watch.js +84 -0
  58. package/dist/nat/address-watch.js.map +1 -0
  59. package/dist/nat/ddns/duckdns.d.ts +2 -1
  60. package/dist/nat/ddns/duckdns.js +5 -1
  61. package/dist/nat/ddns/duckdns.js.map +1 -1
  62. package/dist/nat/ddns/index.d.ts +6 -0
  63. package/dist/nat/ddns/index.js.map +1 -1
  64. package/dist/nat/index.d.ts +13 -9
  65. package/dist/nat/index.js +7 -5
  66. package/dist/nat/index.js.map +1 -1
  67. package/dist/nat/nat-service.d.ts +220 -77
  68. package/dist/nat/nat-service.js +653 -239
  69. package/dist/nat/nat-service.js.map +1 -1
  70. package/dist/nat/nat-store.d.ts +12 -5
  71. package/dist/nat/nat-store.js +124 -29
  72. package/dist/nat/nat-store.js.map +1 -1
  73. package/dist/nat/network-mode.d.ts +19 -0
  74. package/dist/nat/network-mode.js +32 -0
  75. package/dist/nat/network-mode.js.map +1 -0
  76. package/dist/nat/port-mapper.d.ts +83 -79
  77. package/dist/nat/port-mapper.js +148 -189
  78. package/dist/nat/port-mapper.js.map +1 -1
  79. package/dist/nat/reachability.d.ts +32 -15
  80. package/dist/nat/reachability.js +110 -22
  81. package/dist/nat/reachability.js.map +1 -1
  82. package/dist/nat/types.d.ts +98 -27
  83. package/dist/nat/types.js +8 -5
  84. package/dist/nat/types.js.map +1 -1
  85. package/dist/orchestrator/host-process-orchestrator.d.ts +32 -74
  86. package/dist/orchestrator/host-process-orchestrator.js +84 -233
  87. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  88. package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
  89. package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
  90. package/dist/orchestrator/identity-file.js.map +1 -0
  91. package/dist/orchestrator/index.d.ts +2 -3
  92. package/dist/orchestrator/index.js +1 -1
  93. package/dist/orchestrator/index.js.map +1 -1
  94. package/dist/orchestrator/node-identity.d.ts +6 -6
  95. package/dist/orchestrator/node-identity.js +7 -7
  96. package/dist/orchestrator/node-identity.js.map +1 -1
  97. package/dist/orchestrator/port-allocator.d.ts +5 -6
  98. package/dist/orchestrator/port-allocator.js +9 -11
  99. package/dist/orchestrator/port-allocator.js.map +1 -1
  100. package/dist/orchestrator/state-store.d.ts +3 -5
  101. package/dist/orchestrator/state-store.js +2 -10
  102. package/dist/orchestrator/state-store.js.map +1 -1
  103. package/dist/orchestrator/types.d.ts +33 -22
  104. package/dist/orchestrator/types.js.map +1 -1
  105. package/dist/package-version.d.ts +1 -0
  106. package/dist/package-version.js +4 -0
  107. package/dist/package-version.js.map +1 -0
  108. package/dist/server/error-handler.d.ts +5 -25
  109. package/dist/server/error-handler.js +11 -74
  110. package/dist/server/error-handler.js.map +1 -1
  111. package/dist/server/events/types.d.ts +7 -11
  112. package/dist/server/events/types.js +2 -1
  113. package/dist/server/events/types.js.map +1 -1
  114. package/dist/server/index.d.ts +11 -37
  115. package/dist/server/index.js +30 -48
  116. package/dist/server/index.js.map +1 -1
  117. package/dist/server/routes/hosted-nodes.d.ts +28 -0
  118. package/dist/server/routes/hosted-nodes.js +68 -0
  119. package/dist/server/routes/hosted-nodes.js.map +1 -0
  120. package/dist/server/routes/nat.d.ts +4 -1
  121. package/dist/server/routes/nat.js +13 -2
  122. package/dist/server/routes/nat.js.map +1 -1
  123. package/dist/server/routes/nodes.d.ts +6 -20
  124. package/dist/server/routes/nodes.js +7 -86
  125. package/dist/server/routes/nodes.js.map +1 -1
  126. package/dist/server/routes/settings.d.ts +3 -7
  127. package/dist/server/routes/settings.js +18 -8
  128. package/dist/server/routes/settings.js.map +1 -1
  129. package/dist/server/routes/status.d.ts +2 -25
  130. package/dist/server/routes/status.js +3 -43
  131. package/dist/server/routes/status.js.map +1 -1
  132. package/dist/server/server.js +8 -8
  133. package/dist/server/settings-store.d.ts +2 -2
  134. package/dist/server/settings-store.js +2 -2
  135. package/dist/server/static.js +1 -2
  136. package/dist/server/static.js.map +1 -1
  137. package/dist/ui/assets/index-BHd8CWZA.css +1 -0
  138. package/dist/ui/assets/index-Ccr2GjPT.js +23 -0
  139. package/dist/ui/assets/index-Ccr2GjPT.js.map +1 -0
  140. package/dist/ui/index.html +2 -2
  141. package/dist/update/index.d.ts +2 -2
  142. package/dist/update/index.js +2 -2
  143. package/dist/update/store.d.ts +1 -1
  144. package/dist/update/store.js +1 -1
  145. package/dist/update/types.d.ts +1 -1
  146. package/dist/update/types.js +1 -1
  147. package/package.json +5 -7
  148. package/src/__tests__/cli-nat.smoke.test.ts +219 -212
  149. package/src/__tests__/cli.smoke.test.ts +2 -2
  150. package/src/__tests__/global-setup.ts +2 -3
  151. package/src/__tests__/orchestrator-claim-secret.test.ts +111 -0
  152. package/src/__tests__/orchestrator-env-scrub.test.ts +160 -163
  153. package/src/__tests__/orchestrator-handle-contract.ts +134 -134
  154. package/src/__tests__/orchestrator-node-identity.test.ts +177 -203
  155. package/src/__tests__/orchestrator-ports.test.ts +124 -143
  156. package/src/__tests__/orchestrator-push.test.ts +138 -146
  157. package/src/__tests__/orchestrator.test.ts +1017 -1063
  158. package/src/__tests__/ui-access.test.ts +30 -0
  159. package/src/bin/host.ts +397 -572
  160. package/src/bin/join-output.ts +120 -0
  161. package/src/bin/nat-output.ts +188 -0
  162. package/src/bin/ui-access.ts +50 -0
  163. package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +172 -173
  164. package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +43 -23
  165. package/src/hosted/__tests__/hosted-node-service.test.ts +708 -0
  166. package/src/hosted/__tests__/hosted-node-supervisor.test.ts +475 -0
  167. package/src/hosted/__tests__/test-invitation.ts +33 -0
  168. package/src/hosted/claim-details.ts +103 -0
  169. package/src/hosted/hosted-node-service.ts +814 -0
  170. package/src/hosted/hosted-node-store.ts +144 -0
  171. package/src/hosted/hosted-node-supervisor.ts +439 -0
  172. package/src/hosted/hosted-node-watcher.ts +288 -0
  173. package/src/hosted/index.ts +53 -0
  174. package/src/hosted/node-status.ts +97 -0
  175. package/src/hosted/types.ts +160 -0
  176. package/src/index.ts +72 -91
  177. package/src/installer/__tests__/config.test.ts +15 -40
  178. package/src/installer/__tests__/installer.smoke.test.ts +184 -263
  179. package/src/installer/__tests__/wizard.test.ts +25 -13
  180. package/src/installer/config.ts +201 -175
  181. package/src/installer/index.ts +252 -327
  182. package/src/installer/paths.ts +104 -109
  183. package/src/installer/wizard.ts +24 -26
  184. package/src/nat/__tests__/address-resolver.test.ts +107 -82
  185. package/src/nat/__tests__/nat-service.test.ts +608 -515
  186. package/src/nat/__tests__/nat-store.test.ts +49 -12
  187. package/src/nat/__tests__/network-mode.test.ts +38 -0
  188. package/src/nat/__tests__/port-mapper.test.ts +30 -191
  189. package/src/nat/__tests__/reachability.test.ts +96 -13
  190. package/src/nat/address-resolver.ts +94 -52
  191. package/src/nat/address-watch.ts +92 -0
  192. package/src/nat/ddns/duckdns.ts +5 -1
  193. package/src/nat/ddns/index.ts +6 -0
  194. package/src/nat/index.ts +33 -13
  195. package/src/nat/nat-service.ts +942 -501
  196. package/src/nat/nat-store.ts +126 -31
  197. package/src/nat/network-mode.ts +36 -0
  198. package/src/nat/port-mapper.ts +241 -329
  199. package/src/nat/reachability.ts +155 -34
  200. package/src/nat/types.ts +110 -37
  201. package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
  202. package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
  203. package/src/orchestrator/host-process-orchestrator.ts +1089 -1260
  204. package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
  205. package/src/orchestrator/index.ts +9 -10
  206. package/src/orchestrator/node-identity.ts +41 -41
  207. package/src/orchestrator/port-allocator.ts +71 -73
  208. package/src/orchestrator/state-store.ts +76 -90
  209. package/src/orchestrator/types.ts +34 -23
  210. package/src/package-version.ts +4 -0
  211. package/src/server/__tests__/bus.test.ts +38 -38
  212. package/src/server/__tests__/error-handler.test.ts +81 -118
  213. package/src/server/__tests__/fakes.ts +15 -18
  214. package/src/server/__tests__/hosted-nodes-route.test.ts +161 -0
  215. package/src/server/__tests__/nodes-route.test.ts +114 -283
  216. package/src/server/__tests__/publishers.test.ts +193 -180
  217. package/src/server/__tests__/server.smoke.test.ts +134 -223
  218. package/src/server/__tests__/settings-route.test.ts +210 -195
  219. package/src/server/__tests__/sse-route.test.ts +119 -119
  220. package/src/server/__tests__/status-route.test.ts +82 -121
  221. package/src/server/error-handler.ts +112 -178
  222. package/src/server/events/types.ts +20 -24
  223. package/src/server/index.ts +223 -267
  224. package/src/server/routes/hosted-nodes.ts +83 -0
  225. package/src/server/routes/nat.ts +75 -63
  226. package/src/server/routes/nodes.ts +96 -188
  227. package/src/server/routes/settings.ts +20 -15
  228. package/src/server/routes/status.ts +5 -67
  229. package/src/server/server.ts +8 -8
  230. package/src/server/settings-store.ts +2 -2
  231. package/src/server/static.ts +1 -2
  232. package/src/update/index.ts +2 -2
  233. package/src/update/store.ts +1 -1
  234. package/src/update/types.ts +1 -1
  235. package/dist/auth/duration.d.ts +0 -1
  236. package/dist/auth/duration.js +0 -32
  237. package/dist/auth/duration.js.map +0 -1
  238. package/dist/auth/index.d.ts +0 -11
  239. package/dist/auth/index.js +0 -9
  240. package/dist/auth/index.js.map +0 -1
  241. package/dist/auth/self-label.d.ts +0 -36
  242. package/dist/auth/self-label.js +0 -57
  243. package/dist/auth/self-label.js.map +0 -1
  244. package/dist/auth/trust-circle-store.d.ts +0 -26
  245. package/dist/auth/trust-circle-store.js +0 -129
  246. package/dist/auth/trust-circle-store.js.map +0 -1
  247. package/dist/auth/trust-circle.d.ts +0 -156
  248. package/dist/auth/trust-circle.js +0 -307
  249. package/dist/auth/trust-circle.js.map +0 -1
  250. package/dist/auth/types.d.ts +0 -84
  251. package/dist/auth/types.js +0 -24
  252. package/dist/auth/types.js.map +0 -1
  253. package/dist/donation/donation-service.d.ts +0 -389
  254. package/dist/donation/donation-service.js +0 -828
  255. package/dist/donation/donation-service.js.map +0 -1
  256. package/dist/donation/donation-store.d.ts +0 -51
  257. package/dist/donation/donation-store.js +0 -152
  258. package/dist/donation/donation-store.js.map +0 -1
  259. package/dist/donation/donation-supervisor.js +0 -364
  260. package/dist/donation/donation-supervisor.js.map +0 -1
  261. package/dist/donation/grant-service.d.ts +0 -80
  262. package/dist/donation/grant-service.js +0 -210
  263. package/dist/donation/grant-service.js.map +0 -1
  264. package/dist/donation/grant-store.d.ts +0 -38
  265. package/dist/donation/grant-store.js +0 -135
  266. package/dist/donation/grant-store.js.map +0 -1
  267. package/dist/donation/index.d.ts +0 -20
  268. package/dist/donation/index.js +0 -17
  269. package/dist/donation/index.js.map +0 -1
  270. package/dist/donation/types.d.ts +0 -224
  271. package/dist/donation/types.js +0 -40
  272. package/dist/donation/types.js.map +0 -1
  273. package/dist/installer/identity.js.map +0 -1
  274. package/dist/owner/index.d.ts +0 -11
  275. package/dist/owner/index.js +0 -11
  276. package/dist/owner/index.js.map +0 -1
  277. package/dist/owner/owner-node-client.d.ts +0 -110
  278. package/dist/owner/owner-node-client.js +0 -184
  279. package/dist/owner/owner-node-client.js.map +0 -1
  280. package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
  281. package/dist/server/routes/bootstrap-node-validation.js +0 -197
  282. package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
  283. package/dist/server/routes/grants-admin.d.ts +0 -29
  284. package/dist/server/routes/grants-admin.js +0 -58
  285. package/dist/server/routes/grants-admin.js.map +0 -1
  286. package/dist/server/routes/grants.d.ts +0 -28
  287. package/dist/server/routes/grants.js +0 -165
  288. package/dist/server/routes/grants.js.map +0 -1
  289. package/dist/server/routes/provision-request-validation.d.ts +0 -45
  290. package/dist/server/routes/provision-request-validation.js +0 -97
  291. package/dist/server/routes/provision-request-validation.js.map +0 -1
  292. package/dist/server/routes/strands.d.ts +0 -20
  293. package/dist/server/routes/strands.js +0 -38
  294. package/dist/server/routes/strands.js.map +0 -1
  295. package/dist/server/routes/trust-circle.d.ts +0 -17
  296. package/dist/server/routes/trust-circle.js +0 -35
  297. package/dist/server/routes/trust-circle.js.map +0 -1
  298. package/dist/strands/index.d.ts +0 -7
  299. package/dist/strands/index.js +0 -6
  300. package/dist/strands/index.js.map +0 -1
  301. package/dist/strands/strand-service.d.ts +0 -50
  302. package/dist/strands/strand-service.js +0 -103
  303. package/dist/strands/strand-service.js.map +0 -1
  304. package/dist/strands/types.d.ts +0 -93
  305. package/dist/strands/types.js +0 -23
  306. package/dist/strands/types.js.map +0 -1
  307. package/dist/ui/assets/index-C6nELNfC.js +0 -23
  308. package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
  309. package/dist/ui/assets/index-TtIYSeZO.css +0 -1
  310. package/src/__tests__/cli-invite.smoke.test.ts +0 -192
  311. package/src/__tests__/host-owner.smoke.test.ts +0 -123
  312. package/src/__tests__/orchestrator-owner.test.ts +0 -234
  313. package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
  314. package/src/auth/__tests__/duration.test.ts +0 -28
  315. package/src/auth/__tests__/self-label.test.ts +0 -85
  316. package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
  317. package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
  318. package/src/auth/__tests__/trust-circle.test.ts +0 -445
  319. package/src/auth/duration.ts +0 -33
  320. package/src/auth/index.ts +0 -21
  321. package/src/auth/self-label.ts +0 -74
  322. package/src/auth/trust-circle-store.ts +0 -155
  323. package/src/auth/trust-circle.ts +0 -395
  324. package/src/auth/types.ts +0 -99
  325. package/src/donation/__tests__/donation-service.test.ts +0 -1193
  326. package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
  327. package/src/donation/__tests__/grant-service.test.ts +0 -213
  328. package/src/donation/__tests__/grant-store.test.ts +0 -113
  329. package/src/donation/donation-service.ts +0 -1033
  330. package/src/donation/donation-store.ts +0 -176
  331. package/src/donation/donation-supervisor.ts +0 -411
  332. package/src/donation/grant-service.ts +0 -252
  333. package/src/donation/grant-store.ts +0 -152
  334. package/src/donation/index.ts +0 -65
  335. package/src/donation/types.ts +0 -260
  336. package/src/owner/__tests__/owner-node-client.test.ts +0 -194
  337. package/src/owner/index.ts +0 -12
  338. package/src/owner/owner-node-client.ts +0 -259
  339. package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
  340. package/src/server/__tests__/grants-admin-route.test.ts +0 -171
  341. package/src/server/__tests__/grants-route.test.ts +0 -394
  342. package/src/server/__tests__/strands-route.test.ts +0 -234
  343. package/src/server/routes/bootstrap-node-validation.ts +0 -206
  344. package/src/server/routes/grants-admin.ts +0 -74
  345. package/src/server/routes/grants.ts +0 -193
  346. package/src/server/routes/provision-request-validation.ts +0 -105
  347. package/src/server/routes/strands.ts +0 -54
  348. package/src/server/routes/trust-circle.ts +0 -49
  349. package/src/strands/__tests__/strand-service.test.ts +0 -165
  350. package/src/strands/index.ts +0 -14
  351. package/src/strands/strand-service.ts +0 -135
  352. package/src/strands/types.ts +0 -102
@@ -1,143 +1,124 @@
1
- /**
2
- * The pure port-set logic behind `HostProcessOrchestrator`: the helpers that
3
- * allocate, hold, release and reuse one node's set of ports, and the listen
4
- * addresses a child is started with. No child processes — the behaviour these
5
- * feed is exercised against a stub child in `orchestrator.test.ts`.
6
- */
7
-
8
- import { describe, expect, it } from 'vitest';
9
-
10
- import { PortAllocator } from '@serfab/cadre-provider';
11
-
12
- import { childListenAddrs } from '../orchestrator/host-process-orchestrator.js';
13
- import {
14
- allocateNodePorts,
15
- releaseNodePorts,
16
- reserveNodePorts,
17
- reusedNodePorts,
18
- } from '../orchestrator/port-allocator.js';
19
- import type { NodePorts } from '../orchestrator/types.js';
20
-
21
- /**
22
- * A port set as `state.json` hands it back from a build that predates the `ws` key:
23
- * the type says every key is present, the data does not.
24
- */
25
- function portsWithoutWs(ports: Omit<NodePorts, 'ws'>): NodePorts {
26
- return ports as NodePorts;
27
- }
28
-
29
- describe('allocateNodePorts', () => {
30
- it('allocates the five ports in a fixed order, the newest key last', () => {
31
- const a = new PortAllocator(10000, 10010);
32
- expect(allocateNodePorts(a)).toEqual({ health: 10000, metrics: 10001, p2p: 10002, admin: 10003, ws: 10004 });
33
- });
34
-
35
- // A partial set left reserved would leak from a bounded range on every failed
36
- // provision — the whole reason this helper exists rather than five allocates.
37
- it('is all-or-nothing: an exhausted range releases everything it took', () => {
38
- const a = new PortAllocator(13000, 13002);
39
- expect(() => allocateNodePorts(a)).toThrow(/No available ports/);
40
- expect(a.has(13000)).toBe(false);
41
- expect(a.has(13001)).toBe(false);
42
- expect(a.has(13002)).toBe(false);
43
- });
44
-
45
- // Four ports used to be one node's worth. A range sized for that now fails on the
46
- // fifth, and must neither hand back a set missing `ws` nor keep the four it took.
47
- it('fails cleanly on a range with room for four ports but not five', () => {
48
- const a = new PortAllocator(13000, 13003);
49
- expect(() => allocateNodePorts(a)).toThrow(/No available ports/);
50
- for (let port = 13000; port <= 13003; port++) expect(a.has(port)).toBe(false);
51
- });
52
-
53
- // The owner node's p2p port is pinned by the NAT mapping, not allocated.
54
- it('honours an override and reserves it', () => {
55
- const a = new PortAllocator(10000, 10010);
56
- expect(allocateNodePorts(a, { p2p: 10005 })).toEqual({
57
- health: 10000,
58
- metrics: 10001,
59
- p2p: 10005,
60
- admin: 10002,
61
- ws: 10003,
62
- });
63
- // Reserved, so no later allocation can hand it out again.
64
- expect(a.has(10005)).toBe(true);
65
- });
66
-
67
- // An out-of-range override is the production case for the owner node: its
68
- // libp2p port is chosen by NAT config, not by the allocator, so reserving it
69
- // is a documented no-op rather than an error.
70
- it('accepts an override outside the managed range without reserving it', () => {
71
- const a = new PortAllocator(10000, 10010);
72
- expect(allocateNodePorts(a, { p2p: 40000 })).toMatchObject({ p2p: 40000, health: 10000 });
73
- expect(a.has(40000)).toBe(false);
74
- });
75
-
76
- // A re-spawn passes its previous set as overrides; every one must come back exactly.
77
- it('returns a full set of overrides exactly, allocating nothing', () => {
78
- const a = new PortAllocator(10000, 10010);
79
- const previous = { health: 10006, metrics: 10007, p2p: 10008, admin: 10009, ws: 10010 };
80
- expect(allocateNodePorts(a, previous)).toEqual(previous);
81
- expect(a.allocate()).toBe(10000);
82
- });
83
- });
84
-
85
- describe('reserveNodePorts / releaseNodePorts', () => {
86
- it('hold and then release every key of the set', () => {
87
- const a = new PortAllocator(10000, 10010);
88
- const ports = { health: 10000, metrics: 10001, p2p: 10002, admin: 10003, ws: 10004 };
89
- reserveNodePorts(a, ports);
90
- for (const port of Object.values(ports)) expect(a.has(port)).toBe(true);
91
- releaseNodePorts(a, ports);
92
- for (const port of Object.values(ports)) expect(a.has(port)).toBe(false);
93
- });
94
-
95
- // Rehydrating, or dropping, a `state.json` handle written before `ws` existed.
96
- it('skip a key the set lacks', () => {
97
- const a = new PortAllocator(10000, 10010);
98
- const ports = portsWithoutWs({ health: 10000, metrics: 10001, p2p: 10002, admin: 10003 });
99
- reserveNodePorts(a, ports);
100
- expect(a.has(undefined as unknown as number)).toBe(false);
101
- expect(a.allocate()).toBe(10004);
102
- releaseNodePorts(a, ports);
103
- expect(a.has(10000)).toBe(false);
104
- expect(a.has(10004)).toBe(true);
105
- });
106
- });
107
-
108
- describe('reusedNodePorts', () => {
109
- const previous = { health: 10005, metrics: 10006, p2p: 10007, admin: 10008, ws: 10009 };
110
-
111
- it("returns the dropped handle's whole port set", () => {
112
- expect(reusedNodePorts([{ ports: previous }])).toEqual(previous);
113
- });
114
-
115
- it('returns nothing when nothing was dropped, so every port is allocated fresh', () => {
116
- expect(reusedNodePorts([])).toEqual({});
117
- });
118
-
119
- // A handle persisted before `ws` existed: its other ports come back, `ws` is new.
120
- it('omits a key the dropped handle lacks', () => {
121
- const legacy = portsWithoutWs({ health: 10005, metrics: 10006, p2p: 10007, admin: 10008 });
122
- const reused = reusedNodePorts([{ ports: legacy }]);
123
- expect(reused).toEqual({ health: 10005, metrics: 10006, p2p: 10007, admin: 10008 });
124
- expect('ws' in reused).toBe(false);
125
-
126
- const a = new PortAllocator(10000, 10010);
127
- expect(allocateNodePorts(a, reused)).toEqual({ health: 10005, metrics: 10006, p2p: 10007, admin: 10008, ws: 10000 });
128
- });
129
-
130
- it('takes the last handle when a state holding duplicates dropped several', () => {
131
- const older = { health: 10000, metrics: 10001, p2p: 10002, admin: 10003, ws: 10004 };
132
- expect(reusedNodePorts([{ ports: older }, { ports: previous }])).toEqual(previous);
133
- });
134
- });
135
-
136
- describe('childListenAddrs', () => {
137
- it('names a TCP listener on the p2p port and a WebSocket listener on the ws port, on every interface', () => {
138
- expect(childListenAddrs({ p2p: 10002, ws: 10004 })).toEqual([
139
- '/ip4/0.0.0.0/tcp/10002',
140
- '/ip4/0.0.0.0/tcp/10004/ws',
141
- ]);
142
- });
143
- });
1
+ /**
2
+ * The pure port-set logic behind `HostProcessOrchestrator`: the helpers that
3
+ * allocate, hold, release and reuse one node's set of ports, and the listen
4
+ * addresses a child is started with. No child processes — the behaviour these
5
+ * feed is exercised against a stub child in `orchestrator.test.ts`.
6
+ */
7
+
8
+ import { describe, expect, it } from 'vitest';
9
+
10
+ import { PortAllocator } from '@serfab/cadre-provider';
11
+
12
+ import { childListenAddrs } from '../orchestrator/host-process-orchestrator.js';
13
+ import {
14
+ allocateNodePorts,
15
+ releaseNodePorts,
16
+ reserveNodePorts,
17
+ reusedNodePorts,
18
+ } from '../orchestrator/port-allocator.js';
19
+ import type { NodePorts } from '../orchestrator/types.js';
20
+
21
+ /**
22
+ * A port set as `state.json` hands it back from a build that predates the `ws` key:
23
+ * the type says every key is present, the data does not.
24
+ */
25
+ function portsWithoutWs(ports: Omit<NodePorts, 'ws'>): NodePorts {
26
+ return ports as NodePorts;
27
+ }
28
+
29
+ describe('allocateNodePorts', () => {
30
+ it('allocates the four ports in a fixed order', () => {
31
+ const a = new PortAllocator(10000, 10010);
32
+ expect(allocateNodePorts(a)).toEqual({ health: 10000, metrics: 10001, p2p: 10002, ws: 10003 });
33
+ });
34
+
35
+ // A partial set left reserved would leak from a bounded range on every failed
36
+ // provision — the whole reason this helper exists rather than four allocates.
37
+ it('is all-or-nothing: an exhausted range releases everything it took', () => {
38
+ const a = new PortAllocator(13000, 13002);
39
+ expect(() => allocateNodePorts(a)).toThrow(/No available ports/);
40
+ expect(a.has(13000)).toBe(false);
41
+ expect(a.has(13001)).toBe(false);
42
+ expect(a.has(13002)).toBe(false);
43
+ });
44
+
45
+ it('honours an override and reserves it', () => {
46
+ const a = new PortAllocator(10000, 10010);
47
+ expect(allocateNodePorts(a, { p2p: 10005 })).toEqual({
48
+ health: 10000,
49
+ metrics: 10001,
50
+ p2p: 10005,
51
+ ws: 10002,
52
+ });
53
+ // Reserved, so no later allocation can hand it out again.
54
+ expect(a.has(10005)).toBe(true);
55
+ });
56
+
57
+ // A re-spawn passes its previous set as overrides; every one must come back exactly.
58
+ it('returns a full set of overrides exactly, allocating nothing', () => {
59
+ const a = new PortAllocator(10000, 10010);
60
+ const previous = { health: 10006, metrics: 10007, p2p: 10008, ws: 10009 };
61
+ expect(allocateNodePorts(a, previous)).toEqual(previous);
62
+ expect(a.allocate()).toBe(10000);
63
+ });
64
+ });
65
+
66
+ describe('reserveNodePorts / releaseNodePorts', () => {
67
+ it('hold and then release every key of the set', () => {
68
+ const a = new PortAllocator(10000, 10010);
69
+ const ports = { health: 10000, metrics: 10001, p2p: 10002, ws: 10003 };
70
+ reserveNodePorts(a, ports);
71
+ for (const port of Object.values(ports)) expect(a.has(port)).toBe(true);
72
+ releaseNodePorts(a, ports);
73
+ for (const port of Object.values(ports)) expect(a.has(port)).toBe(false);
74
+ });
75
+
76
+ // Rehydrating, or dropping, a `state.json` handle written before `ws` existed.
77
+ it('skip a key the set lacks', () => {
78
+ const a = new PortAllocator(10000, 10010);
79
+ const ports = portsWithoutWs({ health: 10000, metrics: 10001, p2p: 10002 });
80
+ reserveNodePorts(a, ports);
81
+ expect(a.has(undefined as unknown as number)).toBe(false);
82
+ expect(a.allocate()).toBe(10003);
83
+ releaseNodePorts(a, ports);
84
+ expect(a.has(10000)).toBe(false);
85
+ expect(a.has(10003)).toBe(true);
86
+ });
87
+ });
88
+
89
+ describe('reusedNodePorts', () => {
90
+ const previous = { health: 10005, metrics: 10006, p2p: 10007, ws: 10008 };
91
+
92
+ it("returns the dropped handle's whole port set", () => {
93
+ expect(reusedNodePorts([{ ports: previous }])).toEqual(previous);
94
+ });
95
+
96
+ it('returns nothing when nothing was dropped, so every port is allocated fresh', () => {
97
+ expect(reusedNodePorts([])).toEqual({});
98
+ });
99
+
100
+ // A handle persisted before `ws` existed: its other ports come back, `ws` is new.
101
+ it('omits a key the dropped handle lacks', () => {
102
+ const legacy = portsWithoutWs({ health: 10005, metrics: 10006, p2p: 10007 });
103
+ const reused = reusedNodePorts([{ ports: legacy }]);
104
+ expect(reused).toEqual({ health: 10005, metrics: 10006, p2p: 10007 });
105
+ expect('ws' in reused).toBe(false);
106
+
107
+ const a = new PortAllocator(10000, 10010);
108
+ expect(allocateNodePorts(a, reused)).toEqual({ health: 10005, metrics: 10006, p2p: 10007, ws: 10000 });
109
+ });
110
+
111
+ it('takes the last handle when a state holding duplicates dropped several', () => {
112
+ const older = { health: 10000, metrics: 10001, p2p: 10002, ws: 10003 };
113
+ expect(reusedNodePorts([{ ports: older }, { ports: previous }])).toEqual(previous);
114
+ });
115
+ });
116
+
117
+ describe('childListenAddrs', () => {
118
+ it('names a TCP listener on the p2p port and a WebSocket listener on the ws port, on every interface', () => {
119
+ expect(childListenAddrs({ p2p: 10002, ws: 10004 })).toEqual([
120
+ '/ip4/0.0.0.0/tcp/10002',
121
+ '/ip4/0.0.0.0/tcp/10004/ws',
122
+ ]);
123
+ });
124
+ });
@@ -1,146 +1,138 @@
1
- /**
2
- * HostProcessOrchestrator push-credential injection tests.
3
- *
4
- * Verifies the orchestrator writes `config.push` into the spawned node's
5
- * cadre.json when the pushResolver yields credentials, omits it otherwise,
6
- * re-resolves on every (re-)spawn, and never persists a raw private key in
7
- * state.json. Uses a fake CLI entrypoint that just writes its startup token.
8
- */
9
-
10
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
11
- import { mkdtempSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
12
- import { tmpdir } from 'node:os';
13
- import { join } from 'node:path';
14
-
15
- import type { PushCredentials } from '@serfab/cadre-core';
16
- import { HostProcessOrchestrator, OWNER_CONTAINER_ID } from '../orchestrator/host-process-orchestrator.js';
17
- import { removeAllNodes } from './orchestrator-teardown.js';
18
-
19
- const FAKE_CLI = `
20
- import fs from 'node:fs';
21
- const args = process.argv.slice(2);
22
- const get = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined; };
23
- const tokenPath = get('--startup-token-file');
24
- const token = process.env.CADRE_STARTUP_TOKEN ?? '';
25
- if (tokenPath && token) { try { fs.writeFileSync(tokenPath, token, 'utf8'); } catch (e) { console.error(e); } }
26
- process.on('SIGTERM', () => process.exit(0));
27
- setInterval(() => {}, 1 << 30);
28
- `;
29
-
30
- const FCM = { projectId: 'proj', clientEmail: 'svc@proj.iam', privateKey: 'FCM-SECRET-PEM' };
31
- const APNS = { keyId: 'KID', teamId: 'TEAM', bundleId: 'com.example.app', privateKey: 'APNS-SECRET-P8', production: false };
32
- const CFG = { identityPath: 'C:/fake/identity.key', partyId: 'install-id-123', libp2pPort: 4655 };
33
-
34
- let tmpRoot: string;
35
- let scriptPath: string;
36
- const orchestrators: HostProcessOrchestrator[] = [];
37
-
38
- beforeEach(() => {
39
- tmpRoot = mkdtempSync(join(tmpdir(), 'cadre-host-push-'));
40
- scriptPath = join(tmpRoot, 'fake-cli.mjs');
41
- writeFileSync(scriptPath, FAKE_CLI, 'utf8');
42
- });
43
-
44
- afterEach(async () => {
45
- try {
46
- await removeAllNodes(orchestrators);
47
- } finally {
48
- await sleep(50);
49
- try { rmSync(tmpRoot, { recursive: true, force: true }); } catch { /* ignore */ }
50
- }
51
- });
52
-
53
- function makeOrchestrator(rootDir: string, pushResolver?: () => Promise<PushCredentials | undefined>): HostProcessOrchestrator {
54
- mkdirSync(rootDir, { recursive: true });
55
- const orch = new HostProcessOrchestrator({
56
- rootDir,
57
- portRange: { start: 18000, end: 18499 },
58
- stopTimeoutMs: 1500,
59
- spawn: { entrypoint: scriptPath },
60
- ...(pushResolver ? { pushResolver } : {}),
61
- });
62
- orchestrators.push(orch);
63
- return orch;
64
- }
65
-
66
- function readChildConfig(rootDir: string, containerId = OWNER_CONTAINER_ID): Record<string, unknown> {
67
- const raw = readFileSync(join(rootDir, containerId, 'cadre.json'), 'utf8');
68
- return JSON.parse(raw) as Record<string, unknown>;
69
- }
70
-
71
- function sleep(ms: number): Promise<void> {
72
- return new Promise((res) => setTimeout(res, ms));
73
- }
74
-
75
- describe('HostProcessOrchestrator push injection', () => {
76
- it('writes config.push into cadre.json when the resolver yields credentials', async () => {
77
- const rootDir = join(tmpRoot, 'a');
78
- const orch = makeOrchestrator(rootDir, async () => ({ fcm: FCM, apns: APNS, cooldownMs: 1000 }));
79
- await orch.init();
80
- await orch.ensureOwnerNode(CFG);
81
-
82
- const cfg = readChildConfig(rootDir);
83
- expect(cfg.push).toEqual({ fcm: FCM, apns: APNS, cooldownMs: 1000 });
84
- });
85
-
86
- it('omits config.push when no resolver is configured', async () => {
87
- const rootDir = join(tmpRoot, 'b');
88
- const orch = makeOrchestrator(rootDir);
89
- await orch.init();
90
- await orch.ensureOwnerNode(CFG);
91
-
92
- const cfg = readChildConfig(rootDir);
93
- expect(cfg.push).toBeUndefined();
94
- });
95
-
96
- it('omits config.push when the resolver returns undefined (push opt-in)', async () => {
97
- const rootDir = join(tmpRoot, 'c');
98
- const orch = makeOrchestrator(rootDir, async () => undefined);
99
- await orch.init();
100
- await orch.ensureOwnerNode(CFG);
101
-
102
- expect(readChildConfig(rootDir).push).toBeUndefined();
103
- });
104
-
105
- it('degrades to no push (does not fail the spawn) when the resolver throws', async () => {
106
- const rootDir = join(tmpRoot, 'd');
107
- const orch = makeOrchestrator(rootDir, async () => { throw new Error('partial creds'); });
108
- await orch.init();
109
- // Spawn still succeeds.
110
- const node = await orch.ensureOwnerNode(CFG);
111
- expect(node.id).toBe(OWNER_CONTAINER_ID);
112
- expect(readChildConfig(rootDir).push).toBeUndefined();
113
- });
114
-
115
- it('re-resolves on re-spawn and never persists a raw private key in state.json', async () => {
116
- const rootDir = join(tmpRoot, 'e');
117
- let current: PushCredentials | undefined = { fcm: FCM };
118
- const orch = makeOrchestrator(rootDir, async () => current);
119
- await orch.init();
120
- await orch.ensureOwnerNode(CFG);
121
- expect(readChildConfig(rootDir).push).toEqual({ fcm: FCM });
122
-
123
- // state.json must not carry the raw key — only re-resolvable references.
124
- const stateRaw = readFileSync(join(rootDir, 'state.json'), 'utf8');
125
- expect(stateRaw).not.toContain('FCM-SECRET-PEM');
126
-
127
- // Rotate the resolver's answer, then restart — the new spawn must pick it up.
128
- current = { apns: APNS };
129
- await orch.restartOwnerNode();
130
- const cfg2 = readChildConfig(rootDir);
131
- expect(cfg2.push).toEqual({ apns: APNS });
132
- expect((cfg2.push as PushCredentials).fcm).toBeUndefined();
133
- });
134
-
135
- it('injects push for a storage-profile managed node but not a transaction node', async () => {
136
- const rootDir = join(tmpRoot, 'f');
137
- const orch = makeOrchestrator(rootDir, async () => ({ fcm: FCM }));
138
- await orch.init();
139
-
140
- await orch.createContainer({ containerId: 'storage-1', partyId: 'p', bootstrapNodes: [], profile: 'storage' });
141
- expect(readChildConfig(rootDir, 'storage-1').push).toEqual({ fcm: FCM });
142
-
143
- await orch.createContainer({ containerId: 'txn-1', partyId: 'p', bootstrapNodes: [], profile: 'transaction' });
144
- expect(readChildConfig(rootDir, 'txn-1').push).toBeUndefined();
145
- });
146
- });
1
+ /**
2
+ * HostProcessOrchestrator push-credential injection tests.
3
+ *
4
+ * Verifies the orchestrator writes `config.push` into the spawned node's
5
+ * cadre.json when the pushResolver yields credentials, omits it otherwise,
6
+ * re-resolves on every (re-)spawn, and never persists a raw private key in
7
+ * state.json. Uses a fake CLI entrypoint that just writes its startup token.
8
+ */
9
+
10
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
11
+ import { mkdtempSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
12
+ import { tmpdir } from 'node:os';
13
+ import { join } from 'node:path';
14
+
15
+ import type { PushCredentials } from '@serfab/cadre-core';
16
+ import { HostProcessOrchestrator } from '../orchestrator/host-process-orchestrator.js';
17
+ import { removeAllNodes } from './orchestrator-teardown.js';
18
+
19
+ const FAKE_CLI = `
20
+ import fs from 'node:fs';
21
+ const args = process.argv.slice(2);
22
+ const get = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined; };
23
+ const tokenPath = get('--startup-token-file');
24
+ const token = process.env.CADRE_STARTUP_TOKEN ?? '';
25
+ if (tokenPath && token) { try { fs.writeFileSync(tokenPath, token, 'utf8'); } catch (e) { console.error(e); } }
26
+ process.on('SIGTERM', () => process.exit(0));
27
+ setInterval(() => {}, 1 << 30);
28
+ `;
29
+
30
+ const FCM = { projectId: 'proj', clientEmail: 'svc@proj.iam', privateKey: 'FCM-SECRET-PEM' };
31
+ const APNS = { keyId: 'KID', teamId: 'TEAM', bundleId: 'com.example.app', privateKey: 'APNS-SECRET-P8', production: false };
32
+ /** A storage node — the one profile whose spawn carries push. */
33
+ const NODE = { containerId: 'n1', partyId: 'p', profile: 'storage' as const };
34
+
35
+ let tmpRoot: string;
36
+ let scriptPath: string;
37
+ const orchestrators: HostProcessOrchestrator[] = [];
38
+
39
+ beforeEach(() => {
40
+ tmpRoot = mkdtempSync(join(tmpdir(), 'cadre-host-push-'));
41
+ scriptPath = join(tmpRoot, 'fake-cli.mjs');
42
+ writeFileSync(scriptPath, FAKE_CLI, 'utf8');
43
+ });
44
+
45
+ afterEach(async () => {
46
+ try {
47
+ await removeAllNodes(orchestrators);
48
+ } finally {
49
+ await sleep(50);
50
+ try { rmSync(tmpRoot, { recursive: true, force: true }); } catch { /* ignore */ }
51
+ }
52
+ });
53
+
54
+ function makeOrchestrator(rootDir: string, pushResolver?: () => Promise<PushCredentials | undefined>): HostProcessOrchestrator {
55
+ mkdirSync(rootDir, { recursive: true });
56
+ const orch = new HostProcessOrchestrator({
57
+ rootDir,
58
+ portRange: { start: 18000, end: 18499 },
59
+ stopTimeoutMs: 1500,
60
+ spawn: { entrypoint: scriptPath },
61
+ ...(pushResolver ? { pushResolver } : {}),
62
+ });
63
+ orchestrators.push(orch);
64
+ return orch;
65
+ }
66
+
67
+ function readChildConfig(rootDir: string, containerId = NODE.containerId): Record<string, unknown> {
68
+ const raw = readFileSync(join(rootDir, containerId, 'cadre.json'), 'utf8');
69
+ return JSON.parse(raw) as Record<string, unknown>;
70
+ }
71
+
72
+ function sleep(ms: number): Promise<void> {
73
+ return new Promise((res) => setTimeout(res, ms));
74
+ }
75
+
76
+ describe('HostProcessOrchestrator push injection', () => {
77
+ it('omits config.push when no resolver is configured', async () => {
78
+ const rootDir = join(tmpRoot, 'b');
79
+ const orch = makeOrchestrator(rootDir);
80
+ await orch.init();
81
+ await orch.createContainer(NODE);
82
+
83
+ const cfg = readChildConfig(rootDir);
84
+ expect(cfg.push).toBeUndefined();
85
+ });
86
+
87
+ it('omits config.push when the resolver returns undefined (push opt-in)', async () => {
88
+ const rootDir = join(tmpRoot, 'c');
89
+ const orch = makeOrchestrator(rootDir, async () => undefined);
90
+ await orch.init();
91
+ await orch.createContainer(NODE);
92
+
93
+ expect(readChildConfig(rootDir).push).toBeUndefined();
94
+ });
95
+
96
+ it('degrades to no push (does not fail the spawn) when the resolver throws', async () => {
97
+ const rootDir = join(tmpRoot, 'd');
98
+ const orch = makeOrchestrator(rootDir, async () => { throw new Error('partial creds'); });
99
+ await orch.init();
100
+ // Spawn still succeeds.
101
+ const created = await orch.createContainer(NODE);
102
+ expect(orch.getNode(created.dockerId)?.status).toBe('running');
103
+ expect(readChildConfig(rootDir).push).toBeUndefined();
104
+ });
105
+
106
+ it('re-resolves on re-spawn and never persists a raw private key in state.json', async () => {
107
+ const rootDir = join(tmpRoot, 'e');
108
+ let current: PushCredentials | undefined = { fcm: FCM };
109
+ const orch = makeOrchestrator(rootDir, async () => current);
110
+ await orch.init();
111
+ const first = await orch.createContainer(NODE);
112
+ expect(readChildConfig(rootDir).push).toEqual({ fcm: FCM });
113
+
114
+ // state.json must not carry the raw key — only re-resolvable references.
115
+ const stateRaw = readFileSync(join(rootDir, 'state.json'), 'utf8');
116
+ expect(stateRaw).not.toContain('FCM-SECRET-PEM');
117
+
118
+ // Rotate the resolver's answer, then re-spawn — the new spawn must pick it up.
119
+ current = { apns: APNS };
120
+ await orch.stopContainer(first.dockerId);
121
+ await orch.createContainer(NODE);
122
+ const cfg2 = readChildConfig(rootDir);
123
+ expect(cfg2.push).toEqual({ apns: APNS });
124
+ expect((cfg2.push as PushCredentials).fcm).toBeUndefined();
125
+ });
126
+
127
+ it('injects push for a storage-profile managed node but not a transaction node', async () => {
128
+ const rootDir = join(tmpRoot, 'f');
129
+ const orch = makeOrchestrator(rootDir, async () => ({ fcm: FCM, apns: APNS, cooldownMs: 1000 }));
130
+ await orch.init();
131
+
132
+ await orch.createContainer({ containerId: 'storage-1', partyId: 'p', profile: 'storage' });
133
+ expect(readChildConfig(rootDir, 'storage-1').push).toEqual({ fcm: FCM, apns: APNS, cooldownMs: 1000 });
134
+
135
+ await orch.createContainer({ containerId: 'txn-1', partyId: 'p', profile: 'transaction' });
136
+ expect(readChildConfig(rootDir, 'txn-1').push).toBeUndefined();
137
+ });
138
+ });