@straiffi/archon 1.7.7 → 1.7.9

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 (140) hide show
  1. package/dist/client/assets/ActivityView-CDLQOXZm.js +1 -0
  2. package/dist/client/assets/GitHubActionsDialog-DYxLX2HS.js +1 -0
  3. package/dist/client/assets/VerificationDialog-DEKvZG75.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-DVbmGv0b.js → architectureDiagram-3BPJPVTR-ByNXcCIO.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-BsxPuJKK.js → blockDiagram-GPEHLZMM-ByykLAaG.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-9Jjpu2R1.js → c4Diagram-AAUBKEIU-CmWA4Pli.js} +1 -1
  7. package/dist/client/assets/channel-BvcRv90i.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-Dzljq_5a.js → chunk-2J33WTMH-CTgbXRh4.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-CiBHaLdV.js → chunk-3OPIFGDE-D3FajVfq.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-DZFrI-_H.js → chunk-5ZQYHXKU-CUMg_Vfo.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-u519MeJ7.js → chunk-727SXJPM-Oc9MgxZD.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-DC91Nw9K.js → chunk-AQP2D5EJ-5JZboHeP.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-CR7cwH-D.js → chunk-CSCIHK7Q-BpkSMHjB.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-DJwQEfa_.js → chunk-KSCS5N6A-BPBMAswO.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-w6TWSOTT.js → chunk-L5ZTLDWV-BA2sHmu7.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-Dfw_jEjR.js → chunk-LZXEDZCA-Blc9q-fQ.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-DHdJgCA7.js → chunk-ND2GUHAM-GlVS74JP.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-GvOuUiUy.js → chunk-NZK2D7GU-fctBFnAm.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O--r1VQgDc.js → chunk-O5CBEL6O-eGs4DDLJ.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-Xgc6shf5.js → chunk-WU5MYG2G-vxa50E7g.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-IuovJyxl.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BBJ3avX6.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-IcfTsQ6f.js → dagre-BM42HDAG-DXGXNVUp.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-D8Z4_NSQ.js → diagram-2AECGRRQ-BRYr1GvB.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-CIjC8QzH.js → diagram-5GNKFQAL-BcwgMag4.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-BBbYFygn.js → diagram-KO2AKTUF-Delk7lbr.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-lZstJdXv.js → diagram-LMA3HP47-BVtc_fjo.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-Cnqij60i.js → diagram-OG6HWLK6-weIPvUz5.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-1DAJwSbR.js → erDiagram-TEJ5UH35-DKde70dA.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-DIQkR95e.js → flowDiagram-I6XJVG4X-B7Jrk-6c.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-mDGbRM53.js → ganttDiagram-6RSMTGT7-CDzWDgnA.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BQirGZuE.js → gitGraphDiagram-PVQCEYII-DYZCryuN.js} +1 -1
  33. package/dist/client/assets/index-BlKOx9Vf.css +2 -0
  34. package/dist/client/assets/index-CmaBDvIB.js +273 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-CljoXByt.js → infoDiagram-5YYISTIA-DmQOgVKK.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-ouY7AiSo.js → ishikawaDiagram-YF4QCWOH-DCpu-q-Z.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-BiI9xI0r.js → journeyDiagram-JHISSGLW-DhBoglhI.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-CvuW7aYT.js → kanban-definition-UN3LZRKU-BSjvGFTY.js} +1 -1
  39. package/dist/client/assets/{line-CoE97hT8.js → line-ClKBZwDr.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-BHswQsmF.js → mermaid-parser.core-BRA4mJ40.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-a9PzZlL1.js → mermaid.core-BZ7kSfEt.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-CIkEQqLV.js → mindmap-definition-RKZ34NQL-DoUdLHgj.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-BIvcv2M5.js → pieDiagram-4H26LBE5-NVnzqltb.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BWCVll7J.js → quadrantDiagram-W4KKPZXB-DOITgkan.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-w5l0CZn2.js → requirementDiagram-4Y6WPE33-D0VPxrFE.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BuKReL9w.js → sankeyDiagram-5OEKKPKP-Bx0uU7xV.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Br5KpukA.js → sequenceDiagram-3UESZ5HK-B4lFL6cO.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-BfFCsPCI.js → stateDiagram-AJRCARHV-CsLVsMZ4.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CvrvaIdF.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-BrDP2iO0.js → timeline-definition-PNZ67QCA-BkcYpPFI.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-C7lGYnr9.js → vennDiagram-CIIHVFJN-Xc5f2sRQ.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BRKJRuEF.js → wardleyDiagram-YWT4CUSO-n3Dpk02Q.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BjaB0hQ8.js → xychartDiagram-2RQKCTM6-BTMzF8zg.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +460 -2
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +542 -30
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/activeAutomationChild.js +31 -0
  60. package/dist/server/lib/activeAutomationChild.js.map +1 -0
  61. package/dist/server/lib/automationCheckGate.js +341 -0
  62. package/dist/server/lib/automationCheckGate.js.map +1 -0
  63. package/dist/server/lib/automationProgression.js +20 -21
  64. package/dist/server/lib/automationProgression.js.map +1 -1
  65. package/dist/server/lib/automationReconciliation.js +35 -2
  66. package/dist/server/lib/automationReconciliation.js.map +1 -1
  67. package/dist/server/lib/automationResumeDecision.js +61 -1
  68. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  69. package/dist/server/lib/automationStageDetails.js +266 -41
  70. package/dist/server/lib/automationStageDetails.js.map +1 -1
  71. package/dist/server/lib/buildCompletionContinuation.js +109 -0
  72. package/dist/server/lib/buildCompletionContinuation.js.map +1 -0
  73. package/dist/server/lib/buildSequences.js +81 -0
  74. package/dist/server/lib/buildSequences.js.map +1 -1
  75. package/dist/server/lib/buildTicketCommit.js +219 -0
  76. package/dist/server/lib/buildTicketCommit.js.map +1 -0
  77. package/dist/server/lib/bundleAutomationReadiness.js +7 -0
  78. package/dist/server/lib/bundleAutomationReadiness.js.map +1 -0
  79. package/dist/server/lib/bundleReviewOperations.js +17 -5
  80. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  81. package/dist/server/lib/chatOperations.js +2 -2
  82. package/dist/server/lib/chatOperations.js.map +1 -1
  83. package/dist/server/lib/checkDefinitions.js +278 -0
  84. package/dist/server/lib/checkDefinitions.js.map +1 -0
  85. package/dist/server/lib/checkGateEvaluations.js +295 -0
  86. package/dist/server/lib/checkGateEvaluations.js.map +1 -0
  87. package/dist/server/lib/checkRunner.js +674 -0
  88. package/dist/server/lib/checkRunner.js.map +1 -0
  89. package/dist/server/lib/checkRuns.js +736 -0
  90. package/dist/server/lib/checkRuns.js.map +1 -0
  91. package/dist/server/lib/checkWorkspaceIdentity.js +207 -0
  92. package/dist/server/lib/checkWorkspaceIdentity.js.map +1 -0
  93. package/dist/server/lib/executionReconciliation.js +79 -2
  94. package/dist/server/lib/executionReconciliation.js.map +1 -1
  95. package/dist/server/lib/executionRetry.js +14 -6
  96. package/dist/server/lib/executionRetry.js.map +1 -1
  97. package/dist/server/lib/executionRetryContext.js +3 -3
  98. package/dist/server/lib/executionRetryContext.js.map +1 -1
  99. package/dist/server/lib/executionRunRetention.js +41 -3
  100. package/dist/server/lib/executionRunRetention.js.map +1 -1
  101. package/dist/server/lib/executionRuns.js +135 -2
  102. package/dist/server/lib/executionRuns.js.map +1 -1
  103. package/dist/server/lib/executionTimeline.js +52 -1
  104. package/dist/server/lib/executionTimeline.js.map +1 -1
  105. package/dist/server/lib/projectAutoConfig.js +47 -65
  106. package/dist/server/lib/projectAutoConfig.js.map +1 -1
  107. package/dist/server/lib/projects.js +99 -7
  108. package/dist/server/lib/projects.js.map +1 -1
  109. package/dist/server/lib/requiredCheckPolicy.js +93 -0
  110. package/dist/server/lib/requiredCheckPolicy.js.map +1 -0
  111. package/dist/server/lib/shutdown.js +4 -4
  112. package/dist/server/lib/shutdown.js.map +1 -1
  113. package/dist/server/lib/testCommandRunner.js +3 -2
  114. package/dist/server/lib/testCommandRunner.js.map +1 -1
  115. package/dist/server/lib/testSessions.js +2 -6
  116. package/dist/server/lib/testSessions.js.map +1 -1
  117. package/dist/server/lib/ticketAutomationCommit.js +361 -108
  118. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  119. package/dist/server/lib/tickets.js +21 -1
  120. package/dist/server/lib/tickets.js.map +1 -1
  121. package/dist/server/lib/types.js +2 -0
  122. package/dist/server/lib/types.js.map +1 -1
  123. package/dist/server/workers/build.js +50 -81
  124. package/dist/server/workers/build.js.map +1 -1
  125. package/dist/server/workers/chat.js +13 -6
  126. package/dist/server/workers/chat.js.map +1 -1
  127. package/dist/server/workers/followUp.js +53 -30
  128. package/dist/server/workers/followUp.js.map +1 -1
  129. package/dist/server/workers/review.js +2 -10
  130. package/dist/server/workers/review.js.map +1 -1
  131. package/package.json +1 -1
  132. package/dist/client/assets/ActivityView-Btpsv-5U.js +0 -1
  133. package/dist/client/assets/GitHubActionsDialog-DRlIMgSB.js +0 -1
  134. package/dist/client/assets/TestsDialog-DSWDkKPV.js +0 -4
  135. package/dist/client/assets/channel-Lic13vb5.js +0 -1
  136. package/dist/client/assets/classDiagram-4FO5ZUOK-DACLwQIJ.js +0 -1
  137. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CKi6c1d9.js +0 -1
  138. package/dist/client/assets/index-DQt4dcwc.css +0 -2
  139. package/dist/client/assets/index-W9OCqkMU.js +0 -273
  140. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-lhIQ8FTe.js +0 -1
@@ -1,19 +1,48 @@
1
1
  import { createHash } from 'crypto';
2
2
  import db from '../db.js';
3
+ import { assertNoActiveAutomationChild } from './activeAutomationChild.js';
3
4
  import config from './config.js';
4
5
  import { buildCommitMessagePrompt, normalizeGeneratedCommitMessage, } from './commitMessage.js';
5
6
  import { commitPreparedAutomationChanges, computeGitTreeDeltaDigest, fetchRemoteBranchState, getAutomationCommitPaths, getAutomationRemoteIdentity, getGitHeadIdentity, getGitIndexTreeSha, getGitStatus, getReviewDiffData, getSelectedGitUnifiedDiff, prepareAutomationCommit, pushAutomationTarget, resolveWorktreePath, } from './git.js';
6
7
  import { runLightweightPrompt } from './lightweightPrompt.js';
7
8
  import { buildPullRequestContentPrompt, extractBodyFromRawOutput, parsePullRequestContent, } from './pullRequestContent.js';
8
- import { getBundle, getTicket, isProjectRootBundle } from './tickets.js';
9
+ import { getBundle, getTicket, isProjectRootBundle, listTicketsInRunContext } from './tickets.js';
9
10
  import { createGitHubPullRequest, observeGitHubPullRequest, } from './integrations/github.js';
10
11
  import { getGitHubConnectionConfig } from './integrations/index.js';
11
12
  import { upsertBundlePullRequestFromGitHub, } from './bundlePullRequests.js';
12
13
  import { depthAtLeast, getBundleAutomationState, resolveAutomationConfig, } from './ticketAutomation.js';
13
14
  import { completeAutomationProgressionInTransaction, completeAutomationProgression, getAutomationProgression, waitAutomationProgressionInTransaction, waitAutomationProgression, } from './automationProgression.js';
14
- import { AUTOMATION_STAGE_DETAILS_SCHEMA_VERSION, createAutomationStageDetails, ensureAutomationStageResult, } from './automationStageDetails.js';
15
+ import { assertAutomationBoundaryNotRequired, assertAutomationAuthorizationCurrent, authorizeAutomationBoundary, AutomationCheckGateError, } from './automationCheckGate.js';
16
+ import { AUTOMATION_STAGE_DETAILS_SCHEMA_VERSION, createAutomationStageDetails, ensureAutomationStageResult, getAutomationStageDetails, } from './automationStageDetails.js';
15
17
  import { getProjectById } from './projects.js';
16
18
  import { getExecutionRun, withExecutionRunTransaction } from './executionRuns.js';
19
+ export const resolveAutomationCheckGateResume = (projectId, progressionRunId) => {
20
+ const row = db.prepare(`
21
+ SELECT id, kind
22
+ FROM execution_runs
23
+ WHERE project_id = ? AND parent_run_id = ? AND status = 'completed'
24
+ AND kind IN ('automation_commit', 'automation_push')
25
+ ORDER BY rowid DESC
26
+ LIMIT 1
27
+ `).get(projectId, progressionRunId);
28
+ if (!row) {
29
+ return undefined;
30
+ }
31
+ const details = getAutomationStageDetails(projectId, row.id);
32
+ if (!details?.result) {
33
+ throw new Error('Completed automation stage has invalid result lineage');
34
+ }
35
+ if (row.kind === 'automation_push') {
36
+ const result = details.result;
37
+ if (!result.remoteSha) {
38
+ throw new Error('Completed automation push has invalid result lineage');
39
+ }
40
+ return { stageKind: row.kind, expectedHeadSha: result.remoteSha };
41
+ }
42
+ const result = details.result;
43
+ const expectedHeadSha = 'commitSha' in result ? result.commitSha : result.headSha;
44
+ return { stageKind: row.kind, expectedHeadSha };
45
+ };
17
46
  const EMPTY_MESSAGE_DIGEST = `sha256:${createHash('sha256').update('').digest('hex')}`;
18
47
  const getErrorMessage = (error) => error instanceof Error ? error.message : String(error);
19
48
  const digestCommitMessage = (message) => {
@@ -96,6 +125,20 @@ const emitUpdated = (io, ticketId) => {
96
125
  io.emit('ticket:updated', updated);
97
126
  }
98
127
  };
128
+ const emitBundleUpdated = (io, ticket) => {
129
+ const tickets = listTicketsInRunContext(ticket, {
130
+ includeDetail: false,
131
+ includeMessages: false,
132
+ includeLogs: false,
133
+ includeUndo: false,
134
+ includeDiffStats: false,
135
+ includeStaleState: false,
136
+ includeWorktreeState: false,
137
+ });
138
+ for (const updated of tickets) {
139
+ io.emit('ticket:updated', updated);
140
+ }
141
+ };
99
142
  const clearStoppedStagePointer = (projectId, bundleId, runId) => {
100
143
  db.prepare(`
101
144
  UPDATE bundle_automation_state
@@ -117,7 +160,37 @@ const loadLiveState = (projectId, bundleId) => {
117
160
  }
118
161
  return { project, bundle, automationConfig: resolveAutomationConfig(bundle, project) };
119
162
  };
120
- const assertStageCanMutate = (projectId, bundleId, progressionRunId, requiredDepth) => {
163
+ const toStageCheckAuthorization = (authorization) => authorization.requirement === 'required'
164
+ ? {
165
+ kind: 'required',
166
+ gateEvaluationId: authorization.evaluation.id,
167
+ policyFingerprint: authorization.policyFingerprint,
168
+ workspaceFingerprint: authorization.workspaceIdentity.fingerprint,
169
+ }
170
+ : {
171
+ kind: 'not_required',
172
+ policyFingerprint: authorization.policyFingerprint,
173
+ };
174
+ const assertBoundaryAuthorizationCurrent = async ({ authorization, project, bundle, progressionRunId, cwd, boundaryKind, }) => {
175
+ if (authorization.requirement === 'required') {
176
+ await assertAutomationAuthorizationCurrent({
177
+ project,
178
+ bundle,
179
+ progressionRunId,
180
+ cwd,
181
+ evaluationId: authorization.evaluation.id,
182
+ boundaryKind,
183
+ });
184
+ return;
185
+ }
186
+ assertAutomationBoundaryNotRequired({
187
+ project,
188
+ bundle,
189
+ progressionRunId,
190
+ policyFingerprint: authorization.policyFingerprint,
191
+ });
192
+ };
193
+ const assertStageCanMutateWithPolicy = (projectId, bundleId, progressionRunId, ticket, mutationLabel, canMutateLive) => {
121
194
  const live = loadLiveState(projectId, bundleId);
122
195
  const progression = getAutomationProgression(projectId, bundleId);
123
196
  const state = getBundleAutomationState(projectId, bundleId);
@@ -126,11 +199,12 @@ const assertStageCanMutate = (projectId, bundleId, progressionRunId, requiredDep
126
199
  || progression.status !== 'running'
127
200
  || state?.automation_progression_run_id !== progressionRunId
128
201
  || state.automation_paused_reason !== null
129
- || !depthAtLeast(live.automationConfig.depth, requiredDepth)) {
130
- throw new Error(`automation no longer permits ${requiredDepth === 'auto_push' ? 'push' : 'pull-request'} mutation`);
202
+ || !canMutateLive(live, { ticket, progressionRunId })) {
203
+ throw new Error(`automation no longer permits ${mutationLabel} mutation`);
131
204
  }
132
205
  return live;
133
206
  };
207
+ const assertStageCanMutate = (projectId, bundleId, progressionRunId, ticket, requiredDepth) => assertStageCanMutateWithPolicy(projectId, bundleId, progressionRunId, ticket, requiredDepth === 'auto_commit' ? 'commit' : requiredDepth === 'auto_push' ? 'push' : 'pull-request', live => depthAtLeast(live.automationConfig.depth, requiredDepth));
134
208
  const githubConfigMatchesIntent = (githubConfig, intent) => (githubConfig.host.toLowerCase() === intent.repoHost.toLowerCase()
135
209
  && githubConfig.repo_owner.toLowerCase() === intent.repoOwner.toLowerCase()
136
210
  && githubConfig.repo_name.toLowerCase() === intent.repoName.toLowerCase()
@@ -140,17 +214,7 @@ const startStage = ({ projectId, bundleId, ticketId, ticketTitle, progressionRun
140
214
  if (!progression || progression.id !== progressionRunId) {
141
215
  throw new Error('Automation stage has no active progression owner');
142
216
  }
143
- const activeChild = db.prepare(`
144
- SELECT id
145
- FROM execution_runs
146
- WHERE project_id = ? AND parent_run_id = ? AND id <> ?
147
- AND kind IN ('review', 'automation_review_fix', 'automation_commit', 'automation_push', 'automation_pull_request')
148
- AND status IN ('queued', 'running', 'waiting')
149
- LIMIT 1
150
- `).get(projectId, progressionRunId, allowedActiveChildRunId ?? '');
151
- if (activeChild) {
152
- throw new Error('Automation progression already has an active child');
153
- }
217
+ assertNoActiveAutomationChild(projectId, progressionRunId, allowedActiveChildRunId);
154
218
  const progressionTicketLink = db.prepare(`
155
219
  SELECT 1 FROM execution_run_links
156
220
  WHERE execution_run_id = ? AND project_id = ? AND entity_type = 'ticket' AND entity_id = ?
@@ -237,7 +301,7 @@ const finishStage = ({ projectId, bundleId, progressionRunId, runId, stageKind,
237
301
  }
238
302
  }, broadcaster);
239
303
  };
240
- const failStage = ({ projectId, bundleId, progressionRunId, runId, stageKind, pauseCode, reason, ambiguous, broadcaster, }) => withExecutionRunTransaction(transaction => {
304
+ const failStage = ({ projectId, bundleId, progressionRunId, runId, stageKind, pauseCode, reason, ambiguous, reasonCode, pausedStageRunId = runId, broadcaster, }) => withExecutionRunTransaction(transaction => {
241
305
  const existing = getExecutionRun(projectId, runId);
242
306
  if (!existing || !['queued', 'running', 'waiting'].includes(existing.status)) {
243
307
  return;
@@ -247,7 +311,7 @@ const failStage = ({ projectId, bundleId, progressionRunId, runId, stageKind, pa
247
311
  runId,
248
312
  status: 'failed',
249
313
  domainStatus: ambiguous ? 'ambiguous' : 'failed',
250
- failure: { code: ambiguous ? 'side_effect_ambiguous' : 'stage_failed', message: reason },
314
+ failure: { code: reasonCode ?? (ambiguous ? 'side_effect_ambiguous' : 'stage_failed'), message: reason },
251
315
  eventType: `${stageKind}_failed`,
252
316
  });
253
317
  if (!ambiguous) {
@@ -261,11 +325,23 @@ const failStage = ({ projectId, bundleId, progressionRunId, runId, stageKind, pa
261
325
  progressionRunId,
262
326
  pauseCode,
263
327
  pauseReason: reason,
264
- reasonCode: ambiguous ? 'side_effect_ambiguous' : 'stage_failed',
265
- pausedStageRunId: runId,
328
+ reasonCode: reasonCode ?? (ambiguous ? 'side_effect_ambiguous' : 'stage_failed'),
329
+ pausedStageRunId,
266
330
  });
267
331
  }
268
332
  }, broadcaster);
333
+ const failStageAuthorization = ({ projectId, bundleId, progressionRunId, runId, stageKind, error, broadcaster, }) => failStage({
334
+ projectId,
335
+ bundleId,
336
+ progressionRunId,
337
+ runId,
338
+ stageKind,
339
+ pauseCode: 'required_checks',
340
+ reason: `Required-check authorization expired before ${stageKind.replace('automation_', '').replaceAll('_', ' ')}`,
341
+ reasonCode: error.reasonCode,
342
+ pausedStageRunId: null,
343
+ broadcaster,
344
+ });
269
345
  const pauseBeforeStage = ({ projectId, bundleId, progressionRunId, pauseCode, reason, ambiguous, broadcaster, }) => waitAutomationProgression({
270
346
  projectId,
271
347
  bundleId,
@@ -276,6 +352,181 @@ const pauseBeforeStage = ({ projectId, bundleId, progressionRunId, pauseCode, re
276
352
  pausedStageRunId: null,
277
353
  broadcaster,
278
354
  });
355
+ export const runAutomationCommitOperation = async ({ ticket, io, progressionRunId, allowedActiveChildRunId, retryOfRunId, mutationLabel, canCommitLive, shouldCompleteProgression, failureMode = 'return', requiredProgressionPurpose, }) => {
356
+ if (!ticket.project_id || !ticket.worktree_bundle_id) {
357
+ return null;
358
+ }
359
+ const projectId = ticket.project_id;
360
+ const bundleId = ticket.worktree_bundle_id;
361
+ const progression = getAutomationProgression(projectId, bundleId);
362
+ if (!progression || progression.id !== progressionRunId) {
363
+ return null;
364
+ }
365
+ const broadcaster = asBroadcaster(io);
366
+ let live = loadLiveState(projectId, bundleId);
367
+ if (!canCommitLive(live, { ticket, progressionRunId })) {
368
+ completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
369
+ emitUpdated(io, ticket.id);
370
+ return null;
371
+ }
372
+ const branch = live.bundle.branch;
373
+ const cwd = live.bundle.kind === 'project_root'
374
+ ? live.project.repo_path
375
+ : resolveWorktreePath(branch, live.project);
376
+ let commitRunId = null;
377
+ try {
378
+ const status = getGitStatus(cwd, branch, live.project);
379
+ const precondition = evaluateCommitPrecondition(status);
380
+ if (!precondition.ok) {
381
+ throw new Error(precondition.reason);
382
+ }
383
+ if (status.files.length === 0) {
384
+ const authorization = await authorizeAutomationBoundary({
385
+ project: live.project,
386
+ bundle: live.bundle,
387
+ progressionRunId,
388
+ cwd,
389
+ boundaryKind: 'automation_commit',
390
+ requiredProgressionPurpose,
391
+ broadcaster,
392
+ prepare: () => {
393
+ const head = getGitHeadIdentity(cwd);
394
+ return { head, indexTreeSha: getGitIndexTreeSha(cwd) };
395
+ },
396
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
397
+ kind: 'workspace', fingerprint: workspaceIdentity.fingerprint,
398
+ }),
399
+ });
400
+ const { head, indexTreeSha } = authorization.value;
401
+ const intent = {
402
+ branch,
403
+ preActionHeadSha: head.headSha,
404
+ preActionIndexTreeSha: indexTreeSha,
405
+ intendedTreeSha: head.treeSha,
406
+ commitMessageDigest: EMPTY_MESSAGE_DIGEST,
407
+ selectedContentDigest: computeGitTreeDeltaDigest(cwd, head.headSha, head.treeSha),
408
+ checkAuthorization: toStageCheckAuthorization(authorization),
409
+ };
410
+ commitRunId = startStage({
411
+ projectId,
412
+ bundleId,
413
+ ticketId: ticket.id,
414
+ ticketTitle: ticket.title,
415
+ progressionRunId,
416
+ allowedActiveChildRunId,
417
+ stageKind: 'automation_commit',
418
+ intent,
419
+ broadcaster,
420
+ retryOfRunId,
421
+ });
422
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_commit' });
423
+ live = loadLiveState(projectId, bundleId);
424
+ const result = {
425
+ outcome: 'no_changes',
426
+ headSha: head.headSha,
427
+ treeSha: head.treeSha,
428
+ };
429
+ finishStage({
430
+ projectId,
431
+ bundleId,
432
+ progressionRunId,
433
+ runId: commitRunId,
434
+ stageKind: 'automation_commit',
435
+ result,
436
+ outcome: 'no_changes',
437
+ completeProgression: shouldCompleteProgression(live, result),
438
+ broadcaster,
439
+ });
440
+ return { runId: commitRunId, ...result };
441
+ }
442
+ const selectedPaths = getAutomationCommitPaths(status.files);
443
+ const message = await generateCommitMessage(cwd, live.project, selectedPaths);
444
+ live = loadLiveState(projectId, bundleId);
445
+ if (!message) {
446
+ throw new Error('commit message generation produced an empty result');
447
+ }
448
+ if (!canCommitLive(live, { ticket, progressionRunId })) {
449
+ completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
450
+ emitUpdated(io, ticket.id);
451
+ return null;
452
+ }
453
+ const authorization = await authorizeAutomationBoundary({
454
+ project: live.project,
455
+ bundle: live.bundle,
456
+ progressionRunId,
457
+ cwd,
458
+ boundaryKind: 'automation_commit',
459
+ requiredProgressionPurpose,
460
+ broadcaster,
461
+ prepare: () => prepareAutomationCommit(cwd, branch, live.project, selectedPaths),
462
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
463
+ kind: 'workspace', fingerprint: workspaceIdentity.fingerprint,
464
+ }),
465
+ });
466
+ const prepared = authorization.value;
467
+ const intent = {
468
+ branch,
469
+ preActionHeadSha: prepared.preActionHeadSha,
470
+ preActionIndexTreeSha: prepared.preActionIndexTreeSha,
471
+ intendedTreeSha: prepared.intendedTreeSha,
472
+ commitMessageDigest: digestCommitMessage(message),
473
+ selectedContentDigest: prepared.selectedContentDigest,
474
+ checkAuthorization: toStageCheckAuthorization(authorization),
475
+ };
476
+ commitRunId = startStage({
477
+ projectId,
478
+ bundleId,
479
+ ticketId: ticket.id,
480
+ ticketTitle: ticket.title,
481
+ progressionRunId,
482
+ allowedActiveChildRunId,
483
+ stageKind: 'automation_commit',
484
+ intent,
485
+ broadcaster,
486
+ retryOfRunId,
487
+ });
488
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_commit' });
489
+ live = assertStageCanMutateWithPolicy(projectId, bundleId, progressionRunId, ticket, mutationLabel, canCommitLive);
490
+ const result = await commitPreparedAutomationChanges(cwd, branch, live.project, message, prepared);
491
+ live = loadLiveState(projectId, bundleId);
492
+ finishStage({
493
+ projectId,
494
+ bundleId,
495
+ progressionRunId,
496
+ runId: commitRunId,
497
+ stageKind: 'automation_commit',
498
+ result,
499
+ outcome: 'committed',
500
+ completeProgression: shouldCompleteProgression(live, result),
501
+ broadcaster,
502
+ });
503
+ return { outcome: 'committed', runId: commitRunId, ...result };
504
+ }
505
+ catch (error) {
506
+ if (error instanceof AutomationCheckGateError) {
507
+ if (commitRunId) {
508
+ failStageAuthorization({ projectId, bundleId, progressionRunId, runId: commitRunId, stageKind: 'automation_commit', error, broadcaster });
509
+ }
510
+ emitBundleUpdated(io, ticket);
511
+ if (failureMode === 'throw') {
512
+ throw error;
513
+ }
514
+ return null;
515
+ }
516
+ const reason = `commit failed: ${getErrorMessage(error)}`;
517
+ if (commitRunId) {
518
+ failStage({ projectId, bundleId, progressionRunId, runId: commitRunId, stageKind: 'automation_commit', pauseCode: 'commit', reason, ambiguous: true, broadcaster });
519
+ }
520
+ else {
521
+ pauseBeforeStage({ projectId, bundleId, progressionRunId, pauseCode: 'commit', reason, broadcaster });
522
+ }
523
+ emitUpdated(io, ticket.id);
524
+ if (failureMode === 'throw') {
525
+ throw error;
526
+ }
527
+ return null;
528
+ }
529
+ };
279
530
  const remoteStatesEqual = (left, right) => (left.state === right.state && (left.state === 'absent' || (right.state === 'observed' && left.sha === right.sha)));
280
531
  export const runAutomationCommitChain = async (ticket, _project, _bundle, _automationConfig, io, progressionRunId, resumeFrom, triggeringReviewRunId) => {
281
532
  if (!ticket.project_id || !ticket.worktree_bundle_id) {
@@ -296,97 +547,30 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
296
547
  || (resumeFrom.stageKind === 'automation_push' && !isRetry)));
297
548
  let expectedHeadSha = resumeFrom?.expectedHeadSha ?? null;
298
549
  let live = loadLiveState(projectId, bundleId);
299
- if (!depthAtLeast(live.automationConfig.depth, 'auto_commit')) {
300
- completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
301
- emitUpdated(io, ticket.id);
302
- return;
303
- }
304
550
  const branch = live.bundle.branch;
305
551
  const cwd = live.bundle.kind === 'project_root'
306
552
  ? live.project.repo_path
307
553
  : resolveWorktreePath(branch, live.project);
308
- let commitRunId = null;
309
554
  if (!skipCommit) {
310
- try {
311
- const status = getGitStatus(cwd, branch, live.project);
312
- const precondition = evaluateCommitPrecondition(status);
313
- if (!precondition.ok) {
314
- throw new Error(precondition.reason);
315
- }
316
- if (status.files.length === 0) {
317
- const head = getGitHeadIdentity(cwd);
318
- const intent = {
319
- branch,
320
- preActionHeadSha: head.headSha,
321
- preActionIndexTreeSha: getGitIndexTreeSha(cwd),
322
- intendedTreeSha: head.treeSha,
323
- commitMessageDigest: EMPTY_MESSAGE_DIGEST,
324
- selectedContentDigest: computeGitTreeDeltaDigest(cwd, head.headSha, head.treeSha),
325
- };
326
- commitRunId = startStage({ projectId, bundleId, ticketId: ticket.id, ticketTitle: ticket.title, progressionRunId, allowedActiveChildRunId: triggeringReviewRunId, stageKind: 'automation_commit', intent, broadcaster, retryOfRunId: resumeFrom?.stageKind === 'automation_commit' ? resumeFrom.retryOfRunId : undefined });
327
- live = loadLiveState(projectId, bundleId);
328
- finishStage({
329
- projectId,
330
- bundleId,
331
- progressionRunId,
332
- runId: commitRunId,
333
- stageKind: 'automation_commit',
334
- result: { outcome: 'no_changes', headSha: head.headSha, treeSha: head.treeSha },
335
- outcome: 'no_changes',
336
- completeProgression: !depthAtLeast(live.automationConfig.depth, 'auto_push'),
337
- broadcaster,
338
- });
339
- expectedHeadSha = head.headSha;
340
- }
341
- else {
342
- const selectedPaths = getAutomationCommitPaths(status.files);
343
- const message = await generateCommitMessage(cwd, live.project, selectedPaths);
344
- live = loadLiveState(projectId, bundleId);
345
- if (!message) {
346
- throw new Error('commit message generation produced an empty result');
347
- }
348
- if (!depthAtLeast(live.automationConfig.depth, 'auto_commit')) {
349
- completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
350
- emitUpdated(io, ticket.id);
351
- return;
352
- }
353
- const prepared = prepareAutomationCommit(cwd, branch, live.project, selectedPaths);
354
- const intent = {
355
- branch,
356
- preActionHeadSha: prepared.preActionHeadSha,
357
- preActionIndexTreeSha: prepared.preActionIndexTreeSha,
358
- intendedTreeSha: prepared.intendedTreeSha,
359
- commitMessageDigest: digestCommitMessage(message),
360
- selectedContentDigest: prepared.selectedContentDigest,
361
- };
362
- commitRunId = startStage({ projectId, bundleId, ticketId: ticket.id, ticketTitle: ticket.title, progressionRunId, allowedActiveChildRunId: triggeringReviewRunId, stageKind: 'automation_commit', intent, broadcaster, retryOfRunId: resumeFrom?.stageKind === 'automation_commit' ? resumeFrom.retryOfRunId : undefined });
363
- const result = await commitPreparedAutomationChanges(cwd, branch, live.project, message, prepared);
364
- live = loadLiveState(projectId, bundleId);
365
- finishStage({
366
- projectId,
367
- bundleId,
368
- progressionRunId,
369
- runId: commitRunId,
370
- stageKind: 'automation_commit',
371
- result,
372
- outcome: 'committed',
373
- completeProgression: !depthAtLeast(live.automationConfig.depth, 'auto_push'),
374
- broadcaster,
375
- });
376
- expectedHeadSha = result.commitSha;
377
- }
378
- }
379
- catch (error) {
380
- const reason = `commit failed: ${getErrorMessage(error)}`;
381
- if (commitRunId) {
382
- failStage({ projectId, bundleId, progressionRunId, runId: commitRunId, stageKind: 'automation_commit', pauseCode: 'commit', reason, ambiguous: true, broadcaster });
383
- }
384
- else {
385
- pauseBeforeStage({ projectId, bundleId, progressionRunId, pauseCode: 'commit', reason, broadcaster });
386
- }
387
- emitUpdated(io, ticket.id);
555
+ const commitResult = await runAutomationCommitOperation({
556
+ ticket,
557
+ io,
558
+ progressionRunId,
559
+ allowedActiveChildRunId: triggeringReviewRunId,
560
+ retryOfRunId: resumeFrom?.stageKind === 'automation_commit' ? resumeFrom.retryOfRunId : undefined,
561
+ mutationLabel: 'commit',
562
+ canCommitLive: current => depthAtLeast(current.automationConfig.depth, 'auto_commit'),
563
+ shouldCompleteProgression: current => !depthAtLeast(current.automationConfig.depth, 'auto_push'),
564
+ });
565
+ if (!commitResult) {
388
566
  return;
389
567
  }
568
+ if (commitResult.outcome === 'committed') {
569
+ expectedHeadSha = commitResult.commitSha;
570
+ }
571
+ else {
572
+ expectedHeadSha = commitResult.headSha;
573
+ }
390
574
  }
391
575
  live = loadLiveState(projectId, bundleId);
392
576
  if (getAutomationProgression(projectId, bundleId)?.id !== progressionRunId) {
@@ -416,16 +600,45 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
416
600
  if (getGitHeadIdentity(cwd).headSha !== target) {
417
601
  throw new Error('HEAD changed while the push stage was being prepared');
418
602
  }
419
- const intent = { branch, originIdentity, targetLocalSha: target, preActionRemote };
603
+ const authorization = await authorizeAutomationBoundary({
604
+ project: live.project,
605
+ bundle: live.bundle,
606
+ progressionRunId,
607
+ cwd,
608
+ boundaryKind: 'automation_push',
609
+ broadcaster,
610
+ prepare: () => {
611
+ const head = getGitHeadIdentity(cwd);
612
+ if (head.headSha !== target) {
613
+ throw new Error('HEAD changed while required checks were evaluated for push');
614
+ }
615
+ return head;
616
+ },
617
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
618
+ kind: 'commit', commitSha: target, workspaceFingerprint: workspaceIdentity.fingerprint,
619
+ }),
620
+ });
621
+ const intent = {
622
+ branch,
623
+ originIdentity,
624
+ targetLocalSha: target,
625
+ preActionRemote,
626
+ checkAuthorization: toStageCheckAuthorization(authorization),
627
+ intendedTreeSha: authorization.value.treeSha,
628
+ resultingCommitSha: target,
629
+ };
420
630
  pushRunId = startStage({ projectId, bundleId, ticketId: ticket.id, ticketTitle: ticket.title, progressionRunId, allowedActiveChildRunId: triggeringReviewRunId, stageKind: 'automation_push', intent, broadcaster, retryOfRunId: resumeFrom?.stageKind === 'automation_push' ? resumeFrom.retryOfRunId : undefined });
421
631
  const current = await fetchRemoteBranchState(cwd, branch, originIdentity);
422
632
  live = loadLiveState(projectId, bundleId);
423
633
  if (current.state === 'observed' && current.sha === target) {
634
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_push' });
635
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_push');
424
636
  finishStage({ projectId, bundleId, progressionRunId, runId: pushRunId, stageKind: 'automation_push', result: { remoteSha: target }, outcome: 'already_current', completeProgression: !depthAtLeast(live.automationConfig.depth, 'auto_pr'), broadcaster });
425
637
  expectedHeadSha = target;
426
638
  }
427
639
  else if (remoteStatesEqual(current, preActionRemote)) {
428
- live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_push');
640
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_push' });
641
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_push');
429
642
  await pushAutomationTarget(cwd, branch, target, originIdentity);
430
643
  live = loadLiveState(projectId, bundleId);
431
644
  const observed = await fetchRemoteBranchState(cwd, branch, originIdentity);
@@ -441,6 +654,13 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
441
654
  }
442
655
  }
443
656
  catch (error) {
657
+ if (error instanceof AutomationCheckGateError) {
658
+ if (pushRunId) {
659
+ failStageAuthorization({ projectId, bundleId, progressionRunId, runId: pushRunId, stageKind: 'automation_push', error, broadcaster });
660
+ }
661
+ emitBundleUpdated(io, ticket);
662
+ return;
663
+ }
444
664
  const reason = `push ambiguous: ${getErrorMessage(error)}`;
445
665
  if (pushRunId) {
446
666
  failStage({ projectId, bundleId, progressionRunId, runId: pushRunId, stageKind: 'automation_push', pauseCode: 'push', reason, ambiguous: true, broadcaster });
@@ -477,6 +697,24 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
477
697
  throw new Error('HEAD no longer matches the exact automation push result');
478
698
  }
479
699
  const headSha = expectedHeadSha;
700
+ const authorization = await authorizeAutomationBoundary({
701
+ project: live.project,
702
+ bundle: live.bundle,
703
+ progressionRunId,
704
+ cwd,
705
+ boundaryKind: 'automation_pull_request',
706
+ broadcaster,
707
+ prepare: () => {
708
+ const head = getGitHeadIdentity(cwd);
709
+ if (head.headSha !== headSha) {
710
+ throw new Error('HEAD changed while required checks were evaluated for pull request');
711
+ }
712
+ return head;
713
+ },
714
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
715
+ kind: 'commit', commitSha: headSha, workspaceFingerprint: workspaceIdentity.fingerprint,
716
+ }),
717
+ });
480
718
  const intent = {
481
719
  provider: 'github',
482
720
  repoHost: githubConfig.host,
@@ -485,6 +723,9 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
485
723
  baseBranch: githubConfig.default_base_branch,
486
724
  headBranch: branch,
487
725
  headSha,
726
+ checkAuthorization: toStageCheckAuthorization(authorization),
727
+ intendedTreeSha: authorization.value.treeSha,
728
+ resultingCommitSha: headSha,
488
729
  };
489
730
  pullRequestRunId = startStage({ projectId, bundleId, ticketId: ticket.id, ticketTitle: ticket.title, progressionRunId, allowedActiveChildRunId: triggeringReviewRunId, stageKind: 'automation_pull_request', intent, broadcaster, retryOfRunId: resumeFrom?.stageKind === 'automation_pull_request' ? resumeFrom.retryOfRunId : undefined });
490
731
  let observation = await observeGitHubPullRequest(githubConfig, intent);
@@ -492,6 +733,8 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
492
733
  let pullRequest;
493
734
  let outcome;
494
735
  if (observation.outcome === 'exact') {
736
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_pull_request' });
737
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
495
738
  pullRequest = observation.pullRequest;
496
739
  outcome = 'already_exists';
497
740
  }
@@ -502,7 +745,8 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
502
745
  if (!currentConfig || !githubConfigMatchesIntent(currentConfig, intent)) {
503
746
  throw new Error('GitHub configuration changed while the pull-request stage was active');
504
747
  }
505
- live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_pr');
748
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
749
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_pull_request' });
506
750
  if (getGitHeadIdentity(cwd).headSha !== intent.headSha) {
507
751
  throw new Error('HEAD changed while the pull-request stage was active');
508
752
  }
@@ -514,10 +758,12 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
514
758
  if (!mutationConfig || !githubConfigMatchesIntent(mutationConfig, intent)) {
515
759
  throw new Error('GitHub configuration changed while the pull-request stage was active');
516
760
  }
517
- live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_pr');
761
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
518
762
  if (getGitHeadIdentity(cwd).headSha !== intent.headSha) {
519
763
  throw new Error('HEAD changed before pull-request creation');
520
764
  }
765
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_pull_request' });
766
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
521
767
  pullRequest = await createGitHubPullRequest(mutationConfig, {
522
768
  title: content.title,
523
769
  body: content.body,
@@ -557,6 +803,13 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
557
803
  });
558
804
  }
559
805
  catch (error) {
806
+ if (error instanceof AutomationCheckGateError) {
807
+ if (pullRequestRunId) {
808
+ failStageAuthorization({ projectId, bundleId, progressionRunId, runId: pullRequestRunId, stageKind: 'automation_pull_request', error, broadcaster });
809
+ }
810
+ emitBundleUpdated(io, ticket);
811
+ return;
812
+ }
560
813
  const reason = `pull request ambiguous: ${getErrorMessage(error)}`;
561
814
  if (pullRequestRunId) {
562
815
  failStage({ projectId, bundleId, progressionRunId, runId: pullRequestRunId, stageKind: 'automation_pull_request', pauseCode: 'pull_request', reason, ambiguous: true, broadcaster });