@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.
- package/dist/client/assets/ActivityView-CDLQOXZm.js +1 -0
- package/dist/client/assets/GitHubActionsDialog-DYxLX2HS.js +1 -0
- package/dist/client/assets/VerificationDialog-DEKvZG75.js +4 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-DVbmGv0b.js → architectureDiagram-3BPJPVTR-ByNXcCIO.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-BsxPuJKK.js → blockDiagram-GPEHLZMM-ByykLAaG.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-9Jjpu2R1.js → c4Diagram-AAUBKEIU-CmWA4Pli.js} +1 -1
- package/dist/client/assets/channel-BvcRv90i.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Dzljq_5a.js → chunk-2J33WTMH-CTgbXRh4.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-CiBHaLdV.js → chunk-3OPIFGDE-D3FajVfq.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-DZFrI-_H.js → chunk-5ZQYHXKU-CUMg_Vfo.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-u519MeJ7.js → chunk-727SXJPM-Oc9MgxZD.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DC91Nw9K.js → chunk-AQP2D5EJ-5JZboHeP.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-CR7cwH-D.js → chunk-CSCIHK7Q-BpkSMHjB.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-DJwQEfa_.js → chunk-KSCS5N6A-BPBMAswO.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-w6TWSOTT.js → chunk-L5ZTLDWV-BA2sHmu7.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-Dfw_jEjR.js → chunk-LZXEDZCA-Blc9q-fQ.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-DHdJgCA7.js → chunk-ND2GUHAM-GlVS74JP.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-GvOuUiUy.js → chunk-NZK2D7GU-fctBFnAm.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O--r1VQgDc.js → chunk-O5CBEL6O-eGs4DDLJ.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-Xgc6shf5.js → chunk-WU5MYG2G-vxa50E7g.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-IuovJyxl.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BBJ3avX6.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-IcfTsQ6f.js → dagre-BM42HDAG-DXGXNVUp.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-D8Z4_NSQ.js → diagram-2AECGRRQ-BRYr1GvB.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-CIjC8QzH.js → diagram-5GNKFQAL-BcwgMag4.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-BBbYFygn.js → diagram-KO2AKTUF-Delk7lbr.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-lZstJdXv.js → diagram-LMA3HP47-BVtc_fjo.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-Cnqij60i.js → diagram-OG6HWLK6-weIPvUz5.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-1DAJwSbR.js → erDiagram-TEJ5UH35-DKde70dA.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-DIQkR95e.js → flowDiagram-I6XJVG4X-B7Jrk-6c.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-mDGbRM53.js → ganttDiagram-6RSMTGT7-CDzWDgnA.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BQirGZuE.js → gitGraphDiagram-PVQCEYII-DYZCryuN.js} +1 -1
- package/dist/client/assets/index-BlKOx9Vf.css +2 -0
- package/dist/client/assets/index-CmaBDvIB.js +273 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-CljoXByt.js → infoDiagram-5YYISTIA-DmQOgVKK.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-ouY7AiSo.js → ishikawaDiagram-YF4QCWOH-DCpu-q-Z.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-BiI9xI0r.js → journeyDiagram-JHISSGLW-DhBoglhI.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-CvuW7aYT.js → kanban-definition-UN3LZRKU-BSjvGFTY.js} +1 -1
- package/dist/client/assets/{line-CoE97hT8.js → line-ClKBZwDr.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-BHswQsmF.js → mermaid-parser.core-BRA4mJ40.js} +1 -1
- package/dist/client/assets/{mermaid.core-a9PzZlL1.js → mermaid.core-BZ7kSfEt.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-CIkEQqLV.js → mindmap-definition-RKZ34NQL-DoUdLHgj.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-BIvcv2M5.js → pieDiagram-4H26LBE5-NVnzqltb.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BWCVll7J.js → quadrantDiagram-W4KKPZXB-DOITgkan.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-w5l0CZn2.js → requirementDiagram-4Y6WPE33-D0VPxrFE.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BuKReL9w.js → sankeyDiagram-5OEKKPKP-Bx0uU7xV.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Br5KpukA.js → sequenceDiagram-3UESZ5HK-B4lFL6cO.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-BfFCsPCI.js → stateDiagram-AJRCARHV-CsLVsMZ4.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CvrvaIdF.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-BrDP2iO0.js → timeline-definition-PNZ67QCA-BkcYpPFI.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-C7lGYnr9.js → vennDiagram-CIIHVFJN-Xc5f2sRQ.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BRKJRuEF.js → wardleyDiagram-YWT4CUSO-n3Dpk02Q.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-BjaB0hQ8.js → xychartDiagram-2RQKCTM6-BTMzF8zg.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +460 -2
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +542 -30
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/activeAutomationChild.js +31 -0
- package/dist/server/lib/activeAutomationChild.js.map +1 -0
- package/dist/server/lib/automationCheckGate.js +341 -0
- package/dist/server/lib/automationCheckGate.js.map +1 -0
- package/dist/server/lib/automationProgression.js +20 -21
- package/dist/server/lib/automationProgression.js.map +1 -1
- package/dist/server/lib/automationReconciliation.js +35 -2
- package/dist/server/lib/automationReconciliation.js.map +1 -1
- package/dist/server/lib/automationResumeDecision.js +61 -1
- package/dist/server/lib/automationResumeDecision.js.map +1 -1
- package/dist/server/lib/automationStageDetails.js +266 -41
- package/dist/server/lib/automationStageDetails.js.map +1 -1
- package/dist/server/lib/buildCompletionContinuation.js +109 -0
- package/dist/server/lib/buildCompletionContinuation.js.map +1 -0
- package/dist/server/lib/buildSequences.js +81 -0
- package/dist/server/lib/buildSequences.js.map +1 -1
- package/dist/server/lib/buildTicketCommit.js +219 -0
- package/dist/server/lib/buildTicketCommit.js.map +1 -0
- package/dist/server/lib/bundleAutomationReadiness.js +7 -0
- package/dist/server/lib/bundleAutomationReadiness.js.map +1 -0
- package/dist/server/lib/bundleReviewOperations.js +17 -5
- package/dist/server/lib/bundleReviewOperations.js.map +1 -1
- package/dist/server/lib/chatOperations.js +2 -2
- package/dist/server/lib/chatOperations.js.map +1 -1
- package/dist/server/lib/checkDefinitions.js +278 -0
- package/dist/server/lib/checkDefinitions.js.map +1 -0
- package/dist/server/lib/checkGateEvaluations.js +295 -0
- package/dist/server/lib/checkGateEvaluations.js.map +1 -0
- package/dist/server/lib/checkRunner.js +674 -0
- package/dist/server/lib/checkRunner.js.map +1 -0
- package/dist/server/lib/checkRuns.js +736 -0
- package/dist/server/lib/checkRuns.js.map +1 -0
- package/dist/server/lib/checkWorkspaceIdentity.js +207 -0
- package/dist/server/lib/checkWorkspaceIdentity.js.map +1 -0
- package/dist/server/lib/executionReconciliation.js +79 -2
- package/dist/server/lib/executionReconciliation.js.map +1 -1
- package/dist/server/lib/executionRetry.js +14 -6
- package/dist/server/lib/executionRetry.js.map +1 -1
- package/dist/server/lib/executionRetryContext.js +3 -3
- package/dist/server/lib/executionRetryContext.js.map +1 -1
- package/dist/server/lib/executionRunRetention.js +41 -3
- package/dist/server/lib/executionRunRetention.js.map +1 -1
- package/dist/server/lib/executionRuns.js +135 -2
- package/dist/server/lib/executionRuns.js.map +1 -1
- package/dist/server/lib/executionTimeline.js +52 -1
- package/dist/server/lib/executionTimeline.js.map +1 -1
- package/dist/server/lib/projectAutoConfig.js +47 -65
- package/dist/server/lib/projectAutoConfig.js.map +1 -1
- package/dist/server/lib/projects.js +99 -7
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/requiredCheckPolicy.js +93 -0
- package/dist/server/lib/requiredCheckPolicy.js.map +1 -0
- package/dist/server/lib/shutdown.js +4 -4
- package/dist/server/lib/shutdown.js.map +1 -1
- package/dist/server/lib/testCommandRunner.js +3 -2
- package/dist/server/lib/testCommandRunner.js.map +1 -1
- package/dist/server/lib/testSessions.js +2 -6
- package/dist/server/lib/testSessions.js.map +1 -1
- package/dist/server/lib/ticketAutomationCommit.js +361 -108
- package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
- package/dist/server/lib/tickets.js +21 -1
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/lib/types.js +2 -0
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/build.js +50 -81
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/chat.js +13 -6
- package/dist/server/workers/chat.js.map +1 -1
- package/dist/server/workers/followUp.js +53 -30
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/review.js +2 -10
- package/dist/server/workers/review.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/ActivityView-Btpsv-5U.js +0 -1
- package/dist/client/assets/GitHubActionsDialog-DRlIMgSB.js +0 -1
- package/dist/client/assets/TestsDialog-DSWDkKPV.js +0 -4
- package/dist/client/assets/channel-Lic13vb5.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DACLwQIJ.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CKi6c1d9.js +0 -1
- package/dist/client/assets/index-DQt4dcwc.css +0 -2
- package/dist/client/assets/index-W9OCqkMU.js +0 -273
- 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 {
|
|
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
|
|
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
|
-
|| !
|
|
130
|
-
throw new Error(`automation no longer permits ${
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
|
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
|
-
|
|
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 });
|