@mysten-incubation/devstack 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dashboard-ui/assets/grpc-a4usE3Nk.js +3 -0
- package/dashboard-ui/assets/index-B82Bh84P.css +1 -0
- package/dashboard-ui/assets/index-CRYJ4pru.js +1277 -0
- package/dashboard-ui/index.html +2 -2
- package/dist/api/define-capabilities.d.mts +1 -20
- package/dist/api/define-capabilities.mjs +15 -22
- package/dist/api/define-capabilities.mjs.map +1 -1
- package/dist/api/define-devstack-with.d.mts +7 -1
- package/dist/api/define-devstack-with.mjs.map +1 -1
- package/dist/api/define-devstack.mjs.map +1 -1
- package/dist/api/define-plugin.d.mts +2 -1
- package/dist/api/inference-network.d.mts +31 -17
- package/dist/api/inference-network.mjs +40 -43
- package/dist/api/inference-network.mjs.map +1 -1
- package/dist/api/load-config.mjs +88 -0
- package/dist/api/load-config.mjs.map +1 -0
- package/dist/api/mode-narrowed-factory.d.mts +1 -1
- package/dist/api/mode-narrowed-factory.mjs.map +1 -1
- package/dist/api/run-stack-internal.mjs +202 -0
- package/dist/api/run-stack-internal.mjs.map +1 -0
- package/dist/api/run-stack.d.mts +82 -31
- package/dist/api/run-stack.mjs +22 -141
- package/dist/api/run-stack.mjs.map +1 -1
- package/dist/build-integrations/dapp-kit/index.d.mts +61 -0
- package/dist/build-integrations/dapp-kit/index.mjs +58 -0
- package/dist/build-integrations/dapp-kit/index.mjs.map +1 -0
- package/dist/build-integrations/playwright/config.d.mts +1 -30
- package/dist/build-integrations/playwright/config.mjs +1 -27
- package/dist/build-integrations/playwright/config.mjs.map +1 -1
- package/dist/build-integrations/playwright/errors.d.mts +4 -7
- package/dist/build-integrations/playwright/errors.mjs.map +1 -1
- package/dist/build-integrations/playwright/global-setup.d.mts +42 -71
- package/dist/build-integrations/playwright/global-setup.mjs +142 -218
- package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
- package/dist/build-integrations/playwright/index.d.mts +2 -2
- package/dist/build-integrations/playwright/index.mjs +2 -2
- package/dist/build-integrations/playwright/stack-context.d.mts +3 -1
- package/dist/build-integrations/playwright/stack-context.mjs +4 -2
- package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
- package/dist/build-integrations/playwright/wallet-context.mjs +44 -19
- package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
- package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -1
- package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
- package/dist/build-integrations/runtime/discover.d.mts +6 -4
- package/dist/build-integrations/runtime/discover.mjs +15 -4
- package/dist/build-integrations/runtime/discover.mjs.map +1 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
- package/dist/build-integrations/runtime/errors.mjs.map +1 -1
- package/dist/build-integrations/runtime/read-stack-context.mjs +3 -3
- package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +23 -7
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs +25 -12
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -1
- package/dist/build-integrations/runtime/stack-context.d.mts +2 -2
- package/dist/build-integrations/vite/index.d.mts +84 -19
- package/dist/build-integrations/vite/index.mjs +203 -28
- package/dist/build-integrations/vite/index.mjs.map +1 -1
- package/dist/build-integrations/vitest/config.d.mts +20 -2
- package/dist/build-integrations/vitest/config.mjs +16 -4
- package/dist/build-integrations/vitest/config.mjs.map +1 -1
- package/dist/build-integrations/vitest/env.d.mts +18 -10
- package/dist/build-integrations/vitest/env.mjs +11 -10
- package/dist/build-integrations/vitest/env.mjs.map +1 -1
- package/dist/build-integrations/vitest/errors.d.mts +7 -11
- package/dist/build-integrations/vitest/errors.mjs +3 -4
- package/dist/build-integrations/vitest/errors.mjs.map +1 -1
- package/dist/build-integrations/vitest/global-setup.d.mts +35 -0
- package/dist/build-integrations/vitest/global-setup.mjs +156 -0
- package/dist/build-integrations/vitest/global-setup.mjs.map +1 -0
- package/dist/build-integrations/vitest/index.d.mts +3 -2
- package/dist/build-integrations/vitest/index.mjs +3 -2
- package/dist/build-integrations/vitest/setup.mjs +2 -2
- package/dist/build-integrations/vitest/setup.mjs.map +1 -1
- package/dist/build-integrations/vitest/stack-context.d.mts +5 -3
- package/dist/build-integrations/vitest/stack-context.mjs +3 -2
- package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
- package/dist/cli/bail.mjs.map +1 -1
- package/dist/cli/doctor-probes.mjs +1 -3
- package/dist/cli/doctor-probes.mjs.map +1 -1
- package/dist/cli/main.d.mts +23 -1
- package/dist/cli/main.mjs +63 -8
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/prune-direct.mjs.map +1 -1
- package/dist/cli/snapshot-reader.mjs.map +1 -1
- package/dist/cli/up-lifecycle.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +28 -21
- package/dist/cli/wirings/apply.mjs.map +1 -1
- package/dist/cli/wirings/codegen.mjs +108 -0
- package/dist/cli/wirings/codegen.mjs.map +1 -0
- package/dist/cli/wirings/config-loader.mjs +12 -62
- package/dist/cli/wirings/config-loader.mjs.map +1 -1
- package/dist/cli/wirings/dump-ids.mjs +86 -0
- package/dist/cli/wirings/dump-ids.mjs.map +1 -0
- package/dist/cli/wirings/engine-command.mjs +1 -1
- package/dist/cli/wirings/engine-command.mjs.map +1 -1
- package/dist/cli/wirings/identity.mjs +2 -2
- package/dist/cli/wirings/identity.mjs.map +1 -1
- package/dist/cli/wirings/provide-file-system.mjs.map +1 -1
- package/dist/cli/wirings/read-devstack-version.mjs +17 -0
- package/dist/cli/wirings/read-devstack-version.mjs.map +1 -0
- package/dist/cli/wirings/snapshot.mjs +27 -34
- package/dist/cli/wirings/snapshot.mjs.map +1 -1
- package/dist/cli/wirings/up-ipc.mjs +147 -0
- package/dist/cli/wirings/up-ipc.mjs.map +1 -0
- package/dist/cli/wirings/up.mjs +218 -198
- package/dist/cli/wirings/up.mjs.map +1 -1
- package/dist/cli/wirings/wipe.mjs +1 -1
- package/dist/cli/wirings/wipe.mjs.map +1 -1
- package/dist/contracts/chain-probe.d.mts +1 -1
- package/dist/contracts/chain-probe.mjs +1 -1
- package/dist/contracts/chain-probe.mjs.map +1 -1
- package/dist/contracts/codegenable.d.mts +60 -6
- package/dist/contracts/codegenable.mjs +28 -0
- package/dist/contracts/codegenable.mjs.map +1 -0
- package/dist/contracts/config-bindings.mjs +162 -0
- package/dist/contracts/config-bindings.mjs.map +1 -0
- package/dist/contracts/container-runtime.d.mts +23 -17
- package/dist/contracts/plugin-expander.mjs.map +1 -1
- package/dist/contracts/snapshotable.d.mts +5 -7
- package/dist/contracts/wallet-protocol.d.mts +2 -5
- package/dist/contracts/wallet-protocol.mjs +2 -5
- package/dist/contracts/wallet-protocol.mjs.map +1 -1
- package/dist/index.d.mts +21 -26
- package/dist/index.mjs +8 -9
- package/dist/orchestrators/boot.d.mts +28 -0
- package/dist/orchestrators/boot.mjs +440 -0
- package/dist/orchestrators/boot.mjs.map +1 -0
- package/dist/orchestrators/codegen/bindings.d.mts +13 -1
- package/dist/orchestrators/codegen/bindings.mjs +23 -9
- package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
- package/dist/orchestrators/codegen/config-runtime.mjs +208 -0
- package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -0
- package/dist/orchestrators/codegen/emit.mjs +1 -1
- package/dist/orchestrators/codegen/emit.mjs.map +1 -1
- package/dist/orchestrators/codegen/errors.mjs +21 -1
- package/dist/orchestrators/codegen/errors.mjs.map +1 -1
- package/dist/orchestrators/codegen/format.mjs +2 -0
- package/dist/orchestrators/codegen/format.mjs.map +1 -1
- package/dist/orchestrators/codegen/gitignore.mjs +30 -11
- package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
- package/dist/orchestrators/codegen/id-config.mjs +107 -0
- package/dist/orchestrators/codegen/id-config.mjs.map +1 -0
- package/dist/orchestrators/codegen/output-location.mjs +23 -31
- package/dist/orchestrators/codegen/output-location.mjs.map +1 -1
- package/dist/orchestrators/codegen/paths.mjs +8 -1
- package/dist/orchestrators/codegen/paths.mjs.map +1 -1
- package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
- package/dist/orchestrators/codegen/service.mjs +286 -58
- package/dist/orchestrators/codegen/service.mjs.map +1 -1
- package/dist/orchestrators/layers.mjs +31 -0
- package/dist/orchestrators/layers.mjs.map +1 -0
- package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -1
- package/dist/orchestrators/lifecycle-prune/index.mjs +8 -10
- package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -1
- package/dist/orchestrators/network-options.d.mts +52 -0
- package/dist/orchestrators/network-options.mjs +43 -0
- package/dist/orchestrators/network-options.mjs.map +1 -0
- package/dist/orchestrators/router/cleanup.mjs.map +1 -1
- package/dist/orchestrators/router/cors.mjs.map +1 -1
- package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
- package/dist/orchestrators/router/errors.mjs.map +1 -1
- package/dist/orchestrators/router/file-provider.mjs.map +1 -1
- package/dist/orchestrators/router/hostname.mjs.map +1 -1
- package/dist/orchestrators/router/profile.mjs +1 -1
- package/dist/orchestrators/router/profile.mjs.map +1 -1
- package/dist/orchestrators/router/service.d.mts +2 -2
- package/dist/orchestrators/router/service.mjs +12 -27
- package/dist/orchestrators/router/service.mjs.map +1 -1
- package/dist/orchestrators/router/traefik-container.mjs +1 -1
- package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
- package/dist/orchestrators/snapshot/capture.mjs +171 -195
- package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
- package/dist/orchestrators/snapshot/descriptor.d.mts +1 -1
- package/dist/orchestrators/snapshot/descriptor.mjs +53 -11
- package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
- package/dist/orchestrators/snapshot/identity-guard.mjs +3 -5
- package/dist/orchestrators/snapshot/identity-guard.mjs.map +1 -1
- package/dist/orchestrators/snapshot/image-bundle-tags.mjs +32 -101
- package/dist/orchestrators/snapshot/image-bundle-tags.mjs.map +1 -1
- package/dist/orchestrators/snapshot/index.mjs +1 -4
- package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
- package/dist/orchestrators/snapshot/interrupted-restore.mjs +116 -0
- package/dist/orchestrators/snapshot/interrupted-restore.mjs.map +1 -0
- package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -1
- package/dist/orchestrators/snapshot/prune.mjs +40 -31
- package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
- package/dist/orchestrators/snapshot/restore.mjs +221 -196
- package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
- package/dist/orchestrators/snapshot/service.mjs +72 -62
- package/dist/orchestrators/snapshot/service.mjs.map +1 -1
- package/dist/orchestrators/snapshot/wipe.mjs +67 -56
- package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
- package/dist/plugins/account/codegen.mjs +3 -0
- package/dist/plugins/account/codegen.mjs.map +1 -1
- package/dist/plugins/account/errors.d.mts +2 -2
- package/dist/plugins/account/errors.mjs +1 -4
- package/dist/plugins/account/errors.mjs.map +1 -1
- package/dist/plugins/account/funding.mjs +3 -27
- package/dist/plugins/account/funding.mjs.map +1 -1
- package/dist/plugins/account/index.d.mts +3 -8
- package/dist/plugins/account/index.mjs +71 -35
- package/dist/plugins/account/index.mjs.map +1 -1
- package/dist/plugins/account/keypair.d.mts +8 -7
- package/dist/plugins/account/keypair.mjs +5 -18
- package/dist/plugins/account/keypair.mjs.map +1 -1
- package/dist/plugins/account/lease.mjs.map +1 -1
- package/dist/plugins/account/registry.d.mts +1 -46
- package/dist/plugins/account/registry.mjs.map +1 -1
- package/dist/plugins/account/service.d.mts +43 -34
- package/dist/plugins/account/service.mjs +9 -54
- package/dist/plugins/account/service.mjs.map +1 -1
- package/dist/plugins/account/snapshot.mjs.map +1 -1
- package/dist/plugins/account/variants/ephemeral.mjs.map +1 -1
- package/dist/plugins/account/variants/impersonate.mjs.map +1 -1
- package/dist/plugins/account/variants/signer.mjs.map +1 -1
- package/dist/plugins/action/discriminator.mjs.map +1 -1
- package/dist/plugins/action/errors.mjs +1 -4
- package/dist/plugins/action/errors.mjs.map +1 -1
- package/dist/plugins/action/execute.mjs +3 -4
- package/dist/plugins/action/execute.mjs.map +1 -1
- package/dist/plugins/action/index.d.mts +2 -2
- package/dist/plugins/action/index.mjs +7 -11
- package/dist/plugins/action/index.mjs.map +1 -1
- package/dist/plugins/action/service.d.mts +5 -7
- package/dist/plugins/action/service.mjs +2 -16
- package/dist/plugins/action/service.mjs.map +1 -1
- package/dist/plugins/coin/address-resolution.mjs.map +1 -1
- package/dist/plugins/coin/codegen.mjs +97 -16
- package/dist/plugins/coin/codegen.mjs.map +1 -1
- package/dist/plugins/coin/discovery.mjs.map +1 -1
- package/dist/plugins/coin/errors.mjs +1 -4
- package/dist/plugins/coin/errors.mjs.map +1 -1
- package/dist/plugins/coin/index.d.mts +4 -34
- package/dist/plugins/coin/index.mjs +47 -28
- package/dist/plugins/coin/index.mjs.map +1 -1
- package/dist/plugins/coin/metadata.d.mts +1 -0
- package/dist/plugins/coin/metadata.mjs +8 -9
- package/dist/plugins/coin/metadata.mjs.map +1 -1
- package/dist/plugins/coin/mint.mjs +7 -21
- package/dist/plugins/coin/mint.mjs.map +1 -1
- package/dist/plugins/coin/registry.mjs +33 -20
- package/dist/plugins/coin/registry.mjs.map +1 -1
- package/dist/plugins/coin/service.d.mts +1 -1
- package/dist/plugins/coin/service.mjs +1 -8
- package/dist/plugins/coin/service.mjs.map +1 -1
- package/dist/plugins/coin/snapshot.mjs.map +1 -1
- package/dist/plugins/coin/type-strings.mjs.map +1 -1
- package/dist/plugins/dashboard/domain.mjs +106 -167
- package/dist/plugins/dashboard/domain.mjs.map +1 -1
- package/dist/plugins/dashboard/index.mjs +13 -13
- package/dist/plugins/dashboard/index.mjs.map +1 -1
- package/dist/plugins/dashboard/origin-policy.mjs +3 -3
- package/dist/plugins/dashboard/origin-policy.mjs.map +1 -1
- package/dist/plugins/dashboard/routable.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/builder.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/enums.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/root.mjs +23 -44
- package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
- package/dist/plugins/dashboard/schema/types.mjs +37 -91
- package/dist/plugins/dashboard/schema/types.mjs.map +1 -1
- package/dist/plugins/dashboard/schema.mjs.map +1 -1
- package/dist/plugins/dashboard/server.mjs.map +1 -1
- package/dist/plugins/deepbook/codegen.d.mts +12 -11
- package/dist/plugins/deepbook/codegen.mjs +108 -11
- package/dist/plugins/deepbook/codegen.mjs.map +1 -1
- package/dist/plugins/deepbook/deploy.mjs +7 -14
- package/dist/plugins/deepbook/deploy.mjs.map +1 -1
- package/dist/plugins/deepbook/errors.mjs +4 -2
- package/dist/plugins/deepbook/errors.mjs.map +1 -1
- package/dist/plugins/deepbook/faucet-strategy.mjs +4 -10
- package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/deepbook/hash.mjs.map +1 -1
- package/dist/plugins/deepbook/index.d.mts +38 -34
- package/dist/plugins/deepbook/index.mjs +133 -64
- package/dist/plugins/deepbook/index.mjs.map +1 -1
- package/dist/plugins/deepbook/plugin-key.mjs.map +1 -1
- package/dist/plugins/deepbook/pyth/index.mjs +4 -8
- package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
- package/dist/plugins/deepbook/snapshot.mjs +0 -1
- package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
- package/dist/plugins/deepbook/type-strings.mjs.map +1 -1
- package/dist/plugins/deepbook/types.d.mts +2 -1
- package/dist/plugins/deepbook/types.mjs.map +1 -1
- package/dist/plugins/faucet/dispatcher.d.mts +3 -3
- package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
- package/dist/plugins/faucet/errors.mjs.map +1 -1
- package/dist/plugins/faucet/http.mjs +2 -10
- package/dist/plugins/faucet/http.mjs.map +1 -1
- package/dist/plugins/faucet/index.mjs +0 -1
- package/dist/plugins/faucet/index.mjs.map +1 -1
- package/dist/plugins/host-service/errors.d.mts +1 -3
- package/dist/plugins/host-service/errors.mjs +1 -2
- package/dist/plugins/host-service/errors.mjs.map +1 -1
- package/dist/plugins/host-service/index.d.mts +1 -2
- package/dist/plugins/host-service/index.mjs +11 -11
- package/dist/plugins/host-service/index.mjs.map +1 -1
- package/dist/plugins/host-service/routable.mjs.map +1 -1
- package/dist/plugins/host-service/service.mjs +13 -13
- package/dist/plugins/host-service/service.mjs.map +1 -1
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -1
- package/dist/plugins/internal/codegen-helpers.mjs +2 -0
- package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
- package/dist/plugins/internal/funding-failure-error.mjs.map +1 -1
- package/dist/plugins/{postgres → internal/postgres-sidecar}/connection.mjs +1 -1
- package/dist/plugins/internal/postgres-sidecar/connection.mjs.map +1 -0
- package/dist/plugins/{postgres → internal/postgres-sidecar}/db-ensure.mjs +30 -16
- package/dist/plugins/internal/postgres-sidecar/db-ensure.mjs.map +1 -0
- package/dist/plugins/internal/postgres-sidecar/errors.mjs +21 -0
- package/dist/plugins/internal/postgres-sidecar/errors.mjs.map +1 -0
- package/dist/{substrate/runtime/scoped-multimap → plugins/internal/postgres-sidecar}/index.mjs +1 -0
- package/dist/plugins/internal/postgres-sidecar/service.mjs +121 -0
- package/dist/plugins/internal/postgres-sidecar/service.mjs.map +1 -0
- package/dist/plugins/package/build.mjs +2 -2
- package/dist/plugins/package/build.mjs.map +1 -1
- package/dist/plugins/package/codegen.d.mts +7 -14
- package/dist/plugins/package/codegen.mjs +187 -59
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/dep-resolution.mjs +51 -3
- package/dist/plugins/package/dep-resolution.mjs.map +1 -1
- package/dist/plugins/package/errors.mjs +1 -4
- package/dist/plugins/package/errors.mjs.map +1 -1
- package/dist/plugins/package/git-source.d.mts +18 -0
- package/dist/plugins/package/git-source.mjs +119 -0
- package/dist/plugins/package/git-source.mjs.map +1 -0
- package/dist/plugins/package/index.d.mts +28 -19
- package/dist/plugins/package/index.mjs +107 -32
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/package/mode-known.mjs +2 -2
- package/dist/plugins/package/mode-known.mjs.map +1 -1
- package/dist/plugins/package/mode-local.mjs +18 -35
- package/dist/plugins/package/mode-local.mjs.map +1 -1
- package/dist/plugins/package/publish-executor.mjs +13 -8
- package/dist/plugins/package/publish-executor.mjs.map +1 -1
- package/dist/plugins/package/publish-output.d.mts +1 -11
- package/dist/plugins/package/publish-output.mjs +1 -9
- package/dist/plugins/package/publish-output.mjs.map +1 -1
- package/dist/plugins/package/registry.d.mts +1 -1
- package/dist/plugins/package/registry.mjs +26 -14
- package/dist/plugins/package/registry.mjs.map +1 -1
- package/dist/plugins/package/service.mjs.map +1 -1
- package/dist/plugins/package/snapshot.mjs.map +1 -1
- package/dist/plugins/router-entrypoints.mjs +1 -3
- package/dist/plugins/router-entrypoints.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +1 -2
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +1 -6
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
- package/dist/plugins/seal/codegen.d.mts +1 -5
- package/dist/plugins/seal/codegen.mjs +83 -12
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/config-render.mjs +2 -6
- package/dist/plugins/seal/config-render.mjs.map +1 -1
- package/dist/plugins/seal/deploy.mjs +18 -29
- package/dist/plugins/seal/deploy.mjs.map +1 -1
- package/dist/plugins/seal/errors.d.mts +4 -5
- package/dist/plugins/seal/errors.mjs +1 -8
- package/dist/plugins/seal/errors.mjs.map +1 -1
- package/dist/plugins/seal/index.d.mts +7 -28
- package/dist/plugins/seal/index.mjs +69 -47
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/seal/key-manager.mjs.map +1 -1
- package/dist/plugins/seal/key-server.mjs +1 -6
- package/dist/plugins/seal/key-server.mjs.map +1 -1
- package/dist/plugins/seal/keygen.mjs +2 -3
- package/dist/plugins/seal/keygen.mjs.map +1 -1
- package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
- package/dist/plugins/seal/mode/live.mjs +1 -1
- package/dist/plugins/seal/mode/live.mjs.map +1 -1
- package/dist/plugins/seal/mode/local-keygen.mjs +4 -10
- package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
- package/dist/plugins/seal/plugin-key.mjs.map +1 -1
- package/dist/plugins/seal/registry-publish.mjs.map +1 -1
- package/dist/plugins/seal/routable.mjs.map +1 -1
- package/dist/plugins/seal/service.mjs.map +1 -1
- package/dist/plugins/seal/snapshot.mjs +1 -2
- package/dist/plugins/seal/snapshot.mjs.map +1 -1
- package/dist/plugins/sui/auto-tick.mjs +5 -5
- package/dist/plugins/sui/auto-tick.mjs.map +1 -1
- package/dist/plugins/sui/chain-build-container.mjs +2 -2
- package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
- package/dist/plugins/sui/chain-probe.mjs +4 -4
- package/dist/plugins/sui/chain-probe.mjs.map +1 -1
- package/dist/plugins/sui/codegen.d.mts +9 -7
- package/dist/plugins/sui/codegen.mjs +49 -46
- package/dist/plugins/sui/codegen.mjs.map +1 -1
- package/dist/plugins/sui/errors.d.mts +1 -1
- package/dist/plugins/sui/errors.mjs +1 -9
- package/dist/plugins/sui/errors.mjs.map +1 -1
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.d.mts +1 -1
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.mjs +3 -7
- package/dist/plugins/sui/exec/index.mjs.map +1 -0
- package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.mjs +1 -1
- package/dist/plugins/sui/exec/sign-and-dispatch.mjs.map +1 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs +2 -6
- package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -1
- package/dist/plugins/sui/fork-orchestration.mjs +13 -23
- package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
- package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
- package/dist/plugins/sui/index.d.mts +34 -83
- package/dist/plugins/sui/index.mjs +200 -68
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/{substrate/runtime/sui-ledger → plugins/sui/ledger}/object-ref.mjs +1 -1
- package/dist/plugins/sui/ledger/object-ref.mjs.map +1 -0
- package/dist/plugins/sui/local-faucet-strategy.mjs +1 -5
- package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -1
- package/dist/plugins/sui/log-attrs.mjs +11 -0
- package/dist/plugins/sui/log-attrs.mjs.map +1 -0
- package/dist/plugins/sui/mode/external.mjs +4 -12
- package/dist/plugins/sui/mode/external.mjs.map +1 -1
- package/dist/plugins/sui/mode/fork.mjs +19 -16
- package/dist/plugins/sui/mode/fork.mjs.map +1 -1
- package/dist/plugins/sui/mode/live.mjs +4 -17
- package/dist/plugins/sui/mode/live.mjs.map +1 -1
- package/dist/plugins/sui/mode/local.d.mts +1 -0
- package/dist/plugins/sui/mode/local.mjs +203 -61
- package/dist/plugins/sui/mode/local.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared-boot.mjs +18 -23
- package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared.d.mts +6 -7
- package/dist/plugins/sui/mode/shared.mjs.map +1 -1
- package/dist/plugins/sui/mode/spec.d.mts +23 -6
- package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.mjs +28 -30
- package/dist/plugins/sui/move/index.mjs.map +1 -0
- package/dist/plugins/sui/move-summary-runner.mjs +8 -4
- package/dist/plugins/sui/move-summary-runner.mjs.map +1 -1
- package/dist/{substrate/network.d.mts → plugins/sui/network-config.d.mts} +11 -10
- package/dist/plugins/sui/network-resolver.d.mts +6 -6
- package/dist/plugins/sui/routable.mjs.map +1 -1
- package/dist/plugins/sui/service.mjs +10 -6
- package/dist/plugins/sui/service.mjs.map +1 -1
- package/dist/plugins/sui/snapshot.mjs +10 -5
- package/dist/plugins/sui/snapshot.mjs.map +1 -1
- package/dist/plugins/wallet/codegen.d.mts +8 -6
- package/dist/plugins/wallet/codegen.mjs +4 -10
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/errors.mjs +1 -4
- package/dist/plugins/wallet/errors.mjs.map +1 -1
- package/dist/plugins/wallet/index.d.mts +3 -6
- package/dist/plugins/wallet/index.mjs +16 -21
- package/dist/plugins/wallet/index.mjs.map +1 -1
- package/dist/plugins/wallet/origin-policy.mjs +3 -3
- package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
- package/dist/plugins/wallet/pairing.mjs +6 -24
- package/dist/plugins/wallet/pairing.mjs.map +1 -1
- package/dist/plugins/wallet/protocol.mjs.map +1 -1
- package/dist/plugins/wallet/routable.mjs.map +1 -1
- package/dist/plugins/wallet/server.mjs +17 -24
- package/dist/plugins/wallet/server.mjs.map +1 -1
- package/dist/plugins/wallet/service.d.mts +2 -2
- package/dist/plugins/wallet/service.mjs +4 -15
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/plugins/wallet/snapshot.mjs.map +1 -1
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +1 -5
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/walrus/codegen.d.mts +2 -13
- package/dist/plugins/walrus/codegen.mjs +93 -28
- package/dist/plugins/walrus/codegen.mjs.map +1 -1
- package/dist/plugins/walrus/deploy-paths.mjs +2 -3
- package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
- package/dist/plugins/walrus/deploy.mjs +26 -19
- package/dist/plugins/walrus/deploy.mjs.map +1 -1
- package/dist/plugins/walrus/errors.mjs +1 -8
- package/dist/plugins/walrus/errors.mjs.map +1 -1
- package/dist/plugins/walrus/faucet-strategy.mjs +15 -1
- package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/walrus/index.d.mts +13 -44
- package/dist/plugins/walrus/index.mjs +119 -122
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/plugins/walrus/mode/known-deploy.mjs +6 -6
- package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
- package/dist/plugins/walrus/mode/local-cluster.d.mts +2 -2
- package/dist/plugins/walrus/mode/local-cluster.mjs +2 -10
- package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
- package/dist/plugins/walrus/plugin-key.mjs.map +1 -1
- package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
- package/dist/plugins/walrus/routable.mjs.map +1 -1
- package/dist/plugins/walrus/service.mjs.map +1 -1
- package/dist/plugins/walrus/snapshot.mjs +3 -3
- package/dist/plugins/walrus/snapshot.mjs.map +1 -1
- package/dist/plugins/walrus/storage-nodes.mjs +53 -14
- package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
- package/dist/plugins/walrus/wal-swap.mjs +4 -8
- package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
- package/dist/primitives/artifact-publisher.mjs.map +1 -1
- package/dist/primitives/cache.d.mts +1 -1
- package/dist/runtime/docker/client.mjs +47 -3
- package/dist/runtime/docker/client.mjs.map +1 -1
- package/dist/runtime/docker/container.mjs +37 -57
- package/dist/runtime/docker/container.mjs.map +1 -1
- package/dist/runtime/docker/errors.mjs.map +1 -1
- package/dist/runtime/docker/exec.mjs +2 -2
- package/dist/runtime/docker/exec.mjs.map +1 -1
- package/dist/runtime/docker/image.mjs +10 -11
- package/dist/runtime/docker/image.mjs.map +1 -1
- package/dist/runtime/docker/index.mjs +0 -1
- package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -1
- package/dist/runtime/docker/inventory.mjs +9 -9
- package/dist/runtime/docker/inventory.mjs.map +1 -1
- package/dist/runtime/docker/labels.mjs +12 -1
- package/dist/runtime/docker/labels.mjs.map +1 -1
- package/dist/runtime/docker/network.mjs +6 -6
- package/dist/runtime/docker/network.mjs.map +1 -1
- package/dist/runtime/docker/render-run-args.mjs.map +1 -1
- package/dist/runtime/docker/service.mjs +31 -45
- package/dist/runtime/docker/service.mjs.map +1 -1
- package/dist/runtime/docker/sweep.mjs +14 -64
- package/dist/runtime/docker/sweep.mjs.map +1 -1
- package/dist/runtime/docker/volume.mjs +1 -1
- package/dist/runtime/docker/volume.mjs.map +1 -1
- package/dist/runtime/docker/wrap.mjs +14 -2
- package/dist/runtime/docker/wrap.mjs.map +1 -1
- package/dist/substrate/brand.d.mts +2 -5
- package/dist/substrate/brand.mjs +3 -2
- package/dist/substrate/brand.mjs.map +1 -1
- package/dist/substrate/cross-process.mjs +2 -16
- package/dist/substrate/cross-process.mjs.map +1 -1
- package/dist/substrate/event-time.mjs +0 -2
- package/dist/substrate/event-time.mjs.map +1 -1
- package/dist/substrate/events.d.mts +46 -16
- package/dist/substrate/identity.d.mts +11 -5
- package/dist/substrate/manifest.d.mts +33 -26
- package/dist/substrate/manifest.mjs +8 -3
- package/dist/substrate/manifest.mjs.map +1 -1
- package/dist/substrate/options.d.mts +33 -4
- package/dist/substrate/plugin-ctx.d.mts +70 -0
- package/dist/substrate/plugin-ctx.mjs +66 -0
- package/dist/substrate/plugin-ctx.mjs.map +1 -0
- package/dist/substrate/plugin.d.mts +46 -38
- package/dist/substrate/plugin.mjs +13 -9
- package/dist/substrate/plugin.mjs.map +1 -1
- package/dist/substrate/projection.d.mts +1 -1
- package/dist/substrate/runtime/atomic-write.mjs +3 -4
- package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
- package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
- package/dist/substrate/runtime/cache/service.mjs +50 -20
- package/dist/substrate/runtime/cache/service.mjs.map +1 -1
- package/dist/substrate/runtime/config-validation.mjs.map +1 -1
- package/dist/substrate/runtime/control-plane/domain.mjs +34 -12
- package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -1
- package/dist/substrate/runtime/control-plane/service.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +14 -17
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/index.mjs +0 -2
- package/dist/substrate/runtime/cross-process/live-clock.mjs +1 -1
- package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/liveness.mjs +11 -18
- package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +2 -2
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/roster.mjs +17 -127
- package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/stack-lock.mjs +29 -28
- package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
- package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
- package/dist/substrate/runtime/errors.mjs +1 -13
- package/dist/substrate/runtime/errors.mjs.map +1 -1
- package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -1
- package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -1
- package/dist/substrate/runtime/host-gateway.mjs.map +1 -1
- package/dist/substrate/runtime/host-tree-tar/index.mjs +15 -163
- package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
- package/dist/substrate/runtime/http-probe.d.mts +1 -3
- package/dist/substrate/runtime/http-probe.mjs.map +1 -1
- package/dist/substrate/runtime/index.mjs +1 -6
- package/dist/substrate/runtime/lease-broker/service.mjs +5 -23
- package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs +3 -3
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/graph-input-id.d.mts +1 -0
- package/dist/substrate/runtime/lifecycle/graph-input-id.mjs +133 -0
- package/dist/substrate/runtime/lifecycle/graph-input-id.mjs.map +1 -0
- package/dist/substrate/runtime/lifecycle/index.mjs +2 -2
- package/dist/substrate/runtime/lifecycle/plugin-registry.d.mts +1 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +22 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/ready-gate.mjs +2 -15
- package/dist/substrate/runtime/lifecycle/ready-gate.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs +29 -25
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/state-machine.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.d.mts +1 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.mjs +0 -1
- package/dist/substrate/runtime/lifecycle/watch-attribution.mjs.map +1 -1
- package/dist/substrate/runtime/managed-container.d.mts +5 -1
- package/dist/substrate/runtime/managed-container.mjs +8 -10
- package/dist/substrate/runtime/managed-container.mjs.map +1 -1
- package/dist/substrate/runtime/manifest/manifest.mjs +8 -30
- package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/mode-errors.d.mts +1 -2
- package/dist/substrate/runtime/mode-errors.mjs.map +1 -1
- package/dist/substrate/runtime/observability/cascade-formatter.mjs +1 -11
- package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
- package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -1
- package/dist/substrate/runtime/observability/index.d.mts +1 -1
- package/dist/substrate/runtime/observability/index.mjs +4 -6
- package/dist/substrate/runtime/observability/log-attrs.mjs +46 -0
- package/dist/substrate/runtime/observability/log-attrs.mjs.map +1 -0
- package/dist/substrate/runtime/observability/log-store.mjs.map +1 -1
- package/dist/substrate/runtime/observability/logger.mjs +3 -3
- package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
- package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -1
- package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
- package/dist/substrate/runtime/observability/process-lines.mjs.map +1 -1
- package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
- package/dist/substrate/runtime/observability/subprocess-capture.mjs +2 -2
- package/dist/substrate/runtime/observability/subprocess-capture.mjs.map +1 -1
- package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -1
- package/dist/substrate/runtime/paths.mjs +27 -9
- package/dist/substrate/runtime/paths.mjs.map +1 -1
- package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -1
- package/dist/substrate/runtime/port-broker/service.mjs +10 -27
- package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/probes.d.mts +2 -3
- package/dist/substrate/runtime/probes.mjs.map +1 -1
- package/dist/substrate/runtime/process-supervisor.mjs.map +1 -1
- package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
- package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
- package/dist/substrate/runtime/projection/update.mjs +87 -11
- package/dist/substrate/runtime/projection/update.mjs.map +1 -1
- package/dist/substrate/runtime/random-suffix.mjs.map +1 -1
- package/dist/substrate/runtime/reconcile/fs-plan.mjs +146 -0
- package/dist/substrate/runtime/reconcile/fs-plan.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/graph.mjs +19 -0
- package/dist/substrate/runtime/reconcile/graph.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/index.mjs +5 -0
- package/dist/substrate/runtime/reconcile/label.mjs +41 -0
- package/dist/substrate/runtime/reconcile/label.mjs.map +1 -0
- package/dist/substrate/runtime/reconcile/spec.mjs +15 -0
- package/dist/substrate/runtime/reconcile/spec.mjs.map +1 -0
- package/dist/substrate/runtime/retry-policy.d.mts +2 -3
- package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
- package/dist/substrate/runtime/routed-url.mjs.map +1 -1
- package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
- package/dist/substrate/runtime/stage-and-swap/index.mjs +49 -12
- package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/service.mjs +29 -17
- package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
- package/dist/substrate/runtime/subnet-broker.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/acquire-node.mjs +188 -56
- package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/background-tasks.mjs +79 -65
- package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/command-loop.mjs +82 -23
- package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs +16 -0
- package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/errors.mjs +11 -2
- package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/index.mjs +2 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs +6 -6
- package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs +60 -70
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/state.d.mts +1 -1
- package/dist/substrate/runtime/supervisor/state.mjs +1 -1
- package/dist/substrate/runtime/supervisor/state.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/teardown.mjs +10 -9
- package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/wiring.mjs +6 -20
- package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -1
- package/dist/substrate/runtime/tar/reader.mjs +216 -0
- package/dist/substrate/runtime/tar/reader.mjs.map +1 -0
- package/dist/substrate/runtime/typed-env.mjs.map +1 -1
- package/dist/substrate/versioned-doc-schema.mjs.map +1 -1
- package/dist/substrate/versioned-doc-sync.mjs +5 -5
- package/dist/substrate/versioned-doc-sync.mjs.map +1 -1
- package/dist/surfaces/cli/command-tree.mjs +49 -7
- package/dist/surfaces/cli/command-tree.mjs.map +1 -1
- package/dist/surfaces/cli/commands/config.mjs +1 -1
- package/dist/surfaces/cli/commands/config.mjs.map +1 -1
- package/dist/surfaces/cli/commands/confirm-node.mjs.map +1 -1
- package/dist/surfaces/cli/commands/confirm.mjs.map +1 -1
- package/dist/surfaces/cli/commands/doctor.mjs +1 -1
- package/dist/surfaces/cli/commands/doctor.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune.mjs +1 -1
- package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
- package/dist/surfaces/cli/commands/snapshot.mjs +1 -1
- package/dist/surfaces/cli/commands/snapshot.mjs.map +1 -1
- package/dist/surfaces/cli/commands/status.mjs +1 -1
- package/dist/surfaces/cli/commands/status.mjs.map +1 -1
- package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
- package/dist/surfaces/cli/commands/wipe.mjs +3 -4
- package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
- package/dist/surfaces/cli/envelope.mjs.map +1 -1
- package/dist/surfaces/cli/errors.mjs.map +1 -1
- package/dist/surfaces/cli/flags.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs +45 -14
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/dist/surfaces/cli/output.mjs.map +1 -1
- package/dist/surfaces/cli/sysexits.mjs.map +1 -1
- package/dist/surfaces/tui/app.mjs +0 -21
- package/dist/surfaces/tui/app.mjs.map +1 -1
- package/dist/surfaces/tui/dashboard.mjs +0 -48
- package/dist/surfaces/tui/dashboard.mjs.map +1 -1
- package/dist/surfaces/tui/display-derivation.mjs +80 -16
- package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
- package/dist/surfaces/tui/errors.mjs.map +1 -1
- package/dist/surfaces/tui/event-log.mjs +0 -9
- package/dist/surfaces/tui/event-log.mjs.map +1 -1
- package/dist/surfaces/tui/heartbeat.mjs.map +1 -1
- package/dist/surfaces/tui/index.mjs +2 -2
- package/dist/surfaces/tui/index.mjs.map +1 -1
- package/dist/surfaces/tui/input.mjs.map +1 -1
- package/dist/surfaces/tui/mode-detect.mjs.map +1 -1
- package/dist/surfaces/tui/mount-ink.mjs.map +1 -1
- package/dist/surfaces/tui/plain-renderer.mjs +26 -19
- package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
- package/dist/surfaces/tui/resource-table.mjs +8 -10
- package/dist/surfaces/tui/resource-table.mjs.map +1 -1
- package/images/postgres/Dockerfile +0 -1
- package/images/sui/Dockerfile +27 -54
- package/images/sui/entrypoint.sh +17 -179
- package/package.json +23 -15
- package/dashboard-ui/assets/index-Bmi1UtAg.js +0 -1356
- package/dashboard-ui/assets/index-D5EShVt4.js +0 -3
- package/dashboard-ui/assets/index-Deml9drg.css +0 -1
- package/dist/api/plugin-errors.d.mts +0 -7
- package/dist/api/plugin-errors.mjs +0 -10
- package/dist/api/plugin-errors.mjs.map +0 -1
- package/dist/cli/wirings/build-verb-layers.mjs +0 -42
- package/dist/cli/wirings/build-verb-layers.mjs.map +0 -1
- package/dist/contracts/capability-decl.d.mts +0 -40
- package/dist/orchestrators/built-in-plugin-layers.mjs +0 -54
- package/dist/orchestrators/built-in-plugin-layers.mjs.map +0 -1
- package/dist/orchestrators/run.mjs +0 -91
- package/dist/orchestrators/run.mjs.map +0 -1
- package/dist/orchestrators/runtime-composition.d.mts +0 -10
- package/dist/orchestrators/runtime-composition.mjs +0 -252
- package/dist/orchestrators/runtime-composition.mjs.map +0 -1
- package/dist/orchestrators/snapshot/capture-command.d.mts +0 -1
- package/dist/orchestrators/snapshot/capture-command.mjs +0 -25
- package/dist/orchestrators/snapshot/capture-command.mjs.map +0 -1
- package/dist/orchestrators/snapshot/pending-marker.mjs +0 -99
- package/dist/orchestrators/snapshot/pending-marker.mjs.map +0 -1
- package/dist/orchestrators/snapshot/recover-pending.mjs +0 -223
- package/dist/orchestrators/snapshot/recover-pending.mjs.map +0 -1
- package/dist/orchestrators/snapshot/state-document.d.mts +0 -1
- package/dist/orchestrators/snapshot/state-document.mjs +0 -58
- package/dist/orchestrators/snapshot/state-document.mjs.map +0 -1
- package/dist/plugins/account/spans.mjs +0 -16
- package/dist/plugins/account/spans.mjs.map +0 -1
- package/dist/plugins/account/variants/env.mjs +0 -24
- package/dist/plugins/account/variants/env.mjs.map +0 -1
- package/dist/plugins/account/variants/inline.mjs +0 -13
- package/dist/plugins/account/variants/inline.mjs.map +0 -1
- package/dist/plugins/account/variants/keystore.mjs +0 -91
- package/dist/plugins/account/variants/keystore.mjs.map +0 -1
- package/dist/plugins/action/spans.mjs +0 -11
- package/dist/plugins/action/spans.mjs.map +0 -1
- package/dist/plugins/coin/spans.mjs +0 -20
- package/dist/plugins/coin/spans.mjs.map +0 -1
- package/dist/plugins/deepbook/spans.mjs +0 -18
- package/dist/plugins/deepbook/spans.mjs.map +0 -1
- package/dist/plugins/faucet/spans.mjs +0 -12
- package/dist/plugins/faucet/spans.mjs.map +0 -1
- package/dist/plugins/package/spans.mjs +0 -14
- package/dist/plugins/package/spans.mjs.map +0 -1
- package/dist/plugins/postgres/codegen.mjs +0 -42
- package/dist/plugins/postgres/codegen.mjs.map +0 -1
- package/dist/plugins/postgres/connection.d.mts +0 -33
- package/dist/plugins/postgres/connection.mjs.map +0 -1
- package/dist/plugins/postgres/db-ensure.mjs.map +0 -1
- package/dist/plugins/postgres/errors.d.mts +0 -60
- package/dist/plugins/postgres/errors.mjs +0 -29
- package/dist/plugins/postgres/errors.mjs.map +0 -1
- package/dist/plugins/postgres/index.d.mts +0 -37
- package/dist/plugins/postgres/index.mjs +0 -68
- package/dist/plugins/postgres/index.mjs.map +0 -1
- package/dist/plugins/postgres/routable.mjs +0 -33
- package/dist/plugins/postgres/routable.mjs.map +0 -1
- package/dist/plugins/postgres/service.d.mts +0 -50
- package/dist/plugins/postgres/service.mjs +0 -179
- package/dist/plugins/postgres/service.mjs.map +0 -1
- package/dist/plugins/postgres/snapshot.mjs +0 -32
- package/dist/plugins/postgres/snapshot.mjs.map +0 -1
- package/dist/plugins/postgres/spans.mjs +0 -11
- package/dist/plugins/postgres/spans.mjs.map +0 -1
- package/dist/plugins/seal/spans.mjs +0 -18
- package/dist/plugins/seal/spans.mjs.map +0 -1
- package/dist/plugins/sui/spans.mjs +0 -17
- package/dist/plugins/sui/spans.mjs.map +0 -1
- package/dist/plugins/wallet/spans.mjs +0 -22
- package/dist/plugins/wallet/spans.mjs.map +0 -1
- package/dist/plugins/walrus/registry-publish.d.mts +0 -24
- package/dist/plugins/walrus/spans.mjs +0 -18
- package/dist/plugins/walrus/spans.mjs.map +0 -1
- package/dist/runtime/docker/logs.d.mts +0 -1
- package/dist/runtime/docker/logs.mjs +0 -34
- package/dist/runtime/docker/logs.mjs.map +0 -1
- package/dist/substrate/runtime/artifact-publisher/index.mjs +0 -86
- package/dist/substrate/runtime/artifact-publisher/index.mjs.map +0 -1
- package/dist/substrate/runtime/capability-sinks/index.d.mts +0 -1
- package/dist/substrate/runtime/capability-sinks/index.mjs +0 -3
- package/dist/substrate/runtime/capability-sinks/layer.d.mts +0 -1
- package/dist/substrate/runtime/capability-sinks/layer.mjs +0 -31
- package/dist/substrate/runtime/capability-sinks/layer.mjs.map +0 -1
- package/dist/substrate/runtime/capability-sinks/service.d.mts +0 -95
- package/dist/substrate/runtime/capability-sinks/service.mjs +0 -69
- package/dist/substrate/runtime/capability-sinks/service.mjs.map +0 -1
- package/dist/substrate/runtime/cross-process/lock.d.mts +0 -1
- package/dist/substrate/runtime/cross-process/lock.mjs +0 -23
- package/dist/substrate/runtime/cross-process/lock.mjs.map +0 -1
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +0 -113
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +0 -1
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs +0 -31
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +0 -1
- package/dist/substrate/runtime/observability/formatter-registry.d.mts +0 -1
- package/dist/substrate/runtime/observability/formatter-registry.mjs +0 -48
- package/dist/substrate/runtime/observability/formatter-registry.mjs.map +0 -1
- package/dist/substrate/runtime/observability/span-store.d.mts +0 -1
- package/dist/substrate/runtime/observability/span-store.mjs +0 -110
- package/dist/substrate/runtime/observability/span-store.mjs.map +0 -1
- package/dist/substrate/runtime/observability/spans.d.mts +0 -1
- package/dist/substrate/runtime/observability/spans.mjs +0 -87
- package/dist/substrate/runtime/observability/spans.mjs.map +0 -1
- package/dist/substrate/runtime/projection/index.mjs +0 -4
- package/dist/substrate/runtime/projection/persisted.mjs +0 -213
- package/dist/substrate/runtime/projection/persisted.mjs.map +0 -1
- package/dist/substrate/runtime/scoped-multimap/service.mjs +0 -52
- package/dist/substrate/runtime/scoped-multimap/service.mjs.map +0 -1
- package/dist/substrate/runtime/scoped-ref-map/index.mjs +0 -2
- package/dist/substrate/runtime/scoped-ref-map/service.mjs +0 -83
- package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +0 -1
- package/dist/substrate/runtime/state-store/index.mjs +0 -3
- package/dist/substrate/runtime/state-store/schema.d.mts +0 -1
- package/dist/substrate/runtime/state-store/schema.mjs +0 -41
- package/dist/substrate/runtime/state-store/schema.mjs.map +0 -1
- package/dist/substrate/runtime/state-store/service.d.mts +0 -1
- package/dist/substrate/runtime/state-store/service.mjs +0 -145
- package/dist/substrate/runtime/state-store/service.mjs.map +0 -1
- package/dist/substrate/runtime/sui-execute/index.mjs.map +0 -1
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +0 -1
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +0 -1
- package/dist/substrate/runtime/sui-move-build/index.mjs.map +0 -1
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +0 -151
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +0 -1
- package/dist/substrate/state-store.d.mts +0 -1
- /package/dist/orchestrators/{snapshot/pending-marker.d.mts → codegen/id-config.d.mts} +0 -0
- /package/dist/orchestrators/snapshot/{recover-pending.d.mts → interrupted-restore.d.mts} +0 -0
- /package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.d.mts +0 -0
- /package/dist/plugins/{postgres/snapshot.d.mts → sui/ledger/object-ref.d.mts} +0 -0
- /package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.d.mts +0 -0
- /package/dist/substrate/runtime/{state-store/index.d.mts → reconcile/graph.d.mts} +0 -0
- /package/dist/substrate/runtime/{cross-process/snapshot-reservation.d.mts → supervisor/contribution-dispatcher.d.mts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../src/substrate/plugin.ts"],"sourcesContent":["// Resource-native plugin contract.\n//\n// Public plugin authors use `definePlugin({ id, dependsOn, start })`.\n// The substrate now consumes that same shape directly: resource ids\n// drive scheduling, resolved dependency values are constructed by the\n// supervisor, and plugin bodies run through `start(...)`.\n\nimport type { Effect } from 'effect';\n\nimport type { CapabilityDecl } from '../contracts/capability-decl.ts';\nimport type { ChainId, PluginKey } from './brand.ts';\nimport type { Identity } from './identity.ts';\nimport type { PluginRole } from './lifecycle.ts';\nimport type { RowSection } from './projection.ts';\n\nconst resourceBrand: unique symbol = Symbol.for('devstack.resource') as never;\nconst pluginBrand: unique symbol = Symbol.for('devstack.plugin') as never;\nconst resourceValue: unique symbol = Symbol.for('devstack.resource.value') as never;\nconst dependencyInputBrand: unique symbol = Symbol.for('devstack.plugin.dependency-input') as never;\n\nexport interface ResourceRef<Id extends string, Value = unknown> {\n\treadonly id: Id;\n\treadonly [resourceBrand]: true;\n\treadonly [resourceValue]?: () => Value;\n}\n\nexport type AnyResourceRef = ResourceRef<string, unknown>;\n\nexport type ResourceIdOf<R extends AnyResourceRef> = R['id'];\n\nexport type ResourceValueOf<R extends AnyResourceRef> =\n\tR extends ResourceRef<string, infer Value> ? Value : never;\n\nexport const defineId = <const Id extends string>(id: Id): Id => id;\n\nexport const resource = <const Id extends string, Value = unknown>(\n\tid: Id,\n): ResourceRef<Id, Value> =>\n\t({\n\t\tid,\n\t\t[resourceBrand]: true,\n\t}) as ResourceRef<Id, Value>;\n\nexport const isResourceRef = (value: unknown): value is AnyResourceRef =>\n\ttypeof value === 'object' &&\n\tvalue !== null &&\n\t(value as { readonly [resourceBrand]?: true })[resourceBrand] === true;\n\nexport type DependencyInput =\n\t| AnyResourceRef\n\t| readonly AnyResourceRef[]\n\t| Readonly<Record<string, AnyResourceRef>>;\n\nexport type DependencyList<Input> = Input extends readonly AnyResourceRef[]\n\t? Input\n\t: Input extends AnyResourceRef\n\t\t? readonly [Input]\n\t\t: Input extends Readonly<Record<string, AnyResourceRef>>\n\t\t\t? ReadonlyArray<Input[keyof Input]>\n\t\t\t: readonly [];\n\nexport type ResolvedDependencyList<Dependencies extends readonly AnyResourceRef[]> = readonly [\n\t...{\n\t\treadonly [K in keyof Dependencies]: Dependencies[K] extends AnyResourceRef\n\t\t\t? ResourceValueOf<Dependencies[K]>\n\t\t\t: never;\n\t},\n];\n\nexport type ResolvedDependencyObject<\n\tDependencies extends Readonly<Record<string, AnyResourceRef>>,\n> = {\n\treadonly [K in keyof Dependencies]: Dependencies[K] extends AnyResourceRef\n\t\t? ResourceValueOf<Dependencies[K]>\n\t\t: never;\n};\n\nexport type ResolvedDependencies<Input> = Input extends undefined\n\t? undefined\n\t: Input extends readonly AnyResourceRef[]\n\t\t? ResolvedDependencyList<Input>\n\t\t: Input extends AnyResourceRef\n\t\t\t? ResourceValueOf<Input>\n\t\t\t: Input extends Readonly<Record<string, AnyResourceRef>>\n\t\t\t\t? ResolvedDependencyObject<Input>\n\t\t\t\t: never;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyPluginStart = (...args: any[]) => Effect.Effect<unknown, unknown, unknown>;\n\ntype PluginStart<Deps> = [Deps] extends [undefined]\n\t? () => Effect.Effect<unknown, unknown, unknown>\n\t: (deps: Deps) => Effect.Effect<unknown, unknown, unknown>;\n\ntype StartValue<Start> = Start extends (\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t...args: any[]\n) => Effect.Effect<infer Value, unknown, unknown>\n\t? Value\n\t: never;\n\nexport interface PluginErrorContribution {\n\treadonly _tag: 'PluginErrorContribution';\n\treadonly errorTags: ReadonlyArray<string>;\n\treadonly formatter?: (\n\t\tvalue: { readonly _tag: string } & Readonly<Record<string, unknown>>,\n\t\trecurse: (inner: unknown) => string,\n\t) => string | null;\n}\n\nexport interface WatchDecl {\n\treadonly paths: ReadonlyArray<string>;\n\treadonly cascade?: boolean;\n}\n\nexport interface AcquireContext {\n\treadonly identity: Identity;\n\treadonly chain: ChainId;\n\treadonly runtimeRoot: string;\n}\n\nexport type CapabilitiesFactory<Caps extends ReadonlyArray<CapabilityDecl>, Resolved> = (\n\tresolved: Resolved,\n\tctx: AcquireContext,\n) => Caps;\n\nexport type CapabilitySource<Value, Caps extends ReadonlyArray<CapabilityDecl>> =\n\t| Caps\n\t| ((ctx: { readonly value: Value; readonly runtime: AcquireContext }) => Caps);\n\ninterface PluginSpecBase<\n\tId extends string,\n\tStart extends AnyPluginStart,\n\tCaps extends ReadonlyArray<CapabilityDecl>,\n> {\n\treadonly id: Id;\n\treadonly role: PluginRole;\n\treadonly pluginKey?: PluginKey | string;\n\treadonly watch?: WatchDecl;\n\treadonly start: Start;\n\treadonly capabilities?: CapabilitySource<StartValue<Start>, Caps>;\n\treadonly errorContributions?: ReadonlyArray<PluginErrorContribution>;\n\t/** Dashboard section bucket the plugin's rows belong to. Required so\n\t * the renderer never has to pattern-match on plugin name substrings\n\t * to compute it. The supervisor stamps this onto every row at\n\t * acquire-time. */\n\treadonly section: RowSection;\n\t/** Optional override for rows that own a routed endpoint. When set\n\t * and the row carries an endpoint, the renderer groups it under\n\t * `endpointSection` instead of `section`. Use sparingly — the\n\t * default is for the plugin's normal `section` to apply uniformly. */\n\treadonly endpointSection?: RowSection;\n}\n\nexport type PluginSpec<\n\tId extends string,\n\tDependsOn extends DependencyInput | undefined,\n\tStart extends AnyPluginStart,\n\tCaps extends ReadonlyArray<CapabilityDecl>,\n> = PluginSpecBase<Id, Start, Caps> & {\n\treadonly dependsOn?: DependsOn;\n};\n\nexport interface Plugin<\n\tId extends string,\n\tValue,\n\tNeeds extends readonly AnyResourceRef[],\n\tCaps extends ReadonlyArray<CapabilityDecl>,\n> extends ResourceRef<Id, Value> {\n\treadonly [pluginBrand]: true;\n\treadonly [dependencyInputBrand]: DependencyInput | undefined;\n\treadonly dependsOn: Needs;\n\treadonly role: PluginRole;\n\treadonly pluginKey?: PluginKey | string;\n\treadonly watch?: WatchDecl;\n\treadonly start: (\n\t\tdeps: ResolvedDependencies<DependencyInput | undefined>,\n\t) => Effect.Effect<Value, unknown, unknown>;\n\treadonly capabilities?: Caps | CapabilitiesFactory<Caps, Value>;\n\treadonly errorContributions?: ReadonlyArray<PluginErrorContribution>;\n\treadonly section: RowSection;\n\treadonly endpointSection?: RowSection;\n}\n\nexport type AnyPlugin = Plugin<\n\tstring,\n\t// Erased runtime plugin values must be `any` rather than `unknown`\n\t// so concrete dynamic capability factories remain assignable under\n\t// strict function parameter variance. Precise value types stay on\n\t// concrete `Plugin<Id, Value, ...>` instances.\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tany,\n\treadonly AnyResourceRef[],\n\tReadonlyArray<CapabilityDecl>\n>;\n\nexport const isPlugin = (value: unknown): value is AnyPlugin =>\n\ttypeof value === 'object' &&\n\tvalue !== null &&\n\t(value as { readonly [pluginBrand]?: true })[pluginBrand] === true;\n\nexport const dependencyList = <Input extends DependencyInput | undefined>(\n\tdependsOn: Input,\n): DependencyList<Input> => {\n\tif (dependsOn === undefined) {\n\t\treturn [] as unknown as DependencyList<Input>;\n\t}\n\tif (Array.isArray(dependsOn)) {\n\t\treturn dependsOn as unknown as DependencyList<Input>;\n\t}\n\tif (isResourceRef(dependsOn)) {\n\t\treturn [dependsOn] as unknown as DependencyList<Input>;\n\t}\n\treturn Object.values(dependsOn) as unknown as DependencyList<Input>;\n};\n\nexport const uniqueResourceRefs = (refs: readonly AnyResourceRef[]): readonly AnyResourceRef[] => {\n\tconst seen = new Set<string>();\n\tconst unique: AnyResourceRef[] = [];\n\tfor (const ref of refs) {\n\t\tif (seen.has(ref.id)) continue;\n\t\tseen.add(ref.id);\n\t\tunique.push(ref);\n\t}\n\treturn unique;\n};\n\nexport const resolveDependencyValues = <Input extends DependencyInput | undefined>(\n\tdependsOn: Input,\n\tread: (resource: DependencyList<Input>[number]) => unknown,\n): ResolvedDependencies<Input> => {\n\tconst readAny = (resourceRef: AnyResourceRef) =>\n\t\tread(resourceRef as DependencyList<Input>[number]);\n\n\tif (dependsOn === undefined) {\n\t\treturn undefined as ResolvedDependencies<Input>;\n\t}\n\tif (Array.isArray(dependsOn)) {\n\t\treturn dependsOn.map((resourceRef) => readAny(resourceRef)) as ResolvedDependencies<Input>;\n\t}\n\tif (isResourceRef(dependsOn)) {\n\t\treturn readAny(dependsOn) as ResolvedDependencies<Input>;\n\t}\n\treturn Object.fromEntries(\n\t\tObject.entries(dependsOn).map(([key, resourceRef]) => [key, readAny(resourceRef)]),\n\t) as ResolvedDependencies<Input>;\n};\n\nexport const resolvePluginDependencies = (\n\tplugin: AnyPlugin,\n\tread: (resource: AnyResourceRef) => unknown,\n): ResolvedDependencies<DependencyInput | undefined> =>\n\tresolveDependencyValues(plugin[dependencyInputBrand], (resourceRef) =>\n\t\tread(resourceRef as AnyResourceRef),\n\t);\n\nexport const pluginDependencyRefs = (plugin: AnyPlugin): readonly AnyResourceRef[] =>\n\tdependencyList(plugin[dependencyInputBrand]) as readonly AnyResourceRef[];\n\nexport function definePlugin<\n\tconst Id extends string,\n\tconst DependsOn extends readonly AnyResourceRef[],\n\tconst Start extends PluginStart<ResolvedDependencyList<DependsOn>> = PluginStart<\n\t\tResolvedDependencyList<DependsOn>\n\t>,\n\tconst Caps extends ReadonlyArray<CapabilityDecl> = ReadonlyArray<CapabilityDecl>,\n>(\n\tspec: PluginSpecBase<Id, Start, Caps> & {\n\t\treadonly dependsOn: DependsOn;\n\t},\n): Plugin<Id, StartValue<Start>, DependsOn, Caps>;\nexport function definePlugin<\n\tconst Id extends string,\n\tconst DependsOn extends Readonly<Record<string, AnyResourceRef>>,\n\tconst Start extends PluginStart<ResolvedDependencyObject<DependsOn>> = PluginStart<\n\t\tResolvedDependencyObject<DependsOn>\n\t>,\n\tconst Caps extends ReadonlyArray<CapabilityDecl> = ReadonlyArray<CapabilityDecl>,\n>(\n\tspec: PluginSpecBase<Id, Start, Caps> & {\n\t\treadonly dependsOn: DependsOn;\n\t},\n): Plugin<Id, StartValue<Start>, DependencyList<DependsOn>, Caps>;\nexport function definePlugin<\n\tconst Id extends string,\n\tconst DependsOn extends AnyResourceRef,\n\tconst Start extends PluginStart<ResourceValueOf<DependsOn>> = PluginStart<\n\t\tResourceValueOf<DependsOn>\n\t>,\n\tconst Caps extends ReadonlyArray<CapabilityDecl> = ReadonlyArray<CapabilityDecl>,\n>(\n\tspec: PluginSpecBase<Id, Start, Caps> & {\n\t\treadonly dependsOn: DependsOn;\n\t},\n): Plugin<Id, StartValue<Start>, readonly [DependsOn], Caps>;\nexport function definePlugin<\n\tconst Id extends string,\n\tconst Start extends PluginStart<undefined> = PluginStart<undefined>,\n\tconst Caps extends ReadonlyArray<CapabilityDecl> = ReadonlyArray<CapabilityDecl>,\n>(\n\tspec: PluginSpecBase<Id, Start, Caps> & {\n\t\treadonly dependsOn?: undefined;\n\t},\n): Plugin<Id, StartValue<Start>, readonly [], Caps>;\nexport function definePlugin(\n\tspec: PluginSpecBase<string, AnyPluginStart, ReadonlyArray<CapabilityDecl>> & {\n\t\treadonly dependsOn?: DependencyInput;\n\t},\n): AnyPlugin {\n\tconst dependsOn = uniqueResourceRefs(dependencyList(spec.dependsOn));\n\tconst capabilitiesField = spec.capabilities;\n\tconst capabilities =\n\t\ttypeof capabilitiesField === 'function'\n\t\t\t? (value: unknown, runtime: AcquireContext) => capabilitiesField({ value, runtime })\n\t\t\t: capabilitiesField;\n\n\treturn {\n\t\t[resourceBrand]: true,\n\t\t[pluginBrand]: true,\n\t\t[dependencyInputBrand]: spec.dependsOn,\n\t\tid: spec.id,\n\t\tdependsOn,\n\t\trole: spec.role,\n\t\tsection: spec.section,\n\t\tstart: spec.start as AnyPlugin['start'],\n\t\t...(spec.pluginKey === undefined ? {} : { pluginKey: spec.pluginKey }),\n\t\t...(spec.watch === undefined ? {} : { watch: spec.watch }),\n\t\t...(capabilities === undefined ? {} : { capabilities }),\n\t\t...(spec.errorContributions === undefined\n\t\t\t? {}\n\t\t\t: { errorContributions: spec.errorContributions }),\n\t\t...(spec.endpointSection === undefined ? {} : { endpointSection: spec.endpointSection }),\n\t} as AnyPlugin;\n}\n\nexport type ProvidedIdsOf<Members> =\n\tMembers extends ReadonlyArray<unknown>\n\t\t? Members[number] extends { readonly id: infer Id extends string }\n\t\t\t? Id\n\t\t\t: never\n\t\t: never;\n\nexport type ConsumedIdsOf<Members> =\n\tMembers extends ReadonlyArray<unknown>\n\t\t? Members[number] extends { readonly dependsOn: infer Dependencies }\n\t\t\t? Dependencies extends ReadonlyArray<infer Dependency>\n\t\t\t\t? Dependency extends { readonly id: infer Id extends string }\n\t\t\t\t\t? Id\n\t\t\t\t\t: never\n\t\t\t\t: never\n\t\t\t: never\n\t\t: never;\n\nexport type MissingProviders<Members> = Exclude<ConsumedIdsOf<Members>, ProvidedIdsOf<Members>>;\n\nexport interface __MissingProvidersError<Missing extends string> {\n\treadonly __missing_providers: Missing;\n}\n"],"mappings":";AAeA,MAAM,gBAA+B,OAAO,IAAI,oBAAoB;AACpE,MAAM,cAA6B,OAAO,IAAI,kBAAkB;AAEhE,MAAM,uBAAsC,OAAO,IAAI,mCAAmC;AAe1F,MAAa,YAAqC,OAAe;AAEjE,MAAa,YACZ,QAEC;CACA;EACC,gBAAgB;CACjB;AAEF,MAAa,iBAAiB,UAC7B,OAAO,UAAU,YACjB,UAAU,QACT,MAA8C,mBAAmB;AAsJnE,MAAa,YAAY,UACxB,OAAO,UAAU,YACjB,UAAU,QACT,MAA4C,iBAAiB;AAE/D,MAAa,kBACZ,cAC2B;AAC3B,KAAI,cAAc,KAAA,EACjB,QAAO,EAAE;AAEV,KAAI,MAAM,QAAQ,UAAU,CAC3B,QAAO;AAER,KAAI,cAAc,UAAU,CAC3B,QAAO,CAAC,UAAU;AAEnB,QAAO,OAAO,OAAO,UAAU;;AAGhC,MAAa,sBAAsB,SAA+D;CACjG,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,SAA2B,EAAE;AACnC,MAAK,MAAM,OAAO,MAAM;AACvB,MAAI,KAAK,IAAI,IAAI,GAAG,CAAE;AACtB,OAAK,IAAI,IAAI,GAAG;AAChB,SAAO,KAAK,IAAI;;AAEjB,QAAO;;AAGR,MAAa,2BACZ,WACA,SACiC;CACjC,MAAM,WAAW,gBAChB,KAAK,YAA6C;AAEnD,KAAI,cAAc,KAAA,EACjB;AAED,KAAI,MAAM,QAAQ,UAAU,CAC3B,QAAO,UAAU,KAAK,gBAAgB,QAAQ,YAAY,CAAC;AAE5D,KAAI,cAAc,UAAU,CAC3B,QAAO,QAAQ,UAAU;AAE1B,QAAO,OAAO,YACb,OAAO,QAAQ,UAAU,CAAC,KAAK,CAAC,KAAK,iBAAiB,CAAC,KAAK,QAAQ,YAAY,CAAC,CAAC,CAClF;;AAGF,MAAa,6BACZ,QACA,SAEA,wBAAwB,OAAO,wBAAwB,gBACtD,KAAK,YAA8B,CACnC;AAEF,MAAa,wBAAwB,WACpC,eAAe,OAAO,sBAAsB;AA+C7C,SAAgB,aACf,MAGY;CACZ,MAAM,YAAY,mBAAmB,eAAe,KAAK,UAAU,CAAC;CACpE,MAAM,oBAAoB,KAAK;CAC/B,MAAM,eACL,OAAO,sBAAsB,cACzB,OAAgB,YAA4B,kBAAkB;EAAE;EAAO;EAAS,CAAC,GAClF;AAEJ,QAAO;GACL,gBAAgB;GAChB,cAAc;GACd,uBAAuB,KAAK;EAC7B,IAAI,KAAK;EACT;EACA,MAAM,KAAK;EACX,SAAS,KAAK;EACd,OAAO,KAAK;EACZ,GAAI,KAAK,cAAc,KAAA,IAAY,EAAE,GAAG,EAAE,WAAW,KAAK,WAAW;EACrE,GAAI,KAAK,UAAU,KAAA,IAAY,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO;EACzD,GAAI,iBAAiB,KAAA,IAAY,EAAE,GAAG,EAAE,cAAc;EACtD,GAAI,KAAK,uBAAuB,KAAA,IAC7B,EAAE,GACF,EAAE,oBAAoB,KAAK,oBAAoB;EAClD,GAAI,KAAK,oBAAoB,KAAA,IAAY,EAAE,GAAG,EAAE,iBAAiB,KAAK,iBAAiB;EACvF"}
|
|
1
|
+
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../src/substrate/plugin.ts"],"sourcesContent":["// Resource-native plugin contract.\n//\n// Public plugin authors use `definePlugin({ id, dependsOn, start })`.\n// The substrate now consumes that same shape directly: resource ids\n// drive scheduling, resolved dependency values are constructed by the\n// supervisor, and plugin bodies run through `start(...)`.\n\nimport type { Effect } from 'effect';\n\nimport type { StaticCodegenSource } from '../contracts/codegenable.ts';\nimport type { PluginKey } from './brand.ts';\nimport type { PluginRole } from './lifecycle.ts';\nimport type { RowSection } from './projection.ts';\n\nconst resourceBrand: unique symbol = Symbol.for('devstack.resource') as never;\nconst pluginBrand: unique symbol = Symbol.for('devstack.plugin') as never;\nconst resourceValue: unique symbol = Symbol.for('devstack.resource.value') as never;\nconst dependencyInputBrand: unique symbol = Symbol.for('devstack.plugin.dependency-input') as never;\n\nexport interface ResourceRef<Id extends string, Value = unknown> {\n\treadonly id: Id;\n\treadonly [resourceBrand]: true;\n\treadonly [resourceValue]?: () => Value;\n}\n\nexport type AnyResourceRef = ResourceRef<string, unknown>;\n\nexport type ResourceIdOf<R extends AnyResourceRef> = R['id'];\n\nexport type ResourceValueOf<R extends AnyResourceRef> =\n\tR extends ResourceRef<string, infer Value> ? Value : never;\n\nexport const defineId = <const Id extends string>(id: Id): Id => id;\n\nexport const resource = <const Id extends string, Value = unknown>(\n\tid: Id,\n): ResourceRef<Id, Value> =>\n\t({\n\t\tid,\n\t\t[resourceBrand]: true,\n\t}) as ResourceRef<Id, Value>;\n\nexport const isResourceRef = (value: unknown): value is AnyResourceRef =>\n\ttypeof value === 'object' &&\n\tvalue !== null &&\n\t(value as { readonly [resourceBrand]?: true })[resourceBrand] === true;\n\nexport type DependencyInput =\n\t| AnyResourceRef\n\t| readonly AnyResourceRef[]\n\t| Readonly<Record<string, AnyResourceRef>>;\n\nexport type DependencyList<Input> = Input extends readonly AnyResourceRef[]\n\t? Input\n\t: Input extends AnyResourceRef\n\t\t? readonly [Input]\n\t\t: Input extends Readonly<Record<string, AnyResourceRef>>\n\t\t\t? ReadonlyArray<Input[keyof Input]>\n\t\t\t: readonly [];\n\nexport type ResolvedDependencyList<Dependencies extends readonly AnyResourceRef[]> = readonly [\n\t...{\n\t\treadonly [K in keyof Dependencies]: Dependencies[K] extends AnyResourceRef\n\t\t\t? ResourceValueOf<Dependencies[K]>\n\t\t\t: never;\n\t},\n];\n\nexport type ResolvedDependencyObject<\n\tDependencies extends Readonly<Record<string, AnyResourceRef>>,\n> = {\n\treadonly [K in keyof Dependencies]: Dependencies[K] extends AnyResourceRef\n\t\t? ResourceValueOf<Dependencies[K]>\n\t\t: never;\n};\n\nexport type ResolvedDependencies<Input> = Input extends undefined\n\t? undefined\n\t: Input extends readonly AnyResourceRef[]\n\t\t? ResolvedDependencyList<Input>\n\t\t: Input extends AnyResourceRef\n\t\t\t? ResourceValueOf<Input>\n\t\t\t: Input extends Readonly<Record<string, AnyResourceRef>>\n\t\t\t\t? ResolvedDependencyObject<Input>\n\t\t\t\t: never;\n\n// The erased start shape. A plugin's `start` is single-arg\n// (`(deps) => Effect` or `() => Effect`). The R-channel is `unknown`\n// because the supervisor provides the ambient requirements (`PluginCtx`\n// via the `PluginContext` service tag, plus infra) before running it —\n// those requirements never surface in the public contract. `StartValue`\n// recovers the Value from the success channel.\ntype AnyPluginStart = (deps: never) => Effect.Effect<unknown, unknown, unknown>;\n\n// `ctx` is delivered to plugins through the `PluginContext` service tag\n// (`const ctx = yield* PluginContext`), NOT as a 2nd positional `start`\n// argument. That keeps `start` STRICTLY single-arg, which is what\n// gives `deps` automatic contextual typing.\n//\n// `PluginStart<Deps>` is both the CONSTRAINT and the DEFAULT contextual\n// shape `const Start` resolves to when a plugin authors `start: (deps) =>\n// …` (or `start: () => …`). Keeping it single-arg is load-bearing:\n//\n// - `deps` contextually types from the resolved `dependsOn` for EVERY\n// plugin, including those whose `dependsOn` is a runtime-built\n// (non-literal) array — `account` / `wallet` / `deepbook` — with no\n// per-plugin `deps:` annotation. (A `ctx` 2nd slot, optional OR\n// required, regressed `deps` to `any` for exactly those plugins,\n// which is why ctx now arrives via the requirement channel instead.)\n//\n// The `start` Effect's R-channel may include `PluginContext` (and infra\n// services) — that is an ambient requirement the supervisor satisfies; it\n// is held as `unknown` here and never propagates into\n// `Plugin<Id, Value, Needs>` (`Needs` = `dependsOn` only).\ntype PluginStart<Deps> = [Deps] extends [undefined]\n\t? () => Effect.Effect<unknown, unknown, unknown>\n\t: (deps: Deps) => Effect.Effect<unknown, unknown, unknown>;\n\ntype StartValue<Start> = Start extends (\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tdeps: any,\n) => Effect.Effect<infer Value, unknown, unknown>\n\t? Value\n\t: never;\n\nexport interface WatchDecl {\n\treadonly paths: ReadonlyArray<string>;\n\treadonly cascade?: boolean;\n}\n\nexport interface StaticNodeInputContribution {\n\treadonly kind: 'static';\n\treadonly value: unknown;\n}\n\nexport interface ComputedNodeInputContribution {\n\treadonly kind: 'computed';\n\treadonly compute: () => Effect.Effect<unknown, unknown, never>;\n}\n\nexport type NodeInputContribution = StaticNodeInputContribution | ComputedNodeInputContribution;\n\nexport const staticInputIdentity = (value: unknown): StaticNodeInputContribution => ({\n\tkind: 'static',\n\tvalue,\n});\n\nexport const computedInputIdentity = (\n\tcompute: () => Effect.Effect<unknown, unknown, never>,\n): ComputedNodeInputContribution => ({ kind: 'computed', compute });\n\ninterface PluginSpecBase<Id extends string, Start extends AnyPluginStart> {\n\treadonly id: Id;\n\treadonly role: PluginRole;\n\treadonly pluginKey?: PluginKey | string;\n\treadonly watch?: WatchDecl;\n\treadonly start: Start;\n\t/** Dashboard section bucket the plugin's rows belong to. Required so\n\t * the renderer never has to pattern-match on plugin name substrings\n\t * to compute it. The supervisor stamps this onto every row at\n\t * acquire-time. */\n\treadonly section: RowSection;\n\t/** Optional override for rows that own a routed endpoint. When set\n\t * and the row carries an endpoint, the renderer groups it under\n\t * `endpointSection` instead of `section`. Use sparingly — the\n\t * default is for the plugin's normal `section` to apply uniformly. */\n\treadonly endpointSection?: RowSection;\n\t/** When `true`, a live snapshot-restore re-acquire leaves this plugin\n\t * running instead of draining it. Reserved for operator-transport\n\t * plugins that carry no restorable chain state and would tear down the\n\t * very connection a restore is answering on if drained. Substrate's\n\t * restore planner filters on this flag with NO knowledge of which\n\t * plugins set it. Full restart (`stack.restart` / CLI) drains\n\t * everything regardless. */\n\treadonly keepAliveOnRestore?: true;\n\t/** Desired-state inputs folded into this node's substrate input id.\n\t * Dependencies' node input ids are included automatically; this field is\n\t * for inputs owned by the node itself, including file hashes. */\n\treadonly inputIdentity?: NodeInputContribution;\n\t/** Stack-free codegen-decl source. When present, the `codegen` verb can\n\t * emit this plugin's committed-projection contributions from config\n\t * alone (no live acquire), drawing on-chain ids from the supplied\n\t * resolver. Plugins whose contributions need live resolution — or which\n\t * only land in the gitignored dev tree — omit it. */\n\treadonly staticCodegen?: StaticCodegenSource;\n}\n\nexport type PluginSpec<\n\tId extends string,\n\tDependsOn extends DependencyInput | undefined,\n\tStart extends AnyPluginStart,\n> = PluginSpecBase<Id, Start> & {\n\treadonly dependsOn?: DependsOn;\n};\n\nexport interface Plugin<\n\tId extends string,\n\tValue,\n\tNeeds extends readonly AnyResourceRef[],\n> extends ResourceRef<Id, Value> {\n\treadonly [pluginBrand]: true;\n\treadonly [dependencyInputBrand]: DependencyInput | undefined;\n\treadonly dependsOn: Needs;\n\treadonly role: PluginRole;\n\treadonly pluginKey?: PluginKey | string;\n\treadonly watch?: WatchDecl;\n\treadonly start: (\n\t\tdeps: ResolvedDependencies<DependencyInput | undefined>,\n\t) => Effect.Effect<Value, unknown, unknown>;\n\treadonly section: RowSection;\n\treadonly endpointSection?: RowSection;\n\treadonly keepAliveOnRestore?: true;\n\treadonly inputIdentity?: NodeInputContribution;\n\treadonly staticCodegen?: StaticCodegenSource;\n}\n\nexport type AnyPlugin = Plugin<\n\tstring,\n\t// Erased runtime plugin values must be `any` rather than `unknown`\n\t// so concrete plugin instances remain assignable under strict\n\t// function parameter variance. Precise value types stay on concrete\n\t// `Plugin<Id, Value, ...>` instances.\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tany,\n\treadonly AnyResourceRef[]\n>;\n\nexport const isPlugin = (value: unknown): value is AnyPlugin =>\n\ttypeof value === 'object' &&\n\tvalue !== null &&\n\t(value as { readonly [pluginBrand]?: true })[pluginBrand] === true;\n\nexport const dependencyList = <Input extends DependencyInput | undefined>(\n\tdependsOn: Input,\n): DependencyList<Input> => {\n\tif (dependsOn === undefined) {\n\t\treturn [] as unknown as DependencyList<Input>;\n\t}\n\tif (Array.isArray(dependsOn)) {\n\t\treturn dependsOn as unknown as DependencyList<Input>;\n\t}\n\tif (isResourceRef(dependsOn)) {\n\t\treturn [dependsOn] as unknown as DependencyList<Input>;\n\t}\n\treturn Object.values(dependsOn) as unknown as DependencyList<Input>;\n};\n\nexport const uniqueResourceRefs = (refs: readonly AnyResourceRef[]): readonly AnyResourceRef[] => {\n\tconst seen = new Set<string>();\n\tconst unique: AnyResourceRef[] = [];\n\tfor (const ref of refs) {\n\t\tif (seen.has(ref.id)) continue;\n\t\tseen.add(ref.id);\n\t\tunique.push(ref);\n\t}\n\treturn unique;\n};\n\nexport const resolveDependencyValues = <Input extends DependencyInput | undefined>(\n\tdependsOn: Input,\n\tread: (resource: DependencyList<Input>[number]) => unknown,\n): ResolvedDependencies<Input> => {\n\tconst readAny = (resourceRef: AnyResourceRef) =>\n\t\tread(resourceRef as DependencyList<Input>[number]);\n\n\tif (dependsOn === undefined) {\n\t\treturn undefined as ResolvedDependencies<Input>;\n\t}\n\tif (Array.isArray(dependsOn)) {\n\t\treturn dependsOn.map((resourceRef) => readAny(resourceRef)) as ResolvedDependencies<Input>;\n\t}\n\tif (isResourceRef(dependsOn)) {\n\t\treturn readAny(dependsOn) as ResolvedDependencies<Input>;\n\t}\n\treturn Object.fromEntries(\n\t\tObject.entries(dependsOn).map(([key, resourceRef]) => [key, readAny(resourceRef)]),\n\t) as ResolvedDependencies<Input>;\n};\n\nexport const resolvePluginDependencies = (\n\tplugin: AnyPlugin,\n\tread: (resource: AnyResourceRef) => unknown,\n): ResolvedDependencies<DependencyInput | undefined> =>\n\tresolveDependencyValues(plugin[dependencyInputBrand], (resourceRef) =>\n\t\tread(resourceRef as AnyResourceRef),\n\t);\n\nexport const pluginDependencyRefs = (plugin: AnyPlugin): readonly AnyResourceRef[] =>\n\tdependencyList(plugin[dependencyInputBrand]) as readonly AnyResourceRef[];\n\n// Both the `Start` CONSTRAINT and DEFAULT are the single-arg\n// `PluginStart<Deps>`. A `start: (deps) => …` (or\n// `start: () => …`) body falls back to the default and contextually\n// types `deps` from the resolved `dependsOn` — no per-plugin `deps:`\n// annotation, including for plugins whose `dependsOn` is a runtime-built\n// array. Plugins reach `ctx` via `const ctx = yield* PluginContext`\n// inside the body; that requirement rides the start Effect's R-channel\n// (held as `unknown` by `PluginStart`) and never reaches the public\n// `Plugin` contract.\nexport function definePlugin<\n\tconst Id extends string,\n\tconst DependsOn extends readonly AnyResourceRef[],\n\tconst Start extends AnyPluginStart = PluginStart<ResolvedDependencyList<DependsOn>>,\n>(\n\tspec: PluginSpecBase<Id, Start> & {\n\t\treadonly dependsOn: DependsOn;\n\t},\n): Plugin<Id, StartValue<Start>, DependsOn>;\nexport function definePlugin<\n\tconst Id extends string,\n\tconst DependsOn extends Readonly<Record<string, AnyResourceRef>>,\n\tconst Start extends AnyPluginStart = PluginStart<ResolvedDependencyObject<DependsOn>>,\n>(\n\tspec: PluginSpecBase<Id, Start> & {\n\t\treadonly dependsOn: DependsOn;\n\t},\n): Plugin<Id, StartValue<Start>, DependencyList<DependsOn>>;\nexport function definePlugin<\n\tconst Id extends string,\n\tconst DependsOn extends AnyResourceRef,\n\tconst Start extends AnyPluginStart = PluginStart<ResourceValueOf<DependsOn>>,\n>(\n\tspec: PluginSpecBase<Id, Start> & {\n\t\treadonly dependsOn: DependsOn;\n\t},\n): Plugin<Id, StartValue<Start>, readonly [DependsOn]>;\nexport function definePlugin<\n\tconst Id extends string,\n\tconst Start extends AnyPluginStart = PluginStart<undefined>,\n>(\n\tspec: PluginSpecBase<Id, Start> & {\n\t\treadonly dependsOn?: undefined;\n\t},\n): Plugin<Id, StartValue<Start>, readonly []>;\nexport function definePlugin(\n\tspec: PluginSpecBase<string, AnyPluginStart> & {\n\t\treadonly dependsOn?: DependencyInput;\n\t},\n): AnyPlugin {\n\tconst dependsOn = uniqueResourceRefs(dependencyList(spec.dependsOn));\n\n\treturn {\n\t\t[resourceBrand]: true,\n\t\t[pluginBrand]: true,\n\t\t[dependencyInputBrand]: spec.dependsOn,\n\t\tid: spec.id,\n\t\tdependsOn,\n\t\trole: spec.role,\n\t\tsection: spec.section,\n\t\tstart: spec.start as AnyPlugin['start'],\n\t\t...(spec.pluginKey === undefined ? {} : { pluginKey: spec.pluginKey }),\n\t\t...(spec.watch === undefined ? {} : { watch: spec.watch }),\n\t\t...(spec.endpointSection === undefined ? {} : { endpointSection: spec.endpointSection }),\n\t\t...(spec.keepAliveOnRestore === undefined\n\t\t\t? {}\n\t\t\t: { keepAliveOnRestore: spec.keepAliveOnRestore }),\n\t\t...(spec.inputIdentity === undefined ? {} : { inputIdentity: spec.inputIdentity }),\n\t\t...(spec.staticCodegen === undefined ? {} : { staticCodegen: spec.staticCodegen }),\n\t} as AnyPlugin;\n}\n\nexport type ProvidedIdsOf<Members> =\n\tMembers extends ReadonlyArray<unknown>\n\t\t? Members[number] extends { readonly id: infer Id extends string }\n\t\t\t? Id\n\t\t\t: never\n\t\t: never;\n\nexport type ConsumedIdsOf<Members> =\n\tMembers extends ReadonlyArray<unknown>\n\t\t? Members[number] extends { readonly dependsOn: infer Dependencies }\n\t\t\t? Dependencies extends ReadonlyArray<infer Dependency>\n\t\t\t\t? Dependency extends { readonly id: infer Id extends string }\n\t\t\t\t\t? Id\n\t\t\t\t\t: never\n\t\t\t\t: never\n\t\t\t: never\n\t\t: never;\n\nexport type MissingProviders<Members> = Exclude<ConsumedIdsOf<Members>, ProvidedIdsOf<Members>>;\n\nexport interface __MissingProvidersError<Missing extends string> {\n\treadonly __missing_providers: Missing;\n}\n"],"mappings":";AAcA,MAAM,gBAA+B,OAAO,IAAI,mBAAmB;AACnE,MAAM,cAA6B,OAAO,IAAI,iBAAiB;AAE/D,MAAM,uBAAsC,OAAO,IAAI,kCAAkC;AAezF,MAAa,YAAqC,OAAe;AAEjE,MAAa,YACZ,QAEC;CACA;EACC,gBAAgB;AAClB;AAED,MAAa,iBAAiB,UAC7B,OAAO,UAAU,YACjB,UAAU,QACT,MAA8C,mBAAmB;AAiGnE,MAAa,uBAAuB,WAAiD;CACpF,MAAM;CACN;AACD;AAEA,MAAa,yBACZ,aACoC;CAAE,MAAM;CAAY;AAAQ;AA8EjE,MAAa,YAAY,UACxB,OAAO,UAAU,YACjB,UAAU,QACT,MAA4C,iBAAiB;AAE/D,MAAa,kBACZ,cAC2B;CAC3B,IAAI,cAAc,KAAA,GACjB,OAAO,CAAC;CAET,IAAI,MAAM,QAAQ,SAAS,GAC1B,OAAO;CAER,IAAI,cAAc,SAAS,GAC1B,OAAO,CAAC,SAAS;CAElB,OAAO,OAAO,OAAO,SAAS;AAC/B;AAEA,MAAa,sBAAsB,SAA+D;CACjG,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,SAA2B,CAAC;CAClC,KAAK,MAAM,OAAO,MAAM;EACvB,IAAI,KAAK,IAAI,IAAI,EAAE,GAAG;EACtB,KAAK,IAAI,IAAI,EAAE;EACf,OAAO,KAAK,GAAG;CAChB;CACA,OAAO;AACR;AAEA,MAAa,2BACZ,WACA,SACiC;CACjC,MAAM,WAAW,gBAChB,KAAK,WAA4C;CAElD,IAAI,cAAc,KAAA,GACjB;CAED,IAAI,MAAM,QAAQ,SAAS,GAC1B,OAAO,UAAU,KAAK,gBAAgB,QAAQ,WAAW,CAAC;CAE3D,IAAI,cAAc,SAAS,GAC1B,OAAO,QAAQ,SAAS;CAEzB,OAAO,OAAO,YACb,OAAO,QAAQ,SAAS,CAAC,CAAC,KAAK,CAAC,KAAK,iBAAiB,CAAC,KAAK,QAAQ,WAAW,CAAC,CAAC,CAClF;AACD;AAEA,MAAa,6BACZ,QACA,SAEA,wBAAwB,OAAO,wBAAwB,gBACtD,KAAK,WAA6B,CACnC;AAED,MAAa,wBAAwB,WACpC,eAAe,OAAO,qBAAqB;AA8C5C,SAAgB,aACf,MAGY;CACZ,MAAM,YAAY,mBAAmB,eAAe,KAAK,SAAS,CAAC;CAEnE,OAAO;GACL,gBAAgB;GAChB,cAAc;GACd,uBAAuB,KAAK;EAC7B,IAAI,KAAK;EACT;EACA,MAAM,KAAK;EACX,SAAS,KAAK;EACd,OAAO,KAAK;EACZ,GAAI,KAAK,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,KAAK,UAAU;EACpE,GAAI,KAAK,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM;EACxD,GAAI,KAAK,oBAAoB,KAAA,IAAY,CAAC,IAAI,EAAE,iBAAiB,KAAK,gBAAgB;EACtF,GAAI,KAAK,uBAAuB,KAAA,IAC7B,CAAC,IACD,EAAE,oBAAoB,KAAK,mBAAmB;EACjD,GAAI,KAAK,kBAAkB,KAAA,IAAY,CAAC,IAAI,EAAE,eAAe,KAAK,cAAc;EAChF,GAAI,KAAK,kBAAkB,KAAA,IAAY,CAAC,IAAI,EAAE,eAAe,KAAK,cAAc;CACjF;AACD"}
|
|
@@ -12,7 +12,7 @@ interface SubscribableState {
|
|
|
12
12
|
readonly cycle: {
|
|
13
13
|
readonly id: number;
|
|
14
14
|
readonly startedAt: number;
|
|
15
|
-
readonly phase: 'booting' | 'running' | 'restarting' | 'shutting-down';
|
|
15
|
+
readonly phase: 'booting' | 'running' | 'restarting' | 'shutting-down' | 'snapshotting' | 'restoring';
|
|
16
16
|
};
|
|
17
17
|
readonly rows: ReadonlyArray<Row>;
|
|
18
18
|
readonly endpoints: ReadonlyArray<Endpoint>;
|
|
@@ -5,8 +5,7 @@ import { closeSync, fsyncSync, linkSync, mkdirSync, openSync, renameSync, unlink
|
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
//#region src/substrate/runtime/atomic-write.ts
|
|
7
7
|
/** 8-hex tempfile suffix. `crypto.randomUUID()` is collision-safe under
|
|
8
|
-
* parallel callers within the same pid
|
|
9
|
-
* `Math.random()`-based suffix flagged in STYLE_GUIDE §17. */
|
|
8
|
+
* parallel callers within the same pid. */
|
|
10
9
|
const tempSuffix = () => randomUUID().replace(/-/g, "").slice(0, 8);
|
|
11
10
|
const failStage = (path, stage) => (cause) => Effect.fail(new AtomicWriteFailed({
|
|
12
11
|
path,
|
|
@@ -40,7 +39,7 @@ const atomicWriteFile = (path, bytes, options = {}) => Effect.gen(function* () {
|
|
|
40
39
|
}));
|
|
41
40
|
yield* fs.rename(tmp, path).pipe(Effect.catch(failStage(path, "rename")));
|
|
42
41
|
}).pipe(Effect.onError(() => fs.remove(tmp, { force: true }).pipe(Effect.ignore)));
|
|
43
|
-
})
|
|
42
|
+
});
|
|
44
43
|
/**
|
|
45
44
|
* Atomically write a JSON value. Encodes via Schema (so the on-disk
|
|
46
45
|
* shape is the schema's `Encoded` form, not the rich `Type`). Caller
|
|
@@ -110,7 +109,7 @@ const unlinkBestEffort = (path) => {
|
|
|
110
109
|
*
|
|
111
110
|
* Used inside `Effect.try` by the cross-process modules; the caller
|
|
112
111
|
* maps the thrown error to a typed plugin/runtime error
|
|
113
|
-
* (`RosterIoError` / `
|
|
112
|
+
* (`RosterIoError` / `StackLockIoError` / etc.).
|
|
114
113
|
*/
|
|
115
114
|
const atomicWriteFileSync = (path, bytes, options = {}) => {
|
|
116
115
|
const tmp = writeAndFsyncTempSync(path, bytes, options.mode ?? 384, options.parentMode ?? 448);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic-write.mjs","names":[],"sources":["../../../src/substrate/runtime/atomic-write.ts"],"sourcesContent":["// Atomic write primitive — ONE canonical implementation.\n//\n// Architecture § \"What's collapsed\" — three tempfile+rename impls\n// (atomic-write, state-store, global registry) collapse to one. This\n// is that one.\n//\n// Contract:\n// 1. Ensure parent directory exists.\n// 2. Open `<final>.tmp.<pid>.<rand>` with `wx` (O_EXCL) — refuse\n// to clobber a half-written sibling from a crashed earlier\n// writer in the same pid.\n// 3. Write bytes.\n// 4. `fsync` the file.\n// 5. Rename tempfile → final. Rename on POSIX is atomic w.r.t.\n// `open` of the final path.\n// 6. On any failure mid-flight, unlink the tempfile. Best-effort:\n// a tempfile-cleanup failure does NOT mask the original error.\n//\n// Two surfaces:\n// - `atomicWriteFile` / `atomicWriteJson` — Effect/FileSystem-based,\n// used by every async write site (manifest, state-store, cache).\n// - `atomicWriteFileSync` / `atomicWriteJsonSync` — node:fs-sync,\n// used by the cross-process modules (roster, snapshot-reservation,\n// stack-lock) that hold `stack.lock` and must keep their critical\n// section non-yielding. Substrate-fix-plan #11 tracks unifying\n// these onto Effect FS once we lift the cross-process modules off\n// `node:fs`; until then both surfaces share THIS file (and only\n// this file) so the tempfile dance has ONE owner.\n//\n// What we do NOT do here:\n// - fsync the parent directory. Linux's man fsync(2) suggests it\n// for full durability after rename(); the Effect platform layer\n// does not expose dir-fsync. Documented limitation — recoverable\n// on crash because the state-store rewrites on every change and\n// cache misses re-produce.\n\nimport {\n\tcloseSync,\n\tfsyncSync,\n\tlinkSync,\n\tmkdirSync,\n\topenSync,\n\trenameSync,\n\tunlinkSync,\n\twriteFileSync,\n} from 'node:fs';\nimport { randomUUID } from 'node:crypto';\n\nimport { Effect, FileSystem, Schema } from 'effect';\n\nimport { selfPid } from './cross-process/self-pid.ts';\nimport { AtomicWriteFailed } from './errors.ts';\n\n/** 8-hex tempfile suffix. `crypto.randomUUID()` is collision-safe under\n * parallel callers within the same pid — replaces the\n * `Math.random()`-based suffix flagged in STYLE_GUIDE §17. */\nconst tempSuffix = (): string => randomUUID().replace(/-/g, '').slice(0, 8);\n\nconst failStage =\n\t(\n\t\tpath: string,\n\t\tstage: AtomicWriteFailed['stage'],\n\t): ((cause: unknown) => Effect.Effect<never, AtomicWriteFailed>) =>\n\t(cause) =>\n\t\tEffect.fail(new AtomicWriteFailed({ path, stage, cause }));\n\n/**\n * Atomically write `bytes` to `path`. The helper ensures\n * `dirname(path)` exists (recursive mkdir, idempotent).\n *\n * `mode` is the final file mode bits (default 0o600 — secret-safe\n * default; cache callers may bump to 0o644 if they want world-read).\n */\nexport const atomicWriteFile = (\n\tpath: string,\n\tbytes: Uint8Array,\n\toptions: { readonly mode?: number; readonly parentMode?: number } = {},\n): Effect.Effect<void, AtomicWriteFailed, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst mode = options.mode ?? 0o600;\n\t\tconst parentMode = options.parentMode ?? 0o700;\n\t\tconst tmp = `${path}.tmp.${selfPid()}.${tempSuffix()}`;\n\t\t// 1. Ensure parent. recursive: true is idempotent under\n\t\t// concurrent callers — `mkdir -p` semantics.\n\t\tyield* fs\n\t\t\t.makeDirectory(dirname(path), { recursive: true, mode: parentMode })\n\t\t\t.pipe(Effect.catch(failStage(path, 'mkdir-parent')));\n\t\t// 2-5. Write + rename, with unconditional tempfile cleanup on\n\t\t// ANY failure path (open-temp / write / fsync / rename).\n\t\t// `force: true` makes the unlink a no-op if the file\n\t\t// doesn't exist (open-temp failed early, or rename moved\n\t\t// it to its final home).\n\t\tyield* Effect.gen(function* () {\n\t\t\tyield* Effect.scoped(\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst file = yield* fs\n\t\t\t\t\t\t.open(tmp, { flag: 'wx', mode })\n\t\t\t\t\t\t.pipe(Effect.catch(failStage(path, 'open-temp')));\n\t\t\t\t\tyield* file.writeAll(bytes).pipe(Effect.catch(failStage(path, 'write')));\n\t\t\t\t\t// fsync — durability boundary. Without this the\n\t\t\t\t\t// rename can land but bytes can stay in the page\n\t\t\t\t\t// cache through a power loss.\n\t\t\t\t\tyield* file.sync.pipe(Effect.catch(failStage(path, 'fsync')));\n\t\t\t\t}),\n\t\t\t);\n\t\t\tyield* fs.rename(tmp, path).pipe(Effect.catch(failStage(path, 'rename')));\n\t\t}).pipe(Effect.onError(() => fs.remove(tmp, { force: true }).pipe(Effect.ignore)));\n\t}).pipe(Effect.withSpan('substrate.atomicWriteFile', { attributes: { path } }));\n\n/**\n * Atomically write a JSON value. Encodes via Schema (so the on-disk\n * shape is the schema's `Encoded` form, not the rich `Type`). Caller\n * supplies a schema whose `Type` matches `value`.\n */\nexport const atomicWriteJson = <A, I>(\n\tpath: string,\n\tschema: Schema.Codec<A, I>,\n\tvalue: A,\n\toptions?: { readonly mode?: number; readonly parentMode?: number },\n): Effect.Effect<void, AtomicWriteFailed, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst encoded = yield* Schema.encodeUnknownEffect(schema)(value).pipe(\n\t\t\tEffect.catch(failStage(path, 'encode')),\n\t\t);\n\t\tconst json = JSON.stringify(encoded, null, 2);\n\t\tconst bytes = new TextEncoder().encode(json);\n\t\tyield* atomicWriteFile(path, bytes, options);\n\t});\n\n// -----------------------------------------------------------------------------\n// Sync surface — for the cross-process modules that hold `stack.lock`\n// and must keep their critical section non-yielding. Same contract as\n// the Effect surface above.\n// -----------------------------------------------------------------------------\n\n/**\n * Internal: mkdir-parent → O_EXCL temp → write → fsync. Returns the\n * tempfile path on success; throws on any failure (and unlinks the\n * tempfile if it managed to land). The temp exists after return —\n * the caller is responsible for moving it to its final home (rename\n * for clobber semantics, link for exclusive-create semantics) AND\n * for unlinking the temp afterwards.\n *\n * Centralizes the durability boundary so both `atomicWriteFileSync`\n * (rename) and `atomicWriteFileExclusiveSync` (link+unlink) share\n * one tempfile/fsync implementation.\n */\nconst writeAndFsyncTempSync = (\n\tfinalPath: string,\n\tbytes: Uint8Array | string,\n\tmode: number,\n\tparentMode: number,\n): string => {\n\tconst tmp = `${finalPath}.tmp.${selfPid()}.${tempSuffix()}`;\n\tmkdirSync(dirname(finalPath), { recursive: true, mode: parentMode });\n\t// O_EXCL via `flag: 'wx'`. writeFileSync handles open + write +\n\t// close in one call but does NOT fsync; do it manually so we\n\t// preserve the durability boundary the Effect surface has.\n\twriteFileSync(tmp, bytes, { flag: 'wx', mode });\n\t// Tempfile exists from here — fsync failure must unlink before\n\t// rethrowing so a crashed-mid-flight writer doesn't leak a\n\t// half-written sibling.\n\ttry {\n\t\tlet fd: number | null = null;\n\t\ttry {\n\t\t\tfd = openSync(tmp, 'r');\n\t\t\tfsyncSync(fd);\n\t\t} finally {\n\t\t\tif (fd !== null) closeSync(fd);\n\t\t}\n\t} catch (cause) {\n\t\ttry {\n\t\t\tunlinkSync(tmp);\n\t\t} catch {\n\t\t\t// ignore — best-effort\n\t\t}\n\t\tthrow cause;\n\t}\n\treturn tmp;\n};\n\n/** Best-effort unlink. Swallows errors — used in cleanup paths where\n * a failure to remove the temp should NOT mask the original error. */\nconst unlinkBestEffort = (path: string): void => {\n\ttry {\n\t\tunlinkSync(path);\n\t} catch {\n\t\t// ignore\n\t}\n};\n\n/**\n * Synchronous atomic write. Same disk-side contract as\n * `atomicWriteFile` (mkdir-parent → O_EXCL temp → write → fsync →\n * rename). Returns `void` on success; throws on any failure with\n * `cause` set to the underlying `NodeJS.ErrnoException`.\n *\n * Rename CLOBBERS a pre-existing file at the final path. Callers\n * that need exclusive-create semantics (\"fail if the final path\n * already exists, atomically\") want `atomicWriteFileExclusiveSync`\n * instead — that surface uses `linkSync` which is POSIX-atomic and\n * fails with `EEXIST` rather than overwriting.\n *\n * Used inside `Effect.try` by the cross-process modules; the caller\n * maps the thrown error to a typed plugin/runtime error\n * (`RosterIoError` / `SnapshotReservationIoError` / etc.).\n */\nexport const atomicWriteFileSync = (\n\tpath: string,\n\tbytes: Uint8Array | string,\n\toptions: { readonly mode?: number; readonly parentMode?: number } = {},\n): void => {\n\tconst mode = options.mode ?? 0o600;\n\tconst parentMode = options.parentMode ?? 0o700;\n\tconst tmp = writeAndFsyncTempSync(path, bytes, mode, parentMode);\n\tlet renamed = false;\n\ttry {\n\t\trenameSync(tmp, path);\n\t\trenamed = true;\n\t} finally {\n\t\tif (!renamed) unlinkBestEffort(tmp);\n\t}\n};\n\n/**\n * Synchronous atomic EXCLUSIVE-CREATE write. Same disk-side contract\n * as `atomicWriteFileSync` (mkdir-parent → O_EXCL temp → write →\n * fsync) but uses `linkSync(tmp, final)` instead of `renameSync` for\n * the final installation step. `link` is POSIX-atomic and FAILS with\n * `EEXIST` if the target already exists — closing the rename-clobber\n * race that `existsSync` + `renameSync` cannot.\n *\n * On success: the tempfile is unlinked (the hard link satisfies the\n * final path).\n *\n * On `EEXIST`: the tempfile is unlinked and the underlying\n * `NodeJS.ErrnoException` (with `code === 'EEXIST'`) is rethrown so\n * the caller can read the winning final-path and decide what to do.\n *\n * On any other failure: the tempfile is unlinked and the error is\n * rethrown unchanged.\n *\n * POSIX `link(2)` is available on Linux + macOS + WSL. Devstack is\n * POSIX-only at runtime.\n */\nexport const atomicWriteFileExclusiveSync = (\n\tpath: string,\n\tbytes: Uint8Array | string,\n\toptions: { readonly mode?: number; readonly parentMode?: number } = {},\n): void => {\n\tconst mode = options.mode ?? 0o600;\n\tconst parentMode = options.parentMode ?? 0o700;\n\tconst tmp = writeAndFsyncTempSync(path, bytes, mode, parentMode);\n\ttry {\n\t\t// Atomic exclusive-create. On EEXIST the tempfile remains and\n\t\t// the finally below unlinks it; the EEXIST error propagates.\n\t\tlinkSync(tmp, path);\n\t} finally {\n\t\t// Always unlink the tempfile: on success the hard link IS the\n\t\t// final file; on failure (EEXIST or otherwise) the tempfile\n\t\t// should not leak.\n\t\tunlinkBestEffort(tmp);\n\t}\n};\n\n/**\n * Synchronous atomic JSON write. Stringifies `value` and routes\n * through `atomicWriteFileSync`. No Schema encoding here — the\n * cross-process modules round-trip plain JSON (their schemas are\n * Schema.Struct of primitives; `JSON.stringify` is sufficient and\n * keeps the sync path dependency-free of Effect's Schema effects).\n */\nexport const atomicWriteJsonSync = (\n\tpath: string,\n\tvalue: unknown,\n\toptions?: { readonly mode?: number; readonly parentMode?: number },\n): void => {\n\tconst json = JSON.stringify(value);\n\tatomicWriteFileSync(path, json, options);\n};\n\n// Local dirname — we don't yield Path service here because\n// atomic-write may be called outside of a path-service-having\n// context (tests, recovery scripts). Posix-only; devstack is\n// posix-only in practice.\nconst dirname = (p: string): string => {\n\tconst i = p.lastIndexOf('/');\n\treturn i <= 0 ? '/' : p.slice(0, i);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"atomic-write.mjs","names":[],"sources":["../../../src/substrate/runtime/atomic-write.ts"],"sourcesContent":["// Atomic write primitive — ONE canonical implementation.\n//\n// Architecture § \"What's collapsed\" — three tempfile+rename impls\n// (atomic-write, cache, global registry) collapse to one. This\n// is that one.\n//\n// Contract:\n// 1. Ensure parent directory exists.\n// 2. Open `<final>.tmp.<pid>.<rand>` with `wx` (O_EXCL) — refuse\n// to clobber a half-written sibling from a crashed earlier\n// writer in the same pid.\n// 3. Write bytes.\n// 4. `fsync` the file.\n// 5. Rename tempfile → final. Rename on POSIX is atomic w.r.t.\n// `open` of the final path.\n// 6. On any failure mid-flight, unlink the tempfile. Best-effort:\n// a tempfile-cleanup failure does NOT mask the original error.\n//\n// Two surfaces:\n// - `atomicWriteFile` / `atomicWriteJson` — Effect/FileSystem-based,\n// used by every async write site (manifest, cache).\n// - `atomicWriteFileSync` / `atomicWriteJsonSync` — node:fs-sync,\n// used by the cross-process modules (roster, stack-lock) that hold\n// `stack.lock` and must keep their critical\n// section non-yielding. Substrate-fix-plan #11 tracks unifying\n// these onto Effect FS once we lift the cross-process modules off\n// `node:fs`; until then both surfaces share THIS file (and only\n// this file) so the tempfile dance has ONE owner.\n//\n// What we do NOT do here:\n// - fsync the parent directory. Linux's man fsync(2) suggests it\n// for full durability after rename(); the Effect platform layer\n// does not expose dir-fsync. Documented limitation — recoverable\n// on crash because every on-disk artifact rewrites on change and\n// cache misses re-produce.\n\nimport {\n\tcloseSync,\n\tfsyncSync,\n\tlinkSync,\n\tmkdirSync,\n\topenSync,\n\trenameSync,\n\tunlinkSync,\n\twriteFileSync,\n} from 'node:fs';\nimport { randomUUID } from 'node:crypto';\n\nimport { Effect, FileSystem, Schema } from 'effect';\n\nimport { selfPid } from './cross-process/self-pid.ts';\nimport { AtomicWriteFailed } from './errors.ts';\n\n/** 8-hex tempfile suffix. `crypto.randomUUID()` is collision-safe under\n * parallel callers within the same pid. */\nconst tempSuffix = (): string => randomUUID().replace(/-/g, '').slice(0, 8);\n\nconst failStage =\n\t(\n\t\tpath: string,\n\t\tstage: AtomicWriteFailed['stage'],\n\t): ((cause: unknown) => Effect.Effect<never, AtomicWriteFailed>) =>\n\t(cause) =>\n\t\tEffect.fail(new AtomicWriteFailed({ path, stage, cause }));\n\n/**\n * Atomically write `bytes` to `path`. The helper ensures\n * `dirname(path)` exists (recursive mkdir, idempotent).\n *\n * `mode` is the final file mode bits (default 0o600 — secret-safe\n * default; cache callers may bump to 0o644 if they want world-read).\n */\nexport const atomicWriteFile = (\n\tpath: string,\n\tbytes: Uint8Array,\n\toptions: { readonly mode?: number; readonly parentMode?: number } = {},\n): Effect.Effect<void, AtomicWriteFailed, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst mode = options.mode ?? 0o600;\n\t\tconst parentMode = options.parentMode ?? 0o700;\n\t\tconst tmp = `${path}.tmp.${selfPid()}.${tempSuffix()}`;\n\t\t// 1. Ensure parent. recursive: true is idempotent under\n\t\t// concurrent callers — `mkdir -p` semantics.\n\t\tyield* fs\n\t\t\t.makeDirectory(dirname(path), { recursive: true, mode: parentMode })\n\t\t\t.pipe(Effect.catch(failStage(path, 'mkdir-parent')));\n\t\t// 2-5. Write + rename, with unconditional tempfile cleanup on\n\t\t// ANY failure path (open-temp / write / fsync / rename).\n\t\t// `force: true` makes the unlink a no-op if the file\n\t\t// doesn't exist (open-temp failed early, or rename moved\n\t\t// it to its final home).\n\t\tyield* Effect.gen(function* () {\n\t\t\tyield* Effect.scoped(\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst file = yield* fs\n\t\t\t\t\t\t.open(tmp, { flag: 'wx', mode })\n\t\t\t\t\t\t.pipe(Effect.catch(failStage(path, 'open-temp')));\n\t\t\t\t\tyield* file.writeAll(bytes).pipe(Effect.catch(failStage(path, 'write')));\n\t\t\t\t\t// fsync — durability boundary. Without this the\n\t\t\t\t\t// rename can land but bytes can stay in the page\n\t\t\t\t\t// cache through a power loss.\n\t\t\t\t\tyield* file.sync.pipe(Effect.catch(failStage(path, 'fsync')));\n\t\t\t\t}),\n\t\t\t);\n\t\t\tyield* fs.rename(tmp, path).pipe(Effect.catch(failStage(path, 'rename')));\n\t\t}).pipe(Effect.onError(() => fs.remove(tmp, { force: true }).pipe(Effect.ignore)));\n\t});\n\n/**\n * Atomically write a JSON value. Encodes via Schema (so the on-disk\n * shape is the schema's `Encoded` form, not the rich `Type`). Caller\n * supplies a schema whose `Type` matches `value`.\n */\nexport const atomicWriteJson = <A, I>(\n\tpath: string,\n\tschema: Schema.Codec<A, I>,\n\tvalue: A,\n\toptions?: { readonly mode?: number; readonly parentMode?: number },\n): Effect.Effect<void, AtomicWriteFailed, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst encoded = yield* Schema.encodeUnknownEffect(schema)(value).pipe(\n\t\t\tEffect.catch(failStage(path, 'encode')),\n\t\t);\n\t\tconst json = JSON.stringify(encoded, null, 2);\n\t\tconst bytes = new TextEncoder().encode(json);\n\t\tyield* atomicWriteFile(path, bytes, options);\n\t});\n\n// -----------------------------------------------------------------------------\n// Sync surface — for the cross-process modules that hold `stack.lock`\n// and must keep their critical section non-yielding. Same contract as\n// the Effect surface above.\n// -----------------------------------------------------------------------------\n\n/**\n * Internal: mkdir-parent → O_EXCL temp → write → fsync. Returns the\n * tempfile path on success; throws on any failure (and unlinks the\n * tempfile if it managed to land). The temp exists after return —\n * the caller is responsible for moving it to its final home (rename\n * for clobber semantics, link for exclusive-create semantics) AND\n * for unlinking the temp afterwards.\n *\n * Centralizes the durability boundary so both `atomicWriteFileSync`\n * (rename) and `atomicWriteFileExclusiveSync` (link+unlink) share\n * one tempfile/fsync implementation.\n */\nconst writeAndFsyncTempSync = (\n\tfinalPath: string,\n\tbytes: Uint8Array | string,\n\tmode: number,\n\tparentMode: number,\n): string => {\n\tconst tmp = `${finalPath}.tmp.${selfPid()}.${tempSuffix()}`;\n\tmkdirSync(dirname(finalPath), { recursive: true, mode: parentMode });\n\t// O_EXCL via `flag: 'wx'`. writeFileSync handles open + write +\n\t// close in one call but does NOT fsync; do it manually so we\n\t// preserve the durability boundary the Effect surface has.\n\twriteFileSync(tmp, bytes, { flag: 'wx', mode });\n\t// Tempfile exists from here — fsync failure must unlink before\n\t// rethrowing so a crashed-mid-flight writer doesn't leak a\n\t// half-written sibling.\n\ttry {\n\t\tlet fd: number | null = null;\n\t\ttry {\n\t\t\tfd = openSync(tmp, 'r');\n\t\t\tfsyncSync(fd);\n\t\t} finally {\n\t\t\tif (fd !== null) closeSync(fd);\n\t\t}\n\t} catch (cause) {\n\t\ttry {\n\t\t\tunlinkSync(tmp);\n\t\t} catch {\n\t\t\t// ignore — best-effort\n\t\t}\n\t\tthrow cause;\n\t}\n\treturn tmp;\n};\n\n/** Best-effort unlink. Swallows errors — used in cleanup paths where\n * a failure to remove the temp should NOT mask the original error. */\nconst unlinkBestEffort = (path: string): void => {\n\ttry {\n\t\tunlinkSync(path);\n\t} catch {\n\t\t// ignore\n\t}\n};\n\n/**\n * Synchronous atomic write. Same disk-side contract as\n * `atomicWriteFile` (mkdir-parent → O_EXCL temp → write → fsync →\n * rename). Returns `void` on success; throws on any failure with\n * `cause` set to the underlying `NodeJS.ErrnoException`.\n *\n * Rename CLOBBERS a pre-existing file at the final path. Callers\n * that need exclusive-create semantics (\"fail if the final path\n * already exists, atomically\") want `atomicWriteFileExclusiveSync`\n * instead — that surface uses `linkSync` which is POSIX-atomic and\n * fails with `EEXIST` rather than overwriting.\n *\n * Used inside `Effect.try` by the cross-process modules; the caller\n * maps the thrown error to a typed plugin/runtime error\n * (`RosterIoError` / `StackLockIoError` / etc.).\n */\nexport const atomicWriteFileSync = (\n\tpath: string,\n\tbytes: Uint8Array | string,\n\toptions: { readonly mode?: number; readonly parentMode?: number } = {},\n): void => {\n\tconst mode = options.mode ?? 0o600;\n\tconst parentMode = options.parentMode ?? 0o700;\n\tconst tmp = writeAndFsyncTempSync(path, bytes, mode, parentMode);\n\tlet renamed = false;\n\ttry {\n\t\trenameSync(tmp, path);\n\t\trenamed = true;\n\t} finally {\n\t\tif (!renamed) unlinkBestEffort(tmp);\n\t}\n};\n\n/**\n * Synchronous atomic EXCLUSIVE-CREATE write. Same disk-side contract\n * as `atomicWriteFileSync` (mkdir-parent → O_EXCL temp → write →\n * fsync) but uses `linkSync(tmp, final)` instead of `renameSync` for\n * the final installation step. `link` is POSIX-atomic and FAILS with\n * `EEXIST` if the target already exists — closing the rename-clobber\n * race that `existsSync` + `renameSync` cannot.\n *\n * On success: the tempfile is unlinked (the hard link satisfies the\n * final path).\n *\n * On `EEXIST`: the tempfile is unlinked and the underlying\n * `NodeJS.ErrnoException` (with `code === 'EEXIST'`) is rethrown so\n * the caller can read the winning final-path and decide what to do.\n *\n * On any other failure: the tempfile is unlinked and the error is\n * rethrown unchanged.\n *\n * POSIX `link(2)` is available on Linux + macOS + WSL. Devstack is\n * POSIX-only at runtime.\n */\nexport const atomicWriteFileExclusiveSync = (\n\tpath: string,\n\tbytes: Uint8Array | string,\n\toptions: { readonly mode?: number; readonly parentMode?: number } = {},\n): void => {\n\tconst mode = options.mode ?? 0o600;\n\tconst parentMode = options.parentMode ?? 0o700;\n\tconst tmp = writeAndFsyncTempSync(path, bytes, mode, parentMode);\n\ttry {\n\t\t// Atomic exclusive-create. On EEXIST the tempfile remains and\n\t\t// the finally below unlinks it; the EEXIST error propagates.\n\t\tlinkSync(tmp, path);\n\t} finally {\n\t\t// Always unlink the tempfile: on success the hard link IS the\n\t\t// final file; on failure (EEXIST or otherwise) the tempfile\n\t\t// should not leak.\n\t\tunlinkBestEffort(tmp);\n\t}\n};\n\n/**\n * Synchronous atomic JSON write. Stringifies `value` and routes\n * through `atomicWriteFileSync`. No Schema encoding here — the\n * cross-process modules round-trip plain JSON (their schemas are\n * Schema.Struct of primitives; `JSON.stringify` is sufficient and\n * keeps the sync path dependency-free of Effect's Schema effects).\n */\nexport const atomicWriteJsonSync = (\n\tpath: string,\n\tvalue: unknown,\n\toptions?: { readonly mode?: number; readonly parentMode?: number },\n): void => {\n\tconst json = JSON.stringify(value);\n\tatomicWriteFileSync(path, json, options);\n};\n\n// Local dirname — we don't yield Path service here because\n// atomic-write may be called outside of a path-service-having\n// context (tests, recovery scripts). Posix-only; devstack is\n// posix-only in practice.\nconst dirname = (p: string): string => {\n\tconst i = p.lastIndexOf('/');\n\treturn i <= 0 ? '/' : p.slice(0, i);\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.mjs","names":[],"sources":["../../../../src/substrate/runtime/cache/schema.ts"],"sourcesContent":["// On-disk shape for one cache entry.\n//\n// One file per `(namespace,
|
|
1
|
+
{"version":3,"file":"schema.mjs","names":[],"sources":["../../../../src/substrate/runtime/cache/schema.ts"],"sourcesContent":["// On-disk shape for one cache entry.\n//\n// One file per `(namespace, chainId, contentHash)` triple, at\n// `<runtime-root>/stacks/<stack>/cache/<namespace>/<chainId>/<contentHash>.json`.\n//\n// The entry envelope wraps the bytes (base64-encoded — JSON has no\n// binary literal) and the metadata renderer / verify needs. Cache\n// callers don't see the envelope; they only see `CacheEntry` from\n// `primitives/cache.ts` (bytes + writtenAt).\n\nimport { Schema } from 'effect';\n\nimport { versionedDocSchema } from '../../versioned-doc-schema.ts';\n\n/** Versioned envelope. Schema-decode failure on read is treated as\n * a cache MISS (best-effort cache contract — corruption recovers\n * by re-producing the artifact). */\nexport const CacheEntryDoc = versionedDocSchema(1, {\n\t/** Bytes, base64-encoded. Chosen over hex because typical\n\t * artifacts are larger (image manifests, package digests) and\n\t * base64 is 33% overhead vs hex's 100%. */\n\tbytes: Schema.String,\n\t/** Unix millis of the write. Renderers may show \"produced 5s\n\t * ago\"; substrate uses it only for diagnostics. */\n\twrittenAt: Schema.Number,\n\t/** Optional namespace-private hint (e.g. \"image-tag the cached\n\t * blob refers to\"). Substrate stores opaquely. */\n\thint: Schema.optional(Schema.String),\n});\nexport type CacheEntryDoc = typeof CacheEntryDoc.Type;\n"],"mappings":";;;;;;AAiBA,MAAa,gBAAgB,mBAAmB,GAAG;;;;CAIlD,OAAO,OAAO;;;CAGd,WAAW,OAAO;;;CAGlB,MAAM,OAAO,SAAS,OAAO,MAAM;AACpC,CAAC"}
|
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { SpanAttr } from "../observability/spans.mjs";
|
|
1
|
+
import { decodeJsonText, parseJsonTextSync } from "../runtime-decode.mjs";
|
|
3
2
|
import { CacheError } from "../errors.mjs";
|
|
4
3
|
import { atomicWriteJson } from "../atomic-write.mjs";
|
|
5
|
-
import { decodeJsonText } from "../runtime-decode.mjs";
|
|
6
4
|
import { CacheEntryDoc } from "./schema.mjs";
|
|
5
|
+
import { setCurrentPluginPhase } from "../current-plugin.mjs";
|
|
6
|
+
import { StackPathsService } from "../paths.mjs";
|
|
7
7
|
import { Context, Effect, FileSystem, Layer } from "effect";
|
|
8
8
|
//#region src/substrate/runtime/cache/service.ts
|
|
9
9
|
const base64Encode = (bytes) => Buffer.from(bytes).toString("base64");
|
|
10
10
|
const base64Decode = (s) => new Uint8Array(Buffer.from(s, "base64"));
|
|
11
|
+
const encodePayload = (value) => new TextEncoder().encode(JSON.stringify(value));
|
|
12
|
+
const decodePayload = (bytes) => {
|
|
13
|
+
try {
|
|
14
|
+
return parseJsonTextSync(new TextDecoder().decode(bytes), {
|
|
15
|
+
source: "artifact-publisher cache payload",
|
|
16
|
+
mkError: (issue) => issue
|
|
17
|
+
});
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
11
22
|
var CacheService = class extends Context.Service()("@devstack/substrate/Cache") {};
|
|
12
23
|
/**
|
|
13
24
|
* Cache Layer. Stateless beyond the path resolver and the platform
|
|
@@ -37,17 +48,13 @@ const layerCache = Layer.effect(CacheService, Effect.gen(function* () {
|
|
|
37
48
|
}).pipe(Effect.tapCause((cause) => Effect.logDebug("cache entry decode failed; treating as miss", {
|
|
38
49
|
file,
|
|
39
50
|
cause
|
|
40
|
-
})), Effect.catch(() => Effect.
|
|
51
|
+
})), Effect.catch(() => Effect.succeed(null)));
|
|
41
52
|
if (doc === null) return null;
|
|
42
53
|
return {
|
|
43
54
|
bytes: base64Decode(doc.bytes),
|
|
44
55
|
writtenAt: doc.writtenAt
|
|
45
56
|
};
|
|
46
|
-
})
|
|
47
|
-
namespace: key.namespace,
|
|
48
|
-
chain: key.chain,
|
|
49
|
-
contentHash: key.contentHash
|
|
50
|
-
} }));
|
|
57
|
+
});
|
|
51
58
|
const write = (key, bytes) => Effect.gen(function* () {
|
|
52
59
|
const { file } = paths.cacheEntry(key.namespace, key.chain, key.contentHash);
|
|
53
60
|
yield* atomicWriteJson(file, CacheEntryDoc, {
|
|
@@ -62,11 +69,7 @@ const layerCache = Layer.effect(CacheService, Effect.gen(function* () {
|
|
|
62
69
|
detail: `cache write failed: ${file}`,
|
|
63
70
|
cause
|
|
64
71
|
}))), Effect.provideService(FileSystem.FileSystem, fs));
|
|
65
|
-
})
|
|
66
|
-
namespace: key.namespace,
|
|
67
|
-
chain: key.chain,
|
|
68
|
-
contentHash: key.contentHash
|
|
69
|
-
} }));
|
|
72
|
+
});
|
|
70
73
|
const remove = (key) => Effect.gen(function* () {
|
|
71
74
|
const { file } = paths.cacheEntry(key.namespace, key.chain, key.contentHash);
|
|
72
75
|
yield* fs.remove(file, { force: true }).pipe(Effect.catch((cause) => Effect.fail(new CacheError({
|
|
@@ -74,15 +77,42 @@ const layerCache = Layer.effect(CacheService, Effect.gen(function* () {
|
|
|
74
77
|
detail: `cache delete failed: ${file}`,
|
|
75
78
|
cause
|
|
76
79
|
}))));
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
});
|
|
81
|
+
const publish = (spec) => Effect.gen(function* () {
|
|
82
|
+
const hit = yield* lookup({
|
|
83
|
+
namespace: spec.namespace,
|
|
84
|
+
chain: spec.chain,
|
|
85
|
+
contentHash: spec.contentHash
|
|
86
|
+
}).pipe(Effect.catch(() => Effect.succeed(null)));
|
|
87
|
+
if (hit !== null) {
|
|
88
|
+
const cached = decodePayload(hit.bytes);
|
|
89
|
+
if (cached !== null) {
|
|
90
|
+
if ((yield* spec.verify(cached)) !== null) {
|
|
91
|
+
yield* spec.register(cached);
|
|
92
|
+
yield* Effect.logDebug(`artifact-publisher: reusing cached '${spec.namespace}' on chain ${spec.chain} (cache hit; no re-deploy).`);
|
|
93
|
+
return cached;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (hit === null) yield* Effect.logInfo(`artifact-publisher: producing '${spec.namespace}' on chain ${spec.chain} — no cached artifact for this chain + content hash (first deploy on this chain, fresh genesis after a restart, or changed inputs).`);
|
|
98
|
+
else {
|
|
99
|
+
yield* Effect.logWarning(`artifact-publisher: re-deploying '${spec.namespace}' on chain ${spec.chain} — a cached artifact existed but failed on-chain verification (object missing, or RPC unavailable during restart); a new id will replace the prior deployment.`);
|
|
100
|
+
yield* setCurrentPluginPhase(`re-deploying ${spec.namespace} (cached artifact failed verification on restart)`);
|
|
101
|
+
}
|
|
102
|
+
const produced = yield* spec.produce;
|
|
103
|
+
yield* write({
|
|
104
|
+
namespace: spec.namespace,
|
|
105
|
+
chain: spec.chain,
|
|
106
|
+
contentHash: spec.contentHash
|
|
107
|
+
}, encodePayload(produced)).pipe(Effect.catch(() => Effect.void));
|
|
108
|
+
yield* spec.register(produced);
|
|
109
|
+
return produced;
|
|
110
|
+
});
|
|
82
111
|
return CacheService.of({
|
|
83
112
|
lookup,
|
|
84
113
|
write,
|
|
85
|
-
delete: remove
|
|
114
|
+
delete: remove,
|
|
115
|
+
publish
|
|
86
116
|
});
|
|
87
117
|
}));
|
|
88
118
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.mjs","names":[],"sources":["../../../../src/substrate/runtime/cache/service.ts"],"sourcesContent":["// Cache implementation.\n//\n// Universal artifact cache, content-addressed by\n// `(namespace, chainId, contentHash)`. Per-plugin namespace; the\n// substrate folds chainId in.\n//\n// Semantics:\n//\n// - `lookup` is the precise \"is the entry present?\" query. Hit\n// returns `CacheEntry`; miss returns `null`. Schema-decode\n// failure (corruption) is treated as a MISS, not an error —\n// re-producing the artifact is always safe; surfacing decode\n// failures would force every caller to handle a recovery path\n// they don't need. The corruption is logged via the span.\n// - `write` is best-effort. The architecture is explicit: an\n// ArtifactPublisher commits the on-chain effect, THEN\n// writes the cache; a cache-write IO failure must NOT roll back\n// the on-chain effect. We surface write failures via the\n// `CacheError` channel so callers can opt into reporting them,\n// but the typical caller pipes `Effect.ignore` over the write.\n// - `delete` is a best-effort sweep.\n//\n// Tombstones: the cache does NOT use tombstones. Missing-vs-deleted\n// is meaningless for a content-addressed best-effort store —\n// re-producing the artifact has the same effect either way.\n\nimport { Context, Effect, FileSystem, Layer } from 'effect';\n\nimport type { Cache, CacheEntry, CacheKey } from '../../../primitives/cache.ts';\nimport { atomicWriteJson } from '../atomic-write.ts';\nimport { CacheError } from '../errors.ts';\nimport { SpanAttr } from '../observability/spans.ts';\nimport { StackPathsService } from '../paths.ts';\nimport { decodeJsonText } from '../runtime-decode.ts';\nimport { CacheEntryDoc } from './schema.ts';\n\nconst base64Encode = (bytes: Uint8Array): string => Buffer.from(bytes).toString('base64');\nconst base64Decode = (s: string): Uint8Array => new Uint8Array(Buffer.from(s, 'base64'));\n\nexport class CacheService extends Context.Service<CacheService, Cache>()(\n\t'@devstack/substrate/Cache',\n) {}\n\n/**\n * Cache Layer. Stateless beyond the path resolver and the platform\n * FileSystem — every read goes to disk (no in-memory hot cache; the\n * OS page cache is enough). If a future perf pass shows the\n * substrate is hot-path-reading the same entries repeatedly, fold\n * an LRU in here; today's call-sites read once at acquire and never\n * again.\n */\nexport const layerCache: Layer.Layer<\n\tCacheService,\n\tnever,\n\tFileSystem.FileSystem | StackPathsService\n> = Layer.effect(\n\tCacheService,\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst paths = yield* StackPathsService;\n\n\t\tconst lookup = (key: CacheKey): Effect.Effect<CacheEntry | null, CacheError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst { file } = paths.cacheEntry(key.namespace, key.chain, key.contentHash);\n\t\t\t\tconst exists = yield* fs.exists(file).pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\tnew CacheError({\n\t\t\t\t\t\t\t\treason: 'io-failed',\n\t\t\t\t\t\t\t\tdetail: `failed to stat cache entry: ${file}`,\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tif (!exists) return null;\n\t\t\t\tconst text = yield* fs.readFileString(file).pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\tnew CacheError({\n\t\t\t\t\t\t\t\treason: 'io-failed',\n\t\t\t\t\t\t\t\tdetail: `failed to read cache entry: ${file}`,\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\t// Corruption = miss. The cache contract is\n\t\t\t\t// best-effort; surfacing decode failures would force\n\t\t\t\t// every caller to handle a recovery path that's\n\t\t\t\t// already implicit (re-produce). We annotate the span\n\t\t\t\t// AND emit a `logDebug` so the rare corruption is\n\t\t\t\t// visible in the log stream, not only via span backend.\n\t\t\t\tconst doc = yield* decodeJsonText(CacheEntryDoc, text, {\n\t\t\t\t\tsource: file,\n\t\t\t\t\tmkError: (issue) => issue,\n\t\t\t\t}).pipe(\n\t\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\t\tEffect.logDebug('cache entry decode failed; treating as miss', {\n\t\t\t\t\t\t\tfile,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.catch(() =>\n\t\t\t\t\t\tEffect.annotateCurrentSpan({ [SpanAttr.cacheCorruption]: true }).pipe(\n\t\t\t\t\t\t\tEffect.as(null as CacheEntryDoc | null),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tif (doc === null) return null;\n\t\t\t\treturn {\n\t\t\t\t\tbytes: base64Decode(doc.bytes),\n\t\t\t\t\twrittenAt: doc.writtenAt,\n\t\t\t\t};\n\t\t\t}).pipe(\n\t\t\t\tEffect.withSpan('substrate.cache.lookup', {\n\t\t\t\t\tattributes: {\n\t\t\t\t\t\tnamespace: key.namespace,\n\t\t\t\t\t\tchain: key.chain,\n\t\t\t\t\t\tcontentHash: key.contentHash,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\n\t\tconst write = (key: CacheKey, bytes: Uint8Array): Effect.Effect<void, CacheError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst { file } = paths.cacheEntry(key.namespace, key.chain, key.contentHash);\n\t\t\t\tconst doc: CacheEntryDoc = {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tbytes: base64Encode(bytes),\n\t\t\t\t\twrittenAt: Date.now(),\n\t\t\t\t};\n\t\t\t\tyield* atomicWriteJson(file, CacheEntryDoc, doc, {\n\t\t\t\t\tmode: 0o644,\n\t\t\t\t\tparentMode: 0o755,\n\t\t\t\t}).pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\tnew CacheError({\n\t\t\t\t\t\t\t\treason: 'io-failed',\n\t\t\t\t\t\t\t\tdetail: `cache write failed: ${file}`,\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.provideService(FileSystem.FileSystem, fs),\n\t\t\t\t);\n\t\t\t}).pipe(\n\t\t\t\tEffect.withSpan('substrate.cache.write', {\n\t\t\t\t\tattributes: {\n\t\t\t\t\t\tnamespace: key.namespace,\n\t\t\t\t\t\tchain: key.chain,\n\t\t\t\t\t\tcontentHash: key.contentHash,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\n\t\tconst remove = (key: CacheKey): Effect.Effect<void, CacheError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst { file } = paths.cacheEntry(key.namespace, key.chain, key.contentHash);\n\t\t\t\tyield* fs.remove(file, { force: true }).pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\tnew CacheError({\n\t\t\t\t\t\t\t\treason: 'io-failed',\n\t\t\t\t\t\t\t\tdetail: `cache delete failed: ${file}`,\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}).pipe(\n\t\t\t\tEffect.withSpan('substrate.cache.delete', {\n\t\t\t\t\tattributes: {\n\t\t\t\t\t\tnamespace: key.namespace,\n\t\t\t\t\t\tchain: key.chain,\n\t\t\t\t\t\tcontentHash: key.contentHash,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\n\t\treturn CacheService.of({\n\t\t\tlookup,\n\t\t\twrite,\n\t\t\tdelete: remove,\n\t\t});\n\t}),\n);\n"],"mappings":";;;;;;;;AAoCA,MAAM,gBAAgB,UAA8B,OAAO,KAAK,MAAM,CAAC,SAAS,SAAS;AACzF,MAAM,gBAAgB,MAA0B,IAAI,WAAW,OAAO,KAAK,GAAG,SAAS,CAAC;AAExF,IAAa,eAAb,cAAkC,QAAQ,SAA8B,CACvE,4BACA,CAAC;;;;;;;;;AAUF,MAAa,aAIT,MAAM,OACT,cACA,OAAO,IAAI,aAAa;CACvB,MAAM,KAAK,OAAO,WAAW;CAC7B,MAAM,QAAQ,OAAO;CAErB,MAAM,UAAU,QACf,OAAO,IAAI,aAAa;EACvB,MAAM,EAAE,SAAS,MAAM,WAAW,IAAI,WAAW,IAAI,OAAO,IAAI,YAAY;AAY5E,MAAI,EAAC,OAXiB,GAAG,OAAO,KAAK,CAAC,KACrC,OAAO,OAAO,UACb,OAAO,KACN,IAAI,WAAW;GACd,QAAQ;GACR,QAAQ,+BAA+B;GACvC;GACA,CAAC,CACF,CACD,CACD,EACY,QAAO;EAkBpB,MAAM,MAAM,OAAO,eAAe,eAAe,OAjB7B,GAAG,eAAe,KAAK,CAAC,KAC3C,OAAO,OAAO,UACb,OAAO,KACN,IAAI,WAAW;GACd,QAAQ;GACR,QAAQ,+BAA+B;GACvC;GACA,CAAC,CACF,CACD,CACD,EAOsD;GACtD,QAAQ;GACR,UAAU,UAAU;GACpB,CAAC,CAAC,KACF,OAAO,UAAU,UAChB,OAAO,SAAS,+CAA+C;GAC9D;GACA;GACA,CAAC,CACF,EACD,OAAO,YACN,OAAO,oBAAoB,GAAG,SAAS,kBAAkB,MAAM,CAAC,CAAC,KAChE,OAAO,GAAG,KAA6B,CACvC,CACD,CACD;AACD,MAAI,QAAQ,KAAM,QAAO;AACzB,SAAO;GACN,OAAO,aAAa,IAAI,MAAM;GAC9B,WAAW,IAAI;GACf;GACA,CAAC,KACF,OAAO,SAAS,0BAA0B,EACzC,YAAY;EACX,WAAW,IAAI;EACf,OAAO,IAAI;EACX,aAAa,IAAI;EACjB,EACD,CAAC,CACF;CAEF,MAAM,SAAS,KAAe,UAC7B,OAAO,IAAI,aAAa;EACvB,MAAM,EAAE,SAAS,MAAM,WAAW,IAAI,WAAW,IAAI,OAAO,IAAI,YAAY;AAM5E,SAAO,gBAAgB,MAAM,eAAe;GAJ3C,SAAS;GACT,OAAO,aAAa,MAAM;GAC1B,WAAW,KAAK,KAAK;GAEyB,EAAE;GAChD,MAAM;GACN,YAAY;GACZ,CAAC,CAAC,KACF,OAAO,OAAO,UACb,OAAO,KACN,IAAI,WAAW;GACd,QAAQ;GACR,QAAQ,uBAAuB;GAC/B;GACA,CAAC,CACF,CACD,EACD,OAAO,eAAe,WAAW,YAAY,GAAG,CAChD;GACA,CAAC,KACF,OAAO,SAAS,yBAAyB,EACxC,YAAY;EACX,WAAW,IAAI;EACf,OAAO,IAAI;EACX,aAAa,IAAI;EACjB,EACD,CAAC,CACF;CAEF,MAAM,UAAU,QACf,OAAO,IAAI,aAAa;EACvB,MAAM,EAAE,SAAS,MAAM,WAAW,IAAI,WAAW,IAAI,OAAO,IAAI,YAAY;AAC5E,SAAO,GAAG,OAAO,MAAM,EAAE,OAAO,MAAM,CAAC,CAAC,KACvC,OAAO,OAAO,UACb,OAAO,KACN,IAAI,WAAW;GACd,QAAQ;GACR,QAAQ,wBAAwB;GAChC;GACA,CAAC,CACF,CACD,CACD;GACA,CAAC,KACF,OAAO,SAAS,0BAA0B,EACzC,YAAY;EACX,WAAW,IAAI;EACf,OAAO,IAAI;EACX,aAAa,IAAI;EACjB,EACD,CAAC,CACF;AAEF,QAAO,aAAa,GAAG;EACtB;EACA;EACA,QAAQ;EACR,CAAC;EACD,CACF"}
|
|
1
|
+
{"version":3,"file":"service.mjs","names":[],"sources":["../../../../src/substrate/runtime/cache/service.ts"],"sourcesContent":["// Cache implementation.\n//\n// Universal artifact cache, content-addressed by\n// `(namespace, chain, contentHash)`. Per-plugin namespace; `chain` is a\n// plain string key the substrate forwards verbatim (it does NOT fold its\n// own `identity.network` in — that keeps warm-restart ids stable).\n//\n// Semantics:\n//\n// - `lookup` is the precise \"is the entry present?\" query. Hit\n// returns `CacheEntry`; miss returns `null`. Schema-decode\n// failure (corruption) is treated as a MISS, not an error —\n// re-producing the artifact is always safe; surfacing decode\n// failures would force every caller to handle a recovery path\n// they don't need. The corruption is logged via the span.\n// - `write` is best-effort. The architecture is explicit: an\n// ArtifactPublisher commits the on-chain effect, THEN\n// writes the cache; a cache-write IO failure must NOT roll back\n// the on-chain effect. We surface write failures via the\n// `CacheError` channel so callers can opt into reporting them,\n// but the typical caller pipes `Effect.ignore` over the write.\n// - `delete` is a best-effort sweep.\n//\n// Tombstones: the cache does NOT use tombstones. Missing-vs-deleted\n// is meaningless for a content-addressed best-effort store —\n// re-producing the artifact has the same effect either way.\n\nimport { Context, Effect, FileSystem, Layer, type Scope } from 'effect';\n\nimport type { ArtifactPublishError, ArtifactSpec } from '../../../primitives/artifact-publisher.ts';\nimport type { Cache, CacheEntry, CacheKey } from '../../../primitives/cache.ts';\nimport { atomicWriteJson } from '../atomic-write.ts';\nimport { setCurrentPluginPhase } from '../current-plugin.ts';\nimport { CacheError } from '../errors.ts';\nimport { StackPathsService } from '../paths.ts';\nimport { decodeJsonText, parseJsonTextSync } from '../runtime-decode.ts';\nimport { CacheEntryDoc } from './schema.ts';\n\nconst base64Encode = (bytes: Uint8Array): string => Buffer.from(bytes).toString('base64');\nconst base64Decode = (s: string): Uint8Array => new Uint8Array(Buffer.from(s, 'base64'));\n\n// Encode / decode the cached `publish` payload. We serialize `Produced`\n// as JSON. The artifact spec doesn't bake a codec in (`Produced` is\n// generic); JSON round-trip matches existing plugin call sites\n// (`CachedPackageEntry`, `CachedMint`, etc. are JSON-shaped).\nconst encodePayload = (value: unknown): Uint8Array =>\n\tnew TextEncoder().encode(JSON.stringify(value));\n\nconst decodePayload = <Produced>(bytes: Uint8Array): Produced | null => {\n\ttry {\n\t\treturn parseJsonTextSync(new TextDecoder().decode(bytes), {\n\t\t\tsource: 'artifact-publisher cache payload',\n\t\t\tmkError: (issue) => issue,\n\t\t}) as Produced;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nexport class CacheService extends Context.Service<CacheService, Cache>()(\n\t'@devstack/substrate/Cache',\n) {}\n\n/**\n * Cache Layer. Stateless beyond the path resolver and the platform\n * FileSystem — every read goes to disk (no in-memory hot cache; the\n * OS page cache is enough). If a future perf pass shows the\n * substrate is hot-path-reading the same entries repeatedly, fold\n * an LRU in here; today's call-sites read once at acquire and never\n * again.\n */\nexport const layerCache: Layer.Layer<\n\tCacheService,\n\tnever,\n\tFileSystem.FileSystem | StackPathsService\n> = Layer.effect(\n\tCacheService,\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst paths = yield* StackPathsService;\n\n\t\tconst lookup = (key: CacheKey): Effect.Effect<CacheEntry | null, CacheError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst { file } = paths.cacheEntry(key.namespace, key.chain, key.contentHash);\n\t\t\t\tconst exists = yield* fs.exists(file).pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\tnew CacheError({\n\t\t\t\t\t\t\t\treason: 'io-failed',\n\t\t\t\t\t\t\t\tdetail: `failed to stat cache entry: ${file}`,\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tif (!exists) return null;\n\t\t\t\tconst text = yield* fs.readFileString(file).pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\tnew CacheError({\n\t\t\t\t\t\t\t\treason: 'io-failed',\n\t\t\t\t\t\t\t\tdetail: `failed to read cache entry: ${file}`,\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\t// Corruption = miss. The cache contract is\n\t\t\t\t// best-effort; surfacing decode failures would force\n\t\t\t\t// every caller to handle a recovery path that's\n\t\t\t\t// already implicit (re-produce). We annotate the span\n\t\t\t\t// AND emit a `logDebug` so the rare corruption is\n\t\t\t\t// visible in the log stream, not only via span backend.\n\t\t\t\tconst doc = yield* decodeJsonText(CacheEntryDoc, text, {\n\t\t\t\t\tsource: file,\n\t\t\t\t\tmkError: (issue) => issue,\n\t\t\t\t}).pipe(\n\t\t\t\t\tEffect.tapCause((cause) =>\n\t\t\t\t\t\tEffect.logDebug('cache entry decode failed; treating as miss', {\n\t\t\t\t\t\t\tfile,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.catch(() => Effect.succeed(null as CacheEntryDoc | null)),\n\t\t\t\t);\n\t\t\t\tif (doc === null) return null;\n\t\t\t\treturn {\n\t\t\t\t\tbytes: base64Decode(doc.bytes),\n\t\t\t\t\twrittenAt: doc.writtenAt,\n\t\t\t\t};\n\t\t\t});\n\n\t\tconst write = (key: CacheKey, bytes: Uint8Array): Effect.Effect<void, CacheError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst { file } = paths.cacheEntry(key.namespace, key.chain, key.contentHash);\n\t\t\t\tconst doc: CacheEntryDoc = {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tbytes: base64Encode(bytes),\n\t\t\t\t\twrittenAt: Date.now(),\n\t\t\t\t};\n\t\t\t\tyield* atomicWriteJson(file, CacheEntryDoc, doc, {\n\t\t\t\t\tmode: 0o644,\n\t\t\t\t\tparentMode: 0o755,\n\t\t\t\t}).pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\tnew CacheError({\n\t\t\t\t\t\t\t\treason: 'io-failed',\n\t\t\t\t\t\t\t\tdetail: `cache write failed: ${file}`,\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.provideService(FileSystem.FileSystem, fs),\n\t\t\t\t);\n\t\t\t});\n\n\t\tconst remove = (key: CacheKey): Effect.Effect<void, CacheError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst { file } = paths.cacheEntry(key.namespace, key.chain, key.contentHash);\n\t\t\t\tyield* fs.remove(file, { force: true }).pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.fail(\n\t\t\t\t\t\t\tnew CacheError({\n\t\t\t\t\t\t\t\treason: 'io-failed',\n\t\t\t\t\t\t\t\tdetail: `cache delete failed: ${file}`,\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t});\n\n\t\t// publish: cache → verify → produce → register.\n\t\t//\n\t\t// Architecture §10: the substrate primitive that orchestrates the\n\t\t// cycle every on-chain produce/verify plugin (package, coin, walrus\n\t\t// deploy, seal deploy, deepbook deploy, etc.) flows through. Folds in\n\t\t// the former `ArtifactPublisher` facade, closing over the LOCAL\n\t\t// `lookup`/`write` above. The (namespace, chain, contentHash) triple\n\t\t// is the sole cache key; `spec.chain` (the plugin-supplied HEX\n\t\t// on-chain id) is forwarded VERBATIM — the substrate does NOT fold\n\t\t// `identity.network` in, which is what keeps warm-restart ids stable.\n\t\tconst publish = <Produced, Verified>(\n\t\t\tspec: ArtifactSpec<Produced, Verified>,\n\t\t): Effect.Effect<Produced, ArtifactPublishError, Scope.Scope> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// 1. Cache lookup. Best-effort: a CacheError on lookup\n\t\t\t\t// surfaces as `cache-corrupt`; the substrate's contract\n\t\t\t\t// is \"re-produce is always safe\", so we coerce read\n\t\t\t\t// failures to a miss rather than abort the cycle.\n\t\t\t\tconst hit = yield* lookup({\n\t\t\t\t\tnamespace: spec.namespace,\n\t\t\t\t\tchain: spec.chain,\n\t\t\t\t\tcontentHash: spec.contentHash,\n\t\t\t\t}).pipe(Effect.catch(() => Effect.succeed(null)));\n\n\t\t\t\tif (hit !== null) {\n\t\t\t\t\t// Decode the cached `Produced` BEFORE verify so the\n\t\t\t\t\t// plugin's verify Effect can key its on-chain probe\n\t\t\t\t\t// off a field of the cached payload (e.g. action's\n\t\t\t\t\t// `digest`, package's `packageId`). A decode failure\n\t\t\t\t\t// here is \"cache corruption\" — drop through to\n\t\t\t\t\t// re-produce so the cycle is still safe.\n\t\t\t\t\tconst cached = decodePayload<Produced>(hit.bytes);\n\t\t\t\t\tif (cached !== null) {\n\t\t\t\t\t\t// 2. Verify (lenient — returns null on transient\n\t\t\t\t\t\t// failure or not-found). Architecture §10:\n\t\t\t\t\t\t// verify-on-hit covers chain-state drift.\n\t\t\t\t\t\tconst verified = yield* spec.verify(cached);\n\t\t\t\t\t\tif (verified !== null) {\n\t\t\t\t\t\t\t// The substrate always returns the decoded\n\t\t\t\t\t\t\t// `Produced` payload — the `Verified` shape is a\n\t\t\t\t\t\t\t// probe-only signal that never escapes. Callers\n\t\t\t\t\t\t\t// therefore type-narrow trivially against `Produced`.\n\t\t\t\t\t\t\tyield* spec.register(cached);\n\t\t\t\t\t\t\t// Reuse is the quiet, expected warm-restart path — debug\n\t\t\t\t\t\t\t// only. The interesting (and noisy) case is a *re-run*,\n\t\t\t\t\t\t\t// logged below.\n\t\t\t\t\t\t\tyield* Effect.logDebug(\n\t\t\t\t\t\t\t\t`artifact-publisher: reusing cached '${spec.namespace}' on chain ${spec.chain} ` +\n\t\t\t\t\t\t\t\t\t`(cache hit; no re-deploy).`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn cached;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// 3. Miss or verify-failed → produce. State WHEN and WHY loudly:\n\t\t\t\t// on a restart this is the line that explains why an on-chain\n\t\t\t\t// id is about to change. A fresh chain genesis misses every\n\t\t\t\t// cache key (keyed by chain id), so packages / key servers /\n\t\t\t\t// walrus all get brand-new ids, and content bound to the old\n\t\t\t\t// ids (e.g. Seal-encrypted blobs) can no longer be resolved.\n\t\t\t\tif (hit === null) {\n\t\t\t\t\tyield* Effect.logInfo(\n\t\t\t\t\t\t`artifact-publisher: producing '${spec.namespace}' on chain ${spec.chain} — ` +\n\t\t\t\t\t\t\t`no cached artifact for this chain + content hash ` +\n\t\t\t\t\t\t\t`(first deploy on this chain, fresh genesis after a restart, or changed inputs).`,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tyield* Effect.logWarning(\n\t\t\t\t\t\t`artifact-publisher: re-deploying '${spec.namespace}' on chain ${spec.chain} — ` +\n\t\t\t\t\t\t\t`a cached artifact existed but failed on-chain verification ` +\n\t\t\t\t\t\t\t`(object missing, or RPC unavailable during restart); a new id will replace ` +\n\t\t\t\t\t\t\t`the prior deployment.`,\n\t\t\t\t\t);\n\t\t\t\t\t// Effect logs are dropped under the `up` TUI (Logger.layer([])).\n\t\t\t\t\t// The verify-failed re-deploy is the anomalous restart case — a\n\t\t\t\t\t// cached id existed but is no longer resolvable — so narrate it\n\t\t\t\t\t// on the supervised plugin's row, the channel the TUI renders.\n\t\t\t\t\t// (A plain cache miss stays quiet here: on a cold boot every\n\t\t\t\t\t// artifact misses, and the plugin narrates its own publish.)\n\t\t\t\t\tyield* setCurrentPluginPhase(\n\t\t\t\t\t\t`re-deploying ${spec.namespace} (cached artifact failed verification on restart)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst produced = yield* spec.produce;\n\n\t\t\t\t// 4. Cache write — best-effort. Architecture: a write\n\t\t\t\t// failure must NOT roll back the on-chain effect.\n\t\t\t\tyield* write(\n\t\t\t\t\t{\n\t\t\t\t\t\tnamespace: spec.namespace,\n\t\t\t\t\t\tchain: spec.chain,\n\t\t\t\t\t\tcontentHash: spec.contentHash,\n\t\t\t\t\t},\n\t\t\t\t\tencodePayload(produced),\n\t\t\t\t).pipe(Effect.catch(() => Effect.void));\n\n\t\t\t\t// 5. Register — fires on EVERY cycle (architecture §10).\n\t\t\t\tyield* spec.register(produced);\n\t\t\t\treturn produced;\n\t\t\t});\n\n\t\treturn CacheService.of({\n\t\t\tlookup,\n\t\t\twrite,\n\t\t\tdelete: remove,\n\t\t\tpublish,\n\t\t});\n\t}),\n);\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-validation.mjs","names":[],"sources":["../../../src/substrate/runtime/config-validation.ts"],"sourcesContent":["// Plain shape-check helpers — sync-throw discipline.\n//\n// Reach for these when you want to assert a single field's shape\n// (\"port must be a positive integer\", \"name must be a non-empty\n// string\") and throw a plugin-tagged `ConfigIssue` if it isn't.\n// Each helper takes one value + a `{ field, mkError }` and either\n// returns the value or throws. Authors compose them at factory /\n// boundary sites.\n//\n// For compound Schema decode (multi-field shapes, refinements,\n// transforms), reach for `decodeUnknownSync(schema, value,\n// { source, mkError })` from `runtime-decode.ts` instead.\n\nexport interface ConfigIssue {\n\treadonly field: string;\n\treadonly message: string;\n\treadonly hint?: string;\n\treadonly cause?: unknown;\n}\n\nexport type ConfigErrorFactory<E> = (issue: ConfigIssue) => E;\n\nexport const defineConfigError =\n\t<const Tag extends string>(tag: Tag) =>\n\t(issue: ConfigIssue): ConfigIssue & { readonly _tag: Tag } => ({\n\t\t_tag: tag,\n\t\t...issue,\n\t});\n\ninterface ValidatorOptions<E> {\n\treadonly field: string;\n\treadonly mkError: ConfigErrorFactory<E>;\n\treadonly message?: string;\n\treadonly hint?: string;\n}\n\nconst fail = <E>(mkError: ConfigErrorFactory<E>, issue: ConfigIssue): never => {\n\tthrow mkError(issue);\n};\n\nexport const expectNonEmptyString = <E>(value: unknown, options: ValidatorOptions<E>): string => {\n\tif (typeof value === 'string' && value.length > 0) return value;\n\treturn fail(options.mkError, {\n\t\tfield: options.field,\n\t\tmessage: options.message ?? 'must be a non-empty string',\n\t\t...(options.hint === undefined ? {} : { hint: options.hint }),\n\t});\n};\n\nexport const expectPositiveInteger = <E>(value: unknown, options: ValidatorOptions<E>): number => {\n\tif (typeof value === 'number' && Number.isInteger(value) && value > 0) return value;\n\treturn fail(options.mkError, {\n\t\tfield: options.field,\n\t\tmessage: options.message ?? 'must be a positive integer',\n\t\t...(options.hint === undefined ? {} : { hint: options.hint }),\n\t});\n};\n\nexport const expectOptionalPositiveInteger = <E>(\n\tvalue: unknown,\n\toptions: ValidatorOptions<E>,\n): number | undefined => {\n\tif (value === undefined) return undefined;\n\treturn expectPositiveInteger(value, options);\n};\n\nexport const expectPort = <E>(value: unknown, options: ValidatorOptions<E>): number => {\n\tif (typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65_535) {\n\t\treturn value;\n\t}\n\treturn fail(options.mkError, {\n\t\tfield: options.field,\n\t\tmessage: options.message ?? 'must be an integer between 1 and 65535',\n\t\t...(options.hint === undefined ? {} : { hint: options.hint }),\n\t});\n};\n"],"mappings":";;;;;;;;;AAsBA,MAAa,qBACe,SAC1B,WAA8D;CAC9D,MAAM;CACN,GAAG;
|
|
1
|
+
{"version":3,"file":"config-validation.mjs","names":[],"sources":["../../../src/substrate/runtime/config-validation.ts"],"sourcesContent":["// Plain shape-check helpers — sync-throw discipline.\n//\n// Reach for these when you want to assert a single field's shape\n// (\"port must be a positive integer\", \"name must be a non-empty\n// string\") and throw a plugin-tagged `ConfigIssue` if it isn't.\n// Each helper takes one value + a `{ field, mkError }` and either\n// returns the value or throws. Authors compose them at factory /\n// boundary sites.\n//\n// For compound Schema decode (multi-field shapes, refinements,\n// transforms), reach for `decodeUnknownSync(schema, value,\n// { source, mkError })` from `runtime-decode.ts` instead.\n\nexport interface ConfigIssue {\n\treadonly field: string;\n\treadonly message: string;\n\treadonly hint?: string;\n\treadonly cause?: unknown;\n}\n\nexport type ConfigErrorFactory<E> = (issue: ConfigIssue) => E;\n\nexport const defineConfigError =\n\t<const Tag extends string>(tag: Tag) =>\n\t(issue: ConfigIssue): ConfigIssue & { readonly _tag: Tag } => ({\n\t\t_tag: tag,\n\t\t...issue,\n\t});\n\ninterface ValidatorOptions<E> {\n\treadonly field: string;\n\treadonly mkError: ConfigErrorFactory<E>;\n\treadonly message?: string;\n\treadonly hint?: string;\n}\n\nconst fail = <E>(mkError: ConfigErrorFactory<E>, issue: ConfigIssue): never => {\n\tthrow mkError(issue);\n};\n\nexport const expectNonEmptyString = <E>(value: unknown, options: ValidatorOptions<E>): string => {\n\tif (typeof value === 'string' && value.length > 0) return value;\n\treturn fail(options.mkError, {\n\t\tfield: options.field,\n\t\tmessage: options.message ?? 'must be a non-empty string',\n\t\t...(options.hint === undefined ? {} : { hint: options.hint }),\n\t});\n};\n\nexport const expectPositiveInteger = <E>(value: unknown, options: ValidatorOptions<E>): number => {\n\tif (typeof value === 'number' && Number.isInteger(value) && value > 0) return value;\n\treturn fail(options.mkError, {\n\t\tfield: options.field,\n\t\tmessage: options.message ?? 'must be a positive integer',\n\t\t...(options.hint === undefined ? {} : { hint: options.hint }),\n\t});\n};\n\nexport const expectOptionalPositiveInteger = <E>(\n\tvalue: unknown,\n\toptions: ValidatorOptions<E>,\n): number | undefined => {\n\tif (value === undefined) return undefined;\n\treturn expectPositiveInteger(value, options);\n};\n\nexport const expectPort = <E>(value: unknown, options: ValidatorOptions<E>): number => {\n\tif (typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65_535) {\n\t\treturn value;\n\t}\n\treturn fail(options.mkError, {\n\t\tfield: options.field,\n\t\tmessage: options.message ?? 'must be an integer between 1 and 65535',\n\t\t...(options.hint === undefined ? {} : { hint: options.hint }),\n\t});\n};\n"],"mappings":";;;;;;;;;AAsBA,MAAa,qBACe,SAC1B,WAA8D;CAC9D,MAAM;CACN,GAAG;AACJ;AASD,MAAM,QAAW,SAAgC,UAA8B;CAC9E,MAAM,QAAQ,KAAK;AACpB;AAEA,MAAa,wBAA2B,OAAgB,YAAyC;CAChG,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG,OAAO;CAC1D,OAAO,KAAK,QAAQ,SAAS;EAC5B,OAAO,QAAQ;EACf,SAAS,QAAQ,WAAW;EAC5B,GAAI,QAAQ,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,QAAQ,KAAK;CAC5D,CAAC;AACF;AAEA,MAAa,yBAA4B,OAAgB,YAAyC;CACjG,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,QAAQ,GAAG,OAAO;CAC9E,OAAO,KAAK,QAAQ,SAAS;EAC5B,OAAO,QAAQ;EACf,SAAS,QAAQ,WAAW;EAC5B,GAAI,QAAQ,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,QAAQ,KAAK;CAC5D,CAAC;AACF;AAEA,MAAa,iCACZ,OACA,YACwB;CACxB,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAChC,OAAO,sBAAsB,OAAO,OAAO;AAC5C;AAEA,MAAa,cAAiB,OAAgB,YAAyC;CACtF,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,SAAS,OACjF,OAAO;CAER,OAAO,KAAK,QAAQ,SAAS;EAC5B,OAAO,QAAQ;EACf,SAAS,QAAQ,WAAW;EAC5B,GAAI,QAAQ,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,QAAQ,KAAK;CAC5D,CAAC;AACF"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { readResolvedSync } from "../lifecycle/plugin-registry.mjs";
|
|
2
|
+
import { computeSnapshotGraphInputFromGraph } from "../../../orchestrators/snapshot/descriptor.mjs";
|
|
3
|
+
import { graphInputMismatchDetail } from "../../../orchestrators/snapshot/restore.mjs";
|
|
2
4
|
import { SnapshotOrchestratorService } from "../../../orchestrators/snapshot/service.mjs";
|
|
5
|
+
import "../../../orchestrators/snapshot/index.mjs";
|
|
3
6
|
import { Context, Effect, FileSystem } from "effect";
|
|
4
7
|
//#region src/substrate/runtime/control-plane/domain.ts
|
|
5
8
|
/** Enumerate every resolved plugin value, in graph order, as
|
|
@@ -19,8 +22,12 @@ const enumerateResolvedValues = (graph, registry) => {
|
|
|
19
22
|
}
|
|
20
23
|
return out;
|
|
21
24
|
};
|
|
22
|
-
const snapshotEntryFrom = (entry) => {
|
|
25
|
+
const snapshotEntryFrom = (entry, currentGraphInput) => {
|
|
23
26
|
const m = entry.metadata;
|
|
27
|
+
const snapshotGraphInputId = m?.graphInput?.graphInputId ?? null;
|
|
28
|
+
const currentGraphInputId = currentGraphInput?.graphInputId ?? null;
|
|
29
|
+
const graphInputStatus = snapshotGraphInputId === null || currentGraphInputId === null ? "unknown" : snapshotGraphInputId === currentGraphInputId ? "matching" : "stale";
|
|
30
|
+
const graphInputWarning = snapshotGraphInputId === null || currentGraphInputId === null || graphInputStatus !== "stale" ? null : graphInputMismatchDetail(snapshotGraphInputId, currentGraphInputId);
|
|
24
31
|
return {
|
|
25
32
|
id: entry.id,
|
|
26
33
|
label: m?.label ?? null,
|
|
@@ -28,6 +35,10 @@ const snapshotEntryFrom = (entry) => {
|
|
|
28
35
|
app: m?.app ?? null,
|
|
29
36
|
stack: m?.stack ?? null,
|
|
30
37
|
network: m?.network ?? null,
|
|
38
|
+
snapshotGraphInputId,
|
|
39
|
+
currentGraphInputId,
|
|
40
|
+
graphInputStatus,
|
|
41
|
+
graphInputWarning,
|
|
31
42
|
participants: m?.participants ?? [],
|
|
32
43
|
containerCount: m?.containers?.length ?? 0,
|
|
33
44
|
subtreeCount: m?.subtrees?.length ?? 0,
|
|
@@ -35,13 +46,27 @@ const snapshotEntryFrom = (entry) => {
|
|
|
35
46
|
};
|
|
36
47
|
};
|
|
37
48
|
const buildControlPlaneDomain = (deps) => {
|
|
38
|
-
const { graph, registry, snapshotOrchestrator, fileSystem, logStore
|
|
49
|
+
const { graph, stackOptions, devstackVersion, registry, snapshotOrchestrator, fileSystem, logStore } = deps;
|
|
39
50
|
const provideFs = (eff) => fileSystem === null ? Effect.die("control-plane: FileSystem unavailable") : Effect.provideService(eff, FileSystem.FileSystem, fileSystem);
|
|
40
|
-
const
|
|
51
|
+
const currentGraphInput = devstackVersion === null ? Effect.succeed(null) : computeSnapshotGraphInputFromGraph({
|
|
52
|
+
graph,
|
|
53
|
+
options: stackOptions,
|
|
54
|
+
devstackVersion
|
|
55
|
+
}).pipe(Effect.catchCause(() => Effect.succeed(null)));
|
|
56
|
+
const snapshots = snapshotOrchestrator === null ? Effect.succeed([]) : provideFs(Effect.all({
|
|
57
|
+
entries: snapshotOrchestrator.list,
|
|
58
|
+
currentGraphInput
|
|
59
|
+
})).pipe(Effect.map(({ entries, currentGraphInput }) => entries.map((entry) => snapshotEntryFrom(entry, currentGraphInput))), Effect.catchCause(() => Effect.succeed([])));
|
|
41
60
|
const restoreSnapshot = (id) => snapshotOrchestrator === null ? Effect.succeed({
|
|
42
61
|
ok: false,
|
|
43
62
|
detail: "snapshot orchestrator unavailable"
|
|
44
|
-
}) : provideFs(
|
|
63
|
+
}) : provideFs(currentGraphInput.pipe(Effect.flatMap((current) => snapshotOrchestrator.restore({
|
|
64
|
+
id,
|
|
65
|
+
...current === null ? {} : {
|
|
66
|
+
currentGraphInput: current,
|
|
67
|
+
graphInputMismatchPolicy: "warn"
|
|
68
|
+
}
|
|
69
|
+
})))).pipe(Effect.map(() => ({
|
|
45
70
|
ok: true,
|
|
46
71
|
detail: null
|
|
47
72
|
})), Effect.catchCause((cause) => Effect.succeed({
|
|
@@ -60,20 +85,16 @@ const buildControlPlaneDomain = (deps) => {
|
|
|
60
85
|
})));
|
|
61
86
|
const resolvedValues = Effect.sync(() => enumerateResolvedValues(graph, registry));
|
|
62
87
|
const logs = (filter) => logStore === null ? Effect.succeed([]) : logStore.query(filter);
|
|
63
|
-
const logServices = logStore === null ? Effect.succeed([]) : logStore.services;
|
|
64
|
-
const spans = (filter) => spanStore === null ? Effect.succeed([]) : spanStore.query(filter);
|
|
65
88
|
return {
|
|
66
89
|
snapshots,
|
|
67
90
|
restoreSnapshot,
|
|
68
91
|
deleteSnapshot,
|
|
69
92
|
resolvedValues,
|
|
70
93
|
logs,
|
|
71
|
-
logServices
|
|
72
|
-
spans,
|
|
73
|
-
spanServices: spanStore === null ? Effect.succeed([]) : spanStore.services
|
|
94
|
+
logServices: logStore === null ? Effect.succeed([]) : logStore.services
|
|
74
95
|
};
|
|
75
96
|
};
|
|
76
|
-
Effect.succeed([]), Effect.succeed([]), Effect.succeed([])
|
|
97
|
+
Effect.succeed([]), Effect.succeed([]), Effect.succeed([]);
|
|
77
98
|
/** Read an optional service value out of a `Context.Context<never>`,
|
|
78
99
|
* returning `null` when absent. */
|
|
79
100
|
const readOptional = (ctx, tag) => {
|
|
@@ -89,11 +110,12 @@ const readOptional = (ctx, tag) => {
|
|
|
89
110
|
* accessors degrade to empty. */
|
|
90
111
|
const controlPlaneDomainFromContext = (args) => buildControlPlaneDomain({
|
|
91
112
|
graph: args.graph,
|
|
113
|
+
stackOptions: args.stackOptions,
|
|
114
|
+
devstackVersion: args.devstackVersion ?? null,
|
|
92
115
|
registry: args.registry,
|
|
93
116
|
snapshotOrchestrator: readOptional(args.pluginContext, SnapshotOrchestratorService),
|
|
94
117
|
fileSystem: readOptional(args.pluginContext, FileSystem.FileSystem),
|
|
95
|
-
logStore: args.logStore ?? null
|
|
96
|
-
spanStore: args.spanStore ?? null
|
|
118
|
+
logStore: args.logStore ?? null
|
|
97
119
|
});
|
|
98
120
|
//#endregion
|
|
99
121
|
export { controlPlaneDomainFromContext };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.mjs","names":[],"sources":["../../../../src/substrate/runtime/control-plane/domain.ts"],"sourcesContent":["// Control-plane domain builder.\n//\n// Assembles the generic, name-blind `ControlPlaneDomain` accessor surface\n// from the data the supervisor holds at wiring time: the resolved plugin\n// registry + graph, the (optional) snapshot orchestrator + filesystem (for\n// snapshot catalog reads), and the cross-service observability stores.\n//\n// Design discipline:\n// - The projection (`SubscribableState`) is CLOSED — none of this data\n// touches it. We read resolved plugin VALUES via `readResolvedSync`,\n// the same name-blind seam `runtime-composition.ts` uses for the\n// manifest-extras lookup, and hand them out UNINTERPRETED via\n// `resolvedValues`. The substrate never pattern-matches plugin\n// names — plugin-name-aware shaping lives in the dashboard plugin\n// (allowed to name plugins), one layer up.\n// - Every accessor degrades to empty/`null` rather than failing, so a\n// single missing/uninitialised plugin can't take down a dashboard\n// query (`E = never` on the public surface).\n\nimport { Context, Effect, FileSystem } from 'effect';\n\nimport type { ResolvedGraph } from '../lifecycle/index.ts';\nimport { readResolvedSync, type PluginRegistry } from '../lifecycle/plugin-registry.ts';\n// Cross-layer seam (intentional): the control-plane domain is the single\n// place the substrate reads the L3 snapshot orchestrator, so the\n// supervisor core stays free of that import. This is a runtime VALUE import\n// (the `Context.Key` tag) — confined here.\nimport {\n\tSnapshotOrchestratorService,\n\ttype SnapshotOrchestrator,\n} from '../../../orchestrators/snapshot/service.ts';\nimport type {\n\tControlPlaneDomain,\n\tControlPlaneResolvedValue,\n\tControlPlaneSnapshotEntry,\n} from './service.ts';\nimport type { LogStoreShape, SpanStoreShape } from '../observability/index.ts';\n\n// -----------------------------------------------------------------------------\n// Generic resolved-value enumeration\n//\n// We import NO plugin types — the plugins live above the substrate in the\n// layering. We hand out the opaque `unknown` resolved value alongside its\n// registry key + resource id; the consumer (the dashboard plugin) does any\n// narrowing/shaping by resource-id prefix itself.\n// -----------------------------------------------------------------------------\n\n/** Enumerate every resolved plugin value, in graph order, as\n * `{ pluginKey, id, value }` triples. Skips nodes with no resolved value.\n * The substrate never inspects `id` or `value` — they pass through\n * uninterpreted. */\nconst enumerateResolvedValues = (\n\tgraph: ResolvedGraph,\n\tregistry: PluginRegistry,\n): ReadonlyArray<ControlPlaneResolvedValue> => {\n\tconst out: Array<ControlPlaneResolvedValue> = [];\n\tfor (const [key, node] of graph.nodes) {\n\t\tconst value = readResolvedSync(registry, key);\n\t\tif (value === undefined || value === null) continue;\n\t\tout.push({ pluginKey: String(key), id: node.member.id, value });\n\t}\n\treturn out;\n};\n\n// -----------------------------------------------------------------------------\n// Snapshot catalog projection\n// -----------------------------------------------------------------------------\n\nconst snapshotEntryFrom = (entry: {\n\treadonly id: string;\n\treadonly metadata: {\n\t\treadonly label?: string | null;\n\t\treadonly createdAt?: number;\n\t\treadonly app?: string;\n\t\treadonly stack?: string;\n\t\treadonly network?: string;\n\t\treadonly participants?: ReadonlyArray<string>;\n\t\treadonly containers?: ReadonlyArray<unknown>;\n\t\treadonly subtrees?: ReadonlyArray<unknown>;\n\t} | null;\n}): ControlPlaneSnapshotEntry => {\n\tconst m = entry.metadata;\n\treturn {\n\t\tid: entry.id,\n\t\tlabel: m?.label ?? null,\n\t\tcreatedAt: m?.createdAt ?? null,\n\t\tapp: m?.app ?? null,\n\t\tstack: m?.stack ?? null,\n\t\tnetwork: m?.network ?? null,\n\t\tparticipants: m?.participants ?? [],\n\t\tcontainerCount: m?.containers?.length ?? 0,\n\t\tsubtreeCount: m?.subtrees?.length ?? 0,\n\t\tcorrupt: m === null,\n\t};\n};\n\n// -----------------------------------------------------------------------------\n// Builder\n// -----------------------------------------------------------------------------\n\nexport interface ControlPlaneDomainDeps {\n\treadonly graph: ResolvedGraph;\n\treadonly registry: PluginRegistry;\n\t/** Optional — present in production wiring (CLI / runStack), absent in\n\t * bare smoke tests. Snapshot accessors degrade to empty when missing. */\n\treadonly snapshotOrchestrator: SnapshotOrchestrator | null;\n\t/** Optional filesystem (snapshot orchestrator effects require it). */\n\treadonly fileSystem: FileSystem.FileSystem | null;\n\t/** Optional cross-service log store. Absent in bare smoke-test paths;\n\t * the `logs`/`logServices` accessors degrade to empty when null. */\n\treadonly logStore: LogStoreShape | null;\n\t/** Optional completed-span store. Absent in bare smoke-test paths; the\n\t * `spans`/`spanServices` accessors degrade to empty when null. */\n\treadonly spanStore: SpanStoreShape | null;\n}\n\nexport const buildControlPlaneDomain = (deps: ControlPlaneDomainDeps): ControlPlaneDomain => {\n\tconst { graph, registry, snapshotOrchestrator, fileSystem, logStore, spanStore } = deps;\n\n\tconst provideFs = <A, E>(eff: Effect.Effect<A, E, FileSystem.FileSystem>): Effect.Effect<A, E> =>\n\t\tfileSystem === null\n\t\t\t? (Effect.die('control-plane: FileSystem unavailable') as Effect.Effect<A, E>)\n\t\t\t: Effect.provideService(eff, FileSystem.FileSystem, fileSystem);\n\n\tconst snapshots: ControlPlaneDomain['snapshots'] =\n\t\tsnapshotOrchestrator === null\n\t\t\t? Effect.succeed([])\n\t\t\t: provideFs(snapshotOrchestrator.list).pipe(\n\t\t\t\t\tEffect.map((entries) => entries.map(snapshotEntryFrom)),\n\t\t\t\t\tEffect.catchCause(() => Effect.succeed([] as ReadonlyArray<ControlPlaneSnapshotEntry>)),\n\t\t\t\t);\n\n\tconst restoreSnapshot: ControlPlaneDomain['restoreSnapshot'] = (id) =>\n\t\tsnapshotOrchestrator === null\n\t\t\t? Effect.succeed({ ok: false, detail: 'snapshot orchestrator unavailable' as string | null })\n\t\t\t: provideFs(snapshotOrchestrator.restore({ id })).pipe(\n\t\t\t\t\tEffect.map(() => ({ ok: true, detail: null as string | null })),\n\t\t\t\t\tEffect.catchCause((cause) =>\n\t\t\t\t\t\tEffect.succeed({ ok: false, detail: String(cause) as string | null }),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\tconst deleteSnapshot: ControlPlaneDomain['deleteSnapshot'] = (id) =>\n\t\tsnapshotOrchestrator === null\n\t\t\t? Effect.succeed({ ok: false, detail: 'snapshot orchestrator unavailable' as string | null })\n\t\t\t: provideFs(snapshotOrchestrator.delete(id)).pipe(\n\t\t\t\t\tEffect.map(() => ({ ok: true, detail: null as string | null })),\n\t\t\t\t\tEffect.catchCause((cause) =>\n\t\t\t\t\t\tEffect.succeed({ ok: false, detail: String(cause) as string | null }),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t// Generic, name-blind resolved-value enumeration. The seam the\n\t// dashboard plugin uses to find + shape plugin-domain values itself.\n\tconst resolvedValues: ControlPlaneDomain['resolvedValues'] = Effect.sync(() =>\n\t\tenumerateResolvedValues(graph, registry),\n\t);\n\n\t// Observability accessors. These read the process-scoped stores the\n\t// supervisor created (fed off the same Logger path as the projection\n\t// tail / the recording Tracer). Filtering happens server-side in the\n\t// store; the dashboard never pulls the whole ring across the wire.\n\t// Each degrades to empty when the corresponding store is absent.\n\tconst logs: ControlPlaneDomain['logs'] = (filter) =>\n\t\tlogStore === null ? Effect.succeed([]) : logStore.query(filter);\n\n\tconst logServices: ControlPlaneDomain['logServices'] =\n\t\tlogStore === null ? Effect.succeed([]) : logStore.services;\n\n\tconst spans: ControlPlaneDomain['spans'] = (filter) =>\n\t\tspanStore === null ? Effect.succeed([]) : spanStore.query(filter);\n\n\tconst spanServices: ControlPlaneDomain['spanServices'] =\n\t\tspanStore === null ? Effect.succeed([]) : spanStore.services;\n\n\treturn {\n\t\tsnapshots,\n\t\trestoreSnapshot,\n\t\tdeleteSnapshot,\n\t\tresolvedValues,\n\t\tlogs,\n\t\tlogServices,\n\t\tspans,\n\t\tspanServices,\n\t};\n};\n\n/** An all-empty domain surface. Used by bare smoke-test paths and the\n * dashboard server tests that exercise the projection/command plane\n * without a live registry. Every accessor resolves to empty/`null`. */\nexport const emptyControlPlaneDomain: ControlPlaneDomain = {\n\tsnapshots: Effect.succeed([]),\n\trestoreSnapshot: () => Effect.succeed({ ok: false, detail: 'unavailable' }),\n\tdeleteSnapshot: () => Effect.succeed({ ok: false, detail: 'unavailable' }),\n\tresolvedValues: Effect.succeed([]),\n\tlogs: () => Effect.succeed([]),\n\tlogServices: Effect.succeed([]),\n\tspans: () => Effect.succeed([]),\n\tspanServices: Effect.succeed([]),\n};\n\n/** Read an optional service value out of a `Context.Context<never>`,\n * returning `null` when absent. */\nconst readOptional = <S, I>(ctx: Context.Context<never>, tag: Context.Key<I, S>): S | null => {\n\tconst opt = Context.getOption(ctx as Context.Context<I>, tag);\n\treturn opt._tag === 'Some' ? opt.value : null;\n};\n\n/** Build the control-plane domain by reading the optional snapshot\n * orchestrator / filesystem services out of the supervisor's\n * `pluginContext`. The supervisor calls THIS (not `buildControlPlaneDomain`\n * directly) so the L3 service tag stays imported only inside the\n * control-plane seam, never in the supervisor core. Each is optional: bare\n * smoke-test `supervise()` paths don't layer them, so the corresponding\n * accessors degrade to empty. */\nexport const controlPlaneDomainFromContext = (args: {\n\treadonly pluginContext: Context.Context<never>;\n\treadonly graph: ResolvedGraph;\n\treadonly registry: PluginRegistry;\n\t/** The supervisor's process-scoped log store, passed directly (it is\n\t * created in the supervisor closure, not layered into `pluginContext`).\n\t * `null` in bare smoke-test paths that don't build one. */\n\treadonly logStore?: LogStoreShape | null;\n\t/** The supervisor's process-scoped span store, passed directly. `null`\n\t * in bare smoke-test paths. */\n\treadonly spanStore?: SpanStoreShape | null;\n}): ControlPlaneDomain =>\n\tbuildControlPlaneDomain({\n\t\tgraph: args.graph,\n\t\tregistry: args.registry,\n\t\tsnapshotOrchestrator: readOptional(args.pluginContext, SnapshotOrchestratorService),\n\t\tfileSystem: readOptional(args.pluginContext, FileSystem.FileSystem),\n\t\tlogStore: args.logStore ?? null,\n\t\tspanStore: args.spanStore ?? null,\n\t});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"domain.mjs","names":[],"sources":["../../../../src/substrate/runtime/control-plane/domain.ts"],"sourcesContent":["// Control-plane domain builder.\n//\n// Assembles the generic, name-blind `ControlPlaneDomain` accessor surface\n// from the data the supervisor holds at wiring time: the resolved plugin\n// registry + graph, the (optional) snapshot orchestrator + filesystem (for\n// snapshot catalog reads), and the cross-service observability stores.\n//\n// Design discipline:\n// - The projection (`SubscribableState`) is CLOSED — none of this data\n// touches it. We read resolved plugin VALUES via `readResolvedSync`,\n// the same name-blind seam `orchestrators/boot.ts` uses for the\n// manifest-extras lookup, and hand them out UNINTERPRETED via\n// `resolvedValues`. The substrate never pattern-matches plugin\n// names — plugin-name-aware shaping lives in the dashboard plugin\n// (allowed to name plugins), one layer up.\n// - Every accessor degrades to empty/`null` rather than failing, so a\n// single missing/uninitialised plugin can't take down a dashboard\n// query (`E = never` on the public surface).\n\nimport { Context, Effect, FileSystem } from 'effect';\n\nimport type { ResolvedGraph } from '../lifecycle/index.ts';\nimport { readResolvedSync, type PluginRegistry } from '../lifecycle/plugin-registry.ts';\nimport type { DevstackOptions } from '../../options.ts';\n// Cross-layer seam (intentional): the control-plane domain is the single\n// place the substrate reads the L3 snapshot orchestrator, so the\n// supervisor core stays free of that import. This is a runtime VALUE import\n// (the `Context.Key` tag) — confined here.\nimport {\n\tSnapshotOrchestratorService,\n\ttype SnapshotOrchestrator,\n} from '../../../orchestrators/snapshot/service.ts';\nimport {\n\tcomputeSnapshotGraphInputFromGraph,\n\tgraphInputMismatchDetail,\n\ttype SnapshotGraphInputIdentity,\n} from '../../../orchestrators/snapshot/index.ts';\nimport type {\n\tControlPlaneDomain,\n\tControlPlaneResolvedValue,\n\tControlPlaneSnapshotEntry,\n} from './service.ts';\nimport type { LogStoreShape } from '../observability/index.ts';\n\n// -----------------------------------------------------------------------------\n// Generic resolved-value enumeration\n//\n// We import NO plugin types — the plugins live above the substrate in the\n// layering. We hand out the opaque `unknown` resolved value alongside its\n// registry key + resource id; the consumer (the dashboard plugin) does any\n// narrowing/shaping by resource-id prefix itself.\n// -----------------------------------------------------------------------------\n\n/** Enumerate every resolved plugin value, in graph order, as\n * `{ pluginKey, id, value }` triples. Skips nodes with no resolved value.\n * The substrate never inspects `id` or `value` — they pass through\n * uninterpreted. */\nconst enumerateResolvedValues = (\n\tgraph: ResolvedGraph,\n\tregistry: PluginRegistry,\n): ReadonlyArray<ControlPlaneResolvedValue> => {\n\tconst out: Array<ControlPlaneResolvedValue> = [];\n\tfor (const [key, node] of graph.nodes) {\n\t\tconst value = readResolvedSync(registry, key);\n\t\tif (value === undefined || value === null) continue;\n\t\tout.push({ pluginKey: String(key), id: node.member.id, value });\n\t}\n\treturn out;\n};\n\n// -----------------------------------------------------------------------------\n// Snapshot catalog projection\n// -----------------------------------------------------------------------------\n\nconst snapshotEntryFrom = (\n\tentry: {\n\t\treadonly id: string;\n\t\treadonly metadata: {\n\t\t\treadonly label?: string | null;\n\t\t\treadonly createdAt?: number;\n\t\t\treadonly app?: string;\n\t\t\treadonly stack?: string;\n\t\t\treadonly network?: string;\n\t\t\treadonly graphInput?: {\n\t\t\t\treadonly graphInputId?: string;\n\t\t\t};\n\t\t\treadonly participants?: ReadonlyArray<string>;\n\t\t\treadonly containers?: ReadonlyArray<unknown>;\n\t\t\treadonly subtrees?: ReadonlyArray<unknown>;\n\t\t} | null;\n\t},\n\tcurrentGraphInput: SnapshotGraphInputIdentity | null,\n): ControlPlaneSnapshotEntry => {\n\tconst m = entry.metadata;\n\tconst snapshotGraphInputId = m?.graphInput?.graphInputId ?? null;\n\tconst currentGraphInputId = currentGraphInput?.graphInputId ?? null;\n\tconst graphInputStatus =\n\t\tsnapshotGraphInputId === null || currentGraphInputId === null\n\t\t\t? 'unknown'\n\t\t\t: snapshotGraphInputId === currentGraphInputId\n\t\t\t\t? 'matching'\n\t\t\t\t: 'stale';\n\tconst graphInputWarning =\n\t\tsnapshotGraphInputId === null || currentGraphInputId === null || graphInputStatus !== 'stale'\n\t\t\t? null\n\t\t\t: graphInputMismatchDetail(snapshotGraphInputId, currentGraphInputId);\n\treturn {\n\t\tid: entry.id,\n\t\tlabel: m?.label ?? null,\n\t\tcreatedAt: m?.createdAt ?? null,\n\t\tapp: m?.app ?? null,\n\t\tstack: m?.stack ?? null,\n\t\tnetwork: m?.network ?? null,\n\t\tsnapshotGraphInputId,\n\t\tcurrentGraphInputId,\n\t\tgraphInputStatus,\n\t\tgraphInputWarning,\n\t\tparticipants: m?.participants ?? [],\n\t\tcontainerCount: m?.containers?.length ?? 0,\n\t\tsubtreeCount: m?.subtrees?.length ?? 0,\n\t\tcorrupt: m === null,\n\t};\n};\n\n// -----------------------------------------------------------------------------\n// Builder\n// -----------------------------------------------------------------------------\n\nexport interface ControlPlaneDomainDeps {\n\treadonly graph: ResolvedGraph;\n\treadonly stackOptions: DevstackOptions;\n\treadonly devstackVersion: string | null;\n\treadonly registry: PluginRegistry;\n\t/** Optional — present in production wiring (CLI / runStack), absent in\n\t * bare smoke tests. Snapshot accessors degrade to empty when missing. */\n\treadonly snapshotOrchestrator: SnapshotOrchestrator | null;\n\t/** Optional filesystem (snapshot orchestrator effects require it). */\n\treadonly fileSystem: FileSystem.FileSystem | null;\n\t/** Optional cross-service log store. Absent in bare smoke-test paths;\n\t * the `logs`/`logServices` accessors degrade to empty when null. */\n\treadonly logStore: LogStoreShape | null;\n}\n\nexport const buildControlPlaneDomain = (deps: ControlPlaneDomainDeps): ControlPlaneDomain => {\n\tconst {\n\t\tgraph,\n\t\tstackOptions,\n\t\tdevstackVersion,\n\t\tregistry,\n\t\tsnapshotOrchestrator,\n\t\tfileSystem,\n\t\tlogStore,\n\t} = deps;\n\n\tconst provideFs = <A, E>(eff: Effect.Effect<A, E, FileSystem.FileSystem>): Effect.Effect<A, E> =>\n\t\tfileSystem === null\n\t\t\t? (Effect.die('control-plane: FileSystem unavailable') as Effect.Effect<A, E>)\n\t\t\t: Effect.provideService(eff, FileSystem.FileSystem, fileSystem);\n\n\tconst currentGraphInput =\n\t\tdevstackVersion === null\n\t\t\t? Effect.succeed(null)\n\t\t\t: computeSnapshotGraphInputFromGraph({\n\t\t\t\t\tgraph,\n\t\t\t\t\toptions: stackOptions,\n\t\t\t\t\tdevstackVersion,\n\t\t\t\t}).pipe(Effect.catchCause(() => Effect.succeed(null)));\n\n\tconst snapshots: ControlPlaneDomain['snapshots'] =\n\t\tsnapshotOrchestrator === null\n\t\t\t? Effect.succeed([])\n\t\t\t: provideFs(\n\t\t\t\t\tEffect.all({\n\t\t\t\t\t\tentries: snapshotOrchestrator.list,\n\t\t\t\t\t\tcurrentGraphInput,\n\t\t\t\t\t}),\n\t\t\t\t).pipe(\n\t\t\t\t\tEffect.map(({ entries, currentGraphInput }) =>\n\t\t\t\t\t\tentries.map((entry) => snapshotEntryFrom(entry, currentGraphInput)),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.catchCause(() => Effect.succeed([] as ReadonlyArray<ControlPlaneSnapshotEntry>)),\n\t\t\t\t);\n\n\tconst restoreSnapshot: ControlPlaneDomain['restoreSnapshot'] = (id) =>\n\t\tsnapshotOrchestrator === null\n\t\t\t? Effect.succeed({ ok: false, detail: 'snapshot orchestrator unavailable' as string | null })\n\t\t\t: provideFs(\n\t\t\t\t\tcurrentGraphInput.pipe(\n\t\t\t\t\t\tEffect.flatMap((current) =>\n\t\t\t\t\t\t\tsnapshotOrchestrator.restore({\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t\t...(current === null\n\t\t\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\t\tcurrentGraphInput: current,\n\t\t\t\t\t\t\t\t\t\t\tgraphInputMismatchPolicy: 'warn' as const,\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t).pipe(\n\t\t\t\t\tEffect.map(() => ({ ok: true, detail: null as string | null })),\n\t\t\t\t\tEffect.catchCause((cause) =>\n\t\t\t\t\t\tEffect.succeed({ ok: false, detail: String(cause) as string | null }),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\tconst deleteSnapshot: ControlPlaneDomain['deleteSnapshot'] = (id) =>\n\t\tsnapshotOrchestrator === null\n\t\t\t? Effect.succeed({ ok: false, detail: 'snapshot orchestrator unavailable' as string | null })\n\t\t\t: provideFs(snapshotOrchestrator.delete(id)).pipe(\n\t\t\t\t\tEffect.map(() => ({ ok: true, detail: null as string | null })),\n\t\t\t\t\tEffect.catchCause((cause) =>\n\t\t\t\t\t\tEffect.succeed({ ok: false, detail: String(cause) as string | null }),\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t// Generic, name-blind resolved-value enumeration. The seam the\n\t// dashboard plugin uses to find + shape plugin-domain values itself.\n\tconst resolvedValues: ControlPlaneDomain['resolvedValues'] = Effect.sync(() =>\n\t\tenumerateResolvedValues(graph, registry),\n\t);\n\n\t// Observability accessors. These read the process-scoped log store the\n\t// supervisor created (fed off the same Logger path as the projection\n\t// tail). Filtering happens server-side in the store; the dashboard never\n\t// pulls the whole ring across the wire. Degrades to empty when the store\n\t// is absent.\n\tconst logs: ControlPlaneDomain['logs'] = (filter) =>\n\t\tlogStore === null ? Effect.succeed([]) : logStore.query(filter);\n\n\tconst logServices: ControlPlaneDomain['logServices'] =\n\t\tlogStore === null ? Effect.succeed([]) : logStore.services;\n\n\treturn {\n\t\tsnapshots,\n\t\trestoreSnapshot,\n\t\tdeleteSnapshot,\n\t\tresolvedValues,\n\t\tlogs,\n\t\tlogServices,\n\t};\n};\n\n/** An all-empty domain surface. Used by bare smoke-test paths and the\n * dashboard server tests that exercise the projection/command plane\n * without a live registry. Every accessor resolves to empty/`null`. */\nexport const emptyControlPlaneDomain: ControlPlaneDomain = {\n\tsnapshots: Effect.succeed([]),\n\trestoreSnapshot: () => Effect.succeed({ ok: false, detail: 'unavailable' }),\n\tdeleteSnapshot: () => Effect.succeed({ ok: false, detail: 'unavailable' }),\n\tresolvedValues: Effect.succeed([]),\n\tlogs: () => Effect.succeed([]),\n\tlogServices: Effect.succeed([]),\n};\n\n/** Read an optional service value out of a `Context.Context<never>`,\n * returning `null` when absent. */\nconst readOptional = <S, I>(ctx: Context.Context<never>, tag: Context.Key<I, S>): S | null => {\n\tconst opt = Context.getOption(ctx as Context.Context<I>, tag);\n\treturn opt._tag === 'Some' ? opt.value : null;\n};\n\n/** Build the control-plane domain by reading the optional snapshot\n * orchestrator / filesystem services out of the supervisor's\n * `pluginContext`. The supervisor calls THIS (not `buildControlPlaneDomain`\n * directly) so the L3 service tag stays imported only inside the\n * control-plane seam, never in the supervisor core. Each is optional: bare\n * smoke-test `supervise()` paths don't layer them, so the corresponding\n * accessors degrade to empty. */\nexport const controlPlaneDomainFromContext = (args: {\n\treadonly pluginContext: Context.Context<never>;\n\treadonly graph: ResolvedGraph;\n\treadonly stackOptions: DevstackOptions;\n\treadonly devstackVersion?: string | null;\n\treadonly registry: PluginRegistry;\n\t/** The supervisor's process-scoped log store, passed directly (it is\n\t * created in the supervisor closure, not layered into `pluginContext`).\n\t * `null` in bare smoke-test paths that don't build one. */\n\treadonly logStore?: LogStoreShape | null;\n}): ControlPlaneDomain =>\n\tbuildControlPlaneDomain({\n\t\tgraph: args.graph,\n\t\tstackOptions: args.stackOptions,\n\t\tdevstackVersion: args.devstackVersion ?? null,\n\t\tregistry: args.registry,\n\t\tsnapshotOrchestrator: readOptional(args.pluginContext, SnapshotOrchestratorService),\n\t\tfileSystem: readOptional(args.pluginContext, FileSystem.FileSystem),\n\t\tlogStore: args.logStore ?? null,\n\t});\n"],"mappings":"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"}
|