@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":"log-store.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/log-store.ts"],"sourcesContent":["// Cross-service queryable log store.\n//\n// The `Logger` service (logger.ts) keeps a bounded PER-TAG ring buffer that\n// feeds the projection's per-row `logTail`. That surface is renderer-facing:\n// one short tail per plugin row, no cross-service merge, no history, no\n// server-side level/source filtering. The dashboard's Console \"Logs\" tab\n// needs the opposite shape — a single append-only, cross-service stream the\n// browser can filter by service / level / substring / time window.\n//\n// This store is that second surface. It is fed from the SAME source as the\n// projection tail (the supervisor's `withEventPublishingLogger` wrapper taps\n// every `Logger.log` call into here), so we do NOT double-spawn log readers\n// or re-capture container output: there is one log-production path\n// (`Logger.log`) and two consumers hang off it.\n//\n// Shape: ONE BOUNDED RING PER SERVICE, keyed by the `service` string, plus a\n// shared monotonic `seq` counter so cross-service queries order correctly.\n// A single global ring (the previous design) let a chatty service evict\n// everyone else — a crash loop spamming retries would push a quiet service's\n// only error line out of the buffer before anyone could read it. Per-service\n// rings give every service an independent retention window: a noisy service\n// can only ever evict ITS OWN old lines.\n//\n// To keep total memory bounded under an unbounded set of distinct service\n// keys, the number of rings is capped (`maxServices`). When that cap is hit\n// and a brand-new service appears, one existing ring is evicted. Eviction is\n// ERROR-AWARE: rings that contain an `error`/`fatal` record are preferred for\n// RETENTION, so a crashed service's error trail is the last thing dropped.\n// Among equally-(un)important rings we evict the one whose NEWEST record is\n// oldest (LRU on most-recent activity). See `pickEvictionVictim`.\n//\n// Process-scoped — the backing `Ref` is created in the supervisor alongside\n// `hub`/`commands`, so it survives `stack.restart` (only `cycle.id` bumps)\n// exactly like the projection ref.\n//\n// Hot-path discipline: append is a single `Ref.update` that pushes onto the\n// target service's array and trims when it overflows. No I/O, no redaction\n// (the upstream `Logger.log` already redacted the message + fields before\n// calling here). Ring eviction only runs on the FIRST sight of a new service\n// once the cap is reached — not per-append.\n\nimport { Context, Effect, Layer, Ref } from 'effect';\n\nimport type { PluginKey } from '../../brand.ts';\nimport type { LogLevel } from './logger.ts';\n\n// -----------------------------------------------------------------------------\n// Record shape\n// -----------------------------------------------------------------------------\n\n/** One queryable, cross-service log record. Plain data — the dashboard\n * schema projects this directly. `service` is the resource/plugin key the\n * line was logged under (or the bare tag when no plugin key was attached,\n * e.g. the supervisor's own lines). */\nexport interface LogRecord {\n\t/** Monotonic per-process sequence. Stable cursor for the dashboard. */\n\treadonly seq: number;\n\treadonly timestampMillis: number;\n\treadonly level: LogLevel;\n\t/** Resource/plugin key (`postgres`, `deepbook/SUI_USDC`, …) or the bare\n\t * log tag when the line carried no plugin key. */\n\treadonly service: string;\n\treadonly message: string;\n\t/** Structured fields (already redacted upstream). */\n\treadonly fields: Readonly<Record<string, unknown>>;\n}\n\n/** Filter applied server-side before the records cross to the browser. Every\n * field is optional; an absent field means \"don't constrain on this axis\". */\nexport interface LogFilter {\n\t/** Keep only records whose `service` is in this set (exact match). */\n\treadonly services?: ReadonlyArray<string>;\n\t/** Keep only records at one of these levels. */\n\treadonly levels?: ReadonlyArray<LogLevel>;\n\t/** Case-insensitive substring match against the message (and service). */\n\treadonly search?: string;\n\t/** Keep only records at/after this epoch-ms. */\n\treadonly sinceMillis?: number;\n\t/** Cap on returned records (most recent first). Defaults to the merged\n\t * capacity (perServiceCapacity × maxServices). */\n\treadonly limit?: number;\n}\n\n// -----------------------------------------------------------------------------\n// Capacity policy\n// -----------------------------------------------------------------------------\n\n/** Per-service retention. Each service ring keeps at most this many records;\n * a chatty service evicts only its own oldest lines. */\nexport const DEFAULT_PER_SERVICE_CAPACITY = 2000;\n\n/** Max distinct service rings kept simultaneously. Caps total memory at\n * `perServiceCapacity × maxServices` records. When exceeded, one ring is\n * evicted (error-bearing rings preferred for retention — see\n * `pickEvictionVictim`). */\nexport const DEFAULT_MAX_SERVICES = 256;\n\n/** Env-var names for zero-config tuning. Read once when `makeLogStore` is\n * called with no explicit config (the supervisor wiring site). Non-numeric\n * or non-positive values fall back to the defaults. */\nexport const ENV_PER_SERVICE_CAPACITY = 'DEVSTACK_DASHBOARD_LOG_CAPACITY';\nexport const ENV_MAX_SERVICES = 'DEVSTACK_DASHBOARD_LOG_MAX_SERVICES';\n\n/** Tunables for the per-service log store. Both optional; absent fields take\n * the module defaults (or the corresponding env var, when resolved via\n * `resolveLogStoreConfig`). */\nexport interface LogStoreConfig {\n\t/** Max records kept per service ring. Default {@link DEFAULT_PER_SERVICE_CAPACITY}. */\n\treadonly perServiceCapacity?: number;\n\t/** Max distinct service rings. Default {@link DEFAULT_MAX_SERVICES}. */\n\treadonly maxServices?: number;\n}\n\ninterface ResolvedLogStoreConfig {\n\treadonly perServiceCapacity: number;\n\treadonly maxServices: number;\n}\n\nconst positiveIntOr = (raw: string | undefined, fallback: number): number => {\n\tif (raw === undefined) return fallback;\n\tconst n = Number(raw);\n\treturn Number.isFinite(n) && n >= 1 ? Math.floor(n) : fallback;\n};\n\n/** Resolve the effective config: explicit options win, then env vars, then\n * module defaults. Pure given an `env` bag (defaults to `process.env`). */\nexport const resolveLogStoreConfig = (\n\tconfig: LogStoreConfig = {},\n\tenv: Record<string, string | undefined> = process.env,\n): ResolvedLogStoreConfig => ({\n\tperServiceCapacity:\n\t\tconfig.perServiceCapacity !== undefined && config.perServiceCapacity >= 1\n\t\t\t? Math.floor(config.perServiceCapacity)\n\t\t\t: positiveIntOr(env[ENV_PER_SERVICE_CAPACITY], DEFAULT_PER_SERVICE_CAPACITY),\n\tmaxServices:\n\t\tconfig.maxServices !== undefined && config.maxServices >= 1\n\t\t\t? Math.floor(config.maxServices)\n\t\t\t: positiveIntOr(env[ENV_MAX_SERVICES], DEFAULT_MAX_SERVICES),\n});\n\n// -----------------------------------------------------------------------------\n// Service\n// -----------------------------------------------------------------------------\n\nexport interface LogStoreShape {\n\t/** Append a record. Called from the supervisor's logger tap on the hot\n\t * path — a single `Ref.update`, no I/O. Routes to the per-service ring\n\t * (created on first sight of a service). */\n\treadonly append: (record: {\n\t\treadonly level: LogLevel;\n\t\treadonly service: string;\n\t\treadonly message: string;\n\t\treadonly fields?: Readonly<Record<string, unknown>>;\n\t\treadonly timestampMillis?: number;\n\t}) => Effect.Effect<void>;\n\t/** Query across all per-service rings with a filter. Returns\n\t * most-recent-first by `seq`, capped by `filter.limit`. Never fails. */\n\treadonly query: (filter?: LogFilter) => Effect.Effect<ReadonlyArray<LogRecord>>;\n\t/** Distinct service keys with a live ring (for filter dropdowns). */\n\treadonly services: Effect.Effect<ReadonlyArray<string>>;\n}\n\nexport class LogStore extends Context.Service<LogStore, LogStoreShape>()(\n\t'@devstack/substrate/LogStore',\n) {}\n\n/** One service's bounded ring. `records` is oldest-first; `hasError` is a\n * cached flag so eviction doesn't rescan the array (set when any\n * `error`/`fatal` record is present after trimming). */\ninterface ServiceRing {\n\treadonly records: ReadonlyArray<LogRecord>;\n\treadonly hasError: boolean;\n}\n\ninterface StoreState {\n\t/** Per-service rings. A `Map` so first-sight insertion / eviction is O(1)\n\t * and iteration order is insertion order (irrelevant — query sorts by\n\t * seq). */\n\treadonly rings: ReadonlyMap<string, ServiceRing>;\n\treadonly nextSeq: number;\n}\n\nconst isErrorLevel = (level: LogLevel): boolean => level === 'error' || level === 'fatal';\n\n/** Choose which service ring to evict when `maxServices` is reached and a new\n * service appears. Policy (documented in the file header):\n * 1. Prefer to RETAIN rings that hold an `error`/`fatal` record — never\n * drop a crashed/quiet service's only error trail while a chatty\n * all-`info` ring exists. So victims are drawn from the error-free\n * rings first; only if EVERY ring carries an error do we consider\n * error-bearing ones.\n * 2. Within the candidate pool, evict the ring whose NEWEST record is\n * oldest (LRU on most-recent activity) — the least recently active\n * service. Ties broken by lowest newest-seq.\n * Returns the service key to drop, or null when there are no rings. */\nexport const pickEvictionVictim = (rings: ReadonlyMap<string, ServiceRing>): string | null => {\n\tlet victim: string | null = null;\n\tlet victimNewestMillis = Number.POSITIVE_INFINITY;\n\tlet victimNewestSeq = Number.POSITIVE_INFINITY;\n\tlet victimHasError = true;\n\n\tfor (const [service, ring] of rings) {\n\t\tconst newest = ring.records[ring.records.length - 1];\n\t\t// An empty ring should never exist (we only create on append), but be\n\t\t// defensive: treat it as the most-evictable.\n\t\tconst newestMillis = newest?.timestampMillis ?? Number.NEGATIVE_INFINITY;\n\t\tconst newestSeq = newest?.seq ?? Number.NEGATIVE_INFINITY;\n\t\tconst hasError = ring.hasError;\n\n\t\t// An error-free ring always beats an error-bearing one as a victim.\n\t\tif (victimHasError && !hasError) {\n\t\t\tvictim = service;\n\t\t\tvictimNewestMillis = newestMillis;\n\t\t\tvictimNewestSeq = newestSeq;\n\t\t\tvictimHasError = hasError;\n\t\t\tcontinue;\n\t\t}\n\t\t// Don't replace an error-free candidate with an error-bearing one.\n\t\tif (!victimHasError && hasError) continue;\n\n\t\t// Same error-class: prefer the one whose newest record is oldest.\n\t\tif (\n\t\t\tnewestMillis < victimNewestMillis ||\n\t\t\t(newestMillis === victimNewestMillis && newestSeq < victimNewestSeq)\n\t\t) {\n\t\t\tvictim = service;\n\t\t\tvictimNewestMillis = newestMillis;\n\t\t\tvictimNewestSeq = newestSeq;\n\t\t\tvictimHasError = hasError;\n\t\t}\n\t}\n\treturn victim;\n};\n\n/** Build a `LogStoreShape` over a freshly-created `Ref`. The supervisor calls\n * this (not the Layer) so the rings are process-scoped alongside the\n * projection ref. `config` is injectable for tests; with no `config` the\n * env-var defaults apply. */\nexport const makeLogStore = (config: LogStoreConfig = {}): Effect.Effect<LogStoreShape> =>\n\tEffect.gen(function* () {\n\t\tconst { perServiceCapacity, maxServices } = resolveLogStoreConfig(config);\n\t\tconst ref = yield* Ref.make<StoreState>({ rings: new Map(), nextSeq: 0 });\n\n\t\tconst append: LogStoreShape['append'] = (record) =>\n\t\t\tRef.update(ref, (state) => {\n\t\t\t\tconst next: LogRecord = {\n\t\t\t\t\tseq: state.nextSeq,\n\t\t\t\t\ttimestampMillis: record.timestampMillis ?? Date.now(),\n\t\t\t\t\tlevel: record.level,\n\t\t\t\t\tservice: record.service,\n\t\t\t\t\tmessage: record.message,\n\t\t\t\t\tfields: record.fields ?? {},\n\t\t\t\t};\n\n\t\t\t\tconst rings = new Map(state.rings);\n\t\t\t\tconst existing = rings.get(record.service);\n\n\t\t\t\tif (existing === undefined) {\n\t\t\t\t\t// First sight of this service — may need to evict to stay\n\t\t\t\t\t// under `maxServices`. Eviction runs ONLY here, not per\n\t\t\t\t\t// append.\n\t\t\t\t\tif (rings.size >= maxServices) {\n\t\t\t\t\t\tconst victim = pickEvictionVictim(rings);\n\t\t\t\t\t\tif (victim !== null) rings.delete(victim);\n\t\t\t\t\t}\n\t\t\t\t\trings.set(record.service, {\n\t\t\t\t\t\trecords: [next],\n\t\t\t\t\t\thasError: isErrorLevel(next.level),\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tconst merged = [...existing.records, next];\n\t\t\t\t\tconst trimmed =\n\t\t\t\t\t\tmerged.length > perServiceCapacity ? merged.slice(-perServiceCapacity) : merged;\n\t\t\t\t\t// Recompute `hasError` only when the trim could have dropped\n\t\t\t\t\t// the last error (i.e. an actual trim happened); otherwise the\n\t\t\t\t\t// flag only ever grows true.\n\t\t\t\t\tconst hasError =\n\t\t\t\t\t\ttrimmed.length < merged.length\n\t\t\t\t\t\t\t? trimmed.some((r) => isErrorLevel(r.level))\n\t\t\t\t\t\t\t: existing.hasError || isErrorLevel(next.level);\n\t\t\t\t\trings.set(record.service, { records: trimmed, hasError });\n\t\t\t\t}\n\n\t\t\t\treturn { rings, nextSeq: state.nextSeq + 1 };\n\t\t\t});\n\n\t\tconst query: LogStoreShape['query'] = (filter) =>\n\t\t\tRef.get(ref).pipe(Effect.map((state) => queryRings(state.rings, filter, perServiceCapacity)));\n\n\t\tconst services: LogStoreShape['services'] = Ref.get(ref).pipe(\n\t\t\tEffect.map((state) => [...state.rings.keys()].sort()),\n\t\t);\n\n\t\treturn LogStore.of({ append, query, services });\n\t});\n\n// -----------------------------------------------------------------------------\n// Query\n// -----------------------------------------------------------------------------\n\ninterface CompiledFilter {\n\treadonly serviceSet: ReadonlySet<string> | null;\n\treadonly levelSet: ReadonlySet<LogLevel> | null;\n\treadonly search: string | null;\n\treadonly since: number | undefined;\n\treadonly limit: number;\n}\n\nconst compileFilter = (filter: LogFilter | undefined, defaultLimit: number): CompiledFilter => {\n\tconst search = filter?.search?.trim().toLowerCase();\n\treturn {\n\t\tserviceSet:\n\t\t\tfilter?.services && filter.services.length > 0 ? new Set(filter.services) : null,\n\t\tlevelSet: filter?.levels && filter.levels.length > 0 ? new Set(filter.levels) : null,\n\t\tsearch: search !== undefined && search.length > 0 ? search : null,\n\t\tsince: filter?.sinceMillis,\n\t\tlimit: filter?.limit ?? defaultLimit,\n\t};\n};\n\nconst matches = (r: LogRecord, f: CompiledFilter): boolean => {\n\tif (f.serviceSet !== null && !f.serviceSet.has(r.service)) return false;\n\tif (f.levelSet !== null && !f.levelSet.has(r.level)) return false;\n\tif (f.since !== undefined && r.timestampMillis < f.since) return false;\n\tif (\n\t\tf.search !== null &&\n\t\t!r.message.toLowerCase().includes(f.search) &&\n\t\t!r.service.toLowerCase().includes(f.search)\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n};\n\n/** Merge all per-service rings into a single newest-first result. Each ring\n * is oldest-first, so we run a k-way merge from the tail of each ring,\n * emitting the globally-highest `seq` first and stopping once `limit` is\n * filled. This is O(matched-prefix × ringCount) without materializing the\n * whole universe — important when `limit` is small relative to total volume.\n * Exported for tests. */\nexport const queryRings = (\n\trings: ReadonlyMap<string, ServiceRing>,\n\tfilter: LogFilter | undefined,\n\tperServiceCapacity: number,\n): ReadonlyArray<LogRecord> => {\n\tconst f = compileFilter(filter, perServiceCapacity * Math.max(rings.size, 1));\n\tif (f.limit <= 0) return [];\n\n\t// Cursor per ring, walking newest→oldest (from the end of each array).\n\tconst arrays: ReadonlyArray<LogRecord>[] = [];\n\tconst cursors: number[] = [];\n\tfor (const ring of rings.values()) {\n\t\tif (ring.records.length > 0) {\n\t\t\tarrays.push(ring.records);\n\t\t\tcursors.push(ring.records.length - 1);\n\t\t}\n\t}\n\n\tconst out: LogRecord[] = [];\n\twhile (out.length < f.limit) {\n\t\t// Find the ring whose current cursor points at the highest seq.\n\t\tlet bestRing = -1;\n\t\tlet bestSeq = Number.NEGATIVE_INFINITY;\n\t\tfor (let k = 0; k < arrays.length; k += 1) {\n\t\t\tconst c = cursors[k]!;\n\t\t\tif (c < 0) continue;\n\t\t\tconst seq = arrays[k]![c]!.seq;\n\t\t\tif (seq > bestSeq) {\n\t\t\t\tbestSeq = seq;\n\t\t\t\tbestRing = k;\n\t\t\t}\n\t\t}\n\t\tif (bestRing === -1) break; // all rings exhausted\n\n\t\tconst c = cursors[bestRing]!;\n\t\tconst r = arrays[bestRing]![c]!;\n\t\tcursors[bestRing] = c - 1;\n\t\tif (matches(r, f)) out.push(r);\n\t}\n\treturn out;\n};\n\n/** Pure filter application over a flat oldest-first record array — retained\n * for direct use / tests. Returns newest-first, capped to `limit`. */\nexport const applyLogFilter = (\n\trecords: ReadonlyArray<LogRecord>,\n\tfilter: LogFilter | undefined,\n\tcapacity: number,\n): ReadonlyArray<LogRecord> => {\n\tconst f = compileFilter(filter, capacity);\n\tconst out: LogRecord[] = [];\n\tfor (let i = records.length - 1; i >= 0 && out.length < f.limit; i -= 1) {\n\t\tconst r = records[i]!;\n\t\tif (matches(r, f)) out.push(r);\n\t}\n\treturn out;\n};\n\n/** Layer form (for any caller that wants the store via a Layer rather than\n * the supervisor-scoped `makeLogStore`). Uses the default/env config. */\nexport const layerLogStore: Layer.Layer<LogStore> = Layer.effect(LogStore, makeLogStore());\n\n/** Coerce an arbitrary `pluginKey` (the upstream `Logger.log` tag carries an\n * optional `PluginKey`) to the store's `service` string, falling back to the\n * bare log tag when no plugin key is present. */\nexport const serviceKeyFor = (tag: string, pluginKey: PluginKey | null): string =>\n\tpluginKey === null ? tag : String(pluginKey);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"log-store.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/log-store.ts"],"sourcesContent":["// Cross-service queryable log store.\n//\n// The `Logger` service (logger.ts) keeps a bounded PER-TAG ring buffer that\n// feeds the projection's per-row `logTail`. That surface is renderer-facing:\n// one short tail per plugin row, no cross-service merge, no history, no\n// server-side level/source filtering. The dashboard's Console \"Logs\" tab\n// needs the opposite shape — a single append-only, cross-service stream the\n// browser can filter by service / level / substring / time window.\n//\n// This store is that second surface. It is fed from the SAME source as the\n// projection tail (the supervisor's `withEventPublishingLogger` wrapper taps\n// every `Logger.log` call into here), so we do NOT double-spawn log readers\n// or re-capture container output: there is one log-production path\n// (`Logger.log`) and two consumers hang off it.\n//\n// Shape: ONE BOUNDED RING PER SERVICE, keyed by the `service` string, plus a\n// shared monotonic `seq` counter so cross-service queries order correctly.\n// A single global ring (the previous design) let a chatty service evict\n// everyone else — a crash loop spamming retries would push a quiet service's\n// only error line out of the buffer before anyone could read it. Per-service\n// rings give every service an independent retention window: a noisy service\n// can only ever evict ITS OWN old lines.\n//\n// To keep total memory bounded under an unbounded set of distinct service\n// keys, the number of rings is capped (`maxServices`). When that cap is hit\n// and a brand-new service appears, one existing ring is evicted. Eviction is\n// ERROR-AWARE: rings that contain an `error`/`fatal` record are preferred for\n// RETENTION, so a crashed service's error trail is the last thing dropped.\n// Among equally-(un)important rings we evict the one whose NEWEST record is\n// oldest (LRU on most-recent activity). See `pickEvictionVictim`.\n//\n// Process-scoped — the backing `Ref` is created in the supervisor alongside\n// `hub`/`commands`, so it survives `stack.restart` (only `cycle.id` bumps)\n// exactly like the projection ref.\n//\n// Hot-path discipline: append is a single `Ref.update` that pushes onto the\n// target service's array and trims when it overflows. No I/O, no redaction\n// (the upstream `Logger.log` already redacted the message + fields before\n// calling here). Ring eviction only runs on the FIRST sight of a new service\n// once the cap is reached — not per-append.\n\nimport { Context, Effect, Layer, Ref } from 'effect';\n\nimport type { PluginKey } from '../../brand.ts';\nimport type { LogLevel } from './logger.ts';\n\n// -----------------------------------------------------------------------------\n// Record shape\n// -----------------------------------------------------------------------------\n\n/** One queryable, cross-service log record. Plain data — the dashboard\n * schema projects this directly. `service` is the resource/plugin key the\n * line was logged under (or the bare tag when no plugin key was attached,\n * e.g. the supervisor's own lines). */\nexport interface LogRecord {\n\t/** Monotonic per-process sequence. Stable cursor for the dashboard. */\n\treadonly seq: number;\n\treadonly timestampMillis: number;\n\treadonly level: LogLevel;\n\t/** Resource/plugin key (`postgres`, `deepbook/SUI_USDC`, …) or the bare\n\t * log tag when the line carried no plugin key. */\n\treadonly service: string;\n\treadonly message: string;\n\t/** Structured fields (already redacted upstream). */\n\treadonly fields: Readonly<Record<string, unknown>>;\n}\n\n/** Filter applied server-side before the records cross to the browser. Every\n * field is optional; an absent field means \"don't constrain on this axis\". */\nexport interface LogFilter {\n\t/** Keep only records whose `service` is in this set (exact match). */\n\treadonly services?: ReadonlyArray<string>;\n\t/** Keep only records at one of these levels. */\n\treadonly levels?: ReadonlyArray<LogLevel>;\n\t/** Case-insensitive substring match against the message (and service). */\n\treadonly search?: string;\n\t/** Keep only records at/after this epoch-ms. */\n\treadonly sinceMillis?: number;\n\t/** Cap on returned records (most recent first). Defaults to the merged\n\t * capacity (perServiceCapacity × maxServices). */\n\treadonly limit?: number;\n}\n\n// -----------------------------------------------------------------------------\n// Capacity policy\n// -----------------------------------------------------------------------------\n\n/** Per-service retention. Each service ring keeps at most this many records;\n * a chatty service evicts only its own oldest lines. */\nexport const DEFAULT_PER_SERVICE_CAPACITY = 2000;\n\n/** Max distinct service rings kept simultaneously. Caps total memory at\n * `perServiceCapacity × maxServices` records. When exceeded, one ring is\n * evicted (error-bearing rings preferred for retention — see\n * `pickEvictionVictim`). */\nexport const DEFAULT_MAX_SERVICES = 256;\n\n/** Env-var names for zero-config tuning. Read once when `makeLogStore` is\n * called with no explicit config (the supervisor wiring site). Non-numeric\n * or non-positive values fall back to the defaults. */\nexport const ENV_PER_SERVICE_CAPACITY = 'DEVSTACK_DASHBOARD_LOG_CAPACITY';\nexport const ENV_MAX_SERVICES = 'DEVSTACK_DASHBOARD_LOG_MAX_SERVICES';\n\n/** Tunables for the per-service log store. Both optional; absent fields take\n * the module defaults (or the corresponding env var, when resolved via\n * `resolveLogStoreConfig`). */\nexport interface LogStoreConfig {\n\t/** Max records kept per service ring. Default {@link DEFAULT_PER_SERVICE_CAPACITY}. */\n\treadonly perServiceCapacity?: number;\n\t/** Max distinct service rings. Default {@link DEFAULT_MAX_SERVICES}. */\n\treadonly maxServices?: number;\n}\n\ninterface ResolvedLogStoreConfig {\n\treadonly perServiceCapacity: number;\n\treadonly maxServices: number;\n}\n\nconst positiveIntOr = (raw: string | undefined, fallback: number): number => {\n\tif (raw === undefined) return fallback;\n\tconst n = Number(raw);\n\treturn Number.isFinite(n) && n >= 1 ? Math.floor(n) : fallback;\n};\n\n/** Resolve the effective config: explicit options win, then env vars, then\n * module defaults. Pure given an `env` bag (defaults to `process.env`). */\nexport const resolveLogStoreConfig = (\n\tconfig: LogStoreConfig = {},\n\tenv: Record<string, string | undefined> = process.env,\n): ResolvedLogStoreConfig => ({\n\tperServiceCapacity:\n\t\tconfig.perServiceCapacity !== undefined && config.perServiceCapacity >= 1\n\t\t\t? Math.floor(config.perServiceCapacity)\n\t\t\t: positiveIntOr(env[ENV_PER_SERVICE_CAPACITY], DEFAULT_PER_SERVICE_CAPACITY),\n\tmaxServices:\n\t\tconfig.maxServices !== undefined && config.maxServices >= 1\n\t\t\t? Math.floor(config.maxServices)\n\t\t\t: positiveIntOr(env[ENV_MAX_SERVICES], DEFAULT_MAX_SERVICES),\n});\n\n// -----------------------------------------------------------------------------\n// Service\n// -----------------------------------------------------------------------------\n\nexport interface LogStoreShape {\n\t/** Append a record. Called from the supervisor's logger tap on the hot\n\t * path — a single `Ref.update`, no I/O. Routes to the per-service ring\n\t * (created on first sight of a service). */\n\treadonly append: (record: {\n\t\treadonly level: LogLevel;\n\t\treadonly service: string;\n\t\treadonly message: string;\n\t\treadonly fields?: Readonly<Record<string, unknown>>;\n\t\treadonly timestampMillis?: number;\n\t}) => Effect.Effect<void>;\n\t/** Query across all per-service rings with a filter. Returns\n\t * most-recent-first by `seq`, capped by `filter.limit`. Never fails. */\n\treadonly query: (filter?: LogFilter) => Effect.Effect<ReadonlyArray<LogRecord>>;\n\t/** Distinct service keys with a live ring (for filter dropdowns). */\n\treadonly services: Effect.Effect<ReadonlyArray<string>>;\n}\n\nexport class LogStore extends Context.Service<LogStore, LogStoreShape>()(\n\t'@devstack/substrate/LogStore',\n) {}\n\n/** One service's bounded ring. `records` is oldest-first; `hasError` is a\n * cached flag so eviction doesn't rescan the array (set when any\n * `error`/`fatal` record is present after trimming). */\ninterface ServiceRing {\n\treadonly records: ReadonlyArray<LogRecord>;\n\treadonly hasError: boolean;\n}\n\ninterface StoreState {\n\t/** Per-service rings. A `Map` so first-sight insertion / eviction is O(1)\n\t * and iteration order is insertion order (irrelevant — query sorts by\n\t * seq). */\n\treadonly rings: ReadonlyMap<string, ServiceRing>;\n\treadonly nextSeq: number;\n}\n\nconst isErrorLevel = (level: LogLevel): boolean => level === 'error' || level === 'fatal';\n\n/** Choose which service ring to evict when `maxServices` is reached and a new\n * service appears. Policy (documented in the file header):\n * 1. Prefer to RETAIN rings that hold an `error`/`fatal` record — never\n * drop a crashed/quiet service's only error trail while a chatty\n * all-`info` ring exists. So victims are drawn from the error-free\n * rings first; only if EVERY ring carries an error do we consider\n * error-bearing ones.\n * 2. Within the candidate pool, evict the ring whose NEWEST record is\n * oldest (LRU on most-recent activity) — the least recently active\n * service. Ties broken by lowest newest-seq.\n * Returns the service key to drop, or null when there are no rings. */\nexport const pickEvictionVictim = (rings: ReadonlyMap<string, ServiceRing>): string | null => {\n\tlet victim: string | null = null;\n\tlet victimNewestMillis = Number.POSITIVE_INFINITY;\n\tlet victimNewestSeq = Number.POSITIVE_INFINITY;\n\tlet victimHasError = true;\n\n\tfor (const [service, ring] of rings) {\n\t\tconst newest = ring.records[ring.records.length - 1];\n\t\t// An empty ring should never exist (we only create on append), but be\n\t\t// defensive: treat it as the most-evictable.\n\t\tconst newestMillis = newest?.timestampMillis ?? Number.NEGATIVE_INFINITY;\n\t\tconst newestSeq = newest?.seq ?? Number.NEGATIVE_INFINITY;\n\t\tconst hasError = ring.hasError;\n\n\t\t// An error-free ring always beats an error-bearing one as a victim.\n\t\tif (victimHasError && !hasError) {\n\t\t\tvictim = service;\n\t\t\tvictimNewestMillis = newestMillis;\n\t\t\tvictimNewestSeq = newestSeq;\n\t\t\tvictimHasError = hasError;\n\t\t\tcontinue;\n\t\t}\n\t\t// Don't replace an error-free candidate with an error-bearing one.\n\t\tif (!victimHasError && hasError) continue;\n\n\t\t// Same error-class: prefer the one whose newest record is oldest.\n\t\tif (\n\t\t\tnewestMillis < victimNewestMillis ||\n\t\t\t(newestMillis === victimNewestMillis && newestSeq < victimNewestSeq)\n\t\t) {\n\t\t\tvictim = service;\n\t\t\tvictimNewestMillis = newestMillis;\n\t\t\tvictimNewestSeq = newestSeq;\n\t\t\tvictimHasError = hasError;\n\t\t}\n\t}\n\treturn victim;\n};\n\n/** Build a `LogStoreShape` over a freshly-created `Ref`. The supervisor calls\n * this (not the Layer) so the rings are process-scoped alongside the\n * projection ref. `config` is injectable for tests; with no `config` the\n * env-var defaults apply. */\nexport const makeLogStore = (config: LogStoreConfig = {}): Effect.Effect<LogStoreShape> =>\n\tEffect.gen(function* () {\n\t\tconst { perServiceCapacity, maxServices } = resolveLogStoreConfig(config);\n\t\tconst ref = yield* Ref.make<StoreState>({ rings: new Map(), nextSeq: 0 });\n\n\t\tconst append: LogStoreShape['append'] = (record) =>\n\t\t\tRef.update(ref, (state) => {\n\t\t\t\tconst next: LogRecord = {\n\t\t\t\t\tseq: state.nextSeq,\n\t\t\t\t\ttimestampMillis: record.timestampMillis ?? Date.now(),\n\t\t\t\t\tlevel: record.level,\n\t\t\t\t\tservice: record.service,\n\t\t\t\t\tmessage: record.message,\n\t\t\t\t\tfields: record.fields ?? {},\n\t\t\t\t};\n\n\t\t\t\tconst rings = new Map(state.rings);\n\t\t\t\tconst existing = rings.get(record.service);\n\n\t\t\t\tif (existing === undefined) {\n\t\t\t\t\t// First sight of this service — may need to evict to stay\n\t\t\t\t\t// under `maxServices`. Eviction runs ONLY here, not per\n\t\t\t\t\t// append.\n\t\t\t\t\tif (rings.size >= maxServices) {\n\t\t\t\t\t\tconst victim = pickEvictionVictim(rings);\n\t\t\t\t\t\tif (victim !== null) rings.delete(victim);\n\t\t\t\t\t}\n\t\t\t\t\trings.set(record.service, {\n\t\t\t\t\t\trecords: [next],\n\t\t\t\t\t\thasError: isErrorLevel(next.level),\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tconst merged = [...existing.records, next];\n\t\t\t\t\tconst trimmed =\n\t\t\t\t\t\tmerged.length > perServiceCapacity ? merged.slice(-perServiceCapacity) : merged;\n\t\t\t\t\t// Recompute `hasError` only when the trim could have dropped\n\t\t\t\t\t// the last error (i.e. an actual trim happened); otherwise the\n\t\t\t\t\t// flag only ever grows true.\n\t\t\t\t\tconst hasError =\n\t\t\t\t\t\ttrimmed.length < merged.length\n\t\t\t\t\t\t\t? trimmed.some((r) => isErrorLevel(r.level))\n\t\t\t\t\t\t\t: existing.hasError || isErrorLevel(next.level);\n\t\t\t\t\trings.set(record.service, { records: trimmed, hasError });\n\t\t\t\t}\n\n\t\t\t\treturn { rings, nextSeq: state.nextSeq + 1 };\n\t\t\t});\n\n\t\tconst query: LogStoreShape['query'] = (filter) =>\n\t\t\tRef.get(ref).pipe(Effect.map((state) => queryRings(state.rings, filter, perServiceCapacity)));\n\n\t\tconst services: LogStoreShape['services'] = Ref.get(ref).pipe(\n\t\t\tEffect.map((state) => [...state.rings.keys()].sort()),\n\t\t);\n\n\t\treturn LogStore.of({ append, query, services });\n\t});\n\n// -----------------------------------------------------------------------------\n// Query\n// -----------------------------------------------------------------------------\n\ninterface CompiledFilter {\n\treadonly serviceSet: ReadonlySet<string> | null;\n\treadonly levelSet: ReadonlySet<LogLevel> | null;\n\treadonly search: string | null;\n\treadonly since: number | undefined;\n\treadonly limit: number;\n}\n\nconst compileFilter = (filter: LogFilter | undefined, defaultLimit: number): CompiledFilter => {\n\tconst search = filter?.search?.trim().toLowerCase();\n\treturn {\n\t\tserviceSet: filter?.services && filter.services.length > 0 ? new Set(filter.services) : null,\n\t\tlevelSet: filter?.levels && filter.levels.length > 0 ? new Set(filter.levels) : null,\n\t\tsearch: search !== undefined && search.length > 0 ? search : null,\n\t\tsince: filter?.sinceMillis,\n\t\tlimit: filter?.limit ?? defaultLimit,\n\t};\n};\n\nconst matches = (r: LogRecord, f: CompiledFilter): boolean => {\n\tif (f.serviceSet !== null && !f.serviceSet.has(r.service)) return false;\n\tif (f.levelSet !== null && !f.levelSet.has(r.level)) return false;\n\tif (f.since !== undefined && r.timestampMillis < f.since) return false;\n\tif (\n\t\tf.search !== null &&\n\t\t!r.message.toLowerCase().includes(f.search) &&\n\t\t!r.service.toLowerCase().includes(f.search)\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n};\n\n/** Merge all per-service rings into a single newest-first result. Each ring\n * is oldest-first, so we run a k-way merge from the tail of each ring,\n * emitting the globally-highest `seq` first and stopping once `limit` is\n * filled. This is O(matched-prefix × ringCount) without materializing the\n * whole universe — important when `limit` is small relative to total volume.\n * Exported for tests. */\nexport const queryRings = (\n\trings: ReadonlyMap<string, ServiceRing>,\n\tfilter: LogFilter | undefined,\n\tperServiceCapacity: number,\n): ReadonlyArray<LogRecord> => {\n\tconst f = compileFilter(filter, perServiceCapacity * Math.max(rings.size, 1));\n\tif (f.limit <= 0) return [];\n\n\t// Cursor per ring, walking newest→oldest (from the end of each array).\n\tconst arrays: ReadonlyArray<LogRecord>[] = [];\n\tconst cursors: number[] = [];\n\tfor (const ring of rings.values()) {\n\t\tif (ring.records.length > 0) {\n\t\t\tarrays.push(ring.records);\n\t\t\tcursors.push(ring.records.length - 1);\n\t\t}\n\t}\n\n\tconst out: LogRecord[] = [];\n\twhile (out.length < f.limit) {\n\t\t// Find the ring whose current cursor points at the highest seq.\n\t\tlet bestRing = -1;\n\t\tlet bestSeq = Number.NEGATIVE_INFINITY;\n\t\tfor (let k = 0; k < arrays.length; k += 1) {\n\t\t\tconst c = cursors[k]!;\n\t\t\tif (c < 0) continue;\n\t\t\tconst seq = arrays[k]![c]!.seq;\n\t\t\tif (seq > bestSeq) {\n\t\t\t\tbestSeq = seq;\n\t\t\t\tbestRing = k;\n\t\t\t}\n\t\t}\n\t\tif (bestRing === -1) break; // all rings exhausted\n\n\t\tconst c = cursors[bestRing]!;\n\t\tconst r = arrays[bestRing]![c]!;\n\t\tcursors[bestRing] = c - 1;\n\t\tif (matches(r, f)) out.push(r);\n\t}\n\treturn out;\n};\n\n/** Pure filter application over a flat oldest-first record array — retained\n * for direct use / tests. Returns newest-first, capped to `limit`. */\nexport const applyLogFilter = (\n\trecords: ReadonlyArray<LogRecord>,\n\tfilter: LogFilter | undefined,\n\tcapacity: number,\n): ReadonlyArray<LogRecord> => {\n\tconst f = compileFilter(filter, capacity);\n\tconst out: LogRecord[] = [];\n\tfor (let i = records.length - 1; i >= 0 && out.length < f.limit; i -= 1) {\n\t\tconst r = records[i]!;\n\t\tif (matches(r, f)) out.push(r);\n\t}\n\treturn out;\n};\n\n/** Layer form (for any caller that wants the store via a Layer rather than\n * the supervisor-scoped `makeLogStore`). Uses the default/env config. */\nexport const layerLogStore: Layer.Layer<LogStore> = Layer.effect(LogStore, makeLogStore());\n\n/** Coerce an arbitrary `pluginKey` (the upstream `Logger.log` tag carries an\n * optional `PluginKey`) to the store's `service` string, falling back to the\n * bare log tag when no plugin key is present. */\nexport const serviceKeyFor = (tag: string, pluginKey: PluginKey | null): string =>\n\tpluginKey === null ? tag : String(pluginKey);\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LogAttr } from "./log-attrs.mjs";
|
|
2
2
|
import { Context, Effect, Layer, Ref } from "effect";
|
|
3
3
|
/** Hard cap on a single log line in bytes (UTF-8). Lines past this
|
|
4
4
|
* are truncated with an ellipsis. */
|
|
@@ -63,8 +63,8 @@ const layerLogger = Layer.effect(Logger$1, Effect.gen(function* () {
|
|
|
63
63
|
}));
|
|
64
64
|
const logViaEffect = (line) => {
|
|
65
65
|
const annotated = Effect.annotateLogs({
|
|
66
|
-
[
|
|
67
|
-
[
|
|
66
|
+
[LogAttr.logTag]: line.tag,
|
|
67
|
+
[LogAttr.plugin]: line.pluginKey ?? "(none)",
|
|
68
68
|
...line.fields
|
|
69
69
|
});
|
|
70
70
|
switch (line.level) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.mjs","names":["Logger"],"sources":["../../../../src/substrate/runtime/observability/logger.ts"],"sourcesContent":["// Structured per-tag log buffer.\n//\n// Architecture § L0 Observability primitives: \"structured log buffer,\n// span/annotation conventions, cause walker (shared with renderers,\n// not duplicated)\".\n//\n//
|
|
1
|
+
{"version":3,"file":"logger.mjs","names":["Logger"],"sources":["../../../../src/substrate/runtime/observability/logger.ts"],"sourcesContent":["// Structured per-tag log buffer.\n//\n// Architecture § L0 Observability primitives: \"structured log buffer,\n// span/annotation conventions, cause walker (shared with renderers,\n// not duplicated)\".\n//\n// A typed Service for per-tag logging. Plugins acquire a `Logger` and publish lines;\n// the substrate keeps each line in a bounded per-tag ring buffer and\n// mirrors it into Effect's structured logging via `Effect.log*`.\n// Operator-level lines are also projected into the typed event stream\n// by the supervisor wrapper.\n//\n// Per-tag atomicity is guaranteed by `Ref.update` on the per-tag\n// buffer — single-fiber updates are linearizable and the buffer\n// reads are snapshot-consistent. Cross-process atomicity (when the\n// same log lands in two devstack processes via a shared file)\n// is delegated to the cross-process lock primitive in\n// `../cross-process/stack-lock.ts`.\n\nimport { Context, Effect, Layer, Ref } from 'effect';\n\nimport type { PluginKey } from '../../brand.ts';\nimport { LogAttr } from './log-attrs.ts';\n\n// -----------------------------------------------------------------------------\n// Public types\n// -----------------------------------------------------------------------------\n\n/** Closed level set. Mirrors the projection's row.logTail.level\n * vocabulary, plus `debug` / `trace` for lifecycle diagnostics that\n * should stay in buffers / Effect logs rather than the renderer's\n * operator event stream. */\nexport type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n\n/** A buffered log line. Plain data; the projection's `Row.logTail`\n * derives from this. Structured fields live in `fields` for\n * observability consumers that want richer than a string. */\nexport interface LogLine {\n\treadonly tag: string;\n\treadonly pluginKey: PluginKey | null;\n\treadonly level: LogLevel;\n\treadonly message: string;\n\treadonly fields: Readonly<Record<string, unknown>>;\n\treadonly at: number;\n}\n\nexport interface LogPayload {\n\treadonly level: LogLevel;\n\treadonly message: string;\n\treadonly fields?: Readonly<Record<string, unknown>>;\n}\n\n/** Bounded ring-buffer for a single tag. */\nexport interface TagBuffer {\n\treadonly lines: ReadonlyArray<LogLine>;\n\treadonly truncated: boolean;\n}\n\n// -----------------------------------------------------------------------------\n// Capacity policy\n// -----------------------------------------------------------------------------\n\n/** Per-tag retention. Matches the projection's\n * `MAX_ROW_LOG_LINES` default. */\nexport const DEFAULT_MAX_LINES_PER_TAG = 100;\n\n/** Hard cap on a single log line in bytes (UTF-8). Lines past this\n * are truncated with an ellipsis. */\nexport const MAX_LINE_BYTES = 16 * 1024;\n\n// -----------------------------------------------------------------------------\n// Logger service\n// -----------------------------------------------------------------------------\n\n/**\n * Structured per-tag logger. One instance per stack (or per process —\n * provided via `layerLogger`). Plugins acquire it from Context.\n *\n * Discipline:\n * - The tag is the only thing the logger discriminates on. It is\n * not a service name; it's whatever the publishing fiber picked\n * (typically `pluginKey + '/' + sub-tag`).\n * - Atomic per-tag writes: `Ref.update` on a `ReadonlyMap` of\n * `tag → TagBuffer`. Two fibers logging to the same tag interleave\n * by Ref linearization, not by lost updates.\n * - Structured fields: anything serializable. The cascade formatter\n * (see `cascade-formatter.ts`) renders the fields verbatim.\n */\n/**\n * Public shape of the Logger service. Plugins / supervisor depend on\n * this interface; the concrete Layer below builds an instance.\n */\nexport interface LoggerShape {\n\treadonly log: (\n\t\ttag: string,\n\t\tpluginKey: PluginKey | null,\n\t\tpayload: LogPayload,\n\t) => Effect.Effect<void>;\n\treadonly readTag: (tag: string) => Effect.Effect<TagBuffer>;\n\treadonly readAll: Effect.Effect<ReadonlyMap<string, TagBuffer>>;\n\treadonly clearTag: (tag: string) => Effect.Effect<void>;\n}\n\nexport class Logger extends Context.Service<Logger, LoggerShape>()('@devstack/substrate/Logger') {}\n\n/** Layer that constructs the per-stack Logger. Stateful (holds the\n * per-tag ring buffers in a Ref); the substrate provides one per\n * stack-scope. */\nexport const layerLogger: Layer.Layer<Logger> = Layer.effect(\n\tLogger,\n\tEffect.gen(function* () {\n\t\tconst buffers = yield* Ref.make<ReadonlyMap<string, TagBuffer>>(new Map());\n\n\t\tconst truncateLine = (s: string): string =>\n\t\t\ts.length > MAX_LINE_BYTES ? `${s.slice(0, MAX_LINE_BYTES)}…[truncated]` : s;\n\n\t\tconst appendInternal = (line: LogLine): Effect.Effect<void> =>\n\t\t\tRef.update(buffers, (map) => {\n\t\t\t\tconst prev = map.get(line.tag) ?? { lines: [], truncated: false };\n\t\t\t\tconst nextLines = [...prev.lines, line];\n\t\t\t\tconst overflow = nextLines.length > DEFAULT_MAX_LINES_PER_TAG;\n\t\t\t\tconst trimmed = overflow ? nextLines.slice(-DEFAULT_MAX_LINES_PER_TAG) : nextLines;\n\t\t\t\tconst nextMap = new Map(map);\n\t\t\t\tnextMap.set(line.tag, { lines: trimmed, truncated: prev.truncated || overflow });\n\t\t\t\treturn nextMap;\n\t\t\t});\n\n\t\t/** Log a line under `tag`. Atomic per-tag. */\n\t\tconst log = Effect.fn('Logger.log')(function* (\n\t\t\ttag: string,\n\t\t\tpluginKey: PluginKey | null,\n\t\t\tpayload: LogPayload,\n\t\t) {\n\t\t\tconst line: LogLine = {\n\t\t\t\ttag,\n\t\t\t\tpluginKey,\n\t\t\t\tlevel: payload.level,\n\t\t\t\tmessage: truncateLine(payload.message),\n\t\t\t\tfields: payload.fields ?? {},\n\t\t\t\tat: Date.now(),\n\t\t\t};\n\t\t\tyield* appendInternal(line);\n\t\t\t// Mirror to Effect's structured logger so OTEL exporters see\n\t\t\t// the line too. The bounded buffer is the renderer-facing\n\t\t\t// surface; Effect's logger is the observability-pipeline\n\t\t\t// surface.\n\t\t\tyield* logViaEffect(line);\n\t\t});\n\n\t\t/** Read the current snapshot of a single tag's buffer. Used by\n\t\t * the projection updater when a row is created mid-stack. */\n\t\tconst readTag = (tag: string): Effect.Effect<TagBuffer> =>\n\t\t\tRef.get(buffers).pipe(Effect.map((map) => map.get(tag) ?? { lines: [], truncated: false }));\n\n\t\t/** Read every tag's current snapshot. Used by snapshot capture\n\t\t * + diagnostics. Bounded by aggregate retention. */\n\t\tconst readAll = Ref.get(buffers);\n\n\t\t/** Drop the buffer for a tag. Used when a plugin row is removed. */\n\t\tconst clearTag = (tag: string): Effect.Effect<void> =>\n\t\t\tRef.update(buffers, (map) => {\n\t\t\t\tif (!map.has(tag)) return map;\n\t\t\t\tconst next = new Map(map);\n\t\t\t\tnext.delete(tag);\n\t\t\t\treturn next;\n\t\t\t});\n\n\t\treturn Logger.of({ log, readTag, readAll, clearTag });\n\t}),\n);\n\n// -----------------------------------------------------------------------------\n// Effect-logger bridge\n// -----------------------------------------------------------------------------\n\nconst logViaEffect = (line: LogLine): Effect.Effect<void> => {\n\tconst annotated = Effect.annotateLogs({\n\t\t[LogAttr.logTag]: line.tag,\n\t\t[LogAttr.plugin]: line.pluginKey ?? '(none)',\n\t\t...line.fields,\n\t});\n\tswitch (line.level) {\n\t\tcase 'trace':\n\t\t\treturn Effect.logTrace(line.message).pipe(annotated);\n\t\tcase 'debug':\n\t\t\treturn Effect.logDebug(line.message).pipe(annotated);\n\t\tcase 'info':\n\t\t\treturn Effect.logInfo(line.message).pipe(annotated);\n\t\tcase 'warn':\n\t\t\treturn Effect.logWarning(line.message).pipe(annotated);\n\t\tcase 'error':\n\t\t\treturn Effect.logError(line.message).pipe(annotated);\n\t\tcase 'fatal':\n\t\t\treturn Effect.logFatal(line.message).pipe(annotated);\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = line.level;\n\t\t\tvoid _exhaustive;\n\t\t\treturn Effect.void;\n\t\t}\n\t}\n};\n"],"mappings":";;;;AAoEA,MAAa,iBAAiB,KAAK;AAmCnC,IAAaA,WAAb,cAA4B,QAAQ,QAA6B,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC;;;;AAKlG,MAAa,cAAmC,MAAM,OACrDA,UACA,OAAO,IAAI,aAAa;CACvB,MAAM,UAAU,OAAO,IAAI,qBAAqC,IAAI,IAAI,CAAC;CAEzE,MAAM,gBAAgB,MACrB,EAAE,SAAA,QAA0B,GAAG,EAAE,MAAM,GAAG,cAAc,EAAE,gBAAgB;CAE3E,MAAM,kBAAkB,SACvB,IAAI,OAAO,UAAU,QAAQ;EAC5B,MAAM,OAAO,IAAI,IAAI,KAAK,GAAG,KAAK;GAAE,OAAO,CAAC;GAAG,WAAW;EAAM;EAChE,MAAM,YAAY,CAAC,GAAG,KAAK,OAAO,IAAI;EACtC,MAAM,WAAW,UAAU,SAAA;EAC3B,MAAM,UAAU,WAAW,UAAU,MAAM,IAA0B,IAAI;EACzE,MAAM,UAAU,IAAI,IAAI,GAAG;EAC3B,QAAQ,IAAI,KAAK,KAAK;GAAE,OAAO;GAAS,WAAW,KAAK,aAAa;EAAS,CAAC;EAC/E,OAAO;CACR,CAAC;;CAGF,MAAM,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,WACnC,KACA,WACA,SACC;EACD,MAAM,OAAgB;GACrB;GACA;GACA,OAAO,QAAQ;GACf,SAAS,aAAa,QAAQ,OAAO;GACrC,QAAQ,QAAQ,UAAU,CAAC;GAC3B,IAAI,KAAK,IAAI;EACd;EACA,OAAO,eAAe,IAAI;EAK1B,OAAO,aAAa,IAAI;CACzB,CAAC;;;CAID,MAAM,WAAW,QAChB,IAAI,IAAI,OAAO,CAAC,CAAC,KAAK,OAAO,KAAK,QAAQ,IAAI,IAAI,GAAG,KAAK;EAAE,OAAO,CAAC;EAAG,WAAW;CAAM,CAAC,CAAC;;;CAI3F,MAAM,UAAU,IAAI,IAAI,OAAO;;CAG/B,MAAM,YAAY,QACjB,IAAI,OAAO,UAAU,QAAQ;EAC5B,IAAI,CAAC,IAAI,IAAI,GAAG,GAAG,OAAO;EAC1B,MAAM,OAAO,IAAI,IAAI,GAAG;EACxB,KAAK,OAAO,GAAG;EACf,OAAO;CACR,CAAC;CAEF,OAAOA,SAAO,GAAG;EAAE;EAAK;EAAS;EAAS;CAAS,CAAC;AACrD,CAAC,CACF;AAMA,MAAM,gBAAgB,SAAuC;CAC5D,MAAM,YAAY,OAAO,aAAa;GACpC,QAAQ,SAAS,KAAK;GACtB,QAAQ,SAAS,KAAK,aAAa;EACpC,GAAG,KAAK;CACT,CAAC;CACD,QAAQ,KAAK,OAAb;EACC,KAAK,SACJ,OAAO,OAAO,SAAS,KAAK,OAAO,CAAC,CAAC,KAAK,SAAS;EACpD,KAAK,SACJ,OAAO,OAAO,SAAS,KAAK,OAAO,CAAC,CAAC,KAAK,SAAS;EACpD,KAAK,QACJ,OAAO,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,KAAK,SAAS;EACnD,KAAK,QACJ,OAAO,OAAO,WAAW,KAAK,OAAO,CAAC,CAAC,KAAK,SAAS;EACtD,KAAK,SACJ,OAAO,OAAO,SAAS,KAAK,OAAO,CAAC,CAAC,KAAK,SAAS;EACpD,KAAK,SACJ,OAAO,OAAO,SAAS,KAAK,OAAO,CAAC,CAAC,KAAK,SAAS;EACpD;GAC4B,KAAK;GAEhC,OAAO,OAAO;CAEhB;AACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-truncate.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/output-truncate.ts"],"sourcesContent":["// Bounded stdout/stderr trimming for error-message tails.\n//\n// Two failure-message shapes converged on the same primitive:\n// - `walrus/deploy.ts::excerpt` — emit a labelled, JSON-quoted body\n// (` stdout=\"...\"`) with mid-truncation when oversize (head + tail\n// window with a `<truncated N chars>` marker), elided when empty.\n// - `seal/bootstrap-assets/source-fetch.ts::outputTail` — emit just\n// the last N chars of the raw stream, passed as the `stdout:` /\n// `stderr:` field of a typed error.\n//\n// Both are stdout/stderr bound-the-blast-radius helpers.
|
|
1
|
+
{"version":3,"file":"output-truncate.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/output-truncate.ts"],"sourcesContent":["// Bounded stdout/stderr trimming for error-message tails.\n//\n// Two failure-message shapes converged on the same primitive:\n// - `walrus/deploy.ts::excerpt` — emit a labelled, JSON-quoted body\n// (` stdout=\"...\"`) with mid-truncation when oversize (head + tail\n// window with a `<truncated N chars>` marker), elided when empty.\n// - `seal/bootstrap-assets/source-fetch.ts::outputTail` — emit just\n// the last N chars of the raw stream, passed as the `stdout:` /\n// `stderr:` field of a typed error.\n//\n// Both are stdout/stderr bound-the-blast-radius helpers. One module so\n// the boundedness-policy lives in ONE place and the per-call-site shape\n// (label-wrapped vs raw tail) is just an output decision.\n\n/** Return the trailing `maxChars` characters of `value`, or `value`\n * unchanged if shorter. Used at error-construction sites that want to\n * surface the most-recent output (typically where the failure cause\n * appears at the END of a long stream — e.g. a git clone trace whose\n * fatal line is the last one). Defaults to 1000 chars, the source-fetch\n * call sites' original budget. */\nexport const tailOutput = (value: string, maxChars: number = 1_000): string =>\n\tvalue.length > maxChars ? value.slice(-maxChars) : value;\n\n/** Render `value` as a labelled, JSON-quoted suffix (` <label>=<json>`)\n * for inline error messages. Empty / whitespace-only input collapses to\n * the empty string so the caller can unconditionally concatenate the\n * result without producing dangling labels.\n *\n * Oversize input is mid-truncated: a head slice + literal\n * `...<truncated N chars>...` + a tail slice, keeping both the leading\n * context (which often identifies the command) AND the trailing context\n * (which often identifies the cause). Defaults size the output so a\n * combined stdout + stderr fits inside an L4-level log line without\n * overflowing typical terminal scroll buffers.\n *\n * @param maxChars - total budget before mid-truncation kicks in (default\n * 2400). Below this the body is emitted verbatim.\n * @param windowChars - per-side window kept when truncating (default\n * 1100). Total kept output is roughly `2 * windowChars` plus the\n * truncation marker. */\nexport const labelledExcerpt = (\n\tlabel: string,\n\tvalue: string,\n\tmaxChars: number = 2_400,\n\twindowChars: number = 1_100,\n): string => {\n\tconst trimmed = value.trim();\n\tif (trimmed.length === 0) return '';\n\tconst body =\n\t\ttrimmed.length > maxChars\n\t\t\t? `${trimmed.slice(0, windowChars)}...<truncated ${trimmed.length - 2 * windowChars} chars>...${trimmed.slice(-windowChars)}`\n\t\t\t: trimmed;\n\treturn ` ${label}=${JSON.stringify(body)}`;\n};\n"],"mappings":";;;;;;;AAoBA,MAAa,cAAc,OAAe,WAAmB,QAC5D,MAAM,SAAS,WAAW,MAAM,MAAM,CAAC,QAAQ,IAAI;;;;;;;;;;;;;;;;;;AAmBpD,MAAa,mBACZ,OACA,OACA,WAAmB,MACnB,cAAsB,SACV;CACZ,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,QAAQ,WAAW,GAAG,OAAO;CACjC,MAAM,OACL,QAAQ,SAAS,WACd,GAAG,QAAQ,MAAM,GAAG,WAAW,EAAE,gBAAgB,QAAQ,SAAS,IAAI,YAAY,YAAY,QAAQ,MAAM,CAAC,WAAW,MACxH;CACJ,OAAO,IAAI,MAAM,GAAG,KAAK,UAAU,IAAI;AACxC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pretty-error.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/pretty-error.ts"],"sourcesContent":["// Pretty-error renderer.\n//\n// Architecture § L0 Observability: one cause walker, shared with\n// renderers. The cascade-formatter is the pure walk; this module is\n// the IO + convenience layer:\n// - `prettyError(unknown)` — convenience for \"I have a thrown value\"\n// and don't want to thread a Cause.\n// - `prettyCause(cause)` — the supervisor's path: the engine\n// catches via `Effect.catchCause` and renders.\n// - `prettyErrorStructured` — render to a `StructuredError` shape\n// suitable for the projection's `errors` field.\n//\n// This module is *thin* — all real formatting logic is in\n// `cascade-formatter.ts`. Splitting them keeps the formatter pure\n// + reusable while letting this module evolve IO conventions.\n\nimport { Cause } from 'effect';\n\nimport type { PluginKey } from '../../brand.ts';\nimport type { StructuredError } from '../../projection.ts';\n\nimport {\n\ttype FormatOptions,\n\ttype
|
|
1
|
+
{"version":3,"file":"pretty-error.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/pretty-error.ts"],"sourcesContent":["// Pretty-error renderer.\n//\n// Architecture § L0 Observability: one cause walker, shared with\n// renderers. The cascade-formatter is the pure walk; this module is\n// the IO + convenience layer:\n// - `prettyError(unknown)` — convenience for \"I have a thrown value\"\n// and don't want to thread a Cause.\n// - `prettyCause(cause)` — the supervisor's path: the engine\n// catches via `Effect.catchCause` and renders.\n// - `prettyErrorStructured` — render to a `StructuredError` shape\n// suitable for the projection's `errors` field.\n//\n// This module is *thin* — all real formatting logic is in\n// `cascade-formatter.ts`. Splitting them keeps the formatter pure\n// + reusable while letting this module evolve IO conventions.\n\nimport { Cause } from 'effect';\n\nimport type { PluginKey } from '../../brand.ts';\nimport type { StructuredError } from '../../projection.ts';\n\nimport {\n\ttype FormatOptions,\n\ttype TaggedErrorLike,\n\tformatCause,\n\tformatValue,\n\tisTaggedError,\n} from './cascade-formatter.ts';\n\nexport type { FormatOptions, TaggedErrorLike };\n\n/** Render any value (Cause, tagged error, plain Error, anything)\n * using the cascade rules. The pretty-error variant exists so\n * callers don't have to know whether they have a Cause or an Error. */\nexport const prettyError = (value: unknown, options?: FormatOptions): string =>\n\tformatValue(value, options);\n\n/** Render an Effect `Cause.Cause<E>` directly. The supervisor's preferred\n * entry point (it has a Cause from `Effect.catchCause`). */\nexport const prettyCause = <E>(cause: Cause.Cause<E>, options?: FormatOptions): string =>\n\tformatCause(cause, options);\n\n// -----------------------------------------------------------------------------\n// Structured projection\n// -----------------------------------------------------------------------------\n\nexport interface StructureOptions extends FormatOptions {\n\treadonly at?: number;\n\treadonly pluginKey?: PluginKey | null;\n\treadonly severity?: StructuredError['severity'];\n}\n\n/**\n * Render a Cause as a `StructuredError` suitable for the projection's\n * `errors` field. The `chain` array carries the per-layer summaries\n * (header lines only — the full multi-line render is what `prettyCause`\n * returns).\n *\n * Use the projection-shaped variant when building an `error.reported`\n * event; use `prettyCause` when rendering for human display.\n */\nexport const prettyErrorStructured = <E>(\n\tcause: Cause.Cause<E>,\n\toptions?: StructureOptions,\n): StructuredError => {\n\tconst at = options?.at ?? Date.now();\n\tconst pluginKey = options?.pluginKey ?? null;\n\tconst severity = options?.severity ?? 'error';\n\n\tconst { tag, summary } = extractHeadline(cause);\n\tconst chain = extractChain(cause);\n\n\treturn { at, pluginKey, tag, summary, chain, severity };\n};\n\n/** Walk the cause's first `Fail` reason and pull out the outermost\n * tag + message for projection.summary. */\nconst extractHeadline = (cause: Cause.Cause<unknown>): { tag: string; summary: string } => {\n\tfor (const reason of cause.reasons) {\n\t\tif (Cause.isFailReason(reason)) {\n\t\t\tconst error = reason.error;\n\t\t\tif (isTaggedError(error)) {\n\t\t\t\treturn {\n\t\t\t\t\ttag: error._tag,\n\t\t\t\t\tsummary: headlineText(error) ?? error._tag,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (error instanceof Error) {\n\t\t\t\treturn { tag: error.name, summary: error.message };\n\t\t\t}\n\t\t\treturn { tag: 'UnknownFailure', summary: String(error) };\n\t\t}\n\t\tif (Cause.isDieReason(reason)) {\n\t\t\tconst defect = reason.defect;\n\t\t\tif (isTaggedError(defect)) {\n\t\t\t\treturn {\n\t\t\t\t\ttag: `Defect[${defect._tag}]`,\n\t\t\t\t\tsummary: headlineText(defect) ?? defect._tag,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (defect instanceof Error) {\n\t\t\t\treturn { tag: 'Defect', summary: defect.message };\n\t\t\t}\n\t\t\treturn { tag: 'Defect', summary: String(defect) };\n\t\t}\n\t\t// Cause.isInterruptReason(reason)\n\t\treturn { tag: 'Interrupt', summary: 'fiber interrupted' };\n\t}\n\treturn { tag: 'EmptyCause', summary: '(empty cause)' };\n};\n\nconst headlineText = (value: TaggedErrorLike): string | null => {\n\tfor (const key of ['message', 'detail', 'stderr'] as const) {\n\t\tconst field = value[key];\n\t\tif (typeof field === 'string' && field.trim().length > 0) return field.trim();\n\t}\n\treturn null;\n};\n\n/** Walk every layer of the cause's outermost `Fail` chain and build a\n * list of `<tag>: <message>` strings — one per nested layer. */\nconst extractChain = (cause: Cause.Cause<unknown>): ReadonlyArray<string> => {\n\tconst out: Array<string> = [];\n\tfor (const reason of cause.reasons) {\n\t\twalkChainFromReason(reason, out, new WeakSet());\n\t}\n\treturn out;\n};\n\nconst walkChainFromReason = (\n\treason: Cause.Reason<unknown>,\n\tout: Array<string>,\n\tvisited: WeakSet<object>,\n): void => {\n\tif (Cause.isFailReason(reason)) walkChainFromValue(reason.error, out, visited);\n\telse if (Cause.isDieReason(reason)) walkChainFromValue(reason.defect, out, visited);\n\telse out.push('Interrupt');\n};\n\nconst walkChainFromValue = (value: unknown, out: Array<string>, visited: WeakSet<object>): void => {\n\tif (value === undefined || value === null) return;\n\tif (typeof value === 'object') {\n\t\tif (visited.has(value as object)) return;\n\t\tvisited.add(value as object);\n\t}\n\tif (isTaggedError(value)) {\n\t\tconst tag = value._tag;\n\t\tconst msg = headlineText(value) ?? '';\n\t\tout.push(msg ? `${tag}: ${msg}` : tag);\n\t\tif (value.cause !== undefined && value.cause !== null) {\n\t\t\twalkChainFromValue(value.cause, out, visited);\n\t\t}\n\t\treturn;\n\t}\n\tif (value instanceof Error) {\n\t\tout.push(`${value.name}: ${value.message}`);\n\t\treturn;\n\t}\n\tout.push(String(value));\n};\n"],"mappings":";;;;;;;;;;;;AA6DA,MAAa,yBACZ,OACA,YACqB;CACrB,MAAM,KAAK,SAAS,MAAM,KAAK,IAAI;CACnC,MAAM,YAAY,SAAS,aAAa;CACxC,MAAM,WAAW,SAAS,YAAY;CAEtC,MAAM,EAAE,KAAK,YAAY,gBAAgB,KAAK;CAG9C,OAAO;EAAE;EAAI;EAAW;EAAK;EAAS,OAFxB,aAAa,KAEe;EAAG;CAAS;AACvD;;;AAIA,MAAM,mBAAmB,UAAkE;CAC1F,KAAK,MAAM,UAAU,MAAM,SAAS;EACnC,IAAI,MAAM,aAAa,MAAM,GAAG;GAC/B,MAAM,QAAQ,OAAO;GACrB,IAAI,cAAc,KAAK,GACtB,OAAO;IACN,KAAK,MAAM;IACX,SAAS,aAAa,KAAK,KAAK,MAAM;GACvC;GAED,IAAI,iBAAiB,OACpB,OAAO;IAAE,KAAK,MAAM;IAAM,SAAS,MAAM;GAAQ;GAElD,OAAO;IAAE,KAAK;IAAkB,SAAS,OAAO,KAAK;GAAE;EACxD;EACA,IAAI,MAAM,YAAY,MAAM,GAAG;GAC9B,MAAM,SAAS,OAAO;GACtB,IAAI,cAAc,MAAM,GACvB,OAAO;IACN,KAAK,UAAU,OAAO,KAAK;IAC3B,SAAS,aAAa,MAAM,KAAK,OAAO;GACzC;GAED,IAAI,kBAAkB,OACrB,OAAO;IAAE,KAAK;IAAU,SAAS,OAAO;GAAQ;GAEjD,OAAO;IAAE,KAAK;IAAU,SAAS,OAAO,MAAM;GAAE;EACjD;EAEA,OAAO;GAAE,KAAK;GAAa,SAAS;EAAoB;CACzD;CACA,OAAO;EAAE,KAAK;EAAc,SAAS;CAAgB;AACtD;AAEA,MAAM,gBAAgB,UAA0C;CAC/D,KAAK,MAAM,OAAO;EAAC;EAAW;EAAU;CAAQ,GAAY;EAC3D,MAAM,QAAQ,MAAM;EACpB,IAAI,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS,GAAG,OAAO,MAAM,KAAK;CAC7E;CACA,OAAO;AACR;;;AAIA,MAAM,gBAAgB,UAAuD;CAC5E,MAAM,MAAqB,CAAC;CAC5B,KAAK,MAAM,UAAU,MAAM,SAC1B,oBAAoB,QAAQ,qBAAK,IAAI,QAAQ,CAAC;CAE/C,OAAO;AACR;AAEA,MAAM,uBACL,QACA,KACA,YACU;CACV,IAAI,MAAM,aAAa,MAAM,GAAG,mBAAmB,OAAO,OAAO,KAAK,OAAO;MACxE,IAAI,MAAM,YAAY,MAAM,GAAG,mBAAmB,OAAO,QAAQ,KAAK,OAAO;MAC7E,IAAI,KAAK,WAAW;AAC1B;AAEA,MAAM,sBAAsB,OAAgB,KAAoB,YAAmC;CAClG,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM;CAC3C,IAAI,OAAO,UAAU,UAAU;EAC9B,IAAI,QAAQ,IAAI,KAAe,GAAG;EAClC,QAAQ,IAAI,KAAe;CAC5B;CACA,IAAI,cAAc,KAAK,GAAG;EACzB,MAAM,MAAM,MAAM;EAClB,MAAM,MAAM,aAAa,KAAK,KAAK;EACnC,IAAI,KAAK,MAAM,GAAG,IAAI,IAAI,QAAQ,GAAG;EACrC,IAAI,MAAM,UAAU,KAAA,KAAa,MAAM,UAAU,MAChD,mBAAmB,MAAM,OAAO,KAAK,OAAO;EAE7C;CACD;CACA,IAAI,iBAAiB,OAAO;EAC3B,IAAI,KAAK,GAAG,MAAM,KAAK,IAAI,MAAM,SAAS;EAC1C;CACD;CACA,IAAI,KAAK,OAAO,KAAK,CAAC;AACvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-lines.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/process-lines.ts"],"sourcesContent":["import { Effect, Stream } from 'effect';\nimport type { Scope } from 'effect';\n\nimport type { PluginKey } from '../../brand.ts';\nimport type { LoggerShape, LogLevel } from './logger.ts';\n\nexport type ProcessOutputStream = 'stdout' | 'stderr';\n\nexport interface ObservedProcessLine {\n\treadonly stream: ProcessOutputStream;\n\treadonly line: string;\n}\n\nexport interface ObserveProcessLinesOptions {\n\treadonly logger: LoggerShape;\n\treadonly tag: string;\n\treadonly pluginKey: PluginKey | null;\n\treadonly fields?: Readonly<Record<string, unknown>>;\n\treadonly onLine?: (line: ObservedProcessLine) => Effect.Effect<void>;\n\treadonly levelForStream?: (stream: ProcessOutputStream) => LogLevel;\n}\n\nexport type ProcessByteStream<E = unknown, R = never> = Stream.Stream<Uint8Array, E, R>;\n\nexport const splitUtf8Lines = <E, R>(\n\tstream: ProcessByteStream<E, R>,\n): Stream.Stream<string, E, R> => stream.pipe(Stream.decodeText(), Stream.splitLines);\n\nexport const readableToByteStream = (\n\tstream: AsyncIterable<Uint8Array> | null | undefined,\n\tmapError: (cause: unknown) => unknown = (cause) => cause,\n): ProcessByteStream => {\n\tif (stream === null || stream === undefined) return Stream.empty;\n\treturn Stream.fromAsyncIterable(stream, mapError);\n};\n\nconst defaultLevelForStream = (stream: ProcessOutputStream): LogLevel =>\n\tstream === 'stderr' ? 'warn' : 'info';\n\nconst drainLineStream = (\n\tstream: ProcessByteStream,\n\tstreamName: ProcessOutputStream,\n\toptions: ObserveProcessLinesOptions,\n): Effect.Effect<void, never> => {\n\tconst levelForStream = options.levelForStream ?? defaultLevelForStream;\n\tconst fields = options.fields ?? {};\n\tconst drain = splitUtf8Lines(stream).pipe(\n\t\tStream.tap((line) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* options.logger\n\t\t\t\t\t.log(options.tag, options.pluginKey, {\n\t\t\t\t\t\tlevel: levelForStream(streamName),\n\t\t\t\t\t\tmessage: line,\n\t\t\t\t\t\tfields: { ...fields, stream: streamName },\n\t\t\t\t\t})\n\t\t\t\t\t.pipe(Effect.ignore);\n\t\t\t\tif (options.onLine !== undefined) {\n\t\t\t\t\tyield* options.onLine({ stream: streamName, line }).pipe(Effect.ignore);\n\t\t\t\t}\n\t\t\t}),\n\t\t),\n\t\tStream.runDrain,\n\t\tEffect.ignore,\n\t);\n\treturn drain;\n};\n\nexport const observeProcessLines = (\n\tstreams: {\n\t\treadonly stdout?: ProcessByteStream | null;\n\t\treadonly stderr?: ProcessByteStream | null;\n\t},\n\toptions: ObserveProcessLinesOptions,\n): Effect.Effect<void, never, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tif (streams.stdout !== undefined && streams.stdout !== null) {\n\t\t\tyield* drainLineStream(streams.stdout, 'stdout', options).pipe(Effect.forkScoped);\n\t\t}\n\t\tif (streams.stderr !== undefined && streams.stderr !== null) {\n\t\t\tyield* drainLineStream(streams.stderr, 'stderr', options).pipe(Effect.forkScoped);\n\t\t}\n\t});\n"],"mappings":";;;;;;;;AAwBA,MAAa,kBACZ,WACiC,OAAO,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"process-lines.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/process-lines.ts"],"sourcesContent":["import { Effect, Stream } from 'effect';\nimport type { Scope } from 'effect';\n\nimport type { PluginKey } from '../../brand.ts';\nimport type { LoggerShape, LogLevel } from './logger.ts';\n\nexport type ProcessOutputStream = 'stdout' | 'stderr';\n\nexport interface ObservedProcessLine {\n\treadonly stream: ProcessOutputStream;\n\treadonly line: string;\n}\n\nexport interface ObserveProcessLinesOptions {\n\treadonly logger: LoggerShape;\n\treadonly tag: string;\n\treadonly pluginKey: PluginKey | null;\n\treadonly fields?: Readonly<Record<string, unknown>>;\n\treadonly onLine?: (line: ObservedProcessLine) => Effect.Effect<void>;\n\treadonly levelForStream?: (stream: ProcessOutputStream) => LogLevel;\n}\n\nexport type ProcessByteStream<E = unknown, R = never> = Stream.Stream<Uint8Array, E, R>;\n\nexport const splitUtf8Lines = <E, R>(\n\tstream: ProcessByteStream<E, R>,\n): Stream.Stream<string, E, R> => stream.pipe(Stream.decodeText(), Stream.splitLines);\n\nexport const readableToByteStream = (\n\tstream: AsyncIterable<Uint8Array> | null | undefined,\n\tmapError: (cause: unknown) => unknown = (cause) => cause,\n): ProcessByteStream => {\n\tif (stream === null || stream === undefined) return Stream.empty;\n\treturn Stream.fromAsyncIterable(stream, mapError);\n};\n\nconst defaultLevelForStream = (stream: ProcessOutputStream): LogLevel =>\n\tstream === 'stderr' ? 'warn' : 'info';\n\nconst drainLineStream = (\n\tstream: ProcessByteStream,\n\tstreamName: ProcessOutputStream,\n\toptions: ObserveProcessLinesOptions,\n): Effect.Effect<void, never> => {\n\tconst levelForStream = options.levelForStream ?? defaultLevelForStream;\n\tconst fields = options.fields ?? {};\n\tconst drain = splitUtf8Lines(stream).pipe(\n\t\tStream.tap((line) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tyield* options.logger\n\t\t\t\t\t.log(options.tag, options.pluginKey, {\n\t\t\t\t\t\tlevel: levelForStream(streamName),\n\t\t\t\t\t\tmessage: line,\n\t\t\t\t\t\tfields: { ...fields, stream: streamName },\n\t\t\t\t\t})\n\t\t\t\t\t.pipe(Effect.ignore);\n\t\t\t\tif (options.onLine !== undefined) {\n\t\t\t\t\tyield* options.onLine({ stream: streamName, line }).pipe(Effect.ignore);\n\t\t\t\t}\n\t\t\t}),\n\t\t),\n\t\tStream.runDrain,\n\t\tEffect.ignore,\n\t);\n\treturn drain;\n};\n\nexport const observeProcessLines = (\n\tstreams: {\n\t\treadonly stdout?: ProcessByteStream | null;\n\t\treadonly stderr?: ProcessByteStream | null;\n\t},\n\toptions: ObserveProcessLinesOptions,\n): Effect.Effect<void, never, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tif (streams.stdout !== undefined && streams.stdout !== null) {\n\t\t\tyield* drainLineStream(streams.stdout, 'stdout', options).pipe(Effect.forkScoped);\n\t\t}\n\t\tif (streams.stderr !== undefined && streams.stderr !== null) {\n\t\t\tyield* drainLineStream(streams.stderr, 'stderr', options).pipe(Effect.forkScoped);\n\t\t}\n\t});\n"],"mappings":";;;;;;;;AAwBA,MAAa,kBACZ,WACiC,OAAO,KAAK,OAAO,WAAW,GAAG,OAAO,UAAU;AAEpF,MAAa,wBACZ,QACA,YAAyC,UAAU,UAC5B;CACvB,IAAI,WAAW,QAAQ,WAAW,KAAA,GAAW,OAAO,OAAO;CAC3D,OAAO,OAAO,kBAAkB,QAAQ,QAAQ;AACjD;AAEA,MAAM,yBAAyB,WAC9B,WAAW,WAAW,SAAS;AAEhC,MAAM,mBACL,QACA,YACA,YACgC;CAChC,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,SAAS,QAAQ,UAAU,CAAC;CAmBlC,OAlBc,eAAe,MAAM,CAAC,CAAC,KACpC,OAAO,KAAK,SACX,OAAO,IAAI,aAAa;EACvB,OAAO,QAAQ,OACb,IAAI,QAAQ,KAAK,QAAQ,WAAW;GACpC,OAAO,eAAe,UAAU;GAChC,SAAS;GACT,QAAQ;IAAE,GAAG;IAAQ,QAAQ;GAAW;EACzC,CAAC,CAAC,CACD,KAAK,OAAO,MAAM;EACpB,IAAI,QAAQ,WAAW,KAAA,GACtB,OAAO,QAAQ,OAAO;GAAE,QAAQ;GAAY;EAAK,CAAC,CAAC,CAAC,KAAK,OAAO,MAAM;CAExE,CAAC,CACF,GACA,OAAO,UACP,OAAO,MAEG;AACZ;AAEA,MAAa,uBACZ,SAIA,YAEA,OAAO,IAAI,aAAa;CACvB,IAAI,QAAQ,WAAW,KAAA,KAAa,QAAQ,WAAW,MACtD,OAAO,gBAAgB,QAAQ,QAAQ,UAAU,OAAO,CAAC,CAAC,KAAK,OAAO,UAAU;CAEjF,IAAI,QAAQ,WAAW,KAAA,KAAa,QAAQ,WAAW,MACtD,OAAO,gBAAgB,QAAQ,QAAQ,UAAU,OAAO,CAAC,CAAC,KAAK,OAAO,UAAU;AAElF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redaction.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/redaction.ts"],"sourcesContent":["// Pure redaction helpers. These back the inline plugin-level redaction\n// in seal (`redactMasterKey`) and wallet (`redactToken`), and the\n// cascade-formatter's `safeJson` field scrubbing. There is intentionally\n// no engine-wide Redactor service: plugins that surface secret-bearing\n// output defend themselves at the point of emission with a local rule\n// set, which keeps the rules co-located with the thing they protect.\n\nexport type RedactionRule =\n\t| {\n\t\t\treadonly kind: 'literal';\n\t\t\treadonly value: string;\n\t\t\treadonly replacement?: string;\n\t }\n\t| {\n\t\t\treadonly kind: 'pattern';\n\t\t\treadonly pattern: RegExp;\n\t\t\treadonly replacement?: string;\n\t };\n\nconst DEFAULT_REPLACEMENT = '<redacted>';\n\nconst escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n\n/** A non-global pattern redacts only its FIRST match — every later\n * occurrence leaks. Force the global flag so all occurrences are\n * redacted; clone (don't mutate the caller's RegExp) and preserve any\n * unicode flag. Already-global patterns pass through untouched so we\n * don't double-apply. */\nconst asGlobalPattern = (pattern: RegExp): RegExp =>\n\tpattern.global ? pattern : new RegExp(pattern.source, `${pattern.flags}g`);\n\nexport const redactText = (text: string, rules: ReadonlyArray<RedactionRule>): string => {\n\tlet next = text;\n\tfor (const rule of rules) {\n\t\tconst replacement = rule.replacement ?? DEFAULT_REPLACEMENT;\n\t\tif (rule.kind === 'literal') {\n\t\t\tif (rule.value.length === 0) continue;\n\t\t\tnext = next.replace(new RegExp(escapeRegExp(rule.value), 'gu'), replacement);\n\t\t} else {\n\t\t\tnext = next.replace(asGlobalPattern(rule.pattern), replacement);\n\t\t}\n\t}\n\treturn next;\n};\n\nexport const redactValue = (\n\tvalue: unknown,\n\trules: ReadonlyArray<RedactionRule>,\n\tvisited: WeakSet<object> = new WeakSet(),\n): unknown => {\n\tif (typeof value === 'string') return redactText(value, rules);\n\tif (typeof value !== 'object' || value === null) return value;\n\tif (visited.has(value)) return value;\n\tvisited.add(value);\n\tif (Array.isArray(value)) return value.map((entry) => redactValue(entry, rules, visited));\n\tconst out: Record<string, unknown> = {};\n\tfor (const [key, entry] of Object.entries(value)) {\n\t\tout[key] = redactValue(entry, rules, visited);\n\t}\n\treturn out;\n};\n"],"mappings":";;;;;;AAmBA,MAAM,sBAAsB;AAE5B,MAAM,gBAAgB,UAA0B,MAAM,QAAQ,wBAAwB,
|
|
1
|
+
{"version":3,"file":"redaction.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/redaction.ts"],"sourcesContent":["// Pure redaction helpers. These back the inline plugin-level redaction\n// in seal (`redactMasterKey`) and wallet (`redactToken`), and the\n// cascade-formatter's `safeJson` field scrubbing. There is intentionally\n// no engine-wide Redactor service: plugins that surface secret-bearing\n// output defend themselves at the point of emission with a local rule\n// set, which keeps the rules co-located with the thing they protect.\n\nexport type RedactionRule =\n\t| {\n\t\t\treadonly kind: 'literal';\n\t\t\treadonly value: string;\n\t\t\treadonly replacement?: string;\n\t }\n\t| {\n\t\t\treadonly kind: 'pattern';\n\t\t\treadonly pattern: RegExp;\n\t\t\treadonly replacement?: string;\n\t };\n\nconst DEFAULT_REPLACEMENT = '<redacted>';\n\nconst escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n\n/** A non-global pattern redacts only its FIRST match — every later\n * occurrence leaks. Force the global flag so all occurrences are\n * redacted; clone (don't mutate the caller's RegExp) and preserve any\n * unicode flag. Already-global patterns pass through untouched so we\n * don't double-apply. */\nconst asGlobalPattern = (pattern: RegExp): RegExp =>\n\tpattern.global ? pattern : new RegExp(pattern.source, `${pattern.flags}g`);\n\nexport const redactText = (text: string, rules: ReadonlyArray<RedactionRule>): string => {\n\tlet next = text;\n\tfor (const rule of rules) {\n\t\tconst replacement = rule.replacement ?? DEFAULT_REPLACEMENT;\n\t\tif (rule.kind === 'literal') {\n\t\t\tif (rule.value.length === 0) continue;\n\t\t\tnext = next.replace(new RegExp(escapeRegExp(rule.value), 'gu'), replacement);\n\t\t} else {\n\t\t\tnext = next.replace(asGlobalPattern(rule.pattern), replacement);\n\t\t}\n\t}\n\treturn next;\n};\n\nexport const redactValue = (\n\tvalue: unknown,\n\trules: ReadonlyArray<RedactionRule>,\n\tvisited: WeakSet<object> = new WeakSet(),\n): unknown => {\n\tif (typeof value === 'string') return redactText(value, rules);\n\tif (typeof value !== 'object' || value === null) return value;\n\tif (visited.has(value)) return value;\n\tvisited.add(value);\n\tif (Array.isArray(value)) return value.map((entry) => redactValue(entry, rules, visited));\n\tconst out: Record<string, unknown> = {};\n\tfor (const [key, entry] of Object.entries(value)) {\n\t\tout[key] = redactValue(entry, rules, visited);\n\t}\n\treturn out;\n};\n"],"mappings":";;;;;;AAmBA,MAAM,sBAAsB;AAE5B,MAAM,gBAAgB,UAA0B,MAAM,QAAQ,wBAAwB,MAAM;;;;;;AAO5F,MAAM,mBAAmB,YACxB,QAAQ,SAAS,UAAU,IAAI,OAAO,QAAQ,QAAQ,GAAG,QAAQ,MAAM,EAAE;AAE1E,MAAa,cAAc,MAAc,UAAgD;CACxF,IAAI,OAAO;CACX,KAAK,MAAM,QAAQ,OAAO;EACzB,MAAM,cAAc,KAAK,eAAe;EACxC,IAAI,KAAK,SAAS,WAAW;GAC5B,IAAI,KAAK,MAAM,WAAW,GAAG;GAC7B,OAAO,KAAK,QAAQ,IAAI,OAAO,aAAa,KAAK,KAAK,GAAG,IAAI,GAAG,WAAW;EAC5E,OACC,OAAO,KAAK,QAAQ,gBAAgB,KAAK,OAAO,GAAG,WAAW;CAEhE;CACA,OAAO;AACR;AAEA,MAAa,eACZ,OACA,OACA,0BAA2B,IAAI,QAAQ,MAC1B;CACb,IAAI,OAAO,UAAU,UAAU,OAAO,WAAW,OAAO,KAAK;CAC7D,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,IAAI,QAAQ,IAAI,KAAK,GAAG,OAAO;CAC/B,QAAQ,IAAI,KAAK;CACjB,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,UAAU,YAAY,OAAO,OAAO,OAAO,CAAC;CACxF,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAC9C,IAAI,OAAO,YAAY,OAAO,OAAO,OAAO;CAE7C,OAAO;AACR"}
|
|
@@ -39,8 +39,8 @@ const drainObserved = (stream, observer) => {
|
|
|
39
39
|
* non-zero).
|
|
40
40
|
*
|
|
41
41
|
* One function. One error class. Stream observers are opt-in via
|
|
42
|
-
* `opts.onStdoutLine` / `opts.onStderrLine
|
|
43
|
-
* non-streaming
|
|
42
|
+
* `opts.onStdoutLine` / `opts.onStderrLine`; the streaming and
|
|
43
|
+
* non-streaming paths share this single function.
|
|
44
44
|
*/
|
|
45
45
|
const capture = (spawner, cmd, opts) => {
|
|
46
46
|
const op = opOf(cmd, opts?.op);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subprocess-capture.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/subprocess-capture.ts"],"sourcesContent":["// Subprocess output capture — one variant, one error.\n//\n// Architecture § Collapsed: the legacy package shipped per-service\n// per-line docker log sinks and four spawn-and-capture helpers\n// (`captureCommand`, `captureCommandOrFail`, `captureCommandStreaming`,\n// `captureCommandStreamingOrFail`); this module collapses them to one\n// shape parameterised by options.\n//\n// Surface:\n//\n// capture(spawner, cmd, opts) -> Effect<CaptureResult, CaptureError>\n//\n// - Always returns `CaptureResult` for zero AND non-zero exits when\n// `opts.nonZeroIsFailure` is unset (or `false`). The caller decides.\n// - With `opts.nonZeroIsFailure: true` the Effect fails with\n// `CaptureError` carrying `exitCode` on non-zero exit.\n// - With `opts.onStdoutLine` set, each stdout line is emitted to the\n// callback as it arrives (line-buffered across chunk boundaries).\n// The full stdout is still returned in `CaptureResult.stdout` so\n// the error envelope downstream stays uniform.\n// - With `opts.onStderrLine` set, same shape for stderr (motivated\n// by the L1 per-line sink that promotes WARN/ERROR markers — see\n// architecture L1 § \"Shared per-line streaming sink\").\n//\n// One error class for every failure mode (spawner failure, non-zero\n// exit when promoted). The `op` tag attributes the failure to a\n// specific subprocess invocation; downstream wrappers route this into\n// their own envelopes via `Effect.mapError`.\n\nimport { Effect, Stream } from 'effect';\nimport { Data } from 'effect';\nimport type { ChildProcess } from 'effect/unstable/process';\nimport { ChildProcessSpawner } from 'effect/unstable/process';\n\nimport { splitUtf8Lines } from './process-lines.ts';\n\n// -----------------------------------------------------------------------------\n// Single error class\n// -----------------------------------------------------------------------------\n\n/**\n * The one subprocess-capture error class. Two failure modes:\n *\n * (a) Spawner failure (binary missing, fork limit, pipe setup). The\n * `cause` field carries the spawner's raw failure; `exitCode` is\n * undefined.\n * (b) Non-zero exit when `opts.nonZeroIsFailure` was true. The\n * `exitCode`, `stdout`, `stderr` fields are populated;\n * `cause` is undefined.\n *\n * Downstream wrappers (plugins) wrap this with their own envelopes via\n * `Effect.mapError`. The `op` field carries through so the failure\n * attribution survives.\n */\nexport class CaptureError extends Data.TaggedError('CaptureError')<{\n\treadonly op: string;\n\treadonly exitCode?: number;\n\treadonly stdout: string;\n\treadonly stderr: string;\n\treadonly cause?: unknown;\n}> {}\n\n// -----------------------------------------------------------------------------\n// Public types\n// -----------------------------------------------------------------------------\n\nexport interface CaptureResult {\n\treadonly exitCode: number;\n\treadonly stdout: string;\n\treadonly stderr: string;\n}\n\nexport interface CaptureOptions {\n\t/**\n\t * Caller-supplied tag identifying the subprocess invocation. Embedded\n\t * in any `CaptureError` so downstream envelopes have stable\n\t * attribution. Defaults to the command's `command` field when absent.\n\t */\n\treadonly op?: string;\n\t/**\n\t * Maximum stderr bytes preserved on a captured-error path. Past the\n\t * limit, the surplus is dropped and `…[truncated]` is appended.\n\t * Default 500 (tightest of the historical policies; common case is\n\t * a TUI row that must fit one line).\n\t */\n\treadonly stderrTruncate?: number;\n\t/**\n\t * Maximum stdout bytes preserved on a captured-error path. Default\n\t * `Infinity` — most callers want full stdout for parsing.\n\t */\n\treadonly stdoutTruncate?: number;\n\t/**\n\t * Promote a non-zero exit code into a `CaptureError`. The error\n\t * carries the captured stdout/stderr verbatim. Default `false` (the\n\t * caller decides).\n\t */\n\treadonly nonZeroIsFailure?: boolean;\n\t/**\n\t * Per-line stdout observer. Lines are buffered across chunk boundaries\n\t * via `Stream.splitLines`. Callback errors are ignored (the capture\n\t * must never abort because narration failed).\n\t */\n\treadonly onStdoutLine?: (line: string) => Effect.Effect<void>;\n\t/**\n\t * Per-line stderr observer. Same semantics as `onStdoutLine`. The L1\n\t * shared per-line sink (level promotion: stderr lines containing\n\t * `WARN`/`ERROR` markers) routes through here.\n\t */\n\treadonly onStderrLine?: (line: string) => Effect.Effect<void>;\n}\n\n// -----------------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------------\n\nconst DEFAULT_STDERR_TRUNC = 500;\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\nconst truncateTo = (text: string, limit: number): string => {\n\tif (!Number.isFinite(limit) || text.length <= limit) return text;\n\treturn `${text.slice(0, limit)}…[truncated]`;\n};\n\nconst opOf = (cmd: ChildProcess.Command, override: string | undefined): string => {\n\tif (override !== undefined) return override;\n\tif (cmd._tag === 'StandardCommand') return cmd.command;\n\treturn 'piped-command';\n};\n\ntype SpawnerService = ReturnType<typeof ChildProcessSpawner.make>;\n\nconst drainObserved = <E>(\n\tstream: Stream.Stream<Uint8Array, E>,\n\tobserver: ((line: string) => Effect.Effect<void>) | undefined,\n): Effect.Effect<string, E> => {\n\tif (!observer) {\n\t\treturn Stream.mkString(Stream.decodeText(stream));\n\t}\n\tconst lines = splitUtf8Lines(stream).pipe(\n\t\tStream.tap((line) => observer(line).pipe(Effect.ignore)),\n\t);\n\t// Re-fold lines into the full stdout/stderr string. We don't append\n\t// a trailing newline — mirrors the non-observed path so the captured\n\t// error shape is uniform regardless of whether an observer was set.\n\treturn Stream.runFold(\n\t\tlines,\n\t\t() => '',\n\t\t(acc, line) => (acc.length === 0 ? line : `${acc}\\n${line}`),\n\t);\n};\n\n// -----------------------------------------------------------------------------\n// The one capture function\n// -----------------------------------------------------------------------------\n\n/**\n * Spawn `cmd`, drain stdout + stderr + exit code concurrently. Returns\n * the captured result, or fails with `CaptureError` if the spawner\n * itself failed (or if `nonZeroIsFailure` was set and the child exited\n * non-zero).\n *\n * One function. One error class. Stream observers are opt-in via\n * `opts.onStdoutLine` / `opts.onStderrLine` — the streaming and\n * non-streaming variants are no longer separate functions.\n */\nexport const capture = (\n\tspawner: SpawnerService,\n\tcmd: ChildProcess.Command,\n\topts?: CaptureOptions,\n): Effect.Effect<CaptureResult, CaptureError> => {\n\tconst op = opOf(cmd, opts?.op);\n\tconst stderrLimit = opts?.stderrTruncate ?? DEFAULT_STDERR_TRUNC;\n\tconst stdoutLimit = opts?.stdoutTruncate ?? Infinity;\n\tconst nonZeroIsFailure = opts?.nonZeroIsFailure ?? false;\n\tconst onStdoutLine = opts?.onStdoutLine;\n\tconst onStderrLine = opts?.onStderrLine;\n\tconst mapSpawn = (cause: unknown): CaptureError =>\n\t\tnew CaptureError({ op, stdout: '', stderr: '', cause });\n\n\treturn Effect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tconst handle = yield* spawner.spawn(cmd).pipe(Effect.mapError(mapSpawn));\n\t\t\tconst [stdoutText, stderrText, code] = yield* Effect.all(\n\t\t\t\t[\n\t\t\t\t\tdrainObserved(handle.stdout, onStdoutLine).pipe(Effect.mapError(mapSpawn)),\n\t\t\t\t\tdrainObserved(handle.stderr, onStderrLine).pipe(Effect.mapError(mapSpawn)),\n\t\t\t\t\thandle.exitCode.pipe(Effect.mapError(mapSpawn)),\n\t\t\t\t],\n\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t);\n\t\t\tconst result: CaptureResult = {\n\t\t\t\texitCode: code as number,\n\t\t\t\tstdout: truncateTo(stdoutText, stdoutLimit),\n\t\t\t\tstderr: truncateTo(stderrText, stderrLimit),\n\t\t\t};\n\t\t\tif (nonZeroIsFailure && result.exitCode !== 0) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew CaptureError({\n\t\t\t\t\t\top,\n\t\t\t\t\t\texitCode: result.exitCode,\n\t\t\t\t\t\tstdout: result.stdout,\n\t\t\t\t\t\tstderr: result.stderr,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn result;\n\t\t}),\n\t);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsDA,IAAa,eAAb,cAAkC,KAAK,YAAY,eAAe,CAM/D;AAuDH,MAAM,uBAAuB;AAM7B,MAAM,cAAc,MAAc,UAA0B;AAC3D,KAAI,CAAC,OAAO,SAAS,MAAM,IAAI,KAAK,UAAU,MAAO,QAAO;AAC5D,QAAO,GAAG,KAAK,MAAM,GAAG,MAAM,CAAC;;AAGhC,MAAM,QAAQ,KAA2B,aAAyC;AACjF,KAAI,aAAa,KAAA,EAAW,QAAO;AACnC,KAAI,IAAI,SAAS,kBAAmB,QAAO,IAAI;AAC/C,QAAO;;AAKR,MAAM,iBACL,QACA,aAC8B;AAC9B,KAAI,CAAC,SACJ,QAAO,OAAO,SAAS,OAAO,WAAW,OAAO,CAAC;CAElD,MAAM,QAAQ,eAAe,OAAO,CAAC,KACpC,OAAO,KAAK,SAAS,SAAS,KAAK,CAAC,KAAK,OAAO,OAAO,CAAC,CACxD;AAID,QAAO,OAAO,QACb,aACM,KACL,KAAK,SAAU,IAAI,WAAW,IAAI,OAAO,GAAG,IAAI,IAAI,OACrD;;;;;;;;;;;;AAiBF,MAAa,WACZ,SACA,KACA,SACgD;CAChD,MAAM,KAAK,KAAK,KAAK,MAAM,GAAG;CAC9B,MAAM,cAAc,MAAM,kBAAkB;CAC5C,MAAM,cAAc,MAAM,kBAAkB;CAC5C,MAAM,mBAAmB,MAAM,oBAAoB;CACnD,MAAM,eAAe,MAAM;CAC3B,MAAM,eAAe,MAAM;CAC3B,MAAM,YAAY,UACjB,IAAI,aAAa;EAAE;EAAI,QAAQ;EAAI,QAAQ;EAAI;EAAO,CAAC;AAExD,QAAO,OAAO,OACb,OAAO,IAAI,aAAa;EACvB,MAAM,SAAS,OAAO,QAAQ,MAAM,IAAI,CAAC,KAAK,OAAO,SAAS,SAAS,CAAC;EACxE,MAAM,CAAC,YAAY,YAAY,QAAQ,OAAO,OAAO,IACpD;GACC,cAAc,OAAO,QAAQ,aAAa,CAAC,KAAK,OAAO,SAAS,SAAS,CAAC;GAC1E,cAAc,OAAO,QAAQ,aAAa,CAAC,KAAK,OAAO,SAAS,SAAS,CAAC;GAC1E,OAAO,SAAS,KAAK,OAAO,SAAS,SAAS,CAAC;GAC/C,EACD,EAAE,aAAa,aAAa,CAC5B;EACD,MAAM,SAAwB;GAC7B,UAAU;GACV,QAAQ,WAAW,YAAY,YAAY;GAC3C,QAAQ,WAAW,YAAY,YAAY;GAC3C;AACD,MAAI,oBAAoB,OAAO,aAAa,EAC3C,QAAO,OAAO,OAAO,KACpB,IAAI,aAAa;GAChB;GACA,UAAU,OAAO;GACjB,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf,CAAC,CACF;AAEF,SAAO;GACN,CACF"}
|
|
1
|
+
{"version":3,"file":"subprocess-capture.mjs","names":[],"sources":["../../../../src/substrate/runtime/observability/subprocess-capture.ts"],"sourcesContent":["// Subprocess output capture — one variant, one error.\n//\n// One spawn-and-capture surface parameterised by options, covering both\n// the fail-on-nonzero and streaming-observer cases that would otherwise\n// be separate helpers.\n//\n// Surface:\n//\n// capture(spawner, cmd, opts) -> Effect<CaptureResult, CaptureError>\n//\n// - Always returns `CaptureResult` for zero AND non-zero exits when\n// `opts.nonZeroIsFailure` is unset (or `false`). The caller decides.\n// - With `opts.nonZeroIsFailure: true` the Effect fails with\n// `CaptureError` carrying `exitCode` on non-zero exit.\n// - With `opts.onStdoutLine` set, each stdout line is emitted to the\n// callback as it arrives (line-buffered across chunk boundaries).\n// The full stdout is still returned in `CaptureResult.stdout` so\n// the error envelope downstream stays uniform.\n// - With `opts.onStderrLine` set, same shape for stderr (motivated\n// by the L1 per-line sink that promotes WARN/ERROR markers — see\n// architecture L1 § \"Shared per-line streaming sink\").\n//\n// One error class for every failure mode (spawner failure, non-zero\n// exit when promoted). The `op` tag attributes the failure to a\n// specific subprocess invocation; downstream wrappers route this into\n// their own envelopes via `Effect.mapError`.\n\nimport { Effect, Stream } from 'effect';\nimport { Data } from 'effect';\nimport type { ChildProcess } from 'effect/unstable/process';\nimport { ChildProcessSpawner } from 'effect/unstable/process';\n\nimport { splitUtf8Lines } from './process-lines.ts';\n\n// -----------------------------------------------------------------------------\n// Single error class\n// -----------------------------------------------------------------------------\n\n/**\n * The one subprocess-capture error class. Two failure modes:\n *\n * (a) Spawner failure (binary missing, fork limit, pipe setup). The\n * `cause` field carries the spawner's raw failure; `exitCode` is\n * undefined.\n * (b) Non-zero exit when `opts.nonZeroIsFailure` was true. The\n * `exitCode`, `stdout`, `stderr` fields are populated;\n * `cause` is undefined.\n *\n * Downstream wrappers (plugins) wrap this with their own envelopes via\n * `Effect.mapError`. The `op` field carries through so the failure\n * attribution survives.\n */\nexport class CaptureError extends Data.TaggedError('CaptureError')<{\n\treadonly op: string;\n\treadonly exitCode?: number;\n\treadonly stdout: string;\n\treadonly stderr: string;\n\treadonly cause?: unknown;\n}> {}\n\n// -----------------------------------------------------------------------------\n// Public types\n// -----------------------------------------------------------------------------\n\nexport interface CaptureResult {\n\treadonly exitCode: number;\n\treadonly stdout: string;\n\treadonly stderr: string;\n}\n\nexport interface CaptureOptions {\n\t/**\n\t * Caller-supplied tag identifying the subprocess invocation. Embedded\n\t * in any `CaptureError` so downstream envelopes have stable\n\t * attribution. Defaults to the command's `command` field when absent.\n\t */\n\treadonly op?: string;\n\t/**\n\t * Maximum stderr bytes preserved on a captured-error path. Past the\n\t * limit, the surplus is dropped and `…[truncated]` is appended.\n\t * Default 500 (a tight bound; common case is a TUI row that must fit\n\t * one line).\n\t */\n\treadonly stderrTruncate?: number;\n\t/**\n\t * Maximum stdout bytes preserved on a captured-error path. Default\n\t * `Infinity` — most callers want full stdout for parsing.\n\t */\n\treadonly stdoutTruncate?: number;\n\t/**\n\t * Promote a non-zero exit code into a `CaptureError`. The error\n\t * carries the captured stdout/stderr verbatim. Default `false` (the\n\t * caller decides).\n\t */\n\treadonly nonZeroIsFailure?: boolean;\n\t/**\n\t * Per-line stdout observer. Lines are buffered across chunk boundaries\n\t * via `Stream.splitLines`. Callback errors are ignored (the capture\n\t * must never abort because narration failed).\n\t */\n\treadonly onStdoutLine?: (line: string) => Effect.Effect<void>;\n\t/**\n\t * Per-line stderr observer. Same semantics as `onStdoutLine`. The L1\n\t * shared per-line sink (level promotion: stderr lines containing\n\t * `WARN`/`ERROR` markers) routes through here.\n\t */\n\treadonly onStderrLine?: (line: string) => Effect.Effect<void>;\n}\n\n// -----------------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------------\n\nconst DEFAULT_STDERR_TRUNC = 500;\n\n// -----------------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------------\n\nconst truncateTo = (text: string, limit: number): string => {\n\tif (!Number.isFinite(limit) || text.length <= limit) return text;\n\treturn `${text.slice(0, limit)}…[truncated]`;\n};\n\nconst opOf = (cmd: ChildProcess.Command, override: string | undefined): string => {\n\tif (override !== undefined) return override;\n\tif (cmd._tag === 'StandardCommand') return cmd.command;\n\treturn 'piped-command';\n};\n\ntype SpawnerService = ReturnType<typeof ChildProcessSpawner.make>;\n\nconst drainObserved = <E>(\n\tstream: Stream.Stream<Uint8Array, E>,\n\tobserver: ((line: string) => Effect.Effect<void>) | undefined,\n): Effect.Effect<string, E> => {\n\tif (!observer) {\n\t\treturn Stream.mkString(Stream.decodeText(stream));\n\t}\n\tconst lines = splitUtf8Lines(stream).pipe(\n\t\tStream.tap((line) => observer(line).pipe(Effect.ignore)),\n\t);\n\t// Re-fold lines into the full stdout/stderr string. We don't append\n\t// a trailing newline — mirrors the non-observed path so the captured\n\t// error shape is uniform regardless of whether an observer was set.\n\treturn Stream.runFold(\n\t\tlines,\n\t\t() => '',\n\t\t(acc, line) => (acc.length === 0 ? line : `${acc}\\n${line}`),\n\t);\n};\n\n// -----------------------------------------------------------------------------\n// The one capture function\n// -----------------------------------------------------------------------------\n\n/**\n * Spawn `cmd`, drain stdout + stderr + exit code concurrently. Returns\n * the captured result, or fails with `CaptureError` if the spawner\n * itself failed (or if `nonZeroIsFailure` was set and the child exited\n * non-zero).\n *\n * One function. One error class. Stream observers are opt-in via\n * `opts.onStdoutLine` / `opts.onStderrLine`; the streaming and\n * non-streaming paths share this single function.\n */\nexport const capture = (\n\tspawner: SpawnerService,\n\tcmd: ChildProcess.Command,\n\topts?: CaptureOptions,\n): Effect.Effect<CaptureResult, CaptureError> => {\n\tconst op = opOf(cmd, opts?.op);\n\tconst stderrLimit = opts?.stderrTruncate ?? DEFAULT_STDERR_TRUNC;\n\tconst stdoutLimit = opts?.stdoutTruncate ?? Infinity;\n\tconst nonZeroIsFailure = opts?.nonZeroIsFailure ?? false;\n\tconst onStdoutLine = opts?.onStdoutLine;\n\tconst onStderrLine = opts?.onStderrLine;\n\tconst mapSpawn = (cause: unknown): CaptureError =>\n\t\tnew CaptureError({ op, stdout: '', stderr: '', cause });\n\n\treturn Effect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tconst handle = yield* spawner.spawn(cmd).pipe(Effect.mapError(mapSpawn));\n\t\t\tconst [stdoutText, stderrText, code] = yield* Effect.all(\n\t\t\t\t[\n\t\t\t\t\tdrainObserved(handle.stdout, onStdoutLine).pipe(Effect.mapError(mapSpawn)),\n\t\t\t\t\tdrainObserved(handle.stderr, onStderrLine).pipe(Effect.mapError(mapSpawn)),\n\t\t\t\t\thandle.exitCode.pipe(Effect.mapError(mapSpawn)),\n\t\t\t\t],\n\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t);\n\t\t\tconst result: CaptureResult = {\n\t\t\t\texitCode: code as number,\n\t\t\t\tstdout: truncateTo(stdoutText, stdoutLimit),\n\t\t\t\tstderr: truncateTo(stderrText, stderrLimit),\n\t\t\t};\n\t\t\tif (nonZeroIsFailure && result.exitCode !== 0) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew CaptureError({\n\t\t\t\t\t\top,\n\t\t\t\t\t\texitCode: result.exitCode,\n\t\t\t\t\t\tstdout: result.stdout,\n\t\t\t\t\t\tstderr: result.stderr,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn result;\n\t\t}),\n\t);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoDA,IAAa,eAAb,cAAkC,KAAK,YAAY,cAAc,CAAC,CAM/D,CAAC;AAuDJ,MAAM,uBAAuB;AAM7B,MAAM,cAAc,MAAc,UAA0B;CAC3D,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,KAAK,UAAU,OAAO,OAAO;CAC5D,OAAO,GAAG,KAAK,MAAM,GAAG,KAAK,EAAE;AAChC;AAEA,MAAM,QAAQ,KAA2B,aAAyC;CACjF,IAAI,aAAa,KAAA,GAAW,OAAO;CACnC,IAAI,IAAI,SAAS,mBAAmB,OAAO,IAAI;CAC/C,OAAO;AACR;AAIA,MAAM,iBACL,QACA,aAC8B;CAC9B,IAAI,CAAC,UACJ,OAAO,OAAO,SAAS,OAAO,WAAW,MAAM,CAAC;CAEjD,MAAM,QAAQ,eAAe,MAAM,CAAC,CAAC,KACpC,OAAO,KAAK,SAAS,SAAS,IAAI,CAAC,CAAC,KAAK,OAAO,MAAM,CAAC,CACxD;CAIA,OAAO,OAAO,QACb,aACM,KACL,KAAK,SAAU,IAAI,WAAW,IAAI,OAAO,GAAG,IAAI,IAAI,MACtD;AACD;;;;;;;;;;;AAgBA,MAAa,WACZ,SACA,KACA,SACgD;CAChD,MAAM,KAAK,KAAK,KAAK,MAAM,EAAE;CAC7B,MAAM,cAAc,MAAM,kBAAkB;CAC5C,MAAM,cAAc,MAAM,kBAAkB;CAC5C,MAAM,mBAAmB,MAAM,oBAAoB;CACnD,MAAM,eAAe,MAAM;CAC3B,MAAM,eAAe,MAAM;CAC3B,MAAM,YAAY,UACjB,IAAI,aAAa;EAAE;EAAI,QAAQ;EAAI,QAAQ;EAAI;CAAM,CAAC;CAEvD,OAAO,OAAO,OACb,OAAO,IAAI,aAAa;EACvB,MAAM,SAAS,OAAO,QAAQ,MAAM,GAAG,CAAC,CAAC,KAAK,OAAO,SAAS,QAAQ,CAAC;EACvE,MAAM,CAAC,YAAY,YAAY,QAAQ,OAAO,OAAO,IACpD;GACC,cAAc,OAAO,QAAQ,YAAY,CAAC,CAAC,KAAK,OAAO,SAAS,QAAQ,CAAC;GACzE,cAAc,OAAO,QAAQ,YAAY,CAAC,CAAC,KAAK,OAAO,SAAS,QAAQ,CAAC;GACzE,OAAO,SAAS,KAAK,OAAO,SAAS,QAAQ,CAAC;EAC/C,GACA,EAAE,aAAa,YAAY,CAC5B;EACA,MAAM,SAAwB;GAC7B,UAAU;GACV,QAAQ,WAAW,YAAY,WAAW;GAC1C,QAAQ,WAAW,YAAY,WAAW;EAC3C;EACA,IAAI,oBAAoB,OAAO,aAAa,GAC3C,OAAO,OAAO,OAAO,KACpB,IAAI,aAAa;GAChB;GACA,UAAU,OAAO;GACjB,QAAQ,OAAO;GACf,QAAQ,OAAO;EAChB,CAAC,CACF;EAED,OAAO;CACR,CAAC,CACF;AACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passthrough-or-wrap.mjs","names":[],"sources":["../../../src/substrate/runtime/passthrough-or-wrap.ts"],"sourcesContent":["// passthroughOrWrap — collapse the recurring \"let known tagged errors\n// through; wrap everything else into one canonical plugin-side error\"\n// shape that every plugin's outer pipeline implements.\n//\n// Plugins routinely compose `Effect.gen` bodies whose aggregate `E`\n// channel includes substrate-side errors with shapes the plugin author\n// doesn't statically know (artifact-publisher produce bodies, dependency\n// reads). `Effect.catchTags` requires a statically-known tagged union, so\n// the recurring pattern is either a long catchTags chain that re-fails\n// each known tag, or a runtime `_tag` discriminator check that walks the\n// known list and wraps the rest. Both shapes spell out the same logic in\n// 10-20 lines per plugin.\n//\n// This helper centralizes that: caller supplies the list of known tagged\n// `_tag` literals plus a `mkUnknown` constructor; helper returns an\n// Effect operator that lets known errors through unchanged (typed E\n// channel preserved at the call site) and wraps anything else through\n// `mkUnknown`.\n//\n// Distilled-doc § Invariants: already-typed errors must NOT be re-wrapped\n// by the catch-all unknown handler. Putting this in one place enforces\n// that invariant by construction.\n\nimport { Effect } from 'effect';\n\n/** Internal: build the operator from a known-tag set + unknown\n * wrapper. Shared by both the direct and curried entry points. */\nconst buildPassthroughOrWrap =\n\t<KnownE extends { readonly _tag: string }, UnknownE>(\n\t\tknownTags: ReadonlyArray<KnownE['_tag']>,\n\t\tmkUnknown: (cause: unknown) => UnknownE,\n\t) =>\n\t<A, R, EIn = KnownE>(
|
|
1
|
+
{"version":3,"file":"passthrough-or-wrap.mjs","names":[],"sources":["../../../src/substrate/runtime/passthrough-or-wrap.ts"],"sourcesContent":["// passthroughOrWrap — collapse the recurring \"let known tagged errors\n// through; wrap everything else into one canonical plugin-side error\"\n// shape that every plugin's outer pipeline implements.\n//\n// Plugins routinely compose `Effect.gen` bodies whose aggregate `E`\n// channel includes substrate-side errors with shapes the plugin author\n// doesn't statically know (artifact-publisher produce bodies, dependency\n// reads). `Effect.catchTags` requires a statically-known tagged union, so\n// the recurring pattern is either a long catchTags chain that re-fails\n// each known tag, or a runtime `_tag` discriminator check that walks the\n// known list and wraps the rest. Both shapes spell out the same logic in\n// 10-20 lines per plugin.\n//\n// This helper centralizes that: caller supplies the list of known tagged\n// `_tag` literals plus a `mkUnknown` constructor; helper returns an\n// Effect operator that lets known errors through unchanged (typed E\n// channel preserved at the call site) and wraps anything else through\n// `mkUnknown`.\n//\n// Distilled-doc § Invariants: already-typed errors must NOT be re-wrapped\n// by the catch-all unknown handler. Putting this in one place enforces\n// that invariant by construction.\n\nimport { Effect } from 'effect';\n\n/** Internal: build the operator from a known-tag set + unknown\n * wrapper. Shared by both the direct and curried entry points. */\nconst buildPassthroughOrWrap =\n\t<KnownE extends { readonly _tag: string }, UnknownE>(\n\t\tknownTags: ReadonlyArray<KnownE['_tag']>,\n\t\tmkUnknown: (cause: unknown) => UnknownE,\n\t) =>\n\t<A, R, EIn = KnownE>(self: Effect.Effect<A, EIn, R>): Effect.Effect<A, KnownE | UnknownE, R> => {\n\t\tconst known = new Set<string>(knownTags);\n\t\treturn self.pipe(\n\t\t\tEffect.catch((err: unknown): Effect.Effect<never, KnownE | UnknownE> => {\n\t\t\t\tif (\n\t\t\t\t\ttypeof err === 'object' &&\n\t\t\t\t\terr !== null &&\n\t\t\t\t\t'_tag' in err &&\n\t\t\t\t\ttypeof (err as { _tag: unknown })._tag === 'string' &&\n\t\t\t\t\tknown.has((err as { _tag: string })._tag)\n\t\t\t\t) {\n\t\t\t\t\treturn Effect.fail(err as KnownE);\n\t\t\t\t}\n\t\t\t\treturn Effect.fail(mkUnknown(err));\n\t\t\t}),\n\t\t);\n\t};\n\n/** Operator that catches every error: lets through tagged errors whose\n * `_tag` is in `knownTags`, wraps everything else through `mkUnknown`.\n *\n * Use this for the outer plugin-pipeline catch where the aggregate E\n * channel mixes plugin-typed errors with substrate-side unknowns. The\n * output E channel is `KnownE | UnknownE` — callers parameterize\n * `KnownE` at the call site (the runtime `_tag` check is the\n * load-bearing part; the type parameter is documentation + a downstream\n * narrowing aid).\n *\n * Two call shapes:\n * - Direct: `passthroughOrWrap<KnownE, UnknownE>(tags, mkUnknown)` —\n * explicit on both type params.\n * - Curried: `passthroughOrWrap.for<KnownE>()(tags, mkUnknown)` — pin\n * `KnownE` up front so TS infers `UnknownE` from `mkUnknown`'s\n * return type at the second call. Preferred when `KnownE` is a\n * plugin-side union that doesn't appear in the args. */\nexport const passthroughOrWrap: {\n\t<KnownE extends { readonly _tag: string }, UnknownE>(\n\t\tknownTags: ReadonlyArray<KnownE['_tag']>,\n\t\tmkUnknown: (cause: unknown) => UnknownE,\n\t): <A, R, EIn = KnownE>(self: Effect.Effect<A, EIn, R>) => Effect.Effect<A, KnownE | UnknownE, R>;\n\tfor: <KnownE extends { readonly _tag: string }>() => <UnknownE>(\n\t\tknownTags: ReadonlyArray<KnownE['_tag']>,\n\t\tmkUnknown: (cause: unknown) => UnknownE,\n\t) => <A, R, EIn = KnownE>(\n\t\tself: Effect.Effect<A, EIn, R>,\n\t) => Effect.Effect<A, KnownE | UnknownE, R>;\n} = Object.assign(buildPassthroughOrWrap, {\n\tfor:\n\t\t<KnownE extends { readonly _tag: string }>() =>\n\t\t<UnknownE>(knownTags: ReadonlyArray<KnownE['_tag']>, mkUnknown: (cause: unknown) => UnknownE) =>\n\t\t\tbuildPassthroughOrWrap<KnownE, UnknownE>(knownTags, mkUnknown),\n});\n"],"mappings":";;;;AA2BA,MAAM,0BAEJ,WACA,eAEoB,SAA2E;CAC/F,MAAM,QAAQ,IAAI,IAAY,SAAS;CACvC,OAAO,KAAK,KACX,OAAO,OAAO,QAA0D;EACvE,IACC,OAAO,QAAQ,YACf,QAAQ,QACR,UAAU,OACV,OAAQ,IAA0B,SAAS,YAC3C,MAAM,IAAK,IAAyB,IAAI,GAExC,OAAO,OAAO,KAAK,GAAa;EAEjC,OAAO,OAAO,KAAK,UAAU,GAAG,CAAC;CAClC,CAAC,CACF;AACD;;;;;;;;;;;;;;;;;;AAmBD,MAAa,oBAWT,OAAO,OAAO,wBAAwB,EACzC,YAEY,WAA0C,cACpD,uBAAyC,WAAW,SAAS,EAChE,CAAC"}
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { Context, Effect, Layer, Path } from "effect";
|
|
2
2
|
//#region src/substrate/runtime/paths.ts
|
|
3
|
+
/** The single per-stack directory segment. Every per-stack subtree lives
|
|
4
|
+
* under `<root>/stacks/<stack>/...`; this is the one place that literal
|
|
5
|
+
* is authored. */
|
|
6
|
+
const STACKS_SEGMENT = "stacks";
|
|
7
|
+
/**
|
|
8
|
+
* The ONE pure composer for the `<root>/stacks/<stack>/...` path shape.
|
|
9
|
+
*
|
|
10
|
+
* NO base is baked in — the caller supplies BOTH the `join` strategy and
|
|
11
|
+
* the `root`, so the same shape is authored once and reused with different
|
|
12
|
+
* bases:
|
|
13
|
+
*
|
|
14
|
+
* - the substrate roots at `RuntimeRoot` (`~/.devstack` by default):
|
|
15
|
+
* `stackSubpath(Path.join, runtimeRoot, identity.stack, 'cache')`.
|
|
16
|
+
* - codegen roots at the app source tree:
|
|
17
|
+
* `stackSubpath(node.join, join(appRoot, '.devstack'), stack, 'generated')`.
|
|
18
|
+
*
|
|
19
|
+
* Threading `join` (rather than importing one) keeps this purely string
|
|
20
|
+
* algebra: the substrate passes Effect's `Path` service `join` (Windows-
|
|
21
|
+
* correct separators, defense-in-depth) and codegen passes `node:path`'s
|
|
22
|
+
* `join` — neither implementation is baked in here.
|
|
23
|
+
*/
|
|
24
|
+
const stackSubpath = (join, root, stack, ...rest) => join(root, STACKS_SEGMENT, stack, ...rest);
|
|
3
25
|
var RuntimeRoot = class extends Context.Service()("@devstack/substrate/RuntimeRoot") {};
|
|
4
26
|
/** Build a `RuntimeRoot` layer pinned to a literal path. */
|
|
5
27
|
const layerRuntimeRoot = (root) => Layer.succeed(RuntimeRoot)({ root });
|
|
@@ -30,12 +52,12 @@ const layerStackPaths = Layer.effect(StackPathsService, Effect.gen(function* ()
|
|
|
30
52
|
const { root } = yield* RuntimeRoot;
|
|
31
53
|
const identity = yield* IdentityContext;
|
|
32
54
|
const path = yield* Path.Path;
|
|
33
|
-
const stackRoot = path.join
|
|
55
|
+
const stackRoot = stackSubpath(path.join, root, identity.stack);
|
|
34
56
|
const cacheDir = path.join(stackRoot, "cache");
|
|
35
57
|
const cacheNamespaceDir = (namespace) => path.join(cacheDir, namespace);
|
|
36
|
-
const cacheChainDir = (namespace,
|
|
37
|
-
const cacheEntry = (namespace,
|
|
38
|
-
const dir = cacheChainDir(namespace,
|
|
58
|
+
const cacheChainDir = (namespace, chainId) => path.join(cacheNamespaceDir(namespace), chainId);
|
|
59
|
+
const cacheEntry = (namespace, chainId, contentHash) => {
|
|
60
|
+
const dir = cacheChainDir(namespace, chainId);
|
|
39
61
|
return {
|
|
40
62
|
dir,
|
|
41
63
|
file: path.join(dir, `${contentHash}.json`)
|
|
@@ -43,20 +65,16 @@ const layerStackPaths = Layer.effect(StackPathsService, Effect.gen(function* ()
|
|
|
43
65
|
};
|
|
44
66
|
return StackPathsService.of({
|
|
45
67
|
stackRoot,
|
|
46
|
-
stateFile: path.join(stackRoot, "state.json"),
|
|
47
|
-
stateLockHint: path.join(stackRoot, "state.json.lock"),
|
|
48
68
|
cacheDir,
|
|
49
69
|
snapshotDir: path.join(stackRoot, "snapshots"),
|
|
50
70
|
stackLockFile: path.join(stackRoot, "stack.lock"),
|
|
51
71
|
rosterFile: path.join(stackRoot, "roster.json"),
|
|
52
|
-
containerClaimsFile: path.join(stackRoot, "container-claims.json"),
|
|
53
|
-
snapshotReservationFile: path.join(stackRoot, "snapshot.reservation"),
|
|
54
72
|
cacheEntry,
|
|
55
73
|
cacheChainDir,
|
|
56
74
|
cacheNamespaceDir
|
|
57
75
|
});
|
|
58
76
|
}));
|
|
59
77
|
//#endregion
|
|
60
|
-
export { IdentityContext, RuntimeRoot, StackPathsService, layerIdentity, layerRuntimeRoot, layerStackPaths };
|
|
78
|
+
export { IdentityContext, RuntimeRoot, StackPathsService, layerIdentity, layerRuntimeRoot, layerStackPaths, stackSubpath };
|
|
61
79
|
|
|
62
80
|
//# sourceMappingURL=paths.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.mjs","names":[],"sources":["../../../src/substrate/runtime/paths.ts"],"sourcesContent":["// Unified path resolver.\n//\n// Architecture § \"What's collapsed\" — three path resolvers\n// (
|
|
1
|
+
{"version":3,"file":"paths.mjs","names":[],"sources":["../../../src/substrate/runtime/paths.ts"],"sourcesContent":["// Unified path resolver.\n//\n// Architecture § \"What's collapsed\" — three path resolvers\n// (service-paths, snapshot) consolidate to one. This is\n// the L0 helper every disk-touching subsystem reaches for; nothing\n// else in the runtime package may compose `<root>/stacks/<stack>/...`\n// by hand.\n//\n// The resolver is a typed bundle: given an `Identity` and the\n// `RuntimeRoot`, it returns the closed set of paths the substrate\n// understands. Subsystems that need a sub-path inside one of those\n// (e.g. cache namespace + chainId + content-hash) compose ON TOP of the\n// bundle's `cacheDir`, but they NEVER reach for\n// `path.join(root, 'stacks', stack)` themselves.\n\nimport { Context, Effect, Layer, Path } from 'effect';\n\nimport type { Identity } from '../identity.ts';\n\n/** The single per-stack directory segment. Every per-stack subtree lives\n * under `<root>/stacks/<stack>/...`; this is the one place that literal\n * is authored. */\nconst STACKS_SEGMENT = 'stacks';\n\n/**\n * The ONE pure composer for the `<root>/stacks/<stack>/...` path shape.\n *\n * NO base is baked in — the caller supplies BOTH the `join` strategy and\n * the `root`, so the same shape is authored once and reused with different\n * bases:\n *\n * - the substrate roots at `RuntimeRoot` (`~/.devstack` by default):\n * `stackSubpath(Path.join, runtimeRoot, identity.stack, 'cache')`.\n * - codegen roots at the app source tree:\n * `stackSubpath(node.join, join(appRoot, '.devstack'), stack, 'generated')`.\n *\n * Threading `join` (rather than importing one) keeps this purely string\n * algebra: the substrate passes Effect's `Path` service `join` (Windows-\n * correct separators, defense-in-depth) and codegen passes `node:path`'s\n * `join` — neither implementation is baked in here.\n */\nexport const stackSubpath = (\n\tjoin: (...segments: ReadonlyArray<string>) => string,\n\troot: string,\n\tstack: string,\n\t...rest: ReadonlyArray<string>\n): string => join(root, STACKS_SEGMENT, stack, ...rest);\n\n/**\n * Runtime root — the on-disk base under which every stack's state\n * lives. Defaults to `.devstack`, but is injectable so tests pin to\n * a tempdir and parallel-stack tests pin to per-test prefixes.\n *\n * Held as a tagged service so the rest of the substrate can request\n * it without threading the string through every signature.\n */\nexport interface RuntimeRootShape {\n\treadonly root: string;\n}\n\nexport class RuntimeRoot extends Context.Service<RuntimeRoot, RuntimeRootShape>()(\n\t'@devstack/substrate/RuntimeRoot',\n) {}\n\n/** Build a `RuntimeRoot` layer pinned to a literal path. */\nexport const layerRuntimeRoot = (root: string): Layer.Layer<RuntimeRoot> =>\n\tLayer.succeed(RuntimeRoot)({ root });\n\n/**\n * Identity-bound. Same shape as `substrate/identity.ts`'s `Identity`,\n * but lifted into a Context service so path resolution can yield it\n * without taking it as a function argument.\n *\n * Boot wires this once (Identity is validated up front); the\n * resolver and every consumer reads from this single source.\n */\nexport class IdentityContext extends Context.Service<IdentityContext, Identity>()(\n\t'@devstack/substrate/Identity',\n) {}\n\nexport const layerIdentity = (identity: Identity): Layer.Layer<IdentityContext> =>\n\tLayer.succeed(IdentityContext)(identity);\n\n/**\n * Closed bundle the resolver returns. Three groups of paths:\n *\n * - `stackRoot` and the cross-process artifacts (lock, roster,\n * command channel) live directly under the stack directory.\n * Cross-process safety lives here.\n * - `cacheDir` is the parent directory; the cache subsystem\n * composes `<cacheDir>/<namespace>/<chainId>/<contentHash>.json`\n * via a helper on this resolver. Subsystems do NOT reach into\n * `cacheDir` with their own `path.join` calls.\n */\nexport interface StackPaths {\n\treadonly stackRoot: string;\n\treadonly cacheDir: string;\n\treadonly snapshotDir: string;\n\treadonly stackLockFile: string;\n\treadonly rosterFile: string;\n\t/**\n\t * Helper that composes the cache entry path from cache-key\n\t * components. The substrate folds the components together here so\n\t * that nothing else in the runtime tree builds cache paths.\n\t */\n\treadonly cacheEntry: (\n\t\tnamespace: string,\n\t\tchainId: string,\n\t\tcontentHash: string,\n\t) => { readonly dir: string; readonly file: string };\n\t/** Helper that returns the cache namespace directory for a given\n\t * namespace + chainId — for `readDirectory` enumeration. */\n\treadonly cacheChainDir: (namespace: string, chainId: string) => string;\n\t/** Helper that returns the cache namespace directory (across all\n\t * chains) — for namespace-scoped enumeration. */\n\treadonly cacheNamespaceDir: (namespace: string) => string;\n}\n\n/**\n * The path resolver service. Yields `StackPaths` once per stack —\n * `Effect.cached`-friendly since `Identity` + `RuntimeRoot` are\n * boot-immutable.\n */\nexport class StackPathsService extends Context.Service<StackPathsService, StackPaths>()(\n\t'@devstack/substrate/StackPaths',\n) {}\n\n/**\n * Layer that materializes the resolver from `RuntimeRoot` +\n * `IdentityContext` + the Effect `Path` service. The cache helper\n * uses `Path.join` so OS-correct separators round-trip on Windows\n * (defense-in-depth — devstack is posix-only in practice but the\n * cost of `Path.join` over template strings is zero).\n */\nexport const layerStackPaths: Layer.Layer<\n\tStackPathsService,\n\tnever,\n\tRuntimeRoot | IdentityContext | Path.Path\n> = Layer.effect(\n\tStackPathsService,\n\tEffect.gen(function* () {\n\t\tconst { root } = yield* RuntimeRoot;\n\t\tconst identity = yield* IdentityContext;\n\t\tconst path = yield* Path.Path;\n\t\tconst stackRoot = stackSubpath(path.join, root, identity.stack);\n\t\tconst cacheDir = path.join(stackRoot, 'cache');\n\t\tconst cacheNamespaceDir = (namespace: string): string => path.join(cacheDir, namespace);\n\t\tconst cacheChainDir = (namespace: string, chainId: string): string =>\n\t\t\tpath.join(cacheNamespaceDir(namespace), chainId);\n\t\tconst cacheEntry = (\n\t\t\tnamespace: string,\n\t\t\tchainId: string,\n\t\t\tcontentHash: string,\n\t\t): { readonly dir: string; readonly file: string } => {\n\t\t\tconst dir = cacheChainDir(namespace, chainId);\n\t\t\treturn { dir, file: path.join(dir, `${contentHash}.json`) };\n\t\t};\n\t\treturn StackPathsService.of({\n\t\t\tstackRoot,\n\t\t\tcacheDir,\n\t\t\tsnapshotDir: path.join(stackRoot, 'snapshots'),\n\t\t\tstackLockFile: path.join(stackRoot, 'stack.lock'),\n\t\t\trosterFile: path.join(stackRoot, 'roster.json'),\n\t\t\tcacheEntry,\n\t\t\tcacheChainDir,\n\t\t\tcacheNamespaceDir,\n\t\t});\n\t}),\n);\n"],"mappings":";;;;;AAsBA,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;AAmBvB,MAAa,gBACZ,MACA,MACA,OACA,GAAG,SACS,KAAK,MAAM,gBAAgB,OAAO,GAAG,IAAI;AActD,IAAa,cAAb,cAAiC,QAAQ,QAAuC,CAAC,CAChF,iCACD,CAAC,CAAC,CAAC;;AAGH,MAAa,oBAAoB,SAChC,MAAM,QAAQ,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC;;;;;;;;;AAUpC,IAAa,kBAAb,cAAqC,QAAQ,QAAmC,CAAC,CAChF,8BACD,CAAC,CAAC,CAAC;AAEH,MAAa,iBAAiB,aAC7B,MAAM,QAAQ,eAAe,CAAC,CAAC,QAAQ;;;;;;AA0CxC,IAAa,oBAAb,cAAuC,QAAQ,QAAuC,CAAC,CACtF,gCACD,CAAC,CAAC,CAAC;;;;;;;;AASH,MAAa,kBAIT,MAAM,OACT,mBACA,OAAO,IAAI,aAAa;CACvB,MAAM,EAAE,SAAS,OAAO;CACxB,MAAM,WAAW,OAAO;CACxB,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,YAAY,aAAa,KAAK,MAAM,MAAM,SAAS,KAAK;CAC9D,MAAM,WAAW,KAAK,KAAK,WAAW,OAAO;CAC7C,MAAM,qBAAqB,cAA8B,KAAK,KAAK,UAAU,SAAS;CACtF,MAAM,iBAAiB,WAAmB,YACzC,KAAK,KAAK,kBAAkB,SAAS,GAAG,OAAO;CAChD,MAAM,cACL,WACA,SACA,gBACqD;EACrD,MAAM,MAAM,cAAc,WAAW,OAAO;EAC5C,OAAO;GAAE;GAAK,MAAM,KAAK,KAAK,KAAK,GAAG,YAAY,MAAM;EAAE;CAC3D;CACA,OAAO,kBAAkB,GAAG;EAC3B;EACA;EACA,aAAa,KAAK,KAAK,WAAW,WAAW;EAC7C,eAAe,KAAK,KAAK,WAAW,YAAY;EAChD,YAAY,KAAK,KAAK,WAAW,aAAa;EAC9C;EACA;EACA;CACD,CAAC;AACF,CAAC,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phase-preserving-produce.mjs","names":[],"sources":["../../../src/substrate/runtime/phase-preserving-produce.ts"],"sourcesContent":["// withPhasePreservingProduce — substrate helper for the\n// \"Ref-stash typed error across ArtifactPublishError wrap\" pattern.\n//\n// Background: `ArtifactPublisher.publish` requires the `produce` Effect\n// to surface failures as `ArtifactPublishError` (substrate contract —\n// the substrate is name-blind, so domain-typed errors cannot reach the\n// substrate's failure channel). For most plugins this is fine: the\n// produce body's typed error is projected directly to\n// `artifactPublishError('produce-failed', message)` via `mapError` and\n// the plugin caller sees a generic ArtifactPublishError it then maps\n// back to its domain (e.g. deepbook's `mapArtifactError`).\n//\n// But the Action plugin needs to preserve the original typed error's\n// `phase` discriminator across the wrap: the user-supplied body Effect\n// emits `ActionError` with one of several `phase` literals\n// (`'sign' | 'parse' | …`), and consumers `catchTag('ActionError')`\n// expecting that `phase` to survive. The substrate's wrap would\n// uniformly stamp every failure as `phase: 'sign'`.\n//\n// This helper captures the pattern: stash the typed error in a Ref\n// before the mapError boundary, then recover it on the\n// `ArtifactPublishError` recovery seam. Only `reason: 'produce-failed'`\n// surfaces a stashed typed error — the substrate-side reasons\n// (`verify-exhausted`, `cache-corrupt`) have no upstream typed error\n// and propagate as ArtifactPublishError untouched.\n//\n// Boundary discipline: lives under `substrate/runtime/` because the\n// pattern is generic over any tagged typed error — no plugin-domain\n// shape leaks in. The single piece of plugin knowledge the helper\n// requires (the `wrapProduceError` projection) is passed in by the\n// caller.\n//\n// Currently consumed by the Action plugin (`plugins/action/service.ts`).\n// The Package plugin (`plugins/package/mode-local.ts:149-152`) has a\n// similar shape ripe for migration but has not yet adopted the helper —\n// migrate when the next package-plugin pass surfaces.\n\nimport { Effect, Ref } from 'effect';\n\nimport { type ArtifactPublishError } from '../../primitives/artifact-publisher.ts';\n\n/** Wrap a domain-typed `produce` Effect so the typed error survives the\n * substrate's `ArtifactPublishError` boundary.\n *\n * Inputs:\n * - `produce`: the typed produce Effect (`<Produced, TypedError, R>`).\n * This is the Effect the caller would otherwise place in\n * `ArtifactSpec.produce` directly.\n * - `wrapProduceError`: projects `TypedError` to `ArtifactPublishError`\n * for the substrate-facing channel. Mirrors the `Effect.mapError`\n * closure callers would write inline.\n *\n * Returns `{ wrappedProduce, recoverTypedError }`:\n * - `wrappedProduce`: the Effect to place in `ArtifactSpec.produce` —\n * surfaces only `ArtifactPublishError` (substrate contract) but\n * stashes the typed cause into the Ref before the wrap.\n * - `recoverTypedError`: a `pipe`-able recovery step. Apply via\n * `.pipe(recoverTypedError)` AFTER `publisher.publish(...)` to\n * restore the typed error channel. `reason: 'produce-failed'` lifts\n * the stashed typed error; other reasons propagate untouched. */\nexport const withPhasePreservingProduce = <Produced, TypedError, R>(params: {\n\treadonly produce: Effect.Effect<Produced, TypedError, R>;\n\treadonly wrapProduceError: (err: TypedError) => ArtifactPublishError;\n}): Effect.Effect<\n\t{\n\t\treadonly wrappedProduce: Effect.Effect<Produced, ArtifactPublishError, R>;\n\t\treadonly recoverTypedError: <A, E, RR>(\n\t\t\tself: Effect.Effect<A, E, RR>,\n\t\t) => Effect.Effect<A, TypedError | Exclude<E, ArtifactPublishError>, RR>;\n\t},\n\tnever,\n\tnever\n> =>\n\tEffect.gen(function* () {\n\t\t// Stash slot — typed `TypedError | null`. Set on `produce` failure\n\t\t// BEFORE the mapError boundary so the outer recovery can read it.\n\t\tconst stashed = yield* Ref.make<TypedError | null>(null);\n\n\t\tconst wrappedProduce: Effect.Effect<Produced, ArtifactPublishError, R> = params.produce.pipe(\n\t\t\t// Stash the typed error before the mapError boundary so the\n\t\t\t// outer recovery path can re-raise with the original tag\n\t\t\t// (and phase, for `ActionError`-style discriminators) intact.\n\t\t\tEffect.tapError((err) => Ref.set(stashed, err)),\n\t\t\tEffect.mapError(params.wrapProduceError),\n\t\t);\n\n\t\tconst recoverTypedError = <A, E, RR>(\n\t\t\tself: Effect.Effect<A, E, RR>,\n\t\t): Effect.Effect<A, TypedError | Exclude<E, ArtifactPublishError>, RR> =>\n\t\t\t(self as Effect.Effect<A, E | ArtifactPublishError, RR>).pipe(\n\t\t\t\t// Recover the stashed typed error if produce raised one.\n\t\t\t\t// `verify-exhausted` / `cache-corrupt` are substrate-side\n\t\t\t\t// signals with no upstream typed error — they propagate\n\t\t\t\t// untouched.\n\t\t\t\tEffect.catchTag('ArtifactPublishError', (err) =>\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tconst seen = yield* Ref.get(stashed);\n\t\t\t\t\t\tconst typedErr = err as ArtifactPublishError;\n\t\t\t\t\t\tif (seen !== null && typedErr.reason === 'produce-failed') {\n\t\t\t\t\t\t\treturn yield* Effect.fail(seen);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn yield* Effect.fail(typedErr);\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t) as Effect.Effect<A, TypedError | Exclude<E, ArtifactPublishError>, RR>;\n\n\t\treturn { wrappedProduce, recoverTypedError };\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA4DA,MAAa,8BAAuD,WAanE,OAAO,IAAI,aAAa;CAGvB,MAAM,UAAU,OAAO,IAAI,KAAwB,
|
|
1
|
+
{"version":3,"file":"phase-preserving-produce.mjs","names":[],"sources":["../../../src/substrate/runtime/phase-preserving-produce.ts"],"sourcesContent":["// withPhasePreservingProduce — substrate helper for the\n// \"Ref-stash typed error across ArtifactPublishError wrap\" pattern.\n//\n// Background: `ArtifactPublisher.publish` requires the `produce` Effect\n// to surface failures as `ArtifactPublishError` (substrate contract —\n// the substrate is name-blind, so domain-typed errors cannot reach the\n// substrate's failure channel). For most plugins this is fine: the\n// produce body's typed error is projected directly to\n// `artifactPublishError('produce-failed', message)` via `mapError` and\n// the plugin caller sees a generic ArtifactPublishError it then maps\n// back to its domain (e.g. deepbook's `mapArtifactError`).\n//\n// But the Action plugin needs to preserve the original typed error's\n// `phase` discriminator across the wrap: the user-supplied body Effect\n// emits `ActionError` with one of several `phase` literals\n// (`'sign' | 'parse' | …`), and consumers `catchTag('ActionError')`\n// expecting that `phase` to survive. The substrate's wrap would\n// uniformly stamp every failure as `phase: 'sign'`.\n//\n// This helper captures the pattern: stash the typed error in a Ref\n// before the mapError boundary, then recover it on the\n// `ArtifactPublishError` recovery seam. Only `reason: 'produce-failed'`\n// surfaces a stashed typed error — the substrate-side reasons\n// (`verify-exhausted`, `cache-corrupt`) have no upstream typed error\n// and propagate as ArtifactPublishError untouched.\n//\n// Boundary discipline: lives under `substrate/runtime/` because the\n// pattern is generic over any tagged typed error — no plugin-domain\n// shape leaks in. The single piece of plugin knowledge the helper\n// requires (the `wrapProduceError` projection) is passed in by the\n// caller.\n//\n// Currently consumed by the Action plugin (`plugins/action/service.ts`).\n// The Package plugin (`plugins/package/mode-local.ts:149-152`) has a\n// similar shape ripe for migration but has not yet adopted the helper —\n// migrate when the next package-plugin pass surfaces.\n\nimport { Effect, Ref } from 'effect';\n\nimport { type ArtifactPublishError } from '../../primitives/artifact-publisher.ts';\n\n/** Wrap a domain-typed `produce` Effect so the typed error survives the\n * substrate's `ArtifactPublishError` boundary.\n *\n * Inputs:\n * - `produce`: the typed produce Effect (`<Produced, TypedError, R>`).\n * This is the Effect the caller would otherwise place in\n * `ArtifactSpec.produce` directly.\n * - `wrapProduceError`: projects `TypedError` to `ArtifactPublishError`\n * for the substrate-facing channel. Mirrors the `Effect.mapError`\n * closure callers would write inline.\n *\n * Returns `{ wrappedProduce, recoverTypedError }`:\n * - `wrappedProduce`: the Effect to place in `ArtifactSpec.produce` —\n * surfaces only `ArtifactPublishError` (substrate contract) but\n * stashes the typed cause into the Ref before the wrap.\n * - `recoverTypedError`: a `pipe`-able recovery step. Apply via\n * `.pipe(recoverTypedError)` AFTER `publisher.publish(...)` to\n * restore the typed error channel. `reason: 'produce-failed'` lifts\n * the stashed typed error; other reasons propagate untouched. */\nexport const withPhasePreservingProduce = <Produced, TypedError, R>(params: {\n\treadonly produce: Effect.Effect<Produced, TypedError, R>;\n\treadonly wrapProduceError: (err: TypedError) => ArtifactPublishError;\n}): Effect.Effect<\n\t{\n\t\treadonly wrappedProduce: Effect.Effect<Produced, ArtifactPublishError, R>;\n\t\treadonly recoverTypedError: <A, E, RR>(\n\t\t\tself: Effect.Effect<A, E, RR>,\n\t\t) => Effect.Effect<A, TypedError | Exclude<E, ArtifactPublishError>, RR>;\n\t},\n\tnever,\n\tnever\n> =>\n\tEffect.gen(function* () {\n\t\t// Stash slot — typed `TypedError | null`. Set on `produce` failure\n\t\t// BEFORE the mapError boundary so the outer recovery can read it.\n\t\tconst stashed = yield* Ref.make<TypedError | null>(null);\n\n\t\tconst wrappedProduce: Effect.Effect<Produced, ArtifactPublishError, R> = params.produce.pipe(\n\t\t\t// Stash the typed error before the mapError boundary so the\n\t\t\t// outer recovery path can re-raise with the original tag\n\t\t\t// (and phase, for `ActionError`-style discriminators) intact.\n\t\t\tEffect.tapError((err) => Ref.set(stashed, err)),\n\t\t\tEffect.mapError(params.wrapProduceError),\n\t\t);\n\n\t\tconst recoverTypedError = <A, E, RR>(\n\t\t\tself: Effect.Effect<A, E, RR>,\n\t\t): Effect.Effect<A, TypedError | Exclude<E, ArtifactPublishError>, RR> =>\n\t\t\t(self as Effect.Effect<A, E | ArtifactPublishError, RR>).pipe(\n\t\t\t\t// Recover the stashed typed error if produce raised one.\n\t\t\t\t// `verify-exhausted` / `cache-corrupt` are substrate-side\n\t\t\t\t// signals with no upstream typed error — they propagate\n\t\t\t\t// untouched.\n\t\t\t\tEffect.catchTag('ArtifactPublishError', (err) =>\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tconst seen = yield* Ref.get(stashed);\n\t\t\t\t\t\tconst typedErr = err as ArtifactPublishError;\n\t\t\t\t\t\tif (seen !== null && typedErr.reason === 'produce-failed') {\n\t\t\t\t\t\t\treturn yield* Effect.fail(seen);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn yield* Effect.fail(typedErr);\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t) as Effect.Effect<A, TypedError | Exclude<E, ArtifactPublishError>, RR>;\n\n\t\treturn { wrappedProduce, recoverTypedError };\n\t});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA4DA,MAAa,8BAAuD,WAanE,OAAO,IAAI,aAAa;CAGvB,MAAM,UAAU,OAAO,IAAI,KAAwB,IAAI;CAEvD,MAAM,iBAAmE,OAAO,QAAQ,KAIvF,OAAO,UAAU,QAAQ,IAAI,IAAI,SAAS,GAAG,CAAC,GAC9C,OAAO,SAAS,OAAO,gBAAgB,CACxC;CAEA,MAAM,qBACL,SAEC,KAAwD,KAKxD,OAAO,SAAS,yBAAyB,QACxC,OAAO,IAAI,aAAa;EACvB,MAAM,OAAO,OAAO,IAAI,IAAI,OAAO;EACnC,MAAM,WAAW;EACjB,IAAI,SAAS,QAAQ,SAAS,WAAW,kBACxC,OAAO,OAAO,OAAO,KAAK,IAAI;EAE/B,OAAO,OAAO,OAAO,KAAK,QAAQ;CACnC,CAAC,CACF,CACD;CAED,OAAO;EAAE;EAAgB;CAAkB;AAC5C,CAAC"}
|