@mysten-incubation/devstack 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dashboard-ui/assets/grpc-a4usE3Nk.js +3 -0
- package/dashboard-ui/assets/index-B82Bh84P.css +1 -0
- package/dashboard-ui/assets/index-CRYJ4pru.js +1277 -0
- package/dashboard-ui/index.html +2 -2
- package/dist/api/define-capabilities.d.mts +1 -20
- package/dist/api/define-capabilities.mjs +15 -22
- package/dist/api/define-capabilities.mjs.map +1 -1
- package/dist/api/define-devstack-with.d.mts +7 -1
- package/dist/api/define-devstack-with.mjs.map +1 -1
- package/dist/api/define-devstack.mjs.map +1 -1
- package/dist/api/define-plugin.d.mts +2 -1
- package/dist/api/inference-network.d.mts +31 -17
- package/dist/api/inference-network.mjs +40 -43
- package/dist/api/inference-network.mjs.map +1 -1
- package/dist/api/load-config.mjs +88 -0
- package/dist/api/load-config.mjs.map +1 -0
- package/dist/api/mode-narrowed-factory.d.mts +1 -1
- package/dist/api/mode-narrowed-factory.mjs.map +1 -1
- package/dist/api/run-stack-internal.mjs +202 -0
- package/dist/api/run-stack-internal.mjs.map +1 -0
- package/dist/api/run-stack.d.mts +82 -31
- package/dist/api/run-stack.mjs +22 -141
- package/dist/api/run-stack.mjs.map +1 -1
- package/dist/build-integrations/dapp-kit/index.d.mts +61 -0
- package/dist/build-integrations/dapp-kit/index.mjs +58 -0
- package/dist/build-integrations/dapp-kit/index.mjs.map +1 -0
- package/dist/build-integrations/playwright/config.d.mts +1 -30
- package/dist/build-integrations/playwright/config.mjs +1 -27
- package/dist/build-integrations/playwright/config.mjs.map +1 -1
- package/dist/build-integrations/playwright/errors.d.mts +4 -7
- package/dist/build-integrations/playwright/errors.mjs.map +1 -1
- package/dist/build-integrations/playwright/global-setup.d.mts +42 -71
- package/dist/build-integrations/playwright/global-setup.mjs +142 -218
- package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
- package/dist/build-integrations/playwright/index.d.mts +2 -2
- package/dist/build-integrations/playwright/index.mjs +2 -2
- package/dist/build-integrations/playwright/stack-context.d.mts +3 -1
- package/dist/build-integrations/playwright/stack-context.mjs +4 -2
- package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
- package/dist/build-integrations/playwright/wallet-context.mjs +44 -19
- package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
- package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -1
- package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
- package/dist/build-integrations/runtime/discover.d.mts +6 -4
- package/dist/build-integrations/runtime/discover.mjs +15 -4
- package/dist/build-integrations/runtime/discover.mjs.map +1 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
- package/dist/build-integrations/runtime/errors.mjs.map +1 -1
- package/dist/build-integrations/runtime/read-stack-context.mjs +3 -3
- package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +23 -7
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs +25 -12
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -1
- package/dist/build-integrations/runtime/stack-context.d.mts +2 -2
- package/dist/build-integrations/vite/index.d.mts +84 -19
- package/dist/build-integrations/vite/index.mjs +203 -28
- package/dist/build-integrations/vite/index.mjs.map +1 -1
- package/dist/build-integrations/vitest/config.d.mts +20 -2
- package/dist/build-integrations/vitest/config.mjs +16 -4
- package/dist/build-integrations/vitest/config.mjs.map +1 -1
- package/dist/build-integrations/vitest/env.d.mts +18 -10
- package/dist/build-integrations/vitest/env.mjs +11 -10
- package/dist/build-integrations/vitest/env.mjs.map +1 -1
- package/dist/build-integrations/vitest/errors.d.mts +7 -11
- package/dist/build-integrations/vitest/errors.mjs +3 -4
- package/dist/build-integrations/vitest/errors.mjs.map +1 -1
- package/dist/build-integrations/vitest/global-setup.d.mts +35 -0
- package/dist/build-integrations/vitest/global-setup.mjs +156 -0
- package/dist/build-integrations/vitest/global-setup.mjs.map +1 -0
- package/dist/build-integrations/vitest/index.d.mts +3 -2
- package/dist/build-integrations/vitest/index.mjs +3 -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 +5 -3
- package/dist/build-integrations/vitest/stack-context.mjs +3 -2
- package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
- package/dist/cli/bail.mjs.map +1 -1
- package/dist/cli/doctor-probes.mjs +1 -3
- package/dist/cli/doctor-probes.mjs.map +1 -1
- package/dist/cli/main.d.mts +23 -1
- package/dist/cli/main.mjs +63 -8
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/prune-direct.mjs.map +1 -1
- package/dist/cli/snapshot-reader.mjs.map +1 -1
- package/dist/cli/up-lifecycle.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +28 -21
- package/dist/cli/wirings/apply.mjs.map +1 -1
- package/dist/cli/wirings/codegen.mjs +108 -0
- package/dist/cli/wirings/codegen.mjs.map +1 -0
- package/dist/cli/wirings/config-loader.mjs +12 -62
- package/dist/cli/wirings/config-loader.mjs.map +1 -1
- package/dist/cli/wirings/dump-ids.mjs +86 -0
- package/dist/cli/wirings/dump-ids.mjs.map +1 -0
- package/dist/cli/wirings/engine-command.mjs +1 -1
- package/dist/cli/wirings/engine-command.mjs.map +1 -1
- package/dist/cli/wirings/identity.mjs +2 -2
- package/dist/cli/wirings/identity.mjs.map +1 -1
- package/dist/cli/wirings/provide-file-system.mjs.map +1 -1
- package/dist/cli/wirings/read-devstack-version.mjs +17 -0
- package/dist/cli/wirings/read-devstack-version.mjs.map +1 -0
- package/dist/cli/wirings/snapshot.mjs +27 -34
- package/dist/cli/wirings/snapshot.mjs.map +1 -1
- package/dist/cli/wirings/up-ipc.mjs +147 -0
- package/dist/cli/wirings/up-ipc.mjs.map +1 -0
- package/dist/cli/wirings/up.mjs +218 -198
- package/dist/cli/wirings/up.mjs.map +1 -1
- package/dist/cli/wirings/wipe.mjs +1 -1
- package/dist/cli/wirings/wipe.mjs.map +1 -1
- package/dist/contracts/chain-probe.d.mts +1 -1
- package/dist/contracts/chain-probe.mjs +1 -1
- package/dist/contracts/chain-probe.mjs.map +1 -1
- package/dist/contracts/codegenable.d.mts +60 -6
- package/dist/contracts/codegenable.mjs +28 -0
- package/dist/contracts/codegenable.mjs.map +1 -0
- package/dist/contracts/config-bindings.mjs +162 -0
- package/dist/contracts/config-bindings.mjs.map +1 -0
- package/dist/contracts/container-runtime.d.mts +23 -17
- package/dist/contracts/plugin-expander.mjs.map +1 -1
- package/dist/contracts/snapshotable.d.mts +5 -7
- package/dist/contracts/wallet-protocol.d.mts +2 -5
- package/dist/contracts/wallet-protocol.mjs +2 -5
- package/dist/contracts/wallet-protocol.mjs.map +1 -1
- package/dist/index.d.mts +21 -26
- package/dist/index.mjs +8 -9
- package/dist/orchestrators/boot.d.mts +28 -0
- package/dist/orchestrators/boot.mjs +440 -0
- package/dist/orchestrators/boot.mjs.map +1 -0
- package/dist/orchestrators/codegen/bindings.d.mts +13 -1
- package/dist/orchestrators/codegen/bindings.mjs +23 -9
- package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
- package/dist/orchestrators/codegen/config-runtime.mjs +208 -0
- package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -0
- package/dist/orchestrators/codegen/emit.mjs +1 -1
- package/dist/orchestrators/codegen/emit.mjs.map +1 -1
- package/dist/orchestrators/codegen/errors.mjs +21 -1
- package/dist/orchestrators/codegen/errors.mjs.map +1 -1
- package/dist/orchestrators/codegen/format.mjs +2 -0
- package/dist/orchestrators/codegen/format.mjs.map +1 -1
- package/dist/orchestrators/codegen/gitignore.mjs +30 -11
- package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
- package/dist/orchestrators/codegen/id-config.mjs +107 -0
- package/dist/orchestrators/codegen/id-config.mjs.map +1 -0
- package/dist/orchestrators/codegen/output-location.mjs +23 -31
- package/dist/orchestrators/codegen/output-location.mjs.map +1 -1
- package/dist/orchestrators/codegen/paths.mjs +8 -1
- package/dist/orchestrators/codegen/paths.mjs.map +1 -1
- package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
- package/dist/orchestrators/codegen/service.mjs +286 -58
- package/dist/orchestrators/codegen/service.mjs.map +1 -1
- package/dist/orchestrators/layers.mjs +31 -0
- package/dist/orchestrators/layers.mjs.map +1 -0
- package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -1
- package/dist/orchestrators/lifecycle-prune/index.mjs +8 -10
- package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -1
- package/dist/orchestrators/network-options.d.mts +52 -0
- package/dist/orchestrators/network-options.mjs +43 -0
- package/dist/orchestrators/network-options.mjs.map +1 -0
- package/dist/orchestrators/router/cleanup.mjs.map +1 -1
- package/dist/orchestrators/router/cors.mjs.map +1 -1
- package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
- package/dist/orchestrators/router/errors.mjs.map +1 -1
- package/dist/orchestrators/router/file-provider.mjs.map +1 -1
- package/dist/orchestrators/router/hostname.mjs.map +1 -1
- package/dist/orchestrators/router/profile.mjs +1 -1
- package/dist/orchestrators/router/profile.mjs.map +1 -1
- package/dist/orchestrators/router/service.d.mts +2 -2
- package/dist/orchestrators/router/service.mjs +12 -27
- package/dist/orchestrators/router/service.mjs.map +1 -1
- package/dist/orchestrators/router/traefik-container.mjs +1 -1
- package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
- package/dist/orchestrators/snapshot/capture.mjs +171 -195
- package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
- package/dist/orchestrators/snapshot/descriptor.d.mts +1 -1
- package/dist/orchestrators/snapshot/descriptor.mjs +53 -11
- package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
- package/dist/orchestrators/snapshot/identity-guard.mjs +3 -5
- package/dist/orchestrators/snapshot/identity-guard.mjs.map +1 -1
- package/dist/orchestrators/snapshot/image-bundle-tags.mjs +32 -101
- package/dist/orchestrators/snapshot/image-bundle-tags.mjs.map +1 -1
- package/dist/orchestrators/snapshot/index.mjs +1 -4
- package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
- package/dist/orchestrators/snapshot/interrupted-restore.mjs +116 -0
- package/dist/orchestrators/snapshot/interrupted-restore.mjs.map +1 -0
- package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -1
- package/dist/orchestrators/snapshot/prune.mjs +40 -31
- package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
- package/dist/orchestrators/snapshot/restore.mjs +221 -196
- package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
- package/dist/orchestrators/snapshot/service.mjs +72 -62
- package/dist/orchestrators/snapshot/service.mjs.map +1 -1
- package/dist/orchestrators/snapshot/wipe.mjs +67 -56
- package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
- package/dist/plugins/account/codegen.mjs +3 -0
- package/dist/plugins/account/codegen.mjs.map +1 -1
- package/dist/plugins/account/errors.d.mts +2 -2
- package/dist/plugins/account/errors.mjs +1 -4
- package/dist/plugins/account/errors.mjs.map +1 -1
- package/dist/plugins/account/funding.mjs +3 -27
- package/dist/plugins/account/funding.mjs.map +1 -1
- package/dist/plugins/account/index.d.mts +3 -8
- package/dist/plugins/account/index.mjs +71 -35
- package/dist/plugins/account/index.mjs.map +1 -1
- package/dist/plugins/account/keypair.d.mts +8 -7
- package/dist/plugins/account/keypair.mjs +5 -18
- package/dist/plugins/account/keypair.mjs.map +1 -1
- package/dist/plugins/account/lease.mjs.map +1 -1
- package/dist/plugins/account/registry.d.mts +1 -46
- package/dist/plugins/account/registry.mjs.map +1 -1
- package/dist/plugins/account/service.d.mts +43 -34
- package/dist/plugins/account/service.mjs +9 -54
- package/dist/plugins/account/service.mjs.map +1 -1
- package/dist/plugins/account/snapshot.mjs.map +1 -1
- package/dist/plugins/account/variants/ephemeral.mjs.map +1 -1
- package/dist/plugins/account/variants/impersonate.mjs.map +1 -1
- package/dist/plugins/account/variants/signer.mjs.map +1 -1
- package/dist/plugins/action/discriminator.mjs.map +1 -1
- package/dist/plugins/action/errors.mjs +1 -4
- package/dist/plugins/action/errors.mjs.map +1 -1
- package/dist/plugins/action/execute.mjs +3 -4
- package/dist/plugins/action/execute.mjs.map +1 -1
- package/dist/plugins/action/index.d.mts +2 -2
- package/dist/plugins/action/index.mjs +7 -11
- package/dist/plugins/action/index.mjs.map +1 -1
- package/dist/plugins/action/service.d.mts +5 -7
- package/dist/plugins/action/service.mjs +2 -16
- package/dist/plugins/action/service.mjs.map +1 -1
- package/dist/plugins/coin/address-resolution.mjs.map +1 -1
- package/dist/plugins/coin/codegen.mjs +97 -16
- package/dist/plugins/coin/codegen.mjs.map +1 -1
- package/dist/plugins/coin/discovery.mjs.map +1 -1
- package/dist/plugins/coin/errors.mjs +1 -4
- package/dist/plugins/coin/errors.mjs.map +1 -1
- package/dist/plugins/coin/index.d.mts +4 -34
- package/dist/plugins/coin/index.mjs +47 -28
- package/dist/plugins/coin/index.mjs.map +1 -1
- package/dist/plugins/coin/metadata.d.mts +1 -0
- package/dist/plugins/coin/metadata.mjs +8 -9
- package/dist/plugins/coin/metadata.mjs.map +1 -1
- package/dist/plugins/coin/mint.mjs +7 -21
- package/dist/plugins/coin/mint.mjs.map +1 -1
- package/dist/plugins/coin/registry.mjs +33 -20
- package/dist/plugins/coin/registry.mjs.map +1 -1
- package/dist/plugins/coin/service.d.mts +1 -1
- package/dist/plugins/coin/service.mjs +1 -8
- package/dist/plugins/coin/service.mjs.map +1 -1
- package/dist/plugins/coin/snapshot.mjs.map +1 -1
- package/dist/plugins/coin/type-strings.mjs.map +1 -1
- package/dist/plugins/dashboard/domain.mjs +106 -167
- package/dist/plugins/dashboard/domain.mjs.map +1 -1
- package/dist/plugins/dashboard/index.mjs +13 -13
- package/dist/plugins/dashboard/index.mjs.map +1 -1
- package/dist/plugins/dashboard/origin-policy.mjs +3 -3
- package/dist/plugins/dashboard/origin-policy.mjs.map +1 -1
- package/dist/plugins/dashboard/routable.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/builder.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/enums.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/root.mjs +23 -44
- package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/types.mjs +37 -91
- package/dist/plugins/dashboard/schema/types.mjs.map +1 -1
- package/dist/plugins/dashboard/schema.mjs.map +1 -1
- package/dist/plugins/dashboard/server.mjs.map +1 -1
- package/dist/plugins/deepbook/codegen.d.mts +12 -11
- package/dist/plugins/deepbook/codegen.mjs +108 -11
- package/dist/plugins/deepbook/codegen.mjs.map +1 -1
- package/dist/plugins/deepbook/deploy.mjs +7 -14
- package/dist/plugins/deepbook/deploy.mjs.map +1 -1
- package/dist/plugins/deepbook/errors.mjs +4 -2
- package/dist/plugins/deepbook/errors.mjs.map +1 -1
- package/dist/plugins/deepbook/faucet-strategy.mjs +4 -10
- package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/deepbook/hash.mjs.map +1 -1
- package/dist/plugins/deepbook/index.d.mts +38 -34
- package/dist/plugins/deepbook/index.mjs +133 -64
- package/dist/plugins/deepbook/index.mjs.map +1 -1
- package/dist/plugins/deepbook/plugin-key.mjs.map +1 -1
- package/dist/plugins/deepbook/pyth/index.mjs +4 -8
- package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
- package/dist/plugins/deepbook/snapshot.mjs +0 -1
- package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
- package/dist/plugins/deepbook/type-strings.mjs.map +1 -1
- package/dist/plugins/deepbook/types.d.mts +2 -1
- package/dist/plugins/deepbook/types.mjs.map +1 -1
- package/dist/plugins/faucet/dispatcher.d.mts +3 -3
- package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
- package/dist/plugins/faucet/errors.mjs.map +1 -1
- package/dist/plugins/faucet/http.mjs +2 -10
- package/dist/plugins/faucet/http.mjs.map +1 -1
- package/dist/plugins/faucet/index.mjs +0 -1
- package/dist/plugins/faucet/index.mjs.map +1 -1
- package/dist/plugins/host-service/errors.d.mts +1 -3
- package/dist/plugins/host-service/errors.mjs +1 -2
- package/dist/plugins/host-service/errors.mjs.map +1 -1
- package/dist/plugins/host-service/index.d.mts +1 -2
- package/dist/plugins/host-service/index.mjs +11 -11
- package/dist/plugins/host-service/index.mjs.map +1 -1
- package/dist/plugins/host-service/routable.mjs.map +1 -1
- package/dist/plugins/host-service/service.mjs +13 -13
- package/dist/plugins/host-service/service.mjs.map +1 -1
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -1
- package/dist/plugins/internal/codegen-helpers.mjs +2 -0
- package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
- package/dist/plugins/internal/funding-failure-error.mjs.map +1 -1
- package/dist/plugins/{postgres → internal/postgres-sidecar}/connection.mjs +1 -1
- package/dist/plugins/internal/postgres-sidecar/connection.mjs.map +1 -0
- package/dist/plugins/{postgres → internal/postgres-sidecar}/db-ensure.mjs +30 -16
- package/dist/plugins/internal/postgres-sidecar/db-ensure.mjs.map +1 -0
- package/dist/plugins/internal/postgres-sidecar/errors.mjs +21 -0
- package/dist/plugins/internal/postgres-sidecar/errors.mjs.map +1 -0
- package/dist/{substrate/runtime/scoped-multimap → plugins/internal/postgres-sidecar}/index.mjs +1 -0
- package/dist/plugins/internal/postgres-sidecar/service.mjs +121 -0
- package/dist/plugins/internal/postgres-sidecar/service.mjs.map +1 -0
- package/dist/plugins/package/build.mjs +2 -2
- package/dist/plugins/package/build.mjs.map +1 -1
- package/dist/plugins/package/codegen.d.mts +7 -14
- package/dist/plugins/package/codegen.mjs +187 -59
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/dep-resolution.mjs +51 -3
- package/dist/plugins/package/dep-resolution.mjs.map +1 -1
- package/dist/plugins/package/errors.mjs +1 -4
- package/dist/plugins/package/errors.mjs.map +1 -1
- package/dist/plugins/package/git-source.d.mts +18 -0
- package/dist/plugins/package/git-source.mjs +119 -0
- package/dist/plugins/package/git-source.mjs.map +1 -0
- package/dist/plugins/package/index.d.mts +28 -19
- package/dist/plugins/package/index.mjs +107 -32
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/package/mode-known.mjs +2 -2
- package/dist/plugins/package/mode-known.mjs.map +1 -1
- package/dist/plugins/package/mode-local.mjs +18 -35
- package/dist/plugins/package/mode-local.mjs.map +1 -1
- package/dist/plugins/package/publish-executor.mjs +13 -8
- package/dist/plugins/package/publish-executor.mjs.map +1 -1
- package/dist/plugins/package/publish-output.d.mts +1 -11
- package/dist/plugins/package/publish-output.mjs +1 -9
- package/dist/plugins/package/publish-output.mjs.map +1 -1
- package/dist/plugins/package/registry.d.mts +1 -1
- package/dist/plugins/package/registry.mjs +26 -14
- package/dist/plugins/package/registry.mjs.map +1 -1
- package/dist/plugins/package/service.mjs.map +1 -1
- package/dist/plugins/package/snapshot.mjs.map +1 -1
- package/dist/plugins/router-entrypoints.mjs +1 -3
- package/dist/plugins/router-entrypoints.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +1 -2
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +1 -6
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
- package/dist/plugins/seal/codegen.d.mts +1 -5
- package/dist/plugins/seal/codegen.mjs +83 -12
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/config-render.mjs +2 -6
- package/dist/plugins/seal/config-render.mjs.map +1 -1
- package/dist/plugins/seal/deploy.mjs +18 -29
- package/dist/plugins/seal/deploy.mjs.map +1 -1
- package/dist/plugins/seal/errors.d.mts +4 -5
- package/dist/plugins/seal/errors.mjs +1 -8
- package/dist/plugins/seal/errors.mjs.map +1 -1
- package/dist/plugins/seal/index.d.mts +7 -28
- package/dist/plugins/seal/index.mjs +69 -47
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/seal/key-manager.mjs.map +1 -1
- package/dist/plugins/seal/key-server.mjs +1 -6
- package/dist/plugins/seal/key-server.mjs.map +1 -1
- package/dist/plugins/seal/keygen.mjs +2 -3
- package/dist/plugins/seal/keygen.mjs.map +1 -1
- package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
- package/dist/plugins/seal/mode/live.mjs +1 -1
- package/dist/plugins/seal/mode/live.mjs.map +1 -1
- package/dist/plugins/seal/mode/local-keygen.mjs +4 -10
- package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
- package/dist/plugins/seal/plugin-key.mjs.map +1 -1
- 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/snapshot.mjs +1 -2
- package/dist/plugins/seal/snapshot.mjs.map +1 -1
- package/dist/plugins/sui/auto-tick.mjs +5 -5
- package/dist/plugins/sui/auto-tick.mjs.map +1 -1
- package/dist/plugins/sui/chain-build-container.mjs +2 -2
- package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
- package/dist/plugins/sui/chain-probe.mjs +4 -4
- package/dist/plugins/sui/chain-probe.mjs.map +1 -1
- package/dist/plugins/sui/codegen.d.mts +9 -7
- package/dist/plugins/sui/codegen.mjs +49 -46
- package/dist/plugins/sui/codegen.mjs.map +1 -1
- package/dist/plugins/sui/errors.d.mts +1 -1
- package/dist/plugins/sui/errors.mjs +1 -9
- package/dist/plugins/sui/errors.mjs.map +1 -1
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.d.mts +1 -1
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.mjs +3 -7
- package/dist/plugins/sui/exec/index.mjs.map +1 -0
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.mjs +1 -1
- package/dist/plugins/sui/exec/sign-and-dispatch.mjs.map +1 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs +2 -6
- package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -1
- package/dist/plugins/sui/fork-orchestration.mjs +13 -23
- package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
- package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
- package/dist/plugins/sui/index.d.mts +34 -83
- package/dist/plugins/sui/index.mjs +200 -68
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/{substrate/runtime/sui-ledger → plugins/sui/ledger}/object-ref.mjs +1 -1
- package/dist/plugins/sui/ledger/object-ref.mjs.map +1 -0
- package/dist/plugins/sui/local-faucet-strategy.mjs +1 -5
- package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -1
- package/dist/plugins/sui/log-attrs.mjs +11 -0
- package/dist/plugins/sui/log-attrs.mjs.map +1 -0
- package/dist/plugins/sui/mode/external.mjs +4 -12
- package/dist/plugins/sui/mode/external.mjs.map +1 -1
- package/dist/plugins/sui/mode/fork.mjs +19 -16
- package/dist/plugins/sui/mode/fork.mjs.map +1 -1
- package/dist/plugins/sui/mode/live.mjs +4 -17
- package/dist/plugins/sui/mode/live.mjs.map +1 -1
- package/dist/plugins/sui/mode/local.d.mts +1 -0
- package/dist/plugins/sui/mode/local.mjs +203 -61
- package/dist/plugins/sui/mode/local.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared-boot.mjs +18 -23
- package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared.d.mts +6 -7
- package/dist/plugins/sui/mode/shared.mjs.map +1 -1
- package/dist/plugins/sui/mode/spec.d.mts +23 -6
- package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.mjs +28 -30
- package/dist/plugins/sui/move/index.mjs.map +1 -0
- package/dist/plugins/sui/move-summary-runner.mjs +8 -4
- package/dist/plugins/sui/move-summary-runner.mjs.map +1 -1
- package/dist/{substrate/network.d.mts → plugins/sui/network-config.d.mts} +11 -10
- package/dist/plugins/sui/network-resolver.d.mts +6 -6
- package/dist/plugins/sui/routable.mjs.map +1 -1
- package/dist/plugins/sui/service.mjs +10 -6
- package/dist/plugins/sui/service.mjs.map +1 -1
- package/dist/plugins/sui/snapshot.mjs +10 -5
- package/dist/plugins/sui/snapshot.mjs.map +1 -1
- package/dist/plugins/wallet/codegen.d.mts +8 -6
- package/dist/plugins/wallet/codegen.mjs +4 -10
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/errors.mjs +1 -4
- package/dist/plugins/wallet/errors.mjs.map +1 -1
- package/dist/plugins/wallet/index.d.mts +3 -6
- package/dist/plugins/wallet/index.mjs +16 -21
- package/dist/plugins/wallet/index.mjs.map +1 -1
- package/dist/plugins/wallet/origin-policy.mjs +3 -3
- package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
- package/dist/plugins/wallet/pairing.mjs +6 -24
- package/dist/plugins/wallet/pairing.mjs.map +1 -1
- package/dist/plugins/wallet/protocol.mjs.map +1 -1
- package/dist/plugins/wallet/routable.mjs.map +1 -1
- package/dist/plugins/wallet/server.mjs +17 -24
- package/dist/plugins/wallet/server.mjs.map +1 -1
- package/dist/plugins/wallet/service.d.mts +2 -2
- package/dist/plugins/wallet/service.mjs +4 -15
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/plugins/wallet/snapshot.mjs.map +1 -1
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +1 -5
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/walrus/codegen.d.mts +2 -13
- package/dist/plugins/walrus/codegen.mjs +93 -28
- package/dist/plugins/walrus/codegen.mjs.map +1 -1
- package/dist/plugins/walrus/deploy-paths.mjs +2 -3
- package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
- package/dist/plugins/walrus/deploy.mjs +26 -19
- package/dist/plugins/walrus/deploy.mjs.map +1 -1
- package/dist/plugins/walrus/errors.mjs +1 -8
- package/dist/plugins/walrus/errors.mjs.map +1 -1
- package/dist/plugins/walrus/faucet-strategy.mjs +15 -1
- package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/walrus/index.d.mts +13 -44
- package/dist/plugins/walrus/index.mjs +119 -122
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/plugins/walrus/mode/known-deploy.mjs +6 -6
- package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
- package/dist/plugins/walrus/mode/local-cluster.d.mts +2 -2
- package/dist/plugins/walrus/mode/local-cluster.mjs +2 -10
- package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
- package/dist/plugins/walrus/plugin-key.mjs.map +1 -1
- package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
- package/dist/plugins/walrus/routable.mjs.map +1 -1
- package/dist/plugins/walrus/service.mjs.map +1 -1
- package/dist/plugins/walrus/snapshot.mjs +3 -3
- package/dist/plugins/walrus/snapshot.mjs.map +1 -1
- package/dist/plugins/walrus/storage-nodes.mjs +53 -14
- package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
- package/dist/plugins/walrus/wal-swap.mjs +4 -8
- package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
- package/dist/primitives/artifact-publisher.mjs.map +1 -1
- package/dist/primitives/cache.d.mts +1 -1
- package/dist/runtime/docker/client.mjs +47 -3
- package/dist/runtime/docker/client.mjs.map +1 -1
- package/dist/runtime/docker/container.mjs +37 -57
- package/dist/runtime/docker/container.mjs.map +1 -1
- package/dist/runtime/docker/errors.mjs.map +1 -1
- package/dist/runtime/docker/exec.mjs +2 -2
- package/dist/runtime/docker/exec.mjs.map +1 -1
- package/dist/runtime/docker/image.mjs +10 -11
- package/dist/runtime/docker/image.mjs.map +1 -1
- package/dist/runtime/docker/index.mjs +0 -1
- package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -1
- package/dist/runtime/docker/inventory.mjs +9 -9
- package/dist/runtime/docker/inventory.mjs.map +1 -1
- package/dist/runtime/docker/labels.mjs +12 -1
- package/dist/runtime/docker/labels.mjs.map +1 -1
- package/dist/runtime/docker/network.mjs +6 -6
- package/dist/runtime/docker/network.mjs.map +1 -1
- package/dist/runtime/docker/render-run-args.mjs.map +1 -1
- package/dist/runtime/docker/service.mjs +31 -45
- package/dist/runtime/docker/service.mjs.map +1 -1
- package/dist/runtime/docker/sweep.mjs +14 -64
- package/dist/runtime/docker/sweep.mjs.map +1 -1
- package/dist/runtime/docker/volume.mjs +1 -1
- package/dist/runtime/docker/volume.mjs.map +1 -1
- package/dist/runtime/docker/wrap.mjs +14 -2
- package/dist/runtime/docker/wrap.mjs.map +1 -1
- package/dist/substrate/brand.d.mts +2 -5
- package/dist/substrate/brand.mjs +3 -2
- package/dist/substrate/brand.mjs.map +1 -1
- package/dist/substrate/cross-process.mjs +2 -16
- package/dist/substrate/cross-process.mjs.map +1 -1
- package/dist/substrate/event-time.mjs +0 -2
- package/dist/substrate/event-time.mjs.map +1 -1
- package/dist/substrate/events.d.mts +46 -16
- package/dist/substrate/identity.d.mts +11 -5
- package/dist/substrate/manifest.d.mts +33 -26
- package/dist/substrate/manifest.mjs +8 -3
- package/dist/substrate/manifest.mjs.map +1 -1
- package/dist/substrate/options.d.mts +33 -4
- package/dist/substrate/plugin-ctx.d.mts +70 -0
- package/dist/substrate/plugin-ctx.mjs +66 -0
- package/dist/substrate/plugin-ctx.mjs.map +1 -0
- package/dist/substrate/plugin.d.mts +46 -38
- package/dist/substrate/plugin.mjs +13 -9
- package/dist/substrate/plugin.mjs.map +1 -1
- package/dist/substrate/projection.d.mts +1 -1
- package/dist/substrate/runtime/atomic-write.mjs +3 -4
- package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
- package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
- package/dist/substrate/runtime/cache/service.mjs +50 -20
- package/dist/substrate/runtime/cache/service.mjs.map +1 -1
- package/dist/substrate/runtime/config-validation.mjs.map +1 -1
- package/dist/substrate/runtime/control-plane/domain.mjs +34 -12
- package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -1
- package/dist/substrate/runtime/control-plane/service.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +14 -17
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
- 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.map +1 -1
- package/dist/substrate/runtime/cross-process/index.mjs +0 -2
- package/dist/substrate/runtime/cross-process/live-clock.mjs +1 -1
- package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/liveness.mjs +11 -18
- package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +2 -2
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/roster.mjs +17 -127
- package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/stack-lock.mjs +29 -28
- package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
- package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
- package/dist/substrate/runtime/errors.mjs +1 -13
- package/dist/substrate/runtime/errors.mjs.map +1 -1
- package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -1
- package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -1
- package/dist/substrate/runtime/host-gateway.mjs.map +1 -1
- package/dist/substrate/runtime/host-tree-tar/index.mjs +15 -163
- package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
- package/dist/substrate/runtime/http-probe.d.mts +1 -3
- package/dist/substrate/runtime/http-probe.mjs.map +1 -1
- package/dist/substrate/runtime/index.mjs +1 -6
- package/dist/substrate/runtime/lease-broker/service.mjs +5 -23
- package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs +3 -3
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/graph-input-id.d.mts +1 -0
- package/dist/substrate/runtime/lifecycle/graph-input-id.mjs +133 -0
- package/dist/substrate/runtime/lifecycle/graph-input-id.mjs.map +1 -0
- package/dist/substrate/runtime/lifecycle/index.mjs +2 -2
- package/dist/substrate/runtime/lifecycle/plugin-registry.d.mts +1 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +22 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/ready-gate.mjs +2 -15
- package/dist/substrate/runtime/lifecycle/ready-gate.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs +29 -25
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/state-machine.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.d.mts +1 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.mjs +0 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.mjs.map +1 -1
- package/dist/substrate/runtime/managed-container.d.mts +5 -1
- package/dist/substrate/runtime/managed-container.mjs +8 -10
- package/dist/substrate/runtime/managed-container.mjs.map +1 -1
- package/dist/substrate/runtime/manifest/manifest.mjs +8 -30
- package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/mode-errors.d.mts +1 -2
- package/dist/substrate/runtime/mode-errors.mjs.map +1 -1
- package/dist/substrate/runtime/observability/cascade-formatter.mjs +1 -11
- package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
- package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -1
- package/dist/substrate/runtime/observability/index.d.mts +1 -1
- package/dist/substrate/runtime/observability/index.mjs +4 -6
- package/dist/substrate/runtime/observability/log-attrs.mjs +46 -0
- package/dist/substrate/runtime/observability/log-attrs.mjs.map +1 -0
- package/dist/substrate/runtime/observability/log-store.mjs.map +1 -1
- package/dist/substrate/runtime/observability/logger.mjs +3 -3
- package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
- package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -1
- package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
- package/dist/substrate/runtime/observability/process-lines.mjs.map +1 -1
- package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
- package/dist/substrate/runtime/observability/subprocess-capture.mjs +2 -2
- package/dist/substrate/runtime/observability/subprocess-capture.mjs.map +1 -1
- package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -1
- package/dist/substrate/runtime/paths.mjs +27 -9
- package/dist/substrate/runtime/paths.mjs.map +1 -1
- package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -1
- package/dist/substrate/runtime/port-broker/service.mjs +10 -27
- package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/probes.d.mts +2 -3
- package/dist/substrate/runtime/probes.mjs.map +1 -1
- package/dist/substrate/runtime/process-supervisor.mjs.map +1 -1
- package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
- package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
- package/dist/substrate/runtime/projection/update.mjs +87 -11
- package/dist/substrate/runtime/projection/update.mjs.map +1 -1
- package/dist/substrate/runtime/random-suffix.mjs.map +1 -1
- package/dist/substrate/runtime/reconcile/fs-plan.mjs +146 -0
- package/dist/substrate/runtime/reconcile/fs-plan.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/graph.mjs +19 -0
- package/dist/substrate/runtime/reconcile/graph.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/index.mjs +5 -0
- package/dist/substrate/runtime/reconcile/label.mjs +41 -0
- package/dist/substrate/runtime/reconcile/label.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/spec.mjs +15 -0
- package/dist/substrate/runtime/reconcile/spec.mjs.map +1 -0
- package/dist/substrate/runtime/retry-policy.d.mts +2 -3
- package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
- package/dist/substrate/runtime/routed-url.mjs.map +1 -1
- package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
- package/dist/substrate/runtime/stage-and-swap/index.mjs +49 -12
- package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/service.mjs +29 -17
- package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
- package/dist/substrate/runtime/subnet-broker.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/acquire-node.mjs +188 -56
- package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/background-tasks.mjs +79 -65
- package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/command-loop.mjs +82 -23
- package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs +16 -0
- package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/errors.mjs +11 -2
- package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/index.mjs +2 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs +6 -6
- package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs +60 -70
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/state.d.mts +1 -1
- package/dist/substrate/runtime/supervisor/state.mjs +1 -1
- package/dist/substrate/runtime/supervisor/state.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/teardown.mjs +10 -9
- package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/wiring.mjs +6 -20
- package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -1
- package/dist/substrate/runtime/tar/reader.mjs +216 -0
- package/dist/substrate/runtime/tar/reader.mjs.map +1 -0
- package/dist/substrate/runtime/typed-env.mjs.map +1 -1
- package/dist/substrate/versioned-doc-schema.mjs.map +1 -1
- package/dist/substrate/versioned-doc-sync.mjs +5 -5
- package/dist/substrate/versioned-doc-sync.mjs.map +1 -1
- package/dist/surfaces/cli/command-tree.mjs +49 -7
- package/dist/surfaces/cli/command-tree.mjs.map +1 -1
- package/dist/surfaces/cli/commands/config.mjs +1 -1
- package/dist/surfaces/cli/commands/config.mjs.map +1 -1
- package/dist/surfaces/cli/commands/confirm-node.mjs.map +1 -1
- package/dist/surfaces/cli/commands/confirm.mjs.map +1 -1
- package/dist/surfaces/cli/commands/doctor.mjs +1 -1
- package/dist/surfaces/cli/commands/doctor.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune.mjs +1 -1
- package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
- package/dist/surfaces/cli/commands/snapshot.mjs +1 -1
- package/dist/surfaces/cli/commands/snapshot.mjs.map +1 -1
- package/dist/surfaces/cli/commands/status.mjs +1 -1
- package/dist/surfaces/cli/commands/status.mjs.map +1 -1
- package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
- package/dist/surfaces/cli/commands/wipe.mjs +3 -4
- package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
- package/dist/surfaces/cli/envelope.mjs.map +1 -1
- package/dist/surfaces/cli/errors.mjs.map +1 -1
- package/dist/surfaces/cli/flags.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs +45 -14
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/dist/surfaces/cli/output.mjs.map +1 -1
- package/dist/surfaces/cli/sysexits.mjs.map +1 -1
- package/dist/surfaces/tui/app.mjs +0 -21
- package/dist/surfaces/tui/app.mjs.map +1 -1
- package/dist/surfaces/tui/dashboard.mjs +0 -48
- package/dist/surfaces/tui/dashboard.mjs.map +1 -1
- package/dist/surfaces/tui/display-derivation.mjs +80 -16
- package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
- package/dist/surfaces/tui/errors.mjs.map +1 -1
- package/dist/surfaces/tui/event-log.mjs +0 -9
- package/dist/surfaces/tui/event-log.mjs.map +1 -1
- package/dist/surfaces/tui/heartbeat.mjs.map +1 -1
- package/dist/surfaces/tui/index.mjs +2 -2
- package/dist/surfaces/tui/index.mjs.map +1 -1
- package/dist/surfaces/tui/input.mjs.map +1 -1
- package/dist/surfaces/tui/mode-detect.mjs.map +1 -1
- package/dist/surfaces/tui/mount-ink.mjs.map +1 -1
- package/dist/surfaces/tui/plain-renderer.mjs +26 -19
- package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
- package/dist/surfaces/tui/resource-table.mjs +8 -10
- package/dist/surfaces/tui/resource-table.mjs.map +1 -1
- package/images/postgres/Dockerfile +0 -1
- package/images/sui/Dockerfile +27 -54
- package/images/sui/entrypoint.sh +17 -179
- package/package.json +23 -15
- package/dashboard-ui/assets/index-Bmi1UtAg.js +0 -1356
- package/dashboard-ui/assets/index-D5EShVt4.js +0 -3
- package/dashboard-ui/assets/index-Deml9drg.css +0 -1
- package/dist/api/plugin-errors.d.mts +0 -7
- package/dist/api/plugin-errors.mjs +0 -10
- package/dist/api/plugin-errors.mjs.map +0 -1
- package/dist/cli/wirings/build-verb-layers.mjs +0 -42
- package/dist/cli/wirings/build-verb-layers.mjs.map +0 -1
- package/dist/contracts/capability-decl.d.mts +0 -40
- package/dist/orchestrators/built-in-plugin-layers.mjs +0 -54
- package/dist/orchestrators/built-in-plugin-layers.mjs.map +0 -1
- package/dist/orchestrators/run.mjs +0 -91
- package/dist/orchestrators/run.mjs.map +0 -1
- package/dist/orchestrators/runtime-composition.d.mts +0 -10
- package/dist/orchestrators/runtime-composition.mjs +0 -252
- package/dist/orchestrators/runtime-composition.mjs.map +0 -1
- package/dist/orchestrators/snapshot/capture-command.d.mts +0 -1
- package/dist/orchestrators/snapshot/capture-command.mjs +0 -25
- package/dist/orchestrators/snapshot/capture-command.mjs.map +0 -1
- package/dist/orchestrators/snapshot/pending-marker.mjs +0 -99
- package/dist/orchestrators/snapshot/pending-marker.mjs.map +0 -1
- package/dist/orchestrators/snapshot/recover-pending.mjs +0 -223
- package/dist/orchestrators/snapshot/recover-pending.mjs.map +0 -1
- package/dist/orchestrators/snapshot/state-document.d.mts +0 -1
- package/dist/orchestrators/snapshot/state-document.mjs +0 -58
- package/dist/orchestrators/snapshot/state-document.mjs.map +0 -1
- package/dist/plugins/account/spans.mjs +0 -16
- package/dist/plugins/account/spans.mjs.map +0 -1
- package/dist/plugins/account/variants/env.mjs +0 -24
- package/dist/plugins/account/variants/env.mjs.map +0 -1
- package/dist/plugins/account/variants/inline.mjs +0 -13
- package/dist/plugins/account/variants/inline.mjs.map +0 -1
- package/dist/plugins/account/variants/keystore.mjs +0 -91
- package/dist/plugins/account/variants/keystore.mjs.map +0 -1
- package/dist/plugins/action/spans.mjs +0 -11
- package/dist/plugins/action/spans.mjs.map +0 -1
- package/dist/plugins/coin/spans.mjs +0 -20
- package/dist/plugins/coin/spans.mjs.map +0 -1
- package/dist/plugins/deepbook/spans.mjs +0 -18
- package/dist/plugins/deepbook/spans.mjs.map +0 -1
- package/dist/plugins/faucet/spans.mjs +0 -12
- package/dist/plugins/faucet/spans.mjs.map +0 -1
- package/dist/plugins/package/spans.mjs +0 -14
- package/dist/plugins/package/spans.mjs.map +0 -1
- package/dist/plugins/postgres/codegen.mjs +0 -42
- package/dist/plugins/postgres/codegen.mjs.map +0 -1
- package/dist/plugins/postgres/connection.d.mts +0 -33
- package/dist/plugins/postgres/connection.mjs.map +0 -1
- package/dist/plugins/postgres/db-ensure.mjs.map +0 -1
- package/dist/plugins/postgres/errors.d.mts +0 -60
- package/dist/plugins/postgres/errors.mjs +0 -29
- package/dist/plugins/postgres/errors.mjs.map +0 -1
- package/dist/plugins/postgres/index.d.mts +0 -37
- package/dist/plugins/postgres/index.mjs +0 -68
- package/dist/plugins/postgres/index.mjs.map +0 -1
- package/dist/plugins/postgres/routable.mjs +0 -33
- package/dist/plugins/postgres/routable.mjs.map +0 -1
- package/dist/plugins/postgres/service.d.mts +0 -50
- package/dist/plugins/postgres/service.mjs +0 -179
- package/dist/plugins/postgres/service.mjs.map +0 -1
- package/dist/plugins/postgres/snapshot.mjs +0 -32
- package/dist/plugins/postgres/snapshot.mjs.map +0 -1
- package/dist/plugins/postgres/spans.mjs +0 -11
- package/dist/plugins/postgres/spans.mjs.map +0 -1
- package/dist/plugins/seal/spans.mjs +0 -18
- package/dist/plugins/seal/spans.mjs.map +0 -1
- package/dist/plugins/sui/spans.mjs +0 -17
- package/dist/plugins/sui/spans.mjs.map +0 -1
- package/dist/plugins/wallet/spans.mjs +0 -22
- package/dist/plugins/wallet/spans.mjs.map +0 -1
- package/dist/plugins/walrus/registry-publish.d.mts +0 -24
- package/dist/plugins/walrus/spans.mjs +0 -18
- package/dist/plugins/walrus/spans.mjs.map +0 -1
- package/dist/runtime/docker/logs.d.mts +0 -1
- package/dist/runtime/docker/logs.mjs +0 -34
- package/dist/runtime/docker/logs.mjs.map +0 -1
- package/dist/substrate/runtime/artifact-publisher/index.mjs +0 -86
- package/dist/substrate/runtime/artifact-publisher/index.mjs.map +0 -1
- package/dist/substrate/runtime/capability-sinks/index.d.mts +0 -1
- package/dist/substrate/runtime/capability-sinks/index.mjs +0 -3
- package/dist/substrate/runtime/capability-sinks/layer.d.mts +0 -1
- package/dist/substrate/runtime/capability-sinks/layer.mjs +0 -31
- package/dist/substrate/runtime/capability-sinks/layer.mjs.map +0 -1
- package/dist/substrate/runtime/capability-sinks/service.d.mts +0 -95
- package/dist/substrate/runtime/capability-sinks/service.mjs +0 -69
- package/dist/substrate/runtime/capability-sinks/service.mjs.map +0 -1
- package/dist/substrate/runtime/cross-process/lock.d.mts +0 -1
- package/dist/substrate/runtime/cross-process/lock.mjs +0 -23
- package/dist/substrate/runtime/cross-process/lock.mjs.map +0 -1
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +0 -113
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +0 -1
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs +0 -31
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +0 -1
- package/dist/substrate/runtime/observability/formatter-registry.d.mts +0 -1
- package/dist/substrate/runtime/observability/formatter-registry.mjs +0 -48
- package/dist/substrate/runtime/observability/formatter-registry.mjs.map +0 -1
- package/dist/substrate/runtime/observability/span-store.d.mts +0 -1
- package/dist/substrate/runtime/observability/span-store.mjs +0 -110
- package/dist/substrate/runtime/observability/span-store.mjs.map +0 -1
- package/dist/substrate/runtime/observability/spans.d.mts +0 -1
- package/dist/substrate/runtime/observability/spans.mjs +0 -87
- package/dist/substrate/runtime/observability/spans.mjs.map +0 -1
- package/dist/substrate/runtime/projection/index.mjs +0 -4
- package/dist/substrate/runtime/projection/persisted.mjs +0 -213
- package/dist/substrate/runtime/projection/persisted.mjs.map +0 -1
- package/dist/substrate/runtime/scoped-multimap/service.mjs +0 -52
- package/dist/substrate/runtime/scoped-multimap/service.mjs.map +0 -1
- package/dist/substrate/runtime/scoped-ref-map/index.mjs +0 -2
- package/dist/substrate/runtime/scoped-ref-map/service.mjs +0 -83
- package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +0 -1
- package/dist/substrate/runtime/state-store/index.mjs +0 -3
- package/dist/substrate/runtime/state-store/schema.d.mts +0 -1
- package/dist/substrate/runtime/state-store/schema.mjs +0 -41
- package/dist/substrate/runtime/state-store/schema.mjs.map +0 -1
- package/dist/substrate/runtime/state-store/service.d.mts +0 -1
- package/dist/substrate/runtime/state-store/service.mjs +0 -145
- package/dist/substrate/runtime/state-store/service.mjs.map +0 -1
- package/dist/substrate/runtime/sui-execute/index.mjs.map +0 -1
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +0 -1
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +0 -1
- package/dist/substrate/runtime/sui-move-build/index.mjs.map +0 -1
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +0 -151
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +0 -1
- package/dist/substrate/state-store.d.mts +0 -1
- /package/dist/orchestrators/{snapshot/pending-marker.d.mts → codegen/id-config.d.mts} +0 -0
- /package/dist/orchestrators/snapshot/{recover-pending.d.mts → interrupted-restore.d.mts} +0 -0
- /package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.d.mts +0 -0
- /package/dist/plugins/{postgres/snapshot.d.mts → sui/ledger/object-ref.d.mts} +0 -0
- /package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.d.mts +0 -0
- /package/dist/substrate/runtime/{state-store/index.d.mts → reconcile/graph.d.mts} +0 -0
- /package/dist/substrate/runtime/{cross-process/snapshot-reservation.d.mts → supervisor/contribution-dispatcher.d.mts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.mjs","names":[],"sources":["../../../src/runtime/docker/image.ts"],"sourcesContent":["// Docker image lifecycle.\n//\n// Architecture § Docker backend § Images:\n// - Bring-your-own-image vs build is a CLOSED SUM (architecture §8):\n// either the caller supplies an image ref (`docker pull`) or a\n// `ContainerBuildContext` (`docker build`); no third option.\n// - Content-addressed build cache: the substrate's L0 cache holds\n// the `(namespace, chainId, contentHash) → imageDigest` mapping.\n// When the cache hits we short-circuit the build. The `docker\n// build` itself is then a no-op when the resulting tag already\n// exists on the host (defense-in-depth — cache miss but on-host\n// tag-exists still skips the build).\n// - Pull progress narration is wired through `onStdoutLine` /\n// `onStderrLine` per-line callbacks on the L0 capture; the L1\n// observability sink promotes WARN/ERROR markers.\n//\n// Surface:\n// - `imageExists(ref)` — `docker inspect` short-circuit\n// - `pull(ref)` — `docker pull`; surfaces ImageNotFound /\n// ImagePullFailed via wrap.ts\n// - `build(ctx, tag)` — `docker build -t <tag> <ctx>`; surfaces\n// BuildFailed via wrap.ts; integrates with\n// the L0 cache\n// - `tag(src, dst)` — `docker tag`\n// - `inspectDigest` — read the on-host digest of a tag\n\nimport { Effect, Fiber, Schema, Stream } from 'effect';\n\nimport type { ChainId, ContentHash } from '../../substrate/brand.ts';\nimport { CacheService } from '../../substrate/runtime/cache/index.ts';\nimport { decodeJsonTextSync } from '../../substrate/runtime/runtime-decode.ts';\nimport type { ImageRef, LoadedImageBundle } from '../../contracts/container-runtime.ts';\nimport { DockerHost, DockerSpawner, dockerCommand, dockerRun, dockerRunOk } from './client.ts';\nimport type { DockerRuntimeError } from './errors.ts';\nimport {\n\tImageLoadFailed,\n\tImageNotFound,\n\tImageRemoveFailed,\n\tImageSaveFailed,\n\tImageTagFailed,\n} from './errors.ts';\nimport {\n\tisImageNotFoundStderr,\n\tisMissingImageStderr,\n\twrapBuildError,\n\twrapGeneric,\n\twrapPullError,\n} from './wrap.ts';\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\nconst SNAPSHOT_TEMP_TAG_PREFIX = 'devstack-snapshot:';\n\nconst isSnapshotTempTag = (ref: string): boolean => ref.startsWith(SNAPSHOT_TEMP_TAG_PREFIX);\n\n/** Return the trailing `max` chars of `text`, prefixed with an ellipsis\n * when truncation occurred. Used to surface the actual stdout shape in\n * parse-failure errors without ballooning the error message. */\nconst tailBytes = (text: string, max: number): string => {\n\tif (text.length <= max) return text;\n\treturn `…${text.slice(-max)}`;\n};\n\nconst cleanupSnapshotTempTag = (\n\tref: string,\n): Effect.Effect<void, never, DockerHost | DockerSpawner> => {\n\tif (!isSnapshotTempTag(ref)) return Effect.void;\n\treturn dockerRunOk('image', ['rm', ref]).pipe(\n\t\tEffect.tapCause((cause) =>\n\t\t\tEffect.logDebug('docker image rm (snapshot temp tag) failed', { ref, cause }),\n\t\t),\n\t\tEffect.catch(() => Effect.void),\n\t\tEffect.asVoid,\n\t);\n};\n\nexport interface SaveImageOptions {\n\treadonly removeAfterSave?: boolean;\n}\n\nexport interface TagImageOptions {\n\treadonly removeSourceAfterTag?: boolean;\n}\n\n/** Encode/decode the cache entry — we store the resolved image digest\n * as plain UTF-8 bytes so the cache primitive's byte shape works. */\nconst encodeDigest = (digest: string): Uint8Array =>\n\tnew TextEncoder().encode(JSON.stringify({ digest } satisfies { digest: string }));\n\nconst DigestDoc = Schema.Struct({ digest: Schema.String });\n\nconst decodeDigest = (bytes: Uint8Array): string | null => {\n\ttry {\n\t\treturn decodeJsonTextSync(DigestDoc, new TextDecoder().decode(bytes), {\n\t\t\tsource: 'docker image digest cache',\n\t\t\tmkError: (issue) => issue,\n\t\t}).digest;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Exists / inspect\n// -----------------------------------------------------------------------------\n\n/** `docker image inspect <ref>` — returns the digest, or null on miss. */\nexport const imageExists = (\n\tref: string,\n): Effect.Effect<string | null, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('image', ['inspect', '--format', '{{.Id}}', ref]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.image.inspect')),\n\t\t);\n\t\tif (res.exitCode !== 0) return null;\n\t\tconst id = res.stdout.trim();\n\t\treturn id.length > 0 ? id : null;\n\t}).pipe(Effect.withSpan('runtime.docker.image.exists'));\n\n/** Strict variant — surfaces `ImageNotFound` if the inspect misses. */\nexport const inspectDigest = (\n\tref: string,\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst id = yield* imageExists(ref);\n\t\tif (id === null) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew ImageNotFound({ ref, detail: 'docker image inspect returned empty' }),\n\t\t\t);\n\t\t}\n\t\treturn id;\n\t}).pipe(Effect.withSpan('runtime.docker.image.inspectDigest'));\n\n// -----------------------------------------------------------------------------\n// Pull\n// -----------------------------------------------------------------------------\n\n/** `docker pull <ref>` — surfaces ImageNotFound / ImagePullFailed.\n * Per-line progress emission opt-in via `onLine`. */\nexport const pull = (\n\tref: string,\n\tonLine?: (line: string) => Effect.Effect<void>,\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tyield* dockerRun('pull', [ref], { onStdoutLine: onLine }).pipe(\n\t\t\tEffect.mapError(wrapPullError(ref)),\n\t\t);\n\t\treturn yield* inspectDigest(ref);\n\t}).pipe(Effect.withSpan('runtime.docker.image.pull'));\n\n// -----------------------------------------------------------------------------\n// Build (content-addressed)\n// -----------------------------------------------------------------------------\n\nexport interface BuildOptions {\n\treadonly contextPath: string;\n\treadonly dockerfile?: string;\n\treadonly platform?: string;\n\treadonly buildArgs?: Readonly<Record<string, string>>;\n\treadonly tag: string;\n\t/** Image labels rendered as `--label key=value` flags. Stamped onto\n\t * the resulting image so label-driven prune can find it. */\n\treadonly labels?: Readonly<Record<string, string>>;\n\treadonly onLine?: (line: string) => Effect.Effect<void>;\n}\n\n/** `docker build -t <tag> [-f Dockerfile] <ctx>`. On exit-0 the\n * resulting tag is on-host; we read its digest and return.\n *\n * NOTE: the content-addressed cache integration is in `ensureImageCached`\n * below — this verb is the unconditional build, used when the cache\n * has missed AND the on-host tag-exists probe also missed. */\nexport const build = (\n\topts: BuildOptions,\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst args: Array<string> = ['-t', opts.tag];\n\t\tif (opts.platform !== undefined) {\n\t\t\targs.push('--platform', opts.platform);\n\t\t}\n\t\t// `-f` is resolved relative to the docker CLI's CWD, NOT the\n\t\t// context directory. Skip `-f` entirely when the dockerfile is\n\t\t// the canonical default name (`Dockerfile`) and the context is a\n\t\t// directory — docker buildx then auto-discovers `<ctx>/Dockerfile`.\n\t\t// When the user names a non-default dockerfile, resolve it against\n\t\t// the context so `docker build -f a/b/Dockerfile.x .` works\n\t\t// regardless of CWD.\n\t\tif (opts.dockerfile && opts.dockerfile !== 'Dockerfile') {\n\t\t\tconst isAbs = opts.dockerfile.startsWith('/') || /^[A-Za-z]:/.test(opts.dockerfile);\n\t\t\tconst resolved = isAbs\n\t\t\t\t? opts.dockerfile\n\t\t\t\t: `${opts.contextPath.replace(/\\/$/, '')}/${opts.dockerfile}`;\n\t\t\targs.push('-f', resolved);\n\t\t}\n\t\tif (opts.buildArgs) {\n\t\t\tfor (const [k, v] of Object.entries(opts.buildArgs)) {\n\t\t\t\targs.push('--build-arg', `${k}=${v}`);\n\t\t\t}\n\t\t}\n\t\tif (opts.labels) {\n\t\t\tfor (const [k, v] of Object.entries(opts.labels)) {\n\t\t\t\targs.push('--label', `${k}=${v}`);\n\t\t\t}\n\t\t}\n\t\targs.push(opts.contextPath);\n\t\tyield* dockerRun('build', args, {\n\t\t\tonStdoutLine: opts.onLine,\n\t\t\tonStderrLine: opts.onLine,\n\t\t}).pipe(Effect.mapError(wrapBuildError(opts.contextPath, opts.dockerfile)));\n\t\treturn yield* inspectDigest(opts.tag);\n\t}).pipe(Effect.withSpan('runtime.docker.image.build'));\n\n// -----------------------------------------------------------------------------\n// Tag\n// -----------------------------------------------------------------------------\n\n/** `docker tag <src> <dst>` — used by snapshot restore to alias a\n * loaded image back to its original tag. */\nexport const tagImage = (\n\tsrc: string,\n\tdst: string,\n\topts: TagImageOptions = {},\n): Effect.Effect<void, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('tag', [src, dst]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.tag')),\n\t\t);\n\t\tif (res.exitCode !== 0) {\n\t\t\tif (isImageNotFoundStderr(res.stderr)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew ImageNotFound({ ref: src, detail: `tag source missing: ${res.stderr}` }),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn yield* Effect.fail(new ImageTagFailed({ src, dst, stderr: res.stderr }));\n\t\t}\n\t\tif (opts.removeSourceAfterTag === true && src !== dst) {\n\t\t\tyield* cleanupSnapshotTempTag(src);\n\t\t}\n\t}).pipe(Effect.withSpan('runtime.docker.image.tag'));\n\nexport const removeImage = (\n\tref: string,\n): Effect.Effect<void, 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 || isMissingImageStderr(res.stderr)) return;\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}).pipe(Effect.withSpan('runtime.docker.image.remove'));\n\n// -----------------------------------------------------------------------------\n// Save (image → tar stream)\n// -----------------------------------------------------------------------------\n\n/** Stream the bytes of `docker save <ref>` to the consumer. Stdout\n * stays streaming so large images never materialise in memory; stderr\n * and exit are drained concurrently so a missing image cannot look\n * like a successful empty tar. */\nexport const saveImages = (\n\trefs: ReadonlyArray<string>,\n\topts: SaveImageOptions = {},\n): Stream.Stream<Uint8Array, DockerRuntimeError, DockerHost | DockerSpawner> => {\n\tconst mapSpawnError = (cause: unknown): DockerRuntimeError =>\n\t\tnew ImageSaveFailed({\n\t\t\tref: refs.join(' '),\n\t\t\tdetail:\n\t\t\t\trefs.length === 0 ? 'docker save called with no image refs' : 'docker save spawn failed',\n\t\t\tcause,\n\t\t});\n\tif (refs.length === 0) {\n\t\treturn Stream.fail(mapSpawnError(new Error('empty docker save ref list')));\n\t}\n\treturn Stream.unwrap(\n\t\tEffect.gen(function* () {\n\t\t\tconst host = yield* DockerHost;\n\t\t\tconst spawner = yield* DockerSpawner;\n\t\t\tconst cmd = dockerCommand(host, 'save', refs);\n\t\t\tconst handle = yield* spawner.spawn(cmd).pipe(Effect.mapError(mapSpawnError));\n\t\t\t// `Stream.unwrap` IS scope-binding in v4 (STYLE_GUIDE §1): the\n\t\t\t// surrounding Effect.gen runs under the consuming stream's\n\t\t\t// scope, so `forkScoped` ties these helper fibers to that\n\t\t\t// scope. With `forkChild` (unscoped) a caller that errors\n\t\t\t// before draining the returned stream would leak the\n\t\t\t// stderr/exit fibers — `Stream.ensuring(cleanup)` only runs\n\t\t\t// once the stream is actually consumed.\n\t\t\tconst stderrFiber = yield* Effect.forkScoped(\n\t\t\t\tStream.mkString(Stream.decodeText(handle.stderr)).pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageSaveFailed({\n\t\t\t\t\t\t\t\tref: refs.join(' '),\n\t\t\t\t\t\t\t\tdetail: 'docker save stderr drain failed',\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst exitFiber = yield* Effect.forkScoped(\n\t\t\t\thandle.exitCode.pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageSaveFailed({\n\t\t\t\t\t\t\t\tref: refs.join(' '),\n\t\t\t\t\t\t\t\tdetail: 'docker save exit failed',\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst checkExit = Effect.gen(function* () {\n\t\t\t\tconst [stderrText, exitCode] = yield* Effect.all(\n\t\t\t\t\t[Fiber.join(stderrFiber), Fiber.join(exitFiber)] as const,\n\t\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t\t);\n\t\t\t\tif (exitCode !== 0) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew ImageSaveFailed({\n\t\t\t\t\t\t\tref: refs.join(' '),\n\t\t\t\t\t\t\tdetail: `docker save exited ${exitCode}${\n\t\t\t\t\t\t\t\tstderrText.length > 0 ? `: ${stderrText}` : ''\n\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\t\t\tconst cleanupTag =\n\t\t\t\topts.removeAfterSave === true\n\t\t\t\t\t? Effect.forEach(refs, cleanupSnapshotTempTag, { concurrency: 'unbounded' }).pipe(\n\t\t\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\t\t)\n\t\t\t\t\t: Effect.void;\n\t\t\tconst cleanup = Effect.all(\n\t\t\t\t[cleanupTag, Fiber.interrupt(stderrFiber), Fiber.interrupt(exitFiber)],\n\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t).pipe(Effect.asVoid);\n\t\t\treturn handle.stdout.pipe(\n\t\t\t\tStream.mapError(\n\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\tnew ImageSaveFailed({\n\t\t\t\t\t\t\tref: refs.join(' '),\n\t\t\t\t\t\t\tdetail: 'docker save stdout pipe failed',\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\tStream.onEnd(checkExit),\n\t\t\t\tStream.ensuring(cleanup),\n\t\t\t);\n\t\t}),\n\t);\n};\n\nexport const saveImage = (\n\tref: string,\n\topts: SaveImageOptions = {},\n): Stream.Stream<Uint8Array, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tsaveImages([ref], opts);\n\n// -----------------------------------------------------------------------------\n// Load (tar stream → image)\n// -----------------------------------------------------------------------------\n\n/** Parse `docker load` stdout lines to extract the loaded image refs.\n * Docker's output is one of:\n * - `Loaded image: <tag>`\n * - `Loaded image ID: sha256:<digest>`\n * Exported for direct testing without a docker dependency. */\nexport const parseLoadedRefs = (\n\tstdout: string,\n): ReadonlyArray<{ readonly tag?: string; readonly digest?: string }> => {\n\tconst refs: Array<{ tag?: string; digest?: string }> = [];\n\tfor (const rawLine of stdout.split(/\\r?\\n/)) {\n\t\tconst line = rawLine.trim();\n\t\tconst taggedMatch = /^Loaded image: (\\S+)$/.exec(line);\n\t\tif (taggedMatch?.[1]) {\n\t\t\trefs.push({ tag: taggedMatch[1] });\n\t\t\tcontinue;\n\t\t}\n\t\tconst digestMatch = /^Loaded image ID: (sha256:\\S+)$/.exec(line);\n\t\tif (digestMatch?.[1]) {\n\t\t\trefs.push({ digest: digestMatch[1] });\n\t\t}\n\t}\n\treturn refs;\n};\n\n/** `docker load < <tar-stream>`. Returns every freshly-loaded ref\n * Docker reported on stdout. Symmetric with `saveImage`/`saveImages`.\n * Upstream stream errors (e.g. file-read failures from a snapshot\n * tar) are projected to `ImageLoadFailed` so callers see one shape.\n *\n * Stdin lifetime: the supplied stream is fed in via the spawner's\n * `stdin` sink under the same scope as the spawn itself; both close\n * when the child exits. */\nexport const loadImage = (\n\ttar: Stream.Stream<Uint8Array, unknown>,\n): Effect.Effect<LoadedImageBundle, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst host = yield* DockerHost;\n\t\tconst spawner = yield* DockerSpawner;\n\t\tconst cmd = dockerCommand(host, 'load', []);\n\t\tconst mapSpawnError = (cause: unknown): DockerRuntimeError =>\n\t\t\tnew ImageLoadFailed({ detail: 'docker load spawn failed', cause });\n\t\treturn yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst handle = yield* spawner.spawn(cmd).pipe(Effect.mapError(mapSpawnError));\n\t\t\t\t// Concurrently:\n\t\t\t\t// - pump the supplied tar into stdin\n\t\t\t\t// - drain stdout (the \"Loaded image: …\" lines)\n\t\t\t\t// - drain stderr\n\t\t\t\t// - wait for exit\n\t\t\t\tconst writeStdin = Stream.run(tar, handle.stdin).pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause: unknown): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageLoadFailed({ detail: 'docker load stdin pipe failed', cause }),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst stdoutDrain = Stream.mkString(Stream.decodeText(handle.stdout)).pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageLoadFailed({ detail: 'docker load stdout drain failed', cause }),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst stderrDrain = Stream.mkString(Stream.decodeText(handle.stderr)).pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageLoadFailed({ detail: 'docker load stderr drain failed', cause }),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst exit = handle.exitCode.pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageLoadFailed({ detail: 'docker load exit failed', cause }),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst [, stdoutText, stderrText, exitCode] = yield* Effect.all(\n\t\t\t\t\t[writeStdin, stdoutDrain, stderrDrain, exit] as const,\n\t\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t\t);\n\t\t\t\tif (exitCode !== 0) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew ImageLoadFailed({\n\t\t\t\t\t\t\tdetail: `docker load exited ${exitCode}`,\n\t\t\t\t\t\t\tstderr: stderrText,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst parsed = parseLoadedRefs(stdoutText);\n\t\t\t\tif (parsed.length === 0) {\n\t\t\t\t\t// Include the trailing ~500 bytes of stdout so newer\n\t\t\t\t\t// Docker variants that interleave progress markers\n\t\t\t\t\t// (e.g. JSON status lines) surface the actual output\n\t\t\t\t\t// shape instead of an opaque \"no Loaded image lines\n\t\t\t\t\t// found\". The full stderr text is still carried for\n\t\t\t\t\t// the rare case docker emitted a diagnostic there.\n\t\t\t\t\tconst stdoutTail = tailBytes(stdoutText, 500);\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew ImageLoadFailed({\n\t\t\t\t\t\t\tdetail: `docker load succeeded but no Loaded image lines found; stdout tail: ${stdoutTail}`,\n\t\t\t\t\t\t\tstderr: stderrText,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst refs = yield* Effect.forEach(\n\t\t\t\t\tparsed,\n\t\t\t\t\t(ref) =>\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tif (ref.digest !== undefined) {\n\t\t\t\t\t\t\t\treturn refOf(ref.digest, ref.tag);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst digest = yield* imageExists(ref.tag!);\n\t\t\t\t\t\t\tif (digest === null) {\n\t\t\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\t\t\tnew ImageLoadFailed({\n\t\t\t\t\t\t\t\t\t\tdetail: `loaded image '${ref.tag}' not visible to inspect`,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn refOf(digest, ref.tag);\n\t\t\t\t\t\t}),\n\t\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t\t);\n\t\t\t\treturn { refs };\n\t\t\t}),\n\t\t);\n\t}).pipe(Effect.withSpan('runtime.docker.image.load'));\n\n// -----------------------------------------------------------------------------\n// Cache integration\n// -----------------------------------------------------------------------------\n\nexport interface CachedBuildKey {\n\treadonly namespace: string;\n\treadonly chain: ChainId;\n\treadonly contentHash: ContentHash;\n}\n\n/** Content-addressed image build:\n *\n * 1. cache lookup keyed by (namespace, chainId, contentHash)\n * 2. on hit, verify the on-host tag still resolves; if so return\n * 3. on miss, run `docker build`, then write the resolved digest\n * to the cache (best-effort — write failure does not roll back\n * the on-host build).\n *\n * Caller-supplied tag is the content-addressed name; the cache key\n * is independent so an unrelated invalidation (e.g. base-image\n * bump) can drop the cache without disturbing the tag scheme. */\nexport const ensureImageCached = (\n\topts: BuildOptions,\n\tkey: CachedBuildKey,\n): Effect.Effect<string, DockerRuntimeError, CacheService | DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst cache = yield* CacheService;\n\t\tconst hit = yield* cache\n\t\t\t.lookup({ namespace: key.namespace, chain: key.chain, contentHash: key.contentHash })\n\t\t\t.pipe(\n\t\t\t\t// Cache-side failures collapse to MISS — the contract is\n\t\t\t\t// best-effort. We still build, but logDebug so the rare\n\t\t\t\t// cache-read failure is visible.\n\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\tEffect.logDebug('image cache lookup failed; rebuilding', { key, cause }),\n\t\t\t\t),\n\t\t\t\tEffect.catch(() => Effect.succeed(null)),\n\t\t\t);\n\t\tif (hit !== null) {\n\t\t\tconst cached = decodeDigest(hit.bytes);\n\t\t\tif (cached !== null) {\n\t\t\t\t// Verify the on-host tag still resolves to something —\n\t\t\t\t// the cached digest may have been pruned out from under us.\n\t\t\t\tconst existing = yield* imageExists(opts.tag);\n\t\t\t\tif (existing !== null) {\n\t\t\t\t\treturn existing;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// On-host tag-exists short-circuit (defence-in-depth).\n\t\tconst onHost = yield* imageExists(opts.tag);\n\t\tif (onHost !== null) {\n\t\t\tyield* cache\n\t\t\t\t.write(\n\t\t\t\t\t{ namespace: key.namespace, chain: key.chain, contentHash: key.contentHash },\n\t\t\t\t\tencodeDigest(onHost),\n\t\t\t\t)\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\t\tEffect.logDebug('image cache write (on-host hit) failed', { key, cause }),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.catch(() => Effect.void),\n\t\t\t\t);\n\t\t\treturn onHost;\n\t\t}\n\t\tconst digest = yield* build(opts);\n\t\tyield* cache\n\t\t\t.write(\n\t\t\t\t{ namespace: key.namespace, chain: key.chain, contentHash: key.contentHash },\n\t\t\t\tencodeDigest(digest),\n\t\t\t)\n\t\t\t.pipe(\n\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\tEffect.logDebug('image cache write (post-build) failed', { key, cause }),\n\t\t\t\t),\n\t\t\t\tEffect.catch(() => Effect.void),\n\t\t\t);\n\t\treturn digest;\n\t}).pipe(Effect.withSpan('runtime.docker.image.ensureCached'));\n\n// -----------------------------------------------------------------------------\n// Contract-shaped ImageRef helpers\n// -----------------------------------------------------------------------------\n\nexport const refOf = (digest: string, tag?: string): ImageRef =>\n\ttag !== undefined ? { digest, tag } : { digest };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"image.mjs","names":[],"sources":["../../../src/runtime/docker/image.ts"],"sourcesContent":["// Docker image lifecycle.\n//\n// Architecture § Docker backend § Images:\n// - Bring-your-own-image vs build is a CLOSED SUM (architecture §8):\n// either the caller supplies an image ref (`docker pull`) or a\n// `ContainerBuildContext` (`docker build`); no third option.\n// - Content-addressed build cache: the substrate's L0 cache holds\n// the `(namespace, chainId, contentHash) → imageDigest` mapping.\n// When the cache hits we short-circuit the build. The `docker\n// build` itself is then a no-op when the resulting tag already\n// exists on the host (defense-in-depth — cache miss but on-host\n// tag-exists still skips the build).\n// - Pull progress narration is wired through `onStdoutLine` /\n// `onStderrLine` per-line callbacks on the L0 capture; the L1\n// observability sink promotes WARN/ERROR markers.\n//\n// Surface:\n// - `imageExists(ref)` — `docker inspect` short-circuit\n// - `pull(ref)` — `docker pull`; surfaces ImageNotFound /\n// ImagePullFailed via wrap.ts\n// - `build(ctx, tag)` — `docker build -t <tag> <ctx>`; surfaces\n// BuildFailed via wrap.ts; integrates with\n// the L0 cache\n// - `tag(src, dst)` — `docker tag`\n// - `inspectDigest` — read the on-host digest of a tag\n\nimport { Effect, Fiber, Schema, Stream } from 'effect';\n\nimport type { ContentHash } from '../../substrate/brand.ts';\nimport { CacheService } from '../../substrate/runtime/cache/index.ts';\nimport { decodeJsonTextSync } from '../../substrate/runtime/runtime-decode.ts';\nimport type { ImageRef, LoadedImageBundle } from '../../contracts/container-runtime.ts';\nimport { DockerHost, DockerSpawner, dockerCommand, dockerRun, dockerRunOk } from './client.ts';\nimport type { DockerRuntimeError } from './errors.ts';\nimport {\n\tImageLoadFailed,\n\tImageNotFound,\n\tImageRemoveFailed,\n\tImageSaveFailed,\n\tImageTagFailed,\n} from './errors.ts';\nimport {\n\tisImageNotFoundStderr,\n\tisMissingImageStderr,\n\twrapBuildError,\n\twrapGeneric,\n\twrapPullError,\n} from './wrap.ts';\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\nconst SNAPSHOT_TEMP_TAG_PREFIX = 'devstack-snapshot:';\n\nconst isSnapshotTempTag = (ref: string): boolean => ref.startsWith(SNAPSHOT_TEMP_TAG_PREFIX);\n\n/** Return the trailing `max` chars of `text`, prefixed with an ellipsis\n * when truncation occurred. Used to surface the actual stdout shape in\n * parse-failure errors without ballooning the error message. */\nconst tailBytes = (text: string, max: number): string => {\n\tif (text.length <= max) return text;\n\treturn `…${text.slice(-max)}`;\n};\n\nconst cleanupSnapshotTempTag = (\n\tref: string,\n): Effect.Effect<void, never, DockerHost | DockerSpawner> => {\n\tif (!isSnapshotTempTag(ref)) return Effect.void;\n\treturn dockerRunOk('image', ['rm', ref]).pipe(\n\t\tEffect.tapCause((cause) =>\n\t\t\tEffect.logDebug('docker image rm (snapshot temp tag) failed', { ref, cause }),\n\t\t),\n\t\tEffect.catch(() => Effect.void),\n\t\tEffect.asVoid,\n\t);\n};\n\nexport interface SaveImageOptions {\n\treadonly removeAfterSave?: boolean;\n}\n\nexport interface TagImageOptions {\n\treadonly removeSourceAfterTag?: boolean;\n}\n\n/** Encode/decode the cache entry — we store the resolved image digest\n * as plain UTF-8 bytes so the cache primitive's byte shape works. */\nconst encodeDigest = (digest: string): Uint8Array =>\n\tnew TextEncoder().encode(JSON.stringify({ digest } satisfies { digest: string }));\n\nconst DigestDoc = Schema.Struct({ digest: Schema.String });\n\nconst decodeDigest = (bytes: Uint8Array): string | null => {\n\ttry {\n\t\treturn decodeJsonTextSync(DigestDoc, new TextDecoder().decode(bytes), {\n\t\t\tsource: 'docker image digest cache',\n\t\t\tmkError: (issue) => issue,\n\t\t}).digest;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n// -----------------------------------------------------------------------------\n// Exists / inspect\n// -----------------------------------------------------------------------------\n\n/** `docker image inspect <ref>` — returns the digest, or null on miss. */\nexport const imageExists = (\n\tref: string,\n): Effect.Effect<string | null, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('image', ['inspect', '--format', '{{.Id}}', ref]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.image.inspect')),\n\t\t);\n\t\tif (res.exitCode !== 0) return null;\n\t\tconst id = res.stdout.trim();\n\t\treturn id.length > 0 ? id : null;\n\t});\n\n/** Strict variant — surfaces `ImageNotFound` if the inspect misses. */\nexport const inspectDigest = (\n\tref: string,\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst id = yield* imageExists(ref);\n\t\tif (id === null) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew ImageNotFound({ ref, detail: 'docker image inspect returned empty' }),\n\t\t\t);\n\t\t}\n\t\treturn id;\n\t});\n\n// -----------------------------------------------------------------------------\n// Pull\n// -----------------------------------------------------------------------------\n\n/** `docker pull <ref>` — surfaces ImageNotFound / ImagePullFailed.\n * Per-line progress emission opt-in via `onLine`. */\nexport const pull = (\n\tref: string,\n\tonLine?: (line: string) => Effect.Effect<void>,\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tyield* dockerRun('pull', [ref], { onStdoutLine: onLine }).pipe(\n\t\t\tEffect.mapError(wrapPullError(ref)),\n\t\t);\n\t\tconst digest = yield* inspectDigest(ref);\n\t\treturn digest;\n\t});\n\n// -----------------------------------------------------------------------------\n// Build (content-addressed)\n// -----------------------------------------------------------------------------\n\nexport interface BuildOptions {\n\treadonly contextPath: string;\n\treadonly dockerfile?: string;\n\treadonly platform?: string;\n\treadonly buildArgs?: Readonly<Record<string, string>>;\n\treadonly tag: string;\n\t/** Image labels rendered as `--label key=value` flags. Stamped onto\n\t * the resulting image so label-driven prune can find it. */\n\treadonly labels?: Readonly<Record<string, string>>;\n\treadonly onLine?: (line: string) => Effect.Effect<void>;\n}\n\n/** `docker build -t <tag> [-f Dockerfile] <ctx>`. On exit-0 the\n * resulting tag is on-host; we read its digest and return.\n *\n * NOTE: the content-addressed cache integration is in `ensureImageCached`\n * below — this verb is the unconditional build, used when the cache\n * has missed AND the on-host tag-exists probe also missed. */\nexport const build = (\n\topts: BuildOptions,\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst args: Array<string> = ['-t', opts.tag];\n\t\tif (opts.platform !== undefined) {\n\t\t\targs.push('--platform', opts.platform);\n\t\t}\n\t\t// `-f` is resolved relative to the docker CLI's CWD, NOT the\n\t\t// context directory. Skip `-f` entirely when the dockerfile is\n\t\t// the canonical default name (`Dockerfile`) and the context is a\n\t\t// directory — docker buildx then auto-discovers `<ctx>/Dockerfile`.\n\t\t// When the user names a non-default dockerfile, resolve it against\n\t\t// the context so `docker build -f a/b/Dockerfile.x .` works\n\t\t// regardless of CWD.\n\t\tif (opts.dockerfile && opts.dockerfile !== 'Dockerfile') {\n\t\t\tconst isAbs = opts.dockerfile.startsWith('/') || /^[A-Za-z]:/.test(opts.dockerfile);\n\t\t\tconst resolved = isAbs\n\t\t\t\t? opts.dockerfile\n\t\t\t\t: `${opts.contextPath.replace(/\\/$/, '')}/${opts.dockerfile}`;\n\t\t\targs.push('-f', resolved);\n\t\t}\n\t\tif (opts.buildArgs) {\n\t\t\tfor (const [k, v] of Object.entries(opts.buildArgs)) {\n\t\t\t\targs.push('--build-arg', `${k}=${v}`);\n\t\t\t}\n\t\t}\n\t\tif (opts.labels) {\n\t\t\tfor (const [k, v] of Object.entries(opts.labels)) {\n\t\t\t\targs.push('--label', `${k}=${v}`);\n\t\t\t}\n\t\t}\n\t\targs.push(opts.contextPath);\n\t\tyield* dockerRun('build', args, {\n\t\t\tonStdoutLine: opts.onLine,\n\t\t\tonStderrLine: opts.onLine,\n\t\t}).pipe(Effect.mapError(wrapBuildError(opts.contextPath, opts.dockerfile)));\n\t\treturn yield* inspectDigest(opts.tag);\n\t});\n\n// -----------------------------------------------------------------------------\n// Tag\n// -----------------------------------------------------------------------------\n\n/** `docker tag <src> <dst>` — used by snapshot restore to alias a\n * loaded image back to its original tag. */\nexport const tagImage = (\n\tsrc: string,\n\tdst: string,\n\topts: TagImageOptions = {},\n): Effect.Effect<void, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('tag', [src, dst]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.tag')),\n\t\t);\n\t\tif (res.exitCode !== 0) {\n\t\t\tif (isImageNotFoundStderr(res.stderr)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew ImageNotFound({ ref: src, detail: `tag source missing: ${res.stderr}` }),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn yield* Effect.fail(new ImageTagFailed({ src, dst, stderr: res.stderr }));\n\t\t}\n\t\tif (opts.removeSourceAfterTag === true && src !== dst) {\n\t\t\tyield* cleanupSnapshotTempTag(src);\n\t\t}\n\t});\n\nexport const removeImage = (\n\tref: string,\n): Effect.Effect<void, 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 || isMissingImageStderr(res.stderr)) return;\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\n// -----------------------------------------------------------------------------\n// Save (image → tar stream)\n// -----------------------------------------------------------------------------\n\n/** Stream the bytes of `docker save <ref>` to the consumer. Stdout\n * stays streaming so large images never materialise in memory; stderr\n * and exit are drained concurrently so a missing image cannot look\n * like a successful empty tar. */\nexport const saveImages = (\n\trefs: ReadonlyArray<string>,\n\topts: SaveImageOptions = {},\n): Stream.Stream<Uint8Array, DockerRuntimeError, DockerHost | DockerSpawner> => {\n\tconst mapSpawnError = (cause: unknown): DockerRuntimeError =>\n\t\tnew ImageSaveFailed({\n\t\t\tref: refs.join(' '),\n\t\t\tdetail:\n\t\t\t\trefs.length === 0 ? 'docker save called with no image refs' : 'docker save spawn failed',\n\t\t\tcause,\n\t\t});\n\tif (refs.length === 0) {\n\t\treturn Stream.fail(mapSpawnError(new Error('empty docker save ref list')));\n\t}\n\treturn Stream.unwrap(\n\t\tEffect.gen(function* () {\n\t\t\tconst host = yield* DockerHost;\n\t\t\tconst spawner = yield* DockerSpawner;\n\t\t\tconst cmd = dockerCommand(host, 'save', refs);\n\t\t\tconst handle = yield* spawner.spawn(cmd).pipe(Effect.mapError(mapSpawnError));\n\t\t\t// `Stream.unwrap` IS scope-binding in v4 (STYLE_GUIDE §1): the\n\t\t\t// surrounding Effect.gen runs under the consuming stream's\n\t\t\t// scope, so `forkScoped` ties these helper fibers to that\n\t\t\t// scope. With `forkChild` (unscoped) a caller that errors\n\t\t\t// before draining the returned stream would leak the\n\t\t\t// stderr/exit fibers — `Stream.ensuring(cleanup)` only runs\n\t\t\t// once the stream is actually consumed.\n\t\t\tconst stderrFiber = yield* Effect.forkScoped(\n\t\t\t\tStream.mkString(Stream.decodeText(handle.stderr)).pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageSaveFailed({\n\t\t\t\t\t\t\t\tref: refs.join(' '),\n\t\t\t\t\t\t\t\tdetail: 'docker save stderr drain failed',\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst exitFiber = yield* Effect.forkScoped(\n\t\t\t\thandle.exitCode.pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageSaveFailed({\n\t\t\t\t\t\t\t\tref: refs.join(' '),\n\t\t\t\t\t\t\t\tdetail: 'docker save exit failed',\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst checkExit = Effect.gen(function* () {\n\t\t\t\tconst [stderrText, exitCode] = yield* Effect.all(\n\t\t\t\t\t[Fiber.join(stderrFiber), Fiber.join(exitFiber)] as const,\n\t\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t\t);\n\t\t\t\tif (exitCode !== 0) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew ImageSaveFailed({\n\t\t\t\t\t\t\tref: refs.join(' '),\n\t\t\t\t\t\t\tdetail: `docker save exited ${exitCode}${\n\t\t\t\t\t\t\t\tstderrText.length > 0 ? `: ${stderrText}` : ''\n\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\t\t\tconst cleanupTag =\n\t\t\t\topts.removeAfterSave === true\n\t\t\t\t\t? Effect.forEach(refs, cleanupSnapshotTempTag, { concurrency: 'unbounded' }).pipe(\n\t\t\t\t\t\t\tEffect.asVoid,\n\t\t\t\t\t\t)\n\t\t\t\t\t: Effect.void;\n\t\t\tconst cleanup = Effect.all(\n\t\t\t\t[cleanupTag, Fiber.interrupt(stderrFiber), Fiber.interrupt(exitFiber)],\n\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t).pipe(Effect.asVoid);\n\t\t\treturn handle.stdout.pipe(\n\t\t\t\tStream.mapError(\n\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\tnew ImageSaveFailed({\n\t\t\t\t\t\t\tref: refs.join(' '),\n\t\t\t\t\t\t\tdetail: 'docker save stdout pipe failed',\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\tStream.onEnd(checkExit),\n\t\t\t\tStream.ensuring(cleanup),\n\t\t\t);\n\t\t}),\n\t);\n};\n\n// -----------------------------------------------------------------------------\n// Load (tar stream → image)\n// -----------------------------------------------------------------------------\n\n/** Parse `docker load` stdout lines to extract the loaded image refs.\n * Docker's output is one of:\n * - `Loaded image: <tag>`\n * - `Loaded image ID: sha256:<digest>`\n * Exported for direct testing without a docker dependency. */\nexport const parseLoadedRefs = (\n\tstdout: string,\n): ReadonlyArray<{ readonly tag?: string; readonly digest?: string }> => {\n\tconst refs: Array<{ tag?: string; digest?: string }> = [];\n\tfor (const rawLine of stdout.split(/\\r?\\n/)) {\n\t\tconst line = rawLine.trim();\n\t\tconst taggedMatch = /^Loaded image: (\\S+)$/.exec(line);\n\t\tif (taggedMatch?.[1]) {\n\t\t\trefs.push({ tag: taggedMatch[1] });\n\t\t\tcontinue;\n\t\t}\n\t\tconst digestMatch = /^Loaded image ID: (sha256:\\S+)$/.exec(line);\n\t\tif (digestMatch?.[1]) {\n\t\t\trefs.push({ digest: digestMatch[1] });\n\t\t}\n\t}\n\treturn refs;\n};\n\n/** `docker load < <tar-stream>`. Returns every freshly-loaded ref\n * Docker reported on stdout. Symmetric with `saveImages`.\n * Upstream stream errors (e.g. file-read failures from a snapshot\n * tar) are projected to `ImageLoadFailed` so callers see one shape.\n *\n * Stdin lifetime: the supplied stream is fed in via the spawner's\n * `stdin` sink under the same scope as the spawn itself; both close\n * when the child exits. */\nexport const loadImage = (\n\ttar: Stream.Stream<Uint8Array, unknown>,\n): Effect.Effect<LoadedImageBundle, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst host = yield* DockerHost;\n\t\tconst spawner = yield* DockerSpawner;\n\t\tconst cmd = dockerCommand(host, 'load', []);\n\t\tconst mapSpawnError = (cause: unknown): DockerRuntimeError =>\n\t\t\tnew ImageLoadFailed({ detail: 'docker load spawn failed', cause });\n\t\treturn yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst handle = yield* spawner.spawn(cmd).pipe(Effect.mapError(mapSpawnError));\n\t\t\t\t// Concurrently:\n\t\t\t\t// - pump the supplied tar into stdin\n\t\t\t\t// - drain stdout (the \"Loaded image: …\" lines)\n\t\t\t\t// - drain stderr\n\t\t\t\t// - wait for exit\n\t\t\t\tconst writeStdin = Stream.run(tar, handle.stdin).pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause: unknown): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageLoadFailed({ detail: 'docker load stdin pipe failed', cause }),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst stdoutDrain = Stream.mkString(Stream.decodeText(handle.stdout)).pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageLoadFailed({ detail: 'docker load stdout drain failed', cause }),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst stderrDrain = Stream.mkString(Stream.decodeText(handle.stderr)).pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageLoadFailed({ detail: 'docker load stderr drain failed', cause }),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst exit = handle.exitCode.pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DockerRuntimeError =>\n\t\t\t\t\t\t\tnew ImageLoadFailed({ detail: 'docker load exit failed', cause }),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst [, stdoutText, stderrText, exitCode] = yield* Effect.all(\n\t\t\t\t\t[writeStdin, stdoutDrain, stderrDrain, exit] as const,\n\t\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t\t);\n\t\t\t\tif (exitCode !== 0) {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew ImageLoadFailed({\n\t\t\t\t\t\t\tdetail: `docker load exited ${exitCode}`,\n\t\t\t\t\t\t\tstderr: stderrText,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst parsed = parseLoadedRefs(stdoutText);\n\t\t\t\tif (parsed.length === 0) {\n\t\t\t\t\t// Include the trailing ~500 bytes of stdout so newer\n\t\t\t\t\t// Docker variants that interleave progress markers\n\t\t\t\t\t// (e.g. JSON status lines) surface the actual output\n\t\t\t\t\t// shape instead of an opaque \"no Loaded image lines\n\t\t\t\t\t// found\". The full stderr text is still carried for\n\t\t\t\t\t// the rare case docker emitted a diagnostic there.\n\t\t\t\t\tconst stdoutTail = tailBytes(stdoutText, 500);\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew ImageLoadFailed({\n\t\t\t\t\t\t\tdetail: `docker load succeeded but no Loaded image lines found; stdout tail: ${stdoutTail}`,\n\t\t\t\t\t\t\tstderr: stderrText,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst refs = yield* Effect.forEach(\n\t\t\t\t\tparsed,\n\t\t\t\t\t(ref) =>\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tif (ref.digest !== undefined) {\n\t\t\t\t\t\t\t\treturn refOf(ref.digest, ref.tag);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst digest = yield* imageExists(ref.tag!);\n\t\t\t\t\t\t\tif (digest === null) {\n\t\t\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\t\t\tnew ImageLoadFailed({\n\t\t\t\t\t\t\t\t\t\tdetail: `loaded image '${ref.tag}' not visible to inspect`,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn refOf(digest, ref.tag);\n\t\t\t\t\t\t}),\n\t\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t\t);\n\t\t\t\treturn { refs };\n\t\t\t}),\n\t\t);\n\t});\n\n// -----------------------------------------------------------------------------\n// Cache integration\n// -----------------------------------------------------------------------------\n\nexport interface CachedBuildKey {\n\treadonly namespace: string;\n\treadonly chain: string;\n\treadonly contentHash: ContentHash;\n}\n\n/** Content-addressed image build:\n *\n * 1. cache lookup keyed by (namespace, chainId, contentHash)\n * 2. on hit, verify the on-host tag still resolves; if so return\n * 3. on miss, run `docker build`, then write the resolved digest\n * to the cache (best-effort — write failure does not roll back\n * the on-host build).\n *\n * Caller-supplied tag is the content-addressed name; the cache key\n * is independent so an unrelated invalidation (e.g. base-image\n * bump) can drop the cache without disturbing the tag scheme. */\nexport const ensureImageCached = (\n\topts: BuildOptions,\n\tkey: CachedBuildKey,\n): Effect.Effect<string, DockerRuntimeError, CacheService | DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst cache = yield* CacheService;\n\t\tconst hit = yield* cache\n\t\t\t.lookup({ namespace: key.namespace, chain: key.chain, contentHash: key.contentHash })\n\t\t\t.pipe(\n\t\t\t\t// Cache-side failures collapse to MISS — the contract is\n\t\t\t\t// best-effort. We still build, but logDebug so the rare\n\t\t\t\t// cache-read failure is visible.\n\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\tEffect.logDebug('image cache lookup failed; rebuilding', { key, cause }),\n\t\t\t\t),\n\t\t\t\tEffect.catch(() => Effect.succeed(null)),\n\t\t\t);\n\t\tif (hit !== null) {\n\t\t\tconst cached = decodeDigest(hit.bytes);\n\t\t\tif (cached !== null) {\n\t\t\t\t// Verify the on-host tag still resolves to something —\n\t\t\t\t// the cached digest may have been pruned out from under us.\n\t\t\t\tconst existing = yield* imageExists(opts.tag);\n\t\t\t\tif (existing !== null) {\n\t\t\t\t\treturn existing;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// On-host tag-exists short-circuit (defence-in-depth).\n\t\tconst onHost = yield* imageExists(opts.tag);\n\t\tif (onHost !== null) {\n\t\t\tyield* cache\n\t\t\t\t.write(\n\t\t\t\t\t{ namespace: key.namespace, chain: key.chain, contentHash: key.contentHash },\n\t\t\t\t\tencodeDigest(onHost),\n\t\t\t\t)\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\t\tEffect.logDebug('image cache write (on-host hit) failed', { key, cause }),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.catch(() => Effect.void),\n\t\t\t\t);\n\t\t\treturn onHost;\n\t\t}\n\t\tconst digest = yield* build(opts);\n\t\tyield* cache\n\t\t\t.write(\n\t\t\t\t{ namespace: key.namespace, chain: key.chain, contentHash: key.contentHash },\n\t\t\t\tencodeDigest(digest),\n\t\t\t)\n\t\t\t.pipe(\n\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\tEffect.logDebug('image cache write (post-build) failed', { key, cause }),\n\t\t\t\t),\n\t\t\t\tEffect.catch(() => Effect.void),\n\t\t\t);\n\t\treturn digest;\n\t});\n\n// -----------------------------------------------------------------------------\n// Contract-shaped ImageRef helpers\n// -----------------------------------------------------------------------------\n\nexport const refOf = (digest: string, tag?: string): ImageRef =>\n\ttag !== undefined ? { digest, tag } : { digest };\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect-and-decode.mjs","names":[],"sources":["../../../src/runtime/docker/inspect-and-decode.ts"],"sourcesContent":["// `docker <resource> inspect` shared decode pipeline.\n//\n// Three Docker subsystem files (`container.ts`, `network.ts`) inline the\n// SAME post-`docker inspect` shape:\n//\n// 1. examine `CaptureResult.exitCode`\n// 2. on non-zero stderr, classify into `<missing>` (caller-specific),\n// `DaemonUnreachable`, or `DockerInspectFailed`\n// 3. on zero exit, decode the JSON-array element via\n// `decodeJsonArrayElementSync`, wrapping decode issues in\n// `DockerInspectDecodeFailed`\n//\n// This module centralises that ladder so the three sites stay byte-\n// identical and new resource kinds get the same treatment for free.\n//\n// Image inspect is intentionally NOT a consumer: `imageExists` uses\n// `docker inspect --format '{{.Id}}'` (plain stdout, no JSON array),\n// so it never enters this pipeline.\n\nimport { Effect, Schema } from 'effect';\n\nimport { decodeJsonArrayElementSync } from '../../substrate/runtime/runtime-decode.ts';\nimport type { CaptureResult } from '../../substrate/runtime/observability/subprocess-capture.ts';\nimport {\n\tDaemonUnreachable,\n\tDockerInspectDecodeFailed,\n\tDockerInspectFailed,\n\ttype DockerResourceKind,\n\ttype DockerRuntimeError,\n} from './errors.ts';\nimport { isDaemonUnreachableStderr } from './wrap.ts';\n\nexport interface DockerInspectAndDecodeOptions
|
|
1
|
+
{"version":3,"file":"inspect-and-decode.mjs","names":[],"sources":["../../../src/runtime/docker/inspect-and-decode.ts"],"sourcesContent":["// `docker <resource> inspect` shared decode pipeline.\n//\n// Three Docker subsystem files (`container.ts`, `network.ts`) inline the\n// SAME post-`docker inspect` shape:\n//\n// 1. examine `CaptureResult.exitCode`\n// 2. on non-zero stderr, classify into `<missing>` (caller-specific),\n// `DaemonUnreachable`, or `DockerInspectFailed`\n// 3. on zero exit, decode the JSON-array element via\n// `decodeJsonArrayElementSync`, wrapping decode issues in\n// `DockerInspectDecodeFailed`\n//\n// This module centralises that ladder so the three sites stay byte-\n// identical and new resource kinds get the same treatment for free.\n//\n// Image inspect is intentionally NOT a consumer: `imageExists` uses\n// `docker inspect --format '{{.Id}}'` (plain stdout, no JSON array),\n// so it never enters this pipeline.\n\nimport { Effect, Schema } from 'effect';\n\nimport { decodeJsonArrayElementSync } from '../../substrate/runtime/runtime-decode.ts';\nimport type { CaptureResult } from '../../substrate/runtime/observability/subprocess-capture.ts';\nimport {\n\tDaemonUnreachable,\n\tDockerInspectDecodeFailed,\n\tDockerInspectFailed,\n\ttype DockerResourceKind,\n\ttype DockerRuntimeError,\n} from './errors.ts';\nimport { isDaemonUnreachableStderr } from './wrap.ts';\n\nexport interface DockerInspectAndDecodeOptions<S extends Schema.Decoder<unknown>, R> {\n\t/** Resource kind, threaded into `DockerInspectFailed` /\n\t * `DockerInspectDecodeFailed` error envelopes. */\n\treadonly resourceKind: DockerResourceKind;\n\t/** Resource name (container, network, volume) being inspected.\n\t * Threaded into the error envelopes and the decode `source`. */\n\treadonly name: string;\n\t/** Op label for the `DaemonUnreachable` envelope.\n\t * e.g. `'docker.network.inspect'`. */\n\treadonly op: string;\n\t/** Pre-wrapped `docker <resource> inspect <name>` invocation. The\n\t * caller is responsible for the `mapError` step (each site has its\n\t * own wrap.ts classifier) so this Effect already produces typed\n\t * `DockerRuntimeError`s for spawn-side failures. */\n\treadonly inspectCommand: Effect.Effect<CaptureResult, DockerRuntimeError, R>;\n\t/** Schema for the JSON-array element returned by `docker inspect`. */\n\treadonly schema: S;\n\t/** Stderr classifier for the resource-specific \"missing\" case (e.g.\n\t * `No such container`). When this returns true on a non-zero exit,\n\t * the helper returns `null` to the caller; the caller decides what\n\t * the contract-level missing return value should be (`null`, `[]`,\n\t * …). */\n\treadonly isMissingStderr: (stderr: string) => boolean;\n\t/** Optional override for the malformed-JSON decode error detail.\n\t * Sites use slightly different wording (e.g. \"malformed container\n\t * JSON\" vs \"malformed network JSON\"); defaults to a generic\n\t * resource-kind-derived string. */\n\treadonly malformedDetail?: string;\n\t/** Optional array index; the universal pattern is `0` (single-\n\t * element array from `docker inspect`). */\n\treadonly index?: number;\n}\n\n/** Run `docker inspect`, classify the exit code, and decode the\n * JSON-array element. Returns `null` when the resource is missing\n * per the caller-supplied stderr classifier; otherwise returns the\n * decoded schema value. All failure modes surface as\n * `DockerRuntimeError` (`DaemonUnreachable`, `DockerInspectFailed`,\n * or `DockerInspectDecodeFailed`). */\nexport const dockerInspectAndDecode = <S extends Schema.Decoder<unknown>, R>(\n\toptions: DockerInspectAndDecodeOptions<S, R>,\n): Effect.Effect<S['Type'] | null, DockerRuntimeError, R> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* options.inspectCommand;\n\t\tif (res.exitCode !== 0) {\n\t\t\tif (options.isMissingStderr(res.stderr)) return null;\n\t\t\tif (isDaemonUnreachableStderr(res.stderr)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew DaemonUnreachable({\n\t\t\t\t\t\top: options.op,\n\t\t\t\t\t\tdetail: 'docker daemon unreachable',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew DockerInspectFailed({\n\t\t\t\t\tresource: options.resourceKind,\n\t\t\t\t\tname: options.name,\n\t\t\t\t\tstderr: res.stderr,\n\t\t\t\t\texitCode: res.exitCode,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\t// `decodeJsonArrayElementSync`'s `mkError` produces a typed\n\t\t// `DockerInspectDecodeFailed`; we route it through `Effect.try`\n\t\t// so the error channel stays typed. The `catch` narrows on\n\t\t// `instanceof` so that any UNEXPECTED sync defect thrown from\n\t\t// inside the decode body (e.g. a reader helper bug) is wrapped\n\t\t// in a fresh envelope rather than cast-lied as the wrong type.\n\t\tconst malformedDetail =\n\t\t\toptions.malformedDetail ?? `inspect returned malformed ${options.resourceKind} JSON`;\n\t\treturn yield* Effect.try({\n\t\t\ttry: (): S['Type'] =>\n\t\t\t\tdecodeJsonArrayElementSync(options.schema, res.stdout, {\n\t\t\t\t\tsource: `docker ${options.resourceKind} inspect ${options.name}`,\n\t\t\t\t\tmissingMessage: 'inspect returned an empty result',\n\t\t\t\t\tindex: options.index,\n\t\t\t\t\tmkError: (issue) =>\n\t\t\t\t\t\tnew DockerInspectDecodeFailed({\n\t\t\t\t\t\t\tresource: options.resourceKind,\n\t\t\t\t\t\t\tname: options.name,\n\t\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t\tissue.message === 'inspect returned an empty result'\n\t\t\t\t\t\t\t\t\t? issue.message\n\t\t\t\t\t\t\t\t\t: malformedDetail,\n\t\t\t\t\t\t\tcause: issue.cause,\n\t\t\t\t\t\t}),\n\t\t\t\t}),\n\t\t\tcatch: (cause): DockerInspectDecodeFailed =>\n\t\t\t\tcause instanceof DockerInspectDecodeFailed\n\t\t\t\t\t? cause\n\t\t\t\t\t: new DockerInspectDecodeFailed({\n\t\t\t\t\t\t\tresource: options.resourceKind,\n\t\t\t\t\t\t\tname: options.name,\n\t\t\t\t\t\t\tdetail: `unexpected defect while decoding ${options.resourceKind} inspect`,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t});\n\t});\n"],"mappings":";;;;;;;;;;;AAuEA,MAAa,0BACZ,YAEA,OAAO,IAAI,aAAa;CACvB,MAAM,MAAM,OAAO,QAAQ;CAC3B,IAAI,IAAI,aAAa,GAAG;EACvB,IAAI,QAAQ,gBAAgB,IAAI,MAAM,GAAG,OAAO;EAChD,IAAI,0BAA0B,IAAI,MAAM,GACvC,OAAO,OAAO,OAAO,KACpB,IAAI,kBAAkB;GACrB,IAAI,QAAQ;GACZ,QAAQ;EACT,CAAC,CACF;EAED,OAAO,OAAO,OAAO,KACpB,IAAI,oBAAoB;GACvB,UAAU,QAAQ;GAClB,MAAM,QAAQ;GACd,QAAQ,IAAI;GACZ,UAAU,IAAI;EACf,CAAC,CACF;CACD;CAOA,MAAM,kBACL,QAAQ,mBAAmB,8BAA8B,QAAQ,aAAa;CAC/E,OAAO,OAAO,OAAO,IAAI;EACxB,WACC,2BAA2B,QAAQ,QAAQ,IAAI,QAAQ;GACtD,QAAQ,UAAU,QAAQ,aAAa,WAAW,QAAQ;GAC1D,gBAAgB;GAChB,OAAO,QAAQ;GACf,UAAU,UACT,IAAI,0BAA0B;IAC7B,UAAU,QAAQ;IAClB,MAAM,QAAQ;IACd,QACC,MAAM,YAAY,qCACf,MAAM,UACN;IACJ,OAAO,MAAM;GACd,CAAC;EACH,CAAC;EACF,QAAQ,UACP,iBAAiB,4BACd,QACA,IAAI,0BAA0B;GAC9B,UAAU,QAAQ;GAClB,MAAM,QAAQ;GACd,QAAQ,oCAAoC,QAAQ,aAAa;GACjE;EACD,CAAC;CACL,CAAC;AACF,CAAC"}
|
|
@@ -92,7 +92,7 @@ const listContainers = (match) => Effect.gen(function* () {
|
|
|
92
92
|
"{{json .}}",
|
|
93
93
|
...renderFilterArgs(match)
|
|
94
94
|
]).pipe(Effect.mapError(wrapGeneric("docker.ps")))).stdout);
|
|
95
|
-
})
|
|
95
|
+
});
|
|
96
96
|
const listDevstackContainers = () => Effect.gen(function* () {
|
|
97
97
|
return toContainerSummaries((yield* dockerRunOk("ps", [
|
|
98
98
|
"-a",
|
|
@@ -100,7 +100,7 @@ const listDevstackContainers = () => Effect.gen(function* () {
|
|
|
100
100
|
"{{json .}}",
|
|
101
101
|
...devstackAppFilterArgs
|
|
102
102
|
]).pipe(Effect.mapError(wrapGeneric("docker.ps")))).stdout);
|
|
103
|
-
})
|
|
103
|
+
});
|
|
104
104
|
/** List devstack-managed containers whose generic `kind` label matches.
|
|
105
105
|
*
|
|
106
106
|
* Orchestrators that own a slot of named containers (e.g. the router
|
|
@@ -117,21 +117,21 @@ const listDevstackContainersByKind = (kind) => Effect.gen(function* () {
|
|
|
117
117
|
"--filter",
|
|
118
118
|
`label=${LabelKey.kind}=${kind}`
|
|
119
119
|
]).pipe(Effect.mapError(wrapGeneric("docker.ps")))).stdout);
|
|
120
|
-
})
|
|
120
|
+
});
|
|
121
121
|
const listImages = (match) => Effect.gen(function* () {
|
|
122
122
|
return toImageSummaries((yield* dockerRunOk("images", [
|
|
123
123
|
"--format",
|
|
124
124
|
"{{json .}}",
|
|
125
125
|
...renderFilterArgs(match)
|
|
126
126
|
]).pipe(Effect.mapError(wrapGeneric("docker.images")))).stdout);
|
|
127
|
-
})
|
|
127
|
+
});
|
|
128
128
|
const listDevstackImages = () => Effect.gen(function* () {
|
|
129
129
|
return toImageSummaries((yield* dockerRunOk("images", [
|
|
130
130
|
"--format",
|
|
131
131
|
"{{json .}}",
|
|
132
132
|
...devstackAppFilterArgs
|
|
133
133
|
]).pipe(Effect.mapError(wrapGeneric("docker.images")))).stdout);
|
|
134
|
-
})
|
|
134
|
+
});
|
|
135
135
|
const listNetworks = (match) => Effect.gen(function* () {
|
|
136
136
|
return toNetworkSummaries((yield* dockerRunOk("network", [
|
|
137
137
|
"ls",
|
|
@@ -139,7 +139,7 @@ const listNetworks = (match) => Effect.gen(function* () {
|
|
|
139
139
|
"{{json .}}",
|
|
140
140
|
...renderFilterArgs(match)
|
|
141
141
|
]).pipe(Effect.mapError(wrapGeneric("docker.network.ls")))).stdout);
|
|
142
|
-
})
|
|
142
|
+
});
|
|
143
143
|
const listDevstackNetworks = () => Effect.gen(function* () {
|
|
144
144
|
return toNetworkSummaries((yield* dockerRunOk("network", [
|
|
145
145
|
"ls",
|
|
@@ -147,7 +147,7 @@ const listDevstackNetworks = () => Effect.gen(function* () {
|
|
|
147
147
|
"{{json .}}",
|
|
148
148
|
...devstackAppFilterArgs
|
|
149
149
|
]).pipe(Effect.mapError(wrapGeneric("docker.network.ls")))).stdout);
|
|
150
|
-
})
|
|
150
|
+
});
|
|
151
151
|
const listVolumes = (match) => Effect.gen(function* () {
|
|
152
152
|
return toVolumeSummaries((yield* dockerRunOk("volume", [
|
|
153
153
|
"ls",
|
|
@@ -155,7 +155,7 @@ const listVolumes = (match) => Effect.gen(function* () {
|
|
|
155
155
|
"{{json .}}",
|
|
156
156
|
...renderFilterArgs(match)
|
|
157
157
|
]).pipe(Effect.mapError(wrapGeneric("docker.volume.ls")))).stdout);
|
|
158
|
-
})
|
|
158
|
+
});
|
|
159
159
|
const listDevstackVolumes = () => Effect.gen(function* () {
|
|
160
160
|
return toVolumeSummaries((yield* dockerRunOk("volume", [
|
|
161
161
|
"ls",
|
|
@@ -163,7 +163,7 @@ const listDevstackVolumes = () => Effect.gen(function* () {
|
|
|
163
163
|
"{{json .}}",
|
|
164
164
|
...devstackAppFilterArgs
|
|
165
165
|
]).pipe(Effect.mapError(wrapGeneric("docker.volume.ls")))).stdout);
|
|
166
|
-
})
|
|
166
|
+
});
|
|
167
167
|
//#endregion
|
|
168
168
|
export { listContainers, listDevstackContainers, listDevstackContainersByKind, listDevstackImages, listDevstackNetworks, listDevstackVolumes, listImages, listNetworks, listVolumes };
|
|
169
169
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inventory.mjs","names":[],"sources":["../../../src/runtime/docker/inventory.ts"],"sourcesContent":["// Label-driven inventory.\n//\n// Architecture § Container runtime § Label-driven inventory:\n// inventory NEVER greps stdout for container names; it ALWAYS\n// filters by labels via `--filter label=...`. The output format is\n// the `--format` JSON line shape, which we parse line-by-line.\n//\n// Surface:\n// - `listContainers(match)` → ContainerSummary[]\n// - `listImages(match)` → ImageSummary[]\n// - `listNetworks(match)` → NetworkSummary[]\n// - `listVolumes(match)` → VolumeSummary[]\n\nimport { Effect, Schema } from 'effect';\n\nimport type { ContainerLabelTuple } from '../../contracts/snapshotable.ts';\nimport { decodeJsonTextSync } from '../../substrate/runtime/runtime-decode.ts';\nimport { DockerHost, DockerSpawner, dockerRunOk } from './client.ts';\nimport type { DockerRuntimeError } from './errors.ts';\nimport { LabelKey, renderFilterArgs } from './labels.ts';\nimport { wrapGeneric } from './wrap.ts';\n\n// -----------------------------------------------------------------------------\n// Summary shapes — what the label-filter listers return\n// -----------------------------------------------------------------------------\n\nexport interface ContainerSummary {\n\treadonly id: string;\n\treadonly name: string;\n\treadonly image: string;\n\treadonly status: string;\n\treadonly state: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\nexport interface ImageSummary {\n\treadonly id: string;\n\treadonly tag: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\nexport interface NetworkSummary {\n\treadonly id: string;\n\treadonly name: string;\n\treadonly driver: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\nexport interface VolumeSummary {\n\treadonly name: string;\n\treadonly driver: string;\n\treadonly mountpoint: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\n// -----------------------------------------------------------------------------\n// JSON-line schemas (docker --format '{{json .}}')\n// -----------------------------------------------------------------------------\n\nconst PsLine = Schema.Struct({\n\tID: Schema.String,\n\tNames: Schema.String,\n\tImage: Schema.String,\n\tStatus: Schema.String,\n\tState: Schema.String,\n\tLabels: Schema.optional(Schema.String),\n});\n\nconst ImagesLine = Schema.Struct({\n\tID: Schema.String,\n\tRepository: Schema.String,\n\tTag: Schema.String,\n\tLabels: Schema.optional(Schema.String),\n});\n\nconst NetworksLine = Schema.Struct({\n\tID: Schema.String,\n\tName: Schema.String,\n\tDriver: Schema.String,\n\tLabels: Schema.optional(Schema.String),\n});\n\nconst VolumesLine = Schema.Struct({\n\tName: Schema.String,\n\tDriver: Schema.String,\n\tMountpoint: Schema.String,\n\tLabels: Schema.optional(Schema.String),\n});\n\n// -----------------------------------------------------------------------------\n// Label parsing — docker's `Labels` field is a comma-joined `k=v,k=v`\n// -----------------------------------------------------------------------------\n\nconst parseLabelString = (s: string | undefined): Record<string, string> => {\n\tif (!s) return {};\n\tconst out: Record<string, string> = {};\n\tfor (const part of s.split(',')) {\n\t\tconst i = part.indexOf('=');\n\t\tif (i < 0) continue;\n\t\tout[part.slice(0, i).trim()] = part.slice(i + 1).trim();\n\t}\n\treturn out;\n};\n\nconst parseJsonLines = <S extends Schema.Decoder<unknown>>(\n\tschema: S,\n\tstdout: string,\n): ReadonlyArray<S['Type']> => {\n\tconst lines = stdout.split('\\n').filter((l) => l.trim().length > 0);\n\tconst out: Array<S['Type']> = [];\n\tfor (const [index, line] of lines.entries()) {\n\t\ttry {\n\t\t\tout.push(\n\t\t\t\tdecodeJsonTextSync(schema, line, {\n\t\t\t\t\tsource: `docker inventory:${index + 1}`,\n\t\t\t\t\tmkError: (issue) => issue,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch {\n\t\t\t// Malformed line — skip. Inventory is best-effort; a single\n\t\t\t// torn line shouldn't poison the whole sweep.\n\t\t}\n\t}\n\treturn out;\n};\n\n// -----------------------------------------------------------------------------\n// Listers\n// -----------------------------------------------------------------------------\n\nconst toContainerSummaries = (stdout: string): ReadonlyArray<ContainerSummary> => {\n\tconst lines = parseJsonLines(PsLine, stdout);\n\treturn lines.map(\n\t\t(l): ContainerSummary => ({\n\t\t\tid: l.ID,\n\t\t\tname: l.Names.split(',')[0] ?? l.Names,\n\t\t\timage: l.Image,\n\t\t\tstatus: l.Status,\n\t\t\tstate: l.State,\n\t\t\tlabels: parseLabelString(l.Labels),\n\t\t}),\n\t);\n};\n\nconst toImageSummaries = (stdout: string): ReadonlyArray<ImageSummary> => {\n\tconst lines = parseJsonLines(ImagesLine, stdout);\n\treturn lines.map(\n\t\t(l): ImageSummary => ({\n\t\t\tid: l.ID,\n\t\t\ttag: `${l.Repository}:${l.Tag}`,\n\t\t\tlabels: parseLabelString(l.Labels),\n\t\t}),\n\t);\n};\n\nconst toNetworkSummaries = (stdout: string): ReadonlyArray<NetworkSummary> => {\n\tconst lines = parseJsonLines(NetworksLine, stdout);\n\treturn lines.map(\n\t\t(l): NetworkSummary => ({\n\t\t\tid: l.ID,\n\t\t\tname: l.Name,\n\t\t\tdriver: l.Driver,\n\t\t\tlabels: parseLabelString(l.Labels),\n\t\t}),\n\t);\n};\n\nconst toVolumeSummaries = (stdout: string): ReadonlyArray<VolumeSummary> => {\n\tconst lines = parseJsonLines(VolumesLine, stdout);\n\treturn lines.map(\n\t\t(l): VolumeSummary => ({\n\t\t\tname: l.Name,\n\t\t\tdriver: l.Driver,\n\t\t\tmountpoint: l.Mountpoint,\n\t\t\tlabels: parseLabelString(l.Labels),\n\t\t}),\n\t);\n};\n\nconst devstackAppFilterArgs: ReadonlyArray<string> = ['--filter', `label=${LabelKey.app}`];\n\nexport const listContainers = (\n\tmatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<ReadonlyArray<ContainerSummary>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst filters = renderFilterArgs(match);\n\t\tconst res = yield* dockerRunOk('ps', ['-a', '--format', '{{json .}}', ...filters]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.ps')),\n\t\t);\n\t\treturn toContainerSummaries(res.stdout);\n\t}).pipe(Effect.withSpan('runtime.docker.inventory.containers'));\n\nexport const listDevstackContainers = (): Effect.Effect<\n\tReadonlyArray<ContainerSummary>,\n\tDockerRuntimeError,\n\tDockerHost | DockerSpawner\n> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('ps', [\n\t\t\t'-a',\n\t\t\t'--format',\n\t\t\t'{{json .}}',\n\t\t\t...devstackAppFilterArgs,\n\t\t]).pipe(Effect.mapError(wrapGeneric('docker.ps')));\n\t\treturn toContainerSummaries(res.stdout);\n\t}).pipe(Effect.withSpan('runtime.docker.inventory.devstackContainers'));\n\n/** List devstack-managed containers whose generic `kind` label matches.\n *\n * Orchestrators that own a slot of named containers (e.g. the router\n * orchestrator stamps `kind=router`) use this to enumerate their own\n * fleet without L1 learning the orchestrator's name. The L1 lister\n * takes a STRING — it never branches on the value. */\nexport const listDevstackContainersByKind = (\n\tkind: string,\n): Effect.Effect<ReadonlyArray<ContainerSummary>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('ps', [\n\t\t\t'-a',\n\t\t\t'--format',\n\t\t\t'{{json .}}',\n\t\t\t'--filter',\n\t\t\t`label=${LabelKey.managed}=true`,\n\t\t\t'--filter',\n\t\t\t`label=${LabelKey.kind}=${kind}`,\n\t\t]).pipe(Effect.mapError(wrapGeneric('docker.ps')));\n\t\treturn toContainerSummaries(res.stdout);\n\t}).pipe(Effect.withSpan('runtime.docker.inventory.devstackContainersByKind'));\n\nexport const listImages = (\n\tmatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<ReadonlyArray<ImageSummary>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst filters = renderFilterArgs(match);\n\t\tconst res = yield* dockerRunOk('images', ['--format', '{{json .}}', ...filters]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.images')),\n\t\t);\n\t\treturn toImageSummaries(res.stdout);\n\t}).pipe(Effect.withSpan('runtime.docker.inventory.images'));\n\nexport const listDevstackImages = (): Effect.Effect<\n\tReadonlyArray<ImageSummary>,\n\tDockerRuntimeError,\n\tDockerHost | DockerSpawner\n> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('images', [\n\t\t\t'--format',\n\t\t\t'{{json .}}',\n\t\t\t...devstackAppFilterArgs,\n\t\t]).pipe(Effect.mapError(wrapGeneric('docker.images')));\n\t\treturn toImageSummaries(res.stdout);\n\t}).pipe(Effect.withSpan('runtime.docker.inventory.devstackImages'));\n\nexport const listNetworks = (\n\tmatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<ReadonlyArray<NetworkSummary>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst filters = renderFilterArgs(match);\n\t\tconst res = yield* dockerRunOk('network', ['ls', '--format', '{{json .}}', ...filters]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.network.ls')),\n\t\t);\n\t\treturn toNetworkSummaries(res.stdout);\n\t}).pipe(Effect.withSpan('runtime.docker.inventory.networks'));\n\nexport const listDevstackNetworks = (): Effect.Effect<\n\tReadonlyArray<NetworkSummary>,\n\tDockerRuntimeError,\n\tDockerHost | DockerSpawner\n> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('network', [\n\t\t\t'ls',\n\t\t\t'--format',\n\t\t\t'{{json .}}',\n\t\t\t...devstackAppFilterArgs,\n\t\t]).pipe(Effect.mapError(wrapGeneric('docker.network.ls')));\n\t\treturn toNetworkSummaries(res.stdout);\n\t}).pipe(Effect.withSpan('runtime.docker.inventory.devstackNetworks'));\n\nexport const listVolumes = (\n\tmatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<ReadonlyArray<VolumeSummary>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst filters = renderFilterArgs(match);\n\t\tconst res = yield* dockerRunOk('volume', ['ls', '--format', '{{json .}}', ...filters]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.volume.ls')),\n\t\t);\n\t\treturn toVolumeSummaries(res.stdout);\n\t}).pipe(Effect.withSpan('runtime.docker.inventory.volumes'));\n\nexport const listDevstackVolumes = (): Effect.Effect<\n\tReadonlyArray<VolumeSummary>,\n\tDockerRuntimeError,\n\tDockerHost | DockerSpawner\n> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('volume', [\n\t\t\t'ls',\n\t\t\t'--format',\n\t\t\t'{{json .}}',\n\t\t\t...devstackAppFilterArgs,\n\t\t]).pipe(Effect.mapError(wrapGeneric('docker.volume.ls')));\n\t\treturn toVolumeSummaries(res.stdout);\n\t}).pipe(Effect.withSpan('runtime.docker.inventory.devstackVolumes'));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"inventory.mjs","names":[],"sources":["../../../src/runtime/docker/inventory.ts"],"sourcesContent":["// Label-driven inventory.\n//\n// Architecture § Container runtime § Label-driven inventory:\n// inventory NEVER greps stdout for container names; it ALWAYS\n// filters by labels via `--filter label=...`. The output format is\n// the `--format` JSON line shape, which we parse line-by-line.\n//\n// Surface:\n// - `listContainers(match)` → ContainerSummary[]\n// - `listImages(match)` → ImageSummary[]\n// - `listNetworks(match)` → NetworkSummary[]\n// - `listVolumes(match)` → VolumeSummary[]\n\nimport { Effect, Schema } from 'effect';\n\nimport type { ContainerLabelTuple } from '../../contracts/snapshotable.ts';\nimport { decodeJsonTextSync } from '../../substrate/runtime/runtime-decode.ts';\nimport { DockerHost, DockerSpawner, dockerRunOk } from './client.ts';\nimport type { DockerRuntimeError } from './errors.ts';\nimport { LabelKey, renderFilterArgs } from './labels.ts';\nimport { wrapGeneric } from './wrap.ts';\n\n// -----------------------------------------------------------------------------\n// Summary shapes — what the label-filter listers return\n// -----------------------------------------------------------------------------\n\nexport interface ContainerSummary {\n\treadonly id: string;\n\treadonly name: string;\n\treadonly image: string;\n\treadonly status: string;\n\treadonly state: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\nexport interface ImageSummary {\n\treadonly id: string;\n\treadonly tag: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\nexport interface NetworkSummary {\n\treadonly id: string;\n\treadonly name: string;\n\treadonly driver: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\nexport interface VolumeSummary {\n\treadonly name: string;\n\treadonly driver: string;\n\treadonly mountpoint: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\n// -----------------------------------------------------------------------------\n// JSON-line schemas (docker --format '{{json .}}')\n// -----------------------------------------------------------------------------\n\nconst PsLine = Schema.Struct({\n\tID: Schema.String,\n\tNames: Schema.String,\n\tImage: Schema.String,\n\tStatus: Schema.String,\n\tState: Schema.String,\n\tLabels: Schema.optional(Schema.String),\n});\n\nconst ImagesLine = Schema.Struct({\n\tID: Schema.String,\n\tRepository: Schema.String,\n\tTag: Schema.String,\n\tLabels: Schema.optional(Schema.String),\n});\n\nconst NetworksLine = Schema.Struct({\n\tID: Schema.String,\n\tName: Schema.String,\n\tDriver: Schema.String,\n\tLabels: Schema.optional(Schema.String),\n});\n\nconst VolumesLine = Schema.Struct({\n\tName: Schema.String,\n\tDriver: Schema.String,\n\tMountpoint: Schema.String,\n\tLabels: Schema.optional(Schema.String),\n});\n\n// -----------------------------------------------------------------------------\n// Label parsing — docker's `Labels` field is a comma-joined `k=v,k=v`\n// -----------------------------------------------------------------------------\n\nconst parseLabelString = (s: string | undefined): Record<string, string> => {\n\tif (!s) return {};\n\tconst out: Record<string, string> = {};\n\tfor (const part of s.split(',')) {\n\t\tconst i = part.indexOf('=');\n\t\tif (i < 0) continue;\n\t\tout[part.slice(0, i).trim()] = part.slice(i + 1).trim();\n\t}\n\treturn out;\n};\n\nconst parseJsonLines = <S extends Schema.Decoder<unknown>>(\n\tschema: S,\n\tstdout: string,\n): ReadonlyArray<S['Type']> => {\n\tconst lines = stdout.split('\\n').filter((l) => l.trim().length > 0);\n\tconst out: Array<S['Type']> = [];\n\tfor (const [index, line] of lines.entries()) {\n\t\ttry {\n\t\t\tout.push(\n\t\t\t\tdecodeJsonTextSync(schema, line, {\n\t\t\t\t\tsource: `docker inventory:${index + 1}`,\n\t\t\t\t\tmkError: (issue) => issue,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch {\n\t\t\t// Malformed line — skip. Inventory is best-effort; a single\n\t\t\t// torn line shouldn't poison the whole sweep.\n\t\t}\n\t}\n\treturn out;\n};\n\n// -----------------------------------------------------------------------------\n// Listers\n// -----------------------------------------------------------------------------\n\nconst toContainerSummaries = (stdout: string): ReadonlyArray<ContainerSummary> => {\n\tconst lines = parseJsonLines(PsLine, stdout);\n\treturn lines.map(\n\t\t(l): ContainerSummary => ({\n\t\t\tid: l.ID,\n\t\t\tname: l.Names.split(',')[0] ?? l.Names,\n\t\t\timage: l.Image,\n\t\t\tstatus: l.Status,\n\t\t\tstate: l.State,\n\t\t\tlabels: parseLabelString(l.Labels),\n\t\t}),\n\t);\n};\n\nconst toImageSummaries = (stdout: string): ReadonlyArray<ImageSummary> => {\n\tconst lines = parseJsonLines(ImagesLine, stdout);\n\treturn lines.map(\n\t\t(l): ImageSummary => ({\n\t\t\tid: l.ID,\n\t\t\ttag: `${l.Repository}:${l.Tag}`,\n\t\t\tlabels: parseLabelString(l.Labels),\n\t\t}),\n\t);\n};\n\nconst toNetworkSummaries = (stdout: string): ReadonlyArray<NetworkSummary> => {\n\tconst lines = parseJsonLines(NetworksLine, stdout);\n\treturn lines.map(\n\t\t(l): NetworkSummary => ({\n\t\t\tid: l.ID,\n\t\t\tname: l.Name,\n\t\t\tdriver: l.Driver,\n\t\t\tlabels: parseLabelString(l.Labels),\n\t\t}),\n\t);\n};\n\nconst toVolumeSummaries = (stdout: string): ReadonlyArray<VolumeSummary> => {\n\tconst lines = parseJsonLines(VolumesLine, stdout);\n\treturn lines.map(\n\t\t(l): VolumeSummary => ({\n\t\t\tname: l.Name,\n\t\t\tdriver: l.Driver,\n\t\t\tmountpoint: l.Mountpoint,\n\t\t\tlabels: parseLabelString(l.Labels),\n\t\t}),\n\t);\n};\n\nconst devstackAppFilterArgs: ReadonlyArray<string> = ['--filter', `label=${LabelKey.app}`];\n\nexport const listContainers = (\n\tmatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<ReadonlyArray<ContainerSummary>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst filters = renderFilterArgs(match);\n\t\tconst res = yield* dockerRunOk('ps', ['-a', '--format', '{{json .}}', ...filters]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.ps')),\n\t\t);\n\t\treturn toContainerSummaries(res.stdout);\n\t});\n\nexport const listDevstackContainers = (): Effect.Effect<\n\tReadonlyArray<ContainerSummary>,\n\tDockerRuntimeError,\n\tDockerHost | DockerSpawner\n> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('ps', [\n\t\t\t'-a',\n\t\t\t'--format',\n\t\t\t'{{json .}}',\n\t\t\t...devstackAppFilterArgs,\n\t\t]).pipe(Effect.mapError(wrapGeneric('docker.ps')));\n\t\treturn toContainerSummaries(res.stdout);\n\t});\n\n/** List devstack-managed containers whose generic `kind` label matches.\n *\n * Orchestrators that own a slot of named containers (e.g. the router\n * orchestrator stamps `kind=router`) use this to enumerate their own\n * fleet without L1 learning the orchestrator's name. The L1 lister\n * takes a STRING — it never branches on the value. */\nexport const listDevstackContainersByKind = (\n\tkind: string,\n): Effect.Effect<ReadonlyArray<ContainerSummary>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('ps', [\n\t\t\t'-a',\n\t\t\t'--format',\n\t\t\t'{{json .}}',\n\t\t\t'--filter',\n\t\t\t`label=${LabelKey.managed}=true`,\n\t\t\t'--filter',\n\t\t\t`label=${LabelKey.kind}=${kind}`,\n\t\t]).pipe(Effect.mapError(wrapGeneric('docker.ps')));\n\t\treturn toContainerSummaries(res.stdout);\n\t});\n\nexport const listImages = (\n\tmatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<ReadonlyArray<ImageSummary>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst filters = renderFilterArgs(match);\n\t\tconst res = yield* dockerRunOk('images', ['--format', '{{json .}}', ...filters]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.images')),\n\t\t);\n\t\treturn toImageSummaries(res.stdout);\n\t});\n\nexport const listDevstackImages = (): Effect.Effect<\n\tReadonlyArray<ImageSummary>,\n\tDockerRuntimeError,\n\tDockerHost | DockerSpawner\n> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('images', [\n\t\t\t'--format',\n\t\t\t'{{json .}}',\n\t\t\t...devstackAppFilterArgs,\n\t\t]).pipe(Effect.mapError(wrapGeneric('docker.images')));\n\t\treturn toImageSummaries(res.stdout);\n\t});\n\nexport const listNetworks = (\n\tmatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<ReadonlyArray<NetworkSummary>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst filters = renderFilterArgs(match);\n\t\tconst res = yield* dockerRunOk('network', ['ls', '--format', '{{json .}}', ...filters]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.network.ls')),\n\t\t);\n\t\treturn toNetworkSummaries(res.stdout);\n\t});\n\nexport const listDevstackNetworks = (): Effect.Effect<\n\tReadonlyArray<NetworkSummary>,\n\tDockerRuntimeError,\n\tDockerHost | DockerSpawner\n> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('network', [\n\t\t\t'ls',\n\t\t\t'--format',\n\t\t\t'{{json .}}',\n\t\t\t...devstackAppFilterArgs,\n\t\t]).pipe(Effect.mapError(wrapGeneric('docker.network.ls')));\n\t\treturn toNetworkSummaries(res.stdout);\n\t});\n\nexport const listVolumes = (\n\tmatch: Partial<ContainerLabelTuple>,\n): Effect.Effect<ReadonlyArray<VolumeSummary>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst filters = renderFilterArgs(match);\n\t\tconst res = yield* dockerRunOk('volume', ['ls', '--format', '{{json .}}', ...filters]).pipe(\n\t\t\tEffect.mapError(wrapGeneric('docker.volume.ls')),\n\t\t);\n\t\treturn toVolumeSummaries(res.stdout);\n\t});\n\nexport const listDevstackVolumes = (): Effect.Effect<\n\tReadonlyArray<VolumeSummary>,\n\tDockerRuntimeError,\n\tDockerHost | DockerSpawner\n> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('volume', [\n\t\t\t'ls',\n\t\t\t'--format',\n\t\t\t'{{json .}}',\n\t\t\t...devstackAppFilterArgs,\n\t\t]).pipe(Effect.mapError(wrapGeneric('docker.volume.ls')));\n\t\treturn toVolumeSummaries(res.stdout);\n\t});\n"],"mappings":";;;;;;AA2DA,MAAM,SAAS,OAAO,OAAO;CAC5B,IAAI,OAAO;CACX,OAAO,OAAO;CACd,OAAO,OAAO;CACd,QAAQ,OAAO;CACf,OAAO,OAAO;CACd,QAAQ,OAAO,SAAS,OAAO,MAAM;AACtC,CAAC;AAED,MAAM,aAAa,OAAO,OAAO;CAChC,IAAI,OAAO;CACX,YAAY,OAAO;CACnB,KAAK,OAAO;CACZ,QAAQ,OAAO,SAAS,OAAO,MAAM;AACtC,CAAC;AAED,MAAM,eAAe,OAAO,OAAO;CAClC,IAAI,OAAO;CACX,MAAM,OAAO;CACb,QAAQ,OAAO;CACf,QAAQ,OAAO,SAAS,OAAO,MAAM;AACtC,CAAC;AAED,MAAM,cAAc,OAAO,OAAO;CACjC,MAAM,OAAO;CACb,QAAQ,OAAO;CACf,YAAY,OAAO;CACnB,QAAQ,OAAO,SAAS,OAAO,MAAM;AACtC,CAAC;AAMD,MAAM,oBAAoB,MAAkD;CAC3E,IAAI,CAAC,GAAG,OAAO,CAAC;CAChB,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,QAAQ,EAAE,MAAM,GAAG,GAAG;EAChC,MAAM,IAAI,KAAK,QAAQ,GAAG;EAC1B,IAAI,IAAI,GAAG;EACX,IAAI,KAAK,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,KAAK;CACvD;CACA,OAAO;AACR;AAEA,MAAM,kBACL,QACA,WAC8B;CAC9B,MAAM,QAAQ,OAAO,MAAM,IAAI,CAAC,CAAC,QAAQ,MAAM,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC;CAClE,MAAM,MAAwB,CAAC;CAC/B,KAAK,MAAM,CAAC,OAAO,SAAS,MAAM,QAAQ,GACzC,IAAI;EACH,IAAI,KACH,mBAAmB,QAAQ,MAAM;GAChC,QAAQ,oBAAoB,QAAQ;GACpC,UAAU,UAAU;EACrB,CAAC,CACF;CACD,QAAQ,CAGR;CAED,OAAO;AACR;AAMA,MAAM,wBAAwB,WAAoD;CAEjF,OADc,eAAe,QAAQ,MAC1B,CAAC,CAAC,KACX,OAAyB;EACzB,IAAI,EAAE;EACN,MAAM,EAAE,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE;EACjC,OAAO,EAAE;EACT,QAAQ,EAAE;EACV,OAAO,EAAE;EACT,QAAQ,iBAAiB,EAAE,MAAM;CAClC,EACD;AACD;AAEA,MAAM,oBAAoB,WAAgD;CAEzE,OADc,eAAe,YAAY,MAC9B,CAAC,CAAC,KACX,OAAqB;EACrB,IAAI,EAAE;EACN,KAAK,GAAG,EAAE,WAAW,GAAG,EAAE;EAC1B,QAAQ,iBAAiB,EAAE,MAAM;CAClC,EACD;AACD;AAEA,MAAM,sBAAsB,WAAkD;CAE7E,OADc,eAAe,cAAc,MAChC,CAAC,CAAC,KACX,OAAuB;EACvB,IAAI,EAAE;EACN,MAAM,EAAE;EACR,QAAQ,EAAE;EACV,QAAQ,iBAAiB,EAAE,MAAM;CAClC,EACD;AACD;AAEA,MAAM,qBAAqB,WAAiD;CAE3E,OADc,eAAe,aAAa,MAC/B,CAAC,CAAC,KACX,OAAsB;EACtB,MAAM,EAAE;EACR,QAAQ,EAAE;EACV,YAAY,EAAE;EACd,QAAQ,iBAAiB,EAAE,MAAM;CAClC,EACD;AACD;AAEA,MAAM,wBAA+C,CAAC,YAAY,SAAS,SAAS,KAAK;AAEzF,MAAa,kBACZ,UAEA,OAAO,IAAI,aAAa;CAKvB,OAAO,sBAAqB,OAHT,YAAY,MAAM;EAAC;EAAM;EAAY;EAAc,GADtD,iBAAiB,KAC8C;CAAC,CAAC,CAAC,CAAC,KAClF,OAAO,SAAS,YAAY,WAAW,CAAC,CACzC,EAAA,CACgC,MAAM;AACvC,CAAC;AAEF,MAAa,+BAKZ,OAAO,IAAI,aAAa;CAOvB,OAAO,sBAAqB,OANT,YAAY,MAAM;EACpC;EACA;EACA;EACA,GAAG;CACJ,CAAC,CAAC,CAAC,KAAK,OAAO,SAAS,YAAY,WAAW,CAAC,CAAC,EAAA,CACjB,MAAM;AACvC,CAAC;;;;;;;AAQF,MAAa,gCACZ,SAEA,OAAO,IAAI,aAAa;CAUvB,OAAO,sBAAqB,OATT,YAAY,MAAM;EACpC;EACA;EACA;EACA;EACA,SAAS,SAAS,QAAQ;EAC1B;EACA,SAAS,SAAS,KAAK,GAAG;CAC3B,CAAC,CAAC,CAAC,KAAK,OAAO,SAAS,YAAY,WAAW,CAAC,CAAC,EAAA,CACjB,MAAM;AACvC,CAAC;AAEF,MAAa,cACZ,UAEA,OAAO,IAAI,aAAa;CAKvB,OAAO,kBAAiB,OAHL,YAAY,UAAU;EAAC;EAAY;EAAc,GADpD,iBAAiB,KAC4C;CAAC,CAAC,CAAC,CAAC,KAChF,OAAO,SAAS,YAAY,eAAe,CAAC,CAC7C,EAAA,CAC4B,MAAM;AACnC,CAAC;AAEF,MAAa,2BAKZ,OAAO,IAAI,aAAa;CAMvB,OAAO,kBAAiB,OALL,YAAY,UAAU;EACxC;EACA;EACA,GAAG;CACJ,CAAC,CAAC,CAAC,KAAK,OAAO,SAAS,YAAY,eAAe,CAAC,CAAC,EAAA,CACzB,MAAM;AACnC,CAAC;AAEF,MAAa,gBACZ,UAEA,OAAO,IAAI,aAAa;CAKvB,OAAO,oBAAmB,OAHP,YAAY,WAAW;EAAC;EAAM;EAAY;EAAc,GAD3D,iBAAiB,KACmD;CAAC,CAAC,CAAC,CAAC,KACvF,OAAO,SAAS,YAAY,mBAAmB,CAAC,CACjD,EAAA,CAC8B,MAAM;AACrC,CAAC;AAEF,MAAa,6BAKZ,OAAO,IAAI,aAAa;CAOvB,OAAO,oBAAmB,OANP,YAAY,WAAW;EACzC;EACA;EACA;EACA,GAAG;CACJ,CAAC,CAAC,CAAC,KAAK,OAAO,SAAS,YAAY,mBAAmB,CAAC,CAAC,EAAA,CAC3B,MAAM;AACrC,CAAC;AAEF,MAAa,eACZ,UAEA,OAAO,IAAI,aAAa;CAKvB,OAAO,mBAAkB,OAHN,YAAY,UAAU;EAAC;EAAM;EAAY;EAAc,GAD1D,iBAAiB,KACkD;CAAC,CAAC,CAAC,CAAC,KACtF,OAAO,SAAS,YAAY,kBAAkB,CAAC,CAChD,EAAA,CAC6B,MAAM;AACpC,CAAC;AAEF,MAAa,4BAKZ,OAAO,IAAI,aAAa;CAOvB,OAAO,mBAAkB,OANN,YAAY,UAAU;EACxC;EACA;EACA;EACA,GAAG;CACJ,CAAC,CAAC,CAAC,KAAK,OAAO,SAAS,YAAY,kBAAkB,CAAC,CAAC,EAAA,CAC3B,MAAM;AACpC,CAAC"}
|
|
@@ -50,6 +50,17 @@ const normalizeComposeSegment = (value) => {
|
|
|
50
50
|
const normalized = value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
51
51
|
return normalized.length > 0 ? normalized : "unnamed";
|
|
52
52
|
};
|
|
53
|
+
/** Sanitize an `(app | stack)` identifier into a single Docker-tag-safe
|
|
54
|
+
* segment. A Docker tag component is lowercase `[a-z0-9._-]` — this
|
|
55
|
+
* lowercases, replaces every run of illegal characters with a single `-`,
|
|
56
|
+
* and trims leading/trailing `-`/`.`/`_` separators so the result never
|
|
57
|
+
* starts/ends with a separator (which Docker rejects as an invalid
|
|
58
|
+
* reference) and two distinct ids cannot collapse to the empty string.
|
|
59
|
+
* Used to scope the content-addressed build tag by `(app, stack)`. */
|
|
60
|
+
const sanitizeTagSegment = (value) => {
|
|
61
|
+
const normalized = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-._]+|[-._]+$/g, "");
|
|
62
|
+
return normalized.length > 0 ? normalized : "unnamed";
|
|
63
|
+
};
|
|
53
64
|
const composeProjectId = (app, stack) => `${normalizeComposeSegment(app)}-${normalizeComposeSegment(stack)}`;
|
|
54
65
|
const composeServiceId = (tuple) => `${normalizeComposeSegment(tuple.plugin)}.${normalizeComposeSegment(tuple.role)}`;
|
|
55
66
|
const renderComposeContainerLabels = (tuple) => [
|
|
@@ -125,6 +136,6 @@ const renderNetworkLabels = (name, app, stack, opts = {}) => {
|
|
|
125
136
|
return opts.composeUi === false ? ownership : [...ownership, ...renderComposeNetworkLabels(name, app, stack)];
|
|
126
137
|
};
|
|
127
138
|
//#endregion
|
|
128
|
-
export { COMPOSE_UI_VERSION, ComposeLabelKey, LabelKey, composeProjectId, composeServiceId, expectedContainerOwnershipLabels, expectedImageOwnershipLabels, expectedNetworkOwnershipLabels, ownershipMismatchDetail, readLabels, renderComposeContainerLabels, renderComposeNetworkLabels, renderContainerLabels, renderFilterArgs, renderNetworkLabels };
|
|
139
|
+
export { COMPOSE_UI_VERSION, ComposeLabelKey, LabelKey, composeProjectId, composeServiceId, expectedContainerOwnershipLabels, expectedImageOwnershipLabels, expectedNetworkOwnershipLabels, ownershipMismatchDetail, readLabels, renderComposeContainerLabels, renderComposeNetworkLabels, renderContainerLabels, renderFilterArgs, renderNetworkLabels, sanitizeTagSegment };
|
|
129
140
|
|
|
130
141
|
//# sourceMappingURL=labels.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"labels.mjs","names":[],"sources":["../../../src/runtime/docker/labels.ts"],"sourcesContent":["// Docker label contract.\n//\n// Architecture § Container runtime § Label-driven inventory:\n// Containers, images, networks, and volumes are enumerated by the\n// four canonical engine-level dimensions and a small set of\n// well-known role labels. Inventory NEVER greps stdout for names.\n//\n// The four canonical engine dimensions (no service names anywhere):\n//\n// - `devstack.app` — the app/project this resource belongs to\n// - `devstack.stack` — the stack name within that app\n// - `devstack.plugin` — the plugin key (opaque to the runtime)\n// - `devstack.role` — the resource's role within the plugin (also opaque)\n//\n// Plus two protocol-level labels for the lifecycle machinery:\n//\n// - `devstack.cycle` — the engine cycle id that last touched it\n// (sweep filters by this to find orphans)\n// - `devstack.managed` — set to `'true'` on every resource we own;\n// a coarse \"is this ours at all?\" filter\n//\n// Network / image / volume specializations carry only the canonical\n// engine dimensions plus a kind-specific marker.\n\nimport type { ContainerLabelTuple } from '../../contracts/snapshotable.ts';\n\n/** Canonical label keys. Single source of truth.\n *\n * L1 is plugin-blind: it carries the four canonical engine dimensions\n * (`app`/`stack`/`plugin`/`role`) plus generic resource-kind slots\n * (`kind`/`subkind`/`specVersion`). Orchestrators stamp their own\n * values into the generic slots — e.g. the router orchestrator stamps\n * `kind=router`, `subkind=<profile-id>`, `specVersion=<version>` —\n * those become orchestrator-side conventions rather than L1 vocabulary.\n * Per STYLE_GUIDE §7, L1 MUST NOT add orchestrator-named label keys. */\nexport const LabelKey = {\n\tapp: 'devstack.app',\n\tstack: 'devstack.stack',\n\tplugin: 'devstack.plugin',\n\trole: 'devstack.role',\n\tcycle: 'devstack.cycle',\n\tconfigHash: 'devstack.config-hash',\n\tmanaged: 'devstack.managed',\n\t// Resource-kind markers — coarse \"what is this?\" classification\n\t// for inventory walks.\n\tnetworkMarker: 'devstack.network',\n\tvolumeMarker: 'devstack.volume',\n\t// Generic kind/subkind slots — orchestrators stamp their own values\n\t// (e.g. router stamps `kind=router`, `subkind=<profile-id>`).\n\tkind: 'devstack.kind',\n\tsubkind: 'devstack.subkind',\n\tspecVersion: 'devstack.spec-version',\n} as const;\n\nexport type LabelKey = (typeof LabelKey)[keyof typeof LabelKey];\n\n/** Docker Compose-compatible labels used only for Docker Desktop grouping.\n * Cleanup and inventory remain keyed by `LabelKey` above. */\nexport const ComposeLabelKey = {\n\tproject: 'com.docker.compose.project',\n\tservice: 'com.docker.compose.service',\n\tcontainerNumber: 'com.docker.compose.container-number',\n\tversion: 'com.docker.compose.version',\n\toneoff: 'com.docker.compose.oneoff',\n\tnetwork: 'com.docker.compose.network',\n\tvolume: 'com.docker.compose.volume',\n} as const;\n\nexport type ComposeLabelKey = (typeof ComposeLabelKey)[keyof typeof ComposeLabelKey];\n\nexport const COMPOSE_UI_VERSION = '2.0.0';\n\nexport type ExpectedOwnershipLabels = Readonly<Record<string, string>>;\n\n/** Coerce a `docker inspect` `Labels` field (typed `Schema.Unknown`) into\n * a string→string map, dropping any non-string values. Shared by the\n * container / network / volume inspect readers — `docker inspect` reports\n * labels as an object whose values are always strings in practice, but the\n * schema keeps the field `Unknown` so a malformed daemon response cannot\n * blow up decode; this reader is the defensive narrowing. */\nexport const readLabels = (raw: unknown): Readonly<Record<string, string>> => {\n\tif (raw === null || typeof raw !== 'object') return {};\n\tconst out: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(raw)) {\n\t\tif (typeof value === 'string') out[key] = value;\n\t}\n\treturn out;\n};\n\nconst normalizeComposeSegment = (value: string): string => {\n\tconst normalized = value\n\t\t.trim()\n\t\t.replace(/[^a-zA-Z0-9_-]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '');\n\treturn normalized.length > 0 ? normalized : 'unnamed';\n};\n\nexport const composeProjectId = (app: string, stack: string): string =>\n\t`${normalizeComposeSegment(app)}-${normalizeComposeSegment(stack)}`;\n\nexport const composeServiceId = (tuple: ContainerLabelTuple): string =>\n\t`${normalizeComposeSegment(tuple.plugin)}.${normalizeComposeSegment(tuple.role)}`;\n\nexport const renderComposeContainerLabels = (tuple: ContainerLabelTuple): ReadonlyArray<string> => [\n\t`${ComposeLabelKey.project}=${composeProjectId(tuple.app, tuple.stack)}`,\n\t`${ComposeLabelKey.service}=${composeServiceId(tuple)}`,\n\t`${ComposeLabelKey.containerNumber}=1`,\n\t`${ComposeLabelKey.version}=${COMPOSE_UI_VERSION}`,\n\t`${ComposeLabelKey.oneoff}=False`,\n];\n\nexport const renderComposeNetworkLabels = (\n\tname: string,\n\tapp: string,\n\tstack: string,\n): ReadonlyArray<string> => [\n\t`${ComposeLabelKey.project}=${composeProjectId(app, stack)}`,\n\t`${ComposeLabelKey.network}=${name}`,\n\t`${ComposeLabelKey.version}=${COMPOSE_UI_VERSION}`,\n];\n\nexport const renderComposeVolumeLabels = (\n\tname: string,\n\ttuple: ContainerLabelTuple,\n): ReadonlyArray<string> => [\n\t`${ComposeLabelKey.project}=${composeProjectId(tuple.app, tuple.stack)}`,\n\t`${ComposeLabelKey.volume}=${name}`,\n\t`${ComposeLabelKey.version}=${COMPOSE_UI_VERSION}`,\n];\n\nexport const expectedContainerOwnershipLabels = (\n\ttuple: ContainerLabelTuple,\n): ExpectedOwnershipLabels => ({\n\t[LabelKey.managed]: 'true',\n\t[LabelKey.app]: tuple.app,\n\t[LabelKey.stack]: tuple.stack,\n\t[LabelKey.plugin]: tuple.plugin,\n\t[LabelKey.role]: tuple.role,\n});\n\nexport const expectedNetworkOwnershipLabels = (\n\tapp: string,\n\tstack: string,\n): ExpectedOwnershipLabels => ({\n\t[LabelKey.managed]: 'true',\n\t[LabelKey.networkMarker]: 'true',\n\t[LabelKey.app]: app,\n\t[LabelKey.stack]: stack,\n});\n\nexport const expectedVolumeOwnershipLabels = (\n\ttuple: ContainerLabelTuple,\n): ExpectedOwnershipLabels => ({\n\t[LabelKey.managed]: 'true',\n\t[LabelKey.volumeMarker]: 'true',\n\t[LabelKey.app]: tuple.app,\n\t[LabelKey.stack]: tuple.stack,\n\t[LabelKey.plugin]: tuple.plugin,\n\t[LabelKey.role]: tuple.role,\n});\n\n/** Labels stamped on `docker build` outputs so label-driven prune can\n * find the image. `plugin` / `role` are optional — the runtime\n * contract only requires `app` / `stack`. */\nexport const expectedImageOwnershipLabels = (owner: {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly plugin?: string;\n\treadonly role?: string;\n}): ExpectedOwnershipLabels => {\n\tconst out: Record<string, string> = {\n\t\t[LabelKey.managed]: 'true',\n\t\t[LabelKey.app]: owner.app,\n\t\t[LabelKey.stack]: owner.stack,\n\t};\n\tif (owner.plugin !== undefined) out[LabelKey.plugin] = owner.plugin;\n\tif (owner.role !== undefined) out[LabelKey.role] = owner.role;\n\treturn out;\n};\n\nexport const ownershipMismatchDetail = (\n\texpected: ExpectedOwnershipLabels,\n\tactual: Readonly<Record<string, string>>,\n): string | null => {\n\tconst mismatches: Array<string> = [];\n\tfor (const [key, expectedValue] of Object.entries(expected)) {\n\t\tconst actualValue = actual[key];\n\t\tif (actualValue !== expectedValue) {\n\t\t\tmismatches.push(`${key}: expected ${expectedValue}, found ${actualValue ?? '<missing>'}`);\n\t\t}\n\t}\n\treturn mismatches.length === 0 ? null : mismatches.join('; ');\n};\n\n/** Render a `ContainerLabelTuple` to docker `--label key=value` pairs.\n * The cycle/managed labels are stamped at create time by `container.ts`. */\nexport const renderContainerLabels = (\n\ttuple: ContainerLabelTuple,\n\tcycle: number,\n\textra: Readonly<Record<string, string>> = {},\n): ReadonlyArray<string> => {\n\tconst ownership = [\n\t\t...Object.entries(expectedContainerOwnershipLabels(tuple)).map(\n\t\t\t([key, value]) => `${key}=${value}`,\n\t\t),\n\t\t`${LabelKey.cycle}=${cycle}`,\n\t\t...Object.entries(extra).map(([key, value]) => `${key}=${value}`),\n\t];\n\treturn [...ownership, ...renderComposeContainerLabels(tuple)];\n};\n\n/** Render a label-tuple as `--filter label=key=value` args for `docker ps`,\n * `docker volume ls`, etc. Partial tuples allowed (sweep / inventory). */\nexport const renderFilterArgs = (\n\tmatch: Partial<ContainerLabelTuple>,\n\textra: Readonly<Record<string, string>> = {},\n): ReadonlyArray<string> => {\n\tconst filters: Array<string> = [`label=${LabelKey.managed}=true`];\n\tif (match.app !== undefined) filters.push(`label=${LabelKey.app}=${match.app}`);\n\tif (match.stack !== undefined) filters.push(`label=${LabelKey.stack}=${match.stack}`);\n\tif (match.plugin !== undefined) filters.push(`label=${LabelKey.plugin}=${match.plugin}`);\n\tif (match.role !== undefined) filters.push(`label=${LabelKey.role}=${match.role}`);\n\tfor (const [k, v] of Object.entries(extra)) filters.push(`label=${k}=${v}`);\n\treturn filters.flatMap((f) => ['--filter', f]);\n};\n\n/** Network-specific labels (shared `devstack` network is cross-stack;\n * per-stack networks would stamp the full tuple). */\nexport const renderNetworkLabels = (\n\tname: string,\n\tapp: string,\n\tstack: string,\n\topts: { readonly composeUi?: boolean } = {},\n): ReadonlyArray<string> => {\n\tconst ownership = Object.entries(expectedNetworkOwnershipLabels(app, stack)).map(\n\t\t([key, value]) => `${key}=${value}`,\n\t);\n\treturn opts.composeUi === false\n\t\t? ownership\n\t\t: [...ownership, ...renderComposeNetworkLabels(name, app, stack)];\n};\n\n/** Volume labels — pre-create with these before any `run -v` argv to\n * avoid docker's lazy unlabelled-create. */\nexport const renderVolumeLabels = (\n\tname: string,\n\ttuple: ContainerLabelTuple,\n): ReadonlyArray<string> => {\n\tconst ownership = Object.entries(expectedVolumeOwnershipLabels(tuple)).map(\n\t\t([key, value]) => `${key}=${value}`,\n\t);\n\treturn [...ownership, ...renderComposeVolumeLabels(name, tuple)];\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"labels.mjs","names":[],"sources":["../../../src/runtime/docker/labels.ts"],"sourcesContent":["// Docker label contract.\n//\n// Architecture § Container runtime § Label-driven inventory:\n// Containers, images, networks, and volumes are enumerated by the\n// four canonical engine-level dimensions and a small set of\n// well-known role labels. Inventory NEVER greps stdout for names.\n//\n// The four canonical engine dimensions (no service names anywhere):\n//\n// - `devstack.app` — the app/project this resource belongs to\n// - `devstack.stack` — the stack name within that app\n// - `devstack.plugin` — the plugin key (opaque to the runtime)\n// - `devstack.role` — the resource's role within the plugin (also opaque)\n//\n// Plus two protocol-level labels for the lifecycle machinery:\n//\n// - `devstack.cycle` — the engine cycle id that last touched it\n// (sweep filters by this to find orphans)\n// - `devstack.managed` — set to `'true'` on every resource we own;\n// a coarse \"is this ours at all?\" filter\n//\n// Network / image / volume specializations carry only the canonical\n// engine dimensions plus a kind-specific marker.\n\nimport type { ContainerLabelTuple } from '../../contracts/snapshotable.ts';\n\n/** Canonical label keys. Single source of truth.\n *\n * L1 is plugin-blind: it carries the four canonical engine dimensions\n * (`app`/`stack`/`plugin`/`role`) plus generic resource-kind slots\n * (`kind`/`subkind`/`specVersion`). Orchestrators stamp their own\n * values into the generic slots — e.g. the router orchestrator stamps\n * `kind=router`, `subkind=<profile-id>`, `specVersion=<version>` —\n * those become orchestrator-side conventions rather than L1 vocabulary.\n * Per STYLE_GUIDE §7, L1 MUST NOT add orchestrator-named label keys. */\nexport const LabelKey = {\n\tapp: 'devstack.app',\n\tstack: 'devstack.stack',\n\tplugin: 'devstack.plugin',\n\trole: 'devstack.role',\n\tcycle: 'devstack.cycle',\n\tconfigHash: 'devstack.config-hash',\n\tmanaged: 'devstack.managed',\n\t// Resource-kind markers — coarse \"what is this?\" classification\n\t// for inventory walks.\n\tnetworkMarker: 'devstack.network',\n\tvolumeMarker: 'devstack.volume',\n\t// Generic kind/subkind slots — orchestrators stamp their own values\n\t// (e.g. router stamps `kind=router`, `subkind=<profile-id>`).\n\tkind: 'devstack.kind',\n\tsubkind: 'devstack.subkind',\n\tspecVersion: 'devstack.spec-version',\n} as const;\n\nexport type LabelKey = (typeof LabelKey)[keyof typeof LabelKey];\n\n/** Docker Compose-compatible labels used only for Docker Desktop grouping.\n * Cleanup and inventory remain keyed by `LabelKey` above. */\nexport const ComposeLabelKey = {\n\tproject: 'com.docker.compose.project',\n\tservice: 'com.docker.compose.service',\n\tcontainerNumber: 'com.docker.compose.container-number',\n\tversion: 'com.docker.compose.version',\n\toneoff: 'com.docker.compose.oneoff',\n\tnetwork: 'com.docker.compose.network',\n\tvolume: 'com.docker.compose.volume',\n} as const;\n\nexport type ComposeLabelKey = (typeof ComposeLabelKey)[keyof typeof ComposeLabelKey];\n\nexport const COMPOSE_UI_VERSION = '2.0.0';\n\nexport type ExpectedOwnershipLabels = Readonly<Record<string, string>>;\n\n/** Coerce a `docker inspect` `Labels` field (typed `Schema.Unknown`) into\n * a string→string map, dropping any non-string values. Shared by the\n * container / network / volume inspect readers — `docker inspect` reports\n * labels as an object whose values are always strings in practice, but the\n * schema keeps the field `Unknown` so a malformed daemon response cannot\n * blow up decode; this reader is the defensive narrowing. */\nexport const readLabels = (raw: unknown): Readonly<Record<string, string>> => {\n\tif (raw === null || typeof raw !== 'object') return {};\n\tconst out: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(raw)) {\n\t\tif (typeof value === 'string') out[key] = value;\n\t}\n\treturn out;\n};\n\nconst normalizeComposeSegment = (value: string): string => {\n\tconst normalized = value\n\t\t.trim()\n\t\t.replace(/[^a-zA-Z0-9_-]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '');\n\treturn normalized.length > 0 ? normalized : 'unnamed';\n};\n\n/** Sanitize an `(app | stack)` identifier into a single Docker-tag-safe\n * segment. A Docker tag component is lowercase `[a-z0-9._-]` — this\n * lowercases, replaces every run of illegal characters with a single `-`,\n * and trims leading/trailing `-`/`.`/`_` separators so the result never\n * starts/ends with a separator (which Docker rejects as an invalid\n * reference) and two distinct ids cannot collapse to the empty string.\n * Used to scope the content-addressed build tag by `(app, stack)`. */\nexport const sanitizeTagSegment = (value: string): string => {\n\tconst normalized = value\n\t\t.trim()\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9._-]+/g, '-')\n\t\t.replace(/-+/g, '-')\n\t\t.replace(/^[-._]+|[-._]+$/g, '');\n\treturn normalized.length > 0 ? normalized : 'unnamed';\n};\n\nexport const composeProjectId = (app: string, stack: string): string =>\n\t`${normalizeComposeSegment(app)}-${normalizeComposeSegment(stack)}`;\n\nexport const composeServiceId = (tuple: ContainerLabelTuple): string =>\n\t`${normalizeComposeSegment(tuple.plugin)}.${normalizeComposeSegment(tuple.role)}`;\n\nexport const renderComposeContainerLabels = (tuple: ContainerLabelTuple): ReadonlyArray<string> => [\n\t`${ComposeLabelKey.project}=${composeProjectId(tuple.app, tuple.stack)}`,\n\t`${ComposeLabelKey.service}=${composeServiceId(tuple)}`,\n\t`${ComposeLabelKey.containerNumber}=1`,\n\t`${ComposeLabelKey.version}=${COMPOSE_UI_VERSION}`,\n\t`${ComposeLabelKey.oneoff}=False`,\n];\n\nexport const renderComposeNetworkLabels = (\n\tname: string,\n\tapp: string,\n\tstack: string,\n): ReadonlyArray<string> => [\n\t`${ComposeLabelKey.project}=${composeProjectId(app, stack)}`,\n\t`${ComposeLabelKey.network}=${name}`,\n\t`${ComposeLabelKey.version}=${COMPOSE_UI_VERSION}`,\n];\n\nexport const renderComposeVolumeLabels = (\n\tname: string,\n\ttuple: ContainerLabelTuple,\n): ReadonlyArray<string> => [\n\t`${ComposeLabelKey.project}=${composeProjectId(tuple.app, tuple.stack)}`,\n\t`${ComposeLabelKey.volume}=${name}`,\n\t`${ComposeLabelKey.version}=${COMPOSE_UI_VERSION}`,\n];\n\nexport const expectedContainerOwnershipLabels = (\n\ttuple: ContainerLabelTuple,\n): ExpectedOwnershipLabels => ({\n\t[LabelKey.managed]: 'true',\n\t[LabelKey.app]: tuple.app,\n\t[LabelKey.stack]: tuple.stack,\n\t[LabelKey.plugin]: tuple.plugin,\n\t[LabelKey.role]: tuple.role,\n});\n\nexport const expectedNetworkOwnershipLabels = (\n\tapp: string,\n\tstack: string,\n): ExpectedOwnershipLabels => ({\n\t[LabelKey.managed]: 'true',\n\t[LabelKey.networkMarker]: 'true',\n\t[LabelKey.app]: app,\n\t[LabelKey.stack]: stack,\n});\n\nexport const expectedVolumeOwnershipLabels = (\n\ttuple: ContainerLabelTuple,\n): ExpectedOwnershipLabels => ({\n\t[LabelKey.managed]: 'true',\n\t[LabelKey.volumeMarker]: 'true',\n\t[LabelKey.app]: tuple.app,\n\t[LabelKey.stack]: tuple.stack,\n\t[LabelKey.plugin]: tuple.plugin,\n\t[LabelKey.role]: tuple.role,\n});\n\n/** Labels stamped on `docker build` outputs so label-driven prune can\n * find the image. `plugin` / `role` are optional — the runtime\n * contract only requires `app` / `stack`. */\nexport const expectedImageOwnershipLabels = (owner: {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly plugin?: string;\n\treadonly role?: string;\n}): ExpectedOwnershipLabels => {\n\tconst out: Record<string, string> = {\n\t\t[LabelKey.managed]: 'true',\n\t\t[LabelKey.app]: owner.app,\n\t\t[LabelKey.stack]: owner.stack,\n\t};\n\tif (owner.plugin !== undefined) out[LabelKey.plugin] = owner.plugin;\n\tif (owner.role !== undefined) out[LabelKey.role] = owner.role;\n\treturn out;\n};\n\nexport const ownershipMismatchDetail = (\n\texpected: ExpectedOwnershipLabels,\n\tactual: Readonly<Record<string, string>>,\n): string | null => {\n\tconst mismatches: Array<string> = [];\n\tfor (const [key, expectedValue] of Object.entries(expected)) {\n\t\tconst actualValue = actual[key];\n\t\tif (actualValue !== expectedValue) {\n\t\t\tmismatches.push(`${key}: expected ${expectedValue}, found ${actualValue ?? '<missing>'}`);\n\t\t}\n\t}\n\treturn mismatches.length === 0 ? null : mismatches.join('; ');\n};\n\n/** Render a `ContainerLabelTuple` to docker `--label key=value` pairs.\n * The cycle/managed labels are stamped at create time by `container.ts`. */\nexport const renderContainerLabels = (\n\ttuple: ContainerLabelTuple,\n\tcycle: number,\n\textra: Readonly<Record<string, string>> = {},\n): ReadonlyArray<string> => {\n\tconst ownership = [\n\t\t...Object.entries(expectedContainerOwnershipLabels(tuple)).map(\n\t\t\t([key, value]) => `${key}=${value}`,\n\t\t),\n\t\t`${LabelKey.cycle}=${cycle}`,\n\t\t...Object.entries(extra).map(([key, value]) => `${key}=${value}`),\n\t];\n\treturn [...ownership, ...renderComposeContainerLabels(tuple)];\n};\n\n/** Render a label-tuple as `--filter label=key=value` args for `docker ps`,\n * `docker volume ls`, etc. Partial tuples allowed (sweep / inventory). */\nexport const renderFilterArgs = (\n\tmatch: Partial<ContainerLabelTuple>,\n\textra: Readonly<Record<string, string>> = {},\n): ReadonlyArray<string> => {\n\tconst filters: Array<string> = [`label=${LabelKey.managed}=true`];\n\tif (match.app !== undefined) filters.push(`label=${LabelKey.app}=${match.app}`);\n\tif (match.stack !== undefined) filters.push(`label=${LabelKey.stack}=${match.stack}`);\n\tif (match.plugin !== undefined) filters.push(`label=${LabelKey.plugin}=${match.plugin}`);\n\tif (match.role !== undefined) filters.push(`label=${LabelKey.role}=${match.role}`);\n\tfor (const [k, v] of Object.entries(extra)) filters.push(`label=${k}=${v}`);\n\treturn filters.flatMap((f) => ['--filter', f]);\n};\n\n/** Network-specific labels (shared `devstack` network is cross-stack;\n * per-stack networks would stamp the full tuple). */\nexport const renderNetworkLabels = (\n\tname: string,\n\tapp: string,\n\tstack: string,\n\topts: { readonly composeUi?: boolean } = {},\n): ReadonlyArray<string> => {\n\tconst ownership = Object.entries(expectedNetworkOwnershipLabels(app, stack)).map(\n\t\t([key, value]) => `${key}=${value}`,\n\t);\n\treturn opts.composeUi === false\n\t\t? ownership\n\t\t: [...ownership, ...renderComposeNetworkLabels(name, app, stack)];\n};\n\n/** Volume labels — pre-create with these before any `run -v` argv to\n * avoid docker's lazy unlabelled-create. */\nexport const renderVolumeLabels = (\n\tname: string,\n\ttuple: ContainerLabelTuple,\n): ReadonlyArray<string> => {\n\tconst ownership = Object.entries(expectedVolumeOwnershipLabels(tuple)).map(\n\t\t([key, value]) => `${key}=${value}`,\n\t);\n\treturn [...ownership, ...renderComposeVolumeLabels(name, tuple)];\n};\n"],"mappings":"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"}
|
|
@@ -73,7 +73,7 @@ const ensureNetwork = (name, opts) => Effect.gen(function* () {
|
|
|
73
73
|
...labelArgs,
|
|
74
74
|
name
|
|
75
75
|
]).pipe(Effect.mapError(wrapNetworkError("create", name)), Effect.catchTag("NetworkOperationFailed", (err) => err.op === "create" && isNetworkAlreadyExistsStderr(err.stderr) ? adoptExistingNetwork(name, opts, err) : Effect.fail(err)))).stdout.trim();
|
|
76
|
-
})
|
|
76
|
+
});
|
|
77
77
|
/** Concurrent-create-loses recovery: a peer created the network between
|
|
78
78
|
* our inspect-miss and our `create`. Re-inspect, assert it carries our
|
|
79
79
|
* ownership labels, and return its id wrapped as a synthetic create
|
|
@@ -105,7 +105,7 @@ const connect = (containerNameOrId, network, aliases) => Effect.gen(function* ()
|
|
|
105
105
|
network,
|
|
106
106
|
stderr: res.stderr
|
|
107
107
|
}));
|
|
108
|
-
})
|
|
108
|
+
});
|
|
109
109
|
/** Force-disconnect — `docker network disconnect -f`. Used by prune
|
|
110
110
|
* to evict our own endpoints from an in-use network before retrying
|
|
111
111
|
* `network rm`. Idempotent against "not connected". */
|
|
@@ -121,7 +121,7 @@ const forceDisconnect = (containerNameOrId, network) => Effect.gen(function* ()
|
|
|
121
121
|
network,
|
|
122
122
|
stderr: res.stderr
|
|
123
123
|
}));
|
|
124
|
-
})
|
|
124
|
+
});
|
|
125
125
|
/** List the endpoints currently attached to a network. Returns an
|
|
126
126
|
* empty list when the network is missing. Used by prune to decide
|
|
127
127
|
* whether to force-disconnect own endpoints or report foreign
|
|
@@ -137,7 +137,7 @@ const listAttachedContainers = (name) => Effect.gen(function* () {
|
|
|
137
137
|
});
|
|
138
138
|
if (decoded === null) return [];
|
|
139
139
|
return readContainers(decoded.Containers);
|
|
140
|
-
})
|
|
140
|
+
});
|
|
141
141
|
/** Schema of the small JSON slice of `docker inspect` we read for the
|
|
142
142
|
* network-membership / IP shape. */
|
|
143
143
|
const InspectNetworkSettings = Schema.Struct({ NetworkSettings: Schema.Struct({ Networks: Schema.Record(Schema.String, Schema.Struct({
|
|
@@ -179,7 +179,7 @@ const waitForIp = (containerNameOrId, network, opts = {}) => {
|
|
|
179
179
|
waitedMillis: Date.now() - start
|
|
180
180
|
});
|
|
181
181
|
return cause;
|
|
182
|
-
})
|
|
182
|
+
}));
|
|
183
183
|
};
|
|
184
184
|
/** Read out ALL networks + IPs for a container. Used by the contract's
|
|
185
185
|
* `ContainerHandle.ips`. */
|
|
@@ -197,7 +197,7 @@ const readIps = (containerNameOrId) => Effect.gen(function* () {
|
|
|
197
197
|
} catch {
|
|
198
198
|
return [];
|
|
199
199
|
}
|
|
200
|
-
})
|
|
200
|
+
});
|
|
201
201
|
//#endregion
|
|
202
202
|
export { connect, ensureNetwork, forceDisconnect, listAttachedContainers, readIps, waitForIp };
|
|
203
203
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network.mjs","names":[],"sources":["../../../src/runtime/docker/network.ts"],"sourcesContent":["// Docker network lifecycle.\n//\n// Architecture § Docker backend § Networking:\n// - ONE shared `devstack` network per host (cross-stack). Other\n// per-stack secondary networks are allowed by callers, but the\n// default surface assumes the shared bridge.\n// - `network connect` is idempotent — \"already exists in network\"\n// stderr is success (see `wrap.ts` classifier).\n// - `network connect` settle is ASYNCHRONOUS in docker — the\n// endpoint is registered before the IP is allocated. Callers must\n// wait for IP readback (see §5 and `waitForIp`).\n// - `network create` registers NO finalizer; the network outlives\n// individual stacks so resume into a stopped container reattaches\n// to the same bridge id. Cleanup is via `wipe` / `prune`.\n//\n// Surface:\n// - `ensureNetwork(name, opts)` — idempotent create\n// - `connect(container, network, alias?)` — idempotent attach\n// - `disconnect(container, network)` — idempotent detach\n// - `waitForIp(container, network, opts)` — bounded poll for IP\n\nimport { Effect, Schema } from 'effect';\n\nimport { DockerHost, DockerSpawner, dockerRun, dockerRunOk } from './client.ts';\nimport { ProbeTimeoutError, waitForProbe } from '../../substrate/runtime/probes.ts';\nimport { decodeJsonArrayElementSync } from '../../substrate/runtime/runtime-decode.ts';\nimport {\n\ttype DockerRuntimeError,\n\tForeignDockerResource,\n\tNetworkIpReadbackTimeout,\n\tNetworkOperationFailed,\n} from './errors.ts';\nimport { dockerInspectAndDecode } from './inspect-and-decode.ts';\nimport {\n\texpectedNetworkOwnershipLabels,\n\townershipMismatchDetail,\n\treadLabels,\n\trenderNetworkLabels,\n} from './labels.ts';\nimport {\n\tisAlreadyInNetworkStderr,\n\tisMissingNetworkStderr,\n\tisNetworkAlreadyExistsStderr,\n\twrapNetworkError,\n} from './wrap.ts';\n\n/** The cross-host shared network name. Architecture-mandated single\n * bridge per host. Callers can target other networks (per-stack) but\n * this is the constant for the shared one. */\nexport const SHARED_NETWORK_NAME = 'devstack';\n\nexport interface EnsureNetworkOptions {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly subnet?: string;\n\treadonly gateway?: string;\n\treadonly driver?: 'bridge' | 'host' | 'overlay';\n\treadonly composeUi?: boolean;\n}\n\ninterface NetworkInspectFacts {\n\treadonly id: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\nconst NetworkInspectSchema = Schema.Struct({\n\tId: Schema.String,\n\tLabels: Schema.optional(Schema.Unknown),\n\tContainers: Schema.optional(Schema.Unknown),\n});\n\n/** One endpoint attached to a network, as reported by `docker network\n * inspect`. `name` is the container name; `id` is the container id. */\nexport interface NetworkAttachedEndpoint {\n\treadonly id: string;\n\treadonly name: string;\n}\n\nconst readContainers = (raw: unknown): ReadonlyArray<NetworkAttachedEndpoint> => {\n\tif (raw === null || typeof raw !== 'object') return [];\n\tconst out: Array<NetworkAttachedEndpoint> = [];\n\tfor (const [id, value] of Object.entries(raw)) {\n\t\tif (value === null || typeof value !== 'object') continue;\n\t\tconst name = (value as { Name?: unknown }).Name;\n\t\tif (typeof name !== 'string' || name.length === 0) continue;\n\t\tout.push({ id, name });\n\t}\n\treturn out;\n};\n\nconst inspectNetwork = (\n\tname: string,\n): Effect.Effect<NetworkInspectFacts | null, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst decoded = yield* dockerInspectAndDecode({\n\t\t\tresourceKind: 'network',\n\t\t\tname,\n\t\t\top: 'docker.network.inspect',\n\t\t\tinspectCommand: dockerRunOk('network', ['inspect', name]).pipe(\n\t\t\t\tEffect.mapError(wrapNetworkError('inspect', name)),\n\t\t\t),\n\t\t\tschema: NetworkInspectSchema,\n\t\t\tisMissingStderr: isMissingNetworkStderr,\n\t\t});\n\t\tif (decoded === null) return null;\n\t\treturn { id: decoded.Id, labels: readLabels(decoded.Labels) };\n\t});\n\nconst assertNetworkOwned = (\n\tname: string,\n\tfacts: NetworkInspectFacts,\n\topts: EnsureNetworkOptions,\n): Effect.Effect<void, DockerRuntimeError> =>\n\tEffect.gen(function* () {\n\t\tconst expected = expectedNetworkOwnershipLabels(opts.app, opts.stack);\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: 'network',\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// -----------------------------------------------------------------------------\n// Create / inspect\n// -----------------------------------------------------------------------------\n\n/** Idempotent `docker network create`. Returns the network's id (or\n * the pre-existing id). Architecture: NO scope finalizer — networks\n * outlive the supervisor. */\nexport const ensureNetwork = (\n\tname: string,\n\topts: EnsureNetworkOptions,\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst probe = yield* inspectNetwork(name);\n\t\tif (probe !== null) {\n\t\t\tyield* assertNetworkOwned(name, probe, opts);\n\t\t\treturn probe.id;\n\t\t}\n\t\t// Create with full label stamp.\n\t\tconst labelArgs = renderNetworkLabels(name, opts.app, opts.stack, {\n\t\t\tcomposeUi: opts.composeUi,\n\t\t}).flatMap((l) => ['--label', l]);\n\t\tconst driverArgs = opts.driver ? ['--driver', opts.driver] : [];\n\t\tconst subnetArgs = opts.subnet ? ['--subnet', opts.subnet] : [];\n\t\tconst gatewayArgs = opts.gateway ? ['--gateway', opts.gateway] : [];\n\t\tconst created = yield* dockerRun('network', [\n\t\t\t'create',\n\t\t\t...driverArgs,\n\t\t\t...subnetArgs,\n\t\t\t...gatewayArgs,\n\t\t\t...labelArgs,\n\t\t\tname,\n\t\t]).pipe(\n\t\t\tEffect.mapError(wrapNetworkError('create', name)),\n\t\t\t// `docker network create` is NOT name-atomic and — unlike\n\t\t\t// containers, which lean on `--name` atomicity plus a per-name\n\t\t\t// lock — networks have no per-name lock. Two processes booting\n\t\t\t// against the shared `devstack` bridge can both pass the\n\t\t\t// inspect-miss above and both run `create`; the loser's daemon\n\t\t\t// stderr (\"network with name … already exists\" / a `Conflict`\n\t\t\t// envelope) is classified by `wrapNetworkError` as a generic\n\t\t\t// `NetworkOperationFailed`. Recover that ONE case here by\n\t\t\t// re-inspecting and adopting the winner's network — but only if\n\t\t\t// it carries OUR ownership labels. This mirrors the container\n\t\t\t// `startAndAdopt` adopt-on-collision shape: recover from the\n\t\t\t// typed collision error, re-read facts, assert ownership, then\n\t\t\t// return the existing id; a foreign/unowned network surfaces the\n\t\t\t// ownership error rather than being silently adopted.\n\t\t\tEffect.catchTag('NetworkOperationFailed', (err) =>\n\t\t\t\terr.op === 'create' && isNetworkAlreadyExistsStderr(err.stderr)\n\t\t\t\t\t? adoptExistingNetwork(name, opts, err)\n\t\t\t\t\t: Effect.fail(err),\n\t\t\t),\n\t\t);\n\t\treturn created.stdout.trim();\n\t}).pipe(Effect.withSpan('runtime.docker.network.ensure'));\n\n/** Concurrent-create-loses recovery: a peer created the network between\n * our inspect-miss and our `create`. Re-inspect, assert it carries our\n * ownership labels, and return its id wrapped as a synthetic create\n * `CaptureResult` (so the caller's `.stdout.trim()` reads the adopted\n * id). A foreign/unowned network surfaces the ownership error; a vanished\n * network (peer pruned it between create and re-inspect) re-surfaces the\n * original typed create failure for the boot to retry. */\nconst adoptExistingNetwork = (\n\tname: string,\n\topts: EnsureNetworkOptions,\n\tcreateFailure: NetworkOperationFailed,\n): Effect.Effect<{ readonly stdout: string }, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst adopted = yield* inspectNetwork(name);\n\t\tif (adopted === null) return yield* Effect.fail(createFailure);\n\t\tyield* assertNetworkOwned(name, adopted, opts);\n\t\treturn { stdout: adopted.id };\n\t});\n\n// -----------------------------------------------------------------------------\n// Connect / disconnect\n// -----------------------------------------------------------------------------\n\n/** Idempotent `docker network connect`. Treats \"already exists in\n * network\" stderr as success — architecture mandates idempotency on\n * this verb. Multiple `--alias` flags register additional DNS names\n * siblings can dial under the network. */\nexport const connect = (\n\tcontainerNameOrId: string,\n\tnetwork: string,\n\taliases?: ReadonlyArray<string>,\n): Effect.Effect<void, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst aliasArgs = (aliases ?? []).flatMap((alias) => ['--alias', alias]);\n\t\tconst res = yield* dockerRunOk('network', [\n\t\t\t'connect',\n\t\t\t...aliasArgs,\n\t\t\tnetwork,\n\t\t\tcontainerNameOrId,\n\t\t]).pipe(Effect.mapError(wrapNetworkError('connect', network)));\n\t\tif (res.exitCode === 0) return;\n\t\tif (isAlreadyInNetworkStderr(res.stderr)) return;\n\t\t// Non-idempotent failure path.\n\t\treturn yield* Effect.fail(\n\t\t\tnew NetworkOperationFailed({ op: 'connect', network, stderr: res.stderr }),\n\t\t);\n\t}).pipe(Effect.withSpan('runtime.docker.network.connect'));\n\nexport const disconnect = (\n\tcontainerNameOrId: string,\n\tnetwork: string,\n): Effect.Effect<void, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('network', ['disconnect', network, containerNameOrId]).pipe(\n\t\t\tEffect.mapError(wrapNetworkError('disconnect', network)),\n\t\t);\n\t\t// \"is not connected\" is also idempotent success on the\n\t\t// disconnect side; we treat any non-fatal stderr as success here\n\t\t// since the postcondition (not connected) holds.\n\t\tif (res.exitCode !== 0 && !/is not connected/i.test(res.stderr)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew NetworkOperationFailed({ op: 'disconnect', network, stderr: res.stderr }),\n\t\t\t);\n\t\t}\n\t}).pipe(Effect.withSpan('runtime.docker.network.disconnect'));\n\n/** Force-disconnect — `docker network disconnect -f`. Used by prune\n * to evict our own endpoints from an in-use network before retrying\n * `network rm`. Idempotent against \"not connected\". */\nexport const forceDisconnect = (\n\tcontainerNameOrId: string,\n\tnetwork: string,\n): Effect.Effect<void, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('network', [\n\t\t\t'disconnect',\n\t\t\t'-f',\n\t\t\tnetwork,\n\t\t\tcontainerNameOrId,\n\t\t]).pipe(Effect.mapError(wrapNetworkError('disconnect', network)));\n\t\tif (res.exitCode !== 0 && !/is not connected/i.test(res.stderr)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew NetworkOperationFailed({ op: 'disconnect', network, stderr: res.stderr }),\n\t\t\t);\n\t\t}\n\t}).pipe(Effect.withSpan('runtime.docker.network.forceDisconnect'));\n\n/** List the endpoints currently attached to a network. Returns an\n * empty list when the network is missing. Used by prune to decide\n * whether to force-disconnect own endpoints or report foreign\n * holders. */\nexport const listAttachedContainers = (\n\tname: string,\n): Effect.Effect<\n\tReadonlyArray<NetworkAttachedEndpoint>,\n\tDockerRuntimeError,\n\tDockerHost | DockerSpawner\n> =>\n\tEffect.gen(function* () {\n\t\tconst decoded = yield* dockerInspectAndDecode({\n\t\t\tresourceKind: 'network',\n\t\t\tname,\n\t\t\top: 'docker.network.inspect',\n\t\t\tinspectCommand: dockerRunOk('network', ['inspect', name]).pipe(\n\t\t\t\tEffect.mapError(wrapNetworkError('inspect', name)),\n\t\t\t),\n\t\t\tschema: NetworkInspectSchema,\n\t\t\tisMissingStderr: isMissingNetworkStderr,\n\t\t});\n\t\tif (decoded === null) return [];\n\t\treturn readContainers(decoded.Containers);\n\t}).pipe(Effect.withSpan('runtime.docker.network.listAttachedContainers'));\n\n// -----------------------------------------------------------------------------\n// IP readback\n// -----------------------------------------------------------------------------\n\n/** Schema of the small JSON slice of `docker inspect` we read for the\n * network-membership / IP shape. */\nconst InspectNetworkSettings = Schema.Struct({\n\tNetworkSettings: Schema.Struct({\n\t\tNetworks: Schema.Record(\n\t\t\tSchema.String,\n\t\t\tSchema.Struct({\n\t\t\t\tIPAddress: Schema.optional(Schema.String),\n\t\t\t\tNetworkID: Schema.optional(Schema.String),\n\t\t\t}),\n\t\t),\n\t}),\n});\n\nconst DEFAULT_IP_POLL_BUDGET_MILLIS = 3_000;\nconst DEFAULT_IP_POLL_INTERVAL_MILLIS = 100;\n\nexport interface WaitForIpOptions {\n\treadonly budgetMillis?: number;\n\treadonly intervalMillis?: number;\n}\n\n/** Poll `docker inspect` until the container has a non-empty IP on\n * `network`. Architecture §5 — async network-connect settle. */\nexport const waitForIp = (\n\tcontainerNameOrId: string,\n\tnetwork: string,\n\topts: WaitForIpOptions = {},\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> => {\n\tconst budget = opts.budgetMillis ?? DEFAULT_IP_POLL_BUDGET_MILLIS;\n\tconst interval = opts.intervalMillis ?? DEFAULT_IP_POLL_INTERVAL_MILLIS;\n\tconst start = Date.now();\n\tlet foundIp: string | undefined;\n\treturn waitForProbe({\n\t\tlabel: `docker.network.ip:${containerNameOrId}:${network}`,\n\t\ttimeoutMs: budget,\n\t\tintervalMs: interval,\n\t\tisRetryableError: () => false,\n\t\tprobe: () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst res = yield* dockerRunOk('inspect', [containerNameOrId]).pipe(\n\t\t\t\t\tEffect.mapError(wrapNetworkError('inspect', network)),\n\t\t\t\t);\n\t\t\t\tif (res.exitCode === 0) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst decoded = decodeJsonArrayElementSync(InspectNetworkSettings, res.stdout, {\n\t\t\t\t\t\t\tsource: `docker inspect ${containerNameOrId}`,\n\t\t\t\t\t\t\tmkError: (issue) => issue,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst slot = decoded.NetworkSettings.Networks[network];\n\t\t\t\t\t\tconst ip = slot?.IPAddress;\n\t\t\t\t\t\tif (ip && ip.length > 0) {\n\t\t\t\t\t\t\tfoundIp = ip;\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Malformed — fall through to retry.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}),\n\t}).pipe(\n\t\tEffect.map(() => foundIp ?? ''),\n\t\tEffect.mapError((cause) => {\n\t\t\tif (cause instanceof ProbeTimeoutError) {\n\t\t\t\treturn new NetworkIpReadbackTimeout({\n\t\t\t\t\tcontainer: containerNameOrId,\n\t\t\t\t\tnetwork,\n\t\t\t\t\twaitedMillis: Date.now() - start,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn cause;\n\t\t}),\n\t\tEffect.withSpan('runtime.docker.network.waitForIp'),\n\t);\n};\n\n/** Read out ALL networks + IPs for a container. Used by the contract's\n * `ContainerHandle.ips`. */\nexport const readIps = (\n\tcontainerNameOrId: string,\n): Effect.Effect<ReadonlyArray<string>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('inspect', [containerNameOrId]).pipe(\n\t\t\tEffect.mapError(wrapNetworkError('inspect', containerNameOrId)),\n\t\t);\n\t\tif (res.exitCode !== 0) return [];\n\t\ttry {\n\t\t\tconst decoded = decodeJsonArrayElementSync(InspectNetworkSettings, res.stdout, {\n\t\t\t\tsource: `docker inspect ${containerNameOrId}`,\n\t\t\t\tmkError: (issue) => issue,\n\t\t\t});\n\t\t\tconst ips: Array<string> = [];\n\t\t\tfor (const slot of Object.values(decoded.NetworkSettings.Networks)) {\n\t\t\t\tif (slot.IPAddress && slot.IPAddress.length > 0) ips.push(slot.IPAddress);\n\t\t\t}\n\t\t\treturn ips;\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}).pipe(Effect.withSpan('runtime.docker.network.readIps'));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"network.mjs","names":[],"sources":["../../../src/runtime/docker/network.ts"],"sourcesContent":["// Docker network lifecycle.\n//\n// Architecture § Docker backend § Networking:\n// - Per-stack networks: each stack ensures its own bridge, named\n// `<app>-<stack>-…`. Callers may attach secondary networks too.\n// - `network connect` is idempotent — \"already exists in network\"\n// stderr is success (see `wrap.ts` classifier).\n// - `network connect` settle is ASYNCHRONOUS in docker — the\n// endpoint is registered before the IP is allocated. Callers must\n// wait for IP readback (see §5 and `waitForIp`).\n// - `network create` registers NO finalizer; the network outlives\n// individual stacks so resume into a stopped container reattaches\n// to the same bridge id. Cleanup is via `wipe` / `prune`.\n//\n// Surface:\n// - `ensureNetwork(name, opts)` — idempotent create\n// - `connect(container, network, alias?)` — idempotent attach\n// - `disconnect(container, network)` — idempotent detach\n// - `waitForIp(container, network, opts)` — bounded poll for IP\n\nimport { Effect, Schema } from 'effect';\n\nimport { DockerHost, DockerSpawner, dockerRun, dockerRunOk } from './client.ts';\nimport { ProbeTimeoutError, waitForProbe } from '../../substrate/runtime/probes.ts';\nimport { decodeJsonArrayElementSync } from '../../substrate/runtime/runtime-decode.ts';\nimport {\n\ttype DockerRuntimeError,\n\tForeignDockerResource,\n\tNetworkIpReadbackTimeout,\n\tNetworkOperationFailed,\n} from './errors.ts';\nimport { dockerInspectAndDecode } from './inspect-and-decode.ts';\nimport {\n\texpectedNetworkOwnershipLabels,\n\townershipMismatchDetail,\n\treadLabels,\n\trenderNetworkLabels,\n} from './labels.ts';\nimport {\n\tisAlreadyInNetworkStderr,\n\tisMissingNetworkStderr,\n\tisNetworkAlreadyExistsStderr,\n\twrapNetworkError,\n} from './wrap.ts';\n\nexport interface EnsureNetworkOptions {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly subnet?: string;\n\treadonly gateway?: string;\n\treadonly driver?: 'bridge' | 'host' | 'overlay';\n\treadonly composeUi?: boolean;\n}\n\ninterface NetworkInspectFacts {\n\treadonly id: string;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\nconst NetworkInspectSchema = Schema.Struct({\n\tId: Schema.String,\n\tLabels: Schema.optional(Schema.Unknown),\n\tContainers: Schema.optional(Schema.Unknown),\n});\n\n/** One endpoint attached to a network, as reported by `docker network\n * inspect`. `name` is the container name; `id` is the container id. */\nexport interface NetworkAttachedEndpoint {\n\treadonly id: string;\n\treadonly name: string;\n}\n\nconst readContainers = (raw: unknown): ReadonlyArray<NetworkAttachedEndpoint> => {\n\tif (raw === null || typeof raw !== 'object') return [];\n\tconst out: Array<NetworkAttachedEndpoint> = [];\n\tfor (const [id, value] of Object.entries(raw)) {\n\t\tif (value === null || typeof value !== 'object') continue;\n\t\tconst name = (value as { Name?: unknown }).Name;\n\t\tif (typeof name !== 'string' || name.length === 0) continue;\n\t\tout.push({ id, name });\n\t}\n\treturn out;\n};\n\nconst inspectNetwork = (\n\tname: string,\n): Effect.Effect<NetworkInspectFacts | null, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst decoded = yield* dockerInspectAndDecode({\n\t\t\tresourceKind: 'network',\n\t\t\tname,\n\t\t\top: 'docker.network.inspect',\n\t\t\tinspectCommand: dockerRunOk('network', ['inspect', name]).pipe(\n\t\t\t\tEffect.mapError(wrapNetworkError('inspect', name)),\n\t\t\t),\n\t\t\tschema: NetworkInspectSchema,\n\t\t\tisMissingStderr: isMissingNetworkStderr,\n\t\t});\n\t\tif (decoded === null) return null;\n\t\treturn { id: decoded.Id, labels: readLabels(decoded.Labels) };\n\t});\n\nconst assertNetworkOwned = (\n\tname: string,\n\tfacts: NetworkInspectFacts,\n\topts: EnsureNetworkOptions,\n): Effect.Effect<void, DockerRuntimeError> =>\n\tEffect.gen(function* () {\n\t\tconst expected = expectedNetworkOwnershipLabels(opts.app, opts.stack);\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: 'network',\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// -----------------------------------------------------------------------------\n// Create / inspect\n// -----------------------------------------------------------------------------\n\n/** Idempotent `docker network create`. Returns the network's id (or\n * the pre-existing id). Architecture: NO scope finalizer — networks\n * outlive the supervisor. */\nexport const ensureNetwork = (\n\tname: string,\n\topts: EnsureNetworkOptions,\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst probe = yield* inspectNetwork(name);\n\t\tif (probe !== null) {\n\t\t\tyield* assertNetworkOwned(name, probe, opts);\n\t\t\treturn probe.id;\n\t\t}\n\t\t// Create with full label stamp.\n\t\tconst labelArgs = renderNetworkLabels(name, opts.app, opts.stack, {\n\t\t\tcomposeUi: opts.composeUi,\n\t\t}).flatMap((l) => ['--label', l]);\n\t\tconst driverArgs = opts.driver ? ['--driver', opts.driver] : [];\n\t\tconst subnetArgs = opts.subnet ? ['--subnet', opts.subnet] : [];\n\t\tconst gatewayArgs = opts.gateway ? ['--gateway', opts.gateway] : [];\n\t\tconst created = yield* dockerRun('network', [\n\t\t\t'create',\n\t\t\t...driverArgs,\n\t\t\t...subnetArgs,\n\t\t\t...gatewayArgs,\n\t\t\t...labelArgs,\n\t\t\tname,\n\t\t]).pipe(\n\t\t\tEffect.mapError(wrapNetworkError('create', name)),\n\t\t\t// `docker network create` is NOT name-atomic and — unlike\n\t\t\t// containers, which lean on `--name` atomicity plus a per-name\n\t\t\t// lock — networks have no per-name lock. Two processes booting\n\t\t\t// against the shared `devstack` bridge can both pass the\n\t\t\t// inspect-miss above and both run `create`; the loser's daemon\n\t\t\t// stderr (\"network with name … already exists\" / a `Conflict`\n\t\t\t// envelope) is classified by `wrapNetworkError` as a generic\n\t\t\t// `NetworkOperationFailed`. Recover that ONE case here by\n\t\t\t// re-inspecting and adopting the winner's network — but only if\n\t\t\t// it carries OUR ownership labels. This mirrors the container\n\t\t\t// `startAndAdopt` adopt-on-collision shape: recover from the\n\t\t\t// typed collision error, re-read facts, assert ownership, then\n\t\t\t// return the existing id; a foreign/unowned network surfaces the\n\t\t\t// ownership error rather than being silently adopted.\n\t\t\tEffect.catchTag('NetworkOperationFailed', (err) =>\n\t\t\t\terr.op === 'create' && isNetworkAlreadyExistsStderr(err.stderr)\n\t\t\t\t\t? adoptExistingNetwork(name, opts, err)\n\t\t\t\t\t: Effect.fail(err),\n\t\t\t),\n\t\t);\n\t\treturn created.stdout.trim();\n\t});\n\n/** Concurrent-create-loses recovery: a peer created the network between\n * our inspect-miss and our `create`. Re-inspect, assert it carries our\n * ownership labels, and return its id wrapped as a synthetic create\n * `CaptureResult` (so the caller's `.stdout.trim()` reads the adopted\n * id). A foreign/unowned network surfaces the ownership error; a vanished\n * network (peer pruned it between create and re-inspect) re-surfaces the\n * original typed create failure for the boot to retry. */\nconst adoptExistingNetwork = (\n\tname: string,\n\topts: EnsureNetworkOptions,\n\tcreateFailure: NetworkOperationFailed,\n): Effect.Effect<{ readonly stdout: string }, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst adopted = yield* inspectNetwork(name);\n\t\tif (adopted === null) return yield* Effect.fail(createFailure);\n\t\tyield* assertNetworkOwned(name, adopted, opts);\n\t\treturn { stdout: adopted.id };\n\t});\n\n// -----------------------------------------------------------------------------\n// Connect / disconnect\n// -----------------------------------------------------------------------------\n\n/** Idempotent `docker network connect`. Treats \"already exists in\n * network\" stderr as success — architecture mandates idempotency on\n * this verb. Multiple `--alias` flags register additional DNS names\n * siblings can dial under the network. */\nexport const connect = (\n\tcontainerNameOrId: string,\n\tnetwork: string,\n\taliases?: ReadonlyArray<string>,\n): Effect.Effect<void, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst aliasArgs = (aliases ?? []).flatMap((alias) => ['--alias', alias]);\n\t\tconst res = yield* dockerRunOk('network', [\n\t\t\t'connect',\n\t\t\t...aliasArgs,\n\t\t\tnetwork,\n\t\t\tcontainerNameOrId,\n\t\t]).pipe(Effect.mapError(wrapNetworkError('connect', network)));\n\t\tif (res.exitCode === 0) return;\n\t\tif (isAlreadyInNetworkStderr(res.stderr)) return;\n\t\t// Non-idempotent failure path.\n\t\treturn yield* Effect.fail(\n\t\t\tnew NetworkOperationFailed({ op: 'connect', network, stderr: res.stderr }),\n\t\t);\n\t});\n\nexport const disconnect = (\n\tcontainerNameOrId: string,\n\tnetwork: string,\n): Effect.Effect<void, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('network', ['disconnect', network, containerNameOrId]).pipe(\n\t\t\tEffect.mapError(wrapNetworkError('disconnect', network)),\n\t\t);\n\t\t// \"is not connected\" is also idempotent success on the\n\t\t// disconnect side; we treat any non-fatal stderr as success here\n\t\t// since the postcondition (not connected) holds.\n\t\tif (res.exitCode !== 0 && !/is not connected/i.test(res.stderr)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew NetworkOperationFailed({ op: 'disconnect', network, stderr: res.stderr }),\n\t\t\t);\n\t\t}\n\t});\n\n/** Force-disconnect — `docker network disconnect -f`. Used by prune\n * to evict our own endpoints from an in-use network before retrying\n * `network rm`. Idempotent against \"not connected\". */\nexport const forceDisconnect = (\n\tcontainerNameOrId: string,\n\tnetwork: string,\n): Effect.Effect<void, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('network', [\n\t\t\t'disconnect',\n\t\t\t'-f',\n\t\t\tnetwork,\n\t\t\tcontainerNameOrId,\n\t\t]).pipe(Effect.mapError(wrapNetworkError('disconnect', network)));\n\t\tif (res.exitCode !== 0 && !/is not connected/i.test(res.stderr)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew NetworkOperationFailed({ op: 'disconnect', network, stderr: res.stderr }),\n\t\t\t);\n\t\t}\n\t});\n\n/** List the endpoints currently attached to a network. Returns an\n * empty list when the network is missing. Used by prune to decide\n * whether to force-disconnect own endpoints or report foreign\n * holders. */\nexport const listAttachedContainers = (\n\tname: string,\n): Effect.Effect<\n\tReadonlyArray<NetworkAttachedEndpoint>,\n\tDockerRuntimeError,\n\tDockerHost | DockerSpawner\n> =>\n\tEffect.gen(function* () {\n\t\tconst decoded = yield* dockerInspectAndDecode({\n\t\t\tresourceKind: 'network',\n\t\t\tname,\n\t\t\top: 'docker.network.inspect',\n\t\t\tinspectCommand: dockerRunOk('network', ['inspect', name]).pipe(\n\t\t\t\tEffect.mapError(wrapNetworkError('inspect', name)),\n\t\t\t),\n\t\t\tschema: NetworkInspectSchema,\n\t\t\tisMissingStderr: isMissingNetworkStderr,\n\t\t});\n\t\tif (decoded === null) return [];\n\t\treturn readContainers(decoded.Containers);\n\t});\n\n// -----------------------------------------------------------------------------\n// IP readback\n// -----------------------------------------------------------------------------\n\n/** Schema of the small JSON slice of `docker inspect` we read for the\n * network-membership / IP shape. */\nconst InspectNetworkSettings = Schema.Struct({\n\tNetworkSettings: Schema.Struct({\n\t\tNetworks: Schema.Record(\n\t\t\tSchema.String,\n\t\t\tSchema.Struct({\n\t\t\t\tIPAddress: Schema.optional(Schema.String),\n\t\t\t\tNetworkID: Schema.optional(Schema.String),\n\t\t\t}),\n\t\t),\n\t}),\n});\n\nconst DEFAULT_IP_POLL_BUDGET_MILLIS = 3_000;\nconst DEFAULT_IP_POLL_INTERVAL_MILLIS = 100;\n\nexport interface WaitForIpOptions {\n\treadonly budgetMillis?: number;\n\treadonly intervalMillis?: number;\n}\n\n/** Poll `docker inspect` until the container has a non-empty IP on\n * `network`. Architecture §5 — async network-connect settle. */\nexport const waitForIp = (\n\tcontainerNameOrId: string,\n\tnetwork: string,\n\topts: WaitForIpOptions = {},\n): Effect.Effect<string, DockerRuntimeError, DockerHost | DockerSpawner> => {\n\tconst budget = opts.budgetMillis ?? DEFAULT_IP_POLL_BUDGET_MILLIS;\n\tconst interval = opts.intervalMillis ?? DEFAULT_IP_POLL_INTERVAL_MILLIS;\n\tconst start = Date.now();\n\tlet foundIp: string | undefined;\n\treturn waitForProbe({\n\t\tlabel: `docker.network.ip:${containerNameOrId}:${network}`,\n\t\ttimeoutMs: budget,\n\t\tintervalMs: interval,\n\t\tisRetryableError: () => false,\n\t\tprobe: () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst res = yield* dockerRunOk('inspect', [containerNameOrId]).pipe(\n\t\t\t\t\tEffect.mapError(wrapNetworkError('inspect', network)),\n\t\t\t\t);\n\t\t\t\tif (res.exitCode === 0) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst decoded = decodeJsonArrayElementSync(InspectNetworkSettings, res.stdout, {\n\t\t\t\t\t\t\tsource: `docker inspect ${containerNameOrId}`,\n\t\t\t\t\t\t\tmkError: (issue) => issue,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst slot = decoded.NetworkSettings.Networks[network];\n\t\t\t\t\t\tconst ip = slot?.IPAddress;\n\t\t\t\t\t\tif (ip && ip.length > 0) {\n\t\t\t\t\t\t\tfoundIp = ip;\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Malformed — fall through to retry.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}),\n\t}).pipe(\n\t\tEffect.map(() => foundIp ?? ''),\n\t\tEffect.mapError((cause) => {\n\t\t\tif (cause instanceof ProbeTimeoutError) {\n\t\t\t\treturn new NetworkIpReadbackTimeout({\n\t\t\t\t\tcontainer: containerNameOrId,\n\t\t\t\t\tnetwork,\n\t\t\t\t\twaitedMillis: Date.now() - start,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn cause;\n\t\t}),\n\t);\n};\n\n/** Read out ALL networks + IPs for a container. Used by the contract's\n * `ContainerHandle.ips`. */\nexport const readIps = (\n\tcontainerNameOrId: string,\n): Effect.Effect<ReadonlyArray<string>, DockerRuntimeError, DockerHost | DockerSpawner> =>\n\tEffect.gen(function* () {\n\t\tconst res = yield* dockerRunOk('inspect', [containerNameOrId]).pipe(\n\t\t\tEffect.mapError(wrapNetworkError('inspect', containerNameOrId)),\n\t\t);\n\t\tif (res.exitCode !== 0) return [];\n\t\ttry {\n\t\t\tconst decoded = decodeJsonArrayElementSync(InspectNetworkSettings, res.stdout, {\n\t\t\t\tsource: `docker inspect ${containerNameOrId}`,\n\t\t\t\tmkError: (issue) => issue,\n\t\t\t});\n\t\t\tconst ips: Array<string> = [];\n\t\t\tfor (const slot of Object.values(decoded.NetworkSettings.Networks)) {\n\t\t\t\tif (slot.IPAddress && slot.IPAddress.length > 0) ips.push(slot.IPAddress);\n\t\t\t}\n\t\t\treturn ips;\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t});\n"],"mappings":"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"}
|