@mysten-incubation/devstack 0.0.1 → 0.1.1
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 +8 -4
- 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 +41 -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,58 +1,34 @@
|
|
|
1
1
|
import { RuntimeRoot } from "../paths.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { versionedDocSchema } from "../../versioned-doc-schema.mjs";
|
|
3
|
+
import { selfPid } from "../cross-process/self-pid.mjs";
|
|
4
4
|
import { PortBrokerError } from "../errors.mjs";
|
|
5
|
+
import { atomicWriteFileExclusiveSync } from "../atomic-write.mjs";
|
|
5
6
|
import { decodeJsonTextSync } from "../runtime-decode.mjs";
|
|
7
|
+
import { RosterHolderSchema } from "../../cross-process.mjs";
|
|
8
|
+
import { checkHolderLiveness, ownHolder } from "../cross-process/liveness.mjs";
|
|
6
9
|
import { Context, Effect, Layer, Ref, Schema } from "effect";
|
|
10
|
+
import { readFileSync, unlinkSync } from "node:fs";
|
|
7
11
|
import { randomUUID } from "node:crypto";
|
|
8
|
-
import {
|
|
12
|
+
import { join } from "node:path";
|
|
9
13
|
import { createServer } from "node:net";
|
|
10
|
-
import { dirname, join } from "node:path";
|
|
11
14
|
//#region src/substrate/runtime/port-broker/service.ts
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* forward-scan only ever crosses a window boundary if EVERY port in
|
|
24
|
-
* the window is taken (effectively never on dev machines). */
|
|
25
|
-
const PORT_KINDS = [
|
|
26
|
-
"wallet",
|
|
27
|
-
"http",
|
|
28
|
-
"rpc",
|
|
29
|
-
"misc"
|
|
30
|
-
];
|
|
31
|
-
/** Sequential search window per kind. The broker tries `preferredPort`
|
|
32
|
-
* first (if given), then `[start, start + window)` in order. */
|
|
33
|
-
const PORT_RANGES = {
|
|
34
|
-
wallet: {
|
|
35
|
-
start: 39200,
|
|
36
|
-
window: 1e3
|
|
37
|
-
},
|
|
38
|
-
http: {
|
|
39
|
-
start: 5e4,
|
|
40
|
-
window: 1e3
|
|
41
|
-
},
|
|
42
|
-
rpc: {
|
|
43
|
-
start: 51e3,
|
|
44
|
-
window: 1e3
|
|
45
|
-
},
|
|
46
|
-
misc: {
|
|
47
|
-
start: 52e3,
|
|
48
|
-
window: 1e3
|
|
49
|
-
}
|
|
15
|
+
/** Default scan window. Sized for "tens of plugins per stack" with
|
|
16
|
+
* parallel-stack overhead headroom. 1000 ports is more than enough for
|
|
17
|
+
* any realistic in-process devstack; the forward-scan only ever
|
|
18
|
+
* reaches the end if EVERY port in the window is taken (effectively
|
|
19
|
+
* never on dev machines). Start chosen to land between common dev
|
|
20
|
+
* defaults (3000-5173, 8000-8080) and the ephemeral range so a
|
|
21
|
+
* sibling Vite/Next process doesn't accidentally collide on the
|
|
22
|
+
* first probe. */
|
|
23
|
+
const DEFAULT_PORT_WINDOW = {
|
|
24
|
+
start: 39200,
|
|
25
|
+
size: 1e3
|
|
50
26
|
};
|
|
27
|
+
const DEFAULT_OWNER = "unknown";
|
|
51
28
|
const PORT_RESERVATION_VERSION = 1;
|
|
52
|
-
const PortReservationDocSchema =
|
|
53
|
-
version: Schema.Literal(PORT_RESERVATION_VERSION),
|
|
29
|
+
const PortReservationDocSchema = versionedDocSchema(PORT_RESERVATION_VERSION, {
|
|
54
30
|
port: Schema.Number,
|
|
55
|
-
|
|
31
|
+
owner: Schema.String,
|
|
56
32
|
ownerId: Schema.String,
|
|
57
33
|
holder: RosterHolderSchema
|
|
58
34
|
});
|
|
@@ -69,29 +45,21 @@ const parseReservationDoc = (raw) => {
|
|
|
69
45
|
};
|
|
70
46
|
const tryWriteReservationSync = (path, doc) => {
|
|
71
47
|
try {
|
|
72
|
-
|
|
73
|
-
writeFileSync(path, `${JSON.stringify(doc)}\n`, {
|
|
74
|
-
flag: "wx",
|
|
75
|
-
mode: 384
|
|
76
|
-
});
|
|
48
|
+
atomicWriteFileExclusiveSync(path, `${JSON.stringify(doc)}\n`, { mode: 384 });
|
|
77
49
|
return { _tag: "written" };
|
|
78
50
|
} catch (cause) {
|
|
79
|
-
if (cause.code
|
|
80
|
-
_tag: "failed",
|
|
81
|
-
cause
|
|
82
|
-
};
|
|
83
|
-
if (!existsSync(path)) return { _tag: "race" };
|
|
84
|
-
try {
|
|
51
|
+
if (cause.code === "EEXIST") try {
|
|
85
52
|
return {
|
|
86
53
|
_tag: "exists",
|
|
87
54
|
doc: parseReservationDoc(readFileSync(path, "utf8"))
|
|
88
55
|
};
|
|
89
56
|
} catch {
|
|
90
|
-
return {
|
|
91
|
-
_tag: "exists",
|
|
92
|
-
doc: null
|
|
93
|
-
};
|
|
57
|
+
return { _tag: "race" };
|
|
94
58
|
}
|
|
59
|
+
return {
|
|
60
|
+
_tag: "failed",
|
|
61
|
+
cause
|
|
62
|
+
};
|
|
95
63
|
}
|
|
96
64
|
};
|
|
97
65
|
const unlinkReservationIfOwnerSync = (path, ownerId) => {
|
|
@@ -108,16 +76,35 @@ const reclaimReservationIfOwnerSync = (path, ownerId) => {
|
|
|
108
76
|
return false;
|
|
109
77
|
}
|
|
110
78
|
};
|
|
111
|
-
|
|
79
|
+
/** Best-effort removal of an EXISTING reservation file whose body
|
|
80
|
+
* cannot be decoded (a leftover from an incompatible-version
|
|
81
|
+
* supervisor, or a torn write). Such a file carries no readable
|
|
82
|
+
* `ownerId`/`holder`, so neither `reclaimReservationIfOwnerSync` nor
|
|
83
|
+
* liveness can ever clear it; without this it would skip that one
|
|
84
|
+
* port for every future process for the supervisor's lifetime. We
|
|
85
|
+
* treat an undecodable body as a stale artifact and unlink it so the
|
|
86
|
+
* next exclusive-create can win. Returns whether the unlink ran
|
|
87
|
+
* (false if the file vanished first, i.e. a peer already cleared it)
|
|
88
|
+
* so the caller can bound its retry. */
|
|
89
|
+
const reclaimUndecodableReservationSync = (path) => {
|
|
90
|
+
try {
|
|
91
|
+
unlinkSync(path);
|
|
92
|
+
return true;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const acquirePortReservation = (root, port, owner) => Effect.gen(function* () {
|
|
112
98
|
const path = portReservationPath(root, port);
|
|
113
|
-
const ownerId = `${
|
|
99
|
+
const ownerId = `${selfPid()}-${randomUUID().slice(0, 8)}`;
|
|
114
100
|
const doc = {
|
|
115
101
|
version: PORT_RESERVATION_VERSION,
|
|
116
102
|
port,
|
|
117
|
-
|
|
103
|
+
owner,
|
|
118
104
|
ownerId,
|
|
119
105
|
holder: ownHolder()
|
|
120
106
|
};
|
|
107
|
+
let reclaimedUndecodable = false;
|
|
121
108
|
while (true) {
|
|
122
109
|
const attempt = tryWriteReservationSync(path, doc);
|
|
123
110
|
if (attempt._tag === "written") return {
|
|
@@ -134,12 +121,18 @@ const acquirePortReservation = (root, port, kind) => Effect.gen(function* () {
|
|
|
134
121
|
detail: `port reservation ${path} could not be written`,
|
|
135
122
|
cause: attempt.cause
|
|
136
123
|
};
|
|
137
|
-
if (attempt.doc === null)
|
|
124
|
+
if (attempt.doc === null) {
|
|
125
|
+
if (!reclaimedUndecodable && reclaimUndecodableReservationSync(path)) {
|
|
126
|
+
reclaimedUndecodable = true;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
return { _tag: "busy" };
|
|
130
|
+
}
|
|
138
131
|
if ((yield* checkHolderLiveness(attempt.doc.holder).pipe(Effect.catch(() => Effect.succeed("alive")))) === "alive") return { _tag: "busy" };
|
|
139
132
|
if (!reclaimReservationIfOwnerSync(path, attempt.doc.ownerId)) return { _tag: "busy" };
|
|
140
133
|
}
|
|
141
134
|
});
|
|
142
|
-
var PortBrokerService = class extends Context.Service()("@devstack
|
|
135
|
+
var PortBrokerService = class extends Context.Service()("@devstack/substrate/PortBroker") {};
|
|
143
136
|
/**
|
|
144
137
|
* Layer that materializes one `PortBroker` per stack scope.
|
|
145
138
|
*
|
|
@@ -152,13 +145,10 @@ var PortBrokerService = class extends Context.Service()("@devstack-rewrite/subst
|
|
|
152
145
|
const layerPortBroker = Layer.effect(PortBrokerService, Effect.gen(function* () {
|
|
153
146
|
const reservationRoot = (yield* RuntimeRoot).root;
|
|
154
147
|
const state = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
155
|
-
const tryReserve = (port,
|
|
148
|
+
const tryReserve = (port, owner) => Ref.modify(state, (current) => {
|
|
156
149
|
if (current.has(port)) return [false, current];
|
|
157
150
|
const next = new Map(current);
|
|
158
|
-
next.set(port, {
|
|
159
|
-
kind,
|
|
160
|
-
owner
|
|
161
|
-
});
|
|
151
|
+
next.set(port, { owner });
|
|
162
152
|
return [true, next];
|
|
163
153
|
});
|
|
164
154
|
const drop = (port) => Ref.update(state, (current) => {
|
|
@@ -167,81 +157,130 @@ const layerPortBroker = Layer.effect(PortBrokerService, Effect.gen(function* ()
|
|
|
167
157
|
next.delete(port);
|
|
168
158
|
return next;
|
|
169
159
|
});
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
160
|
+
/** Reserve `port` in the in-process Map, run the probe+reservation
|
|
161
|
+
* chain through to either (a) `finishAllocation` arming its scope
|
|
162
|
+
* finalizer or (b) a clean release of BOTH the in-process slot
|
|
163
|
+
* AND the on-disk reservation file.
|
|
164
|
+
*
|
|
165
|
+
* Interrupt safety: the critical region is wrapped in
|
|
166
|
+
* `Effect.acquireUseRelease`, whose `release` step is
|
|
167
|
+
* uninterruptible while its `use` step is NOT (Effect v4). The
|
|
168
|
+
* acquired `PortReservation` is created INSIDE `use` (after the
|
|
169
|
+
* on-disk file is written), so the `use` body stashes it in
|
|
170
|
+
* `reservationCell`; the uninterruptible `release` reads that cell
|
|
171
|
+
* and unlinks the reservation file on ANY non-`kept` exit —
|
|
172
|
+
* interrupt, typed failure, or a non-`kept` success outcome.
|
|
173
|
+
* Without this, an interrupt landing between the reservation write
|
|
174
|
+
* (in `acquirePortReservation`) and the finalizer arm (in
|
|
175
|
+
* `finishAllocation`) left the file on disk while the old release
|
|
176
|
+
* only dropped the in-process slot — leaking
|
|
177
|
+
* `port-locks/<port>.json`. Its `holder` is this still-alive
|
|
178
|
+
* process, so every future `allocate` of that port then saw
|
|
179
|
+
* `checkHolderLiveness` report `alive`, marking it busy for the
|
|
180
|
+
* whole supervisor lifetime.
|
|
181
|
+
*
|
|
182
|
+
* The only path that intentionally KEEPS both the slot and the
|
|
183
|
+
* file is the `kept` outcome, where ownership transfers to the
|
|
184
|
+
* scope finalizer (see `finishAllocation`); `reservationCell` is
|
|
185
|
+
* cleared there so `release` is a no-op for it.
|
|
186
|
+
* (`unlinkReservationIfOwnerSync` is owner-id-guarded and
|
|
187
|
+
* idempotent, so a stray double-release is harmless — clearing
|
|
188
|
+
* the cell just avoids the redundant stat.)
|
|
189
|
+
*/
|
|
190
|
+
const attemptSlot = (port, owner, probeHost) => {
|
|
191
|
+
let reservationCell = null;
|
|
192
|
+
return Effect.acquireUseRelease(tryReserve(port, owner), (reserved) => {
|
|
193
|
+
if (!reserved) return Effect.succeed(null);
|
|
194
|
+
return Effect.gen(function* () {
|
|
195
|
+
const reservation = yield* acquirePortReservation(reservationRoot, port, owner);
|
|
196
|
+
if (reservation._tag === "busy") return { _tag: "busy" };
|
|
197
|
+
if (reservation._tag === "failed") return {
|
|
198
|
+
_tag: "reservation-failed",
|
|
199
|
+
detail: reservation.detail,
|
|
200
|
+
cause: reservation.cause
|
|
201
|
+
};
|
|
202
|
+
reservationCell = reservation.reservation;
|
|
203
|
+
const ok = yield* probePort(port, probeHost);
|
|
204
|
+
if (ok._tag === "ok") {
|
|
205
|
+
const allocated = yield* finishAllocation(port, owner, drop, reservation.reservation);
|
|
206
|
+
reservationCell = null;
|
|
207
|
+
return {
|
|
208
|
+
_tag: "kept",
|
|
209
|
+
allocated
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (ok._tag === "probe-failed") return {
|
|
213
|
+
_tag: "probe-failed",
|
|
214
|
+
cause: ok.cause
|
|
215
|
+
};
|
|
216
|
+
return { _tag: "busy" };
|
|
217
|
+
});
|
|
218
|
+
}, (reserved, exit) => {
|
|
219
|
+
if (!reserved) return Effect.void;
|
|
220
|
+
if (exit._tag === "Success" && exit.value?._tag === "kept") return Effect.void;
|
|
221
|
+
const releaseReservation = reservationCell !== null ? reservationCell.release : Effect.void;
|
|
222
|
+
return Effect.all([drop(port), releaseReservation], { discard: true });
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
const allocate = (opts = {}) => Effect.gen(function* () {
|
|
226
|
+
const owner = opts.owner ?? DEFAULT_OWNER;
|
|
173
227
|
const probeHost = opts.probeHost ?? "127.0.0.1";
|
|
228
|
+
const windowStart = opts.windowHint?.start ?? DEFAULT_PORT_WINDOW.start;
|
|
229
|
+
const windowSize = opts.windowHint?.size ?? DEFAULT_PORT_WINDOW.size;
|
|
174
230
|
if (opts.preferredPort !== void 0) {
|
|
175
|
-
|
|
231
|
+
const outcome = yield* attemptSlot(opts.preferredPort, owner, probeHost);
|
|
232
|
+
if (outcome === null) return yield* Effect.fail(new PortBrokerError({
|
|
176
233
|
reason: "preferred-busy",
|
|
177
|
-
detail: `preferred port ${opts.preferredPort} (
|
|
234
|
+
detail: `preferred port ${opts.preferredPort} (owner=${owner}) is already held by another allocation in this stack`
|
|
235
|
+
}));
|
|
236
|
+
if (outcome._tag === "kept") return outcome.allocated;
|
|
237
|
+
if (outcome._tag === "reservation-failed") return yield* Effect.fail(new PortBrokerError({
|
|
238
|
+
reason: "reservation-failed",
|
|
239
|
+
detail: outcome.detail,
|
|
240
|
+
cause: outcome.cause
|
|
241
|
+
}));
|
|
242
|
+
if (outcome._tag === "probe-failed") return yield* Effect.fail(new PortBrokerError({
|
|
243
|
+
reason: "bind-probe-failed",
|
|
244
|
+
detail: `bind probe on preferred port ${opts.preferredPort} (owner=${owner}, host=${probeHost}) failed with non-EADDRINUSE error`,
|
|
245
|
+
cause: outcome.cause
|
|
178
246
|
}));
|
|
179
|
-
const reservation = yield* acquirePortReservation(reservationRoot, opts.preferredPort, opts.kind);
|
|
180
|
-
if (reservation._tag === "busy") yield* drop(opts.preferredPort);
|
|
181
|
-
else if (reservation._tag === "failed") {
|
|
182
|
-
yield* drop(opts.preferredPort);
|
|
183
|
-
return yield* Effect.fail(new PortBrokerError({
|
|
184
|
-
reason: "reservation-failed",
|
|
185
|
-
detail: reservation.detail,
|
|
186
|
-
cause: reservation.cause
|
|
187
|
-
}));
|
|
188
|
-
} else {
|
|
189
|
-
const ok = yield* probePort(opts.preferredPort, probeHost);
|
|
190
|
-
if (ok._tag === "ok") return yield* finishAllocation(opts.preferredPort, opts.kind, drop, reservation.reservation);
|
|
191
|
-
yield* reservation.reservation.release;
|
|
192
|
-
yield* drop(opts.preferredPort);
|
|
193
|
-
if (ok._tag === "probe-failed") return yield* Effect.fail(new PortBrokerError({
|
|
194
|
-
reason: "bind-probe-failed",
|
|
195
|
-
detail: `bind probe on preferred port ${opts.preferredPort} (${opts.kind}, host=${probeHost}) failed with non-EADDRINUSE error`,
|
|
196
|
-
cause: ok.cause
|
|
197
|
-
}));
|
|
198
|
-
}
|
|
199
247
|
}
|
|
200
|
-
for (let p =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
reason: "reservation-failed",
|
|
211
|
-
detail: reservation.detail,
|
|
212
|
-
cause: reservation.cause
|
|
213
|
-
}));
|
|
214
|
-
}
|
|
215
|
-
const ok = yield* probePort(p, probeHost);
|
|
216
|
-
if (ok._tag === "ok") return yield* finishAllocation(p, opts.kind, drop, reservation.reservation);
|
|
217
|
-
yield* reservation.reservation.release;
|
|
218
|
-
yield* drop(p);
|
|
219
|
-
if (ok._tag === "probe-failed") return yield* Effect.fail(new PortBrokerError({
|
|
248
|
+
for (let p = windowStart; p < windowStart + windowSize; p++) {
|
|
249
|
+
const outcome = yield* attemptSlot(p, owner, probeHost);
|
|
250
|
+
if (outcome === null) continue;
|
|
251
|
+
if (outcome._tag === "kept") return outcome.allocated;
|
|
252
|
+
if (outcome._tag === "reservation-failed") return yield* Effect.fail(new PortBrokerError({
|
|
253
|
+
reason: "reservation-failed",
|
|
254
|
+
detail: outcome.detail,
|
|
255
|
+
cause: outcome.cause
|
|
256
|
+
}));
|
|
257
|
+
if (outcome._tag === "probe-failed") return yield* Effect.fail(new PortBrokerError({
|
|
220
258
|
reason: "bind-probe-failed",
|
|
221
|
-
detail: `bind probe on port ${p} (
|
|
222
|
-
cause:
|
|
259
|
+
detail: `bind probe on port ${p} (owner=${owner}, host=${probeHost}) failed with non-EADDRINUSE error`,
|
|
260
|
+
cause: outcome.cause
|
|
223
261
|
}));
|
|
224
262
|
}
|
|
225
263
|
return yield* Effect.fail(new PortBrokerError({
|
|
226
264
|
reason: "no-free-port",
|
|
227
|
-
detail: `no free port found in [${
|
|
265
|
+
detail: `no free port found in [${windowStart}, ${windowStart + windowSize}) for owner '${owner}'`
|
|
228
266
|
}));
|
|
229
267
|
}).pipe(Effect.withSpan("substrate.portBroker.allocate", { attributes: {
|
|
230
|
-
|
|
268
|
+
owner: opts.owner ?? DEFAULT_OWNER,
|
|
231
269
|
preferredPort: opts.preferredPort ?? -1,
|
|
232
|
-
probeHost: opts.probeHost ?? "127.0.0.1"
|
|
270
|
+
probeHost: opts.probeHost ?? "127.0.0.1",
|
|
271
|
+
windowStart: opts.windowHint?.start ?? DEFAULT_PORT_WINDOW.start,
|
|
272
|
+
windowSize: opts.windowHint?.size ?? DEFAULT_PORT_WINDOW.size
|
|
233
273
|
} }));
|
|
234
|
-
const finishAllocation = (port,
|
|
274
|
+
const finishAllocation = (port, owner, dropFn, reservation) => Effect.gen(function* () {
|
|
235
275
|
const release = Effect.all([dropFn(port), reservation.release], { discard: true });
|
|
236
276
|
yield* Effect.addFinalizer(() => release.pipe(Effect.uninterruptible));
|
|
237
277
|
yield* Effect.annotateCurrentSpan({
|
|
238
278
|
"portBroker.port": port,
|
|
239
|
-
"portBroker.
|
|
279
|
+
"portBroker.owner": owner
|
|
240
280
|
});
|
|
241
|
-
yield* Effect.logDebug(`port-broker allocated ${port} (
|
|
281
|
+
yield* Effect.logDebug(`port-broker allocated ${port} (owner=${owner})`);
|
|
242
282
|
return {
|
|
243
283
|
port,
|
|
244
|
-
kind,
|
|
245
284
|
release
|
|
246
285
|
};
|
|
247
286
|
});
|
|
@@ -252,7 +291,7 @@ const layerPortBroker = Layer.effect(PortBrokerService, Effect.gen(function* ()
|
|
|
252
291
|
* The OS atomically rejects with `EADDRINUSE` if another process
|
|
253
292
|
* already holds the port — that's our cross-process collision check.
|
|
254
293
|
*
|
|
255
|
-
* The default probe is loopback-only because
|
|
294
|
+
* The default probe is loopback-only because most servers bind
|
|
256
295
|
* loopback. Docker-backed callers pass `0.0.0.0`; on Docker Desktop,
|
|
257
296
|
* binding a transient Node server on `0.0.0.0` does not always reject
|
|
258
297
|
* ports already held on `127.0.0.1`, even though Docker's publish step
|
|
@@ -312,8 +351,13 @@ const probeSinglePort = (port, host) => Effect.callback((resume) => {
|
|
|
312
351
|
server?.close();
|
|
313
352
|
} catch {}
|
|
314
353
|
}
|
|
354
|
+
return Effect.sync(() => {
|
|
355
|
+
try {
|
|
356
|
+
server?.close();
|
|
357
|
+
} catch {}
|
|
358
|
+
});
|
|
315
359
|
});
|
|
316
360
|
//#endregion
|
|
317
|
-
export { PortBrokerService, layerPortBroker };
|
|
361
|
+
export { DEFAULT_PORT_WINDOW, PortBrokerService, layerPortBroker };
|
|
318
362
|
|
|
319
363
|
//# sourceMappingURL=service.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.mjs","names":["createNetServer"],"sources":["../../../../src/substrate/runtime/port-broker/service.ts"],"sourcesContent":["// Port broker — substrate-level service.\n//\n// Architecture (`notes/redesign/architecture.md` § \"What's collapsed\"):\n// resource broker for host ports. Plugins that need to bind a host\n// HTTP server (wallet) or publish a container port on the host (Sui)\n// yield this service and call\n// `allocate({...})`. The broker:\n//\n// 1. Tracks in-process allocations in a `Ref<Map<port, Holder>>` so\n// two sibling plugins in the same stack don't both pick the same\n// port between bind-probe and `server.listen`.\n// 2. Claims a runtime-root scoped port reservation file before the\n// bind probe. This closes the cross-process probe→listen race:\n// two independent `devstack apply` processes sharing a state dir\n// cannot both choose the same candidate while neither real server\n// is listening yet.\n// 3. Verifies kernel-level availability by binding a transient\n// `net.Server` on the candidate port and closing it.\n// The OS's `EADDRINUSE` remains the final collision check for\n// non-devstack processes and stale reservation gaps.\n// 4. Releases on scope close. The broker hands back a `release`\n// Effect AND installs a scope finalizer; the architecture's\n// \"scope-local, never module-level\" invariant is honoured by\n// construction (the Layer is scope-local, the entries die with\n// the scope).\n//\n// \"Kind-window\" sequential search: each `kind` has its own starting\n// port and window size (`PORT_RANGES`). The broker scans forward from\n// `start` up to `start + window` (and from `preferredPort` if\n// supplied) until a probe succeeds.\n//\n// Cross-process safety: the reservation file is scoped to the runtime\n// root, not the stack root, because host ports are machine-global. The\n// stack-scoped supervisor lock serializes acquires of the SAME stack;\n// these reservation files serialize host-port choices across DIFFERENT\n// stacks and apps that share a state dir.\n\nimport { randomUUID } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport {\n\tcreateServer as createNetServer,\n\ttype AddressInfo,\n\ttype Server as NetServer,\n} from 'node:net';\nimport { dirname, join } from 'node:path';\n\nimport { Context, Effect, Layer, Ref, Schema, Scope } from 'effect';\n\nimport { RosterHolderSchema, type RosterHolder } from '../../cross-process.ts';\nimport { checkHolderLiveness, ownHolder } from '../cross-process/liveness.ts';\nimport { PortBrokerError } from '../errors.ts';\nimport { RuntimeRoot } from '../paths.ts';\nimport { decodeJsonTextSync } from '../runtime-decode.ts';\n\n// ----------------------------------------------------------------------\n// Public shape\n// ----------------------------------------------------------------------\n\n/** Allocation kind — drives the search-window starting port. Plugins\n * pass a literal so a `wallet` allocation and a `rpc` allocation\n * never collide on the same starting point.\n *\n * Extend `PORT_RANGES` below to add a new kind. The literal type is\n * closed deliberately — agnostic-naming would invite plugins to\n * freelance kinds (and overlap window starts unintentionally).\n *\n * Today's windows are sized for \"tens of plugins per stack\" with\n * parallel-stack overhead headroom. Each window is 1000 ports; that's\n * more than enough for any realistic in-process devstack, and the\n * forward-scan only ever crosses a window boundary if EVERY port in\n * the window is taken (effectively never on dev machines). */\nconst PORT_KINDS = ['wallet', 'http', 'rpc', 'misc'] as const;\n\nexport type PortKind = (typeof PORT_KINDS)[number];\n\n/** Host interface used by the transient bind probe. Callers binding a\n * real server on loopback should use the default. Callers that hand\n * the port to Docker's all-interface publish path should pass\n * `'0.0.0.0'` so the probe asks the same kernel question. */\nexport type PortProbeHost = '127.0.0.1' | '0.0.0.0';\n\n/** Sequential search window per kind. The broker tries `preferredPort`\n * first (if given), then `[start, start + window)` in order. */\nconst PORT_RANGES: Readonly<Record<PortKind, { readonly start: number; readonly window: number }>> =\n\t{\n\t\t// 39200 — chosen to land between the wallet's legacy 39082 default\n\t\t// (still functional via `preferredPort`) and the high ephemeral\n\t\t// range. Stays clear of common dev-server defaults (3000-5173, 8000-\n\t\t// 8080) so a sibling Vite / Next.js process on the host doesn't\n\t\t// accidentally collide on the first probe.\n\t\twallet: { start: 39200, window: 1000 },\n\t\t// 50000 — well clear of postgres (5432), redis (6379), and the\n\t\t// Vite/Next dev-server cluster. Used by any plugin that just wants\n\t\t// \"a free HTTP port\".\n\t\thttp: { start: 50000, window: 1000 },\n\t\t// 51000 — reserved for RPC-shaped servers (graphql, custom RPC).\n\t\t// Kept distinct from `http` so the kind-window discrimination\n\t\t// surfaces in `ss -ltnp` listings during diagnosis.\n\t\trpc: { start: 51000, window: 1000 },\n\t\t// 52000 — escape hatch for plugins that don't fit the typed kinds.\n\t\tmisc: { start: 52000, window: 1000 },\n\t};\n\n/** Input to `allocate`. */\nexport interface AllocateOptions {\n\t/** Optional caller hint. The broker tries this FIRST; if the port is\n\t * already held in-process the call FAILS with\n\t * `reason: 'preferred-busy'` (the caller asked specifically for\n\t * this port and got an obvious collision). If the bind-probe fails\n\t * with EADDRINUSE the broker falls through to the kind-window\n\t * scan — preferred is a HINT for the kernel-collision case, but a\n\t * HARD CHOICE against in-process collision. */\n\treadonly preferredPort?: number;\n\t/** Interface to bind during the kernel probe. Defaults to\n\t * `127.0.0.1`, matching host-loopback servers such as the wallet.\n\t * Docker `-p host:container` publishes on all interfaces, so\n\t * Docker-backed callers pass `0.0.0.0`. */\n\treadonly probeHost?: PortProbeHost;\n\t/** Allocation kind — drives the search window when no preferred is\n\t * supplied (or when preferred is taken at the kernel level). */\n\treadonly kind: PortKind;\n}\n\n/** Successful allocation. `release` is provided in addition to the\n * scope finalizer so tests / orchestrators can drive release\n * explicitly without unwinding the whole scope. Calling `release`\n * twice is a no-op. */\nexport interface AllocatedPort {\n\treadonly port: number;\n\treadonly kind: PortKind;\n\treadonly release: Effect.Effect<void>;\n}\n\n/** Service shape — what plugins yield from Context. */\nexport interface PortBroker {\n\t/**\n\t * Allocate a port for `kind`. The returned port is exclusive within\n\t * THIS process (the broker's `Ref` map) and best-effort-exclusive\n\t * across devstack processes that share a runtime root (port\n\t * reservation file), then verified against the kernel bind table\n\t * on `probeHost`.\n\t *\n\t * Scope-bound release: the broker installs a finalizer on the\n\t * surrounding scope; callers can additionally invoke\n\t * `result.release` explicitly.\n\t *\n\t * Failures (`PortBrokerError`):\n\t * - `preferred-busy` — caller's `preferredPort` is held by another\n\t * in-process allocation on the same stack.\n\t * - `no-free-port` — the kind window was exhausted without a\n\t * probe-pass candidate.\n\t * - `bind-probe-failed` — non-EADDRINUSE error from the OS bind\n\t * probe (EACCES on a privileged port etc.).\n\t * - `reservation-failed` — port reservation file IO failed.\n\t */\n\treadonly allocate: (\n\t\topts: AllocateOptions,\n\t) => Effect.Effect<AllocatedPort, PortBrokerError, Scope.Scope>;\n}\n\n// ----------------------------------------------------------------------\n// Internal state\n// ----------------------------------------------------------------------\n\n/** In-process holder for a port. Carries the kind for diagnostics\n * (e.g. surfaced in `no-free-port` errors). The `owner` is a\n * free-form caller string used for log lines and the future cause\n * walker; today every caller passes the literal `kind`. */\ninterface Holder {\n\treadonly kind: PortKind;\n\treadonly owner: string;\n}\n\ntype State = ReadonlyMap<number, Holder>;\n\nconst PORT_RESERVATION_VERSION = 1 as const;\n\nconst PortReservationDocSchema = Schema.Struct({\n\tversion: Schema.Literal(PORT_RESERVATION_VERSION),\n\tport: Schema.Number,\n\tkind: Schema.Literals(PORT_KINDS),\n\townerId: Schema.String,\n\tholder: RosterHolderSchema,\n});\n\ntype PortReservationDoc = Schema.Schema.Type<typeof PortReservationDocSchema>;\n\ninterface PortReservation {\n\treadonly path: string;\n\treadonly ownerId: string;\n\treadonly release: Effect.Effect<void>;\n}\n\ntype ReservationAttempt =\n\t| { readonly _tag: 'acquired'; readonly reservation: PortReservation }\n\t| { readonly _tag: 'busy' }\n\t| { readonly _tag: 'failed'; readonly detail: string; readonly cause: unknown };\n\ntype ReservationWriteAttempt =\n\t| { readonly _tag: 'written' }\n\t| { readonly _tag: 'race' }\n\t| { readonly _tag: 'exists'; readonly doc: PortReservationDoc | null }\n\t| { readonly _tag: 'failed'; readonly cause: unknown };\n\nconst portReservationPath = (root: string, port: number): string =>\n\tjoin(root, 'port-locks', `${port}.json`);\n\nconst parseReservationDoc = (raw: string): PortReservationDoc | null => {\n\ttry {\n\t\treturn decodeJsonTextSync(PortReservationDocSchema, raw, {\n\t\t\tsource: 'port reservation',\n\t\t\tmkError: (issue) => issue,\n\t\t});\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nconst tryWriteReservationSync = (\n\tpath: string,\n\tdoc: PortReservationDoc,\n): ReservationWriteAttempt => {\n\ttry {\n\t\tmkdirSync(dirname(path), { recursive: true });\n\t\twriteFileSync(path, `${JSON.stringify(doc)}\\n`, { flag: 'wx', mode: 0o600 });\n\t\treturn { _tag: 'written' };\n\t} catch (cause) {\n\t\tconst code = (cause as NodeJS.ErrnoException).code;\n\t\tif (code !== 'EEXIST') return { _tag: 'failed', cause };\n\t\tif (!existsSync(path)) return { _tag: 'race' };\n\t\ttry {\n\t\t\treturn { _tag: 'exists', doc: parseReservationDoc(readFileSync(path, 'utf8')) };\n\t\t} catch {\n\t\t\treturn { _tag: 'exists', doc: null };\n\t\t}\n\t}\n};\n\nconst unlinkReservationIfOwnerSync = (path: string, ownerId: string): void => {\n\ttry {\n\t\tconst doc = parseReservationDoc(readFileSync(path, 'utf8'));\n\t\tif (doc?.ownerId === ownerId) {\n\t\t\tunlinkSync(path);\n\t\t}\n\t} catch {\n\t\t// Missing or unreadable: release is best-effort. A peer never\n\t\t// reclaims unreadable reservations; it simply skips that port.\n\t}\n};\n\nconst reclaimReservationIfOwnerSync = (path: string, ownerId: string): boolean => {\n\ttry {\n\t\tconst doc = parseReservationDoc(readFileSync(path, 'utf8'));\n\t\tif (doc?.ownerId !== ownerId) return false;\n\t\tunlinkSync(path);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n};\n\nconst acquirePortReservation = (\n\troot: string,\n\tport: number,\n\tkind: PortKind,\n): Effect.Effect<ReservationAttempt> =>\n\tEffect.gen(function* () {\n\t\tconst path = portReservationPath(root, port);\n\t\tconst ownerId = `${process.pid}-${randomUUID().slice(0, 8)}`;\n\t\tconst holder: RosterHolder = ownHolder();\n\t\tconst doc: PortReservationDoc = {\n\t\t\tversion: PORT_RESERVATION_VERSION,\n\t\t\tport,\n\t\t\tkind,\n\t\t\townerId,\n\t\t\tholder,\n\t\t};\n\n\t\twhile (true) {\n\t\t\tconst attempt = tryWriteReservationSync(path, doc);\n\t\t\tif (attempt._tag === 'written') {\n\t\t\t\treturn {\n\t\t\t\t\t_tag: 'acquired',\n\t\t\t\t\treservation: {\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\townerId,\n\t\t\t\t\t\trelease: Effect.sync(() => unlinkReservationIfOwnerSync(path, ownerId)),\n\t\t\t\t\t},\n\t\t\t\t} as const;\n\t\t\t}\n\t\t\tif (attempt._tag === 'race') continue;\n\t\t\tif (attempt._tag === 'failed') {\n\t\t\t\treturn {\n\t\t\t\t\t_tag: 'failed',\n\t\t\t\t\tdetail: `port reservation ${path} could not be written`,\n\t\t\t\t\tcause: attempt.cause,\n\t\t\t\t} as const;\n\t\t\t}\n\t\t\tif (attempt.doc === null) return { _tag: 'busy' } as const;\n\n\t\t\tconst liveness = yield* checkHolderLiveness(attempt.doc.holder).pipe(\n\t\t\t\tEffect.catch(() => Effect.succeed('alive' as const)),\n\t\t\t);\n\t\t\tif (liveness === 'alive') return { _tag: 'busy' } as const;\n\t\t\tif (!reclaimReservationIfOwnerSync(path, attempt.doc.ownerId)) {\n\t\t\t\treturn { _tag: 'busy' } as const;\n\t\t\t}\n\t\t}\n\t});\n\n// ----------------------------------------------------------------------\n// Service tag + Layer\n// ----------------------------------------------------------------------\n\nexport class PortBrokerService extends Context.Service<PortBrokerService, PortBroker>()(\n\t'@devstack-rewrite/substrate/PortBroker',\n) {}\n\n/**\n * Layer that materializes one `PortBroker` per stack scope.\n *\n * The broker's state lives in a `Ref` captured by the Layer's\n * `Effect.gen`; closing the Layer's scope drops every allocation.\n * Parallel stacks each get their own broker (Layer-driven), and they\n * coordinate cross-process via runtime-root reservation files plus the\n * kernel's bind table — never via shared in-process state.\n */\nexport const layerPortBroker: Layer.Layer<PortBrokerService, never, RuntimeRoot> = Layer.effect(\n\tPortBrokerService,\n\tEffect.gen(function* () {\n\t\tconst runtimeRoot = yield* RuntimeRoot;\n\t\tconst reservationRoot = runtimeRoot.root;\n\t\tconst state = yield* Ref.make<State>(new Map());\n\n\t\tconst tryReserve = (port: number, kind: PortKind, owner: string): Effect.Effect<boolean> =>\n\t\t\tRef.modify<State, boolean>(state, (current) => {\n\t\t\t\tif (current.has(port)) return [false, current];\n\t\t\t\tconst next = new Map(current);\n\t\t\t\tnext.set(port, { kind, owner });\n\t\t\t\treturn [true, next];\n\t\t\t});\n\n\t\tconst drop = (port: number): Effect.Effect<void> =>\n\t\t\tRef.update(state, (current) => {\n\t\t\t\tif (!current.has(port)) return current;\n\t\t\t\tconst next = new Map(current);\n\t\t\t\tnext.delete(port);\n\t\t\t\treturn next;\n\t\t\t});\n\n\t\tconst allocate: PortBroker['allocate'] = (opts) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst range = PORT_RANGES[opts.kind];\n\t\t\t\tconst owner = opts.kind;\n\t\t\t\tconst probeHost = opts.probeHost ?? '127.0.0.1';\n\n\t\t\t\t// 1. Preferred-port path. The broker honours `preferredPort`\n\t\t\t\t// as a strong hint, but refuses in-process collisions\n\t\t\t\t// (architecture §6: same-stack siblings MUST NOT trample).\n\t\t\t\tif (opts.preferredPort !== undefined) {\n\t\t\t\t\tconst reserved = yield* tryReserve(opts.preferredPort, opts.kind, owner);\n\t\t\t\t\tif (!reserved) {\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\t\t\treason: 'preferred-busy',\n\t\t\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t\t\t`preferred port ${opts.preferredPort} (${opts.kind}) is ` +\n\t\t\t\t\t\t\t\t\t`already held by another allocation in this stack`,\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\t// Kernel-level probe. If a foreign process holds the\n\t\t\t\t\t// port, drop our reservation and FALL THROUGH to the\n\t\t\t\t\t// kind-window scan — the caller's preferred was a HINT\n\t\t\t\t\t// for cross-process collisions.\n\t\t\t\t\tconst reservation = yield* acquirePortReservation(\n\t\t\t\t\t\treservationRoot,\n\t\t\t\t\t\topts.preferredPort,\n\t\t\t\t\t\topts.kind,\n\t\t\t\t\t);\n\t\t\t\t\tif (reservation._tag === 'busy') {\n\t\t\t\t\t\tyield* drop(opts.preferredPort);\n\t\t\t\t\t} else if (reservation._tag === 'failed') {\n\t\t\t\t\t\tyield* drop(opts.preferredPort);\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\t\t\treason: 'reservation-failed',\n\t\t\t\t\t\t\t\tdetail: reservation.detail,\n\t\t\t\t\t\t\t\tcause: reservation.cause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst ok = yield* probePort(opts.preferredPort, probeHost);\n\t\t\t\t\t\tif (ok._tag === 'ok') {\n\t\t\t\t\t\t\treturn yield* finishAllocation(\n\t\t\t\t\t\t\t\topts.preferredPort,\n\t\t\t\t\t\t\t\topts.kind,\n\t\t\t\t\t\t\t\tdrop,\n\t\t\t\t\t\t\t\treservation.reservation,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield* reservation.reservation.release;\n\t\t\t\t\t\tyield* drop(opts.preferredPort);\n\t\t\t\t\t\tif (ok._tag === 'probe-failed') {\n\t\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\t\t\t\treason: 'bind-probe-failed',\n\t\t\t\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t\t\t\t`bind probe on preferred port ${opts.preferredPort} ` +\n\t\t\t\t\t\t\t\t\t\t`(${opts.kind}, host=${probeHost}) failed with non-EADDRINUSE error`,\n\t\t\t\t\t\t\t\t\tcause: ok.cause,\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\t\t\t// `ok._tag === 'in-use'` — fall through to scan.\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// 2. Forward-scan the kind window.\n\t\t\t\tfor (let p = range.start; p < range.start + range.window; p++) {\n\t\t\t\t\tconst reserved = yield* tryReserve(p, opts.kind, owner);\n\t\t\t\t\tif (!reserved) continue;\n\t\t\t\t\tconst reservation = yield* acquirePortReservation(reservationRoot, p, opts.kind);\n\t\t\t\t\tif (reservation._tag === 'busy') {\n\t\t\t\t\t\tyield* drop(p);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (reservation._tag === 'failed') {\n\t\t\t\t\t\tyield* drop(p);\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\t\t\treason: 'reservation-failed',\n\t\t\t\t\t\t\t\tdetail: reservation.detail,\n\t\t\t\t\t\t\t\tcause: reservation.cause,\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 ok = yield* probePort(p, probeHost);\n\t\t\t\t\tif (ok._tag === 'ok') {\n\t\t\t\t\t\treturn yield* finishAllocation(p, opts.kind, drop, reservation.reservation);\n\t\t\t\t\t}\n\t\t\t\t\tyield* reservation.reservation.release;\n\t\t\t\t\tyield* drop(p);\n\t\t\t\t\tif (ok._tag === 'probe-failed') {\n\t\t\t\t\t\t// A privileged port (EACCES) in the middle of the\n\t\t\t\t\t\t// window is unrecoverable for this kind. Surface\n\t\t\t\t\t\t// immediately rather than thrash the whole window.\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\t\t\treason: 'bind-probe-failed',\n\t\t\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t\t\t`bind probe on port ${p} (${opts.kind}, host=${probeHost}) failed ` +\n\t\t\t\t\t\t\t\t\t`with non-EADDRINUSE error`,\n\t\t\t\t\t\t\t\tcause: ok.cause,\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\t// In-use → next candidate.\n\t\t\t\t}\n\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\treason: 'no-free-port',\n\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t`no free port found in [${range.start}, ` +\n\t\t\t\t\t\t\t`${range.start + range.window}) for kind '${opts.kind}'`,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}).pipe(\n\t\t\t\tEffect.withSpan('substrate.portBroker.allocate', {\n\t\t\t\t\tattributes: {\n\t\t\t\t\t\tkind: opts.kind,\n\t\t\t\t\t\tpreferredPort: opts.preferredPort ?? -1,\n\t\t\t\t\t\tprobeHost: opts.probeHost ?? '127.0.0.1',\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\n\t\tconst finishAllocation = (\n\t\t\tport: number,\n\t\t\tkind: PortKind,\n\t\t\tdropFn: (p: number) => Effect.Effect<void>,\n\t\t\treservation: PortReservation,\n\t\t): Effect.Effect<AllocatedPort, never, Scope.Scope> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// Scope finalizer — drops the entry when the surrounding\n\t\t\t\t// scope closes. Uninterruptible so a Ctrl-C double-tap\n\t\t\t\t// doesn't leave the entry dangling in the broker's Ref.\n\t\t\t\tconst release = Effect.all([dropFn(port), reservation.release], {\n\t\t\t\t\tdiscard: true,\n\t\t\t\t});\n\t\t\t\tyield* Effect.addFinalizer(() => release.pipe(Effect.uninterruptible));\n\t\t\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t\t\t'portBroker.port': port,\n\t\t\t\t\t'portBroker.kind': kind,\n\t\t\t\t});\n\t\t\t\tyield* Effect.logDebug(`port-broker allocated ${port} (kind=${kind})`);\n\t\t\t\treturn {\n\t\t\t\t\tport,\n\t\t\t\t\tkind,\n\t\t\t\t\trelease,\n\t\t\t\t} satisfies AllocatedPort;\n\t\t\t});\n\n\t\treturn PortBrokerService.of({ allocate });\n\t}),\n);\n\n// ----------------------------------------------------------------------\n// Kernel-level bind probe\n// ----------------------------------------------------------------------\n\n/** Probe result. `'in-use'` is the recoverable case (try the next\n * port); `'probe-failed'` is the unrecoverable case (caller's\n * problem — surface). */\ntype ProbeResult =\n\t| { readonly _tag: 'ok' }\n\t| { readonly _tag: 'in-use' }\n\t| { readonly _tag: 'probe-failed'; readonly cause: unknown };\n\n/**\n * Bind a transient `net.Server` on `<host>:<port>` and close it.\n * The OS atomically rejects with `EADDRINUSE` if another process\n * already holds the port — that's our cross-process collision check.\n *\n * The default probe is loopback-only because the wallet binds\n * loopback. Docker-backed callers pass `0.0.0.0`; on Docker Desktop,\n * binding a transient Node server on `0.0.0.0` does not always reject\n * ports already held on `127.0.0.1`, even though Docker's publish step\n * will reject them. Wildcard probes therefore check loopback as well.\n *\n * Race window: between the probe-close and the caller's real `listen`,\n * a non-devstack process could still grab the port. Devstack peers are\n * blocked by the reservation file until the allocation scope releases.\n */\nconst probePort = (port: number, host: PortProbeHost): Effect.Effect<ProbeResult> =>\n\thost === '0.0.0.0'\n\t\t? Effect.gen(function* () {\n\t\t\t\tconst loopback = yield* probeSinglePort(port, '127.0.0.1');\n\t\t\t\tif (loopback._tag !== 'ok') return loopback;\n\t\t\t\treturn yield* probeSinglePort(port, host);\n\t\t\t}).pipe(Effect.withSpan('substrate.portBroker.probe', { attributes: { host, port } }))\n\t\t: probeSinglePort(port, host).pipe(\n\t\t\t\tEffect.withSpan('substrate.portBroker.probe', { attributes: { host, port } }),\n\t\t\t);\n\nconst probeSinglePort = (port: number, host: PortProbeHost): Effect.Effect<ProbeResult> =>\n\tEffect.callback<ProbeResult>((resume) => {\n\t\tlet settled = false;\n\t\tlet server: NetServer | null = null;\n\t\tconst settle = (r: ProbeResult): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tresume(Effect.succeed(r));\n\t\t};\n\t\ttry {\n\t\t\tserver = createNetServer();\n\t\t\tserver.unref();\n\t\t\tconst onError = (err: NodeJS.ErrnoException): void => {\n\t\t\t\tconst code = err.code ?? '';\n\t\t\t\tif (code === 'EADDRINUSE' || code === 'EACCES') {\n\t\t\t\t\t// EACCES on these hosts is exotic but treat as in-use\n\t\t\t\t\t// for non-privileged ports (our windows start at\n\t\t\t\t\t// 39200, well above 1024). Surfaces as probe-failed\n\t\t\t\t\t// only if it persists for every port in the window.\n\t\t\t\t\tsettle({ _tag: 'in-use' });\n\t\t\t\t} else {\n\t\t\t\t\tsettle({ _tag: 'probe-failed', cause: err });\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tserver?.close();\n\t\t\t\t} catch {\n\t\t\t\t\t/* defensive */\n\t\t\t\t}\n\t\t\t};\n\t\t\tserver.once('error', onError);\n\t\t\tserver.listen(port, host, () => {\n\t\t\t\t// Verify the OS actually gave us THIS port (defensive —\n\t\t\t\t// asking for a specific port shouldn't ever surprise\n\t\t\t\t// us, but `address()` is the source of truth).\n\t\t\t\tconst addr = server?.address() as AddressInfo | null;\n\t\t\t\tconst ok = addr !== null && addr.port === port;\n\t\t\t\tserver?.close(() => {\n\t\t\t\t\tsettle(ok ? { _tag: 'ok' } : { _tag: 'in-use' });\n\t\t\t\t});\n\t\t\t});\n\t\t} catch (cause) {\n\t\t\tsettle({ _tag: 'probe-failed', cause });\n\t\t\ttry {\n\t\t\t\tserver?.close();\n\t\t\t} catch {\n\t\t\t\t/* defensive */\n\t\t\t}\n\t\t}\n\t});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.mjs","names":["createNetServer"],"sources":["../../../../src/substrate/runtime/port-broker/service.ts"],"sourcesContent":["// Port broker — substrate-level service.\n//\n// Architecture (`notes/redesign/architecture.md` § \"What's collapsed\"):\n// resource broker for host ports. Plugins that need to bind a host\n// HTTP server or publish a container port on the host yield this\n// service and call `allocate({...})`. The broker:\n//\n// 1. Tracks in-process allocations in a `Ref<Map<port, Holder>>` so\n// two sibling plugins in the same stack don't both pick the same\n// port between bind-probe and `server.listen`.\n// 2. Claims a runtime-root scoped port reservation file before the\n// bind probe. This closes the cross-process probe→listen race:\n// two independent `devstack apply` processes sharing a state dir\n// cannot both choose the same candidate while neither real server\n// is listening yet.\n// 3. Verifies kernel-level availability by binding a transient\n// `net.Server` on the candidate port and closing it.\n// The OS's `EADDRINUSE` remains the final collision check for\n// non-devstack processes and stale reservation gaps.\n// 4. Releases on scope close. The broker hands back a `release`\n// Effect AND installs a scope finalizer; the architecture's\n// \"scope-local, never module-level\" invariant is honoured by\n// construction (the Layer is scope-local, the entries die with\n// the scope).\n//\n// Sequential search: callers may pass `preferredPort` (tried first) and\n// an optional `windowHint: { start, size }` (the scan window if\n// preferred is busy or absent). The default window —\n// `DEFAULT_PORT_WINDOW` below — is intentionally wide enough for any\n// realistic in-process devstack; plugins do NOT freelance per-kind\n// window starts. The substrate stays name-blind: there is no `PortKind`\n// literal; plugins are opaque port-holders from the broker's view.\n//\n// Cross-process safety: the reservation file is scoped to the runtime\n// root, not the stack root, because host ports are machine-global. The\n// stack-scoped supervisor lock serializes acquires of the SAME stack;\n// these reservation files serialize host-port choices across DIFFERENT\n// stacks and apps that share a state dir.\n\nimport { randomUUID } from 'node:crypto';\nimport { readFileSync, unlinkSync } from 'node:fs';\nimport {\n\tcreateServer as createNetServer,\n\ttype AddressInfo,\n\ttype Server as NetServer,\n} from 'node:net';\nimport { join } from 'node:path';\n\nimport { Context, Effect, Layer, Ref, Schema, Scope } from 'effect';\n\nimport { RosterHolderSchema, type RosterHolder } from '../../cross-process.ts';\nimport { atomicWriteFileExclusiveSync } from '../atomic-write.ts';\nimport { checkHolderLiveness, ownHolder } from '../cross-process/liveness.ts';\nimport { selfPid } from '../cross-process/self-pid.ts';\nimport { PortBrokerError } from '../errors.ts';\nimport { RuntimeRoot } from '../paths.ts';\nimport { decodeJsonTextSync } from '../runtime-decode.ts';\nimport { versionedDocSchema } from '../../versioned-doc-schema.ts';\n\n// ----------------------------------------------------------------------\n// Public shape\n// ----------------------------------------------------------------------\n\n/** Host interface used by the transient bind probe. Callers binding a\n * real server on loopback should use the default. Callers that hand\n * the port to Docker's all-interface publish path should pass\n * `'0.0.0.0'` so the probe asks the same kernel question. */\nexport type PortProbeHost = '127.0.0.1' | '0.0.0.0';\n\n/** Optional per-call window override. The broker scans\n * `[start, start + size)` in order when `preferredPort` is busy or\n * absent. Plugins with UX-pinned ports (e.g. a dev wallet whose\n * legacy adapters auto-connect to a specific range) pass this so the\n * fall-back range stays predictable; everyone else gets the default. */\nexport interface PortAllocationWindow {\n\treadonly start: number;\n\treadonly size?: number;\n}\n\n/** Default scan window. Sized for \"tens of plugins per stack\" with\n * parallel-stack overhead headroom. 1000 ports is more than enough for\n * any realistic in-process devstack; the forward-scan only ever\n * reaches the end if EVERY port in the window is taken (effectively\n * never on dev machines). Start chosen to land between common dev\n * defaults (3000-5173, 8000-8080) and the ephemeral range so a\n * sibling Vite/Next process doesn't accidentally collide on the\n * first probe. */\nexport const DEFAULT_PORT_WINDOW: Required<PortAllocationWindow> = {\n\tstart: 39200,\n\tsize: 1000,\n};\n\n/** Input to `allocate`. */\nexport interface AllocateOptions {\n\t/** Optional caller hint. The broker tries this FIRST; if the port is\n\t * already held in-process the call FAILS with\n\t * `reason: 'preferred-busy'` (the caller asked specifically for\n\t * this port and got an obvious collision). If the bind-probe fails\n\t * with EADDRINUSE the broker falls through to the window scan —\n\t * preferred is a HINT for the kernel-collision case, but a HARD\n\t * CHOICE against in-process collision. */\n\treadonly preferredPort?: number;\n\t/** Interface to bind during the kernel probe. Defaults to\n\t * `127.0.0.1`, matching host-loopback servers. Docker\n\t * `-p host:container` publishes on all interfaces, so Docker-backed\n\t * callers pass `0.0.0.0`. */\n\treadonly probeHost?: PortProbeHost;\n\t/** Optional per-call window override; defaults to\n\t * `DEFAULT_PORT_WINDOW`. */\n\treadonly windowHint?: PortAllocationWindow;\n\t/** Optional free-form owner label for diagnostics (`no-free-port`\n\t * error detail). The broker treats this as opaque; pass something\n\t * short and human-readable like `'wallet'` or `'sui:rpc'`. */\n\treadonly owner?: string;\n}\n\n/** Successful allocation. `release` is provided in addition to the\n * scope finalizer so tests / orchestrators can drive release\n * explicitly without unwinding the whole scope. Calling `release`\n * twice is a no-op. */\nexport interface AllocatedPort {\n\treadonly port: number;\n\treadonly release: Effect.Effect<void>;\n}\n\n/** Service shape — what plugins yield from Context. */\nexport interface PortBroker {\n\t/**\n\t * Allocate a port. The returned port is exclusive within THIS\n\t * process (the broker's `Ref` map) and best-effort-exclusive across\n\t * devstack processes that share a runtime root (port reservation\n\t * file), then verified against the kernel bind table on `probeHost`.\n\t *\n\t * Scope-bound release: the broker installs a finalizer on the\n\t * surrounding scope; callers can additionally invoke\n\t * `result.release` explicitly.\n\t *\n\t * Failures (`PortBrokerError`):\n\t * - `preferred-busy` — caller's `preferredPort` is held by another\n\t * in-process allocation on the same stack.\n\t * - `no-free-port` — the scan window was exhausted without a\n\t * probe-pass candidate.\n\t * - `bind-probe-failed` — non-EADDRINUSE error from the OS bind\n\t * probe (EACCES on a privileged port etc.).\n\t * - `reservation-failed` — port reservation file IO failed.\n\t */\n\treadonly allocate: (\n\t\topts?: AllocateOptions,\n\t) => Effect.Effect<AllocatedPort, PortBrokerError, Scope.Scope>;\n}\n\n// ----------------------------------------------------------------------\n// Internal state\n// ----------------------------------------------------------------------\n\nconst DEFAULT_OWNER = 'unknown';\n\n/** In-process holder for a port. Carries the owner string for\n * diagnostics (e.g. surfaced in `no-free-port` errors). */\ninterface Holder {\n\treadonly owner: string;\n}\n\ntype State = ReadonlyMap<number, Holder>;\n\n// Reservation-doc shape. NOTE: this changed incompatibly from an\n// earlier `{ version: 1, kind, ... }` to `{ version: 1, owner, ... }`\n// without bumping the version. A version bump alone would NOT recover\n// a SIGKILL'd prior-version leftover (an old body still fails decode\n// under any new schema and would block its port forever). Instead the\n// recovery is behavioural: `acquirePortReservation` treats an EEXIST\n// file it cannot decode as a stale artifact and self-heals it\n// (best-effort unlink + retry), so a leftover incompatible reservation\n// clears itself on the next allocate of that port.\nconst PORT_RESERVATION_VERSION = 1 as const;\n\nconst PortReservationDocSchema = versionedDocSchema(PORT_RESERVATION_VERSION, {\n\tport: Schema.Number,\n\towner: Schema.String,\n\townerId: Schema.String,\n\tholder: RosterHolderSchema,\n});\n\ntype PortReservationDoc = Schema.Schema.Type<typeof PortReservationDocSchema>;\n\ninterface PortReservation {\n\treadonly path: string;\n\treadonly ownerId: string;\n\treadonly release: Effect.Effect<void>;\n}\n\ntype ReservationAttempt =\n\t| { readonly _tag: 'acquired'; readonly reservation: PortReservation }\n\t| { readonly _tag: 'busy' }\n\t| { readonly _tag: 'failed'; readonly detail: string; readonly cause: unknown };\n\ntype ReservationWriteAttempt =\n\t| { readonly _tag: 'written' }\n\t| { readonly _tag: 'race' }\n\t| { readonly _tag: 'exists'; readonly doc: PortReservationDoc | null }\n\t| { readonly _tag: 'failed'; readonly cause: unknown };\n\nconst portReservationPath = (root: string, port: number): string =>\n\tjoin(root, 'port-locks', `${port}.json`);\n\nconst parseReservationDoc = (raw: string): PortReservationDoc | null => {\n\ttry {\n\t\treturn decodeJsonTextSync(PortReservationDocSchema, raw, {\n\t\t\tsource: 'port reservation',\n\t\t\tmkError: (issue) => issue,\n\t\t});\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nconst tryWriteReservationSync = (\n\tpath: string,\n\tdoc: PortReservationDoc,\n): ReservationWriteAttempt => {\n\t// `atomicWriteFileExclusiveSync` writes a tempfile under O_EXCL +\n\t// fsync, then `linkSync`s it onto the final path. POSIX `link(2)`\n\t// is atomic AND fails with `EEXIST` if the target exists — that is\n\t// the exclusive-create primitive `rename` lacks (rename clobbers).\n\t// A sibling pid that wins the race becomes the canonical reservation\n\t// at `path`; we observe it via the EEXIST error and surface the\n\t// winner's doc to the caller (`acquirePortReservation`), which then\n\t// decides busy-vs-stale based on the holder's liveness.\n\ttry {\n\t\tatomicWriteFileExclusiveSync(path, `${JSON.stringify(doc)}\\n`, { mode: 0o600 });\n\t\treturn { _tag: 'written' };\n\t} catch (cause) {\n\t\tconst code = (cause as NodeJS.ErrnoException).code;\n\t\tif (code === 'EEXIST') {\n\t\t\ttry {\n\t\t\t\treturn { _tag: 'exists', doc: parseReservationDoc(readFileSync(path, 'utf8')) };\n\t\t\t} catch {\n\t\t\t\t// The winning reservation file was removed between our\n\t\t\t\t// link-EEXIST and our read (a peer raced release). Retry.\n\t\t\t\treturn { _tag: 'race' };\n\t\t\t}\n\t\t}\n\t\treturn { _tag: 'failed', cause };\n\t}\n};\n\nconst unlinkReservationIfOwnerSync = (path: string, ownerId: string): void => {\n\ttry {\n\t\tconst doc = parseReservationDoc(readFileSync(path, 'utf8'));\n\t\tif (doc?.ownerId === ownerId) {\n\t\t\tunlinkSync(path);\n\t\t}\n\t} catch {\n\t\t// Missing or unreadable: release is best-effort. A peer never\n\t\t// reclaims unreadable reservations; it simply skips that port.\n\t}\n};\n\nconst reclaimReservationIfOwnerSync = (path: string, ownerId: string): boolean => {\n\ttry {\n\t\tconst doc = parseReservationDoc(readFileSync(path, 'utf8'));\n\t\tif (doc?.ownerId !== ownerId) return false;\n\t\tunlinkSync(path);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n};\n\n/** Best-effort removal of an EXISTING reservation file whose body\n * cannot be decoded (a leftover from an incompatible-version\n * supervisor, or a torn write). Such a file carries no readable\n * `ownerId`/`holder`, so neither `reclaimReservationIfOwnerSync` nor\n * liveness can ever clear it; without this it would skip that one\n * port for every future process for the supervisor's lifetime. We\n * treat an undecodable body as a stale artifact and unlink it so the\n * next exclusive-create can win. Returns whether the unlink ran\n * (false if the file vanished first, i.e. a peer already cleared it)\n * so the caller can bound its retry. */\nconst reclaimUndecodableReservationSync = (path: string): boolean => {\n\ttry {\n\t\tunlinkSync(path);\n\t\treturn true;\n\t} catch {\n\t\t// Already gone (peer reclaimed it) or unremovable; either way\n\t\t// the next loop turn re-evaluates via exclusive-create.\n\t\treturn false;\n\t}\n};\n\nconst acquirePortReservation = (\n\troot: string,\n\tport: number,\n\towner: string,\n): Effect.Effect<ReservationAttempt> =>\n\tEffect.gen(function* () {\n\t\tconst path = portReservationPath(root, port);\n\t\tconst ownerId = `${selfPid()}-${randomUUID().slice(0, 8)}`;\n\t\tconst holder: RosterHolder = ownHolder();\n\t\tconst doc: PortReservationDoc = {\n\t\t\tversion: PORT_RESERVATION_VERSION,\n\t\t\tport,\n\t\t\towner,\n\t\t\townerId,\n\t\t\tholder,\n\t\t};\n\n\t\t// Bounds the self-heal: an undecodable EEXIST body is reclaimed at\n\t\t// most once. If we see another undecodable body after that (a peer\n\t\t// keeps rewriting garbage), give up with `busy` rather than spin.\n\t\tlet reclaimedUndecodable = false;\n\t\twhile (true) {\n\t\t\tconst attempt = tryWriteReservationSync(path, doc);\n\t\t\tif (attempt._tag === 'written') {\n\t\t\t\treturn {\n\t\t\t\t\t_tag: 'acquired',\n\t\t\t\t\treservation: {\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\townerId,\n\t\t\t\t\t\trelease: Effect.sync(() => unlinkReservationIfOwnerSync(path, ownerId)),\n\t\t\t\t\t},\n\t\t\t\t} as const;\n\t\t\t}\n\t\t\tif (attempt._tag === 'race') continue;\n\t\t\tif (attempt._tag === 'failed') {\n\t\t\t\treturn {\n\t\t\t\t\t_tag: 'failed',\n\t\t\t\t\tdetail: `port reservation ${path} could not be written`,\n\t\t\t\t\tcause: attempt.cause,\n\t\t\t\t} as const;\n\t\t\t}\n\t\t\tif (attempt.doc === null) {\n\t\t\t\t// EEXIST file we cannot decode (stale incompatible-version\n\t\t\t\t// leftover or torn write). Self-heal once: unlink it and retry\n\t\t\t\t// the exclusive-create so a live port is not lost permanently.\n\t\t\t\tif (!reclaimedUndecodable && reclaimUndecodableReservationSync(path)) {\n\t\t\t\t\treclaimedUndecodable = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\treturn { _tag: 'busy' } as const;\n\t\t\t}\n\n\t\t\tconst liveness = yield* checkHolderLiveness(attempt.doc.holder).pipe(\n\t\t\t\tEffect.catch(() => Effect.succeed('alive' as const)),\n\t\t\t);\n\t\t\tif (liveness === 'alive') return { _tag: 'busy' } as const;\n\t\t\tif (!reclaimReservationIfOwnerSync(path, attempt.doc.ownerId)) {\n\t\t\t\treturn { _tag: 'busy' } as const;\n\t\t\t}\n\t\t}\n\t});\n\n// ----------------------------------------------------------------------\n// Service tag + Layer\n// ----------------------------------------------------------------------\n\nexport class PortBrokerService extends Context.Service<PortBrokerService, PortBroker>()(\n\t'@devstack/substrate/PortBroker',\n) {}\n\n/**\n * Layer that materializes one `PortBroker` per stack scope.\n *\n * The broker's state lives in a `Ref` captured by the Layer's\n * `Effect.gen`; closing the Layer's scope drops every allocation.\n * Parallel stacks each get their own broker (Layer-driven), and they\n * coordinate cross-process via runtime-root reservation files plus the\n * kernel's bind table — never via shared in-process state.\n */\nexport const layerPortBroker: Layer.Layer<PortBrokerService, never, RuntimeRoot> = Layer.effect(\n\tPortBrokerService,\n\tEffect.gen(function* () {\n\t\tconst runtimeRoot = yield* RuntimeRoot;\n\t\tconst reservationRoot = runtimeRoot.root;\n\t\tconst state = yield* Ref.make<State>(new Map());\n\n\t\tconst tryReserve = (port: number, owner: string): Effect.Effect<boolean> =>\n\t\t\tRef.modify<State, boolean>(state, (current) => {\n\t\t\t\tif (current.has(port)) return [false, current];\n\t\t\t\tconst next = new Map(current);\n\t\t\t\tnext.set(port, { owner });\n\t\t\t\treturn [true, next];\n\t\t\t});\n\n\t\tconst drop = (port: number): Effect.Effect<void> =>\n\t\t\tRef.update(state, (current) => {\n\t\t\t\tif (!current.has(port)) return current;\n\t\t\t\tconst next = new Map(current);\n\t\t\t\tnext.delete(port);\n\t\t\t\treturn next;\n\t\t\t});\n\n\t\t/** Outcome of one candidate-port attempt.\n\t\t *\n\t\t * `kept` means `finishAllocation` armed its scope finalizer; the\n\t\t * in-process slot is now owned by that finalizer and we MUST\n\t\t * NOT drop it on this critical region's exit. Any other outcome\n\t\t * (busy / probe-in-use / probe-failed / reservation-failed)\n\t\t * must release the slot before we hand control back to the\n\t\t * caller. */\n\t\ttype SlotOutcome =\n\t\t\t| { readonly _tag: 'kept'; readonly allocated: AllocatedPort }\n\t\t\t| { readonly _tag: 'busy' }\n\t\t\t| {\n\t\t\t\t\treadonly _tag: 'reservation-failed';\n\t\t\t\t\treadonly detail: string;\n\t\t\t\t\treadonly cause: unknown;\n\t\t\t }\n\t\t\t| { readonly _tag: 'probe-failed'; readonly cause: unknown };\n\n\t\t/** Reserve `port` in the in-process Map, run the probe+reservation\n\t\t * chain through to either (a) `finishAllocation` arming its scope\n\t\t * finalizer or (b) a clean release of BOTH the in-process slot\n\t\t * AND the on-disk reservation file.\n\t\t *\n\t\t * Interrupt safety: the critical region is wrapped in\n\t\t * `Effect.acquireUseRelease`, whose `release` step is\n\t\t * uninterruptible while its `use` step is NOT (Effect v4). The\n\t\t * acquired `PortReservation` is created INSIDE `use` (after the\n\t\t * on-disk file is written), so the `use` body stashes it in\n\t\t * `reservationCell`; the uninterruptible `release` reads that cell\n\t\t * and unlinks the reservation file on ANY non-`kept` exit —\n\t\t * interrupt, typed failure, or a non-`kept` success outcome.\n\t\t * Without this, an interrupt landing between the reservation write\n\t\t * (in `acquirePortReservation`) and the finalizer arm (in\n\t\t * `finishAllocation`) left the file on disk while the old release\n\t\t * only dropped the in-process slot — leaking\n\t\t * `port-locks/<port>.json`. Its `holder` is this still-alive\n\t\t * process, so every future `allocate` of that port then saw\n\t\t * `checkHolderLiveness` report `alive`, marking it busy for the\n\t\t * whole supervisor lifetime.\n\t\t *\n\t\t * The only path that intentionally KEEPS both the slot and the\n\t\t * file is the `kept` outcome, where ownership transfers to the\n\t\t * scope finalizer (see `finishAllocation`); `reservationCell` is\n\t\t * cleared there so `release` is a no-op for it.\n\t\t * (`unlinkReservationIfOwnerSync` is owner-id-guarded and\n\t\t * idempotent, so a stray double-release is harmless — clearing\n\t\t * the cell just avoids the redundant stat.)\n\t\t */\n\t\tconst attemptSlot = (\n\t\t\tport: number,\n\t\t\towner: string,\n\t\t\tprobeHost: PortProbeHost,\n\t\t): Effect.Effect<SlotOutcome | null, never, Scope.Scope> => {\n\t\t\t// Visible to BOTH the `use` body (fills it once the reservation\n\t\t\t// file exists) and the uninterruptible `release` callback\n\t\t\t// (unlinks it on any non-`kept` exit). On-disk analogue of the\n\t\t\t// in-process slot.\n\t\t\tlet reservationCell: PortReservation | null = null;\n\t\t\treturn Effect.acquireUseRelease(\n\t\t\t\ttryReserve(port, owner),\n\t\t\t\t(reserved): Effect.Effect<SlotOutcome | null, never, Scope.Scope> => {\n\t\t\t\t\tif (!reserved) return Effect.succeed(null);\n\t\t\t\t\treturn Effect.gen(function* () {\n\t\t\t\t\t\tconst reservation = yield* acquirePortReservation(reservationRoot, port, owner);\n\t\t\t\t\t\tif (reservation._tag === 'busy') return { _tag: 'busy' as const };\n\t\t\t\t\t\tif (reservation._tag === 'failed') {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t_tag: 'reservation-failed' as const,\n\t\t\t\t\t\t\t\tdetail: reservation.detail,\n\t\t\t\t\t\t\t\tcause: reservation.cause,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// File is now on disk. Hand it to the uninterruptible\n\t\t\t\t\t\t// `release` step so an interrupt at the `probePort` /\n\t\t\t\t\t\t// `finishAllocation` boundary cannot leak it.\n\t\t\t\t\t\treservationCell = reservation.reservation;\n\t\t\t\t\t\tconst ok = yield* probePort(port, probeHost);\n\t\t\t\t\t\tif (ok._tag === 'ok') {\n\t\t\t\t\t\t\tconst allocated = yield* finishAllocation(port, owner, drop, reservation.reservation);\n\t\t\t\t\t\t\t// Ownership transferred to the scope finalizer; the\n\t\t\t\t\t\t\t// `release` step must NOT also unlink the file.\n\t\t\t\t\t\t\treservationCell = null;\n\t\t\t\t\t\t\treturn { _tag: 'kept' as const, allocated };\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Probe missed. The uninterruptible `release` step below\n\t\t\t\t\t\t// unlinks the reservation file (via `reservationCell`) and\n\t\t\t\t\t\t// drops the in-process slot, so a single owner cleans up\n\t\t\t\t\t\t// both even if an interrupt preempts us right here.\n\t\t\t\t\t\tif (ok._tag === 'probe-failed') {\n\t\t\t\t\t\t\treturn { _tag: 'probe-failed' as const, cause: ok.cause };\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn { _tag: 'busy' as const };\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\t(reserved, exit) => {\n\t\t\t\t\t// Slot + reservation release (uninterruptible). Drop the\n\t\t\t\t\t// in-process entry AND unlink the on-disk reservation\n\t\t\t\t\t// UNLESS we transferred ownership to a scope finalizer\n\t\t\t\t\t// (the `kept` outcome, which cleared `reservationCell`).\n\t\t\t\t\t// Interrupts, typed failures, and non-`kept` success\n\t\t\t\t\t// outcomes all flow through here, closing both the Map\n\t\t\t\t\t// leak and the port-locks/*.json leak (the latter\n\t\t\t\t\t// previously persisted until the supervisor exited).\n\t\t\t\t\tif (!reserved) return Effect.void;\n\t\t\t\t\tconst kept = exit._tag === 'Success' && exit.value?._tag === 'kept';\n\t\t\t\t\tif (kept) return Effect.void;\n\t\t\t\t\tconst releaseReservation =\n\t\t\t\t\t\treservationCell !== null ? reservationCell.release : Effect.void;\n\t\t\t\t\treturn Effect.all([drop(port), releaseReservation], { discard: true });\n\t\t\t\t},\n\t\t\t);\n\t\t};\n\n\t\tconst allocate: PortBroker['allocate'] = (opts = {}) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst owner = opts.owner ?? DEFAULT_OWNER;\n\t\t\t\tconst probeHost = opts.probeHost ?? '127.0.0.1';\n\t\t\t\tconst windowStart = opts.windowHint?.start ?? DEFAULT_PORT_WINDOW.start;\n\t\t\t\tconst windowSize = opts.windowHint?.size ?? DEFAULT_PORT_WINDOW.size;\n\n\t\t\t\t// 1. Preferred-port path. The broker honours `preferredPort`\n\t\t\t\t// as a strong hint, but refuses in-process collisions\n\t\t\t\t// (architecture §6: same-stack siblings MUST NOT trample).\n\t\t\t\tif (opts.preferredPort !== undefined) {\n\t\t\t\t\tconst outcome = yield* attemptSlot(opts.preferredPort, owner, probeHost);\n\t\t\t\t\tif (outcome === null) {\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\t\t\treason: 'preferred-busy',\n\t\t\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t\t\t`preferred port ${opts.preferredPort} (owner=${owner}) is ` +\n\t\t\t\t\t\t\t\t\t`already held by another allocation in this stack`,\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\tif (outcome._tag === 'kept') return outcome.allocated;\n\t\t\t\t\tif (outcome._tag === 'reservation-failed') {\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\t\t\treason: 'reservation-failed',\n\t\t\t\t\t\t\t\tdetail: outcome.detail,\n\t\t\t\t\t\t\t\tcause: outcome.cause,\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\tif (outcome._tag === 'probe-failed') {\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\t\t\treason: 'bind-probe-failed',\n\t\t\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t\t\t`bind probe on preferred port ${opts.preferredPort} ` +\n\t\t\t\t\t\t\t\t\t`(owner=${owner}, host=${probeHost}) failed with non-EADDRINUSE error`,\n\t\t\t\t\t\t\t\tcause: outcome.cause,\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\t// `busy` (either cross-process reservation held by a\n\t\t\t\t\t// live peer OR kernel-level in-use) — fall through to\n\t\t\t\t\t// the window scan; the caller's preferred was a HINT.\n\t\t\t\t}\n\n\t\t\t\t// 2. Forward-scan the window.\n\t\t\t\tfor (let p = windowStart; p < windowStart + windowSize; p++) {\n\t\t\t\t\tconst outcome = yield* attemptSlot(p, owner, probeHost);\n\t\t\t\t\tif (outcome === null) continue;\n\t\t\t\t\tif (outcome._tag === 'kept') return outcome.allocated;\n\t\t\t\t\tif (outcome._tag === 'reservation-failed') {\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\t\t\treason: 'reservation-failed',\n\t\t\t\t\t\t\t\tdetail: outcome.detail,\n\t\t\t\t\t\t\t\tcause: outcome.cause,\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\tif (outcome._tag === 'probe-failed') {\n\t\t\t\t\t\t// A privileged port (EACCES) in the middle of the\n\t\t\t\t\t\t// window is unrecoverable for this scan. Surface\n\t\t\t\t\t\t// immediately rather than thrash the whole window.\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\t\t\treason: 'bind-probe-failed',\n\t\t\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t\t\t`bind probe on port ${p} (owner=${owner}, host=${probeHost}) failed ` +\n\t\t\t\t\t\t\t\t\t`with non-EADDRINUSE error`,\n\t\t\t\t\t\t\t\tcause: outcome.cause,\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\t// `busy` — next candidate.\n\t\t\t\t}\n\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew PortBrokerError({\n\t\t\t\t\t\treason: 'no-free-port',\n\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\t`no free port found in [${windowStart}, ` +\n\t\t\t\t\t\t\t`${windowStart + windowSize}) for owner '${owner}'`,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}).pipe(\n\t\t\t\tEffect.withSpan('substrate.portBroker.allocate', {\n\t\t\t\t\tattributes: {\n\t\t\t\t\t\towner: opts.owner ?? DEFAULT_OWNER,\n\t\t\t\t\t\tpreferredPort: opts.preferredPort ?? -1,\n\t\t\t\t\t\tprobeHost: opts.probeHost ?? '127.0.0.1',\n\t\t\t\t\t\twindowStart: opts.windowHint?.start ?? DEFAULT_PORT_WINDOW.start,\n\t\t\t\t\t\twindowSize: opts.windowHint?.size ?? DEFAULT_PORT_WINDOW.size,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\n\t\tconst finishAllocation = (\n\t\t\tport: number,\n\t\t\towner: string,\n\t\t\tdropFn: (p: number) => Effect.Effect<void>,\n\t\t\treservation: PortReservation,\n\t\t): Effect.Effect<AllocatedPort, never, Scope.Scope> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// Scope finalizer — drops the entry when the surrounding\n\t\t\t\t// scope closes. Uninterruptible so a Ctrl-C double-tap\n\t\t\t\t// doesn't leave the entry dangling in the broker's Ref.\n\t\t\t\tconst release = Effect.all([dropFn(port), reservation.release], {\n\t\t\t\t\tdiscard: true,\n\t\t\t\t});\n\t\t\t\tyield* Effect.addFinalizer(() => release.pipe(Effect.uninterruptible));\n\t\t\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t\t\t'portBroker.port': port,\n\t\t\t\t\t'portBroker.owner': owner,\n\t\t\t\t});\n\t\t\t\tyield* Effect.logDebug(`port-broker allocated ${port} (owner=${owner})`);\n\t\t\t\treturn {\n\t\t\t\t\tport,\n\t\t\t\t\trelease,\n\t\t\t\t} satisfies AllocatedPort;\n\t\t\t});\n\n\t\treturn PortBrokerService.of({ allocate });\n\t}),\n);\n\n// ----------------------------------------------------------------------\n// Kernel-level bind probe\n// ----------------------------------------------------------------------\n\n/** Probe result. `'in-use'` is the recoverable case (try the next\n * port); `'probe-failed'` is the unrecoverable case (caller's\n * problem — surface). */\ntype ProbeResult =\n\t| { readonly _tag: 'ok' }\n\t| { readonly _tag: 'in-use' }\n\t| { readonly _tag: 'probe-failed'; readonly cause: unknown };\n\n/**\n * Bind a transient `net.Server` on `<host>:<port>` and close it.\n * The OS atomically rejects with `EADDRINUSE` if another process\n * already holds the port — that's our cross-process collision check.\n *\n * The default probe is loopback-only because most servers bind\n * loopback. Docker-backed callers pass `0.0.0.0`; on Docker Desktop,\n * binding a transient Node server on `0.0.0.0` does not always reject\n * ports already held on `127.0.0.1`, even though Docker's publish step\n * will reject them. Wildcard probes therefore check loopback as well.\n *\n * Race window: between the probe-close and the caller's real `listen`,\n * a non-devstack process could still grab the port. Devstack peers are\n * blocked by the reservation file until the allocation scope releases.\n */\nconst probePort = (port: number, host: PortProbeHost): Effect.Effect<ProbeResult> =>\n\thost === '0.0.0.0'\n\t\t? Effect.gen(function* () {\n\t\t\t\tconst loopback = yield* probeSinglePort(port, '127.0.0.1');\n\t\t\t\tif (loopback._tag !== 'ok') return loopback;\n\t\t\t\treturn yield* probeSinglePort(port, host);\n\t\t\t}).pipe(Effect.withSpan('substrate.portBroker.probe', { attributes: { host, port } }))\n\t\t: probeSinglePort(port, host).pipe(\n\t\t\t\tEffect.withSpan('substrate.portBroker.probe', { attributes: { host, port } }),\n\t\t\t);\n\nconst probeSinglePort = (port: number, host: PortProbeHost): Effect.Effect<ProbeResult> =>\n\tEffect.callback<ProbeResult>((resume) => {\n\t\tlet settled = false;\n\t\tlet server: NetServer | null = null;\n\t\tconst settle = (r: ProbeResult): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tresume(Effect.succeed(r));\n\t\t};\n\t\ttry {\n\t\t\tserver = createNetServer();\n\t\t\tserver.unref();\n\t\t\tconst onError = (err: NodeJS.ErrnoException): void => {\n\t\t\t\tconst code = err.code ?? '';\n\t\t\t\tif (code === 'EADDRINUSE' || code === 'EACCES') {\n\t\t\t\t\t// EACCES on these hosts is exotic but treat as in-use\n\t\t\t\t\t// for non-privileged ports (our default window starts\n\t\t\t\t\t// at 39200, well above 1024). Surfaces as probe-failed\n\t\t\t\t\t// only if it persists for every port in the window.\n\t\t\t\t\tsettle({ _tag: 'in-use' });\n\t\t\t\t} else {\n\t\t\t\t\tsettle({ _tag: 'probe-failed', cause: err });\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tserver?.close();\n\t\t\t\t} catch {\n\t\t\t\t\t/* defensive */\n\t\t\t\t}\n\t\t\t};\n\t\t\tserver.once('error', onError);\n\t\t\tserver.listen(port, host, () => {\n\t\t\t\t// Verify the OS actually gave us THIS port (defensive —\n\t\t\t\t// asking for a specific port shouldn't ever surprise\n\t\t\t\t// us, but `address()` is the source of truth).\n\t\t\t\tconst addr = server?.address() as AddressInfo | null;\n\t\t\t\tconst ok = addr !== null && addr.port === port;\n\t\t\t\tserver?.close(() => {\n\t\t\t\t\tsettle(ok ? { _tag: 'ok' } : { _tag: 'in-use' });\n\t\t\t\t});\n\t\t\t});\n\t\t} catch (cause) {\n\t\t\tsettle({ _tag: 'probe-failed', cause });\n\t\t\ttry {\n\t\t\t\tserver?.close();\n\t\t\t} catch {\n\t\t\t\t/* defensive */\n\t\t\t}\n\t\t}\n\t\t// Interrupt mid-`listen()` must close the transient server so the\n\t\t// probed port isn't held open by an orphaned handle.\n\t\treturn Effect.sync(() => {\n\t\t\ttry {\n\t\t\t\tserver?.close();\n\t\t\t} catch {\n\t\t\t\t/* defensive */\n\t\t\t}\n\t\t});\n\t});\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Context, Data, Effect, Layer, Ref, Scope } from "effect";
|
|
2
2
|
//#region src/substrate/runtime/post-acquire-tasks.ts
|
|
3
3
|
var PostAcquireTaskFailed = class extends Data.TaggedError("PostAcquireTaskFailed") {};
|
|
4
|
-
var PostAcquireTasksService = class extends Context.Service()("@devstack
|
|
4
|
+
var PostAcquireTasksService = class extends Context.Service()("@devstack/substrate/PostAcquireTasks") {};
|
|
5
5
|
const layerPostAcquireTasks = Layer.effect(PostAcquireTasksService, Effect.gen(function* () {
|
|
6
6
|
const tasksRef = yield* Ref.make([]);
|
|
7
7
|
const register = (task) => Effect.gen(function* () {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-acquire-tasks.mjs","names":[],"sources":["../../../src/substrate/runtime/post-acquire-tasks.ts"],"sourcesContent":["import { Context, Data, Effect, Layer, Ref, Scope } from 'effect';\n\nimport type { PluginKey } from '../brand.ts';\n\nexport interface PostAcquireTask {\n\treadonly pluginKey: PluginKey;\n\treadonly label: string;\n\treadonly run: Effect.Effect<void, unknown, Scope.Scope>;\n}\n\nexport class PostAcquireTaskFailed extends Data.TaggedError('PostAcquireTaskFailed')<{\n\treadonly pluginKey: PluginKey;\n\treadonly label: string;\n\treadonly cause: unknown;\n}> {}\n\nexport interface PostAcquireTasksShape {\n\treadonly register: (task: PostAcquireTask) => Effect.Effect<void, never, Scope.Scope>;\n\treadonly runAll: Effect.Effect<void, PostAcquireTaskFailed, never>;\n}\n\nexport class PostAcquireTasksService extends Context.Service<\n\tPostAcquireTasksService,\n\tPostAcquireTasksShape\n>()('@devstack
|
|
1
|
+
{"version":3,"file":"post-acquire-tasks.mjs","names":[],"sources":["../../../src/substrate/runtime/post-acquire-tasks.ts"],"sourcesContent":["import { Context, Data, Effect, Layer, Ref, Scope } from 'effect';\n\nimport type { PluginKey } from '../brand.ts';\n\nexport interface PostAcquireTask {\n\treadonly pluginKey: PluginKey;\n\treadonly label: string;\n\treadonly run: Effect.Effect<void, unknown, Scope.Scope>;\n}\n\nexport class PostAcquireTaskFailed extends Data.TaggedError('PostAcquireTaskFailed')<{\n\treadonly pluginKey: PluginKey;\n\treadonly label: string;\n\treadonly cause: unknown;\n}> {}\n\nexport interface PostAcquireTasksShape {\n\treadonly register: (task: PostAcquireTask) => Effect.Effect<void, never, Scope.Scope>;\n\treadonly runAll: Effect.Effect<void, PostAcquireTaskFailed, never>;\n}\n\nexport class PostAcquireTasksService extends Context.Service<\n\tPostAcquireTasksService,\n\tPostAcquireTasksShape\n>()('@devstack/substrate/PostAcquireTasks') {}\n\ninterface RegisteredPostAcquireTask extends PostAcquireTask {\n\treadonly id: symbol;\n\treadonly scope: Scope.Scope;\n}\n\nexport const layerPostAcquireTasks: Layer.Layer<PostAcquireTasksService> = Layer.effect(\n\tPostAcquireTasksService,\n\tEffect.gen(function* () {\n\t\tconst tasksRef = yield* Ref.make<ReadonlyArray<RegisteredPostAcquireTask>>([]);\n\n\t\tconst register = (task: PostAcquireTask): Effect.Effect<void, never, Scope.Scope> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst scope = yield* Effect.scope;\n\t\t\t\tconst id = Symbol(task.label);\n\t\t\t\tconst registered: RegisteredPostAcquireTask = { ...task, id, scope };\n\t\t\t\tyield* Ref.update(tasksRef, (tasks) => [...tasks, registered]);\n\t\t\t\tyield* Effect.addFinalizer(() =>\n\t\t\t\t\tRef.update(tasksRef, (tasks) => tasks.filter((candidate) => candidate.id !== id)),\n\t\t\t\t);\n\t\t\t});\n\n\t\tconst runAll: Effect.Effect<void, PostAcquireTaskFailed, never> = Effect.gen(function* () {\n\t\t\tconst tasks = yield* Ref.get(tasksRef);\n\t\t\tfor (const task of tasks) {\n\t\t\t\tyield* Scope.provide(task.run, task.scope).pipe(\n\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t(cause) =>\n\t\t\t\t\t\t\tnew PostAcquireTaskFailed({\n\t\t\t\t\t\t\t\tpluginKey: task.pluginKey,\n\t\t\t\t\t\t\t\tlabel: task.label,\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});\n\n\t\treturn PostAcquireTasksService.of({ register, runAll });\n\t}),\n);\n"],"mappings":";;AAUA,IAAa,wBAAb,cAA2C,KAAK,YAAY,wBAAwB,CAIjF;AAOH,IAAa,0BAAb,cAA6C,QAAQ,SAGlD,CAAC,uCAAuC,CAAC;AAO5C,MAAa,wBAA8D,MAAM,OAChF,yBACA,OAAO,IAAI,aAAa;CACvB,MAAM,WAAW,OAAO,IAAI,KAA+C,EAAE,CAAC;CAE9E,MAAM,YAAY,SACjB,OAAO,IAAI,aAAa;EACvB,MAAM,QAAQ,OAAO,OAAO;EAC5B,MAAM,KAAK,OAAO,KAAK,MAAM;EAC7B,MAAM,aAAwC;GAAE,GAAG;GAAM;GAAI;GAAO;AACpE,SAAO,IAAI,OAAO,WAAW,UAAU,CAAC,GAAG,OAAO,WAAW,CAAC;AAC9D,SAAO,OAAO,mBACb,IAAI,OAAO,WAAW,UAAU,MAAM,QAAQ,cAAc,UAAU,OAAO,GAAG,CAAC,CACjF;GACA;CAEH,MAAM,SAA4D,OAAO,IAAI,aAAa;EACzF,MAAM,QAAQ,OAAO,IAAI,IAAI,SAAS;AACtC,OAAK,MAAM,QAAQ,MAClB,QAAO,MAAM,QAAQ,KAAK,KAAK,KAAK,MAAM,CAAC,KAC1C,OAAO,UACL,UACA,IAAI,sBAAsB;GACzB,WAAW,KAAK;GAChB,OAAO,KAAK;GACZ;GACA,CAAC,CACH,CACD;GAED;AAEF,QAAO,wBAAwB,GAAG;EAAE;EAAU;EAAQ,CAAC;EACtD,CACF"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import * as _$effect_Types0 from "effect/Types";
|
|
1
|
+
import { Effect, Types } from "effect";
|
|
3
2
|
import * as _$effect_Cause0 from "effect/Cause";
|
|
4
3
|
|
|
5
4
|
//#region src/substrate/runtime/probes.d.ts
|
|
6
5
|
declare namespace probes_d_exports {
|
|
7
|
-
export { ExitCodeProbeResult, ProbeAttemptResult, ProbeAttemptTimeoutError, ProbeOptions, ProbeTimeoutError, exitCodeProbeResult, waitForProbe };
|
|
6
|
+
export { ExitCodeProbeResult, ProbeAttemptResult, ProbeAttemptTimeoutError, ProbeOptions, ProbeTimeoutError, exitCodeProbeResult, probeManyLenient, waitForProbe };
|
|
8
7
|
}
|
|
9
8
|
type ProbeAttemptResult = void | boolean | {
|
|
10
9
|
readonly ready: boolean;
|
|
@@ -18,7 +17,7 @@ interface ProbeOptions<E = unknown, R = never> {
|
|
|
18
17
|
readonly probe: () => Effect.Effect<ProbeAttemptResult, E, R>;
|
|
19
18
|
readonly isRetryableError?: (error: E) => boolean;
|
|
20
19
|
}
|
|
21
|
-
declare const ProbeAttemptTimeoutError_base: new <A extends Record<string, any> = {}>(args:
|
|
20
|
+
declare const ProbeAttemptTimeoutError_base: new <A extends Record<string, any> = {}>(args: Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => _$effect_Cause0.YieldableError & {
|
|
22
21
|
readonly _tag: "ProbeAttemptTimeoutError";
|
|
23
22
|
} & Readonly<A>;
|
|
24
23
|
declare class ProbeAttemptTimeoutError extends ProbeAttemptTimeoutError_base<{
|
|
@@ -26,7 +25,7 @@ declare class ProbeAttemptTimeoutError extends ProbeAttemptTimeoutError_base<{
|
|
|
26
25
|
readonly attemptTimeoutMs: number;
|
|
27
26
|
readonly message: string;
|
|
28
27
|
}> {}
|
|
29
|
-
declare const ProbeTimeoutError_base: new <A extends Record<string, any> = {}>(args:
|
|
28
|
+
declare const ProbeTimeoutError_base: new <A extends Record<string, any> = {}>(args: Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => _$effect_Cause0.YieldableError & {
|
|
30
29
|
readonly _tag: "ProbeTimeoutError";
|
|
31
30
|
} & Readonly<A>;
|
|
32
31
|
declare class ProbeTimeoutError extends ProbeTimeoutError_base<{
|
|
@@ -46,6 +45,9 @@ interface ExitCodeProbeResult {
|
|
|
46
45
|
readonly stderr?: string;
|
|
47
46
|
}
|
|
48
47
|
declare const exitCodeProbeResult: (result: ExitCodeProbeResult) => ProbeAttemptResult;
|
|
48
|
+
declare const probeManyLenient: <T, E, R>(probes: ReadonlyArray<Effect.Effect<T | null, E, R>>, options?: {
|
|
49
|
+
readonly concurrency?: Types.Concurrency;
|
|
50
|
+
}) => Effect.Effect<ReadonlyArray<T | null>, E, R>;
|
|
49
51
|
//#endregion
|
|
50
52
|
export { probes_d_exports };
|
|
51
53
|
//# sourceMappingURL=probes.d.mts.map
|