@mysten-incubation/devstack 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (838) hide show
  1. package/README.md +8 -4
  2. package/dashboard-ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  3. package/dashboard-ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  4. package/dashboard-ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  5. package/dashboard-ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  6. package/dashboard-ui/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
  7. package/dashboard-ui/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
  8. package/dashboard-ui/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
  9. package/dashboard-ui/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
  10. package/dashboard-ui/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
  11. package/dashboard-ui/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
  12. package/dashboard-ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  13. package/dashboard-ui/assets/index-Bmi1UtAg.js +1356 -0
  14. package/dashboard-ui/assets/index-D5EShVt4.js +3 -0
  15. package/dashboard-ui/assets/index-Deml9drg.css +1 -0
  16. package/dashboard-ui/index.html +13 -0
  17. package/dist/api/define-capabilities.d.mts +14 -3
  18. package/dist/api/define-capabilities.mjs +18 -1
  19. package/dist/api/define-capabilities.mjs.map +1 -1
  20. package/dist/api/define-devstack.d.mts +1 -1
  21. package/dist/api/define-devstack.mjs +6 -26
  22. package/dist/api/define-devstack.mjs.map +1 -1
  23. package/dist/api/define-plugin.d.mts +1 -0
  24. package/dist/api/define-plugin.mjs +2 -0
  25. package/dist/api/inference-network.d.mts +132 -0
  26. package/dist/api/inference-network.mjs +105 -16
  27. package/dist/api/inference-network.mjs.map +1 -1
  28. package/dist/api/plugin-errors.d.mts +7 -0
  29. package/dist/api/run-stack.d.mts +20 -9
  30. package/dist/api/run-stack.mjs +56 -29
  31. package/dist/api/run-stack.mjs.map +1 -1
  32. package/dist/build-integrations/playwright/config.d.mts +6 -4
  33. package/dist/build-integrations/playwright/config.mjs +5 -3
  34. package/dist/build-integrations/playwright/config.mjs.map +1 -1
  35. package/dist/build-integrations/playwright/errors.d.mts +1 -1
  36. package/dist/build-integrations/playwright/global-setup.d.mts +43 -17
  37. package/dist/build-integrations/playwright/global-setup.mjs +250 -13
  38. package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
  39. package/dist/build-integrations/playwright/index.d.mts +5 -3
  40. package/dist/build-integrations/playwright/index.mjs +4 -2
  41. package/dist/build-integrations/playwright/stack-context.d.mts +0 -1
  42. package/dist/build-integrations/playwright/stack-context.mjs +24 -63
  43. package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
  44. package/dist/build-integrations/playwright/wallet-context.d.mts +7 -10
  45. package/dist/build-integrations/playwright/wallet-context.mjs +8 -13
  46. package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
  47. package/dist/build-integrations/runtime/cold-start-url.d.mts +5 -0
  48. package/dist/build-integrations/runtime/cold-start-url.mjs +4 -3
  49. package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
  50. package/dist/build-integrations/runtime/conventional-routes.d.mts +51 -0
  51. package/dist/build-integrations/runtime/conventional-routes.mjs +92 -0
  52. package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -0
  53. package/dist/build-integrations/runtime/discover.d.mts +38 -30
  54. package/dist/build-integrations/runtime/discover.mjs +73 -70
  55. package/dist/build-integrations/runtime/discover.mjs.map +1 -1
  56. package/dist/build-integrations/runtime/endpoint-registry.d.mts +3 -1
  57. package/dist/build-integrations/runtime/endpoint-registry.mjs +3 -1
  58. package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
  59. package/dist/build-integrations/runtime/index.d.mts +7 -2
  60. package/dist/build-integrations/runtime/index.mjs +9 -2
  61. package/dist/build-integrations/runtime/manifest-types.d.mts +2 -0
  62. package/dist/build-integrations/runtime/manifest-types.mjs +3 -0
  63. package/dist/build-integrations/runtime/playwright-stack-context-slot.d.mts +29 -0
  64. package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs +10 -0
  65. package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs.map +1 -0
  66. package/dist/build-integrations/runtime/read-stack-context.d.mts +1 -1
  67. package/dist/build-integrations/runtime/read-stack-context.mjs +10 -4
  68. package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
  69. package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +48 -0
  70. package/dist/build-integrations/runtime/resolve-discovery-env.mjs +42 -0
  71. package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -0
  72. package/dist/build-integrations/runtime/stack-context.d.mts +1 -1
  73. package/dist/build-integrations/runtime/wallet-paths.mjs +2 -0
  74. package/dist/build-integrations/vite/index.d.mts +51 -0
  75. package/dist/build-integrations/vite/index.mjs +73 -0
  76. package/dist/build-integrations/vite/index.mjs.map +1 -0
  77. package/dist/build-integrations/vitest/env.d.mts +4 -3
  78. package/dist/build-integrations/vitest/env.mjs +6 -4
  79. package/dist/build-integrations/vitest/env.mjs.map +1 -1
  80. package/dist/build-integrations/vitest/errors.d.mts +25 -4
  81. package/dist/build-integrations/vitest/errors.mjs +15 -2
  82. package/dist/build-integrations/vitest/errors.mjs.map +1 -1
  83. package/dist/build-integrations/vitest/index.d.mts +2 -2
  84. package/dist/build-integrations/vitest/index.mjs +3 -3
  85. package/dist/build-integrations/vitest/setup.d.mts +4 -2
  86. package/dist/build-integrations/vitest/setup.mjs +2 -2
  87. package/dist/build-integrations/vitest/setup.mjs.map +1 -1
  88. package/dist/build-integrations/vitest/stack-context.d.mts +0 -1
  89. package/dist/build-integrations/vitest/stack-context.mjs +33 -13
  90. package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
  91. package/dist/cli/bail.mjs +23 -0
  92. package/dist/cli/bail.mjs.map +1 -0
  93. package/dist/{surfaces/cli/commands → cli}/doctor-probes.mjs +26 -16
  94. package/dist/cli/doctor-probes.mjs.map +1 -0
  95. package/dist/cli/main.d.mts +15 -1
  96. package/dist/cli/main.mjs +166 -482
  97. package/dist/cli/main.mjs.map +1 -1
  98. package/dist/cli/prune-direct.mjs +31 -201
  99. package/dist/cli/prune-direct.mjs.map +1 -1
  100. package/dist/cli/snapshot-reader.mjs +14 -11
  101. package/dist/cli/snapshot-reader.mjs.map +1 -1
  102. package/dist/cli/up-lifecycle.mjs +4 -1
  103. package/dist/cli/up-lifecycle.mjs.map +1 -1
  104. package/dist/cli/wirings/apply.mjs +95 -0
  105. package/dist/cli/wirings/apply.mjs.map +1 -0
  106. package/dist/cli/wirings/build-verb-layers.mjs +42 -0
  107. package/dist/cli/wirings/build-verb-layers.mjs.map +1 -0
  108. package/dist/cli/wirings/config-loader.mjs +69 -0
  109. package/dist/cli/wirings/config-loader.mjs.map +1 -0
  110. package/dist/cli/wirings/engine-command.mjs +30 -0
  111. package/dist/cli/wirings/engine-command.mjs.map +1 -0
  112. package/dist/cli/wirings/identity.mjs +72 -0
  113. package/dist/cli/wirings/identity.mjs.map +1 -0
  114. package/dist/cli/wirings/provide-file-system.mjs +7 -0
  115. package/dist/cli/wirings/provide-file-system.mjs.map +1 -0
  116. package/dist/cli/wirings/snapshot.mjs +177 -0
  117. package/dist/cli/wirings/snapshot.mjs.map +1 -0
  118. package/dist/cli/wirings/up.mjs +287 -0
  119. package/dist/cli/wirings/up.mjs.map +1 -0
  120. package/dist/cli/wirings/wipe.mjs +40 -0
  121. package/dist/cli/wirings/wipe.mjs.map +1 -0
  122. package/dist/contracts/capability-decl.d.mts +0 -2
  123. package/dist/contracts/codegenable.d.mts +43 -0
  124. package/dist/contracts/container-runtime.d.mts +26 -1
  125. package/dist/contracts/faucet-strategy.d.mts +25 -0
  126. package/dist/contracts/funding-strategy.d.mts +42 -0
  127. package/dist/contracts/plugin-expander.mjs +60 -0
  128. package/dist/contracts/plugin-expander.mjs.map +1 -0
  129. package/dist/contracts/projection.d.mts +5 -3
  130. package/dist/contracts/renderer.d.mts +23 -0
  131. package/dist/contracts/routable.d.mts +14 -1
  132. package/dist/contracts/snapshotable.d.mts +25 -3
  133. package/dist/contracts/wallet-protocol.d.mts +52 -0
  134. package/dist/contracts/wallet-protocol.mjs +52 -0
  135. package/dist/contracts/wallet-protocol.mjs.map +1 -0
  136. package/dist/index.d.mts +26 -25
  137. package/dist/index.mjs +20 -13
  138. package/dist/{runtime → orchestrators}/built-in-plugin-layers.mjs +10 -5
  139. package/dist/orchestrators/built-in-plugin-layers.mjs.map +1 -0
  140. package/dist/orchestrators/codegen/bindings.d.mts +1 -2
  141. package/dist/orchestrators/codegen/bindings.mjs +52 -186
  142. package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
  143. package/dist/orchestrators/codegen/emit.mjs +19 -1
  144. package/dist/orchestrators/codegen/emit.mjs.map +1 -1
  145. package/dist/orchestrators/codegen/errors.mjs +8 -3
  146. package/dist/orchestrators/codegen/errors.mjs.map +1 -1
  147. package/dist/orchestrators/codegen/format.mjs +47 -39
  148. package/dist/orchestrators/codegen/format.mjs.map +1 -1
  149. package/dist/orchestrators/codegen/gitignore.mjs +3 -1
  150. package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
  151. package/dist/orchestrators/codegen/output-location.mjs +41 -0
  152. package/dist/orchestrators/codegen/output-location.mjs.map +1 -0
  153. package/dist/orchestrators/codegen/paths.d.mts +1 -1
  154. package/dist/orchestrators/codegen/paths.mjs +40 -14
  155. package/dist/orchestrators/codegen/paths.mjs.map +1 -1
  156. package/dist/orchestrators/codegen/permissions.mjs +3 -1
  157. package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
  158. package/dist/orchestrators/codegen/service.mjs +157 -105
  159. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  160. package/dist/orchestrators/lifecycle-prune/errors.mjs +31 -0
  161. package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -0
  162. package/dist/orchestrators/lifecycle-prune/index.mjs +350 -0
  163. package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -0
  164. package/dist/orchestrators/router/cleanup.mjs +26 -27
  165. package/dist/orchestrators/router/cleanup.mjs.map +1 -1
  166. package/dist/orchestrators/router/entrypoints.mjs +2 -2
  167. package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
  168. package/dist/orchestrators/router/file-provider.mjs +14 -5
  169. package/dist/orchestrators/router/file-provider.mjs.map +1 -1
  170. package/dist/orchestrators/router/hostname.mjs +19 -55
  171. package/dist/orchestrators/router/hostname.mjs.map +1 -1
  172. package/dist/orchestrators/router/index.mjs +11 -0
  173. package/dist/orchestrators/router/profile.mjs +2 -2
  174. package/dist/orchestrators/router/sentinels.mjs +26 -0
  175. package/dist/orchestrators/router/sentinels.mjs.map +1 -0
  176. package/dist/orchestrators/router/service.mjs +40 -37
  177. package/dist/orchestrators/router/service.mjs.map +1 -1
  178. package/dist/orchestrators/router/traefik-container.mjs +27 -16
  179. package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
  180. package/dist/{substrate/runtime → orchestrators}/run.mjs +26 -21
  181. package/dist/orchestrators/run.mjs.map +1 -0
  182. package/dist/orchestrators/runtime-composition.d.mts +1 -1
  183. package/dist/orchestrators/runtime-composition.mjs +123 -39
  184. package/dist/orchestrators/runtime-composition.mjs.map +1 -1
  185. package/dist/orchestrators/snapshot/capture-command.d.mts +1 -0
  186. package/dist/orchestrators/snapshot/capture-command.mjs +25 -0
  187. package/dist/orchestrators/snapshot/capture-command.mjs.map +1 -0
  188. package/dist/orchestrators/snapshot/capture.mjs +42 -28
  189. package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
  190. package/dist/orchestrators/snapshot/descriptor.mjs +35 -10
  191. package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
  192. package/dist/orchestrators/snapshot/identity-guard.mjs +1 -1
  193. package/dist/orchestrators/snapshot/index.mjs +13 -0
  194. package/dist/orchestrators/snapshot/integrity.d.mts +1 -1
  195. package/dist/orchestrators/snapshot/integrity.mjs +73 -17
  196. package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
  197. package/dist/orchestrators/snapshot/pending-marker.d.mts +1 -0
  198. package/dist/orchestrators/snapshot/pending-marker.mjs +99 -0
  199. package/dist/orchestrators/snapshot/pending-marker.mjs.map +1 -0
  200. package/dist/orchestrators/snapshot/phase-error.mjs +23 -0
  201. package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -0
  202. package/dist/orchestrators/snapshot/prune.mjs +20 -55
  203. package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
  204. package/dist/orchestrators/snapshot/recover-pending.d.mts +1 -0
  205. package/dist/orchestrators/snapshot/recover-pending.mjs +223 -0
  206. package/dist/orchestrators/snapshot/recover-pending.mjs.map +1 -0
  207. package/dist/orchestrators/snapshot/restore.mjs +118 -84
  208. package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
  209. package/dist/orchestrators/snapshot/service.mjs +53 -56
  210. package/dist/orchestrators/snapshot/service.mjs.map +1 -1
  211. package/dist/orchestrators/snapshot/state-document.d.mts +1 -0
  212. package/dist/orchestrators/snapshot/state-document.mjs +47 -7
  213. package/dist/orchestrators/snapshot/state-document.mjs.map +1 -1
  214. package/dist/orchestrators/snapshot/wipe.mjs +67 -10
  215. package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
  216. package/dist/plugins/account/codegen.mjs +9 -8
  217. package/dist/plugins/account/codegen.mjs.map +1 -1
  218. package/dist/plugins/account/errors.d.mts +33 -6
  219. package/dist/plugins/account/errors.mjs.map +1 -1
  220. package/dist/plugins/account/funding.d.mts +29 -19
  221. package/dist/plugins/account/funding.mjs +84 -66
  222. package/dist/plugins/account/funding.mjs.map +1 -1
  223. package/dist/plugins/account/index.d.mts +2 -4
  224. package/dist/plugins/account/index.mjs +64 -31
  225. package/dist/plugins/account/index.mjs.map +1 -1
  226. package/dist/plugins/account/keypair.mjs +6 -9
  227. package/dist/plugins/account/keypair.mjs.map +1 -1
  228. package/dist/plugins/account/lease.mjs +1 -0
  229. package/dist/plugins/account/lease.mjs.map +1 -1
  230. package/dist/plugins/account/registry.d.mts +14 -1
  231. package/dist/plugins/account/registry.mjs +18 -18
  232. package/dist/plugins/account/registry.mjs.map +1 -1
  233. package/dist/plugins/account/service.d.mts +23 -4
  234. package/dist/plugins/account/service.mjs +83 -45
  235. package/dist/plugins/account/service.mjs.map +1 -1
  236. package/dist/plugins/account/snapshot.mjs +2 -5
  237. package/dist/plugins/account/snapshot.mjs.map +1 -1
  238. package/dist/plugins/account/spans.mjs +16 -0
  239. package/dist/plugins/account/spans.mjs.map +1 -0
  240. package/dist/plugins/account/variants/keystore.mjs +6 -8
  241. package/dist/plugins/account/variants/keystore.mjs.map +1 -1
  242. package/dist/plugins/action/build-context.d.mts +3 -2
  243. package/dist/plugins/action/errors.d.mts +16 -12
  244. package/dist/plugins/action/errors.mjs.map +1 -1
  245. package/dist/plugins/action/execute.mjs +65 -103
  246. package/dist/plugins/action/execute.mjs.map +1 -1
  247. package/dist/plugins/action/index.d.mts +0 -2
  248. package/dist/plugins/action/index.mjs +17 -13
  249. package/dist/plugins/action/index.mjs.map +1 -1
  250. package/dist/plugins/action/service.mjs +38 -35
  251. package/dist/plugins/action/service.mjs.map +1 -1
  252. package/dist/plugins/action/spans.mjs +11 -0
  253. package/dist/plugins/action/spans.mjs.map +1 -0
  254. package/dist/plugins/coin/codegen.mjs +9 -8
  255. package/dist/plugins/coin/codegen.mjs.map +1 -1
  256. package/dist/plugins/coin/discovery.mjs +8 -4
  257. package/dist/plugins/coin/discovery.mjs.map +1 -1
  258. package/dist/plugins/coin/index.d.mts +11 -29
  259. package/dist/plugins/coin/index.mjs +56 -27
  260. package/dist/plugins/coin/index.mjs.map +1 -1
  261. package/dist/plugins/coin/metadata.mjs +8 -15
  262. package/dist/plugins/coin/metadata.mjs.map +1 -1
  263. package/dist/plugins/coin/mint.d.mts +30 -11
  264. package/dist/plugins/coin/mint.mjs +78 -74
  265. package/dist/plugins/coin/mint.mjs.map +1 -1
  266. package/dist/plugins/coin/registry.mjs +4 -7
  267. package/dist/plugins/coin/registry.mjs.map +1 -1
  268. package/dist/plugins/coin/service.d.mts +39 -1
  269. package/dist/plugins/coin/service.mjs +11 -4
  270. package/dist/plugins/coin/service.mjs.map +1 -1
  271. package/dist/plugins/coin/spans.mjs +20 -0
  272. package/dist/plugins/coin/spans.mjs.map +1 -0
  273. package/dist/plugins/dashboard/domain.mjs +402 -0
  274. package/dist/plugins/dashboard/domain.mjs.map +1 -0
  275. package/dist/plugins/dashboard/index.d.mts +29 -0
  276. package/dist/plugins/dashboard/index.mjs +91 -0
  277. package/dist/plugins/dashboard/index.mjs.map +1 -0
  278. package/dist/plugins/dashboard/origin-policy.mjs +36 -0
  279. package/dist/plugins/dashboard/origin-policy.mjs.map +1 -0
  280. package/dist/plugins/dashboard/routable.mjs +27 -0
  281. package/dist/plugins/dashboard/routable.mjs.map +1 -0
  282. package/dist/plugins/dashboard/schema/builder.mjs +21 -0
  283. package/dist/plugins/dashboard/schema/builder.mjs.map +1 -0
  284. package/dist/plugins/dashboard/schema/enums.mjs +72 -0
  285. package/dist/plugins/dashboard/schema/enums.mjs.map +1 -0
  286. package/dist/plugins/dashboard/schema/root.mjs +351 -0
  287. package/dist/plugins/dashboard/schema/root.mjs.map +1 -0
  288. package/dist/plugins/dashboard/schema/types.mjs +508 -0
  289. package/dist/plugins/dashboard/schema/types.mjs.map +1 -0
  290. package/dist/plugins/dashboard/schema.mjs +11 -0
  291. package/dist/plugins/dashboard/schema.mjs.map +1 -0
  292. package/dist/plugins/dashboard/server.mjs +166 -0
  293. package/dist/plugins/dashboard/server.mjs.map +1 -0
  294. package/dist/plugins/deepbook/deploy.mjs +53 -115
  295. package/dist/plugins/deepbook/deploy.mjs.map +1 -1
  296. package/dist/plugins/deepbook/errors.mjs +1 -0
  297. package/dist/plugins/deepbook/errors.mjs.map +1 -1
  298. package/dist/plugins/deepbook/faucet-strategy.mjs +47 -34
  299. package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
  300. package/dist/plugins/deepbook/hash.mjs +9 -0
  301. package/dist/plugins/deepbook/hash.mjs.map +1 -0
  302. package/dist/plugins/deepbook/index.d.mts +1 -10
  303. package/dist/plugins/deepbook/index.mjs +25 -22
  304. package/dist/plugins/deepbook/index.mjs.map +1 -1
  305. package/dist/plugins/deepbook/pyth/index.mjs +26 -55
  306. package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
  307. package/dist/plugins/deepbook/snapshot.mjs +17 -29
  308. package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
  309. package/dist/plugins/deepbook/spans.mjs +18 -0
  310. package/dist/plugins/deepbook/spans.mjs.map +1 -0
  311. package/dist/plugins/deepbook/type-strings.mjs +65 -0
  312. package/dist/plugins/deepbook/type-strings.mjs.map +1 -0
  313. package/dist/plugins/deepbook/types.d.mts +2 -3
  314. package/dist/plugins/deepbook/types.mjs.map +1 -1
  315. package/dist/plugins/faucet/dispatcher.d.mts +21 -2
  316. package/dist/plugins/faucet/dispatcher.mjs +1 -2
  317. package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
  318. package/dist/plugins/faucet/errors.d.mts +11 -24
  319. package/dist/plugins/faucet/errors.mjs.map +1 -1
  320. package/dist/plugins/faucet/http.mjs +33 -14
  321. package/dist/plugins/faucet/http.mjs.map +1 -1
  322. package/dist/plugins/faucet/index.d.mts +15 -5
  323. package/dist/plugins/faucet/index.mjs +3 -0
  324. package/dist/plugins/faucet/index.mjs.map +1 -1
  325. package/dist/plugins/faucet/spans.mjs +12 -0
  326. package/dist/plugins/faucet/spans.mjs.map +1 -0
  327. package/dist/plugins/host-service/errors.d.mts +1 -1
  328. package/dist/plugins/host-service/index.mjs +34 -8
  329. package/dist/plugins/host-service/index.mjs.map +1 -1
  330. package/dist/plugins/host-service/routable.mjs +2 -1
  331. package/dist/plugins/host-service/routable.mjs.map +1 -1
  332. package/dist/plugins/host-service/service.d.mts +21 -0
  333. package/dist/plugins/host-service/service.mjs +136 -81
  334. package/dist/plugins/host-service/service.mjs.map +1 -1
  335. package/dist/plugins/internal/acquire-on-chain-artifact.mjs +23 -0
  336. package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -0
  337. package/dist/plugins/internal/codegen-helpers.mjs +18 -0
  338. package/dist/plugins/internal/codegen-helpers.mjs.map +1 -0
  339. package/dist/plugins/internal/funding-failure-error.mjs +32 -0
  340. package/dist/plugins/internal/funding-failure-error.mjs.map +1 -0
  341. package/dist/plugins/package/build.mjs +1 -1
  342. package/dist/plugins/package/build.mjs.map +1 -1
  343. package/dist/plugins/package/codegen.mjs +26 -0
  344. package/dist/plugins/package/codegen.mjs.map +1 -1
  345. package/dist/plugins/package/errors.d.mts +16 -6
  346. package/dist/plugins/package/errors.mjs.map +1 -1
  347. package/dist/plugins/package/index.d.mts +0 -4
  348. package/dist/plugins/package/index.mjs +27 -21
  349. package/dist/plugins/package/index.mjs.map +1 -1
  350. package/dist/plugins/package/mode-known.mjs +26 -12
  351. package/dist/plugins/package/mode-known.mjs.map +1 -1
  352. package/dist/plugins/package/mode-local.mjs +47 -41
  353. package/dist/plugins/package/mode-local.mjs.map +1 -1
  354. package/dist/plugins/package/publish-executor.mjs +55 -114
  355. package/dist/plugins/package/publish-executor.mjs.map +1 -1
  356. package/dist/plugins/package/registry.mjs +2 -1
  357. package/dist/plugins/package/registry.mjs.map +1 -1
  358. package/dist/plugins/package/service.mjs.map +1 -1
  359. package/dist/plugins/package/spans.mjs +14 -0
  360. package/dist/plugins/package/spans.mjs.map +1 -0
  361. package/dist/plugins/postgres/db-ensure.mjs +19 -12
  362. package/dist/plugins/postgres/db-ensure.mjs.map +1 -1
  363. package/dist/plugins/postgres/errors.d.mts +5 -0
  364. package/dist/plugins/postgres/errors.mjs.map +1 -1
  365. package/dist/plugins/postgres/index.mjs +10 -17
  366. package/dist/plugins/postgres/index.mjs.map +1 -1
  367. package/dist/plugins/postgres/service.mjs +58 -24
  368. package/dist/plugins/postgres/service.mjs.map +1 -1
  369. package/dist/plugins/postgres/snapshot.mjs.map +1 -1
  370. package/dist/plugins/postgres/spans.mjs +11 -0
  371. package/dist/plugins/postgres/spans.mjs.map +1 -0
  372. package/dist/plugins/router-entrypoints.mjs +4 -4
  373. package/dist/plugins/router-entrypoints.mjs.map +1 -1
  374. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +15 -13
  375. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
  376. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +15 -19
  377. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
  378. package/dist/plugins/seal/codegen.d.mts +2 -3
  379. package/dist/plugins/seal/codegen.mjs.map +1 -1
  380. package/dist/plugins/seal/config-render.mjs +35 -8
  381. package/dist/plugins/seal/config-render.mjs.map +1 -1
  382. package/dist/plugins/seal/deploy.mjs +31 -39
  383. package/dist/plugins/seal/deploy.mjs.map +1 -1
  384. package/dist/plugins/seal/errors.d.mts +1 -3
  385. package/dist/plugins/seal/errors.mjs +7 -1
  386. package/dist/plugins/seal/errors.mjs.map +1 -1
  387. package/dist/plugins/seal/index.d.mts +1 -6
  388. package/dist/plugins/seal/index.mjs +40 -14
  389. package/dist/plugins/seal/index.mjs.map +1 -1
  390. package/dist/plugins/seal/key-manager.d.mts +1 -9
  391. package/dist/plugins/seal/key-manager.mjs +2 -18
  392. package/dist/plugins/seal/key-manager.mjs.map +1 -1
  393. package/dist/plugins/seal/key-server.mjs +37 -36
  394. package/dist/plugins/seal/key-server.mjs.map +1 -1
  395. package/dist/plugins/seal/keygen.mjs +16 -17
  396. package/dist/plugins/seal/keygen.mjs.map +1 -1
  397. package/dist/plugins/seal/mode/fork-known.mjs +36 -14
  398. package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
  399. package/dist/plugins/seal/mode/live.d.mts +6 -4
  400. package/dist/plugins/seal/mode/live.mjs +14 -21
  401. package/dist/plugins/seal/mode/live.mjs.map +1 -1
  402. package/dist/plugins/seal/mode/local-keygen.mjs +57 -21
  403. package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
  404. package/dist/plugins/seal/registry-publish.mjs +1 -0
  405. package/dist/plugins/seal/registry-publish.mjs.map +1 -1
  406. package/dist/plugins/seal/routable.mjs.map +1 -1
  407. package/dist/plugins/seal/service.mjs.map +1 -1
  408. package/dist/plugins/seal/spans.mjs +18 -0
  409. package/dist/plugins/seal/spans.mjs.map +1 -0
  410. package/dist/plugins/sui/auto-tick.mjs +17 -23
  411. package/dist/plugins/sui/auto-tick.mjs.map +1 -1
  412. package/dist/plugins/sui/chain-build-container.mjs +18 -0
  413. package/dist/plugins/sui/chain-build-container.mjs.map +1 -0
  414. package/dist/plugins/sui/chain-probe.d.mts +5 -5
  415. package/dist/plugins/sui/chain-probe.mjs +28 -58
  416. package/dist/plugins/sui/chain-probe.mjs.map +1 -1
  417. package/dist/plugins/sui/codegen.mjs +26 -0
  418. package/dist/plugins/sui/codegen.mjs.map +1 -1
  419. package/dist/plugins/sui/errors.d.mts +2 -29
  420. package/dist/plugins/sui/errors.mjs +1 -3
  421. package/dist/plugins/sui/errors.mjs.map +1 -1
  422. package/dist/plugins/sui/fork-faucet-strategy.d.mts +1 -0
  423. package/dist/plugins/sui/fork-faucet-strategy.mjs +55 -0
  424. package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -0
  425. package/dist/plugins/sui/fork-orchestration.d.mts +1 -0
  426. package/dist/plugins/sui/fork-orchestration.mjs +170 -2
  427. package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
  428. package/dist/plugins/sui/fork-transaction.d.mts +2 -0
  429. package/dist/plugins/sui/fork-transaction.mjs +92 -28
  430. package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
  431. package/dist/plugins/sui/index.d.mts +33 -70
  432. package/dist/plugins/sui/index.mjs +58 -35
  433. package/dist/plugins/sui/index.mjs.map +1 -1
  434. package/dist/plugins/{faucet/strategies/sui-local.mjs → sui/local-faucet-strategy.mjs} +10 -7
  435. package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -0
  436. package/dist/plugins/sui/mode/external.mjs +7 -13
  437. package/dist/plugins/sui/mode/external.mjs.map +1 -1
  438. package/dist/plugins/sui/mode/fork.mjs +136 -59
  439. package/dist/plugins/sui/mode/fork.mjs.map +1 -1
  440. package/dist/plugins/sui/mode/live.mjs +10 -16
  441. package/dist/plugins/sui/mode/live.mjs.map +1 -1
  442. package/dist/plugins/sui/mode/local.mjs +37 -36
  443. package/dist/plugins/sui/mode/local.mjs.map +1 -1
  444. package/dist/plugins/sui/mode/shared-boot.mjs +38 -115
  445. package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
  446. package/dist/plugins/sui/mode/shared.d.mts +1 -5
  447. package/dist/plugins/sui/mode/shared.mjs.map +1 -1
  448. package/dist/plugins/sui/mode/spec.d.mts +18 -0
  449. package/dist/plugins/sui/move-summary-runner.mjs +207 -0
  450. package/dist/plugins/sui/move-summary-runner.mjs.map +1 -0
  451. package/dist/plugins/sui/network-resolver.d.mts +4 -3
  452. package/dist/plugins/sui/service.mjs.map +1 -1
  453. package/dist/plugins/sui/snapshot.mjs +3 -0
  454. package/dist/plugins/sui/snapshot.mjs.map +1 -1
  455. package/dist/plugins/sui/spans.mjs +17 -0
  456. package/dist/plugins/sui/spans.mjs.map +1 -0
  457. package/dist/plugins/wallet/codegen.d.mts +0 -1
  458. package/dist/plugins/wallet/codegen.mjs +8 -9
  459. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  460. package/dist/plugins/wallet/index.d.mts +6 -8
  461. package/dist/plugins/wallet/index.mjs +46 -35
  462. package/dist/plugins/wallet/index.mjs.map +1 -1
  463. package/dist/plugins/wallet/origin-policy.mjs +12 -28
  464. package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
  465. package/dist/plugins/wallet/pairing.mjs +6 -4
  466. package/dist/plugins/wallet/pairing.mjs.map +1 -1
  467. package/dist/plugins/wallet/protocol.d.mts +1 -0
  468. package/dist/plugins/wallet/protocol.mjs +5 -44
  469. package/dist/plugins/wallet/protocol.mjs.map +1 -1
  470. package/dist/plugins/wallet/routable.mjs +2 -8
  471. package/dist/plugins/wallet/routable.mjs.map +1 -1
  472. package/dist/plugins/wallet/server.mjs +36 -44
  473. package/dist/plugins/wallet/server.mjs.map +1 -1
  474. package/dist/plugins/wallet/service.d.mts +1 -5
  475. package/dist/plugins/wallet/service.mjs +22 -18
  476. package/dist/plugins/wallet/service.mjs.map +1 -1
  477. package/dist/plugins/wallet/spans.mjs +22 -0
  478. package/dist/plugins/wallet/spans.mjs.map +1 -0
  479. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +33 -8
  480. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
  481. package/dist/plugins/walrus/codegen.d.mts +3 -3
  482. package/dist/plugins/walrus/codegen.mjs.map +1 -1
  483. package/dist/plugins/walrus/deploy-paths.mjs +21 -5
  484. package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
  485. package/dist/plugins/walrus/deploy.mjs +67 -69
  486. package/dist/plugins/walrus/deploy.mjs.map +1 -1
  487. package/dist/plugins/walrus/errors.mjs +1 -0
  488. package/dist/plugins/walrus/errors.mjs.map +1 -1
  489. package/dist/plugins/walrus/faucet-strategy.mjs +12 -7
  490. package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
  491. package/dist/plugins/walrus/index.d.mts +2 -13
  492. package/dist/plugins/walrus/index.mjs +18 -23
  493. package/dist/plugins/walrus/index.mjs.map +1 -1
  494. package/dist/plugins/walrus/mode/known-deploy.mjs +7 -5
  495. package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
  496. package/dist/plugins/walrus/mode/local-cluster.mjs +26 -7
  497. package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
  498. package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
  499. package/dist/plugins/walrus/routable.mjs +7 -23
  500. package/dist/plugins/walrus/routable.mjs.map +1 -1
  501. package/dist/plugins/walrus/service.mjs.map +1 -1
  502. package/dist/plugins/walrus/spans.mjs +18 -0
  503. package/dist/plugins/walrus/spans.mjs.map +1 -0
  504. package/dist/plugins/walrus/storage-nodes.d.mts +6 -2
  505. package/dist/plugins/walrus/storage-nodes.mjs +15 -8
  506. package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
  507. package/dist/plugins/walrus/wal-swap.mjs +58 -15
  508. package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
  509. package/dist/primitives/artifact-publisher.d.mts +1 -1
  510. package/dist/primitives/artifact-publisher.mjs +15 -0
  511. package/dist/primitives/artifact-publisher.mjs.map +1 -0
  512. package/dist/runtime/docker/client.mjs +17 -4
  513. package/dist/runtime/docker/client.mjs.map +1 -1
  514. package/dist/runtime/docker/container.mjs +219 -132
  515. package/dist/runtime/docker/container.mjs.map +1 -1
  516. package/dist/runtime/docker/errors.mjs +0 -6
  517. package/dist/runtime/docker/errors.mjs.map +1 -1
  518. package/dist/runtime/docker/exec.mjs +21 -18
  519. package/dist/runtime/docker/exec.mjs.map +1 -1
  520. package/dist/runtime/docker/image.mjs +36 -13
  521. package/dist/runtime/docker/image.mjs.map +1 -1
  522. package/dist/runtime/docker/index.mjs +14 -0
  523. package/dist/runtime/docker/inspect-and-decode.mjs +51 -0
  524. package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -0
  525. package/dist/runtime/docker/inventory.mjs +14 -11
  526. package/dist/runtime/docker/inventory.mjs.map +1 -1
  527. package/dist/runtime/docker/labels.mjs +38 -5
  528. package/dist/runtime/docker/labels.mjs.map +1 -1
  529. package/dist/runtime/docker/logs.d.mts +1 -1
  530. package/dist/runtime/docker/logs.mjs.map +1 -1
  531. package/dist/runtime/docker/network.mjs +83 -47
  532. package/dist/runtime/docker/network.mjs.map +1 -1
  533. package/dist/runtime/docker/render-run-args.mjs +82 -0
  534. package/dist/runtime/docker/render-run-args.mjs.map +1 -0
  535. package/dist/runtime/docker/service.d.mts +1 -1
  536. package/dist/runtime/docker/service.mjs +12 -7
  537. package/dist/runtime/docker/service.mjs.map +1 -1
  538. package/dist/runtime/docker/sweep.mjs +118 -43
  539. package/dist/runtime/docker/sweep.mjs.map +1 -1
  540. package/dist/runtime/docker/volume.mjs +2 -0
  541. package/dist/runtime/docker/volume.mjs.map +1 -1
  542. package/dist/runtime/docker/wrap.mjs +30 -1
  543. package/dist/runtime/docker/wrap.mjs.map +1 -1
  544. package/dist/substrate/cross-process.mjs +14 -8
  545. package/dist/substrate/cross-process.mjs.map +1 -1
  546. package/dist/substrate/event-time.mjs +33 -0
  547. package/dist/substrate/event-time.mjs.map +1 -0
  548. package/dist/substrate/events.d.mts +28 -8
  549. package/dist/substrate/identity.d.mts +2 -1
  550. package/dist/substrate/manifest.d.mts +68 -5
  551. package/dist/substrate/manifest.mjs +34 -5
  552. package/dist/substrate/manifest.mjs.map +1 -1
  553. package/dist/substrate/network.d.mts +3 -5
  554. package/dist/substrate/options.d.mts +0 -2
  555. package/dist/substrate/plugin.d.mts +13 -0
  556. package/dist/substrate/plugin.mjs +4 -2
  557. package/dist/substrate/plugin.mjs.map +1 -1
  558. package/dist/substrate/projection.d.mts +35 -3
  559. package/dist/substrate/runtime/artifact-publisher/index.mjs +19 -9
  560. package/dist/substrate/runtime/artifact-publisher/index.mjs.map +1 -1
  561. package/dist/substrate/runtime/atomic-write.mjs +97 -32
  562. package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
  563. package/dist/substrate/runtime/cache/index.mjs +3 -0
  564. package/dist/substrate/runtime/cache/schema.mjs +2 -2
  565. package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
  566. package/dist/substrate/runtime/cache/service.mjs +9 -7
  567. package/dist/substrate/runtime/cache/service.mjs.map +1 -1
  568. package/dist/substrate/runtime/capability-sinks/index.d.mts +1 -1
  569. package/dist/substrate/runtime/capability-sinks/index.mjs +3 -0
  570. package/dist/substrate/runtime/capability-sinks/layer.mjs +1 -1
  571. package/dist/substrate/runtime/capability-sinks/service.d.mts +61 -2
  572. package/dist/substrate/runtime/capability-sinks/service.mjs +21 -26
  573. package/dist/substrate/runtime/capability-sinks/service.mjs.map +1 -1
  574. package/dist/substrate/runtime/config-validation.d.mts +1 -13
  575. package/dist/substrate/runtime/config-validation.mjs +2 -91
  576. package/dist/substrate/runtime/config-validation.mjs.map +1 -1
  577. package/dist/substrate/runtime/container-runtime.d.mts +1 -0
  578. package/dist/substrate/runtime/container-runtime.mjs +2 -0
  579. package/dist/substrate/runtime/control-plane/domain.mjs +101 -0
  580. package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -0
  581. package/dist/substrate/runtime/control-plane/service.mjs +7 -0
  582. package/dist/substrate/runtime/control-plane/service.mjs.map +1 -0
  583. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +46 -19
  584. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
  585. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs +30 -12
  586. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
  587. package/dist/substrate/runtime/cross-process/command-channel/index.mjs +5 -0
  588. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs +11 -2
  589. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
  590. package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs +1 -1
  591. package/dist/substrate/runtime/cross-process/index.mjs +11 -0
  592. package/dist/substrate/runtime/cross-process/live-clock.mjs +34 -0
  593. package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -0
  594. package/dist/substrate/runtime/cross-process/liveness.mjs +52 -10
  595. package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
  596. package/dist/substrate/runtime/cross-process/lock.d.mts +1 -0
  597. package/dist/substrate/runtime/cross-process/lock.mjs +23 -0
  598. package/dist/substrate/runtime/cross-process/lock.mjs.map +1 -0
  599. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +64 -0
  600. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -0
  601. package/dist/substrate/runtime/cross-process/roster.mjs +97 -83
  602. package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
  603. package/dist/substrate/runtime/cross-process/self-pid.mjs +8 -0
  604. package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -0
  605. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +24 -28
  606. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +1 -1
  607. package/dist/substrate/runtime/cross-process/stack-lock.mjs +41 -20
  608. package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
  609. package/dist/substrate/runtime/current-plugin.mjs +2 -2
  610. package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
  611. package/dist/substrate/runtime/errors.mjs +12 -6
  612. package/dist/substrate/runtime/errors.mjs.map +1 -1
  613. package/dist/substrate/runtime/format-unknown-error.mjs +30 -0
  614. package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -0
  615. package/dist/substrate/runtime/host-bind-mount-owner.mjs +10 -0
  616. package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -0
  617. package/dist/substrate/runtime/host-gateway.mjs +20 -0
  618. package/dist/substrate/runtime/host-gateway.mjs.map +1 -0
  619. package/dist/substrate/runtime/host-tree-tar/index.mjs +61 -50
  620. package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
  621. package/dist/substrate/runtime/http-probe.d.mts +1 -1
  622. package/dist/substrate/runtime/index.mjs +14 -0
  623. package/dist/substrate/runtime/lease-broker/index.d.mts +1 -1
  624. package/dist/substrate/runtime/lease-broker/index.mjs +2 -0
  625. package/dist/substrate/runtime/lease-broker/service.d.mts +1 -64
  626. package/dist/substrate/runtime/lease-broker/service.mjs +1 -1
  627. package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
  628. package/dist/substrate/runtime/lifecycle/dep-graph.mjs +22 -3
  629. package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
  630. package/dist/substrate/runtime/lifecycle/index.mjs +9 -0
  631. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +1 -1
  632. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +20 -3
  633. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
  634. package/dist/substrate/runtime/lifecycle/selective-restart.mjs +1 -1
  635. package/dist/substrate/runtime/lifecycle/signals.mjs +5 -5
  636. package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
  637. package/dist/substrate/runtime/lifecycle/state-machine.mjs +1 -1
  638. package/dist/substrate/runtime/managed-container.d.mts +19 -1
  639. package/dist/substrate/runtime/managed-container.mjs +20 -1
  640. package/dist/substrate/runtime/managed-container.mjs.map +1 -1
  641. package/dist/substrate/runtime/manifest/index.mjs +3 -0
  642. package/dist/substrate/runtime/manifest/manifest.mjs +15 -11
  643. package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
  644. package/dist/substrate/runtime/mode-errors.mjs +20 -0
  645. package/dist/substrate/runtime/mode-errors.mjs.map +1 -0
  646. package/dist/substrate/runtime/observability/cascade-formatter.mjs +9 -13
  647. package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
  648. package/dist/substrate/runtime/observability/formatter-registry.mjs +19 -36
  649. package/dist/substrate/runtime/observability/formatter-registry.mjs.map +1 -1
  650. package/dist/substrate/runtime/observability/ignore-with-log.d.mts +1 -0
  651. package/dist/substrate/runtime/observability/ignore-with-log.mjs +17 -0
  652. package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -0
  653. package/dist/substrate/runtime/observability/index.d.mts +1 -1
  654. package/dist/substrate/runtime/observability/index.mjs +13 -0
  655. package/dist/substrate/runtime/observability/log-store.d.mts +1 -0
  656. package/dist/substrate/runtime/observability/log-store.mjs +175 -0
  657. package/dist/substrate/runtime/observability/log-store.mjs.map +1 -0
  658. package/dist/substrate/runtime/observability/logger.d.mts +2 -1
  659. package/dist/substrate/runtime/observability/logger.mjs +5 -9
  660. package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
  661. package/dist/substrate/runtime/observability/output-truncate.mjs +35 -0
  662. package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -0
  663. package/dist/substrate/runtime/observability/pretty-error.mjs +9 -12
  664. package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
  665. package/dist/substrate/runtime/observability/redaction.d.mts +2 -13
  666. package/dist/substrate/runtime/observability/redaction.mjs +8 -14
  667. package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
  668. package/dist/substrate/runtime/observability/span-store.d.mts +1 -0
  669. package/dist/substrate/runtime/observability/span-store.mjs +110 -0
  670. package/dist/substrate/runtime/observability/span-store.mjs.map +1 -0
  671. package/dist/substrate/runtime/observability/spans.mjs +14 -14
  672. package/dist/substrate/runtime/observability/spans.mjs.map +1 -1
  673. package/dist/substrate/runtime/observability/subprocess-capture.mjs +1 -1
  674. package/dist/substrate/runtime/passthrough-or-wrap.mjs +33 -0
  675. package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -0
  676. package/dist/substrate/runtime/paths.d.mts +1 -1
  677. package/dist/substrate/runtime/paths.mjs +4 -3
  678. package/dist/substrate/runtime/paths.mjs.map +1 -1
  679. package/dist/substrate/runtime/phase-preserving-produce.mjs +40 -0
  680. package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -0
  681. package/dist/substrate/runtime/port-broker/index.mjs +3 -0
  682. package/dist/substrate/runtime/port-broker/service.mjs +170 -126
  683. package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
  684. package/dist/substrate/runtime/post-acquire-tasks.mjs +1 -1
  685. package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
  686. package/dist/substrate/runtime/probes.d.mts +7 -5
  687. package/dist/substrate/runtime/probes.mjs +3 -1
  688. package/dist/substrate/runtime/probes.mjs.map +1 -1
  689. package/dist/substrate/runtime/projection/index.mjs +4 -0
  690. package/dist/substrate/runtime/projection/operational-endpoints.mjs +1 -2
  691. package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
  692. package/dist/substrate/runtime/projection/persisted.mjs +33 -15
  693. package/dist/substrate/runtime/projection/persisted.mjs.map +1 -1
  694. package/dist/substrate/runtime/projection/state-ref.mjs +26 -2
  695. package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
  696. package/dist/substrate/runtime/projection/update.mjs +88 -24
  697. package/dist/substrate/runtime/projection/update.mjs.map +1 -1
  698. package/dist/substrate/runtime/random-suffix.mjs +11 -0
  699. package/dist/substrate/runtime/random-suffix.mjs.map +1 -0
  700. package/dist/substrate/runtime/retry-policy.d.mts +59 -1
  701. package/dist/substrate/runtime/retry-policy.mjs +66 -1
  702. package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
  703. package/dist/substrate/runtime/routed-url.d.mts +1 -0
  704. package/dist/substrate/runtime/routed-url.mjs +79 -0
  705. package/dist/substrate/runtime/routed-url.mjs.map +1 -0
  706. package/dist/substrate/runtime/runtime-decode.d.mts +1 -1
  707. package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
  708. package/dist/substrate/runtime/scoped-http-server.mjs +2 -3
  709. package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
  710. package/dist/substrate/runtime/scoped-multimap/index.mjs +2 -0
  711. package/dist/substrate/runtime/scoped-multimap/service.mjs +52 -0
  712. package/dist/substrate/runtime/scoped-multimap/service.mjs.map +1 -0
  713. package/dist/substrate/runtime/scoped-ref-map/index.mjs +2 -0
  714. package/dist/substrate/runtime/scoped-ref-map/service.mjs +2 -2
  715. package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +1 -1
  716. package/dist/substrate/runtime/stage-and-swap/index.mjs +72 -4
  717. package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
  718. package/dist/substrate/runtime/state-store/index.d.mts +1 -0
  719. package/dist/substrate/runtime/state-store/index.mjs +3 -0
  720. package/dist/substrate/runtime/state-store/schema.d.mts +1 -0
  721. package/dist/substrate/runtime/state-store/schema.mjs +19 -14
  722. package/dist/substrate/runtime/state-store/schema.mjs.map +1 -1
  723. package/dist/substrate/runtime/state-store/service.d.mts +1 -0
  724. package/dist/substrate/runtime/state-store/service.mjs +145 -0
  725. package/dist/substrate/runtime/state-store/service.mjs.map +1 -0
  726. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.d.mts +1 -0
  727. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs +33 -0
  728. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -0
  729. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs +1 -1
  730. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
  731. package/dist/substrate/runtime/strategy-registry/index.mjs +4 -0
  732. package/dist/substrate/runtime/strategy-registry/service.mjs +19 -33
  733. package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
  734. package/dist/substrate/runtime/subnet-broker.mjs +60 -0
  735. package/dist/substrate/runtime/subnet-broker.mjs.map +1 -0
  736. package/dist/substrate/runtime/sui-execute/index.d.mts +21 -1
  737. package/dist/substrate/runtime/sui-execute/index.mjs +84 -20
  738. package/dist/substrate/runtime/sui-execute/index.mjs.map +1 -1
  739. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.d.mts +1 -0
  740. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs +32 -0
  741. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +1 -0
  742. package/dist/substrate/runtime/sui-ledger/object-ref.mjs +32 -0
  743. package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +1 -0
  744. package/dist/substrate/runtime/sui-move-build/index.mjs +93 -19
  745. package/dist/substrate/runtime/sui-move-build/index.mjs.map +1 -1
  746. package/dist/substrate/runtime/supervisor/acquire-node.mjs +181 -0
  747. package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -0
  748. package/dist/substrate/runtime/supervisor/background-tasks.mjs +144 -0
  749. package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -0
  750. package/dist/substrate/runtime/supervisor/command-loop.mjs +68 -0
  751. package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -0
  752. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +151 -0
  753. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +1 -0
  754. package/dist/substrate/runtime/supervisor/errors.d.mts +1 -0
  755. package/dist/substrate/runtime/supervisor/errors.mjs +9 -0
  756. package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -0
  757. package/dist/substrate/runtime/supervisor/index.d.mts +1 -0
  758. package/dist/substrate/runtime/supervisor/index.mjs +3 -0
  759. package/dist/substrate/runtime/supervisor/shutdown.mjs +51 -0
  760. package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -0
  761. package/dist/substrate/runtime/supervisor/start-supervisor.d.mts +1 -0
  762. package/dist/substrate/runtime/supervisor/start-supervisor.mjs +250 -0
  763. package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -0
  764. package/dist/substrate/runtime/supervisor/state.d.mts +1 -0
  765. package/dist/substrate/runtime/supervisor/state.mjs +22 -0
  766. package/dist/substrate/runtime/supervisor/state.mjs.map +1 -0
  767. package/dist/substrate/runtime/supervisor/teardown.mjs +57 -0
  768. package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -0
  769. package/dist/substrate/runtime/supervisor/types.d.mts +1 -0
  770. package/dist/substrate/runtime/supervisor/wiring.mjs +133 -0
  771. package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -0
  772. package/dist/substrate/runtime/typed-env.mjs +8 -0
  773. package/dist/substrate/runtime/typed-env.mjs.map +1 -0
  774. package/dist/substrate/state-store.d.mts +1 -0
  775. package/dist/substrate/versioned-doc-schema.mjs +17 -0
  776. package/dist/substrate/versioned-doc-schema.mjs.map +1 -0
  777. package/dist/substrate/versioned-doc-sync.mjs +94 -0
  778. package/dist/substrate/versioned-doc-sync.mjs.map +1 -0
  779. package/dist/surfaces/cli/command-tree.mjs.map +1 -1
  780. package/dist/surfaces/cli/commands/index.mjs +9 -0
  781. package/dist/surfaces/cli/commands/prune-picker-entry.mjs +13 -0
  782. package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -0
  783. package/dist/surfaces/cli/commands/prune-picker.mjs +3 -2
  784. package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
  785. package/dist/surfaces/cli/commands/prune.mjs +34 -17
  786. package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
  787. package/dist/surfaces/cli/commands/status.mjs +2 -2
  788. package/dist/surfaces/cli/commands/status.mjs.map +1 -1
  789. package/dist/surfaces/cli/commands/supervisor-presence.mjs +5 -3
  790. package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
  791. package/dist/surfaces/cli/commands/wipe.mjs +38 -4
  792. package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
  793. package/dist/surfaces/cli/errors.mjs +1 -1
  794. package/dist/surfaces/cli/errors.mjs.map +1 -1
  795. package/dist/surfaces/cli/flags.mjs +0 -1
  796. package/dist/surfaces/cli/flags.mjs.map +1 -1
  797. package/dist/surfaces/cli/index.mjs +98 -18
  798. package/dist/surfaces/cli/index.mjs.map +1 -1
  799. package/dist/surfaces/cli/output.mjs +1 -0
  800. package/dist/surfaces/cli/output.mjs.map +1 -1
  801. package/dist/surfaces/cli/sysexits.mjs +2 -1
  802. package/dist/surfaces/cli/sysexits.mjs.map +1 -1
  803. package/dist/surfaces/tui/app.mjs +25 -3
  804. package/dist/surfaces/tui/app.mjs.map +1 -1
  805. package/dist/surfaces/tui/display-derivation.mjs +23 -74
  806. package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
  807. package/dist/surfaces/tui/event-log.mjs +35 -23
  808. package/dist/surfaces/tui/event-log.mjs.map +1 -1
  809. package/dist/surfaces/tui/input.mjs +5 -1
  810. package/dist/surfaces/tui/input.mjs.map +1 -1
  811. package/dist/surfaces/tui/plain-renderer.mjs +70 -34
  812. package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
  813. package/package.json +24 -4
  814. package/dist/contracts/liveness-classifier.d.mts +0 -19
  815. package/dist/contracts/network-resolver.d.mts +0 -15
  816. package/dist/contracts/network-resolver.mjs +0 -7
  817. package/dist/contracts/network-resolver.mjs.map +0 -1
  818. package/dist/orchestrators/codegen/extras.mjs +0 -16
  819. package/dist/orchestrators/codegen/extras.mjs.map +0 -1
  820. package/dist/plugins/deepbook/routable.mjs +0 -21
  821. package/dist/plugins/deepbook/routable.mjs.map +0 -1
  822. package/dist/plugins/faucet/service.d.mts +0 -20
  823. package/dist/plugins/faucet/strategies/sui-local.d.mts +0 -23
  824. package/dist/plugins/faucet/strategies/sui-local.mjs.map +0 -1
  825. package/dist/plugins/sui/seed-objects.d.mts +0 -18
  826. package/dist/plugins/sui/seed-objects.mjs +0 -25
  827. package/dist/plugins/sui/seed-objects.mjs.map +0 -1
  828. package/dist/runtime/built-in-plugin-layers.mjs.map +0 -1
  829. package/dist/substrate/runtime/context-helpers.mjs +0 -32
  830. package/dist/substrate/runtime/context-helpers.mjs.map +0 -1
  831. package/dist/substrate/runtime/run.mjs.map +0 -1
  832. package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs +0 -30
  833. package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs.map +0 -1
  834. package/dist/substrate/runtime/supervisor.d.mts +0 -1
  835. package/dist/substrate/runtime/supervisor.mjs +0 -831
  836. package/dist/substrate/runtime/supervisor.mjs.map +0 -1
  837. package/dist/surfaces/cli/commands/doctor-probes.mjs.map +0 -1
  838. /package/dist/{substrate/runtime/strategy-registry/faucet-capability-for.d.mts → plugins/faucet/http.d.mts} +0 -0
@@ -0,0 +1,99 @@
1
+ import { versionedDocSchema } from "../../substrate/versioned-doc-schema.mjs";
2
+ import { atomicWriteFile } from "../../substrate/runtime/atomic-write.mjs";
3
+ import { Effect, FileSystem, Schema } from "effect";
4
+ const RESTORE_PENDING_FILE_NAME = "snapshot.restore-pending.json";
5
+ const RestorePendingContainerSchema = Schema.Struct({
6
+ plugin: Schema.String,
7
+ role: Schema.String,
8
+ targetImageName: Schema.String,
9
+ stagedImageTag: Schema.String,
10
+ /** Content-addressed image identity (e.g. `sha256:...`). The
11
+ * scanner uses this as the third-and-final recovery source when
12
+ * both `targetImageName` and `stagedImageTag` have been removed
13
+ * from the daemon (e.g. operator `docker system prune` between
14
+ * crash and restart). Digests survive prune because they ARE the
15
+ * image — Docker can re-tag from any digest still backed by a
16
+ * pinned layer. */
17
+ digest: Schema.String
18
+ });
19
+ /** v2 marker variant — current shape. New variants are added by
20
+ * appending another `versionedDocSchema(N, ...)` arm to the Union
21
+ * below; consumers switch on `version` after decode. */
22
+ const RestorePendingDocumentV2Schema = versionedDocSchema(2, {
23
+ snapshotId: Schema.String,
24
+ artifactDir: Schema.String,
25
+ app: Schema.String,
26
+ stack: Schema.String,
27
+ network: Schema.String,
28
+ containers: Schema.Array(RestorePendingContainerSchema)
29
+ });
30
+ /** Discriminated union of every supported marker version. Today only
31
+ * v2 is on disk; the Union shape exists so a v3 migration is a
32
+ * one-line `Schema.Union(...v2, ...v3)` extension here plus a new
33
+ * `case` in the recovery scanner. Stale pre-v2 markers fail decode
34
+ * cleanly — the scanner translates that into a "leave on disk for
35
+ * operator review" warning rather than a fatal error. */
36
+ const RestorePendingDocumentSchema = Schema.Union([RestorePendingDocumentV2Schema]);
37
+ /** Path the marker lives at, relative to a runtime stack root or
38
+ * staged-tree root. */
39
+ const pendingMarkerPath = (root) => `${root}/${RESTORE_PENDING_FILE_NAME}`;
40
+ /** Failure shape for marker IO. The scanner / restore both map this
41
+ * onto their own typed channels. */
42
+ var RestorePendingMarkerIoError = class extends Schema.TaggedErrorClass()("SnapshotRestorePendingMarkerIoError", {
43
+ op: Schema.Literals([
44
+ "read",
45
+ "write",
46
+ "remove",
47
+ "decode"
48
+ ]),
49
+ path: Schema.String,
50
+ detail: Schema.String,
51
+ cause: Schema.optional(Schema.Defect)
52
+ }) {};
53
+ /** Write or rewrite the marker. The serialized payload is one trailing
54
+ * newline larger than `JSON.stringify(doc, null, 2)`. */
55
+ const writePendingMarker = (root, doc) => Effect.gen(function* () {
56
+ const path = pendingMarkerPath(root);
57
+ yield* atomicWriteFile(path, new TextEncoder().encode(`${JSON.stringify(doc, null, 2)}\n`)).pipe(Effect.catch((cause) => Effect.fail(new RestorePendingMarkerIoError({
58
+ op: "write",
59
+ path,
60
+ detail: `write ${RESTORE_PENDING_FILE_NAME} failed`,
61
+ cause
62
+ }))));
63
+ });
64
+ /** Build a marker document from the snapshot metadata + outstanding
65
+ * per-image entries. Caller decides what's outstanding. */
66
+ const makePendingMarkerDocument = (args) => ({
67
+ version: 2,
68
+ snapshotId: args.meta.id,
69
+ artifactDir: args.artifactDir,
70
+ app: args.meta.app,
71
+ stack: args.meta.stack,
72
+ network: args.meta.network,
73
+ containers: args.containers
74
+ });
75
+ /** Rewrite an existing marker, replacing the `containers` list.
76
+ * The recovery scanner uses this to whittle the outstanding entry
77
+ * list down as it retags each image — the next supervise (or the
78
+ * next loop iteration after a transient daemon hiccup) sees an
79
+ * up-to-date marker without re-deriving identity from `meta.json`. */
80
+ const rewritePendingMarkerContainers = (doc, containers) => ({
81
+ ...doc,
82
+ containers
83
+ });
84
+ /** Remove the marker. Idempotent — `force: true` swallows "already
85
+ * gone". Other IO failures surface as `RestorePendingMarkerIoError`. */
86
+ const removePendingMarker = (root) => Effect.gen(function* () {
87
+ const fs = yield* FileSystem.FileSystem;
88
+ const path = pendingMarkerPath(root);
89
+ yield* fs.remove(path, { force: true }).pipe(Effect.catch((cause) => Effect.fail(new RestorePendingMarkerIoError({
90
+ op: "remove",
91
+ path,
92
+ detail: `remove ${RESTORE_PENDING_FILE_NAME} failed`,
93
+ cause
94
+ }))));
95
+ });
96
+ //#endregion
97
+ export { RESTORE_PENDING_FILE_NAME, RestorePendingDocumentSchema, RestorePendingMarkerIoError, makePendingMarkerDocument, pendingMarkerPath, removePendingMarker, rewritePendingMarkerContainers, writePendingMarker };
98
+
99
+ //# sourceMappingURL=pending-marker.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-marker.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/pending-marker.ts"],"sourcesContent":["// Restore-pending marker — shared schema + IO between `restore.ts`\n// (writer/clearer) and `recover-pending.ts` (reader/recoverer).\n//\n// Recovery contract:\n//\n// The marker is written into the snapshot's staged tree BEFORE the\n// atomic stage-and-swap publish, then survives the swap into the\n// restored runtime root. After publish, `runRestore` runs the\n// Docker handoff (promote staged tags → recorded names, remove\n// captured managed containers) under `Effect.uninterruptible`. If\n// the process is hard-killed mid-handoff — or any step fails after\n// K of N images promoted — the marker stays on disk and lists which\n// `(targetImageName ← stagedImageTag, digest)` operations remain\n// pending. The digest is the loaded image's content-addressed\n// identity; the scanner falls back to it when the staging tag has\n// been pruned from the daemon between crash and restart.\n//\n// On the next supervise startup, `recoverPendingRestore` reads the\n// marker, retries each pending entry's `tagImage`, removes the\n// marker only when every entry verifies, and is idempotent: a\n// marker present with zero outstanding entries is treated as\n// already-recovered and cleared in one call.\n//\n// The marker is updated AFTER EACH successful image promote in the\n// happy path (see `restore.ts:promoteStagedImages`) so the\n// recovery scanner only has to address the entries still listed —\n// it never has to know which of the original N images was the one\n// that failed.\n\nimport { Effect, FileSystem, Schema } from 'effect';\n\nimport { atomicWriteFile } from '../../substrate/runtime/atomic-write.ts';\nimport { versionedDocSchema } from '../../substrate/versioned-doc-schema.ts';\nimport type { SnapshotMetadata } from './descriptor.ts';\n\nexport const SNAPSHOT_RESTORE_PENDING_VERSION = 2 as const;\nexport const RESTORE_PENDING_FILE_NAME = 'snapshot.restore-pending.json' as const;\n\nexport const RestorePendingContainerSchema = Schema.Struct({\n\tplugin: Schema.String,\n\trole: Schema.String,\n\ttargetImageName: Schema.String,\n\tstagedImageTag: Schema.String,\n\t/** Content-addressed image identity (e.g. `sha256:...`). The\n\t * scanner uses this as the third-and-final recovery source when\n\t * both `targetImageName` and `stagedImageTag` have been removed\n\t * from the daemon (e.g. operator `docker system prune` between\n\t * crash and restart). Digests survive prune because they ARE the\n\t * image — Docker can re-tag from any digest still backed by a\n\t * pinned layer. */\n\tdigest: Schema.String,\n});\nexport type RestorePendingContainer = Schema.Schema.Type<typeof RestorePendingContainerSchema>;\n\nconst RestorePendingDocumentV2Payload = {\n\tsnapshotId: Schema.String,\n\tartifactDir: Schema.String,\n\tapp: Schema.String,\n\tstack: Schema.String,\n\tnetwork: Schema.String,\n\tcontainers: Schema.Array(RestorePendingContainerSchema),\n} as const;\n\n/** v2 marker variant — current shape. New variants are added by\n * appending another `versionedDocSchema(N, ...)` arm to the Union\n * below; consumers switch on `version` after decode. */\nexport const RestorePendingDocumentV2Schema = versionedDocSchema(\n\tSNAPSHOT_RESTORE_PENDING_VERSION,\n\tRestorePendingDocumentV2Payload,\n);\nexport type RestorePendingDocumentV2 = Schema.Schema.Type<typeof RestorePendingDocumentV2Schema>;\n\n/** Discriminated union of every supported marker version. Today only\n * v2 is on disk; the Union shape exists so a v3 migration is a\n * one-line `Schema.Union(...v2, ...v3)` extension here plus a new\n * `case` in the recovery scanner. Stale pre-v2 markers fail decode\n * cleanly — the scanner translates that into a \"leave on disk for\n * operator review\" warning rather than a fatal error. */\nexport const RestorePendingDocumentSchema = Schema.Union([RestorePendingDocumentV2Schema]);\nexport type RestorePendingDocument = Schema.Schema.Type<typeof RestorePendingDocumentSchema>;\n\n/** Path the marker lives at, relative to a runtime stack root or\n * staged-tree root. */\nexport const pendingMarkerPath = (root: string): string =>\n\t`${root}/${RESTORE_PENDING_FILE_NAME}`;\n\n/** Failure shape for marker IO. The scanner / restore both map this\n * onto their own typed channels. */\nexport class RestorePendingMarkerIoError extends Schema.TaggedErrorClass<RestorePendingMarkerIoError>()(\n\t'SnapshotRestorePendingMarkerIoError',\n\t{\n\t\top: Schema.Literals(['read', 'write', 'remove', 'decode']),\n\t\tpath: Schema.String,\n\t\tdetail: Schema.String,\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\n/** Write or rewrite the marker. The serialized payload is one trailing\n * newline larger than `JSON.stringify(doc, null, 2)`. */\nexport const writePendingMarker = (\n\troot: string,\n\tdoc: RestorePendingDocument,\n): Effect.Effect<void, RestorePendingMarkerIoError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst path = pendingMarkerPath(root);\n\t\t// The marker is durable cross-process state on the LIVE runtime\n\t\t// root (recovery scanner reads it on the next supervise). Route\n\t\t// through the canonical atomic primitive (STYLE_GUIDE §17) so a\n\t\t// crashed mid-rewrite never leaves the scanner a half-written /\n\t\t// undecodable marker. `atomicWriteFile` (not `atomicWriteJson`)\n\t\t// preserves the exact `JSON.stringify(…, 2)` + trailing-newline\n\t\t// byte shape this writer has always produced.\n\t\tconst bytes = new TextEncoder().encode(`${JSON.stringify(doc, null, 2)}\\n`);\n\t\tyield* atomicWriteFile(path, bytes).pipe(\n\t\t\tEffect.catch(\n\t\t\t\t(cause): Effect.Effect<never, RestorePendingMarkerIoError> =>\n\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\tnew RestorePendingMarkerIoError({\n\t\t\t\t\t\t\top: 'write',\n\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\tdetail: `write ${RESTORE_PENDING_FILE_NAME} failed`,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t),\n\t\t);\n\t});\n\n/** Build a marker document from the snapshot metadata + outstanding\n * per-image entries. Caller decides what's outstanding. */\nexport const makePendingMarkerDocument = (args: {\n\treadonly meta: SnapshotMetadata;\n\treadonly artifactDir: string;\n\treadonly containers: ReadonlyArray<RestorePendingContainer>;\n}): RestorePendingDocument => ({\n\tversion: SNAPSHOT_RESTORE_PENDING_VERSION,\n\tsnapshotId: args.meta.id,\n\tartifactDir: args.artifactDir,\n\tapp: args.meta.app,\n\tstack: args.meta.stack,\n\tnetwork: args.meta.network,\n\tcontainers: args.containers,\n});\n\n/** Rewrite an existing marker, replacing the `containers` list.\n * The recovery scanner uses this to whittle the outstanding entry\n * list down as it retags each image — the next supervise (or the\n * next loop iteration after a transient daemon hiccup) sees an\n * up-to-date marker without re-deriving identity from `meta.json`. */\nexport const rewritePendingMarkerContainers = (\n\tdoc: RestorePendingDocument,\n\tcontainers: ReadonlyArray<RestorePendingContainer>,\n): RestorePendingDocument => ({\n\t...doc,\n\tcontainers,\n});\n\n/** Remove the marker. Idempotent — `force: true` swallows \"already\n * gone\". Other IO failures surface as `RestorePendingMarkerIoError`. */\nexport const removePendingMarker = (\n\troot: string,\n): Effect.Effect<void, RestorePendingMarkerIoError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst path = pendingMarkerPath(root);\n\t\tyield* fs.remove(path, { force: true }).pipe(\n\t\t\tEffect.catch(\n\t\t\t\t(cause): Effect.Effect<never, RestorePendingMarkerIoError> =>\n\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\tnew RestorePendingMarkerIoError({\n\t\t\t\t\t\t\top: 'remove',\n\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\tdetail: `remove ${RESTORE_PENDING_FILE_NAME} failed`,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t),\n\t\t);\n\t});\n"],"mappings":";;;AAoCA,MAAa,4BAA4B;AAEzC,MAAa,gCAAgC,OAAO,OAAO;CAC1D,QAAQ,OAAO;CACf,MAAM,OAAO;CACb,iBAAiB,OAAO;CACxB,gBAAgB,OAAO;;;;;;;;CAQvB,QAAQ,OAAO;CACf,CAAC;;;;AAeF,MAAa,iCAAiC,mBAAA,GAE7C;CAbA,YAAY,OAAO;CACnB,aAAa,OAAO;CACpB,KAAK,OAAO;CACZ,OAAO,OAAO;CACd,SAAS,OAAO;CAChB,YAAY,OAAO,MAAM,8BAA8B;CAQvD,CACA;;;;;;;AASD,MAAa,+BAA+B,OAAO,MAAM,CAAC,+BAA+B,CAAC;;;AAK1F,MAAa,qBAAqB,SACjC,GAAG,KAAK,GAAG;;;AAIZ,IAAa,8BAAb,cAAiD,OAAO,kBAA+C,CACtG,uCACA;CACC,IAAI,OAAO,SAAS;EAAC;EAAQ;EAAS;EAAU;EAAS,CAAC;CAC1D,MAAM,OAAO;CACb,QAAQ,OAAO;CACf,OAAO,OAAO,SAAS,OAAO,OAAO;CACrC,CACD,CAAC;;;AAIF,MAAa,sBACZ,MACA,QAEA,OAAO,IAAI,aAAa;CACvB,MAAM,OAAO,kBAAkB,KAAK;AASpC,QAAO,gBAAgB,MADT,IAAI,aAAa,CAAC,OAAO,GAAG,KAAK,UAAU,KAAK,MAAM,EAAE,CAAC,IACrC,CAAC,CAAC,KACnC,OAAO,OACL,UACA,OAAO,KACN,IAAI,4BAA4B;EAC/B,IAAI;EACJ;EACA,QAAQ,SAAS,0BAA0B;EAC3C;EACA,CAAC,CACF,CACF,CACD;EACA;;;AAIH,MAAa,6BAA6B,UAIX;CAC9B,SAAA;CACA,YAAY,KAAK,KAAK;CACtB,aAAa,KAAK;CAClB,KAAK,KAAK,KAAK;CACf,OAAO,KAAK,KAAK;CACjB,SAAS,KAAK,KAAK;CACnB,YAAY,KAAK;CACjB;;;;;;AAOD,MAAa,kCACZ,KACA,gBAC6B;CAC7B,GAAG;CACH;CACA;;;AAID,MAAa,uBACZ,SAEA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,kBAAkB,KAAK;AACpC,QAAO,GAAG,OAAO,MAAM,EAAE,OAAO,MAAM,CAAC,CAAC,KACvC,OAAO,OACL,UACA,OAAO,KACN,IAAI,4BAA4B;EAC/B,IAAI;EACJ;EACA,QAAQ,UAAU,0BAA0B;EAC5C;EACA,CAAC,CACF,CACF,CACD;EACA"}
@@ -0,0 +1,23 @@
1
+ import { Effect } from "effect";
2
+ //#region src/orchestrators/snapshot/phase-error.ts
3
+ /**
4
+ * Build an orchestrator's curried error-failer from its phase-tagged
5
+ * error constructor.
6
+ *
7
+ * `makePhaseFailer(XPhaseError)` returns
8
+ * `(phase, detail, plugin?) => (cause) => Effect.fail(new XPhaseError(...))`.
9
+ *
10
+ * The `plugin` argument is forwarded only when defined, so call sites
11
+ * for the plugin-less classes (`PrunePhaseError`, `WipePhaseError`)
12
+ * produce the exact same constructor payload as before.
13
+ */
14
+ const makePhaseFailer = (ErrorClass) => (phase, detail, plugin) => (cause) => Effect.fail(new ErrorClass({
15
+ phase,
16
+ detail,
17
+ cause,
18
+ ...plugin === void 0 ? {} : { plugin }
19
+ }));
20
+ //#endregion
21
+ export { makePhaseFailer };
22
+
23
+ //# sourceMappingURL=phase-error.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phase-error.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/phase-error.ts"],"sourcesContent":["// Shared phase-error failer factory.\n//\n// The four snapshot orchestrators (capture / restore / prune / wipe)\n// each define a phase-tagged error class and the SAME curried failer\n// shape over it:\n//\n// (phase, detail, plugin?) => (cause: unknown) => Effect.fail(\n// new XPhaseError({ phase, detail, cause, ...(plugin ? { plugin } : {}) }))\n//\n// The error CLASSES differ (distinct `_tag`s, distinct `phase`\n// literal unions, and only capture/restore carry an optional\n// `plugin`), so the factory is parameterized on the constructor.\n// `makePhaseFailer(ErrorClass)` returns that orchestrator's curried\n// failer; each call site keeps byte-identical behavior.\n//\n// The sibling `lifecycle-prune/errors.ts` failer is intentionally NOT\n// built on this — it is a single-curry-level `(phase) => (cause) =>\n// new LifecyclePruneError(...)` that returns a plain error (not an\n// `Effect.fail`), takes a typed `DockerRuntimeError`, and synthesizes\n// its own `detail`. Different model; left alone.\n\nimport { Effect } from 'effect';\n\n/** Fields the factory needs to construct any of the snapshot phase\n * errors. `plugin` is optional and only consumed by the error classes\n * that declare it (capture / restore); the prune / wipe classes ignore\n * it (the factory never passes a key for those call sites). */\ninterface PhaseErrorFields<Phase extends string> {\n\treadonly phase: Phase;\n\treadonly detail: string;\n\treadonly cause?: unknown;\n\treadonly plugin?: string;\n}\n\n/**\n * Build an orchestrator's curried error-failer from its phase-tagged\n * error constructor.\n *\n * `makePhaseFailer(XPhaseError)` returns\n * `(phase, detail, plugin?) => (cause) => Effect.fail(new XPhaseError(...))`.\n *\n * The `plugin` argument is forwarded only when defined, so call sites\n * for the plugin-less classes (`PrunePhaseError`, `WipePhaseError`)\n * produce the exact same constructor payload as before.\n */\nexport const makePhaseFailer =\n\t<Phase extends string, E>(ErrorClass: new (fields: PhaseErrorFields<Phase>) => E) =>\n\t(\n\t\tphase: Phase,\n\t\tdetail: string,\n\t\tplugin?: string,\n\t): ((cause: unknown) => Effect.Effect<never, E>) =>\n\t(cause) =>\n\t\tEffect.fail(\n\t\t\tnew ErrorClass({ phase, detail, cause, ...(plugin === undefined ? {} : { plugin }) }),\n\t\t);\n"],"mappings":";;;;;;;;;;;;;AA6CA,MAAa,mBACc,gBAEzB,OACA,QACA,YAEA,UACA,OAAO,KACN,IAAI,WAAW;CAAE;CAAO;CAAQ;CAAO,GAAI,WAAW,KAAA,IAAY,EAAE,GAAG,EAAE,QAAQ;CAAG,CAAC,CACrF"}
@@ -1,4 +1,7 @@
1
+ import { decodeJsonText } from "../../substrate/runtime/runtime-decode.mjs";
2
+ import { SNAPSHOT_IMAGE_ROLE } from "../../runtime/docker/container.mjs";
1
3
  import { SnapshotMetadataSchema } from "./descriptor.mjs";
4
+ import { makePhaseFailer } from "./phase-error.mjs";
2
5
  import { SNAPSHOTS_DIR_NAME } from "./wipe.mjs";
3
6
  import { Effect, FileSystem, Schema } from "effect";
4
7
  //#region src/orchestrators/snapshot/prune.ts
@@ -6,51 +9,31 @@ var PrunePhaseError = class extends Schema.TaggedErrorClass()("SnapshotPrunePhas
6
9
  phase: Schema.Literals([
7
10
  "enumerate-catalog",
8
11
  "read-meta",
9
- "classify",
10
12
  "sweep-images",
11
13
  "sweep-directories"
12
14
  ]),
13
15
  detail: Schema.String,
14
16
  cause: Schema.optional(Schema.Defect)
15
17
  }) {};
16
- const failPhase = (phase, detail) => (cause) => Effect.fail(new PrunePhaseError({
17
- phase,
18
- detail,
19
- cause
20
- }));
18
+ const failPhase = makePhaseFailer(PrunePhaseError);
21
19
  const readMetaOpt = (dir) => Effect.gen(function* () {
22
20
  const fs = yield* FileSystem.FileSystem;
23
21
  const path = `${dir}/meta.json`;
24
22
  if (!(yield* fs.exists(path).pipe(Effect.catch(() => Effect.succeed(false))))) return null;
25
- const text = yield* fs.readFileString(path).pipe(Effect.catch(failPhase("read-meta", `read ${path} failed`)));
26
- const raw = yield* Effect.try({
27
- try: () => JSON.parse(text),
28
- catch: () => null
23
+ return yield* decodeJsonText(SnapshotMetadataSchema, yield* fs.readFileString(path).pipe(Effect.catch(failPhase("read-meta", `read ${path} failed`))), {
24
+ source: path,
25
+ mkError: () => null
29
26
  }).pipe(Effect.catch(() => Effect.succeed(null)));
30
- if (raw === null) return null;
31
- return yield* Schema.decodeUnknownEffect(SnapshotMetadataSchema)(raw).pipe(Effect.catch(() => Effect.succeed(null)));
32
- });
33
- /** Build the hints document a classifier consumes. Snapshot prune is
34
- * age- + identity-based by default; plugin-specific hints flow
35
- * through `pluginHints`. */
36
- const hintsFor = (meta) => ({
37
- heartbeatAt: meta.createdAt,
38
- claimPid: null,
39
- claimStartTime: null,
40
- pluginHints: {
41
- snapshotId: meta.id,
42
- identity: meta.identity,
43
- participants: meta.participants
44
- }
45
27
  });
46
28
  /**
47
- * Walk the snapshot catalog, dispatch each artifact's hints through
48
- * the contributed classifiers, and reap anything classified
49
- * `abandoned`. Concurrent sweeps over the same catalog are not
50
- * supported (caller holds `snapshot.reservation` or `stack.lock`).
29
+ * Walk the snapshot catalog and reap partial artifacts (entries whose
30
+ * `meta.json` is missing or unparseable). Concurrent sweeps over the
31
+ * same catalog are not supported (caller holds `snapshot.reservation`
32
+ * or `stack.lock`).
51
33
  *
52
- * Also removes managed images via the runtime adapter's label-filtered
53
- * image cleanup.
34
+ * Also removes committed snapshot byproduct images via the runtime
35
+ * adapter's label-filtered image cleanup, scoped to `role:
36
+ * SNAPSHOT_IMAGE_ROLE` so build images are never touched.
54
37
  */
55
38
  const runPrune = (inputs) => Effect.gen(function* () {
56
39
  const fs = yield* FileSystem.FileSystem;
@@ -65,8 +48,7 @@ const runPrune = (inputs) => Effect.gen(function* () {
65
48
  const reaped = [];
66
49
  for (const id of ids) {
67
50
  const dir = `${catalogDir}/${id}`;
68
- const meta = yield* readMetaOpt(dir);
69
- if (meta === null) {
51
+ if ((yield* readMetaOpt(dir)) === null) {
70
52
  yield* fs.remove(dir, {
71
53
  recursive: true,
72
54
  force: true
@@ -75,29 +57,12 @@ const runPrune = (inputs) => Effect.gen(function* () {
75
57
  id,
76
58
  classification: "abandoned"
77
59
  });
78
- continue;
79
- }
80
- const hints = hintsFor(meta);
81
- let classification = "alive";
82
- for (const { decl } of inputs.classifiers) {
83
- const c = yield* decl.classify(hints).pipe(Effect.catch(() => Effect.succeed("alive")));
84
- if (c !== "alive") {
85
- classification = c;
86
- break;
87
- }
88
- }
89
- if (classification === "abandoned") {
90
- yield* fs.remove(dir, {
91
- recursive: true,
92
- force: true
93
- }).pipe(Effect.catch(failPhase("sweep-directories", `remove ${dir} failed`)));
94
- reaped.push({
95
- id,
96
- classification
97
- });
98
60
  }
99
61
  }
100
- const imagesSwept = yield* inputs.runtime.removeManagedImages(inputs.imageLabelFilter).pipe(Effect.catch(failPhase("sweep-images", `image sweep failed`)));
62
+ const imagesSwept = yield* inputs.runtime.removeManagedImages({
63
+ ...inputs.imageLabelFilter,
64
+ role: SNAPSHOT_IMAGE_ROLE
65
+ }).pipe(Effect.catch(failPhase("sweep-images", `image sweep failed`)));
101
66
  return {
102
67
  inspected: ids.length,
103
68
  reaped,
@@ -105,6 +70,6 @@ const runPrune = (inputs) => Effect.gen(function* () {
105
70
  };
106
71
  }).pipe(Effect.withSpan("orchestrator.snapshot.prune"));
107
72
  //#endregion
108
- export { runPrune };
73
+ export { PrunePhaseError, runPrune };
109
74
 
110
75
  //# sourceMappingURL=prune.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"prune.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/prune.ts"],"sourcesContent":["// Prune — label-scoped orphan sweep.\n//\n// Architecture § 4 / Decision §10: plugins emit `LivenessClassifier`\n// decls; the L3 prune orchestrator dispatches to them with the\n// registry-persisted hints and reaps anything classified `abandoned`.\n//\n// This module is the snapshot-orchestrator-side of prune: sweeping\n// snapshot-byproduct images, snapshot-catalog directories that lost\n// their owning stack, and cache entries whose chainId is no longer\n// referenced anywhere.\n//\n// The substrate-wide registry prune (engine resources / stack\n// roster) is a sibling orchestrator and lives elsewhere; this file\n// is scoped to snapshot adjacent artifacts.\n\nimport { Effect, FileSystem, Schema } from 'effect';\n\nimport type {\n\tLivenessClassification,\n\tLivenessClassifierDecl,\n\tLivenessHints,\n} from '../../contracts/liveness-classifier.ts';\nimport type { ContainerRuntime } from '../../contracts/container-runtime.ts';\nimport { SnapshotMetadataSchema, type SnapshotMetadata } from './descriptor.ts';\nimport { SNAPSHOTS_DIR_NAME } from './wipe.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\nexport class PrunePhaseError extends Schema.TaggedErrorClass<PrunePhaseError>()(\n\t'SnapshotPrunePhaseError',\n\t{\n\t\tphase: Schema.Literals([\n\t\t\t'enumerate-catalog',\n\t\t\t'read-meta',\n\t\t\t'classify',\n\t\t\t'sweep-images',\n\t\t\t'sweep-directories',\n\t\t]),\n\t\tdetail: Schema.String,\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\nconst failPhase =\n\t(\n\t\tphase: PrunePhaseError['phase'],\n\t\tdetail: string,\n\t): ((cause: unknown) => Effect.Effect<never, PrunePhaseError>) =>\n\t(cause) =>\n\t\tEffect.fail(new PrunePhaseError({ phase, detail, cause }));\n\n// -----------------------------------------------------------------------------\n// Inputs\n// -----------------------------------------------------------------------------\n\n/** Per-classifier dispatch entry — the L3 prune orchestrator hands\n * the classifier the persisted hints for one snapshot artifact /\n * cache entry / etc., and the classifier returns `'alive' | 'dormant'\n * | 'stale' | 'abandoned'`. */\nexport interface ClassifierDispatch {\n\treadonly plugin: string;\n\treadonly decl: LivenessClassifierDecl;\n}\n\nexport interface PruneInputs {\n\t/** Stack root containing the `snapshots/` catalog. */\n\treadonly stackRoot: string;\n\t/** Filter for managed image cleanup. */\n\treadonly imageLabelFilter: { readonly app: string; readonly stack: string };\n\t/** Classifiers contributed by plugins — same set the L3 prune\n\t * orchestrator dispatches to. Iterated in declaration order; the\n\t * first non-`alive` classification wins. */\n\treadonly classifiers: ReadonlyArray<ClassifierDispatch>;\n\treadonly runtime: ContainerRuntime;\n}\n\nexport interface PruneResult {\n\treadonly inspected: number;\n\treadonly reaped: ReadonlyArray<{\n\t\treadonly id: string;\n\t\treadonly classification: LivenessClassification;\n\t}>;\n\treadonly imagesSwept: number;\n}\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\nconst readMetaOpt = (\n\tdir: string,\n): Effect.Effect<SnapshotMetadata | null, PrunePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst path = `${dir}/meta.json`;\n\t\tconst exists = yield* fs.exists(path).pipe(Effect.catch(() => Effect.succeed(false)));\n\t\tif (!exists) return null;\n\t\tconst text = yield* fs\n\t\t\t.readFileString(path)\n\t\t\t.pipe(Effect.catch(failPhase('read-meta', `read ${path} failed`)));\n\t\tconst raw = yield* Effect.try({\n\t\t\ttry: () => JSON.parse(text) as unknown,\n\t\t\tcatch: () => null,\n\t\t}).pipe(Effect.catch(() => Effect.succeed(null)));\n\t\tif (raw === null) return null;\n\t\treturn yield* Schema.decodeUnknownEffect(SnapshotMetadataSchema)(raw).pipe(\n\t\t\tEffect.catch(() => Effect.succeed(null)),\n\t\t);\n\t});\n\n/** Build the hints document a classifier consumes. Snapshot prune is\n * age- + identity-based by default; plugin-specific hints flow\n * through `pluginHints`. */\nconst hintsFor = (meta: SnapshotMetadata): LivenessHints => ({\n\theartbeatAt: meta.createdAt,\n\tclaimPid: null,\n\tclaimStartTime: null,\n\tpluginHints: {\n\t\tsnapshotId: meta.id,\n\t\tidentity: meta.identity,\n\t\tparticipants: meta.participants,\n\t},\n});\n\n// -----------------------------------------------------------------------------\n// Top-level prune\n// -----------------------------------------------------------------------------\n\n/**\n * Walk the snapshot catalog, dispatch each artifact's hints through\n * the contributed classifiers, and reap anything classified\n * `abandoned`. Concurrent sweeps over the same catalog are not\n * supported (caller holds `snapshot.reservation` or `stack.lock`).\n *\n * Also removes managed images via the runtime adapter's label-filtered\n * image cleanup.\n */\nexport const runPrune = (\n\tinputs: PruneInputs,\n): Effect.Effect<PruneResult, PrunePhaseError, 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': 'prune',\n\t\t});\n\t\tconst catalogDir = `${inputs.stackRoot}/${SNAPSHOTS_DIR_NAME}`;\n\t\tconst catalogExists = yield* fs\n\t\t\t.exists(catalogDir)\n\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\tif (!catalogExists) {\n\t\t\treturn { inspected: 0, reaped: [], imagesSwept: 0 } satisfies PruneResult;\n\t\t}\n\t\tconst ids = yield* fs\n\t\t\t.readDirectory(catalogDir)\n\t\t\t.pipe(Effect.catch(failPhase('enumerate-catalog', `readdir ${catalogDir} failed`)));\n\n\t\tconst reaped: Array<{ id: string; classification: LivenessClassification }> = [];\n\t\tfor (const id of ids) {\n\t\t\tconst dir = `${catalogDir}/${id}`;\n\t\t\tconst meta = yield* readMetaOpt(dir);\n\t\t\t// Partial artifacts (no meta) — classify as `abandoned` by\n\t\t\t// convention. The catalog list already hides them; reaping\n\t\t\t// is just freeing the disk slot.\n\t\t\tif (meta === null) {\n\t\t\t\tyield* fs\n\t\t\t\t\t.remove(dir, { recursive: true, force: true })\n\t\t\t\t\t.pipe(Effect.catch(failPhase('sweep-directories', `remove ${dir} failed`)));\n\t\t\t\treaped.push({ id, classification: 'abandoned' });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst hints = hintsFor(meta);\n\t\t\tlet classification: LivenessClassification = 'alive';\n\t\t\tfor (const { decl } of inputs.classifiers) {\n\t\t\t\tconst c = yield* decl\n\t\t\t\t\t.classify(hints)\n\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed('alive' as LivenessClassification)));\n\t\t\t\tif (c !== 'alive') {\n\t\t\t\t\tclassification = c;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (classification === 'abandoned') {\n\t\t\t\tyield* fs\n\t\t\t\t\t.remove(dir, { recursive: true, force: true })\n\t\t\t\t\t.pipe(Effect.catch(failPhase('sweep-directories', `remove ${dir} failed`)));\n\t\t\t\treaped.push({ id, classification });\n\t\t\t}\n\t\t}\n\n\t\t// Sweep snapshot-byproduct images via the runtime adapter's\n\t\t// label-scoped image cleanup. Architecture § Decision §8 —\n\t\t// committed snapshot images accumulate; the orchestrator GCs\n\t\t// alongside catalog prune.\n\t\tconst imagesSwept = yield* inputs.runtime\n\t\t\t.removeManagedImages(inputs.imageLabelFilter)\n\t\t\t.pipe(Effect.catch(failPhase('sweep-images', `image sweep failed`)));\n\n\t\treturn {\n\t\t\tinspected: ids.length,\n\t\t\treaped,\n\t\t\timagesSwept,\n\t\t} satisfies PruneResult;\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.prune'));\n"],"mappings":"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"}
1
+ {"version":3,"file":"prune.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/prune.ts"],"sourcesContent":["// Prune — label-scoped orphan sweep.\n//\n// Architecture § 4: the L3 prune orchestrator sweeps committed snapshot\n// byproduct images and snapshot-catalog directories whose meta document\n// is missing (partial artifacts) or unreadable. Stack-wide registry\n// pruning (engine resources / stack roster) is a sibling orchestrator and\n// lives elsewhere; this file is scoped to snapshot-adjacent artifacts.\n//\n// Image sweep scope (load-bearing): committed snapshot images are stamped\n// at `docker commit` time with `{managed, app, stack, role:\n// SNAPSHOT_IMAGE_ROLE}` (see `runtime/docker/container.ts`). Prune scopes\n// its sweep to THAT role so it reaps only snapshot byproducts. Plugin\n// BUILD images share `{managed, app, stack}` but carry the source\n// plugin's real role (or none) — never `SNAPSHOT_IMAGE_ROLE` — so the\n// sweep can NEVER untag a live stack's build images and force a silent\n// rebuild. Prune holds only `snapshot.reservation` (not stack liveness)\n// and is CLI-exposed, so this scoping is what keeps it safe against a\n// running stack.\n\nimport { Effect, FileSystem, Schema } from 'effect';\n\nimport type { ContainerRuntime } from '../../contracts/container-runtime.ts';\nimport { SNAPSHOT_IMAGE_ROLE } from '../../runtime/docker/container.ts';\nimport { decodeJsonText } from '../../substrate/runtime/runtime-decode.ts';\nimport { SnapshotMetadataSchema, type SnapshotMetadata } from './descriptor.ts';\nimport { makePhaseFailer } from './phase-error.ts';\nimport { SNAPSHOTS_DIR_NAME } from './wipe.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\nexport class PrunePhaseError extends Schema.TaggedErrorClass<PrunePhaseError>()(\n\t'SnapshotPrunePhaseError',\n\t{\n\t\tphase: Schema.Literals([\n\t\t\t'enumerate-catalog',\n\t\t\t'read-meta',\n\t\t\t'sweep-images',\n\t\t\t'sweep-directories',\n\t\t]),\n\t\tdetail: Schema.String,\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\nconst failPhase = makePhaseFailer(PrunePhaseError);\n\n// -----------------------------------------------------------------------------\n// Inputs\n// -----------------------------------------------------------------------------\n\nexport interface PruneInputs {\n\t/** Stack root containing the `snapshots/` catalog. */\n\treadonly stackRoot: string;\n\t/** App/stack scope for the committed-snapshot-image sweep. Prune\n\t * narrows this to `role: SNAPSHOT_IMAGE_ROLE` before removing images,\n\t * so only this stack's snapshot byproducts are swept — never its\n\t * build images, never a sibling stack. */\n\treadonly imageLabelFilter: { readonly app: string; readonly stack: string };\n\treadonly runtime: ContainerRuntime;\n}\n\nexport interface PruneResult {\n\treadonly inspected: number;\n\treadonly reaped: ReadonlyArray<{\n\t\treadonly id: string;\n\t\t/** Reason the artifact was reaped. Currently only `'abandoned'`\n\t\t * (partial artifact with no readable meta document). */\n\t\treadonly classification: 'abandoned';\n\t}>;\n\t/** Count of committed snapshot byproduct images removed (those stamped\n\t * with `role: SNAPSHOT_IMAGE_ROLE` in this app/stack). Build images\n\t * are never included. */\n\treadonly imagesSwept: number;\n}\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\nconst readMetaOpt = (\n\tdir: string,\n): Effect.Effect<SnapshotMetadata | null, PrunePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst path = `${dir}/meta.json`;\n\t\tconst exists = yield* fs.exists(path).pipe(Effect.catch(() => Effect.succeed(false)));\n\t\tif (!exists) return null;\n\t\tconst text = yield* fs\n\t\t\t.readFileString(path)\n\t\t\t.pipe(Effect.catch(failPhase('read-meta', `read ${path} failed`)));\n\t\treturn yield* decodeJsonText(SnapshotMetadataSchema, text, {\n\t\t\tsource: path,\n\t\t\tmkError: () => null,\n\t\t}).pipe(Effect.catch(() => Effect.succeed(null)));\n\t});\n\n// -----------------------------------------------------------------------------\n// Top-level prune\n// -----------------------------------------------------------------------------\n\n/**\n * Walk the snapshot catalog and reap partial artifacts (entries whose\n * `meta.json` is missing or unparseable). Concurrent sweeps over the\n * same catalog are not supported (caller holds `snapshot.reservation`\n * or `stack.lock`).\n *\n * Also removes committed snapshot byproduct images via the runtime\n * adapter's label-filtered image cleanup, scoped to `role:\n * SNAPSHOT_IMAGE_ROLE` so build images are never touched.\n */\nexport const runPrune = (\n\tinputs: PruneInputs,\n): Effect.Effect<PruneResult, PrunePhaseError, 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': 'prune',\n\t\t});\n\t\tconst catalogDir = `${inputs.stackRoot}/${SNAPSHOTS_DIR_NAME}`;\n\t\tconst catalogExists = yield* fs\n\t\t\t.exists(catalogDir)\n\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\tif (!catalogExists) {\n\t\t\treturn { inspected: 0, reaped: [], imagesSwept: 0 } satisfies PruneResult;\n\t\t}\n\t\tconst ids = yield* fs\n\t\t\t.readDirectory(catalogDir)\n\t\t\t.pipe(Effect.catch(failPhase('enumerate-catalog', `readdir ${catalogDir} failed`)));\n\n\t\tconst reaped: Array<{ id: string; classification: 'abandoned' }> = [];\n\t\tfor (const id of ids) {\n\t\t\tconst dir = `${catalogDir}/${id}`;\n\t\t\tconst meta = yield* readMetaOpt(dir);\n\t\t\t// Partial artifacts (no meta) — reap to free the disk slot.\n\t\t\t// The catalog list already hides them; reaping is just GC.\n\t\t\tif (meta === null) {\n\t\t\t\tyield* fs\n\t\t\t\t\t.remove(dir, { recursive: true, force: true })\n\t\t\t\t\t.pipe(Effect.catch(failPhase('sweep-directories', `remove ${dir} failed`)));\n\t\t\t\treaped.push({ id, classification: 'abandoned' });\n\t\t\t}\n\t\t}\n\n\t\t// Sweep committed snapshot byproduct images via the runtime\n\t\t// adapter's label-scoped image cleanup. Architecture § Decision §8 —\n\t\t// committed snapshot images accumulate (a hard-killed capture can\n\t\t// leak its temp image before cleanup); the orchestrator GCs them\n\t\t// alongside catalog prune. The `role: SNAPSHOT_IMAGE_ROLE` narrowing\n\t\t// is what distinguishes these byproducts from the live stack's\n\t\t// build images (which share `{app, stack}` but carry a different\n\t\t// role / no role) — without it, prune would untag build images and\n\t\t// force silent rebuilds.\n\t\tconst imagesSwept = yield* inputs.runtime\n\t\t\t.removeManagedImages({ ...inputs.imageLabelFilter, role: SNAPSHOT_IMAGE_ROLE })\n\t\t\t.pipe(Effect.catch(failPhase('sweep-images', `image sweep failed`)));\n\n\t\treturn {\n\t\t\tinspected: ids.length,\n\t\t\treaped,\n\t\t\timagesSwept,\n\t\t} satisfies PruneResult;\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.prune'));\n"],"mappings":";;;;;;;AAgCA,IAAa,kBAAb,cAAqC,OAAO,kBAAmC,CAC9E,2BACA;CACC,OAAO,OAAO,SAAS;EACtB;EACA;EACA;EACA;EACA,CAAC;CACF,QAAQ,OAAO;CACf,OAAO,OAAO,SAAS,OAAO,OAAO;CACrC,CACD,CAAC;AAEF,MAAM,YAAY,gBAAgB,gBAAgB;AAmClD,MAAM,eACL,QAEA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,GAAG,IAAI;AAEpB,KAAI,EAAC,OADiB,GAAG,OAAO,KAAK,CAAC,KAAK,OAAO,YAAY,OAAO,QAAQ,MAAM,CAAC,CAAC,EACxE,QAAO;AAIpB,QAAO,OAAO,eAAe,wBAAwB,OAHjC,GAClB,eAAe,KAAK,CACpB,KAAK,OAAO,MAAM,UAAU,aAAa,QAAQ,KAAK,SAAS,CAAC,CAAC,EACR;EAC1D,QAAQ;EACR,eAAe;EACf,CAAC,CAAC,KAAK,OAAO,YAAY,OAAO,QAAQ,KAAK,CAAC,CAAC;EAChD;;;;;;;;;;;AAgBH,MAAa,YACZ,WAEA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;AAC7B,QAAO,OAAO,oBAAoB,EACjC,2BAA2B,SAC3B,CAAC;CACF,MAAM,aAAa,GAAG,OAAO,UAAU,GAAG;AAI1C,KAAI,EAAC,OAHwB,GAC3B,OAAO,WAAW,CAClB,KAAK,OAAO,YAAY,OAAO,QAAQ,MAAM,CAAC,CAAC,EAEhD,QAAO;EAAE,WAAW;EAAG,QAAQ,EAAE;EAAE,aAAa;EAAG;CAEpD,MAAM,MAAM,OAAO,GACjB,cAAc,WAAW,CACzB,KAAK,OAAO,MAAM,UAAU,qBAAqB,WAAW,WAAW,SAAS,CAAC,CAAC;CAEpF,MAAM,SAA6D,EAAE;AACrE,MAAK,MAAM,MAAM,KAAK;EACrB,MAAM,MAAM,GAAG,WAAW,GAAG;AAI7B,OAAI,OAHgB,YAAY,IAAI,MAGvB,MAAM;AAClB,UAAO,GACL,OAAO,KAAK;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC,CAC7C,KAAK,OAAO,MAAM,UAAU,qBAAqB,UAAU,IAAI,SAAS,CAAC,CAAC;AAC5E,UAAO,KAAK;IAAE;IAAI,gBAAgB;IAAa,CAAC;;;CAalD,MAAM,cAAc,OAAO,OAAO,QAChC,oBAAoB;EAAE,GAAG,OAAO;EAAkB,MAAM;EAAqB,CAAC,CAC9E,KAAK,OAAO,MAAM,UAAU,gBAAgB,qBAAqB,CAAC,CAAC;AAErE,QAAO;EACN,WAAW,IAAI;EACf;EACA;EACA;EACA,CAAC,KAAK,OAAO,SAAS,8BAA8B,CAAC"}
@@ -0,0 +1 @@
1
+ import { Effect, FileSystem, Schema } from "effect";
@@ -0,0 +1,223 @@
1
+ import { decodeUnknown, parseJsonText } from "../../substrate/runtime/runtime-decode.mjs";
2
+ import { RestorePendingDocumentSchema, pendingMarkerPath, removePendingMarker, rewritePendingMarkerContainers, writePendingMarker } from "./pending-marker.mjs";
3
+ import { Effect, FileSystem, Schema } from "effect";
4
+ //#region src/orchestrators/snapshot/recover-pending.ts
5
+ /** Tagged failure surfaced by the recovery scanner. The `kind`
6
+ * discriminator lets the supervisor decide how to react: a corrupt
7
+ * marker is investigable but not fatal (the operator can repair the
8
+ * Docker side manually); a Docker daemon outage is transient. */
9
+ var RestorePendingRecoveryError = class extends Schema.TaggedErrorClass()("SnapshotRestorePendingRecoveryError", {
10
+ kind: Schema.Literals([
11
+ "marker-io",
12
+ "marker-decode",
13
+ "retag",
14
+ "rewrite"
15
+ ]),
16
+ detail: Schema.String,
17
+ path: Schema.optional(Schema.String),
18
+ entry: Schema.optional(Schema.Struct({
19
+ plugin: Schema.String,
20
+ role: Schema.String,
21
+ targetImageName: Schema.String,
22
+ stagedImageTag: Schema.String,
23
+ digest: Schema.String
24
+ })),
25
+ cause: Schema.optional(Schema.Defect)
26
+ }) {};
27
+ const readMarker = (stackRoot) => Effect.gen(function* () {
28
+ const fs = yield* FileSystem.FileSystem;
29
+ const path = pendingMarkerPath(stackRoot);
30
+ if (!(yield* fs.exists(path).pipe(Effect.catch(() => Effect.succeed(false))))) return { kind: "absent" };
31
+ const raw = yield* parseJsonText(yield* fs.readFileString(path).pipe(Effect.catch((cause) => Effect.fail(new RestorePendingRecoveryError({
32
+ kind: "marker-io",
33
+ path,
34
+ detail: `read pending marker failed`,
35
+ cause
36
+ })))), {
37
+ source: path,
38
+ mkError: (issue) => new RestorePendingRecoveryError({
39
+ kind: "marker-decode",
40
+ path,
41
+ detail: `pending marker is not valid JSON`,
42
+ cause: issue.cause
43
+ })
44
+ });
45
+ const peekedVersion = typeof raw === "object" && raw !== null && "version" in raw ? raw.version : void 0;
46
+ if (!SUPPORTED_MARKER_VERSIONS.has(peekedVersion)) return {
47
+ kind: "unsupported-version",
48
+ version: peekedVersion,
49
+ path
50
+ };
51
+ const decoded = yield* decodeUnknown(RestorePendingDocumentSchema, raw, {
52
+ source: path,
53
+ mkError: (issue) => new RestorePendingRecoveryError({
54
+ kind: "marker-decode",
55
+ path,
56
+ detail: `pending marker failed schema decode`,
57
+ cause: issue.cause
58
+ })
59
+ });
60
+ switch (decoded.version) {
61
+ case 2: return {
62
+ kind: "present",
63
+ doc: decoded
64
+ };
65
+ }
66
+ });
67
+ /** Set of versions the Union decoder above accepts. Keep in sync with
68
+ * the arms of `RestorePendingDocumentSchema` in `pending-marker.ts` —
69
+ * the peek uses this to distinguish "stale/future marker version" from
70
+ * "blob shape mismatch within a known version". */
71
+ const SUPPORTED_MARKER_VERSIONS = new Set([2]);
72
+ const rewriteMarker = (stackRoot, doc, stillPending) => writePendingMarker(stackRoot, rewritePendingMarkerContainers(doc, stillPending)).pipe(Effect.catchTag("SnapshotRestorePendingMarkerIoError", (err) => Effect.fail(new RestorePendingRecoveryError({
73
+ kind: "rewrite",
74
+ path: err.path,
75
+ detail: err.detail,
76
+ cause: err.cause
77
+ }))));
78
+ /** Probe whether the snapshot's EXPECTED image (addressed by its
79
+ * content digest) is resolvable on the host, and if so (re)point
80
+ * `targetImageName` at it.
81
+ *
82
+ * We deliberately do NOT self-tag the target NAME here. A self-tag of
83
+ * `targetImageName` onto itself succeeds for ANY image that happens to
84
+ * sit at that name — including a wholly unrelated managed/base/pulled
85
+ * image that merely collides with the recorded name. In the prune
86
+ * scenario (source pruned out-of-band) that name-existence check would
87
+ * false-positive: it drops the marker as "recovered" while the
88
+ * snapshot's committed image was never promoted, booting the container
89
+ * from the WRONG image with the marker silently cleared.
90
+ *
91
+ * Instead we address the marker's `digest` (the loaded image's
92
+ * content-addressed `{{.Id}}`, written by the restore promote loop).
93
+ * The contract resolves `tag ?? digest`, so `tagImage({ digest }, …)`
94
+ * tags the EXPECTED image onto `targetImageName`:
95
+ *
96
+ * - expected image still resident by digest (a prior recovery
97
+ * promoted it; the digest survives `docker system prune` while any
98
+ * pinned layer keeps it accessible) → tag succeeds, the name is
99
+ * (idempotently) repaired to point at exactly the snapshot image →
100
+ * genuinely recovered, drop the marker entry;
101
+ * - expected image genuinely gone (digest absent) → tag fails →
102
+ * keep the entry pending.
103
+ *
104
+ * This is strictly stronger than the old name-existence probe: a
105
+ * colliding unrelated image at `targetImageName` can no longer trigger
106
+ * a false drop, because the probe never addresses the name as a source
107
+ * — only the expected content digest can satisfy it. It also closes
108
+ * the silent-wrong-image hole by leaving `targetImageName` pointing at
109
+ * the right image when it does succeed.
110
+ *
111
+ * `true` → expected image promoted/confirmed at the target name;
112
+ * `false` → expected image absent OR the probe hit a transient daemon
113
+ * error. The caller treats `false` as still-pending, so a transient
114
+ * probe failure errs on the safe side (keep the marker, retry next
115
+ * supervise) rather than dropping it. */
116
+ const targetImageResolves = (entry, runtime) => runtime.tagImage({ digest: entry.digest }, entry.targetImageName).pipe(Effect.as(true), Effect.catch(() => Effect.succeed(false)));
117
+ /** Attempt to re-tag the target image from each known identity in
118
+ * decreasing-likelihood-of-success order:
119
+ *
120
+ * 1. `stagedImageTag` — what the original promote loop was retagging
121
+ * from. Survives the simplest crash (no Docker mutation between
122
+ * crash and restart).
123
+ * 2. `digest` — the loaded image's content-addressed identity.
124
+ * Survives `docker system prune` because the digest IS the image:
125
+ * any pinned layer keeps the image accessible by digest even
126
+ * after every tag pointing at it has been removed.
127
+ *
128
+ * When BOTH source attempts fail we must NOT blindly report the entry
129
+ * as still-pending: a fully missing source (both staged tag and digest
130
+ * gone as *named/tagged* refs) can mean a *previous* recovery already
131
+ * promoted this entry to `targetImageName` and a stale marker simply
132
+ * hasn't been rewritten. Collapsing that case into "still pending"
133
+ * retags forever and never makes progress. So after both attempts fail
134
+ * we PROBE whether the EXPECTED image is still resolvable BY ITS
135
+ * CONTENT DIGEST (see `targetImageResolves`) — not by the target name:
136
+ *
137
+ * - expected image resolves → (re)point `targetImageName` at it and
138
+ * treat the entry as recovered (drop the marker entry);
139
+ * - expected image absent → the snapshot image genuinely vanished
140
+ * without a completed promote (e.g. a transient daemon error on
141
+ * both tag attempts, or operator prune of source AND target), so
142
+ * keep the entry pending for the next supervise.
143
+ *
144
+ * Probing the digest rather than the name is what makes this safe: it
145
+ * stops conflating "absent source because already recovered" with
146
+ * "transient daemon error", AND prevents an unrelated image that
147
+ * happens to occupy `targetImageName` from masquerading as a completed
148
+ * recovery (the wrong-image hole a bare name-existence check left open). */
149
+ const tryRecoverEntry = (entry, runtime) => Effect.gen(function* () {
150
+ if (yield* runtime.tagImage({
151
+ digest: entry.digest,
152
+ tag: entry.stagedImageTag
153
+ }, entry.targetImageName, { removeSourceAfterTag: true }).pipe(Effect.as(true), Effect.catch(() => Effect.succeed(false)))) return true;
154
+ if (yield* runtime.tagImage({ digest: entry.digest }, entry.targetImageName, { removeSourceAfterTag: true }).pipe(Effect.as(true), Effect.catch(() => Effect.succeed(false)))) return true;
155
+ return yield* targetImageResolves(entry, runtime);
156
+ });
157
+ /** Read the restore-pending marker for this stack root and retry any
158
+ * outstanding image promotions. Idempotent. The supervisor calls this
159
+ * BEFORE any plugin acquire, so a half-promoted snapshot restore is
160
+ * reconciled before L2 lookups observe the runtime tree.
161
+ *
162
+ * Returns a typed summary instead of failing on partial recovery —
163
+ * the supervisor surfaces "marker remains, N entries outstanding"
164
+ * via the cascade-formatter so the operator can decide whether to
165
+ * re-restore or repair Docker out of band. Only marker IO/decode
166
+ * failures escape as `RestorePendingRecoveryError`. */
167
+ const recoverPendingRestore = (stackRoot, runtime) => Effect.gen(function* () {
168
+ const result = yield* readMarker(stackRoot);
169
+ if (result.kind === "absent") return {
170
+ noMarker: true,
171
+ snapshotId: null,
172
+ inspected: 0,
173
+ recovered: 0,
174
+ stillPending: [],
175
+ markerCleared: false
176
+ };
177
+ if (result.kind === "unsupported-version") {
178
+ yield* Effect.logWarning(`snapshot restore-pending marker at ${result.path} has unsupported version ${String(result.version)} (expected 2); leaving on disk for operator review`);
179
+ return {
180
+ noMarker: true,
181
+ snapshotId: null,
182
+ inspected: 0,
183
+ recovered: 0,
184
+ stillPending: [],
185
+ markerCleared: false
186
+ };
187
+ }
188
+ const doc = result.doc;
189
+ yield* Effect.annotateCurrentSpan({
190
+ "devstack.snapshot.recovery.snapshotId": doc.snapshotId,
191
+ "devstack.snapshot.recovery.pending": doc.containers.length
192
+ });
193
+ const stillPending = [];
194
+ let recovered = 0;
195
+ for (let i = 0; i < doc.containers.length; i += 1) {
196
+ const entry = doc.containers[i];
197
+ if (yield* tryRecoverEntry(entry, runtime)) recovered += 1;
198
+ else stillPending.push(entry);
199
+ yield* rewriteMarker(stackRoot, doc, [...stillPending, ...doc.containers.slice(i + 1)]);
200
+ }
201
+ let markerCleared = false;
202
+ if (stillPending.length === 0) {
203
+ yield* removePendingMarker(stackRoot).pipe(Effect.catchTag("SnapshotRestorePendingMarkerIoError", (err) => Effect.fail(new RestorePendingRecoveryError({
204
+ kind: "marker-io",
205
+ path: err.path,
206
+ detail: err.detail,
207
+ cause: err.cause
208
+ }))));
209
+ markerCleared = true;
210
+ } else yield* rewriteMarker(stackRoot, doc, stillPending);
211
+ return {
212
+ noMarker: false,
213
+ snapshotId: doc.snapshotId,
214
+ inspected: doc.containers.length,
215
+ recovered,
216
+ stillPending,
217
+ markerCleared
218
+ };
219
+ }).pipe(Effect.withSpan("orchestrator.snapshot.recover-pending"));
220
+ //#endregion
221
+ export { RestorePendingRecoveryError, recoverPendingRestore };
222
+
223
+ //# sourceMappingURL=recover-pending.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recover-pending.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/recover-pending.ts"],"sourcesContent":["// Restore-pending recovery scanner.\n//\n// Recovery contract (mirrors the writer in `pending-marker.ts`):\n//\n// 1. The supervise startup hook calls `recoverPendingRestore` before\n// any plugin acquire fires, so a half-promoted set of images is\n// reconciled before any L2 lookup observes the runtime root.\n// 2. The scanner reads `${stackRoot}/snapshot.restore-pending.json`.\n// Absent marker → no recovery needed, return immediately.\n// 3. For each outstanding entry it asks the runtime to tag the\n// source onto the target name (`removeSourceAfterTag: true`).\n// The source is resolved by trying, in order: (a) the staging\n// tag, (b) the digest. The digest fallback covers the case\n// where Docker has dropped the staging tag too (e.g. operator\n// `docker system prune` between crash and restart) — the digest\n// is the image's content-addressed identity and survives prune.\n// Successful tag → entry recovered; rewrite the marker with the\n// entry removed. When BOTH source attempts fail the scanner\n// probes whether the EXPECTED image (addressed by its content\n// digest) is still resolvable — a prior recovery may have promoted\n// it and left the digest resident; if so it (re)points\n// `targetImageName` at that image and treats the entry as\n// recovered, otherwise it stays pending. The probe addresses the\n// digest, NOT the target name, so a colliding unrelated image at\n// `targetImageName` cannot false-positive a drop.\n// 4. When the outstanding list reaches zero, remove the marker.\n// 5. On a genuine per-entry failure (both sources gone AND the\n// target not yet visible — e.g. a transient daemon error): keep\n// the entry in the marker so the next supervise retries. The\n// scanner returns a typed summary so the supervisor can surface\n// the partial-recovery state without crashing the boot path.\n//\n// Idempotency: a marker with `containers: []` is treated as\n// already-recovered and removed in one call. Re-running the scanner\n// against a marker for an image that has already been tagged by an\n// out-of-band operator is also safe — `tagImage` is idempotent on the\n// happy path and the failure on a missing staged source is treated\n// as \"entry already recovered\" if the target image is now visible.\n//\n// The scanner sits next to `restore.ts` and shares the marker\n// schema/IO with it via `pending-marker.ts`. The supervisor calls in\n// via `SnapshotOrchestratorService.recoverPendingRestore`, so the L1\n// docker layer requirement is satisfied through the existing service\n// composition — no new substrate plumbing.\n\nimport { Effect, FileSystem, Schema } from 'effect';\n\nimport type { ContainerRuntime } from '../../contracts/container-runtime.ts';\nimport { decodeUnknown, parseJsonText } from '../../substrate/runtime/runtime-decode.ts';\nimport {\n\tpendingMarkerPath,\n\tremovePendingMarker,\n\tRestorePendingMarkerIoError,\n\tRestorePendingDocumentSchema,\n\trewritePendingMarkerContainers,\n\tSNAPSHOT_RESTORE_PENDING_VERSION,\n\twritePendingMarker,\n\ttype RestorePendingContainer,\n\ttype RestorePendingDocument,\n} from './pending-marker.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\n/** Tagged failure surfaced by the recovery scanner. The `kind`\n * discriminator lets the supervisor decide how to react: a corrupt\n * marker is investigable but not fatal (the operator can repair the\n * Docker side manually); a Docker daemon outage is transient. */\nexport class RestorePendingRecoveryError extends Schema.TaggedErrorClass<RestorePendingRecoveryError>()(\n\t'SnapshotRestorePendingRecoveryError',\n\t{\n\t\tkind: Schema.Literals(['marker-io', 'marker-decode', 'retag', 'rewrite']),\n\t\tdetail: Schema.String,\n\t\tpath: Schema.optional(Schema.String),\n\t\tentry: Schema.optional(\n\t\t\tSchema.Struct({\n\t\t\t\tplugin: Schema.String,\n\t\t\t\trole: Schema.String,\n\t\t\t\ttargetImageName: Schema.String,\n\t\t\t\tstagedImageTag: Schema.String,\n\t\t\t\tdigest: Schema.String,\n\t\t\t}),\n\t\t),\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\n// -----------------------------------------------------------------------------\n// Summary surface\n// -----------------------------------------------------------------------------\n\nexport interface RestorePendingRecoverySummary {\n\t/** True when no marker was present at the runtime root. */\n\treadonly noMarker: boolean;\n\t/** Snapshot id whose restore was interrupted; null when noMarker. */\n\treadonly snapshotId: string | null;\n\t/** Number of pending entries the scanner observed. */\n\treadonly inspected: number;\n\t/** Number of entries successfully retagged. */\n\treadonly recovered: number;\n\t/** Entries still pending after the scanner finished. Empty in the\n\t * happy path; populated when one or more retags failed. */\n\treadonly stillPending: ReadonlyArray<RestorePendingContainer>;\n\t/** True when the marker was removed (every entry recovered or\n\t * marker started empty). */\n\treadonly markerCleared: boolean;\n}\n\n// -----------------------------------------------------------------------------\n// Internal helpers\n// -----------------------------------------------------------------------------\n\n/** Result of reading a marker on disk. `unsupported-version` covers\n * stale pre-v2 markers whose schema can't safely be recovered (the\n * v1 shape had no `digest` per entry, and digest is the only identity\n * that survives `docker system prune`); the Union decode fails them\n * cleanly and the scanner surfaces them as \"leave on disk for\n * operator review\" rather than as a fatal decode error. */\ntype ReadMarkerResult =\n\t| { readonly kind: 'absent' }\n\t| { readonly kind: 'unsupported-version'; readonly version: unknown; readonly path: string }\n\t| { readonly kind: 'present'; readonly doc: RestorePendingDocument };\n\nconst readMarker = (\n\tstackRoot: string,\n): Effect.Effect<ReadMarkerResult, RestorePendingRecoveryError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst path = pendingMarkerPath(stackRoot);\n\t\tconst exists = yield* fs.exists(path).pipe(Effect.catch(() => Effect.succeed(false)));\n\t\tif (!exists) return { kind: 'absent' } as const;\n\t\tconst text = yield* fs.readFileString(path).pipe(\n\t\t\tEffect.catch(\n\t\t\t\t(cause): Effect.Effect<never, RestorePendingRecoveryError> =>\n\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\tnew RestorePendingRecoveryError({\n\t\t\t\t\t\t\tkind: 'marker-io',\n\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\tdetail: `read pending marker failed`,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t),\n\t\t);\n\t\tconst raw = yield* parseJsonText(text, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) =>\n\t\t\t\tnew RestorePendingRecoveryError({\n\t\t\t\t\tkind: 'marker-decode',\n\t\t\t\t\tpath,\n\t\t\t\t\tdetail: `pending marker is not valid JSON`,\n\t\t\t\t\tcause: issue.cause,\n\t\t\t\t}),\n\t\t});\n\t\t// The schema is a `Schema.Union` of every supported marker\n\t\t// version (currently just v2). We peek the discriminant before\n\t\t// the full Union decode so a stale pre-v2 (or future-v3)\n\t\t// marker is reported as `unsupported-version` rather than as a\n\t\t// schema-decode failure — the supervisor surfaces them\n\t\t// differently (operator-review vs corrupt-JSON). A blob whose\n\t\t// `version` matches a known arm but whose inner shape is\n\t\t// malformed still surfaces as `marker-decode`. Future v3\n\t\t// migration appends an arm to the Union in `pending-marker.ts`,\n\t\t// adds the literal to `SUPPORTED_MARKER_VERSIONS`, and adds a\n\t\t// matching `case` in the switch below.\n\t\tconst peekedVersion =\n\t\t\ttypeof raw === 'object' && raw !== null && 'version' in raw\n\t\t\t\t? (raw as { readonly version: unknown }).version\n\t\t\t\t: undefined;\n\t\tif (!SUPPORTED_MARKER_VERSIONS.has(peekedVersion as number)) {\n\t\t\treturn {\n\t\t\t\tkind: 'unsupported-version',\n\t\t\t\tversion: peekedVersion,\n\t\t\t\tpath,\n\t\t\t} as const;\n\t\t}\n\t\tconst decoded: RestorePendingDocument = yield* decodeUnknown(\n\t\t\tRestorePendingDocumentSchema,\n\t\t\traw,\n\t\t\t{\n\t\t\t\tsource: path,\n\t\t\t\tmkError: (issue) =>\n\t\t\t\t\tnew RestorePendingRecoveryError({\n\t\t\t\t\t\tkind: 'marker-decode',\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tdetail: `pending marker failed schema decode`,\n\t\t\t\t\t\tcause: issue.cause,\n\t\t\t\t\t}),\n\t\t\t},\n\t\t);\n\t\t// Today the Union has one arm (v2). A future v3 migration adds a\n\t\t// `case 3:` arm here AND extends `SUPPORTED_MARKER_VERSIONS`; the\n\t\t// unsupported-version branch becomes reachable once the Union\n\t\t// grows. With a single-arm Union, TypeScript narrows `decoded`\n\t\t// to `never` after `case 2`, so this single return is exhaustive.\n\t\tswitch (decoded.version) {\n\t\t\tcase 2:\n\t\t\t\treturn { kind: 'present', doc: decoded } as const;\n\t\t}\n\t});\n\n/** Set of versions the Union decoder above accepts. Keep in sync with\n * the arms of `RestorePendingDocumentSchema` in `pending-marker.ts` —\n * the peek uses this to distinguish \"stale/future marker version\" from\n * \"blob shape mismatch within a known version\". */\nconst SUPPORTED_MARKER_VERSIONS = new Set<number>([SNAPSHOT_RESTORE_PENDING_VERSION]);\n\nconst rewriteMarker = (\n\tstackRoot: string,\n\tdoc: RestorePendingDocument,\n\tstillPending: ReadonlyArray<RestorePendingContainer>,\n): Effect.Effect<void, RestorePendingRecoveryError, FileSystem.FileSystem> =>\n\twritePendingMarker(stackRoot, rewritePendingMarkerContainers(doc, stillPending)).pipe(\n\t\tEffect.catchTag('SnapshotRestorePendingMarkerIoError', (err: RestorePendingMarkerIoError) =>\n\t\t\tEffect.fail(\n\t\t\t\tnew RestorePendingRecoveryError({\n\t\t\t\t\tkind: 'rewrite',\n\t\t\t\t\tpath: err.path,\n\t\t\t\t\tdetail: err.detail,\n\t\t\t\t\tcause: err.cause,\n\t\t\t\t}),\n\t\t\t),\n\t\t),\n\t);\n\n/** Probe whether the snapshot's EXPECTED image (addressed by its\n * content digest) is resolvable on the host, and if so (re)point\n * `targetImageName` at it.\n *\n * We deliberately do NOT self-tag the target NAME here. A self-tag of\n * `targetImageName` onto itself succeeds for ANY image that happens to\n * sit at that name — including a wholly unrelated managed/base/pulled\n * image that merely collides with the recorded name. In the prune\n * scenario (source pruned out-of-band) that name-existence check would\n * false-positive: it drops the marker as \"recovered\" while the\n * snapshot's committed image was never promoted, booting the container\n * from the WRONG image with the marker silently cleared.\n *\n * Instead we address the marker's `digest` (the loaded image's\n * content-addressed `{{.Id}}`, written by the restore promote loop).\n * The contract resolves `tag ?? digest`, so `tagImage({ digest }, …)`\n * tags the EXPECTED image onto `targetImageName`:\n *\n * - expected image still resident by digest (a prior recovery\n * promoted it; the digest survives `docker system prune` while any\n * pinned layer keeps it accessible) → tag succeeds, the name is\n * (idempotently) repaired to point at exactly the snapshot image →\n * genuinely recovered, drop the marker entry;\n * - expected image genuinely gone (digest absent) → tag fails →\n * keep the entry pending.\n *\n * This is strictly stronger than the old name-existence probe: a\n * colliding unrelated image at `targetImageName` can no longer trigger\n * a false drop, because the probe never addresses the name as a source\n * — only the expected content digest can satisfy it. It also closes\n * the silent-wrong-image hole by leaving `targetImageName` pointing at\n * the right image when it does succeed.\n *\n * `true` → expected image promoted/confirmed at the target name;\n * `false` → expected image absent OR the probe hit a transient daemon\n * error. The caller treats `false` as still-pending, so a transient\n * probe failure errs on the safe side (keep the marker, retry next\n * supervise) rather than dropping it. */\nconst targetImageResolves = (\n\tentry: RestorePendingContainer,\n\truntime: ContainerRuntime,\n): Effect.Effect<boolean> =>\n\truntime\n\t\t// Address the EXPECTED image by its content digest (NOT the target\n\t\t// name). `{ digest }` with `tag` omitted forces the contract's\n\t\t// `tag ?? digest` resolution onto the digest, so only the snapshot's\n\t\t// committed image can satisfy this — a name collision cannot.\n\t\t.tagImage({ digest: entry.digest }, entry.targetImageName)\n\t\t.pipe(\n\t\t\tEffect.as(true as const),\n\t\t\tEffect.catch(() => Effect.succeed(false as const)),\n\t\t);\n\n/** Attempt to re-tag the target image from each known identity in\n * decreasing-likelihood-of-success order:\n *\n * 1. `stagedImageTag` — what the original promote loop was retagging\n * from. Survives the simplest crash (no Docker mutation between\n * crash and restart).\n * 2. `digest` — the loaded image's content-addressed identity.\n * Survives `docker system prune` because the digest IS the image:\n * any pinned layer keeps the image accessible by digest even\n * after every tag pointing at it has been removed.\n *\n * When BOTH source attempts fail we must NOT blindly report the entry\n * as still-pending: a fully missing source (both staged tag and digest\n * gone as *named/tagged* refs) can mean a *previous* recovery already\n * promoted this entry to `targetImageName` and a stale marker simply\n * hasn't been rewritten. Collapsing that case into \"still pending\"\n * retags forever and never makes progress. So after both attempts fail\n * we PROBE whether the EXPECTED image is still resolvable BY ITS\n * CONTENT DIGEST (see `targetImageResolves`) — not by the target name:\n *\n * - expected image resolves → (re)point `targetImageName` at it and\n * treat the entry as recovered (drop the marker entry);\n * - expected image absent → the snapshot image genuinely vanished\n * without a completed promote (e.g. a transient daemon error on\n * both tag attempts, or operator prune of source AND target), so\n * keep the entry pending for the next supervise.\n *\n * Probing the digest rather than the name is what makes this safe: it\n * stops conflating \"absent source because already recovered\" with\n * \"transient daemon error\", AND prevents an unrelated image that\n * happens to occupy `targetImageName` from masquerading as a completed\n * recovery (the wrong-image hole a bare name-existence check left open). */\nconst tryRecoverEntry = (\n\tentry: RestorePendingContainer,\n\truntime: ContainerRuntime,\n): Effect.Effect<boolean> =>\n\tEffect.gen(function* () {\n\t\t// The contract's docker impl resolves `tag ?? digest`, so when\n\t\t// we want to address by digest we MUST pass `tag: undefined`\n\t\t// (omitting the field) — otherwise the staged tag would still\n\t\t// win the resolution race and we'd be re-running attempt #1.\n\t\tconst stagedFirst = yield* runtime\n\t\t\t.tagImage({ digest: entry.digest, tag: entry.stagedImageTag }, entry.targetImageName, {\n\t\t\t\tremoveSourceAfterTag: true,\n\t\t\t})\n\t\t\t.pipe(\n\t\t\t\tEffect.as(true as const),\n\t\t\t\tEffect.catch(() => Effect.succeed(false as const)),\n\t\t\t);\n\t\tif (stagedFirst) return true;\n\t\tconst digestFallback = yield* runtime\n\t\t\t.tagImage({ digest: entry.digest }, entry.targetImageName, { removeSourceAfterTag: true })\n\t\t\t.pipe(\n\t\t\t\tEffect.as(true as const),\n\t\t\t\tEffect.catch(() => Effect.succeed(false as const)),\n\t\t\t);\n\t\tif (digestFallback) return true;\n\t\t// Both source identities failed. Distinguish \"already promoted by\n\t\t// a prior recovery\" (target now visible → recovered) from a\n\t\t// genuine transient/missing-both failure (target absent → keep\n\t\t// pending). See the doc comment above.\n\t\treturn yield* targetImageResolves(entry, runtime);\n\t});\n\n// -----------------------------------------------------------------------------\n// Public entry point\n// -----------------------------------------------------------------------------\n\n/** Read the restore-pending marker for this stack root and retry any\n * outstanding image promotions. Idempotent. The supervisor calls this\n * BEFORE any plugin acquire, so a half-promoted snapshot restore is\n * reconciled before L2 lookups observe the runtime tree.\n *\n * Returns a typed summary instead of failing on partial recovery —\n * the supervisor surfaces \"marker remains, N entries outstanding\"\n * via the cascade-formatter so the operator can decide whether to\n * re-restore or repair Docker out of band. Only marker IO/decode\n * failures escape as `RestorePendingRecoveryError`. */\nexport const recoverPendingRestore = (\n\tstackRoot: string,\n\truntime: ContainerRuntime,\n): Effect.Effect<\n\tRestorePendingRecoverySummary,\n\tRestorePendingRecoveryError,\n\tFileSystem.FileSystem\n> =>\n\tEffect.gen(function* () {\n\t\tconst result = yield* readMarker(stackRoot);\n\t\tif (result.kind === 'absent') {\n\t\t\treturn {\n\t\t\t\tnoMarker: true,\n\t\t\t\tsnapshotId: null,\n\t\t\t\tinspected: 0,\n\t\t\t\trecovered: 0,\n\t\t\t\tstillPending: [],\n\t\t\t\tmarkerCleared: false,\n\t\t\t} as const;\n\t\t}\n\t\tif (result.kind === 'unsupported-version') {\n\t\t\t// Pre-upgrade marker — the schema bumped to v2 to add the\n\t\t\t// `digest` field per entry, and a v1 marker without\n\t\t\t// digests can't be safely recovered (digest is the only\n\t\t\t// identity that survives `docker system prune`). Log a\n\t\t\t// warning and leave the marker alone so the operator can\n\t\t\t// inspect / manually clean up. We treat this as\n\t\t\t// \"no recovery needed\" from the supervisor's perspective.\n\t\t\tyield* Effect.logWarning(\n\t\t\t\t`snapshot restore-pending marker at ${result.path} has unsupported version ${String(\n\t\t\t\t\tresult.version,\n\t\t\t\t)} (expected ${SNAPSHOT_RESTORE_PENDING_VERSION}); leaving on disk for operator review`,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tnoMarker: true,\n\t\t\t\tsnapshotId: null,\n\t\t\t\tinspected: 0,\n\t\t\t\trecovered: 0,\n\t\t\t\tstillPending: [],\n\t\t\t\tmarkerCleared: false,\n\t\t\t} as const;\n\t\t}\n\t\tconst doc = result.doc;\n\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t'devstack.snapshot.recovery.snapshotId': doc.snapshotId,\n\t\t\t'devstack.snapshot.recovery.pending': doc.containers.length,\n\t\t});\n\n\t\tconst stillPending: RestorePendingContainer[] = [];\n\t\tlet recovered = 0;\n\t\tfor (let i = 0; i < doc.containers.length; i += 1) {\n\t\t\tconst entry = doc.containers[i]!;\n\t\t\tconst ok = yield* tryRecoverEntry(entry, runtime);\n\t\t\tif (ok) {\n\t\t\t\trecovered += 1;\n\t\t\t} else {\n\t\t\t\tstillPending.push(entry);\n\t\t\t}\n\t\t\t// Rewrite-after-each so a crash mid-recovery doesn't lose\n\t\t\t// the work-so-far. The new marker carries the still-pending\n\t\t\t// entries plus everything we haven't tried yet. Cheap (one\n\t\t\t// write per image, rare path). Unconditional — the rewrite\n\t\t\t// is idempotent and the final-rewrite below (or\n\t\t\t// `removePendingMarker` when nothing remains) covers steady\n\t\t\t// state.\n\t\t\tconst remaining: RestorePendingContainer[] = [\n\t\t\t\t...stillPending,\n\t\t\t\t...doc.containers.slice(i + 1),\n\t\t\t];\n\t\t\tyield* rewriteMarker(stackRoot, doc, remaining);\n\t\t}\n\n\t\tlet markerCleared = false;\n\t\tif (stillPending.length === 0) {\n\t\t\tyield* removePendingMarker(stackRoot).pipe(\n\t\t\t\tEffect.catchTag('SnapshotRestorePendingMarkerIoError', (err: RestorePendingMarkerIoError) =>\n\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\tnew RestorePendingRecoveryError({\n\t\t\t\t\t\t\tkind: 'marker-io',\n\t\t\t\t\t\t\tpath: err.path,\n\t\t\t\t\t\t\tdetail: err.detail,\n\t\t\t\t\t\t\tcause: err.cause,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\tmarkerCleared = true;\n\t\t} else {\n\t\t\t// Final rewrite — the per-loop rewrite covers crash-during-\n\t\t\t// recovery, this one covers the steady-state \"we finished\n\t\t\t// the loop and some entries are still pending\".\n\t\t\tyield* rewriteMarker(stackRoot, doc, stillPending);\n\t\t}\n\n\t\treturn {\n\t\t\tnoMarker: false,\n\t\t\tsnapshotId: doc.snapshotId,\n\t\t\tinspected: doc.containers.length,\n\t\t\trecovered,\n\t\t\tstillPending,\n\t\t\tmarkerCleared,\n\t\t} as const;\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.recover-pending'));\n"],"mappings":"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"}