@mysten-incubation/devstack 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dashboard-ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dashboard-ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dashboard-ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dashboard-ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
- package/dashboard-ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dashboard-ui/assets/index-Bmi1UtAg.js +1356 -0
- package/dashboard-ui/assets/index-D5EShVt4.js +3 -0
- package/dashboard-ui/assets/index-Deml9drg.css +1 -0
- package/dashboard-ui/index.html +13 -0
- package/dist/api/define-capabilities.d.mts +14 -3
- package/dist/api/define-capabilities.mjs +18 -1
- package/dist/api/define-capabilities.mjs.map +1 -1
- package/dist/api/define-devstack.d.mts +1 -1
- package/dist/api/define-devstack.mjs +6 -26
- package/dist/api/define-devstack.mjs.map +1 -1
- package/dist/api/define-plugin.d.mts +1 -0
- package/dist/api/define-plugin.mjs +2 -0
- package/dist/api/inference-network.d.mts +132 -0
- package/dist/api/inference-network.mjs +105 -16
- package/dist/api/inference-network.mjs.map +1 -1
- package/dist/api/plugin-errors.d.mts +7 -0
- package/dist/api/run-stack.d.mts +20 -9
- package/dist/api/run-stack.mjs +56 -29
- package/dist/api/run-stack.mjs.map +1 -1
- package/dist/build-integrations/playwright/config.d.mts +6 -4
- package/dist/build-integrations/playwright/config.mjs +5 -3
- package/dist/build-integrations/playwright/config.mjs.map +1 -1
- package/dist/build-integrations/playwright/errors.d.mts +1 -1
- package/dist/build-integrations/playwright/global-setup.d.mts +43 -17
- package/dist/build-integrations/playwright/global-setup.mjs +250 -13
- package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
- package/dist/build-integrations/playwright/index.d.mts +5 -3
- package/dist/build-integrations/playwright/index.mjs +4 -2
- package/dist/build-integrations/playwright/stack-context.d.mts +0 -1
- package/dist/build-integrations/playwright/stack-context.mjs +24 -63
- package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
- package/dist/build-integrations/playwright/wallet-context.d.mts +7 -10
- package/dist/build-integrations/playwright/wallet-context.mjs +8 -13
- package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/cold-start-url.d.mts +5 -0
- package/dist/build-integrations/runtime/cold-start-url.mjs +4 -3
- package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
- package/dist/build-integrations/runtime/conventional-routes.d.mts +51 -0
- package/dist/build-integrations/runtime/conventional-routes.mjs +92 -0
- package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -0
- package/dist/build-integrations/runtime/discover.d.mts +38 -30
- package/dist/build-integrations/runtime/discover.mjs +73 -70
- package/dist/build-integrations/runtime/discover.mjs.map +1 -1
- package/dist/build-integrations/runtime/endpoint-registry.d.mts +3 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs +3 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
- package/dist/build-integrations/runtime/index.d.mts +7 -2
- package/dist/build-integrations/runtime/index.mjs +9 -2
- package/dist/build-integrations/runtime/manifest-types.d.mts +2 -0
- package/dist/build-integrations/runtime/manifest-types.mjs +3 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.d.mts +29 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs +10 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs.map +1 -0
- package/dist/build-integrations/runtime/read-stack-context.d.mts +1 -1
- package/dist/build-integrations/runtime/read-stack-context.mjs +10 -4
- package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +48 -0
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs +42 -0
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -0
- package/dist/build-integrations/runtime/stack-context.d.mts +1 -1
- package/dist/build-integrations/runtime/wallet-paths.mjs +2 -0
- package/dist/build-integrations/vite/index.d.mts +51 -0
- package/dist/build-integrations/vite/index.mjs +73 -0
- package/dist/build-integrations/vite/index.mjs.map +1 -0
- package/dist/build-integrations/vitest/env.d.mts +4 -3
- package/dist/build-integrations/vitest/env.mjs +6 -4
- package/dist/build-integrations/vitest/env.mjs.map +1 -1
- package/dist/build-integrations/vitest/errors.d.mts +25 -4
- package/dist/build-integrations/vitest/errors.mjs +15 -2
- package/dist/build-integrations/vitest/errors.mjs.map +1 -1
- package/dist/build-integrations/vitest/index.d.mts +2 -2
- package/dist/build-integrations/vitest/index.mjs +3 -3
- package/dist/build-integrations/vitest/setup.d.mts +4 -2
- package/dist/build-integrations/vitest/setup.mjs +2 -2
- package/dist/build-integrations/vitest/setup.mjs.map +1 -1
- package/dist/build-integrations/vitest/stack-context.d.mts +0 -1
- package/dist/build-integrations/vitest/stack-context.mjs +33 -13
- package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
- package/dist/cli/bail.mjs +23 -0
- package/dist/cli/bail.mjs.map +1 -0
- package/dist/{surfaces/cli/commands → cli}/doctor-probes.mjs +26 -16
- package/dist/cli/doctor-probes.mjs.map +1 -0
- package/dist/cli/main.d.mts +15 -1
- package/dist/cli/main.mjs +166 -482
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/prune-direct.mjs +31 -201
- package/dist/cli/prune-direct.mjs.map +1 -1
- package/dist/cli/snapshot-reader.mjs +14 -11
- package/dist/cli/snapshot-reader.mjs.map +1 -1
- package/dist/cli/up-lifecycle.mjs +4 -1
- package/dist/cli/up-lifecycle.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +95 -0
- package/dist/cli/wirings/apply.mjs.map +1 -0
- package/dist/cli/wirings/build-verb-layers.mjs +42 -0
- package/dist/cli/wirings/build-verb-layers.mjs.map +1 -0
- package/dist/cli/wirings/config-loader.mjs +69 -0
- package/dist/cli/wirings/config-loader.mjs.map +1 -0
- package/dist/cli/wirings/engine-command.mjs +30 -0
- package/dist/cli/wirings/engine-command.mjs.map +1 -0
- package/dist/cli/wirings/identity.mjs +72 -0
- package/dist/cli/wirings/identity.mjs.map +1 -0
- package/dist/cli/wirings/provide-file-system.mjs +7 -0
- package/dist/cli/wirings/provide-file-system.mjs.map +1 -0
- package/dist/cli/wirings/snapshot.mjs +177 -0
- package/dist/cli/wirings/snapshot.mjs.map +1 -0
- package/dist/cli/wirings/up.mjs +287 -0
- package/dist/cli/wirings/up.mjs.map +1 -0
- package/dist/cli/wirings/wipe.mjs +40 -0
- package/dist/cli/wirings/wipe.mjs.map +1 -0
- package/dist/contracts/capability-decl.d.mts +0 -2
- package/dist/contracts/codegenable.d.mts +43 -0
- package/dist/contracts/container-runtime.d.mts +26 -1
- package/dist/contracts/faucet-strategy.d.mts +25 -0
- package/dist/contracts/funding-strategy.d.mts +42 -0
- package/dist/contracts/plugin-expander.mjs +60 -0
- package/dist/contracts/plugin-expander.mjs.map +1 -0
- package/dist/contracts/projection.d.mts +5 -3
- package/dist/contracts/renderer.d.mts +23 -0
- package/dist/contracts/routable.d.mts +14 -1
- package/dist/contracts/snapshotable.d.mts +25 -3
- package/dist/contracts/wallet-protocol.d.mts +52 -0
- package/dist/contracts/wallet-protocol.mjs +52 -0
- package/dist/contracts/wallet-protocol.mjs.map +1 -0
- package/dist/index.d.mts +26 -25
- package/dist/index.mjs +20 -13
- package/dist/{runtime → orchestrators}/built-in-plugin-layers.mjs +10 -5
- package/dist/orchestrators/built-in-plugin-layers.mjs.map +1 -0
- package/dist/orchestrators/codegen/bindings.d.mts +1 -2
- package/dist/orchestrators/codegen/bindings.mjs +52 -186
- package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
- package/dist/orchestrators/codegen/emit.mjs +19 -1
- package/dist/orchestrators/codegen/emit.mjs.map +1 -1
- package/dist/orchestrators/codegen/errors.mjs +8 -3
- package/dist/orchestrators/codegen/errors.mjs.map +1 -1
- package/dist/orchestrators/codegen/format.mjs +47 -39
- package/dist/orchestrators/codegen/format.mjs.map +1 -1
- package/dist/orchestrators/codegen/gitignore.mjs +3 -1
- package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
- package/dist/orchestrators/codegen/output-location.mjs +40 -0
- package/dist/orchestrators/codegen/output-location.mjs.map +1 -0
- package/dist/orchestrators/codegen/paths.d.mts +1 -1
- package/dist/orchestrators/codegen/paths.mjs +40 -14
- package/dist/orchestrators/codegen/paths.mjs.map +1 -1
- package/dist/orchestrators/codegen/permissions.mjs +3 -1
- package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
- package/dist/orchestrators/codegen/service.mjs +157 -105
- package/dist/orchestrators/codegen/service.mjs.map +1 -1
- package/dist/orchestrators/lifecycle-prune/errors.mjs +31 -0
- package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -0
- package/dist/orchestrators/lifecycle-prune/index.mjs +350 -0
- package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -0
- package/dist/orchestrators/router/cleanup.mjs +26 -27
- package/dist/orchestrators/router/cleanup.mjs.map +1 -1
- package/dist/orchestrators/router/entrypoints.mjs +2 -2
- package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
- package/dist/orchestrators/router/file-provider.mjs +14 -5
- package/dist/orchestrators/router/file-provider.mjs.map +1 -1
- package/dist/orchestrators/router/hostname.mjs +19 -55
- package/dist/orchestrators/router/hostname.mjs.map +1 -1
- package/dist/orchestrators/router/index.mjs +11 -0
- package/dist/orchestrators/router/profile.mjs +2 -2
- package/dist/orchestrators/router/sentinels.mjs +26 -0
- package/dist/orchestrators/router/sentinels.mjs.map +1 -0
- package/dist/orchestrators/router/service.mjs +40 -37
- package/dist/orchestrators/router/service.mjs.map +1 -1
- package/dist/orchestrators/router/traefik-container.mjs +27 -16
- package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
- package/dist/{substrate/runtime → orchestrators}/run.mjs +26 -21
- package/dist/orchestrators/run.mjs.map +1 -0
- package/dist/orchestrators/runtime-composition.d.mts +1 -1
- package/dist/orchestrators/runtime-composition.mjs +123 -39
- package/dist/orchestrators/runtime-composition.mjs.map +1 -1
- package/dist/orchestrators/snapshot/capture-command.d.mts +1 -0
- package/dist/orchestrators/snapshot/capture-command.mjs +25 -0
- package/dist/orchestrators/snapshot/capture-command.mjs.map +1 -0
- package/dist/orchestrators/snapshot/capture.mjs +42 -28
- package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
- package/dist/orchestrators/snapshot/descriptor.mjs +35 -10
- package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
- package/dist/orchestrators/snapshot/identity-guard.mjs +1 -1
- package/dist/orchestrators/snapshot/index.mjs +13 -0
- package/dist/orchestrators/snapshot/integrity.d.mts +1 -1
- package/dist/orchestrators/snapshot/integrity.mjs +73 -17
- package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
- package/dist/orchestrators/snapshot/pending-marker.d.mts +1 -0
- package/dist/orchestrators/snapshot/pending-marker.mjs +99 -0
- package/dist/orchestrators/snapshot/pending-marker.mjs.map +1 -0
- package/dist/orchestrators/snapshot/phase-error.mjs +23 -0
- package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -0
- package/dist/orchestrators/snapshot/prune.mjs +20 -55
- package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
- package/dist/orchestrators/snapshot/recover-pending.d.mts +1 -0
- package/dist/orchestrators/snapshot/recover-pending.mjs +223 -0
- package/dist/orchestrators/snapshot/recover-pending.mjs.map +1 -0
- package/dist/orchestrators/snapshot/restore.mjs +118 -84
- package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
- package/dist/orchestrators/snapshot/service.mjs +53 -56
- package/dist/orchestrators/snapshot/service.mjs.map +1 -1
- package/dist/orchestrators/snapshot/state-document.d.mts +1 -0
- package/dist/orchestrators/snapshot/state-document.mjs +47 -7
- package/dist/orchestrators/snapshot/state-document.mjs.map +1 -1
- package/dist/orchestrators/snapshot/wipe.mjs +67 -10
- package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
- package/dist/plugins/account/codegen.mjs +9 -8
- package/dist/plugins/account/codegen.mjs.map +1 -1
- package/dist/plugins/account/errors.d.mts +33 -6
- package/dist/plugins/account/errors.mjs.map +1 -1
- package/dist/plugins/account/funding.d.mts +29 -19
- package/dist/plugins/account/funding.mjs +84 -66
- package/dist/plugins/account/funding.mjs.map +1 -1
- package/dist/plugins/account/index.d.mts +2 -4
- package/dist/plugins/account/index.mjs +64 -31
- package/dist/plugins/account/index.mjs.map +1 -1
- package/dist/plugins/account/keypair.mjs +6 -9
- package/dist/plugins/account/keypair.mjs.map +1 -1
- package/dist/plugins/account/lease.mjs +1 -0
- package/dist/plugins/account/lease.mjs.map +1 -1
- package/dist/plugins/account/registry.d.mts +14 -1
- package/dist/plugins/account/registry.mjs +18 -18
- package/dist/plugins/account/registry.mjs.map +1 -1
- package/dist/plugins/account/service.d.mts +23 -4
- package/dist/plugins/account/service.mjs +83 -45
- package/dist/plugins/account/service.mjs.map +1 -1
- package/dist/plugins/account/snapshot.mjs +2 -5
- package/dist/plugins/account/snapshot.mjs.map +1 -1
- package/dist/plugins/account/spans.mjs +16 -0
- package/dist/plugins/account/spans.mjs.map +1 -0
- package/dist/plugins/account/variants/keystore.mjs +6 -8
- package/dist/plugins/account/variants/keystore.mjs.map +1 -1
- package/dist/plugins/action/build-context.d.mts +3 -2
- package/dist/plugins/action/errors.d.mts +16 -12
- package/dist/plugins/action/errors.mjs.map +1 -1
- package/dist/plugins/action/execute.mjs +65 -103
- package/dist/plugins/action/execute.mjs.map +1 -1
- package/dist/plugins/action/index.d.mts +0 -2
- package/dist/plugins/action/index.mjs +17 -13
- package/dist/plugins/action/index.mjs.map +1 -1
- package/dist/plugins/action/service.mjs +38 -35
- package/dist/plugins/action/service.mjs.map +1 -1
- package/dist/plugins/action/spans.mjs +11 -0
- package/dist/plugins/action/spans.mjs.map +1 -0
- package/dist/plugins/coin/codegen.mjs +9 -8
- package/dist/plugins/coin/codegen.mjs.map +1 -1
- package/dist/plugins/coin/discovery.mjs +8 -4
- package/dist/plugins/coin/discovery.mjs.map +1 -1
- package/dist/plugins/coin/index.d.mts +11 -29
- package/dist/plugins/coin/index.mjs +56 -27
- package/dist/plugins/coin/index.mjs.map +1 -1
- package/dist/plugins/coin/metadata.mjs +8 -15
- package/dist/plugins/coin/metadata.mjs.map +1 -1
- package/dist/plugins/coin/mint.d.mts +30 -11
- package/dist/plugins/coin/mint.mjs +78 -74
- package/dist/plugins/coin/mint.mjs.map +1 -1
- package/dist/plugins/coin/registry.mjs +4 -7
- package/dist/plugins/coin/registry.mjs.map +1 -1
- package/dist/plugins/coin/service.d.mts +39 -1
- package/dist/plugins/coin/service.mjs +11 -4
- package/dist/plugins/coin/service.mjs.map +1 -1
- package/dist/plugins/coin/spans.mjs +20 -0
- package/dist/plugins/coin/spans.mjs.map +1 -0
- package/dist/plugins/dashboard/domain.mjs +402 -0
- package/dist/plugins/dashboard/domain.mjs.map +1 -0
- package/dist/plugins/dashboard/index.d.mts +29 -0
- package/dist/plugins/dashboard/index.mjs +91 -0
- package/dist/plugins/dashboard/index.mjs.map +1 -0
- package/dist/plugins/dashboard/origin-policy.mjs +36 -0
- package/dist/plugins/dashboard/origin-policy.mjs.map +1 -0
- package/dist/plugins/dashboard/routable.mjs +27 -0
- package/dist/plugins/dashboard/routable.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/builder.mjs +21 -0
- package/dist/plugins/dashboard/schema/builder.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/enums.mjs +72 -0
- package/dist/plugins/dashboard/schema/enums.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/root.mjs +351 -0
- package/dist/plugins/dashboard/schema/root.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/types.mjs +508 -0
- package/dist/plugins/dashboard/schema/types.mjs.map +1 -0
- package/dist/plugins/dashboard/schema.mjs +11 -0
- package/dist/plugins/dashboard/schema.mjs.map +1 -0
- package/dist/plugins/dashboard/server.mjs +166 -0
- package/dist/plugins/dashboard/server.mjs.map +1 -0
- package/dist/plugins/deepbook/deploy.mjs +53 -115
- package/dist/plugins/deepbook/deploy.mjs.map +1 -1
- package/dist/plugins/deepbook/errors.mjs +1 -0
- package/dist/plugins/deepbook/errors.mjs.map +1 -1
- package/dist/plugins/deepbook/faucet-strategy.mjs +47 -34
- package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/deepbook/hash.mjs +9 -0
- package/dist/plugins/deepbook/hash.mjs.map +1 -0
- package/dist/plugins/deepbook/index.d.mts +1 -10
- package/dist/plugins/deepbook/index.mjs +25 -22
- package/dist/plugins/deepbook/index.mjs.map +1 -1
- package/dist/plugins/deepbook/pyth/index.mjs +26 -55
- package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
- package/dist/plugins/deepbook/snapshot.mjs +17 -29
- package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
- package/dist/plugins/deepbook/spans.mjs +18 -0
- package/dist/plugins/deepbook/spans.mjs.map +1 -0
- package/dist/plugins/deepbook/type-strings.mjs +65 -0
- package/dist/plugins/deepbook/type-strings.mjs.map +1 -0
- package/dist/plugins/deepbook/types.d.mts +2 -3
- package/dist/plugins/deepbook/types.mjs.map +1 -1
- package/dist/plugins/faucet/dispatcher.d.mts +21 -2
- package/dist/plugins/faucet/dispatcher.mjs +1 -2
- package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
- package/dist/plugins/faucet/errors.d.mts +11 -24
- package/dist/plugins/faucet/errors.mjs.map +1 -1
- package/dist/plugins/faucet/http.mjs +33 -14
- package/dist/plugins/faucet/http.mjs.map +1 -1
- package/dist/plugins/faucet/index.d.mts +15 -5
- package/dist/plugins/faucet/index.mjs +3 -0
- package/dist/plugins/faucet/index.mjs.map +1 -1
- package/dist/plugins/faucet/spans.mjs +12 -0
- package/dist/plugins/faucet/spans.mjs.map +1 -0
- package/dist/plugins/host-service/errors.d.mts +1 -1
- package/dist/plugins/host-service/index.mjs +34 -8
- package/dist/plugins/host-service/index.mjs.map +1 -1
- package/dist/plugins/host-service/routable.mjs +2 -1
- package/dist/plugins/host-service/routable.mjs.map +1 -1
- package/dist/plugins/host-service/service.d.mts +21 -0
- package/dist/plugins/host-service/service.mjs +136 -81
- package/dist/plugins/host-service/service.mjs.map +1 -1
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs +23 -0
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -0
- package/dist/plugins/internal/codegen-helpers.mjs +18 -0
- package/dist/plugins/internal/codegen-helpers.mjs.map +1 -0
- package/dist/plugins/internal/funding-failure-error.mjs +32 -0
- package/dist/plugins/internal/funding-failure-error.mjs.map +1 -0
- package/dist/plugins/package/build.mjs +1 -1
- package/dist/plugins/package/build.mjs.map +1 -1
- package/dist/plugins/package/codegen.mjs +26 -0
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/errors.d.mts +16 -6
- package/dist/plugins/package/errors.mjs.map +1 -1
- package/dist/plugins/package/index.d.mts +0 -4
- package/dist/plugins/package/index.mjs +27 -21
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/package/mode-known.mjs +26 -12
- package/dist/plugins/package/mode-known.mjs.map +1 -1
- package/dist/plugins/package/mode-local.mjs +47 -41
- package/dist/plugins/package/mode-local.mjs.map +1 -1
- package/dist/plugins/package/publish-executor.mjs +55 -114
- package/dist/plugins/package/publish-executor.mjs.map +1 -1
- package/dist/plugins/package/registry.mjs +2 -1
- package/dist/plugins/package/registry.mjs.map +1 -1
- package/dist/plugins/package/service.mjs.map +1 -1
- package/dist/plugins/package/spans.mjs +14 -0
- package/dist/plugins/package/spans.mjs.map +1 -0
- package/dist/plugins/postgres/db-ensure.mjs +19 -12
- package/dist/plugins/postgres/db-ensure.mjs.map +1 -1
- package/dist/plugins/postgres/errors.d.mts +5 -0
- package/dist/plugins/postgres/errors.mjs.map +1 -1
- package/dist/plugins/postgres/index.mjs +10 -17
- package/dist/plugins/postgres/index.mjs.map +1 -1
- package/dist/plugins/postgres/service.mjs +58 -24
- package/dist/plugins/postgres/service.mjs.map +1 -1
- package/dist/plugins/postgres/snapshot.mjs.map +1 -1
- package/dist/plugins/postgres/spans.mjs +11 -0
- package/dist/plugins/postgres/spans.mjs.map +1 -0
- package/dist/plugins/router-entrypoints.mjs +4 -4
- package/dist/plugins/router-entrypoints.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +15 -13
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +15 -19
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
- package/dist/plugins/seal/codegen.d.mts +2 -3
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/config-render.mjs +35 -8
- package/dist/plugins/seal/config-render.mjs.map +1 -1
- package/dist/plugins/seal/deploy.mjs +31 -39
- package/dist/plugins/seal/deploy.mjs.map +1 -1
- package/dist/plugins/seal/errors.d.mts +1 -3
- package/dist/plugins/seal/errors.mjs +7 -1
- package/dist/plugins/seal/errors.mjs.map +1 -1
- package/dist/plugins/seal/index.d.mts +1 -6
- package/dist/plugins/seal/index.mjs +40 -14
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/seal/key-manager.d.mts +1 -9
- package/dist/plugins/seal/key-manager.mjs +2 -18
- package/dist/plugins/seal/key-manager.mjs.map +1 -1
- package/dist/plugins/seal/key-server.mjs +37 -36
- package/dist/plugins/seal/key-server.mjs.map +1 -1
- package/dist/plugins/seal/keygen.mjs +16 -17
- package/dist/plugins/seal/keygen.mjs.map +1 -1
- package/dist/plugins/seal/mode/fork-known.mjs +36 -14
- package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
- package/dist/plugins/seal/mode/live.d.mts +6 -4
- package/dist/plugins/seal/mode/live.mjs +14 -21
- package/dist/plugins/seal/mode/live.mjs.map +1 -1
- package/dist/plugins/seal/mode/local-keygen.mjs +57 -21
- package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
- package/dist/plugins/seal/registry-publish.mjs +1 -0
- package/dist/plugins/seal/registry-publish.mjs.map +1 -1
- package/dist/plugins/seal/routable.mjs.map +1 -1
- package/dist/plugins/seal/service.mjs.map +1 -1
- package/dist/plugins/seal/spans.mjs +18 -0
- package/dist/plugins/seal/spans.mjs.map +1 -0
- package/dist/plugins/sui/auto-tick.mjs +17 -23
- package/dist/plugins/sui/auto-tick.mjs.map +1 -1
- package/dist/plugins/sui/chain-build-container.mjs +18 -0
- package/dist/plugins/sui/chain-build-container.mjs.map +1 -0
- package/dist/plugins/sui/chain-probe.d.mts +5 -5
- package/dist/plugins/sui/chain-probe.mjs +28 -58
- package/dist/plugins/sui/chain-probe.mjs.map +1 -1
- package/dist/plugins/sui/codegen.mjs +26 -0
- package/dist/plugins/sui/codegen.mjs.map +1 -1
- package/dist/plugins/sui/errors.d.mts +2 -29
- package/dist/plugins/sui/errors.mjs +1 -3
- package/dist/plugins/sui/errors.mjs.map +1 -1
- package/dist/plugins/sui/fork-faucet-strategy.d.mts +1 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs +55 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -0
- package/dist/plugins/sui/fork-orchestration.d.mts +1 -0
- package/dist/plugins/sui/fork-orchestration.mjs +170 -2
- package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
- package/dist/plugins/sui/fork-transaction.d.mts +2 -0
- package/dist/plugins/sui/fork-transaction.mjs +92 -28
- package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
- package/dist/plugins/sui/index.d.mts +33 -70
- package/dist/plugins/sui/index.mjs +58 -35
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/plugins/{faucet/strategies/sui-local.mjs → sui/local-faucet-strategy.mjs} +10 -7
- package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -0
- package/dist/plugins/sui/mode/external.mjs +7 -13
- package/dist/plugins/sui/mode/external.mjs.map +1 -1
- package/dist/plugins/sui/mode/fork.mjs +136 -59
- package/dist/plugins/sui/mode/fork.mjs.map +1 -1
- package/dist/plugins/sui/mode/live.mjs +10 -16
- package/dist/plugins/sui/mode/live.mjs.map +1 -1
- package/dist/plugins/sui/mode/local.mjs +37 -36
- package/dist/plugins/sui/mode/local.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared-boot.mjs +38 -115
- package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared.d.mts +1 -5
- package/dist/plugins/sui/mode/shared.mjs.map +1 -1
- package/dist/plugins/sui/mode/spec.d.mts +18 -0
- package/dist/plugins/sui/move-summary-runner.mjs +207 -0
- package/dist/plugins/sui/move-summary-runner.mjs.map +1 -0
- package/dist/plugins/sui/network-resolver.d.mts +4 -3
- package/dist/plugins/sui/service.mjs.map +1 -1
- package/dist/plugins/sui/snapshot.mjs +3 -0
- package/dist/plugins/sui/snapshot.mjs.map +1 -1
- package/dist/plugins/sui/spans.mjs +17 -0
- package/dist/plugins/sui/spans.mjs.map +1 -0
- package/dist/plugins/wallet/codegen.d.mts +0 -1
- package/dist/plugins/wallet/codegen.mjs +8 -9
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/index.d.mts +6 -8
- package/dist/plugins/wallet/index.mjs +46 -35
- package/dist/plugins/wallet/index.mjs.map +1 -1
- package/dist/plugins/wallet/origin-policy.mjs +12 -28
- package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
- package/dist/plugins/wallet/pairing.mjs +6 -4
- package/dist/plugins/wallet/pairing.mjs.map +1 -1
- package/dist/plugins/wallet/protocol.d.mts +1 -0
- package/dist/plugins/wallet/protocol.mjs +5 -44
- package/dist/plugins/wallet/protocol.mjs.map +1 -1
- package/dist/plugins/wallet/routable.mjs +2 -8
- package/dist/plugins/wallet/routable.mjs.map +1 -1
- package/dist/plugins/wallet/server.mjs +36 -44
- package/dist/plugins/wallet/server.mjs.map +1 -1
- package/dist/plugins/wallet/service.d.mts +1 -5
- package/dist/plugins/wallet/service.mjs +22 -18
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/plugins/wallet/spans.mjs +22 -0
- package/dist/plugins/wallet/spans.mjs.map +1 -0
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +33 -8
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/walrus/codegen.d.mts +3 -3
- package/dist/plugins/walrus/codegen.mjs.map +1 -1
- package/dist/plugins/walrus/deploy-paths.mjs +21 -5
- package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
- package/dist/plugins/walrus/deploy.mjs +67 -69
- package/dist/plugins/walrus/deploy.mjs.map +1 -1
- package/dist/plugins/walrus/errors.mjs +1 -0
- package/dist/plugins/walrus/errors.mjs.map +1 -1
- package/dist/plugins/walrus/faucet-strategy.mjs +12 -7
- package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/walrus/index.d.mts +2 -13
- package/dist/plugins/walrus/index.mjs +18 -23
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/plugins/walrus/mode/known-deploy.mjs +7 -5
- package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
- package/dist/plugins/walrus/mode/local-cluster.mjs +26 -7
- package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
- package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
- package/dist/plugins/walrus/routable.mjs +7 -23
- package/dist/plugins/walrus/routable.mjs.map +1 -1
- package/dist/plugins/walrus/service.mjs.map +1 -1
- package/dist/plugins/walrus/spans.mjs +18 -0
- package/dist/plugins/walrus/spans.mjs.map +1 -0
- package/dist/plugins/walrus/storage-nodes.d.mts +6 -2
- package/dist/plugins/walrus/storage-nodes.mjs +15 -8
- package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
- package/dist/plugins/walrus/wal-swap.mjs +58 -15
- package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
- package/dist/primitives/artifact-publisher.d.mts +1 -1
- package/dist/primitives/artifact-publisher.mjs +15 -0
- package/dist/primitives/artifact-publisher.mjs.map +1 -0
- package/dist/runtime/docker/client.mjs +17 -4
- package/dist/runtime/docker/client.mjs.map +1 -1
- package/dist/runtime/docker/container.mjs +219 -132
- package/dist/runtime/docker/container.mjs.map +1 -1
- package/dist/runtime/docker/errors.mjs +0 -6
- package/dist/runtime/docker/errors.mjs.map +1 -1
- package/dist/runtime/docker/exec.mjs +21 -18
- package/dist/runtime/docker/exec.mjs.map +1 -1
- package/dist/runtime/docker/image.mjs +36 -13
- package/dist/runtime/docker/image.mjs.map +1 -1
- package/dist/runtime/docker/index.mjs +14 -0
- package/dist/runtime/docker/inspect-and-decode.mjs +51 -0
- package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -0
- package/dist/runtime/docker/inventory.mjs +14 -11
- package/dist/runtime/docker/inventory.mjs.map +1 -1
- package/dist/runtime/docker/labels.mjs +38 -5
- package/dist/runtime/docker/labels.mjs.map +1 -1
- package/dist/runtime/docker/logs.d.mts +1 -1
- package/dist/runtime/docker/logs.mjs.map +1 -1
- package/dist/runtime/docker/network.mjs +83 -47
- package/dist/runtime/docker/network.mjs.map +1 -1
- package/dist/runtime/docker/render-run-args.mjs +82 -0
- package/dist/runtime/docker/render-run-args.mjs.map +1 -0
- package/dist/runtime/docker/service.d.mts +1 -1
- package/dist/runtime/docker/service.mjs +12 -7
- package/dist/runtime/docker/service.mjs.map +1 -1
- package/dist/runtime/docker/sweep.mjs +118 -43
- package/dist/runtime/docker/sweep.mjs.map +1 -1
- package/dist/runtime/docker/volume.mjs +2 -0
- package/dist/runtime/docker/volume.mjs.map +1 -1
- package/dist/runtime/docker/wrap.mjs +30 -1
- package/dist/runtime/docker/wrap.mjs.map +1 -1
- package/dist/substrate/cross-process.mjs +14 -8
- package/dist/substrate/cross-process.mjs.map +1 -1
- package/dist/substrate/event-time.mjs +33 -0
- package/dist/substrate/event-time.mjs.map +1 -0
- package/dist/substrate/events.d.mts +28 -8
- package/dist/substrate/identity.d.mts +2 -1
- package/dist/substrate/manifest.d.mts +68 -5
- package/dist/substrate/manifest.mjs +34 -5
- package/dist/substrate/manifest.mjs.map +1 -1
- package/dist/substrate/network.d.mts +3 -5
- package/dist/substrate/options.d.mts +0 -2
- package/dist/substrate/plugin.d.mts +13 -0
- package/dist/substrate/plugin.mjs +4 -2
- package/dist/substrate/plugin.mjs.map +1 -1
- package/dist/substrate/projection.d.mts +35 -3
- package/dist/substrate/runtime/artifact-publisher/index.mjs +19 -9
- package/dist/substrate/runtime/artifact-publisher/index.mjs.map +1 -1
- package/dist/substrate/runtime/atomic-write.mjs +97 -32
- package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
- package/dist/substrate/runtime/cache/index.mjs +3 -0
- package/dist/substrate/runtime/cache/schema.mjs +2 -2
- package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
- package/dist/substrate/runtime/cache/service.mjs +9 -7
- package/dist/substrate/runtime/cache/service.mjs.map +1 -1
- package/dist/substrate/runtime/capability-sinks/index.d.mts +1 -1
- package/dist/substrate/runtime/capability-sinks/index.mjs +3 -0
- package/dist/substrate/runtime/capability-sinks/layer.mjs +1 -1
- package/dist/substrate/runtime/capability-sinks/service.d.mts +61 -2
- package/dist/substrate/runtime/capability-sinks/service.mjs +21 -26
- package/dist/substrate/runtime/capability-sinks/service.mjs.map +1 -1
- package/dist/substrate/runtime/config-validation.d.mts +1 -13
- package/dist/substrate/runtime/config-validation.mjs +2 -91
- package/dist/substrate/runtime/config-validation.mjs.map +1 -1
- package/dist/substrate/runtime/container-runtime.d.mts +1 -0
- package/dist/substrate/runtime/container-runtime.mjs +2 -0
- package/dist/substrate/runtime/control-plane/domain.mjs +101 -0
- package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -0
- package/dist/substrate/runtime/control-plane/service.mjs +7 -0
- package/dist/substrate/runtime/control-plane/service.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +46 -19
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs +30 -12
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/index.mjs +5 -0
- package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs +11 -2
- package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs +1 -1
- package/dist/substrate/runtime/cross-process/index.mjs +11 -0
- package/dist/substrate/runtime/cross-process/live-clock.mjs +34 -0
- package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/liveness.mjs +52 -10
- package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/lock.d.mts +1 -0
- package/dist/substrate/runtime/cross-process/lock.mjs +23 -0
- package/dist/substrate/runtime/cross-process/lock.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +64 -0
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/roster.mjs +97 -83
- package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/self-pid.mjs +8 -0
- package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +24 -28
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/stack-lock.mjs +41 -20
- package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
- package/dist/substrate/runtime/current-plugin.mjs +2 -2
- package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
- package/dist/substrate/runtime/errors.mjs +12 -6
- package/dist/substrate/runtime/errors.mjs.map +1 -1
- package/dist/substrate/runtime/format-unknown-error.mjs +30 -0
- package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -0
- package/dist/substrate/runtime/host-bind-mount-owner.mjs +10 -0
- package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -0
- package/dist/substrate/runtime/host-gateway.mjs +20 -0
- package/dist/substrate/runtime/host-gateway.mjs.map +1 -0
- package/dist/substrate/runtime/host-tree-tar/index.mjs +61 -50
- package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
- package/dist/substrate/runtime/http-probe.d.mts +1 -1
- package/dist/substrate/runtime/index.mjs +14 -0
- package/dist/substrate/runtime/lease-broker/index.d.mts +1 -1
- package/dist/substrate/runtime/lease-broker/index.mjs +2 -0
- package/dist/substrate/runtime/lease-broker/service.d.mts +1 -64
- package/dist/substrate/runtime/lease-broker/service.mjs +1 -1
- package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs +22 -3
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/index.mjs +9 -0
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +20 -3
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs +5 -5
- package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/state-machine.mjs +1 -1
- package/dist/substrate/runtime/managed-container.d.mts +19 -1
- package/dist/substrate/runtime/managed-container.mjs +20 -1
- package/dist/substrate/runtime/managed-container.mjs.map +1 -1
- package/dist/substrate/runtime/manifest/index.mjs +3 -0
- package/dist/substrate/runtime/manifest/manifest.mjs +15 -11
- package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/mode-errors.mjs +20 -0
- package/dist/substrate/runtime/mode-errors.mjs.map +1 -0
- package/dist/substrate/runtime/observability/cascade-formatter.mjs +9 -13
- package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
- package/dist/substrate/runtime/observability/formatter-registry.mjs +19 -36
- package/dist/substrate/runtime/observability/formatter-registry.mjs.map +1 -1
- package/dist/substrate/runtime/observability/ignore-with-log.d.mts +1 -0
- package/dist/substrate/runtime/observability/ignore-with-log.mjs +17 -0
- package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -0
- package/dist/substrate/runtime/observability/index.d.mts +1 -1
- package/dist/substrate/runtime/observability/index.mjs +13 -0
- package/dist/substrate/runtime/observability/log-store.d.mts +1 -0
- package/dist/substrate/runtime/observability/log-store.mjs +175 -0
- package/dist/substrate/runtime/observability/log-store.mjs.map +1 -0
- package/dist/substrate/runtime/observability/logger.d.mts +2 -1
- package/dist/substrate/runtime/observability/logger.mjs +5 -9
- package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
- package/dist/substrate/runtime/observability/output-truncate.mjs +35 -0
- package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -0
- package/dist/substrate/runtime/observability/pretty-error.mjs +9 -12
- package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
- package/dist/substrate/runtime/observability/redaction.d.mts +2 -13
- package/dist/substrate/runtime/observability/redaction.mjs +8 -14
- package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
- package/dist/substrate/runtime/observability/span-store.d.mts +1 -0
- package/dist/substrate/runtime/observability/span-store.mjs +110 -0
- package/dist/substrate/runtime/observability/span-store.mjs.map +1 -0
- package/dist/substrate/runtime/observability/spans.mjs +14 -14
- package/dist/substrate/runtime/observability/spans.mjs.map +1 -1
- package/dist/substrate/runtime/observability/subprocess-capture.mjs +1 -1
- package/dist/substrate/runtime/passthrough-or-wrap.mjs +33 -0
- package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -0
- package/dist/substrate/runtime/paths.d.mts +1 -1
- package/dist/substrate/runtime/paths.mjs +4 -3
- package/dist/substrate/runtime/paths.mjs.map +1 -1
- package/dist/substrate/runtime/phase-preserving-produce.mjs +40 -0
- package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -0
- package/dist/substrate/runtime/port-broker/index.mjs +3 -0
- package/dist/substrate/runtime/port-broker/service.mjs +170 -126
- package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/probes.d.mts +7 -5
- package/dist/substrate/runtime/probes.mjs +3 -1
- package/dist/substrate/runtime/probes.mjs.map +1 -1
- package/dist/substrate/runtime/projection/index.mjs +4 -0
- package/dist/substrate/runtime/projection/operational-endpoints.mjs +1 -2
- package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
- package/dist/substrate/runtime/projection/persisted.mjs +33 -15
- package/dist/substrate/runtime/projection/persisted.mjs.map +1 -1
- package/dist/substrate/runtime/projection/state-ref.mjs +26 -2
- package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
- package/dist/substrate/runtime/projection/update.mjs +88 -24
- package/dist/substrate/runtime/projection/update.mjs.map +1 -1
- package/dist/substrate/runtime/random-suffix.mjs +11 -0
- package/dist/substrate/runtime/random-suffix.mjs.map +1 -0
- package/dist/substrate/runtime/retry-policy.d.mts +59 -1
- package/dist/substrate/runtime/retry-policy.mjs +66 -1
- package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
- package/dist/substrate/runtime/routed-url.d.mts +1 -0
- package/dist/substrate/runtime/routed-url.mjs +79 -0
- package/dist/substrate/runtime/routed-url.mjs.map +1 -0
- package/dist/substrate/runtime/runtime-decode.d.mts +1 -1
- package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-http-server.mjs +2 -3
- package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-multimap/index.mjs +2 -0
- package/dist/substrate/runtime/scoped-multimap/service.mjs +52 -0
- package/dist/substrate/runtime/scoped-multimap/service.mjs.map +1 -0
- package/dist/substrate/runtime/scoped-ref-map/index.mjs +2 -0
- package/dist/substrate/runtime/scoped-ref-map/service.mjs +2 -2
- package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +1 -1
- package/dist/substrate/runtime/stage-and-swap/index.mjs +72 -4
- package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
- package/dist/substrate/runtime/state-store/index.d.mts +1 -0
- package/dist/substrate/runtime/state-store/index.mjs +3 -0
- package/dist/substrate/runtime/state-store/schema.d.mts +1 -0
- package/dist/substrate/runtime/state-store/schema.mjs +19 -14
- package/dist/substrate/runtime/state-store/schema.mjs.map +1 -1
- package/dist/substrate/runtime/state-store/service.d.mts +1 -0
- package/dist/substrate/runtime/state-store/service.mjs +145 -0
- package/dist/substrate/runtime/state-store/service.mjs.map +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.d.mts +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs +33 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/index.mjs +4 -0
- package/dist/substrate/runtime/strategy-registry/service.mjs +19 -33
- package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
- package/dist/substrate/runtime/subnet-broker.mjs +60 -0
- package/dist/substrate/runtime/subnet-broker.mjs.map +1 -0
- package/dist/substrate/runtime/sui-execute/index.d.mts +21 -1
- package/dist/substrate/runtime/sui-execute/index.mjs +84 -20
- package/dist/substrate/runtime/sui-execute/index.mjs.map +1 -1
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.d.mts +1 -0
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs +32 -0
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +1 -0
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs +32 -0
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +1 -0
- package/dist/substrate/runtime/sui-move-build/index.mjs +93 -19
- package/dist/substrate/runtime/sui-move-build/index.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/acquire-node.mjs +181 -0
- package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/background-tasks.mjs +144 -0
- package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/command-loop.mjs +68 -0
- package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +151 -0
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/errors.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/errors.mjs +9 -0
- package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/index.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/index.mjs +3 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs +51 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs +250 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/state.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/state.mjs +22 -0
- package/dist/substrate/runtime/supervisor/state.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/teardown.mjs +57 -0
- package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/types.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/wiring.mjs +133 -0
- package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -0
- package/dist/substrate/runtime/typed-env.mjs +8 -0
- package/dist/substrate/runtime/typed-env.mjs.map +1 -0
- package/dist/substrate/state-store.d.mts +1 -0
- package/dist/substrate/versioned-doc-schema.mjs +17 -0
- package/dist/substrate/versioned-doc-schema.mjs.map +1 -0
- package/dist/substrate/versioned-doc-sync.mjs +94 -0
- package/dist/substrate/versioned-doc-sync.mjs.map +1 -0
- package/dist/surfaces/cli/command-tree.mjs.map +1 -1
- package/dist/surfaces/cli/commands/index.mjs +9 -0
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs +13 -0
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -0
- package/dist/surfaces/cli/commands/prune-picker.mjs +3 -2
- package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune.mjs +34 -17
- package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
- package/dist/surfaces/cli/commands/status.mjs +2 -2
- package/dist/surfaces/cli/commands/status.mjs.map +1 -1
- package/dist/surfaces/cli/commands/supervisor-presence.mjs +5 -3
- package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
- package/dist/surfaces/cli/commands/wipe.mjs +38 -4
- package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
- package/dist/surfaces/cli/errors.mjs +1 -1
- package/dist/surfaces/cli/errors.mjs.map +1 -1
- package/dist/surfaces/cli/flags.mjs +0 -1
- package/dist/surfaces/cli/flags.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs +98 -18
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/dist/surfaces/cli/output.mjs +1 -0
- package/dist/surfaces/cli/output.mjs.map +1 -1
- package/dist/surfaces/cli/sysexits.mjs +2 -1
- package/dist/surfaces/cli/sysexits.mjs.map +1 -1
- package/dist/surfaces/tui/app.mjs +25 -3
- package/dist/surfaces/tui/app.mjs.map +1 -1
- package/dist/surfaces/tui/display-derivation.mjs +23 -74
- package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
- package/dist/surfaces/tui/event-log.mjs +35 -23
- package/dist/surfaces/tui/event-log.mjs.map +1 -1
- package/dist/surfaces/tui/input.mjs +5 -1
- package/dist/surfaces/tui/input.mjs.map +1 -1
- package/dist/surfaces/tui/plain-renderer.mjs +70 -34
- package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
- package/package.json +24 -4
- package/dist/contracts/liveness-classifier.d.mts +0 -19
- package/dist/contracts/network-resolver.d.mts +0 -15
- package/dist/contracts/network-resolver.mjs +0 -7
- package/dist/contracts/network-resolver.mjs.map +0 -1
- package/dist/orchestrators/codegen/extras.mjs +0 -16
- package/dist/orchestrators/codegen/extras.mjs.map +0 -1
- package/dist/plugins/deepbook/routable.mjs +0 -21
- package/dist/plugins/deepbook/routable.mjs.map +0 -1
- package/dist/plugins/faucet/service.d.mts +0 -20
- package/dist/plugins/faucet/strategies/sui-local.d.mts +0 -23
- package/dist/plugins/faucet/strategies/sui-local.mjs.map +0 -1
- package/dist/plugins/sui/seed-objects.d.mts +0 -18
- package/dist/plugins/sui/seed-objects.mjs +0 -25
- package/dist/plugins/sui/seed-objects.mjs.map +0 -1
- package/dist/runtime/built-in-plugin-layers.mjs.map +0 -1
- package/dist/substrate/runtime/context-helpers.mjs +0 -32
- package/dist/substrate/runtime/context-helpers.mjs.map +0 -1
- package/dist/substrate/runtime/run.mjs.map +0 -1
- package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs +0 -30
- package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs.map +0 -1
- package/dist/substrate/runtime/supervisor.d.mts +0 -1
- package/dist/substrate/runtime/supervisor.mjs +0 -831
- package/dist/substrate/runtime/supervisor.mjs.map +0 -1
- package/dist/surfaces/cli/commands/doctor-probes.mjs.map +0 -1
- /package/dist/{substrate/runtime/strategy-registry/faucet-capability-for.d.mts → plugins/faucet/http.d.mts} +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { DEFAULT_SWEEP_POLICY } from "../../cross-process.mjs";
|
|
2
|
+
import { readFileSync, statSync, unlinkSync } from "node:fs";
|
|
3
|
+
//#region src/substrate/runtime/cross-process/reclaim-stale-file.ts
|
|
4
|
+
const identityOf = (path) => {
|
|
5
|
+
try {
|
|
6
|
+
const s = statSync(path);
|
|
7
|
+
return {
|
|
8
|
+
mtimeMs: s.mtimeMs,
|
|
9
|
+
ino: s.ino,
|
|
10
|
+
dev: s.dev
|
|
11
|
+
};
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const sameIdentity = (a, b) => a.mtimeMs === b.mtimeMs && a.ino === b.ino && a.dev === b.dev;
|
|
17
|
+
const readOrNull = (path) => {
|
|
18
|
+
try {
|
|
19
|
+
return readFileSync(path, "utf8");
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/** Reclaim `path` IFF it is an aged, unparseable orphan — with a re-stat
|
|
25
|
+
* guard that NARROWS (not closes) the TOCTOU window between the
|
|
26
|
+
* staleness check and the unlink. See the module header for the
|
|
27
|
+
* deliberately-accepted sub-microsecond residual between the final
|
|
28
|
+
* re-read and the path-based unlink.
|
|
29
|
+
*
|
|
30
|
+
* `parse` is the caller's body decoder; it returns a non-null value for
|
|
31
|
+
* a well-formed body and `null` for an unparseable one. We never look
|
|
32
|
+
* at the decoded value — only at whether it parsed — so the lock and
|
|
33
|
+
* the reservation can share one guard despite carrying different
|
|
34
|
+
* schemas.
|
|
35
|
+
*
|
|
36
|
+
* The staleness threshold is `DEFAULT_SWEEP_POLICY.staleAfterMillis`
|
|
37
|
+
* (30s), matching the roster's eviction window: anything that crashed
|
|
38
|
+
* mid-write at least 30s ago is presumed abandoned.
|
|
39
|
+
*
|
|
40
|
+
* Never throws. Any I/O failure resolves to a conservative non-
|
|
41
|
+
* reclaiming outcome so a transient stat/read hiccup never triggers a
|
|
42
|
+
* spurious unlink — the caller's backoff loop retries on the next pass. */
|
|
43
|
+
const reclaimUnparseableStaleFile = (path, parse, staleAfterMillis = DEFAULT_SWEEP_POLICY.staleAfterMillis) => {
|
|
44
|
+
const before = identityOf(path);
|
|
45
|
+
if (before === null) return "absent";
|
|
46
|
+
const rawBefore = readOrNull(path);
|
|
47
|
+
if (rawBefore === null) return "absent";
|
|
48
|
+
if (parse(rawBefore) !== null) return "parseable";
|
|
49
|
+
if (!(Date.now() - before.mtimeMs > staleAfterMillis)) return "fresh";
|
|
50
|
+
const after = identityOf(path);
|
|
51
|
+
if (after === null) return "reclaimed";
|
|
52
|
+
if (!sameIdentity(before, after)) return "changed";
|
|
53
|
+
const rawAfter = readOrNull(path);
|
|
54
|
+
if (rawAfter === null) return "reclaimed";
|
|
55
|
+
if (parse(rawAfter) !== null) return "changed";
|
|
56
|
+
try {
|
|
57
|
+
unlinkSync(path);
|
|
58
|
+
} catch {}
|
|
59
|
+
return "reclaimed";
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { reclaimUnparseableStaleFile };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=reclaim-stale-file.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reclaim-stale-file.mjs","names":[],"sources":["../../../../src/substrate/runtime/cross-process/reclaim-stale-file.ts"],"sourcesContent":["// Shared reclaim guard for unparseable/orphaned coordination files.\n//\n// Both `stack.lock` (stack-lock.ts) and `snapshot.reservation`\n// (snapshot-reservation.ts) reclaim a file whose on-disk body fails to\n// parse (a peer that crashed mid-write) once it ages past the roster\n// sweep's staleness window. The PID liveness check has nothing to\n// consult when the body won't parse, so mtime + the shared staleness\n// budget is the only abandonment signal.\n//\n// The naive shape — `statSync` for the mtime, then `unlinkSync` — has a\n// TOCTOU window: between reading the mtime and unlinking, a competing\n// process can legitimately reclaim the garbage lock and write a FRESH,\n// VALID O_EXCL body. The unconditional `unlinkSync` would then clobber\n// that live lock, producing two simultaneous holders (a mutual-\n// exclusion break). This module NARROWS the window — it does not fully\n// eliminate it — by RE-STATTING the file immediately before the unlink\n// and confirming it is STILL the same stale inode (same mtime + ino +\n// dev) AND still unparseable. If anything changed, the reclaim bails\n// and the caller falls back to its normal backoff/retry path so the\n// competitor's fresh body survives.\n//\n// Residual race (deliberately accepted): the final `unlinkSync` is\n// path-based and UNCONDITIONAL, so between the re-stat/re-read and the\n// syscall a competing reclaimer can still unlink+O_EXCL-create a fresh\n// body that this unlink then deletes. Closing it fully would need an\n// atomic, inode-pinned delete (`renameat2(RENAME_EXCHANGE)` or an fd\n// held across the check) that `node:fs` does not portably expose — a\n// plain `rename(path, …)` is itself path-based and would relocate the\n// competitor's fresh body just as readily, so it buys nothing. The\n// re-stat shrinks the window from \"mtime read → unlink\" (a parse +\n// staleness comparison wide) to \"re-read → unlink\" (a couple of\n// syscalls), making a real collision require both racers mid-reclaim of\n// the SAME file with sub-microsecond timing. Given that, the residual\n// is left open rather than papered over with an equally-racy rename.\n//\n// The dead-PID liveness reclaim path is NOT routed through here: that\n// branch is gated on a holder-liveness probe (see liveness.ts) and is\n// already safe — a dead holder cannot have written a fresh body. Only\n// the unparseable/mtime-stale branch carries the TOCTOU, so only it\n// needs this guard.\n//\n// Sync `node:fs` by design — the call sites are inside `Effect.try`\n// critical sections that need a non-blocking attempt; the rest of the\n// substrate stays Effect-native.\n\nimport { readFileSync, statSync, unlinkSync } from 'node:fs';\n\nimport { DEFAULT_SWEEP_POLICY } from '../../cross-process.ts';\n\n/** Outcome of a reclaim attempt.\n *\n * - `reclaimed` — the file was an aged, unparseable orphan and we\n * unlinked it (or it vanished from under us mid-reclaim, which is the\n * same observable end state: the slot is free for the next O_EXCL).\n * - `parseable` — the body parsed, so it is NOT an orphan; a live peer\n * holds it (possibly one that just reclaimed the garbage ahead of\n * us). Do not touch it.\n * - `fresh` — the body is unparseable but its mtime is inside the\n * staleness window; a writer is presumed mid-flush. Respect the\n * budget and leave it.\n * - `changed` — the re-stat guard fired: the file's identity (mtime /\n * ino / dev) or parse-state changed between the staleness check and\n * the unlink, meaning a competitor reclaimed the slot and wrote a\n * fresh body in the window. We bailed to avoid clobbering it.\n * - `absent` — the file was already gone when we looked. */\nexport type ReclaimOutcome = 'reclaimed' | 'parseable' | 'fresh' | 'changed' | 'absent';\n\n/** A minimal, comparable identity for a file on disk. Two reads of the\n * same untouched inode produce equal triples; any rewrite (O_EXCL\n * unlink+create, or an in-place touch) perturbs at least one field. */\ninterface FileIdentity {\n\treadonly mtimeMs: number;\n\treadonly ino: number;\n\treadonly dev: number;\n}\n\nconst identityOf = (path: string): FileIdentity | null => {\n\ttry {\n\t\tconst s = statSync(path);\n\t\treturn { mtimeMs: s.mtimeMs, ino: s.ino, dev: s.dev };\n\t} catch {\n\t\t// ENOENT (gone) or any transient stat failure — treat as absent.\n\t\treturn null;\n\t}\n};\n\nconst sameIdentity = (a: FileIdentity, b: FileIdentity): boolean =>\n\ta.mtimeMs === b.mtimeMs && a.ino === b.ino && a.dev === b.dev;\n\nconst readOrNull = (path: string): string | null => {\n\ttry {\n\t\treturn readFileSync(path, 'utf8');\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/** Reclaim `path` IFF it is an aged, unparseable orphan — with a re-stat\n * guard that NARROWS (not closes) the TOCTOU window between the\n * staleness check and the unlink. See the module header for the\n * deliberately-accepted sub-microsecond residual between the final\n * re-read and the path-based unlink.\n *\n * `parse` is the caller's body decoder; it returns a non-null value for\n * a well-formed body and `null` for an unparseable one. We never look\n * at the decoded value — only at whether it parsed — so the lock and\n * the reservation can share one guard despite carrying different\n * schemas.\n *\n * The staleness threshold is `DEFAULT_SWEEP_POLICY.staleAfterMillis`\n * (30s), matching the roster's eviction window: anything that crashed\n * mid-write at least 30s ago is presumed abandoned.\n *\n * Never throws. Any I/O failure resolves to a conservative non-\n * reclaiming outcome so a transient stat/read hiccup never triggers a\n * spurious unlink — the caller's backoff loop retries on the next pass. */\nexport const reclaimUnparseableStaleFile = (\n\tpath: string,\n\tparse: (raw: string) => unknown,\n\tstaleAfterMillis: number = DEFAULT_SWEEP_POLICY.staleAfterMillis,\n): ReclaimOutcome => {\n\t// 1. Initial identity + staleness.\n\tconst before = identityOf(path);\n\tif (before === null) return 'absent';\n\n\tconst rawBefore = readOrNull(path);\n\tif (rawBefore === null) return 'absent';\n\tif (parse(rawBefore) !== null) return 'parseable';\n\n\tconst isStale = Date.now() - before.mtimeMs > staleAfterMillis;\n\tif (!isStale) return 'fresh';\n\n\t// 2. Re-stat guard — NARROWS (does not close) the TOCTOU. Between the\n\t// read above and the unlink below, a competitor may have reclaimed\n\t// the garbage and written a FRESH valid O_EXCL body. Re-confirm the\n\t// file is STILL the same stale inode AND still unparseable before\n\t// unlinking. A sub-microsecond residual remains between this\n\t// re-read and the path-based unlink (see header) — accepted.\n\tconst after = identityOf(path);\n\tif (after === null) {\n\t\t// Vanished from under us — a sibling reclaimer won the unlink. The\n\t\t// slot is free; report it as reclaimed (same observable result).\n\t\treturn 'reclaimed';\n\t}\n\tif (!sameIdentity(before, after)) return 'changed';\n\n\tconst rawAfter = readOrNull(path);\n\tif (rawAfter === null) return 'reclaimed';\n\tif (parse(rawAfter) !== null) return 'changed';\n\n\t// 3. Still the same stale, unparseable orphan — safe to unlink.\n\ttry {\n\t\tunlinkSync(path);\n\t} catch {\n\t\t// Race with another reclaimer that unlinked first — harmless; the\n\t\t// slot is free either way.\n\t}\n\treturn 'reclaimed';\n};\n"],"mappings":";;;AA4EA,MAAM,cAAc,SAAsC;AACzD,KAAI;EACH,MAAM,IAAI,SAAS,KAAK;AACxB,SAAO;GAAE,SAAS,EAAE;GAAS,KAAK,EAAE;GAAK,KAAK,EAAE;GAAK;SAC9C;AAEP,SAAO;;;AAIT,MAAM,gBAAgB,GAAiB,MACtC,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE;AAE3D,MAAM,cAAc,SAAgC;AACnD,KAAI;AACH,SAAO,aAAa,MAAM,OAAO;SAC1B;AACP,SAAO;;;;;;;;;;;;;;;;;;;;;;AAuBT,MAAa,+BACZ,MACA,OACA,mBAA2B,qBAAqB,qBAC5B;CAEpB,MAAM,SAAS,WAAW,KAAK;AAC/B,KAAI,WAAW,KAAM,QAAO;CAE5B,MAAM,YAAY,WAAW,KAAK;AAClC,KAAI,cAAc,KAAM,QAAO;AAC/B,KAAI,MAAM,UAAU,KAAK,KAAM,QAAO;AAGtC,KAAI,EADY,KAAK,KAAK,GAAG,OAAO,UAAU,kBAChC,QAAO;CAQrB,MAAM,QAAQ,WAAW,KAAK;AAC9B,KAAI,UAAU,KAGb,QAAO;AAER,KAAI,CAAC,aAAa,QAAQ,MAAM,CAAE,QAAO;CAEzC,MAAM,WAAW,WAAW,KAAK;AACjC,KAAI,aAAa,KAAM,QAAO;AAC9B,KAAI,MAAM,SAAS,KAAK,KAAM,QAAO;AAGrC,KAAI;AACH,aAAW,KAAK;SACT;AAIR,QAAO"}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { DEFAULT_SWEEP_POLICY, RosterDocumentSchema } from "../../cross-process.mjs";
|
|
2
|
-
import { checkHolderLiveness, isPidAlive, ownHolder, processStartTime } from "./liveness.mjs";
|
|
3
|
-
import { decodeJsonText } from "../runtime-decode.mjs";
|
|
4
|
-
import { atomicWriteJsonSync } from "../atomic-write.mjs";
|
|
5
1
|
import { SpanAttr } from "../observability/spans.mjs";
|
|
2
|
+
import { versionedDocSchema } from "../../versioned-doc-schema.mjs";
|
|
3
|
+
import { selfPid } from "./self-pid.mjs";
|
|
4
|
+
import { DEFAULT_SWEEP_POLICY, RosterDocumentSchema } from "../../cross-process.mjs";
|
|
5
|
+
import { readVersionedDocumentSync, writeVersionedDocumentSync } from "../../versioned-doc-sync.mjs";
|
|
6
|
+
import { LivenessProbeScope, isPidAlive, layerLivenessProbeScope, ownHolder, processStartTime } from "./liveness.mjs";
|
|
6
7
|
import { acquireStackLock } from "./stack-lock.mjs";
|
|
7
8
|
import { Data, Effect, Schema } from "effect";
|
|
8
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
-
import { dirname } from "node:path";
|
|
10
9
|
import { hostname } from "node:os";
|
|
11
10
|
//#region src/substrate/runtime/cross-process/roster.ts
|
|
12
11
|
var RosterCorruptError = class extends Data.TaggedError("RosterCorruptError") {};
|
|
@@ -15,39 +14,27 @@ const EMPTY_ROSTER = {
|
|
|
15
14
|
version: 1,
|
|
16
15
|
holders: []
|
|
17
16
|
};
|
|
17
|
+
const ROSTER_DOC_ERRORS = {
|
|
18
|
+
mkIo: ({ path, cause }) => new RosterIoError({
|
|
19
|
+
path,
|
|
20
|
+
cause
|
|
21
|
+
}),
|
|
22
|
+
mkCorrupt: ({ path, raw, cause }) => new RosterCorruptError({
|
|
23
|
+
path,
|
|
24
|
+
raw,
|
|
25
|
+
cause
|
|
26
|
+
})
|
|
27
|
+
};
|
|
18
28
|
/** Read the roster from disk. Returns the empty document if absent.
|
|
19
29
|
* Tolerates a missing file but NOT a malformed file (a malformed
|
|
20
30
|
* roster surfaces a typed error so callers can decide whether to
|
|
21
31
|
* abandon or rewrite). */
|
|
22
|
-
const readRoster = (path) => Effect.
|
|
23
|
-
if (!existsSync(path)) return EMPTY_ROSTER;
|
|
24
|
-
const raw = yield* Effect.try({
|
|
25
|
-
try: () => readFileSync(path, "utf8"),
|
|
26
|
-
catch: (cause) => new RosterIoError({
|
|
27
|
-
path,
|
|
28
|
-
cause
|
|
29
|
-
})
|
|
30
|
-
});
|
|
31
|
-
return yield* decodeJsonText(RosterDocumentSchema, raw, {
|
|
32
|
-
source: path,
|
|
33
|
-
mkError: (issue) => new RosterCorruptError({
|
|
34
|
-
path,
|
|
35
|
-
raw,
|
|
36
|
-
cause: issue.cause ?? issue
|
|
37
|
-
})
|
|
38
|
-
});
|
|
39
|
-
}).pipe(Effect.withSpan("cross-process.roster.read"));
|
|
32
|
+
const readRoster = (path) => readVersionedDocumentSync(path, RosterDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_ROSTER).pipe(Effect.withSpan("cross-process.roster.read"));
|
|
40
33
|
/** Atomic write: route through the canonical sync primitive. The
|
|
41
34
|
* roster's mutations are all under `stack.lock`, so the non-yielding
|
|
42
35
|
* sync surface is correct here — and it shares ONE owner of the
|
|
43
36
|
* tempfile dance with state-store, cache, and manifest. */
|
|
44
|
-
const atomicWriteRoster = (path, doc) =>
|
|
45
|
-
try: () => atomicWriteJsonSync(path, doc),
|
|
46
|
-
catch: (cause) => new RosterIoError({
|
|
47
|
-
path,
|
|
48
|
-
cause
|
|
49
|
-
})
|
|
50
|
-
});
|
|
37
|
+
const atomicWriteRoster = (path, doc) => writeVersionedDocumentSync(path, doc, ROSTER_DOC_ERRORS);
|
|
51
38
|
/** Walk the roster's holders and drop those that fail the liveness
|
|
52
39
|
* test AND whose heartbeats are older than `staleAfterMillis`.
|
|
53
40
|
*
|
|
@@ -58,14 +45,16 @@ const atomicWriteRoster = (path, doc) => Effect.try({
|
|
|
58
45
|
* A live holder with a stale heartbeat is NOT evicted — heartbeat
|
|
59
46
|
* staleness alone is allowed (slow peer). The conjunction matters. */
|
|
60
47
|
const sweepStaleHolders = Effect.fn("cross-process.roster.sweep")(function* (doc, policy = DEFAULT_SWEEP_POLICY, now = Date.now()) {
|
|
48
|
+
const probe = yield* LivenessProbeScope;
|
|
61
49
|
const survivors = [];
|
|
62
50
|
const evicted = [];
|
|
51
|
+
const ownHost = hostname();
|
|
63
52
|
for (const holder of doc.holders) {
|
|
64
53
|
if (!(now - holder.heartbeatAt > policy.staleAfterMillis)) {
|
|
65
54
|
survivors.push(holder);
|
|
66
55
|
continue;
|
|
67
56
|
}
|
|
68
|
-
if ((yield*
|
|
57
|
+
if ((yield* probe.probeHolderLiveness(holder, ownHost).pipe(Effect.catch(() => Effect.succeed("alive")))) === "dead") evicted.push(holder);
|
|
69
58
|
else survivors.push(holder);
|
|
70
59
|
}
|
|
71
60
|
return {
|
|
@@ -75,7 +64,37 @@ const sweepStaleHolders = Effect.fn("cross-process.roster.sweep")(function* (doc
|
|
|
75
64
|
},
|
|
76
65
|
evicted
|
|
77
66
|
};
|
|
78
|
-
});
|
|
67
|
+
}, Effect.provide(layerLivenessProbeScope));
|
|
68
|
+
/** Materialize the ledger path from `RosterPaths`. Callers that
|
|
69
|
+
* invoke any of the claim-ledger APIs (`readClaims`,
|
|
70
|
+
* `pruneStaleClaims`, `addClaim`, `removeClaim`) MUST construct
|
|
71
|
+
* `RosterPaths` with `containerClaimsFile` populated from
|
|
72
|
+
* `StackPathsService.containerClaimsFile`. The previous behavior of
|
|
73
|
+
* reconstructing `dirname(rosterFile) + '/container-claims.json'`
|
|
74
|
+
* internally has been removed so nothing in the runtime tree builds
|
|
75
|
+
* cross-process paths outside the substrate path resolver. */
|
|
76
|
+
const requireClaimsPath = (paths) => {
|
|
77
|
+
if (paths.containerClaimsFile === void 0) throw new Error("cross-process.roster: container-claim API called without `containerClaimsFile` on the RosterPaths bundle. Source this from StackPathsService.containerClaimsFile.");
|
|
78
|
+
return paths.containerClaimsFile;
|
|
79
|
+
};
|
|
80
|
+
/** Match a roster holder against THIS process's identity.
|
|
81
|
+
*
|
|
82
|
+
* Liveness elsewhere (`checkHolderLiveness`, `isContainerClaimLive`)
|
|
83
|
+
* uses `(pid, hostname, startTime)` — PID alone is insufficient on
|
|
84
|
+
* long-uptime hosts where the kernel can recycle PIDs. The roster
|
|
85
|
+
* mutators (`heartbeat`, `release`, `setIntent`) must apply the same
|
|
86
|
+
* triple match so a recycled-PID peer's entry is never silently
|
|
87
|
+
* overwritten/removed by this process.
|
|
88
|
+
*
|
|
89
|
+
* `startTime` is the FNV-1a hash of `ps -o lstart` (see
|
|
90
|
+
* `liveness.processStartTime`). A `null` probe (process gone, or
|
|
91
|
+
* exotic platform) skips the start-time check — same conservative
|
|
92
|
+
* policy as `isContainerClaimLive`. */
|
|
93
|
+
const isOwnEntry = (h, ownPid, ownHost, ownStartTime) => {
|
|
94
|
+
if (h.pid !== ownPid || h.hostname !== ownHost) return false;
|
|
95
|
+
if (ownStartTime === null || h.startTime === null) return true;
|
|
96
|
+
return h.startTime === ownStartTime;
|
|
97
|
+
};
|
|
79
98
|
const withStackLock = (paths, body) => Effect.scoped(Effect.gen(function* () {
|
|
80
99
|
yield* acquireStackLock(paths.stackLockFile);
|
|
81
100
|
return yield* body;
|
|
@@ -115,15 +134,16 @@ const claim = (paths, intent = "normal", policy = DEFAULT_SWEEP_POLICY) => withS
|
|
|
115
134
|
* stack lock to keep mutation atomic. Architecture § Heartbeat
|
|
116
135
|
* protocol.
|
|
117
136
|
*/
|
|
118
|
-
const heartbeat = (paths, ownPid =
|
|
137
|
+
const heartbeat = (paths, ownPid = selfPid()) => withStackLock(paths, Effect.gen(function* () {
|
|
119
138
|
const current = yield* readRoster(paths.rosterFile).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_ROSTER)));
|
|
120
139
|
const now = Date.now();
|
|
121
140
|
const ownHost = hostname();
|
|
141
|
+
const ownStartTime = processStartTime(ownPid);
|
|
122
142
|
let touched = false;
|
|
123
143
|
const next = {
|
|
124
144
|
version: 1,
|
|
125
145
|
holders: current.holders.map((h) => {
|
|
126
|
-
if (h
|
|
146
|
+
if (isOwnEntry(h, ownPid, ownHost, ownStartTime)) {
|
|
127
147
|
touched = true;
|
|
128
148
|
return {
|
|
129
149
|
...h,
|
|
@@ -141,10 +161,11 @@ const heartbeat = (paths, ownPid = process.pid) => withStackLock(paths, Effect.g
|
|
|
141
161
|
* Removes this process's holder. Returns whether this was the
|
|
142
162
|
* last-leaver (caller runs the stop finalizer if so).
|
|
143
163
|
*/
|
|
144
|
-
const release = (paths, ownPid =
|
|
164
|
+
const release = (paths, ownPid = selfPid()) => withStackLock(paths, Effect.gen(function* () {
|
|
145
165
|
const current = yield* readRoster(paths.rosterFile).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_ROSTER)));
|
|
146
166
|
const ownHost = hostname();
|
|
147
|
-
const
|
|
167
|
+
const ownStartTime = processStartTime(ownPid);
|
|
168
|
+
const remaining = current.holders.filter((h) => !isOwnEntry(h, ownPid, ownHost, ownStartTime));
|
|
148
169
|
const next = {
|
|
149
170
|
version: 1,
|
|
150
171
|
holders: remaining
|
|
@@ -177,64 +198,56 @@ const ContainerClaimSchema = Schema.Struct({
|
|
|
177
198
|
hostname: Schema.String,
|
|
178
199
|
claimedAt: Schema.Number
|
|
179
200
|
});
|
|
180
|
-
const ContainerClaimDocumentSchema = Schema.
|
|
181
|
-
version: Schema.Literal(1),
|
|
182
|
-
claims: Schema.Array(ContainerClaimSchema)
|
|
183
|
-
});
|
|
201
|
+
const ContainerClaimDocumentSchema = versionedDocSchema(1, { claims: Schema.Array(ContainerClaimSchema) });
|
|
184
202
|
const EMPTY_CLAIMS = {
|
|
185
203
|
version: 1,
|
|
186
204
|
claims: []
|
|
187
205
|
};
|
|
188
|
-
|
|
189
|
-
|
|
206
|
+
/** Match a container claim against THIS process's identity using the
|
|
207
|
+
* same `(pid, hostname, startTime)` triple that `isOwnEntry` /
|
|
208
|
+
* `isContainerClaimLive` apply to roster holders — PID alone is
|
|
209
|
+
* insufficient on long-uptime hosts where the kernel recycles PIDs.
|
|
210
|
+
* A null/undefined start-time on either side falls back to
|
|
211
|
+
* `(pid, hostname)`, matching the conservative policy elsewhere
|
|
212
|
+
* (avoids orphaning our own entry when the probe failed). */
|
|
213
|
+
const isOwnContainerClaim = (c, containerKey, ownPid, ownHost, ownStartTime) => {
|
|
214
|
+
if (c.containerKey !== containerKey || c.pid !== ownPid || c.hostname !== ownHost) return false;
|
|
215
|
+
if (ownStartTime === void 0 || c.startTime === void 0) return true;
|
|
216
|
+
return c.startTime === ownStartTime;
|
|
217
|
+
};
|
|
218
|
+
const isContainerClaimLive = (claim, probeStartTime, ownHost = hostname()) => {
|
|
190
219
|
if (claim.hostname !== ownHost) return true;
|
|
191
220
|
if (!isPidAlive(claim.pid)) return false;
|
|
192
221
|
if (claim.startTime === void 0) return true;
|
|
193
|
-
const probedStart =
|
|
222
|
+
const probedStart = probeStartTime(claim.pid);
|
|
194
223
|
if (probedStart === null) return true;
|
|
195
224
|
return probedStart === claim.startTime;
|
|
196
225
|
};
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
226
|
+
/** Effect-flavored filter that yields a fresh `LivenessProbeScope` so
|
|
227
|
+
* the same pid is probed at most once across this pass — even when
|
|
228
|
+
* multiple claims by the same pid sit in the ledger. The scope's
|
|
229
|
+
* cache is private to this call (provided via `Effect.provide`). */
|
|
230
|
+
const liveContainerClaims = (doc) => Effect.gen(function* () {
|
|
231
|
+
const probe = yield* LivenessProbeScope;
|
|
232
|
+
const ownHost = hostname();
|
|
233
|
+
return {
|
|
234
|
+
version: 1,
|
|
235
|
+
claims: doc.claims.filter((claim) => isContainerClaimLive(claim, probe.probeStartTime, ownHost))
|
|
236
|
+
};
|
|
237
|
+
}).pipe(Effect.provide(layerLivenessProbeScope));
|
|
238
|
+
const writeClaims = (path, doc) => writeVersionedDocumentSync(path, doc, ROSTER_DOC_ERRORS);
|
|
208
239
|
/** Read the container-claim ledger. */
|
|
209
|
-
const readClaims = (paths) => Effect.
|
|
210
|
-
const path = claimsPath(paths.rosterFile);
|
|
211
|
-
if (!existsSync(path)) return EMPTY_CLAIMS;
|
|
212
|
-
const raw = yield* Effect.try({
|
|
213
|
-
try: () => readFileSync(path, "utf8"),
|
|
214
|
-
catch: (cause) => new RosterIoError({
|
|
215
|
-
path,
|
|
216
|
-
cause
|
|
217
|
-
})
|
|
218
|
-
});
|
|
219
|
-
return yield* decodeJsonText(ContainerClaimDocumentSchema, raw, {
|
|
220
|
-
source: path,
|
|
221
|
-
mkError: (issue) => new RosterCorruptError({
|
|
222
|
-
path,
|
|
223
|
-
raw,
|
|
224
|
-
cause: issue.cause ?? issue
|
|
225
|
-
})
|
|
226
|
-
});
|
|
227
|
-
}).pipe(Effect.withSpan("cross-process.roster.readClaims"));
|
|
240
|
+
const readClaims = (paths) => Effect.suspend(() => readVersionedDocumentSync(requireClaimsPath(paths), ContainerClaimDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_CLAIMS)).pipe(Effect.withSpan("cross-process.roster.readClaims"));
|
|
228
241
|
/** Record that this process now claims `containerKey`. No-op if
|
|
229
242
|
* already claimed by this process. */
|
|
230
243
|
const addClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(function* () {
|
|
231
|
-
const path =
|
|
244
|
+
const path = requireClaimsPath(paths);
|
|
232
245
|
const current = yield* readClaims(paths).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_CLAIMS)));
|
|
233
|
-
const live = liveContainerClaims(current);
|
|
234
|
-
const ownPid =
|
|
246
|
+
const live = yield* liveContainerClaims(current);
|
|
247
|
+
const ownPid = selfPid();
|
|
235
248
|
const ownStartTime = processStartTime(ownPid) ?? void 0;
|
|
236
249
|
const ownHost = hostname();
|
|
237
|
-
if (live.claims.some((c) => c
|
|
250
|
+
if (live.claims.some((c) => isOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime))) {
|
|
238
251
|
if (live.claims.length !== current.claims.length) yield* writeClaims(path, live);
|
|
239
252
|
return;
|
|
240
253
|
}
|
|
@@ -257,11 +270,12 @@ const addClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(functi
|
|
|
257
270
|
* releases its last claim and confirms no other process holds the
|
|
258
271
|
* container, it may tear down." */
|
|
259
272
|
const removeClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(function* () {
|
|
260
|
-
const path =
|
|
261
|
-
const live = liveContainerClaims(yield* readClaims(paths).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_CLAIMS))));
|
|
262
|
-
const ownPid =
|
|
273
|
+
const path = requireClaimsPath(paths);
|
|
274
|
+
const live = yield* liveContainerClaims(yield* readClaims(paths).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_CLAIMS))));
|
|
275
|
+
const ownPid = selfPid();
|
|
276
|
+
const ownStartTime = processStartTime(ownPid) ?? void 0;
|
|
263
277
|
const ownHost = hostname();
|
|
264
|
-
const remaining = live.claims.filter((c) => !(c
|
|
278
|
+
const remaining = live.claims.filter((c) => !isOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime));
|
|
265
279
|
const stillClaimedByPeer = remaining.some((c) => c.containerKey === containerKey);
|
|
266
280
|
yield* writeClaims(path, {
|
|
267
281
|
version: 1,
|
|
@@ -270,6 +284,6 @@ const removeClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(fun
|
|
|
270
284
|
return { lastClaimReleased: !stillClaimedByPeer };
|
|
271
285
|
})).pipe(Effect.withSpan("cross-process.roster.removeClaim"));
|
|
272
286
|
//#endregion
|
|
273
|
-
export { addClaim, claim, heartbeatFiber, readClaims, readRoster, release, removeClaim };
|
|
287
|
+
export { RosterCorruptError, RosterIoError, addClaim, claim, heartbeat, heartbeatFiber, readClaims, readRoster, release, removeClaim, sweepStaleHolders };
|
|
274
288
|
|
|
275
289
|
//# sourceMappingURL=roster.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roster.mjs","names":["nodeHostname"],"sources":["../../../../src/substrate/runtime/cross-process/roster.ts"],"sourcesContent":["// `roster.json` — authoritative cross-process holder record.\n//\n// Architecture § Cross-process safety protocol § Roster:\n// \"the authoritative cross-process record of which OS processes are\n// currently 'in' this stack.\"\n//\n// Mutated only under `stack.lock`. Schema-validated on every read so a\n// truncated/forward-version write never corrupts a peer's view. Stale\n// entries reaped during step-3 sweep on the next claim.\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { hostname as nodeHostname } from 'node:os';\nimport { dirname } from 'node:path';\n\nimport { Data, Effect, Schema } from 'effect';\n\nimport {\n\tDEFAULT_SWEEP_POLICY,\n\ttype RosterDocument,\n\tRosterDocumentSchema,\n\ttype RosterHolder,\n\ttype RosterSweepPolicy,\n} from '../../cross-process.ts';\nimport { atomicWriteJsonSync } from '../atomic-write.ts';\nimport { SpanAttr } from '../observability/spans.ts';\nimport { decodeJsonText } from '../runtime-decode.ts';\nimport { acquireStackLock } from './stack-lock.ts';\nimport { checkHolderLiveness, isPidAlive, ownHolder, processStartTime } from './liveness.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\nexport class RosterCorruptError extends Data.TaggedError('RosterCorruptError')<{\n\treadonly path: string;\n\treadonly raw: string;\n\treadonly cause: unknown;\n}> {}\n\nexport class RosterIoError extends Data.TaggedError('RosterIoError')<{\n\treadonly path: string;\n\treadonly cause: unknown;\n}> {}\n\nexport type RosterError = RosterCorruptError | RosterIoError;\n\n// -----------------------------------------------------------------------------\n// Read / write\n// -----------------------------------------------------------------------------\n\nconst EMPTY_ROSTER: RosterDocument = { version: 1, holders: [] };\n\n/** Read the roster from disk. Returns the empty document if absent.\n * Tolerates a missing file but NOT a malformed file (a malformed\n * roster surfaces a typed error so callers can decide whether to\n * abandon or rewrite). */\nexport const readRoster = (path: string): Effect.Effect<RosterDocument, RosterError> =>\n\tEffect.gen(function* () {\n\t\tif (!existsSync(path)) return EMPTY_ROSTER;\n\t\tconst raw = yield* Effect.try({\n\t\t\ttry: () => readFileSync(path, 'utf8'),\n\t\t\tcatch: (cause) => new RosterIoError({ path, cause }),\n\t\t});\n\t\tconst decoded = yield* decodeJsonText(RosterDocumentSchema, raw, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) => new RosterCorruptError({ path, raw, cause: issue.cause ?? issue }),\n\t\t});\n\t\treturn decoded;\n\t}).pipe(Effect.withSpan('cross-process.roster.read'));\n\n/** Atomic write: route through the canonical sync primitive. The\n * roster's mutations are all under `stack.lock`, so the non-yielding\n * sync surface is correct here — and it shares ONE owner of the\n * tempfile dance with state-store, cache, and manifest. */\nconst atomicWriteRoster = (path: string, doc: RosterDocument): Effect.Effect<void, RosterIoError> =>\n\tEffect.try({\n\t\ttry: () => atomicWriteJsonSync(path, doc),\n\t\tcatch: (cause) => new RosterIoError({ path, cause }),\n\t});\n\n// -----------------------------------------------------------------------------\n// Sweep\n// -----------------------------------------------------------------------------\n\n/** Walk the roster's holders and drop those that fail the liveness\n * test AND whose heartbeats are older than `staleAfterMillis`.\n *\n * Architecture § Claim protocol step 3 — \"Holders whose\n * `heartbeatAt` is older than 3× the heartbeat interval AND who fail\n * the PID liveness check are evicted.\"\n *\n * A live holder with a stale heartbeat is NOT evicted — heartbeat\n * staleness alone is allowed (slow peer). The conjunction matters. */\nexport const sweepStaleHolders = Effect.fn('cross-process.roster.sweep')(function* (\n\tdoc: RosterDocument,\n\tpolicy: RosterSweepPolicy = DEFAULT_SWEEP_POLICY,\n\tnow: number = Date.now(),\n) {\n\tconst survivors: RosterHolder[] = [];\n\tconst evicted: RosterHolder[] = [];\n\tfor (const holder of doc.holders) {\n\t\tconst heartbeatStale = now - holder.heartbeatAt > policy.staleAfterMillis;\n\t\tif (!heartbeatStale) {\n\t\t\tsurvivors.push(holder);\n\t\t\tcontinue;\n\t\t}\n\t\tconst liveness = yield* checkHolderLiveness(holder).pipe(\n\t\t\tEffect.catch(() => Effect.succeed('alive' as const)),\n\t\t);\n\t\tif (liveness === 'dead') {\n\t\t\tevicted.push(holder);\n\t\t} else {\n\t\t\tsurvivors.push(holder);\n\t\t}\n\t}\n\treturn {\n\t\tswept: { version: doc.version, holders: survivors } satisfies RosterDocument,\n\t\tevicted: evicted as ReadonlyArray<RosterHolder>,\n\t};\n});\n\n// -----------------------------------------------------------------------------\n// Claim / release / heartbeat\n// -----------------------------------------------------------------------------\n\n/** Outcome of a claim: the holder this process registered, the swept\n * document, and whether this process is now the sole holder. */\nexport interface ClaimResult {\n\treadonly self: RosterHolder;\n\treadonly roster: RosterDocument;\n\treadonly evicted: ReadonlyArray<RosterHolder>;\n\treadonly soleHolder: boolean;\n}\n\n/** Outcome of a release: the swept document and whether this process\n * was the last leaver (no peers remain after removal). Architecture §\n * Release protocol step 4. */\nexport interface ReleaseResult {\n\treadonly roster: RosterDocument;\n\treadonly lastLeaver: boolean;\n}\n\ninterface RosterPaths {\n\treadonly stackLockFile: string;\n\treadonly rosterFile: string;\n}\n\nconst withStackLock = <A, E, R>(\n\tpaths: RosterPaths,\n\tbody: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E | import('./stack-lock.ts').StackLockError, R> =>\n\tEffect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tyield* acquireStackLock(paths.stackLockFile);\n\t\t\treturn yield* body;\n\t\t}),\n\t);\n\n/**\n * Claim protocol — architecture § Claim protocol.\n *\n * Under the exclusive lock:\n * 1. Read the roster (or initialize empty if missing).\n * 2. Sweep stale holders (PID liveness + heartbeat age).\n * 3. Append this process's entry.\n * 4. Atomic write the result.\n *\n * Returns the swept document, our holder entry, and any evicted peers.\n */\nexport const claim = (\n\tpaths: RosterPaths,\n\tintent: 'normal' | 'snapshot' = 'normal',\n\tpolicy: RosterSweepPolicy = DEFAULT_SWEEP_POLICY,\n): Effect.Effect<ClaimResult, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t\t'devstack.roster.path': paths.rosterFile,\n\t\t\t\t'devstack.roster.intent': intent,\n\t\t\t});\n\t\t\tconst initial = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst { swept, evicted } = yield* sweepStaleHolders(initial, policy);\n\t\t\tconst self: RosterHolder = { ...ownHolder(intent) };\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: [...swept.holders, self],\n\t\t\t};\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t\treturn {\n\t\t\t\tself,\n\t\t\t\troster: next,\n\t\t\t\tevicted,\n\t\t\t\tsoleHolder: swept.holders.length === 0,\n\t\t\t};\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.claim'));\n\n/**\n * Heartbeat protocol — refresh this process's `heartbeatAt`. Under the\n * stack lock to keep mutation atomic. Architecture § Heartbeat\n * protocol.\n */\nexport const heartbeat = (\n\tpaths: RosterPaths,\n\townPid: number = process.pid,\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst now = Date.now();\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tlet touched = false;\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: current.holders.map((h) => {\n\t\t\t\t\tif (h.pid === ownPid && h.hostname === ownHost) {\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t\treturn { ...h, heartbeatAt: now };\n\t\t\t\t\t}\n\t\t\t\t\treturn h;\n\t\t\t\t}),\n\t\t\t};\n\t\t\tif (touched) yield* atomicWriteRoster(paths.rosterFile, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.heartbeat'));\n\n/**\n * Release protocol — architecture § Release protocol.\n *\n * Removes this process's holder. Returns whether this was the\n * last-leaver (caller runs the stop finalizer if so).\n */\nexport const release = (\n\tpaths: RosterPaths,\n\townPid: number = process.pid,\n): Effect.Effect<ReleaseResult, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst remaining = current.holders.filter(\n\t\t\t\t(h) => !(h.pid === ownPid && h.hostname === ownHost),\n\t\t\t);\n\t\t\tconst next: RosterDocument = { version: 1, holders: remaining };\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t\treturn {\n\t\t\t\troster: next,\n\t\t\t\tlastLeaver: remaining.length === 0,\n\t\t\t};\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.release'));\n\n/**\n * Set this process's `intent` (`normal` ↔ `snapshot`) under the\n * exclusive lock. Architecture § Concurrent snapshot step 2 / 5.\n */\nexport const setIntent = (\n\tpaths: RosterPaths,\n\tintent: 'normal' | 'snapshot',\n\townPid: number = process.pid,\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: current.holders.map((h) =>\n\t\t\t\t\th.pid === ownPid && h.hostname === ownHost ? { ...h, intent } : h,\n\t\t\t\t),\n\t\t\t};\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.setIntent'));\n\n/** Background heartbeat fiber. Wakes every `intervalMillis` (default\n * matches `DEFAULT_SWEEP_POLICY.heartbeatIntervalMillis`) and refreshes\n * this process's `heartbeatAt`.\n *\n * Returns an Effect that runs forever in its Scope — the supervisor\n * forks it via `Effect.forkScoped` so it tears down with the stack. */\nexport const heartbeatFiber = (\n\tpaths: RosterPaths,\n\tintervalMillis: number = DEFAULT_SWEEP_POLICY.heartbeatIntervalMillis,\n): Effect.Effect<never> =>\n\tEffect.gen(function* () {\n\t\twhile (true) {\n\t\t\tyield* Effect.sleep(`${intervalMillis} millis`);\n\t\t\tyield* heartbeat(paths).pipe(\n\t\t\t\t// A heartbeat failure is non-fatal — the next peer's sweep\n\t\t\t\t// would otherwise evict us, but the architecture treats this\n\t\t\t\t// as \"the dev didn't get to flush a heartbeat in time;\n\t\t\t\t// recover by retrying next interval.\" Log via Effect's\n\t\t\t\t// logger; do not propagate.\n\t\t\t\tEffect.catch((err) =>\n\t\t\t\t\tEffect.logWarning('roster heartbeat failed').pipe(\n\t\t\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t\t\t[SpanAttr.rosterHeartbeatIntervalMs]: intervalMillis,\n\t\t\t\t\t\t\t[SpanAttr.errorCause]: String(err),\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}).pipe(Effect.withSpan('cross-process.roster.heartbeatFiber'));\n\n// -----------------------------------------------------------------------------\n// Container-claim ledger\n// -----------------------------------------------------------------------------\n//\n// Architecture § Cross-process safety protocol: roster \"Records claimed\n// containers per process. Last-leaver semantics: when a process\n// releases its last claim and confirms no other process holds the\n// container, it may tear down.\"\n//\n// The roster document carries holders only — the per-container claim\n// ledger lives in an EXTENSION file at `<stackRoot>/container-claims.json`\n// so the architecture-mandated `RosterDocument` shape doesn't widen.\n// Same atomicity discipline: mutate under `stack.lock`.\n\nexport interface ContainerClaim {\n\treadonly containerKey: string;\n\treadonly pid: number;\n\treadonly startTime?: number;\n\treadonly hostname: string;\n\treadonly claimedAt: number;\n}\n\nexport interface ContainerClaimDocument {\n\treadonly version: 1;\n\treadonly claims: ReadonlyArray<ContainerClaim>;\n}\n\nconst ContainerClaimSchema = Schema.Struct({\n\tcontainerKey: Schema.String,\n\tpid: Schema.Number,\n\tstartTime: Schema.optional(Schema.Number),\n\thostname: Schema.String,\n\tclaimedAt: Schema.Number,\n});\n\nconst ContainerClaimDocumentSchema = Schema.Struct({\n\tversion: Schema.Literal(1),\n\tclaims: Schema.Array(ContainerClaimSchema),\n});\n\nconst EMPTY_CLAIMS: ContainerClaimDocument = { version: 1, claims: [] };\n\nconst claimsPath = (rosterFile: string): string => `${dirname(rosterFile)}/container-claims.json`;\n\nconst isContainerClaimLive = (claim: ContainerClaim, ownHost: string = nodeHostname()): boolean => {\n\tif (claim.hostname !== ownHost) return true;\n\tif (!isPidAlive(claim.pid)) return false;\n\tif (claim.startTime === undefined) return true;\n\tconst probedStart = processStartTime(claim.pid);\n\tif (probedStart === null) return true;\n\treturn probedStart === claim.startTime;\n};\n\nconst liveContainerClaims = (doc: ContainerClaimDocument): ContainerClaimDocument => ({\n\tversion: 1,\n\tclaims: doc.claims.filter((claim) => isContainerClaimLive(claim)),\n});\n\nconst writeClaims = (\n\tpath: string,\n\tdoc: ContainerClaimDocument,\n): Effect.Effect<void, RosterIoError> =>\n\tEffect.try({\n\t\ttry: () => atomicWriteJsonSync(path, doc),\n\t\tcatch: (cause) => new RosterIoError({ path, cause }),\n\t});\n\n/** Read the container-claim ledger. */\nexport const readClaims = (\n\tpaths: RosterPaths,\n): Effect.Effect<ContainerClaimDocument, RosterError> =>\n\tEffect.gen(function* () {\n\t\tconst path = claimsPath(paths.rosterFile);\n\t\tif (!existsSync(path)) return EMPTY_CLAIMS;\n\t\tconst raw = yield* Effect.try({\n\t\t\ttry: () => readFileSync(path, 'utf8'),\n\t\t\tcatch: (cause) => new RosterIoError({ path, cause }),\n\t\t});\n\t\treturn yield* decodeJsonText(ContainerClaimDocumentSchema, raw, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) => new RosterCorruptError({ path, raw, cause: issue.cause ?? issue }),\n\t\t});\n\t}).pipe(Effect.withSpan('cross-process.roster.readClaims'));\n\n/** Prune stale same-host claims. This is the recovery path for an\n * interrupted process that could not run its scope finalizer. */\nexport const pruneStaleClaims = (\n\tpaths: RosterPaths,\n): Effect.Effect<ContainerClaimDocument, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = claimsPath(paths.rosterFile);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\tconst next = liveContainerClaims(current);\n\t\t\tif (next.claims.length !== current.claims.length) {\n\t\t\t\tyield* writeClaims(path, next);\n\t\t\t}\n\t\t\treturn next;\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.pruneStaleClaims'));\n\n/** Record that this process now claims `containerKey`. No-op if\n * already claimed by this process. */\nexport const addClaim = (\n\tpaths: RosterPaths,\n\tcontainerKey: string,\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = claimsPath(paths.rosterFile);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\tconst live = liveContainerClaims(current);\n\t\t\tconst ownPid = process.pid;\n\t\t\tconst ownStartTime = processStartTime(ownPid) ?? undefined;\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tif (\n\t\t\t\tlive.claims.some(\n\t\t\t\t\t(c) => c.containerKey === containerKey && c.pid === ownPid && c.hostname === ownHost,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tif (live.claims.length !== current.claims.length) yield* writeClaims(path, live);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst next: ContainerClaimDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tclaims: [\n\t\t\t\t\t...live.claims,\n\t\t\t\t\t{\n\t\t\t\t\t\tcontainerKey,\n\t\t\t\t\t\tpid: ownPid,\n\t\t\t\t\t\t...(ownStartTime === undefined ? {} : { startTime: ownStartTime }),\n\t\t\t\t\t\thostname: ownHost,\n\t\t\t\t\t\tclaimedAt: Date.now(),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t\tyield* writeClaims(path, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.addClaim'));\n\n/** Release this process's claim on `containerKey`. Returns whether\n * the container has zero remaining claims AFTER this release — the\n * \"last-leaver\" signal that authorizes a teardown.\n *\n * Architecture § Cross-process safety protocol: \"when a process\n * releases its last claim and confirms no other process holds the\n * container, it may tear down.\" */\nexport const removeClaim = (\n\tpaths: RosterPaths,\n\tcontainerKey: string,\n): Effect.Effect<\n\t{ readonly lastClaimReleased: boolean },\n\tRosterError | import('./stack-lock.ts').StackLockError\n> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = claimsPath(paths.rosterFile);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\tconst live = liveContainerClaims(current);\n\t\t\tconst ownPid = process.pid;\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst remaining = live.claims.filter(\n\t\t\t\t(c) => !(c.containerKey === containerKey && c.pid === ownPid && c.hostname === ownHost),\n\t\t\t);\n\t\t\tconst stillClaimedByPeer = remaining.some((c) => c.containerKey === containerKey);\n\t\t\tconst next: ContainerClaimDocument = { version: 1, claims: remaining };\n\t\t\tyield* writeClaims(path, next);\n\t\t\treturn { lastClaimReleased: !stillClaimedByPeer };\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.removeClaim'));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"roster.mjs","names":["nodeHostname"],"sources":["../../../../src/substrate/runtime/cross-process/roster.ts"],"sourcesContent":["// `roster.json` — authoritative cross-process holder record.\n//\n// Architecture § Cross-process safety protocol § Roster:\n// \"the authoritative cross-process record of which OS processes are\n// currently 'in' this stack.\"\n//\n// Mutated only under `stack.lock`. Schema-validated on every read so a\n// truncated/forward-version write never corrupts a peer's view. Stale\n// entries reaped during step-3 sweep on the next claim.\n\nimport { hostname as nodeHostname } from 'node:os';\n\nimport { Data, Effect, Schema } from 'effect';\n\nimport {\n\tDEFAULT_SWEEP_POLICY,\n\ttype RosterDocument,\n\tRosterDocumentSchema,\n\ttype RosterHolder,\n\ttype RosterSweepPolicy,\n} from '../../cross-process.ts';\nimport { SpanAttr } from '../observability/spans.ts';\nimport { versionedDocSchema } from '../../versioned-doc-schema.ts';\nimport {\n\treadVersionedDocumentSync,\n\twriteVersionedDocumentSync,\n} from '../../versioned-doc-sync.ts';\nimport { selfPid } from './self-pid.ts';\nimport { acquireStackLock } from './stack-lock.ts';\nimport {\n\tisPidAlive,\n\tlayerLivenessProbeScope,\n\tLivenessProbeScope,\n\townHolder,\n\tprocessStartTime,\n} from './liveness.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\nexport class RosterCorruptError extends Data.TaggedError('RosterCorruptError')<{\n\treadonly path: string;\n\treadonly raw: string;\n\treadonly cause: unknown;\n}> {}\n\nexport class RosterIoError extends Data.TaggedError('RosterIoError')<{\n\treadonly path: string;\n\treadonly cause: unknown;\n}> {}\n\nexport type RosterError = RosterCorruptError | RosterIoError;\n\n// -----------------------------------------------------------------------------\n// Read / write\n// -----------------------------------------------------------------------------\n\nconst EMPTY_ROSTER: RosterDocument = { version: 1, holders: [] };\n\nconst ROSTER_DOC_ERRORS = {\n\tmkIo: ({ path, cause }: { path: string; cause: unknown }) => new RosterIoError({ path, cause }),\n\tmkCorrupt: ({ path, raw, cause }: { path: string; raw: string; cause: unknown }) =>\n\t\tnew RosterCorruptError({ path, raw, cause }),\n} as const;\n\n/** Read the roster from disk. Returns the empty document if absent.\n * Tolerates a missing file but NOT a malformed file (a malformed\n * roster surfaces a typed error so callers can decide whether to\n * abandon or rewrite). */\nexport const readRoster = (path: string): Effect.Effect<RosterDocument, RosterError> =>\n\treadVersionedDocumentSync(path, RosterDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_ROSTER).pipe(\n\t\tEffect.withSpan('cross-process.roster.read'),\n\t);\n\n/** Atomic write: route through the canonical sync primitive. The\n * roster's mutations are all under `stack.lock`, so the non-yielding\n * sync surface is correct here — and it shares ONE owner of the\n * tempfile dance with state-store, cache, and manifest. */\nconst atomicWriteRoster = (path: string, doc: RosterDocument): Effect.Effect<void, RosterIoError> =>\n\twriteVersionedDocumentSync(path, doc, ROSTER_DOC_ERRORS);\n\n// -----------------------------------------------------------------------------\n// Sweep\n// -----------------------------------------------------------------------------\n\n/** Walk the roster's holders and drop those that fail the liveness\n * test AND whose heartbeats are older than `staleAfterMillis`.\n *\n * Architecture § Claim protocol step 3 — \"Holders whose\n * `heartbeatAt` is older than 3× the heartbeat interval AND who fail\n * the PID liveness check are evicted.\"\n *\n * A live holder with a stale heartbeat is NOT evicted — heartbeat\n * staleness alone is allowed (slow peer). The conjunction matters. */\nexport const sweepStaleHolders = Effect.fn('cross-process.roster.sweep')(function* (\n\tdoc: RosterDocument,\n\tpolicy: RosterSweepPolicy = DEFAULT_SWEEP_POLICY,\n\tnow: number = Date.now(),\n) {\n\t// Yield a fresh per-sweep liveness scope so the same pid is probed\n\t// AT MOST once across all holders in this pass — two holders sharing\n\t// a pid (corrupted-roster edge case) collapse to one fork. The\n\t// `Layer.provide` below scopes the cache to THIS sweep only.\n\tconst probe = yield* LivenessProbeScope;\n\tconst survivors: RosterHolder[] = [];\n\tconst evicted: RosterHolder[] = [];\n\tconst ownHost = nodeHostname();\n\tfor (const holder of doc.holders) {\n\t\tconst heartbeatStale = now - holder.heartbeatAt > policy.staleAfterMillis;\n\t\tif (!heartbeatStale) {\n\t\t\tsurvivors.push(holder);\n\t\t\tcontinue;\n\t\t}\n\t\tconst liveness = yield* probe\n\t\t\t.probeHolderLiveness(holder, ownHost)\n\t\t\t.pipe(Effect.catch(() => Effect.succeed('alive' as const)));\n\t\tif (liveness === 'dead') {\n\t\t\tevicted.push(holder);\n\t\t} else {\n\t\t\tsurvivors.push(holder);\n\t\t}\n\t}\n\treturn {\n\t\tswept: { version: doc.version, holders: survivors } satisfies RosterDocument,\n\t\tevicted: evicted as ReadonlyArray<RosterHolder>,\n\t};\n}, Effect.provide(layerLivenessProbeScope));\n\n// -----------------------------------------------------------------------------\n// Claim / release / heartbeat\n// -----------------------------------------------------------------------------\n\n/** Outcome of a claim: the holder this process registered, the swept\n * document, and whether this process is now the sole holder. */\nexport interface ClaimResult {\n\treadonly self: RosterHolder;\n\treadonly roster: RosterDocument;\n\treadonly evicted: ReadonlyArray<RosterHolder>;\n\treadonly soleHolder: boolean;\n}\n\n/** Outcome of a release: the swept document and whether this process\n * was the last leaver (no peers remain after removal). Architecture §\n * Release protocol step 4. */\nexport interface ReleaseResult {\n\treadonly roster: RosterDocument;\n\treadonly lastLeaver: boolean;\n}\n\ninterface RosterPaths {\n\treadonly stackLockFile: string;\n\treadonly rosterFile: string;\n\t/** Sibling-file path for the container-claim ledger. Optional\n\t * because the roster's holder mutations (`claim` / `release` /\n\t * `heartbeat` / `setIntent`) never touch the ledger; only the\n\t * `addClaim` / `removeClaim` / `readClaims` / `pruneStaleClaims`\n\t * entry points require it, and they assert presence at the call\n\t * site. Sourced from `StackPathsService.containerClaimsFile` —\n\t * see `substrate/runtime/paths.ts` for the policy rationale\n\t * (closed L0 path resolver). */\n\treadonly containerClaimsFile?: string;\n}\n\n/** Materialize the ledger path from `RosterPaths`. Callers that\n * invoke any of the claim-ledger APIs (`readClaims`,\n * `pruneStaleClaims`, `addClaim`, `removeClaim`) MUST construct\n * `RosterPaths` with `containerClaimsFile` populated from\n * `StackPathsService.containerClaimsFile`. The previous behavior of\n * reconstructing `dirname(rosterFile) + '/container-claims.json'`\n * internally has been removed so nothing in the runtime tree builds\n * cross-process paths outside the substrate path resolver. */\nconst requireClaimsPath = (paths: RosterPaths): string => {\n\tif (paths.containerClaimsFile === undefined) {\n\t\tthrow new Error(\n\t\t\t'cross-process.roster: container-claim API called without `containerClaimsFile` ' +\n\t\t\t\t'on the RosterPaths bundle. Source this from StackPathsService.containerClaimsFile.',\n\t\t);\n\t}\n\treturn paths.containerClaimsFile;\n};\n\n/** Match a roster holder against THIS process's identity.\n *\n * Liveness elsewhere (`checkHolderLiveness`, `isContainerClaimLive`)\n * uses `(pid, hostname, startTime)` — PID alone is insufficient on\n * long-uptime hosts where the kernel can recycle PIDs. The roster\n * mutators (`heartbeat`, `release`, `setIntent`) must apply the same\n * triple match so a recycled-PID peer's entry is never silently\n * overwritten/removed by this process.\n *\n * `startTime` is the FNV-1a hash of `ps -o lstart` (see\n * `liveness.processStartTime`). A `null` probe (process gone, or\n * exotic platform) skips the start-time check — same conservative\n * policy as `isContainerClaimLive`. */\nconst isOwnEntry = (\n\th: RosterHolder,\n\townPid: number,\n\townHost: string,\n\townStartTime: number | null,\n): boolean => {\n\tif (h.pid !== ownPid || h.hostname !== ownHost) return false;\n\t// Either side null → fall back to (pid, hostname). The roster's\n\t// own-entry test must symmetrically accept a null recorded stamp:\n\t// the writer's probe failed (exotic platform / transient `ps`\n\t// error) but the entry IS ours. Mismatching a probed `ownStartTime`\n\t// against a recorded `null` would orphan our own entry — peers\n\t// would then harvest it as \"dead\" on the next sweep.\n\tif (ownStartTime === null || h.startTime === null) return true;\n\treturn h.startTime === ownStartTime;\n};\n\nconst withStackLock = <A, E, R>(\n\tpaths: RosterPaths,\n\tbody: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E | import('./stack-lock.ts').StackLockError, R> =>\n\tEffect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tyield* acquireStackLock(paths.stackLockFile);\n\t\t\treturn yield* body;\n\t\t}),\n\t);\n\n/**\n * Claim protocol — architecture § Claim protocol.\n *\n * Under the exclusive lock:\n * 1. Read the roster (or initialize empty if missing).\n * 2. Sweep stale holders (PID liveness + heartbeat age).\n * 3. Append this process's entry.\n * 4. Atomic write the result.\n *\n * Returns the swept document, our holder entry, and any evicted peers.\n */\nexport const claim = (\n\tpaths: RosterPaths,\n\tintent: 'normal' | 'snapshot' = 'normal',\n\tpolicy: RosterSweepPolicy = DEFAULT_SWEEP_POLICY,\n): Effect.Effect<ClaimResult, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t\t'devstack.roster.path': paths.rosterFile,\n\t\t\t\t'devstack.roster.intent': intent,\n\t\t\t});\n\t\t\tconst initial = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst { swept, evicted } = yield* sweepStaleHolders(initial, policy);\n\t\t\tconst self: RosterHolder = { ...ownHolder(intent) };\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: [...swept.holders, self],\n\t\t\t};\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t\treturn {\n\t\t\t\tself,\n\t\t\t\troster: next,\n\t\t\t\tevicted,\n\t\t\t\tsoleHolder: swept.holders.length === 0,\n\t\t\t};\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.claim'));\n\n/**\n * Heartbeat protocol — refresh this process's `heartbeatAt`. Under the\n * stack lock to keep mutation atomic. Architecture § Heartbeat\n * protocol.\n */\nexport const heartbeat = (\n\tpaths: RosterPaths,\n\townPid: number = selfPid(),\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst now = Date.now();\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst ownStartTime = processStartTime(ownPid);\n\t\t\tlet touched = false;\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: current.holders.map((h) => {\n\t\t\t\t\tif (isOwnEntry(h, ownPid, ownHost, ownStartTime)) {\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t\treturn { ...h, heartbeatAt: now };\n\t\t\t\t\t}\n\t\t\t\t\treturn h;\n\t\t\t\t}),\n\t\t\t};\n\t\t\tif (touched) yield* atomicWriteRoster(paths.rosterFile, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.heartbeat'));\n\n/**\n * Release protocol — architecture § Release protocol.\n *\n * Removes this process's holder. Returns whether this was the\n * last-leaver (caller runs the stop finalizer if so).\n */\nexport const release = (\n\tpaths: RosterPaths,\n\townPid: number = selfPid(),\n): Effect.Effect<ReleaseResult, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst ownStartTime = processStartTime(ownPid);\n\t\t\tconst remaining = current.holders.filter(\n\t\t\t\t(h) => !isOwnEntry(h, ownPid, ownHost, ownStartTime),\n\t\t\t);\n\t\t\tconst next: RosterDocument = { version: 1, holders: remaining };\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t\treturn {\n\t\t\t\troster: next,\n\t\t\t\tlastLeaver: remaining.length === 0,\n\t\t\t};\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.release'));\n\n/**\n * Set this process's `intent` (`normal` ↔ `snapshot`) under the\n * exclusive lock. Architecture § Concurrent snapshot step 2 / 5.\n */\nexport const setIntent = (\n\tpaths: RosterPaths,\n\tintent: 'normal' | 'snapshot',\n\townPid: number = selfPid(),\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst current = yield* readRoster(paths.rosterFile).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_ROSTER)),\n\t\t\t);\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst ownStartTime = processStartTime(ownPid);\n\t\t\tconst next: RosterDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tholders: current.holders.map((h) =>\n\t\t\t\t\tisOwnEntry(h, ownPid, ownHost, ownStartTime) ? { ...h, intent } : h,\n\t\t\t\t),\n\t\t\t};\n\t\t\tyield* atomicWriteRoster(paths.rosterFile, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.setIntent'));\n\n/** Background heartbeat fiber. Wakes every `intervalMillis` (default\n * matches `DEFAULT_SWEEP_POLICY.heartbeatIntervalMillis`) and refreshes\n * this process's `heartbeatAt`.\n *\n * Returns an Effect that runs forever in its Scope — the supervisor\n * forks it via `Effect.forkScoped` so it tears down with the stack. */\nexport const heartbeatFiber = (\n\tpaths: RosterPaths,\n\tintervalMillis: number = DEFAULT_SWEEP_POLICY.heartbeatIntervalMillis,\n): Effect.Effect<never> =>\n\tEffect.gen(function* () {\n\t\twhile (true) {\n\t\t\tyield* Effect.sleep(`${intervalMillis} millis`);\n\t\t\tyield* heartbeat(paths).pipe(\n\t\t\t\t// A heartbeat failure is non-fatal — the next peer's sweep\n\t\t\t\t// would otherwise evict us, but the architecture treats this\n\t\t\t\t// as \"the dev didn't get to flush a heartbeat in time;\n\t\t\t\t// recover by retrying next interval.\" Log via Effect's\n\t\t\t\t// logger; do not propagate.\n\t\t\t\tEffect.catch((err) =>\n\t\t\t\t\tEffect.logWarning('roster heartbeat failed').pipe(\n\t\t\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t\t\t[SpanAttr.rosterHeartbeatIntervalMs]: intervalMillis,\n\t\t\t\t\t\t\t[SpanAttr.errorCause]: String(err),\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}).pipe(Effect.withSpan('cross-process.roster.heartbeatFiber'));\n\n// -----------------------------------------------------------------------------\n// Container-claim ledger\n// -----------------------------------------------------------------------------\n//\n// Architecture § Cross-process safety protocol: roster \"Records claimed\n// containers per process. Last-leaver semantics: when a process\n// releases its last claim and confirms no other process holds the\n// container, it may tear down.\"\n//\n// The roster document carries holders only — the per-container claim\n// ledger lives in an EXTENSION file at `<stackRoot>/container-claims.json`\n// so the architecture-mandated `RosterDocument` shape doesn't widen.\n// Same atomicity discipline: mutate under `stack.lock`.\n\nexport interface ContainerClaim {\n\treadonly containerKey: string;\n\treadonly pid: number;\n\treadonly startTime?: number;\n\treadonly hostname: string;\n\treadonly claimedAt: number;\n}\n\nexport interface ContainerClaimDocument {\n\treadonly version: 1;\n\treadonly claims: ReadonlyArray<ContainerClaim>;\n}\n\nconst ContainerClaimSchema = Schema.Struct({\n\tcontainerKey: Schema.String,\n\tpid: Schema.Number,\n\tstartTime: Schema.optional(Schema.Number),\n\thostname: Schema.String,\n\tclaimedAt: Schema.Number,\n});\n\nconst ContainerClaimDocumentSchema = versionedDocSchema(1, {\n\tclaims: Schema.Array(ContainerClaimSchema),\n});\n\nconst EMPTY_CLAIMS: ContainerClaimDocument = { version: 1, claims: [] };\n\n/** Match a container claim against THIS process's identity using the\n * same `(pid, hostname, startTime)` triple that `isOwnEntry` /\n * `isContainerClaimLive` apply to roster holders — PID alone is\n * insufficient on long-uptime hosts where the kernel recycles PIDs.\n * A null/undefined start-time on either side falls back to\n * `(pid, hostname)`, matching the conservative policy elsewhere\n * (avoids orphaning our own entry when the probe failed). */\nconst isOwnContainerClaim = (\n\tc: ContainerClaim,\n\tcontainerKey: string,\n\townPid: number,\n\townHost: string,\n\townStartTime: number | undefined,\n): boolean => {\n\tif (c.containerKey !== containerKey || c.pid !== ownPid || c.hostname !== ownHost) return false;\n\tif (ownStartTime === undefined || c.startTime === undefined) return true;\n\treturn c.startTime === ownStartTime;\n};\n\nconst isContainerClaimLive = (\n\tclaim: ContainerClaim,\n\tprobeStartTime: (pid: number) => number | null,\n\townHost: string = nodeHostname(),\n): boolean => {\n\tif (claim.hostname !== ownHost) return true;\n\tif (!isPidAlive(claim.pid)) return false;\n\tif (claim.startTime === undefined) return true;\n\tconst probedStart = probeStartTime(claim.pid);\n\tif (probedStart === null) return true;\n\treturn probedStart === claim.startTime;\n};\n\n/** Effect-flavored filter that yields a fresh `LivenessProbeScope` so\n * the same pid is probed at most once across this pass — even when\n * multiple claims by the same pid sit in the ledger. The scope's\n * cache is private to this call (provided via `Effect.provide`). */\nconst liveContainerClaims = (\n\tdoc: ContainerClaimDocument,\n): Effect.Effect<ContainerClaimDocument> =>\n\tEffect.gen(function* () {\n\t\tconst probe = yield* LivenessProbeScope;\n\t\tconst ownHost = nodeHostname();\n\t\tconst filtered: ContainerClaimDocument = {\n\t\t\tversion: 1,\n\t\t\tclaims: doc.claims.filter((claim) =>\n\t\t\t\tisContainerClaimLive(claim, probe.probeStartTime, ownHost),\n\t\t\t),\n\t\t};\n\t\treturn filtered;\n\t}).pipe(Effect.provide(layerLivenessProbeScope));\n\nconst writeClaims = (\n\tpath: string,\n\tdoc: ContainerClaimDocument,\n): Effect.Effect<void, RosterIoError> => writeVersionedDocumentSync(path, doc, ROSTER_DOC_ERRORS);\n\n/** Read the container-claim ledger. */\nexport const readClaims = (\n\tpaths: RosterPaths,\n): Effect.Effect<ContainerClaimDocument, RosterError> =>\n\tEffect.suspend(() =>\n\t\treadVersionedDocumentSync(\n\t\t\trequireClaimsPath(paths),\n\t\t\tContainerClaimDocumentSchema,\n\t\t\tROSTER_DOC_ERRORS,\n\t\t\tEMPTY_CLAIMS,\n\t\t),\n\t).pipe(Effect.withSpan('cross-process.roster.readClaims'));\n\n/** Prune stale same-host claims. This is the recovery path for an\n * interrupted process that could not run its scope finalizer. */\nexport const pruneStaleClaims = (\n\tpaths: RosterPaths,\n): Effect.Effect<ContainerClaimDocument, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = requireClaimsPath(paths);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\tconst next = yield* liveContainerClaims(current);\n\t\t\tif (next.claims.length !== current.claims.length) {\n\t\t\t\tyield* writeClaims(path, next);\n\t\t\t}\n\t\t\treturn next;\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.pruneStaleClaims'));\n\n/** Record that this process now claims `containerKey`. No-op if\n * already claimed by this process. */\nexport const addClaim = (\n\tpaths: RosterPaths,\n\tcontainerKey: string,\n): Effect.Effect<void, RosterError | import('./stack-lock.ts').StackLockError> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = requireClaimsPath(paths);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\t// `liveContainerClaims` pre-filter must run BEFORE the own-claim\n\t\t\t// scan below: it drops dead peers so a recycled-PID peer can't\n\t\t\t// shadow our own (pid, hostname, startTime) identity. Ordering\n\t\t\t// here is load-bearing.\n\t\t\tconst live = yield* liveContainerClaims(current);\n\t\t\tconst ownPid = selfPid();\n\t\t\tconst ownStartTime = processStartTime(ownPid) ?? undefined;\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tif (\n\t\t\t\tlive.claims.some((c) =>\n\t\t\t\t\tisOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime),\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tif (live.claims.length !== current.claims.length) yield* writeClaims(path, live);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst next: ContainerClaimDocument = {\n\t\t\t\tversion: 1,\n\t\t\t\tclaims: [\n\t\t\t\t\t...live.claims,\n\t\t\t\t\t{\n\t\t\t\t\t\tcontainerKey,\n\t\t\t\t\t\tpid: ownPid,\n\t\t\t\t\t\t...(ownStartTime === undefined ? {} : { startTime: ownStartTime }),\n\t\t\t\t\t\thostname: ownHost,\n\t\t\t\t\t\tclaimedAt: Date.now(),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t\tyield* writeClaims(path, next);\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.addClaim'));\n\n/** Release this process's claim on `containerKey`. Returns whether\n * the container has zero remaining claims AFTER this release — the\n * \"last-leaver\" signal that authorizes a teardown.\n *\n * Architecture § Cross-process safety protocol: \"when a process\n * releases its last claim and confirms no other process holds the\n * container, it may tear down.\" */\nexport const removeClaim = (\n\tpaths: RosterPaths,\n\tcontainerKey: string,\n): Effect.Effect<\n\t{ readonly lastClaimReleased: boolean },\n\tRosterError | import('./stack-lock.ts').StackLockError\n> =>\n\twithStackLock(\n\t\tpaths,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = requireClaimsPath(paths);\n\t\t\tconst current = yield* readClaims(paths).pipe(\n\t\t\t\tEffect.catchTag('RosterCorruptError', () => Effect.succeed(EMPTY_CLAIMS)),\n\t\t\t);\n\t\t\t// `liveContainerClaims` pre-filter must run BEFORE the own-claim\n\t\t\t// scan below: it drops dead peers so a recycled-PID peer can't\n\t\t\t// shadow our own (pid, hostname, startTime) identity. Ordering\n\t\t\t// here is load-bearing.\n\t\t\tconst live = yield* liveContainerClaims(current);\n\t\t\tconst ownPid = selfPid();\n\t\t\tconst ownStartTime = processStartTime(ownPid) ?? undefined;\n\t\t\tconst ownHost = nodeHostname();\n\t\t\tconst remaining = live.claims.filter(\n\t\t\t\t(c) => !isOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime),\n\t\t\t);\n\t\t\tconst stillClaimedByPeer = remaining.some((c) => c.containerKey === containerKey);\n\t\t\tconst next: ContainerClaimDocument = { version: 1, claims: remaining };\n\t\t\tyield* writeClaims(path, next);\n\t\t\treturn { lastClaimReleased: !stillClaimedByPeer };\n\t\t}),\n\t).pipe(Effect.withSpan('cross-process.roster.removeClaim'));\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/substrate/runtime/cross-process/self-pid.ts
|
|
2
|
+
/** This process's PID. Module-level indirection so cross-process
|
|
3
|
+
* protocol modules read through one named entry point. */
|
|
4
|
+
const selfPid = () => process.pid;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { selfPid };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=self-pid.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-pid.mjs","names":[],"sources":["../../../../src/substrate/runtime/cross-process/self-pid.ts"],"sourcesContent":["// Own-process PID accessor.\n//\n// Centralizes `process.pid` reads so:\n// 1. The cross-process protocol modules (roster, snapshot-reservation,\n// liveness, atomic-write tempfiles, port-broker owner ids) have a\n// single named entry point instead of scattering the global read.\n// 2. Tests that want to simulate a peer can swap this module in a\n// vitest mock without monkey-patching `process` globally.\n//\n// The implementation is intentionally trivial — `() => process.pid` —\n// and lives in its own module so call sites read with a meaningful name\n// rather than a raw global.\n\n/** This process's PID. Module-level indirection so cross-process\n * protocol modules read through one named entry point. */\nexport const selfPid = (): number => process.pid;\n"],"mappings":";;;AAeA,MAAa,gBAAwB,QAAQ"}
|
|
@@ -1,25 +1,22 @@
|
|
|
1
|
+
import { SpanAttr } from "../observability/spans.mjs";
|
|
2
|
+
import { selfPid } from "./self-pid.mjs";
|
|
1
3
|
import { SnapshotReservationSchema } from "../../cross-process.mjs";
|
|
4
|
+
import { parseVersionedDocumentBodyOrNull } from "../../versioned-doc-sync.mjs";
|
|
2
5
|
import { checkHolderLiveness } from "./liveness.mjs";
|
|
3
|
-
import {
|
|
6
|
+
import { reclaimUnparseableStaleFile } from "./reclaim-stale-file.mjs";
|
|
4
7
|
import { Data, Effect } from "effect";
|
|
5
8
|
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { hostname } from "node:os";
|
|
6
10
|
//#region src/substrate/runtime/cross-process/snapshot-reservation.ts
|
|
7
11
|
var SnapshotReservationHeldError = class extends Data.TaggedError("SnapshotReservationHeldError") {};
|
|
8
12
|
var SnapshotReservationIoError = class extends Data.TaggedError("SnapshotReservationIoError") {};
|
|
9
|
-
const parseReservation = (raw) =>
|
|
10
|
-
try {
|
|
11
|
-
return decodeJsonTextSync(SnapshotReservationSchema, raw, {
|
|
12
|
-
source: "snapshot.reservation",
|
|
13
|
-
mkError: (issue) => issue
|
|
14
|
-
});
|
|
15
|
-
} catch {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
13
|
+
const parseReservation = (raw) => parseVersionedDocumentBodyOrNull(raw, SnapshotReservationSchema, "snapshot.reservation");
|
|
19
14
|
const ownReservation = (startTime) => ({
|
|
20
|
-
|
|
15
|
+
version: 1,
|
|
16
|
+
creatorPid: selfPid(),
|
|
21
17
|
creatorStartTime: startTime,
|
|
22
|
-
createdAt: Date.now()
|
|
18
|
+
createdAt: Date.now(),
|
|
19
|
+
hostname: hostname()
|
|
23
20
|
});
|
|
24
21
|
/**
|
|
25
22
|
* Sweep an orphaned reservation. If the file exists AND its creator is
|
|
@@ -33,7 +30,7 @@ const ownReservation = (startTime) => ({
|
|
|
33
30
|
* reservation's creator entry is dead) and unlinks it."
|
|
34
31
|
*/
|
|
35
32
|
const sweepOrphan = (path) => Effect.gen(function* () {
|
|
36
|
-
yield* Effect.annotateCurrentSpan({
|
|
33
|
+
yield* Effect.annotateCurrentSpan({ [SpanAttr.snapshotReservationPath]: path });
|
|
37
34
|
if (!existsSync(path)) return { swept: false };
|
|
38
35
|
const reservation = parseReservation(yield* Effect.try({
|
|
39
36
|
try: () => readFileSync(path, "utf8"),
|
|
@@ -42,24 +39,23 @@ const sweepOrphan = (path) => Effect.gen(function* () {
|
|
|
42
39
|
cause
|
|
43
40
|
})
|
|
44
41
|
}));
|
|
45
|
-
if (reservation === null) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
42
|
+
if (reservation === null) return { swept: (yield* Effect.try({
|
|
43
|
+
try: () => reclaimUnparseableStaleFile(path, parseReservation),
|
|
44
|
+
catch: (cause) => new SnapshotReservationIoError({
|
|
45
|
+
path,
|
|
46
|
+
cause
|
|
47
|
+
})
|
|
48
|
+
})) === "reclaimed" };
|
|
49
|
+
const ownHost = hostname();
|
|
50
|
+
if (reservation.hostname !== ownHost) return { swept: false };
|
|
55
51
|
if ((yield* checkHolderLiveness({
|
|
56
52
|
pid: reservation.creatorPid,
|
|
57
53
|
startTime: reservation.creatorStartTime,
|
|
58
|
-
hostname:
|
|
54
|
+
hostname: reservation.hostname,
|
|
59
55
|
claimedAt: reservation.createdAt,
|
|
60
56
|
heartbeatAt: reservation.createdAt,
|
|
61
57
|
intent: "snapshot"
|
|
62
|
-
},
|
|
58
|
+
}, ownHost).pipe(Effect.catch(() => Effect.succeed("alive")))) === "alive") return { swept: false };
|
|
63
59
|
yield* Effect.try({
|
|
64
60
|
try: () => unlinkSync(path),
|
|
65
61
|
catch: (cause) => new SnapshotReservationIoError({
|
|
@@ -82,7 +78,7 @@ const sweepOrphan = (path) => Effect.gen(function* () {
|
|
|
82
78
|
* the orphan sweep's pid-startTime check resolves identically.
|
|
83
79
|
*/
|
|
84
80
|
const acquireReservation = (path, startTime) => Effect.gen(function* () {
|
|
85
|
-
yield* Effect.annotateCurrentSpan({
|
|
81
|
+
yield* Effect.annotateCurrentSpan({ [SpanAttr.snapshotReservationPath]: path });
|
|
86
82
|
yield* sweepOrphan(path);
|
|
87
83
|
const body = ownReservation(startTime);
|
|
88
84
|
yield* Effect.try({
|
|
@@ -112,6 +108,6 @@ const acquireReservation = (path, startTime) => Effect.gen(function* () {
|
|
|
112
108
|
return body;
|
|
113
109
|
}).pipe(Effect.withSpan("cross-process.snapshot-reservation.acquire"));
|
|
114
110
|
//#endregion
|
|
115
|
-
export { acquireReservation };
|
|
111
|
+
export { SnapshotReservationHeldError, SnapshotReservationIoError, acquireReservation, sweepOrphan };
|
|
116
112
|
|
|
117
113
|
//# sourceMappingURL=snapshot-reservation.mjs.map
|