audit-tools 0.52.3 → 0.52.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -1
- package/dist/audit/cli/args.d.ts.map +1 -1
- package/dist/audit/cli/args.js +6 -2
- package/dist/audit/cli/args.js.map +1 -1
- package/dist/audit/cli/argumentContract.d.ts +11 -0
- package/dist/audit/cli/argumentContract.d.ts.map +1 -0
- package/dist/audit/cli/argumentContract.js +45 -0
- package/dist/audit/cli/argumentContract.js.map +1 -0
- package/dist/audit/cli/auditReviewBindings.d.ts +49 -0
- package/dist/audit/cli/auditReviewBindings.d.ts.map +1 -0
- package/dist/audit/cli/auditReviewBindings.js +61 -0
- package/dist/audit/cli/auditReviewBindings.js.map +1 -0
- package/dist/audit/cli/auditStep.d.ts +1 -27
- package/dist/audit/cli/auditStep.d.ts.map +1 -1
- package/dist/audit/cli/auditStep.js +7 -32
- package/dist/audit/cli/auditStep.js.map +1 -1
- package/dist/audit/cli/charterComparisonPrompt.d.ts +2 -0
- package/dist/audit/cli/charterComparisonPrompt.d.ts.map +1 -1
- package/dist/audit/cli/charterComparisonPrompt.js +9 -0
- package/dist/audit/cli/charterComparisonPrompt.js.map +1 -1
- package/dist/audit/cli/charterExtractionPrompt.d.ts.map +1 -1
- package/dist/audit/cli/charterExtractionPrompt.js +17 -3
- package/dist/audit/cli/charterExtractionPrompt.js.map +1 -1
- package/dist/audit/cli/conceptualDispatch.d.ts +1 -1
- package/dist/audit/cli/conceptualDispatch.d.ts.map +1 -1
- package/dist/audit/cli/conceptualDispatch.js +15 -2
- package/dist/audit/cli/conceptualDispatch.js.map +1 -1
- package/dist/audit/cli/confirmIntentStep.d.ts.map +1 -1
- package/dist/audit/cli/confirmIntentStep.js +10 -17
- package/dist/audit/cli/confirmIntentStep.js.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.js +231 -229
- package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
- package/dist/audit/cli/explainTaskCommand.d.ts.map +1 -1
- package/dist/audit/cli/explainTaskCommand.js +3 -1
- package/dist/audit/cli/explainTaskCommand.js.map +1 -1
- package/dist/audit/cli/fanoutLanes.d.ts +10 -14
- package/dist/audit/cli/fanoutLanes.d.ts.map +1 -1
- package/dist/audit/cli/fanoutLanes.js +50 -16
- package/dist/audit/cli/fanoutLanes.js.map +1 -1
- package/dist/audit/cli/functionalPreflight.d.ts +96 -0
- package/dist/audit/cli/functionalPreflight.d.ts.map +1 -0
- package/dist/audit/cli/functionalPreflight.js +103 -0
- package/dist/audit/cli/functionalPreflight.js.map +1 -0
- package/dist/audit/cli/laneSubmissions.d.ts +5 -1
- package/dist/audit/cli/laneSubmissions.d.ts.map +1 -1
- package/dist/audit/cli/laneSubmissions.js +45 -34
- package/dist/audit/cli/laneSubmissions.js.map +1 -1
- package/dist/audit/cli/nextStepCommand.d.ts +1 -9
- package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
- package/dist/audit/cli/nextStepCommand.js +101 -38
- package/dist/audit/cli/nextStepCommand.js.map +1 -1
- package/dist/audit/cli/nextStepHelpers.d.ts +10 -25
- package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
- package/dist/audit/cli/nextStepHelpers.js +57 -67
- package/dist/audit/cli/nextStepHelpers.js.map +1 -1
- package/dist/audit/cli/prompts.d.ts.map +1 -1
- package/dist/audit/cli/prompts.js +9 -4
- package/dist/audit/cli/prompts.js.map +1 -1
- package/dist/audit/cli/recoverSubmissionCommand.d.ts.map +1 -1
- package/dist/audit/cli/recoverSubmissionCommand.js +8 -1
- package/dist/audit/cli/recoverSubmissionCommand.js.map +1 -1
- package/dist/audit/cli/reviewRun.js +1 -1
- package/dist/audit/cli/reviewRun.js.map +1 -1
- package/dist/audit/cli/reviewSubmission.d.ts +24 -0
- package/dist/audit/cli/reviewSubmission.d.ts.map +1 -0
- package/dist/audit/cli/reviewSubmission.js +97 -0
- package/dist/audit/cli/reviewSubmission.js.map +1 -0
- package/dist/audit/cli/sampleRunCommand.d.ts +3 -3
- package/dist/audit/cli/steps.d.ts +18 -18
- package/dist/audit/cli/steps.d.ts.map +1 -1
- package/dist/audit/cli/steps.js +1 -0
- package/dist/audit/cli/steps.js.map +1 -1
- package/dist/audit/cli/unacceptResultsCommand.d.ts.map +1 -1
- package/dist/audit/cli/unacceptResultsCommand.js +2 -1
- package/dist/audit/cli/unacceptResultsCommand.js.map +1 -1
- package/dist/audit/cli.d.ts +2 -1
- package/dist/audit/cli.d.ts.map +1 -1
- package/dist/audit/cli.js +27 -19
- package/dist/audit/cli.js.map +1 -1
- package/dist/audit/contracts/workerSchemas.d.ts +10 -10
- package/dist/audit/contracts/wrapperResponse.d.ts +34 -34
- package/dist/audit/extractors/commentDecomposition.d.ts +14 -0
- package/dist/audit/extractors/commentDecomposition.d.ts.map +1 -1
- package/dist/audit/extractors/commentDecomposition.js +2 -1
- package/dist/audit/extractors/commentDecomposition.js.map +1 -1
- package/dist/audit/extractors/graph.d.ts +1 -1
- package/dist/audit/extractors/graph.d.ts.map +1 -1
- package/dist/audit/extractors/graph.js +12 -7
- package/dist/audit/extractors/graph.js.map +1 -1
- package/dist/audit/extractors/graphPythonImports.d.ts +3 -0
- package/dist/audit/extractors/graphPythonImports.d.ts.map +1 -1
- package/dist/audit/extractors/graphPythonImports.js +24 -5
- package/dist/audit/extractors/graphPythonImports.js.map +1 -1
- package/dist/audit/extractors/graphRouteSources.d.ts +7 -0
- package/dist/audit/extractors/graphRouteSources.d.ts.map +1 -0
- package/dist/audit/extractors/graphRouteSources.js +208 -0
- package/dist/audit/extractors/graphRouteSources.js.map +1 -0
- package/dist/audit/extractors/graphRoutes.d.ts +1 -1
- package/dist/audit/extractors/graphRoutes.d.ts.map +1 -1
- package/dist/audit/extractors/graphRoutes.js +65 -68
- package/dist/audit/extractors/graphRoutes.js.map +1 -1
- package/dist/audit/io/artifacts.d.ts +1 -0
- package/dist/audit/io/artifacts.d.ts.map +1 -1
- package/dist/audit/io/artifacts.js +29 -10
- package/dist/audit/io/artifacts.js.map +1 -1
- package/dist/audit/io/runArtifacts.d.ts +1 -1
- package/dist/audit/io/runArtifacts.d.ts.map +1 -1
- package/dist/audit/io/runArtifacts.js +3 -0
- package/dist/audit/io/runArtifacts.js.map +1 -1
- package/dist/audit/io/toolingManifest.d.ts.map +1 -1
- package/dist/audit/io/toolingManifest.js +11 -32
- package/dist/audit/io/toolingManifest.js.map +1 -1
- package/dist/audit/orchestrator/acquisitionExecutor.d.ts +2 -3
- package/dist/audit/orchestrator/acquisitionExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/acquisitionExecutor.js.map +1 -1
- package/dist/audit/orchestrator/advanceTypes.d.ts +1 -2
- package/dist/audit/orchestrator/advanceTypes.d.ts.map +1 -1
- package/dist/audit/orchestrator/autoFixExecutor.d.ts +2 -2
- package/dist/audit/orchestrator/autoFixExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/autoFixExecutor.js +2 -2
- package/dist/audit/orchestrator/autoFixExecutor.js.map +1 -1
- package/dist/audit/orchestrator/charterFidelityPacket.d.ts +2 -0
- package/dist/audit/orchestrator/charterFidelityPacket.d.ts.map +1 -1
- package/dist/audit/orchestrator/charterFidelityPacket.js +6 -1
- package/dist/audit/orchestrator/charterFidelityPacket.js.map +1 -1
- package/dist/audit/orchestrator/charterPackets.d.ts +2 -0
- package/dist/audit/orchestrator/charterPackets.d.ts.map +1 -1
- package/dist/audit/orchestrator/charterPackets.js +6 -0
- package/dist/audit/orchestrator/charterPackets.js.map +1 -1
- package/dist/audit/orchestrator/designReviewProjection.d.ts +5 -8
- package/dist/audit/orchestrator/designReviewProjection.d.ts.map +1 -1
- package/dist/audit/orchestrator/designReviewProjection.js +7 -1
- package/dist/audit/orchestrator/designReviewProjection.js.map +1 -1
- package/dist/audit/orchestrator/designReviewPrompt.d.ts +3 -47
- package/dist/audit/orchestrator/designReviewPrompt.d.ts.map +1 -1
- package/dist/audit/orchestrator/designReviewPrompt.js +28 -135
- package/dist/audit/orchestrator/designReviewPrompt.js.map +1 -1
- package/dist/audit/orchestrator/designReviewSnapshot.d.ts +24 -1
- package/dist/audit/orchestrator/designReviewSnapshot.d.ts.map +1 -1
- package/dist/audit/orchestrator/designReviewSnapshot.js +8 -2
- package/dist/audit/orchestrator/designReviewSnapshot.js.map +1 -1
- package/dist/audit/orchestrator/designReviewTask.d.ts +46 -0
- package/dist/audit/orchestrator/designReviewTask.d.ts.map +1 -0
- package/dist/audit/orchestrator/designReviewTask.js +44 -0
- package/dist/audit/orchestrator/designReviewTask.js.map +1 -0
- package/dist/audit/orchestrator/intentCheckpointGate.d.ts +1 -1
- package/dist/audit/orchestrator/intentCheckpointGate.d.ts.map +1 -1
- package/dist/audit/orchestrator/intentCheckpointGate.js +4 -2
- package/dist/audit/orchestrator/intentCheckpointGate.js.map +1 -1
- package/dist/audit/orchestrator/intentEquivalenceExecutor.d.ts +3 -0
- package/dist/audit/orchestrator/intentEquivalenceExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/intentEquivalenceExecutor.js +2 -0
- package/dist/audit/orchestrator/intentEquivalenceExecutor.js.map +1 -1
- package/dist/audit/orchestrator/intentInterpreter.d.ts +1 -9
- package/dist/audit/orchestrator/intentInterpreter.d.ts.map +1 -1
- package/dist/audit/orchestrator/intentInterpreter.js +6 -11
- package/dist/audit/orchestrator/intentInterpreter.js.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/shared.d.ts +2 -2
- package/dist/audit/orchestrator/selectiveDeepening/shared.d.ts.map +1 -1
- package/dist/audit/orchestrator/staleness.d.ts.map +1 -1
- package/dist/audit/orchestrator/staleness.js +32 -40
- package/dist/audit/orchestrator/staleness.js.map +1 -1
- package/dist/audit/orchestrator/state.d.ts.map +1 -1
- package/dist/audit/orchestrator/state.js +8 -14
- package/dist/audit/orchestrator/state.js.map +1 -1
- package/dist/audit/orchestrator/systemicChallengeExecutor.d.ts +19 -0
- package/dist/audit/orchestrator/systemicChallengeExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/systemicChallengeExecutor.js +15 -1
- package/dist/audit/orchestrator/systemicChallengeExecutor.js.map +1 -1
- package/dist/audit/reporting/criticalFlowFallbackPrompt.d.ts +1 -1
- package/dist/audit/reporting/criticalFlowFallbackPrompt.d.ts.map +1 -1
- package/dist/audit/reporting/criticalFlowFallbackPrompt.js +4 -2
- package/dist/audit/reporting/criticalFlowFallbackPrompt.js.map +1 -1
- package/dist/audit/reporting/mergeFindings.d.ts.map +1 -1
- package/dist/audit/reporting/mergeFindings.js +43 -2
- package/dist/audit/reporting/mergeFindings.js.map +1 -1
- package/dist/audit/types/auditScope.d.ts +2 -2
- package/dist/audit/types/auditState.d.ts +6 -6
- package/dist/audit/types/conceptualAdjudication.d.ts +19 -19
- package/dist/audit/types/conceptualAdjudication.d.ts.map +1 -1
- package/dist/audit/types/conceptualAdjudication.js +3 -3
- package/dist/audit/types/conceptualAdjudication.js.map +1 -1
- package/dist/audit/types.d.ts +17 -19
- package/dist/audit/types.d.ts.map +1 -1
- package/dist/audit/types.js +19 -20
- package/dist/audit/types.js.map +1 -1
- package/dist/remediate/contractPipeline/artifactStore.d.ts +3 -1
- package/dist/remediate/contractPipeline/artifactStore.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/artifactStore.js +26 -31
- package/dist/remediate/contractPipeline/artifactStore.js.map +1 -1
- package/dist/remediate/contractPipeline/changeClassification.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/changeClassification.js +20 -8
- package/dist/remediate/contractPipeline/changeClassification.js.map +1 -1
- package/dist/remediate/contractPipeline/contractReviewBinding.d.ts +28 -0
- package/dist/remediate/contractPipeline/contractReviewBinding.d.ts.map +1 -0
- package/dist/remediate/contractPipeline/contractReviewBinding.js +75 -0
- package/dist/remediate/contractPipeline/contractReviewBinding.js.map +1 -0
- package/dist/remediate/contractPipeline/repairState.d.ts +3 -1
- package/dist/remediate/contractPipeline/repairState.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/repairState.js +1 -0
- package/dist/remediate/contractPipeline/repairState.js.map +1 -1
- package/dist/remediate/contractPipeline/sketchSource.d.ts +1 -0
- package/dist/remediate/contractPipeline/sketchSource.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/sketchSource.js +1 -0
- package/dist/remediate/contractPipeline/sketchSource.js.map +1 -1
- package/dist/remediate/dedup/crossLensDedup.d.ts +1 -2
- package/dist/remediate/dedup/crossLensDedup.d.ts.map +1 -1
- package/dist/remediate/dedup/crossLensDedup.js +1 -0
- package/dist/remediate/dedup/crossLensDedup.js.map +1 -1
- package/dist/remediate/index.d.ts.map +1 -1
- package/dist/remediate/index.js +92 -52
- package/dist/remediate/index.js.map +1 -1
- package/dist/remediate/intake.d.ts +54 -0
- package/dist/remediate/intake.d.ts.map +1 -1
- package/dist/remediate/intake.js +15 -1
- package/dist/remediate/intake.js.map +1 -1
- package/dist/remediate/intakeSelection.d.ts +27 -0
- package/dist/remediate/intakeSelection.d.ts.map +1 -0
- package/dist/remediate/intakeSelection.js +37 -0
- package/dist/remediate/intakeSelection.js.map +1 -0
- package/dist/remediate/intent/intentPersistence.d.ts +41 -0
- package/dist/remediate/intent/intentPersistence.d.ts.map +1 -0
- package/dist/remediate/intent/intentPersistence.js +129 -0
- package/dist/remediate/intent/intentPersistence.js.map +1 -0
- package/dist/remediate/phases/close.d.ts +2 -1
- package/dist/remediate/phases/close.d.ts.map +1 -1
- package/dist/remediate/phases/close.js +19 -2
- package/dist/remediate/phases/close.js.map +1 -1
- package/dist/remediate/phases/closeReviewProvenance.d.ts +4 -0
- package/dist/remediate/phases/closeReviewProvenance.d.ts.map +1 -0
- package/dist/remediate/phases/closeReviewProvenance.js +16 -0
- package/dist/remediate/phases/closeReviewProvenance.js.map +1 -0
- package/dist/remediate/phases/triage.d.ts.map +1 -1
- package/dist/remediate/phases/triage.js +3 -1
- package/dist/remediate/phases/triage.js.map +1 -1
- package/dist/remediate/review/filterDispositions.d.ts +13 -0
- package/dist/remediate/review/filterDispositions.d.ts.map +1 -0
- package/dist/remediate/review/filterDispositions.js +32 -0
- package/dist/remediate/review/filterDispositions.js.map +1 -0
- package/dist/remediate/state/runIdentity.d.ts +27 -0
- package/dist/remediate/state/runIdentity.d.ts.map +1 -0
- package/dist/remediate/state/runIdentity.js +38 -0
- package/dist/remediate/state/runIdentity.js.map +1 -0
- package/dist/remediate/state/store.d.ts +80 -1
- package/dist/remediate/state/store.d.ts.map +1 -1
- package/dist/remediate/state/store.js +82 -1
- package/dist/remediate/state/store.js.map +1 -1
- package/dist/remediate/state/types.d.ts +215 -42
- package/dist/remediate/state/types.d.ts.map +1 -1
- package/dist/remediate/state/types.js +11 -3
- package/dist/remediate/state/types.js.map +1 -1
- package/dist/remediate/steps/contractPipeline.d.ts +61 -3
- package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipeline.js +121 -47
- package/dist/remediate/steps/contractPipeline.js.map +1 -1
- package/dist/remediate/steps/contractPipelinePrompts.d.ts +27 -4
- package/dist/remediate/steps/contractPipelinePrompts.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipelinePrompts.js +34 -32
- package/dist/remediate/steps/contractPipelinePrompts.js.map +1 -1
- package/dist/remediate/steps/dispatch/contractConformanceReview.d.ts +31 -0
- package/dist/remediate/steps/dispatch/contractConformanceReview.d.ts.map +1 -0
- package/dist/remediate/steps/dispatch/contractConformanceReview.js +98 -0
- package/dist/remediate/steps/dispatch/contractConformanceReview.js.map +1 -0
- package/dist/remediate/steps/dispatch/hostContracts.d.ts +133 -0
- package/dist/remediate/steps/dispatch/hostContracts.d.ts.map +1 -0
- package/dist/remediate/steps/dispatch/hostContracts.js +104 -0
- package/dist/remediate/steps/dispatch/hostContracts.js.map +1 -0
- package/dist/remediate/steps/dispatch/hostCorroboration.d.ts +70 -0
- package/dist/remediate/steps/dispatch/hostCorroboration.d.ts.map +1 -0
- package/dist/remediate/steps/dispatch/hostCorroboration.js +350 -0
- package/dist/remediate/steps/dispatch/hostCorroboration.js.map +1 -0
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts +8 -162
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.js +137 -690
- package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
- package/dist/remediate/steps/dispatch/requiredTests.d.ts +83 -0
- package/dist/remediate/steps/dispatch/requiredTests.d.ts.map +1 -0
- package/dist/remediate/steps/dispatch/requiredTests.js +262 -0
- package/dist/remediate/steps/dispatch/requiredTests.js.map +1 -0
- package/dist/remediate/steps/finalGate.d.ts +8 -7
- package/dist/remediate/steps/finalGate.d.ts.map +1 -1
- package/dist/remediate/steps/finalGate.js +14 -11
- package/dist/remediate/steps/finalGate.js.map +1 -1
- package/dist/remediate/steps/frictionCloseout.d.ts +34 -0
- package/dist/remediate/steps/frictionCloseout.d.ts.map +1 -0
- package/dist/remediate/steps/frictionCloseout.js +113 -0
- package/dist/remediate/steps/frictionCloseout.js.map +1 -0
- package/dist/remediate/steps/gateCommands.d.ts +7 -9
- package/dist/remediate/steps/gateCommands.d.ts.map +1 -1
- package/dist/remediate/steps/gateCommands.js +37 -11
- package/dist/remediate/steps/gateCommands.js.map +1 -1
- package/dist/remediate/steps/intakeResolver.d.ts +2 -0
- package/dist/remediate/steps/intakeResolver.d.ts.map +1 -1
- package/dist/remediate/steps/intakeResolver.js +36 -11
- package/dist/remediate/steps/intakeResolver.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts +23 -110
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +232 -508
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/steps/prompts.d.ts +1 -1
- package/dist/remediate/steps/prompts.d.ts.map +1 -1
- package/dist/remediate/steps/prompts.js +7 -2
- package/dist/remediate/steps/prompts.js.map +1 -1
- package/dist/remediate/steps/recoverIngest.d.ts +57 -0
- package/dist/remediate/steps/recoverIngest.d.ts.map +1 -0
- package/dist/remediate/steps/recoverIngest.js +171 -0
- package/dist/remediate/steps/recoverIngest.js.map +1 -0
- package/dist/remediate/steps/stepWriter.d.ts +5 -4
- package/dist/remediate/steps/stepWriter.d.ts.map +1 -1
- package/dist/remediate/steps/stepWriter.js +2 -1
- package/dist/remediate/steps/stepWriter.js.map +1 -1
- package/dist/remediate/steps/types.d.ts +2 -3
- package/dist/remediate/steps/types.d.ts.map +1 -1
- package/dist/remediate/steps/types.js.map +1 -1
- package/dist/remediate/validation/artifacts.d.ts.map +1 -1
- package/dist/remediate/validation/artifacts.js +5 -12
- package/dist/remediate/validation/artifacts.js.map +1 -1
- package/dist/remediate/validation/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/validation/contractPipeline.js +10 -7
- package/dist/remediate/validation/contractPipeline.js.map +1 -1
- package/dist/remediate/validation/contractPipelineGates.d.ts +13 -67
- package/dist/remediate/validation/contractPipelineGates.d.ts.map +1 -1
- package/dist/remediate/validation/contractPipelineGates.js +65 -139
- package/dist/remediate/validation/contractPipelineGates.js.map +1 -1
- package/dist/remediate/validation/remediationState.d.ts.map +1 -1
- package/dist/remediate/validation/remediationState.js +9 -3
- package/dist/remediate/validation/remediationState.js.map +1 -1
- package/dist/shared/agentReflections.d.ts +2 -2
- package/dist/shared/analyzerPolicy.d.ts +2 -16
- package/dist/shared/analyzerPolicy.d.ts.map +1 -1
- package/dist/shared/analyzerPolicy.js +22 -24
- package/dist/shared/analyzerPolicy.js.map +1 -1
- package/dist/shared/analyzerRunConsent.d.ts +33 -0
- package/dist/shared/analyzerRunConsent.d.ts.map +1 -0
- package/dist/shared/analyzerRunConsent.js +42 -0
- package/dist/shared/analyzerRunConsent.js.map +1 -0
- package/dist/shared/analyzers/acquisitionEngine.d.ts +3 -8
- package/dist/shared/analyzers/acquisitionEngine.d.ts.map +1 -1
- package/dist/shared/analyzers/acquisitionEngine.js +1 -0
- package/dist/shared/analyzers/acquisitionEngine.js.map +1 -1
- package/dist/shared/decompose/charterExtraction.d.ts +8 -8
- package/dist/shared/decompose/systemicChallenge.d.ts +12 -12
- package/dist/shared/engine/obligationEngine.d.ts +2 -2
- package/dist/shared/findings/dedupe.d.ts.map +1 -1
- package/dist/shared/findings/dedupe.js +16 -0
- package/dist/shared/findings/dedupe.js.map +1 -1
- package/dist/shared/graph/orderedReachability.d.ts +9 -0
- package/dist/shared/graph/orderedReachability.d.ts.map +1 -0
- package/dist/shared/graph/orderedReachability.js +21 -0
- package/dist/shared/graph/orderedReachability.js.map +1 -0
- package/dist/shared/index.d.ts +9 -3
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +6 -3
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/intake/guidanceBootstrap.d.ts +2 -0
- package/dist/shared/intake/guidanceBootstrap.d.ts.map +1 -1
- package/dist/shared/intake/guidanceBootstrap.js +10 -1
- package/dist/shared/intake/guidanceBootstrap.js.map +1 -1
- package/dist/shared/intent/constraintClauses.d.ts +3 -0
- package/dist/shared/intent/constraintClauses.d.ts.map +1 -1
- package/dist/shared/intent/constraintClauses.js +9 -0
- package/dist/shared/intent/constraintClauses.js.map +1 -1
- package/dist/shared/io/artifactTreeHold.d.ts +5 -0
- package/dist/shared/io/artifactTreeHold.d.ts.map +1 -0
- package/dist/shared/io/artifactTreeHold.js +13 -0
- package/dist/shared/io/artifactTreeHold.js.map +1 -0
- package/dist/shared/io/auditToolsPaths.d.ts +3 -1
- package/dist/shared/io/auditToolsPaths.d.ts.map +1 -1
- package/dist/shared/io/auditToolsPaths.js +6 -1
- package/dist/shared/io/auditToolsPaths.js.map +1 -1
- package/dist/shared/io/collectFilesSorted.d.ts +10 -0
- package/dist/shared/io/collectFilesSorted.d.ts.map +1 -0
- package/dist/shared/io/collectFilesSorted.js +38 -0
- package/dist/shared/io/collectFilesSorted.js.map +1 -0
- package/dist/shared/io/json.d.ts.map +1 -1
- package/dist/shared/io/json.js +11 -4
- package/dist/shared/io/json.js.map +1 -1
- package/dist/shared/io/stepContractWriter.d.ts.map +1 -1
- package/dist/shared/io/stepContractWriter.js +8 -0
- package/dist/shared/io/stepContractWriter.js.map +1 -1
- package/dist/shared/loopCorePaths.d.ts.map +1 -1
- package/dist/shared/loopCorePaths.js +9 -0
- package/dist/shared/loopCorePaths.js.map +1 -1
- package/dist/shared/observability/rootLogObservations.d.ts +10 -0
- package/dist/shared/observability/rootLogObservations.d.ts.map +1 -0
- package/dist/shared/observability/rootLogObservations.js +147 -0
- package/dist/shared/observability/rootLogObservations.js.map +1 -0
- package/dist/shared/prompts.d.ts +7 -7
- package/dist/shared/prompts.d.ts.map +1 -1
- package/dist/shared/prompts.js +11 -11
- package/dist/shared/prompts.js.map +1 -1
- package/dist/shared/submission/submissionLedger.d.ts +7 -1
- package/dist/shared/submission/submissionLedger.d.ts.map +1 -1
- package/dist/shared/submission/submissionLedger.js +8 -0
- package/dist/shared/submission/submissionLedger.js.map +1 -1
- package/dist/shared/tooling/testCommand.d.ts +1 -0
- package/dist/shared/tooling/testCommand.d.ts.map +1 -1
- package/dist/shared/tooling/testCommand.js +4 -0
- package/dist/shared/tooling/testCommand.js.map +1 -1
- package/dist/shared/types/charter.d.ts +8 -8
- package/dist/shared/types/conceptualPerspective.d.ts +66 -0
- package/dist/shared/types/conceptualPerspective.d.ts.map +1 -0
- package/dist/shared/types/conceptualPerspective.js +135 -0
- package/dist/shared/types/conceptualPerspective.js.map +1 -0
- package/dist/shared/types/contractPipeline/design.d.ts +5 -0
- package/dist/shared/types/contractPipeline/design.d.ts.map +1 -1
- package/dist/shared/types/contractPipeline/design.js +6 -0
- package/dist/shared/types/contractPipeline/design.js.map +1 -1
- package/dist/shared/types/contractPipeline/implementation.d.ts +54 -0
- package/dist/shared/types/contractPipeline/implementation.d.ts.map +1 -1
- package/dist/shared/types/contractPipeline/implementation.js +10 -5
- package/dist/shared/types/contractPipeline/implementation.js.map +1 -1
- package/dist/shared/types/contractPipeline/obligations.d.ts +21 -10
- package/dist/shared/types/contractPipeline/obligations.d.ts.map +1 -1
- package/dist/shared/types/contractPipeline/obligations.js +13 -1
- package/dist/shared/types/contractPipeline/obligations.js.map +1 -1
- package/dist/shared/types/disposition.d.ts +6 -6
- package/dist/shared/types/finding.d.ts +50 -50
- package/dist/shared/types/intentCheckpoint.d.ts +66 -11
- package/dist/shared/types/intentCheckpoint.d.ts.map +1 -1
- package/dist/shared/types/intentCheckpoint.js +10 -2
- package/dist/shared/types/intentCheckpoint.js.map +1 -1
- package/dist/shared/types/remediationOutcome.d.ts +252 -24
- package/dist/shared/types/remediationOutcome.d.ts.map +1 -1
- package/dist/shared/types/remediationOutcome.js +4 -0
- package/dist/shared/types/remediationOutcome.js.map +1 -1
- package/dist/shared/types/reviewIndependence.d.ts +225 -0
- package/dist/shared/types/reviewIndependence.d.ts.map +1 -0
- package/dist/shared/types/reviewIndependence.js +67 -0
- package/dist/shared/types/reviewIndependence.js.map +1 -0
- package/dist/shared/types/stepContract.d.ts +8 -2
- package/dist/shared/types/stepContract.d.ts.map +1 -1
- package/dist/shared/types/stepContract.js +4 -1
- package/dist/shared/types/stepContract.js.map +1 -1
- package/dist/shared/validation/findingsReport.d.ts.map +1 -1
- package/dist/shared/validation/findingsReport.js +13 -0
- package/dist/shared/validation/findingsReport.js.map +1 -1
- package/docs/audit-pkg/contracts.md +38 -2
- package/docs/audit-pkg/operator-guide.md +59 -4
- package/package.json +5 -5
- package/skills/audit-code/audit-code.prompt.md +1 -31
- package/skills/remediate-code/remediate-code.prompt.md +12 -6
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import { recordHostRootLogBoundary } from "../../../shared/observability/rootLogObservations.js";
|
|
2
|
+
import { AcceptedConformanceReviewSchema } from "../../../shared/types/reviewIndependence.js";
|
|
3
|
+
import { corroborateHostResult, corroborateNoChangeClaim } from "./hostCorroboration.js";
|
|
4
|
+
import { runRequiredTest, rerunRequiredTests, requiredTestIssue, requiredTestVerdictKey } from "./requiredTests.js";
|
|
5
|
+
import { readIntentCheckpoint } from "../../../shared/types/intentCheckpoint.js";
|
|
6
|
+
import { checkContractConformance, conformanceReviewPaths } from "./contractConformanceReview.js";
|
|
1
7
|
// sites-pinned: tests/remediate/host-handoff-corroboration.test.ts, tests/remediate/host-handoff.test.ts
|
|
2
8
|
// host-handoff-corroboration: the bounded required-test failure message.
|
|
3
9
|
// host-handoff: the "landing gates" block fails when the close-owns-the-gates prompt line
|
|
4
10
|
// or the id-glossary write scope is reverted.
|
|
5
11
|
import { mkdir } from "node:fs/promises";
|
|
6
|
-
import { isAbsolute } from "node:path";
|
|
7
|
-
import {
|
|
12
|
+
import { isAbsolute, join } from "node:path";
|
|
13
|
+
import { headCommit, commandLeavesDeclaredShape, FindingSchema, SUBMISSION_ISSUE_REMEDY, WORKLOAD_ISSUE_REMEDY, bindWorkerPrompt, deriveResultId, SUBMISSION_LEDGER_EVENT_CONTRACT_VERSION, appendSubmissionEvent, enrichMissingSubmissionIssues, isMissingObservation, compareCodeUnits, contentSha256, declaredInvariantIds, deriveLaneDemand, discoverLandingGates, hasExactKeys, identityFailureDiagnostic, hostHandoffResultPath, idsAreStrictlyAscending, isCommit, isGitRepo, isRecord, isSha256, LaneDemandSchema, normalizeRepoPath, parseAllWorkloadItems, parseWorkloadEnvelope, readSubmissionDocument, readJsonFile, readSubmissionLedger, readTrailingSubmissionRefusals, recoveryMarkMatches, recordHostResultOutcomes, repoRelativePath, resolveContainedPath, resolveHostHandoffPaths, sameStrings, scanBoundSubmission, SEVERITIES, severityRank, stringArray, stableStringify, withGlossaryScope, writeJsonFile, } from "audit-tools/shared";
|
|
8
14
|
import { REMEDIATION_STATE_CONTRACT_VERSION, } from "../../state/store.js";
|
|
9
|
-
import { REMEDIATION_HOST_HANDOFF_RECORD_V1ALPHA1, REMEDIATION_HOST_HANDOFF_RECORD_V1ALPHA2, REMEDIATION_HOST_SCOPE_SEMANTICS, RemediationHostHandoffRecordSchema, RemediationPlanSchema, isClarificationCategory, } from "../../state/types.js";
|
|
15
|
+
import { REMEDIATION_HOST_HANDOFF_RECORD_V1ALPHA1, REMEDIATION_HOST_HANDOFF_RECORD_V1ALPHA2, REMEDIATION_HOST_SCOPE_SEMANTICS, RemediationHostHandoffRecordSchema, ConformanceReviewBindingSchema, RemediationPlanSchema, isClarificationCategory, } from "../../state/types.js";
|
|
10
16
|
import { ITEM_STATUSES, isTerminalStatus, isVerifiedCompleteStatus, } from "../../state/itemStatus.js";
|
|
11
17
|
import { REMEDIATION_HOST_DECISION_CONTRACT_VERSION as DECISION_CONTRACT_VERSION, REMEDIATION_HOST_RESULT_CONTRACT_VERSION as RESULT_CONTRACT_VERSION, REMEDIATION_HOST_WORKLOAD_CONTRACT_VERSION as WORKLOAD_CONTRACT_VERSION, } from "../types.js";
|
|
12
18
|
// The state contract version is the STORE's declaration, not a second literal
|
|
@@ -14,103 +20,15 @@ import { REMEDIATION_HOST_DECISION_CONTRACT_VERSION as DECISION_CONTRACT_VERSION
|
|
|
14
20
|
// to its own parser at the call site below, so the check it performed was
|
|
15
21
|
// "does the constant equal itself".
|
|
16
22
|
const STATE_CONTRACT_VERSION = REMEDIATION_STATE_CONTRACT_VERSION;
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* `commit_not_landed` into the shared core would suggest it could emit one.
|
|
27
|
-
*/
|
|
28
|
-
export const REMEDIATION_ISSUE_CODES = [
|
|
29
|
-
...SUBMISSION_ISSUE_CODES,
|
|
30
|
-
"workload_missing",
|
|
31
|
-
"workload_invalid",
|
|
32
|
-
"trusted_binding_missing",
|
|
33
|
-
"commit_missing",
|
|
34
|
-
"commit_not_landed",
|
|
35
|
-
"baseline_not_ancestor",
|
|
36
|
-
"changed_files_mismatch",
|
|
37
|
-
"run_start_dirty_overlap",
|
|
38
|
-
"required_test_failed",
|
|
39
|
-
/**
|
|
40
|
-
* A required test exceeded its deadline. DISTINCT from `required_test_failed`
|
|
41
|
-
* by code alone: a hung suite and a genuine red are different facts about the
|
|
42
|
-
* work, and telling them apart must not require parsing a joined message.
|
|
43
|
-
*/
|
|
44
|
-
"required_test_timed_out",
|
|
45
|
-
/**
|
|
46
|
-
* A required test produced more output than the capture buffer holds, so the
|
|
47
|
-
* runner killed it. NOT a verdict on the tests: the child was terminated by
|
|
48
|
-
* the capture cap, and whether the suite would have passed is unknown. It has
|
|
49
|
-
* its own code because it was previously indistinguishable from a hang — node
|
|
50
|
-
* kills an over-buffer child with a signal, which the old discriminator read
|
|
51
|
-
* as a deadline miss.
|
|
52
|
-
*/
|
|
53
|
-
"required_test_output_overflow",
|
|
54
|
-
/**
|
|
55
|
-
* A plan block declares a dependency id that exists in NO block of the plan.
|
|
56
|
-
* The block is unschedulable — never level 0 — and the producer bug is named
|
|
57
|
-
* rather than absorbed.
|
|
58
|
-
*/
|
|
59
|
-
"dependency_missing",
|
|
60
|
-
/**
|
|
61
|
-
* A block arrived outside the normalized write-scope / declared-command shape
|
|
62
|
-
* this boundary consumes (artifact:normalized-block-write-scope). Refused, not
|
|
63
|
-
* silently normalized: a silently sorted, deduped or re-rooted write scope
|
|
64
|
-
* hides the producer bug and widens what a host may touch.
|
|
65
|
-
*/
|
|
66
|
-
"block_contract_invalid",
|
|
67
|
-
/**
|
|
68
|
-
* A recovery-mode acceptance could not be marked on the submission ledger, so
|
|
69
|
-
* it was refused. An acceptance that used the relaxation MUST stay
|
|
70
|
-
* distinguishable from a clean one; an unrecordable mark is a refusal, never
|
|
71
|
-
* a silent acceptance.
|
|
72
|
-
*/
|
|
73
|
-
"recovery_unrecorded",
|
|
74
|
-
/**
|
|
75
|
-
* The repository HEAD moved between the recovery verb's unlocked test phase
|
|
76
|
-
* and its locked write phase, so the pre-computed test verdicts describe a
|
|
77
|
-
* tree that is no longer current. The whole recovery aborts.
|
|
78
|
-
*/
|
|
79
|
-
"tree_moved_between_phases",
|
|
80
|
-
/**
|
|
81
|
-
* The run's own WORKLOAD BINDING changed between the recovery verb's unlocked
|
|
82
|
-
* test phase and its locked write phase — the sibling of
|
|
83
|
-
* `tree_moved_between_phases` for a concurrent state writer that settles items
|
|
84
|
-
* and re-mints the binding without moving a commit. The pre-computed verdicts
|
|
85
|
-
* describe work that is no longer pending, so the whole recovery aborts.
|
|
86
|
-
*/
|
|
87
|
-
"state_moved_between_phases",
|
|
88
|
-
/**
|
|
89
|
-
* The work item's baseline commit is not in this repository, so nothing can
|
|
90
|
-
* be corroborated against it. The run's recorded state is at fault, not the
|
|
91
|
-
* result.
|
|
92
|
-
*/
|
|
93
|
-
"baseline_missing",
|
|
94
|
-
/**
|
|
95
|
-
* The work item's baseline commit exists but is no longer an ancestor of HEAD
|
|
96
|
-
* (history was rewritten under the run). The spawn-free `recover-ingest` verb
|
|
97
|
-
* is the named repair; no worker can make it.
|
|
98
|
-
*/
|
|
99
|
-
"baseline_orphaned",
|
|
100
|
-
/**
|
|
101
|
-
* `landed_commit` names a commit that cannot be this item's landed work: the
|
|
102
|
-
* baseline itself, or a commit whose diff against the baseline is empty.
|
|
103
|
-
*/
|
|
104
|
-
"landed_commit_invalid",
|
|
105
|
-
/**
|
|
106
|
-
* A result names a work item that the persisted workload binds but that is no
|
|
107
|
-
* longer pending (it settled, or its dependencies are not complete).
|
|
108
|
-
*/
|
|
109
|
-
"work_item_not_eligible",
|
|
110
|
-
// `workload_stale` is shared with the audit draw: its meaning and its remedy
|
|
111
|
-
// live beside `WORKLOAD_ISSUE_CODES`.
|
|
112
|
-
...WORKLOAD_ISSUE_CODES,
|
|
113
|
-
];
|
|
23
|
+
/** A recognized preparation defect, emitted as a bounded repair step by next-step. */
|
|
24
|
+
export class RemediationHostPreparationError extends Error {
|
|
25
|
+
code;
|
|
26
|
+
constructor(code, message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.code = code;
|
|
29
|
+
this.name = "RemediationHostPreparationError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
114
32
|
/**
|
|
115
33
|
* What the host must DO about each remediation ingest code (owner review of
|
|
116
34
|
* prompt 20, 2026-09-18). The `Record` over the whole union is the enforcement:
|
|
@@ -120,6 +38,9 @@ export const REMEDIATION_ISSUE_CODES = [
|
|
|
120
38
|
const REMEDIATION_ISSUE_REMEDY = {
|
|
121
39
|
...SUBMISSION_ISSUE_REMEDY,
|
|
122
40
|
...WORKLOAD_ISSUE_REMEDY,
|
|
41
|
+
conformance_review_required: "none",
|
|
42
|
+
conformance_review_unavailable: "none",
|
|
43
|
+
conformance_review_insufficient: "repair",
|
|
123
44
|
// Here a duplicate is two result files carrying one result_id in the same
|
|
124
45
|
// call, and NEITHER is accepted — so the item is still pending and the fix is
|
|
125
46
|
// a corrected file. (The audit draw raises it only after an acceptance, so
|
|
@@ -162,6 +83,7 @@ const CURRENT_STATE_KEYS = new Set([
|
|
|
162
83
|
"contract_version",
|
|
163
84
|
"items",
|
|
164
85
|
"host_handoff",
|
|
86
|
+
"conformance_review",
|
|
165
87
|
"plan",
|
|
166
88
|
"plan_coverage",
|
|
167
89
|
"run_start_dirty",
|
|
@@ -183,6 +105,7 @@ const CURRENT_ITEM_KEYS = new Set([
|
|
|
183
105
|
"host_landed_commit",
|
|
184
106
|
"host_landed_files",
|
|
185
107
|
"host_result_evidence",
|
|
108
|
+
"conformance_review",
|
|
186
109
|
"finding_id",
|
|
187
110
|
"incomplete_coverage_attempts",
|
|
188
111
|
"last_successful_step",
|
|
@@ -201,6 +124,8 @@ function parseCurrentState(value) {
|
|
|
201
124
|
value.status !== "implementing") {
|
|
202
125
|
return null;
|
|
203
126
|
}
|
|
127
|
+
if (value.conformance_review !== undefined && !ConformanceReviewBindingSchema.safeParse(value.conformance_review).success)
|
|
128
|
+
return null;
|
|
204
129
|
const parsedPlan = RemediationPlanSchema.safeParse(value.plan);
|
|
205
130
|
if (!parsedPlan.success || !isRecord(value.items))
|
|
206
131
|
return null;
|
|
@@ -209,6 +134,9 @@ function parseCurrentState(value) {
|
|
|
209
134
|
const parsedHandoff = RemediationHostHandoffRecordSchema.safeParse(value.host_handoff);
|
|
210
135
|
if (!parsedHandoff.success)
|
|
211
136
|
return null;
|
|
137
|
+
if (parsedHandoff.data.conformance_policy_sha256 !== undefined &&
|
|
138
|
+
(value.conformance_review === undefined || contentSha256(value.conformance_review) !== parsedHandoff.data.conformance_policy_sha256))
|
|
139
|
+
return null;
|
|
212
140
|
const ids = parsedHandoff.data.work_item_ids;
|
|
213
141
|
if (new Set(ids).size !== ids.length ||
|
|
214
142
|
ids.some((id, index) => index > 0 && compareCodeUnits(ids[index - 1], id) >= 0)) {
|
|
@@ -230,6 +158,8 @@ function parseCurrentState(value) {
|
|
|
230
158
|
!knownStatuses.has(String(item.status))) {
|
|
231
159
|
return null;
|
|
232
160
|
}
|
|
161
|
+
if (item.conformance_review !== undefined && !AcceptedConformanceReviewSchema.safeParse(item.conformance_review).success)
|
|
162
|
+
return null;
|
|
233
163
|
const block = blockById.get(item.block_id);
|
|
234
164
|
if (!block || !block.items.includes(findingId))
|
|
235
165
|
return null;
|
|
@@ -251,20 +181,6 @@ function normalizeDeclaredPath(root, candidate, label) {
|
|
|
251
181
|
const normalized = repoRelativePath(root, candidate, label);
|
|
252
182
|
return candidate.endsWith("/") ? `${normalized}/` : normalized;
|
|
253
183
|
}
|
|
254
|
-
function pathIsAllowedByWriteScope(root, candidate, allowedFiles) {
|
|
255
|
-
let normalized;
|
|
256
|
-
try {
|
|
257
|
-
normalized = repoRelativePath(root, candidate, "landed file");
|
|
258
|
-
}
|
|
259
|
-
catch {
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
if (normalized !== candidate)
|
|
263
|
-
return false;
|
|
264
|
-
return allowedFiles.some((allowed) => allowed.endsWith("/")
|
|
265
|
-
? normalized.startsWith(allowed)
|
|
266
|
-
: normalized === allowed);
|
|
267
|
-
}
|
|
268
184
|
function deriveLegacyDirectoryPathsForBlock(state, blockId, declaredPaths) {
|
|
269
185
|
const binding = state.host_handoff;
|
|
270
186
|
if (binding?.contract_version !== REMEDIATION_HOST_HANDOFF_RECORD_V1ALPHA1) {
|
|
@@ -670,6 +586,7 @@ export function permanentlyDeadPendingBlocks(state) {
|
|
|
670
586
|
}
|
|
671
587
|
function buildPrompt(item) {
|
|
672
588
|
const assignment = stableStringify({
|
|
589
|
+
...(item.implementationContext ? { implementation_context: item.implementationContext } : {}),
|
|
673
590
|
allowed_files: item.allowedFiles,
|
|
674
591
|
assignments: item.assignments,
|
|
675
592
|
baseline_commit: item.baselineCommit,
|
|
@@ -897,6 +814,7 @@ function buildWorkItem(paths, runId, block, baselineCommit, state) {
|
|
|
897
814
|
hasLandingGates,
|
|
898
815
|
resultPath,
|
|
899
816
|
moduleContracts: block.module_contracts ?? [],
|
|
817
|
+
implementationContext: block.implementation_context,
|
|
900
818
|
}), (promptDigest) => renderResultTemplate({
|
|
901
819
|
runId,
|
|
902
820
|
blockId: block.block_id,
|
|
@@ -1172,223 +1090,27 @@ function parseResult(value, runId, workItem) {
|
|
|
1172
1090
|
result: value,
|
|
1173
1091
|
};
|
|
1174
1092
|
}
|
|
1175
|
-
// The corroboration probes below run while the remediation state lock (and, on
|
|
1176
|
-
// the fold path, the phase lock) is held — ASYNC with the shared deadline
|
|
1177
|
-
// (INV-SSF), so the held lock's mtime heartbeat keeps beating through each
|
|
1178
|
-
// probe and a git that never answers cannot hang the ingest.
|
|
1179
|
-
async function gitCommitExists(root, commit) {
|
|
1180
|
-
const result = await runTrackedAsync(["git", "rev-parse", "--verify", "--quiet", `${commit}^{commit}`], { cwd: root, encoding: "utf8", timeout: TRACKED_CHILD_DEADLINE_MS });
|
|
1181
|
-
return !result.error && result.status === 0;
|
|
1182
|
-
}
|
|
1183
|
-
// INV-WTS-3 (landed-node ancestry): a landed node's commit must be an ancestor
|
|
1184
|
-
// of the ref it claims to have landed on. `git merge-base --is-ancestor` exits 0
|
|
1185
|
-
// exactly when that holds.
|
|
1186
|
-
async function gitCommitIsAncestor(root, ancestor, descendant) {
|
|
1187
|
-
const result = await runTrackedAsync(["git", "merge-base", "--is-ancestor", ancestor, descendant], { cwd: root, encoding: "utf8", timeout: TRACKED_CHILD_DEADLINE_MS });
|
|
1188
|
-
return !result.error && result.status === 0;
|
|
1189
|
-
}
|
|
1190
|
-
/**
|
|
1191
|
-
* Is this commit ORPHANED — unreachable from anything the repository still
|
|
1192
|
-
* keeps?
|
|
1193
|
-
*
|
|
1194
|
-
* "Not an ancestor of HEAD" is NOT orphanhood. A baseline sitting on an
|
|
1195
|
-
* unmerged `feature` branch while the work landed on trunk fails the ancestry
|
|
1196
|
-
* test exactly like a rewritten-away commit does, and treating that as orphaned
|
|
1197
|
-
* would hand the relaxation to the ordinary cross-branch case — precisely the
|
|
1198
|
-
* stale-worker situation the ancestry check exists to catch.
|
|
1199
|
-
*
|
|
1200
|
-
* So orphanhood is the CONJUNCTION of two probes: `git for-each-ref --contains`
|
|
1201
|
-
* lists every branch/tag/remote ref whose history contains the commit (empty
|
|
1202
|
-
* output = no live ref keeps it), and the HEAD ancestry check rides alongside
|
|
1203
|
-
* it.
|
|
1204
|
-
*
|
|
1205
|
-
* A THIRD source of reachability sits outside `for-each-ref` entirely: the
|
|
1206
|
-
* HEADs of this repository's OTHER worktrees, checked into no ref at all. In a
|
|
1207
|
-
* linked worktree git DETACHES HEAD (or parks a per-worktree branch), so a
|
|
1208
|
-
* baseline that is the live HEAD of a sibling worktree — exactly the state a
|
|
1209
|
-
* parallel remediation lane is in — would be reported as contained by nothing,
|
|
1210
|
-
* i.e. orphaned, and the relaxation would be handed out for a commit the
|
|
1211
|
-
* repository is actively keeping. `git worktree list --porcelain` enumerates
|
|
1212
|
-
* every worktree with its current HEAD, so it is read and compared. The linked
|
|
1213
|
-
* worktree's own HEAD is included by this probe, which is why the ordinary
|
|
1214
|
-
* detached-HEAD case needs no separate arm.
|
|
1215
|
-
*
|
|
1216
|
-
* RESIDUAL, deliberately not closed: that probe compares HEAD for EQUALITY, so
|
|
1217
|
-
* it covers a worktree detached at the baseline ONLY when its HEAD *is* the
|
|
1218
|
-
* baseline. A sibling worktree detached at a DESCENDANT of the baseline — a
|
|
1219
|
-
* lane that landed further commits on top — keeps the baseline reachable while
|
|
1220
|
-
* matching no ref and no worktree HEAD, and is still reported orphaned. Closing
|
|
1221
|
-
* it means an ancestry probe per enumerated worktree HEAD; the residual is
|
|
1222
|
-
* stated here rather than left as an implied full cover.
|
|
1223
|
-
*
|
|
1224
|
-
* A failed scan is not evidence of orphanhood — any probe that cannot answer
|
|
1225
|
-
* fails closed, so a git that cannot answer never unlocks the relaxation.
|
|
1226
|
-
*/
|
|
1227
|
-
async function gitCommitIsOrphaned(root, commit) {
|
|
1228
|
-
if (await gitCommitIsAncestor(root, commit, "HEAD"))
|
|
1229
|
-
return false;
|
|
1230
|
-
const result = await runTrackedAsync(["git", "for-each-ref", "--contains", commit, "--format=%(refname)"], { cwd: root, encoding: "utf8", timeout: TRACKED_CHILD_DEADLINE_MS });
|
|
1231
|
-
if (result.error || result.status !== 0)
|
|
1232
|
-
return false;
|
|
1233
|
-
if (result.stdout.trim().length > 0)
|
|
1234
|
-
return false;
|
|
1235
|
-
// Reached only when no REF keeps the commit. Every worktree HEAD is then
|
|
1236
|
-
// checked, so the detached (and per-worktree-branch) HEADs git does not
|
|
1237
|
-
// enumerate as refs are still seen.
|
|
1238
|
-
const worktrees = await runTrackedAsync(["git", "worktree", "list", "--porcelain"], { cwd: root, encoding: "utf8", timeout: TRACKED_CHILD_DEADLINE_MS });
|
|
1239
|
-
if (worktrees.error || worktrees.status !== 0)
|
|
1240
|
-
return false;
|
|
1241
|
-
return !worktrees.stdout
|
|
1242
|
-
.split("\n")
|
|
1243
|
-
.filter((line) => line.startsWith("HEAD "))
|
|
1244
|
-
.some((line) => line.slice("HEAD ".length).trim() === commit);
|
|
1245
|
-
}
|
|
1246
|
-
async function gitChangedFilesOfCommit(root, commit) {
|
|
1247
|
-
const result = await runTrackedAsync([
|
|
1248
|
-
"git",
|
|
1249
|
-
"diff-tree",
|
|
1250
|
-
"--root",
|
|
1251
|
-
"--no-commit-id",
|
|
1252
|
-
"--name-only",
|
|
1253
|
-
"-r",
|
|
1254
|
-
"-z",
|
|
1255
|
-
commit,
|
|
1256
|
-
], { cwd: root, encoding: "utf8", timeout: TRACKED_CHILD_DEADLINE_MS });
|
|
1257
|
-
if (result.error || result.status !== 0)
|
|
1258
|
-
return null;
|
|
1259
|
-
return [...new Set(result.stdout.split("\0").filter(Boolean))].sort(compareCodeUnits);
|
|
1260
|
-
}
|
|
1261
|
-
/**
|
|
1262
|
-
* Every repo-relative path the tree shows as touched since `baseline` — the
|
|
1263
|
-
* commits baseline→HEAD, the working tree's own deviation from HEAD (staged and
|
|
1264
|
-
* unstaged alike), and the untracked files git considers repository content.
|
|
1265
|
-
*
|
|
1266
|
-
* All three legs are needed to falsify a no-change claim, and they enumerate the
|
|
1267
|
-
* three ways a host can have edited: committed (leg 1), edited a TRACKED file
|
|
1268
|
-
* and left it uncommitted (leg 2), and CREATED a file (leg 3). A new `src/*.ts`
|
|
1269
|
-
* is a real edit and the most natural shape a remediation takes; without leg 3
|
|
1270
|
-
* the cheapest way to smuggle one past a no-change claim was simply never to
|
|
1271
|
-
* `git add` it. `null` means git could not answer, which callers must treat as
|
|
1272
|
-
* "cannot corroborate" rather than as "nothing changed".
|
|
1273
|
-
*
|
|
1274
|
-
* The untracked leg honours `--exclude-standard`, so it enumerates only what git
|
|
1275
|
-
* itself treats as content — a repo's `.gitignore`d build and coverage output is
|
|
1276
|
-
* already invisible to it. Two exemptions cover the remainder, both ground
|
|
1277
|
-
* truth rather than the host's word:
|
|
1278
|
-
*
|
|
1279
|
-
* - THIS TOOL'S OWN ARTIFACT TREE ({@link AUDIT_TOOLS_DIRNAME}) is subtracted
|
|
1280
|
-
* here, explicitly. In a real repository the tool writes a managed
|
|
1281
|
-
* `.gitignore` block covering it, so it never reaches this probe at all; the
|
|
1282
|
-
* explicit subtraction is what makes that independent of whether the block
|
|
1283
|
-
* has been written yet, so a bare root (a fixture, a first run) cannot
|
|
1284
|
-
* manufacture a false refusal out of the tool's own workload, prompt and
|
|
1285
|
-
* result documents.
|
|
1286
|
-
* - PRE-EXISTING untracked strays are excused by the caller's `excusedPaths`,
|
|
1287
|
-
* for free: `run_start_dirty` is captured from `stagedAndUntracked` before
|
|
1288
|
-
* any remediation edit exists, so it already enumerates untracked files.
|
|
1289
|
-
* What survives both is an untracked file that appeared DURING the run — the
|
|
1290
|
-
* only untracked class that can be this host's edit.
|
|
1291
|
-
*/
|
|
1292
|
-
async function gitChangedFilesSince(root, baseline) {
|
|
1293
|
-
const files = new Set();
|
|
1294
|
-
for (const args of [
|
|
1295
|
-
// baseline → HEAD: what the host committed.
|
|
1296
|
-
["diff", "--name-only", "-z", baseline, "HEAD"],
|
|
1297
|
-
// HEAD → working tree: what the host edited and did not commit.
|
|
1298
|
-
["diff", "--name-only", "-z", "HEAD"],
|
|
1299
|
-
// Never added: what the host CREATED. `--exclude-standard` keeps git's own
|
|
1300
|
-
// ignore rules authoritative.
|
|
1301
|
-
["ls-files", "--others", "--exclude-standard", "-z"],
|
|
1302
|
-
]) {
|
|
1303
|
-
const probe = await runTrackedAsync(["git", ...args], {
|
|
1304
|
-
cwd: root,
|
|
1305
|
-
encoding: "utf8",
|
|
1306
|
-
timeout: TRACKED_CHILD_DEADLINE_MS,
|
|
1307
|
-
});
|
|
1308
|
-
if (probe.error || probe.status !== 0)
|
|
1309
|
-
return null;
|
|
1310
|
-
for (const file of probe.stdout.split("\0").filter(Boolean)) {
|
|
1311
|
-
if (isAuditToolsArtifactPath(file))
|
|
1312
|
-
continue;
|
|
1313
|
-
files.add(file);
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
return [...files].sort(compareCodeUnits);
|
|
1317
|
-
}
|
|
1318
|
-
/** Whether a repo-relative path lives inside this tool's own artifact tree. */
|
|
1319
|
-
function isAuditToolsArtifactPath(path) {
|
|
1320
|
-
const normalized = normalizeRepoPath(path);
|
|
1321
|
-
return (normalized === AUDIT_TOOLS_DIRNAME ||
|
|
1322
|
-
normalized.startsWith(`${AUDIT_TOOLS_DIRNAME}/`));
|
|
1323
|
-
}
|
|
1324
1093
|
/**
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1327
|
-
* A no-change decision used to be accepted on its evidence STRINGS alone, with
|
|
1328
|
-
* only the required tests re-run — so a host that had in fact edited and then
|
|
1329
|
-
* declared "nothing to do" was recorded as verified-no-change, and the edit
|
|
1330
|
-
* rode into the run unattributed. The claim is mechanically falsifiable, so it
|
|
1331
|
-
* is FALSIFIED.
|
|
1094
|
+
* Pre-computed required-test verdicts, keyed by `root` + command: `null` =
|
|
1095
|
+
* green, a string = the failure detail.
|
|
1332
1096
|
*
|
|
1333
|
-
*
|
|
1334
|
-
*
|
|
1335
|
-
*
|
|
1336
|
-
*
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1097
|
+
* This is the recovery path's ANSWER TABLE, not a lazy cache. A required-test
|
|
1098
|
+
* rerun is a `spawnSync`, which blocks the event loop for its whole duration —
|
|
1099
|
+
* so running one inside the state lock would starve the lock's own heartbeat
|
|
1100
|
+
* timer and let a second acquirer reclaim the lock as stale mid-hold. The
|
|
1101
|
+
* recovery verb therefore runs every distinct command ONCE, up front and
|
|
1102
|
+
* unlocked ({@link precomputeRecoveryTestVerdicts}), and hands the finished
|
|
1103
|
+
* table to the locked phase, which only ever READS it.
|
|
1340
1104
|
*
|
|
1341
|
-
*
|
|
1342
|
-
*
|
|
1343
|
-
*
|
|
1344
|
-
* `
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
* Fails CLOSED, like every other corroboration here: a git that cannot answer
|
|
1350
|
-
* refuses the claim rather than admitting it.
|
|
1105
|
+
* Two consequences are deliberate. A command absent from the table is treated
|
|
1106
|
+
* as FAILED, never spawned — fail-closed is the only answer that keeps the
|
|
1107
|
+
* no-spawn-under-the-lock property mechanical rather than remembered. And the
|
|
1108
|
+
* table is recovery-only: a `targeted_command` is host-authored and need not be
|
|
1109
|
+
* idempotent (one that appends to a log, bumps a counter, or is flaky produces
|
|
1110
|
+
* a genuinely different second run), so collapsing spawns is a behavior change.
|
|
1111
|
+
* The normal lane passes `null` and stays byte-identical to the pre-recovery
|
|
1112
|
+
* behavior — every command spawns once per work item, exactly as before.
|
|
1351
1113
|
*/
|
|
1352
|
-
async function corroborateNoChangeClaim(params) {
|
|
1353
|
-
const { root, workItem, excusedPaths } = params;
|
|
1354
|
-
if (!(await isGitRepo(root)))
|
|
1355
|
-
return { ok: true };
|
|
1356
|
-
const baseline = workItem.baseline_commit;
|
|
1357
|
-
if (!(await gitCommitExists(root, baseline))) {
|
|
1358
|
-
return {
|
|
1359
|
-
ok: false,
|
|
1360
|
-
code: "commit_missing",
|
|
1361
|
-
check: "no_change_corroboration",
|
|
1362
|
-
message: "resolved_no_change cannot be corroborated: baseline_commit does not resolve to a real commit",
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
|
-
const changed = await gitChangedFilesSince(root, baseline);
|
|
1366
|
-
if (changed === null) {
|
|
1367
|
-
return {
|
|
1368
|
-
ok: false,
|
|
1369
|
-
code: "commit_missing",
|
|
1370
|
-
check: "no_change_corroboration",
|
|
1371
|
-
message: "resolved_no_change cannot be corroborated: git could not enumerate the changes since the workload baseline",
|
|
1372
|
-
};
|
|
1373
|
-
}
|
|
1374
|
-
const violating = changed.filter((path) => !excusedPaths.has(normalizeRepoPath(path)));
|
|
1375
|
-
if (violating.length > 0) {
|
|
1376
|
-
const inScope = violating.filter((path) => pathIsAllowedByWriteScope(root, path, workItem.allowed_files));
|
|
1377
|
-
const outOfScope = violating.filter((path) => !pathIsAllowedByWriteScope(root, path, workItem.allowed_files));
|
|
1378
|
-
return {
|
|
1379
|
-
ok: false,
|
|
1380
|
-
code: "changed_files_mismatch",
|
|
1381
|
-
check: "no_change_corroboration",
|
|
1382
|
-
message: "resolved_no_change is contradicted by the tree — these files changed since the " +
|
|
1383
|
-
`workload baseline: ${violating.join(", ")}` +
|
|
1384
|
-
(outOfScope.length > 0
|
|
1385
|
-
? ` (outside the prompt-bound allowed_files: ${outOfScope.join(", ")}` +
|
|
1386
|
-
(inScope.length > 0 ? `; inside: ${inScope.join(", ")})` : ")")
|
|
1387
|
-
: ""),
|
|
1388
|
-
};
|
|
1389
|
-
}
|
|
1390
|
-
return { ok: true };
|
|
1391
|
-
}
|
|
1392
1114
|
/**
|
|
1393
1115
|
* The MINTED verdict table: a Map subclass that exists only inside this module.
|
|
1394
1116
|
*
|
|
@@ -1467,245 +1189,6 @@ function unresolvedFrontierWorkItemIds(state, record) {
|
|
|
1467
1189
|
.filter((id) => pendingBlockIds.has(id))
|
|
1468
1190
|
.sort(compareCodeUnits);
|
|
1469
1191
|
}
|
|
1470
|
-
/**
|
|
1471
|
-
* Per-command deadline. A required test is host-authored and may legitimately be
|
|
1472
|
-
* a full suite, so the bound is generous; what changed is that hitting it is now
|
|
1473
|
-
* a NAMED outcome instead of an unlabelled failure string.
|
|
1474
|
-
*/
|
|
1475
|
-
const REQUIRED_TEST_TIMEOUT_MS = 10 * 60 * 1_000;
|
|
1476
|
-
/**
|
|
1477
|
-
* Captured output is bounded and TAIL-biased: a failing suite's verdict is at
|
|
1478
|
-
* the end, and an unbounded capture would put a whole test log into state and
|
|
1479
|
-
* into every rendered issue.
|
|
1480
|
-
*/
|
|
1481
|
-
const CAPTURED_OUTPUT_LIMIT = 4_000;
|
|
1482
|
-
/**
|
|
1483
|
-
* The bound on the MESSAGE a required-test failure produces.
|
|
1484
|
-
*
|
|
1485
|
-
* `CAPTURED_OUTPUT_LIMIT` bounds one STREAM; the message is a different thing —
|
|
1486
|
-
* `requiredTestIssue` joins every failing command, and a work item may bind any
|
|
1487
|
-
* number of them, so the rendered message grew with the number of failures. A
|
|
1488
|
-
* message is a HOST-FACING delivery (it is rendered into the step prompt an
|
|
1489
|
-
* operator reads), so its size cannot be a function of how many commands
|
|
1490
|
-
* happened to fail.
|
|
1491
|
-
*
|
|
1492
|
-
* The bound TRUNCATES the excerpt and says so; it never drops the verdict. What
|
|
1493
|
-
* survives at the front is the part that identifies the failure — the command
|
|
1494
|
-
* and its outcome — and the marker tells the operator the rest was elided and
|
|
1495
|
-
* where to look instead.
|
|
1496
|
-
*/
|
|
1497
|
-
export const REQUIRED_TEST_MESSAGE_LIMIT = 8_000;
|
|
1498
|
-
/** The marker that names an elided excerpt, so truncation is never silent. */
|
|
1499
|
-
const REQUIRED_TEST_TRUNCATION_MARKER = "… [excerpt truncated — re-run the command to see the full output]";
|
|
1500
|
-
/**
|
|
1501
|
-
* The spawn's raw capture buffer. Exceeding it does not truncate — node KILLS
|
|
1502
|
-
* the child — so the cap is a named constant the `output_overflow` message can
|
|
1503
|
-
* quote, rather than a literal buried in the spawn options.
|
|
1504
|
-
*/
|
|
1505
|
-
const REQUIRED_TEST_MAX_BUFFER_BYTES = 8 * 1_024 * 1_024;
|
|
1506
|
-
function tail(value) {
|
|
1507
|
-
const text = value ?? "";
|
|
1508
|
-
return text.length <= CAPTURED_OUTPUT_LIMIT
|
|
1509
|
-
? text
|
|
1510
|
-
: `…${text.slice(text.length - CAPTURED_OUTPUT_LIMIT)}`;
|
|
1511
|
-
}
|
|
1512
|
-
/**
|
|
1513
|
-
* Render one classified failure for a host-facing issue message.
|
|
1514
|
-
*
|
|
1515
|
-
* `output_overflow` says the verdict is UNKNOWN, not that the tests were fine: a
|
|
1516
|
-
* child killed at the buffer cap may have been heading for exit 0 or exit 3, and
|
|
1517
|
-
* the runner cannot tell which. Either way the item is refused — the honest
|
|
1518
|
-
* report is "we could not find out", and it fails closed.
|
|
1519
|
-
*
|
|
1520
|
-
* A signal-killed child renders the SIGNAL, not `exit null`: `exit_code` is null
|
|
1521
|
-
* for every non-exit outcome, so printing it there described nothing.
|
|
1522
|
-
*/
|
|
1523
|
-
function describeRequiredTestFailure(failure) {
|
|
1524
|
-
const head = failure.outcome === "timed_out"
|
|
1525
|
-
? `${failure.command} (timed out)`
|
|
1526
|
-
: failure.outcome === "output_overflow"
|
|
1527
|
-
? `${failure.command} (killed after exceeding the ${String(REQUIRED_TEST_MAX_BUFFER_BYTES)}-byte ` +
|
|
1528
|
-
"output buffer — the run ended at the capture cap, so whether the tests pass is UNKNOWN)"
|
|
1529
|
-
: failure.outcome === "spawn_error"
|
|
1530
|
-
? `${failure.command} (could not be started)`
|
|
1531
|
-
: failure.exit_code === null
|
|
1532
|
-
? `${failure.command} (terminated by ${failure.signal ?? "an unreported signal"})`
|
|
1533
|
-
: `${failure.command} (exit ${String(failure.exit_code)})`;
|
|
1534
|
-
const captured = [failure.stdout, failure.stderr]
|
|
1535
|
-
.filter((stream) => stream.trim().length > 0)
|
|
1536
|
-
.join("\n");
|
|
1537
|
-
return captured.length > 0 ? `${head}: ${captured}` : head;
|
|
1538
|
-
}
|
|
1539
|
-
/**
|
|
1540
|
-
* Length-prefixed so the root/command boundary is unambiguous for any path, and
|
|
1541
|
-
* printable so the source stays text (a raw separator byte would make the file
|
|
1542
|
-
* binary to git and invisible to grep). The root is part of the key because a
|
|
1543
|
-
* verdict is a fact about one command in one working tree, and nothing
|
|
1544
|
-
* guarantees a single process only ever ingests for one root.
|
|
1545
|
-
*/
|
|
1546
|
-
function requiredTestVerdictKey(root, command) {
|
|
1547
|
-
return `${String(root.length)}:${root}:${command}`;
|
|
1548
|
-
}
|
|
1549
|
-
/**
|
|
1550
|
-
* The ONE place a required-test command is spawned.
|
|
1551
|
-
*
|
|
1552
|
-
* `timeoutMs` is a parameter so the deadline is exercisable: a hang is a
|
|
1553
|
-
* first-class outcome of this function, and an outcome that can only be reached
|
|
1554
|
-
* by waiting ten real minutes is an outcome nothing ever tests.
|
|
1555
|
-
*/
|
|
1556
|
-
export async function runRequiredTest(root, command, timeoutMs = REQUIRED_TEST_TIMEOUT_MS) {
|
|
1557
|
-
// AWAITED, never `spawnSync`: ingestion runs with the remediation state lock
|
|
1558
|
-
// held, and a synchronous child blocks the event loop for the whole suite —
|
|
1559
|
-
// starving the lock's mtime heartbeat until a LIVE lock is classified stale
|
|
1560
|
-
// and stolen mid-ingest.
|
|
1561
|
-
//
|
|
1562
|
-
// argv + `shell: false`, never a shell string. A required test is a workload
|
|
1563
|
-
// command that already cleared the declared-shape gate at the producer, so
|
|
1564
|
-
// splitting it is unambiguous, and dropping the shell removes the last place
|
|
1565
|
-
// an ingest hands a declared string to `sh`/`cmd.exe`. `resolveExecArgv`
|
|
1566
|
-
// inside the runner is what keeps the npm/npx shims resolvable on win32.
|
|
1567
|
-
const result = await runTrackedAsync(parseCommandString(command), {
|
|
1568
|
-
cwd: root,
|
|
1569
|
-
// Captured, not discarded: without it a red ingest reports that something
|
|
1570
|
-
// failed and nothing about why.
|
|
1571
|
-
encoding: "utf8",
|
|
1572
|
-
maxBuffer: REQUIRED_TEST_MAX_BUFFER_BYTES,
|
|
1573
|
-
timeout: timeoutMs,
|
|
1574
|
-
windowsHide: true,
|
|
1575
|
-
});
|
|
1576
|
-
const stdout = tail(result.stdout);
|
|
1577
|
-
const stderr = tail(result.stderr);
|
|
1578
|
-
// The ERROR CODE discriminates, never `signal`. node kills an over-deadline
|
|
1579
|
-
// child AND an over-`maxBuffer` child, and an external `kill` sets `signal`
|
|
1580
|
-
// too — so `signal !== null` was true for three unrelated facts and reported
|
|
1581
|
-
// all of them as a hang, including a command killed purely for printing more
|
|
1582
|
-
// than the buffer holds.
|
|
1583
|
-
//
|
|
1584
|
-
// ASSUMPTION, stated: a deadline miss reports `ETIMEDOUT`. Verified on win32;
|
|
1585
|
-
// it is node's documented contract, not a platform quirk this code confirmed
|
|
1586
|
-
// everywhere. On a platform that killed a child at the deadline WITHOUT that
|
|
1587
|
-
// code, the case degrades to `spawn_error` — a less specific refusal, still a
|
|
1588
|
-
// refusal, so the fail direction holds and only the label is lost.
|
|
1589
|
-
const code = result.error?.code;
|
|
1590
|
-
if (code === "ETIMEDOUT") {
|
|
1591
|
-
return { command, outcome: "timed_out", exit_code: null, stdout, stderr };
|
|
1592
|
-
}
|
|
1593
|
-
if (code === "ENOBUFS") {
|
|
1594
|
-
return {
|
|
1595
|
-
command,
|
|
1596
|
-
outcome: "output_overflow",
|
|
1597
|
-
exit_code: null,
|
|
1598
|
-
stdout,
|
|
1599
|
-
stderr,
|
|
1600
|
-
};
|
|
1601
|
-
}
|
|
1602
|
-
if (result.error) {
|
|
1603
|
-
return {
|
|
1604
|
-
command,
|
|
1605
|
-
outcome: "spawn_error",
|
|
1606
|
-
exit_code: null,
|
|
1607
|
-
stdout,
|
|
1608
|
-
stderr: stderr.length > 0 ? stderr : result.error.message,
|
|
1609
|
-
};
|
|
1610
|
-
}
|
|
1611
|
-
// Killed by something outside this runner (an operator `kill`, an OOM reaper).
|
|
1612
|
-
// Reported as FAILED with the signal named: the command did not complete, and
|
|
1613
|
-
// calling it a deadline miss would attribute it to a bound this runner set.
|
|
1614
|
-
//
|
|
1615
|
-
// POSIX-ONLY IN PRACTICE, and UNTESTED for that reason: Windows has no signal
|
|
1616
|
-
// delivery to report here — a killed child surfaces as an ordinary non-zero
|
|
1617
|
-
// `status` with `signal` null — so this branch is unreachable on the platform
|
|
1618
|
-
// this repo runs its suites on, and no test exercises it. It is kept because
|
|
1619
|
-
// the runner is OS-agnostic by contract, not because it has been observed.
|
|
1620
|
-
if (result.signal !== null && result.signal !== undefined) {
|
|
1621
|
-
return {
|
|
1622
|
-
command,
|
|
1623
|
-
outcome: "failed",
|
|
1624
|
-
exit_code: null,
|
|
1625
|
-
stdout,
|
|
1626
|
-
stderr,
|
|
1627
|
-
signal: result.signal,
|
|
1628
|
-
};
|
|
1629
|
-
}
|
|
1630
|
-
if (result.status !== 0) {
|
|
1631
|
-
return {
|
|
1632
|
-
command,
|
|
1633
|
-
outcome: "failed",
|
|
1634
|
-
exit_code: result.status,
|
|
1635
|
-
stdout,
|
|
1636
|
-
stderr,
|
|
1637
|
-
};
|
|
1638
|
-
}
|
|
1639
|
-
return null;
|
|
1640
|
-
}
|
|
1641
|
-
async function rerunRequiredTests(root, commands,
|
|
1642
|
-
/** `null` on the normal lane — see {@link RemediationRequiredTestVerdicts}. */
|
|
1643
|
-
verdicts) {
|
|
1644
|
-
const failures = [];
|
|
1645
|
-
for (const command of commands) {
|
|
1646
|
-
if (verdicts) {
|
|
1647
|
-
const verdict = verdicts.get(requiredTestVerdictKey(root, command));
|
|
1648
|
-
if (verdict === undefined) {
|
|
1649
|
-
failures.push({
|
|
1650
|
-
command,
|
|
1651
|
-
outcome: "spawn_error",
|
|
1652
|
-
exit_code: null,
|
|
1653
|
-
stdout: "",
|
|
1654
|
-
stderr: "no pre-computed verdict — refusing to spawn a test while the state lock is held",
|
|
1655
|
-
});
|
|
1656
|
-
}
|
|
1657
|
-
else if (verdict !== null) {
|
|
1658
|
-
failures.push(verdict);
|
|
1659
|
-
}
|
|
1660
|
-
continue;
|
|
1661
|
-
}
|
|
1662
|
-
const failure = await runRequiredTest(root, command);
|
|
1663
|
-
if (failure !== null)
|
|
1664
|
-
failures.push(failure);
|
|
1665
|
-
}
|
|
1666
|
-
return failures;
|
|
1667
|
-
}
|
|
1668
|
-
/**
|
|
1669
|
-
* The classified issue for a set of required-test failures. An ENVIRONMENT fact
|
|
1670
|
-
* anywhere in the set wins over a red sibling, timeout first: a hung or
|
|
1671
|
-
* buffer-killed suite is the fact that explains the ingest, and burying it under
|
|
1672
|
-
* a sibling's exit code is exactly the conflation the code split exists to end.
|
|
1673
|
-
* Only a set where every failure is a genuine non-zero exit reads as
|
|
1674
|
-
* `required_test_failed`.
|
|
1675
|
-
*/
|
|
1676
|
-
function requiredTestIssue(workItem, failures) {
|
|
1677
|
-
// Bounded at the point the message is BUILT, not left to the caller: the
|
|
1678
|
-
// failure count is a property of the work item's bound commands, so bounding
|
|
1679
|
-
// anywhere downstream would still have carried an unbounded string through
|
|
1680
|
-
// state and into the ledger. See REQUIRED_TEST_MESSAGE_LIMIT.
|
|
1681
|
-
const body = `mechanical required-test rerun failed: ${failures
|
|
1682
|
-
.map(describeRequiredTestFailure)
|
|
1683
|
-
.join("; ")}`;
|
|
1684
|
-
return {
|
|
1685
|
-
code: failures.some((failure) => failure.outcome === "timed_out")
|
|
1686
|
-
? "required_test_timed_out"
|
|
1687
|
-
: failures.some((failure) => failure.outcome === "output_overflow")
|
|
1688
|
-
? "required_test_output_overflow"
|
|
1689
|
-
: "required_test_failed",
|
|
1690
|
-
check: "required_tests",
|
|
1691
|
-
work_item_id: workItem.id,
|
|
1692
|
-
result_path: workItem.result_path,
|
|
1693
|
-
message: boundRequiredTestMessage(body),
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
/**
|
|
1697
|
-
* Truncate a required-test failure message to {@link REQUIRED_TEST_MESSAGE_LIMIT},
|
|
1698
|
-
* marking the elision. The head is kept because it is what identifies the
|
|
1699
|
-
* failure — the command and its classified outcome — and the marker replaces
|
|
1700
|
-
* the tail rather than being appended past the cap, so the result is bounded by
|
|
1701
|
-
* construction.
|
|
1702
|
-
*/
|
|
1703
|
-
function boundRequiredTestMessage(message) {
|
|
1704
|
-
if (message.length <= REQUIRED_TEST_MESSAGE_LIMIT)
|
|
1705
|
-
return message;
|
|
1706
|
-
const room = REQUIRED_TEST_MESSAGE_LIMIT - REQUIRED_TEST_TRUNCATION_MARKER.length;
|
|
1707
|
-
return `${message.slice(0, room)}${REQUIRED_TEST_TRUNCATION_MARKER}`;
|
|
1708
|
-
}
|
|
1709
1192
|
/**
|
|
1710
1193
|
* Run every required-test command a recovery ingest could need, ONCE each, and
|
|
1711
1194
|
* return the finished verdict table. Call this OUTSIDE the state lock — that is
|
|
@@ -1748,121 +1231,6 @@ export async function precomputeRecoveryTestVerdicts(params) {
|
|
|
1748
1231
|
}
|
|
1749
1232
|
return verdicts;
|
|
1750
1233
|
}
|
|
1751
|
-
async function corroborateHostResult(params) {
|
|
1752
|
-
const { root, state, workItem, result, verdicts } = params;
|
|
1753
|
-
const baseline = workItem.baseline_commit;
|
|
1754
|
-
const landed = result.landed_commit;
|
|
1755
|
-
let usedRecovery = false;
|
|
1756
|
-
if (!(await gitCommitExists(root, baseline))) {
|
|
1757
|
-
return {
|
|
1758
|
-
ok: false,
|
|
1759
|
-
code: "baseline_missing",
|
|
1760
|
-
check: "landed_commit",
|
|
1761
|
-
message: "the work item's baseline_commit is not in this repository",
|
|
1762
|
-
};
|
|
1763
|
-
}
|
|
1764
|
-
if (!(await gitCommitExists(root, landed))) {
|
|
1765
|
-
return {
|
|
1766
|
-
ok: false,
|
|
1767
|
-
code: "commit_missing",
|
|
1768
|
-
check: "landed_commit",
|
|
1769
|
-
message: "landed_commit is not a commit in this repository",
|
|
1770
|
-
};
|
|
1771
|
-
}
|
|
1772
|
-
if (landed === baseline) {
|
|
1773
|
-
return {
|
|
1774
|
-
ok: false,
|
|
1775
|
-
code: "landed_commit_invalid",
|
|
1776
|
-
check: "landed_commit",
|
|
1777
|
-
message: "landed_commit is the baseline commit; name the commit that carries this item's edits",
|
|
1778
|
-
};
|
|
1779
|
-
}
|
|
1780
|
-
if (!(await gitCommitIsAncestor(root, baseline, landed))) {
|
|
1781
|
-
if (!params.recovery) {
|
|
1782
|
-
// An ORPHANED baseline (no ref contains it, HEAD cannot reach it) is a
|
|
1783
|
-
// fault in the run, not in the work: no commit could ever descend from
|
|
1784
|
-
// it. The spawn-free recovery verb is its named repair.
|
|
1785
|
-
if (await gitCommitIsOrphaned(root, baseline)) {
|
|
1786
|
-
return {
|
|
1787
|
-
ok: false,
|
|
1788
|
-
code: "baseline_orphaned",
|
|
1789
|
-
check: "landed_commit",
|
|
1790
|
-
message: "the work item's baseline_commit is orphaned (history was rewritten under the run); " +
|
|
1791
|
-
"the operator can run `recover-ingest` to accept the landed work",
|
|
1792
|
-
};
|
|
1793
|
-
}
|
|
1794
|
-
return {
|
|
1795
|
-
ok: false,
|
|
1796
|
-
code: "baseline_not_ancestor",
|
|
1797
|
-
check: "landed_commit",
|
|
1798
|
-
message: "the trusted workload baseline is not an ancestor of the claimed landed commit",
|
|
1799
|
-
};
|
|
1800
|
-
}
|
|
1801
|
-
// The relaxation is precondition-bound: it applies ONLY when the trusted
|
|
1802
|
-
// baseline is genuinely ORPHANED — contained by no ref AND unreachable from
|
|
1803
|
-
// HEAD (see gitCommitIsOrphaned). That is the one state in which no landed
|
|
1804
|
-
// commit could ever descend from it, so the item is unacceptable under
|
|
1805
|
-
// every preparable binding. A baseline the repository still keeps — on an
|
|
1806
|
-
// unmerged branch, a tag, a remote ref, or HEAD itself — is a HEALTHY
|
|
1807
|
-
// binding, and a landed commit that does not descend from it is exactly the
|
|
1808
|
-
// stale-worker case the ancestry check exists to catch; recovery refuses it
|
|
1809
|
-
// identically to the normal lane.
|
|
1810
|
-
if (!(await gitCommitIsOrphaned(root, baseline))) {
|
|
1811
|
-
return {
|
|
1812
|
-
ok: false,
|
|
1813
|
-
code: "baseline_not_ancestor",
|
|
1814
|
-
check: "landed_commit",
|
|
1815
|
-
message: "the trusted workload baseline is not an ancestor of the claimed landed commit, " +
|
|
1816
|
-
"and the baseline is NOT orphaned (a ref still contains it, or it is reachable " +
|
|
1817
|
-
"from HEAD), so the stale-worker protection stands and recovery cannot waive it",
|
|
1818
|
-
};
|
|
1819
|
-
}
|
|
1820
|
-
usedRecovery = true;
|
|
1821
|
-
}
|
|
1822
|
-
if (!(await gitCommitIsAncestor(root, landed, "HEAD"))) {
|
|
1823
|
-
return {
|
|
1824
|
-
ok: false,
|
|
1825
|
-
code: "commit_not_landed",
|
|
1826
|
-
check: "landed_commit",
|
|
1827
|
-
message: "landed_commit is not reachable from the repository HEAD",
|
|
1828
|
-
};
|
|
1829
|
-
}
|
|
1830
|
-
const actualFiles = await gitChangedFilesOfCommit(root, landed);
|
|
1831
|
-
if (!actualFiles || actualFiles.length === 0) {
|
|
1832
|
-
return {
|
|
1833
|
-
ok: false,
|
|
1834
|
-
code: "landed_commit_invalid",
|
|
1835
|
-
check: "landed_commit",
|
|
1836
|
-
message: "landed_commit changes no file; name the commit that carries this item's edits",
|
|
1837
|
-
};
|
|
1838
|
-
}
|
|
1839
|
-
const outOfScope = actualFiles.filter((path) => !pathIsAllowedByWriteScope(root, path, workItem.allowed_files));
|
|
1840
|
-
if (outOfScope.length > 0) {
|
|
1841
|
-
return {
|
|
1842
|
-
ok: false,
|
|
1843
|
-
code: "changed_files_mismatch",
|
|
1844
|
-
check: "write_scope",
|
|
1845
|
-
message: "the landed commit changed files outside the prompt-bound allowed_files: " +
|
|
1846
|
-
outOfScope.join(", "),
|
|
1847
|
-
};
|
|
1848
|
-
}
|
|
1849
|
-
const runStartDirty = new Set((state.run_start_dirty ?? []).map(normalizeRepoPath));
|
|
1850
|
-
const dirtyOverlap = actualFiles.filter((path) => runStartDirty.has(normalizeRepoPath(path)));
|
|
1851
|
-
if (dirtyOverlap.length > 0) {
|
|
1852
|
-
return {
|
|
1853
|
-
ok: false,
|
|
1854
|
-
code: "run_start_dirty_overlap",
|
|
1855
|
-
check: "run_start_dirt",
|
|
1856
|
-
message: `landed files overlap pre-existing run-start dirt: ${dirtyOverlap.join(", ")}`,
|
|
1857
|
-
};
|
|
1858
|
-
}
|
|
1859
|
-
const failedTests = await rerunRequiredTests(root, workItem.required_tests, verdicts);
|
|
1860
|
-
if (failedTests.length > 0) {
|
|
1861
|
-
const issue = requiredTestIssue(workItem, failedTests);
|
|
1862
|
-
return { ok: false, code: issue.code, check: "required_tests", message: issue.message };
|
|
1863
|
-
}
|
|
1864
|
-
return { ok: true, changedFiles: actualFiles, usedRecovery };
|
|
1865
|
-
}
|
|
1866
1234
|
/**
|
|
1867
1235
|
* Was this persisted workload written under an earlier workload contract
|
|
1868
1236
|
* version? Only a document that NAMES a different version counts: a document
|
|
@@ -1873,9 +1241,22 @@ function persistedWorkloadIsStale(value) {
|
|
|
1873
1241
|
typeof value.contract_version === "string" &&
|
|
1874
1242
|
value.contract_version !== WORKLOAD_CONTRACT_VERSION);
|
|
1875
1243
|
}
|
|
1876
|
-
async function
|
|
1877
|
-
const
|
|
1878
|
-
|
|
1244
|
+
async function persistedWorkloadAllowsRebind(workloadPath, expected) {
|
|
1245
|
+
const value = await readJsonFile(workloadPath);
|
|
1246
|
+
// A version change can alter shape, never the run/baseline/work identities.
|
|
1247
|
+
if (!isRecord(value) || value.run_id !== expected.run_id || !Array.isArray(value.work_items))
|
|
1248
|
+
return false;
|
|
1249
|
+
const ids = [];
|
|
1250
|
+
for (const item of value.work_items) {
|
|
1251
|
+
if (!isRecord(item) || typeof item.id !== "string" || item.baseline_commit !== expected.baseline_commit)
|
|
1252
|
+
return false;
|
|
1253
|
+
ids.push(item.id);
|
|
1254
|
+
}
|
|
1255
|
+
if (new Set(ids).size !== ids.length ||
|
|
1256
|
+
!sameStrings(ids.sort(compareCodeUnits), [...expected.work_item_ids].sort(compareCodeUnits)))
|
|
1257
|
+
return false;
|
|
1258
|
+
// Ordinary state movement also proves the predecessor's complete digest.
|
|
1259
|
+
return persistedWorkloadIsStale(value) || contentSha256(value) === expected.workload_sha256;
|
|
1879
1260
|
}
|
|
1880
1261
|
export async function prepareRemediationHostHandoff(params) {
|
|
1881
1262
|
const state = parseCurrentState(params.state);
|
|
@@ -1916,7 +1297,7 @@ export async function prepareRemediationHostHandoff(params) {
|
|
|
1916
1297
|
// producer-defect exits read alike.
|
|
1917
1298
|
if (!(error instanceof BlockContractError))
|
|
1918
1299
|
throw error;
|
|
1919
|
-
throw new
|
|
1300
|
+
throw new RemediationHostPreparationError("plan_repair_required", cannotPrepareMessage(paths.root, state, error));
|
|
1920
1301
|
}
|
|
1921
1302
|
if (workload.work_items.length === 0) {
|
|
1922
1303
|
// Name the producer defect when it is the cause. An empty level 0 that is
|
|
@@ -1924,7 +1305,7 @@ export async function prepareRemediationHostHandoff(params) {
|
|
|
1924
1305
|
// exist" used to surface as a bare "empty workload", sending the operator
|
|
1925
1306
|
// to look at scheduling rather than at the plan.
|
|
1926
1307
|
const blocked = planBlockIssues(paths.root, state);
|
|
1927
|
-
throw new
|
|
1308
|
+
throw new RemediationHostPreparationError("plan_repair_required", blocked.length === 0
|
|
1928
1309
|
? "Cannot prepare an empty remediation host workload"
|
|
1929
1310
|
: `Cannot prepare a remediation host workload: ${blocked
|
|
1930
1311
|
.map((issue) => issue.message)
|
|
@@ -1936,11 +1317,31 @@ export async function prepareRemediationHostHandoff(params) {
|
|
|
1936
1317
|
// the DIGEST moves — anything else that no longer matches is still a refusal.
|
|
1937
1318
|
const remintStaleDigest = existingRecord !== undefined &&
|
|
1938
1319
|
existingRecord.workload_sha256 !== workloadDigest &&
|
|
1939
|
-
(await
|
|
1320
|
+
(await persistedWorkloadAllowsRebind(paths.workloadPath, existingRecord));
|
|
1940
1321
|
if (existingRecord &&
|
|
1941
1322
|
existingRecord.workload_sha256 !== workloadDigest &&
|
|
1942
1323
|
!remintStaleDigest) {
|
|
1943
|
-
throw new
|
|
1324
|
+
throw new RemediationHostPreparationError("handoff_rebind_required", `Trusted remediation host workload no longer matches the persisted state binding; ${paths.workloadPath} cannot prove its trusted identity`);
|
|
1325
|
+
}
|
|
1326
|
+
let conformanceReview = state.conformance_review;
|
|
1327
|
+
if (conformanceReview && conformanceReview.run_id !== params.runId) {
|
|
1328
|
+
throw new Error("Conformance review policy belongs to another run; restore coherent run state or confirm a new run.");
|
|
1329
|
+
}
|
|
1330
|
+
if (!conformanceReview) {
|
|
1331
|
+
// A lost snapshot must not reset a review already requested for this run.
|
|
1332
|
+
for (const item of workload.work_items) {
|
|
1333
|
+
const priorReview = await readSubmissionDocument(conformanceReviewPaths(paths.artifactsDir, params.runId, item.id).request);
|
|
1334
|
+
if (priorReview.kind !== "missing") {
|
|
1335
|
+
throw new Error("The conformance review has begun but the trusted run policy is missing. Restore the run state before continuing; review cannot be silently disabled.");
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
const checkpoint = await readIntentCheckpoint(join(paths.artifactsDir, "intent_checkpoint.json"));
|
|
1339
|
+
conformanceReview = { run_id: params.runId, enabled: checkpoint?.conformance_review === true,
|
|
1340
|
+
...(checkpoint ? { checkpoint_sha256: contentSha256(checkpoint) } : {}) };
|
|
1341
|
+
}
|
|
1342
|
+
const conformancePolicyDigest = conformanceReview.enabled ? contentSha256(conformanceReview) : undefined;
|
|
1343
|
+
if (existingRecord?.conformance_policy_sha256 && existingRecord.conformance_policy_sha256 !== conformancePolicyDigest) {
|
|
1344
|
+
throw new Error("The conformance review snapshot no longer matches its trusted handoff binding.");
|
|
1944
1345
|
}
|
|
1945
1346
|
const handoffRecord = existingRecord
|
|
1946
1347
|
? remintStaleDigest
|
|
@@ -1955,11 +1356,13 @@ export async function prepareRemediationHostHandoff(params) {
|
|
|
1955
1356
|
work_item_ids: workload.work_items.map((item) => item.id),
|
|
1956
1357
|
};
|
|
1957
1358
|
await mkdir(paths.resultDir, { recursive: true });
|
|
1359
|
+
await recordHostRootLogBoundary({ ...paths, runId: params.runId, phase: "prepare" });
|
|
1958
1360
|
await writeJsonFile(paths.workloadPath, workload);
|
|
1959
1361
|
return {
|
|
1960
1362
|
workload,
|
|
1961
1363
|
workload_path: paths.workloadPath,
|
|
1962
|
-
handoff_record: handoffRecord,
|
|
1364
|
+
handoff_record: conformancePolicyDigest ? { ...handoffRecord, conformance_policy_sha256: conformancePolicyDigest } : handoffRecord,
|
|
1365
|
+
conformance_review: conformanceReview,
|
|
1963
1366
|
};
|
|
1964
1367
|
}
|
|
1965
1368
|
/**
|
|
@@ -2019,6 +1422,16 @@ export async function ingestRemediationHostResults(params) {
|
|
|
2019
1422
|
"a caller-supplied verdict table asserts required tests ran that no runner produced");
|
|
2020
1423
|
}
|
|
2021
1424
|
const paths = resolveBoundaryPaths(params);
|
|
1425
|
+
if (state.conformance_review && state.conformance_review.run_id !== params.runId) {
|
|
1426
|
+
throw new Error("Conformance review policy belongs to another run; refusing result acceptance.");
|
|
1427
|
+
}
|
|
1428
|
+
if (!state.conformance_review && state.host_handoff) {
|
|
1429
|
+
for (const id of state.host_handoff.work_item_ids) {
|
|
1430
|
+
const priorReview = await readSubmissionDocument(conformanceReviewPaths(paths.artifactsDir, params.runId, id).request);
|
|
1431
|
+
if (priorReview.kind !== "missing")
|
|
1432
|
+
throw new Error("Required conformance review policy is missing; restore the trusted run state.");
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
2022
1435
|
const nextState = structuredClone(state);
|
|
2023
1436
|
const validated = await validateHostResultBundle({
|
|
2024
1437
|
state,
|
|
@@ -2029,6 +1442,8 @@ export async function ingestRemediationHostResults(params) {
|
|
|
2029
1442
|
});
|
|
2030
1443
|
if (validated.kind === "summary") {
|
|
2031
1444
|
const issues = await recordAndEnrichHostIssues(params.artifactsDir, params.runId, validated.summary.issues, validated.summary.completed_work_item_ids);
|
|
1445
|
+
if (state.host_handoff)
|
|
1446
|
+
await recordHostRootLogBoundary({ ...paths, runId: params.runId, phase: "ingest" });
|
|
2032
1447
|
return { ...validated.summary, issues };
|
|
2033
1448
|
}
|
|
2034
1449
|
const acc = {
|
|
@@ -2042,7 +1457,9 @@ export async function ingestRemediationHostResults(params) {
|
|
|
2042
1457
|
};
|
|
2043
1458
|
const verdicts = await executeHostVerificationReruns(validated.ctx, acc);
|
|
2044
1459
|
const issues = await recordAndEnrichHostIssues(params.artifactsDir, params.runId, acc.issues, acc.completed);
|
|
2045
|
-
|
|
1460
|
+
const summary = await commitRemediationStateUpdates(validated.ctx, acc, verdicts, issues);
|
|
1461
|
+
await recordHostRootLogBoundary({ ...paths, runId: params.runId, phase: "ingest" });
|
|
1462
|
+
return summary;
|
|
2046
1463
|
}
|
|
2047
1464
|
/** Record raw observations first, then decorate only the returned diagnostics. */
|
|
2048
1465
|
async function recordAndEnrichHostIssues(artifactsDir, runId, issues, acceptedIds) {
|
|
@@ -2225,6 +1642,15 @@ async function validateHostResultBundle(input) {
|
|
|
2225
1642
|
function hasLandedCommitFor(state, workItem) {
|
|
2226
1643
|
return workItem.finding_ids.some((findingId) => typeof state.items[findingId]?.host_landed_commit === "string");
|
|
2227
1644
|
}
|
|
1645
|
+
async function requiredConformanceReview(ctx, workItem, result) {
|
|
1646
|
+
if (!ctx.state.conformance_review?.enabled)
|
|
1647
|
+
return { ok: true };
|
|
1648
|
+
return checkContractConformance({
|
|
1649
|
+
root: ctx.paths.root, artifactsDir: ctx.paths.artifactsDir, runId: ctx.runId,
|
|
1650
|
+
item: workItem, result,
|
|
1651
|
+
contracts: ctx.state.plan.blocks.find(block => block.block_id === workItem.id)?.module_contracts ?? [],
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
2228
1654
|
async function executeHostVerificationReruns(ctx, acc) {
|
|
2229
1655
|
const { paths, nextState, state } = ctx;
|
|
2230
1656
|
const verdicts = [];
|
|
@@ -2322,6 +1748,12 @@ async function executeHostVerificationReruns(ctx, acc) {
|
|
|
2322
1748
|
continue;
|
|
2323
1749
|
}
|
|
2324
1750
|
}
|
|
1751
|
+
const review = outcome.status === "resolved_no_change"
|
|
1752
|
+
? await requiredConformanceReview(ctx, workItem, result) : { ok: true };
|
|
1753
|
+
if (!review.ok) {
|
|
1754
|
+
acc.issues.push(review.issue);
|
|
1755
|
+
continue;
|
|
1756
|
+
}
|
|
2325
1757
|
for (const findingId of pendingItems)
|
|
2326
1758
|
acc.settledFindingIds.add(findingId);
|
|
2327
1759
|
verdicts.push({
|
|
@@ -2330,6 +1762,7 @@ async function executeHostVerificationReruns(ctx, acc) {
|
|
|
2330
1762
|
pendingItems,
|
|
2331
1763
|
at: new Date().toISOString(),
|
|
2332
1764
|
outcome,
|
|
1765
|
+
conformance_review: review.receipt,
|
|
2333
1766
|
});
|
|
2334
1767
|
acc.completed.push(workItem.id);
|
|
2335
1768
|
continue;
|
|
@@ -2363,6 +1796,11 @@ async function executeHostVerificationReruns(ctx, acc) {
|
|
|
2363
1796
|
});
|
|
2364
1797
|
continue;
|
|
2365
1798
|
}
|
|
1799
|
+
const review = await requiredConformanceReview(ctx, workItem, result);
|
|
1800
|
+
if (!review.ok) {
|
|
1801
|
+
acc.issues.push(review.issue);
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
2366
1804
|
if (corroborated.usedRecovery) {
|
|
2367
1805
|
// No acceptance without a record. The mark goes down BEFORE the item is
|
|
2368
1806
|
// marked resolved, and an append that throws refuses this item rather
|
|
@@ -2419,6 +1857,7 @@ async function executeHostVerificationReruns(ctx, acc) {
|
|
|
2419
1857
|
acc.settledFindingIds.add(findingId);
|
|
2420
1858
|
verdicts.push({
|
|
2421
1859
|
kind: "landed",
|
|
1860
|
+
conformance_review: review.receipt,
|
|
2422
1861
|
workItem,
|
|
2423
1862
|
pendingItems,
|
|
2424
1863
|
at: new Date().toISOString(),
|
|
@@ -2447,6 +1886,10 @@ function commitRemediationStateUpdates(ctx, acc, verdicts, issues) {
|
|
|
2447
1886
|
const outcome = verdict.outcome;
|
|
2448
1887
|
for (const findingId of verdict.pendingItems) {
|
|
2449
1888
|
const item = nextState.items[findingId];
|
|
1889
|
+
if (verdict.conformance_review)
|
|
1890
|
+
item.conformance_review = verdict.conformance_review;
|
|
1891
|
+
else
|
|
1892
|
+
delete item.conformance_review;
|
|
2450
1893
|
item.started_at ??= verdict.at;
|
|
2451
1894
|
if (outcome.status === "resolved_no_change") {
|
|
2452
1895
|
item.status = "resolved_no_change";
|
|
@@ -2477,6 +1920,10 @@ function commitRemediationStateUpdates(ctx, acc, verdicts, issues) {
|
|
|
2477
1920
|
}
|
|
2478
1921
|
for (const findingId of verdict.pendingItems) {
|
|
2479
1922
|
const item = nextState.items[findingId];
|
|
1923
|
+
if (verdict.conformance_review)
|
|
1924
|
+
item.conformance_review = verdict.conformance_review;
|
|
1925
|
+
else
|
|
1926
|
+
delete item.conformance_review;
|
|
2480
1927
|
item.status = "resolved";
|
|
2481
1928
|
item.started_at ??= verdict.at;
|
|
2482
1929
|
item.completed_at = verdict.at;
|