Skip to content

Commit bf8bb14

Browse files
fix(miner): fail closed on an unparseable lease in portfolio-queue findStuckItems (#8033)
findStuckItems skipped any in_progress item whose leasedAt couldn't be parsed (leaseAgeMs returns null -> the old 'if (ageMs === null) continue'), so a corrupted or hand-edited lease timestamp stranded that item 'in_progress' forever, never reclaimed by the stuck-item sweep. claim-ledger-expiry.ts's findExpiredClaims already fails closed on the same shape post-#7732 (an unparseable claimedAt is treated as expired). Match that posture: an item whose leasedAt is missing/unparseable is now returned as stuck (swept back to queued) rather than silently skipped. Updated the doc comment and split the existing test so unparseable/missing leases assert the fail-closed sweep instead of being bundled into the 'ignored' case. Closes #8007
1 parent 53ad637 commit bf8bb14

2 files changed

Lines changed: 14 additions & 6 deletions

File tree

packages/loopover-miner/lib/portfolio-queue-expiry.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ function leaseAgeMs(item: QueueLeaseEntry, nowMs: number): number | null {
2121

2222
/**
2323
* Return in-flight items whose lease age is strictly greater than `maxLeaseMs`. An item whose age equals
24-
* `maxLeaseMs` exactly is still within the window (not stuck). Items that are not 'in_progress', or whose
25-
* `leasedAt` is missing/unparseable, are never returned.
24+
* `maxLeaseMs` exactly is still within the window (not stuck). Items that are not 'in_progress' are never
25+
* returned; an item whose `leasedAt` is missing/unparseable fails closed and IS returned (swept), so a
26+
* corrupted lease can't strand an item 'in_progress' forever (#8007, matching claim-ledger-expiry post-#7732).
2627
*/
2728
export function findStuckItems(items: QueueLeaseEntry[], nowMs: number, maxLeaseMs: number): QueueLeaseEntry[] {
2829
if (!Number.isFinite(nowMs) || nowMs < 0) throw new Error("invalid_now_ms");
@@ -33,8 +34,9 @@ export function findStuckItems(items: QueueLeaseEntry[], nowMs: number, maxLease
3334
for (const item of items) {
3435
if (item?.status !== "in_progress") continue;
3536
const ageMs = leaseAgeMs(item, nowMs);
36-
if (ageMs === null) continue;
37-
if (ageMs > maxLeaseMs) stuck.push(item);
37+
// Fail closed on an unparseable leasedAt (#8007): a corrupted/hand-edited row whose age can't be computed
38+
// must still be reclaimable, not left 'in_progress' forever -- mirroring findExpiredClaims post-#7732.
39+
if (ageMs === null || ageMs > maxLeaseMs) stuck.push(item);
3840
}
3941
return stuck;
4042
}

test/unit/miner-portfolio-queue-expiry.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,18 @@ describe("findStuckItems (#4827)", () => {
112112
expect(findStuckItems([atBound], now, max)).toEqual([]);
113113
});
114114

115-
it("ignores fresh, non-in_progress, and unparseable-lease items", () => {
115+
it("ignores fresh and non-in_progress items", () => {
116116
const fresh = leaseItem({ identifier: "fresh", leasedAt: new Date(now - 1).toISOString() });
117117
const queued = leaseItem({ identifier: "queued", status: "queued", leasedAt: new Date(now - max - 5).toISOString() });
118+
expect(findStuckItems([fresh, queued], now, max)).toEqual([]);
119+
});
120+
121+
it("fails closed: sweeps an in_progress item whose leasedAt is missing or unparseable (#8007)", () => {
122+
// A corrupted/hand-edited lease whose age can't be computed must be reclaimable, not stranded
123+
// 'in_progress' forever -- matching claim-ledger-expiry's post-#7732 posture.
118124
const noLease = leaseItem({ identifier: "nolease", leasedAt: null });
119125
const bogus = leaseItem({ identifier: "bogus", leasedAt: "not-a-date" });
120-
expect(findStuckItems([fresh, queued, noLease, bogus], now, max)).toEqual([]);
126+
expect(findStuckItems([noLease, bogus], now, max)).toEqual([noLease, bogus]);
121127
});
122128

123129
it("validates its arguments", () => {

0 commit comments

Comments
 (0)