@mysten-incubation/devstack 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (858) hide show
  1. package/README.md +7 -4
  2. package/dashboard-ui/assets/grpc-a4usE3Nk.js +3 -0
  3. package/dashboard-ui/assets/index-B82Bh84P.css +1 -0
  4. package/dashboard-ui/assets/index-CRYJ4pru.js +1277 -0
  5. package/dashboard-ui/index.html +2 -2
  6. package/dist/api/define-capabilities.d.mts +1 -20
  7. package/dist/api/define-capabilities.mjs +15 -22
  8. package/dist/api/define-capabilities.mjs.map +1 -1
  9. package/dist/api/define-devstack-with.d.mts +7 -1
  10. package/dist/api/define-devstack-with.mjs.map +1 -1
  11. package/dist/api/define-devstack.mjs.map +1 -1
  12. package/dist/api/define-plugin.d.mts +2 -1
  13. package/dist/api/inference-network.d.mts +31 -17
  14. package/dist/api/inference-network.mjs +40 -43
  15. package/dist/api/inference-network.mjs.map +1 -1
  16. package/dist/api/load-config.mjs +88 -0
  17. package/dist/api/load-config.mjs.map +1 -0
  18. package/dist/api/mode-narrowed-factory.d.mts +1 -1
  19. package/dist/api/mode-narrowed-factory.mjs.map +1 -1
  20. package/dist/api/run-stack-internal.mjs +202 -0
  21. package/dist/api/run-stack-internal.mjs.map +1 -0
  22. package/dist/api/run-stack.d.mts +82 -31
  23. package/dist/api/run-stack.mjs +22 -141
  24. package/dist/api/run-stack.mjs.map +1 -1
  25. package/dist/build-integrations/dapp-kit/index.d.mts +61 -0
  26. package/dist/build-integrations/dapp-kit/index.mjs +58 -0
  27. package/dist/build-integrations/dapp-kit/index.mjs.map +1 -0
  28. package/dist/build-integrations/playwright/config.d.mts +1 -30
  29. package/dist/build-integrations/playwright/config.mjs +1 -27
  30. package/dist/build-integrations/playwright/config.mjs.map +1 -1
  31. package/dist/build-integrations/playwright/errors.d.mts +4 -7
  32. package/dist/build-integrations/playwright/errors.mjs.map +1 -1
  33. package/dist/build-integrations/playwright/global-setup.d.mts +42 -71
  34. package/dist/build-integrations/playwright/global-setup.mjs +142 -218
  35. package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
  36. package/dist/build-integrations/playwright/index.d.mts +2 -2
  37. package/dist/build-integrations/playwright/index.mjs +2 -2
  38. package/dist/build-integrations/playwright/stack-context.d.mts +3 -1
  39. package/dist/build-integrations/playwright/stack-context.mjs +4 -2
  40. package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
  41. package/dist/build-integrations/playwright/wallet-context.mjs +44 -19
  42. package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
  43. package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
  44. package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -1
  45. package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
  46. package/dist/build-integrations/runtime/discover.d.mts +6 -4
  47. package/dist/build-integrations/runtime/discover.mjs +15 -4
  48. package/dist/build-integrations/runtime/discover.mjs.map +1 -1
  49. package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
  50. package/dist/build-integrations/runtime/errors.mjs.map +1 -1
  51. package/dist/build-integrations/runtime/read-stack-context.mjs +3 -3
  52. package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
  53. package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +23 -7
  54. package/dist/build-integrations/runtime/resolve-discovery-env.mjs +25 -12
  55. package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -1
  56. package/dist/build-integrations/runtime/stack-context.d.mts +2 -2
  57. package/dist/build-integrations/vite/index.d.mts +84 -19
  58. package/dist/build-integrations/vite/index.mjs +203 -28
  59. package/dist/build-integrations/vite/index.mjs.map +1 -1
  60. package/dist/build-integrations/vitest/config.d.mts +20 -2
  61. package/dist/build-integrations/vitest/config.mjs +16 -4
  62. package/dist/build-integrations/vitest/config.mjs.map +1 -1
  63. package/dist/build-integrations/vitest/env.d.mts +18 -10
  64. package/dist/build-integrations/vitest/env.mjs +11 -10
  65. package/dist/build-integrations/vitest/env.mjs.map +1 -1
  66. package/dist/build-integrations/vitest/errors.d.mts +7 -11
  67. package/dist/build-integrations/vitest/errors.mjs +3 -4
  68. package/dist/build-integrations/vitest/errors.mjs.map +1 -1
  69. package/dist/build-integrations/vitest/global-setup.d.mts +35 -0
  70. package/dist/build-integrations/vitest/global-setup.mjs +156 -0
  71. package/dist/build-integrations/vitest/global-setup.mjs.map +1 -0
  72. package/dist/build-integrations/vitest/index.d.mts +3 -2
  73. package/dist/build-integrations/vitest/index.mjs +3 -2
  74. package/dist/build-integrations/vitest/setup.mjs +2 -2
  75. package/dist/build-integrations/vitest/setup.mjs.map +1 -1
  76. package/dist/build-integrations/vitest/stack-context.d.mts +5 -3
  77. package/dist/build-integrations/vitest/stack-context.mjs +3 -2
  78. package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
  79. package/dist/cli/bail.mjs.map +1 -1
  80. package/dist/cli/doctor-probes.mjs +1 -3
  81. package/dist/cli/doctor-probes.mjs.map +1 -1
  82. package/dist/cli/main.d.mts +23 -1
  83. package/dist/cli/main.mjs +63 -8
  84. package/dist/cli/main.mjs.map +1 -1
  85. package/dist/cli/prune-direct.mjs.map +1 -1
  86. package/dist/cli/snapshot-reader.mjs.map +1 -1
  87. package/dist/cli/up-lifecycle.mjs.map +1 -1
  88. package/dist/cli/wirings/apply.mjs +28 -21
  89. package/dist/cli/wirings/apply.mjs.map +1 -1
  90. package/dist/cli/wirings/codegen.mjs +108 -0
  91. package/dist/cli/wirings/codegen.mjs.map +1 -0
  92. package/dist/cli/wirings/config-loader.mjs +12 -62
  93. package/dist/cli/wirings/config-loader.mjs.map +1 -1
  94. package/dist/cli/wirings/dump-ids.mjs +86 -0
  95. package/dist/cli/wirings/dump-ids.mjs.map +1 -0
  96. package/dist/cli/wirings/engine-command.mjs +1 -1
  97. package/dist/cli/wirings/engine-command.mjs.map +1 -1
  98. package/dist/cli/wirings/identity.mjs +2 -2
  99. package/dist/cli/wirings/identity.mjs.map +1 -1
  100. package/dist/cli/wirings/provide-file-system.mjs.map +1 -1
  101. package/dist/cli/wirings/read-devstack-version.mjs +17 -0
  102. package/dist/cli/wirings/read-devstack-version.mjs.map +1 -0
  103. package/dist/cli/wirings/snapshot.mjs +27 -34
  104. package/dist/cli/wirings/snapshot.mjs.map +1 -1
  105. package/dist/cli/wirings/up-ipc.mjs +147 -0
  106. package/dist/cli/wirings/up-ipc.mjs.map +1 -0
  107. package/dist/cli/wirings/up.mjs +218 -198
  108. package/dist/cli/wirings/up.mjs.map +1 -1
  109. package/dist/cli/wirings/wipe.mjs +1 -1
  110. package/dist/cli/wirings/wipe.mjs.map +1 -1
  111. package/dist/contracts/chain-probe.d.mts +1 -1
  112. package/dist/contracts/chain-probe.mjs +1 -1
  113. package/dist/contracts/chain-probe.mjs.map +1 -1
  114. package/dist/contracts/codegenable.d.mts +60 -6
  115. package/dist/contracts/codegenable.mjs +28 -0
  116. package/dist/contracts/codegenable.mjs.map +1 -0
  117. package/dist/contracts/config-bindings.mjs +162 -0
  118. package/dist/contracts/config-bindings.mjs.map +1 -0
  119. package/dist/contracts/container-runtime.d.mts +23 -17
  120. package/dist/contracts/plugin-expander.mjs.map +1 -1
  121. package/dist/contracts/snapshotable.d.mts +5 -7
  122. package/dist/contracts/wallet-protocol.d.mts +2 -5
  123. package/dist/contracts/wallet-protocol.mjs +2 -5
  124. package/dist/contracts/wallet-protocol.mjs.map +1 -1
  125. package/dist/index.d.mts +21 -26
  126. package/dist/index.mjs +8 -9
  127. package/dist/orchestrators/boot.d.mts +28 -0
  128. package/dist/orchestrators/boot.mjs +440 -0
  129. package/dist/orchestrators/boot.mjs.map +1 -0
  130. package/dist/orchestrators/codegen/bindings.d.mts +13 -1
  131. package/dist/orchestrators/codegen/bindings.mjs +23 -9
  132. package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
  133. package/dist/orchestrators/codegen/config-runtime.mjs +208 -0
  134. package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -0
  135. package/dist/orchestrators/codegen/emit.mjs +1 -1
  136. package/dist/orchestrators/codegen/emit.mjs.map +1 -1
  137. package/dist/orchestrators/codegen/errors.mjs +21 -1
  138. package/dist/orchestrators/codegen/errors.mjs.map +1 -1
  139. package/dist/orchestrators/codegen/format.mjs +2 -0
  140. package/dist/orchestrators/codegen/format.mjs.map +1 -1
  141. package/dist/orchestrators/codegen/gitignore.mjs +30 -11
  142. package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
  143. package/dist/orchestrators/codegen/id-config.mjs +107 -0
  144. package/dist/orchestrators/codegen/id-config.mjs.map +1 -0
  145. package/dist/orchestrators/codegen/output-location.mjs +23 -31
  146. package/dist/orchestrators/codegen/output-location.mjs.map +1 -1
  147. package/dist/orchestrators/codegen/paths.mjs +8 -1
  148. package/dist/orchestrators/codegen/paths.mjs.map +1 -1
  149. package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
  150. package/dist/orchestrators/codegen/service.mjs +286 -58
  151. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  152. package/dist/orchestrators/layers.mjs +31 -0
  153. package/dist/orchestrators/layers.mjs.map +1 -0
  154. package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -1
  155. package/dist/orchestrators/lifecycle-prune/index.mjs +8 -10
  156. package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -1
  157. package/dist/orchestrators/network-options.d.mts +52 -0
  158. package/dist/orchestrators/network-options.mjs +43 -0
  159. package/dist/orchestrators/network-options.mjs.map +1 -0
  160. package/dist/orchestrators/router/cleanup.mjs.map +1 -1
  161. package/dist/orchestrators/router/cors.mjs.map +1 -1
  162. package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
  163. package/dist/orchestrators/router/errors.mjs.map +1 -1
  164. package/dist/orchestrators/router/file-provider.mjs.map +1 -1
  165. package/dist/orchestrators/router/hostname.mjs.map +1 -1
  166. package/dist/orchestrators/router/profile.mjs +1 -1
  167. package/dist/orchestrators/router/profile.mjs.map +1 -1
  168. package/dist/orchestrators/router/service.d.mts +2 -2
  169. package/dist/orchestrators/router/service.mjs +12 -27
  170. package/dist/orchestrators/router/service.mjs.map +1 -1
  171. package/dist/orchestrators/router/traefik-container.mjs +1 -1
  172. package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
  173. package/dist/orchestrators/snapshot/capture.mjs +171 -195
  174. package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
  175. package/dist/orchestrators/snapshot/descriptor.d.mts +1 -1
  176. package/dist/orchestrators/snapshot/descriptor.mjs +53 -11
  177. package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
  178. package/dist/orchestrators/snapshot/identity-guard.mjs +3 -5
  179. package/dist/orchestrators/snapshot/identity-guard.mjs.map +1 -1
  180. package/dist/orchestrators/snapshot/image-bundle-tags.mjs +32 -101
  181. package/dist/orchestrators/snapshot/image-bundle-tags.mjs.map +1 -1
  182. package/dist/orchestrators/snapshot/index.mjs +1 -4
  183. package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
  184. package/dist/orchestrators/snapshot/interrupted-restore.mjs +116 -0
  185. package/dist/orchestrators/snapshot/interrupted-restore.mjs.map +1 -0
  186. package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -1
  187. package/dist/orchestrators/snapshot/prune.mjs +40 -31
  188. package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
  189. package/dist/orchestrators/snapshot/restore.mjs +221 -196
  190. package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
  191. package/dist/orchestrators/snapshot/service.mjs +72 -62
  192. package/dist/orchestrators/snapshot/service.mjs.map +1 -1
  193. package/dist/orchestrators/snapshot/wipe.mjs +67 -56
  194. package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
  195. package/dist/plugins/account/codegen.mjs +3 -0
  196. package/dist/plugins/account/codegen.mjs.map +1 -1
  197. package/dist/plugins/account/errors.d.mts +2 -2
  198. package/dist/plugins/account/errors.mjs +1 -4
  199. package/dist/plugins/account/errors.mjs.map +1 -1
  200. package/dist/plugins/account/funding.mjs +3 -27
  201. package/dist/plugins/account/funding.mjs.map +1 -1
  202. package/dist/plugins/account/index.d.mts +3 -8
  203. package/dist/plugins/account/index.mjs +71 -35
  204. package/dist/plugins/account/index.mjs.map +1 -1
  205. package/dist/plugins/account/keypair.d.mts +8 -7
  206. package/dist/plugins/account/keypair.mjs +5 -18
  207. package/dist/plugins/account/keypair.mjs.map +1 -1
  208. package/dist/plugins/account/lease.mjs.map +1 -1
  209. package/dist/plugins/account/registry.d.mts +1 -46
  210. package/dist/plugins/account/registry.mjs.map +1 -1
  211. package/dist/plugins/account/service.d.mts +43 -34
  212. package/dist/plugins/account/service.mjs +9 -54
  213. package/dist/plugins/account/service.mjs.map +1 -1
  214. package/dist/plugins/account/snapshot.mjs.map +1 -1
  215. package/dist/plugins/account/variants/ephemeral.mjs.map +1 -1
  216. package/dist/plugins/account/variants/impersonate.mjs.map +1 -1
  217. package/dist/plugins/account/variants/signer.mjs.map +1 -1
  218. package/dist/plugins/action/discriminator.mjs.map +1 -1
  219. package/dist/plugins/action/errors.mjs +1 -4
  220. package/dist/plugins/action/errors.mjs.map +1 -1
  221. package/dist/plugins/action/execute.mjs +3 -4
  222. package/dist/plugins/action/execute.mjs.map +1 -1
  223. package/dist/plugins/action/index.d.mts +2 -2
  224. package/dist/plugins/action/index.mjs +7 -11
  225. package/dist/plugins/action/index.mjs.map +1 -1
  226. package/dist/plugins/action/service.d.mts +5 -7
  227. package/dist/plugins/action/service.mjs +2 -16
  228. package/dist/plugins/action/service.mjs.map +1 -1
  229. package/dist/plugins/coin/address-resolution.mjs.map +1 -1
  230. package/dist/plugins/coin/codegen.mjs +97 -16
  231. package/dist/plugins/coin/codegen.mjs.map +1 -1
  232. package/dist/plugins/coin/discovery.mjs.map +1 -1
  233. package/dist/plugins/coin/errors.mjs +1 -4
  234. package/dist/plugins/coin/errors.mjs.map +1 -1
  235. package/dist/plugins/coin/index.d.mts +4 -34
  236. package/dist/plugins/coin/index.mjs +47 -28
  237. package/dist/plugins/coin/index.mjs.map +1 -1
  238. package/dist/plugins/coin/metadata.d.mts +1 -0
  239. package/dist/plugins/coin/metadata.mjs +8 -9
  240. package/dist/plugins/coin/metadata.mjs.map +1 -1
  241. package/dist/plugins/coin/mint.mjs +7 -21
  242. package/dist/plugins/coin/mint.mjs.map +1 -1
  243. package/dist/plugins/coin/registry.mjs +33 -20
  244. package/dist/plugins/coin/registry.mjs.map +1 -1
  245. package/dist/plugins/coin/service.d.mts +1 -1
  246. package/dist/plugins/coin/service.mjs +1 -8
  247. package/dist/plugins/coin/service.mjs.map +1 -1
  248. package/dist/plugins/coin/snapshot.mjs.map +1 -1
  249. package/dist/plugins/coin/type-strings.mjs.map +1 -1
  250. package/dist/plugins/dashboard/domain.mjs +106 -167
  251. package/dist/plugins/dashboard/domain.mjs.map +1 -1
  252. package/dist/plugins/dashboard/index.mjs +13 -13
  253. package/dist/plugins/dashboard/index.mjs.map +1 -1
  254. package/dist/plugins/dashboard/origin-policy.mjs +3 -3
  255. package/dist/plugins/dashboard/origin-policy.mjs.map +1 -1
  256. package/dist/plugins/dashboard/routable.mjs.map +1 -1
  257. package/dist/plugins/dashboard/schema/builder.mjs.map +1 -1
  258. package/dist/plugins/dashboard/schema/enums.mjs.map +1 -1
  259. package/dist/plugins/dashboard/schema/root.mjs +23 -44
  260. package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
  261. package/dist/plugins/dashboard/schema/types.mjs +37 -91
  262. package/dist/plugins/dashboard/schema/types.mjs.map +1 -1
  263. package/dist/plugins/dashboard/schema.mjs.map +1 -1
  264. package/dist/plugins/dashboard/server.mjs.map +1 -1
  265. package/dist/plugins/deepbook/codegen.d.mts +12 -11
  266. package/dist/plugins/deepbook/codegen.mjs +108 -11
  267. package/dist/plugins/deepbook/codegen.mjs.map +1 -1
  268. package/dist/plugins/deepbook/deploy.mjs +7 -14
  269. package/dist/plugins/deepbook/deploy.mjs.map +1 -1
  270. package/dist/plugins/deepbook/errors.mjs +4 -2
  271. package/dist/plugins/deepbook/errors.mjs.map +1 -1
  272. package/dist/plugins/deepbook/faucet-strategy.mjs +4 -10
  273. package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
  274. package/dist/plugins/deepbook/hash.mjs.map +1 -1
  275. package/dist/plugins/deepbook/index.d.mts +38 -34
  276. package/dist/plugins/deepbook/index.mjs +133 -64
  277. package/dist/plugins/deepbook/index.mjs.map +1 -1
  278. package/dist/plugins/deepbook/plugin-key.mjs.map +1 -1
  279. package/dist/plugins/deepbook/pyth/index.mjs +4 -8
  280. package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
  281. package/dist/plugins/deepbook/snapshot.mjs +0 -1
  282. package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
  283. package/dist/plugins/deepbook/type-strings.mjs.map +1 -1
  284. package/dist/plugins/deepbook/types.d.mts +2 -1
  285. package/dist/plugins/deepbook/types.mjs.map +1 -1
  286. package/dist/plugins/faucet/dispatcher.d.mts +3 -3
  287. package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
  288. package/dist/plugins/faucet/errors.mjs.map +1 -1
  289. package/dist/plugins/faucet/http.mjs +2 -10
  290. package/dist/plugins/faucet/http.mjs.map +1 -1
  291. package/dist/plugins/faucet/index.mjs +0 -1
  292. package/dist/plugins/faucet/index.mjs.map +1 -1
  293. package/dist/plugins/host-service/errors.d.mts +1 -3
  294. package/dist/plugins/host-service/errors.mjs +1 -2
  295. package/dist/plugins/host-service/errors.mjs.map +1 -1
  296. package/dist/plugins/host-service/index.d.mts +1 -2
  297. package/dist/plugins/host-service/index.mjs +11 -11
  298. package/dist/plugins/host-service/index.mjs.map +1 -1
  299. package/dist/plugins/host-service/routable.mjs.map +1 -1
  300. package/dist/plugins/host-service/service.mjs +13 -13
  301. package/dist/plugins/host-service/service.mjs.map +1 -1
  302. package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -1
  303. package/dist/plugins/internal/codegen-helpers.mjs +2 -0
  304. package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
  305. package/dist/plugins/internal/funding-failure-error.mjs.map +1 -1
  306. package/dist/plugins/{postgres → internal/postgres-sidecar}/connection.mjs +1 -1
  307. package/dist/plugins/internal/postgres-sidecar/connection.mjs.map +1 -0
  308. package/dist/plugins/{postgres → internal/postgres-sidecar}/db-ensure.mjs +30 -16
  309. package/dist/plugins/internal/postgres-sidecar/db-ensure.mjs.map +1 -0
  310. package/dist/plugins/internal/postgres-sidecar/errors.mjs +21 -0
  311. package/dist/plugins/internal/postgres-sidecar/errors.mjs.map +1 -0
  312. package/dist/{substrate/runtime/scoped-multimap → plugins/internal/postgres-sidecar}/index.mjs +1 -0
  313. package/dist/plugins/internal/postgres-sidecar/service.mjs +121 -0
  314. package/dist/plugins/internal/postgres-sidecar/service.mjs.map +1 -0
  315. package/dist/plugins/package/build.mjs +2 -2
  316. package/dist/plugins/package/build.mjs.map +1 -1
  317. package/dist/plugins/package/codegen.d.mts +7 -14
  318. package/dist/plugins/package/codegen.mjs +187 -59
  319. package/dist/plugins/package/codegen.mjs.map +1 -1
  320. package/dist/plugins/package/dep-resolution.mjs +51 -3
  321. package/dist/plugins/package/dep-resolution.mjs.map +1 -1
  322. package/dist/plugins/package/errors.mjs +1 -4
  323. package/dist/plugins/package/errors.mjs.map +1 -1
  324. package/dist/plugins/package/git-source.d.mts +18 -0
  325. package/dist/plugins/package/git-source.mjs +119 -0
  326. package/dist/plugins/package/git-source.mjs.map +1 -0
  327. package/dist/plugins/package/index.d.mts +28 -19
  328. package/dist/plugins/package/index.mjs +107 -32
  329. package/dist/plugins/package/index.mjs.map +1 -1
  330. package/dist/plugins/package/mode-known.mjs +2 -2
  331. package/dist/plugins/package/mode-known.mjs.map +1 -1
  332. package/dist/plugins/package/mode-local.mjs +18 -35
  333. package/dist/plugins/package/mode-local.mjs.map +1 -1
  334. package/dist/plugins/package/publish-executor.mjs +13 -8
  335. package/dist/plugins/package/publish-executor.mjs.map +1 -1
  336. package/dist/plugins/package/publish-output.d.mts +1 -11
  337. package/dist/plugins/package/publish-output.mjs +1 -9
  338. package/dist/plugins/package/publish-output.mjs.map +1 -1
  339. package/dist/plugins/package/registry.d.mts +1 -1
  340. package/dist/plugins/package/registry.mjs +26 -14
  341. package/dist/plugins/package/registry.mjs.map +1 -1
  342. package/dist/plugins/package/service.mjs.map +1 -1
  343. package/dist/plugins/package/snapshot.mjs.map +1 -1
  344. package/dist/plugins/router-entrypoints.mjs +1 -3
  345. package/dist/plugins/router-entrypoints.mjs.map +1 -1
  346. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +1 -2
  347. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
  348. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +1 -6
  349. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
  350. package/dist/plugins/seal/codegen.d.mts +1 -5
  351. package/dist/plugins/seal/codegen.mjs +83 -12
  352. package/dist/plugins/seal/codegen.mjs.map +1 -1
  353. package/dist/plugins/seal/config-render.mjs +2 -6
  354. package/dist/plugins/seal/config-render.mjs.map +1 -1
  355. package/dist/plugins/seal/deploy.mjs +18 -29
  356. package/dist/plugins/seal/deploy.mjs.map +1 -1
  357. package/dist/plugins/seal/errors.d.mts +4 -5
  358. package/dist/plugins/seal/errors.mjs +1 -8
  359. package/dist/plugins/seal/errors.mjs.map +1 -1
  360. package/dist/plugins/seal/index.d.mts +7 -28
  361. package/dist/plugins/seal/index.mjs +69 -47
  362. package/dist/plugins/seal/index.mjs.map +1 -1
  363. package/dist/plugins/seal/key-manager.mjs.map +1 -1
  364. package/dist/plugins/seal/key-server.mjs +1 -6
  365. package/dist/plugins/seal/key-server.mjs.map +1 -1
  366. package/dist/plugins/seal/keygen.mjs +2 -3
  367. package/dist/plugins/seal/keygen.mjs.map +1 -1
  368. package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
  369. package/dist/plugins/seal/mode/live.mjs +1 -1
  370. package/dist/plugins/seal/mode/live.mjs.map +1 -1
  371. package/dist/plugins/seal/mode/local-keygen.mjs +4 -10
  372. package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
  373. package/dist/plugins/seal/plugin-key.mjs.map +1 -1
  374. package/dist/plugins/seal/registry-publish.mjs.map +1 -1
  375. package/dist/plugins/seal/routable.mjs.map +1 -1
  376. package/dist/plugins/seal/service.mjs.map +1 -1
  377. package/dist/plugins/seal/snapshot.mjs +1 -2
  378. package/dist/plugins/seal/snapshot.mjs.map +1 -1
  379. package/dist/plugins/sui/auto-tick.mjs +5 -5
  380. package/dist/plugins/sui/auto-tick.mjs.map +1 -1
  381. package/dist/plugins/sui/chain-build-container.mjs +2 -2
  382. package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
  383. package/dist/plugins/sui/chain-probe.mjs +4 -4
  384. package/dist/plugins/sui/chain-probe.mjs.map +1 -1
  385. package/dist/plugins/sui/codegen.d.mts +9 -7
  386. package/dist/plugins/sui/codegen.mjs +49 -46
  387. package/dist/plugins/sui/codegen.mjs.map +1 -1
  388. package/dist/plugins/sui/errors.d.mts +1 -1
  389. package/dist/plugins/sui/errors.mjs +1 -9
  390. package/dist/plugins/sui/errors.mjs.map +1 -1
  391. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.d.mts +1 -1
  392. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.mjs +3 -7
  393. package/dist/plugins/sui/exec/index.mjs.map +1 -0
  394. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.mjs +1 -1
  395. package/dist/plugins/sui/exec/sign-and-dispatch.mjs.map +1 -0
  396. package/dist/plugins/sui/fork-faucet-strategy.mjs +2 -6
  397. package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -1
  398. package/dist/plugins/sui/fork-orchestration.mjs +13 -23
  399. package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
  400. package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
  401. package/dist/plugins/sui/index.d.mts +34 -83
  402. package/dist/plugins/sui/index.mjs +200 -68
  403. package/dist/plugins/sui/index.mjs.map +1 -1
  404. package/dist/{substrate/runtime/sui-ledger → plugins/sui/ledger}/object-ref.mjs +1 -1
  405. package/dist/plugins/sui/ledger/object-ref.mjs.map +1 -0
  406. package/dist/plugins/sui/local-faucet-strategy.mjs +1 -5
  407. package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -1
  408. package/dist/plugins/sui/log-attrs.mjs +11 -0
  409. package/dist/plugins/sui/log-attrs.mjs.map +1 -0
  410. package/dist/plugins/sui/mode/external.mjs +4 -12
  411. package/dist/plugins/sui/mode/external.mjs.map +1 -1
  412. package/dist/plugins/sui/mode/fork.mjs +19 -16
  413. package/dist/plugins/sui/mode/fork.mjs.map +1 -1
  414. package/dist/plugins/sui/mode/live.mjs +4 -17
  415. package/dist/plugins/sui/mode/live.mjs.map +1 -1
  416. package/dist/plugins/sui/mode/local.d.mts +1 -0
  417. package/dist/plugins/sui/mode/local.mjs +203 -61
  418. package/dist/plugins/sui/mode/local.mjs.map +1 -1
  419. package/dist/plugins/sui/mode/shared-boot.mjs +18 -23
  420. package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
  421. package/dist/plugins/sui/mode/shared.d.mts +6 -7
  422. package/dist/plugins/sui/mode/shared.mjs.map +1 -1
  423. package/dist/plugins/sui/mode/spec.d.mts +23 -6
  424. package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.mjs +28 -30
  425. package/dist/plugins/sui/move/index.mjs.map +1 -0
  426. package/dist/plugins/sui/move-summary-runner.mjs +8 -4
  427. package/dist/plugins/sui/move-summary-runner.mjs.map +1 -1
  428. package/dist/{substrate/network.d.mts → plugins/sui/network-config.d.mts} +11 -10
  429. package/dist/plugins/sui/network-resolver.d.mts +6 -6
  430. package/dist/plugins/sui/routable.mjs.map +1 -1
  431. package/dist/plugins/sui/service.mjs +10 -6
  432. package/dist/plugins/sui/service.mjs.map +1 -1
  433. package/dist/plugins/sui/snapshot.mjs +10 -5
  434. package/dist/plugins/sui/snapshot.mjs.map +1 -1
  435. package/dist/plugins/wallet/codegen.d.mts +8 -6
  436. package/dist/plugins/wallet/codegen.mjs +4 -10
  437. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  438. package/dist/plugins/wallet/errors.mjs +1 -4
  439. package/dist/plugins/wallet/errors.mjs.map +1 -1
  440. package/dist/plugins/wallet/index.d.mts +3 -6
  441. package/dist/plugins/wallet/index.mjs +16 -21
  442. package/dist/plugins/wallet/index.mjs.map +1 -1
  443. package/dist/plugins/wallet/origin-policy.mjs +3 -3
  444. package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
  445. package/dist/plugins/wallet/pairing.mjs +6 -24
  446. package/dist/plugins/wallet/pairing.mjs.map +1 -1
  447. package/dist/plugins/wallet/protocol.mjs.map +1 -1
  448. package/dist/plugins/wallet/routable.mjs.map +1 -1
  449. package/dist/plugins/wallet/server.mjs +17 -24
  450. package/dist/plugins/wallet/server.mjs.map +1 -1
  451. package/dist/plugins/wallet/service.d.mts +2 -2
  452. package/dist/plugins/wallet/service.mjs +4 -15
  453. package/dist/plugins/wallet/service.mjs.map +1 -1
  454. package/dist/plugins/wallet/snapshot.mjs.map +1 -1
  455. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +1 -5
  456. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
  457. package/dist/plugins/walrus/codegen.d.mts +2 -13
  458. package/dist/plugins/walrus/codegen.mjs +93 -28
  459. package/dist/plugins/walrus/codegen.mjs.map +1 -1
  460. package/dist/plugins/walrus/deploy-paths.mjs +2 -3
  461. package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
  462. package/dist/plugins/walrus/deploy.mjs +26 -19
  463. package/dist/plugins/walrus/deploy.mjs.map +1 -1
  464. package/dist/plugins/walrus/errors.mjs +1 -8
  465. package/dist/plugins/walrus/errors.mjs.map +1 -1
  466. package/dist/plugins/walrus/faucet-strategy.mjs +15 -1
  467. package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
  468. package/dist/plugins/walrus/index.d.mts +13 -44
  469. package/dist/plugins/walrus/index.mjs +119 -122
  470. package/dist/plugins/walrus/index.mjs.map +1 -1
  471. package/dist/plugins/walrus/mode/known-deploy.mjs +6 -6
  472. package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
  473. package/dist/plugins/walrus/mode/local-cluster.d.mts +2 -2
  474. package/dist/plugins/walrus/mode/local-cluster.mjs +2 -10
  475. package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
  476. package/dist/plugins/walrus/plugin-key.mjs.map +1 -1
  477. package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
  478. package/dist/plugins/walrus/routable.mjs.map +1 -1
  479. package/dist/plugins/walrus/service.mjs.map +1 -1
  480. package/dist/plugins/walrus/snapshot.mjs +3 -3
  481. package/dist/plugins/walrus/snapshot.mjs.map +1 -1
  482. package/dist/plugins/walrus/storage-nodes.mjs +53 -14
  483. package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
  484. package/dist/plugins/walrus/wal-swap.mjs +4 -8
  485. package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
  486. package/dist/primitives/artifact-publisher.mjs.map +1 -1
  487. package/dist/primitives/cache.d.mts +1 -1
  488. package/dist/runtime/docker/client.mjs +47 -3
  489. package/dist/runtime/docker/client.mjs.map +1 -1
  490. package/dist/runtime/docker/container.mjs +37 -57
  491. package/dist/runtime/docker/container.mjs.map +1 -1
  492. package/dist/runtime/docker/errors.mjs.map +1 -1
  493. package/dist/runtime/docker/exec.mjs +2 -2
  494. package/dist/runtime/docker/exec.mjs.map +1 -1
  495. package/dist/runtime/docker/image.mjs +10 -11
  496. package/dist/runtime/docker/image.mjs.map +1 -1
  497. package/dist/runtime/docker/index.mjs +0 -1
  498. package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -1
  499. package/dist/runtime/docker/inventory.mjs +9 -9
  500. package/dist/runtime/docker/inventory.mjs.map +1 -1
  501. package/dist/runtime/docker/labels.mjs +12 -1
  502. package/dist/runtime/docker/labels.mjs.map +1 -1
  503. package/dist/runtime/docker/network.mjs +6 -6
  504. package/dist/runtime/docker/network.mjs.map +1 -1
  505. package/dist/runtime/docker/render-run-args.mjs.map +1 -1
  506. package/dist/runtime/docker/service.mjs +31 -45
  507. package/dist/runtime/docker/service.mjs.map +1 -1
  508. package/dist/runtime/docker/sweep.mjs +14 -64
  509. package/dist/runtime/docker/sweep.mjs.map +1 -1
  510. package/dist/runtime/docker/volume.mjs +1 -1
  511. package/dist/runtime/docker/volume.mjs.map +1 -1
  512. package/dist/runtime/docker/wrap.mjs +14 -2
  513. package/dist/runtime/docker/wrap.mjs.map +1 -1
  514. package/dist/substrate/brand.d.mts +2 -5
  515. package/dist/substrate/brand.mjs +3 -2
  516. package/dist/substrate/brand.mjs.map +1 -1
  517. package/dist/substrate/cross-process.mjs +2 -16
  518. package/dist/substrate/cross-process.mjs.map +1 -1
  519. package/dist/substrate/event-time.mjs +0 -2
  520. package/dist/substrate/event-time.mjs.map +1 -1
  521. package/dist/substrate/events.d.mts +46 -16
  522. package/dist/substrate/identity.d.mts +11 -5
  523. package/dist/substrate/manifest.d.mts +33 -26
  524. package/dist/substrate/manifest.mjs +8 -3
  525. package/dist/substrate/manifest.mjs.map +1 -1
  526. package/dist/substrate/options.d.mts +33 -4
  527. package/dist/substrate/plugin-ctx.d.mts +70 -0
  528. package/dist/substrate/plugin-ctx.mjs +66 -0
  529. package/dist/substrate/plugin-ctx.mjs.map +1 -0
  530. package/dist/substrate/plugin.d.mts +46 -38
  531. package/dist/substrate/plugin.mjs +13 -9
  532. package/dist/substrate/plugin.mjs.map +1 -1
  533. package/dist/substrate/projection.d.mts +1 -1
  534. package/dist/substrate/runtime/atomic-write.mjs +3 -4
  535. package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
  536. package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
  537. package/dist/substrate/runtime/cache/service.mjs +50 -20
  538. package/dist/substrate/runtime/cache/service.mjs.map +1 -1
  539. package/dist/substrate/runtime/config-validation.mjs.map +1 -1
  540. package/dist/substrate/runtime/control-plane/domain.mjs +34 -12
  541. package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -1
  542. package/dist/substrate/runtime/control-plane/service.mjs.map +1 -1
  543. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +14 -17
  544. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
  545. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
  546. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
  547. package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs.map +1 -1
  548. package/dist/substrate/runtime/cross-process/index.mjs +0 -2
  549. package/dist/substrate/runtime/cross-process/live-clock.mjs +1 -1
  550. package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -1
  551. package/dist/substrate/runtime/cross-process/liveness.mjs +11 -18
  552. package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
  553. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +2 -2
  554. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -1
  555. package/dist/substrate/runtime/cross-process/roster.mjs +17 -127
  556. package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
  557. package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -1
  558. package/dist/substrate/runtime/cross-process/stack-lock.mjs +29 -28
  559. package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
  560. package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
  561. package/dist/substrate/runtime/errors.mjs +1 -13
  562. package/dist/substrate/runtime/errors.mjs.map +1 -1
  563. package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -1
  564. package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -1
  565. package/dist/substrate/runtime/host-gateway.mjs.map +1 -1
  566. package/dist/substrate/runtime/host-tree-tar/index.mjs +15 -163
  567. package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
  568. package/dist/substrate/runtime/http-probe.d.mts +1 -3
  569. package/dist/substrate/runtime/http-probe.mjs.map +1 -1
  570. package/dist/substrate/runtime/index.mjs +1 -6
  571. package/dist/substrate/runtime/lease-broker/service.mjs +5 -23
  572. package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
  573. package/dist/substrate/runtime/lifecycle/dep-graph.mjs +3 -3
  574. package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
  575. package/dist/substrate/runtime/lifecycle/graph-input-id.d.mts +1 -0
  576. package/dist/substrate/runtime/lifecycle/graph-input-id.mjs +133 -0
  577. package/dist/substrate/runtime/lifecycle/graph-input-id.mjs.map +1 -0
  578. package/dist/substrate/runtime/lifecycle/index.mjs +2 -2
  579. package/dist/substrate/runtime/lifecycle/plugin-registry.d.mts +1 -1
  580. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +22 -1
  581. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
  582. package/dist/substrate/runtime/lifecycle/ready-gate.mjs +2 -15
  583. package/dist/substrate/runtime/lifecycle/ready-gate.mjs.map +1 -1
  584. package/dist/substrate/runtime/lifecycle/selective-restart.mjs +29 -25
  585. package/dist/substrate/runtime/lifecycle/selective-restart.mjs.map +1 -1
  586. package/dist/substrate/runtime/lifecycle/signals.mjs +1 -1
  587. package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
  588. package/dist/substrate/runtime/lifecycle/state-machine.mjs.map +1 -1
  589. package/dist/substrate/runtime/lifecycle/watch-attribution.d.mts +1 -1
  590. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs +0 -1
  591. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs.map +1 -1
  592. package/dist/substrate/runtime/managed-container.d.mts +5 -1
  593. package/dist/substrate/runtime/managed-container.mjs +8 -10
  594. package/dist/substrate/runtime/managed-container.mjs.map +1 -1
  595. package/dist/substrate/runtime/manifest/manifest.mjs +8 -30
  596. package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
  597. package/dist/substrate/runtime/mode-errors.d.mts +1 -2
  598. package/dist/substrate/runtime/mode-errors.mjs.map +1 -1
  599. package/dist/substrate/runtime/observability/cascade-formatter.mjs +1 -11
  600. package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
  601. package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -1
  602. package/dist/substrate/runtime/observability/index.d.mts +1 -1
  603. package/dist/substrate/runtime/observability/index.mjs +4 -6
  604. package/dist/substrate/runtime/observability/log-attrs.mjs +46 -0
  605. package/dist/substrate/runtime/observability/log-attrs.mjs.map +1 -0
  606. package/dist/substrate/runtime/observability/log-store.mjs.map +1 -1
  607. package/dist/substrate/runtime/observability/logger.mjs +3 -3
  608. package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
  609. package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -1
  610. package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
  611. package/dist/substrate/runtime/observability/process-lines.mjs.map +1 -1
  612. package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
  613. package/dist/substrate/runtime/observability/subprocess-capture.mjs +2 -2
  614. package/dist/substrate/runtime/observability/subprocess-capture.mjs.map +1 -1
  615. package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -1
  616. package/dist/substrate/runtime/paths.mjs +27 -9
  617. package/dist/substrate/runtime/paths.mjs.map +1 -1
  618. package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -1
  619. package/dist/substrate/runtime/port-broker/service.mjs +10 -27
  620. package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
  621. package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
  622. package/dist/substrate/runtime/probes.d.mts +2 -3
  623. package/dist/substrate/runtime/probes.mjs.map +1 -1
  624. package/dist/substrate/runtime/process-supervisor.mjs.map +1 -1
  625. package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
  626. package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
  627. package/dist/substrate/runtime/projection/update.mjs +87 -11
  628. package/dist/substrate/runtime/projection/update.mjs.map +1 -1
  629. package/dist/substrate/runtime/random-suffix.mjs.map +1 -1
  630. package/dist/substrate/runtime/reconcile/fs-plan.mjs +146 -0
  631. package/dist/substrate/runtime/reconcile/fs-plan.mjs.map +1 -0
  632. package/dist/substrate/runtime/reconcile/graph.mjs +19 -0
  633. package/dist/substrate/runtime/reconcile/graph.mjs.map +1 -0
  634. package/dist/substrate/runtime/reconcile/index.mjs +5 -0
  635. package/dist/substrate/runtime/reconcile/label.mjs +41 -0
  636. package/dist/substrate/runtime/reconcile/label.mjs.map +1 -0
  637. package/dist/substrate/runtime/reconcile/spec.mjs +15 -0
  638. package/dist/substrate/runtime/reconcile/spec.mjs.map +1 -0
  639. package/dist/substrate/runtime/retry-policy.d.mts +2 -3
  640. package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
  641. package/dist/substrate/runtime/routed-url.mjs.map +1 -1
  642. package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
  643. package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
  644. package/dist/substrate/runtime/stage-and-swap/index.mjs +49 -12
  645. package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
  646. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -1
  647. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
  648. package/dist/substrate/runtime/strategy-registry/service.mjs +29 -17
  649. package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
  650. package/dist/substrate/runtime/subnet-broker.mjs.map +1 -1
  651. package/dist/substrate/runtime/supervisor/acquire-node.mjs +188 -56
  652. package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -1
  653. package/dist/substrate/runtime/supervisor/background-tasks.mjs +79 -65
  654. package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -1
  655. package/dist/substrate/runtime/supervisor/command-loop.mjs +82 -23
  656. package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -1
  657. package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs +16 -0
  658. package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs.map +1 -0
  659. package/dist/substrate/runtime/supervisor/errors.mjs +11 -2
  660. package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -1
  661. package/dist/substrate/runtime/supervisor/index.mjs +2 -0
  662. package/dist/substrate/runtime/supervisor/shutdown.mjs +6 -6
  663. package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -1
  664. package/dist/substrate/runtime/supervisor/start-supervisor.mjs +60 -70
  665. package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -1
  666. package/dist/substrate/runtime/supervisor/state.d.mts +1 -1
  667. package/dist/substrate/runtime/supervisor/state.mjs +1 -1
  668. package/dist/substrate/runtime/supervisor/state.mjs.map +1 -1
  669. package/dist/substrate/runtime/supervisor/teardown.mjs +10 -9
  670. package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -1
  671. package/dist/substrate/runtime/supervisor/wiring.mjs +6 -20
  672. package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -1
  673. package/dist/substrate/runtime/tar/reader.mjs +216 -0
  674. package/dist/substrate/runtime/tar/reader.mjs.map +1 -0
  675. package/dist/substrate/runtime/typed-env.mjs.map +1 -1
  676. package/dist/substrate/versioned-doc-schema.mjs.map +1 -1
  677. package/dist/substrate/versioned-doc-sync.mjs +5 -5
  678. package/dist/substrate/versioned-doc-sync.mjs.map +1 -1
  679. package/dist/surfaces/cli/command-tree.mjs +49 -7
  680. package/dist/surfaces/cli/command-tree.mjs.map +1 -1
  681. package/dist/surfaces/cli/commands/config.mjs +1 -1
  682. package/dist/surfaces/cli/commands/config.mjs.map +1 -1
  683. package/dist/surfaces/cli/commands/confirm-node.mjs.map +1 -1
  684. package/dist/surfaces/cli/commands/confirm.mjs.map +1 -1
  685. package/dist/surfaces/cli/commands/doctor.mjs +1 -1
  686. package/dist/surfaces/cli/commands/doctor.mjs.map +1 -1
  687. package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -1
  688. package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
  689. package/dist/surfaces/cli/commands/prune.mjs +1 -1
  690. package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
  691. package/dist/surfaces/cli/commands/snapshot.mjs +1 -1
  692. package/dist/surfaces/cli/commands/snapshot.mjs.map +1 -1
  693. package/dist/surfaces/cli/commands/status.mjs +1 -1
  694. package/dist/surfaces/cli/commands/status.mjs.map +1 -1
  695. package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
  696. package/dist/surfaces/cli/commands/wipe.mjs +3 -4
  697. package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
  698. package/dist/surfaces/cli/envelope.mjs.map +1 -1
  699. package/dist/surfaces/cli/errors.mjs.map +1 -1
  700. package/dist/surfaces/cli/flags.mjs.map +1 -1
  701. package/dist/surfaces/cli/index.mjs +45 -14
  702. package/dist/surfaces/cli/index.mjs.map +1 -1
  703. package/dist/surfaces/cli/output.mjs.map +1 -1
  704. package/dist/surfaces/cli/sysexits.mjs.map +1 -1
  705. package/dist/surfaces/tui/app.mjs +0 -21
  706. package/dist/surfaces/tui/app.mjs.map +1 -1
  707. package/dist/surfaces/tui/dashboard.mjs +0 -48
  708. package/dist/surfaces/tui/dashboard.mjs.map +1 -1
  709. package/dist/surfaces/tui/display-derivation.mjs +80 -16
  710. package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
  711. package/dist/surfaces/tui/errors.mjs.map +1 -1
  712. package/dist/surfaces/tui/event-log.mjs +0 -9
  713. package/dist/surfaces/tui/event-log.mjs.map +1 -1
  714. package/dist/surfaces/tui/heartbeat.mjs.map +1 -1
  715. package/dist/surfaces/tui/index.mjs +2 -2
  716. package/dist/surfaces/tui/index.mjs.map +1 -1
  717. package/dist/surfaces/tui/input.mjs.map +1 -1
  718. package/dist/surfaces/tui/mode-detect.mjs.map +1 -1
  719. package/dist/surfaces/tui/mount-ink.mjs.map +1 -1
  720. package/dist/surfaces/tui/plain-renderer.mjs +26 -19
  721. package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
  722. package/dist/surfaces/tui/resource-table.mjs +8 -10
  723. package/dist/surfaces/tui/resource-table.mjs.map +1 -1
  724. package/images/postgres/Dockerfile +0 -1
  725. package/images/sui/Dockerfile +27 -54
  726. package/images/sui/entrypoint.sh +17 -179
  727. package/package.json +23 -15
  728. package/dashboard-ui/assets/index-Bmi1UtAg.js +0 -1356
  729. package/dashboard-ui/assets/index-D5EShVt4.js +0 -3
  730. package/dashboard-ui/assets/index-Deml9drg.css +0 -1
  731. package/dist/api/plugin-errors.d.mts +0 -7
  732. package/dist/api/plugin-errors.mjs +0 -10
  733. package/dist/api/plugin-errors.mjs.map +0 -1
  734. package/dist/cli/wirings/build-verb-layers.mjs +0 -42
  735. package/dist/cli/wirings/build-verb-layers.mjs.map +0 -1
  736. package/dist/contracts/capability-decl.d.mts +0 -40
  737. package/dist/orchestrators/built-in-plugin-layers.mjs +0 -54
  738. package/dist/orchestrators/built-in-plugin-layers.mjs.map +0 -1
  739. package/dist/orchestrators/run.mjs +0 -91
  740. package/dist/orchestrators/run.mjs.map +0 -1
  741. package/dist/orchestrators/runtime-composition.d.mts +0 -10
  742. package/dist/orchestrators/runtime-composition.mjs +0 -252
  743. package/dist/orchestrators/runtime-composition.mjs.map +0 -1
  744. package/dist/orchestrators/snapshot/capture-command.d.mts +0 -1
  745. package/dist/orchestrators/snapshot/capture-command.mjs +0 -25
  746. package/dist/orchestrators/snapshot/capture-command.mjs.map +0 -1
  747. package/dist/orchestrators/snapshot/pending-marker.mjs +0 -99
  748. package/dist/orchestrators/snapshot/pending-marker.mjs.map +0 -1
  749. package/dist/orchestrators/snapshot/recover-pending.mjs +0 -223
  750. package/dist/orchestrators/snapshot/recover-pending.mjs.map +0 -1
  751. package/dist/orchestrators/snapshot/state-document.d.mts +0 -1
  752. package/dist/orchestrators/snapshot/state-document.mjs +0 -58
  753. package/dist/orchestrators/snapshot/state-document.mjs.map +0 -1
  754. package/dist/plugins/account/spans.mjs +0 -16
  755. package/dist/plugins/account/spans.mjs.map +0 -1
  756. package/dist/plugins/account/variants/env.mjs +0 -24
  757. package/dist/plugins/account/variants/env.mjs.map +0 -1
  758. package/dist/plugins/account/variants/inline.mjs +0 -13
  759. package/dist/plugins/account/variants/inline.mjs.map +0 -1
  760. package/dist/plugins/account/variants/keystore.mjs +0 -91
  761. package/dist/plugins/account/variants/keystore.mjs.map +0 -1
  762. package/dist/plugins/action/spans.mjs +0 -11
  763. package/dist/plugins/action/spans.mjs.map +0 -1
  764. package/dist/plugins/coin/spans.mjs +0 -20
  765. package/dist/plugins/coin/spans.mjs.map +0 -1
  766. package/dist/plugins/deepbook/spans.mjs +0 -18
  767. package/dist/plugins/deepbook/spans.mjs.map +0 -1
  768. package/dist/plugins/faucet/spans.mjs +0 -12
  769. package/dist/plugins/faucet/spans.mjs.map +0 -1
  770. package/dist/plugins/package/spans.mjs +0 -14
  771. package/dist/plugins/package/spans.mjs.map +0 -1
  772. package/dist/plugins/postgres/codegen.mjs +0 -42
  773. package/dist/plugins/postgres/codegen.mjs.map +0 -1
  774. package/dist/plugins/postgres/connection.d.mts +0 -33
  775. package/dist/plugins/postgres/connection.mjs.map +0 -1
  776. package/dist/plugins/postgres/db-ensure.mjs.map +0 -1
  777. package/dist/plugins/postgres/errors.d.mts +0 -60
  778. package/dist/plugins/postgres/errors.mjs +0 -29
  779. package/dist/plugins/postgres/errors.mjs.map +0 -1
  780. package/dist/plugins/postgres/index.d.mts +0 -37
  781. package/dist/plugins/postgres/index.mjs +0 -68
  782. package/dist/plugins/postgres/index.mjs.map +0 -1
  783. package/dist/plugins/postgres/routable.mjs +0 -33
  784. package/dist/plugins/postgres/routable.mjs.map +0 -1
  785. package/dist/plugins/postgres/service.d.mts +0 -50
  786. package/dist/plugins/postgres/service.mjs +0 -179
  787. package/dist/plugins/postgres/service.mjs.map +0 -1
  788. package/dist/plugins/postgres/snapshot.mjs +0 -32
  789. package/dist/plugins/postgres/snapshot.mjs.map +0 -1
  790. package/dist/plugins/postgres/spans.mjs +0 -11
  791. package/dist/plugins/postgres/spans.mjs.map +0 -1
  792. package/dist/plugins/seal/spans.mjs +0 -18
  793. package/dist/plugins/seal/spans.mjs.map +0 -1
  794. package/dist/plugins/sui/spans.mjs +0 -17
  795. package/dist/plugins/sui/spans.mjs.map +0 -1
  796. package/dist/plugins/wallet/spans.mjs +0 -22
  797. package/dist/plugins/wallet/spans.mjs.map +0 -1
  798. package/dist/plugins/walrus/registry-publish.d.mts +0 -24
  799. package/dist/plugins/walrus/spans.mjs +0 -18
  800. package/dist/plugins/walrus/spans.mjs.map +0 -1
  801. package/dist/runtime/docker/logs.d.mts +0 -1
  802. package/dist/runtime/docker/logs.mjs +0 -34
  803. package/dist/runtime/docker/logs.mjs.map +0 -1
  804. package/dist/substrate/runtime/artifact-publisher/index.mjs +0 -86
  805. package/dist/substrate/runtime/artifact-publisher/index.mjs.map +0 -1
  806. package/dist/substrate/runtime/capability-sinks/index.d.mts +0 -1
  807. package/dist/substrate/runtime/capability-sinks/index.mjs +0 -3
  808. package/dist/substrate/runtime/capability-sinks/layer.d.mts +0 -1
  809. package/dist/substrate/runtime/capability-sinks/layer.mjs +0 -31
  810. package/dist/substrate/runtime/capability-sinks/layer.mjs.map +0 -1
  811. package/dist/substrate/runtime/capability-sinks/service.d.mts +0 -95
  812. package/dist/substrate/runtime/capability-sinks/service.mjs +0 -69
  813. package/dist/substrate/runtime/capability-sinks/service.mjs.map +0 -1
  814. package/dist/substrate/runtime/cross-process/lock.d.mts +0 -1
  815. package/dist/substrate/runtime/cross-process/lock.mjs +0 -23
  816. package/dist/substrate/runtime/cross-process/lock.mjs.map +0 -1
  817. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +0 -113
  818. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +0 -1
  819. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs +0 -31
  820. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +0 -1
  821. package/dist/substrate/runtime/observability/formatter-registry.d.mts +0 -1
  822. package/dist/substrate/runtime/observability/formatter-registry.mjs +0 -48
  823. package/dist/substrate/runtime/observability/formatter-registry.mjs.map +0 -1
  824. package/dist/substrate/runtime/observability/span-store.d.mts +0 -1
  825. package/dist/substrate/runtime/observability/span-store.mjs +0 -110
  826. package/dist/substrate/runtime/observability/span-store.mjs.map +0 -1
  827. package/dist/substrate/runtime/observability/spans.d.mts +0 -1
  828. package/dist/substrate/runtime/observability/spans.mjs +0 -87
  829. package/dist/substrate/runtime/observability/spans.mjs.map +0 -1
  830. package/dist/substrate/runtime/projection/index.mjs +0 -4
  831. package/dist/substrate/runtime/projection/persisted.mjs +0 -213
  832. package/dist/substrate/runtime/projection/persisted.mjs.map +0 -1
  833. package/dist/substrate/runtime/scoped-multimap/service.mjs +0 -52
  834. package/dist/substrate/runtime/scoped-multimap/service.mjs.map +0 -1
  835. package/dist/substrate/runtime/scoped-ref-map/index.mjs +0 -2
  836. package/dist/substrate/runtime/scoped-ref-map/service.mjs +0 -83
  837. package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +0 -1
  838. package/dist/substrate/runtime/state-store/index.mjs +0 -3
  839. package/dist/substrate/runtime/state-store/schema.d.mts +0 -1
  840. package/dist/substrate/runtime/state-store/schema.mjs +0 -41
  841. package/dist/substrate/runtime/state-store/schema.mjs.map +0 -1
  842. package/dist/substrate/runtime/state-store/service.d.mts +0 -1
  843. package/dist/substrate/runtime/state-store/service.mjs +0 -145
  844. package/dist/substrate/runtime/state-store/service.mjs.map +0 -1
  845. package/dist/substrate/runtime/sui-execute/index.mjs.map +0 -1
  846. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +0 -1
  847. package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +0 -1
  848. package/dist/substrate/runtime/sui-move-build/index.mjs.map +0 -1
  849. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +0 -151
  850. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +0 -1
  851. package/dist/substrate/state-store.d.mts +0 -1
  852. /package/dist/orchestrators/{snapshot/pending-marker.d.mts → codegen/id-config.d.mts} +0 -0
  853. /package/dist/orchestrators/snapshot/{recover-pending.d.mts → interrupted-restore.d.mts} +0 -0
  854. /package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.d.mts +0 -0
  855. /package/dist/plugins/{postgres/snapshot.d.mts → sui/ledger/object-ref.d.mts} +0 -0
  856. /package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.d.mts +0 -0
  857. /package/dist/substrate/runtime/{state-store/index.d.mts → reconcile/graph.d.mts} +0 -0
  858. /package/dist/substrate/runtime/{cross-process/snapshot-reservation.d.mts → supervisor/contribution-dispatcher.d.mts} +0 -0
@@ -1,11 +1,10 @@
1
- import { SpanAttr } from "../observability/spans.mjs";
2
- import { versionedDocSchema } from "../../versioned-doc-schema.mjs";
3
- import { selfPid } from "./self-pid.mjs";
4
1
  import { DEFAULT_SWEEP_POLICY, RosterDocumentSchema } from "../../cross-process.mjs";
2
+ import { selfPid } from "./self-pid.mjs";
5
3
  import { readVersionedDocumentSync, writeVersionedDocumentSync } from "../../versioned-doc-sync.mjs";
6
- import { LivenessProbeScope, isPidAlive, layerLivenessProbeScope, ownHolder, processStartTime } from "./liveness.mjs";
4
+ import { LivenessProbeScope, layerLivenessProbeScope, ownHolder, processStartTime } from "./liveness.mjs";
7
5
  import { acquireStackLock } from "./stack-lock.mjs";
8
- import { Data, Effect, Schema } from "effect";
6
+ import { LogAttr } from "../observability/log-attrs.mjs";
7
+ import { Data, Effect } from "effect";
9
8
  import { hostname } from "node:os";
10
9
  //#region src/substrate/runtime/cross-process/roster.ts
11
10
  var RosterCorruptError = class extends Data.TaggedError("RosterCorruptError") {};
@@ -29,11 +28,11 @@ const ROSTER_DOC_ERRORS = {
29
28
  * Tolerates a missing file but NOT a malformed file (a malformed
30
29
  * roster surfaces a typed error so callers can decide whether to
31
30
  * abandon or rewrite). */
32
- const readRoster = (path) => readVersionedDocumentSync(path, RosterDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_ROSTER).pipe(Effect.withSpan("cross-process.roster.read"));
31
+ const readRoster = (path) => readVersionedDocumentSync(path, RosterDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_ROSTER);
33
32
  /** Atomic write: route through the canonical sync primitive. The
34
33
  * roster's mutations are all under `stack.lock`, so the non-yielding
35
34
  * sync surface is correct here — and it shares ONE owner of the
36
- * tempfile dance with state-store, cache, and manifest. */
35
+ * tempfile dance with cache and manifest. */
37
36
  const atomicWriteRoster = (path, doc) => writeVersionedDocumentSync(path, doc, ROSTER_DOC_ERRORS);
38
37
  /** Walk the roster's holders and drop those that fail the liveness
39
38
  * test AND whose heartbeats are older than `staleAfterMillis`.
@@ -48,13 +47,12 @@ const sweepStaleHolders = Effect.fn("cross-process.roster.sweep")(function* (doc
48
47
  const probe = yield* LivenessProbeScope;
49
48
  const survivors = [];
50
49
  const evicted = [];
51
- const ownHost = hostname();
52
50
  for (const holder of doc.holders) {
53
51
  if (!(now - holder.heartbeatAt > policy.staleAfterMillis)) {
54
52
  survivors.push(holder);
55
53
  continue;
56
54
  }
57
- if ((yield* probe.probeHolderLiveness(holder, ownHost).pipe(Effect.catch(() => Effect.succeed("alive")))) === "dead") evicted.push(holder);
55
+ if ((yield* probe.probeHolderLiveness(holder).pipe(Effect.catch(() => Effect.succeed("alive")))) === "dead") evicted.push(holder);
58
56
  else survivors.push(holder);
59
57
  }
60
58
  return {
@@ -65,22 +63,10 @@ const sweepStaleHolders = Effect.fn("cross-process.roster.sweep")(function* (doc
65
63
  evicted
66
64
  };
67
65
  }, 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
66
  /** Match a roster holder against THIS process's identity.
81
67
  *
82
- * Liveness elsewhere (`checkHolderLiveness`, `isContainerClaimLive`)
83
- * uses `(pid, hostname, startTime)` — PID alone is insufficient on
68
+ * Liveness elsewhere (`checkHolderLiveness`) uses
69
+ * `(pid, hostname, startTime)` — PID alone is insufficient on
84
70
  * long-uptime hosts where the kernel can recycle PIDs. The roster
85
71
  * mutators (`heartbeat`, `release`, `setIntent`) must apply the same
86
72
  * triple match so a recycled-PID peer's entry is never silently
@@ -88,8 +74,8 @@ const requireClaimsPath = (paths) => {
88
74
  *
89
75
  * `startTime` is the FNV-1a hash of `ps -o lstart` (see
90
76
  * `liveness.processStartTime`). A `null` probe (process gone, or
91
- * exotic platform) skips the start-time check — same conservative
92
- * policy as `isContainerClaimLive`. */
77
+ * exotic platform) skips the start-time check — the same conservative
78
+ * policy `checkHolderLiveness` applies. */
93
79
  const isOwnEntry = (h, ownPid, ownHost, ownStartTime) => {
94
80
  if (h.pid !== ownPid || h.hostname !== ownHost) return false;
95
81
  if (ownStartTime === null || h.startTime === null) return true;
@@ -111,10 +97,6 @@ const withStackLock = (paths, body) => Effect.scoped(Effect.gen(function* () {
111
97
  * Returns the swept document, our holder entry, and any evicted peers.
112
98
  */
113
99
  const claim = (paths, intent = "normal", policy = DEFAULT_SWEEP_POLICY) => withStackLock(paths, Effect.gen(function* () {
114
- yield* Effect.annotateCurrentSpan({
115
- "devstack.roster.path": paths.rosterFile,
116
- "devstack.roster.intent": intent
117
- });
118
100
  const { swept, evicted } = yield* sweepStaleHolders(yield* readRoster(paths.rosterFile).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_ROSTER))), policy);
119
101
  const self = { ...ownHolder(intent) };
120
102
  const next = {
@@ -128,7 +110,7 @@ const claim = (paths, intent = "normal", policy = DEFAULT_SWEEP_POLICY) => withS
128
110
  evicted,
129
111
  soleHolder: swept.holders.length === 0
130
112
  };
131
- })).pipe(Effect.withSpan("cross-process.roster.claim"));
113
+ }));
132
114
  /**
133
115
  * Heartbeat protocol — refresh this process's `heartbeatAt`. Under the
134
116
  * stack lock to keep mutation atomic. Architecture § Heartbeat
@@ -154,7 +136,7 @@ const heartbeat = (paths, ownPid = selfPid()) => withStackLock(paths, Effect.gen
154
136
  })
155
137
  };
156
138
  if (touched) yield* atomicWriteRoster(paths.rosterFile, next);
157
- })).pipe(Effect.withSpan("cross-process.roster.heartbeat"));
139
+ }));
158
140
  /**
159
141
  * Release protocol — architecture § Release protocol.
160
142
  *
@@ -175,7 +157,7 @@ const release = (paths, ownPid = selfPid()) => withStackLock(paths, Effect.gen(f
175
157
  roster: next,
176
158
  lastLeaver: remaining.length === 0
177
159
  };
178
- })).pipe(Effect.withSpan("cross-process.roster.release"));
160
+ }));
179
161
  /** Background heartbeat fiber. Wakes every `intervalMillis` (default
180
162
  * matches `DEFAULT_SWEEP_POLICY.heartbeatIntervalMillis`) and refreshes
181
163
  * this process's `heartbeatAt`.
@@ -186,104 +168,12 @@ const heartbeatFiber = (paths, intervalMillis = DEFAULT_SWEEP_POLICY.heartbeatIn
186
168
  while (true) {
187
169
  yield* Effect.sleep(`${intervalMillis} millis`);
188
170
  yield* heartbeat(paths).pipe(Effect.catch((err) => Effect.logWarning("roster heartbeat failed").pipe(Effect.annotateLogs({
189
- [SpanAttr.rosterHeartbeatIntervalMs]: intervalMillis,
190
- [SpanAttr.errorCause]: String(err)
171
+ [LogAttr.rosterHeartbeatIntervalMs]: intervalMillis,
172
+ [LogAttr.errorCause]: String(err)
191
173
  }))));
192
174
  }
193
- }).pipe(Effect.withSpan("cross-process.roster.heartbeatFiber"));
194
- const ContainerClaimSchema = Schema.Struct({
195
- containerKey: Schema.String,
196
- pid: Schema.Number,
197
- startTime: Schema.optional(Schema.Number),
198
- hostname: Schema.String,
199
- claimedAt: Schema.Number
200
175
  });
201
- const ContainerClaimDocumentSchema = versionedDocSchema(1, { claims: Schema.Array(ContainerClaimSchema) });
202
- const EMPTY_CLAIMS = {
203
- version: 1,
204
- claims: []
205
- };
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()) => {
219
- if (claim.hostname !== ownHost) return true;
220
- if (!isPidAlive(claim.pid)) return false;
221
- if (claim.startTime === void 0) return true;
222
- const probedStart = probeStartTime(claim.pid);
223
- if (probedStart === null) return true;
224
- return probedStart === claim.startTime;
225
- };
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);
239
- /** Read the container-claim ledger. */
240
- const readClaims = (paths) => Effect.suspend(() => readVersionedDocumentSync(requireClaimsPath(paths), ContainerClaimDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_CLAIMS)).pipe(Effect.withSpan("cross-process.roster.readClaims"));
241
- /** Record that this process now claims `containerKey`. No-op if
242
- * already claimed by this process. */
243
- const addClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(function* () {
244
- const path = requireClaimsPath(paths);
245
- const current = yield* readClaims(paths).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_CLAIMS)));
246
- const live = yield* liveContainerClaims(current);
247
- const ownPid = selfPid();
248
- const ownStartTime = processStartTime(ownPid) ?? void 0;
249
- const ownHost = hostname();
250
- if (live.claims.some((c) => isOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime))) {
251
- if (live.claims.length !== current.claims.length) yield* writeClaims(path, live);
252
- return;
253
- }
254
- yield* writeClaims(path, {
255
- version: 1,
256
- claims: [...live.claims, {
257
- containerKey,
258
- pid: ownPid,
259
- ...ownStartTime === void 0 ? {} : { startTime: ownStartTime },
260
- hostname: ownHost,
261
- claimedAt: Date.now()
262
- }]
263
- });
264
- })).pipe(Effect.withSpan("cross-process.roster.addClaim"));
265
- /** Release this process's claim on `containerKey`. Returns whether
266
- * the container has zero remaining claims AFTER this release — the
267
- * "last-leaver" signal that authorizes a teardown.
268
- *
269
- * Architecture § Cross-process safety protocol: "when a process
270
- * releases its last claim and confirms no other process holds the
271
- * container, it may tear down." */
272
- const removeClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(function* () {
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;
277
- const ownHost = hostname();
278
- const remaining = live.claims.filter((c) => !isOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime));
279
- const stillClaimedByPeer = remaining.some((c) => c.containerKey === containerKey);
280
- yield* writeClaims(path, {
281
- version: 1,
282
- claims: remaining
283
- });
284
- return { lastClaimReleased: !stillClaimedByPeer };
285
- })).pipe(Effect.withSpan("cross-process.roster.removeClaim"));
286
176
  //#endregion
287
- export { RosterCorruptError, RosterIoError, addClaim, claim, heartbeat, heartbeatFiber, readClaims, readRoster, release, removeClaim, sweepStaleHolders };
177
+ export { RosterCorruptError, RosterIoError, claim, heartbeat, heartbeatFiber, readRoster, release, sweepStaleHolders };
288
178
 
289
179
  //# 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 { 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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"}
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 } 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 { LogAttr } from '../observability/log-attrs.ts';\nimport { readVersionedDocumentSync, writeVersionedDocumentSync } from '../../versioned-doc-sync.ts';\nimport { selfPid } from './self-pid.ts';\nimport { acquireStackLock } from './stack-lock.ts';\nimport {\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);\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 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\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)\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}\n\n/** Match a roster holder against THIS process's identity.\n *\n * Liveness elsewhere (`checkHolderLiveness`) uses\n * `(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 — the same conservative\n * policy `checkHolderLiveness` applies. */\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\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);\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);\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);\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);\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[LogAttr.rosterHeartbeatIntervalMs]: intervalMillis,\n\t\t\t\t\t\t\t[LogAttr.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});\n"],"mappings":"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"}
@@ -1 +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
+ {"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, stack-lock,\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,4 +1,3 @@
1
- import { SpanAttr } from "../observability/spans.mjs";
2
1
  import "../../cross-process.mjs";
3
2
  import { parseVersionedDocumentBodyOrNull } from "../../versioned-doc-sync.mjs";
4
3
  import { underLiveClock } from "./live-clock.mjs";
@@ -41,15 +40,16 @@ const DEFAULT_ACQUIRE_TIMEOUT_MILLIS = 5e3;
41
40
  const INITIAL_BACKOFF_MILLIS = 25;
42
41
  const MAX_BACKOFF_MILLIS = 200;
43
42
  /**
44
- * Sync attempt at O_EXCL-create. Returns whether we own the lock now.
43
+ * Sync attempt at O_EXCL-create with the lock's JSON body. Returns
44
+ * whether we own the file now; on EEXIST it probes the existing body
45
+ * through `parseLockBody`.
45
46
  *
46
47
  * Effect-platform's FileSystem doesn't expose a sync `open` shape, but
47
48
  * the critical-section discipline says we need a non-blocking attempt
48
49
  * inside a retry loop. Falling through to Node sync APIs here is the
49
50
  * cleanest path; the rest of the substrate stays Effect-native.
50
51
  */
51
- const tryAcquireSync = (path) => {
52
- const body = ownHolder();
52
+ const tryAcquireSync = (path, body) => {
53
53
  mkdirSync(dirname(path), { recursive: true });
54
54
  try {
55
55
  writeFileSync(path, JSON.stringify(body), { flag: "wx" });
@@ -74,25 +74,30 @@ const tryAcquireSync = (path) => {
74
74
  }
75
75
  };
76
76
  /**
77
- * Acquire `stack.lock` as a scoped resource. The lock is released
78
- * when the surrounding Scope closes.
77
+ * Acquire `stack.lock` as a scoped resource. The lock is released when
78
+ * the surrounding Scope closes (the unlink finalizer is registered INSIDE
79
+ * this scope, so every caller — roster claim/release/heartbeat, the
80
+ * snapshot bounce, command-channel append — releases on scope close,
81
+ * never bypassed). Yields a `void` resource — the caller MUST keep its
82
+ * Scope tight; the architecture forbids holding the lock across long
83
+ * operations.
79
84
  *
80
- * Retry loop: exponential backoff up to `timeoutMillis` (default 5s).
81
- * On every miss, probe the holder's liveness — if it's dead, reclaim
82
- * by unlinking and looping. The reclaim path is the architecture's
83
- * "stale lock" handling same PID + start-time semantics as the
84
- * roster sweep, so a process that crashed under the lock never blocks
85
- * the next peer indefinitely.
86
- *
87
- * IMPORTANT: this primitive yields a `void` resource. The caller MUST
88
- * keep its Scope tight — the architecture forbids holding the lock
89
- * across long operations.
85
+ * The O_EXCL retry loop: exponential backoff up to `timeoutMillis`
86
+ * (default 5s). On every miss, probe the holder's liveness — dead holders
87
+ * are reclaimed (unlink + loop); an unparseable body is reclaimed only
88
+ * through the shared re-stat guard once it ages past
89
+ * `DEFAULT_SWEEP_POLICY.staleAfterMillis`. Backoff resets on holder
90
+ * change and on reclaim. The reclaim path is the architecture's "stale
91
+ * lock" handling — same PID + start-time semantics as the roster sweep,
92
+ * so a process that crashed under the lock never blocks the next peer
93
+ * indefinitely.
90
94
  */
91
95
  const acquireStackLock = (path, timeoutMillis = DEFAULT_ACQUIRE_TIMEOUT_MILLIS) => Effect.gen(function* () {
92
- yield* Effect.annotateCurrentSpan({
93
- [SpanAttr.stackLockPath]: path,
94
- [SpanAttr.stackLockTimeoutMillis]: timeoutMillis
95
- });
96
+ const addUnlinkFinalizer = Effect.addFinalizer(() => Effect.sync(() => {
97
+ try {
98
+ unlinkSync(path);
99
+ } catch {}
100
+ }));
96
101
  const startedAt = Date.now();
97
102
  let backoff = INITIAL_BACKOFF_MILLIS;
98
103
  let lastHolder = null;
@@ -105,22 +110,18 @@ const acquireStackLock = (path, timeoutMillis = DEFAULT_ACQUIRE_TIMEOUT_MILLIS)
105
110
  holder: lastHolder
106
111
  }));
107
112
  const attempt = yield* Effect.try({
108
- try: () => tryAcquireSync(path),
113
+ try: () => tryAcquireSync(path, ownHolder()),
109
114
  catch: (cause) => new StackLockIoError({
110
115
  path,
111
116
  cause
112
117
  })
113
118
  });
114
119
  if (attempt.ok) {
115
- yield* Effect.addFinalizer(() => Effect.sync(() => {
116
- try {
117
- unlinkSync(path);
118
- } catch {}
119
- }));
120
+ yield* addUnlinkFinalizer;
120
121
  return;
121
122
  }
122
123
  lastHolder = attempt.holder;
123
- const currentHolderPid = lastHolder?.pid ?? null;
124
+ const currentHolderPid = lastHolder !== null ? lastHolder.pid : null;
124
125
  if (currentHolderPid !== prevHolderPid) {
125
126
  backoff = INITIAL_BACKOFF_MILLIS;
126
127
  prevHolderPid = currentHolderPid;
@@ -156,7 +157,7 @@ const acquireStackLock = (path, timeoutMillis = DEFAULT_ACQUIRE_TIMEOUT_MILLIS)
156
157
  yield* underLiveClock(Effect.sleep(`${backoff} millis`));
157
158
  backoff = Math.min(backoff * 2, MAX_BACKOFF_MILLIS);
158
159
  }
159
- }).pipe(Effect.withSpan("cross-process.stack-lock.acquire"));
160
+ });
160
161
  //#endregion
161
162
  export { DEFAULT_ACQUIRE_TIMEOUT_MILLIS, StackLockIoError, StackLockTimeoutError, acquireStackLock };
162
163
 
@@ -1 +1 @@
1
- {"version":3,"file":"stack-lock.mjs","names":[],"sources":["../../../../src/substrate/runtime/cross-process/stack-lock.ts"],"sourcesContent":["// `stack.lock` — O_EXCL exclusive lock for short critical sections.\n//\n// Architecture § Cross-process safety protocol § \"What is locked\":\n// - `stack.lock` — OS-advisory exclusive lock file. Used only for\n// short critical sections (roster mutations, snapshot reservation).\n// Acquired via `flock(LOCK_EX)` on Unix and `LockFileEx` on Windows;\n// never held across a long operation.\n//\n// The implementation here uses a portable O_EXCL-create dance — the\n// same approach the legacy `engine/file-lock.ts` proved out — because\n// `flock` isn't available cross-platform out of `node:fs`. The\n// architecture's wording (\"OS-advisory exclusive lock file\") covers\n// both `flock` and `O_EXCL`-with-pid-body; the latter is what every\n// portable POSIX tool reaches for when `flock` isn't on the table.\n//\n// Discipline:\n// - The lock is held BRIEFLY — within a single Effect.scoped block\n// that mutates the roster or the snapshot reservation.\n// - Stale locks (owner crashed under the lock) are reclaimed via the\n// PID + start-time liveness check before the acquire reattempts.\n// - Acquire retries with exponential backoff up to 5 seconds total\n// (architecture § Claim protocol step 1).\n\nimport { mkdirSync, unlinkSync, readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nimport { Data, Effect, Schema, Scope } from 'effect';\n\nimport { type RosterHolder } from '../../cross-process.ts';\nimport { parseVersionedDocumentBodyOrNull } from '../../versioned-doc-sync.ts';\nimport { SpanAttr } from '../observability/spans.ts';\nimport { underLiveClock } from './live-clock.ts';\nimport { checkHolderLiveness, ownHolder } from './liveness.ts';\nimport { reclaimUnparseableStaleFile } from './reclaim-stale-file.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\n/** Tagged failure: acquire window elapsed and the lock is still held.\n * Carries the in-disk body (if parseable) so the caller can name the\n * blocking peer in user-facing error reporting. */\nexport class StackLockTimeoutError extends Data.TaggedError('StackLockTimeoutError')<{\n\treadonly path: string;\n\treadonly waitedMillis: number;\n\treadonly holder: RosterHolder | null;\n}> {}\n\n/** Tagged failure: I/O error other than EEXIST during acquire. */\nexport class StackLockIoError extends Data.TaggedError('StackLockIoError')<{\n\treadonly path: string;\n\treadonly cause: unknown;\n}> {}\n\nexport type StackLockError = StackLockTimeoutError | StackLockIoError;\n\n// -----------------------------------------------------------------------------\n// Codec\n// -----------------------------------------------------------------------------\n\n/** The on-disk body is the same `RosterHolder` shape — re-using the schema\n * means roster sweep + stack-lock reclaim see the same fields.\n *\n * `startTime` mirrors `RosterHolderSchema`: `number | null` so an\n * unprobable platform's lock body round-trips cleanly through the\n * decoder. The shared liveness predicate honors null conservatively. */\nconst StackLockBodySchema = Schema.Struct({\n\tpid: Schema.Number,\n\tstartTime: Schema.NullOr(Schema.Number),\n\thostname: Schema.String,\n\tclaimedAt: Schema.Number,\n\theartbeatAt: Schema.Number,\n\tintent: Schema.Literals(['normal', 'snapshot']),\n});\n\nconst parseLockBody = (raw: string): RosterHolder | null =>\n\tparseVersionedDocumentBodyOrNull(raw, StackLockBodySchema, 'stack.lock');\n\n// -----------------------------------------------------------------------------\n// Acquire / release\n// -----------------------------------------------------------------------------\n\n/** Default acquire timeout — architecture § Claim protocol step 1\n * (\"Block up to 5 seconds; if unavailable, retry with backoff\"). */\nexport const DEFAULT_ACQUIRE_TIMEOUT_MILLIS = 5_000;\n\n/** Per-attempt initial wait. Doubles each retry up to the cap. The\n * backoff also resets whenever the holder identity changes — see the\n * reclaim loop comment for the contention story (review fix phase\n * 22f). The cap is intentionally tight (200ms) so peers react quickly\n * to a release; combined with the O_EXCL-arbitrated retry on reclaim,\n * no staggering jitter is needed. */\nconst INITIAL_BACKOFF_MILLIS = 25;\nconst MAX_BACKOFF_MILLIS = 200;\n\n/**\n * Sync attempt at O_EXCL-create. Returns whether we own the lock now.\n *\n * Effect-platform's FileSystem doesn't expose a sync `open` shape, but\n * the critical-section discipline says we need a non-blocking attempt\n * inside a retry loop. Falling through to Node sync APIs here is the\n * cleanest path; the rest of the substrate stays Effect-native.\n */\nconst tryAcquireSync = (\n\tpath: string,\n): { readonly ok: true } | { readonly ok: false; readonly holder: RosterHolder | null } => {\n\tconst body = ownHolder();\n\t// The lock's parent directory may not exist on first-claim of a\n\t// fresh runtime root — devstack's `<runtimeRoot>/stacks/<stack>/` is\n\t// the substrate's responsibility to bring into being, but no other\n\t// L0 subsystem touches the disk until the first claim. `mkdir -p`\n\t// here is idempotent and harmless on the warm path; it transforms\n\t// the otherwise-fatal ENOENT-during-write into a clean acquire.\n\t// Architecture § Cross-process safety protocol: stack root is\n\t// created by whichever subsystem first reaches for a path under it.\n\tmkdirSync(dirname(path), { recursive: true });\n\ttry {\n\t\twriteFileSync(path, JSON.stringify(body), { flag: 'wx' });\n\t\treturn { ok: true };\n\t} catch (err) {\n\t\tconst code = (err as NodeJS.ErrnoException).code;\n\t\tif (code !== 'EEXIST') {\n\t\t\tthrow err;\n\t\t}\n\t\t// Peer holds it (or stale). Probe the existing body.\n\t\tif (!existsSync(path)) {\n\t\t\t// Race: holder unlinked between our EEXIST and the existsSync.\n\t\t\t// Retry on the next loop iteration.\n\t\t\treturn { ok: false, holder: null };\n\t\t}\n\t\ttry {\n\t\t\tconst raw = readFileSync(path, 'utf8');\n\t\t\treturn { ok: false, holder: parseLockBody(raw) };\n\t\t} catch {\n\t\t\treturn { ok: false, holder: null };\n\t\t}\n\t}\n};\n\n/**\n * Acquire `stack.lock` as a scoped resource. The lock is released\n * when the surrounding Scope closes.\n *\n * Retry loop: exponential backoff up to `timeoutMillis` (default 5s).\n * On every miss, probe the holder's liveness — if it's dead, reclaim\n * by unlinking and looping. The reclaim path is the architecture's\n * \"stale lock\" handling — same PID + start-time semantics as the\n * roster sweep, so a process that crashed under the lock never blocks\n * the next peer indefinitely.\n *\n * IMPORTANT: this primitive yields a `void` resource. The caller MUST\n * keep its Scope tight — the architecture forbids holding the lock\n * across long operations.\n */\nexport const acquireStackLock = (\n\tpath: string,\n\ttimeoutMillis: number = DEFAULT_ACQUIRE_TIMEOUT_MILLIS,\n): Effect.Effect<void, StackLockError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t[SpanAttr.stackLockPath]: path,\n\t\t\t[SpanAttr.stackLockTimeoutMillis]: timeoutMillis,\n\t\t});\n\t\tconst startedAt = Date.now();\n\t\tlet backoff = INITIAL_BACKOFF_MILLIS;\n\t\tlet lastHolder: RosterHolder | null = null;\n\t\tlet prevHolderPid: number | null = null;\n\t\twhile (true) {\n\t\t\tconst elapsed = Date.now() - startedAt;\n\t\t\tif (elapsed > timeoutMillis) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew StackLockTimeoutError({ path, waitedMillis: elapsed, holder: lastHolder }),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst attempt = yield* Effect.try({\n\t\t\t\ttry: () => tryAcquireSync(path),\n\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t});\n\t\t\tif (attempt.ok) {\n\t\t\t\t// Register a finalizer that unlinks the lock on scope close.\n\t\t\t\tyield* Effect.addFinalizer(() =>\n\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tunlinkSync(path);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Already gone — ok. Crash-during-release is handled\n\t\t\t\t\t\t\t// by the next peer's stale-PID reclaim.\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlastHolder = attempt.holder;\n\t\t\t// Reset the backoff whenever the holder identity changes —\n\t\t\t// a new holder means the previous one released, and our\n\t\t\t// long-saturated backoff (driven by waiting on the prior\n\t\t\t// holder) is now stale. Without this, late waiters under\n\t\t\t// heavy contention saturate at MAX_BACKOFF_MILLIS while\n\t\t\t// each peer holds the lock only briefly, exhausting the\n\t\t\t// claim budget before they can win (review fix phase 22f\n\t\t\t// reclaim-stress reproducer caught the case).\n\t\t\tconst currentHolderPid = lastHolder?.pid ?? null;\n\t\t\tif (currentHolderPid !== prevHolderPid) {\n\t\t\t\tbackoff = INITIAL_BACKOFF_MILLIS;\n\t\t\t\tprevHolderPid = currentHolderPid;\n\t\t\t}\n\t\t\t// Reclaim if the holder is dead OR the lock body is\n\t\t\t// unparseable AND old enough to be presumed-abandoned. The\n\t\t\t// unparseable case covers a peer that died mid-write: the\n\t\t\t// PID liveness check has nothing to consult, so the only\n\t\t\t// signal is the file's mtime falling outside the staleness\n\t\t\t// window (`DEFAULT_SWEEP_POLICY.staleAfterMillis`, shared\n\t\t\t// with the roster sweep). Without this branch, a mid-write\n\t\t\t// crash keeps every peer blocked for the full 5s claim\n\t\t\t// window — see review fix phase 22f (cross-process).\n\t\t\tlet reclaimed = false;\n\t\t\tif (lastHolder !== null) {\n\t\t\t\t// Dead-PID reclaim: gated on a liveness probe, so the\n\t\t\t\t// holder cannot have written a fresh body — a plain\n\t\t\t\t// unlink is safe here (no TOCTOU). An alive holder falls\n\t\t\t\t// through to the backoff below.\n\t\t\t\tconst status = yield* checkHolderLiveness(lastHolder).pipe(\n\t\t\t\t\tEffect.catch(() => Effect.succeed('alive' as const)),\n\t\t\t\t);\n\t\t\t\tif (status === 'dead') {\n\t\t\t\t\tyield* Effect.try({\n\t\t\t\t\t\ttry: () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tunlinkSync(path);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Race with another reclaimer — ok.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t\t\t});\n\t\t\t\t\treclaimed = true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Unparseable body: reclaim ONLY through the re-stat\n\t\t\t\t// guard. A bare mtime read + unlink races a competitor\n\t\t\t\t// who legitimately reclaims the garbage and writes a\n\t\t\t\t// fresh valid O_EXCL body in the window — the unlink\n\t\t\t\t// would clobber that LIVE lock (two simultaneous\n\t\t\t\t// holders). `reclaimUnparseableStaleFile` re-confirms the\n\t\t\t\t// file is still the same stale, unparseable inode\n\t\t\t\t// immediately before unlinking; any other outcome leaves\n\t\t\t\t// the file untouched and we fall through to back off.\n\t\t\t\tconst outcome = yield* Effect.try({\n\t\t\t\t\ttry: () => reclaimUnparseableStaleFile(path, parseLockBody),\n\t\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t\t});\n\t\t\t\treclaimed = outcome === 'reclaimed';\n\t\t\t}\n\t\t\tif (reclaimed) {\n\t\t\t\t// O_EXCL atomicity alone arbitrates the post-reclaim\n\t\t\t\t// race; reset the backoff so the contest starts fresh\n\t\t\t\t// (the prior growth was driven by a now-evicted dead\n\t\t\t\t// holder).\n\t\t\t\tbackoff = INITIAL_BACKOFF_MILLIS;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Peer holds an alive lock — back off exponentially.\n\t\t\tyield* underLiveClock(Effect.sleep(`${backoff} millis`));\n\t\t\tbackoff = Math.min(backoff * 2, MAX_BACKOFF_MILLIS);\n\t\t}\n\t}).pipe(Effect.withSpan('cross-process.stack-lock.acquire'));\n"],"mappings":";;;;;;;;;;;;;AA0CA,IAAa,wBAAb,cAA2C,KAAK,YAAY,wBAAwB,CAIjF;;AAGH,IAAa,mBAAb,cAAsC,KAAK,YAAY,mBAAmB,CAGvE;;;;;;;AAcH,MAAM,sBAAsB,OAAO,OAAO;CACzC,KAAK,OAAO;CACZ,WAAW,OAAO,OAAO,OAAO,OAAO;CACvC,UAAU,OAAO;CACjB,WAAW,OAAO;CAClB,aAAa,OAAO;CACpB,QAAQ,OAAO,SAAS,CAAC,UAAU,WAAW,CAAC;CAC/C,CAAC;AAEF,MAAM,iBAAiB,QACtB,iCAAiC,KAAK,qBAAqB,aAAa;;;AAQzE,MAAa,iCAAiC;;;;;;;AAQ9C,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;;;;;;;;;AAU3B,MAAM,kBACL,SAC0F;CAC1F,MAAM,OAAO,WAAW;AASxB,WAAU,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAC7C,KAAI;AACH,gBAAc,MAAM,KAAK,UAAU,KAAK,EAAE,EAAE,MAAM,MAAM,CAAC;AACzD,SAAO,EAAE,IAAI,MAAM;UACX,KAAK;AAEb,MADc,IAA8B,SAC/B,SACZ,OAAM;AAGP,MAAI,CAAC,WAAW,KAAK,CAGpB,QAAO;GAAE,IAAI;GAAO,QAAQ;GAAM;AAEnC,MAAI;AAEH,UAAO;IAAE,IAAI;IAAO,QAAQ,cADhB,aAAa,MAAM,OACc,CAAC;IAAE;UACzC;AACP,UAAO;IAAE,IAAI;IAAO,QAAQ;IAAM;;;;;;;;;;;;;;;;;;;AAoBrC,MAAa,oBACZ,MACA,gBAAwB,mCAExB,OAAO,IAAI,aAAa;AACvB,QAAO,OAAO,oBAAoB;GAChC,SAAS,gBAAgB;GACzB,SAAS,yBAAyB;EACnC,CAAC;CACF,MAAM,YAAY,KAAK,KAAK;CAC5B,IAAI,UAAU;CACd,IAAI,aAAkC;CACtC,IAAI,gBAA+B;AACnC,QAAO,MAAM;EACZ,MAAM,UAAU,KAAK,KAAK,GAAG;AAC7B,MAAI,UAAU,cACb,QAAO,OAAO,OAAO,KACpB,IAAI,sBAAsB;GAAE;GAAM,cAAc;GAAS,QAAQ;GAAY,CAAC,CAC9E;EAEF,MAAM,UAAU,OAAO,OAAO,IAAI;GACjC,WAAW,eAAe,KAAK;GAC/B,QAAQ,UAAU,IAAI,iBAAiB;IAAE;IAAM;IAAO,CAAC;GACvD,CAAC;AACF,MAAI,QAAQ,IAAI;AAEf,UAAO,OAAO,mBACb,OAAO,WAAW;AACjB,QAAI;AACH,gBAAW,KAAK;YACT;KAIP,CACF;AACD;;AAED,eAAa,QAAQ;EASrB,MAAM,mBAAmB,YAAY,OAAO;AAC5C,MAAI,qBAAqB,eAAe;AACvC,aAAU;AACV,mBAAgB;;EAWjB,IAAI,YAAY;AAChB,MAAI,eAAe;QAQd,OAHkB,oBAAoB,WAAW,CAAC,KACrD,OAAO,YAAY,OAAO,QAAQ,QAAiB,CAAC,CACpD,MACc,QAAQ;AACtB,WAAO,OAAO,IAAI;KACjB,WAAW;AACV,UAAI;AACH,kBAAW,KAAK;cACT;AAGR,aAAO;;KAER,QAAQ,UAAU,IAAI,iBAAiB;MAAE;MAAM;MAAO,CAAC;KACvD,CAAC;AACF,gBAAY;;QAgBb,cAAY,OAJW,OAAO,IAAI;GACjC,WAAW,4BAA4B,MAAM,cAAc;GAC3D,QAAQ,UAAU,IAAI,iBAAiB;IAAE;IAAM;IAAO,CAAC;GACvD,CAAC,MACsB;AAEzB,MAAI,WAAW;AAKd,aAAU;AACV;;AAGD,SAAO,eAAe,OAAO,MAAM,GAAG,QAAQ,SAAS,CAAC;AACxD,YAAU,KAAK,IAAI,UAAU,GAAG,mBAAmB;;EAEnD,CAAC,KAAK,OAAO,SAAS,mCAAmC,CAAC"}
1
+ {"version":3,"file":"stack-lock.mjs","names":[],"sources":["../../../../src/substrate/runtime/cross-process/stack-lock.ts"],"sourcesContent":["// `stack.lock` — O_EXCL exclusive lock for short critical sections.\n//\n// Architecture § Cross-process safety protocol § \"What is locked\":\n// - `stack.lock` — OS-advisory exclusive lock file. Used only for\n// short critical sections (roster mutations, the snapshot bounce).\n// Acquired via `flock(LOCK_EX)` on Unix and `LockFileEx` on Windows;\n// never held across a long operation.\n//\n// The implementation here uses a portable O_EXCL-create dance because\n// `flock` isn't available cross-platform out of `node:fs`. The\n// architecture's wording (\"OS-advisory exclusive lock file\") covers\n// both `flock` and `O_EXCL`-with-pid-body; the latter is what every\n// portable POSIX tool reaches for when `flock` isn't on the table.\n//\n// Discipline:\n// - The lock is held BRIEFLY — within a single Effect.scoped block\n// that mutates the roster or spans the snapshot bounce.\n// - Stale locks (owner crashed under the lock) are reclaimed via the\n// PID + start-time liveness check before the acquire reattempts.\n// - Acquire retries with exponential backoff up to 5 seconds total\n// (architecture § Claim protocol step 1).\n\nimport { mkdirSync, unlinkSync, readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nimport { Data, Effect, Schema, Scope } from 'effect';\n\nimport { type RosterHolder } from '../../cross-process.ts';\nimport { parseVersionedDocumentBodyOrNull } from '../../versioned-doc-sync.ts';\nimport { underLiveClock } from './live-clock.ts';\nimport { checkHolderLiveness, ownHolder } from './liveness.ts';\nimport { reclaimUnparseableStaleFile } from './reclaim-stale-file.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\n/** Tagged failure: acquire window elapsed and the lock is still held.\n * Carries the in-disk body (if parseable) so the caller can name the\n * blocking peer in user-facing error reporting. */\nexport class StackLockTimeoutError extends Data.TaggedError('StackLockTimeoutError')<{\n\treadonly path: string;\n\treadonly waitedMillis: number;\n\treadonly holder: RosterHolder | null;\n}> {}\n\n/** Tagged failure: I/O error other than EEXIST during acquire. */\nexport class StackLockIoError extends Data.TaggedError('StackLockIoError')<{\n\treadonly path: string;\n\treadonly cause: unknown;\n}> {}\n\nexport type StackLockError = StackLockTimeoutError | StackLockIoError;\n\n// -----------------------------------------------------------------------------\n// Codec\n// -----------------------------------------------------------------------------\n\n/** The on-disk body is the same `RosterHolder` shape — re-using the schema\n * means roster sweep + stack-lock reclaim see the same fields.\n *\n * `startTime` mirrors `RosterHolderSchema`: `number | null` so an\n * unprobable platform's lock body round-trips cleanly through the\n * decoder. The shared liveness predicate honors null conservatively. */\nconst StackLockBodySchema = Schema.Struct({\n\tpid: Schema.Number,\n\tstartTime: Schema.NullOr(Schema.Number),\n\thostname: Schema.String,\n\tclaimedAt: Schema.Number,\n\theartbeatAt: Schema.Number,\n\tintent: Schema.Literals(['normal', 'snapshot']),\n});\n\nconst parseLockBody = (raw: string): RosterHolder | null =>\n\tparseVersionedDocumentBodyOrNull(raw, StackLockBodySchema, 'stack.lock');\n\n// -----------------------------------------------------------------------------\n// Acquire / release\n// -----------------------------------------------------------------------------\n\n/** Default acquire timeout — architecture § Claim protocol step 1\n * (\"Block up to 5 seconds; if unavailable, retry with backoff\"). */\nexport const DEFAULT_ACQUIRE_TIMEOUT_MILLIS = 5_000;\n\n/** Per-attempt initial wait. Doubles each retry up to the cap. The\n * backoff also resets whenever the holder identity changes — see the\n * reclaim loop comment for the contention story (review fix phase\n * 22f). The cap is intentionally tight (200ms) so peers react quickly\n * to a release; combined with the O_EXCL-arbitrated retry on reclaim,\n * no staggering jitter is needed. */\nconst INITIAL_BACKOFF_MILLIS = 25;\nconst MAX_BACKOFF_MILLIS = 200;\n\n/**\n * Sync attempt at O_EXCL-create with the lock's JSON body. Returns\n * whether we own the file now; on EEXIST it probes the existing body\n * through `parseLockBody`.\n *\n * Effect-platform's FileSystem doesn't expose a sync `open` shape, but\n * the critical-section discipline says we need a non-blocking attempt\n * inside a retry loop. Falling through to Node sync APIs here is the\n * cleanest path; the rest of the substrate stays Effect-native.\n */\nconst tryAcquireSync = (\n\tpath: string,\n\tbody: RosterHolder,\n): { readonly ok: true } | { readonly ok: false; readonly holder: RosterHolder | null } => {\n\t// The lock's parent directory may not exist on first-claim of a\n\t// fresh runtime root — devstack's `<runtimeRoot>/stacks/<stack>/` is\n\t// the substrate's responsibility to bring into being, but no other\n\t// L0 subsystem touches the disk until the first claim. `mkdir -p`\n\t// here is idempotent and harmless on the warm path; it transforms\n\t// the otherwise-fatal ENOENT-during-write into a clean acquire.\n\t// Architecture § Cross-process safety protocol: stack root is\n\t// created by whichever subsystem first reaches for a path under it.\n\tmkdirSync(dirname(path), { recursive: true });\n\ttry {\n\t\twriteFileSync(path, JSON.stringify(body), { flag: 'wx' });\n\t\treturn { ok: true };\n\t} catch (err) {\n\t\tconst code = (err as NodeJS.ErrnoException).code;\n\t\tif (code !== 'EEXIST') {\n\t\t\tthrow err;\n\t\t}\n\t\t// Peer holds it (or stale). Probe the existing body.\n\t\tif (!existsSync(path)) {\n\t\t\t// Race: holder unlinked between our EEXIST and the existsSync.\n\t\t\t// Retry on the next loop iteration.\n\t\t\treturn { ok: false, holder: null };\n\t\t}\n\t\ttry {\n\t\t\tconst raw = readFileSync(path, 'utf8');\n\t\t\treturn { ok: false, holder: parseLockBody(raw) };\n\t\t} catch {\n\t\t\treturn { ok: false, holder: null };\n\t\t}\n\t}\n};\n\n/**\n * Acquire `stack.lock` as a scoped resource. The lock is released when\n * the surrounding Scope closes (the unlink finalizer is registered INSIDE\n * this scope, so every caller — roster claim/release/heartbeat, the\n * snapshot bounce, command-channel append — releases on scope close,\n * never bypassed). Yields a `void` resource — the caller MUST keep its\n * Scope tight; the architecture forbids holding the lock across long\n * operations.\n *\n * The O_EXCL retry loop: exponential backoff up to `timeoutMillis`\n * (default 5s). On every miss, probe the holder's liveness — dead holders\n * are reclaimed (unlink + loop); an unparseable body is reclaimed only\n * through the shared re-stat guard once it ages past\n * `DEFAULT_SWEEP_POLICY.staleAfterMillis`. Backoff resets on holder\n * change and on reclaim. The reclaim path is the architecture's \"stale\n * lock\" handling — same PID + start-time semantics as the roster sweep,\n * so a process that crashed under the lock never blocks the next peer\n * indefinitely.\n */\nexport const acquireStackLock = (\n\tpath: string,\n\ttimeoutMillis: number = DEFAULT_ACQUIRE_TIMEOUT_MILLIS,\n): Effect.Effect<void, StackLockError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst addUnlinkFinalizer = Effect.addFinalizer(() =>\n\t\t\tEffect.sync(() => {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(path);\n\t\t\t\t} catch {\n\t\t\t\t\t// Already gone — ok. Crash-during-release is handled by\n\t\t\t\t\t// the next peer's stale-PID reclaim.\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t\tconst startedAt = Date.now();\n\t\tlet backoff = INITIAL_BACKOFF_MILLIS;\n\t\tlet lastHolder: RosterHolder | null = null;\n\t\tlet prevHolderPid: number | null = null;\n\t\twhile (true) {\n\t\t\tconst elapsed = Date.now() - startedAt;\n\t\t\tif (elapsed > timeoutMillis) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew StackLockTimeoutError({ path, waitedMillis: elapsed, holder: lastHolder }),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst attempt = yield* Effect.try({\n\t\t\t\ttry: () => tryAcquireSync(path, ownHolder()),\n\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t});\n\t\t\tif (attempt.ok) {\n\t\t\t\t// Register a finalizer that unlinks the lock on scope close.\n\t\t\t\tyield* addUnlinkFinalizer;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlastHolder = attempt.holder;\n\t\t\t// Reset the backoff whenever the holder identity changes —\n\t\t\t// a new holder means the previous one released, and our\n\t\t\t// long-saturated backoff (driven by waiting on the prior\n\t\t\t// holder) is now stale. Without this, late waiters under\n\t\t\t// heavy contention saturate at MAX_BACKOFF_MILLIS while\n\t\t\t// each peer holds the lock only briefly, exhausting the\n\t\t\t// claim budget before they can win (review fix phase 22f\n\t\t\t// reclaim-stress reproducer caught the case).\n\t\t\tconst currentHolderPid = lastHolder !== null ? lastHolder.pid : null;\n\t\t\tif (currentHolderPid !== prevHolderPid) {\n\t\t\t\tbackoff = INITIAL_BACKOFF_MILLIS;\n\t\t\t\tprevHolderPid = currentHolderPid;\n\t\t\t}\n\t\t\t// Reclaim if the holder is dead OR the lock body is\n\t\t\t// unparseable AND old enough to be presumed-abandoned. The\n\t\t\t// unparseable case covers a peer that died mid-write: the\n\t\t\t// PID liveness check has nothing to consult, so the only\n\t\t\t// signal is the file's mtime falling outside the staleness\n\t\t\t// window (`DEFAULT_SWEEP_POLICY.staleAfterMillis`, shared\n\t\t\t// with the roster sweep). Without this branch, a mid-write\n\t\t\t// crash keeps every peer blocked for the full 5s claim\n\t\t\t// window — see review fix phase 22f (cross-process).\n\t\t\tlet reclaimed = false;\n\t\t\tif (lastHolder !== null) {\n\t\t\t\t// Dead-PID reclaim: gated on a liveness probe, so the\n\t\t\t\t// holder cannot have written a fresh body — a plain\n\t\t\t\t// unlink is safe here (no TOCTOU). An alive holder falls\n\t\t\t\t// through to the backoff below.\n\t\t\t\tconst status = yield* checkHolderLiveness(lastHolder).pipe(\n\t\t\t\t\tEffect.catch(() => Effect.succeed('alive' as const)),\n\t\t\t\t);\n\t\t\t\tif (status === 'dead') {\n\t\t\t\t\tyield* Effect.try({\n\t\t\t\t\t\ttry: () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tunlinkSync(path);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Race with another reclaimer — ok.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t\t\t});\n\t\t\t\t\treclaimed = true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Unparseable body: reclaim ONLY through the re-stat\n\t\t\t\t// guard. A bare mtime read + unlink races a competitor\n\t\t\t\t// who legitimately reclaims the garbage and writes a\n\t\t\t\t// fresh valid O_EXCL body in the window — the unlink\n\t\t\t\t// would clobber that LIVE lock (two simultaneous\n\t\t\t\t// holders). `reclaimUnparseableStaleFile` re-confirms the\n\t\t\t\t// file is still the same stale, unparseable inode\n\t\t\t\t// immediately before unlinking; any other outcome leaves\n\t\t\t\t// the file untouched and we fall through to back off.\n\t\t\t\tconst outcome = yield* Effect.try({\n\t\t\t\t\ttry: () => reclaimUnparseableStaleFile(path, parseLockBody),\n\t\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t\t});\n\t\t\t\treclaimed = outcome === 'reclaimed';\n\t\t\t}\n\t\t\tif (reclaimed) {\n\t\t\t\t// O_EXCL atomicity alone arbitrates the post-reclaim\n\t\t\t\t// race; reset the backoff so the contest starts fresh\n\t\t\t\t// (the prior growth was driven by a now-evicted dead\n\t\t\t\t// holder).\n\t\t\t\tbackoff = INITIAL_BACKOFF_MILLIS;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Peer holds an alive lock — back off exponentially.\n\t\t\tyield* underLiveClock(Effect.sleep(`${backoff} millis`));\n\t\t\tbackoff = Math.min(backoff * 2, MAX_BACKOFF_MILLIS);\n\t\t}\n\t});\n"],"mappings":";;;;;;;;;;;;AAwCA,IAAa,wBAAb,cAA2C,KAAK,YAAY,uBAAuB,CAAC,CAIjF,CAAC;;AAGJ,IAAa,mBAAb,cAAsC,KAAK,YAAY,kBAAkB,CAAC,CAGvE,CAAC;;;;;;;AAcJ,MAAM,sBAAsB,OAAO,OAAO;CACzC,KAAK,OAAO;CACZ,WAAW,OAAO,OAAO,OAAO,MAAM;CACtC,UAAU,OAAO;CACjB,WAAW,OAAO;CAClB,aAAa,OAAO;CACpB,QAAQ,OAAO,SAAS,CAAC,UAAU,UAAU,CAAC;AAC/C,CAAC;AAED,MAAM,iBAAiB,QACtB,iCAAiC,KAAK,qBAAqB,YAAY;;;AAQxE,MAAa,iCAAiC;;;;;;;AAQ9C,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;;;;;;;;;;;AAY3B,MAAM,kBACL,MACA,SAC0F;CAS1F,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CAC5C,IAAI;EACH,cAAc,MAAM,KAAK,UAAU,IAAI,GAAG,EAAE,MAAM,KAAK,CAAC;EACxD,OAAO,EAAE,IAAI,KAAK;CACnB,SAAS,KAAK;EAEb,IADc,IAA8B,SAC/B,UACZ,MAAM;EAGP,IAAI,CAAC,WAAW,IAAI,GAGnB,OAAO;GAAE,IAAI;GAAO,QAAQ;EAAK;EAElC,IAAI;GAEH,OAAO;IAAE,IAAI;IAAO,QAAQ,cADhB,aAAa,MAAM,MACa,CAAC;GAAE;EAChD,QAAQ;GACP,OAAO;IAAE,IAAI;IAAO,QAAQ;GAAK;EAClC;CACD;AACD;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,oBACZ,MACA,gBAAwB,mCAExB,OAAO,IAAI,aAAa;CACvB,MAAM,qBAAqB,OAAO,mBACjC,OAAO,WAAW;EACjB,IAAI;GACH,WAAW,IAAI;EAChB,QAAQ,CAGR;CACD,CAAC,CACF;CACA,MAAM,YAAY,KAAK,IAAI;CAC3B,IAAI,UAAU;CACd,IAAI,aAAkC;CACtC,IAAI,gBAA+B;CACnC,OAAO,MAAM;EACZ,MAAM,UAAU,KAAK,IAAI,IAAI;EAC7B,IAAI,UAAU,eACb,OAAO,OAAO,OAAO,KACpB,IAAI,sBAAsB;GAAE;GAAM,cAAc;GAAS,QAAQ;EAAW,CAAC,CAC9E;EAED,MAAM,UAAU,OAAO,OAAO,IAAI;GACjC,WAAW,eAAe,MAAM,UAAU,CAAC;GAC3C,QAAQ,UAAU,IAAI,iBAAiB;IAAE;IAAM;GAAM,CAAC;EACvD,CAAC;EACD,IAAI,QAAQ,IAAI;GAEf,OAAO;GACP;EACD;EACA,aAAa,QAAQ;EASrB,MAAM,mBAAmB,eAAe,OAAO,WAAW,MAAM;EAChE,IAAI,qBAAqB,eAAe;GACvC,UAAU;GACV,gBAAgB;EACjB;EAUA,IAAI,YAAY;EAChB,IAAI,eAAe;QAQd,OAHkB,oBAAoB,UAAU,CAAC,CAAC,KACrD,OAAO,YAAY,OAAO,QAAQ,OAAgB,CAAC,CACpD,OACe,QAAQ;IACtB,OAAO,OAAO,IAAI;KACjB,WAAW;MACV,IAAI;OACH,WAAW,IAAI;MAChB,QAAQ,CAER;MACA,OAAO;KACR;KACA,QAAQ,UAAU,IAAI,iBAAiB;MAAE;MAAM;KAAM,CAAC;IACvD,CAAC;IACD,YAAY;GACb;SAeA,aAAY,OAJW,OAAO,IAAI;GACjC,WAAW,4BAA4B,MAAM,aAAa;GAC1D,QAAQ,UAAU,IAAI,iBAAiB;IAAE;IAAM;GAAM,CAAC;EACvD,CAAC,OACuB;EAEzB,IAAI,WAAW;GAKd,UAAU;GACV;EACD;EAEA,OAAO,eAAe,OAAO,MAAM,GAAG,QAAQ,QAAQ,CAAC;EACvD,UAAU,KAAK,IAAI,UAAU,GAAG,kBAAkB;CACnD;AACD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"current-plugin.mjs","names":[],"sources":["../../../src/substrate/runtime/current-plugin.ts"],"sourcesContent":["import { Context, Effect, Option } from 'effect';\n\nimport type { PluginKey } from '../brand.ts';\nimport type { PhaseNarration } from '../lifecycle.ts';\n\n/** Acquire-time identity for the plugin currently being supervised.\n *\n * Plugins that stream host-process output need the supervisor-minted\n * row key so logger lines publish as `log.appended` events for the\n * correct projection row. */\nexport interface CurrentPluginKeyShape {\n\treadonly key: PluginKey;\n}\n\nexport class CurrentPluginKey extends Context.Service<CurrentPluginKey, CurrentPluginKeyShape>()(\n\t'@devstack/substrate/CurrentPluginKey',\n) {}\n\nexport interface CurrentPluginProgressShape {\n\treadonly setPhase: (phase: PhaseNarration | null) => Effect.Effect<void>;\n}\n\nexport class CurrentPluginProgress extends Context.Service<\n\tCurrentPluginProgress,\n\tCurrentPluginProgressShape\n>()('@devstack/substrate/CurrentPluginProgress') {}\n\n/** Publish acquire-time row narration for the currently supervised\n * plugin. Safe to call from plugin code: if the caller is running\n * outside a supervisor, the helper is a no-op. */\nexport const setCurrentPluginPhase = (phase: PhaseNarration | null): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tconst progress = yield* Effect.serviceOption(CurrentPluginProgress);\n\t\tif (Option.isSome(progress)) {\n\t\t\tyield* progress.value.setPhase(phase);\n\t\t}\n\t});\n"],"mappings":";;AAcA,IAAa,mBAAb,cAAsC,QAAQ,SAAkD,CAC/F,uCACA,CAAC;AAMF,IAAa,wBAAb,cAA2C,QAAQ,SAGhD,CAAC,4CAA4C,CAAC;;;;AAKjD,MAAa,yBAAyB,UACrC,OAAO,IAAI,aAAa;CACvB,MAAM,WAAW,OAAO,OAAO,cAAc,sBAAsB;AACnE,KAAI,OAAO,OAAO,SAAS,CAC1B,QAAO,SAAS,MAAM,SAAS,MAAM;EAErC"}
1
+ {"version":3,"file":"current-plugin.mjs","names":[],"sources":["../../../src/substrate/runtime/current-plugin.ts"],"sourcesContent":["import { Context, Effect, Option } from 'effect';\n\nimport type { PluginKey } from '../brand.ts';\nimport type { PhaseNarration } from '../lifecycle.ts';\n\n/** Acquire-time identity for the plugin currently being supervised.\n *\n * Plugins that stream host-process output need the supervisor-minted\n * row key so logger lines publish as `log.appended` events for the\n * correct projection row. */\nexport interface CurrentPluginKeyShape {\n\treadonly key: PluginKey;\n}\n\nexport class CurrentPluginKey extends Context.Service<CurrentPluginKey, CurrentPluginKeyShape>()(\n\t'@devstack/substrate/CurrentPluginKey',\n) {}\n\nexport interface CurrentPluginProgressShape {\n\treadonly setPhase: (phase: PhaseNarration | null) => Effect.Effect<void>;\n}\n\nexport class CurrentPluginProgress extends Context.Service<\n\tCurrentPluginProgress,\n\tCurrentPluginProgressShape\n>()('@devstack/substrate/CurrentPluginProgress') {}\n\n/** Publish acquire-time row narration for the currently supervised\n * plugin. Safe to call from plugin code: if the caller is running\n * outside a supervisor, the helper is a no-op. */\nexport const setCurrentPluginPhase = (phase: PhaseNarration | null): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tconst progress = yield* Effect.serviceOption(CurrentPluginProgress);\n\t\tif (Option.isSome(progress)) {\n\t\t\tyield* progress.value.setPhase(phase);\n\t\t}\n\t});\n"],"mappings":";;AAcA,IAAa,mBAAb,cAAsC,QAAQ,QAAiD,CAAC,CAC/F,sCACD,CAAC,CAAC,CAAC;AAMH,IAAa,wBAAb,cAA2C,QAAQ,QAGjD,CAAC,CAAC,2CAA2C,CAAC,CAAC,CAAC;;;;AAKlD,MAAa,yBAAyB,UACrC,OAAO,IAAI,aAAa;CACvB,MAAM,WAAW,OAAO,OAAO,cAAc,qBAAqB;CAClE,IAAI,OAAO,OAAO,QAAQ,GACzB,OAAO,SAAS,MAAM,SAAS,KAAK;AAEtC,CAAC"}
@@ -15,18 +15,6 @@ var AtomicWriteFailed = class extends Schema.TaggedErrorClass()("AtomicWriteFail
15
15
  ]),
16
16
  cause: Schema.optional(Schema.Defect)
17
17
  }) {};
18
- /** State-store I/O / corruption surface. Distinct from
19
- * `AtomicWriteFailed` so consumers can recover ("re-init on
20
- * corruption") without catching write errors. */
21
- var StateStoreError = class extends Schema.TaggedErrorClass()("StateStoreError", {
22
- reason: Schema.Literals([
23
- "io-failed",
24
- "corruption",
25
- "lock-contention"
26
- ]),
27
- detail: Schema.String,
28
- cause: Schema.optional(Schema.Defect)
29
- }) {};
30
18
  /** Cache surface — same tri-state as the L0 cache contract. */
31
19
  var CacheError = class extends Schema.TaggedErrorClass()("CacheError", {
32
20
  reason: Schema.Literals([
@@ -77,6 +65,6 @@ var PortBrokerError = class extends Schema.TaggedErrorClass()("PortBrokerError",
77
65
  cause: Schema.optional(Schema.Defect)
78
66
  }) {};
79
67
  //#endregion
80
- export { AtomicWriteFailed, CacheError, PortBrokerError, StateStoreError, StrategyNotFoundError };
68
+ export { AtomicWriteFailed, CacheError, PortBrokerError, StrategyNotFoundError };
81
69
 
82
70
  //# sourceMappingURL=errors.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../../../src/substrate/runtime/errors.ts"],"sourcesContent":["// Shared tagged errors for the runtime persistence layer.\n//\n// Per-subsystem tags so `Effect.catchTags({ ... })` reads at the call\n// site as \"what did the state-store / cache / registry just fail with\".\n\nimport { Schema } from 'effect';\n\n/** Atomic-write failure — one tag covers every stage. The `stage`\n * field discriminates inside renderer messages without\n * proliferating tag types. */\nexport class AtomicWriteFailed extends Schema.TaggedErrorClass<AtomicWriteFailed>()(\n\t'AtomicWriteFailed',\n\t{\n\t\tpath: Schema.String,\n\t\tstage: Schema.Literals(['mkdir-parent', 'open-temp', 'write', 'fsync', 'rename', 'encode']),\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\n/** State-store I/O / corruption surface. Distinct from\n * `AtomicWriteFailed` so consumers can recover (\"re-init on\n * corruption\") without catching write errors. */\nexport class StateStoreError extends Schema.TaggedErrorClass<StateStoreError>()('StateStoreError', {\n\treason: Schema.Literals(['io-failed', 'corruption', 'lock-contention']),\n\tdetail: Schema.String,\n\tcause: Schema.optional(Schema.Defect),\n}) {}\n\n/** Cache surface — same tri-state as the L0 cache contract. */\nexport class CacheError extends Schema.TaggedErrorClass<CacheError>()('CacheError', {\n\treason: Schema.Literals(['io-failed', 'corruption', 'lock-contention']),\n\tdetail: Schema.String,\n\tcause: Schema.optional(Schema.Defect),\n}) {}\n\n/** Strategy registry missing-key surface. Canonical tagged error\n * yielded from `StrategyRegistry.get` when no contributor is\n * registered under the requested capability key. The\n * `contracts/strategy-contributor.ts` `StrategyRegistry` interface\n * imports this class as its `E` channel — one `_tag` literal,\n * one class, package-wide. */\nexport class StrategyNotFoundError extends Schema.TaggedErrorClass<StrategyNotFoundError>()(\n\t'StrategyNotFoundError',\n\t{\n\t\tcapabilityKey: Schema.String,\n\t\tregisteredKeys: Schema.Array(Schema.String),\n\t},\n) {}\n\n/** Port-broker allocation failure — surfaced when no free port could be\n * found in the configured range, when a caller-supplied `preferredPort`\n * is already taken by another in-process allocation, or when the bind-\n * probe rejected every candidate.\n *\n * `reason` discriminates the failure mode for renderer mapping:\n * - `no-free-port` : scanned the whole window for `kind`, every\n * candidate failed the bind-probe (EADDRINUSE) or\n * was already held by a sibling allocation in\n * this process.\n * - `preferred-busy`: caller supplied a `preferredPort` and the\n * port is currently held by another in-process\n * allocation (refused per architecture §6).\n * - `bind-probe-failed`: the OS-level bind probe surfaced an error\n * OTHER than `EADDRINUSE` (e.g. EACCES on a\n * privileged port, EPERM under jail). Caller\n * should fall back to user opt-in / docs.\n * - `reservation-failed`: the runtime-root scoped port reservation\n * file could not be created/read/reclaimed. */\nexport class PortBrokerError extends Schema.TaggedErrorClass<PortBrokerError>()('PortBrokerError', {\n\treason: Schema.Literals([\n\t\t'no-free-port',\n\t\t'preferred-busy',\n\t\t'bind-probe-failed',\n\t\t'reservation-failed',\n\t]),\n\tdetail: Schema.String,\n\tcause: Schema.optional(Schema.Defect),\n}) {}\n"],"mappings":";;;;;AAUA,IAAa,oBAAb,cAAuC,OAAO,kBAAqC,CAClF,qBACA;CACC,MAAM,OAAO;CACb,OAAO,OAAO,SAAS;EAAC;EAAgB;EAAa;EAAS;EAAS;EAAU;EAAS,CAAC;CAC3F,OAAO,OAAO,SAAS,OAAO,OAAO;CACrC,CACD,CAAC;;;;AAKF,IAAa,kBAAb,cAAqC,OAAO,kBAAmC,CAAC,mBAAmB;CAClG,QAAQ,OAAO,SAAS;EAAC;EAAa;EAAc;EAAkB,CAAC;CACvE,QAAQ,OAAO;CACf,OAAO,OAAO,SAAS,OAAO,OAAO;CACrC,CAAC,CAAC;;AAGH,IAAa,aAAb,cAAgC,OAAO,kBAA8B,CAAC,cAAc;CACnF,QAAQ,OAAO,SAAS;EAAC;EAAa;EAAc;EAAkB,CAAC;CACvE,QAAQ,OAAO;CACf,OAAO,OAAO,SAAS,OAAO,OAAO;CACrC,CAAC,CAAC;;;;;;;AAQH,IAAa,wBAAb,cAA2C,OAAO,kBAAyC,CAC1F,yBACA;CACC,eAAe,OAAO;CACtB,gBAAgB,OAAO,MAAM,OAAO,OAAO;CAC3C,CACD,CAAC;;;;;;;;;;;;;;;;;;;;AAqBF,IAAa,kBAAb,cAAqC,OAAO,kBAAmC,CAAC,mBAAmB;CAClG,QAAQ,OAAO,SAAS;EACvB;EACA;EACA;EACA;EACA,CAAC;CACF,QAAQ,OAAO;CACf,OAAO,OAAO,SAAS,OAAO,OAAO;CACrC,CAAC,CAAC"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../../../src/substrate/runtime/errors.ts"],"sourcesContent":["// Shared tagged errors for the runtime persistence layer.\n//\n// Per-subsystem tags so `Effect.catchTags({ ... })` reads at the call\n// site as \"what did the cache / registry just fail with\".\n\nimport { Schema } from 'effect';\n\n/** Atomic-write failure — one tag covers every stage. The `stage`\n * field discriminates inside renderer messages without\n * proliferating tag types. */\nexport class AtomicWriteFailed extends Schema.TaggedErrorClass<AtomicWriteFailed>()(\n\t'AtomicWriteFailed',\n\t{\n\t\tpath: Schema.String,\n\t\tstage: Schema.Literals(['mkdir-parent', 'open-temp', 'write', 'fsync', 'rename', 'encode']),\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\n/** Cache surface — same tri-state as the L0 cache contract. */\nexport class CacheError extends Schema.TaggedErrorClass<CacheError>()('CacheError', {\n\treason: Schema.Literals(['io-failed', 'corruption', 'lock-contention']),\n\tdetail: Schema.String,\n\tcause: Schema.optional(Schema.Defect),\n}) {}\n\n/** Strategy registry missing-key surface. Canonical tagged error\n * yielded from `StrategyRegistry.get` when no contributor is\n * registered under the requested capability key. The\n * `contracts/strategy-contributor.ts` `StrategyRegistry` interface\n * imports this class as its `E` channel — one `_tag` literal,\n * one class, package-wide. */\nexport class StrategyNotFoundError extends Schema.TaggedErrorClass<StrategyNotFoundError>()(\n\t'StrategyNotFoundError',\n\t{\n\t\tcapabilityKey: Schema.String,\n\t\tregisteredKeys: Schema.Array(Schema.String),\n\t},\n) {}\n\n/** Port-broker allocation failure — surfaced when no free port could be\n * found in the configured range, when a caller-supplied `preferredPort`\n * is already taken by another in-process allocation, or when the bind-\n * probe rejected every candidate.\n *\n * `reason` discriminates the failure mode for renderer mapping:\n * - `no-free-port` : scanned the whole window for `kind`, every\n * candidate failed the bind-probe (EADDRINUSE) or\n * was already held by a sibling allocation in\n * this process.\n * - `preferred-busy`: caller supplied a `preferredPort` and the\n * port is currently held by another in-process\n * allocation (refused per architecture §6).\n * - `bind-probe-failed`: the OS-level bind probe surfaced an error\n * OTHER than `EADDRINUSE` (e.g. EACCES on a\n * privileged port, EPERM under jail). Caller\n * should fall back to user opt-in / docs.\n * - `reservation-failed`: the runtime-root scoped port reservation\n * file could not be created/read/reclaimed. */\nexport class PortBrokerError extends Schema.TaggedErrorClass<PortBrokerError>()('PortBrokerError', {\n\treason: Schema.Literals([\n\t\t'no-free-port',\n\t\t'preferred-busy',\n\t\t'bind-probe-failed',\n\t\t'reservation-failed',\n\t]),\n\tdetail: Schema.String,\n\tcause: Schema.optional(Schema.Defect),\n}) {}\n"],"mappings":";;;;;AAUA,IAAa,oBAAb,cAAuC,OAAO,iBAAoC,CAAC,CAClF,qBACA;CACC,MAAM,OAAO;CACb,OAAO,OAAO,SAAS;EAAC;EAAgB;EAAa;EAAS;EAAS;EAAU;CAAQ,CAAC;CAC1F,OAAO,OAAO,SAAS,OAAO,MAAM;AACrC,CACD,CAAC,CAAC,CAAC;;AAGH,IAAa,aAAb,cAAgC,OAAO,iBAA6B,CAAC,CAAC,cAAc;CACnF,QAAQ,OAAO,SAAS;EAAC;EAAa;EAAc;CAAiB,CAAC;CACtE,QAAQ,OAAO;CACf,OAAO,OAAO,SAAS,OAAO,MAAM;AACrC,CAAC,CAAC,CAAC,CAAC;;;;;;;AAQJ,IAAa,wBAAb,cAA2C,OAAO,iBAAwC,CAAC,CAC1F,yBACA;CACC,eAAe,OAAO;CACtB,gBAAgB,OAAO,MAAM,OAAO,MAAM;AAC3C,CACD,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;AAqBH,IAAa,kBAAb,cAAqC,OAAO,iBAAkC,CAAC,CAAC,mBAAmB;CAClG,QAAQ,OAAO,SAAS;EACvB;EACA;EACA;EACA;CACD,CAAC;CACD,QAAQ,OAAO;CACf,OAAO,OAAO,SAAS,OAAO,MAAM;AACrC,CAAC,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"format-unknown-error.mjs","names":[],"sources":["../../../src/substrate/runtime/format-unknown-error.ts"],"sourcesContent":["// Shared `unknown → string` projector for plugin error messages.\n//\n// Lifted from 5 identical local definitions across mode/local.ts,\n// mode/shared-boot.ts, mode/fork.ts, mode/external.ts, chain-probe.ts,\n// fork-transaction.ts. Callers concatenate this into plugin-tagged\n// error `message` fields; cascade-formatter walks the actual `cause`\n// separately for structured rendering.\n//\n// NB: this does NOT stringify Effect's `Cause` type — it projects an\n// arbitrary `unknown` (typically a caught throwable) to a short\n// string suitable for splicing into a human-readable error message.\n\n// Bounds the `.cause` walk so a self-referential or pathologically deep\n// chain can't produce a runaway string (or loop forever).\nconst MAX_CAUSE_DEPTH = 5;\n\n// A nested `.cause` only earns a place in the spliced message when it\n// carries a human-readable string of its own — an `Error`, a string, or\n// a tagged object with a `.message`. Raw detail bags (e.g. `{ sender,\n// objectCount }`) are skipped here so they don't dump JSON into the\n// headline message; the structured cascade-formatter still renders them.\nconst nestedCauseMessage = (cause: unknown, depth: number): string | null => {\n\tif (cause instanceof Error) return project(cause, depth);\n\tif (typeof cause === 'string') return cause.length > 0 ? cause : null;\n\tif (typeof cause === 'object' && cause !== null) {\n\t\tconst message = (cause as { readonly message?: unknown }).message;\n\t\tif (typeof message === 'string' && message.length > 0) {\n\t\t\treturn project(cause, depth);\n\t\t}\n\t}\n\treturn null;\n};\n\nconst project = (cause: unknown, depth: number): string => {\n\tif (typeof cause === 'string') return cause;\n\t// `Error` instances AND tagged devstack errors (`AccountSignError`,\n\t// `SuiPluginError`, … — plain objects, NOT `Error`s, so `String(obj)`\n\t// yields `[object Object]`) both carry a string `.message` and may carry\n\t// a `.cause`. Prefer the message, then chain the `.cause` so a generic\n\t// wrapper (\"… submit failed.\") still surfaces the actionable root\n\t// (\"… no SUI gas coins found for 0x…\"). `Error.cause` is read the same\n\t// way, so a thrown `new Error(msg, { cause })` chain is walked too.\n\tconst tagged = cause as { readonly message?: unknown; readonly cause?: unknown };\n\tconst message =\n\t\tcause instanceof Error\n\t\t\t? cause.message\n\t\t\t: typeof cause === 'object' && cause !== null && typeof tagged.message === 'string'\n\t\t\t\t? tagged.message\n\t\t\t\t: undefined;\n\tif (message !== undefined && message.length > 0) {\n\t\tconst nested = depth < MAX_CAUSE_DEPTH ? nestedCauseMessage(tagged.cause, depth + 1) : null;\n\t\treturn nested !== null && !message.includes(nested)\n\t\t\t? `${message} [cause: ${nested}]`\n\t\t\t: message;\n\t}\n\ttry {\n\t\treturn JSON.stringify(cause);\n\t} catch {\n\t\treturn String(cause);\n\t}\n};\n\nexport const formatUnknownError = (cause: unknown): string => project(cause, 0);\n"],"mappings":";AAcA,MAAM,kBAAkB;AAOxB,MAAM,sBAAsB,OAAgB,UAAiC;AAC5E,KAAI,iBAAiB,MAAO,QAAO,QAAQ,OAAO,MAAM;AACxD,KAAI,OAAO,UAAU,SAAU,QAAO,MAAM,SAAS,IAAI,QAAQ;AACjE,KAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAChD,MAAM,UAAW,MAAyC;AAC1D,MAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,EACnD,QAAO,QAAQ,OAAO,MAAM;;AAG9B,QAAO;;AAGR,MAAM,WAAW,OAAgB,UAA0B;AAC1D,KAAI,OAAO,UAAU,SAAU,QAAO;CAQtC,MAAM,SAAS;CACf,MAAM,UACL,iBAAiB,QACd,MAAM,UACN,OAAO,UAAU,YAAY,UAAU,QAAQ,OAAO,OAAO,YAAY,WACxE,OAAO,UACP,KAAA;AACL,KAAI,YAAY,KAAA,KAAa,QAAQ,SAAS,GAAG;EAChD,MAAM,SAAS,QAAQ,kBAAkB,mBAAmB,OAAO,OAAO,QAAQ,EAAE,GAAG;AACvF,SAAO,WAAW,QAAQ,CAAC,QAAQ,SAAS,OAAO,GAChD,GAAG,QAAQ,WAAW,OAAO,KAC7B;;AAEJ,KAAI;AACH,SAAO,KAAK,UAAU,MAAM;SACrB;AACP,SAAO,OAAO,MAAM;;;AAItB,MAAa,sBAAsB,UAA2B,QAAQ,OAAO,EAAE"}
1
+ {"version":3,"file":"format-unknown-error.mjs","names":[],"sources":["../../../src/substrate/runtime/format-unknown-error.ts"],"sourcesContent":["// Shared `unknown → string` projector for plugin error messages.\n//\n// Lifted from 5 identical local definitions across mode/local.ts,\n// mode/shared-boot.ts, mode/fork.ts, mode/external.ts, chain-probe.ts,\n// fork-transaction.ts. Callers concatenate this into plugin-tagged\n// error `message` fields; cascade-formatter walks the actual `cause`\n// separately for structured rendering.\n//\n// NB: this does NOT stringify Effect's `Cause` type — it projects an\n// arbitrary `unknown` (typically a caught throwable) to a short\n// string suitable for splicing into a human-readable error message.\n\n// Bounds the `.cause` walk so a self-referential or pathologically deep\n// chain can't produce a runaway string (or loop forever).\nconst MAX_CAUSE_DEPTH = 5;\n\n// A nested `.cause` only earns a place in the spliced message when it\n// carries a human-readable string of its own — an `Error`, a string, or\n// a tagged object with a `.message`. Raw detail bags (e.g. `{ sender,\n// objectCount }`) are skipped here so they don't dump JSON into the\n// headline message; the structured cascade-formatter still renders them.\nconst nestedCauseMessage = (cause: unknown, depth: number): string | null => {\n\tif (cause instanceof Error) return project(cause, depth);\n\tif (typeof cause === 'string') return cause.length > 0 ? cause : null;\n\tif (typeof cause === 'object' && cause !== null) {\n\t\tconst message = (cause as { readonly message?: unknown }).message;\n\t\tif (typeof message === 'string' && message.length > 0) {\n\t\t\treturn project(cause, depth);\n\t\t}\n\t}\n\treturn null;\n};\n\nconst project = (cause: unknown, depth: number): string => {\n\tif (typeof cause === 'string') return cause;\n\t// `Error` instances AND tagged devstack errors (`AccountSignError`,\n\t// `SuiPluginError`, … — plain objects, NOT `Error`s, so `String(obj)`\n\t// yields `[object Object]`) both carry a string `.message` and may carry\n\t// a `.cause`. Prefer the message, then chain the `.cause` so a generic\n\t// wrapper (\"… submit failed.\") still surfaces the actionable root\n\t// (\"… no SUI gas coins found for 0x…\"). `Error.cause` is read the same\n\t// way, so a thrown `new Error(msg, { cause })` chain is walked too.\n\tconst tagged = cause as { readonly message?: unknown; readonly cause?: unknown };\n\tconst message =\n\t\tcause instanceof Error\n\t\t\t? cause.message\n\t\t\t: typeof cause === 'object' && cause !== null && typeof tagged.message === 'string'\n\t\t\t\t? tagged.message\n\t\t\t\t: undefined;\n\tif (message !== undefined && message.length > 0) {\n\t\tconst nested = depth < MAX_CAUSE_DEPTH ? nestedCauseMessage(tagged.cause, depth + 1) : null;\n\t\treturn nested !== null && !message.includes(nested) ? `${message} [cause: ${nested}]` : message;\n\t}\n\ttry {\n\t\treturn JSON.stringify(cause);\n\t} catch {\n\t\treturn String(cause);\n\t}\n};\n\nexport const formatUnknownError = (cause: unknown): string => project(cause, 0);\n"],"mappings":";AAcA,MAAM,kBAAkB;AAOxB,MAAM,sBAAsB,OAAgB,UAAiC;CAC5E,IAAI,iBAAiB,OAAO,OAAO,QAAQ,OAAO,KAAK;CACvD,IAAI,OAAO,UAAU,UAAU,OAAO,MAAM,SAAS,IAAI,QAAQ;CACjE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAChD,MAAM,UAAW,MAAyC;EAC1D,IAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,GACnD,OAAO,QAAQ,OAAO,KAAK;CAE7B;CACA,OAAO;AACR;AAEA,MAAM,WAAW,OAAgB,UAA0B;CAC1D,IAAI,OAAO,UAAU,UAAU,OAAO;CAQtC,MAAM,SAAS;CACf,MAAM,UACL,iBAAiB,QACd,MAAM,UACN,OAAO,UAAU,YAAY,UAAU,QAAQ,OAAO,OAAO,YAAY,WACxE,OAAO,UACP,KAAA;CACL,IAAI,YAAY,KAAA,KAAa,QAAQ,SAAS,GAAG;EAChD,MAAM,SAAS,QAAQ,kBAAkB,mBAAmB,OAAO,OAAO,QAAQ,CAAC,IAAI;EACvF,OAAO,WAAW,QAAQ,CAAC,QAAQ,SAAS,MAAM,IAAI,GAAG,QAAQ,WAAW,OAAO,KAAK;CACzF;CACA,IAAI;EACH,OAAO,KAAK,UAAU,KAAK;CAC5B,QAAQ;EACP,OAAO,OAAO,KAAK;CACpB;AACD;AAEA,MAAa,sBAAsB,UAA2B,QAAQ,OAAO,CAAC"}