@velum-labs/routekit-eval-setup 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/authoring-responses-request.d.ts +5 -0
- package/dist/adapters/authoring-responses-request.js +38 -0
- package/dist/adapters/evaluation-evidence-freshness.d.ts +7 -0
- package/dist/adapters/evaluation-evidence-freshness.js +76 -0
- package/dist/adapters/git-task-history.d.ts +67 -0
- package/dist/adapters/git-task-history.js +171 -0
- package/dist/adapters/integrated-repository-history.d.ts +21 -0
- package/dist/adapters/integrated-repository-history.js +175 -0
- package/dist/adapters/repository-command-diagnostic.d.ts +8 -0
- package/dist/adapters/repository-command-diagnostic.js +46 -0
- package/dist/adapters/repository-command-evidence.d.ts +13 -0
- package/dist/adapters/repository-command-evidence.js +102 -0
- package/dist/adapters/repository-command-runner.d.ts +238 -0
- package/dist/adapters/repository-command-runner.js +1483 -0
- package/dist/adapters/repository-import-context.d.ts +47 -0
- package/dist/adapters/repository-import-context.js +469 -0
- package/dist/adapters/repository-node-test-reporter.d.ts +3 -0
- package/dist/adapters/repository-node-test-reporter.js +27 -0
- package/dist/adapters/repository-review-evidence.d.ts +39 -0
- package/dist/adapters/repository-review-evidence.js +632 -0
- package/dist/adapters/repository-seed-selection.d.ts +7 -0
- package/dist/adapters/repository-seed-selection.js +79 -0
- package/dist/adapters/repository-solution-edits.d.ts +49 -0
- package/dist/adapters/repository-solution-edits.js +136 -0
- package/dist/adapters/repository-vitest-phase-adapter.d.ts +8 -0
- package/dist/adapters/repository-vitest-phase-adapter.js +310 -0
- package/dist/adapters/repository-vitest-reporter.d.ts +24 -0
- package/dist/adapters/repository-vitest-reporter.js +314 -0
- package/dist/adapters/strict-authoring-schema.d.ts +5 -0
- package/dist/adapters/strict-authoring-schema.js +158 -0
- package/dist/adapters/test-discovery.d.ts +30 -0
- package/dist/adapters/test-discovery.js +124 -0
- package/dist/adapters/typescript-repository-index.d.ts +51 -0
- package/dist/adapters/typescript-repository-index.js +226 -0
- package/dist/agentic-capabilities-protocol.d.ts +1373 -0
- package/dist/agentic-capabilities-protocol.js +786 -0
- package/dist/case-checkpoint-store.d.ts +29 -0
- package/dist/case-checkpoint-store.js +133 -0
- package/dist/case-pipeline-protocol-v2.d.ts +184 -0
- package/dist/case-pipeline-protocol-v2.js +193 -0
- package/dist/case-pipeline-protocol.d.ts +2626 -0
- package/dist/case-pipeline-protocol.js +371 -0
- package/dist/effect-api.d.ts +74 -10
- package/dist/effect-api.js +56 -6
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +10 -0
- package/dist/eval-capability-execution-envelope.d.ts +64 -0
- package/dist/eval-capability-execution-envelope.js +98 -0
- package/dist/eval-capability-policy.d.ts +90 -0
- package/dist/eval-capability-policy.js +107 -0
- package/dist/eval-event-log.d.ts +140 -0
- package/dist/eval-event-log.js +220 -0
- package/dist/evaluation-authoring-policy.d.ts +18 -0
- package/dist/evaluation-authoring-policy.js +19 -0
- package/dist/evaluation-authoring-validation.d.ts +22 -0
- package/dist/evaluation-authoring-validation.js +72 -0
- package/dist/evaluation-evidence.d.ts +20 -0
- package/dist/evaluation-evidence.js +319 -0
- package/dist/evaluation-grader-calibration-protocol.d.ts +108 -0
- package/dist/evaluation-grader-calibration-protocol.js +80 -0
- package/dist/evaluation-grader-calibration.d.ts +18 -0
- package/dist/evaluation-grader-calibration.js +334 -0
- package/dist/evaluation-grading-policy.d.ts +24 -0
- package/dist/evaluation-grading-policy.js +54 -0
- package/dist/evaluation-proposal-policy.d.ts +4 -0
- package/dist/evaluation-proposal-policy.js +91 -0
- package/dist/evaluation-source-retrieval.d.ts +68 -0
- package/dist/evaluation-source-retrieval.js +513 -0
- package/dist/evaluation-structure-policy.d.ts +29 -0
- package/dist/evaluation-structure-policy.js +138 -0
- package/dist/index.d.ts +124 -17
- package/dist/index.js +69 -11
- package/dist/inspection.js +2 -3
- package/dist/project-artifacts.d.ts +7 -2
- package/dist/project-artifacts.js +49 -136
- package/dist/project-authoring.d.ts +66 -5
- package/dist/project-authoring.js +783 -109
- package/dist/project-contracts.d.ts +419 -84
- package/dist/project-contracts.js +160 -52
- package/dist/project-store.js +2 -1
- package/dist/project-workflow.d.ts +5 -4
- package/dist/project-workflow.js +154 -35
- package/dist/repository-adversary-protocol.d.ts +64 -0
- package/dist/repository-adversary-protocol.js +105 -0
- package/dist/repository-behavior-protocol.d.ts +188 -0
- package/dist/repository-behavior-protocol.js +202 -0
- package/dist/repository-benchmark-protocol.d.ts +487 -0
- package/dist/repository-benchmark-protocol.js +96 -0
- package/dist/repository-execution-protocol.d.ts +150 -0
- package/dist/repository-execution-protocol.js +38 -0
- package/dist/repository-fixture-instructions.d.ts +3 -0
- package/dist/repository-fixture-instructions.js +91 -0
- package/dist/repository-fixture-protocol.d.ts +79 -0
- package/dist/repository-fixture-protocol.js +79 -0
- package/dist/repository-foundry-plan-protocol.d.ts +118 -0
- package/dist/repository-foundry-plan-protocol.js +296 -0
- package/dist/repository-foundry-progress-protocol.d.ts +52 -0
- package/dist/repository-foundry-progress-protocol.js +52 -0
- package/dist/repository-improvement-protocol.d.ts +100 -0
- package/dist/repository-improvement-protocol.js +106 -0
- package/dist/repository-language-model-protocol.d.ts +43 -0
- package/dist/repository-language-model-protocol.js +146 -0
- package/dist/repository-oracle-coverage-protocol.d.ts +18 -0
- package/dist/repository-oracle-coverage-protocol.js +39 -0
- package/dist/repository-oracle-execution-binding.d.ts +27 -0
- package/dist/repository-oracle-execution-binding.js +59 -0
- package/dist/repository-oracle-protocol.d.ts +230 -0
- package/dist/repository-oracle-protocol.js +156 -0
- package/dist/repository-oracle-scope-policy.d.ts +22 -0
- package/dist/repository-oracle-scope-policy.js +92 -0
- package/dist/repository-quality-policy.d.ts +15 -0
- package/dist/repository-quality-policy.js +357 -0
- package/dist/repository-routing-benchmark-protocol.d.ts +176 -0
- package/dist/repository-routing-benchmark-protocol.js +103 -0
- package/dist/repository-routing-model-protocol.d.ts +36 -0
- package/dist/repository-routing-model-protocol.js +89 -0
- package/dist/repository-routing-plan-protocol.d.ts +112 -0
- package/dist/repository-routing-plan-protocol.js +58 -0
- package/dist/repository-routing-quality-policy.d.ts +9 -0
- package/dist/repository-routing-quality-policy.js +191 -0
- package/dist/repository-seed-qualification-progress-protocol.d.ts +205 -0
- package/dist/repository-seed-qualification-progress-protocol.js +28 -0
- package/dist/repository-semantic-calibration-protocol.d.ts +768 -0
- package/dist/repository-semantic-calibration-protocol.js +276 -0
- package/dist/repository-semantic-calibration.d.ts +163 -0
- package/dist/repository-semantic-calibration.js +581 -0
- package/dist/repository-specification-contract-facts-protocol.d.ts +224 -0
- package/dist/repository-specification-contract-facts-protocol.js +276 -0
- package/dist/repository-specification-critique-protocol.d.ts +189 -0
- package/dist/repository-specification-critique-protocol.js +103 -0
- package/dist/repository-task-family-protocol.d.ts +24 -0
- package/dist/repository-task-family-protocol.js +37 -0
- package/dist/repository-task-seed-protocol.d.ts +384 -0
- package/dist/repository-task-seed-protocol.js +236 -0
- package/dist/repository-trajectory-protocol.d.ts +20 -0
- package/dist/repository-trajectory-protocol.js +42 -0
- package/dist/service.js +1 -1
- package/dist/services/adversary/service.d.ts +64 -0
- package/dist/services/adversary/service.js +330 -0
- package/dist/services/benchmark-compiler/service.d.ts +450 -0
- package/dist/services/benchmark-compiler/service.js +9 -0
- package/dist/services/budgeted-model/service.d.ts +118 -0
- package/dist/services/budgeted-model/service.js +460 -0
- package/dist/services/case-authoring/service.d.ts +163 -0
- package/dist/services/case-authoring/service.js +1456 -0
- package/dist/services/case-finalization/service.d.ts +283 -0
- package/dist/services/case-finalization/service.js +370 -0
- package/dist/services/case-generation/service.d.ts +619 -0
- package/dist/services/case-generation/service.js +2628 -0
- package/dist/services/case-pipeline/service.d.ts +31 -0
- package/dist/services/case-pipeline/service.js +485 -0
- package/dist/services/case-pipeline-v2/service.d.ts +70 -0
- package/dist/services/case-pipeline-v2/service.js +477 -0
- package/dist/services/command-observability/service.d.ts +13 -0
- package/dist/services/command-observability/service.js +3 -0
- package/dist/services/dimension-labeling/service.d.ts +77 -0
- package/dist/services/dimension-labeling/service.js +188 -0
- package/dist/services/eval-candidate/service.d.ts +208 -0
- package/dist/services/eval-candidate/service.js +64 -0
- package/dist/services/eval-capabilities/service.d.ts +183 -0
- package/dist/services/eval-capabilities/service.js +1433 -0
- package/dist/services/eval-environment/service.d.ts +173 -0
- package/dist/services/eval-environment/service.js +127 -0
- package/dist/services/evidence-reconstruction/service.d.ts +36 -0
- package/dist/services/evidence-reconstruction/service.js +145 -0
- package/dist/services/fixture-builder/service.d.ts +62 -0
- package/dist/services/fixture-builder/service.js +36 -0
- package/dist/services/fixture-validation/service.d.ts +75 -0
- package/dist/services/fixture-validation/service.js +295 -0
- package/dist/services/foundry/service.d.ts +831 -0
- package/dist/services/foundry/service.js +442 -0
- package/dist/services/foundry-progress/service.d.ts +62 -0
- package/dist/services/foundry-progress/service.js +149 -0
- package/dist/services/foundry-v2/service.d.ts +54 -0
- package/dist/services/foundry-v2/service.js +28 -0
- package/dist/services/grounded-authoring/service.d.ts +126 -0
- package/dist/services/grounded-authoring/service.js +822 -0
- package/dist/services/historical-case/service.d.ts +722 -0
- package/dist/services/historical-case/service.js +177 -0
- package/dist/services/improvement-loop/service.d.ts +59 -0
- package/dist/services/improvement-loop/service.js +176 -0
- package/dist/services/language-model/service.d.ts +52 -0
- package/dist/services/language-model/service.js +194 -0
- package/dist/services/oracle-builder/service.d.ts +146 -0
- package/dist/services/oracle-builder/service.js +513 -0
- package/dist/services/oracle-coverage/service.d.ts +28 -0
- package/dist/services/oracle-coverage/service.js +50 -0
- package/dist/services/oracle-coverage-witness/service.d.ts +130 -0
- package/dist/services/oracle-coverage-witness/service.js +538 -0
- package/dist/services/pipeline-challenge/service.d.ts +551 -0
- package/dist/services/pipeline-challenge/service.js +427 -0
- package/dist/services/pipeline-controls/service.d.ts +130 -0
- package/dist/services/pipeline-controls/service.js +483 -0
- package/dist/services/pipeline-oracle/service.d.ts +8 -0
- package/dist/services/pipeline-oracle/service.js +256 -0
- package/dist/services/pipeline-seed/service.d.ts +298 -0
- package/dist/services/pipeline-seed/service.js +428 -0
- package/dist/services/pipeline-spec/service.d.ts +103 -0
- package/dist/services/pipeline-spec/service.js +619 -0
- package/dist/services/pipeline-tournament/service.d.ts +258 -0
- package/dist/services/pipeline-tournament/service.js +476 -0
- package/dist/services/quality-gate/service.d.ts +233 -0
- package/dist/services/quality-gate/service.js +136 -0
- package/dist/services/repository-bundle/service.d.ts +33 -0
- package/dist/services/repository-bundle/service.js +114 -0
- package/dist/services/repository-model/service.d.ts +105 -0
- package/dist/services/repository-model/service.js +250 -0
- package/dist/services/repository-public-artifact/service.d.ts +133 -0
- package/dist/services/repository-public-artifact/service.js +330 -0
- package/dist/services/routing-benchmark/service.d.ts +362 -0
- package/dist/services/routing-benchmark/service.js +96 -0
- package/dist/services/specification-critic/service.d.ts +92 -0
- package/dist/services/specification-critic/service.js +172 -0
- package/dist/services/task-family/service.d.ts +40 -0
- package/dist/services/task-family/service.js +55 -0
- package/dist/services/task-seed/service.d.ts +906 -0
- package/dist/services/task-seed/service.js +1406 -0
- package/dist/services/task-specification/service.d.ts +27 -0
- package/dist/services/task-specification/service.js +40 -0
- package/dist/services/trajectory-policy/service.d.ts +110 -0
- package/dist/services/trajectory-policy/service.js +216 -0
- package/dist/test/agentic-capabilities-protocol.test.d.ts +1 -0
- package/dist/test/agentic-capabilities-protocol.test.js +570 -0
- package/dist/test/agentic-capabilities.test.d.ts +1 -0
- package/dist/test/agentic-capabilities.test.js +1461 -0
- package/dist/test/agentic-environment.test.d.ts +1 -0
- package/dist/test/agentic-environment.test.js +213 -0
- package/dist/test/case-pipeline-foundation.test.d.ts +1 -0
- package/dist/test/case-pipeline-foundation.test.js +535 -0
- package/dist/test/case-pipeline-protocol-v2.test.d.ts +1 -0
- package/dist/test/case-pipeline-protocol-v2.test.js +124 -0
- package/dist/test/case-pipeline-v2.test.d.ts +1 -0
- package/dist/test/case-pipeline-v2.test.js +286 -0
- package/dist/test/case-pipeline.test.d.ts +1 -0
- package/dist/test/case-pipeline.test.js +851 -0
- package/dist/test/eval-capability-policy.test.d.ts +1 -0
- package/dist/test/eval-capability-policy.test.js +50 -0
- package/dist/test/eval-event-log.test.d.ts +1 -0
- package/dist/test/eval-event-log.test.js +125 -0
- package/dist/test/evaluation-evidence-freshness.test.d.ts +1 -0
- package/dist/test/evaluation-evidence-freshness.test.js +44 -0
- package/dist/test/evaluation-evidence.test.d.ts +1 -0
- package/dist/test/evaluation-evidence.test.js +230 -0
- package/dist/test/evaluation-grader-calibration.test.d.ts +1 -0
- package/dist/test/evaluation-grader-calibration.test.js +373 -0
- package/dist/test/evaluation-proposal-digest.test.d.ts +1 -0
- package/dist/test/evaluation-proposal-digest.test.js +187 -0
- package/dist/test/evaluation-source-retrieval.test.d.ts +1 -0
- package/dist/test/evaluation-source-retrieval.test.js +237 -0
- package/dist/test/evaluation-structure-policy.test.d.ts +1 -0
- package/dist/test/evaluation-structure-policy.test.js +196 -0
- package/dist/test/fixtures/repository-resource-panel.d.ts +39 -0
- package/dist/test/fixtures/repository-resource-panel.js +111 -0
- package/dist/test/fixtures/vitest-boundary-panel.d.ts +84 -0
- package/dist/test/fixtures/vitest-boundary-panel.js +120 -0
- package/dist/test/fixtures/vitest-phase-panel.d.ts +135 -0
- package/dist/test/fixtures/vitest-phase-panel.js +213 -0
- package/dist/test/fixtures/vitest-reporter-results.d.ts +76 -0
- package/dist/test/fixtures/vitest-reporter-results.js +94 -0
- package/dist/test/grounded-authoring.test.d.ts +1 -0
- package/dist/test/grounded-authoring.test.js +565 -0
- package/dist/test/integrated-repository-history.test.d.ts +1 -0
- package/dist/test/integrated-repository-history.test.js +227 -0
- package/dist/test/project-authoring.test.js +593 -43
- package/dist/test/project-workflow.test.js +419 -40
- package/dist/test/repository-authoring-artifacts.test.d.ts +1 -0
- package/dist/test/repository-authoring-artifacts.test.js +185 -0
- package/dist/test/repository-bundle.test.d.ts +1 -0
- package/dist/test/repository-bundle.test.js +52 -0
- package/dist/test/repository-case-generation.test.d.ts +1 -0
- package/dist/test/repository-case-generation.test.js +3465 -0
- package/dist/test/repository-command-diagnostic.test.d.ts +1 -0
- package/dist/test/repository-command-diagnostic.test.js +55 -0
- package/dist/test/repository-command-signals.test.d.ts +1 -0
- package/dist/test/repository-command-signals.test.js +124 -0
- package/dist/test/repository-fixture-scope-coverage.test.d.ts +1 -0
- package/dist/test/repository-fixture-scope-coverage.test.js +127 -0
- package/dist/test/repository-fixture-validation.test.d.ts +1 -0
- package/dist/test/repository-fixture-validation.test.js +362 -0
- package/dist/test/repository-foundry-progress.test.d.ts +1 -0
- package/dist/test/repository-foundry-progress.test.js +110 -0
- package/dist/test/repository-foundry-quality.test.d.ts +1 -0
- package/dist/test/repository-foundry-quality.test.js +1138 -0
- package/dist/test/repository-import-context.test.d.ts +1 -0
- package/dist/test/repository-import-context.test.js +354 -0
- package/dist/test/repository-model-authoring.test.d.ts +1 -0
- package/dist/test/repository-model-authoring.test.js +544 -0
- package/dist/test/repository-model.test.d.ts +1 -0
- package/dist/test/repository-model.test.js +2195 -0
- package/dist/test/repository-node-test-reporter.test.d.ts +1 -0
- package/dist/test/repository-node-test-reporter.test.js +104 -0
- package/dist/test/repository-oracle-concurrency.test.d.ts +1 -0
- package/dist/test/repository-oracle-concurrency.test.js +542 -0
- package/dist/test/repository-oracle-coverage-witness.test.d.ts +1 -0
- package/dist/test/repository-oracle-coverage-witness.test.js +511 -0
- package/dist/test/repository-oracle-coverage.test.d.ts +1 -0
- package/dist/test/repository-oracle-coverage.test.js +168 -0
- package/dist/test/repository-oracle-evidence.test.d.ts +1 -0
- package/dist/test/repository-oracle-evidence.test.js +185 -0
- package/dist/test/repository-oracle-plan.test.d.ts +1 -0
- package/dist/test/repository-oracle-plan.test.js +176 -0
- package/dist/test/repository-overlay-isolation.test.d.ts +1 -0
- package/dist/test/repository-overlay-isolation.test.js +85 -0
- package/dist/test/repository-preparation-cache.test.d.ts +1 -0
- package/dist/test/repository-preparation-cache.test.js +414 -0
- package/dist/test/repository-public-artifact.test.d.ts +1 -0
- package/dist/test/repository-public-artifact.test.js +273 -0
- package/dist/test/repository-qualification-diagnostics.test.d.ts +1 -0
- package/dist/test/repository-qualification-diagnostics.test.js +524 -0
- package/dist/test/repository-reference-authoring.test.d.ts +1 -0
- package/dist/test/repository-reference-authoring.test.js +1633 -0
- package/dist/test/repository-review-evidence-v2.test.d.ts +1 -0
- package/dist/test/repository-review-evidence-v2.test.js +183 -0
- package/dist/test/repository-review-evidence.test.d.ts +1 -0
- package/dist/test/repository-review-evidence.test.js +124 -0
- package/dist/test/repository-seed-exclusions.test.d.ts +1 -0
- package/dist/test/repository-seed-exclusions.test.js +96 -0
- package/dist/test/repository-seed-selection.test.d.ts +1 -0
- package/dist/test/repository-seed-selection.test.js +504 -0
- package/dist/test/repository-semantic-calibration.test.d.ts +1 -0
- package/dist/test/repository-semantic-calibration.test.js +688 -0
- package/dist/test/repository-solution-edits.test.d.ts +1 -0
- package/dist/test/repository-solution-edits.test.js +377 -0
- package/dist/test/repository-specification-budget.test.d.ts +1 -0
- package/dist/test/repository-specification-budget.test.js +171 -0
- package/dist/test/repository-specification-contract-checkpoint.test.d.ts +1 -0
- package/dist/test/repository-specification-contract-checkpoint.test.js +228 -0
- package/dist/test/repository-specification-contract-facts.test.d.ts +1 -0
- package/dist/test/repository-specification-contract-facts.test.js +177 -0
- package/dist/test/repository-trajectory-authoring.test.d.ts +1 -0
- package/dist/test/repository-trajectory-authoring.test.js +176 -0
- package/dist/test/repository-valid-control-plan.test.d.ts +1 -0
- package/dist/test/repository-valid-control-plan.test.js +45 -0
- package/dist/test/repository-vitest-phase.test.d.ts +1 -0
- package/dist/test/repository-vitest-phase.test.js +848 -0
- package/dist/test/repository-vitest-reporter.test.d.ts +1 -0
- package/dist/test/repository-vitest-reporter.test.js +158 -0
- package/dist/test/repository-workspace-build.test.d.ts +1 -0
- package/dist/test/repository-workspace-build.test.js +160 -0
- package/dist/test/strict-authoring-schema.test.d.ts +1 -0
- package/dist/test/strict-authoring-schema.test.js +169 -0
- package/package.json +48 -6
|
@@ -0,0 +1,1406 @@
|
|
|
1
|
+
import { posix as path } from "node:path";
|
|
2
|
+
import { Clock, Context, Effect, Layer } from "effect";
|
|
3
|
+
import { captureGitTreeSnapshotV1, readGitTreeFileV1 } from "../../adapters/git-task-history.js";
|
|
4
|
+
import { repositoryCommandTestEvidenceV1 } from "../../adapters/repository-command-evidence.js";
|
|
5
|
+
import { encodeRepositoryCommandDiagnosticV1, sanitizeRepositoryCommandDiagnosticV1 } from "../../adapters/repository-command-diagnostic.js";
|
|
6
|
+
import { makeRepositoryPreparationCacheV1, replayRepositoryCommandsV1 } from "../../adapters/repository-command-runner.js";
|
|
7
|
+
import { selectTypeScriptWorkspaceManifestPathsV1 } from "../../adapters/typescript-repository-index.js";
|
|
8
|
+
import { RepositoryFoundryError } from "../../errors.js";
|
|
9
|
+
import { assertRepositoryHiddenFixtureSuiteV1 } from "../../repository-fixture-protocol.js";
|
|
10
|
+
import { assertRepositorySeedCrossBatchQualificationV1, assertRepositoryTaskSeedV1 } from "../../repository-task-seed-protocol.js";
|
|
11
|
+
import { RepositoryFoundryEvidenceReconstruction } from "../evidence-reconstruction/service.js";
|
|
12
|
+
import { reportFoundryAuthoringArtifactV1, reportFoundryHeartbeatV1 } from "../foundry-progress/service.js";
|
|
13
|
+
const failure = (detail) => new RepositoryFoundryError({
|
|
14
|
+
operation: "extract-task-seeds",
|
|
15
|
+
detail
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Qualification and oracle admission consume the same executed test evidence.
|
|
19
|
+
* Passing baseline/reference builds cannot turn an unexecuted pre-change grader
|
|
20
|
+
* into a behavioral failure, even when an overlay imports a newly added API.
|
|
21
|
+
*/
|
|
22
|
+
const qualificationExecutionBlocked = (result) => repositoryCommandTestEvidenceV1(result, result.recipeId).outcome === "unknown";
|
|
23
|
+
const qualificationExecutionFailure = (phase, result) => {
|
|
24
|
+
const diagnostic = sanitizeRepositoryCommandDiagnosticV1(result.stdout, result.stderr, result.outputTruncated);
|
|
25
|
+
return new RepositoryFoundryError({
|
|
26
|
+
operation: "extract-task-seeds",
|
|
27
|
+
classification: "environment_blocked",
|
|
28
|
+
detail: `historical qualification stopped after operational failure prevented grading: phase=${phase}; stage=${result.stage}; recipe=${result.recipeId}; exit=${String(result.exitCode)}; timeout=${String(result.timedOut)}; outputTruncated=${String(result.outputTruncated)}${diagnostic === undefined ? "" :
|
|
29
|
+
`; diagnostic=${encodeRepositoryCommandDiagnosticV1(diagnostic.text)}; diagnosticTruncated=${String(diagnostic.truncated)}`}; evidence=${repositoryCommandTestEvidenceV1(result, result.recipeId).detail}; unstarted phases skipped; later batches were not started. Qualification requires executable reviewed tests on both revisions and attributable pre-change assertion failures. Repair the execution environment or select another candidate; repeating this unchanged plan cannot establish qualification.`
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const hiddenQualificationOverlays = Effect.fnUntraced(function* (seed, fixture) {
|
|
33
|
+
const hiddenTestOverlays = seed.capabilityEvidence
|
|
34
|
+
.filter((evidence) => evidence.kind === "test" && evidence.path !== undefined)
|
|
35
|
+
.map((evidence) => ({
|
|
36
|
+
path: evidence.path,
|
|
37
|
+
sourceCommit: seed.referenceCommit
|
|
38
|
+
}));
|
|
39
|
+
const hiddenContentOverlays = fixture?.overlays.map((overlay) => ({
|
|
40
|
+
path: overlay.path,
|
|
41
|
+
content: overlay.content
|
|
42
|
+
})) ?? [];
|
|
43
|
+
if (fixture !== undefined) {
|
|
44
|
+
assertRepositoryHiddenFixtureSuiteV1(fixture);
|
|
45
|
+
const reviewedTestPaths = new Set(seed.capabilityEvidence.flatMap((evidence) => evidence.kind === "test" && evidence.path !== undefined ? [evidence.path] : []));
|
|
46
|
+
const outOfScopePath = fixture.overlays.find((overlay) => !reviewedTestPaths.has(overlay.path))?.path;
|
|
47
|
+
if (outOfScopePath !== undefined) {
|
|
48
|
+
return yield* failure(`qualification fixture targets a path outside the reviewed test scope: ${outOfScopePath}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { hiddenTestOverlays, hiddenContentOverlays };
|
|
52
|
+
});
|
|
53
|
+
const packageRoot = (map, paths) => [...map.packages]
|
|
54
|
+
.filter((entry) => paths.some((path) => entry.path === "." || path === entry.path || path.startsWith(`${entry.path}/`)))
|
|
55
|
+
.sort((left, right) => right.path.length - left.path.length)[0]?.path;
|
|
56
|
+
const commandsFor = (map, testPaths, changedPaths) => {
|
|
57
|
+
const commandIds = new Set(map.tests.filter((test) => testPaths.includes(test.path)).flatMap((test) => test.commandIds));
|
|
58
|
+
const bound = map.commands
|
|
59
|
+
.filter((command) => command.kind === "test" && commandIds.has(command.id))
|
|
60
|
+
.sort((left, right) => left.cwd.localeCompare(right.cwd) || left.id.localeCompare(right.id));
|
|
61
|
+
if (bound.length > 0) {
|
|
62
|
+
const scoped = bound.filter((command) => command.cwd !== ".");
|
|
63
|
+
// A root workspace test command is a broad fallback, not an additional
|
|
64
|
+
// package-level grade. Keeping it beside specifically bound package tests
|
|
65
|
+
// expands replay preparation to unrelated deployment applications (and
|
|
66
|
+
// their environment-only builds), making a focused historical seed depend
|
|
67
|
+
// on credentials that are intentionally unavailable in the sandbox.
|
|
68
|
+
return scoped.length > 0 ? scoped : bound;
|
|
69
|
+
}
|
|
70
|
+
const root = packageRoot(map, changedPaths);
|
|
71
|
+
const fallback = map.commands.find((command) => command.kind === "test" && command.cwd === root);
|
|
72
|
+
return fallback === undefined ? [] : [fallback];
|
|
73
|
+
};
|
|
74
|
+
const manifestPathFor = (cwd) => cwd === "." ? "package.json" : `${cwd}/package.json`;
|
|
75
|
+
const manifestRootFor = (path) => path === "package.json" ? "." : path.slice(0, -"/package.json".length);
|
|
76
|
+
const recipeSafeId = (value) => value
|
|
77
|
+
.replace(/^@/u, "")
|
|
78
|
+
.replace(/[^A-Za-z0-9]+/gu, "-")
|
|
79
|
+
.replace(/^-|-$/gu, "");
|
|
80
|
+
/**
|
|
81
|
+
* Historical qualification replays one reviewed grade at a time inside a
|
|
82
|
+
* ten-minute resumable capability slice. Package discovery's two-minute test
|
|
83
|
+
* default is appropriate for a quick repository probe, but it is too short
|
|
84
|
+
* for a focused test file that legitimately contains several integration
|
|
85
|
+
* cases. Leave two minutes in the slice for preparation, durable checkpoint
|
|
86
|
+
* persistence, and command teardown.
|
|
87
|
+
*/
|
|
88
|
+
const HISTORICAL_QUALIFICATION_GRADE_TIMEOUT_MS_V1 = 8 * 60_000;
|
|
89
|
+
const qualificationGradeRecipe = (recipe) => recipe.kind === "test"
|
|
90
|
+
? {
|
|
91
|
+
...recipe,
|
|
92
|
+
timeoutMs: Math.max(recipe.timeoutMs, HISTORICAL_QUALIFICATION_GRADE_TIMEOUT_MS_V1)
|
|
93
|
+
}
|
|
94
|
+
: recipe;
|
|
95
|
+
const recipeReadsEmittedArtifacts = (recipe) => recipe.args.some((argument) => /(?:^|[/\\])dist(?:[/\\]|$)/u.test(argument));
|
|
96
|
+
const candidateValidationScript = (input) => {
|
|
97
|
+
const build = input.scripts.build;
|
|
98
|
+
if (typeof build !== "string")
|
|
99
|
+
return undefined;
|
|
100
|
+
if (!input.targetRoots.has(input.root) ||
|
|
101
|
+
!/\bnext\s+build\b/u.test(build) ||
|
|
102
|
+
input.gradeRecipes.some((recipe) => recipe.cwd === input.root && recipeReadsEmittedArtifacts(recipe))) {
|
|
103
|
+
return "build";
|
|
104
|
+
}
|
|
105
|
+
return (["types:check", "typecheck"].find((script) => typeof input.scripts[script] === "string") ??
|
|
106
|
+
"build");
|
|
107
|
+
};
|
|
108
|
+
const compiledTestExtensions = {
|
|
109
|
+
".ts": ".js",
|
|
110
|
+
".tsx": ".js",
|
|
111
|
+
".mts": ".mjs",
|
|
112
|
+
".cts": ".cjs"
|
|
113
|
+
};
|
|
114
|
+
const compiledNodeTestPath = (relativeTestPath) => {
|
|
115
|
+
if (!relativeTestPath.startsWith("src/test/"))
|
|
116
|
+
return undefined;
|
|
117
|
+
const extension = path.extname(relativeTestPath);
|
|
118
|
+
const outputExtension = compiledTestExtensions[extension];
|
|
119
|
+
if (outputExtension === undefined)
|
|
120
|
+
return undefined;
|
|
121
|
+
return `dist/${relativeTestPath.slice("src/".length, -extension.length)}${outputExtension}`;
|
|
122
|
+
};
|
|
123
|
+
const historicalTestScript = (manifest) => {
|
|
124
|
+
if (manifest?.scripts === null || typeof manifest?.scripts !== "object")
|
|
125
|
+
return undefined;
|
|
126
|
+
const test = manifest.scripts.test;
|
|
127
|
+
return typeof test === "string" ? test : undefined;
|
|
128
|
+
};
|
|
129
|
+
const recognizedPnpmTestRunner = (testScript) => {
|
|
130
|
+
if (testScript === undefined)
|
|
131
|
+
return undefined;
|
|
132
|
+
const normalized = testScript.trim().replace(/\s+/gu, " ");
|
|
133
|
+
if (normalized === "vp test run") {
|
|
134
|
+
return { argv: ["vp", "test", "run"] };
|
|
135
|
+
}
|
|
136
|
+
if (normalized === "pnpm vitest run") {
|
|
137
|
+
return {
|
|
138
|
+
argv: ["vitest", "run"],
|
|
139
|
+
runFromRepositoryRoot: true
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (normalized === "vitest" || normalized === "vitest run") {
|
|
143
|
+
return { argv: ["vitest", "run"] };
|
|
144
|
+
}
|
|
145
|
+
if (!normalized.startsWith("node --test") && !normalized.startsWith("tsx --test")) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const concurrencyFlag = /^(?:node|tsx) --test (--test-concurrency=[1-9]\d{0,2})(?= |$)/u.exec(normalized)?.[1];
|
|
149
|
+
const suiteScript = concurrencyFlag === undefined ? normalized : normalized.replace(` ${concurrencyFlag}`, "");
|
|
150
|
+
const nodeRunner = normalized.startsWith("tsx ") ? "tsx" : "node";
|
|
151
|
+
const compiledSuite = /^(?:node|tsx) --test (?:"|')?dist\/test\/\*\.test\.js(?:"|')?$/u.test(suiteScript);
|
|
152
|
+
const sourceSuite = suiteScript === "node --test" ||
|
|
153
|
+
suiteScript === "tsx --test" ||
|
|
154
|
+
/^(?:node|tsx) --test (?!["']?-)(?:[\w./*?{}-]+|"[\w./*?{}-]+"|'[\w./*?{}-]+')(?: (?!["']?-)(?:[\w./*?{}-]+|"[\w./*?{}-]+"|'[\w./*?{}-]+'))*$/u.test(suiteScript);
|
|
155
|
+
if (!compiledSuite && !sourceSuite)
|
|
156
|
+
return undefined;
|
|
157
|
+
return {
|
|
158
|
+
argv: concurrencyFlag === undefined
|
|
159
|
+
? [nodeRunner, "--test"]
|
|
160
|
+
: [nodeRunner, "--test", concurrencyFlag],
|
|
161
|
+
compiledTests: compiledSuite ? true : undefined
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
const relativeRecipeTestPaths = (input) => input.testPaths
|
|
165
|
+
.filter((testPath) => input.recipe.cwd === "." || testPath.startsWith(`${input.recipe.cwd}/`))
|
|
166
|
+
.map((testPath) => input.recipe.cwd === "." ? testPath : path.relative(input.recipe.cwd, testPath))
|
|
167
|
+
.filter((testPath) => testPath.length > 0 && testPath !== ".." && !testPath.startsWith("../"))
|
|
168
|
+
.sort();
|
|
169
|
+
const targetedPnpmTestRecipe = (input) => {
|
|
170
|
+
const testPaths = input.runner.runFromRepositoryRoot
|
|
171
|
+
? [...input.testPaths].sort()
|
|
172
|
+
: relativeRecipeTestPaths(input);
|
|
173
|
+
if (testPaths.length === 0)
|
|
174
|
+
return undefined;
|
|
175
|
+
const projectedTestPaths = input.runner.compiledTests
|
|
176
|
+
? testPaths.map(compiledNodeTestPath)
|
|
177
|
+
: testPaths;
|
|
178
|
+
if (!projectedTestPaths.every((testPath) => testPath !== undefined)) {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
let executable = "node";
|
|
182
|
+
let args = [...input.runner.argv.slice(1), ...projectedTestPaths];
|
|
183
|
+
if (input.packageManagerVersion !== undefined) {
|
|
184
|
+
executable = "corepack";
|
|
185
|
+
args = [
|
|
186
|
+
`pnpm@${input.packageManagerVersion}`,
|
|
187
|
+
"exec",
|
|
188
|
+
...input.runner.argv,
|
|
189
|
+
...projectedTestPaths
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
return qualificationGradeRecipe({
|
|
193
|
+
...input.recipe,
|
|
194
|
+
id: input.id,
|
|
195
|
+
cwd: input.runner.runFromRepositoryRoot ? "." : input.recipe.cwd,
|
|
196
|
+
executable,
|
|
197
|
+
args
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
const narrowPnpmTestRecipe = (input) => {
|
|
201
|
+
if (input.recipe.kind !== "test") {
|
|
202
|
+
return input.recipe;
|
|
203
|
+
}
|
|
204
|
+
const runner = recognizedPnpmTestRunner(historicalTestScript(input.manifest));
|
|
205
|
+
if (runner === undefined)
|
|
206
|
+
return qualificationGradeRecipe(input.recipe);
|
|
207
|
+
if (input.packageManagerVersion === undefined && runner.argv[0] !== "node")
|
|
208
|
+
return qualificationGradeRecipe(input.recipe);
|
|
209
|
+
return targetedPnpmTestRecipe({
|
|
210
|
+
recipe: input.recipe,
|
|
211
|
+
id: `${input.recipe.id}-targeted`,
|
|
212
|
+
runner,
|
|
213
|
+
packageManagerVersion: input.packageManagerVersion,
|
|
214
|
+
testPaths: input.testPaths
|
|
215
|
+
}) ?? qualificationGradeRecipe(input.recipe);
|
|
216
|
+
};
|
|
217
|
+
const historicalBaselineRecipe = (input) => {
|
|
218
|
+
const baselineId = `${input.recipe.id}-baseline-control`;
|
|
219
|
+
const testScript = historicalTestScript(input.manifest);
|
|
220
|
+
const runner = recognizedPnpmTestRunner(testScript);
|
|
221
|
+
const baselineRecipe = qualificationGradeRecipe({ ...input.recipe, id: baselineId });
|
|
222
|
+
if (input.recipe.kind !== "test" ||
|
|
223
|
+
(input.packageManagerVersion === undefined && runner?.argv[0] !== "node") ||
|
|
224
|
+
testScript === undefined) {
|
|
225
|
+
return baselineRecipe;
|
|
226
|
+
}
|
|
227
|
+
if (runner !== undefined) {
|
|
228
|
+
const targeted = targetedPnpmTestRecipe({
|
|
229
|
+
recipe: input.recipe,
|
|
230
|
+
id: baselineId,
|
|
231
|
+
runner,
|
|
232
|
+
packageManagerVersion: input.packageManagerVersion,
|
|
233
|
+
testPaths: input.controlTestPaths
|
|
234
|
+
});
|
|
235
|
+
if (targeted !== undefined)
|
|
236
|
+
return targeted;
|
|
237
|
+
}
|
|
238
|
+
if (input.packageManagerVersion === undefined)
|
|
239
|
+
return baselineRecipe;
|
|
240
|
+
return qualificationGradeRecipe({
|
|
241
|
+
...input.recipe,
|
|
242
|
+
id: baselineId,
|
|
243
|
+
executable: "corepack",
|
|
244
|
+
args: [`pnpm@${input.packageManagerVersion}`, "run", "test"]
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
const repositoryTestPath = (value) => {
|
|
248
|
+
const lower = value.toLowerCase();
|
|
249
|
+
const base = lower.split("/").at(-1) ?? lower;
|
|
250
|
+
return (/\.(?:[cm]?[jt]sx?|py|go|rs)$/u.test(lower) &&
|
|
251
|
+
(/(?:^|[._-])(?:test|spec)(?:[._-]|$)/u.test(base) ||
|
|
252
|
+
/(^|\/)(?:test|tests|__tests__)(\/|$)/u.test(lower)));
|
|
253
|
+
};
|
|
254
|
+
const semanticTokens = (content) => {
|
|
255
|
+
const ignored = new Set([
|
|
256
|
+
"assert",
|
|
257
|
+
"async",
|
|
258
|
+
"await",
|
|
259
|
+
"boolean",
|
|
260
|
+
"catch",
|
|
261
|
+
"class",
|
|
262
|
+
"const",
|
|
263
|
+
"describe",
|
|
264
|
+
"effect",
|
|
265
|
+
"else",
|
|
266
|
+
"error",
|
|
267
|
+
"expect",
|
|
268
|
+
"export",
|
|
269
|
+
"false",
|
|
270
|
+
"finally",
|
|
271
|
+
"from",
|
|
272
|
+
"function",
|
|
273
|
+
"import",
|
|
274
|
+
"input",
|
|
275
|
+
"interface",
|
|
276
|
+
"length",
|
|
277
|
+
"message",
|
|
278
|
+
"never",
|
|
279
|
+
"node",
|
|
280
|
+
"null",
|
|
281
|
+
"number",
|
|
282
|
+
"object",
|
|
283
|
+
"process",
|
|
284
|
+
"readonly",
|
|
285
|
+
"return",
|
|
286
|
+
"shared",
|
|
287
|
+
"string",
|
|
288
|
+
"sync",
|
|
289
|
+
"test",
|
|
290
|
+
"t3tools",
|
|
291
|
+
"true",
|
|
292
|
+
"type",
|
|
293
|
+
"undefined",
|
|
294
|
+
"unknown",
|
|
295
|
+
"value",
|
|
296
|
+
"void",
|
|
297
|
+
"while",
|
|
298
|
+
"yield"
|
|
299
|
+
]);
|
|
300
|
+
return new Set([...content.matchAll(/[A-Za-z_$][A-Za-z0-9_$]*/gu)]
|
|
301
|
+
.map((match) => match[0].toLowerCase())
|
|
302
|
+
.filter((token) => token.length >= 3 && !ignored.has(token)));
|
|
303
|
+
};
|
|
304
|
+
const selectHistoricalBaselineControlPathsV1 = Effect.fn("TaskSeed.selectHistoricalBaselineControls")(function* (input) {
|
|
305
|
+
const withinRecipe = (candidate) => input.recipe.cwd === "." || candidate.startsWith(`${input.recipe.cwd}/`);
|
|
306
|
+
const changedSources = input.changedPaths.filter((candidate) => withinRecipe(candidate) &&
|
|
307
|
+
input.snapshot.files.includes(candidate) &&
|
|
308
|
+
!repositoryTestPath(candidate));
|
|
309
|
+
const existingChangedTests = input.testPaths.filter((candidate) => withinRecipe(candidate) &&
|
|
310
|
+
input.snapshot.files.includes(candidate) &&
|
|
311
|
+
repositoryTestPath(candidate));
|
|
312
|
+
if (existingChangedTests.length > 0) {
|
|
313
|
+
return [...new Set(existingChangedTests)].sort();
|
|
314
|
+
}
|
|
315
|
+
const allPackageTests = input.snapshot.files.filter((candidate) => withinRecipe(candidate) && repositoryTestPath(candidate));
|
|
316
|
+
if (allPackageTests.length === 0)
|
|
317
|
+
return [];
|
|
318
|
+
const changedDirectories = new Set(changedSources.map((candidate) => path.dirname(candidate)));
|
|
319
|
+
const sameDirectoryTests = allPackageTests.filter((candidate) => changedDirectories.has(path.dirname(candidate)));
|
|
320
|
+
const candidates = sameDirectoryTests.length > 0 ? sameDirectoryTests : allPackageTests;
|
|
321
|
+
const changedContents = yield* Effect.forEach(changedSources, (candidate) => readGitTreeFileV1({
|
|
322
|
+
snapshot: input.snapshot,
|
|
323
|
+
path: candidate
|
|
324
|
+
}));
|
|
325
|
+
const sourceTokens = semanticTokens(changedContents.join("\n"));
|
|
326
|
+
const candidateContents = yield* Effect.forEach(candidates, (candidate) => readGitTreeFileV1({
|
|
327
|
+
snapshot: input.snapshot,
|
|
328
|
+
path: candidate
|
|
329
|
+
}).pipe(Effect.map((content) => ({
|
|
330
|
+
path: candidate,
|
|
331
|
+
content,
|
|
332
|
+
tokens: semanticTokens(content)
|
|
333
|
+
}))));
|
|
334
|
+
const documentFrequency = new Map();
|
|
335
|
+
for (const candidate of candidateContents) {
|
|
336
|
+
for (const token of candidate.tokens) {
|
|
337
|
+
documentFrequency.set(token, (documentFrequency.get(token) ?? 0) + 1);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const score = (candidate) => {
|
|
341
|
+
const semantic = [...sourceTokens]
|
|
342
|
+
.filter((token) => candidate.tokens.has(token))
|
|
343
|
+
.reduce((total, token) => total +
|
|
344
|
+
Math.log((candidateContents.length + 1) / ((documentFrequency.get(token) ?? 0) + 1)) +
|
|
345
|
+
1, 0);
|
|
346
|
+
return semantic / Math.sqrt(Math.max(1, candidate.content.length));
|
|
347
|
+
};
|
|
348
|
+
return candidateContents
|
|
349
|
+
.sort((left, right) => score(right) - score(left) ||
|
|
350
|
+
left.content.length - right.content.length ||
|
|
351
|
+
left.path.localeCompare(right.path))
|
|
352
|
+
.slice(0, 1)
|
|
353
|
+
.map((candidate) => candidate.path);
|
|
354
|
+
});
|
|
355
|
+
const readJsonObject = Effect.fn("TaskSeed.readEnvironmentManifest")(function* (input) {
|
|
356
|
+
if (!input.snapshot.files.includes(input.path))
|
|
357
|
+
return undefined;
|
|
358
|
+
const content = yield* readGitTreeFileV1(input);
|
|
359
|
+
return yield* Effect.try({
|
|
360
|
+
try: () => JSON.parse(content),
|
|
361
|
+
catch: () => failure(`historical environment manifest is invalid JSON: ${input.path}`)
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
const stripJsonComments = (content) => {
|
|
365
|
+
let result = "";
|
|
366
|
+
let inString = false;
|
|
367
|
+
let escaped = false;
|
|
368
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
369
|
+
const character = content[index];
|
|
370
|
+
const next = content[index + 1];
|
|
371
|
+
if (inString) {
|
|
372
|
+
result += character;
|
|
373
|
+
if (escaped) {
|
|
374
|
+
escaped = false;
|
|
375
|
+
}
|
|
376
|
+
else if (character === "\\") {
|
|
377
|
+
escaped = true;
|
|
378
|
+
}
|
|
379
|
+
else if (character === '"') {
|
|
380
|
+
inString = false;
|
|
381
|
+
}
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (character === '"') {
|
|
385
|
+
inString = true;
|
|
386
|
+
result += character;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (character === "/" && next === "/") {
|
|
390
|
+
result += " ";
|
|
391
|
+
index += 1;
|
|
392
|
+
while (index + 1 < content.length && content[index + 1] !== "\n") {
|
|
393
|
+
result += " ";
|
|
394
|
+
index += 1;
|
|
395
|
+
}
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (character === "/" && next === "*") {
|
|
399
|
+
result += " ";
|
|
400
|
+
index += 1;
|
|
401
|
+
let closed = false;
|
|
402
|
+
while (index + 1 < content.length) {
|
|
403
|
+
const commentCharacter = content[index + 1];
|
|
404
|
+
const commentNext = content[index + 2];
|
|
405
|
+
if (commentCharacter === "*" && commentNext === "/") {
|
|
406
|
+
result += " ";
|
|
407
|
+
index += 2;
|
|
408
|
+
closed = true;
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
result += commentCharacter === "\n" ? "\n" : " ";
|
|
412
|
+
index += 1;
|
|
413
|
+
}
|
|
414
|
+
if (!closed) {
|
|
415
|
+
throw new Error("unterminated block comment");
|
|
416
|
+
}
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
result += character;
|
|
420
|
+
}
|
|
421
|
+
return result;
|
|
422
|
+
};
|
|
423
|
+
const stripTrailingJsonCommas = (content) => {
|
|
424
|
+
let result = "";
|
|
425
|
+
let inString = false;
|
|
426
|
+
let escaped = false;
|
|
427
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
428
|
+
const character = content[index];
|
|
429
|
+
if (inString) {
|
|
430
|
+
result += character;
|
|
431
|
+
if (escaped) {
|
|
432
|
+
escaped = false;
|
|
433
|
+
}
|
|
434
|
+
else if (character === "\\") {
|
|
435
|
+
escaped = true;
|
|
436
|
+
}
|
|
437
|
+
else if (character === '"') {
|
|
438
|
+
inString = false;
|
|
439
|
+
}
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (character === '"') {
|
|
443
|
+
inString = true;
|
|
444
|
+
result += character;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (character === ",") {
|
|
448
|
+
let lookahead = index + 1;
|
|
449
|
+
while (lookahead < content.length && /\s/u.test(content[lookahead])) {
|
|
450
|
+
lookahead += 1;
|
|
451
|
+
}
|
|
452
|
+
if (content[lookahead] === "]" || content[lookahead] === "}") {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
result += character;
|
|
457
|
+
}
|
|
458
|
+
return result;
|
|
459
|
+
};
|
|
460
|
+
const readJsoncObject = Effect.fn("TaskSeed.readTypeScriptProjectConfig")(function* (input) {
|
|
461
|
+
if (!input.snapshot.files.includes(input.path))
|
|
462
|
+
return undefined;
|
|
463
|
+
const content = yield* readGitTreeFileV1(input);
|
|
464
|
+
return yield* Effect.try({
|
|
465
|
+
try: () => JSON.parse(stripTrailingJsonCommas(stripJsonComments(content))),
|
|
466
|
+
catch: () => failure(`historical TypeScript project config is invalid JSONC: ${input.path}`)
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
const normalizeProjectReference = (configPath, referencePath) => {
|
|
470
|
+
const portableReference = referencePath.replaceAll("\\", "/").trim();
|
|
471
|
+
if (portableReference.length === 0 ||
|
|
472
|
+
portableReference.includes("\0") ||
|
|
473
|
+
path.isAbsolute(portableReference) ||
|
|
474
|
+
/^[A-Za-z]:\//u.test(portableReference)) {
|
|
475
|
+
throw failure(`historical TypeScript project reference is not repository-relative: ${configPath} -> ${JSON.stringify(referencePath)}`);
|
|
476
|
+
}
|
|
477
|
+
const resolved = path.normalize(path.join(path.dirname(configPath), portableReference));
|
|
478
|
+
if (resolved === ".." || resolved.startsWith("../")) {
|
|
479
|
+
throw failure(`historical TypeScript project reference escapes the repository: ${configPath} -> ${JSON.stringify(referencePath)}`);
|
|
480
|
+
}
|
|
481
|
+
return resolved.endsWith(".json") ? resolved : path.join(resolved, "tsconfig.json");
|
|
482
|
+
};
|
|
483
|
+
export const deriveHistoricalSeedEnvironmentV1 = Effect.fn("TaskSeed.deriveHistoricalEnvironment")(function* (input) {
|
|
484
|
+
const snapshot = yield* captureGitTreeSnapshotV1({
|
|
485
|
+
repositoryRoot: input.repositoryRoot,
|
|
486
|
+
requestedRef: input.commit
|
|
487
|
+
});
|
|
488
|
+
const { selectedManifestPaths } = yield* selectTypeScriptWorkspaceManifestPathsV1(snapshot);
|
|
489
|
+
const selectedManifestPathSet = new Set(selectedManifestPaths);
|
|
490
|
+
const rootManifest = yield* readJsonObject({
|
|
491
|
+
snapshot,
|
|
492
|
+
path: "package.json"
|
|
493
|
+
});
|
|
494
|
+
const targetManifests = yield* Effect.forEach(input.gradeRecipes, (gradeRecipe) => readJsonObject({
|
|
495
|
+
snapshot,
|
|
496
|
+
path: manifestPathFor(gradeRecipe.cwd)
|
|
497
|
+
}).pipe(Effect.map((manifest) => ({
|
|
498
|
+
root: gradeRecipe.cwd,
|
|
499
|
+
manifest
|
|
500
|
+
}))));
|
|
501
|
+
const packageManager = typeof rootManifest?.packageManager === "string"
|
|
502
|
+
? /^pnpm@((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?)$/u.exec(rootManifest.packageManager)
|
|
503
|
+
: null;
|
|
504
|
+
const lockfilePath = snapshot.files.includes("pnpm-lock.yaml") ? "pnpm-lock.yaml" : undefined;
|
|
505
|
+
const gradeRecipes = input.gradeRecipes.map((recipe) => narrowPnpmTestRecipe({
|
|
506
|
+
recipe,
|
|
507
|
+
manifest: targetManifests.find((target) => target.root === recipe.cwd)?.manifest,
|
|
508
|
+
packageManagerVersion: packageManager?.[1],
|
|
509
|
+
testPaths: input.testPaths
|
|
510
|
+
}));
|
|
511
|
+
const baselineGradeRecipes = yield* Effect.forEach(input.gradeRecipes, (recipe) => selectHistoricalBaselineControlPathsV1({
|
|
512
|
+
snapshot,
|
|
513
|
+
recipe,
|
|
514
|
+
changedPaths: input.changedPaths,
|
|
515
|
+
testPaths: input.testPaths
|
|
516
|
+
}).pipe(Effect.map((controlTestPaths) => historicalBaselineRecipe({
|
|
517
|
+
recipe,
|
|
518
|
+
manifest: targetManifests.find((target) => target.root === recipe.cwd)?.manifest,
|
|
519
|
+
packageManagerVersion: packageManager?.[1],
|
|
520
|
+
controlTestPaths
|
|
521
|
+
}))));
|
|
522
|
+
const trustedPreparationRecipes = [];
|
|
523
|
+
const candidateValidationRecipes = [];
|
|
524
|
+
const referencedProjectConfigPaths = new Set();
|
|
525
|
+
if (packageManager?.[1] !== undefined && lockfilePath !== undefined) {
|
|
526
|
+
const pnpm = `pnpm@${packageManager[1]}`;
|
|
527
|
+
const packageManifests = new Map();
|
|
528
|
+
for (const path of snapshot.files.filter((candidate) => selectedManifestPathSet.has(candidate))) {
|
|
529
|
+
const manifest = yield* readJsonObject({ snapshot, path });
|
|
530
|
+
if (typeof manifest?.name !== "string")
|
|
531
|
+
continue;
|
|
532
|
+
const manifestScripts = manifest.scripts !== null && typeof manifest.scripts === "object"
|
|
533
|
+
? manifest.scripts
|
|
534
|
+
: {};
|
|
535
|
+
const dependencies = [
|
|
536
|
+
manifest.dependencies,
|
|
537
|
+
manifest.devDependencies,
|
|
538
|
+
manifest.optionalDependencies,
|
|
539
|
+
manifest.peerDependencies
|
|
540
|
+
].flatMap((value) => value !== null && typeof value === "object"
|
|
541
|
+
? Object.keys(value)
|
|
542
|
+
: []);
|
|
543
|
+
packageManifests.set(manifest.name, {
|
|
544
|
+
root: manifestRootFor(path),
|
|
545
|
+
scripts: manifestScripts,
|
|
546
|
+
manifestDependencies: dependencies,
|
|
547
|
+
emitsArtifacts: /(?:^|[\/])(?:dist|build|lib)[\/]/u.test(JSON.stringify([manifest.main, manifest.module, manifest.exports]))
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
const packageByRoot = new Map([...packageManifests].map(([name, manifest]) => [manifest.root, name]));
|
|
551
|
+
const owningPackageName = (projectConfigPath) => [...packageByRoot]
|
|
552
|
+
.filter(([root]) => root === "."
|
|
553
|
+
? !projectConfigPath.includes("/")
|
|
554
|
+
: projectConfigPath === `${root}/tsconfig.json` ||
|
|
555
|
+
projectConfigPath.startsWith(`${root}/`))
|
|
556
|
+
.sort(([left], [right]) => right.length - left.length || left.localeCompare(right))[0]?.[1];
|
|
557
|
+
const projectReferences = new Map();
|
|
558
|
+
const loadProjectReferences = Effect.fn("TaskSeed.loadTypeScriptProjectReferences")(function* (configPath) {
|
|
559
|
+
const existing = projectReferences.get(configPath);
|
|
560
|
+
if (existing !== undefined)
|
|
561
|
+
return existing;
|
|
562
|
+
const config = yield* readJsoncObject({
|
|
563
|
+
snapshot,
|
|
564
|
+
path: configPath
|
|
565
|
+
});
|
|
566
|
+
if (config === undefined) {
|
|
567
|
+
return yield* failure(`historical TypeScript project reference target is missing: ${configPath}`);
|
|
568
|
+
}
|
|
569
|
+
if (config.references !== undefined && !Array.isArray(config.references)) {
|
|
570
|
+
return yield* failure(`historical TypeScript project references must be an array: ${configPath}`);
|
|
571
|
+
}
|
|
572
|
+
const references = [];
|
|
573
|
+
for (const [index, entry] of (config.references ?? []).entries()) {
|
|
574
|
+
if (entry === null ||
|
|
575
|
+
typeof entry !== "object" ||
|
|
576
|
+
typeof entry.path !== "string") {
|
|
577
|
+
return yield* failure(`historical TypeScript project reference ${String(index)} is malformed: ${configPath}`);
|
|
578
|
+
}
|
|
579
|
+
const referencedConfigPath = yield* Effect.try({
|
|
580
|
+
try: () => normalizeProjectReference(configPath, entry.path),
|
|
581
|
+
catch: (cause) => cause instanceof RepositoryFoundryError
|
|
582
|
+
? cause
|
|
583
|
+
: failure(`failed to resolve historical TypeScript project reference ${String(index)}: ${configPath}`)
|
|
584
|
+
});
|
|
585
|
+
if (!snapshot.files.includes(referencedConfigPath)) {
|
|
586
|
+
return yield* failure(`historical TypeScript project reference target is missing: ${configPath} -> ${referencedConfigPath}`);
|
|
587
|
+
}
|
|
588
|
+
references.push(referencedConfigPath);
|
|
589
|
+
}
|
|
590
|
+
const ordered = [...new Set(references)].sort();
|
|
591
|
+
projectReferences.set(configPath, ordered);
|
|
592
|
+
referencedProjectConfigPaths.add(configPath);
|
|
593
|
+
for (const reference of ordered) {
|
|
594
|
+
referencedProjectConfigPaths.add(reference);
|
|
595
|
+
}
|
|
596
|
+
return ordered;
|
|
597
|
+
});
|
|
598
|
+
const projectDependencies = new Map();
|
|
599
|
+
const visitedProjectConfigs = new Set();
|
|
600
|
+
const visitingProjectConfigs = [];
|
|
601
|
+
const visitProjectConfig = (configPath, inheritedPackageName) => Effect.gen(function* () {
|
|
602
|
+
const sourcePackageName = owningPackageName(configPath) ?? inheritedPackageName;
|
|
603
|
+
const visitKey = `${sourcePackageName}\0${configPath}`;
|
|
604
|
+
if (visitedProjectConfigs.has(visitKey))
|
|
605
|
+
return;
|
|
606
|
+
const cycleIndex = visitingProjectConfigs.indexOf(visitKey);
|
|
607
|
+
if (cycleIndex >= 0) {
|
|
608
|
+
return yield* failure(`historical TypeScript project references contain a cycle: ${[
|
|
609
|
+
...visitingProjectConfigs.slice(cycleIndex),
|
|
610
|
+
visitKey
|
|
611
|
+
]
|
|
612
|
+
.map((entry) => entry.split("\0")[1])
|
|
613
|
+
.join(" -> ")}`);
|
|
614
|
+
}
|
|
615
|
+
visitingProjectConfigs.push(visitKey);
|
|
616
|
+
const references = yield* loadProjectReferences(configPath);
|
|
617
|
+
for (const referencedConfigPath of references) {
|
|
618
|
+
const dependencyPackageName = owningPackageName(referencedConfigPath);
|
|
619
|
+
if (sourcePackageName !== undefined &&
|
|
620
|
+
dependencyPackageName !== undefined &&
|
|
621
|
+
sourcePackageName !== dependencyPackageName) {
|
|
622
|
+
const dependencies = projectDependencies.get(sourcePackageName) ?? new Set();
|
|
623
|
+
dependencies.add(dependencyPackageName);
|
|
624
|
+
projectDependencies.set(sourcePackageName, dependencies);
|
|
625
|
+
}
|
|
626
|
+
yield* visitProjectConfig(referencedConfigPath, dependencyPackageName ?? sourcePackageName);
|
|
627
|
+
}
|
|
628
|
+
visitingProjectConfigs.pop();
|
|
629
|
+
visitedProjectConfigs.add(visitKey);
|
|
630
|
+
});
|
|
631
|
+
const targetPackageNames = targetManifests.flatMap(({ manifest }) => typeof manifest?.name === "string" ? [manifest.name] : []);
|
|
632
|
+
const targetRoots = new Set(targetManifests.map(({ root }) => root));
|
|
633
|
+
const orderedPackageNames = [];
|
|
634
|
+
const visited = new Set();
|
|
635
|
+
const visiting = [];
|
|
636
|
+
const visit = (name) => Effect.gen(function* () {
|
|
637
|
+
if (visited.has(name))
|
|
638
|
+
return;
|
|
639
|
+
const cycleIndex = visiting.indexOf(name);
|
|
640
|
+
if (cycleIndex >= 0) {
|
|
641
|
+
return yield* failure(`historical workspace build graph contains a cycle: ${[
|
|
642
|
+
...visiting.slice(cycleIndex),
|
|
643
|
+
name
|
|
644
|
+
].join(" -> ")}`);
|
|
645
|
+
}
|
|
646
|
+
const manifest = packageManifests.get(name);
|
|
647
|
+
if (manifest === undefined)
|
|
648
|
+
return;
|
|
649
|
+
visiting.push(name);
|
|
650
|
+
const configPath = manifest.root === "." ? "tsconfig.json" : `${manifest.root}/tsconfig.json`;
|
|
651
|
+
if (snapshot.files.includes(configPath)) {
|
|
652
|
+
yield* visitProjectConfig(configPath, name);
|
|
653
|
+
}
|
|
654
|
+
const dependencies = [
|
|
655
|
+
...new Set([...manifest.manifestDependencies, ...(projectDependencies.get(name) ?? [])])
|
|
656
|
+
]
|
|
657
|
+
.filter((candidate) => packageManifests.has(candidate))
|
|
658
|
+
.sort();
|
|
659
|
+
for (const dependency of dependencies) {
|
|
660
|
+
yield* visit(dependency);
|
|
661
|
+
}
|
|
662
|
+
visiting.pop();
|
|
663
|
+
visited.add(name);
|
|
664
|
+
orderedPackageNames.push(name);
|
|
665
|
+
});
|
|
666
|
+
for (const name of [...new Set(targetPackageNames)].sort()) {
|
|
667
|
+
yield* visit(name);
|
|
668
|
+
}
|
|
669
|
+
// The topological traversal already includes both manifest dependencies and
|
|
670
|
+
// TypeScript references. Framework apps may have no project references,
|
|
671
|
+
// while their workspace dependencies still export generated build artifacts.
|
|
672
|
+
const validationPackageNames = new Set(orderedPackageNames);
|
|
673
|
+
trustedPreparationRecipes.push({
|
|
674
|
+
id: "command-environment-pnpm-frozen-install",
|
|
675
|
+
kind: "custom",
|
|
676
|
+
cwd: ".",
|
|
677
|
+
executable: "corepack",
|
|
678
|
+
args: [
|
|
679
|
+
pnpm,
|
|
680
|
+
...(input.executionMode === undefined
|
|
681
|
+
? []
|
|
682
|
+
: targetPackageNames.flatMap((name) => ["--filter", `${name}...`])),
|
|
683
|
+
"install",
|
|
684
|
+
"--prefer-offline",
|
|
685
|
+
"--frozen-lockfile",
|
|
686
|
+
"--ignore-pnpmfile",
|
|
687
|
+
"--ignore-scripts"
|
|
688
|
+
],
|
|
689
|
+
timeoutMs: 10 * 60_000
|
|
690
|
+
});
|
|
691
|
+
for (const name of orderedPackageNames) {
|
|
692
|
+
if (!validationPackageNames.has(name))
|
|
693
|
+
continue;
|
|
694
|
+
const manifest = packageManifests.get(name);
|
|
695
|
+
if (input.executionMode !== undefined) {
|
|
696
|
+
const target = targetRoots.has(manifest.root);
|
|
697
|
+
const targetNeedsBuild = input.executionMode === "emitted" ||
|
|
698
|
+
gradeRecipes.some((recipe) => recipe.cwd === manifest.root && recipeReadsEmittedArtifacts(recipe));
|
|
699
|
+
// Source-capable targets do not need their own compiler. Dependencies
|
|
700
|
+
// still build when their package surface resolves to emitted artifacts.
|
|
701
|
+
if (target ? !targetNeedsBuild : !manifest.emitsArtifacts)
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
const script = candidateValidationScript({
|
|
705
|
+
root: manifest.root,
|
|
706
|
+
scripts: manifest.scripts,
|
|
707
|
+
targetRoots,
|
|
708
|
+
gradeRecipes
|
|
709
|
+
});
|
|
710
|
+
if (script === undefined)
|
|
711
|
+
continue;
|
|
712
|
+
candidateValidationRecipes.push({
|
|
713
|
+
id: `command-environment-${recipeSafeId(script)}-${recipeSafeId(name)}`,
|
|
714
|
+
kind: "build",
|
|
715
|
+
cwd: manifest.root,
|
|
716
|
+
executable: "corepack",
|
|
717
|
+
args: [pnpm, "run", script],
|
|
718
|
+
timeoutMs: 10 * 60_000
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const environment = {
|
|
723
|
+
...(packageManager?.[1] === undefined || lockfilePath === undefined
|
|
724
|
+
? {}
|
|
725
|
+
: {
|
|
726
|
+
packageManager: {
|
|
727
|
+
name: "pnpm",
|
|
728
|
+
version: packageManager[1]
|
|
729
|
+
},
|
|
730
|
+
lockfilePath
|
|
731
|
+
}),
|
|
732
|
+
protectedControlPaths: [
|
|
733
|
+
...new Set([
|
|
734
|
+
"package.json",
|
|
735
|
+
"pnpm-lock.yaml",
|
|
736
|
+
"pnpm-workspace.yaml",
|
|
737
|
+
".npmrc",
|
|
738
|
+
"turbo.json",
|
|
739
|
+
"tsconfig.base.json",
|
|
740
|
+
...[
|
|
741
|
+
...input.gradeRecipes.map((recipe) => recipe.cwd),
|
|
742
|
+
...candidateValidationRecipes.map((recipe) => recipe.cwd)
|
|
743
|
+
].flatMap((cwd) => [
|
|
744
|
+
manifestPathFor(cwd),
|
|
745
|
+
cwd === "." ? "tsconfig.json" : `${cwd}/tsconfig.json`
|
|
746
|
+
]),
|
|
747
|
+
...referencedProjectConfigPaths
|
|
748
|
+
].filter((path) => snapshot.files.includes(path) && !input.changedPaths.includes(path)))
|
|
749
|
+
].sort(),
|
|
750
|
+
trustedPreparationRecipes,
|
|
751
|
+
candidateValidationRecipes,
|
|
752
|
+
baselineGradeRecipes,
|
|
753
|
+
gradeRecipes
|
|
754
|
+
};
|
|
755
|
+
return environment;
|
|
756
|
+
});
|
|
757
|
+
const historicalSourceKind = (subject) => {
|
|
758
|
+
if (/^(?:feat)(?:\([^)]+\))?[!:]/iu.test(subject) || /^(?:add|implement)\b/iu.test(subject)) {
|
|
759
|
+
return "historical-feature";
|
|
760
|
+
}
|
|
761
|
+
if (/^(?:refactor)(?:\([^)]+\))?[!:]/iu.test(subject) ||
|
|
762
|
+
/^(?:refactor|migrate|restructure)\b/iu.test(subject)) {
|
|
763
|
+
return "historical-refactor";
|
|
764
|
+
}
|
|
765
|
+
return "historical-fix";
|
|
766
|
+
};
|
|
767
|
+
export class TaskSeed extends Context.Service()("@velum-labs/routekit-eval-setup/TaskSeed") {
|
|
768
|
+
}
|
|
769
|
+
export const extractHistoricalTaskSeedsV1 = Effect.fn("TaskSeed.extractHistorical")(function* (map, options) {
|
|
770
|
+
const seeds = [];
|
|
771
|
+
for (const episode of map.historyEpisodes.filter((entry) => entry.seedEligible)) {
|
|
772
|
+
if (episode.parentCommit === undefined)
|
|
773
|
+
continue;
|
|
774
|
+
const gradeRecipes = commandsFor(map, episode.testPaths, [
|
|
775
|
+
...episode.implementationPaths,
|
|
776
|
+
...episode.protocolPaths,
|
|
777
|
+
...episode.testPaths
|
|
778
|
+
]);
|
|
779
|
+
if (gradeRecipes.length === 0) {
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
const environment = options?.deferEnvironment === true
|
|
783
|
+
? {
|
|
784
|
+
protectedControlPaths: [],
|
|
785
|
+
trustedPreparationRecipes: [],
|
|
786
|
+
candidateValidationRecipes: [],
|
|
787
|
+
baselineGradeRecipes: [],
|
|
788
|
+
gradeRecipes
|
|
789
|
+
}
|
|
790
|
+
: yield* deriveHistoricalSeedEnvironmentV1({
|
|
791
|
+
repositoryRoot: map.repository.root,
|
|
792
|
+
commit: episode.parentCommit,
|
|
793
|
+
gradeRecipes,
|
|
794
|
+
testPaths: episode.testPaths,
|
|
795
|
+
changedPaths: episode.changedPaths.flatMap(({ path, previousPath }) => previousPath === undefined ? [path] : [previousPath, path])
|
|
796
|
+
});
|
|
797
|
+
const testIds = map.tests
|
|
798
|
+
.filter((test) => episode.testPaths.includes(test.path))
|
|
799
|
+
.map((test) => test.id);
|
|
800
|
+
const changedProductionPaths = [...episode.implementationPaths, ...episode.protocolPaths];
|
|
801
|
+
const owningPackageIds = new Set(map.packages
|
|
802
|
+
.filter((entry) => changedProductionPaths.some((changedPath) => entry.path === "." ||
|
|
803
|
+
changedPath === entry.path ||
|
|
804
|
+
changedPath.startsWith(`${entry.path}/`)))
|
|
805
|
+
.map((entry) => entry.id));
|
|
806
|
+
const publicSurfaceEvidence = map.publicSurfaces
|
|
807
|
+
.filter((surface) => surface.packageId !== undefined &&
|
|
808
|
+
owningPackageIds.has(surface.packageId) &&
|
|
809
|
+
!episode.testPaths.includes(surface.path))
|
|
810
|
+
.map((surface) => ({
|
|
811
|
+
kind: "public-surface",
|
|
812
|
+
id: surface.id,
|
|
813
|
+
path: surface.path
|
|
814
|
+
}));
|
|
815
|
+
const targetEvidence = [
|
|
816
|
+
...episode.implementationPaths.map((path) => ({
|
|
817
|
+
kind: "symbol",
|
|
818
|
+
id: `path-${path}`,
|
|
819
|
+
path
|
|
820
|
+
})),
|
|
821
|
+
...episode.protocolPaths.map((path) => ({
|
|
822
|
+
kind: "protocol",
|
|
823
|
+
id: `path-${path}`,
|
|
824
|
+
path
|
|
825
|
+
})),
|
|
826
|
+
...episode.testPaths.map((path) => ({
|
|
827
|
+
kind: "test",
|
|
828
|
+
id: map.tests.find((test) => test.path === path)?.id ?? `path-${path}`,
|
|
829
|
+
path
|
|
830
|
+
})),
|
|
831
|
+
...publicSurfaceEvidence
|
|
832
|
+
];
|
|
833
|
+
const seed = {
|
|
834
|
+
version: 1,
|
|
835
|
+
id: `seed-${episode.commit}`,
|
|
836
|
+
status: "candidate",
|
|
837
|
+
source: {
|
|
838
|
+
kind: historicalSourceKind(episode.subject),
|
|
839
|
+
changeEpisodeId: episode.id
|
|
840
|
+
},
|
|
841
|
+
summary: episode.subject,
|
|
842
|
+
capabilityEvidence: [{ kind: "history-episode", id: episode.id }, ...targetEvidence],
|
|
843
|
+
initialState: { commit: episode.parentCommit },
|
|
844
|
+
targetBehavior: [
|
|
845
|
+
{
|
|
846
|
+
id: "historical-regression",
|
|
847
|
+
description: episode.subject.trim().length > 0
|
|
848
|
+
? episode.subject
|
|
849
|
+
: "Preserve the behavior established by the historical change.",
|
|
850
|
+
critical: true,
|
|
851
|
+
evidence: targetEvidence
|
|
852
|
+
}
|
|
853
|
+
],
|
|
854
|
+
referenceCommit: episode.commit,
|
|
855
|
+
baselineObservations: [],
|
|
856
|
+
preChangeObservations: [],
|
|
857
|
+
postChangeObservations: [],
|
|
858
|
+
candidateTestIds: testIds,
|
|
859
|
+
environment,
|
|
860
|
+
confidence: testIds.length > 0 ? "medium" : "review-required",
|
|
861
|
+
risks: ["review-required", ...(testIds.length > 0 ? [] : ["weak-test-delta"])],
|
|
862
|
+
rejectionReasons: []
|
|
863
|
+
};
|
|
864
|
+
// Source drafts intentionally have no selected baseline environment yet.
|
|
865
|
+
// Only legacy/executable seeds satisfy the stronger environment assertions.
|
|
866
|
+
if (options?.deferEnvironment !== true)
|
|
867
|
+
assertRepositoryTaskSeedV1(seed);
|
|
868
|
+
seeds.push(seed);
|
|
869
|
+
}
|
|
870
|
+
if (seeds.length === 0 && map.historyEpisodes.length === 0) {
|
|
871
|
+
return yield* failure("repository contains no historical episodes");
|
|
872
|
+
}
|
|
873
|
+
return seeds;
|
|
874
|
+
});
|
|
875
|
+
export const qualifyHistoricalTaskSeedV1 = Effect.fn("TaskSeed.qualifyHistorical")(function* (input) {
|
|
876
|
+
if ((input.seed.source.kind !== "historical-fix" &&
|
|
877
|
+
input.seed.source.kind !== "historical-feature" &&
|
|
878
|
+
input.seed.source.kind !== "historical-refactor") ||
|
|
879
|
+
input.seed.referenceCommit === undefined) {
|
|
880
|
+
return yield* failure("historical qualification requires a historical change seed with a reference commit");
|
|
881
|
+
}
|
|
882
|
+
const { hiddenTestOverlays, hiddenContentOverlays } = yield* hiddenQualificationOverlays(input.seed, input.hiddenFixtureSuite);
|
|
883
|
+
const operationalFailures = [];
|
|
884
|
+
const replayErrors = [];
|
|
885
|
+
const replayPhase = Effect.fnUntraced(function* (phase, replayInput) {
|
|
886
|
+
// Already-started phases finish normally. Only queued phases are skipped,
|
|
887
|
+
// so their sibling's failure cannot discard replay rows or cleanup work.
|
|
888
|
+
if (operationalFailures.length > 0 || replayErrors.length > 0)
|
|
889
|
+
return undefined;
|
|
890
|
+
const startedAt = yield* Clock.currentTimeMillis;
|
|
891
|
+
const replay = yield* replayRepositoryCommandsV1(replayInput).pipe(Effect.match({
|
|
892
|
+
onFailure: (error) => ({ results: [], error }),
|
|
893
|
+
onSuccess: (results) => ({ results, error: undefined })
|
|
894
|
+
}));
|
|
895
|
+
if (replay.error !== undefined)
|
|
896
|
+
replayErrors.push(replay.error);
|
|
897
|
+
for (const result of replay.results) {
|
|
898
|
+
if (qualificationExecutionBlocked(result)) {
|
|
899
|
+
const diagnostic = sanitizeRepositoryCommandDiagnosticV1(result.stdout, result.stderr, result.outputTruncated);
|
|
900
|
+
const failure = {
|
|
901
|
+
phase,
|
|
902
|
+
recipeId: result.recipeId,
|
|
903
|
+
repetition: result.repetition,
|
|
904
|
+
stage: result.stage,
|
|
905
|
+
exitCode: result.exitCode,
|
|
906
|
+
timedOut: result.timedOut,
|
|
907
|
+
evidenceDetail: repositoryCommandTestEvidenceV1(result, result.recipeId).detail,
|
|
908
|
+
...(diagnostic === undefined
|
|
909
|
+
? {}
|
|
910
|
+
: {
|
|
911
|
+
commandDiagnostic: diagnostic.text,
|
|
912
|
+
diagnosticTruncated: diagnostic.truncated
|
|
913
|
+
})
|
|
914
|
+
};
|
|
915
|
+
operationalFailures.push(failure);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
// The scoped replay has completed its finalizers before these unchanged
|
|
919
|
+
// rows are recorded, so isolation timings include checkout cleanup.
|
|
920
|
+
const wallMs = (yield* Clock.currentTimeMillis) - startedAt;
|
|
921
|
+
const batch = input.diagnosticBatch ?? 1;
|
|
922
|
+
const report = {
|
|
923
|
+
diagnosticOnly: true,
|
|
924
|
+
admissionEvidence: false,
|
|
925
|
+
seedId: input.seed.id,
|
|
926
|
+
referenceCommit: input.seed.referenceCommit,
|
|
927
|
+
batch,
|
|
928
|
+
phase,
|
|
929
|
+
wallMs,
|
|
930
|
+
...replay
|
|
931
|
+
};
|
|
932
|
+
yield* reportFoundryAuthoringArtifactV1({
|
|
933
|
+
version: 1,
|
|
934
|
+
operationId: `seed-qualification:${input.seed.id}:${input.seed.referenceCommit}:batch-${batch}:${phase}`,
|
|
935
|
+
role: "seed-qualifier",
|
|
936
|
+
model: "local-replay",
|
|
937
|
+
reasoningEffort: "none",
|
|
938
|
+
schemaName: "routekit_repository_seed_qualification_phase_v1",
|
|
939
|
+
validation: "validated",
|
|
940
|
+
request: {
|
|
941
|
+
instructions: "Private qualification phase diagnostics; no model request or independent admission evidence.",
|
|
942
|
+
input: replayInput,
|
|
943
|
+
jsonSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
944
|
+
maximumOutputTokens: 0
|
|
945
|
+
},
|
|
946
|
+
response: { text: JSON.stringify(report) },
|
|
947
|
+
value: report
|
|
948
|
+
});
|
|
949
|
+
yield* reportFoundryHeartbeatV1({
|
|
950
|
+
role: "seed-qualifier",
|
|
951
|
+
detail: `qualification phase complete; batch=${batch}; phase=${phase}; results=${replay.results.length}; wallMs=${wallMs}`
|
|
952
|
+
});
|
|
953
|
+
return replay.results;
|
|
954
|
+
});
|
|
955
|
+
// Repository validation commands can be full production builds. A standard
|
|
956
|
+
// sandbox has enough memory for one such build, but not two independent
|
|
957
|
+
// phase replays at once. Keep phases sequential within each already
|
|
958
|
+
// sequential batch; campaign-level sandbox concurrency remains unchanged.
|
|
959
|
+
const [baseline, pre, post] = yield* Effect.all([
|
|
960
|
+
replayPhase("baseline", {
|
|
961
|
+
repositoryRoot: input.repositoryRoot,
|
|
962
|
+
commit: input.seed.initialState.commit,
|
|
963
|
+
recipes: input.seed.environment.baselineGradeRecipes,
|
|
964
|
+
trustedPreparationRecipes: input.seed.environment.trustedPreparationRecipes,
|
|
965
|
+
candidateValidationRecipes: input.seed.environment.candidateValidationRecipes,
|
|
966
|
+
...(input.preparationCache === undefined
|
|
967
|
+
? {}
|
|
968
|
+
: { preparationCache: input.preparationCache }),
|
|
969
|
+
...(input.repetitions === undefined ? {} : { repetitions: input.repetitions })
|
|
970
|
+
}),
|
|
971
|
+
replayPhase("pre-change", {
|
|
972
|
+
repositoryRoot: input.repositoryRoot,
|
|
973
|
+
commit: input.seed.initialState.commit,
|
|
974
|
+
recipes: input.seed.environment.gradeRecipes,
|
|
975
|
+
trustedPreparationRecipes: input.seed.environment.trustedPreparationRecipes,
|
|
976
|
+
candidateValidationRecipes: input.seed.environment.candidateValidationRecipes,
|
|
977
|
+
overlays: hiddenTestOverlays,
|
|
978
|
+
contentOverlays: hiddenContentOverlays,
|
|
979
|
+
...(input.preparationCache === undefined
|
|
980
|
+
? {}
|
|
981
|
+
: { preparationCache: input.preparationCache }),
|
|
982
|
+
...(input.repetitions === undefined ? {} : { repetitions: input.repetitions })
|
|
983
|
+
}),
|
|
984
|
+
replayPhase("post-change", {
|
|
985
|
+
repositoryRoot: input.repositoryRoot,
|
|
986
|
+
commit: input.seed.referenceCommit,
|
|
987
|
+
recipes: input.seed.environment.gradeRecipes,
|
|
988
|
+
trustedPreparationRecipes: input.seed.environment.trustedPreparationRecipes,
|
|
989
|
+
candidateValidationRecipes: input.seed.environment.candidateValidationRecipes,
|
|
990
|
+
overlays: hiddenTestOverlays,
|
|
991
|
+
contentOverlays: hiddenContentOverlays,
|
|
992
|
+
...(input.preparationCache === undefined
|
|
993
|
+
? {}
|
|
994
|
+
: { preparationCache: input.preparationCache }),
|
|
995
|
+
...(input.repetitions === undefined ? {} : { repetitions: input.repetitions })
|
|
996
|
+
})
|
|
997
|
+
], { concurrency: 1 });
|
|
998
|
+
// Throw only after Effect.all joins every started phase and its diagnostic
|
|
999
|
+
// recorder. A partial run must never be presented as completed qualification.
|
|
1000
|
+
if (operationalFailures.length > 0) {
|
|
1001
|
+
const reasons = [
|
|
1002
|
+
...new Set(operationalFailures.map((entry) => `phase=${entry.phase}; stage=${entry.stage}; recipe=${entry.recipeId}; exit=${String(entry.exitCode)}; timeout=${String(entry.timedOut)}${entry.commandDiagnostic === undefined
|
|
1003
|
+
? ""
|
|
1004
|
+
: `; diagnostic=${encodeRepositoryCommandDiagnosticV1(entry.commandDiagnostic)}; diagnosticTruncated=${String(entry.diagnosticTruncated ?? false)}`}; evidence=${entry.evidenceDetail}`))
|
|
1005
|
+
];
|
|
1006
|
+
const detail = `historical qualification stopped after operational failure prevented grading: ${reasons.join(" | ")}; unstarted phases skipped=${[baseline, pre, post].filter((results) => results === undefined).length}; later batches were not started; qualification requires executable reviewed tests on both revisions and attributable pre-change assertion failures`;
|
|
1007
|
+
yield* reportFoundryHeartbeatV1({ role: "seed-qualifier", detail });
|
|
1008
|
+
return yield* new RepositoryFoundryError({
|
|
1009
|
+
operation: "extract-task-seeds",
|
|
1010
|
+
classification: "environment_blocked",
|
|
1011
|
+
detail,
|
|
1012
|
+
cause: { operationalFailures, ...(replayErrors.length === 0 ? {} : { replayErrors }) }
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
if (replayErrors.length > 0)
|
|
1016
|
+
return yield* replayErrors[0];
|
|
1017
|
+
if (baseline === undefined || pre === undefined || post === undefined) {
|
|
1018
|
+
return yield* failure("historical qualification stopped before all required phases completed");
|
|
1019
|
+
}
|
|
1020
|
+
const resultSignature = (result) => [
|
|
1021
|
+
result.recipeId,
|
|
1022
|
+
result.stage,
|
|
1023
|
+
String(result.exitCode),
|
|
1024
|
+
String(result.timedOut),
|
|
1025
|
+
String(result.gradeExecuted),
|
|
1026
|
+
repositoryCommandTestEvidenceV1(result, result.recipeId).outcome
|
|
1027
|
+
].join(":");
|
|
1028
|
+
const stableByRecipe = (results, recipeId) => new Set(results.filter((result) => result.recipeId === recipeId).map(resultSignature))
|
|
1029
|
+
.size === 1;
|
|
1030
|
+
const allBaselineGradeRecipeIds = input.seed.environment.baselineGradeRecipes.map((recipe) => recipe.id);
|
|
1031
|
+
const allGradeRecipeIds = input.seed.environment.gradeRecipes.map((recipe) => recipe.id);
|
|
1032
|
+
const baselineStable = allBaselineGradeRecipeIds.every((recipeId) => stableByRecipe(baseline, recipeId));
|
|
1033
|
+
const postStable = allGradeRecipeIds.every((recipeId) => stableByRecipe(post, recipeId));
|
|
1034
|
+
const prePrepared = pre.every((result) => result.stage !== "trusted-preparation" ||
|
|
1035
|
+
(result.gradeExecuted && result.exitCode === 0 && !result.timedOut));
|
|
1036
|
+
const postPrepared = post.every((result) => result.stage !== "trusted-preparation" ||
|
|
1037
|
+
(result.gradeExecuted && result.exitCode === 0 && !result.timedOut));
|
|
1038
|
+
const everyRecipeHasOutcome = (results, recipeIds, outcome) => recipeIds.every((recipeId) => {
|
|
1039
|
+
const observations = results.filter((result) => result.recipeId === recipeId);
|
|
1040
|
+
return (observations.length > 0 &&
|
|
1041
|
+
observations.every((result) => repositoryCommandTestEvidenceV1(result, recipeId).outcome === outcome));
|
|
1042
|
+
});
|
|
1043
|
+
const baselinePasses = everyRecipeHasOutcome(baseline, allBaselineGradeRecipeIds, "pass");
|
|
1044
|
+
const postPasses = everyRecipeHasOutcome(post, allGradeRecipeIds, "pass");
|
|
1045
|
+
const preStable = allGradeRecipeIds.every((recipeId) => stableByRecipe(pre, recipeId));
|
|
1046
|
+
const preFails = everyRecipeHasOutcome(pre, allGradeRecipeIds, "fail");
|
|
1047
|
+
const preInconclusive = pre.some((result) => repositoryCommandTestEvidenceV1(result, result.recipeId).outcome === "unknown");
|
|
1048
|
+
const rejectionReasons = [
|
|
1049
|
+
...(baselineStable ? [] : ["one or more initial baseline commands are unstable"]),
|
|
1050
|
+
...(baselinePasses
|
|
1051
|
+
? []
|
|
1052
|
+
: ["initial baseline does not pass before reviewed hidden evidence is overlaid"]),
|
|
1053
|
+
...(prePrepared ? [] : ["pre-change trusted preparation failed"]),
|
|
1054
|
+
...(postPrepared ? [] : ["post-change trusted preparation failed"]),
|
|
1055
|
+
...(preStable ? [] : ["one or more pre-change commands are unstable"]),
|
|
1056
|
+
...(postStable ? [] : ["one or more post-change commands are unstable"]),
|
|
1057
|
+
...(preFails ? [] : ["every reviewed grade command must reproduce the pre-change failure"]),
|
|
1058
|
+
...(preInconclusive
|
|
1059
|
+
? [
|
|
1060
|
+
"pre-change grading is inconclusive; setup, compilation, runtime, and discovery failures do not establish a behavioral regression"
|
|
1061
|
+
]
|
|
1062
|
+
: []),
|
|
1063
|
+
...(postPasses ? [] : ["one or more post-change commands do not pass"])
|
|
1064
|
+
];
|
|
1065
|
+
const observation = (phase, results) => results.flatMap((result) => {
|
|
1066
|
+
const diagnostic = result.exitCode === 0 && !result.timedOut
|
|
1067
|
+
? undefined
|
|
1068
|
+
: sanitizeRepositoryCommandDiagnosticV1(result.stdout, result.stderr, result.outputTruncated)?.text;
|
|
1069
|
+
const expectedRecipeIds = phase === "baseline" ? allBaselineGradeRecipeIds : allGradeRecipeIds;
|
|
1070
|
+
const projectedRecipeIds = result.gradeExecuted ? [result.recipeId] : expectedRecipeIds;
|
|
1071
|
+
return projectedRecipeIds.map((recipeId) => {
|
|
1072
|
+
const evidence = repositoryCommandTestEvidenceV1(result, recipeId);
|
|
1073
|
+
return {
|
|
1074
|
+
id: `${phase}-${recipeId}-${String(result.repetition)}`,
|
|
1075
|
+
kind: "test-command",
|
|
1076
|
+
subjectId: recipeId,
|
|
1077
|
+
outcome: evidence.outcome,
|
|
1078
|
+
detail: [
|
|
1079
|
+
`exit=${String(result.exitCode)}`,
|
|
1080
|
+
`stage=${result.stage}`,
|
|
1081
|
+
`gradeExecuted=${String(result.gradeExecuted)}`,
|
|
1082
|
+
`evidence=${evidence.detail}`,
|
|
1083
|
+
`timeout=${String(result.timedOut)}`,
|
|
1084
|
+
`durationMs=${String(result.durationMs)}`,
|
|
1085
|
+
...(result.gradeExecuted ? [] : [`terminalRecipe=${result.recipeId}`]),
|
|
1086
|
+
...result.trustedPreparationResults.map((preparation) => `trustedPreparation=${preparation.recipeId}:exit=${String(preparation.exitCode)}:timeout=${String(preparation.timedOut)}`),
|
|
1087
|
+
...result.candidateValidationResults.map((validation) => `candidateValidation=${validation.recipeId}:exit=${String(validation.exitCode)}:timeout=${String(validation.timedOut)}`),
|
|
1088
|
+
...(result.outputTruncated ? ["output=truncated"] : []),
|
|
1089
|
+
...(diagnostic === undefined ? [] : [`diagnostic=${JSON.stringify(diagnostic)}`])
|
|
1090
|
+
].join("; ")
|
|
1091
|
+
};
|
|
1092
|
+
});
|
|
1093
|
+
});
|
|
1094
|
+
const qualified = {
|
|
1095
|
+
...input.seed,
|
|
1096
|
+
status: rejectionReasons.length === 0 ? "qualified" : "rejected",
|
|
1097
|
+
baselineObservations: observation("baseline", baseline),
|
|
1098
|
+
preChangeObservations: observation("pre", pre),
|
|
1099
|
+
postChangeObservations: observation("post", post),
|
|
1100
|
+
confidence: rejectionReasons.length === 0 ? "high" : "review-required",
|
|
1101
|
+
risks: rejectionReasons.length === 0
|
|
1102
|
+
? input.seed.risks.filter((risk) => risk !== "review-required")
|
|
1103
|
+
: [...new Set([...input.seed.risks, "review-required"])],
|
|
1104
|
+
rejectionReasons
|
|
1105
|
+
};
|
|
1106
|
+
assertRepositoryTaskSeedV1(qualified);
|
|
1107
|
+
return qualified;
|
|
1108
|
+
});
|
|
1109
|
+
const observationSignature = (observation) => {
|
|
1110
|
+
const detail = observation.detail;
|
|
1111
|
+
const value = (name) => new RegExp(`(?:^|; )${name}=([^;]+)`, "u").exec(detail)?.[1] ?? "unknown";
|
|
1112
|
+
return [
|
|
1113
|
+
observation.subjectId,
|
|
1114
|
+
observation.outcome,
|
|
1115
|
+
`stage=${value("stage")}`,
|
|
1116
|
+
`exit=${value("exit")}`,
|
|
1117
|
+
`gradeExecuted=${value("gradeExecuted")}`,
|
|
1118
|
+
`timeout=${value("timeout")}`
|
|
1119
|
+
].join(":");
|
|
1120
|
+
};
|
|
1121
|
+
const phaseObservations = (seed, phase) => phase === "baseline"
|
|
1122
|
+
? seed.baselineObservations
|
|
1123
|
+
: phase === "pre-change"
|
|
1124
|
+
? seed.preChangeObservations
|
|
1125
|
+
: seed.postChangeObservations;
|
|
1126
|
+
const qualificationPhases = [
|
|
1127
|
+
"baseline",
|
|
1128
|
+
"pre-change",
|
|
1129
|
+
"post-change"
|
|
1130
|
+
];
|
|
1131
|
+
const assembleCrossBatchQualificationV1 = (input) => {
|
|
1132
|
+
const { qualifiedBatches, batchesRequired, repetitionsPerBatch } = input;
|
|
1133
|
+
const batches = qualifiedBatches.map((seed, index) => ({
|
|
1134
|
+
batch: index + 1,
|
|
1135
|
+
status: seed.status === "qualified" ? "qualified" : "rejected",
|
|
1136
|
+
phaseSignatures: qualificationPhases.map((phase) => ({
|
|
1137
|
+
phase,
|
|
1138
|
+
signatures: [...new Set(phaseObservations(seed, phase).map(observationSignature))].sort()
|
|
1139
|
+
})),
|
|
1140
|
+
rejectionReasons: [...seed.rejectionReasons]
|
|
1141
|
+
}));
|
|
1142
|
+
const divergentPhases = qualificationPhases.filter((phase) => {
|
|
1143
|
+
const signatures = batches.map((batch) => JSON.stringify(batch.phaseSignatures.find((entry) => entry.phase === phase)?.signatures ?? []));
|
|
1144
|
+
return new Set(signatures).size > 1;
|
|
1145
|
+
});
|
|
1146
|
+
const failureSignatureCounts = new Map();
|
|
1147
|
+
for (const seed of qualifiedBatches) {
|
|
1148
|
+
for (const phase of qualificationPhases) {
|
|
1149
|
+
for (const observation of phaseObservations(seed, phase)) {
|
|
1150
|
+
if (observation.outcome !== "fail")
|
|
1151
|
+
continue;
|
|
1152
|
+
const signature = `${phase}:${observationSignature(observation)}`;
|
|
1153
|
+
failureSignatureCounts.set(signature, (failureSignatureCounts.get(signature) ?? 0) + 1);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
const rejectionReasons = [
|
|
1158
|
+
...qualifiedBatches.flatMap((seed, index) => seed.rejectionReasons.map((reason) => `batch ${String(index + 1)}: ${reason}`)),
|
|
1159
|
+
...divergentPhases.map((phase) => `cross-batch qualification diverged for ${phase}`)
|
|
1160
|
+
];
|
|
1161
|
+
const stable = rejectionReasons.length === 0;
|
|
1162
|
+
const observations = (select) => qualifiedBatches.flatMap((seed, index) => select(seed).map((observation) => ({
|
|
1163
|
+
...observation,
|
|
1164
|
+
id: `batch-${String(index + 1)}-${observation.id}`
|
|
1165
|
+
})));
|
|
1166
|
+
const seed = {
|
|
1167
|
+
...qualifiedBatches.at(-1),
|
|
1168
|
+
status: stable ? "qualified" : "rejected",
|
|
1169
|
+
baselineObservations: observations((qualified) => qualified.baselineObservations),
|
|
1170
|
+
preChangeObservations: observations((qualified) => qualified.preChangeObservations),
|
|
1171
|
+
postChangeObservations: observations((qualified) => qualified.postChangeObservations),
|
|
1172
|
+
confidence: stable ? "high" : "review-required",
|
|
1173
|
+
risks: stable
|
|
1174
|
+
? input.sourceSeed.risks.filter((risk) => risk !== "review-required")
|
|
1175
|
+
: [
|
|
1176
|
+
...new Set([
|
|
1177
|
+
...input.sourceSeed.risks,
|
|
1178
|
+
"cross-batch-instability",
|
|
1179
|
+
"review-required"
|
|
1180
|
+
])
|
|
1181
|
+
],
|
|
1182
|
+
rejectionReasons
|
|
1183
|
+
};
|
|
1184
|
+
const qualification = {
|
|
1185
|
+
seed,
|
|
1186
|
+
stability: {
|
|
1187
|
+
version: 1,
|
|
1188
|
+
seedId: seed.id,
|
|
1189
|
+
batchesRequired,
|
|
1190
|
+
repetitionsPerBatch,
|
|
1191
|
+
stable,
|
|
1192
|
+
batches,
|
|
1193
|
+
divergentPhases,
|
|
1194
|
+
failureSignatures: [...failureSignatureCounts]
|
|
1195
|
+
.sort((left, right) => left[0].localeCompare(right[0]))
|
|
1196
|
+
.map(([signature, count]) => ({ signature, count })),
|
|
1197
|
+
rejectionReasons
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
assertRepositoryTaskSeedV1(seed);
|
|
1201
|
+
assertRepositorySeedCrossBatchQualificationV1(qualification);
|
|
1202
|
+
return qualification;
|
|
1203
|
+
};
|
|
1204
|
+
export const qualifyHistoricalTaskSeedAcrossBatchesV1 = Effect.fn("TaskSeed.qualifyHistoricalAcrossBatches")(function* (input) {
|
|
1205
|
+
const batchesRequired = Math.max(2, Math.min(input.batches ?? 2, 5));
|
|
1206
|
+
const repetitionsPerBatch = Math.max(2, Math.min(input.repetitionsPerBatch ?? 2, 5));
|
|
1207
|
+
const qualifiedBatches = yield* Effect.scoped(Effect.gen(function* () {
|
|
1208
|
+
const preparationCache = yield* makeRepositoryPreparationCacheV1({
|
|
1209
|
+
repositoryRoot: input.repositoryRoot,
|
|
1210
|
+
commits: [
|
|
1211
|
+
input.seed.initialState.commit,
|
|
1212
|
+
...(input.seed.referenceCommit === undefined ? [] : [input.seed.referenceCommit])
|
|
1213
|
+
],
|
|
1214
|
+
trustedPreparationRecipes: input.seed.environment.trustedPreparationRecipes
|
|
1215
|
+
});
|
|
1216
|
+
const qualified = [];
|
|
1217
|
+
for (let batch = 1; batch <= batchesRequired; batch += 1) {
|
|
1218
|
+
qualified.push(yield* qualifyHistoricalTaskSeedV1({
|
|
1219
|
+
repositoryRoot: input.repositoryRoot,
|
|
1220
|
+
seed: input.seed,
|
|
1221
|
+
...(input.hiddenFixtureSuite === undefined
|
|
1222
|
+
? {}
|
|
1223
|
+
: { hiddenFixtureSuite: input.hiddenFixtureSuite }),
|
|
1224
|
+
repetitions: repetitionsPerBatch,
|
|
1225
|
+
diagnosticBatch: batch,
|
|
1226
|
+
preparationCache
|
|
1227
|
+
}));
|
|
1228
|
+
}
|
|
1229
|
+
return qualified;
|
|
1230
|
+
}));
|
|
1231
|
+
return assembleCrossBatchQualificationV1({
|
|
1232
|
+
sourceSeed: input.seed,
|
|
1233
|
+
qualifiedBatches,
|
|
1234
|
+
batchesRequired,
|
|
1235
|
+
repetitionsPerBatch
|
|
1236
|
+
});
|
|
1237
|
+
});
|
|
1238
|
+
const qualificationUnitKey = (unit) => `${String(unit.batch)}:${unit.phase}:${String(unit.repetition)}`;
|
|
1239
|
+
export const repositorySeedQualificationProgressSummaryV1 = (progress) => ({
|
|
1240
|
+
completedBatches: progress.completedBatches.length,
|
|
1241
|
+
batchesRequired: progress.batchesRequired,
|
|
1242
|
+
completedUnits: progress.activeUnits.length,
|
|
1243
|
+
unitsPerBatch: qualificationPhases.length * progress.repetitionsPerBatch
|
|
1244
|
+
});
|
|
1245
|
+
/**
|
|
1246
|
+
* Resume reference qualification only at completed replay boundaries. Every
|
|
1247
|
+
* started replay still owns and closes its checkout scope; interruption may
|
|
1248
|
+
* repeat the in-flight replay, but never a previously checkpointed repetition.
|
|
1249
|
+
*/
|
|
1250
|
+
export const qualifyHistoricalTaskSeedAcrossBatchesResumableV1 = Effect.fn("TaskSeed.qualifyHistoricalAcrossBatchesResumable")(function* (input) {
|
|
1251
|
+
const batchesRequired = Math.max(2, Math.min(input.batches ?? 2, 5));
|
|
1252
|
+
const repetitionsPerBatch = Math.max(2, Math.min(input.repetitionsPerBatch ?? 2, 5));
|
|
1253
|
+
if ((input.seed.source.kind !== "historical-fix" &&
|
|
1254
|
+
input.seed.source.kind !== "historical-feature" &&
|
|
1255
|
+
input.seed.source.kind !== "historical-refactor") ||
|
|
1256
|
+
input.seed.referenceCommit === undefined) {
|
|
1257
|
+
return yield* failure("historical qualification requires a historical change seed with a reference commit");
|
|
1258
|
+
}
|
|
1259
|
+
const emptyProgress = () => ({
|
|
1260
|
+
version: 1,
|
|
1261
|
+
bindingDigest: input.bindingDigest,
|
|
1262
|
+
batchesRequired,
|
|
1263
|
+
repetitionsPerBatch,
|
|
1264
|
+
completedBatches: [],
|
|
1265
|
+
activeUnits: []
|
|
1266
|
+
});
|
|
1267
|
+
let progress = input.progress?.bindingDigest === input.bindingDigest &&
|
|
1268
|
+
input.progress.batchesRequired === batchesRequired &&
|
|
1269
|
+
input.progress.repetitionsPerBatch === repetitionsPerBatch
|
|
1270
|
+
? input.progress
|
|
1271
|
+
: emptyProgress();
|
|
1272
|
+
const expectedCompletedBatches = progress.completedBatches.map((_, index) => index + 1);
|
|
1273
|
+
const activeBatch = progress.completedBatches.length + 1;
|
|
1274
|
+
const activeKeys = progress.activeUnits.map(qualificationUnitKey);
|
|
1275
|
+
if (progress.completedBatches.length > batchesRequired ||
|
|
1276
|
+
progress.completedBatches.some((entry, index) => entry.batch !== expectedCompletedBatches[index] ||
|
|
1277
|
+
(entry.seed.status !== "qualified" && entry.seed.status !== "rejected")) ||
|
|
1278
|
+
progress.activeUnits.some((unit) => unit.batch !== activeBatch ||
|
|
1279
|
+
unit.repetition < 1 ||
|
|
1280
|
+
unit.repetition > repetitionsPerBatch ||
|
|
1281
|
+
unit.results.some((result) => result.repetition !== unit.repetition)) ||
|
|
1282
|
+
new Set(activeKeys).size !== activeKeys.length) {
|
|
1283
|
+
return yield* failure("retained historical qualification progress violates its batch, phase, or repetition binding");
|
|
1284
|
+
}
|
|
1285
|
+
// Completed batches are persisted verdicts, not permission to bypass the
|
|
1286
|
+
// current evidence contract. Reject legacy compile-only qualifications before
|
|
1287
|
+
// any preparation or queued replay can run.
|
|
1288
|
+
for (const entry of progress.completedBatches) {
|
|
1289
|
+
yield* Effect.try({
|
|
1290
|
+
try: () => assertRepositoryTaskSeedV1(entry.seed),
|
|
1291
|
+
catch: (cause) => new RepositoryFoundryError({
|
|
1292
|
+
operation: "extract-task-seeds",
|
|
1293
|
+
classification: "environment_blocked",
|
|
1294
|
+
detail: "retained qualification does not satisfy the executed behavioral evidence contract; select another candidate or repair the grading environment",
|
|
1295
|
+
cause
|
|
1296
|
+
})
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
// An incomplete or unattributable grade is retained diagnostic evidence, not useful
|
|
1300
|
+
// forward execution. Reopening the same binding must report it without
|
|
1301
|
+
// replaying it or spending on queued phases.
|
|
1302
|
+
for (const unit of progress.activeUnits) {
|
|
1303
|
+
const blocked = unit.results.find((result) => qualificationExecutionBlocked(result));
|
|
1304
|
+
if (blocked !== undefined)
|
|
1305
|
+
return yield* qualificationExecutionFailure(unit.phase, blocked);
|
|
1306
|
+
}
|
|
1307
|
+
const { hiddenTestOverlays, hiddenContentOverlays } = yield* hiddenQualificationOverlays(input.seed, input.hiddenFixtureSuite);
|
|
1308
|
+
const qualification = yield* Effect.scoped(Effect.gen(function* () {
|
|
1309
|
+
const preparationCache = yield* makeRepositoryPreparationCacheV1({
|
|
1310
|
+
repositoryRoot: input.repositoryRoot,
|
|
1311
|
+
commits: [input.seed.initialState.commit, input.seed.referenceCommit],
|
|
1312
|
+
trustedPreparationRecipes: input.seed.environment.trustedPreparationRecipes
|
|
1313
|
+
});
|
|
1314
|
+
for (let batch = progress.completedBatches.length + 1; batch <= batchesRequired; batch += 1) {
|
|
1315
|
+
for (const phase of qualificationPhases) {
|
|
1316
|
+
for (let repetition = 1; repetition <= repetitionsPerBatch; repetition += 1) {
|
|
1317
|
+
const key = qualificationUnitKey({ batch, phase, repetition });
|
|
1318
|
+
if (progress.activeUnits.some((unit) => qualificationUnitKey(unit) === key))
|
|
1319
|
+
continue;
|
|
1320
|
+
const common = {
|
|
1321
|
+
repositoryRoot: input.repositoryRoot,
|
|
1322
|
+
trustedPreparationRecipes: input.seed.environment.trustedPreparationRecipes,
|
|
1323
|
+
candidateValidationRecipes: input.seed.environment.candidateValidationRecipes,
|
|
1324
|
+
preparationCache,
|
|
1325
|
+
repetitions: 1,
|
|
1326
|
+
repetitionStart: repetition
|
|
1327
|
+
};
|
|
1328
|
+
const replayInput = phase === "baseline"
|
|
1329
|
+
? {
|
|
1330
|
+
...common,
|
|
1331
|
+
commit: input.seed.initialState.commit,
|
|
1332
|
+
recipes: input.seed.environment.baselineGradeRecipes
|
|
1333
|
+
}
|
|
1334
|
+
: {
|
|
1335
|
+
...common,
|
|
1336
|
+
commit: phase === "pre-change"
|
|
1337
|
+
? input.seed.initialState.commit
|
|
1338
|
+
: input.seed.referenceCommit,
|
|
1339
|
+
recipes: input.seed.environment.gradeRecipes,
|
|
1340
|
+
overlays: hiddenTestOverlays,
|
|
1341
|
+
contentOverlays: hiddenContentOverlays
|
|
1342
|
+
};
|
|
1343
|
+
const results = yield* replayRepositoryCommandsV1(replayInput);
|
|
1344
|
+
progress = {
|
|
1345
|
+
...progress,
|
|
1346
|
+
activeUnits: [...progress.activeUnits, { batch, phase, repetition, results }]
|
|
1347
|
+
};
|
|
1348
|
+
yield* input.checkpoint(progress);
|
|
1349
|
+
const operational = results.find((result) => qualificationExecutionBlocked(result));
|
|
1350
|
+
if (operational !== undefined)
|
|
1351
|
+
return yield* qualificationExecutionFailure(phase, operational);
|
|
1352
|
+
yield* reportFoundryHeartbeatV1({
|
|
1353
|
+
role: "seed-qualifier",
|
|
1354
|
+
detail: `qualification replay checkpointed; batch=${String(batch)}/${String(batchesRequired)}; phase=${phase}; repetition=${String(repetition)}/${String(repetitionsPerBatch)}`
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
const phaseResults = qualificationPhases.map((phase) => progress.activeUnits
|
|
1359
|
+
.filter((unit) => unit.batch === batch && unit.phase === phase)
|
|
1360
|
+
.sort((left, right) => left.repetition - right.repetition)
|
|
1361
|
+
.flatMap((unit) => unit.results));
|
|
1362
|
+
let cursor = 0;
|
|
1363
|
+
const reconstruction = {
|
|
1364
|
+
commands: () => {
|
|
1365
|
+
const results = phaseResults[cursor++];
|
|
1366
|
+
return results === undefined
|
|
1367
|
+
? Effect.fail(failure("retained qualification phase evidence was exhausted early"))
|
|
1368
|
+
: Effect.succeed(results);
|
|
1369
|
+
},
|
|
1370
|
+
oracle: () => Effect.fail(failure("seed qualification reconstruction cannot supply oracle evidence")),
|
|
1371
|
+
assertConsumed: Effect.suspend(() => cursor === phaseResults.length
|
|
1372
|
+
? Effect.void
|
|
1373
|
+
: Effect.fail(failure("retained qualification phase evidence was not fully consumed")))
|
|
1374
|
+
};
|
|
1375
|
+
const seed = yield* qualifyHistoricalTaskSeedV1({
|
|
1376
|
+
repositoryRoot: input.repositoryRoot,
|
|
1377
|
+
seed: input.seed,
|
|
1378
|
+
...(input.hiddenFixtureSuite === undefined
|
|
1379
|
+
? {}
|
|
1380
|
+
: { hiddenFixtureSuite: input.hiddenFixtureSuite }),
|
|
1381
|
+
repetitions: repetitionsPerBatch,
|
|
1382
|
+
diagnosticBatch: batch
|
|
1383
|
+
}).pipe(Effect.provideService(RepositoryFoundryEvidenceReconstruction, reconstruction));
|
|
1384
|
+
yield* reconstruction.assertConsumed;
|
|
1385
|
+
progress = {
|
|
1386
|
+
...progress,
|
|
1387
|
+
completedBatches: [...progress.completedBatches, { batch, seed }],
|
|
1388
|
+
activeUnits: []
|
|
1389
|
+
};
|
|
1390
|
+
yield* input.checkpoint(progress);
|
|
1391
|
+
}
|
|
1392
|
+
return assembleCrossBatchQualificationV1({
|
|
1393
|
+
sourceSeed: input.seed,
|
|
1394
|
+
qualifiedBatches: progress.completedBatches.map((entry) => entry.seed),
|
|
1395
|
+
batchesRequired,
|
|
1396
|
+
repetitionsPerBatch
|
|
1397
|
+
});
|
|
1398
|
+
}));
|
|
1399
|
+
return { qualification, progress };
|
|
1400
|
+
});
|
|
1401
|
+
export const makeTaskSeed = Effect.succeed(TaskSeed.of({
|
|
1402
|
+
extractHistorical: extractHistoricalTaskSeedsV1,
|
|
1403
|
+
qualifyHistorical: qualifyHistoricalTaskSeedV1,
|
|
1404
|
+
qualifyHistoricalAcrossBatches: qualifyHistoricalTaskSeedAcrossBatchesV1
|
|
1405
|
+
}));
|
|
1406
|
+
export const TaskSeedLive = Layer.effect(TaskSeed, makeTaskSeed);
|