@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.
- package/README.md +7 -3
- package/dashboard-ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dashboard-ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dashboard-ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dashboard-ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
- package/dashboard-ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dashboard-ui/assets/index-Bmi1UtAg.js +1356 -0
- package/dashboard-ui/assets/index-D5EShVt4.js +3 -0
- package/dashboard-ui/assets/index-Deml9drg.css +1 -0
- package/dashboard-ui/index.html +13 -0
- package/dist/api/define-capabilities.d.mts +14 -3
- package/dist/api/define-capabilities.mjs +18 -1
- package/dist/api/define-capabilities.mjs.map +1 -1
- package/dist/api/define-devstack.d.mts +1 -1
- package/dist/api/define-devstack.mjs +6 -26
- package/dist/api/define-devstack.mjs.map +1 -1
- package/dist/api/define-plugin.d.mts +1 -0
- package/dist/api/define-plugin.mjs +2 -0
- package/dist/api/inference-network.d.mts +132 -0
- package/dist/api/inference-network.mjs +105 -16
- package/dist/api/inference-network.mjs.map +1 -1
- package/dist/api/plugin-errors.d.mts +7 -0
- package/dist/api/run-stack.d.mts +20 -9
- package/dist/api/run-stack.mjs +56 -29
- package/dist/api/run-stack.mjs.map +1 -1
- package/dist/build-integrations/playwright/config.d.mts +6 -4
- package/dist/build-integrations/playwright/config.mjs +5 -3
- package/dist/build-integrations/playwright/config.mjs.map +1 -1
- package/dist/build-integrations/playwright/errors.d.mts +1 -1
- package/dist/build-integrations/playwright/global-setup.d.mts +43 -17
- package/dist/build-integrations/playwright/global-setup.mjs +250 -13
- package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
- package/dist/build-integrations/playwright/index.d.mts +5 -3
- package/dist/build-integrations/playwright/index.mjs +4 -2
- package/dist/build-integrations/playwright/stack-context.d.mts +0 -1
- package/dist/build-integrations/playwright/stack-context.mjs +24 -63
- package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
- package/dist/build-integrations/playwright/wallet-context.d.mts +7 -10
- package/dist/build-integrations/playwright/wallet-context.mjs +8 -13
- package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/cold-start-url.d.mts +5 -0
- package/dist/build-integrations/runtime/cold-start-url.mjs +4 -3
- package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
- package/dist/build-integrations/runtime/conventional-routes.d.mts +51 -0
- package/dist/build-integrations/runtime/conventional-routes.mjs +92 -0
- package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -0
- package/dist/build-integrations/runtime/discover.d.mts +38 -30
- package/dist/build-integrations/runtime/discover.mjs +73 -70
- package/dist/build-integrations/runtime/discover.mjs.map +1 -1
- package/dist/build-integrations/runtime/endpoint-registry.d.mts +3 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs +3 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
- package/dist/build-integrations/runtime/index.d.mts +7 -2
- package/dist/build-integrations/runtime/index.mjs +9 -2
- package/dist/build-integrations/runtime/manifest-types.d.mts +2 -0
- package/dist/build-integrations/runtime/manifest-types.mjs +3 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.d.mts +29 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs +10 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs.map +1 -0
- package/dist/build-integrations/runtime/read-stack-context.d.mts +1 -1
- package/dist/build-integrations/runtime/read-stack-context.mjs +10 -4
- package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +48 -0
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs +42 -0
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -0
- package/dist/build-integrations/runtime/stack-context.d.mts +1 -1
- package/dist/build-integrations/runtime/wallet-paths.mjs +2 -0
- package/dist/build-integrations/vite/index.d.mts +51 -0
- package/dist/build-integrations/vite/index.mjs +73 -0
- package/dist/build-integrations/vite/index.mjs.map +1 -0
- package/dist/build-integrations/vitest/env.d.mts +4 -3
- package/dist/build-integrations/vitest/env.mjs +6 -4
- package/dist/build-integrations/vitest/env.mjs.map +1 -1
- package/dist/build-integrations/vitest/errors.d.mts +25 -4
- package/dist/build-integrations/vitest/errors.mjs +15 -2
- package/dist/build-integrations/vitest/errors.mjs.map +1 -1
- package/dist/build-integrations/vitest/index.d.mts +2 -2
- package/dist/build-integrations/vitest/index.mjs +3 -3
- package/dist/build-integrations/vitest/setup.d.mts +4 -2
- package/dist/build-integrations/vitest/setup.mjs +2 -2
- package/dist/build-integrations/vitest/setup.mjs.map +1 -1
- package/dist/build-integrations/vitest/stack-context.d.mts +0 -1
- package/dist/build-integrations/vitest/stack-context.mjs +33 -13
- package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
- package/dist/cli/bail.mjs +23 -0
- package/dist/cli/bail.mjs.map +1 -0
- package/dist/{surfaces/cli/commands → cli}/doctor-probes.mjs +26 -16
- package/dist/cli/doctor-probes.mjs.map +1 -0
- package/dist/cli/main.d.mts +15 -1
- package/dist/cli/main.mjs +166 -482
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/prune-direct.mjs +31 -201
- package/dist/cli/prune-direct.mjs.map +1 -1
- package/dist/cli/snapshot-reader.mjs +14 -11
- package/dist/cli/snapshot-reader.mjs.map +1 -1
- package/dist/cli/up-lifecycle.mjs +4 -1
- package/dist/cli/up-lifecycle.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +95 -0
- package/dist/cli/wirings/apply.mjs.map +1 -0
- package/dist/cli/wirings/build-verb-layers.mjs +42 -0
- package/dist/cli/wirings/build-verb-layers.mjs.map +1 -0
- package/dist/cli/wirings/config-loader.mjs +69 -0
- package/dist/cli/wirings/config-loader.mjs.map +1 -0
- package/dist/cli/wirings/engine-command.mjs +30 -0
- package/dist/cli/wirings/engine-command.mjs.map +1 -0
- package/dist/cli/wirings/identity.mjs +72 -0
- package/dist/cli/wirings/identity.mjs.map +1 -0
- package/dist/cli/wirings/provide-file-system.mjs +7 -0
- package/dist/cli/wirings/provide-file-system.mjs.map +1 -0
- package/dist/cli/wirings/snapshot.mjs +177 -0
- package/dist/cli/wirings/snapshot.mjs.map +1 -0
- package/dist/cli/wirings/up.mjs +287 -0
- package/dist/cli/wirings/up.mjs.map +1 -0
- package/dist/cli/wirings/wipe.mjs +40 -0
- package/dist/cli/wirings/wipe.mjs.map +1 -0
- package/dist/contracts/capability-decl.d.mts +0 -2
- package/dist/contracts/codegenable.d.mts +43 -0
- package/dist/contracts/container-runtime.d.mts +26 -1
- package/dist/contracts/faucet-strategy.d.mts +25 -0
- package/dist/contracts/funding-strategy.d.mts +42 -0
- package/dist/contracts/plugin-expander.mjs +60 -0
- package/dist/contracts/plugin-expander.mjs.map +1 -0
- package/dist/contracts/projection.d.mts +5 -3
- package/dist/contracts/renderer.d.mts +23 -0
- package/dist/contracts/routable.d.mts +14 -1
- package/dist/contracts/snapshotable.d.mts +25 -3
- package/dist/contracts/wallet-protocol.d.mts +52 -0
- package/dist/contracts/wallet-protocol.mjs +52 -0
- package/dist/contracts/wallet-protocol.mjs.map +1 -0
- package/dist/index.d.mts +26 -25
- package/dist/index.mjs +20 -13
- package/dist/{runtime → orchestrators}/built-in-plugin-layers.mjs +10 -5
- package/dist/orchestrators/built-in-plugin-layers.mjs.map +1 -0
- package/dist/orchestrators/codegen/bindings.d.mts +1 -2
- package/dist/orchestrators/codegen/bindings.mjs +52 -186
- package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
- package/dist/orchestrators/codegen/emit.mjs +19 -1
- package/dist/orchestrators/codegen/emit.mjs.map +1 -1
- package/dist/orchestrators/codegen/errors.mjs +8 -3
- package/dist/orchestrators/codegen/errors.mjs.map +1 -1
- package/dist/orchestrators/codegen/format.mjs +47 -39
- package/dist/orchestrators/codegen/format.mjs.map +1 -1
- package/dist/orchestrators/codegen/gitignore.mjs +3 -1
- package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
- package/dist/orchestrators/codegen/output-location.mjs +40 -0
- package/dist/orchestrators/codegen/output-location.mjs.map +1 -0
- package/dist/orchestrators/codegen/paths.d.mts +1 -1
- package/dist/orchestrators/codegen/paths.mjs +40 -14
- package/dist/orchestrators/codegen/paths.mjs.map +1 -1
- package/dist/orchestrators/codegen/permissions.mjs +3 -1
- package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
- package/dist/orchestrators/codegen/service.mjs +157 -105
- package/dist/orchestrators/codegen/service.mjs.map +1 -1
- package/dist/orchestrators/lifecycle-prune/errors.mjs +31 -0
- package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -0
- package/dist/orchestrators/lifecycle-prune/index.mjs +350 -0
- package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -0
- package/dist/orchestrators/router/cleanup.mjs +26 -27
- package/dist/orchestrators/router/cleanup.mjs.map +1 -1
- package/dist/orchestrators/router/entrypoints.mjs +2 -2
- package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
- package/dist/orchestrators/router/file-provider.mjs +14 -5
- package/dist/orchestrators/router/file-provider.mjs.map +1 -1
- package/dist/orchestrators/router/hostname.mjs +19 -55
- package/dist/orchestrators/router/hostname.mjs.map +1 -1
- package/dist/orchestrators/router/index.mjs +11 -0
- package/dist/orchestrators/router/profile.mjs +2 -2
- package/dist/orchestrators/router/sentinels.mjs +26 -0
- package/dist/orchestrators/router/sentinels.mjs.map +1 -0
- package/dist/orchestrators/router/service.mjs +40 -37
- package/dist/orchestrators/router/service.mjs.map +1 -1
- package/dist/orchestrators/router/traefik-container.mjs +27 -16
- package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
- package/dist/{substrate/runtime → orchestrators}/run.mjs +26 -21
- package/dist/orchestrators/run.mjs.map +1 -0
- package/dist/orchestrators/runtime-composition.d.mts +1 -1
- package/dist/orchestrators/runtime-composition.mjs +123 -39
- package/dist/orchestrators/runtime-composition.mjs.map +1 -1
- package/dist/orchestrators/snapshot/capture-command.d.mts +1 -0
- package/dist/orchestrators/snapshot/capture-command.mjs +25 -0
- package/dist/orchestrators/snapshot/capture-command.mjs.map +1 -0
- package/dist/orchestrators/snapshot/capture.mjs +42 -28
- package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
- package/dist/orchestrators/snapshot/descriptor.mjs +35 -10
- package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
- package/dist/orchestrators/snapshot/identity-guard.mjs +1 -1
- package/dist/orchestrators/snapshot/index.mjs +13 -0
- package/dist/orchestrators/snapshot/integrity.d.mts +1 -1
- package/dist/orchestrators/snapshot/integrity.mjs +73 -17
- package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
- package/dist/orchestrators/snapshot/pending-marker.d.mts +1 -0
- package/dist/orchestrators/snapshot/pending-marker.mjs +99 -0
- package/dist/orchestrators/snapshot/pending-marker.mjs.map +1 -0
- package/dist/orchestrators/snapshot/phase-error.mjs +23 -0
- package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -0
- package/dist/orchestrators/snapshot/prune.mjs +20 -55
- package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
- package/dist/orchestrators/snapshot/recover-pending.d.mts +1 -0
- package/dist/orchestrators/snapshot/recover-pending.mjs +223 -0
- package/dist/orchestrators/snapshot/recover-pending.mjs.map +1 -0
- package/dist/orchestrators/snapshot/restore.mjs +118 -84
- package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
- package/dist/orchestrators/snapshot/service.mjs +53 -56
- package/dist/orchestrators/snapshot/service.mjs.map +1 -1
- package/dist/orchestrators/snapshot/state-document.d.mts +1 -0
- package/dist/orchestrators/snapshot/state-document.mjs +47 -7
- package/dist/orchestrators/snapshot/state-document.mjs.map +1 -1
- package/dist/orchestrators/snapshot/wipe.mjs +67 -10
- package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
- package/dist/plugins/account/codegen.mjs +9 -8
- package/dist/plugins/account/codegen.mjs.map +1 -1
- package/dist/plugins/account/errors.d.mts +33 -6
- package/dist/plugins/account/errors.mjs.map +1 -1
- package/dist/plugins/account/funding.d.mts +29 -19
- package/dist/plugins/account/funding.mjs +84 -66
- package/dist/plugins/account/funding.mjs.map +1 -1
- package/dist/plugins/account/index.d.mts +2 -4
- package/dist/plugins/account/index.mjs +64 -31
- package/dist/plugins/account/index.mjs.map +1 -1
- package/dist/plugins/account/keypair.mjs +6 -9
- package/dist/plugins/account/keypair.mjs.map +1 -1
- package/dist/plugins/account/lease.mjs +1 -0
- package/dist/plugins/account/lease.mjs.map +1 -1
- package/dist/plugins/account/registry.d.mts +14 -1
- package/dist/plugins/account/registry.mjs +18 -18
- package/dist/plugins/account/registry.mjs.map +1 -1
- package/dist/plugins/account/service.d.mts +23 -4
- package/dist/plugins/account/service.mjs +83 -45
- package/dist/plugins/account/service.mjs.map +1 -1
- package/dist/plugins/account/snapshot.mjs +2 -5
- package/dist/plugins/account/snapshot.mjs.map +1 -1
- package/dist/plugins/account/spans.mjs +16 -0
- package/dist/plugins/account/spans.mjs.map +1 -0
- package/dist/plugins/account/variants/keystore.mjs +6 -8
- package/dist/plugins/account/variants/keystore.mjs.map +1 -1
- package/dist/plugins/action/build-context.d.mts +3 -2
- package/dist/plugins/action/errors.d.mts +16 -12
- package/dist/plugins/action/errors.mjs.map +1 -1
- package/dist/plugins/action/execute.mjs +65 -103
- package/dist/plugins/action/execute.mjs.map +1 -1
- package/dist/plugins/action/index.d.mts +0 -2
- package/dist/plugins/action/index.mjs +17 -13
- package/dist/plugins/action/index.mjs.map +1 -1
- package/dist/plugins/action/service.mjs +38 -35
- package/dist/plugins/action/service.mjs.map +1 -1
- package/dist/plugins/action/spans.mjs +11 -0
- package/dist/plugins/action/spans.mjs.map +1 -0
- package/dist/plugins/coin/codegen.mjs +9 -8
- package/dist/plugins/coin/codegen.mjs.map +1 -1
- package/dist/plugins/coin/discovery.mjs +8 -4
- package/dist/plugins/coin/discovery.mjs.map +1 -1
- package/dist/plugins/coin/index.d.mts +11 -29
- package/dist/plugins/coin/index.mjs +56 -27
- package/dist/plugins/coin/index.mjs.map +1 -1
- package/dist/plugins/coin/metadata.mjs +8 -15
- package/dist/plugins/coin/metadata.mjs.map +1 -1
- package/dist/plugins/coin/mint.d.mts +30 -11
- package/dist/plugins/coin/mint.mjs +78 -74
- package/dist/plugins/coin/mint.mjs.map +1 -1
- package/dist/plugins/coin/registry.mjs +4 -7
- package/dist/plugins/coin/registry.mjs.map +1 -1
- package/dist/plugins/coin/service.d.mts +39 -1
- package/dist/plugins/coin/service.mjs +11 -4
- package/dist/plugins/coin/service.mjs.map +1 -1
- package/dist/plugins/coin/spans.mjs +20 -0
- package/dist/plugins/coin/spans.mjs.map +1 -0
- package/dist/plugins/dashboard/domain.mjs +402 -0
- package/dist/plugins/dashboard/domain.mjs.map +1 -0
- package/dist/plugins/dashboard/index.d.mts +29 -0
- package/dist/plugins/dashboard/index.mjs +91 -0
- package/dist/plugins/dashboard/index.mjs.map +1 -0
- package/dist/plugins/dashboard/origin-policy.mjs +36 -0
- package/dist/plugins/dashboard/origin-policy.mjs.map +1 -0
- package/dist/plugins/dashboard/routable.mjs +27 -0
- package/dist/plugins/dashboard/routable.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/builder.mjs +21 -0
- package/dist/plugins/dashboard/schema/builder.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/enums.mjs +72 -0
- package/dist/plugins/dashboard/schema/enums.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/root.mjs +351 -0
- package/dist/plugins/dashboard/schema/root.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/types.mjs +508 -0
- package/dist/plugins/dashboard/schema/types.mjs.map +1 -0
- package/dist/plugins/dashboard/schema.mjs +11 -0
- package/dist/plugins/dashboard/schema.mjs.map +1 -0
- package/dist/plugins/dashboard/server.mjs +166 -0
- package/dist/plugins/dashboard/server.mjs.map +1 -0
- package/dist/plugins/deepbook/deploy.mjs +53 -115
- package/dist/plugins/deepbook/deploy.mjs.map +1 -1
- package/dist/plugins/deepbook/errors.mjs +1 -0
- package/dist/plugins/deepbook/errors.mjs.map +1 -1
- package/dist/plugins/deepbook/faucet-strategy.mjs +47 -34
- package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/deepbook/hash.mjs +9 -0
- package/dist/plugins/deepbook/hash.mjs.map +1 -0
- package/dist/plugins/deepbook/index.d.mts +1 -10
- package/dist/plugins/deepbook/index.mjs +25 -22
- package/dist/plugins/deepbook/index.mjs.map +1 -1
- package/dist/plugins/deepbook/pyth/index.mjs +26 -55
- package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
- package/dist/plugins/deepbook/snapshot.mjs +17 -29
- package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
- package/dist/plugins/deepbook/spans.mjs +18 -0
- package/dist/plugins/deepbook/spans.mjs.map +1 -0
- package/dist/plugins/deepbook/type-strings.mjs +65 -0
- package/dist/plugins/deepbook/type-strings.mjs.map +1 -0
- package/dist/plugins/deepbook/types.d.mts +2 -3
- package/dist/plugins/deepbook/types.mjs.map +1 -1
- package/dist/plugins/faucet/dispatcher.d.mts +21 -2
- package/dist/plugins/faucet/dispatcher.mjs +1 -2
- package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
- package/dist/plugins/faucet/errors.d.mts +11 -24
- package/dist/plugins/faucet/errors.mjs.map +1 -1
- package/dist/plugins/faucet/http.mjs +33 -14
- package/dist/plugins/faucet/http.mjs.map +1 -1
- package/dist/plugins/faucet/index.d.mts +15 -5
- package/dist/plugins/faucet/index.mjs +3 -0
- package/dist/plugins/faucet/index.mjs.map +1 -1
- package/dist/plugins/faucet/spans.mjs +12 -0
- package/dist/plugins/faucet/spans.mjs.map +1 -0
- package/dist/plugins/host-service/errors.d.mts +1 -1
- package/dist/plugins/host-service/index.mjs +34 -8
- package/dist/plugins/host-service/index.mjs.map +1 -1
- package/dist/plugins/host-service/routable.mjs +2 -1
- package/dist/plugins/host-service/routable.mjs.map +1 -1
- package/dist/plugins/host-service/service.d.mts +21 -0
- package/dist/plugins/host-service/service.mjs +136 -81
- package/dist/plugins/host-service/service.mjs.map +1 -1
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs +23 -0
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -0
- package/dist/plugins/internal/codegen-helpers.mjs +18 -0
- package/dist/plugins/internal/codegen-helpers.mjs.map +1 -0
- package/dist/plugins/internal/funding-failure-error.mjs +32 -0
- package/dist/plugins/internal/funding-failure-error.mjs.map +1 -0
- package/dist/plugins/package/build.mjs +1 -1
- package/dist/plugins/package/build.mjs.map +1 -1
- package/dist/plugins/package/codegen.mjs +26 -0
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/errors.d.mts +16 -6
- package/dist/plugins/package/errors.mjs.map +1 -1
- package/dist/plugins/package/index.d.mts +0 -4
- package/dist/plugins/package/index.mjs +27 -21
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/package/mode-known.mjs +26 -12
- package/dist/plugins/package/mode-known.mjs.map +1 -1
- package/dist/plugins/package/mode-local.mjs +47 -41
- package/dist/plugins/package/mode-local.mjs.map +1 -1
- package/dist/plugins/package/publish-executor.mjs +55 -114
- package/dist/plugins/package/publish-executor.mjs.map +1 -1
- package/dist/plugins/package/registry.mjs +2 -1
- package/dist/plugins/package/registry.mjs.map +1 -1
- package/dist/plugins/package/service.mjs.map +1 -1
- package/dist/plugins/package/spans.mjs +14 -0
- package/dist/plugins/package/spans.mjs.map +1 -0
- package/dist/plugins/postgres/db-ensure.mjs +19 -12
- package/dist/plugins/postgres/db-ensure.mjs.map +1 -1
- package/dist/plugins/postgres/errors.d.mts +5 -0
- package/dist/plugins/postgres/errors.mjs.map +1 -1
- package/dist/plugins/postgres/index.mjs +10 -17
- package/dist/plugins/postgres/index.mjs.map +1 -1
- package/dist/plugins/postgres/service.mjs +58 -24
- package/dist/plugins/postgres/service.mjs.map +1 -1
- package/dist/plugins/postgres/snapshot.mjs.map +1 -1
- package/dist/plugins/postgres/spans.mjs +11 -0
- package/dist/plugins/postgres/spans.mjs.map +1 -0
- package/dist/plugins/router-entrypoints.mjs +4 -4
- package/dist/plugins/router-entrypoints.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +15 -13
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +15 -19
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
- package/dist/plugins/seal/codegen.d.mts +2 -3
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/config-render.mjs +35 -8
- package/dist/plugins/seal/config-render.mjs.map +1 -1
- package/dist/plugins/seal/deploy.mjs +31 -39
- package/dist/plugins/seal/deploy.mjs.map +1 -1
- package/dist/plugins/seal/errors.d.mts +1 -3
- package/dist/plugins/seal/errors.mjs +7 -1
- package/dist/plugins/seal/errors.mjs.map +1 -1
- package/dist/plugins/seal/index.d.mts +1 -6
- package/dist/plugins/seal/index.mjs +40 -14
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/seal/key-manager.d.mts +1 -9
- package/dist/plugins/seal/key-manager.mjs +2 -18
- package/dist/plugins/seal/key-manager.mjs.map +1 -1
- package/dist/plugins/seal/key-server.mjs +37 -36
- package/dist/plugins/seal/key-server.mjs.map +1 -1
- package/dist/plugins/seal/keygen.mjs +16 -17
- package/dist/plugins/seal/keygen.mjs.map +1 -1
- package/dist/plugins/seal/mode/fork-known.mjs +36 -14
- package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
- package/dist/plugins/seal/mode/live.d.mts +6 -4
- package/dist/plugins/seal/mode/live.mjs +14 -21
- package/dist/plugins/seal/mode/live.mjs.map +1 -1
- package/dist/plugins/seal/mode/local-keygen.mjs +57 -21
- package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
- package/dist/plugins/seal/registry-publish.mjs +1 -0
- package/dist/plugins/seal/registry-publish.mjs.map +1 -1
- package/dist/plugins/seal/routable.mjs.map +1 -1
- package/dist/plugins/seal/service.mjs.map +1 -1
- package/dist/plugins/seal/spans.mjs +18 -0
- package/dist/plugins/seal/spans.mjs.map +1 -0
- package/dist/plugins/sui/auto-tick.mjs +17 -23
- package/dist/plugins/sui/auto-tick.mjs.map +1 -1
- package/dist/plugins/sui/chain-build-container.mjs +18 -0
- package/dist/plugins/sui/chain-build-container.mjs.map +1 -0
- package/dist/plugins/sui/chain-probe.d.mts +5 -5
- package/dist/plugins/sui/chain-probe.mjs +28 -58
- package/dist/plugins/sui/chain-probe.mjs.map +1 -1
- package/dist/plugins/sui/codegen.mjs +26 -0
- package/dist/plugins/sui/codegen.mjs.map +1 -1
- package/dist/plugins/sui/errors.d.mts +2 -29
- package/dist/plugins/sui/errors.mjs +1 -3
- package/dist/plugins/sui/errors.mjs.map +1 -1
- package/dist/plugins/sui/fork-faucet-strategy.d.mts +1 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs +55 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -0
- package/dist/plugins/sui/fork-orchestration.d.mts +1 -0
- package/dist/plugins/sui/fork-orchestration.mjs +170 -2
- package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
- package/dist/plugins/sui/fork-transaction.d.mts +2 -0
- package/dist/plugins/sui/fork-transaction.mjs +92 -28
- package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
- package/dist/plugins/sui/index.d.mts +33 -70
- package/dist/plugins/sui/index.mjs +58 -35
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/plugins/{faucet/strategies/sui-local.mjs → sui/local-faucet-strategy.mjs} +10 -7
- package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -0
- package/dist/plugins/sui/mode/external.mjs +7 -13
- package/dist/plugins/sui/mode/external.mjs.map +1 -1
- package/dist/plugins/sui/mode/fork.mjs +136 -59
- package/dist/plugins/sui/mode/fork.mjs.map +1 -1
- package/dist/plugins/sui/mode/live.mjs +10 -16
- package/dist/plugins/sui/mode/live.mjs.map +1 -1
- package/dist/plugins/sui/mode/local.mjs +37 -36
- package/dist/plugins/sui/mode/local.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared-boot.mjs +38 -115
- package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared.d.mts +1 -5
- package/dist/plugins/sui/mode/shared.mjs.map +1 -1
- package/dist/plugins/sui/mode/spec.d.mts +18 -0
- package/dist/plugins/sui/move-summary-runner.mjs +207 -0
- package/dist/plugins/sui/move-summary-runner.mjs.map +1 -0
- package/dist/plugins/sui/network-resolver.d.mts +4 -3
- package/dist/plugins/sui/service.mjs.map +1 -1
- package/dist/plugins/sui/snapshot.mjs +3 -0
- package/dist/plugins/sui/snapshot.mjs.map +1 -1
- package/dist/plugins/sui/spans.mjs +17 -0
- package/dist/plugins/sui/spans.mjs.map +1 -0
- package/dist/plugins/wallet/codegen.d.mts +0 -1
- package/dist/plugins/wallet/codegen.mjs +8 -9
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/index.d.mts +6 -8
- package/dist/plugins/wallet/index.mjs +46 -35
- package/dist/plugins/wallet/index.mjs.map +1 -1
- package/dist/plugins/wallet/origin-policy.mjs +12 -28
- package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
- package/dist/plugins/wallet/pairing.mjs +6 -4
- package/dist/plugins/wallet/pairing.mjs.map +1 -1
- package/dist/plugins/wallet/protocol.d.mts +1 -0
- package/dist/plugins/wallet/protocol.mjs +5 -44
- package/dist/plugins/wallet/protocol.mjs.map +1 -1
- package/dist/plugins/wallet/routable.mjs +2 -8
- package/dist/plugins/wallet/routable.mjs.map +1 -1
- package/dist/plugins/wallet/server.mjs +36 -44
- package/dist/plugins/wallet/server.mjs.map +1 -1
- package/dist/plugins/wallet/service.d.mts +1 -5
- package/dist/plugins/wallet/service.mjs +22 -18
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/plugins/wallet/spans.mjs +22 -0
- package/dist/plugins/wallet/spans.mjs.map +1 -0
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +33 -8
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/walrus/codegen.d.mts +3 -3
- package/dist/plugins/walrus/codegen.mjs.map +1 -1
- package/dist/plugins/walrus/deploy-paths.mjs +21 -5
- package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
- package/dist/plugins/walrus/deploy.mjs +67 -69
- package/dist/plugins/walrus/deploy.mjs.map +1 -1
- package/dist/plugins/walrus/errors.mjs +1 -0
- package/dist/plugins/walrus/errors.mjs.map +1 -1
- package/dist/plugins/walrus/faucet-strategy.mjs +12 -7
- package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/walrus/index.d.mts +2 -13
- package/dist/plugins/walrus/index.mjs +18 -23
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/plugins/walrus/mode/known-deploy.mjs +7 -5
- package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
- package/dist/plugins/walrus/mode/local-cluster.mjs +26 -7
- package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
- package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
- package/dist/plugins/walrus/routable.mjs +7 -23
- package/dist/plugins/walrus/routable.mjs.map +1 -1
- package/dist/plugins/walrus/service.mjs.map +1 -1
- package/dist/plugins/walrus/spans.mjs +18 -0
- package/dist/plugins/walrus/spans.mjs.map +1 -0
- package/dist/plugins/walrus/storage-nodes.d.mts +6 -2
- package/dist/plugins/walrus/storage-nodes.mjs +15 -8
- package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
- package/dist/plugins/walrus/wal-swap.mjs +58 -15
- package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
- package/dist/primitives/artifact-publisher.d.mts +1 -1
- package/dist/primitives/artifact-publisher.mjs +15 -0
- package/dist/primitives/artifact-publisher.mjs.map +1 -0
- package/dist/runtime/docker/client.mjs +17 -4
- package/dist/runtime/docker/client.mjs.map +1 -1
- package/dist/runtime/docker/container.mjs +219 -132
- package/dist/runtime/docker/container.mjs.map +1 -1
- package/dist/runtime/docker/errors.mjs +0 -6
- package/dist/runtime/docker/errors.mjs.map +1 -1
- package/dist/runtime/docker/exec.mjs +21 -18
- package/dist/runtime/docker/exec.mjs.map +1 -1
- package/dist/runtime/docker/image.mjs +36 -13
- package/dist/runtime/docker/image.mjs.map +1 -1
- package/dist/runtime/docker/index.mjs +14 -0
- package/dist/runtime/docker/inspect-and-decode.mjs +51 -0
- package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -0
- package/dist/runtime/docker/inventory.mjs +14 -11
- package/dist/runtime/docker/inventory.mjs.map +1 -1
- package/dist/runtime/docker/labels.mjs +38 -5
- package/dist/runtime/docker/labels.mjs.map +1 -1
- package/dist/runtime/docker/logs.d.mts +1 -1
- package/dist/runtime/docker/logs.mjs.map +1 -1
- package/dist/runtime/docker/network.mjs +83 -47
- package/dist/runtime/docker/network.mjs.map +1 -1
- package/dist/runtime/docker/render-run-args.mjs +82 -0
- package/dist/runtime/docker/render-run-args.mjs.map +1 -0
- package/dist/runtime/docker/service.d.mts +1 -1
- package/dist/runtime/docker/service.mjs +12 -7
- package/dist/runtime/docker/service.mjs.map +1 -1
- package/dist/runtime/docker/sweep.mjs +118 -43
- package/dist/runtime/docker/sweep.mjs.map +1 -1
- package/dist/runtime/docker/volume.mjs +2 -0
- package/dist/runtime/docker/volume.mjs.map +1 -1
- package/dist/runtime/docker/wrap.mjs +30 -1
- package/dist/runtime/docker/wrap.mjs.map +1 -1
- package/dist/substrate/cross-process.mjs +14 -8
- package/dist/substrate/cross-process.mjs.map +1 -1
- package/dist/substrate/event-time.mjs +33 -0
- package/dist/substrate/event-time.mjs.map +1 -0
- package/dist/substrate/events.d.mts +28 -8
- package/dist/substrate/identity.d.mts +2 -1
- package/dist/substrate/manifest.d.mts +68 -5
- package/dist/substrate/manifest.mjs +34 -5
- package/dist/substrate/manifest.mjs.map +1 -1
- package/dist/substrate/network.d.mts +3 -5
- package/dist/substrate/options.d.mts +0 -2
- package/dist/substrate/plugin.d.mts +13 -0
- package/dist/substrate/plugin.mjs +4 -2
- package/dist/substrate/plugin.mjs.map +1 -1
- package/dist/substrate/projection.d.mts +35 -3
- package/dist/substrate/runtime/artifact-publisher/index.mjs +19 -9
- package/dist/substrate/runtime/artifact-publisher/index.mjs.map +1 -1
- package/dist/substrate/runtime/atomic-write.mjs +97 -32
- package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
- package/dist/substrate/runtime/cache/index.mjs +3 -0
- package/dist/substrate/runtime/cache/schema.mjs +2 -2
- package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
- package/dist/substrate/runtime/cache/service.mjs +9 -7
- package/dist/substrate/runtime/cache/service.mjs.map +1 -1
- package/dist/substrate/runtime/capability-sinks/index.d.mts +1 -1
- package/dist/substrate/runtime/capability-sinks/index.mjs +3 -0
- package/dist/substrate/runtime/capability-sinks/layer.mjs +1 -1
- package/dist/substrate/runtime/capability-sinks/service.d.mts +61 -2
- package/dist/substrate/runtime/capability-sinks/service.mjs +21 -26
- package/dist/substrate/runtime/capability-sinks/service.mjs.map +1 -1
- package/dist/substrate/runtime/config-validation.d.mts +1 -13
- package/dist/substrate/runtime/config-validation.mjs +2 -91
- package/dist/substrate/runtime/config-validation.mjs.map +1 -1
- package/dist/substrate/runtime/container-runtime.d.mts +1 -0
- package/dist/substrate/runtime/container-runtime.mjs +2 -0
- package/dist/substrate/runtime/control-plane/domain.mjs +101 -0
- package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -0
- package/dist/substrate/runtime/control-plane/service.mjs +7 -0
- package/dist/substrate/runtime/control-plane/service.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +46 -19
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs +30 -12
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/index.mjs +5 -0
- package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs +11 -2
- package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs +1 -1
- package/dist/substrate/runtime/cross-process/index.mjs +11 -0
- package/dist/substrate/runtime/cross-process/live-clock.mjs +34 -0
- package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/liveness.mjs +52 -10
- package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/lock.d.mts +1 -0
- package/dist/substrate/runtime/cross-process/lock.mjs +23 -0
- package/dist/substrate/runtime/cross-process/lock.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +64 -0
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/roster.mjs +97 -83
- package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/self-pid.mjs +8 -0
- package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +24 -28
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/stack-lock.mjs +41 -20
- package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
- package/dist/substrate/runtime/current-plugin.mjs +2 -2
- package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
- package/dist/substrate/runtime/errors.mjs +12 -6
- package/dist/substrate/runtime/errors.mjs.map +1 -1
- package/dist/substrate/runtime/format-unknown-error.mjs +30 -0
- package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -0
- package/dist/substrate/runtime/host-bind-mount-owner.mjs +10 -0
- package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -0
- package/dist/substrate/runtime/host-gateway.mjs +20 -0
- package/dist/substrate/runtime/host-gateway.mjs.map +1 -0
- package/dist/substrate/runtime/host-tree-tar/index.mjs +61 -50
- package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
- package/dist/substrate/runtime/http-probe.d.mts +1 -1
- package/dist/substrate/runtime/index.mjs +14 -0
- package/dist/substrate/runtime/lease-broker/index.d.mts +1 -1
- package/dist/substrate/runtime/lease-broker/index.mjs +2 -0
- package/dist/substrate/runtime/lease-broker/service.d.mts +1 -64
- package/dist/substrate/runtime/lease-broker/service.mjs +1 -1
- package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs +22 -3
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/index.mjs +9 -0
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +20 -3
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs +5 -5
- package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/state-machine.mjs +1 -1
- package/dist/substrate/runtime/managed-container.d.mts +19 -1
- package/dist/substrate/runtime/managed-container.mjs +20 -1
- package/dist/substrate/runtime/managed-container.mjs.map +1 -1
- package/dist/substrate/runtime/manifest/index.mjs +3 -0
- package/dist/substrate/runtime/manifest/manifest.mjs +15 -11
- package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/mode-errors.mjs +20 -0
- package/dist/substrate/runtime/mode-errors.mjs.map +1 -0
- package/dist/substrate/runtime/observability/cascade-formatter.mjs +9 -13
- package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
- package/dist/substrate/runtime/observability/formatter-registry.mjs +19 -36
- package/dist/substrate/runtime/observability/formatter-registry.mjs.map +1 -1
- package/dist/substrate/runtime/observability/ignore-with-log.d.mts +1 -0
- package/dist/substrate/runtime/observability/ignore-with-log.mjs +17 -0
- package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -0
- package/dist/substrate/runtime/observability/index.d.mts +1 -1
- package/dist/substrate/runtime/observability/index.mjs +13 -0
- package/dist/substrate/runtime/observability/log-store.d.mts +1 -0
- package/dist/substrate/runtime/observability/log-store.mjs +175 -0
- package/dist/substrate/runtime/observability/log-store.mjs.map +1 -0
- package/dist/substrate/runtime/observability/logger.d.mts +2 -1
- package/dist/substrate/runtime/observability/logger.mjs +5 -9
- package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
- package/dist/substrate/runtime/observability/output-truncate.mjs +35 -0
- package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -0
- package/dist/substrate/runtime/observability/pretty-error.mjs +9 -12
- package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
- package/dist/substrate/runtime/observability/redaction.d.mts +2 -13
- package/dist/substrate/runtime/observability/redaction.mjs +8 -14
- package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
- package/dist/substrate/runtime/observability/span-store.d.mts +1 -0
- package/dist/substrate/runtime/observability/span-store.mjs +110 -0
- package/dist/substrate/runtime/observability/span-store.mjs.map +1 -0
- package/dist/substrate/runtime/observability/spans.mjs +14 -14
- package/dist/substrate/runtime/observability/spans.mjs.map +1 -1
- package/dist/substrate/runtime/observability/subprocess-capture.mjs +1 -1
- package/dist/substrate/runtime/passthrough-or-wrap.mjs +33 -0
- package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -0
- package/dist/substrate/runtime/paths.d.mts +1 -1
- package/dist/substrate/runtime/paths.mjs +4 -3
- package/dist/substrate/runtime/paths.mjs.map +1 -1
- package/dist/substrate/runtime/phase-preserving-produce.mjs +40 -0
- package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -0
- package/dist/substrate/runtime/port-broker/index.mjs +3 -0
- package/dist/substrate/runtime/port-broker/service.mjs +170 -126
- package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/probes.d.mts +7 -5
- package/dist/substrate/runtime/probes.mjs +3 -1
- package/dist/substrate/runtime/probes.mjs.map +1 -1
- package/dist/substrate/runtime/projection/index.mjs +4 -0
- package/dist/substrate/runtime/projection/operational-endpoints.mjs +1 -2
- package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
- package/dist/substrate/runtime/projection/persisted.mjs +33 -15
- package/dist/substrate/runtime/projection/persisted.mjs.map +1 -1
- package/dist/substrate/runtime/projection/state-ref.mjs +26 -2
- package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
- package/dist/substrate/runtime/projection/update.mjs +88 -24
- package/dist/substrate/runtime/projection/update.mjs.map +1 -1
- package/dist/substrate/runtime/random-suffix.mjs +11 -0
- package/dist/substrate/runtime/random-suffix.mjs.map +1 -0
- package/dist/substrate/runtime/retry-policy.d.mts +59 -1
- package/dist/substrate/runtime/retry-policy.mjs +66 -1
- package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
- package/dist/substrate/runtime/routed-url.d.mts +1 -0
- package/dist/substrate/runtime/routed-url.mjs +79 -0
- package/dist/substrate/runtime/routed-url.mjs.map +1 -0
- package/dist/substrate/runtime/runtime-decode.d.mts +1 -1
- package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-http-server.mjs +2 -3
- package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-multimap/index.mjs +2 -0
- package/dist/substrate/runtime/scoped-multimap/service.mjs +52 -0
- package/dist/substrate/runtime/scoped-multimap/service.mjs.map +1 -0
- package/dist/substrate/runtime/scoped-ref-map/index.mjs +2 -0
- package/dist/substrate/runtime/scoped-ref-map/service.mjs +2 -2
- package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +1 -1
- package/dist/substrate/runtime/stage-and-swap/index.mjs +72 -4
- package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
- package/dist/substrate/runtime/state-store/index.d.mts +1 -0
- package/dist/substrate/runtime/state-store/index.mjs +3 -0
- package/dist/substrate/runtime/state-store/schema.d.mts +1 -0
- package/dist/substrate/runtime/state-store/schema.mjs +19 -14
- package/dist/substrate/runtime/state-store/schema.mjs.map +1 -1
- package/dist/substrate/runtime/state-store/service.d.mts +1 -0
- package/dist/substrate/runtime/state-store/service.mjs +145 -0
- package/dist/substrate/runtime/state-store/service.mjs.map +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.d.mts +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs +33 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/index.mjs +4 -0
- package/dist/substrate/runtime/strategy-registry/service.mjs +19 -33
- package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
- package/dist/substrate/runtime/subnet-broker.mjs +60 -0
- package/dist/substrate/runtime/subnet-broker.mjs.map +1 -0
- package/dist/substrate/runtime/sui-execute/index.d.mts +21 -1
- package/dist/substrate/runtime/sui-execute/index.mjs +84 -20
- package/dist/substrate/runtime/sui-execute/index.mjs.map +1 -1
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.d.mts +1 -0
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs +32 -0
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +1 -0
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs +32 -0
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +1 -0
- package/dist/substrate/runtime/sui-move-build/index.mjs +93 -19
- package/dist/substrate/runtime/sui-move-build/index.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/acquire-node.mjs +181 -0
- package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/background-tasks.mjs +144 -0
- package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/command-loop.mjs +68 -0
- package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +151 -0
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/errors.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/errors.mjs +9 -0
- package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/index.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/index.mjs +3 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs +51 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs +250 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/state.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/state.mjs +22 -0
- package/dist/substrate/runtime/supervisor/state.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/teardown.mjs +57 -0
- package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/types.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/wiring.mjs +133 -0
- package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -0
- package/dist/substrate/runtime/typed-env.mjs +8 -0
- package/dist/substrate/runtime/typed-env.mjs.map +1 -0
- package/dist/substrate/state-store.d.mts +1 -0
- package/dist/substrate/versioned-doc-schema.mjs +17 -0
- package/dist/substrate/versioned-doc-schema.mjs.map +1 -0
- package/dist/substrate/versioned-doc-sync.mjs +94 -0
- package/dist/substrate/versioned-doc-sync.mjs.map +1 -0
- package/dist/surfaces/cli/command-tree.mjs.map +1 -1
- package/dist/surfaces/cli/commands/index.mjs +9 -0
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs +13 -0
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -0
- package/dist/surfaces/cli/commands/prune-picker.mjs +3 -2
- package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune.mjs +34 -17
- package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
- package/dist/surfaces/cli/commands/status.mjs +2 -2
- package/dist/surfaces/cli/commands/status.mjs.map +1 -1
- package/dist/surfaces/cli/commands/supervisor-presence.mjs +5 -3
- package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
- package/dist/surfaces/cli/commands/wipe.mjs +38 -4
- package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
- package/dist/surfaces/cli/errors.mjs +1 -1
- package/dist/surfaces/cli/errors.mjs.map +1 -1
- package/dist/surfaces/cli/flags.mjs +0 -1
- package/dist/surfaces/cli/flags.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs +98 -18
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/dist/surfaces/cli/output.mjs +1 -0
- package/dist/surfaces/cli/output.mjs.map +1 -1
- package/dist/surfaces/cli/sysexits.mjs +2 -1
- package/dist/surfaces/cli/sysexits.mjs.map +1 -1
- package/dist/surfaces/tui/app.mjs +25 -3
- package/dist/surfaces/tui/app.mjs.map +1 -1
- package/dist/surfaces/tui/display-derivation.mjs +23 -74
- package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
- package/dist/surfaces/tui/event-log.mjs +35 -23
- package/dist/surfaces/tui/event-log.mjs.map +1 -1
- package/dist/surfaces/tui/input.mjs +5 -1
- package/dist/surfaces/tui/input.mjs.map +1 -1
- package/dist/surfaces/tui/plain-renderer.mjs +70 -34
- package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
- package/package.json +24 -4
- package/dist/contracts/liveness-classifier.d.mts +0 -19
- package/dist/contracts/network-resolver.d.mts +0 -15
- package/dist/contracts/network-resolver.mjs +0 -7
- package/dist/contracts/network-resolver.mjs.map +0 -1
- package/dist/orchestrators/codegen/extras.mjs +0 -16
- package/dist/orchestrators/codegen/extras.mjs.map +0 -1
- package/dist/plugins/deepbook/routable.mjs +0 -21
- package/dist/plugins/deepbook/routable.mjs.map +0 -1
- package/dist/plugins/faucet/service.d.mts +0 -20
- package/dist/plugins/faucet/strategies/sui-local.d.mts +0 -23
- package/dist/plugins/faucet/strategies/sui-local.mjs.map +0 -1
- package/dist/plugins/sui/seed-objects.d.mts +0 -18
- package/dist/plugins/sui/seed-objects.mjs +0 -25
- package/dist/plugins/sui/seed-objects.mjs.map +0 -1
- package/dist/runtime/built-in-plugin-layers.mjs.map +0 -1
- package/dist/substrate/runtime/context-helpers.mjs +0 -32
- package/dist/substrate/runtime/context-helpers.mjs.map +0 -1
- package/dist/substrate/runtime/run.mjs.map +0 -1
- package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs +0 -30
- package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs.map +0 -1
- package/dist/substrate/runtime/supervisor.d.mts +0 -1
- package/dist/substrate/runtime/supervisor.mjs +0 -831
- package/dist/substrate/runtime/supervisor.mjs.map +0 -1
- package/dist/surfaces/cli/commands/doctor-probes.mjs.map +0 -1
- /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 {
|
|
8
|
-
import { listContainers, listDevstackContainers, listDevstackImages, listDevstackNetworks,
|
|
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 (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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 ??
|
|
182
|
-
retryDelayMillis: options.retryDelayMillis ??
|
|
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")
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 {
|
|
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":"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"}
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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"}
|