@mysten-incubation/devstack 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dashboard-ui/assets/grpc-a4usE3Nk.js +3 -0
- package/dashboard-ui/assets/index-B82Bh84P.css +1 -0
- package/dashboard-ui/assets/index-CRYJ4pru.js +1277 -0
- package/dashboard-ui/index.html +2 -2
- package/dist/api/define-capabilities.d.mts +1 -20
- package/dist/api/define-capabilities.mjs +15 -22
- package/dist/api/define-capabilities.mjs.map +1 -1
- package/dist/api/define-devstack-with.d.mts +7 -1
- package/dist/api/define-devstack-with.mjs.map +1 -1
- package/dist/api/define-devstack.mjs.map +1 -1
- package/dist/api/define-plugin.d.mts +2 -1
- package/dist/api/inference-network.d.mts +31 -17
- package/dist/api/inference-network.mjs +40 -43
- package/dist/api/inference-network.mjs.map +1 -1
- package/dist/api/load-config.mjs +88 -0
- package/dist/api/load-config.mjs.map +1 -0
- package/dist/api/mode-narrowed-factory.d.mts +1 -1
- package/dist/api/mode-narrowed-factory.mjs.map +1 -1
- package/dist/api/run-stack-internal.mjs +202 -0
- package/dist/api/run-stack-internal.mjs.map +1 -0
- package/dist/api/run-stack.d.mts +82 -31
- package/dist/api/run-stack.mjs +22 -141
- package/dist/api/run-stack.mjs.map +1 -1
- package/dist/build-integrations/dapp-kit/index.d.mts +61 -0
- package/dist/build-integrations/dapp-kit/index.mjs +58 -0
- package/dist/build-integrations/dapp-kit/index.mjs.map +1 -0
- package/dist/build-integrations/playwright/config.d.mts +1 -30
- package/dist/build-integrations/playwright/config.mjs +1 -27
- package/dist/build-integrations/playwright/config.mjs.map +1 -1
- package/dist/build-integrations/playwright/errors.d.mts +4 -7
- package/dist/build-integrations/playwright/errors.mjs.map +1 -1
- package/dist/build-integrations/playwright/global-setup.d.mts +42 -71
- package/dist/build-integrations/playwright/global-setup.mjs +142 -218
- package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
- package/dist/build-integrations/playwright/index.d.mts +2 -2
- package/dist/build-integrations/playwright/index.mjs +2 -2
- package/dist/build-integrations/playwright/stack-context.d.mts +3 -1
- package/dist/build-integrations/playwright/stack-context.mjs +4 -2
- package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
- package/dist/build-integrations/playwright/wallet-context.mjs +44 -19
- package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
- package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -1
- package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
- package/dist/build-integrations/runtime/discover.d.mts +6 -4
- package/dist/build-integrations/runtime/discover.mjs +15 -4
- package/dist/build-integrations/runtime/discover.mjs.map +1 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
- package/dist/build-integrations/runtime/errors.mjs.map +1 -1
- package/dist/build-integrations/runtime/read-stack-context.mjs +3 -3
- package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +23 -7
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs +25 -12
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -1
- package/dist/build-integrations/runtime/stack-context.d.mts +2 -2
- package/dist/build-integrations/vite/index.d.mts +84 -19
- package/dist/build-integrations/vite/index.mjs +203 -28
- package/dist/build-integrations/vite/index.mjs.map +1 -1
- package/dist/build-integrations/vitest/config.d.mts +20 -2
- package/dist/build-integrations/vitest/config.mjs +16 -4
- package/dist/build-integrations/vitest/config.mjs.map +1 -1
- package/dist/build-integrations/vitest/env.d.mts +18 -10
- package/dist/build-integrations/vitest/env.mjs +11 -10
- package/dist/build-integrations/vitest/env.mjs.map +1 -1
- package/dist/build-integrations/vitest/errors.d.mts +7 -11
- package/dist/build-integrations/vitest/errors.mjs +3 -4
- package/dist/build-integrations/vitest/errors.mjs.map +1 -1
- package/dist/build-integrations/vitest/global-setup.d.mts +35 -0
- package/dist/build-integrations/vitest/global-setup.mjs +156 -0
- package/dist/build-integrations/vitest/global-setup.mjs.map +1 -0
- package/dist/build-integrations/vitest/index.d.mts +3 -2
- package/dist/build-integrations/vitest/index.mjs +3 -2
- package/dist/build-integrations/vitest/setup.mjs +2 -2
- package/dist/build-integrations/vitest/setup.mjs.map +1 -1
- package/dist/build-integrations/vitest/stack-context.d.mts +5 -3
- package/dist/build-integrations/vitest/stack-context.mjs +3 -2
- package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
- package/dist/cli/bail.mjs.map +1 -1
- package/dist/cli/doctor-probes.mjs +1 -3
- package/dist/cli/doctor-probes.mjs.map +1 -1
- package/dist/cli/main.d.mts +23 -1
- package/dist/cli/main.mjs +63 -8
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/prune-direct.mjs.map +1 -1
- package/dist/cli/snapshot-reader.mjs.map +1 -1
- package/dist/cli/up-lifecycle.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +28 -21
- package/dist/cli/wirings/apply.mjs.map +1 -1
- package/dist/cli/wirings/codegen.mjs +108 -0
- package/dist/cli/wirings/codegen.mjs.map +1 -0
- package/dist/cli/wirings/config-loader.mjs +12 -62
- package/dist/cli/wirings/config-loader.mjs.map +1 -1
- package/dist/cli/wirings/dump-ids.mjs +86 -0
- package/dist/cli/wirings/dump-ids.mjs.map +1 -0
- package/dist/cli/wirings/engine-command.mjs +1 -1
- package/dist/cli/wirings/engine-command.mjs.map +1 -1
- package/dist/cli/wirings/identity.mjs +2 -2
- package/dist/cli/wirings/identity.mjs.map +1 -1
- package/dist/cli/wirings/provide-file-system.mjs.map +1 -1
- package/dist/cli/wirings/read-devstack-version.mjs +17 -0
- package/dist/cli/wirings/read-devstack-version.mjs.map +1 -0
- package/dist/cli/wirings/snapshot.mjs +27 -34
- package/dist/cli/wirings/snapshot.mjs.map +1 -1
- package/dist/cli/wirings/up-ipc.mjs +147 -0
- package/dist/cli/wirings/up-ipc.mjs.map +1 -0
- package/dist/cli/wirings/up.mjs +218 -198
- package/dist/cli/wirings/up.mjs.map +1 -1
- package/dist/cli/wirings/wipe.mjs +1 -1
- package/dist/cli/wirings/wipe.mjs.map +1 -1
- package/dist/contracts/chain-probe.d.mts +1 -1
- package/dist/contracts/chain-probe.mjs +1 -1
- package/dist/contracts/chain-probe.mjs.map +1 -1
- package/dist/contracts/codegenable.d.mts +60 -6
- package/dist/contracts/codegenable.mjs +28 -0
- package/dist/contracts/codegenable.mjs.map +1 -0
- package/dist/contracts/config-bindings.mjs +162 -0
- package/dist/contracts/config-bindings.mjs.map +1 -0
- package/dist/contracts/container-runtime.d.mts +23 -17
- package/dist/contracts/plugin-expander.mjs.map +1 -1
- package/dist/contracts/snapshotable.d.mts +5 -7
- package/dist/contracts/wallet-protocol.d.mts +2 -5
- package/dist/contracts/wallet-protocol.mjs +2 -5
- package/dist/contracts/wallet-protocol.mjs.map +1 -1
- package/dist/index.d.mts +21 -26
- package/dist/index.mjs +8 -9
- package/dist/orchestrators/boot.d.mts +28 -0
- package/dist/orchestrators/boot.mjs +440 -0
- package/dist/orchestrators/boot.mjs.map +1 -0
- package/dist/orchestrators/codegen/bindings.d.mts +13 -1
- package/dist/orchestrators/codegen/bindings.mjs +23 -9
- package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
- package/dist/orchestrators/codegen/config-runtime.mjs +208 -0
- package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -0
- package/dist/orchestrators/codegen/emit.mjs +1 -1
- package/dist/orchestrators/codegen/emit.mjs.map +1 -1
- package/dist/orchestrators/codegen/errors.mjs +21 -1
- package/dist/orchestrators/codegen/errors.mjs.map +1 -1
- package/dist/orchestrators/codegen/format.mjs +2 -0
- package/dist/orchestrators/codegen/format.mjs.map +1 -1
- package/dist/orchestrators/codegen/gitignore.mjs +30 -11
- package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
- package/dist/orchestrators/codegen/id-config.mjs +107 -0
- package/dist/orchestrators/codegen/id-config.mjs.map +1 -0
- package/dist/orchestrators/codegen/output-location.mjs +23 -32
- package/dist/orchestrators/codegen/output-location.mjs.map +1 -1
- package/dist/orchestrators/codegen/paths.mjs +8 -1
- package/dist/orchestrators/codegen/paths.mjs.map +1 -1
- package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
- package/dist/orchestrators/codegen/service.mjs +286 -58
- package/dist/orchestrators/codegen/service.mjs.map +1 -1
- package/dist/orchestrators/layers.mjs +31 -0
- package/dist/orchestrators/layers.mjs.map +1 -0
- package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -1
- package/dist/orchestrators/lifecycle-prune/index.mjs +8 -10
- package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -1
- package/dist/orchestrators/network-options.d.mts +52 -0
- package/dist/orchestrators/network-options.mjs +43 -0
- package/dist/orchestrators/network-options.mjs.map +1 -0
- package/dist/orchestrators/router/cleanup.mjs.map +1 -1
- package/dist/orchestrators/router/cors.mjs.map +1 -1
- package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
- package/dist/orchestrators/router/errors.mjs.map +1 -1
- package/dist/orchestrators/router/file-provider.mjs.map +1 -1
- package/dist/orchestrators/router/hostname.mjs.map +1 -1
- package/dist/orchestrators/router/profile.mjs +1 -1
- package/dist/orchestrators/router/profile.mjs.map +1 -1
- package/dist/orchestrators/router/service.d.mts +2 -2
- package/dist/orchestrators/router/service.mjs +12 -27
- package/dist/orchestrators/router/service.mjs.map +1 -1
- package/dist/orchestrators/router/traefik-container.mjs +1 -1
- package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
- package/dist/orchestrators/snapshot/capture.mjs +171 -195
- package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
- package/dist/orchestrators/snapshot/descriptor.d.mts +1 -1
- package/dist/orchestrators/snapshot/descriptor.mjs +53 -11
- package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
- package/dist/orchestrators/snapshot/identity-guard.mjs +3 -5
- package/dist/orchestrators/snapshot/identity-guard.mjs.map +1 -1
- package/dist/orchestrators/snapshot/image-bundle-tags.mjs +32 -101
- package/dist/orchestrators/snapshot/image-bundle-tags.mjs.map +1 -1
- package/dist/orchestrators/snapshot/index.mjs +1 -4
- package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
- package/dist/orchestrators/snapshot/interrupted-restore.mjs +116 -0
- package/dist/orchestrators/snapshot/interrupted-restore.mjs.map +1 -0
- package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -1
- package/dist/orchestrators/snapshot/prune.mjs +40 -31
- package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
- package/dist/orchestrators/snapshot/restore.mjs +221 -196
- package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
- package/dist/orchestrators/snapshot/service.mjs +72 -62
- package/dist/orchestrators/snapshot/service.mjs.map +1 -1
- package/dist/orchestrators/snapshot/wipe.mjs +67 -56
- package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
- package/dist/plugins/account/codegen.mjs +3 -0
- package/dist/plugins/account/codegen.mjs.map +1 -1
- package/dist/plugins/account/errors.d.mts +2 -2
- package/dist/plugins/account/errors.mjs +1 -4
- package/dist/plugins/account/errors.mjs.map +1 -1
- package/dist/plugins/account/funding.mjs +3 -27
- package/dist/plugins/account/funding.mjs.map +1 -1
- package/dist/plugins/account/index.d.mts +3 -8
- package/dist/plugins/account/index.mjs +71 -35
- package/dist/plugins/account/index.mjs.map +1 -1
- package/dist/plugins/account/keypair.d.mts +8 -7
- package/dist/plugins/account/keypair.mjs +5 -18
- package/dist/plugins/account/keypair.mjs.map +1 -1
- package/dist/plugins/account/lease.mjs.map +1 -1
- package/dist/plugins/account/registry.d.mts +1 -46
- package/dist/plugins/account/registry.mjs.map +1 -1
- package/dist/plugins/account/service.d.mts +43 -34
- package/dist/plugins/account/service.mjs +9 -54
- package/dist/plugins/account/service.mjs.map +1 -1
- package/dist/plugins/account/snapshot.mjs.map +1 -1
- package/dist/plugins/account/variants/ephemeral.mjs.map +1 -1
- package/dist/plugins/account/variants/impersonate.mjs.map +1 -1
- package/dist/plugins/account/variants/signer.mjs.map +1 -1
- package/dist/plugins/action/discriminator.mjs.map +1 -1
- package/dist/plugins/action/errors.mjs +1 -4
- package/dist/plugins/action/errors.mjs.map +1 -1
- package/dist/plugins/action/execute.mjs +3 -4
- package/dist/plugins/action/execute.mjs.map +1 -1
- package/dist/plugins/action/index.d.mts +2 -2
- package/dist/plugins/action/index.mjs +7 -11
- package/dist/plugins/action/index.mjs.map +1 -1
- package/dist/plugins/action/service.d.mts +5 -7
- package/dist/plugins/action/service.mjs +2 -16
- package/dist/plugins/action/service.mjs.map +1 -1
- package/dist/plugins/coin/address-resolution.mjs.map +1 -1
- package/dist/plugins/coin/codegen.mjs +97 -16
- package/dist/plugins/coin/codegen.mjs.map +1 -1
- package/dist/plugins/coin/discovery.mjs.map +1 -1
- package/dist/plugins/coin/errors.mjs +1 -4
- package/dist/plugins/coin/errors.mjs.map +1 -1
- package/dist/plugins/coin/index.d.mts +4 -34
- package/dist/plugins/coin/index.mjs +47 -28
- package/dist/plugins/coin/index.mjs.map +1 -1
- package/dist/plugins/coin/metadata.d.mts +1 -0
- package/dist/plugins/coin/metadata.mjs +8 -9
- package/dist/plugins/coin/metadata.mjs.map +1 -1
- package/dist/plugins/coin/mint.mjs +7 -21
- package/dist/plugins/coin/mint.mjs.map +1 -1
- package/dist/plugins/coin/registry.mjs +33 -20
- package/dist/plugins/coin/registry.mjs.map +1 -1
- package/dist/plugins/coin/service.d.mts +1 -1
- package/dist/plugins/coin/service.mjs +1 -8
- package/dist/plugins/coin/service.mjs.map +1 -1
- package/dist/plugins/coin/snapshot.mjs.map +1 -1
- package/dist/plugins/coin/type-strings.mjs.map +1 -1
- package/dist/plugins/dashboard/domain.mjs +106 -167
- package/dist/plugins/dashboard/domain.mjs.map +1 -1
- package/dist/plugins/dashboard/index.mjs +13 -13
- package/dist/plugins/dashboard/index.mjs.map +1 -1
- package/dist/plugins/dashboard/origin-policy.mjs +3 -3
- package/dist/plugins/dashboard/origin-policy.mjs.map +1 -1
- package/dist/plugins/dashboard/routable.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/builder.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/enums.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/root.mjs +23 -44
- package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/types.mjs +37 -91
- package/dist/plugins/dashboard/schema/types.mjs.map +1 -1
- package/dist/plugins/dashboard/schema.mjs.map +1 -1
- package/dist/plugins/dashboard/server.mjs.map +1 -1
- package/dist/plugins/deepbook/codegen.d.mts +12 -11
- package/dist/plugins/deepbook/codegen.mjs +108 -11
- package/dist/plugins/deepbook/codegen.mjs.map +1 -1
- package/dist/plugins/deepbook/deploy.mjs +7 -14
- package/dist/plugins/deepbook/deploy.mjs.map +1 -1
- package/dist/plugins/deepbook/errors.mjs +4 -2
- package/dist/plugins/deepbook/errors.mjs.map +1 -1
- package/dist/plugins/deepbook/faucet-strategy.mjs +4 -10
- package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/deepbook/hash.mjs.map +1 -1
- package/dist/plugins/deepbook/index.d.mts +38 -34
- package/dist/plugins/deepbook/index.mjs +133 -64
- package/dist/plugins/deepbook/index.mjs.map +1 -1
- package/dist/plugins/deepbook/plugin-key.mjs.map +1 -1
- package/dist/plugins/deepbook/pyth/index.mjs +4 -8
- package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
- package/dist/plugins/deepbook/snapshot.mjs +0 -1
- package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
- package/dist/plugins/deepbook/type-strings.mjs.map +1 -1
- package/dist/plugins/deepbook/types.d.mts +2 -1
- package/dist/plugins/deepbook/types.mjs.map +1 -1
- package/dist/plugins/faucet/dispatcher.d.mts +3 -3
- package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
- package/dist/plugins/faucet/errors.mjs.map +1 -1
- package/dist/plugins/faucet/http.mjs +2 -10
- package/dist/plugins/faucet/http.mjs.map +1 -1
- package/dist/plugins/faucet/index.mjs +0 -1
- package/dist/plugins/faucet/index.mjs.map +1 -1
- package/dist/plugins/host-service/errors.d.mts +1 -3
- package/dist/plugins/host-service/errors.mjs +1 -2
- package/dist/plugins/host-service/errors.mjs.map +1 -1
- package/dist/plugins/host-service/index.d.mts +1 -2
- package/dist/plugins/host-service/index.mjs +11 -11
- package/dist/plugins/host-service/index.mjs.map +1 -1
- package/dist/plugins/host-service/routable.mjs.map +1 -1
- package/dist/plugins/host-service/service.mjs +13 -13
- package/dist/plugins/host-service/service.mjs.map +1 -1
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -1
- package/dist/plugins/internal/codegen-helpers.mjs +2 -0
- package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
- package/dist/plugins/internal/funding-failure-error.mjs.map +1 -1
- package/dist/plugins/{postgres → internal/postgres-sidecar}/connection.mjs +1 -1
- package/dist/plugins/internal/postgres-sidecar/connection.mjs.map +1 -0
- package/dist/plugins/{postgres → internal/postgres-sidecar}/db-ensure.mjs +30 -16
- package/dist/plugins/internal/postgres-sidecar/db-ensure.mjs.map +1 -0
- package/dist/plugins/internal/postgres-sidecar/errors.mjs +21 -0
- package/dist/plugins/internal/postgres-sidecar/errors.mjs.map +1 -0
- package/dist/{substrate/runtime/scoped-multimap → plugins/internal/postgres-sidecar}/index.mjs +1 -0
- package/dist/plugins/internal/postgres-sidecar/service.mjs +121 -0
- package/dist/plugins/internal/postgres-sidecar/service.mjs.map +1 -0
- package/dist/plugins/package/build.mjs +2 -2
- package/dist/plugins/package/build.mjs.map +1 -1
- package/dist/plugins/package/codegen.d.mts +7 -14
- package/dist/plugins/package/codegen.mjs +187 -59
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/dep-resolution.mjs +51 -3
- package/dist/plugins/package/dep-resolution.mjs.map +1 -1
- package/dist/plugins/package/errors.mjs +1 -4
- package/dist/plugins/package/errors.mjs.map +1 -1
- package/dist/plugins/package/git-source.d.mts +18 -0
- package/dist/plugins/package/git-source.mjs +119 -0
- package/dist/plugins/package/git-source.mjs.map +1 -0
- package/dist/plugins/package/index.d.mts +28 -19
- package/dist/plugins/package/index.mjs +107 -32
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/package/mode-known.mjs +2 -2
- package/dist/plugins/package/mode-known.mjs.map +1 -1
- package/dist/plugins/package/mode-local.mjs +18 -35
- package/dist/plugins/package/mode-local.mjs.map +1 -1
- package/dist/plugins/package/publish-executor.mjs +13 -8
- package/dist/plugins/package/publish-executor.mjs.map +1 -1
- package/dist/plugins/package/publish-output.d.mts +1 -11
- package/dist/plugins/package/publish-output.mjs +1 -9
- package/dist/plugins/package/publish-output.mjs.map +1 -1
- package/dist/plugins/package/registry.d.mts +1 -1
- package/dist/plugins/package/registry.mjs +26 -14
- package/dist/plugins/package/registry.mjs.map +1 -1
- package/dist/plugins/package/service.mjs.map +1 -1
- package/dist/plugins/package/snapshot.mjs.map +1 -1
- package/dist/plugins/router-entrypoints.mjs +1 -3
- package/dist/plugins/router-entrypoints.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +1 -2
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +1 -6
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
- package/dist/plugins/seal/codegen.d.mts +1 -5
- package/dist/plugins/seal/codegen.mjs +83 -12
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/config-render.mjs +2 -6
- package/dist/plugins/seal/config-render.mjs.map +1 -1
- package/dist/plugins/seal/deploy.mjs +18 -29
- package/dist/plugins/seal/deploy.mjs.map +1 -1
- package/dist/plugins/seal/errors.d.mts +4 -5
- package/dist/plugins/seal/errors.mjs +1 -8
- package/dist/plugins/seal/errors.mjs.map +1 -1
- package/dist/plugins/seal/index.d.mts +7 -28
- package/dist/plugins/seal/index.mjs +69 -47
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/seal/key-manager.mjs.map +1 -1
- package/dist/plugins/seal/key-server.mjs +1 -6
- package/dist/plugins/seal/key-server.mjs.map +1 -1
- package/dist/plugins/seal/keygen.mjs +2 -3
- package/dist/plugins/seal/keygen.mjs.map +1 -1
- package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
- package/dist/plugins/seal/mode/live.mjs +1 -1
- package/dist/plugins/seal/mode/live.mjs.map +1 -1
- package/dist/plugins/seal/mode/local-keygen.mjs +4 -10
- package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
- package/dist/plugins/seal/plugin-key.mjs.map +1 -1
- package/dist/plugins/seal/registry-publish.mjs.map +1 -1
- package/dist/plugins/seal/routable.mjs.map +1 -1
- package/dist/plugins/seal/service.mjs.map +1 -1
- package/dist/plugins/seal/snapshot.mjs +1 -2
- package/dist/plugins/seal/snapshot.mjs.map +1 -1
- package/dist/plugins/sui/auto-tick.mjs +5 -5
- package/dist/plugins/sui/auto-tick.mjs.map +1 -1
- package/dist/plugins/sui/chain-build-container.mjs +2 -2
- package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
- package/dist/plugins/sui/chain-probe.mjs +4 -4
- package/dist/plugins/sui/chain-probe.mjs.map +1 -1
- package/dist/plugins/sui/codegen.d.mts +9 -7
- package/dist/plugins/sui/codegen.mjs +49 -46
- package/dist/plugins/sui/codegen.mjs.map +1 -1
- package/dist/plugins/sui/errors.d.mts +1 -1
- package/dist/plugins/sui/errors.mjs +1 -9
- package/dist/plugins/sui/errors.mjs.map +1 -1
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.d.mts +1 -1
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.mjs +3 -7
- package/dist/plugins/sui/exec/index.mjs.map +1 -0
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.mjs +1 -1
- package/dist/plugins/sui/exec/sign-and-dispatch.mjs.map +1 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs +2 -6
- package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -1
- package/dist/plugins/sui/fork-orchestration.mjs +13 -23
- package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
- package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
- package/dist/plugins/sui/index.d.mts +34 -83
- package/dist/plugins/sui/index.mjs +200 -68
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/{substrate/runtime/sui-ledger → plugins/sui/ledger}/object-ref.mjs +1 -1
- package/dist/plugins/sui/ledger/object-ref.mjs.map +1 -0
- package/dist/plugins/sui/local-faucet-strategy.mjs +1 -5
- package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -1
- package/dist/plugins/sui/log-attrs.mjs +11 -0
- package/dist/plugins/sui/log-attrs.mjs.map +1 -0
- package/dist/plugins/sui/mode/external.mjs +4 -12
- package/dist/plugins/sui/mode/external.mjs.map +1 -1
- package/dist/plugins/sui/mode/fork.mjs +19 -16
- package/dist/plugins/sui/mode/fork.mjs.map +1 -1
- package/dist/plugins/sui/mode/live.mjs +4 -17
- package/dist/plugins/sui/mode/live.mjs.map +1 -1
- package/dist/plugins/sui/mode/local.d.mts +1 -0
- package/dist/plugins/sui/mode/local.mjs +203 -61
- package/dist/plugins/sui/mode/local.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared-boot.mjs +18 -23
- package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared.d.mts +6 -7
- package/dist/plugins/sui/mode/shared.mjs.map +1 -1
- package/dist/plugins/sui/mode/spec.d.mts +23 -6
- package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.mjs +28 -30
- package/dist/plugins/sui/move/index.mjs.map +1 -0
- package/dist/plugins/sui/move-summary-runner.mjs +8 -4
- package/dist/plugins/sui/move-summary-runner.mjs.map +1 -1
- package/dist/{substrate/network.d.mts → plugins/sui/network-config.d.mts} +11 -10
- package/dist/plugins/sui/network-resolver.d.mts +6 -6
- package/dist/plugins/sui/routable.mjs.map +1 -1
- package/dist/plugins/sui/service.mjs +10 -6
- package/dist/plugins/sui/service.mjs.map +1 -1
- package/dist/plugins/sui/snapshot.mjs +10 -5
- package/dist/plugins/sui/snapshot.mjs.map +1 -1
- package/dist/plugins/wallet/codegen.d.mts +8 -6
- package/dist/plugins/wallet/codegen.mjs +4 -10
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/errors.mjs +1 -4
- package/dist/plugins/wallet/errors.mjs.map +1 -1
- package/dist/plugins/wallet/index.d.mts +3 -6
- package/dist/plugins/wallet/index.mjs +16 -21
- package/dist/plugins/wallet/index.mjs.map +1 -1
- package/dist/plugins/wallet/origin-policy.mjs +3 -3
- package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
- package/dist/plugins/wallet/pairing.mjs +6 -24
- package/dist/plugins/wallet/pairing.mjs.map +1 -1
- package/dist/plugins/wallet/protocol.mjs.map +1 -1
- package/dist/plugins/wallet/routable.mjs.map +1 -1
- package/dist/plugins/wallet/server.mjs +17 -24
- package/dist/plugins/wallet/server.mjs.map +1 -1
- package/dist/plugins/wallet/service.d.mts +2 -2
- package/dist/plugins/wallet/service.mjs +4 -15
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/plugins/wallet/snapshot.mjs.map +1 -1
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +1 -5
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/walrus/codegen.d.mts +2 -13
- package/dist/plugins/walrus/codegen.mjs +93 -28
- package/dist/plugins/walrus/codegen.mjs.map +1 -1
- package/dist/plugins/walrus/deploy-paths.mjs +2 -3
- package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
- package/dist/plugins/walrus/deploy.mjs +26 -19
- package/dist/plugins/walrus/deploy.mjs.map +1 -1
- package/dist/plugins/walrus/errors.mjs +1 -8
- package/dist/plugins/walrus/errors.mjs.map +1 -1
- package/dist/plugins/walrus/faucet-strategy.mjs +15 -1
- package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/walrus/index.d.mts +13 -44
- package/dist/plugins/walrus/index.mjs +119 -122
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/plugins/walrus/mode/known-deploy.mjs +6 -6
- package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
- package/dist/plugins/walrus/mode/local-cluster.d.mts +2 -2
- package/dist/plugins/walrus/mode/local-cluster.mjs +2 -10
- package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
- package/dist/plugins/walrus/plugin-key.mjs.map +1 -1
- package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
- package/dist/plugins/walrus/routable.mjs.map +1 -1
- package/dist/plugins/walrus/service.mjs.map +1 -1
- package/dist/plugins/walrus/snapshot.mjs +3 -3
- package/dist/plugins/walrus/snapshot.mjs.map +1 -1
- package/dist/plugins/walrus/storage-nodes.mjs +53 -14
- package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
- package/dist/plugins/walrus/wal-swap.mjs +4 -8
- package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
- package/dist/primitives/artifact-publisher.mjs.map +1 -1
- package/dist/primitives/cache.d.mts +1 -1
- package/dist/runtime/docker/client.mjs +47 -3
- package/dist/runtime/docker/client.mjs.map +1 -1
- package/dist/runtime/docker/container.mjs +37 -57
- package/dist/runtime/docker/container.mjs.map +1 -1
- package/dist/runtime/docker/errors.mjs.map +1 -1
- package/dist/runtime/docker/exec.mjs +2 -2
- package/dist/runtime/docker/exec.mjs.map +1 -1
- package/dist/runtime/docker/image.mjs +10 -11
- package/dist/runtime/docker/image.mjs.map +1 -1
- package/dist/runtime/docker/index.mjs +0 -1
- package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -1
- package/dist/runtime/docker/inventory.mjs +9 -9
- package/dist/runtime/docker/inventory.mjs.map +1 -1
- package/dist/runtime/docker/labels.mjs +12 -1
- package/dist/runtime/docker/labels.mjs.map +1 -1
- package/dist/runtime/docker/network.mjs +6 -6
- package/dist/runtime/docker/network.mjs.map +1 -1
- package/dist/runtime/docker/render-run-args.mjs.map +1 -1
- package/dist/runtime/docker/service.mjs +31 -45
- package/dist/runtime/docker/service.mjs.map +1 -1
- package/dist/runtime/docker/sweep.mjs +14 -64
- package/dist/runtime/docker/sweep.mjs.map +1 -1
- package/dist/runtime/docker/volume.mjs +1 -1
- package/dist/runtime/docker/volume.mjs.map +1 -1
- package/dist/runtime/docker/wrap.mjs +14 -2
- package/dist/runtime/docker/wrap.mjs.map +1 -1
- package/dist/substrate/brand.d.mts +2 -5
- package/dist/substrate/brand.mjs +3 -2
- package/dist/substrate/brand.mjs.map +1 -1
- package/dist/substrate/cross-process.mjs +2 -16
- package/dist/substrate/cross-process.mjs.map +1 -1
- package/dist/substrate/event-time.mjs +0 -2
- package/dist/substrate/event-time.mjs.map +1 -1
- package/dist/substrate/events.d.mts +46 -16
- package/dist/substrate/identity.d.mts +11 -5
- package/dist/substrate/manifest.d.mts +33 -26
- package/dist/substrate/manifest.mjs +8 -3
- package/dist/substrate/manifest.mjs.map +1 -1
- package/dist/substrate/options.d.mts +33 -4
- package/dist/substrate/plugin-ctx.d.mts +70 -0
- package/dist/substrate/plugin-ctx.mjs +66 -0
- package/dist/substrate/plugin-ctx.mjs.map +1 -0
- package/dist/substrate/plugin.d.mts +46 -38
- package/dist/substrate/plugin.mjs +13 -9
- package/dist/substrate/plugin.mjs.map +1 -1
- package/dist/substrate/projection.d.mts +1 -1
- package/dist/substrate/runtime/atomic-write.mjs +3 -4
- package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
- package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
- package/dist/substrate/runtime/cache/service.mjs +50 -20
- package/dist/substrate/runtime/cache/service.mjs.map +1 -1
- package/dist/substrate/runtime/config-validation.mjs.map +1 -1
- package/dist/substrate/runtime/control-plane/domain.mjs +34 -12
- package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -1
- package/dist/substrate/runtime/control-plane/service.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +14 -17
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/index.mjs +0 -2
- package/dist/substrate/runtime/cross-process/live-clock.mjs +1 -1
- package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/liveness.mjs +11 -18
- package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +2 -2
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/roster.mjs +17 -127
- package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/stack-lock.mjs +29 -28
- package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
- package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
- package/dist/substrate/runtime/errors.mjs +1 -13
- package/dist/substrate/runtime/errors.mjs.map +1 -1
- package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -1
- package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -1
- package/dist/substrate/runtime/host-gateway.mjs.map +1 -1
- package/dist/substrate/runtime/host-tree-tar/index.mjs +15 -163
- package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
- package/dist/substrate/runtime/http-probe.d.mts +1 -3
- package/dist/substrate/runtime/http-probe.mjs.map +1 -1
- package/dist/substrate/runtime/index.mjs +1 -6
- package/dist/substrate/runtime/lease-broker/service.mjs +5 -23
- package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs +3 -3
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/graph-input-id.d.mts +1 -0
- package/dist/substrate/runtime/lifecycle/graph-input-id.mjs +133 -0
- package/dist/substrate/runtime/lifecycle/graph-input-id.mjs.map +1 -0
- package/dist/substrate/runtime/lifecycle/index.mjs +2 -2
- package/dist/substrate/runtime/lifecycle/plugin-registry.d.mts +1 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +22 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/ready-gate.mjs +2 -15
- package/dist/substrate/runtime/lifecycle/ready-gate.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs +29 -25
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/state-machine.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.d.mts +1 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.mjs +0 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.mjs.map +1 -1
- package/dist/substrate/runtime/managed-container.d.mts +5 -1
- package/dist/substrate/runtime/managed-container.mjs +8 -10
- package/dist/substrate/runtime/managed-container.mjs.map +1 -1
- package/dist/substrate/runtime/manifest/manifest.mjs +8 -30
- package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/mode-errors.d.mts +1 -2
- package/dist/substrate/runtime/mode-errors.mjs.map +1 -1
- package/dist/substrate/runtime/observability/cascade-formatter.mjs +1 -11
- package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
- package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -1
- package/dist/substrate/runtime/observability/index.d.mts +1 -1
- package/dist/substrate/runtime/observability/index.mjs +4 -6
- package/dist/substrate/runtime/observability/log-attrs.mjs +46 -0
- package/dist/substrate/runtime/observability/log-attrs.mjs.map +1 -0
- package/dist/substrate/runtime/observability/log-store.mjs.map +1 -1
- package/dist/substrate/runtime/observability/logger.mjs +3 -3
- package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
- package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -1
- package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
- package/dist/substrate/runtime/observability/process-lines.mjs.map +1 -1
- package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
- package/dist/substrate/runtime/observability/subprocess-capture.mjs +2 -2
- package/dist/substrate/runtime/observability/subprocess-capture.mjs.map +1 -1
- package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -1
- package/dist/substrate/runtime/paths.mjs +27 -9
- package/dist/substrate/runtime/paths.mjs.map +1 -1
- package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -1
- package/dist/substrate/runtime/port-broker/service.mjs +10 -27
- package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/probes.d.mts +2 -3
- package/dist/substrate/runtime/probes.mjs.map +1 -1
- package/dist/substrate/runtime/process-supervisor.mjs.map +1 -1
- package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
- package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
- package/dist/substrate/runtime/projection/update.mjs +87 -11
- package/dist/substrate/runtime/projection/update.mjs.map +1 -1
- package/dist/substrate/runtime/random-suffix.mjs.map +1 -1
- package/dist/substrate/runtime/reconcile/fs-plan.mjs +146 -0
- package/dist/substrate/runtime/reconcile/fs-plan.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/graph.mjs +19 -0
- package/dist/substrate/runtime/reconcile/graph.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/index.mjs +5 -0
- package/dist/substrate/runtime/reconcile/label.mjs +41 -0
- package/dist/substrate/runtime/reconcile/label.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/spec.mjs +15 -0
- package/dist/substrate/runtime/reconcile/spec.mjs.map +1 -0
- package/dist/substrate/runtime/retry-policy.d.mts +2 -3
- package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
- package/dist/substrate/runtime/routed-url.mjs.map +1 -1
- package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
- package/dist/substrate/runtime/stage-and-swap/index.mjs +49 -12
- package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/service.mjs +29 -17
- package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
- package/dist/substrate/runtime/subnet-broker.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/acquire-node.mjs +188 -56
- package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/background-tasks.mjs +79 -65
- package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/command-loop.mjs +82 -23
- package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs +16 -0
- package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/errors.mjs +11 -2
- package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/index.mjs +2 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs +6 -6
- package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs +60 -70
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/state.d.mts +1 -1
- package/dist/substrate/runtime/supervisor/state.mjs +1 -1
- package/dist/substrate/runtime/supervisor/state.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/teardown.mjs +10 -9
- package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/wiring.mjs +6 -20
- package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -1
- package/dist/substrate/runtime/tar/reader.mjs +216 -0
- package/dist/substrate/runtime/tar/reader.mjs.map +1 -0
- package/dist/substrate/runtime/typed-env.mjs.map +1 -1
- package/dist/substrate/versioned-doc-schema.mjs.map +1 -1
- package/dist/substrate/versioned-doc-sync.mjs +5 -5
- package/dist/substrate/versioned-doc-sync.mjs.map +1 -1
- package/dist/surfaces/cli/command-tree.mjs +49 -7
- package/dist/surfaces/cli/command-tree.mjs.map +1 -1
- package/dist/surfaces/cli/commands/config.mjs +1 -1
- package/dist/surfaces/cli/commands/config.mjs.map +1 -1
- package/dist/surfaces/cli/commands/confirm-node.mjs.map +1 -1
- package/dist/surfaces/cli/commands/confirm.mjs.map +1 -1
- package/dist/surfaces/cli/commands/doctor.mjs +1 -1
- package/dist/surfaces/cli/commands/doctor.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune.mjs +1 -1
- package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
- package/dist/surfaces/cli/commands/snapshot.mjs +1 -1
- package/dist/surfaces/cli/commands/snapshot.mjs.map +1 -1
- package/dist/surfaces/cli/commands/status.mjs +1 -1
- package/dist/surfaces/cli/commands/status.mjs.map +1 -1
- package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
- package/dist/surfaces/cli/commands/wipe.mjs +3 -4
- package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
- package/dist/surfaces/cli/envelope.mjs.map +1 -1
- package/dist/surfaces/cli/errors.mjs.map +1 -1
- package/dist/surfaces/cli/flags.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs +45 -14
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/dist/surfaces/cli/output.mjs.map +1 -1
- package/dist/surfaces/cli/sysexits.mjs.map +1 -1
- package/dist/surfaces/tui/app.mjs +0 -21
- package/dist/surfaces/tui/app.mjs.map +1 -1
- package/dist/surfaces/tui/dashboard.mjs +0 -48
- package/dist/surfaces/tui/dashboard.mjs.map +1 -1
- package/dist/surfaces/tui/display-derivation.mjs +80 -16
- package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
- package/dist/surfaces/tui/errors.mjs.map +1 -1
- package/dist/surfaces/tui/event-log.mjs +0 -9
- package/dist/surfaces/tui/event-log.mjs.map +1 -1
- package/dist/surfaces/tui/heartbeat.mjs.map +1 -1
- package/dist/surfaces/tui/index.mjs +2 -2
- package/dist/surfaces/tui/index.mjs.map +1 -1
- package/dist/surfaces/tui/input.mjs.map +1 -1
- package/dist/surfaces/tui/mode-detect.mjs.map +1 -1
- package/dist/surfaces/tui/mount-ink.mjs.map +1 -1
- package/dist/surfaces/tui/plain-renderer.mjs +26 -19
- package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
- package/dist/surfaces/tui/resource-table.mjs +8 -10
- package/dist/surfaces/tui/resource-table.mjs.map +1 -1
- package/images/postgres/Dockerfile +0 -1
- package/images/sui/Dockerfile +27 -54
- package/images/sui/entrypoint.sh +17 -179
- package/package.json +23 -15
- package/dashboard-ui/assets/index-Bmi1UtAg.js +0 -1356
- package/dashboard-ui/assets/index-D5EShVt4.js +0 -3
- package/dashboard-ui/assets/index-Deml9drg.css +0 -1
- package/dist/api/plugin-errors.d.mts +0 -7
- package/dist/api/plugin-errors.mjs +0 -10
- package/dist/api/plugin-errors.mjs.map +0 -1
- package/dist/cli/wirings/build-verb-layers.mjs +0 -42
- package/dist/cli/wirings/build-verb-layers.mjs.map +0 -1
- package/dist/contracts/capability-decl.d.mts +0 -40
- package/dist/orchestrators/built-in-plugin-layers.mjs +0 -54
- package/dist/orchestrators/built-in-plugin-layers.mjs.map +0 -1
- package/dist/orchestrators/run.mjs +0 -91
- package/dist/orchestrators/run.mjs.map +0 -1
- package/dist/orchestrators/runtime-composition.d.mts +0 -10
- package/dist/orchestrators/runtime-composition.mjs +0 -252
- package/dist/orchestrators/runtime-composition.mjs.map +0 -1
- package/dist/orchestrators/snapshot/capture-command.d.mts +0 -1
- package/dist/orchestrators/snapshot/capture-command.mjs +0 -25
- package/dist/orchestrators/snapshot/capture-command.mjs.map +0 -1
- package/dist/orchestrators/snapshot/pending-marker.mjs +0 -99
- package/dist/orchestrators/snapshot/pending-marker.mjs.map +0 -1
- package/dist/orchestrators/snapshot/recover-pending.mjs +0 -223
- package/dist/orchestrators/snapshot/recover-pending.mjs.map +0 -1
- package/dist/orchestrators/snapshot/state-document.d.mts +0 -1
- package/dist/orchestrators/snapshot/state-document.mjs +0 -58
- package/dist/orchestrators/snapshot/state-document.mjs.map +0 -1
- package/dist/plugins/account/spans.mjs +0 -16
- package/dist/plugins/account/spans.mjs.map +0 -1
- package/dist/plugins/account/variants/env.mjs +0 -24
- package/dist/plugins/account/variants/env.mjs.map +0 -1
- package/dist/plugins/account/variants/inline.mjs +0 -13
- package/dist/plugins/account/variants/inline.mjs.map +0 -1
- package/dist/plugins/account/variants/keystore.mjs +0 -91
- package/dist/plugins/account/variants/keystore.mjs.map +0 -1
- package/dist/plugins/action/spans.mjs +0 -11
- package/dist/plugins/action/spans.mjs.map +0 -1
- package/dist/plugins/coin/spans.mjs +0 -20
- package/dist/plugins/coin/spans.mjs.map +0 -1
- package/dist/plugins/deepbook/spans.mjs +0 -18
- package/dist/plugins/deepbook/spans.mjs.map +0 -1
- package/dist/plugins/faucet/spans.mjs +0 -12
- package/dist/plugins/faucet/spans.mjs.map +0 -1
- package/dist/plugins/package/spans.mjs +0 -14
- package/dist/plugins/package/spans.mjs.map +0 -1
- package/dist/plugins/postgres/codegen.mjs +0 -42
- package/dist/plugins/postgres/codegen.mjs.map +0 -1
- package/dist/plugins/postgres/connection.d.mts +0 -33
- package/dist/plugins/postgres/connection.mjs.map +0 -1
- package/dist/plugins/postgres/db-ensure.mjs.map +0 -1
- package/dist/plugins/postgres/errors.d.mts +0 -60
- package/dist/plugins/postgres/errors.mjs +0 -29
- package/dist/plugins/postgres/errors.mjs.map +0 -1
- package/dist/plugins/postgres/index.d.mts +0 -37
- package/dist/plugins/postgres/index.mjs +0 -68
- package/dist/plugins/postgres/index.mjs.map +0 -1
- package/dist/plugins/postgres/routable.mjs +0 -33
- package/dist/plugins/postgres/routable.mjs.map +0 -1
- package/dist/plugins/postgres/service.d.mts +0 -50
- package/dist/plugins/postgres/service.mjs +0 -179
- package/dist/plugins/postgres/service.mjs.map +0 -1
- package/dist/plugins/postgres/snapshot.mjs +0 -32
- package/dist/plugins/postgres/snapshot.mjs.map +0 -1
- package/dist/plugins/postgres/spans.mjs +0 -11
- package/dist/plugins/postgres/spans.mjs.map +0 -1
- package/dist/plugins/seal/spans.mjs +0 -18
- package/dist/plugins/seal/spans.mjs.map +0 -1
- package/dist/plugins/sui/spans.mjs +0 -17
- package/dist/plugins/sui/spans.mjs.map +0 -1
- package/dist/plugins/wallet/spans.mjs +0 -22
- package/dist/plugins/wallet/spans.mjs.map +0 -1
- package/dist/plugins/walrus/registry-publish.d.mts +0 -24
- package/dist/plugins/walrus/spans.mjs +0 -18
- package/dist/plugins/walrus/spans.mjs.map +0 -1
- package/dist/runtime/docker/logs.d.mts +0 -1
- package/dist/runtime/docker/logs.mjs +0 -34
- package/dist/runtime/docker/logs.mjs.map +0 -1
- package/dist/substrate/runtime/artifact-publisher/index.mjs +0 -86
- package/dist/substrate/runtime/artifact-publisher/index.mjs.map +0 -1
- package/dist/substrate/runtime/capability-sinks/index.d.mts +0 -1
- package/dist/substrate/runtime/capability-sinks/index.mjs +0 -3
- package/dist/substrate/runtime/capability-sinks/layer.d.mts +0 -1
- package/dist/substrate/runtime/capability-sinks/layer.mjs +0 -31
- package/dist/substrate/runtime/capability-sinks/layer.mjs.map +0 -1
- package/dist/substrate/runtime/capability-sinks/service.d.mts +0 -95
- package/dist/substrate/runtime/capability-sinks/service.mjs +0 -69
- package/dist/substrate/runtime/capability-sinks/service.mjs.map +0 -1
- package/dist/substrate/runtime/cross-process/lock.d.mts +0 -1
- package/dist/substrate/runtime/cross-process/lock.mjs +0 -23
- package/dist/substrate/runtime/cross-process/lock.mjs.map +0 -1
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +0 -113
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +0 -1
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs +0 -31
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +0 -1
- package/dist/substrate/runtime/observability/formatter-registry.d.mts +0 -1
- package/dist/substrate/runtime/observability/formatter-registry.mjs +0 -48
- package/dist/substrate/runtime/observability/formatter-registry.mjs.map +0 -1
- package/dist/substrate/runtime/observability/span-store.d.mts +0 -1
- package/dist/substrate/runtime/observability/span-store.mjs +0 -110
- package/dist/substrate/runtime/observability/span-store.mjs.map +0 -1
- package/dist/substrate/runtime/observability/spans.d.mts +0 -1
- package/dist/substrate/runtime/observability/spans.mjs +0 -87
- package/dist/substrate/runtime/observability/spans.mjs.map +0 -1
- package/dist/substrate/runtime/projection/index.mjs +0 -4
- package/dist/substrate/runtime/projection/persisted.mjs +0 -213
- package/dist/substrate/runtime/projection/persisted.mjs.map +0 -1
- package/dist/substrate/runtime/scoped-multimap/service.mjs +0 -52
- package/dist/substrate/runtime/scoped-multimap/service.mjs.map +0 -1
- package/dist/substrate/runtime/scoped-ref-map/index.mjs +0 -2
- package/dist/substrate/runtime/scoped-ref-map/service.mjs +0 -83
- package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +0 -1
- package/dist/substrate/runtime/state-store/index.mjs +0 -3
- package/dist/substrate/runtime/state-store/schema.d.mts +0 -1
- package/dist/substrate/runtime/state-store/schema.mjs +0 -41
- package/dist/substrate/runtime/state-store/schema.mjs.map +0 -1
- package/dist/substrate/runtime/state-store/service.d.mts +0 -1
- package/dist/substrate/runtime/state-store/service.mjs +0 -145
- package/dist/substrate/runtime/state-store/service.mjs.map +0 -1
- package/dist/substrate/runtime/sui-execute/index.mjs.map +0 -1
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +0 -1
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +0 -1
- package/dist/substrate/runtime/sui-move-build/index.mjs.map +0 -1
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +0 -151
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +0 -1
- package/dist/substrate/state-store.d.mts +0 -1
- /package/dist/orchestrators/{snapshot/pending-marker.d.mts → codegen/id-config.d.mts} +0 -0
- /package/dist/orchestrators/snapshot/{recover-pending.d.mts → interrupted-restore.d.mts} +0 -0
- /package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.d.mts +0 -0
- /package/dist/plugins/{postgres/snapshot.d.mts → sui/ledger/object-ref.d.mts} +0 -0
- /package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.d.mts +0 -0
- /package/dist/substrate/runtime/{state-store/index.d.mts → reconcile/graph.d.mts} +0 -0
- /package/dist/substrate/runtime/{cross-process/snapshot-reservation.d.mts → supervisor/contribution-dispatcher.d.mts} +0 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { SpanAttr } from "../observability/spans.mjs";
|
|
2
|
-
import { versionedDocSchema } from "../../versioned-doc-schema.mjs";
|
|
3
|
-
import { selfPid } from "./self-pid.mjs";
|
|
4
1
|
import { DEFAULT_SWEEP_POLICY, RosterDocumentSchema } from "../../cross-process.mjs";
|
|
2
|
+
import { selfPid } from "./self-pid.mjs";
|
|
5
3
|
import { readVersionedDocumentSync, writeVersionedDocumentSync } from "../../versioned-doc-sync.mjs";
|
|
6
|
-
import { LivenessProbeScope,
|
|
4
|
+
import { LivenessProbeScope, layerLivenessProbeScope, ownHolder, processStartTime } from "./liveness.mjs";
|
|
7
5
|
import { acquireStackLock } from "./stack-lock.mjs";
|
|
8
|
-
import {
|
|
6
|
+
import { LogAttr } from "../observability/log-attrs.mjs";
|
|
7
|
+
import { Data, Effect } from "effect";
|
|
9
8
|
import { hostname } from "node:os";
|
|
10
9
|
//#region src/substrate/runtime/cross-process/roster.ts
|
|
11
10
|
var RosterCorruptError = class extends Data.TaggedError("RosterCorruptError") {};
|
|
@@ -29,11 +28,11 @@ const ROSTER_DOC_ERRORS = {
|
|
|
29
28
|
* Tolerates a missing file but NOT a malformed file (a malformed
|
|
30
29
|
* roster surfaces a typed error so callers can decide whether to
|
|
31
30
|
* abandon or rewrite). */
|
|
32
|
-
const readRoster = (path) => readVersionedDocumentSync(path, RosterDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_ROSTER)
|
|
31
|
+
const readRoster = (path) => readVersionedDocumentSync(path, RosterDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_ROSTER);
|
|
33
32
|
/** Atomic write: route through the canonical sync primitive. The
|
|
34
33
|
* roster's mutations are all under `stack.lock`, so the non-yielding
|
|
35
34
|
* sync surface is correct here — and it shares ONE owner of the
|
|
36
|
-
* tempfile dance with
|
|
35
|
+
* tempfile dance with cache and manifest. */
|
|
37
36
|
const atomicWriteRoster = (path, doc) => writeVersionedDocumentSync(path, doc, ROSTER_DOC_ERRORS);
|
|
38
37
|
/** Walk the roster's holders and drop those that fail the liveness
|
|
39
38
|
* test AND whose heartbeats are older than `staleAfterMillis`.
|
|
@@ -48,13 +47,12 @@ const sweepStaleHolders = Effect.fn("cross-process.roster.sweep")(function* (doc
|
|
|
48
47
|
const probe = yield* LivenessProbeScope;
|
|
49
48
|
const survivors = [];
|
|
50
49
|
const evicted = [];
|
|
51
|
-
const ownHost = hostname();
|
|
52
50
|
for (const holder of doc.holders) {
|
|
53
51
|
if (!(now - holder.heartbeatAt > policy.staleAfterMillis)) {
|
|
54
52
|
survivors.push(holder);
|
|
55
53
|
continue;
|
|
56
54
|
}
|
|
57
|
-
if ((yield* probe.probeHolderLiveness(holder
|
|
55
|
+
if ((yield* probe.probeHolderLiveness(holder).pipe(Effect.catch(() => Effect.succeed("alive")))) === "dead") evicted.push(holder);
|
|
58
56
|
else survivors.push(holder);
|
|
59
57
|
}
|
|
60
58
|
return {
|
|
@@ -65,22 +63,10 @@ const sweepStaleHolders = Effect.fn("cross-process.roster.sweep")(function* (doc
|
|
|
65
63
|
evicted
|
|
66
64
|
};
|
|
67
65
|
}, 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
66
|
/** Match a roster holder against THIS process's identity.
|
|
81
67
|
*
|
|
82
|
-
* Liveness elsewhere (`checkHolderLiveness
|
|
83
|
-
*
|
|
68
|
+
* Liveness elsewhere (`checkHolderLiveness`) uses
|
|
69
|
+
* `(pid, hostname, startTime)` — PID alone is insufficient on
|
|
84
70
|
* long-uptime hosts where the kernel can recycle PIDs. The roster
|
|
85
71
|
* mutators (`heartbeat`, `release`, `setIntent`) must apply the same
|
|
86
72
|
* triple match so a recycled-PID peer's entry is never silently
|
|
@@ -88,8 +74,8 @@ const requireClaimsPath = (paths) => {
|
|
|
88
74
|
*
|
|
89
75
|
* `startTime` is the FNV-1a hash of `ps -o lstart` (see
|
|
90
76
|
* `liveness.processStartTime`). A `null` probe (process gone, or
|
|
91
|
-
* exotic platform) skips the start-time check — same conservative
|
|
92
|
-
* policy
|
|
77
|
+
* exotic platform) skips the start-time check — the same conservative
|
|
78
|
+
* policy `checkHolderLiveness` applies. */
|
|
93
79
|
const isOwnEntry = (h, ownPid, ownHost, ownStartTime) => {
|
|
94
80
|
if (h.pid !== ownPid || h.hostname !== ownHost) return false;
|
|
95
81
|
if (ownStartTime === null || h.startTime === null) return true;
|
|
@@ -111,10 +97,6 @@ const withStackLock = (paths, body) => Effect.scoped(Effect.gen(function* () {
|
|
|
111
97
|
* Returns the swept document, our holder entry, and any evicted peers.
|
|
112
98
|
*/
|
|
113
99
|
const claim = (paths, intent = "normal", policy = DEFAULT_SWEEP_POLICY) => withStackLock(paths, Effect.gen(function* () {
|
|
114
|
-
yield* Effect.annotateCurrentSpan({
|
|
115
|
-
"devstack.roster.path": paths.rosterFile,
|
|
116
|
-
"devstack.roster.intent": intent
|
|
117
|
-
});
|
|
118
100
|
const { swept, evicted } = yield* sweepStaleHolders(yield* readRoster(paths.rosterFile).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_ROSTER))), policy);
|
|
119
101
|
const self = { ...ownHolder(intent) };
|
|
120
102
|
const next = {
|
|
@@ -128,7 +110,7 @@ const claim = (paths, intent = "normal", policy = DEFAULT_SWEEP_POLICY) => withS
|
|
|
128
110
|
evicted,
|
|
129
111
|
soleHolder: swept.holders.length === 0
|
|
130
112
|
};
|
|
131
|
-
}))
|
|
113
|
+
}));
|
|
132
114
|
/**
|
|
133
115
|
* Heartbeat protocol — refresh this process's `heartbeatAt`. Under the
|
|
134
116
|
* stack lock to keep mutation atomic. Architecture § Heartbeat
|
|
@@ -154,7 +136,7 @@ const heartbeat = (paths, ownPid = selfPid()) => withStackLock(paths, Effect.gen
|
|
|
154
136
|
})
|
|
155
137
|
};
|
|
156
138
|
if (touched) yield* atomicWriteRoster(paths.rosterFile, next);
|
|
157
|
-
}))
|
|
139
|
+
}));
|
|
158
140
|
/**
|
|
159
141
|
* Release protocol — architecture § Release protocol.
|
|
160
142
|
*
|
|
@@ -175,7 +157,7 @@ const release = (paths, ownPid = selfPid()) => withStackLock(paths, Effect.gen(f
|
|
|
175
157
|
roster: next,
|
|
176
158
|
lastLeaver: remaining.length === 0
|
|
177
159
|
};
|
|
178
|
-
}))
|
|
160
|
+
}));
|
|
179
161
|
/** Background heartbeat fiber. Wakes every `intervalMillis` (default
|
|
180
162
|
* matches `DEFAULT_SWEEP_POLICY.heartbeatIntervalMillis`) and refreshes
|
|
181
163
|
* this process's `heartbeatAt`.
|
|
@@ -186,104 +168,12 @@ const heartbeatFiber = (paths, intervalMillis = DEFAULT_SWEEP_POLICY.heartbeatIn
|
|
|
186
168
|
while (true) {
|
|
187
169
|
yield* Effect.sleep(`${intervalMillis} millis`);
|
|
188
170
|
yield* heartbeat(paths).pipe(Effect.catch((err) => Effect.logWarning("roster heartbeat failed").pipe(Effect.annotateLogs({
|
|
189
|
-
[
|
|
190
|
-
[
|
|
171
|
+
[LogAttr.rosterHeartbeatIntervalMs]: intervalMillis,
|
|
172
|
+
[LogAttr.errorCause]: String(err)
|
|
191
173
|
}))));
|
|
192
174
|
}
|
|
193
|
-
}).pipe(Effect.withSpan("cross-process.roster.heartbeatFiber"));
|
|
194
|
-
const ContainerClaimSchema = Schema.Struct({
|
|
195
|
-
containerKey: Schema.String,
|
|
196
|
-
pid: Schema.Number,
|
|
197
|
-
startTime: Schema.optional(Schema.Number),
|
|
198
|
-
hostname: Schema.String,
|
|
199
|
-
claimedAt: Schema.Number
|
|
200
175
|
});
|
|
201
|
-
const ContainerClaimDocumentSchema = versionedDocSchema(1, { claims: Schema.Array(ContainerClaimSchema) });
|
|
202
|
-
const EMPTY_CLAIMS = {
|
|
203
|
-
version: 1,
|
|
204
|
-
claims: []
|
|
205
|
-
};
|
|
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()) => {
|
|
219
|
-
if (claim.hostname !== ownHost) return true;
|
|
220
|
-
if (!isPidAlive(claim.pid)) return false;
|
|
221
|
-
if (claim.startTime === void 0) return true;
|
|
222
|
-
const probedStart = probeStartTime(claim.pid);
|
|
223
|
-
if (probedStart === null) return true;
|
|
224
|
-
return probedStart === claim.startTime;
|
|
225
|
-
};
|
|
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);
|
|
239
|
-
/** Read the container-claim ledger. */
|
|
240
|
-
const readClaims = (paths) => Effect.suspend(() => readVersionedDocumentSync(requireClaimsPath(paths), ContainerClaimDocumentSchema, ROSTER_DOC_ERRORS, EMPTY_CLAIMS)).pipe(Effect.withSpan("cross-process.roster.readClaims"));
|
|
241
|
-
/** Record that this process now claims `containerKey`. No-op if
|
|
242
|
-
* already claimed by this process. */
|
|
243
|
-
const addClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(function* () {
|
|
244
|
-
const path = requireClaimsPath(paths);
|
|
245
|
-
const current = yield* readClaims(paths).pipe(Effect.catchTag("RosterCorruptError", () => Effect.succeed(EMPTY_CLAIMS)));
|
|
246
|
-
const live = yield* liveContainerClaims(current);
|
|
247
|
-
const ownPid = selfPid();
|
|
248
|
-
const ownStartTime = processStartTime(ownPid) ?? void 0;
|
|
249
|
-
const ownHost = hostname();
|
|
250
|
-
if (live.claims.some((c) => isOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime))) {
|
|
251
|
-
if (live.claims.length !== current.claims.length) yield* writeClaims(path, live);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
yield* writeClaims(path, {
|
|
255
|
-
version: 1,
|
|
256
|
-
claims: [...live.claims, {
|
|
257
|
-
containerKey,
|
|
258
|
-
pid: ownPid,
|
|
259
|
-
...ownStartTime === void 0 ? {} : { startTime: ownStartTime },
|
|
260
|
-
hostname: ownHost,
|
|
261
|
-
claimedAt: Date.now()
|
|
262
|
-
}]
|
|
263
|
-
});
|
|
264
|
-
})).pipe(Effect.withSpan("cross-process.roster.addClaim"));
|
|
265
|
-
/** Release this process's claim on `containerKey`. Returns whether
|
|
266
|
-
* the container has zero remaining claims AFTER this release — the
|
|
267
|
-
* "last-leaver" signal that authorizes a teardown.
|
|
268
|
-
*
|
|
269
|
-
* Architecture § Cross-process safety protocol: "when a process
|
|
270
|
-
* releases its last claim and confirms no other process holds the
|
|
271
|
-
* container, it may tear down." */
|
|
272
|
-
const removeClaim = (paths, containerKey) => withStackLock(paths, Effect.gen(function* () {
|
|
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;
|
|
277
|
-
const ownHost = hostname();
|
|
278
|
-
const remaining = live.claims.filter((c) => !isOwnContainerClaim(c, containerKey, ownPid, ownHost, ownStartTime));
|
|
279
|
-
const stillClaimedByPeer = remaining.some((c) => c.containerKey === containerKey);
|
|
280
|
-
yield* writeClaims(path, {
|
|
281
|
-
version: 1,
|
|
282
|
-
claims: remaining
|
|
283
|
-
});
|
|
284
|
-
return { lastClaimReleased: !stillClaimedByPeer };
|
|
285
|
-
})).pipe(Effect.withSpan("cross-process.roster.removeClaim"));
|
|
286
176
|
//#endregion
|
|
287
|
-
export { RosterCorruptError, RosterIoError,
|
|
177
|
+
export { RosterCorruptError, RosterIoError, claim, heartbeat, heartbeatFiber, readRoster, release, sweepStaleHolders };
|
|
288
178
|
|
|
289
179
|
//# 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 { 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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"}
|
|
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 } 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 { LogAttr } from '../observability/log-attrs.ts';\nimport { readVersionedDocumentSync, writeVersionedDocumentSync } from '../../versioned-doc-sync.ts';\nimport { selfPid } from './self-pid.ts';\nimport { acquireStackLock } from './stack-lock.ts';\nimport {\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);\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 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\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)\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}\n\n/** Match a roster holder against THIS process's identity.\n *\n * Liveness elsewhere (`checkHolderLiveness`) uses\n * `(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 — the same conservative\n * policy `checkHolderLiveness` applies. */\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\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);\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);\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);\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);\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[LogAttr.rosterHeartbeatIntervalMs]: intervalMillis,\n\t\t\t\t\t\t\t[LogAttr.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});\n"],"mappings":"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"}
|
|
@@ -1 +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,
|
|
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, stack-lock,\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,4 +1,3 @@
|
|
|
1
|
-
import { SpanAttr } from "../observability/spans.mjs";
|
|
2
1
|
import "../../cross-process.mjs";
|
|
3
2
|
import { parseVersionedDocumentBodyOrNull } from "../../versioned-doc-sync.mjs";
|
|
4
3
|
import { underLiveClock } from "./live-clock.mjs";
|
|
@@ -41,15 +40,16 @@ const DEFAULT_ACQUIRE_TIMEOUT_MILLIS = 5e3;
|
|
|
41
40
|
const INITIAL_BACKOFF_MILLIS = 25;
|
|
42
41
|
const MAX_BACKOFF_MILLIS = 200;
|
|
43
42
|
/**
|
|
44
|
-
* Sync attempt at O_EXCL-create
|
|
43
|
+
* Sync attempt at O_EXCL-create with the lock's JSON body. Returns
|
|
44
|
+
* whether we own the file now; on EEXIST it probes the existing body
|
|
45
|
+
* through `parseLockBody`.
|
|
45
46
|
*
|
|
46
47
|
* Effect-platform's FileSystem doesn't expose a sync `open` shape, but
|
|
47
48
|
* the critical-section discipline says we need a non-blocking attempt
|
|
48
49
|
* inside a retry loop. Falling through to Node sync APIs here is the
|
|
49
50
|
* cleanest path; the rest of the substrate stays Effect-native.
|
|
50
51
|
*/
|
|
51
|
-
const tryAcquireSync = (path) => {
|
|
52
|
-
const body = ownHolder();
|
|
52
|
+
const tryAcquireSync = (path, body) => {
|
|
53
53
|
mkdirSync(dirname(path), { recursive: true });
|
|
54
54
|
try {
|
|
55
55
|
writeFileSync(path, JSON.stringify(body), { flag: "wx" });
|
|
@@ -74,25 +74,30 @@ const tryAcquireSync = (path) => {
|
|
|
74
74
|
}
|
|
75
75
|
};
|
|
76
76
|
/**
|
|
77
|
-
* Acquire `stack.lock` as a scoped resource. The lock is released
|
|
78
|
-
*
|
|
77
|
+
* Acquire `stack.lock` as a scoped resource. The lock is released when
|
|
78
|
+
* the surrounding Scope closes (the unlink finalizer is registered INSIDE
|
|
79
|
+
* this scope, so every caller — roster claim/release/heartbeat, the
|
|
80
|
+
* snapshot bounce, command-channel append — releases on scope close,
|
|
81
|
+
* never bypassed). Yields a `void` resource — the caller MUST keep its
|
|
82
|
+
* Scope tight; the architecture forbids holding the lock across long
|
|
83
|
+
* operations.
|
|
79
84
|
*
|
|
80
|
-
*
|
|
81
|
-
* On every miss, probe the holder's liveness —
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* across long operations.
|
|
85
|
+
* The O_EXCL retry loop: exponential backoff up to `timeoutMillis`
|
|
86
|
+
* (default 5s). On every miss, probe the holder's liveness — dead holders
|
|
87
|
+
* are reclaimed (unlink + loop); an unparseable body is reclaimed only
|
|
88
|
+
* through the shared re-stat guard once it ages past
|
|
89
|
+
* `DEFAULT_SWEEP_POLICY.staleAfterMillis`. Backoff resets on holder
|
|
90
|
+
* change and on reclaim. The reclaim path is the architecture's "stale
|
|
91
|
+
* lock" handling — same PID + start-time semantics as the roster sweep,
|
|
92
|
+
* so a process that crashed under the lock never blocks the next peer
|
|
93
|
+
* indefinitely.
|
|
90
94
|
*/
|
|
91
95
|
const acquireStackLock = (path, timeoutMillis = DEFAULT_ACQUIRE_TIMEOUT_MILLIS) => Effect.gen(function* () {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
const addUnlinkFinalizer = Effect.addFinalizer(() => Effect.sync(() => {
|
|
97
|
+
try {
|
|
98
|
+
unlinkSync(path);
|
|
99
|
+
} catch {}
|
|
100
|
+
}));
|
|
96
101
|
const startedAt = Date.now();
|
|
97
102
|
let backoff = INITIAL_BACKOFF_MILLIS;
|
|
98
103
|
let lastHolder = null;
|
|
@@ -105,22 +110,18 @@ const acquireStackLock = (path, timeoutMillis = DEFAULT_ACQUIRE_TIMEOUT_MILLIS)
|
|
|
105
110
|
holder: lastHolder
|
|
106
111
|
}));
|
|
107
112
|
const attempt = yield* Effect.try({
|
|
108
|
-
try: () => tryAcquireSync(path),
|
|
113
|
+
try: () => tryAcquireSync(path, ownHolder()),
|
|
109
114
|
catch: (cause) => new StackLockIoError({
|
|
110
115
|
path,
|
|
111
116
|
cause
|
|
112
117
|
})
|
|
113
118
|
});
|
|
114
119
|
if (attempt.ok) {
|
|
115
|
-
yield*
|
|
116
|
-
try {
|
|
117
|
-
unlinkSync(path);
|
|
118
|
-
} catch {}
|
|
119
|
-
}));
|
|
120
|
+
yield* addUnlinkFinalizer;
|
|
120
121
|
return;
|
|
121
122
|
}
|
|
122
123
|
lastHolder = attempt.holder;
|
|
123
|
-
const currentHolderPid = lastHolder
|
|
124
|
+
const currentHolderPid = lastHolder !== null ? lastHolder.pid : null;
|
|
124
125
|
if (currentHolderPid !== prevHolderPid) {
|
|
125
126
|
backoff = INITIAL_BACKOFF_MILLIS;
|
|
126
127
|
prevHolderPid = currentHolderPid;
|
|
@@ -156,7 +157,7 @@ const acquireStackLock = (path, timeoutMillis = DEFAULT_ACQUIRE_TIMEOUT_MILLIS)
|
|
|
156
157
|
yield* underLiveClock(Effect.sleep(`${backoff} millis`));
|
|
157
158
|
backoff = Math.min(backoff * 2, MAX_BACKOFF_MILLIS);
|
|
158
159
|
}
|
|
159
|
-
})
|
|
160
|
+
});
|
|
160
161
|
//#endregion
|
|
161
162
|
export { DEFAULT_ACQUIRE_TIMEOUT_MILLIS, StackLockIoError, StackLockTimeoutError, acquireStackLock };
|
|
162
163
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack-lock.mjs","names":[],"sources":["../../../../src/substrate/runtime/cross-process/stack-lock.ts"],"sourcesContent":["// `stack.lock` — O_EXCL exclusive lock for short critical sections.\n//\n// Architecture § Cross-process safety protocol § \"What is locked\":\n// - `stack.lock` — OS-advisory exclusive lock file. Used only for\n// short critical sections (roster mutations, snapshot reservation).\n// Acquired via `flock(LOCK_EX)` on Unix and `LockFileEx` on Windows;\n// never held across a long operation.\n//\n// The implementation here uses a portable O_EXCL-create dance — the\n// same approach the legacy `engine/file-lock.ts` proved out — because\n// `flock` isn't available cross-platform out of `node:fs`. The\n// architecture's wording (\"OS-advisory exclusive lock file\") covers\n// both `flock` and `O_EXCL`-with-pid-body; the latter is what every\n// portable POSIX tool reaches for when `flock` isn't on the table.\n//\n// Discipline:\n// - The lock is held BRIEFLY — within a single Effect.scoped block\n// that mutates the roster or the snapshot reservation.\n// - Stale locks (owner crashed under the lock) are reclaimed via the\n// PID + start-time liveness check before the acquire reattempts.\n// - Acquire retries with exponential backoff up to 5 seconds total\n// (architecture § Claim protocol step 1).\n\nimport { mkdirSync, unlinkSync, readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nimport { Data, Effect, Schema, Scope } from 'effect';\n\nimport { type RosterHolder } from '../../cross-process.ts';\nimport { parseVersionedDocumentBodyOrNull } from '../../versioned-doc-sync.ts';\nimport { SpanAttr } from '../observability/spans.ts';\nimport { underLiveClock } from './live-clock.ts';\nimport { checkHolderLiveness, ownHolder } from './liveness.ts';\nimport { reclaimUnparseableStaleFile } from './reclaim-stale-file.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\n/** Tagged failure: acquire window elapsed and the lock is still held.\n * Carries the in-disk body (if parseable) so the caller can name the\n * blocking peer in user-facing error reporting. */\nexport class StackLockTimeoutError extends Data.TaggedError('StackLockTimeoutError')<{\n\treadonly path: string;\n\treadonly waitedMillis: number;\n\treadonly holder: RosterHolder | null;\n}> {}\n\n/** Tagged failure: I/O error other than EEXIST during acquire. */\nexport class StackLockIoError extends Data.TaggedError('StackLockIoError')<{\n\treadonly path: string;\n\treadonly cause: unknown;\n}> {}\n\nexport type StackLockError = StackLockTimeoutError | StackLockIoError;\n\n// -----------------------------------------------------------------------------\n// Codec\n// -----------------------------------------------------------------------------\n\n/** The on-disk body is the same `RosterHolder` shape — re-using the schema\n * means roster sweep + stack-lock reclaim see the same fields.\n *\n * `startTime` mirrors `RosterHolderSchema`: `number | null` so an\n * unprobable platform's lock body round-trips cleanly through the\n * decoder. The shared liveness predicate honors null conservatively. */\nconst StackLockBodySchema = Schema.Struct({\n\tpid: Schema.Number,\n\tstartTime: Schema.NullOr(Schema.Number),\n\thostname: Schema.String,\n\tclaimedAt: Schema.Number,\n\theartbeatAt: Schema.Number,\n\tintent: Schema.Literals(['normal', 'snapshot']),\n});\n\nconst parseLockBody = (raw: string): RosterHolder | null =>\n\tparseVersionedDocumentBodyOrNull(raw, StackLockBodySchema, 'stack.lock');\n\n// -----------------------------------------------------------------------------\n// Acquire / release\n// -----------------------------------------------------------------------------\n\n/** Default acquire timeout — architecture § Claim protocol step 1\n * (\"Block up to 5 seconds; if unavailable, retry with backoff\"). */\nexport const DEFAULT_ACQUIRE_TIMEOUT_MILLIS = 5_000;\n\n/** Per-attempt initial wait. Doubles each retry up to the cap. The\n * backoff also resets whenever the holder identity changes — see the\n * reclaim loop comment for the contention story (review fix phase\n * 22f). The cap is intentionally tight (200ms) so peers react quickly\n * to a release; combined with the O_EXCL-arbitrated retry on reclaim,\n * no staggering jitter is needed. */\nconst INITIAL_BACKOFF_MILLIS = 25;\nconst MAX_BACKOFF_MILLIS = 200;\n\n/**\n * Sync attempt at O_EXCL-create. Returns whether we own the lock now.\n *\n * Effect-platform's FileSystem doesn't expose a sync `open` shape, but\n * the critical-section discipline says we need a non-blocking attempt\n * inside a retry loop. Falling through to Node sync APIs here is the\n * cleanest path; the rest of the substrate stays Effect-native.\n */\nconst tryAcquireSync = (\n\tpath: string,\n): { readonly ok: true } | { readonly ok: false; readonly holder: RosterHolder | null } => {\n\tconst body = ownHolder();\n\t// The lock's parent directory may not exist on first-claim of a\n\t// fresh runtime root — devstack's `<runtimeRoot>/stacks/<stack>/` is\n\t// the substrate's responsibility to bring into being, but no other\n\t// L0 subsystem touches the disk until the first claim. `mkdir -p`\n\t// here is idempotent and harmless on the warm path; it transforms\n\t// the otherwise-fatal ENOENT-during-write into a clean acquire.\n\t// Architecture § Cross-process safety protocol: stack root is\n\t// created by whichever subsystem first reaches for a path under it.\n\tmkdirSync(dirname(path), { recursive: true });\n\ttry {\n\t\twriteFileSync(path, JSON.stringify(body), { flag: 'wx' });\n\t\treturn { ok: true };\n\t} catch (err) {\n\t\tconst code = (err as NodeJS.ErrnoException).code;\n\t\tif (code !== 'EEXIST') {\n\t\t\tthrow err;\n\t\t}\n\t\t// Peer holds it (or stale). Probe the existing body.\n\t\tif (!existsSync(path)) {\n\t\t\t// Race: holder unlinked between our EEXIST and the existsSync.\n\t\t\t// Retry on the next loop iteration.\n\t\t\treturn { ok: false, holder: null };\n\t\t}\n\t\ttry {\n\t\t\tconst raw = readFileSync(path, 'utf8');\n\t\t\treturn { ok: false, holder: parseLockBody(raw) };\n\t\t} catch {\n\t\t\treturn { ok: false, holder: null };\n\t\t}\n\t}\n};\n\n/**\n * Acquire `stack.lock` as a scoped resource. The lock is released\n * when the surrounding Scope closes.\n *\n * Retry loop: exponential backoff up to `timeoutMillis` (default 5s).\n * On every miss, probe the holder's liveness — if it's dead, reclaim\n * by unlinking and looping. The reclaim path is the architecture's\n * \"stale lock\" handling — same PID + start-time semantics as the\n * roster sweep, so a process that crashed under the lock never blocks\n * the next peer indefinitely.\n *\n * IMPORTANT: this primitive yields a `void` resource. The caller MUST\n * keep its Scope tight — the architecture forbids holding the lock\n * across long operations.\n */\nexport const acquireStackLock = (\n\tpath: string,\n\ttimeoutMillis: number = DEFAULT_ACQUIRE_TIMEOUT_MILLIS,\n): Effect.Effect<void, StackLockError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t[SpanAttr.stackLockPath]: path,\n\t\t\t[SpanAttr.stackLockTimeoutMillis]: timeoutMillis,\n\t\t});\n\t\tconst startedAt = Date.now();\n\t\tlet backoff = INITIAL_BACKOFF_MILLIS;\n\t\tlet lastHolder: RosterHolder | null = null;\n\t\tlet prevHolderPid: number | null = null;\n\t\twhile (true) {\n\t\t\tconst elapsed = Date.now() - startedAt;\n\t\t\tif (elapsed > timeoutMillis) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew StackLockTimeoutError({ path, waitedMillis: elapsed, holder: lastHolder }),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst attempt = yield* Effect.try({\n\t\t\t\ttry: () => tryAcquireSync(path),\n\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t});\n\t\t\tif (attempt.ok) {\n\t\t\t\t// Register a finalizer that unlinks the lock on scope close.\n\t\t\t\tyield* Effect.addFinalizer(() =>\n\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tunlinkSync(path);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Already gone — ok. Crash-during-release is handled\n\t\t\t\t\t\t\t// by the next peer's stale-PID reclaim.\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlastHolder = attempt.holder;\n\t\t\t// Reset the backoff whenever the holder identity changes —\n\t\t\t// a new holder means the previous one released, and our\n\t\t\t// long-saturated backoff (driven by waiting on the prior\n\t\t\t// holder) is now stale. Without this, late waiters under\n\t\t\t// heavy contention saturate at MAX_BACKOFF_MILLIS while\n\t\t\t// each peer holds the lock only briefly, exhausting the\n\t\t\t// claim budget before they can win (review fix phase 22f\n\t\t\t// reclaim-stress reproducer caught the case).\n\t\t\tconst currentHolderPid = lastHolder?.pid ?? null;\n\t\t\tif (currentHolderPid !== prevHolderPid) {\n\t\t\t\tbackoff = INITIAL_BACKOFF_MILLIS;\n\t\t\t\tprevHolderPid = currentHolderPid;\n\t\t\t}\n\t\t\t// Reclaim if the holder is dead OR the lock body is\n\t\t\t// unparseable AND old enough to be presumed-abandoned. The\n\t\t\t// unparseable case covers a peer that died mid-write: the\n\t\t\t// PID liveness check has nothing to consult, so the only\n\t\t\t// signal is the file's mtime falling outside the staleness\n\t\t\t// window (`DEFAULT_SWEEP_POLICY.staleAfterMillis`, shared\n\t\t\t// with the roster sweep). Without this branch, a mid-write\n\t\t\t// crash keeps every peer blocked for the full 5s claim\n\t\t\t// window — see review fix phase 22f (cross-process).\n\t\t\tlet reclaimed = false;\n\t\t\tif (lastHolder !== null) {\n\t\t\t\t// Dead-PID reclaim: gated on a liveness probe, so the\n\t\t\t\t// holder cannot have written a fresh body — a plain\n\t\t\t\t// unlink is safe here (no TOCTOU). An alive holder falls\n\t\t\t\t// through to the backoff below.\n\t\t\t\tconst status = yield* checkHolderLiveness(lastHolder).pipe(\n\t\t\t\t\tEffect.catch(() => Effect.succeed('alive' as const)),\n\t\t\t\t);\n\t\t\t\tif (status === 'dead') {\n\t\t\t\t\tyield* Effect.try({\n\t\t\t\t\t\ttry: () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tunlinkSync(path);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Race with another reclaimer — ok.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t\t\t});\n\t\t\t\t\treclaimed = true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Unparseable body: reclaim ONLY through the re-stat\n\t\t\t\t// guard. A bare mtime read + unlink races a competitor\n\t\t\t\t// who legitimately reclaims the garbage and writes a\n\t\t\t\t// fresh valid O_EXCL body in the window — the unlink\n\t\t\t\t// would clobber that LIVE lock (two simultaneous\n\t\t\t\t// holders). `reclaimUnparseableStaleFile` re-confirms the\n\t\t\t\t// file is still the same stale, unparseable inode\n\t\t\t\t// immediately before unlinking; any other outcome leaves\n\t\t\t\t// the file untouched and we fall through to back off.\n\t\t\t\tconst outcome = yield* Effect.try({\n\t\t\t\t\ttry: () => reclaimUnparseableStaleFile(path, parseLockBody),\n\t\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t\t});\n\t\t\t\treclaimed = outcome === 'reclaimed';\n\t\t\t}\n\t\t\tif (reclaimed) {\n\t\t\t\t// O_EXCL atomicity alone arbitrates the post-reclaim\n\t\t\t\t// race; reset the backoff so the contest starts fresh\n\t\t\t\t// (the prior growth was driven by a now-evicted dead\n\t\t\t\t// holder).\n\t\t\t\tbackoff = INITIAL_BACKOFF_MILLIS;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Peer holds an alive lock — back off exponentially.\n\t\t\tyield* underLiveClock(Effect.sleep(`${backoff} millis`));\n\t\t\tbackoff = Math.min(backoff * 2, MAX_BACKOFF_MILLIS);\n\t\t}\n\t}).pipe(Effect.withSpan('cross-process.stack-lock.acquire'));\n"],"mappings":";;;;;;;;;;;;;AA0CA,IAAa,wBAAb,cAA2C,KAAK,YAAY,wBAAwB,CAIjF;;AAGH,IAAa,mBAAb,cAAsC,KAAK,YAAY,mBAAmB,CAGvE;;;;;;;AAcH,MAAM,sBAAsB,OAAO,OAAO;CACzC,KAAK,OAAO;CACZ,WAAW,OAAO,OAAO,OAAO,OAAO;CACvC,UAAU,OAAO;CACjB,WAAW,OAAO;CAClB,aAAa,OAAO;CACpB,QAAQ,OAAO,SAAS,CAAC,UAAU,WAAW,CAAC;CAC/C,CAAC;AAEF,MAAM,iBAAiB,QACtB,iCAAiC,KAAK,qBAAqB,aAAa;;;AAQzE,MAAa,iCAAiC;;;;;;;AAQ9C,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;;;;;;;;;AAU3B,MAAM,kBACL,SAC0F;CAC1F,MAAM,OAAO,WAAW;AASxB,WAAU,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAC7C,KAAI;AACH,gBAAc,MAAM,KAAK,UAAU,KAAK,EAAE,EAAE,MAAM,MAAM,CAAC;AACzD,SAAO,EAAE,IAAI,MAAM;UACX,KAAK;AAEb,MADc,IAA8B,SAC/B,SACZ,OAAM;AAGP,MAAI,CAAC,WAAW,KAAK,CAGpB,QAAO;GAAE,IAAI;GAAO,QAAQ;GAAM;AAEnC,MAAI;AAEH,UAAO;IAAE,IAAI;IAAO,QAAQ,cADhB,aAAa,MAAM,OACc,CAAC;IAAE;UACzC;AACP,UAAO;IAAE,IAAI;IAAO,QAAQ;IAAM;;;;;;;;;;;;;;;;;;;AAoBrC,MAAa,oBACZ,MACA,gBAAwB,mCAExB,OAAO,IAAI,aAAa;AACvB,QAAO,OAAO,oBAAoB;GAChC,SAAS,gBAAgB;GACzB,SAAS,yBAAyB;EACnC,CAAC;CACF,MAAM,YAAY,KAAK,KAAK;CAC5B,IAAI,UAAU;CACd,IAAI,aAAkC;CACtC,IAAI,gBAA+B;AACnC,QAAO,MAAM;EACZ,MAAM,UAAU,KAAK,KAAK,GAAG;AAC7B,MAAI,UAAU,cACb,QAAO,OAAO,OAAO,KACpB,IAAI,sBAAsB;GAAE;GAAM,cAAc;GAAS,QAAQ;GAAY,CAAC,CAC9E;EAEF,MAAM,UAAU,OAAO,OAAO,IAAI;GACjC,WAAW,eAAe,KAAK;GAC/B,QAAQ,UAAU,IAAI,iBAAiB;IAAE;IAAM;IAAO,CAAC;GACvD,CAAC;AACF,MAAI,QAAQ,IAAI;AAEf,UAAO,OAAO,mBACb,OAAO,WAAW;AACjB,QAAI;AACH,gBAAW,KAAK;YACT;KAIP,CACF;AACD;;AAED,eAAa,QAAQ;EASrB,MAAM,mBAAmB,YAAY,OAAO;AAC5C,MAAI,qBAAqB,eAAe;AACvC,aAAU;AACV,mBAAgB;;EAWjB,IAAI,YAAY;AAChB,MAAI,eAAe;QAQd,OAHkB,oBAAoB,WAAW,CAAC,KACrD,OAAO,YAAY,OAAO,QAAQ,QAAiB,CAAC,CACpD,MACc,QAAQ;AACtB,WAAO,OAAO,IAAI;KACjB,WAAW;AACV,UAAI;AACH,kBAAW,KAAK;cACT;AAGR,aAAO;;KAER,QAAQ,UAAU,IAAI,iBAAiB;MAAE;MAAM;MAAO,CAAC;KACvD,CAAC;AACF,gBAAY;;QAgBb,cAAY,OAJW,OAAO,IAAI;GACjC,WAAW,4BAA4B,MAAM,cAAc;GAC3D,QAAQ,UAAU,IAAI,iBAAiB;IAAE;IAAM;IAAO,CAAC;GACvD,CAAC,MACsB;AAEzB,MAAI,WAAW;AAKd,aAAU;AACV;;AAGD,SAAO,eAAe,OAAO,MAAM,GAAG,QAAQ,SAAS,CAAC;AACxD,YAAU,KAAK,IAAI,UAAU,GAAG,mBAAmB;;EAEnD,CAAC,KAAK,OAAO,SAAS,mCAAmC,CAAC"}
|
|
1
|
+
{"version":3,"file":"stack-lock.mjs","names":[],"sources":["../../../../src/substrate/runtime/cross-process/stack-lock.ts"],"sourcesContent":["// `stack.lock` — O_EXCL exclusive lock for short critical sections.\n//\n// Architecture § Cross-process safety protocol § \"What is locked\":\n// - `stack.lock` — OS-advisory exclusive lock file. Used only for\n// short critical sections (roster mutations, the snapshot bounce).\n// Acquired via `flock(LOCK_EX)` on Unix and `LockFileEx` on Windows;\n// never held across a long operation.\n//\n// The implementation here uses a portable O_EXCL-create dance because\n// `flock` isn't available cross-platform out of `node:fs`. The\n// architecture's wording (\"OS-advisory exclusive lock file\") covers\n// both `flock` and `O_EXCL`-with-pid-body; the latter is what every\n// portable POSIX tool reaches for when `flock` isn't on the table.\n//\n// Discipline:\n// - The lock is held BRIEFLY — within a single Effect.scoped block\n// that mutates the roster or spans the snapshot bounce.\n// - Stale locks (owner crashed under the lock) are reclaimed via the\n// PID + start-time liveness check before the acquire reattempts.\n// - Acquire retries with exponential backoff up to 5 seconds total\n// (architecture § Claim protocol step 1).\n\nimport { mkdirSync, unlinkSync, readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nimport { Data, Effect, Schema, Scope } from 'effect';\n\nimport { type RosterHolder } from '../../cross-process.ts';\nimport { parseVersionedDocumentBodyOrNull } from '../../versioned-doc-sync.ts';\nimport { underLiveClock } from './live-clock.ts';\nimport { checkHolderLiveness, ownHolder } from './liveness.ts';\nimport { reclaimUnparseableStaleFile } from './reclaim-stale-file.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\n/** Tagged failure: acquire window elapsed and the lock is still held.\n * Carries the in-disk body (if parseable) so the caller can name the\n * blocking peer in user-facing error reporting. */\nexport class StackLockTimeoutError extends Data.TaggedError('StackLockTimeoutError')<{\n\treadonly path: string;\n\treadonly waitedMillis: number;\n\treadonly holder: RosterHolder | null;\n}> {}\n\n/** Tagged failure: I/O error other than EEXIST during acquire. */\nexport class StackLockIoError extends Data.TaggedError('StackLockIoError')<{\n\treadonly path: string;\n\treadonly cause: unknown;\n}> {}\n\nexport type StackLockError = StackLockTimeoutError | StackLockIoError;\n\n// -----------------------------------------------------------------------------\n// Codec\n// -----------------------------------------------------------------------------\n\n/** The on-disk body is the same `RosterHolder` shape — re-using the schema\n * means roster sweep + stack-lock reclaim see the same fields.\n *\n * `startTime` mirrors `RosterHolderSchema`: `number | null` so an\n * unprobable platform's lock body round-trips cleanly through the\n * decoder. The shared liveness predicate honors null conservatively. */\nconst StackLockBodySchema = Schema.Struct({\n\tpid: Schema.Number,\n\tstartTime: Schema.NullOr(Schema.Number),\n\thostname: Schema.String,\n\tclaimedAt: Schema.Number,\n\theartbeatAt: Schema.Number,\n\tintent: Schema.Literals(['normal', 'snapshot']),\n});\n\nconst parseLockBody = (raw: string): RosterHolder | null =>\n\tparseVersionedDocumentBodyOrNull(raw, StackLockBodySchema, 'stack.lock');\n\n// -----------------------------------------------------------------------------\n// Acquire / release\n// -----------------------------------------------------------------------------\n\n/** Default acquire timeout — architecture § Claim protocol step 1\n * (\"Block up to 5 seconds; if unavailable, retry with backoff\"). */\nexport const DEFAULT_ACQUIRE_TIMEOUT_MILLIS = 5_000;\n\n/** Per-attempt initial wait. Doubles each retry up to the cap. The\n * backoff also resets whenever the holder identity changes — see the\n * reclaim loop comment for the contention story (review fix phase\n * 22f). The cap is intentionally tight (200ms) so peers react quickly\n * to a release; combined with the O_EXCL-arbitrated retry on reclaim,\n * no staggering jitter is needed. */\nconst INITIAL_BACKOFF_MILLIS = 25;\nconst MAX_BACKOFF_MILLIS = 200;\n\n/**\n * Sync attempt at O_EXCL-create with the lock's JSON body. Returns\n * whether we own the file now; on EEXIST it probes the existing body\n * through `parseLockBody`.\n *\n * Effect-platform's FileSystem doesn't expose a sync `open` shape, but\n * the critical-section discipline says we need a non-blocking attempt\n * inside a retry loop. Falling through to Node sync APIs here is the\n * cleanest path; the rest of the substrate stays Effect-native.\n */\nconst tryAcquireSync = (\n\tpath: string,\n\tbody: RosterHolder,\n): { readonly ok: true } | { readonly ok: false; readonly holder: RosterHolder | null } => {\n\t// The lock's parent directory may not exist on first-claim of a\n\t// fresh runtime root — devstack's `<runtimeRoot>/stacks/<stack>/` is\n\t// the substrate's responsibility to bring into being, but no other\n\t// L0 subsystem touches the disk until the first claim. `mkdir -p`\n\t// here is idempotent and harmless on the warm path; it transforms\n\t// the otherwise-fatal ENOENT-during-write into a clean acquire.\n\t// Architecture § Cross-process safety protocol: stack root is\n\t// created by whichever subsystem first reaches for a path under it.\n\tmkdirSync(dirname(path), { recursive: true });\n\ttry {\n\t\twriteFileSync(path, JSON.stringify(body), { flag: 'wx' });\n\t\treturn { ok: true };\n\t} catch (err) {\n\t\tconst code = (err as NodeJS.ErrnoException).code;\n\t\tif (code !== 'EEXIST') {\n\t\t\tthrow err;\n\t\t}\n\t\t// Peer holds it (or stale). Probe the existing body.\n\t\tif (!existsSync(path)) {\n\t\t\t// Race: holder unlinked between our EEXIST and the existsSync.\n\t\t\t// Retry on the next loop iteration.\n\t\t\treturn { ok: false, holder: null };\n\t\t}\n\t\ttry {\n\t\t\tconst raw = readFileSync(path, 'utf8');\n\t\t\treturn { ok: false, holder: parseLockBody(raw) };\n\t\t} catch {\n\t\t\treturn { ok: false, holder: null };\n\t\t}\n\t}\n};\n\n/**\n * Acquire `stack.lock` as a scoped resource. The lock is released when\n * the surrounding Scope closes (the unlink finalizer is registered INSIDE\n * this scope, so every caller — roster claim/release/heartbeat, the\n * snapshot bounce, command-channel append — releases on scope close,\n * never bypassed). Yields a `void` resource — the caller MUST keep its\n * Scope tight; the architecture forbids holding the lock across long\n * operations.\n *\n * The O_EXCL retry loop: exponential backoff up to `timeoutMillis`\n * (default 5s). On every miss, probe the holder's liveness — dead holders\n * are reclaimed (unlink + loop); an unparseable body is reclaimed only\n * through the shared re-stat guard once it ages past\n * `DEFAULT_SWEEP_POLICY.staleAfterMillis`. Backoff resets on holder\n * change and on reclaim. The reclaim path is the architecture's \"stale\n * lock\" handling — same PID + start-time semantics as the roster sweep,\n * so a process that crashed under the lock never blocks the next peer\n * indefinitely.\n */\nexport const acquireStackLock = (\n\tpath: string,\n\ttimeoutMillis: number = DEFAULT_ACQUIRE_TIMEOUT_MILLIS,\n): Effect.Effect<void, StackLockError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst addUnlinkFinalizer = Effect.addFinalizer(() =>\n\t\t\tEffect.sync(() => {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(path);\n\t\t\t\t} catch {\n\t\t\t\t\t// Already gone — ok. Crash-during-release is handled by\n\t\t\t\t\t// the next peer's stale-PID reclaim.\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t\tconst startedAt = Date.now();\n\t\tlet backoff = INITIAL_BACKOFF_MILLIS;\n\t\tlet lastHolder: RosterHolder | null = null;\n\t\tlet prevHolderPid: number | null = null;\n\t\twhile (true) {\n\t\t\tconst elapsed = Date.now() - startedAt;\n\t\t\tif (elapsed > timeoutMillis) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew StackLockTimeoutError({ path, waitedMillis: elapsed, holder: lastHolder }),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst attempt = yield* Effect.try({\n\t\t\t\ttry: () => tryAcquireSync(path, ownHolder()),\n\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t});\n\t\t\tif (attempt.ok) {\n\t\t\t\t// Register a finalizer that unlinks the lock on scope close.\n\t\t\t\tyield* addUnlinkFinalizer;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlastHolder = attempt.holder;\n\t\t\t// Reset the backoff whenever the holder identity changes —\n\t\t\t// a new holder means the previous one released, and our\n\t\t\t// long-saturated backoff (driven by waiting on the prior\n\t\t\t// holder) is now stale. Without this, late waiters under\n\t\t\t// heavy contention saturate at MAX_BACKOFF_MILLIS while\n\t\t\t// each peer holds the lock only briefly, exhausting the\n\t\t\t// claim budget before they can win (review fix phase 22f\n\t\t\t// reclaim-stress reproducer caught the case).\n\t\t\tconst currentHolderPid = lastHolder !== null ? lastHolder.pid : null;\n\t\t\tif (currentHolderPid !== prevHolderPid) {\n\t\t\t\tbackoff = INITIAL_BACKOFF_MILLIS;\n\t\t\t\tprevHolderPid = currentHolderPid;\n\t\t\t}\n\t\t\t// Reclaim if the holder is dead OR the lock body is\n\t\t\t// unparseable AND old enough to be presumed-abandoned. The\n\t\t\t// unparseable case covers a peer that died mid-write: the\n\t\t\t// PID liveness check has nothing to consult, so the only\n\t\t\t// signal is the file's mtime falling outside the staleness\n\t\t\t// window (`DEFAULT_SWEEP_POLICY.staleAfterMillis`, shared\n\t\t\t// with the roster sweep). Without this branch, a mid-write\n\t\t\t// crash keeps every peer blocked for the full 5s claim\n\t\t\t// window — see review fix phase 22f (cross-process).\n\t\t\tlet reclaimed = false;\n\t\t\tif (lastHolder !== null) {\n\t\t\t\t// Dead-PID reclaim: gated on a liveness probe, so the\n\t\t\t\t// holder cannot have written a fresh body — a plain\n\t\t\t\t// unlink is safe here (no TOCTOU). An alive holder falls\n\t\t\t\t// through to the backoff below.\n\t\t\t\tconst status = yield* checkHolderLiveness(lastHolder).pipe(\n\t\t\t\t\tEffect.catch(() => Effect.succeed('alive' as const)),\n\t\t\t\t);\n\t\t\t\tif (status === 'dead') {\n\t\t\t\t\tyield* Effect.try({\n\t\t\t\t\t\ttry: () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tunlinkSync(path);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Race with another reclaimer — ok.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t\t\t});\n\t\t\t\t\treclaimed = true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Unparseable body: reclaim ONLY through the re-stat\n\t\t\t\t// guard. A bare mtime read + unlink races a competitor\n\t\t\t\t// who legitimately reclaims the garbage and writes a\n\t\t\t\t// fresh valid O_EXCL body in the window — the unlink\n\t\t\t\t// would clobber that LIVE lock (two simultaneous\n\t\t\t\t// holders). `reclaimUnparseableStaleFile` re-confirms the\n\t\t\t\t// file is still the same stale, unparseable inode\n\t\t\t\t// immediately before unlinking; any other outcome leaves\n\t\t\t\t// the file untouched and we fall through to back off.\n\t\t\t\tconst outcome = yield* Effect.try({\n\t\t\t\t\ttry: () => reclaimUnparseableStaleFile(path, parseLockBody),\n\t\t\t\t\tcatch: (cause) => new StackLockIoError({ path, cause }),\n\t\t\t\t});\n\t\t\t\treclaimed = outcome === 'reclaimed';\n\t\t\t}\n\t\t\tif (reclaimed) {\n\t\t\t\t// O_EXCL atomicity alone arbitrates the post-reclaim\n\t\t\t\t// race; reset the backoff so the contest starts fresh\n\t\t\t\t// (the prior growth was driven by a now-evicted dead\n\t\t\t\t// holder).\n\t\t\t\tbackoff = INITIAL_BACKOFF_MILLIS;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Peer holds an alive lock — back off exponentially.\n\t\t\tyield* underLiveClock(Effect.sleep(`${backoff} millis`));\n\t\t\tbackoff = Math.min(backoff * 2, MAX_BACKOFF_MILLIS);\n\t\t}\n\t});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"current-plugin.mjs","names":[],"sources":["../../../src/substrate/runtime/current-plugin.ts"],"sourcesContent":["import { Context, Effect, Option } from 'effect';\n\nimport type { PluginKey } from '../brand.ts';\nimport type { PhaseNarration } from '../lifecycle.ts';\n\n/** Acquire-time identity for the plugin currently being supervised.\n *\n * Plugins that stream host-process output need the supervisor-minted\n * row key so logger lines publish as `log.appended` events for the\n * correct projection row. */\nexport interface CurrentPluginKeyShape {\n\treadonly key: PluginKey;\n}\n\nexport class CurrentPluginKey extends Context.Service<CurrentPluginKey, CurrentPluginKeyShape>()(\n\t'@devstack/substrate/CurrentPluginKey',\n) {}\n\nexport interface CurrentPluginProgressShape {\n\treadonly setPhase: (phase: PhaseNarration | null) => Effect.Effect<void>;\n}\n\nexport class CurrentPluginProgress extends Context.Service<\n\tCurrentPluginProgress,\n\tCurrentPluginProgressShape\n>()('@devstack/substrate/CurrentPluginProgress') {}\n\n/** Publish acquire-time row narration for the currently supervised\n * plugin. Safe to call from plugin code: if the caller is running\n * outside a supervisor, the helper is a no-op. */\nexport const setCurrentPluginPhase = (phase: PhaseNarration | null): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tconst progress = yield* Effect.serviceOption(CurrentPluginProgress);\n\t\tif (Option.isSome(progress)) {\n\t\t\tyield* progress.value.setPhase(phase);\n\t\t}\n\t});\n"],"mappings":";;AAcA,IAAa,mBAAb,cAAsC,QAAQ,
|
|
1
|
+
{"version":3,"file":"current-plugin.mjs","names":[],"sources":["../../../src/substrate/runtime/current-plugin.ts"],"sourcesContent":["import { Context, Effect, Option } from 'effect';\n\nimport type { PluginKey } from '../brand.ts';\nimport type { PhaseNarration } from '../lifecycle.ts';\n\n/** Acquire-time identity for the plugin currently being supervised.\n *\n * Plugins that stream host-process output need the supervisor-minted\n * row key so logger lines publish as `log.appended` events for the\n * correct projection row. */\nexport interface CurrentPluginKeyShape {\n\treadonly key: PluginKey;\n}\n\nexport class CurrentPluginKey extends Context.Service<CurrentPluginKey, CurrentPluginKeyShape>()(\n\t'@devstack/substrate/CurrentPluginKey',\n) {}\n\nexport interface CurrentPluginProgressShape {\n\treadonly setPhase: (phase: PhaseNarration | null) => Effect.Effect<void>;\n}\n\nexport class CurrentPluginProgress extends Context.Service<\n\tCurrentPluginProgress,\n\tCurrentPluginProgressShape\n>()('@devstack/substrate/CurrentPluginProgress') {}\n\n/** Publish acquire-time row narration for the currently supervised\n * plugin. Safe to call from plugin code: if the caller is running\n * outside a supervisor, the helper is a no-op. */\nexport const setCurrentPluginPhase = (phase: PhaseNarration | null): Effect.Effect<void> =>\n\tEffect.gen(function* () {\n\t\tconst progress = yield* Effect.serviceOption(CurrentPluginProgress);\n\t\tif (Option.isSome(progress)) {\n\t\t\tyield* progress.value.setPhase(phase);\n\t\t}\n\t});\n"],"mappings":";;AAcA,IAAa,mBAAb,cAAsC,QAAQ,QAAiD,CAAC,CAC/F,sCACD,CAAC,CAAC,CAAC;AAMH,IAAa,wBAAb,cAA2C,QAAQ,QAGjD,CAAC,CAAC,2CAA2C,CAAC,CAAC,CAAC;;;;AAKlD,MAAa,yBAAyB,UACrC,OAAO,IAAI,aAAa;CACvB,MAAM,WAAW,OAAO,OAAO,cAAc,qBAAqB;CAClE,IAAI,OAAO,OAAO,QAAQ,GACzB,OAAO,SAAS,MAAM,SAAS,KAAK;AAEtC,CAAC"}
|
|
@@ -15,18 +15,6 @@ var AtomicWriteFailed = class extends Schema.TaggedErrorClass()("AtomicWriteFail
|
|
|
15
15
|
]),
|
|
16
16
|
cause: Schema.optional(Schema.Defect)
|
|
17
17
|
}) {};
|
|
18
|
-
/** State-store I/O / corruption surface. Distinct from
|
|
19
|
-
* `AtomicWriteFailed` so consumers can recover ("re-init on
|
|
20
|
-
* corruption") without catching write errors. */
|
|
21
|
-
var StateStoreError = class extends Schema.TaggedErrorClass()("StateStoreError", {
|
|
22
|
-
reason: Schema.Literals([
|
|
23
|
-
"io-failed",
|
|
24
|
-
"corruption",
|
|
25
|
-
"lock-contention"
|
|
26
|
-
]),
|
|
27
|
-
detail: Schema.String,
|
|
28
|
-
cause: Schema.optional(Schema.Defect)
|
|
29
|
-
}) {};
|
|
30
18
|
/** Cache surface — same tri-state as the L0 cache contract. */
|
|
31
19
|
var CacheError = class extends Schema.TaggedErrorClass()("CacheError", {
|
|
32
20
|
reason: Schema.Literals([
|
|
@@ -77,6 +65,6 @@ var PortBrokerError = class extends Schema.TaggedErrorClass()("PortBrokerError",
|
|
|
77
65
|
cause: Schema.optional(Schema.Defect)
|
|
78
66
|
}) {};
|
|
79
67
|
//#endregion
|
|
80
|
-
export { AtomicWriteFailed, CacheError, PortBrokerError,
|
|
68
|
+
export { AtomicWriteFailed, CacheError, PortBrokerError, StrategyNotFoundError };
|
|
81
69
|
|
|
82
70
|
//# sourceMappingURL=errors.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.mjs","names":[],"sources":["../../../src/substrate/runtime/errors.ts"],"sourcesContent":["// Shared tagged errors for the runtime persistence layer.\n//\n// Per-subsystem tags so `Effect.catchTags({ ... })` reads at the call\n// site as \"what did the
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../../../src/substrate/runtime/errors.ts"],"sourcesContent":["// Shared tagged errors for the runtime persistence layer.\n//\n// Per-subsystem tags so `Effect.catchTags({ ... })` reads at the call\n// site as \"what did the cache / registry just fail with\".\n\nimport { Schema } from 'effect';\n\n/** Atomic-write failure — one tag covers every stage. The `stage`\n * field discriminates inside renderer messages without\n * proliferating tag types. */\nexport class AtomicWriteFailed extends Schema.TaggedErrorClass<AtomicWriteFailed>()(\n\t'AtomicWriteFailed',\n\t{\n\t\tpath: Schema.String,\n\t\tstage: Schema.Literals(['mkdir-parent', 'open-temp', 'write', 'fsync', 'rename', 'encode']),\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\n/** Cache surface — same tri-state as the L0 cache contract. */\nexport class CacheError extends Schema.TaggedErrorClass<CacheError>()('CacheError', {\n\treason: Schema.Literals(['io-failed', 'corruption', 'lock-contention']),\n\tdetail: Schema.String,\n\tcause: Schema.optional(Schema.Defect),\n}) {}\n\n/** Strategy registry missing-key surface. Canonical tagged error\n * yielded from `StrategyRegistry.get` when no contributor is\n * registered under the requested capability key. The\n * `contracts/strategy-contributor.ts` `StrategyRegistry` interface\n * imports this class as its `E` channel — one `_tag` literal,\n * one class, package-wide. */\nexport class StrategyNotFoundError extends Schema.TaggedErrorClass<StrategyNotFoundError>()(\n\t'StrategyNotFoundError',\n\t{\n\t\tcapabilityKey: Schema.String,\n\t\tregisteredKeys: Schema.Array(Schema.String),\n\t},\n) {}\n\n/** Port-broker allocation failure — surfaced when no free port could be\n * found in the configured range, when a caller-supplied `preferredPort`\n * is already taken by another in-process allocation, or when the bind-\n * probe rejected every candidate.\n *\n * `reason` discriminates the failure mode for renderer mapping:\n * - `no-free-port` : scanned the whole window for `kind`, every\n * candidate failed the bind-probe (EADDRINUSE) or\n * was already held by a sibling allocation in\n * this process.\n * - `preferred-busy`: caller supplied a `preferredPort` and the\n * port is currently held by another in-process\n * allocation (refused per architecture §6).\n * - `bind-probe-failed`: the OS-level bind probe surfaced an error\n * OTHER than `EADDRINUSE` (e.g. EACCES on a\n * privileged port, EPERM under jail). Caller\n * should fall back to user opt-in / docs.\n * - `reservation-failed`: the runtime-root scoped port reservation\n * file could not be created/read/reclaimed. */\nexport class PortBrokerError extends Schema.TaggedErrorClass<PortBrokerError>()('PortBrokerError', {\n\treason: Schema.Literals([\n\t\t'no-free-port',\n\t\t'preferred-busy',\n\t\t'bind-probe-failed',\n\t\t'reservation-failed',\n\t]),\n\tdetail: Schema.String,\n\tcause: Schema.optional(Schema.Defect),\n}) {}\n"],"mappings":";;;;;AAUA,IAAa,oBAAb,cAAuC,OAAO,iBAAoC,CAAC,CAClF,qBACA;CACC,MAAM,OAAO;CACb,OAAO,OAAO,SAAS;EAAC;EAAgB;EAAa;EAAS;EAAS;EAAU;CAAQ,CAAC;CAC1F,OAAO,OAAO,SAAS,OAAO,MAAM;AACrC,CACD,CAAC,CAAC,CAAC;;AAGH,IAAa,aAAb,cAAgC,OAAO,iBAA6B,CAAC,CAAC,cAAc;CACnF,QAAQ,OAAO,SAAS;EAAC;EAAa;EAAc;CAAiB,CAAC;CACtE,QAAQ,OAAO;CACf,OAAO,OAAO,SAAS,OAAO,MAAM;AACrC,CAAC,CAAC,CAAC,CAAC;;;;;;;AAQJ,IAAa,wBAAb,cAA2C,OAAO,iBAAwC,CAAC,CAC1F,yBACA;CACC,eAAe,OAAO;CACtB,gBAAgB,OAAO,MAAM,OAAO,MAAM;AAC3C,CACD,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;AAqBH,IAAa,kBAAb,cAAqC,OAAO,iBAAkC,CAAC,CAAC,mBAAmB;CAClG,QAAQ,OAAO,SAAS;EACvB;EACA;EACA;EACA;CACD,CAAC;CACD,QAAQ,OAAO;CACf,OAAO,OAAO,SAAS,OAAO,MAAM;AACrC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-unknown-error.mjs","names":[],"sources":["../../../src/substrate/runtime/format-unknown-error.ts"],"sourcesContent":["// Shared `unknown → string` projector for plugin error messages.\n//\n// Lifted from 5 identical local definitions across mode/local.ts,\n// mode/shared-boot.ts, mode/fork.ts, mode/external.ts, chain-probe.ts,\n// fork-transaction.ts. Callers concatenate this into plugin-tagged\n// error `message` fields; cascade-formatter walks the actual `cause`\n// separately for structured rendering.\n//\n// NB: this does NOT stringify Effect's `Cause` type — it projects an\n// arbitrary `unknown` (typically a caught throwable) to a short\n// string suitable for splicing into a human-readable error message.\n\n// Bounds the `.cause` walk so a self-referential or pathologically deep\n// chain can't produce a runaway string (or loop forever).\nconst MAX_CAUSE_DEPTH = 5;\n\n// A nested `.cause` only earns a place in the spliced message when it\n// carries a human-readable string of its own — an `Error`, a string, or\n// a tagged object with a `.message`. Raw detail bags (e.g. `{ sender,\n// objectCount }`) are skipped here so they don't dump JSON into the\n// headline message; the structured cascade-formatter still renders them.\nconst nestedCauseMessage = (cause: unknown, depth: number): string | null => {\n\tif (cause instanceof Error) return project(cause, depth);\n\tif (typeof cause === 'string') return cause.length > 0 ? cause : null;\n\tif (typeof cause === 'object' && cause !== null) {\n\t\tconst message = (cause as { readonly message?: unknown }).message;\n\t\tif (typeof message === 'string' && message.length > 0) {\n\t\t\treturn project(cause, depth);\n\t\t}\n\t}\n\treturn null;\n};\n\nconst project = (cause: unknown, depth: number): string => {\n\tif (typeof cause === 'string') return cause;\n\t// `Error` instances AND tagged devstack errors (`AccountSignError`,\n\t// `SuiPluginError`, … — plain objects, NOT `Error`s, so `String(obj)`\n\t// yields `[object Object]`) both carry a string `.message` and may carry\n\t// a `.cause`. Prefer the message, then chain the `.cause` so a generic\n\t// wrapper (\"… submit failed.\") still surfaces the actionable root\n\t// (\"… no SUI gas coins found for 0x…\"). `Error.cause` is read the same\n\t// way, so a thrown `new Error(msg, { cause })` chain is walked too.\n\tconst tagged = cause as { readonly message?: unknown; readonly cause?: unknown };\n\tconst message =\n\t\tcause instanceof Error\n\t\t\t? cause.message\n\t\t\t: typeof cause === 'object' && cause !== null && typeof tagged.message === 'string'\n\t\t\t\t? tagged.message\n\t\t\t\t: undefined;\n\tif (message !== undefined && message.length > 0) {\n\t\tconst nested = depth < MAX_CAUSE_DEPTH ? nestedCauseMessage(tagged.cause, depth + 1) : null;\n\t\treturn nested !== null && !message.includes(nested)
|
|
1
|
+
{"version":3,"file":"format-unknown-error.mjs","names":[],"sources":["../../../src/substrate/runtime/format-unknown-error.ts"],"sourcesContent":["// Shared `unknown → string` projector for plugin error messages.\n//\n// Lifted from 5 identical local definitions across mode/local.ts,\n// mode/shared-boot.ts, mode/fork.ts, mode/external.ts, chain-probe.ts,\n// fork-transaction.ts. Callers concatenate this into plugin-tagged\n// error `message` fields; cascade-formatter walks the actual `cause`\n// separately for structured rendering.\n//\n// NB: this does NOT stringify Effect's `Cause` type — it projects an\n// arbitrary `unknown` (typically a caught throwable) to a short\n// string suitable for splicing into a human-readable error message.\n\n// Bounds the `.cause` walk so a self-referential or pathologically deep\n// chain can't produce a runaway string (or loop forever).\nconst MAX_CAUSE_DEPTH = 5;\n\n// A nested `.cause` only earns a place in the spliced message when it\n// carries a human-readable string of its own — an `Error`, a string, or\n// a tagged object with a `.message`. Raw detail bags (e.g. `{ sender,\n// objectCount }`) are skipped here so they don't dump JSON into the\n// headline message; the structured cascade-formatter still renders them.\nconst nestedCauseMessage = (cause: unknown, depth: number): string | null => {\n\tif (cause instanceof Error) return project(cause, depth);\n\tif (typeof cause === 'string') return cause.length > 0 ? cause : null;\n\tif (typeof cause === 'object' && cause !== null) {\n\t\tconst message = (cause as { readonly message?: unknown }).message;\n\t\tif (typeof message === 'string' && message.length > 0) {\n\t\t\treturn project(cause, depth);\n\t\t}\n\t}\n\treturn null;\n};\n\nconst project = (cause: unknown, depth: number): string => {\n\tif (typeof cause === 'string') return cause;\n\t// `Error` instances AND tagged devstack errors (`AccountSignError`,\n\t// `SuiPluginError`, … — plain objects, NOT `Error`s, so `String(obj)`\n\t// yields `[object Object]`) both carry a string `.message` and may carry\n\t// a `.cause`. Prefer the message, then chain the `.cause` so a generic\n\t// wrapper (\"… submit failed.\") still surfaces the actionable root\n\t// (\"… no SUI gas coins found for 0x…\"). `Error.cause` is read the same\n\t// way, so a thrown `new Error(msg, { cause })` chain is walked too.\n\tconst tagged = cause as { readonly message?: unknown; readonly cause?: unknown };\n\tconst message =\n\t\tcause instanceof Error\n\t\t\t? cause.message\n\t\t\t: typeof cause === 'object' && cause !== null && typeof tagged.message === 'string'\n\t\t\t\t? tagged.message\n\t\t\t\t: undefined;\n\tif (message !== undefined && message.length > 0) {\n\t\tconst nested = depth < MAX_CAUSE_DEPTH ? nestedCauseMessage(tagged.cause, depth + 1) : null;\n\t\treturn nested !== null && !message.includes(nested) ? `${message} [cause: ${nested}]` : message;\n\t}\n\ttry {\n\t\treturn JSON.stringify(cause);\n\t} catch {\n\t\treturn String(cause);\n\t}\n};\n\nexport const formatUnknownError = (cause: unknown): string => project(cause, 0);\n"],"mappings":";AAcA,MAAM,kBAAkB;AAOxB,MAAM,sBAAsB,OAAgB,UAAiC;CAC5E,IAAI,iBAAiB,OAAO,OAAO,QAAQ,OAAO,KAAK;CACvD,IAAI,OAAO,UAAU,UAAU,OAAO,MAAM,SAAS,IAAI,QAAQ;CACjE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAChD,MAAM,UAAW,MAAyC;EAC1D,IAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,GACnD,OAAO,QAAQ,OAAO,KAAK;CAE7B;CACA,OAAO;AACR;AAEA,MAAM,WAAW,OAAgB,UAA0B;CAC1D,IAAI,OAAO,UAAU,UAAU,OAAO;CAQtC,MAAM,SAAS;CACf,MAAM,UACL,iBAAiB,QACd,MAAM,UACN,OAAO,UAAU,YAAY,UAAU,QAAQ,OAAO,OAAO,YAAY,WACxE,OAAO,UACP,KAAA;CACL,IAAI,YAAY,KAAA,KAAa,QAAQ,SAAS,GAAG;EAChD,MAAM,SAAS,QAAQ,kBAAkB,mBAAmB,OAAO,OAAO,QAAQ,CAAC,IAAI;EACvF,OAAO,WAAW,QAAQ,CAAC,QAAQ,SAAS,MAAM,IAAI,GAAG,QAAQ,WAAW,OAAO,KAAK;CACzF;CACA,IAAI;EACH,OAAO,KAAK,UAAU,KAAK;CAC5B,QAAQ;EACP,OAAO,OAAO,KAAK;CACpB;AACD;AAEA,MAAa,sBAAsB,UAA2B,QAAQ,OAAO,CAAC"}
|