merge-steward 0.9.1 → 0.9.3
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/reconciler.js +19 -0
- package/dist/service.d.ts +6 -0
- package/dist/service.js +25 -0
- package/package.json +1 -1
package/dist/reconciler.js
CHANGED
|
@@ -38,6 +38,25 @@ export async function reconcile(ctx) {
|
|
|
38
38
|
// Truth guard: verify entry against GitHub before processing.
|
|
39
39
|
if (await sanitizeEntry(ctx, entry))
|
|
40
40
|
continue;
|
|
41
|
+
// Stale dependency guard: if this entry's spec was built on top of
|
|
42
|
+
// another entry that was dequeued or evicted (without downstream
|
|
43
|
+
// invalidation), the spec is contaminated with the removed entry's
|
|
44
|
+
// changes. Reset so it rebuilds on the correct base next tick.
|
|
45
|
+
// Exclude "merged" — a merged dependency means main advanced to its
|
|
46
|
+
// spec, so our cumulative spec is still valid (speculative consistency).
|
|
47
|
+
if (entry.specBasedOn) {
|
|
48
|
+
const dep = ctx.store.getEntry(entry.specBasedOn);
|
|
49
|
+
if (!dep || dep.status === "dequeued" || dep.status === "evicted") {
|
|
50
|
+
emit(ctx, entry, "invalidated", {
|
|
51
|
+
detail: `dependency ${entry.specBasedOn} is ${dep?.status ?? "removed"}`,
|
|
52
|
+
});
|
|
53
|
+
await cleanupSpec(ctx, entry);
|
|
54
|
+
ctx.store.transition(entry.id, "preparing_head", {
|
|
55
|
+
...CLEAN_CI, ...CLEAN_SPEC,
|
|
56
|
+
}, `stale dependency ${dep?.status ?? "removed"}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
41
60
|
const isHead = i === 0;
|
|
42
61
|
const prevEntry = i > 0 ? ctx.store.getEntry(allActive[i - 1].id) ?? null : null;
|
|
43
62
|
const phase = entry.status;
|
package/dist/service.d.ts
CHANGED
|
@@ -67,6 +67,12 @@ export declare class MergeStewardService {
|
|
|
67
67
|
updateHeadByPR(prNumber: number, headSha: string): void;
|
|
68
68
|
/** Acknowledge an external merge (webhook: PR merged outside queue). */
|
|
69
69
|
acknowledgeExternalMerge(prNumber: number): void;
|
|
70
|
+
/**
|
|
71
|
+
* Reset all active entries positioned after the given entry to preparing_head
|
|
72
|
+
* with clean spec/CI state. Prevents downstream specs that included the
|
|
73
|
+
* dequeued entry's changes from being merged to main.
|
|
74
|
+
*/
|
|
75
|
+
private invalidateDownstreamOf;
|
|
70
76
|
private runTick;
|
|
71
77
|
private scheduleNextTick;
|
|
72
78
|
private describeMainBroken;
|
package/dist/service.js
CHANGED
|
@@ -127,6 +127,7 @@ export class MergeStewardService {
|
|
|
127
127
|
if (!entry)
|
|
128
128
|
return false;
|
|
129
129
|
this.store.dequeue(entryId);
|
|
130
|
+
this.invalidateDownstreamOf(entry);
|
|
130
131
|
this.logger.info({ entryId, prNumber: entry.prNumber }, "Entry dequeued");
|
|
131
132
|
return true;
|
|
132
133
|
}
|
|
@@ -252,6 +253,7 @@ export class MergeStewardService {
|
|
|
252
253
|
const entry = this.store.getEntryByPR(this.config.repoId, prNumber);
|
|
253
254
|
if (entry) {
|
|
254
255
|
this.store.dequeue(entry.id);
|
|
256
|
+
this.invalidateDownstreamOf(entry);
|
|
255
257
|
this.logger.info({ prNumber, entryId: entry.id }, "PR dequeued");
|
|
256
258
|
}
|
|
257
259
|
}
|
|
@@ -275,6 +277,29 @@ export class MergeStewardService {
|
|
|
275
277
|
this.logger.info({ prNumber, entryId: entry.id }, "External merge acknowledged");
|
|
276
278
|
}
|
|
277
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Reset all active entries positioned after the given entry to preparing_head
|
|
282
|
+
* with clean spec/CI state. Prevents downstream specs that included the
|
|
283
|
+
* dequeued entry's changes from being merged to main.
|
|
284
|
+
*/
|
|
285
|
+
invalidateDownstreamOf(removedEntry) {
|
|
286
|
+
const allActive = this.store.listActive(this.config.repoId);
|
|
287
|
+
let invalidated = 0;
|
|
288
|
+
for (const downstream of allActive) {
|
|
289
|
+
if (downstream.position <= removedEntry.position)
|
|
290
|
+
continue;
|
|
291
|
+
if (TERMINAL_STATUSES.includes(downstream.status))
|
|
292
|
+
continue;
|
|
293
|
+
this.store.transition(downstream.id, "preparing_head", {
|
|
294
|
+
ciRunId: null, ciRetries: 0,
|
|
295
|
+
specBranch: null, specSha: null, specBasedOn: null,
|
|
296
|
+
}, `invalidated: entry ${removedEntry.id.slice(0, 8)} dequeued`);
|
|
297
|
+
invalidated++;
|
|
298
|
+
}
|
|
299
|
+
if (invalidated > 0) {
|
|
300
|
+
this.logger.info({ removedEntryId: removedEntry.id, invalidated }, "Invalidated downstream entries after dequeue");
|
|
301
|
+
}
|
|
302
|
+
}
|
|
278
303
|
async runTick() {
|
|
279
304
|
if (this.tickInProgress)
|
|
280
305
|
return false;
|