@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,80 +0,0 @@
1
- import type { DonationService } from './donation-service.js';
2
- import type { GrantStore } from './grant-store.js';
3
- import type { Grant, GrantAdminHandlers, GrantValidation, GrantValidator } from './types.js';
4
- /** Default node cap when the admin doesn't specify one. */
5
- export declare const DEFAULT_MAX_NODES = 1;
6
- /** Constructor options. */
7
- export interface GrantServiceOptions {
8
- /** Persistent grant store (`grants.json`). */
9
- store: GrantStore;
10
- /** Clock override for tests. */
11
- now?: () => Date;
12
- }
13
- /**
14
- * GrantService — issues, validates, lists, and revokes donation grant tokens.
15
- *
16
- * It is the authority for a grant's *identity, expiry, and revocation*, and
17
- * for the per-grantee *node cap* (`maxNodes`). It is deliberately **not** the
18
- * authority for how many nodes are currently live under a grant — that tally
19
- * belongs to the donation service (`2-donation-service`), which persists the
20
- * grant→node records and can see terminations. `validateForProvision` takes
21
- * the count as a parameter so the two never drift.
22
- *
23
- * All methods are synchronous: issuance/validation/revocation are pure local
24
- * store operations with no node round-trip (unlike the trust-circle flow,
25
- * which delegates to the owner node).
26
- */
27
- export declare class GrantService implements GrantValidator {
28
- private readonly store;
29
- private readonly now;
30
- constructor(opts: GrantServiceOptions);
31
- /**
32
- * Issue a new grant. Generates a high-entropy base64url token, persists it,
33
- * and returns the whole `Grant` (the CLI/UI prints/QR-encodes the token).
34
- */
35
- issue(opts: {
36
- label: string;
37
- maxNodes?: number;
38
- ttlMs?: number;
39
- }): Grant;
40
- /**
41
- * Validate a presented bearer token for a non-provisioning op: it must name a
42
- * known grant that is neither revoked nor expired. No quota check. Never
43
- * throws for a bad token — returns a denial.
44
- */
45
- validate(token: string): GrantValidation;
46
- /**
47
- * Validate for a *new* provision request. Runs the identity/expiry/revocation
48
- * checks of `validate`, then — only if those pass — consults the
49
- * caller-supplied live-node tally against the grant's `maxNodes`.
50
- *
51
- * Concurrency boundary: this is a pure function of the passed count. Two
52
- * provision requests racing at the quota edge (both seeing count = N-1)
53
- * would both pass here; serialising them so only one wins is the donation
54
- * service's responsibility (`2-donation-service` owns the grant→node lock).
55
- */
56
- validateForProvision(token: string, liveNodeCount: (token: string) => number): GrantValidation;
57
- /**
58
- * Revoke a grant by token: mark it revoked (denies future requests).
59
- * Idempotent on an already-revoked grant; throws not_found when the token is
60
- * unknown.
61
- *
62
- * This only marks the grant. Tearing down the nodes already donated under it
63
- * is `DonationService.terminateGrant`, which the admin handler
64
- * ({@link createGrantAdminHandlers}) runs after this unless asked to keep them.
65
- */
66
- revoke(token: string): void;
67
- /** Enumerate all grants (for the CLI / UI). */
68
- list(): Grant[];
69
- }
70
- /**
71
- * Wrap a GrantService into the typed handler shape consumed by the loopback
72
- * management server's `/grants-admin` routes. Errors propagate as GrantError /
73
- * DonationError; the server maps `.code` → HTTP status.
74
- *
75
- * `donations` is where revoke's node teardown, the single-donation terminate and
76
- * the listing's per-grant node counts come from. Without it there can be no
77
- * donated nodes, so revoke only marks the grant, every grant lists with no
78
- * nodes, and `terminateDonation` is left off.
79
- */
80
- export declare function createGrantAdminHandlers(service: GrantService, donations?: Pick<DonationService, 'terminate' | 'terminateGrant' | 'list'>): GrantAdminHandlers;
@@ -1,210 +0,0 @@
1
- import { randomBytes } from 'node:crypto';
2
- import debug from 'debug';
3
- import { isLiveDonationStatus } from './donation-store.js';
4
- import { GrantError } from './types.js';
5
- const log = debug('cadre:host:grant-service');
6
- /** Token byte length (base64url-encoded; ~43 chars). Matches trust-circle. */
7
- const TOKEN_BYTES = 32;
8
- /** Default node cap when the admin doesn't specify one. */
9
- export const DEFAULT_MAX_NODES = 1;
10
- /**
11
- * GrantService — issues, validates, lists, and revokes donation grant tokens.
12
- *
13
- * It is the authority for a grant's *identity, expiry, and revocation*, and
14
- * for the per-grantee *node cap* (`maxNodes`). It is deliberately **not** the
15
- * authority for how many nodes are currently live under a grant — that tally
16
- * belongs to the donation service (`2-donation-service`), which persists the
17
- * grant→node records and can see terminations. `validateForProvision` takes
18
- * the count as a parameter so the two never drift.
19
- *
20
- * All methods are synchronous: issuance/validation/revocation are pure local
21
- * store operations with no node round-trip (unlike the trust-circle flow,
22
- * which delegates to the owner node).
23
- */
24
- export class GrantService {
25
- constructor(opts) {
26
- this.store = opts.store;
27
- this.now = opts.now ?? (() => new Date());
28
- }
29
- /**
30
- * Issue a new grant. Generates a high-entropy base64url token, persists it,
31
- * and returns the whole `Grant` (the CLI/UI prints/QR-encodes the token).
32
- */
33
- issue(opts) {
34
- const label = normaliseLabel(opts.label);
35
- const maxNodes = normaliseMaxNodes(opts.maxNodes);
36
- const created = this.now();
37
- const grant = {
38
- token: generateToken(),
39
- label,
40
- maxNodes,
41
- createdAt: created.toISOString(),
42
- };
43
- if (opts.ttlMs !== undefined) {
44
- if (!Number.isFinite(opts.ttlMs) || opts.ttlMs <= 0) {
45
- throw new GrantError('invalid_ttl', `Invalid ttlMs: ${opts.ttlMs}`);
46
- }
47
- grant.expiresAt = new Date(created.getTime() + opts.ttlMs).toISOString();
48
- }
49
- this.store.add(grant);
50
- log('issued grant "%s" (token=%s, maxNodes=%d, expires=%s)', label, grant.token, maxNodes, grant.expiresAt ?? 'never');
51
- return grant;
52
- }
53
- /**
54
- * Validate a presented bearer token for a non-provisioning op: it must name a
55
- * known grant that is neither revoked nor expired. No quota check. Never
56
- * throws for a bad token — returns a denial.
57
- */
58
- validate(token) {
59
- if (!token)
60
- return { ok: false, reason: 'unknown_token' };
61
- const grant = this.store.get(token);
62
- if (!grant)
63
- return { ok: false, reason: 'unknown_token' };
64
- // Revocation is an explicit admin action; check it before expiry so a
65
- // grant that is both revoked and expired reports the stronger signal.
66
- if (grant.revokedAt)
67
- return { ok: false, grant, reason: 'revoked' };
68
- if (grant.expiresAt) {
69
- const expiresAt = new Date(grant.expiresAt);
70
- if (Number.isFinite(expiresAt.getTime()) && this.now() > expiresAt) {
71
- return { ok: false, grant, reason: 'expired' };
72
- }
73
- }
74
- return { ok: true, grant };
75
- }
76
- /**
77
- * Validate for a *new* provision request. Runs the identity/expiry/revocation
78
- * checks of `validate`, then — only if those pass — consults the
79
- * caller-supplied live-node tally against the grant's `maxNodes`.
80
- *
81
- * Concurrency boundary: this is a pure function of the passed count. Two
82
- * provision requests racing at the quota edge (both seeing count = N-1)
83
- * would both pass here; serialising them so only one wins is the donation
84
- * service's responsibility (`2-donation-service` owns the grant→node lock).
85
- */
86
- validateForProvision(token, liveNodeCount) {
87
- const base = this.validate(token);
88
- if (!base.ok || !base.grant)
89
- return base;
90
- const live = liveNodeCount(token);
91
- if (live >= base.grant.maxNodes) {
92
- return { ok: false, grant: base.grant, reason: 'quota_exceeded' };
93
- }
94
- return { ok: true, grant: base.grant };
95
- }
96
- /**
97
- * Revoke a grant by token: mark it revoked (denies future requests).
98
- * Idempotent on an already-revoked grant; throws not_found when the token is
99
- * unknown.
100
- *
101
- * This only marks the grant. Tearing down the nodes already donated under it
102
- * is `DonationService.terminateGrant`, which the admin handler
103
- * ({@link createGrantAdminHandlers}) runs after this unless asked to keep them.
104
- */
105
- revoke(token) {
106
- if (!this.store.markRevoked(token, this.now().toISOString())) {
107
- throw new GrantError('not_found', `Unknown grant: ${token}`);
108
- }
109
- log('revoked grant (token=%s)', token);
110
- }
111
- /** Enumerate all grants (for the CLI / UI). */
112
- list() {
113
- return this.store.list();
114
- }
115
- }
116
- /**
117
- * Wrap a GrantService into the typed handler shape consumed by the loopback
118
- * management server's `/grants-admin` routes. Errors propagate as GrantError /
119
- * DonationError; the server maps `.code` → HTTP status.
120
- *
121
- * `donations` is where revoke's node teardown, the single-donation terminate and
122
- * the listing's per-grant node counts come from. Without it there can be no
123
- * donated nodes, so revoke only marks the grant, every grant lists with no
124
- * nodes, and `terminateDonation` is left off.
125
- */
126
- export function createGrantAdminHandlers(service, donations) {
127
- const handlers = {
128
- async postGrant(body) {
129
- if (!body || typeof body.label !== 'string') {
130
- throw new GrantError('invalid_label', 'label is required');
131
- }
132
- const grant = service.issue({
133
- label: body.label,
134
- ...(body.maxNodes !== undefined ? { maxNodes: body.maxNodes } : {}),
135
- ...(body.ttlMs !== undefined ? { ttlMs: body.ttlMs } : {}),
136
- });
137
- return { grant };
138
- },
139
- async listGrants() {
140
- const byGrant = groupByGrant(donations?.list() ?? []);
141
- return { grants: service.list().map((grant) => toListing(grant, byGrant.get(grant.token) ?? [])) };
142
- },
143
- async deleteGrant(token, { keepNodes }) {
144
- // Revoke first: it throws not_found for an unknown token before any
145
- // teardown, and a provision racing this call is then refused as revoked.
146
- service.revoke(token);
147
- // NOTE: the teardown runs inside the HTTP request, one stop + reclaim per
148
- // node in series. Fine for a handful of nodes per grant; if grants ever
149
- // carry many, answer after the revoke and tear down in the background.
150
- if (keepNodes || !donations)
151
- return { terminated: [] };
152
- return { terminated: await donations.terminateGrant(token) };
153
- },
154
- };
155
- if (donations) {
156
- handlers.terminateDonation = (id) => donations.terminate(id);
157
- }
158
- return handlers;
159
- }
160
- /** One read of the donation store, bucketed by grant token. */
161
- function groupByGrant(donations) {
162
- const byGrant = new Map();
163
- for (const donation of donations) {
164
- const bucket = byGrant.get(donation.grantToken);
165
- if (bucket)
166
- bucket.push(donation);
167
- else
168
- byGrant.set(donation.grantToken, [donation]);
169
- }
170
- return byGrant;
171
- }
172
- /**
173
- * `liveNodes` uses the quota's own rule; `donations` is the set a revoke tears
174
- * down (every record not yet `terminated`, `error` included), so the admin can be
175
- * told the real count before confirming.
176
- */
177
- function toListing(grant, donations) {
178
- const unterminated = donations.filter((d) => d.status !== 'terminated');
179
- return {
180
- ...grant,
181
- liveNodes: unterminated.filter((d) => isLiveDonationStatus(d.status)).length,
182
- donations: unterminated.map(({ id, status }) => ({ id, status })),
183
- };
184
- }
185
- function generateToken() {
186
- // base64url so it survives in URLs, QR codes, and shell arguments.
187
- return randomBytes(TOKEN_BYTES).toString('base64url');
188
- }
189
- function normaliseLabel(label) {
190
- if (typeof label !== 'string') {
191
- throw new GrantError('invalid_label', 'label must be a string');
192
- }
193
- const trimmed = label.trim();
194
- if (trimmed.length === 0) {
195
- throw new GrantError('invalid_label', 'label must not be empty');
196
- }
197
- if (trimmed.length > 200) {
198
- throw new GrantError('invalid_label', 'label must be 200 characters or fewer');
199
- }
200
- return trimmed;
201
- }
202
- function normaliseMaxNodes(maxNodes) {
203
- if (maxNodes === undefined)
204
- return DEFAULT_MAX_NODES;
205
- if (typeof maxNodes !== 'number' || !Number.isInteger(maxNodes) || maxNodes < 1) {
206
- throw new GrantError('invalid_max_nodes', `maxNodes must be a positive integer (got ${JSON.stringify(maxNodes)})`);
207
- }
208
- return maxNodes;
209
- }
210
- //# sourceMappingURL=grant-service.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"grant-service.js","sourceRoot":"","sources":["../../src/donation/grant-service.ts"],"names":[],"mappings":"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"}
@@ -1,38 +0,0 @@
1
- import type { Grant, GrantFile } from './types.js';
2
- /**
3
- * Atomic JSON store for `grants.json`. Modelled on `auth/trust-circle-store.ts`:
4
- * a single file, written to `<path>.tmp` then renamed, with an in-memory cache
5
- * keyed by token.
6
- *
7
- * Unlike the trust-circle pending rows, grants are **not** self-reaping: a
8
- * grant may legitimately outlive many nodes, so expiry is evaluated at
9
- * validate time (in `GrantService`) and the admin removes stale grants
10
- * explicitly. This store keeps no clock.
11
- *
12
- * Concurrency assumption: only one cadre-host process owns a given rootDir
13
- * (the orchestrator already enforces this). No file-locking primitives.
14
- */
15
- export declare class GrantStore {
16
- private readonly path;
17
- private cache;
18
- constructor(rootDir: string);
19
- filePath(): string;
20
- /**
21
- * Load (with caching). A missing file returns an empty store (fresh
22
- * install). A present-but-malformed file throws — silently wiping it would
23
- * revoke every grantee at once.
24
- */
25
- load(): GrantFile;
26
- /** Persist the current state atomically (write then rename). */
27
- save(state: GrantFile): void;
28
- add(grant: Grant): void;
29
- get(token: string): Grant | undefined;
30
- list(): Grant[];
31
- remove(token: string): boolean;
32
- /**
33
- * Mark a grant revoked at the given ISO instant. Idempotent: an
34
- * already-revoked grant keeps its original `revokedAt`. Returns false when
35
- * the token is unknown.
36
- */
37
- markRevoked(token: string, at: string): boolean;
38
- }
@@ -1,135 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
3
- import debug from 'debug';
4
- import { GrantError } from './types.js';
5
- const log = debug('cadre:host:grant-store');
6
- const FILE_VERSION = 1;
7
- /**
8
- * Atomic JSON store for `grants.json`. Modelled on `auth/trust-circle-store.ts`:
9
- * a single file, written to `<path>.tmp` then renamed, with an in-memory cache
10
- * keyed by token.
11
- *
12
- * Unlike the trust-circle pending rows, grants are **not** self-reaping: a
13
- * grant may legitimately outlive many nodes, so expiry is evaluated at
14
- * validate time (in `GrantService`) and the admin removes stale grants
15
- * explicitly. This store keeps no clock.
16
- *
17
- * Concurrency assumption: only one cadre-host process owns a given rootDir
18
- * (the orchestrator already enforces this). No file-locking primitives.
19
- */
20
- export class GrantStore {
21
- constructor(rootDir) {
22
- this.cache = null;
23
- mkdirSync(rootDir, { recursive: true });
24
- this.path = join(rootDir, 'grants.json');
25
- }
26
- filePath() {
27
- return this.path;
28
- }
29
- /**
30
- * Load (with caching). A missing file returns an empty store (fresh
31
- * install). A present-but-malformed file throws — silently wiping it would
32
- * revoke every grantee at once.
33
- */
34
- load() {
35
- if (this.cache)
36
- return this.cache;
37
- if (!existsSync(this.path)) {
38
- this.cache = emptyFile();
39
- return this.cache;
40
- }
41
- let raw;
42
- try {
43
- raw = readFileSync(this.path, 'utf8');
44
- }
45
- catch (err) {
46
- throw new GrantError('storage_error', `failed to read grants file at ${this.path}: ${err.message}`);
47
- }
48
- let parsed;
49
- try {
50
- parsed = JSON.parse(raw);
51
- }
52
- catch (err) {
53
- throw new GrantError('storage_error', `grants file at ${this.path} is not valid JSON: ${err.message}`);
54
- }
55
- if (!isGrantFile(parsed)) {
56
- throw new GrantError('storage_error', `grants file at ${this.path} has unexpected shape`);
57
- }
58
- this.cache = parsed;
59
- return this.cache;
60
- }
61
- /** Persist the current state atomically (write then rename). */
62
- save(state) {
63
- // NOTE: rewrites the whole grants.json on every mutation. Fine at household
64
- // scale (a handful of grantees); if grant counts ever grow large, switch to
65
- // an append/compact log or per-token files.
66
- mkdirSync(dirname(this.path), { recursive: true });
67
- const payload = JSON.stringify({ ...state, version: FILE_VERSION }, null, 2);
68
- const tmp = `${this.path}.tmp`;
69
- try {
70
- writeFileSync(tmp, payload, { encoding: 'utf8' });
71
- renameSync(tmp, this.path);
72
- }
73
- catch (err) {
74
- // Surface as storage_error (→ 500) like load(), not a bare Error (→
75
- // "internal"); a write failure on issue/revoke should read consistently.
76
- throw new GrantError('storage_error', `failed to write grants file at ${this.path}: ${err.message}`);
77
- }
78
- this.cache = { ...state, version: FILE_VERSION };
79
- log('saved grants (%d) to %s', Object.keys(state.grants).length, this.path);
80
- }
81
- add(grant) {
82
- const state = this.load();
83
- const { token, ...rest } = grant;
84
- state.grants[token] = rest;
85
- this.save(state);
86
- }
87
- get(token) {
88
- const state = this.load();
89
- const row = state.grants[token];
90
- if (!row)
91
- return undefined;
92
- return { token, ...row };
93
- }
94
- list() {
95
- const state = this.load();
96
- return Object.entries(state.grants).map(([token, row]) => ({ token, ...row }));
97
- }
98
- remove(token) {
99
- const state = this.load();
100
- if (!(token in state.grants))
101
- return false;
102
- delete state.grants[token];
103
- this.save(state);
104
- return true;
105
- }
106
- /**
107
- * Mark a grant revoked at the given ISO instant. Idempotent: an
108
- * already-revoked grant keeps its original `revokedAt`. Returns false when
109
- * the token is unknown.
110
- */
111
- markRevoked(token, at) {
112
- const state = this.load();
113
- const row = state.grants[token];
114
- if (!row)
115
- return false;
116
- if (!row.revokedAt)
117
- row.revokedAt = at;
118
- this.save(state);
119
- return true;
120
- }
121
- }
122
- function emptyFile() {
123
- return { version: FILE_VERSION, grants: {} };
124
- }
125
- function isGrantFile(v) {
126
- if (!v || typeof v !== 'object')
127
- return false;
128
- const obj = v;
129
- if (obj.version !== FILE_VERSION)
130
- return false;
131
- if (!obj.grants || typeof obj.grants !== 'object')
132
- return false;
133
- return true;
134
- }
135
- //# sourceMappingURL=grant-store.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"grant-store.js","sourceRoot":"","sources":["../../src/donation/grant-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,GAAG,GAAG,KAAK,CAAC,wBAAwB,CAAC,CAAC;AAE5C,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,UAAU;IAIrB,YAAY,OAAe;QAFnB,UAAK,GAAqB,IAAI,CAAC;QAGrC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAC3C,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,IAAI;QACF,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC;QAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,KAAK,GAAG,SAAS,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,KAAK,CAAC;QACpB,CAAC;QACD,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,UAAU,CAClB,eAAe,EACf,iCAAiC,IAAI,CAAC,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CACxE,CAAC;QACJ,CAAC;QACD,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,UAAU,CAClB,eAAe,EACf,kBAAkB,IAAI,CAAC,IAAI,uBAAwB,GAAa,CAAC,OAAO,EAAE,CAC3E,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,UAAU,CAClB,eAAe,EACf,kBAAkB,IAAI,CAAC,IAAI,uBAAuB,CACnD,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC;QACpB,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,gEAAgE;IAChE,IAAI,CAAC,KAAgB;QACnB,4EAA4E;QAC5E,4EAA4E;QAC5E,4CAA4C;QAC5C,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7E,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,MAAM,CAAC;QAC/B,IAAI,CAAC;YACH,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;YAClD,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,oEAAoE;YACpE,yEAAyE;YACzE,MAAM,IAAI,UAAU,CAClB,eAAe,EACf,kCAAkC,IAAI,CAAC,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CACzE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;QACjD,GAAG,CAAC,yBAAyB,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9E,CAAC;IAED,GAAG,CAAC,KAAY;QACd,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;QACjC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IAED,GAAG,CAAC,KAAa;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,EAAE,CAAC;IAC3B,CAAC;IAED,IAAI;QACF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;IACjF,CAAC;IAED,MAAM,CAAC,KAAa;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3C,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,WAAW,CAAC,KAAa,EAAE,EAAU;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC;QACvB,IAAI,CAAC,GAAG,CAAC,SAAS;YAAE,GAAG,CAAC,SAAS,GAAG,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,SAAS,SAAS;IAChB,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AAC/C,CAAC;AAED,SAAS,WAAW,CAAC,CAAU;IAC7B,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC9C,MAAM,GAAG,GAAG,CAA4B,CAAC;IACzC,IAAI,GAAG,CAAC,OAAO,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAChE,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1,20 +0,0 @@
1
- /**
2
- * Donation grant-token layer for cadre-host. See ./types.ts, ./grant-store.ts,
3
- * and ./grant-service.ts.
4
- *
5
- * This layer gates *who may ask this host to donate a node*. The lifecycle
6
- * service that consumes a validated grant to actually provision a node lands in
7
- * the `2-donation-service` ticket, which imports the `GrantValidator` interface
8
- * defined here.
9
- */
10
- export { GrantStore } from './grant-store.js';
11
- export { GrantService, createGrantAdminHandlers, DEFAULT_MAX_NODES, } from './grant-service.js';
12
- export type { GrantServiceOptions } from './grant-service.js';
13
- export { GrantError, DonationError } from './types.js';
14
- export type { Grant, GrantListing, GrantDenyReason, GrantValidation, GrantValidator, GrantFile, GrantAdminHandlers, GrantErrorCode, } from './types.js';
15
- export { DonationStore, isLiveDonationStatus } from './donation-store.js';
16
- export { DonationService, DONATION_AWAITING_SEED_TTL_MS, DONATION_PROVISIONING_TTL_MS, DONATION_REAP_SWEEP_MS, } from './donation-service.js';
17
- export type { DonationOrchestrator, DonationProvisionRequest, DonationSeedResult, DonationPeerInfo, DonationServiceOptions, RespawnResult, } from './donation-service.js';
18
- export { DonationSupervisor, DONATION_RESPAWN_BACKOFF_BASE_MS, DONATION_RESPAWN_BACKOFF_MAX_MS, DONATION_RESPAWN_MAX_ATTEMPTS, DONATION_RESPAWN_HEALTHY_MS, DONATION_RESPAWN_SWEEP_MS, } from './donation-supervisor.js';
19
- export type { DonationSupervisorOptions, SupervisedOrchestrator, } from './donation-supervisor.js';
20
- export type { Donation, DonationView, DonationStatus, DonationFile, DonationErrorCode, } from './types.js';
@@ -1,17 +0,0 @@
1
- /**
2
- * Donation grant-token layer for cadre-host. See ./types.ts, ./grant-store.ts,
3
- * and ./grant-service.ts.
4
- *
5
- * This layer gates *who may ask this host to donate a node*. The lifecycle
6
- * service that consumes a validated grant to actually provision a node lands in
7
- * the `2-donation-service` ticket, which imports the `GrantValidator` interface
8
- * defined here.
9
- */
10
- export { GrantStore } from './grant-store.js';
11
- export { GrantService, createGrantAdminHandlers, DEFAULT_MAX_NODES, } from './grant-service.js';
12
- export { GrantError, DonationError } from './types.js';
13
- /* ──────────────── donation lifecycle (2-donation-service) ──────────────── */
14
- export { DonationStore, isLiveDonationStatus } from './donation-store.js';
15
- export { DonationService, DONATION_AWAITING_SEED_TTL_MS, DONATION_PROVISIONING_TTL_MS, DONATION_REAP_SWEEP_MS, } from './donation-service.js';
16
- export { DonationSupervisor, DONATION_RESPAWN_BACKOFF_BASE_MS, DONATION_RESPAWN_BACKOFF_MAX_MS, DONATION_RESPAWN_MAX_ATTEMPTS, DONATION_RESPAWN_HEALTHY_MS, DONATION_RESPAWN_SWEEP_MS, } from './donation-supervisor.js';
17
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/donation/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAYvD,+EAA+E;AAE/E,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EACL,eAAe,EACf,6BAA6B,EAC7B,4BAA4B,EAC5B,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAS/B,OAAO,EACL,kBAAkB,EAClB,gCAAgC,EAChC,+BAA+B,EAC/B,6BAA6B,EAC7B,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,0BAA0B,CAAC"}