@straiffi/archon 1.7.10 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/client/assets/{ActivityView-IUWr92uj.js → ActivityView-EVy76nes.js} +1 -1
  2. package/dist/client/assets/{GitHubActionsDialog-0YtWvbFk.js → GitHubActionsDialog-D53aA0Fv.js} +1 -1
  3. package/dist/client/assets/VerificationDialog-Dm5DcY8F.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-LLMyOR-6.js → architectureDiagram-3BPJPVTR-CmMRkze6.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-aa1HitBJ.js → blockDiagram-GPEHLZMM-BgLsRgxM.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-BpSt5sdj.js → c4Diagram-AAUBKEIU-C3WJl13c.js} +1 -1
  7. package/dist/client/assets/channel-xqaERdN6.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-D8pf3RNr.js → chunk-2J33WTMH-BfH4_PkQ.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-_yx1N2iT.js → chunk-3OPIFGDE-CkVGPLM4.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-CYURGSbL.js → chunk-5ZQYHXKU-CDTwT753.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-Ci2Mu1zz.js → chunk-727SXJPM-CKEhBrr_.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-CqVgrKze.js → chunk-AQP2D5EJ-D5QPK_k7.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-CT8aPk-o.js → chunk-CSCIHK7Q-BU4Ysr_2.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-yM1i0J-p.js → chunk-KSCS5N6A-B9lHLGg1.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-BKMGr31Z.js → chunk-L5ZTLDWV-BrNsmxtD.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-A7oUDETP.js → chunk-LZXEDZCA-CVgZc1nv.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-Cagc-XAG.js → chunk-ND2GUHAM-DWWdi42a.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-pe8tKLWI.js → chunk-NZK2D7GU-46-aRMvO.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-BahwghWW.js → chunk-O5CBEL6O-BW6wHmzD.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-BfSAFufr.js → chunk-WU5MYG2G-Wfk15Qtf.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-BdwFrsZE.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DxAuXA1L.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-DCma6-hS.js → dagre-BM42HDAG-CZ_BDahz.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-DUeggZrO.js → diagram-2AECGRRQ-CQKXQvjO.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-CdjOKGtl.js → diagram-5GNKFQAL-Dgsh82oA.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-CYjXhgh_.js → diagram-KO2AKTUF-DUwmm3sV.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-DYBCh4cm.js → diagram-LMA3HP47-DLGz0g10.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-D3HPQt3z.js → diagram-OG6HWLK6-CjS6v8Zz.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-mG8Yztg-.js → erDiagram-TEJ5UH35-DHaCFHn9.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-4FvjuoSZ.js → flowDiagram-I6XJVG4X-E4LSVqs6.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-ByCQI8ft.js → ganttDiagram-6RSMTGT7-BWywa_xn.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CzEUA7nB.js → gitGraphDiagram-PVQCEYII-BK4kWKuB.js} +1 -1
  33. package/dist/client/assets/index-CS2arNNP.js +274 -0
  34. package/dist/client/assets/index-Cb_ZVYaw.css +2 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-Q9CqnXO1.js → infoDiagram-5YYISTIA-BX7HzDWX.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CO8BToGf.js → ishikawaDiagram-YF4QCWOH-CEnWCNes.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-DtOyWvDJ.js → journeyDiagram-JHISSGLW-MQPDgPxD.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-DIn9bYpk.js → kanban-definition-UN3LZRKU-PwVLKAnN.js} +1 -1
  39. package/dist/client/assets/{line-H-k0M9_s.js → line-BJMnG1sl.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-CZOUS5zK.js → mermaid-parser.core-Bwv8qN83.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-Ck71Qr7h.js → mermaid.core-DpFOXZmb.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-BRFEcJw_.js → mindmap-definition-RKZ34NQL-Dt-9JT_z.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-CwihXXcJ.js → pieDiagram-4H26LBE5-aD3cg6m3.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-moiRw4Cm.js → quadrantDiagram-W4KKPZXB-DUh4hgmW.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-tshvM_vV.js → requirementDiagram-4Y6WPE33-DOgz-iLT.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-rtKIuBEp.js → sankeyDiagram-5OEKKPKP-BqpDXW6-.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-lkTCo4wE.js → sequenceDiagram-3UESZ5HK-BM0Gvqia.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-CFQhVUSd.js → stateDiagram-AJRCARHV-XZMyDtEy.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-C0Qe8FiL.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-BmlQ7Wiy.js → timeline-definition-PNZ67QCA-gqiDwxVu.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-CZyZepcT.js → vennDiagram-CIIHVFJN-MxeqqqyG.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-C2b52gni.js → wardleyDiagram-YWT4CUSO-pMgGO6cB.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-FqnROUHq.js → xychartDiagram-2RQKCTM6-_VK_NZDn.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +452 -7
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +309 -195
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/activeAutomationChild.js +6 -1
  60. package/dist/server/lib/activeAutomationChild.js.map +1 -1
  61. package/dist/server/lib/automationCheckGate.js +183 -50
  62. package/dist/server/lib/automationCheckGate.js.map +1 -1
  63. package/dist/server/lib/automationProgression.js +76 -9
  64. package/dist/server/lib/automationProgression.js.map +1 -1
  65. package/dist/server/lib/automationReconciliation.js +65 -0
  66. package/dist/server/lib/automationReconciliation.js.map +1 -1
  67. package/dist/server/lib/automationResumeDecision.js +69 -32
  68. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  69. package/dist/server/lib/automationReviewGate.js +6 -1
  70. package/dist/server/lib/automationReviewGate.js.map +1 -1
  71. package/dist/server/lib/bundleActivity.js +20 -0
  72. package/dist/server/lib/bundleActivity.js.map +1 -1
  73. package/dist/server/lib/bundleReviewOperations.js +36 -5
  74. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  75. package/dist/server/lib/chatTicketProposals.js +2 -1
  76. package/dist/server/lib/chatTicketProposals.js.map +1 -1
  77. package/dist/server/lib/checkDiagnosticParsers.js +301 -0
  78. package/dist/server/lib/checkDiagnosticParsers.js.map +1 -0
  79. package/dist/server/lib/checkDiagnostics.js +142 -0
  80. package/dist/server/lib/checkDiagnostics.js.map +1 -0
  81. package/dist/server/lib/checkGateEvaluations.js +89 -1
  82. package/dist/server/lib/checkGateEvaluations.js.map +1 -1
  83. package/dist/server/lib/checkRepair.js +1031 -0
  84. package/dist/server/lib/checkRepair.js.map +1 -0
  85. package/dist/server/lib/checkRepairLifecycle.js +56 -0
  86. package/dist/server/lib/checkRepairLifecycle.js.map +1 -0
  87. package/dist/server/lib/checkRepairReconciliation.js +145 -0
  88. package/dist/server/lib/checkRepairReconciliation.js.map +1 -0
  89. package/dist/server/lib/checkRunner.js +115 -21
  90. package/dist/server/lib/checkRunner.js.map +1 -1
  91. package/dist/server/lib/checkRuns.js +37 -3
  92. package/dist/server/lib/checkRuns.js.map +1 -1
  93. package/dist/server/lib/executionReconciliation.js +21 -11
  94. package/dist/server/lib/executionReconciliation.js.map +1 -1
  95. package/dist/server/lib/executionRetryPredicates.js +3 -1
  96. package/dist/server/lib/executionRetryPredicates.js.map +1 -1
  97. package/dist/server/lib/executionRunRetention.js +24 -3
  98. package/dist/server/lib/executionRunRetention.js.map +1 -1
  99. package/dist/server/lib/executionRuns.js +5 -2
  100. package/dist/server/lib/executionRuns.js.map +1 -1
  101. package/dist/server/lib/planModeQuestions.js +2 -0
  102. package/dist/server/lib/planModeQuestions.js.map +1 -1
  103. package/dist/server/lib/planQuestions.js +39 -7
  104. package/dist/server/lib/planQuestions.js.map +1 -1
  105. package/dist/server/lib/projectMemorySuggestions.js +2 -1
  106. package/dist/server/lib/projectMemorySuggestions.js.map +1 -1
  107. package/dist/server/lib/projects.js +1 -26
  108. package/dist/server/lib/projects.js.map +1 -1
  109. package/dist/server/lib/pullRequestContent.js +14 -0
  110. package/dist/server/lib/pullRequestContent.js.map +1 -1
  111. package/dist/server/lib/requiredCheckPolicy.js +13 -49
  112. package/dist/server/lib/requiredCheckPolicy.js.map +1 -1
  113. package/dist/server/lib/requiredCheckPolicyPrompt.js +46 -0
  114. package/dist/server/lib/requiredCheckPolicyPrompt.js.map +1 -0
  115. package/dist/server/lib/specDraft.js +6 -0
  116. package/dist/server/lib/specDraft.js.map +1 -1
  117. package/dist/server/lib/specResources.js +42 -0
  118. package/dist/server/lib/specResources.js.map +1 -0
  119. package/dist/server/lib/specs.js +9 -5
  120. package/dist/server/lib/specs.js.map +1 -1
  121. package/dist/server/lib/ticketAutomation.js +10 -3
  122. package/dist/server/lib/ticketAutomation.js.map +1 -1
  123. package/dist/server/lib/ticketAutomationCommit.js +36 -11
  124. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  125. package/dist/server/lib/ticketFollowUpOperations.js +24 -9
  126. package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
  127. package/dist/server/lib/tickets.js +9 -32
  128. package/dist/server/lib/tickets.js.map +1 -1
  129. package/dist/server/lib/types.js +1 -0
  130. package/dist/server/lib/types.js.map +1 -1
  131. package/dist/server/workers/build.js +34 -12
  132. package/dist/server/workers/build.js.map +1 -1
  133. package/dist/server/workers/chat.js +4 -3
  134. package/dist/server/workers/chat.js.map +1 -1
  135. package/dist/server/workers/followUp.js +60 -17
  136. package/dist/server/workers/followUp.js.map +1 -1
  137. package/dist/server/workers/plan.js +5 -3
  138. package/dist/server/workers/plan.js.map +1 -1
  139. package/dist/server/workers/planFollowUp.js +5 -6
  140. package/dist/server/workers/planFollowUp.js.map +1 -1
  141. package/dist/server/workers/review.js +38 -6
  142. package/dist/server/workers/review.js.map +1 -1
  143. package/dist/server/workers/specSession.js +10 -4
  144. package/dist/server/workers/specSession.js.map +1 -1
  145. package/package.json +1 -1
  146. package/dist/client/assets/VerificationDialog-B4Pyz1LV.js +0 -4
  147. package/dist/client/assets/channel-DWPRC-jg.js +0 -1
  148. package/dist/client/assets/classDiagram-4FO5ZUOK-DBZaZLLc.js +0 -1
  149. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B7HsVFoJ.js +0 -1
  150. package/dist/client/assets/index-D-miHTTb.js +0 -273
  151. package/dist/client/assets/index-TAQIlNdJ.css +0 -2
  152. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-rrN4DgWp.js +0 -1
@@ -31,7 +31,7 @@ import { runLightweightPrompt } from './lib/lightweightPrompt.js';
31
31
  import { advanceBuildChain, startBuildChain, stopBuildChain } from './lib/buildChains.js';
32
32
  import { activateQueuedBuildSequenceTicket, compensateBuildSequenceAcceptance, enqueueBundledBuild, getDependencyOrderedBundleTicketIds, getBuildSequenceIdForTicket, hasActiveSequenceForBundle, removeTicketFromBuildSequence, stopBuildSequence, } from './lib/buildSequences.js';
33
33
  import { prepareTicketForBuild } from './lib/buildFlow.js';
34
- import { getBundlePlanMutationBlockerById, shouldBlockPlanForBundleMutationById, } from './lib/bundleActivity.js';
34
+ import { getActiveBundleRepairBlocker, getBundlePlanMutationBlockerById, shouldBlockPlanForBundleMutationById, } from './lib/bundleActivity.js';
35
35
  import { getDiscoveredModels, normalizeModelId, normalizeTicketModelId } from './lib/models.js';
36
36
  import { upgradeTool, UpgradeInProgressError } from './lib/toolUpgrade.js';
37
37
  import { acceptProjectMemorySuggestion, applyProjectMemorySuggestionToDecision, applyProjectMemorySuggestionToConvention, archiveProjectConvention, archiveProjectDecision, createProjectConvention, createProjectDecision, dismissProjectMemorySuggestion, getProjectMemoryById, getProjectMemorySuggestionById, isProjectMemoryEnabled, listProjectConventions, listLatestSuccessfulProjectContextArtifacts, listProjectDecisions, listProjectMemories, listProjectMemorySuggestions, restoreProjectConvention, restoreProjectDecision, serializeProjectMemoryFromConventionValue, serializeProjectMemoryFromDecisionValue, serializeProjectMemoryFromSuggestionValue, supersedeProjectDecision, supersedeProjectDecisionFromSuggestion, updateProjectConvention, updateProjectDecision, validateProjectConventionPayload, validateProjectDecisionPayload, } from './lib/projectMemory.js';
@@ -47,7 +47,8 @@ import { exportProjectMemoryArchive, importProjectMemoryArchive } from './lib/pr
47
47
  import { buildReviewFindingsResponse, computeDiffSignature, getLatestReviewRunForContext, listReviewFindings } from './lib/reviewFindings.js';
48
48
  import { formatBuildOpenQuestionAnswersPrompt, getLatestBuildOpenQuestionBatchStatus, hasActiveBuildOpenQuestionBatch, listBuildOpenQuestionsForBundle, loadBuildOpenQuestionsByBundleIds, } from './lib/buildOpenQuestions.js';
49
49
  import { acceptBuildOpenQuestionResolution } from './lib/buildOpenQuestionOperations.js';
50
- import { completeAutomationProgression, createAutomationReviewFix, ensureAutomationProgression, finishAutomationReviewFix, getAutomationProgression, recoverLegacyAutomationProgression, recoverStoppedSideEffectProgression, runAutomationProgression, stopAutomationProgression, waitAutomationProgression } from './lib/automationProgression.js';
50
+ import { areBundleTicketsReadyForAutomaticReview } from './lib/bundleAutomationReadiness.js';
51
+ import { abandonAutomationPause, completeAutomationProgression, createAutomationReviewFix, ensureAutomationProgression, finishAutomationReviewFix, getAutomationProgression, recoverLegacyAutomationProgression, recoverStoppedSideEffectProgression, runAutomationProgression, stopAutomationProgression, waitAutomationProgression } from './lib/automationProgression.js';
51
52
  import { reconcileAutomationStage } from './lib/automationReconciliation.js';
52
53
  import { runAutomationReviewGateOrPause } from './lib/automationReviewGate.js';
53
54
  import { pausedRunBelongsToProgression, resolveAutomationResumeDecision } from './lib/automationResumeDecision.js';
@@ -82,11 +83,13 @@ import { countInboundProjectLinks, getEffectiveProject, getProjectCheckDefinitio
82
83
  import { getActiveRunStatus, getActiveRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runSetupCommandsInWorkspace, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
83
84
  import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession, doesTestFileExistForSession, getTestSession, isTestSelectionSupported, startTestSessionRun, stopTestSessionRun } from './lib/testSessions.js';
84
85
  import { closeCheckRunnerAdmissions, getCheckBatchAttempts, openCheckRunnerAdmissions, rerunCheckBatchAndSchedule, shutdownCheckRunner, startCheckBatch, stopCheckBatch } from './lib/checkRunner.js';
86
+ import { CheckRepairError, getCheckRepairCandidateState, getCheckRepairDetails, getLatestCheckRepairForBatch, retryCheckRepair, startCheckRepair, stopCheckRepair } from './lib/checkRepair.js';
85
87
  import { findActiveCheckBatchForTarget, getCheckAttemptDetails, getCheckBatchDetails, hasActiveProjectCheckBatch, isCheckAttemptCurrent, resolveCheckBatchRerunEligibility } from './lib/checkRuns.js';
86
- import { getCheckDefinitionFingerprint, validateRequiredCheckPolicySnapshotSize } from './lib/checkDefinitions.js';
87
- import { getCheckGateEvaluation } from './lib/checkGateEvaluations.js';
88
+ import { getCheckAttemptDiagnosticResponse, getCheckBatchDiagnosticResponse } from './lib/checkDiagnostics.js';
89
+ import { getCheckDefinitionFingerprint } from './lib/checkDefinitions.js';
90
+ import { getCheckGateEvaluation, hydrateCheckGatePresentApplicability } from './lib/checkGateEvaluations.js';
88
91
  import { captureCheckWorkspaceIdentity } from './lib/checkWorkspaceIdentity.js';
89
- import { canonicalizeBundleRequiredCheckPolicyOverride, parseBundleRequiredCheckPolicyOverride, resolveEffectiveRequiredCheckPolicyMembership, } from './lib/requiredCheckPolicy.js';
92
+ import { resolveEffectiveRequiredCheckPolicyMembership, resolveRequiredCheckPolicy } from './lib/requiredCheckPolicy.js';
90
93
  import { getBundleBuildBlockerResponse, shouldStartBundleBuildNow, transitionTicketState, } from './lib/ticketWorkflowOperations.js';
91
94
  import { submitPlanTicketFollowUp, submitTicketFollowUp, } from './lib/ticketFollowUpOperations.js';
92
95
  import { closePendingTicketPlanQuestionSet, listPendingTicketPlanQuestionSetMessageIds, resolveTicketPlanQuestionSet, restoreClosedTicketPlanQuestionSets, } from './lib/planQuestionOperations.js';
@@ -103,9 +106,9 @@ import { HttpError } from './lib/httpError.js';
103
106
  import { parseIncomingExternalSource } from './lib/externalSource.js';
104
107
  import { materializeTicketsWithDependencies } from './lib/ticketBatch.js';
105
108
  import { IMPLEMENTATION_READY_DESCRIPTION_SECTION_HEADINGS } from './lib/ticketDescriptionContract.js';
106
- import { buildSpecImplementationDraft, buildVisualizationAppendix } from './lib/specDraft.js';
109
+ import { buildImplementationResourcesAppendix, buildSpecImplementationDraft, buildVisualizationAppendix } from './lib/specDraft.js';
107
110
  import { exportSpecToDocs, checkSpecExportCollision } from './lib/specExport.js';
108
- import { createTicketDependency, deleteTicketDependenciesForTicket, deleteTicketDependency, getTicketDependency, isTicketRunSetupReady, listTicketDependencies, validateCreateTicketDependency, } from './lib/ticketDependencies.js';
111
+ import { createTicketDependency, deleteTicketDependenciesForTicket, deleteTicketDependency, getTicketDependency, isTicketBuildComplete, isTicketRunSetupReady, listTicketDependencies, validateCreateTicketDependency, } from './lib/ticketDependencies.js';
109
112
  import { startBuild } from './workers/build.js';
110
113
  import { expandPlanningTicketOutline, generatePlanningTicketOutline, generatePlanText, prepareLinkedPlanningResearch, startPlan, stopAllPlanAgents, } from './workers/plan.js';
111
114
  import { EXECUTION_RUN_KINDS, MAX_ARTIFACTS_PER_RUN, validateWorkflowContext } from './lib/types.js';
@@ -380,11 +383,19 @@ const buildExecutionRunDetailResponse = async (project, run) => {
380
383
  retry_eligibility: retryEligibility,
381
384
  action_kind: null,
382
385
  }
383
- : run.kind === 'github_actions'
384
- ? { ...serializeGitHubActionsExecutionDetail(getGitHubActionsExecutionDetails(project.id, run.id)), retry_eligibility: retryEligibility }
385
- : ['automation_commit', 'automation_push', 'automation_pull_request'].includes(run.kind)
386
- ? { kind: run.kind, available: true, automation_stage: getAutomationStageDetails(project.id, run.id), retry_eligibility: retryEligibility }
387
- : { kind: run.kind, available: true, retry_eligibility: retryEligibility };
386
+ : run.kind === 'check_repair'
387
+ ? {
388
+ kind: 'check_repair',
389
+ available: true,
390
+ check_repair: getCheckRepairDetails(project.id, run.id),
391
+ retry_eligibility: retryEligibility,
392
+ action_kind: null,
393
+ }
394
+ : run.kind === 'github_actions'
395
+ ? { ...serializeGitHubActionsExecutionDetail(getGitHubActionsExecutionDetails(project.id, run.id)), retry_eligibility: retryEligibility }
396
+ : ['automation_commit', 'automation_push', 'automation_pull_request'].includes(run.kind)
397
+ ? { kind: run.kind, available: true, automation_stage: getAutomationStageDetails(project.id, run.id), retry_eligibility: retryEligibility }
398
+ : { kind: run.kind, available: true, retry_eligibility: retryEligibility };
388
399
  const artifacts = loadExecutionArtifactSummariesForRuns(project.id, [run.id], MAX_ARTIFACTS_PER_RUN).get(run.id)
389
400
  ?? { artifacts: [], artifact_count: 0 };
390
401
  return {
@@ -444,7 +455,14 @@ const getParkTicketError = (ticket) => {
444
455
  const getBundlePlanBlockerError = () => {
445
456
  return 'AI planning is blocked because this bundle worktree is currently being modified.';
446
457
  };
458
+ const hasActiveBundleCheckWork = (projectId, bundleId) => Boolean(getActiveBundleRepairBlocker(bundleId)
459
+ || findActiveCheckBatchForTarget(projectId, 'bundle', bundleId));
447
460
  const deleteTicketRecord = async (ticket) => {
461
+ if (ticket.project_id
462
+ && ticket.worktree_bundle_id
463
+ && hasActiveBundleCheckWork(ticket.project_id, ticket.worktree_bundle_id)) {
464
+ return { ok: false, status: 409, error: 'Cannot delete a ticket while its bundle has active check work' };
465
+ }
448
466
  if (ticket.agent_status === 'running'
449
467
  || (ticket.project_id && hasActiveBuildExecutionRunForTicket(ticket.project_id, ticket.id))) {
450
468
  return { ok: false, status: 409, error: 'Cannot delete a ticket while the agent is running' };
@@ -812,6 +830,9 @@ const cleanupBundleIfEmpty = (bundleId, project) => {
812
830
  if (isProjectRootBundle(bundle)) {
813
831
  return;
814
832
  }
833
+ if (hasActiveBundleCheckWork(project.id, bundle.id)) {
834
+ throw new HttpError(409, 'Cannot delete a bundle while check work is active');
835
+ }
815
836
  try {
816
837
  deleteWorktree(bundle.branch, project);
817
838
  }
@@ -1464,10 +1485,7 @@ const serializeBundleRow = (projectId, bundle, openQuestions = bundle ? loadBuil
1464
1485
  const hasPreparedWorktree = bundle.kind === 'project_root'
1465
1486
  ? true
1466
1487
  : Boolean(project && resolveExistingWorktreePath(bundle.branch, project));
1467
- const parsedOverride = bundle.kind === 'project_root'
1468
- ? { value: null }
1469
- : parseBundleRequiredCheckPolicyOverride(bundle.required_check_policy_override);
1470
- const effectivePolicy = resolveEffectiveRequiredCheckPolicyMembership(project, bundle);
1488
+ const effectivePolicy = resolveEffectiveRequiredCheckPolicyMembership(project);
1471
1489
  return {
1472
1490
  ...bundle,
1473
1491
  has_prepared_worktree: hasPreparedWorktree,
@@ -1475,7 +1493,6 @@ const serializeBundleRow = (projectId, bundle, openQuestions = bundle ? loadBuil
1475
1493
  ? serializeBundlePullRequest(getBundlePullRequestRecord(bundle.id, projectId))
1476
1494
  : null,
1477
1495
  automation_config: resolveAutomationConfig(bundle, project),
1478
- required_check_policy_override: 'error' in parsedOverride ? null : parsedOverride.value,
1479
1496
  required_check_policy: 'error' in effectivePolicy
1480
1497
  ? undefined
1481
1498
  : {
@@ -1483,7 +1500,6 @@ const serializeBundleRow = (projectId, bundle, openQuestions = bundle ? loadBuil
1483
1500
  required_check_definition_ids: effectivePolicy.required_check_definition_ids,
1484
1501
  pre_review_check_definition_ids: effectivePolicy.pre_review_check_definition_ids,
1485
1502
  },
1486
- required_check_policy_error: 'error' in parsedOverride ? parsedOverride.error : ('error' in effectivePolicy ? effectivePolicy.error : null),
1487
1503
  open_questions: openQuestions,
1488
1504
  build_open_question_batch_status: getLatestBuildOpenQuestionBatchStatus(projectId, bundle.id),
1489
1505
  };
@@ -3049,11 +3065,14 @@ app.post('/projects/:id/spec-sessions/:sessionId/snapshots/:snapshotId/create-ti
3049
3065
  ? createAutoBundleForName(bundleName, project.id)
3050
3066
  : createAutoPlanningBundle(project.id);
3051
3067
  const ticketsForMaterialization = snapshot.tickets.map(ticket => {
3052
- const appendix = buildVisualizationAppendix(ticket.relevant_visualizations ?? [], snapshot);
3068
+ const appendices = [
3069
+ buildVisualizationAppendix(ticket.relevant_visualizations ?? [], snapshot),
3070
+ buildImplementationResourcesAppendix(snapshot.resource_urls),
3071
+ ].filter(Boolean);
3053
3072
  return {
3054
3073
  key: ticket.key,
3055
3074
  title: ticket.title,
3056
- description: appendix ? `${ticket.description}\n\n${appendix}` : ticket.description,
3075
+ description: appendices.length > 0 ? `${ticket.description}\n\n${appendices.join('\n\n')}` : ticket.description,
3057
3076
  };
3058
3077
  });
3059
3078
  const { createdTickets, createdDependencies } = materializeTicketsWithDependencies({
@@ -3719,15 +3738,15 @@ app.delete('/bundles/:id', (req, res) => {
3719
3738
  if (isProjectRootBundle(bundle)) {
3720
3739
  return res.status(409).json({ error: 'Project root cannot be deleted' });
3721
3740
  }
3741
+ if (hasActiveBundleCheckWork(result.project.id, bundle.id)) {
3742
+ return res.status(409).json({ error: 'Cannot delete a bundle while check work is active' });
3743
+ }
3722
3744
  if (countBundleTickets(bundle.id, result.project.id) > 0) {
3723
3745
  return res.status(409).json({ error: 'Cannot delete a bundle that is still used by tickets' });
3724
3746
  }
3725
3747
  if (result.project.active_target_kind === 'bundle' && result.project.active_target_bundle_id === bundle.id) {
3726
3748
  return res.status(409).json({ error: 'Cannot delete the currently selected target bundle' });
3727
3749
  }
3728
- if (findActiveCheckBatchForTarget(result.project.id, 'bundle', bundle.id)) {
3729
- return res.status(409).json({ error: 'Cannot delete a bundle while checks are running' });
3730
- }
3731
3750
  const affectedChatSessionIds = listChatSessions(result.project.id)
3732
3751
  .filter(chatSession => chatSession.target_bundle_id === bundle.id)
3733
3752
  .map(chatSession => chatSession.id);
@@ -3758,14 +3777,14 @@ app.patch('/bundles/:id', (req, res) => {
3758
3777
  return res.status(404).json({ error: 'Bundle not found' });
3759
3778
  }
3760
3779
  const hasNameUpdate = hasOwn(req.body, 'name');
3761
- const hasRequiredCheckPolicyOverrideUpdate = hasOwn(req.body, 'required_check_policy_override');
3780
+ if (hasOwn(req.body, 'required_check_policy_override')) {
3781
+ return res.status(400).json({ error: 'Bundle required-check policy overrides are no longer supported; configure required checks on the project' });
3782
+ }
3762
3783
  if (isProjectRootBundle(bundle)) {
3763
3784
  return res.status(409).json({
3764
3785
  error: hasNameUpdate
3765
3786
  ? 'Project root bundles cannot be renamed'
3766
- : hasRequiredCheckPolicyOverrideUpdate
3767
- ? 'Project root bundles cannot override required-check policy'
3768
- : 'Project root bundle automation cannot be overridden',
3787
+ : 'Project root bundle automation cannot be overridden',
3769
3788
  });
3770
3789
  }
3771
3790
  let nextName = bundle.name;
@@ -3838,39 +3857,6 @@ app.patch('/bundles/:id', (req, res) => {
3838
3857
  return res.status(400).json({ error: 'automation_pause_on_open_questions must be a boolean' });
3839
3858
  }
3840
3859
  }
3841
- let requiredCheckPolicyOverride;
3842
- let requiredCheckPolicyOverrideValue;
3843
- if (hasRequiredCheckPolicyOverrideUpdate) {
3844
- const value = req.body.required_check_policy_override;
3845
- if (value == null) {
3846
- requiredCheckPolicyOverride = null;
3847
- requiredCheckPolicyOverrideValue = null;
3848
- }
3849
- else {
3850
- const result = canonicalizeBundleRequiredCheckPolicyOverride(value);
3851
- if ('error' in result) {
3852
- return res.status(400).json({ error: result.error });
3853
- }
3854
- requiredCheckPolicyOverrideValue = result.value;
3855
- requiredCheckPolicyOverride = result.value ? JSON.stringify(result.value) : null;
3856
- }
3857
- }
3858
- if (requiredCheckPolicyOverrideValue) {
3859
- const definitions = getProjectCheckDefinitions(result.project);
3860
- const definitionIds = new Set(definitions.map(definition => definition.id));
3861
- const missingId = [
3862
- ...requiredCheckPolicyOverrideValue.required_check_definition_ids,
3863
- ...requiredCheckPolicyOverrideValue.pre_review_check_definition_ids,
3864
- ].find(id => !definitionIds.has(id));
3865
- if (missingId) {
3866
- return res.status(400).json({ error: `required_check_policy_override references missing check definition ${missingId}` });
3867
- }
3868
- const requiredDefinitions = definitions.filter(definition => requiredCheckPolicyOverrideValue.required_check_definition_ids.includes(definition.id));
3869
- const policySnapshotSizeError = validateRequiredCheckPolicySnapshotSize(requiredCheckPolicyOverrideValue.required_check_definition_ids, requiredDefinitions);
3870
- if (policySnapshotSizeError) {
3871
- return res.status(400).json({ error: policySnapshotSizeError });
3872
- }
3873
- }
3874
3860
  let updated = null;
3875
3861
  try {
3876
3862
  updated = db.transaction(() => {
@@ -3888,7 +3874,6 @@ app.patch('/bundles/:id', (req, res) => {
3888
3874
  automationDepth,
3889
3875
  automationReviewFixEnabled,
3890
3876
  automationPauseOnOpenQuestions,
3891
- requiredCheckPolicyOverride,
3892
3877
  });
3893
3878
  })();
3894
3879
  }
@@ -3928,7 +3913,7 @@ app.post('/bundles/:id/review', (req, res) => {
3928
3913
  }
3929
3914
  return res.json(reviewResult.details);
3930
3915
  });
3931
- app.post('/bundles/:id/build-open-questions/resolve', (req, res) => {
3916
+ app.post('/bundles/:id/build-open-questions/resolve', async (req, res) => {
3932
3917
  const result = getRequiredProject(req);
3933
3918
  if ('error' in result) {
3934
3919
  return res.status(400).json({ error: result.error });
@@ -3971,54 +3956,70 @@ app.post('/bundles/:id/build-open-questions/resolve', (req, res) => {
3971
3956
  }
3972
3957
  if (accepted.kind === 'dismissed') {
3973
3958
  const automationConfig = resolveAutomationConfig(bundle, result.project);
3974
- const reviewResult = depthAtLeast(automationConfig.depth, 'auto_review')
3975
- ? transitionBundleToReview(result.project.id, bundle.id, io, {
3976
- automatic: true,
3977
- initiation: {
3978
- causalParentRunId: accepted.progressionRunId,
3979
- workflowContext: validateWorkflowContext({ build_open_question_batch_id: accepted.batchId }),
3980
- },
3981
- })
3982
- : null;
3983
- if (!reviewResult) {
3959
+ if (!depthAtLeast(automationConfig.depth, 'auto_review')) {
3984
3960
  completeAutomationProgression({
3985
3961
  projectId: result.project.id,
3986
3962
  bundleId: bundle.id,
3987
3963
  progressionRunId: accepted.progressionRunId,
3988
3964
  broadcaster: io,
3989
3965
  });
3966
+ for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
3967
+ io.emit('ticket:updated', ticket);
3968
+ }
3969
+ return res.json(serializeBundleDetails(result.project, bundle));
3990
3970
  }
3991
- for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
3992
- io.emit('ticket:updated', ticket);
3993
- }
3994
- if (reviewResult && !reviewResult.ok) {
3995
- if (reviewResult.code === 'bundle_build_incomplete') {
3996
- completeAutomationProgression({
3997
- projectId: result.project.id,
3998
- bundleId: bundle.id,
3999
- progressionRunId: accepted.progressionRunId,
4000
- broadcaster: io,
4001
- });
4002
- for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
4003
- io.emit('ticket:updated', ticket);
4004
- }
4005
- return res.json(serializeBundleDetails(result.project, bundle));
3971
+ const ticketsBeforeReview = listDetailedBundleTickets(result.project.id, bundle.id);
3972
+ if (!areBundleTicketsReadyForAutomaticReview(ticketsBeforeReview, bundle.id)) {
3973
+ completeAutomationProgression({
3974
+ projectId: result.project.id,
3975
+ bundleId: bundle.id,
3976
+ progressionRunId: accepted.progressionRunId,
3977
+ broadcaster: io,
3978
+ });
3979
+ for (const ticket of ticketsBeforeReview) {
3980
+ io.emit('ticket:updated', ticket);
4006
3981
  }
3982
+ return res.json(serializeBundleDetails(result.project, bundle));
3983
+ }
3984
+ let reviewStartFailureStatus = null;
3985
+ let reviewStartFailureError = null;
3986
+ const reviewStarted = await runAutomationReviewGateOrPause({
3987
+ projectId: result.project.id,
3988
+ bundleId: bundle.id,
3989
+ progressionRunId: accepted.progressionRunId,
3990
+ broadcaster: io,
3991
+ pausedStageRunId: null,
3992
+ initiation: {
3993
+ workflowContext: validateWorkflowContext({ build_open_question_batch_id: accepted.batchId }),
3994
+ },
3995
+ onReviewStartFailure: failure => {
3996
+ reviewStartFailureStatus = failure.status;
3997
+ reviewStartFailureError = failure.error;
3998
+ },
3999
+ });
4000
+ if (reviewStartFailureStatus !== null && reviewStartFailureError !== null) {
4007
4001
  waitAutomationProgression({
4008
4002
  projectId: result.project.id,
4009
4003
  bundleId: bundle.id,
4010
4004
  progressionRunId: accepted.progressionRunId,
4011
4005
  pauseCode: 'review',
4012
- pauseReason: reviewResult.error,
4006
+ pauseReason: reviewStartFailureError,
4013
4007
  reasonCode: 'stage_failed',
4014
4008
  pausedStageRunId: null,
4015
4009
  broadcaster: io,
4016
4010
  });
4017
- return res.status(reviewResult.status).json({ error: reviewResult.error });
4011
+ return res.status(reviewStartFailureStatus).json({ error: reviewStartFailureError });
4012
+ }
4013
+ const updatedTickets = listDetailedBundleTickets(result.project.id, bundle.id);
4014
+ for (const ticket of updatedTickets) {
4015
+ io.emit('ticket:updated', ticket);
4018
4016
  }
4017
+ const reviewOwner = reviewStarted
4018
+ ? updatedTickets.find(ticket => ticket.state === 'review' && ticket.parked_at === null)
4019
+ : null;
4019
4020
  return res.json(serializeBundleDetails(result.project, bundle, {
4020
- review_owner_ticket_id: reviewResult?.ok ? reviewResult.value?.id : undefined,
4021
- target_ticket_id: reviewResult?.ok ? reviewResult.value?.id : undefined,
4021
+ review_owner_ticket_id: reviewOwner?.id,
4022
+ target_ticket_id: reviewOwner?.id,
4022
4023
  }));
4023
4024
  }
4024
4025
  for (const ticket of listDetailedBundleTickets(result.project.id, bundle.id)) {
@@ -4125,7 +4126,7 @@ app.post('/bundles/:id/follow-up', (req, res) => {
4125
4126
  return res.status(response.status).json(response.value);
4126
4127
  });
4127
4128
  const AUTOMATION_SIDE_EFFECT_KINDS = new Set(['automation_commit', 'automation_push', 'automation_pull_request']);
4128
- const AUTOMATION_WORKER_PAUSE_KINDS = new Set(['build', 'review', 'build_follow_up', 'automation_review_fix', 'check_batch']);
4129
+ const AUTOMATION_WORKER_PAUSE_KINDS = new Set(['build', 'review', 'build_follow_up', 'automation_review_fix', 'check_repair', 'check_batch']);
4129
4130
  app.post('/bundles/:id/resume-automation', async (req, res) => {
4130
4131
  const result = getRequiredProject(req);
4131
4132
  if ('error' in result) {
@@ -4208,7 +4209,11 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4208
4209
  });
4209
4210
  if (checkpointLineage) {
4210
4211
  const owner = getTicket(decision.owner_ticket_id);
4211
- if (!owner || owner.id !== checkpointLineage.ticket.id || owner.agent_status !== 'done') {
4212
+ if (!owner
4213
+ || owner.id !== checkpointLineage.ticket.id
4214
+ || owner.state !== 'build'
4215
+ || owner.parked_at !== null
4216
+ || !isTicketBuildComplete(owner)) {
4212
4217
  return res.status(409).json({ error: 'Checkpoint progression owner is no longer the active Build ticket' });
4213
4218
  }
4214
4219
  let retryOfRunId;
@@ -4463,6 +4468,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4463
4468
  pauseReason: bundleState.automation_paused_reason ?? 'Automation paused',
4464
4469
  reasonCode: bundleState.automation_pause_reason_code ?? 'manual_transition',
4465
4470
  pausedStageRunId: bundleState.automation_paused_stage_run_id,
4471
+ pausedBoundaryPurpose: bundleState.automation_paused_boundary_purpose,
4466
4472
  broadcaster: io,
4467
4473
  });
4468
4474
  };
@@ -4500,7 +4506,62 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4500
4506
  }
4501
4507
  return res.json(serializeBundleDetails(result.project, bundle));
4502
4508
  }
4503
- if (owner.state === 'review' && owner.agent_status !== 'running') {
4509
+ if (decision.outcome === 'complete_manual_boundary') {
4510
+ if (!revalidateDecision()) {
4511
+ return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
4512
+ }
4513
+ completeAutomationProgression({
4514
+ projectId: result.project.id,
4515
+ bundleId: bundle.id,
4516
+ progressionRunId,
4517
+ domainStatus: 'manual_boundary_reached',
4518
+ broadcaster: io,
4519
+ });
4520
+ return res.json(serializeBundleDetails(result.project, bundle));
4521
+ }
4522
+ if ((decision.outcome === 'start_review' || decision.outcome === 'rerun_review_checks')
4523
+ && owner.parked_at === null
4524
+ && isTicketBuildComplete(owner)
4525
+ && (owner.state === 'build' || (decision.outcome === 'rerun_review_checks' && owner.state === 'review'))) {
4526
+ if (!revalidateDecision()) {
4527
+ return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
4528
+ }
4529
+ const progression = getAutomationProgression(result.project.id, bundle.id);
4530
+ if (!progression || progression.id !== progressionRunId) {
4531
+ return res.status(409).json({ error: 'Automation progression ownership is invalid' });
4532
+ }
4533
+ const workflowContext = validateWorkflowContext(progression.metadata);
4534
+ let reviewStartFailureStatus = null;
4535
+ let reviewStartFailureError = null;
4536
+ try {
4537
+ runAutomationProgression({
4538
+ projectId: result.project.id,
4539
+ bundleId: bundle.id,
4540
+ progressionRunId,
4541
+ broadcaster: io,
4542
+ });
4543
+ await runAutomationReviewGateOrPause({
4544
+ projectId: result.project.id,
4545
+ bundleId: bundle.id,
4546
+ progressionRunId,
4547
+ broadcaster: io,
4548
+ pausedStageRunId: bundleState.automation_paused_stage_run_id,
4549
+ initiation: { workflowContext },
4550
+ onReviewStartFailure: failure => {
4551
+ reviewStartFailureStatus = failure.status;
4552
+ reviewStartFailureError = failure.error;
4553
+ restoreWaitingProgression();
4554
+ },
4555
+ });
4556
+ }
4557
+ catch (error) {
4558
+ return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4559
+ }
4560
+ if (reviewStartFailureStatus !== null && reviewStartFailureError !== null) {
4561
+ return res.status(reviewStartFailureStatus).json({ error: reviewStartFailureError });
4562
+ }
4563
+ }
4564
+ else if (owner.state === 'review' && owner.agent_status !== 'running') {
4504
4565
  const iteration = bundleState.review_iteration_count ?? 0;
4505
4566
  if (decision.outcome === 'start_review_fix') {
4506
4567
  let reviewFixRunId = null;
@@ -4610,7 +4671,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4610
4671
  broadcaster: io,
4611
4672
  });
4612
4673
  const checkGateResume = decision.outcome === 'rerun_required_checks'
4613
- ? resolveAutomationCheckGateResume(result.project.id, progressionRunId)
4674
+ ? resolveAutomationCheckGateResume(result.project.id, progressionRunId, owner.bundle?.kind === 'project_root' ? project.repo_path : resolveWorktreePath(bundle.branch, project))
4614
4675
  : undefined;
4615
4676
  await runAutomationCommitChain(owner, project, owner.bundle ?? null, automationConfig, io, progressionRunId, checkGateResume);
4616
4677
  }
@@ -4635,45 +4696,6 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4635
4696
  return res.status(409).json({ error: 'The selected Resume outcome no longer matches the bundle state' });
4636
4697
  }
4637
4698
  }
4638
- else if (owner.state === 'build' && owner.agent_status === 'done' && (decision.outcome === 'start_review' || decision.outcome === 'rerun_review_checks')) {
4639
- if (!revalidateDecision()) {
4640
- return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
4641
- }
4642
- const progression = getAutomationProgression(result.project.id, bundle.id);
4643
- if (!progression || progression.id !== progressionRunId) {
4644
- return res.status(409).json({ error: 'Automation progression ownership is invalid' });
4645
- }
4646
- const workflowContext = validateWorkflowContext(progression.metadata);
4647
- let reviewStartFailureStatus = null;
4648
- let reviewStartFailureError = null;
4649
- try {
4650
- runAutomationProgression({
4651
- projectId: result.project.id,
4652
- bundleId: bundle.id,
4653
- progressionRunId,
4654
- broadcaster: io,
4655
- });
4656
- await runAutomationReviewGateOrPause({
4657
- projectId: result.project.id,
4658
- bundleId: bundle.id,
4659
- progressionRunId,
4660
- broadcaster: io,
4661
- pausedStageRunId: bundleState.automation_paused_stage_run_id,
4662
- initiation: { workflowContext },
4663
- onReviewStartFailure: failure => {
4664
- reviewStartFailureStatus = failure.status;
4665
- reviewStartFailureError = failure.error;
4666
- restoreWaitingProgression();
4667
- },
4668
- });
4669
- }
4670
- catch (error) {
4671
- return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4672
- }
4673
- if (reviewStartFailureStatus !== null && reviewStartFailureError !== null) {
4674
- return res.status(reviewStartFailureStatus).json({ error: reviewStartFailureError });
4675
- }
4676
- }
4677
4699
  else {
4678
4700
  return res.status(409).json({ error: 'Ticket is not in a resumable state' });
4679
4701
  }
@@ -4704,72 +4726,31 @@ app.post('/bundles/:id/dismiss-automation-pause', async (req, res) => {
4704
4726
  && listBuildOpenQuestionsForBundle(result.project.id, bundle.id).length > 0) {
4705
4727
  return res.status(409).json({ error: 'Resolve or dismiss all Build open questions before dismissing this pause' });
4706
4728
  }
4707
- let progressionRunId = bundleState.automation_progression_run_id;
4708
- if (!progressionRunId) {
4729
+ const progressionRunId = bundleState.automation_progression_run_id;
4730
+ const checkpointLineage = progressionRunId
4731
+ ? resolveBuildTicketCommitCheckpointLineage({
4732
+ projectId: result.project.id,
4733
+ bundleId: bundle.id,
4734
+ progressionRunId,
4735
+ })
4736
+ : null;
4737
+ if (progressionRunId) {
4709
4738
  try {
4710
- progressionRunId = (bundleState.automation_paused_stage_run_id
4711
- ? recoverStoppedSideEffectProgression({
4712
- projectId: result.project.id,
4713
- bundleId: bundle.id,
4714
- broadcaster: io,
4715
- })
4716
- : recoverLegacyAutomationProgression({
4717
- projectId: result.project.id,
4718
- bundleId: bundle.id,
4719
- broadcaster: io,
4720
- })).runId;
4721
- }
4722
- catch (error) {
4723
- return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4724
- }
4725
- }
4726
- try {
4727
- if (getAutomationProgression(result.project.id, bundle.id)?.id !== progressionRunId) {
4728
- return res.status(409).json({ error: 'Automation progression ownership is invalid' });
4729
- }
4730
- }
4731
- catch (error) {
4732
- return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4733
- }
4734
- const checkpointLineage = resolveBuildTicketCommitCheckpointLineage({
4735
- projectId: result.project.id,
4736
- bundleId: bundle.id,
4737
- progressionRunId,
4738
- });
4739
- if (bundleState.automation_paused_stage_run_id) {
4740
- const stage = getExecutionRun(result.project.id, bundleState.automation_paused_stage_run_id);
4741
- if (!stage
4742
- || (!AUTOMATION_SIDE_EFFECT_KINDS.has(stage.kind) && !AUTOMATION_WORKER_PAUSE_KINDS.has(stage.kind))
4743
- || !pausedRunBelongsToProgression(result.project.id, bundle.id, progressionRunId, stage.id)) {
4744
- return res.status(409).json({ error: 'The paused automation stage does not belong to the active progression' });
4739
+ await stopAutomationProgression({
4740
+ projectId: result.project.id,
4741
+ bundleId: bundle.id,
4742
+ broadcaster: io,
4743
+ stopLocalWorker: worker => (worker.kind === 'review' ? stopReviewAgent(worker.ticket_id) : stopBuildAgent(worker.ticket_id)),
4744
+ });
4745
4745
  }
4746
- if (AUTOMATION_SIDE_EFFECT_KINDS.has(stage.kind)) {
4747
- let reconciled;
4748
- try {
4749
- reconciled = await reconcileAutomationStage({
4750
- projectId: result.project.id,
4751
- bundleId: bundle.id,
4752
- progressionRunId,
4753
- stageRunId: stage.id,
4754
- });
4755
- }
4756
- catch (error) {
4757
- return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4758
- }
4759
- if (reconciled.domainStatus === 'ambiguous') {
4760
- return res.status(409).json({ error: 'The paused automation side effect is ambiguous and cannot be dismissed safely.' });
4761
- }
4762
- if (!(reconciled.status === 'completed' && reconciled.domainStatus === 'reconciled_completed')
4763
- && !(reconciled.status === 'interrupted' && reconciled.domainStatus === 'reconciled_absent')) {
4764
- return res.status(409).json({ error: 'The paused automation side effect has no safely dismissible outcome.' });
4765
- }
4746
+ catch {
4747
+ // Explicit abandonment also repairs a stale progression pointer.
4766
4748
  }
4767
4749
  }
4768
- await stopAutomationProgression({
4750
+ abandonAutomationPause({
4769
4751
  projectId: result.project.id,
4770
4752
  bundleId: bundle.id,
4771
4753
  broadcaster: io,
4772
- stopLocalWorker: worker => (worker.kind === 'review' ? stopReviewAgent(worker.ticket_id) : stopBuildAgent(worker.ticket_id)),
4773
4754
  });
4774
4755
  if (checkpointLineage) {
4775
4756
  const stoppedSequence = stopBuildSequence(checkpointLineage.ticket.id);
@@ -7187,6 +7168,12 @@ const completeClaimedProjectDeletion = async (projectId, claimId) => {
7187
7168
  WHERE evaluation_id IN (SELECT id FROM check_gate_evaluations WHERE project_id = ?)
7188
7169
  `).run(projectId);
7189
7170
  db.prepare('DELETE FROM check_gate_evaluations WHERE project_id = ?').run(projectId);
7171
+ if (db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'check_repair_details'").get()) {
7172
+ db.prepare(`
7173
+ DELETE FROM check_repair_details
7174
+ WHERE execution_run_id IN (SELECT id FROM execution_runs WHERE project_id = ?)
7175
+ `).run(projectId);
7176
+ }
7190
7177
  db.prepare(`
7191
7178
  DELETE FROM check_attempt_details
7192
7179
  WHERE execution_run_id IN (SELECT id FROM execution_runs WHERE project_id = ?)
@@ -8131,6 +8118,98 @@ app.post('/projects/:id/check-batches', async (req, res) => {
8131
8118
  return res.status(409).json({ error: getErrorMessage(error) });
8132
8119
  }
8133
8120
  });
8121
+ app.post('/projects/:id/check-repairs', async (req, res) => {
8122
+ const project = getProjectById(req.params.id);
8123
+ if (!project) {
8124
+ return res.status(404).json({ error: 'Not found' });
8125
+ }
8126
+ const body = req.body && typeof req.body === 'object' && !Array.isArray(req.body)
8127
+ ? req.body
8128
+ : null;
8129
+ if (!body || Object.keys(body).some(key => !['diagnostic_ids', 'raw_output_attempt_run_ids', 'ticket_id'].includes(key))) {
8130
+ return res.status(400).json({ error: 'Only diagnostic_ids, raw_output_attempt_run_ids, and optional ticket_id are accepted' });
8131
+ }
8132
+ const diagnosticIds = body.diagnostic_ids === undefined
8133
+ ? []
8134
+ : Array.isArray(body.diagnostic_ids)
8135
+ && body.diagnostic_ids.length <= 32
8136
+ && body.diagnostic_ids.every(id => typeof id === 'string' && id.trim().length > 0 && id.trim().length <= 100)
8137
+ ? body.diagnostic_ids.map(id => id.trim())
8138
+ : null;
8139
+ const rawOutputAttemptRunIds = body.raw_output_attempt_run_ids === undefined
8140
+ ? []
8141
+ : Array.isArray(body.raw_output_attempt_run_ids)
8142
+ && body.raw_output_attempt_run_ids.length <= 8
8143
+ && body.raw_output_attempt_run_ids.every(id => typeof id === 'string' && id.trim().length > 0 && id.trim().length <= 100)
8144
+ ? body.raw_output_attempt_run_ids.map(id => id.trim())
8145
+ : null;
8146
+ if (!diagnosticIds
8147
+ || diagnosticIds.length !== new Set(diagnosticIds).size
8148
+ || !rawOutputAttemptRunIds
8149
+ || rawOutputAttemptRunIds.length !== new Set(rawOutputAttemptRunIds).size
8150
+ || diagnosticIds.length + rawOutputAttemptRunIds.length === 0) {
8151
+ return res.status(400).json({ error: 'At least one unique diagnostic_ids or raw_output_attempt_run_ids evidence reference is required' });
8152
+ }
8153
+ if (body.ticket_id !== undefined && (typeof body.ticket_id !== 'string'
8154
+ || body.ticket_id.trim().length === 0
8155
+ || body.ticket_id.trim().length > 100)) {
8156
+ return res.status(400).json({ error: 'ticket_id must be a non-empty opaque ticket ID' });
8157
+ }
8158
+ try {
8159
+ return res.status(202).json(await startCheckRepair({
8160
+ projectId: project.id,
8161
+ diagnosticIds,
8162
+ rawOutputAttemptRunIds,
8163
+ ticketId: typeof body.ticket_id === 'string' ? body.ticket_id.trim() : null,
8164
+ io,
8165
+ }));
8166
+ }
8167
+ catch (error) {
8168
+ if (error instanceof CheckRepairError) {
8169
+ return res.status(error.status).json({ error: error.message });
8170
+ }
8171
+ return res.status(409).json({ error: getErrorMessage(error) });
8172
+ }
8173
+ });
8174
+ app.get('/projects/:id/check-repairs/:repairRunId', (req, res) => {
8175
+ const project = getProjectById(req.params.id);
8176
+ if (!project) {
8177
+ return res.status(404).json({ error: 'Not found' });
8178
+ }
8179
+ const repair = getCheckRepairDetails(project.id, req.params.repairRunId);
8180
+ return repair ? res.json(repair) : res.status(404).json({ error: 'Check repair not found' });
8181
+ });
8182
+ app.post('/projects/:id/check-repairs/:repairRunId/stop', async (req, res) => {
8183
+ if (!getProjectById(req.params.id)) {
8184
+ return res.status(404).json({ error: 'Not found' });
8185
+ }
8186
+ try {
8187
+ return res.json(await stopCheckRepair(req.params.id, req.params.repairRunId, io));
8188
+ }
8189
+ catch (error) {
8190
+ if (error instanceof CheckRepairError) {
8191
+ return res.status(error.status).json({ error: error.message });
8192
+ }
8193
+ return res.status(409).json({ error: getErrorMessage(error) });
8194
+ }
8195
+ });
8196
+ app.post('/projects/:id/check-repairs/:repairRunId/retry', async (req, res) => {
8197
+ if (!getProjectById(req.params.id)) {
8198
+ return res.status(404).json({ error: 'Not found' });
8199
+ }
8200
+ if (req.body && typeof req.body === 'object' && Object.keys(req.body).length > 0) {
8201
+ return res.status(400).json({ error: 'Check repair retries do not accept client-supplied repair context' });
8202
+ }
8203
+ try {
8204
+ return res.status(202).json(await retryCheckRepair(req.params.id, req.params.repairRunId, io));
8205
+ }
8206
+ catch (error) {
8207
+ if (error instanceof CheckRepairError) {
8208
+ return res.status(error.status).json({ error: error.message });
8209
+ }
8210
+ return res.status(409).json({ error: getErrorMessage(error) });
8211
+ }
8212
+ });
8134
8213
  const parseCheckDefinitionIds = (value) => {
8135
8214
  if (!Array.isArray(value) || value.length === 0 || value.some(id => typeof id !== 'string' || id.trim() === '')) {
8136
8215
  return null;
@@ -8230,12 +8309,47 @@ app.get('/projects/:id/check-batches/:batchRunId', async (req, res) => {
8230
8309
  attempts: await hydrateCheckAttemptCurrency(project, batch, getCheckBatchAttempts(req.params.id, req.params.batchRunId)),
8231
8310
  });
8232
8311
  });
8233
- app.get('/projects/:id/check-gate-evaluations/:evaluationId', (req, res) => {
8312
+ app.get('/projects/:id/check-batches/:batchRunId/diagnostics', async (req, res) => {
8313
+ const project = getProjectById(req.params.id);
8314
+ if (!project) {
8315
+ return res.status(404).json({ error: 'Not found' });
8316
+ }
8317
+ const diagnostics = getCheckBatchDiagnosticResponse(project.id, req.params.batchRunId);
8318
+ if (!diagnostics) {
8319
+ return res.status(404).json({ error: 'Check batch not found' });
8320
+ }
8321
+ return res.json({
8322
+ ...diagnostics,
8323
+ repair: await getCheckRepairCandidateState(project.id, req.params.batchRunId),
8324
+ latest_repair: getLatestCheckRepairForBatch(project.id, req.params.batchRunId),
8325
+ });
8326
+ });
8327
+ app.get('/projects/:id/check-attempts/:attemptRunId/diagnostics', (req, res) => {
8328
+ const project = getProjectById(req.params.id);
8329
+ if (!project) {
8330
+ return res.status(404).json({ error: 'Not found' });
8331
+ }
8332
+ const diagnostics = getCheckAttemptDiagnosticResponse(project.id, req.params.attemptRunId);
8333
+ if (!diagnostics) {
8334
+ return res.status(404).json({ error: 'Check attempt diagnostics not found' });
8335
+ }
8336
+ return res.json(diagnostics);
8337
+ });
8338
+ app.get('/projects/:id/check-gate-evaluations/:evaluationId', async (req, res) => {
8339
+ const project = getProjectById(req.params.id);
8340
+ if (!project) {
8341
+ return res.status(404).json({ error: 'Not found' });
8342
+ }
8234
8343
  const evaluation = getCheckGateEvaluation(req.params.id, req.params.evaluationId);
8235
8344
  if (!evaluation) {
8236
8345
  return res.status(404).json({ error: 'Check gate evaluation not found' });
8237
8346
  }
8238
- return res.json(evaluation);
8347
+ const workspace = resolveExplicitProjectTestWorkspace(project, evaluation.target.targetKind, evaluation.target.targetBundleId);
8348
+ const currentWorkspaceIdentity = 'error' in workspace
8349
+ ? null
8350
+ : await captureCheckWorkspaceIdentity(workspace.cwd);
8351
+ const currentPolicyResolution = resolveRequiredCheckPolicy(project, { boundaryKind: evaluation.boundaryKind });
8352
+ return res.json(hydrateCheckGatePresentApplicability(evaluation, currentWorkspaceIdentity, currentPolicyResolution));
8239
8353
  });
8240
8354
  app.post('/projects/:id/check-batches/:batchRunId/stop', async (req, res) => {
8241
8355
  if (!getCheckBatchDetails(req.params.id, req.params.batchRunId)) {