patchrelay 0.75.2 → 0.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-input-service.js +40 -26
- package/dist/build-info.json +3 -3
- package/dist/cli/data.js +3 -1
- package/dist/db/issue-session-store.js +44 -9
- package/dist/db/issue-store.js +31 -2
- package/dist/db/migrations.js +3 -0
- package/dist/factory-state.js +23 -0
- package/dist/github-webhook-reactive-run.js +15 -11
- package/dist/github-webhook-stack-coordination.js +8 -4
- package/dist/github-webhook-state-projector.js +204 -139
- package/dist/github-webhook-terminal-handler.js +37 -27
- package/dist/idle-reconciliation.js +122 -66
- package/dist/implementation-outcome-policy.js +5 -1
- package/dist/interrupted-run-recovery.js +46 -33
- package/dist/issue-session-projection-invalidator.js +9 -0
- package/dist/linear-agent-session-client.js +16 -8
- package/dist/linear-issue-projection.js +15 -11
- package/dist/linear-status-comment-sync.js +8 -4
- package/dist/linear-workflow-state-sync.js +9 -5
- package/dist/merged-linear-completion-reconciler.js +39 -17
- package/dist/no-pr-completion-check.js +51 -29
- package/dist/orchestration-parent-wake.js +15 -8
- package/dist/queue-health-monitor.js +17 -8
- package/dist/reactive-run-policy.js +5 -1
- package/dist/run-finalizer.js +61 -29
- package/dist/run-launcher.js +42 -12
- package/dist/run-notification-handler.js +19 -7
- package/dist/run-orchestrator.js +121 -18
- package/dist/run-reconciler.js +121 -50
- package/dist/run-recovery-service.js +70 -33
- package/dist/run-wake-planner.js +39 -29
- package/dist/service-issue-actions.js +45 -28
- package/dist/service-startup-recovery.js +61 -35
- package/dist/telemetry.js +9 -0
- package/dist/terminal-wake-reconciler.js +20 -3
- package/dist/webhooks/agent-session-handler.js +22 -12
- package/dist/webhooks/dependency-readiness-handler.js +17 -10
- package/dist/webhooks/desired-stage-recorder.js +32 -13
- package/dist/webhooks/issue-removal-handler.js +24 -13
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import { canClearFailureProvenance, deriveImmediatePrCheckStatus, getGateCheckNa
|
|
|
5
5
|
import { buildGitHubQueueFailureContext, resolveGitHubBranchFailureContext, } from "./github-webhook-failure-context.js";
|
|
6
6
|
import { emitGitHubLinearActivity, syncGitHubLinearSession } from "./github-linear-session-sync.js";
|
|
7
7
|
import { buildQueueRepairContextFromEvent } from "./merge-queue-incident.js";
|
|
8
|
+
const WRITER = "github-webhook-state-projector";
|
|
8
9
|
export async function projectGitHubWebhookState(deps, issue, event, project, linkedBy) {
|
|
9
10
|
const failureContextResolver = deps.failureContextResolver ?? createGitHubFailureContextResolver();
|
|
10
11
|
const ciSnapshotResolver = deps.ciSnapshotResolver ?? createGitHubCiSnapshotResolver();
|
|
@@ -15,26 +16,31 @@ export async function projectGitHubWebhookState(deps, issue, event, project, lin
|
|
|
15
16
|
// the field when a base ref reverts to the default (e.g. parent
|
|
16
17
|
// landed and GitHub auto-retargeted) or when the PR closes.
|
|
17
18
|
const parentPrBranch = computeParentPrBranchUpdate(event, project);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
? {
|
|
32
|
-
: event.
|
|
33
|
-
|
|
19
|
+
// Unconditional commit: every field below is a fact carried by the webhook
|
|
20
|
+
// payload itself, not derived from a prior read of the issue row.
|
|
21
|
+
deps.db.issueSessions.commitIssueState({
|
|
22
|
+
writer: WRITER,
|
|
23
|
+
update: {
|
|
24
|
+
projectId: issue.projectId,
|
|
25
|
+
linearIssueId: issue.linearIssueId,
|
|
26
|
+
...(event.prNumber !== undefined ? { prNumber: event.prNumber } : {}),
|
|
27
|
+
...(event.prUrl !== undefined ? { prUrl: event.prUrl } : {}),
|
|
28
|
+
...(event.prState !== undefined ? { prState: event.prState } : {}),
|
|
29
|
+
...(event.headSha !== undefined ? { prHeadSha: event.headSha } : {}),
|
|
30
|
+
...(event.prAuthorLogin !== undefined ? { prAuthorLogin: event.prAuthorLogin } : {}),
|
|
31
|
+
...(event.reviewState !== undefined ? { prReviewState: event.reviewState } : {}),
|
|
32
|
+
...(immediateCheckStatus !== undefined ? { prCheckStatus: immediateCheckStatus } : {}),
|
|
33
|
+
...(linkedBy === "issue_key" ? { branchName: event.branchName } : {}),
|
|
34
|
+
...(parentPrBranch !== undefined ? { parentPrBranch } : {}),
|
|
35
|
+
...(event.reviewState === "changes_requested"
|
|
36
|
+
? { lastBlockingReviewHeadSha: event.reviewCommitId ?? event.headSha ?? null }
|
|
37
|
+
: event.reviewState === "approved"
|
|
38
|
+
? { lastBlockingReviewHeadSha: null }
|
|
39
|
+
: {}),
|
|
40
|
+
...(event.triggerEvent === "pr_closed"
|
|
41
|
+
? buildClosedPrCleanupFields()
|
|
34
42
|
: {}),
|
|
35
|
-
|
|
36
|
-
? buildClosedPrCleanupFields()
|
|
37
|
-
: {}),
|
|
43
|
+
},
|
|
38
44
|
});
|
|
39
45
|
await updateGitHubCiSnapshot(deps, issue, event, project, ciSnapshotResolver);
|
|
40
46
|
await updateGitHubFailureProvenance(deps, issue, event, project, failureContextResolver);
|
|
@@ -51,66 +57,96 @@ export async function projectGitHubWebhookState(deps, issue, event, project, lin
|
|
|
51
57
|
}
|
|
52
58
|
: undefined);
|
|
53
59
|
if (newState && newState !== afterMetadata.factoryState) {
|
|
54
|
-
deps.db.issueSessions.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
newState,
|
|
81
|
-
event,
|
|
82
|
-
});
|
|
83
|
-
void syncGitHubLinearSession({
|
|
84
|
-
config: deps.config,
|
|
85
|
-
linearProvider: deps.linearProvider,
|
|
86
|
-
logger: deps.logger,
|
|
87
|
-
issue: transitionedIssue,
|
|
60
|
+
const transitionCommit = deps.db.issueSessions.commitIssueState({
|
|
61
|
+
writer: WRITER,
|
|
62
|
+
expectedVersion: afterMetadata.version,
|
|
63
|
+
update: {
|
|
64
|
+
projectId: issue.projectId,
|
|
65
|
+
linearIssueId: issue.linearIssueId,
|
|
66
|
+
factoryState: newState,
|
|
67
|
+
},
|
|
68
|
+
// Conflict: another writer landed since `afterMetadata` was read.
|
|
69
|
+
// Re-resolve the transition against the fresh row so we never
|
|
70
|
+
// regress a state someone else just advanced.
|
|
71
|
+
onConflict: (current) => {
|
|
72
|
+
const recomputed = resolveGitHubFactoryStateForEvent(current, event, project, activeRun
|
|
73
|
+
? {
|
|
74
|
+
...(activeRun.runType ? { runType: activeRun.runType } : {}),
|
|
75
|
+
...(activeRun.sourceHeadSha ? { sourceHeadSha: activeRun.sourceHeadSha } : {}),
|
|
76
|
+
}
|
|
77
|
+
: undefined);
|
|
78
|
+
if (!recomputed || recomputed === current.factoryState)
|
|
79
|
+
return undefined;
|
|
80
|
+
return {
|
|
81
|
+
projectId: issue.projectId,
|
|
82
|
+
linearIssueId: issue.linearIssueId,
|
|
83
|
+
factoryState: recomputed,
|
|
84
|
+
};
|
|
85
|
+
},
|
|
88
86
|
});
|
|
87
|
+
const appliedState = transitionCommit.outcome === "applied"
|
|
88
|
+
? transitionCommit.issue.factoryState
|
|
89
|
+
: undefined;
|
|
90
|
+
if (appliedState) {
|
|
91
|
+
deps.logger.info({ issueKey: issue.issueKey, from: afterMetadata.factoryState, to: appliedState, trigger: event.triggerEvent }, "Factory state transition from GitHub event");
|
|
92
|
+
// Plan §4.4: when the transition fired *because* an approval
|
|
93
|
+
// landed during a review_fix run on the same head (the
|
|
94
|
+
// mid-run-approval rule), the run's premise is gone. Mark it
|
|
95
|
+
// superseded and set the publication-suppression flag so the
|
|
96
|
+
// finalizer cannot push a cosmetic patch-id-equivalent commit.
|
|
97
|
+
maybeSupersedeActiveRun({
|
|
98
|
+
db: deps.db,
|
|
99
|
+
logger: deps.logger,
|
|
100
|
+
feed: deps.feed,
|
|
101
|
+
issue: afterMetadata,
|
|
102
|
+
newState: appliedState,
|
|
103
|
+
event,
|
|
104
|
+
activeRun,
|
|
105
|
+
});
|
|
106
|
+
const transitionedIssue = deps.db.issues.getIssue(issue.projectId, issue.linearIssueId) ?? issue;
|
|
107
|
+
void emitGitHubLinearActivity({
|
|
108
|
+
linearProvider: deps.linearProvider,
|
|
109
|
+
logger: deps.logger,
|
|
110
|
+
feed: deps.feed,
|
|
111
|
+
issue: transitionedIssue,
|
|
112
|
+
newState: appliedState,
|
|
113
|
+
event,
|
|
114
|
+
});
|
|
115
|
+
void syncGitHubLinearSession({
|
|
116
|
+
config: deps.config,
|
|
117
|
+
linearProvider: deps.linearProvider,
|
|
118
|
+
logger: deps.logger,
|
|
119
|
+
issue: transitionedIssue,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
89
122
|
}
|
|
90
123
|
}
|
|
91
124
|
const freshIssue = deps.db.issues.getIssue(issue.projectId, issue.linearIssueId) ?? issue;
|
|
92
125
|
if (event.triggerEvent === "pr_synchronize" && !freshIssue.activeRunId) {
|
|
93
|
-
deps.db.issueSessions.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
deps.db.issueSessions.commitIssueState({
|
|
127
|
+
writer: WRITER,
|
|
128
|
+
update: {
|
|
129
|
+
projectId: issue.projectId,
|
|
130
|
+
linearIssueId: issue.linearIssueId,
|
|
131
|
+
ciRepairAttempts: 0,
|
|
132
|
+
queueRepairAttempts: 0,
|
|
133
|
+
lastGitHubFailureSource: null,
|
|
134
|
+
lastGitHubFailureHeadSha: null,
|
|
135
|
+
lastGitHubFailureSignature: null,
|
|
136
|
+
lastGitHubFailureCheckName: null,
|
|
137
|
+
lastGitHubFailureCheckUrl: null,
|
|
138
|
+
lastGitHubFailureContextJson: null,
|
|
139
|
+
lastGitHubFailureAt: null,
|
|
140
|
+
lastGitHubCiSnapshotHeadSha: event.headSha ?? null,
|
|
141
|
+
lastGitHubCiSnapshotGateCheckName: getPrimaryGateCheckName(project),
|
|
142
|
+
lastGitHubCiSnapshotGateCheckStatus: "pending",
|
|
143
|
+
lastGitHubCiSnapshotJson: null,
|
|
144
|
+
lastGitHubCiSnapshotSettledAt: null,
|
|
145
|
+
lastQueueIncidentJson: null,
|
|
146
|
+
lastAttemptedFailureHeadSha: null,
|
|
147
|
+
lastAttemptedFailureSignature: null,
|
|
148
|
+
lastAttemptedFailureAt: null,
|
|
149
|
+
},
|
|
114
150
|
});
|
|
115
151
|
}
|
|
116
152
|
deps.logger.info({ issueKey: issue.issueKey, branchName: event.branchName, triggerEvent: event.triggerEvent, prNumber: event.prNumber }, "GitHub webhook: updated issue PR state");
|
|
@@ -181,27 +217,33 @@ function maybeSupersedeActiveRun(params) {
|
|
|
181
217
|
}
|
|
182
218
|
async function updateGitHubCiSnapshot(deps, issue, event, project, ciSnapshotResolver) {
|
|
183
219
|
if (event.triggerEvent === "pr_merged") {
|
|
184
|
-
deps.db.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
220
|
+
deps.db.issueSessions.commitIssueState({
|
|
221
|
+
writer: WRITER,
|
|
222
|
+
update: {
|
|
223
|
+
projectId: issue.projectId,
|
|
224
|
+
linearIssueId: issue.linearIssueId,
|
|
225
|
+
lastGitHubCiSnapshotHeadSha: null,
|
|
226
|
+
lastGitHubCiSnapshotGateCheckName: null,
|
|
227
|
+
lastGitHubCiSnapshotGateCheckStatus: null,
|
|
228
|
+
lastGitHubCiSnapshotJson: null,
|
|
229
|
+
lastGitHubCiSnapshotSettledAt: null,
|
|
230
|
+
},
|
|
192
231
|
});
|
|
193
232
|
return;
|
|
194
233
|
}
|
|
195
234
|
if (event.triggerEvent === "pr_synchronize") {
|
|
196
|
-
deps.db.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
235
|
+
deps.db.issueSessions.commitIssueState({
|
|
236
|
+
writer: WRITER,
|
|
237
|
+
update: {
|
|
238
|
+
projectId: issue.projectId,
|
|
239
|
+
linearIssueId: issue.linearIssueId,
|
|
240
|
+
prCheckStatus: "pending",
|
|
241
|
+
lastGitHubCiSnapshotHeadSha: event.headSha ?? null,
|
|
242
|
+
lastGitHubCiSnapshotGateCheckName: getPrimaryGateCheckName(project),
|
|
243
|
+
lastGitHubCiSnapshotGateCheckStatus: "pending",
|
|
244
|
+
lastGitHubCiSnapshotJson: null,
|
|
245
|
+
lastGitHubCiSnapshotSettledAt: null,
|
|
246
|
+
},
|
|
205
247
|
});
|
|
206
248
|
return;
|
|
207
249
|
}
|
|
@@ -216,32 +258,42 @@ async function updateGitHubCiSnapshot(deps, issue, event, project, ciSnapshotRes
|
|
|
216
258
|
if (isStaleGateEvent(issue, event))
|
|
217
259
|
return;
|
|
218
260
|
if (event.triggerEvent === "check_pending") {
|
|
219
|
-
deps.db.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
261
|
+
deps.db.issueSessions.commitIssueState({
|
|
262
|
+
writer: WRITER,
|
|
263
|
+
update: {
|
|
264
|
+
projectId: issue.projectId,
|
|
265
|
+
linearIssueId: issue.linearIssueId,
|
|
266
|
+
prCheckStatus: "pending",
|
|
267
|
+
lastGitHubCiSnapshotHeadSha: event.headSha ?? issue.lastGitHubCiSnapshotHeadSha ?? null,
|
|
268
|
+
lastGitHubCiSnapshotGateCheckName: event.checkName ?? getPrimaryGateCheckName(project),
|
|
269
|
+
lastGitHubCiSnapshotGateCheckStatus: "pending",
|
|
270
|
+
lastGitHubCiSnapshotJson: null,
|
|
271
|
+
lastGitHubCiSnapshotSettledAt: null,
|
|
272
|
+
},
|
|
228
273
|
});
|
|
229
274
|
return;
|
|
230
275
|
}
|
|
276
|
+
// Version read just before the async snapshot resolution: a conflict on the
|
|
277
|
+
// write below means another writer landed while we were calling GitHub.
|
|
278
|
+
const preResolveVersion = (deps.db.issues.getIssue(issue.projectId, issue.linearIssueId) ?? issue).version;
|
|
231
279
|
const snapshot = await ciSnapshotResolver.resolve({
|
|
232
280
|
repoFullName: project?.github?.repoFullName ?? event.repoFullName,
|
|
233
281
|
event,
|
|
234
282
|
gateCheckNames: getGateCheckNames(project),
|
|
235
283
|
});
|
|
236
284
|
if (!snapshot) {
|
|
237
|
-
deps.db.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
285
|
+
deps.db.issueSessions.commitIssueState({
|
|
286
|
+
writer: WRITER,
|
|
287
|
+
expectedVersion: preResolveVersion,
|
|
288
|
+
update: {
|
|
289
|
+
projectId: issue.projectId,
|
|
290
|
+
linearIssueId: issue.linearIssueId,
|
|
291
|
+
lastGitHubCiSnapshotHeadSha: event.headSha ?? issue.lastGitHubCiSnapshotHeadSha ?? null,
|
|
292
|
+
lastGitHubCiSnapshotGateCheckName: getPrimaryGateCheckName(project),
|
|
293
|
+
lastGitHubCiSnapshotGateCheckStatus: "pending",
|
|
294
|
+
lastGitHubCiSnapshotJson: null,
|
|
295
|
+
lastGitHubCiSnapshotSettledAt: null,
|
|
296
|
+
},
|
|
245
297
|
});
|
|
246
298
|
deps.logger.warn({ issueKey: issue.issueKey, repoFullName: project?.github?.repoFullName ?? event.repoFullName, headSha: event.headSha }, "Could not resolve settled CI snapshot; waiting before CI repair");
|
|
247
299
|
deps.feed?.publish({
|
|
@@ -255,15 +307,19 @@ async function updateGitHubCiSnapshot(deps, issue, event, project, ciSnapshotRes
|
|
|
255
307
|
});
|
|
256
308
|
return;
|
|
257
309
|
}
|
|
258
|
-
deps.db.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
310
|
+
deps.db.issueSessions.commitIssueState({
|
|
311
|
+
writer: WRITER,
|
|
312
|
+
expectedVersion: preResolveVersion,
|
|
313
|
+
update: {
|
|
314
|
+
projectId: issue.projectId,
|
|
315
|
+
linearIssueId: issue.linearIssueId,
|
|
316
|
+
prCheckStatus: snapshot.gateCheckStatus,
|
|
317
|
+
lastGitHubCiSnapshotHeadSha: snapshot.headSha,
|
|
318
|
+
lastGitHubCiSnapshotGateCheckName: snapshot.gateCheckName ?? getPrimaryGateCheckName(project),
|
|
319
|
+
lastGitHubCiSnapshotGateCheckStatus: snapshot.gateCheckStatus,
|
|
320
|
+
lastGitHubCiSnapshotJson: JSON.stringify(snapshot),
|
|
321
|
+
lastGitHubCiSnapshotSettledAt: snapshot.settledAt ?? null,
|
|
322
|
+
},
|
|
267
323
|
});
|
|
268
324
|
}
|
|
269
325
|
async function updateGitHubFailureProvenance(deps, issue, event, project, failureContextResolver) {
|
|
@@ -275,6 +331,8 @@ async function updateGitHubFailureProvenance(deps, issue, event, project, failur
|
|
|
275
331
|
if (source === "branch_ci" && !isSettledBranchFailure(deps.db, issue, event, project)) {
|
|
276
332
|
return;
|
|
277
333
|
}
|
|
334
|
+
// Version read before the (possibly async) failure-context resolution.
|
|
335
|
+
const preResolveVersion = (deps.db.issues.getIssue(issue.projectId, issue.linearIssueId) ?? issue).version;
|
|
278
336
|
const failureContext = source === "queue_eviction"
|
|
279
337
|
? buildGitHubQueueFailureContext(event, project, buildQueueRepairContextFromEvent(event))
|
|
280
338
|
: await resolveGitHubBranchFailureContext({
|
|
@@ -284,24 +342,28 @@ async function updateGitHubFailureProvenance(deps, issue, event, project, failur
|
|
|
284
342
|
project,
|
|
285
343
|
failureContextResolver,
|
|
286
344
|
});
|
|
287
|
-
deps.db.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
345
|
+
deps.db.issueSessions.commitIssueState({
|
|
346
|
+
writer: WRITER,
|
|
347
|
+
expectedVersion: preResolveVersion,
|
|
348
|
+
update: {
|
|
349
|
+
projectId: issue.projectId,
|
|
350
|
+
linearIssueId: issue.linearIssueId,
|
|
351
|
+
lastGitHubFailureSource: source,
|
|
352
|
+
lastGitHubFailureHeadSha: failureContext.failureHeadSha ?? event.headSha ?? null,
|
|
353
|
+
lastGitHubFailureSignature: failureContext.failureSignature ?? null,
|
|
354
|
+
lastGitHubFailureCheckName: failureContext.checkName ?? event.checkName ?? null,
|
|
355
|
+
lastGitHubFailureCheckUrl: failureContext.checkUrl ?? event.checkUrl ?? null,
|
|
356
|
+
lastGitHubFailureContextJson: JSON.stringify(failureContext),
|
|
357
|
+
lastGitHubFailureAt: new Date().toISOString(),
|
|
358
|
+
...(source === "queue_eviction"
|
|
359
|
+
? {
|
|
360
|
+
lastQueueSignalAt: new Date().toISOString(),
|
|
361
|
+
lastQueueIncidentJson: JSON.stringify(buildQueueRepairContextFromEvent(event)),
|
|
362
|
+
}
|
|
363
|
+
: {
|
|
364
|
+
lastQueueIncidentJson: null,
|
|
365
|
+
}),
|
|
366
|
+
},
|
|
305
367
|
});
|
|
306
368
|
return;
|
|
307
369
|
}
|
|
@@ -311,10 +373,13 @@ async function updateGitHubFailureProvenance(deps, issue, event, project, failur
|
|
|
311
373
|
if (event.triggerEvent === "check_passed" && !canClearFailureProvenance(issue, event, project)) {
|
|
312
374
|
return;
|
|
313
375
|
}
|
|
314
|
-
deps.db.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
376
|
+
deps.db.issueSessions.commitIssueState({
|
|
377
|
+
writer: WRITER,
|
|
378
|
+
update: {
|
|
379
|
+
projectId: issue.projectId,
|
|
380
|
+
linearIssueId: issue.linearIssueId,
|
|
381
|
+
...CLEARED_FAILURE_PROVENANCE,
|
|
382
|
+
},
|
|
318
383
|
});
|
|
319
384
|
}
|
|
320
385
|
}
|
|
@@ -3,6 +3,7 @@ import { resolvePostMergeFactoryState } from "./post-merge-deploy.js";
|
|
|
3
3
|
import { resolvePreferredCompletedLinearState } from "./linear-workflow.js";
|
|
4
4
|
import { syncGitHubLinearSession } from "./github-linear-session-sync.js";
|
|
5
5
|
import { wakeOrchestrationParentsForChildEvent } from "./orchestration-parent-wake.js";
|
|
6
|
+
const WRITER = "github-webhook-terminal-handler";
|
|
6
7
|
export async function handleGitHubTerminalPrEvent(params) {
|
|
7
8
|
const { db, linearProvider, wakeDispatcher, logger, codex, issue, event, config } = params;
|
|
8
9
|
const eventType = event.triggerEvent === "pr_merged" ? "pr_merged" : "pr_closed";
|
|
@@ -33,32 +34,35 @@ export async function handleGitHubTerminalPrEvent(params) {
|
|
|
33
34
|
logger.warn({ issueKey: issue.issueKey, runId: run.id, error: error instanceof Error ? error.message : String(error) }, "Failed to steer active run after terminal PR event");
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
|
-
const
|
|
37
|
-
if (run) {
|
|
38
|
-
db.runs.finishRun(run.id, {
|
|
39
|
-
status: "released",
|
|
40
|
-
failureReason: event.triggerEvent === "pr_merged"
|
|
41
|
-
? "Pull request merged during active run"
|
|
42
|
-
: "Pull request closed during active run",
|
|
43
|
-
});
|
|
44
|
-
}
|
|
37
|
+
const buildTerminalUpdate = (row) => {
|
|
45
38
|
const terminalFactoryState = event.triggerEvent === "pr_merged"
|
|
46
39
|
? postMergeState
|
|
47
|
-
: resolveClosedPrFactoryState(
|
|
48
|
-
|
|
40
|
+
: resolveClosedPrFactoryState(row);
|
|
41
|
+
return {
|
|
49
42
|
projectId: issue.projectId,
|
|
50
43
|
linearIssueId: issue.linearIssueId,
|
|
51
44
|
activeRunId: null,
|
|
52
45
|
factoryState: terminalFactoryState,
|
|
53
46
|
...(terminalFactoryState === "deploying" ? { deployStartedAt: new Date().toISOString() } : {}),
|
|
54
|
-
}
|
|
47
|
+
};
|
|
55
48
|
};
|
|
56
49
|
const activeLease = db.issueSessions.getActiveIssueSessionLease(issue.projectId, issue.linearIssueId);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
const terminalCommit = db.issueSessions.commitIssueState({
|
|
51
|
+
writer: WRITER,
|
|
52
|
+
expectedVersion: issue.version,
|
|
53
|
+
...(activeLease ? { lease: activeLease } : {}),
|
|
54
|
+
update: buildTerminalUpdate(issue),
|
|
55
|
+
// The terminal PR fact comes from GitHub; re-derive the closed-PR
|
|
56
|
+
// disposition from the fresh row instead of dropping the event.
|
|
57
|
+
onConflict: (current) => buildTerminalUpdate(current),
|
|
58
|
+
});
|
|
59
|
+
if (terminalCommit.outcome === "applied" && run) {
|
|
60
|
+
db.runs.finishRun(run.id, {
|
|
61
|
+
status: "released",
|
|
62
|
+
failureReason: event.triggerEvent === "pr_merged"
|
|
63
|
+
? "Pull request merged during active run"
|
|
64
|
+
: "Pull request closed during active run",
|
|
65
|
+
});
|
|
62
66
|
}
|
|
63
67
|
db.issueSessions.releaseIssueSessionLeaseRespectingActiveLease(issue.projectId, issue.linearIssueId);
|
|
64
68
|
const updatedIssue = db.issues.getIssue(issue.projectId, issue.linearIssueId) ?? issue;
|
|
@@ -102,20 +106,26 @@ async function completeLinearIssueAfterMerge(params, issue) {
|
|
|
102
106
|
}
|
|
103
107
|
const normalizedCurrent = liveIssue.stateName?.trim().toLowerCase();
|
|
104
108
|
if (normalizedCurrent === targetState.trim().toLowerCase()) {
|
|
105
|
-
params.db.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
params.db.issueSessions.commitIssueState({
|
|
110
|
+
writer: WRITER,
|
|
111
|
+
update: {
|
|
112
|
+
projectId: issue.projectId,
|
|
113
|
+
linearIssueId: issue.linearIssueId,
|
|
114
|
+
...(liveIssue.stateName ? { currentLinearState: liveIssue.stateName } : {}),
|
|
115
|
+
...(liveIssue.stateType ? { currentLinearStateType: liveIssue.stateType } : {}),
|
|
116
|
+
},
|
|
110
117
|
});
|
|
111
118
|
return;
|
|
112
119
|
}
|
|
113
120
|
const updated = await linear.setIssueState(issue.linearIssueId, targetState);
|
|
114
|
-
params.db.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
params.db.issueSessions.commitIssueState({
|
|
122
|
+
writer: WRITER,
|
|
123
|
+
update: {
|
|
124
|
+
projectId: issue.projectId,
|
|
125
|
+
linearIssueId: issue.linearIssueId,
|
|
126
|
+
...(updated.stateName ? { currentLinearState: updated.stateName } : {}),
|
|
127
|
+
...(updated.stateType ? { currentLinearStateType: updated.stateType } : {}),
|
|
128
|
+
},
|
|
119
129
|
});
|
|
120
130
|
}
|
|
121
131
|
catch (error) {
|