niceeval 0.13.1 → 0.13.2
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/INDEX.md +25 -2
- package/README.md +9 -10
- package/README.zh.md +9 -10
- package/dist/agents/ai-sdk.cjs.map +1 -1
- package/dist/agents/ai-sdk.d.cts +1 -1
- package/dist/agents/ai-sdk.d.mts +1 -1
- package/dist/agents/ai-sdk.d.ts +1 -1
- package/dist/agents/bub-install-spec.cjs +19 -5
- package/dist/agents/bub-install-spec.cjs.map +1 -1
- package/dist/agents/bub-install-spec.d.cts +9 -2
- package/dist/agents/bub-install-spec.d.mts +9 -2
- package/dist/agents/bub-install-spec.d.ts +9 -2
- package/dist/agents/bub-install-spec.js +14 -12
- package/dist/agents/bub.cjs +10 -6
- package/dist/agents/bub.cjs.map +1 -1
- package/dist/agents/bub.d.cts +2 -2
- package/dist/agents/bub.d.mts +2 -2
- package/dist/agents/bub.d.ts +2 -2
- package/dist/agents/claude-code.cjs.map +1 -1
- package/dist/agents/claude-code.d.cts +2 -2
- package/dist/agents/claude-code.d.mts +2 -2
- package/dist/agents/claude-code.d.ts +2 -2
- package/dist/agents/codex.cjs +4 -4
- package/dist/agents/codex.cjs.map +1 -1
- package/dist/agents/codex.d.cts +3 -3
- package/dist/agents/codex.d.mts +3 -3
- package/dist/agents/codex.d.ts +3 -3
- package/dist/agents/coding-cli-versions.cjs +10 -1
- package/dist/agents/coding-cli-versions.cjs.map +1 -1
- package/dist/agents/coding-cli-versions.d.cts +8 -3
- package/dist/agents/coding-cli-versions.d.mts +8 -3
- package/dist/agents/coding-cli-versions.d.ts +8 -3
- package/dist/agents/coding-cli-versions.js +18 -12
- package/dist/agents/deepseek-harness.cjs +151 -0
- package/dist/agents/deepseek-harness.cjs.map +1 -0
- package/dist/agents/deepseek-harness.d.cts +14 -0
- package/dist/agents/deepseek-harness.d.mts +14 -0
- package/dist/agents/deepseek-harness.d.ts +14 -0
- package/dist/agents/deepseek-harness.js +7 -0
- package/dist/agents/deepseek-harness.js.map +1 -0
- package/dist/agents/exact-npm-plugins.cjs +28 -0
- package/dist/agents/exact-npm-plugins.cjs.map +1 -0
- package/dist/agents/exact-npm-plugins.d.cts +10 -0
- package/dist/agents/exact-npm-plugins.d.mts +10 -0
- package/dist/agents/exact-npm-plugins.d.ts +10 -0
- package/dist/agents/exact-npm-plugins.js +8 -0
- package/dist/agents/exact-npm-plugins.js.map +1 -0
- package/dist/agents/index.cjs +5 -1
- package/dist/agents/index.cjs.map +1 -1
- package/dist/agents/index.d.cts +4 -0
- package/dist/agents/index.d.mts +4 -0
- package/dist/agents/index.d.ts +4 -0
- package/dist/agents/index.js +54 -50
- package/dist/agents/index.mjs +54 -50
- package/dist/agents/omp.cjs +170 -0
- package/dist/agents/omp.cjs.map +1 -0
- package/dist/agents/omp.d.cts +14 -0
- package/dist/agents/omp.d.mts +14 -0
- package/dist/agents/omp.d.ts +14 -0
- package/dist/agents/omp.js +7 -0
- package/dist/agents/omp.js.map +1 -0
- package/dist/agents/openclaw.cjs +51 -2
- package/dist/agents/openclaw.cjs.map +1 -1
- package/dist/agents/openclaw.d.cts +2 -0
- package/dist/agents/openclaw.d.mts +2 -0
- package/dist/agents/openclaw.d.ts +2 -0
- package/dist/agents/types.cjs.map +1 -1
- package/dist/agents/types.d.cts +4 -4
- package/dist/agents/types.d.mts +4 -4
- package/dist/agents/types.d.ts +4 -4
- package/dist/agents/ui-message-stream.cjs +24 -7
- package/dist/agents/ui-message-stream.cjs.map +1 -1
- package/dist/analysis/api.cjs +28 -2
- package/dist/analysis/api.cjs.map +1 -1
- package/dist/analysis/api.d.cts +22 -1
- package/dist/analysis/api.d.mts +22 -1
- package/dist/analysis/api.d.ts +22 -1
- package/dist/analysis/api.js +8 -6
- package/dist/analysis/bindings.cjs +11 -18
- package/dist/analysis/bindings.cjs.map +1 -1
- package/dist/analysis/bindings.d.cts +177 -49
- package/dist/analysis/bindings.d.mts +177 -49
- package/dist/analysis/bindings.d.ts +177 -49
- package/dist/analysis/contracts.cjs +1 -0
- package/dist/analysis/contracts.cjs.map +1 -1
- package/dist/analysis/contracts.d.cts +43 -3
- package/dist/analysis/contracts.d.mts +43 -3
- package/dist/analysis/contracts.d.ts +43 -3
- package/dist/analysis/cost-projection.cjs +2 -1
- package/dist/analysis/cost-projection.cjs.map +1 -1
- package/dist/analysis/cost-projection.d.cts +1 -1
- package/dist/analysis/cost-projection.d.mts +1 -1
- package/dist/analysis/cost-projection.d.ts +1 -1
- package/dist/analysis/cost.cjs +13 -3
- package/dist/analysis/cost.cjs.map +1 -1
- package/dist/analysis/cost.d.cts +10 -3
- package/dist/analysis/cost.d.mts +10 -3
- package/dist/analysis/cost.d.ts +10 -3
- package/dist/analysis/experiment-groups.cjs +96 -0
- package/dist/analysis/experiment-groups.cjs.map +1 -0
- package/dist/analysis/experiment-groups.d.cts +9 -0
- package/dist/analysis/experiment-groups.d.mts +9 -0
- package/dist/analysis/experiment-groups.d.ts +9 -0
- package/dist/analysis/experiment-groups.js +10 -0
- package/dist/analysis/experiment-groups.js.map +1 -0
- package/dist/analysis/index.cjs +4 -1
- package/dist/analysis/index.cjs.map +1 -1
- package/dist/analysis/index.d.cts +4 -2
- package/dist/analysis/index.d.mts +4 -2
- package/dist/analysis/index.d.ts +4 -2
- package/dist/analysis/index.js +40 -36
- package/dist/analysis/index.mjs +40 -36
- package/dist/assertions/limits.cjs +4 -0
- package/dist/assertions/limits.cjs.map +1 -1
- package/dist/assertions/limits.d.cts +4 -0
- package/dist/assertions/limits.d.mts +4 -0
- package/dist/assertions/limits.d.ts +4 -0
- package/dist/assertions/match.cjs +63 -25
- package/dist/assertions/match.cjs.map +1 -1
- package/dist/assertions/match.d.cts +7 -0
- package/dist/assertions/match.d.mts +7 -0
- package/dist/assertions/match.d.ts +7 -0
- package/dist/assertions/record/attachment.cjs +32 -4
- package/dist/assertions/record/attachment.cjs.map +1 -1
- package/dist/assertions/record/diff.cjs +10 -4
- package/dist/assertions/record/diff.cjs.map +1 -1
- package/dist/assertions/record/producer.cjs +101 -4
- package/dist/assertions/record/producer.cjs.map +1 -1
- package/dist/assertions/record/producer.d.cts +4 -1
- package/dist/assertions/record/producer.d.mts +4 -1
- package/dist/assertions/record/producer.d.ts +4 -1
- package/dist/assertions/runtime.cjs +45 -5
- package/dist/assertions/runtime.cjs.map +1 -1
- package/dist/cli/record.cjs +113 -8
- package/dist/cli/record.cjs.map +1 -1
- package/dist/cli/record.d.cts +20 -1
- package/dist/cli/record.d.mts +20 -1
- package/dist/cli/record.d.ts +20 -1
- package/dist/cli/record.js +4 -2
- package/dist/cli.cjs +386 -31
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +10 -2
- package/dist/cli.d.mts +10 -2
- package/dist/cli.d.ts +10 -2
- package/dist/context/send-retry.cjs +4 -4
- package/dist/context/send-retry.cjs.map +1 -1
- package/dist/coordination/host/runtime.cjs +8 -30
- package/dist/coordination/host/runtime.cjs.map +1 -1
- package/dist/coordination/host/runtime.d.cts +3 -3
- package/dist/coordination/host/runtime.d.mts +3 -3
- package/dist/coordination/host/runtime.d.ts +3 -3
- package/dist/coordination/host/types.cjs.map +1 -1
- package/dist/coordination/host/types.d.cts +0 -5
- package/dist/coordination/host/types.d.mts +0 -5
- package/dist/coordination/host/types.d.ts +0 -5
- package/dist/coordination/platform/node.cjs +90 -9
- package/dist/coordination/platform/node.cjs.map +1 -1
- package/dist/define.cjs +25 -1
- package/dist/define.cjs.map +1 -1
- package/dist/experiment/host/index.cjs +1 -0
- package/dist/experiment/host/index.cjs.map +1 -1
- package/dist/i18n/en.cjs +26 -8
- package/dist/i18n/en.cjs.map +1 -1
- package/dist/i18n/en.d.cts +19 -6
- package/dist/i18n/en.d.mts +19 -6
- package/dist/i18n/en.d.ts +19 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/o11y/parsers/hermes.cjs +1 -1
- package/dist/o11y/parsers/hermes.cjs.map +1 -1
- package/dist/o11y/prices.json +1 -1
- package/dist/o11y/record/families.cjs +12 -3
- package/dist/o11y/record/families.cjs.map +1 -1
- package/dist/o11y/record/families.d.cts +8 -8
- package/dist/o11y/record/families.d.mts +8 -8
- package/dist/o11y/record/families.d.ts +8 -8
- package/dist/o11y/record/family-projectors.d.cts +66 -18
- package/dist/o11y/record/family-projectors.d.mts +66 -18
- package/dist/o11y/record/family-projectors.d.ts +66 -18
- package/dist/o11y/record/family-writers.d.cts +11 -3
- package/dist/o11y/record/family-writers.d.mts +11 -3
- package/dist/o11y/record/family-writers.d.ts +11 -3
- package/dist/o11y/record/runner-producer.cjs +14 -4
- package/dist/o11y/record/runner-producer.cjs.map +1 -1
- package/dist/record/codec/core.d.cts +2 -2
- package/dist/record/codec/core.d.mts +2 -2
- package/dist/record/codec/core.d.ts +2 -2
- package/dist/record/family/catalog.d.cts +44 -12
- package/dist/record/family/catalog.d.mts +44 -12
- package/dist/record/family/catalog.d.ts +44 -12
- package/dist/record/family/index.js +128 -124
- package/dist/record/family/observability-v1.cjs +35 -0
- package/dist/record/family/observability-v1.cjs.map +1 -0
- package/dist/record/family/observability-v1.d.cts +3 -0
- package/dist/record/family/observability-v1.d.mts +3 -0
- package/dist/record/family/observability-v1.d.ts +3 -0
- package/dist/record/family/observability-v1.js +6 -0
- package/dist/record/family/observability-v1.js.map +1 -0
- package/dist/record/family/observability.cjs +58 -9
- package/dist/record/family/observability.cjs.map +1 -1
- package/dist/record/family/observability.d.cts +1014 -15
- package/dist/record/family/observability.d.mts +1014 -15
- package/dist/record/family/observability.d.ts +1014 -15
- package/dist/record/family/observability.js +22 -18
- package/dist/record/host/runtime.cjs +600 -31
- package/dist/record/host/runtime.cjs.map +1 -1
- package/dist/record/host/types.cjs.map +1 -1
- package/dist/record/host/types.d.cts +33 -9
- package/dist/record/host/types.d.mts +33 -9
- package/dist/record/host/types.d.ts +33 -9
- package/dist/record/maintenance/runtime.cjs +1 -2
- package/dist/record/maintenance/runtime.cjs.map +1 -1
- package/dist/record/model/core.cjs.map +1 -1
- package/dist/record/model/core.d.cts +1 -1
- package/dist/record/model/core.d.mts +1 -1
- package/dist/record/model/core.d.ts +1 -1
- package/dist/record/model/definition.d.cts +6 -6
- package/dist/record/model/definition.d.mts +6 -6
- package/dist/record/model/definition.d.ts +6 -6
- package/dist/record/model/identifiers.cjs +1 -1
- package/dist/record/model/identifiers.cjs.map +1 -1
- package/dist/record/model/identifiers.d.cts +1 -1
- package/dist/record/model/identifiers.d.mts +1 -1
- package/dist/record/model/identifiers.d.ts +1 -1
- package/dist/record/platform/node.cjs +159 -23
- package/dist/record/platform/node.cjs.map +1 -1
- package/dist/record/platform/services.cjs.map +1 -1
- package/dist/record/platform/services.d.cts +15 -2
- package/dist/record/platform/services.d.mts +15 -2
- package/dist/record/platform/services.d.ts +15 -2
- package/dist/record/reader/errors.cjs +37 -1
- package/dist/record/reader/errors.cjs.map +1 -1
- package/dist/record/reader/errors.d.cts +49 -1
- package/dist/record/reader/errors.d.mts +49 -1
- package/dist/record/reader/errors.d.ts +49 -1
- package/dist/record/reader/errors.js +22 -12
- package/dist/record/reader/format.cjs +76 -17
- package/dist/record/reader/format.cjs.map +1 -1
- package/dist/record/reader/format.d.cts +11 -4
- package/dist/record/reader/format.d.mts +11 -4
- package/dist/record/reader/format.d.ts +11 -4
- package/dist/record/reader/format.js +2 -0
- package/dist/record/reader/index.js +32 -18
- package/dist/record/reader/runtime.cjs +64 -0
- package/dist/record/reader/runtime.cjs.map +1 -1
- package/dist/record/reader/runtime.d.cts +15 -0
- package/dist/record/reader/runtime.d.mts +15 -0
- package/dist/record/reader/runtime.d.ts +15 -0
- package/dist/record/reader/runtime.js +2 -0
- package/dist/report/assets/styles.css +53 -29
- package/dist/report/built-in/analysis-values.cjs +21 -13
- package/dist/report/built-in/analysis-values.cjs.map +1 -1
- package/dist/report/built-in/analysis-values.d.cts +11 -22
- package/dist/report/built-in/analysis-values.d.mts +11 -22
- package/dist/report/built-in/analysis-values.d.ts +11 -22
- package/dist/report/built-in/analysis-values.js +4 -6
- package/dist/report/built-in/index.cjs +1 -3
- package/dist/report/built-in/index.cjs.map +1 -1
- package/dist/report/built-in/index.d.cts +1 -1
- package/dist/report/built-in/index.d.mts +1 -1
- package/dist/report/built-in/index.d.ts +1 -1
- package/dist/report/built-in/index.js +4 -8
- package/dist/report/built-in/index.mjs +4 -8
- package/dist/report/built-in/machine.cjs +0 -2
- package/dist/report/built-in/machine.cjs.map +1 -1
- package/dist/report/built-in/machine.d.cts +0 -2
- package/dist/report/built-in/machine.d.mts +0 -2
- package/dist/report/built-in/machine.d.ts +0 -2
- package/dist/report/built-in/result-components.cjs +49 -51
- package/dist/report/built-in/result-components.cjs.map +1 -1
- package/dist/report/built-in/result-components.d.cts +16 -17
- package/dist/report/built-in/result-components.d.mts +16 -17
- package/dist/report/built-in/result-components.d.ts +16 -17
- package/dist/report/built-in/result-components.js +0 -4
- package/dist/report/built-in/run-membership-overview.cjs +126 -17
- package/dist/report/built-in/run-membership-overview.cjs.map +1 -1
- package/dist/report/built-in/run-membership-overview.d.cts +14 -0
- package/dist/report/built-in/run-membership-overview.d.mts +14 -0
- package/dist/report/built-in/run-membership-overview.d.ts +14 -0
- package/dist/report/built-in/run-membership-overview.js +4 -2
- package/dist/report/built-in/standard.cjs +80 -45
- package/dist/report/built-in/standard.cjs.map +1 -1
- package/dist/report/built-in/standard.d.cts +19 -41
- package/dist/report/built-in/standard.d.mts +19 -41
- package/dist/report/built-in/standard.d.ts +19 -41
- package/dist/report/built-in/standard.js +6 -6
- package/dist/report/components/attempt-detail/compute.cjs +78 -2
- package/dist/report/components/attempt-detail/compute.cjs.map +1 -1
- package/dist/report/components/attempt-detail/compute.d.cts +1 -1
- package/dist/report/components/attempt-detail/compute.d.mts +1 -1
- package/dist/report/components/attempt-detail/compute.d.ts +1 -1
- package/dist/report/components/attempt-detail/content.cjs +1 -1
- package/dist/report/components/attempt-detail/content.cjs.map +1 -1
- package/dist/report/components/entity-lists/compute.cjs +42 -5
- package/dist/report/components/entity-lists/compute.cjs.map +1 -1
- package/dist/report/components/entity-lists/compute.d.cts +10 -8
- package/dist/report/components/entity-lists/compute.d.mts +10 -8
- package/dist/report/components/entity-lists/compute.d.ts +10 -8
- package/dist/report/components/entity-lists/content.cjs +31 -6
- package/dist/report/components/entity-lists/content.cjs.map +1 -1
- package/dist/report/components/entity-lists/index.cjs +9 -4
- package/dist/report/components/entity-lists/index.cjs.map +1 -1
- package/dist/report/components/entity-lists/index.d.cts +4 -7
- package/dist/report/components/entity-lists/index.d.mts +4 -7
- package/dist/report/components/entity-lists/index.d.ts +4 -7
- package/dist/report/components/summaries/compute.cjs +8 -1
- package/dist/report/components/summaries/compute.cjs.map +1 -1
- package/dist/report/components/summaries/compute.d.cts +9 -1
- package/dist/report/components/summaries/compute.d.mts +9 -1
- package/dist/report/components/summaries/compute.d.ts +9 -1
- package/dist/report/components/summaries/index.cjs +48 -4
- package/dist/report/components/summaries/index.cjs.map +1 -1
- package/dist/report/components/summaries/index.d.cts +7 -4
- package/dist/report/components/summaries/index.d.mts +7 -4
- package/dist/report/components/summaries/index.d.ts +7 -4
- package/dist/report/definition/cell.cjs +3 -0
- package/dist/report/definition/cell.cjs.map +1 -1
- package/dist/report/definition/cell.d.cts +2 -0
- package/dist/report/definition/cell.d.mts +2 -0
- package/dist/report/definition/cell.d.ts +2 -0
- package/dist/report/definition/primitives/chart.cjs +8 -5
- package/dist/report/definition/primitives/chart.cjs.map +1 -1
- package/dist/report/definition/primitives/points-dataset.cjs +1 -0
- package/dist/report/definition/primitives/points-dataset.cjs.map +1 -1
- package/dist/report/definition/primitives.cjs +15 -2
- package/dist/report/definition/primitives.cjs.map +1 -1
- package/dist/report/definition/report.cjs +27 -3
- package/dist/report/definition/report.cjs.map +1 -1
- package/dist/report/definition/report.d.cts +8 -0
- package/dist/report/definition/report.d.mts +8 -0
- package/dist/report/definition/report.d.ts +8 -0
- package/dist/report/execution/machine.cjs +30 -14
- package/dist/report/execution/machine.cjs.map +1 -1
- package/dist/report/execution/machine.d.cts +20 -10
- package/dist/report/execution/machine.d.mts +20 -10
- package/dist/report/execution/machine.d.ts +20 -10
- package/dist/report/execution/machine.js +6 -6
- package/dist/report/host/execute.cjs +25 -16
- package/dist/report/host/execute.cjs.map +1 -1
- package/dist/report/host/execute.d.cts +4 -0
- package/dist/report/host/execute.d.mts +4 -0
- package/dist/report/host/execute.d.ts +4 -0
- package/dist/report/host/from-record.cjs +22 -5
- package/dist/report/host/from-record.cjs.map +1 -1
- package/dist/report/host/machine.cjs +53 -11
- package/dist/report/host/machine.cjs.map +1 -1
- package/dist/report/host/site-runtime.cjs +9 -8
- package/dist/report/host/site-runtime.cjs.map +1 -1
- package/dist/report/host/site-runtime.d.cts +1 -1
- package/dist/report/host/site-runtime.d.mts +1 -1
- package/dist/report/host/site-runtime.d.ts +1 -1
- package/dist/report/host/static.cjs +56 -9
- package/dist/report/host/static.cjs.map +1 -1
- package/dist/report/index.cjs.map +1 -1
- package/dist/report/index.d.cts +1 -1
- package/dist/report/index.d.mts +1 -1
- package/dist/report/index.d.ts +1 -1
- package/dist/report/model/dataset.cjs +7 -2
- package/dist/report/model/dataset.cjs.map +1 -1
- package/dist/report/model/locale.cjs +2 -0
- package/dist/report/model/locale.cjs.map +1 -1
- package/dist/report/model/locale.d.cts +1 -0
- package/dist/report/model/locale.d.mts +1 -0
- package/dist/report/model/locale.d.ts +1 -0
- package/dist/report/model/metrics.cjs +3 -1
- package/dist/report/model/metrics.cjs.map +1 -1
- package/dist/report/model/pricing.cjs +16 -11
- package/dist/report/model/pricing.cjs.map +1 -1
- package/dist/report/react/styles.css +53 -29
- package/dist/runner/adoption.cjs +1 -0
- package/dist/runner/adoption.cjs.map +1 -1
- package/dist/runner/attempt.cjs +44 -5
- package/dist/runner/attempt.cjs.map +1 -1
- package/dist/runner/attempt.d.cts +13 -1
- package/dist/runner/attempt.d.mts +13 -1
- package/dist/runner/attempt.d.ts +13 -1
- package/dist/runner/config-identity.cjs +10 -2
- package/dist/runner/config-identity.cjs.map +1 -1
- package/dist/runner/config-identity.d.cts +4 -2
- package/dist/runner/config-identity.d.mts +4 -2
- package/dist/runner/config-identity.d.ts +4 -2
- package/dist/runner/feedback/human.cjs +298 -59
- package/dist/runner/feedback/human.cjs.map +1 -1
- package/dist/runner/feedback/json.cjs +14 -6
- package/dist/runner/feedback/json.cjs.map +1 -1
- package/dist/runner/feedback/json.d.cts +3 -2
- package/dist/runner/feedback/json.d.mts +3 -2
- package/dist/runner/feedback/json.d.ts +3 -2
- package/dist/runner/feedback/reducer.cjs +2 -0
- package/dist/runner/feedback/reducer.cjs.map +1 -1
- package/dist/runner/judge-config.cjs +1 -1
- package/dist/runner/judge-config.cjs.map +1 -1
- package/dist/runner/ledger.cjs +124 -12
- package/dist/runner/ledger.cjs.map +1 -1
- package/dist/runner/project-current.cjs +13 -5
- package/dist/runner/project-current.cjs.map +1 -1
- package/dist/runner/run.cjs +564 -256
- package/dist/runner/run.cjs.map +1 -1
- package/dist/runner/sandbox-pool.cjs +161 -30
- package/dist/runner/sandbox-pool.cjs.map +1 -1
- package/dist/runner/sandbox-pool.d.cts +34 -2
- package/dist/runner/sandbox-pool.d.mts +34 -2
- package/dist/runner/sandbox-pool.d.ts +34 -2
- package/dist/runner/shared-state-lease.cjs +803 -0
- package/dist/runner/shared-state-lease.cjs.map +1 -0
- package/dist/runner/shared-state-lease.d.cts +121 -0
- package/dist/runner/shared-state-lease.d.mts +121 -0
- package/dist/runner/shared-state-lease.d.ts +121 -0
- package/dist/runner/shared-state-lease.js +24 -0
- package/dist/runner/shared-state-lease.js.map +1 -0
- package/dist/runner/teardown-registry.cjs +97 -34
- package/dist/runner/teardown-registry.cjs.map +1 -1
- package/dist/runner/teardown-registry.d.cts +27 -1
- package/dist/runner/teardown-registry.d.mts +27 -1
- package/dist/runner/teardown-registry.d.ts +27 -1
- package/dist/runner/teardown-registry.js +20 -16
- package/dist/runner/types.cjs.map +1 -1
- package/dist/runner/types.d.cts +31 -5
- package/dist/runner/types.d.mts +31 -5
- package/dist/runner/types.d.ts +31 -5
- package/dist/sample/analysis.cjs +249 -459
- package/dist/sample/analysis.cjs.map +1 -1
- package/dist/sample/capability.cjs +138 -7
- package/dist/sample/capability.cjs.map +1 -1
- package/dist/sample/capability.d.cts +3 -1
- package/dist/sample/capability.d.mts +3 -1
- package/dist/sample/capability.d.ts +3 -1
- package/dist/sample/capability.js +8 -6
- package/dist/sandbox/docker-agent-image.cjs +5 -1
- package/dist/sandbox/docker-agent-image.cjs.map +1 -1
- package/dist/sandbox/docker-agent-image.d.cts +2 -0
- package/dist/sandbox/docker-agent-image.d.mts +2 -0
- package/dist/sandbox/docker-agent-image.d.ts +2 -0
- package/dist/sandbox/docker-agent-image.js +14 -10
- package/dist/sandbox/docker-profile/schema.cjs +283 -534
- package/dist/sandbox/docker-profile/schema.cjs.map +1 -1
- package/dist/sandbox/docker-profile/schema.d.cts +10 -27
- package/dist/sandbox/docker-profile/schema.d.mts +10 -27
- package/dist/sandbox/docker-profile/schema.d.ts +10 -27
- package/dist/sandbox/e2b-agent-template.cjs +5 -2
- package/dist/sandbox/e2b-agent-template.cjs.map +1 -1
- package/dist/sandbox/index.cjs +4 -2
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.d.cts +1 -1
- package/dist/sandbox/index.d.mts +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.js +120 -116
- package/dist/sandbox/index.mjs +120 -116
- package/dist/sandbox/keep-registry.cjs +28 -66
- package/dist/sandbox/keep-registry.cjs.map +1 -1
- package/dist/sandbox/types.cjs.map +1 -1
- package/dist/sandbox/types.d.cts +1 -1
- package/dist/sandbox/types.d.mts +1 -1
- package/dist/sandbox/types.d.ts +1 -1
- package/dist/shared/aggregate.cjs +16 -6
- package/dist/shared/aggregate.cjs.map +1 -1
- package/dist/shared/aggregate.d.cts +12 -4
- package/dist/shared/aggregate.d.mts +12 -4
- package/dist/shared/aggregate.d.ts +12 -4
- package/dist/shared/turn-label.cjs +20 -2
- package/dist/shared/turn-label.cjs.map +1 -1
- package/dist/shared/turn-label.d.cts +2 -0
- package/dist/shared/turn-label.d.mts +2 -0
- package/dist/shared/turn-label.d.ts +2 -0
- package/dist/shared/turn-label.js +4 -2
- package/dist/vendor/remark/remark-gfm.cjs +7 -7
- package/dist/vendor/remark/remark-gfm.cjs.map +1 -1
- package/dist/vendor/remark/remark-parse.cjs +12 -12
- package/dist/vendor/remark/remark-parse.cjs.map +1 -1
- package/dist/vendor/remark/remark.cjs +13 -13
- package/dist/vendor/remark/remark.cjs.map +1 -1
- package/dist/view/server.cjs +6 -0
- package/dist/view/server.cjs.map +1 -1
- package/docs-site/images/hitl-handshake-zh.svg +3 -3
- package/docs-site/zh/examples/ai-agent-application.mdx +1 -1
- package/docs-site/zh/examples/coding-agent-extensions.mdx +1 -1
- package/docs-site/zh/examples/integrations/ai-sdk-v7.mdx +4 -3
- package/docs-site/zh/examples/integrations/claude-sdk.mdx +3 -3
- package/docs-site/zh/examples/integrations/codex-sdk.mdx +5 -5
- package/docs-site/zh/examples/integrations/langgraph.mdx +4 -4
- package/docs-site/zh/examples/integrations/pi-sdk.mdx +5 -5
- package/docs-site/zh/explanation/adapter.mdx +5 -5
- package/docs-site/zh/explanation/assert.mdx +7 -7
- package/docs-site/zh/explanation/drive.mdx +4 -2
- package/docs-site/zh/explanation/evals.mdx +10 -8
- package/docs-site/zh/explanation/experiment.mdx +2 -2
- package/docs-site/zh/explanation/hitl.mdx +9 -9
- package/docs-site/zh/explanation/judge.mdx +5 -5
- package/docs-site/zh/explanation/overview.mdx +3 -3
- package/docs-site/zh/explanation/runner.mdx +1 -1
- package/docs-site/zh/index.mdx +3 -3
- package/docs-site/zh/reference/builtin-agents.mdx +13 -13
- package/docs-site/zh/reference/capabilities.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +18 -12
- package/docs-site/zh/reference/define-agent.mdx +130 -5
- package/docs-site/zh/reference/define-config.mdx +2 -2
- package/docs-site/zh/reference/define-eval.mdx +4 -4
- package/docs-site/zh/reference/events.mdx +11 -11
- package/docs-site/zh/reference/official-adapters.mdx +6 -6
- package/docs-site/zh/reference/results-data.mdx +6 -0
- package/docs-site/zh/troubleshooting/debugging.mdx +1 -1
- package/docs-site/zh/troubleshooting/recover-after-kill.mdx +37 -5
- package/docs-site/zh/tutorials/accept-results.mdx +72 -0
- package/docs-site/zh/tutorials/agent-onboarding.mdx +6 -6
- package/docs-site/zh/tutorials/authoring.mdx +9 -13
- package/docs-site/zh/tutorials/ci-integration.mdx +6 -6
- package/docs-site/zh/tutorials/compare-runs.mdx +61 -0
- package/docs-site/zh/tutorials/concurrency.mdx +13 -12
- package/docs-site/zh/tutorials/configuration.mdx +4 -6
- package/docs-site/zh/tutorials/connect-your-agent.mdx +1 -1
- package/docs-site/zh/tutorials/control-run-cost.mdx +58 -0
- package/docs-site/zh/tutorials/criteria-files.mdx +2 -2
- package/docs-site/zh/tutorials/custom-reports.mdx +2 -2
- package/docs-site/zh/tutorials/debug-lifecycle-plan.mdx +46 -0
- package/docs-site/zh/tutorials/docker-in-docker.mdx +52 -43
- package/docs-site/zh/tutorials/eval-groups.mdx +6 -6
- package/docs-site/zh/tutorials/evaluation-kinds.mdx +3 -3
- package/docs-site/zh/tutorials/experiments.mdx +2 -3
- package/docs-site/zh/tutorials/handle-execution-failures.mdx +18 -10
- package/docs-site/zh/tutorials/install-custom-sandbox-agent.mdx +16 -8
- package/docs-site/zh/tutorials/nixos-managed-dind.mdx +160 -0
- package/docs-site/zh/tutorials/plugins.mdx +14 -0
- package/docs-site/zh/tutorials/rerun-and-cache.mdx +4 -2
- package/docs-site/zh/tutorials/sandbox-agent.mdx +17 -11
- package/docs-site/zh/tutorials/sandbox-providers.mdx +17 -13
- package/docs-site/zh/tutorials/sandbox-reuse.mdx +12 -19
- package/docs-site/zh/tutorials/select-and-preview.mdx +65 -0
- package/docs-site/zh/tutorials/verify-judge.mdx +78 -0
- package/docs-site/zh/tutorials/viewing-results.mdx +38 -1
- package/docs-site/zh/tutorials/write-experiment.mdx +7 -5
- package/docs-site/zh/tutorials/write-send.mdx +9 -8
- package/package.json +9 -5
- package/dist/report/built-in/overview.cjs +0 -44
- package/dist/report/built-in/overview.cjs.map +0 -1
- package/dist/report/built-in/overview.d.cts +0 -6
- package/dist/report/built-in/overview.d.mts +0 -6
- package/dist/report/built-in/overview.d.ts +0 -6
- package/dist/report/built-in/overview.js +0 -9
- package/dist/report/built-in/overview.js.map +0 -1
- package/dist/runner/gate-lease.cjs +0 -283
- package/dist/runner/gate-lease.cjs.map +0 -1
- package/dist/runner/gate-lease.d.cts +0 -62
- package/dist/runner/gate-lease.d.mts +0 -62
- package/dist/runner/gate-lease.d.ts +0 -62
- package/dist/runner/gate-lease.js +0 -22
- package/dist/runner/gate-lease.js.map +0 -1
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SharedStateLeaseError = exports.SHARED_STATE_LEASE_HEARTBEAT_INTERVAL_MS = void 0;
|
|
4
|
+
exports.sharedStateLeasesDirOf = sharedStateLeasesDirOf;
|
|
5
|
+
exports.readSharedStateLeaseEffect = readSharedStateLeaseEffect;
|
|
6
|
+
exports.readSharedStateLeaseRecoveryTargetEffect = readSharedStateLeaseRecoveryTargetEffect;
|
|
7
|
+
exports.processIdentityForPidEffect = processIdentityForPidEffect;
|
|
8
|
+
exports.currentProcessIdentityEffect = currentProcessIdentityEffect;
|
|
9
|
+
exports.acquireSharedStateLeaseEffect = acquireSharedStateLeaseEffect;
|
|
10
|
+
exports.beginExplicitSharedStateRecoveryEffect = beginExplicitSharedStateRecoveryEffect;
|
|
11
|
+
exports.completeExplicitSharedStateRecoveryEffect = completeExplicitSharedStateRecoveryEffect;
|
|
12
|
+
// Shared-state leases protect an author's external mutable state. They are
|
|
13
|
+
// deliberately *not* case locks: a lease never expires and is never taken
|
|
14
|
+
// over by a waiter. A crashed or failed cleanup path stays visible until an
|
|
15
|
+
// operator performs the explicit, owner-token-checked recovery flow.
|
|
16
|
+
//
|
|
17
|
+
// The durable authority is an append-only generation chain. A transition may
|
|
18
|
+
// only publish generation N + 1 after observing exact generation N, and uses
|
|
19
|
+
// a same-directory hard-link to occupy that fixed N + 1 name. Consequently a
|
|
20
|
+
// stale actor cannot delete, replace, or otherwise reach a newer generation.
|
|
21
|
+
const node_child_process_1 = require("node:child_process");
|
|
22
|
+
const node_crypto_1 = require("node:crypto");
|
|
23
|
+
const promises_1 = require("node:fs/promises");
|
|
24
|
+
const node_path_1 = require("node:path");
|
|
25
|
+
const effect_1 = require("effect");
|
|
26
|
+
const entry_file_store_ts_1 = require("../shared/entry-file-store.cjs");
|
|
27
|
+
const SHARED_STATE_LEASE_NAMESPACE = "niceeval/shared-state/v3";
|
|
28
|
+
const SHARED_STATE_GENERATION_FORMAT = "niceeval.shared-state-generation/v1";
|
|
29
|
+
const SHARED_STATE_HEARTBEAT_FORMAT = "niceeval.shared-state-heartbeat/v1";
|
|
30
|
+
const GENERATION_FILE_PREFIX = "generation-";
|
|
31
|
+
const GENERATION_FILE_SUFFIX = ".json";
|
|
32
|
+
const GENERATION_WIDTH = 20;
|
|
33
|
+
exports.SHARED_STATE_LEASE_HEARTBEAT_INTERVAL_MS = 10000;
|
|
34
|
+
class SharedStateLeaseError extends Error {
|
|
35
|
+
code;
|
|
36
|
+
constructor(code, message) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.code = code;
|
|
39
|
+
this.name = "SharedStateLeaseError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.SharedStateLeaseError = SharedStateLeaseError;
|
|
43
|
+
function errnoCode(cause) {
|
|
44
|
+
return typeof cause === "object" && cause !== null && "code" in cause && typeof cause.code === "string"
|
|
45
|
+
? cause.code
|
|
46
|
+
: undefined;
|
|
47
|
+
}
|
|
48
|
+
function isPositiveInteger(value) {
|
|
49
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
50
|
+
}
|
|
51
|
+
function isNonNegativeInteger(value) {
|
|
52
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
53
|
+
}
|
|
54
|
+
function isTimestamp(value) {
|
|
55
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
56
|
+
}
|
|
57
|
+
function isNonEmptyString(value) {
|
|
58
|
+
return typeof value === "string" && value.length > 0;
|
|
59
|
+
}
|
|
60
|
+
function recordOf(value) {
|
|
61
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
62
|
+
? value
|
|
63
|
+
: undefined;
|
|
64
|
+
}
|
|
65
|
+
function nodeIo(operation) {
|
|
66
|
+
return effect_1.Effect.tryPromise({ try: operation, catch: (cause) => cause });
|
|
67
|
+
}
|
|
68
|
+
function nodeSync(operation) {
|
|
69
|
+
return effect_1.Effect.try({ try: operation, catch: (cause) => cause });
|
|
70
|
+
}
|
|
71
|
+
function withOpenFile(path, flags, use) {
|
|
72
|
+
return effect_1.Effect.scoped(effect_1.Effect.acquireRelease(nodeIo(() => (0, promises_1.open)(path, flags)), (handle) => nodeIo(() => handle.close()).pipe(effect_1.Effect.orDie)).pipe(effect_1.Effect.flatMap(use)));
|
|
73
|
+
}
|
|
74
|
+
function sharedStateLeasesDirOf(niceevalRoot) {
|
|
75
|
+
return (0, node_path_1.join)(niceevalRoot, "shared-state-leases");
|
|
76
|
+
}
|
|
77
|
+
function sharedStateLeaseEntryId(key) {
|
|
78
|
+
return (0, entry_file_store_ts_1.slugHashEntryId)(key, [SHARED_STATE_LEASE_NAMESPACE, key]);
|
|
79
|
+
}
|
|
80
|
+
function sharedStateLeaseGenerationDirOf(niceevalRoot, key) {
|
|
81
|
+
return (0, node_path_1.join)(sharedStateLeasesDirOf(niceevalRoot), `${sharedStateLeaseEntryId(key)}.generations`);
|
|
82
|
+
}
|
|
83
|
+
function sharedStateLeaseHeartbeatDirOf(generationDir) {
|
|
84
|
+
return (0, node_path_1.join)(generationDir, "heartbeats");
|
|
85
|
+
}
|
|
86
|
+
function sharedStateLeaseHeartbeatEntryId(record) {
|
|
87
|
+
return (0, entry_file_store_ts_1.slugHashEntryId)(`generation-${record.generation}`, ["niceeval/shared-state-heartbeat/v1", record.key, record.ownerToken, String(record.generation)]);
|
|
88
|
+
}
|
|
89
|
+
/** v2 used a mutable flat entry. We only read it to fail closed or explicitly recover it. */
|
|
90
|
+
function legacySharedStateLeasePath(niceevalRoot, key) {
|
|
91
|
+
return (0, node_path_1.join)(sharedStateLeasesDirOf(niceevalRoot), `${(0, entry_file_store_ts_1.slugHashEntryId)(key, ["niceeval/shared-state/v2", key])}.json`);
|
|
92
|
+
}
|
|
93
|
+
function generationFileName(generation) {
|
|
94
|
+
return `${GENERATION_FILE_PREFIX}${String(generation).padStart(GENERATION_WIDTH, "0")}${GENERATION_FILE_SUFFIX}`;
|
|
95
|
+
}
|
|
96
|
+
function generationFromFileName(name) {
|
|
97
|
+
const match = /^generation-(\d+)\.json$/u.exec(name);
|
|
98
|
+
if (match === null)
|
|
99
|
+
return undefined;
|
|
100
|
+
const generation = Number(match[1]);
|
|
101
|
+
return isPositiveInteger(generation) && name === generationFileName(generation) ? generation : undefined;
|
|
102
|
+
}
|
|
103
|
+
function decodeRecoveryActor(value) {
|
|
104
|
+
const actor = recordOf(value);
|
|
105
|
+
if (actor === undefined ||
|
|
106
|
+
!isPositiveInteger(actor.pid) ||
|
|
107
|
+
!isNonEmptyString(actor.host) ||
|
|
108
|
+
!isNonEmptyString(actor.processIdentity) ||
|
|
109
|
+
!isTimestamp(actor.startedAt)) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
return Object.freeze({
|
|
113
|
+
pid: actor.pid,
|
|
114
|
+
host: actor.host,
|
|
115
|
+
processIdentity: actor.processIdentity,
|
|
116
|
+
startedAt: actor.startedAt,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function decodeSharedStateLeaseRecord(key, value, opts = {}) {
|
|
120
|
+
const record = recordOf(value);
|
|
121
|
+
if (record === undefined)
|
|
122
|
+
return undefined;
|
|
123
|
+
const recoveryActor = record.recoveryActor === undefined ? undefined : decodeRecoveryActor(record.recoveryActor);
|
|
124
|
+
const recovering = record.status === "recovering";
|
|
125
|
+
const generation = opts.legacy ? 0 : record.generation;
|
|
126
|
+
const parentGeneration = opts.legacy ? 0 : record.parentGeneration;
|
|
127
|
+
if (record.key !== key ||
|
|
128
|
+
!isNonEmptyString(record.experimentId) ||
|
|
129
|
+
!isNonEmptyString(record.ownerToken) ||
|
|
130
|
+
!isPositiveInteger(record.pid) ||
|
|
131
|
+
!isNonEmptyString(record.host) ||
|
|
132
|
+
!isNonEmptyString(record.processIdentity) ||
|
|
133
|
+
!isTimestamp(record.acquiredAt) ||
|
|
134
|
+
!isTimestamp(record.heartbeatAt) ||
|
|
135
|
+
(record.status !== "active" && !recovering) ||
|
|
136
|
+
(!opts.legacy && (!isPositiveInteger(generation) || !isNonNegativeInteger(parentGeneration) || parentGeneration >= generation)) ||
|
|
137
|
+
(recovering && (!isNonEmptyString(record.recoveryId) || recoveryActor === undefined)) ||
|
|
138
|
+
(!recovering && (record.recoveryId !== undefined || record.recoveryActor !== undefined))) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
return Object.freeze({
|
|
142
|
+
key,
|
|
143
|
+
experimentId: record.experimentId,
|
|
144
|
+
ownerToken: record.ownerToken,
|
|
145
|
+
pid: record.pid,
|
|
146
|
+
host: record.host,
|
|
147
|
+
processIdentity: record.processIdentity,
|
|
148
|
+
acquiredAt: record.acquiredAt,
|
|
149
|
+
heartbeatAt: record.heartbeatAt,
|
|
150
|
+
status: record.status,
|
|
151
|
+
generation: generation,
|
|
152
|
+
parentGeneration: parentGeneration,
|
|
153
|
+
...(recovering ? { recoveryId: record.recoveryId, recoveryActor: recoveryActor } : {}),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function decodeSharedStateLeaseHeartbeat(record, value) {
|
|
157
|
+
const heartbeat = recordOf(value);
|
|
158
|
+
if (heartbeat === undefined ||
|
|
159
|
+
heartbeat.format !== SHARED_STATE_HEARTBEAT_FORMAT ||
|
|
160
|
+
heartbeat.key !== record.key ||
|
|
161
|
+
heartbeat.ownerToken !== record.ownerToken ||
|
|
162
|
+
heartbeat.generation !== record.generation ||
|
|
163
|
+
heartbeat.parentGeneration !== record.parentGeneration ||
|
|
164
|
+
!isTimestamp(heartbeat.heartbeatAt) ||
|
|
165
|
+
Date.parse(heartbeat.heartbeatAt) < Date.parse(record.acquiredAt)) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return Object.freeze({
|
|
169
|
+
format: SHARED_STATE_HEARTBEAT_FORMAT,
|
|
170
|
+
key: record.key,
|
|
171
|
+
ownerToken: record.ownerToken,
|
|
172
|
+
generation: record.generation,
|
|
173
|
+
parentGeneration: record.parentGeneration,
|
|
174
|
+
heartbeatAt: heartbeat.heartbeatAt,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* A heartbeat is diagnostic only: unreadable, missing, stale, or malformed
|
|
179
|
+
* sidecars leave the immutable generation record unchanged. Matching all of
|
|
180
|
+
* key/token/generation/parent makes an old owner's sidecar inapplicable after
|
|
181
|
+
* any release, recovery, or later acquisition.
|
|
182
|
+
*/
|
|
183
|
+
function readDiagnosticHeartbeatEffect(generationDir, record) {
|
|
184
|
+
return (0, entry_file_store_ts_1.readEntryFileEffect)(sharedStateLeaseHeartbeatDirOf(generationDir), sharedStateLeaseHeartbeatEntryId(record), (value) => decodeSharedStateLeaseHeartbeat(record, value)).pipe(effect_1.Effect.map((heartbeat) => heartbeat === undefined
|
|
185
|
+
? record
|
|
186
|
+
: Object.freeze({ ...record, heartbeatAt: heartbeat.heartbeatAt })), effect_1.Effect.catchAll(() => effect_1.Effect.succeed(record)));
|
|
187
|
+
}
|
|
188
|
+
function writeDiagnosticHeartbeatEffect(generationDir, record, heartbeatAt) {
|
|
189
|
+
const heartbeat = Object.freeze({
|
|
190
|
+
format: SHARED_STATE_HEARTBEAT_FORMAT,
|
|
191
|
+
key: record.key,
|
|
192
|
+
ownerToken: record.ownerToken,
|
|
193
|
+
generation: record.generation,
|
|
194
|
+
parentGeneration: record.parentGeneration,
|
|
195
|
+
heartbeatAt,
|
|
196
|
+
});
|
|
197
|
+
return (0, entry_file_store_ts_1.writeEntryFileEffect)(sharedStateLeaseHeartbeatDirOf(generationDir), sharedStateLeaseHeartbeatEntryId(record), heartbeat);
|
|
198
|
+
}
|
|
199
|
+
function decodeGeneration(key, value) {
|
|
200
|
+
const generation = recordOf(value);
|
|
201
|
+
if (generation === undefined || generation.format !== SHARED_STATE_GENERATION_FORMAT)
|
|
202
|
+
return undefined;
|
|
203
|
+
if (generation.kind === "lease") {
|
|
204
|
+
const record = decodeSharedStateLeaseRecord(key, generation);
|
|
205
|
+
return record === undefined ? undefined : Object.freeze({ kind: "lease", record });
|
|
206
|
+
}
|
|
207
|
+
if (generation.kind !== "free")
|
|
208
|
+
return undefined;
|
|
209
|
+
const previous = decodeSharedStateLeaseRecord(key, generation.previous);
|
|
210
|
+
if (previous === undefined ||
|
|
211
|
+
generation.key !== key ||
|
|
212
|
+
!isPositiveInteger(generation.generation) ||
|
|
213
|
+
!isNonNegativeInteger(generation.parentGeneration) ||
|
|
214
|
+
generation.parentGeneration >= generation.generation ||
|
|
215
|
+
generation.parentGeneration !== previous.generation ||
|
|
216
|
+
!isTimestamp(generation.releasedAt)) {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
return Object.freeze({
|
|
220
|
+
kind: "free",
|
|
221
|
+
key,
|
|
222
|
+
generation: generation.generation,
|
|
223
|
+
parentGeneration: generation.parentGeneration,
|
|
224
|
+
releasedAt: generation.releasedAt,
|
|
225
|
+
previous,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function durableLeaseGeneration(record) {
|
|
229
|
+
return { format: SHARED_STATE_GENERATION_FORMAT, kind: "lease", ...record };
|
|
230
|
+
}
|
|
231
|
+
function durableFreeGeneration(record) {
|
|
232
|
+
return {
|
|
233
|
+
format: SHARED_STATE_GENERATION_FORMAT,
|
|
234
|
+
kind: "free",
|
|
235
|
+
key: record.key,
|
|
236
|
+
generation: record.generation,
|
|
237
|
+
parentGeneration: record.parentGeneration,
|
|
238
|
+
releasedAt: record.releasedAt,
|
|
239
|
+
previous: record.previous,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function generationNumber(generation) {
|
|
243
|
+
if (generation === undefined)
|
|
244
|
+
return 0;
|
|
245
|
+
return generation.kind === "free" ? generation.generation : generation.record.generation;
|
|
246
|
+
}
|
|
247
|
+
function leaseRecordOf(generation) {
|
|
248
|
+
return generation?.kind === "lease" || generation?.kind === "legacy" ? generation.record : undefined;
|
|
249
|
+
}
|
|
250
|
+
function sameRecoveryActor(left, right) {
|
|
251
|
+
return left.pid === right.pid &&
|
|
252
|
+
left.host === right.host &&
|
|
253
|
+
left.processIdentity === right.processIdentity &&
|
|
254
|
+
left.startedAt === right.startedAt;
|
|
255
|
+
}
|
|
256
|
+
function sameLeaseRecord(left, right) {
|
|
257
|
+
return left.key === right.key &&
|
|
258
|
+
left.experimentId === right.experimentId &&
|
|
259
|
+
left.ownerToken === right.ownerToken &&
|
|
260
|
+
left.pid === right.pid &&
|
|
261
|
+
left.host === right.host &&
|
|
262
|
+
left.processIdentity === right.processIdentity &&
|
|
263
|
+
left.acquiredAt === right.acquiredAt &&
|
|
264
|
+
left.heartbeatAt === right.heartbeatAt &&
|
|
265
|
+
left.status === right.status &&
|
|
266
|
+
left.generation === right.generation &&
|
|
267
|
+
left.parentGeneration === right.parentGeneration &&
|
|
268
|
+
left.recoveryId === right.recoveryId &&
|
|
269
|
+
(left.recoveryActor === undefined
|
|
270
|
+
? right.recoveryActor === undefined
|
|
271
|
+
: right.recoveryActor !== undefined && sameRecoveryActor(left.recoveryActor, right.recoveryActor));
|
|
272
|
+
}
|
|
273
|
+
/** The immutable holder evidence must survive a recovery-to-recovery transition. */
|
|
274
|
+
function sameLeaseOwnerEvidence(left, right) {
|
|
275
|
+
return left.key === right.key &&
|
|
276
|
+
left.experimentId === right.experimentId &&
|
|
277
|
+
left.ownerToken === right.ownerToken &&
|
|
278
|
+
left.pid === right.pid &&
|
|
279
|
+
left.host === right.host &&
|
|
280
|
+
left.processIdentity === right.processIdentity &&
|
|
281
|
+
left.acquiredAt === right.acquiredAt &&
|
|
282
|
+
left.heartbeatAt === right.heartbeatAt;
|
|
283
|
+
}
|
|
284
|
+
function legalFirstGeneration(first, legacy) {
|
|
285
|
+
if (first.kind !== "lease")
|
|
286
|
+
return false;
|
|
287
|
+
if (first.record.status === "active")
|
|
288
|
+
return legacy === undefined;
|
|
289
|
+
// The only allowed v2 migration is explicit recovery: legacy has no v3
|
|
290
|
+
// transition slot, so it becomes immutable recovering generation 1.
|
|
291
|
+
return legacy !== undefined && sameLeaseOwnerEvidence(first.record, legacy);
|
|
292
|
+
}
|
|
293
|
+
function legalGenerationTransition(previous, next) {
|
|
294
|
+
if (previous.kind === "free") {
|
|
295
|
+
// Only a normal acquisition may follow a released generation.
|
|
296
|
+
return next.kind === "lease" && next.record.status === "active";
|
|
297
|
+
}
|
|
298
|
+
const previousRecord = previous.record;
|
|
299
|
+
if (next.kind === "free") {
|
|
300
|
+
// A free record is a compare-owner release, not a newly invented history.
|
|
301
|
+
return sameLeaseRecord(next.previous, previousRecord);
|
|
302
|
+
}
|
|
303
|
+
// An active holder cannot be replaced by another active holder. The sole
|
|
304
|
+
// lease-to-lease transition is explicit recovery, retaining the original
|
|
305
|
+
// owner evidence while assigning a new recovery actor/id.
|
|
306
|
+
if (next.record.status !== "recovering" || !sameLeaseOwnerEvidence(next.record, previousRecord))
|
|
307
|
+
return false;
|
|
308
|
+
if (previousRecord.status !== "recovering")
|
|
309
|
+
return true;
|
|
310
|
+
// Recovering -> recovering is a new, separately identified recovery after
|
|
311
|
+
// the prior actor stopped. Reusing either identity would make a stale actor
|
|
312
|
+
// indistinguishable from the new operation.
|
|
313
|
+
return next.record.recoveryId !== previousRecord.recoveryId &&
|
|
314
|
+
next.record.recoveryActor !== undefined &&
|
|
315
|
+
previousRecord.recoveryActor !== undefined &&
|
|
316
|
+
!sameRecoveryActor(next.record.recoveryActor, previousRecord.recoveryActor);
|
|
317
|
+
}
|
|
318
|
+
function validateGenerationTransitions(key, entries, legacy) {
|
|
319
|
+
let previousGeneration = 0;
|
|
320
|
+
let previous;
|
|
321
|
+
for (const entry of entries) {
|
|
322
|
+
const stateGeneration = generationNumber(entry.state);
|
|
323
|
+
const parentGeneration = entry.state.kind === "free"
|
|
324
|
+
? entry.state.parentGeneration
|
|
325
|
+
: entry.state.record.parentGeneration;
|
|
326
|
+
if (entry.generation !== previousGeneration + 1 || stateGeneration !== entry.generation || parentGeneration !== previousGeneration) {
|
|
327
|
+
return invalidLeaseError(key, "generations are not a contiguous exact-parent chain");
|
|
328
|
+
}
|
|
329
|
+
if (previous === undefined) {
|
|
330
|
+
if (!legalFirstGeneration(entry.state, legacy)) {
|
|
331
|
+
return invalidLeaseError(key, "generation 1 must be an active fresh lease or a recovery of the exact v2 legacy owner");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else if (!legalGenerationTransition(previous, entry.state)) {
|
|
335
|
+
return invalidLeaseError(key, `generation ${entry.generation} is not a legal transition from generation ${previousGeneration}`);
|
|
336
|
+
}
|
|
337
|
+
previous = entry.state;
|
|
338
|
+
previousGeneration = entry.generation;
|
|
339
|
+
}
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
function invalidLeaseError(key, message) {
|
|
343
|
+
return new SharedStateLeaseError("shared-state-lease-invalid", `sharedState generation chain for ${JSON.stringify(key)} is invalid: ${message}. Recovery fails closed.`);
|
|
344
|
+
}
|
|
345
|
+
function readLegacySharedStateLeaseEffect(niceevalRoot, key) {
|
|
346
|
+
return nodeIo(() => (0, promises_1.readFile)(legacySharedStateLeasePath(niceevalRoot, key), "utf8")).pipe(effect_1.Effect.flatMap((raw) => nodeSync(() => JSON.parse(raw))), effect_1.Effect.flatMap((value) => {
|
|
347
|
+
const record = decodeSharedStateLeaseRecord(key, value, { legacy: true });
|
|
348
|
+
return record === undefined
|
|
349
|
+
? effect_1.Effect.fail(invalidLeaseError(key, "the legacy mutable record cannot be decoded"))
|
|
350
|
+
: effect_1.Effect.succeed(record);
|
|
351
|
+
}), effect_1.Effect.catchAll((cause) => errnoCode(cause) === "ENOENT" ? effect_1.Effect.succeed(undefined) : effect_1.Effect.fail(cause)));
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Read a contiguous immutable generation chain. Proposal files are ignored:
|
|
355
|
+
* they are not authoritative until the hard-link has occupied a generation
|
|
356
|
+
* filename. A malformed committed filename or record is an explicit fail
|
|
357
|
+
* closed condition, never an opportunity to silently start a new holder.
|
|
358
|
+
*/
|
|
359
|
+
function readSharedStateLedgerEffect(niceevalRoot, key) {
|
|
360
|
+
const dir = sharedStateLeaseGenerationDirOf(niceevalRoot, key);
|
|
361
|
+
return nodeIo(() => (0, promises_1.readdir)(dir)).pipe(effect_1.Effect.catchAll((cause) => errnoCode(cause) === "ENOENT" ? effect_1.Effect.succeed([]) : effect_1.Effect.fail(cause)), effect_1.Effect.flatMap((names) => {
|
|
362
|
+
const generationNames = names.filter((name) => name.startsWith(GENERATION_FILE_PREFIX));
|
|
363
|
+
const ordered = [];
|
|
364
|
+
for (const name of generationNames) {
|
|
365
|
+
const generation = generationFromFileName(name);
|
|
366
|
+
if (generation === undefined) {
|
|
367
|
+
return effect_1.Effect.fail(invalidLeaseError(key, `committed filename ${JSON.stringify(name)} is not canonical`));
|
|
368
|
+
}
|
|
369
|
+
ordered.push({ name, generation });
|
|
370
|
+
}
|
|
371
|
+
ordered.sort((left, right) => left.generation - right.generation);
|
|
372
|
+
if (ordered.length === 0) {
|
|
373
|
+
return readLegacySharedStateLeaseEffect(niceevalRoot, key).pipe(effect_1.Effect.map((legacy) => Object.freeze({
|
|
374
|
+
dir,
|
|
375
|
+
latest: legacy === undefined ? undefined : Object.freeze({ kind: "legacy", record: legacy }),
|
|
376
|
+
})));
|
|
377
|
+
}
|
|
378
|
+
return effect_1.Effect.forEach(ordered, ({ name, generation }) => nodeIo(() => (0, promises_1.readFile)((0, node_path_1.join)(dir, name), "utf8")).pipe(effect_1.Effect.flatMap((raw) => nodeSync(() => JSON.parse(raw))), effect_1.Effect.flatMap((value) => {
|
|
379
|
+
const decoded = decodeGeneration(key, value);
|
|
380
|
+
return decoded === undefined
|
|
381
|
+
? effect_1.Effect.fail(invalidLeaseError(key, `generation ${generation} cannot be decoded`))
|
|
382
|
+
: effect_1.Effect.succeed({ generation, state: decoded });
|
|
383
|
+
}))).pipe(effect_1.Effect.flatMap((entries) => readLegacySharedStateLeaseEffect(niceevalRoot, key).pipe(effect_1.Effect.flatMap((legacy) => {
|
|
384
|
+
const invalid = validateGenerationTransitions(key, entries, legacy);
|
|
385
|
+
return invalid === undefined
|
|
386
|
+
? effect_1.Effect.succeed(Object.freeze({ dir, latest: entries.at(-1).state }))
|
|
387
|
+
: effect_1.Effect.fail(invalid);
|
|
388
|
+
}))));
|
|
389
|
+
}));
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Publish one immutable generation. `link(proposal, generation-N)` is the
|
|
393
|
+
* linearization point: it succeeds only if no other transition has occupied
|
|
394
|
+
* the same next-generation slot. The proposal is unique and non-authoritative
|
|
395
|
+
* before the link, so a crash cannot leave a reusable fence or an ABA target.
|
|
396
|
+
*/
|
|
397
|
+
function publishGenerationExclusiveEffect(dir, generation, state) {
|
|
398
|
+
const name = generationFileName(generation);
|
|
399
|
+
const proposalPath = (0, node_path_1.join)(dir, `.${name}.${process.pid}.${(0, node_crypto_1.randomUUID)()}.proposal`);
|
|
400
|
+
const targetPath = (0, node_path_1.join)(dir, name);
|
|
401
|
+
const data = state.kind === "lease"
|
|
402
|
+
? durableLeaseGeneration(state.record)
|
|
403
|
+
: state.kind === "free"
|
|
404
|
+
? durableFreeGeneration(state)
|
|
405
|
+
: undefined;
|
|
406
|
+
if (data === undefined) {
|
|
407
|
+
return effect_1.Effect.fail(new SharedStateLeaseError("shared-state-lease-invalid", "A legacy sharedState record cannot be republished without explicit recovery."));
|
|
408
|
+
}
|
|
409
|
+
const writeProposal = withOpenFile(proposalPath, "wx", (handle) => nodeIo(() => handle.writeFile(JSON.stringify(data, null, 2), "utf8")).pipe(effect_1.Effect.zipRight(nodeIo(() => handle.sync()))));
|
|
410
|
+
return nodeIo(() => (0, promises_1.mkdir)(dir, { recursive: true })).pipe(effect_1.Effect.zipRight(writeProposal), effect_1.Effect.zipRight(nodeIo(() => (0, promises_1.link)(proposalPath, targetPath)).pipe(effect_1.Effect.as("published"), effect_1.Effect.catchAll((cause) => errnoCode(cause) === "EEXIST" ? effect_1.Effect.succeed("occupied") : effect_1.Effect.fail(cause)))), effect_1.Effect.flatMap((result) => result === "published"
|
|
411
|
+
? (0, entry_file_store_ts_1.fsyncDirEffect)(dir).pipe(effect_1.Effect.as(result))
|
|
412
|
+
: effect_1.Effect.succeed(result)),
|
|
413
|
+
// Only this caller's random proposal is removed. Published generation
|
|
414
|
+
// files are intentionally never deleted, renamed, or reused.
|
|
415
|
+
effect_1.Effect.ensuring(nodeIo(() => (0, promises_1.rm)(proposalPath, { force: true })).pipe(effect_1.Effect.ignore)));
|
|
416
|
+
}
|
|
417
|
+
function readSharedStateLeaseEffect(niceevalRoot, key) {
|
|
418
|
+
return readSharedStateLedgerEffect(niceevalRoot, key).pipe(effect_1.Effect.flatMap((ledger) => {
|
|
419
|
+
const record = leaseRecordOf(ledger.latest);
|
|
420
|
+
return record === undefined
|
|
421
|
+
? effect_1.Effect.succeed(undefined)
|
|
422
|
+
// Sidecar I/O is deliberately non-authoritative. A bad/missing read
|
|
423
|
+
// cannot hide or invalidate the immutable lease generation.
|
|
424
|
+
: readDiagnosticHeartbeatEffect(ledger.dir, record).pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed(record)));
|
|
425
|
+
}));
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* The public recovery command may show the prior owner after a completed
|
|
429
|
+
* release. This lets it report `AlreadyReleased` without asking users to look
|
|
430
|
+
* at private files; a new active generation still takes precedence and makes
|
|
431
|
+
* an old token fail exact-owner validation.
|
|
432
|
+
*/
|
|
433
|
+
function readSharedStateLeaseRecoveryTargetEffect(niceevalRoot, key) {
|
|
434
|
+
return readSharedStateLedgerEffect(niceevalRoot, key).pipe(effect_1.Effect.flatMap((ledger) => {
|
|
435
|
+
const record = ledger.latest?.kind === "free" ? ledger.latest.previous : leaseRecordOf(ledger.latest);
|
|
436
|
+
return record === undefined
|
|
437
|
+
? effect_1.Effect.succeed(undefined)
|
|
438
|
+
: readDiagnosticHeartbeatEffect(ledger.dir, record).pipe(effect_1.Effect.catchAll(() => effect_1.Effect.succeed(record)));
|
|
439
|
+
}));
|
|
440
|
+
}
|
|
441
|
+
function makeAbortError(signal) {
|
|
442
|
+
const reason = signal?.reason;
|
|
443
|
+
if (reason instanceof Error)
|
|
444
|
+
return reason;
|
|
445
|
+
const error = new Error("aborted while waiting for shared-state recovery");
|
|
446
|
+
error.name = "AbortError";
|
|
447
|
+
return error;
|
|
448
|
+
}
|
|
449
|
+
function awaitAbort(signal) {
|
|
450
|
+
if (signal === undefined)
|
|
451
|
+
return effect_1.Effect.never;
|
|
452
|
+
return effect_1.Effect.async((resume, effectSignal) => {
|
|
453
|
+
let completed = false;
|
|
454
|
+
const cleanup = () => {
|
|
455
|
+
signal.removeEventListener("abort", onAbort);
|
|
456
|
+
effectSignal.removeEventListener("abort", onEffectAbort);
|
|
457
|
+
};
|
|
458
|
+
const complete = () => {
|
|
459
|
+
if (completed)
|
|
460
|
+
return;
|
|
461
|
+
completed = true;
|
|
462
|
+
cleanup();
|
|
463
|
+
};
|
|
464
|
+
const onAbort = () => {
|
|
465
|
+
if (completed)
|
|
466
|
+
return;
|
|
467
|
+
complete();
|
|
468
|
+
resume(effect_1.Effect.fail(makeAbortError(signal)));
|
|
469
|
+
};
|
|
470
|
+
const onEffectAbort = () => complete();
|
|
471
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
472
|
+
effectSignal.addEventListener("abort", onEffectAbort, { once: true });
|
|
473
|
+
// Both listeners must be live before inspecting either signal: otherwise
|
|
474
|
+
// an abort between inspection and registration could strand the waiter.
|
|
475
|
+
if (effectSignal.aborted)
|
|
476
|
+
onEffectAbort();
|
|
477
|
+
else if (signal.aborted)
|
|
478
|
+
onAbort();
|
|
479
|
+
return effect_1.Effect.sync(complete);
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
function delayOrAbortEffect(ms, signal) {
|
|
483
|
+
if (signal?.aborted)
|
|
484
|
+
return effect_1.Effect.fail(makeAbortError(signal));
|
|
485
|
+
return effect_1.Effect.raceFirst(effect_1.Effect.sleep(ms), awaitAbort(signal)).pipe(effect_1.Effect.asVoid);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Returns a host-local process identity that distinguishes a PID reuse. Linux
|
|
489
|
+
* uses the kernel start tick plus `/proc`'s terminal-state boundary; other
|
|
490
|
+
* POSIX hosts use `ps`'s process start time. An absent or terminal process
|
|
491
|
+
* returns undefined; unreadable or malformed identity evidence fails closed.
|
|
492
|
+
*/
|
|
493
|
+
function processIdentityForPidEffect(pid) {
|
|
494
|
+
return effect_1.Effect.tryPromise({
|
|
495
|
+
try: async () => {
|
|
496
|
+
if (process.platform === "linux") {
|
|
497
|
+
try {
|
|
498
|
+
const raw = await (0, promises_1.readFile)(`/proc/${pid}/stat`, "utf8");
|
|
499
|
+
const closing = raw.lastIndexOf(")");
|
|
500
|
+
if (closing < 0 || raw[closing + 1] !== " ") {
|
|
501
|
+
throw new Error("Could not parse /proc/<pid>/stat process identity.");
|
|
502
|
+
}
|
|
503
|
+
const fields = raw.slice(closing + 2).trim().split(/\s+/u);
|
|
504
|
+
const state = fields[0];
|
|
505
|
+
// field 3 is the task state. A zombie or either Linux dead state
|
|
506
|
+
// cannot execute the owner cleanup, even though its start tick still
|
|
507
|
+
// matches the durable lease. Treat only these exact terminal states
|
|
508
|
+
// as stopped; a missing or malformed state remains unavailable.
|
|
509
|
+
if (state === "Z" || state === "X" || state === "x")
|
|
510
|
+
return undefined;
|
|
511
|
+
if (state === undefined || !/^[A-Za-z]$/u.test(state)) {
|
|
512
|
+
throw new Error("Could not parse /proc/<pid>/stat process state.");
|
|
513
|
+
}
|
|
514
|
+
// /proc/<pid>/stat field 22 is starttime; `fields[0]` is field 3.
|
|
515
|
+
const started = fields[19];
|
|
516
|
+
if (started !== undefined && /^\d+$/u.test(started))
|
|
517
|
+
return `linux-starttime:${started}`;
|
|
518
|
+
throw new Error("Could not parse /proc/<pid>/stat process identity.");
|
|
519
|
+
}
|
|
520
|
+
catch (cause) {
|
|
521
|
+
if (errnoCode(cause) === "ENOENT")
|
|
522
|
+
return undefined;
|
|
523
|
+
throw cause;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return await new Promise((resolve, reject) => {
|
|
527
|
+
(0, node_child_process_1.execFile)("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8" }, (error, stdout) => {
|
|
528
|
+
if (error !== null) {
|
|
529
|
+
if (stdout.trim().length === 0)
|
|
530
|
+
resolve(undefined);
|
|
531
|
+
else
|
|
532
|
+
reject(error);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const started = stdout.trim();
|
|
536
|
+
resolve(started.length === 0 ? undefined : `ps-lstart:${started}`);
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
},
|
|
540
|
+
catch: (cause) => cause,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
function currentProcessIdentityEffect() {
|
|
544
|
+
return processIdentityForPidEffect(process.pid).pipe(effect_1.Effect.flatMap((identity) => identity === undefined
|
|
545
|
+
? effect_1.Effect.fail(new SharedStateLeaseError("shared-state-process-identity-unavailable", "NiceEval could not establish this process identity; sharedState fails closed rather than coordinating from a PID alone."))
|
|
546
|
+
: effect_1.Effect.succeed(identity)));
|
|
547
|
+
}
|
|
548
|
+
function recoveryActor(host, processIdentity) {
|
|
549
|
+
return Object.freeze({
|
|
550
|
+
pid: process.pid,
|
|
551
|
+
host,
|
|
552
|
+
processIdentity,
|
|
553
|
+
startedAt: new Date().toISOString(),
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Heartbeats intentionally make no authority transition. They verify that the
|
|
558
|
+
* exact immutable generation/token is still current, then atomically replace
|
|
559
|
+
* only that generation's diagnostic sidecar. A sidecar failure is ignored and
|
|
560
|
+
* cannot expire, take over, or resurrect any owner.
|
|
561
|
+
*/
|
|
562
|
+
function heartbeatEffect(niceevalRoot, key, expected) {
|
|
563
|
+
return readSharedStateLedgerEffect(niceevalRoot, key).pipe(effect_1.Effect.flatMap((ledger) => {
|
|
564
|
+
const current = leaseRecordOf(ledger.latest);
|
|
565
|
+
if (current === undefined || !sameLeaseRecord(current, expected))
|
|
566
|
+
return effect_1.Effect.succeed("lost");
|
|
567
|
+
return effect_1.Clock.currentTimeMillis.pipe(effect_1.Effect.flatMap((nowMs) => writeDiagnosticHeartbeatEffect(ledger.dir, expected, new Date(nowMs).toISOString()).pipe(
|
|
568
|
+
// This write is merely user-facing evidence. The immutable head is
|
|
569
|
+
// still authoritative even if the filesystem rejects the sidecar.
|
|
570
|
+
effect_1.Effect.catchAll(() => effect_1.Effect.void), effect_1.Effect.as("confirmed"))));
|
|
571
|
+
}));
|
|
572
|
+
}
|
|
573
|
+
function makeActiveLeaseRecord(input) {
|
|
574
|
+
return Object.freeze({
|
|
575
|
+
key: input.key,
|
|
576
|
+
experimentId: input.identity.experimentId,
|
|
577
|
+
ownerToken: input.ownerToken,
|
|
578
|
+
pid: input.identity.pid,
|
|
579
|
+
host: input.identity.host,
|
|
580
|
+
processIdentity: input.identity.processIdentity,
|
|
581
|
+
acquiredAt: input.now,
|
|
582
|
+
heartbeatAt: input.now,
|
|
583
|
+
status: "active",
|
|
584
|
+
generation: input.generation,
|
|
585
|
+
parentGeneration: input.parentGeneration,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
function freeGenerationFrom(record, generation, releasedAt) {
|
|
589
|
+
return Object.freeze({
|
|
590
|
+
kind: "free",
|
|
591
|
+
key: record.key,
|
|
592
|
+
generation,
|
|
593
|
+
parentGeneration: record.generation,
|
|
594
|
+
releasedAt,
|
|
595
|
+
previous: record,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
function releaseError(key, latest) {
|
|
599
|
+
return new SharedStateLeaseError(latest === undefined ? "shared-state-lease-missing" : "shared-state-lease-owner-mismatch", `sharedState lease ${JSON.stringify(key)} was not released because its exact owner generation no longer matches; explicit recovery is required.`);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Advance exactly this active generation to a free generation. A competing
|
|
603
|
+
* transition can only occupy the same next slot; after that happens we inspect
|
|
604
|
+
* the new immutable head and accept it only if it is this exact release.
|
|
605
|
+
*/
|
|
606
|
+
function releaseExactLeaseEffect(niceevalRoot, key, expected) {
|
|
607
|
+
return effect_1.Effect.suspend(() => readSharedStateLedgerEffect(niceevalRoot, key).pipe(effect_1.Effect.flatMap((ledger) => {
|
|
608
|
+
if (ledger.latest?.kind === "free" && sameLeaseRecord(ledger.latest.previous, expected))
|
|
609
|
+
return effect_1.Effect.void;
|
|
610
|
+
const current = leaseRecordOf(ledger.latest);
|
|
611
|
+
if (current === undefined || !sameLeaseRecord(current, expected))
|
|
612
|
+
return effect_1.Effect.fail(releaseError(key, ledger.latest));
|
|
613
|
+
const next = freeGenerationFrom(expected, expected.generation + 1, new Date().toISOString());
|
|
614
|
+
return publishGenerationExclusiveEffect(ledger.dir, next.generation, next).pipe(effect_1.Effect.flatMap((published) => published === "published" ? effect_1.Effect.void : releaseExactLeaseEffect(niceevalRoot, key, expected)));
|
|
615
|
+
})));
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Acquires only a free/absent generation head. Existing active/recovering
|
|
619
|
+
* leases are waited on indefinitely; heartbeat age and PID liveness are
|
|
620
|
+
* diagnostic facts, never authority to take ownership.
|
|
621
|
+
*/
|
|
622
|
+
function acquireSharedStateLeaseEffect(niceevalRoot, key, identity, opts = {}) {
|
|
623
|
+
const ownerToken = (0, node_crypto_1.randomUUID)();
|
|
624
|
+
const pollIntervalMs = opts.pollIntervalMs ?? exports.SHARED_STATE_LEASE_HEARTBEAT_INTERVAL_MS;
|
|
625
|
+
const heartbeatIntervalMs = opts.heartbeatIntervalMs ?? exports.SHARED_STATE_LEASE_HEARTBEAT_INTERVAL_MS;
|
|
626
|
+
let waitReported = false;
|
|
627
|
+
const acquire = () => effect_1.Effect.suspend(() => {
|
|
628
|
+
if (opts.signal?.aborted)
|
|
629
|
+
return effect_1.Effect.fail(makeAbortError(opts.signal));
|
|
630
|
+
return readSharedStateLedgerEffect(niceevalRoot, key).pipe(effect_1.Effect.flatMap((ledger) => {
|
|
631
|
+
const holder = leaseRecordOf(ledger.latest);
|
|
632
|
+
if (holder !== undefined) {
|
|
633
|
+
const reportWait = waitReported
|
|
634
|
+
? effect_1.Effect.void
|
|
635
|
+
: readDiagnosticHeartbeatEffect(ledger.dir, holder).pipe(effect_1.Effect.flatMap((displayHolder) => effect_1.Effect.sync(() => {
|
|
636
|
+
waitReported = true;
|
|
637
|
+
opts.onWaitStart?.(displayHolder);
|
|
638
|
+
})));
|
|
639
|
+
return reportWait.pipe(effect_1.Effect.zipRight(delayOrAbortEffect(pollIntervalMs, opts.signal)), effect_1.Effect.zipRight(acquire()));
|
|
640
|
+
}
|
|
641
|
+
return effect_1.Clock.currentTimeMillis.pipe(effect_1.Effect.flatMap((nowMs) => {
|
|
642
|
+
const parentGeneration = generationNumber(ledger.latest);
|
|
643
|
+
const record = makeActiveLeaseRecord({
|
|
644
|
+
key,
|
|
645
|
+
identity,
|
|
646
|
+
ownerToken,
|
|
647
|
+
generation: parentGeneration + 1,
|
|
648
|
+
parentGeneration,
|
|
649
|
+
now: new Date(nowMs).toISOString(),
|
|
650
|
+
});
|
|
651
|
+
const next = Object.freeze({ kind: "lease", record });
|
|
652
|
+
return publishGenerationExclusiveEffect(ledger.dir, record.generation, next).pipe(effect_1.Effect.flatMap((published) => published === "published" ? effect_1.Effect.succeed(record) : acquire()));
|
|
653
|
+
}));
|
|
654
|
+
}));
|
|
655
|
+
});
|
|
656
|
+
return effect_1.Effect.uninterruptibleMask((restore) => restore(acquire()).pipe(effect_1.Effect.flatMap((record) => {
|
|
657
|
+
let heartbeatStopped = false;
|
|
658
|
+
let lostOwnership = false;
|
|
659
|
+
const heartbeat = effect_1.Effect.forever(effect_1.Effect.sleep(heartbeatIntervalMs).pipe(effect_1.Effect.zipRight(heartbeatEffect(niceevalRoot, key, record).pipe(effect_1.Effect.tap((outcome) => effect_1.Effect.sync(() => {
|
|
660
|
+
if (outcome === "lost")
|
|
661
|
+
lostOwnership = true;
|
|
662
|
+
})),
|
|
663
|
+
// A diagnostic probe failure does not silently abandon an
|
|
664
|
+
// otherwise-owned lease; release still performs the exact CAS.
|
|
665
|
+
effect_1.Effect.ignore))));
|
|
666
|
+
return effect_1.Effect.forkDaemon(restore(heartbeat)).pipe(effect_1.Effect.flatMap((fiber) => effect_1.Deferred.make().pipe(effect_1.Effect.map((releaseCompletion) => {
|
|
667
|
+
const stopHeartbeat = effect_1.Effect.uninterruptible(effect_1.Effect.suspend(() => {
|
|
668
|
+
if (heartbeatStopped)
|
|
669
|
+
return effect_1.Effect.void;
|
|
670
|
+
heartbeatStopped = true;
|
|
671
|
+
return effect_1.Fiber.interrupt(fiber).pipe(effect_1.Effect.asVoid);
|
|
672
|
+
}));
|
|
673
|
+
const releaseOperation = stopHeartbeat.pipe(effect_1.Effect.zipRight(releaseExactLeaseEffect(niceevalRoot, key, record)), effect_1.Effect.catchAll((cause) => {
|
|
674
|
+
// Keep the exact failure visible to the caller. `lostOwnership`
|
|
675
|
+
// only improves the diagnosis; it never changes authority.
|
|
676
|
+
if (cause instanceof SharedStateLeaseError)
|
|
677
|
+
return effect_1.Effect.fail(cause);
|
|
678
|
+
return effect_1.Effect.fail(lostOwnership
|
|
679
|
+
? new SharedStateLeaseError("shared-state-lease-owner-mismatch", `sharedState lease ${JSON.stringify(key)} changed while this owner was active; explicit recovery is required.`)
|
|
680
|
+
: cause);
|
|
681
|
+
}));
|
|
682
|
+
const release = effect_1.Effect.uninterruptible(effect_1.Deferred.complete(releaseCompletion, releaseOperation).pipe(effect_1.Effect.zipRight(effect_1.Deferred.await(releaseCompletion))));
|
|
683
|
+
return Object.freeze({ claim: Object.freeze({ ownerToken, release, abandon: stopHeartbeat }) });
|
|
684
|
+
}))));
|
|
685
|
+
})));
|
|
686
|
+
}
|
|
687
|
+
function assertRecoveryConfirmed(input) {
|
|
688
|
+
return input.confirmOwnerTerminated && input.confirmRemoteQuiesced
|
|
689
|
+
? effect_1.Effect.void
|
|
690
|
+
: effect_1.Effect.fail(new SharedStateLeaseError("shared-state-recovery-confirmation-required", "Shared-state recovery requires explicit confirmation that the owner terminated and remote state is quiesced."));
|
|
691
|
+
}
|
|
692
|
+
/** A local identity mismatch proves PID reuse; an unreadable identity fails closed. */
|
|
693
|
+
function assertActorStoppedEffect(actor, localHost, code, remoteQuiesced) {
|
|
694
|
+
if (actor.host !== localHost) {
|
|
695
|
+
return remoteQuiesced
|
|
696
|
+
? effect_1.Effect.void
|
|
697
|
+
: effect_1.Effect.fail(new SharedStateLeaseError("shared-state-recovery-identity-unavailable", `NiceEval cannot verify remote owner ${actor.host} without an explicit remote-quiesced acknowledgement.`));
|
|
698
|
+
}
|
|
699
|
+
return processIdentityForPidEffect(actor.pid).pipe(effect_1.Effect.mapError(() => new SharedStateLeaseError("shared-state-recovery-identity-unavailable", `NiceEval could not verify local process identity for PID ${actor.pid}; recovery fails closed.`)), effect_1.Effect.flatMap((current) => {
|
|
700
|
+
if (current === undefined || current !== actor.processIdentity)
|
|
701
|
+
return effect_1.Effect.void;
|
|
702
|
+
return effect_1.Effect.fail(new SharedStateLeaseError(code, code === "shared-state-owner-still-live"
|
|
703
|
+
? `sharedState owner is still live on ${actor.host} as PID ${actor.pid}; recovery is refused.`
|
|
704
|
+
: `A prior sharedState recovery is still live on ${actor.host} as PID ${actor.pid}; recovery is refused.`));
|
|
705
|
+
}));
|
|
706
|
+
}
|
|
707
|
+
function recoveryRecordFrom(source, recoveryId, actor) {
|
|
708
|
+
return Object.freeze({
|
|
709
|
+
...source,
|
|
710
|
+
status: "recovering",
|
|
711
|
+
generation: source.generation + 1,
|
|
712
|
+
parentGeneration: source.generation,
|
|
713
|
+
recoveryId,
|
|
714
|
+
recoveryActor: actor,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
function isExactRecoveringSource(record, recoveryId, actor) {
|
|
718
|
+
return record.status === "recovering" &&
|
|
719
|
+
record.recoveryId === recoveryId &&
|
|
720
|
+
record.recoveryActor !== undefined &&
|
|
721
|
+
// Do not reduce this to processIdentity: PID, host, and the immutable
|
|
722
|
+
// recovery id are all part of the replacement compare-owner predicate.
|
|
723
|
+
sameRecoveryActor(record.recoveryActor, actor);
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Claims a failed holder for one recovery command without releasing it. The
|
|
727
|
+
* recovery record is an immutable next generation. If two operators recover
|
|
728
|
+
* concurrently, only one can publish that exact next slot; the loser never
|
|
729
|
+
* clears or reuses a coordination path and must re-inspect/retry explicitly.
|
|
730
|
+
*/
|
|
731
|
+
function beginExplicitSharedStateRecoveryEffect(input) {
|
|
732
|
+
return effect_1.Effect.gen(function* () {
|
|
733
|
+
yield* assertRecoveryConfirmed(input);
|
|
734
|
+
const processIdentity = yield* currentProcessIdentityEffect();
|
|
735
|
+
const actor = recoveryActor(input.localHost, processIdentity);
|
|
736
|
+
const ledger = yield* readSharedStateLedgerEffect(input.niceevalRoot, input.key);
|
|
737
|
+
if (ledger.latest === undefined) {
|
|
738
|
+
return yield* effect_1.Effect.fail(new SharedStateLeaseError("shared-state-lease-missing", `No decodable sharedState lease exists for ${JSON.stringify(input.key)}.`));
|
|
739
|
+
}
|
|
740
|
+
if (ledger.latest.kind === "free") {
|
|
741
|
+
if (ledger.latest.previous.ownerToken === input.ownerToken) {
|
|
742
|
+
return Object.freeze({ _tag: "AlreadyReleased", record: ledger.latest.previous });
|
|
743
|
+
}
|
|
744
|
+
return yield* effect_1.Effect.fail(new SharedStateLeaseError("shared-state-lease-owner-mismatch", `The supplied owner token does not own sharedState key ${JSON.stringify(input.key)}.`));
|
|
745
|
+
}
|
|
746
|
+
const observed = ledger.latest.record;
|
|
747
|
+
if (observed.ownerToken !== input.ownerToken) {
|
|
748
|
+
return yield* effect_1.Effect.fail(new SharedStateLeaseError("shared-state-lease-owner-mismatch", `The supplied owner token does not own sharedState key ${JSON.stringify(input.key)}.`));
|
|
749
|
+
}
|
|
750
|
+
yield* assertActorStoppedEffect(observed, input.localHost, "shared-state-owner-still-live", input.confirmRemoteQuiesced);
|
|
751
|
+
if (observed.status === "recovering") {
|
|
752
|
+
const observedRecoveryId = observed.recoveryId;
|
|
753
|
+
const observedRecoveryActor = observed.recoveryActor;
|
|
754
|
+
// The observed immutable parent itself is the exact compare-owner input.
|
|
755
|
+
// Keep the full predicate explicit so a PID/processIdentity coincidence
|
|
756
|
+
// cannot replace a different recovery operation.
|
|
757
|
+
if (!isExactRecoveringSource(observed, observedRecoveryId, observedRecoveryActor)) {
|
|
758
|
+
return yield* effect_1.Effect.fail(invalidLeaseError(input.key, "recovering generation lacks an exact recovery actor"));
|
|
759
|
+
}
|
|
760
|
+
yield* assertActorStoppedEffect(observedRecoveryActor, input.localHost, "shared-state-recovery-in-progress", input.confirmRemoteQuiesced);
|
|
761
|
+
}
|
|
762
|
+
const recoveryId = (0, node_crypto_1.randomUUID)();
|
|
763
|
+
const recovered = recoveryRecordFrom(observed, recoveryId, actor);
|
|
764
|
+
const next = Object.freeze({ kind: "lease", record: recovered });
|
|
765
|
+
const published = yield* publishGenerationExclusiveEffect(ledger.dir, recovered.generation, next);
|
|
766
|
+
if (published === "published")
|
|
767
|
+
return Object.freeze({ _tag: "Claimed", record: recovered, recoveryId });
|
|
768
|
+
return yield* effect_1.Effect.fail(new SharedStateLeaseError("shared-state-recovery-state-mismatch", `sharedState lease ${JSON.stringify(input.key)} changed before explicit recovery could claim its exact generation.`));
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
function exactRecoveryActorMatchesCurrentProcess(actor, localHost, processIdentity) {
|
|
772
|
+
return actor.pid === process.pid && actor.host === localHost && actor.processIdentity === processIdentity;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Final recovery transition: exactly owner token + recovery id + recovery
|
|
776
|
+
* actor host/PID/process identity advances its observed generation to free.
|
|
777
|
+
* It does not unlink anything and therefore cannot erase a later holder.
|
|
778
|
+
*/
|
|
779
|
+
function completeExplicitSharedStateRecoveryEffect(input) {
|
|
780
|
+
const complete = (expected) => effect_1.Effect.suspend(() => readSharedStateLedgerEffect(input.niceevalRoot, input.key).pipe(effect_1.Effect.flatMap((ledger) => {
|
|
781
|
+
if (ledger.latest?.kind === "free" && expected !== undefined && sameLeaseRecord(ledger.latest.previous, expected)) {
|
|
782
|
+
return effect_1.Effect.void;
|
|
783
|
+
}
|
|
784
|
+
const current = leaseRecordOf(ledger.latest);
|
|
785
|
+
if (current === undefined ||
|
|
786
|
+
(expected !== undefined && !sameLeaseRecord(current, expected)) ||
|
|
787
|
+
current.ownerToken !== input.ownerToken ||
|
|
788
|
+
current.status !== "recovering" ||
|
|
789
|
+
current.recoveryId !== input.recoveryId ||
|
|
790
|
+
current.recoveryActor === undefined) {
|
|
791
|
+
return effect_1.Effect.fail(new SharedStateLeaseError(ledger.latest === undefined ? "shared-state-lease-missing" : "shared-state-recovery-state-mismatch", `sharedState lease ${JSON.stringify(input.key)} no longer matches this explicit recovery operation.`));
|
|
792
|
+
}
|
|
793
|
+
return currentProcessIdentityEffect().pipe(effect_1.Effect.flatMap((processIdentity) => {
|
|
794
|
+
if (!exactRecoveryActorMatchesCurrentProcess(current.recoveryActor, input.localHost, processIdentity)) {
|
|
795
|
+
return effect_1.Effect.fail(new SharedStateLeaseError("shared-state-recovery-state-mismatch", `sharedState lease ${JSON.stringify(input.key)} is not owned by this exact recovery actor.`));
|
|
796
|
+
}
|
|
797
|
+
const next = freeGenerationFrom(current, current.generation + 1, new Date().toISOString());
|
|
798
|
+
return publishGenerationExclusiveEffect(ledger.dir, next.generation, next).pipe(effect_1.Effect.flatMap((published) => published === "published" ? effect_1.Effect.void : complete(current)));
|
|
799
|
+
}));
|
|
800
|
+
})));
|
|
801
|
+
return complete();
|
|
802
|
+
}
|
|
803
|
+
//# sourceMappingURL=shared-state-lease.cjs.map
|