@straiffi/archon 1.10.9 → 1.10.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/ActivityView-BFn_Nhj4.js +1 -0
- package/dist/client/assets/GitHubActionsDialog-iZD86Yju.js +1 -0
- package/dist/client/assets/VerificationDialog-CqKE8t1d.js +4 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BAIGV9Mj.js → architectureDiagram-3BPJPVTR-C8KefY67.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-B-5v5jSZ.js → blockDiagram-GPEHLZMM-BO2Q_Osr.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-C6wU415o.js → c4Diagram-AAUBKEIU-DGPkhbrK.js} +1 -1
- package/dist/client/assets/channel-DXKn3TOv.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-COmvX2tP.js → chunk-2J33WTMH-Dtb7Xu8C.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-BlANptdY.js → chunk-3OPIFGDE-Bnf4GbOF.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-ETyA2_GU.js → chunk-5ZQYHXKU-BWGid3g7.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-D5-DudwF.js → chunk-727SXJPM-ScA8IRGP.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-k1AJ4sA3.js → chunk-AQP2D5EJ-Bygg04Ip.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-jfksXwY9.js → chunk-CSCIHK7Q-CxdDDtfN.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-CHj0FDj9.js → chunk-KSCS5N6A-BgpZFsB5.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-1ODGxXhG.js → chunk-L5ZTLDWV-CKSew0OL.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-Bgh9X2W0.js → chunk-LZXEDZCA-DVG9Rx0K.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-DDkb3sp4.js → chunk-ND2GUHAM-CHBqNmuF.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-B6B8PZ_-.js → chunk-NZK2D7GU-DvZYWn7V.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-DhiUIFgj.js → chunk-O5CBEL6O-BL7lRTSe.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-Dzd0fbP3.js → chunk-WU5MYG2G-C-il2wHZ.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-U0fU20hT.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C6bQNSxL.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-DoNDMCXI.js → dagre-BM42HDAG-B00l5tLi.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-ETDXSa5q.js → diagram-2AECGRRQ-Tum6g1_5.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-CgTUep7e.js → diagram-5GNKFQAL-BK1GX5jc.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-BKTx2iRe.js → diagram-KO2AKTUF-BrzmD5NB.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-9iEiScHT.js → diagram-LMA3HP47-BhrlgXiJ.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-UIZRh7Qa.js → diagram-OG6HWLK6-K-fWsJ3a.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-BpNJmJva.js → erDiagram-TEJ5UH35-Der6PjHE.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-C-rK0XZ1.js → flowDiagram-I6XJVG4X-BVWgGhXe.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-DWyfPvv_.js → ganttDiagram-6RSMTGT7-DcoZ5ggj.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-x71bzdc_.js → gitGraphDiagram-PVQCEYII-BhhbMTBu.js} +1 -1
- package/dist/client/assets/index-B6BltHSr.css +2 -0
- package/dist/client/assets/index-CFOYm3j9.js +276 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-CbRus0Xu.js → infoDiagram-5YYISTIA-Lwc-agyc.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DvaMHrgN.js → ishikawaDiagram-YF4QCWOH-B95U1Xcl.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-BQBu4YGn.js → journeyDiagram-JHISSGLW-xFpigy5D.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-8rU-U7L1.js → kanban-definition-UN3LZRKU-D3mudQnR.js} +1 -1
- package/dist/client/assets/{line-CSZnwjLA.js → line-DtTL3ZI2.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-CTOl2slE.js → mermaid-parser.core-Dau7K6zV.js} +1 -1
- package/dist/client/assets/{mermaid.core-B9gr0Vqf.js → mermaid.core-B8qtvATu.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-But1_PWQ.js → mindmap-definition-RKZ34NQL-Cus04Yq3.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-CnlVmfSG.js → pieDiagram-4H26LBE5-BCMGpBnn.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Dww1ZGuR.js → quadrantDiagram-W4KKPZXB-CAmOr-PJ.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-CNf_loe8.js → requirementDiagram-4Y6WPE33-kisD3jc4.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DBhi37Jj.js → sankeyDiagram-5OEKKPKP-CsCedmM0.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B64hb4E4.js → sequenceDiagram-3UESZ5HK-B-dAi954.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-DcS6oB-S.js → stateDiagram-AJRCARHV-X0BnedIW.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-6B8YH8Y2.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-Dq-4tXfh.js → timeline-definition-PNZ67QCA-DfFoSjhr.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-CeDklSE_.js → vennDiagram-CIIHVFJN-CKSCBwhG.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-D2cJISMm.js → wardleyDiagram-YWT4CUSO-DNIBQmHO.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-CBF-tTpF.js → xychartDiagram-2RQKCTM6-FvB55TQN.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +368 -5
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +194 -250
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agentRuns.js +14 -1
- package/dist/server/lib/agentRuns.js.map +1 -1
- package/dist/server/lib/automationLoopProjection.js +55 -12
- package/dist/server/lib/automationLoopProjection.js.map +1 -1
- package/dist/server/lib/automationProgression.js +2 -5
- package/dist/server/lib/automationProgression.js.map +1 -1
- package/dist/server/lib/automationResumeDecision.js +19 -1
- package/dist/server/lib/automationResumeDecision.js.map +1 -1
- package/dist/server/lib/buildControlEligibility.js +4 -0
- package/dist/server/lib/buildControlEligibility.js.map +1 -0
- package/dist/server/lib/buildExecutionAdmission.js +76 -0
- package/dist/server/lib/buildExecutionAdmission.js.map +1 -0
- package/dist/server/lib/buildSequenceResume.js +79 -0
- package/dist/server/lib/buildSequenceResume.js.map +1 -0
- package/dist/server/lib/buildWorkload.js +11 -0
- package/dist/server/lib/buildWorkload.js.map +1 -0
- package/dist/server/lib/checkAutoFixDetails.js +36 -1
- package/dist/server/lib/checkAutoFixDetails.js.map +1 -1
- package/dist/server/lib/executionEnvironmentReconciliation.js +15 -2
- package/dist/server/lib/executionEnvironmentReconciliation.js.map +1 -1
- package/dist/server/lib/executionEnvironments.js +15 -0
- package/dist/server/lib/executionEnvironments.js.map +1 -1
- package/dist/server/lib/executionRetry.js +13 -1
- package/dist/server/lib/executionRetry.js.map +1 -1
- package/dist/server/lib/executionRunAuthority.js +2 -2
- package/dist/server/lib/executionRunAuthority.js.map +1 -1
- package/dist/server/lib/executionTargets.js +7 -4
- package/dist/server/lib/executionTargets.js.map +1 -1
- package/dist/server/lib/executionTimeline.js +8 -13
- package/dist/server/lib/executionTimeline.js.map +1 -1
- package/dist/server/lib/executionTimelineHierarchy.js +83 -0
- package/dist/server/lib/executionTimelineHierarchy.js.map +1 -0
- package/dist/server/lib/failedBuildSequenceRecovery.js +133 -0
- package/dist/server/lib/failedBuildSequenceRecovery.js.map +1 -0
- package/dist/server/lib/hostRunSession.js +62 -0
- package/dist/server/lib/hostRunSession.js.map +1 -0
- package/dist/server/lib/hostServiceRecovery.js +159 -0
- package/dist/server/lib/hostServiceRecovery.js.map +1 -0
- package/dist/server/lib/hostServiceRunProvider.js +32 -0
- package/dist/server/lib/hostServiceRunProvider.js.map +1 -0
- package/dist/server/lib/mobileAccess.js +6 -2
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/projectAutoConfig.js +5 -2
- package/dist/server/lib/projectAutoConfig.js.map +1 -1
- package/dist/server/lib/projectTargets.js +16 -2
- package/dist/server/lib/projectTargets.js.map +1 -1
- package/dist/server/lib/projects.js +25 -1
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/reviewFindingReconciliation.js +9 -2
- package/dist/server/lib/reviewFindingReconciliation.js.map +1 -1
- package/dist/server/lib/reviewFindings.js +82 -21
- package/dist/server/lib/reviewFindings.js.map +1 -1
- package/dist/server/lib/reviewRepairAssessmentRecords.js +41 -0
- package/dist/server/lib/reviewRepairAssessmentRecords.js.map +1 -0
- package/dist/server/lib/reviewRepairCompletion.js +78 -18
- package/dist/server/lib/reviewRepairCompletion.js.map +1 -1
- package/dist/server/lib/reviewRepairIterationRecords.js +4 -2
- package/dist/server/lib/reviewRepairIterationRecords.js.map +1 -1
- package/dist/server/lib/reviewRepairIterations.js +8 -2
- package/dist/server/lib/reviewRepairIterations.js.map +1 -1
- package/dist/server/lib/reviewRepairProgress.js +177 -0
- package/dist/server/lib/reviewRepairProgress.js.map +1 -0
- package/dist/server/lib/reviewRepairReconciliation.js +119 -140
- package/dist/server/lib/reviewRepairReconciliation.js.map +1 -1
- package/dist/server/lib/reviewRepairResumeOverride.js +55 -0
- package/dist/server/lib/reviewRepairResumeOverride.js.map +1 -0
- package/dist/server/lib/run.js +541 -504
- package/dist/server/lib/run.js.map +1 -1
- package/dist/server/lib/runProcessIdentity.js +71 -0
- package/dist/server/lib/runProcessIdentity.js.map +1 -0
- package/dist/server/lib/runProcesses.js +273 -0
- package/dist/server/lib/runProcesses.js.map +1 -0
- package/dist/server/lib/runServices.js +223 -0
- package/dist/server/lib/runServices.js.map +1 -0
- package/dist/server/lib/runSessionLifecycle.js +77 -0
- package/dist/server/lib/runSessionLifecycle.js.map +1 -0
- package/dist/server/lib/runStatus.js +8 -0
- package/dist/server/lib/runStatus.js.map +1 -0
- package/dist/server/lib/serviceRunCleanup.js +70 -0
- package/dist/server/lib/serviceRunCleanup.js.map +1 -0
- package/dist/server/lib/serviceRuns.js +458 -19
- package/dist/server/lib/serviceRuns.js.map +1 -1
- package/dist/server/lib/ticketAutomation.js +4 -27
- package/dist/server/lib/ticketAutomation.js.map +1 -1
- package/dist/server/lib/ticketRunOperations.js +103 -34
- package/dist/server/lib/ticketRunOperations.js.map +1 -1
- package/dist/server/lib/tickets.js +0 -13
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/build.js +44 -69
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/plan.js +0 -7
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/review.js +95 -146
- package/dist/server/workers/review.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/ActivityView-CKvbDXeA.js +0 -1
- package/dist/client/assets/GitHubActionsDialog-Dzo0vtak.js +0 -1
- package/dist/client/assets/VerificationDialog-BJnOitrJ.js +0 -4
- package/dist/client/assets/channel-5mIy7nm1.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BiQ-X2Sg.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CF-wCYW-.js +0 -1
- package/dist/client/assets/index-CIAO5aE_.css +0 -2
- package/dist/client/assets/index-nCGyalrn.js +0 -276
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-PWeeL0tR.js +0 -1
- package/dist/server/lib/automationDeliveryDecision.js +0 -34
- package/dist/server/lib/automationDeliveryDecision.js.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -70,8 +70,8 @@ import { abandonAutomationPause, completeAutomationProgression, completeAutomati
|
|
|
70
70
|
import { reconcileAutomationStage } from './lib/automationReconciliation.js';
|
|
71
71
|
import { runAutomationReviewGateOrPause } from './lib/automationReviewGate.js';
|
|
72
72
|
import { pausedRunBelongsToProgression, resolveAutomationResumeDecision } from './lib/automationResumeDecision.js';
|
|
73
|
-
import {
|
|
74
|
-
import {
|
|
73
|
+
import { isLegacyReviewRepairMigrationPause, recoverCompletedReviewRepairVerification } from './lib/reviewRepairReconciliation.js';
|
|
74
|
+
import { overrideNoProgressReviewRepairInTransaction } from './lib/reviewRepairResumeOverride.js';
|
|
75
75
|
import { getAutomationStageDetails, } from './lib/automationStageDetails.js';
|
|
76
76
|
import { buildAutomationFixInstruction, clearAutomationPause, countBlockingFindings, depthAtLeast, getBundleAutomationState, isAutomationActive, isAutomationDepth, recordAutomationPause, resetBundleAutomationLoop, resolveAutomationConfig, } from './lib/ticketAutomation.js';
|
|
77
77
|
import { resolveAutomationCheckGateResume, runAutomationCommitChain, runAutomationCommitOperation } from './lib/ticketAutomationCommit.js';
|
|
@@ -80,7 +80,7 @@ import { continueAfterBuildCheckpoint } from './lib/automationBuildContinuation.
|
|
|
80
80
|
import { getDiscoveredSkills, normalizeSkillNames } from './lib/skills.js';
|
|
81
81
|
import { clearAllPreviewProxies } from './lib/previewProxy.js';
|
|
82
82
|
import { createExecutionRun, addExecutionRunLink, addExecutionRunLinks, getExecutionRun, getLifecycleEvent, hasActiveBuildExecutionRunForTicket, getGitHubActionsExecutionDetails, getTestExecutionDetails, GitHubActionsDispatchConflictError, listExecutionRunLinks, listExecutionRuns, listLifecycleEvents, listProjectLifecycleEvents, toCanonicalLifecycleEvent, transitionExecutionRun, withExecutionRunTransaction, } from './lib/executionRuns.js';
|
|
83
|
-
import { appendReviewRepairIterationDecisionInTransaction,
|
|
83
|
+
import { appendReviewRepairIterationDecisionInTransaction, attachReviewRepairActionEnvelopeInTransaction, attachReviewRepairActionInTransaction, claimReviewRepairIterationInTransaction, assertReviewRepairIterationDeletionEligible, deleteTerminalReviewRepairIterationsForTicketInTransaction, getActiveReviewRepairIteration, getReviewRepairIteration, pauseReviewRepairIteration, rewindStaleReviewRepairVerification, replaceReviewRepairActionEnvelopeRetryInTransaction, replaceReviewRepairActionRetryInTransaction, replaceReviewRepairVerificationRetryInTransaction, resetReviewRepairGateForRetry, resumePausedReviewRepairIteration, terminalizeReviewRepairIteration, terminalizeReviewRepairIterationInTransaction, } from './lib/reviewRepairIterations.js';
|
|
84
84
|
import { dispatchExecutionRetry, getExecutionRetryEligibility, resolveExecutionRetry } from './lib/executionRetry.js';
|
|
85
85
|
import { listExecutionTimeline } from './lib/executionTimeline.js';
|
|
86
86
|
import { loadReviewAutomationLoopDetails } from './lib/automationLoopProjection.js';
|
|
@@ -105,7 +105,10 @@ import { refreshIfNeeded as refreshAssignedIssues, clearCachedAssignedIssues } f
|
|
|
105
105
|
import { buildCommitMessagePrompt, normalizeGeneratedCommitMessage } from './lib/commitMessage.js';
|
|
106
106
|
import { BUNDLE_PULL_REQUEST_SYNC_TTL_MS, getBundlePullRequestRecord, hasOpenBundlePullRequest, serializeBundlePullRequest, shouldSyncBundlePullRequest, syncTrackedBundlePullRequestIfNeeded, upsertBundlePullRequest, upsertBundlePullRequestFromGitHub, } from './lib/bundlePullRequests.js';
|
|
107
107
|
import { countInboundProjectLinks, getEffectiveProject, getProjectCheckDefinitions, getProjectById, getProjectTestCommands, getProjectTicketStats, hasProjectRunIde, listInterruptedProjectDeletions, listProjects, releaseInterruptedProjectDeletionClaims, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectRequiredCheckPolicyReferences, validateProjectPayload, } from './lib/projects.js';
|
|
108
|
-
import { getActiveRunStatus, getActiveRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
|
|
108
|
+
import { forceReleaseProjectRunCleanup, getActiveRunStatus, getActiveRunStatusForProject, getLastRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
|
|
109
|
+
import { reconcilePendingServiceCleanups } from './lib/serviceRunCleanup.js';
|
|
110
|
+
import { getProjectServiceCleanupStatus, hasActiveProjectServiceRun } from './lib/serviceRuns.js';
|
|
111
|
+
import { projectRunStatus } from './lib/runStatus.js';
|
|
109
112
|
import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession, doesTestFileExistForSession, getTestSession, isTestSelectionSupported, startTestSessionRun, stopTestSessionRun } from './lib/testSessions.js';
|
|
110
113
|
import { closeCheckRunnerAdmissions, finalizeProductionCheckApproval, getCheckBatchAttempts, openCheckRunnerAdmissions, rerunCheckBatchAndSchedule, resumeProductionCheckApproval, shutdownCheckRunner, startCheckBatch, stopCheckBatch } from './lib/checkRunner.js';
|
|
111
114
|
import { CheckRepairError, getCheckRepairCandidateState, getCheckRepairDetails, getLatestCheckRepairForBatch, retryCheckRepair, startCheckRepair, stopCheckRepair } from './lib/checkRepair.js';
|
|
@@ -421,7 +424,7 @@ const getCheckBatchRerunEligibility = (project, batch, selection) => {
|
|
|
421
424
|
workspaceAvailable: !('error' in workspace),
|
|
422
425
|
});
|
|
423
426
|
};
|
|
424
|
-
const buildExecutionRunDetailResponse = async (project, run) => {
|
|
427
|
+
const buildExecutionRunDetailResponse = async (project, run, scope) => {
|
|
425
428
|
const checkBatch = run.kind === 'check_batch' ? getCheckBatchDetails(project.id, run.id) : null;
|
|
426
429
|
const retryEligibility = checkBatch
|
|
427
430
|
? getCheckBatchRerunEligibility(project, checkBatch)
|
|
@@ -463,7 +466,7 @@ const buildExecutionRunDetailResponse = async (project, run) => {
|
|
|
463
466
|
? {
|
|
464
467
|
kind: 'check_auto_fix',
|
|
465
468
|
available: true,
|
|
466
|
-
check_auto_fix: getCheckAutoFixDetails(project.id, run.id),
|
|
469
|
+
check_auto_fix: getCheckAutoFixDetails(project.id, run.id, scope),
|
|
467
470
|
retry_eligibility: retryEligibility,
|
|
468
471
|
action_kind: null,
|
|
469
472
|
}
|
|
@@ -490,7 +493,7 @@ const buildExecutionRunDetailResponse = async (project, run) => {
|
|
|
490
493
|
...artifacts,
|
|
491
494
|
environment_context: getExecutionEnvironmentContextForRun(project.id, run.id),
|
|
492
495
|
automation_loops: run.kind === 'automation_progression'
|
|
493
|
-
? loadReviewAutomationLoopDetails(project.id, run.id)
|
|
496
|
+
? loadReviewAutomationLoopDetails(project.id, run.id, scope)
|
|
494
497
|
: [],
|
|
495
498
|
};
|
|
496
499
|
};
|
|
@@ -576,7 +579,26 @@ const cleanupWorktreeExecutionTarget = async ({ project, targetKind, targetKey,
|
|
|
576
579
|
}
|
|
577
580
|
return { branchWarning: buildBranchCleanupWarning(branch, deleteLocalBranch(branch, project)) };
|
|
578
581
|
};
|
|
582
|
+
const assertTicketServiceTargetMutable = (ticketId) => {
|
|
583
|
+
db.transaction(() => {
|
|
584
|
+
const authority = db.prepare(`SELECT 1 FROM active_project_service_authorities active
|
|
585
|
+
JOIN execution_runs run ON run.id = active.execution_run_id
|
|
586
|
+
WHERE run.source_kind = 'ticket' AND run.source_id = ? LIMIT 1`).get(ticketId);
|
|
587
|
+
if (authority) {
|
|
588
|
+
throw new HttpError(409, 'Ticket has active service authority. Stop the service session and verify cleanup before changing or deleting its target.');
|
|
589
|
+
}
|
|
590
|
+
}).immediate();
|
|
591
|
+
};
|
|
579
592
|
const deleteTicketRecord = async (ticket) => {
|
|
593
|
+
try {
|
|
594
|
+
assertTicketServiceTargetMutable(ticket.id);
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
if (error instanceof HttpError) {
|
|
598
|
+
return { ok: false, status: error.status, error: error.message };
|
|
599
|
+
}
|
|
600
|
+
throw error;
|
|
601
|
+
}
|
|
580
602
|
if (ticket.project_id) {
|
|
581
603
|
try {
|
|
582
604
|
assertReviewRepairIterationDeletionEligible(ticket.project_id, ticket.id);
|
|
@@ -667,6 +689,7 @@ const deleteTicketRecord = async (ticket) => {
|
|
|
667
689
|
}
|
|
668
690
|
try {
|
|
669
691
|
withExecutionRunTransaction(transaction => {
|
|
692
|
+
assertTicketServiceTargetMutable(ticket.id);
|
|
670
693
|
if (ticket.project_id) {
|
|
671
694
|
deleteTerminalReviewRepairIterationsForTicketInTransaction(transaction, ticket.project_id, ticket.id);
|
|
672
695
|
}
|
|
@@ -2799,11 +2822,9 @@ app.get('/run-config', (req, res) => {
|
|
|
2799
2822
|
});
|
|
2800
2823
|
app.get('/run-status', (req, res) => {
|
|
2801
2824
|
const requestedProjectId = getRequestedProjectId(req);
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
: getActiveRunStatus(),
|
|
2806
|
-
});
|
|
2825
|
+
const activeRun = requestedProjectId ? getActiveRunStatusForProject(requestedProjectId) : getActiveRunStatus();
|
|
2826
|
+
const durableAuthority = hasActiveProjectServiceRun(requestedProjectId);
|
|
2827
|
+
return res.json(projectRunStatus(activeRun, requestedProjectId ? getLastRunStatusForProject(requestedProjectId) : null, durableAuthority, requestedProjectId && !activeRun && durableAuthority ? getProjectServiceCleanupStatus(requestedProjectId) : null));
|
|
2807
2828
|
});
|
|
2808
2829
|
app.get('/preview-status', (req, res) => {
|
|
2809
2830
|
const projectId = getRequestedProjectId(req);
|
|
@@ -4592,7 +4613,25 @@ app.post('/bundles/:id/undo-review', (req, res) => {
|
|
|
4592
4613
|
}
|
|
4593
4614
|
return res.json(undoResult.details);
|
|
4594
4615
|
});
|
|
4595
|
-
const
|
|
4616
|
+
const parseServiceRunControl = (body) => {
|
|
4617
|
+
if (body === undefined) {
|
|
4618
|
+
return {};
|
|
4619
|
+
}
|
|
4620
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
4621
|
+
return { error: 'Run control body must be an object' };
|
|
4622
|
+
}
|
|
4623
|
+
const input = body;
|
|
4624
|
+
for (const field of ['service_id', 'session_id']) {
|
|
4625
|
+
if (input[field] !== undefined && (typeof input[field] !== 'string' || !input[field].trim())) {
|
|
4626
|
+
return { error: `${field} must be a non-empty string` };
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
return {
|
|
4630
|
+
serviceId: typeof input.service_id === 'string' ? input.service_id.trim() : undefined,
|
|
4631
|
+
sessionId: typeof input.session_id === 'string' ? input.session_id.trim() : undefined,
|
|
4632
|
+
};
|
|
4633
|
+
};
|
|
4634
|
+
const parseServiceExecutionProviderChoice = (body, allowServiceControl = false) => {
|
|
4596
4635
|
if (body === undefined || body === null) {
|
|
4597
4636
|
return 'project_default';
|
|
4598
4637
|
}
|
|
@@ -4600,7 +4639,8 @@ const parseServiceExecutionProviderChoice = (body) => {
|
|
|
4600
4639
|
return null;
|
|
4601
4640
|
}
|
|
4602
4641
|
const request = body;
|
|
4603
|
-
if (Object.keys(request).some(key => key !== 'execution_provider'
|
|
4642
|
+
if (Object.keys(request).some(key => key !== 'execution_provider'
|
|
4643
|
+
&& !(allowServiceControl && (key === 'service_id' || key === 'session_id')))) {
|
|
4604
4644
|
return null;
|
|
4605
4645
|
}
|
|
4606
4646
|
if (request.execution_provider === undefined || request.execution_provider === 'project_default') {
|
|
@@ -4659,8 +4699,17 @@ app.post('/bundles/:id/run', (req, res) => {
|
|
|
4659
4699
|
return res.status(409).json(activeRunConflict);
|
|
4660
4700
|
}
|
|
4661
4701
|
const profileSelection = createServiceExecutionProfileSelection(project.id, executionProvider);
|
|
4702
|
+
try {
|
|
4703
|
+
void runTicketInWorkspace(targetTicket, io, project, workspace.cwd, profileSelection)
|
|
4704
|
+
.catch(error => console.error('[run] admitted bundle service startup failed:', error));
|
|
4705
|
+
}
|
|
4706
|
+
catch (error) {
|
|
4707
|
+
return res.status(409).json({
|
|
4708
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4709
|
+
code: error instanceof Error && 'code' in error ? error.code : 'service_start_rejected',
|
|
4710
|
+
});
|
|
4711
|
+
}
|
|
4662
4712
|
emitRunStateForContext(targetTicket, true);
|
|
4663
|
-
runTicketInWorkspace(targetTicket, io, project, workspace.cwd, profileSelection);
|
|
4664
4713
|
return res.json(serializeBundleDetails(result.project, bundle));
|
|
4665
4714
|
});
|
|
4666
4715
|
app.post('/bundles/:id/stop', async (req, res) => {
|
|
@@ -4700,52 +4749,6 @@ app.post('/bundles/:id/follow-up', (req, res) => {
|
|
|
4700
4749
|
});
|
|
4701
4750
|
const AUTOMATION_SIDE_EFFECT_KINDS = new Set(['automation_commit', 'automation_push', 'automation_pull_request']);
|
|
4702
4751
|
const AUTOMATION_WORKER_PAUSE_KINDS = new Set(['build', 'review', 'build_follow_up', 'automation_review_fix', 'check_auto_fix', 'check_repair', 'check_batch']);
|
|
4703
|
-
app.post('/bundles/:id/continue-delivery', async (req, res) => {
|
|
4704
|
-
const result = getRequiredProject(req);
|
|
4705
|
-
if ('error' in result) {
|
|
4706
|
-
return res.status(400).json({ error: result.error });
|
|
4707
|
-
}
|
|
4708
|
-
const bundle = getBundle(req.params.id, result.project.id);
|
|
4709
|
-
if (!bundle) {
|
|
4710
|
-
return res.status(404).json({ error: 'Bundle not found' });
|
|
4711
|
-
}
|
|
4712
|
-
const state = getBundleAutomationState(result.project.id, bundle.id);
|
|
4713
|
-
const tickets = listDetailedBundleTickets(result.project.id, bundle.id);
|
|
4714
|
-
if (!state) {
|
|
4715
|
-
return res.status(409).json({ error: 'Bundle delivery is not ready to continue' });
|
|
4716
|
-
}
|
|
4717
|
-
const automationConfig = resolveAutomationConfig(bundle, result.project);
|
|
4718
|
-
const decision = resolveAutomationDeliveryDecision({
|
|
4719
|
-
projectId: result.project.id,
|
|
4720
|
-
bundleId: bundle.id,
|
|
4721
|
-
state,
|
|
4722
|
-
config: automationConfig,
|
|
4723
|
-
tickets,
|
|
4724
|
-
});
|
|
4725
|
-
if (!decision) {
|
|
4726
|
-
return res.status(409).json({ error: 'Bundle delivery is not ready to continue' });
|
|
4727
|
-
}
|
|
4728
|
-
const owner = tickets.find(ticket => ticket.id === decision.owner_ticket_id);
|
|
4729
|
-
const reviewStageRunId = state.automation_delivery_ready_stage_run_id;
|
|
4730
|
-
if (!owner || !reviewStageRunId) {
|
|
4731
|
-
return res.status(409).json({ error: 'Bundle delivery owner is no longer available' });
|
|
4732
|
-
}
|
|
4733
|
-
try {
|
|
4734
|
-
const progression = ensureAutomationProgression({
|
|
4735
|
-
projectId: result.project.id,
|
|
4736
|
-
bundleId: bundle.id,
|
|
4737
|
-
ticketId: owner.id,
|
|
4738
|
-
ticketTitle: owner.title,
|
|
4739
|
-
parentRunId: reviewStageRunId,
|
|
4740
|
-
broadcaster: io,
|
|
4741
|
-
});
|
|
4742
|
-
await runAutomationCommitChain(owner, result.project, owner.bundle ?? bundle, automationConfig, io, progression.runId, undefined, reviewStageRunId);
|
|
4743
|
-
}
|
|
4744
|
-
catch (error) {
|
|
4745
|
-
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
4746
|
-
}
|
|
4747
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
4748
|
-
});
|
|
4749
4752
|
app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
4750
4753
|
const result = getRequiredProject(req);
|
|
4751
4754
|
if ('error' in result) {
|
|
@@ -4785,7 +4788,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4785
4788
|
: 'Bundle automation cannot be resumed in its current state';
|
|
4786
4789
|
return res.status(409).json({ error, automation_resume_decision: initialDecision });
|
|
4787
4790
|
}
|
|
4788
|
-
|
|
4791
|
+
const decision = initialDecision;
|
|
4789
4792
|
const revalidateDecision = () => {
|
|
4790
4793
|
const current = resolveCurrentDecision();
|
|
4791
4794
|
return current?.status === 'enabled'
|
|
@@ -5001,9 +5004,21 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5001
5004
|
}
|
|
5002
5005
|
const automationConfig = resolveAutomationConfig(owner.bundle ?? null, project);
|
|
5003
5006
|
const legacyReviewRepairPause = isLegacyReviewRepairMigrationPause(result.project.id, bundle.id, progressionRunId);
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
+
const completedVerificationIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
|
|
5008
|
+
if (completedVerificationIteration
|
|
5009
|
+
&& ['paused', 'verification_active'].includes(completedVerificationIteration.phase)
|
|
5010
|
+
&& completedVerificationIteration.stop_reason !== 'no_progress'
|
|
5011
|
+
&& completedVerificationIteration.resulting_review_execution_run_id
|
|
5012
|
+
&& getExecutionRun(result.project.id, completedVerificationIteration.resulting_review_execution_run_id)?.status === 'completed') {
|
|
5013
|
+
try {
|
|
5014
|
+
await recoverCompletedReviewRepairVerification(result.project.id, completedVerificationIteration.id, io, { resume: true });
|
|
5015
|
+
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5016
|
+
}
|
|
5017
|
+
catch (error) {
|
|
5018
|
+
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
if (completedVerificationIteration?.phase === 'paused' && completedVerificationIteration.stop_reason !== 'no_progress') {
|
|
5007
5022
|
withExecutionRunTransaction(transaction => {
|
|
5008
5023
|
appendReviewRepairIterationDecisionInTransaction(transaction, {
|
|
5009
5024
|
projectId: result.project.id,
|
|
@@ -5014,165 +5029,6 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5014
5029
|
});
|
|
5015
5030
|
}, io, 'immediate');
|
|
5016
5031
|
}
|
|
5017
|
-
if (completedVerificationIteration?.phase === 'paused') {
|
|
5018
|
-
const verificationRunId = completedVerificationIteration.resulting_review_execution_run_id;
|
|
5019
|
-
const verificationRun = verificationRunId ? getExecutionRun(result.project.id, verificationRunId) : null;
|
|
5020
|
-
if (verificationRun?.status === 'completed') {
|
|
5021
|
-
try {
|
|
5022
|
-
completedVerificationIteration = resumePausedReviewRepairIteration({
|
|
5023
|
-
projectId: result.project.id,
|
|
5024
|
-
iterationId: completedVerificationIteration.id,
|
|
5025
|
-
expectedCurrentRunId: verificationRunId,
|
|
5026
|
-
broadcaster: io,
|
|
5027
|
-
});
|
|
5028
|
-
}
|
|
5029
|
-
catch (error) {
|
|
5030
|
-
const current = getReviewRepairIteration(result.project.id, completedVerificationIteration.id);
|
|
5031
|
-
if (current?.resulting_review_execution_run_id === verificationRunId
|
|
5032
|
-
&& ['verification_active', 'terminal'].includes(current.phase)) {
|
|
5033
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5034
|
-
}
|
|
5035
|
-
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
5036
|
-
}
|
|
5037
|
-
}
|
|
5038
|
-
}
|
|
5039
|
-
if (completedVerificationIteration?.phase === 'verification_active'
|
|
5040
|
-
&& completedVerificationIteration.resulting_review_execution_run_id) {
|
|
5041
|
-
const verificationRunId = completedVerificationIteration.resulting_review_execution_run_id;
|
|
5042
|
-
const verificationRun = getExecutionRun(result.project.id, verificationRunId);
|
|
5043
|
-
const resultingReview = verificationRun?.status === 'completed'
|
|
5044
|
-
? db.prepare(`
|
|
5045
|
-
SELECT review.id
|
|
5046
|
-
FROM review_runs review
|
|
5047
|
-
JOIN execution_run_links result_link
|
|
5048
|
-
ON result_link.execution_run_id = ?
|
|
5049
|
-
AND result_link.project_id = review.project_id
|
|
5050
|
-
AND result_link.entity_type = 'review_run'
|
|
5051
|
-
AND result_link.entity_id = review.id
|
|
5052
|
-
AND result_link.relationship = 'result'
|
|
5053
|
-
WHERE review.project_id = ? AND review.ticket_id = ?
|
|
5054
|
-
AND review.worktree_bundle_id = ? AND review.status = 'completed'
|
|
5055
|
-
LIMIT 1
|
|
5056
|
-
`).get(verificationRunId, result.project.id, owner.id, bundle.id)
|
|
5057
|
-
: undefined;
|
|
5058
|
-
if (resultingReview) {
|
|
5059
|
-
reconciledSourceReviewRunId = resultingReview.id;
|
|
5060
|
-
const blockingCount = countBlockingFindings(listReviewFindings(resultingReview.id), automationConfig);
|
|
5061
|
-
const terminal = !isAutomationActive(automationConfig)
|
|
5062
|
-
? { outcome: 'interrupted', stopReason: 'configuration_changed' }
|
|
5063
|
-
: blockingCount === 0
|
|
5064
|
-
? { outcome: 'repaired', stopReason: 'no_blocking_findings' }
|
|
5065
|
-
: automationConfig.reviewFixEnabled
|
|
5066
|
-
? completedVerificationIteration.ordinal < automationConfig.reviewMaxIterations
|
|
5067
|
-
? { outcome: 'continuing', stopReason: null }
|
|
5068
|
-
: { outcome: 'exhausted', stopReason: 'max_iterations' }
|
|
5069
|
-
: { outcome: 'interrupted', stopReason: 'automation_disabled' };
|
|
5070
|
-
try {
|
|
5071
|
-
withExecutionRunTransaction(transaction => {
|
|
5072
|
-
if (!completedVerificationIteration.resulting_review_run_id) {
|
|
5073
|
-
attachReviewRepairResultInTransaction(transaction, {
|
|
5074
|
-
projectId: result.project.id,
|
|
5075
|
-
iterationId: completedVerificationIteration.id,
|
|
5076
|
-
expectedPhase: 'verification_active',
|
|
5077
|
-
expectedVerificationRunId: verificationRunId,
|
|
5078
|
-
resultingReviewRunId: resultingReview.id,
|
|
5079
|
-
});
|
|
5080
|
-
}
|
|
5081
|
-
appendReviewRepairIterationDecisionInTransaction(transaction, {
|
|
5082
|
-
projectId: result.project.id,
|
|
5083
|
-
iterationId: completedVerificationIteration.id,
|
|
5084
|
-
decisionPoint: 'verification_completion',
|
|
5085
|
-
effectiveAutomationConfig: automationConfig,
|
|
5086
|
-
decisionCode: 'verification_reconciled_on_resume',
|
|
5087
|
-
operationRunId: verificationRunId,
|
|
5088
|
-
});
|
|
5089
|
-
terminalizeReviewRepairIterationInTransaction(transaction, {
|
|
5090
|
-
projectId: result.project.id,
|
|
5091
|
-
iterationId: completedVerificationIteration.id,
|
|
5092
|
-
expectedPhase: 'verification_active',
|
|
5093
|
-
expectedCurrentRunId: verificationRunId,
|
|
5094
|
-
outcome: terminal.outcome,
|
|
5095
|
-
stopReason: terminal.stopReason,
|
|
5096
|
-
resultingBlockingCount: blockingCount,
|
|
5097
|
-
});
|
|
5098
|
-
if (automationConfig.depth === 'manual') {
|
|
5099
|
-
completeAutomationProgressionInTransaction(transaction, {
|
|
5100
|
-
projectId: result.project.id,
|
|
5101
|
-
bundleId: bundle.id,
|
|
5102
|
-
progressionRunId,
|
|
5103
|
-
domainStatus: 'manual_boundary_reached',
|
|
5104
|
-
});
|
|
5105
|
-
}
|
|
5106
|
-
if (terminal.outcome === 'continuing') {
|
|
5107
|
-
claimReviewRepairIterationInTransaction(transaction, {
|
|
5108
|
-
projectId: result.project.id,
|
|
5109
|
-
worktreeBundleId: bundle.id,
|
|
5110
|
-
ownerTicketId: owner.id,
|
|
5111
|
-
automationProgressionRunId: progressionRunId,
|
|
5112
|
-
triggeringReviewExecutionRunId: verificationRunId,
|
|
5113
|
-
triggeringReviewRunId: resultingReview.id,
|
|
5114
|
-
triggeringBlockingCount: blockingCount,
|
|
5115
|
-
effectiveAutomationConfig: automationConfig,
|
|
5116
|
-
decisionCode: 'resume_verification_repair_admitted',
|
|
5117
|
-
});
|
|
5118
|
-
}
|
|
5119
|
-
}, io, 'immediate');
|
|
5120
|
-
}
|
|
5121
|
-
catch (error) {
|
|
5122
|
-
const current = getReviewRepairIteration(result.project.id, completedVerificationIteration.id);
|
|
5123
|
-
if (current?.phase === 'terminal'
|
|
5124
|
-
&& current.resulting_review_execution_run_id === verificationRunId
|
|
5125
|
-
&& current.resulting_review_run_id === resultingReview.id) {
|
|
5126
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5127
|
-
}
|
|
5128
|
-
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
5129
|
-
}
|
|
5130
|
-
if (terminal.outcome === 'continuing') {
|
|
5131
|
-
const continuationDecision = resolveCurrentDecision();
|
|
5132
|
-
if (!continuationDecision || continuationDecision.status !== 'enabled') {
|
|
5133
|
-
return res.status(409).json({ error: 'Bundle automation state changed before repair continuation could start' });
|
|
5134
|
-
}
|
|
5135
|
-
decision = continuationDecision;
|
|
5136
|
-
}
|
|
5137
|
-
else if (terminal.outcome === 'repaired') {
|
|
5138
|
-
resetBundleAutomationLoop(result.project.id, bundle.id);
|
|
5139
|
-
runAutomationProgression({
|
|
5140
|
-
projectId: result.project.id,
|
|
5141
|
-
bundleId: bundle.id,
|
|
5142
|
-
progressionRunId,
|
|
5143
|
-
broadcaster: io,
|
|
5144
|
-
});
|
|
5145
|
-
if (depthAtLeast(automationConfig.depth, 'auto_commit')) {
|
|
5146
|
-
await runAutomationCommitChain(owner, project, owner.bundle ?? null, automationConfig, io, progressionRunId, undefined, verificationRunId);
|
|
5147
|
-
}
|
|
5148
|
-
else {
|
|
5149
|
-
completeAutomationProgression({
|
|
5150
|
-
projectId: result.project.id,
|
|
5151
|
-
bundleId: bundle.id,
|
|
5152
|
-
progressionRunId,
|
|
5153
|
-
broadcaster: io,
|
|
5154
|
-
});
|
|
5155
|
-
}
|
|
5156
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5157
|
-
}
|
|
5158
|
-
else if (automationConfig.depth !== 'manual') {
|
|
5159
|
-
waitAutomationProgression({
|
|
5160
|
-
projectId: result.project.id,
|
|
5161
|
-
bundleId: bundle.id,
|
|
5162
|
-
progressionRunId,
|
|
5163
|
-
pauseCode: 'review',
|
|
5164
|
-
pauseReason: 'Blocking Review findings remain after repair verification.',
|
|
5165
|
-
reasonCode: 'precondition_failed',
|
|
5166
|
-
pausedStageRunId: verificationRunId,
|
|
5167
|
-
broadcaster: io,
|
|
5168
|
-
});
|
|
5169
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5170
|
-
}
|
|
5171
|
-
else {
|
|
5172
|
-
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5173
|
-
}
|
|
5174
|
-
}
|
|
5175
|
-
}
|
|
5176
5032
|
const restoreWaitingProgression = () => {
|
|
5177
5033
|
waitAutomationProgression({
|
|
5178
5034
|
projectId: result.project.id,
|
|
@@ -5365,7 +5221,16 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5365
5221
|
}
|
|
5366
5222
|
const workflowContext = validateWorkflowContext(progression.metadata);
|
|
5367
5223
|
let existingIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
|
|
5368
|
-
const
|
|
5224
|
+
const overridingNoProgress = existingIteration?.phase === 'paused' && existingIteration.stop_reason === 'no_progress';
|
|
5225
|
+
if (overridingNoProgress) {
|
|
5226
|
+
existingIteration = withExecutionRunTransaction(transaction => {
|
|
5227
|
+
if (!revalidateDecision()) {
|
|
5228
|
+
throw new Error('Bundle automation state changed before Resume override could start');
|
|
5229
|
+
}
|
|
5230
|
+
return overrideNoProgressReviewRepairInTransaction(transaction, result.project.id, existingIteration.id, io);
|
|
5231
|
+
}, io, 'immediate');
|
|
5232
|
+
}
|
|
5233
|
+
const sourceReviewRunId = existingIteration?.triggering_review_run_id ?? null;
|
|
5369
5234
|
if (!sourceReviewRunId) {
|
|
5370
5235
|
throw new Error('The completed review findings to resume are unavailable');
|
|
5371
5236
|
}
|
|
@@ -5374,12 +5239,14 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5374
5239
|
review_finding_run_id: sourceReviewRunId,
|
|
5375
5240
|
review_severity: automationConfig.reviewSeverity,
|
|
5376
5241
|
});
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5242
|
+
if (!overridingNoProgress) {
|
|
5243
|
+
runAutomationProgression({
|
|
5244
|
+
projectId: result.project.id,
|
|
5245
|
+
bundleId: bundle.id,
|
|
5246
|
+
progressionRunId,
|
|
5247
|
+
broadcaster: io,
|
|
5248
|
+
});
|
|
5249
|
+
}
|
|
5383
5250
|
if (existingIteration?.phase === 'paused') {
|
|
5384
5251
|
existingIteration = resumePausedReviewRepairIteration({
|
|
5385
5252
|
projectId: result.project.id,
|
|
@@ -5650,9 +5517,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5650
5517
|
});
|
|
5651
5518
|
}, io, 'immediate');
|
|
5652
5519
|
}
|
|
5653
|
-
|
|
5654
|
-
resetBundleAutomationLoop(result.project.id, bundle.id);
|
|
5655
|
-
}
|
|
5520
|
+
resetBundleAutomationLoop(result.project.id, bundle.id);
|
|
5656
5521
|
}
|
|
5657
5522
|
runAutomationProgression({
|
|
5658
5523
|
projectId: result.project.id,
|
|
@@ -8201,14 +8066,6 @@ app.patch('/projects/:id', async (req, res) => {
|
|
|
8201
8066
|
if ('error' in roleModelValidation) {
|
|
8202
8067
|
return res.status(400).json({ error: roleModelValidation.error });
|
|
8203
8068
|
}
|
|
8204
|
-
if (hasOwn(validation.values, 'repo_path') && validation.values.repo_path !== project.repo_path) {
|
|
8205
|
-
const stats = getProjectTicketStats(project.id);
|
|
8206
|
-
if (stats.ticketCount > 0 || stats.worktreeCount > 0 || stats.bundleCount > 0) {
|
|
8207
|
-
return res.status(409).json({
|
|
8208
|
-
error: 'repo_path cannot be changed after tickets, bundles, or worktrees exist for this project',
|
|
8209
|
-
});
|
|
8210
|
-
}
|
|
8211
|
-
}
|
|
8212
8069
|
const { name, repo_path, linked_project_ids, run_setup, run_services, check_definitions, required_check_definition_ids, pre_review_check_definition_ids, post_build_check_definition_ids, auto_fix_check_definition_ids, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, helper_models_json, reviewer_model, reviewer_variant, reviewer_models_json, commit_message_rules, commit_after_each_ticket, auto_park_stale_tickets, memory_enabled, scan_include_globs_json, scan_exclude_globs_json, worktree_sync, spec_aware_code_review_enabled, automation_depth, automation_review_fix_enabled, automation_pause_on_open_questions, automation_review_severity, automation_review_max_iterations, } = validation.values;
|
|
8213
8070
|
const updateProject = db.prepare(`
|
|
8214
8071
|
UPDATE projects SET
|
|
@@ -8266,7 +8123,7 @@ app.patch('/projects/:id', async (req, res) => {
|
|
|
8266
8123
|
const nextReviewerVariant = nextReviewerModel
|
|
8267
8124
|
? (hasOwn(validation.values, 'reviewer_variant') ? reviewer_variant ?? null : project.reviewer_variant)
|
|
8268
8125
|
: null;
|
|
8269
|
-
const
|
|
8126
|
+
const applyUpdate = db.transaction(() => {
|
|
8270
8127
|
const currentProject = getProjectById(project.id);
|
|
8271
8128
|
if (!currentProject || JSON.stringify(currentProject) !== JSON.stringify(project)) {
|
|
8272
8129
|
return {
|
|
@@ -8274,6 +8131,15 @@ app.patch('/projects/:id', async (req, res) => {
|
|
|
8274
8131
|
status: 409,
|
|
8275
8132
|
};
|
|
8276
8133
|
}
|
|
8134
|
+
if (hasOwn(validation.values, 'repo_path') && repo_path !== currentProject.repo_path) {
|
|
8135
|
+
if (currentProject.deletion_started_at || hasActiveProjectServiceRun(project.id) || getActiveRunStatusForProject(project.id)) {
|
|
8136
|
+
return { error: 'Cannot change project repository while service authority or deletion is active', status: 409 };
|
|
8137
|
+
}
|
|
8138
|
+
const stats = getProjectTicketStats(project.id);
|
|
8139
|
+
if (stats.ticketCount > 0 || stats.worktreeCount > 0 || stats.bundleCount > 0) {
|
|
8140
|
+
return { error: 'repo_path cannot be changed after tickets, bundles, or worktrees exist for this project', status: 409 };
|
|
8141
|
+
}
|
|
8142
|
+
}
|
|
8277
8143
|
const policyReferences = validateProjectRequiredCheckPolicyReferences({
|
|
8278
8144
|
projectId: project.id,
|
|
8279
8145
|
checkDefinitionsJson: nextCheckDefinitions,
|
|
@@ -8305,7 +8171,17 @@ app.patch('/projects/:id', async (req, res) => {
|
|
|
8305
8171
|
replaceProjectLinks(project.id, linked_project_ids ?? []);
|
|
8306
8172
|
}
|
|
8307
8173
|
return { ok: true };
|
|
8308
|
-
})
|
|
8174
|
+
});
|
|
8175
|
+
let updateResult;
|
|
8176
|
+
try {
|
|
8177
|
+
updateResult = applyUpdate.immediate();
|
|
8178
|
+
}
|
|
8179
|
+
catch (error) {
|
|
8180
|
+
if (error instanceof Error && 'code' in error && error.code === 'SQLITE_CONSTRAINT_TRIGGER') {
|
|
8181
|
+
return res.status(409).json({ error: error.message });
|
|
8182
|
+
}
|
|
8183
|
+
throw error;
|
|
8184
|
+
}
|
|
8309
8185
|
if ('error' in updateResult) {
|
|
8310
8186
|
const status = 'status' in updateResult && typeof updateResult.status === 'number'
|
|
8311
8187
|
? updateResult.status
|
|
@@ -8316,6 +8192,9 @@ app.patch('/projects/:id', async (req, res) => {
|
|
|
8316
8192
|
res.json(serializeProject(updated ?? null));
|
|
8317
8193
|
});
|
|
8318
8194
|
const assertProjectDeletionEligible = (projectId, claimId) => {
|
|
8195
|
+
if (hasActiveProjectServiceRun(projectId) || getActiveRunStatusForProject(projectId)) {
|
|
8196
|
+
throw new ProjectDeletionError(409, 'Cannot delete a project while services are active');
|
|
8197
|
+
}
|
|
8319
8198
|
const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId);
|
|
8320
8199
|
if (!project) {
|
|
8321
8200
|
throw new ProjectDeletionError(404, 'Not found');
|
|
@@ -8363,6 +8242,7 @@ const assertProjectDeletionEligible = (projectId, claimId) => {
|
|
|
8363
8242
|
}
|
|
8364
8243
|
};
|
|
8365
8244
|
const completeClaimedProjectDeletion = async (projectId, claimId) => {
|
|
8245
|
+
assertProjectDeletionEligible(projectId, claimId);
|
|
8366
8246
|
const marked = db.prepare(`
|
|
8367
8247
|
UPDATE projects
|
|
8368
8248
|
SET deletion_cleanup_started_at = COALESCE(deletion_cleanup_started_at, CURRENT_TIMESTAMP)
|
|
@@ -8599,6 +8479,17 @@ app.patch('/tickets/:id', async (req, res) => {
|
|
|
8599
8479
|
const nextProjectId = hasProjectField ? project_id : ticket.project_id;
|
|
8600
8480
|
const isProjectChanging = nextProjectId !== ticket.project_id;
|
|
8601
8481
|
const hasStandaloneWorktree = typeof ticket.branch === 'string' && ticket.branch.trim() !== '';
|
|
8482
|
+
if (isProjectChanging || (hasBundleField && req.body.worktree_bundle_id !== ticket.worktree_bundle_id)) {
|
|
8483
|
+
try {
|
|
8484
|
+
assertTicketServiceTargetMutable(ticket.id);
|
|
8485
|
+
}
|
|
8486
|
+
catch (error) {
|
|
8487
|
+
if (error instanceof HttpError) {
|
|
8488
|
+
return res.status(error.status).json({ error: error.message });
|
|
8489
|
+
}
|
|
8490
|
+
throw error;
|
|
8491
|
+
}
|
|
8492
|
+
}
|
|
8602
8493
|
if (isProjectChanging && hasStandaloneWorktree) {
|
|
8603
8494
|
return res.status(409).json({ error: 'project_id cannot be changed after a worktree has been created' });
|
|
8604
8495
|
}
|
|
@@ -8708,10 +8599,23 @@ app.patch('/tickets/:id', async (req, res) => {
|
|
|
8708
8599
|
}
|
|
8709
8600
|
}
|
|
8710
8601
|
}
|
|
8711
|
-
|
|
8712
|
-
|
|
8602
|
+
try {
|
|
8603
|
+
db.transaction(() => {
|
|
8604
|
+
if (bundleChanged) {
|
|
8605
|
+
assertTicketServiceTargetMutable(ticket.id);
|
|
8606
|
+
}
|
|
8607
|
+
if (isProjectChanging) {
|
|
8608
|
+
deleteTicketDependenciesForTicket(ticket.id);
|
|
8609
|
+
}
|
|
8610
|
+
db.prepare('UPDATE tickets SET title = ?, description = ?, model = ?, variant = ?, skills_json = ?, worktree_bundle_id = ?, project_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(title?.trim() ?? ticket.title, description ?? ticket.description, resolvedModel, resolvedVariant, JSON.stringify(resolvedSkills), resolvedBundleId, nextProjectId, req.params.id);
|
|
8611
|
+
}).immediate();
|
|
8612
|
+
}
|
|
8613
|
+
catch (error) {
|
|
8614
|
+
if (error instanceof HttpError) {
|
|
8615
|
+
return res.status(error.status).json({ error: error.message });
|
|
8616
|
+
}
|
|
8617
|
+
throw error;
|
|
8713
8618
|
}
|
|
8714
|
-
db.prepare('UPDATE tickets SET title = ?, description = ?, model = ?, variant = ?, skills_json = ?, worktree_bundle_id = ?, project_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(title?.trim() ?? ticket.title, description ?? ticket.description, resolvedModel, resolvedVariant, JSON.stringify(resolvedSkills), resolvedBundleId, nextProjectId, req.params.id);
|
|
8715
8619
|
upsertTicketDescriptionMessage(req.params.id, description ?? ticket.description);
|
|
8716
8620
|
const updated = getTicket(req.params.id);
|
|
8717
8621
|
if (bundleChanged && previousBundleId && previousProject) {
|
|
@@ -9133,7 +9037,12 @@ app.post('/tickets/:id/undo', (req, res) => {
|
|
|
9133
9037
|
return res.json(result.ticket);
|
|
9134
9038
|
});
|
|
9135
9039
|
app.post('/tickets/:id/run', (req, res) => {
|
|
9040
|
+
const control = parseServiceRunControl(req.body);
|
|
9041
|
+
if ('error' in control) {
|
|
9042
|
+
return res.status(400).json({ error: control.error });
|
|
9043
|
+
}
|
|
9136
9044
|
const result = runTicketRun({
|
|
9045
|
+
...control,
|
|
9137
9046
|
ticketId: req.params.id,
|
|
9138
9047
|
requestedProjectId: getRequestedProjectId(req),
|
|
9139
9048
|
broadcaster: io,
|
|
@@ -9394,11 +9303,16 @@ app.post('/projects/:id/open-ide', (req, res) => {
|
|
|
9394
9303
|
return res.json({ ok: true });
|
|
9395
9304
|
});
|
|
9396
9305
|
app.post('/projects/:id/run', (req, res) => {
|
|
9397
|
-
const
|
|
9306
|
+
const control = parseServiceRunControl(req.body);
|
|
9307
|
+
if ('error' in control) {
|
|
9308
|
+
return res.status(400).json({ error: control.error });
|
|
9309
|
+
}
|
|
9310
|
+
const executionProvider = parseServiceExecutionProviderChoice(req.body, true);
|
|
9398
9311
|
if (!executionProvider) {
|
|
9399
9312
|
return res.status(400).json({ error: 'execution_provider must be project_default or host' });
|
|
9400
9313
|
}
|
|
9401
9314
|
const result = runProjectTarget({
|
|
9315
|
+
...control,
|
|
9402
9316
|
projectId: req.params.id,
|
|
9403
9317
|
executionProvider,
|
|
9404
9318
|
});
|
|
@@ -9883,7 +9797,7 @@ app.get('/projects/:id/execution-timeline/:runId', async (req, res) => {
|
|
|
9883
9797
|
if (scope.scopeEntity && !links.some(link => link.entity_type === scope.scopeEntity?.type && link.entity_id === scope.scopeEntity.id)) {
|
|
9884
9798
|
return res.status(404).json({ error: 'Execution run not found' });
|
|
9885
9799
|
}
|
|
9886
|
-
return res.json(await buildExecutionRunDetailResponse(project, run));
|
|
9800
|
+
return res.json(await buildExecutionRunDetailResponse(project, run, scope.scopeEntity));
|
|
9887
9801
|
});
|
|
9888
9802
|
app.get('/projects/:id/execution-runs/:runId', async (req, res) => {
|
|
9889
9803
|
const project = getProjectById(req.params.id);
|
|
@@ -10131,7 +10045,12 @@ app.post('/terminals/:sessionId/destroy', (req, res) => {
|
|
|
10131
10045
|
return res.status(204).send();
|
|
10132
10046
|
});
|
|
10133
10047
|
app.post('/tickets/:id/stop', async (req, res) => {
|
|
10048
|
+
const control = parseServiceRunControl(req.body);
|
|
10049
|
+
if ('error' in control) {
|
|
10050
|
+
return res.status(400).json({ error: control.error });
|
|
10051
|
+
}
|
|
10134
10052
|
const result = await stopTicketRun({
|
|
10053
|
+
...control,
|
|
10135
10054
|
ticketId: req.params.id,
|
|
10136
10055
|
requestedProjectId: getRequestedProjectId(req),
|
|
10137
10056
|
broadcaster: io,
|
|
@@ -10142,7 +10061,12 @@ app.post('/tickets/:id/stop', async (req, res) => {
|
|
|
10142
10061
|
return res.status(result.status).json(result.value);
|
|
10143
10062
|
});
|
|
10144
10063
|
app.post('/projects/:id/stop-run', async (req, res) => {
|
|
10064
|
+
const control = parseServiceRunControl(req.body);
|
|
10065
|
+
if ('error' in control) {
|
|
10066
|
+
return res.status(400).json({ error: control.error });
|
|
10067
|
+
}
|
|
10145
10068
|
const result = await stopProjectTargetRun({
|
|
10069
|
+
...control,
|
|
10146
10070
|
projectId: req.params.id,
|
|
10147
10071
|
});
|
|
10148
10072
|
if (!result.ok) {
|
|
@@ -10150,6 +10074,23 @@ app.post('/projects/:id/stop-run', async (req, res) => {
|
|
|
10150
10074
|
}
|
|
10151
10075
|
return res.status(result.status).json(result.value);
|
|
10152
10076
|
});
|
|
10077
|
+
app.post('/projects/:id/service-cleanup/force-release', (req, res) => {
|
|
10078
|
+
if (req.body?.confirm !== true) {
|
|
10079
|
+
return res.status(400).json({ error: 'Force release requires explicit confirmation' });
|
|
10080
|
+
}
|
|
10081
|
+
if (!getProjectById(req.params.id)) {
|
|
10082
|
+
return res.status(404).json({ error: 'Not found' });
|
|
10083
|
+
}
|
|
10084
|
+
try {
|
|
10085
|
+
const released = forceReleaseProjectRunCleanup(req.params.id);
|
|
10086
|
+
return released
|
|
10087
|
+
? res.json({ active_run: null })
|
|
10088
|
+
: res.status(409).json({ error: 'No pending service cleanup was found' });
|
|
10089
|
+
}
|
|
10090
|
+
catch (error) {
|
|
10091
|
+
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
10092
|
+
}
|
|
10093
|
+
});
|
|
10153
10094
|
app.post('/tickets/:id/stop-build', async (req, res) => {
|
|
10154
10095
|
const result = await stopBuildTicketRun({
|
|
10155
10096
|
ticketId: req.params.id,
|
|
@@ -10439,7 +10380,10 @@ const runExecutionEnvironmentLifecycleReconciliation = () => {
|
|
|
10439
10380
|
if (executionEnvironmentReconciliationPromise) {
|
|
10440
10381
|
return executionEnvironmentReconciliationPromise;
|
|
10441
10382
|
}
|
|
10442
|
-
executionEnvironmentReconciliationPromise =
|
|
10383
|
+
executionEnvironmentReconciliationPromise = reconcilePendingServiceCleanups({
|
|
10384
|
+
isProjectOwnedInMemory: projectId => getActiveRunStatusForProject(projectId) !== null,
|
|
10385
|
+
})
|
|
10386
|
+
.then(() => reconcileExecutionEnvironmentLifecycleOperations())
|
|
10443
10387
|
.then(() => undefined)
|
|
10444
10388
|
.catch(error => {
|
|
10445
10389
|
console.error('Failed to reconcile execution environment lifecycle operations', error);
|