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.
Files changed (443) hide show
  1. package/README.md +31 -1
  2. package/dist/audit/cli/args.d.ts.map +1 -1
  3. package/dist/audit/cli/args.js +6 -2
  4. package/dist/audit/cli/args.js.map +1 -1
  5. package/dist/audit/cli/argumentContract.d.ts +11 -0
  6. package/dist/audit/cli/argumentContract.d.ts.map +1 -0
  7. package/dist/audit/cli/argumentContract.js +45 -0
  8. package/dist/audit/cli/argumentContract.js.map +1 -0
  9. package/dist/audit/cli/auditReviewBindings.d.ts +49 -0
  10. package/dist/audit/cli/auditReviewBindings.d.ts.map +1 -0
  11. package/dist/audit/cli/auditReviewBindings.js +61 -0
  12. package/dist/audit/cli/auditReviewBindings.js.map +1 -0
  13. package/dist/audit/cli/auditStep.d.ts +1 -27
  14. package/dist/audit/cli/auditStep.d.ts.map +1 -1
  15. package/dist/audit/cli/auditStep.js +7 -32
  16. package/dist/audit/cli/auditStep.js.map +1 -1
  17. package/dist/audit/cli/charterComparisonPrompt.d.ts +2 -0
  18. package/dist/audit/cli/charterComparisonPrompt.d.ts.map +1 -1
  19. package/dist/audit/cli/charterComparisonPrompt.js +9 -0
  20. package/dist/audit/cli/charterComparisonPrompt.js.map +1 -1
  21. package/dist/audit/cli/charterExtractionPrompt.d.ts.map +1 -1
  22. package/dist/audit/cli/charterExtractionPrompt.js +17 -3
  23. package/dist/audit/cli/charterExtractionPrompt.js.map +1 -1
  24. package/dist/audit/cli/conceptualDispatch.d.ts +1 -1
  25. package/dist/audit/cli/conceptualDispatch.d.ts.map +1 -1
  26. package/dist/audit/cli/conceptualDispatch.js +15 -2
  27. package/dist/audit/cli/conceptualDispatch.js.map +1 -1
  28. package/dist/audit/cli/confirmIntentStep.d.ts.map +1 -1
  29. package/dist/audit/cli/confirmIntentStep.js +10 -17
  30. package/dist/audit/cli/confirmIntentStep.js.map +1 -1
  31. package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
  32. package/dist/audit/cli/dispatch/hostHandoff.js +231 -229
  33. package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
  34. package/dist/audit/cli/explainTaskCommand.d.ts.map +1 -1
  35. package/dist/audit/cli/explainTaskCommand.js +3 -1
  36. package/dist/audit/cli/explainTaskCommand.js.map +1 -1
  37. package/dist/audit/cli/fanoutLanes.d.ts +10 -14
  38. package/dist/audit/cli/fanoutLanes.d.ts.map +1 -1
  39. package/dist/audit/cli/fanoutLanes.js +50 -16
  40. package/dist/audit/cli/fanoutLanes.js.map +1 -1
  41. package/dist/audit/cli/functionalPreflight.d.ts +96 -0
  42. package/dist/audit/cli/functionalPreflight.d.ts.map +1 -0
  43. package/dist/audit/cli/functionalPreflight.js +103 -0
  44. package/dist/audit/cli/functionalPreflight.js.map +1 -0
  45. package/dist/audit/cli/laneSubmissions.d.ts +5 -1
  46. package/dist/audit/cli/laneSubmissions.d.ts.map +1 -1
  47. package/dist/audit/cli/laneSubmissions.js +45 -34
  48. package/dist/audit/cli/laneSubmissions.js.map +1 -1
  49. package/dist/audit/cli/nextStepCommand.d.ts +1 -9
  50. package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
  51. package/dist/audit/cli/nextStepCommand.js +101 -38
  52. package/dist/audit/cli/nextStepCommand.js.map +1 -1
  53. package/dist/audit/cli/nextStepHelpers.d.ts +10 -25
  54. package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
  55. package/dist/audit/cli/nextStepHelpers.js +57 -67
  56. package/dist/audit/cli/nextStepHelpers.js.map +1 -1
  57. package/dist/audit/cli/prompts.d.ts.map +1 -1
  58. package/dist/audit/cli/prompts.js +9 -4
  59. package/dist/audit/cli/prompts.js.map +1 -1
  60. package/dist/audit/cli/recoverSubmissionCommand.d.ts.map +1 -1
  61. package/dist/audit/cli/recoverSubmissionCommand.js +8 -1
  62. package/dist/audit/cli/recoverSubmissionCommand.js.map +1 -1
  63. package/dist/audit/cli/reviewRun.js +1 -1
  64. package/dist/audit/cli/reviewRun.js.map +1 -1
  65. package/dist/audit/cli/reviewSubmission.d.ts +24 -0
  66. package/dist/audit/cli/reviewSubmission.d.ts.map +1 -0
  67. package/dist/audit/cli/reviewSubmission.js +97 -0
  68. package/dist/audit/cli/reviewSubmission.js.map +1 -0
  69. package/dist/audit/cli/sampleRunCommand.d.ts +3 -3
  70. package/dist/audit/cli/steps.d.ts +18 -18
  71. package/dist/audit/cli/steps.d.ts.map +1 -1
  72. package/dist/audit/cli/steps.js +1 -0
  73. package/dist/audit/cli/steps.js.map +1 -1
  74. package/dist/audit/cli/unacceptResultsCommand.d.ts.map +1 -1
  75. package/dist/audit/cli/unacceptResultsCommand.js +2 -1
  76. package/dist/audit/cli/unacceptResultsCommand.js.map +1 -1
  77. package/dist/audit/cli.d.ts +2 -1
  78. package/dist/audit/cli.d.ts.map +1 -1
  79. package/dist/audit/cli.js +27 -19
  80. package/dist/audit/cli.js.map +1 -1
  81. package/dist/audit/contracts/workerSchemas.d.ts +10 -10
  82. package/dist/audit/contracts/wrapperResponse.d.ts +34 -34
  83. package/dist/audit/extractors/commentDecomposition.d.ts +14 -0
  84. package/dist/audit/extractors/commentDecomposition.d.ts.map +1 -1
  85. package/dist/audit/extractors/commentDecomposition.js +2 -1
  86. package/dist/audit/extractors/commentDecomposition.js.map +1 -1
  87. package/dist/audit/extractors/graph.d.ts +1 -1
  88. package/dist/audit/extractors/graph.d.ts.map +1 -1
  89. package/dist/audit/extractors/graph.js +12 -7
  90. package/dist/audit/extractors/graph.js.map +1 -1
  91. package/dist/audit/extractors/graphPythonImports.d.ts +3 -0
  92. package/dist/audit/extractors/graphPythonImports.d.ts.map +1 -1
  93. package/dist/audit/extractors/graphPythonImports.js +24 -5
  94. package/dist/audit/extractors/graphPythonImports.js.map +1 -1
  95. package/dist/audit/extractors/graphRouteSources.d.ts +7 -0
  96. package/dist/audit/extractors/graphRouteSources.d.ts.map +1 -0
  97. package/dist/audit/extractors/graphRouteSources.js +208 -0
  98. package/dist/audit/extractors/graphRouteSources.js.map +1 -0
  99. package/dist/audit/extractors/graphRoutes.d.ts +1 -1
  100. package/dist/audit/extractors/graphRoutes.d.ts.map +1 -1
  101. package/dist/audit/extractors/graphRoutes.js +65 -68
  102. package/dist/audit/extractors/graphRoutes.js.map +1 -1
  103. package/dist/audit/io/artifacts.d.ts +1 -0
  104. package/dist/audit/io/artifacts.d.ts.map +1 -1
  105. package/dist/audit/io/artifacts.js +29 -10
  106. package/dist/audit/io/artifacts.js.map +1 -1
  107. package/dist/audit/io/runArtifacts.d.ts +1 -1
  108. package/dist/audit/io/runArtifacts.d.ts.map +1 -1
  109. package/dist/audit/io/runArtifacts.js +3 -0
  110. package/dist/audit/io/runArtifacts.js.map +1 -1
  111. package/dist/audit/io/toolingManifest.d.ts.map +1 -1
  112. package/dist/audit/io/toolingManifest.js +11 -32
  113. package/dist/audit/io/toolingManifest.js.map +1 -1
  114. package/dist/audit/orchestrator/acquisitionExecutor.d.ts +2 -3
  115. package/dist/audit/orchestrator/acquisitionExecutor.d.ts.map +1 -1
  116. package/dist/audit/orchestrator/acquisitionExecutor.js.map +1 -1
  117. package/dist/audit/orchestrator/advanceTypes.d.ts +1 -2
  118. package/dist/audit/orchestrator/advanceTypes.d.ts.map +1 -1
  119. package/dist/audit/orchestrator/autoFixExecutor.d.ts +2 -2
  120. package/dist/audit/orchestrator/autoFixExecutor.d.ts.map +1 -1
  121. package/dist/audit/orchestrator/autoFixExecutor.js +2 -2
  122. package/dist/audit/orchestrator/autoFixExecutor.js.map +1 -1
  123. package/dist/audit/orchestrator/charterFidelityPacket.d.ts +2 -0
  124. package/dist/audit/orchestrator/charterFidelityPacket.d.ts.map +1 -1
  125. package/dist/audit/orchestrator/charterFidelityPacket.js +6 -1
  126. package/dist/audit/orchestrator/charterFidelityPacket.js.map +1 -1
  127. package/dist/audit/orchestrator/charterPackets.d.ts +2 -0
  128. package/dist/audit/orchestrator/charterPackets.d.ts.map +1 -1
  129. package/dist/audit/orchestrator/charterPackets.js +6 -0
  130. package/dist/audit/orchestrator/charterPackets.js.map +1 -1
  131. package/dist/audit/orchestrator/designReviewProjection.d.ts +5 -8
  132. package/dist/audit/orchestrator/designReviewProjection.d.ts.map +1 -1
  133. package/dist/audit/orchestrator/designReviewProjection.js +7 -1
  134. package/dist/audit/orchestrator/designReviewProjection.js.map +1 -1
  135. package/dist/audit/orchestrator/designReviewPrompt.d.ts +3 -47
  136. package/dist/audit/orchestrator/designReviewPrompt.d.ts.map +1 -1
  137. package/dist/audit/orchestrator/designReviewPrompt.js +28 -135
  138. package/dist/audit/orchestrator/designReviewPrompt.js.map +1 -1
  139. package/dist/audit/orchestrator/designReviewSnapshot.d.ts +24 -1
  140. package/dist/audit/orchestrator/designReviewSnapshot.d.ts.map +1 -1
  141. package/dist/audit/orchestrator/designReviewSnapshot.js +8 -2
  142. package/dist/audit/orchestrator/designReviewSnapshot.js.map +1 -1
  143. package/dist/audit/orchestrator/designReviewTask.d.ts +46 -0
  144. package/dist/audit/orchestrator/designReviewTask.d.ts.map +1 -0
  145. package/dist/audit/orchestrator/designReviewTask.js +44 -0
  146. package/dist/audit/orchestrator/designReviewTask.js.map +1 -0
  147. package/dist/audit/orchestrator/intentCheckpointGate.d.ts +1 -1
  148. package/dist/audit/orchestrator/intentCheckpointGate.d.ts.map +1 -1
  149. package/dist/audit/orchestrator/intentCheckpointGate.js +4 -2
  150. package/dist/audit/orchestrator/intentCheckpointGate.js.map +1 -1
  151. package/dist/audit/orchestrator/intentEquivalenceExecutor.d.ts +3 -0
  152. package/dist/audit/orchestrator/intentEquivalenceExecutor.d.ts.map +1 -1
  153. package/dist/audit/orchestrator/intentEquivalenceExecutor.js +2 -0
  154. package/dist/audit/orchestrator/intentEquivalenceExecutor.js.map +1 -1
  155. package/dist/audit/orchestrator/intentInterpreter.d.ts +1 -9
  156. package/dist/audit/orchestrator/intentInterpreter.d.ts.map +1 -1
  157. package/dist/audit/orchestrator/intentInterpreter.js +6 -11
  158. package/dist/audit/orchestrator/intentInterpreter.js.map +1 -1
  159. package/dist/audit/orchestrator/selectiveDeepening/shared.d.ts +2 -2
  160. package/dist/audit/orchestrator/selectiveDeepening/shared.d.ts.map +1 -1
  161. package/dist/audit/orchestrator/staleness.d.ts.map +1 -1
  162. package/dist/audit/orchestrator/staleness.js +32 -40
  163. package/dist/audit/orchestrator/staleness.js.map +1 -1
  164. package/dist/audit/orchestrator/state.d.ts.map +1 -1
  165. package/dist/audit/orchestrator/state.js +8 -14
  166. package/dist/audit/orchestrator/state.js.map +1 -1
  167. package/dist/audit/orchestrator/systemicChallengeExecutor.d.ts +19 -0
  168. package/dist/audit/orchestrator/systemicChallengeExecutor.d.ts.map +1 -1
  169. package/dist/audit/orchestrator/systemicChallengeExecutor.js +15 -1
  170. package/dist/audit/orchestrator/systemicChallengeExecutor.js.map +1 -1
  171. package/dist/audit/reporting/criticalFlowFallbackPrompt.d.ts +1 -1
  172. package/dist/audit/reporting/criticalFlowFallbackPrompt.d.ts.map +1 -1
  173. package/dist/audit/reporting/criticalFlowFallbackPrompt.js +4 -2
  174. package/dist/audit/reporting/criticalFlowFallbackPrompt.js.map +1 -1
  175. package/dist/audit/reporting/mergeFindings.d.ts.map +1 -1
  176. package/dist/audit/reporting/mergeFindings.js +43 -2
  177. package/dist/audit/reporting/mergeFindings.js.map +1 -1
  178. package/dist/audit/types/auditScope.d.ts +2 -2
  179. package/dist/audit/types/auditState.d.ts +6 -6
  180. package/dist/audit/types/conceptualAdjudication.d.ts +19 -19
  181. package/dist/audit/types/conceptualAdjudication.d.ts.map +1 -1
  182. package/dist/audit/types/conceptualAdjudication.js +3 -3
  183. package/dist/audit/types/conceptualAdjudication.js.map +1 -1
  184. package/dist/audit/types.d.ts +17 -19
  185. package/dist/audit/types.d.ts.map +1 -1
  186. package/dist/audit/types.js +19 -20
  187. package/dist/audit/types.js.map +1 -1
  188. package/dist/remediate/contractPipeline/artifactStore.d.ts +3 -1
  189. package/dist/remediate/contractPipeline/artifactStore.d.ts.map +1 -1
  190. package/dist/remediate/contractPipeline/artifactStore.js +26 -31
  191. package/dist/remediate/contractPipeline/artifactStore.js.map +1 -1
  192. package/dist/remediate/contractPipeline/changeClassification.d.ts.map +1 -1
  193. package/dist/remediate/contractPipeline/changeClassification.js +20 -8
  194. package/dist/remediate/contractPipeline/changeClassification.js.map +1 -1
  195. package/dist/remediate/contractPipeline/contractReviewBinding.d.ts +28 -0
  196. package/dist/remediate/contractPipeline/contractReviewBinding.d.ts.map +1 -0
  197. package/dist/remediate/contractPipeline/contractReviewBinding.js +75 -0
  198. package/dist/remediate/contractPipeline/contractReviewBinding.js.map +1 -0
  199. package/dist/remediate/contractPipeline/repairState.d.ts +3 -1
  200. package/dist/remediate/contractPipeline/repairState.d.ts.map +1 -1
  201. package/dist/remediate/contractPipeline/repairState.js +1 -0
  202. package/dist/remediate/contractPipeline/repairState.js.map +1 -1
  203. package/dist/remediate/contractPipeline/sketchSource.d.ts +1 -0
  204. package/dist/remediate/contractPipeline/sketchSource.d.ts.map +1 -1
  205. package/dist/remediate/contractPipeline/sketchSource.js +1 -0
  206. package/dist/remediate/contractPipeline/sketchSource.js.map +1 -1
  207. package/dist/remediate/dedup/crossLensDedup.d.ts +1 -2
  208. package/dist/remediate/dedup/crossLensDedup.d.ts.map +1 -1
  209. package/dist/remediate/dedup/crossLensDedup.js +1 -0
  210. package/dist/remediate/dedup/crossLensDedup.js.map +1 -1
  211. package/dist/remediate/index.d.ts.map +1 -1
  212. package/dist/remediate/index.js +92 -52
  213. package/dist/remediate/index.js.map +1 -1
  214. package/dist/remediate/intake.d.ts +54 -0
  215. package/dist/remediate/intake.d.ts.map +1 -1
  216. package/dist/remediate/intake.js +15 -1
  217. package/dist/remediate/intake.js.map +1 -1
  218. package/dist/remediate/intakeSelection.d.ts +27 -0
  219. package/dist/remediate/intakeSelection.d.ts.map +1 -0
  220. package/dist/remediate/intakeSelection.js +37 -0
  221. package/dist/remediate/intakeSelection.js.map +1 -0
  222. package/dist/remediate/intent/intentPersistence.d.ts +41 -0
  223. package/dist/remediate/intent/intentPersistence.d.ts.map +1 -0
  224. package/dist/remediate/intent/intentPersistence.js +129 -0
  225. package/dist/remediate/intent/intentPersistence.js.map +1 -0
  226. package/dist/remediate/phases/close.d.ts +2 -1
  227. package/dist/remediate/phases/close.d.ts.map +1 -1
  228. package/dist/remediate/phases/close.js +19 -2
  229. package/dist/remediate/phases/close.js.map +1 -1
  230. package/dist/remediate/phases/closeReviewProvenance.d.ts +4 -0
  231. package/dist/remediate/phases/closeReviewProvenance.d.ts.map +1 -0
  232. package/dist/remediate/phases/closeReviewProvenance.js +16 -0
  233. package/dist/remediate/phases/closeReviewProvenance.js.map +1 -0
  234. package/dist/remediate/phases/triage.d.ts.map +1 -1
  235. package/dist/remediate/phases/triage.js +3 -1
  236. package/dist/remediate/phases/triage.js.map +1 -1
  237. package/dist/remediate/review/filterDispositions.d.ts +13 -0
  238. package/dist/remediate/review/filterDispositions.d.ts.map +1 -0
  239. package/dist/remediate/review/filterDispositions.js +32 -0
  240. package/dist/remediate/review/filterDispositions.js.map +1 -0
  241. package/dist/remediate/state/runIdentity.d.ts +27 -0
  242. package/dist/remediate/state/runIdentity.d.ts.map +1 -0
  243. package/dist/remediate/state/runIdentity.js +38 -0
  244. package/dist/remediate/state/runIdentity.js.map +1 -0
  245. package/dist/remediate/state/store.d.ts +80 -1
  246. package/dist/remediate/state/store.d.ts.map +1 -1
  247. package/dist/remediate/state/store.js +82 -1
  248. package/dist/remediate/state/store.js.map +1 -1
  249. package/dist/remediate/state/types.d.ts +215 -42
  250. package/dist/remediate/state/types.d.ts.map +1 -1
  251. package/dist/remediate/state/types.js +11 -3
  252. package/dist/remediate/state/types.js.map +1 -1
  253. package/dist/remediate/steps/contractPipeline.d.ts +61 -3
  254. package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
  255. package/dist/remediate/steps/contractPipeline.js +121 -47
  256. package/dist/remediate/steps/contractPipeline.js.map +1 -1
  257. package/dist/remediate/steps/contractPipelinePrompts.d.ts +27 -4
  258. package/dist/remediate/steps/contractPipelinePrompts.d.ts.map +1 -1
  259. package/dist/remediate/steps/contractPipelinePrompts.js +34 -32
  260. package/dist/remediate/steps/contractPipelinePrompts.js.map +1 -1
  261. package/dist/remediate/steps/dispatch/contractConformanceReview.d.ts +31 -0
  262. package/dist/remediate/steps/dispatch/contractConformanceReview.d.ts.map +1 -0
  263. package/dist/remediate/steps/dispatch/contractConformanceReview.js +98 -0
  264. package/dist/remediate/steps/dispatch/contractConformanceReview.js.map +1 -0
  265. package/dist/remediate/steps/dispatch/hostContracts.d.ts +133 -0
  266. package/dist/remediate/steps/dispatch/hostContracts.d.ts.map +1 -0
  267. package/dist/remediate/steps/dispatch/hostContracts.js +104 -0
  268. package/dist/remediate/steps/dispatch/hostContracts.js.map +1 -0
  269. package/dist/remediate/steps/dispatch/hostCorroboration.d.ts +70 -0
  270. package/dist/remediate/steps/dispatch/hostCorroboration.d.ts.map +1 -0
  271. package/dist/remediate/steps/dispatch/hostCorroboration.js +350 -0
  272. package/dist/remediate/steps/dispatch/hostCorroboration.js.map +1 -0
  273. package/dist/remediate/steps/dispatch/hostHandoff.d.ts +8 -162
  274. package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
  275. package/dist/remediate/steps/dispatch/hostHandoff.js +137 -690
  276. package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
  277. package/dist/remediate/steps/dispatch/requiredTests.d.ts +83 -0
  278. package/dist/remediate/steps/dispatch/requiredTests.d.ts.map +1 -0
  279. package/dist/remediate/steps/dispatch/requiredTests.js +262 -0
  280. package/dist/remediate/steps/dispatch/requiredTests.js.map +1 -0
  281. package/dist/remediate/steps/finalGate.d.ts +8 -7
  282. package/dist/remediate/steps/finalGate.d.ts.map +1 -1
  283. package/dist/remediate/steps/finalGate.js +14 -11
  284. package/dist/remediate/steps/finalGate.js.map +1 -1
  285. package/dist/remediate/steps/frictionCloseout.d.ts +34 -0
  286. package/dist/remediate/steps/frictionCloseout.d.ts.map +1 -0
  287. package/dist/remediate/steps/frictionCloseout.js +113 -0
  288. package/dist/remediate/steps/frictionCloseout.js.map +1 -0
  289. package/dist/remediate/steps/gateCommands.d.ts +7 -9
  290. package/dist/remediate/steps/gateCommands.d.ts.map +1 -1
  291. package/dist/remediate/steps/gateCommands.js +37 -11
  292. package/dist/remediate/steps/gateCommands.js.map +1 -1
  293. package/dist/remediate/steps/intakeResolver.d.ts +2 -0
  294. package/dist/remediate/steps/intakeResolver.d.ts.map +1 -1
  295. package/dist/remediate/steps/intakeResolver.js +36 -11
  296. package/dist/remediate/steps/intakeResolver.js.map +1 -1
  297. package/dist/remediate/steps/nextStep.d.ts +23 -110
  298. package/dist/remediate/steps/nextStep.d.ts.map +1 -1
  299. package/dist/remediate/steps/nextStep.js +232 -508
  300. package/dist/remediate/steps/nextStep.js.map +1 -1
  301. package/dist/remediate/steps/prompts.d.ts +1 -1
  302. package/dist/remediate/steps/prompts.d.ts.map +1 -1
  303. package/dist/remediate/steps/prompts.js +7 -2
  304. package/dist/remediate/steps/prompts.js.map +1 -1
  305. package/dist/remediate/steps/recoverIngest.d.ts +57 -0
  306. package/dist/remediate/steps/recoverIngest.d.ts.map +1 -0
  307. package/dist/remediate/steps/recoverIngest.js +171 -0
  308. package/dist/remediate/steps/recoverIngest.js.map +1 -0
  309. package/dist/remediate/steps/stepWriter.d.ts +5 -4
  310. package/dist/remediate/steps/stepWriter.d.ts.map +1 -1
  311. package/dist/remediate/steps/stepWriter.js +2 -1
  312. package/dist/remediate/steps/stepWriter.js.map +1 -1
  313. package/dist/remediate/steps/types.d.ts +2 -3
  314. package/dist/remediate/steps/types.d.ts.map +1 -1
  315. package/dist/remediate/steps/types.js.map +1 -1
  316. package/dist/remediate/validation/artifacts.d.ts.map +1 -1
  317. package/dist/remediate/validation/artifacts.js +5 -12
  318. package/dist/remediate/validation/artifacts.js.map +1 -1
  319. package/dist/remediate/validation/contractPipeline.d.ts.map +1 -1
  320. package/dist/remediate/validation/contractPipeline.js +10 -7
  321. package/dist/remediate/validation/contractPipeline.js.map +1 -1
  322. package/dist/remediate/validation/contractPipelineGates.d.ts +13 -67
  323. package/dist/remediate/validation/contractPipelineGates.d.ts.map +1 -1
  324. package/dist/remediate/validation/contractPipelineGates.js +65 -139
  325. package/dist/remediate/validation/contractPipelineGates.js.map +1 -1
  326. package/dist/remediate/validation/remediationState.d.ts.map +1 -1
  327. package/dist/remediate/validation/remediationState.js +9 -3
  328. package/dist/remediate/validation/remediationState.js.map +1 -1
  329. package/dist/shared/agentReflections.d.ts +2 -2
  330. package/dist/shared/analyzerPolicy.d.ts +2 -16
  331. package/dist/shared/analyzerPolicy.d.ts.map +1 -1
  332. package/dist/shared/analyzerPolicy.js +22 -24
  333. package/dist/shared/analyzerPolicy.js.map +1 -1
  334. package/dist/shared/analyzerRunConsent.d.ts +33 -0
  335. package/dist/shared/analyzerRunConsent.d.ts.map +1 -0
  336. package/dist/shared/analyzerRunConsent.js +42 -0
  337. package/dist/shared/analyzerRunConsent.js.map +1 -0
  338. package/dist/shared/analyzers/acquisitionEngine.d.ts +3 -8
  339. package/dist/shared/analyzers/acquisitionEngine.d.ts.map +1 -1
  340. package/dist/shared/analyzers/acquisitionEngine.js +1 -0
  341. package/dist/shared/analyzers/acquisitionEngine.js.map +1 -1
  342. package/dist/shared/decompose/charterExtraction.d.ts +8 -8
  343. package/dist/shared/decompose/systemicChallenge.d.ts +12 -12
  344. package/dist/shared/engine/obligationEngine.d.ts +2 -2
  345. package/dist/shared/findings/dedupe.d.ts.map +1 -1
  346. package/dist/shared/findings/dedupe.js +16 -0
  347. package/dist/shared/findings/dedupe.js.map +1 -1
  348. package/dist/shared/graph/orderedReachability.d.ts +9 -0
  349. package/dist/shared/graph/orderedReachability.d.ts.map +1 -0
  350. package/dist/shared/graph/orderedReachability.js +21 -0
  351. package/dist/shared/graph/orderedReachability.js.map +1 -0
  352. package/dist/shared/index.d.ts +9 -3
  353. package/dist/shared/index.d.ts.map +1 -1
  354. package/dist/shared/index.js +6 -3
  355. package/dist/shared/index.js.map +1 -1
  356. package/dist/shared/intake/guidanceBootstrap.d.ts +2 -0
  357. package/dist/shared/intake/guidanceBootstrap.d.ts.map +1 -1
  358. package/dist/shared/intake/guidanceBootstrap.js +10 -1
  359. package/dist/shared/intake/guidanceBootstrap.js.map +1 -1
  360. package/dist/shared/intent/constraintClauses.d.ts +3 -0
  361. package/dist/shared/intent/constraintClauses.d.ts.map +1 -1
  362. package/dist/shared/intent/constraintClauses.js +9 -0
  363. package/dist/shared/intent/constraintClauses.js.map +1 -1
  364. package/dist/shared/io/artifactTreeHold.d.ts +5 -0
  365. package/dist/shared/io/artifactTreeHold.d.ts.map +1 -0
  366. package/dist/shared/io/artifactTreeHold.js +13 -0
  367. package/dist/shared/io/artifactTreeHold.js.map +1 -0
  368. package/dist/shared/io/auditToolsPaths.d.ts +3 -1
  369. package/dist/shared/io/auditToolsPaths.d.ts.map +1 -1
  370. package/dist/shared/io/auditToolsPaths.js +6 -1
  371. package/dist/shared/io/auditToolsPaths.js.map +1 -1
  372. package/dist/shared/io/collectFilesSorted.d.ts +10 -0
  373. package/dist/shared/io/collectFilesSorted.d.ts.map +1 -0
  374. package/dist/shared/io/collectFilesSorted.js +38 -0
  375. package/dist/shared/io/collectFilesSorted.js.map +1 -0
  376. package/dist/shared/io/json.d.ts.map +1 -1
  377. package/dist/shared/io/json.js +11 -4
  378. package/dist/shared/io/json.js.map +1 -1
  379. package/dist/shared/io/stepContractWriter.d.ts.map +1 -1
  380. package/dist/shared/io/stepContractWriter.js +8 -0
  381. package/dist/shared/io/stepContractWriter.js.map +1 -1
  382. package/dist/shared/loopCorePaths.d.ts.map +1 -1
  383. package/dist/shared/loopCorePaths.js +9 -0
  384. package/dist/shared/loopCorePaths.js.map +1 -1
  385. package/dist/shared/observability/rootLogObservations.d.ts +10 -0
  386. package/dist/shared/observability/rootLogObservations.d.ts.map +1 -0
  387. package/dist/shared/observability/rootLogObservations.js +147 -0
  388. package/dist/shared/observability/rootLogObservations.js.map +1 -0
  389. package/dist/shared/prompts.d.ts +7 -7
  390. package/dist/shared/prompts.d.ts.map +1 -1
  391. package/dist/shared/prompts.js +11 -11
  392. package/dist/shared/prompts.js.map +1 -1
  393. package/dist/shared/submission/submissionLedger.d.ts +7 -1
  394. package/dist/shared/submission/submissionLedger.d.ts.map +1 -1
  395. package/dist/shared/submission/submissionLedger.js +8 -0
  396. package/dist/shared/submission/submissionLedger.js.map +1 -1
  397. package/dist/shared/tooling/testCommand.d.ts +1 -0
  398. package/dist/shared/tooling/testCommand.d.ts.map +1 -1
  399. package/dist/shared/tooling/testCommand.js +4 -0
  400. package/dist/shared/tooling/testCommand.js.map +1 -1
  401. package/dist/shared/types/charter.d.ts +8 -8
  402. package/dist/shared/types/conceptualPerspective.d.ts +66 -0
  403. package/dist/shared/types/conceptualPerspective.d.ts.map +1 -0
  404. package/dist/shared/types/conceptualPerspective.js +135 -0
  405. package/dist/shared/types/conceptualPerspective.js.map +1 -0
  406. package/dist/shared/types/contractPipeline/design.d.ts +5 -0
  407. package/dist/shared/types/contractPipeline/design.d.ts.map +1 -1
  408. package/dist/shared/types/contractPipeline/design.js +6 -0
  409. package/dist/shared/types/contractPipeline/design.js.map +1 -1
  410. package/dist/shared/types/contractPipeline/implementation.d.ts +54 -0
  411. package/dist/shared/types/contractPipeline/implementation.d.ts.map +1 -1
  412. package/dist/shared/types/contractPipeline/implementation.js +10 -5
  413. package/dist/shared/types/contractPipeline/implementation.js.map +1 -1
  414. package/dist/shared/types/contractPipeline/obligations.d.ts +21 -10
  415. package/dist/shared/types/contractPipeline/obligations.d.ts.map +1 -1
  416. package/dist/shared/types/contractPipeline/obligations.js +13 -1
  417. package/dist/shared/types/contractPipeline/obligations.js.map +1 -1
  418. package/dist/shared/types/disposition.d.ts +6 -6
  419. package/dist/shared/types/finding.d.ts +50 -50
  420. package/dist/shared/types/intentCheckpoint.d.ts +66 -11
  421. package/dist/shared/types/intentCheckpoint.d.ts.map +1 -1
  422. package/dist/shared/types/intentCheckpoint.js +10 -2
  423. package/dist/shared/types/intentCheckpoint.js.map +1 -1
  424. package/dist/shared/types/remediationOutcome.d.ts +252 -24
  425. package/dist/shared/types/remediationOutcome.d.ts.map +1 -1
  426. package/dist/shared/types/remediationOutcome.js +4 -0
  427. package/dist/shared/types/remediationOutcome.js.map +1 -1
  428. package/dist/shared/types/reviewIndependence.d.ts +225 -0
  429. package/dist/shared/types/reviewIndependence.d.ts.map +1 -0
  430. package/dist/shared/types/reviewIndependence.js +67 -0
  431. package/dist/shared/types/reviewIndependence.js.map +1 -0
  432. package/dist/shared/types/stepContract.d.ts +8 -2
  433. package/dist/shared/types/stepContract.d.ts.map +1 -1
  434. package/dist/shared/types/stepContract.js +4 -1
  435. package/dist/shared/types/stepContract.js.map +1 -1
  436. package/dist/shared/validation/findingsReport.d.ts.map +1 -1
  437. package/dist/shared/validation/findingsReport.js +13 -0
  438. package/dist/shared/validation/findingsReport.js.map +1 -1
  439. package/docs/audit-pkg/contracts.md +38 -2
  440. package/docs/audit-pkg/operator-guide.md +59 -4
  441. package/package.json +5 -5
  442. package/skills/audit-code/audit-code.prompt.md +1 -31
  443. 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 { 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";
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
- * Remediation's issue vocabulary: the SHARED submission codes plus this draw's
19
- * own domain corroboration codes.
20
- *
21
- * The submission half is imported, never restated — a submission that is
22
- * missing, unparseable, contract-invalid, or a duplicate identity means exactly
23
- * the same thing on both sides of the pipeline, and the two used to spell it
24
- * differently (`result_missing` here, a bare `null` in the audit ingest). The
25
- * git/worktree/test half stays here: audit has no analogue and dragging
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
- * Corroborate an explicit `resolved_no_change` decision against the repository.
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
- * The scope of the falsification is the FULL write-scope corroboration every
1334
- * other acceptance path gets, NOT a narrowing to this item's `allowed_files`.
1335
- * `corroborateHostResult` refuses a landed commit that touched anything outside
1336
- * `allowed_files`; a no-change decision that narrowed the check to files INSIDE
1337
- * `allowed_files` would be the inverse rule — the out-of-scope edit, the more
1338
- * serious of the two, would be the one silently admitted. So EVERY path the
1339
- * tree shows as moved since the workload baseline refuses the claim.
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
- * `excusedPaths` is the only exemption, and it is ground truth rather than the
1342
- * host's word: `run_start_dirty` (already dirty before the run began, so not
1343
- * evidence that this host edited anything — and because it is captured from
1344
- * `stagedAndUntracked`, it excuses pre-existing UNTRACKED strays too) unioned
1345
- * with the accepted edit surface — `applied_edit_surface` plus whatever this
1346
- * same ingest has already corroborated and accepted, so a sibling work item's
1347
- * legitimately landed files do not falsify this item's claim.
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 persistedWorkloadFileIsStale(workloadPath) {
1877
- const read = await readSubmissionDocument(workloadPath);
1878
- return read.kind === "value" && persistedWorkloadIsStale(read.value);
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 Error(cannotPrepareMessage(paths.root, state, error));
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 Error(blocked.length === 0
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 persistedWorkloadFileIsStale(paths.workloadPath));
1320
+ (await persistedWorkloadAllowsRebind(paths.workloadPath, existingRecord));
1940
1321
  if (existingRecord &&
1941
1322
  existingRecord.workload_sha256 !== workloadDigest &&
1942
1323
  !remintStaleDigest) {
1943
- throw new Error("Trusted remediation host workload no longer matches the persisted state binding");
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
- return commitRemediationStateUpdates(validated.ctx, acc, verdicts, issues);
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;