audit-tools 0.52.0 → 0.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/cli/auditStep.d.ts.map +1 -1
- package/dist/audit/cli/auditStep.js +2 -2
- package/dist/audit/cli/auditStep.js.map +1 -1
- package/dist/audit/cli/charterClarificationPrompt.d.ts +16 -8
- package/dist/audit/cli/charterClarificationPrompt.d.ts.map +1 -1
- package/dist/audit/cli/charterClarificationPrompt.js +95 -42
- package/dist/audit/cli/charterClarificationPrompt.js.map +1 -1
- package/dist/audit/cli/charterComparisonPrompt.d.ts +16 -6
- package/dist/audit/cli/charterComparisonPrompt.d.ts.map +1 -1
- package/dist/audit/cli/charterComparisonPrompt.js +76 -29
- package/dist/audit/cli/charterComparisonPrompt.js.map +1 -1
- package/dist/audit/cli/charterExtractionPrompt.d.ts +5 -4
- package/dist/audit/cli/charterExtractionPrompt.d.ts.map +1 -1
- package/dist/audit/cli/charterExtractionPrompt.js +64 -42
- package/dist/audit/cli/charterExtractionPrompt.js.map +1 -1
- package/dist/audit/cli/charterFidelityPrompt.d.ts +17 -4
- package/dist/audit/cli/charterFidelityPrompt.d.ts.map +1 -1
- package/dist/audit/cli/charterFidelityPrompt.js +72 -21
- package/dist/audit/cli/charterFidelityPrompt.js.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts +23 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.js +204 -41
- package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
- package/dist/audit/cli/dispatch/packetFilter.d.ts +8 -0
- package/dist/audit/cli/dispatch/packetFilter.d.ts.map +1 -1
- package/dist/audit/cli/fanoutLanes.d.ts.map +1 -1
- package/dist/audit/cli/fanoutLanes.js +20 -4
- package/dist/audit/cli/fanoutLanes.js.map +1 -1
- package/dist/audit/cli/laneValidators.d.ts +19 -7
- package/dist/audit/cli/laneValidators.d.ts.map +1 -1
- package/dist/audit/cli/laneValidators.js +73 -18
- package/dist/audit/cli/laneValidators.js.map +1 -1
- package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
- package/dist/audit/cli/nextStepCommand.js +13 -6
- package/dist/audit/cli/nextStepCommand.js.map +1 -1
- package/dist/audit/cli/nextStepHelpers.d.ts +25 -2
- package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
- package/dist/audit/cli/nextStepHelpers.js +105 -35
- package/dist/audit/cli/nextStepHelpers.js.map +1 -1
- package/dist/audit/cli/recoverSubmissionCommand.d.ts.map +1 -1
- package/dist/audit/cli/recoverSubmissionCommand.js +89 -4
- package/dist/audit/cli/recoverSubmissionCommand.js.map +1 -1
- package/dist/audit/cli/sampleRunCommand.d.ts +10 -0
- package/dist/audit/cli/sampleRunCommand.d.ts.map +1 -1
- package/dist/audit/cli/semanticReviewStep.d.ts +4 -4
- package/dist/audit/cli/semanticReviewStep.d.ts.map +1 -1
- package/dist/audit/cli/semanticReviewStep.js +35 -71
- package/dist/audit/cli/semanticReviewStep.js.map +1 -1
- package/dist/audit/contracts/findingContractPrompt.d.ts.map +1 -1
- package/dist/audit/contracts/findingContractPrompt.js +7 -1
- package/dist/audit/contracts/findingContractPrompt.js.map +1 -1
- package/dist/audit/contracts/workerSchemas.d.ts +280 -4
- package/dist/audit/contracts/workerSchemas.d.ts.map +1 -1
- package/dist/audit/contracts/workerSchemas.js +24 -2
- package/dist/audit/contracts/workerSchemas.js.map +1 -1
- package/dist/audit/orchestrator/charterClarificationExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/charterClarificationExecutor.js +39 -0
- package/dist/audit/orchestrator/charterClarificationExecutor.js.map +1 -1
- package/dist/audit/orchestrator/charterExtractionExecutor.d.ts +12 -5
- package/dist/audit/orchestrator/charterExtractionExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/charterExtractionExecutor.js +39 -9
- package/dist/audit/orchestrator/charterExtractionExecutor.js.map +1 -1
- package/dist/audit/orchestrator/designReviewPrompt.d.ts +7 -0
- package/dist/audit/orchestrator/designReviewPrompt.d.ts.map +1 -1
- package/dist/audit/orchestrator/designReviewPrompt.js +174 -19
- package/dist/audit/orchestrator/designReviewPrompt.js.map +1 -1
- package/dist/audit/orchestrator/intentCheckpointGate.d.ts.map +1 -1
- package/dist/audit/orchestrator/intentCheckpointGate.js +1 -5
- package/dist/audit/orchestrator/intentCheckpointGate.js.map +1 -1
- package/dist/audit/orchestrator/requeueCommand.d.ts +8 -0
- package/dist/audit/orchestrator/requeueCommand.d.ts.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/lensVerification.d.ts.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/lensVerification.js +94 -71
- package/dist/audit/orchestrator/selectiveDeepening/lensVerification.js.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/shared.d.ts +0 -2
- package/dist/audit/orchestrator/selectiveDeepening/shared.d.ts.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/shared.js +9 -2
- package/dist/audit/orchestrator/selectiveDeepening/shared.js.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/stewardFollowup.d.ts.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/stewardFollowup.js +28 -4
- package/dist/audit/orchestrator/selectiveDeepening/stewardFollowup.js.map +1 -1
- package/dist/audit/reporting/synthesis.d.ts +16 -3
- package/dist/audit/reporting/synthesis.d.ts.map +1 -1
- package/dist/audit/reporting/synthesis.js +40 -11
- package/dist/audit/reporting/synthesis.js.map +1 -1
- package/dist/audit/reporting/synthesisNarrativePrompt.d.ts +26 -1
- package/dist/audit/reporting/synthesisNarrativePrompt.d.ts.map +1 -1
- package/dist/audit/reporting/synthesisNarrativePrompt.js +53 -13
- package/dist/audit/reporting/synthesisNarrativePrompt.js.map +1 -1
- package/dist/audit/systemic/secondOrderAdversaryPrompt.d.ts.map +1 -1
- package/dist/audit/systemic/secondOrderAdversaryPrompt.js +67 -40
- package/dist/audit/systemic/secondOrderAdversaryPrompt.js.map +1 -1
- package/dist/audit/systemic/systemicChallengeLoop.d.ts.map +1 -1
- package/dist/audit/systemic/systemicChallengeLoop.js +13 -1
- package/dist/audit/systemic/systemicChallengeLoop.js.map +1 -1
- package/dist/audit/types/conceptualAdjudication.d.ts +26 -2
- package/dist/audit/types/conceptualAdjudication.d.ts.map +1 -1
- package/dist/audit/types/conceptualAdjudication.js +26 -7
- package/dist/audit/types/conceptualAdjudication.js.map +1 -1
- package/dist/audit/types.d.ts +279 -0
- package/dist/audit/types.d.ts.map +1 -1
- package/dist/audit/types.js +52 -0
- package/dist/audit/types.js.map +1 -1
- package/dist/audit/validation/auditResults.d.ts.map +1 -1
- package/dist/audit/validation/auditResults.js +51 -6
- package/dist/audit/validation/auditResults.js.map +1 -1
- package/dist/audit/validation/ingestIssueCodes.d.ts +3 -1
- package/dist/audit/validation/ingestIssueCodes.d.ts.map +1 -1
- package/dist/audit/validation/ingestIssueCodes.js +27 -15
- package/dist/audit/validation/ingestIssueCodes.js.map +1 -1
- package/dist/remediate/contractPipeline/sketchSource.d.ts +13 -3
- package/dist/remediate/contractPipeline/sketchSource.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/sketchSource.js +14 -8
- package/dist/remediate/contractPipeline/sketchSource.js.map +1 -1
- package/dist/remediate/intake.d.ts +140 -36
- package/dist/remediate/intake.d.ts.map +1 -1
- package/dist/remediate/intake.js +159 -38
- package/dist/remediate/intake.js.map +1 -1
- package/dist/remediate/intent/checkpointFilter.d.ts +1 -2
- package/dist/remediate/intent/checkpointFilter.d.ts.map +1 -1
- package/dist/remediate/intent/checkpointFilter.js +1 -5
- package/dist/remediate/intent/checkpointFilter.js.map +1 -1
- package/dist/remediate/phases/close.d.ts.map +1 -1
- package/dist/remediate/phases/close.js +2 -2
- package/dist/remediate/phases/close.js.map +1 -1
- package/dist/remediate/phases/plan.d.ts +1 -1
- package/dist/remediate/review/reviewGate.d.ts +68 -39
- package/dist/remediate/review/reviewGate.d.ts.map +1 -1
- package/dist/remediate/review/reviewGate.js +133 -50
- package/dist/remediate/review/reviewGate.js.map +1 -1
- package/dist/remediate/state/store.d.ts +1 -2
- package/dist/remediate/state/store.d.ts.map +1 -1
- package/dist/remediate/state/store.js +124 -3
- package/dist/remediate/state/store.js.map +1 -1
- package/dist/remediate/state/types.d.ts +28 -4
- package/dist/remediate/state/types.d.ts.map +1 -1
- package/dist/remediate/state/types.js.map +1 -1
- package/dist/remediate/steps/contractPipeline.d.ts +17 -1
- package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipeline.js +100 -52
- package/dist/remediate/steps/contractPipeline.js.map +1 -1
- package/dist/remediate/steps/contractPipelinePrompts.d.ts +32 -8
- package/dist/remediate/steps/contractPipelinePrompts.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipelinePrompts.js +145 -131
- package/dist/remediate/steps/contractPipelinePrompts.js.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts +6 -4
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.js +291 -155
- package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
- package/dist/remediate/steps/intakeResolver.d.ts +1 -1
- package/dist/remediate/steps/intakeResolver.d.ts.map +1 -1
- package/dist/remediate/steps/intakeResolver.js +50 -49
- package/dist/remediate/steps/intakeResolver.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +315 -288
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/steps/prompts.d.ts +10 -3
- package/dist/remediate/steps/prompts.d.ts.map +1 -1
- package/dist/remediate/steps/prompts.js +191 -228
- package/dist/remediate/steps/prompts.js.map +1 -1
- package/dist/remediate/steps/types.d.ts +2 -2
- package/dist/remediate/steps/types.d.ts.map +1 -1
- package/dist/remediate/steps/types.js +10 -3
- 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 -22
- 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 +25 -8
- package/dist/remediate/validation/contractPipeline.js.map +1 -1
- package/dist/remediate/validation/remediationState.d.ts +0 -1
- package/dist/remediate/validation/remediationState.d.ts.map +1 -1
- package/dist/remediate/validation/remediationState.js +1 -20
- package/dist/remediate/validation/remediationState.js.map +1 -1
- package/dist/shared/decompose/charterClarification.d.ts +7 -0
- package/dist/shared/decompose/charterClarification.d.ts.map +1 -1
- package/dist/shared/decompose/charterClarification.js +8 -1
- package/dist/shared/decompose/charterClarification.js.map +1 -1
- package/dist/shared/decompose/charterExtraction.d.ts +204 -16
- package/dist/shared/decompose/charterExtraction.d.ts.map +1 -1
- package/dist/shared/decompose/charterExtraction.js +70 -13
- package/dist/shared/decompose/charterExtraction.js.map +1 -1
- package/dist/shared/decompose/systemicChallenge.d.ts +42 -1
- package/dist/shared/decompose/systemicChallenge.d.ts.map +1 -1
- package/dist/shared/decompose/systemicChallenge.js +51 -0
- package/dist/shared/decompose/systemicChallenge.js.map +1 -1
- package/dist/shared/index.d.ts +14 -11
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +16 -9
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/io/stepContractWriter.d.ts.map +1 -1
- package/dist/shared/io/stepContractWriter.js +13 -4
- package/dist/shared/io/stepContractWriter.js.map +1 -1
- package/dist/shared/submission/hostHandoffCore.d.ts +7 -0
- package/dist/shared/submission/hostHandoffCore.d.ts.map +1 -1
- package/dist/shared/submission/hostHandoffCore.js +15 -2
- package/dist/shared/submission/hostHandoffCore.js.map +1 -1
- package/dist/shared/submission/hostResultOutcomes.d.ts +60 -10
- package/dist/shared/submission/hostResultOutcomes.d.ts.map +1 -1
- package/dist/shared/submission/hostResultOutcomes.js +42 -10
- package/dist/shared/submission/hostResultOutcomes.js.map +1 -1
- package/dist/shared/submission/ingestReport.d.ts +54 -0
- package/dist/shared/submission/ingestReport.d.ts.map +1 -0
- package/dist/shared/submission/ingestReport.js +44 -0
- package/dist/shared/submission/ingestReport.js.map +1 -0
- package/dist/shared/submission/ingestionChecks.d.ts +8 -13
- package/dist/shared/submission/ingestionChecks.d.ts.map +1 -1
- package/dist/shared/submission/ingestionChecks.js +9 -14
- package/dist/shared/submission/ingestionChecks.js.map +1 -1
- package/dist/shared/submission/submissionClassifier.d.ts +6 -0
- package/dist/shared/submission/submissionClassifier.d.ts.map +1 -1
- package/dist/shared/submission/submissionClassifier.js +16 -0
- package/dist/shared/submission/submissionClassifier.js.map +1 -1
- package/dist/shared/submission/workerPromptBinding.d.ts +18 -0
- package/dist/shared/submission/workerPromptBinding.d.ts.map +1 -0
- package/dist/shared/submission/workerPromptBinding.js +38 -0
- package/dist/shared/submission/workerPromptBinding.js.map +1 -0
- package/dist/shared/tooling/exec.d.ts +24 -0
- package/dist/shared/tooling/exec.d.ts.map +1 -1
- package/dist/shared/tooling/exec.js +30 -0
- package/dist/shared/tooling/exec.js.map +1 -1
- package/dist/shared/types/charter.d.ts +143 -7
- package/dist/shared/types/charter.d.ts.map +1 -1
- package/dist/shared/types/charter.js +63 -7
- package/dist/shared/types/charter.js.map +1 -1
- package/dist/shared/types/charterPacket.d.ts +13 -0
- package/dist/shared/types/charterPacket.d.ts.map +1 -1
- package/dist/shared/types/charterPacket.js.map +1 -1
- package/dist/shared/types/contractPipeline/obligations.d.ts +30 -11
- package/dist/shared/types/contractPipeline/obligations.d.ts.map +1 -1
- package/dist/shared/types/contractPipeline/obligations.js +6 -1
- package/dist/shared/types/contractPipeline/obligations.js.map +1 -1
- package/dist/shared/types/contractPropertyShapes.d.ts.map +1 -1
- package/dist/shared/types/contractPropertyShapes.js +13 -0
- package/dist/shared/types/contractPropertyShapes.js.map +1 -1
- package/dist/shared/types/finding.d.ts +154 -15
- package/dist/shared/types/finding.d.ts.map +1 -1
- package/dist/shared/types/finding.js +41 -5
- package/dist/shared/types/finding.js.map +1 -1
- package/dist/shared/types/intentCheckpoint.d.ts +26 -40
- package/dist/shared/types/intentCheckpoint.d.ts.map +1 -1
- package/dist/shared/types/intentCheckpoint.js +30 -33
- package/dist/shared/types/intentCheckpoint.js.map +1 -1
- package/dist/shared/types/remediationOutcome.d.ts +8 -8
- package/dist/shared/validation/citationGrounding.d.ts +29 -5
- package/dist/shared/validation/citationGrounding.d.ts.map +1 -1
- package/dist/shared/validation/citationGrounding.js +56 -9
- package/dist/shared/validation/citationGrounding.js.map +1 -1
- package/dist/shared/validation/designFindingGrounding.d.ts +30 -9
- package/dist/shared/validation/designFindingGrounding.d.ts.map +1 -1
- package/dist/shared/validation/designFindingGrounding.js +32 -12
- package/dist/shared/validation/designFindingGrounding.js.map +1 -1
- package/docs/audit-pkg/contracts.md +4 -5
- package/package.json +1 -1
- package/schemas/audit_result.schema.json +51 -0
- package/schemas/audit_results.schema.json +51 -0
- package/schemas/audit_task.schema.json +44 -0
- package/schemas/finding.schema.json +4 -0
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// or the id-glossary write scope is reverted.
|
|
5
5
|
import { mkdir } from "node:fs/promises";
|
|
6
6
|
import { isAbsolute } from "node:path";
|
|
7
|
-
import { AUDIT_TOOLS_DIRNAME, headCommit, commandLeavesDeclaredShape, FindingSchema, SUBMISSION_ISSUE_CODES, SUBMISSION_LEDGER_EVENT_CONTRACT_VERSION, appendSubmissionEvent, enrichMissingSubmissionIssues, isMissingObservation, compareCodeUnits, contentSha256, declaredInvariantIds, deriveLaneDemand, discoverLandingGates, hasExactKeys, identityFailureDiagnostic, hostHandoffResultPath, idsAreStrictlyAscending, isCommit, isGitRepo, isRecord, isSha256, LaneDemandSchema, normalizeRepoPath, parseAllWorkloadItems, parseCommandString, parseWorkloadEnvelope,
|
|
8
|
-
import { REMEDIATION_STATE_CONTRACT_VERSION,
|
|
7
|
+
import { AUDIT_TOOLS_DIRNAME, headCommit, commandLeavesDeclaredShape, FindingSchema, SUBMISSION_ISSUE_CODES, SUBMISSION_ISSUE_REMEDY, WORKLOAD_ISSUE_CODES, 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, parseCommandString, parseWorkloadEnvelope, readSubmissionDocument, readSubmissionLedger, readTrailingSubmissionRefusals, recoveryMarkMatches, recordHostResultOutcomes, repoRelativePath, resolveContainedPath, resolveHostHandoffPaths, runTrackedAsync, sameStrings, scanBoundSubmission, SEVERITIES, severityRank, stringArray, stableStringify, TRACKED_CHILD_DEADLINE_MS, withGlossaryScope, writeJsonFile, } from "audit-tools/shared";
|
|
8
|
+
import { REMEDIATION_STATE_CONTRACT_VERSION, } from "../../state/store.js";
|
|
9
9
|
import { REMEDIATION_HOST_HANDOFF_RECORD_V1ALPHA1, REMEDIATION_HOST_HANDOFF_RECORD_V1ALPHA2, REMEDIATION_HOST_SCOPE_SEMANTICS, RemediationHostHandoffRecordSchema, RemediationPlanSchema, isClarificationCategory, } from "../../state/types.js";
|
|
10
10
|
import { ITEM_STATUSES, isTerminalStatus, isVerifiedCompleteStatus, } from "../../state/itemStatus.js";
|
|
11
11
|
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";
|
|
@@ -85,10 +85,78 @@ export const REMEDIATION_ISSUE_CODES = [
|
|
|
85
85
|
* describe work that is no longer pending, so the whole recovery aborts.
|
|
86
86
|
*/
|
|
87
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,
|
|
88
113
|
];
|
|
114
|
+
/**
|
|
115
|
+
* What the host must DO about each remediation ingest code (owner review of
|
|
116
|
+
* prompt 20, 2026-09-18). The `Record` over the whole union is the enforcement:
|
|
117
|
+
* a code added above with no remedy here is a type error. Read `IssueRemedy`
|
|
118
|
+
* for what the four remedies mean.
|
|
119
|
+
*/
|
|
120
|
+
const REMEDIATION_ISSUE_REMEDY = {
|
|
121
|
+
...SUBMISSION_ISSUE_REMEDY,
|
|
122
|
+
...WORKLOAD_ISSUE_REMEDY,
|
|
123
|
+
// Here a duplicate is two result files carrying one result_id in the same
|
|
124
|
+
// call, and NEITHER is accepted — so the item is still pending and the fix is
|
|
125
|
+
// a corrected file. (The audit draw raises it only after an acceptance, so
|
|
126
|
+
// there it stays settled.)
|
|
127
|
+
duplicate_submission_id: "repair",
|
|
128
|
+
// The run's own documents: the next prepare re-mints the workload and its
|
|
129
|
+
// binding in the same call, so nothing is asked of the host.
|
|
130
|
+
workload_missing: "none",
|
|
131
|
+
workload_invalid: "none",
|
|
132
|
+
trusted_binding_missing: "none",
|
|
133
|
+
// A fault in the landed work or in the result that describes it — a worker
|
|
134
|
+
// can make a new commit or correct the file.
|
|
135
|
+
commit_missing: "repair",
|
|
136
|
+
commit_not_landed: "repair",
|
|
137
|
+
baseline_not_ancestor: "repair",
|
|
138
|
+
changed_files_mismatch: "repair",
|
|
139
|
+
run_start_dirty_overlap: "repair",
|
|
140
|
+
required_test_failed: "repair",
|
|
141
|
+
required_test_timed_out: "repair",
|
|
142
|
+
landed_commit_invalid: "repair",
|
|
143
|
+
// The plan, the run's recorded state, or the tool's own limit is at fault.
|
|
144
|
+
required_test_output_overflow: "operator",
|
|
145
|
+
dependency_missing: "operator",
|
|
146
|
+
block_contract_invalid: "operator",
|
|
147
|
+
recovery_unrecorded: "operator",
|
|
148
|
+
tree_moved_between_phases: "operator",
|
|
149
|
+
state_moved_between_phases: "operator",
|
|
150
|
+
baseline_missing: "operator",
|
|
151
|
+
baseline_orphaned: "operator",
|
|
152
|
+
work_item_not_eligible: "operator",
|
|
153
|
+
};
|
|
154
|
+
/** The remedy for one classified remediation ingest failure. */
|
|
155
|
+
export function remediationIssueRemedy(issue) {
|
|
156
|
+
return REMEDIATION_ISSUE_REMEDY[issue.code];
|
|
157
|
+
}
|
|
89
158
|
const CURRENT_STATE_KEYS = new Set([
|
|
90
159
|
"applied_edit_surface",
|
|
91
|
-
"clarifications",
|
|
92
160
|
"closing_context",
|
|
93
161
|
"closing_plan",
|
|
94
162
|
"contract_version",
|
|
@@ -104,6 +172,7 @@ const CURRENT_STATE_KEYS = new Set([
|
|
|
104
172
|
const CURRENT_ITEM_KEYS = new Set([
|
|
105
173
|
"block_id",
|
|
106
174
|
"clarification_context",
|
|
175
|
+
"clarification_question",
|
|
107
176
|
"completed_at",
|
|
108
177
|
"failure_context",
|
|
109
178
|
"failure_reason",
|
|
@@ -185,7 +254,7 @@ function normalizeDeclaredPath(root, candidate, label) {
|
|
|
185
254
|
function pathIsAllowedByWriteScope(root, candidate, allowedFiles) {
|
|
186
255
|
let normalized;
|
|
187
256
|
try {
|
|
188
|
-
normalized = repoRelativePath(root, candidate, "
|
|
257
|
+
normalized = repoRelativePath(root, candidate, "landed file");
|
|
189
258
|
}
|
|
190
259
|
catch {
|
|
191
260
|
return false;
|
|
@@ -461,36 +530,13 @@ export async function remediationSubmissionBinding(params) {
|
|
|
461
530
|
const workItem = workload.work_items.find((item) => isRecord(item) && item.id === params.workItemId);
|
|
462
531
|
if (workItem === undefined)
|
|
463
532
|
return null;
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
// widen the in-memory view.
|
|
468
|
-
let validationWorkItem = workItem;
|
|
469
|
-
try {
|
|
470
|
-
const storedState = await new StateStore(paths.artifactsDir).loadState();
|
|
471
|
-
// No version is supplied here. The store reads the state from disk and
|
|
472
|
-
// stamps the contract version it just VALIDATED, so `parseCurrentState`
|
|
473
|
-
// tests the value that was in the file. It used to be handed
|
|
474
|
-
// `STATE_CONTRACT_VERSION` from this line, which made the parser's version
|
|
475
|
-
// check compare a constant to itself and accept any state the store was
|
|
476
|
-
// willing to return.
|
|
477
|
-
const state = storedState ? parseCurrentState(storedState) : null;
|
|
478
|
-
const canonicalWorkload = state
|
|
479
|
-
? parseWorkload(read.value, paths, params.runId, state)
|
|
480
|
-
: null;
|
|
481
|
-
const canonicalWorkItem = canonicalWorkload?.work_items.find((item) => item.id === params.workItemId);
|
|
482
|
-
if (state?.host_handoff && canonicalWorkItem) {
|
|
483
|
-
validationWorkItem = deriveLegacyDirectoryScopeRecovery(state, canonicalWorkItem).workItem;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
catch {
|
|
487
|
-
// Invalid/missing state cannot authorize widening. The raw workload's
|
|
488
|
-
// exact scope remains the fail-closed validator for this recovery write.
|
|
489
|
-
}
|
|
533
|
+
// The shape gate reads only the item's identity (id and prompt digest). The
|
|
534
|
+
// write scope is checked at ingest against the landed commit, so no stored
|
|
535
|
+
// state can widen what this validator accepts.
|
|
490
536
|
return {
|
|
491
537
|
submissionDir: paths.resultDir,
|
|
492
538
|
validate: (value) => {
|
|
493
|
-
const parsed = parseResult(value, params.runId,
|
|
539
|
+
const parsed = parseResult(value, params.runId, workItem);
|
|
494
540
|
return parsed.ok
|
|
495
541
|
? null
|
|
496
542
|
: { code: "submission_contract_invalid", check: parsed.check, message: parsed.reason };
|
|
@@ -641,28 +687,82 @@ function buildPrompt(item) {
|
|
|
641
687
|
required_tests: item.requiredTests,
|
|
642
688
|
result_path: item.resultPath,
|
|
643
689
|
});
|
|
690
|
+
// The BODY only. The result template is appended by `bindWorkerPrompt`, and
|
|
691
|
+
// the prompt digest covers exactly this text (owner review of prompt 20,
|
|
692
|
+
// 2026-09-18: plain language, rules as bullets, no word a worker cannot act
|
|
693
|
+
// on).
|
|
644
694
|
return [
|
|
645
|
-
|
|
646
|
-
"
|
|
647
|
-
"
|
|
648
|
-
|
|
695
|
+
`# Implement remediation work item \`${item.blockId}\``,
|
|
696
|
+
"",
|
|
697
|
+
"Assignment:",
|
|
698
|
+
"",
|
|
699
|
+
"```json",
|
|
700
|
+
assignment,
|
|
701
|
+
"```",
|
|
702
|
+
"",
|
|
703
|
+
"Rules:",
|
|
704
|
+
"",
|
|
705
|
+
'- Edit only the files in `allowed_files`. An entry that ends in "/" allows every file below that directory. Every other entry allows only that one file.',
|
|
706
|
+
"- Apply each finding in `assignments` exactly, with the clarified scope or the retry context it carries.",
|
|
649
707
|
...(item.moduleContracts.length > 0
|
|
650
708
|
? [
|
|
651
|
-
"
|
|
709
|
+
"- Conform to each contract in `module_contracts`: every declared input, output, invariant, side effect, validation boundary, failure mode and seam adjustment. Code that breaks a contract is a defect, even when the build and the tests pass.",
|
|
652
710
|
]
|
|
653
711
|
: []),
|
|
654
|
-
...(item.
|
|
712
|
+
...(item.requiredTests.length > 0
|
|
655
713
|
? [
|
|
656
|
-
"
|
|
714
|
+
"- Run each command in `required_tests` until it passes. The tool runs each command again before it accepts your result.",
|
|
657
715
|
]
|
|
658
716
|
: []),
|
|
659
|
-
`
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
717
|
+
"- Make one commit on top of `baseline_commit`, with all your edits in it. Merge it so that HEAD contains it.",
|
|
718
|
+
...(item.hasLandingGates
|
|
719
|
+
? ["- Do not run the landing gates. The close phase runs them once, on the merged tree."]
|
|
720
|
+
: []),
|
|
721
|
+
].join("\n");
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* The result template every remediation worker prompt ends with. The tool
|
|
725
|
+
* fills the identity values from the body digest; the worker changes only the
|
|
726
|
+
* marked values. The decision form follows for an item where no edit should
|
|
727
|
+
* land.
|
|
728
|
+
*/
|
|
729
|
+
function renderResultTemplate(item) {
|
|
730
|
+
const identity = {
|
|
731
|
+
result_id: deriveResultId(item.blockId, item.promptDigest),
|
|
732
|
+
run_id: item.runId,
|
|
733
|
+
work_item_id: item.blockId,
|
|
734
|
+
prompt_sha256: item.promptDigest,
|
|
735
|
+
};
|
|
736
|
+
const landed = {
|
|
737
|
+
contract_version: RESULT_CONTRACT_VERSION,
|
|
738
|
+
...identity,
|
|
739
|
+
landed_commit: "<full id of your commit>",
|
|
740
|
+
obligation_evidence: item.obligationIds.map((obligationId) => ({
|
|
741
|
+
obligation_id: obligationId,
|
|
742
|
+
evidence: ["<a file, symbol or test that shows this obligation holds>"],
|
|
743
|
+
})),
|
|
744
|
+
};
|
|
745
|
+
const decision = {
|
|
746
|
+
contract_version: DECISION_CONTRACT_VERSION,
|
|
747
|
+
...identity,
|
|
748
|
+
outcome: "<one of the three forms below>",
|
|
749
|
+
};
|
|
750
|
+
return [
|
|
751
|
+
`When HEAD contains your commit, write this JSON to \`${item.resultPath}\`. Change only the ${item.obligationIds.length > 0 ? "marked values" : "marked value"}:`,
|
|
752
|
+
"",
|
|
753
|
+
"```json",
|
|
754
|
+
JSON.stringify(landed, null, 2),
|
|
755
|
+
"```",
|
|
756
|
+
"",
|
|
757
|
+
`If no edit should land, write this JSON to \`${item.resultPath}\` instead. Replace \`outcome\` with one of the three forms below:`,
|
|
758
|
+
"",
|
|
759
|
+
"```json",
|
|
760
|
+
JSON.stringify(decision, null, 2),
|
|
761
|
+
"```",
|
|
762
|
+
"",
|
|
763
|
+
'- `{"status": "resolved_no_change", "evidence": ["<why the code needs no change>"]}`',
|
|
764
|
+
'- `{"status": "blocked", "failure_reason": "<what stops the work>"}`',
|
|
765
|
+
'- `{"status": "needs_clarification", "question": "<your question>"}` — you can also add a `"category"`.',
|
|
666
766
|
].join("\n");
|
|
667
767
|
}
|
|
668
768
|
/**
|
|
@@ -740,7 +840,7 @@ function buildFindingAssignments(state, block) {
|
|
|
740
840
|
};
|
|
741
841
|
});
|
|
742
842
|
}
|
|
743
|
-
function buildWorkItem(paths, block, baselineCommit, state) {
|
|
843
|
+
function buildWorkItem(paths, runId, block, baselineCommit, state) {
|
|
744
844
|
// The consumed-shape gate runs FIRST: a block outside the write-scope /
|
|
745
845
|
// command contract must never become a work item, so nothing downstream can
|
|
746
846
|
// dispatch it or execute its commands.
|
|
@@ -771,7 +871,7 @@ function buildWorkItem(paths, block, baselineCommit, state) {
|
|
|
771
871
|
//
|
|
772
872
|
// The per-item required tests are the block's OWN commands, unchanged, which
|
|
773
873
|
// is what this field meant before the gates were folded in and what the
|
|
774
|
-
//
|
|
874
|
+
// tool reruns at ingestion.
|
|
775
875
|
//
|
|
776
876
|
// ONE FACT still rides the emitted prompt: whether the target root declares
|
|
777
877
|
// any landing gate at all, so the host knows a close will run them.
|
|
@@ -786,7 +886,7 @@ function buildWorkItem(paths, block, baselineCommit, state) {
|
|
|
786
886
|
...new Set(block.items.flatMap((findingId) => state.plan.findings.find((entry) => entry.id === findingId)
|
|
787
887
|
?.contract_obligation_ids ?? [])),
|
|
788
888
|
].sort(compareCodeUnits);
|
|
789
|
-
const
|
|
889
|
+
const prompt = bindWorkerPrompt(buildPrompt({
|
|
790
890
|
blockId: block.block_id,
|
|
791
891
|
findingIds: block.items,
|
|
792
892
|
assignments,
|
|
@@ -797,14 +897,20 @@ function buildWorkItem(paths, block, baselineCommit, state) {
|
|
|
797
897
|
hasLandingGates,
|
|
798
898
|
resultPath,
|
|
799
899
|
moduleContracts: block.module_contracts ?? [],
|
|
800
|
-
})
|
|
900
|
+
}), (promptDigest) => renderResultTemplate({
|
|
901
|
+
runId,
|
|
902
|
+
blockId: block.block_id,
|
|
903
|
+
resultPath,
|
|
904
|
+
obligationIds,
|
|
905
|
+
promptDigest,
|
|
906
|
+
}));
|
|
801
907
|
return {
|
|
802
908
|
id: block.block_id,
|
|
803
909
|
finding_ids: [...block.items],
|
|
804
910
|
allowed_files: allowedFiles,
|
|
805
911
|
baseline_commit: baselineCommit,
|
|
806
912
|
obligation_ids: obligationIds,
|
|
807
|
-
prompt: { text:
|
|
913
|
+
prompt: { text: prompt.text, sha256: prompt.sha256 },
|
|
808
914
|
required_tests: requiredTests,
|
|
809
915
|
result_path: resultPath,
|
|
810
916
|
demand: deriveLaneDemand({
|
|
@@ -831,10 +937,10 @@ function buildCanonicalWorkload(params) {
|
|
|
831
937
|
return {
|
|
832
938
|
contract_version: WORKLOAD_CONTRACT_VERSION,
|
|
833
939
|
run_id: params.runId,
|
|
834
|
-
work_items: blocks.map((block) => buildWorkItem(params.paths, block, params.baselineCommit, params.state)),
|
|
940
|
+
work_items: blocks.map((block) => buildWorkItem(params.paths, params.runId, block, params.baselineCommit, params.state)),
|
|
835
941
|
};
|
|
836
942
|
}
|
|
837
|
-
function parseWorkItem(value, paths, state, expectedBaselineCommit) {
|
|
943
|
+
function parseWorkItem(value, paths, runId, state, expectedBaselineCommit) {
|
|
838
944
|
if (!isRecord(value) ||
|
|
839
945
|
!hasExactKeys(value, [
|
|
840
946
|
"allowed_files",
|
|
@@ -865,8 +971,7 @@ function parseWorkItem(value, paths, state, expectedBaselineCommit) {
|
|
|
865
971
|
!isRecord(value.prompt) ||
|
|
866
972
|
!hasExactKeys(value.prompt, ["sha256", "text"]) ||
|
|
867
973
|
typeof value.prompt.text !== "string" ||
|
|
868
|
-
!isSha256(value.prompt.sha256)
|
|
869
|
-
promptSha256(value.prompt.text) !== value.prompt.sha256) {
|
|
974
|
+
!isSha256(value.prompt.sha256)) {
|
|
870
975
|
return null;
|
|
871
976
|
}
|
|
872
977
|
const block = state.plan.blocks.find((candidate) => candidate.block_id === value.id);
|
|
@@ -874,7 +979,7 @@ function parseWorkItem(value, paths, state, expectedBaselineCommit) {
|
|
|
874
979
|
return null;
|
|
875
980
|
let expected;
|
|
876
981
|
try {
|
|
877
|
-
expected = buildWorkItem(paths, block, expectedBaselineCommit ?? value.baseline_commit, state);
|
|
982
|
+
expected = buildWorkItem(paths, runId, block, expectedBaselineCommit ?? value.baseline_commit, state);
|
|
878
983
|
}
|
|
879
984
|
catch {
|
|
880
985
|
return null;
|
|
@@ -898,7 +1003,7 @@ function parseWorkload(value, paths, runId, state) {
|
|
|
898
1003
|
binding.workload_sha256)) {
|
|
899
1004
|
return null;
|
|
900
1005
|
}
|
|
901
|
-
const workItems = parseAllWorkloadItems(envelope.rawItems, (item) => parseWorkItem(item, paths, state, binding?.baseline_commit));
|
|
1006
|
+
const workItems = parseAllWorkloadItems(envelope.rawItems, (item) => parseWorkItem(item, paths, runId, state, binding?.baseline_commit));
|
|
902
1007
|
if (workItems === null)
|
|
903
1008
|
return null;
|
|
904
1009
|
const ids = workItems.map((item) => item.id);
|
|
@@ -927,7 +1032,7 @@ const remediationScanMessages = {
|
|
|
927
1032
|
contractInvalid: (detail) => detail,
|
|
928
1033
|
duplicate: (resultId) => `result_id ${resultId} is duplicated in this workload`,
|
|
929
1034
|
};
|
|
930
|
-
function parseResult(value, runId, workItem
|
|
1035
|
+
function parseResult(value, runId, workItem) {
|
|
931
1036
|
if (isRecord(value) && value.contract_version === DECISION_CONTRACT_VERSION) {
|
|
932
1037
|
// Envelope first, identity second: one message (the host repairs the same
|
|
933
1038
|
// file either way), two registered checks, so the issue names which failed.
|
|
@@ -990,29 +1095,27 @@ function parseResult(value, runId, workItem, root) {
|
|
|
990
1095
|
result: value,
|
|
991
1096
|
};
|
|
992
1097
|
}
|
|
1098
|
+
// v1alpha3 (owner review of prompt 20, 2026-09-18): the host states the
|
|
1099
|
+
// landed commit and the obligation evidence, and nothing it could only
|
|
1100
|
+
// restate. The changed files come from git, the tests from the tool's own
|
|
1101
|
+
// rerun, and the landing from ancestry — `corroborateHostResult` derives and
|
|
1102
|
+
// checks each of them, so an extra key here is a refusal, never a hint.
|
|
993
1103
|
if (!isRecord(value) ||
|
|
994
1104
|
!hasExactKeys(value, [
|
|
995
|
-
"acceptance",
|
|
996
|
-
"changed_files",
|
|
997
|
-
"commit_evidence",
|
|
998
1105
|
"contract_version",
|
|
999
|
-
"
|
|
1106
|
+
"landed_commit",
|
|
1000
1107
|
"obligation_evidence",
|
|
1001
1108
|
"prompt_sha256",
|
|
1002
1109
|
"result_id",
|
|
1003
1110
|
"run_id",
|
|
1004
|
-
"test_evidence",
|
|
1005
1111
|
"work_item_id",
|
|
1006
|
-
"worktree_evidence",
|
|
1007
1112
|
]) ||
|
|
1008
1113
|
value.contract_version !== RESULT_CONTRACT_VERSION) {
|
|
1009
1114
|
return invalidResult("result_envelope", "result must match the exact current contract, run, work-item, and prompt binding");
|
|
1010
1115
|
}
|
|
1011
1116
|
// The identity walk AND its vocabulary are the CORE's, so the same broken
|
|
1012
1117
|
// submission is reported with the same named component whichever half of the
|
|
1013
|
-
// pipeline reads it. Only the framing is this draw's
|
|
1014
|
-
// part that used to differ, producing two undifferentiated sentences that
|
|
1015
|
-
// named no component at all.
|
|
1118
|
+
// pipeline reads it. Only the framing is this draw's.
|
|
1016
1119
|
const resultIdentityFailure = identityFailureDiagnostic(value, {
|
|
1017
1120
|
runId,
|
|
1018
1121
|
workItemId: workItem.id,
|
|
@@ -1021,32 +1124,8 @@ function parseResult(value, runId, workItem, root) {
|
|
|
1021
1124
|
if (resultIdentityFailure !== null) {
|
|
1022
1125
|
return invalidResult("identity_binding", `result must match the exact current contract, run, work-item, and prompt binding: ${resultIdentityFailure}`);
|
|
1023
1126
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
changedFiles.length === 0 ||
|
|
1027
|
-
new Set(changedFiles).size !== changedFiles.length ||
|
|
1028
|
-
!sameStrings([...changedFiles].sort(compareCodeUnits), changedFiles) ||
|
|
1029
|
-
changedFiles.some((path) => !pathIsAllowedByWriteScope(root, path, workItem.allowed_files))) {
|
|
1030
|
-
return invalidResult("write_scope", "changed_files must be non-empty, sorted, unique, normalized paths within allowed_files");
|
|
1031
|
-
}
|
|
1032
|
-
if (!isRecord(value.commit_evidence) ||
|
|
1033
|
-
!hasExactKeys(value.commit_evidence, ["after", "before"]) ||
|
|
1034
|
-
value.commit_evidence.before !== workItem.baseline_commit ||
|
|
1035
|
-
!isCommit(value.commit_evidence.after) ||
|
|
1036
|
-
value.commit_evidence.after === value.commit_evidence.before) {
|
|
1037
|
-
return invalidResult("commit_evidence", "commit_evidence must bind the workload baseline to a distinct full commit id");
|
|
1038
|
-
}
|
|
1039
|
-
if (!Array.isArray(value.test_evidence) ||
|
|
1040
|
-
value.test_evidence.length !== workItem.required_tests.length) {
|
|
1041
|
-
return invalidResult("test_evidence", "test_evidence must contain exactly one entry for every required test");
|
|
1042
|
-
}
|
|
1043
|
-
for (const [index, evidence] of value.test_evidence.entries()) {
|
|
1044
|
-
if (!isRecord(evidence) ||
|
|
1045
|
-
!hasExactKeys(evidence, ["command", "status"]) ||
|
|
1046
|
-
evidence.command !== workItem.required_tests[index] ||
|
|
1047
|
-
evidence.status !== "passed") {
|
|
1048
|
-
return invalidResult("test_evidence", `test_evidence[${index}] must echo the bound command with status passed`);
|
|
1049
|
-
}
|
|
1127
|
+
if (!isCommit(value.landed_commit)) {
|
|
1128
|
+
return invalidResult("landed_commit", "landed_commit must be the full id of the commit that carries this item's edits");
|
|
1050
1129
|
}
|
|
1051
1130
|
// The evidence-coverage floor: the cited obligation set must equal the
|
|
1052
1131
|
// work item's tool-owned binding exactly. Shape and coverage only — judging
|
|
@@ -1087,23 +1166,6 @@ function parseResult(value, runId, workItem, root) {
|
|
|
1087
1166
|
if (unknown.length > 0) {
|
|
1088
1167
|
return invalidResult("obligation_evidence", `obligation_evidence cites obligations the work item does not bind: ${unknown.join(", ")}`);
|
|
1089
1168
|
}
|
|
1090
|
-
if (!isRecord(value.worktree_evidence) ||
|
|
1091
|
-
!hasExactKeys(value.worktree_evidence, ["baseline_commit", "changed_files"]) ||
|
|
1092
|
-
value.worktree_evidence.baseline_commit !== workItem.baseline_commit) {
|
|
1093
|
-
return invalidResult("worktree_evidence", "worktree_evidence must bind the workload baseline and changed-file list");
|
|
1094
|
-
}
|
|
1095
|
-
const worktreeFiles = stringArray(value.worktree_evidence.changed_files);
|
|
1096
|
-
if (!worktreeFiles || !sameStrings(worktreeFiles, changedFiles)) {
|
|
1097
|
-
return invalidResult("worktree_evidence", "worktree_evidence.changed_files must exactly equal changed_files");
|
|
1098
|
-
}
|
|
1099
|
-
if (!isRecord(value.acceptance) ||
|
|
1100
|
-
!hasExactKeys(value.acceptance, ["status"]) ||
|
|
1101
|
-
value.acceptance.status !== "accepted" ||
|
|
1102
|
-
!isRecord(value.merge) ||
|
|
1103
|
-
!hasExactKeys(value.merge, ["status"]) ||
|
|
1104
|
-
value.merge.status !== "merged") {
|
|
1105
|
-
return invalidResult("landing_attestation", "acceptance and merge must both attest a completed landing");
|
|
1106
|
-
}
|
|
1107
1169
|
return {
|
|
1108
1170
|
ok: true,
|
|
1109
1171
|
kind: "landed",
|
|
@@ -1625,7 +1687,7 @@ function requiredTestIssue(workItem, failures) {
|
|
|
1625
1687
|
: failures.some((failure) => failure.outcome === "output_overflow")
|
|
1626
1688
|
? "required_test_output_overflow"
|
|
1627
1689
|
: "required_test_failed",
|
|
1628
|
-
check: "
|
|
1690
|
+
check: "required_tests",
|
|
1629
1691
|
work_item_id: workItem.id,
|
|
1630
1692
|
result_path: workItem.result_path,
|
|
1631
1693
|
message: boundRequiredTestMessage(body),
|
|
@@ -1689,23 +1751,50 @@ export async function precomputeRecoveryTestVerdicts(params) {
|
|
|
1689
1751
|
async function corroborateHostResult(params) {
|
|
1690
1752
|
const { root, state, workItem, result, verdicts } = params;
|
|
1691
1753
|
const baseline = workItem.baseline_commit;
|
|
1692
|
-
const landed = result.
|
|
1754
|
+
const landed = result.landed_commit;
|
|
1693
1755
|
let usedRecovery = false;
|
|
1694
|
-
if (!(await gitCommitExists(root, baseline))
|
|
1695
|
-
|
|
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))) {
|
|
1696
1765
|
return {
|
|
1697
1766
|
ok: false,
|
|
1698
1767
|
code: "commit_missing",
|
|
1699
|
-
check: "
|
|
1700
|
-
message: "
|
|
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",
|
|
1701
1778
|
};
|
|
1702
1779
|
}
|
|
1703
1780
|
if (!(await gitCommitIsAncestor(root, baseline, landed))) {
|
|
1704
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
|
+
}
|
|
1705
1794
|
return {
|
|
1706
1795
|
ok: false,
|
|
1707
1796
|
code: "baseline_not_ancestor",
|
|
1708
|
-
check: "
|
|
1797
|
+
check: "landed_commit",
|
|
1709
1798
|
message: "the trusted workload baseline is not an ancestor of the claimed landed commit",
|
|
1710
1799
|
};
|
|
1711
1800
|
}
|
|
@@ -1722,7 +1811,7 @@ async function corroborateHostResult(params) {
|
|
|
1722
1811
|
return {
|
|
1723
1812
|
ok: false,
|
|
1724
1813
|
code: "baseline_not_ancestor",
|
|
1725
|
-
check: "
|
|
1814
|
+
check: "landed_commit",
|
|
1726
1815
|
message: "the trusted workload baseline is not an ancestor of the claimed landed commit, " +
|
|
1727
1816
|
"and the baseline is NOT orphaned (a ref still contains it, or it is reachable " +
|
|
1728
1817
|
"from HEAD), so the stale-worker protection stands and recovery cannot waive it",
|
|
@@ -1734,25 +1823,27 @@ async function corroborateHostResult(params) {
|
|
|
1734
1823
|
return {
|
|
1735
1824
|
ok: false,
|
|
1736
1825
|
code: "commit_not_landed",
|
|
1737
|
-
check: "
|
|
1738
|
-
message: "
|
|
1826
|
+
check: "landed_commit",
|
|
1827
|
+
message: "landed_commit is not reachable from the repository HEAD",
|
|
1739
1828
|
};
|
|
1740
1829
|
}
|
|
1741
1830
|
const actualFiles = await gitChangedFilesOfCommit(root, landed);
|
|
1742
|
-
if (!actualFiles ||
|
|
1831
|
+
if (!actualFiles || actualFiles.length === 0) {
|
|
1743
1832
|
return {
|
|
1744
1833
|
ok: false,
|
|
1745
|
-
code: "
|
|
1746
|
-
check: "
|
|
1747
|
-
message: "
|
|
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",
|
|
1748
1837
|
};
|
|
1749
1838
|
}
|
|
1750
|
-
|
|
1839
|
+
const outOfScope = actualFiles.filter((path) => !pathIsAllowedByWriteScope(root, path, workItem.allowed_files));
|
|
1840
|
+
if (outOfScope.length > 0) {
|
|
1751
1841
|
return {
|
|
1752
1842
|
ok: false,
|
|
1753
1843
|
code: "changed_files_mismatch",
|
|
1754
1844
|
check: "write_scope",
|
|
1755
|
-
message: "the landed commit changed
|
|
1845
|
+
message: "the landed commit changed files outside the prompt-bound allowed_files: " +
|
|
1846
|
+
outOfScope.join(", "),
|
|
1756
1847
|
};
|
|
1757
1848
|
}
|
|
1758
1849
|
const runStartDirty = new Set((state.run_start_dirty ?? []).map(normalizeRepoPath));
|
|
@@ -1761,17 +1852,31 @@ async function corroborateHostResult(params) {
|
|
|
1761
1852
|
return {
|
|
1762
1853
|
ok: false,
|
|
1763
1854
|
code: "run_start_dirty_overlap",
|
|
1764
|
-
check: "
|
|
1855
|
+
check: "run_start_dirt",
|
|
1765
1856
|
message: `landed files overlap pre-existing run-start dirt: ${dirtyOverlap.join(", ")}`,
|
|
1766
1857
|
};
|
|
1767
1858
|
}
|
|
1768
1859
|
const failedTests = await rerunRequiredTests(root, workItem.required_tests, verdicts);
|
|
1769
1860
|
if (failedTests.length > 0) {
|
|
1770
1861
|
const issue = requiredTestIssue(workItem, failedTests);
|
|
1771
|
-
return { ok: false, code: issue.code, check: "
|
|
1862
|
+
return { ok: false, code: issue.code, check: "required_tests", message: issue.message };
|
|
1772
1863
|
}
|
|
1773
1864
|
return { ok: true, changedFiles: actualFiles, usedRecovery };
|
|
1774
1865
|
}
|
|
1866
|
+
/**
|
|
1867
|
+
* Was this persisted workload written under an earlier workload contract
|
|
1868
|
+
* version? Only a document that NAMES a different version counts: a document
|
|
1869
|
+
* with no version, or not an object at all, is invalid, not stale.
|
|
1870
|
+
*/
|
|
1871
|
+
function persistedWorkloadIsStale(value) {
|
|
1872
|
+
return (isRecord(value) &&
|
|
1873
|
+
typeof value.contract_version === "string" &&
|
|
1874
|
+
value.contract_version !== WORKLOAD_CONTRACT_VERSION);
|
|
1875
|
+
}
|
|
1876
|
+
async function persistedWorkloadFileIsStale(workloadPath) {
|
|
1877
|
+
const read = await readSubmissionDocument(workloadPath);
|
|
1878
|
+
return read.kind === "value" && persistedWorkloadIsStale(read.value);
|
|
1879
|
+
}
|
|
1775
1880
|
export async function prepareRemediationHostHandoff(params) {
|
|
1776
1881
|
const state = parseCurrentState(params.state);
|
|
1777
1882
|
if (!state)
|
|
@@ -1826,18 +1931,29 @@ export async function prepareRemediationHostHandoff(params) {
|
|
|
1826
1931
|
.join("; ")}`);
|
|
1827
1932
|
}
|
|
1828
1933
|
const workloadDigest = contentSha256(workload);
|
|
1934
|
+
// A binding whose workload file an earlier contract version wrote is
|
|
1935
|
+
// re-minted in place: same run, baseline and work item ids, new digest. Only
|
|
1936
|
+
// the DIGEST moves — anything else that no longer matches is still a refusal.
|
|
1937
|
+
const remintStaleDigest = existingRecord !== undefined &&
|
|
1938
|
+
existingRecord.workload_sha256 !== workloadDigest &&
|
|
1939
|
+
(await persistedWorkloadFileIsStale(paths.workloadPath));
|
|
1829
1940
|
if (existingRecord &&
|
|
1830
|
-
existingRecord.workload_sha256 !== workloadDigest
|
|
1941
|
+
existingRecord.workload_sha256 !== workloadDigest &&
|
|
1942
|
+
!remintStaleDigest) {
|
|
1831
1943
|
throw new Error("Trusted remediation host workload no longer matches the persisted state binding");
|
|
1832
1944
|
}
|
|
1833
|
-
const handoffRecord = existingRecord
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1945
|
+
const handoffRecord = existingRecord
|
|
1946
|
+
? remintStaleDigest
|
|
1947
|
+
? { ...existingRecord, workload_sha256: workloadDigest }
|
|
1948
|
+
: existingRecord
|
|
1949
|
+
: {
|
|
1950
|
+
contract_version: REMEDIATION_HOST_HANDOFF_RECORD_V1ALPHA2,
|
|
1951
|
+
scope_semantics: REMEDIATION_HOST_SCOPE_SEMANTICS,
|
|
1952
|
+
run_id: params.runId,
|
|
1953
|
+
baseline_commit: baselineCommit,
|
|
1954
|
+
workload_sha256: workloadDigest,
|
|
1955
|
+
work_item_ids: workload.work_items.map((item) => item.id),
|
|
1956
|
+
};
|
|
1841
1957
|
await mkdir(paths.resultDir, { recursive: true });
|
|
1842
1958
|
await writeJsonFile(paths.workloadPath, workload);
|
|
1843
1959
|
return {
|
|
@@ -1867,8 +1983,8 @@ export async function prepareRemediationHostHandoff(params) {
|
|
|
1867
1983
|
* remote ref) also fails the ancestry test when work lands elsewhere, and that
|
|
1868
1984
|
* is the ordinary stale-worker case — recovery refuses it. Every other
|
|
1869
1985
|
* corroboration check runs unchanged (the landed commit exists and is reachable
|
|
1870
|
-
* from HEAD; its mechanically derived changed files
|
|
1871
|
-
*
|
|
1986
|
+
* from HEAD; its mechanically derived changed files lie within the
|
|
1987
|
+
* prompt-bound `allowed_files`; no overlap
|
|
1872
1988
|
* with run-start dirt; the required tests rerun green), `parseResult` stays
|
|
1873
1989
|
* fully strict, and dependency/phase eligibility is enforced exactly as on the
|
|
1874
1990
|
* normal lane.
|
|
@@ -2014,6 +2130,29 @@ async function validateHostResultBundle(input) {
|
|
|
2014
2130
|
},
|
|
2015
2131
|
};
|
|
2016
2132
|
}
|
|
2133
|
+
// A workload an EARLIER contract version wrote is not a defect in it: the
|
|
2134
|
+
// tool changed underneath a live binding. It is reported as stale — no host
|
|
2135
|
+
// action — and `prepareRemediationHostHandoff` re-mints the digest and
|
|
2136
|
+
// rewrites the file on the same step, under the same work item ids.
|
|
2137
|
+
if (state.host_handoff && persistedWorkloadIsStale(workloadRead.value)) {
|
|
2138
|
+
issues.push({
|
|
2139
|
+
code: "workload_stale",
|
|
2140
|
+
check: "workload_binding",
|
|
2141
|
+
message: "the workload file was written under an earlier contract version; the tool writes it again",
|
|
2142
|
+
});
|
|
2143
|
+
return {
|
|
2144
|
+
kind: "summary",
|
|
2145
|
+
summary: {
|
|
2146
|
+
accepted_count: 0,
|
|
2147
|
+
completed_work_item_ids: [],
|
|
2148
|
+
pending_work_item_ids: state.host_handoff.work_item_ids,
|
|
2149
|
+
issues,
|
|
2150
|
+
work_item_outcomes: new Map(),
|
|
2151
|
+
state_changed: false,
|
|
2152
|
+
state: nextState,
|
|
2153
|
+
},
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2017
2156
|
const workload = parseWorkload(workloadRead.value, paths, input.runId, state);
|
|
2018
2157
|
if (!workload) {
|
|
2019
2158
|
// Accumulated, not replaced: when a block-contract defect is WHY the
|
|
@@ -2075,8 +2214,8 @@ async function validateHostResultBundle(input) {
|
|
|
2075
2214
|
*
|
|
2076
2215
|
* Ground truth, not a claim: `host_landed_commit` is written only after
|
|
2077
2216
|
* `corroborateHostResult` verified the commit resolves, is reachable from HEAD,
|
|
2078
|
-
* and that its mechanically derived diff
|
|
2079
|
-
*
|
|
2217
|
+
* and that its mechanically derived diff lies within the item's write scope.
|
|
2218
|
+
* Anything less is not a landing.
|
|
2080
2219
|
*
|
|
2081
2220
|
* ANY finding of the item counts, because the commit is attributed per FINDING
|
|
2082
2221
|
* and a work item is a bundle of them: a commit for one of its findings is work
|
|
@@ -2096,7 +2235,7 @@ async function executeHostVerificationReruns(ctx, acc) {
|
|
|
2096
2235
|
continue;
|
|
2097
2236
|
if (!ctx.eligibleIds.has(workItem.id)) {
|
|
2098
2237
|
acc.issues.push({
|
|
2099
|
-
code: "
|
|
2238
|
+
code: "work_item_not_eligible",
|
|
2100
2239
|
work_item_id: workItem.id,
|
|
2101
2240
|
result_path: workItem.result_path,
|
|
2102
2241
|
message: "the work item is no longer dependency/phase eligible",
|
|
@@ -2109,7 +2248,7 @@ async function executeHostVerificationReruns(ctx, acc) {
|
|
|
2109
2248
|
workItemId: workItem.id,
|
|
2110
2249
|
resultPath: workItem.result_path,
|
|
2111
2250
|
parse: (value) => {
|
|
2112
|
-
const result = parseResult(value, ctx.runId, workItem
|
|
2251
|
+
const result = parseResult(value, ctx.runId, workItem);
|
|
2113
2252
|
return result.ok
|
|
2114
2253
|
? { ok: true, parsed: result }
|
|
2115
2254
|
: { ok: false, check: result.check, detail: result.reason };
|
|
@@ -2239,7 +2378,7 @@ async function executeHostVerificationReruns(ctx, acc) {
|
|
|
2239
2378
|
// landed commit is carried on the event as `landed_commit` and read
|
|
2240
2379
|
// back through `recoveryMarkMatches` — prose is not an identity, and a
|
|
2241
2380
|
// message reworded by any later edit silently un-deduped the mark.
|
|
2242
|
-
const landedCommit = result.
|
|
2381
|
+
const landedCommit = result.landed_commit;
|
|
2243
2382
|
const alreadyMarked = acc.recordedRecoveryMarks.some((event) => event.run_id === ctx.runId &&
|
|
2244
2383
|
recoveryMarkMatches(event, workItem.id, landedCommit));
|
|
2245
2384
|
if (!alreadyMarked) {
|
|
@@ -2283,7 +2422,7 @@ async function executeHostVerificationReruns(ctx, acc) {
|
|
|
2283
2422
|
workItem,
|
|
2284
2423
|
pendingItems,
|
|
2285
2424
|
at: new Date().toISOString(),
|
|
2286
|
-
landedCommit: result.
|
|
2425
|
+
landedCommit: result.landed_commit,
|
|
2287
2426
|
changedFiles: corroborated.changedFiles,
|
|
2288
2427
|
});
|
|
2289
2428
|
for (const changedFile of corroborated.changedFiles) {
|
|
@@ -2324,17 +2463,14 @@ function commitRemediationStateUpdates(ctx, acc, verdicts, issues) {
|
|
|
2324
2463
|
item.status = "needs_clarification";
|
|
2325
2464
|
delete item.completed_at;
|
|
2326
2465
|
item.failure_reason = outcome.question;
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
});
|
|
2336
|
-
}
|
|
2337
|
-
nextState.clarifications = clarifications;
|
|
2466
|
+
// The question lives on the item (its one home): the clarification
|
|
2467
|
+
// round lists every paused item, so no answer can erase another's.
|
|
2468
|
+
item.clarification_question = {
|
|
2469
|
+
category: isClarificationCategory(outcome.category)
|
|
2470
|
+
? outcome.category
|
|
2471
|
+
: "scope_of_fix",
|
|
2472
|
+
description: outcome.question,
|
|
2473
|
+
};
|
|
2338
2474
|
}
|
|
2339
2475
|
}
|
|
2340
2476
|
continue;
|