@mysten-incubation/devstack 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (858) hide show
  1. package/README.md +7 -4
  2. package/dashboard-ui/assets/grpc-a4usE3Nk.js +3 -0
  3. package/dashboard-ui/assets/index-B82Bh84P.css +1 -0
  4. package/dashboard-ui/assets/index-CRYJ4pru.js +1277 -0
  5. package/dashboard-ui/index.html +2 -2
  6. package/dist/api/define-capabilities.d.mts +1 -20
  7. package/dist/api/define-capabilities.mjs +15 -22
  8. package/dist/api/define-capabilities.mjs.map +1 -1
  9. package/dist/api/define-devstack-with.d.mts +7 -1
  10. package/dist/api/define-devstack-with.mjs.map +1 -1
  11. package/dist/api/define-devstack.mjs.map +1 -1
  12. package/dist/api/define-plugin.d.mts +2 -1
  13. package/dist/api/inference-network.d.mts +31 -17
  14. package/dist/api/inference-network.mjs +40 -43
  15. package/dist/api/inference-network.mjs.map +1 -1
  16. package/dist/api/load-config.mjs +88 -0
  17. package/dist/api/load-config.mjs.map +1 -0
  18. package/dist/api/mode-narrowed-factory.d.mts +1 -1
  19. package/dist/api/mode-narrowed-factory.mjs.map +1 -1
  20. package/dist/api/run-stack-internal.mjs +202 -0
  21. package/dist/api/run-stack-internal.mjs.map +1 -0
  22. package/dist/api/run-stack.d.mts +82 -31
  23. package/dist/api/run-stack.mjs +22 -141
  24. package/dist/api/run-stack.mjs.map +1 -1
  25. package/dist/build-integrations/dapp-kit/index.d.mts +61 -0
  26. package/dist/build-integrations/dapp-kit/index.mjs +58 -0
  27. package/dist/build-integrations/dapp-kit/index.mjs.map +1 -0
  28. package/dist/build-integrations/playwright/config.d.mts +1 -30
  29. package/dist/build-integrations/playwright/config.mjs +1 -27
  30. package/dist/build-integrations/playwright/config.mjs.map +1 -1
  31. package/dist/build-integrations/playwright/errors.d.mts +4 -7
  32. package/dist/build-integrations/playwright/errors.mjs.map +1 -1
  33. package/dist/build-integrations/playwright/global-setup.d.mts +42 -71
  34. package/dist/build-integrations/playwright/global-setup.mjs +142 -218
  35. package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
  36. package/dist/build-integrations/playwright/index.d.mts +2 -2
  37. package/dist/build-integrations/playwright/index.mjs +2 -2
  38. package/dist/build-integrations/playwright/stack-context.d.mts +3 -1
  39. package/dist/build-integrations/playwright/stack-context.mjs +4 -2
  40. package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
  41. package/dist/build-integrations/playwright/wallet-context.mjs +44 -19
  42. package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
  43. package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
  44. package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -1
  45. package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
  46. package/dist/build-integrations/runtime/discover.d.mts +6 -4
  47. package/dist/build-integrations/runtime/discover.mjs +15 -4
  48. package/dist/build-integrations/runtime/discover.mjs.map +1 -1
  49. package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
  50. package/dist/build-integrations/runtime/errors.mjs.map +1 -1
  51. package/dist/build-integrations/runtime/read-stack-context.mjs +3 -3
  52. package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
  53. package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +23 -7
  54. package/dist/build-integrations/runtime/resolve-discovery-env.mjs +25 -12
  55. package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -1
  56. package/dist/build-integrations/runtime/stack-context.d.mts +2 -2
  57. package/dist/build-integrations/vite/index.d.mts +84 -19
  58. package/dist/build-integrations/vite/index.mjs +203 -28
  59. package/dist/build-integrations/vite/index.mjs.map +1 -1
  60. package/dist/build-integrations/vitest/config.d.mts +20 -2
  61. package/dist/build-integrations/vitest/config.mjs +16 -4
  62. package/dist/build-integrations/vitest/config.mjs.map +1 -1
  63. package/dist/build-integrations/vitest/env.d.mts +18 -10
  64. package/dist/build-integrations/vitest/env.mjs +11 -10
  65. package/dist/build-integrations/vitest/env.mjs.map +1 -1
  66. package/dist/build-integrations/vitest/errors.d.mts +7 -11
  67. package/dist/build-integrations/vitest/errors.mjs +3 -4
  68. package/dist/build-integrations/vitest/errors.mjs.map +1 -1
  69. package/dist/build-integrations/vitest/global-setup.d.mts +35 -0
  70. package/dist/build-integrations/vitest/global-setup.mjs +156 -0
  71. package/dist/build-integrations/vitest/global-setup.mjs.map +1 -0
  72. package/dist/build-integrations/vitest/index.d.mts +3 -2
  73. package/dist/build-integrations/vitest/index.mjs +3 -2
  74. package/dist/build-integrations/vitest/setup.mjs +2 -2
  75. package/dist/build-integrations/vitest/setup.mjs.map +1 -1
  76. package/dist/build-integrations/vitest/stack-context.d.mts +5 -3
  77. package/dist/build-integrations/vitest/stack-context.mjs +3 -2
  78. package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
  79. package/dist/cli/bail.mjs.map +1 -1
  80. package/dist/cli/doctor-probes.mjs +1 -3
  81. package/dist/cli/doctor-probes.mjs.map +1 -1
  82. package/dist/cli/main.d.mts +23 -1
  83. package/dist/cli/main.mjs +63 -8
  84. package/dist/cli/main.mjs.map +1 -1
  85. package/dist/cli/prune-direct.mjs.map +1 -1
  86. package/dist/cli/snapshot-reader.mjs.map +1 -1
  87. package/dist/cli/up-lifecycle.mjs.map +1 -1
  88. package/dist/cli/wirings/apply.mjs +28 -21
  89. package/dist/cli/wirings/apply.mjs.map +1 -1
  90. package/dist/cli/wirings/codegen.mjs +108 -0
  91. package/dist/cli/wirings/codegen.mjs.map +1 -0
  92. package/dist/cli/wirings/config-loader.mjs +12 -62
  93. package/dist/cli/wirings/config-loader.mjs.map +1 -1
  94. package/dist/cli/wirings/dump-ids.mjs +86 -0
  95. package/dist/cli/wirings/dump-ids.mjs.map +1 -0
  96. package/dist/cli/wirings/engine-command.mjs +1 -1
  97. package/dist/cli/wirings/engine-command.mjs.map +1 -1
  98. package/dist/cli/wirings/identity.mjs +2 -2
  99. package/dist/cli/wirings/identity.mjs.map +1 -1
  100. package/dist/cli/wirings/provide-file-system.mjs.map +1 -1
  101. package/dist/cli/wirings/read-devstack-version.mjs +17 -0
  102. package/dist/cli/wirings/read-devstack-version.mjs.map +1 -0
  103. package/dist/cli/wirings/snapshot.mjs +27 -34
  104. package/dist/cli/wirings/snapshot.mjs.map +1 -1
  105. package/dist/cli/wirings/up-ipc.mjs +147 -0
  106. package/dist/cli/wirings/up-ipc.mjs.map +1 -0
  107. package/dist/cli/wirings/up.mjs +218 -198
  108. package/dist/cli/wirings/up.mjs.map +1 -1
  109. package/dist/cli/wirings/wipe.mjs +1 -1
  110. package/dist/cli/wirings/wipe.mjs.map +1 -1
  111. package/dist/contracts/chain-probe.d.mts +1 -1
  112. package/dist/contracts/chain-probe.mjs +1 -1
  113. package/dist/contracts/chain-probe.mjs.map +1 -1
  114. package/dist/contracts/codegenable.d.mts +60 -6
  115. package/dist/contracts/codegenable.mjs +28 -0
  116. package/dist/contracts/codegenable.mjs.map +1 -0
  117. package/dist/contracts/config-bindings.mjs +162 -0
  118. package/dist/contracts/config-bindings.mjs.map +1 -0
  119. package/dist/contracts/container-runtime.d.mts +23 -17
  120. package/dist/contracts/plugin-expander.mjs.map +1 -1
  121. package/dist/contracts/snapshotable.d.mts +5 -7
  122. package/dist/contracts/wallet-protocol.d.mts +2 -5
  123. package/dist/contracts/wallet-protocol.mjs +2 -5
  124. package/dist/contracts/wallet-protocol.mjs.map +1 -1
  125. package/dist/index.d.mts +21 -26
  126. package/dist/index.mjs +8 -9
  127. package/dist/orchestrators/boot.d.mts +28 -0
  128. package/dist/orchestrators/boot.mjs +440 -0
  129. package/dist/orchestrators/boot.mjs.map +1 -0
  130. package/dist/orchestrators/codegen/bindings.d.mts +13 -1
  131. package/dist/orchestrators/codegen/bindings.mjs +23 -9
  132. package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
  133. package/dist/orchestrators/codegen/config-runtime.mjs +208 -0
  134. package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -0
  135. package/dist/orchestrators/codegen/emit.mjs +1 -1
  136. package/dist/orchestrators/codegen/emit.mjs.map +1 -1
  137. package/dist/orchestrators/codegen/errors.mjs +21 -1
  138. package/dist/orchestrators/codegen/errors.mjs.map +1 -1
  139. package/dist/orchestrators/codegen/format.mjs +2 -0
  140. package/dist/orchestrators/codegen/format.mjs.map +1 -1
  141. package/dist/orchestrators/codegen/gitignore.mjs +30 -11
  142. package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
  143. package/dist/orchestrators/codegen/id-config.mjs +107 -0
  144. package/dist/orchestrators/codegen/id-config.mjs.map +1 -0
  145. package/dist/orchestrators/codegen/output-location.mjs +23 -31
  146. package/dist/orchestrators/codegen/output-location.mjs.map +1 -1
  147. package/dist/orchestrators/codegen/paths.mjs +8 -1
  148. package/dist/orchestrators/codegen/paths.mjs.map +1 -1
  149. package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
  150. package/dist/orchestrators/codegen/service.mjs +286 -58
  151. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  152. package/dist/orchestrators/layers.mjs +31 -0
  153. package/dist/orchestrators/layers.mjs.map +1 -0
  154. package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -1
  155. package/dist/orchestrators/lifecycle-prune/index.mjs +8 -10
  156. package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -1
  157. package/dist/orchestrators/network-options.d.mts +52 -0
  158. package/dist/orchestrators/network-options.mjs +43 -0
  159. package/dist/orchestrators/network-options.mjs.map +1 -0
  160. package/dist/orchestrators/router/cleanup.mjs.map +1 -1
  161. package/dist/orchestrators/router/cors.mjs.map +1 -1
  162. package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
  163. package/dist/orchestrators/router/errors.mjs.map +1 -1
  164. package/dist/orchestrators/router/file-provider.mjs.map +1 -1
  165. package/dist/orchestrators/router/hostname.mjs.map +1 -1
  166. package/dist/orchestrators/router/profile.mjs +1 -1
  167. package/dist/orchestrators/router/profile.mjs.map +1 -1
  168. package/dist/orchestrators/router/service.d.mts +2 -2
  169. package/dist/orchestrators/router/service.mjs +12 -27
  170. package/dist/orchestrators/router/service.mjs.map +1 -1
  171. package/dist/orchestrators/router/traefik-container.mjs +1 -1
  172. package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
  173. package/dist/orchestrators/snapshot/capture.mjs +171 -195
  174. package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
  175. package/dist/orchestrators/snapshot/descriptor.d.mts +1 -1
  176. package/dist/orchestrators/snapshot/descriptor.mjs +53 -11
  177. package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
  178. package/dist/orchestrators/snapshot/identity-guard.mjs +3 -5
  179. package/dist/orchestrators/snapshot/identity-guard.mjs.map +1 -1
  180. package/dist/orchestrators/snapshot/image-bundle-tags.mjs +32 -101
  181. package/dist/orchestrators/snapshot/image-bundle-tags.mjs.map +1 -1
  182. package/dist/orchestrators/snapshot/index.mjs +1 -4
  183. package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
  184. package/dist/orchestrators/snapshot/interrupted-restore.mjs +116 -0
  185. package/dist/orchestrators/snapshot/interrupted-restore.mjs.map +1 -0
  186. package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -1
  187. package/dist/orchestrators/snapshot/prune.mjs +40 -31
  188. package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
  189. package/dist/orchestrators/snapshot/restore.mjs +221 -196
  190. package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
  191. package/dist/orchestrators/snapshot/service.mjs +72 -62
  192. package/dist/orchestrators/snapshot/service.mjs.map +1 -1
  193. package/dist/orchestrators/snapshot/wipe.mjs +67 -56
  194. package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
  195. package/dist/plugins/account/codegen.mjs +3 -0
  196. package/dist/plugins/account/codegen.mjs.map +1 -1
  197. package/dist/plugins/account/errors.d.mts +2 -2
  198. package/dist/plugins/account/errors.mjs +1 -4
  199. package/dist/plugins/account/errors.mjs.map +1 -1
  200. package/dist/plugins/account/funding.mjs +3 -27
  201. package/dist/plugins/account/funding.mjs.map +1 -1
  202. package/dist/plugins/account/index.d.mts +3 -8
  203. package/dist/plugins/account/index.mjs +71 -35
  204. package/dist/plugins/account/index.mjs.map +1 -1
  205. package/dist/plugins/account/keypair.d.mts +8 -7
  206. package/dist/plugins/account/keypair.mjs +5 -18
  207. package/dist/plugins/account/keypair.mjs.map +1 -1
  208. package/dist/plugins/account/lease.mjs.map +1 -1
  209. package/dist/plugins/account/registry.d.mts +1 -46
  210. package/dist/plugins/account/registry.mjs.map +1 -1
  211. package/dist/plugins/account/service.d.mts +43 -34
  212. package/dist/plugins/account/service.mjs +9 -54
  213. package/dist/plugins/account/service.mjs.map +1 -1
  214. package/dist/plugins/account/snapshot.mjs.map +1 -1
  215. package/dist/plugins/account/variants/ephemeral.mjs.map +1 -1
  216. package/dist/plugins/account/variants/impersonate.mjs.map +1 -1
  217. package/dist/plugins/account/variants/signer.mjs.map +1 -1
  218. package/dist/plugins/action/discriminator.mjs.map +1 -1
  219. package/dist/plugins/action/errors.mjs +1 -4
  220. package/dist/plugins/action/errors.mjs.map +1 -1
  221. package/dist/plugins/action/execute.mjs +3 -4
  222. package/dist/plugins/action/execute.mjs.map +1 -1
  223. package/dist/plugins/action/index.d.mts +2 -2
  224. package/dist/plugins/action/index.mjs +7 -11
  225. package/dist/plugins/action/index.mjs.map +1 -1
  226. package/dist/plugins/action/service.d.mts +5 -7
  227. package/dist/plugins/action/service.mjs +2 -16
  228. package/dist/plugins/action/service.mjs.map +1 -1
  229. package/dist/plugins/coin/address-resolution.mjs.map +1 -1
  230. package/dist/plugins/coin/codegen.mjs +97 -16
  231. package/dist/plugins/coin/codegen.mjs.map +1 -1
  232. package/dist/plugins/coin/discovery.mjs.map +1 -1
  233. package/dist/plugins/coin/errors.mjs +1 -4
  234. package/dist/plugins/coin/errors.mjs.map +1 -1
  235. package/dist/plugins/coin/index.d.mts +4 -34
  236. package/dist/plugins/coin/index.mjs +47 -28
  237. package/dist/plugins/coin/index.mjs.map +1 -1
  238. package/dist/plugins/coin/metadata.d.mts +1 -0
  239. package/dist/plugins/coin/metadata.mjs +8 -9
  240. package/dist/plugins/coin/metadata.mjs.map +1 -1
  241. package/dist/plugins/coin/mint.mjs +7 -21
  242. package/dist/plugins/coin/mint.mjs.map +1 -1
  243. package/dist/plugins/coin/registry.mjs +33 -20
  244. package/dist/plugins/coin/registry.mjs.map +1 -1
  245. package/dist/plugins/coin/service.d.mts +1 -1
  246. package/dist/plugins/coin/service.mjs +1 -8
  247. package/dist/plugins/coin/service.mjs.map +1 -1
  248. package/dist/plugins/coin/snapshot.mjs.map +1 -1
  249. package/dist/plugins/coin/type-strings.mjs.map +1 -1
  250. package/dist/plugins/dashboard/domain.mjs +106 -167
  251. package/dist/plugins/dashboard/domain.mjs.map +1 -1
  252. package/dist/plugins/dashboard/index.mjs +13 -13
  253. package/dist/plugins/dashboard/index.mjs.map +1 -1
  254. package/dist/plugins/dashboard/origin-policy.mjs +3 -3
  255. package/dist/plugins/dashboard/origin-policy.mjs.map +1 -1
  256. package/dist/plugins/dashboard/routable.mjs.map +1 -1
  257. package/dist/plugins/dashboard/schema/builder.mjs.map +1 -1
  258. package/dist/plugins/dashboard/schema/enums.mjs.map +1 -1
  259. package/dist/plugins/dashboard/schema/root.mjs +23 -44
  260. package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
  261. package/dist/plugins/dashboard/schema/types.mjs +37 -91
  262. package/dist/plugins/dashboard/schema/types.mjs.map +1 -1
  263. package/dist/plugins/dashboard/schema.mjs.map +1 -1
  264. package/dist/plugins/dashboard/server.mjs.map +1 -1
  265. package/dist/plugins/deepbook/codegen.d.mts +12 -11
  266. package/dist/plugins/deepbook/codegen.mjs +108 -11
  267. package/dist/plugins/deepbook/codegen.mjs.map +1 -1
  268. package/dist/plugins/deepbook/deploy.mjs +7 -14
  269. package/dist/plugins/deepbook/deploy.mjs.map +1 -1
  270. package/dist/plugins/deepbook/errors.mjs +4 -2
  271. package/dist/plugins/deepbook/errors.mjs.map +1 -1
  272. package/dist/plugins/deepbook/faucet-strategy.mjs +4 -10
  273. package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
  274. package/dist/plugins/deepbook/hash.mjs.map +1 -1
  275. package/dist/plugins/deepbook/index.d.mts +38 -34
  276. package/dist/plugins/deepbook/index.mjs +133 -64
  277. package/dist/plugins/deepbook/index.mjs.map +1 -1
  278. package/dist/plugins/deepbook/plugin-key.mjs.map +1 -1
  279. package/dist/plugins/deepbook/pyth/index.mjs +4 -8
  280. package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
  281. package/dist/plugins/deepbook/snapshot.mjs +0 -1
  282. package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
  283. package/dist/plugins/deepbook/type-strings.mjs.map +1 -1
  284. package/dist/plugins/deepbook/types.d.mts +2 -1
  285. package/dist/plugins/deepbook/types.mjs.map +1 -1
  286. package/dist/plugins/faucet/dispatcher.d.mts +3 -3
  287. package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
  288. package/dist/plugins/faucet/errors.mjs.map +1 -1
  289. package/dist/plugins/faucet/http.mjs +2 -10
  290. package/dist/plugins/faucet/http.mjs.map +1 -1
  291. package/dist/plugins/faucet/index.mjs +0 -1
  292. package/dist/plugins/faucet/index.mjs.map +1 -1
  293. package/dist/plugins/host-service/errors.d.mts +1 -3
  294. package/dist/plugins/host-service/errors.mjs +1 -2
  295. package/dist/plugins/host-service/errors.mjs.map +1 -1
  296. package/dist/plugins/host-service/index.d.mts +1 -2
  297. package/dist/plugins/host-service/index.mjs +11 -11
  298. package/dist/plugins/host-service/index.mjs.map +1 -1
  299. package/dist/plugins/host-service/routable.mjs.map +1 -1
  300. package/dist/plugins/host-service/service.mjs +13 -13
  301. package/dist/plugins/host-service/service.mjs.map +1 -1
  302. package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -1
  303. package/dist/plugins/internal/codegen-helpers.mjs +2 -0
  304. package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
  305. package/dist/plugins/internal/funding-failure-error.mjs.map +1 -1
  306. package/dist/plugins/{postgres → internal/postgres-sidecar}/connection.mjs +1 -1
  307. package/dist/plugins/internal/postgres-sidecar/connection.mjs.map +1 -0
  308. package/dist/plugins/{postgres → internal/postgres-sidecar}/db-ensure.mjs +30 -16
  309. package/dist/plugins/internal/postgres-sidecar/db-ensure.mjs.map +1 -0
  310. package/dist/plugins/internal/postgres-sidecar/errors.mjs +21 -0
  311. package/dist/plugins/internal/postgres-sidecar/errors.mjs.map +1 -0
  312. package/dist/{substrate/runtime/scoped-multimap → plugins/internal/postgres-sidecar}/index.mjs +1 -0
  313. package/dist/plugins/internal/postgres-sidecar/service.mjs +121 -0
  314. package/dist/plugins/internal/postgres-sidecar/service.mjs.map +1 -0
  315. package/dist/plugins/package/build.mjs +2 -2
  316. package/dist/plugins/package/build.mjs.map +1 -1
  317. package/dist/plugins/package/codegen.d.mts +7 -14
  318. package/dist/plugins/package/codegen.mjs +187 -59
  319. package/dist/plugins/package/codegen.mjs.map +1 -1
  320. package/dist/plugins/package/dep-resolution.mjs +51 -3
  321. package/dist/plugins/package/dep-resolution.mjs.map +1 -1
  322. package/dist/plugins/package/errors.mjs +1 -4
  323. package/dist/plugins/package/errors.mjs.map +1 -1
  324. package/dist/plugins/package/git-source.d.mts +18 -0
  325. package/dist/plugins/package/git-source.mjs +119 -0
  326. package/dist/plugins/package/git-source.mjs.map +1 -0
  327. package/dist/plugins/package/index.d.mts +28 -19
  328. package/dist/plugins/package/index.mjs +107 -32
  329. package/dist/plugins/package/index.mjs.map +1 -1
  330. package/dist/plugins/package/mode-known.mjs +2 -2
  331. package/dist/plugins/package/mode-known.mjs.map +1 -1
  332. package/dist/plugins/package/mode-local.mjs +18 -35
  333. package/dist/plugins/package/mode-local.mjs.map +1 -1
  334. package/dist/plugins/package/publish-executor.mjs +13 -8
  335. package/dist/plugins/package/publish-executor.mjs.map +1 -1
  336. package/dist/plugins/package/publish-output.d.mts +1 -11
  337. package/dist/plugins/package/publish-output.mjs +1 -9
  338. package/dist/plugins/package/publish-output.mjs.map +1 -1
  339. package/dist/plugins/package/registry.d.mts +1 -1
  340. package/dist/plugins/package/registry.mjs +26 -14
  341. package/dist/plugins/package/registry.mjs.map +1 -1
  342. package/dist/plugins/package/service.mjs.map +1 -1
  343. package/dist/plugins/package/snapshot.mjs.map +1 -1
  344. package/dist/plugins/router-entrypoints.mjs +1 -3
  345. package/dist/plugins/router-entrypoints.mjs.map +1 -1
  346. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +1 -2
  347. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
  348. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +1 -6
  349. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
  350. package/dist/plugins/seal/codegen.d.mts +1 -5
  351. package/dist/plugins/seal/codegen.mjs +83 -12
  352. package/dist/plugins/seal/codegen.mjs.map +1 -1
  353. package/dist/plugins/seal/config-render.mjs +2 -6
  354. package/dist/plugins/seal/config-render.mjs.map +1 -1
  355. package/dist/plugins/seal/deploy.mjs +18 -29
  356. package/dist/plugins/seal/deploy.mjs.map +1 -1
  357. package/dist/plugins/seal/errors.d.mts +4 -5
  358. package/dist/plugins/seal/errors.mjs +1 -8
  359. package/dist/plugins/seal/errors.mjs.map +1 -1
  360. package/dist/plugins/seal/index.d.mts +7 -28
  361. package/dist/plugins/seal/index.mjs +69 -47
  362. package/dist/plugins/seal/index.mjs.map +1 -1
  363. package/dist/plugins/seal/key-manager.mjs.map +1 -1
  364. package/dist/plugins/seal/key-server.mjs +1 -6
  365. package/dist/plugins/seal/key-server.mjs.map +1 -1
  366. package/dist/plugins/seal/keygen.mjs +2 -3
  367. package/dist/plugins/seal/keygen.mjs.map +1 -1
  368. package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
  369. package/dist/plugins/seal/mode/live.mjs +1 -1
  370. package/dist/plugins/seal/mode/live.mjs.map +1 -1
  371. package/dist/plugins/seal/mode/local-keygen.mjs +4 -10
  372. package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
  373. package/dist/plugins/seal/plugin-key.mjs.map +1 -1
  374. package/dist/plugins/seal/registry-publish.mjs.map +1 -1
  375. package/dist/plugins/seal/routable.mjs.map +1 -1
  376. package/dist/plugins/seal/service.mjs.map +1 -1
  377. package/dist/plugins/seal/snapshot.mjs +1 -2
  378. package/dist/plugins/seal/snapshot.mjs.map +1 -1
  379. package/dist/plugins/sui/auto-tick.mjs +5 -5
  380. package/dist/plugins/sui/auto-tick.mjs.map +1 -1
  381. package/dist/plugins/sui/chain-build-container.mjs +2 -2
  382. package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
  383. package/dist/plugins/sui/chain-probe.mjs +4 -4
  384. package/dist/plugins/sui/chain-probe.mjs.map +1 -1
  385. package/dist/plugins/sui/codegen.d.mts +9 -7
  386. package/dist/plugins/sui/codegen.mjs +49 -46
  387. package/dist/plugins/sui/codegen.mjs.map +1 -1
  388. package/dist/plugins/sui/errors.d.mts +1 -1
  389. package/dist/plugins/sui/errors.mjs +1 -9
  390. package/dist/plugins/sui/errors.mjs.map +1 -1
  391. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.d.mts +1 -1
  392. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.mjs +3 -7
  393. package/dist/plugins/sui/exec/index.mjs.map +1 -0
  394. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.mjs +1 -1
  395. package/dist/plugins/sui/exec/sign-and-dispatch.mjs.map +1 -0
  396. package/dist/plugins/sui/fork-faucet-strategy.mjs +2 -6
  397. package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -1
  398. package/dist/plugins/sui/fork-orchestration.mjs +13 -23
  399. package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
  400. package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
  401. package/dist/plugins/sui/index.d.mts +34 -83
  402. package/dist/plugins/sui/index.mjs +200 -68
  403. package/dist/plugins/sui/index.mjs.map +1 -1
  404. package/dist/{substrate/runtime/sui-ledger → plugins/sui/ledger}/object-ref.mjs +1 -1
  405. package/dist/plugins/sui/ledger/object-ref.mjs.map +1 -0
  406. package/dist/plugins/sui/local-faucet-strategy.mjs +1 -5
  407. package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -1
  408. package/dist/plugins/sui/log-attrs.mjs +11 -0
  409. package/dist/plugins/sui/log-attrs.mjs.map +1 -0
  410. package/dist/plugins/sui/mode/external.mjs +4 -12
  411. package/dist/plugins/sui/mode/external.mjs.map +1 -1
  412. package/dist/plugins/sui/mode/fork.mjs +19 -16
  413. package/dist/plugins/sui/mode/fork.mjs.map +1 -1
  414. package/dist/plugins/sui/mode/live.mjs +4 -17
  415. package/dist/plugins/sui/mode/live.mjs.map +1 -1
  416. package/dist/plugins/sui/mode/local.d.mts +1 -0
  417. package/dist/plugins/sui/mode/local.mjs +203 -61
  418. package/dist/plugins/sui/mode/local.mjs.map +1 -1
  419. package/dist/plugins/sui/mode/shared-boot.mjs +18 -23
  420. package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
  421. package/dist/plugins/sui/mode/shared.d.mts +6 -7
  422. package/dist/plugins/sui/mode/shared.mjs.map +1 -1
  423. package/dist/plugins/sui/mode/spec.d.mts +23 -6
  424. package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.mjs +28 -30
  425. package/dist/plugins/sui/move/index.mjs.map +1 -0
  426. package/dist/plugins/sui/move-summary-runner.mjs +8 -4
  427. package/dist/plugins/sui/move-summary-runner.mjs.map +1 -1
  428. package/dist/{substrate/network.d.mts → plugins/sui/network-config.d.mts} +11 -10
  429. package/dist/plugins/sui/network-resolver.d.mts +6 -6
  430. package/dist/plugins/sui/routable.mjs.map +1 -1
  431. package/dist/plugins/sui/service.mjs +10 -6
  432. package/dist/plugins/sui/service.mjs.map +1 -1
  433. package/dist/plugins/sui/snapshot.mjs +10 -5
  434. package/dist/plugins/sui/snapshot.mjs.map +1 -1
  435. package/dist/plugins/wallet/codegen.d.mts +8 -6
  436. package/dist/plugins/wallet/codegen.mjs +4 -10
  437. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  438. package/dist/plugins/wallet/errors.mjs +1 -4
  439. package/dist/plugins/wallet/errors.mjs.map +1 -1
  440. package/dist/plugins/wallet/index.d.mts +3 -6
  441. package/dist/plugins/wallet/index.mjs +16 -21
  442. package/dist/plugins/wallet/index.mjs.map +1 -1
  443. package/dist/plugins/wallet/origin-policy.mjs +3 -3
  444. package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
  445. package/dist/plugins/wallet/pairing.mjs +6 -24
  446. package/dist/plugins/wallet/pairing.mjs.map +1 -1
  447. package/dist/plugins/wallet/protocol.mjs.map +1 -1
  448. package/dist/plugins/wallet/routable.mjs.map +1 -1
  449. package/dist/plugins/wallet/server.mjs +17 -24
  450. package/dist/plugins/wallet/server.mjs.map +1 -1
  451. package/dist/plugins/wallet/service.d.mts +2 -2
  452. package/dist/plugins/wallet/service.mjs +4 -15
  453. package/dist/plugins/wallet/service.mjs.map +1 -1
  454. package/dist/plugins/wallet/snapshot.mjs.map +1 -1
  455. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +1 -5
  456. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
  457. package/dist/plugins/walrus/codegen.d.mts +2 -13
  458. package/dist/plugins/walrus/codegen.mjs +93 -28
  459. package/dist/plugins/walrus/codegen.mjs.map +1 -1
  460. package/dist/plugins/walrus/deploy-paths.mjs +2 -3
  461. package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
  462. package/dist/plugins/walrus/deploy.mjs +26 -19
  463. package/dist/plugins/walrus/deploy.mjs.map +1 -1
  464. package/dist/plugins/walrus/errors.mjs +1 -8
  465. package/dist/plugins/walrus/errors.mjs.map +1 -1
  466. package/dist/plugins/walrus/faucet-strategy.mjs +15 -1
  467. package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
  468. package/dist/plugins/walrus/index.d.mts +13 -44
  469. package/dist/plugins/walrus/index.mjs +119 -122
  470. package/dist/plugins/walrus/index.mjs.map +1 -1
  471. package/dist/plugins/walrus/mode/known-deploy.mjs +6 -6
  472. package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
  473. package/dist/plugins/walrus/mode/local-cluster.d.mts +2 -2
  474. package/dist/plugins/walrus/mode/local-cluster.mjs +2 -10
  475. package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
  476. package/dist/plugins/walrus/plugin-key.mjs.map +1 -1
  477. package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
  478. package/dist/plugins/walrus/routable.mjs.map +1 -1
  479. package/dist/plugins/walrus/service.mjs.map +1 -1
  480. package/dist/plugins/walrus/snapshot.mjs +3 -3
  481. package/dist/plugins/walrus/snapshot.mjs.map +1 -1
  482. package/dist/plugins/walrus/storage-nodes.mjs +53 -14
  483. package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
  484. package/dist/plugins/walrus/wal-swap.mjs +4 -8
  485. package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
  486. package/dist/primitives/artifact-publisher.mjs.map +1 -1
  487. package/dist/primitives/cache.d.mts +1 -1
  488. package/dist/runtime/docker/client.mjs +47 -3
  489. package/dist/runtime/docker/client.mjs.map +1 -1
  490. package/dist/runtime/docker/container.mjs +37 -57
  491. package/dist/runtime/docker/container.mjs.map +1 -1
  492. package/dist/runtime/docker/errors.mjs.map +1 -1
  493. package/dist/runtime/docker/exec.mjs +2 -2
  494. package/dist/runtime/docker/exec.mjs.map +1 -1
  495. package/dist/runtime/docker/image.mjs +10 -11
  496. package/dist/runtime/docker/image.mjs.map +1 -1
  497. package/dist/runtime/docker/index.mjs +0 -1
  498. package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -1
  499. package/dist/runtime/docker/inventory.mjs +9 -9
  500. package/dist/runtime/docker/inventory.mjs.map +1 -1
  501. package/dist/runtime/docker/labels.mjs +12 -1
  502. package/dist/runtime/docker/labels.mjs.map +1 -1
  503. package/dist/runtime/docker/network.mjs +6 -6
  504. package/dist/runtime/docker/network.mjs.map +1 -1
  505. package/dist/runtime/docker/render-run-args.mjs.map +1 -1
  506. package/dist/runtime/docker/service.mjs +31 -45
  507. package/dist/runtime/docker/service.mjs.map +1 -1
  508. package/dist/runtime/docker/sweep.mjs +14 -64
  509. package/dist/runtime/docker/sweep.mjs.map +1 -1
  510. package/dist/runtime/docker/volume.mjs +1 -1
  511. package/dist/runtime/docker/volume.mjs.map +1 -1
  512. package/dist/runtime/docker/wrap.mjs +14 -2
  513. package/dist/runtime/docker/wrap.mjs.map +1 -1
  514. package/dist/substrate/brand.d.mts +2 -5
  515. package/dist/substrate/brand.mjs +3 -2
  516. package/dist/substrate/brand.mjs.map +1 -1
  517. package/dist/substrate/cross-process.mjs +2 -16
  518. package/dist/substrate/cross-process.mjs.map +1 -1
  519. package/dist/substrate/event-time.mjs +0 -2
  520. package/dist/substrate/event-time.mjs.map +1 -1
  521. package/dist/substrate/events.d.mts +46 -16
  522. package/dist/substrate/identity.d.mts +11 -5
  523. package/dist/substrate/manifest.d.mts +33 -26
  524. package/dist/substrate/manifest.mjs +8 -3
  525. package/dist/substrate/manifest.mjs.map +1 -1
  526. package/dist/substrate/options.d.mts +33 -4
  527. package/dist/substrate/plugin-ctx.d.mts +70 -0
  528. package/dist/substrate/plugin-ctx.mjs +66 -0
  529. package/dist/substrate/plugin-ctx.mjs.map +1 -0
  530. package/dist/substrate/plugin.d.mts +46 -38
  531. package/dist/substrate/plugin.mjs +13 -9
  532. package/dist/substrate/plugin.mjs.map +1 -1
  533. package/dist/substrate/projection.d.mts +1 -1
  534. package/dist/substrate/runtime/atomic-write.mjs +3 -4
  535. package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
  536. package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
  537. package/dist/substrate/runtime/cache/service.mjs +50 -20
  538. package/dist/substrate/runtime/cache/service.mjs.map +1 -1
  539. package/dist/substrate/runtime/config-validation.mjs.map +1 -1
  540. package/dist/substrate/runtime/control-plane/domain.mjs +34 -12
  541. package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -1
  542. package/dist/substrate/runtime/control-plane/service.mjs.map +1 -1
  543. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +14 -17
  544. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
  545. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
  546. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
  547. package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs.map +1 -1
  548. package/dist/substrate/runtime/cross-process/index.mjs +0 -2
  549. package/dist/substrate/runtime/cross-process/live-clock.mjs +1 -1
  550. package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -1
  551. package/dist/substrate/runtime/cross-process/liveness.mjs +11 -18
  552. package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
  553. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +2 -2
  554. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -1
  555. package/dist/substrate/runtime/cross-process/roster.mjs +17 -127
  556. package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
  557. package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -1
  558. package/dist/substrate/runtime/cross-process/stack-lock.mjs +29 -28
  559. package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
  560. package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
  561. package/dist/substrate/runtime/errors.mjs +1 -13
  562. package/dist/substrate/runtime/errors.mjs.map +1 -1
  563. package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -1
  564. package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -1
  565. package/dist/substrate/runtime/host-gateway.mjs.map +1 -1
  566. package/dist/substrate/runtime/host-tree-tar/index.mjs +15 -163
  567. package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
  568. package/dist/substrate/runtime/http-probe.d.mts +1 -3
  569. package/dist/substrate/runtime/http-probe.mjs.map +1 -1
  570. package/dist/substrate/runtime/index.mjs +1 -6
  571. package/dist/substrate/runtime/lease-broker/service.mjs +5 -23
  572. package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
  573. package/dist/substrate/runtime/lifecycle/dep-graph.mjs +3 -3
  574. package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
  575. package/dist/substrate/runtime/lifecycle/graph-input-id.d.mts +1 -0
  576. package/dist/substrate/runtime/lifecycle/graph-input-id.mjs +133 -0
  577. package/dist/substrate/runtime/lifecycle/graph-input-id.mjs.map +1 -0
  578. package/dist/substrate/runtime/lifecycle/index.mjs +2 -2
  579. package/dist/substrate/runtime/lifecycle/plugin-registry.d.mts +1 -1
  580. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +22 -1
  581. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
  582. package/dist/substrate/runtime/lifecycle/ready-gate.mjs +2 -15
  583. package/dist/substrate/runtime/lifecycle/ready-gate.mjs.map +1 -1
  584. package/dist/substrate/runtime/lifecycle/selective-restart.mjs +29 -25
  585. package/dist/substrate/runtime/lifecycle/selective-restart.mjs.map +1 -1
  586. package/dist/substrate/runtime/lifecycle/signals.mjs +1 -1
  587. package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
  588. package/dist/substrate/runtime/lifecycle/state-machine.mjs.map +1 -1
  589. package/dist/substrate/runtime/lifecycle/watch-attribution.d.mts +1 -1
  590. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs +0 -1
  591. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs.map +1 -1
  592. package/dist/substrate/runtime/managed-container.d.mts +5 -1
  593. package/dist/substrate/runtime/managed-container.mjs +8 -10
  594. package/dist/substrate/runtime/managed-container.mjs.map +1 -1
  595. package/dist/substrate/runtime/manifest/manifest.mjs +8 -30
  596. package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
  597. package/dist/substrate/runtime/mode-errors.d.mts +1 -2
  598. package/dist/substrate/runtime/mode-errors.mjs.map +1 -1
  599. package/dist/substrate/runtime/observability/cascade-formatter.mjs +1 -11
  600. package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
  601. package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -1
  602. package/dist/substrate/runtime/observability/index.d.mts +1 -1
  603. package/dist/substrate/runtime/observability/index.mjs +4 -6
  604. package/dist/substrate/runtime/observability/log-attrs.mjs +46 -0
  605. package/dist/substrate/runtime/observability/log-attrs.mjs.map +1 -0
  606. package/dist/substrate/runtime/observability/log-store.mjs.map +1 -1
  607. package/dist/substrate/runtime/observability/logger.mjs +3 -3
  608. package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
  609. package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -1
  610. package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
  611. package/dist/substrate/runtime/observability/process-lines.mjs.map +1 -1
  612. package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
  613. package/dist/substrate/runtime/observability/subprocess-capture.mjs +2 -2
  614. package/dist/substrate/runtime/observability/subprocess-capture.mjs.map +1 -1
  615. package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -1
  616. package/dist/substrate/runtime/paths.mjs +27 -9
  617. package/dist/substrate/runtime/paths.mjs.map +1 -1
  618. package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -1
  619. package/dist/substrate/runtime/port-broker/service.mjs +10 -27
  620. package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
  621. package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
  622. package/dist/substrate/runtime/probes.d.mts +2 -3
  623. package/dist/substrate/runtime/probes.mjs.map +1 -1
  624. package/dist/substrate/runtime/process-supervisor.mjs.map +1 -1
  625. package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
  626. package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
  627. package/dist/substrate/runtime/projection/update.mjs +87 -11
  628. package/dist/substrate/runtime/projection/update.mjs.map +1 -1
  629. package/dist/substrate/runtime/random-suffix.mjs.map +1 -1
  630. package/dist/substrate/runtime/reconcile/fs-plan.mjs +146 -0
  631. package/dist/substrate/runtime/reconcile/fs-plan.mjs.map +1 -0
  632. package/dist/substrate/runtime/reconcile/graph.mjs +19 -0
  633. package/dist/substrate/runtime/reconcile/graph.mjs.map +1 -0
  634. package/dist/substrate/runtime/reconcile/index.mjs +5 -0
  635. package/dist/substrate/runtime/reconcile/label.mjs +41 -0
  636. package/dist/substrate/runtime/reconcile/label.mjs.map +1 -0
  637. package/dist/substrate/runtime/reconcile/spec.mjs +15 -0
  638. package/dist/substrate/runtime/reconcile/spec.mjs.map +1 -0
  639. package/dist/substrate/runtime/retry-policy.d.mts +2 -3
  640. package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
  641. package/dist/substrate/runtime/routed-url.mjs.map +1 -1
  642. package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
  643. package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
  644. package/dist/substrate/runtime/stage-and-swap/index.mjs +49 -12
  645. package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
  646. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -1
  647. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
  648. package/dist/substrate/runtime/strategy-registry/service.mjs +29 -17
  649. package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
  650. package/dist/substrate/runtime/subnet-broker.mjs.map +1 -1
  651. package/dist/substrate/runtime/supervisor/acquire-node.mjs +188 -56
  652. package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -1
  653. package/dist/substrate/runtime/supervisor/background-tasks.mjs +79 -65
  654. package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -1
  655. package/dist/substrate/runtime/supervisor/command-loop.mjs +82 -23
  656. package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -1
  657. package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs +16 -0
  658. package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs.map +1 -0
  659. package/dist/substrate/runtime/supervisor/errors.mjs +11 -2
  660. package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -1
  661. package/dist/substrate/runtime/supervisor/index.mjs +2 -0
  662. package/dist/substrate/runtime/supervisor/shutdown.mjs +6 -6
  663. package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -1
  664. package/dist/substrate/runtime/supervisor/start-supervisor.mjs +60 -70
  665. package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -1
  666. package/dist/substrate/runtime/supervisor/state.d.mts +1 -1
  667. package/dist/substrate/runtime/supervisor/state.mjs +1 -1
  668. package/dist/substrate/runtime/supervisor/state.mjs.map +1 -1
  669. package/dist/substrate/runtime/supervisor/teardown.mjs +10 -9
  670. package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -1
  671. package/dist/substrate/runtime/supervisor/wiring.mjs +6 -20
  672. package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -1
  673. package/dist/substrate/runtime/tar/reader.mjs +216 -0
  674. package/dist/substrate/runtime/tar/reader.mjs.map +1 -0
  675. package/dist/substrate/runtime/typed-env.mjs.map +1 -1
  676. package/dist/substrate/versioned-doc-schema.mjs.map +1 -1
  677. package/dist/substrate/versioned-doc-sync.mjs +5 -5
  678. package/dist/substrate/versioned-doc-sync.mjs.map +1 -1
  679. package/dist/surfaces/cli/command-tree.mjs +49 -7
  680. package/dist/surfaces/cli/command-tree.mjs.map +1 -1
  681. package/dist/surfaces/cli/commands/config.mjs +1 -1
  682. package/dist/surfaces/cli/commands/config.mjs.map +1 -1
  683. package/dist/surfaces/cli/commands/confirm-node.mjs.map +1 -1
  684. package/dist/surfaces/cli/commands/confirm.mjs.map +1 -1
  685. package/dist/surfaces/cli/commands/doctor.mjs +1 -1
  686. package/dist/surfaces/cli/commands/doctor.mjs.map +1 -1
  687. package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -1
  688. package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
  689. package/dist/surfaces/cli/commands/prune.mjs +1 -1
  690. package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
  691. package/dist/surfaces/cli/commands/snapshot.mjs +1 -1
  692. package/dist/surfaces/cli/commands/snapshot.mjs.map +1 -1
  693. package/dist/surfaces/cli/commands/status.mjs +1 -1
  694. package/dist/surfaces/cli/commands/status.mjs.map +1 -1
  695. package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
  696. package/dist/surfaces/cli/commands/wipe.mjs +3 -4
  697. package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
  698. package/dist/surfaces/cli/envelope.mjs.map +1 -1
  699. package/dist/surfaces/cli/errors.mjs.map +1 -1
  700. package/dist/surfaces/cli/flags.mjs.map +1 -1
  701. package/dist/surfaces/cli/index.mjs +45 -14
  702. package/dist/surfaces/cli/index.mjs.map +1 -1
  703. package/dist/surfaces/cli/output.mjs.map +1 -1
  704. package/dist/surfaces/cli/sysexits.mjs.map +1 -1
  705. package/dist/surfaces/tui/app.mjs +0 -21
  706. package/dist/surfaces/tui/app.mjs.map +1 -1
  707. package/dist/surfaces/tui/dashboard.mjs +0 -48
  708. package/dist/surfaces/tui/dashboard.mjs.map +1 -1
  709. package/dist/surfaces/tui/display-derivation.mjs +80 -16
  710. package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
  711. package/dist/surfaces/tui/errors.mjs.map +1 -1
  712. package/dist/surfaces/tui/event-log.mjs +0 -9
  713. package/dist/surfaces/tui/event-log.mjs.map +1 -1
  714. package/dist/surfaces/tui/heartbeat.mjs.map +1 -1
  715. package/dist/surfaces/tui/index.mjs +2 -2
  716. package/dist/surfaces/tui/index.mjs.map +1 -1
  717. package/dist/surfaces/tui/input.mjs.map +1 -1
  718. package/dist/surfaces/tui/mode-detect.mjs.map +1 -1
  719. package/dist/surfaces/tui/mount-ink.mjs.map +1 -1
  720. package/dist/surfaces/tui/plain-renderer.mjs +26 -19
  721. package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
  722. package/dist/surfaces/tui/resource-table.mjs +8 -10
  723. package/dist/surfaces/tui/resource-table.mjs.map +1 -1
  724. package/images/postgres/Dockerfile +0 -1
  725. package/images/sui/Dockerfile +27 -54
  726. package/images/sui/entrypoint.sh +17 -179
  727. package/package.json +23 -15
  728. package/dashboard-ui/assets/index-Bmi1UtAg.js +0 -1356
  729. package/dashboard-ui/assets/index-D5EShVt4.js +0 -3
  730. package/dashboard-ui/assets/index-Deml9drg.css +0 -1
  731. package/dist/api/plugin-errors.d.mts +0 -7
  732. package/dist/api/plugin-errors.mjs +0 -10
  733. package/dist/api/plugin-errors.mjs.map +0 -1
  734. package/dist/cli/wirings/build-verb-layers.mjs +0 -42
  735. package/dist/cli/wirings/build-verb-layers.mjs.map +0 -1
  736. package/dist/contracts/capability-decl.d.mts +0 -40
  737. package/dist/orchestrators/built-in-plugin-layers.mjs +0 -54
  738. package/dist/orchestrators/built-in-plugin-layers.mjs.map +0 -1
  739. package/dist/orchestrators/run.mjs +0 -91
  740. package/dist/orchestrators/run.mjs.map +0 -1
  741. package/dist/orchestrators/runtime-composition.d.mts +0 -10
  742. package/dist/orchestrators/runtime-composition.mjs +0 -252
  743. package/dist/orchestrators/runtime-composition.mjs.map +0 -1
  744. package/dist/orchestrators/snapshot/capture-command.d.mts +0 -1
  745. package/dist/orchestrators/snapshot/capture-command.mjs +0 -25
  746. package/dist/orchestrators/snapshot/capture-command.mjs.map +0 -1
  747. package/dist/orchestrators/snapshot/pending-marker.mjs +0 -99
  748. package/dist/orchestrators/snapshot/pending-marker.mjs.map +0 -1
  749. package/dist/orchestrators/snapshot/recover-pending.mjs +0 -223
  750. package/dist/orchestrators/snapshot/recover-pending.mjs.map +0 -1
  751. package/dist/orchestrators/snapshot/state-document.d.mts +0 -1
  752. package/dist/orchestrators/snapshot/state-document.mjs +0 -58
  753. package/dist/orchestrators/snapshot/state-document.mjs.map +0 -1
  754. package/dist/plugins/account/spans.mjs +0 -16
  755. package/dist/plugins/account/spans.mjs.map +0 -1
  756. package/dist/plugins/account/variants/env.mjs +0 -24
  757. package/dist/plugins/account/variants/env.mjs.map +0 -1
  758. package/dist/plugins/account/variants/inline.mjs +0 -13
  759. package/dist/plugins/account/variants/inline.mjs.map +0 -1
  760. package/dist/plugins/account/variants/keystore.mjs +0 -91
  761. package/dist/plugins/account/variants/keystore.mjs.map +0 -1
  762. package/dist/plugins/action/spans.mjs +0 -11
  763. package/dist/plugins/action/spans.mjs.map +0 -1
  764. package/dist/plugins/coin/spans.mjs +0 -20
  765. package/dist/plugins/coin/spans.mjs.map +0 -1
  766. package/dist/plugins/deepbook/spans.mjs +0 -18
  767. package/dist/plugins/deepbook/spans.mjs.map +0 -1
  768. package/dist/plugins/faucet/spans.mjs +0 -12
  769. package/dist/plugins/faucet/spans.mjs.map +0 -1
  770. package/dist/plugins/package/spans.mjs +0 -14
  771. package/dist/plugins/package/spans.mjs.map +0 -1
  772. package/dist/plugins/postgres/codegen.mjs +0 -42
  773. package/dist/plugins/postgres/codegen.mjs.map +0 -1
  774. package/dist/plugins/postgres/connection.d.mts +0 -33
  775. package/dist/plugins/postgres/connection.mjs.map +0 -1
  776. package/dist/plugins/postgres/db-ensure.mjs.map +0 -1
  777. package/dist/plugins/postgres/errors.d.mts +0 -60
  778. package/dist/plugins/postgres/errors.mjs +0 -29
  779. package/dist/plugins/postgres/errors.mjs.map +0 -1
  780. package/dist/plugins/postgres/index.d.mts +0 -37
  781. package/dist/plugins/postgres/index.mjs +0 -68
  782. package/dist/plugins/postgres/index.mjs.map +0 -1
  783. package/dist/plugins/postgres/routable.mjs +0 -33
  784. package/dist/plugins/postgres/routable.mjs.map +0 -1
  785. package/dist/plugins/postgres/service.d.mts +0 -50
  786. package/dist/plugins/postgres/service.mjs +0 -179
  787. package/dist/plugins/postgres/service.mjs.map +0 -1
  788. package/dist/plugins/postgres/snapshot.mjs +0 -32
  789. package/dist/plugins/postgres/snapshot.mjs.map +0 -1
  790. package/dist/plugins/postgres/spans.mjs +0 -11
  791. package/dist/plugins/postgres/spans.mjs.map +0 -1
  792. package/dist/plugins/seal/spans.mjs +0 -18
  793. package/dist/plugins/seal/spans.mjs.map +0 -1
  794. package/dist/plugins/sui/spans.mjs +0 -17
  795. package/dist/plugins/sui/spans.mjs.map +0 -1
  796. package/dist/plugins/wallet/spans.mjs +0 -22
  797. package/dist/plugins/wallet/spans.mjs.map +0 -1
  798. package/dist/plugins/walrus/registry-publish.d.mts +0 -24
  799. package/dist/plugins/walrus/spans.mjs +0 -18
  800. package/dist/plugins/walrus/spans.mjs.map +0 -1
  801. package/dist/runtime/docker/logs.d.mts +0 -1
  802. package/dist/runtime/docker/logs.mjs +0 -34
  803. package/dist/runtime/docker/logs.mjs.map +0 -1
  804. package/dist/substrate/runtime/artifact-publisher/index.mjs +0 -86
  805. package/dist/substrate/runtime/artifact-publisher/index.mjs.map +0 -1
  806. package/dist/substrate/runtime/capability-sinks/index.d.mts +0 -1
  807. package/dist/substrate/runtime/capability-sinks/index.mjs +0 -3
  808. package/dist/substrate/runtime/capability-sinks/layer.d.mts +0 -1
  809. package/dist/substrate/runtime/capability-sinks/layer.mjs +0 -31
  810. package/dist/substrate/runtime/capability-sinks/layer.mjs.map +0 -1
  811. package/dist/substrate/runtime/capability-sinks/service.d.mts +0 -95
  812. package/dist/substrate/runtime/capability-sinks/service.mjs +0 -69
  813. package/dist/substrate/runtime/capability-sinks/service.mjs.map +0 -1
  814. package/dist/substrate/runtime/cross-process/lock.d.mts +0 -1
  815. package/dist/substrate/runtime/cross-process/lock.mjs +0 -23
  816. package/dist/substrate/runtime/cross-process/lock.mjs.map +0 -1
  817. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +0 -113
  818. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +0 -1
  819. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs +0 -31
  820. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +0 -1
  821. package/dist/substrate/runtime/observability/formatter-registry.d.mts +0 -1
  822. package/dist/substrate/runtime/observability/formatter-registry.mjs +0 -48
  823. package/dist/substrate/runtime/observability/formatter-registry.mjs.map +0 -1
  824. package/dist/substrate/runtime/observability/span-store.d.mts +0 -1
  825. package/dist/substrate/runtime/observability/span-store.mjs +0 -110
  826. package/dist/substrate/runtime/observability/span-store.mjs.map +0 -1
  827. package/dist/substrate/runtime/observability/spans.d.mts +0 -1
  828. package/dist/substrate/runtime/observability/spans.mjs +0 -87
  829. package/dist/substrate/runtime/observability/spans.mjs.map +0 -1
  830. package/dist/substrate/runtime/projection/index.mjs +0 -4
  831. package/dist/substrate/runtime/projection/persisted.mjs +0 -213
  832. package/dist/substrate/runtime/projection/persisted.mjs.map +0 -1
  833. package/dist/substrate/runtime/scoped-multimap/service.mjs +0 -52
  834. package/dist/substrate/runtime/scoped-multimap/service.mjs.map +0 -1
  835. package/dist/substrate/runtime/scoped-ref-map/index.mjs +0 -2
  836. package/dist/substrate/runtime/scoped-ref-map/service.mjs +0 -83
  837. package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +0 -1
  838. package/dist/substrate/runtime/state-store/index.mjs +0 -3
  839. package/dist/substrate/runtime/state-store/schema.d.mts +0 -1
  840. package/dist/substrate/runtime/state-store/schema.mjs +0 -41
  841. package/dist/substrate/runtime/state-store/schema.mjs.map +0 -1
  842. package/dist/substrate/runtime/state-store/service.d.mts +0 -1
  843. package/dist/substrate/runtime/state-store/service.mjs +0 -145
  844. package/dist/substrate/runtime/state-store/service.mjs.map +0 -1
  845. package/dist/substrate/runtime/sui-execute/index.mjs.map +0 -1
  846. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +0 -1
  847. package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +0 -1
  848. package/dist/substrate/runtime/sui-move-build/index.mjs.map +0 -1
  849. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +0 -151
  850. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +0 -1
  851. package/dist/substrate/state-store.d.mts +0 -1
  852. /package/dist/orchestrators/{snapshot/pending-marker.d.mts → codegen/id-config.d.mts} +0 -0
  853. /package/dist/orchestrators/snapshot/{recover-pending.d.mts → interrupted-restore.d.mts} +0 -0
  854. /package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.d.mts +0 -0
  855. /package/dist/plugins/{postgres/snapshot.d.mts → sui/ledger/object-ref.d.mts} +0 -0
  856. /package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.d.mts +0 -0
  857. /package/dist/substrate/runtime/{state-store/index.d.mts → reconcile/graph.d.mts} +0 -0
  858. /package/dist/substrate/runtime/{cross-process/snapshot-reservation.d.mts → supervisor/contribution-dispatcher.d.mts} +0 -0
@@ -1,3 +1,4 @@
1
+ import { captureEntry, isSafeArchivePath, makeTarReaderState, processTarChunk, skipEntry, stopScan } from "../../substrate/runtime/tar/reader.mjs";
1
2
  import { isRestorableContainerImageName } from "./descriptor.mjs";
2
3
  import { Effect, FileSystem, Schema, Stream } from "effect";
3
4
  //#region src/orchestrators/snapshot/image-bundle-tags.ts
@@ -9,110 +10,37 @@ const failScan = (detail, cause) => new ImageBundleTagScanError({
9
10
  detail,
10
11
  ...cause === void 0 ? {} : { cause }
11
12
  });
12
- const TAR_BLOCK_SIZE = 512;
13
13
  const MAX_DOCKER_SAVE_MANIFEST_BYTES = 1024 * 1024;
14
- const bytesToString = (bytes) => {
15
- const nul = bytes.indexOf(0);
16
- const end = nul === -1 ? bytes.length : nul;
17
- return Buffer.from(bytes.subarray(0, end)).toString("utf8");
18
- };
19
- const concatBytes = (a, b) => {
20
- if (a.length === 0) return b;
21
- if (b.length === 0) return a;
22
- const out = new Uint8Array(a.length + b.length);
23
- out.set(a, 0);
24
- out.set(b, a.length);
25
- return out;
26
- };
27
- const consumeBytes = (buffer, count) => buffer.subarray(count);
28
- const isZeroBlock = (block) => block.every((byte) => byte === 0);
29
- const parseTarSize = (header) => {
30
- const raw = bytesToString(header.subarray(124, 136)).trim();
31
- if (raw === "") return 0;
32
- if (!/^[0-7]+$/.test(raw)) return null;
33
- const parsed = Number.parseInt(raw, 8);
34
- return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
35
- };
36
- const tarPathFromHeader = (header) => {
37
- const name = bytesToString(header.subarray(0, 100));
38
- const prefix = bytesToString(header.subarray(345, 500));
39
- return prefix === "" ? name : `${prefix}/${name}`;
40
- };
41
14
  const normalizedTarEntryPath = (path) => {
42
15
  let normalized = path;
43
16
  while (normalized.startsWith("./")) normalized = normalized.slice(2);
44
17
  return normalized;
45
18
  };
46
- const processDockerSaveMetadataChunk = (state, chunk) => {
47
- state.buffer = concatBytes(state.buffer, chunk);
48
- while (state.buffer.length > 0) {
49
- if (state.content !== null) {
50
- const content = state.content;
51
- const remaining = content.paddedSize - content.totalBytesRead;
52
- const take = Math.min(remaining, state.buffer.length);
53
- const contentTake = Math.max(0, Math.min(take, content.size - content.contentBytesRead));
54
- if (contentTake > 0) content.chunks.push(state.buffer.subarray(0, contentTake));
55
- state.content = {
56
- ...content,
57
- contentBytesRead: content.contentBytesRead + contentTake,
58
- totalBytesRead: content.totalBytesRead + take
59
- };
60
- state.buffer = consumeBytes(state.buffer, take);
61
- if (state.content.totalBytesRead === state.content.paddedSize) {
62
- const completed = state.content;
63
- state.content = null;
64
- const entryBytes = completed.chunks.length === 1 ? completed.chunks[0] : Buffer.concat(completed.chunks.map((entry) => Buffer.from(entry)));
65
- if (completed.entry === "manifest.json") {
66
- state.manifestBytes = entryBytes;
67
- return { done: true };
68
- }
69
- state.indexBytes = entryBytes;
19
+ const onMetadataEntry = (state, entry) => {
20
+ if (!isSafeArchivePath(entry.path)) return failScan(`docker save bundle has unsafe tar entry path: ${entry.path}`);
21
+ if ((entry.typeflag === "1" || entry.typeflag === "2") && !isSafeArchivePath(entry.linkPath)) return failScan(`docker save bundle has unsafe tar link target: ${entry.linkPath}`);
22
+ const entryPath = normalizedTarEntryPath(entry.path);
23
+ if (entryPath === "manifest.json" || entryPath === "index.json") {
24
+ if (entry.size > MAX_DOCKER_SAVE_MANIFEST_BYTES) return failScan(`docker save ${entryPath} is too large`);
25
+ if (entry.size === 0) {
26
+ if (entryPath === "manifest.json") {
27
+ state.manifestBytes = new Uint8Array(0);
28
+ state.done = true;
29
+ return stopScan();
70
30
  }
71
- continue;
72
- }
73
- if (state.skipRemaining > 0) {
74
- const take = Math.min(state.skipRemaining, state.buffer.length);
75
- state.skipRemaining -= take;
76
- state.buffer = consumeBytes(state.buffer, take);
77
- continue;
31
+ state.indexBytes = new Uint8Array(0);
32
+ return skipEntry();
78
33
  }
79
- if (state.buffer.length < TAR_BLOCK_SIZE) return { done: false };
80
- const header = state.buffer.subarray(0, TAR_BLOCK_SIZE);
81
- state.buffer = consumeBytes(state.buffer, TAR_BLOCK_SIZE);
82
- if (isZeroBlock(header)) continue;
83
- const size = parseTarSize(header);
84
- if (size === null) return {
85
- done: false,
86
- error: failScan("docker save bundle has invalid tar size")
87
- };
88
- const paddedSize = Math.ceil(size / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE;
89
- const entryPath = normalizedTarEntryPath(tarPathFromHeader(header));
90
- if (entryPath === "manifest.json" || entryPath === "index.json") {
91
- if (size > MAX_DOCKER_SAVE_MANIFEST_BYTES) return {
92
- done: false,
93
- error: failScan(`docker save ${entryPath} is too large`)
94
- };
95
- if (size === 0) {
96
- if (entryPath === "manifest.json") {
97
- state.manifestBytes = new Uint8Array(0);
98
- return { done: true };
99
- }
100
- state.indexBytes = new Uint8Array(0);
101
- continue;
102
- }
103
- state.content = {
104
- entry: entryPath,
105
- size,
106
- paddedSize,
107
- chunks: [],
108
- contentBytesRead: 0,
109
- totalBytesRead: 0
110
- };
111
- continue;
112
- }
113
- state.skipRemaining = paddedSize;
34
+ return captureEntry();
114
35
  }
115
- return { done: false };
36
+ return skipEntry();
37
+ };
38
+ const onMetadataContent = (state, entry, body) => {
39
+ if (normalizedTarEntryPath(entry.path) === "manifest.json") {
40
+ state.manifestBytes = body;
41
+ state.done = true;
42
+ } else state.indexBytes = body;
43
+ return null;
116
44
  };
117
45
  const parseDockerSaveManifestTags = (bytes, tarPath) => Effect.gen(function* () {
118
46
  const raw = yield* Effect.try({
@@ -172,16 +100,19 @@ const parseOciImageLayoutIndexTags = (bytes, tarPath) => Effect.gen(function* ()
172
100
  const readImageBundleTags = (fullTarPath, tarPath) => Effect.gen(function* () {
173
101
  const fs = yield* FileSystem.FileSystem;
174
102
  const state = {
175
- buffer: new Uint8Array(0),
176
- skipRemaining: 0,
177
103
  manifestBytes: null,
178
104
  indexBytes: null,
179
- content: null
105
+ done: false
180
106
  };
107
+ const reader = makeTarReaderState();
181
108
  yield* fs.stream(fullTarPath).pipe(Stream.takeUntilEffect((chunk) => {
182
- const result = processDockerSaveMetadataChunk(state, chunk);
183
- if (result.error !== void 0) return Effect.fail(result.error);
184
- return Effect.succeed(result.done);
109
+ const error = processTarChunk(reader, chunk, {
110
+ onEntry: (entry) => onMetadataEntry(state, entry),
111
+ onContent: (entry, body) => onMetadataContent(state, entry, body),
112
+ onExtendedError: (detail) => failScan(`docker save bundle ${detail}`)
113
+ });
114
+ if (error !== null) return Effect.fail(error);
115
+ return Effect.succeed(state.done);
185
116
  }), Stream.runDrain, Effect.catch((cause) => cause instanceof ImageBundleTagScanError ? Effect.fail(cause) : Effect.fail(failScan(`read docker save metadata from ${fullTarPath} failed`, cause))));
186
117
  if (state.manifestBytes !== null) return yield* parseDockerSaveManifestTags(state.manifestBytes, tarPath);
187
118
  if (state.indexBytes !== null) return yield* parseOciImageLayoutIndexTags(state.indexBytes, tarPath);
@@ -1 +1 @@
1
- {"version":3,"file":"image-bundle-tags.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/image-bundle-tags.ts"],"sourcesContent":["import { Effect, FileSystem, Schema, Stream } from 'effect';\n\nimport { isRestorableContainerImageName } from './descriptor.ts';\n\nexport class ImageBundleTagScanError extends Schema.TaggedErrorClass<ImageBundleTagScanError>()(\n\t'SnapshotImageBundleTagScanError',\n\t{\n\t\tdetail: Schema.String,\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\nconst failScan = (detail: string, cause?: unknown): ImageBundleTagScanError =>\n\tnew ImageBundleTagScanError({\n\t\tdetail,\n\t\t...(cause === undefined ? {} : { cause }),\n\t});\n\nconst TAR_BLOCK_SIZE = 512;\nconst MAX_DOCKER_SAVE_MANIFEST_BYTES = 1024 * 1024;\n\nconst bytesToString = (bytes: Uint8Array): string => {\n\tconst nul = bytes.indexOf(0);\n\tconst end = nul === -1 ? bytes.length : nul;\n\treturn Buffer.from(bytes.subarray(0, end)).toString('utf8');\n};\n\nconst concatBytes = (a: Uint8Array, b: Uint8Array): Uint8Array => {\n\tif (a.length === 0) return b;\n\tif (b.length === 0) return a;\n\tconst out = new Uint8Array(a.length + b.length);\n\tout.set(a, 0);\n\tout.set(b, a.length);\n\treturn out;\n};\n\nconst consumeBytes = (buffer: Uint8Array, count: number): Uint8Array => buffer.subarray(count);\n\nconst isZeroBlock = (block: Uint8Array): boolean => block.every((byte) => byte === 0);\n\nconst parseTarSize = (header: Uint8Array): number | null => {\n\tconst raw = bytesToString(header.subarray(124, 136)).trim();\n\tif (raw === '') return 0;\n\tif (!/^[0-7]+$/.test(raw)) return null;\n\tconst parsed = Number.parseInt(raw, 8);\n\treturn Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;\n};\n\nconst tarPathFromHeader = (header: Uint8Array): string => {\n\tconst name = bytesToString(header.subarray(0, 100));\n\tconst prefix = bytesToString(header.subarray(345, 500));\n\treturn prefix === '' ? name : `${prefix}/${name}`;\n};\n\nconst normalizedTarEntryPath = (path: string): string => {\n\tlet normalized = path;\n\twhile (normalized.startsWith('./')) normalized = normalized.slice(2);\n\treturn normalized;\n};\n\ntype ImageBundleMetadataEntry = 'manifest.json' | 'index.json';\n\ninterface DockerSaveMetadataTarState {\n\tbuffer: Uint8Array;\n\tskipRemaining: number;\n\tmanifestBytes: Uint8Array | null;\n\tindexBytes: Uint8Array | null;\n\tcontent: {\n\t\treadonly entry: ImageBundleMetadataEntry;\n\t\treadonly size: number;\n\t\treadonly paddedSize: number;\n\t\treadonly chunks: Array<Uint8Array>;\n\t\treadonly contentBytesRead: number;\n\t\treadonly totalBytesRead: number;\n\t} | null;\n}\n\nconst processDockerSaveMetadataChunk = (\n\tstate: DockerSaveMetadataTarState,\n\tchunk: Uint8Array,\n): { readonly done: boolean; readonly error?: ImageBundleTagScanError } => {\n\tstate.buffer = concatBytes(state.buffer, chunk);\n\twhile (state.buffer.length > 0) {\n\t\tif (state.content !== null) {\n\t\t\tconst content = state.content;\n\t\t\tconst remaining = content.paddedSize - content.totalBytesRead;\n\t\t\tconst take = Math.min(remaining, state.buffer.length);\n\t\t\tconst contentTake = Math.max(0, Math.min(take, content.size - content.contentBytesRead));\n\t\t\tif (contentTake > 0) {\n\t\t\t\tcontent.chunks.push(state.buffer.subarray(0, contentTake));\n\t\t\t}\n\t\t\tstate.content = {\n\t\t\t\t...content,\n\t\t\t\tcontentBytesRead: content.contentBytesRead + contentTake,\n\t\t\t\ttotalBytesRead: content.totalBytesRead + take,\n\t\t\t};\n\t\t\tstate.buffer = consumeBytes(state.buffer, take);\n\t\t\tif (state.content.totalBytesRead === state.content.paddedSize) {\n\t\t\t\tconst completed = state.content;\n\t\t\t\tstate.content = null;\n\t\t\t\tconst entryBytes =\n\t\t\t\t\tcompleted.chunks.length === 1\n\t\t\t\t\t\t? completed.chunks[0]!\n\t\t\t\t\t\t: Buffer.concat(completed.chunks.map((entry) => Buffer.from(entry)));\n\t\t\t\tif (completed.entry === 'manifest.json') {\n\t\t\t\t\tstate.manifestBytes = entryBytes;\n\t\t\t\t\treturn { done: true };\n\t\t\t\t}\n\t\t\t\tstate.indexBytes = entryBytes;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (state.skipRemaining > 0) {\n\t\t\tconst take = Math.min(state.skipRemaining, state.buffer.length);\n\t\t\tstate.skipRemaining -= take;\n\t\t\tstate.buffer = consumeBytes(state.buffer, take);\n\t\t\tcontinue;\n\t\t}\n\t\tif (state.buffer.length < TAR_BLOCK_SIZE) return { done: false };\n\t\tconst header = state.buffer.subarray(0, TAR_BLOCK_SIZE);\n\t\tstate.buffer = consumeBytes(state.buffer, TAR_BLOCK_SIZE);\n\t\tif (isZeroBlock(header)) continue;\n\t\tconst size = parseTarSize(header);\n\t\tif (size === null) {\n\t\t\treturn { done: false, error: failScan('docker save bundle has invalid tar size') };\n\t\t}\n\t\tconst paddedSize = Math.ceil(size / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE;\n\t\tconst entryPath = normalizedTarEntryPath(tarPathFromHeader(header));\n\t\tif (entryPath === 'manifest.json' || entryPath === 'index.json') {\n\t\t\tif (size > MAX_DOCKER_SAVE_MANIFEST_BYTES) {\n\t\t\t\treturn {\n\t\t\t\t\tdone: false,\n\t\t\t\t\terror: failScan(`docker save ${entryPath} is too large`),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (size === 0) {\n\t\t\t\tif (entryPath === 'manifest.json') {\n\t\t\t\t\tstate.manifestBytes = new Uint8Array(0);\n\t\t\t\t\treturn { done: true };\n\t\t\t\t}\n\t\t\t\tstate.indexBytes = new Uint8Array(0);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tstate.content = {\n\t\t\t\tentry: entryPath,\n\t\t\t\tsize,\n\t\t\t\tpaddedSize,\n\t\t\t\tchunks: [],\n\t\t\t\tcontentBytesRead: 0,\n\t\t\t\ttotalBytesRead: 0,\n\t\t\t};\n\t\t\tcontinue;\n\t\t}\n\t\tstate.skipRemaining = paddedSize;\n\t}\n\treturn { done: false };\n};\n\nconst parseDockerSaveManifestTags = (\n\tbytes: Uint8Array,\n\ttarPath: string,\n): Effect.Effect<ReadonlySet<string>, ImageBundleTagScanError> =>\n\tEffect.gen(function* () {\n\t\tconst raw = yield* Effect.try({\n\t\t\ttry: () => JSON.parse(Buffer.from(bytes).toString('utf8')) as unknown,\n\t\t\tcatch: (cause) =>\n\t\t\t\tfailScan(`docker save manifest.json in ${tarPath} is not valid JSON`, cause),\n\t\t});\n\t\tif (!Array.isArray(raw)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tfailScan(`docker save manifest.json in ${tarPath} is not an array`),\n\t\t\t);\n\t\t}\n\t\tconst tags = new Set<string>();\n\t\tfor (const [index, entry] of raw.entries()) {\n\t\t\tif (typeof entry !== 'object' || entry === null) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tfailScan(`docker save manifest entry ${index} in ${tarPath} is not an object`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst repoTags = (entry as { readonly RepoTags?: unknown }).RepoTags;\n\t\t\tif (repoTags === undefined || repoTags === null) continue;\n\t\t\tif (!Array.isArray(repoTags)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tfailScan(`docker save manifest entry ${index} in ${tarPath} has non-array RepoTags`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tfor (const tag of repoTags) {\n\t\t\t\tif (typeof tag !== 'string' || !isRestorableContainerImageName(tag)) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tfailScan(\n\t\t\t\t\t\t\t`docker save manifest entry ${index} in ${tarPath} has invalid RepoTag ${String(\n\t\t\t\t\t\t\t\ttag,\n\t\t\t\t\t\t\t)}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (tags.has(tag)) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tfailScan(`docker save manifest in ${tarPath} repeats RepoTag ${tag}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\ttags.add(tag);\n\t\t\t}\n\t\t}\n\t\treturn tags;\n\t});\n\nconst normalizeOciImageName = (value: string): string | null => {\n\tconst dockerLibraryPrefix = 'docker.io/library/';\n\tif (value.startsWith(dockerLibraryPrefix)) {\n\t\tconst localName = value.slice(dockerLibraryPrefix.length);\n\t\tif (localName.startsWith('devstack-snapshot:') && isRestorableContainerImageName(localName)) {\n\t\t\treturn localName;\n\t\t}\n\t\treturn null;\n\t}\n\tif (value.startsWith('devstack-snapshot:') && isRestorableContainerImageName(value)) {\n\t\treturn value;\n\t}\n\treturn null;\n};\n\nconst parseOciImageLayoutIndexTags = (\n\tbytes: Uint8Array,\n\ttarPath: string,\n): Effect.Effect<ReadonlySet<string>, ImageBundleTagScanError> =>\n\tEffect.gen(function* () {\n\t\tconst raw = yield* Effect.try({\n\t\t\ttry: () => JSON.parse(Buffer.from(bytes).toString('utf8')) as unknown,\n\t\t\tcatch: (cause) => failScan(`docker save index.json in ${tarPath} is not valid JSON`, cause),\n\t\t});\n\t\tif (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n\t\t\treturn yield* Effect.fail(failScan(`docker save index.json in ${tarPath} is not an object`));\n\t\t}\n\t\tconst manifests = (raw as { readonly manifests?: unknown }).manifests;\n\t\tif (!Array.isArray(manifests)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tfailScan(`docker save index.json in ${tarPath} has non-array manifests`),\n\t\t\t);\n\t\t}\n\t\tconst tags = new Set<string>();\n\t\tfor (const [index, entry] of manifests.entries()) {\n\t\t\tif (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tfailScan(`docker save index manifest ${index} in ${tarPath} is not an object`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst annotations = (entry as { readonly annotations?: unknown }).annotations;\n\t\t\tif (annotations === undefined || annotations === null) continue;\n\t\t\tif (typeof annotations !== 'object' || Array.isArray(annotations)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tfailScan(`docker save index manifest ${index} in ${tarPath} has invalid annotations`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst imageName = (annotations as { readonly ['io.containerd.image.name']?: unknown })[\n\t\t\t\t'io.containerd.image.name'\n\t\t\t];\n\t\t\tconst refName = (annotations as { readonly ['org.opencontainers.image.ref.name']?: unknown })[\n\t\t\t\t'org.opencontainers.image.ref.name'\n\t\t\t];\n\t\t\tfor (const value of [imageName, refName]) {\n\t\t\t\tif (typeof value !== 'string') continue;\n\t\t\t\tconst tag = normalizeOciImageName(value);\n\t\t\t\tif (tag === null) continue;\n\t\t\t\tif (tags.has(tag)) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tfailScan(`docker save index in ${tarPath} repeats RepoTag ${tag}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\ttags.add(tag);\n\t\t\t}\n\t\t}\n\t\treturn tags;\n\t});\n\nexport const readImageBundleTags = (\n\tfullTarPath: string,\n\ttarPath: string,\n): Effect.Effect<ReadonlySet<string>, ImageBundleTagScanError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst state: DockerSaveMetadataTarState = {\n\t\t\tbuffer: new Uint8Array(0),\n\t\t\tskipRemaining: 0,\n\t\t\tmanifestBytes: null,\n\t\t\tindexBytes: null,\n\t\t\tcontent: null,\n\t\t};\n\t\tyield* fs.stream(fullTarPath).pipe(\n\t\t\tStream.takeUntilEffect((chunk) => {\n\t\t\t\tconst result = processDockerSaveMetadataChunk(state, chunk);\n\t\t\t\tif (result.error !== undefined) return Effect.fail(result.error);\n\t\t\t\treturn Effect.succeed(result.done);\n\t\t\t}),\n\t\t\tStream.runDrain,\n\t\t\tEffect.catch((cause) =>\n\t\t\t\tcause instanceof ImageBundleTagScanError\n\t\t\t\t\t? Effect.fail(cause)\n\t\t\t\t\t: Effect.fail(failScan(`read docker save metadata from ${fullTarPath} failed`, cause)),\n\t\t\t),\n\t\t);\n\t\tif (state.manifestBytes !== null) {\n\t\t\treturn yield* parseDockerSaveManifestTags(state.manifestBytes, tarPath);\n\t\t}\n\t\tif (state.indexBytes !== null) {\n\t\t\treturn yield* parseOciImageLayoutIndexTags(state.indexBytes, tarPath);\n\t\t}\n\t\treturn yield* Effect.fail(\n\t\t\tfailScan(`docker save bundle ${tarPath} does not contain manifest.json or index.json`),\n\t\t);\n\t});\n\nexport const verifyImageBundleTags = (\n\ttarPath: string,\n\tactualTags: ReadonlySet<string>,\n\texpectedSnapshotTags: ReadonlyArray<string>,\n): Effect.Effect<void, ImageBundleTagScanError> => {\n\tconst expected = new Set<string>();\n\tconst repeatedExpected: string[] = [];\n\tfor (const tag of expectedSnapshotTags) {\n\t\tif (expected.has(tag)) {\n\t\t\trepeatedExpected.push(tag);\n\t\t} else {\n\t\t\texpected.add(tag);\n\t\t}\n\t}\n\tif (repeatedExpected.length > 0) {\n\t\treturn Effect.fail(\n\t\t\tfailScan(\n\t\t\t\t`docker save bundle ${tarPath} expected duplicate snapshot tags: ${repeatedExpected.join(\n\t\t\t\t\t', ',\n\t\t\t\t)}`,\n\t\t\t),\n\t\t);\n\t}\n\tconst missing = [...expected].filter((tag) => !actualTags.has(tag));\n\tconst unexpected = [...actualTags].filter((tag) => !expected.has(tag));\n\tif (missing.length === 0 && unexpected.length === 0) return Effect.void;\n\tconst parts: string[] = [];\n\tif (missing.length > 0) parts.push(`missing: ${missing.join(', ')}`);\n\tif (unexpected.length > 0) parts.push(`unexpected: ${unexpected.join(', ')}`);\n\treturn Effect.fail(\n\t\tfailScan(\n\t\t\t`docker save bundle ${tarPath} tags do not match snapshot metadata (${parts.join('; ')})`,\n\t\t),\n\t);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"image-bundle-tags.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/image-bundle-tags.ts"],"sourcesContent":["import { Effect, FileSystem, Schema, Stream } from 'effect';\n\nimport {\n\tcaptureEntry,\n\tisSafeArchivePath,\n\tmakeTarReaderState,\n\tprocessTarChunk,\n\tskipEntry,\n\tstopScan,\n\ttype TarEntry,\n\ttype TarEntryDirective,\n} from '../../substrate/runtime/tar/reader.ts';\nimport { isRestorableContainerImageName } from './descriptor.ts';\n\nexport class ImageBundleTagScanError extends Schema.TaggedErrorClass<ImageBundleTagScanError>()(\n\t'SnapshotImageBundleTagScanError',\n\t{\n\t\tdetail: Schema.String,\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\nconst failScan = (detail: string, cause?: unknown): ImageBundleTagScanError =>\n\tnew ImageBundleTagScanError({\n\t\tdetail,\n\t\t...(cause === undefined ? {} : { cause }),\n\t});\n\nconst MAX_DOCKER_SAVE_MANIFEST_BYTES = 1024 * 1024;\n\nconst normalizedTarEntryPath = (path: string): string => {\n\tlet normalized = path;\n\twhile (normalized.startsWith('./')) normalized = normalized.slice(2);\n\treturn normalized;\n};\n\n// Metadata-only scan state. The shared tar reader owns the block\n// discipline + pax/gnu handling; this state just collects the two\n// metadata entries we care about and a `done` flag so the stream drain\n// can short-circuit once manifest.json lands.\ninterface DockerSaveMetadataState {\n\tmanifestBytes: Uint8Array | null;\n\tindexBytes: Uint8Array | null;\n\tdone: boolean;\n}\n\n// Per-entry directive for the Docker-save / OCI-layout metadata scan.\n// Docker `save` bundles are a TRUSTED-extraction path: this scanner only\n// reads manifest.json / index.json metadata and never writes any file to\n// disk, so it does NOT untar bodies. `isSafeArchivePath` is applied for\n// defense-in-depth (a malicious linkpath / escaping entry path is\n// rejected) without tightening acceptance of valid OCI bundles — only\n// the manifest/index entries are captured; all other members (layer\n// blobs, configs) are skipped as before.\nconst onMetadataEntry = (\n\tstate: DockerSaveMetadataState,\n\tentry: TarEntry,\n): TarEntryDirective | ImageBundleTagScanError => {\n\tif (!isSafeArchivePath(entry.path)) {\n\t\treturn failScan(`docker save bundle has unsafe tar entry path: ${entry.path}`);\n\t}\n\tif ((entry.typeflag === '1' || entry.typeflag === '2') && !isSafeArchivePath(entry.linkPath)) {\n\t\treturn failScan(`docker save bundle has unsafe tar link target: ${entry.linkPath}`);\n\t}\n\tconst entryPath = normalizedTarEntryPath(entry.path);\n\tif (entryPath === 'manifest.json' || entryPath === 'index.json') {\n\t\tif (entry.size > MAX_DOCKER_SAVE_MANIFEST_BYTES) {\n\t\t\treturn failScan(`docker save ${entryPath} is too large`);\n\t\t}\n\t\tif (entry.size === 0) {\n\t\t\tif (entryPath === 'manifest.json') {\n\t\t\t\tstate.manifestBytes = new Uint8Array(0);\n\t\t\t\tstate.done = true;\n\t\t\t\treturn stopScan();\n\t\t\t}\n\t\t\tstate.indexBytes = new Uint8Array(0);\n\t\t\treturn skipEntry();\n\t\t}\n\t\treturn captureEntry();\n\t}\n\treturn skipEntry();\n};\n\nconst onMetadataContent = (\n\tstate: DockerSaveMetadataState,\n\tentry: TarEntry,\n\tbody: Uint8Array,\n): null => {\n\tconst entryPath = normalizedTarEntryPath(entry.path);\n\tif (entryPath === 'manifest.json') {\n\t\tstate.manifestBytes = body;\n\t\tstate.done = true;\n\t} else {\n\t\tstate.indexBytes = body;\n\t}\n\treturn null;\n};\n\nconst parseDockerSaveManifestTags = (\n\tbytes: Uint8Array,\n\ttarPath: string,\n): Effect.Effect<ReadonlySet<string>, ImageBundleTagScanError> =>\n\tEffect.gen(function* () {\n\t\tconst raw = yield* Effect.try({\n\t\t\ttry: () => JSON.parse(Buffer.from(bytes).toString('utf8')) as unknown,\n\t\t\tcatch: (cause) =>\n\t\t\t\tfailScan(`docker save manifest.json in ${tarPath} is not valid JSON`, cause),\n\t\t});\n\t\tif (!Array.isArray(raw)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tfailScan(`docker save manifest.json in ${tarPath} is not an array`),\n\t\t\t);\n\t\t}\n\t\tconst tags = new Set<string>();\n\t\tfor (const [index, entry] of raw.entries()) {\n\t\t\tif (typeof entry !== 'object' || entry === null) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tfailScan(`docker save manifest entry ${index} in ${tarPath} is not an object`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst repoTags = (entry as { readonly RepoTags?: unknown }).RepoTags;\n\t\t\tif (repoTags === undefined || repoTags === null) continue;\n\t\t\tif (!Array.isArray(repoTags)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tfailScan(`docker save manifest entry ${index} in ${tarPath} has non-array RepoTags`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tfor (const tag of repoTags) {\n\t\t\t\tif (typeof tag !== 'string' || !isRestorableContainerImageName(tag)) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tfailScan(\n\t\t\t\t\t\t\t`docker save manifest entry ${index} in ${tarPath} has invalid RepoTag ${String(\n\t\t\t\t\t\t\t\ttag,\n\t\t\t\t\t\t\t)}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (tags.has(tag)) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tfailScan(`docker save manifest in ${tarPath} repeats RepoTag ${tag}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\ttags.add(tag);\n\t\t\t}\n\t\t}\n\t\treturn tags;\n\t});\n\nconst normalizeOciImageName = (value: string): string | null => {\n\tconst dockerLibraryPrefix = 'docker.io/library/';\n\tif (value.startsWith(dockerLibraryPrefix)) {\n\t\tconst localName = value.slice(dockerLibraryPrefix.length);\n\t\tif (localName.startsWith('devstack-snapshot:') && isRestorableContainerImageName(localName)) {\n\t\t\treturn localName;\n\t\t}\n\t\treturn null;\n\t}\n\tif (value.startsWith('devstack-snapshot:') && isRestorableContainerImageName(value)) {\n\t\treturn value;\n\t}\n\treturn null;\n};\n\nconst parseOciImageLayoutIndexTags = (\n\tbytes: Uint8Array,\n\ttarPath: string,\n): Effect.Effect<ReadonlySet<string>, ImageBundleTagScanError> =>\n\tEffect.gen(function* () {\n\t\tconst raw = yield* Effect.try({\n\t\t\ttry: () => JSON.parse(Buffer.from(bytes).toString('utf8')) as unknown,\n\t\t\tcatch: (cause) => failScan(`docker save index.json in ${tarPath} is not valid JSON`, cause),\n\t\t});\n\t\tif (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n\t\t\treturn yield* Effect.fail(failScan(`docker save index.json in ${tarPath} is not an object`));\n\t\t}\n\t\tconst manifests = (raw as { readonly manifests?: unknown }).manifests;\n\t\tif (!Array.isArray(manifests)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tfailScan(`docker save index.json in ${tarPath} has non-array manifests`),\n\t\t\t);\n\t\t}\n\t\tconst tags = new Set<string>();\n\t\tfor (const [index, entry] of manifests.entries()) {\n\t\t\tif (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tfailScan(`docker save index manifest ${index} in ${tarPath} is not an object`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst annotations = (entry as { readonly annotations?: unknown }).annotations;\n\t\t\tif (annotations === undefined || annotations === null) continue;\n\t\t\tif (typeof annotations !== 'object' || Array.isArray(annotations)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tfailScan(`docker save index manifest ${index} in ${tarPath} has invalid annotations`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst imageName = (annotations as { readonly ['io.containerd.image.name']?: unknown })[\n\t\t\t\t'io.containerd.image.name'\n\t\t\t];\n\t\t\tconst refName = (annotations as { readonly ['org.opencontainers.image.ref.name']?: unknown })[\n\t\t\t\t'org.opencontainers.image.ref.name'\n\t\t\t];\n\t\t\tfor (const value of [imageName, refName]) {\n\t\t\t\tif (typeof value !== 'string') continue;\n\t\t\t\tconst tag = normalizeOciImageName(value);\n\t\t\t\tif (tag === null) continue;\n\t\t\t\tif (tags.has(tag)) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tfailScan(`docker save index in ${tarPath} repeats RepoTag ${tag}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\ttags.add(tag);\n\t\t\t}\n\t\t}\n\t\treturn tags;\n\t});\n\nexport const readImageBundleTags = (\n\tfullTarPath: string,\n\ttarPath: string,\n): Effect.Effect<ReadonlySet<string>, ImageBundleTagScanError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst state: DockerSaveMetadataState = {\n\t\t\tmanifestBytes: null,\n\t\t\tindexBytes: null,\n\t\t\tdone: false,\n\t\t};\n\t\tconst reader = makeTarReaderState();\n\t\tyield* fs.stream(fullTarPath).pipe(\n\t\t\tStream.takeUntilEffect((chunk) => {\n\t\t\t\tconst error = processTarChunk(reader, chunk, {\n\t\t\t\t\tonEntry: (entry) => onMetadataEntry(state, entry),\n\t\t\t\t\tonContent: (entry, body) => onMetadataContent(state, entry, body),\n\t\t\t\t\tonExtendedError: (detail) => failScan(`docker save bundle ${detail}`),\n\t\t\t\t});\n\t\t\t\tif (error !== null) return Effect.fail(error);\n\t\t\t\treturn Effect.succeed(state.done);\n\t\t\t}),\n\t\t\tStream.runDrain,\n\t\t\tEffect.catch((cause) =>\n\t\t\t\tcause instanceof ImageBundleTagScanError\n\t\t\t\t\t? Effect.fail(cause)\n\t\t\t\t\t: Effect.fail(failScan(`read docker save metadata from ${fullTarPath} failed`, cause)),\n\t\t\t),\n\t\t);\n\t\tif (state.manifestBytes !== null) {\n\t\t\treturn yield* parseDockerSaveManifestTags(state.manifestBytes, tarPath);\n\t\t}\n\t\tif (state.indexBytes !== null) {\n\t\t\treturn yield* parseOciImageLayoutIndexTags(state.indexBytes, tarPath);\n\t\t}\n\t\treturn yield* Effect.fail(\n\t\t\tfailScan(`docker save bundle ${tarPath} does not contain manifest.json or index.json`),\n\t\t);\n\t});\n\nexport const verifyImageBundleTags = (\n\ttarPath: string,\n\tactualTags: ReadonlySet<string>,\n\texpectedSnapshotTags: ReadonlyArray<string>,\n): Effect.Effect<void, ImageBundleTagScanError> => {\n\tconst expected = new Set<string>();\n\tconst repeatedExpected: string[] = [];\n\tfor (const tag of expectedSnapshotTags) {\n\t\tif (expected.has(tag)) {\n\t\t\trepeatedExpected.push(tag);\n\t\t} else {\n\t\t\texpected.add(tag);\n\t\t}\n\t}\n\tif (repeatedExpected.length > 0) {\n\t\treturn Effect.fail(\n\t\t\tfailScan(\n\t\t\t\t`docker save bundle ${tarPath} expected duplicate snapshot tags: ${repeatedExpected.join(\n\t\t\t\t\t', ',\n\t\t\t\t)}`,\n\t\t\t),\n\t\t);\n\t}\n\tconst missing = [...expected].filter((tag) => !actualTags.has(tag));\n\tconst unexpected = [...actualTags].filter((tag) => !expected.has(tag));\n\tif (missing.length === 0 && unexpected.length === 0) return Effect.void;\n\tconst parts: string[] = [];\n\tif (missing.length > 0) parts.push(`missing: ${missing.join(', ')}`);\n\tif (unexpected.length > 0) parts.push(`unexpected: ${unexpected.join(', ')}`);\n\treturn Effect.fail(\n\t\tfailScan(\n\t\t\t`docker save bundle ${tarPath} tags do not match snapshot metadata (${parts.join('; ')})`,\n\t\t),\n\t);\n};\n"],"mappings":"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"}
@@ -2,12 +2,9 @@ import "./descriptor.mjs";
2
2
  import "./wipe.mjs";
3
3
  import "./integrity.mjs";
4
4
  import "./identity-guard.mjs";
5
- import "./state-document.mjs";
6
5
  import "./capture.mjs";
7
6
  import "./prune.mjs";
8
- import "./pending-marker.mjs";
9
- import "./recover-pending.mjs";
7
+ import "./interrupted-restore.mjs";
10
8
  import "./restore.mjs";
11
9
  import "./service.mjs";
12
- import "./capture-command.mjs";
13
10
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"integrity.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/integrity.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport { Effect, FileSystem, Schema, Stream } from 'effect';\n\nimport { decodeUnknown, parseJsonText } from '../../substrate/runtime/runtime-decode.ts';\nimport {\n\tIntegrityFileSchema,\n\tSNAPSHOT_INTEGRITY_VERSION,\n\tSnapshotLayout,\n\tisSafeSnapshotRelativePath,\n\ttype IntegrityFile,\n} from './descriptor.ts';\n\nconst SHA256_HEX = /^[a-f0-9]{64}$/;\n\n/** Tagged failure raised by snapshot integrity helpers. `kind`\n * discriminates the failure class so downstream phase classifiers can\n * branch by tag, not by message substring. */\nexport class SnapshotIntegrityError extends Schema.TaggedErrorClass<SnapshotIntegrityError>()(\n\t'SnapshotIntegrityError',\n\t{\n\t\t/**\n\t\t * - `'missing'` — `integrity.json` file absent.\n\t\t * - `'corrupt'` — present but unparseable / schema-decode failure.\n\t\t * - `'mismatch'` — present, parses, but a file's hash does not\n\t\t * match its recorded value (or the file list disagrees).\n\t\t * - `'walk-failed'` — directory walk failure (filesystem error\n\t\t * during enumeration, hashing, or write of the integrity doc).\n\t\t */\n\t\tkind: Schema.Literals(['missing', 'corrupt', 'mismatch', 'walk-failed']),\n\t\tdetail: Schema.String,\n\t\tpath: Schema.optional(Schema.String),\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\nconst failWalk = (\n\tdetail: string,\n\tpath?: string,\n\tcause?: unknown,\n): Effect.Effect<never, SnapshotIntegrityError> =>\n\tEffect.fail(new SnapshotIntegrityError({ kind: 'walk-failed', detail, path, cause }));\n\nconst failCorrupt = (\n\tdetail: string,\n\tpath?: string,\n\tcause?: unknown,\n): Effect.Effect<never, SnapshotIntegrityError> =>\n\tEffect.fail(new SnapshotIntegrityError({ kind: 'corrupt', detail, path, cause }));\n\nconst failMismatch = (\n\tdetail: string,\n\tpath?: string,\n): Effect.Effect<never, SnapshotIntegrityError> =>\n\tEffect.fail(new SnapshotIntegrityError({ kind: 'mismatch', detail, path }));\n\nconst hashFile = (\n\tpath: string,\n): Effect.Effect<string, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst hash = createHash('sha256');\n\t\tyield* Stream.runForEach(fs.stream(path), (chunk) =>\n\t\t\tEffect.sync(() => {\n\t\t\t\thash.update(chunk);\n\t\t\t}),\n\t\t).pipe(Effect.catch((cause) => failWalk(`hash ${path} failed`, path, cause)));\n\t\treturn hash.digest('hex');\n\t});\n\nconst collectArtifactFiles = (\n\troot: string,\n): Effect.Effect<ReadonlyArray<string>, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst files: string[] = [];\n\t\tconst walk = (dir: string, prefix: string): Effect.Effect<void, SnapshotIntegrityError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst names = yield* fs\n\t\t\t\t\t.readDirectory(dir)\n\t\t\t\t\t.pipe(Effect.catch((cause) => failWalk(`readDirectory(${dir}) failed`, dir, cause)));\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tconst relPath = prefix === '' ? name : `${prefix}/${name}`;\n\t\t\t\t\tif (!isSafeSnapshotRelativePath(relPath)) {\n\t\t\t\t\t\treturn yield* failWalk(`unsafe artifact relative path: ${relPath}`, relPath);\n\t\t\t\t\t}\n\t\t\t\t\tconst absPath = `${dir}/${name}`;\n\t\t\t\t\tconst stat = yield* fs\n\t\t\t\t\t\t.stat(absPath)\n\t\t\t\t\t\t.pipe(Effect.catch((cause) => failWalk(`stat(${absPath}) failed`, absPath, cause)));\n\t\t\t\t\tif (stat.type === 'Directory') {\n\t\t\t\t\t\tyield* walk(absPath, relPath);\n\t\t\t\t\t} else if (stat.type === 'File') {\n\t\t\t\t\t\tif (relPath !== SnapshotLayout.integrityFile) {\n\t\t\t\t\t\t\tfiles.push(relPath);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn yield* failWalk(\n\t\t\t\t\t\t\t`snapshot artifact path is not a regular file: ${relPath}`,\n\t\t\t\t\t\t\tabsPath,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\tyield* walk(root, '');\n\t\treturn files.sort((a, b) => a.localeCompare(b));\n\t});\n\nexport const computeArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<IntegrityFile, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst hashes: Record<string, string> = {};\n\t\tfor (const relPath of yield* collectArtifactFiles(root)) {\n\t\t\thashes[relPath] = yield* hashFile(`${root}/${relPath}`);\n\t\t}\n\t\treturn { version: SNAPSHOT_INTEGRITY_VERSION, hashes };\n\t});\n\nexport const writeArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<IntegrityFile, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst integrity = yield* computeArtifactIntegrity(root);\n\t\tconst path = `${root}/${SnapshotLayout.integrityFile}`;\n\t\tyield* fs\n\t\t\t.writeFileString(path, JSON.stringify(integrity, null, 2))\n\t\t\t.pipe(\n\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\tfailWalk(`write ${SnapshotLayout.integrityFile} failed`, path, cause),\n\t\t\t\t),\n\t\t\t);\n\t\treturn integrity;\n\t});\n\nconst readIntegrity = (\n\troot: string,\n): Effect.Effect<IntegrityFile, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst path = `${root}/${SnapshotLayout.integrityFile}`;\n\t\tconst exists = yield* fs.exists(path).pipe(Effect.catch(() => Effect.succeed(false)));\n\t\tif (!exists) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew SnapshotIntegrityError({\n\t\t\t\t\tkind: 'missing',\n\t\t\t\t\tdetail: `snapshot integrity file absent at ${path}`,\n\t\t\t\t\tpath,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst text = yield* fs\n\t\t\t.readFileString(path)\n\t\t\t.pipe(Effect.catch((cause) => failWalk(`read ${path} failed`, path, cause)));\n\t\tconst raw = yield* parseJsonText(text, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) =>\n\t\t\t\tnew SnapshotIntegrityError({\n\t\t\t\t\tkind: 'corrupt',\n\t\t\t\t\tdetail: `${path} is not valid JSON`,\n\t\t\t\t\tpath,\n\t\t\t\t\tcause: issue.cause,\n\t\t\t\t}),\n\t\t});\n\t\tconst decoded = yield* decodeUnknown(IntegrityFileSchema, raw, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) =>\n\t\t\t\tnew SnapshotIntegrityError({\n\t\t\t\t\tkind: 'corrupt',\n\t\t\t\t\tdetail: `${path} failed schema decode`,\n\t\t\t\t\tpath,\n\t\t\t\t\tcause: issue.cause,\n\t\t\t\t}),\n\t\t});\n\t\tfor (const [relPath, digest] of Object.entries(decoded.hashes)) {\n\t\t\tif (!isSafeSnapshotRelativePath(relPath) || relPath === SnapshotLayout.integrityFile) {\n\t\t\t\treturn yield* failCorrupt(`unsafe integrity path: ${relPath}`, path);\n\t\t\t}\n\t\t\tif (!SHA256_HEX.test(digest)) {\n\t\t\t\treturn yield* failCorrupt(`invalid sha256 digest for ${relPath}`, path);\n\t\t\t}\n\t\t}\n\t\treturn decoded;\n\t});\n\nexport const verifyArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<void, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst expected = yield* readIntegrity(root);\n\t\tconst actual = yield* computeArtifactIntegrity(root);\n\t\tconst expectedPaths = Object.keys(expected.hashes).sort((a, b) => a.localeCompare(b));\n\t\tconst actualPaths = Object.keys(actual.hashes).sort((a, b) => a.localeCompare(b));\n\t\tif (expectedPaths.join('\\n') !== actualPaths.join('\\n')) {\n\t\t\treturn yield* failMismatch('snapshot integrity file list does not match artifact contents');\n\t\t}\n\t\tfor (const relPath of expectedPaths) {\n\t\t\tif (expected.hashes[relPath] !== actual.hashes[relPath]) {\n\t\t\t\treturn yield* failMismatch(`snapshot integrity mismatch for ${relPath}`, relPath);\n\t\t\t}\n\t\t}\n\t});\n"],"mappings":"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"}
1
+ {"version":3,"file":"integrity.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/integrity.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport { Effect, FileSystem, Schema, Stream } from 'effect';\n\nimport { decodeUnknown, parseJsonText } from '../../substrate/runtime/runtime-decode.ts';\nimport {\n\tIntegrityFileSchema,\n\tSNAPSHOT_INTEGRITY_VERSION,\n\tSnapshotLayout,\n\tisSafeSnapshotRelativePath,\n\ttype IntegrityFile,\n} from './descriptor.ts';\n\nconst SHA256_HEX = /^[a-f0-9]{64}$/;\n\n/** Tagged failure raised by snapshot integrity helpers. `kind`\n * discriminates the failure class so downstream phase classifiers can\n * branch by tag, not by message substring. */\nexport class SnapshotIntegrityError extends Schema.TaggedErrorClass<SnapshotIntegrityError>()(\n\t'SnapshotIntegrityError',\n\t{\n\t\t/**\n\t\t * - `'missing'` — `integrity.json` file absent.\n\t\t * - `'corrupt'` — present but unparseable / schema-decode failure.\n\t\t * - `'mismatch'` — present, parses, but a file's hash does not\n\t\t * match its recorded value (or the file list disagrees).\n\t\t * - `'walk-failed'` — directory walk failure (filesystem error\n\t\t * during enumeration, hashing, or write of the integrity doc).\n\t\t */\n\t\tkind: Schema.Literals(['missing', 'corrupt', 'mismatch', 'walk-failed']),\n\t\tdetail: Schema.String,\n\t\tpath: Schema.optional(Schema.String),\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\nconst failWalk = (\n\tdetail: string,\n\tpath?: string,\n\tcause?: unknown,\n): Effect.Effect<never, SnapshotIntegrityError> =>\n\tEffect.fail(new SnapshotIntegrityError({ kind: 'walk-failed', detail, path, cause }));\n\nconst failCorrupt = (\n\tdetail: string,\n\tpath?: string,\n\tcause?: unknown,\n): Effect.Effect<never, SnapshotIntegrityError> =>\n\tEffect.fail(new SnapshotIntegrityError({ kind: 'corrupt', detail, path, cause }));\n\nconst failMismatch = (\n\tdetail: string,\n\tpath?: string,\n): Effect.Effect<never, SnapshotIntegrityError> =>\n\tEffect.fail(new SnapshotIntegrityError({ kind: 'mismatch', detail, path }));\n\nconst hashFile = (\n\tpath: string,\n): Effect.Effect<string, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst hash = createHash('sha256');\n\t\tyield* Stream.runForEach(fs.stream(path), (chunk) =>\n\t\t\tEffect.sync(() => {\n\t\t\t\thash.update(chunk);\n\t\t\t}),\n\t\t).pipe(Effect.catch((cause) => failWalk(`hash ${path} failed`, path, cause)));\n\t\treturn hash.digest('hex');\n\t});\n\nconst collectArtifactFiles = (\n\troot: string,\n): Effect.Effect<ReadonlyArray<string>, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst files: string[] = [];\n\t\tconst walk = (dir: string, prefix: string): Effect.Effect<void, SnapshotIntegrityError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst names = yield* fs\n\t\t\t\t\t.readDirectory(dir)\n\t\t\t\t\t.pipe(Effect.catch((cause) => failWalk(`readDirectory(${dir}) failed`, dir, cause)));\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tconst relPath = prefix === '' ? name : `${prefix}/${name}`;\n\t\t\t\t\tif (!isSafeSnapshotRelativePath(relPath)) {\n\t\t\t\t\t\treturn yield* failWalk(`unsafe artifact relative path: ${relPath}`, relPath);\n\t\t\t\t\t}\n\t\t\t\t\tconst absPath = `${dir}/${name}`;\n\t\t\t\t\tconst stat = yield* fs\n\t\t\t\t\t\t.stat(absPath)\n\t\t\t\t\t\t.pipe(Effect.catch((cause) => failWalk(`stat(${absPath}) failed`, absPath, cause)));\n\t\t\t\t\tif (stat.type === 'Directory') {\n\t\t\t\t\t\tyield* walk(absPath, relPath);\n\t\t\t\t\t} else if (stat.type === 'File') {\n\t\t\t\t\t\tif (relPath !== SnapshotLayout.integrityFile) {\n\t\t\t\t\t\t\tfiles.push(relPath);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn yield* failWalk(\n\t\t\t\t\t\t\t`snapshot artifact path is not a regular file: ${relPath}`,\n\t\t\t\t\t\t\tabsPath,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\tyield* walk(root, '');\n\t\treturn files.sort((a, b) => a.localeCompare(b));\n\t});\n\nexport const computeArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<IntegrityFile, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst hashes: Record<string, string> = {};\n\t\tfor (const relPath of yield* collectArtifactFiles(root)) {\n\t\t\thashes[relPath] = yield* hashFile(`${root}/${relPath}`);\n\t\t}\n\t\treturn { version: SNAPSHOT_INTEGRITY_VERSION, hashes };\n\t});\n\nexport const writeArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<IntegrityFile, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst integrity = yield* computeArtifactIntegrity(root);\n\t\tconst path = `${root}/${SnapshotLayout.integrityFile}`;\n\t\tyield* fs\n\t\t\t.writeFileString(path, JSON.stringify(integrity, null, 2))\n\t\t\t.pipe(\n\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\tfailWalk(`write ${SnapshotLayout.integrityFile} failed`, path, cause),\n\t\t\t\t),\n\t\t\t);\n\t\treturn integrity;\n\t});\n\nconst readIntegrity = (\n\troot: string,\n): Effect.Effect<IntegrityFile, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst path = `${root}/${SnapshotLayout.integrityFile}`;\n\t\tconst exists = yield* fs.exists(path).pipe(Effect.catch(() => Effect.succeed(false)));\n\t\tif (!exists) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew SnapshotIntegrityError({\n\t\t\t\t\tkind: 'missing',\n\t\t\t\t\tdetail: `snapshot integrity file absent at ${path}`,\n\t\t\t\t\tpath,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst text = yield* fs\n\t\t\t.readFileString(path)\n\t\t\t.pipe(Effect.catch((cause) => failWalk(`read ${path} failed`, path, cause)));\n\t\tconst raw = yield* parseJsonText(text, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) =>\n\t\t\t\tnew SnapshotIntegrityError({\n\t\t\t\t\tkind: 'corrupt',\n\t\t\t\t\tdetail: `${path} is not valid JSON`,\n\t\t\t\t\tpath,\n\t\t\t\t\tcause: issue.cause,\n\t\t\t\t}),\n\t\t});\n\t\tconst decoded = yield* decodeUnknown(IntegrityFileSchema, raw, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) =>\n\t\t\t\tnew SnapshotIntegrityError({\n\t\t\t\t\tkind: 'corrupt',\n\t\t\t\t\tdetail: `${path} failed schema decode`,\n\t\t\t\t\tpath,\n\t\t\t\t\tcause: issue.cause,\n\t\t\t\t}),\n\t\t});\n\t\tfor (const [relPath, digest] of Object.entries(decoded.hashes)) {\n\t\t\tif (!isSafeSnapshotRelativePath(relPath) || relPath === SnapshotLayout.integrityFile) {\n\t\t\t\treturn yield* failCorrupt(`unsafe integrity path: ${relPath}`, path);\n\t\t\t}\n\t\t\tif (!SHA256_HEX.test(digest)) {\n\t\t\t\treturn yield* failCorrupt(`invalid sha256 digest for ${relPath}`, path);\n\t\t\t}\n\t\t}\n\t\treturn decoded;\n\t});\n\nexport const verifyArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<void, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst expected = yield* readIntegrity(root);\n\t\tconst actual = yield* computeArtifactIntegrity(root);\n\t\tconst expectedPaths = Object.keys(expected.hashes).sort((a, b) => a.localeCompare(b));\n\t\tconst actualPaths = Object.keys(actual.hashes).sort((a, b) => a.localeCompare(b));\n\t\tif (expectedPaths.join('\\n') !== actualPaths.join('\\n')) {\n\t\t\treturn yield* failMismatch('snapshot integrity file list does not match artifact contents');\n\t\t}\n\t\tfor (const relPath of expectedPaths) {\n\t\t\tif (expected.hashes[relPath] !== actual.hashes[relPath]) {\n\t\t\t\treturn yield* failMismatch(`snapshot integrity mismatch for ${relPath}`, relPath);\n\t\t\t}\n\t\t}\n\t});\n"],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ import { decodeUnknown, parseJsonText } from "../../substrate/runtime/runtime-decode.mjs";
2
+ import { versionedDocSchema } from "../../substrate/versioned-doc-schema.mjs";
3
+ import { parseSnapshotId } from "./descriptor.mjs";
4
+ import { Effect, FileSystem, Schema } from "effect";
5
+ //#region src/orchestrators/snapshot/interrupted-restore.ts
6
+ /** Fixed hidden filename for the interrupted-restore sentinel. Written at
7
+ * the ROOT of the staged tree (and therefore at the root of the live
8
+ * runtime stack root after the swap lands). Hidden + dotted so it never
9
+ * collides with a plugin's runtime subtree and is visually distinct from
10
+ * the operator's content. */
11
+ const RESTORE_SENTINEL_FILE_NAME = ".devstack-restore-in-progress.json";
12
+ /** The interrupted-restore sentinel record. Carries exactly what
13
+ * re-running the restore needs: the snapshot id (the orchestrator
14
+ * derives the artifact dir from it) plus the resolved artifact dir as a
15
+ * cross-check / diagnostic (the snapshots subtree rides the same atomic
16
+ * swap, so the artifact at this path is preserved across the restore). */
17
+ const RestoreSentinelSchema = versionedDocSchema(1, {
18
+ snapshotId: Schema.String,
19
+ artifactDir: Schema.String
20
+ });
21
+ const sentinelPath = (root) => `${root}/${RESTORE_SENTINEL_FILE_NAME}`;
22
+ /** Write the sentinel JSON into the STAGED tree root at the fixed hidden
23
+ * path so it rides the atomic stage-and-swap into the live runtime root.
24
+ * Called from `runRestore` immediately before `executeFsPlan` publishes
25
+ * the swap. Encode validates the payload against the schema (the
26
+ * `version` discriminator is supplied here). */
27
+ const writeRestoreSentinel = (stagedRoot, payload) => Effect.gen(function* () {
28
+ const fs = yield* FileSystem.FileSystem;
29
+ const doc = {
30
+ version: 1,
31
+ snapshotId: payload.snapshotId,
32
+ artifactDir: payload.artifactDir
33
+ };
34
+ yield* fs.writeFileString(sentinelPath(stagedRoot), JSON.stringify(doc)).pipe(Effect.catch((cause) => Effect.logWarning(`restore sentinel write failed at ${sentinelPath(stagedRoot)} — a hard kill mid-promotion would be unrecoverable for snapshot ${payload.snapshotId}: ${String(cause)}`)));
35
+ });
36
+ /** Read the sentinel from the LIVE runtime root. Returns `null` when the
37
+ * sentinel is absent (the common case — no interrupted restore) or
38
+ * present-but-unparseable (logged, then treated as absent so a corrupt
39
+ * breadcrumb cannot wedge boot; the operator can recover manually). */
40
+ const readRestoreSentinel = (liveRoot) => Effect.gen(function* () {
41
+ const fs = yield* FileSystem.FileSystem;
42
+ const path = sentinelPath(liveRoot);
43
+ if (!(yield* fs.exists(path).pipe(Effect.catch(() => Effect.succeed(false))))) return null;
44
+ const readExit = yield* Effect.exit(fs.readFileString(path));
45
+ if (readExit._tag === "Failure") {
46
+ yield* Effect.logWarning(`restore sentinel read failed at ${path}; treating as absent (no auto-recovery)`);
47
+ return null;
48
+ }
49
+ const decodeExit = yield* Effect.exit(parseJsonText(readExit.value, {
50
+ source: path,
51
+ mkError: (issue) => issue
52
+ }).pipe(Effect.flatMap((raw) => decodeUnknown(RestoreSentinelSchema, raw, {
53
+ source: path,
54
+ mkError: (issue) => issue
55
+ }))));
56
+ if (decodeExit._tag === "Failure") {
57
+ yield* Effect.logWarning(`restore sentinel at ${path} is unparseable; treating as absent (no auto-recovery)`);
58
+ return null;
59
+ }
60
+ return decodeExit.value;
61
+ });
62
+ /** Remove the sentinel from the LIVE runtime root. Idempotent
63
+ * (`force: true`) — a missing sentinel is a no-op. Called on the restore
64
+ * success path the instant the promotion+removal handoff completes, and
65
+ * again after a successful auto-recovery re-run. */
66
+ const clearRestoreSentinel = (liveRoot) => Effect.gen(function* () {
67
+ yield* (yield* FileSystem.FileSystem).remove(sentinelPath(liveRoot), { force: true }).pipe(Effect.ignore);
68
+ });
69
+ /**
70
+ * Boot-time auto-recovery for a restore interrupted by a hard kill /
71
+ * power-loss between the atomic swap and the end of the image-promotion
72
+ * handoff. Reads the sentinel from the live runtime root; when present,
73
+ * logs a clear "resuming interrupted restore <id>" and re-invokes the
74
+ * restore for that snapshot. No-op when the sentinel is absent.
75
+ *
76
+ * Wired at boot BEFORE the first plugin acquire (the `beforeInitialAcquire`
77
+ * hook), so a half-promoted image set is reconciled before any L2 lookup
78
+ * observes the runtime root.
79
+ *
80
+ * ## Loop-safety
81
+ *
82
+ * - A CLEAN restore cleared the sentinel on its success path, so a clean
83
+ * boot reads `null` and returns immediately — boot never loops.
84
+ * - A SUCCESSFUL recovery clears the sentinel after the re-run returns,
85
+ * so the next boot is clean.
86
+ * - A STILL-FAILING artifact (re-run fails) DELIBERATELY leaves the
87
+ * sentinel in place so the next boot retries. The failure is logged
88
+ * and SWALLOWED — boot continues rather than wedging — but the
89
+ * sentinel persists as the durable retry breadcrumb. This is the
90
+ * intended trade: a genuinely corrupt artifact will re-fail every boot
91
+ * (surfaced by the warning + the restore's own typed phase error in
92
+ * the logs) rather than silently dropping the recovery intent. An
93
+ * operator resolves it by repairing/removing the artifact or clearing
94
+ * the sentinel by hand.
95
+ */
96
+ const recoverInterruptedRestore = (deps) => Effect.gen(function* () {
97
+ const sentinel = yield* readRestoreSentinel(deps.liveRoot);
98
+ if (sentinel === null) return;
99
+ const snapshotId = parseSnapshotId(sentinel.snapshotId);
100
+ if (snapshotId === null) {
101
+ yield* Effect.logWarning(`interrupted-restore sentinel carries an unsafe snapshot id (${sentinel.snapshotId}); clearing it — no auto-recovery possible`);
102
+ yield* clearRestoreSentinel(deps.liveRoot);
103
+ return;
104
+ }
105
+ yield* Effect.logInfo(`resuming interrupted restore ${snapshotId} (sentinel at ${deps.liveRoot}/${RESTORE_SENTINEL_FILE_NAME})`).pipe(Effect.annotateLogs({ "devstack.snapshot.id": snapshotId }));
106
+ if ((yield* Effect.exit(deps.restoreSnapshot(snapshotId)))._tag === "Failure") {
107
+ yield* Effect.logWarning(`interrupted-restore recovery for ${snapshotId} did not complete; leaving sentinel for the next boot to retry. Investigate the snapshot artifact if this recurs.`);
108
+ return;
109
+ }
110
+ yield* clearRestoreSentinel(deps.liveRoot);
111
+ yield* Effect.logInfo(`interrupted restore ${snapshotId} recovered`).pipe(Effect.annotateLogs({ "devstack.snapshot.id": snapshotId }));
112
+ });
113
+ //#endregion
114
+ export { RESTORE_SENTINEL_FILE_NAME, RestoreSentinelSchema, clearRestoreSentinel, readRestoreSentinel, recoverInterruptedRestore, writeRestoreSentinel };
115
+
116
+ //# sourceMappingURL=interrupted-restore.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interrupted-restore.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/interrupted-restore.ts"],"sourcesContent":["// Interrupted-restore sentinel + boot-time auto-recovery.\n//\n// THE GAP this closes:\n//\n// `runRestore` (restore.ts) does the staged→target image promotion\n// (`promoteStagedImages`) + captured-container removal inside an\n// `Effect.uninterruptible` block AFTER the atomic stage-and-swap has\n// already published the new runtime root. `Effect.uninterruptible`\n// guards against an Effect-level interrupt — it does NOT survive a\n// SIGKILL / power-loss. A hard kill mid-promotion therefore leaves the\n// swap published (new host-tree + control files live) but the images\n// only PARTIALLY promoted (a prefix re-tagged to their target names,\n// the rest still at staging tags or gone), and the scope finalizer\n// never runs to clean up. On the next boot there was previously NO\n// breadcrumb, so the half-promotion was unrecoverable without manual\n// `docker tag` surgery.\n//\n// THE FIX (this module):\n//\n// A typed sentinel is written into the STAGED tree IMMEDIATELY BEFORE\n// the atomic swap, so publishing the swap atomically publishes the\n// sentinel too (it rides the same `rename(staging → runtimeStackRoot)`;\n// see `fs-plan.ts` `runSwapTree` → `stageAndSwap` step 3). On the\n// success path restore CLEARS the sentinel the instant the\n// promotion+removal handoff completes. So:\n//\n// - a CLEAN restore leaves NO sentinel (boot never loops);\n// - a hard kill BEFORE the swap landed leaves no published sentinel\n// (the staging tree was discarded), and the live root is untouched\n// — nothing to recover;\n// - a hard kill AFTER the swap but mid-promotion leaves the sentinel\n// LIVE, so the next boot's `recoverInterruptedRestore` re-runs the\n// restore for that snapshot id.\n//\n// Recovery is idempotent because the on-disk snapshot artifact is\n// preserved across restores (it lives under `<stackRoot>/snapshots/`,\n// which is on the restore preserve list) and survives a `docker system\n// prune` (re-staging re-loads the image bundle from the artifact tar).\n// Re-running `runRestore` re-stages, re-promotes, and re-removes from\n// scratch; a still-failing artifact simply leaves the sentinel in place\n// for the next boot to retry (documented loop-safety below).\n\nimport { Effect, FileSystem, Schema } from 'effect';\n\nimport { decodeUnknown, parseJsonText } from '../../substrate/runtime/runtime-decode.ts';\nimport { versionedDocSchema } from '../../substrate/versioned-doc-schema.ts';\nimport { parseSnapshotId, type SnapshotId } from './descriptor.ts';\n\n// -----------------------------------------------------------------------------\n// Sentinel layout + schema\n// -----------------------------------------------------------------------------\n\n/** Fixed hidden filename for the interrupted-restore sentinel. Written at\n * the ROOT of the staged tree (and therefore at the root of the live\n * runtime stack root after the swap lands). Hidden + dotted so it never\n * collides with a plugin's runtime subtree and is visually distinct from\n * the operator's content. */\nexport const RESTORE_SENTINEL_FILE_NAME = '.devstack-restore-in-progress.json';\n\nexport const SNAPSHOT_RESTORE_SENTINEL_VERSION = 1 as const;\n\n/** The interrupted-restore sentinel record. Carries exactly what\n * re-running the restore needs: the snapshot id (the orchestrator\n * derives the artifact dir from it) plus the resolved artifact dir as a\n * cross-check / diagnostic (the snapshots subtree rides the same atomic\n * swap, so the artifact at this path is preserved across the restore). */\nexport const RestoreSentinelSchema = versionedDocSchema(SNAPSHOT_RESTORE_SENTINEL_VERSION, {\n\tsnapshotId: Schema.String,\n\tartifactDir: Schema.String,\n});\nexport type RestoreSentinel = Schema.Schema.Type<typeof RestoreSentinelSchema>;\n\nconst sentinelPath = (root: string): string => `${root}/${RESTORE_SENTINEL_FILE_NAME}`;\n\n// -----------------------------------------------------------------------------\n// Write / read / clear\n// -----------------------------------------------------------------------------\n\n/** Write the sentinel JSON into the STAGED tree root at the fixed hidden\n * path so it rides the atomic stage-and-swap into the live runtime root.\n * Called from `runRestore` immediately before `executeFsPlan` publishes\n * the swap. Encode validates the payload against the schema (the\n * `version` discriminator is supplied here). */\nexport const writeRestoreSentinel = (\n\tstagedRoot: string,\n\tpayload: { readonly snapshotId: SnapshotId; readonly artifactDir: string },\n): Effect.Effect<void, never, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst doc: RestoreSentinel = {\n\t\t\tversion: SNAPSHOT_RESTORE_SENTINEL_VERSION,\n\t\t\tsnapshotId: payload.snapshotId,\n\t\t\tartifactDir: payload.artifactDir,\n\t\t};\n\t\t// Best-effort: the sentinel is an OPTIMIZATION for crash recovery, not\n\t\t// a correctness gate on the restore path. A write failure here must NOT\n\t\t// abort the restore (which would be a worse outcome than the gap the\n\t\t// sentinel closes); it just means a hard kill in the tiny window that\n\t\t// follows would be unrecoverable, exactly as before. Log loud and\n\t\t// proceed.\n\t\tyield* fs\n\t\t\t.writeFileString(sentinelPath(stagedRoot), JSON.stringify(doc))\n\t\t\t.pipe(\n\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\tEffect.logWarning(\n\t\t\t\t\t\t`restore sentinel write failed at ${sentinelPath(stagedRoot)} — a hard kill ` +\n\t\t\t\t\t\t\t`mid-promotion would be unrecoverable for snapshot ${payload.snapshotId}: ${String(cause)}`,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t});\n\n/** Read the sentinel from the LIVE runtime root. Returns `null` when the\n * sentinel is absent (the common case — no interrupted restore) or\n * present-but-unparseable (logged, then treated as absent so a corrupt\n * breadcrumb cannot wedge boot; the operator can recover manually). */\nexport const readRestoreSentinel = (\n\tliveRoot: string,\n): Effect.Effect<RestoreSentinel | null, never, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst path = sentinelPath(liveRoot);\n\t\tconst exists = yield* fs.exists(path).pipe(Effect.catch(() => Effect.succeed(false)));\n\t\tif (!exists) return null;\n\t\tconst readExit = yield* Effect.exit(fs.readFileString(path));\n\t\tif (readExit._tag === 'Failure') {\n\t\t\tyield* Effect.logWarning(\n\t\t\t\t`restore sentinel read failed at ${path}; treating as absent (no auto-recovery)`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t\tconst decodeExit = yield* Effect.exit(\n\t\t\tparseJsonText(readExit.value, {\n\t\t\t\tsource: path,\n\t\t\t\tmkError: (issue) => issue,\n\t\t\t}).pipe(\n\t\t\t\tEffect.flatMap((raw) =>\n\t\t\t\t\tdecodeUnknown(RestoreSentinelSchema, raw, { source: path, mkError: (issue) => issue }),\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t\tif (decodeExit._tag === 'Failure') {\n\t\t\tyield* Effect.logWarning(\n\t\t\t\t`restore sentinel at ${path} is unparseable; treating as absent (no auto-recovery)`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\t\treturn decodeExit.value;\n\t});\n\n/** Remove the sentinel from the LIVE runtime root. Idempotent\n * (`force: true`) — a missing sentinel is a no-op. Called on the restore\n * success path the instant the promotion+removal handoff completes, and\n * again after a successful auto-recovery re-run. */\nexport const clearRestoreSentinel = (\n\tliveRoot: string,\n): Effect.Effect<void, never, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tyield* fs.remove(sentinelPath(liveRoot), { force: true }).pipe(Effect.ignore);\n\t});\n\n// -----------------------------------------------------------------------------\n// Boot-time recovery\n// -----------------------------------------------------------------------------\n\nexport interface RecoverInterruptedRestoreDeps {\n\t/** The LIVE runtime stack root (`StackPathsService.stackRoot`) the\n\t * sentinel rode into after the interrupted restore's swap. */\n\treadonly liveRoot: string;\n\t/** Re-run the restore for a snapshot id. Wired by the caller to the\n\t * orchestrator's `restore({ id })`, which owns the `stack.lock`\n\t * discipline and the participant/runtime deps — re-running it is\n\t * idempotent (re-stages from the preserved on-disk artifact). The\n\t * return value is ignored; the recovery only cares whether it\n\t * succeeded. The `FileSystem` requirement unifies with this module's\n\t * own (boot provides it once), so the caller can pass the\n\t * orchestrator's `restore` verb directly. */\n\treadonly restoreSnapshot: (\n\t\tsnapshotId: SnapshotId,\n\t) => Effect.Effect<unknown, unknown, FileSystem.FileSystem>;\n}\n\n/**\n * Boot-time auto-recovery for a restore interrupted by a hard kill /\n * power-loss between the atomic swap and the end of the image-promotion\n * handoff. Reads the sentinel from the live runtime root; when present,\n * logs a clear \"resuming interrupted restore <id>\" and re-invokes the\n * restore for that snapshot. No-op when the sentinel is absent.\n *\n * Wired at boot BEFORE the first plugin acquire (the `beforeInitialAcquire`\n * hook), so a half-promoted image set is reconciled before any L2 lookup\n * observes the runtime root.\n *\n * ## Loop-safety\n *\n * - A CLEAN restore cleared the sentinel on its success path, so a clean\n * boot reads `null` and returns immediately — boot never loops.\n * - A SUCCESSFUL recovery clears the sentinel after the re-run returns,\n * so the next boot is clean.\n * - A STILL-FAILING artifact (re-run fails) DELIBERATELY leaves the\n * sentinel in place so the next boot retries. The failure is logged\n * and SWALLOWED — boot continues rather than wedging — but the\n * sentinel persists as the durable retry breadcrumb. This is the\n * intended trade: a genuinely corrupt artifact will re-fail every boot\n * (surfaced by the warning + the restore's own typed phase error in\n * the logs) rather than silently dropping the recovery intent. An\n * operator resolves it by repairing/removing the artifact or clearing\n * the sentinel by hand.\n */\nexport const recoverInterruptedRestore = (\n\tdeps: RecoverInterruptedRestoreDeps,\n): Effect.Effect<void, never, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst sentinel = yield* readRestoreSentinel(deps.liveRoot);\n\t\tif (sentinel === null) return;\n\n\t\tconst snapshotId = parseSnapshotId(sentinel.snapshotId);\n\t\tif (snapshotId === null) {\n\t\t\t// A sentinel whose id is unsafe can't drive a restore (the\n\t\t\t// orchestrator would reject it anyway). Clear it so it doesn't\n\t\t\t// re-trigger this branch every boot, and log loud.\n\t\t\tyield* Effect.logWarning(\n\t\t\t\t`interrupted-restore sentinel carries an unsafe snapshot id (${sentinel.snapshotId}); ` +\n\t\t\t\t\t`clearing it — no auto-recovery possible`,\n\t\t\t);\n\t\t\tyield* clearRestoreSentinel(deps.liveRoot);\n\t\t\treturn;\n\t\t}\n\n\t\tyield* Effect.logInfo(\n\t\t\t`resuming interrupted restore ${snapshotId} (sentinel at ${deps.liveRoot}/${RESTORE_SENTINEL_FILE_NAME})`,\n\t\t).pipe(Effect.annotateLogs({ 'devstack.snapshot.id': snapshotId }));\n\n\t\tconst exit = yield* Effect.exit(deps.restoreSnapshot(snapshotId));\n\t\tif (exit._tag === 'Failure') {\n\t\t\t// Leave the sentinel for the next boot's retry (loop-safety doc\n\t\t\t// above). Swallow so boot continues — a failing recovery must not\n\t\t\t// wedge the stack.\n\t\t\tyield* Effect.logWarning(\n\t\t\t\t`interrupted-restore recovery for ${snapshotId} did not complete; leaving sentinel for ` +\n\t\t\t\t\t`the next boot to retry. Investigate the snapshot artifact if this recurs.`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Recovery succeeded — `runRestore` cleared the sentinel on its own\n\t\t// success path, but clear again defensively (idempotent) so a code\n\t\t// path that ever skipped the in-restore clear cannot re-loop.\n\t\tyield* clearRestoreSentinel(deps.liveRoot);\n\t\tyield* Effect.logInfo(`interrupted restore ${snapshotId} recovered`).pipe(\n\t\t\tEffect.annotateLogs({ 'devstack.snapshot.id': snapshotId }),\n\t\t);\n\t});\n"],"mappings":";;;;;;;;;;AAyDA,MAAa,6BAA6B;;;;;;AAS1C,MAAa,wBAAwB,mBAAA,GAAsD;CAC1F,YAAY,OAAO;CACnB,aAAa,OAAO;AACrB,CAAC;AAGD,MAAM,gBAAgB,SAAyB,GAAG,KAAK,GAAG;;;;;;AAW1D,MAAa,wBACZ,YACA,YAEA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,MAAuB;EAC5B,SAAA;EACA,YAAY,QAAQ;EACpB,aAAa,QAAQ;CACtB;CAOA,OAAO,GACL,gBAAgB,aAAa,UAAU,GAAG,KAAK,UAAU,GAAG,CAAC,CAAC,CAC9D,KACA,OAAO,OAAO,UACb,OAAO,WACN,oCAAoC,aAAa,UAAU,EAAE,mEACP,QAAQ,WAAW,IAAI,OAAO,KAAK,GAC1F,CACD,CACD;AACF,CAAC;;;;;AAMF,MAAa,uBACZ,aAEA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,OAAO,aAAa,QAAQ;CAElC,IAAI,EAAC,OADiB,GAAG,OAAO,IAAI,CAAC,CAAC,KAAK,OAAO,YAAY,OAAO,QAAQ,KAAK,CAAC,CAAC,IACvE,OAAO;CACpB,MAAM,WAAW,OAAO,OAAO,KAAK,GAAG,eAAe,IAAI,CAAC;CAC3D,IAAI,SAAS,SAAS,WAAW;EAChC,OAAO,OAAO,WACb,mCAAmC,KAAK,wCACzC;EACA,OAAO;CACR;CACA,MAAM,aAAa,OAAO,OAAO,KAChC,cAAc,SAAS,OAAO;EAC7B,QAAQ;EACR,UAAU,UAAU;CACrB,CAAC,CAAC,CAAC,KACF,OAAO,SAAS,QACf,cAAc,uBAAuB,KAAK;EAAE,QAAQ;EAAM,UAAU,UAAU;CAAM,CAAC,CACtF,CACD,CACD;CACA,IAAI,WAAW,SAAS,WAAW;EAClC,OAAO,OAAO,WACb,uBAAuB,KAAK,uDAC7B;EACA,OAAO;CACR;CACA,OAAO,WAAW;AACnB,CAAC;;;;;AAMF,MAAa,wBACZ,aAEA,OAAO,IAAI,aAAa;CAEvB,QAAO,OADW,WAAW,WAAA,CACnB,OAAO,aAAa,QAAQ,GAAG,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,OAAO,MAAM;AAC7E,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDF,MAAa,6BACZ,SAEA,OAAO,IAAI,aAAa;CACvB,MAAM,WAAW,OAAO,oBAAoB,KAAK,QAAQ;CACzD,IAAI,aAAa,MAAM;CAEvB,MAAM,aAAa,gBAAgB,SAAS,UAAU;CACtD,IAAI,eAAe,MAAM;EAIxB,OAAO,OAAO,WACb,+DAA+D,SAAS,WAAW,2CAEpF;EACA,OAAO,qBAAqB,KAAK,QAAQ;EACzC;CACD;CAEA,OAAO,OAAO,QACb,gCAAgC,WAAW,gBAAgB,KAAK,SAAS,GAAG,2BAA2B,EACxG,CAAC,CAAC,KAAK,OAAO,aAAa,EAAE,wBAAwB,WAAW,CAAC,CAAC;CAGlE,KAAI,OADgB,OAAO,KAAK,KAAK,gBAAgB,UAAU,CAAC,EAAA,CACvD,SAAS,WAAW;EAI5B,OAAO,OAAO,WACb,oCAAoC,WAAW,kHAEhD;EACA;CACD;CAKA,OAAO,qBAAqB,KAAK,QAAQ;CACzC,OAAO,OAAO,QAAQ,uBAAuB,WAAW,WAAW,CAAC,CAAC,KACpE,OAAO,aAAa,EAAE,wBAAwB,WAAW,CAAC,CAC3D;AACD,CAAC"}
@@ -1 +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
+ {"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(phase: Phase, detail: string, plugin?: string): ((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,gBACzB,OAAc,QAAgB,YAC9B,UACA,OAAO,KACN,IAAI,WAAW;CAAE;CAAO;CAAQ;CAAO,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO;AAAG,CAAC,CACrF"}
@@ -1,6 +1,10 @@
1
+ import { appName, stackName } from "../../substrate/brand.mjs";
1
2
  import { decodeJsonText } from "../../substrate/runtime/runtime-decode.mjs";
2
3
  import { SNAPSHOT_IMAGE_ROLE } from "../../runtime/docker/container.mjs";
3
4
  import { SnapshotMetadataSchema } from "./descriptor.mjs";
5
+ import { labelScope, reconcileSpec } from "../../substrate/runtime/reconcile/spec.mjs";
6
+ import { reconcileLabel } from "../../substrate/runtime/reconcile/label.mjs";
7
+ import "../../substrate/runtime/reconcile/index.mjs";
4
8
  import { makePhaseFailer } from "./phase-error.mjs";
5
9
  import { SNAPSHOTS_DIR_NAME } from "./wipe.mjs";
6
10
  import { Effect, FileSystem, Schema } from "effect";
@@ -28,47 +32,52 @@ const readMetaOpt = (dir) => Effect.gen(function* () {
28
32
  /**
29
33
  * Walk the snapshot catalog and reap partial artifacts (entries whose
30
34
  * `meta.json` is missing or unparseable). Concurrent sweeps over the
31
- * same catalog are not supported (caller holds `snapshot.reservation`
32
- * or `stack.lock`).
35
+ * same catalog are not supported (caller holds `stack.lock`).
33
36
  *
34
37
  * Also removes committed snapshot byproduct images via the runtime
35
38
  * adapter's label-filtered image cleanup, scoped to `role:
36
39
  * SNAPSHOT_IMAGE_ROLE` so build images are never touched.
40
+ *
41
+ * Routed through the unified reconcile: a flat LABEL-scope spec narrowed
42
+ * to `role: SNAPSHOT_IMAGE_ROLE`, carrying NO container target (prune
43
+ * mutates no containers — `target: 'running'` is the label-scope no-op
44
+ * container target) and an `fsPlan` of two ops — `reap-meta-missing`
45
+ * (catalog GC) then `reap-images` (byproduct image sweep). Prune never
46
+ * touches the live deploy cache — only the snapshot byproducts (the image
47
+ * GC); the `PruneResult` shape carries `reaped` + `imagesSwept`.
37
48
  */
38
49
  const runPrune = (inputs) => Effect.gen(function* () {
39
- const fs = yield* FileSystem.FileSystem;
40
- yield* Effect.annotateCurrentSpan({ "devstack.snapshot.phase": "prune" });
41
50
  const catalogDir = `${inputs.stackRoot}/${SNAPSHOTS_DIR_NAME}`;
42
- if (!(yield* fs.exists(catalogDir).pipe(Effect.catch(() => Effect.succeed(false))))) return {
43
- inspected: 0,
44
- reaped: [],
45
- imagesSwept: 0
51
+ const reapMetaMissing = {
52
+ op: "reap-meta-missing",
53
+ catalogDir,
54
+ isMetaMissing: (dir) => readMetaOpt(dir).pipe(Effect.map((meta) => meta === null)),
55
+ onReaddirError: failPhase("enumerate-catalog", `readdir ${catalogDir} failed`),
56
+ onRemoveError: failPhase("sweep-directories", `remove catalog entry failed`)
46
57
  };
47
- const ids = yield* fs.readDirectory(catalogDir).pipe(Effect.catch(failPhase("enumerate-catalog", `readdir ${catalogDir} failed`)));
48
- const reaped = [];
49
- for (const id of ids) {
50
- const dir = `${catalogDir}/${id}`;
51
- if ((yield* readMetaOpt(dir)) === null) {
52
- yield* fs.remove(dir, {
53
- recursive: true,
54
- force: true
55
- }).pipe(Effect.catch(failPhase("sweep-directories", `remove ${dir} failed`)));
56
- reaped.push({
57
- id,
58
- classification: "abandoned"
59
- });
60
- }
61
- }
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`)));
58
+ const reapImages = {
59
+ op: "reap-images",
60
+ onError: failPhase("sweep-images", `image sweep failed`)
61
+ };
62
+ const result = yield* reconcileLabel(reconcileSpec({
63
+ target: "running",
64
+ scope: labelScope({
65
+ app: appName(inputs.imageLabelFilter.app),
66
+ stack: stackName(inputs.imageLabelFilter.stack),
67
+ role: SNAPSHOT_IMAGE_ROLE
68
+ }),
69
+ direction: "drain",
70
+ fsPlan: { ops: [reapMetaMissing, reapImages] }
71
+ }), { runtime: inputs.runtime });
66
72
  return {
67
- inspected: ids.length,
68
- reaped,
69
- imagesSwept
73
+ inspected: result.inspected,
74
+ reaped: result.reapedIds.map((id) => ({
75
+ id,
76
+ classification: "abandoned"
77
+ })),
78
+ imagesSwept: result.imagesSwept
70
79
  };
71
- }).pipe(Effect.withSpan("orchestrator.snapshot.prune"));
80
+ });
72
81
  //#endregion
73
82
  export { PrunePhaseError, runPrune };
74
83