@mysten-incubation/devstack 0.1.1 → 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 +6 -3
  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 -32
  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 +1 @@
1
- {"version":3,"file":"service.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/service.ts"],"sourcesContent":["// Snapshot orchestrator service.\n//\n// Architecture § L3 § Snapshot orchestrator:\n// \"Collects all `Snapshotable` decls, applies one capture-wide\n// pause window for managed containers, tars host subtrees with mode\n// round-trip, commits container writable layers, threads a typed metadata record\n// (identity guard + per-participant slice), atomic stage-and-swap\n// for restore, identity-guard fires before any destructive mutation.\"\n//\n// This file is the orchestrator's typed entry point — `capture`,\n// `restore`, `list`, `delete`, `wipe`, `prune` — wired to the\n// cross-process safety primitives (`snapshot.reservation`,\n// `stack.lock`) and the substrate path resolver.\n//\n// The service surface is name-blind. The two participant lists\n// (capture and restore) are populated by the substrate at boot from\n// the `Snapshotable` decls of registered plugins; service-specific\n// behavior never reaches this file. Registration is scope-bound: the\n// supervisor calls `registerParticipant(pluginKey, decl)` from inside\n// each plugin's scope so the registry entry is reaped on plugin\n// teardown.\n\nimport { mintRandomSuffix } from '../../substrate/runtime/random-suffix.ts';\n\nimport { Context, Effect, FileSystem, Layer, Ref, Schema, Scope } from 'effect';\n\nimport type { SnapshotableDecl } from '../../contracts/snapshotable.ts';\nimport type { ContainerRuntime } from '../../contracts/container-runtime.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/index.ts';\nimport { decodeJsonText } from '../../substrate/runtime/runtime-decode.ts';\nimport { ownHolder } from '../../substrate/runtime/cross-process/liveness.ts';\nimport {\n\tacquireReservation,\n\ttype SnapshotReservationError,\n} from '../../substrate/runtime/cross-process/snapshot-reservation.ts';\nimport {\n\tacquireStackLock,\n\ttype StackLockError,\n} from '../../substrate/runtime/cross-process/stack-lock.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport {\n\trunCapture,\n\ttype CapturePhaseError,\n\ttype SnapshotParticipant,\n\ttype SnapshotProgressReporter,\n} from './capture.ts';\nimport {\n\tSnapshotDescriptorError,\n\tSnapshotLayout,\n\tSnapshotMetadataSchema,\n\tSNAPSHOT_ID_RULE,\n\tparseSnapshotId,\n\ttype SnapshotId,\n\ttype SnapshotCatalogEntry,\n\ttype SnapshotMetadata,\n} from './descriptor.ts';\nimport {\n\ttype IdentityContributionConflictError,\n\ttype IdentityGuardError,\n} from './identity-guard.ts';\nimport { runPrune, type PruneResult, type PrunePhaseError } from './prune.ts';\nimport {\n\trecoverPendingRestore,\n\ttype RestorePendingRecoveryError,\n\ttype RestorePendingRecoverySummary,\n} from './recover-pending.ts';\nimport { runRestore, type RestoreParticipant, type RestorePhaseError } from './restore.ts';\nimport {\n\tstageAndSwap,\n\ttype StageAndSwapError,\n} from '../../substrate/runtime/stage-and-swap/index.ts';\nimport { planWipe, runWipe, type WipePhaseError, type WipeTargets } from './wipe.ts';\n\n// -----------------------------------------------------------------------------\n// The service shape\n// -----------------------------------------------------------------------------\n\n/** Tagged union of every error the orchestrator surface can surface.\n * Callers `catchTags` on the precise tags they care about. */\nexport type SnapshotOrchestratorError =\n\t| CapturePhaseError\n\t| RestorePhaseError\n\t| RestorePendingRecoveryError\n\t| WipePhaseError\n\t| PrunePhaseError\n\t| IdentityGuardError\n\t| IdentityContributionConflictError\n\t| StageAndSwapError\n\t| SnapshotReservationError\n\t| StackLockError\n\t| SnapshotIdError\n\t| SnapshotDescriptorError;\n\nexport class SnapshotIdError extends Schema.TaggedErrorClass<SnapshotIdError>()('SnapshotIdError', {\n\toperation: Schema.Literals(['capture', 'restore', 'delete']),\n\tfield: Schema.Literals(['id', 'name']),\n\tvalue: Schema.String,\n\tdetail: Schema.String,\n}) {}\n\nexport interface SnapshotOrchestrator {\n\t/** Register a `SnapshotableDecl` from a plugin. Scope-bound — when\n\t * the caller's scope (the plugin's acquire scope) closes, the\n\t * registration is reaped. The supervisor calls this once per\n\t * `SnapshotableDecl` on each plugin's `capabilities` tuple.\n\t *\n\t * `captureIdentity` / `captureContribution` are separately wired\n\t * hooks (not on `SnapshotableDecl` itself) — they plumb through\n\t * the plugin's resolved value. Identity is validated by the\n\t * orchestrator; contribution payloads are persisted as opaque JSON\n\t * that plugins must validate at their own boundary if they read it. */\n\treadonly registerParticipant: (\n\t\tpluginKey: string,\n\t\tdecl: SnapshotableDecl,\n\t\thooks?: {\n\t\t\treadonly captureIdentity?: Effect.Effect<Readonly<Record<string, string>>>;\n\t\t\treadonly captureContribution?: Effect.Effect<unknown>;\n\t\t\treadonly liveIdentity?: Effect.Effect<Readonly<Record<string, string>>>;\n\t\t},\n\t) => Effect.Effect<void, never, Scope.Scope>;\n\n\t/** Capture a new snapshot. The id MAY be caller-supplied; if\n\t * omitted, the substrate mints a random suffix to sidestep the\n\t * concurrent-saves-against-same-id silent overwrite.\n\t *\n\t * `participants` overrides the registered set — primarily for\n\t * tests. Production callers omit it; the orchestrator reads the\n\t * registered list. */\n\treadonly capture: (args: {\n\t\treadonly id?: string;\n\t\treadonly label?: string;\n\t\treadonly participants?: ReadonlyArray<SnapshotParticipant>;\n\t\treadonly onProgress?: SnapshotProgressReporter;\n\t}) => Effect.Effect<SnapshotMetadata, SnapshotOrchestratorError, FileSystem.FileSystem>;\n\n\t/** Restore from a previously-captured artifact id. Refuses on\n\t * identity mismatch BEFORE any destructive mutation. */\n\treadonly restore: (args: {\n\t\treadonly id: string;\n\t\treadonly participants?: ReadonlyArray<RestoreParticipant>;\n\t}) => Effect.Effect<SnapshotMetadata, SnapshotOrchestratorError, FileSystem.FileSystem>;\n\n\t/** Catalog list — tolerates partial / corrupt entries (they appear\n\t * with `metadata: null`). */\n\treadonly list: Effect.Effect<\n\t\tReadonlyArray<SnapshotCatalogEntry>,\n\t\tSnapshotOrchestratorError,\n\t\tFileSystem.FileSystem\n\t>;\n\n\t/** Delete one artifact by id. Idempotent — missing id is a no-op. */\n\treadonly delete: (\n\t\tid: string,\n\t) => Effect.Effect<void, SnapshotOrchestratorError, FileSystem.FileSystem>;\n\n\t/** Wipe the live (`app`, `stack`) footprint; preserves the\n\t * snapshot catalog by default. Stack-local artifact cache is\n\t * removed unless `keepCache` is explicitly true. */\n\treadonly wipe: (args: {\n\t\treadonly keepSnapshots?: boolean;\n\t\treadonly keepCache?: boolean;\n\t}) => Effect.Effect<void, SnapshotOrchestratorError, FileSystem.FileSystem>;\n\n\t/** Enumerate the concrete teardown targets a `wipe` of the same\n\t * `(app, stack)` would remove WITHOUT removing anything — the\n\t * read-only preview behind `devstack wipe --dry-run`. Same args as\n\t * `wipe` so the preview honors the same `keepSnapshots`/`keepCache`\n\t * preservation policy the real wipe applies. */\n\treadonly wipePlan: (args: {\n\t\treadonly keepSnapshots?: boolean;\n\t\treadonly keepCache?: boolean;\n\t}) => Effect.Effect<WipeTargets, SnapshotOrchestratorError, FileSystem.FileSystem>;\n\n\t/** Prune the snapshot catalog (reaps partial artifacts) and sweeps\n\t * byproduct images via the runtime adapter's label-scoped cleanup. */\n\treadonly prune: () => Effect.Effect<\n\t\tPruneResult,\n\t\tSnapshotOrchestratorError,\n\t\tFileSystem.FileSystem\n\t>;\n\n\t/** Recover from a snapshot-restore that crashed mid-way through the\n\t * post-publish Docker handoff. Reads the on-disk pending marker,\n\t * retries each outstanding image promote, and clears the marker.\n\t * Idempotent — safe to call on every supervise startup; a no-op\n\t * when no marker is present. */\n\treadonly recoverPendingRestore: Effect.Effect<\n\t\tRestorePendingRecoverySummary,\n\t\tSnapshotOrchestratorError,\n\t\tFileSystem.FileSystem\n\t>;\n}\n\nexport class SnapshotOrchestratorService extends Context.Service<\n\tSnapshotOrchestratorService,\n\tSnapshotOrchestrator\n>()('@devstack/orchestrators/Snapshot') {}\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\n/** Mint a snapshot id when the caller didn't pass one. Carries an\n * 8-hex random suffix from `crypto.randomUUID()` (STYLE_GUIDE §17) so\n * concurrent saves don't silently overwrite. */\nconst mintId = (prefix = 'snap'): string => `${prefix}-${Date.now()}-${mintRandomSuffix(8)}`;\n\nconst mintSnapshotId = (): Effect.Effect<SnapshotId, SnapshotDescriptorError> => {\n\tconst raw = mintId();\n\tconst id = parseSnapshotId(raw);\n\treturn id === null\n\t\t? Effect.fail(\n\t\t\t\tnew SnapshotDescriptorError({\n\t\t\t\t\tkind: 'invalid-id',\n\t\t\t\t\tdetail: 'internal snapshot id minting produced an invalid id',\n\t\t\t\t\tvalue: raw,\n\t\t\t\t}),\n\t\t\t)\n\t\t: Effect.succeed(id);\n};\n\nconst mintSnapshotName = (): string => {\n\tconst stamp = new Date().toISOString().replaceAll('-', '').replaceAll(':', '').slice(0, 15);\n\treturn `manual-${stamp}-${mintRandomSuffix(8)}`;\n};\n\nconst validateSnapshotId = (\n\toperation: SnapshotIdError['operation'],\n\tvalue: string,\n): Effect.Effect<SnapshotId, SnapshotIdError> => {\n\tconst id = parseSnapshotId(value);\n\treturn id === null\n\t\t? Effect.fail(\n\t\t\t\tnew SnapshotIdError({\n\t\t\t\t\toperation,\n\t\t\t\t\tfield: 'id',\n\t\t\t\t\tvalue,\n\t\t\t\t\tdetail: SNAPSHOT_ID_RULE,\n\t\t\t\t}),\n\t\t\t)\n\t\t: Effect.succeed(id);\n};\n\nconst normalizeSnapshotName = (\n\toperation: SnapshotIdError['operation'],\n\tname: string | undefined,\n): Effect.Effect<string, SnapshotIdError> => {\n\tif (name === undefined) return Effect.succeed(mintSnapshotName());\n\tconst normalized = name.trim();\n\tif (normalized.length === 0 || normalized.includes('\\0') || normalized.length > 128) {\n\t\treturn Effect.fail(\n\t\t\tnew SnapshotIdError({\n\t\t\t\toperation,\n\t\t\t\tfield: 'name',\n\t\t\t\tvalue: name,\n\t\t\t\tdetail:\n\t\t\t\t\t'snapshot names must be 1-128 characters after trimming and cannot contain NUL bytes',\n\t\t\t}),\n\t\t);\n\t}\n\treturn Effect.succeed(normalized);\n};\n\n/** Read this process's canonical startTime stamp for the snapshot\n * reservation liveness check. Returns `null` when the kernel-probe\n * couldn't determine startTime — matches `RosterHolderSchema.startTime`\n * semantics and is honored by the sweep's `checkHolderLiveness`\n * null-conservative short-circuit. */\nconst ownStartTime = (): number | null => ownHolder('snapshot').startTime;\n\nconst normalizeIdentityValue = (value: unknown): unknown => {\n\tif (Array.isArray(value)) return value.map(normalizeIdentityValue);\n\tif (value !== null && typeof value === 'object') {\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(value)\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([key, nested]) => [key, normalizeIdentityValue(nested)]),\n\t\t);\n\t}\n\treturn value;\n};\n\nconst stableIdentityString = (value: unknown): string => {\n\tconst encoded = JSON.stringify(normalizeIdentityValue(value));\n\treturn encoded ?? String(value);\n};\n\nconst identityHookFromDecl = (\n\tpluginKey: string,\n\tdecl: SnapshotableDecl,\n): Effect.Effect<Readonly<Record<string, string>>> =>\n\tdecl.preRestore === undefined\n\t\t? Effect.succeed({})\n\t\t: decl.preRestore.pipe(Effect.map((value) => ({ [pluginKey]: stableIdentityString(value) })));\n\n// -----------------------------------------------------------------------------\n// Layer\n// -----------------------------------------------------------------------------\n\n/**\n * Wire the orchestrator from the substrate's path resolver + the\n * runtime container adapter.\n *\n * Discipline:\n * - Capture/restore acquire `snapshot.reservation` (O_EXCL,\n * no retry) — concurrent capture is structurally refused.\n * - List/delete/wipe acquire `stack.lock` briefly (no reservation\n * needed; they don't pause containers).\n * - Prune acquires both — it walks the catalog AND sweeps images.\n */\n/** Per-plugin participant registration entry kept on the orchestrator's\n * inner ref. Sequence number lets parallel registrations under the\n * same key be reaped independently when their owning scopes close. */\ninterface RegisteredParticipantEntry {\n\treadonly pluginKey: string;\n\treadonly capture: SnapshotParticipant;\n\treadonly restore: RestoreParticipant;\n\treadonly seq: number;\n}\n\nexport const layerSnapshotOrchestrator: Layer.Layer<\n\tSnapshotOrchestratorService,\n\tnever,\n\tStackPathsService | IdentityContext | ContainerRuntimeService\n> = Layer.effect(\n\tSnapshotOrchestratorService,\n\tEffect.gen(function* () {\n\t\tconst paths = yield* StackPathsService;\n\t\tconst identity = yield* IdentityContext;\n\t\tconst runtime: ContainerRuntime = yield* ContainerRuntimeService;\n\n\t\t// Scope-local participant registry. The supervisor adds entries\n\t\t// from each plugin's acquire scope; finalizers remove them on\n\t\t// scope close. Sequence number protects against the parallel-\n\t\t// registration-same-key race when two stack instances register\n\t\t// concurrently.\n\t\tconst participantsRef = yield* Ref.make<ReadonlyArray<RegisteredParticipantEntry>>([]);\n\t\tconst seqRef = yield* Ref.make(0);\n\n\t\tconst registerParticipant: SnapshotOrchestrator['registerParticipant'] = (\n\t\t\tpluginKey,\n\t\t\tdecl,\n\t\t\thooks,\n\t\t) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst seq = yield* Ref.updateAndGet(seqRef, (n) => n + 1);\n\t\t\t\tconst declIdentity = identityHookFromDecl(pluginKey, decl);\n\t\t\t\tconst capture: SnapshotParticipant = {\n\t\t\t\t\tplugin: pluginKey,\n\t\t\t\t\tdecl,\n\t\t\t\t\tcaptureIdentity: hooks?.captureIdentity ?? declIdentity,\n\t\t\t\t\tcaptureContribution: hooks?.captureContribution ?? Effect.succeed<unknown>(undefined),\n\t\t\t\t};\n\t\t\t\tconst restore: RestoreParticipant = {\n\t\t\t\t\tplugin: pluginKey,\n\t\t\t\t\tliveIdentity: hooks?.liveIdentity ?? declIdentity,\n\t\t\t\t\tpostRestore: decl.postRestore as Effect.Effect<void> | undefined,\n\t\t\t\t};\n\t\t\t\tconst entry: RegisteredParticipantEntry = { pluginKey, capture, restore, seq };\n\t\t\t\tyield* Ref.update(participantsRef, (xs) => [...xs, entry]);\n\t\t\t\tyield* Effect.addFinalizer(() =>\n\t\t\t\t\tRef.update(participantsRef, (xs) => xs.filter((e) => e.seq !== seq)),\n\t\t\t\t);\n\t\t\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t\t\t'snapshot.participant.plugin': pluginKey,\n\t\t\t\t\t'snapshot.participant.subtrees': decl.subtrees.length,\n\t\t\t\t});\n\t\t\t}).pipe(Effect.withSpan('orchestrator.snapshot.registerParticipant')) as Effect.Effect<\n\t\t\t\tvoid,\n\t\t\t\tnever,\n\t\t\t\tScope.Scope\n\t\t\t>;\n\n\t\tconst capture: SnapshotOrchestrator['capture'] = ({ id, label, participants, onProgress }) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst snapshotId =\n\t\t\t\t\tid === undefined ? yield* mintSnapshotId() : yield* validateSnapshotId('capture', id);\n\t\t\t\tconst snapshotName = yield* normalizeSnapshotName('capture', label);\n\t\t\t\tconst effectiveParticipants =\n\t\t\t\t\tparticipants ?? (yield* Ref.get(participantsRef)).map((e) => e.capture);\n\t\t\t\tconst artifactDir = `${paths.snapshotDir}/${snapshotId}`;\n\t\t\t\t// Snapshot stages at the SNAPSHOT-DIR level (siblings of\n\t\t\t\t// other artifact dirs, NOT siblings of `artifactDir`) so\n\t\t\t\t// the `list` walker's `.staging.` / `.bak.` prefix skip\n\t\t\t\t// at line 517 keeps transient dirs invisible without\n\t\t\t\t// having to descend a level. This layout is structurally\n\t\t\t\t// distinct from `${target}.staging.<id>` so we pass\n\t\t\t\t// explicit `stagingPath`/`backupPath` instead of\n\t\t\t\t// `idSuffix` (the substrate primitive supports both\n\t\t\t\t// shapes — see `stageAndSwap`'s union arg).\n\t\t\t\tconst stagingDir = `${paths.snapshotDir}/.staging.${snapshotId}`;\n\t\t\t\tconst backupDir = `${paths.snapshotDir}/.bak.${snapshotId}`;\n\n\t\t\t\t// Acquire `snapshot.reservation` (O_EXCL, no retry) for the\n\t\t\t\t// duration of the capture. The stack.lock is acquired\n\t\t\t\t// briefly inside `stageAndSwap` for the rename publish.\n\t\t\t\treturn yield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireReservation(paths.snapshotReservationFile, ownStartTime());\n\t\t\t\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\t\t\t\tconst targetExists = yield* fs\n\t\t\t\t\t\t\t.exists(artifactDir)\n\t\t\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\t\t\t\t\tif (targetExists) {\n\t\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\t\tnew SnapshotIdError({\n\t\t\t\t\t\t\t\t\toperation: 'capture',\n\t\t\t\t\t\t\t\t\tfield: 'id',\n\t\t\t\t\t\t\t\t\tvalue: snapshotId,\n\t\t\t\t\t\t\t\t\tdetail: 'snapshot id already exists',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// O(N) catalog scan for label uniqueness — acceptable\n\t\t\t\t\t\t// because N (snapshot count per stack) is small in\n\t\t\t\t\t\t// practice and capture is a rare, human-paced verb. A\n\t\t\t\t\t\t// future optimization could maintain a sibling name-\n\t\t\t\t\t\t// index (e.g. `${snapshotDir}/.names/${label}` empty\n\t\t\t\t\t\t// marker files) that lets uniqueness collapse to a\n\t\t\t\t\t\t// single `fs.exists`, but that would need a paired\n\t\t\t\t\t\t// cleanup-on-delete pathway + a corruption-tolerance\n\t\t\t\t\t\t// story for the sidecar, which is outside this scope.\n\t\t\t\t\t\t//\n\t\t\t\t\t\t// Forward-reference safety: `list` is declared LATER\n\t\t\t\t\t\t// in this outer `Effect.gen` body. No TDZ fires\n\t\t\t\t\t\t// because `capture` is an arrow function returned\n\t\t\t\t\t\t// from the gen — by the time any caller invokes it,\n\t\t\t\t\t\t// the outer gen has fully resolved and all `const`\n\t\t\t\t\t\t// bindings (including `list`) are bound. The\n\t\t\t\t\t\t// regression guard at\n\t\t\t\t\t\t// test/orchestrators/snapshot/capture-collision-tdz.test.ts\n\t\t\t\t\t\t// pins this; if a future refactor wraps `list` in\n\t\t\t\t\t\t// an `Effect.fn` decorator or extracts it through a\n\t\t\t\t\t\t// hoisting-sensitive path, the guard will fail.\n\t\t\t\t\t\tconst existing = yield* list;\n\t\t\t\t\t\tif (existing.some((entry) => entry.metadata?.label === snapshotName)) {\n\t\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\t\tnew SnapshotIdError({\n\t\t\t\t\t\t\t\t\toperation: 'capture',\n\t\t\t\t\t\t\t\t\tfield: 'name',\n\t\t\t\t\t\t\t\t\tvalue: snapshotName,\n\t\t\t\t\t\t\t\t\tdetail: 'snapshot name already exists',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Stage-and-swap publishes the artifact directory\n\t\t\t\t\t\t// atomically — external watchers (and `list`)\n\t\t\t\t\t\t// never observe a half-written tree.\n\t\t\t\t\t\tconst meta = yield* stageAndSwap({\n\t\t\t\t\t\t\ttargetPath: artifactDir,\n\t\t\t\t\t\t\tstagingPath: stagingDir,\n\t\t\t\t\t\t\tbackupPath: backupDir,\n\t\t\t\t\t\t\tbuild: runCapture({\n\t\t\t\t\t\t\t\tstagingDir,\n\t\t\t\t\t\t\t\tsnapshotId,\n\t\t\t\t\t\t\t\tlabel: snapshotName,\n\t\t\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\t\t\tnetwork: identity.chain,\n\t\t\t\t\t\t\t\truntimeStackRoot: paths.stackRoot,\n\t\t\t\t\t\t\t\tstateFilePath: paths.stateFile,\n\t\t\t\t\t\t\t\tparticipants: effectiveParticipants,\n\t\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\t\tonProgress,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn meta;\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}).pipe(Effect.withSpan('orchestrator.snapshot.capture.entry'));\n\n\t\tconst restore: SnapshotOrchestrator['restore'] = ({ id, participants }) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst snapshotId = yield* validateSnapshotId('restore', id);\n\t\t\t\tconst artifactDir = `${paths.snapshotDir}/${snapshotId}`;\n\t\t\t\tconst effectiveParticipants =\n\t\t\t\t\tparticipants ?? (yield* Ref.get(participantsRef)).map((e) => e.restore);\n\t\t\t\treturn yield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireReservation(paths.snapshotReservationFile, ownStartTime());\n\t\t\t\t\t\treturn yield* runRestore({\n\t\t\t\t\t\t\tsnapshotId,\n\t\t\t\t\t\t\tartifactDir,\n\t\t\t\t\t\t\truntimeStackRoot: paths.stackRoot,\n\t\t\t\t\t\t\truntimeStagingPath: `${paths.stackRoot}.staging.${snapshotId}`,\n\t\t\t\t\t\t\truntimeBackupPath: `${paths.stackRoot}.bak.${snapshotId}`,\n\t\t\t\t\t\t\tparticipants: effectiveParticipants,\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\truntimeIdentity: {\n\t\t\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\t\t\tnetwork: identity.chain,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}).pipe(Effect.withSpan('orchestrator.snapshot.restore.entry'));\n\n\t\tconst list: SnapshotOrchestrator['list'] = Effect.gen(function* () {\n\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\tconst exists = yield* fs\n\t\t\t\t.exists(paths.snapshotDir)\n\t\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\t\tif (!exists) return [];\n\t\t\tconst ids = yield* fs\n\t\t\t\t.readDirectory(paths.snapshotDir)\n\t\t\t\t.pipe(Effect.catch(() => Effect.succeed([] as ReadonlyArray<string>)));\n\t\t\tconst entries: SnapshotCatalogEntry[] = [];\n\t\t\tfor (const id of ids) {\n\t\t\t\t// Skip transient staging/backup directories — they're\n\t\t\t\t// invisible to the catalog by construction (the\n\t\t\t\t// `.staging.<id>` / `.bak.<id>` naming).\n\t\t\t\tif (id.startsWith('.staging.') || id.startsWith('.bak.')) continue;\n\t\t\t\tconst parsedId = parseSnapshotId(id);\n\t\t\t\tif (parsedId === null) {\n\t\t\t\t\tyield* Effect.logWarning(`ignoring unsafe snapshot catalog entry: ${id}`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst dir = `${paths.snapshotDir}/${id}`;\n\t\t\t\tconst metaPath = `${dir}/${SnapshotLayout.metaFile}`;\n\t\t\t\tconst metaExists = yield* fs\n\t\t\t\t\t.exists(metaPath)\n\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\t\t\tif (!metaExists) {\n\t\t\t\t\tentries.push({ id, directory: dir, metadata: null });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst text = yield* fs\n\t\t\t\t\t.readFileString(metaPath)\n\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed('')));\n\t\t\t\tif (text === '') {\n\t\t\t\t\tentries.push({ id, directory: dir, metadata: null });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst decoded = yield* decodeJsonText(SnapshotMetadataSchema, text, {\n\t\t\t\t\tsource: metaPath,\n\t\t\t\t\tmkError: () => null,\n\t\t\t\t}).pipe(Effect.catch(() => Effect.succeed(null)));\n\t\t\t\tif (decoded !== null && parseSnapshotId(decoded.id) === null) {\n\t\t\t\t\tyield* Effect.logWarning(`ignoring snapshot metadata with unsafe id: ${decoded.id}`);\n\t\t\t\t\tentries.push({ id, directory: dir, metadata: null });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tentries.push({ id: parsedId, directory: dir, metadata: decoded });\n\t\t\t}\n\t\t\treturn entries;\n\t\t}).pipe(Effect.withSpan('orchestrator.snapshot.list'));\n\n\t\tconst del: SnapshotOrchestrator['delete'] = (id) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst snapshotId = yield* validateSnapshotId('delete', id);\n\t\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\t\tconst dir = `${paths.snapshotDir}/${snapshotId}`;\n\t\t\t\tyield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireStackLock(paths.stackLockFile);\n\t\t\t\t\t\tyield* fs.remove(dir, { recursive: true, force: true }).pipe(Effect.ignore);\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}).pipe(Effect.withSpan('orchestrator.snapshot.delete'));\n\n\t\tconst wipe: SnapshotOrchestrator['wipe'] = (args) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireStackLock(paths.stackLockFile);\n\t\t\t\t\t\tyield* runWipe({\n\t\t\t\t\t\t\tlabelMatch: { app: identity.app, stack: identity.stack },\n\t\t\t\t\t\t\tstackRoot: paths.stackRoot,\n\t\t\t\t\t\t\tstateFilePath: paths.stateFile,\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tkeepSnapshots: args.keepSnapshots,\n\t\t\t\t\t\t\tkeepCache: args.keepCache,\n\t\t\t\t\t\t});\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}).pipe(Effect.withSpan('orchestrator.snapshot.wipe.entry'));\n\n\t\tconst wipePlan: SnapshotOrchestrator['wipePlan'] = (args) =>\n\t\t\t// Read-only: no stack-lock / reservation. `planWipe` only lists\n\t\t\t// matching containers and reads the stack-root directory, so it\n\t\t\t// is safe to run without serializing against peers (a concurrent\n\t\t\t// mutation just makes the preview slightly stale — acceptable for\n\t\t\t// a dry-run estimate).\n\t\t\tplanWipe({\n\t\t\t\tlabelMatch: { app: identity.app, stack: identity.stack },\n\t\t\t\tstackRoot: paths.stackRoot,\n\t\t\t\tstateFilePath: paths.stateFile,\n\t\t\t\truntime,\n\t\t\t\tkeepSnapshots: args.keepSnapshots,\n\t\t\t\tkeepCache: args.keepCache,\n\t\t\t}).pipe(Effect.withSpan('orchestrator.snapshot.wipe.plan.entry'));\n\n\t\tconst prune: SnapshotOrchestrator['prune'] = () =>\n\t\t\tEffect.scoped(\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* acquireReservation(paths.snapshotReservationFile, ownStartTime());\n\t\t\t\t\treturn yield* runPrune({\n\t\t\t\t\t\tstackRoot: paths.stackRoot,\n\t\t\t\t\t\timageLabelFilter: { app: identity.app, stack: identity.stack },\n\t\t\t\t\t\truntime,\n\t\t\t\t\t});\n\t\t\t\t}),\n\t\t\t).pipe(Effect.withSpan('orchestrator.snapshot.prune.entry'));\n\n\t\tconst recoverPendingRestoreImpl: SnapshotOrchestrator['recoverPendingRestore'] =\n\t\t\trecoverPendingRestore(paths.stackRoot, runtime).pipe(\n\t\t\t\tEffect.withSpan('orchestrator.snapshot.recover-pending.entry'),\n\t\t\t);\n\n\t\treturn SnapshotOrchestratorService.of({\n\t\t\tregisterParticipant,\n\t\t\tcapture,\n\t\t\trestore,\n\t\t\tlist,\n\t\t\tdelete: del,\n\t\t\twipe,\n\t\t\twipePlan,\n\t\t\tprune,\n\t\t\trecoverPendingRestore: recoverPendingRestoreImpl,\n\t\t});\n\t}),\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"service.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/service.ts"],"sourcesContent":["// Snapshot orchestrator service.\n//\n// Snapshotting is a parameterization of the lifecycle bounce, not a\n// separate subsystem:\n//\n// gracefully stop everything → snapshot (docker commit each container +\n// capture local files) / swap-in (restore) → resume if needed.\n//\n// The two mutate verbs (`capture`, `restore`) share that bounce; `list`,\n// `delete`, `wipe`, `prune` are the catalog/teardown surface around it.\n//\n// Lock discipline: the bounce holds `stack.lock` for the (bounded,\n// whole-stack-stopped) snapshot window. There is no separate snapshot\n// reservation — the stop+commit window is bounded and the lock subsumes\n// the concurrency guard while keeping the roster heartbeat from starving.\n//\n// The service surface is name-blind. The participant list is populated by\n// the substrate at boot from the `Snapshotable` decls of registered\n// plugins; service-specific behavior never reaches this file. Registration\n// is scope-bound: the supervisor calls `registerParticipant(pluginKey,\n// decl)` from inside each plugin's scope so the entry is reaped on teardown.\n\nimport { mintRandomSuffix } from '../../substrate/runtime/random-suffix.ts';\n\nimport { Context, Effect, FileSystem, Layer, Ref, Schema, Scope } from 'effect';\n\nimport type { SnapshotableDecl } from '../../contracts/snapshotable.ts';\nimport type { ContainerRuntime } from '../../contracts/container-runtime.ts';\nimport { ContainerRuntimeService } from '../../runtime/docker/index.ts';\nimport { decodeJsonText } from '../../substrate/runtime/runtime-decode.ts';\nimport {\n\tacquireStackLock,\n\ttype StackLockError,\n} from '../../substrate/runtime/cross-process/stack-lock.ts';\nimport { IdentityContext, StackPathsService } from '../../substrate/runtime/paths.ts';\nimport {\n\trunCapture,\n\tresumeAfterCapture,\n\ttype CapturePhaseError,\n\ttype SnapshotParticipant,\n} from './capture.ts';\nimport {\n\tSnapshotDescriptorError,\n\tSnapshotLayout,\n\tSnapshotMetadataSchema,\n\tSNAPSHOT_ID_RULE,\n\tparseSnapshotId,\n\ttype SnapshotId,\n\ttype SnapshotCatalogEntry,\n\ttype SnapshotGraphInputIdentity,\n\ttype SnapshotMetadata,\n} from './descriptor.ts';\nimport {\n\ttype IdentityContributionConflictError,\n\ttype IdentityGuardError,\n} from './identity-guard.ts';\nimport { runPrune, type PruneResult, type PrunePhaseError } from './prune.ts';\nimport { runRestore, RestorePhaseError, type RestoreParticipant } from './restore.ts';\nimport {\n\tstageAndSwap,\n\ttype StageAndSwapError,\n} from '../../substrate/runtime/stage-and-swap/index.ts';\nimport { planWipe, runWipe, type WipePhaseError, type WipeTargets } from './wipe.ts';\n\n// -----------------------------------------------------------------------------\n// The service shape\n// -----------------------------------------------------------------------------\n\n/** Tagged union of every error the orchestrator surface can surface.\n * Callers `catchTags` on the precise tags they care about. */\nexport type SnapshotOrchestratorError =\n\t| CapturePhaseError\n\t| RestorePhaseError\n\t| WipePhaseError\n\t| PrunePhaseError\n\t| IdentityGuardError\n\t| IdentityContributionConflictError\n\t| StageAndSwapError\n\t| StackLockError\n\t| SnapshotIdError\n\t| SnapshotDescriptorError;\n\nexport class SnapshotIdError extends Schema.TaggedErrorClass<SnapshotIdError>()('SnapshotIdError', {\n\toperation: Schema.Literals(['capture', 'restore', 'delete']),\n\tfield: Schema.Literals(['id', 'name']),\n\tvalue: Schema.String,\n\tdetail: Schema.String,\n}) {}\n\nexport interface SnapshotOrchestrator {\n\t/** Register a `SnapshotableDecl` from a plugin. Scope-bound — when\n\t * the caller's scope (the plugin's acquire scope) closes, the\n\t * registration is reaped. The supervisor calls this once per\n\t * `SnapshotableDecl` on each plugin's `capabilities` tuple.\n\t *\n\t * `captureIdentity` / `captureContribution` are separately wired\n\t * hooks (not on `SnapshotableDecl` itself) — they plumb through\n\t * the plugin's resolved value. Identity is validated by the\n\t * orchestrator; contribution payloads are persisted as opaque JSON\n\t * that plugins must validate at their own boundary if they read it. */\n\treadonly registerParticipant: (\n\t\tpluginKey: string,\n\t\tdecl: SnapshotableDecl,\n\t\thooks?: {\n\t\t\treadonly captureIdentity?: Effect.Effect<Readonly<Record<string, string>>>;\n\t\t\treadonly captureContribution?: Effect.Effect<unknown>;\n\t\t\treadonly liveIdentity?: Effect.Effect<Readonly<Record<string, string>>>;\n\t\t},\n\t) => Effect.Effect<void, never, Scope.Scope>;\n\n\t/** Capture a new snapshot — the lifecycle bounce: gather → graceful-stop\n\t * (flush) → commit + tar + meta → retag committed images onto original\n\t * names + hard-rm → resume (recreate + wait-write-ready). The id MAY be\n\t * caller-supplied; if omitted, the substrate mints a random suffix to\n\t * sidestep the concurrent-saves-against-same-id silent overwrite.\n\t *\n\t * `resume` re-converges the stack to write-ready after the publish (the\n\t * supervisor wires it to a stack restart; omit on an offline one-shot\n\t * capture where the next boot is the resume).\n\t *\n\t * `participants` overrides the registered set — primarily for tests.\n\t * Production callers omit it; the orchestrator reads the registered list. */\n\treadonly capture: (args: {\n\t\treadonly id?: string;\n\t\treadonly label?: string;\n\t\treadonly graphInput: SnapshotGraphInputIdentity;\n\t\treadonly replaceExistingLabel?: boolean;\n\t\treadonly participants?: ReadonlyArray<SnapshotParticipant>;\n\t\treadonly resume?: Effect.Effect<void>;\n\t}) => Effect.Effect<SnapshotMetadata, SnapshotOrchestratorError, FileSystem.FileSystem>;\n\n\t/** Restore from a captured artifact id — the destructive,\n\t * ordered half of the bounce: identity-guard fail-closed + cache-missing\n\t * preflight BEFORE any mutation → swap host-tree in + load images → hard-\n\t * rm → resume = recreate (the next boot / the injected converge). */\n\treadonly restore: (args: {\n\t\treadonly id: string;\n\t\treadonly currentGraphInput?: SnapshotGraphInputIdentity;\n\t\treadonly graphInputMismatchPolicy?: 'warn' | 'block';\n\t\treadonly participants?: ReadonlyArray<RestoreParticipant>;\n\t\treadonly resume?: Effect.Effect<void>;\n\t}) => Effect.Effect<SnapshotMetadata, SnapshotOrchestratorError, FileSystem.FileSystem>;\n\n\t/** Catalog list — tolerates partial / corrupt entries (they appear\n\t * with `metadata: null`). */\n\treadonly list: Effect.Effect<\n\t\tReadonlyArray<SnapshotCatalogEntry>,\n\t\tSnapshotOrchestratorError,\n\t\tFileSystem.FileSystem\n\t>;\n\n\t/** Delete one artifact by id. Idempotent — missing id is a no-op. */\n\treadonly delete: (\n\t\tid: string,\n\t) => Effect.Effect<void, SnapshotOrchestratorError, FileSystem.FileSystem>;\n\n\t/** Wipe the live (`app`, `stack`) footprint; preserves the snapshot\n\t * catalog AND the deploy cache together by default (`keepSnapshots`,\n\t * default true). A hard reset (`keepSnapshots: false`) drops both so\n\t * on-chain artifacts re-prove against the next chain — there is no\n\t * asymmetric keep-snapshots-drop-cache degree of freedom. */\n\treadonly wipe: (args: {\n\t\treadonly keepSnapshots?: boolean;\n\t}) => Effect.Effect<void, SnapshotOrchestratorError, FileSystem.FileSystem>;\n\n\t/** Enumerate the concrete teardown targets a `wipe` of the same\n\t * `(app, stack)` would remove WITHOUT removing anything — the\n\t * read-only preview behind `devstack wipe --dry-run`. */\n\treadonly wipePlan: (args: {\n\t\treadonly keepSnapshots?: boolean;\n\t}) => Effect.Effect<WipeTargets, SnapshotOrchestratorError, FileSystem.FileSystem>;\n\n\t/** Prune the snapshot catalog (reaps partial artifacts) and sweeps\n\t * byproduct images via the runtime adapter's label-scoped cleanup. */\n\treadonly prune: () => Effect.Effect<\n\t\tPruneResult,\n\t\tSnapshotOrchestratorError,\n\t\tFileSystem.FileSystem\n\t>;\n}\n\nexport class SnapshotOrchestratorService extends Context.Service<\n\tSnapshotOrchestratorService,\n\tSnapshotOrchestrator\n>()('@devstack/orchestrators/Snapshot') {}\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\n/** Mint a snapshot id when the caller didn't pass one. Carries an\n * 8-hex random suffix from `crypto.randomUUID()` (STYLE_GUIDE §17) so\n * concurrent saves don't silently overwrite. */\nconst mintId = (prefix = 'snap'): string => `${prefix}-${Date.now()}-${mintRandomSuffix(8)}`;\n\nconst mintSnapshotId = (): Effect.Effect<SnapshotId, SnapshotDescriptorError> => {\n\tconst raw = mintId();\n\tconst id = parseSnapshotId(raw);\n\treturn id === null\n\t\t? Effect.fail(\n\t\t\t\tnew SnapshotDescriptorError({\n\t\t\t\t\tkind: 'invalid-id',\n\t\t\t\t\tdetail: 'internal snapshot id minting produced an invalid id',\n\t\t\t\t\tvalue: raw,\n\t\t\t\t}),\n\t\t\t)\n\t\t: Effect.succeed(id);\n};\n\nconst mintSnapshotName = (): string => {\n\tconst stamp = new Date().toISOString().replaceAll('-', '').replaceAll(':', '').slice(0, 15);\n\treturn `manual-${stamp}-${mintRandomSuffix(8)}`;\n};\n\nconst validateSnapshotId = (\n\toperation: SnapshotIdError['operation'],\n\tvalue: string,\n): Effect.Effect<SnapshotId, SnapshotIdError> => {\n\tconst id = parseSnapshotId(value);\n\treturn id === null\n\t\t? Effect.fail(\n\t\t\t\tnew SnapshotIdError({\n\t\t\t\t\toperation,\n\t\t\t\t\tfield: 'id',\n\t\t\t\t\tvalue,\n\t\t\t\t\tdetail: SNAPSHOT_ID_RULE,\n\t\t\t\t}),\n\t\t\t)\n\t\t: Effect.succeed(id);\n};\n\nconst normalizeSnapshotName = (\n\toperation: SnapshotIdError['operation'],\n\tname: string | undefined,\n): Effect.Effect<string, SnapshotIdError> => {\n\tif (name === undefined) return Effect.succeed(mintSnapshotName());\n\tconst normalized = name.trim();\n\tif (normalized.length === 0 || normalized.includes('\\0') || normalized.length > 128) {\n\t\treturn Effect.fail(\n\t\t\tnew SnapshotIdError({\n\t\t\t\toperation,\n\t\t\t\tfield: 'name',\n\t\t\t\tvalue: name,\n\t\t\t\tdetail:\n\t\t\t\t\t'snapshot names must be 1-128 characters after trimming and cannot contain NUL bytes',\n\t\t\t}),\n\t\t);\n\t}\n\treturn Effect.succeed(normalized);\n};\n\nconst normalizeIdentityValue = (value: unknown): unknown => {\n\tif (Array.isArray(value)) return value.map(normalizeIdentityValue);\n\tif (value !== null && typeof value === 'object') {\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(value)\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([key, nested]) => [key, normalizeIdentityValue(nested)]),\n\t\t);\n\t}\n\treturn value;\n};\n\nconst stableIdentityString = (value: unknown): string => {\n\tconst encoded = JSON.stringify(normalizeIdentityValue(value));\n\treturn encoded ?? String(value);\n};\n\nconst identityHookFromDecl = (\n\tpluginKey: string,\n\tdecl: SnapshotableDecl,\n): Effect.Effect<Readonly<Record<string, string>>> =>\n\tdecl.preRestore === undefined\n\t\t? Effect.succeed({})\n\t\t: decl.preRestore.pipe(Effect.map((value) => ({ [pluginKey]: stableIdentityString(value) })));\n\n// -----------------------------------------------------------------------------\n// Layer\n// -----------------------------------------------------------------------------\n\n/** Per-plugin participant registration entry kept on the orchestrator's\n * inner ref. Sequence number lets parallel registrations under the\n * same key be reaped independently when their owning scopes close. */\ninterface RegisteredParticipantEntry {\n\treadonly pluginKey: string;\n\treadonly capture: SnapshotParticipant;\n\treadonly restore: RestoreParticipant;\n\treadonly seq: number;\n}\n\n/**\n * Wire the orchestrator from the substrate's path resolver + the runtime\n * container adapter.\n *\n * Discipline: capture/restore acquire `stack.lock` for the (bounded,\n * whole-stack-stopped) snapshot window; the `stageAndSwap` rename publishes\n * atomically inside it. List/delete/wipe/prune acquire `stack.lock` briefly\n * (they don't bounce containers).\n */\nexport const layerSnapshotOrchestrator: Layer.Layer<\n\tSnapshotOrchestratorService,\n\tnever,\n\tStackPathsService | IdentityContext | ContainerRuntimeService\n> = Layer.effect(\n\tSnapshotOrchestratorService,\n\tEffect.gen(function* () {\n\t\tconst paths = yield* StackPathsService;\n\t\tconst identity = yield* IdentityContext;\n\t\tconst runtime: ContainerRuntime = yield* ContainerRuntimeService;\n\n\t\t// Scope-local participant registry. The supervisor adds entries from\n\t\t// each plugin's acquire scope; finalizers remove them on scope close.\n\t\t// Sequence number protects against the parallel-registration-same-key\n\t\t// race when two stack instances register concurrently.\n\t\tconst participantsRef = yield* Ref.make<ReadonlyArray<RegisteredParticipantEntry>>([]);\n\t\tconst seqRef = yield* Ref.make(0);\n\n\t\tconst registerParticipant: SnapshotOrchestrator['registerParticipant'] = (\n\t\t\tpluginKey,\n\t\t\tdecl,\n\t\t\thooks,\n\t\t) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst seq = yield* Ref.updateAndGet(seqRef, (n) => n + 1);\n\t\t\t\tconst declIdentity = identityHookFromDecl(pluginKey, decl);\n\t\t\t\tconst capture: SnapshotParticipant = {\n\t\t\t\t\tplugin: pluginKey,\n\t\t\t\t\tdecl,\n\t\t\t\t\tcaptureIdentity: hooks?.captureIdentity ?? declIdentity,\n\t\t\t\t\tcaptureContribution: hooks?.captureContribution ?? Effect.succeed<unknown>(undefined),\n\t\t\t\t};\n\t\t\t\tconst restore: RestoreParticipant = {\n\t\t\t\t\tplugin: pluginKey,\n\t\t\t\t\tliveIdentity: hooks?.liveIdentity ?? declIdentity,\n\t\t\t\t\tpostRestore: decl.postRestore as Effect.Effect<void> | undefined,\n\t\t\t\t};\n\t\t\t\tconst entry: RegisteredParticipantEntry = { pluginKey, capture, restore, seq };\n\t\t\t\tyield* Ref.update(participantsRef, (xs) => [...xs, entry]);\n\t\t\t\tyield* Effect.addFinalizer(() =>\n\t\t\t\t\tRef.update(participantsRef, (xs) => xs.filter((e) => e.seq !== seq)),\n\t\t\t\t);\n\t\t\t}) as Effect.Effect<void, never, Scope.Scope>;\n\n\t\tconst capture: SnapshotOrchestrator['capture'] = ({\n\t\t\tid,\n\t\t\tlabel,\n\t\t\tgraphInput,\n\t\t\treplaceExistingLabel,\n\t\t\tparticipants,\n\t\t\tresume,\n\t\t}) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst snapshotId =\n\t\t\t\t\tid === undefined ? yield* mintSnapshotId() : yield* validateSnapshotId('capture', id);\n\t\t\t\tconst snapshotName = yield* normalizeSnapshotName('capture', label);\n\t\t\t\tconst effectiveParticipants =\n\t\t\t\t\tparticipants ?? (yield* Ref.get(participantsRef)).map((e) => e.capture);\n\t\t\t\tconst artifactDir = `${paths.snapshotDir}/${snapshotId}`;\n\t\t\t\t// Snapshot stages at the SNAPSHOT-DIR level (siblings of other\n\t\t\t\t// artifact dirs) so the `list` walker's `.staging.` / `.bak.`\n\t\t\t\t// prefix skip keeps transient dirs invisible.\n\t\t\t\tconst stagingDir = `${paths.snapshotDir}/.staging.${snapshotId}`;\n\t\t\t\tconst backupDir = `${paths.snapshotDir}/.bak.${snapshotId}`;\n\n\t\t\t\t// Hold `stack.lock` ONLY across the build+publish half of the\n\t\t\t\t// bounce (gather → graceful-stop → commit + tar + meta →\n\t\t\t\t// stage-and-swap). The lock subsumes the concurrency guard for\n\t\t\t\t// that bounded window while keeping the roster heartbeat from\n\t\t\t\t// starving.\n\t\t\t\t//\n\t\t\t\t// CRITICAL: the lock is RELEASED before the resume. The post-\n\t\t\t\t// publish resume re-converges the stack, and each plugin's\n\t\t\t\t// re-acquire takes `stack.lock` for its container-claim protocol\n\t\t\t\t// (runtime/docker/container.ts) — `stack.lock` is a non-reentrant\n\t\t\t\t// O_EXCL file lock, so holding it across the resume self-deadlocks:\n\t\t\t\t// the re-acquire's claim EEXISTs against THIS process's own still-\n\t\t\t\t// held lock, can't reclaim it (the holder PID is alive — us), and\n\t\t\t\t// times out after 5s → every plugin's `start` fails → the resume\n\t\t\t\t// comes back `failed` (sui RPC dead → `fetch failed`; codegen\n\t\t\t\t// contributions never re-register → empty `config.ts`). Scoping the\n\t\t\t\t// lock to the publish and running `resumeAfterCapture` AFTER it\n\t\t\t\t// releases lets the re-acquire claim containers normally.\n\t\t\t\tconst { meta, replacedSnapshotIds } = yield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireStackLock(paths.stackLockFile);\n\t\t\t\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\t\t\t\tconst targetExists = yield* fs\n\t\t\t\t\t\t\t.exists(artifactDir)\n\t\t\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\t\t\t\t\tif (targetExists) {\n\t\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\t\tnew SnapshotIdError({\n\t\t\t\t\t\t\t\t\toperation: 'capture',\n\t\t\t\t\t\t\t\t\tfield: 'id',\n\t\t\t\t\t\t\t\t\tvalue: snapshotId,\n\t\t\t\t\t\t\t\t\tdetail: 'snapshot id already exists',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// O(N) catalog scan for label uniqueness — N is small and\n\t\t\t\t\t\t// capture is human-paced. `list` is declared LATER in this\n\t\t\t\t\t\t// gen; no TDZ fires because `capture` is invoked only after\n\t\t\t\t\t\t// the outer gen has resolved all `const` bindings.\n\t\t\t\t\t\tconst existing = yield* list;\n\t\t\t\t\t\tconst duplicateLabels = existing.filter(\n\t\t\t\t\t\t\t(entry) => entry.metadata?.label === snapshotName,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (duplicateLabels.length > 0 && replaceExistingLabel !== true) {\n\t\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\t\tnew SnapshotIdError({\n\t\t\t\t\t\t\t\t\toperation: 'capture',\n\t\t\t\t\t\t\t\t\tfield: 'name',\n\t\t\t\t\t\t\t\t\tvalue: snapshotName,\n\t\t\t\t\t\t\t\t\tdetail: 'snapshot name already exists',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst replacedSnapshotIds = duplicateLabels\n\t\t\t\t\t\t\t.map((entry) => entry.id)\n\t\t\t\t\t\t\t.filter((entryId) => entryId !== snapshotId);\n\n\t\t\t\t\t\t// Build + publish the artifact (gather → stop → commit +\n\t\t\t\t\t\t// tar + meta), atomically via stage-and-swap so watchers /\n\t\t\t\t\t\t// `list` never observe a half-written tree.\n\t\t\t\t\t\tconst meta = yield* stageAndSwap({\n\t\t\t\t\t\t\ttargetPath: artifactDir,\n\t\t\t\t\t\t\tstagingPath: stagingDir,\n\t\t\t\t\t\t\tbackupPath: backupDir,\n\t\t\t\t\t\t\tbuild: runCapture({\n\t\t\t\t\t\t\t\tstagingDir,\n\t\t\t\t\t\t\t\tsnapshotId,\n\t\t\t\t\t\t\t\tlabel: snapshotName,\n\t\t\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\t\t\tnetwork: identity.network,\n\t\t\t\t\t\t\t\tgraphInput,\n\t\t\t\t\t\t\t\truntimeStackRoot: paths.stackRoot,\n\t\t\t\t\t\t\t\tparticipants: effectiveParticipants,\n\t\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn { meta, replacedSnapshotIds };\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\t// Post-publish bounce tail — runs with `stack.lock` RELEASED (see\n\t\t\t\t// the CRITICAL note above) so the resume's per-plugin container\n\t\t\t\t// claims can take the lock. Retag committed images onto original\n\t\t\t\t// names + hard-rm + resume (recreate + wait-write-ready). Runs AFTER\n\t\t\t\t// the swap so a publish failure leaves the live stack only stopped\n\t\t\t\t// (recoverable by the next boot).\n\t\t\t\tyield* resumeAfterCapture(meta, {\n\t\t\t\t\truntime,\n\t\t\t\t\tapp: identity.app,\n\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t...(resume === undefined ? {} : { resume }),\n\t\t\t\t});\n\t\t\t\tyield* Effect.forEach(replacedSnapshotIds, del, { discard: true });\n\t\t\t\treturn meta;\n\t\t\t});\n\n\t\tconst restore: SnapshotOrchestrator['restore'] = ({\n\t\t\tid,\n\t\t\tcurrentGraphInput,\n\t\t\tgraphInputMismatchPolicy,\n\t\t\tparticipants,\n\t\t\tresume,\n\t\t}) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst snapshotId = yield* validateSnapshotId('restore', id);\n\t\t\t\tconst artifactDir = `${paths.snapshotDir}/${snapshotId}`;\n\t\t\t\t// The effective participant set is the live registered set (or an\n\t\t\t\t// explicit override). During startup restore, interrupted-restore\n\t\t\t\t// recovery, and offline CLI restore, no plugin has registered yet,\n\t\t\t\t// so this is EMPTY. `runRestore` reads an empty\n\t\t\t\t// participant set as \"no live stack to compare against\" and skips\n\t\t\t\t// ONLY the cross-plugin contribution guard (the runtime app/stack/\n\t\t\t\t// network guard + the snapshot-side emptiness refusal still fire).\n\t\t\t\t// A LIVE-supervisor restore (operator-triggered while the stack is up)\n\t\t\t\t// has a populated registered set, so the real contribution guard runs.\n\t\t\t\tconst effectiveParticipants =\n\t\t\t\t\tparticipants ?? (yield* Ref.get(participantsRef)).map((e) => e.restore);\n\t\t\t\t// Hold `stack.lock` ONLY across the destructive swap+load+hard-rm\n\t\t\t\t// half of the bounce — the resume is run AFTER the lock releases.\n\t\t\t\t// The resume re-converges the stack and each plugin's re-acquire\n\t\t\t\t// takes `stack.lock` for its container-claim protocol; `stack.lock`\n\t\t\t\t// is a non-reentrant O_EXCL file lock, so holding it across the\n\t\t\t\t// resume self-deadlocks (the claim EEXISTs against this process's\n\t\t\t\t// own live lock and times out → every plugin fails). The offline\n\t\t\t\t// restore omits `resume` (the next boot is the resume), so this only\n\t\t\t\t// matters for a live-supervisor restore — but keep both bounce verbs\n\t\t\t\t// consistent: lock the publish, resume unlocked. So `runRestore` runs\n\t\t\t\t// WITHOUT `resume`, and we run the injected resume below, unlocked.\n\t\t\t\tconst meta = yield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireStackLock(paths.stackLockFile);\n\t\t\t\t\t\treturn yield* runRestore({\n\t\t\t\t\t\t\tsnapshotId,\n\t\t\t\t\t\t\tartifactDir,\n\t\t\t\t\t\t\truntimeStackRoot: paths.stackRoot,\n\t\t\t\t\t\t\truntimeStagingPath: `${paths.stackRoot}.staging.${snapshotId}`,\n\t\t\t\t\t\t\truntimeBackupPath: `${paths.stackRoot}.bak.${snapshotId}`,\n\t\t\t\t\t\t\tparticipants: effectiveParticipants,\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\truntimeIdentity: {\n\t\t\t\t\t\t\t\tapp: identity.app,\n\t\t\t\t\t\t\t\tstack: identity.stack,\n\t\t\t\t\t\t\t\tnetwork: identity.network,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t...(currentGraphInput === undefined ? {} : { currentGraphInput }),\n\t\t\t\t\t\t\t...(graphInputMismatchPolicy === undefined ? {} : { graphInputMismatchPolicy }),\n\t\t\t\t\t\t});\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\t// Resume with `stack.lock` RELEASED so the re-converge's per-plugin\n\t\t\t\t// container claims can take the lock (see the note above). Omitted\n\t\t\t\t// on the offline restore (the next boot is the resume).\n\t\t\t\tif (resume !== undefined) {\n\t\t\t\t\tyield* resume.pipe(\n\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\t\tnew RestorePhaseError({\n\t\t\t\t\t\t\t\t\tphase: 'resume',\n\t\t\t\t\t\t\t\t\tdetail: 'stack resume after restore failed',\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn meta;\n\t\t\t});\n\n\t\tconst list: SnapshotOrchestrator['list'] = Effect.gen(function* () {\n\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\tconst exists = yield* fs\n\t\t\t\t.exists(paths.snapshotDir)\n\t\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\t\tif (!exists) return [];\n\t\t\tconst ids = yield* fs\n\t\t\t\t.readDirectory(paths.snapshotDir)\n\t\t\t\t.pipe(Effect.catch(() => Effect.succeed([] as ReadonlyArray<string>)));\n\t\t\tconst entries: SnapshotCatalogEntry[] = [];\n\t\t\tfor (const id of ids) {\n\t\t\t\t// Skip transient staging/backup directories — invisible to the\n\t\t\t\t// catalog by construction (the `.staging.<id>` / `.bak.<id>` naming).\n\t\t\t\tif (id.startsWith('.staging.') || id.startsWith('.bak.')) continue;\n\t\t\t\tconst parsedId = parseSnapshotId(id);\n\t\t\t\tif (parsedId === null) {\n\t\t\t\t\tyield* Effect.logWarning(`ignoring unsafe snapshot catalog entry: ${id}`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst dir = `${paths.snapshotDir}/${id}`;\n\t\t\t\tconst metaPath = `${dir}/${SnapshotLayout.metaFile}`;\n\t\t\t\tconst metaExists = yield* fs\n\t\t\t\t\t.exists(metaPath)\n\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\t\t\tif (!metaExists) {\n\t\t\t\t\tentries.push({ id, directory: dir, metadata: null });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst text = yield* fs\n\t\t\t\t\t.readFileString(metaPath)\n\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed('')));\n\t\t\t\tif (text === '') {\n\t\t\t\t\tentries.push({ id, directory: dir, metadata: null });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst decoded = yield* decodeJsonText(SnapshotMetadataSchema, text, {\n\t\t\t\t\tsource: metaPath,\n\t\t\t\t\tmkError: () => null,\n\t\t\t\t}).pipe(Effect.catch(() => Effect.succeed(null)));\n\t\t\t\tif (decoded !== null && parseSnapshotId(decoded.id) === null) {\n\t\t\t\t\tyield* Effect.logWarning(`ignoring snapshot metadata with unsafe id: ${decoded.id}`);\n\t\t\t\t\tentries.push({ id, directory: dir, metadata: null });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tentries.push({ id: parsedId, directory: dir, metadata: decoded });\n\t\t\t}\n\t\t\treturn entries;\n\t\t});\n\n\t\tconst del: SnapshotOrchestrator['delete'] = (id) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst snapshotId = yield* validateSnapshotId('delete', id);\n\t\t\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t\t\tconst dir = `${paths.snapshotDir}/${snapshotId}`;\n\t\t\t\tyield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireStackLock(paths.stackLockFile);\n\t\t\t\t\t\tyield* fs.remove(dir, { recursive: true, force: true }).pipe(Effect.ignore);\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t});\n\n\t\tconst wipe: SnapshotOrchestrator['wipe'] = (args) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireStackLock(paths.stackLockFile);\n\t\t\t\t\t\tyield* runWipe({\n\t\t\t\t\t\t\tlabelMatch: { app: identity.app, stack: identity.stack },\n\t\t\t\t\t\t\tstackRoot: paths.stackRoot,\n\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\tkeepSnapshots: args.keepSnapshots,\n\t\t\t\t\t\t});\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t});\n\n\t\tconst wipePlan: SnapshotOrchestrator['wipePlan'] = (args) =>\n\t\t\t// Read-only: no stack-lock. `planWipe` only lists matching\n\t\t\t// containers and reads the stack-root directory, so it is safe to\n\t\t\t// run without serializing against peers (a concurrent mutation just\n\t\t\t// makes the preview slightly stale — acceptable for a dry-run).\n\t\t\tplanWipe({\n\t\t\t\tlabelMatch: { app: identity.app, stack: identity.stack },\n\t\t\t\tstackRoot: paths.stackRoot,\n\t\t\t\truntime,\n\t\t\t\tkeepSnapshots: args.keepSnapshots,\n\t\t\t});\n\n\t\tconst prune: SnapshotOrchestrator['prune'] = () =>\n\t\t\tEffect.scoped(\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* acquireStackLock(paths.stackLockFile);\n\t\t\t\t\treturn yield* runPrune({\n\t\t\t\t\t\tstackRoot: paths.stackRoot,\n\t\t\t\t\t\timageLabelFilter: { app: identity.app, stack: identity.stack },\n\t\t\t\t\t\truntime,\n\t\t\t\t\t});\n\t\t\t\t}),\n\t\t\t);\n\n\t\treturn SnapshotOrchestratorService.of({\n\t\t\tregisterParticipant,\n\t\t\tcapture,\n\t\t\trestore,\n\t\t\tlist,\n\t\t\tdelete: del,\n\t\t\twipe,\n\t\t\twipePlan,\n\t\t\tprune,\n\t\t});\n\t}),\n);\n"],"mappings":"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"}
@@ -1,3 +1,7 @@
1
+ import { appName, stackName } from "../../substrate/brand.mjs";
2
+ import { labelScope, reconcileSpec } from "../../substrate/runtime/reconcile/spec.mjs";
3
+ import { reconcileLabel } from "../../substrate/runtime/reconcile/label.mjs";
4
+ import "../../substrate/runtime/reconcile/index.mjs";
1
5
  import { makePhaseFailer } from "./phase-error.mjs";
2
6
  import { Effect, FileSystem, Schema } from "effect";
3
7
  //#region src/orchestrators/snapshot/wipe.ts
@@ -5,43 +9,36 @@ var WipePhaseError = class extends Schema.TaggedErrorClass()("SnapshotWipePhaseE
5
9
  phase: Schema.Literals([
6
10
  "sweep-containers",
7
11
  "sweep-networks-volumes",
8
- "remove-state",
9
12
  "remove-runtime-tree"
10
13
  ]),
11
14
  detail: Schema.String,
12
15
  cause: Schema.optional(Schema.Defect)
13
16
  }) {};
14
17
  const failPhase = makePhaseFailer(WipePhaseError);
15
- const resolvePreservePolicy = (inputs) => ({
16
- keepSnapshots: inputs.keepSnapshots ?? true,
17
- keepCache: inputs.keepCache ?? false
18
- });
19
18
  /** True when a direct child of `stackRoot` is PRESERVED (not removed)
20
- * by a wipe under `policy`. `snapshots/` survives by default; `cache/`
21
- * survives only when explicitly requested. Every other child state,
22
- * cross-process artifacts, per-plugin runtime treesis removed. */
23
- const isPreservedChild = (name, policy) => policy.keepSnapshots && name === "snapshots" || policy.keepCache && name === "cache";
19
+ * by a wipe under `preserve`. The snapshot catalog (`snapshots/`) and
20
+ * the deploy cache (`cache/`) survive TOGETHER by default and are
21
+ * dropped together on a hard reset they ride the one `preserve`
22
+ * flag. Every other child state, cross-process artifacts, per-plugin
23
+ * runtime trees — is removed regardless. */
24
+ const isPreservedChild = (name, preserve) => preserve && (name === "snapshots" || name === "cache");
24
25
  /**
25
26
  * Enumerate the concrete targets a wipe of `inputs.labelMatch` would
26
27
  * remove, WITHOUT removing anything. Read-only: lists matching
27
28
  * containers via the runtime adapter and reads the stack-root directory
28
29
  * to classify each child against the SAME preserve predicate `runWipe`
29
- * uses. Backs `devstack wipe --dry-run`.
30
+ * uses (`snapshots/` and `cache/` survive together by default). Backs
31
+ * `devstack wipe --dry-run`.
30
32
  */
31
33
  const planWipe = (inputs) => Effect.gen(function* () {
32
34
  const fs = yield* FileSystem.FileSystem;
33
- yield* Effect.annotateCurrentSpan({
34
- "devstack.snapshot.phase": "wipe-plan",
35
- "devstack.app": inputs.labelMatch.app,
36
- "devstack.stack": inputs.labelMatch.stack
37
- });
38
35
  const containers = (yield* inputs.runtime.inspectByLabels(inputs.labelMatch).pipe(Effect.catch(failPhase("sweep-containers", `container inspect failed`)))).map((h) => h.name).sort();
39
- const policy = resolvePreservePolicy(inputs);
36
+ const preserve = inputs.keepSnapshots ?? true;
40
37
  const children = yield* fs.readDirectory(inputs.stackRoot).pipe(Effect.catch(() => Effect.succeed([])));
41
38
  const onDiskPaths = [];
42
39
  const preserved = [];
43
40
  for (const name of [...children].sort()) {
44
- if (isPreservedChild(name, policy)) {
41
+ if (isPreservedChild(name, preserve)) {
45
42
  preserved.push(name);
46
43
  continue;
47
44
  }
@@ -53,62 +50,76 @@ const planWipe = (inputs) => Effect.gen(function* () {
53
50
  containers,
54
51
  networkLabelMatch: inputs.labelMatch,
55
52
  volumeLabelMatch: inputs.labelMatch,
56
- stateFile: inputs.stateFilePath,
57
53
  stackRoot: inputs.stackRoot,
58
54
  onDiskPaths,
59
55
  preserved
60
56
  };
61
- }).pipe(Effect.withSpan("orchestrator.snapshot.wipe.plan"));
57
+ });
62
58
  /**
63
- * Tear down a stack's live footprint. Snapshots survive by default
64
- * (architecture § wipe).
59
+ * Tear down a stack's live footprint. The snapshot catalog AND the
60
+ * deploy cache survive together by default (architecture § wipe).
61
+ *
62
+ * Routed through the unified reconcile: a flat LABEL-scope spec —
63
+ * `target: 'absent'` (hard container/network/volume rm) +
64
+ * `fsPlan: [sweep-children(isPreservedChild), reap-empty]` — executed by
65
+ * `reconcileLabel`. The per-step order is:
65
66
  *
66
- * Order:
67
67
  * 1. Force-remove managed containers by `{ app, stack }` labels.
68
68
  * 2. Remove managed networks and volumes by the same label filter.
69
- * 3. Remove state.json.
70
- * 4. Remove the runtime tree EXCEPT the snapshot catalog by default.
71
- * 5. Remove the now-empty stack root when nothing survived (no
72
- * preserved child remains) so wipe doesn't leak an empty
73
- * `stacks/<stack>/` directory.
69
+ * 3. Sweep the runtime tree EXCEPT the wipe-scoped survivors
70
+ * (`snapshots/` AND `cache/`) by default the `sweep-children` op
71
+ * consults the SAME `isPreservedChild` predicate `planWipe` uses, so
72
+ * the preview can never drift from what the real wipe removes.
73
+ * 4. Reap the now-empty stack root when nothing survived so wipe doesn't
74
+ * leak an empty `stacks/<stack>/` directory (the `reap-empty` op,
75
+ * reading the preserved-count threaded from `sweep-children`).
76
+ *
77
+ * `WipePhaseError` tags are preserved by passing each step's failer into
78
+ * the reconcile (containers → `sweep-containers`; networks/volumes →
79
+ * `sweep-networks-volumes`; child removal → `remove-runtime-tree`). The
80
+ * reap-empty step is best-effort (no phase).
81
+ *
82
+ * `snapshots/` and `cache/` are coupled (decision-1: there is no asymmetric
83
+ * keep-snapshots-drop-cache degree of freedom on disk), so the single
84
+ * `keepSnapshots` flag drives both: the on-disk preservation is driven by
85
+ * `isPreservedChild`.
74
86
  */
75
87
  const runWipe = (inputs) => Effect.gen(function* () {
76
- const fs = yield* FileSystem.FileSystem;
77
- yield* Effect.annotateCurrentSpan({
78
- "devstack.snapshot.phase": "wipe",
79
- "devstack.app": inputs.labelMatch.app,
80
- "devstack.stack": inputs.labelMatch.stack
88
+ const preserve = inputs.keepSnapshots ?? true;
89
+ const sweepChildren = {
90
+ op: "sweep-children",
91
+ stackRoot: inputs.stackRoot,
92
+ preserve: (name) => isPreservedChild(name, preserve),
93
+ onError: failPhase("remove-runtime-tree", `remove stack-root child failed`)
94
+ };
95
+ const reapEmpty = {
96
+ op: "reap-empty",
97
+ stackRoot: inputs.stackRoot
98
+ };
99
+ yield* reconcileLabel(reconcileSpec({
100
+ target: "absent",
101
+ scope: labelScope({
102
+ app: appName(inputs.labelMatch.app),
103
+ stack: stackName(inputs.labelMatch.stack)
104
+ }),
105
+ direction: "drain",
106
+ fsPlan: { ops: [sweepChildren, reapEmpty] }
107
+ }), {
108
+ runtime: inputs.runtime,
109
+ onContainersError: failPhase("sweep-containers", `container sweep failed`),
110
+ onNetworksError: failPhase("sweep-networks-volumes", `network sweep failed`),
111
+ onVolumesError: failPhase("sweep-networks-volumes", `volume sweep failed`)
81
112
  });
82
- yield* inputs.runtime.removeManagedContainers(inputs.labelMatch).pipe(Effect.catch(failPhase("sweep-containers", `container sweep failed`)));
83
- yield* inputs.runtime.removeManagedNetworks(inputs.labelMatch).pipe(Effect.catch(failPhase("sweep-networks-volumes", `network sweep failed`)));
84
- yield* inputs.runtime.removeManagedVolumes(inputs.labelMatch).pipe(Effect.catch(failPhase("sweep-networks-volumes", `volume sweep failed`)));
85
- yield* fs.remove(inputs.stateFilePath, { force: true }).pipe(Effect.catch(failPhase("remove-state", `remove state.json failed`)));
86
- const policy = resolvePreservePolicy(inputs);
87
- const children = yield* fs.readDirectory(inputs.stackRoot).pipe(Effect.catch(() => Effect.succeed([])));
88
- let preservedCount = 0;
89
- for (const name of children) {
90
- if (isPreservedChild(name, policy)) {
91
- preservedCount += 1;
92
- continue;
93
- }
94
- yield* fs.remove(`${inputs.stackRoot}/${name}`, {
95
- recursive: true,
96
- force: true
97
- }).pipe(Effect.catch(failPhase("remove-runtime-tree", `remove ${name} failed`)));
98
- }
99
- if (preservedCount === 0 && children.length > 0) yield* fs.remove(inputs.stackRoot, {
100
- recursive: true,
101
- force: true
102
- }).pipe(Effect.ignore);
103
- }).pipe(Effect.withSpan("orchestrator.snapshot.wipe"));
113
+ });
104
114
  /** Centralized constant — the canonical snapshot-catalog directory
105
115
  * name. Used by both `runWipe` (to preserve) and the substrate's
106
116
  * path resolver (to compose `snapshotDir`). Distilled §17 calls out
107
117
  * the "centralize the snapshots-dir-name constant" opportunity. */
108
118
  const SNAPSHOTS_DIR_NAME = "snapshots";
109
119
  /** Canonical stack-local artifact-cache directory name. `runWipe`
110
- * preserves it only under `keepCache`; otherwise it is removed so a
111
- * reset re-proves on-chain artifacts against the next chain. */
120
+ * preserves it together with `snapshots/` under the one `keepSnapshots`
121
+ * flag (default); a hard reset (`keepSnapshots: false`) removes it so a
122
+ * fresh boot re-proves on-chain artifacts against the next chain. */
112
123
  const CACHE_DIR_NAME = "cache";
113
124
  //#endregion
114
125
  export { CACHE_DIR_NAME, SNAPSHOTS_DIR_NAME, WipePhaseError, planWipe, runWipe };
@@ -1 +1 @@
1
- {"version":3,"file":"wipe.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/wipe.ts"],"sourcesContent":["// Wipe — label-scoped teardown.\n//\n// Architecture § Snapshot responsibilities:\n// \"Provide a wipe operation scoped to one `(app, stack)` that tears\n// down containers, networks, volumes, and per-stack on-disk state,\n// with snapshots surviving by default.\"\n//\n// Label-scoped: enumeration uses partial `ContainerLabelTuple` filters\n// (just `{ app, stack }`); the orchestrator does NOT reach for plugin\n// names. The runtime adapter sweeps containers/networks/volumes\n// matching the label set.\n\nimport { Effect, FileSystem, Schema } from 'effect';\n\nimport type { ContainerLabelTuple } from '../../contracts/snapshotable.ts';\nimport type { ContainerRuntime } from '../../contracts/container-runtime.ts';\nimport { makePhaseFailer } from './phase-error.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\nexport class WipePhaseError extends Schema.TaggedErrorClass<WipePhaseError>()(\n\t'SnapshotWipePhaseError',\n\t{\n\t\tphase: Schema.Literals([\n\t\t\t'sweep-containers',\n\t\t\t'sweep-networks-volumes',\n\t\t\t'remove-state',\n\t\t\t'remove-runtime-tree',\n\t\t]),\n\t\tdetail: Schema.String,\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\nconst failPhase = makePhaseFailer(WipePhaseError);\n\n// -----------------------------------------------------------------------------\n// Inputs\n// -----------------------------------------------------------------------------\n\nexport interface WipeInputs {\n\treadonly labelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>;\n\treadonly stackRoot: string;\n\treadonly stateFilePath: string;\n\treadonly runtime: ContainerRuntime;\n\t/** Preserve the snapshot catalog (default behavior). When false,\n\t * the catalog is removed alongside the runtime tree. */\n\treadonly keepSnapshots?: boolean;\n\t/** Preserve stack-local artifact caches. Defaults to false; wipe\n\t * should force on-chain artifacts to re-prove against the next\n\t * chain instead of carrying local ids across a reset. */\n\treadonly keepCache?: boolean;\n}\n\n// -----------------------------------------------------------------------------\n// Preserve predicate — the single source of truth for \"what survives a\n// wipe on disk\". Both `runWipe` (the destructive pass) and `planWipe`\n// (the dry-run enumeration) consult this so the preview can never drift\n// from what the real wipe removes.\n// -----------------------------------------------------------------------------\n\ninterface PreservePolicy {\n\treadonly keepSnapshots: boolean;\n\treadonly keepCache: boolean;\n}\n\nconst resolvePreservePolicy = (inputs: WipeInputs): PreservePolicy => ({\n\tkeepSnapshots: inputs.keepSnapshots ?? true,\n\tkeepCache: inputs.keepCache ?? false,\n});\n\n/** True when a direct child of `stackRoot` is PRESERVED (not removed)\n * by a wipe under `policy`. `snapshots/` survives by default; `cache/`\n * survives only when explicitly requested. Every other child — state,\n * cross-process artifacts, per-plugin runtime trees — is removed. */\nconst isPreservedChild = (name: string, policy: PreservePolicy): boolean =>\n\t(policy.keepSnapshots && name === SNAPSHOTS_DIR_NAME) ||\n\t(policy.keepCache && name === CACHE_DIR_NAME);\n\n// -----------------------------------------------------------------------------\n// Dry-run enumeration\n// -----------------------------------------------------------------------------\n\n/** Concrete teardown targets a wipe of one `(app, stack)` would remove.\n * Produced by `planWipe` WITHOUT mutating anything so `devstack wipe\n * --dry-run` can show the operator exactly what a real wipe deletes. */\nexport interface WipeTargets {\n\treadonly app: string;\n\treadonly stack: string;\n\t/** Managed container NAMES that match the `{ app, stack }` label\n\t * filter — enumerated via the runtime adapter so the preview lists\n\t * the exact containers a real wipe force-removes. */\n\treadonly containers: ReadonlyArray<string>;\n\t/** Networks/volumes are removed by the SAME `{ app, stack }` label\n\t * filter the runtime adapter sweeps on. The contract exposes no\n\t * by-label LIST for these, so the plan reports the selector that\n\t * scopes the removal rather than a (daemon-round-trip) name list. */\n\treadonly networkLabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>;\n\treadonly volumeLabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>;\n\t/** Absolute path of the per-stack `state.json` that is removed. */\n\treadonly stateFile: string;\n\t/** Absolute path of the per-stack runtime root. Its non-preserved\n\t * children (see `onDiskPaths`) are removed; the directory itself is\n\t * reaped too when nothing survives. */\n\treadonly stackRoot: string;\n\t/** Absolute paths of the `stackRoot` children a real wipe removes —\n\t * everything except the preserved `snapshots/` (and `cache/` unless\n\t * `keepCache`). Empty when the stack root does not exist yet. */\n\treadonly onDiskPaths: ReadonlyArray<string>;\n\t/** Direct children PRESERVED on disk (`snapshots/`, optionally\n\t * `cache/`) — surfaced so the preview is explicit about survivors. */\n\treadonly preserved: ReadonlyArray<string>;\n}\n\n/**\n * Enumerate the concrete targets a wipe of `inputs.labelMatch` would\n * remove, WITHOUT removing anything. Read-only: lists matching\n * containers via the runtime adapter and reads the stack-root directory\n * to classify each child against the SAME preserve predicate `runWipe`\n * uses. Backs `devstack wipe --dry-run`.\n */\nexport const planWipe = (\n\tinputs: WipeInputs,\n): Effect.Effect<WipeTargets, WipePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t'devstack.snapshot.phase': 'wipe-plan',\n\t\t\t'devstack.app': inputs.labelMatch.app,\n\t\t\t'devstack.stack': inputs.labelMatch.stack,\n\t\t});\n\n\t\tconst handles = yield* inputs.runtime\n\t\t\t.inspectByLabels(inputs.labelMatch as ContainerLabelTuple)\n\t\t\t.pipe(Effect.catch(failPhase('sweep-containers', `container inspect failed`)));\n\t\tconst containers = handles.map((h) => h.name).sort();\n\n\t\tconst policy = resolvePreservePolicy(inputs);\n\t\tconst children = yield* fs\n\t\t\t.readDirectory(inputs.stackRoot)\n\t\t\t.pipe(Effect.catch(() => Effect.succeed([] as ReadonlyArray<string>)));\n\t\tconst onDiskPaths: Array<string> = [];\n\t\tconst preserved: Array<string> = [];\n\t\tfor (const name of [...children].sort()) {\n\t\t\tif (isPreservedChild(name, policy)) {\n\t\t\t\tpreserved.push(name);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tonDiskPaths.push(`${inputs.stackRoot}/${name}`);\n\t\t}\n\n\t\treturn {\n\t\t\tapp: inputs.labelMatch.app,\n\t\t\tstack: inputs.labelMatch.stack,\n\t\t\tcontainers,\n\t\t\tnetworkLabelMatch: inputs.labelMatch,\n\t\t\tvolumeLabelMatch: inputs.labelMatch,\n\t\t\tstateFile: inputs.stateFilePath,\n\t\t\tstackRoot: inputs.stackRoot,\n\t\t\tonDiskPaths,\n\t\t\tpreserved,\n\t\t} satisfies WipeTargets;\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.wipe.plan'));\n\n// -----------------------------------------------------------------------------\n// Wipe\n// -----------------------------------------------------------------------------\n\n/**\n * Tear down a stack's live footprint. Snapshots survive by default\n * (architecture § wipe).\n *\n * Order:\n * 1. Force-remove managed containers by `{ app, stack }` labels.\n * 2. Remove managed networks and volumes by the same label filter.\n * 3. Remove state.json.\n * 4. Remove the runtime tree EXCEPT the snapshot catalog by default.\n * 5. Remove the now-empty stack root when nothing survived (no\n * preserved child remains) so wipe doesn't leak an empty\n * `stacks/<stack>/` directory.\n */\nexport const runWipe = (\n\tinputs: WipeInputs,\n): Effect.Effect<void, WipePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t'devstack.snapshot.phase': 'wipe',\n\t\t\t'devstack.app': inputs.labelMatch.app,\n\t\t\t'devstack.stack': inputs.labelMatch.stack,\n\t\t});\n\n\t\t// 1. Containers.\n\t\tyield* inputs.runtime\n\t\t\t.removeManagedContainers(inputs.labelMatch)\n\t\t\t.pipe(Effect.catch(failPhase('sweep-containers', `container sweep failed`)));\n\n\t\t// 2. Networks + volumes.\n\t\tyield* inputs.runtime\n\t\t\t.removeManagedNetworks(inputs.labelMatch)\n\t\t\t.pipe(Effect.catch(failPhase('sweep-networks-volumes', `network sweep failed`)));\n\t\tyield* inputs.runtime\n\t\t\t.removeManagedVolumes(inputs.labelMatch)\n\t\t\t.pipe(Effect.catch(failPhase('sweep-networks-volumes', `volume sweep failed`)));\n\n\t\t// 3. Remove state.json.\n\t\tyield* fs\n\t\t\t.remove(inputs.stateFilePath, { force: true })\n\t\t\t.pipe(Effect.catch(failPhase('remove-state', `remove state.json failed`)));\n\n\t\t// 4. Remove the runtime tree — but PRESERVE snapshots by default.\n\t\t// Strategy: enumerate the stack root and remove each child the\n\t\t// preserve predicate does NOT keep (`snapshots/` by default;\n\t\t// `cache/` only when `keepCache`). Stack-local artifact caches\n\t\t// are state and are removed unless explicitly requested.\n\t\tconst policy = resolvePreservePolicy(inputs);\n\t\tconst children = yield* fs\n\t\t\t.readDirectory(inputs.stackRoot)\n\t\t\t.pipe(Effect.catch(() => Effect.succeed([] as ReadonlyArray<string>)));\n\t\tlet preservedCount = 0;\n\t\tfor (const name of children) {\n\t\t\tif (isPreservedChild(name, policy)) {\n\t\t\t\tpreservedCount += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tyield* fs\n\t\t\t\t.remove(`${inputs.stackRoot}/${name}`, { recursive: true, force: true })\n\t\t\t\t.pipe(Effect.catch(failPhase('remove-runtime-tree', `remove ${name} failed`)));\n\t\t}\n\n\t\t// 5. Reap the now-empty stack root. When NOTHING was preserved\n\t\t// (no `snapshots/`/`cache/` survivor) every child was removed in\n\t\t// step 4 — any child whose removal FAILED would have raised a\n\t\t// `remove-runtime-tree` error and aborted before here — so the\n\t\t// directory is empty at this point. Leaving it behind leaks an\n\t\t// empty `stacks/<stack>/` shell that `prune --list` would show as\n\t\t// a bare group. `recursive` is required to remove a directory at\n\t\t// all (a plain `remove` raises EISDIR even for an empty dir); it\n\t\t// is safe here precisely BECAUSE the `preservedCount === 0` guard\n\t\t// means no preserved subtree exists to be recursively swept.\n\t\t// Best-effort: a (racing) re-created child just leaves the dir.\n\t\tif (preservedCount === 0 && children.length > 0) {\n\t\t\tyield* fs.remove(inputs.stackRoot, { recursive: true, force: true }).pipe(Effect.ignore);\n\t\t}\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.wipe'));\n\n/** Centralized constant — the canonical snapshot-catalog directory\n * name. Used by both `runWipe` (to preserve) and the substrate's\n * path resolver (to compose `snapshotDir`). Distilled §17 calls out\n * the \"centralize the snapshots-dir-name constant\" opportunity. */\nexport const SNAPSHOTS_DIR_NAME = 'snapshots';\n\n/** Canonical stack-local artifact-cache directory name. `runWipe`\n * preserves it only under `keepCache`; otherwise it is removed so a\n * reset re-proves on-chain artifacts against the next chain. */\nexport const CACHE_DIR_NAME = 'cache';\n"],"mappings":"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"}
1
+ {"version":3,"file":"wipe.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/wipe.ts"],"sourcesContent":["// Wipe — label-scoped teardown.\n//\n// Architecture § Snapshot responsibilities:\n// \"Provide a wipe operation scoped to one `(app, stack)` that tears\n// down containers, networks, volumes, and per-stack on-disk state,\n// with the snapshot catalog AND the deploy cache surviving together\n// by default; a hard reset drops both.\"\n//\n// Wipe-scope coupling (decision-1): `snapshots/` and `cache/` ride ONE\n// flag (`keepSnapshots`). An ordinary wipe preserves both so a later\n// restore can REUSE the live deploy cache (the deploy ids survive the\n// teardown); a hard reset (`keepSnapshots: false`) drops both so a\n// fresh boot re-proves every on-chain artifact against the next chain.\n//\n// Label-scoped: enumeration uses partial `ContainerLabelTuple` filters\n// (just `{ app, stack }`); the orchestrator does NOT reach for plugin\n// names. The runtime adapter sweeps containers/networks/volumes\n// matching the label set.\n\nimport { Effect, FileSystem, Schema } from 'effect';\n\nimport type { ContainerLabelTuple } from '../../contracts/snapshotable.ts';\nimport type { ContainerRuntime } from '../../contracts/container-runtime.ts';\nimport { appName, stackName } from '../../substrate/brand.ts';\nimport {\n\tlabelScope,\n\treconcileLabel,\n\treconcileSpec,\n\ttype ReconcileFsOp,\n} from '../../substrate/runtime/reconcile/index.ts';\nimport { makePhaseFailer } from './phase-error.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\nexport class WipePhaseError extends Schema.TaggedErrorClass<WipePhaseError>()(\n\t'SnapshotWipePhaseError',\n\t{\n\t\tphase: Schema.Literals(['sweep-containers', 'sweep-networks-volumes', 'remove-runtime-tree']),\n\t\tdetail: Schema.String,\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\nconst failPhase = makePhaseFailer(WipePhaseError);\n\n// -----------------------------------------------------------------------------\n// Inputs\n// -----------------------------------------------------------------------------\n\nexport interface WipeInputs {\n\treadonly labelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>;\n\treadonly stackRoot: string;\n\treadonly runtime: ContainerRuntime;\n\t/** Preserve the wipe-scoped state — the snapshot catalog (`snapshots/`)\n\t * AND the deploy cache (`cache/`) — across the teardown. Defaults to\n\t * true: an ordinary wipe keeps both so a later restore can REUSE the\n\t * live deploy cache. Set false for a hard reset, which drops both\n\t * alongside the runtime tree so a fresh boot re-proves every on-chain\n\t * artifact against the next chain. The two dirs are coupled — there\n\t * is no asymmetric \"keep snapshots, drop cache\" (or vice versa). */\n\treadonly keepSnapshots?: boolean;\n}\n\n// -----------------------------------------------------------------------------\n// Preserve predicate — the single source of truth for \"what survives a\n// wipe on disk\". Both `runWipe` (the destructive pass) and `planWipe`\n// (the dry-run enumeration) consult this so the preview can never drift\n// from what the real wipe removes.\n// -----------------------------------------------------------------------------\n\n/** True when a direct child of `stackRoot` is PRESERVED (not removed)\n * by a wipe under `preserve`. The snapshot catalog (`snapshots/`) and\n * the deploy cache (`cache/`) survive TOGETHER by default and are\n * dropped together on a hard reset — they ride the one `preserve`\n * flag. Every other child — state, cross-process artifacts, per-plugin\n * runtime trees — is removed regardless. */\nconst isPreservedChild = (name: string, preserve: boolean): boolean =>\n\tpreserve && (name === SNAPSHOTS_DIR_NAME || name === CACHE_DIR_NAME);\n\n// -----------------------------------------------------------------------------\n// Dry-run enumeration\n// -----------------------------------------------------------------------------\n\n/** Concrete teardown targets a wipe of one `(app, stack)` would remove.\n * Produced by `planWipe` WITHOUT mutating anything so `devstack wipe\n * --dry-run` can show the operator exactly what a real wipe deletes. */\nexport interface WipeTargets {\n\treadonly app: string;\n\treadonly stack: string;\n\t/** Managed container NAMES that match the `{ app, stack }` label\n\t * filter — enumerated via the runtime adapter so the preview lists\n\t * the exact containers a real wipe force-removes. */\n\treadonly containers: ReadonlyArray<string>;\n\t/** Networks/volumes are removed by the SAME `{ app, stack }` label\n\t * filter the runtime adapter sweeps on. The contract exposes no\n\t * by-label LIST for these, so the plan reports the selector that\n\t * scopes the removal rather than a (daemon-round-trip) name list. */\n\treadonly networkLabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>;\n\treadonly volumeLabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>;\n\t/** Absolute path of the per-stack runtime root. Its non-preserved\n\t * children (see `onDiskPaths`) are removed; the directory itself is\n\t * reaped too when nothing survives. */\n\treadonly stackRoot: string;\n\t/** Absolute paths of the `stackRoot` children a real wipe removes —\n\t * everything except the wipe-scoped survivors (`snapshots/` AND\n\t * `cache/`, kept together unless this is a hard reset). Empty when\n\t * the stack root does not exist yet. */\n\treadonly onDiskPaths: ReadonlyArray<string>;\n\t/** Direct children PRESERVED on disk (`snapshots/` and `cache/`,\n\t * coupled) — surfaced so the preview is explicit about survivors. */\n\treadonly preserved: ReadonlyArray<string>;\n}\n\n/**\n * Enumerate the concrete targets a wipe of `inputs.labelMatch` would\n * remove, WITHOUT removing anything. Read-only: lists matching\n * containers via the runtime adapter and reads the stack-root directory\n * to classify each child against the SAME preserve predicate `runWipe`\n * uses (`snapshots/` and `cache/` survive together by default). Backs\n * `devstack wipe --dry-run`.\n */\nexport const planWipe = (\n\tinputs: WipeInputs,\n): Effect.Effect<WipeTargets, WipePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\n\t\tconst handles = yield* inputs.runtime\n\t\t\t.inspectByLabels(inputs.labelMatch as ContainerLabelTuple)\n\t\t\t.pipe(Effect.catch(failPhase('sweep-containers', `container inspect failed`)));\n\t\tconst containers = handles.map((h) => h.name).sort();\n\n\t\tconst preserve = inputs.keepSnapshots ?? true;\n\t\tconst children = yield* fs\n\t\t\t.readDirectory(inputs.stackRoot)\n\t\t\t.pipe(Effect.catch(() => Effect.succeed([] as ReadonlyArray<string>)));\n\t\tconst onDiskPaths: Array<string> = [];\n\t\tconst preserved: Array<string> = [];\n\t\tfor (const name of [...children].sort()) {\n\t\t\tif (isPreservedChild(name, preserve)) {\n\t\t\t\tpreserved.push(name);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tonDiskPaths.push(`${inputs.stackRoot}/${name}`);\n\t\t}\n\n\t\treturn {\n\t\t\tapp: inputs.labelMatch.app,\n\t\t\tstack: inputs.labelMatch.stack,\n\t\t\tcontainers,\n\t\t\tnetworkLabelMatch: inputs.labelMatch,\n\t\t\tvolumeLabelMatch: inputs.labelMatch,\n\t\t\tstackRoot: inputs.stackRoot,\n\t\t\tonDiskPaths,\n\t\t\tpreserved,\n\t\t} satisfies WipeTargets;\n\t});\n\n// -----------------------------------------------------------------------------\n// Wipe\n// -----------------------------------------------------------------------------\n\n/**\n * Tear down a stack's live footprint. The snapshot catalog AND the\n * deploy cache survive together by default (architecture § wipe).\n *\n * Routed through the unified reconcile: a flat LABEL-scope spec —\n * `target: 'absent'` (hard container/network/volume rm) +\n * `fsPlan: [sweep-children(isPreservedChild), reap-empty]` — executed by\n * `reconcileLabel`. The per-step order is:\n *\n * 1. Force-remove managed containers by `{ app, stack }` labels.\n * 2. Remove managed networks and volumes by the same label filter.\n * 3. Sweep the runtime tree EXCEPT the wipe-scoped survivors\n * (`snapshots/` AND `cache/`) by default — the `sweep-children` op\n * consults the SAME `isPreservedChild` predicate `planWipe` uses, so\n * the preview can never drift from what the real wipe removes.\n * 4. Reap the now-empty stack root when nothing survived so wipe doesn't\n * leak an empty `stacks/<stack>/` directory (the `reap-empty` op,\n * reading the preserved-count threaded from `sweep-children`).\n *\n * `WipePhaseError` tags are preserved by passing each step's failer into\n * the reconcile (containers → `sweep-containers`; networks/volumes →\n * `sweep-networks-volumes`; child removal → `remove-runtime-tree`). The\n * reap-empty step is best-effort (no phase).\n *\n * `snapshots/` and `cache/` are coupled (decision-1: there is no asymmetric\n * keep-snapshots-drop-cache degree of freedom on disk), so the single\n * `keepSnapshots` flag drives both: the on-disk preservation is driven by\n * `isPreservedChild`.\n */\nexport const runWipe = (\n\tinputs: WipeInputs,\n): Effect.Effect<void, WipePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst preserve = inputs.keepSnapshots ?? true;\n\n\t\tconst sweepChildren: ReconcileFsOp<WipePhaseError> = {\n\t\t\top: 'sweep-children',\n\t\t\tstackRoot: inputs.stackRoot,\n\t\t\tpreserve: (name) => isPreservedChild(name, preserve),\n\t\t\t// The failing child's name is not available here, so the detail\n\t\t\t// names the step rather than the child.\n\t\t\tonError: failPhase('remove-runtime-tree', `remove stack-root child failed`),\n\t\t};\n\t\tconst reapEmpty: ReconcileFsOp<WipePhaseError> = {\n\t\t\top: 'reap-empty',\n\t\t\tstackRoot: inputs.stackRoot,\n\t\t};\n\n\t\tyield* reconcileLabel(\n\t\t\treconcileSpec<WipePhaseError>({\n\t\t\t\ttarget: 'absent',\n\t\t\t\tscope: labelScope({\n\t\t\t\t\tapp: appName(inputs.labelMatch.app),\n\t\t\t\t\tstack: stackName(inputs.labelMatch.stack),\n\t\t\t\t}),\n\t\t\t\tdirection: 'drain',\n\t\t\t\tfsPlan: { ops: [sweepChildren, reapEmpty] },\n\t\t\t}),\n\t\t\t{\n\t\t\t\truntime: inputs.runtime,\n\t\t\t\tonContainersError: failPhase('sweep-containers', `container sweep failed`),\n\t\t\t\tonNetworksError: failPhase('sweep-networks-volumes', `network sweep failed`),\n\t\t\t\tonVolumesError: failPhase('sweep-networks-volumes', `volume sweep failed`),\n\t\t\t},\n\t\t);\n\t});\n\n/** Centralized constant — the canonical snapshot-catalog directory\n * name. Used by both `runWipe` (to preserve) and the substrate's\n * path resolver (to compose `snapshotDir`). Distilled §17 calls out\n * the \"centralize the snapshots-dir-name constant\" opportunity. */\nexport const SNAPSHOTS_DIR_NAME = 'snapshots';\n\n/** Canonical stack-local artifact-cache directory name. `runWipe`\n * preserves it together with `snapshots/` under the one `keepSnapshots`\n * flag (default); a hard reset (`keepSnapshots: false`) removes it so a\n * fresh boot re-proves on-chain artifacts against the next chain. */\nexport const CACHE_DIR_NAME = 'cache';\n"],"mappings":";;;;;;;AAoCA,IAAa,iBAAb,cAAoC,OAAO,iBAAiC,CAAC,CAC5E,0BACA;CACC,OAAO,OAAO,SAAS;EAAC;EAAoB;EAA0B;CAAqB,CAAC;CAC5F,QAAQ,OAAO;CACf,OAAO,OAAO,SAAS,OAAO,MAAM;AACrC,CACD,CAAC,CAAC,CAAC;AAEH,MAAM,YAAY,gBAAgB,cAAc;;;;;;;AAiChD,MAAM,oBAAoB,MAAc,aACvC,aAAa,SAAA,eAA+B,SAAA;;;;;;;;;AA4C7C,MAAa,YACZ,WAEA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;CAK7B,MAAM,cAAa,OAHI,OAAO,QAC5B,gBAAgB,OAAO,UAAiC,CAAC,CACzD,KAAK,OAAO,MAAM,UAAU,oBAAoB,0BAA0B,CAAC,CAAC,EAAA,CACnD,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK;CAEnD,MAAM,WAAW,OAAO,iBAAiB;CACzC,MAAM,WAAW,OAAO,GACtB,cAAc,OAAO,SAAS,CAAC,CAC/B,KAAK,OAAO,YAAY,OAAO,QAAQ,CAAC,CAA0B,CAAC,CAAC;CACtE,MAAM,cAA6B,CAAC;CACpC,MAAM,YAA2B,CAAC;CAClC,KAAK,MAAM,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,KAAK,GAAG;EACxC,IAAI,iBAAiB,MAAM,QAAQ,GAAG;GACrC,UAAU,KAAK,IAAI;GACnB;EACD;EACA,YAAY,KAAK,GAAG,OAAO,UAAU,GAAG,MAAM;CAC/C;CAEA,OAAO;EACN,KAAK,OAAO,WAAW;EACvB,OAAO,OAAO,WAAW;EACzB;EACA,mBAAmB,OAAO;EAC1B,kBAAkB,OAAO;EACzB,WAAW,OAAO;EAClB;EACA;CACD;AACD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCF,MAAa,WACZ,WAEA,OAAO,IAAI,aAAa;CACvB,MAAM,WAAW,OAAO,iBAAiB;CAEzC,MAAM,gBAA+C;EACpD,IAAI;EACJ,WAAW,OAAO;EAClB,WAAW,SAAS,iBAAiB,MAAM,QAAQ;EAGnD,SAAS,UAAU,uBAAuB,gCAAgC;CAC3E;CACA,MAAM,YAA2C;EAChD,IAAI;EACJ,WAAW,OAAO;CACnB;CAEA,OAAO,eACN,cAA8B;EAC7B,QAAQ;EACR,OAAO,WAAW;GACjB,KAAK,QAAQ,OAAO,WAAW,GAAG;GAClC,OAAO,UAAU,OAAO,WAAW,KAAK;EACzC,CAAC;EACD,WAAW;EACX,QAAQ,EAAE,KAAK,CAAC,eAAe,SAAS,EAAE;CAC3C,CAAC,GACD;EACC,SAAS,OAAO;EAChB,mBAAmB,UAAU,oBAAoB,wBAAwB;EACzE,iBAAiB,UAAU,0BAA0B,sBAAsB;EAC3E,gBAAgB,UAAU,0BAA0B,qBAAqB;CAC1E,CACD;AACD,CAAC;;;;;AAMF,MAAa,qBAAqB;;;;;AAMlC,MAAa,iBAAiB"}
@@ -11,9 +11,12 @@ const makeAccountCodegen = (parts) => defineSimpleConstExport({
11
11
  outputPath: `accounts/${parts.name}.ts`,
12
12
  exportName: parts.name,
13
13
  value: parts.resolved,
14
+ outputLocation: "generated-extras",
15
+ aggregateOnly: true,
14
16
  aggregate: {
15
17
  kind: "account",
16
18
  bucket: "accounts.ts",
19
+ outputLocation: "generated-extras",
17
20
  project: (exported) => exported
18
21
  }
19
22
  });
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/account/codegen.ts"],"sourcesContent":["// Account plugin — Codegenable contribution.\n//\n// Architecture §6 + 12-account.md \"Cross-component references\":\n// Codegen emits a constant address map keyed by account name so\n// downstream code can `import { accounts } from '<staged>/accounts'`\n// and read `accounts.alice.address` at the type level.\n//\n// Distilled-doc tension (12-account.md \"publicKey caveat\"):\n// impersonation accounts have a zero-buffer publicKey — emitting it\n// into the codegen bindings would be a type-level lie. The emitted\n// shape below carries the `source` discriminator INSTEAD of\n// publicKey; consumers branch on `source: 'impersonate'` rather than\n// publicKey-truthiness.\n//\n// SAFETY: the codegen output never emits secret material. Only the\n// `name`, `address`, `scheme`, and `source` fields land on disk.\n// `sensitive: false` is correct because none of these are secret.\n\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\n\nimport { defineSimpleConstExport } from '../internal/codegen-helpers.ts';\n\n/** The typed shape the emitted file exports. Per-account record keyed\n * by name. */\nexport interface AccountBindings {\n\treadonly name: string;\n\treadonly address: string;\n\treadonly scheme: 'ed25519' | 'secp256k1' | 'secp256r1';\n\treadonly source: 'real' | 'impersonate';\n}\n\n/** Construct the Codegenable contribution. One emit per account.\n *\n * The emitter name is `account/${name}` (literal) so multiple\n * accounts in a single stack get co-located but typed-distinct\n * emissions; the codegen orchestrator can fold them into a single\n * `accounts` namespace at staging time. */\nexport const makeAccountCodegen = <Name extends string>(parts: {\n\treadonly name: Name;\n\treadonly resolved: AccountBindings;\n}): CodegenableDecl<`account/${Name}`> =>\n\tdefineSimpleConstExport({\n\t\temitterName: `account/${parts.name}` as `account/${Name}`,\n\t\toutputPath: `accounts/${parts.name}.ts`,\n\t\texportName: parts.name,\n\t\tvalue: parts.resolved,\n\t\taggregate: {\n\t\t\tkind: 'account',\n\t\t\tbucket: 'accounts.ts',\n\t\t\t// Pass-through: this decl's exported map already keys by\n\t\t\t// account name, which is the aggregate's merge key.\n\t\t\tproject: (exported) => exported,\n\t\t},\n\t});\n"],"mappings":";;;;;;;;AAqCA,MAAa,sBAA2C,UAIvD,wBAAwB;CACvB,aAAa,WAAW,MAAM;CAC9B,YAAY,YAAY,MAAM,KAAK;CACnC,YAAY,MAAM;CAClB,OAAO,MAAM;CACb,WAAW;EACV,MAAM;EACN,QAAQ;EAGR,UAAU,aAAa;EACvB;CACD,CAAC"}
1
+ {"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/account/codegen.ts"],"sourcesContent":["// Account plugin — Codegenable contribution.\n//\n// Architecture §6 + 12-account.md \"Cross-component references\":\n// Codegen emits a constant address map keyed by account name so\n// downstream code can `import { accounts } from '<staged>/accounts'`\n// and read `accounts.alice.address` at the type level.\n//\n// Distilled-doc tension (12-account.md \"publicKey caveat\"):\n// impersonation accounts have a zero-buffer publicKey — emitting it\n// into the codegen bindings would be a type-level lie. The emitted\n// shape below carries the `source` discriminator INSTEAD of\n// publicKey; consumers branch on `source: 'impersonate'` rather than\n// publicKey-truthiness.\n//\n// SAFETY: the codegen output never emits secret material. Only the\n// `name`, `address`, `scheme`, and `source` fields land on disk.\n// `sensitive: false` is correct because none of these are secret.\n\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\n\nimport { defineSimpleConstExport } from '../internal/codegen-helpers.ts';\n\n/** The typed shape the emitted file exports. Per-account record keyed\n * by name. */\nexport interface AccountBindings {\n\treadonly name: string;\n\treadonly address: string;\n\treadonly scheme: 'ed25519' | 'secp256k1' | 'secp256r1';\n\treadonly source: 'real' | 'impersonate';\n}\n\n/** Construct the Codegenable contribution. One emit per account.\n *\n * The emitter name is `account/${name}` (literal) so multiple\n * accounts in a single stack get co-located but typed-distinct\n * emissions; the codegen orchestrator can fold them into a single\n * `accounts` namespace at staging time. */\nexport const makeAccountCodegen = <Name extends string>(parts: {\n\treadonly name: Name;\n\treadonly resolved: AccountBindings;\n}): CodegenableDecl<`account/${Name}`> =>\n\tdefineSimpleConstExport({\n\t\temitterName: `account/${parts.name}` as `account/${Name}`,\n\t\toutputPath: `accounts/${parts.name}.ts`,\n\t\texportName: parts.name,\n\t\tvalue: parts.resolved,\n\t\t// Dev-only surface: the combined `accounts.ts` lands in the\n\t\t// gitignored `generated-extras` tree (reached via `@devstack-dev`),\n\t\t// and no standalone per-account file is emitted (aggregateOnly).\n\t\t// The template app must NOT use accounts at runtime; examples\n\t\t// (dev-only) import them from `@devstack-dev`.\n\t\toutputLocation: 'generated-extras',\n\t\taggregateOnly: true,\n\t\taggregate: {\n\t\t\tkind: 'account',\n\t\t\tbucket: 'accounts.ts',\n\t\t\toutputLocation: 'generated-extras',\n\t\t\t// Pass-through: this decl's exported map already keys by\n\t\t\t// account name, which is the aggregate's merge key.\n\t\t\tproject: (exported) => exported,\n\t\t},\n\t});\n"],"mappings":";;;;;;;;AAqCA,MAAa,sBAA2C,UAIvD,wBAAwB;CACvB,aAAa,WAAW,MAAM;CAC9B,YAAY,YAAY,MAAM,KAAK;CACnC,YAAY,MAAM;CAClB,OAAO,MAAM;CAMb,gBAAgB;CAChB,eAAe;CACf,WAAW;EACV,MAAM;EACN,QAAQ;EACR,gBAAgB;EAGhB,UAAU,aAAa;CACxB;AACD,CAAC"}
@@ -1,7 +1,7 @@
1
1
  //#region src/plugins/account/errors.d.ts
2
2
  /** Phases for `AccountAcquireError`. Closed sum — adding a phase
3
3
  * requires editing this list AND the cause-walker's display table. */
4
- type AccountAcquirePhase = 'validate-name' | 'validate-funding' | 'load-keystore' | 'read-env' | 'decode-inline' | 'bind-signer' | 'bind-impersonation-slot' | 'generate-keypair' | 'persist-keypair' | 'read-persisted-keypair' | 'restrict-permissions' | 'derive-address' | 'unsupported-scheme' | 'await-chain-ready' | 'fund-default' | 'fund-cross-cutting' | 'register';
4
+ type AccountAcquirePhase = 'validate-name' | 'validate-funding' | 'decode-inline' | 'bind-signer' | 'bind-impersonation-slot' | 'generate-keypair' | 'persist-keypair' | 'read-persisted-keypair' | 'restrict-permissions' | 'derive-address' | 'unsupported-scheme' | 'await-chain-ready' | 'fund-default' | 'fund-cross-cutting' | 'register';
5
5
  /** Account acquisition error — raised by the variant resolvers and
6
6
  * the funding pass. Carries the per-variant + per-phase column. */
7
7
  interface AccountAcquireError {
@@ -63,7 +63,7 @@ interface AccountSignError {
63
63
  }
64
64
  /** Account variant discriminator. Mirrors the user-facing
65
65
  * `AccountOptions` shape's `kind:` field. */
66
- type AccountVariantKind = 'ephemeral' | 'keystore' | 'env' | 'inline' | 'signer' | 'impersonate';
66
+ type AccountVariantKind = 'ephemeral' | 'signer' | 'impersonate';
67
67
  /** Union of every error an Account-plugin caller may encounter. */
68
68
  type AccountError = AccountAcquireError | AccountSignError;
69
69
  //#endregion
@@ -7,10 +7,7 @@ const accountSignError = (parts) => ({
7
7
  _tag: "AccountSignError",
8
8
  ...parts
9
9
  });
10
- /** Error tags this plugin contributes — surfaced to the cause walker
11
- * via `PluginErrorContribution`. */
12
- const ACCOUNT_ERROR_TAGS = ["AccountAcquireError", "AccountSignError"];
13
10
  //#endregion
14
- export { ACCOUNT_ERROR_TAGS, accountAcquireError, accountSignError };
11
+ export { accountAcquireError, accountSignError };
15
12
 
16
13
  //# sourceMappingURL=errors.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../../../src/plugins/account/errors.ts"],"sourcesContent":["// Account plugin — typed errors.\n//\n// Distilled-doc finding (12-account.md \"Acquisition vs signing error\n// split\" invariant): two error families must not be mixed. Acquisition\n// errors (keystore unreadable, faucet exhausted, scheme unsupported)\n// surface as `AccountAcquireError`; signing errors (gRPC failure,\n// finality wait, locked-shared-object) surface as `AccountSignError`.\n// Mixing them breaks downstream `catchTag` boundaries — the engine's\n// pretty-cause walker also dispatches on `_tag`, so any merger here\n// would force a renderer-side branch on `phase` instead.\n//\n// Effect v4: plain interface + `_tag` literal discriminator (no\n// subclassing). The architecture's per-plugin tagged-error convention\n// (12-account.md \"Promote the sign/execute error to the same\n// tagged-error class shape\") is honored here — both errors are\n// uniform-shape, the cause walker handles them by `_tag`.\n\n/** Phases for `AccountAcquireError`. Closed sum — adding a phase\n * requires editing this list AND the cause-walker's display table. */\nexport type AccountAcquirePhase =\n\t| 'validate-name'\n\t| 'validate-funding'\n\t| 'load-keystore'\n\t| 'read-env'\n\t| 'decode-inline'\n\t| 'bind-signer'\n\t| 'bind-impersonation-slot'\n\t| 'generate-keypair'\n\t| 'persist-keypair'\n\t| 'read-persisted-keypair'\n\t| 'restrict-permissions'\n\t| 'derive-address'\n\t| 'unsupported-scheme'\n\t| 'await-chain-ready'\n\t| 'fund-default'\n\t| 'fund-cross-cutting'\n\t| 'register';\n\n/** Account acquisition error — raised by the variant resolvers and\n * the funding pass. Carries the per-variant + per-phase column. */\nexport interface AccountAcquireError {\n\treadonly _tag: 'AccountAcquireError';\n\treadonly phase: AccountAcquirePhase;\n\treadonly accountName: string;\n\treadonly variant: AccountVariantKind;\n\treadonly message: string;\n\treadonly hint?: string;\n\treadonly cause?: unknown;\n}\n\nexport const accountAcquireError = (\n\tparts: Omit<AccountAcquireError, '_tag'>,\n): AccountAcquireError => ({ _tag: 'AccountAcquireError', ...parts });\n\n/** Phases for `AccountSignError`. Strictly transport / lifecycle\n * failures along the sign-and-execute pipeline — `signAndExecute`\n * itself returns a `SignAndExecuteResult` discriminated union for\n * the on-chain outcome (success vs `FailedTransaction`), so\n * on-chain failures are NOT in this phase set. The impersonation\n * refusal lives here so a synchronous throw maps cleanly onto an\n * async catch boundary when callers wrap the resolved-account\n * signer in `Effect.try`.\n *\n * - `build-tx` — transaction serialisation threw before\n * signing.\n * - `sign` — the signer rejected / threw.\n * - `submit` — `executeTransaction` transport failure\n * (RPC unreachable, network error). The\n * response never arrived.\n * - `no-digest` — `executeTransaction` returned a response\n * that violates the SDK envelope contract\n * (`$kind` missing OR `Transaction.digest`\n * missing OR `FailedTransaction.digest`\n * missing). Protocol violation, not a\n * transport failure — kept distinct so\n * `'submit'` keeps a single failure-kind\n * (per §2 \"phases describe steps, not\n * failure kinds\").\n * - `await-finality` — `waitForTransaction` failed AFTER a\n * submit-success; the digest exists but the\n * finality wait broke.\n * - `dependent-package-not-found`\n * - `lease-acquire`\n * - `impersonation-bypass-attempt`\n */\nexport type AccountSignPhase =\n\t| 'build-tx'\n\t| 'sign'\n\t| 'submit'\n\t| 'no-digest'\n\t| 'await-finality'\n\t| 'dependent-package-not-found'\n\t| 'lease-acquire'\n\t| 'impersonation-bypass-attempt';\n\n/** Account sign/execute error — raised by the per-account signer's\n * `signAndExecute` / `signTransaction` / `signPersonalMessage`\n * surfaces.\n *\n * Distilled-doc opportunity: today's implementation projects this to\n * JSON specially. Promoting to the uniform-shape interface here lets\n * the cause walker treat it like every other plugin's tagged error;\n * the special JSON-projection case can go away. */\nexport interface AccountSignError {\n\treadonly _tag: 'AccountSignError';\n\treadonly phase: AccountSignPhase;\n\treadonly accountName: string;\n\treadonly address: string;\n\treadonly message: string;\n\treadonly cause?: unknown;\n}\n\nexport const accountSignError = (parts: Omit<AccountSignError, '_tag'>): AccountSignError => ({\n\t_tag: 'AccountSignError',\n\t...parts,\n});\n\n/** Account variant discriminator. Mirrors the user-facing\n * `AccountOptions` shape's `kind:` field. */\nexport type AccountVariantKind =\n\t| 'ephemeral'\n\t| 'keystore'\n\t| 'env'\n\t| 'inline'\n\t| 'signer'\n\t| 'impersonate';\n\n/** Union of every error an Account-plugin caller may encounter. */\nexport type AccountError = AccountAcquireError | AccountSignError;\n\n/** Error tags this plugin contributes surfaced to the cause walker\n * via `PluginErrorContribution`. */\nexport const ACCOUNT_ERROR_TAGS: ReadonlyArray<AccountError['_tag']> = [\n\t'AccountAcquireError',\n\t'AccountSignError',\n] as const;\n"],"mappings":";AAkDA,MAAa,uBACZ,WAC0B;CAAE,MAAM;CAAuB,GAAG;CAAO;AA4DpE,MAAa,oBAAoB,WAA6D;CAC7F,MAAM;CACN,GAAG;CACH;;;AAiBD,MAAa,qBAA0D,CACtE,uBACA,mBACA"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../../../src/plugins/account/errors.ts"],"sourcesContent":["// Account plugin — typed errors.\n//\n// Distilled-doc finding (12-account.md \"Acquisition vs signing error\n// split\" invariant): two error families must not be mixed. Acquisition\n// errors (keystore unreadable, faucet exhausted, scheme unsupported)\n// surface as `AccountAcquireError`; signing errors (gRPC failure,\n// finality wait, locked-shared-object) surface as `AccountSignError`.\n// Mixing them breaks downstream `catchTag` boundaries — the engine's\n// pretty-cause walker also dispatches on `_tag`, so any merger here\n// would force a renderer-side branch on `phase` instead.\n//\n// Effect v4: plain interface + `_tag` literal discriminator (no\n// subclassing). The architecture's per-plugin tagged-error convention\n// (12-account.md \"Promote the sign/execute error to the same\n// tagged-error class shape\") is honored here — both errors are\n// uniform-shape, the cause walker handles them by `_tag`.\n\n/** Phases for `AccountAcquireError`. Closed sum — adding a phase\n * requires editing this list AND the cause-walker's display table. */\nexport type AccountAcquirePhase =\n\t| 'validate-name'\n\t| 'validate-funding'\n\t| 'decode-inline'\n\t| 'bind-signer'\n\t| 'bind-impersonation-slot'\n\t| 'generate-keypair'\n\t| 'persist-keypair'\n\t| 'read-persisted-keypair'\n\t| 'restrict-permissions'\n\t| 'derive-address'\n\t| 'unsupported-scheme'\n\t| 'await-chain-ready'\n\t| 'fund-default'\n\t| 'fund-cross-cutting'\n\t| 'register';\n\n/** Account acquisition error — raised by the variant resolvers and\n * the funding pass. Carries the per-variant + per-phase column. */\nexport interface AccountAcquireError {\n\treadonly _tag: 'AccountAcquireError';\n\treadonly phase: AccountAcquirePhase;\n\treadonly accountName: string;\n\treadonly variant: AccountVariantKind;\n\treadonly message: string;\n\treadonly hint?: string;\n\treadonly cause?: unknown;\n}\n\nexport const accountAcquireError = (\n\tparts: Omit<AccountAcquireError, '_tag'>,\n): AccountAcquireError => ({ _tag: 'AccountAcquireError', ...parts });\n\n/** Phases for `AccountSignError`. Strictly transport / lifecycle\n * failures along the sign-and-execute pipeline — `signAndExecute`\n * itself returns a `SignAndExecuteResult` discriminated union for\n * the on-chain outcome (success vs `FailedTransaction`), so\n * on-chain failures are NOT in this phase set. The impersonation\n * refusal lives here so a synchronous throw maps cleanly onto an\n * async catch boundary when callers wrap the resolved-account\n * signer in `Effect.try`.\n *\n * - `build-tx` — transaction serialisation threw before\n * signing.\n * - `sign` — the signer rejected / threw.\n * - `submit` — `executeTransaction` transport failure\n * (RPC unreachable, network error). The\n * response never arrived.\n * - `no-digest` — `executeTransaction` returned a response\n * that violates the SDK envelope contract\n * (`$kind` missing OR `Transaction.digest`\n * missing OR `FailedTransaction.digest`\n * missing). Protocol violation, not a\n * transport failure — kept distinct so\n * `'submit'` keeps a single failure-kind\n * (per §2 \"phases describe steps, not\n * failure kinds\").\n * - `await-finality` — `waitForTransaction` failed AFTER a\n * submit-success; the digest exists but the\n * finality wait broke.\n * - `dependent-package-not-found`\n * - `lease-acquire`\n * - `impersonation-bypass-attempt`\n */\nexport type AccountSignPhase =\n\t| 'build-tx'\n\t| 'sign'\n\t| 'submit'\n\t| 'no-digest'\n\t| 'await-finality'\n\t| 'dependent-package-not-found'\n\t| 'lease-acquire'\n\t| 'impersonation-bypass-attempt';\n\n/** Account sign/execute error — raised by the per-account signer's\n * `signAndExecute` / `signTransaction` / `signPersonalMessage`\n * surfaces.\n *\n * Distilled-doc opportunity: today's implementation projects this to\n * JSON specially. Promoting to the uniform-shape interface here lets\n * the cause walker treat it like every other plugin's tagged error;\n * the special JSON-projection case can go away. */\nexport interface AccountSignError {\n\treadonly _tag: 'AccountSignError';\n\treadonly phase: AccountSignPhase;\n\treadonly accountName: string;\n\treadonly address: string;\n\treadonly message: string;\n\treadonly cause?: unknown;\n}\n\nexport const accountSignError = (parts: Omit<AccountSignError, '_tag'>): AccountSignError => ({\n\t_tag: 'AccountSignError',\n\t...parts,\n});\n\n/** Account variant discriminator. Mirrors the user-facing\n * `AccountOptions` shape's `kind:` field. */\nexport type AccountVariantKind = 'ephemeral' | 'signer' | 'impersonate';\n\n/** Union of every error an Account-plugin caller may encounter. */\nexport type AccountError = AccountAcquireError | AccountSignError;\n\n/** The catchable error tags this plugin exposes. Pinned against the\n * user-facing error catalog by the error-catalog-parity test. */\nexport const ACCOUNT_ERROR_TAGS: ReadonlyArray<AccountError['_tag']> = [\n\t'AccountAcquireError',\n\t'AccountSignError',\n] as const;\n"],"mappings":";AAgDA,MAAa,uBACZ,WAC0B;CAAE,MAAM;CAAuB,GAAG;AAAM;AA4DnE,MAAa,oBAAoB,WAA6D;CAC7F,MAAM;CACN,GAAG;AACJ"}
@@ -1,13 +1,10 @@
1
- import { BALANCE_POLL_PROFILE, makeBoundedSpacedSchedule } from "../../substrate/runtime/retry-policy.mjs";
2
1
  import { setCurrentPluginPhase } from "../../substrate/runtime/current-plugin.mjs";
2
+ import { BALANCE_POLL_PROFILE, makeBoundedSpacedSchedule } from "../../substrate/runtime/retry-policy.mjs";
3
3
  import { StrategyRegistryService } from "../../substrate/runtime/strategy-registry/service.mjs";
4
4
  import { chainKeyedStrategyFor } from "../../substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs";
5
5
  import "../../substrate/runtime/strategy-registry/index.mjs";
6
- import { SuiSpans } from "../sui/spans.mjs";
7
6
  import { FAUCET_CAPABILITY_KEY_PREFIX } from "../faucet/dispatcher.mjs";
8
7
  import "../faucet/index.mjs";
9
- import "../sui/index.mjs";
10
- import { AccountSpans } from "./spans.mjs";
11
8
  import { accountAcquireError } from "./errors.mjs";
12
9
  import { withAddressLease } from "./lease.mjs";
13
10
  import { fundingFailureError } from "../internal/funding-failure-error.mjs";
@@ -74,19 +71,8 @@ const fundEphemeralDefault = (parts) => Effect.gen(function* () {
74
71
  coinLabel: "SUI",
75
72
  amount: parts.amountMist
76
73
  });
77
- yield* Effect.annotateCurrentSpan({
78
- [AccountSpans.name]: parts.accountName,
79
- [AccountSpans.address]: parts.address,
80
- [AccountSpans.fundAmountMist]: parts.amountMist.toString(),
81
- [SuiSpans.chain]: parts.chainId,
82
- [SuiSpans.mode]: parts.suiMode
83
- });
84
74
  return "funded";
85
- }).pipe(Effect.withSpan("devstack.plugin.account.fundEphemeralDefault", { attributes: {
86
- [AccountSpans.name]: parts.accountName,
87
- [AccountSpans.address]: parts.address,
88
- [SuiSpans.mode]: parts.suiMode
89
- } }));
75
+ });
90
76
  /** Apply the cross-cutting funding pass. Variant-agnostic — runs for
91
77
  * every variant once the keypair / impersonation slot is bound and
92
78
  * the address is known.
@@ -177,18 +163,8 @@ const applyCrossCuttingFunding = (parts) => Effect.gen(function* () {
177
163
  outcome: "funded"
178
164
  });
179
165
  }
180
- yield* Effect.annotateCurrentSpan({
181
- [AccountSpans.name]: parts.accountName,
182
- [AccountSpans.address]: parts.address,
183
- [AccountSpans.fundCrossCuttingCount]: parts.funding.length,
184
- [SuiSpans.chain]: parts.chainId
185
- });
186
166
  return applied;
187
- }).pipe(Effect.withSpan("devstack.plugin.account.applyCrossCuttingFunding", { attributes: {
188
- [AccountSpans.name]: parts.accountName,
189
- [AccountSpans.address]: parts.address,
190
- [AccountSpans.fundCrossCuttingEntries]: parts.funding.length
191
- } }));
167
+ });
192
168
  const readExistingBalance = (balanceReader, args) => balanceReader.readBalance(args);
193
169
  const waitForBalanceAtLeast = (parts) => {
194
170
  const reader = parts.balanceReader;