@mysten-incubation/devstack 0.0.1 → 0.1.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 (838) hide show
  1. package/README.md +7 -3
  2. package/dashboard-ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  3. package/dashboard-ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  4. package/dashboard-ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  5. package/dashboard-ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  6. package/dashboard-ui/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
  7. package/dashboard-ui/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
  8. package/dashboard-ui/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
  9. package/dashboard-ui/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
  10. package/dashboard-ui/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
  11. package/dashboard-ui/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
  12. package/dashboard-ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  13. package/dashboard-ui/assets/index-Bmi1UtAg.js +1356 -0
  14. package/dashboard-ui/assets/index-D5EShVt4.js +3 -0
  15. package/dashboard-ui/assets/index-Deml9drg.css +1 -0
  16. package/dashboard-ui/index.html +13 -0
  17. package/dist/api/define-capabilities.d.mts +14 -3
  18. package/dist/api/define-capabilities.mjs +18 -1
  19. package/dist/api/define-capabilities.mjs.map +1 -1
  20. package/dist/api/define-devstack.d.mts +1 -1
  21. package/dist/api/define-devstack.mjs +6 -26
  22. package/dist/api/define-devstack.mjs.map +1 -1
  23. package/dist/api/define-plugin.d.mts +1 -0
  24. package/dist/api/define-plugin.mjs +2 -0
  25. package/dist/api/inference-network.d.mts +132 -0
  26. package/dist/api/inference-network.mjs +105 -16
  27. package/dist/api/inference-network.mjs.map +1 -1
  28. package/dist/api/plugin-errors.d.mts +7 -0
  29. package/dist/api/run-stack.d.mts +20 -9
  30. package/dist/api/run-stack.mjs +56 -29
  31. package/dist/api/run-stack.mjs.map +1 -1
  32. package/dist/build-integrations/playwright/config.d.mts +6 -4
  33. package/dist/build-integrations/playwright/config.mjs +5 -3
  34. package/dist/build-integrations/playwright/config.mjs.map +1 -1
  35. package/dist/build-integrations/playwright/errors.d.mts +1 -1
  36. package/dist/build-integrations/playwright/global-setup.d.mts +43 -17
  37. package/dist/build-integrations/playwright/global-setup.mjs +250 -13
  38. package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
  39. package/dist/build-integrations/playwright/index.d.mts +5 -3
  40. package/dist/build-integrations/playwright/index.mjs +4 -2
  41. package/dist/build-integrations/playwright/stack-context.d.mts +0 -1
  42. package/dist/build-integrations/playwright/stack-context.mjs +24 -63
  43. package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
  44. package/dist/build-integrations/playwright/wallet-context.d.mts +7 -10
  45. package/dist/build-integrations/playwright/wallet-context.mjs +8 -13
  46. package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
  47. package/dist/build-integrations/runtime/cold-start-url.d.mts +5 -0
  48. package/dist/build-integrations/runtime/cold-start-url.mjs +4 -3
  49. package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
  50. package/dist/build-integrations/runtime/conventional-routes.d.mts +51 -0
  51. package/dist/build-integrations/runtime/conventional-routes.mjs +92 -0
  52. package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -0
  53. package/dist/build-integrations/runtime/discover.d.mts +38 -30
  54. package/dist/build-integrations/runtime/discover.mjs +73 -70
  55. package/dist/build-integrations/runtime/discover.mjs.map +1 -1
  56. package/dist/build-integrations/runtime/endpoint-registry.d.mts +3 -1
  57. package/dist/build-integrations/runtime/endpoint-registry.mjs +3 -1
  58. package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
  59. package/dist/build-integrations/runtime/index.d.mts +7 -2
  60. package/dist/build-integrations/runtime/index.mjs +9 -2
  61. package/dist/build-integrations/runtime/manifest-types.d.mts +2 -0
  62. package/dist/build-integrations/runtime/manifest-types.mjs +3 -0
  63. package/dist/build-integrations/runtime/playwright-stack-context-slot.d.mts +29 -0
  64. package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs +10 -0
  65. package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs.map +1 -0
  66. package/dist/build-integrations/runtime/read-stack-context.d.mts +1 -1
  67. package/dist/build-integrations/runtime/read-stack-context.mjs +10 -4
  68. package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
  69. package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +48 -0
  70. package/dist/build-integrations/runtime/resolve-discovery-env.mjs +42 -0
  71. package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -0
  72. package/dist/build-integrations/runtime/stack-context.d.mts +1 -1
  73. package/dist/build-integrations/runtime/wallet-paths.mjs +2 -0
  74. package/dist/build-integrations/vite/index.d.mts +51 -0
  75. package/dist/build-integrations/vite/index.mjs +73 -0
  76. package/dist/build-integrations/vite/index.mjs.map +1 -0
  77. package/dist/build-integrations/vitest/env.d.mts +4 -3
  78. package/dist/build-integrations/vitest/env.mjs +6 -4
  79. package/dist/build-integrations/vitest/env.mjs.map +1 -1
  80. package/dist/build-integrations/vitest/errors.d.mts +25 -4
  81. package/dist/build-integrations/vitest/errors.mjs +15 -2
  82. package/dist/build-integrations/vitest/errors.mjs.map +1 -1
  83. package/dist/build-integrations/vitest/index.d.mts +2 -2
  84. package/dist/build-integrations/vitest/index.mjs +3 -3
  85. package/dist/build-integrations/vitest/setup.d.mts +4 -2
  86. package/dist/build-integrations/vitest/setup.mjs +2 -2
  87. package/dist/build-integrations/vitest/setup.mjs.map +1 -1
  88. package/dist/build-integrations/vitest/stack-context.d.mts +0 -1
  89. package/dist/build-integrations/vitest/stack-context.mjs +33 -13
  90. package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
  91. package/dist/cli/bail.mjs +23 -0
  92. package/dist/cli/bail.mjs.map +1 -0
  93. package/dist/{surfaces/cli/commands → cli}/doctor-probes.mjs +26 -16
  94. package/dist/cli/doctor-probes.mjs.map +1 -0
  95. package/dist/cli/main.d.mts +15 -1
  96. package/dist/cli/main.mjs +166 -482
  97. package/dist/cli/main.mjs.map +1 -1
  98. package/dist/cli/prune-direct.mjs +31 -201
  99. package/dist/cli/prune-direct.mjs.map +1 -1
  100. package/dist/cli/snapshot-reader.mjs +14 -11
  101. package/dist/cli/snapshot-reader.mjs.map +1 -1
  102. package/dist/cli/up-lifecycle.mjs +4 -1
  103. package/dist/cli/up-lifecycle.mjs.map +1 -1
  104. package/dist/cli/wirings/apply.mjs +95 -0
  105. package/dist/cli/wirings/apply.mjs.map +1 -0
  106. package/dist/cli/wirings/build-verb-layers.mjs +42 -0
  107. package/dist/cli/wirings/build-verb-layers.mjs.map +1 -0
  108. package/dist/cli/wirings/config-loader.mjs +69 -0
  109. package/dist/cli/wirings/config-loader.mjs.map +1 -0
  110. package/dist/cli/wirings/engine-command.mjs +30 -0
  111. package/dist/cli/wirings/engine-command.mjs.map +1 -0
  112. package/dist/cli/wirings/identity.mjs +72 -0
  113. package/dist/cli/wirings/identity.mjs.map +1 -0
  114. package/dist/cli/wirings/provide-file-system.mjs +7 -0
  115. package/dist/cli/wirings/provide-file-system.mjs.map +1 -0
  116. package/dist/cli/wirings/snapshot.mjs +177 -0
  117. package/dist/cli/wirings/snapshot.mjs.map +1 -0
  118. package/dist/cli/wirings/up.mjs +287 -0
  119. package/dist/cli/wirings/up.mjs.map +1 -0
  120. package/dist/cli/wirings/wipe.mjs +40 -0
  121. package/dist/cli/wirings/wipe.mjs.map +1 -0
  122. package/dist/contracts/capability-decl.d.mts +0 -2
  123. package/dist/contracts/codegenable.d.mts +43 -0
  124. package/dist/contracts/container-runtime.d.mts +26 -1
  125. package/dist/contracts/faucet-strategy.d.mts +25 -0
  126. package/dist/contracts/funding-strategy.d.mts +42 -0
  127. package/dist/contracts/plugin-expander.mjs +60 -0
  128. package/dist/contracts/plugin-expander.mjs.map +1 -0
  129. package/dist/contracts/projection.d.mts +5 -3
  130. package/dist/contracts/renderer.d.mts +23 -0
  131. package/dist/contracts/routable.d.mts +14 -1
  132. package/dist/contracts/snapshotable.d.mts +25 -3
  133. package/dist/contracts/wallet-protocol.d.mts +52 -0
  134. package/dist/contracts/wallet-protocol.mjs +52 -0
  135. package/dist/contracts/wallet-protocol.mjs.map +1 -0
  136. package/dist/index.d.mts +26 -25
  137. package/dist/index.mjs +20 -13
  138. package/dist/{runtime → orchestrators}/built-in-plugin-layers.mjs +10 -5
  139. package/dist/orchestrators/built-in-plugin-layers.mjs.map +1 -0
  140. package/dist/orchestrators/codegen/bindings.d.mts +1 -2
  141. package/dist/orchestrators/codegen/bindings.mjs +52 -186
  142. package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
  143. package/dist/orchestrators/codegen/emit.mjs +19 -1
  144. package/dist/orchestrators/codegen/emit.mjs.map +1 -1
  145. package/dist/orchestrators/codegen/errors.mjs +8 -3
  146. package/dist/orchestrators/codegen/errors.mjs.map +1 -1
  147. package/dist/orchestrators/codegen/format.mjs +47 -39
  148. package/dist/orchestrators/codegen/format.mjs.map +1 -1
  149. package/dist/orchestrators/codegen/gitignore.mjs +3 -1
  150. package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
  151. package/dist/orchestrators/codegen/output-location.mjs +40 -0
  152. package/dist/orchestrators/codegen/output-location.mjs.map +1 -0
  153. package/dist/orchestrators/codegen/paths.d.mts +1 -1
  154. package/dist/orchestrators/codegen/paths.mjs +40 -14
  155. package/dist/orchestrators/codegen/paths.mjs.map +1 -1
  156. package/dist/orchestrators/codegen/permissions.mjs +3 -1
  157. package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
  158. package/dist/orchestrators/codegen/service.mjs +157 -105
  159. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  160. package/dist/orchestrators/lifecycle-prune/errors.mjs +31 -0
  161. package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -0
  162. package/dist/orchestrators/lifecycle-prune/index.mjs +350 -0
  163. package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -0
  164. package/dist/orchestrators/router/cleanup.mjs +26 -27
  165. package/dist/orchestrators/router/cleanup.mjs.map +1 -1
  166. package/dist/orchestrators/router/entrypoints.mjs +2 -2
  167. package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
  168. package/dist/orchestrators/router/file-provider.mjs +14 -5
  169. package/dist/orchestrators/router/file-provider.mjs.map +1 -1
  170. package/dist/orchestrators/router/hostname.mjs +19 -55
  171. package/dist/orchestrators/router/hostname.mjs.map +1 -1
  172. package/dist/orchestrators/router/index.mjs +11 -0
  173. package/dist/orchestrators/router/profile.mjs +2 -2
  174. package/dist/orchestrators/router/sentinels.mjs +26 -0
  175. package/dist/orchestrators/router/sentinels.mjs.map +1 -0
  176. package/dist/orchestrators/router/service.mjs +40 -37
  177. package/dist/orchestrators/router/service.mjs.map +1 -1
  178. package/dist/orchestrators/router/traefik-container.mjs +27 -16
  179. package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
  180. package/dist/{substrate/runtime → orchestrators}/run.mjs +26 -21
  181. package/dist/orchestrators/run.mjs.map +1 -0
  182. package/dist/orchestrators/runtime-composition.d.mts +1 -1
  183. package/dist/orchestrators/runtime-composition.mjs +123 -39
  184. package/dist/orchestrators/runtime-composition.mjs.map +1 -1
  185. package/dist/orchestrators/snapshot/capture-command.d.mts +1 -0
  186. package/dist/orchestrators/snapshot/capture-command.mjs +25 -0
  187. package/dist/orchestrators/snapshot/capture-command.mjs.map +1 -0
  188. package/dist/orchestrators/snapshot/capture.mjs +42 -28
  189. package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
  190. package/dist/orchestrators/snapshot/descriptor.mjs +35 -10
  191. package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
  192. package/dist/orchestrators/snapshot/identity-guard.mjs +1 -1
  193. package/dist/orchestrators/snapshot/index.mjs +13 -0
  194. package/dist/orchestrators/snapshot/integrity.d.mts +1 -1
  195. package/dist/orchestrators/snapshot/integrity.mjs +73 -17
  196. package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
  197. package/dist/orchestrators/snapshot/pending-marker.d.mts +1 -0
  198. package/dist/orchestrators/snapshot/pending-marker.mjs +99 -0
  199. package/dist/orchestrators/snapshot/pending-marker.mjs.map +1 -0
  200. package/dist/orchestrators/snapshot/phase-error.mjs +23 -0
  201. package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -0
  202. package/dist/orchestrators/snapshot/prune.mjs +20 -55
  203. package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
  204. package/dist/orchestrators/snapshot/recover-pending.d.mts +1 -0
  205. package/dist/orchestrators/snapshot/recover-pending.mjs +223 -0
  206. package/dist/orchestrators/snapshot/recover-pending.mjs.map +1 -0
  207. package/dist/orchestrators/snapshot/restore.mjs +118 -84
  208. package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
  209. package/dist/orchestrators/snapshot/service.mjs +53 -56
  210. package/dist/orchestrators/snapshot/service.mjs.map +1 -1
  211. package/dist/orchestrators/snapshot/state-document.d.mts +1 -0
  212. package/dist/orchestrators/snapshot/state-document.mjs +47 -7
  213. package/dist/orchestrators/snapshot/state-document.mjs.map +1 -1
  214. package/dist/orchestrators/snapshot/wipe.mjs +67 -10
  215. package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
  216. package/dist/plugins/account/codegen.mjs +9 -8
  217. package/dist/plugins/account/codegen.mjs.map +1 -1
  218. package/dist/plugins/account/errors.d.mts +33 -6
  219. package/dist/plugins/account/errors.mjs.map +1 -1
  220. package/dist/plugins/account/funding.d.mts +29 -19
  221. package/dist/plugins/account/funding.mjs +84 -66
  222. package/dist/plugins/account/funding.mjs.map +1 -1
  223. package/dist/plugins/account/index.d.mts +2 -4
  224. package/dist/plugins/account/index.mjs +64 -31
  225. package/dist/plugins/account/index.mjs.map +1 -1
  226. package/dist/plugins/account/keypair.mjs +6 -9
  227. package/dist/plugins/account/keypair.mjs.map +1 -1
  228. package/dist/plugins/account/lease.mjs +1 -0
  229. package/dist/plugins/account/lease.mjs.map +1 -1
  230. package/dist/plugins/account/registry.d.mts +14 -1
  231. package/dist/plugins/account/registry.mjs +18 -18
  232. package/dist/plugins/account/registry.mjs.map +1 -1
  233. package/dist/plugins/account/service.d.mts +23 -4
  234. package/dist/plugins/account/service.mjs +83 -45
  235. package/dist/plugins/account/service.mjs.map +1 -1
  236. package/dist/plugins/account/snapshot.mjs +2 -5
  237. package/dist/plugins/account/snapshot.mjs.map +1 -1
  238. package/dist/plugins/account/spans.mjs +16 -0
  239. package/dist/plugins/account/spans.mjs.map +1 -0
  240. package/dist/plugins/account/variants/keystore.mjs +6 -8
  241. package/dist/plugins/account/variants/keystore.mjs.map +1 -1
  242. package/dist/plugins/action/build-context.d.mts +3 -2
  243. package/dist/plugins/action/errors.d.mts +16 -12
  244. package/dist/plugins/action/errors.mjs.map +1 -1
  245. package/dist/plugins/action/execute.mjs +65 -103
  246. package/dist/plugins/action/execute.mjs.map +1 -1
  247. package/dist/plugins/action/index.d.mts +0 -2
  248. package/dist/plugins/action/index.mjs +17 -13
  249. package/dist/plugins/action/index.mjs.map +1 -1
  250. package/dist/plugins/action/service.mjs +38 -35
  251. package/dist/plugins/action/service.mjs.map +1 -1
  252. package/dist/plugins/action/spans.mjs +11 -0
  253. package/dist/plugins/action/spans.mjs.map +1 -0
  254. package/dist/plugins/coin/codegen.mjs +9 -8
  255. package/dist/plugins/coin/codegen.mjs.map +1 -1
  256. package/dist/plugins/coin/discovery.mjs +8 -4
  257. package/dist/plugins/coin/discovery.mjs.map +1 -1
  258. package/dist/plugins/coin/index.d.mts +11 -29
  259. package/dist/plugins/coin/index.mjs +56 -27
  260. package/dist/plugins/coin/index.mjs.map +1 -1
  261. package/dist/plugins/coin/metadata.mjs +8 -15
  262. package/dist/plugins/coin/metadata.mjs.map +1 -1
  263. package/dist/plugins/coin/mint.d.mts +30 -11
  264. package/dist/plugins/coin/mint.mjs +78 -74
  265. package/dist/plugins/coin/mint.mjs.map +1 -1
  266. package/dist/plugins/coin/registry.mjs +4 -7
  267. package/dist/plugins/coin/registry.mjs.map +1 -1
  268. package/dist/plugins/coin/service.d.mts +39 -1
  269. package/dist/plugins/coin/service.mjs +11 -4
  270. package/dist/plugins/coin/service.mjs.map +1 -1
  271. package/dist/plugins/coin/spans.mjs +20 -0
  272. package/dist/plugins/coin/spans.mjs.map +1 -0
  273. package/dist/plugins/dashboard/domain.mjs +402 -0
  274. package/dist/plugins/dashboard/domain.mjs.map +1 -0
  275. package/dist/plugins/dashboard/index.d.mts +29 -0
  276. package/dist/plugins/dashboard/index.mjs +91 -0
  277. package/dist/plugins/dashboard/index.mjs.map +1 -0
  278. package/dist/plugins/dashboard/origin-policy.mjs +36 -0
  279. package/dist/plugins/dashboard/origin-policy.mjs.map +1 -0
  280. package/dist/plugins/dashboard/routable.mjs +27 -0
  281. package/dist/plugins/dashboard/routable.mjs.map +1 -0
  282. package/dist/plugins/dashboard/schema/builder.mjs +21 -0
  283. package/dist/plugins/dashboard/schema/builder.mjs.map +1 -0
  284. package/dist/plugins/dashboard/schema/enums.mjs +72 -0
  285. package/dist/plugins/dashboard/schema/enums.mjs.map +1 -0
  286. package/dist/plugins/dashboard/schema/root.mjs +351 -0
  287. package/dist/plugins/dashboard/schema/root.mjs.map +1 -0
  288. package/dist/plugins/dashboard/schema/types.mjs +508 -0
  289. package/dist/plugins/dashboard/schema/types.mjs.map +1 -0
  290. package/dist/plugins/dashboard/schema.mjs +11 -0
  291. package/dist/plugins/dashboard/schema.mjs.map +1 -0
  292. package/dist/plugins/dashboard/server.mjs +166 -0
  293. package/dist/plugins/dashboard/server.mjs.map +1 -0
  294. package/dist/plugins/deepbook/deploy.mjs +53 -115
  295. package/dist/plugins/deepbook/deploy.mjs.map +1 -1
  296. package/dist/plugins/deepbook/errors.mjs +1 -0
  297. package/dist/plugins/deepbook/errors.mjs.map +1 -1
  298. package/dist/plugins/deepbook/faucet-strategy.mjs +47 -34
  299. package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
  300. package/dist/plugins/deepbook/hash.mjs +9 -0
  301. package/dist/plugins/deepbook/hash.mjs.map +1 -0
  302. package/dist/plugins/deepbook/index.d.mts +1 -10
  303. package/dist/plugins/deepbook/index.mjs +25 -22
  304. package/dist/plugins/deepbook/index.mjs.map +1 -1
  305. package/dist/plugins/deepbook/pyth/index.mjs +26 -55
  306. package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
  307. package/dist/plugins/deepbook/snapshot.mjs +17 -29
  308. package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
  309. package/dist/plugins/deepbook/spans.mjs +18 -0
  310. package/dist/plugins/deepbook/spans.mjs.map +1 -0
  311. package/dist/plugins/deepbook/type-strings.mjs +65 -0
  312. package/dist/plugins/deepbook/type-strings.mjs.map +1 -0
  313. package/dist/plugins/deepbook/types.d.mts +2 -3
  314. package/dist/plugins/deepbook/types.mjs.map +1 -1
  315. package/dist/plugins/faucet/dispatcher.d.mts +21 -2
  316. package/dist/plugins/faucet/dispatcher.mjs +1 -2
  317. package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
  318. package/dist/plugins/faucet/errors.d.mts +11 -24
  319. package/dist/plugins/faucet/errors.mjs.map +1 -1
  320. package/dist/plugins/faucet/http.mjs +33 -14
  321. package/dist/plugins/faucet/http.mjs.map +1 -1
  322. package/dist/plugins/faucet/index.d.mts +15 -5
  323. package/dist/plugins/faucet/index.mjs +3 -0
  324. package/dist/plugins/faucet/index.mjs.map +1 -1
  325. package/dist/plugins/faucet/spans.mjs +12 -0
  326. package/dist/plugins/faucet/spans.mjs.map +1 -0
  327. package/dist/plugins/host-service/errors.d.mts +1 -1
  328. package/dist/plugins/host-service/index.mjs +34 -8
  329. package/dist/plugins/host-service/index.mjs.map +1 -1
  330. package/dist/plugins/host-service/routable.mjs +2 -1
  331. package/dist/plugins/host-service/routable.mjs.map +1 -1
  332. package/dist/plugins/host-service/service.d.mts +21 -0
  333. package/dist/plugins/host-service/service.mjs +136 -81
  334. package/dist/plugins/host-service/service.mjs.map +1 -1
  335. package/dist/plugins/internal/acquire-on-chain-artifact.mjs +23 -0
  336. package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -0
  337. package/dist/plugins/internal/codegen-helpers.mjs +18 -0
  338. package/dist/plugins/internal/codegen-helpers.mjs.map +1 -0
  339. package/dist/plugins/internal/funding-failure-error.mjs +32 -0
  340. package/dist/plugins/internal/funding-failure-error.mjs.map +1 -0
  341. package/dist/plugins/package/build.mjs +1 -1
  342. package/dist/plugins/package/build.mjs.map +1 -1
  343. package/dist/plugins/package/codegen.mjs +26 -0
  344. package/dist/plugins/package/codegen.mjs.map +1 -1
  345. package/dist/plugins/package/errors.d.mts +16 -6
  346. package/dist/plugins/package/errors.mjs.map +1 -1
  347. package/dist/plugins/package/index.d.mts +0 -4
  348. package/dist/plugins/package/index.mjs +27 -21
  349. package/dist/plugins/package/index.mjs.map +1 -1
  350. package/dist/plugins/package/mode-known.mjs +26 -12
  351. package/dist/plugins/package/mode-known.mjs.map +1 -1
  352. package/dist/plugins/package/mode-local.mjs +47 -41
  353. package/dist/plugins/package/mode-local.mjs.map +1 -1
  354. package/dist/plugins/package/publish-executor.mjs +55 -114
  355. package/dist/plugins/package/publish-executor.mjs.map +1 -1
  356. package/dist/plugins/package/registry.mjs +2 -1
  357. package/dist/plugins/package/registry.mjs.map +1 -1
  358. package/dist/plugins/package/service.mjs.map +1 -1
  359. package/dist/plugins/package/spans.mjs +14 -0
  360. package/dist/plugins/package/spans.mjs.map +1 -0
  361. package/dist/plugins/postgres/db-ensure.mjs +19 -12
  362. package/dist/plugins/postgres/db-ensure.mjs.map +1 -1
  363. package/dist/plugins/postgres/errors.d.mts +5 -0
  364. package/dist/plugins/postgres/errors.mjs.map +1 -1
  365. package/dist/plugins/postgres/index.mjs +10 -17
  366. package/dist/plugins/postgres/index.mjs.map +1 -1
  367. package/dist/plugins/postgres/service.mjs +58 -24
  368. package/dist/plugins/postgres/service.mjs.map +1 -1
  369. package/dist/plugins/postgres/snapshot.mjs.map +1 -1
  370. package/dist/plugins/postgres/spans.mjs +11 -0
  371. package/dist/plugins/postgres/spans.mjs.map +1 -0
  372. package/dist/plugins/router-entrypoints.mjs +4 -4
  373. package/dist/plugins/router-entrypoints.mjs.map +1 -1
  374. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +15 -13
  375. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
  376. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +15 -19
  377. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
  378. package/dist/plugins/seal/codegen.d.mts +2 -3
  379. package/dist/plugins/seal/codegen.mjs.map +1 -1
  380. package/dist/plugins/seal/config-render.mjs +35 -8
  381. package/dist/plugins/seal/config-render.mjs.map +1 -1
  382. package/dist/plugins/seal/deploy.mjs +31 -39
  383. package/dist/plugins/seal/deploy.mjs.map +1 -1
  384. package/dist/plugins/seal/errors.d.mts +1 -3
  385. package/dist/plugins/seal/errors.mjs +7 -1
  386. package/dist/plugins/seal/errors.mjs.map +1 -1
  387. package/dist/plugins/seal/index.d.mts +1 -6
  388. package/dist/plugins/seal/index.mjs +40 -14
  389. package/dist/plugins/seal/index.mjs.map +1 -1
  390. package/dist/plugins/seal/key-manager.d.mts +1 -9
  391. package/dist/plugins/seal/key-manager.mjs +2 -18
  392. package/dist/plugins/seal/key-manager.mjs.map +1 -1
  393. package/dist/plugins/seal/key-server.mjs +37 -36
  394. package/dist/plugins/seal/key-server.mjs.map +1 -1
  395. package/dist/plugins/seal/keygen.mjs +16 -17
  396. package/dist/plugins/seal/keygen.mjs.map +1 -1
  397. package/dist/plugins/seal/mode/fork-known.mjs +36 -14
  398. package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
  399. package/dist/plugins/seal/mode/live.d.mts +6 -4
  400. package/dist/plugins/seal/mode/live.mjs +14 -21
  401. package/dist/plugins/seal/mode/live.mjs.map +1 -1
  402. package/dist/plugins/seal/mode/local-keygen.mjs +57 -21
  403. package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
  404. package/dist/plugins/seal/registry-publish.mjs +1 -0
  405. package/dist/plugins/seal/registry-publish.mjs.map +1 -1
  406. package/dist/plugins/seal/routable.mjs.map +1 -1
  407. package/dist/plugins/seal/service.mjs.map +1 -1
  408. package/dist/plugins/seal/spans.mjs +18 -0
  409. package/dist/plugins/seal/spans.mjs.map +1 -0
  410. package/dist/plugins/sui/auto-tick.mjs +17 -23
  411. package/dist/plugins/sui/auto-tick.mjs.map +1 -1
  412. package/dist/plugins/sui/chain-build-container.mjs +18 -0
  413. package/dist/plugins/sui/chain-build-container.mjs.map +1 -0
  414. package/dist/plugins/sui/chain-probe.d.mts +5 -5
  415. package/dist/plugins/sui/chain-probe.mjs +28 -58
  416. package/dist/plugins/sui/chain-probe.mjs.map +1 -1
  417. package/dist/plugins/sui/codegen.mjs +26 -0
  418. package/dist/plugins/sui/codegen.mjs.map +1 -1
  419. package/dist/plugins/sui/errors.d.mts +2 -29
  420. package/dist/plugins/sui/errors.mjs +1 -3
  421. package/dist/plugins/sui/errors.mjs.map +1 -1
  422. package/dist/plugins/sui/fork-faucet-strategy.d.mts +1 -0
  423. package/dist/plugins/sui/fork-faucet-strategy.mjs +55 -0
  424. package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -0
  425. package/dist/plugins/sui/fork-orchestration.d.mts +1 -0
  426. package/dist/plugins/sui/fork-orchestration.mjs +170 -2
  427. package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
  428. package/dist/plugins/sui/fork-transaction.d.mts +2 -0
  429. package/dist/plugins/sui/fork-transaction.mjs +92 -28
  430. package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
  431. package/dist/plugins/sui/index.d.mts +33 -70
  432. package/dist/plugins/sui/index.mjs +58 -35
  433. package/dist/plugins/sui/index.mjs.map +1 -1
  434. package/dist/plugins/{faucet/strategies/sui-local.mjs → sui/local-faucet-strategy.mjs} +10 -7
  435. package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -0
  436. package/dist/plugins/sui/mode/external.mjs +7 -13
  437. package/dist/plugins/sui/mode/external.mjs.map +1 -1
  438. package/dist/plugins/sui/mode/fork.mjs +136 -59
  439. package/dist/plugins/sui/mode/fork.mjs.map +1 -1
  440. package/dist/plugins/sui/mode/live.mjs +10 -16
  441. package/dist/plugins/sui/mode/live.mjs.map +1 -1
  442. package/dist/plugins/sui/mode/local.mjs +37 -36
  443. package/dist/plugins/sui/mode/local.mjs.map +1 -1
  444. package/dist/plugins/sui/mode/shared-boot.mjs +38 -115
  445. package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
  446. package/dist/plugins/sui/mode/shared.d.mts +1 -5
  447. package/dist/plugins/sui/mode/shared.mjs.map +1 -1
  448. package/dist/plugins/sui/mode/spec.d.mts +18 -0
  449. package/dist/plugins/sui/move-summary-runner.mjs +207 -0
  450. package/dist/plugins/sui/move-summary-runner.mjs.map +1 -0
  451. package/dist/plugins/sui/network-resolver.d.mts +4 -3
  452. package/dist/plugins/sui/service.mjs.map +1 -1
  453. package/dist/plugins/sui/snapshot.mjs +3 -0
  454. package/dist/plugins/sui/snapshot.mjs.map +1 -1
  455. package/dist/plugins/sui/spans.mjs +17 -0
  456. package/dist/plugins/sui/spans.mjs.map +1 -0
  457. package/dist/plugins/wallet/codegen.d.mts +0 -1
  458. package/dist/plugins/wallet/codegen.mjs +8 -9
  459. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  460. package/dist/plugins/wallet/index.d.mts +6 -8
  461. package/dist/plugins/wallet/index.mjs +46 -35
  462. package/dist/plugins/wallet/index.mjs.map +1 -1
  463. package/dist/plugins/wallet/origin-policy.mjs +12 -28
  464. package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
  465. package/dist/plugins/wallet/pairing.mjs +6 -4
  466. package/dist/plugins/wallet/pairing.mjs.map +1 -1
  467. package/dist/plugins/wallet/protocol.d.mts +1 -0
  468. package/dist/plugins/wallet/protocol.mjs +5 -44
  469. package/dist/plugins/wallet/protocol.mjs.map +1 -1
  470. package/dist/plugins/wallet/routable.mjs +2 -8
  471. package/dist/plugins/wallet/routable.mjs.map +1 -1
  472. package/dist/plugins/wallet/server.mjs +36 -44
  473. package/dist/plugins/wallet/server.mjs.map +1 -1
  474. package/dist/plugins/wallet/service.d.mts +1 -5
  475. package/dist/plugins/wallet/service.mjs +22 -18
  476. package/dist/plugins/wallet/service.mjs.map +1 -1
  477. package/dist/plugins/wallet/spans.mjs +22 -0
  478. package/dist/plugins/wallet/spans.mjs.map +1 -0
  479. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +33 -8
  480. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
  481. package/dist/plugins/walrus/codegen.d.mts +3 -3
  482. package/dist/plugins/walrus/codegen.mjs.map +1 -1
  483. package/dist/plugins/walrus/deploy-paths.mjs +21 -5
  484. package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
  485. package/dist/plugins/walrus/deploy.mjs +67 -69
  486. package/dist/plugins/walrus/deploy.mjs.map +1 -1
  487. package/dist/plugins/walrus/errors.mjs +1 -0
  488. package/dist/plugins/walrus/errors.mjs.map +1 -1
  489. package/dist/plugins/walrus/faucet-strategy.mjs +12 -7
  490. package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
  491. package/dist/plugins/walrus/index.d.mts +2 -13
  492. package/dist/plugins/walrus/index.mjs +18 -23
  493. package/dist/plugins/walrus/index.mjs.map +1 -1
  494. package/dist/plugins/walrus/mode/known-deploy.mjs +7 -5
  495. package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
  496. package/dist/plugins/walrus/mode/local-cluster.mjs +26 -7
  497. package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
  498. package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
  499. package/dist/plugins/walrus/routable.mjs +7 -23
  500. package/dist/plugins/walrus/routable.mjs.map +1 -1
  501. package/dist/plugins/walrus/service.mjs.map +1 -1
  502. package/dist/plugins/walrus/spans.mjs +18 -0
  503. package/dist/plugins/walrus/spans.mjs.map +1 -0
  504. package/dist/plugins/walrus/storage-nodes.d.mts +6 -2
  505. package/dist/plugins/walrus/storage-nodes.mjs +15 -8
  506. package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
  507. package/dist/plugins/walrus/wal-swap.mjs +58 -15
  508. package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
  509. package/dist/primitives/artifact-publisher.d.mts +1 -1
  510. package/dist/primitives/artifact-publisher.mjs +15 -0
  511. package/dist/primitives/artifact-publisher.mjs.map +1 -0
  512. package/dist/runtime/docker/client.mjs +17 -4
  513. package/dist/runtime/docker/client.mjs.map +1 -1
  514. package/dist/runtime/docker/container.mjs +219 -132
  515. package/dist/runtime/docker/container.mjs.map +1 -1
  516. package/dist/runtime/docker/errors.mjs +0 -6
  517. package/dist/runtime/docker/errors.mjs.map +1 -1
  518. package/dist/runtime/docker/exec.mjs +21 -18
  519. package/dist/runtime/docker/exec.mjs.map +1 -1
  520. package/dist/runtime/docker/image.mjs +36 -13
  521. package/dist/runtime/docker/image.mjs.map +1 -1
  522. package/dist/runtime/docker/index.mjs +14 -0
  523. package/dist/runtime/docker/inspect-and-decode.mjs +51 -0
  524. package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -0
  525. package/dist/runtime/docker/inventory.mjs +14 -11
  526. package/dist/runtime/docker/inventory.mjs.map +1 -1
  527. package/dist/runtime/docker/labels.mjs +38 -5
  528. package/dist/runtime/docker/labels.mjs.map +1 -1
  529. package/dist/runtime/docker/logs.d.mts +1 -1
  530. package/dist/runtime/docker/logs.mjs.map +1 -1
  531. package/dist/runtime/docker/network.mjs +83 -47
  532. package/dist/runtime/docker/network.mjs.map +1 -1
  533. package/dist/runtime/docker/render-run-args.mjs +82 -0
  534. package/dist/runtime/docker/render-run-args.mjs.map +1 -0
  535. package/dist/runtime/docker/service.d.mts +1 -1
  536. package/dist/runtime/docker/service.mjs +12 -7
  537. package/dist/runtime/docker/service.mjs.map +1 -1
  538. package/dist/runtime/docker/sweep.mjs +118 -43
  539. package/dist/runtime/docker/sweep.mjs.map +1 -1
  540. package/dist/runtime/docker/volume.mjs +2 -0
  541. package/dist/runtime/docker/volume.mjs.map +1 -1
  542. package/dist/runtime/docker/wrap.mjs +30 -1
  543. package/dist/runtime/docker/wrap.mjs.map +1 -1
  544. package/dist/substrate/cross-process.mjs +14 -8
  545. package/dist/substrate/cross-process.mjs.map +1 -1
  546. package/dist/substrate/event-time.mjs +33 -0
  547. package/dist/substrate/event-time.mjs.map +1 -0
  548. package/dist/substrate/events.d.mts +28 -8
  549. package/dist/substrate/identity.d.mts +2 -1
  550. package/dist/substrate/manifest.d.mts +68 -5
  551. package/dist/substrate/manifest.mjs +34 -5
  552. package/dist/substrate/manifest.mjs.map +1 -1
  553. package/dist/substrate/network.d.mts +3 -5
  554. package/dist/substrate/options.d.mts +0 -2
  555. package/dist/substrate/plugin.d.mts +13 -0
  556. package/dist/substrate/plugin.mjs +4 -2
  557. package/dist/substrate/plugin.mjs.map +1 -1
  558. package/dist/substrate/projection.d.mts +35 -3
  559. package/dist/substrate/runtime/artifact-publisher/index.mjs +19 -9
  560. package/dist/substrate/runtime/artifact-publisher/index.mjs.map +1 -1
  561. package/dist/substrate/runtime/atomic-write.mjs +97 -32
  562. package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
  563. package/dist/substrate/runtime/cache/index.mjs +3 -0
  564. package/dist/substrate/runtime/cache/schema.mjs +2 -2
  565. package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
  566. package/dist/substrate/runtime/cache/service.mjs +9 -7
  567. package/dist/substrate/runtime/cache/service.mjs.map +1 -1
  568. package/dist/substrate/runtime/capability-sinks/index.d.mts +1 -1
  569. package/dist/substrate/runtime/capability-sinks/index.mjs +3 -0
  570. package/dist/substrate/runtime/capability-sinks/layer.mjs +1 -1
  571. package/dist/substrate/runtime/capability-sinks/service.d.mts +61 -2
  572. package/dist/substrate/runtime/capability-sinks/service.mjs +21 -26
  573. package/dist/substrate/runtime/capability-sinks/service.mjs.map +1 -1
  574. package/dist/substrate/runtime/config-validation.d.mts +1 -13
  575. package/dist/substrate/runtime/config-validation.mjs +2 -91
  576. package/dist/substrate/runtime/config-validation.mjs.map +1 -1
  577. package/dist/substrate/runtime/container-runtime.d.mts +1 -0
  578. package/dist/substrate/runtime/container-runtime.mjs +2 -0
  579. package/dist/substrate/runtime/control-plane/domain.mjs +101 -0
  580. package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -0
  581. package/dist/substrate/runtime/control-plane/service.mjs +7 -0
  582. package/dist/substrate/runtime/control-plane/service.mjs.map +1 -0
  583. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +46 -19
  584. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
  585. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs +30 -12
  586. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
  587. package/dist/substrate/runtime/cross-process/command-channel/index.mjs +5 -0
  588. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs +11 -2
  589. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
  590. package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs +1 -1
  591. package/dist/substrate/runtime/cross-process/index.mjs +11 -0
  592. package/dist/substrate/runtime/cross-process/live-clock.mjs +34 -0
  593. package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -0
  594. package/dist/substrate/runtime/cross-process/liveness.mjs +52 -10
  595. package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
  596. package/dist/substrate/runtime/cross-process/lock.d.mts +1 -0
  597. package/dist/substrate/runtime/cross-process/lock.mjs +23 -0
  598. package/dist/substrate/runtime/cross-process/lock.mjs.map +1 -0
  599. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +64 -0
  600. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -0
  601. package/dist/substrate/runtime/cross-process/roster.mjs +97 -83
  602. package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
  603. package/dist/substrate/runtime/cross-process/self-pid.mjs +8 -0
  604. package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -0
  605. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +24 -28
  606. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +1 -1
  607. package/dist/substrate/runtime/cross-process/stack-lock.mjs +41 -20
  608. package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
  609. package/dist/substrate/runtime/current-plugin.mjs +2 -2
  610. package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
  611. package/dist/substrate/runtime/errors.mjs +12 -6
  612. package/dist/substrate/runtime/errors.mjs.map +1 -1
  613. package/dist/substrate/runtime/format-unknown-error.mjs +30 -0
  614. package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -0
  615. package/dist/substrate/runtime/host-bind-mount-owner.mjs +10 -0
  616. package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -0
  617. package/dist/substrate/runtime/host-gateway.mjs +20 -0
  618. package/dist/substrate/runtime/host-gateway.mjs.map +1 -0
  619. package/dist/substrate/runtime/host-tree-tar/index.mjs +61 -50
  620. package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
  621. package/dist/substrate/runtime/http-probe.d.mts +1 -1
  622. package/dist/substrate/runtime/index.mjs +14 -0
  623. package/dist/substrate/runtime/lease-broker/index.d.mts +1 -1
  624. package/dist/substrate/runtime/lease-broker/index.mjs +2 -0
  625. package/dist/substrate/runtime/lease-broker/service.d.mts +1 -64
  626. package/dist/substrate/runtime/lease-broker/service.mjs +1 -1
  627. package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
  628. package/dist/substrate/runtime/lifecycle/dep-graph.mjs +22 -3
  629. package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
  630. package/dist/substrate/runtime/lifecycle/index.mjs +9 -0
  631. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +1 -1
  632. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +20 -3
  633. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
  634. package/dist/substrate/runtime/lifecycle/selective-restart.mjs +1 -1
  635. package/dist/substrate/runtime/lifecycle/signals.mjs +5 -5
  636. package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
  637. package/dist/substrate/runtime/lifecycle/state-machine.mjs +1 -1
  638. package/dist/substrate/runtime/managed-container.d.mts +19 -1
  639. package/dist/substrate/runtime/managed-container.mjs +20 -1
  640. package/dist/substrate/runtime/managed-container.mjs.map +1 -1
  641. package/dist/substrate/runtime/manifest/index.mjs +3 -0
  642. package/dist/substrate/runtime/manifest/manifest.mjs +15 -11
  643. package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
  644. package/dist/substrate/runtime/mode-errors.mjs +20 -0
  645. package/dist/substrate/runtime/mode-errors.mjs.map +1 -0
  646. package/dist/substrate/runtime/observability/cascade-formatter.mjs +9 -13
  647. package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
  648. package/dist/substrate/runtime/observability/formatter-registry.mjs +19 -36
  649. package/dist/substrate/runtime/observability/formatter-registry.mjs.map +1 -1
  650. package/dist/substrate/runtime/observability/ignore-with-log.d.mts +1 -0
  651. package/dist/substrate/runtime/observability/ignore-with-log.mjs +17 -0
  652. package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -0
  653. package/dist/substrate/runtime/observability/index.d.mts +1 -1
  654. package/dist/substrate/runtime/observability/index.mjs +13 -0
  655. package/dist/substrate/runtime/observability/log-store.d.mts +1 -0
  656. package/dist/substrate/runtime/observability/log-store.mjs +175 -0
  657. package/dist/substrate/runtime/observability/log-store.mjs.map +1 -0
  658. package/dist/substrate/runtime/observability/logger.d.mts +2 -1
  659. package/dist/substrate/runtime/observability/logger.mjs +5 -9
  660. package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
  661. package/dist/substrate/runtime/observability/output-truncate.mjs +35 -0
  662. package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -0
  663. package/dist/substrate/runtime/observability/pretty-error.mjs +9 -12
  664. package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
  665. package/dist/substrate/runtime/observability/redaction.d.mts +2 -13
  666. package/dist/substrate/runtime/observability/redaction.mjs +8 -14
  667. package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
  668. package/dist/substrate/runtime/observability/span-store.d.mts +1 -0
  669. package/dist/substrate/runtime/observability/span-store.mjs +110 -0
  670. package/dist/substrate/runtime/observability/span-store.mjs.map +1 -0
  671. package/dist/substrate/runtime/observability/spans.mjs +14 -14
  672. package/dist/substrate/runtime/observability/spans.mjs.map +1 -1
  673. package/dist/substrate/runtime/observability/subprocess-capture.mjs +1 -1
  674. package/dist/substrate/runtime/passthrough-or-wrap.mjs +33 -0
  675. package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -0
  676. package/dist/substrate/runtime/paths.d.mts +1 -1
  677. package/dist/substrate/runtime/paths.mjs +4 -3
  678. package/dist/substrate/runtime/paths.mjs.map +1 -1
  679. package/dist/substrate/runtime/phase-preserving-produce.mjs +40 -0
  680. package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -0
  681. package/dist/substrate/runtime/port-broker/index.mjs +3 -0
  682. package/dist/substrate/runtime/port-broker/service.mjs +170 -126
  683. package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
  684. package/dist/substrate/runtime/post-acquire-tasks.mjs +1 -1
  685. package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
  686. package/dist/substrate/runtime/probes.d.mts +7 -5
  687. package/dist/substrate/runtime/probes.mjs +3 -1
  688. package/dist/substrate/runtime/probes.mjs.map +1 -1
  689. package/dist/substrate/runtime/projection/index.mjs +4 -0
  690. package/dist/substrate/runtime/projection/operational-endpoints.mjs +1 -2
  691. package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
  692. package/dist/substrate/runtime/projection/persisted.mjs +33 -15
  693. package/dist/substrate/runtime/projection/persisted.mjs.map +1 -1
  694. package/dist/substrate/runtime/projection/state-ref.mjs +26 -2
  695. package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
  696. package/dist/substrate/runtime/projection/update.mjs +88 -24
  697. package/dist/substrate/runtime/projection/update.mjs.map +1 -1
  698. package/dist/substrate/runtime/random-suffix.mjs +11 -0
  699. package/dist/substrate/runtime/random-suffix.mjs.map +1 -0
  700. package/dist/substrate/runtime/retry-policy.d.mts +59 -1
  701. package/dist/substrate/runtime/retry-policy.mjs +66 -1
  702. package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
  703. package/dist/substrate/runtime/routed-url.d.mts +1 -0
  704. package/dist/substrate/runtime/routed-url.mjs +79 -0
  705. package/dist/substrate/runtime/routed-url.mjs.map +1 -0
  706. package/dist/substrate/runtime/runtime-decode.d.mts +1 -1
  707. package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
  708. package/dist/substrate/runtime/scoped-http-server.mjs +2 -3
  709. package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
  710. package/dist/substrate/runtime/scoped-multimap/index.mjs +2 -0
  711. package/dist/substrate/runtime/scoped-multimap/service.mjs +52 -0
  712. package/dist/substrate/runtime/scoped-multimap/service.mjs.map +1 -0
  713. package/dist/substrate/runtime/scoped-ref-map/index.mjs +2 -0
  714. package/dist/substrate/runtime/scoped-ref-map/service.mjs +2 -2
  715. package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +1 -1
  716. package/dist/substrate/runtime/stage-and-swap/index.mjs +72 -4
  717. package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
  718. package/dist/substrate/runtime/state-store/index.d.mts +1 -0
  719. package/dist/substrate/runtime/state-store/index.mjs +3 -0
  720. package/dist/substrate/runtime/state-store/schema.d.mts +1 -0
  721. package/dist/substrate/runtime/state-store/schema.mjs +19 -14
  722. package/dist/substrate/runtime/state-store/schema.mjs.map +1 -1
  723. package/dist/substrate/runtime/state-store/service.d.mts +1 -0
  724. package/dist/substrate/runtime/state-store/service.mjs +145 -0
  725. package/dist/substrate/runtime/state-store/service.mjs.map +1 -0
  726. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.d.mts +1 -0
  727. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs +33 -0
  728. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -0
  729. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs +1 -1
  730. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
  731. package/dist/substrate/runtime/strategy-registry/index.mjs +4 -0
  732. package/dist/substrate/runtime/strategy-registry/service.mjs +19 -33
  733. package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
  734. package/dist/substrate/runtime/subnet-broker.mjs +60 -0
  735. package/dist/substrate/runtime/subnet-broker.mjs.map +1 -0
  736. package/dist/substrate/runtime/sui-execute/index.d.mts +21 -1
  737. package/dist/substrate/runtime/sui-execute/index.mjs +84 -20
  738. package/dist/substrate/runtime/sui-execute/index.mjs.map +1 -1
  739. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.d.mts +1 -0
  740. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs +32 -0
  741. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +1 -0
  742. package/dist/substrate/runtime/sui-ledger/object-ref.mjs +32 -0
  743. package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +1 -0
  744. package/dist/substrate/runtime/sui-move-build/index.mjs +93 -19
  745. package/dist/substrate/runtime/sui-move-build/index.mjs.map +1 -1
  746. package/dist/substrate/runtime/supervisor/acquire-node.mjs +181 -0
  747. package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -0
  748. package/dist/substrate/runtime/supervisor/background-tasks.mjs +144 -0
  749. package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -0
  750. package/dist/substrate/runtime/supervisor/command-loop.mjs +68 -0
  751. package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -0
  752. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +151 -0
  753. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +1 -0
  754. package/dist/substrate/runtime/supervisor/errors.d.mts +1 -0
  755. package/dist/substrate/runtime/supervisor/errors.mjs +9 -0
  756. package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -0
  757. package/dist/substrate/runtime/supervisor/index.d.mts +1 -0
  758. package/dist/substrate/runtime/supervisor/index.mjs +3 -0
  759. package/dist/substrate/runtime/supervisor/shutdown.mjs +51 -0
  760. package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -0
  761. package/dist/substrate/runtime/supervisor/start-supervisor.d.mts +1 -0
  762. package/dist/substrate/runtime/supervisor/start-supervisor.mjs +250 -0
  763. package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -0
  764. package/dist/substrate/runtime/supervisor/state.d.mts +1 -0
  765. package/dist/substrate/runtime/supervisor/state.mjs +22 -0
  766. package/dist/substrate/runtime/supervisor/state.mjs.map +1 -0
  767. package/dist/substrate/runtime/supervisor/teardown.mjs +57 -0
  768. package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -0
  769. package/dist/substrate/runtime/supervisor/types.d.mts +1 -0
  770. package/dist/substrate/runtime/supervisor/wiring.mjs +133 -0
  771. package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -0
  772. package/dist/substrate/runtime/typed-env.mjs +8 -0
  773. package/dist/substrate/runtime/typed-env.mjs.map +1 -0
  774. package/dist/substrate/state-store.d.mts +1 -0
  775. package/dist/substrate/versioned-doc-schema.mjs +17 -0
  776. package/dist/substrate/versioned-doc-schema.mjs.map +1 -0
  777. package/dist/substrate/versioned-doc-sync.mjs +94 -0
  778. package/dist/substrate/versioned-doc-sync.mjs.map +1 -0
  779. package/dist/surfaces/cli/command-tree.mjs.map +1 -1
  780. package/dist/surfaces/cli/commands/index.mjs +9 -0
  781. package/dist/surfaces/cli/commands/prune-picker-entry.mjs +13 -0
  782. package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -0
  783. package/dist/surfaces/cli/commands/prune-picker.mjs +3 -2
  784. package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
  785. package/dist/surfaces/cli/commands/prune.mjs +34 -17
  786. package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
  787. package/dist/surfaces/cli/commands/status.mjs +2 -2
  788. package/dist/surfaces/cli/commands/status.mjs.map +1 -1
  789. package/dist/surfaces/cli/commands/supervisor-presence.mjs +5 -3
  790. package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
  791. package/dist/surfaces/cli/commands/wipe.mjs +38 -4
  792. package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
  793. package/dist/surfaces/cli/errors.mjs +1 -1
  794. package/dist/surfaces/cli/errors.mjs.map +1 -1
  795. package/dist/surfaces/cli/flags.mjs +0 -1
  796. package/dist/surfaces/cli/flags.mjs.map +1 -1
  797. package/dist/surfaces/cli/index.mjs +98 -18
  798. package/dist/surfaces/cli/index.mjs.map +1 -1
  799. package/dist/surfaces/cli/output.mjs +1 -0
  800. package/dist/surfaces/cli/output.mjs.map +1 -1
  801. package/dist/surfaces/cli/sysexits.mjs +2 -1
  802. package/dist/surfaces/cli/sysexits.mjs.map +1 -1
  803. package/dist/surfaces/tui/app.mjs +25 -3
  804. package/dist/surfaces/tui/app.mjs.map +1 -1
  805. package/dist/surfaces/tui/display-derivation.mjs +23 -74
  806. package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
  807. package/dist/surfaces/tui/event-log.mjs +35 -23
  808. package/dist/surfaces/tui/event-log.mjs.map +1 -1
  809. package/dist/surfaces/tui/input.mjs +5 -1
  810. package/dist/surfaces/tui/input.mjs.map +1 -1
  811. package/dist/surfaces/tui/plain-renderer.mjs +70 -34
  812. package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
  813. package/package.json +24 -4
  814. package/dist/contracts/liveness-classifier.d.mts +0 -19
  815. package/dist/contracts/network-resolver.d.mts +0 -15
  816. package/dist/contracts/network-resolver.mjs +0 -7
  817. package/dist/contracts/network-resolver.mjs.map +0 -1
  818. package/dist/orchestrators/codegen/extras.mjs +0 -16
  819. package/dist/orchestrators/codegen/extras.mjs.map +0 -1
  820. package/dist/plugins/deepbook/routable.mjs +0 -21
  821. package/dist/plugins/deepbook/routable.mjs.map +0 -1
  822. package/dist/plugins/faucet/service.d.mts +0 -20
  823. package/dist/plugins/faucet/strategies/sui-local.d.mts +0 -23
  824. package/dist/plugins/faucet/strategies/sui-local.mjs.map +0 -1
  825. package/dist/plugins/sui/seed-objects.d.mts +0 -18
  826. package/dist/plugins/sui/seed-objects.mjs +0 -25
  827. package/dist/plugins/sui/seed-objects.mjs.map +0 -1
  828. package/dist/runtime/built-in-plugin-layers.mjs.map +0 -1
  829. package/dist/substrate/runtime/context-helpers.mjs +0 -32
  830. package/dist/substrate/runtime/context-helpers.mjs.map +0 -1
  831. package/dist/substrate/runtime/run.mjs.map +0 -1
  832. package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs +0 -30
  833. package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs.map +0 -1
  834. package/dist/substrate/runtime/supervisor.d.mts +0 -1
  835. package/dist/substrate/runtime/supervisor.mjs +0 -831
  836. package/dist/substrate/runtime/supervisor.mjs.map +0 -1
  837. package/dist/surfaces/cli/commands/doctor-probes.mjs.map +0 -1
  838. /package/dist/{substrate/runtime/strategy-registry/faucet-capability-for.d.mts → plugins/faucet/http.d.mts} +0 -0
@@ -1,14 +1,26 @@
1
1
  import { StackPathsService } from "../../substrate/runtime/paths.mjs";
2
- import { dockerRunOk } from "./client.mjs";
3
2
  import { acquireStackLock } from "../../substrate/runtime/cross-process/stack-lock.mjs";
4
3
  import { readClaims } from "../../substrate/runtime/cross-process/roster.mjs";
4
+ import { dockerRunOk } from "./client.mjs";
5
5
  import { ContainerRemoveFailed, DaemonUnreachable, ImageRemoveFailed, NetworkOperationFailed } from "./errors.mjs";
6
+ import { isMissingImageStderr, isMissingNetworkStderr, isNetworkInUseStderr, isNoSuchContainerStderr, wrapGeneric } from "./wrap.mjs";
6
7
  import { LabelKey } from "./labels.mjs";
7
- import { isNoSuchContainerStderr, wrapGeneric } from "./wrap.mjs";
8
- import { listContainers, listDevstackContainers, listDevstackImages, listDevstackNetworks, listDevstackRouterContainers, listDevstackVolumes, listImages, listNetworks, listVolumes } from "./inventory.mjs";
8
+ import { forceDisconnect, listAttachedContainers } from "./network.mjs";
9
+ import { listContainers, listDevstackContainers, listDevstackContainersByKind, listDevstackImages, listDevstackNetworks, listDevstackVolumes, listImages, listNetworks, listVolumes } from "./inventory.mjs";
10
+ import { NETWORK_REMOVE_RETRY_PROFILE, makeSpacedRetrySchedule } from "../../substrate/runtime/retry-policy.mjs";
9
11
  import { removeVolume } from "./volume.mjs";
10
12
  import { Effect } from "effect";
11
13
  //#region src/runtime/docker/sweep.ts
14
+ const removeManagedContainer = (name) => Effect.gen(function* () {
15
+ const res = yield* dockerRunOk("rm", ["-f", name]).pipe(Effect.mapError(wrapGeneric("docker.rm")));
16
+ if (res.exitCode === 0) return true;
17
+ if (isNoSuchContainerStderr(res.stderr)) return false;
18
+ return yield* Effect.fail(new ContainerRemoveFailed({
19
+ name,
20
+ stderr: res.stderr,
21
+ exitCode: res.exitCode
22
+ }));
23
+ });
12
24
  /** Sweep orphan containers matching the partial label tuple. Returns
13
25
  * the number of containers removed.
14
26
  *
@@ -42,31 +54,21 @@ const sweepOrphans = (labelMatch) => Effect.gen(function* () {
42
54
  yield* acquireStackLock(paths.stackLockFile).pipe(Effect.mapError(mapSubstrateError));
43
55
  const doc = yield* readClaims({
44
56
  stackLockFile: paths.stackLockFile,
45
- rosterFile: paths.rosterFile
57
+ rosterFile: paths.rosterFile,
58
+ containerClaimsFile: paths.containerClaimsFile
46
59
  }).pipe(Effect.mapError(mapSubstrateError));
47
60
  return new Set(doc.claims.map((c) => c.containerKey));
48
61
  }));
49
62
  let removed = 0;
50
63
  for (const c of containers) {
51
64
  if (claimNames.has(c.name)) continue;
52
- if ((yield* dockerRunOk("rm", ["-f", c.name]).pipe(Effect.catch(() => Effect.succeed({
53
- exitCode: 1,
54
- stdout: "",
55
- stderr: "sweep rm spawn failed"
56
- })))).exitCode === 0) removed += 1;
65
+ if (yield* removeManagedContainer(c.name).pipe(Effect.tapCause((cause) => Effect.logWarning("sweep: docker rm -f failed", {
66
+ name: c.name,
67
+ cause
68
+ })), Effect.catch(() => Effect.succeed(false)))) removed += 1;
57
69
  }
58
70
  return removed;
59
71
  }).pipe(Effect.withSpan("runtime.docker.sweep"));
60
- const removeManagedContainer = (name) => Effect.gen(function* () {
61
- const res = yield* dockerRunOk("rm", ["-f", name]).pipe(Effect.mapError(wrapGeneric("docker.rm")));
62
- if (res.exitCode === 0) return true;
63
- if (isNoSuchContainerStderr(res.stderr)) return false;
64
- return yield* Effect.fail(new ContainerRemoveFailed({
65
- name,
66
- stderr: res.stderr,
67
- exitCode: res.exitCode
68
- }));
69
- });
70
72
  /** Explicit teardown. Unlike `sweepOrphans`, this does not consult the
71
73
  * claim ledger; wipe/restore are intentional destructive operations. */
72
74
  const removeManagedContainers = (labelMatch) => Effect.gen(function* () {
@@ -85,16 +87,18 @@ const removeDevstackContainers = (labelMatch) => Effect.gen(function* () {
85
87
  }
86
88
  return removed;
87
89
  }).pipe(Effect.withSpan("runtime.docker.removeDevstackContainers"));
88
- const removeDevstackRouterContainers = (containerName) => Effect.gen(function* () {
89
- const containers = yield* listDevstackRouterContainers();
90
+ /** Remove devstack-managed containers whose generic `kind` label matches
91
+ * AND whose name matches. The L1 helper is plugin-blind: orchestrators
92
+ * pass the kind value they themselves stamp at create time. */
93
+ const removeDevstackContainersByKindAndName = (kind, containerName) => Effect.gen(function* () {
94
+ const containers = yield* listDevstackContainersByKind(kind);
90
95
  let removed = 0;
91
96
  for (const c of containers) {
92
97
  if (c.name !== containerName) continue;
93
98
  if (yield* removeManagedContainer(c.name)) removed += 1;
94
99
  }
95
100
  return removed;
96
- }).pipe(Effect.withSpan("runtime.docker.removeDevstackRouterContainers"));
97
- const isMissingImageStderr = (stderr) => /no such image|not found|reference does not exist/i.test(stderr);
101
+ }).pipe(Effect.withSpan("runtime.docker.removeDevstackContainersByKindAndName"));
98
102
  const removeManagedImage = (ref) => Effect.gen(function* () {
99
103
  const res = yield* dockerRunOk("image", [
100
104
  "rm",
@@ -115,6 +119,7 @@ const removeManagedImages = (labelMatch) => Effect.gen(function* () {
115
119
  const seen = /* @__PURE__ */ new Set();
116
120
  for (const image of images) {
117
121
  const ref = image.tag;
122
+ if (ref.endsWith(":<none>")) continue;
118
123
  if (seen.has(ref)) continue;
119
124
  seen.add(ref);
120
125
  if (yield* removeManagedImage(ref)) removed += 1;
@@ -135,7 +140,6 @@ const removeDevstackImages = (labelMatch) => Effect.gen(function* () {
135
140
  }
136
141
  return removed;
137
142
  }).pipe(Effect.withSpan("runtime.docker.removeDevstackImages"));
138
- const isMissingNetworkStderr = (stderr) => /no such network|network .* not found|not found/i.test(stderr);
139
143
  const removeManagedNetwork = (name) => Effect.gen(function* () {
140
144
  const res = yield* dockerRunOk("network", ["rm", name]).pipe(Effect.mapError(wrapGeneric("docker.network.rm")));
141
145
  if (res.exitCode === 0) return true;
@@ -155,43 +159,110 @@ const removeManagedNetworks = (labelMatch) => Effect.gen(function* () {
155
159
  }
156
160
  return removed;
157
161
  }).pipe(Effect.withSpan("runtime.docker.removeManagedNetworks"));
158
- const isNetworkInUseStderr = (stderr) => /active endpoints|has active endpoint|network .* is in use/i.test(stderr);
162
+ /** Parse `network X has active endpoints (name:"A" id:"B")` stderr
163
+ * Docker enumerates each blocking endpoint as a `(name:"…" id:"…")`
164
+ * tuple. Returns an empty array on non-matching stderr. */
165
+ const parseActiveEndpointsFromStderr = (stderr) => {
166
+ const out = [];
167
+ const re = /\(name:"([^"]+)"\s+id:"([^"]+)"\)/g;
168
+ let m;
169
+ while ((m = re.exec(stderr)) !== null) out.push({
170
+ name: m[1],
171
+ id: m[2]
172
+ });
173
+ return out;
174
+ };
159
175
  const removeManagedNetworkOnce = (name) => Effect.gen(function* () {
160
176
  const res = yield* dockerRunOk("network", ["rm", name]).pipe(Effect.mapError(wrapGeneric("docker.network.rm")));
161
- if (res.exitCode === 0) return "removed";
162
- if (isMissingNetworkStderr(res.stderr)) return "missing";
163
- if (isNetworkInUseStderr(res.stderr)) return "in-use";
177
+ if (res.exitCode === 0) return { kind: "removed" };
178
+ if (isMissingNetworkStderr(res.stderr)) return { kind: "missing" };
179
+ if (isNetworkInUseStderr(res.stderr)) return {
180
+ kind: "in-use",
181
+ stderr: res.stderr
182
+ };
164
183
  return yield* Effect.fail(new NetworkOperationFailed({
165
184
  op: "remove",
166
185
  network: name,
167
186
  stderr: res.stderr
168
187
  }));
169
188
  });
189
+ /** Best-effort network removal that actively evicts our own endpoints
190
+ * before declaring "in-use". Strategy on first in-use response:
191
+ * 1. inspect the network for attached endpoints
192
+ * 2. for each endpoint we own (`devstack.managed=true`), force-
193
+ * disconnect it
194
+ * 3. retry `network rm`
195
+ * Anything still holding the network after that is reported as a
196
+ * foreign holder — typically a non-devstack container, a test fixture,
197
+ * or a sibling stack we deliberately left alone. */
170
198
  const removeManagedNetworkBestEffort = (name, options) => Effect.gen(function* () {
171
- for (let attempt = 0; attempt < options.retryAttempts; attempt += 1) {
172
- const outcome = yield* removeManagedNetworkOnce(name);
173
- if (outcome !== "in-use") return outcome;
174
- if (attempt < options.retryAttempts - 1) yield* Effect.sleep(`${options.retryDelayMillis} millis`);
199
+ const first = yield* removeManagedNetworkOnce(name);
200
+ if (first.kind !== "in-use") return first;
201
+ const attachments = yield* listAttachedContainers(name).pipe(Effect.catch(() => Effect.succeed([])));
202
+ if (attachments.length > 0) {
203
+ const ownedNames = /* @__PURE__ */ new Set();
204
+ const all = yield* listDevstackContainers().pipe(Effect.catch(() => Effect.succeed([])));
205
+ for (const c of all) ownedNames.add(c.name);
206
+ for (const attached of attachments) {
207
+ if (!ownedNames.has(attached.name)) continue;
208
+ yield* forceDisconnect(attached.name, name).pipe(Effect.tapCause((cause) => Effect.logDebug("prune: force-disconnect failed; will retry rm anyway", {
209
+ network: name,
210
+ container: attached.name,
211
+ cause
212
+ })), Effect.catch(() => Effect.void));
213
+ }
175
214
  }
176
- return "in-use";
215
+ const lastStep = yield* removeManagedNetworkOnce(name).pipe(Effect.repeat({
216
+ schedule: makeSpacedRetrySchedule(options.retryDelayMillis, options.retryAttempts),
217
+ until: (step) => step.kind !== "in-use"
218
+ }));
219
+ if (lastStep.kind === "removed") return { kind: "removed" };
220
+ if (lastStep.kind === "missing") return { kind: "missing" };
221
+ const lastStderr = lastStep.stderr;
222
+ const finalAttachments = yield* listAttachedContainers(name).pipe(Effect.catch(() => Effect.succeed([])));
223
+ const ownedNames = /* @__PURE__ */ new Set();
224
+ const all = yield* listDevstackContainers().pipe(Effect.catch(() => Effect.succeed([])));
225
+ for (const c of all) ownedNames.add(c.name);
226
+ const foreign = finalAttachments.filter((a) => !ownedNames.has(a.name));
227
+ const attachedNames = new Set(finalAttachments.map((a) => a.name));
228
+ return {
229
+ kind: "in-use",
230
+ foreignHolders: foreign,
231
+ staleEndpoints: parseActiveEndpointsFromStderr(lastStderr).filter((ep) => !attachedNames.has(ep.name))
232
+ };
177
233
  });
178
234
  const removeDevstackNetworksBestEffort = (labelMatch, options = {}) => Effect.gen(function* () {
179
235
  const networks = yield* listDevstackNetworks();
180
236
  const retryOptions = {
181
- retryAttempts: options.retryAttempts ?? 6,
182
- retryDelayMillis: options.retryDelayMillis ?? 250
237
+ retryAttempts: options.retryAttempts ?? NETWORK_REMOVE_RETRY_PROFILE.attempts,
238
+ retryDelayMillis: options.retryDelayMillis ?? NETWORK_REMOVE_RETRY_PROFILE.delayMs
183
239
  };
184
240
  let removed = 0;
185
241
  let skippedInUse = 0;
242
+ const foreignHolders = [];
243
+ const staleEndpoints = [];
186
244
  for (const network of networks) {
187
245
  if (!labelsMatchAppStack(network.labels, labelMatch)) continue;
188
246
  const outcome = yield* removeManagedNetworkBestEffort(network.name, retryOptions);
189
- if (outcome === "removed") removed += 1;
190
- if (outcome === "in-use") skippedInUse += 1;
247
+ if (outcome.kind === "removed") removed += 1;
248
+ else if (outcome.kind === "in-use") {
249
+ skippedInUse += 1;
250
+ for (const container of outcome.foreignHolders) foreignHolders.push({
251
+ network: network.name,
252
+ container
253
+ });
254
+ for (const ep of outcome.staleEndpoints) staleEndpoints.push({
255
+ network: network.name,
256
+ name: ep.name,
257
+ id: ep.id
258
+ });
259
+ }
191
260
  }
192
261
  return {
193
262
  removed,
194
- skippedInUse
263
+ skippedInUse,
264
+ foreignHolders,
265
+ staleEndpoints
195
266
  };
196
267
  }).pipe(Effect.withSpan("runtime.docker.removeDevstackNetworksBestEffort"));
197
268
  const removeManagedVolumes = (labelMatch) => Effect.gen(function* () {
@@ -199,8 +270,10 @@ const removeManagedVolumes = (labelMatch) => Effect.gen(function* () {
199
270
  let removed = 0;
200
271
  for (const volume of volumes) {
201
272
  if (volume.labels[LabelKey.volumeMarker] !== "true") continue;
202
- yield* removeVolume(volume.name);
203
- removed += 1;
273
+ if (yield* removeVolume(volume.name).pipe(Effect.tapCause((cause) => Effect.logWarning("sweep: docker volume rm failed", {
274
+ name: volume.name,
275
+ cause
276
+ })), Effect.as(true), Effect.catch(() => Effect.succeed(false)))) removed += 1;
204
277
  }
205
278
  return removed;
206
279
  }).pipe(Effect.withSpan("runtime.docker.removeManagedVolumes"));
@@ -209,12 +282,14 @@ const removeDevstackVolumes = (labelMatch) => Effect.gen(function* () {
209
282
  let removed = 0;
210
283
  for (const volume of volumes) {
211
284
  if (!labelsMatchAppStack(volume.labels, labelMatch)) continue;
212
- yield* removeVolume(volume.name);
213
- removed += 1;
285
+ if (yield* removeVolume(volume.name).pipe(Effect.tapCause((cause) => Effect.logWarning("sweep: docker volume rm failed", {
286
+ name: volume.name,
287
+ cause
288
+ })), Effect.as(true), Effect.catch(() => Effect.succeed(false)))) removed += 1;
214
289
  }
215
290
  return removed;
216
291
  }).pipe(Effect.withSpan("runtime.docker.removeDevstackVolumes"));
217
292
  //#endregion
218
- export { removeDevstackContainers, removeDevstackImages, removeDevstackNetworksBestEffort, removeDevstackRouterContainers, removeDevstackVolumes, removeManagedContainers, removeManagedImages, removeManagedNetworks, removeManagedVolumes, sweepOrphans };
293
+ export { removeDevstackContainers, removeDevstackContainersByKindAndName, removeDevstackImages, removeDevstackNetworksBestEffort, removeDevstackVolumes, removeManagedContainers, removeManagedImages, removeManagedNetworks, removeManagedVolumes, sweepOrphans };
219
294
 
220
295
  //# sourceMappingURL=sweep.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"sweep.mjs","names":[],"sources":["../../../src/runtime/docker/sweep.ts"],"sourcesContent":["// Orphan sweep.\n//\n// Architecture § Container runtime § Sweep timing:\n// The supervisor invokes `sweepOrphans` AT BOOT, BEFORE any plugin\n// acquire fires. Sweep finds containers stamped with the engine's\n// label tuple but with no live process holder, and reclaims them\n// (force-remove) under `stack.lock`.\n//\n// Orphan = labelled with `(app, stack)` AND NOT in the active\n// container-claim ledger AND NOT held by any live peer.\n//\n// \"Live peer\" = roster holder whose PID+startTime liveness check\n// succeeds. The cross-process roster + claim-ledger is the authority;\n// we never use docker labels alone (a peer's container counts as\n// claimed even if our claim ledger is empty for it).\n\nimport { Effect } from 'effect';\n\nimport type { ContainerLabelTuple } from '../../contracts/snapshotable.ts';\nimport { readClaims, type ContainerClaim } from '../../substrate/runtime/cross-process/roster.ts';\nimport { acquireStackLock } from '../../substrate/runtime/cross-process/stack-lock.ts';\nimport { StackPathsService } from '../../substrate/runtime/paths.ts';\nimport { DockerHost, DockerSpawner, dockerRunOk } from './client.ts';\nimport {\n\tContainerRemoveFailed,\n\tDaemonUnreachable,\n\tImageRemoveFailed,\n\tNetworkOperationFailed,\n\ttype DockerRuntimeError,\n} from './errors.ts';\nimport {\n\tlistContainers,\n\tlistDevstackContainers,\n\tlistDevstackImages,\n\tlistDevstackNetworks,\n\tlistDevstackRouterContainers,\n\tlistDevstackVolumes,\n\tlistImages,\n\tlistNetworks,\n\tlistVolumes,\n} from './inventory.ts';\nimport { LabelKey } from './labels.ts';\nimport { removeVolume } from './volume.ts';\nimport { isNoSuchContainerStderr, wrapGeneric } from './wrap.ts';\n\n/** Sweep orphan containers matching the partial label tuple. Returns\n * the number of containers removed.\n *\n * The sweep:\n *\n * 1. Lists all containers with the canonical `devstack.managed=true`\n * + caller-supplied partial tuple (typically `{app, stack}`).\n * 2. Reads the container-claim ledger (the file held alongside\n * roster.json; mutated only under stack.lock).\n * 3. Under stack.lock, removes any container whose name is NOT in\n * the ledger.\n *\n * Concurrent sweepers are serialized by stack.lock. The architecture\n * says briefly here — we hold the lock only across the read + the\n * remove decision; we DON'T hold it across the individual `docker\n * rm` calls (which can be slow). The cost: a peer that registered a\n * claim between our decision and our rm gets its container rm'd\n * anyway. The mitigation: this only fires on the BOOT path before\n * any plugin acquires, so the steady-state \"peer just claimed\" race\n * is not in the picture. */\nexport const sweepOrphans = (\n\tlabelMatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner | StackPathsService> =>\n\tEffect.gen(function* () {\n\t\tconst paths = yield* StackPathsService;\n\t\tconst containers = yield* listContainers(labelMatch);\n\t\tif (containers.length === 0) return 0;\n\n\t\t// Under stack.lock, snapshot the claim ledger. The decision\n\t\t// (which to remove) happens here; the rm calls happen outside\n\t\t// the lock.\n\t\tconst mapSubstrateError = (cause: unknown): DockerRuntimeError =>\n\t\t\tnew DaemonUnreachable({\n\t\t\t\top: 'cross-process.sweep',\n\t\t\t\tdetail: `sweep substrate read failed: ${String(cause)}`,\n\t\t\t\tcause,\n\t\t\t});\n\t\tconst claimNames = yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* acquireStackLock(paths.stackLockFile).pipe(Effect.mapError(mapSubstrateError));\n\t\t\t\tconst doc = yield* readClaims({\n\t\t\t\t\tstackLockFile: paths.stackLockFile,\n\t\t\t\t\trosterFile: paths.rosterFile,\n\t\t\t\t}).pipe(Effect.mapError(mapSubstrateError));\n\t\t\t\treturn new Set(doc.claims.map((c: ContainerClaim) => c.containerKey));\n\t\t\t}),\n\t\t);\n\n\t\t// Sweep — best-effort per container. Individual rm failures\n\t\t// don't poison the whole sweep.\n\t\tlet removed = 0;\n\t\tfor (const c of containers) {\n\t\t\tif (claimNames.has(c.name)) continue;\n\t\t\tconst res = yield* dockerRunOk('rm', ['-f', c.name]).pipe(\n\t\t\t\tEffect.catch(() =>\n\t\t\t\t\tEffect.succeed({ exitCode: 1, stdout: '', stderr: 'sweep rm spawn failed' }),\n\t\t\t\t),\n\t\t\t);\n\t\t\tif (res.exitCode === 0) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.sweep'));\n\nconst removeManagedContainer = (\n\tname: string,\n): Effect.Effect<boolean, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('rm', ['-f', name]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.rm')),\n\t\t);\n\t\tif (res.exitCode === 0) return true;\n\t\tif (isNoSuchContainerStderr(res.stderr)) return false;\n\t\treturn yield* Effect.fail(\n\t\t\tnew ContainerRemoveFailed({\n\t\t\t\tname,\n\t\t\t\tstderr: res.stderr,\n\t\t\t\texitCode: res.exitCode,\n\t\t\t}),\n\t\t);\n\t});\n\n/** Explicit teardown. Unlike `sweepOrphans`, this does not consult the\n * claim ledger; wipe/restore are intentional destructive operations. */\nexport const removeManagedContainers = (\n\tlabelMatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst containers = yield* listContainers(labelMatch);\n\t\tlet removed = 0;\n\t\tfor (const c of containers) {\n\t\t\tconst didRemove = yield* removeManagedContainer(c.name);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeManagedContainers'));\n\nconst labelsMatchAppStack = (\n\tlabels: Readonly<Record<string, string>>,\n\tmatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n): boolean => labels[LabelKey.app] === match.app && labels[LabelKey.stack] === match.stack;\n\nexport const removeDevstackContainers = (\n\tlabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst containers = yield* listDevstackContainers();\n\t\tlet removed = 0;\n\t\tfor (const c of containers) {\n\t\t\tif (!labelsMatchAppStack(c.labels, labelMatch)) continue;\n\t\t\tconst didRemove = yield* removeManagedContainer(c.name);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackContainers'));\n\nexport const removeDevstackRouterContainers = (\n\tcontainerName: string,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst containers = yield* listDevstackRouterContainers();\n\t\tlet removed = 0;\n\t\tfor (const c of containers) {\n\t\t\tif (c.name !== containerName) continue;\n\t\t\tconst didRemove = yield* removeManagedContainer(c.name);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackRouterContainers'));\n\nconst isMissingImageStderr = (stderr: string): boolean =>\n\t/no such image|not found|reference does not exist/i.test(stderr);\n\nconst removeManagedImage = (\n\tref: string,\n): Effect.Effect<boolean, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('image', ['rm', '-f', ref]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.image.rm')),\n\t\t);\n\t\tif (res.exitCode === 0) return true;\n\t\tif (isMissingImageStderr(res.stderr)) return false;\n\t\treturn yield* Effect.fail(\n\t\t\tnew ImageRemoveFailed({\n\t\t\t\tref,\n\t\t\t\tstderr: res.stderr,\n\t\t\t\texitCode: res.exitCode,\n\t\t\t}),\n\t\t);\n\t});\n\nexport const removeManagedImages = (\n\tlabelMatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst images = yield* listImages(labelMatch);\n\t\tlet removed = 0;\n\t\tconst seen = new Set<string>();\n\t\tfor (const image of images) {\n\t\t\tconst ref = image.tag;\n\t\t\tif (seen.has(ref)) continue;\n\t\t\tseen.add(ref);\n\t\t\tconst didRemove = yield* removeManagedImage(ref);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeManagedImages'));\n\nexport const removeDevstackImages = (\n\tlabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst images = yield* listDevstackImages();\n\t\tlet removed = 0;\n\t\tconst seen = new Set<string>();\n\t\tfor (const image of images) {\n\t\t\tif (!labelsMatchAppStack(image.labels, labelMatch)) continue;\n\t\t\tconst ref = image.tag;\n\t\t\tif (ref.endsWith(':<none>')) continue;\n\t\t\tif (seen.has(ref)) continue;\n\t\t\tseen.add(ref);\n\t\t\tconst didRemove = yield* removeManagedImage(ref);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackImages'));\n\nconst isMissingNetworkStderr = (stderr: string): boolean =>\n\t/no such network|network .* not found|not found/i.test(stderr);\n\nconst removeManagedNetwork = (\n\tname: string,\n): Effect.Effect<boolean, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('network', ['rm', name]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.network.rm')),\n\t\t);\n\t\tif (res.exitCode === 0) return true;\n\t\tif (isMissingNetworkStderr(res.stderr)) return false;\n\t\treturn yield* Effect.fail(\n\t\t\tnew NetworkOperationFailed({ op: 'remove', network: name, stderr: res.stderr }),\n\t\t);\n\t});\n\nexport const removeManagedNetworks = (\n\tlabelMatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst networks = yield* listNetworks(labelMatch);\n\t\tlet removed = 0;\n\t\tfor (const network of networks) {\n\t\t\tif (network.labels[LabelKey.networkMarker] !== 'true') continue;\n\t\t\tconst didRemove = yield* removeManagedNetwork(network.name);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeManagedNetworks'));\n\nconst isNetworkInUseStderr = (stderr: string): boolean =>\n\t/active endpoints|has active endpoint|network .* is in use/i.test(stderr);\n\ntype NetworkRemoveOutcome = 'removed' | 'missing' | 'in-use';\n\nexport interface DevstackNetworkRemovalSummary {\n\treadonly removed: number;\n\treadonly skippedInUse: number;\n}\n\ninterface DevstackNetworkRemovalOptions {\n\treadonly retryAttempts?: number;\n\treadonly retryDelayMillis?: number;\n}\n\nconst removeManagedNetworkOnce = (\n\tname: string,\n): Effect.Effect<NetworkRemoveOutcome, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('network', ['rm', name]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.network.rm')),\n\t\t);\n\t\tif (res.exitCode === 0) return 'removed' as const;\n\t\tif (isMissingNetworkStderr(res.stderr)) return 'missing' as const;\n\t\tif (isNetworkInUseStderr(res.stderr)) return 'in-use' as const;\n\t\treturn yield* Effect.fail(\n\t\t\tnew NetworkOperationFailed({ op: 'remove', network: name, stderr: res.stderr }),\n\t\t);\n\t});\n\nconst removeManagedNetworkBestEffort = (\n\tname: string,\n\toptions: Required<DevstackNetworkRemovalOptions>,\n): Effect.Effect<NetworkRemoveOutcome, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tfor (let attempt = 0; attempt < options.retryAttempts; attempt += 1) {\n\t\t\tconst outcome = yield* removeManagedNetworkOnce(name);\n\t\t\tif (outcome !== 'in-use') return outcome;\n\t\t\tif (attempt < options.retryAttempts - 1) {\n\t\t\t\tyield* Effect.sleep(`${options.retryDelayMillis} millis`);\n\t\t\t}\n\t\t}\n\t\treturn 'in-use' as const;\n\t});\n\nexport const removeDevstackNetworks = (\n\tlabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst networks = yield* listDevstackNetworks();\n\t\tlet removed = 0;\n\t\tfor (const network of networks) {\n\t\t\tif (!labelsMatchAppStack(network.labels, labelMatch)) continue;\n\t\t\tconst didRemove = yield* removeManagedNetwork(network.name);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackNetworks'));\n\nexport const removeDevstackNetworksBestEffort = (\n\tlabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n\toptions: DevstackNetworkRemovalOptions = {},\n): Effect.Effect<DevstackNetworkRemovalSummary, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst networks = yield* listDevstackNetworks();\n\t\tconst retryOptions = {\n\t\t\tretryAttempts: options.retryAttempts ?? 6,\n\t\t\tretryDelayMillis: options.retryDelayMillis ?? 250,\n\t\t};\n\t\tlet removed = 0;\n\t\tlet skippedInUse = 0;\n\t\tfor (const network of networks) {\n\t\t\tif (!labelsMatchAppStack(network.labels, labelMatch)) continue;\n\t\t\tconst outcome = yield* removeManagedNetworkBestEffort(network.name, retryOptions);\n\t\t\tif (outcome === 'removed') removed += 1;\n\t\t\tif (outcome === 'in-use') skippedInUse += 1;\n\t\t}\n\t\treturn { removed, skippedInUse };\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackNetworksBestEffort'));\n\nexport const removeManagedVolumes = (\n\tlabelMatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst volumes = yield* listVolumes(labelMatch);\n\t\tlet removed = 0;\n\t\tfor (const volume of volumes) {\n\t\t\tif (volume.labels[LabelKey.volumeMarker] !== 'true') continue;\n\t\t\tyield* removeVolume(volume.name);\n\t\t\tremoved += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeManagedVolumes'));\n\nexport const removeDevstackVolumes = (\n\tlabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst volumes = yield* listDevstackVolumes();\n\t\tlet removed = 0;\n\t\tfor (const volume of volumes) {\n\t\t\tif (!labelsMatchAppStack(volume.labels, labelMatch)) continue;\n\t\t\tyield* removeVolume(volume.name);\n\t\t\tremoved += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackVolumes'));\n"],"mappings":"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"}
1
+ {"version":3,"file":"sweep.mjs","names":[],"sources":["../../../src/runtime/docker/sweep.ts"],"sourcesContent":["// Orphan sweep.\n//\n// Architecture § Container runtime § Sweep timing:\n// The supervisor invokes `sweepOrphans` AT BOOT, BEFORE any plugin\n// acquire fires. Sweep finds containers stamped with the engine's\n// label tuple but with no live process holder, and reclaims them\n// (force-remove) under `stack.lock`.\n//\n// Orphan = labelled with `(app, stack)` AND NOT in the active\n// container-claim ledger AND NOT held by any live peer.\n//\n// \"Live peer\" = roster holder whose PID+startTime liveness check\n// succeeds. The cross-process roster + claim-ledger is the authority;\n// we never use docker labels alone (a peer's container counts as\n// claimed even if our claim ledger is empty for it).\n\nimport { Effect } from 'effect';\n\nimport type { ContainerLabelTuple } from '../../contracts/snapshotable.ts';\nimport { readClaims, type ContainerClaim } from '../../substrate/runtime/cross-process/roster.ts';\nimport { acquireStackLock } from '../../substrate/runtime/cross-process/stack-lock.ts';\nimport { StackPathsService } from '../../substrate/runtime/paths.ts';\nimport {\n\tmakeSpacedRetrySchedule,\n\tNETWORK_REMOVE_RETRY_PROFILE,\n} from '../../substrate/runtime/retry-policy.ts';\nimport { DockerHost, DockerSpawner, dockerRunOk } from './client.ts';\nimport {\n\tContainerRemoveFailed,\n\tDaemonUnreachable,\n\tImageRemoveFailed,\n\tNetworkOperationFailed,\n\ttype DockerRuntimeError,\n} from './errors.ts';\nimport {\n\tlistContainers,\n\tlistDevstackContainers,\n\tlistDevstackContainersByKind,\n\tlistDevstackImages,\n\tlistDevstackNetworks,\n\tlistDevstackVolumes,\n\tlistImages,\n\tlistNetworks,\n\tlistVolumes,\n} from './inventory.ts';\nimport { LabelKey } from './labels.ts';\nimport {\n\tforceDisconnect,\n\tlistAttachedContainers,\n\ttype NetworkAttachedEndpoint,\n} from './network.ts';\nimport { removeVolume } from './volume.ts';\nimport {\n\tisMissingImageStderr,\n\tisMissingNetworkStderr,\n\tisNetworkInUseStderr,\n\tisNoSuchContainerStderr,\n\twrapGeneric,\n} from './wrap.ts';\n\nconst removeManagedContainer = (\n\tname: string,\n): Effect.Effect<boolean, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('rm', ['-f', name]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.rm')),\n\t\t);\n\t\tif (res.exitCode === 0) return true;\n\t\tif (isNoSuchContainerStderr(res.stderr)) return false;\n\t\treturn yield* Effect.fail(\n\t\t\tnew ContainerRemoveFailed({\n\t\t\t\tname,\n\t\t\t\tstderr: res.stderr,\n\t\t\t\texitCode: res.exitCode,\n\t\t\t}),\n\t\t);\n\t});\n\n/** Sweep orphan containers matching the partial label tuple. Returns\n * the number of containers removed.\n *\n * The sweep:\n *\n * 1. Lists all containers with the canonical `devstack.managed=true`\n * + caller-supplied partial tuple (typically `{app, stack}`).\n * 2. Reads the container-claim ledger (the file held alongside\n * roster.json; mutated only under stack.lock).\n * 3. Under stack.lock, removes any container whose name is NOT in\n * the ledger.\n *\n * Concurrent sweepers are serialized by stack.lock. The architecture\n * says briefly here — we hold the lock only across the read + the\n * remove decision; we DON'T hold it across the individual `docker\n * rm` calls (which can be slow). The cost: a peer that registered a\n * claim between our decision and our rm gets its container rm'd\n * anyway. The mitigation: this only fires on the BOOT path before\n * any plugin acquires, so the steady-state \"peer just claimed\" race\n * is not in the picture. */\nexport const sweepOrphans = (\n\tlabelMatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner | StackPathsService> =>\n\tEffect.gen(function* () {\n\t\tconst paths = yield* StackPathsService;\n\t\tconst containers = yield* listContainers(labelMatch);\n\t\tif (containers.length === 0) return 0;\n\n\t\t// Under stack.lock, snapshot the claim ledger. The decision\n\t\t// (which to remove) happens here; the rm calls happen outside\n\t\t// the lock.\n\t\tconst mapSubstrateError = (cause: unknown): DockerRuntimeError =>\n\t\t\tnew DaemonUnreachable({\n\t\t\t\top: 'cross-process.sweep',\n\t\t\t\tdetail: `sweep substrate read failed: ${String(cause)}`,\n\t\t\t\tcause,\n\t\t\t});\n\t\tconst claimNames = yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* acquireStackLock(paths.stackLockFile).pipe(Effect.mapError(mapSubstrateError));\n\t\t\t\tconst doc = yield* readClaims({\n\t\t\t\t\tstackLockFile: paths.stackLockFile,\n\t\t\t\t\trosterFile: paths.rosterFile,\n\t\t\t\t\tcontainerClaimsFile: paths.containerClaimsFile,\n\t\t\t\t}).pipe(Effect.mapError(mapSubstrateError));\n\t\t\t\treturn new Set(doc.claims.map((c: ContainerClaim) => c.containerKey));\n\t\t\t}),\n\t\t);\n\n\t\t// Sweep — best-effort per container. Individual rm failures\n\t\t// don't poison the whole sweep, but they DO need operator\n\t\t// visibility: a broken docker socket would otherwise silently\n\t\t// swallow every entry. Surface the cause via `logWarning` so the\n\t\t// supervisor's cascade and structured-log consumers can investigate.\n\t\t// We reuse `removeManagedContainer` so the idempotent \"no such\n\t\t// container\" path (a container reaped between list+rm) doesn't\n\t\t// trip the warning logger.\n\t\tlet removed = 0;\n\t\tfor (const c of containers) {\n\t\t\tif (claimNames.has(c.name)) continue;\n\t\t\tconst didRemove = yield* removeManagedContainer(c.name).pipe(\n\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\tEffect.logWarning('sweep: docker rm -f failed', { name: c.name, cause }),\n\t\t\t\t),\n\t\t\t\tEffect.catch(() => Effect.succeed(false)),\n\t\t\t);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.sweep'));\n\n/** Explicit teardown. Unlike `sweepOrphans`, this does not consult the\n * claim ledger; wipe/restore are intentional destructive operations. */\nexport const removeManagedContainers = (\n\tlabelMatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst containers = yield* listContainers(labelMatch);\n\t\tlet removed = 0;\n\t\tfor (const c of containers) {\n\t\t\tconst didRemove = yield* removeManagedContainer(c.name);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeManagedContainers'));\n\nconst labelsMatchAppStack = (\n\tlabels: Readonly<Record<string, string>>,\n\tmatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n): boolean => labels[LabelKey.app] === match.app && labels[LabelKey.stack] === match.stack;\n\nexport const removeDevstackContainers = (\n\tlabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst containers = yield* listDevstackContainers();\n\t\tlet removed = 0;\n\t\tfor (const c of containers) {\n\t\t\tif (!labelsMatchAppStack(c.labels, labelMatch)) continue;\n\t\t\tconst didRemove = yield* removeManagedContainer(c.name);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackContainers'));\n\n/** Remove devstack-managed containers whose generic `kind` label matches\n * AND whose name matches. The L1 helper is plugin-blind: orchestrators\n * pass the kind value they themselves stamp at create time. */\nexport const removeDevstackContainersByKindAndName = (\n\tkind: string,\n\tcontainerName: string,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst containers = yield* listDevstackContainersByKind(kind);\n\t\tlet removed = 0;\n\t\tfor (const c of containers) {\n\t\t\tif (c.name !== containerName) continue;\n\t\t\tconst didRemove = yield* removeManagedContainer(c.name);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackContainersByKindAndName'));\n\nconst removeManagedImage = (\n\tref: string,\n): Effect.Effect<boolean, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('image', ['rm', '-f', ref]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.image.rm')),\n\t\t);\n\t\tif (res.exitCode === 0) return true;\n\t\tif (isMissingImageStderr(res.stderr)) return false;\n\t\treturn yield* Effect.fail(\n\t\t\tnew ImageRemoveFailed({\n\t\t\t\tref,\n\t\t\t\tstderr: res.stderr,\n\t\t\t\texitCode: res.exitCode,\n\t\t\t}),\n\t\t);\n\t});\n\nexport const removeManagedImages = (\n\tlabelMatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst images = yield* listImages(labelMatch);\n\t\tlet removed = 0;\n\t\tconst seen = new Set<string>();\n\t\tfor (const image of images) {\n\t\t\tconst ref = image.tag;\n\t\t\tif (ref.endsWith(':<none>')) continue;\n\t\t\tif (seen.has(ref)) continue;\n\t\t\tseen.add(ref);\n\t\t\tconst didRemove = yield* removeManagedImage(ref);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeManagedImages'));\n\nexport const removeDevstackImages = (\n\tlabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst images = yield* listDevstackImages();\n\t\tlet removed = 0;\n\t\tconst seen = new Set<string>();\n\t\tfor (const image of images) {\n\t\t\tif (!labelsMatchAppStack(image.labels, labelMatch)) continue;\n\t\t\tconst ref = image.tag;\n\t\t\tif (ref.endsWith(':<none>')) continue;\n\t\t\tif (seen.has(ref)) continue;\n\t\t\tseen.add(ref);\n\t\t\tconst didRemove = yield* removeManagedImage(ref);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackImages'));\n\nconst removeManagedNetwork = (\n\tname: string,\n): Effect.Effect<boolean, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('network', ['rm', name]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.network.rm')),\n\t\t);\n\t\tif (res.exitCode === 0) return true;\n\t\tif (isMissingNetworkStderr(res.stderr)) return false;\n\t\treturn yield* Effect.fail(\n\t\t\tnew NetworkOperationFailed({ op: 'remove', network: name, stderr: res.stderr }),\n\t\t);\n\t});\n\nexport const removeManagedNetworks = (\n\tlabelMatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst networks = yield* listNetworks(labelMatch);\n\t\tlet removed = 0;\n\t\tfor (const network of networks) {\n\t\t\tif (network.labels[LabelKey.networkMarker] !== 'true') continue;\n\t\t\tconst didRemove = yield* removeManagedNetwork(network.name);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeManagedNetworks'));\n\n/** Parse `network X has active endpoints (name:\"A\" id:\"B\")` stderr —\n * Docker enumerates each blocking endpoint as a `(name:\"…\" id:\"…\")`\n * tuple. Returns an empty array on non-matching stderr. */\nconst parseActiveEndpointsFromStderr = (\n\tstderr: string,\n): ReadonlyArray<{ readonly name: string; readonly id: string }> => {\n\tconst out: Array<{ name: string; id: string }> = [];\n\tconst re = /\\(name:\"([^\"]+)\"\\s+id:\"([^\"]+)\"\\)/g;\n\tlet m: RegExpExecArray | null;\n\twhile ((m = re.exec(stderr)) !== null) {\n\t\tout.push({ name: m[1]!, id: m[2]! });\n\t}\n\treturn out;\n};\n\n/** One foreign endpoint preventing a network from being removed —\n * surfaced in the prune summary so the operator can investigate. */\nexport interface ForeignNetworkHolder {\n\treadonly network: string;\n\treadonly container: NetworkAttachedEndpoint;\n}\n\n/** A `(name, id)` reference that appears in Docker's `network rm`\n * \"active endpoints\" error but is NOT addressable via `network\n * disconnect -f` and does NOT appear in `network inspect`'s\n * Containers map. Symptom of a Docker engine bug where the bridge\n * driver leaks endpoint metadata for a container that no longer\n * exists. Only a Docker daemon restart reclaims these. */\nexport interface StaleNetworkEndpoint {\n\treadonly network: string;\n\treadonly name: string;\n\treadonly id: string;\n}\n\ntype NetworkRemoveStep =\n\t| { readonly kind: 'removed' }\n\t| { readonly kind: 'missing' }\n\t| { readonly kind: 'in-use'; readonly stderr: string };\n\ntype NetworkRemoveOutcome =\n\t| { readonly kind: 'removed' }\n\t| { readonly kind: 'missing' }\n\t| {\n\t\t\treadonly kind: 'in-use';\n\t\t\treadonly foreignHolders: ReadonlyArray<NetworkAttachedEndpoint>;\n\t\t\treadonly staleEndpoints: ReadonlyArray<{ readonly name: string; readonly id: string }>;\n\t };\n\nexport interface DevstackNetworkRemovalSummary {\n\treadonly removed: number;\n\treadonly skippedInUse: number;\n\treadonly foreignHolders: ReadonlyArray<ForeignNetworkHolder>;\n\treadonly staleEndpoints: ReadonlyArray<StaleNetworkEndpoint>;\n}\n\ninterface DevstackNetworkRemovalOptions {\n\treadonly retryAttempts?: number;\n\treadonly retryDelayMillis?: number;\n}\n\nconst removeManagedNetworkOnce = (\n\tname: string,\n): Effect.Effect<NetworkRemoveStep, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('network', ['rm', name]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.network.rm')),\n\t\t);\n\t\tif (res.exitCode === 0) return { kind: 'removed' as const };\n\t\tif (isMissingNetworkStderr(res.stderr)) return { kind: 'missing' as const };\n\t\tif (isNetworkInUseStderr(res.stderr)) {\n\t\t\treturn { kind: 'in-use' as const, stderr: res.stderr };\n\t\t}\n\t\treturn yield* Effect.fail(\n\t\t\tnew NetworkOperationFailed({ op: 'remove', network: name, stderr: res.stderr }),\n\t\t);\n\t});\n\n/** Best-effort network removal that actively evicts our own endpoints\n * before declaring \"in-use\". Strategy on first in-use response:\n * 1. inspect the network for attached endpoints\n * 2. for each endpoint we own (`devstack.managed=true`), force-\n * disconnect it\n * 3. retry `network rm`\n * Anything still holding the network after that is reported as a\n * foreign holder — typically a non-devstack container, a test fixture,\n * or a sibling stack we deliberately left alone. */\nconst removeManagedNetworkBestEffort = (\n\tname: string,\n\toptions: Required<DevstackNetworkRemovalOptions>,\n): Effect.Effect<NetworkRemoveOutcome, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst first = yield* removeManagedNetworkOnce(name);\n\t\tif (first.kind !== 'in-use') return first;\n\t\t// Investigate, evict our own endpoints, retry.\n\t\tconst attachments = yield* listAttachedContainers(name).pipe(\n\t\t\tEffect.catch(() => Effect.succeed([] as ReadonlyArray<NetworkAttachedEndpoint>)),\n\t\t);\n\t\tif (attachments.length > 0) {\n\t\t\tconst ownedNames = new Set<string>();\n\t\t\tconst all = yield* listDevstackContainers().pipe(\n\t\t\t\tEffect.catch(() => Effect.succeed([] as ReadonlyArray<{ readonly name: string }>)),\n\t\t\t);\n\t\t\tfor (const c of all) ownedNames.add(c.name);\n\t\t\tfor (const attached of attachments) {\n\t\t\t\tif (!ownedNames.has(attached.name)) continue;\n\t\t\t\tyield* forceDisconnect(attached.name, name).pipe(\n\t\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\t\tEffect.logDebug('prune: force-disconnect failed; will retry rm anyway', {\n\t\t\t\t\t\t\tnetwork: name,\n\t\t\t\t\t\t\tcontainer: attached.name,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.catch(() => Effect.void),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\t// Retry `network rm` after eviction — Effect.repeat with a\n\t\t// spaced schedule per §16 \"Balance / poll loop\" canonical\n\t\t// helper (substrate `NETWORK_REMOVE_RETRY_PROFILE`). User-\n\t\t// supplied `options.retryAttempts` / `retryDelayMillis`\n\t\t// override the profile defaults. `Schedule.recurs(N)` allows\n\t\t// N additional recurrences, so this retry block makes\n\t\t// `1 + retryAttempts` rm calls when persistently in-use. Counting\n\t\t// the pre-eviction `first` probe above, the worst-case in-use\n\t\t// total for this function is `2 + retryAttempts` rm invocations.\n\t\tconst lastStep: NetworkRemoveStep = yield* removeManagedNetworkOnce(name).pipe(\n\t\t\tEffect.repeat({\n\t\t\t\tschedule: makeSpacedRetrySchedule(options.retryDelayMillis, options.retryAttempts),\n\t\t\t\tuntil: (step): boolean => step.kind !== 'in-use',\n\t\t\t}),\n\t\t);\n\t\tif (lastStep.kind === 'removed') return { kind: 'removed' as const };\n\t\tif (lastStep.kind === 'missing') return { kind: 'missing' as const };\n\t\tconst lastStderr = lastStep.stderr;\n\t\t// Still in-use after our best effort — diagnose what's holding it.\n\t\tconst finalAttachments = yield* listAttachedContainers(name).pipe(\n\t\t\tEffect.catch(() => Effect.succeed([] as ReadonlyArray<NetworkAttachedEndpoint>)),\n\t\t);\n\t\tconst ownedNames = new Set<string>();\n\t\tconst all = yield* listDevstackContainers().pipe(\n\t\t\tEffect.catch(() => Effect.succeed([] as ReadonlyArray<{ readonly name: string }>)),\n\t\t);\n\t\tfor (const c of all) ownedNames.add(c.name);\n\t\tconst foreign = finalAttachments.filter((a) => !ownedNames.has(a.name));\n\t\t// Endpoints Docker mentions in the rm error but which DON'T\n\t\t// appear in inspect → stale metadata. The bridge driver kept\n\t\t// the endpoint record around after the container was reaped;\n\t\t// no CLI/API path can remove it. Operator must restart Docker.\n\t\tconst attachedNames = new Set(finalAttachments.map((a) => a.name));\n\t\tconst stale = parseActiveEndpointsFromStderr(lastStderr).filter(\n\t\t\t(ep) => !attachedNames.has(ep.name),\n\t\t);\n\t\treturn {\n\t\t\tkind: 'in-use' as const,\n\t\t\tforeignHolders: foreign,\n\t\t\tstaleEndpoints: stale,\n\t\t};\n\t});\n\nexport const removeDevstackNetworks = (\n\tlabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst networks = yield* listDevstackNetworks();\n\t\tlet removed = 0;\n\t\tfor (const network of networks) {\n\t\t\tif (!labelsMatchAppStack(network.labels, labelMatch)) continue;\n\t\t\tconst didRemove = yield* removeManagedNetwork(network.name);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackNetworks'));\n\nexport const removeDevstackNetworksBestEffort = (\n\tlabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n\toptions: DevstackNetworkRemovalOptions = {},\n): Effect.Effect<DevstackNetworkRemovalSummary, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst networks = yield* listDevstackNetworks();\n\t\tconst retryOptions = {\n\t\t\tretryAttempts: options.retryAttempts ?? NETWORK_REMOVE_RETRY_PROFILE.attempts,\n\t\t\tretryDelayMillis: options.retryDelayMillis ?? NETWORK_REMOVE_RETRY_PROFILE.delayMs,\n\t\t};\n\t\tlet removed = 0;\n\t\tlet skippedInUse = 0;\n\t\tconst foreignHolders: Array<ForeignNetworkHolder> = [];\n\t\tconst staleEndpoints: Array<StaleNetworkEndpoint> = [];\n\t\tfor (const network of networks) {\n\t\t\tif (!labelsMatchAppStack(network.labels, labelMatch)) continue;\n\t\t\tconst outcome = yield* removeManagedNetworkBestEffort(network.name, retryOptions);\n\t\t\tif (outcome.kind === 'removed') removed += 1;\n\t\t\telse if (outcome.kind === 'in-use') {\n\t\t\t\tskippedInUse += 1;\n\t\t\t\tfor (const container of outcome.foreignHolders) {\n\t\t\t\t\tforeignHolders.push({ network: network.name, container });\n\t\t\t\t}\n\t\t\t\tfor (const ep of outcome.staleEndpoints) {\n\t\t\t\t\tstaleEndpoints.push({ network: network.name, name: ep.name, id: ep.id });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn { removed, skippedInUse, foreignHolders, staleEndpoints };\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackNetworksBestEffort'));\n\nexport const removeManagedVolumes = (\n\tlabelMatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst volumes = yield* listVolumes(labelMatch);\n\t\tlet removed = 0;\n\t\tfor (const volume of volumes) {\n\t\t\tif (volume.labels[LabelKey.volumeMarker] !== 'true') continue;\n\t\t\t// Best-effort per volume: an in-use/locked volume must not poison\n\t\t\t// the loop and strand later volumes. Surface the cause via\n\t\t\t// `logWarning` for operator visibility, then continue.\n\t\t\tconst didRemove = yield* removeVolume(volume.name).pipe(\n\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\tEffect.logWarning('sweep: docker volume rm failed', { name: volume.name, cause }),\n\t\t\t\t),\n\t\t\t\tEffect.as(true),\n\t\t\t\tEffect.catch(() => Effect.succeed(false)),\n\t\t\t);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeManagedVolumes'));\n\nexport const removeDevstackVolumes = (\n\tlabelMatch: Pick<ContainerLabelTuple, 'app' | 'stack'>,\n): Effect.Effect<number, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst volumes = yield* listDevstackVolumes();\n\t\tlet removed = 0;\n\t\tfor (const volume of volumes) {\n\t\t\tif (!labelsMatchAppStack(volume.labels, labelMatch)) continue;\n\t\t\t// Best-effort per volume: an in-use/locked volume must not poison\n\t\t\t// the loop and strand later volumes. Surface the cause via\n\t\t\t// `logWarning` for operator visibility, then continue.\n\t\t\tconst didRemove = yield* removeVolume(volume.name).pipe(\n\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\tEffect.logWarning('sweep: docker volume rm failed', { name: volume.name, cause }),\n\t\t\t\t),\n\t\t\t\tEffect.as(true),\n\t\t\t\tEffect.catch(() => Effect.succeed(false)),\n\t\t\t);\n\t\t\tif (didRemove) removed += 1;\n\t\t}\n\t\treturn removed;\n\t}).pipe(Effect.withSpan('runtime.docker.removeDevstackVolumes'));\n"],"mappings":"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"}
@@ -1,6 +1,8 @@
1
1
  import { dockerRunOk } from "./client.mjs";
2
2
  import { VolumeOperationFailed } from "./errors.mjs";
3
3
  import { wrapVolumeError } from "./wrap.mjs";
4
+ import "./inspect-and-decode.mjs";
5
+ import "./labels.mjs";
4
6
  import { Effect, Schema } from "effect";
5
7
  Schema.Struct({
6
8
  Name: Schema.String,
@@ -1 +1 @@
1
- {"version":3,"file":"volume.mjs","names":[],"sources":["../../../src/runtime/docker/volume.ts"],"sourcesContent":["// Docker volume lifecycle.\n//\n// Architecture invariant: PRE-CREATE labelled volumes BEFORE the run\n// argv fires. Docker's lazy `-v <name>:<path>` produces UNLABELLED\n// volumes that no inventory enumeration can find. Bind mounts pass\n// through unchanged (host-owned paths).\n\nimport { Effect, Schema } from 'effect';\n\nimport type { ContainerLabelTuple } from '../../contracts/snapshotable.ts';\nimport { decodeJsonArrayElementSync } from '../../substrate/runtime/runtime-decode.ts';\nimport { DockerHost, DockerSpawner, dockerRun, dockerRunOk } from './client.ts';\nimport {\n\tDockerInspectDecodeFailed,\n\tDockerInspectFailed,\n\tDaemonUnreachable,\n\ttype DockerRuntimeError,\n\tForeignDockerResource,\n\tVolumeOperationFailed,\n} from './errors.ts';\nimport {\n\texpectedVolumeOwnershipLabels,\n\townershipMismatchDetail,\n\trenderVolumeLabels,\n} from './labels.ts';\nimport { isDaemonUnreachableStderr, wrapVolumeError } from './wrap.ts';\n\ninterface VolumeInspectFacts {\n\treadonly name: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\nconst VolumeInspectSchema = Schema.Struct({\n\tName: Schema.String,\n\tLabels: Schema.optional(Schema.Unknown),\n});\n\nconst readLabels = (raw: unknown): Readonly<Record<string, string>> => {\n\tif (raw === null || typeof raw !== 'object') return {};\n\tconst out: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(raw)) {\n\t\tif (typeof value === 'string') out[key] = value;\n\t}\n\treturn out;\n};\n\nconst isNoSuchVolumeStderr = (stderr: string): boolean => /no such volume/i.test(stderr);\n\nconst inspectVolume = (\n\tname: string,\n): Effect.Effect<VolumeInspectFacts | null, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('volume', ['inspect', name]).pipe(\n\t\t\tEffect.mapError(wrapVolumeError('inspect', name)),\n\t\t);\n\t\tif (res.exitCode !== 0) {\n\t\t\tif (isNoSuchVolumeStderr(res.stderr)) return null;\n\t\t\tif (isDaemonUnreachableStderr(res.stderr)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew DaemonUnreachable({\n\t\t\t\t\t\top: 'docker.volume.inspect',\n\t\t\t\t\t\tdetail: 'docker daemon unreachable',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew DockerInspectFailed({\n\t\t\t\t\tresource: 'volume',\n\t\t\t\t\tname,\n\t\t\t\t\tstderr: res.stderr,\n\t\t\t\t\texitCode: res.exitCode,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\ttry {\n\t\t\tconst decoded = decodeJsonArrayElementSync(VolumeInspectSchema, res.stdout, {\n\t\t\t\tsource: `docker volume inspect ${name}`,\n\t\t\t\tmissingMessage: 'inspect returned an empty result',\n\t\t\t\tmkError: (issue) =>\n\t\t\t\t\tnew DockerInspectDecodeFailed({\n\t\t\t\t\t\tresource: 'volume',\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\tissue.message === 'inspect returned an empty result'\n\t\t\t\t\t\t\t\t? issue.message\n\t\t\t\t\t\t\t\t: 'inspect returned malformed volume JSON',\n\t\t\t\t\t\tcause: issue.cause,\n\t\t\t\t\t}),\n\t\t\t});\n\t\t\treturn { name: decoded.Name, labels: readLabels(decoded.Labels) };\n\t\t} catch (cause) {\n\t\t\treturn yield* Effect.fail(cause as DockerRuntimeError);\n\t\t}\n\t});\n\nconst assertVolumeOwned = (\n\tname: string,\n\tfacts: VolumeInspectFacts,\n\ttuple: ContainerLabelTuple,\n): Effect.Effect<void, DockerRuntimeError> =>\n\tEffect.gen(function* () {\n\t\tconst expected = expectedVolumeOwnershipLabels(tuple);\n\t\tconst mismatch = ownershipMismatchDetail(expected, facts.labels);\n\t\tif (mismatch !== null) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew ForeignDockerResource({\n\t\t\t\t\tresource: 'volume',\n\t\t\t\t\tname,\n\t\t\t\t\texpected,\n\t\t\t\t\tactual: facts.labels,\n\t\t\t\t\tdetail: mismatch,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t});\n\n/** Idempotent `docker volume create`. Stamps the canonical label set.\n * Returns the volume name on success. */\nexport const ensureVolume = (\n\tname: string,\n\ttuple: ContainerLabelTuple,\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst probe = yield* inspectVolume(name);\n\t\tif (probe !== null) {\n\t\t\tyield* assertVolumeOwned(name, probe, tuple);\n\t\t\treturn probe.name;\n\t\t}\n\t\tconst labelArgs = renderVolumeLabels(name, tuple).flatMap((l) => ['--label', l]);\n\t\tyield* dockerRun('volume', ['create', ...labelArgs, name]).pipe(\n\t\t\tEffect.mapError(wrapVolumeError('create', name)),\n\t\t);\n\t\treturn name;\n\t}).pipe(Effect.withSpan('runtime.docker.volume.ensure'));\n\nexport const removeVolume = (\n\tname: string,\n): Effect.Effect<void, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('volume', ['rm', '--force', name]).pipe(\n\t\t\tEffect.mapError(wrapVolumeError('remove', name)),\n\t\t);\n\t\t// \"no such volume\" is idempotent success.\n\t\tif (res.exitCode !== 0 && !/no such volume/i.test(res.stderr)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew VolumeOperationFailed({ op: 'remove', volume: name, stderr: res.stderr }),\n\t\t\t);\n\t\t}\n\t}).pipe(Effect.withSpan('runtime.docker.volume.remove'));\n"],"mappings":";;;;AAgC4B,OAAO,OAAO;CACzC,MAAM,OAAO;CACb,QAAQ,OAAO,SAAS,OAAO,QAAQ;CACvC,CAAC;AAoGF,MAAa,gBACZ,SAEA,OAAO,IAAI,aAAa;CACvB,MAAM,MAAM,OAAO,YAAY,UAAU;EAAC;EAAM;EAAW;EAAK,CAAC,CAAC,KACjE,OAAO,SAAS,gBAAgB,UAAU,KAAK,CAAC,CAChD;AAED,KAAI,IAAI,aAAa,KAAK,CAAC,kBAAkB,KAAK,IAAI,OAAO,CAC5D,QAAO,OAAO,OAAO,KACpB,IAAI,sBAAsB;EAAE,IAAI;EAAU,QAAQ;EAAM,QAAQ,IAAI;EAAQ,CAAC,CAC7E;EAED,CAAC,KAAK,OAAO,SAAS,+BAA+B,CAAC"}
1
+ {"version":3,"file":"volume.mjs","names":[],"sources":["../../../src/runtime/docker/volume.ts"],"sourcesContent":["// Docker volume lifecycle.\n//\n// Architecture invariant: PRE-CREATE labelled volumes BEFORE the run\n// argv fires. Docker's lazy `-v <name>:<path>` produces UNLABELLED\n// volumes that no inventory enumeration can find. Bind mounts pass\n// through unchanged (host-owned paths).\n\nimport { Effect, Schema } from 'effect';\n\nimport type { ContainerLabelTuple } from '../../contracts/snapshotable.ts';\nimport { DockerHost, DockerSpawner, dockerRun, dockerRunOk } from './client.ts';\nimport {\n\ttype DockerRuntimeError,\n\tForeignDockerResource,\n\tVolumeOperationFailed,\n} from './errors.ts';\nimport { dockerInspectAndDecode } from './inspect-and-decode.ts';\nimport {\n\texpectedVolumeOwnershipLabels,\n\townershipMismatchDetail,\n\treadLabels,\n\trenderVolumeLabels,\n} from './labels.ts';\nimport { wrapVolumeError } from './wrap.ts';\n\ninterface VolumeInspectFacts {\n\treadonly name: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\nconst VolumeInspectSchema = Schema.Struct({\n\tName: Schema.String,\n\tLabels: Schema.optional(Schema.Unknown),\n});\n\nconst isNoSuchVolumeStderr = (stderr: string): boolean => /no such volume/i.test(stderr);\n\nconst inspectVolume = (\n\tname: string,\n): Effect.Effect<VolumeInspectFacts | null, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst decoded = yield* dockerInspectAndDecode({\n\t\t\tresourceKind: 'volume',\n\t\t\tname,\n\t\t\top: 'docker.volume.inspect',\n\t\t\tinspectCommand: dockerRunOk('volume', ['inspect', name]).pipe(\n\t\t\t\tEffect.mapError(wrapVolumeError('inspect', name)),\n\t\t\t),\n\t\t\tschema: VolumeInspectSchema,\n\t\t\tisMissingStderr: isNoSuchVolumeStderr,\n\t\t});\n\t\tif (decoded === null) return null;\n\t\treturn { name: decoded.Name, labels: readLabels(decoded.Labels) };\n\t});\n\nconst assertVolumeOwned = (\n\tname: string,\n\tfacts: VolumeInspectFacts,\n\ttuple: ContainerLabelTuple,\n): Effect.Effect<void, DockerRuntimeError> =>\n\tEffect.gen(function* () {\n\t\tconst expected = expectedVolumeOwnershipLabels(tuple);\n\t\tconst mismatch = ownershipMismatchDetail(expected, facts.labels);\n\t\tif (mismatch !== null) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew ForeignDockerResource({\n\t\t\t\t\tresource: 'volume',\n\t\t\t\t\tname,\n\t\t\t\t\texpected,\n\t\t\t\t\tactual: facts.labels,\n\t\t\t\t\tdetail: mismatch,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t});\n\n/** Idempotent `docker volume create`. Stamps the canonical label set.\n * Returns the volume name on success. */\nexport const ensureVolume = (\n\tname: string,\n\ttuple: ContainerLabelTuple,\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst probe = yield* inspectVolume(name);\n\t\tif (probe !== null) {\n\t\t\tyield* assertVolumeOwned(name, probe, tuple);\n\t\t\treturn probe.name;\n\t\t}\n\t\tconst labelArgs = renderVolumeLabels(name, tuple).flatMap((l) => ['--label', l]);\n\t\tyield* dockerRun('volume', ['create', ...labelArgs, name]).pipe(\n\t\t\tEffect.mapError(wrapVolumeError('create', name)),\n\t\t);\n\t\treturn name;\n\t}).pipe(Effect.withSpan('runtime.docker.volume.ensure'));\n\nexport const removeVolume = (\n\tname: string,\n): Effect.Effect<void, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('volume', ['rm', '--force', name]).pipe(\n\t\t\tEffect.mapError(wrapVolumeError('remove', name)),\n\t\t);\n\t\t// \"no such volume\" is idempotent success.\n\t\tif (res.exitCode !== 0 && !/no such volume/i.test(res.stderr)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew VolumeOperationFailed({ op: 'remove', volume: name, stderr: res.stderr }),\n\t\t\t);\n\t\t}\n\t}).pipe(Effect.withSpan('runtime.docker.volume.remove'));\n"],"mappings":";;;;;;AA8B4B,OAAO,OAAO;CACzC,MAAM,OAAO;CACb,QAAQ,OAAO,SAAS,OAAO,QAAQ;CACvC,CAAC;AA8DF,MAAa,gBACZ,SAEA,OAAO,IAAI,aAAa;CACvB,MAAM,MAAM,OAAO,YAAY,UAAU;EAAC;EAAM;EAAW;EAAK,CAAC,CAAC,KACjE,OAAO,SAAS,gBAAgB,UAAU,KAAK,CAAC,CAChD;AAED,KAAI,IAAI,aAAa,KAAK,CAAC,kBAAkB,KAAK,IAAI,OAAO,CAC5D,QAAO,OAAO,OAAO,KACpB,IAAI,sBAAsB;EAAE,IAAI;EAAU,QAAQ;EAAM,QAAQ,IAAI;EAAQ,CAAC,CAC7E;EAED,CAAC,KAAK,OAAO,SAAS,+BAA+B,CAAC"}
@@ -12,11 +12,40 @@ const isPortConflictStderr = (stderr) => /port is already allocated/i.test(stder
12
12
  const isNoSuchContainerStderr = (stderr) => /No such container/i.test(stderr) || /Error response from daemon: No such container/i.test(stderr) || /no such object/i.test(stderr);
13
13
  /** Network attach idempotency — "already exists in network" is success. */
14
14
  const isAlreadyInNetworkStderr = (stderr) => /already exists in network/i.test(stderr) || /endpoint with name .* already exists/i.test(stderr);
15
+ /** Network-create collision — `docker network create` is NOT name-atomic
16
+ * and there is no per-name lock for networks, so two processes booting
17
+ * against the shared `devstack` bridge can both pass the inspect-miss and
18
+ * both run `create`; the loser sees this stderr. Matches the daemon's
19
+ * canonical wording `network with name <name> already exists` plus the
20
+ * `Conflict` (409) envelope libnetwork returns for the same condition.
21
+ * Distinct from `isAlreadyInNetworkStderr` (the connect-side
22
+ * endpoint-already-attached case) — this is the network object itself
23
+ * already existing, the signal for `ensureNetwork`'s adopt-on-collision
24
+ * re-inspect. */
25
+ const isNetworkAlreadyExistsStderr = (stderr) => /network with name \S+ already exists/i.test(stderr) || /Conflict.*network/i.test(stderr) || /network \S+ already exists/i.test(stderr);
15
26
  /** Docker bridge IPAM exhaustion. This is a stale-network / missing
16
27
  * explicit-subnet policy failure, not a daemon reachability failure. */
17
28
  const isNetworkAddressPoolExhaustedStderr = (stderr) => /all predefined address pools have been fully subnetted/i.test(stderr);
18
29
  /** Image not found — pull or inspect against an unknown ref. */
19
30
  const isImageNotFoundStderr = (stderr) => /pull access denied/i.test(stderr) || /manifest .* not found/i.test(stderr) || /repository .* not found/i.test(stderr) || /No such image/i.test(stderr);
31
+ /** Image missing — classifier for `docker image rm` / `docker tag`
32
+ * flows. Matches the daemon's canonical wording only — a bare
33
+ * `/not found/` alternation would misclassify permission-denied and
34
+ * registry-auth errors (e.g. `pull access denied … repository does
35
+ * not exist`) as "missing" and let sweep silently skip them. Distinct
36
+ * from `isImageNotFoundStderr` (pull-flow-specific) — this one is the
37
+ * union used by idempotent remove/tag paths. */
38
+ const isMissingImageStderr = (stderr) => /no such image|reference does not exist/i.test(stderr);
39
+ /** Network missing — idempotent classifier for `docker network rm` /
40
+ * `docker network inspect`. Matches the daemon's canonical wordings:
41
+ * `No such network: <name>` and `network <name> not found`. A bare
42
+ * `/not found/` alternation would misclassify auth / permission errors
43
+ * on shared docker hosts. */
44
+ const isMissingNetworkStderr = (stderr) => /no such network|network \S+ not found/i.test(stderr);
45
+ /** Network in-use — `docker network rm` failed because endpoints are
46
+ * still attached. Inverse predicate of `isMissingNetworkStderr`; lives
47
+ * next to its siblings so the two evolve together. */
48
+ const isNetworkInUseStderr = (stderr) => /active endpoints|has active endpoint|network .* is in use/i.test(stderr);
20
49
  /** Daemon-unreachable check is the universal short-circuit. Every
21
50
  * wrapper runs this first. */
22
51
  const checkDaemon = (err) => {
@@ -126,6 +155,6 @@ const classifyExit = (res) => ({
126
155
  stderr: res.stderr
127
156
  });
128
157
  //#endregion
129
- export { classifyExit, isAlreadyInNetworkStderr, isDaemonUnreachableStderr, isImageNotFoundStderr, isNameCollisionStderr, isNoSuchContainerStderr, wrapBuildError, wrapCreateError, wrapGeneric, wrapNetworkError, wrapPullError, wrapVolumeError };
158
+ export { classifyExit, isAlreadyInNetworkStderr, isDaemonUnreachableStderr, isImageNotFoundStderr, isMissingImageStderr, isMissingNetworkStderr, isNameCollisionStderr, isNetworkAlreadyExistsStderr, isNetworkInUseStderr, isNoSuchContainerStderr, isPortConflictStderr, wrapBuildError, wrapCreateError, wrapGeneric, wrapNetworkError, wrapPullError, wrapVolumeError };
130
159
 
131
160
  //# sourceMappingURL=wrap.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"wrap.mjs","names":[],"sources":["../../../src/runtime/docker/wrap.ts"],"sourcesContent":["// Subprocess-capture → typed docker-error translation.\n//\n// Architecture inverts the legacy \"stderr-pattern wording\" coupling:\n// instead of every plugin staring at stderr substrings, we classify\n// in ONE place and surface typed errors. Plugins consume typed errors\n// (`ImageNotFound`, `BuildFailed`, …) and never see raw stderr text\n// outside the error envelope's `detail` field.\n//\n// Stderr classifiers are kept here, not scattered. New runtime\n// backends (podman / finch) ship their own dialect of the classifier\n// set; the typed-error surface is the same.\n\nimport type {\n\tCaptureError,\n\tCaptureResult,\n} from '../../substrate/runtime/observability/subprocess-capture.ts';\nimport {\n\tBuildFailed,\n\tContainerCreateFailed,\n\tContainerNameCollisionUnrecoverable,\n\tContainerPortPublishConflict,\n\tDaemonUnreachable,\n\ttype DockerRuntimeError,\n\tImageNotFound,\n\tImagePullFailed,\n\tNetworkAddressPoolExhausted,\n\tNetworkOperationFailed,\n\tVolumeOperationFailed,\n} from './errors.ts';\n\n// -----------------------------------------------------------------------------\n// Classifiers\n// -----------------------------------------------------------------------------\n\n/** Daemon unreachable — the connection itself failed. Highest-priority\n * classifier; if this is true, no further classification is meaningful. */\nexport const isDaemonUnreachableStderr = (stderr: string): boolean =>\n\t/Cannot connect to the Docker daemon/i.test(stderr) ||\n\t/Is the docker daemon running\\?/i.test(stderr) ||\n\t/ENOENT.*docker/i.test(stderr);\n\n/** Name collision — `docker create` / `run -d` returns exit 125 with\n * \"name is already in use\" wording. Architecture §1 — name-atomic create. */\nexport const isNameCollisionStderr = (stderr: string): boolean =>\n\t/is already in use by container/i.test(stderr) || /Conflict.*The container name/i.test(stderr);\n\n/** Port conflict — used by container resume-fallback path. */\nexport const isPortConflictStderr = (stderr: string): boolean =>\n\t/port is already allocated/i.test(stderr) || /bind.*address already in use/i.test(stderr);\n\n/** \"No such container\" — TOCTOU recovery signal. */\nexport const isNoSuchContainerStderr = (stderr: string): boolean =>\n\t/No such container/i.test(stderr) ||\n\t/Error response from daemon: No such container/i.test(stderr) ||\n\t/no such object/i.test(stderr);\n\n/** Network attach idempotency — \"already exists in network\" is success. */\nexport const isAlreadyInNetworkStderr = (stderr: string): boolean =>\n\t/already exists in network/i.test(stderr) || /endpoint with name .* already exists/i.test(stderr);\n\n/** Docker bridge IPAM exhaustion. This is a stale-network / missing\n * explicit-subnet policy failure, not a daemon reachability failure. */\nexport const isNetworkAddressPoolExhaustedStderr = (stderr: string): boolean =>\n\t/all predefined address pools have been fully subnetted/i.test(stderr);\n\n/** Image not found — pull or inspect against an unknown ref. */\nexport const isImageNotFoundStderr = (stderr: string): boolean =>\n\t/pull access denied/i.test(stderr) ||\n\t/manifest .* not found/i.test(stderr) ||\n\t/repository .* not found/i.test(stderr) ||\n\t/No such image/i.test(stderr);\n\n// -----------------------------------------------------------------------------\n// Wrappers — translate CaptureError → typed DockerRuntimeError\n// -----------------------------------------------------------------------------\n\n/** Daemon-unreachable check is the universal short-circuit. Every\n * wrapper runs this first. */\nconst checkDaemon = (err: CaptureError): DaemonUnreachable | null => {\n\tif (isDaemonUnreachableStderr(err.stderr)) {\n\t\treturn new DaemonUnreachable({\n\t\t\top: err.op,\n\t\t\tdetail: 'docker daemon unreachable',\n\t\t\tcause: err.cause,\n\t\t});\n\t}\n\tif (err.cause !== undefined && err.exitCode === undefined) {\n\t\treturn new DaemonUnreachable({\n\t\t\top: err.op,\n\t\t\tdetail: 'docker CLI spawn failed (binary missing or fork failure)',\n\t\t\tcause: err.cause,\n\t\t});\n\t}\n\treturn null;\n};\n\nexport const wrapPullError =\n\t(ref: string) =>\n\t(err: CaptureError): DockerRuntimeError => {\n\t\tconst d = checkDaemon(err);\n\t\tif (d) return d;\n\t\tif (isImageNotFoundStderr(err.stderr)) {\n\t\t\treturn new ImageNotFound({ ref, detail: 'pull target not found' });\n\t\t}\n\t\treturn new ImagePullFailed({ ref, stderr: err.stderr, exitCode: err.exitCode });\n\t};\n\nexport const wrapBuildError =\n\t(contextPath: string, dockerfile: string | undefined) =>\n\t(err: CaptureError): DockerRuntimeError => {\n\t\tconst d = checkDaemon(err);\n\t\tif (d) return d;\n\t\t// STUB: best-effort parse of `Step <n>/<m> : <line>` from a\n\t\t// buildkit / classic builder log. The architecture allows this\n\t\t// to be empty — the raw stderr is still surfaced in `detail`.\n\t\t// Follow-up: extract from buildkit's `#<n> ERROR: …` envelope.\n\t\tconst parsedStep = parseFailingBuildStep(err.stdout, err.stderr);\n\t\treturn new BuildFailed({\n\t\t\tcontextPath,\n\t\t\tdockerfile,\n\t\t\tstderr: err.stderr,\n\t\t\tparsedStep,\n\t\t\texitCode: err.exitCode,\n\t\t});\n\t};\n\nexport const wrapCreateError =\n\t(name: string) =>\n\t(err: CaptureError): DockerRuntimeError => {\n\t\tconst d = checkDaemon(err);\n\t\tif (d) return d;\n\t\tif (isNameCollisionStderr(err.stderr)) {\n\t\t\t// Architecture §1: surface the collision typed so the\n\t\t\t// state machine can decide adopt vs typed-failure. The\n\t\t\t// `Unrecoverable` variant fires only AFTER the start-and-\n\t\t\t// adopt fallback has exhausted; here we signal the collision\n\t\t\t// for the caller to handle.\n\t\t\treturn new ContainerNameCollisionUnrecoverable({\n\t\t\t\tname,\n\t\t\t\tdetail: 'name collision; caller should attempt adopt fallback',\n\t\t\t});\n\t\t}\n\t\tif (isPortConflictStderr(err.stderr)) {\n\t\t\treturn new ContainerPortPublishConflict({\n\t\t\t\tname,\n\t\t\t\tstderr: err.stderr,\n\t\t\t\texitCode: err.exitCode,\n\t\t\t});\n\t\t}\n\t\treturn new ContainerCreateFailed({ name, stderr: err.stderr, exitCode: err.exitCode });\n\t};\n\nexport const wrapNetworkError =\n\t(op: 'create' | 'connect' | 'disconnect' | 'inspect' | 'remove', network: string) =>\n\t(err: CaptureError): DockerRuntimeError => {\n\t\tconst d = checkDaemon(err);\n\t\tif (d) return d;\n\t\tif (op === 'create' && isNetworkAddressPoolExhaustedStderr(err.stderr)) {\n\t\t\treturn new NetworkAddressPoolExhausted({\n\t\t\t\tnetwork,\n\t\t\t\tstderr: err.stderr,\n\t\t\t\thint: 'Docker exhausted its predefined bridge address pools. Remove stale devstack networks with wipe/prune (for example `docker network prune`) or request an explicit non-overlapping subnet/gateway.',\n\t\t\t});\n\t\t}\n\t\treturn new NetworkOperationFailed({ op, network, stderr: err.stderr });\n\t};\n\nexport const wrapVolumeError =\n\t(op: 'create' | 'inspect' | 'remove', volume: string) =>\n\t(err: CaptureError): DockerRuntimeError => {\n\t\tconst d = checkDaemon(err);\n\t\tif (d) return d;\n\t\treturn new VolumeOperationFailed({ op, volume, stderr: err.stderr });\n\t};\n\n/** Generic wrapper for op surfaces (`docker logs`, `docker inspect`,\n * etc.) where the only distinct failure mode is daemon down. */\nexport const wrapGeneric =\n\t(op: string) =>\n\t(err: CaptureError): DockerRuntimeError =>\n\t\tcheckDaemon(err) ??\n\t\tnew DaemonUnreachable({\n\t\t\top,\n\t\t\tdetail: err.stderr || `${op} failed (exit ${err.exitCode ?? '?'})`,\n\t\t\tcause: err.cause,\n\t\t});\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\n/** STUB: extract a \"failing step\" line from build output. The\n * buildkit envelope shape is documented; classic builder shape is\n * `Step N/M : <directive>`. Returning `undefined` is fine — the raw\n * stderr is still in the BuildFailed envelope. */\nconst parseFailingBuildStep = (stdout: string, stderr: string): string | undefined => {\n\tconst combined = `${stderr}\\n${stdout}`;\n\tconst classic = combined.match(/Step (\\d+)\\/(\\d+)\\s*:\\s*([^\\n]+)/);\n\tif (classic) return `Step ${classic[1]}/${classic[2]} ${classic[3]}`;\n\tconst buildkit = combined.match(/#(\\d+)\\s+ERROR\\s*:\\s*([^\\n]+)/);\n\tif (buildkit) return `#${buildkit[1]} ${buildkit[2]}`;\n\treturn undefined;\n};\n\n/** Inspect a `CaptureResult`'s exit code; if non-zero return null\n * (caller decides what to do). Used by the lifecycle state machine\n * to classify stderr patterns WITHOUT promoting non-zero to failure\n * via `nonZeroIsFailure`. */\nexport const classifyExit = (\n\tres: CaptureResult,\n): { readonly ok: boolean; readonly exitCode: number; readonly stderr: string } => ({\n\tok: res.exitCode === 0,\n\texitCode: res.exitCode,\n\tstderr: res.stderr,\n});\n"],"mappings":";;;;AAoCA,MAAa,6BAA6B,WACzC,uCAAuC,KAAK,OAAO,IACnD,kCAAkC,KAAK,OAAO,IAC9C,kBAAkB,KAAK,OAAO;;;AAI/B,MAAa,yBAAyB,WACrC,kCAAkC,KAAK,OAAO,IAAI,gCAAgC,KAAK,OAAO;;AAG/F,MAAa,wBAAwB,WACpC,6BAA6B,KAAK,OAAO,IAAI,gCAAgC,KAAK,OAAO;;AAG1F,MAAa,2BAA2B,WACvC,qBAAqB,KAAK,OAAO,IACjC,iDAAiD,KAAK,OAAO,IAC7D,kBAAkB,KAAK,OAAO;;AAG/B,MAAa,4BAA4B,WACxC,6BAA6B,KAAK,OAAO,IAAI,wCAAwC,KAAK,OAAO;;;AAIlG,MAAa,uCAAuC,WACnD,0DAA0D,KAAK,OAAO;;AAGvE,MAAa,yBAAyB,WACrC,sBAAsB,KAAK,OAAO,IAClC,yBAAyB,KAAK,OAAO,IACrC,2BAA2B,KAAK,OAAO,IACvC,iBAAiB,KAAK,OAAO;;;AAQ9B,MAAM,eAAe,QAAgD;AACpE,KAAI,0BAA0B,IAAI,OAAO,CACxC,QAAO,IAAI,kBAAkB;EAC5B,IAAI,IAAI;EACR,QAAQ;EACR,OAAO,IAAI;EACX,CAAC;AAEH,KAAI,IAAI,UAAU,KAAA,KAAa,IAAI,aAAa,KAAA,EAC/C,QAAO,IAAI,kBAAkB;EAC5B,IAAI,IAAI;EACR,QAAQ;EACR,OAAO,IAAI;EACX,CAAC;AAEH,QAAO;;AAGR,MAAa,iBACX,SACA,QAA0C;CAC1C,MAAM,IAAI,YAAY,IAAI;AAC1B,KAAI,EAAG,QAAO;AACd,KAAI,sBAAsB,IAAI,OAAO,CACpC,QAAO,IAAI,cAAc;EAAE;EAAK,QAAQ;EAAyB,CAAC;AAEnE,QAAO,IAAI,gBAAgB;EAAE;EAAK,QAAQ,IAAI;EAAQ,UAAU,IAAI;EAAU,CAAC;;AAGjF,MAAa,kBACX,aAAqB,gBACrB,QAA0C;CAC1C,MAAM,IAAI,YAAY,IAAI;AAC1B,KAAI,EAAG,QAAO;CAKd,MAAM,aAAa,sBAAsB,IAAI,QAAQ,IAAI,OAAO;AAChE,QAAO,IAAI,YAAY;EACtB;EACA;EACA,QAAQ,IAAI;EACZ;EACA,UAAU,IAAI;EACd,CAAC;;AAGJ,MAAa,mBACX,UACA,QAA0C;CAC1C,MAAM,IAAI,YAAY,IAAI;AAC1B,KAAI,EAAG,QAAO;AACd,KAAI,sBAAsB,IAAI,OAAO,CAMpC,QAAO,IAAI,oCAAoC;EAC9C;EACA,QAAQ;EACR,CAAC;AAEH,KAAI,qBAAqB,IAAI,OAAO,CACnC,QAAO,IAAI,6BAA6B;EACvC;EACA,QAAQ,IAAI;EACZ,UAAU,IAAI;EACd,CAAC;AAEH,QAAO,IAAI,sBAAsB;EAAE;EAAM,QAAQ,IAAI;EAAQ,UAAU,IAAI;EAAU,CAAC;;AAGxF,MAAa,oBACX,IAAgE,aAChE,QAA0C;CAC1C,MAAM,IAAI,YAAY,IAAI;AAC1B,KAAI,EAAG,QAAO;AACd,KAAI,OAAO,YAAY,oCAAoC,IAAI,OAAO,CACrE,QAAO,IAAI,4BAA4B;EACtC;EACA,QAAQ,IAAI;EACZ,MAAM;EACN,CAAC;AAEH,QAAO,IAAI,uBAAuB;EAAE;EAAI;EAAS,QAAQ,IAAI;EAAQ,CAAC;;AAGxE,MAAa,mBACX,IAAqC,YACrC,QAA0C;CAC1C,MAAM,IAAI,YAAY,IAAI;AAC1B,KAAI,EAAG,QAAO;AACd,QAAO,IAAI,sBAAsB;EAAE;EAAI;EAAQ,QAAQ,IAAI;EAAQ,CAAC;;;;AAKtE,MAAa,eACX,QACA,QACA,YAAY,IAAI,IAChB,IAAI,kBAAkB;CACrB;CACA,QAAQ,IAAI,UAAU,GAAG,GAAG,gBAAgB,IAAI,YAAY,IAAI;CAChE,OAAO,IAAI;CACX,CAAC;;;;;AAUJ,MAAM,yBAAyB,QAAgB,WAAuC;CACrF,MAAM,WAAW,GAAG,OAAO,IAAI;CAC/B,MAAM,UAAU,SAAS,MAAM,mCAAmC;AAClE,KAAI,QAAS,QAAO,QAAQ,QAAQ,GAAG,GAAG,QAAQ,GAAG,GAAG,QAAQ;CAChE,MAAM,WAAW,SAAS,MAAM,gCAAgC;AAChE,KAAI,SAAU,QAAO,IAAI,SAAS,GAAG,GAAG,SAAS;;;;;;AAQlD,MAAa,gBACZ,SACmF;CACnF,IAAI,IAAI,aAAa;CACrB,UAAU,IAAI;CACd,QAAQ,IAAI;CACZ"}
1
+ {"version":3,"file":"wrap.mjs","names":[],"sources":["../../../src/runtime/docker/wrap.ts"],"sourcesContent":["// Subprocess-capture → typed docker-error translation.\n//\n// Architecture inverts the legacy \"stderr-pattern wording\" coupling:\n// instead of every plugin staring at stderr substrings, we classify\n// in ONE place and surface typed errors. Plugins consume typed errors\n// (`ImageNotFound`, `BuildFailed`, …) and never see raw stderr text\n// outside the error envelope's `detail` field.\n//\n// Stderr classifiers are kept here, not scattered. New runtime\n// backends (podman / finch) ship their own dialect of the classifier\n// set; the typed-error surface is the same.\n\nimport type {\n\tCaptureError,\n\tCaptureResult,\n} from '../../substrate/runtime/observability/subprocess-capture.ts';\nimport {\n\tBuildFailed,\n\tContainerCreateFailed,\n\tContainerNameCollisionUnrecoverable,\n\tContainerPortPublishConflict,\n\tDaemonUnreachable,\n\ttype DockerRuntimeError,\n\tImageNotFound,\n\tImagePullFailed,\n\tNetworkAddressPoolExhausted,\n\tNetworkOperationFailed,\n\tVolumeOperationFailed,\n} from './errors.ts';\n\n// -----------------------------------------------------------------------------\n// Classifiers\n// -----------------------------------------------------------------------------\n\n/** Daemon unreachable — the connection itself failed. Highest-priority\n * classifier; if this is true, no further classification is meaningful. */\nexport const isDaemonUnreachableStderr = (stderr: string): boolean =>\n\t/Cannot connect to the Docker daemon/i.test(stderr) ||\n\t/Is the docker daemon running\\?/i.test(stderr) ||\n\t/ENOENT.*docker/i.test(stderr);\n\n/** Name collision — `docker create` / `run -d` returns exit 125 with\n * \"name is already in use\" wording. Architecture §1 — name-atomic create. */\nexport const isNameCollisionStderr = (stderr: string): boolean =>\n\t/is already in use by container/i.test(stderr) || /Conflict.*The container name/i.test(stderr);\n\n/** Port conflict — used by container resume-fallback path. */\nexport const isPortConflictStderr = (stderr: string): boolean =>\n\t/port is already allocated/i.test(stderr) || /bind.*address already in use/i.test(stderr);\n\n/** \"No such container\" — TOCTOU recovery signal. */\nexport const isNoSuchContainerStderr = (stderr: string): boolean =>\n\t/No such container/i.test(stderr) ||\n\t/Error response from daemon: No such container/i.test(stderr) ||\n\t/no such object/i.test(stderr);\n\n/** Network attach idempotency — \"already exists in network\" is success. */\nexport const isAlreadyInNetworkStderr = (stderr: string): boolean =>\n\t/already exists in network/i.test(stderr) || /endpoint with name .* already exists/i.test(stderr);\n\n/** Network-create collision — `docker network create` is NOT name-atomic\n * and there is no per-name lock for networks, so two processes booting\n * against the shared `devstack` bridge can both pass the inspect-miss and\n * both run `create`; the loser sees this stderr. Matches the daemon's\n * canonical wording `network with name <name> already exists` plus the\n * `Conflict` (409) envelope libnetwork returns for the same condition.\n * Distinct from `isAlreadyInNetworkStderr` (the connect-side\n * endpoint-already-attached case) — this is the network object itself\n * already existing, the signal for `ensureNetwork`'s adopt-on-collision\n * re-inspect. */\nexport const isNetworkAlreadyExistsStderr = (stderr: string): boolean =>\n\t/network with name \\S+ already exists/i.test(stderr) ||\n\t/Conflict.*network/i.test(stderr) ||\n\t/network \\S+ already exists/i.test(stderr);\n\n/** Docker bridge IPAM exhaustion. This is a stale-network / missing\n * explicit-subnet policy failure, not a daemon reachability failure. */\nexport const isNetworkAddressPoolExhaustedStderr = (stderr: string): boolean =>\n\t/all predefined address pools have been fully subnetted/i.test(stderr);\n\n/** Image not found — pull or inspect against an unknown ref. */\nexport const isImageNotFoundStderr = (stderr: string): boolean =>\n\t/pull access denied/i.test(stderr) ||\n\t/manifest .* not found/i.test(stderr) ||\n\t/repository .* not found/i.test(stderr) ||\n\t/No such image/i.test(stderr);\n\n/** Image missing — classifier for `docker image rm` / `docker tag`\n * flows. Matches the daemon's canonical wording only — a bare\n * `/not found/` alternation would misclassify permission-denied and\n * registry-auth errors (e.g. `pull access denied … repository does\n * not exist`) as \"missing\" and let sweep silently skip them. Distinct\n * from `isImageNotFoundStderr` (pull-flow-specific) — this one is the\n * union used by idempotent remove/tag paths. */\nexport const isMissingImageStderr = (stderr: string): boolean =>\n\t/no such image|reference does not exist/i.test(stderr);\n\n/** Network missing — idempotent classifier for `docker network rm` /\n * `docker network inspect`. Matches the daemon's canonical wordings:\n * `No such network: <name>` and `network <name> not found`. A bare\n * `/not found/` alternation would misclassify auth / permission errors\n * on shared docker hosts. */\nexport const isMissingNetworkStderr = (stderr: string): boolean =>\n\t/no such network|network \\S+ not found/i.test(stderr);\n\n/** Network in-use — `docker network rm` failed because endpoints are\n * still attached. Inverse predicate of `isMissingNetworkStderr`; lives\n * next to its siblings so the two evolve together. */\nexport const isNetworkInUseStderr = (stderr: string): boolean =>\n\t/active endpoints|has active endpoint|network .* is in use/i.test(stderr);\n\n// -----------------------------------------------------------------------------\n// Wrappers — translate CaptureError → typed DockerRuntimeError\n// -----------------------------------------------------------------------------\n\n/** Daemon-unreachable check is the universal short-circuit. Every\n * wrapper runs this first. */\nconst checkDaemon = (err: CaptureError): DaemonUnreachable | null => {\n\tif (isDaemonUnreachableStderr(err.stderr)) {\n\t\treturn new DaemonUnreachable({\n\t\t\top: err.op,\n\t\t\tdetail: 'docker daemon unreachable',\n\t\t\tcause: err.cause,\n\t\t});\n\t}\n\tif (err.cause !== undefined && err.exitCode === undefined) {\n\t\treturn new DaemonUnreachable({\n\t\t\top: err.op,\n\t\t\tdetail: 'docker CLI spawn failed (binary missing or fork failure)',\n\t\t\tcause: err.cause,\n\t\t});\n\t}\n\treturn null;\n};\n\nexport const wrapPullError =\n\t(ref: string) =>\n\t(err: CaptureError): DockerRuntimeError => {\n\t\tconst d = checkDaemon(err);\n\t\tif (d) return d;\n\t\tif (isImageNotFoundStderr(err.stderr)) {\n\t\t\treturn new ImageNotFound({ ref, detail: 'pull target not found' });\n\t\t}\n\t\treturn new ImagePullFailed({ ref, stderr: err.stderr, exitCode: err.exitCode });\n\t};\n\nexport const wrapBuildError =\n\t(contextPath: string, dockerfile: string | undefined) =>\n\t(err: CaptureError): DockerRuntimeError => {\n\t\tconst d = checkDaemon(err);\n\t\tif (d) return d;\n\t\t// STUB: best-effort parse of `Step <n>/<m> : <line>` from a\n\t\t// buildkit / classic builder log. The architecture allows this\n\t\t// to be empty — the raw stderr is still surfaced in `detail`.\n\t\t// Follow-up: extract from buildkit's `#<n> ERROR: …` envelope.\n\t\tconst parsedStep = parseFailingBuildStep(err.stdout, err.stderr);\n\t\treturn new BuildFailed({\n\t\t\tcontextPath,\n\t\t\tdockerfile,\n\t\t\tstderr: err.stderr,\n\t\t\tparsedStep,\n\t\t\texitCode: err.exitCode,\n\t\t});\n\t};\n\nexport const wrapCreateError =\n\t(name: string) =>\n\t(err: CaptureError): DockerRuntimeError => {\n\t\tconst d = checkDaemon(err);\n\t\tif (d) return d;\n\t\tif (isNameCollisionStderr(err.stderr)) {\n\t\t\t// Architecture §1: surface the collision typed so the\n\t\t\t// state machine can decide adopt vs typed-failure. The\n\t\t\t// `Unrecoverable` variant fires only AFTER the start-and-\n\t\t\t// adopt fallback has exhausted; here we signal the collision\n\t\t\t// for the caller to handle.\n\t\t\treturn new ContainerNameCollisionUnrecoverable({\n\t\t\t\tname,\n\t\t\t\tdetail: 'name collision; caller should attempt adopt fallback',\n\t\t\t});\n\t\t}\n\t\tif (isPortConflictStderr(err.stderr)) {\n\t\t\treturn new ContainerPortPublishConflict({\n\t\t\t\tname,\n\t\t\t\tstderr: err.stderr,\n\t\t\t\texitCode: err.exitCode,\n\t\t\t});\n\t\t}\n\t\treturn new ContainerCreateFailed({ name, stderr: err.stderr, exitCode: err.exitCode });\n\t};\n\nexport const wrapNetworkError =\n\t(op: 'create' | 'connect' | 'disconnect' | 'inspect' | 'remove', network: string) =>\n\t(err: CaptureError): DockerRuntimeError => {\n\t\tconst d = checkDaemon(err);\n\t\tif (d) return d;\n\t\tif (op === 'create' && isNetworkAddressPoolExhaustedStderr(err.stderr)) {\n\t\t\treturn new NetworkAddressPoolExhausted({\n\t\t\t\tnetwork,\n\t\t\t\tstderr: err.stderr,\n\t\t\t\thint: 'Docker exhausted its predefined bridge address pools. Remove stale devstack networks with wipe/prune (for example `docker network prune`) or request an explicit non-overlapping subnet/gateway.',\n\t\t\t});\n\t\t}\n\t\treturn new NetworkOperationFailed({ op, network, stderr: err.stderr });\n\t};\n\nexport const wrapVolumeError =\n\t(op: 'create' | 'inspect' | 'remove', volume: string) =>\n\t(err: CaptureError): DockerRuntimeError => {\n\t\tconst d = checkDaemon(err);\n\t\tif (d) return d;\n\t\treturn new VolumeOperationFailed({ op, volume, stderr: err.stderr });\n\t};\n\n/** Generic wrapper for op surfaces (`docker logs`, `docker inspect`,\n * etc.) where the only distinct failure mode is daemon down. */\nexport const wrapGeneric =\n\t(op: string) =>\n\t(err: CaptureError): DockerRuntimeError =>\n\t\tcheckDaemon(err) ??\n\t\tnew DaemonUnreachable({\n\t\t\top,\n\t\t\tdetail: err.stderr || `${op} failed (exit ${err.exitCode ?? '?'})`,\n\t\t\tcause: err.cause,\n\t\t});\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\n/** STUB: extract a \"failing step\" line from build output. The\n * buildkit envelope shape is documented; classic builder shape is\n * `Step N/M : <directive>`. Returning `undefined` is fine — the raw\n * stderr is still in the BuildFailed envelope. */\nconst parseFailingBuildStep = (stdout: string, stderr: string): string | undefined => {\n\tconst combined = `${stderr}\\n${stdout}`;\n\tconst classic = combined.match(/Step (\\d+)\\/(\\d+)\\s*:\\s*([^\\n]+)/);\n\tif (classic) return `Step ${classic[1]}/${classic[2]} ${classic[3]}`;\n\tconst buildkit = combined.match(/#(\\d+)\\s+ERROR\\s*:\\s*([^\\n]+)/);\n\tif (buildkit) return `#${buildkit[1]} ${buildkit[2]}`;\n\treturn undefined;\n};\n\n/** Inspect a `CaptureResult`'s exit code; if non-zero return null\n * (caller decides what to do). Used by the lifecycle state machine\n * to classify stderr patterns WITHOUT promoting non-zero to failure\n * via `nonZeroIsFailure`. */\nexport const classifyExit = (\n\tres: CaptureResult,\n): { readonly ok: boolean; readonly exitCode: number; readonly stderr: string } => ({\n\tok: res.exitCode === 0,\n\texitCode: res.exitCode,\n\tstderr: res.stderr,\n});\n"],"mappings":"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"}
@@ -1,26 +1,32 @@
1
+ import { versionedDocSchema } from "./versioned-doc-schema.mjs";
1
2
  import { Schema } from "effect";
2
3
  //#region src/substrate/cross-process.ts
3
4
  const RosterHolderSchema = Schema.Struct({
4
5
  pid: Schema.Number,
5
- startTime: Schema.Number,
6
+ startTime: Schema.NullOr(Schema.Number),
6
7
  hostname: Schema.String,
7
8
  claimedAt: Schema.Number,
8
9
  heartbeatAt: Schema.Number,
9
10
  intent: Schema.Literals(["normal", "snapshot"])
10
11
  });
11
- const RosterDocumentSchema = Schema.Struct({
12
- version: Schema.Literal(1),
13
- holders: Schema.Array(RosterHolderSchema)
14
- });
12
+ const RosterDocumentSchema = versionedDocSchema(1, { holders: Schema.Array(RosterHolderSchema) });
15
13
  const DEFAULT_SWEEP_POLICY = {
16
14
  heartbeatIntervalMillis: 1e4,
17
15
  staleAfterMillis: 3e4,
18
16
  trustForeignHosts: true
19
17
  };
20
- const SnapshotReservationSchema = Schema.Struct({
18
+ /** Versioned schema — routed through `versionedDocSchema` so a v2
19
+ * migration can add a literal-tagged `Schema.Union` at one call site
20
+ * rather than hand-rolling a discriminator. `creatorStartTime` mirrors
21
+ * `RosterHolderSchema.startTime` (`NullOr(Number)`): when the creator
22
+ * couldn't probe its own startTime, write null and the sweep's
23
+ * liveness check short-circuits to "alive" rather than evicting on a
24
+ * literal `0` mismatch. */
25
+ const SnapshotReservationSchema = versionedDocSchema(1, {
21
26
  creatorPid: Schema.Number,
22
- creatorStartTime: Schema.Number,
23
- createdAt: Schema.Number
27
+ creatorStartTime: Schema.NullOr(Schema.Number),
28
+ createdAt: Schema.Number,
29
+ hostname: Schema.String
24
30
  });
25
31
  //#endregion
26
32
  export { DEFAULT_SWEEP_POLICY, RosterDocumentSchema, RosterHolderSchema, SnapshotReservationSchema };
@@ -1 +1 @@
1
- {"version":3,"file":"cross-process.mjs","names":[],"sources":["../../src/substrate/cross-process.ts"],"sourcesContent":["// Cross-process safety protocol.\n//\n// Architecture § Cross-process safety protocol. Core artifacts per\n// stack on disk under `<runtime-root>/stacks/<stack>/`:\n//\n// - `stack.lock` — OS-advisory exclusive lock; short critical\n// sections only.\n// - `roster.json` — authoritative cross-process record of holders;\n// mutated only under the lock.\n// - `commands.ndjson` / `events.ndjson` — filesystem command\n// channel between peer CLI commands and the live supervisor.\n// - `snapshot.reservation` — present-or-absent file (O_EXCL create\n// = acquire; unlink = release).\n\nimport { Schema } from 'effect';\n\n/** Holder intent — `normal` for ordinary peers; `snapshot` while\n * the holder is mid-capture (peers' commands defer). */\nexport type HolderIntent = 'normal' | 'snapshot';\n\n/** One holder entry in `roster.json`. PID + startTime liveness check\n * is the industry-standard pattern referenced in the synthesis. */\nexport interface RosterHolder {\n\treadonly pid: number;\n\t/** Process start-time, used for PID-reuse-safe liveness. */\n\treadonly startTime: number;\n\treadonly hostname: string;\n\treadonly claimedAt: number;\n\treadonly heartbeatAt: number;\n\treadonly intent: HolderIntent;\n}\n\n/** Roster document schema — versioned for forward compatibility. */\nexport interface RosterDocument {\n\treadonly version: 1;\n\treadonly holders: ReadonlyArray<RosterHolder>;\n}\n\nexport const RosterHolderSchema = Schema.Struct({\n\tpid: Schema.Number,\n\tstartTime: Schema.Number,\n\thostname: Schema.String,\n\tclaimedAt: Schema.Number,\n\theartbeatAt: Schema.Number,\n\tintent: Schema.Literals(['normal', 'snapshot']),\n});\n\nexport const RosterDocumentSchema = Schema.Struct({\n\tversion: Schema.Literal(1),\n\tholders: Schema.Array(RosterHolderSchema),\n});\n\n/** Sweep policy — peers older than `staleAfterMillis` AND failing\n * the PID liveness check are evicted on the next claim under the\n * exclusive lock. Foreign-host entries are treated as alive\n * (NFS-safe conservative default). */\nexport interface RosterSweepPolicy {\n\treadonly heartbeatIntervalMillis: number;\n\treadonly staleAfterMillis: number;\n\treadonly trustForeignHosts: boolean;\n}\n\nexport const DEFAULT_SWEEP_POLICY: RosterSweepPolicy = {\n\theartbeatIntervalMillis: 10_000,\n\tstaleAfterMillis: 30_000,\n\ttrustForeignHosts: true,\n};\n\n/** Snapshot reservation file — presence-or-absence semaphore. The\n * reservation's creator pid is encoded in the JSON body for the\n * orphan sweep. */\nexport interface SnapshotReservation {\n\treadonly creatorPid: number;\n\treadonly creatorStartTime: number;\n\treadonly createdAt: number;\n}\n\nexport const SnapshotReservationSchema = Schema.Struct({\n\tcreatorPid: Schema.Number,\n\tcreatorStartTime: Schema.Number,\n\tcreatedAt: Schema.Number,\n});\n"],"mappings":";;AAsCA,MAAa,qBAAqB,OAAO,OAAO;CAC/C,KAAK,OAAO;CACZ,WAAW,OAAO;CAClB,UAAU,OAAO;CACjB,WAAW,OAAO;CAClB,aAAa,OAAO;CACpB,QAAQ,OAAO,SAAS,CAAC,UAAU,WAAW,CAAC;CAC/C,CAAC;AAEF,MAAa,uBAAuB,OAAO,OAAO;CACjD,SAAS,OAAO,QAAQ,EAAE;CAC1B,SAAS,OAAO,MAAM,mBAAmB;CACzC,CAAC;AAYF,MAAa,uBAA0C;CACtD,yBAAyB;CACzB,kBAAkB;CAClB,mBAAmB;CACnB;AAWD,MAAa,4BAA4B,OAAO,OAAO;CACtD,YAAY,OAAO;CACnB,kBAAkB,OAAO;CACzB,WAAW,OAAO;CAClB,CAAC"}
1
+ {"version":3,"file":"cross-process.mjs","names":[],"sources":["../../src/substrate/cross-process.ts"],"sourcesContent":["// Cross-process safety protocol.\n//\n// Architecture § Cross-process safety protocol. Core artifacts per\n// stack on disk under `<runtime-root>/stacks/<stack>/`:\n//\n// - `stack.lock` — OS-advisory exclusive lock; short critical\n// sections only.\n// - `roster.json` — authoritative cross-process record of holders;\n// mutated only under the lock.\n// - `commands.ndjson` / `events.ndjson` — filesystem command\n// channel between peer CLI commands and the live supervisor.\n// - `snapshot.reservation` — present-or-absent file (O_EXCL create\n// = acquire; unlink = release).\n\nimport { Schema } from 'effect';\n\nimport { versionedDocSchema } from './versioned-doc-schema.ts';\n\n/** Holder intent — `normal` for ordinary peers; `snapshot` while\n * the holder is mid-capture (peers' commands defer). */\nexport type HolderIntent = 'normal' | 'snapshot';\n\n/** One holder entry in `roster.json`. PID + startTime liveness check\n * is the industry-standard pattern referenced in the synthesis.\n *\n * `startTime` is `number | null` — `null` means \"the platform could\n * not probe a start-time stamp for this process at write time\" (an\n * exotic platform, or `ps`/`tasklist` failed). Readers MUST treat\n * `null` as the conservative branch (see `isOwnEntry` in `roster.ts`\n * and `checkHolderLiveness` in `liveness.ts`): on a null recorded\n * stamp the start-time comparison is skipped and the (pid, hostname)\n * pair carries the identity. Writing `0` for \"unknown\" was the prior\n * shape and caused a false-dead harvest: a later probe yielding a\n * real stamp would mismatch the recorded `0`. */\nexport interface RosterHolder {\n\treadonly pid: number;\n\t/** Process start-time, used for PID-reuse-safe liveness. `null`\n\t * means \"unprobable\" — see the interface doc above. */\n\treadonly startTime: number | null;\n\treadonly hostname: string;\n\treadonly claimedAt: number;\n\treadonly heartbeatAt: number;\n\treadonly intent: HolderIntent;\n}\n\n/** Roster document schema — versioned for forward compatibility. */\nexport interface RosterDocument {\n\treadonly version: 1;\n\treadonly holders: ReadonlyArray<RosterHolder>;\n}\n\nexport const RosterHolderSchema = Schema.Struct({\n\tpid: Schema.Number,\n\tstartTime: Schema.NullOr(Schema.Number),\n\thostname: Schema.String,\n\tclaimedAt: Schema.Number,\n\theartbeatAt: Schema.Number,\n\tintent: Schema.Literals(['normal', 'snapshot']),\n});\n\nexport const RosterDocumentSchema = versionedDocSchema(1, {\n\tholders: Schema.Array(RosterHolderSchema),\n});\n\n/** Sweep policy — peers older than `staleAfterMillis` AND failing\n * the PID liveness check are evicted on the next claim under the\n * exclusive lock. Foreign-host entries are treated as alive\n * (NFS-safe conservative default). */\nexport interface RosterSweepPolicy {\n\treadonly heartbeatIntervalMillis: number;\n\treadonly staleAfterMillis: number;\n\treadonly trustForeignHosts: boolean;\n}\n\nexport const DEFAULT_SWEEP_POLICY: RosterSweepPolicy = {\n\theartbeatIntervalMillis: 10_000,\n\tstaleAfterMillis: 30_000,\n\ttrustForeignHosts: true,\n};\n\n/** Snapshot reservation file — presence-or-absence semaphore. The\n * reservation's creator pid is encoded in the JSON body for the\n * orphan sweep.\n *\n * `hostname` is carried so the orphan sweep can skip foreign-host\n * reservations on shared filesystems (NFS): a peer on host B writing\n * the reservation would be falsely declared \"dead\" by host A's kernel\n * probe (`kill(pid, 0)` only meaningful on the local host). The sweep\n * treats foreign-host reservations as alive (conservative — matches\n * the roster's `trustForeignHosts` policy). */\nexport interface SnapshotReservation {\n\treadonly version: 1;\n\treadonly creatorPid: number;\n\treadonly creatorStartTime: number | null;\n\treadonly createdAt: number;\n\treadonly hostname: string;\n}\n\n/** Versioned schema — routed through `versionedDocSchema` so a v2\n * migration can add a literal-tagged `Schema.Union` at one call site\n * rather than hand-rolling a discriminator. `creatorStartTime` mirrors\n * `RosterHolderSchema.startTime` (`NullOr(Number)`): when the creator\n * couldn't probe its own startTime, write null and the sweep's\n * liveness check short-circuits to \"alive\" rather than evicting on a\n * literal `0` mismatch. */\nexport const SnapshotReservationSchema = versionedDocSchema(1, {\n\tcreatorPid: Schema.Number,\n\tcreatorStartTime: Schema.NullOr(Schema.Number),\n\tcreatedAt: Schema.Number,\n\thostname: Schema.String,\n});\n"],"mappings":";;;AAmDA,MAAa,qBAAqB,OAAO,OAAO;CAC/C,KAAK,OAAO;CACZ,WAAW,OAAO,OAAO,OAAO,OAAO;CACvC,UAAU,OAAO;CACjB,WAAW,OAAO;CAClB,aAAa,OAAO;CACpB,QAAQ,OAAO,SAAS,CAAC,UAAU,WAAW,CAAC;CAC/C,CAAC;AAEF,MAAa,uBAAuB,mBAAmB,GAAG,EACzD,SAAS,OAAO,MAAM,mBAAmB,EACzC,CAAC;AAYF,MAAa,uBAA0C;CACtD,yBAAyB;CACzB,kBAAkB;CAClB,mBAAmB;CACnB;;;;;;;;AA2BD,MAAa,4BAA4B,mBAAmB,GAAG;CAC9D,YAAY,OAAO;CACnB,kBAAkB,OAAO,OAAO,OAAO,OAAO;CAC9C,WAAW,OAAO;CAClB,UAAU,OAAO;CACjB,CAAC"}