@atolis-hq/wake 0.2.95 → 0.2.97
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/src/core/stale-run-reconciler.js +111 -7
- package/dist/src/core/tick-runner.js +153 -36
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { createLabelsEvent } from './event-builders.js';
|
|
2
2
|
import { RUN_COMPLETED_EVENT } from '../domain/event-types.js';
|
|
3
3
|
import { labelsForWorkItem } from '../domain/work-item-labels.js';
|
|
4
|
+
import { readJsonFile, writeJsonFile } from '../lib/json-file.js';
|
|
5
|
+
import { isMissingPathError } from '../lib/state-health.js';
|
|
4
6
|
import { createEventEnvelope } from '../lib/event-log.js';
|
|
7
|
+
import { join } from 'node:path';
|
|
5
8
|
// Reconciles run records still marked `running` past the runner timeout: a
|
|
6
9
|
// record whose work item has already moved on is superseded, otherwise it is
|
|
7
10
|
// failed and a completion event replayed so the projection stops waiting on it.
|
|
@@ -56,6 +59,97 @@ export function createStaleRunReconciler(deps) {
|
|
|
56
59
|
retrySafety,
|
|
57
60
|
};
|
|
58
61
|
}
|
|
62
|
+
function watcherStateFile(key) {
|
|
63
|
+
return join(deps.stateStore.paths.dataRoot, 'watchers', `${key}.json`);
|
|
64
|
+
}
|
|
65
|
+
function isWatcherTrigger(value) {
|
|
66
|
+
if (value === null || typeof value !== 'object')
|
|
67
|
+
return false;
|
|
68
|
+
const record = value;
|
|
69
|
+
return ((record.kind === 'event' && typeof record.eventId === 'string') ||
|
|
70
|
+
(record.kind === 'schedule' && typeof record.slot === 'string'));
|
|
71
|
+
}
|
|
72
|
+
function watcherMetadata(record) {
|
|
73
|
+
const metadata = record.metadata;
|
|
74
|
+
if (metadata?.watcher !== true)
|
|
75
|
+
return {};
|
|
76
|
+
return {
|
|
77
|
+
...(typeof metadata.watcherStateKey === 'string'
|
|
78
|
+
? { stateKey: metadata.watcherStateKey }
|
|
79
|
+
: {}),
|
|
80
|
+
...(isWatcherTrigger(metadata.watcherTrigger) ? { trigger: metadata.watcherTrigger } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function readWatcherState(key) {
|
|
84
|
+
try {
|
|
85
|
+
const raw = await readJsonFile(watcherStateFile(key));
|
|
86
|
+
if (raw === null || typeof raw !== 'object')
|
|
87
|
+
return null;
|
|
88
|
+
const record = raw;
|
|
89
|
+
return record.schemaVersion === 1 &&
|
|
90
|
+
record.key === key &&
|
|
91
|
+
typeof record.updatedAt === 'string'
|
|
92
|
+
? {
|
|
93
|
+
schemaVersion: 1,
|
|
94
|
+
key,
|
|
95
|
+
...(typeof record.lastDispatchedEventId === 'string'
|
|
96
|
+
? { lastDispatchedEventId: record.lastDispatchedEventId }
|
|
97
|
+
: {}),
|
|
98
|
+
...(typeof record.lastDispatchedSlot === 'string'
|
|
99
|
+
? { lastDispatchedSlot: record.lastDispatchedSlot }
|
|
100
|
+
: {}),
|
|
101
|
+
...(typeof record.failureCount === 'number' &&
|
|
102
|
+
Number.isInteger(record.failureCount) &&
|
|
103
|
+
record.failureCount >= 0
|
|
104
|
+
? { failureCount: record.failureCount }
|
|
105
|
+
: {}),
|
|
106
|
+
updatedAt: record.updatedAt,
|
|
107
|
+
}
|
|
108
|
+
: null;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (isMissingPathError(error))
|
|
112
|
+
return null;
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function writeWatcherState(key, patch, updatedAt) {
|
|
117
|
+
const current = await readWatcherState(key);
|
|
118
|
+
await writeJsonFile(watcherStateFile(key), {
|
|
119
|
+
schemaVersion: 1,
|
|
120
|
+
key,
|
|
121
|
+
...(current?.lastDispatchedEventId === undefined
|
|
122
|
+
? {}
|
|
123
|
+
: { lastDispatchedEventId: current.lastDispatchedEventId }),
|
|
124
|
+
...(current?.lastDispatchedSlot === undefined
|
|
125
|
+
? {}
|
|
126
|
+
: { lastDispatchedSlot: current.lastDispatchedSlot }),
|
|
127
|
+
...(current?.failureCount === undefined ? {} : { failureCount: current.failureCount }),
|
|
128
|
+
...patch,
|
|
129
|
+
updatedAt,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function watcherCursorPatch(trigger) {
|
|
133
|
+
return {
|
|
134
|
+
...(trigger.kind === 'event'
|
|
135
|
+
? { lastDispatchedEventId: trigger.eventId }
|
|
136
|
+
: { lastDispatchedSlot: trigger.slot }),
|
|
137
|
+
failureCount: 0,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function updateWatcherStateAfterCompletion(input) {
|
|
141
|
+
if (input.stateKey === undefined || input.trigger === undefined)
|
|
142
|
+
return;
|
|
143
|
+
if (input.retrySafety === 'SAFE_TO_RETRY' || input.retrySafety === 'SAFE_TO_RESUME') {
|
|
144
|
+
const current = await readWatcherState(input.stateKey);
|
|
145
|
+
const failureCount = (current?.failureCount ?? 0) + 1;
|
|
146
|
+
if (failureCount < deps.config.retry.maxFailureRetries) {
|
|
147
|
+
await writeWatcherState(input.stateKey, { failureCount }, input.updatedAt);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
await writeWatcherState(input.stateKey, watcherCursorPatch(input.trigger), input.updatedAt);
|
|
152
|
+
}
|
|
59
153
|
async function staleReason(record, now) {
|
|
60
154
|
if (record.status !== 'running') {
|
|
61
155
|
return null;
|
|
@@ -166,11 +260,15 @@ export function createStaleRunReconciler(deps) {
|
|
|
166
260
|
candidate.runId !== record.runId &&
|
|
167
261
|
candidate.status !== 'running' &&
|
|
168
262
|
Date.parse(candidate.startedAt) > Date.parse(record.startedAt));
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
263
|
+
const fullRecord = (await deps.stateStore.readRunRecord(record.runId)) ?? record;
|
|
264
|
+
const watcher = watcherMetadata(fullRecord);
|
|
265
|
+
const watcherRun = watcher.trigger !== undefined;
|
|
266
|
+
// Parent-stage runs must still be the projection's latest run. Watcher
|
|
267
|
+
// runs are isolated child runs, so their claim events intentionally do
|
|
268
|
+
// not replace the parent's wake.lastRunId.
|
|
269
|
+
if (projection === null ||
|
|
270
|
+
(!watcherRun && projection.wake.lastRunId !== record.runId) ||
|
|
271
|
+
newerCompletedRun) {
|
|
174
272
|
await deps.stateStore.writeRunRecord({
|
|
175
273
|
...fullRecord,
|
|
176
274
|
lifecycle: 'TERMINAL',
|
|
@@ -188,7 +286,6 @@ export function createStaleRunReconciler(deps) {
|
|
|
188
286
|
}
|
|
189
287
|
const staleExecutionOutcome = reason === 'timeout' ? 'TIMED_OUT' : recoveryOutcomeForLifecycle(record.lifecycle);
|
|
190
288
|
const failureContext = classifyReconciledFailure(record);
|
|
191
|
-
const fullRecord = (await deps.stateStore.readRunRecord(record.runId)) ?? record;
|
|
192
289
|
await deps.stateStore.writeRunRecord({
|
|
193
290
|
...fullRecord,
|
|
194
291
|
lifecycle: 'TERMINAL',
|
|
@@ -234,12 +331,19 @@ export function createStaleRunReconciler(deps) {
|
|
|
234
331
|
? `runner:recover-${record.lifecycle.toLowerCase()}`
|
|
235
332
|
: 'runner:orphaned-process',
|
|
236
333
|
...(record.routing === undefined ? {} : { routing: record.routing }),
|
|
334
|
+
...(watcherRun ? { watcherRun: true, watcherTrigger: watcher.trigger } : {}),
|
|
237
335
|
},
|
|
238
336
|
});
|
|
239
337
|
await deps.stateStore.appendEventEnvelope(runCompletedEvent);
|
|
240
338
|
await deps.projectionUpdater.rebuildFromEvents([runCompletedEvent]);
|
|
339
|
+
await updateWatcherStateAfterCompletion({
|
|
340
|
+
stateKey: watcher.stateKey,
|
|
341
|
+
trigger: watcher.trigger,
|
|
342
|
+
retrySafety: failureContext.retrySafety,
|
|
343
|
+
updatedAt: finishedAt,
|
|
344
|
+
});
|
|
241
345
|
const updatedProjection = await deps.stateStore.readIssueState(projection.workItemKey);
|
|
242
|
-
if (updatedProjection !== null) {
|
|
346
|
+
if (!watcherRun && updatedProjection !== null) {
|
|
243
347
|
await deliverRecoveredLabels(updatedProjection, record.runId, finishedAt);
|
|
244
348
|
}
|
|
245
349
|
}
|
|
@@ -482,6 +482,38 @@ export function createTickRunner(deps) {
|
|
|
482
482
|
}
|
|
483
483
|
return { blocked: false };
|
|
484
484
|
}
|
|
485
|
+
async function applyApprovedPrMergePolicy(input) {
|
|
486
|
+
if (input.approvalResolution.targetResourceUri === undefined || input.mergePolicy === null) {
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
const risk = await evaluatePrMergeRisk({
|
|
490
|
+
projection: input.projection,
|
|
491
|
+
targetResourceUri: input.approvalResolution.targetResourceUri,
|
|
492
|
+
policy: input.mergePolicy,
|
|
493
|
+
});
|
|
494
|
+
if (!risk.passed) {
|
|
495
|
+
return await publishPrMergePolicyBlock({
|
|
496
|
+
projection: input.projection,
|
|
497
|
+
approvalResolution: input.approvalResolution,
|
|
498
|
+
reason: risk.reason,
|
|
499
|
+
workflowName: input.workflowName,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
const mergeActionsResult = await performApprovedPrMergeActions({
|
|
503
|
+
projection: input.projection,
|
|
504
|
+
approvalResolution: input.approvalResolution,
|
|
505
|
+
mergePolicy: input.mergePolicy,
|
|
506
|
+
});
|
|
507
|
+
if (mergeActionsResult.blocked) {
|
|
508
|
+
return await publishPrMergePolicyBlock({
|
|
509
|
+
projection: input.projection,
|
|
510
|
+
approvalResolution: input.approvalResolution,
|
|
511
|
+
reason: mergeActionsResult.reason,
|
|
512
|
+
workflowName: input.workflowName,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
485
517
|
// The one deterministic approval transition: /approved, wake:auto, and a
|
|
486
518
|
// watcher child's onSuccess.approve all resolve a pending approval through
|
|
487
519
|
// this same event shape, so replay folds them identically.
|
|
@@ -978,6 +1010,11 @@ export function createTickRunner(deps) {
|
|
|
978
1010
|
...(typeof record.lastDispatchedSlot === 'string'
|
|
979
1011
|
? { lastDispatchedSlot: record.lastDispatchedSlot }
|
|
980
1012
|
: {}),
|
|
1013
|
+
...(typeof record.failureCount === 'number' &&
|
|
1014
|
+
Number.isInteger(record.failureCount) &&
|
|
1015
|
+
record.failureCount >= 0
|
|
1016
|
+
? { failureCount: record.failureCount }
|
|
1017
|
+
: {}),
|
|
981
1018
|
updatedAt: record.updatedAt,
|
|
982
1019
|
}
|
|
983
1020
|
: null;
|
|
@@ -999,10 +1036,36 @@ export function createTickRunner(deps) {
|
|
|
999
1036
|
...(current?.lastDispatchedSlot === undefined
|
|
1000
1037
|
? {}
|
|
1001
1038
|
: { lastDispatchedSlot: current.lastDispatchedSlot }),
|
|
1039
|
+
...(current?.failureCount === undefined ? {} : { failureCount: current.failureCount }),
|
|
1002
1040
|
...patch,
|
|
1003
1041
|
updatedAt: deps.clock.now().toISOString(),
|
|
1004
1042
|
});
|
|
1005
1043
|
}
|
|
1044
|
+
function watcherCursorPatch(trigger) {
|
|
1045
|
+
return {
|
|
1046
|
+
...(trigger.kind === 'event'
|
|
1047
|
+
? { lastDispatchedEventId: trigger.eventId }
|
|
1048
|
+
: { lastDispatchedSlot: trigger.slot }),
|
|
1049
|
+
failureCount: 0,
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
function isRetryEligibleWatcherFailure(input) {
|
|
1053
|
+
return (input.sentinel === 'FAILED' &&
|
|
1054
|
+
(input.retrySafety === 'SAFE_TO_RETRY' || input.retrySafety === 'SAFE_TO_RESUME'));
|
|
1055
|
+
}
|
|
1056
|
+
async function updateWatcherStateAfterCompletion(input) {
|
|
1057
|
+
if (input.key === undefined || input.trigger === undefined)
|
|
1058
|
+
return;
|
|
1059
|
+
if (isRetryEligibleWatcherFailure(input)) {
|
|
1060
|
+
const current = await readWatcherState(input.key);
|
|
1061
|
+
const failureCount = (current?.failureCount ?? 0) + 1;
|
|
1062
|
+
if (failureCount < deps.config.retry.maxFailureRetries) {
|
|
1063
|
+
await writeWatcherState(input.key, { failureCount });
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
await writeWatcherState(input.key, watcherCursorPatch(input.trigger));
|
|
1068
|
+
}
|
|
1006
1069
|
async function nextWatcherDispatch(projections, now) {
|
|
1007
1070
|
for (const projection of projections) {
|
|
1008
1071
|
// A closed issue can still carry a stale status label (e.g. squash-merged
|
|
@@ -1044,15 +1107,16 @@ export function createTickRunner(deps) {
|
|
|
1044
1107
|
const cursorIndex = state?.lastDispatchedEventId === undefined
|
|
1045
1108
|
? -1
|
|
1046
1109
|
: matchingEvents.findIndex((event) => event.eventId === state.lastDispatchedEventId);
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1110
|
+
const undispatched = matchingEvents.slice(cursorIndex + 1);
|
|
1111
|
+
const next = (state?.failureCount ?? 0) > 0 ? undispatched.at(0) : undispatched.at(-1);
|
|
1112
|
+
if (next !== undefined) {
|
|
1049
1113
|
return {
|
|
1050
1114
|
projection,
|
|
1051
1115
|
parentWorkflowName,
|
|
1052
1116
|
parentStage: projection.wake.stage,
|
|
1053
1117
|
watcherIndex,
|
|
1054
1118
|
targetWorkflowName: watcher.workflow,
|
|
1055
|
-
trigger: { kind: 'event', eventId:
|
|
1119
|
+
trigger: { kind: 'event', eventId: next.eventId },
|
|
1056
1120
|
};
|
|
1057
1121
|
}
|
|
1058
1122
|
}
|
|
@@ -1301,34 +1365,14 @@ export function createTickRunner(deps) {
|
|
|
1301
1365
|
}
|
|
1302
1366
|
}
|
|
1303
1367
|
else if (approvalResolution.approved) {
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
return await publishPrMergePolicyBlock({
|
|
1313
|
-
projection: candidate,
|
|
1314
|
-
approvalResolution,
|
|
1315
|
-
reason: risk.reason,
|
|
1316
|
-
workflowName,
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
const mergeActionsResult = await performApprovedPrMergeActions({
|
|
1320
|
-
projection: candidate,
|
|
1321
|
-
approvalResolution,
|
|
1322
|
-
mergePolicy,
|
|
1323
|
-
});
|
|
1324
|
-
if (mergeActionsResult.blocked) {
|
|
1325
|
-
return await publishPrMergePolicyBlock({
|
|
1326
|
-
projection: candidate,
|
|
1327
|
-
approvalResolution,
|
|
1328
|
-
reason: mergeActionsResult.reason,
|
|
1329
|
-
workflowName,
|
|
1330
|
-
});
|
|
1331
|
-
}
|
|
1368
|
+
const mergePolicyBlock = await applyApprovedPrMergePolicy({
|
|
1369
|
+
projection: candidate,
|
|
1370
|
+
approvalResolution,
|
|
1371
|
+
workflowName,
|
|
1372
|
+
mergePolicy: approvedMergePolicyForStage(workflow.stages[candidate.wake.stage]),
|
|
1373
|
+
});
|
|
1374
|
+
if (mergePolicyBlock !== null) {
|
|
1375
|
+
return mergePolicyBlock;
|
|
1332
1376
|
}
|
|
1333
1377
|
const approvalId = `approval-${candidate.issue.number}-${deps.clock.now().getTime()}`;
|
|
1334
1378
|
const approvedAt = deps.clock.now().toISOString();
|
|
@@ -1492,6 +1536,7 @@ export function createTickRunner(deps) {
|
|
|
1492
1536
|
? {
|
|
1493
1537
|
watcher: true,
|
|
1494
1538
|
watcherWorkflow: workflowName,
|
|
1539
|
+
watcherStateKey: watcherStateKeyForRun,
|
|
1495
1540
|
watcherTrigger: watcherTriggerForRun,
|
|
1496
1541
|
}
|
|
1497
1542
|
: {}),
|
|
@@ -1587,11 +1632,6 @@ export function createTickRunner(deps) {
|
|
|
1587
1632
|
}
|
|
1588
1633
|
await transitionRunLifecycle('CLAIMED');
|
|
1589
1634
|
await projectionUpdater.rebuildFromEvents([claimedEvent]);
|
|
1590
|
-
if (watcherStateKeyForRun !== undefined && watcherTriggerForRun !== undefined) {
|
|
1591
|
-
await writeWatcherState(watcherStateKeyForRun, watcherTriggerForRun.kind === 'event'
|
|
1592
|
-
? { lastDispatchedEventId: watcherTriggerForRun.eventId }
|
|
1593
|
-
: { lastDispatchedSlot: watcherTriggerForRun.slot });
|
|
1594
|
-
}
|
|
1595
1635
|
// Watcher runs execute a *different* workflow's stage (e.g. plan-review)
|
|
1596
1636
|
// against the same parent projection, not a stage transition of the
|
|
1597
1637
|
// parent's own workflow — writing labels here would stamp the child
|
|
@@ -2055,6 +2095,12 @@ export function createTickRunner(deps) {
|
|
|
2055
2095
|
});
|
|
2056
2096
|
await deps.stateStore.appendEventEnvelope(runCompletedEvent);
|
|
2057
2097
|
await projectionUpdater.rebuildFromEvents([runCompletedEvent]);
|
|
2098
|
+
await updateWatcherStateAfterCompletion({
|
|
2099
|
+
key: watcherStateKeyForRun,
|
|
2100
|
+
trigger: watcherTriggerForRun,
|
|
2101
|
+
sentinel,
|
|
2102
|
+
retrySafety: failureContext?.retrySafety,
|
|
2103
|
+
});
|
|
2058
2104
|
if (!watcherRun) {
|
|
2059
2105
|
await deliverOutboundEvent(createLabelsEvent({
|
|
2060
2106
|
projection: candidate,
|
|
@@ -2099,6 +2145,71 @@ export function createTickRunner(deps) {
|
|
|
2099
2145
|
idempotencyKey: `${runId}:pr-review-verdict-comment`,
|
|
2100
2146
|
},
|
|
2101
2147
|
});
|
|
2148
|
+
const approvalId = `${runId}-parent-approval`;
|
|
2149
|
+
const parentWorkflow = watcherDispatch === null
|
|
2150
|
+
? undefined
|
|
2151
|
+
: deps.config.workflows[watcherDispatch.parentWorkflowName];
|
|
2152
|
+
if (sentinel === 'DONE' &&
|
|
2153
|
+
watcherDispatch !== null &&
|
|
2154
|
+
isAwaitingApproval(candidate) &&
|
|
2155
|
+
typeof pendingApprovalAction === 'string' &&
|
|
2156
|
+
parentWorkflow !== undefined &&
|
|
2157
|
+
(await deps.stateStore.readEventEnvelope(`${approvalId}-completed`)) === null) {
|
|
2158
|
+
const approvedAt = eventStampNow();
|
|
2159
|
+
const approvalResolution = {
|
|
2160
|
+
approved: true,
|
|
2161
|
+
pendingAction: pendingApprovalAction,
|
|
2162
|
+
targetResourceUri: prReviewTargetResourceUri,
|
|
2163
|
+
triggeringCommentId: approvalId,
|
|
2164
|
+
triggeringCommentBody: parsedRunnerResult.body,
|
|
2165
|
+
};
|
|
2166
|
+
const mergePolicyBlock = await applyApprovedPrMergePolicy({
|
|
2167
|
+
projection: candidate,
|
|
2168
|
+
approvalResolution,
|
|
2169
|
+
workflowName: watcherDispatch.parentWorkflowName,
|
|
2170
|
+
mergePolicy: approvedMergePolicyForStage(parentWorkflow.stages[watcherDispatch.parentStage]),
|
|
2171
|
+
});
|
|
2172
|
+
if (mergePolicyBlock !== null) {
|
|
2173
|
+
return mergePolicyBlock;
|
|
2174
|
+
}
|
|
2175
|
+
const applied = await applyApprovalTransition({
|
|
2176
|
+
projection: candidate,
|
|
2177
|
+
pendingAction: pendingApprovalAction,
|
|
2178
|
+
approvalId,
|
|
2179
|
+
approvedAt,
|
|
2180
|
+
reason: 'watcher:approved',
|
|
2181
|
+
workflow: parentWorkflow,
|
|
2182
|
+
workflowName: watcherDispatch.parentWorkflowName,
|
|
2183
|
+
});
|
|
2184
|
+
if (applied !== null) {
|
|
2185
|
+
await appendAuditEvent({
|
|
2186
|
+
eventId: `${approvalId}-audit-watcher-approval`,
|
|
2187
|
+
decisionType: 'approval.watcher-resolved',
|
|
2188
|
+
workItemKey: candidate.workItemKey,
|
|
2189
|
+
runId,
|
|
2190
|
+
workflowRevision: await computeWorkflowRevision({
|
|
2191
|
+
config: deps.config,
|
|
2192
|
+
workflowName: watcherDispatch.parentWorkflowName,
|
|
2193
|
+
workflow: parentWorkflow,
|
|
2194
|
+
action: pendingApprovalAction,
|
|
2195
|
+
}),
|
|
2196
|
+
inputsConsidered: {
|
|
2197
|
+
watcherWorkflow: watcherDispatch.targetWorkflowName,
|
|
2198
|
+
pendingAction: pendingApprovalAction,
|
|
2199
|
+
childRunId: runId,
|
|
2200
|
+
childSentinel: sentinel,
|
|
2201
|
+
verifiedTargetResourceUri: prReviewTargetResourceUri,
|
|
2202
|
+
},
|
|
2203
|
+
outcome: { approved: true, nextStage: applied.nextStage },
|
|
2204
|
+
timestamp: approvedAt,
|
|
2205
|
+
sourceRefs: {
|
|
2206
|
+
repo: candidate.issue.repo,
|
|
2207
|
+
issueNumber: candidate.issue.number,
|
|
2208
|
+
resourceUri: prReviewTargetResourceUri,
|
|
2209
|
+
},
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2102
2213
|
}
|
|
2103
2214
|
else if (watcherDispatch !== null &&
|
|
2104
2215
|
watcherSuccessPolicy?.approve === true &&
|
|
@@ -2254,6 +2365,12 @@ export function createTickRunner(deps) {
|
|
|
2254
2365
|
});
|
|
2255
2366
|
await deps.stateStore.appendEventEnvelope(runCompletedEvent);
|
|
2256
2367
|
await projectionUpdater.rebuildFromEvents([runCompletedEvent]);
|
|
2368
|
+
await updateWatcherStateAfterCompletion({
|
|
2369
|
+
key: watcherStateKeyForRun,
|
|
2370
|
+
trigger: watcherTriggerForRun,
|
|
2371
|
+
sentinel,
|
|
2372
|
+
retrySafety: failureContext.retrySafety,
|
|
2373
|
+
});
|
|
2257
2374
|
// See the matching guard on the claim path above: a watcher run's
|
|
2258
2375
|
// failure is not the parent's own action failing, so it must not
|
|
2259
2376
|
// stamp the child workflow's stage/workflow onto the parent's labels.
|
package/dist/src/version.js
CHANGED