@mysten-incubation/devstack 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dashboard-ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dashboard-ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dashboard-ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dashboard-ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
- package/dashboard-ui/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
- package/dashboard-ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dashboard-ui/assets/index-Bmi1UtAg.js +1356 -0
- package/dashboard-ui/assets/index-D5EShVt4.js +3 -0
- package/dashboard-ui/assets/index-Deml9drg.css +1 -0
- package/dashboard-ui/index.html +13 -0
- package/dist/api/define-capabilities.d.mts +14 -3
- package/dist/api/define-capabilities.mjs +18 -1
- package/dist/api/define-capabilities.mjs.map +1 -1
- package/dist/api/define-devstack.d.mts +1 -1
- package/dist/api/define-devstack.mjs +6 -26
- package/dist/api/define-devstack.mjs.map +1 -1
- package/dist/api/define-plugin.d.mts +1 -0
- package/dist/api/define-plugin.mjs +2 -0
- package/dist/api/inference-network.d.mts +132 -0
- package/dist/api/inference-network.mjs +105 -16
- package/dist/api/inference-network.mjs.map +1 -1
- package/dist/api/plugin-errors.d.mts +7 -0
- package/dist/api/run-stack.d.mts +20 -9
- package/dist/api/run-stack.mjs +56 -29
- package/dist/api/run-stack.mjs.map +1 -1
- package/dist/build-integrations/playwright/config.d.mts +6 -4
- package/dist/build-integrations/playwright/config.mjs +5 -3
- package/dist/build-integrations/playwright/config.mjs.map +1 -1
- package/dist/build-integrations/playwright/errors.d.mts +1 -1
- package/dist/build-integrations/playwright/global-setup.d.mts +43 -17
- package/dist/build-integrations/playwright/global-setup.mjs +250 -13
- package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
- package/dist/build-integrations/playwright/index.d.mts +5 -3
- package/dist/build-integrations/playwright/index.mjs +4 -2
- package/dist/build-integrations/playwright/stack-context.d.mts +0 -1
- package/dist/build-integrations/playwright/stack-context.mjs +24 -63
- package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
- package/dist/build-integrations/playwright/wallet-context.d.mts +7 -10
- package/dist/build-integrations/playwright/wallet-context.mjs +8 -13
- package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/cold-start-url.d.mts +5 -0
- package/dist/build-integrations/runtime/cold-start-url.mjs +4 -3
- package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
- package/dist/build-integrations/runtime/conventional-routes.d.mts +51 -0
- package/dist/build-integrations/runtime/conventional-routes.mjs +92 -0
- package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -0
- package/dist/build-integrations/runtime/discover.d.mts +38 -30
- package/dist/build-integrations/runtime/discover.mjs +73 -70
- package/dist/build-integrations/runtime/discover.mjs.map +1 -1
- package/dist/build-integrations/runtime/endpoint-registry.d.mts +3 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs +3 -1
- package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
- package/dist/build-integrations/runtime/index.d.mts +7 -2
- package/dist/build-integrations/runtime/index.mjs +9 -2
- package/dist/build-integrations/runtime/manifest-types.d.mts +2 -0
- package/dist/build-integrations/runtime/manifest-types.mjs +3 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.d.mts +29 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs +10 -0
- package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs.map +1 -0
- package/dist/build-integrations/runtime/read-stack-context.d.mts +1 -1
- package/dist/build-integrations/runtime/read-stack-context.mjs +10 -4
- package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
- package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +48 -0
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs +42 -0
- package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -0
- package/dist/build-integrations/runtime/stack-context.d.mts +1 -1
- package/dist/build-integrations/runtime/wallet-paths.mjs +2 -0
- package/dist/build-integrations/vite/index.d.mts +51 -0
- package/dist/build-integrations/vite/index.mjs +73 -0
- package/dist/build-integrations/vite/index.mjs.map +1 -0
- package/dist/build-integrations/vitest/env.d.mts +4 -3
- package/dist/build-integrations/vitest/env.mjs +6 -4
- package/dist/build-integrations/vitest/env.mjs.map +1 -1
- package/dist/build-integrations/vitest/errors.d.mts +25 -4
- package/dist/build-integrations/vitest/errors.mjs +15 -2
- package/dist/build-integrations/vitest/errors.mjs.map +1 -1
- package/dist/build-integrations/vitest/index.d.mts +2 -2
- package/dist/build-integrations/vitest/index.mjs +3 -3
- package/dist/build-integrations/vitest/setup.d.mts +4 -2
- package/dist/build-integrations/vitest/setup.mjs +2 -2
- package/dist/build-integrations/vitest/setup.mjs.map +1 -1
- package/dist/build-integrations/vitest/stack-context.d.mts +0 -1
- package/dist/build-integrations/vitest/stack-context.mjs +33 -13
- package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
- package/dist/cli/bail.mjs +23 -0
- package/dist/cli/bail.mjs.map +1 -0
- package/dist/{surfaces/cli/commands → cli}/doctor-probes.mjs +26 -16
- package/dist/cli/doctor-probes.mjs.map +1 -0
- package/dist/cli/main.d.mts +15 -1
- package/dist/cli/main.mjs +166 -482
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/prune-direct.mjs +31 -201
- package/dist/cli/prune-direct.mjs.map +1 -1
- package/dist/cli/snapshot-reader.mjs +14 -11
- package/dist/cli/snapshot-reader.mjs.map +1 -1
- package/dist/cli/up-lifecycle.mjs +4 -1
- package/dist/cli/up-lifecycle.mjs.map +1 -1
- package/dist/cli/wirings/apply.mjs +95 -0
- package/dist/cli/wirings/apply.mjs.map +1 -0
- package/dist/cli/wirings/build-verb-layers.mjs +42 -0
- package/dist/cli/wirings/build-verb-layers.mjs.map +1 -0
- package/dist/cli/wirings/config-loader.mjs +69 -0
- package/dist/cli/wirings/config-loader.mjs.map +1 -0
- package/dist/cli/wirings/engine-command.mjs +30 -0
- package/dist/cli/wirings/engine-command.mjs.map +1 -0
- package/dist/cli/wirings/identity.mjs +72 -0
- package/dist/cli/wirings/identity.mjs.map +1 -0
- package/dist/cli/wirings/provide-file-system.mjs +7 -0
- package/dist/cli/wirings/provide-file-system.mjs.map +1 -0
- package/dist/cli/wirings/snapshot.mjs +177 -0
- package/dist/cli/wirings/snapshot.mjs.map +1 -0
- package/dist/cli/wirings/up.mjs +287 -0
- package/dist/cli/wirings/up.mjs.map +1 -0
- package/dist/cli/wirings/wipe.mjs +40 -0
- package/dist/cli/wirings/wipe.mjs.map +1 -0
- package/dist/contracts/capability-decl.d.mts +0 -2
- package/dist/contracts/codegenable.d.mts +43 -0
- package/dist/contracts/container-runtime.d.mts +26 -1
- package/dist/contracts/faucet-strategy.d.mts +25 -0
- package/dist/contracts/funding-strategy.d.mts +42 -0
- package/dist/contracts/plugin-expander.mjs +60 -0
- package/dist/contracts/plugin-expander.mjs.map +1 -0
- package/dist/contracts/projection.d.mts +5 -3
- package/dist/contracts/renderer.d.mts +23 -0
- package/dist/contracts/routable.d.mts +14 -1
- package/dist/contracts/snapshotable.d.mts +25 -3
- package/dist/contracts/wallet-protocol.d.mts +52 -0
- package/dist/contracts/wallet-protocol.mjs +52 -0
- package/dist/contracts/wallet-protocol.mjs.map +1 -0
- package/dist/index.d.mts +26 -25
- package/dist/index.mjs +20 -13
- package/dist/{runtime → orchestrators}/built-in-plugin-layers.mjs +10 -5
- package/dist/orchestrators/built-in-plugin-layers.mjs.map +1 -0
- package/dist/orchestrators/codegen/bindings.d.mts +1 -2
- package/dist/orchestrators/codegen/bindings.mjs +52 -186
- package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
- package/dist/orchestrators/codegen/emit.mjs +19 -1
- package/dist/orchestrators/codegen/emit.mjs.map +1 -1
- package/dist/orchestrators/codegen/errors.mjs +8 -3
- package/dist/orchestrators/codegen/errors.mjs.map +1 -1
- package/dist/orchestrators/codegen/format.mjs +47 -39
- package/dist/orchestrators/codegen/format.mjs.map +1 -1
- package/dist/orchestrators/codegen/gitignore.mjs +3 -1
- package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
- package/dist/orchestrators/codegen/output-location.mjs +41 -0
- package/dist/orchestrators/codegen/output-location.mjs.map +1 -0
- package/dist/orchestrators/codegen/paths.d.mts +1 -1
- package/dist/orchestrators/codegen/paths.mjs +40 -14
- package/dist/orchestrators/codegen/paths.mjs.map +1 -1
- package/dist/orchestrators/codegen/permissions.mjs +3 -1
- package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
- package/dist/orchestrators/codegen/service.mjs +157 -105
- package/dist/orchestrators/codegen/service.mjs.map +1 -1
- package/dist/orchestrators/lifecycle-prune/errors.mjs +31 -0
- package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -0
- package/dist/orchestrators/lifecycle-prune/index.mjs +350 -0
- package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -0
- package/dist/orchestrators/router/cleanup.mjs +26 -27
- package/dist/orchestrators/router/cleanup.mjs.map +1 -1
- package/dist/orchestrators/router/entrypoints.mjs +2 -2
- package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
- package/dist/orchestrators/router/file-provider.mjs +14 -5
- package/dist/orchestrators/router/file-provider.mjs.map +1 -1
- package/dist/orchestrators/router/hostname.mjs +19 -55
- package/dist/orchestrators/router/hostname.mjs.map +1 -1
- package/dist/orchestrators/router/index.mjs +11 -0
- package/dist/orchestrators/router/profile.mjs +2 -2
- package/dist/orchestrators/router/sentinels.mjs +26 -0
- package/dist/orchestrators/router/sentinels.mjs.map +1 -0
- package/dist/orchestrators/router/service.mjs +40 -37
- package/dist/orchestrators/router/service.mjs.map +1 -1
- package/dist/orchestrators/router/traefik-container.mjs +27 -16
- package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
- package/dist/{substrate/runtime → orchestrators}/run.mjs +26 -21
- package/dist/orchestrators/run.mjs.map +1 -0
- package/dist/orchestrators/runtime-composition.d.mts +1 -1
- package/dist/orchestrators/runtime-composition.mjs +123 -39
- package/dist/orchestrators/runtime-composition.mjs.map +1 -1
- package/dist/orchestrators/snapshot/capture-command.d.mts +1 -0
- package/dist/orchestrators/snapshot/capture-command.mjs +25 -0
- package/dist/orchestrators/snapshot/capture-command.mjs.map +1 -0
- package/dist/orchestrators/snapshot/capture.mjs +42 -28
- package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
- package/dist/orchestrators/snapshot/descriptor.mjs +35 -10
- package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
- package/dist/orchestrators/snapshot/identity-guard.mjs +1 -1
- package/dist/orchestrators/snapshot/index.mjs +13 -0
- package/dist/orchestrators/snapshot/integrity.d.mts +1 -1
- package/dist/orchestrators/snapshot/integrity.mjs +73 -17
- package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
- package/dist/orchestrators/snapshot/pending-marker.d.mts +1 -0
- package/dist/orchestrators/snapshot/pending-marker.mjs +99 -0
- package/dist/orchestrators/snapshot/pending-marker.mjs.map +1 -0
- package/dist/orchestrators/snapshot/phase-error.mjs +23 -0
- package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -0
- package/dist/orchestrators/snapshot/prune.mjs +20 -55
- package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
- package/dist/orchestrators/snapshot/recover-pending.d.mts +1 -0
- package/dist/orchestrators/snapshot/recover-pending.mjs +223 -0
- package/dist/orchestrators/snapshot/recover-pending.mjs.map +1 -0
- package/dist/orchestrators/snapshot/restore.mjs +118 -84
- package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
- package/dist/orchestrators/snapshot/service.mjs +53 -56
- package/dist/orchestrators/snapshot/service.mjs.map +1 -1
- package/dist/orchestrators/snapshot/state-document.d.mts +1 -0
- package/dist/orchestrators/snapshot/state-document.mjs +47 -7
- package/dist/orchestrators/snapshot/state-document.mjs.map +1 -1
- package/dist/orchestrators/snapshot/wipe.mjs +67 -10
- package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
- package/dist/plugins/account/codegen.mjs +9 -8
- package/dist/plugins/account/codegen.mjs.map +1 -1
- package/dist/plugins/account/errors.d.mts +33 -6
- package/dist/plugins/account/errors.mjs.map +1 -1
- package/dist/plugins/account/funding.d.mts +29 -19
- package/dist/plugins/account/funding.mjs +84 -66
- package/dist/plugins/account/funding.mjs.map +1 -1
- package/dist/plugins/account/index.d.mts +2 -4
- package/dist/plugins/account/index.mjs +64 -31
- package/dist/plugins/account/index.mjs.map +1 -1
- package/dist/plugins/account/keypair.mjs +6 -9
- package/dist/plugins/account/keypair.mjs.map +1 -1
- package/dist/plugins/account/lease.mjs +1 -0
- package/dist/plugins/account/lease.mjs.map +1 -1
- package/dist/plugins/account/registry.d.mts +14 -1
- package/dist/plugins/account/registry.mjs +18 -18
- package/dist/plugins/account/registry.mjs.map +1 -1
- package/dist/plugins/account/service.d.mts +23 -4
- package/dist/plugins/account/service.mjs +83 -45
- package/dist/plugins/account/service.mjs.map +1 -1
- package/dist/plugins/account/snapshot.mjs +2 -5
- package/dist/plugins/account/snapshot.mjs.map +1 -1
- package/dist/plugins/account/spans.mjs +16 -0
- package/dist/plugins/account/spans.mjs.map +1 -0
- package/dist/plugins/account/variants/keystore.mjs +6 -8
- package/dist/plugins/account/variants/keystore.mjs.map +1 -1
- package/dist/plugins/action/build-context.d.mts +3 -2
- package/dist/plugins/action/errors.d.mts +16 -12
- package/dist/plugins/action/errors.mjs.map +1 -1
- package/dist/plugins/action/execute.mjs +65 -103
- package/dist/plugins/action/execute.mjs.map +1 -1
- package/dist/plugins/action/index.d.mts +0 -2
- package/dist/plugins/action/index.mjs +17 -13
- package/dist/plugins/action/index.mjs.map +1 -1
- package/dist/plugins/action/service.mjs +38 -35
- package/dist/plugins/action/service.mjs.map +1 -1
- package/dist/plugins/action/spans.mjs +11 -0
- package/dist/plugins/action/spans.mjs.map +1 -0
- package/dist/plugins/coin/codegen.mjs +9 -8
- package/dist/plugins/coin/codegen.mjs.map +1 -1
- package/dist/plugins/coin/discovery.mjs +8 -4
- package/dist/plugins/coin/discovery.mjs.map +1 -1
- package/dist/plugins/coin/index.d.mts +11 -29
- package/dist/plugins/coin/index.mjs +56 -27
- package/dist/plugins/coin/index.mjs.map +1 -1
- package/dist/plugins/coin/metadata.mjs +8 -15
- package/dist/plugins/coin/metadata.mjs.map +1 -1
- package/dist/plugins/coin/mint.d.mts +30 -11
- package/dist/plugins/coin/mint.mjs +78 -74
- package/dist/plugins/coin/mint.mjs.map +1 -1
- package/dist/plugins/coin/registry.mjs +4 -7
- package/dist/plugins/coin/registry.mjs.map +1 -1
- package/dist/plugins/coin/service.d.mts +39 -1
- package/dist/plugins/coin/service.mjs +11 -4
- package/dist/plugins/coin/service.mjs.map +1 -1
- package/dist/plugins/coin/spans.mjs +20 -0
- package/dist/plugins/coin/spans.mjs.map +1 -0
- package/dist/plugins/dashboard/domain.mjs +402 -0
- package/dist/plugins/dashboard/domain.mjs.map +1 -0
- package/dist/plugins/dashboard/index.d.mts +29 -0
- package/dist/plugins/dashboard/index.mjs +91 -0
- package/dist/plugins/dashboard/index.mjs.map +1 -0
- package/dist/plugins/dashboard/origin-policy.mjs +36 -0
- package/dist/plugins/dashboard/origin-policy.mjs.map +1 -0
- package/dist/plugins/dashboard/routable.mjs +27 -0
- package/dist/plugins/dashboard/routable.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/builder.mjs +21 -0
- package/dist/plugins/dashboard/schema/builder.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/enums.mjs +72 -0
- package/dist/plugins/dashboard/schema/enums.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/root.mjs +351 -0
- package/dist/plugins/dashboard/schema/root.mjs.map +1 -0
- package/dist/plugins/dashboard/schema/types.mjs +508 -0
- package/dist/plugins/dashboard/schema/types.mjs.map +1 -0
- package/dist/plugins/dashboard/schema.mjs +11 -0
- package/dist/plugins/dashboard/schema.mjs.map +1 -0
- package/dist/plugins/dashboard/server.mjs +166 -0
- package/dist/plugins/dashboard/server.mjs.map +1 -0
- package/dist/plugins/deepbook/deploy.mjs +53 -115
- package/dist/plugins/deepbook/deploy.mjs.map +1 -1
- package/dist/plugins/deepbook/errors.mjs +1 -0
- package/dist/plugins/deepbook/errors.mjs.map +1 -1
- package/dist/plugins/deepbook/faucet-strategy.mjs +47 -34
- package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/deepbook/hash.mjs +9 -0
- package/dist/plugins/deepbook/hash.mjs.map +1 -0
- package/dist/plugins/deepbook/index.d.mts +1 -10
- package/dist/plugins/deepbook/index.mjs +25 -22
- package/dist/plugins/deepbook/index.mjs.map +1 -1
- package/dist/plugins/deepbook/pyth/index.mjs +26 -55
- package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
- package/dist/plugins/deepbook/snapshot.mjs +17 -29
- package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
- package/dist/plugins/deepbook/spans.mjs +18 -0
- package/dist/plugins/deepbook/spans.mjs.map +1 -0
- package/dist/plugins/deepbook/type-strings.mjs +65 -0
- package/dist/plugins/deepbook/type-strings.mjs.map +1 -0
- package/dist/plugins/deepbook/types.d.mts +2 -3
- package/dist/plugins/deepbook/types.mjs.map +1 -1
- package/dist/plugins/faucet/dispatcher.d.mts +21 -2
- package/dist/plugins/faucet/dispatcher.mjs +1 -2
- package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
- package/dist/plugins/faucet/errors.d.mts +11 -24
- package/dist/plugins/faucet/errors.mjs.map +1 -1
- package/dist/plugins/faucet/http.mjs +33 -14
- package/dist/plugins/faucet/http.mjs.map +1 -1
- package/dist/plugins/faucet/index.d.mts +15 -5
- package/dist/plugins/faucet/index.mjs +3 -0
- package/dist/plugins/faucet/index.mjs.map +1 -1
- package/dist/plugins/faucet/spans.mjs +12 -0
- package/dist/plugins/faucet/spans.mjs.map +1 -0
- package/dist/plugins/host-service/errors.d.mts +1 -1
- package/dist/plugins/host-service/index.mjs +34 -8
- package/dist/plugins/host-service/index.mjs.map +1 -1
- package/dist/plugins/host-service/routable.mjs +2 -1
- package/dist/plugins/host-service/routable.mjs.map +1 -1
- package/dist/plugins/host-service/service.d.mts +21 -0
- package/dist/plugins/host-service/service.mjs +136 -81
- package/dist/plugins/host-service/service.mjs.map +1 -1
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs +23 -0
- package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -0
- package/dist/plugins/internal/codegen-helpers.mjs +18 -0
- package/dist/plugins/internal/codegen-helpers.mjs.map +1 -0
- package/dist/plugins/internal/funding-failure-error.mjs +32 -0
- package/dist/plugins/internal/funding-failure-error.mjs.map +1 -0
- package/dist/plugins/package/build.mjs +1 -1
- package/dist/plugins/package/build.mjs.map +1 -1
- package/dist/plugins/package/codegen.mjs +26 -0
- package/dist/plugins/package/codegen.mjs.map +1 -1
- package/dist/plugins/package/errors.d.mts +16 -6
- package/dist/plugins/package/errors.mjs.map +1 -1
- package/dist/plugins/package/index.d.mts +0 -4
- package/dist/plugins/package/index.mjs +27 -21
- package/dist/plugins/package/index.mjs.map +1 -1
- package/dist/plugins/package/mode-known.mjs +26 -12
- package/dist/plugins/package/mode-known.mjs.map +1 -1
- package/dist/plugins/package/mode-local.mjs +47 -41
- package/dist/plugins/package/mode-local.mjs.map +1 -1
- package/dist/plugins/package/publish-executor.mjs +55 -114
- package/dist/plugins/package/publish-executor.mjs.map +1 -1
- package/dist/plugins/package/registry.mjs +2 -1
- package/dist/plugins/package/registry.mjs.map +1 -1
- package/dist/plugins/package/service.mjs.map +1 -1
- package/dist/plugins/package/spans.mjs +14 -0
- package/dist/plugins/package/spans.mjs.map +1 -0
- package/dist/plugins/postgres/db-ensure.mjs +19 -12
- package/dist/plugins/postgres/db-ensure.mjs.map +1 -1
- package/dist/plugins/postgres/errors.d.mts +5 -0
- package/dist/plugins/postgres/errors.mjs.map +1 -1
- package/dist/plugins/postgres/index.mjs +10 -17
- package/dist/plugins/postgres/index.mjs.map +1 -1
- package/dist/plugins/postgres/service.mjs +58 -24
- package/dist/plugins/postgres/service.mjs.map +1 -1
- package/dist/plugins/postgres/snapshot.mjs.map +1 -1
- package/dist/plugins/postgres/spans.mjs +11 -0
- package/dist/plugins/postgres/spans.mjs.map +1 -0
- package/dist/plugins/router-entrypoints.mjs +4 -4
- package/dist/plugins/router-entrypoints.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +15 -13
- package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +15 -19
- package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
- package/dist/plugins/seal/codegen.d.mts +2 -3
- package/dist/plugins/seal/codegen.mjs.map +1 -1
- package/dist/plugins/seal/config-render.mjs +35 -8
- package/dist/plugins/seal/config-render.mjs.map +1 -1
- package/dist/plugins/seal/deploy.mjs +31 -39
- package/dist/plugins/seal/deploy.mjs.map +1 -1
- package/dist/plugins/seal/errors.d.mts +1 -3
- package/dist/plugins/seal/errors.mjs +7 -1
- package/dist/plugins/seal/errors.mjs.map +1 -1
- package/dist/plugins/seal/index.d.mts +1 -6
- package/dist/plugins/seal/index.mjs +40 -14
- package/dist/plugins/seal/index.mjs.map +1 -1
- package/dist/plugins/seal/key-manager.d.mts +1 -9
- package/dist/plugins/seal/key-manager.mjs +2 -18
- package/dist/plugins/seal/key-manager.mjs.map +1 -1
- package/dist/plugins/seal/key-server.mjs +37 -36
- package/dist/plugins/seal/key-server.mjs.map +1 -1
- package/dist/plugins/seal/keygen.mjs +16 -17
- package/dist/plugins/seal/keygen.mjs.map +1 -1
- package/dist/plugins/seal/mode/fork-known.mjs +36 -14
- package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
- package/dist/plugins/seal/mode/live.d.mts +6 -4
- package/dist/plugins/seal/mode/live.mjs +14 -21
- package/dist/plugins/seal/mode/live.mjs.map +1 -1
- package/dist/plugins/seal/mode/local-keygen.mjs +57 -21
- package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
- package/dist/plugins/seal/registry-publish.mjs +1 -0
- package/dist/plugins/seal/registry-publish.mjs.map +1 -1
- package/dist/plugins/seal/routable.mjs.map +1 -1
- package/dist/plugins/seal/service.mjs.map +1 -1
- package/dist/plugins/seal/spans.mjs +18 -0
- package/dist/plugins/seal/spans.mjs.map +1 -0
- package/dist/plugins/sui/auto-tick.mjs +17 -23
- package/dist/plugins/sui/auto-tick.mjs.map +1 -1
- package/dist/plugins/sui/chain-build-container.mjs +18 -0
- package/dist/plugins/sui/chain-build-container.mjs.map +1 -0
- package/dist/plugins/sui/chain-probe.d.mts +5 -5
- package/dist/plugins/sui/chain-probe.mjs +28 -58
- package/dist/plugins/sui/chain-probe.mjs.map +1 -1
- package/dist/plugins/sui/codegen.mjs +26 -0
- package/dist/plugins/sui/codegen.mjs.map +1 -1
- package/dist/plugins/sui/errors.d.mts +2 -29
- package/dist/plugins/sui/errors.mjs +1 -3
- package/dist/plugins/sui/errors.mjs.map +1 -1
- package/dist/plugins/sui/fork-faucet-strategy.d.mts +1 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs +55 -0
- package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -0
- package/dist/plugins/sui/fork-orchestration.d.mts +1 -0
- package/dist/plugins/sui/fork-orchestration.mjs +170 -2
- package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
- package/dist/plugins/sui/fork-transaction.d.mts +2 -0
- package/dist/plugins/sui/fork-transaction.mjs +92 -28
- package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
- package/dist/plugins/sui/index.d.mts +33 -70
- package/dist/plugins/sui/index.mjs +58 -35
- package/dist/plugins/sui/index.mjs.map +1 -1
- package/dist/plugins/{faucet/strategies/sui-local.mjs → sui/local-faucet-strategy.mjs} +10 -7
- package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -0
- package/dist/plugins/sui/mode/external.mjs +7 -13
- package/dist/plugins/sui/mode/external.mjs.map +1 -1
- package/dist/plugins/sui/mode/fork.mjs +136 -59
- package/dist/plugins/sui/mode/fork.mjs.map +1 -1
- package/dist/plugins/sui/mode/live.mjs +10 -16
- package/dist/plugins/sui/mode/live.mjs.map +1 -1
- package/dist/plugins/sui/mode/local.mjs +37 -36
- package/dist/plugins/sui/mode/local.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared-boot.mjs +38 -115
- package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
- package/dist/plugins/sui/mode/shared.d.mts +1 -5
- package/dist/plugins/sui/mode/shared.mjs.map +1 -1
- package/dist/plugins/sui/mode/spec.d.mts +18 -0
- package/dist/plugins/sui/move-summary-runner.mjs +207 -0
- package/dist/plugins/sui/move-summary-runner.mjs.map +1 -0
- package/dist/plugins/sui/network-resolver.d.mts +4 -3
- package/dist/plugins/sui/service.mjs.map +1 -1
- package/dist/plugins/sui/snapshot.mjs +3 -0
- package/dist/plugins/sui/snapshot.mjs.map +1 -1
- package/dist/plugins/sui/spans.mjs +17 -0
- package/dist/plugins/sui/spans.mjs.map +1 -0
- package/dist/plugins/wallet/codegen.d.mts +0 -1
- package/dist/plugins/wallet/codegen.mjs +8 -9
- package/dist/plugins/wallet/codegen.mjs.map +1 -1
- package/dist/plugins/wallet/index.d.mts +6 -8
- package/dist/plugins/wallet/index.mjs +46 -35
- package/dist/plugins/wallet/index.mjs.map +1 -1
- package/dist/plugins/wallet/origin-policy.mjs +12 -28
- package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
- package/dist/plugins/wallet/pairing.mjs +6 -4
- package/dist/plugins/wallet/pairing.mjs.map +1 -1
- package/dist/plugins/wallet/protocol.d.mts +1 -0
- package/dist/plugins/wallet/protocol.mjs +5 -44
- package/dist/plugins/wallet/protocol.mjs.map +1 -1
- package/dist/plugins/wallet/routable.mjs +2 -8
- package/dist/plugins/wallet/routable.mjs.map +1 -1
- package/dist/plugins/wallet/server.mjs +36 -44
- package/dist/plugins/wallet/server.mjs.map +1 -1
- package/dist/plugins/wallet/service.d.mts +1 -5
- package/dist/plugins/wallet/service.mjs +22 -18
- package/dist/plugins/wallet/service.mjs.map +1 -1
- package/dist/plugins/wallet/spans.mjs +22 -0
- package/dist/plugins/wallet/spans.mjs.map +1 -0
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +33 -8
- package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
- package/dist/plugins/walrus/codegen.d.mts +3 -3
- package/dist/plugins/walrus/codegen.mjs.map +1 -1
- package/dist/plugins/walrus/deploy-paths.mjs +21 -5
- package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
- package/dist/plugins/walrus/deploy.mjs +67 -69
- package/dist/plugins/walrus/deploy.mjs.map +1 -1
- package/dist/plugins/walrus/errors.mjs +1 -0
- package/dist/plugins/walrus/errors.mjs.map +1 -1
- package/dist/plugins/walrus/faucet-strategy.mjs +12 -7
- package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
- package/dist/plugins/walrus/index.d.mts +2 -13
- package/dist/plugins/walrus/index.mjs +18 -23
- package/dist/plugins/walrus/index.mjs.map +1 -1
- package/dist/plugins/walrus/mode/known-deploy.mjs +7 -5
- package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
- package/dist/plugins/walrus/mode/local-cluster.mjs +26 -7
- package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
- package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
- package/dist/plugins/walrus/routable.mjs +7 -23
- package/dist/plugins/walrus/routable.mjs.map +1 -1
- package/dist/plugins/walrus/service.mjs.map +1 -1
- package/dist/plugins/walrus/spans.mjs +18 -0
- package/dist/plugins/walrus/spans.mjs.map +1 -0
- package/dist/plugins/walrus/storage-nodes.d.mts +6 -2
- package/dist/plugins/walrus/storage-nodes.mjs +15 -8
- package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
- package/dist/plugins/walrus/wal-swap.mjs +58 -15
- package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
- package/dist/primitives/artifact-publisher.d.mts +1 -1
- package/dist/primitives/artifact-publisher.mjs +15 -0
- package/dist/primitives/artifact-publisher.mjs.map +1 -0
- package/dist/runtime/docker/client.mjs +17 -4
- package/dist/runtime/docker/client.mjs.map +1 -1
- package/dist/runtime/docker/container.mjs +219 -132
- package/dist/runtime/docker/container.mjs.map +1 -1
- package/dist/runtime/docker/errors.mjs +0 -6
- package/dist/runtime/docker/errors.mjs.map +1 -1
- package/dist/runtime/docker/exec.mjs +21 -18
- package/dist/runtime/docker/exec.mjs.map +1 -1
- package/dist/runtime/docker/image.mjs +36 -13
- package/dist/runtime/docker/image.mjs.map +1 -1
- package/dist/runtime/docker/index.mjs +14 -0
- package/dist/runtime/docker/inspect-and-decode.mjs +51 -0
- package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -0
- package/dist/runtime/docker/inventory.mjs +14 -11
- package/dist/runtime/docker/inventory.mjs.map +1 -1
- package/dist/runtime/docker/labels.mjs +38 -5
- package/dist/runtime/docker/labels.mjs.map +1 -1
- package/dist/runtime/docker/logs.d.mts +1 -1
- package/dist/runtime/docker/logs.mjs.map +1 -1
- package/dist/runtime/docker/network.mjs +83 -47
- package/dist/runtime/docker/network.mjs.map +1 -1
- package/dist/runtime/docker/render-run-args.mjs +82 -0
- package/dist/runtime/docker/render-run-args.mjs.map +1 -0
- package/dist/runtime/docker/service.d.mts +1 -1
- package/dist/runtime/docker/service.mjs +12 -7
- package/dist/runtime/docker/service.mjs.map +1 -1
- package/dist/runtime/docker/sweep.mjs +118 -43
- package/dist/runtime/docker/sweep.mjs.map +1 -1
- package/dist/runtime/docker/volume.mjs +2 -0
- package/dist/runtime/docker/volume.mjs.map +1 -1
- package/dist/runtime/docker/wrap.mjs +30 -1
- package/dist/runtime/docker/wrap.mjs.map +1 -1
- package/dist/substrate/cross-process.mjs +14 -8
- package/dist/substrate/cross-process.mjs.map +1 -1
- package/dist/substrate/event-time.mjs +33 -0
- package/dist/substrate/event-time.mjs.map +1 -0
- package/dist/substrate/events.d.mts +28 -8
- package/dist/substrate/identity.d.mts +2 -1
- package/dist/substrate/manifest.d.mts +68 -5
- package/dist/substrate/manifest.mjs +34 -5
- package/dist/substrate/manifest.mjs.map +1 -1
- package/dist/substrate/network.d.mts +3 -5
- package/dist/substrate/options.d.mts +0 -2
- package/dist/substrate/plugin.d.mts +13 -0
- package/dist/substrate/plugin.mjs +4 -2
- package/dist/substrate/plugin.mjs.map +1 -1
- package/dist/substrate/projection.d.mts +35 -3
- package/dist/substrate/runtime/artifact-publisher/index.mjs +19 -9
- package/dist/substrate/runtime/artifact-publisher/index.mjs.map +1 -1
- package/dist/substrate/runtime/atomic-write.mjs +97 -32
- package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
- package/dist/substrate/runtime/cache/index.mjs +3 -0
- package/dist/substrate/runtime/cache/schema.mjs +2 -2
- package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
- package/dist/substrate/runtime/cache/service.mjs +9 -7
- package/dist/substrate/runtime/cache/service.mjs.map +1 -1
- package/dist/substrate/runtime/capability-sinks/index.d.mts +1 -1
- package/dist/substrate/runtime/capability-sinks/index.mjs +3 -0
- package/dist/substrate/runtime/capability-sinks/layer.mjs +1 -1
- package/dist/substrate/runtime/capability-sinks/service.d.mts +61 -2
- package/dist/substrate/runtime/capability-sinks/service.mjs +21 -26
- package/dist/substrate/runtime/capability-sinks/service.mjs.map +1 -1
- package/dist/substrate/runtime/config-validation.d.mts +1 -13
- package/dist/substrate/runtime/config-validation.mjs +2 -91
- package/dist/substrate/runtime/config-validation.mjs.map +1 -1
- package/dist/substrate/runtime/container-runtime.d.mts +1 -0
- package/dist/substrate/runtime/container-runtime.mjs +2 -0
- package/dist/substrate/runtime/control-plane/domain.mjs +101 -0
- package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -0
- package/dist/substrate/runtime/control-plane/service.mjs +7 -0
- package/dist/substrate/runtime/control-plane/service.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +46 -19
- package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs +30 -12
- package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/index.mjs +5 -0
- package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs +11 -2
- package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs +1 -1
- package/dist/substrate/runtime/cross-process/index.mjs +11 -0
- package/dist/substrate/runtime/cross-process/live-clock.mjs +34 -0
- package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/liveness.mjs +52 -10
- package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/lock.d.mts +1 -0
- package/dist/substrate/runtime/cross-process/lock.mjs +23 -0
- package/dist/substrate/runtime/cross-process/lock.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +64 -0
- package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/roster.mjs +97 -83
- package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/self-pid.mjs +8 -0
- package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -0
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +24 -28
- package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +1 -1
- package/dist/substrate/runtime/cross-process/stack-lock.mjs +41 -20
- package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
- package/dist/substrate/runtime/current-plugin.mjs +2 -2
- package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
- package/dist/substrate/runtime/errors.mjs +12 -6
- package/dist/substrate/runtime/errors.mjs.map +1 -1
- package/dist/substrate/runtime/format-unknown-error.mjs +30 -0
- package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -0
- package/dist/substrate/runtime/host-bind-mount-owner.mjs +10 -0
- package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -0
- package/dist/substrate/runtime/host-gateway.mjs +20 -0
- package/dist/substrate/runtime/host-gateway.mjs.map +1 -0
- package/dist/substrate/runtime/host-tree-tar/index.mjs +61 -50
- package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
- package/dist/substrate/runtime/http-probe.d.mts +1 -1
- package/dist/substrate/runtime/index.mjs +14 -0
- package/dist/substrate/runtime/lease-broker/index.d.mts +1 -1
- package/dist/substrate/runtime/lease-broker/index.mjs +2 -0
- package/dist/substrate/runtime/lease-broker/service.d.mts +1 -64
- package/dist/substrate/runtime/lease-broker/service.mjs +1 -1
- package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs +22 -3
- package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/index.mjs +9 -0
- package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +20 -3
- package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/selective-restart.mjs +1 -1
- package/dist/substrate/runtime/lifecycle/signals.mjs +5 -5
- package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
- package/dist/substrate/runtime/lifecycle/state-machine.mjs +1 -1
- package/dist/substrate/runtime/managed-container.d.mts +19 -1
- package/dist/substrate/runtime/managed-container.mjs +20 -1
- package/dist/substrate/runtime/managed-container.mjs.map +1 -1
- package/dist/substrate/runtime/manifest/index.mjs +3 -0
- package/dist/substrate/runtime/manifest/manifest.mjs +15 -11
- package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
- package/dist/substrate/runtime/mode-errors.mjs +20 -0
- package/dist/substrate/runtime/mode-errors.mjs.map +1 -0
- package/dist/substrate/runtime/observability/cascade-formatter.mjs +9 -13
- package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
- package/dist/substrate/runtime/observability/formatter-registry.mjs +19 -36
- package/dist/substrate/runtime/observability/formatter-registry.mjs.map +1 -1
- package/dist/substrate/runtime/observability/ignore-with-log.d.mts +1 -0
- package/dist/substrate/runtime/observability/ignore-with-log.mjs +17 -0
- package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -0
- package/dist/substrate/runtime/observability/index.d.mts +1 -1
- package/dist/substrate/runtime/observability/index.mjs +13 -0
- package/dist/substrate/runtime/observability/log-store.d.mts +1 -0
- package/dist/substrate/runtime/observability/log-store.mjs +175 -0
- package/dist/substrate/runtime/observability/log-store.mjs.map +1 -0
- package/dist/substrate/runtime/observability/logger.d.mts +2 -1
- package/dist/substrate/runtime/observability/logger.mjs +5 -9
- package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
- package/dist/substrate/runtime/observability/output-truncate.mjs +35 -0
- package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -0
- package/dist/substrate/runtime/observability/pretty-error.mjs +9 -12
- package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
- package/dist/substrate/runtime/observability/redaction.d.mts +2 -13
- package/dist/substrate/runtime/observability/redaction.mjs +8 -14
- package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
- package/dist/substrate/runtime/observability/span-store.d.mts +1 -0
- package/dist/substrate/runtime/observability/span-store.mjs +110 -0
- package/dist/substrate/runtime/observability/span-store.mjs.map +1 -0
- package/dist/substrate/runtime/observability/spans.mjs +14 -14
- package/dist/substrate/runtime/observability/spans.mjs.map +1 -1
- package/dist/substrate/runtime/observability/subprocess-capture.mjs +1 -1
- package/dist/substrate/runtime/passthrough-or-wrap.mjs +33 -0
- package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -0
- package/dist/substrate/runtime/paths.d.mts +1 -1
- package/dist/substrate/runtime/paths.mjs +4 -3
- package/dist/substrate/runtime/paths.mjs.map +1 -1
- package/dist/substrate/runtime/phase-preserving-produce.mjs +40 -0
- package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -0
- package/dist/substrate/runtime/port-broker/index.mjs +3 -0
- package/dist/substrate/runtime/port-broker/service.mjs +170 -126
- package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs +1 -1
- package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
- package/dist/substrate/runtime/probes.d.mts +7 -5
- package/dist/substrate/runtime/probes.mjs +3 -1
- package/dist/substrate/runtime/probes.mjs.map +1 -1
- package/dist/substrate/runtime/projection/index.mjs +4 -0
- package/dist/substrate/runtime/projection/operational-endpoints.mjs +1 -2
- package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
- package/dist/substrate/runtime/projection/persisted.mjs +33 -15
- package/dist/substrate/runtime/projection/persisted.mjs.map +1 -1
- package/dist/substrate/runtime/projection/state-ref.mjs +26 -2
- package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
- package/dist/substrate/runtime/projection/update.mjs +88 -24
- package/dist/substrate/runtime/projection/update.mjs.map +1 -1
- package/dist/substrate/runtime/random-suffix.mjs +11 -0
- package/dist/substrate/runtime/random-suffix.mjs.map +1 -0
- package/dist/substrate/runtime/retry-policy.d.mts +59 -1
- package/dist/substrate/runtime/retry-policy.mjs +66 -1
- package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
- package/dist/substrate/runtime/routed-url.d.mts +1 -0
- package/dist/substrate/runtime/routed-url.mjs +79 -0
- package/dist/substrate/runtime/routed-url.mjs.map +1 -0
- package/dist/substrate/runtime/runtime-decode.d.mts +1 -1
- package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-http-server.mjs +2 -3
- package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
- package/dist/substrate/runtime/scoped-multimap/index.mjs +2 -0
- package/dist/substrate/runtime/scoped-multimap/service.mjs +52 -0
- package/dist/substrate/runtime/scoped-multimap/service.mjs.map +1 -0
- package/dist/substrate/runtime/scoped-ref-map/index.mjs +2 -0
- package/dist/substrate/runtime/scoped-ref-map/service.mjs +2 -2
- package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +1 -1
- package/dist/substrate/runtime/stage-and-swap/index.mjs +72 -4
- package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
- package/dist/substrate/runtime/state-store/index.d.mts +1 -0
- package/dist/substrate/runtime/state-store/index.mjs +3 -0
- package/dist/substrate/runtime/state-store/schema.d.mts +1 -0
- package/dist/substrate/runtime/state-store/schema.mjs +19 -14
- package/dist/substrate/runtime/state-store/schema.mjs.map +1 -1
- package/dist/substrate/runtime/state-store/service.d.mts +1 -0
- package/dist/substrate/runtime/state-store/service.mjs +145 -0
- package/dist/substrate/runtime/state-store/service.mjs.map +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.d.mts +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs +33 -0
- package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -0
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs +1 -1
- package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
- package/dist/substrate/runtime/strategy-registry/index.mjs +4 -0
- package/dist/substrate/runtime/strategy-registry/service.mjs +19 -33
- package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
- package/dist/substrate/runtime/subnet-broker.mjs +60 -0
- package/dist/substrate/runtime/subnet-broker.mjs.map +1 -0
- package/dist/substrate/runtime/sui-execute/index.d.mts +21 -1
- package/dist/substrate/runtime/sui-execute/index.mjs +84 -20
- package/dist/substrate/runtime/sui-execute/index.mjs.map +1 -1
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.d.mts +1 -0
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs +32 -0
- package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +1 -0
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs +32 -0
- package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +1 -0
- package/dist/substrate/runtime/sui-move-build/index.mjs +93 -19
- package/dist/substrate/runtime/sui-move-build/index.mjs.map +1 -1
- package/dist/substrate/runtime/supervisor/acquire-node.mjs +181 -0
- package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/background-tasks.mjs +144 -0
- package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/command-loop.mjs +68 -0
- package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +151 -0
- package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/errors.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/errors.mjs +9 -0
- package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/index.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/index.mjs +3 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs +51 -0
- package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs +250 -0
- package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/state.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/state.mjs +22 -0
- package/dist/substrate/runtime/supervisor/state.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/teardown.mjs +57 -0
- package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -0
- package/dist/substrate/runtime/supervisor/types.d.mts +1 -0
- package/dist/substrate/runtime/supervisor/wiring.mjs +133 -0
- package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -0
- package/dist/substrate/runtime/typed-env.mjs +8 -0
- package/dist/substrate/runtime/typed-env.mjs.map +1 -0
- package/dist/substrate/state-store.d.mts +1 -0
- package/dist/substrate/versioned-doc-schema.mjs +17 -0
- package/dist/substrate/versioned-doc-schema.mjs.map +1 -0
- package/dist/substrate/versioned-doc-sync.mjs +94 -0
- package/dist/substrate/versioned-doc-sync.mjs.map +1 -0
- package/dist/surfaces/cli/command-tree.mjs.map +1 -1
- package/dist/surfaces/cli/commands/index.mjs +9 -0
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs +13 -0
- package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -0
- package/dist/surfaces/cli/commands/prune-picker.mjs +3 -2
- package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
- package/dist/surfaces/cli/commands/prune.mjs +34 -17
- package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
- package/dist/surfaces/cli/commands/status.mjs +2 -2
- package/dist/surfaces/cli/commands/status.mjs.map +1 -1
- package/dist/surfaces/cli/commands/supervisor-presence.mjs +5 -3
- package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
- package/dist/surfaces/cli/commands/wipe.mjs +38 -4
- package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
- package/dist/surfaces/cli/errors.mjs +1 -1
- package/dist/surfaces/cli/errors.mjs.map +1 -1
- package/dist/surfaces/cli/flags.mjs +0 -1
- package/dist/surfaces/cli/flags.mjs.map +1 -1
- package/dist/surfaces/cli/index.mjs +98 -18
- package/dist/surfaces/cli/index.mjs.map +1 -1
- package/dist/surfaces/cli/output.mjs +1 -0
- package/dist/surfaces/cli/output.mjs.map +1 -1
- package/dist/surfaces/cli/sysexits.mjs +2 -1
- package/dist/surfaces/cli/sysexits.mjs.map +1 -1
- package/dist/surfaces/tui/app.mjs +25 -3
- package/dist/surfaces/tui/app.mjs.map +1 -1
- package/dist/surfaces/tui/display-derivation.mjs +23 -74
- package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
- package/dist/surfaces/tui/event-log.mjs +35 -23
- package/dist/surfaces/tui/event-log.mjs.map +1 -1
- package/dist/surfaces/tui/input.mjs +5 -1
- package/dist/surfaces/tui/input.mjs.map +1 -1
- package/dist/surfaces/tui/plain-renderer.mjs +70 -34
- package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
- package/package.json +24 -4
- package/dist/contracts/liveness-classifier.d.mts +0 -19
- package/dist/contracts/network-resolver.d.mts +0 -15
- package/dist/contracts/network-resolver.mjs +0 -7
- package/dist/contracts/network-resolver.mjs.map +0 -1
- package/dist/orchestrators/codegen/extras.mjs +0 -16
- package/dist/orchestrators/codegen/extras.mjs.map +0 -1
- package/dist/plugins/deepbook/routable.mjs +0 -21
- package/dist/plugins/deepbook/routable.mjs.map +0 -1
- package/dist/plugins/faucet/service.d.mts +0 -20
- package/dist/plugins/faucet/strategies/sui-local.d.mts +0 -23
- package/dist/plugins/faucet/strategies/sui-local.mjs.map +0 -1
- package/dist/plugins/sui/seed-objects.d.mts +0 -18
- package/dist/plugins/sui/seed-objects.mjs +0 -25
- package/dist/plugins/sui/seed-objects.mjs.map +0 -1
- package/dist/runtime/built-in-plugin-layers.mjs.map +0 -1
- package/dist/substrate/runtime/context-helpers.mjs +0 -32
- package/dist/substrate/runtime/context-helpers.mjs.map +0 -1
- package/dist/substrate/runtime/run.mjs.map +0 -1
- package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs +0 -30
- package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs.map +0 -1
- package/dist/substrate/runtime/supervisor.d.mts +0 -1
- package/dist/substrate/runtime/supervisor.mjs +0 -831
- package/dist/substrate/runtime/supervisor.mjs.map +0 -1
- package/dist/surfaces/cli/commands/doctor-probes.mjs.map +0 -1
- /package/dist/{substrate/runtime/strategy-registry/faucet-capability-for.d.mts → plugins/faucet/http.d.mts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.mjs","names":["streamHostTreeTar"],"sources":["../../../src/orchestrators/snapshot/capture.ts"],"sourcesContent":["// Capture pipeline.\n//\n// Architecture § Snapshot lifecycle (capture half):\n//\n// Walk Snapshotable registry; group by plugin.\n// Validate the full capture set.\n// Pause every running managed container (unless already stopped).\n// While paused:\n// docker commit + tag committed images\n// tar host-tree subtrees with mode round-trip\n// collect metadata slice\n// Unpause (always — success AND failure)\n// Stage everything in tempdir.\n// Atomic rename → snapshot catalog entry.\n//\n// The orchestrator is name-blind: it walks `Snapshotable` contributions\n// without referencing any service. Containers are enumerated via the\n// label tuple the participant declared; subtrees via the relative\n// paths the participant declared.\n\nimport { Effect, Exit, FileSystem, Schema, Stream } from 'effect';\n\nimport type {\n\tContainerRuntime,\n\tContainerHandle,\n\tTaggedImageRef,\n} from '../../contracts/container-runtime.ts';\nimport type { ContainerLabelTuple, SnapshotableDecl } from '../../contracts/snapshotable.ts';\nimport { tarHostTree as streamHostTreeTar } from '../../substrate/runtime/host-tree-tar/index.ts';\nimport {\n\tcontainerImagesBundlePath,\n\tcontributionPath,\n\tSnapshotLayout,\n\ttype CapturedContainer,\n\ttype CapturedSubtree,\n\ttype ContributionDoc,\n\ttype IdentitySlice,\n\ttype SnapshotMetadata,\n\ttype SnapshotId,\n\tSNAPSHOT_META_VERSION,\n\tisRestorableContainerImageName,\n\tisSafeSnapshotPathSegment,\n\tSNAPSHOT_CONTRIBUTION_VERSION,\n} from './descriptor.ts';\nimport {\n\tImageBundleTagScanError,\n\treadImageBundleTags,\n\tverifyImageBundleTags,\n} from './image-bundle-tags.ts';\nimport { writeArtifactIntegrity } from './integrity.ts';\nimport { requireIdentity, type IdentityGuardError } from './identity-guard.ts';\nimport { readSnapshotStateDocument, writeSnapshotStateDocument } from './state-document.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\n/** Tagged failure during one capture step. `phase` discriminates so\n * the user-facing message names the failing step. */\nexport class CapturePhaseError extends Schema.TaggedErrorClass<CapturePhaseError>()(\n\t'SnapshotCapturePhaseError',\n\t{\n\t\tphase: Schema.Literals([\n\t\t\t'enumerate-containers',\n\t\t\t'quiesce',\n\t\t\t'pause',\n\t\t\t'commit',\n\t\t\t'save-images',\n\t\t\t'tar-subtree',\n\t\t\t'tar-host-tree',\n\t\t\t'read-state',\n\t\t\t'write-state',\n\t\t\t'write-contribution',\n\t\t\t'write-meta',\n\t\t\t'write-integrity',\n\t\t\t'unpause',\n\t\t]),\n\t\tplugin: Schema.optional(Schema.String),\n\t\tdetail: Schema.String,\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\n// -----------------------------------------------------------------------------\n// Participant — what the orchestrator receives from each plugin\n// -----------------------------------------------------------------------------\n\n/** Capture-time projection of one `SnapshotableDecl` plus the\n * contributing plugin's identity. The orchestrator does not see\n * `SnapshotableDecl` directly — it consumes this normalized shape so\n * the contribution is closed over the plugin's key (the contract\n * doesn't carry it; the substrate stamps it at registration time). */\nexport interface SnapshotParticipant {\n\treadonly plugin: string;\n\treadonly decl: SnapshotableDecl;\n\t/** Identity slice this participant contributes to the guard. The\n\t * orchestrator merges contributions across participants — see\n\t * `identity-guard.ts`. Returned by the participant's preRestore\n\t * hook on RESTORE; on CAPTURE the substrate reads the live value\n\t * via this `captureIdentity` helper (separately wired). */\n\treadonly captureIdentity: Effect.Effect<IdentitySlice>;\n\t/** Opaque JSON metadata the plugin wants snapshotted. The\n\t * contribution document validates only an envelope around this\n\t * payload; the orchestrator never relies on the payload shape. */\n\treadonly captureContribution: Effect.Effect<unknown>;\n}\n\nexport type SnapshotCaptureProgressPhase =\n\t| 'quiescing'\n\t| 'pausing'\n\t| 'paused'\n\t| 'capturing-containers'\n\t| 'saving-images'\n\t| 'capturing-host-tree'\n\t| 'saving-state'\n\t| 'saving-contributions'\n\t| 'writing-metadata'\n\t| 'resuming';\n\nexport interface SnapshotCaptureProgress {\n\treadonly phase: SnapshotCaptureProgressPhase;\n\treadonly detail?: string;\n\treadonly pausedContainers?: number;\n\treadonly totalContainers?: number;\n}\n\nexport type SnapshotProgressReporter = (\n\tprogress: SnapshotCaptureProgress,\n) => Effect.Effect<void, never>;\n\n// -----------------------------------------------------------------------------\n// Staging — populate a directory; the caller wraps in stage-and-swap.\n// -----------------------------------------------------------------------------\n\nconst failPhase =\n\t(\n\t\tphase: CapturePhaseError['phase'],\n\t\tdetail: string,\n\t\tplugin?: string,\n\t): ((cause: unknown) => Effect.Effect<never, CapturePhaseError>) =>\n\t(cause) =>\n\t\tEffect.fail(new CapturePhaseError({ phase, plugin, detail, cause }));\n\nconst failImageBundleTagScan = (\n\tcause: ImageBundleTagScanError,\n): Effect.Effect<never, CapturePhaseError> =>\n\tEffect.fail(new CapturePhaseError({ phase: 'save-images', detail: cause.detail, cause }));\n\ninterface QuiescedContainer {\n\treadonly handle: ContainerHandle;\n\treadonly labels: ContainerLabelTuple;\n\treadonly unpauseAfterCapture: boolean;\n}\n\ninterface PlannedContainerCapture extends QuiescedContainer {\n\treadonly participant: SnapshotParticipant;\n}\n\ninterface ReadyContainerCapture extends PlannedContainerCapture {\n\treadonly commitHandle: ContainerHandle;\n}\n\ninterface CommittedContainerCapture {\n\treadonly captured: CapturedContainer;\n\treadonly imageRef: TaggedImageRef;\n}\n\nconst validateSnapshotPathSegment = (\n\tkind: string,\n\tvalue: string,\n\tphase: CapturePhaseError['phase'],\n\tplugin?: string,\n): Effect.Effect<void, CapturePhaseError> =>\n\tisSafeSnapshotPathSegment(value)\n\t\t? Effect.void\n\t\t: Effect.fail(\n\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\tphase,\n\t\t\t\t\tplugin,\n\t\t\t\t\tdetail: `unsafe snapshot ${kind} path segment: ${value}`,\n\t\t\t\t}),\n\t\t\t);\n\nconst validateCapturedContainer = (\n\thandle: ContainerHandle,\n\tlabels: ContainerLabelTuple,\n\tparticipant: SnapshotParticipant,\n): Effect.Effect<void, CapturePhaseError> =>\n\tEffect.gen(function* () {\n\t\tyield* validateSnapshotPathSegment('plugin', labels.plugin, 'commit', participant.plugin);\n\t\tyield* validateSnapshotPathSegment('role', labels.role, 'commit', participant.plugin);\n\t\tif (!isRestorableContainerImageName(handle.imageName)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\tphase: 'commit',\n\t\t\t\t\tplugin: participant.plugin,\n\t\t\t\t\tdetail: `container ${handle.name} imageName is not a restorable Docker tag destination: ${handle.imageName}`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t});\n\nconst detectContainerArtifactCollisions = (\n\tplanned: ReadonlyArray<PlannedContainerCapture>,\n): Effect.Effect<void, CapturePhaseError> =>\n\tEffect.gen(function* () {\n\t\tconst seen = new Map<string, PlannedContainerCapture>();\n\t\tfor (const candidate of planned) {\n\t\t\tconst key = `${candidate.labels.plugin}/${candidate.labels.role}`;\n\t\t\tconst previous = seen.get(key);\n\t\t\tif (previous !== undefined) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\t\tphase: 'commit',\n\t\t\t\t\t\tplugin: candidate.participant.plugin,\n\t\t\t\t\t\tdetail: `duplicate managed container snapshot identity ${key} for ${previous.handle.name} and ${candidate.handle.name}`,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tseen.set(key, candidate);\n\t\t}\n\t});\n\n/**\n * Enumerate a participant's live managed containers, then run its\n * optional app-level quiesce hook. Already-paused, exited, and\n * created containers are committed as-is and are not unpaused by\n * capture finalization.\n *\n * Architecture § Invariants: \"A running container must be paused\n * around the writable-layer commit; the unpause must fire on both\n * success and failure paths (no orphaned paused containers). A\n * stopped container is already quiescent and must not be paused.\"\n *\n * The default quiescence is `pauseAndCommit`; participants that need\n * an application-level flush (postgres, RocksDB) declare a longer-grace\n * `quiesce` effect on their decl — the orchestrator runs it BEFORE\n * `pauseAndCommit`.\n */\nconst quiesceParticipant = (\n\tparticipant: SnapshotParticipant,\n\truntime: ContainerRuntime,\n): Effect.Effect<ReadonlyArray<QuiescedContainer>, CapturePhaseError> =>\n\tEffect.gen(function* () {\n\t\tconst labelTuples = participant.decl.managedContainers ?? [];\n\t\tconst containers: QuiescedContainer[] = [];\n\t\tfor (const tuple of labelTuples) {\n\t\t\tconst matched = yield* runtime\n\t\t\t\t.inspectByLabels(tuple)\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.catch(\n\t\t\t\t\t\tfailPhase(\n\t\t\t\t\t\t\t'enumerate-containers',\n\t\t\t\t\t\t\t`inspect by labels failed for ${tuple.plugin}/${tuple.role}`,\n\t\t\t\t\t\t\tparticipant.plugin,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\tfor (const handle of matched) {\n\t\t\t\tif (handle.status === 'running') {\n\t\t\t\t\tcontainers.push({ handle, labels: tuple, unpauseAfterCapture: true });\n\t\t\t\t} else if (\n\t\t\t\t\thandle.status === 'paused' ||\n\t\t\t\t\thandle.status === 'exited' ||\n\t\t\t\t\thandle.status === 'created'\n\t\t\t\t) {\n\t\t\t\t\tcontainers.push({ handle, labels: tuple, unpauseAfterCapture: false });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Run the optional app-level flush hook first; default pause\n\t\t// follows. The hook itself only signals failure-or-success in\n\t\t// `Cause<never>` (per `SnapshotableDecl.quiesce`'s typed shape) —\n\t\t// the orchestrator does not surface flush errors as a separate\n\t\t// tag; defects propagate through the underlying Cause.\n\t\tif (participant.decl.quiesce) {\n\t\t\tyield* participant.decl.quiesce.pipe(Effect.scoped, Effect.ignore);\n\t\t}\n\t\treturn containers;\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.capture.quiesce'));\n\n/** Commit one container's writable layer to a temporary snapshot image tag. */\nconst commitContainerToImage = (\n\thandle: ContainerHandle,\n\tlabels: ContainerLabelTuple,\n\truntime: ContainerRuntime,\n\tparticipant: SnapshotParticipant,\n\tregisterCommittedRef: (ref: TaggedImageRef) => Effect.Effect<void>,\n): Effect.Effect<CommittedContainerCapture, CapturePhaseError> =>\n\tEffect.gen(function* () {\n\t\t// pause+commit produces the image; the orchestrator does NOT\n\t\t// unpause here — `runCapture` owns the unpause-on-all-paths\n\t\t// finalizer.\n\t\tconst imageRef = yield* runtime\n\t\t\t.pauseAndCommit(handle)\n\t\t\t.pipe(\n\t\t\t\tEffect.catch(\n\t\t\t\t\tfailPhase('commit', `pauseAndCommit failed for ${handle.name}`, participant.plugin),\n\t\t\t\t),\n\t\t\t);\n\t\tconst snapshotTag = imageRef.tag;\n\t\tyield* registerCommittedRef(imageRef);\n\t\tif (!isRestorableContainerImageName(snapshotTag)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\tphase: 'commit',\n\t\t\t\t\tplugin: participant.plugin,\n\t\t\t\t\tdetail: `committed image for ${handle.name} did not receive a restorable snapshot tag`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\tcaptured: {\n\t\t\t\tplugin: labels.plugin,\n\t\t\t\trole: labels.role,\n\t\t\t\timageName: handle.imageName,\n\t\t\t\tsnapshotTag,\n\t\t\t\ttarPath: containerImagesBundlePath(),\n\t\t\t},\n\t\t\timageRef,\n\t\t};\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.capture.commit'));\n\nconst saveCommittedImages = (\n\tcommitted: ReadonlyArray<CommittedContainerCapture>,\n\tstagingDir: string,\n\truntime: ContainerRuntime,\n): Effect.Effect<void, CapturePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tif (committed.length === 0) return;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst tarPath = containerImagesBundlePath();\n\t\tconst tarDest = `${stagingDir}/${tarPath}`;\n\t\tyield* fs\n\t\t\t.makeDirectory(`${stagingDir}/${SnapshotLayout.containersDir}`, { recursive: true })\n\t\t\t.pipe(Effect.catch(failPhase('save-images', `mkdir containers dir failed`)));\n\t\tyield* Stream.run(\n\t\t\truntime.saveImages(\n\t\t\t\tcommitted.map((entry) => entry.imageRef),\n\t\t\t\t{ removeAfterSave: true },\n\t\t\t),\n\t\t\tfs.sink(tarDest),\n\t\t).pipe(\n\t\t\tEffect.catch(\n\t\t\t\tfailPhase(\n\t\t\t\t\t'save-images',\n\t\t\t\t\t`save ${committed.length} committed container images to ${tarDest} failed`,\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t\tconst savedTags = yield* readImageBundleTags(tarDest, tarPath).pipe(\n\t\t\tEffect.catch(failImageBundleTagScan),\n\t\t);\n\t\tyield* verifyImageBundleTags(\n\t\t\ttarPath,\n\t\t\tsavedTags,\n\t\t\tcommitted.map((entry) => entry.captured.snapshotTag),\n\t\t).pipe(Effect.catch(failImageBundleTagScan));\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.capture.save-images'));\n\nconst cleanupCommittedRefs = (\n\truntime: ContainerRuntime,\n\trefs: ReadonlyArray<TaggedImageRef>,\n): Effect.Effect<void> =>\n\tEffect.forEach(\n\t\trefs,\n\t\t(ref) =>\n\t\t\truntime\n\t\t\t\t.removeImage(ref)\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.logWarning(\n\t\t\t\t\t\t\t`remove committed snapshot image ${ref.tag} failed during capture cleanup: ${String(\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t)}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t{ concurrency: 'unbounded' },\n\t).pipe(Effect.asVoid);\n\nconst isSafeSubtreePath = (relPath: string): boolean =>\n\trelPath !== '' &&\n\trelPath !== '.' &&\n\t!relPath.startsWith('/') &&\n\t!relPath.split(/[\\\\/]+/).includes('..');\n\n/** Resolve declared subtrees against the live stack root. Missing\n * `fine` subtrees are skipped; missing `fatal` subtrees fail before\n * tar starts so the error names the responsible plugin. */\nconst resolveCapturedSubtrees = (\n\tsubtrees: ReadonlyArray<CapturedSubtree>,\n\tstackRoot: string,\n): Effect.Effect<ReadonlyArray<CapturedSubtree>, CapturePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst captured: CapturedSubtree[] = [];\n\t\tfor (const subtree of subtrees) {\n\t\t\tif (!isSafeSubtreePath(subtree.relPath)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\t\tphase: 'tar-subtree',\n\t\t\t\t\t\tplugin: subtree.plugin,\n\t\t\t\t\t\tdetail: `unsafe snapshot subtree path: ${subtree.relPath}`,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst exists = yield* fs\n\t\t\t\t.exists(`${stackRoot}/${subtree.relPath}`)\n\t\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\t\tif (!exists) {\n\t\t\t\tif (subtree.missingTolerance === 'fatal') {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\t\t\tphase: 'tar-subtree',\n\t\t\t\t\t\t\tplugin: subtree.plugin,\n\t\t\t\t\t\t\tdetail: `required snapshot subtree missing: ${subtree.relPath}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcaptured.push(subtree);\n\t\t}\n\t\treturn captured;\n\t});\n\n/** Tar the union of declared live subtrees into one `host-tree.tar`. */\nconst writeHostTreeTar = (\n\tstackRoot: string,\n\tsubtrees: ReadonlyArray<CapturedSubtree>,\n\ttarDest: string,\n): Effect.Effect<void, CapturePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst archive = streamHostTreeTar({\n\t\t\tparentDir: stackRoot,\n\t\t\trelPaths: subtrees.map((s) => s.relPath),\n\t\t});\n\t\tyield* Stream.run(archive, fs.sink(tarDest)).pipe(\n\t\t\tEffect.catch(\n\t\t\t\tfailPhase(\n\t\t\t\t\t'tar-host-tree',\n\t\t\t\t\t`tar host-tree (${subtrees.length} subtrees) to ${tarDest} failed`,\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.capture.tar-host-tree'));\n\n// -----------------------------------------------------------------------------\n// Top-level capture — populate a staging directory; caller wraps in swap.\n// -----------------------------------------------------------------------------\n\nexport interface CaptureInputs {\n\treadonly stagingDir: string;\n\treadonly snapshotId: SnapshotId;\n\treadonly label: string | null;\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly network: string;\n\treadonly runtimeStackRoot: string;\n\treadonly stateFilePath: string;\n\treadonly participants: ReadonlyArray<SnapshotParticipant>;\n\treadonly runtime: ContainerRuntime;\n\treadonly onProgress?: SnapshotProgressReporter;\n}\n\n/**\n * Populate `stagingDir` with a complete snapshot artifact.\n *\n * Discipline:\n * - Iterate participants — no service names appear.\n * - Pause-around-commit with always-unpause via `addFinalizer`.\n * - Mode bits preserved by the host-tree tar primitive.\n * - meta.json is written LAST so a crashed save leaves the artifact\n * invisible to the catalog (architecture § \"Partial saves are inert\").\n *\n * Caller wraps this in `stageAndSwap` + `acquireReservation` + the\n * stack lock.\n */\nexport const runCapture = (\n\tinputs: CaptureInputs,\n): Effect.Effect<SnapshotMetadata, CapturePhaseError | IdentityGuardError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t'devstack.snapshot.phase': 'capture',\n\t\t\t'devstack.snapshot.id': inputs.snapshotId,\n\t\t});\n\n\t\tconst report = (progress: SnapshotCaptureProgress): Effect.Effect<void, never> =>\n\t\t\tinputs.onProgress?.(progress) ?? Effect.void;\n\n\t\treturn yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// 1. Quiesce every participant, validate the full capture\n\t\t\t\t// set, then pause all running managed containers before\n\t\t\t\t// any artifact is written. The pause finalizer covers the\n\t\t\t\t// entire capture pipeline so container commits, host-tree\n\t\t\t\t// tar, scalar state, and contribution metadata share one\n\t\t\t\t// consistent snapshot window.\n\t\t\t\tconst capturedContainers: CapturedContainer[] = [];\n\t\t\t\tconst paused: ContainerHandle[] = [];\n\t\t\t\tyield* Effect.addFinalizer(() =>\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tif (paused.length > 0) {\n\t\t\t\t\t\t\tyield* report({\n\t\t\t\t\t\t\t\tphase: 'resuming',\n\t\t\t\t\t\t\t\tdetail: `unpausing ${paused.length} container${paused.length === 1 ? '' : 's'}`,\n\t\t\t\t\t\t\t\tpausedContainers: paused.length,\n\t\t\t\t\t\t\t\ttotalContainers: paused.length,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield* Effect.forEach(\n\t\t\t\t\t\t\tpaused,\n\t\t\t\t\t\t\t(handle) =>\n\t\t\t\t\t\t\t\tinputs.runtime\n\t\t\t\t\t\t\t\t\t.unpause(handle)\n\t\t\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\t\t\t\t\t\tEffect.logWarning(\n\t\t\t\t\t\t\t\t\t\t\t\t`unpause(${handle.name}) failed during snapshot capture: ${String(cause)}`,\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t\t\t\t);\n\t\t\t\t\t}).pipe(Effect.asVoid),\n\t\t\t\t);\n\t\t\t\tconst plannedContainers: PlannedContainerCapture[] = [];\n\t\t\t\tconst committedRefs: TaggedImageRef[] = [];\n\t\t\t\tyield* Effect.addFinalizer((exit) =>\n\t\t\t\t\tExit.isFailure(exit) ? cleanupCommittedRefs(inputs.runtime, committedRefs) : Effect.void,\n\t\t\t\t);\n\t\t\t\tyield* report({\n\t\t\t\t\tphase: 'quiescing',\n\t\t\t\t\tdetail: `checking ${inputs.participants.length} snapshot participant${\n\t\t\t\t\t\tinputs.participants.length === 1 ? '' : 's'\n\t\t\t\t\t}`,\n\t\t\t\t});\n\t\t\t\tfor (const participant of inputs.participants) {\n\t\t\t\t\tconst containers = yield* quiesceParticipant(participant, inputs.runtime);\n\t\t\t\t\tfor (const { handle, labels, unpauseAfterCapture } of containers) {\n\t\t\t\t\t\tyield* validateCapturedContainer(handle, labels, participant);\n\t\t\t\t\t\tplannedContainers.push({\n\t\t\t\t\t\t\thandle,\n\t\t\t\t\t\t\tlabels,\n\t\t\t\t\t\t\tunpauseAfterCapture,\n\t\t\t\t\t\t\tparticipant,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tyield* detectContainerArtifactCollisions(plannedContainers);\n\t\t\t\tconst runningContainers = plannedContainers.filter((entry) => entry.unpauseAfterCapture);\n\t\t\t\tif (runningContainers.length > 0) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'pausing',\n\t\t\t\t\t\tdetail: `pausing ${runningContainers.length} running container${\n\t\t\t\t\t\t\trunningContainers.length === 1 ? '' : 's'\n\t\t\t\t\t\t}`,\n\t\t\t\t\t\tpausedContainers: 0,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst readyContainers: ReadyContainerCapture[] = [];\n\t\t\t\tfor (const entry of plannedContainers) {\n\t\t\t\t\tconst { handle, unpauseAfterCapture } = entry;\n\t\t\t\t\tif (unpauseAfterCapture) {\n\t\t\t\t\t\tyield* inputs.runtime\n\t\t\t\t\t\t\t.pause(handle)\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\tEffect.catch(\n\t\t\t\t\t\t\t\t\tfailPhase('pause', `pause failed for ${handle.name}`, entry.participant.plugin),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\tpaused.push(handle);\n\t\t\t\t\t}\n\t\t\t\t\treadyContainers.push({\n\t\t\t\t\t\t...entry,\n\t\t\t\t\t\tcommitHandle: unpauseAfterCapture ? { ...handle, status: 'paused' as const } : handle,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (runningContainers.length > 0) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'paused',\n\t\t\t\t\t\tdetail: `stack paused for snapshot (${paused.length}/${runningContainers.length} containers)`,\n\t\t\t\t\t\tpausedContainers: paused.length,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// 2. Commit managed containers while the whole capture set\n\t\t\t\t// remains paused.\n\t\t\t\tconst committedContainers: CommittedContainerCapture[] = [];\n\t\t\t\tif (readyContainers.length > 0) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'capturing-containers',\n\t\t\t\t\t\tdetail: `committing ${readyContainers.length} container${\n\t\t\t\t\t\t\treadyContainers.length === 1 ? '' : 's'\n\t\t\t\t\t\t}`,\n\t\t\t\t\t\tpausedContainers: paused.length,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tfor (const { commitHandle, labels, participant } of readyContainers) {\n\t\t\t\t\tconst committed = yield* commitContainerToImage(\n\t\t\t\t\t\tcommitHandle,\n\t\t\t\t\t\tlabels,\n\t\t\t\t\t\tinputs.runtime,\n\t\t\t\t\t\tparticipant,\n\t\t\t\t\t\t(ref) =>\n\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\tcommittedRefs.push(ref);\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tcommittedContainers.push(committed);\n\t\t\t\t\tcapturedContainers.push(committed.captured);\n\t\t\t\t}\n\t\t\t\tif (committedContainers.length > 0) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'saving-images',\n\t\t\t\t\t\tdetail: `saving ${committedContainers.length} committed image${\n\t\t\t\t\t\t\tcommittedContainers.length === 1 ? '' : 's'\n\t\t\t\t\t\t}`,\n\t\t\t\t\t\tpausedContainers: paused.length,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tyield* saveCommittedImages(committedContainers, inputs.stagingDir, inputs.runtime);\n\n\t\t\t\t// 3. Tar the host-tree subtrees declared by participants.\n\t\t\t\tconst declaredSubtrees: CapturedSubtree[] = inputs.participants.flatMap((p) =>\n\t\t\t\t\tp.decl.subtrees.map((relPath) => ({\n\t\t\t\t\t\tplugin: p.plugin,\n\t\t\t\t\t\trelPath,\n\t\t\t\t\t\tmissingTolerance: p.decl.missingTolerance,\n\t\t\t\t\t\tsecretMaterial: p.decl.secretMaterial ?? false,\n\t\t\t\t\t})),\n\t\t\t\t);\n\t\t\t\tconst subtrees = yield* resolveCapturedSubtrees(declaredSubtrees, inputs.runtimeStackRoot);\n\t\t\t\tconst hostTreeIncluded = subtrees.length > 0;\n\t\t\t\tif (hostTreeIncluded) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'capturing-host-tree',\n\t\t\t\t\t\tdetail: `archiving ${subtrees.length} host subtree${subtrees.length === 1 ? '' : 's'}`,\n\t\t\t\t\t\tpausedContainers: paused.length,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t\tyield* writeHostTreeTar(\n\t\t\t\t\t\tinputs.runtimeStackRoot,\n\t\t\t\t\t\tsubtrees,\n\t\t\t\t\t\t`${inputs.stagingDir}/${SnapshotLayout.hostTreeTar}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// 4. Copy the scalar state file (best-effort missing-OK —\n\t\t\t\t// empty stack on first-boot has no state.json yet).\n\t\t\t\tconst stateExists = yield* fs\n\t\t\t\t\t.exists(inputs.stateFilePath)\n\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\t\t\tif (stateExists) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'saving-state',\n\t\t\t\t\t\tdetail: 'copying runtime state',\n\t\t\t\t\t\tpausedContainers: paused.length,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t\tconst stateDoc = yield* readSnapshotStateDocument(inputs.stateFilePath).pipe(\n\t\t\t\t\t\tEffect.catch(failPhase('read-state', `state.json failed schema validation`)),\n\t\t\t\t\t);\n\t\t\t\t\tyield* writeSnapshotStateDocument(\n\t\t\t\t\t\t`${inputs.stagingDir}/${SnapshotLayout.stateFile}`,\n\t\t\t\t\t\tstateDoc,\n\t\t\t\t\t).pipe(Effect.catch(failPhase('write-state', `write state.json failed`)));\n\t\t\t\t}\n\n\t\t\t\t// 5. Write per-participant contribution docs + collect identity.\n\t\t\t\tyield* report({\n\t\t\t\t\tphase: 'saving-contributions',\n\t\t\t\t\tdetail: `writing ${inputs.participants.length} contribution document${\n\t\t\t\t\t\tinputs.participants.length === 1 ? '' : 's'\n\t\t\t\t\t}`,\n\t\t\t\t\tpausedContainers: paused.length,\n\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t});\n\t\t\t\tyield* fs\n\t\t\t\t\t.makeDirectory(`${inputs.stagingDir}/${SnapshotLayout.contributionsDir}`, {\n\t\t\t\t\t\trecursive: true,\n\t\t\t\t\t})\n\t\t\t\t\t.pipe(Effect.catch(failPhase('write-contribution', `mkdir contributions dir failed`)));\n\t\t\t\tconst identityMerged: Record<string, string> = {};\n\t\t\t\tconst participantKeys: string[] = [];\n\t\t\t\tfor (const participant of inputs.participants) {\n\t\t\t\t\tconst identity = yield* participant.captureIdentity;\n\t\t\t\t\tfor (const [k, v] of Object.entries(identity)) {\n\t\t\t\t\t\t// Conflict-on-same-key handled by identity-guard's\n\t\t\t\t\t\t// `mergeContributions` on the restore side; on capture we\n\t\t\t\t\t\t// last-write-wins (typical: only one plugin contributes\n\t\t\t\t\t\t// each key on capture).\n\t\t\t\t\t\tidentityMerged[k] = v;\n\t\t\t\t\t}\n\t\t\t\t\tconst state = yield* participant.captureContribution;\n\t\t\t\t\tconst doc: ContributionDoc = {\n\t\t\t\t\t\tversion: SNAPSHOT_CONTRIBUTION_VERSION,\n\t\t\t\t\t\tplugin: participant.plugin,\n\t\t\t\t\t\tidentity,\n\t\t\t\t\t\t...(state === undefined\n\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\topaqueState: {\n\t\t\t\t\t\t\t\t\t\tencoding: 'json' as const,\n\t\t\t\t\t\t\t\t\t\tvalue: state,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t};\n\t\t\t\t\tyield* fs\n\t\t\t\t\t\t.writeFileString(\n\t\t\t\t\t\t\t`${inputs.stagingDir}/${contributionPath(participant.plugin)}`,\n\t\t\t\t\t\t\tJSON.stringify(doc, null, 2),\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\tEffect.catch(\n\t\t\t\t\t\t\t\tfailPhase(\n\t\t\t\t\t\t\t\t\t'write-contribution',\n\t\t\t\t\t\t\t\t\t`write contribution doc failed`,\n\t\t\t\t\t\t\t\t\tparticipant.plugin,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\tparticipantKeys.push(participant.plugin);\n\t\t\t\t}\n\t\t\t\tyield* requireIdentity(identityMerged, 'snapshot');\n\n\t\t\t\t// 6. Write meta.json, then integrity over the full artifact.\n\t\t\t\t// The caller publishes via stage-and-swap, so catalog\n\t\t\t\t// readers still never observe a half-written artifact.\n\t\t\t\tyield* report({\n\t\t\t\t\tphase: 'writing-metadata',\n\t\t\t\t\tdetail: 'finalizing snapshot artifact',\n\t\t\t\t\tpausedContainers: paused.length,\n\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t});\n\t\t\t\tconst meta: SnapshotMetadata = {\n\t\t\t\t\tversion: SNAPSHOT_META_VERSION,\n\t\t\t\t\tid: inputs.snapshotId,\n\t\t\t\t\tlabel: inputs.label,\n\t\t\t\t\tcreatedAt: Date.now(),\n\t\t\t\t\tapp: inputs.app,\n\t\t\t\t\tstack: inputs.stack,\n\t\t\t\t\tnetwork: inputs.network,\n\t\t\t\t\thostTreeIncluded,\n\t\t\t\t\tsubtrees,\n\t\t\t\t\tcontainers: capturedContainers,\n\t\t\t\t\tidentity: identityMerged,\n\t\t\t\t\tparticipants: participantKeys,\n\t\t\t\t};\n\t\t\t\tyield* fs\n\t\t\t\t\t.writeFileString(\n\t\t\t\t\t\t`${inputs.stagingDir}/${SnapshotLayout.metaFile}`,\n\t\t\t\t\t\tJSON.stringify(meta, null, 2),\n\t\t\t\t\t)\n\t\t\t\t\t.pipe(Effect.catch(failPhase('write-meta', `write meta.json failed`)));\n\t\t\t\tyield* writeArtifactIntegrity(inputs.stagingDir).pipe(\n\t\t\t\t\tEffect.catch(failPhase('write-integrity', `write integrity.json failed`)),\n\t\t\t\t);\n\n\t\t\t\treturn meta;\n\t\t\t}),\n\t\t);\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.capture'));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"capture.mjs","names":["streamHostTreeTar"],"sources":["../../../src/orchestrators/snapshot/capture.ts"],"sourcesContent":["// Capture pipeline.\n//\n// Architecture § Snapshot lifecycle (capture half):\n//\n// Walk Snapshotable registry; group by plugin.\n// Validate the full capture set.\n// Pause every running managed container (unless already stopped).\n// While paused:\n// docker commit + tag committed images\n// tar host-tree subtrees with mode round-trip\n// collect metadata slice\n// Unpause (always — success AND failure)\n// Stage everything in tempdir.\n// Atomic rename → snapshot catalog entry.\n//\n// The orchestrator is name-blind: it walks `Snapshotable` contributions\n// without referencing any service. Containers are enumerated via the\n// label tuple the participant declared; subtrees via the relative\n// paths the participant declared.\n\nimport { Effect, Exit, FileSystem, Schema, Stream } from 'effect';\n\nimport type {\n\tContainerRuntime,\n\tContainerHandle,\n\tTaggedImageRef,\n} from '../../contracts/container-runtime.ts';\nimport type { ContainerLabelTuple, SnapshotableDecl } from '../../contracts/snapshotable.ts';\nimport { tarHostTree as streamHostTreeTar } from '../../substrate/runtime/host-tree-tar/index.ts';\nimport {\n\tcontainerImagesBundlePath,\n\tcontributionPath,\n\tDEPLOY_CACHE_NAMESPACES,\n\tSnapshotLayout,\n\ttype CapturedContainer,\n\ttype CapturedSubtree,\n\ttype ContributionDoc,\n\ttype IdentitySlice,\n\ttype SnapshotMetadata,\n\ttype SnapshotId,\n\tSNAPSHOT_META_VERSION,\n\tisRestorableContainerImageName,\n\tisSafeSnapshotPathSegment,\n\tSNAPSHOT_CONTRIBUTION_VERSION,\n} from './descriptor.ts';\nimport { CACHE_DIR_NAME } from './wipe.ts';\nimport {\n\tImageBundleTagScanError,\n\treadImageBundleTags,\n\tverifyImageBundleTags,\n} from './image-bundle-tags.ts';\nimport { writeArtifactIntegrity } from './integrity.ts';\nimport { makePhaseFailer } from './phase-error.ts';\nimport {\n\tmergeContributions,\n\trequireIdentity,\n\ttype IdentityContribution,\n\ttype IdentityContributionConflictError,\n\ttype IdentityGuardError,\n} from './identity-guard.ts';\nimport { readSnapshotStateDocument, writeSnapshotStateDocument } from './state-document.ts';\n\n// -----------------------------------------------------------------------------\n// Errors\n// -----------------------------------------------------------------------------\n\n/** Tagged failure during one capture step. `phase` discriminates so\n * the user-facing message names the failing step. */\nexport class CapturePhaseError extends Schema.TaggedErrorClass<CapturePhaseError>()(\n\t'SnapshotCapturePhaseError',\n\t{\n\t\tphase: Schema.Literals([\n\t\t\t'enumerate-containers',\n\t\t\t'quiesce',\n\t\t\t'pause',\n\t\t\t'commit',\n\t\t\t'save-images',\n\t\t\t'tar-subtree',\n\t\t\t'tar-host-tree',\n\t\t\t'read-state',\n\t\t\t'write-state',\n\t\t\t'write-contribution',\n\t\t\t'write-meta',\n\t\t\t'write-integrity',\n\t\t\t'unpause',\n\t\t]),\n\t\tplugin: Schema.optional(Schema.String),\n\t\tdetail: Schema.String,\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\n// -----------------------------------------------------------------------------\n// Participant — what the orchestrator receives from each plugin\n// -----------------------------------------------------------------------------\n\n/** Capture-time projection of one `SnapshotableDecl` plus the\n * contributing plugin's identity. The orchestrator does not see\n * `SnapshotableDecl` directly — it consumes this normalized shape so\n * the contribution is closed over the plugin's key (the contract\n * doesn't carry it; the substrate stamps it at registration time). */\nexport interface SnapshotParticipant {\n\treadonly plugin: string;\n\treadonly decl: SnapshotableDecl;\n\t/** Identity slice this participant contributes to the guard. The\n\t * orchestrator merges contributions across participants — see\n\t * `identity-guard.ts`. Returned by the participant's preRestore\n\t * hook on RESTORE; on CAPTURE the substrate reads the live value\n\t * via this `captureIdentity` helper (separately wired). */\n\treadonly captureIdentity: Effect.Effect<IdentitySlice>;\n\t/** Opaque JSON metadata the plugin wants snapshotted. The\n\t * contribution document validates only an envelope around this\n\t * payload; the orchestrator never relies on the payload shape. */\n\treadonly captureContribution: Effect.Effect<unknown>;\n}\n\nexport type SnapshotCaptureProgressPhase =\n\t| 'quiescing'\n\t| 'pausing'\n\t| 'paused'\n\t| 'capturing-containers'\n\t| 'saving-images'\n\t| 'capturing-host-tree'\n\t| 'saving-state'\n\t| 'saving-contributions'\n\t| 'writing-metadata'\n\t| 'resuming';\n\nexport interface SnapshotCaptureProgress {\n\treadonly phase: SnapshotCaptureProgressPhase;\n\treadonly detail?: string;\n\treadonly pausedContainers?: number;\n\treadonly totalContainers?: number;\n}\n\nexport type SnapshotProgressReporter = (\n\tprogress: SnapshotCaptureProgress,\n) => Effect.Effect<void, never>;\n\n// -----------------------------------------------------------------------------\n// Staging — populate a directory; the caller wraps in stage-and-swap.\n// -----------------------------------------------------------------------------\n\nconst failPhase = makePhaseFailer(CapturePhaseError);\n\nconst failImageBundleTagScan = (\n\tcause: ImageBundleTagScanError,\n): Effect.Effect<never, CapturePhaseError> =>\n\tEffect.fail(new CapturePhaseError({ phase: 'save-images', detail: cause.detail, cause }));\n\ninterface QuiescedContainer {\n\treadonly handle: ContainerHandle;\n\treadonly labels: ContainerLabelTuple;\n\treadonly unpauseAfterCapture: boolean;\n}\n\ninterface PlannedContainerCapture extends QuiescedContainer {\n\treadonly participant: SnapshotParticipant;\n}\n\ninterface ReadyContainerCapture extends PlannedContainerCapture {\n\treadonly commitHandle: ContainerHandle;\n}\n\ninterface CommittedContainerCapture {\n\treadonly captured: CapturedContainer;\n\treadonly imageRef: TaggedImageRef;\n}\n\nconst validateSnapshotPathSegment = (\n\tkind: string,\n\tvalue: string,\n\tphase: CapturePhaseError['phase'],\n\tplugin?: string,\n): Effect.Effect<void, CapturePhaseError> =>\n\tisSafeSnapshotPathSegment(value)\n\t\t? Effect.void\n\t\t: Effect.fail(\n\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\tphase,\n\t\t\t\t\tplugin,\n\t\t\t\t\tdetail: `unsafe snapshot ${kind} path segment: ${value}`,\n\t\t\t\t}),\n\t\t\t);\n\nconst validateCapturedContainer = (\n\thandle: ContainerHandle,\n\tlabels: ContainerLabelTuple,\n\tparticipant: SnapshotParticipant,\n): Effect.Effect<void, CapturePhaseError> =>\n\tEffect.gen(function* () {\n\t\tyield* validateSnapshotPathSegment('plugin', labels.plugin, 'commit', participant.plugin);\n\t\tyield* validateSnapshotPathSegment('role', labels.role, 'commit', participant.plugin);\n\t\tif (!isRestorableContainerImageName(handle.imageName)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\tphase: 'commit',\n\t\t\t\t\tplugin: participant.plugin,\n\t\t\t\t\tdetail: `container ${handle.name} imageName is not a restorable Docker tag destination: ${handle.imageName}`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t});\n\nconst detectContainerArtifactCollisions = (\n\tplanned: ReadonlyArray<PlannedContainerCapture>,\n): Effect.Effect<void, CapturePhaseError> =>\n\tEffect.gen(function* () {\n\t\tconst seen = new Map<string, PlannedContainerCapture>();\n\t\tfor (const candidate of planned) {\n\t\t\tconst key = `${candidate.labels.plugin}/${candidate.labels.role}`;\n\t\t\tconst previous = seen.get(key);\n\t\t\tif (previous !== undefined) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\t\tphase: 'commit',\n\t\t\t\t\t\tplugin: candidate.participant.plugin,\n\t\t\t\t\t\tdetail: `duplicate managed container snapshot identity ${key} for ${previous.handle.name} and ${candidate.handle.name}`,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tseen.set(key, candidate);\n\t\t}\n\t});\n\n/**\n * Enumerate a participant's live managed containers, then run its\n * optional app-level quiesce hook. Already-paused, exited, and\n * created containers are committed as-is and are not unpaused by\n * capture finalization.\n *\n * Architecture § Invariants: \"A running container must be paused\n * around the writable-layer commit; the unpause must fire on both\n * success and failure paths (no orphaned paused containers). A\n * stopped container is already quiescent and must not be paused.\"\n *\n * The default quiescence is `pauseAndCommit`; participants that need\n * an application-level flush (postgres, RocksDB) declare a longer-grace\n * `quiesce` effect on their decl — the orchestrator runs it BEFORE\n * `pauseAndCommit`.\n */\nconst quiesceParticipant = (\n\tparticipant: SnapshotParticipant,\n\truntime: ContainerRuntime,\n): Effect.Effect<ReadonlyArray<QuiescedContainer>, CapturePhaseError> =>\n\tEffect.gen(function* () {\n\t\tconst labelTuples = participant.decl.managedContainers ?? [];\n\t\tconst containers: QuiescedContainer[] = [];\n\t\tfor (const tuple of labelTuples) {\n\t\t\tconst matched = yield* runtime\n\t\t\t\t.inspectByLabels(tuple)\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.catch(\n\t\t\t\t\t\tfailPhase(\n\t\t\t\t\t\t\t'enumerate-containers',\n\t\t\t\t\t\t\t`inspect by labels failed for ${tuple.plugin}/${tuple.role}`,\n\t\t\t\t\t\t\tparticipant.plugin,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\tfor (const handle of matched) {\n\t\t\t\tif (handle.status === 'running') {\n\t\t\t\t\tcontainers.push({ handle, labels: tuple, unpauseAfterCapture: true });\n\t\t\t\t} else if (\n\t\t\t\t\thandle.status === 'paused' ||\n\t\t\t\t\thandle.status === 'exited' ||\n\t\t\t\t\thandle.status === 'created'\n\t\t\t\t) {\n\t\t\t\t\tcontainers.push({ handle, labels: tuple, unpauseAfterCapture: false });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Run the optional app-level flush hook first; default pause\n\t\t// follows. The hook itself only signals failure-or-success in\n\t\t// `Cause<never>` (per `SnapshotableDecl.quiesce`'s typed shape) —\n\t\t// the orchestrator does not surface flush errors as a separate\n\t\t// tag; defects propagate through the underlying Cause.\n\t\tif (participant.decl.quiesce) {\n\t\t\tyield* participant.decl.quiesce.pipe(Effect.scoped, Effect.ignore);\n\t\t}\n\t\treturn containers;\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.capture.quiesce'));\n\n/** Commit one container's writable layer to a temporary snapshot image tag. */\nconst commitContainerToImage = (\n\thandle: ContainerHandle,\n\tlabels: ContainerLabelTuple,\n\truntime: ContainerRuntime,\n\tparticipant: SnapshotParticipant,\n\tregisterCommittedRef: (ref: TaggedImageRef) => Effect.Effect<void>,\n): Effect.Effect<CommittedContainerCapture, CapturePhaseError> =>\n\tEffect.gen(function* () {\n\t\t// pause+commit produces the image; the orchestrator does NOT\n\t\t// unpause here — `runCapture` owns the unpause-on-all-paths\n\t\t// finalizer.\n\t\tconst imageRef = yield* runtime\n\t\t\t.pauseAndCommit(handle)\n\t\t\t.pipe(\n\t\t\t\tEffect.catch(\n\t\t\t\t\tfailPhase('commit', `pauseAndCommit failed for ${handle.name}`, participant.plugin),\n\t\t\t\t),\n\t\t\t);\n\t\tconst snapshotTag = imageRef.tag;\n\t\tyield* registerCommittedRef(imageRef);\n\t\tif (!isRestorableContainerImageName(snapshotTag)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\tphase: 'commit',\n\t\t\t\t\tplugin: participant.plugin,\n\t\t\t\t\tdetail: `committed image for ${handle.name} did not receive a restorable snapshot tag`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\tcaptured: {\n\t\t\t\tplugin: labels.plugin,\n\t\t\t\trole: labels.role,\n\t\t\t\timageName: handle.imageName,\n\t\t\t\tsnapshotTag,\n\t\t\t\ttarPath: containerImagesBundlePath(),\n\t\t\t},\n\t\t\timageRef,\n\t\t};\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.capture.commit'));\n\nconst saveCommittedImages = (\n\tcommitted: ReadonlyArray<CommittedContainerCapture>,\n\tstagingDir: string,\n\truntime: ContainerRuntime,\n): Effect.Effect<void, CapturePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tif (committed.length === 0) return;\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst tarPath = containerImagesBundlePath();\n\t\tconst tarDest = `${stagingDir}/${tarPath}`;\n\t\tyield* fs\n\t\t\t.makeDirectory(`${stagingDir}/${SnapshotLayout.containersDir}`, { recursive: true })\n\t\t\t.pipe(Effect.catch(failPhase('save-images', `mkdir containers dir failed`)));\n\t\tyield* Stream.run(\n\t\t\truntime.saveImages(\n\t\t\t\tcommitted.map((entry) => entry.imageRef),\n\t\t\t\t{ removeAfterSave: true },\n\t\t\t),\n\t\t\tfs.sink(tarDest),\n\t\t).pipe(\n\t\t\tEffect.catch(\n\t\t\t\tfailPhase(\n\t\t\t\t\t'save-images',\n\t\t\t\t\t`save ${committed.length} committed container images to ${tarDest} failed`,\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t\tconst savedTags = yield* readImageBundleTags(tarDest, tarPath).pipe(\n\t\t\tEffect.catch(failImageBundleTagScan),\n\t\t);\n\t\tyield* verifyImageBundleTags(\n\t\t\ttarPath,\n\t\t\tsavedTags,\n\t\t\tcommitted.map((entry) => entry.captured.snapshotTag),\n\t\t).pipe(Effect.catch(failImageBundleTagScan));\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.capture.save-images'));\n\nconst cleanupCommittedRefs = (\n\truntime: ContainerRuntime,\n\trefs: ReadonlyArray<TaggedImageRef>,\n): Effect.Effect<void> =>\n\tEffect.forEach(\n\t\trefs,\n\t\t(ref) =>\n\t\t\truntime\n\t\t\t\t.removeImage(ref)\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\tEffect.logWarning(\n\t\t\t\t\t\t\t`remove committed snapshot image ${ref.tag} failed during capture cleanup: ${String(\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t)}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t{ concurrency: 'unbounded' },\n\t).pipe(Effect.asVoid);\n\nconst isSafeSubtreePath = (relPath: string): boolean =>\n\trelPath !== '' &&\n\trelPath !== '.' &&\n\t!relPath.startsWith('/') &&\n\t!relPath.split(/[\\\\/]+/).includes('..');\n\n/** Resolve declared subtrees against the live stack root. Missing\n * `fine` subtrees are skipped; missing `fatal` subtrees fail before\n * tar starts so the error names the responsible plugin. */\nconst resolveCapturedSubtrees = (\n\tsubtrees: ReadonlyArray<CapturedSubtree>,\n\tstackRoot: string,\n): Effect.Effect<ReadonlyArray<CapturedSubtree>, CapturePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst captured: CapturedSubtree[] = [];\n\t\tfor (const subtree of subtrees) {\n\t\t\tif (!isSafeSubtreePath(subtree.relPath)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\t\tphase: 'tar-subtree',\n\t\t\t\t\t\tplugin: subtree.plugin,\n\t\t\t\t\t\tdetail: `unsafe snapshot subtree path: ${subtree.relPath}`,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst exists = yield* fs\n\t\t\t\t.exists(`${stackRoot}/${subtree.relPath}`)\n\t\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\t\tif (!exists) {\n\t\t\t\tif (subtree.missingTolerance === 'fatal') {\n\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\tnew CapturePhaseError({\n\t\t\t\t\t\t\tphase: 'tar-subtree',\n\t\t\t\t\t\t\tplugin: subtree.plugin,\n\t\t\t\t\t\t\tdetail: `required snapshot subtree missing: ${subtree.relPath}`,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcaptured.push(subtree);\n\t\t}\n\t\treturn captured;\n\t});\n\n/** Tar the union of declared live subtrees into one `host-tree.tar`. */\nconst writeHostTreeTar = (\n\tstackRoot: string,\n\tsubtrees: ReadonlyArray<CapturedSubtree>,\n\ttarDest: string,\n): Effect.Effect<void, CapturePhaseError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst archive = streamHostTreeTar({\n\t\t\tparentDir: stackRoot,\n\t\t\trelPaths: subtrees.map((s) => s.relPath),\n\t\t});\n\t\tyield* Stream.run(archive, fs.sink(tarDest)).pipe(\n\t\t\tEffect.catch(\n\t\t\t\tfailPhase(\n\t\t\t\t\t'tar-host-tree',\n\t\t\t\t\t`tar host-tree (${subtrees.length} subtrees) to ${tarDest} failed`,\n\t\t\t\t),\n\t\t\t),\n\t\t);\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.capture.tar-host-tree'));\n\n// -----------------------------------------------------------------------------\n// Top-level capture — populate a staging directory; caller wraps in swap.\n// -----------------------------------------------------------------------------\n\nexport interface CaptureInputs {\n\treadonly stagingDir: string;\n\treadonly snapshotId: SnapshotId;\n\treadonly label: string | null;\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly network: string;\n\treadonly runtimeStackRoot: string;\n\treadonly stateFilePath: string;\n\treadonly participants: ReadonlyArray<SnapshotParticipant>;\n\treadonly runtime: ContainerRuntime;\n\treadonly onProgress?: SnapshotProgressReporter;\n}\n\n/**\n * Populate `stagingDir` with a complete snapshot artifact.\n *\n * Discipline:\n * - Iterate participants — no service names appear.\n * - Pause-around-commit with always-unpause via `addFinalizer`.\n * - Mode bits preserved by the host-tree tar primitive.\n * - meta.json is written LAST so a crashed save leaves the artifact\n * invisible to the catalog (architecture § \"Partial saves are inert\").\n *\n * Caller wraps this in `stageAndSwap` + `acquireReservation` + the\n * stack lock.\n */\nexport const runCapture = (\n\tinputs: CaptureInputs,\n): Effect.Effect<\n\tSnapshotMetadata,\n\tCapturePhaseError | IdentityGuardError | IdentityContributionConflictError,\n\tFileSystem.FileSystem\n> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t'devstack.snapshot.phase': 'capture',\n\t\t\t'devstack.snapshot.id': inputs.snapshotId,\n\t\t});\n\n\t\tconst report = (progress: SnapshotCaptureProgress): Effect.Effect<void, never> =>\n\t\t\tinputs.onProgress?.(progress) ?? Effect.void;\n\n\t\treturn yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\t// 1. Quiesce every participant, validate the full capture\n\t\t\t\t// set, then pause all running managed containers before\n\t\t\t\t// any artifact is written. The pause finalizer covers the\n\t\t\t\t// entire capture pipeline so container commits, host-tree\n\t\t\t\t// tar, scalar state, and contribution metadata share one\n\t\t\t\t// consistent snapshot window.\n\t\t\t\tconst capturedContainers: CapturedContainer[] = [];\n\t\t\t\t// Two buckets:\n\t\t\t\t//\n\t\t\t\t// `pauseIntended` — every container the orchestrator\n\t\t\t\t// ASKED Docker to pause, recorded BEFORE the pause\n\t\t\t\t// syscall. The finalizer attempts `unpause` for every\n\t\t\t\t// one of these so a `docker pause` failure that left\n\t\t\t\t// the engine in a half-paused state (race: SIGSTOP\n\t\t\t\t// delivered, then `pause` returns non-zero on a\n\t\t\t\t// downstream check; ambiguous Docker daemon behavior —\n\t\t\t\t// `pause` is a multi-step engine op against cgroups +\n\t\t\t\t// the freezer) still gets `unpause`'d on the way out.\n\t\t\t\t//\n\t\t\t\t// `pauseConfirmed` — entries Docker confirmed paused\n\t\t\t\t// (i.e. pause returned success). Used only for progress\n\t\t\t\t// reporting so the operator sees the same count that\n\t\t\t\t// the engine acknowledged.\n\t\t\t\t//\n\t\t\t\t// The `unpause` finalizer treats this as best-effort:\n\t\t\t\t// `unpause` against a non-paused container is the typical\n\t\t\t\t// Docker \"container not paused\" error which we silently\n\t\t\t\t// swallow. Any other failure is logged but does not\n\t\t\t\t// interrupt the finalizer.\n\t\t\t\tconst pauseIntended: ContainerHandle[] = [];\n\t\t\t\tconst pauseConfirmed: ContainerHandle[] = [];\n\t\t\t\tyield* Effect.addFinalizer(() =>\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tif (pauseIntended.length > 0) {\n\t\t\t\t\t\t\tyield* report({\n\t\t\t\t\t\t\t\tphase: 'resuming',\n\t\t\t\t\t\t\t\tdetail: `unpausing ${pauseIntended.length} container${pauseIntended.length === 1 ? '' : 's'}`,\n\t\t\t\t\t\t\t\tpausedContainers: pauseConfirmed.length,\n\t\t\t\t\t\t\t\ttotalContainers: pauseIntended.length,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield* Effect.forEach(\n\t\t\t\t\t\t\tpauseIntended,\n\t\t\t\t\t\t\t(handle) =>\n\t\t\t\t\t\t\t\tinputs.runtime\n\t\t\t\t\t\t\t\t\t.unpause(handle)\n\t\t\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\t\t\t\t\t\t\tEffect.logWarning(\n\t\t\t\t\t\t\t\t\t\t\t\t`unpause(${handle.name}) failed during snapshot capture: ${String(cause)}`,\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t\t\t\t);\n\t\t\t\t\t}).pipe(Effect.asVoid),\n\t\t\t\t);\n\t\t\t\tconst plannedContainers: PlannedContainerCapture[] = [];\n\t\t\t\tconst committedRefs: TaggedImageRef[] = [];\n\t\t\t\tyield* Effect.addFinalizer((exit) =>\n\t\t\t\t\tExit.isFailure(exit) ? cleanupCommittedRefs(inputs.runtime, committedRefs) : Effect.void,\n\t\t\t\t);\n\t\t\t\tyield* report({\n\t\t\t\t\tphase: 'quiescing',\n\t\t\t\t\tdetail: `checking ${inputs.participants.length} snapshot participant${\n\t\t\t\t\t\tinputs.participants.length === 1 ? '' : 's'\n\t\t\t\t\t}`,\n\t\t\t\t});\n\t\t\t\tfor (const participant of inputs.participants) {\n\t\t\t\t\tconst containers = yield* quiesceParticipant(participant, inputs.runtime);\n\t\t\t\t\tfor (const { handle, labels, unpauseAfterCapture } of containers) {\n\t\t\t\t\t\tyield* validateCapturedContainer(handle, labels, participant);\n\t\t\t\t\t\tplannedContainers.push({\n\t\t\t\t\t\t\thandle,\n\t\t\t\t\t\t\tlabels,\n\t\t\t\t\t\t\tunpauseAfterCapture,\n\t\t\t\t\t\t\tparticipant,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tyield* detectContainerArtifactCollisions(plannedContainers);\n\t\t\t\tconst runningContainers = plannedContainers.filter((entry) => entry.unpauseAfterCapture);\n\t\t\t\tif (runningContainers.length > 0) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'pausing',\n\t\t\t\t\t\tdetail: `pausing ${runningContainers.length} running container${\n\t\t\t\t\t\t\trunningContainers.length === 1 ? '' : 's'\n\t\t\t\t\t\t}`,\n\t\t\t\t\t\tpausedContainers: 0,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst readyContainers: ReadyContainerCapture[] = [];\n\t\t\t\tfor (const entry of plannedContainers) {\n\t\t\t\t\tconst { handle, unpauseAfterCapture } = entry;\n\t\t\t\t\tif (unpauseAfterCapture) {\n\t\t\t\t\t\t// Stamp INTENT before issuing the pause. If\n\t\t\t\t\t\t// `pause` fails partway through (Docker daemon\n\t\t\t\t\t\t// may have already moved the freezer into a\n\t\t\t\t\t\t// transitional state before returning non-zero),\n\t\t\t\t\t\t// the addFinalizer above will still attempt\n\t\t\t\t\t\t// `unpause(handle)` — `unpause` is a no-op for a\n\t\t\t\t\t\t// non-paused container so the worst case is a\n\t\t\t\t\t\t// swallowed \"container not paused\" log line.\n\t\t\t\t\t\t// Without this stamp, a half-paused container is\n\t\t\t\t\t\t// silently abandoned to its frozen state.\n\t\t\t\t\t\tpauseIntended.push(handle);\n\t\t\t\t\t\tyield* inputs.runtime\n\t\t\t\t\t\t\t.pause(handle)\n\t\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\t\tEffect.catch(\n\t\t\t\t\t\t\t\t\tfailPhase('pause', `pause failed for ${handle.name}`, entry.participant.plugin),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\tpauseConfirmed.push(handle);\n\t\t\t\t\t}\n\t\t\t\t\treadyContainers.push({\n\t\t\t\t\t\t...entry,\n\t\t\t\t\t\tcommitHandle: unpauseAfterCapture ? { ...handle, status: 'paused' as const } : handle,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (runningContainers.length > 0) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'paused',\n\t\t\t\t\t\tdetail: `stack paused for snapshot (${pauseConfirmed.length}/${runningContainers.length} containers)`,\n\t\t\t\t\t\tpausedContainers: pauseConfirmed.length,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// 2. Commit managed containers while the whole capture set\n\t\t\t\t// remains paused.\n\t\t\t\tconst committedContainers: CommittedContainerCapture[] = [];\n\t\t\t\tif (readyContainers.length > 0) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'capturing-containers',\n\t\t\t\t\t\tdetail: `committing ${readyContainers.length} container${\n\t\t\t\t\t\t\treadyContainers.length === 1 ? '' : 's'\n\t\t\t\t\t\t}`,\n\t\t\t\t\t\tpausedContainers: pauseConfirmed.length,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tfor (const { commitHandle, labels, participant } of readyContainers) {\n\t\t\t\t\tconst committed = yield* commitContainerToImage(\n\t\t\t\t\t\tcommitHandle,\n\t\t\t\t\t\tlabels,\n\t\t\t\t\t\tinputs.runtime,\n\t\t\t\t\t\tparticipant,\n\t\t\t\t\t\t(ref) =>\n\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\tcommittedRefs.push(ref);\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tcommittedContainers.push(committed);\n\t\t\t\t\tcapturedContainers.push(committed.captured);\n\t\t\t\t}\n\t\t\t\tif (committedContainers.length > 0) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'saving-images',\n\t\t\t\t\t\tdetail: `saving ${committedContainers.length} committed image${\n\t\t\t\t\t\t\tcommittedContainers.length === 1 ? '' : 's'\n\t\t\t\t\t\t}`,\n\t\t\t\t\t\tpausedContainers: pauseConfirmed.length,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tyield* saveCommittedImages(committedContainers, inputs.stagingDir, inputs.runtime);\n\n\t\t\t\t// 3. Tar the host-tree subtrees declared by participants.\n\t\t\t\tconst declaredSubtrees: CapturedSubtree[] = [\n\t\t\t\t\t...inputs.participants.flatMap((p) =>\n\t\t\t\t\t\tp.decl.subtrees.map((relPath) => ({\n\t\t\t\t\t\t\tplugin: p.plugin,\n\t\t\t\t\t\t\trelPath,\n\t\t\t\t\t\t\tmissingTolerance: p.decl.missingTolerance,\n\t\t\t\t\t\t\tsecretMaterial: p.decl.secretMaterial ?? false,\n\t\t\t\t\t\t})),\n\t\t\t\t\t),\n\t\t\t\t\t// Capture the deploy/mint artifact caches so the snapshot is\n\t\t\t\t\t// SELF-CONTAINED: the cached on-chain ids ride the tar and are\n\t\t\t\t\t// restored even when the live cache is gone (`snapshot → wipe →\n\t\t\t\t\t// fresh boot → restore`). `fine` tolerance — a stack with no\n\t\t\t\t\t// deploys yet simply has no `cache/<ns>` dir to capture. The\n\t\t\t\t\t// restore side ALSO preserves the live copy (the in-place fast\n\t\t\t\t\t// path) — see LIVE_RESTORE_PRESERVED_PATHS in restore.ts.\n\t\t\t\t\t...DEPLOY_CACHE_NAMESPACES.map(\n\t\t\t\t\t\t(ns): CapturedSubtree => ({\n\t\t\t\t\t\t\tplugin: 'snapshot/deploy-cache',\n\t\t\t\t\t\t\trelPath: `${CACHE_DIR_NAME}/${ns}`,\n\t\t\t\t\t\t\tmissingTolerance: 'fine',\n\t\t\t\t\t\t\tsecretMaterial: false,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t];\n\t\t\t\tconst subtrees = yield* resolveCapturedSubtrees(declaredSubtrees, inputs.runtimeStackRoot);\n\t\t\t\tconst hostTreeIncluded = subtrees.length > 0;\n\t\t\t\tif (hostTreeIncluded) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'capturing-host-tree',\n\t\t\t\t\t\tdetail: `archiving ${subtrees.length} host subtree${subtrees.length === 1 ? '' : 's'}`,\n\t\t\t\t\t\tpausedContainers: pauseConfirmed.length,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t\tyield* writeHostTreeTar(\n\t\t\t\t\t\tinputs.runtimeStackRoot,\n\t\t\t\t\t\tsubtrees,\n\t\t\t\t\t\t`${inputs.stagingDir}/${SnapshotLayout.hostTreeTar}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// 4. Copy the scalar state file (best-effort missing-OK —\n\t\t\t\t// empty stack on first-boot has no state.json yet).\n\t\t\t\tconst stateExists = yield* fs\n\t\t\t\t\t.exists(inputs.stateFilePath)\n\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed(false)));\n\t\t\t\tif (stateExists) {\n\t\t\t\t\tyield* report({\n\t\t\t\t\t\tphase: 'saving-state',\n\t\t\t\t\t\tdetail: 'copying runtime state',\n\t\t\t\t\t\tpausedContainers: pauseConfirmed.length,\n\t\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t\t});\n\t\t\t\t\tconst stateDoc = yield* readSnapshotStateDocument(inputs.stateFilePath).pipe(\n\t\t\t\t\t\tEffect.catch(failPhase('read-state', `state.json failed schema validation`)),\n\t\t\t\t\t);\n\t\t\t\t\tyield* writeSnapshotStateDocument(\n\t\t\t\t\t\t`${inputs.stagingDir}/${SnapshotLayout.stateFile}`,\n\t\t\t\t\t\tstateDoc,\n\t\t\t\t\t).pipe(Effect.catch(failPhase('write-state', `write state.json failed`)));\n\t\t\t\t}\n\n\t\t\t\t// 5. Write per-participant contribution docs + collect identity.\n\t\t\t\tyield* report({\n\t\t\t\t\tphase: 'saving-contributions',\n\t\t\t\t\tdetail: `writing ${inputs.participants.length} contribution document${\n\t\t\t\t\t\tinputs.participants.length === 1 ? '' : 's'\n\t\t\t\t\t}`,\n\t\t\t\t\tpausedContainers: pauseConfirmed.length,\n\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t});\n\t\t\t\tyield* fs\n\t\t\t\t\t.makeDirectory(`${inputs.stagingDir}/${SnapshotLayout.contributionsDir}`, {\n\t\t\t\t\t\trecursive: true,\n\t\t\t\t\t})\n\t\t\t\t\t.pipe(Effect.catch(failPhase('write-contribution', `mkdir contributions dir failed`)));\n\t\t\t\t// Collect per-participant identity slices first so the same\n\t\t\t\t// `mergeContributions` predicate the restore-side guard uses\n\t\t\t\t// rejects intra-capture conflicts here too. A capture that\n\t\t\t\t// silently last-write-wins would hide a conflict the\n\t\t\t\t// downstream restore would later reject as\n\t\t\t\t// `IdentityContributionConflictError` — surface it AT THE\n\t\t\t\t// CAPTURE SITE instead so the operator can fix the offending\n\t\t\t\t// plugins before the broken artifact is even written.\n\t\t\t\tconst identityContributions: IdentityContribution[] = [];\n\t\t\t\tconst stateByParticipant = new Map<string, unknown>();\n\t\t\t\tconst participantKeys: string[] = [];\n\t\t\t\tfor (const participant of inputs.participants) {\n\t\t\t\t\tconst identity = yield* participant.captureIdentity;\n\t\t\t\t\tidentityContributions.push({ plugin: participant.plugin, slice: identity });\n\t\t\t\t\tconst state = yield* participant.captureContribution;\n\t\t\t\t\tstateByParticipant.set(participant.plugin, state);\n\t\t\t\t}\n\t\t\t\tconst identityMerged = yield* mergeContributions(identityContributions);\n\t\t\t\tfor (const participant of inputs.participants) {\n\t\t\t\t\tconst identity =\n\t\t\t\t\t\tidentityContributions.find((c) => c.plugin === participant.plugin)?.slice ?? {};\n\t\t\t\t\tconst state = stateByParticipant.get(participant.plugin);\n\t\t\t\t\tconst doc: ContributionDoc = {\n\t\t\t\t\t\tversion: SNAPSHOT_CONTRIBUTION_VERSION,\n\t\t\t\t\t\tplugin: participant.plugin,\n\t\t\t\t\t\tidentity,\n\t\t\t\t\t\t...(state === undefined\n\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\topaqueState: {\n\t\t\t\t\t\t\t\t\t\tencoding: 'json' as const,\n\t\t\t\t\t\t\t\t\t\tvalue: state,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t};\n\t\t\t\t\tyield* fs\n\t\t\t\t\t\t.writeFileString(\n\t\t\t\t\t\t\t`${inputs.stagingDir}/${contributionPath(participant.plugin)}`,\n\t\t\t\t\t\t\tJSON.stringify(doc, null, 2),\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.pipe(\n\t\t\t\t\t\t\tEffect.catch(\n\t\t\t\t\t\t\t\tfailPhase(\n\t\t\t\t\t\t\t\t\t'write-contribution',\n\t\t\t\t\t\t\t\t\t`write contribution doc failed`,\n\t\t\t\t\t\t\t\t\tparticipant.plugin,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\tparticipantKeys.push(participant.plugin);\n\t\t\t\t}\n\t\t\t\tyield* requireIdentity(identityMerged, 'snapshot');\n\n\t\t\t\t// 6. Write meta.json, then integrity over the full artifact.\n\t\t\t\t// The caller publishes via stage-and-swap, so catalog\n\t\t\t\t// readers still never observe a half-written artifact.\n\t\t\t\tyield* report({\n\t\t\t\t\tphase: 'writing-metadata',\n\t\t\t\t\tdetail: 'finalizing snapshot artifact',\n\t\t\t\t\tpausedContainers: pauseConfirmed.length,\n\t\t\t\t\ttotalContainers: runningContainers.length,\n\t\t\t\t});\n\t\t\t\tconst meta: SnapshotMetadata = {\n\t\t\t\t\tversion: SNAPSHOT_META_VERSION,\n\t\t\t\t\tid: inputs.snapshotId,\n\t\t\t\t\tlabel: inputs.label,\n\t\t\t\t\tcreatedAt: Date.now(),\n\t\t\t\t\tapp: inputs.app,\n\t\t\t\t\tstack: inputs.stack,\n\t\t\t\t\tnetwork: inputs.network,\n\t\t\t\t\thostTreeIncluded,\n\t\t\t\t\tsubtrees,\n\t\t\t\t\tcontainers: capturedContainers,\n\t\t\t\t\tidentity: identityMerged,\n\t\t\t\t\tparticipants: participantKeys,\n\t\t\t\t};\n\t\t\t\tyield* fs\n\t\t\t\t\t.writeFileString(\n\t\t\t\t\t\t`${inputs.stagingDir}/${SnapshotLayout.metaFile}`,\n\t\t\t\t\t\tJSON.stringify(meta, null, 2),\n\t\t\t\t\t)\n\t\t\t\t\t.pipe(Effect.catch(failPhase('write-meta', `write meta.json failed`)));\n\t\t\t\tyield* writeArtifactIntegrity(inputs.stagingDir).pipe(\n\t\t\t\t\tEffect.catch(failPhase('write-integrity', `write integrity.json failed`)),\n\t\t\t\t);\n\n\t\t\t\treturn meta;\n\t\t\t}),\n\t\t);\n\t}).pipe(Effect.withSpan('orchestrator.snapshot.capture'));\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { versionedDocSchema } from "../../substrate/versioned-doc-schema.mjs";
|
|
1
2
|
import { Schema } from "effect";
|
|
2
3
|
//#region src/orchestrators/snapshot/descriptor.ts
|
|
3
4
|
/** Canonical file / directory names inside one snapshot artifact. */
|
|
@@ -11,6 +12,14 @@ const SnapshotLayout = {
|
|
|
11
12
|
};
|
|
12
13
|
const SNAPSHOT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
13
14
|
const SNAPSHOT_ID_RULE = "snapshot ids must be 1-128 characters matching [A-Za-z0-9][A-Za-z0-9_-]*";
|
|
15
|
+
/** Tagged failure for descriptor-layer validation. Carries a discriminator
|
|
16
|
+
* so downstream phase classifiers can `catchTag` and dispatch on `kind`
|
|
17
|
+
* without sniffing the message. STYLE_GUIDE §2 rule 5. */
|
|
18
|
+
var SnapshotDescriptorError = class extends Schema.TaggedErrorClass()("SnapshotDescriptorError", {
|
|
19
|
+
kind: Schema.Literals(["invalid-id", "invalid-segment"]),
|
|
20
|
+
detail: Schema.String,
|
|
21
|
+
value: Schema.String
|
|
22
|
+
}) {};
|
|
14
23
|
const isValidSnapshotId = (value) => SNAPSHOT_ID_PATTERN.test(value) && isSafeSnapshotPathSegment(value);
|
|
15
24
|
const parseSnapshotId = (value) => isValidSnapshotId(value) ? value : null;
|
|
16
25
|
const isSafeSnapshotPathSegment = (segment) => segment.length > 0 && segment !== "." && segment !== ".." && !segment.includes("/") && !segment.includes("\\") && !segment.includes("\0");
|
|
@@ -55,6 +64,26 @@ const CapturedSubtreeSchema = Schema.Struct({
|
|
|
55
64
|
* the 0o600/0o700 mode round-trip discipline. */
|
|
56
65
|
secretMaterial: Schema.Boolean
|
|
57
66
|
});
|
|
67
|
+
/** Artifact-cache namespaces (`<stackRoot>/cache/<ns>/`) whose cached payload
|
|
68
|
+
* is an on-chain deploy/mint identity — package id, walrus system/staking
|
|
69
|
+
* objects, seal key-server object, deepbook pool ids, coin treasury. These
|
|
70
|
+
* must come back after a restore so the post-restore boot REUSES the deploy
|
|
71
|
+
* instead of re-running it with fresh ids (which orphans every pre-snapshot
|
|
72
|
+
* object). Honored by BOTH halves so the contract holds across lifecycles:
|
|
73
|
+
* the capture TARS `cache/<ns>` into the snapshot (self-contained — survives a
|
|
74
|
+
* `wipe` that drops the live cache), and the restore also PRESERVES the live
|
|
75
|
+
* `cache/<ns>` (the in-place `snapshot → restore` fast path, no tar). A
|
|
76
|
+
* slash-prefixed plugin namespace (`seal/package`, `deepbook/pools`) nests
|
|
77
|
+
* under its root, so the root entry covers all of that plugin's namespaces.
|
|
78
|
+
* The generic per-call `cache/entry` is NOT here and stays dropped on restore. */
|
|
79
|
+
const DEPLOY_CACHE_NAMESPACES = [
|
|
80
|
+
"walrus-deploy",
|
|
81
|
+
"package",
|
|
82
|
+
"seal",
|
|
83
|
+
"deepbook",
|
|
84
|
+
"coin-mint",
|
|
85
|
+
"action"
|
|
86
|
+
];
|
|
58
87
|
/** Identity slice that fires the cross-chain refusal guard. The
|
|
59
88
|
* orchestrator threads contributions from participating plugins
|
|
60
89
|
* through this map — `chain` is the canonical case but other plugins
|
|
@@ -70,16 +99,14 @@ const OpaqueContributionStateSchema = Schema.Struct({
|
|
|
70
99
|
/** Per-participant metadata envelope. The plugin payload is explicitly
|
|
71
100
|
* opaque so a successful decode cannot be mistaken for validation of
|
|
72
101
|
* plugin-owned state. */
|
|
73
|
-
const ContributionDocSchema =
|
|
74
|
-
version: Schema.Literal(1),
|
|
102
|
+
const ContributionDocSchema = versionedDocSchema(1, {
|
|
75
103
|
plugin: Schema.String,
|
|
76
104
|
identity: Schema.optional(IdentitySliceSchema),
|
|
77
105
|
opaqueState: Schema.optional(OpaqueContributionStateSchema)
|
|
78
106
|
});
|
|
79
107
|
/** Top-level metadata record. Architecture § Snapshot — single canonical
|
|
80
108
|
* metadata; "metadata absent = do not trust this directory". */
|
|
81
|
-
const SnapshotMetadataSchema =
|
|
82
|
-
version: Schema.Literal(3),
|
|
109
|
+
const SnapshotMetadataSchema = versionedDocSchema(3, {
|
|
83
110
|
/** Stable snapshot id (caller-supplied or substrate-minted). */
|
|
84
111
|
id: Schema.String,
|
|
85
112
|
/** User-facing name. It is never used as filesystem authority. */
|
|
@@ -98,12 +125,10 @@ const SnapshotMetadataSchema = Schema.Struct({
|
|
|
98
125
|
* disk under `contributions/<encoded-plugin>.json`. */
|
|
99
126
|
participants: Schema.Array(Schema.String)
|
|
100
127
|
});
|
|
101
|
-
const IntegrityFileSchema =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
hashes: Schema.Record(Schema.String, Schema.String)
|
|
105
|
-
});
|
|
128
|
+
const IntegrityFileSchema = versionedDocSchema(1, {
|
|
129
|
+
/** Relative-path → hex-encoded SHA-256 of the file's bytes. */
|
|
130
|
+
hashes: Schema.Record(Schema.String, Schema.String) });
|
|
106
131
|
//#endregion
|
|
107
|
-
export { ContributionDocSchema, IntegrityFileSchema, SNAPSHOT_ID_RULE, SnapshotLayout, SnapshotMetadataSchema, containerImagesBundlePath, contributionPath, isRestorableContainerImageName, isSafeSnapshotPathSegment, isSafeSnapshotRelativePath, parseSnapshotId };
|
|
132
|
+
export { ContributionDocSchema, DEPLOY_CACHE_NAMESPACES, IdentitySliceSchema, IntegrityFileSchema, OpaqueContributionStateSchema, SNAPSHOT_ID_RULE, SnapshotDescriptorError, SnapshotLayout, SnapshotMetadataSchema, containerImagesBundlePath, contributionPath, isRestorableContainerImageName, isSafeSnapshotPathSegment, isSafeSnapshotRelativePath, isValidSnapshotId, parseSnapshotId };
|
|
108
133
|
|
|
109
134
|
//# sourceMappingURL=descriptor.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"descriptor.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/descriptor.ts"],"sourcesContent":["// Snapshot artifact descriptor + metadata schema.\n//\n// Architecture § Snapshot:\n// \"A tar of declared filesystem subtrees, committed container images\n// (one per managed container identified by label tuples), a typed\n// metadata slice (identity-guard contributions + plugin's structured\n// state).\"\n//\n// On-disk layout of one snapshot artifact (under\n// `<stackRoot>/snapshots/<snapshotId>/`):\n//\n// meta.json — versioned metadata record, schema-decoded\n// state.json — copy of the stack's scalar state-store\n// host-tree.tar — tar of subtrees declared by participants\n// containers/images.tar — deduplicated Docker save bundle for all\n// committed managed-container images\n// contributions/<encoded-plugin>.json\n// — one typed metadata slice per participant\n// integrity.json — hashes over the above for resume / probe\n//\n// Layout discipline:\n// - `meta.json` is authoritative. Architecture § Snapshotable\n// \"Metadata is authoritative\": if it's absent or unparseable,\n// restore refuses (no silent partial-restore downgrade).\n// - Anything else missing surfaces as a phase-tagged\n// `SnapshotPartialError` and aborts before any destructive mutation.\n// - The artifact directory itself becomes \"complete\" only via the\n// stage-and-swap atomic rename — readers never observe a\n// half-written tree.\n\nimport { Schema } from 'effect';\n\nimport type { Brand } from '../../substrate/brand.ts';\n\n// -----------------------------------------------------------------------------\n// File layout — string constants the capture / restore code reaches for\n// -----------------------------------------------------------------------------\n\n/** Canonical file / directory names inside one snapshot artifact. */\nexport const SnapshotLayout = {\n\tmetaFile: 'meta.json',\n\tstateFile: 'state.json',\n\thostTreeTar: 'host-tree.tar',\n\tcontainersDir: 'containers',\n\tcontributionsDir: 'contributions',\n\tintegrityFile: 'integrity.json',\n} as const;\n\nexport type SnapshotId = Brand<string, 'SnapshotId'>;\n\nconst SNAPSHOT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;\n\nexport const SNAPSHOT_ID_RULE =\n\t'snapshot ids must be 1-128 characters matching [A-Za-z0-9][A-Za-z0-9_-]*';\n\nexport const isValidSnapshotId = (value: string): boolean =>\n\tSNAPSHOT_ID_PATTERN.test(value) && isSafeSnapshotPathSegment(value);\n\nexport const parseSnapshotId = (value: string): SnapshotId | null =>\n\tisValidSnapshotId(value) ? (value as SnapshotId) : null;\n\nexport const snapshotIdFromString = (value: string): SnapshotId => {\n\tconst parsed = parseSnapshotId(value);\n\tif (parsed === null) {\n\t\tthrow new Error(`${SNAPSHOT_ID_RULE}: ${value}`);\n\t}\n\treturn parsed;\n};\n\nexport const isSafeSnapshotPathSegment = (segment: string): boolean =>\n\tsegment.length > 0 &&\n\tsegment !== '.' &&\n\tsegment !== '..' &&\n\t!segment.includes('/') &&\n\t!segment.includes('\\\\') &&\n\t!segment.includes('\\0');\n\nexport const isSafeSnapshotRelativePath = (relPath: string): boolean =>\n\trelPath !== '' &&\n\trelPath !== '.' &&\n\t!relPath.startsWith('/') &&\n\t!relPath.includes('\\\\') &&\n\t!relPath.includes('\\0') &&\n\t!relPath.split(/[\\\\/]+/).includes('..');\n\nexport const isRestorableContainerImageName = (imageName: string): boolean =>\n\timageName.length > 0 &&\n\timageName.trim() === imageName &&\n\t!/\\s/.test(imageName) &&\n\t!imageName.includes('@') &&\n\t!imageName.startsWith('sha256:');\n\nconst snapshotPathSegment = (kind: string, value: string): string => {\n\tif (!isSafeSnapshotPathSegment(value)) {\n\t\tthrow new Error(`unsafe snapshot ${kind} path segment: ${value}`);\n\t}\n\treturn value;\n};\n\nconst encodedSnapshotPathSegment = (value: string): string => {\n\tlet hex = '';\n\tfor (const byte of new TextEncoder().encode(value)) {\n\t\thex += byte.toString(16).padStart(2, '0');\n\t}\n\treturn `p-${hex}`;\n};\n\n/** Canonical sub-path for one captured container image. */\nexport const containerImagePath = (plugin: string, role: string): string =>\n\t`${SnapshotLayout.containersDir}/${snapshotPathSegment(\n\t\t'plugin',\n\t\tplugin,\n\t)}/${snapshotPathSegment('role', role)}.tar`;\n\n/** Canonical sub-path for the deduplicated managed-container image bundle. */\nexport const containerImagesBundlePath = (): string => `${SnapshotLayout.containersDir}/images.tar`;\n\n/** Canonical sub-path for one participant's typed metadata slice. */\nexport const contributionPath = (plugin: string): string =>\n\t`${SnapshotLayout.contributionsDir}/${encodedSnapshotPathSegment(plugin)}.json`;\n\n// -----------------------------------------------------------------------------\n// Metadata schemas — versioned record\n// -----------------------------------------------------------------------------\n\n/** A label tuple recorded for re-tag-on-restore. Mirrors\n * `ContainerLabelTuple` from `contracts/snapshotable.ts` so the\n * metadata file holds the same shape the runtime adapter filters on. */\nexport const CapturedContainerSchema = Schema.Struct({\n\tplugin: Schema.String,\n\trole: Schema.String,\n\t/** Original image name the supervisor used when last creating this\n\t * container — must be re-tagged on restore so the supervisor's\n\t * reuse-if-name-and-image-match probe adopts the restored image. */\n\timageName: Schema.String,\n\t/** Temporary tag assigned to this committed container image inside\n\t * the Docker save bundle. Restore loads the bundle once, then\n\t * re-tags this source to `imageName`. */\n\tsnapshotTag: Schema.String,\n\t/** Sub-path of the committed image bundle inside the artifact. */\n\ttarPath: Schema.String,\n});\nexport type CapturedContainer = Schema.Schema.Type<typeof CapturedContainerSchema>;\n\n/** Captured host-tree subtree entry — preserved for missing-tolerance\n * classification on restore. */\nexport const CapturedSubtreeSchema = Schema.Struct({\n\tplugin: Schema.String,\n\t/** Subtree path relative to the runtime root (i.e. `<plugin>/...`). */\n\trelPath: Schema.String,\n\t/** `fatal` = restore must refuse if absent on disk at restore time;\n\t * `fine` = silently skipped. Mirrors `SnapshotableDecl.missingTolerance`. */\n\tmissingTolerance: Schema.Literals(['fatal', 'fine']),\n\t/** Did the subtree carry secret material at capture time? Drives\n\t * the 0o600/0o700 mode round-trip discipline. */\n\tsecretMaterial: Schema.Boolean,\n});\nexport type CapturedSubtree = Schema.Schema.Type<typeof CapturedSubtreeSchema>;\n\n/** Identity slice that fires the cross-chain refusal guard. The\n * orchestrator threads contributions from participating plugins\n * through this map — `chain` is the canonical case but other plugins\n * may contribute (e.g. `postgres.majorVersion`). */\nexport const IdentitySliceSchema = Schema.Record(Schema.String, Schema.String);\nexport type IdentitySlice = Schema.Schema.Type<typeof IdentitySliceSchema>;\n\nexport const SNAPSHOT_CONTRIBUTION_VERSION = 1 as const;\n\n/** Plugin-owned JSON payload. The orchestrator validates only this\n * envelope and never interprets `value`; plugins that need stronger\n * guarantees own their schema at the plugin boundary. */\nexport const OpaqueContributionStateSchema = Schema.Struct({\n\tencoding: Schema.Literal('json'),\n\tvalue: Schema.Unknown,\n});\nexport type OpaqueContributionState = Schema.Schema.Type<typeof OpaqueContributionStateSchema>;\n\n/** Per-participant metadata envelope. The plugin payload is explicitly\n * opaque so a successful decode cannot be mistaken for validation of\n * plugin-owned state. */\nexport const ContributionDocSchema = Schema.Struct({\n\tversion: Schema.Literal(SNAPSHOT_CONTRIBUTION_VERSION),\n\tplugin: Schema.String,\n\tidentity: Schema.optional(IdentitySliceSchema),\n\topaqueState: Schema.optional(OpaqueContributionStateSchema),\n});\nexport type ContributionDoc = Schema.Schema.Type<typeof ContributionDocSchema>;\n\n/** Schema version of the metadata record. Bumped when the on-disk\n * shape changes in a way that earlier readers cannot ignore. */\nexport const SNAPSHOT_META_VERSION = 3 as const;\n\n/** Top-level metadata record. Architecture § Snapshot — single canonical\n * metadata; \"metadata absent = do not trust this directory\". */\nexport const SnapshotMetadataSchema = Schema.Struct({\n\tversion: Schema.Literal(SNAPSHOT_META_VERSION),\n\t/** Stable snapshot id (caller-supplied or substrate-minted). */\n\tid: Schema.String,\n\t/** User-facing name. It is never used as filesystem authority. */\n\tlabel: Schema.NullOr(Schema.String),\n\tcreatedAt: Schema.Number,\n\tapp: Schema.String,\n\tstack: Schema.String,\n\tnetwork: Schema.String,\n\t/** Whether `host-tree.tar` is present in the artifact (false for\n\t * the first-boot / empty-stack capture). */\n\thostTreeIncluded: Schema.Boolean,\n\tsubtrees: Schema.Array(CapturedSubtreeSchema),\n\tcontainers: Schema.Array(CapturedContainerSchema),\n\tidentity: IdentitySliceSchema,\n\t/** Plugin keys that contributed a contribution doc. Mirror is on\n\t * disk under `contributions/<encoded-plugin>.json`. */\n\tparticipants: Schema.Array(Schema.String),\n});\nexport type SnapshotMetadata = Schema.Schema.Type<typeof SnapshotMetadataSchema>;\n\n/** Integrity record — per-file SHA-256 over the artifact's contents.\n * Separate file so restore can re-verify after an atomic swap landed\n * without re-reading the (potentially large) tar. */\nexport const SNAPSHOT_INTEGRITY_VERSION = 1 as const;\n\nexport const IntegrityFileSchema = Schema.Struct({\n\tversion: Schema.Literal(SNAPSHOT_INTEGRITY_VERSION),\n\t/** Relative-path → hex-encoded SHA-256 of the file's bytes. */\n\thashes: Schema.Record(Schema.String, Schema.String),\n});\nexport type IntegrityFile = Schema.Schema.Type<typeof IntegrityFileSchema>;\n\n// -----------------------------------------------------------------------------\n// Catalog-listing shape — projected by service.ts; partial / corrupt\n// entries collapse to `null` so the listing tolerates damage.\n// -----------------------------------------------------------------------------\n\nexport interface SnapshotCatalogEntry {\n\treadonly id: string;\n\treadonly directory: string;\n\treadonly metadata: SnapshotMetadata | null;\n}\n"],"mappings":";;;AAuCA,MAAa,iBAAiB;CAC7B,UAAU;CACV,WAAW;CACX,aAAa;CACb,eAAe;CACf,kBAAkB;CAClB,eAAe;CACf;AAID,MAAM,sBAAsB;AAE5B,MAAa,mBACZ;AAED,MAAa,qBAAqB,UACjC,oBAAoB,KAAK,MAAM,IAAI,0BAA0B,MAAM;AAEpE,MAAa,mBAAmB,UAC/B,kBAAkB,MAAM,GAAI,QAAuB;AAUpD,MAAa,6BAA6B,YACzC,QAAQ,SAAS,KACjB,YAAY,OACZ,YAAY,QACZ,CAAC,QAAQ,SAAS,IAAI,IACtB,CAAC,QAAQ,SAAS,KAAK,IACvB,CAAC,QAAQ,SAAS,KAAK;AAExB,MAAa,8BAA8B,YAC1C,YAAY,MACZ,YAAY,OACZ,CAAC,QAAQ,WAAW,IAAI,IACxB,CAAC,QAAQ,SAAS,KAAK,IACvB,CAAC,QAAQ,SAAS,KAAK,IACvB,CAAC,QAAQ,MAAM,SAAS,CAAC,SAAS,KAAK;AAExC,MAAa,kCAAkC,cAC9C,UAAU,SAAS,KACnB,UAAU,MAAM,KAAK,aACrB,CAAC,KAAK,KAAK,UAAU,IACrB,CAAC,UAAU,SAAS,IAAI,IACxB,CAAC,UAAU,WAAW,UAAU;AASjC,MAAM,8BAA8B,UAA0B;CAC7D,IAAI,MAAM;AACV,MAAK,MAAM,QAAQ,IAAI,aAAa,CAAC,OAAO,MAAM,CACjD,QAAO,KAAK,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;AAE1C,QAAO,KAAK;;;AAWb,MAAa,kCAA0C,GAAG,eAAe,cAAc;;AAGvF,MAAa,oBAAoB,WAChC,GAAG,eAAe,iBAAiB,GAAG,2BAA2B,OAAO,CAAC;;;;AAS1E,MAAa,0BAA0B,OAAO,OAAO;CACpD,QAAQ,OAAO;CACf,MAAM,OAAO;;;;CAIb,WAAW,OAAO;;;;CAIlB,aAAa,OAAO;;CAEpB,SAAS,OAAO;CAChB,CAAC;;;AAKF,MAAa,wBAAwB,OAAO,OAAO;CAClD,QAAQ,OAAO;;CAEf,SAAS,OAAO;;;CAGhB,kBAAkB,OAAO,SAAS,CAAC,SAAS,OAAO,CAAC;;;CAGpD,gBAAgB,OAAO;CACvB,CAAC;;;;;AAOF,MAAa,sBAAsB,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO;;;;AAQ9E,MAAa,gCAAgC,OAAO,OAAO;CAC1D,UAAU,OAAO,QAAQ,OAAO;CAChC,OAAO,OAAO;CACd,CAAC;;;;AAMF,MAAa,wBAAwB,OAAO,OAAO;CAClD,SAAS,OAAO,QAAA,EAAsC;CACtD,QAAQ,OAAO;CACf,UAAU,OAAO,SAAS,oBAAoB;CAC9C,aAAa,OAAO,SAAS,8BAA8B;CAC3D,CAAC;;;AASF,MAAa,yBAAyB,OAAO,OAAO;CACnD,SAAS,OAAO,QAAA,EAA8B;;CAE9C,IAAI,OAAO;;CAEX,OAAO,OAAO,OAAO,OAAO,OAAO;CACnC,WAAW,OAAO;CAClB,KAAK,OAAO;CACZ,OAAO,OAAO;CACd,SAAS,OAAO;;;CAGhB,kBAAkB,OAAO;CACzB,UAAU,OAAO,MAAM,sBAAsB;CAC7C,YAAY,OAAO,MAAM,wBAAwB;CACjD,UAAU;;;CAGV,cAAc,OAAO,MAAM,OAAO,OAAO;CACzC,CAAC;AAQF,MAAa,sBAAsB,OAAO,OAAO;CAChD,SAAS,OAAO,QAAA,EAAmC;;CAEnD,QAAQ,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO;CACnD,CAAC"}
|
|
1
|
+
{"version":3,"file":"descriptor.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/descriptor.ts"],"sourcesContent":["// Snapshot artifact descriptor + metadata schema.\n//\n// Architecture § Snapshot:\n// \"A tar of declared filesystem subtrees, committed container images\n// (one per managed container identified by label tuples), a typed\n// metadata slice (identity-guard contributions + plugin's structured\n// state).\"\n//\n// On-disk layout of one snapshot artifact (under\n// `<stackRoot>/snapshots/<snapshotId>/`):\n//\n// meta.json — versioned metadata record, schema-decoded\n// state.json — copy of the stack's scalar state-store\n// host-tree.tar — tar of subtrees declared by participants\n// containers/images.tar — deduplicated Docker save bundle for all\n// committed managed-container images\n// contributions/<encoded-plugin>.json\n// — one typed metadata slice per participant\n// integrity.json — hashes over the above for resume / probe\n//\n// Layout discipline:\n// - `meta.json` is authoritative. Architecture § Snapshotable\n// \"Metadata is authoritative\": if it's absent or unparseable,\n// restore refuses (no silent partial-restore downgrade).\n// - Anything else missing surfaces as a phase-tagged\n// `SnapshotPartialError` and aborts before any destructive mutation.\n// - The artifact directory itself becomes \"complete\" only via the\n// stage-and-swap atomic rename — readers never observe a\n// half-written tree.\n\nimport { Schema } from 'effect';\n\nimport type { Brand } from '../../substrate/brand.ts';\nimport { versionedDocSchema } from '../../substrate/versioned-doc-schema.ts';\n\n// -----------------------------------------------------------------------------\n// File layout — string constants the capture / restore code reaches for\n// -----------------------------------------------------------------------------\n\n/** Canonical file / directory names inside one snapshot artifact. */\nexport const SnapshotLayout = {\n\tmetaFile: 'meta.json',\n\tstateFile: 'state.json',\n\thostTreeTar: 'host-tree.tar',\n\tcontainersDir: 'containers',\n\tcontributionsDir: 'contributions',\n\tintegrityFile: 'integrity.json',\n} as const;\n\nexport type SnapshotId = Brand<string, 'SnapshotId'>;\n\nconst SNAPSHOT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;\n\nexport const SNAPSHOT_ID_RULE =\n\t'snapshot ids must be 1-128 characters matching [A-Za-z0-9][A-Za-z0-9_-]*';\n\n/** Tagged failure for descriptor-layer validation. Carries a discriminator\n * so downstream phase classifiers can `catchTag` and dispatch on `kind`\n * without sniffing the message. STYLE_GUIDE §2 rule 5. */\nexport class SnapshotDescriptorError extends Schema.TaggedErrorClass<SnapshotDescriptorError>()(\n\t'SnapshotDescriptorError',\n\t{\n\t\tkind: Schema.Literals(['invalid-id', 'invalid-segment']),\n\t\tdetail: Schema.String,\n\t\tvalue: Schema.String,\n\t},\n) {}\n\nexport const isValidSnapshotId = (value: string): boolean =>\n\tSNAPSHOT_ID_PATTERN.test(value) && isSafeSnapshotPathSegment(value);\n\nexport const parseSnapshotId = (value: string): SnapshotId | null =>\n\tisValidSnapshotId(value) ? (value as SnapshotId) : null;\n\n/** Brand-cast helper for known-valid id literals (primarily test\n * fixtures). Throws a typed `SnapshotDescriptorError` instance on\n * violation so any defect surfacing carries the canonical `_tag`\n * instead of a bare `Error` string-match. */\nexport const snapshotIdFromString = (value: string): SnapshotId => {\n\tconst parsed = parseSnapshotId(value);\n\tif (parsed === null) {\n\t\tthrow new SnapshotDescriptorError({\n\t\t\tkind: 'invalid-id',\n\t\t\tdetail: SNAPSHOT_ID_RULE,\n\t\t\tvalue,\n\t\t});\n\t}\n\treturn parsed;\n};\n\nexport const isSafeSnapshotPathSegment = (segment: string): boolean =>\n\tsegment.length > 0 &&\n\tsegment !== '.' &&\n\tsegment !== '..' &&\n\t!segment.includes('/') &&\n\t!segment.includes('\\\\') &&\n\t!segment.includes('\\0');\n\nexport const isSafeSnapshotRelativePath = (relPath: string): boolean =>\n\trelPath !== '' &&\n\trelPath !== '.' &&\n\t!relPath.startsWith('/') &&\n\t!relPath.includes('\\\\') &&\n\t!relPath.includes('\\0') &&\n\t!relPath.split(/[\\\\/]+/).includes('..');\n\nexport const isRestorableContainerImageName = (imageName: string): boolean =>\n\timageName.length > 0 &&\n\timageName.trim() === imageName &&\n\t!/\\s/.test(imageName) &&\n\t!imageName.includes('@') &&\n\t!imageName.startsWith('sha256:');\n\nconst snapshotPathSegment = (kind: string, value: string): string => {\n\tif (!isSafeSnapshotPathSegment(value)) {\n\t\tthrow new SnapshotDescriptorError({\n\t\t\tkind: 'invalid-segment',\n\t\t\tdetail: `unsafe snapshot ${kind} path segment`,\n\t\t\tvalue,\n\t\t});\n\t}\n\treturn value;\n};\n\nconst encodedSnapshotPathSegment = (value: string): string => {\n\tlet hex = '';\n\tfor (const byte of new TextEncoder().encode(value)) {\n\t\thex += byte.toString(16).padStart(2, '0');\n\t}\n\treturn `p-${hex}`;\n};\n\n/** Canonical sub-path for one captured container image. */\nexport const containerImagePath = (plugin: string, role: string): string =>\n\t`${SnapshotLayout.containersDir}/${snapshotPathSegment(\n\t\t'plugin',\n\t\tplugin,\n\t)}/${snapshotPathSegment('role', role)}.tar`;\n\n/** Canonical sub-path for the deduplicated managed-container image bundle. */\nexport const containerImagesBundlePath = (): string => `${SnapshotLayout.containersDir}/images.tar`;\n\n/** Canonical sub-path for one participant's typed metadata slice. */\nexport const contributionPath = (plugin: string): string =>\n\t`${SnapshotLayout.contributionsDir}/${encodedSnapshotPathSegment(plugin)}.json`;\n\n// -----------------------------------------------------------------------------\n// Metadata schemas — versioned record\n// -----------------------------------------------------------------------------\n\n/** A label tuple recorded for re-tag-on-restore. Mirrors\n * `ContainerLabelTuple` from `contracts/snapshotable.ts` so the\n * metadata file holds the same shape the runtime adapter filters on. */\nexport const CapturedContainerSchema = Schema.Struct({\n\tplugin: Schema.String,\n\trole: Schema.String,\n\t/** Original image name the supervisor used when last creating this\n\t * container — must be re-tagged on restore so the supervisor's\n\t * reuse-if-name-and-image-match probe adopts the restored image. */\n\timageName: Schema.String,\n\t/** Temporary tag assigned to this committed container image inside\n\t * the Docker save bundle. Restore loads the bundle once, then\n\t * re-tags this source to `imageName`. */\n\tsnapshotTag: Schema.String,\n\t/** Sub-path of the committed image bundle inside the artifact. */\n\ttarPath: Schema.String,\n});\nexport type CapturedContainer = Schema.Schema.Type<typeof CapturedContainerSchema>;\n\n/** Captured host-tree subtree entry — preserved for missing-tolerance\n * classification on restore. */\nexport const CapturedSubtreeSchema = Schema.Struct({\n\tplugin: Schema.String,\n\t/** Subtree path relative to the runtime root (i.e. `<plugin>/...`). */\n\trelPath: Schema.String,\n\t/** `fatal` = restore must refuse if absent on disk at restore time;\n\t * `fine` = silently skipped. Mirrors `SnapshotableDecl.missingTolerance`. */\n\tmissingTolerance: Schema.Literals(['fatal', 'fine']),\n\t/** Did the subtree carry secret material at capture time? Drives\n\t * the 0o600/0o700 mode round-trip discipline. */\n\tsecretMaterial: Schema.Boolean,\n});\nexport type CapturedSubtree = Schema.Schema.Type<typeof CapturedSubtreeSchema>;\n\n/** Artifact-cache namespaces (`<stackRoot>/cache/<ns>/`) whose cached payload\n * is an on-chain deploy/mint identity — package id, walrus system/staking\n * objects, seal key-server object, deepbook pool ids, coin treasury. These\n * must come back after a restore so the post-restore boot REUSES the deploy\n * instead of re-running it with fresh ids (which orphans every pre-snapshot\n * object). Honored by BOTH halves so the contract holds across lifecycles:\n * the capture TARS `cache/<ns>` into the snapshot (self-contained — survives a\n * `wipe` that drops the live cache), and the restore also PRESERVES the live\n * `cache/<ns>` (the in-place `snapshot → restore` fast path, no tar). A\n * slash-prefixed plugin namespace (`seal/package`, `deepbook/pools`) nests\n * under its root, so the root entry covers all of that plugin's namespaces.\n * The generic per-call `cache/entry` is NOT here and stays dropped on restore. */\nexport const DEPLOY_CACHE_NAMESPACES: ReadonlyArray<string> = [\n\t'walrus-deploy',\n\t'package',\n\t'seal',\n\t'deepbook',\n\t'coin-mint',\n\t'action',\n];\n\n/** Identity slice that fires the cross-chain refusal guard. The\n * orchestrator threads contributions from participating plugins\n * through this map — `chain` is the canonical case but other plugins\n * may contribute (e.g. `postgres.majorVersion`). */\nexport const IdentitySliceSchema = Schema.Record(Schema.String, Schema.String);\nexport type IdentitySlice = Schema.Schema.Type<typeof IdentitySliceSchema>;\n\nexport const SNAPSHOT_CONTRIBUTION_VERSION = 1 as const;\n\n/** Plugin-owned JSON payload. The orchestrator validates only this\n * envelope and never interprets `value`; plugins that need stronger\n * guarantees own their schema at the plugin boundary. */\nexport const OpaqueContributionStateSchema = Schema.Struct({\n\tencoding: Schema.Literal('json'),\n\tvalue: Schema.Unknown,\n});\nexport type OpaqueContributionState = Schema.Schema.Type<typeof OpaqueContributionStateSchema>;\n\n/** Per-participant metadata envelope. The plugin payload is explicitly\n * opaque so a successful decode cannot be mistaken for validation of\n * plugin-owned state. */\nexport const ContributionDocSchema = versionedDocSchema(SNAPSHOT_CONTRIBUTION_VERSION, {\n\tplugin: Schema.String,\n\tidentity: Schema.optional(IdentitySliceSchema),\n\topaqueState: Schema.optional(OpaqueContributionStateSchema),\n});\nexport type ContributionDoc = Schema.Schema.Type<typeof ContributionDocSchema>;\n\n/** Schema version of the metadata record. Bumped when the on-disk\n * shape changes in a way that earlier readers cannot ignore. */\nexport const SNAPSHOT_META_VERSION = 3 as const;\n\n/** Top-level metadata record. Architecture § Snapshot — single canonical\n * metadata; \"metadata absent = do not trust this directory\". */\nexport const SnapshotMetadataSchema = versionedDocSchema(SNAPSHOT_META_VERSION, {\n\t/** Stable snapshot id (caller-supplied or substrate-minted). */\n\tid: Schema.String,\n\t/** User-facing name. It is never used as filesystem authority. */\n\tlabel: Schema.NullOr(Schema.String),\n\tcreatedAt: Schema.Number,\n\tapp: Schema.String,\n\tstack: Schema.String,\n\tnetwork: Schema.String,\n\t/** Whether `host-tree.tar` is present in the artifact (false for\n\t * the first-boot / empty-stack capture). */\n\thostTreeIncluded: Schema.Boolean,\n\tsubtrees: Schema.Array(CapturedSubtreeSchema),\n\tcontainers: Schema.Array(CapturedContainerSchema),\n\tidentity: IdentitySliceSchema,\n\t/** Plugin keys that contributed a contribution doc. Mirror is on\n\t * disk under `contributions/<encoded-plugin>.json`. */\n\tparticipants: Schema.Array(Schema.String),\n});\nexport type SnapshotMetadata = Schema.Schema.Type<typeof SnapshotMetadataSchema>;\n\n/** Integrity record — per-file SHA-256 over the artifact's contents.\n * Separate file so restore can re-verify after an atomic swap landed\n * without re-reading the (potentially large) tar. */\nexport const SNAPSHOT_INTEGRITY_VERSION = 1 as const;\n\nexport const IntegrityFileSchema = versionedDocSchema(SNAPSHOT_INTEGRITY_VERSION, {\n\t/** Relative-path → hex-encoded SHA-256 of the file's bytes. */\n\thashes: Schema.Record(Schema.String, Schema.String),\n});\nexport type IntegrityFile = Schema.Schema.Type<typeof IntegrityFileSchema>;\n\n// -----------------------------------------------------------------------------\n// Catalog-listing shape — projected by service.ts; partial / corrupt\n// entries collapse to `null` so the listing tolerates damage.\n// -----------------------------------------------------------------------------\n\nexport interface SnapshotCatalogEntry {\n\treadonly id: string;\n\treadonly directory: string;\n\treadonly metadata: SnapshotMetadata | null;\n}\n"],"mappings":";;;;AAwCA,MAAa,iBAAiB;CAC7B,UAAU;CACV,WAAW;CACX,aAAa;CACb,eAAe;CACf,kBAAkB;CAClB,eAAe;CACf;AAID,MAAM,sBAAsB;AAE5B,MAAa,mBACZ;;;;AAKD,IAAa,0BAAb,cAA6C,OAAO,kBAA2C,CAC9F,2BACA;CACC,MAAM,OAAO,SAAS,CAAC,cAAc,kBAAkB,CAAC;CACxD,QAAQ,OAAO;CACf,OAAO,OAAO;CACd,CACD,CAAC;AAEF,MAAa,qBAAqB,UACjC,oBAAoB,KAAK,MAAM,IAAI,0BAA0B,MAAM;AAEpE,MAAa,mBAAmB,UAC/B,kBAAkB,MAAM,GAAI,QAAuB;AAkBpD,MAAa,6BAA6B,YACzC,QAAQ,SAAS,KACjB,YAAY,OACZ,YAAY,QACZ,CAAC,QAAQ,SAAS,IAAI,IACtB,CAAC,QAAQ,SAAS,KAAK,IACvB,CAAC,QAAQ,SAAS,KAAK;AAExB,MAAa,8BAA8B,YAC1C,YAAY,MACZ,YAAY,OACZ,CAAC,QAAQ,WAAW,IAAI,IACxB,CAAC,QAAQ,SAAS,KAAK,IACvB,CAAC,QAAQ,SAAS,KAAK,IACvB,CAAC,QAAQ,MAAM,SAAS,CAAC,SAAS,KAAK;AAExC,MAAa,kCAAkC,cAC9C,UAAU,SAAS,KACnB,UAAU,MAAM,KAAK,aACrB,CAAC,KAAK,KAAK,UAAU,IACrB,CAAC,UAAU,SAAS,IAAI,IACxB,CAAC,UAAU,WAAW,UAAU;AAajC,MAAM,8BAA8B,UAA0B;CAC7D,IAAI,MAAM;AACV,MAAK,MAAM,QAAQ,IAAI,aAAa,CAAC,OAAO,MAAM,CACjD,QAAO,KAAK,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;AAE1C,QAAO,KAAK;;;AAWb,MAAa,kCAA0C,GAAG,eAAe,cAAc;;AAGvF,MAAa,oBAAoB,WAChC,GAAG,eAAe,iBAAiB,GAAG,2BAA2B,OAAO,CAAC;;;;AAS1E,MAAa,0BAA0B,OAAO,OAAO;CACpD,QAAQ,OAAO;CACf,MAAM,OAAO;;;;CAIb,WAAW,OAAO;;;;CAIlB,aAAa,OAAO;;CAEpB,SAAS,OAAO;CAChB,CAAC;;;AAKF,MAAa,wBAAwB,OAAO,OAAO;CAClD,QAAQ,OAAO;;CAEf,SAAS,OAAO;;;CAGhB,kBAAkB,OAAO,SAAS,CAAC,SAAS,OAAO,CAAC;;;CAGpD,gBAAgB,OAAO;CACvB,CAAC;;;;;;;;;;;;;AAeF,MAAa,0BAAiD;CAC7D;CACA;CACA;CACA;CACA;CACA;CACA;;;;;AAMD,MAAa,sBAAsB,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO;;;;AAQ9E,MAAa,gCAAgC,OAAO,OAAO;CAC1D,UAAU,OAAO,QAAQ,OAAO;CAChC,OAAO,OAAO;CACd,CAAC;;;;AAMF,MAAa,wBAAwB,mBAAA,GAAkD;CACtF,QAAQ,OAAO;CACf,UAAU,OAAO,SAAS,oBAAoB;CAC9C,aAAa,OAAO,SAAS,8BAA8B;CAC3D,CAAC;;;AASF,MAAa,yBAAyB,mBAAA,GAA0C;;CAE/E,IAAI,OAAO;;CAEX,OAAO,OAAO,OAAO,OAAO,OAAO;CACnC,WAAW,OAAO;CAClB,KAAK,OAAO;CACZ,OAAO,OAAO;CACd,SAAS,OAAO;;;CAGhB,kBAAkB,OAAO;CACzB,UAAU,OAAO,MAAM,sBAAsB;CAC7C,YAAY,OAAO,MAAM,wBAAwB;CACjD,UAAU;;;CAGV,cAAc,OAAO,MAAM,OAAO,OAAO;CACzC,CAAC;AAQF,MAAa,sBAAsB,mBAAA,GAA+C;;AAEjF,QAAQ,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO,EACnD,CAAC"}
|
|
@@ -127,6 +127,6 @@ const runRuntimeIdentityGuard = (snapshotIdentity, liveIdentity) => Effect.gen(f
|
|
|
127
127
|
}));
|
|
128
128
|
}).pipe(Effect.withSpan("orchestrator.snapshot.runtime-identity-guard"));
|
|
129
129
|
//#endregion
|
|
130
|
-
export { mergeContributions, requireIdentity, runIdentityGuard, runRuntimeIdentityGuard };
|
|
130
|
+
export { IdentityContributionConflictError, IdentityEmptyError, IdentityMismatchError, IdentityMissingLiveError, IdentityMissingSnapshotError, mergeContributions, requireIdentity, runIdentityGuard, runRuntimeIdentityGuard };
|
|
131
131
|
|
|
132
132
|
//# sourceMappingURL=identity-guard.mjs.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import "./descriptor.mjs";
|
|
2
|
+
import "./wipe.mjs";
|
|
3
|
+
import "./integrity.mjs";
|
|
4
|
+
import "./identity-guard.mjs";
|
|
5
|
+
import "./state-document.mjs";
|
|
6
|
+
import "./capture.mjs";
|
|
7
|
+
import "./prune.mjs";
|
|
8
|
+
import "./pending-marker.mjs";
|
|
9
|
+
import "./recover-pending.mjs";
|
|
10
|
+
import "./restore.mjs";
|
|
11
|
+
import "./service.mjs";
|
|
12
|
+
import "./capture-command.mjs";
|
|
13
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import { Effect, FileSystem } from "effect";
|
|
1
|
+
import { Effect, FileSystem, Schema } from "effect";
|
|
@@ -1,31 +1,70 @@
|
|
|
1
|
+
import { decodeUnknown, parseJsonText } from "../../substrate/runtime/runtime-decode.mjs";
|
|
1
2
|
import { IntegrityFileSchema, SnapshotLayout, isSafeSnapshotRelativePath } from "./descriptor.mjs";
|
|
2
3
|
import { Effect, FileSystem, Schema, Stream } from "effect";
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
4
5
|
//#region src/orchestrators/snapshot/integrity.ts
|
|
5
6
|
const SHA256_HEX = /^[a-f0-9]{64}$/;
|
|
6
|
-
|
|
7
|
+
/** Tagged failure raised by snapshot integrity helpers. `kind`
|
|
8
|
+
* discriminates the failure class so downstream phase classifiers can
|
|
9
|
+
* branch by tag, not by message substring. */
|
|
10
|
+
var SnapshotIntegrityError = class extends Schema.TaggedErrorClass()("SnapshotIntegrityError", {
|
|
11
|
+
/**
|
|
12
|
+
* - `'missing'` — `integrity.json` file absent.
|
|
13
|
+
* - `'corrupt'` — present but unparseable / schema-decode failure.
|
|
14
|
+
* - `'mismatch'` — present, parses, but a file's hash does not
|
|
15
|
+
* match its recorded value (or the file list disagrees).
|
|
16
|
+
* - `'walk-failed'` — directory walk failure (filesystem error
|
|
17
|
+
* during enumeration, hashing, or write of the integrity doc).
|
|
18
|
+
*/
|
|
19
|
+
kind: Schema.Literals([
|
|
20
|
+
"missing",
|
|
21
|
+
"corrupt",
|
|
22
|
+
"mismatch",
|
|
23
|
+
"walk-failed"
|
|
24
|
+
]),
|
|
25
|
+
detail: Schema.String,
|
|
26
|
+
path: Schema.optional(Schema.String),
|
|
27
|
+
cause: Schema.optional(Schema.Defect)
|
|
28
|
+
}) {};
|
|
29
|
+
const failWalk = (detail, path, cause) => Effect.fail(new SnapshotIntegrityError({
|
|
30
|
+
kind: "walk-failed",
|
|
31
|
+
detail,
|
|
32
|
+
path,
|
|
33
|
+
cause
|
|
34
|
+
}));
|
|
35
|
+
const failCorrupt = (detail, path, cause) => Effect.fail(new SnapshotIntegrityError({
|
|
36
|
+
kind: "corrupt",
|
|
37
|
+
detail,
|
|
38
|
+
path,
|
|
39
|
+
cause
|
|
40
|
+
}));
|
|
41
|
+
const failMismatch = (detail, path) => Effect.fail(new SnapshotIntegrityError({
|
|
42
|
+
kind: "mismatch",
|
|
43
|
+
detail,
|
|
44
|
+
path
|
|
45
|
+
}));
|
|
7
46
|
const hashFile = (path) => Effect.gen(function* () {
|
|
8
47
|
const fs = yield* FileSystem.FileSystem;
|
|
9
48
|
const hash = createHash("sha256");
|
|
10
49
|
yield* Stream.runForEach(fs.stream(path), (chunk) => Effect.sync(() => {
|
|
11
50
|
hash.update(chunk);
|
|
12
|
-
})).pipe(Effect.catch((cause) =>
|
|
51
|
+
})).pipe(Effect.catch((cause) => failWalk(`hash ${path} failed`, path, cause)));
|
|
13
52
|
return hash.digest("hex");
|
|
14
53
|
});
|
|
15
54
|
const collectArtifactFiles = (root) => Effect.gen(function* () {
|
|
16
55
|
const fs = yield* FileSystem.FileSystem;
|
|
17
56
|
const files = [];
|
|
18
57
|
const walk = (dir, prefix) => Effect.gen(function* () {
|
|
19
|
-
const names = yield* fs.readDirectory(dir).pipe(Effect.catch((cause) =>
|
|
58
|
+
const names = yield* fs.readDirectory(dir).pipe(Effect.catch((cause) => failWalk(`readDirectory(${dir}) failed`, dir, cause)));
|
|
20
59
|
for (const name of names) {
|
|
21
60
|
const relPath = prefix === "" ? name : `${prefix}/${name}`;
|
|
22
|
-
if (!isSafeSnapshotRelativePath(relPath)) return yield*
|
|
61
|
+
if (!isSafeSnapshotRelativePath(relPath)) return yield* failWalk(`unsafe artifact relative path: ${relPath}`, relPath);
|
|
23
62
|
const absPath = `${dir}/${name}`;
|
|
24
|
-
const stat = yield* fs.stat(absPath).pipe(Effect.catch((cause) =>
|
|
63
|
+
const stat = yield* fs.stat(absPath).pipe(Effect.catch((cause) => failWalk(`stat(${absPath}) failed`, absPath, cause)));
|
|
25
64
|
if (stat.type === "Directory") yield* walk(absPath, relPath);
|
|
26
65
|
else if (stat.type === "File") {
|
|
27
66
|
if (relPath !== SnapshotLayout.integrityFile) files.push(relPath);
|
|
28
|
-
} else return yield*
|
|
67
|
+
} else return yield* failWalk(`snapshot artifact path is not a regular file: ${relPath}`, absPath);
|
|
29
68
|
}
|
|
30
69
|
});
|
|
31
70
|
yield* walk(root, "");
|
|
@@ -42,21 +81,38 @@ const computeArtifactIntegrity = (root) => Effect.gen(function* () {
|
|
|
42
81
|
const writeArtifactIntegrity = (root) => Effect.gen(function* () {
|
|
43
82
|
const fs = yield* FileSystem.FileSystem;
|
|
44
83
|
const integrity = yield* computeArtifactIntegrity(root);
|
|
45
|
-
|
|
84
|
+
const path = `${root}/${SnapshotLayout.integrityFile}`;
|
|
85
|
+
yield* fs.writeFileString(path, JSON.stringify(integrity, null, 2)).pipe(Effect.catch((cause) => failWalk(`write ${SnapshotLayout.integrityFile} failed`, path, cause)));
|
|
46
86
|
return integrity;
|
|
47
87
|
});
|
|
48
88
|
const readIntegrity = (root) => Effect.gen(function* () {
|
|
49
89
|
const fs = yield* FileSystem.FileSystem;
|
|
50
90
|
const path = `${root}/${SnapshotLayout.integrityFile}`;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
91
|
+
if (!(yield* fs.exists(path).pipe(Effect.catch(() => Effect.succeed(false))))) return yield* Effect.fail(new SnapshotIntegrityError({
|
|
92
|
+
kind: "missing",
|
|
93
|
+
detail: `snapshot integrity file absent at ${path}`,
|
|
94
|
+
path
|
|
95
|
+
}));
|
|
96
|
+
const decoded = yield* decodeUnknown(IntegrityFileSchema, yield* parseJsonText(yield* fs.readFileString(path).pipe(Effect.catch((cause) => failWalk(`read ${path} failed`, path, cause))), {
|
|
97
|
+
source: path,
|
|
98
|
+
mkError: (issue) => new SnapshotIntegrityError({
|
|
99
|
+
kind: "corrupt",
|
|
100
|
+
detail: `${path} is not valid JSON`,
|
|
101
|
+
path,
|
|
102
|
+
cause: issue.cause
|
|
103
|
+
})
|
|
104
|
+
}), {
|
|
105
|
+
source: path,
|
|
106
|
+
mkError: (issue) => new SnapshotIntegrityError({
|
|
107
|
+
kind: "corrupt",
|
|
108
|
+
detail: `${path} failed schema decode`,
|
|
109
|
+
path,
|
|
110
|
+
cause: issue.cause
|
|
111
|
+
})
|
|
55
112
|
});
|
|
56
|
-
const decoded = yield* Schema.decodeUnknownEffect(IntegrityFileSchema)(raw).pipe(Effect.catch((cause) => fail(`${path} failed schema decode: ${String(cause)}`)));
|
|
57
113
|
for (const [relPath, digest] of Object.entries(decoded.hashes)) {
|
|
58
|
-
if (!isSafeSnapshotRelativePath(relPath) || relPath === SnapshotLayout.integrityFile) return yield*
|
|
59
|
-
if (!SHA256_HEX.test(digest)) return yield*
|
|
114
|
+
if (!isSafeSnapshotRelativePath(relPath) || relPath === SnapshotLayout.integrityFile) return yield* failCorrupt(`unsafe integrity path: ${relPath}`, path);
|
|
115
|
+
if (!SHA256_HEX.test(digest)) return yield* failCorrupt(`invalid sha256 digest for ${relPath}`, path);
|
|
60
116
|
}
|
|
61
117
|
return decoded;
|
|
62
118
|
});
|
|
@@ -65,10 +121,10 @@ const verifyArtifactIntegrity = (root) => Effect.gen(function* () {
|
|
|
65
121
|
const actual = yield* computeArtifactIntegrity(root);
|
|
66
122
|
const expectedPaths = Object.keys(expected.hashes).sort((a, b) => a.localeCompare(b));
|
|
67
123
|
const actualPaths = Object.keys(actual.hashes).sort((a, b) => a.localeCompare(b));
|
|
68
|
-
if (expectedPaths.join("\n") !== actualPaths.join("\n")) return yield*
|
|
69
|
-
for (const relPath of expectedPaths) if (expected.hashes[relPath] !== actual.hashes[relPath]) return yield*
|
|
124
|
+
if (expectedPaths.join("\n") !== actualPaths.join("\n")) return yield* failMismatch("snapshot integrity file list does not match artifact contents");
|
|
125
|
+
for (const relPath of expectedPaths) if (expected.hashes[relPath] !== actual.hashes[relPath]) return yield* failMismatch(`snapshot integrity mismatch for ${relPath}`, relPath);
|
|
70
126
|
});
|
|
71
127
|
//#endregion
|
|
72
|
-
export { verifyArtifactIntegrity, writeArtifactIntegrity };
|
|
128
|
+
export { SnapshotIntegrityError, computeArtifactIntegrity, verifyArtifactIntegrity, writeArtifactIntegrity };
|
|
73
129
|
|
|
74
130
|
//# sourceMappingURL=integrity.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integrity.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/integrity.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport { Effect, FileSystem, Schema, Stream } from 'effect';\n\nimport {\n\tIntegrityFileSchema,\n\tSNAPSHOT_INTEGRITY_VERSION,\n\tSnapshotLayout,\n\tisSafeSnapshotRelativePath,\n\ttype IntegrityFile,\n} from './descriptor.ts';\n\nconst SHA256_HEX = /^[a-f0-9]{64}$/;\n\nconst fail = (detail: string): Effect.Effect<never, Error> => Effect.fail(new Error(detail));\n\nconst hashFile = (path: string): Effect.Effect<string, Error, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst hash = createHash('sha256');\n\t\tyield* Stream.runForEach(fs.stream(path), (chunk) =>\n\t\t\tEffect.sync(() => {\n\t\t\t\thash.update(chunk);\n\t\t\t}),\n\t\t).pipe(Effect.catch((cause) => fail(`hash ${path} failed: ${String(cause)}`)));\n\t\treturn hash.digest('hex');\n\t});\n\nconst collectArtifactFiles = (\n\troot: string,\n): Effect.Effect<ReadonlyArray<string>, Error, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst files: string[] = [];\n\t\tconst walk = (dir: string, prefix: string): Effect.Effect<void, Error> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst names = yield* fs\n\t\t\t\t\t.readDirectory(dir)\n\t\t\t\t\t.pipe(Effect.catch((cause) => fail(`readDirectory(${dir}) failed: ${String(cause)}`)));\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tconst relPath = prefix === '' ? name : `${prefix}/${name}`;\n\t\t\t\t\tif (!isSafeSnapshotRelativePath(relPath)) {\n\t\t\t\t\t\treturn yield* fail(`unsafe artifact relative path: ${relPath}`);\n\t\t\t\t\t}\n\t\t\t\t\tconst absPath = `${dir}/${name}`;\n\t\t\t\t\tconst stat = yield* fs\n\t\t\t\t\t\t.stat(absPath)\n\t\t\t\t\t\t.pipe(Effect.catch((cause) => fail(`stat(${absPath}) failed: ${String(cause)}`)));\n\t\t\t\t\tif (stat.type === 'Directory') {\n\t\t\t\t\t\tyield* walk(absPath, relPath);\n\t\t\t\t\t} else if (stat.type === 'File') {\n\t\t\t\t\t\tif (relPath !== SnapshotLayout.integrityFile) {\n\t\t\t\t\t\t\tfiles.push(relPath);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn yield* fail(`snapshot artifact path is not a regular file: ${relPath}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\tyield* walk(root, '');\n\t\treturn files.sort((a, b) => a.localeCompare(b));\n\t});\n\nexport const computeArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<IntegrityFile, Error, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst hashes: Record<string, string> = {};\n\t\tfor (const relPath of yield* collectArtifactFiles(root)) {\n\t\t\thashes[relPath] = yield* hashFile(`${root}/${relPath}`);\n\t\t}\n\t\treturn { version: SNAPSHOT_INTEGRITY_VERSION, hashes };\n\t});\n\nexport const writeArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<IntegrityFile, Error, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst integrity = yield* computeArtifactIntegrity(root);\n\t\tyield* fs\n\t\t\t.writeFileString(\n\t\t\t\t`${root}/${SnapshotLayout.integrityFile}`,\n\t\t\t\tJSON.stringify(integrity, null, 2),\n\t\t\t)\n\t\t\t.pipe(\n\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\tfail(`write ${SnapshotLayout.integrityFile} failed: ${String(cause)}`),\n\t\t\t\t),\n\t\t\t);\n\t\treturn integrity;\n\t});\n\nconst readIntegrity = (root: string): Effect.Effect<IntegrityFile, Error, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst path = `${root}/${SnapshotLayout.integrityFile}`;\n\t\tconst text = yield* fs\n\t\t\t.readFileString(path)\n\t\t\t.pipe(Effect.catch((cause) => fail(`read ${path} failed: ${String(cause)}`)));\n\t\tconst raw = yield* Effect.try({\n\t\t\ttry: () => JSON.parse(text) as unknown,\n\t\t\tcatch: (cause) => new Error(`${path} is not valid JSON: ${String(cause)}`),\n\t\t});\n\t\tconst decoded = yield* Schema.decodeUnknownEffect(IntegrityFileSchema)(raw).pipe(\n\t\t\tEffect.catch((cause) => fail(`${path} failed schema decode: ${String(cause)}`)),\n\t\t);\n\t\tfor (const [relPath, digest] of Object.entries(decoded.hashes)) {\n\t\t\tif (!isSafeSnapshotRelativePath(relPath) || relPath === SnapshotLayout.integrityFile) {\n\t\t\t\treturn yield* fail(`unsafe integrity path: ${relPath}`);\n\t\t\t}\n\t\t\tif (!SHA256_HEX.test(digest)) {\n\t\t\t\treturn yield* fail(`invalid sha256 digest for ${relPath}`);\n\t\t\t}\n\t\t}\n\t\treturn decoded;\n\t});\n\nexport const verifyArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<void, Error, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst expected = yield* readIntegrity(root);\n\t\tconst actual = yield* computeArtifactIntegrity(root);\n\t\tconst expectedPaths = Object.keys(expected.hashes).sort((a, b) => a.localeCompare(b));\n\t\tconst actualPaths = Object.keys(actual.hashes).sort((a, b) => a.localeCompare(b));\n\t\tif (expectedPaths.join('\\n') !== actualPaths.join('\\n')) {\n\t\t\treturn yield* fail('snapshot integrity file list does not match artifact contents');\n\t\t}\n\t\tfor (const relPath of expectedPaths) {\n\t\t\tif (expected.hashes[relPath] !== actual.hashes[relPath]) {\n\t\t\t\treturn yield* fail(`snapshot integrity mismatch for ${relPath}`);\n\t\t\t}\n\t\t}\n\t});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"integrity.mjs","names":[],"sources":["../../../src/orchestrators/snapshot/integrity.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\n\nimport { Effect, FileSystem, Schema, Stream } from 'effect';\n\nimport { decodeUnknown, parseJsonText } from '../../substrate/runtime/runtime-decode.ts';\nimport {\n\tIntegrityFileSchema,\n\tSNAPSHOT_INTEGRITY_VERSION,\n\tSnapshotLayout,\n\tisSafeSnapshotRelativePath,\n\ttype IntegrityFile,\n} from './descriptor.ts';\n\nconst SHA256_HEX = /^[a-f0-9]{64}$/;\n\n/** Tagged failure raised by snapshot integrity helpers. `kind`\n * discriminates the failure class so downstream phase classifiers can\n * branch by tag, not by message substring. */\nexport class SnapshotIntegrityError extends Schema.TaggedErrorClass<SnapshotIntegrityError>()(\n\t'SnapshotIntegrityError',\n\t{\n\t\t/**\n\t\t * - `'missing'` — `integrity.json` file absent.\n\t\t * - `'corrupt'` — present but unparseable / schema-decode failure.\n\t\t * - `'mismatch'` — present, parses, but a file's hash does not\n\t\t * match its recorded value (or the file list disagrees).\n\t\t * - `'walk-failed'` — directory walk failure (filesystem error\n\t\t * during enumeration, hashing, or write of the integrity doc).\n\t\t */\n\t\tkind: Schema.Literals(['missing', 'corrupt', 'mismatch', 'walk-failed']),\n\t\tdetail: Schema.String,\n\t\tpath: Schema.optional(Schema.String),\n\t\tcause: Schema.optional(Schema.Defect),\n\t},\n) {}\n\nconst failWalk = (\n\tdetail: string,\n\tpath?: string,\n\tcause?: unknown,\n): Effect.Effect<never, SnapshotIntegrityError> =>\n\tEffect.fail(new SnapshotIntegrityError({ kind: 'walk-failed', detail, path, cause }));\n\nconst failCorrupt = (\n\tdetail: string,\n\tpath?: string,\n\tcause?: unknown,\n): Effect.Effect<never, SnapshotIntegrityError> =>\n\tEffect.fail(new SnapshotIntegrityError({ kind: 'corrupt', detail, path, cause }));\n\nconst failMismatch = (\n\tdetail: string,\n\tpath?: string,\n): Effect.Effect<never, SnapshotIntegrityError> =>\n\tEffect.fail(new SnapshotIntegrityError({ kind: 'mismatch', detail, path }));\n\nconst hashFile = (\n\tpath: string,\n): Effect.Effect<string, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst hash = createHash('sha256');\n\t\tyield* Stream.runForEach(fs.stream(path), (chunk) =>\n\t\t\tEffect.sync(() => {\n\t\t\t\thash.update(chunk);\n\t\t\t}),\n\t\t).pipe(Effect.catch((cause) => failWalk(`hash ${path} failed`, path, cause)));\n\t\treturn hash.digest('hex');\n\t});\n\nconst collectArtifactFiles = (\n\troot: string,\n): Effect.Effect<ReadonlyArray<string>, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst files: string[] = [];\n\t\tconst walk = (dir: string, prefix: string): Effect.Effect<void, SnapshotIntegrityError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst names = yield* fs\n\t\t\t\t\t.readDirectory(dir)\n\t\t\t\t\t.pipe(Effect.catch((cause) => failWalk(`readDirectory(${dir}) failed`, dir, cause)));\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tconst relPath = prefix === '' ? name : `${prefix}/${name}`;\n\t\t\t\t\tif (!isSafeSnapshotRelativePath(relPath)) {\n\t\t\t\t\t\treturn yield* failWalk(`unsafe artifact relative path: ${relPath}`, relPath);\n\t\t\t\t\t}\n\t\t\t\t\tconst absPath = `${dir}/${name}`;\n\t\t\t\t\tconst stat = yield* fs\n\t\t\t\t\t\t.stat(absPath)\n\t\t\t\t\t\t.pipe(Effect.catch((cause) => failWalk(`stat(${absPath}) failed`, absPath, cause)));\n\t\t\t\t\tif (stat.type === 'Directory') {\n\t\t\t\t\t\tyield* walk(absPath, relPath);\n\t\t\t\t\t} else if (stat.type === 'File') {\n\t\t\t\t\t\tif (relPath !== SnapshotLayout.integrityFile) {\n\t\t\t\t\t\t\tfiles.push(relPath);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn yield* failWalk(\n\t\t\t\t\t\t\t`snapshot artifact path is not a regular file: ${relPath}`,\n\t\t\t\t\t\t\tabsPath,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\tyield* walk(root, '');\n\t\treturn files.sort((a, b) => a.localeCompare(b));\n\t});\n\nexport const computeArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<IntegrityFile, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst hashes: Record<string, string> = {};\n\t\tfor (const relPath of yield* collectArtifactFiles(root)) {\n\t\t\thashes[relPath] = yield* hashFile(`${root}/${relPath}`);\n\t\t}\n\t\treturn { version: SNAPSHOT_INTEGRITY_VERSION, hashes };\n\t});\n\nexport const writeArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<IntegrityFile, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst integrity = yield* computeArtifactIntegrity(root);\n\t\tconst path = `${root}/${SnapshotLayout.integrityFile}`;\n\t\tyield* fs\n\t\t\t.writeFileString(path, JSON.stringify(integrity, null, 2))\n\t\t\t.pipe(\n\t\t\t\tEffect.catch((cause) =>\n\t\t\t\t\tfailWalk(`write ${SnapshotLayout.integrityFile} failed`, path, cause),\n\t\t\t\t),\n\t\t\t);\n\t\treturn integrity;\n\t});\n\nconst readIntegrity = (\n\troot: string,\n): Effect.Effect<IntegrityFile, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem.FileSystem;\n\t\tconst path = `${root}/${SnapshotLayout.integrityFile}`;\n\t\tconst exists = yield* fs.exists(path).pipe(Effect.catch(() => Effect.succeed(false)));\n\t\tif (!exists) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew SnapshotIntegrityError({\n\t\t\t\t\tkind: 'missing',\n\t\t\t\t\tdetail: `snapshot integrity file absent at ${path}`,\n\t\t\t\t\tpath,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst text = yield* fs\n\t\t\t.readFileString(path)\n\t\t\t.pipe(Effect.catch((cause) => failWalk(`read ${path} failed`, path, cause)));\n\t\tconst raw = yield* parseJsonText(text, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) =>\n\t\t\t\tnew SnapshotIntegrityError({\n\t\t\t\t\tkind: 'corrupt',\n\t\t\t\t\tdetail: `${path} is not valid JSON`,\n\t\t\t\t\tpath,\n\t\t\t\t\tcause: issue.cause,\n\t\t\t\t}),\n\t\t});\n\t\tconst decoded = yield* decodeUnknown(IntegrityFileSchema, raw, {\n\t\t\tsource: path,\n\t\t\tmkError: (issue) =>\n\t\t\t\tnew SnapshotIntegrityError({\n\t\t\t\t\tkind: 'corrupt',\n\t\t\t\t\tdetail: `${path} failed schema decode`,\n\t\t\t\t\tpath,\n\t\t\t\t\tcause: issue.cause,\n\t\t\t\t}),\n\t\t});\n\t\tfor (const [relPath, digest] of Object.entries(decoded.hashes)) {\n\t\t\tif (!isSafeSnapshotRelativePath(relPath) || relPath === SnapshotLayout.integrityFile) {\n\t\t\t\treturn yield* failCorrupt(`unsafe integrity path: ${relPath}`, path);\n\t\t\t}\n\t\t\tif (!SHA256_HEX.test(digest)) {\n\t\t\t\treturn yield* failCorrupt(`invalid sha256 digest for ${relPath}`, path);\n\t\t\t}\n\t\t}\n\t\treturn decoded;\n\t});\n\nexport const verifyArtifactIntegrity = (\n\troot: string,\n): Effect.Effect<void, SnapshotIntegrityError, FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tconst expected = yield* readIntegrity(root);\n\t\tconst actual = yield* computeArtifactIntegrity(root);\n\t\tconst expectedPaths = Object.keys(expected.hashes).sort((a, b) => a.localeCompare(b));\n\t\tconst actualPaths = Object.keys(actual.hashes).sort((a, b) => a.localeCompare(b));\n\t\tif (expectedPaths.join('\\n') !== actualPaths.join('\\n')) {\n\t\t\treturn yield* failMismatch('snapshot integrity file list does not match artifact contents');\n\t\t}\n\t\tfor (const relPath of expectedPaths) {\n\t\t\tif (expected.hashes[relPath] !== actual.hashes[relPath]) {\n\t\t\t\treturn yield* failMismatch(`snapshot integrity mismatch for ${relPath}`, relPath);\n\t\t\t}\n\t\t}\n\t});\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { Effect, FileSystem, Schema } from "effect";
|