@mysten-incubation/devstack 0.0.1 → 0.1.1

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 (838) hide show
  1. package/README.md +8 -4
  2. package/dashboard-ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  3. package/dashboard-ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  4. package/dashboard-ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  5. package/dashboard-ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  6. package/dashboard-ui/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
  7. package/dashboard-ui/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
  8. package/dashboard-ui/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
  9. package/dashboard-ui/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
  10. package/dashboard-ui/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
  11. package/dashboard-ui/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
  12. package/dashboard-ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  13. package/dashboard-ui/assets/index-Bmi1UtAg.js +1356 -0
  14. package/dashboard-ui/assets/index-D5EShVt4.js +3 -0
  15. package/dashboard-ui/assets/index-Deml9drg.css +1 -0
  16. package/dashboard-ui/index.html +13 -0
  17. package/dist/api/define-capabilities.d.mts +14 -3
  18. package/dist/api/define-capabilities.mjs +18 -1
  19. package/dist/api/define-capabilities.mjs.map +1 -1
  20. package/dist/api/define-devstack.d.mts +1 -1
  21. package/dist/api/define-devstack.mjs +6 -26
  22. package/dist/api/define-devstack.mjs.map +1 -1
  23. package/dist/api/define-plugin.d.mts +1 -0
  24. package/dist/api/define-plugin.mjs +2 -0
  25. package/dist/api/inference-network.d.mts +132 -0
  26. package/dist/api/inference-network.mjs +105 -16
  27. package/dist/api/inference-network.mjs.map +1 -1
  28. package/dist/api/plugin-errors.d.mts +7 -0
  29. package/dist/api/run-stack.d.mts +20 -9
  30. package/dist/api/run-stack.mjs +56 -29
  31. package/dist/api/run-stack.mjs.map +1 -1
  32. package/dist/build-integrations/playwright/config.d.mts +6 -4
  33. package/dist/build-integrations/playwright/config.mjs +5 -3
  34. package/dist/build-integrations/playwright/config.mjs.map +1 -1
  35. package/dist/build-integrations/playwright/errors.d.mts +1 -1
  36. package/dist/build-integrations/playwright/global-setup.d.mts +43 -17
  37. package/dist/build-integrations/playwright/global-setup.mjs +250 -13
  38. package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
  39. package/dist/build-integrations/playwright/index.d.mts +5 -3
  40. package/dist/build-integrations/playwright/index.mjs +4 -2
  41. package/dist/build-integrations/playwright/stack-context.d.mts +0 -1
  42. package/dist/build-integrations/playwright/stack-context.mjs +24 -63
  43. package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
  44. package/dist/build-integrations/playwright/wallet-context.d.mts +7 -10
  45. package/dist/build-integrations/playwright/wallet-context.mjs +8 -13
  46. package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
  47. package/dist/build-integrations/runtime/cold-start-url.d.mts +5 -0
  48. package/dist/build-integrations/runtime/cold-start-url.mjs +4 -3
  49. package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
  50. package/dist/build-integrations/runtime/conventional-routes.d.mts +51 -0
  51. package/dist/build-integrations/runtime/conventional-routes.mjs +92 -0
  52. package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -0
  53. package/dist/build-integrations/runtime/discover.d.mts +38 -30
  54. package/dist/build-integrations/runtime/discover.mjs +73 -70
  55. package/dist/build-integrations/runtime/discover.mjs.map +1 -1
  56. package/dist/build-integrations/runtime/endpoint-registry.d.mts +3 -1
  57. package/dist/build-integrations/runtime/endpoint-registry.mjs +3 -1
  58. package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
  59. package/dist/build-integrations/runtime/index.d.mts +7 -2
  60. package/dist/build-integrations/runtime/index.mjs +9 -2
  61. package/dist/build-integrations/runtime/manifest-types.d.mts +2 -0
  62. package/dist/build-integrations/runtime/manifest-types.mjs +3 -0
  63. package/dist/build-integrations/runtime/playwright-stack-context-slot.d.mts +29 -0
  64. package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs +10 -0
  65. package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs.map +1 -0
  66. package/dist/build-integrations/runtime/read-stack-context.d.mts +1 -1
  67. package/dist/build-integrations/runtime/read-stack-context.mjs +10 -4
  68. package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
  69. package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +48 -0
  70. package/dist/build-integrations/runtime/resolve-discovery-env.mjs +42 -0
  71. package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -0
  72. package/dist/build-integrations/runtime/stack-context.d.mts +1 -1
  73. package/dist/build-integrations/runtime/wallet-paths.mjs +2 -0
  74. package/dist/build-integrations/vite/index.d.mts +51 -0
  75. package/dist/build-integrations/vite/index.mjs +73 -0
  76. package/dist/build-integrations/vite/index.mjs.map +1 -0
  77. package/dist/build-integrations/vitest/env.d.mts +4 -3
  78. package/dist/build-integrations/vitest/env.mjs +6 -4
  79. package/dist/build-integrations/vitest/env.mjs.map +1 -1
  80. package/dist/build-integrations/vitest/errors.d.mts +25 -4
  81. package/dist/build-integrations/vitest/errors.mjs +15 -2
  82. package/dist/build-integrations/vitest/errors.mjs.map +1 -1
  83. package/dist/build-integrations/vitest/index.d.mts +2 -2
  84. package/dist/build-integrations/vitest/index.mjs +3 -3
  85. package/dist/build-integrations/vitest/setup.d.mts +4 -2
  86. package/dist/build-integrations/vitest/setup.mjs +2 -2
  87. package/dist/build-integrations/vitest/setup.mjs.map +1 -1
  88. package/dist/build-integrations/vitest/stack-context.d.mts +0 -1
  89. package/dist/build-integrations/vitest/stack-context.mjs +33 -13
  90. package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
  91. package/dist/cli/bail.mjs +23 -0
  92. package/dist/cli/bail.mjs.map +1 -0
  93. package/dist/{surfaces/cli/commands → cli}/doctor-probes.mjs +26 -16
  94. package/dist/cli/doctor-probes.mjs.map +1 -0
  95. package/dist/cli/main.d.mts +15 -1
  96. package/dist/cli/main.mjs +166 -482
  97. package/dist/cli/main.mjs.map +1 -1
  98. package/dist/cli/prune-direct.mjs +31 -201
  99. package/dist/cli/prune-direct.mjs.map +1 -1
  100. package/dist/cli/snapshot-reader.mjs +14 -11
  101. package/dist/cli/snapshot-reader.mjs.map +1 -1
  102. package/dist/cli/up-lifecycle.mjs +4 -1
  103. package/dist/cli/up-lifecycle.mjs.map +1 -1
  104. package/dist/cli/wirings/apply.mjs +95 -0
  105. package/dist/cli/wirings/apply.mjs.map +1 -0
  106. package/dist/cli/wirings/build-verb-layers.mjs +42 -0
  107. package/dist/cli/wirings/build-verb-layers.mjs.map +1 -0
  108. package/dist/cli/wirings/config-loader.mjs +69 -0
  109. package/dist/cli/wirings/config-loader.mjs.map +1 -0
  110. package/dist/cli/wirings/engine-command.mjs +30 -0
  111. package/dist/cli/wirings/engine-command.mjs.map +1 -0
  112. package/dist/cli/wirings/identity.mjs +72 -0
  113. package/dist/cli/wirings/identity.mjs.map +1 -0
  114. package/dist/cli/wirings/provide-file-system.mjs +7 -0
  115. package/dist/cli/wirings/provide-file-system.mjs.map +1 -0
  116. package/dist/cli/wirings/snapshot.mjs +177 -0
  117. package/dist/cli/wirings/snapshot.mjs.map +1 -0
  118. package/dist/cli/wirings/up.mjs +287 -0
  119. package/dist/cli/wirings/up.mjs.map +1 -0
  120. package/dist/cli/wirings/wipe.mjs +40 -0
  121. package/dist/cli/wirings/wipe.mjs.map +1 -0
  122. package/dist/contracts/capability-decl.d.mts +0 -2
  123. package/dist/contracts/codegenable.d.mts +43 -0
  124. package/dist/contracts/container-runtime.d.mts +26 -1
  125. package/dist/contracts/faucet-strategy.d.mts +25 -0
  126. package/dist/contracts/funding-strategy.d.mts +42 -0
  127. package/dist/contracts/plugin-expander.mjs +60 -0
  128. package/dist/contracts/plugin-expander.mjs.map +1 -0
  129. package/dist/contracts/projection.d.mts +5 -3
  130. package/dist/contracts/renderer.d.mts +23 -0
  131. package/dist/contracts/routable.d.mts +14 -1
  132. package/dist/contracts/snapshotable.d.mts +25 -3
  133. package/dist/contracts/wallet-protocol.d.mts +52 -0
  134. package/dist/contracts/wallet-protocol.mjs +52 -0
  135. package/dist/contracts/wallet-protocol.mjs.map +1 -0
  136. package/dist/index.d.mts +26 -25
  137. package/dist/index.mjs +20 -13
  138. package/dist/{runtime → orchestrators}/built-in-plugin-layers.mjs +10 -5
  139. package/dist/orchestrators/built-in-plugin-layers.mjs.map +1 -0
  140. package/dist/orchestrators/codegen/bindings.d.mts +1 -2
  141. package/dist/orchestrators/codegen/bindings.mjs +52 -186
  142. package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
  143. package/dist/orchestrators/codegen/emit.mjs +19 -1
  144. package/dist/orchestrators/codegen/emit.mjs.map +1 -1
  145. package/dist/orchestrators/codegen/errors.mjs +8 -3
  146. package/dist/orchestrators/codegen/errors.mjs.map +1 -1
  147. package/dist/orchestrators/codegen/format.mjs +47 -39
  148. package/dist/orchestrators/codegen/format.mjs.map +1 -1
  149. package/dist/orchestrators/codegen/gitignore.mjs +3 -1
  150. package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
  151. package/dist/orchestrators/codegen/output-location.mjs +41 -0
  152. package/dist/orchestrators/codegen/output-location.mjs.map +1 -0
  153. package/dist/orchestrators/codegen/paths.d.mts +1 -1
  154. package/dist/orchestrators/codegen/paths.mjs +40 -14
  155. package/dist/orchestrators/codegen/paths.mjs.map +1 -1
  156. package/dist/orchestrators/codegen/permissions.mjs +3 -1
  157. package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
  158. package/dist/orchestrators/codegen/service.mjs +157 -105
  159. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  160. package/dist/orchestrators/lifecycle-prune/errors.mjs +31 -0
  161. package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -0
  162. package/dist/orchestrators/lifecycle-prune/index.mjs +350 -0
  163. package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -0
  164. package/dist/orchestrators/router/cleanup.mjs +26 -27
  165. package/dist/orchestrators/router/cleanup.mjs.map +1 -1
  166. package/dist/orchestrators/router/entrypoints.mjs +2 -2
  167. package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
  168. package/dist/orchestrators/router/file-provider.mjs +14 -5
  169. package/dist/orchestrators/router/file-provider.mjs.map +1 -1
  170. package/dist/orchestrators/router/hostname.mjs +19 -55
  171. package/dist/orchestrators/router/hostname.mjs.map +1 -1
  172. package/dist/orchestrators/router/index.mjs +11 -0
  173. package/dist/orchestrators/router/profile.mjs +2 -2
  174. package/dist/orchestrators/router/sentinels.mjs +26 -0
  175. package/dist/orchestrators/router/sentinels.mjs.map +1 -0
  176. package/dist/orchestrators/router/service.mjs +40 -37
  177. package/dist/orchestrators/router/service.mjs.map +1 -1
  178. package/dist/orchestrators/router/traefik-container.mjs +27 -16
  179. package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
  180. package/dist/{substrate/runtime → orchestrators}/run.mjs +26 -21
  181. package/dist/orchestrators/run.mjs.map +1 -0
  182. package/dist/orchestrators/runtime-composition.d.mts +1 -1
  183. package/dist/orchestrators/runtime-composition.mjs +123 -39
  184. package/dist/orchestrators/runtime-composition.mjs.map +1 -1
  185. package/dist/orchestrators/snapshot/capture-command.d.mts +1 -0
  186. package/dist/orchestrators/snapshot/capture-command.mjs +25 -0
  187. package/dist/orchestrators/snapshot/capture-command.mjs.map +1 -0
  188. package/dist/orchestrators/snapshot/capture.mjs +42 -28
  189. package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
  190. package/dist/orchestrators/snapshot/descriptor.mjs +35 -10
  191. package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
  192. package/dist/orchestrators/snapshot/identity-guard.mjs +1 -1
  193. package/dist/orchestrators/snapshot/index.mjs +13 -0
  194. package/dist/orchestrators/snapshot/integrity.d.mts +1 -1
  195. package/dist/orchestrators/snapshot/integrity.mjs +73 -17
  196. package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
  197. package/dist/orchestrators/snapshot/pending-marker.d.mts +1 -0
  198. package/dist/orchestrators/snapshot/pending-marker.mjs +99 -0
  199. package/dist/orchestrators/snapshot/pending-marker.mjs.map +1 -0
  200. package/dist/orchestrators/snapshot/phase-error.mjs +23 -0
  201. package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -0
  202. package/dist/orchestrators/snapshot/prune.mjs +20 -55
  203. package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
  204. package/dist/orchestrators/snapshot/recover-pending.d.mts +1 -0
  205. package/dist/orchestrators/snapshot/recover-pending.mjs +223 -0
  206. package/dist/orchestrators/snapshot/recover-pending.mjs.map +1 -0
  207. package/dist/orchestrators/snapshot/restore.mjs +118 -84
  208. package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
  209. package/dist/orchestrators/snapshot/service.mjs +53 -56
  210. package/dist/orchestrators/snapshot/service.mjs.map +1 -1
  211. package/dist/orchestrators/snapshot/state-document.d.mts +1 -0
  212. package/dist/orchestrators/snapshot/state-document.mjs +47 -7
  213. package/dist/orchestrators/snapshot/state-document.mjs.map +1 -1
  214. package/dist/orchestrators/snapshot/wipe.mjs +67 -10
  215. package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
  216. package/dist/plugins/account/codegen.mjs +9 -8
  217. package/dist/plugins/account/codegen.mjs.map +1 -1
  218. package/dist/plugins/account/errors.d.mts +33 -6
  219. package/dist/plugins/account/errors.mjs.map +1 -1
  220. package/dist/plugins/account/funding.d.mts +29 -19
  221. package/dist/plugins/account/funding.mjs +84 -66
  222. package/dist/plugins/account/funding.mjs.map +1 -1
  223. package/dist/plugins/account/index.d.mts +2 -4
  224. package/dist/plugins/account/index.mjs +64 -31
  225. package/dist/plugins/account/index.mjs.map +1 -1
  226. package/dist/plugins/account/keypair.mjs +6 -9
  227. package/dist/plugins/account/keypair.mjs.map +1 -1
  228. package/dist/plugins/account/lease.mjs +1 -0
  229. package/dist/plugins/account/lease.mjs.map +1 -1
  230. package/dist/plugins/account/registry.d.mts +14 -1
  231. package/dist/plugins/account/registry.mjs +18 -18
  232. package/dist/plugins/account/registry.mjs.map +1 -1
  233. package/dist/plugins/account/service.d.mts +23 -4
  234. package/dist/plugins/account/service.mjs +83 -45
  235. package/dist/plugins/account/service.mjs.map +1 -1
  236. package/dist/plugins/account/snapshot.mjs +2 -5
  237. package/dist/plugins/account/snapshot.mjs.map +1 -1
  238. package/dist/plugins/account/spans.mjs +16 -0
  239. package/dist/plugins/account/spans.mjs.map +1 -0
  240. package/dist/plugins/account/variants/keystore.mjs +6 -8
  241. package/dist/plugins/account/variants/keystore.mjs.map +1 -1
  242. package/dist/plugins/action/build-context.d.mts +3 -2
  243. package/dist/plugins/action/errors.d.mts +16 -12
  244. package/dist/plugins/action/errors.mjs.map +1 -1
  245. package/dist/plugins/action/execute.mjs +65 -103
  246. package/dist/plugins/action/execute.mjs.map +1 -1
  247. package/dist/plugins/action/index.d.mts +0 -2
  248. package/dist/plugins/action/index.mjs +17 -13
  249. package/dist/plugins/action/index.mjs.map +1 -1
  250. package/dist/plugins/action/service.mjs +38 -35
  251. package/dist/plugins/action/service.mjs.map +1 -1
  252. package/dist/plugins/action/spans.mjs +11 -0
  253. package/dist/plugins/action/spans.mjs.map +1 -0
  254. package/dist/plugins/coin/codegen.mjs +9 -8
  255. package/dist/plugins/coin/codegen.mjs.map +1 -1
  256. package/dist/plugins/coin/discovery.mjs +8 -4
  257. package/dist/plugins/coin/discovery.mjs.map +1 -1
  258. package/dist/plugins/coin/index.d.mts +11 -29
  259. package/dist/plugins/coin/index.mjs +56 -27
  260. package/dist/plugins/coin/index.mjs.map +1 -1
  261. package/dist/plugins/coin/metadata.mjs +8 -15
  262. package/dist/plugins/coin/metadata.mjs.map +1 -1
  263. package/dist/plugins/coin/mint.d.mts +30 -11
  264. package/dist/plugins/coin/mint.mjs +78 -74
  265. package/dist/plugins/coin/mint.mjs.map +1 -1
  266. package/dist/plugins/coin/registry.mjs +4 -7
  267. package/dist/plugins/coin/registry.mjs.map +1 -1
  268. package/dist/plugins/coin/service.d.mts +39 -1
  269. package/dist/plugins/coin/service.mjs +11 -4
  270. package/dist/plugins/coin/service.mjs.map +1 -1
  271. package/dist/plugins/coin/spans.mjs +20 -0
  272. package/dist/plugins/coin/spans.mjs.map +1 -0
  273. package/dist/plugins/dashboard/domain.mjs +402 -0
  274. package/dist/plugins/dashboard/domain.mjs.map +1 -0
  275. package/dist/plugins/dashboard/index.d.mts +29 -0
  276. package/dist/plugins/dashboard/index.mjs +91 -0
  277. package/dist/plugins/dashboard/index.mjs.map +1 -0
  278. package/dist/plugins/dashboard/origin-policy.mjs +36 -0
  279. package/dist/plugins/dashboard/origin-policy.mjs.map +1 -0
  280. package/dist/plugins/dashboard/routable.mjs +27 -0
  281. package/dist/plugins/dashboard/routable.mjs.map +1 -0
  282. package/dist/plugins/dashboard/schema/builder.mjs +21 -0
  283. package/dist/plugins/dashboard/schema/builder.mjs.map +1 -0
  284. package/dist/plugins/dashboard/schema/enums.mjs +72 -0
  285. package/dist/plugins/dashboard/schema/enums.mjs.map +1 -0
  286. package/dist/plugins/dashboard/schema/root.mjs +351 -0
  287. package/dist/plugins/dashboard/schema/root.mjs.map +1 -0
  288. package/dist/plugins/dashboard/schema/types.mjs +508 -0
  289. package/dist/plugins/dashboard/schema/types.mjs.map +1 -0
  290. package/dist/plugins/dashboard/schema.mjs +11 -0
  291. package/dist/plugins/dashboard/schema.mjs.map +1 -0
  292. package/dist/plugins/dashboard/server.mjs +166 -0
  293. package/dist/plugins/dashboard/server.mjs.map +1 -0
  294. package/dist/plugins/deepbook/deploy.mjs +53 -115
  295. package/dist/plugins/deepbook/deploy.mjs.map +1 -1
  296. package/dist/plugins/deepbook/errors.mjs +1 -0
  297. package/dist/plugins/deepbook/errors.mjs.map +1 -1
  298. package/dist/plugins/deepbook/faucet-strategy.mjs +47 -34
  299. package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
  300. package/dist/plugins/deepbook/hash.mjs +9 -0
  301. package/dist/plugins/deepbook/hash.mjs.map +1 -0
  302. package/dist/plugins/deepbook/index.d.mts +1 -10
  303. package/dist/plugins/deepbook/index.mjs +25 -22
  304. package/dist/plugins/deepbook/index.mjs.map +1 -1
  305. package/dist/plugins/deepbook/pyth/index.mjs +26 -55
  306. package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
  307. package/dist/plugins/deepbook/snapshot.mjs +17 -29
  308. package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
  309. package/dist/plugins/deepbook/spans.mjs +18 -0
  310. package/dist/plugins/deepbook/spans.mjs.map +1 -0
  311. package/dist/plugins/deepbook/type-strings.mjs +65 -0
  312. package/dist/plugins/deepbook/type-strings.mjs.map +1 -0
  313. package/dist/plugins/deepbook/types.d.mts +2 -3
  314. package/dist/plugins/deepbook/types.mjs.map +1 -1
  315. package/dist/plugins/faucet/dispatcher.d.mts +21 -2
  316. package/dist/plugins/faucet/dispatcher.mjs +1 -2
  317. package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
  318. package/dist/plugins/faucet/errors.d.mts +11 -24
  319. package/dist/plugins/faucet/errors.mjs.map +1 -1
  320. package/dist/plugins/faucet/http.mjs +33 -14
  321. package/dist/plugins/faucet/http.mjs.map +1 -1
  322. package/dist/plugins/faucet/index.d.mts +15 -5
  323. package/dist/plugins/faucet/index.mjs +3 -0
  324. package/dist/plugins/faucet/index.mjs.map +1 -1
  325. package/dist/plugins/faucet/spans.mjs +12 -0
  326. package/dist/plugins/faucet/spans.mjs.map +1 -0
  327. package/dist/plugins/host-service/errors.d.mts +1 -1
  328. package/dist/plugins/host-service/index.mjs +34 -8
  329. package/dist/plugins/host-service/index.mjs.map +1 -1
  330. package/dist/plugins/host-service/routable.mjs +2 -1
  331. package/dist/plugins/host-service/routable.mjs.map +1 -1
  332. package/dist/plugins/host-service/service.d.mts +21 -0
  333. package/dist/plugins/host-service/service.mjs +136 -81
  334. package/dist/plugins/host-service/service.mjs.map +1 -1
  335. package/dist/plugins/internal/acquire-on-chain-artifact.mjs +23 -0
  336. package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -0
  337. package/dist/plugins/internal/codegen-helpers.mjs +18 -0
  338. package/dist/plugins/internal/codegen-helpers.mjs.map +1 -0
  339. package/dist/plugins/internal/funding-failure-error.mjs +32 -0
  340. package/dist/plugins/internal/funding-failure-error.mjs.map +1 -0
  341. package/dist/plugins/package/build.mjs +1 -1
  342. package/dist/plugins/package/build.mjs.map +1 -1
  343. package/dist/plugins/package/codegen.mjs +26 -0
  344. package/dist/plugins/package/codegen.mjs.map +1 -1
  345. package/dist/plugins/package/errors.d.mts +16 -6
  346. package/dist/plugins/package/errors.mjs.map +1 -1
  347. package/dist/plugins/package/index.d.mts +0 -4
  348. package/dist/plugins/package/index.mjs +27 -21
  349. package/dist/plugins/package/index.mjs.map +1 -1
  350. package/dist/plugins/package/mode-known.mjs +26 -12
  351. package/dist/plugins/package/mode-known.mjs.map +1 -1
  352. package/dist/plugins/package/mode-local.mjs +47 -41
  353. package/dist/plugins/package/mode-local.mjs.map +1 -1
  354. package/dist/plugins/package/publish-executor.mjs +55 -114
  355. package/dist/plugins/package/publish-executor.mjs.map +1 -1
  356. package/dist/plugins/package/registry.mjs +2 -1
  357. package/dist/plugins/package/registry.mjs.map +1 -1
  358. package/dist/plugins/package/service.mjs.map +1 -1
  359. package/dist/plugins/package/spans.mjs +14 -0
  360. package/dist/plugins/package/spans.mjs.map +1 -0
  361. package/dist/plugins/postgres/db-ensure.mjs +19 -12
  362. package/dist/plugins/postgres/db-ensure.mjs.map +1 -1
  363. package/dist/plugins/postgres/errors.d.mts +5 -0
  364. package/dist/plugins/postgres/errors.mjs.map +1 -1
  365. package/dist/plugins/postgres/index.mjs +10 -17
  366. package/dist/plugins/postgres/index.mjs.map +1 -1
  367. package/dist/plugins/postgres/service.mjs +58 -24
  368. package/dist/plugins/postgres/service.mjs.map +1 -1
  369. package/dist/plugins/postgres/snapshot.mjs.map +1 -1
  370. package/dist/plugins/postgres/spans.mjs +11 -0
  371. package/dist/plugins/postgres/spans.mjs.map +1 -0
  372. package/dist/plugins/router-entrypoints.mjs +4 -4
  373. package/dist/plugins/router-entrypoints.mjs.map +1 -1
  374. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +15 -13
  375. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
  376. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +15 -19
  377. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
  378. package/dist/plugins/seal/codegen.d.mts +2 -3
  379. package/dist/plugins/seal/codegen.mjs.map +1 -1
  380. package/dist/plugins/seal/config-render.mjs +35 -8
  381. package/dist/plugins/seal/config-render.mjs.map +1 -1
  382. package/dist/plugins/seal/deploy.mjs +31 -39
  383. package/dist/plugins/seal/deploy.mjs.map +1 -1
  384. package/dist/plugins/seal/errors.d.mts +1 -3
  385. package/dist/plugins/seal/errors.mjs +7 -1
  386. package/dist/plugins/seal/errors.mjs.map +1 -1
  387. package/dist/plugins/seal/index.d.mts +1 -6
  388. package/dist/plugins/seal/index.mjs +40 -14
  389. package/dist/plugins/seal/index.mjs.map +1 -1
  390. package/dist/plugins/seal/key-manager.d.mts +1 -9
  391. package/dist/plugins/seal/key-manager.mjs +2 -18
  392. package/dist/plugins/seal/key-manager.mjs.map +1 -1
  393. package/dist/plugins/seal/key-server.mjs +37 -36
  394. package/dist/plugins/seal/key-server.mjs.map +1 -1
  395. package/dist/plugins/seal/keygen.mjs +16 -17
  396. package/dist/plugins/seal/keygen.mjs.map +1 -1
  397. package/dist/plugins/seal/mode/fork-known.mjs +36 -14
  398. package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
  399. package/dist/plugins/seal/mode/live.d.mts +6 -4
  400. package/dist/plugins/seal/mode/live.mjs +14 -21
  401. package/dist/plugins/seal/mode/live.mjs.map +1 -1
  402. package/dist/plugins/seal/mode/local-keygen.mjs +57 -21
  403. package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
  404. package/dist/plugins/seal/registry-publish.mjs +1 -0
  405. package/dist/plugins/seal/registry-publish.mjs.map +1 -1
  406. package/dist/plugins/seal/routable.mjs.map +1 -1
  407. package/dist/plugins/seal/service.mjs.map +1 -1
  408. package/dist/plugins/seal/spans.mjs +18 -0
  409. package/dist/plugins/seal/spans.mjs.map +1 -0
  410. package/dist/plugins/sui/auto-tick.mjs +17 -23
  411. package/dist/plugins/sui/auto-tick.mjs.map +1 -1
  412. package/dist/plugins/sui/chain-build-container.mjs +18 -0
  413. package/dist/plugins/sui/chain-build-container.mjs.map +1 -0
  414. package/dist/plugins/sui/chain-probe.d.mts +5 -5
  415. package/dist/plugins/sui/chain-probe.mjs +28 -58
  416. package/dist/plugins/sui/chain-probe.mjs.map +1 -1
  417. package/dist/plugins/sui/codegen.mjs +26 -0
  418. package/dist/plugins/sui/codegen.mjs.map +1 -1
  419. package/dist/plugins/sui/errors.d.mts +2 -29
  420. package/dist/plugins/sui/errors.mjs +1 -3
  421. package/dist/plugins/sui/errors.mjs.map +1 -1
  422. package/dist/plugins/sui/fork-faucet-strategy.d.mts +1 -0
  423. package/dist/plugins/sui/fork-faucet-strategy.mjs +55 -0
  424. package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -0
  425. package/dist/plugins/sui/fork-orchestration.d.mts +1 -0
  426. package/dist/plugins/sui/fork-orchestration.mjs +170 -2
  427. package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
  428. package/dist/plugins/sui/fork-transaction.d.mts +2 -0
  429. package/dist/plugins/sui/fork-transaction.mjs +92 -28
  430. package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
  431. package/dist/plugins/sui/index.d.mts +33 -70
  432. package/dist/plugins/sui/index.mjs +58 -35
  433. package/dist/plugins/sui/index.mjs.map +1 -1
  434. package/dist/plugins/{faucet/strategies/sui-local.mjs → sui/local-faucet-strategy.mjs} +10 -7
  435. package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -0
  436. package/dist/plugins/sui/mode/external.mjs +7 -13
  437. package/dist/plugins/sui/mode/external.mjs.map +1 -1
  438. package/dist/plugins/sui/mode/fork.mjs +136 -59
  439. package/dist/plugins/sui/mode/fork.mjs.map +1 -1
  440. package/dist/plugins/sui/mode/live.mjs +10 -16
  441. package/dist/plugins/sui/mode/live.mjs.map +1 -1
  442. package/dist/plugins/sui/mode/local.mjs +37 -36
  443. package/dist/plugins/sui/mode/local.mjs.map +1 -1
  444. package/dist/plugins/sui/mode/shared-boot.mjs +38 -115
  445. package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
  446. package/dist/plugins/sui/mode/shared.d.mts +1 -5
  447. package/dist/plugins/sui/mode/shared.mjs.map +1 -1
  448. package/dist/plugins/sui/mode/spec.d.mts +18 -0
  449. package/dist/plugins/sui/move-summary-runner.mjs +207 -0
  450. package/dist/plugins/sui/move-summary-runner.mjs.map +1 -0
  451. package/dist/plugins/sui/network-resolver.d.mts +4 -3
  452. package/dist/plugins/sui/service.mjs.map +1 -1
  453. package/dist/plugins/sui/snapshot.mjs +3 -0
  454. package/dist/plugins/sui/snapshot.mjs.map +1 -1
  455. package/dist/plugins/sui/spans.mjs +17 -0
  456. package/dist/plugins/sui/spans.mjs.map +1 -0
  457. package/dist/plugins/wallet/codegen.d.mts +0 -1
  458. package/dist/plugins/wallet/codegen.mjs +8 -9
  459. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  460. package/dist/plugins/wallet/index.d.mts +6 -8
  461. package/dist/plugins/wallet/index.mjs +46 -35
  462. package/dist/plugins/wallet/index.mjs.map +1 -1
  463. package/dist/plugins/wallet/origin-policy.mjs +12 -28
  464. package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
  465. package/dist/plugins/wallet/pairing.mjs +6 -4
  466. package/dist/plugins/wallet/pairing.mjs.map +1 -1
  467. package/dist/plugins/wallet/protocol.d.mts +1 -0
  468. package/dist/plugins/wallet/protocol.mjs +5 -44
  469. package/dist/plugins/wallet/protocol.mjs.map +1 -1
  470. package/dist/plugins/wallet/routable.mjs +2 -8
  471. package/dist/plugins/wallet/routable.mjs.map +1 -1
  472. package/dist/plugins/wallet/server.mjs +36 -44
  473. package/dist/plugins/wallet/server.mjs.map +1 -1
  474. package/dist/plugins/wallet/service.d.mts +1 -5
  475. package/dist/plugins/wallet/service.mjs +22 -18
  476. package/dist/plugins/wallet/service.mjs.map +1 -1
  477. package/dist/plugins/wallet/spans.mjs +22 -0
  478. package/dist/plugins/wallet/spans.mjs.map +1 -0
  479. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +33 -8
  480. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
  481. package/dist/plugins/walrus/codegen.d.mts +3 -3
  482. package/dist/plugins/walrus/codegen.mjs.map +1 -1
  483. package/dist/plugins/walrus/deploy-paths.mjs +21 -5
  484. package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
  485. package/dist/plugins/walrus/deploy.mjs +67 -69
  486. package/dist/plugins/walrus/deploy.mjs.map +1 -1
  487. package/dist/plugins/walrus/errors.mjs +1 -0
  488. package/dist/plugins/walrus/errors.mjs.map +1 -1
  489. package/dist/plugins/walrus/faucet-strategy.mjs +12 -7
  490. package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
  491. package/dist/plugins/walrus/index.d.mts +2 -13
  492. package/dist/plugins/walrus/index.mjs +18 -23
  493. package/dist/plugins/walrus/index.mjs.map +1 -1
  494. package/dist/plugins/walrus/mode/known-deploy.mjs +7 -5
  495. package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
  496. package/dist/plugins/walrus/mode/local-cluster.mjs +26 -7
  497. package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
  498. package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
  499. package/dist/plugins/walrus/routable.mjs +7 -23
  500. package/dist/plugins/walrus/routable.mjs.map +1 -1
  501. package/dist/plugins/walrus/service.mjs.map +1 -1
  502. package/dist/plugins/walrus/spans.mjs +18 -0
  503. package/dist/plugins/walrus/spans.mjs.map +1 -0
  504. package/dist/plugins/walrus/storage-nodes.d.mts +6 -2
  505. package/dist/plugins/walrus/storage-nodes.mjs +15 -8
  506. package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
  507. package/dist/plugins/walrus/wal-swap.mjs +58 -15
  508. package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
  509. package/dist/primitives/artifact-publisher.d.mts +1 -1
  510. package/dist/primitives/artifact-publisher.mjs +15 -0
  511. package/dist/primitives/artifact-publisher.mjs.map +1 -0
  512. package/dist/runtime/docker/client.mjs +17 -4
  513. package/dist/runtime/docker/client.mjs.map +1 -1
  514. package/dist/runtime/docker/container.mjs +219 -132
  515. package/dist/runtime/docker/container.mjs.map +1 -1
  516. package/dist/runtime/docker/errors.mjs +0 -6
  517. package/dist/runtime/docker/errors.mjs.map +1 -1
  518. package/dist/runtime/docker/exec.mjs +21 -18
  519. package/dist/runtime/docker/exec.mjs.map +1 -1
  520. package/dist/runtime/docker/image.mjs +36 -13
  521. package/dist/runtime/docker/image.mjs.map +1 -1
  522. package/dist/runtime/docker/index.mjs +14 -0
  523. package/dist/runtime/docker/inspect-and-decode.mjs +51 -0
  524. package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -0
  525. package/dist/runtime/docker/inventory.mjs +14 -11
  526. package/dist/runtime/docker/inventory.mjs.map +1 -1
  527. package/dist/runtime/docker/labels.mjs +38 -5
  528. package/dist/runtime/docker/labels.mjs.map +1 -1
  529. package/dist/runtime/docker/logs.d.mts +1 -1
  530. package/dist/runtime/docker/logs.mjs.map +1 -1
  531. package/dist/runtime/docker/network.mjs +83 -47
  532. package/dist/runtime/docker/network.mjs.map +1 -1
  533. package/dist/runtime/docker/render-run-args.mjs +82 -0
  534. package/dist/runtime/docker/render-run-args.mjs.map +1 -0
  535. package/dist/runtime/docker/service.d.mts +1 -1
  536. package/dist/runtime/docker/service.mjs +12 -7
  537. package/dist/runtime/docker/service.mjs.map +1 -1
  538. package/dist/runtime/docker/sweep.mjs +118 -43
  539. package/dist/runtime/docker/sweep.mjs.map +1 -1
  540. package/dist/runtime/docker/volume.mjs +2 -0
  541. package/dist/runtime/docker/volume.mjs.map +1 -1
  542. package/dist/runtime/docker/wrap.mjs +30 -1
  543. package/dist/runtime/docker/wrap.mjs.map +1 -1
  544. package/dist/substrate/cross-process.mjs +14 -8
  545. package/dist/substrate/cross-process.mjs.map +1 -1
  546. package/dist/substrate/event-time.mjs +33 -0
  547. package/dist/substrate/event-time.mjs.map +1 -0
  548. package/dist/substrate/events.d.mts +28 -8
  549. package/dist/substrate/identity.d.mts +2 -1
  550. package/dist/substrate/manifest.d.mts +68 -5
  551. package/dist/substrate/manifest.mjs +34 -5
  552. package/dist/substrate/manifest.mjs.map +1 -1
  553. package/dist/substrate/network.d.mts +3 -5
  554. package/dist/substrate/options.d.mts +0 -2
  555. package/dist/substrate/plugin.d.mts +13 -0
  556. package/dist/substrate/plugin.mjs +4 -2
  557. package/dist/substrate/plugin.mjs.map +1 -1
  558. package/dist/substrate/projection.d.mts +35 -3
  559. package/dist/substrate/runtime/artifact-publisher/index.mjs +19 -9
  560. package/dist/substrate/runtime/artifact-publisher/index.mjs.map +1 -1
  561. package/dist/substrate/runtime/atomic-write.mjs +97 -32
  562. package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
  563. package/dist/substrate/runtime/cache/index.mjs +3 -0
  564. package/dist/substrate/runtime/cache/schema.mjs +2 -2
  565. package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
  566. package/dist/substrate/runtime/cache/service.mjs +9 -7
  567. package/dist/substrate/runtime/cache/service.mjs.map +1 -1
  568. package/dist/substrate/runtime/capability-sinks/index.d.mts +1 -1
  569. package/dist/substrate/runtime/capability-sinks/index.mjs +3 -0
  570. package/dist/substrate/runtime/capability-sinks/layer.mjs +1 -1
  571. package/dist/substrate/runtime/capability-sinks/service.d.mts +61 -2
  572. package/dist/substrate/runtime/capability-sinks/service.mjs +21 -26
  573. package/dist/substrate/runtime/capability-sinks/service.mjs.map +1 -1
  574. package/dist/substrate/runtime/config-validation.d.mts +1 -13
  575. package/dist/substrate/runtime/config-validation.mjs +2 -91
  576. package/dist/substrate/runtime/config-validation.mjs.map +1 -1
  577. package/dist/substrate/runtime/container-runtime.d.mts +1 -0
  578. package/dist/substrate/runtime/container-runtime.mjs +2 -0
  579. package/dist/substrate/runtime/control-plane/domain.mjs +101 -0
  580. package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -0
  581. package/dist/substrate/runtime/control-plane/service.mjs +7 -0
  582. package/dist/substrate/runtime/control-plane/service.mjs.map +1 -0
  583. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +46 -19
  584. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
  585. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs +30 -12
  586. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
  587. package/dist/substrate/runtime/cross-process/command-channel/index.mjs +5 -0
  588. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs +11 -2
  589. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
  590. package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs +1 -1
  591. package/dist/substrate/runtime/cross-process/index.mjs +11 -0
  592. package/dist/substrate/runtime/cross-process/live-clock.mjs +34 -0
  593. package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -0
  594. package/dist/substrate/runtime/cross-process/liveness.mjs +52 -10
  595. package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
  596. package/dist/substrate/runtime/cross-process/lock.d.mts +1 -0
  597. package/dist/substrate/runtime/cross-process/lock.mjs +23 -0
  598. package/dist/substrate/runtime/cross-process/lock.mjs.map +1 -0
  599. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +64 -0
  600. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -0
  601. package/dist/substrate/runtime/cross-process/roster.mjs +97 -83
  602. package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
  603. package/dist/substrate/runtime/cross-process/self-pid.mjs +8 -0
  604. package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -0
  605. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +24 -28
  606. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +1 -1
  607. package/dist/substrate/runtime/cross-process/stack-lock.mjs +41 -20
  608. package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
  609. package/dist/substrate/runtime/current-plugin.mjs +2 -2
  610. package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
  611. package/dist/substrate/runtime/errors.mjs +12 -6
  612. package/dist/substrate/runtime/errors.mjs.map +1 -1
  613. package/dist/substrate/runtime/format-unknown-error.mjs +30 -0
  614. package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -0
  615. package/dist/substrate/runtime/host-bind-mount-owner.mjs +10 -0
  616. package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -0
  617. package/dist/substrate/runtime/host-gateway.mjs +20 -0
  618. package/dist/substrate/runtime/host-gateway.mjs.map +1 -0
  619. package/dist/substrate/runtime/host-tree-tar/index.mjs +61 -50
  620. package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
  621. package/dist/substrate/runtime/http-probe.d.mts +1 -1
  622. package/dist/substrate/runtime/index.mjs +14 -0
  623. package/dist/substrate/runtime/lease-broker/index.d.mts +1 -1
  624. package/dist/substrate/runtime/lease-broker/index.mjs +2 -0
  625. package/dist/substrate/runtime/lease-broker/service.d.mts +1 -64
  626. package/dist/substrate/runtime/lease-broker/service.mjs +1 -1
  627. package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
  628. package/dist/substrate/runtime/lifecycle/dep-graph.mjs +22 -3
  629. package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
  630. package/dist/substrate/runtime/lifecycle/index.mjs +9 -0
  631. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +1 -1
  632. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +20 -3
  633. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
  634. package/dist/substrate/runtime/lifecycle/selective-restart.mjs +1 -1
  635. package/dist/substrate/runtime/lifecycle/signals.mjs +5 -5
  636. package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
  637. package/dist/substrate/runtime/lifecycle/state-machine.mjs +1 -1
  638. package/dist/substrate/runtime/managed-container.d.mts +19 -1
  639. package/dist/substrate/runtime/managed-container.mjs +20 -1
  640. package/dist/substrate/runtime/managed-container.mjs.map +1 -1
  641. package/dist/substrate/runtime/manifest/index.mjs +3 -0
  642. package/dist/substrate/runtime/manifest/manifest.mjs +15 -11
  643. package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
  644. package/dist/substrate/runtime/mode-errors.mjs +20 -0
  645. package/dist/substrate/runtime/mode-errors.mjs.map +1 -0
  646. package/dist/substrate/runtime/observability/cascade-formatter.mjs +9 -13
  647. package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
  648. package/dist/substrate/runtime/observability/formatter-registry.mjs +19 -36
  649. package/dist/substrate/runtime/observability/formatter-registry.mjs.map +1 -1
  650. package/dist/substrate/runtime/observability/ignore-with-log.d.mts +1 -0
  651. package/dist/substrate/runtime/observability/ignore-with-log.mjs +17 -0
  652. package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -0
  653. package/dist/substrate/runtime/observability/index.d.mts +1 -1
  654. package/dist/substrate/runtime/observability/index.mjs +13 -0
  655. package/dist/substrate/runtime/observability/log-store.d.mts +1 -0
  656. package/dist/substrate/runtime/observability/log-store.mjs +175 -0
  657. package/dist/substrate/runtime/observability/log-store.mjs.map +1 -0
  658. package/dist/substrate/runtime/observability/logger.d.mts +2 -1
  659. package/dist/substrate/runtime/observability/logger.mjs +5 -9
  660. package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
  661. package/dist/substrate/runtime/observability/output-truncate.mjs +35 -0
  662. package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -0
  663. package/dist/substrate/runtime/observability/pretty-error.mjs +9 -12
  664. package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
  665. package/dist/substrate/runtime/observability/redaction.d.mts +2 -13
  666. package/dist/substrate/runtime/observability/redaction.mjs +8 -14
  667. package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
  668. package/dist/substrate/runtime/observability/span-store.d.mts +1 -0
  669. package/dist/substrate/runtime/observability/span-store.mjs +110 -0
  670. package/dist/substrate/runtime/observability/span-store.mjs.map +1 -0
  671. package/dist/substrate/runtime/observability/spans.mjs +14 -14
  672. package/dist/substrate/runtime/observability/spans.mjs.map +1 -1
  673. package/dist/substrate/runtime/observability/subprocess-capture.mjs +1 -1
  674. package/dist/substrate/runtime/passthrough-or-wrap.mjs +33 -0
  675. package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -0
  676. package/dist/substrate/runtime/paths.d.mts +1 -1
  677. package/dist/substrate/runtime/paths.mjs +4 -3
  678. package/dist/substrate/runtime/paths.mjs.map +1 -1
  679. package/dist/substrate/runtime/phase-preserving-produce.mjs +40 -0
  680. package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -0
  681. package/dist/substrate/runtime/port-broker/index.mjs +3 -0
  682. package/dist/substrate/runtime/port-broker/service.mjs +170 -126
  683. package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
  684. package/dist/substrate/runtime/post-acquire-tasks.mjs +1 -1
  685. package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
  686. package/dist/substrate/runtime/probes.d.mts +7 -5
  687. package/dist/substrate/runtime/probes.mjs +3 -1
  688. package/dist/substrate/runtime/probes.mjs.map +1 -1
  689. package/dist/substrate/runtime/projection/index.mjs +4 -0
  690. package/dist/substrate/runtime/projection/operational-endpoints.mjs +1 -2
  691. package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
  692. package/dist/substrate/runtime/projection/persisted.mjs +33 -15
  693. package/dist/substrate/runtime/projection/persisted.mjs.map +1 -1
  694. package/dist/substrate/runtime/projection/state-ref.mjs +26 -2
  695. package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
  696. package/dist/substrate/runtime/projection/update.mjs +88 -24
  697. package/dist/substrate/runtime/projection/update.mjs.map +1 -1
  698. package/dist/substrate/runtime/random-suffix.mjs +11 -0
  699. package/dist/substrate/runtime/random-suffix.mjs.map +1 -0
  700. package/dist/substrate/runtime/retry-policy.d.mts +59 -1
  701. package/dist/substrate/runtime/retry-policy.mjs +66 -1
  702. package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
  703. package/dist/substrate/runtime/routed-url.d.mts +1 -0
  704. package/dist/substrate/runtime/routed-url.mjs +79 -0
  705. package/dist/substrate/runtime/routed-url.mjs.map +1 -0
  706. package/dist/substrate/runtime/runtime-decode.d.mts +1 -1
  707. package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
  708. package/dist/substrate/runtime/scoped-http-server.mjs +2 -3
  709. package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
  710. package/dist/substrate/runtime/scoped-multimap/index.mjs +2 -0
  711. package/dist/substrate/runtime/scoped-multimap/service.mjs +52 -0
  712. package/dist/substrate/runtime/scoped-multimap/service.mjs.map +1 -0
  713. package/dist/substrate/runtime/scoped-ref-map/index.mjs +2 -0
  714. package/dist/substrate/runtime/scoped-ref-map/service.mjs +2 -2
  715. package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +1 -1
  716. package/dist/substrate/runtime/stage-and-swap/index.mjs +72 -4
  717. package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
  718. package/dist/substrate/runtime/state-store/index.d.mts +1 -0
  719. package/dist/substrate/runtime/state-store/index.mjs +3 -0
  720. package/dist/substrate/runtime/state-store/schema.d.mts +1 -0
  721. package/dist/substrate/runtime/state-store/schema.mjs +19 -14
  722. package/dist/substrate/runtime/state-store/schema.mjs.map +1 -1
  723. package/dist/substrate/runtime/state-store/service.d.mts +1 -0
  724. package/dist/substrate/runtime/state-store/service.mjs +145 -0
  725. package/dist/substrate/runtime/state-store/service.mjs.map +1 -0
  726. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.d.mts +1 -0
  727. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs +33 -0
  728. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -0
  729. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs +1 -1
  730. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
  731. package/dist/substrate/runtime/strategy-registry/index.mjs +4 -0
  732. package/dist/substrate/runtime/strategy-registry/service.mjs +19 -33
  733. package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
  734. package/dist/substrate/runtime/subnet-broker.mjs +60 -0
  735. package/dist/substrate/runtime/subnet-broker.mjs.map +1 -0
  736. package/dist/substrate/runtime/sui-execute/index.d.mts +21 -1
  737. package/dist/substrate/runtime/sui-execute/index.mjs +84 -20
  738. package/dist/substrate/runtime/sui-execute/index.mjs.map +1 -1
  739. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.d.mts +1 -0
  740. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs +32 -0
  741. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +1 -0
  742. package/dist/substrate/runtime/sui-ledger/object-ref.mjs +32 -0
  743. package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +1 -0
  744. package/dist/substrate/runtime/sui-move-build/index.mjs +93 -19
  745. package/dist/substrate/runtime/sui-move-build/index.mjs.map +1 -1
  746. package/dist/substrate/runtime/supervisor/acquire-node.mjs +181 -0
  747. package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -0
  748. package/dist/substrate/runtime/supervisor/background-tasks.mjs +144 -0
  749. package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -0
  750. package/dist/substrate/runtime/supervisor/command-loop.mjs +68 -0
  751. package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -0
  752. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +151 -0
  753. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +1 -0
  754. package/dist/substrate/runtime/supervisor/errors.d.mts +1 -0
  755. package/dist/substrate/runtime/supervisor/errors.mjs +9 -0
  756. package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -0
  757. package/dist/substrate/runtime/supervisor/index.d.mts +1 -0
  758. package/dist/substrate/runtime/supervisor/index.mjs +3 -0
  759. package/dist/substrate/runtime/supervisor/shutdown.mjs +51 -0
  760. package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -0
  761. package/dist/substrate/runtime/supervisor/start-supervisor.d.mts +1 -0
  762. package/dist/substrate/runtime/supervisor/start-supervisor.mjs +250 -0
  763. package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -0
  764. package/dist/substrate/runtime/supervisor/state.d.mts +1 -0
  765. package/dist/substrate/runtime/supervisor/state.mjs +22 -0
  766. package/dist/substrate/runtime/supervisor/state.mjs.map +1 -0
  767. package/dist/substrate/runtime/supervisor/teardown.mjs +57 -0
  768. package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -0
  769. package/dist/substrate/runtime/supervisor/types.d.mts +1 -0
  770. package/dist/substrate/runtime/supervisor/wiring.mjs +133 -0
  771. package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -0
  772. package/dist/substrate/runtime/typed-env.mjs +8 -0
  773. package/dist/substrate/runtime/typed-env.mjs.map +1 -0
  774. package/dist/substrate/state-store.d.mts +1 -0
  775. package/dist/substrate/versioned-doc-schema.mjs +17 -0
  776. package/dist/substrate/versioned-doc-schema.mjs.map +1 -0
  777. package/dist/substrate/versioned-doc-sync.mjs +94 -0
  778. package/dist/substrate/versioned-doc-sync.mjs.map +1 -0
  779. package/dist/surfaces/cli/command-tree.mjs.map +1 -1
  780. package/dist/surfaces/cli/commands/index.mjs +9 -0
  781. package/dist/surfaces/cli/commands/prune-picker-entry.mjs +13 -0
  782. package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -0
  783. package/dist/surfaces/cli/commands/prune-picker.mjs +3 -2
  784. package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
  785. package/dist/surfaces/cli/commands/prune.mjs +34 -17
  786. package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
  787. package/dist/surfaces/cli/commands/status.mjs +2 -2
  788. package/dist/surfaces/cli/commands/status.mjs.map +1 -1
  789. package/dist/surfaces/cli/commands/supervisor-presence.mjs +5 -3
  790. package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
  791. package/dist/surfaces/cli/commands/wipe.mjs +38 -4
  792. package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
  793. package/dist/surfaces/cli/errors.mjs +1 -1
  794. package/dist/surfaces/cli/errors.mjs.map +1 -1
  795. package/dist/surfaces/cli/flags.mjs +0 -1
  796. package/dist/surfaces/cli/flags.mjs.map +1 -1
  797. package/dist/surfaces/cli/index.mjs +98 -18
  798. package/dist/surfaces/cli/index.mjs.map +1 -1
  799. package/dist/surfaces/cli/output.mjs +1 -0
  800. package/dist/surfaces/cli/output.mjs.map +1 -1
  801. package/dist/surfaces/cli/sysexits.mjs +2 -1
  802. package/dist/surfaces/cli/sysexits.mjs.map +1 -1
  803. package/dist/surfaces/tui/app.mjs +25 -3
  804. package/dist/surfaces/tui/app.mjs.map +1 -1
  805. package/dist/surfaces/tui/display-derivation.mjs +23 -74
  806. package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
  807. package/dist/surfaces/tui/event-log.mjs +35 -23
  808. package/dist/surfaces/tui/event-log.mjs.map +1 -1
  809. package/dist/surfaces/tui/input.mjs +5 -1
  810. package/dist/surfaces/tui/input.mjs.map +1 -1
  811. package/dist/surfaces/tui/plain-renderer.mjs +70 -34
  812. package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
  813. package/package.json +24 -4
  814. package/dist/contracts/liveness-classifier.d.mts +0 -19
  815. package/dist/contracts/network-resolver.d.mts +0 -15
  816. package/dist/contracts/network-resolver.mjs +0 -7
  817. package/dist/contracts/network-resolver.mjs.map +0 -1
  818. package/dist/orchestrators/codegen/extras.mjs +0 -16
  819. package/dist/orchestrators/codegen/extras.mjs.map +0 -1
  820. package/dist/plugins/deepbook/routable.mjs +0 -21
  821. package/dist/plugins/deepbook/routable.mjs.map +0 -1
  822. package/dist/plugins/faucet/service.d.mts +0 -20
  823. package/dist/plugins/faucet/strategies/sui-local.d.mts +0 -23
  824. package/dist/plugins/faucet/strategies/sui-local.mjs.map +0 -1
  825. package/dist/plugins/sui/seed-objects.d.mts +0 -18
  826. package/dist/plugins/sui/seed-objects.mjs +0 -25
  827. package/dist/plugins/sui/seed-objects.mjs.map +0 -1
  828. package/dist/runtime/built-in-plugin-layers.mjs.map +0 -1
  829. package/dist/substrate/runtime/context-helpers.mjs +0 -32
  830. package/dist/substrate/runtime/context-helpers.mjs.map +0 -1
  831. package/dist/substrate/runtime/run.mjs.map +0 -1
  832. package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs +0 -30
  833. package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs.map +0 -1
  834. package/dist/substrate/runtime/supervisor.d.mts +0 -1
  835. package/dist/substrate/runtime/supervisor.mjs +0 -831
  836. package/dist/substrate/runtime/supervisor.mjs.map +0 -1
  837. package/dist/surfaces/cli/commands/doctor-probes.mjs.map +0 -1
  838. /package/dist/{substrate/runtime/strategy-registry/faucet-capability-for.d.mts → plugins/faucet/http.d.mts} +0 -0
@@ -0,0 +1,64 @@
1
+ import { DEFAULT_SWEEP_POLICY } from "../../cross-process.mjs";
2
+ import { readFileSync, statSync, unlinkSync } from "node:fs";
3
+ //#region src/substrate/runtime/cross-process/reclaim-stale-file.ts
4
+ const identityOf = (path) => {
5
+ try {
6
+ const s = statSync(path);
7
+ return {
8
+ mtimeMs: s.mtimeMs,
9
+ ino: s.ino,
10
+ dev: s.dev
11
+ };
12
+ } catch {
13
+ return null;
14
+ }
15
+ };
16
+ const sameIdentity = (a, b) => a.mtimeMs === b.mtimeMs && a.ino === b.ino && a.dev === b.dev;
17
+ const readOrNull = (path) => {
18
+ try {
19
+ return readFileSync(path, "utf8");
20
+ } catch {
21
+ return null;
22
+ }
23
+ };
24
+ /** Reclaim `path` IFF it is an aged, unparseable orphan — with a re-stat
25
+ * guard that NARROWS (not closes) the TOCTOU window between the
26
+ * staleness check and the unlink. See the module header for the
27
+ * deliberately-accepted sub-microsecond residual between the final
28
+ * re-read and the path-based unlink.
29
+ *
30
+ * `parse` is the caller's body decoder; it returns a non-null value for
31
+ * a well-formed body and `null` for an unparseable one. We never look
32
+ * at the decoded value — only at whether it parsed — so the lock and
33
+ * the reservation can share one guard despite carrying different
34
+ * schemas.
35
+ *
36
+ * The staleness threshold is `DEFAULT_SWEEP_POLICY.staleAfterMillis`
37
+ * (30s), matching the roster's eviction window: anything that crashed
38
+ * mid-write at least 30s ago is presumed abandoned.
39
+ *
40
+ * Never throws. Any I/O failure resolves to a conservative non-
41
+ * reclaiming outcome so a transient stat/read hiccup never triggers a
42
+ * spurious unlink — the caller's backoff loop retries on the next pass. */
43
+ const reclaimUnparseableStaleFile = (path, parse, staleAfterMillis = DEFAULT_SWEEP_POLICY.staleAfterMillis) => {
44
+ const before = identityOf(path);
45
+ if (before === null) return "absent";
46
+ const rawBefore = readOrNull(path);
47
+ if (rawBefore === null) return "absent";
48
+ if (parse(rawBefore) !== null) return "parseable";
49
+ if (!(Date.now() - before.mtimeMs > staleAfterMillis)) return "fresh";
50
+ const after = identityOf(path);
51
+ if (after === null) return "reclaimed";
52
+ if (!sameIdentity(before, after)) return "changed";
53
+ const rawAfter = readOrNull(path);
54
+ if (rawAfter === null) return "reclaimed";
55
+ if (parse(rawAfter) !== null) return "changed";
56
+ try {
57
+ unlinkSync(path);
58
+ } catch {}
59
+ return "reclaimed";
60
+ };
61
+ //#endregion
62
+ export { reclaimUnparseableStaleFile };
63
+
64
+ //# sourceMappingURL=reclaim-stale-file.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reclaim-stale-file.mjs","names":[],"sources":["../../../../src/substrate/runtime/cross-process/reclaim-stale-file.ts"],"sourcesContent":["// Shared reclaim guard for unparseable/orphaned coordination files.\n//\n// Both `stack.lock` (stack-lock.ts) and `snapshot.reservation`\n// (snapshot-reservation.ts) reclaim a file whose on-disk body fails to\n// parse (a peer that crashed mid-write) once it ages past the roster\n// sweep's staleness window. The PID liveness check has nothing to\n// consult when the body won't parse, so mtime + the shared staleness\n// budget is the only abandonment signal.\n//\n// The naive shape — `statSync` for the mtime, then `unlinkSync` — has a\n// TOCTOU window: between reading the mtime and unlinking, a competing\n// process can legitimately reclaim the garbage lock and write a FRESH,\n// VALID O_EXCL body. The unconditional `unlinkSync` would then clobber\n// that live lock, producing two simultaneous holders (a mutual-\n// exclusion break). This module NARROWS the window — it does not fully\n// eliminate it — by RE-STATTING the file immediately before the unlink\n// and confirming it is STILL the same stale inode (same mtime + ino +\n// dev) AND still unparseable. If anything changed, the reclaim bails\n// and the caller falls back to its normal backoff/retry path so the\n// competitor's fresh body survives.\n//\n// Residual race (deliberately accepted): the final `unlinkSync` is\n// path-based and UNCONDITIONAL, so between the re-stat/re-read and the\n// syscall a competing reclaimer can still unlink+O_EXCL-create a fresh\n// body that this unlink then deletes. Closing it fully would need an\n// atomic, inode-pinned delete (`renameat2(RENAME_EXCHANGE)` or an fd\n// held across the check) that `node:fs` does not portably expose — a\n// plain `rename(path, …)` is itself path-based and would relocate the\n// competitor's fresh body just as readily, so it buys nothing. The\n// re-stat shrinks the window from \"mtime read → unlink\" (a parse +\n// staleness comparison wide) to \"re-read → unlink\" (a couple of\n// syscalls), making a real collision require both racers mid-reclaim of\n// the SAME file with sub-microsecond timing. Given that, the residual\n// is left open rather than papered over with an equally-racy rename.\n//\n// The dead-PID liveness reclaim path is NOT routed through here: that\n// branch is gated on a holder-liveness probe (see liveness.ts) and is\n// already safe — a dead holder cannot have written a fresh body. Only\n// the unparseable/mtime-stale branch carries the TOCTOU, so only it\n// needs this guard.\n//\n// Sync `node:fs` by design — the call sites are inside `Effect.try`\n// critical sections that need a non-blocking attempt; the rest of the\n// substrate stays Effect-native.\n\nimport { readFileSync, statSync, unlinkSync } from 'node:fs';\n\nimport { DEFAULT_SWEEP_POLICY } from '../../cross-process.ts';\n\n/** Outcome of a reclaim attempt.\n *\n * - `reclaimed` — the file was an aged, unparseable orphan and we\n * unlinked it (or it vanished from under us mid-reclaim, which is the\n * same observable end state: the slot is free for the next O_EXCL).\n * - `parseable` — the body parsed, so it is NOT an orphan; a live peer\n * holds it (possibly one that just reclaimed the garbage ahead of\n * us). Do not touch it.\n * - `fresh` — the body is unparseable but its mtime is inside the\n * staleness window; a writer is presumed mid-flush. Respect the\n * budget and leave it.\n * - `changed` — the re-stat guard fired: the file's identity (mtime /\n * ino / dev) or parse-state changed between the staleness check and\n * the unlink, meaning a competitor reclaimed the slot and wrote a\n * fresh body in the window. We bailed to avoid clobbering it.\n * - `absent` — the file was already gone when we looked. */\nexport type ReclaimOutcome = 'reclaimed' | 'parseable' | 'fresh' | 'changed' | 'absent';\n\n/** A minimal, comparable identity for a file on disk. Two reads of the\n * same untouched inode produce equal triples; any rewrite (O_EXCL\n * unlink+create, or an in-place touch) perturbs at least one field. */\ninterface FileIdentity {\n\treadonly mtimeMs: number;\n\treadonly ino: number;\n\treadonly dev: number;\n}\n\nconst identityOf = (path: string): FileIdentity | null => {\n\ttry {\n\t\tconst s = statSync(path);\n\t\treturn { mtimeMs: s.mtimeMs, ino: s.ino, dev: s.dev };\n\t} catch {\n\t\t// ENOENT (gone) or any transient stat failure — treat as absent.\n\t\treturn null;\n\t}\n};\n\nconst sameIdentity = (a: FileIdentity, b: FileIdentity): boolean =>\n\ta.mtimeMs === b.mtimeMs && a.ino === b.ino && a.dev === b.dev;\n\nconst readOrNull = (path: string): string | null => {\n\ttry {\n\t\treturn readFileSync(path, 'utf8');\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** Reclaim `path` IFF it is an aged, unparseable orphan — with a re-stat\n * guard that NARROWS (not closes) the TOCTOU window between the\n * staleness check and the unlink. See the module header for the\n * deliberately-accepted sub-microsecond residual between the final\n * re-read and the path-based unlink.\n *\n * `parse` is the caller's body decoder; it returns a non-null value for\n * a well-formed body and `null` for an unparseable one. We never look\n * at the decoded value — only at whether it parsed — so the lock and\n * the reservation can share one guard despite carrying different\n * schemas.\n *\n * The staleness threshold is `DEFAULT_SWEEP_POLICY.staleAfterMillis`\n * (30s), matching the roster's eviction window: anything that crashed\n * mid-write at least 30s ago is presumed abandoned.\n *\n * Never throws. Any I/O failure resolves to a conservative non-\n * reclaiming outcome so a transient stat/read hiccup never triggers a\n * spurious unlink — the caller's backoff loop retries on the next pass. */\nexport const reclaimUnparseableStaleFile = (\n\tpath: string,\n\tparse: (raw: string) => unknown,\n\tstaleAfterMillis: number = DEFAULT_SWEEP_POLICY.staleAfterMillis,\n): ReclaimOutcome => {\n\t// 1. Initial identity + staleness.\n\tconst before = identityOf(path);\n\tif (before === null) return 'absent';\n\n\tconst rawBefore = readOrNull(path);\n\tif (rawBefore === null) return 'absent';\n\tif (parse(rawBefore) !== null) return 'parseable';\n\n\tconst isStale = Date.now() - before.mtimeMs > staleAfterMillis;\n\tif (!isStale) return 'fresh';\n\n\t// 2. Re-stat guard — NARROWS (does not close) the TOCTOU. Between the\n\t// read above and the unlink below, a competitor may have reclaimed\n\t// the garbage and written a FRESH valid O_EXCL body. Re-confirm the\n\t// file is STILL the same stale inode AND still unparseable before\n\t// unlinking. A sub-microsecond residual remains between this\n\t// re-read and the path-based unlink (see header) — accepted.\n\tconst after = identityOf(path);\n\tif (after === null) {\n\t\t// Vanished from under us — a sibling reclaimer won the unlink. The\n\t\t// slot is free; report it as reclaimed (same observable result).\n\t\treturn 'reclaimed';\n\t}\n\tif (!sameIdentity(before, after)) return 'changed';\n\n\tconst rawAfter = readOrNull(path);\n\tif (rawAfter === null) return 'reclaimed';\n\tif (parse(rawAfter) !== null) return 'changed';\n\n\t// 3. Still the same stale, unparseable orphan — safe to unlink.\n\ttry {\n\t\tunlinkSync(path);\n\t} catch {\n\t\t// Race with another reclaimer that unlinked first — harmless; the\n\t\t// slot is free either way.\n\t}\n\treturn 'reclaimed';\n};\n"],"mappings":";;;AA4EA,MAAM,cAAc,SAAsC;AACzD,KAAI;EACH,MAAM,IAAI,SAAS,KAAK;AACxB,SAAO;GAAE,SAAS,EAAE;GAAS,KAAK,EAAE;GAAK,KAAK,EAAE;GAAK;SAC9C;AAEP,SAAO;;;AAIT,MAAM,gBAAgB,GAAiB,MACtC,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE;AAE3D,MAAM,cAAc,SAAgC;AACnD,KAAI;AACH,SAAO,aAAa,MAAM,OAAO;SAC1B;AACP,SAAO;;;;;;;;;;;;;;;;;;;;;;AAuBT,MAAa,+BACZ,MACA,OACA,mBAA2B,qBAAqB,qBAC5B;CAEpB,MAAM,SAAS,WAAW,KAAK;AAC/B,KAAI,WAAW,KAAM,QAAO;CAE5B,MAAM,YAAY,WAAW,KAAK;AAClC,KAAI,cAAc,KAAM,QAAO;AAC/B,KAAI,MAAM,UAAU,KAAK,KAAM,QAAO;AAGtC,KAAI,EADY,KAAK,KAAK,GAAG,OAAO,UAAU,kBAChC,QAAO;CAQrB,MAAM,QAAQ,WAAW,KAAK;AAC9B,KAAI,UAAU,KAGb,QAAO;AAER,KAAI,CAAC,aAAa,QAAQ,MAAM,CAAE,QAAO;CAEzC,MAAM,WAAW,WAAW,KAAK;AACjC,KAAI,aAAa,KAAM,QAAO;AAC9B,KAAI,MAAM,SAAS,KAAK,KAAM,QAAO;AAGrC,KAAI;AACH,aAAW,KAAK;SACT;AAIR,QAAO"}
@@ -1,12 +1,11 @@
1
- import { DEFAULT_SWEEP_POLICY, RosterDocumentSchema } from "../../cross-process.mjs";
2
- import { checkHolderLiveness, isPidAlive, ownHolder, processStartTime } from "./liveness.mjs";
3
- import { decodeJsonText } from "../runtime-decode.mjs";
4
- import { atomicWriteJsonSync } from "../atomic-write.mjs";
5
1
  import { SpanAttr } from "../observability/spans.mjs";
2
+ import { versionedDocSchema } from "../../versioned-doc-schema.mjs";
3
+ import { selfPid } from "./self-pid.mjs";
4
+ import { DEFAULT_SWEEP_POLICY, RosterDocumentSchema } from "../../cross-process.mjs";
5
+ import { readVersionedDocumentSync, writeVersionedDocumentSync } from "../../versioned-doc-sync.mjs";
6
+ import { LivenessProbeScope, isPidAlive, layerLivenessProbeScope, ownHolder, processStartTime } from "./liveness.mjs";
6
7
  import { acquireStackLock } from "./stack-lock.mjs";
7
8
  import { Data, Effect, Schema } from "effect";
8
- import { existsSync, readFileSync } from "node:fs";
9
- import { dirname } from "node:path";
10
9
  import { hostname } from "node:os";
11
10
  //#region src/substrate/runtime/cross-process/roster.ts
12
11
  var RosterCorruptError = class extends Data.TaggedError("RosterCorruptError") {};
@@ -15,39 +14,27 @@ const EMPTY_ROSTER = {
15
14
  version: 1,
16
15
  holders: []
17
16
  };
17
+ const ROSTER_DOC_ERRORS = {
18
+ mkIo: ({ path, cause }) => new RosterIoError({
19
+ path,
20
+ cause
21
+ }),
22
+ mkCorrupt: ({ path, raw, cause }) => new RosterCorruptError({
23
+ path,
24
+ raw,
25
+ cause
26
+ })
27
+ };
18
28
  /** Read the roster from disk. Returns the empty document if absent.
19
29
  * Tolerates a missing file but NOT a malformed file (a malformed
20
30
  * roster surfaces a typed error so callers can decide whether to
21
31
  * abandon or rewrite). */
22
- const readRoster = (path) => Effect.gen(function* () {
23
- if (!existsSync(path)) return EMPTY_ROSTER;
24
- const raw = yield* Effect.try({
25
- try: () => readFileSync(path, "utf8"),
26
- catch: (cause) => new RosterIoError({
27
- path,
28
- cause
29
- })
30
- });
31
- return yield* decodeJsonText(RosterDocumentSchema, raw, {
32
- source: path,
33
- mkError: (issue) => new RosterCorruptError({
34
- path,
35
- raw,
36
- cause: issue.cause ?? issue
37
- })
38
- });
39
- }).pipe(Effect.withSpan("cross-process.roster.read"));
32
+ const readRoster = (path) => readVersionedDocumentSync(path, RosterDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_ROSTER).pipe(Effect.withSpan("cross-process.roster.read"));
40
33
  /** Atomic write: route through the canonical sync primitive. The
41
34
  * roster's mutations are all under `stack.lock`, so the non-yielding
42
35
  * sync surface is correct here — and it shares ONE owner of the
43
36
  * tempfile dance with state-store, cache, and manifest. */
44
- const atomicWriteRoster = (path, doc) => Effect.try({
45
- try: () => atomicWriteJsonSync(path, doc),
46
- catch: (cause) => new RosterIoError({
47
- path,
48
- cause
49
- })
50
- });
37
+ const atomicWriteRoster = (path, doc) => writeVersionedDocumentSync(path, doc, ROSTER_DOC_ERRORS);
51
38
  /** Walk the roster's holders and drop those that fail the liveness
52
39
  * test AND whose heartbeats are older than `staleAfterMillis`.
53
40
  *
@@ -58,14 +45,16 @@ const atomicWriteRoster = (path, doc) => Effect.try({
58
45
  * A live holder with a stale heartbeat is NOT evicted — heartbeat
59
46
  * staleness alone is allowed (slow peer). The conjunction matters. */
60
47
  const sweepStaleHolders = Effect.fn("cross-process.roster.sweep")(function* (doc, policy = DEFAULT_SWEEP_POLICY, now = Date.now()) {
48
+ const probe = yield* LivenessProbeScope;
61
49
  const survivors = [];
62
50
  const evicted = [];
51
+ const ownHost = hostname();
63
52
  for (const holder of doc.holders) {
64
53
  if (!(now - holder.heartbeatAt > policy.staleAfterMillis)) {
65
54
  survivors.push(holder);
66
55
  continue;
67
56
  }
68
- if ((yield* checkHolderLiveness(holder).pipe(Effect.catch(() => Effect.succeed("alive")))) === "dead") evicted.push(holder);
57
+ if ((yield* probe.probeHolderLiveness(holder, ownHost).pipe(Effect.catch(() => Effect.succeed("alive")))) === "dead") evicted.push(holder);
69
58
  else survivors.push(holder);
70
59
  }
71
60
  return {
@@ -75,7 +64,37 @@ const sweepStaleHolders = Effect.fn("cross-process.roster.sweep")(function* (doc
75
64
  },
76
65
  evicted
77
66
  };
78
- });
67
+ }, Effect.provide(layerLivenessProbeScope));
68
+ /** Materialize the ledger path from `RosterPaths`. Callers that
69
+ * invoke any of the claim-ledger APIs (`readClaims`,
70
+ * `pruneStaleClaims`, `addClaim`, `removeClaim`) MUST construct
71
+ * `RosterPaths` with `containerClaimsFile` populated from
72
+ * `StackPathsService.containerClaimsFile`. The previous behavior of
73
+ * reconstructing `dirname(rosterFile) + '/container-claims.json'`
74
+ * internally has been removed so nothing in the runtime tree builds
75
+ * cross-process paths outside the substrate path resolver. */
76
+ const requireClaimsPath = (paths) => {
77
+ if (paths.containerClaimsFile === void 0) throw new Error("cross-process.roster: container-claim API called without `containerClaimsFile` on the RosterPaths bundle. Source this from StackPathsService.containerClaimsFile.");
78
+ return paths.containerClaimsFile;
79
+ };
80
+ /** Match a roster holder against THIS process's identity.
81
+ *
82
+ * Liveness elsewhere (`checkHolderLiveness`, `isContainerClaimLive`)
83
+ * uses `(pid, hostname, startTime)` — PID alone is insufficient on
84
+ * long-uptime hosts where the kernel can recycle PIDs. The roster
85
+ * mutators (`heartbeat`, `release`, `setIntent`) must apply the same
86
+ * triple match so a recycled-PID peer's entry is never silently
87
+ * overwritten/removed by this process.
88
+ *
89
+ * `startTime` is the FNV-1a hash of `ps -o lstart` (see
90
+ * `liveness.processStartTime`). A `null` probe (process gone, or
91
+ * exotic platform) skips the start-time check — same conservative
92
+ * policy as `isContainerClaimLive`. */
93
+ const isOwnEntry = (h, ownPid, ownHost, ownStartTime) => {
94
+ if (h.pid !== ownPid || h.hostname !== ownHost) return false;
95
+ if (ownStartTime === null || h.startTime === null) return true;
96
+ return h.startTime === ownStartTime;
97
+ };
79
98
  const withStackLock = (paths, body) => Effect.scoped(Effect.gen(function* () {
80
99
  yield* acquireStackLock(paths.stackLockFile);
81
100
  return yield* body;
@@ -115,15 +134,16 @@ const claim = (paths, intent = "normal", policy = DEFAULT_SWEEP_POLICY) => withS
115
134
  * stack lock to keep mutation atomic. Architecture § Heartbeat
116
135
  * protocol.
117
136
  */
118
- const heartbeat = (paths, ownPid = process.pid) => withStackLock(paths, Effect.gen(function* () {
137
+ const heartbeat = (paths, ownPid = selfPid()) => withStackLock(paths, Effect.gen(function* () {
119
138
  const current = yield* readRoster(paths.rosterFile).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_ROSTER)));
120
139
  const now = Date.now();
121
140
  const ownHost = hostname();
141
+ const ownStartTime = processStartTime(ownPid);
122
142
  let touched = false;
123
143
  const next = {
124
144
  version: 1,
125
145
  holders: current.holders.map((h) => {
126
- if (h.pid === ownPid && h.hostname === ownHost) {
146
+ if (isOwnEntry(h, ownPid, ownHost, ownStartTime)) {
127
147
  touched = true;
128
148
  return {
129
149
  ...h,
@@ -141,10 +161,11 @@ const heartbeat = (paths, ownPid = process.pid) => withStackLock(paths, Effect.g
141
161
  * Removes this process's holder. Returns whether this was the
142
162
  * last-leaver (caller runs the stop finalizer if so).
143
163
  */
144
- const release = (paths, ownPid = process.pid) => withStackLock(paths, Effect.gen(function* () {
164
+ const release = (paths, ownPid = selfPid()) => withStackLock(paths, Effect.gen(function* () {
145
165
  const current = yield* readRoster(paths.rosterFile).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_ROSTER)));
146
166
  const ownHost = hostname();
147
- const remaining = current.holders.filter((h) => !(h.pid === ownPid && h.hostname === ownHost));
167
+ const ownStartTime = processStartTime(ownPid);
168
+ const remaining = current.holders.filter((h) => !isOwnEntry(h, ownPid, ownHost, ownStartTime));
148
169
  const next = {
149
170
  version: 1,
150
171
  holders: remaining
@@ -177,64 +198,56 @@ const ContainerClaimSchema = Schema.Struct({
177
198
  hostname: Schema.String,
178
199
  claimedAt: Schema.Number
179
200
  });
180
- const ContainerClaimDocumentSchema = Schema.Struct({
181
- version: Schema.Literal(1),
182
- claims: Schema.Array(ContainerClaimSchema)
183
- });
201
+ const ContainerClaimDocumentSchema = versionedDocSchema(1, { claims: Schema.Array(ContainerClaimSchema) });
184
202
  const EMPTY_CLAIMS = {
185
203
  version: 1,
186
204
  claims: []
187
205
  };
188
- const claimsPath = (rosterFile) => `${dirname(rosterFile)}/container-claims.json`;
189
- const isContainerClaimLive = (claim, ownHost = hostname()) => {
206
+ /** Match a container claim against THIS process's identity using the
207
+ * same `(pid, hostname, startTime)` triple that `isOwnEntry` /
208
+ * `isContainerClaimLive` apply to roster holders — PID alone is
209
+ * insufficient on long-uptime hosts where the kernel recycles PIDs.
210
+ * A null/undefined start-time on either side falls back to
211
+ * `(pid, hostname)`, matching the conservative policy elsewhere
212
+ * (avoids orphaning our own entry when the probe failed). */
213
+ const isOwnContainerClaim = (c, containerKey, ownPid, ownHost, ownStartTime) => {
214
+ if (c.containerKey !== containerKey || c.pid !== ownPid || c.hostname !== ownHost) return false;
215
+ if (ownStartTime === void 0 || c.startTime === void 0) return true;
216
+ return c.startTime === ownStartTime;
217
+ };
218
+ const isContainerClaimLive = (claim, probeStartTime, ownHost = hostname()) => {
190
219
  if (claim.hostname !== ownHost) return true;
191
220
  if (!isPidAlive(claim.pid)) return false;
192
221
  if (claim.startTime === void 0) return true;
193
- const probedStart = processStartTime(claim.pid);
222
+ const probedStart = probeStartTime(claim.pid);
194
223
  if (probedStart === null) return true;
195
224
  return probedStart === claim.startTime;
196
225
  };
197
- const liveContainerClaims = (doc) => ({
198
- version: 1,
199
- claims: doc.claims.filter((claim) => isContainerClaimLive(claim))
200
- });
201
- const writeClaims = (path, doc) => Effect.try({
202
- try: () => atomicWriteJsonSync(path, doc),
203
- catch: (cause) => new RosterIoError({
204
- path,
205
- cause
206
- })
207
- });
226
+ /** Effect-flavored filter that yields a fresh `LivenessProbeScope` so
227
+ * the same pid is probed at most once across this pass — even when
228
+ * multiple claims by the same pid sit in the ledger. The scope's
229
+ * cache is private to this call (provided via `Effect.provide`). */
230
+ const liveContainerClaims = (doc) => Effect.gen(function* () {
231
+ const probe = yield* LivenessProbeScope;
232
+ const ownHost = hostname();
233
+ return {
234
+ version: 1,
235
+ claims: doc.claims.filter((claim) => isContainerClaimLive(claim, probe.probeStartTime, ownHost))
236
+ };
237
+ }).pipe(Effect.provide(layerLivenessProbeScope));
238
+ const writeClaims = (path, doc) => writeVersionedDocumentSync(path, doc, ROSTER_DOC_ERRORS);
208
239
  /** Read the container-claim ledger. */
209
- const readClaims = (paths) => Effect.gen(function* () {
210
- const path = claimsPath(paths.rosterFile);
211
- if (!existsSync(path)) return EMPTY_CLAIMS;
212
- const raw = yield* Effect.try({
213
- try: () => readFileSync(path, "utf8"),
214
- catch: (cause) => new RosterIoError({
215
- path,
216
- cause
217
- })
218
- });
219
- return yield* decodeJsonText(ContainerClaimDocumentSchema, raw, {
220
- source: path,
221
- mkError: (issue) => new RosterCorruptError({
222
- path,
223
- raw,
224
- cause: issue.cause ?? issue
225
- })
226
- });
227
- }).pipe(Effect.withSpan("cross-process.roster.readClaims"));
240
+ const readClaims = (paths) => Effect.suspend(() => readVersionedDocumentSync(requireClaimsPath(paths), ContainerClaimDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_CLAIMS)).pipe(Effect.withSpan("cross-process.roster.readClaims"));
228
241
  /** Record that this process now claims `containerKey`. No-op if
229
242
  * already claimed by this process. */
230
243
  const addClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(function* () {
231
- const path = claimsPath(paths.rosterFile);
244
+ const path = requireClaimsPath(paths);
232
245
  const current = yield* readClaims(paths).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_CLAIMS)));
233
- const live = liveContainerClaims(current);
234
- const ownPid = process.pid;
246
+ const live = yield* liveContainerClaims(current);
247
+ const ownPid = selfPid();
235
248
  const ownStartTime = processStartTime(ownPid) ?? void 0;
236
249
  const ownHost = hostname();
237
- if (live.claims.some((c) => c.containerKey === containerKey && c.pid === ownPid && c.hostname === ownHost)) {
250
+ if (live.claims.some((c) => isOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime))) {
238
251
  if (live.claims.length !== current.claims.length) yield* writeClaims(path, live);
239
252
  return;
240
253
  }
@@ -257,11 +270,12 @@ const addClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(functi
257
270
  * releases its last claim and confirms no other process holds the
258
271
  * container, it may tear down." */
259
272
  const removeClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(function* () {
260
- const path = claimsPath(paths.rosterFile);
261
- const live = liveContainerClaims(yield* readClaims(paths).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_CLAIMS))));
262
- const ownPid = process.pid;
273
+ const path = requireClaimsPath(paths);
274
+ const live = yield* liveContainerClaims(yield* readClaims(paths).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_CLAIMS))));
275
+ const ownPid = selfPid();
276
+ const ownStartTime = processStartTime(ownPid) ?? void 0;
263
277
  const ownHost = hostname();
264
- const remaining = live.claims.filter((c) => !(c.containerKey === containerKey && c.pid === ownPid && c.hostname === ownHost));
278
+ const remaining = live.claims.filter((c) => !isOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime));
265
279
  const stillClaimedByPeer = remaining.some((c) => c.containerKey === containerKey);
266
280
  yield* writeClaims(path, {
267
281
  version: 1,
@@ -270,6 +284,6 @@ const removeClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(fun
270
284
  return { lastClaimReleased: !stillClaimedByPeer };
271
285
  })).pipe(Effect.withSpan("cross-process.roster.removeClaim"));
272
286
  //#endregion
273
- export { addClaim, claim, heartbeatFiber, readClaims, readRoster, release, removeClaim };
287
+ export { RosterCorruptError, RosterIoError, addClaim, claim, heartbeat, heartbeatFiber, readClaims, readRoster, release, removeClaim, sweepStaleHolders };
274
288
 
275
289
  //# sourceMappingURL=roster.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"roster.mjs","names":["nodeHostname"],"sources":["../../../../src/substrate/runtime/cross-process/roster.ts"],"sourcesContent":["// `roster.json` — authoritative cross-process holder record.\n//\n// Architecture § Cross-process safety protocol § Roster:\n// \"the authoritative cross-process record of which OS processes are\n// currently 'in' this stack.\"\n//\n// Mutated only under `stack.lock`. Schema-validated on every read so a\n// truncated/forward-version write never corrupts a peer's view. Stale\n// entries reaped during step-3 sweep on the next claim.\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { hostname as nodeHostname } from 'node:os';\nimport { dirname } from 'node:path';\n\nimport { Data, Effect, Schema } from 'effect';\n\nimport {\n\tDEFAULT_SWEEP_POLICY,\n\ttype RosterDocument,\n\tRosterDocumentSchema,\n\ttype RosterHolder,\n\ttype RosterSweepPolicy,\n} from '../../cross-process.ts';\nimport { atomicWriteJsonSync } from '../atomic-write.ts';\nimport { SpanAttr } from '../observability/spans.ts';\nimport { decodeJsonText } from '../runtime-decode.ts';\nimport { acquireStackLock } from './stack-lock.ts';\nimport { checkHolderLiveness, isPidAlive, ownHolder, processStartTime } from './liveness.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\nexport class RosterCorruptError extends Data.TaggedError('RosterCorruptError')<{\n\treadonly path: string;\n\treadonly raw: string;\n\treadonly cause: unknown;\n}> {}\n\nexport class RosterIoError extends Data.TaggedError('RosterIoError')<{\n\treadonly path: string;\n\treadonly cause: unknown;\n}> {}\n\nexport type RosterError = RosterCorruptError | RosterIoError;\n\n// -----------------------------------------------------------------------------\n// Read / write\n// -----------------------------------------------------------------------------\n\nconst EMPTY_ROSTER: RosterDocument = { version: 1, holders: [] };\n\n/** Read the roster from disk. Returns the empty document if absent.\n * Tolerates a missing file but NOT a malformed file (a malformed\n * roster surfaces a typed error so callers can decide whether to\n * abandon or rewrite). */\nexport const readRoster = (path: string): Effect.Effect<RosterDocument, RosterError> =>\n\tEffect.gen(function* () {\n\t\tif (!existsSync(path)) return EMPTY_ROSTER;\n\t\tconst raw = yield* Effect.try({\n\t\t\ttry: () => readFileSync(path, 'utf8'),\n\t\t\tcatch: (cause) => new RosterIoError({ path, cause }),\n\t\t});\n\t\tconst decoded = yield* decodeJsonText(RosterDocumentSchema, raw, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) => new RosterCorruptError({ path, raw, cause: issue.cause ?? issue }),\n\t\t});\n\t\treturn decoded;\n\t}).pipe(Effect.withSpan('cross-process.roster.read'));\n\n/** Atomic write: route through the canonical sync primitive. The\n * roster's mutations are all under `stack.lock`, so the non-yielding\n * sync surface is correct here — and it shares ONE owner of the\n * tempfile dance with state-store, cache, and manifest. */\nconst atomicWriteRoster = (path: string, doc: RosterDocument): Effect.Effect<void, RosterIoError> =>\n\tEffect.try({\n\t\ttry: () => atomicWriteJsonSync(path, doc),\n\t\tcatch: (cause) => new RosterIoError({ path, cause }),\n\t});\n\n// -----------------------------------------------------------------------------\n// Sweep\n// -----------------------------------------------------------------------------\n\n/** Walk the roster's holders and drop those that fail the liveness\n * test AND whose heartbeats are older than `staleAfterMillis`.\n *\n * Architecture § Claim protocol step 3 — \"Holders whose\n * `heartbeatAt` is older than 3× the heartbeat interval AND who fail\n * the PID liveness check are evicted.\"\n *\n * A live holder with a stale heartbeat is NOT evicted — heartbeat\n * staleness alone is allowed (slow peer). The conjunction matters. */\nexport const sweepStaleHolders = Effect.fn('cross-process.roster.sweep')(function* (\n\tdoc: RosterDocument,\n\tpolicy: RosterSweepPolicy = DEFAULT_SWEEP_POLICY,\n\tnow: number = Date.now(),\n) {\n\tconst survivors: RosterHolder[] = [];\n\tconst evicted: RosterHolder[] = [];\n\tfor (const holder of doc.holders) {\n\t\tconst heartbeatStale = now - holder.heartbeatAt > policy.staleAfterMillis;\n\t\tif (!heartbeatStale) {\n\t\t\tsurvivors.push(holder);\n\t\t\tcontinue;\n\t\t}\n\t\tconst liveness = yield* checkHolderLiveness(holder).pipe(\n\t\t\tEffect.catch(() => Effect.succeed('alive' as const)),\n\t\t);\n\t\tif (liveness === 'dead') {\n\t\t\tevicted.push(holder);\n\t\t} else {\n\t\t\tsurvivors.push(holder);\n\t\t}\n\t}\n\treturn {\n\t\tswept: { version: doc.version, holders: survivors } satisfies RosterDocument,\n\t\tevicted: evicted as ReadonlyArray<RosterHolder>,\n\t};\n});\n\n// -----------------------------------------------------------------------------\n// Claim / release / heartbeat\n// -----------------------------------------------------------------------------\n\n/** Outcome of a claim: the holder this process registered, the swept\n * document, and whether this process is now the sole holder. */\nexport interface ClaimResult {\n\treadonly self: RosterHolder;\n\treadonly roster: RosterDocument;\n\treadonly evicted: ReadonlyArray<RosterHolder>;\n\treadonly soleHolder: boolean;\n}\n\n/** Outcome of a release: the swept document and whether this process\n * was the last leaver (no peers remain after removal). Architecture §\n * Release protocol step 4. */\nexport interface ReleaseResult {\n\treadonly roster: RosterDocument;\n\treadonly lastLeaver: boolean;\n}\n\ninterface RosterPaths {\n\treadonly stackLockFile: string;\n\treadonly rosterFile: string;\n}\n\nconst withStackLock = <A, E, R>(\n\tpaths: RosterPaths,\n\tbody: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E | import('./stack-lock.ts').StackLockError, R> =>\n\tEffect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tyield* acquireStackLock(paths.stackLockFile);\n\t\t\treturn yield* body;\n\t\t}),\n\t);\n\n/**\n * Claim protocol — architecture § Claim protocol.\n *\n * Under the exclusive lock:\n * 1. Read the roster (or initialize empty if missing).\n * 2. Sweep stale holders (PID liveness + heartbeat age).\n * 3. Append this process's entry.\n * 4. Atomic write the result.\n *\n * Returns the swept document, our holder entry, and any evicted peers.\n */\nexport const claim = (\n\tpaths: RosterPaths,\n\tintent: 'normal' | 'snapshot' = 'normal',\n\tpolicy: RosterSweepPolicy = DEFAULT_SWEEP_POLICY,\n): Effect.Effect<ClaimResult, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t\t'devstack.roster.path': paths.rosterFile,\n\t\t\t\t'devstack.roster.intent': intent,\n\t\t\t});\n\t\t\tconst initial = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst { swept, evicted } = yield* sweepStaleHolders(initial, policy);\n\t\t\tconst self: RosterHolder = { ...ownHolder(intent) };\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: [...swept.holders, self],\n\t\t\t};\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t\treturn {\n\t\t\t\tself,\n\t\t\t\troster: next,\n\t\t\t\tevicted,\n\t\t\t\tsoleHolder: swept.holders.length === 0,\n\t\t\t};\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.claim'));\n\n/**\n * Heartbeat protocol — refresh this process's `heartbeatAt`. Under the\n * stack lock to keep mutation atomic. Architecture § Heartbeat\n * protocol.\n */\nexport const heartbeat = (\n\tpaths: RosterPaths,\n\townPid: number = process.pid,\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst now = Date.now();\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tlet touched = false;\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: current.holders.map((h) => {\n\t\t\t\t\tif (h.pid === ownPid && h.hostname === ownHost) {\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t\treturn { ...h, heartbeatAt: now };\n\t\t\t\t\t}\n\t\t\t\t\treturn h;\n\t\t\t\t}),\n\t\t\t};\n\t\t\tif (touched) yield* atomicWriteRoster(paths.rosterFile, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.heartbeat'));\n\n/**\n * Release protocol — architecture § Release protocol.\n *\n * Removes this process's holder. Returns whether this was the\n * last-leaver (caller runs the stop finalizer if so).\n */\nexport const release = (\n\tpaths: RosterPaths,\n\townPid: number = process.pid,\n): Effect.Effect<ReleaseResult, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst remaining = current.holders.filter(\n\t\t\t\t(h) => !(h.pid === ownPid && h.hostname === ownHost),\n\t\t\t);\n\t\t\tconst next: RosterDocument = { version: 1, holders: remaining };\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t\treturn {\n\t\t\t\troster: next,\n\t\t\t\tlastLeaver: remaining.length === 0,\n\t\t\t};\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.release'));\n\n/**\n * Set this process's `intent` (`normal` ↔ `snapshot`) under the\n * exclusive lock. Architecture § Concurrent snapshot step 2 / 5.\n */\nexport const setIntent = (\n\tpaths: RosterPaths,\n\tintent: 'normal' | 'snapshot',\n\townPid: number = process.pid,\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: current.holders.map((h) =>\n\t\t\t\t\th.pid === ownPid && h.hostname === ownHost ? { ...h, intent } : h,\n\t\t\t\t),\n\t\t\t};\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.setIntent'));\n\n/** Background heartbeat fiber. Wakes every `intervalMillis` (default\n * matches `DEFAULT_SWEEP_POLICY.heartbeatIntervalMillis`) and refreshes\n * this process's `heartbeatAt`.\n *\n * Returns an Effect that runs forever in its Scope — the supervisor\n * forks it via `Effect.forkScoped` so it tears down with the stack. */\nexport const heartbeatFiber = (\n\tpaths: RosterPaths,\n\tintervalMillis: number = DEFAULT_SWEEP_POLICY.heartbeatIntervalMillis,\n): Effect.Effect<never> =>\n\tEffect.gen(function* () {\n\t\twhile (true) {\n\t\t\tyield* Effect.sleep(`${intervalMillis} millis`);\n\t\t\tyield* heartbeat(paths).pipe(\n\t\t\t\t// A heartbeat failure is non-fatal — the next peer's sweep\n\t\t\t\t// would otherwise evict us, but the architecture treats this\n\t\t\t\t// as \"the dev didn't get to flush a heartbeat in time;\n\t\t\t\t// recover by retrying next interval.\" Log via Effect's\n\t\t\t\t// logger; do not propagate.\n\t\t\t\tEffect.catch((err) =>\n\t\t\t\t\tEffect.logWarning('roster heartbeat failed').pipe(\n\t\t\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t\t\t[SpanAttr.rosterHeartbeatIntervalMs]: intervalMillis,\n\t\t\t\t\t\t\t[SpanAttr.errorCause]: String(err),\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}).pipe(Effect.withSpan('cross-process.roster.heartbeatFiber'));\n\n// -----------------------------------------------------------------------------\n// Container-claim ledger\n// -----------------------------------------------------------------------------\n//\n// Architecture § Cross-process safety protocol: roster \"Records claimed\n// containers per process. Last-leaver semantics: when a process\n// releases its last claim and confirms no other process holds the\n// container, it may tear down.\"\n//\n// The roster document carries holders only — the per-container claim\n// ledger lives in an EXTENSION file at `<stackRoot>/container-claims.json`\n// so the architecture-mandated `RosterDocument` shape doesn't widen.\n// Same atomicity discipline: mutate under `stack.lock`.\n\nexport interface ContainerClaim {\n\treadonly containerKey: string;\n\treadonly pid: number;\n\treadonly startTime?: number;\n\treadonly hostname: string;\n\treadonly claimedAt: number;\n}\n\nexport interface ContainerClaimDocument {\n\treadonly version: 1;\n\treadonly claims: ReadonlyArray<ContainerClaim>;\n}\n\nconst ContainerClaimSchema = Schema.Struct({\n\tcontainerKey: Schema.String,\n\tpid: Schema.Number,\n\tstartTime: Schema.optional(Schema.Number),\n\thostname: Schema.String,\n\tclaimedAt: Schema.Number,\n});\n\nconst ContainerClaimDocumentSchema = Schema.Struct({\n\tversion: Schema.Literal(1),\n\tclaims: Schema.Array(ContainerClaimSchema),\n});\n\nconst EMPTY_CLAIMS: ContainerClaimDocument = { version: 1, claims: [] };\n\nconst claimsPath = (rosterFile: string): string => `${dirname(rosterFile)}/container-claims.json`;\n\nconst isContainerClaimLive = (claim: ContainerClaim, ownHost: string = nodeHostname()): boolean => {\n\tif (claim.hostname !== ownHost) return true;\n\tif (!isPidAlive(claim.pid)) return false;\n\tif (claim.startTime === undefined) return true;\n\tconst probedStart = processStartTime(claim.pid);\n\tif (probedStart === null) return true;\n\treturn probedStart === claim.startTime;\n};\n\nconst liveContainerClaims = (doc: ContainerClaimDocument): ContainerClaimDocument => ({\n\tversion: 1,\n\tclaims: doc.claims.filter((claim) => isContainerClaimLive(claim)),\n});\n\nconst writeClaims = (\n\tpath: string,\n\tdoc: ContainerClaimDocument,\n): Effect.Effect<void, RosterIoError> =>\n\tEffect.try({\n\t\ttry: () => atomicWriteJsonSync(path, doc),\n\t\tcatch: (cause) => new RosterIoError({ path, cause }),\n\t});\n\n/** Read the container-claim ledger. */\nexport const readClaims = (\n\tpaths: RosterPaths,\n): Effect.Effect<ContainerClaimDocument, RosterError> =>\n\tEffect.gen(function* () {\n\t\tconst path = claimsPath(paths.rosterFile);\n\t\tif (!existsSync(path)) return EMPTY_CLAIMS;\n\t\tconst raw = yield* Effect.try({\n\t\t\ttry: () => readFileSync(path, 'utf8'),\n\t\t\tcatch: (cause) => new RosterIoError({ path, cause }),\n\t\t});\n\t\treturn yield* decodeJsonText(ContainerClaimDocumentSchema, raw, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) => new RosterCorruptError({ path, raw, cause: issue.cause ?? issue }),\n\t\t});\n\t}).pipe(Effect.withSpan('cross-process.roster.readClaims'));\n\n/** Prune stale same-host claims. This is the recovery path for an\n * interrupted process that could not run its scope finalizer. */\nexport const pruneStaleClaims = (\n\tpaths: RosterPaths,\n): Effect.Effect<ContainerClaimDocument, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = claimsPath(paths.rosterFile);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\tconst next = liveContainerClaims(current);\n\t\t\tif (next.claims.length !== current.claims.length) {\n\t\t\t\tyield* writeClaims(path, next);\n\t\t\t}\n\t\t\treturn next;\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.pruneStaleClaims'));\n\n/** Record that this process now claims `containerKey`. No-op if\n * already claimed by this process. */\nexport const addClaim = (\n\tpaths: RosterPaths,\n\tcontainerKey: string,\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = claimsPath(paths.rosterFile);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\tconst live = liveContainerClaims(current);\n\t\t\tconst ownPid = process.pid;\n\t\t\tconst ownStartTime = processStartTime(ownPid) ?? undefined;\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tif (\n\t\t\t\tlive.claims.some(\n\t\t\t\t\t(c) => c.containerKey === containerKey && c.pid === ownPid && c.hostname === ownHost,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tif (live.claims.length !== current.claims.length) yield* writeClaims(path, live);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst next: ContainerClaimDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tclaims: [\n\t\t\t\t\t...live.claims,\n\t\t\t\t\t{\n\t\t\t\t\t\tcontainerKey,\n\t\t\t\t\t\tpid: ownPid,\n\t\t\t\t\t\t...(ownStartTime === undefined ? {} : { startTime: ownStartTime }),\n\t\t\t\t\t\thostname: ownHost,\n\t\t\t\t\t\tclaimedAt: Date.now(),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t\tyield* writeClaims(path, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.addClaim'));\n\n/** Release this process's claim on `containerKey`. Returns whether\n * the container has zero remaining claims AFTER this release — the\n * \"last-leaver\" signal that authorizes a teardown.\n *\n * Architecture § Cross-process safety protocol: \"when a process\n * releases its last claim and confirms no other process holds the\n * container, it may tear down.\" */\nexport const removeClaim = (\n\tpaths: RosterPaths,\n\tcontainerKey: string,\n): Effect.Effect<\n\t{ readonly lastClaimReleased: boolean },\n\tRosterError | import('./stack-lock.ts').StackLockError\n> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = claimsPath(paths.rosterFile);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\tconst live = liveContainerClaims(current);\n\t\t\tconst ownPid = process.pid;\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst remaining = live.claims.filter(\n\t\t\t\t(c) => !(c.containerKey === containerKey && c.pid === ownPid && c.hostname === ownHost),\n\t\t\t);\n\t\t\tconst stillClaimedByPeer = remaining.some((c) => c.containerKey === containerKey);\n\t\t\tconst next: ContainerClaimDocument = { version: 1, claims: remaining };\n\t\t\tyield* writeClaims(path, next);\n\t\t\treturn { lastClaimReleased: !stillClaimedByPeer };\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.removeClaim'));\n"],"mappings":"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"}
1
+ {"version":3,"file":"roster.mjs","names":["nodeHostname"],"sources":["../../../../src/substrate/runtime/cross-process/roster.ts"],"sourcesContent":["// `roster.json` — authoritative cross-process holder record.\n//\n// Architecture § Cross-process safety protocol § Roster:\n// \"the authoritative cross-process record of which OS processes are\n// currently 'in' this stack.\"\n//\n// Mutated only under `stack.lock`. Schema-validated on every read so a\n// truncated/forward-version write never corrupts a peer's view. Stale\n// entries reaped during step-3 sweep on the next claim.\n\nimport { hostname as nodeHostname } from 'node:os';\n\nimport { Data, Effect, Schema } from 'effect';\n\nimport {\n\tDEFAULT_SWEEP_POLICY,\n\ttype RosterDocument,\n\tRosterDocumentSchema,\n\ttype RosterHolder,\n\ttype RosterSweepPolicy,\n} from '../../cross-process.ts';\nimport { SpanAttr } from '../observability/spans.ts';\nimport { versionedDocSchema } from '../../versioned-doc-schema.ts';\nimport {\n\treadVersionedDocumentSync,\n\twriteVersionedDocumentSync,\n} from '../../versioned-doc-sync.ts';\nimport { selfPid } from './self-pid.ts';\nimport { acquireStackLock } from './stack-lock.ts';\nimport {\n\tisPidAlive,\n\tlayerLivenessProbeScope,\n\tLivenessProbeScope,\n\townHolder,\n\tprocessStartTime,\n} from './liveness.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\nexport class RosterCorruptError extends Data.TaggedError('RosterCorruptError')<{\n\treadonly path: string;\n\treadonly raw: string;\n\treadonly cause: unknown;\n}> {}\n\nexport class RosterIoError extends Data.TaggedError('RosterIoError')<{\n\treadonly path: string;\n\treadonly cause: unknown;\n}> {}\n\nexport type RosterError = RosterCorruptError | RosterIoError;\n\n// -----------------------------------------------------------------------------\n// Read / write\n// -----------------------------------------------------------------------------\n\nconst EMPTY_ROSTER: RosterDocument = { version: 1, holders: [] };\n\nconst ROSTER_DOC_ERRORS = {\n\tmkIo: ({ path, cause }: { path: string; cause: unknown }) => new RosterIoError({ path, cause }),\n\tmkCorrupt: ({ path, raw, cause }: { path: string; raw: string; cause: unknown }) =>\n\t\tnew RosterCorruptError({ path, raw, cause }),\n} as const;\n\n/** Read the roster from disk. Returns the empty document if absent.\n * Tolerates a missing file but NOT a malformed file (a malformed\n * roster surfaces a typed error so callers can decide whether to\n * abandon or rewrite). */\nexport const readRoster = (path: string): Effect.Effect<RosterDocument, RosterError> =>\n\treadVersionedDocumentSync(path, RosterDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_ROSTER).pipe(\n\t\tEffect.withSpan('cross-process.roster.read'),\n\t);\n\n/** Atomic write: route through the canonical sync primitive. The\n * roster's mutations are all under `stack.lock`, so the non-yielding\n * sync surface is correct here — and it shares ONE owner of the\n * tempfile dance with state-store, cache, and manifest. */\nconst atomicWriteRoster = (path: string, doc: RosterDocument): Effect.Effect<void, RosterIoError> =>\n\twriteVersionedDocumentSync(path, doc, ROSTER_DOC_ERRORS);\n\n// -----------------------------------------------------------------------------\n// Sweep\n// -----------------------------------------------------------------------------\n\n/** Walk the roster's holders and drop those that fail the liveness\n * test AND whose heartbeats are older than `staleAfterMillis`.\n *\n * Architecture § Claim protocol step 3 — \"Holders whose\n * `heartbeatAt` is older than 3× the heartbeat interval AND who fail\n * the PID liveness check are evicted.\"\n *\n * A live holder with a stale heartbeat is NOT evicted — heartbeat\n * staleness alone is allowed (slow peer). The conjunction matters. */\nexport const sweepStaleHolders = Effect.fn('cross-process.roster.sweep')(function* (\n\tdoc: RosterDocument,\n\tpolicy: RosterSweepPolicy = DEFAULT_SWEEP_POLICY,\n\tnow: number = Date.now(),\n) {\n\t// Yield a fresh per-sweep liveness scope so the same pid is probed\n\t// AT MOST once across all holders in this pass — two holders sharing\n\t// a pid (corrupted-roster edge case) collapse to one fork. The\n\t// `Layer.provide` below scopes the cache to THIS sweep only.\n\tconst probe = yield* LivenessProbeScope;\n\tconst survivors: RosterHolder[] = [];\n\tconst evicted: RosterHolder[] = [];\n\tconst ownHost = nodeHostname();\n\tfor (const holder of doc.holders) {\n\t\tconst heartbeatStale = now - holder.heartbeatAt > policy.staleAfterMillis;\n\t\tif (!heartbeatStale) {\n\t\t\tsurvivors.push(holder);\n\t\t\tcontinue;\n\t\t}\n\t\tconst liveness = yield* probe\n\t\t\t.probeHolderLiveness(holder, ownHost)\n\t\t\t.pipe(Effect.catch(() => Effect.succeed('alive' as const)));\n\t\tif (liveness === 'dead') {\n\t\t\tevicted.push(holder);\n\t\t} else {\n\t\t\tsurvivors.push(holder);\n\t\t}\n\t}\n\treturn {\n\t\tswept: { version: doc.version, holders: survivors } satisfies RosterDocument,\n\t\tevicted: evicted as ReadonlyArray<RosterHolder>,\n\t};\n}, Effect.provide(layerLivenessProbeScope));\n\n// -----------------------------------------------------------------------------\n// Claim / release / heartbeat\n// -----------------------------------------------------------------------------\n\n/** Outcome of a claim: the holder this process registered, the swept\n * document, and whether this process is now the sole holder. */\nexport interface ClaimResult {\n\treadonly self: RosterHolder;\n\treadonly roster: RosterDocument;\n\treadonly evicted: ReadonlyArray<RosterHolder>;\n\treadonly soleHolder: boolean;\n}\n\n/** Outcome of a release: the swept document and whether this process\n * was the last leaver (no peers remain after removal). Architecture §\n * Release protocol step 4. */\nexport interface ReleaseResult {\n\treadonly roster: RosterDocument;\n\treadonly lastLeaver: boolean;\n}\n\ninterface RosterPaths {\n\treadonly stackLockFile: string;\n\treadonly rosterFile: string;\n\t/** Sibling-file path for the container-claim ledger. Optional\n\t * because the roster's holder mutations (`claim` / `release` /\n\t * `heartbeat` / `setIntent`) never touch the ledger; only the\n\t * `addClaim` / `removeClaim` / `readClaims` / `pruneStaleClaims`\n\t * entry points require it, and they assert presence at the call\n\t * site. Sourced from `StackPathsService.containerClaimsFile` —\n\t * see `substrate/runtime/paths.ts` for the policy rationale\n\t * (closed L0 path resolver). */\n\treadonly containerClaimsFile?: string;\n}\n\n/** Materialize the ledger path from `RosterPaths`. Callers that\n * invoke any of the claim-ledger APIs (`readClaims`,\n * `pruneStaleClaims`, `addClaim`, `removeClaim`) MUST construct\n * `RosterPaths` with `containerClaimsFile` populated from\n * `StackPathsService.containerClaimsFile`. The previous behavior of\n * reconstructing `dirname(rosterFile) + '/container-claims.json'`\n * internally has been removed so nothing in the runtime tree builds\n * cross-process paths outside the substrate path resolver. */\nconst requireClaimsPath = (paths: RosterPaths): string => {\n\tif (paths.containerClaimsFile === undefined) {\n\t\tthrow new Error(\n\t\t\t'cross-process.roster: container-claim API called without `containerClaimsFile` ' +\n\t\t\t\t'on the RosterPaths bundle. Source this from StackPathsService.containerClaimsFile.',\n\t\t);\n\t}\n\treturn paths.containerClaimsFile;\n};\n\n/** Match a roster holder against THIS process's identity.\n *\n * Liveness elsewhere (`checkHolderLiveness`, `isContainerClaimLive`)\n * uses `(pid, hostname, startTime)` — PID alone is insufficient on\n * long-uptime hosts where the kernel can recycle PIDs. The roster\n * mutators (`heartbeat`, `release`, `setIntent`) must apply the same\n * triple match so a recycled-PID peer's entry is never silently\n * overwritten/removed by this process.\n *\n * `startTime` is the FNV-1a hash of `ps -o lstart` (see\n * `liveness.processStartTime`). A `null` probe (process gone, or\n * exotic platform) skips the start-time check — same conservative\n * policy as `isContainerClaimLive`. */\nconst isOwnEntry = (\n\th: RosterHolder,\n\townPid: number,\n\townHost: string,\n\townStartTime: number | null,\n): boolean => {\n\tif (h.pid !== ownPid || h.hostname !== ownHost) return false;\n\t// Either side null → fall back to (pid, hostname). The roster's\n\t// own-entry test must symmetrically accept a null recorded stamp:\n\t// the writer's probe failed (exotic platform / transient `ps`\n\t// error) but the entry IS ours. Mismatching a probed `ownStartTime`\n\t// against a recorded `null` would orphan our own entry — peers\n\t// would then harvest it as \"dead\" on the next sweep.\n\tif (ownStartTime === null || h.startTime === null) return true;\n\treturn h.startTime === ownStartTime;\n};\n\nconst withStackLock = <A, E, R>(\n\tpaths: RosterPaths,\n\tbody: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E | import('./stack-lock.ts').StackLockError, R> =>\n\tEffect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tyield* acquireStackLock(paths.stackLockFile);\n\t\t\treturn yield* body;\n\t\t}),\n\t);\n\n/**\n * Claim protocol — architecture § Claim protocol.\n *\n * Under the exclusive lock:\n * 1. Read the roster (or initialize empty if missing).\n * 2. Sweep stale holders (PID liveness + heartbeat age).\n * 3. Append this process's entry.\n * 4. Atomic write the result.\n *\n * Returns the swept document, our holder entry, and any evicted peers.\n */\nexport const claim = (\n\tpaths: RosterPaths,\n\tintent: 'normal' | 'snapshot' = 'normal',\n\tpolicy: RosterSweepPolicy = DEFAULT_SWEEP_POLICY,\n): Effect.Effect<ClaimResult, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t\t'devstack.roster.path': paths.rosterFile,\n\t\t\t\t'devstack.roster.intent': intent,\n\t\t\t});\n\t\t\tconst initial = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst { swept, evicted } = yield* sweepStaleHolders(initial, policy);\n\t\t\tconst self: RosterHolder = { ...ownHolder(intent) };\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: [...swept.holders, self],\n\t\t\t};\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t\treturn {\n\t\t\t\tself,\n\t\t\t\troster: next,\n\t\t\t\tevicted,\n\t\t\t\tsoleHolder: swept.holders.length === 0,\n\t\t\t};\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.claim'));\n\n/**\n * Heartbeat protocol — refresh this process's `heartbeatAt`. Under the\n * stack lock to keep mutation atomic. Architecture § Heartbeat\n * protocol.\n */\nexport const heartbeat = (\n\tpaths: RosterPaths,\n\townPid: number = selfPid(),\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst now = Date.now();\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst ownStartTime = processStartTime(ownPid);\n\t\t\tlet touched = false;\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: current.holders.map((h) => {\n\t\t\t\t\tif (isOwnEntry(h, ownPid, ownHost, ownStartTime)) {\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t\treturn { ...h, heartbeatAt: now };\n\t\t\t\t\t}\n\t\t\t\t\treturn h;\n\t\t\t\t}),\n\t\t\t};\n\t\t\tif (touched) yield* atomicWriteRoster(paths.rosterFile, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.heartbeat'));\n\n/**\n * Release protocol — architecture § Release protocol.\n *\n * Removes this process's holder. Returns whether this was the\n * last-leaver (caller runs the stop finalizer if so).\n */\nexport const release = (\n\tpaths: RosterPaths,\n\townPid: number = selfPid(),\n): Effect.Effect<ReleaseResult, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst ownStartTime = processStartTime(ownPid);\n\t\t\tconst remaining = current.holders.filter(\n\t\t\t\t(h) => !isOwnEntry(h, ownPid, ownHost, ownStartTime),\n\t\t\t);\n\t\t\tconst next: RosterDocument = { version: 1, holders: remaining };\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t\treturn {\n\t\t\t\troster: next,\n\t\t\t\tlastLeaver: remaining.length === 0,\n\t\t\t};\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.release'));\n\n/**\n * Set this process's `intent` (`normal` ↔ `snapshot`) under the\n * exclusive lock. Architecture § Concurrent snapshot step 2 / 5.\n */\nexport const setIntent = (\n\tpaths: RosterPaths,\n\tintent: 'normal' | 'snapshot',\n\townPid: number = selfPid(),\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst ownStartTime = processStartTime(ownPid);\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: current.holders.map((h) =>\n\t\t\t\t\tisOwnEntry(h, ownPid, ownHost, ownStartTime) ? { ...h, intent } : h,\n\t\t\t\t),\n\t\t\t};\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.setIntent'));\n\n/** Background heartbeat fiber. Wakes every `intervalMillis` (default\n * matches `DEFAULT_SWEEP_POLICY.heartbeatIntervalMillis`) and refreshes\n * this process's `heartbeatAt`.\n *\n * Returns an Effect that runs forever in its Scope — the supervisor\n * forks it via `Effect.forkScoped` so it tears down with the stack. */\nexport const heartbeatFiber = (\n\tpaths: RosterPaths,\n\tintervalMillis: number = DEFAULT_SWEEP_POLICY.heartbeatIntervalMillis,\n): Effect.Effect<never> =>\n\tEffect.gen(function* () {\n\t\twhile (true) {\n\t\t\tyield* Effect.sleep(`${intervalMillis} millis`);\n\t\t\tyield* heartbeat(paths).pipe(\n\t\t\t\t// A heartbeat failure is non-fatal — the next peer's sweep\n\t\t\t\t// would otherwise evict us, but the architecture treats this\n\t\t\t\t// as \"the dev didn't get to flush a heartbeat in time;\n\t\t\t\t// recover by retrying next interval.\" Log via Effect's\n\t\t\t\t// logger; do not propagate.\n\t\t\t\tEffect.catch((err) =>\n\t\t\t\t\tEffect.logWarning('roster heartbeat failed').pipe(\n\t\t\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t\t\t[SpanAttr.rosterHeartbeatIntervalMs]: intervalMillis,\n\t\t\t\t\t\t\t[SpanAttr.errorCause]: String(err),\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}).pipe(Effect.withSpan('cross-process.roster.heartbeatFiber'));\n\n// -----------------------------------------------------------------------------\n// Container-claim ledger\n// -----------------------------------------------------------------------------\n//\n// Architecture § Cross-process safety protocol: roster \"Records claimed\n// containers per process. Last-leaver semantics: when a process\n// releases its last claim and confirms no other process holds the\n// container, it may tear down.\"\n//\n// The roster document carries holders only — the per-container claim\n// ledger lives in an EXTENSION file at `<stackRoot>/container-claims.json`\n// so the architecture-mandated `RosterDocument` shape doesn't widen.\n// Same atomicity discipline: mutate under `stack.lock`.\n\nexport interface ContainerClaim {\n\treadonly containerKey: string;\n\treadonly pid: number;\n\treadonly startTime?: number;\n\treadonly hostname: string;\n\treadonly claimedAt: number;\n}\n\nexport interface ContainerClaimDocument {\n\treadonly version: 1;\n\treadonly claims: ReadonlyArray<ContainerClaim>;\n}\n\nconst ContainerClaimSchema = Schema.Struct({\n\tcontainerKey: Schema.String,\n\tpid: Schema.Number,\n\tstartTime: Schema.optional(Schema.Number),\n\thostname: Schema.String,\n\tclaimedAt: Schema.Number,\n});\n\nconst ContainerClaimDocumentSchema = versionedDocSchema(1, {\n\tclaims: Schema.Array(ContainerClaimSchema),\n});\n\nconst EMPTY_CLAIMS: ContainerClaimDocument = { version: 1, claims: [] };\n\n/** Match a container claim against THIS process's identity using the\n * same `(pid, hostname, startTime)` triple that `isOwnEntry` /\n * `isContainerClaimLive` apply to roster holders — PID alone is\n * insufficient on long-uptime hosts where the kernel recycles PIDs.\n * A null/undefined start-time on either side falls back to\n * `(pid, hostname)`, matching the conservative policy elsewhere\n * (avoids orphaning our own entry when the probe failed). */\nconst isOwnContainerClaim = (\n\tc: ContainerClaim,\n\tcontainerKey: string,\n\townPid: number,\n\townHost: string,\n\townStartTime: number | undefined,\n): boolean => {\n\tif (c.containerKey !== containerKey || c.pid !== ownPid || c.hostname !== ownHost) return false;\n\tif (ownStartTime === undefined || c.startTime === undefined) return true;\n\treturn c.startTime === ownStartTime;\n};\n\nconst isContainerClaimLive = (\n\tclaim: ContainerClaim,\n\tprobeStartTime: (pid: number) => number | null,\n\townHost: string = nodeHostname(),\n): boolean => {\n\tif (claim.hostname !== ownHost) return true;\n\tif (!isPidAlive(claim.pid)) return false;\n\tif (claim.startTime === undefined) return true;\n\tconst probedStart = probeStartTime(claim.pid);\n\tif (probedStart === null) return true;\n\treturn probedStart === claim.startTime;\n};\n\n/** Effect-flavored filter that yields a fresh `LivenessProbeScope` so\n * the same pid is probed at most once across this pass — even when\n * multiple claims by the same pid sit in the ledger. The scope's\n * cache is private to this call (provided via `Effect.provide`). */\nconst liveContainerClaims = (\n\tdoc: ContainerClaimDocument,\n): Effect.Effect<ContainerClaimDocument> =>\n\tEffect.gen(function* () {\n\t\tconst probe = yield* LivenessProbeScope;\n\t\tconst ownHost = nodeHostname();\n\t\tconst filtered: ContainerClaimDocument = {\n\t\t\tversion: 1,\n\t\t\tclaims: doc.claims.filter((claim) =>\n\t\t\t\tisContainerClaimLive(claim, probe.probeStartTime, ownHost),\n\t\t\t),\n\t\t};\n\t\treturn filtered;\n\t}).pipe(Effect.provide(layerLivenessProbeScope));\n\nconst writeClaims = (\n\tpath: string,\n\tdoc: ContainerClaimDocument,\n): Effect.Effect<void, RosterIoError> => writeVersionedDocumentSync(path, doc, ROSTER_DOC_ERRORS);\n\n/** Read the container-claim ledger. */\nexport const readClaims = (\n\tpaths: RosterPaths,\n): Effect.Effect<ContainerClaimDocument, RosterError> =>\n\tEffect.suspend(() =>\n\t\treadVersionedDocumentSync(\n\t\t\trequireClaimsPath(paths),\n\t\t\tContainerClaimDocumentSchema,\n\t\t\tROSTER_DOC_ERRORS,\n\t\t\tEMPTY_CLAIMS,\n\t\t),\n\t).pipe(Effect.withSpan('cross-process.roster.readClaims'));\n\n/** Prune stale same-host claims. This is the recovery path for an\n * interrupted process that could not run its scope finalizer. */\nexport const pruneStaleClaims = (\n\tpaths: RosterPaths,\n): Effect.Effect<ContainerClaimDocument, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = requireClaimsPath(paths);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\tconst next = yield* liveContainerClaims(current);\n\t\t\tif (next.claims.length !== current.claims.length) {\n\t\t\t\tyield* writeClaims(path, next);\n\t\t\t}\n\t\t\treturn next;\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.pruneStaleClaims'));\n\n/** Record that this process now claims `containerKey`. No-op if\n * already claimed by this process. */\nexport const addClaim = (\n\tpaths: RosterPaths,\n\tcontainerKey: string,\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = requireClaimsPath(paths);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\t// `liveContainerClaims` pre-filter must run BEFORE the own-claim\n\t\t\t// scan below: it drops dead peers so a recycled-PID peer can't\n\t\t\t// shadow our own (pid, hostname, startTime) identity. Ordering\n\t\t\t// here is load-bearing.\n\t\t\tconst live = yield* liveContainerClaims(current);\n\t\t\tconst ownPid = selfPid();\n\t\t\tconst ownStartTime = processStartTime(ownPid) ?? undefined;\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tif (\n\t\t\t\tlive.claims.some((c) =>\n\t\t\t\t\tisOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime),\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tif (live.claims.length !== current.claims.length) yield* writeClaims(path, live);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst next: ContainerClaimDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tclaims: [\n\t\t\t\t\t...live.claims,\n\t\t\t\t\t{\n\t\t\t\t\t\tcontainerKey,\n\t\t\t\t\t\tpid: ownPid,\n\t\t\t\t\t\t...(ownStartTime === undefined ? {} : { startTime: ownStartTime }),\n\t\t\t\t\t\thostname: ownHost,\n\t\t\t\t\t\tclaimedAt: Date.now(),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t\tyield* writeClaims(path, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.addClaim'));\n\n/** Release this process's claim on `containerKey`. Returns whether\n * the container has zero remaining claims AFTER this release — the\n * \"last-leaver\" signal that authorizes a teardown.\n *\n * Architecture § Cross-process safety protocol: \"when a process\n * releases its last claim and confirms no other process holds the\n * container, it may tear down.\" */\nexport const removeClaim = (\n\tpaths: RosterPaths,\n\tcontainerKey: string,\n): Effect.Effect<\n\t{ readonly lastClaimReleased: boolean },\n\tRosterError | import('./stack-lock.ts').StackLockError\n> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = requireClaimsPath(paths);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\t// `liveContainerClaims` pre-filter must run BEFORE the own-claim\n\t\t\t// scan below: it drops dead peers so a recycled-PID peer can't\n\t\t\t// shadow our own (pid, hostname, startTime) identity. Ordering\n\t\t\t// here is load-bearing.\n\t\t\tconst live = yield* liveContainerClaims(current);\n\t\t\tconst ownPid = selfPid();\n\t\t\tconst ownStartTime = processStartTime(ownPid) ?? undefined;\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst remaining = live.claims.filter(\n\t\t\t\t(c) => !isOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime),\n\t\t\t);\n\t\t\tconst stillClaimedByPeer = remaining.some((c) => c.containerKey === containerKey);\n\t\t\tconst next: ContainerClaimDocument = { version: 1, claims: remaining };\n\t\t\tyield* writeClaims(path, next);\n\t\t\treturn { lastClaimReleased: !stillClaimedByPeer };\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.removeClaim'));\n"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ //#region src/substrate/runtime/cross-process/self-pid.ts
2
+ /** This process's PID. Module-level indirection so cross-process
3
+ * protocol modules read through one named entry point. */
4
+ const selfPid = () => process.pid;
5
+ //#endregion
6
+ export { selfPid };
7
+
8
+ //# sourceMappingURL=self-pid.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-pid.mjs","names":[],"sources":["../../../../src/substrate/runtime/cross-process/self-pid.ts"],"sourcesContent":["// Own-process PID accessor.\n//\n// Centralizes `process.pid` reads so:\n// 1. The cross-process protocol modules (roster, snapshot-reservation,\n// liveness, atomic-write tempfiles, port-broker owner ids) have a\n// single named entry point instead of scattering the global read.\n// 2. Tests that want to simulate a peer can swap this module in a\n// vitest mock without monkey-patching `process` globally.\n//\n// The implementation is intentionally trivial — `() => process.pid` —\n// and lives in its own module so call sites read with a meaningful name\n// rather than a raw global.\n\n/** This process's PID. Module-level indirection so cross-process\n * protocol modules read through one named entry point. */\nexport const selfPid = (): number => process.pid;\n"],"mappings":";;;AAeA,MAAa,gBAAwB,QAAQ"}
@@ -1,25 +1,22 @@
1
+ import { SpanAttr } from "../observability/spans.mjs";
2
+ import { selfPid } from "./self-pid.mjs";
1
3
  import { SnapshotReservationSchema } from "../../cross-process.mjs";
4
+ import { parseVersionedDocumentBodyOrNull } from "../../versioned-doc-sync.mjs";
2
5
  import { checkHolderLiveness } from "./liveness.mjs";
3
- import { decodeJsonTextSync } from "../runtime-decode.mjs";
6
+ import { reclaimUnparseableStaleFile } from "./reclaim-stale-file.mjs";
4
7
  import { Data, Effect } from "effect";
5
8
  import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
9
+ import { hostname } from "node:os";
6
10
  //#region src/substrate/runtime/cross-process/snapshot-reservation.ts
7
11
  var SnapshotReservationHeldError = class extends Data.TaggedError("SnapshotReservationHeldError") {};
8
12
  var SnapshotReservationIoError = class extends Data.TaggedError("SnapshotReservationIoError") {};
9
- const parseReservation = (raw) => {
10
- try {
11
- return decodeJsonTextSync(SnapshotReservationSchema, raw, {
12
- source: "snapshot.reservation",
13
- mkError: (issue) => issue
14
- });
15
- } catch {
16
- return null;
17
- }
18
- };
13
+ const parseReservation = (raw) => parseVersionedDocumentBodyOrNull(raw, SnapshotReservationSchema, "snapshot.reservation");
19
14
  const ownReservation = (startTime) => ({
20
- creatorPid: process.pid,
15
+ version: 1,
16
+ creatorPid: selfPid(),
21
17
  creatorStartTime: startTime,
22
- createdAt: Date.now()
18
+ createdAt: Date.now(),
19
+ hostname: hostname()
23
20
  });
24
21
  /**
25
22
  * Sweep an orphaned reservation. If the file exists AND its creator is
@@ -33,7 +30,7 @@ const ownReservation = (startTime) => ({
33
30
  * reservation's creator entry is dead) and unlinks it."
34
31
  */
35
32
  const sweepOrphan = (path) => Effect.gen(function* () {
36
- yield* Effect.annotateCurrentSpan({ "devstack.snapshot-reservation.path": path });
33
+ yield* Effect.annotateCurrentSpan({ [SpanAttr.snapshotReservationPath]: path });
37
34
  if (!existsSync(path)) return { swept: false };
38
35
  const reservation = parseReservation(yield* Effect.try({
39
36
  try: () => readFileSync(path, "utf8"),
@@ -42,24 +39,23 @@ const sweepOrphan = (path) => Effect.gen(function* () {
42
39
  cause
43
40
  })
44
41
  }));
45
- if (reservation === null) {
46
- yield* Effect.try({
47
- try: () => unlinkSync(path),
48
- catch: (cause) => new SnapshotReservationIoError({
49
- path,
50
- cause
51
- })
52
- });
53
- return { swept: true };
54
- }
42
+ if (reservation === null) return { swept: (yield* Effect.try({
43
+ try: () => reclaimUnparseableStaleFile(path, parseReservation),
44
+ catch: (cause) => new SnapshotReservationIoError({
45
+ path,
46
+ cause
47
+ })
48
+ })) === "reclaimed" };
49
+ const ownHost = hostname();
50
+ if (reservation.hostname !== ownHost) return { swept: false };
55
51
  if ((yield* checkHolderLiveness({
56
52
  pid: reservation.creatorPid,
57
53
  startTime: reservation.creatorStartTime,
58
- hostname: "",
54
+ hostname: reservation.hostname,
59
55
  claimedAt: reservation.createdAt,
60
56
  heartbeatAt: reservation.createdAt,
61
57
  intent: "snapshot"
62
- }, "").pipe(Effect.catch(() => Effect.succeed("alive")))) === "alive") return { swept: false };
58
+ }, ownHost).pipe(Effect.catch(() => Effect.succeed("alive")))) === "alive") return { swept: false };
63
59
  yield* Effect.try({
64
60
  try: () => unlinkSync(path),
65
61
  catch: (cause) => new SnapshotReservationIoError({
@@ -82,7 +78,7 @@ const sweepOrphan = (path) => Effect.gen(function* () {
82
78
  * the orphan sweep's pid-startTime check resolves identically.
83
79
  */
84
80
  const acquireReservation = (path, startTime) => Effect.gen(function* () {
85
- yield* Effect.annotateCurrentSpan({ "devstack.snapshot-reservation.path": path });
81
+ yield* Effect.annotateCurrentSpan({ [SpanAttr.snapshotReservationPath]: path });
86
82
  yield* sweepOrphan(path);
87
83
  const body = ownReservation(startTime);
88
84
  yield* Effect.try({
@@ -112,6 +108,6 @@ const acquireReservation = (path, startTime) => Effect.gen(function* () {
112
108
  return body;
113
109
  }).pipe(Effect.withSpan("cross-process.snapshot-reservation.acquire"));
114
110
  //#endregion
115
- export { acquireReservation };
111
+ export { SnapshotReservationHeldError, SnapshotReservationIoError, acquireReservation, sweepOrphan };
116
112
 
117
113
  //# sourceMappingURL=snapshot-reservation.mjs.map