@elaraai/e3-rack 0.0.0-stage → 1.0.95

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 (307) hide show
  1. package/LICENSE.md +54 -0
  2. package/README.md +409 -2
  3. package/dist/src/client/client.spec.d.ts +6 -0
  4. package/dist/src/client/client.spec.d.ts.map +1 -0
  5. package/dist/src/client/client.spec.js +188 -0
  6. package/dist/src/client/client.spec.js.map +1 -0
  7. package/dist/src/client/connect.d.ts +54 -0
  8. package/dist/src/client/connect.d.ts.map +1 -0
  9. package/dist/src/client/connect.js +115 -0
  10. package/dist/src/client/connect.js.map +1 -0
  11. package/dist/src/client/hub-client.d.ts +57 -0
  12. package/dist/src/client/hub-client.d.ts.map +1 -0
  13. package/dist/src/client/hub-client.js +87 -0
  14. package/dist/src/client/hub-client.js.map +1 -0
  15. package/dist/src/client/local-http.d.ts +78 -0
  16. package/dist/src/client/local-http.d.ts.map +1 -0
  17. package/dist/src/client/local-http.js +149 -0
  18. package/dist/src/client/local-http.js.map +1 -0
  19. package/dist/src/client/session-attempts.d.ts +20 -0
  20. package/dist/src/client/session-attempts.d.ts.map +1 -0
  21. package/dist/src/client/session-attempts.js +88 -0
  22. package/dist/src/client/session-attempts.js.map +1 -0
  23. package/dist/src/client/session-data-server.d.ts +37 -0
  24. package/dist/src/client/session-data-server.d.ts.map +1 -0
  25. package/dist/src/client/session-data-server.js +280 -0
  26. package/dist/src/client/session-data-server.js.map +1 -0
  27. package/dist/src/client/session-data-server.spec.d.ts +6 -0
  28. package/dist/src/client/session-data-server.spec.d.ts.map +1 -0
  29. package/dist/src/client/session-data-server.spec.js +192 -0
  30. package/dist/src/client/session-data-server.spec.js.map +1 -0
  31. package/dist/src/client/session.d.ts +119 -0
  32. package/dist/src/client/session.d.ts.map +1 -0
  33. package/dist/src/client/session.js +290 -0
  34. package/dist/src/client/session.js.map +1 -0
  35. package/dist/src/hub/config.d.ts +74 -0
  36. package/dist/src/hub/config.d.ts.map +1 -0
  37. package/dist/src/hub/config.js +159 -0
  38. package/dist/src/hub/config.js.map +1 -0
  39. package/dist/src/hub/fair-lease-store.d.ts +36 -0
  40. package/dist/src/hub/fair-lease-store.d.ts.map +1 -0
  41. package/dist/src/hub/fair-lease-store.js +73 -0
  42. package/dist/src/hub/fair-lease-store.js.map +1 -0
  43. package/dist/src/hub/hub.d.ts +91 -0
  44. package/dist/src/hub/hub.d.ts.map +1 -0
  45. package/dist/src/hub/hub.js +641 -0
  46. package/dist/src/hub/hub.js.map +1 -0
  47. package/dist/src/hub/hub.spec.d.ts +6 -0
  48. package/dist/src/hub/hub.spec.d.ts.map +1 -0
  49. package/dist/src/hub/hub.spec.js +363 -0
  50. package/dist/src/hub/hub.spec.js.map +1 -0
  51. package/dist/src/hub/infrastructure.spec.d.ts +6 -0
  52. package/dist/src/hub/infrastructure.spec.d.ts.map +1 -0
  53. package/dist/src/hub/infrastructure.spec.js +211 -0
  54. package/dist/src/hub/infrastructure.spec.js.map +1 -0
  55. package/dist/src/hub/lock.d.ts +62 -0
  56. package/dist/src/hub/lock.d.ts.map +1 -0
  57. package/dist/src/hub/lock.js +165 -0
  58. package/dist/src/hub/lock.js.map +1 -0
  59. package/dist/src/hub/main.d.ts +16 -0
  60. package/dist/src/hub/main.d.ts.map +1 -0
  61. package/dist/src/hub/main.js +56 -0
  62. package/dist/src/hub/main.js.map +1 -0
  63. package/dist/src/hub/session-bridge.d.ts +116 -0
  64. package/dist/src/hub/session-bridge.d.ts.map +1 -0
  65. package/dist/src/hub/session-bridge.js +336 -0
  66. package/dist/src/hub/session-bridge.js.map +1 -0
  67. package/dist/src/identity/file-identity-store.d.ts +18 -0
  68. package/dist/src/identity/file-identity-store.d.ts.map +1 -0
  69. package/dist/src/identity/file-identity-store.js +34 -0
  70. package/dist/src/identity/file-identity-store.js.map +1 -0
  71. package/dist/src/identity/file-identity-store.spec.d.ts +6 -0
  72. package/dist/src/identity/file-identity-store.spec.d.ts.map +1 -0
  73. package/dist/src/identity/file-identity-store.spec.js +59 -0
  74. package/dist/src/identity/file-identity-store.spec.js.map +1 -0
  75. package/dist/src/identity/in-memory-identity-store.d.ts +48 -0
  76. package/dist/src/identity/in-memory-identity-store.d.ts.map +1 -0
  77. package/dist/src/identity/in-memory-identity-store.js +102 -0
  78. package/dist/src/identity/in-memory-identity-store.js.map +1 -0
  79. package/dist/src/identity/machine-identity.d.ts +134 -0
  80. package/dist/src/identity/machine-identity.d.ts.map +1 -0
  81. package/dist/src/identity/machine-identity.js +65 -0
  82. package/dist/src/identity/machine-identity.js.map +1 -0
  83. package/dist/src/index.d.ts +34 -0
  84. package/dist/src/index.d.ts.map +1 -0
  85. package/dist/src/index.js +34 -0
  86. package/dist/src/index.js.map +1 -0
  87. package/dist/src/lease/closure.d.ts +96 -0
  88. package/dist/src/lease/closure.d.ts.map +1 -0
  89. package/dist/src/lease/closure.js +192 -0
  90. package/dist/src/lease/closure.js.map +1 -0
  91. package/dist/src/lease/closure.spec.d.ts +6 -0
  92. package/dist/src/lease/closure.spec.d.ts.map +1 -0
  93. package/dist/src/lease/closure.spec.js +35 -0
  94. package/dist/src/lease/closure.spec.js.map +1 -0
  95. package/dist/src/lease/coordinator.d.ts +34 -0
  96. package/dist/src/lease/coordinator.d.ts.map +1 -0
  97. package/dist/src/lease/coordinator.js +42 -0
  98. package/dist/src/lease/coordinator.js.map +1 -0
  99. package/dist/src/lease/in-memory-lease-store.d.ts +58 -0
  100. package/dist/src/lease/in-memory-lease-store.d.ts.map +1 -0
  101. package/dist/src/lease/in-memory-lease-store.js +132 -0
  102. package/dist/src/lease/in-memory-lease-store.js.map +1 -0
  103. package/dist/src/lease/lease-store.d.ts +169 -0
  104. package/dist/src/lease/lease-store.d.ts.map +1 -0
  105. package/dist/src/lease/lease-store.js +94 -0
  106. package/dist/src/lease/lease-store.js.map +1 -0
  107. package/dist/src/lease/lease-store.spec.d.ts +6 -0
  108. package/dist/src/lease/lease-store.spec.d.ts.map +1 -0
  109. package/dist/src/lease/lease-store.spec.js +8 -0
  110. package/dist/src/lease/lease-store.spec.js.map +1 -0
  111. package/dist/src/lease/rack-dispatch.d.ts +105 -0
  112. package/dist/src/lease/rack-dispatch.d.ts.map +1 -0
  113. package/dist/src/lease/rack-dispatch.js +6 -0
  114. package/dist/src/lease/rack-dispatch.js.map +1 -0
  115. package/dist/src/mutation-queue.d.ts +13 -0
  116. package/dist/src/mutation-queue.d.ts.map +1 -0
  117. package/dist/src/mutation-queue.js +17 -0
  118. package/dist/src/mutation-queue.js.map +1 -0
  119. package/dist/src/paths.d.ts +56 -0
  120. package/dist/src/paths.d.ts.map +1 -0
  121. package/dist/src/paths.js +82 -0
  122. package/dist/src/paths.js.map +1 -0
  123. package/dist/src/protocol/control.d.ts +295 -0
  124. package/dist/src/protocol/control.d.ts.map +1 -0
  125. package/dist/src/protocol/control.js +74 -0
  126. package/dist/src/protocol/control.js.map +1 -0
  127. package/dist/src/protocol/index.d.ts +9 -0
  128. package/dist/src/protocol/index.d.ts.map +1 -0
  129. package/dist/src/protocol/index.js +9 -0
  130. package/dist/src/protocol/index.js.map +1 -0
  131. package/dist/src/protocol/rack-wire.d.ts +357 -0
  132. package/dist/src/protocol/rack-wire.d.ts.map +1 -0
  133. package/dist/src/protocol/rack-wire.js +262 -0
  134. package/dist/src/protocol/rack-wire.js.map +1 -0
  135. package/dist/src/protocol/rack-wire.spec.d.ts +6 -0
  136. package/dist/src/protocol/rack-wire.spec.d.ts.map +1 -0
  137. package/dist/src/protocol/rack-wire.spec.js +14 -0
  138. package/dist/src/protocol/rack-wire.spec.js.map +1 -0
  139. package/dist/src/protocol/registration.d.ts +70 -0
  140. package/dist/src/protocol/registration.d.ts.map +1 -0
  141. package/dist/src/protocol/registration.js +67 -0
  142. package/dist/src/protocol/registration.js.map +1 -0
  143. package/dist/src/protocol/session-data.d.ts +69 -0
  144. package/dist/src/protocol/session-data.d.ts.map +1 -0
  145. package/dist/src/protocol/session-data.js +26 -0
  146. package/dist/src/protocol/session-data.js.map +1 -0
  147. package/dist/src/protocol/task-envelope.d.ts +198 -0
  148. package/dist/src/protocol/task-envelope.d.ts.map +1 -0
  149. package/dist/src/protocol/task-envelope.js +93 -0
  150. package/dist/src/protocol/task-envelope.js.map +1 -0
  151. package/dist/src/protocol/wire-golden.d.ts +8 -0
  152. package/dist/src/protocol/wire-golden.d.ts.map +1 -0
  153. package/dist/src/protocol/wire-golden.js +34 -0
  154. package/dist/src/protocol/wire-golden.js.map +1 -0
  155. package/dist/src/protocol/wire-samples.d.ts +11 -0
  156. package/dist/src/protocol/wire-samples.d.ts.map +1 -0
  157. package/dist/src/protocol/wire-samples.js +52 -0
  158. package/dist/src/protocol/wire-samples.js.map +1 -0
  159. package/dist/src/registry/file-rack-registry.d.ts +68 -0
  160. package/dist/src/registry/file-rack-registry.d.ts.map +1 -0
  161. package/dist/src/registry/file-rack-registry.js +138 -0
  162. package/dist/src/registry/file-rack-registry.js.map +1 -0
  163. package/dist/src/registry/file-rack-registry.spec.d.ts +6 -0
  164. package/dist/src/registry/file-rack-registry.spec.d.ts.map +1 -0
  165. package/dist/src/registry/file-rack-registry.spec.js +50 -0
  166. package/dist/src/registry/file-rack-registry.spec.js.map +1 -0
  167. package/dist/src/registry/rack-registry.d.ts +64 -0
  168. package/dist/src/registry/rack-registry.d.ts.map +1 -0
  169. package/dist/src/registry/rack-registry.js +65 -0
  170. package/dist/src/registry/rack-registry.js.map +1 -0
  171. package/dist/src/registry/rack-registry.spec.d.ts +6 -0
  172. package/dist/src/registry/rack-registry.spec.d.ts.map +1 -0
  173. package/dist/src/registry/rack-registry.spec.js +8 -0
  174. package/dist/src/registry/rack-registry.spec.js.map +1 -0
  175. package/dist/src/routing/eligibility.d.ts +38 -0
  176. package/dist/src/routing/eligibility.d.ts.map +1 -0
  177. package/dist/src/routing/eligibility.js +78 -0
  178. package/dist/src/routing/eligibility.js.map +1 -0
  179. package/dist/src/routing/host-coupling.d.ts +26 -0
  180. package/dist/src/routing/host-coupling.d.ts.map +1 -0
  181. package/dist/src/routing/host-coupling.js +62 -0
  182. package/dist/src/routing/host-coupling.js.map +1 -0
  183. package/dist/src/routing/policy.d.ts +188 -0
  184. package/dist/src/routing/policy.d.ts.map +1 -0
  185. package/dist/src/routing/policy.js +212 -0
  186. package/dist/src/routing/policy.js.map +1 -0
  187. package/dist/src/routing/routing.spec.d.ts +6 -0
  188. package/dist/src/routing/routing.spec.d.ts.map +1 -0
  189. package/dist/src/routing/routing.spec.js +100 -0
  190. package/dist/src/routing/routing.spec.js.map +1 -0
  191. package/dist/src/routing/tiers.d.ts +22 -0
  192. package/dist/src/routing/tiers.d.ts.map +1 -0
  193. package/dist/src/routing/tiers.js +26 -0
  194. package/dist/src/routing/tiers.js.map +1 -0
  195. package/dist/src/runner/api-server.d.ts +45 -0
  196. package/dist/src/runner/api-server.d.ts.map +1 -0
  197. package/dist/src/runner/api-server.js +21 -0
  198. package/dist/src/runner/api-server.js.map +1 -0
  199. package/dist/src/runner/capacity.d.ts +13 -0
  200. package/dist/src/runner/capacity.d.ts.map +1 -0
  201. package/dist/src/runner/capacity.js +18 -0
  202. package/dist/src/runner/capacity.js.map +1 -0
  203. package/dist/src/runner/rack-body.d.ts +43 -0
  204. package/dist/src/runner/rack-body.d.ts.map +1 -0
  205. package/dist/src/runner/rack-body.js +208 -0
  206. package/dist/src/runner/rack-body.js.map +1 -0
  207. package/dist/src/runner/rack-runner.d.ts +62 -0
  208. package/dist/src/runner/rack-runner.d.ts.map +1 -0
  209. package/dist/src/runner/rack-runner.js +75 -0
  210. package/dist/src/runner/rack-runner.js.map +1 -0
  211. package/dist/src/runner/rack-runner.spec.d.ts +6 -0
  212. package/dist/src/runner/rack-runner.spec.d.ts.map +1 -0
  213. package/dist/src/runner/rack-runner.spec.js +302 -0
  214. package/dist/src/runner/rack-runner.spec.js.map +1 -0
  215. package/dist/src/runner/split.spec.d.ts +6 -0
  216. package/dist/src/runner/split.spec.d.ts.map +1 -0
  217. package/dist/src/runner/split.spec.js +188 -0
  218. package/dist/src/runner/split.spec.js.map +1 -0
  219. package/dist/src/runner-event.d.ts +261 -0
  220. package/dist/src/runner-event.d.ts.map +1 -0
  221. package/dist/src/runner-event.js +332 -0
  222. package/dist/src/runner-event.js.map +1 -0
  223. package/dist/src/server/beast2.d.ts +19 -0
  224. package/dist/src/server/beast2.d.ts.map +1 -0
  225. package/dist/src/server/beast2.js +18 -0
  226. package/dist/src/server/beast2.js.map +1 -0
  227. package/dist/src/server/body-limit.d.ts +16 -0
  228. package/dist/src/server/body-limit.d.ts.map +1 -0
  229. package/dist/src/server/body-limit.js +53 -0
  230. package/dist/src/server/body-limit.js.map +1 -0
  231. package/dist/src/server/body-limit.spec.d.ts +2 -0
  232. package/dist/src/server/body-limit.spec.d.ts.map +1 -0
  233. package/dist/src/server/body-limit.spec.js +32 -0
  234. package/dist/src/server/body-limit.spec.js.map +1 -0
  235. package/dist/src/server/bounded.d.ts +16 -0
  236. package/dist/src/server/bounded.d.ts.map +1 -0
  237. package/dist/src/server/bounded.js +39 -0
  238. package/dist/src/server/bounded.js.map +1 -0
  239. package/dist/src/server/dispatch.d.ts +137 -0
  240. package/dist/src/server/dispatch.d.ts.map +1 -0
  241. package/dist/src/server/dispatch.js +267 -0
  242. package/dist/src/server/dispatch.js.map +1 -0
  243. package/dist/src/server/env-publication.d.ts +45 -0
  244. package/dist/src/server/env-publication.d.ts.map +1 -0
  245. package/dist/src/server/env-publication.js +10 -0
  246. package/dist/src/server/env-publication.js.map +1 -0
  247. package/dist/src/server/index.d.ts +16 -0
  248. package/dist/src/server/index.d.ts.map +1 -0
  249. package/dist/src/server/index.js +17 -0
  250. package/dist/src/server/index.js.map +1 -0
  251. package/dist/src/server/protocol.spec.d.ts +6 -0
  252. package/dist/src/server/protocol.spec.d.ts.map +1 -0
  253. package/dist/src/server/protocol.spec.js +78 -0
  254. package/dist/src/server/protocol.spec.js.map +1 -0
  255. package/dist/src/server/rack-routes.d.ts +175 -0
  256. package/dist/src/server/rack-routes.d.ts.map +1 -0
  257. package/dist/src/server/rack-routes.js +755 -0
  258. package/dist/src/server/rack-routes.js.map +1 -0
  259. package/dist/src/server/rack-routes.spec.d.ts +6 -0
  260. package/dist/src/server/rack-routes.spec.d.ts.map +1 -0
  261. package/dist/src/server/rack-routes.spec.js +477 -0
  262. package/dist/src/server/rack-routes.spec.js.map +1 -0
  263. package/dist/src/server/storage-bridge.d.ts +140 -0
  264. package/dist/src/server/storage-bridge.d.ts.map +1 -0
  265. package/dist/src/server/storage-bridge.js +6 -0
  266. package/dist/src/server/storage-bridge.js.map +1 -0
  267. package/dist/src/state-file.d.ts +10 -0
  268. package/dist/src/state-file.d.ts.map +1 -0
  269. package/dist/src/state-file.js +38 -0
  270. package/dist/src/state-file.js.map +1 -0
  271. package/dist/src/testing/in-memory.d.ts +116 -0
  272. package/dist/src/testing/in-memory.d.ts.map +1 -0
  273. package/dist/src/testing/in-memory.js +193 -0
  274. package/dist/src/testing/in-memory.js.map +1 -0
  275. package/dist/src/testing/index.d.ts +14 -0
  276. package/dist/src/testing/index.d.ts.map +1 -0
  277. package/dist/src/testing/index.js +14 -0
  278. package/dist/src/testing/index.js.map +1 -0
  279. package/dist/src/testing/lease-store-contract.d.ts +14 -0
  280. package/dist/src/testing/lease-store-contract.d.ts.map +1 -0
  281. package/dist/src/testing/lease-store-contract.js +78 -0
  282. package/dist/src/testing/lease-store-contract.js.map +1 -0
  283. package/dist/src/testing/protocol-suite.d.ts +38 -0
  284. package/dist/src/testing/protocol-suite.d.ts.map +1 -0
  285. package/dist/src/testing/protocol-suite.js +207 -0
  286. package/dist/src/testing/protocol-suite.js.map +1 -0
  287. package/dist/src/testing/rack-registry-contract.d.ts +15 -0
  288. package/dist/src/testing/rack-registry-contract.d.ts.map +1 -0
  289. package/dist/src/testing/rack-registry-contract.js +106 -0
  290. package/dist/src/testing/rack-registry-contract.js.map +1 -0
  291. package/dist/src/testing/test-rack-agent.d.ts +121 -0
  292. package/dist/src/testing/test-rack-agent.d.ts.map +1 -0
  293. package/dist/src/testing/test-rack-agent.js +316 -0
  294. package/dist/src/testing/test-rack-agent.js.map +1 -0
  295. package/dist/src/tls/tls.d.ts +67 -0
  296. package/dist/src/tls/tls.d.ts.map +1 -0
  297. package/dist/src/tls/tls.js +200 -0
  298. package/dist/src/tls/tls.js.map +1 -0
  299. package/dist/src/tls/tls.spec.d.ts +6 -0
  300. package/dist/src/tls/tls.spec.d.ts.map +1 -0
  301. package/dist/src/tls/tls.spec.js +101 -0
  302. package/dist/src/tls/tls.spec.js.map +1 -0
  303. package/dist/src/version.d.ts +9 -0
  304. package/dist/src/version.d.ts.map +1 -0
  305. package/dist/src/version.js +10 -0
  306. package/dist/src/version.js.map +1 -0
  307. package/package.json +72 -3
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ import { afterEach, it } from 'node:test';
6
+ import assert from 'node:assert/strict';
7
+ import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
8
+ import { tmpdir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ import { spawn } from 'node:child_process';
11
+ import { once } from 'node:events';
12
+ import { setTimeout as delay } from 'node:timers/promises';
13
+ import { Hono } from 'hono';
14
+ import { BooleanType, StringType, decodeBeast2For, encodeBeast2For, some, variant } from '@elaraai/east';
15
+ import { processOwner } from '@elaraai/e3-core';
16
+ import { decodeBody, sendError, sendSuccess } from '../server/beast2.js';
17
+ import { acquireHubLock, HubLockType, readHubLock } from './lock.js';
18
+ import { defaultHubConfig, listenerProblem, loadHubConfig, saveHubConfig, validateHubConfig, wildcardHost } from './config.js';
19
+ import { FairLeaseStore } from './fair-lease-store.js';
20
+ import { SessionLeaseCoordinator, SessionProxyStorageBridge } from './session-bridge.js';
21
+ import { InMemoryRackLeaseStore } from '../lease/in-memory-lease-store.js';
22
+ import { makeLeaseId } from '../lease/lease-store.js';
23
+ import { closeSocketServer, RackHubError, REQUEST_BODY_TIMEOUT_MS, serveOnSocket, socketRequest, socketStream } from '../client/local-http.js';
24
+ import { SessionLogsType } from '../protocol/session-data.js';
25
+ import { HUB_CONFIG_FILE, HUB_LOCK_FILE, sessionSocketPath } from '../paths.js';
26
+ const cleanup = [];
27
+ afterEach(async () => { for (const close of cleanup.splice(0).reverse())
28
+ await close(); });
29
+ async function home() {
30
+ const path = await mkdtemp(join(tmpdir(), 'e3r-'));
31
+ cleanup.push(() => rm(path, { recursive: true, force: true }));
32
+ return path;
33
+ }
34
+ it('persists hub configuration atomically and refuses invalid or corrupt state', async () => {
35
+ const path = await home();
36
+ assert.deepEqual(await loadHubConfig(path), defaultHubConfig());
37
+ const config = { ...defaultHubConfig(), listen: some({ host: '127.0.0.1', port: 7331n }) };
38
+ await saveHubConfig(config, path);
39
+ assert.deepEqual(await loadHubConfig(path), config);
40
+ await assert.rejects(saveHubConfig({ ...config, idleExitMinutes: -1n }, path), /timeout/);
41
+ assert.deepEqual(await loadHubConfig(path), config);
42
+ await writeFile(join(path, HUB_CONFIG_FILE), new Uint8Array([1, 2, 3]));
43
+ await assert.rejects(loadHubConfig(path));
44
+ });
45
+ it('serves plain HTTP only on the loopback or a private address it names, and TLS on any address', () => {
46
+ const listen = (host) => some({ host, port: 7331n });
47
+ const plain = (host, allow = ['127.0.0.1']) => ({ ...defaultHubConfig(), listen: listen(host), allow, tls: false });
48
+ assert.equal(defaultHubConfig().tls, true);
49
+ assert.equal(listenerProblem(defaultHubConfig()), undefined);
50
+ for (const host of ['127.0.0.1', '::1', 'localhost', '10.1.2.3', '172.16.0.9', '192.168.1.2', '100.64.0.10', '169.254.3.4', 'fd00::5', 'fe80::1']) {
51
+ assert.equal(listenerProblem(plain(host)), undefined, host);
52
+ }
53
+ assert.match(listenerProblem(plain('127.0.0.1', [])), /must name the addresses it admits/);
54
+ for (const host of ['0.0.0.0', '::'])
55
+ assert.match(listenerProblem(plain(host)), /every address/);
56
+ for (const host of ['8.8.8.8', '172.32.0.1', '2001:db8::1', 'hub.example.com'])
57
+ assert.match(listenerProblem(plain(host)), /loopback or a private address/, host);
58
+ for (const host of ['0.0.0.0', '8.8.8.8', 'hub.example.com'])
59
+ assert.equal(listenerProblem({ ...defaultHubConfig(), listen: listen(host) }), undefined, host);
60
+ assert.match(listenerProblem({ ...defaultHubConfig(), listen: listen('127.0.0.1'), advertiseUrl: some('http://relay.example') }), /https:\/\//);
61
+ assert.equal(listenerProblem({ ...plain('127.0.0.1'), advertiseUrl: some('https://terminator.example') }), undefined);
62
+ assert.throws(() => validateHubConfig(plain('127.0.0.1', [])), /must name the addresses/);
63
+ assert.throws(() => validateHubConfig({ ...defaultHubConfig(), allow: ['not-an-ip'] }), /not an IP address/);
64
+ assert.deepEqual(['0.0.0.0', '::', '0:0:0:0:0:0:0:0', '127.0.0.1', 'localhost'].map(wildcardHost), [true, true, true, false, false]);
65
+ });
66
+ it('publishes one whole hub lock and recovers a stale owner under simultaneous contenders', async () => {
67
+ const path = await home();
68
+ for (let round = 0; round < 3; round++) {
69
+ const acquired = await Promise.all(Array.from({ length: 12 }, () => acquireHubLock(path)));
70
+ const winners = acquired.filter((lock) => lock !== null);
71
+ assert.equal(winners.length, 1);
72
+ const winner = winners[0];
73
+ cleanup.push(() => winner.release());
74
+ assert.deepEqual(decodeBeast2For(HubLockType)(await readFile(join(path, HUB_LOCK_FILE))), winner.record);
75
+ assert.equal(await acquireHubLock(path), null);
76
+ await winner.release();
77
+ assert.equal(await readHubLock(path), null);
78
+ // A complete record left by a dead process: all contenders see the same stale owner.
79
+ await writeFile(join(path, HUB_LOCK_FILE), encodeBeast2For(HubLockType)({ ...winner.record, pid: -1n }));
80
+ }
81
+ });
82
+ it('asks after a contender\'s liveness once in a while during an election, never once per claim per poll', async () => {
83
+ const path = await home();
84
+ const self = await processOwner();
85
+ // Twelve contenders of distinct identities, each liveness answer costing
86
+ // about what a macOS answer does, which spawns sysctl and ps.
87
+ const contenders = Array.from({ length: 12 }, (_, n) => ({ ...self, pid: 100000n + BigInt(n) }));
88
+ let probes = 0;
89
+ const alive = async () => { probes++; await delay(15); return true; };
90
+ const started = Date.now();
91
+ const acquired = await Promise.all(contenders.map((owner) => acquireHubLock(path, { owner, alive })));
92
+ const elapsed = Date.now() - started;
93
+ const winners = acquired.filter((lock) => lock !== null);
94
+ assert.equal(winners.length, 1);
95
+ cleanup.push(() => winners[0].release());
96
+ assert(probes <= contenders.length * contenders.length, `${probes} liveness answers for ${contenders.length} contenders`);
97
+ assert(elapsed < 3000, `the election took ${elapsed} ms`);
98
+ });
99
+ it('elects one hub across processes and recovers its lock after an ungraceful exit', async () => {
100
+ const path = await home();
101
+ const module = new URL('./lock.js', import.meta.url).href;
102
+ const launch = async () => {
103
+ const script = `import { acquireHubLock } from ${JSON.stringify(module)};
104
+ const lock = await acquireHubLock(process.argv[1]);
105
+ console.log(lock ? 'held' : 'busy');
106
+ if (lock) setInterval(() => {}, 1000);`;
107
+ const child = spawn(process.execPath, ['--input-type=module', '-e', script, path], { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
108
+ cleanup.push(async () => {
109
+ if (child.exitCode === null && child.signalCode === null) {
110
+ const exit = once(child, 'exit');
111
+ child.kill('SIGKILL');
112
+ await exit;
113
+ }
114
+ });
115
+ const [bytes] = await once(child.stdout, 'data', { signal: AbortSignal.timeout(10_000) });
116
+ return { child, held: String(bytes).trim() === 'held' };
117
+ };
118
+ const contenders = await Promise.all([launch(), launch(), launch()]);
119
+ const winners = contenders.filter((c) => c.held);
120
+ assert.equal(winners.length, 1);
121
+ const winner = winners[0].child;
122
+ const exit = once(winner, 'exit');
123
+ winner.kill('SIGKILL');
124
+ await exit;
125
+ const reclaimed = await Promise.all([launch(), launch(), launch()]);
126
+ assert.equal(reclaimed.filter((c) => c.held).length, 1);
127
+ });
128
+ it('serves typed and raw data over a private socket with deadlines and cancellation', async () => {
129
+ const path = sessionSocketPath(await home());
130
+ const app = new Hono();
131
+ app.post('/echo', async (c) => sendSuccess(StringType, await decodeBody(c, StringType)));
132
+ app.get('/error', () => sendError(StringType, variant('internal', { message: 'test error' })));
133
+ app.get('/slow', async () => { await delay(150); return sendSuccess(StringType, 'late'); });
134
+ app.get('/bytes', () => new Response(new Uint8Array([1, 2, 3])));
135
+ const server = await serveOnSocket(app, path);
136
+ cleanup.push(() => closeSocketServer(server, path));
137
+ assert.equal(server.requestTimeout, REQUEST_BODY_TIMEOUT_MS, "a request body may take a data URL's whole lifetime to arrive");
138
+ if (process.platform !== 'win32')
139
+ assert.equal((await stat(path)).mode & 0o777, 0o600);
140
+ assert.equal(await socketRequest(path, 'POST', '/echo', { requestType: StringType, body: 'hello', responseType: StringType }), 'hello');
141
+ await assert.rejects(socketRequest(path, 'GET', '/error', { responseType: StringType }), /test error/);
142
+ await assert.rejects(socketRequest(path, 'GET', '/slow', { responseType: StringType, timeoutMs: 20 }), (err) => err instanceof RackHubError && err.code === 'timeout');
143
+ const abort = new AbortController();
144
+ const pending = socketRequest(path, 'GET', '/slow', { responseType: StringType, signal: abort.signal });
145
+ abort.abort();
146
+ await assert.rejects(pending, (err) => err instanceof RackHubError && err.code === 'aborted');
147
+ const response = await socketStream(path, 'GET', '/bytes');
148
+ const chunks = [];
149
+ for await (const chunk of response)
150
+ chunks.push(Buffer.from(chunk));
151
+ assert.deepEqual(Buffer.concat(chunks), Buffer.from([1, 2, 3]));
152
+ });
153
+ it('drops a log batch whose flush the session refused, so the rack\'s re-post is written once', async () => {
154
+ const path = sessionSocketPath(await home());
155
+ const app = new Hono();
156
+ const batches = [];
157
+ let refusals = 1;
158
+ app.post('/v1/logs', async (c) => {
159
+ const request = await decodeBody(c, SessionLogsType);
160
+ if (refusals-- > 0)
161
+ return c.text('session busy', 503);
162
+ batches.push(request.chunks.map((chunk) => chunk.data));
163
+ return sendSuccess(BooleanType, true);
164
+ });
165
+ const server = await serveOnSocket(app, path);
166
+ cleanup.push(() => closeSocketServer(server, path));
167
+ const leases = new InMemoryRackLeaseStore();
168
+ const taskHash = 'a'.repeat(64);
169
+ const inputsHash = 'b'.repeat(64);
170
+ await leases.put({
171
+ leaseId: makeLeaseId(taskHash, inputsHash), repo: 'repo', workspace: 'dev', taskHash, inputsHash, tier: 'node', status: 'claimed',
172
+ claimedBy: 'rack', createdAtMs: 0, attempt: { executionId: 'run', startedAtMs: 0 },
173
+ event: { version: 2, workspace: 'dev', taskName: 'task', closure: { hashes: [] }, runnerEvent: { mode: 'task', repo: 'repo', launchId: 'launch', taskHash, inputHashes: [], timeoutMs: 1000 } },
174
+ });
175
+ const coordinator = new SessionLeaseCoordinator();
176
+ const bridge = new SessionProxyStorageBridge({ leases, coordinator, session: () => ({ dataSocket: path, dataToken: 'token' }), owner: await processOwner(), advertiseOrigin: () => undefined });
177
+ const post = (line) => coordinator.run('repo', 'lease', async () => {
178
+ await bridge.attempts.logs.append('repo', taskHash, inputsHash, 'run', 'stdout', line);
179
+ await bridge.attempts.logs.flush('repo', taskHash, inputsHash, 'run');
180
+ });
181
+ await assert.rejects(post('first\n'), /session busy/);
182
+ await post('first\n'); // the rack, told its lines were not stored, posts them again
183
+ await post('second\n');
184
+ assert.deepEqual(batches, [['first\n'], ['second\n']]);
185
+ });
186
+ it('rotates claims across owners even when one session fills the oldest queue positions', async () => {
187
+ const backend = new InMemoryRackLeaseStore();
188
+ const store = new FairLeaseStore(backend, (lease) => lease.workspace);
189
+ const taskHash = 'a'.repeat(64);
190
+ const inputsHash = 'b'.repeat(64);
191
+ for (const workspace of ['first', 'second'])
192
+ for (let n = 0; n < 6; n++) {
193
+ const lease = {
194
+ leaseId: makeLeaseId(taskHash, inputsHash), repo: 'repo', workspace, taskHash, inputsHash,
195
+ tier: 'node', status: 'pending', createdAtMs: workspace === 'first' ? n : n + 10,
196
+ event: { version: 2, workspace, taskName: 'task', closure: { hashes: [] }, runnerEvent: {
197
+ mode: 'task', repo: 'repo', launchId: 'launch', taskHash, inputHashes: [], timeoutMs: 1000,
198
+ } },
199
+ };
200
+ await store.put(lease);
201
+ }
202
+ const owners = [];
203
+ for (let n = 0; n < 12; n++) {
204
+ const [lease] = await store.listClaimable(['node'], Date.now(), 1);
205
+ assert(lease);
206
+ assert(await store.claim('repo', lease.leaseId, 'rack', Date.now() + 60_000));
207
+ owners.push(lease.workspace);
208
+ }
209
+ assert.deepEqual(owners, Array.from({ length: 12 }, (_, n) => n % 2 ? 'second' : 'first'));
210
+ });
211
+ //# sourceMappingURL=infrastructure.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infrastructure.spec.js","sourceRoot":"","sources":["../../../src/hub/infrastructure.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ import { DateTimeType, IntegerType, StringType, StructType, type ValueTypeOf } from '@elaraai/east';
6
+ import { type ExecutionOwner } from '@elaraai/e3-types';
7
+ /** Records the one hub's process identity and the control protocol it serves. */
8
+ export declare const HubLockType: StructType<{
9
+ readonly ownerId: StringType;
10
+ readonly pid: IntegerType;
11
+ readonly pidStartTime: IntegerType;
12
+ readonly bootId: StringType;
13
+ readonly version: StringType;
14
+ readonly protocol: IntegerType;
15
+ readonly controlSocket: StringType;
16
+ readonly startedAt: DateTimeType;
17
+ }>;
18
+ /** The process holding this user's hub. */
19
+ export type HubLock = ValueTypeOf<typeof HubLockType>;
20
+ /** Test seams of {@link acquireHubLock}: a contender's identity and its liveness probe. */
21
+ export interface AcquireHubLockOptions {
22
+ /** The contender's process identity; this process's unless given, as a test racing contenders of distinct identities gives it. */
23
+ owner?: ExecutionOwner;
24
+ /** Whether a claimant's process is alive; e3-core's process liveness unless given. */
25
+ alive?: (claimant: ExecutionOwner) => Promise<boolean>;
26
+ }
27
+ /** A held hub lock; its release removes only files belonging to this holder. */
28
+ export interface HeldHubLock {
29
+ /** Process identity used by sessions for execution ownership. */
30
+ readonly record: HubLock;
31
+ /** Releases ownership once the listeners have closed. */
32
+ release(): Promise<void>;
33
+ }
34
+ /**
35
+ * Reads the hub's ownership record, without deleting an in-progress publication.
36
+ * @param home - Private rack home
37
+ * @returns The complete lock, or null if absent
38
+ * @throws {Error} When a present lock is corrupt
39
+ */
40
+ export declare function readHubLock(home?: string): Promise<HubLock | null>;
41
+ /**
42
+ * Claims the single hub, including concurrent recovery of a crashed holder.
43
+ *
44
+ * @remarks
45
+ * A shared lock name cannot safely be unlinked after an asynchronous liveness
46
+ * check: another contender may already have replaced it. A bakery election
47
+ * uses unique, atomically published claim files instead. Each contender first
48
+ * announces that it is choosing (ticket zero), then publishes max(ticket)+1.
49
+ * A live lower (ticket, UUID) wins. Only dead, uniquely named claims are
50
+ * reclaimed, so no recovery deletes a new holder's lock. `hub.lock` is
51
+ * published whole once the election is won, and is never observed empty.
52
+ *
53
+ * @param home - Private rack home
54
+ * @param options - Test seams: the contender's identity and its liveness probe
55
+ * @returns Ownership, or null when another live contender/holder wins
56
+ * @throws {Error} When a claim cannot be read or startup exceeds ten seconds
57
+ * @example
58
+ * const lock = await acquireHubLock();
59
+ * if (lock !== null) await lock.release();
60
+ */
61
+ export declare function acquireHubLock(home?: string, options?: AcquireHubLockOptions): Promise<HeldHubLock | null>;
62
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../../src/hub/lock.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAoE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AACtK,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAK5E,iFAAiF;AACjF,eAAO,MAAM,WAAW;;;;;;;;;EAGtB,CAAC;AACH,2CAA2C;AAC3C,MAAM,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,WAAW,CAAC,CAAC;AAmBtD,2FAA2F;AAC3F,MAAM,WAAW,qBAAqB;IACpC,kIAAkI;IAClI,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,sFAAsF;IACtF,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxD;AAED,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,yDAAyD;IACzD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAMD;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,IAAI,SAAa,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAK5E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,cAAc,CAAC,IAAI,SAAa,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAsExH"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ import { randomUUID } from 'node:crypto';
6
+ import { readdir, readFile, unlink } from 'node:fs/promises';
7
+ import { join } from 'node:path';
8
+ import { setTimeout as delay } from 'node:timers/promises';
9
+ import { DateTimeType, IntegerType, StringType, StructType, compareFor, decodeBeast2For, encodeBeast2For, equalFor, printFor } from '@elaraai/east';
10
+ import { ExecutionOwnerType } from '@elaraai/e3-types';
11
+ import { atomicWriteFile, isProcessAlive, processOwner } from '@elaraai/e3-core';
12
+ import { ensureRackHome, HUB_LOCK_FILE, hubSocketPath, rackHome } from '../paths.js';
13
+ import { E3_RACK_VERSION, HUB_PROTOCOL } from '../version.js';
14
+ /** Records the one hub's process identity and the control protocol it serves. */
15
+ export const HubLockType = StructType({
16
+ ownerId: StringType, pid: IntegerType, pidStartTime: IntegerType, bootId: StringType,
17
+ version: StringType, protocol: IntegerType, controlSocket: StringType, startedAt: DateTimeType,
18
+ });
19
+ const ClaimType = StructType({ owner: HubLockType, ticket: IntegerType });
20
+ const encodeClaim = encodeBeast2For(ClaimType);
21
+ const decodeClaim = decodeBeast2For(ClaimType);
22
+ const encodeLock = encodeBeast2For(HubLockType);
23
+ const decodeLock = decodeBeast2For(HubLockType);
24
+ const compareTicket = compareFor(IntegerType);
25
+ const compareOwner = compareFor(StringType);
26
+ const sameProcess = equalFor(ExecutionOwnerType);
27
+ const printProcess = printFor(ExecutionOwnerType);
28
+ const PREFIX = '.hub-claim-';
29
+ /**
30
+ * How long one election trusts an answer about a claimant's liveness. On
31
+ * macOS each answer spawns `sysctl` and `ps`, and every contender reads every
32
+ * claim on every 20 ms poll: a dozen contenders asking afresh each time outran
33
+ * the election's own deadline there.
34
+ */
35
+ const LIVENESS_TTL_MS = 1000;
36
+ async function remove(file) {
37
+ await unlink(file).catch((err) => { if (err.code !== 'ENOENT')
38
+ throw err; });
39
+ }
40
+ /**
41
+ * Reads the hub's ownership record, without deleting an in-progress publication.
42
+ * @param home - Private rack home
43
+ * @returns The complete lock, or null if absent
44
+ * @throws {Error} When a present lock is corrupt
45
+ */
46
+ export async function readHubLock(home = rackHome()) {
47
+ try {
48
+ return decodeLock(await readFile(join(home, HUB_LOCK_FILE)));
49
+ }
50
+ catch (err) {
51
+ if (err.code === 'ENOENT')
52
+ return null;
53
+ throw err;
54
+ }
55
+ }
56
+ /**
57
+ * Claims the single hub, including concurrent recovery of a crashed holder.
58
+ *
59
+ * @remarks
60
+ * A shared lock name cannot safely be unlinked after an asynchronous liveness
61
+ * check: another contender may already have replaced it. A bakery election
62
+ * uses unique, atomically published claim files instead. Each contender first
63
+ * announces that it is choosing (ticket zero), then publishes max(ticket)+1.
64
+ * A live lower (ticket, UUID) wins. Only dead, uniquely named claims are
65
+ * reclaimed, so no recovery deletes a new holder's lock. `hub.lock` is
66
+ * published whole once the election is won, and is never observed empty.
67
+ *
68
+ * @param home - Private rack home
69
+ * @param options - Test seams: the contender's identity and its liveness probe
70
+ * @returns Ownership, or null when another live contender/holder wins
71
+ * @throws {Error} When a claim cannot be read or startup exceeds ten seconds
72
+ * @example
73
+ * const lock = await acquireHubLock();
74
+ * if (lock !== null) await lock.release();
75
+ */
76
+ export async function acquireHubLock(home = rackHome(), options = {}) {
77
+ ensureRackHome(home);
78
+ const owner = options.owner ?? await processOwner();
79
+ const probe = options.alive ?? ((claimant) => isProcessAlive(Number(claimant.pid), Number(claimant.pidStartTime), claimant.bootId));
80
+ // A claim of this process needs no asking; another claimant's answer holds
81
+ // for LIVENESS_TTL_MS, so an election asks after each once in a while, not
82
+ // once per claim per poll.
83
+ const answered = new Map();
84
+ const alive = async (lock) => {
85
+ const claimant = { pid: lock.pid, pidStartTime: lock.pidStartTime, bootId: lock.bootId };
86
+ if (sameProcess(claimant, owner))
87
+ return true;
88
+ const key = printProcess(claimant);
89
+ const known = answered.get(key);
90
+ if (known !== undefined && Date.now() - known.at < LIVENESS_TTL_MS)
91
+ return known.alive;
92
+ const result = await probe(claimant);
93
+ answered.set(key, { at: Date.now(), alive: result });
94
+ return result;
95
+ };
96
+ const existing = await readHubLock(home);
97
+ if (existing !== null && await alive(existing))
98
+ return null;
99
+ const record = {
100
+ ...owner, ownerId: randomUUID(), version: E3_RACK_VERSION, protocol: BigInt(HUB_PROTOCOL),
101
+ controlSocket: hubSocketPath(home), startedAt: new Date(),
102
+ };
103
+ const file = join(home, `${PREFIX}${record.ownerId}.beast2`);
104
+ const deadline = Date.now() + 10_000;
105
+ let held = false;
106
+ const claims = async () => {
107
+ const result = [];
108
+ for (const name of await readdir(home)) {
109
+ if (!name.startsWith(PREFIX) || !name.endsWith('.beast2'))
110
+ continue;
111
+ const path = join(home, name);
112
+ let claim;
113
+ try {
114
+ claim = decodeClaim(await readFile(path));
115
+ }
116
+ catch (err) {
117
+ if (err.code === 'ENOENT')
118
+ continue;
119
+ throw err; // Whole atomic publications: corrupt is never evidence of death.
120
+ }
121
+ if (await alive(claim.owner))
122
+ result.push(claim);
123
+ else
124
+ await remove(path);
125
+ }
126
+ return result;
127
+ };
128
+ try {
129
+ await atomicWriteFile(file, encodeClaim({ owner: record, ticket: 0n }));
130
+ let ticket = 1n;
131
+ for (const claim of await claims())
132
+ if (compareTicket(claim.ticket, ticket) >= 0)
133
+ ticket = claim.ticket + 1n;
134
+ await atomicWriteFile(file, encodeClaim({ owner: record, ticket }));
135
+ for (;;) {
136
+ const others = (await claims()).filter((claim) => compareOwner(claim.owner.ownerId, record.ownerId) !== 0);
137
+ if (others.some((claim) => compareTicket(claim.ticket, 0n) === 0)) {
138
+ if (Date.now() >= deadline)
139
+ throw new Error('Rack hub startup is waiting for another live contender');
140
+ await delay(20);
141
+ continue;
142
+ }
143
+ if (others.some((claim) => compareTicket(claim.ticket, ticket) < 0 ||
144
+ (compareTicket(claim.ticket, ticket) === 0 && compareOwner(claim.owner.ownerId, record.ownerId) < 0)))
145
+ return null;
146
+ await atomicWriteFile(join(home, HUB_LOCK_FILE), encodeLock(record));
147
+ held = true;
148
+ let released = false;
149
+ return { record, async release() {
150
+ if (released)
151
+ return;
152
+ released = true;
153
+ const current = await readHubLock(home);
154
+ if (current !== null && compareOwner(current.ownerId, record.ownerId) === 0)
155
+ await remove(join(home, HUB_LOCK_FILE));
156
+ await remove(file);
157
+ } };
158
+ }
159
+ }
160
+ finally {
161
+ if (!held)
162
+ await remove(file);
163
+ }
164
+ }
165
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../../src/hub/lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ /**
6
+ * Runs the shared hub until it is stopped, drained or idle.
7
+ * Background callers redirect stdout/stderr to the private hub log; a
8
+ * foreground invocation prints the same timestamped diagnostics directly.
9
+ * @param argv - --foreground or --background, and optional --idle-exit minutes
10
+ * @returns Once the owned listeners and lock are released
11
+ * @throws {Error} When arguments, state or startup are invalid
12
+ * @example
13
+ * await runHub(['--foreground']);
14
+ */
15
+ export declare function runHub(argv: string[]): Promise<void>;
16
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/hub/main.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;;;;;;;;GASG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB1D"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Licensed under BSL 1.1. See LICENSE for details.
4
+ */
5
+ import { pathToFileURL } from 'node:url';
6
+ import { RackHub } from './hub.js';
7
+ /**
8
+ * Runs the shared hub until it is stopped, drained or idle.
9
+ * Background callers redirect stdout/stderr to the private hub log; a
10
+ * foreground invocation prints the same timestamped diagnostics directly.
11
+ * @param argv - --foreground or --background, and optional --idle-exit minutes
12
+ * @returns Once the owned listeners and lock are released
13
+ * @throws {Error} When arguments, state or startup are invalid
14
+ * @example
15
+ * await runHub(['--foreground']);
16
+ */
17
+ export async function runHub(argv) {
18
+ let foreground = true;
19
+ let idleExitMinutes;
20
+ for (let index = 0; index < argv.length; index++) {
21
+ switch (argv[index]) {
22
+ case '--foreground':
23
+ foreground = true;
24
+ break;
25
+ case '--background':
26
+ foreground = false;
27
+ break;
28
+ case '--idle-exit': {
29
+ idleExitMinutes = Number(argv[++index]);
30
+ if (!Number.isFinite(idleExitMinutes) || idleExitMinutes < 0 || idleExitMinutes > 35000)
31
+ throw new Error('--idle-exit requires 0–35000 minutes');
32
+ break;
33
+ }
34
+ default: throw new Error(`Unknown rack hub option: ${argv[index]}`);
35
+ }
36
+ }
37
+ if (!foreground)
38
+ process.title = 'e3-rack-hub';
39
+ const log = (line) => { process.stdout.write(`${new Date().toISOString()} ${line}\n`); };
40
+ const hub = new RackHub({ foreground, idleExitMinutes, log });
41
+ const stop = () => { void hub.stop().catch((err) => { console.error(err); process.exitCode = 1; }); };
42
+ process.once('SIGINT', stop);
43
+ process.once('SIGTERM', stop);
44
+ try {
45
+ if (await hub.start())
46
+ await hub.closed;
47
+ }
48
+ finally {
49
+ process.off('SIGINT', stop);
50
+ process.off('SIGTERM', stop);
51
+ }
52
+ }
53
+ if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
54
+ void runHub(process.argv.slice(2)).catch((err) => { console.error(err); process.exitCode = 1; });
55
+ }
56
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../../src/hub/main.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAc;IACzC,IAAI,UAAU,GAAG,IAAI,CAAC;IACtB,IAAI,eAAmC,CAAC;IACxC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACjD,QAAQ,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACpB,KAAK,cAAc;gBAAE,UAAU,GAAG,IAAI,CAAC;gBAAC,MAAM;YAC9C,KAAK,cAAc;gBAAE,UAAU,GAAG,KAAK,CAAC;gBAAC,MAAM;YAC/C,KAAK,aAAa,CAAC,CAAC,CAAC;gBACnB,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;gBACxC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,eAAe,GAAG,CAAC,IAAI,eAAe,GAAG,KAAK;oBAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;gBACjJ,MAAM;YACR,CAAC;YACD,OAAO,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IACD,IAAI,CAAC,UAAU;QAAE,OAAO,CAAC,KAAK,GAAG,aAAa,CAAC;IAC/C,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACjG,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,GAAG,EAAE,GAAG,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/G,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC5D,IAAI,CAAC;QAAC,IAAI,MAAM,GAAG,CAAC,KAAK,EAAE;YAAE,MAAM,GAAG,CAAC,MAAM,CAAC;IAAC,CAAC;YAAS,CAAC;QAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAAC,CAAC;AACzH,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7F,KAAK,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5G,CAAC"}