@planu/cli 4.14.1 → 5.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/CHANGELOG.md +82 -0
- package/LICENSE +55 -12
- package/README.md +10 -2
- package/dist/cli/commands/install.d.ts +1 -12
- package/dist/cli/commands/install.js +9 -65
- package/dist/cli/commands/job.js +4 -3
- package/dist/cli/commands/spec.js +12 -3
- package/dist/cli/commands/validate.js +2 -2
- package/dist/cli/router.js +1 -3
- package/dist/config/environment-schema.json +1 -3
- package/dist/config/official-sdd-tools.d.ts +4 -0
- package/dist/config/official-sdd-tools.js +35 -0
- package/dist/config/product-public.d.ts +139 -3
- package/dist/config/product-public.js +8 -1
- package/dist/config/product-public.json +7 -36
- package/dist/config/registries/hosts/codex.json +20 -363
- package/dist/config/release-policy.json +84 -2
- package/dist/config/runtime-policy.json +12 -1
- package/dist/config/skill-templates/planu-resume-work.md +2 -2
- package/dist/core/index.d.ts +1 -1
- package/dist/core/spec-validator.d.ts +4 -4
- package/dist/core/spec-validator.js +33 -19
- package/dist/engine/abortable-process-runner.d.ts +20 -2
- package/dist/engine/abortable-process-runner.js +243 -74
- package/dist/engine/ambiguity-scorer.js +4 -26
- package/dist/engine/auth/scope-mapper.js +1 -7
- package/dist/engine/autopilot/state-updater.js +4 -4
- package/dist/engine/cascade-hooks/hooks/review-enricher.hook.js +9 -6
- package/dist/engine/cascade-hooks/state-drift-detector.d.ts +3 -3
- package/dist/engine/cascade-hooks/state-drift-detector.js +1 -1
- package/dist/engine/ci-generator/planu-config.js +1 -7
- package/dist/engine/clarification-gate/gate.d.ts +3 -3
- package/dist/engine/code-scanner/layer-scanner.js +6 -22
- package/dist/engine/core-bridge.d.ts +3 -3
- package/dist/engine/cost-guardrails.d.ts +1 -1
- package/dist/engine/cost-guardrails.js +1 -1
- package/dist/engine/criterion-identity.d.ts +1 -0
- package/dist/engine/criterion-identity.js +7 -0
- package/dist/engine/dashboard/data-loader.js +2 -7
- package/dist/engine/dashboard/templates-layout.js +0 -3
- package/dist/engine/dashboard/templates-project.js +1 -13
- package/dist/engine/docs-site-generator/data-collector.d.ts +1 -1
- package/dist/engine/docs-site-generator/data-collector.js +7 -28
- package/dist/engine/docs-site-generator/tools-renderer.js +3 -10
- package/dist/engine/evidence-gates/artifact-reader.d.ts +12 -1
- package/dist/engine/evidence-gates/artifact-reader.js +174 -4
- package/dist/engine/evidence-gates/evidence-autofill.d.ts +10 -0
- package/dist/engine/evidence-gates/evidence-autofill.js +148 -0
- package/dist/engine/evidence-gates/evidence-skeletons.d.ts +19 -0
- package/dist/engine/evidence-gates/evidence-skeletons.js +69 -0
- package/dist/engine/evidence-gates/lifecycle-gate.js +4 -3
- package/dist/engine/evidence-gates/reconciliation-freshness.d.ts +11 -0
- package/dist/engine/evidence-gates/reconciliation-freshness.js +72 -0
- package/dist/engine/evidence-index/done-drift.d.ts +1 -1
- package/dist/engine/evidence-index/done-drift.js +40 -18
- package/dist/engine/execution/context.js +3 -0
- package/dist/engine/execution/durable-job-heartbeat-runtime.d.ts +4 -0
- package/dist/engine/execution/durable-job-heartbeat-runtime.js +400 -0
- package/dist/engine/execution/job-runtime.d.ts +30 -0
- package/dist/engine/execution/job-runtime.js +114 -6
- package/dist/engine/execution/operation-journal.js +10 -4
- package/dist/engine/execution/public-job-sanitizer.d.ts +4 -0
- package/dist/engine/execution/public-job-sanitizer.js +31 -0
- package/dist/engine/execution/validate-job-executor.d.ts +12 -1
- package/dist/engine/execution/validate-job-executor.js +549 -86
- package/dist/engine/execution/validate-job-payload.js +107 -50
- package/dist/engine/execution/validate-job-recovery.js +1 -1
- package/dist/engine/execution/validate-worker-runtime.d.ts +5 -0
- package/dist/engine/execution/validate-worker-runtime.js +90 -2
- package/dist/engine/execution/validate-worker-thread.js +17 -24
- package/dist/engine/handoff-artifacts/schemas.d.ts +2 -1
- package/dist/engine/handoff-artifacts/schemas.js +33 -0
- package/dist/engine/handoff-packager.d.ts +19 -0
- package/dist/engine/handoff-packager.js +415 -13
- package/dist/engine/hooks/effort-configurator.js +1 -1
- package/dist/engine/impact-detector/test-break-predictor.js +3 -3
- package/dist/engine/impact-detector/tool-registration.js +1 -1
- package/dist/engine/implementation-contract/renderer.js +23 -8
- package/dist/engine/lifecycle-reconciliation-io.d.ts +11 -0
- package/dist/engine/lifecycle-reconciliation-io.js +180 -0
- package/dist/engine/lifecycle-reconciliation.d.ts +16 -0
- package/dist/engine/lifecycle-reconciliation.js +406 -0
- package/dist/engine/local-first/tool-classification.js +2 -0
- package/dist/engine/macos-keychain-native-port.d.ts +5 -0
- package/dist/engine/macos-keychain-native-port.js +19 -7
- package/dist/engine/migrations/remove-commercial-state.d.ts +3 -0
- package/dist/engine/migrations/remove-commercial-state.js +343 -0
- package/dist/engine/minimality/policy-loader.js +247 -6
- package/dist/engine/network-policy.js +1 -0
- package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-arm64.node.sbom.json +20 -20
- package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-x64.node.sbom.json +20 -20
- package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +20 -20
- package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +20 -20
- package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +20 -20
- package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +20 -20
- package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +20 -20
- package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +20 -20
- package/dist/engine/readiness-checker.d.ts +1 -16
- package/dist/engine/readiness-checker.js +27 -88
- package/dist/engine/reverse-engineer/api-detector.js +2 -13
- package/dist/engine/reverse-engineer/complexity-analyzer.js +2 -13
- package/dist/engine/reverse-engineer/config-analyzer.js +2 -13
- package/dist/engine/reverse-engineer/dependency-graph.js +2 -13
- package/dist/engine/reverse-engineer/test-analyzer.js +2 -13
- package/dist/engine/reverse-engineer/walk-ignore.d.ts +3 -0
- package/dist/engine/reverse-engineer/walk-ignore.js +26 -0
- package/dist/engine/runtime-policy.d.ts +2 -0
- package/dist/engine/runtime-policy.js +42 -5
- package/dist/engine/runtime-security/rate-limiter.js +0 -1
- package/dist/engine/safety/contained-project-file.d.ts +3 -0
- package/dist/engine/safety/contained-project-file.js +23 -0
- package/dist/engine/scope-boundaries/contradiction-checker.js +63 -1
- package/dist/engine/scope-boundaries/scope-validator.js +45 -7
- package/dist/engine/session/checkpoint-writer.js +0 -9
- package/dist/engine/spec-format/acceptance-criteria.d.ts +6 -2
- package/dist/engine/spec-format/acceptance-criteria.js +82 -22
- package/dist/engine/spec-format/markdown-sections.d.ts +14 -0
- package/dist/engine/spec-format/markdown-sections.js +105 -0
- package/dist/engine/spec-format/read-technical-section.js +33 -5
- package/dist/engine/spec-format/technical-md-populator.js +3 -2
- package/dist/engine/spec-format/text-fences.js +20 -2
- package/dist/engine/spec-format/unified-spec-builder.d.ts +7 -0
- package/dist/engine/spec-format/unified-spec-builder.js +202 -0
- package/dist/engine/spec-migrator/canonical-deduplicator.d.ts +18 -0
- package/dist/engine/spec-migrator/canonical-deduplicator.js +479 -0
- package/dist/engine/spec-migrator/filesystem-import.d.ts +4 -8
- package/dist/engine/spec-migrator/filesystem-import.js +121 -14
- package/dist/engine/spec-migrator/frontmatter-parser.js +32 -15
- package/dist/engine/spec-migrator/index.d.ts +1 -0
- package/dist/engine/spec-migrator/index.js +1 -0
- package/dist/engine/spec-state-machine/transition-spec.d.ts +6 -1
- package/dist/engine/spec-state-machine/transition-spec.js +10 -1
- package/dist/engine/spec-state-syncer.js +1 -1
- package/dist/engine/technical-enricher/index.js +16 -8
- package/dist/engine/technical-enricher/render-enriched.d.ts +1 -0
- package/dist/engine/technical-enricher/render-enriched.js +42 -14
- package/dist/engine/telemetry/health-checker.js +53 -17
- package/dist/engine/telemetry/telemetry-client.js +26 -8
- package/dist/engine/text-signal-boundaries.d.ts +2 -0
- package/dist/engine/text-signal-boundaries.js +25 -2
- package/dist/engine/time/relative-date.d.ts +3 -0
- package/dist/engine/time/relative-date.js +16 -0
- package/dist/engine/timing/budget.d.ts +11 -0
- package/dist/engine/timing/budget.js +31 -9
- package/dist/engine/token-optimizer/analytics.d.ts +1 -1
- package/dist/engine/token-optimizer/analytics.js +5 -6
- package/dist/engine/usage-tracker/core.d.ts +2 -2
- package/dist/engine/usage-tracker/core.js +1 -2
- package/dist/engine/usage-tracker/index.d.ts +0 -3
- package/dist/engine/usage-tracker/index.js +0 -3
- package/dist/engine/usage-tracker/stats.d.ts +2 -2
- package/dist/engine/usage-tracker/stats.js +1 -28
- package/dist/engine/validation/durable-validation.d.ts +1 -1
- package/dist/engine/validation/durable-validation.js +3 -82
- package/dist/engine/validation/validation-freshness.d.ts +11 -1
- package/dist/engine/validation/validation-freshness.js +231 -91
- package/dist/engine/validation/validation-receipt.d.ts +9 -6
- package/dist/engine/validation/validation-receipt.js +84 -24
- package/dist/engine/validation/validation-source-digest.d.ts +2 -0
- package/dist/engine/validation/validation-source-digest.js +86 -0
- package/dist/engine/validation/validation-submission.d.ts +5 -4
- package/dist/engine/validation/validation-submission.js +25 -11
- package/dist/engine/validation/validation-worktree.d.ts +21 -0
- package/dist/engine/validation/validation-worktree.js +260 -0
- package/dist/engine/validation-impact-planner.d.ts +14 -2
- package/dist/engine/validation-impact-planner.js +0 -3
- package/dist/engine/validator/dor-dod.d.ts +1 -0
- package/dist/engine/validator/dor-dod.js +5 -3
- package/dist/engine/validator/extractors.d.ts +1 -5
- package/dist/engine/validator/extractors.js +24 -69
- package/dist/engine/validator/spec-compliance-runner.d.ts +12 -24
- package/dist/engine/validator/spec-compliance-runner.js +429 -211
- package/dist/engine/validator.js +35 -22
- package/dist/index.js +4 -16
- package/dist/resources/usage-stats-resource.js +2 -18
- package/dist/security/adapters/linux-secret-service.js +7 -3
- package/dist/security/adapters/windows-credential-manager.js +6 -4
- package/dist/server/routes/specs.js +13 -1
- package/dist/storage/base-store.d.ts +1 -1
- package/dist/storage/base-store.js +1 -1
- package/dist/storage/feedback-remote.d.ts +2 -3
- package/dist/storage/feedback-remote.js +45 -31
- package/dist/storage/global-store.d.ts +1 -17
- package/dist/storage/global-store.js +1 -42
- package/dist/storage/index.d.ts +0 -2
- package/dist/storage/index.js +0 -2
- package/dist/storage/migrations/canonical-storage.js +124 -11
- package/dist/storage/runtime-db.d.ts +9 -0
- package/dist/storage/runtime-db.js +64 -8
- package/dist/storage/spec-store.d.ts +14 -2
- package/dist/storage/spec-store.js +104 -14
- package/dist/storage/status-store/atomic-status-write.d.ts +5 -0
- package/dist/storage/status-store/atomic-status-write.js +57 -0
- package/dist/storage/status-store/file-lock.js +30 -11
- package/dist/storage/status-store/self-healing.js +3 -3
- package/dist/storage/status-store/version-sync.d.ts +2 -1
- package/dist/storage/status-store/version-sync.js +35 -40
- package/dist/storage/usage-store.d.ts +1 -5
- package/dist/storage/usage-store.js +0 -35
- package/dist/tools/auto-update-handler.d.ts +3 -3
- package/dist/tools/auto-update-handler.js +3 -3
- package/dist/tools/challenge-spec/resilience-challenge-scenarios.d.ts +2 -1
- package/dist/tools/challenge-spec/resilience-challenge-scenarios.js +10 -7
- package/dist/tools/challenge-spec/scenario-collector.js +1 -1
- package/dist/tools/challenge-spec/scenarios-data.js +1 -1
- package/dist/tools/challenge-spec/scenarios-utils.d.ts +1 -1
- package/dist/tools/challenge-spec/scenarios-utils.js +35 -11
- package/dist/tools/challenge-spec-helpers.d.ts +10 -1
- package/dist/tools/challenge-spec-helpers.js +63 -22
- package/dist/tools/challenge-spec.js +23 -8
- package/dist/tools/check-readiness.js +38 -14
- package/dist/tools/configure-telemetry.js +30 -2
- package/dist/tools/create-spec/spec-builder.d.ts +7 -0
- package/dist/tools/create-spec/spec-builder.js +19 -15
- package/dist/tools/create-spec.d.ts +1 -0
- package/dist/tools/create-spec.js +584 -309
- package/dist/tools/git/branch-ops.d.ts +1 -1
- package/dist/tools/git/branch-ops.js +3 -2
- package/dist/tools/init-project/handler.js +10 -12
- package/dist/tools/init-project/host-assets-writer.js +26 -1
- package/dist/tools/init-project/lint-scaffolder.d.ts +3 -3
- package/dist/tools/init-project/migration-runner.d.ts +2 -4
- package/dist/tools/init-project/migration-runner.js +3 -2
- package/dist/tools/init-project/portable-index-reconciler.d.ts +4 -0
- package/dist/tools/init-project/portable-index-reconciler.js +14 -0
- package/dist/tools/init-project/result-builder.js +1 -3
- package/dist/tools/jobs/handlers.js +38 -6
- package/dist/tools/list-specs.js +5 -26
- package/dist/tools/merge-risk-handler.js +2 -2
- package/dist/tools/package-handoff.js +2 -0
- package/dist/tools/reconcile-spec.js +0 -27
- package/dist/tools/register-agent-squad-tools.js +2 -2
- package/dist/tools/register-platform-tools/design-stack-tools.js +11 -11
- package/dist/tools/register-platform-tools/lifecycle-infra-tools.js +7 -7
- package/dist/tools/register-sdd-tools.d.ts +1 -1
- package/dist/tools/register-sdd-tools.js +2 -29
- package/dist/tools/register-spec-tools/analysis-tools.js +8 -8
- package/dist/tools/register-spec-tools/core-spec-tools.js +29 -19
- package/dist/tools/safe-handler.d.ts +5 -13
- package/dist/tools/safe-handler.js +7 -23
- package/dist/tools/schemas/index.d.ts +1 -1
- package/dist/tools/schemas/index.js +1 -1
- package/dist/tools/schemas/output-schemas.d.ts +0 -23
- package/dist/tools/schemas/output-schemas.js +0 -28
- package/dist/tools/schemas/spec.d.ts +4 -0
- package/dist/tools/schemas/spec.js +3 -0
- package/dist/tools/schemas/validate-output-schema.d.ts +11 -0
- package/dist/tools/schemas/validate-output-schema.js +7 -0
- package/dist/tools/spec-prompt-handler.js +3 -3
- package/dist/tools/sync-spec-state-handler.d.ts +1 -1
- package/dist/tools/sync-spec-state-handler.js +50 -2
- package/dist/tools/tool-entry.d.ts +4 -4
- package/dist/tools/tool-entry.js +3 -3
- package/dist/tools/tool-registry/core-tools.js +31 -15
- package/dist/tools/tool-registry/group-infra.js +27 -110
- package/dist/tools/tool-registry/group-quality-compliance.js +26 -26
- package/dist/tools/tool-registry/inventory.d.ts +2 -0
- package/dist/tools/tool-registry/inventory.js +2 -0
- package/dist/tools/tool-registry-helpers.d.ts +3 -3
- package/dist/tools/tool-registry-helpers.js +3 -3
- package/dist/tools/tool-usage-report/registered-tools.d.ts +1 -1
- package/dist/tools/tool-usage-report/registered-tools.js +3 -15
- package/dist/tools/update-status/approval-gates.js +3 -0
- package/dist/tools/update-status/batch.d.ts +6 -2
- package/dist/tools/update-status/batch.js +58 -1
- package/dist/tools/update-status/dod-gates.d.ts +16 -1
- package/dist/tools/update-status/dod-gates.js +191 -1
- package/dist/tools/update-status/done-receipt-verifier.d.ts +8 -0
- package/dist/tools/update-status/done-receipt-verifier.js +75 -27
- package/dist/tools/update-status/evidence-gate.d.ts +4 -0
- package/dist/tools/update-status/evidence-gate.js +103 -3
- package/dist/tools/update-status/file-sync.d.ts +4 -3
- package/dist/tools/update-status/file-sync.js +57 -6
- package/dist/tools/update-status/index.d.ts +25 -1
- package/dist/tools/update-status/index.js +292 -78
- package/dist/tools/update-status/response-builder.js +3 -3
- package/dist/tools/update-status/side-effects.d.ts +2 -0
- package/dist/tools/update-status/side-effects.js +2 -2
- package/dist/tools/update-status/transition-guard.js +45 -16
- package/dist/tools/update-status-actions.d.ts +4 -2
- package/dist/tools/update-status-actions.js +16 -14
- package/dist/tools/usage-report.js +1 -16
- package/dist/tools/usage-stats.js +1 -18
- package/dist/tools/usage-tracking.d.ts +4 -0
- package/dist/tools/usage-tracking.js +20 -0
- package/dist/tools/validate-lint.d.ts +1 -0
- package/dist/tools/validate-lint.js +1 -1
- package/dist/tools/validate.d.ts +2 -1
- package/dist/tools/validate.js +114 -34
- package/dist/tools/workspace-dashboard-handler.js +38 -0
- package/dist/transports/http-transport.js +6 -8
- package/dist/transports/mcp-types.d.ts +0 -2
- package/dist/transports/session-context.js +1 -1
- package/dist/types/cascade-hooks.d.ts +1 -1
- package/dist/types/ci.d.ts +0 -4
- package/dist/types/clarification-token.d.ts +1 -1
- package/dist/types/claude-code-runtime.d.ts +0 -27
- package/dist/types/commercial-migration.d.ts +17 -0
- package/dist/types/commercial-migration.js +2 -0
- package/dist/types/common/primitives.d.ts +0 -2
- package/dist/types/core-bridge.d.ts +4 -10
- package/dist/types/dashboard.d.ts +3 -7
- package/dist/types/docs.d.ts +0 -1
- package/dist/types/durable-job.d.ts +4 -4
- package/dist/types/durable-validation.d.ts +100 -2
- package/dist/types/durable-validation.js +165 -1
- package/dist/types/errors/structured-error.d.ts +3 -3
- package/dist/types/estimation.d.ts +2 -0
- package/dist/types/evidence-autofill.d.ts +34 -0
- package/dist/types/evidence-autofill.js +2 -0
- package/dist/types/evidence-gates.d.ts +9 -1
- package/dist/types/execution.d.ts +2 -0
- package/dist/types/gemini-integration.d.ts +0 -11
- package/dist/types/handoff-artifacts.d.ts +41 -0
- package/dist/types/hooks-advanced.d.ts +0 -13
- package/dist/types/impact-detection.d.ts +2 -2
- package/dist/types/index.d.ts +3 -6
- package/dist/types/index.js +2 -5
- package/dist/types/kiro-integration.d.ts +0 -6
- package/dist/types/network-policy.d.ts +1 -1
- package/dist/types/project/lint-scaffolder.d.ts +1 -1
- package/dist/types/runtime-policy.d.ts +11 -0
- package/dist/types/session-safeguard.d.ts +0 -31
- package/dist/types/spec/canonical-repair.d.ts +81 -0
- package/dist/types/spec/canonical-repair.js +2 -0
- package/dist/types/spec/core.d.ts +20 -0
- package/dist/types/spec/index.d.ts +1 -0
- package/dist/types/spec/index.js +1 -0
- package/dist/types/spec/inputs.d.ts +35 -1
- package/dist/types/spec-format.d.ts +25 -1
- package/dist/types/telemetry.d.ts +4 -1
- package/dist/types/transition-log.d.ts +1 -1
- package/dist/types/transport.d.ts +0 -2
- package/dist/types/usage.d.ts +1 -36
- package/dist/types/usage.js +1 -1
- package/dist/types/validate-worker.d.ts +6 -0
- package/dist/types/validation-receipt.d.ts +1 -1
- package/dist/types/validation.d.ts +10 -4
- package/package.json +17 -12
- package/planu-native.json +1 -1
- package/planu-plugin.json +23 -40
- package/scripts/lib/pending-release-file.mjs +93 -0
- package/src/i18n/messages/en.json +0 -41
- package/src/i18n/messages/es.json +0 -41
- package/src/i18n/messages/pt.json +0 -41
- package/dist/cli/commands/activate.d.ts +0 -14
- package/dist/cli/commands/activate.js +0 -174
- package/dist/config/license-plans.json +0 -251
- package/dist/config/product-public.ts +0 -148
- package/dist/config/server-instructions.ts +0 -121
- package/dist/config/version-defaults.ts +0 -76
- package/dist/config/version.ts +0 -21
- package/dist/engine/ai-integration/gemini/settings-generator.d.ts +0 -4
- package/dist/engine/ai-integration/gemini/settings-generator.js +0 -195
- package/dist/engine/ai-integration/kiro/hooks-generator.d.ts +0 -6
- package/dist/engine/ai-integration/kiro/hooks-generator.js +0 -75
- package/dist/engine/hooks/full-spectrum-generator.d.ts +0 -32
- package/dist/engine/hooks/full-spectrum-generator.js +0 -117
- package/dist/engine/license-validator/config-loader.d.ts +0 -5
- package/dist/engine/license-validator/config-loader.js +0 -16
- package/dist/engine/license-validator/core.d.ts +0 -15
- package/dist/engine/license-validator/core.js +0 -151
- package/dist/engine/license-validator/fingerprint.d.ts +0 -3
- package/dist/engine/license-validator/fingerprint.js +0 -17
- package/dist/engine/license-validator/lemon-squeezy.d.ts +0 -5
- package/dist/engine/license-validator/lemon-squeezy.js +0 -55
- package/dist/engine/license-validator.d.ts +0 -5
- package/dist/engine/license-validator.js +0 -6
- package/dist/engine/product-intelligence/index.d.ts +0 -7
- package/dist/engine/product-intelligence/index.js +0 -211
- package/dist/engine/session/auto-save.d.ts +0 -18
- package/dist/engine/session/auto-save.js +0 -98
- package/dist/engine/session/context-snapshot.d.ts +0 -8
- package/dist/engine/session/context-snapshot.js +0 -49
- package/dist/engine/session/memory-sync.d.ts +0 -12
- package/dist/engine/session/memory-sync.js +0 -34
- package/dist/engine/session/session-diff.d.ts +0 -11
- package/dist/engine/session/session-diff.js +0 -42
- package/dist/engine/session/session-merge.d.ts +0 -12
- package/dist/engine/session/session-merge.js +0 -55
- package/dist/engine/session-safeguard/checkpoint-runner.d.ts +0 -4
- package/dist/engine/session-safeguard/checkpoint-runner.js +0 -70
- package/dist/engine/session-safeguard/learnings-buffer.d.ts +0 -15
- package/dist/engine/session-safeguard/learnings-buffer.js +0 -91
- package/dist/engine/session-safeguard/precompaction-drain.d.ts +0 -10
- package/dist/engine/session-safeguard/precompaction-drain.js +0 -82
- package/dist/engine/session-safeguard/unpushed-detector.d.ts +0 -7
- package/dist/engine/session-safeguard/unpushed-detector.js +0 -59
- package/dist/engine/spec-changelog/core.d.ts +0 -16
- package/dist/engine/spec-changelog/core.js +0 -176
- package/dist/engine/spec-changelog/diff.d.ts +0 -18
- package/dist/engine/spec-changelog/diff.js +0 -121
- package/dist/engine/spec-changelog/index.d.ts +0 -3
- package/dist/engine/spec-changelog/index.js +0 -4
- package/dist/engine/trial-engine.d.ts +0 -5
- package/dist/engine/trial-engine.js +0 -14
- package/dist/engine/usage-tracker/rate-check.d.ts +0 -7
- package/dist/engine/usage-tracker/rate-check.js +0 -18
- package/dist/engine/usage-tracker/rate-limiter.d.ts +0 -14
- package/dist/engine/usage-tracker/rate-limiter.js +0 -73
- package/dist/engine/usage-tracker/trial.d.ts +0 -27
- package/dist/engine/usage-tracker/trial.js +0 -49
- package/dist/engine/webhook/event-handlers.d.ts +0 -25
- package/dist/engine/webhook/event-handlers.js +0 -89
- package/dist/engine/webhook/index.d.ts +0 -4
- package/dist/engine/webhook/index.js +0 -5
- package/dist/engine/webhook/server.d.ts +0 -12
- package/dist/engine/webhook/server.js +0 -249
- package/dist/engine/webhook/signature.d.ts +0 -16
- package/dist/engine/webhook/signature.js +0 -49
- package/dist/storage/license-store.d.ts +0 -6
- package/dist/storage/license-store.js +0 -63
- package/dist/storage/session-store.d.ts +0 -20
- package/dist/storage/session-store.js +0 -136
- package/dist/tools/activate-license.d.ts +0 -6
- package/dist/tools/activate-license.js +0 -132
- package/dist/tools/checkpoint-handler.d.ts +0 -3
- package/dist/tools/checkpoint-handler.js +0 -130
- package/dist/tools/export-session.d.ts +0 -3
- package/dist/tools/export-session.js +0 -90
- package/dist/tools/license-gate.d.ts +0 -18
- package/dist/tools/license-gate.js +0 -222
- package/dist/tools/license-status.d.ts +0 -3
- package/dist/tools/license-status.js +0 -93
- package/dist/tools/list-sessions.d.ts +0 -3
- package/dist/tools/list-sessions.js +0 -46
- package/dist/tools/planu-session-checkpoint.d.ts +0 -6
- package/dist/tools/planu-session-checkpoint.js +0 -42
- package/dist/tools/restore-session.d.ts +0 -3
- package/dist/tools/restore-session.js +0 -71
- package/dist/tools/spec-history.d.ts +0 -3
- package/dist/tools/spec-history.js +0 -59
- package/dist/tools/webhook.d.ts +0 -6
- package/dist/tools/webhook.js +0 -150
- package/dist/types/changelog.d.ts +0 -49
- package/dist/types/changelog.js +0 -3
- package/dist/types/licensing.d.ts +0 -109
- package/dist/types/licensing.js +0 -4
- package/dist/types/product-intelligence.d.ts +0 -60
- package/dist/types/product-intelligence.js +0 -3
- package/dist/types/session.d.ts +0 -84
- package/dist/types/session.js +0 -3
- package/dist/types/webhook.d.ts +0 -62
- package/dist/types/webhook.js +0 -4
|
@@ -1,19 +1,80 @@
|
|
|
1
|
+
/* eslint-disable max-lines -- durable validation, resumable receipt publication, and fencing stay co-located for auditability */
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { join } from 'node:path';
|
|
2
4
|
import { DurableJobRuntime } from './job-runtime.js';
|
|
3
5
|
import { RuntimeDatabase } from '../../storage/runtime-db.js';
|
|
4
6
|
import { createExecutionScope } from './context.js';
|
|
5
7
|
import { runValidateWorker } from './validate-worker-runtime.js';
|
|
6
8
|
import { redactValidationProgress, computeDurableValidationBindings, toValidationReceiptBindings, writeValidationArtifactProvenance, } from '../validation/durable-validation.js';
|
|
9
|
+
import { parseValidationReceiptPendingCheckpoint, isValidationReceiptPendingIntent, } from '../../types/durable-validation.js';
|
|
7
10
|
import { resolveStorageLayout } from '../../storage/storage-layout.js';
|
|
8
11
|
import { reportClassifiedDegradation } from '../../errors/classified-degradation.js';
|
|
9
|
-
import { createValidationReceiptAuthority, ensureValidationIssuerKey, issueValidationReceipt, LOCAL_VALIDATION_RECEIPT_ISSUER, } from '../validation/validation-receipt.js';
|
|
12
|
+
import { createValidationReceiptAuthority, ensureValidationIssuerKey, issueValidationReceipt, LOCAL_VALIDATION_RECEIPT_ISSUER, preflightValidationReceiptSigner, } from '../validation/validation-receipt.js';
|
|
10
13
|
import { appendArtifact, readArtifact } from '../handoff-artifacts/io.js';
|
|
11
|
-
import { assertValidationFreshnessBarrier, rebaselineValidationFreshnessLeaseAfterRecompute, } from '../validation/validation-freshness.js';
|
|
14
|
+
import { assertValidationFreshnessBarrier, beginValidationFreshnessBarrier, bindValidationFreshnessLease, closeValidationFreshnessLease, rebaselineValidationFreshnessLeaseAfterRecompute, } from '../validation/validation-freshness.js';
|
|
12
15
|
import { recoverValidateJobs } from './validate-job-recovery.js';
|
|
13
16
|
import { getRuntimePolicy } from '../runtime-policy.js';
|
|
14
17
|
import { isValidateJob, parseValidatePayload, rehydrateValidationPayload, } from './validate-job-payload.js';
|
|
18
|
+
import { revalidateValidationWorktree } from '../validation/validation-worktree.js';
|
|
19
|
+
import { projectDataDir } from '../../storage/base-store.js';
|
|
20
|
+
import { redactSensitiveValue, redactText } from '../../security/redactor.js';
|
|
21
|
+
import { MacOSKeychainOperationError } from '../../security/adapters/macos-keychain.js';
|
|
22
|
+
import { createDurableJobHeartbeat } from './durable-job-heartbeat-runtime.js';
|
|
15
23
|
const WORKSPACE_ID = 'global';
|
|
16
|
-
const activeScopes = new
|
|
24
|
+
const activeScopes = new Map();
|
|
25
|
+
const RECEIPT_PENDING_ERROR = '[Planu] VALIDATION_RECEIPT_PENDING action=restart_job';
|
|
26
|
+
class ReceiptPublicationTimeoutError extends Error {
|
|
27
|
+
constructor() {
|
|
28
|
+
super('[Planu] Receipt publication deadline elapsed');
|
|
29
|
+
this.name = 'ReceiptPublicationTimeoutError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
class ReceiptPublicationInterruptedError extends Error {
|
|
33
|
+
constructor(options) {
|
|
34
|
+
super('[Planu] Receipt publication was interrupted after persistence', options);
|
|
35
|
+
this.name = 'ReceiptPublicationInterruptedError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
class ValidationAuthorityLostError extends Error {
|
|
39
|
+
reason;
|
|
40
|
+
constructor(reason, options) {
|
|
41
|
+
super(`[Planu] Durable validate job authority was lost (${reason})`, options);
|
|
42
|
+
this.reason = reason;
|
|
43
|
+
this.name = 'ValidationAuthorityLostError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function recoverableReceiptError(error) {
|
|
47
|
+
return (error instanceof ReceiptPublicationTimeoutError ||
|
|
48
|
+
error instanceof ReceiptPublicationInterruptedError ||
|
|
49
|
+
(error instanceof MacOSKeychainOperationError && error.code === 'KEYCHAIN_TIMEOUT'));
|
|
50
|
+
}
|
|
51
|
+
function decodeUtf8Prefix(encoded, prefixLength) {
|
|
52
|
+
try {
|
|
53
|
+
return {
|
|
54
|
+
valid: true,
|
|
55
|
+
value: new TextDecoder('utf-8', { fatal: true }).decode(encoded.subarray(0, prefixLength)),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { valid: false };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function truncateUtf8ByBytes(value, maximumBytes) {
|
|
63
|
+
const encoded = Buffer.from(value, 'utf8');
|
|
64
|
+
if (encoded.length <= maximumBytes) {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
for (let length = maximumBytes; length >= 0; length -= 1) {
|
|
68
|
+
const decoded = decodeUtf8Prefix(encoded, length);
|
|
69
|
+
if (decoded.valid) {
|
|
70
|
+
return decoded.value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
function canonicalJson(value) {
|
|
76
|
+
return JSON.stringify(value);
|
|
77
|
+
}
|
|
17
78
|
function hasReceipt(result) {
|
|
18
79
|
const structured = result.structuredContent;
|
|
19
80
|
return typeof structured?.receiptId === 'string' && typeof structured.receiptKey === 'string';
|
|
@@ -23,6 +84,28 @@ function validationPassed(result) {
|
|
|
23
84
|
const report = structured?.validationReport;
|
|
24
85
|
return structured?.ready === true && report?.passed === true;
|
|
25
86
|
}
|
|
87
|
+
async function closeFreshnessLeaseSafely(lease) {
|
|
88
|
+
if (!lease) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
await closeValidationFreshnessLease(lease);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
reportClassifiedDegradation('VALIDATION_FRESHNESS_CHECK_FAILED', error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function stopHeartbeatSafely(heartbeat) {
|
|
99
|
+
if (!heartbeat) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
await heartbeat.stop();
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
reportClassifiedDegradation('VALIDATE_JOB_HEARTBEAT_FAILURE', error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
26
109
|
function toolResultError(result) {
|
|
27
110
|
return result.content
|
|
28
111
|
.map((item) => item.text)
|
|
@@ -53,11 +136,32 @@ export class ValidateJobExecutor {
|
|
|
53
136
|
return Promise.all(candidates.map((job) => this.execute(job)));
|
|
54
137
|
}
|
|
55
138
|
async runUntilIdle(projectId, workspaceId = WORKSPACE_ID) {
|
|
139
|
+
const capacity = getRuntimePolicy().validation.workerConcurrency;
|
|
140
|
+
const active = new Set();
|
|
56
141
|
for (;;) {
|
|
57
|
-
const
|
|
58
|
-
|
|
142
|
+
const available = capacity - active.size;
|
|
143
|
+
const candidates = this.runtime
|
|
144
|
+
.list(projectId, workspaceId)
|
|
145
|
+
.filter((job) => (job.state === 'accepted' || job.state === 'checkpointed') &&
|
|
146
|
+
isValidateJob(job.payload))
|
|
147
|
+
.slice(0, available);
|
|
148
|
+
for (const candidate of candidates) {
|
|
149
|
+
const execution = this.execute(candidate)
|
|
150
|
+
.then(() => undefined)
|
|
151
|
+
.catch((error) => {
|
|
152
|
+
reportClassifiedDegradation('VALIDATE_JOB_EXECUTION_FAILURE', error);
|
|
153
|
+
})
|
|
154
|
+
.finally(() => {
|
|
155
|
+
active.delete(execution);
|
|
156
|
+
});
|
|
157
|
+
active.add(execution);
|
|
158
|
+
}
|
|
159
|
+
if (active.size === 0) {
|
|
59
160
|
return;
|
|
60
161
|
}
|
|
162
|
+
if (active.size >= capacity || candidates.length === 0) {
|
|
163
|
+
await Promise.race(active);
|
|
164
|
+
}
|
|
61
165
|
}
|
|
62
166
|
}
|
|
63
167
|
async execute(job) {
|
|
@@ -69,12 +173,24 @@ export class ValidateJobExecutor {
|
|
|
69
173
|
const claimed = this.runtime.claim({
|
|
70
174
|
...identity,
|
|
71
175
|
workerId: this.workerId,
|
|
72
|
-
leaseMs:
|
|
176
|
+
leaseMs: this.workerTimingPolicy().workerLeaseMs,
|
|
73
177
|
});
|
|
74
178
|
if (!claimed) {
|
|
75
179
|
return this.runtime.get(identity);
|
|
76
180
|
}
|
|
77
|
-
|
|
181
|
+
let parsedPayload;
|
|
182
|
+
try {
|
|
183
|
+
parsedPayload = parseValidatePayload(claimed.payload);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
reportClassifiedDegradation('VALIDATE_JOB_PAYLOAD_PARSE_FAILURE', error);
|
|
187
|
+
return this.runtime.fail({
|
|
188
|
+
...identity,
|
|
189
|
+
workerId: this.workerId,
|
|
190
|
+
fencingToken: claimed.fencingToken,
|
|
191
|
+
error: error instanceof Error ? error.message : '[Planu] Invalid validate job payload',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
78
194
|
if (!parsedPayload) {
|
|
79
195
|
return this.runtime.fail({
|
|
80
196
|
...identity,
|
|
@@ -83,54 +199,73 @@ export class ValidateJobExecutor {
|
|
|
83
199
|
error: '[Planu] Invalid validate job payload',
|
|
84
200
|
});
|
|
85
201
|
}
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
202
|
+
const pendingCheckpoint = parseValidationReceiptPendingCheckpoint(claimed.checkpoint, this.receiptCheckpointLimits());
|
|
203
|
+
if (pendingCheckpoint) {
|
|
204
|
+
return this.resumeReceiptPublication(identity, parsedPayload, pendingCheckpoint, claimed.fencingToken);
|
|
205
|
+
}
|
|
206
|
+
if (isValidationReceiptPendingIntent(claimed.checkpoint)) {
|
|
88
207
|
return this.runtime.fail({
|
|
89
208
|
...identity,
|
|
90
209
|
workerId: this.workerId,
|
|
91
210
|
fencingToken: claimed.fencingToken,
|
|
92
|
-
error: '[Planu]
|
|
211
|
+
error: '[Planu] Invalid validation receipt checkpoint',
|
|
93
212
|
});
|
|
94
213
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
payload = await rehydrateValidationPayload(parsedPayload);
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
/* reliability-optional: VALIDATE_JOB_SUBMISSION_FAILURE — runtime records typed failure */
|
|
101
|
-
reportClassifiedDegradation('VALIDATE_JOB_SUBMISSION_FAILURE', error);
|
|
214
|
+
const deadlineAt = parsedPayload.deadlineAt ?? Date.now() + getRuntimePolicy().validation.defaultDeadlineMs;
|
|
215
|
+
if (deadlineAt <= Date.now()) {
|
|
102
216
|
return this.runtime.fail({
|
|
103
217
|
...identity,
|
|
104
218
|
workerId: this.workerId,
|
|
105
219
|
fencingToken: claimed.fencingToken,
|
|
106
|
-
error:
|
|
220
|
+
error: '[Planu] Durable validate job deadline elapsed',
|
|
107
221
|
});
|
|
108
222
|
}
|
|
109
|
-
return this.runClaimedValidation(identity,
|
|
223
|
+
return this.runClaimedValidation(identity, parsedPayload, deadlineAt, claimed.fencingToken);
|
|
110
224
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const heartbeat = this.startHeartbeat(identity, fencingToken, (reason) => {
|
|
117
|
-
scope.cancel(reason);
|
|
118
|
-
});
|
|
225
|
+
// eslint-disable-next-line max-lines-per-function -- worker and receipt failure cleanup share one lease boundary
|
|
226
|
+
async runClaimedValidation(identity, submittedPayload, deadlineAt, fencingToken) {
|
|
227
|
+
let scope;
|
|
228
|
+
let heartbeat;
|
|
229
|
+
let payload;
|
|
119
230
|
try {
|
|
231
|
+
const executionScope = createExecutionScope({ requestId: identity.operationId, deadlineAt });
|
|
232
|
+
scope = executionScope;
|
|
233
|
+
heartbeat = await this.startHeartbeat(identity, fencingToken, (reason) => {
|
|
234
|
+
executionScope.cancel(reason);
|
|
235
|
+
});
|
|
236
|
+
let hydratedPayload;
|
|
237
|
+
try {
|
|
238
|
+
hydratedPayload = await rehydrateValidationPayload(submittedPayload);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
reportClassifiedDegradation('VALIDATE_JOB_SUBMISSION_FAILURE', error);
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
payload = hydratedPayload;
|
|
245
|
+
await executionScope.run(() => preflightValidationReceiptSigner({
|
|
246
|
+
projectId: identity.projectId,
|
|
247
|
+
issuerId: LOCAL_VALIDATION_RECEIPT_ISSUER,
|
|
248
|
+
}));
|
|
249
|
+
const afterPreflight = this.observeForSettlement(identity);
|
|
250
|
+
if (afterPreflight.state === 'cancelled' || afterPreflight.cancelRequested) {
|
|
251
|
+
return afterPreflight;
|
|
252
|
+
}
|
|
253
|
+
this.requireCurrentFence(afterPreflight, fencingToken);
|
|
120
254
|
const startedAt = Date.now();
|
|
121
|
-
const result = await
|
|
255
|
+
const result = await executionScope.run(() => (this.options.runWorker ?? runValidateWorker)({
|
|
122
256
|
requestId: identity.operationId,
|
|
123
|
-
input:
|
|
257
|
+
input: { ...hydratedPayload.input, specPath: hydratedPayload.specPath },
|
|
124
258
|
deadlineAt,
|
|
125
|
-
signal:
|
|
259
|
+
signal: executionScope.context.signal,
|
|
126
260
|
onProgress: (progress) => {
|
|
127
261
|
this.persistProgress(identity, fencingToken, progress);
|
|
128
262
|
},
|
|
129
263
|
}));
|
|
130
|
-
const current = this.
|
|
131
|
-
if (current.state === 'cancelled') {
|
|
264
|
+
const current = this.observeForSettlement(identity);
|
|
265
|
+
if (current.state === 'cancelled' || current.cancelRequested) {
|
|
132
266
|
return current;
|
|
133
267
|
}
|
|
268
|
+
this.requireCurrentFence(current, fencingToken);
|
|
134
269
|
if (result.isError === true) {
|
|
135
270
|
return this.runtime.fail({
|
|
136
271
|
...identity,
|
|
@@ -143,8 +278,9 @@ export class ValidateJobExecutor {
|
|
|
143
278
|
throw new Error('[Planu] Validate workers cannot self-issue authoritative receipts');
|
|
144
279
|
}
|
|
145
280
|
const durableResult = validationPassed(result)
|
|
146
|
-
? await this.
|
|
281
|
+
? await this.publishInitialReceipt(identity, payload, result, Date.now() - startedAt, fencingToken, executionScope.context.signal)
|
|
147
282
|
: result;
|
|
283
|
+
this.currentFence(identity, fencingToken);
|
|
148
284
|
return this.runtime.complete({
|
|
149
285
|
...identity,
|
|
150
286
|
workerId: this.workerId,
|
|
@@ -155,10 +291,34 @@ export class ValidateJobExecutor {
|
|
|
155
291
|
catch (error) {
|
|
156
292
|
/* reliability-optional: VALIDATE_JOB_EXECUTION_FAILURE — runtime records typed failure */
|
|
157
293
|
reportClassifiedDegradation('VALIDATE_JOB_EXECUTION_FAILURE', error);
|
|
158
|
-
const
|
|
294
|
+
const disposition = this.failureDisposition(identity, fencingToken, error);
|
|
295
|
+
if (disposition.settleLocally) {
|
|
296
|
+
if (disposition.current) {
|
|
297
|
+
return disposition.current;
|
|
298
|
+
}
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
const current = disposition.current;
|
|
159
302
|
if (current.state === 'cancelled') {
|
|
160
303
|
return current;
|
|
161
304
|
}
|
|
305
|
+
if (recoverableReceiptError(error) &&
|
|
306
|
+
parseValidationReceiptPendingCheckpoint(current.checkpoint, this.receiptCheckpointLimits())) {
|
|
307
|
+
return this.runtime.fail({
|
|
308
|
+
...identity,
|
|
309
|
+
workerId: this.workerId,
|
|
310
|
+
fencingToken,
|
|
311
|
+
error: RECEIPT_PENDING_ERROR,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
if (parseValidationReceiptPendingCheckpoint(current.checkpoint, this.receiptCheckpointLimits())) {
|
|
315
|
+
this.recordFencedProgress(identity, fencingToken, {
|
|
316
|
+
phase: 'receipt-publication',
|
|
317
|
+
command: '[Planu] Receipt publication failed closed',
|
|
318
|
+
durationMs: 0,
|
|
319
|
+
status: 'failed',
|
|
320
|
+
});
|
|
321
|
+
}
|
|
162
322
|
return this.runtime.fail({
|
|
163
323
|
...identity,
|
|
164
324
|
workerId: this.workerId,
|
|
@@ -172,49 +332,325 @@ export class ValidateJobExecutor {
|
|
|
172
332
|
});
|
|
173
333
|
}
|
|
174
334
|
finally {
|
|
175
|
-
|
|
176
|
-
scope
|
|
335
|
+
await stopHeartbeatSafely(heartbeat);
|
|
336
|
+
scope?.dispose();
|
|
337
|
+
await closeFreshnessLeaseSafely(payload?.freshnessLease);
|
|
177
338
|
}
|
|
178
339
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
340
|
+
// eslint-disable-next-line max-lines-per-function -- receipt resume must keep its fresh lease and fail-closed cleanup together
|
|
341
|
+
async resumeReceiptPublication(identity, payload, checkpoint, fencingToken) {
|
|
342
|
+
const publicationDeadlineMs = getRuntimePolicy().validation.receiptPublicationDeadlineMs;
|
|
343
|
+
const deadlineAt = Date.now() + publicationDeadlineMs;
|
|
344
|
+
const scope = createExecutionScope({ requestId: identity.operationId, deadlineAt });
|
|
345
|
+
const deadlineTimer = setTimeout(() => {
|
|
346
|
+
scope.cancel(new ReceiptPublicationTimeoutError());
|
|
347
|
+
}, publicationDeadlineMs);
|
|
348
|
+
deadlineTimer.unref();
|
|
349
|
+
let heartbeat;
|
|
350
|
+
let ownedLease;
|
|
351
|
+
try {
|
|
352
|
+
heartbeat = await this.startHeartbeat(identity, fencingToken, (reason) => {
|
|
353
|
+
scope.cancel(reason);
|
|
354
|
+
});
|
|
355
|
+
const receiptBindings = checkpoint.receiptInput.bindings;
|
|
356
|
+
const resolved = await revalidateValidationWorktree({
|
|
357
|
+
projectPath: payload.input.projectPath ?? '',
|
|
358
|
+
specPath: payload.specPath,
|
|
359
|
+
specId: payload.input.specId,
|
|
360
|
+
validationRootId: payload.validationRootId,
|
|
361
|
+
});
|
|
362
|
+
const lease = await beginValidationFreshnessBarrier({
|
|
363
|
+
projectId: identity.projectId,
|
|
364
|
+
specId: payload.input.specId,
|
|
365
|
+
projectPath: resolved.projectPath,
|
|
366
|
+
handoffPath: join(projectDataDir(identity.projectId), 'handoffs', payload.input.specId),
|
|
367
|
+
invalidateCurrentBindings: false,
|
|
368
|
+
});
|
|
369
|
+
ownedLease = lease;
|
|
370
|
+
const observed = await computeDurableValidationBindings({
|
|
371
|
+
canonicalProjectId: identity.projectId,
|
|
372
|
+
specId: payload.input.specId,
|
|
373
|
+
projectPath: resolved.projectPath,
|
|
374
|
+
specPath: resolved.specPath,
|
|
375
|
+
});
|
|
376
|
+
const observedBindings = toValidationReceiptBindings(observed.bindings, observed.artifactDigests);
|
|
377
|
+
if (canonicalJson(observedBindings) !== canonicalJson(receiptBindings) ||
|
|
378
|
+
!(await bindValidationFreshnessLease(lease, observedBindings))) {
|
|
379
|
+
throw new Error('[Planu] Durable validation bindings became stale; submit a new validation');
|
|
185
380
|
}
|
|
186
|
-
|
|
381
|
+
const hydratedPayload = {
|
|
382
|
+
...payload,
|
|
383
|
+
input: { ...payload.input, projectPath: resolved.projectPath },
|
|
384
|
+
specPath: resolved.specPath,
|
|
385
|
+
bindings: observed.bindings,
|
|
386
|
+
artifactDigests: observed.artifactDigests,
|
|
387
|
+
freshnessLease: lease,
|
|
388
|
+
};
|
|
389
|
+
const result = await scope.run(() => {
|
|
390
|
+
this.currentFence(identity, fencingToken);
|
|
391
|
+
return this.issueReceipt(identity, hydratedPayload, checkpoint, lease, fencingToken, scope.context.signal);
|
|
392
|
+
});
|
|
393
|
+
this.currentFence(identity, fencingToken);
|
|
394
|
+
return this.runtime.complete({
|
|
187
395
|
...identity,
|
|
188
396
|
workerId: this.workerId,
|
|
189
397
|
fencingToken,
|
|
190
|
-
|
|
191
|
-
})
|
|
192
|
-
|
|
398
|
+
result,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
reportClassifiedDegradation('VALIDATE_JOB_EXECUTION_FAILURE', error);
|
|
403
|
+
const disposition = this.failureDisposition(identity, fencingToken, error);
|
|
404
|
+
if (disposition.settleLocally) {
|
|
405
|
+
if (disposition.current) {
|
|
406
|
+
return disposition.current;
|
|
407
|
+
}
|
|
408
|
+
throw error;
|
|
193
409
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
410
|
+
const current = disposition.current;
|
|
411
|
+
if (current.state === 'cancelled') {
|
|
412
|
+
return current;
|
|
413
|
+
}
|
|
414
|
+
if (recoverableReceiptError(error)) {
|
|
415
|
+
return this.runtime.fail({
|
|
416
|
+
...identity,
|
|
417
|
+
workerId: this.workerId,
|
|
418
|
+
fencingToken,
|
|
419
|
+
error: RECEIPT_PENDING_ERROR,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
this.recordFencedProgress(identity, fencingToken, {
|
|
423
|
+
phase: 'receipt-publication',
|
|
424
|
+
command: '[Planu] Receipt publication failed closed',
|
|
425
|
+
durationMs: 0,
|
|
426
|
+
status: 'failed',
|
|
427
|
+
});
|
|
428
|
+
return this.runtime.fail({
|
|
429
|
+
...identity,
|
|
430
|
+
workerId: this.workerId,
|
|
431
|
+
fencingToken,
|
|
432
|
+
error: '[Planu] Receipt publication failed closed',
|
|
433
|
+
});
|
|
202
434
|
}
|
|
203
|
-
|
|
435
|
+
finally {
|
|
436
|
+
clearTimeout(deadlineTimer);
|
|
437
|
+
await stopHeartbeatSafely(heartbeat);
|
|
438
|
+
scope.dispose();
|
|
439
|
+
await closeFreshnessLeaseSafely(ownedLease);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
startHeartbeat(identity, fencingToken, cancel) {
|
|
443
|
+
const timing = this.workerTimingPolicy();
|
|
444
|
+
const policy = getRuntimePolicy().validation;
|
|
445
|
+
const factory = this.options.heartbeatFactory ?? createDurableJobHeartbeat;
|
|
446
|
+
return factory({
|
|
204
447
|
...identity,
|
|
205
448
|
workerId: this.workerId,
|
|
206
449
|
fencingToken,
|
|
207
|
-
|
|
450
|
+
databasePath: this.runtime.runtimeDatabasePath(),
|
|
451
|
+
leaseMs: timing.workerLeaseMs,
|
|
452
|
+
heartbeatMs: timing.workerHeartbeatMs,
|
|
453
|
+
databaseBusyTimeoutMs: policy.heartbeatDatabaseBusyTimeoutMs,
|
|
454
|
+
startupTimeoutMs: policy.heartbeatStartupTimeoutMs,
|
|
455
|
+
shutdownTimeoutMs: policy.heartbeatShutdownTimeoutMs,
|
|
456
|
+
onAuthorityLost: (reason) => {
|
|
457
|
+
reportClassifiedDegradation('VALIDATE_JOB_HEARTBEAT_FAILURE', reason);
|
|
458
|
+
cancel(new ValidationAuthorityLostError('infrastructure', { cause: reason }));
|
|
459
|
+
},
|
|
460
|
+
}).catch((error) => {
|
|
461
|
+
const authorityError = new ValidationAuthorityLostError('infrastructure', { cause: error });
|
|
462
|
+
reportClassifiedDegradation('VALIDATE_JOB_HEARTBEAT_FAILURE', error);
|
|
463
|
+
cancel(authorityError);
|
|
464
|
+
throw authorityError;
|
|
208
465
|
});
|
|
209
466
|
}
|
|
210
|
-
|
|
467
|
+
persistProgress(identity, fencingToken, progress) {
|
|
468
|
+
const current = this.observeForSettlement(identity);
|
|
469
|
+
if (current.state !== 'running') {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const previous = current.checkpoint;
|
|
473
|
+
if (previous &&
|
|
474
|
+
(progress.completedGates < (previous.completedGates ?? 0) ||
|
|
475
|
+
(typeof previous.lastProgressAt === 'string' &&
|
|
476
|
+
progress.lastProgressAt < previous.lastProgressAt) ||
|
|
477
|
+
(progress.completedGates === previous.completedGates &&
|
|
478
|
+
progress.phase === previous.phase &&
|
|
479
|
+
progress.status === previous.status))) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
this.recordFencedProgress(identity, fencingToken, redactValidationProgress(progress));
|
|
483
|
+
}
|
|
484
|
+
async publishInitialReceipt(identity, payload, result, durationMs, fencingToken, signal) {
|
|
485
|
+
this.currentFence(identity, fencingToken);
|
|
211
486
|
if (!hasReceiptInputs(payload)) {
|
|
212
487
|
throw new Error('[Planu] Passing durable validation is missing receipt bindings');
|
|
213
488
|
}
|
|
214
489
|
const receiptBindings = toValidationReceiptBindings(payload.bindings, payload.artifactDigests);
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
490
|
+
const checkpoint = this.buildReceiptPendingCheckpoint(payload, receiptBindings, result, durationMs);
|
|
491
|
+
this.recordFencedProgress(identity, fencingToken, checkpoint);
|
|
492
|
+
const publicationDeadlineMs = getRuntimePolicy().validation.receiptPublicationDeadlineMs;
|
|
493
|
+
const publicationScope = createExecutionScope({
|
|
494
|
+
requestId: identity.operationId,
|
|
495
|
+
deadlineAt: Date.now() + publicationDeadlineMs,
|
|
496
|
+
upstreamSignal: signal,
|
|
497
|
+
});
|
|
498
|
+
const deadlineTimer = setTimeout(() => {
|
|
499
|
+
publicationScope.cancel(new ReceiptPublicationTimeoutError());
|
|
500
|
+
}, publicationDeadlineMs);
|
|
501
|
+
deadlineTimer.unref();
|
|
502
|
+
try {
|
|
503
|
+
return await publicationScope.run(async () => {
|
|
504
|
+
const publicationLease = await this.preparePublicationLease(payload, receiptBindings);
|
|
505
|
+
this.currentFence(identity, fencingToken);
|
|
506
|
+
return this.issueReceipt(identity, payload, checkpoint, publicationLease, fencingToken, publicationScope.context.signal);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
finally {
|
|
510
|
+
clearTimeout(deadlineTimer);
|
|
511
|
+
publicationScope.dispose();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
workerTimingPolicy() {
|
|
515
|
+
const production = getRuntimePolicy().validation;
|
|
516
|
+
const injected = this.options.workerTimingPolicy;
|
|
517
|
+
if (!injected) {
|
|
518
|
+
return production;
|
|
519
|
+
}
|
|
520
|
+
const { workerLeaseMs, workerHeartbeatMs } = injected;
|
|
521
|
+
if (!Number.isSafeInteger(workerLeaseMs) ||
|
|
522
|
+
workerLeaseMs <= 0 ||
|
|
523
|
+
!Number.isSafeInteger(workerHeartbeatMs) ||
|
|
524
|
+
workerHeartbeatMs <= 0 ||
|
|
525
|
+
workerHeartbeatMs >= workerLeaseMs ||
|
|
526
|
+
BigInt(workerHeartbeatMs) * BigInt(production.workerLeaseMs) !==
|
|
527
|
+
BigInt(workerLeaseMs) * BigInt(production.workerHeartbeatMs) ||
|
|
528
|
+
production.heartbeatDatabaseBusyTimeoutMs >= workerLeaseMs - workerHeartbeatMs) {
|
|
529
|
+
throw new Error('[Planu] Invalid validation worker timing policy');
|
|
530
|
+
}
|
|
531
|
+
return injected;
|
|
532
|
+
}
|
|
533
|
+
requireCurrentFence(current, fencingToken) {
|
|
534
|
+
if (!this.hasCurrentFence(current, fencingToken)) {
|
|
535
|
+
throw new ValidationAuthorityLostError('reclaimed');
|
|
536
|
+
}
|
|
537
|
+
if (!current.leaseExpiresAt || Date.parse(current.leaseExpiresAt) <= Date.now()) {
|
|
538
|
+
throw new ValidationAuthorityLostError('expired');
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
hasCurrentFence(current, fencingToken) {
|
|
542
|
+
return (current.state === 'running' &&
|
|
543
|
+
current.leaseOwner === this.workerId &&
|
|
544
|
+
current.fencingToken === fencingToken &&
|
|
545
|
+
!current.cancelRequested);
|
|
546
|
+
}
|
|
547
|
+
currentFence(identity, fencingToken) {
|
|
548
|
+
const current = this.observeForSettlement(identity);
|
|
549
|
+
this.requireCurrentFence(current, fencingToken);
|
|
550
|
+
return current;
|
|
551
|
+
}
|
|
552
|
+
observeForSettlement(identity) {
|
|
553
|
+
try {
|
|
554
|
+
return this.runtime.get(identity);
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
reportClassifiedDegradation('VALIDATE_JOB_STATE_READ_FAILURE', error);
|
|
558
|
+
throw new ValidationAuthorityLostError('infrastructure', { cause: error });
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
recordFencedProgress(identity, fencingToken, checkpoint) {
|
|
562
|
+
try {
|
|
563
|
+
this.runtime.recordProgress({
|
|
564
|
+
...identity,
|
|
565
|
+
workerId: this.workerId,
|
|
566
|
+
fencingToken,
|
|
567
|
+
leaseMs: this.workerTimingPolicy().workerLeaseMs,
|
|
568
|
+
checkpoint,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
reportClassifiedDegradation('VALIDATE_JOB_PROGRESS_FAILURE', error);
|
|
573
|
+
throw new ValidationAuthorityLostError('infrastructure', { cause: error });
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
failureDisposition(identity, fencingToken, error) {
|
|
577
|
+
let current;
|
|
578
|
+
try {
|
|
579
|
+
current = this.runtime.get(identity);
|
|
580
|
+
}
|
|
581
|
+
catch (readError) {
|
|
582
|
+
reportClassifiedDegradation('VALIDATE_JOB_STATE_READ_FAILURE', readError);
|
|
583
|
+
return { settleLocally: true };
|
|
584
|
+
}
|
|
585
|
+
if (current.state === 'cancelled' || current.cancelRequested) {
|
|
586
|
+
return { settleLocally: true, current };
|
|
587
|
+
}
|
|
588
|
+
if (!this.hasCurrentFence(current, fencingToken) ||
|
|
589
|
+
!current.leaseExpiresAt ||
|
|
590
|
+
Date.parse(current.leaseExpiresAt) <= Date.now()) {
|
|
591
|
+
return { settleLocally: true, current };
|
|
592
|
+
}
|
|
593
|
+
return error instanceof ValidationAuthorityLostError
|
|
594
|
+
? { settleLocally: true, current }
|
|
595
|
+
: { settleLocally: false, current };
|
|
596
|
+
}
|
|
597
|
+
buildReceiptPendingCheckpoint(payload, receiptBindings, result, durationMs) {
|
|
598
|
+
const { logMaxBytes, resultMaxBytes } = this.receiptCheckpointLimits();
|
|
599
|
+
if (!Number.isSafeInteger(logMaxBytes) ||
|
|
600
|
+
logMaxBytes <= 0 ||
|
|
601
|
+
!Number.isSafeInteger(resultMaxBytes) ||
|
|
602
|
+
resultMaxBytes <= 0 ||
|
|
603
|
+
logMaxBytes > resultMaxBytes) {
|
|
604
|
+
throw new Error('[Planu] Invalid receipt checkpoint policy');
|
|
605
|
+
}
|
|
606
|
+
const redactedResult = redactSensitiveValue(result);
|
|
607
|
+
if (!redactedResult ||
|
|
608
|
+
typeof redactedResult !== 'object' ||
|
|
609
|
+
Buffer.byteLength(canonicalJson(redactedResult), 'utf8') > resultMaxBytes) {
|
|
610
|
+
throw new Error('[Planu] VALIDATION_CHECKPOINT_TOO_LARGE');
|
|
611
|
+
}
|
|
612
|
+
const receiptInput = {
|
|
613
|
+
bindings: receiptBindings,
|
|
614
|
+
command: {
|
|
615
|
+
executable: 'planu',
|
|
616
|
+
args: ['validate', payload.bindings.specId],
|
|
617
|
+
cwd: '<project>',
|
|
618
|
+
},
|
|
619
|
+
versions: { node: process.version, receiptSchema: '1' },
|
|
620
|
+
exitCode: 0,
|
|
621
|
+
durationMs,
|
|
622
|
+
log: truncateUtf8ByBytes(redactText(result.content.map((item) => item.text).join('\n')), logMaxBytes),
|
|
623
|
+
gates: [{ gate: 'validation', status: 'passed', durationMs }],
|
|
624
|
+
issuedAt: new Date().toISOString(),
|
|
625
|
+
};
|
|
626
|
+
const checkpoint = {
|
|
627
|
+
kind: 'validation-receipt-pending',
|
|
628
|
+
schemaVersion: 1,
|
|
629
|
+
errorCode: 'VALIDATION_RECEIPT_PENDING',
|
|
630
|
+
recoveryAction: 'restart_job',
|
|
631
|
+
result: redactedResult,
|
|
632
|
+
receiptInput,
|
|
633
|
+
};
|
|
634
|
+
const parsed = parseValidationReceiptPendingCheckpoint(checkpoint, this.receiptCheckpointLimits());
|
|
635
|
+
if (!parsed) {
|
|
636
|
+
throw new Error('[Planu] Validation receipt checkpoint is not persistable');
|
|
637
|
+
}
|
|
638
|
+
return parsed;
|
|
639
|
+
}
|
|
640
|
+
receiptCheckpointLimits() {
|
|
641
|
+
const policy = getRuntimePolicy().validation;
|
|
642
|
+
return {
|
|
643
|
+
logMaxBytes: this.options.receiptCheckpointPolicy?.logMaxBytes ?? policy.receiptCheckpointLogMaxBytes,
|
|
644
|
+
resultMaxBytes: this.options.receiptCheckpointPolicy?.resultMaxBytes ??
|
|
645
|
+
policy.receiptCheckpointResultMaxBytes,
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
async issueReceipt(identity, payload, checkpoint, publicationLease, fencingToken, signal) {
|
|
649
|
+
const receiptBindings = checkpoint.receiptInput.bindings;
|
|
650
|
+
if (executionAborted(signal)) {
|
|
651
|
+
signal.throwIfAborted();
|
|
652
|
+
}
|
|
653
|
+
if (!(await assertValidationFreshnessBarrier(publicationLease, receiptBindings))) {
|
|
218
654
|
throw new Error('[Planu] Validation inputs changed before receipt publication');
|
|
219
655
|
}
|
|
220
656
|
const database = new RuntimeDatabase({ path: resolveStorageLayout().runtimeDatabase });
|
|
@@ -223,40 +659,47 @@ export class ValidateJobExecutor {
|
|
|
223
659
|
projectId: identity.projectId,
|
|
224
660
|
issuerId: LOCAL_VALIDATION_RECEIPT_ISSUER,
|
|
225
661
|
});
|
|
662
|
+
signal.throwIfAborted();
|
|
226
663
|
const { store } = createValidationReceiptAuthority({
|
|
227
664
|
database,
|
|
228
665
|
workspaceId: identity.workspaceId,
|
|
229
666
|
projectId: identity.projectId,
|
|
230
667
|
issuerId: LOCAL_VALIDATION_RECEIPT_ISSUER,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
668
|
+
authorizePersistence: (persistenceDatabase) => {
|
|
669
|
+
persistenceDatabase.assertActiveJobLease({
|
|
670
|
+
id: identity.operationId,
|
|
671
|
+
projectId: identity.projectId,
|
|
672
|
+
workspaceId: identity.workspaceId,
|
|
673
|
+
workerId: this.workerId,
|
|
674
|
+
fencingToken,
|
|
675
|
+
leaseMs: this.workerTimingPolicy().workerLeaseMs,
|
|
676
|
+
now: new Date().toISOString(),
|
|
677
|
+
});
|
|
238
678
|
},
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
durationMs,
|
|
242
|
-
log: result.content.map((item) => item.text).join('\n'),
|
|
243
|
-
gates: [{ gate: 'validation', status: 'passed', durationMs }],
|
|
244
|
-
}, {
|
|
679
|
+
});
|
|
680
|
+
const receipt = await issueValidationReceipt(checkpoint.receiptInput, {
|
|
245
681
|
issuerId: LOCAL_VALIDATION_RECEIPT_ISSUER,
|
|
246
682
|
signingKeyReference,
|
|
247
683
|
store,
|
|
248
684
|
});
|
|
249
|
-
if (executionAborted(signal)
|
|
250
|
-
|
|
685
|
+
if (executionAborted(signal)) {
|
|
686
|
+
signal.throwIfAborted();
|
|
687
|
+
}
|
|
688
|
+
if (!(await assertValidationFreshnessBarrier(publicationLease, receiptBindings))) {
|
|
251
689
|
await store.invalidateCurrentBindings(identity.projectId, payload.bindings.specId);
|
|
252
690
|
throw new Error('[Planu] Validation inputs changed during receipt publication');
|
|
253
691
|
}
|
|
254
692
|
const receiptKey = `validation-receipt:${receipt.receiptId.slice('sha256:'.length)}`;
|
|
255
|
-
|
|
693
|
+
try {
|
|
694
|
+
await this.persistReceiptArtifacts(identity, payload, receipt.receiptId, receiptKey);
|
|
695
|
+
}
|
|
696
|
+
catch (error) {
|
|
697
|
+
throw new ReceiptPublicationInterruptedError({ cause: error });
|
|
698
|
+
}
|
|
256
699
|
return {
|
|
257
|
-
...result,
|
|
700
|
+
...checkpoint.result,
|
|
258
701
|
structuredContent: {
|
|
259
|
-
...(result.structuredContent ?? {}),
|
|
702
|
+
...(checkpoint.result.structuredContent ?? {}),
|
|
260
703
|
receiptId: receipt.receiptId,
|
|
261
704
|
receiptKey,
|
|
262
705
|
},
|
|
@@ -275,8 +718,14 @@ export class ValidateJobExecutor {
|
|
|
275
718
|
return lease;
|
|
276
719
|
}
|
|
277
720
|
const bindings = payload.bindings;
|
|
278
|
-
const
|
|
279
|
-
|
|
721
|
+
const resolved = await revalidateValidationWorktree({
|
|
722
|
+
projectPath: payload.input.projectPath,
|
|
723
|
+
specPath: payload.specPath,
|
|
724
|
+
specId: payload.input.specId,
|
|
725
|
+
validationRootId: payload.validationRootId,
|
|
726
|
+
});
|
|
727
|
+
const projectPath = resolved.projectPath;
|
|
728
|
+
const specPath = resolved.specPath;
|
|
280
729
|
const rebaselined = await rebaselineValidationFreshnessLeaseAfterRecompute({
|
|
281
730
|
lease,
|
|
282
731
|
expectedBindings,
|
|
@@ -322,19 +771,33 @@ export class ValidateJobExecutor {
|
|
|
322
771
|
/** Schedules background execution after the submit response can be flushed. */
|
|
323
772
|
export function scheduleValidateJobExecution(projectId, workspaceId = WORKSPACE_ID) {
|
|
324
773
|
const scope = `${projectId}:${workspaceId}`;
|
|
325
|
-
|
|
774
|
+
const active = activeScopes.get(scope);
|
|
775
|
+
if (active) {
|
|
776
|
+
active.rescheduleRequested = true;
|
|
326
777
|
return;
|
|
327
778
|
}
|
|
328
|
-
|
|
779
|
+
const state = { rescheduleRequested: false };
|
|
780
|
+
activeScopes.set(scope, state);
|
|
329
781
|
setImmediate(() => {
|
|
330
782
|
const runtime = new DurableJobRuntime();
|
|
331
783
|
const executor = new ValidateJobExecutor(runtime);
|
|
332
|
-
void
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
784
|
+
void (async () => {
|
|
785
|
+
try {
|
|
786
|
+
do {
|
|
787
|
+
state.rescheduleRequested = false;
|
|
788
|
+
await executor.runUntilIdle(projectId, workspaceId);
|
|
789
|
+
} while (rescheduleRequested(state));
|
|
790
|
+
}
|
|
791
|
+
finally {
|
|
792
|
+
runtime.close();
|
|
793
|
+
activeScopes.delete(scope);
|
|
794
|
+
}
|
|
795
|
+
})();
|
|
336
796
|
});
|
|
337
797
|
}
|
|
798
|
+
function rescheduleRequested(state) {
|
|
799
|
+
return state.rescheduleRequested;
|
|
800
|
+
}
|
|
338
801
|
/** Re-enqueues accepted/checkpointed validate work discovered after a process restart. */
|
|
339
802
|
export function recoverValidateJobsAtStartup(options = {}) {
|
|
340
803
|
recoverValidateJobs(scheduleValidateJobExecution, options);
|