@mysten-incubation/devstack 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dashboard-ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dashboard-ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dashboard-ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dashboard-ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
- package/dashboard-ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dashboard-ui/assets/index-Bmi1UtAg.js +1356 -0
- package/dashboard-ui/assets/index-D5EShVt4.js +3 -0
- package/dashboard-ui/assets/index-Deml9drg.css +1 -0
- package/dashboard-ui/index.html +13 -0
- package/dist/api/define-capabilities.d.mts +14 -3
- package/dist/api/define-capabilities.mjs +18 -1
- package/dist/api/define-capabilities.mjs.map +1 -1
- package/dist/api/define-devstack.d.mts +1 -1
- package/dist/api/define-devstack.mjs +6 -26
- package/dist/api/define-devstack.mjs.map +1 -1
- package/dist/api/define-plugin.d.mts +1 -0
- package/dist/api/define-plugin.mjs +2 -0
- package/dist/api/inference-network.d.mts +132 -0
- package/dist/api/inference-network.mjs +105 -16
- package/dist/api/inference-network.mjs.map +1 -1
- package/dist/api/plugin-errors.d.mts +7 -0
- package/dist/api/run-stack.d.mts +20 -9
- package/dist/api/run-stack.mjs +56 -29
- package/dist/api/run-stack.mjs.map +1 -1
- package/dist/build-integrations/playwright/config.d.mts +6 -4
- package/dist/build-integrations/playwright/config.mjs +5 -3
- package/dist/build-integrations/playwright/config.mjs.map +1 -1
- package/dist/build-integrations/playwright/errors.d.mts +1 -1
- package/dist/build-integrations/playwright/global-setup.d.mts +43 -17
- package/dist/build-integrations/playwright/global-setup.mjs +250 -13
- package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
- package/dist/build-integrations/playwright/index.d.mts +5 -3
- package/dist/build-integrations/playwright/index.mjs +4 -2
- package/dist/build-integrations/playwright/stack-context.d.mts +0 -1
- package/dist/build-integrations/playwright/stack-context.mjs +24 -63
- package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
- package/dist/build-integrations/playwright/wallet-context.d.mts +7 -10
- package/dist/build-integrations/playwright/wallet-context.mjs +8 -13
- package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/cold-start-url.d.mts +5 -0
- package/dist/build-integrations/runtime/cold-start-url.mjs +4 -3
- package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
- package/dist/build-integrations/runtime/conventional-routes.d.mts +51 -0
- package/dist/build-integrations/runtime/conventional-routes.mjs +92 -0
- package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -0
- package/dist/build-integrations/runtime/discover.d.mts +38 -30
- package/dist/build-integrations/runtime/discover.mjs +73 -70
- package/dist/build-integrations/runtime/discover.mjs.map +1 -1
- package/dist/build-integrations/runtime/endpoint-registry.d.mts +3 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs +3 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
- package/dist/build-integrations/runtime/index.d.mts +7 -2
- package/dist/build-integrations/runtime/index.mjs +9 -2
- package/dist/build-integrations/runtime/manifest-types.d.mts +2 -0
- package/dist/build-integrations/runtime/manifest-types.mjs +3 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.d.mts +29 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs +10 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs.map +1 -0
- package/dist/build-integrations/runtime/read-stack-context.d.mts +1 -1
- package/dist/build-integrations/runtime/read-stack-context.mjs +10 -4
- package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +48 -0
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs +42 -0
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -0
- package/dist/build-integrations/runtime/stack-context.d.mts +1 -1
- package/dist/build-integrations/runtime/wallet-paths.mjs +2 -0
- package/dist/build-integrations/vite/index.d.mts +51 -0
- package/dist/build-integrations/vite/index.mjs +73 -0
- package/dist/build-integrations/vite/index.mjs.map +1 -0
- package/dist/build-integrations/vitest/env.d.mts +4 -3
- package/dist/build-integrations/vitest/env.mjs +6 -4
- package/dist/build-integrations/vitest/env.mjs.map +1 -1
- package/dist/build-integrations/vitest/errors.d.mts +25 -4
- package/dist/build-integrations/vitest/errors.mjs +15 -2
- package/dist/build-integrations/vitest/errors.mjs.map +1 -1
- package/dist/build-integrations/vitest/index.d.mts +2 -2
- package/dist/build-integrations/vitest/index.mjs +3 -3
- package/dist/build-integrations/vitest/setup.d.mts +4 -2
- package/dist/build-integrations/vitest/setup.mjs +2 -2
- package/dist/build-integrations/vitest/setup.mjs.map +1 -1
- package/dist/build-integrations/vitest/stack-context.d.mts +0 -1
- package/dist/build-integrations/vitest/stack-context.mjs +33 -13
- package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
- package/dist/cli/bail.mjs +23 -0
- package/dist/cli/bail.mjs.map +1 -0
- package/dist/{surfaces/cli/commands → cli}/doctor-probes.mjs +26 -16
- package/dist/cli/doctor-probes.mjs.map +1 -0
- package/dist/cli/main.d.mts +15 -1
- package/dist/cli/main.mjs +166 -482
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/prune-direct.mjs +31 -201
- package/dist/cli/prune-direct.mjs.map +1 -1
- package/dist/cli/snapshot-reader.mjs +14 -11
- package/dist/cli/snapshot-reader.mjs.map +1 -1
- package/dist/cli/up-lifecycle.mjs +4 -1
- package/dist/cli/up-lifecycle.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +95 -0
- package/dist/cli/wirings/apply.mjs.map +1 -0
- package/dist/cli/wirings/build-verb-layers.mjs +42 -0
- package/dist/cli/wirings/build-verb-layers.mjs.map +1 -0
- package/dist/cli/wirings/config-loader.mjs +69 -0
- package/dist/cli/wirings/config-loader.mjs.map +1 -0
- package/dist/cli/wirings/engine-command.mjs +30 -0
- package/dist/cli/wirings/engine-command.mjs.map +1 -0
- package/dist/cli/wirings/identity.mjs +72 -0
- package/dist/cli/wirings/identity.mjs.map +1 -0
- package/dist/cli/wirings/provide-file-system.mjs +7 -0
- package/dist/cli/wirings/provide-file-system.mjs.map +1 -0
- package/dist/cli/wirings/snapshot.mjs +177 -0
- package/dist/cli/wirings/snapshot.mjs.map +1 -0
- package/dist/cli/wirings/up.mjs +287 -0
- package/dist/cli/wirings/up.mjs.map +1 -0
- package/dist/cli/wirings/wipe.mjs +40 -0
- package/dist/cli/wirings/wipe.mjs.map +1 -0
- package/dist/contracts/capability-decl.d.mts +0 -2
- package/dist/contracts/codegenable.d.mts +43 -0
- package/dist/contracts/container-runtime.d.mts +26 -1
- package/dist/contracts/faucet-strategy.d.mts +25 -0
- package/dist/contracts/funding-strategy.d.mts +42 -0
- package/dist/contracts/plugin-expander.mjs +60 -0
- package/dist/contracts/plugin-expander.mjs.map +1 -0
- package/dist/contracts/projection.d.mts +5 -3
- package/dist/contracts/renderer.d.mts +23 -0
- package/dist/contracts/routable.d.mts +14 -1
- package/dist/contracts/snapshotable.d.mts +25 -3
- package/dist/contracts/wallet-protocol.d.mts +52 -0
- package/dist/contracts/wallet-protocol.mjs +52 -0
- package/dist/contracts/wallet-protocol.mjs.map +1 -0
- package/dist/index.d.mts +26 -25
- package/dist/index.mjs +20 -13
- package/dist/{runtime → orchestrators}/built-in-plugin-layers.mjs +10 -5
- package/dist/orchestrators/built-in-plugin-layers.mjs.map +1 -0
- package/dist/orchestrators/codegen/bindings.d.mts +1 -2
- package/dist/orchestrators/codegen/bindings.mjs +52 -186
- package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
- package/dist/orchestrators/codegen/emit.mjs +19 -1
- package/dist/orchestrators/codegen/emit.mjs.map +1 -1
- package/dist/orchestrators/codegen/errors.mjs +8 -3
- package/dist/orchestrators/codegen/errors.mjs.map +1 -1
- package/dist/orchestrators/codegen/format.mjs +47 -39
- package/dist/orchestrators/codegen/format.mjs.map +1 -1
- package/dist/orchestrators/codegen/gitignore.mjs +3 -1
- package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
- package/dist/orchestrators/codegen/output-location.mjs +40 -0
- package/dist/orchestrators/codegen/output-location.mjs.map +1 -0
- package/dist/orchestrators/codegen/paths.d.mts +1 -1
- package/dist/orchestrators/codegen/paths.mjs +40 -14
- package/dist/orchestrators/codegen/paths.mjs.map +1 -1
- package/dist/orchestrators/codegen/permissions.mjs +3 -1
- package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
- package/dist/orchestrators/codegen/service.mjs +157 -105
- package/dist/orchestrators/codegen/service.mjs.map +1 -1
- package/dist/orchestrators/lifecycle-prune/errors.mjs +31 -0
- package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -0
- package/dist/orchestrators/lifecycle-prune/index.mjs +350 -0
- package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -0
- package/dist/orchestrators/router/cleanup.mjs +26 -27
- package/dist/orchestrators/router/cleanup.mjs.map +1 -1
- package/dist/orchestrators/router/entrypoints.mjs +2 -2
- package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
- package/dist/orchestrators/router/file-provider.mjs +14 -5
- package/dist/orchestrators/router/file-provider.mjs.map +1 -1
- package/dist/orchestrators/router/hostname.mjs +19 -55
- package/dist/orchestrators/router/hostname.mjs.map +1 -1
- package/dist/orchestrators/router/index.mjs +11 -0
- package/dist/orchestrators/router/profile.mjs +2 -2
- package/dist/orchestrators/router/sentinels.mjs +26 -0
- package/dist/orchestrators/router/sentinels.mjs.map +1 -0
- package/dist/orchestrators/router/service.mjs +40 -37
- package/dist/orchestrators/router/service.mjs.map +1 -1
- package/dist/orchestrators/router/traefik-container.mjs +27 -16
- package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
- package/dist/{substrate/runtime → orchestrators}/run.mjs +26 -21
- package/dist/orchestrators/run.mjs.map +1 -0
- package/dist/orchestrators/runtime-composition.d.mts +1 -1
- package/dist/orchestrators/runtime-composition.mjs +123 -39
- package/dist/orchestrators/runtime-composition.mjs.map +1 -1
- package/dist/orchestrators/snapshot/capture-command.d.mts +1 -0
- package/dist/orchestrators/snapshot/capture-command.mjs +25 -0
- package/dist/orchestrators/snapshot/capture-command.mjs.map +1 -0
- package/dist/orchestrators/snapshot/capture.mjs +42 -28
- package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
- package/dist/orchestrators/snapshot/descriptor.mjs +35 -10
- package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
- package/dist/orchestrators/snapshot/identity-guard.mjs +1 -1
- package/dist/orchestrators/snapshot/index.mjs +13 -0
- package/dist/orchestrators/snapshot/integrity.d.mts +1 -1
- package/dist/orchestrators/snapshot/integrity.mjs +73 -17
- package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
- package/dist/orchestrators/snapshot/pending-marker.d.mts +1 -0
- package/dist/orchestrators/snapshot/pending-marker.mjs +99 -0
- package/dist/orchestrators/snapshot/pending-marker.mjs.map +1 -0
- package/dist/orchestrators/snapshot/phase-error.mjs +23 -0
- package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -0
- package/dist/orchestrators/snapshot/prune.mjs +20 -55
- package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
- package/dist/orchestrators/snapshot/recover-pending.d.mts +1 -0
- package/dist/orchestrators/snapshot/recover-pending.mjs +223 -0
- package/dist/orchestrators/snapshot/recover-pending.mjs.map +1 -0
- package/dist/orchestrators/snapshot/restore.mjs +118 -84
- package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
- package/dist/orchestrators/snapshot/service.mjs +53 -56
- package/dist/orchestrators/snapshot/service.mjs.map +1 -1
- package/dist/orchestrators/snapshot/state-document.d.mts +1 -0
- package/dist/orchestrators/snapshot/state-document.mjs +47 -7
- package/dist/orchestrators/snapshot/state-document.mjs.map +1 -1
- package/dist/orchestrators/snapshot/wipe.mjs +67 -10
- package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
- package/dist/plugins/account/codegen.mjs +9 -8
- package/dist/plugins/account/codegen.mjs.map +1 -1
- package/dist/plugins/account/errors.d.mts +33 -6
- package/dist/plugins/account/errors.mjs.map +1 -1
- package/dist/plugins/account/funding.d.mts +29 -19
- package/dist/plugins/account/funding.mjs +84 -66
- package/dist/plugins/account/funding.mjs.map +1 -1
- package/dist/plugins/account/index.d.mts +2 -4
- package/dist/plugins/account/index.mjs +64 -31
- package/dist/plugins/account/index.mjs.map +1 -1
- package/dist/plugins/account/keypair.mjs +6 -9
- package/dist/plugins/account/keypair.mjs.map +1 -1
- package/dist/plugins/account/lease.mjs +1 -0
- package/dist/plugins/account/lease.mjs.map +1 -1
- package/dist/plugins/account/registry.d.mts +14 -1
- package/dist/plugins/account/registry.mjs +18 -18
- package/dist/plugins/account/registry.mjs.map +1 -1
- package/dist/plugins/account/service.d.mts +23 -4
- package/dist/plugins/account/service.mjs +83 -45
- package/dist/plugins/account/service.mjs.map +1 -1
- package/dist/plugins/account/snapshot.mjs +2 -5
- package/dist/plugins/account/snapshot.mjs.map +1 -1
- package/dist/plugins/account/spans.mjs +16 -0
- package/dist/plugins/account/spans.mjs.map +1 -0
- package/dist/plugins/account/variants/keystore.mjs +6 -8
- package/dist/plugins/account/variants/keystore.mjs.map +1 -1
- package/dist/plugins/action/build-context.d.mts +3 -2
- package/dist/plugins/action/errors.d.mts +16 -12
- package/dist/plugins/action/errors.mjs.map +1 -1
- package/dist/plugins/action/execute.mjs +65 -103
- package/dist/plugins/action/execute.mjs.map +1 -1
- package/dist/plugins/action/index.d.mts +0 -2
- package/dist/plugins/action/index.mjs +17 -13
- package/dist/plugins/action/index.mjs.map +1 -1
- package/dist/plugins/action/service.mjs +38 -35
- package/dist/plugins/action/service.mjs.map +1 -1
- package/dist/plugins/action/spans.mjs +11 -0
- package/dist/plugins/action/spans.mjs.map +1 -0
- package/dist/plugins/coin/codegen.mjs +9 -8
- package/dist/plugins/coin/codegen.mjs.map +1 -1
- package/dist/plugins/coin/discovery.mjs +8 -4
- package/dist/plugins/coin/discovery.mjs.map +1 -1
- package/dist/plugins/coin/index.d.mts +11 -29
- package/dist/plugins/coin/index.mjs +56 -27
- package/dist/plugins/coin/index.mjs.map +1 -1
- package/dist/plugins/coin/metadata.mjs +8 -15
- package/dist/plugins/coin/metadata.mjs.map +1 -1
- package/dist/plugins/coin/mint.d.mts +30 -11
- package/dist/plugins/coin/mint.mjs +78 -74
- package/dist/plugins/coin/mint.mjs.map +1 -1
- package/dist/plugins/coin/registry.mjs +4 -7
- package/dist/plugins/coin/registry.mjs.map +1 -1
- package/dist/plugins/coin/service.d.mts +39 -1
- package/dist/plugins/coin/service.mjs +11 -4
- package/dist/plugins/coin/service.mjs.map +1 -1
- package/dist/plugins/coin/spans.mjs +20 -0
- package/dist/plugins/coin/spans.mjs.map +1 -0
- package/dist/plugins/dashboard/domain.mjs +402 -0
- package/dist/plugins/dashboard/domain.mjs.map +1 -0
- package/dist/plugins/dashboard/index.d.mts +29 -0
- package/dist/plugins/dashboard/index.mjs +91 -0
- package/dist/plugins/dashboard/index.mjs.map +1 -0
- package/dist/plugins/dashboard/origin-policy.mjs +36 -0
- package/dist/plugins/dashboard/origin-policy.mjs.map +1 -0
- package/dist/plugins/dashboard/routable.mjs +27 -0
- package/dist/plugins/dashboard/routable.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/builder.mjs +21 -0
- package/dist/plugins/dashboard/schema/builder.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/enums.mjs +72 -0
- package/dist/plugins/dashboard/schema/enums.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/root.mjs +351 -0
- package/dist/plugins/dashboard/schema/root.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/types.mjs +508 -0
- package/dist/plugins/dashboard/schema/types.mjs.map +1 -0
- package/dist/plugins/dashboard/schema.mjs +11 -0
- package/dist/plugins/dashboard/schema.mjs.map +1 -0
- package/dist/plugins/dashboard/server.mjs +166 -0
- package/dist/plugins/dashboard/server.mjs.map +1 -0
- package/dist/plugins/deepbook/deploy.mjs +53 -115
- package/dist/plugins/deepbook/deploy.mjs.map +1 -1
- package/dist/plugins/deepbook/errors.mjs +1 -0
- package/dist/plugins/deepbook/errors.mjs.map +1 -1
- package/dist/plugins/deepbook/faucet-strategy.mjs +47 -34
- package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/deepbook/hash.mjs +9 -0
- package/dist/plugins/deepbook/hash.mjs.map +1 -0
- package/dist/plugins/deepbook/index.d.mts +1 -10
- package/dist/plugins/deepbook/index.mjs +25 -22
- package/dist/plugins/deepbook/index.mjs.map +1 -1
- package/dist/plugins/deepbook/pyth/index.mjs +26 -55
- package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
- package/dist/plugins/deepbook/snapshot.mjs +17 -29
- package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
- package/dist/plugins/deepbook/spans.mjs +18 -0
- package/dist/plugins/deepbook/spans.mjs.map +1 -0
- package/dist/plugins/deepbook/type-strings.mjs +65 -0
- package/dist/plugins/deepbook/type-strings.mjs.map +1 -0
- package/dist/plugins/deepbook/types.d.mts +2 -3
- package/dist/plugins/deepbook/types.mjs.map +1 -1
- package/dist/plugins/faucet/dispatcher.d.mts +21 -2
- package/dist/plugins/faucet/dispatcher.mjs +1 -2
- package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
- package/dist/plugins/faucet/errors.d.mts +11 -24
- package/dist/plugins/faucet/errors.mjs.map +1 -1
- package/dist/plugins/faucet/http.mjs +33 -14
- package/dist/plugins/faucet/http.mjs.map +1 -1
- package/dist/plugins/faucet/index.d.mts +15 -5
- package/dist/plugins/faucet/index.mjs +3 -0
- package/dist/plugins/faucet/index.mjs.map +1 -1
- package/dist/plugins/faucet/spans.mjs +12 -0
- package/dist/plugins/faucet/spans.mjs.map +1 -0
- package/dist/plugins/host-service/errors.d.mts +1 -1
- package/dist/plugins/host-service/index.mjs +34 -8
- package/dist/plugins/host-service/index.mjs.map +1 -1
- package/dist/plugins/host-service/routable.mjs +2 -1
- package/dist/plugins/host-service/routable.mjs.map +1 -1
- package/dist/plugins/host-service/service.d.mts +21 -0
- package/dist/plugins/host-service/service.mjs +136 -81
- package/dist/plugins/host-service/service.mjs.map +1 -1
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs +23 -0
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -0
- package/dist/plugins/internal/codegen-helpers.mjs +18 -0
- package/dist/plugins/internal/codegen-helpers.mjs.map +1 -0
- package/dist/plugins/internal/funding-failure-error.mjs +32 -0
- package/dist/plugins/internal/funding-failure-error.mjs.map +1 -0
- package/dist/plugins/package/build.mjs +1 -1
- package/dist/plugins/package/build.mjs.map +1 -1
- package/dist/plugins/package/codegen.mjs +26 -0
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/errors.d.mts +16 -6
- package/dist/plugins/package/errors.mjs.map +1 -1
- package/dist/plugins/package/index.d.mts +0 -4
- package/dist/plugins/package/index.mjs +27 -21
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/package/mode-known.mjs +26 -12
- package/dist/plugins/package/mode-known.mjs.map +1 -1
- package/dist/plugins/package/mode-local.mjs +47 -41
- package/dist/plugins/package/mode-local.mjs.map +1 -1
- package/dist/plugins/package/publish-executor.mjs +55 -114
- package/dist/plugins/package/publish-executor.mjs.map +1 -1
- package/dist/plugins/package/registry.mjs +2 -1
- package/dist/plugins/package/registry.mjs.map +1 -1
- package/dist/plugins/package/service.mjs.map +1 -1
- package/dist/plugins/package/spans.mjs +14 -0
- package/dist/plugins/package/spans.mjs.map +1 -0
- package/dist/plugins/postgres/db-ensure.mjs +19 -12
- package/dist/plugins/postgres/db-ensure.mjs.map +1 -1
- package/dist/plugins/postgres/errors.d.mts +5 -0
- package/dist/plugins/postgres/errors.mjs.map +1 -1
- package/dist/plugins/postgres/index.mjs +10 -17
- package/dist/plugins/postgres/index.mjs.map +1 -1
- package/dist/plugins/postgres/service.mjs +58 -24
- package/dist/plugins/postgres/service.mjs.map +1 -1
- package/dist/plugins/postgres/snapshot.mjs.map +1 -1
- package/dist/plugins/postgres/spans.mjs +11 -0
- package/dist/plugins/postgres/spans.mjs.map +1 -0
- package/dist/plugins/router-entrypoints.mjs +4 -4
- package/dist/plugins/router-entrypoints.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +15 -13
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +15 -19
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
- package/dist/plugins/seal/codegen.d.mts +2 -3
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/config-render.mjs +35 -8
- package/dist/plugins/seal/config-render.mjs.map +1 -1
- package/dist/plugins/seal/deploy.mjs +31 -39
- package/dist/plugins/seal/deploy.mjs.map +1 -1
- package/dist/plugins/seal/errors.d.mts +1 -3
- package/dist/plugins/seal/errors.mjs +7 -1
- package/dist/plugins/seal/errors.mjs.map +1 -1
- package/dist/plugins/seal/index.d.mts +1 -6
- package/dist/plugins/seal/index.mjs +40 -14
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/seal/key-manager.d.mts +1 -9
- package/dist/plugins/seal/key-manager.mjs +2 -18
- package/dist/plugins/seal/key-manager.mjs.map +1 -1
- package/dist/plugins/seal/key-server.mjs +37 -36
- package/dist/plugins/seal/key-server.mjs.map +1 -1
- package/dist/plugins/seal/keygen.mjs +16 -17
- package/dist/plugins/seal/keygen.mjs.map +1 -1
- package/dist/plugins/seal/mode/fork-known.mjs +36 -14
- package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
- package/dist/plugins/seal/mode/live.d.mts +6 -4
- package/dist/plugins/seal/mode/live.mjs +14 -21
- package/dist/plugins/seal/mode/live.mjs.map +1 -1
- package/dist/plugins/seal/mode/local-keygen.mjs +57 -21
- package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
- package/dist/plugins/seal/registry-publish.mjs +1 -0
- package/dist/plugins/seal/registry-publish.mjs.map +1 -1
- package/dist/plugins/seal/routable.mjs.map +1 -1
- package/dist/plugins/seal/service.mjs.map +1 -1
- package/dist/plugins/seal/spans.mjs +18 -0
- package/dist/plugins/seal/spans.mjs.map +1 -0
- package/dist/plugins/sui/auto-tick.mjs +17 -23
- package/dist/plugins/sui/auto-tick.mjs.map +1 -1
- package/dist/plugins/sui/chain-build-container.mjs +18 -0
- package/dist/plugins/sui/chain-build-container.mjs.map +1 -0
- package/dist/plugins/sui/chain-probe.d.mts +5 -5
- package/dist/plugins/sui/chain-probe.mjs +28 -58
- package/dist/plugins/sui/chain-probe.mjs.map +1 -1
- package/dist/plugins/sui/codegen.mjs +26 -0
- package/dist/plugins/sui/codegen.mjs.map +1 -1
- package/dist/plugins/sui/errors.d.mts +2 -29
- package/dist/plugins/sui/errors.mjs +1 -3
- package/dist/plugins/sui/errors.mjs.map +1 -1
- package/dist/plugins/sui/fork-faucet-strategy.d.mts +1 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs +55 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -0
- package/dist/plugins/sui/fork-orchestration.d.mts +1 -0
- package/dist/plugins/sui/fork-orchestration.mjs +170 -2
- package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
- package/dist/plugins/sui/fork-transaction.d.mts +2 -0
- package/dist/plugins/sui/fork-transaction.mjs +92 -28
- package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
- package/dist/plugins/sui/index.d.mts +33 -70
- package/dist/plugins/sui/index.mjs +58 -35
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/plugins/{faucet/strategies/sui-local.mjs → sui/local-faucet-strategy.mjs} +10 -7
- package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -0
- package/dist/plugins/sui/mode/external.mjs +7 -13
- package/dist/plugins/sui/mode/external.mjs.map +1 -1
- package/dist/plugins/sui/mode/fork.mjs +136 -59
- package/dist/plugins/sui/mode/fork.mjs.map +1 -1
- package/dist/plugins/sui/mode/live.mjs +10 -16
- package/dist/plugins/sui/mode/live.mjs.map +1 -1
- package/dist/plugins/sui/mode/local.mjs +37 -36
- package/dist/plugins/sui/mode/local.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared-boot.mjs +38 -115
- package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared.d.mts +1 -5
- package/dist/plugins/sui/mode/shared.mjs.map +1 -1
- package/dist/plugins/sui/mode/spec.d.mts +18 -0
- package/dist/plugins/sui/move-summary-runner.mjs +207 -0
- package/dist/plugins/sui/move-summary-runner.mjs.map +1 -0
- package/dist/plugins/sui/network-resolver.d.mts +4 -3
- package/dist/plugins/sui/service.mjs.map +1 -1
- package/dist/plugins/sui/snapshot.mjs +3 -0
- package/dist/plugins/sui/snapshot.mjs.map +1 -1
- package/dist/plugins/sui/spans.mjs +17 -0
- package/dist/plugins/sui/spans.mjs.map +1 -0
- package/dist/plugins/wallet/codegen.d.mts +0 -1
- package/dist/plugins/wallet/codegen.mjs +8 -9
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/index.d.mts +6 -8
- package/dist/plugins/wallet/index.mjs +46 -35
- package/dist/plugins/wallet/index.mjs.map +1 -1
- package/dist/plugins/wallet/origin-policy.mjs +12 -28
- package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
- package/dist/plugins/wallet/pairing.mjs +6 -4
- package/dist/plugins/wallet/pairing.mjs.map +1 -1
- package/dist/plugins/wallet/protocol.d.mts +1 -0
- package/dist/plugins/wallet/protocol.mjs +5 -44
- package/dist/plugins/wallet/protocol.mjs.map +1 -1
- package/dist/plugins/wallet/routable.mjs +2 -8
- package/dist/plugins/wallet/routable.mjs.map +1 -1
- package/dist/plugins/wallet/server.mjs +36 -44
- package/dist/plugins/wallet/server.mjs.map +1 -1
- package/dist/plugins/wallet/service.d.mts +1 -5
- package/dist/plugins/wallet/service.mjs +22 -18
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/plugins/wallet/spans.mjs +22 -0
- package/dist/plugins/wallet/spans.mjs.map +1 -0
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +33 -8
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/walrus/codegen.d.mts +3 -3
- package/dist/plugins/walrus/codegen.mjs.map +1 -1
- package/dist/plugins/walrus/deploy-paths.mjs +21 -5
- package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
- package/dist/plugins/walrus/deploy.mjs +67 -69
- package/dist/plugins/walrus/deploy.mjs.map +1 -1
- package/dist/plugins/walrus/errors.mjs +1 -0
- package/dist/plugins/walrus/errors.mjs.map +1 -1
- package/dist/plugins/walrus/faucet-strategy.mjs +12 -7
- package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/walrus/index.d.mts +2 -13
- package/dist/plugins/walrus/index.mjs +18 -23
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/plugins/walrus/mode/known-deploy.mjs +7 -5
- package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
- package/dist/plugins/walrus/mode/local-cluster.mjs +26 -7
- package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
- package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
- package/dist/plugins/walrus/routable.mjs +7 -23
- package/dist/plugins/walrus/routable.mjs.map +1 -1
- package/dist/plugins/walrus/service.mjs.map +1 -1
- package/dist/plugins/walrus/spans.mjs +18 -0
- package/dist/plugins/walrus/spans.mjs.map +1 -0
- package/dist/plugins/walrus/storage-nodes.d.mts +6 -2
- package/dist/plugins/walrus/storage-nodes.mjs +15 -8
- package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
- package/dist/plugins/walrus/wal-swap.mjs +58 -15
- package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
- package/dist/primitives/artifact-publisher.d.mts +1 -1
- package/dist/primitives/artifact-publisher.mjs +15 -0
- package/dist/primitives/artifact-publisher.mjs.map +1 -0
- package/dist/runtime/docker/client.mjs +17 -4
- package/dist/runtime/docker/client.mjs.map +1 -1
- package/dist/runtime/docker/container.mjs +219 -132
- package/dist/runtime/docker/container.mjs.map +1 -1
- package/dist/runtime/docker/errors.mjs +0 -6
- package/dist/runtime/docker/errors.mjs.map +1 -1
- package/dist/runtime/docker/exec.mjs +21 -18
- package/dist/runtime/docker/exec.mjs.map +1 -1
- package/dist/runtime/docker/image.mjs +36 -13
- package/dist/runtime/docker/image.mjs.map +1 -1
- package/dist/runtime/docker/index.mjs +14 -0
- package/dist/runtime/docker/inspect-and-decode.mjs +51 -0
- package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -0
- package/dist/runtime/docker/inventory.mjs +14 -11
- package/dist/runtime/docker/inventory.mjs.map +1 -1
- package/dist/runtime/docker/labels.mjs +38 -5
- package/dist/runtime/docker/labels.mjs.map +1 -1
- package/dist/runtime/docker/logs.d.mts +1 -1
- package/dist/runtime/docker/logs.mjs.map +1 -1
- package/dist/runtime/docker/network.mjs +83 -47
- package/dist/runtime/docker/network.mjs.map +1 -1
- package/dist/runtime/docker/render-run-args.mjs +82 -0
- package/dist/runtime/docker/render-run-args.mjs.map +1 -0
- package/dist/runtime/docker/service.d.mts +1 -1
- package/dist/runtime/docker/service.mjs +12 -7
- package/dist/runtime/docker/service.mjs.map +1 -1
- package/dist/runtime/docker/sweep.mjs +118 -43
- package/dist/runtime/docker/sweep.mjs.map +1 -1
- package/dist/runtime/docker/volume.mjs +2 -0
- package/dist/runtime/docker/volume.mjs.map +1 -1
- package/dist/runtime/docker/wrap.mjs +30 -1
- package/dist/runtime/docker/wrap.mjs.map +1 -1
- package/dist/substrate/cross-process.mjs +14 -8
- package/dist/substrate/cross-process.mjs.map +1 -1
- package/dist/substrate/event-time.mjs +33 -0
- package/dist/substrate/event-time.mjs.map +1 -0
- package/dist/substrate/events.d.mts +28 -8
- package/dist/substrate/identity.d.mts +2 -1
- package/dist/substrate/manifest.d.mts +68 -5
- package/dist/substrate/manifest.mjs +34 -5
- package/dist/substrate/manifest.mjs.map +1 -1
- package/dist/substrate/network.d.mts +3 -5
- package/dist/substrate/options.d.mts +0 -2
- package/dist/substrate/plugin.d.mts +13 -0
- package/dist/substrate/plugin.mjs +4 -2
- package/dist/substrate/plugin.mjs.map +1 -1
- package/dist/substrate/projection.d.mts +35 -3
- package/dist/substrate/runtime/artifact-publisher/index.mjs +19 -9
- package/dist/substrate/runtime/artifact-publisher/index.mjs.map +1 -1
- package/dist/substrate/runtime/atomic-write.mjs +97 -32
- package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
- package/dist/substrate/runtime/cache/index.mjs +3 -0
- package/dist/substrate/runtime/cache/schema.mjs +2 -2
- package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
- package/dist/substrate/runtime/cache/service.mjs +9 -7
- package/dist/substrate/runtime/cache/service.mjs.map +1 -1
- package/dist/substrate/runtime/capability-sinks/index.d.mts +1 -1
- package/dist/substrate/runtime/capability-sinks/index.mjs +3 -0
- package/dist/substrate/runtime/capability-sinks/layer.mjs +1 -1
- package/dist/substrate/runtime/capability-sinks/service.d.mts +61 -2
- package/dist/substrate/runtime/capability-sinks/service.mjs +21 -26
- package/dist/substrate/runtime/capability-sinks/service.mjs.map +1 -1
- package/dist/substrate/runtime/config-validation.d.mts +1 -13
- package/dist/substrate/runtime/config-validation.mjs +2 -91
- package/dist/substrate/runtime/config-validation.mjs.map +1 -1
- package/dist/substrate/runtime/container-runtime.d.mts +1 -0
- package/dist/substrate/runtime/container-runtime.mjs +2 -0
- package/dist/substrate/runtime/control-plane/domain.mjs +101 -0
- package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -0
- package/dist/substrate/runtime/control-plane/service.mjs +7 -0
- package/dist/substrate/runtime/control-plane/service.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +46 -19
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs +30 -12
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/index.mjs +5 -0
- package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs +11 -2
- package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs +1 -1
- package/dist/substrate/runtime/cross-process/index.mjs +11 -0
- package/dist/substrate/runtime/cross-process/live-clock.mjs +34 -0
- package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/liveness.mjs +52 -10
- package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/lock.d.mts +1 -0
- package/dist/substrate/runtime/cross-process/lock.mjs +23 -0
- package/dist/substrate/runtime/cross-process/lock.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +64 -0
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/roster.mjs +97 -83
- package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/self-pid.mjs +8 -0
- package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +24 -28
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/stack-lock.mjs +41 -20
- package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
- package/dist/substrate/runtime/current-plugin.mjs +2 -2
- package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
- package/dist/substrate/runtime/errors.mjs +12 -6
- package/dist/substrate/runtime/errors.mjs.map +1 -1
- package/dist/substrate/runtime/format-unknown-error.mjs +30 -0
- package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -0
- package/dist/substrate/runtime/host-bind-mount-owner.mjs +10 -0
- package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -0
- package/dist/substrate/runtime/host-gateway.mjs +20 -0
- package/dist/substrate/runtime/host-gateway.mjs.map +1 -0
- package/dist/substrate/runtime/host-tree-tar/index.mjs +61 -50
- package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
- package/dist/substrate/runtime/http-probe.d.mts +1 -1
- package/dist/substrate/runtime/index.mjs +14 -0
- package/dist/substrate/runtime/lease-broker/index.d.mts +1 -1
- package/dist/substrate/runtime/lease-broker/index.mjs +2 -0
- package/dist/substrate/runtime/lease-broker/service.d.mts +1 -64
- package/dist/substrate/runtime/lease-broker/service.mjs +1 -1
- package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs +22 -3
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/index.mjs +9 -0
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +20 -3
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs +5 -5
- package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/state-machine.mjs +1 -1
- package/dist/substrate/runtime/managed-container.d.mts +19 -1
- package/dist/substrate/runtime/managed-container.mjs +20 -1
- package/dist/substrate/runtime/managed-container.mjs.map +1 -1
- package/dist/substrate/runtime/manifest/index.mjs +3 -0
- package/dist/substrate/runtime/manifest/manifest.mjs +15 -11
- package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/mode-errors.mjs +20 -0
- package/dist/substrate/runtime/mode-errors.mjs.map +1 -0
- package/dist/substrate/runtime/observability/cascade-formatter.mjs +9 -13
- package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
- package/dist/substrate/runtime/observability/formatter-registry.mjs +19 -36
- package/dist/substrate/runtime/observability/formatter-registry.mjs.map +1 -1
- package/dist/substrate/runtime/observability/ignore-with-log.d.mts +1 -0
- package/dist/substrate/runtime/observability/ignore-with-log.mjs +17 -0
- package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -0
- package/dist/substrate/runtime/observability/index.d.mts +1 -1
- package/dist/substrate/runtime/observability/index.mjs +13 -0
- package/dist/substrate/runtime/observability/log-store.d.mts +1 -0
- package/dist/substrate/runtime/observability/log-store.mjs +175 -0
- package/dist/substrate/runtime/observability/log-store.mjs.map +1 -0
- package/dist/substrate/runtime/observability/logger.d.mts +2 -1
- package/dist/substrate/runtime/observability/logger.mjs +5 -9
- package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
- package/dist/substrate/runtime/observability/output-truncate.mjs +35 -0
- package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -0
- package/dist/substrate/runtime/observability/pretty-error.mjs +9 -12
- package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
- package/dist/substrate/runtime/observability/redaction.d.mts +2 -13
- package/dist/substrate/runtime/observability/redaction.mjs +8 -14
- package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
- package/dist/substrate/runtime/observability/span-store.d.mts +1 -0
- package/dist/substrate/runtime/observability/span-store.mjs +110 -0
- package/dist/substrate/runtime/observability/span-store.mjs.map +1 -0
- package/dist/substrate/runtime/observability/spans.mjs +14 -14
- package/dist/substrate/runtime/observability/spans.mjs.map +1 -1
- package/dist/substrate/runtime/observability/subprocess-capture.mjs +1 -1
- package/dist/substrate/runtime/passthrough-or-wrap.mjs +33 -0
- package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -0
- package/dist/substrate/runtime/paths.d.mts +1 -1
- package/dist/substrate/runtime/paths.mjs +4 -3
- package/dist/substrate/runtime/paths.mjs.map +1 -1
- package/dist/substrate/runtime/phase-preserving-produce.mjs +40 -0
- package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -0
- package/dist/substrate/runtime/port-broker/index.mjs +3 -0
- package/dist/substrate/runtime/port-broker/service.mjs +170 -126
- package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/probes.d.mts +7 -5
- package/dist/substrate/runtime/probes.mjs +3 -1
- package/dist/substrate/runtime/probes.mjs.map +1 -1
- package/dist/substrate/runtime/projection/index.mjs +4 -0
- package/dist/substrate/runtime/projection/operational-endpoints.mjs +1 -2
- package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
- package/dist/substrate/runtime/projection/persisted.mjs +33 -15
- package/dist/substrate/runtime/projection/persisted.mjs.map +1 -1
- package/dist/substrate/runtime/projection/state-ref.mjs +26 -2
- package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
- package/dist/substrate/runtime/projection/update.mjs +88 -24
- package/dist/substrate/runtime/projection/update.mjs.map +1 -1
- package/dist/substrate/runtime/random-suffix.mjs +11 -0
- package/dist/substrate/runtime/random-suffix.mjs.map +1 -0
- package/dist/substrate/runtime/retry-policy.d.mts +59 -1
- package/dist/substrate/runtime/retry-policy.mjs +66 -1
- package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
- package/dist/substrate/runtime/routed-url.d.mts +1 -0
- package/dist/substrate/runtime/routed-url.mjs +79 -0
- package/dist/substrate/runtime/routed-url.mjs.map +1 -0
- package/dist/substrate/runtime/runtime-decode.d.mts +1 -1
- package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-http-server.mjs +2 -3
- package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-multimap/index.mjs +2 -0
- package/dist/substrate/runtime/scoped-multimap/service.mjs +52 -0
- package/dist/substrate/runtime/scoped-multimap/service.mjs.map +1 -0
- package/dist/substrate/runtime/scoped-ref-map/index.mjs +2 -0
- package/dist/substrate/runtime/scoped-ref-map/service.mjs +2 -2
- package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +1 -1
- package/dist/substrate/runtime/stage-and-swap/index.mjs +72 -4
- package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
- package/dist/substrate/runtime/state-store/index.d.mts +1 -0
- package/dist/substrate/runtime/state-store/index.mjs +3 -0
- package/dist/substrate/runtime/state-store/schema.d.mts +1 -0
- package/dist/substrate/runtime/state-store/schema.mjs +19 -14
- package/dist/substrate/runtime/state-store/schema.mjs.map +1 -1
- package/dist/substrate/runtime/state-store/service.d.mts +1 -0
- package/dist/substrate/runtime/state-store/service.mjs +145 -0
- package/dist/substrate/runtime/state-store/service.mjs.map +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.d.mts +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs +33 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/index.mjs +4 -0
- package/dist/substrate/runtime/strategy-registry/service.mjs +19 -33
- package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
- package/dist/substrate/runtime/subnet-broker.mjs +60 -0
- package/dist/substrate/runtime/subnet-broker.mjs.map +1 -0
- package/dist/substrate/runtime/sui-execute/index.d.mts +21 -1
- package/dist/substrate/runtime/sui-execute/index.mjs +84 -20
- package/dist/substrate/runtime/sui-execute/index.mjs.map +1 -1
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.d.mts +1 -0
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs +32 -0
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +1 -0
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs +32 -0
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +1 -0
- package/dist/substrate/runtime/sui-move-build/index.mjs +93 -19
- package/dist/substrate/runtime/sui-move-build/index.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/acquire-node.mjs +181 -0
- package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/background-tasks.mjs +144 -0
- package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/command-loop.mjs +68 -0
- package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +151 -0
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/errors.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/errors.mjs +9 -0
- package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/index.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/index.mjs +3 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs +51 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs +250 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/state.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/state.mjs +22 -0
- package/dist/substrate/runtime/supervisor/state.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/teardown.mjs +57 -0
- package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/types.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/wiring.mjs +133 -0
- package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -0
- package/dist/substrate/runtime/typed-env.mjs +8 -0
- package/dist/substrate/runtime/typed-env.mjs.map +1 -0
- package/dist/substrate/state-store.d.mts +1 -0
- package/dist/substrate/versioned-doc-schema.mjs +17 -0
- package/dist/substrate/versioned-doc-schema.mjs.map +1 -0
- package/dist/substrate/versioned-doc-sync.mjs +94 -0
- package/dist/substrate/versioned-doc-sync.mjs.map +1 -0
- package/dist/surfaces/cli/command-tree.mjs.map +1 -1
- package/dist/surfaces/cli/commands/index.mjs +9 -0
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs +13 -0
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -0
- package/dist/surfaces/cli/commands/prune-picker.mjs +3 -2
- package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune.mjs +34 -17
- package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
- package/dist/surfaces/cli/commands/status.mjs +2 -2
- package/dist/surfaces/cli/commands/status.mjs.map +1 -1
- package/dist/surfaces/cli/commands/supervisor-presence.mjs +5 -3
- package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
- package/dist/surfaces/cli/commands/wipe.mjs +38 -4
- package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
- package/dist/surfaces/cli/errors.mjs +1 -1
- package/dist/surfaces/cli/errors.mjs.map +1 -1
- package/dist/surfaces/cli/flags.mjs +0 -1
- package/dist/surfaces/cli/flags.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs +98 -18
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/dist/surfaces/cli/output.mjs +1 -0
- package/dist/surfaces/cli/output.mjs.map +1 -1
- package/dist/surfaces/cli/sysexits.mjs +2 -1
- package/dist/surfaces/cli/sysexits.mjs.map +1 -1
- package/dist/surfaces/tui/app.mjs +25 -3
- package/dist/surfaces/tui/app.mjs.map +1 -1
- package/dist/surfaces/tui/display-derivation.mjs +23 -74
- package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
- package/dist/surfaces/tui/event-log.mjs +35 -23
- package/dist/surfaces/tui/event-log.mjs.map +1 -1
- package/dist/surfaces/tui/input.mjs +5 -1
- package/dist/surfaces/tui/input.mjs.map +1 -1
- package/dist/surfaces/tui/plain-renderer.mjs +70 -34
- package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
- package/package.json +24 -4
- package/dist/contracts/liveness-classifier.d.mts +0 -19
- package/dist/contracts/network-resolver.d.mts +0 -15
- package/dist/contracts/network-resolver.mjs +0 -7
- package/dist/contracts/network-resolver.mjs.map +0 -1
- package/dist/orchestrators/codegen/extras.mjs +0 -16
- package/dist/orchestrators/codegen/extras.mjs.map +0 -1
- package/dist/plugins/deepbook/routable.mjs +0 -21
- package/dist/plugins/deepbook/routable.mjs.map +0 -1
- package/dist/plugins/faucet/service.d.mts +0 -20
- package/dist/plugins/faucet/strategies/sui-local.d.mts +0 -23
- package/dist/plugins/faucet/strategies/sui-local.mjs.map +0 -1
- package/dist/plugins/sui/seed-objects.d.mts +0 -18
- package/dist/plugins/sui/seed-objects.mjs +0 -25
- package/dist/plugins/sui/seed-objects.mjs.map +0 -1
- package/dist/runtime/built-in-plugin-layers.mjs.map +0 -1
- package/dist/substrate/runtime/context-helpers.mjs +0 -32
- package/dist/substrate/runtime/context-helpers.mjs.map +0 -1
- package/dist/substrate/runtime/run.mjs.map +0 -1
- package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs +0 -30
- package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs.map +0 -1
- package/dist/substrate/runtime/supervisor.d.mts +0 -1
- package/dist/substrate/runtime/supervisor.mjs +0 -831
- package/dist/substrate/runtime/supervisor.mjs.map +0 -1
- package/dist/surfaces/cli/commands/doctor-probes.mjs.map +0 -1
- /package/dist/{substrate/runtime/strategy-registry/faucet-capability-for.d.mts → plugins/faucet/http.d.mts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.mjs","names":["httpRequest"],"sources":["../../../src/orchestrators/router/service.ts"],"sourcesContent":["// Router orchestrator — the L3 service that walks `Routable`\n// contributions, mints hostnames + ids, resolves upstream URLs, and\n// writes file-provider config to a watched directory.\n//\n// Architecture distilled-doc §\"Responsibilities\":\n// 1. Ensure exactly one Traefik container and one docker network\n// exist for the router profile. → `bootstrap()`\n// 2. Maintain the profile dispatch directory mounted into that\n// singleton (one file per backend). → `contributeRoute()`\n// 3. Mint per-backend hostnames + ids from `(app, stack, service)`. → `hostname.ts`\n// 4. Provide a single shared, permissive CORS middleware. → `cors.ts`\n// 5. Tear down per-backend dispatch entries when their owning scope\n// closes; leave the shared container running. → scope finalizers\n//\n// What this orchestrator DOES NOT do (architecture §\"What's NOT in it\"):\n// - Hardcode service names. The orchestrator iterates Routable decls,\n// resolves them by upstream-kind, and renders YAML — no `if (decl.endpointName === 'wallet')`\n// anywhere.\n// - Talk to docker directly. The Traefik container lives behind\n// `TraefikContainerOpsService`. Upstream IP resolution lives behind\n// `UpstreamResolver`. Production composition wires both seams.\n// - Hold module-level mutable state. Contribution state is a\n// `SubscriptionRef`; the shared-network-id and boot decision live\n// in fiber-scoped Refs.\n//\n// Hot-reload protocol:\n// - Contributions are exposed as a `SubscriptionRef<Map<dispatchFileId, RoutableDecl>>`.\n// - Each `contributeRoute(decl)` updates the ref and is paired with a\n// scope finalizer that removes the decl on scope close.\n// - A background fiber watches the ref's change stream; on each\n// emission it diffs against the last applied set and:\n// * for each ADD: resolves upstream + atomically writes the\n// dispatch file.\n// * for each REMOVE: best-effort unlinks the dispatch file.\n// - All writes go through `atomicWriteFile` (tmp + rename) per\n// architecture invariant #5.\n\nimport { Context, Effect, FileSystem, Layer, Ref, Stream, SubscriptionRef } from 'effect';\nimport { request as httpRequest } from 'node:http';\nimport * as path from 'node:path';\n\nimport type { RoutableDecl } from '../../contracts/routable.ts';\nimport { connect, DockerHost, DockerSpawner, waitForIp } from '../../runtime/docker/index.ts';\nimport { atomicWriteFile } from '../../substrate/runtime/atomic-write.ts';\nimport type { Identity } from '../../substrate/identity.ts';\nimport { checkHolderLiveness, ownHolder } from '../../substrate/runtime/cross-process/liveness.ts';\nimport { acquireStackLock } from '../../substrate/runtime/cross-process/stack-lock.ts';\nimport { waitForHttpEndpoint, type HttpProbeFetch } from '../../substrate/runtime/http-probe.ts';\nimport { IdentityContext } from '../../substrate/runtime/paths.ts';\nimport { CORS_MIDDLEWARE_FILENAME, renderCorsMiddlewareYaml } from './cors.ts';\nimport { EntrypointRegistry, type EntrypointRegistryShape } from './entrypoints.ts';\nimport {\n\tDispatchWriteFailed,\n\tRouteCollision,\n\tRouteReadinessProbeFailed,\n\tRouterBootFailed,\n\tRouterDisabledRouteUnsupported,\n\tRouterValidationError,\n\ttype UnknownEntrypoint,\n\ttype RouterError,\n} from './errors.ts';\nimport {\n\tdispatchFilename,\n\tdispatchFileIdFromFilename,\n\ttype DispatchRouteMetadata,\n\ttype DispatchRouteDecodeDiagnostic,\n\ttype ResolvedRoute,\n\ttype ResolvedWireProtocol,\n\ttype RouteLeaseMetadata,\n\ttype UpstreamResolver,\n\tdetectCollisions,\n\tparseDispatchRouteFile,\n\trenderRouteYaml,\n\tROUTE_READINESS_HEADER,\n\tresolveRoute,\n\tROUTER_ROUTE_LEASE_VERSION,\n} from './file-provider.ts';\nimport { dispatchFileId } from './hostname.ts';\nimport type { RouterProfile } from './profile.ts';\nimport { bootstrap, type BootReport, TraefikContainerOpsService } from './traefik-container.ts';\n\n// Router operations are shared across every stack using the same Docker\n// context. The generic stack-lock default stays short for normal metadata\n// mutations; router boot and dispatch can legitimately queue behind other\n// devstack processes during multi-example CI or local parallel runs.\nconst ROUTER_LOCK_TIMEOUT_MILLIS = 120_000;\n\n// ---------------------------------------------------------------------------\n// RouterConfig — orchestrator-level knobs\n// ---------------------------------------------------------------------------\n\n/** Knobs surfaced through `defineDevstack({ router: {...} })` per\n * distilled-doc open question #5. We model them as a typed Context\n * service so the orchestrator reads from a single source and tests\n * can override per scenario. */\nexport interface RouterConfigShape {\n\t/** Disable the router entirely. Only host-loopback Routables can\n\t * produce direct URLs in this mode; container upstreams require\n\t * the router's Docker network + proxy entrypoint and fail\n\t * explicitly. */\n\treadonly disabled: boolean;\n\t/** User + Docker-daemon scoped router profile. Runtime roots own\n\t * route leases; the profile owns the singleton proxy process,\n\t * dispatch directory, network name, and cross-process locks. */\n\treadonly profile: RouterProfile;\n\t/** Traefik image (tag or digest). */\n\treadonly image: string;\n\t/** Optional production gate that waits for Traefik to serve each\n\t * public HTTP route before the endpoint is published. Tests that\n\t * use the stub Traefik layer omit this. */\n\treadonly routeReadinessProbe?: RouteReadinessProbeConfig;\n}\n\nexport interface RouteReadinessProbeConfig {\n\treadonly enabled: boolean;\n\treadonly timeoutMs?: number;\n\treadonly intervalMs?: number;\n\treadonly requestTimeoutMs?: number;\n\treadonly fetch?: HttpProbeFetch;\n}\n\nexport class RouterConfig extends Context.Service<RouterConfig, RouterConfigShape>()(\n\t'@devstack-rewrite/orchestrators/router/RouterConfig',\n) {}\n\n/** Default-config layer for tests. Production wires this from\n * `runtime-composition.ts` at the engine boundary. */\nexport const layerRouterConfigLiteral = (cfg: RouterConfigShape): Layer.Layer<RouterConfig> =>\n\tLayer.succeed(RouterConfig)(cfg);\n\n// ---------------------------------------------------------------------------\n// UpstreamResolverService — Context wrapper around the seam\n// ---------------------------------------------------------------------------\n\nexport class UpstreamResolverService extends Context.Service<\n\tUpstreamResolverService,\n\tUpstreamResolver\n>()('@devstack-rewrite/orchestrators/router/UpstreamResolver') {}\n\nexport const layerDockerUpstreamResolver = (\n\tprofile: RouterProfile,\n): Layer.Layer<UpstreamResolverService, never, DockerHost | DockerSpawner> =>\n\tLayer.effect(\n\t\tUpstreamResolverService,\n\t\tEffect.gen(function* () {\n\t\t\tconst dockerHost = yield* DockerHost;\n\t\t\tconst dockerSpawner = yield* DockerSpawner;\n\t\t\tconst networkName = profile.networkName;\n\t\t\tconst provideDocker = <A, E>(\n\t\t\t\teffect: Effect.Effect<A, E, DockerHost | DockerSpawner>,\n\t\t\t): Effect.Effect<A, E, never> =>\n\t\t\t\teffect.pipe(\n\t\t\t\t\tEffect.provideService(DockerHost, dockerHost),\n\t\t\t\t\tEffect.provideService(DockerSpawner, dockerSpawner),\n\t\t\t\t);\n\n\t\t\treturn UpstreamResolverService.of({\n\t\t\t\tresolveContainer: (target) =>\n\t\t\t\t\tprovideDocker(\n\t\t\t\t\t\tconnect(target.containerName, networkName).pipe(\n\t\t\t\t\t\t\tEffect.andThen(waitForIp(target.containerName, networkName)),\n\t\t\t\t\t\t),\n\t\t\t\t\t).pipe(\n\t\t\t\t\t\tEffect.map((host) => ({ host, port: target.containerPort })),\n\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\t\tnew RouterValidationError({\n\t\t\t\t\t\t\t\t\tfield: 'upstreamUrl',\n\t\t\t\t\t\t\t\t\tvalue: target.containerName,\n\t\t\t\t\t\t\t\t\tdetail: `failed to attach/read container upstream on router network: ${String(cause)}`,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\tresolveHostLoopback: (target) =>\n\t\t\t\t\tEffect.succeed({ host: 'host.docker.internal', port: target.port }),\n\t\t\t});\n\t\t}),\n\t);\n\n// ---------------------------------------------------------------------------\n// Router service surface\n// ---------------------------------------------------------------------------\n\nexport interface RouterServiceShape {\n\t/** Boot the Traefik container once per supervisor lifetime.\n\t * Idempotent. Architecture invariant #11 — caller mounts this on\n\t * the long-lived outer scope, NOT inside the hot-reload loop. */\n\treadonly boot: () => Effect.Effect<BootReport, RouterError>;\n\n\t/** Contribute a `RoutableDecl`. The orchestrator resolves the\n\t * upstream URL, renders the file-provider YAML, and writes it\n\t * atomically. The returned `EndpointUrl` is the dispatched URL the\n\t * manifest writer + codegen consume.\n\t *\n\t * Scope-bound: when the caller's scope closes, the dispatch file\n\t * is removed (best-effort) and the contribution is dropped from the\n\t * subscribable map. */\n\treadonly contributeRoute: (\n\t\tdecl: RoutableDecl,\n\t) => Effect.Effect<EndpointUrl, RouterError, import('effect').Scope.Scope>;\n\n\t/** Subscribable view of currently-applied routes. Surfaces +\n\t * diagnostics consume this. */\n\treadonly applied: SubscriptionRef.SubscriptionRef<ReadonlyArray<ResolvedRoute>>;\n}\n\n/** What `contributeRoute` returns — the post-mint URL and the metadata\n * the manifest needs. `wireProtocol: 'tcp'` carries `tcp://` URLs;\n * consumers (codegen, manifest) translate to their protocol-specific\n * scheme (`postgres://`, `redis://`, …). */\nexport interface EndpointUrl {\n\treadonly endpointName: string;\n\treadonly hostname: string;\n\treadonly entrypointPort: number;\n\treadonly url: string;\n\treadonly wireProtocol: 'http' | 'h2c' | 'tcp';\n}\n\nexport class RouterService extends Context.Service<RouterService, RouterServiceShape>()(\n\t'@devstack-rewrite/orchestrators/router/Router',\n) {}\n\ninterface DispatchRouteScanDiagnostic extends DispatchRouteDecodeDiagnostic {\n\treadonly path: string;\n}\n\ninterface DispatchRouteScan {\n\treadonly routes: ReadonlyArray<DispatchRouteMetadata>;\n\treadonly unknownRouteFileIds: ReadonlyArray<string>;\n\treadonly diagnostics: ReadonlyArray<DispatchRouteScanDiagnostic>;\n}\n\nconst warnDispatchDecodeDiagnostic = (\n\tdiagnostic: DispatchRouteScanDiagnostic,\n): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t'router.dispatch.path': diagnostic.path,\n\t\t\t'router.dispatch.file_id': diagnostic.dispatchFileId,\n\t\t\t'router.dispatch.decode_reason': diagnostic.reason,\n\t\t});\n\t\tyield* Effect.logWarning(\n\t\t\t`router dispatch route file ${diagnostic.path} could not be fully decoded; ` +\n\t\t\t\t`reason=${diagnostic.reason}; treating it as an unknown route lease where destructive bootstrap safety matters`,\n\t\t);\n\t});\n\nconst readDispatchRouteScan = (\n\tfs: FileSystem.FileSystem,\n\tdispatchDir: string,\n\tdispatchFileId: string,\n\toptions: { readonly strict?: boolean } = {},\n): Effect.Effect<DispatchRouteScan, DispatchWriteFailed> =>\n\tEffect.gen(function* () {\n\t\tconst files = yield* fs.readDirectory(dispatchDir).pipe(\n\t\t\tEffect.mapError(\n\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\tdispatchFileId,\n\t\t\t\t\t\tpath: dispatchDir,\n\t\t\t\t\t\tdetail: `readDirectory(${dispatchDir}) failed`,\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\tconst routeFiles = files\n\t\t\t.map((filename) => ({ filename, fileId: dispatchFileIdFromFilename(filename) }))\n\t\t\t.filter(\n\t\t\t\t(entry): entry is { readonly filename: string; readonly fileId: string } =>\n\t\t\t\t\tentry.fileId !== null,\n\t\t\t);\n\t\tconst parsed = yield* Effect.forEach(\n\t\t\trouteFiles,\n\t\t\t(entry) => {\n\t\t\t\tconst filePath = path.join(dispatchDir, entry.filename);\n\t\t\t\treturn fs.readFileString(filePath).pipe(\n\t\t\t\t\tEffect.map((body) => ({\n\t\t\t\t\t\tpath: filePath,\n\t\t\t\t\t\tresult: parseDispatchRouteFile(body, entry.fileId),\n\t\t\t\t\t})),\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\t\t\tdispatchFileId,\n\t\t\t\t\t\t\t\tpath: filePath,\n\t\t\t\t\t\t\t\tdetail: `readFileString(${filePath}) 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\t{ concurrency: 'unbounded' },\n\t\t);\n\t\tconst routes: DispatchRouteMetadata[] = [];\n\t\tconst unknownRouteFileIds: string[] = [];\n\t\tconst diagnostics: DispatchRouteScanDiagnostic[] = [];\n\t\tfor (const entry of parsed) {\n\t\t\tfor (const diagnostic of entry.result.diagnostics) {\n\t\t\t\tconst withPath = { ...diagnostic, path: entry.path };\n\t\t\t\tdiagnostics.push(withPath);\n\t\t\t\tyield* warnDispatchDecodeDiagnostic(withPath);\n\t\t\t}\n\t\t\tif (entry.result._tag === 'valid') {\n\t\t\t\troutes.push(entry.result.route);\n\t\t\t} else {\n\t\t\t\tunknownRouteFileIds.push(entry.result.dispatchFileId);\n\t\t\t}\n\t\t}\n\t\tif (options.strict === true && diagnostics.length > 0) {\n\t\t\tconst first = diagnostics[0];\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\tdispatchFileId: first?.dispatchFileId ?? dispatchFileId,\n\t\t\t\t\tpath: first?.path ?? dispatchDir,\n\t\t\t\t\tdetail: first?.detail ?? 'dispatch route decode failed',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn { routes, unknownRouteFileIds, diagnostics };\n\t});\n\ntype DispatchLeaseStatus = 'live' | 'stale' | 'unknown-owner';\ntype RoutePublishOwnership = 'direct' | 'owned' | 'reused-live';\n\nconst classifyDispatchLease = (\n\troute: DispatchRouteMetadata,\n): Effect.Effect<DispatchLeaseStatus> => {\n\tif (route.lease === null) return Effect.succeed('unknown-owner');\n\treturn checkHolderLiveness(route.lease.owner).pipe(\n\t\tEffect.map((status) => (status === 'dead' ? 'stale' : 'live')),\n\t\tEffect.catch(() => Effect.succeed('live' as const)),\n\t);\n};\n\nconst sweepStaleDispatchRoutes = (\n\tfs: FileSystem.FileSystem,\n\tprofile: RouterProfile,\n\troutes: ReadonlyArray<DispatchRouteMetadata>,\n\tdispatchFileId: string,\n): Effect.Effect<ReadonlyArray<DispatchRouteMetadata>, DispatchWriteFailed> =>\n\tEffect.gen(function* () {\n\t\tconst active: DispatchRouteMetadata[] = [];\n\t\tfor (const route of routes) {\n\t\t\tconst status = yield* classifyDispatchLease(route);\n\t\t\tif (status === 'stale') {\n\t\t\t\tconst filePath = path.join(profile.dispatchDir, dispatchFilename(route.dispatchFileId));\n\t\t\t\tyield* fs.remove(filePath).pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\t\t\tdispatchFileId,\n\t\t\t\t\t\t\t\tpath: filePath,\n\t\t\t\t\t\t\t\tdetail: `failed to remove stale route lease ${route.dispatchFileId}`,\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\tcontinue;\n\t\t\t}\n\t\t\tactive.push(route);\n\t\t}\n\t\treturn active;\n\t});\n\nconst makeRouteLease = (profile: RouterProfile, identity: Identity): RouteLeaseMetadata => ({\n\tversion: ROUTER_ROUTE_LEASE_VERSION,\n\trouterProfileId: profile.id,\n\tapp: String(identity.app),\n\tstack: String(identity.stack),\n\towner: ownHolder(),\n});\n\nconst sameRouteSurface = (\n\ta: Pick<DispatchRouteMetadata, 'hostname' | 'entrypointName' | 'entrypointPort' | 'wireProtocol'>,\n\tb: Pick<ResolvedRoute, 'hostname' | 'entrypointName' | 'entrypointPort' | 'wireProtocol'>,\n): boolean =>\n\ta.hostname === b.hostname &&\n\ta.entrypointName === b.entrypointName &&\n\ta.entrypointPort === b.entrypointPort &&\n\ta.wireProtocol === b.wireProtocol;\n\nconst liveRouteLeaseMismatch = (\n\texisting: DispatchRouteMetadata,\n\tresolved: ResolvedRoute,\n): RouteCollision =>\n\tnew RouteCollision({\n\t\tmessage:\n\t\t\t`router route ${resolved.dispatchFileId} is already leased by a live process ` +\n\t\t\t`with a different public route (existing ${existing.entrypointName}/` +\n\t\t\t`${existing.hostname}, attempted ${resolved.entrypointName}/${resolved.hostname})`,\n\t\thostname: resolved.hostname,\n\t\tentrypoint: resolved.entrypointName,\n\t\tdispatchIds: [existing.dispatchFileId, resolved.dispatchFileId],\n\t});\n\nconst resolvedWireProtocolFor = (decl: RoutableDecl): ResolvedWireProtocol =>\n\tdecl.wireProtocol === 'tcp' ? 'tcp' : decl.wireProtocol === 'h2c' ? 'h2c' : 'http';\n\nconst validateWireProtocolFamily = (\n\tdecl: RoutableDecl,\n\tentrypoint: { readonly name: string; readonly protocol: 'http' | 'h2c' | 'tcp' },\n): Effect.Effect<ResolvedWireProtocol, RouterValidationError> => {\n\tconst wireProtocol = resolvedWireProtocolFor(decl);\n\tconst expectFamily: 'tcp' | 'http' = wireProtocol === 'tcp' ? 'tcp' : 'http';\n\tconst entrypointFamily: 'tcp' | 'http' = entrypoint.protocol === 'tcp' ? 'tcp' : 'http';\n\tif (expectFamily === entrypointFamily) return Effect.succeed(wireProtocol);\n\treturn Effect.fail(\n\t\tnew RouterValidationError({\n\t\t\tfield: 'entrypointName',\n\t\t\tvalue: entrypoint.name,\n\t\t\tdetail:\n\t\t\t\t`wireProtocol family mismatch: decl is '${wireProtocol}' but ` +\n\t\t\t\t`entrypoint '${entrypoint.name}' is '${entrypoint.protocol}'`,\n\t\t}),\n\t);\n};\n\nconst directLoopbackHost = '127.0.0.1';\nexport const DEFAULT_ROUTE_READINESS_TIMEOUT_MS = 60_000;\nconst DEFAULT_ROUTE_READINESS_INTERVAL_MS = 100;\nconst DEFAULT_ROUTE_READINESS_REQUEST_TIMEOUT_MS = 750;\nconst proxyGatewayStatuses = new Set([502, 503, 504]);\n\nconst responseHasReadyRoute = (response: Response, resolved: ResolvedRoute): boolean =>\n\tresponse.headers.get(ROUTE_READINESS_HEADER) === resolved.dispatchFileId &&\n\t!proxyGatewayStatuses.has(response.status);\n\nconst fetchHttpRouteViaLoopback: HttpProbeFetch = (input, init) =>\n\tnew Promise<Response>((resolveResponse, rejectResponse) => {\n\t\tconst url = new URL(String(input));\n\t\tconst headers = new Headers(init?.headers);\n\t\tconst signal = init?.signal ?? undefined;\n\t\tconst hostHeader = headers.get('host') ?? headers.get('Host') ?? url.host;\n\t\theaders.delete('host');\n\t\theaders.delete('Host');\n\t\tconst requestHeaders: Record<string, string> = {};\n\t\theaders.forEach((value, key) => {\n\t\t\trequestHeaders[key] = value;\n\t\t});\n\t\trequestHeaders.host = hostHeader;\n\t\tlet settled = false;\n\t\tlet req: ReturnType<typeof httpRequest> | null = null;\n\t\tfunction onAbort(): void {\n\t\t\tconst cause = new Error('route readiness probe aborted');\n\t\t\treq?.destroy(cause);\n\t\t\tsettle(rejectResponse, cause);\n\t\t}\n\t\tconst settle = <T>(fn: (value: T) => void, value: T): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (signal !== undefined) {\n\t\t\t\tsignal.removeEventListener('abort', onAbort);\n\t\t\t}\n\t\t\tfn(value);\n\t\t};\n\t\treq = httpRequest(\n\t\t\t{\n\t\t\t\thostname: directLoopbackHost,\n\t\t\t\tport: url.port === '' ? 80 : Number(url.port),\n\t\t\t\tpath: `${url.pathname}${url.search}`,\n\t\t\t\tmethod: init?.method ?? 'GET',\n\t\t\t\theaders: requestHeaders,\n\t\t\t},\n\t\t\t(res) => {\n\t\t\t\tconst chunks: Uint8Array[] = [];\n\t\t\t\tres.on('data', (chunk: Buffer | string) => {\n\t\t\t\t\tchunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);\n\t\t\t\t});\n\t\t\t\tres.on('end', () => {\n\t\t\t\t\tconst responseHeaders = new Headers();\n\t\t\t\t\tfor (const [key, value] of Object.entries(res.headers)) {\n\t\t\t\t\t\tif (value === undefined) continue;\n\t\t\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\t\t\tfor (const item of value) responseHeaders.append(key, item);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresponseHeaders.set(key, value);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tsettle(\n\t\t\t\t\t\tresolveResponse,\n\t\t\t\t\t\tnew Response(new Uint8Array(Buffer.concat(chunks)), {\n\t\t\t\t\t\t\tstatus: res.statusCode ?? 599,\n\t\t\t\t\t\t\tstatusText: res.statusMessage,\n\t\t\t\t\t\t\theaders: responseHeaders,\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);\n\t\tif (signal?.aborted === true) {\n\t\t\tonAbort();\n\t\t\treturn;\n\t\t}\n\t\tsignal?.addEventListener('abort', onAbort, { once: true });\n\t\treq.on('error', (cause) => settle(rejectResponse, cause));\n\t\treq.end();\n\t});\n\nconst resolveDisabledDirectRoute = (\n\tidentity: Identity,\n\tdecl: RoutableDecl,\n\tregistry: EntrypointRegistryShape,\n): Effect.Effect<\n\tResolvedRoute,\n\tUnknownEntrypoint | RouterDisabledRouteUnsupported | RouterValidationError\n> =>\n\tEffect.gen(function* () {\n\t\tif (decl.upstream.type === 'container') {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew RouterDisabledRouteUnsupported({\n\t\t\t\t\tendpointName: decl.endpointName,\n\t\t\t\t\tupstreamKind: 'container',\n\t\t\t\t\tdetail:\n\t\t\t\t\t\t'router is disabled, but container upstreams are only reachable through the router network/proxy; ' +\n\t\t\t\t\t\t'use a host-loopback upstream or publish an explicit direct endpoint instead',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tconst port = decl.upstream.port;\n\t\tif (!Number.isInteger(port) || port <= 0 || port > 65535) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew RouterValidationError({\n\t\t\t\t\tfield: 'upstreamUrl',\n\t\t\t\t\tvalue: `${directLoopbackHost}:${port}`,\n\t\t\t\t\tdetail: 'disabled-router direct host-loopback routes require a concrete TCP port 1-65535',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tconst entrypoint = yield* registry.byName(decl.endpointName);\n\t\tconst wireProtocol = yield* validateWireProtocolFamily(decl, entrypoint);\n\t\tconst fileId = yield* dispatchFileId({ identity, dispatch: decl.dispatchId });\n\t\tconst scheme = wireProtocol === 'tcp' ? 'tcp' : 'http';\n\t\treturn {\n\t\t\tdispatchFileId: fileId,\n\t\t\thostname: directLoopbackHost,\n\t\t\tentrypointName: entrypoint.name,\n\t\t\tentrypointPort: port,\n\t\t\tupstreamUrl: `${scheme}://${directLoopbackHost}:${port}`,\n\t\t\tcors: decl.wireProtocol === 'tcp' ? false : decl.cors,\n\t\t\twireProtocol,\n\t\t};\n\t});\n\nconst endpointFromResolvedRoute = (decl: RoutableDecl, resolved: ResolvedRoute): EndpointUrl => {\n\tconst url =\n\t\tresolved.wireProtocol === 'tcp'\n\t\t\t? `tcp://127.0.0.1:${resolved.entrypointPort}`\n\t\t\t: `http://${resolved.hostname}:${resolved.entrypointPort}`;\n\treturn {\n\t\tendpointName: decl.endpointName,\n\t\thostname: resolved.hostname,\n\t\tentrypointPort: resolved.entrypointPort,\n\t\turl,\n\t\twireProtocol: resolved.wireProtocol,\n\t};\n};\n\nconst removeDispatchFile = (\n\tfs: FileSystem.FileSystem,\n\tprofile: RouterProfile,\n\tresolved: ResolvedRoute,\n): Effect.Effect<void> =>\n\tfs\n\t\t.remove(path.join(profile.dispatchDir, dispatchFilename(resolved.dispatchFileId)))\n\t\t.pipe(Effect.ignore);\n\nconst waitForPublicRouteReadiness = (\n\tcfg: RouterConfigShape,\n\tendpoint: EndpointUrl,\n\tresolved: ResolvedRoute,\n): Effect.Effect<void, RouteReadinessProbeFailed> => {\n\tconst options = cfg.routeReadinessProbe;\n\tif (options?.enabled !== true || cfg.disabled || resolved.wireProtocol === 'tcp') {\n\t\treturn Effect.void;\n\t}\n\tconst timeoutMs = options.timeoutMs ?? DEFAULT_ROUTE_READINESS_TIMEOUT_MS;\n\tconst intervalMs = options.intervalMs ?? DEFAULT_ROUTE_READINESS_INTERVAL_MS;\n\tconst requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_ROUTE_READINESS_REQUEST_TIMEOUT_MS;\n\tconst probeUrl = `http://${directLoopbackHost}:${resolved.entrypointPort}`;\n\treturn waitForHttpEndpoint({\n\t\tendpoint: probeUrl,\n\t\ttimeoutMs,\n\t\tintervalMs,\n\t\trequestTimeoutMs,\n\t\trequestInit: { headers: { host: resolved.hostname } },\n\t\tfetch: options.fetch ?? fetchHttpRouteViaLoopback,\n\t\tvalidate: (response) => responseHasReadyRoute(response, resolved),\n\t}).pipe(\n\t\tEffect.mapError(\n\t\t\t(cause): RouteReadinessProbeFailed =>\n\t\t\t\tnew RouteReadinessProbeFailed({\n\t\t\t\t\tdispatchFileId: resolved.dispatchFileId,\n\t\t\t\t\turl: endpoint.url,\n\t\t\t\t\ttimeoutMs,\n\t\t\t\t\tdetail:\n\t\t\t\t\t\t`public router endpoint ${endpoint.url} did not serve route ` +\n\t\t\t\t\t\t`${resolved.dispatchFileId} within ${timeoutMs}ms ` +\n\t\t\t\t\t\t`(probeUrl=${probeUrl}, hostHeader=${resolved.hostname})`,\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t),\n\t);\n};\n\n// ---------------------------------------------------------------------------\n// Layer — wire the orchestrator\n// ---------------------------------------------------------------------------\n\nexport const layerRouterService: Layer.Layer<\n\tRouterService,\n\tnever,\n\t| RouterConfig\n\t| IdentityContext\n\t| EntrypointRegistry\n\t| TraefikContainerOpsService\n\t| UpstreamResolverService\n\t| FileSystem.FileSystem\n> = Layer.effect(\n\tRouterService,\n\tEffect.gen(function* () {\n\t\tconst cfg = yield* RouterConfig;\n\t\tconst identity = yield* IdentityContext;\n\t\tconst registry = yield* EntrypointRegistry;\n\t\tconst upstreams = yield* UpstreamResolverService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t// Capture the Traefik ops service in the layer's outer scope so\n\t\t// `bootstrap` (which reads it from Context) doesn't surface\n\t\t// requirements through `boot()`'s return type. Provided to\n\t\t// downstream calls via `Effect.provideService`.\n\t\tconst traefikOps = yield* TraefikContainerOpsService;\n\n\t\t// Cached boot report — set on first `boot()` call.\n\t\tconst bootRef = yield* Ref.make<BootReport | null>(null);\n\n\t\t// Applied routes — subscribable for diagnostics. Empty until the\n\t\t// first `contributeRoute()`. The orchestrator publishes the\n\t\t// *resolved* shape (post URL-resolution) rather than the raw\n\t\t// decls so downstream surfaces don't have to re-resolve.\n\t\tconst applied = yield* SubscriptionRef.make<ReadonlyArray<ResolvedRoute>>([]);\n\n\t\t// ---------------------------------------------------------------\n\t\t// boot — adopt-or-create the Traefik container.\n\t\t// ---------------------------------------------------------------\n\t\tconst boot: RouterServiceShape['boot'] = () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst profile = cfg.profile;\n\t\t\t\tif (cfg.disabled) {\n\t\t\t\t\tconst report: BootReport = {\n\t\t\t\t\t\tdecision: 'opt-out',\n\t\t\t\t\t\tcontainerId: null,\n\t\t\t\t\t\tnetworkId: null,\n\t\t\t\t\t\timageMatches: true,\n\t\t\t\t\t};\n\t\t\t\t\tyield* Ref.set(bootRef, report);\n\t\t\t\t\treturn report;\n\t\t\t\t}\n\t\t\t\tconst cached = yield* Ref.get(bootRef);\n\t\t\t\tif (cached !== null) return cached;\n\t\t\t\tconst protectedRouteLeaseIds = yield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireStackLock(profile.dispatchLockFile, ROUTER_LOCK_TIMEOUT_MILLIS).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'ensure-container',\n\t\t\t\t\t\t\t\t\t\tdetail: `failed to acquire router dispatch lock ${profile.dispatchLockFile}`,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\t// Write the shared CORS middleware file before reading\n\t\t\t\t\t\t// active dispatch routes so the later bootstrap phase can\n\t\t\t\t\t\t// hold only the bootstrap lock.\n\t\t\t\t\t\tyield* fs.makeDirectory(profile.dispatchDir, { recursive: true }).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'write-shared-config',\n\t\t\t\t\t\t\t\t\t\tdetail: `makeDirectory(${profile.dispatchDir}) failed`,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tyield* atomicWriteFile(\n\t\t\t\t\t\t\tpath.join(profile.dispatchDir, CORS_MIDDLEWARE_FILENAME),\n\t\t\t\t\t\t\tnew TextEncoder().encode(renderCorsMiddlewareYaml()),\n\t\t\t\t\t\t\t{ mode: 0o644 },\n\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'write-shared-config',\n\t\t\t\t\t\t\t\t\t\tdetail: `atomicWriteFile(${CORS_MIDDLEWARE_FILENAME}) failed at stage ${cause.stage}`,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tEffect.provideService(FileSystem.FileSystem, fs),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst existingDispatchScan = yield* readDispatchRouteScan(\n\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\tprofile.dispatchDir,\n\t\t\t\t\t\t\t'router-boot',\n\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'inspect',\n\t\t\t\t\t\t\t\t\t\tdetail: cause.detail,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst activeDispatchRoutes = yield* sweepStaleDispatchRoutes(\n\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\tprofile,\n\t\t\t\t\t\t\texistingDispatchScan.routes,\n\t\t\t\t\t\t\t'router-boot',\n\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'write-shared-config',\n\t\t\t\t\t\t\t\t\t\tdetail: cause.detail,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t...activeDispatchRoutes.map((route) => route.dispatchFileId),\n\t\t\t\t\t\t\t...existingDispatchScan.unknownRouteFileIds,\n\t\t\t\t\t\t];\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tconst report = yield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireStackLock(profile.bootstrapLockFile, ROUTER_LOCK_TIMEOUT_MILLIS).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'ensure-container',\n\t\t\t\t\t\t\t\t\t\tdetail: `failed to acquire router bootstrap lock ${profile.bootstrapLockFile}`,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn yield* bootstrap({\n\t\t\t\t\t\t\timage: cfg.image,\n\t\t\t\t\t\t\tentrypoints: registry.all(),\n\t\t\t\t\t\t\tprofile,\n\t\t\t\t\t\t\tprotectedRouteLeaseIds,\n\t\t\t\t\t\t}).pipe(Effect.provideService(TraefikContainerOpsService, traefikOps));\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tyield* Ref.set(bootRef, report);\n\t\t\t\treturn report;\n\t\t\t}).pipe(Effect.withSpan('orchestrator.router.boot'));\n\n\t\t// ---------------------------------------------------------------\n\t\t// contributeRoute — resolve + write dispatch file + scope finalizer.\n\t\t// ---------------------------------------------------------------\n\t\tconst contributeRoute: RouterServiceShape['contributeRoute'] = (decl) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst profile = cfg.profile;\n\t\t\t\tconst resolved = cfg.disabled\n\t\t\t\t\t? yield* resolveDisabledDirectRoute(identity, decl, registry)\n\t\t\t\t\t: yield* resolveRoute(identity, decl, registry, upstreams);\n\t\t\t\tconst lease = makeRouteLease(profile, identity);\n\t\t\t\tconst endpoint = endpointFromResolvedRoute(decl, resolved);\n\t\t\t\tconst publishRouteFile: Effect.Effect<\n\t\t\t\t\tRoutePublishOwnership,\n\t\t\t\t\tDispatchWriteFailed | RouteCollision\n\t\t\t\t> = Effect.gen(function* () {\n\t\t\t\t\t// Collision check against this process's applied set\n\t\t\t\t\t// plus files already present in the shared dispatch\n\t\t\t\t\t// directory. The dispatch lock makes the scan + write a\n\t\t\t\t\t// cross-process critical section.\n\t\t\t\t\tif (cfg.disabled) return 'direct';\n\t\t\t\t\tif (!cfg.disabled) {\n\t\t\t\t\t\tyield* fs.makeDirectory(profile.dispatchDir, { recursive: true }).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\t\t\t\t\tdispatchFileId: resolved.dispatchFileId,\n\t\t\t\t\t\t\t\t\t\tpath: profile.dispatchDir,\n\t\t\t\t\t\t\t\t\t\tdetail: `makeDirectory(${profile.dispatchDir}) failed`,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\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\tconst currentApplied = yield* SubscriptionRef.get(applied);\n\t\t\t\t\tconst currentIds = new Set(currentApplied.map((route) => route.dispatchFileId));\n\t\t\t\t\tlet reuseLiveRoute = false;\n\t\t\t\t\tconst readScan = yield* readDispatchRouteScan(\n\t\t\t\t\t\tfs,\n\t\t\t\t\t\tprofile.dispatchDir,\n\t\t\t\t\t\tresolved.dispatchFileId,\n\t\t\t\t\t);\n\t\t\t\t\tconst activeDispatchRoutes = yield* sweepStaleDispatchRoutes(\n\t\t\t\t\t\tfs,\n\t\t\t\t\t\tprofile,\n\t\t\t\t\t\treadScan.routes,\n\t\t\t\t\t\tresolved.dispatchFileId,\n\t\t\t\t\t);\n\t\t\t\t\tconst existingSameDispatchRoute = activeDispatchRoutes.find(\n\t\t\t\t\t\t(route) => route.dispatchFileId === resolved.dispatchFileId,\n\t\t\t\t\t);\n\t\t\t\t\tif (\n\t\t\t\t\t\texistingSameDispatchRoute !== undefined &&\n\t\t\t\t\t\t!currentIds.has(existingSameDispatchRoute.dispatchFileId)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst status = yield* classifyDispatchLease(existingSameDispatchRoute);\n\t\t\t\t\t\tif (status === 'live') {\n\t\t\t\t\t\t\tif (!sameRouteSurface(existingSameDispatchRoute, resolved)) {\n\t\t\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\t\t\tliveRouteLeaseMismatch(existingSameDispatchRoute, resolved),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treuseLiveRoute = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tconst existingDispatchRoutes = activeDispatchRoutes.filter(\n\t\t\t\t\t\t(route) =>\n\t\t\t\t\t\t\t!currentIds.has(route.dispatchFileId) &&\n\t\t\t\t\t\t\troute.dispatchFileId !== resolved.dispatchFileId,\n\t\t\t\t\t);\n\t\t\t\t\tconst collision = detectCollisions([\n\t\t\t\t\t\t...existingDispatchRoutes,\n\t\t\t\t\t\t...currentApplied,\n\t\t\t\t\t\tresolved,\n\t\t\t\t\t]);\n\t\t\t\t\tif (collision) return yield* Effect.fail(collision);\n\t\t\t\t\tif (reuseLiveRoute) return 'reused-live';\n\n\t\t\t\t\t// Atomic write — invariant #5. tmp + rename via the\n\t\t\t\t\t// substrate's `atomicWriteFile` helper; Traefik's\n\t\t\t\t\t// watcher tolerates rename atomically.\n\t\t\t\t\tconst filePath = path.join(\n\t\t\t\t\t\tprofile.dispatchDir,\n\t\t\t\t\t\tdispatchFilename(resolved.dispatchFileId),\n\t\t\t\t\t);\n\t\t\t\t\tyield* atomicWriteFile(\n\t\t\t\t\t\tfilePath,\n\t\t\t\t\t\tnew TextEncoder().encode(renderRouteYaml(resolved, lease)),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmode: 0o644,\n\t\t\t\t\t\t},\n\t\t\t\t\t).pipe(\n\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\t\t\t\tdispatchFileId: resolved.dispatchFileId,\n\t\t\t\t\t\t\t\t\tpath: filePath,\n\t\t\t\t\t\t\t\t\tdetail: `atomicWriteFile failed at stage ${cause.stage}`,\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tEffect.provideService(FileSystem.FileSystem, fs),\n\t\t\t\t\t);\n\t\t\t\t\treturn 'owned';\n\t\t\t\t});\n\n\t\t\t\tlet publishOwnership: RoutePublishOwnership;\n\t\t\t\tif (cfg.disabled) {\n\t\t\t\t\tpublishOwnership = yield* publishRouteFile;\n\t\t\t\t} else {\n\t\t\t\t\tpublishOwnership = yield* Effect.scoped(\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tyield* acquireStackLock(profile.dispatchLockFile, ROUTER_LOCK_TIMEOUT_MILLIS).pipe(\n\t\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\t\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\t\t\t\t\t\tdispatchFileId: resolved.dispatchFileId,\n\t\t\t\t\t\t\t\t\t\t\tpath: profile.dispatchLockFile,\n\t\t\t\t\t\t\t\t\t\t\tdetail: `failed to acquire dispatch lock ${profile.dispatchLockFile}`,\n\t\t\t\t\t\t\t\t\t\t\tcause,\n\t\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 yield* publishRouteFile;\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tyield* waitForPublicRouteReadiness(cfg, endpoint, resolved).pipe(\n\t\t\t\t\tEffect.onError(() =>\n\t\t\t\t\t\tcfg.disabled || publishOwnership !== 'owned'\n\t\t\t\t\t\t\t? Effect.void\n\t\t\t\t\t\t\t: Effect.scoped(\n\t\t\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\t\t\tyield* acquireStackLock(\n\t\t\t\t\t\t\t\t\t\t\tprofile.dispatchLockFile,\n\t\t\t\t\t\t\t\t\t\t\tROUTER_LOCK_TIMEOUT_MILLIS,\n\t\t\t\t\t\t\t\t\t\t).pipe(Effect.ignore);\n\t\t\t\t\t\t\t\t\t\tyield* removeDispatchFile(fs, profile, resolved);\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),\n\t\t\t\t);\n\t\t\t\tif (publishOwnership !== 'reused-live') {\n\t\t\t\t\tyield* SubscriptionRef.update(applied, (arr) => [...arr, resolved]);\n\n\t\t\t\t\t// Scope finalizer — remove the file + drop from applied\n\t\t\t\t\t// when the caller's scope closes. Best-effort: \"already\n\t\t\t\t\t// gone\" is fine per distilled-doc.\n\t\t\t\t\tyield* Effect.addFinalizer(() =>\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tif (publishOwnership === 'owned') {\n\t\t\t\t\t\t\t\tyield* Effect.scoped(\n\t\t\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\t\t\tyield* acquireStackLock(profile.dispatchLockFile, ROUTER_LOCK_TIMEOUT_MILLIS);\n\t\t\t\t\t\t\t\t\t\tyield* removeDispatchFile(fs, profile, resolved);\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t).pipe(Effect.ignore);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tyield* SubscriptionRef.update(applied, (arr) =>\n\t\t\t\t\t\t\t\tarr.filter((r) => r.dispatchFileId !== resolved.dispatchFileId),\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\n\t\t\t\treturn endpoint;\n\t\t\t}).pipe(Effect.withSpan('orchestrator.router.contributeRoute'));\n\n\t\treturn RouterService.of({ boot, contributeRoute, applied });\n\t}),\n);\n\n// ---------------------------------------------------------------------------\n// Helpers for tests + composition\n// ---------------------------------------------------------------------------\n\n/** Stream of the resolved-route set. Use from a renderer / inventory\n * surface to react to route adds + removes. The supervisor's main\n * loop subscribes through this stream. */\nexport const routesStream = (\n\trouter: RouterServiceShape,\n): Stream.Stream<ReadonlyArray<ResolvedRoute>> => SubscriptionRef.changes(router.applied);\n\n// Re-export for ergonomics — callers reach for the orchestrator\n// without having to spell out the sibling module paths.\nexport { type ResolvedRoute, type UpstreamResolver } from './file-provider.ts';\nexport type { BootReport } from './traefik-container.ts';\nexport type { UpstreamResolveTimeout } from './errors.ts';\nexport type { Identity } from '../../substrate/identity.ts';\nexport type { RoutableDecl } from '../../contracts/routable.ts';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.mjs","names":["httpRequest"],"sources":["../../../src/orchestrators/router/service.ts"],"sourcesContent":["// Router orchestrator — the L3 service that walks `Routable`\n// contributions, mints hostnames + ids, resolves upstream URLs, and\n// writes file-provider config to a watched directory.\n//\n// Architecture distilled-doc §\"Responsibilities\":\n// 1. Ensure exactly one Traefik container and one docker network\n// exist for the router profile. → `bootstrap()`\n// 2. Maintain the profile dispatch directory mounted into that\n// singleton (one file per backend). → `contributeRoute()`\n// 3. Mint per-backend hostnames + ids from `(app, stack, service)`. → `hostname.ts`\n// 4. Provide a single shared, permissive CORS middleware. → `cors.ts`\n// 5. Tear down per-backend dispatch entries when their owning scope\n// closes; leave the shared container running. → scope finalizers\n//\n// What this orchestrator DOES NOT do (architecture §\"What's NOT in it\"):\n// - Hardcode service names. The orchestrator iterates Routable decls,\n// resolves them by upstream-kind, and renders YAML — no `if (decl.endpointName === 'wallet')`\n// anywhere.\n// - Talk to docker directly. The Traefik container lives behind\n// `TraefikContainerOpsService`. Upstream IP resolution lives behind\n// `UpstreamResolver`. Production composition wires both seams.\n// - Hold module-level mutable state. Contribution state is a\n// `SubscriptionRef`; the shared-network-id and boot decision live\n// in fiber-scoped Refs.\n//\n// Hot-reload protocol:\n// - Contributions are exposed as a `SubscriptionRef<Map<dispatchFileId, RoutableDecl>>`.\n// - Each `contributeRoute(decl)` updates the ref and is paired with a\n// scope finalizer that removes the decl on scope close.\n// - A background fiber watches the ref's change stream; on each\n// emission it diffs against the last applied set and:\n// * for each ADD: resolves upstream + atomically writes the\n// dispatch file.\n// * for each REMOVE: best-effort unlinks the dispatch file.\n// - All writes go through `atomicWriteFile` (tmp + rename) per\n// architecture invariant #5.\n\nimport { Context, Effect, FileSystem, Layer, Ref, Stream, SubscriptionRef } from 'effect';\nimport { request as httpRequest } from 'node:http';\nimport * as path from 'node:path';\n\nimport type { RoutableDecl } from '../../contracts/routable.ts';\nimport { connect, DockerHost, DockerSpawner, waitForIp } from '../../runtime/docker/index.ts';\nimport { atomicWriteFile } from '../../substrate/runtime/atomic-write.ts';\nimport { logWarningAndIgnore } from '../../substrate/runtime/observability/index.ts';\nimport type { Identity } from '../../substrate/identity.ts';\nimport {\n\tcheckHolderLiveness,\n\tlayerLivenessProbeScope,\n\tLivenessProbeScope,\n\townHolder,\n} from '../../substrate/runtime/cross-process/liveness.ts';\nimport { acquireStackLock } from '../../substrate/runtime/cross-process/stack-lock.ts';\nimport { waitForHttpEndpoint, type HttpProbeFetch } from '../../substrate/runtime/http-probe.ts';\nimport { IdentityContext } from '../../substrate/runtime/paths.ts';\nimport { CORS_MIDDLEWARE_FILENAME, renderCorsMiddlewareYaml } from './cors.ts';\nimport { EntrypointRegistry, type EntrypointRegistryShape } from './entrypoints.ts';\nimport {\n\tDispatchWriteFailed,\n\tRouteCollision,\n\tRouteReadinessProbeFailed,\n\tRouterBootFailed,\n\tRouterDisabledRouteUnsupported,\n\tRouterValidationError,\n\ttype UnknownEntrypoint,\n\ttype RouterError,\n} from './errors.ts';\nimport {\n\tdispatchFilename,\n\tdispatchFileIdFromFilename,\n\ttype DispatchRouteMetadata,\n\ttype DispatchRouteDecodeDiagnostic,\n\ttype ResolvedRoute,\n\ttype ResolvedWireProtocol,\n\ttype RouteLeaseMetadata,\n\ttype UpstreamResolver,\n\tdetectCollisions,\n\tparseDispatchRouteFile,\n\trenderRouteYaml,\n\tROUTE_READINESS_HEADER,\n\tresolveRoute,\n\tROUTER_ROUTE_LEASE_VERSION,\n} from './file-provider.ts';\nimport { dispatchFileId } from './hostname.ts';\nimport type { RouterProfile } from './profile.ts';\nimport { bootstrap, type BootReport, TraefikContainerOpsService } from './traefik-container.ts';\n\n// Router operations are shared across every stack using the same Docker\n// context. The generic stack-lock default stays short for normal metadata\n// mutations; router boot and dispatch can legitimately queue behind other\n// devstack processes during multi-example CI or local parallel runs.\nconst ROUTER_LOCK_TIMEOUT_MILLIS = 120_000;\n\n// ---------------------------------------------------------------------------\n// RouterConfig — orchestrator-level knobs\n// ---------------------------------------------------------------------------\n\n/** Knobs surfaced through `defineDevstack({ router: {...} })` per\n * distilled-doc open question #5. We model them as a typed Context\n * service so the orchestrator reads from a single source and tests\n * can override per scenario. */\nexport interface RouterConfigShape {\n\t/** Disable the router entirely. Only host-loopback Routables can\n\t * produce direct URLs in this mode; container upstreams require\n\t * the router's Docker network + proxy entrypoint and fail\n\t * explicitly. */\n\treadonly disabled: boolean;\n\t/** User + Docker-daemon scoped router profile. Runtime roots own\n\t * route leases; the profile owns the singleton proxy process,\n\t * dispatch directory, network name, and cross-process locks. */\n\treadonly profile: RouterProfile;\n\t/** Traefik image (tag or digest). */\n\treadonly image: string;\n\t/** Optional production gate that waits for Traefik to serve each\n\t * public HTTP route before the endpoint is published. Tests that\n\t * use the stub Traefik layer omit this. */\n\treadonly routeReadinessProbe?: RouteReadinessProbeConfig;\n}\n\nexport interface RouteReadinessProbeConfig {\n\treadonly enabled: boolean;\n\treadonly timeoutMs?: number;\n\treadonly intervalMs?: number;\n\treadonly requestTimeoutMs?: number;\n\treadonly fetch?: HttpProbeFetch;\n}\n\nexport class RouterConfig extends Context.Service<RouterConfig, RouterConfigShape>()(\n\t'@devstack/orchestrators/router/RouterConfig',\n) {}\n\n/** Default-config layer for tests. Production wires this from\n * `runtime-composition.ts` at the engine boundary. */\nexport const layerRouterConfigLiteral = (cfg: RouterConfigShape): Layer.Layer<RouterConfig> =>\n\tLayer.succeed(RouterConfig)(cfg);\n\n// ---------------------------------------------------------------------------\n// UpstreamResolverService — Context wrapper around the seam\n// ---------------------------------------------------------------------------\n\nexport class UpstreamResolverService extends Context.Service<\n\tUpstreamResolverService,\n\tUpstreamResolver\n>()('@devstack/orchestrators/router/UpstreamResolver') {}\n\nexport const layerDockerUpstreamResolver = (\n\tprofile: RouterProfile,\n): Layer.Layer<UpstreamResolverService, never, DockerHost | DockerSpawner> =>\n\tLayer.effect(\n\t\tUpstreamResolverService,\n\t\tEffect.gen(function* () {\n\t\t\tconst dockerHost = yield* DockerHost;\n\t\t\tconst dockerSpawner = yield* DockerSpawner;\n\t\t\tconst networkName = profile.networkName;\n\t\t\tconst provideDocker = <A, E>(\n\t\t\t\teffect: Effect.Effect<A, E, DockerHost | DockerSpawner>,\n\t\t\t): Effect.Effect<A, E, never> =>\n\t\t\t\teffect.pipe(\n\t\t\t\t\tEffect.provideService(DockerHost, dockerHost),\n\t\t\t\t\tEffect.provideService(DockerSpawner, dockerSpawner),\n\t\t\t\t);\n\n\t\t\treturn UpstreamResolverService.of({\n\t\t\t\tresolveContainer: (target) =>\n\t\t\t\t\tprovideDocker(\n\t\t\t\t\t\tconnect(target.containerName, networkName).pipe(\n\t\t\t\t\t\t\tEffect.andThen(waitForIp(target.containerName, networkName)),\n\t\t\t\t\t\t),\n\t\t\t\t\t).pipe(\n\t\t\t\t\t\tEffect.map((host) => ({ host, port: target.containerPort })),\n\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\t\tnew RouterValidationError({\n\t\t\t\t\t\t\t\t\tfield: 'upstreamUrl',\n\t\t\t\t\t\t\t\t\tvalue: target.containerName,\n\t\t\t\t\t\t\t\t\tdetail: `failed to attach/read container upstream on router network: ${String(cause)}`,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\tresolveHostLoopback: (target) =>\n\t\t\t\t\tEffect.succeed({ host: 'host.docker.internal', port: target.port }),\n\t\t\t});\n\t\t}),\n\t);\n\n// ---------------------------------------------------------------------------\n// Router service surface\n// ---------------------------------------------------------------------------\n\nexport interface RouterServiceShape {\n\t/** Boot the Traefik container once per supervisor lifetime.\n\t * Idempotent. Architecture invariant #11 — caller mounts this on\n\t * the long-lived outer scope, NOT inside the hot-reload loop. */\n\treadonly boot: () => Effect.Effect<BootReport, RouterError>;\n\n\t/** Contribute a `RoutableDecl`. The orchestrator resolves the\n\t * upstream URL, renders the file-provider YAML, and writes it\n\t * atomically. The returned `EndpointUrl` is the dispatched URL the\n\t * manifest writer + codegen consume.\n\t *\n\t * Scope-bound: when the caller's scope closes, the dispatch file\n\t * is removed (best-effort) and the contribution is dropped from the\n\t * subscribable map. */\n\treadonly contributeRoute: (\n\t\tdecl: RoutableDecl,\n\t) => Effect.Effect<EndpointUrl, RouterError, import('effect').Scope.Scope>;\n\n\t/** Subscribable view of currently-applied routes. Surfaces +\n\t * diagnostics consume this. */\n\treadonly applied: SubscriptionRef.SubscriptionRef<ReadonlyArray<ResolvedRoute>>;\n}\n\n/** What `contributeRoute` returns — the post-mint URL and the metadata\n * the manifest needs. `wireProtocol: 'tcp'` carries `tcp://` URLs;\n * consumers (codegen, manifest) translate to their protocol-specific\n * scheme (`postgres://`, `redis://`, …). */\nexport interface EndpointUrl {\n\treadonly endpointName: string;\n\treadonly hostname: string;\n\treadonly entrypointPort: number;\n\treadonly url: string;\n\treadonly wireProtocol: 'http' | 'h2c' | 'tcp';\n}\n\nexport class RouterService extends Context.Service<RouterService, RouterServiceShape>()(\n\t'@devstack/orchestrators/router/Router',\n) {}\n\ninterface DispatchRouteScanDiagnostic extends DispatchRouteDecodeDiagnostic {\n\treadonly path: string;\n}\n\ninterface DispatchRouteScan {\n\treadonly routes: ReadonlyArray<DispatchRouteMetadata>;\n\treadonly unknownRouteFileIds: ReadonlyArray<string>;\n\treadonly diagnostics: ReadonlyArray<DispatchRouteScanDiagnostic>;\n}\n\nconst warnDispatchDecodeDiagnostic = (\n\tdiagnostic: DispatchRouteScanDiagnostic,\n): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t'router.dispatch.path': diagnostic.path,\n\t\t\t'router.dispatch.file_id': diagnostic.dispatchFileId,\n\t\t\t'router.dispatch.decode_reason': diagnostic.reason,\n\t\t});\n\t\tyield* Effect.logWarning(\n\t\t\t`router dispatch route file ${diagnostic.path} could not be fully decoded; ` +\n\t\t\t\t`reason=${diagnostic.reason}; treating it as an unknown route lease where destructive bootstrap safety matters`,\n\t\t);\n\t});\n\nconst readDispatchRouteScan = (\n\tfs: FileSystem.FileSystem,\n\tdispatchDir: string,\n\tdispatchFileId: string,\n\toptions: { readonly strict?: boolean } = {},\n): Effect.Effect<DispatchRouteScan, DispatchWriteFailed> =>\n\tEffect.gen(function* () {\n\t\tconst files = yield* fs.readDirectory(dispatchDir).pipe(\n\t\t\tEffect.mapError(\n\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\tdispatchFileId,\n\t\t\t\t\t\tpath: dispatchDir,\n\t\t\t\t\t\tdetail: `readDirectory(${dispatchDir}) failed`,\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\tconst routeFiles = files\n\t\t\t.map((filename) => ({ filename, fileId: dispatchFileIdFromFilename(filename) }))\n\t\t\t.filter(\n\t\t\t\t(entry): entry is { readonly filename: string; readonly fileId: string } =>\n\t\t\t\t\tentry.fileId !== null,\n\t\t\t);\n\t\tconst parsed = yield* Effect.forEach(\n\t\t\trouteFiles,\n\t\t\t(entry) => {\n\t\t\t\tconst filePath = path.join(dispatchDir, entry.filename);\n\t\t\t\treturn fs.readFileString(filePath).pipe(\n\t\t\t\t\tEffect.map((body) => ({\n\t\t\t\t\t\tpath: filePath,\n\t\t\t\t\t\tresult: parseDispatchRouteFile(body, entry.fileId),\n\t\t\t\t\t})),\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\t\t\tdispatchFileId,\n\t\t\t\t\t\t\t\tpath: filePath,\n\t\t\t\t\t\t\t\tdetail: `readFileString(${filePath}) 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\t{ concurrency: 'unbounded' },\n\t\t);\n\t\tconst routes: DispatchRouteMetadata[] = [];\n\t\tconst unknownRouteFileIds: string[] = [];\n\t\tconst diagnostics: DispatchRouteScanDiagnostic[] = [];\n\t\tfor (const entry of parsed) {\n\t\t\tfor (const diagnostic of entry.result.diagnostics) {\n\t\t\t\tconst withPath = { ...diagnostic, path: entry.path };\n\t\t\t\tdiagnostics.push(withPath);\n\t\t\t\tyield* warnDispatchDecodeDiagnostic(withPath);\n\t\t\t}\n\t\t\tif (entry.result._tag === 'valid') {\n\t\t\t\troutes.push(entry.result.route);\n\t\t\t} else {\n\t\t\t\tunknownRouteFileIds.push(entry.result.dispatchFileId);\n\t\t\t}\n\t\t}\n\t\tif (options.strict === true && diagnostics.length > 0) {\n\t\t\tconst first = diagnostics[0];\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\tdispatchFileId: first?.dispatchFileId ?? dispatchFileId,\n\t\t\t\t\tpath: first?.path ?? dispatchDir,\n\t\t\t\t\tdetail: first?.detail ?? 'dispatch route decode failed',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn { routes, unknownRouteFileIds, diagnostics };\n\t});\n\ntype DispatchLeaseStatus = 'live' | 'stale' | 'unknown-owner';\ntype RoutePublishOwnership = 'direct' | 'owned' | 'reused-live';\n\nconst classifyDispatchLease = (\n\troute: DispatchRouteMetadata,\n): Effect.Effect<DispatchLeaseStatus> => {\n\tif (route.lease === null) return Effect.succeed('unknown-owner');\n\treturn checkHolderLiveness(route.lease.owner).pipe(\n\t\tEffect.map((status) => (status === 'dead' ? 'stale' : 'live')),\n\t\tEffect.catch(() => Effect.succeed('live' as const)),\n\t);\n};\n\nconst sweepStaleDispatchRoutes = (\n\tfs: FileSystem.FileSystem,\n\tprofile: RouterProfile,\n\troutes: ReadonlyArray<DispatchRouteMetadata>,\n\tdispatchFileId: string,\n): Effect.Effect<ReadonlyArray<DispatchRouteMetadata>, DispatchWriteFailed> =>\n\t// Yield a fresh per-sweep `LivenessProbeScope` so a recycled lease\n\t// owner (multiple stale routes pointing at the same dead pid) forks\n\t// `ps`/`tasklist` AT MOST once across the loop — matches the roster\n\t// sweep migration in Phase 9A.\n\tEffect.gen(function* () {\n\t\tconst probe = yield* LivenessProbeScope;\n\t\tconst active: DispatchRouteMetadata[] = [];\n\t\tfor (const route of routes) {\n\t\t\tconst status: DispatchLeaseStatus =\n\t\t\t\troute.lease === null\n\t\t\t\t\t? 'unknown-owner'\n\t\t\t\t\t: yield* probe\n\t\t\t\t\t\t\t.probeHolderLiveness(route.lease.owner)\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\tEffect.map((s) => (s === 'dead' ? ('stale' as const) : ('live' as const))),\n\t\t\t\t\t\t\t\tEffect.catch(() => Effect.succeed('live' as const)),\n\t\t\t\t\t\t\t);\n\t\t\tif (status === 'stale') {\n\t\t\t\tconst filePath = path.join(profile.dispatchDir, dispatchFilename(route.dispatchFileId));\n\t\t\t\tyield* fs.remove(filePath).pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\t\t\tdispatchFileId,\n\t\t\t\t\t\t\t\tpath: filePath,\n\t\t\t\t\t\t\t\tdetail: `failed to remove stale route lease ${route.dispatchFileId}`,\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\tcontinue;\n\t\t\t}\n\t\t\tactive.push(route);\n\t\t}\n\t\treturn active;\n\t}).pipe(Effect.provide(layerLivenessProbeScope));\n\nconst makeRouteLease = (profile: RouterProfile, identity: Identity): RouteLeaseMetadata => ({\n\tversion: ROUTER_ROUTE_LEASE_VERSION,\n\trouterProfileId: profile.id,\n\tapp: String(identity.app),\n\tstack: String(identity.stack),\n\towner: ownHolder(),\n});\n\nconst sameRouteSurface = (\n\ta: Pick<DispatchRouteMetadata, 'hostname' | 'entrypointName' | 'entrypointPort' | 'wireProtocol'>,\n\tb: Pick<ResolvedRoute, 'hostname' | 'entrypointName' | 'entrypointPort' | 'wireProtocol'>,\n): boolean =>\n\ta.hostname === b.hostname &&\n\ta.entrypointName === b.entrypointName &&\n\ta.entrypointPort === b.entrypointPort &&\n\ta.wireProtocol === b.wireProtocol;\n\nconst liveRouteLeaseMismatch = (\n\texisting: DispatchRouteMetadata,\n\tresolved: ResolvedRoute,\n): RouteCollision =>\n\tnew RouteCollision({\n\t\tmessage:\n\t\t\t`router route ${resolved.dispatchFileId} is already leased by a live process ` +\n\t\t\t`with a different public route (existing ${existing.entrypointName}/` +\n\t\t\t`${existing.hostname}, attempted ${resolved.entrypointName}/${resolved.hostname})`,\n\t\thostname: resolved.hostname,\n\t\tentrypoint: resolved.entrypointName,\n\t\tdispatchIds: [existing.dispatchFileId, resolved.dispatchFileId],\n\t});\n\nconst resolvedWireProtocolFor = (decl: RoutableDecl): ResolvedWireProtocol =>\n\tdecl.wireProtocol === 'tcp' ? 'tcp' : decl.wireProtocol === 'h2c' ? 'h2c' : 'http';\n\nconst validateWireProtocolFamily = (\n\tdecl: RoutableDecl,\n\tentrypoint: { readonly name: string; readonly protocol: 'http' | 'h2c' | 'tcp' },\n): Effect.Effect<ResolvedWireProtocol, RouterValidationError> => {\n\tconst wireProtocol = resolvedWireProtocolFor(decl);\n\tconst expectFamily: 'tcp' | 'http' = wireProtocol === 'tcp' ? 'tcp' : 'http';\n\tconst entrypointFamily: 'tcp' | 'http' = entrypoint.protocol === 'tcp' ? 'tcp' : 'http';\n\tif (expectFamily === entrypointFamily) return Effect.succeed(wireProtocol);\n\treturn Effect.fail(\n\t\tnew RouterValidationError({\n\t\t\tfield: 'entrypointName',\n\t\t\tvalue: entrypoint.name,\n\t\t\tdetail:\n\t\t\t\t`wireProtocol family mismatch: decl is '${wireProtocol}' but ` +\n\t\t\t\t`entrypoint '${entrypoint.name}' is '${entrypoint.protocol}'`,\n\t\t}),\n\t);\n};\n\nconst directLoopbackHost = '127.0.0.1';\nexport const DEFAULT_ROUTE_READINESS_TIMEOUT_MS = 60_000;\nconst DEFAULT_ROUTE_READINESS_INTERVAL_MS = 100;\nconst DEFAULT_ROUTE_READINESS_REQUEST_TIMEOUT_MS = 750;\nconst proxyGatewayStatuses = new Set([502, 503, 504]);\n\nconst responseHasReadyRoute = (response: Response, resolved: ResolvedRoute): boolean =>\n\tresponse.headers.get(ROUTE_READINESS_HEADER) === resolved.dispatchFileId &&\n\t!proxyGatewayStatuses.has(response.status);\n\nconst fetchHttpRouteViaLoopback: HttpProbeFetch = (input, init) =>\n\tnew Promise<Response>((resolveResponse, rejectResponse) => {\n\t\tconst url = new URL(String(input));\n\t\tconst headers = new Headers(init?.headers);\n\t\tconst signal = init?.signal ?? undefined;\n\t\tconst hostHeader = headers.get('host') ?? headers.get('Host') ?? url.host;\n\t\theaders.delete('host');\n\t\theaders.delete('Host');\n\t\tconst requestHeaders: Record<string, string> = {};\n\t\theaders.forEach((value, key) => {\n\t\t\trequestHeaders[key] = value;\n\t\t});\n\t\trequestHeaders.host = hostHeader;\n\t\tlet settled = false;\n\t\tlet req: ReturnType<typeof httpRequest> | null = null;\n\t\tfunction onAbort(): void {\n\t\t\tconst cause = new Error('route readiness probe aborted');\n\t\t\treq?.destroy(cause);\n\t\t\tsettle(rejectResponse, cause);\n\t\t}\n\t\tconst settle = <T>(fn: (value: T) => void, value: T): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (signal !== undefined) {\n\t\t\t\tsignal.removeEventListener('abort', onAbort);\n\t\t\t}\n\t\t\tfn(value);\n\t\t};\n\t\treq = httpRequest(\n\t\t\t{\n\t\t\t\thostname: directLoopbackHost,\n\t\t\t\tport: url.port === '' ? 80 : Number(url.port),\n\t\t\t\tpath: `${url.pathname}${url.search}`,\n\t\t\t\tmethod: init?.method ?? 'GET',\n\t\t\t\theaders: requestHeaders,\n\t\t\t},\n\t\t\t(res) => {\n\t\t\t\tconst chunks: Uint8Array[] = [];\n\t\t\t\tres.on('data', (chunk: Buffer | string) => {\n\t\t\t\t\tchunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);\n\t\t\t\t});\n\t\t\t\tres.on('end', () => {\n\t\t\t\t\tconst responseHeaders = new Headers();\n\t\t\t\t\tfor (const [key, value] of Object.entries(res.headers)) {\n\t\t\t\t\t\tif (value === undefined) continue;\n\t\t\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\t\t\tfor (const item of value) responseHeaders.append(key, item);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresponseHeaders.set(key, value);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tsettle(\n\t\t\t\t\t\tresolveResponse,\n\t\t\t\t\t\tnew Response(new Uint8Array(Buffer.concat(chunks)), {\n\t\t\t\t\t\t\tstatus: res.statusCode ?? 599,\n\t\t\t\t\t\t\tstatusText: res.statusMessage,\n\t\t\t\t\t\t\theaders: responseHeaders,\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);\n\t\tif (signal?.aborted === true) {\n\t\t\tonAbort();\n\t\t\treturn;\n\t\t}\n\t\tsignal?.addEventListener('abort', onAbort, { once: true });\n\t\treq.on('error', (cause) => settle(rejectResponse, cause));\n\t\treq.end();\n\t});\n\nconst resolveDisabledDirectRoute = (\n\tidentity: Identity,\n\tdecl: RoutableDecl,\n\tregistry: EntrypointRegistryShape,\n): Effect.Effect<\n\tResolvedRoute,\n\tUnknownEntrypoint | RouterDisabledRouteUnsupported | RouterValidationError\n> =>\n\tEffect.gen(function* () {\n\t\tif (decl.upstream.type === 'container') {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew RouterDisabledRouteUnsupported({\n\t\t\t\t\tendpointName: decl.endpointName,\n\t\t\t\t\tupstreamKind: 'container',\n\t\t\t\t\tdetail:\n\t\t\t\t\t\t'router is disabled, but container upstreams are only reachable through the router network/proxy; ' +\n\t\t\t\t\t\t'use a host-loopback upstream or publish an explicit direct endpoint instead',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tconst port = decl.upstream.port;\n\t\tif (!Number.isInteger(port) || port <= 0 || port > 65535) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew RouterValidationError({\n\t\t\t\t\tfield: 'upstreamUrl',\n\t\t\t\t\tvalue: `${directLoopbackHost}:${port}`,\n\t\t\t\t\tdetail: 'disabled-router direct host-loopback routes require a concrete TCP port 1-65535',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tconst entrypoint = yield* registry.byName(decl.endpointName);\n\t\tconst wireProtocol = yield* validateWireProtocolFamily(decl, entrypoint);\n\t\tconst fileId = yield* dispatchFileId({ identity, dispatch: decl.dispatchId });\n\t\tconst scheme = wireProtocol === 'tcp' ? 'tcp' : 'http';\n\t\treturn {\n\t\t\tdispatchFileId: fileId,\n\t\t\thostname: directLoopbackHost,\n\t\t\tentrypointName: entrypoint.name,\n\t\t\tentrypointPort: port,\n\t\t\tupstreamUrl: `${scheme}://${directLoopbackHost}:${port}`,\n\t\t\tcors: decl.wireProtocol === 'tcp' ? false : decl.cors,\n\t\t\twireProtocol,\n\t\t};\n\t});\n\nconst endpointFromResolvedRoute = (decl: RoutableDecl, resolved: ResolvedRoute): EndpointUrl => {\n\tconst url =\n\t\tresolved.wireProtocol === 'tcp'\n\t\t\t? `tcp://127.0.0.1:${resolved.entrypointPort}`\n\t\t\t: `http://${resolved.hostname}:${resolved.entrypointPort}`;\n\treturn {\n\t\tendpointName: decl.endpointName,\n\t\thostname: resolved.hostname,\n\t\tentrypointPort: resolved.entrypointPort,\n\t\turl,\n\t\twireProtocol: resolved.wireProtocol,\n\t};\n};\n\nconst removeDispatchFile = (\n\tfs: FileSystem.FileSystem,\n\tprofile: RouterProfile,\n\tresolved: ResolvedRoute,\n): Effect.Effect<void> =>\n\tfs\n\t\t.remove(path.join(profile.dispatchDir, dispatchFilename(resolved.dispatchFileId)))\n\t\t.pipe(Effect.ignore);\n\nconst waitForPublicRouteReadiness = (\n\tcfg: RouterConfigShape,\n\tdecl: RoutableDecl,\n\tendpoint: EndpointUrl,\n\tresolved: ResolvedRoute,\n): Effect.Effect<void, RouteReadinessProbeFailed> => {\n\tconst options = cfg.routeReadinessProbe;\n\tif (\n\t\toptions?.enabled !== true ||\n\t\tcfg.disabled ||\n\t\tresolved.wireProtocol === 'tcp' ||\n\t\tdecl.readiness === 'deferred'\n\t) {\n\t\treturn Effect.void;\n\t}\n\tconst timeoutMs = options.timeoutMs ?? DEFAULT_ROUTE_READINESS_TIMEOUT_MS;\n\tconst intervalMs = options.intervalMs ?? DEFAULT_ROUTE_READINESS_INTERVAL_MS;\n\tconst requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_ROUTE_READINESS_REQUEST_TIMEOUT_MS;\n\tconst probeUrl = `http://${directLoopbackHost}:${resolved.entrypointPort}`;\n\treturn waitForHttpEndpoint({\n\t\tendpoint: probeUrl,\n\t\ttimeoutMs,\n\t\tintervalMs,\n\t\trequestTimeoutMs,\n\t\trequestInit: { headers: { host: resolved.hostname } },\n\t\tfetch: options.fetch ?? fetchHttpRouteViaLoopback,\n\t\tvalidate: (response) => responseHasReadyRoute(response, resolved),\n\t}).pipe(\n\t\tEffect.mapError(\n\t\t\t(cause): RouteReadinessProbeFailed =>\n\t\t\t\tnew RouteReadinessProbeFailed({\n\t\t\t\t\tdispatchFileId: resolved.dispatchFileId,\n\t\t\t\t\turl: endpoint.url,\n\t\t\t\t\ttimeoutMs,\n\t\t\t\t\tdetail:\n\t\t\t\t\t\t`public router endpoint ${endpoint.url} did not serve route ` +\n\t\t\t\t\t\t`${resolved.dispatchFileId} within ${timeoutMs}ms ` +\n\t\t\t\t\t\t`(probeUrl=${probeUrl}, hostHeader=${resolved.hostname})`,\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t),\n\t);\n};\n\n// ---------------------------------------------------------------------------\n// Layer — wire the orchestrator\n// ---------------------------------------------------------------------------\n\nexport const layerRouterService: Layer.Layer<\n\tRouterService,\n\tnever,\n\t| RouterConfig\n\t| IdentityContext\n\t| EntrypointRegistry\n\t| TraefikContainerOpsService\n\t| UpstreamResolverService\n\t| FileSystem.FileSystem\n> = Layer.effect(\n\tRouterService,\n\tEffect.gen(function* () {\n\t\tconst cfg = yield* RouterConfig;\n\t\tconst identity = yield* IdentityContext;\n\t\tconst registry = yield* EntrypointRegistry;\n\t\tconst upstreams = yield* UpstreamResolverService;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\t// Capture the Traefik ops service in the layer's outer scope so\n\t\t// `bootstrap` (which reads it from Context) doesn't surface\n\t\t// requirements through `boot()`'s return type. Provided to\n\t\t// downstream calls via `Effect.provideService`.\n\t\tconst traefikOps = yield* TraefikContainerOpsService;\n\n\t\t// Cached boot report — set on first `boot()` call.\n\t\tconst bootRef = yield* Ref.make<BootReport | null>(null);\n\n\t\t// Applied routes — subscribable for diagnostics. Empty until the\n\t\t// first `contributeRoute()`. The orchestrator publishes the\n\t\t// *resolved* shape (post URL-resolution) rather than the raw\n\t\t// decls so downstream surfaces don't have to re-resolve.\n\t\tconst applied = yield* SubscriptionRef.make<ReadonlyArray<ResolvedRoute>>([]);\n\n\t\t// ---------------------------------------------------------------\n\t\t// boot — adopt-or-create the Traefik container.\n\t\t// ---------------------------------------------------------------\n\t\tconst boot: RouterServiceShape['boot'] = () =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst profile = cfg.profile;\n\t\t\t\tif (cfg.disabled) {\n\t\t\t\t\tconst report: BootReport = {\n\t\t\t\t\t\tdecision: 'opt-out',\n\t\t\t\t\t\tcontainerId: null,\n\t\t\t\t\t\tnetworkId: null,\n\t\t\t\t\t\timageMatches: true,\n\t\t\t\t\t};\n\t\t\t\t\tyield* Ref.set(bootRef, report);\n\t\t\t\t\treturn report;\n\t\t\t\t}\n\t\t\t\tconst cached = yield* Ref.get(bootRef);\n\t\t\t\tif (cached !== null) return cached;\n\t\t\t\t// Single outer scope holds BOTH locks for the entire boot duration.\n\t\t\t\t// Acquire dispatch lock first (outer), then bootstrap lock (inner) so\n\t\t\t\t// scope finalizers release in reverse order: bootstrap lock first, then\n\t\t\t\t// dispatch lock. `protectedRouteLeaseIds` is computed under the\n\t\t\t\t// dispatch lock and consumed by `bootstrap` while still under the same\n\t\t\t\t// lock — no peer-write window can publish a new dispatch route file\n\t\t\t\t// between the scan and the bootstrap-time forceRemove decision.\n\t\t\t\t// STYLE_GUIDE §18 cross-process protocol — router boot must hold the\n\t\t\t\t// dispatch lock across the scan + bootstrap critical section, exactly\n\t\t\t\t// the same way `contributeRoute` holds it across write + probe.\n\t\t\t\tconst report = yield* Effect.scoped(\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tyield* acquireStackLock(profile.dispatchLockFile, ROUTER_LOCK_TIMEOUT_MILLIS).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'ensure-container',\n\t\t\t\t\t\t\t\t\t\tdetail: `failed to acquire router dispatch lock ${profile.dispatchLockFile}`,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\t// Re-check after acquiring the dispatch lock: a peer fiber\n\t\t\t\t\t\t// (parallel plugin acquire) may have finished boot while we\n\t\t\t\t\t\t// waited for this lock. The O_EXCL lock serializes in-process\n\t\t\t\t\t\t// fibers too, and the winner sets `bootRef` below while still\n\t\t\t\t\t\t// holding the lock — so a non-null read here means boot is\n\t\t\t\t\t\t// done; skip the redundant bootstrap (docker inspect + decision).\n\t\t\t\t\t\tconst bootedByPeer = yield* Ref.get(bootRef);\n\t\t\t\t\t\tif (bootedByPeer !== null) return bootedByPeer;\n\t\t\t\t\t\tyield* fs.makeDirectory(profile.dispatchDir, { recursive: true }).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'write-shared-config',\n\t\t\t\t\t\t\t\t\t\tdetail: `makeDirectory(${profile.dispatchDir}) failed`,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tyield* atomicWriteFile(\n\t\t\t\t\t\t\tpath.join(profile.dispatchDir, CORS_MIDDLEWARE_FILENAME),\n\t\t\t\t\t\t\tnew TextEncoder().encode(renderCorsMiddlewareYaml()),\n\t\t\t\t\t\t\t{ mode: 0o644 },\n\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'write-shared-config',\n\t\t\t\t\t\t\t\t\t\tdetail: `atomicWriteFile(${CORS_MIDDLEWARE_FILENAME}) failed at stage ${cause.stage}`,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tEffect.provideService(FileSystem.FileSystem, fs),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst existingDispatchScan = yield* readDispatchRouteScan(\n\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\tprofile.dispatchDir,\n\t\t\t\t\t\t\t'router-boot',\n\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'inspect',\n\t\t\t\t\t\t\t\t\t\tdetail: cause.detail,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst activeDispatchRoutes = yield* sweepStaleDispatchRoutes(\n\t\t\t\t\t\t\tfs,\n\t\t\t\t\t\t\tprofile,\n\t\t\t\t\t\t\texistingDispatchScan.routes,\n\t\t\t\t\t\t\t'router-boot',\n\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'write-shared-config',\n\t\t\t\t\t\t\t\t\t\tdetail: cause.detail,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst protectedRouteLeaseIds = [\n\t\t\t\t\t\t\t...activeDispatchRoutes.map((route) => route.dispatchFileId),\n\t\t\t\t\t\t\t...existingDispatchScan.unknownRouteFileIds,\n\t\t\t\t\t\t];\n\t\t\t\t\t\tyield* acquireStackLock(profile.bootstrapLockFile, ROUTER_LOCK_TIMEOUT_MILLIS).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): RouterBootFailed =>\n\t\t\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\t\t\tstage: 'ensure-container',\n\t\t\t\t\t\t\t\t\t\tdetail: `failed to acquire router bootstrap lock ${profile.bootstrapLockFile}`,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst booted = yield* bootstrap({\n\t\t\t\t\t\t\timage: cfg.image,\n\t\t\t\t\t\t\tentrypoints: registry.all(),\n\t\t\t\t\t\t\tprofile,\n\t\t\t\t\t\t\tprotectedRouteLeaseIds,\n\t\t\t\t\t\t}).pipe(Effect.provideService(TraefikContainerOpsService, traefikOps));\n\t\t\t\t\t\t// Publish the cached report WHILE still holding the locks so a\n\t\t\t\t\t\t// peer fiber's post-lock re-check (above) observes it — the\n\t\t\t\t\t\t// locks release at scope close, which is after this set. On\n\t\t\t\t\t\t// bootstrap failure we never reach here, so `bootRef` stays\n\t\t\t\t\t\t// null and a later boot() retries (retry-on-failure preserved).\n\t\t\t\t\t\tyield* Ref.set(bootRef, booted);\n\t\t\t\t\t\treturn booted;\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn report;\n\t\t\t}).pipe(Effect.withSpan('orchestrator.router.boot'));\n\n\t\t// ---------------------------------------------------------------\n\t\t// contributeRoute — resolve + write dispatch file + scope finalizer.\n\t\t// ---------------------------------------------------------------\n\t\tconst contributeRoute: RouterServiceShape['contributeRoute'] = (decl) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst profile = cfg.profile;\n\t\t\t\tconst resolved = cfg.disabled\n\t\t\t\t\t? yield* resolveDisabledDirectRoute(identity, decl, registry)\n\t\t\t\t\t: yield* resolveRoute(identity, decl, registry, upstreams);\n\t\t\t\tconst lease = makeRouteLease(profile, identity);\n\t\t\t\tconst endpoint = endpointFromResolvedRoute(decl, resolved);\n\t\t\t\tconst publishRouteFile: Effect.Effect<\n\t\t\t\t\tRoutePublishOwnership,\n\t\t\t\t\tDispatchWriteFailed | RouteCollision\n\t\t\t\t> = Effect.gen(function* () {\n\t\t\t\t\t// Collision check against this process's applied set\n\t\t\t\t\t// plus files already present in the shared dispatch\n\t\t\t\t\t// directory. The dispatch lock makes the scan + write a\n\t\t\t\t\t// cross-process critical section.\n\t\t\t\t\t// WHY: disabled mode resolves direct-loopback routes with no\n\t\t\t\t\t// proxy dispatch file, so there is no shared resource a\n\t\t\t\t\t// duplicate could clobber — intentionally skip\n\t\t\t\t\t// `detectCollisions` and short-circuit before the\n\t\t\t\t\t// dispatch-dir scan/write below.\n\t\t\t\t\tif (cfg.disabled) return 'direct';\n\t\t\t\t\tif (!cfg.disabled) {\n\t\t\t\t\t\tyield* fs.makeDirectory(profile.dispatchDir, { recursive: true }).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\t\t\t\t\tdispatchFileId: resolved.dispatchFileId,\n\t\t\t\t\t\t\t\t\t\tpath: profile.dispatchDir,\n\t\t\t\t\t\t\t\t\t\tdetail: `makeDirectory(${profile.dispatchDir}) failed`,\n\t\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t\t}),\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\tconst currentApplied = yield* SubscriptionRef.get(applied);\n\t\t\t\t\tconst currentIds = new Set(currentApplied.map((route) => route.dispatchFileId));\n\t\t\t\t\tlet reuseLiveRoute = false;\n\t\t\t\t\tconst readScan = yield* readDispatchRouteScan(\n\t\t\t\t\t\tfs,\n\t\t\t\t\t\tprofile.dispatchDir,\n\t\t\t\t\t\tresolved.dispatchFileId,\n\t\t\t\t\t);\n\t\t\t\t\tconst activeDispatchRoutes = yield* sweepStaleDispatchRoutes(\n\t\t\t\t\t\tfs,\n\t\t\t\t\t\tprofile,\n\t\t\t\t\t\treadScan.routes,\n\t\t\t\t\t\tresolved.dispatchFileId,\n\t\t\t\t\t);\n\t\t\t\t\tconst existingSameDispatchRoute = activeDispatchRoutes.find(\n\t\t\t\t\t\t(route) => route.dispatchFileId === resolved.dispatchFileId,\n\t\t\t\t\t);\n\t\t\t\t\tif (\n\t\t\t\t\t\texistingSameDispatchRoute !== undefined &&\n\t\t\t\t\t\t!currentIds.has(existingSameDispatchRoute.dispatchFileId)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst status = yield* classifyDispatchLease(existingSameDispatchRoute);\n\t\t\t\t\t\tif (status === 'live') {\n\t\t\t\t\t\t\tif (!sameRouteSurface(existingSameDispatchRoute, resolved)) {\n\t\t\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\t\t\tliveRouteLeaseMismatch(existingSameDispatchRoute, resolved),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treuseLiveRoute = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tconst existingDispatchRoutes = activeDispatchRoutes.filter(\n\t\t\t\t\t\t(route) =>\n\t\t\t\t\t\t\t!currentIds.has(route.dispatchFileId) &&\n\t\t\t\t\t\t\troute.dispatchFileId !== resolved.dispatchFileId,\n\t\t\t\t\t);\n\t\t\t\t\tconst collision = detectCollisions([\n\t\t\t\t\t\t...existingDispatchRoutes,\n\t\t\t\t\t\t...currentApplied,\n\t\t\t\t\t\tresolved,\n\t\t\t\t\t]);\n\t\t\t\t\tif (collision) return yield* Effect.fail(collision);\n\t\t\t\t\tif (reuseLiveRoute) return 'reused-live';\n\n\t\t\t\t\t// Atomic write — invariant #5. tmp + rename via the\n\t\t\t\t\t// substrate's `atomicWriteFile` helper; Traefik's\n\t\t\t\t\t// watcher tolerates rename atomically.\n\t\t\t\t\tconst filePath = path.join(\n\t\t\t\t\t\tprofile.dispatchDir,\n\t\t\t\t\t\tdispatchFilename(resolved.dispatchFileId),\n\t\t\t\t\t);\n\t\t\t\t\tyield* atomicWriteFile(\n\t\t\t\t\t\tfilePath,\n\t\t\t\t\t\tnew TextEncoder().encode(renderRouteYaml(resolved, lease)),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmode: 0o644,\n\t\t\t\t\t\t},\n\t\t\t\t\t).pipe(\n\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\t\t\t\tdispatchFileId: resolved.dispatchFileId,\n\t\t\t\t\t\t\t\t\tpath: filePath,\n\t\t\t\t\t\t\t\t\tdetail: `atomicWriteFile failed at stage ${cause.stage}`,\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tEffect.provideService(FileSystem.FileSystem, fs),\n\t\t\t\t\t);\n\t\t\t\t\treturn 'owned';\n\t\t\t\t});\n\n\t\t\t\tlet publishOwnership: RoutePublishOwnership;\n\t\t\t\tif (cfg.disabled) {\n\t\t\t\t\tpublishOwnership = yield* publishRouteFile;\n\t\t\t\t\tyield* waitForPublicRouteReadiness(cfg, decl, endpoint, resolved);\n\t\t\t\t\tif (publishOwnership !== 'reused-live') {\n\t\t\t\t\t\tyield* SubscriptionRef.update(applied, (arr) => [...arr, resolved]);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// The dispatch lock MUST be held across (a) the on-disk\n\t\t\t\t\t// scan + write inside `publishRouteFile`, (b) the\n\t\t\t\t\t// readiness probe, AND (c) the in-process\n\t\t\t\t\t// `SubscriptionRef.update(applied, …)` that publishes the\n\t\t\t\t\t// new route to peer fibers in this process. Releasing\n\t\t\t\t\t// the lock before the SubscriptionRef update would let a\n\t\t\t\t\t// concurrent in-process `contributeRoute` sample the\n\t\t\t\t\t// stale `applied` set at line 803 even though our\n\t\t\t\t\t// dispatch file is already on disk — the on-disk scan\n\t\t\t\t\t// covers cross-process visibility but the lock must also\n\t\t\t\t\t// gate the in-process publish so the two views agree.\n\t\t\t\t\t// See STYLE_GUIDE §18 cross-process protocol — router\n\t\t\t\t\t// contributeRoute serialization rule.\n\t\t\t\t\tpublishOwnership = yield* Effect.scoped(\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tyield* acquireStackLock(profile.dispatchLockFile, ROUTER_LOCK_TIMEOUT_MILLIS).pipe(\n\t\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t\t(cause): DispatchWriteFailed =>\n\t\t\t\t\t\t\t\t\t\tnew DispatchWriteFailed({\n\t\t\t\t\t\t\t\t\t\t\tdispatchFileId: resolved.dispatchFileId,\n\t\t\t\t\t\t\t\t\t\t\tpath: profile.dispatchLockFile,\n\t\t\t\t\t\t\t\t\t\t\tdetail: `failed to acquire dispatch lock ${profile.dispatchLockFile}`,\n\t\t\t\t\t\t\t\t\t\t\tcause,\n\t\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\tconst ownership = yield* publishRouteFile;\n\t\t\t\t\t\t\tyield* waitForPublicRouteReadiness(cfg, decl, endpoint, resolved).pipe(\n\t\t\t\t\t\t\t\tEffect.onError(() =>\n\t\t\t\t\t\t\t\t\townership !== 'owned' ? Effect.void : removeDispatchFile(fs, profile, resolved),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (ownership !== 'reused-live') {\n\t\t\t\t\t\t\t\tyield* SubscriptionRef.update(applied, (arr) => [...arr, resolved]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn ownership;\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (publishOwnership !== 'reused-live') {\n\t\t\t\t\t// Scope finalizer — remove the file + drop from applied\n\t\t\t\t\t// when the caller's scope closes. Best-effort: \"already\n\t\t\t\t\t// gone\" is fine, but lock contention / IO failures surface\n\t\t\t\t\t// via logWarning so leaked dispatch files don't go silent\n\t\t\t\t\t// (STYLE_GUIDE §18 — `Effect.ignore` on `acquireStackLock`\n\t\t\t\t\t// without a tap is forbidden).\n\t\t\t\t\tyield* Effect.addFinalizer(() =>\n\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\tif (publishOwnership === 'owned') {\n\t\t\t\t\t\t\t\tyield* Effect.scoped(\n\t\t\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\t\t\tyield* acquireStackLock(profile.dispatchLockFile, ROUTER_LOCK_TIMEOUT_MILLIS);\n\t\t\t\t\t\t\t\t\t\tyield* removeDispatchFile(fs, profile, resolved);\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t).pipe(\n\t\t\t\t\t\t\t\t\tlogWarningAndIgnore('router scope-close cleanup failed', {\n\t\t\t\t\t\t\t\t\t\tdispatchFileId: resolved.dispatchFileId,\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\tyield* SubscriptionRef.update(applied, (arr) =>\n\t\t\t\t\t\t\t\tarr.filter((r) => r.dispatchFileId !== resolved.dispatchFileId),\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\n\t\t\t\treturn endpoint;\n\t\t\t}).pipe(Effect.withSpan('orchestrator.router.contributeRoute'));\n\n\t\treturn RouterService.of({ boot, contributeRoute, applied });\n\t}),\n);\n\n// ---------------------------------------------------------------------------\n// Helpers for tests + composition\n// ---------------------------------------------------------------------------\n\n/** Stream of the resolved-route set. Use from a renderer / inventory\n * surface to react to route adds + removes. The supervisor's main\n * loop subscribes through this stream. */\nexport const routesStream = (\n\trouter: RouterServiceShape,\n): Stream.Stream<ReadonlyArray<ResolvedRoute>> => SubscriptionRef.changes(router.applied);\n\n// Re-export for ergonomics — callers reach for the orchestrator\n// without having to spell out the sibling module paths.\nexport { type ResolvedRoute, type UpstreamResolver } from './file-provider.ts';\nexport type { BootReport } from './traefik-container.ts';\nexport type { UpstreamResolveTimeout } from './errors.ts';\nexport type { Identity } from '../../substrate/identity.ts';\nexport type { RoutableDecl } from '../../contracts/routable.ts';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2FA,MAAM,6BAA6B;AAoCnC,IAAa,eAAb,cAAkC,QAAQ,SAA0C,CACnF,8CACA,CAAC;;;AAIF,MAAa,4BAA4B,QACxC,MAAM,QAAQ,aAAa,CAAC,IAAI;AAMjC,IAAa,0BAAb,cAA6C,QAAQ,SAGlD,CAAC,kDAAkD,CAAC;AAEvD,MAAa,+BACZ,YAEA,MAAM,OACL,yBACA,OAAO,IAAI,aAAa;CACvB,MAAM,aAAa,OAAO;CAC1B,MAAM,gBAAgB,OAAO;CAC7B,MAAM,cAAc,QAAQ;CAC5B,MAAM,iBACL,WAEA,OAAO,KACN,OAAO,eAAe,YAAY,WAAW,EAC7C,OAAO,eAAe,eAAe,cAAc,CACnD;AAEF,QAAO,wBAAwB,GAAG;EACjC,mBAAmB,WAClB,cACC,QAAQ,OAAO,eAAe,YAAY,CAAC,KAC1C,OAAO,QAAQ,UAAU,OAAO,eAAe,YAAY,CAAC,CAC5D,CACD,CAAC,KACD,OAAO,KAAK,UAAU;GAAE;GAAM,MAAM,OAAO;GAAe,EAAE,EAC5D,OAAO,UACL,UACA,IAAI,sBAAsB;GACzB,OAAO;GACP,OAAO,OAAO;GACd,QAAQ,+DAA+D,OAAO,MAAM;GACpF,CAAC,CACH,CACD;EACF,sBAAsB,WACrB,OAAO,QAAQ;GAAE,MAAM;GAAwB,MAAM,OAAO;GAAM,CAAC;EACpE,CAAC;EACD,CACF;AAyCF,IAAa,gBAAb,cAAmC,QAAQ,SAA4C,CACtF,wCACA,CAAC;AAYF,MAAM,gCACL,eAEA,OAAO,IAAI,aAAa;AACvB,QAAO,OAAO,oBAAoB;EACjC,wBAAwB,WAAW;EACnC,2BAA2B,WAAW;EACtC,iCAAiC,WAAW;EAC5C,CAAC;AACF,QAAO,OAAO,WACb,8BAA8B,WAAW,KAAK,sCACnC,WAAW,OAAO,oFAC7B;EACA;AAEH,MAAM,yBACL,IACA,aACA,gBACA,UAAyC,EAAE,KAE3C,OAAO,IAAI,aAAa;CAYvB,MAAM,cAAa,OAXE,GAAG,cAAc,YAAY,CAAC,KAClD,OAAO,UACL,UACA,IAAI,oBAAoB;EACvB;EACA,MAAM;EACN,QAAQ,iBAAiB,YAAY;EACrC;EACA,CAAC,CACH,CACD,EAEC,KAAK,cAAc;EAAE;EAAU,QAAQ,2BAA2B,SAAS;EAAE,EAAE,CAC/E,QACC,UACA,MAAM,WAAW,KAClB;CACF,MAAM,SAAS,OAAO,OAAO,QAC5B,aACC,UAAU;EACV,MAAM,WAAW,KAAK,KAAK,aAAa,MAAM,SAAS;AACvD,SAAO,GAAG,eAAe,SAAS,CAAC,KAClC,OAAO,KAAK,UAAU;GACrB,MAAM;GACN,QAAQ,uBAAuB,MAAM,MAAM,OAAO;GAClD,EAAE,EACH,OAAO,UACL,UACA,IAAI,oBAAoB;GACvB;GACA,MAAM;GACN,QAAQ,kBAAkB,SAAS;GACnC;GACA,CAAC,CACH,CACD;IAEF,EAAE,aAAa,aAAa,CAC5B;CACD,MAAM,SAAkC,EAAE;CAC1C,MAAM,sBAAgC,EAAE;CACxC,MAAM,cAA6C,EAAE;AACrD,MAAK,MAAM,SAAS,QAAQ;AAC3B,OAAK,MAAM,cAAc,MAAM,OAAO,aAAa;GAClD,MAAM,WAAW;IAAE,GAAG;IAAY,MAAM,MAAM;IAAM;AACpD,eAAY,KAAK,SAAS;AAC1B,UAAO,6BAA6B,SAAS;;AAE9C,MAAI,MAAM,OAAO,SAAS,QACzB,QAAO,KAAK,MAAM,OAAO,MAAM;MAE/B,qBAAoB,KAAK,MAAM,OAAO,eAAe;;AAGvD,KAAI,QAAQ,WAAW,QAAQ,YAAY,SAAS,GAAG;EACtD,MAAM,QAAQ,YAAY;AAC1B,SAAO,OAAO,OAAO,KACpB,IAAI,oBAAoB;GACvB,gBAAgB,OAAO,kBAAkB;GACzC,MAAM,OAAO,QAAQ;GACrB,QAAQ,OAAO,UAAU;GACzB,CAAC,CACF;;AAEF,QAAO;EAAE;EAAQ;EAAqB;EAAa;EAClD;AAKH,MAAM,yBACL,UACwC;AACxC,KAAI,MAAM,UAAU,KAAM,QAAO,OAAO,QAAQ,gBAAgB;AAChE,QAAO,oBAAoB,MAAM,MAAM,MAAM,CAAC,KAC7C,OAAO,KAAK,WAAY,WAAW,SAAS,UAAU,OAAQ,EAC9D,OAAO,YAAY,OAAO,QAAQ,OAAgB,CAAC,CACnD;;AAGF,MAAM,4BACL,IACA,SACA,QACA,mBAMA,OAAO,IAAI,aAAa;CACvB,MAAM,QAAQ,OAAO;CACrB,MAAM,SAAkC,EAAE;AAC1C,MAAK,MAAM,SAAS,QAAQ;AAU3B,OARC,MAAM,UAAU,OACb,kBACA,OAAO,MACN,oBAAoB,MAAM,MAAM,MAAM,CACtC,KACA,OAAO,KAAK,MAAO,MAAM,SAAU,UAAqB,OAAkB,EAC1E,OAAO,YAAY,OAAO,QAAQ,OAAgB,CAAC,CACnD,MACU,SAAS;GACvB,MAAM,WAAW,KAAK,KAAK,QAAQ,aAAa,iBAAiB,MAAM,eAAe,CAAC;AACvF,UAAO,GAAG,OAAO,SAAS,CAAC,KAC1B,OAAO,UACL,UACA,IAAI,oBAAoB;IACvB;IACA,MAAM;IACN,QAAQ,sCAAsC,MAAM;IACpD;IACA,CAAC,CACH,CACD;AACD;;AAED,SAAO,KAAK,MAAM;;AAEnB,QAAO;EACN,CAAC,KAAK,OAAO,QAAQ,wBAAwB,CAAC;AAEjD,MAAM,kBAAkB,SAAwB,cAA4C;CAC3F,SAAA;CACA,iBAAiB,QAAQ;CACzB,KAAK,OAAO,SAAS,IAAI;CACzB,OAAO,OAAO,SAAS,MAAM;CAC7B,OAAO,WAAW;CAClB;AAED,MAAM,oBACL,GACA,MAEA,EAAE,aAAa,EAAE,YACjB,EAAE,mBAAmB,EAAE,kBACvB,EAAE,mBAAmB,EAAE,kBACvB,EAAE,iBAAiB,EAAE;AAEtB,MAAM,0BACL,UACA,aAEA,IAAI,eAAe;CAClB,SACC,gBAAgB,SAAS,eAAe,+EACG,SAAS,eAAe,GAChE,SAAS,SAAS,cAAc,SAAS,eAAe,GAAG,SAAS,SAAS;CACjF,UAAU,SAAS;CACnB,YAAY,SAAS;CACrB,aAAa,CAAC,SAAS,gBAAgB,SAAS,eAAe;CAC/D,CAAC;AAEH,MAAM,2BAA2B,SAChC,KAAK,iBAAiB,QAAQ,QAAQ,KAAK,iBAAiB,QAAQ,QAAQ;AAE7E,MAAM,8BACL,MACA,eACgE;CAChE,MAAM,eAAe,wBAAwB,KAAK;AAGlD,MAFqC,iBAAiB,QAAQ,QAAQ,aAC7B,WAAW,aAAa,QAAQ,QAAQ,QAC1C,QAAO,OAAO,QAAQ,aAAa;AAC1E,QAAO,OAAO,KACb,IAAI,sBAAsB;EACzB,OAAO;EACP,OAAO,WAAW;EAClB,QACC,0CAA0C,aAAa,oBACxC,WAAW,KAAK,QAAQ,WAAW,SAAS;EAC5D,CAAC,CACF;;AAGF,MAAM,qBAAqB;AAE3B,MAAM,sCAAsC;AAC5C,MAAM,6CAA6C;AACnD,MAAM,uBAAuB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAI,CAAC;AAErD,MAAM,yBAAyB,UAAoB,aAClD,SAAS,QAAQ,IAAA,sBAA2B,KAAK,SAAS,kBAC1D,CAAC,qBAAqB,IAAI,SAAS,OAAO;AAE3C,MAAM,6BAA6C,OAAO,SACzD,IAAI,SAAmB,iBAAiB,mBAAmB;CAC1D,MAAM,MAAM,IAAI,IAAI,OAAO,MAAM,CAAC;CAClC,MAAM,UAAU,IAAI,QAAQ,MAAM,QAAQ;CAC1C,MAAM,SAAS,MAAM,UAAU,KAAA;CAC/B,MAAM,aAAa,QAAQ,IAAI,OAAO,IAAI,QAAQ,IAAI,OAAO,IAAI,IAAI;AACrE,SAAQ,OAAO,OAAO;AACtB,SAAQ,OAAO,OAAO;CACtB,MAAM,iBAAyC,EAAE;AACjD,SAAQ,SAAS,OAAO,QAAQ;AAC/B,iBAAe,OAAO;GACrB;AACF,gBAAe,OAAO;CACtB,IAAI,UAAU;CACd,IAAI,MAA6C;CACjD,SAAS,UAAgB;EACxB,MAAM,wBAAQ,IAAI,MAAM,gCAAgC;AACxD,OAAK,QAAQ,MAAM;AACnB,SAAO,gBAAgB,MAAM;;CAE9B,MAAM,UAAa,IAAwB,UAAmB;AAC7D,MAAI,QAAS;AACb,YAAU;AACV,MAAI,WAAW,KAAA,EACd,QAAO,oBAAoB,SAAS,QAAQ;AAE7C,KAAG,MAAM;;AAEV,OAAMA,QACL;EACC,UAAU;EACV,MAAM,IAAI,SAAS,KAAK,KAAK,OAAO,IAAI,KAAK;EAC7C,MAAM,GAAG,IAAI,WAAW,IAAI;EAC5B,QAAQ,MAAM,UAAU;EACxB,SAAS;EACT,GACA,QAAQ;EACR,MAAM,SAAuB,EAAE;AAC/B,MAAI,GAAG,SAAS,UAA2B;AAC1C,UAAO,KAAK,OAAO,UAAU,WAAW,OAAO,KAAK,MAAM,GAAG,MAAM;IAClE;AACF,MAAI,GAAG,aAAa;GACnB,MAAM,kBAAkB,IAAI,SAAS;AACrC,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,QAAQ,EAAE;AACvD,QAAI,UAAU,KAAA,EAAW;AACzB,QAAI,MAAM,QAAQ,MAAM,CACvB,MAAK,MAAM,QAAQ,MAAO,iBAAgB,OAAO,KAAK,KAAK;QAE3D,iBAAgB,IAAI,KAAK,MAAM;;AAGjC,UACC,iBACA,IAAI,SAAS,IAAI,WAAW,OAAO,OAAO,OAAO,CAAC,EAAE;IACnD,QAAQ,IAAI,cAAc;IAC1B,YAAY,IAAI;IAChB,SAAS;IACT,CAAC,CACF;IACA;GAEH;AACD,KAAI,QAAQ,YAAY,MAAM;AAC7B,WAAS;AACT;;AAED,SAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1D,KAAI,GAAG,UAAU,UAAU,OAAO,gBAAgB,MAAM,CAAC;AACzD,KAAI,KAAK;EACR;AAEH,MAAM,8BACL,UACA,MACA,aAKA,OAAO,IAAI,aAAa;AACvB,KAAI,KAAK,SAAS,SAAS,YAC1B,QAAO,OAAO,OAAO,KACpB,IAAI,+BAA+B;EAClC,cAAc,KAAK;EACnB,cAAc;EACd,QACC;EAED,CAAC,CACF;CAGF,MAAM,OAAO,KAAK,SAAS;AAC3B,KAAI,CAAC,OAAO,UAAU,KAAK,IAAI,QAAQ,KAAK,OAAO,MAClD,QAAO,OAAO,OAAO,KACpB,IAAI,sBAAsB;EACzB,OAAO;EACP,OAAO,GAAG,mBAAmB,GAAG;EAChC,QAAQ;EACR,CAAC,CACF;CAGF,MAAM,aAAa,OAAO,SAAS,OAAO,KAAK,aAAa;CAC5D,MAAM,eAAe,OAAO,2BAA2B,MAAM,WAAW;CACxE,MAAM,SAAS,OAAO,eAAe;EAAE;EAAU,UAAU,KAAK;EAAY,CAAC;CAC7E,MAAM,SAAS,iBAAiB,QAAQ,QAAQ;AAChD,QAAO;EACN,gBAAgB;EAChB,UAAU;EACV,gBAAgB,WAAW;EAC3B,gBAAgB;EAChB,aAAa,GAAG,OAAO,KAAK,mBAAmB,GAAG;EAClD,MAAM,KAAK,iBAAiB,QAAQ,QAAQ,KAAK;EACjD;EACA;EACA;AAEH,MAAM,6BAA6B,MAAoB,aAAyC;CAC/F,MAAM,MACL,SAAS,iBAAiB,QACvB,mBAAmB,SAAS,mBAC5B,UAAU,SAAS,SAAS,GAAG,SAAS;AAC5C,QAAO;EACN,cAAc,KAAK;EACnB,UAAU,SAAS;EACnB,gBAAgB,SAAS;EACzB;EACA,cAAc,SAAS;EACvB;;AAGF,MAAM,sBACL,IACA,SACA,aAEA,GACE,OAAO,KAAK,KAAK,QAAQ,aAAa,iBAAiB,SAAS,eAAe,CAAC,CAAC,CACjF,KAAK,OAAO,OAAO;AAEtB,MAAM,+BACL,KACA,MACA,UACA,aACoD;CACpD,MAAM,UAAU,IAAI;AACpB,KACC,SAAS,YAAY,QACrB,IAAI,YACJ,SAAS,iBAAiB,SAC1B,KAAK,cAAc,WAEnB,QAAO,OAAO;CAEf,MAAM,YAAY,QAAQ,aAAA;CAC1B,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,mBAAmB,QAAQ,oBAAoB;CACrD,MAAM,WAAW,UAAU,mBAAmB,GAAG,SAAS;AAC1D,QAAO,oBAAoB;EAC1B,UAAU;EACV;EACA;EACA;EACA,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,UAAU,EAAE;EACrD,OAAO,QAAQ,SAAS;EACxB,WAAW,aAAa,sBAAsB,UAAU,SAAS;EACjE,CAAC,CAAC,KACF,OAAO,UACL,UACA,IAAI,0BAA0B;EAC7B,gBAAgB,SAAS;EACzB,KAAK,SAAS;EACd;EACA,QACC,0BAA0B,SAAS,IAAI,uBACpC,SAAS,eAAe,UAAU,UAAU,eAClC,SAAS,eAAe,SAAS,SAAS;EACxD;EACA,CAAC,CACH,CACD;;AAOF,MAAa,qBAST,MAAM,OACT,eACA,OAAO,IAAI,aAAa;CACvB,MAAM,MAAM,OAAO;CACnB,MAAM,WAAW,OAAO;CACxB,MAAM,WAAW,OAAO;CACxB,MAAM,YAAY,OAAO;CACzB,MAAM,KAAK,OAAO,WAAW;CAK7B,MAAM,aAAa,OAAO;CAG1B,MAAM,UAAU,OAAO,IAAI,KAAwB,KAAK;CAMxD,MAAM,UAAU,OAAO,gBAAgB,KAAmC,EAAE,CAAC;CAK7E,MAAM,aACL,OAAO,IAAI,aAAa;EACvB,MAAM,UAAU,IAAI;AACpB,MAAI,IAAI,UAAU;GACjB,MAAM,SAAqB;IAC1B,UAAU;IACV,aAAa;IACb,WAAW;IACX,cAAc;IACd;AACD,UAAO,IAAI,IAAI,SAAS,OAAO;AAC/B,UAAO;;EAER,MAAM,SAAS,OAAO,IAAI,IAAI,QAAQ;AACtC,MAAI,WAAW,KAAM,QAAO;AAkH5B,SAAO,OAvGe,OAAO,OAC5B,OAAO,IAAI,aAAa;AACvB,UAAO,iBAAiB,QAAQ,kBAAkB,2BAA2B,CAAC,KAC7E,OAAO,UACL,UACA,IAAI,iBAAiB;IACpB,OAAO;IACP,QAAQ,0CAA0C,QAAQ;IAC1D;IACA,CAAC,CACH,CACD;GAOD,MAAM,eAAe,OAAO,IAAI,IAAI,QAAQ;AAC5C,OAAI,iBAAiB,KAAM,QAAO;AAClC,UAAO,GAAG,cAAc,QAAQ,aAAa,EAAE,WAAW,MAAM,CAAC,CAAC,KACjE,OAAO,UACL,UACA,IAAI,iBAAiB;IACpB,OAAO;IACP,QAAQ,iBAAiB,QAAQ,YAAY;IAC7C;IACA,CAAC,CACH,CACD;AACD,UAAO,gBACN,KAAK,KAAK,QAAQ,aAAa,yBAAyB,EACxD,IAAI,aAAa,CAAC,OAAO,0BAA0B,CAAC,EACpD,EAAE,MAAM,KAAO,CACf,CAAC,KACD,OAAO,UACL,UACA,IAAI,iBAAiB;IACpB,OAAO;IACP,QAAQ,mBAAmB,yBAAyB,oBAAoB,MAAM;IAC9E;IACA,CAAC,CACH,EACD,OAAO,eAAe,WAAW,YAAY,GAAG,CAChD;GACD,MAAM,uBAAuB,OAAO,sBACnC,IACA,QAAQ,aACR,cACA,CAAC,KACD,OAAO,UACL,UACA,IAAI,iBAAiB;IACpB,OAAO;IACP,QAAQ,MAAM;IACd;IACA,CAAC,CACH,CACD;GAgBD,MAAM,yBAAyB,CAC9B,IAAG,OAhBgC,yBACnC,IACA,SACA,qBAAqB,QACrB,cACA,CAAC,KACD,OAAO,UACL,UACA,IAAI,iBAAiB;IACpB,OAAO;IACP,QAAQ,MAAM;IACd;IACA,CAAC,CACH,CACD,EAEwB,KAAK,UAAU,MAAM,eAAe,EAC5D,GAAG,qBAAqB,oBACxB;AACD,UAAO,iBAAiB,QAAQ,mBAAmB,2BAA2B,CAAC,KAC9E,OAAO,UACL,UACA,IAAI,iBAAiB;IACpB,OAAO;IACP,QAAQ,2CAA2C,QAAQ;IAC3D;IACA,CAAC,CACH,CACD;GACD,MAAM,SAAS,OAAO,UAAU;IAC/B,OAAO,IAAI;IACX,aAAa,SAAS,KAAK;IAC3B;IACA;IACA,CAAC,CAAC,KAAK,OAAO,eAAe,4BAA4B,WAAW,CAAC;AAMtE,UAAO,IAAI,IAAI,SAAS,OAAO;AAC/B,UAAO;IACN,CACF;GAEA,CAAC,KAAK,OAAO,SAAS,2BAA2B,CAAC;CAKrD,MAAM,mBAA0D,SAC/D,OAAO,IAAI,aAAa;EACvB,MAAM,UAAU,IAAI;EACpB,MAAM,WAAW,IAAI,WAClB,OAAO,2BAA2B,UAAU,MAAM,SAAS,GAC3D,OAAO,aAAa,UAAU,MAAM,UAAU,UAAU;EAC3D,MAAM,QAAQ,eAAe,SAAS,SAAS;EAC/C,MAAM,WAAW,0BAA0B,MAAM,SAAS;EAC1D,MAAM,mBAGF,OAAO,IAAI,aAAa;AAU3B,OAAI,IAAI,SAAU,QAAO;AACzB,OAAI,CAAC,IAAI,SACR,QAAO,GAAG,cAAc,QAAQ,aAAa,EAAE,WAAW,MAAM,CAAC,CAAC,KACjE,OAAO,UACL,UACA,IAAI,oBAAoB;IACvB,gBAAgB,SAAS;IACzB,MAAM,QAAQ;IACd,QAAQ,iBAAiB,QAAQ,YAAY;IAC7C;IACA,CAAC,CACH,CACD;GAEF,MAAM,iBAAiB,OAAO,gBAAgB,IAAI,QAAQ;GAC1D,MAAM,aAAa,IAAI,IAAI,eAAe,KAAK,UAAU,MAAM,eAAe,CAAC;GAC/E,IAAI,iBAAiB;GAMrB,MAAM,uBAAuB,OAAO,yBACnC,IACA,UACA,OARuB,sBACvB,IACA,QAAQ,aACR,SAAS,eACT,EAIS,QACT,SAAS,eACT;GACD,MAAM,4BAA4B,qBAAqB,MACrD,UAAU,MAAM,mBAAmB,SAAS,eAC7C;AACD,OACC,8BAA8B,KAAA,KAC9B,CAAC,WAAW,IAAI,0BAA0B,eAAe;SAGrD,OADkB,sBAAsB,0BAA0B,MACvD,QAAQ;AACtB,SAAI,CAAC,iBAAiB,2BAA2B,SAAS,CACzD,QAAO,OAAO,OAAO,KACpB,uBAAuB,2BAA2B,SAAS,CAC3D;AAEF,sBAAiB;;;GAQnB,MAAM,YAAY,iBAAiB;IAClC,GAN8B,qBAAqB,QAClD,UACA,CAAC,WAAW,IAAI,MAAM,eAAe,IACrC,MAAM,mBAAmB,SAAS,eAGV;IACzB,GAAG;IACH;IACA,CAAC;AACF,OAAI,UAAW,QAAO,OAAO,OAAO,KAAK,UAAU;AACnD,OAAI,eAAgB,QAAO;GAK3B,MAAM,WAAW,KAAK,KACrB,QAAQ,aACR,iBAAiB,SAAS,eAAe,CACzC;AACD,UAAO,gBACN,UACA,IAAI,aAAa,CAAC,OAAO,gBAAgB,UAAU,MAAM,CAAC,EAC1D,EACC,MAAM,KACN,CACD,CAAC,KACD,OAAO,UACL,UACA,IAAI,oBAAoB;IACvB,gBAAgB,SAAS;IACzB,MAAM;IACN,QAAQ,mCAAmC,MAAM;IACjD;IACA,CAAC,CACH,EACD,OAAO,eAAe,WAAW,YAAY,GAAG,CAChD;AACD,UAAO;IACN;EAEF,IAAI;AACJ,MAAI,IAAI,UAAU;AACjB,sBAAmB,OAAO;AAC1B,UAAO,4BAA4B,KAAK,MAAM,UAAU,SAAS;AACjE,OAAI,qBAAqB,cACxB,QAAO,gBAAgB,OAAO,UAAU,QAAQ,CAAC,GAAG,KAAK,SAAS,CAAC;QAgBpE,oBAAmB,OAAO,OAAO,OAChC,OAAO,IAAI,aAAa;AACvB,UAAO,iBAAiB,QAAQ,kBAAkB,2BAA2B,CAAC,KAC7E,OAAO,UACL,UACA,IAAI,oBAAoB;IACvB,gBAAgB,SAAS;IACzB,MAAM,QAAQ;IACd,QAAQ,mCAAmC,QAAQ;IACnD;IACA,CAAC,CACH,CACD;GACD,MAAM,YAAY,OAAO;AACzB,UAAO,4BAA4B,KAAK,MAAM,UAAU,SAAS,CAAC,KACjE,OAAO,cACN,cAAc,UAAU,OAAO,OAAO,mBAAmB,IAAI,SAAS,SAAS,CAC/E,CACD;AACD,OAAI,cAAc,cACjB,QAAO,gBAAgB,OAAO,UAAU,QAAQ,CAAC,GAAG,KAAK,SAAS,CAAC;AAEpE,UAAO;IACN,CACF;AAEF,MAAI,qBAAqB,cAOxB,QAAO,OAAO,mBACb,OAAO,IAAI,aAAa;AACvB,OAAI,qBAAqB,QACxB,QAAO,OAAO,OACb,OAAO,IAAI,aAAa;AACvB,WAAO,iBAAiB,QAAQ,kBAAkB,2BAA2B;AAC7E,WAAO,mBAAmB,IAAI,SAAS,SAAS;KAC/C,CACF,CAAC,KACD,oBAAoB,qCAAqC,EACxD,gBAAgB,SAAS,gBACzB,CAAC,CACF;AAEF,UAAO,gBAAgB,OAAO,UAAU,QACvC,IAAI,QAAQ,MAAM,EAAE,mBAAmB,SAAS,eAAe,CAC/D;IACA,CACF;AAGF,SAAO;GACN,CAAC,KAAK,OAAO,SAAS,sCAAsC,CAAC;AAEhE,QAAO,cAAc,GAAG;EAAE;EAAM;EAAiB;EAAS,CAAC;EAC1D,CACF"}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { RouterBootFailed } from "./errors.mjs";
|
|
2
1
|
import { DockerHost, DockerSpawner, dockerRun, dockerRunOk } from "../../runtime/docker/client.mjs";
|
|
3
|
-
import { ComposeLabelKey, LabelKey } from "../../runtime/docker/labels.mjs";
|
|
4
2
|
import { isNoSuchContainerStderr } from "../../runtime/docker/wrap.mjs";
|
|
3
|
+
import { ComposeLabelKey, LabelKey } from "../../runtime/docker/labels.mjs";
|
|
5
4
|
import { ensureNetwork } from "../../runtime/docker/network.mjs";
|
|
6
5
|
import { inspectContainer } from "../../runtime/docker/container.mjs";
|
|
6
|
+
import "../../runtime/docker/index.mjs";
|
|
7
|
+
import { RouterBootFailed } from "./errors.mjs";
|
|
8
|
+
import { HOST_GATEWAY_ADD_HOST_FLAGS } from "../../substrate/runtime/host-gateway.mjs";
|
|
9
|
+
import { ROUTER_KIND_LABEL_VALUE, ROUTER_PROFILE_LABEL } from "./sentinels.mjs";
|
|
7
10
|
import { Context, Effect, Layer } from "effect";
|
|
8
11
|
import * as path from "node:path";
|
|
9
12
|
//#region src/orchestrators/router/traefik-container.ts
|
|
10
13
|
const TRAEFIK_DISPATCH_MOUNT_TARGET = "/etc/traefik/dispatch";
|
|
11
|
-
const ROUTER_PROFILE_LABEL = LabelKey.routerProfile;
|
|
12
|
-
const HOST_GATEWAY_ALIAS = "host.docker.internal:host-gateway";
|
|
13
14
|
/** Default Traefik image. Tag, not digest — distilled-doc open
|
|
14
15
|
* question #9 (digest-pin is a follow-up). */
|
|
15
16
|
const DEFAULT_TRAEFIK_IMAGE = "traefik:v3.5";
|
|
16
|
-
var TraefikContainerOpsService = class extends Context.Service()("@devstack
|
|
17
|
+
var TraefikContainerOpsService = class extends Context.Service()("@devstack/orchestrators/router/TraefikContainerOps") {};
|
|
17
18
|
const sameHostPath = (left, right) => path.resolve(left) === path.resolve(right);
|
|
18
19
|
const uniqueSortedEntrypointPorts = (entrypoints) => [...new Set(entrypoints.map((entrypoint) => entrypoint.port))].sort((a, b) => a - b);
|
|
19
20
|
const traefikListenerEntrypoints = (entrypoints) => {
|
|
@@ -30,8 +31,19 @@ const traefikExpectedCommand = (entrypoints) => [
|
|
|
30
31
|
...traefikListenerEntrypoints(entrypoints).map((entrypoint) => `--entrypoints.${entrypoint.name}.address=:${entrypoint.port}`)
|
|
31
32
|
];
|
|
32
33
|
const mountedDispatchDirMatches = (existing, expected) => existing.dispatchMount !== null && existing.dispatchMount.target === "/etc/traefik/dispatch" && existing.dispatchMount.readOnly && sameHostPath(existing.dispatchMount.source, expected);
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
/** `existing ⊇ required` — every required value is present in the
|
|
35
|
+
* existing set; the existing set MAY carry extras. Used for the
|
|
36
|
+
* entrypoint/port/command dimensions so a SHARED router that publishes
|
|
37
|
+
* a SUPERSET of the entrypoints this stack needs (e.g. another build's
|
|
38
|
+
* extra `:9810` listener) is still adoptable. Extra entrypoints are
|
|
39
|
+
* harmless: routes dispatch by Host header on the shared entrypoints,
|
|
40
|
+
* so a wider listener set cannot mis-route a narrower stack. A router
|
|
41
|
+
* MISSING a required entrypoint (subset) still fails this check and so
|
|
42
|
+
* stays a mismatch. */
|
|
43
|
+
const supersetOf = (existing, required) => {
|
|
44
|
+
const have = new Set(existing);
|
|
45
|
+
return required.every((value) => have.has(value));
|
|
46
|
+
};
|
|
35
47
|
const portBindingPair = (binding) => {
|
|
36
48
|
const [container, host] = binding.split("=");
|
|
37
49
|
const containerPort = container?.split("/")[0];
|
|
@@ -41,15 +53,15 @@ const portBindingPair = (binding) => {
|
|
|
41
53
|
if (!/^\d+$/.test(containerPort) || !/^\d+$/.test(hostPort)) return null;
|
|
42
54
|
return `${containerPort}=${hostIp}:${hostPort}`;
|
|
43
55
|
};
|
|
44
|
-
const portBindingsMatch = (existing, entrypoints) =>
|
|
56
|
+
const portBindingsMatch = (existing, entrypoints) => supersetOf(existing.flatMap((binding) => {
|
|
45
57
|
const pair = portBindingPair(binding);
|
|
46
58
|
return pair === null ? [] : [pair];
|
|
47
59
|
}), traefikExpectedPortPairs(entrypoints));
|
|
48
60
|
const routerProfileLabelsMatch = (labels, profile) => {
|
|
49
61
|
if (labels[LabelKey.managed] !== "true") return false;
|
|
50
|
-
if (labels[LabelKey.
|
|
62
|
+
if (labels[LabelKey.kind] !== "router") return false;
|
|
51
63
|
if (labels[ROUTER_PROFILE_LABEL] !== profile.id) return false;
|
|
52
|
-
if (labels[LabelKey.
|
|
64
|
+
if (labels[LabelKey.specVersion] !== "3") return false;
|
|
53
65
|
for (const forbidden of [
|
|
54
66
|
LabelKey.app,
|
|
55
67
|
LabelKey.stack,
|
|
@@ -66,7 +78,7 @@ const routerProfileLabelsMatch = (labels, profile) => {
|
|
|
66
78
|
]) if (labels[forbidden] !== void 0) return false;
|
|
67
79
|
return true;
|
|
68
80
|
};
|
|
69
|
-
const routerSpecMatches = (existing, inputs) => existing.image === inputs.image && mountedDispatchDirMatches(existing, inputs.profile.dispatchDir) && portBindingsMatch(existing.portBindings, inputs.entrypoints) &&
|
|
81
|
+
const routerSpecMatches = (existing, inputs) => existing.image === inputs.image && mountedDispatchDirMatches(existing, inputs.profile.dispatchDir) && portBindingsMatch(existing.portBindings, inputs.entrypoints) && supersetOf(existing.command, traefikExpectedCommand(inputs.entrypoints)) && existing.networks.includes(inputs.profile.networkName) && routerProfileLabelsMatch(existing.labels, inputs.profile);
|
|
70
82
|
/** Adopt-or-create per the architecture's 4-way decision table.
|
|
71
83
|
* Idempotent: callers may invoke this once per supervisor lifetime,
|
|
72
84
|
* and a follow-up call returns `decision: 'adopt'` if nothing has
|
|
@@ -169,14 +181,13 @@ const traefikRunArgs = (args) => {
|
|
|
169
181
|
"--label",
|
|
170
182
|
`${LabelKey.managed}=true`,
|
|
171
183
|
"--label",
|
|
172
|
-
`${LabelKey.
|
|
184
|
+
`${LabelKey.kind}=${ROUTER_KIND_LABEL_VALUE}`,
|
|
173
185
|
"--label",
|
|
174
186
|
`${ROUTER_PROFILE_LABEL}=${args.routerProfileId}`,
|
|
175
187
|
"--label",
|
|
176
|
-
`${LabelKey.
|
|
177
|
-
"--add-host",
|
|
178
|
-
HOST_GATEWAY_ALIAS
|
|
188
|
+
`${LabelKey.specVersion}=3`
|
|
179
189
|
];
|
|
190
|
+
for (const pair of HOST_GATEWAY_ADD_HOST_FLAGS) dockerArgs.push("--add-host", pair);
|
|
180
191
|
for (const port of ports) dockerArgs.push("-p", `127.0.0.1:${port}:${port}`);
|
|
181
192
|
dockerArgs.push("--mount", `type=bind,source=${args.dispatchDirHostPath},target=${TRAEFIK_DISPATCH_MOUNT_TARGET},readonly`, args.image, ...traefikExpectedCommand(args.entrypoints));
|
|
182
193
|
return dockerArgs;
|
|
@@ -224,6 +235,6 @@ Layer.succeed(TraefikContainerOpsService)({
|
|
|
224
235
|
forceRemove: (_name) => Effect.succeed(void 0)
|
|
225
236
|
});
|
|
226
237
|
//#endregion
|
|
227
|
-
export { DEFAULT_TRAEFIK_IMAGE, TraefikContainerOpsService, bootstrap, layerTraefikContainerOpsDocker, routerProfileLabelsMatch, uniqueSortedEntrypointPorts };
|
|
238
|
+
export { DEFAULT_TRAEFIK_IMAGE, TRAEFIK_DISPATCH_MOUNT_TARGET, TraefikContainerOpsService, bootstrap, layerTraefikContainerOpsDocker, routerProfileLabelsMatch, uniqueSortedEntrypointPorts };
|
|
228
239
|
|
|
229
240
|
//# sourceMappingURL=traefik-container.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"traefik-container.mjs","names":[],"sources":["../../../src/orchestrators/router/traefik-container.ts"],"sourcesContent":["// Traefik container lifecycle — router-profile singleton.\n//\n// Architecture distilled-doc §\"Lifecycle states\":\n// | Found state | Image matches | Action |\n// |----------------------------|---------------|------------------------------|\n// | Container absent | n/a | Ensure network + create fresh|\n// | Container running | yes | Adopt (no-op) |\n// | Container running | no | Force-remove + create fresh |\n// | Container stopped | yes | Resume; on fail recreate fresh|\n// | Container stopped | no | Force-remove + create fresh |\n// | Network absent | n/a | Create network first |\n//\n// Key invariants:\n// - One container per router profile. NOT per-stack — the router\n// outlives any one `pnpm dev`.\n// - The container does NOT take the stack identity label set;\n// instead a singleton router label distinguishes it from per-stack\n// resources (so per-stack `wipe` doesn't touch it).\n// - Bootstrap runs ONCE per supervisor lifetime (architecture\n// invariant #11). Hot-reload cycles do NOT re-pay docker inspect.\n//\n// This file is INTENTIONALLY a thin orchestration shell. Real container\n// I/O lives in `runtime/docker/*`; the effectful operations are\n// abstracted behind a `TraefikContainerOps` service so tests can inject\n// a no-op implementation without changing route delivery.\n\nimport { Context, Effect, Layer } from 'effect';\nimport * as path from 'node:path';\n\nimport { RouterBootFailed } from './errors.ts';\nimport type { Entrypoint } from './entrypoints.ts';\nimport {\n\tDockerHost,\n\tDockerSpawner,\n\tComposeLabelKey,\n\tensureNetwork,\n\tinspectContainer,\n\tisNoSuchContainerStderr,\n\tLabelKey,\n} from '../../runtime/docker/index.ts';\nimport { dockerRun, dockerRunOk } from '../../runtime/docker/client.ts';\nimport type { RouterProfile } from './profile.ts';\n\nexport const TRAEFIK_DISPATCH_MOUNT_TARGET = '/etc/traefik/dispatch';\nexport const ROUTER_PROFILE_LABEL = LabelKey.routerProfile;\nexport const ROUTER_CONTAINER_SPEC_VERSION = '3';\nexport const HOST_GATEWAY_ALIAS = 'host.docker.internal:host-gateway';\n\n/** Default Traefik image. Tag, not digest — distilled-doc open\n * question #9 (digest-pin is a follow-up). */\nexport const DEFAULT_TRAEFIK_IMAGE = 'traefik:v3.5';\n\n// ---------------------------------------------------------------------------\n// BootDecision — observability shape\n// ---------------------------------------------------------------------------\n\n/** What the lifecycle decision was. Emitted as a span attribute and\n * threaded into the inventory row (architecture distilled-doc\n * §\"Outputs / capabilities provided\" — \"A boot-decision report\"). */\nexport type BootDecision = 'adopt' | 'resume' | 'recreate-fresh' | 'opt-out';\n\nexport interface BootReport {\n\treadonly decision: BootDecision;\n\treadonly containerId: string | null;\n\treadonly networkId: string | null;\n\t/** Whether the observed or freshly-created container is the right image. */\n\treadonly imageMatches: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// TraefikContainerOps — the I/O seam\n// ---------------------------------------------------------------------------\n\nexport interface InspectedTraefikContainer {\n\treadonly id: string;\n\treadonly running: boolean | 'unknown';\n\treadonly image: string;\n\treadonly dispatchMount: {\n\t\treadonly source: string;\n\t\treadonly target: string;\n\t\treadonly readOnly: boolean;\n\t} | null;\n\treadonly portBindings: ReadonlyArray<string>;\n\treadonly command: ReadonlyArray<string>;\n\treadonly networks: ReadonlyArray<string>;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\n/** The orchestrator does NOT talk to docker directly. It yields these\n * operations through a service tag whose layer can be wired to\n * `runtime/docker/*` in production or to a no-op stub in tests. This\n * is the load-bearing decoupling that lets the orchestrator stay at\n * \"walk decls + write files\" complexity without growing a docker\n * dependency.\n *\n * Architecture: orchestrator depends on L0+L1 contracts, never on\n * concrete L1 implementations. */\nexport interface TraefikContainerOps {\n\t/** Idempotent network ensure. Returns the network id. */\n\treadonly ensureNetwork: (\n\t\tname: string,\n\t) => Effect.Effect<{ readonly id: string }, RouterBootFailed>;\n\n\t/** Inspect the singleton router container. Returns `null` when\n\t * absent. */\n\treadonly inspectContainer: (\n\t\tname: string,\n\t) => Effect.Effect<InspectedTraefikContainer | null, RouterBootFailed>;\n\n\t/** Create the container fresh (after force-remove if necessary).\n\t * The container is started with file-provider configured against\n\t * `dispatchDirHostPath` (bind-mounted read-only to `/etc/traefik/dispatch`)\n\t * and one `--entrypoints.<name>.address=:<port>` flag per registered\n\t * entrypoint. The shared network is attached at create time. */\n\treadonly createFresh: (args: {\n\t\treadonly name: string;\n\t\treadonly image: string;\n\t\treadonly network: string;\n\t\treadonly routerProfileId: string;\n\t\treadonly entrypoints: ReadonlyArray<Entrypoint>;\n\t\treadonly dispatchDirHostPath: string;\n\t}) => Effect.Effect<{ readonly id: string }, RouterBootFailed>;\n\n\t/** Start an already-existing stopped container. May fail (port\n\t * conflict, daemon flake); caller's contract is to fall back to\n\t * fresh-recreate on failure. */\n\treadonly resume: (name: string) => Effect.Effect<{ readonly id: string }, RouterBootFailed>;\n\n\t/** Force-remove a container (running or stopped). Idempotent on\n\t * \"no such container\". */\n\treadonly forceRemove: (name: string) => Effect.Effect<void, RouterBootFailed>;\n}\n\nexport class TraefikContainerOpsService extends Context.Service<\n\tTraefikContainerOpsService,\n\tTraefikContainerOps\n>()('@devstack-rewrite/orchestrators/router/TraefikContainerOps') {}\n\n// ---------------------------------------------------------------------------\n// Bootstrap — decides + executes\n// ---------------------------------------------------------------------------\n\nexport interface BootstrapInputs {\n\treadonly image: string;\n\treadonly entrypoints: ReadonlyArray<Entrypoint>;\n\treadonly profile: RouterProfile;\n\treadonly protectedRouteLeaseIds: ReadonlyArray<string>;\n}\n\nconst sameHostPath = (left: string, right: string): boolean =>\n\tpath.resolve(left) === path.resolve(right);\n\nexport const uniqueSortedEntrypointPorts = (\n\tentrypoints: ReadonlyArray<Entrypoint>,\n): ReadonlyArray<number> =>\n\t[...new Set(entrypoints.map((entrypoint) => entrypoint.port))].sort((a, b) => a - b);\n\nconst traefikListenerEntrypoints = (\n\tentrypoints: ReadonlyArray<Entrypoint>,\n): ReadonlyArray<Entrypoint> => {\n\tconst byPort = new Map<number, Entrypoint>();\n\tfor (const entrypoint of entrypoints) {\n\t\tif (!byPort.has(entrypoint.port)) byPort.set(entrypoint.port, entrypoint);\n\t}\n\treturn Array.from(byPort.values());\n};\n\nexport const traefikExpectedPortBindings = (\n\tentrypoints: ReadonlyArray<Entrypoint>,\n): ReadonlyArray<string> =>\n\tuniqueSortedEntrypointPorts(entrypoints).map((port) => `${port}/tcp=127.0.0.1:${port}`);\n\nconst traefikExpectedPortPairs = (entrypoints: ReadonlyArray<Entrypoint>): ReadonlyArray<string> =>\n\tuniqueSortedEntrypointPorts(entrypoints).map((port) => `${port}=127.0.0.1:${port}`);\n\nexport const traefikExpectedCommand = (\n\tentrypoints: ReadonlyArray<Entrypoint>,\n): ReadonlyArray<string> => [\n\t`--providers.file.directory=${TRAEFIK_DISPATCH_MOUNT_TARGET}`,\n\t'--providers.file.watch=true',\n\t'--api.dashboard=false',\n\t'--log.level=INFO',\n\t...traefikListenerEntrypoints(entrypoints).map(\n\t\t(entrypoint) => `--entrypoints.${entrypoint.name}.address=:${entrypoint.port}`,\n\t),\n];\n\nconst mountedDispatchDirMatches = (\n\texisting: {\n\t\treadonly dispatchMount: {\n\t\t\treadonly source: string;\n\t\t\treadonly target: string;\n\t\t\treadonly readOnly: boolean;\n\t\t} | null;\n\t},\n\texpected: string,\n): boolean =>\n\texisting.dispatchMount !== null &&\n\texisting.dispatchMount.target === TRAEFIK_DISPATCH_MOUNT_TARGET &&\n\texisting.dispatchMount.readOnly &&\n\tsameHostPath(existing.dispatchMount.source, expected);\n\nconst sameStrings = (left: ReadonlyArray<string>, right: ReadonlyArray<string>): boolean =>\n\tleft.length === right.length && left.every((value, index) => value === right[index]);\n\nconst sameStringSet = (left: ReadonlyArray<string>, right: ReadonlyArray<string>): boolean =>\n\tsameStrings([...new Set(left)].sort(), [...new Set(right)].sort());\n\nconst portBindingPair = (binding: string): string | null => {\n\tconst [container, host] = binding.split('=');\n\tconst containerPort = container?.split('/')[0];\n\tconst hostPort = host?.slice(host.lastIndexOf(':') + 1);\n\tconst hostIp = host?.slice(0, host.lastIndexOf(':'));\n\tif (containerPort === undefined || hostPort === undefined || hostIp === undefined) return null;\n\tif (!/^\\d+$/.test(containerPort) || !/^\\d+$/.test(hostPort)) return null;\n\treturn `${containerPort}=${hostIp}:${hostPort}`;\n};\n\nconst portBindingsMatch = (\n\texisting: ReadonlyArray<string>,\n\tentrypoints: ReadonlyArray<Entrypoint>,\n): boolean =>\n\tsameStringSet(\n\t\texisting.flatMap((binding) => {\n\t\t\tconst pair = portBindingPair(binding);\n\t\t\treturn pair === null ? [] : [pair];\n\t\t}),\n\t\ttraefikExpectedPortPairs(entrypoints),\n\t);\n\nexport const routerProfileLabelsMatch = (\n\tlabels: Readonly<Record<string, string>>,\n\tprofile: RouterProfile,\n): boolean => {\n\tif (labels[LabelKey.managed] !== 'true') return false;\n\tif (labels[LabelKey.routerMarker] !== 'true') return false;\n\tif (labels[ROUTER_PROFILE_LABEL] !== profile.id) return false;\n\tif (labels[LabelKey.routerSpecVersion] !== ROUTER_CONTAINER_SPEC_VERSION) return false;\n\tfor (const forbidden of [\n\t\tLabelKey.app,\n\t\tLabelKey.stack,\n\t\tLabelKey.plugin,\n\t\tLabelKey.role,\n\t\tLabelKey.cycle,\n\t\tComposeLabelKey.project,\n\t\tComposeLabelKey.service,\n\t\tComposeLabelKey.containerNumber,\n\t\tComposeLabelKey.version,\n\t\tComposeLabelKey.oneoff,\n\t\tComposeLabelKey.network,\n\t\tComposeLabelKey.volume,\n\t]) {\n\t\tif (labels[forbidden] !== undefined) return false;\n\t}\n\treturn true;\n};\n\nconst routerSpecMatches = (existing: InspectedTraefikContainer, inputs: BootstrapInputs): boolean =>\n\texisting.image === inputs.image &&\n\tmountedDispatchDirMatches(existing, inputs.profile.dispatchDir) &&\n\tportBindingsMatch(existing.portBindings, inputs.entrypoints) &&\n\tsameStringSet(existing.command, traefikExpectedCommand(inputs.entrypoints)) &&\n\texisting.networks.includes(inputs.profile.networkName) &&\n\trouterProfileLabelsMatch(existing.labels, inputs.profile);\n\n/** Adopt-or-create per the architecture's 4-way decision table.\n * Idempotent: callers may invoke this once per supervisor lifetime,\n * and a follow-up call returns `decision: 'adopt'` if nothing has\n * changed. */\nexport const bootstrap = (\n\tinputs: BootstrapInputs,\n): Effect.Effect<BootReport, RouterBootFailed, TraefikContainerOpsService> =>\n\tEffect.gen(function* () {\n\t\tconst ops = yield* TraefikContainerOpsService;\n\t\tconst network = yield* ops.ensureNetwork(inputs.profile.networkName);\n\t\tconst existing = yield* ops.inspectContainer(inputs.profile.containerName);\n\n\t\t// Decision table.\n\t\tif (existing === null) {\n\t\t\tconst created = yield* ops.createFresh({\n\t\t\t\tname: inputs.profile.containerName,\n\t\t\t\timage: inputs.image,\n\t\t\t\tnetwork: inputs.profile.networkName,\n\t\t\t\trouterProfileId: inputs.profile.id,\n\t\t\t\tentrypoints: inputs.entrypoints,\n\t\t\t\tdispatchDirHostPath: inputs.profile.dispatchDir,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tdecision: 'recreate-fresh' as const,\n\t\t\t\tcontainerId: created.id,\n\t\t\t\tnetworkId: network.id,\n\t\t\t\timageMatches: true,\n\t\t\t};\n\t\t}\n\n\t\tconst configMatches = routerSpecMatches(existing, inputs);\n\n\t\tif (existing.running === true && configMatches) {\n\t\t\treturn {\n\t\t\t\tdecision: 'adopt' as const,\n\t\t\t\tcontainerId: existing.id,\n\t\t\t\tnetworkId: network.id,\n\t\t\t\timageMatches: true,\n\t\t\t};\n\t\t}\n\n\t\tif (existing.running === 'unknown' && configMatches) {\n\t\t\tif (inputs.protectedRouteLeaseIds.length > 0) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\tstage: 'ensure-container',\n\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t`router profile '${inputs.profile.id}' container '${inputs.profile.containerName}' ` +\n\t\t\t\t\t\t\t`has unknown lifecycle state, and live or unknown route leases exist: ` +\n\t\t\t\t\t\t\tinputs.protectedRouteLeaseIds.join(', '),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tyield* ops.forceRemove(inputs.profile.containerName);\n\t\t\tconst created = yield* ops.createFresh({\n\t\t\t\tname: inputs.profile.containerName,\n\t\t\t\timage: inputs.image,\n\t\t\t\tnetwork: inputs.profile.networkName,\n\t\t\t\trouterProfileId: inputs.profile.id,\n\t\t\t\tentrypoints: inputs.entrypoints,\n\t\t\t\tdispatchDirHostPath: inputs.profile.dispatchDir,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tdecision: 'recreate-fresh' as const,\n\t\t\t\tcontainerId: created.id,\n\t\t\t\tnetworkId: network.id,\n\t\t\t\timageMatches: true,\n\t\t\t};\n\t\t}\n\n\t\tif (!configMatches) {\n\t\t\tif (inputs.protectedRouteLeaseIds.length > 0) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\tstage: 'ensure-container',\n\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t`router profile '${inputs.profile.id}' container '${inputs.profile.containerName}' ` +\n\t\t\t\t\t\t\t`does not match the requested spec, but live or unknown route leases exist: ` +\n\t\t\t\t\t\t\tinputs.protectedRouteLeaseIds.join(', '),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Running or stopped with stale image, bind mount, entrypoints,\n\t\t\t// provider args, network, labels, or mount mode — force-remove\n\t\t\t// + recreate. A profile singleton can only serve the spec it\n\t\t\t// was created with.\n\t\t\tyield* ops.forceRemove(inputs.profile.containerName);\n\t\t\tconst created = yield* ops.createFresh({\n\t\t\t\tname: inputs.profile.containerName,\n\t\t\t\timage: inputs.image,\n\t\t\t\tnetwork: inputs.profile.networkName,\n\t\t\t\trouterProfileId: inputs.profile.id,\n\t\t\t\tentrypoints: inputs.entrypoints,\n\t\t\t\tdispatchDirHostPath: inputs.profile.dispatchDir,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tdecision: 'recreate-fresh' as const,\n\t\t\t\tcontainerId: created.id,\n\t\t\t\tnetworkId: network.id,\n\t\t\t\timageMatches: true,\n\t\t\t};\n\t\t}\n\n\t\t// Stopped + image matches → resume; on failure recreate fresh.\n\t\tconst resumed = yield* ops.resume(inputs.profile.containerName).pipe(\n\t\t\tEffect.catchTag('RouterBootFailed', (cause) =>\n\t\t\t\tinputs.protectedRouteLeaseIds.length > 0\n\t\t\t\t\t? Effect.fail(\n\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\tstage: 'ensure-container',\n\t\t\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t\t\t`router profile '${inputs.profile.id}' container '${inputs.profile.containerName}' ` +\n\t\t\t\t\t\t\t\t\t`could not be resumed, and live or unknown route leases exist: ` +\n\t\t\t\t\t\t\t\t\tinputs.protectedRouteLeaseIds.join(', '),\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t)\n\t\t\t\t\t: ops.forceRemove(inputs.profile.containerName).pipe(\n\t\t\t\t\t\t\tEffect.andThen(\n\t\t\t\t\t\t\t\tops.createFresh({\n\t\t\t\t\t\t\t\t\tname: inputs.profile.containerName,\n\t\t\t\t\t\t\t\t\timage: inputs.image,\n\t\t\t\t\t\t\t\t\tnetwork: inputs.profile.networkName,\n\t\t\t\t\t\t\t\t\trouterProfileId: inputs.profile.id,\n\t\t\t\t\t\t\t\t\tentrypoints: inputs.entrypoints,\n\t\t\t\t\t\t\t\t\tdispatchDirHostPath: inputs.profile.dispatchDir,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t),\n\t\t);\n\t\treturn {\n\t\t\tdecision: 'resume' as const,\n\t\t\tcontainerId: resumed.id,\n\t\t\tnetworkId: network.id,\n\t\t\timageMatches: true,\n\t\t};\n\t}).pipe(Effect.withSpan('orchestrator.router.bootstrap'));\n\n// ---------------------------------------------------------------------------\n// Docker-backed layer\n// ---------------------------------------------------------------------------\n\nconst routerBootFailed = (\n\tstage: RouterBootFailed['stage'],\n\tdetail: string,\n\tcause?: unknown,\n): RouterBootFailed =>\n\tnew RouterBootFailed({\n\t\tstage,\n\t\tdetail,\n\t\t...(cause === undefined ? {} : { cause }),\n\t});\n\nconst traefikRunArgs = (args: {\n\treadonly name: string;\n\treadonly image: string;\n\treadonly network: string;\n\treadonly routerProfileId: string;\n\treadonly entrypoints: ReadonlyArray<Entrypoint>;\n\treadonly dispatchDirHostPath: string;\n}): ReadonlyArray<string> => {\n\tconst ports = uniqueSortedEntrypointPorts(args.entrypoints);\n\tconst dockerArgs: Array<string> = [\n\t\t'-d',\n\t\t'--name',\n\t\targs.name,\n\t\t'--network',\n\t\targs.network,\n\t\t'--label',\n\t\t`${LabelKey.managed}=true`,\n\t\t'--label',\n\t\t`${LabelKey.routerMarker}=true`,\n\t\t'--label',\n\t\t`${ROUTER_PROFILE_LABEL}=${args.routerProfileId}`,\n\t\t'--label',\n\t\t`${LabelKey.routerSpecVersion}=${ROUTER_CONTAINER_SPEC_VERSION}`,\n\t\t'--add-host',\n\t\tHOST_GATEWAY_ALIAS,\n\t];\n\tfor (const port of ports) {\n\t\tdockerArgs.push('-p', `127.0.0.1:${port}:${port}`);\n\t}\n\tdockerArgs.push(\n\t\t'--mount',\n\t\t`type=bind,source=${args.dispatchDirHostPath},target=${TRAEFIK_DISPATCH_MOUNT_TARGET},readonly`,\n\t\targs.image,\n\t\t...traefikExpectedCommand(args.entrypoints),\n\t);\n\treturn dockerArgs;\n};\n\nexport const layerTraefikContainerOpsDocker: Layer.Layer<\n\tTraefikContainerOpsService,\n\tnever,\n\tDockerHost | DockerSpawner\n> = Layer.effect(\n\tTraefikContainerOpsService,\n\tEffect.gen(function* () {\n\t\tconst dockerHost = yield* DockerHost;\n\t\tconst dockerSpawner = yield* DockerSpawner;\n\t\tconst provideDocker = <A, E>(\n\t\t\teffect: Effect.Effect<A, E, DockerHost | DockerSpawner>,\n\t\t): Effect.Effect<A, E, never> =>\n\t\t\teffect.pipe(\n\t\t\t\tEffect.provideService(DockerHost, dockerHost),\n\t\t\t\tEffect.provideService(DockerSpawner, dockerSpawner),\n\t\t\t);\n\n\t\tconst ops: TraefikContainerOps = {\n\t\t\tensureNetwork: (name) =>\n\t\t\t\tprovideDocker(\n\t\t\t\t\tensureNetwork(name, { app: 'devstack-router', stack: name, composeUi: false }),\n\t\t\t\t).pipe(\n\t\t\t\t\tEffect.map((id) => ({ id })),\n\t\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\t\trouterBootFailed(\n\t\t\t\t\t\t\t'ensure-network',\n\t\t\t\t\t\t\t`failed to ensure shared router network '${name}'`,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\tinspectContainer: (name) =>\n\t\t\t\tprovideDocker(inspectContainer(name)).pipe(\n\t\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\t\trouterBootFailed('inspect', `failed to inspect router container '${name}'`, cause),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.map((facts) =>\n\t\t\t\t\t\tfacts === null\n\t\t\t\t\t\t\t? null\n\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\tid: facts.id,\n\t\t\t\t\t\t\t\t\trunning: facts.lifecycle.kind === 'unknown' ? 'unknown' : facts.running,\n\t\t\t\t\t\t\t\t\timage: facts.image,\n\t\t\t\t\t\t\t\t\tdispatchMount: (() => {\n\t\t\t\t\t\t\t\t\t\tconst mount = facts.mounts?.find(\n\t\t\t\t\t\t\t\t\t\t\t(m) => m.target === TRAEFIK_DISPATCH_MOUNT_TARGET,\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\treturn mount === undefined\n\t\t\t\t\t\t\t\t\t\t\t? null\n\t\t\t\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tsource: mount.source,\n\t\t\t\t\t\t\t\t\t\t\t\t\ttarget: mount.target,\n\t\t\t\t\t\t\t\t\t\t\t\t\treadOnly: mount.readOnly === true,\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t})(),\n\t\t\t\t\t\t\t\t\tportBindings: facts.portBindings ?? [],\n\t\t\t\t\t\t\t\t\tcommand: facts.command ?? [],\n\t\t\t\t\t\t\t\t\tnetworks: facts.networks ?? [],\n\t\t\t\t\t\t\t\t\tlabels: facts.labels ?? {},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\tcreateFresh: (args) =>\n\t\t\t\tprovideDocker(dockerRun('run', traefikRunArgs(args))).pipe(\n\t\t\t\t\tEffect.map((result) => ({ id: result.stdout.trim() })),\n\t\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\t\trouterBootFailed(\n\t\t\t\t\t\t\t'ensure-container',\n\t\t\t\t\t\t\t`failed to create router container '${args.name}'`,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\tresume: (name) =>\n\t\t\t\tprovideDocker(dockerRun('start', [name])).pipe(\n\t\t\t\t\tEffect.flatMap(() => provideDocker(inspectContainer(name))),\n\t\t\t\t\tEffect.flatMap((facts) =>\n\t\t\t\t\t\tfacts === null\n\t\t\t\t\t\t\t? Effect.fail(\n\t\t\t\t\t\t\t\t\trouterBootFailed(\n\t\t\t\t\t\t\t\t\t\t'inspect',\n\t\t\t\t\t\t\t\t\t\t`router container '${name}' started but could not be inspected`,\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: Effect.succeed({ id: facts.id }),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\t\tcause instanceof RouterBootFailed\n\t\t\t\t\t\t\t? cause\n\t\t\t\t\t\t\t: routerBootFailed(\n\t\t\t\t\t\t\t\t\t'ensure-container',\n\t\t\t\t\t\t\t\t\t`failed to resume router container '${name}'`,\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\tforceRemove: (name) =>\n\t\t\t\tprovideDocker(dockerRunOk('rm', ['-f', name])).pipe(\n\t\t\t\t\tEffect.flatMap((result) => {\n\t\t\t\t\t\tif (result.exitCode === 0 || isNoSuchContainerStderr(result.stderr)) {\n\t\t\t\t\t\t\treturn Effect.void;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn Effect.fail(\n\t\t\t\t\t\t\trouterBootFailed(\n\t\t\t\t\t\t\t\t'ensure-container',\n\t\t\t\t\t\t\t\t`failed to remove router container '${name}': ${result.stderr}`,\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\tEffect.mapError((cause) =>\n\t\t\t\t\t\tcause instanceof RouterBootFailed\n\t\t\t\t\t\t\t? cause\n\t\t\t\t\t\t\t: routerBootFailed(\n\t\t\t\t\t\t\t\t\t'ensure-container',\n\t\t\t\t\t\t\t\t\t`failed to remove router container '${name}'`,\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t};\n\n\t\treturn TraefikContainerOpsService.of(ops);\n\t}),\n);\n\n// ---------------------------------------------------------------------------\n// Stub layer — injected by tests\n// ---------------------------------------------------------------------------\n\n/** No-op implementation for tests that assert file-provider delivery\n * without managing a real Traefik container. Production composition\n * uses `layerTraefikContainerOpsDocker`. */\nexport const layerTraefikContainerOpsStub: Layer.Layer<TraefikContainerOpsService> = Layer.succeed(\n\tTraefikContainerOpsService,\n)({\n\tensureNetwork: (_name) => Effect.succeed({ id: 'stub-network' }),\n\tinspectContainer: (_name) => Effect.succeed(null),\n\tcreateFresh: (_args) => Effect.succeed({ id: 'stub-container' }),\n\tresume: (_name) => Effect.succeed({ id: 'stub-container' }),\n\tforceRemove: (_name) => Effect.succeed(undefined),\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"traefik-container.mjs","names":[],"sources":["../../../src/orchestrators/router/traefik-container.ts"],"sourcesContent":["// Traefik container lifecycle — router-profile singleton.\n//\n// Architecture distilled-doc §\"Lifecycle states\":\n// | Found state | Image matches | Action |\n// |----------------------------|---------------|------------------------------|\n// | Container absent | n/a | Ensure network + create fresh|\n// | Container running | yes | Adopt (no-op) |\n// | Container running | no | Force-remove + create fresh |\n// | Container stopped | yes | Resume; on fail recreate fresh|\n// | Container stopped | no | Force-remove + create fresh |\n// | Network absent | n/a | Create network first |\n//\n// Key invariants:\n// - One container per router profile. NOT per-stack — the router\n// outlives any one `pnpm dev`.\n// - The container does NOT take the stack identity label set;\n// instead a singleton router label distinguishes it from per-stack\n// resources (so per-stack `wipe` doesn't touch it).\n// - Bootstrap runs ONCE per supervisor lifetime (architecture\n// invariant #11). Hot-reload cycles do NOT re-pay docker inspect.\n//\n// This file is INTENTIONALLY a thin orchestration shell. Real container\n// I/O lives in `runtime/docker/*`; the effectful operations are\n// abstracted behind a `TraefikContainerOps` service so tests can inject\n// a no-op implementation without changing route delivery.\n\nimport { Context, Effect, Layer } from 'effect';\nimport * as path from 'node:path';\n\nimport { RouterBootFailed } from './errors.ts';\nimport type { Entrypoint } from './entrypoints.ts';\nimport {\n\tDockerHost,\n\tDockerSpawner,\n\tComposeLabelKey,\n\tensureNetwork,\n\tinspectContainer,\n\tisNoSuchContainerStderr,\n\tLabelKey,\n} from '../../runtime/docker/index.ts';\nimport { dockerRun, dockerRunOk } from '../../runtime/docker/client.ts';\nimport { HOST_GATEWAY_ADD_HOST_FLAGS } from '../../substrate/runtime/host-gateway.ts';\nimport type { RouterProfile } from './profile.ts';\nimport {\n\tROUTER_CONTAINER_SPEC_VERSION,\n\tROUTER_KIND_LABEL_VALUE,\n\tROUTER_PROFILE_LABEL,\n} from './sentinels.ts';\n\nexport {\n\tROUTER_CONTAINER_SPEC_VERSION,\n\tROUTER_KIND_LABEL_VALUE,\n\tROUTER_PROFILE_LABEL,\n} from './sentinels.ts';\n\nexport const TRAEFIK_DISPATCH_MOUNT_TARGET = '/etc/traefik/dispatch';\n\n/** Default Traefik image. Tag, not digest — distilled-doc open\n * question #9 (digest-pin is a follow-up). */\nexport const DEFAULT_TRAEFIK_IMAGE = 'traefik:v3.5';\n\n// ---------------------------------------------------------------------------\n// BootDecision — observability shape\n// ---------------------------------------------------------------------------\n\n/** What the lifecycle decision was. Emitted as a span attribute and\n * threaded into the inventory row (architecture distilled-doc\n * §\"Outputs / capabilities provided\" — \"A boot-decision report\"). */\nexport type BootDecision = 'adopt' | 'resume' | 'recreate-fresh' | 'opt-out';\n\nexport interface BootReport {\n\treadonly decision: BootDecision;\n\treadonly containerId: string | null;\n\treadonly networkId: string | null;\n\t/** Whether the observed or freshly-created container is the right image. */\n\treadonly imageMatches: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// TraefikContainerOps — the I/O seam\n// ---------------------------------------------------------------------------\n\nexport interface InspectedTraefikContainer {\n\treadonly id: string;\n\treadonly running: boolean | 'unknown';\n\treadonly image: string;\n\treadonly dispatchMount: {\n\t\treadonly source: string;\n\t\treadonly target: string;\n\t\treadonly readOnly: boolean;\n\t} | null;\n\treadonly portBindings: ReadonlyArray<string>;\n\treadonly command: ReadonlyArray<string>;\n\treadonly networks: ReadonlyArray<string>;\n\treadonly labels: Readonly<Record<string, string>>;\n}\n\n/** The orchestrator does NOT talk to docker directly. It yields these\n * operations through a service tag whose layer can be wired to\n * `runtime/docker/*` in production or to a no-op stub in tests. This\n * is the load-bearing decoupling that lets the orchestrator stay at\n * \"walk decls + write files\" complexity without growing a docker\n * dependency.\n *\n * Architecture: orchestrator depends on L0+L1 contracts, never on\n * concrete L1 implementations. */\nexport interface TraefikContainerOps {\n\t/** Idempotent network ensure. Returns the network id. */\n\treadonly ensureNetwork: (\n\t\tname: string,\n\t) => Effect.Effect<{ readonly id: string }, RouterBootFailed>;\n\n\t/** Inspect the singleton router container. Returns `null` when\n\t * absent. */\n\treadonly inspectContainer: (\n\t\tname: string,\n\t) => Effect.Effect<InspectedTraefikContainer | null, RouterBootFailed>;\n\n\t/** Create the container fresh (after force-remove if necessary).\n\t * The container is started with file-provider configured against\n\t * `dispatchDirHostPath` (bind-mounted read-only to `/etc/traefik/dispatch`)\n\t * and one `--entrypoints.<name>.address=:<port>` flag per registered\n\t * entrypoint. The shared network is attached at create time. */\n\treadonly createFresh: (args: {\n\t\treadonly name: string;\n\t\treadonly image: string;\n\t\treadonly network: string;\n\t\treadonly routerProfileId: string;\n\t\treadonly entrypoints: ReadonlyArray<Entrypoint>;\n\t\treadonly dispatchDirHostPath: string;\n\t}) => Effect.Effect<{ readonly id: string }, RouterBootFailed>;\n\n\t/** Start an already-existing stopped container. May fail (port\n\t * conflict, daemon flake); caller's contract is to fall back to\n\t * fresh-recreate on failure. */\n\treadonly resume: (name: string) => Effect.Effect<{ readonly id: string }, RouterBootFailed>;\n\n\t/** Force-remove a container (running or stopped). Idempotent on\n\t * \"no such container\". */\n\treadonly forceRemove: (name: string) => Effect.Effect<void, RouterBootFailed>;\n}\n\nexport class TraefikContainerOpsService extends Context.Service<\n\tTraefikContainerOpsService,\n\tTraefikContainerOps\n>()('@devstack/orchestrators/router/TraefikContainerOps') {}\n\n// ---------------------------------------------------------------------------\n// Bootstrap — decides + executes\n// ---------------------------------------------------------------------------\n\nexport interface BootstrapInputs {\n\treadonly image: string;\n\treadonly entrypoints: ReadonlyArray<Entrypoint>;\n\treadonly profile: RouterProfile;\n\treadonly protectedRouteLeaseIds: ReadonlyArray<string>;\n}\n\nconst sameHostPath = (left: string, right: string): boolean =>\n\tpath.resolve(left) === path.resolve(right);\n\nexport const uniqueSortedEntrypointPorts = (\n\tentrypoints: ReadonlyArray<Entrypoint>,\n): ReadonlyArray<number> =>\n\t[...new Set(entrypoints.map((entrypoint) => entrypoint.port))].sort((a, b) => a - b);\n\nconst traefikListenerEntrypoints = (\n\tentrypoints: ReadonlyArray<Entrypoint>,\n): ReadonlyArray<Entrypoint> => {\n\tconst byPort = new Map<number, Entrypoint>();\n\tfor (const entrypoint of entrypoints) {\n\t\tif (!byPort.has(entrypoint.port)) byPort.set(entrypoint.port, entrypoint);\n\t}\n\treturn Array.from(byPort.values());\n};\n\nexport const traefikExpectedPortBindings = (\n\tentrypoints: ReadonlyArray<Entrypoint>,\n): ReadonlyArray<string> =>\n\tuniqueSortedEntrypointPorts(entrypoints).map((port) => `${port}/tcp=127.0.0.1:${port}`);\n\nconst traefikExpectedPortPairs = (entrypoints: ReadonlyArray<Entrypoint>): ReadonlyArray<string> =>\n\tuniqueSortedEntrypointPorts(entrypoints).map((port) => `${port}=127.0.0.1:${port}`);\n\nexport const traefikExpectedCommand = (\n\tentrypoints: ReadonlyArray<Entrypoint>,\n): ReadonlyArray<string> => [\n\t`--providers.file.directory=${TRAEFIK_DISPATCH_MOUNT_TARGET}`,\n\t'--providers.file.watch=true',\n\t'--api.dashboard=false',\n\t'--log.level=INFO',\n\t...traefikListenerEntrypoints(entrypoints).map(\n\t\t(entrypoint) => `--entrypoints.${entrypoint.name}.address=:${entrypoint.port}`,\n\t),\n];\n\nconst mountedDispatchDirMatches = (\n\texisting: {\n\t\treadonly dispatchMount: {\n\t\t\treadonly source: string;\n\t\t\treadonly target: string;\n\t\t\treadonly readOnly: boolean;\n\t\t} | null;\n\t},\n\texpected: string,\n): boolean =>\n\texisting.dispatchMount !== null &&\n\texisting.dispatchMount.target === TRAEFIK_DISPATCH_MOUNT_TARGET &&\n\texisting.dispatchMount.readOnly &&\n\tsameHostPath(existing.dispatchMount.source, expected);\n\n/** `existing ⊇ required` — every required value is present in the\n * existing set; the existing set MAY carry extras. Used for the\n * entrypoint/port/command dimensions so a SHARED router that publishes\n * a SUPERSET of the entrypoints this stack needs (e.g. another build's\n * extra `:9810` listener) is still adoptable. Extra entrypoints are\n * harmless: routes dispatch by Host header on the shared entrypoints,\n * so a wider listener set cannot mis-route a narrower stack. A router\n * MISSING a required entrypoint (subset) still fails this check and so\n * stays a mismatch. */\nconst supersetOf = (existing: ReadonlyArray<string>, required: ReadonlyArray<string>): boolean => {\n\tconst have = new Set(existing);\n\treturn required.every((value) => have.has(value));\n};\n\nconst portBindingPair = (binding: string): string | null => {\n\tconst [container, host] = binding.split('=');\n\tconst containerPort = container?.split('/')[0];\n\tconst hostPort = host?.slice(host.lastIndexOf(':') + 1);\n\tconst hostIp = host?.slice(0, host.lastIndexOf(':'));\n\tif (containerPort === undefined || hostPort === undefined || hostIp === undefined) return null;\n\tif (!/^\\d+$/.test(containerPort) || !/^\\d+$/.test(hostPort)) return null;\n\treturn `${containerPort}=${hostIp}:${hostPort}`;\n};\n\n// Superset-tolerant (existing ⊇ required): a router that publishes the\n// required port bindings plus extras (another build's wider entrypoint\n// set) is still a match. A router MISSING a required binding (subset)\n// is not. The exactness that protects against genuine incompatibilities\n// lives in the image-tag / spec-version / dispatch-mount / network /\n// profile-label dimensions of `routerSpecMatches`.\nconst portBindingsMatch = (\n\texisting: ReadonlyArray<string>,\n\tentrypoints: ReadonlyArray<Entrypoint>,\n): boolean =>\n\tsupersetOf(\n\t\texisting.flatMap((binding) => {\n\t\t\tconst pair = portBindingPair(binding);\n\t\t\treturn pair === null ? [] : [pair];\n\t\t}),\n\t\ttraefikExpectedPortPairs(entrypoints),\n\t);\n\nexport const routerProfileLabelsMatch = (\n\tlabels: Readonly<Record<string, string>>,\n\tprofile: RouterProfile,\n): boolean => {\n\tif (labels[LabelKey.managed] !== 'true') return false;\n\tif (labels[LabelKey.kind] !== ROUTER_KIND_LABEL_VALUE) return false;\n\tif (labels[ROUTER_PROFILE_LABEL] !== profile.id) return false;\n\tif (labels[LabelKey.specVersion] !== ROUTER_CONTAINER_SPEC_VERSION) return false;\n\tfor (const forbidden of [\n\t\tLabelKey.app,\n\t\tLabelKey.stack,\n\t\tLabelKey.plugin,\n\t\tLabelKey.role,\n\t\tLabelKey.cycle,\n\t\tComposeLabelKey.project,\n\t\tComposeLabelKey.service,\n\t\tComposeLabelKey.containerNumber,\n\t\tComposeLabelKey.version,\n\t\tComposeLabelKey.oneoff,\n\t\tComposeLabelKey.network,\n\t\tComposeLabelKey.volume,\n\t]) {\n\t\tif (labels[forbidden] !== undefined) return false;\n\t}\n\treturn true;\n};\n\n// Adoptability of a live SHARED router. EXACT on the dimensions a\n// profile singleton can never reconcile in place — image tag,\n// spec-version + profile labels (via `routerProfileLabelsMatch`),\n// dispatch-dir bind mount, network membership. SUPERSET-tolerant only\n// on the entrypoint dimension (port bindings + the derived Traefik\n// command args): a router whose published entrypoints are a superset of\n// what this stack needs is adoptable, because extra entrypoints can't\n// mis-route Host-header dispatch. A router MISSING a required entrypoint\n// (subset), or carrying a different image/spec-version, is still a\n// mismatch — keeping the fail-safe intact when live routes exist.\nconst routerSpecMatches = (existing: InspectedTraefikContainer, inputs: BootstrapInputs): boolean =>\n\texisting.image === inputs.image &&\n\tmountedDispatchDirMatches(existing, inputs.profile.dispatchDir) &&\n\tportBindingsMatch(existing.portBindings, inputs.entrypoints) &&\n\tsupersetOf(existing.command, traefikExpectedCommand(inputs.entrypoints)) &&\n\texisting.networks.includes(inputs.profile.networkName) &&\n\trouterProfileLabelsMatch(existing.labels, inputs.profile);\n\n/** Adopt-or-create per the architecture's 4-way decision table.\n * Idempotent: callers may invoke this once per supervisor lifetime,\n * and a follow-up call returns `decision: 'adopt'` if nothing has\n * changed. */\nexport const bootstrap = (\n\tinputs: BootstrapInputs,\n): Effect.Effect<BootReport, RouterBootFailed, TraefikContainerOpsService> =>\n\tEffect.gen(function* () {\n\t\tconst ops = yield* TraefikContainerOpsService;\n\t\tconst network = yield* ops.ensureNetwork(inputs.profile.networkName);\n\t\tconst existing = yield* ops.inspectContainer(inputs.profile.containerName);\n\n\t\t// Decision table.\n\t\tif (existing === null) {\n\t\t\tconst created = yield* ops.createFresh({\n\t\t\t\tname: inputs.profile.containerName,\n\t\t\t\timage: inputs.image,\n\t\t\t\tnetwork: inputs.profile.networkName,\n\t\t\t\trouterProfileId: inputs.profile.id,\n\t\t\t\tentrypoints: inputs.entrypoints,\n\t\t\t\tdispatchDirHostPath: inputs.profile.dispatchDir,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tdecision: 'recreate-fresh' as const,\n\t\t\t\tcontainerId: created.id,\n\t\t\t\tnetworkId: network.id,\n\t\t\t\timageMatches: true,\n\t\t\t};\n\t\t}\n\n\t\tconst configMatches = routerSpecMatches(existing, inputs);\n\n\t\tif (existing.running === true && configMatches) {\n\t\t\treturn {\n\t\t\t\tdecision: 'adopt' as const,\n\t\t\t\tcontainerId: existing.id,\n\t\t\t\tnetworkId: network.id,\n\t\t\t\timageMatches: true,\n\t\t\t};\n\t\t}\n\n\t\tif (existing.running === 'unknown' && configMatches) {\n\t\t\tif (inputs.protectedRouteLeaseIds.length > 0) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\tstage: 'ensure-container',\n\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t`router profile '${inputs.profile.id}' container '${inputs.profile.containerName}' ` +\n\t\t\t\t\t\t\t`has unknown lifecycle state, and live or unknown route leases exist: ` +\n\t\t\t\t\t\t\tinputs.protectedRouteLeaseIds.join(', '),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tyield* ops.forceRemove(inputs.profile.containerName);\n\t\t\tconst created = yield* ops.createFresh({\n\t\t\t\tname: inputs.profile.containerName,\n\t\t\t\timage: inputs.image,\n\t\t\t\tnetwork: inputs.profile.networkName,\n\t\t\t\trouterProfileId: inputs.profile.id,\n\t\t\t\tentrypoints: inputs.entrypoints,\n\t\t\t\tdispatchDirHostPath: inputs.profile.dispatchDir,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tdecision: 'recreate-fresh' as const,\n\t\t\t\tcontainerId: created.id,\n\t\t\t\tnetworkId: network.id,\n\t\t\t\timageMatches: true,\n\t\t\t};\n\t\t}\n\n\t\tif (!configMatches) {\n\t\t\tif (inputs.protectedRouteLeaseIds.length > 0) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\tstage: 'ensure-container',\n\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t`router profile '${inputs.profile.id}' container '${inputs.profile.containerName}' ` +\n\t\t\t\t\t\t\t`does not match the requested spec, but live or unknown route leases exist: ` +\n\t\t\t\t\t\t\tinputs.protectedRouteLeaseIds.join(', '),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Running or stopped with stale image, bind mount, entrypoints,\n\t\t\t// provider args, network, labels, or mount mode — force-remove\n\t\t\t// + recreate. A profile singleton can only serve the spec it\n\t\t\t// was created with.\n\t\t\tyield* ops.forceRemove(inputs.profile.containerName);\n\t\t\tconst created = yield* ops.createFresh({\n\t\t\t\tname: inputs.profile.containerName,\n\t\t\t\timage: inputs.image,\n\t\t\t\tnetwork: inputs.profile.networkName,\n\t\t\t\trouterProfileId: inputs.profile.id,\n\t\t\t\tentrypoints: inputs.entrypoints,\n\t\t\t\tdispatchDirHostPath: inputs.profile.dispatchDir,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tdecision: 'recreate-fresh' as const,\n\t\t\t\tcontainerId: created.id,\n\t\t\t\tnetworkId: network.id,\n\t\t\t\timageMatches: true,\n\t\t\t};\n\t\t}\n\n\t\t// Stopped + image matches → resume; on failure recreate fresh.\n\t\tconst resumed = yield* ops.resume(inputs.profile.containerName).pipe(\n\t\t\tEffect.catchTag('RouterBootFailed', (cause) =>\n\t\t\t\tinputs.protectedRouteLeaseIds.length > 0\n\t\t\t\t\t? Effect.fail(\n\t\t\t\t\t\t\tnew RouterBootFailed({\n\t\t\t\t\t\t\t\tstage: 'ensure-container',\n\t\t\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t\t\t`router profile '${inputs.profile.id}' container '${inputs.profile.containerName}' ` +\n\t\t\t\t\t\t\t\t\t`could not be resumed, and live or unknown route leases exist: ` +\n\t\t\t\t\t\t\t\t\tinputs.protectedRouteLeaseIds.join(', '),\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t)\n\t\t\t\t\t: ops.forceRemove(inputs.profile.containerName).pipe(\n\t\t\t\t\t\t\tEffect.andThen(\n\t\t\t\t\t\t\t\tops.createFresh({\n\t\t\t\t\t\t\t\t\tname: inputs.profile.containerName,\n\t\t\t\t\t\t\t\t\timage: inputs.image,\n\t\t\t\t\t\t\t\t\tnetwork: inputs.profile.networkName,\n\t\t\t\t\t\t\t\t\trouterProfileId: inputs.profile.id,\n\t\t\t\t\t\t\t\t\tentrypoints: inputs.entrypoints,\n\t\t\t\t\t\t\t\t\tdispatchDirHostPath: inputs.profile.dispatchDir,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t),\n\t\t);\n\t\treturn {\n\t\t\tdecision: 'resume' as const,\n\t\t\tcontainerId: resumed.id,\n\t\t\tnetworkId: network.id,\n\t\t\timageMatches: true,\n\t\t};\n\t}).pipe(Effect.withSpan('orchestrator.router.bootstrap'));\n\n// ---------------------------------------------------------------------------\n// Docker-backed layer\n// ---------------------------------------------------------------------------\n\nconst routerBootFailed = (\n\tstage: RouterBootFailed['stage'],\n\tdetail: string,\n\tcause?: unknown,\n): RouterBootFailed =>\n\tnew RouterBootFailed({\n\t\tstage,\n\t\tdetail,\n\t\t...(cause === undefined ? {} : { cause }),\n\t});\n\nconst traefikRunArgs = (args: {\n\treadonly name: string;\n\treadonly image: string;\n\treadonly network: string;\n\treadonly routerProfileId: string;\n\treadonly entrypoints: ReadonlyArray<Entrypoint>;\n\treadonly dispatchDirHostPath: string;\n}): ReadonlyArray<string> => {\n\tconst ports = uniqueSortedEntrypointPorts(args.entrypoints);\n\tconst dockerArgs: Array<string> = [\n\t\t'-d',\n\t\t'--name',\n\t\targs.name,\n\t\t'--network',\n\t\targs.network,\n\t\t'--label',\n\t\t`${LabelKey.managed}=true`,\n\t\t'--label',\n\t\t`${LabelKey.kind}=${ROUTER_KIND_LABEL_VALUE}`,\n\t\t'--label',\n\t\t`${ROUTER_PROFILE_LABEL}=${args.routerProfileId}`,\n\t\t'--label',\n\t\t`${LabelKey.specVersion}=${ROUTER_CONTAINER_SPEC_VERSION}`,\n\t];\n\tfor (const pair of HOST_GATEWAY_ADD_HOST_FLAGS) {\n\t\tdockerArgs.push('--add-host', pair);\n\t}\n\tfor (const port of ports) {\n\t\tdockerArgs.push('-p', `127.0.0.1:${port}:${port}`);\n\t}\n\tdockerArgs.push(\n\t\t'--mount',\n\t\t`type=bind,source=${args.dispatchDirHostPath},target=${TRAEFIK_DISPATCH_MOUNT_TARGET},readonly`,\n\t\targs.image,\n\t\t...traefikExpectedCommand(args.entrypoints),\n\t);\n\treturn dockerArgs;\n};\n\nexport const layerTraefikContainerOpsDocker: Layer.Layer<\n\tTraefikContainerOpsService,\n\tnever,\n\tDockerHost | DockerSpawner\n> = Layer.effect(\n\tTraefikContainerOpsService,\n\tEffect.gen(function* () {\n\t\tconst dockerHost = yield* DockerHost;\n\t\tconst dockerSpawner = yield* DockerSpawner;\n\t\tconst provideDocker = <A, E>(\n\t\t\teffect: Effect.Effect<A, E, DockerHost | DockerSpawner>,\n\t\t): Effect.Effect<A, E, never> =>\n\t\t\teffect.pipe(\n\t\t\t\tEffect.provideService(DockerHost, dockerHost),\n\t\t\t\tEffect.provideService(DockerSpawner, dockerSpawner),\n\t\t\t);\n\n\t\tconst ops: TraefikContainerOps = {\n\t\t\tensureNetwork: (name) =>\n\t\t\t\tprovideDocker(\n\t\t\t\t\tensureNetwork(name, { app: 'devstack-router', stack: name, composeUi: false }),\n\t\t\t\t).pipe(\n\t\t\t\t\tEffect.map((id) => ({ id })),\n\t\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\t\trouterBootFailed(\n\t\t\t\t\t\t\t'ensure-network',\n\t\t\t\t\t\t\t`failed to ensure shared router network '${name}'`,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\tinspectContainer: (name) =>\n\t\t\t\tprovideDocker(inspectContainer(name)).pipe(\n\t\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\t\trouterBootFailed('inspect', `failed to inspect router container '${name}'`, cause),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.map((facts) =>\n\t\t\t\t\t\tfacts === null\n\t\t\t\t\t\t\t? null\n\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\tid: facts.id,\n\t\t\t\t\t\t\t\t\trunning: facts.lifecycle.kind === 'unknown' ? 'unknown' : facts.running,\n\t\t\t\t\t\t\t\t\timage: facts.image,\n\t\t\t\t\t\t\t\t\tdispatchMount: (() => {\n\t\t\t\t\t\t\t\t\t\tconst mount = facts.mounts?.find(\n\t\t\t\t\t\t\t\t\t\t\t(m) => m.target === TRAEFIK_DISPATCH_MOUNT_TARGET,\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\treturn mount === undefined\n\t\t\t\t\t\t\t\t\t\t\t? null\n\t\t\t\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tsource: mount.source,\n\t\t\t\t\t\t\t\t\t\t\t\t\ttarget: mount.target,\n\t\t\t\t\t\t\t\t\t\t\t\t\treadOnly: mount.readOnly === true,\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t})(),\n\t\t\t\t\t\t\t\t\tportBindings: facts.portBindings ?? [],\n\t\t\t\t\t\t\t\t\tcommand: facts.command ?? [],\n\t\t\t\t\t\t\t\t\tnetworks: facts.networks ?? [],\n\t\t\t\t\t\t\t\t\tlabels: facts.labels ?? {},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\tcreateFresh: (args) =>\n\t\t\t\tprovideDocker(dockerRun('run', traefikRunArgs(args))).pipe(\n\t\t\t\t\tEffect.map((result) => ({ id: result.stdout.trim() })),\n\t\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\t\trouterBootFailed(\n\t\t\t\t\t\t\t'ensure-container',\n\t\t\t\t\t\t\t`failed to create router container '${args.name}'`,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\tresume: (name) =>\n\t\t\t\tprovideDocker(dockerRun('start', [name])).pipe(\n\t\t\t\t\tEffect.flatMap(() => provideDocker(inspectContainer(name))),\n\t\t\t\t\tEffect.flatMap((facts) =>\n\t\t\t\t\t\tfacts === null\n\t\t\t\t\t\t\t? Effect.fail(\n\t\t\t\t\t\t\t\t\trouterBootFailed(\n\t\t\t\t\t\t\t\t\t\t'inspect',\n\t\t\t\t\t\t\t\t\t\t`router container '${name}' started but could not be inspected`,\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: Effect.succeed({ id: facts.id }),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\t\tcause instanceof RouterBootFailed\n\t\t\t\t\t\t\t? cause\n\t\t\t\t\t\t\t: routerBootFailed(\n\t\t\t\t\t\t\t\t\t'ensure-container',\n\t\t\t\t\t\t\t\t\t`failed to resume router container '${name}'`,\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\tforceRemove: (name) =>\n\t\t\t\tprovideDocker(dockerRunOk('rm', ['-f', name])).pipe(\n\t\t\t\t\tEffect.flatMap((result) => {\n\t\t\t\t\t\tif (result.exitCode === 0 || isNoSuchContainerStderr(result.stderr)) {\n\t\t\t\t\t\t\treturn Effect.void;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn Effect.fail(\n\t\t\t\t\t\t\trouterBootFailed(\n\t\t\t\t\t\t\t\t'ensure-container',\n\t\t\t\t\t\t\t\t`failed to remove router container '${name}': ${result.stderr}`,\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\tEffect.mapError((cause) =>\n\t\t\t\t\t\tcause instanceof RouterBootFailed\n\t\t\t\t\t\t\t? cause\n\t\t\t\t\t\t\t: routerBootFailed(\n\t\t\t\t\t\t\t\t\t'ensure-container',\n\t\t\t\t\t\t\t\t\t`failed to remove router container '${name}'`,\n\t\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t};\n\n\t\treturn TraefikContainerOpsService.of(ops);\n\t}),\n);\n\n// ---------------------------------------------------------------------------\n// Stub layer — injected by tests\n// ---------------------------------------------------------------------------\n\n/** No-op implementation for tests that assert file-provider delivery\n * without managing a real Traefik container. Production composition\n * uses `layerTraefikContainerOpsDocker`. */\nexport const layerTraefikContainerOpsStub: Layer.Layer<TraefikContainerOpsService> = Layer.succeed(\n\tTraefikContainerOpsService,\n)({\n\tensureNetwork: (_name) => Effect.succeed({ id: 'stub-network' }),\n\tinspectContainer: (_name) => Effect.succeed(null),\n\tcreateFresh: (_args) => Effect.succeed({ id: 'stub-container' }),\n\tresume: (_name) => Effect.succeed({ id: 'stub-container' }),\n\tforceRemove: (_name) => Effect.succeed(undefined),\n});\n"],"mappings":"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"}
|
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import { IdentityContext, RuntimeRoot, StackPathsService, layerIdentity, layerRuntimeRoot, layerStackPaths } from "
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import { PostAcquireTasksService, layerPostAcquireTasks } from "
|
|
13
|
-
import { startSupervisor } from "
|
|
1
|
+
import { IdentityContext, RuntimeRoot, StackPathsService, layerIdentity, layerRuntimeRoot, layerStackPaths } from "../substrate/runtime/paths.mjs";
|
|
2
|
+
import { Logger as Logger$1, layerLogger } from "../substrate/runtime/observability/logger.mjs";
|
|
3
|
+
import "../substrate/runtime/observability/index.mjs";
|
|
4
|
+
import { CacheService, layerCache } from "../substrate/runtime/cache/service.mjs";
|
|
5
|
+
import "../substrate/runtime/cache/index.mjs";
|
|
6
|
+
import { DockerSpawner, layerDockerHostDefault } from "../runtime/docker/client.mjs";
|
|
7
|
+
import { ContainerRuntimeService, layerContainerRuntimeDocker, layerDockerCycleInitial } from "../runtime/docker/service.mjs";
|
|
8
|
+
import "../runtime/docker/index.mjs";
|
|
9
|
+
import { awaitAll } from "../substrate/runtime/lifecycle/ready-gate.mjs";
|
|
10
|
+
import "../substrate/runtime/lifecycle/index.mjs";
|
|
11
|
+
import { StrategyRegistryService, layerStrategyRegistry } from "../substrate/runtime/strategy-registry/service.mjs";
|
|
12
|
+
import { PostAcquireTasksService, layerPostAcquireTasks } from "../substrate/runtime/post-acquire-tasks.mjs";
|
|
13
|
+
import { startSupervisor } from "../substrate/runtime/supervisor/start-supervisor.mjs";
|
|
14
|
+
import "../substrate/runtime/supervisor/index.mjs";
|
|
15
|
+
import { LeaseBrokerService, layerLeaseBroker } from "../substrate/runtime/lease-broker/service.mjs";
|
|
16
|
+
import "../substrate/runtime/lease-broker/index.mjs";
|
|
17
|
+
import { ArtifactPublisherService, layerArtifactPublisher } from "../substrate/runtime/artifact-publisher/index.mjs";
|
|
18
|
+
import { PortBrokerService, layerPortBroker } from "../substrate/runtime/port-broker/service.mjs";
|
|
19
|
+
import "../substrate/runtime/port-broker/index.mjs";
|
|
20
|
+
import "../substrate/runtime/strategy-registry/index.mjs";
|
|
21
|
+
import "../substrate/runtime/projection/index.mjs";
|
|
14
22
|
import { Context, Effect, Layer } from "effect";
|
|
15
23
|
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
|
|
16
24
|
import * as NodePath from "@effect/platform-node/NodePath";
|
|
17
25
|
import * as NodeChildProcessSpawner from "@effect/platform-node/NodeChildProcessSpawner";
|
|
18
26
|
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
|
|
19
|
-
//#region src/
|
|
27
|
+
//#region src/orchestrators/run.ts
|
|
20
28
|
/** Substrate Layer stack for a single supervised run. Composes every L0
|
|
21
29
|
* service the supervisor yields from its R-channel, plus the L1 Docker
|
|
22
30
|
* `ContainerRuntime`. Built-in plugin services are layered outside
|
|
@@ -35,8 +43,7 @@ const buildSubstrateLayers = (identity, runtimeRoot) => {
|
|
|
35
43
|
const withSpawnerAdapter = layerDockerSpawnerFromNode.pipe(Layer.provideMerge(withLeaseBroker));
|
|
36
44
|
const withContainerRuntime = layerContainerRuntimeDocker.pipe(Layer.provideMerge(withSpawnerAdapter));
|
|
37
45
|
const withPostAcquireTasks = layerPostAcquireTasks.pipe(Layer.provideMerge(withContainerRuntime));
|
|
38
|
-
|
|
39
|
-
return layerLogger.pipe(Layer.provideMerge(withRedactor));
|
|
46
|
+
return layerLogger.pipe(Layer.provideMerge(withPostAcquireTasks));
|
|
40
47
|
};
|
|
41
48
|
/** Build the opaque `Context.Context<never>` the supervisor hands to
|
|
42
49
|
* every plugin's `acquire` body. Yields every substrate service the
|
|
@@ -55,18 +62,16 @@ const buildPluginContext = () => Effect.gen(function* () {
|
|
|
55
62
|
const leaseBroker = yield* LeaseBrokerService;
|
|
56
63
|
const postAcquireTasks = yield* PostAcquireTasksService;
|
|
57
64
|
const logger = yield* Logger$1;
|
|
58
|
-
|
|
59
|
-
return Context.empty().pipe(Context.add(IdentityContext, identityCtx), Context.add(RuntimeRoot, runtimeRootResolved), Context.add(StackPathsService, stackPaths), Context.add(CacheService, cache), Context.add(StrategyRegistryService, registry), Context.add(ContainerRuntimeService, containerRuntime), Context.add(ArtifactPublisherService, publisher), Context.add(PortBrokerService, portBroker), Context.add(LeaseBrokerService, leaseBroker), Context.add(PostAcquireTasksService, postAcquireTasks), Context.add(Logger$1, logger), Context.add(Redactor, redactor));
|
|
65
|
+
return Context.empty().pipe(Context.add(IdentityContext, identityCtx), Context.add(RuntimeRoot, runtimeRootResolved), Context.add(StackPathsService, stackPaths), Context.add(CacheService, cache), Context.add(StrategyRegistryService, registry), Context.add(ContainerRuntimeService, containerRuntime), Context.add(ArtifactPublisherService, publisher), Context.add(PortBrokerService, portBroker), Context.add(LeaseBrokerService, leaseBroker), Context.add(PostAcquireTasksService, postAcquireTasks), Context.add(Logger$1, logger));
|
|
60
66
|
});
|
|
61
67
|
/** Effect that boots the supervisor for `stack` and blocks on the
|
|
62
68
|
* supervisor's shutdown latch. Returns the final `SubscribableState`
|
|
63
69
|
* snapshot so callers (CLI, library handle) can inspect post-shutdown
|
|
64
70
|
* state. */
|
|
65
71
|
const superviseStackEffect = (stack, identity, state, opts = {}) => Effect.gen(function* () {
|
|
66
|
-
const baseContext = yield* buildPluginContext();
|
|
67
|
-
const pluginContext = opts.extendContext === void 0 ? baseContext : yield* opts.extendContext(baseContext);
|
|
68
72
|
return yield* Effect.scoped(Effect.gen(function* () {
|
|
69
|
-
const
|
|
73
|
+
const baseContext = yield* buildPluginContext();
|
|
74
|
+
const startup = yield* startSupervisor(stack, identity, state, opts.extendContext === void 0 ? baseContext : yield* opts.extendContext(baseContext), opts.orchestratorSinks ?? [], opts.commandHandler, opts.postAcquireHook, { commandLoop: opts.lifetime !== "one-shot" });
|
|
70
75
|
const { handle } = startup;
|
|
71
76
|
if (opts.beforeInitialAcquire !== void 0) yield* opts.beforeInitialAcquire(handle);
|
|
72
77
|
if (opts.lifetime === "one-shot") {
|