fix(queue): guard computeDaysSince so a malformed timestamp can't make the ranking non-deterministic#1543
Conversation
…e the ranking non-deterministic computeDaysSince parsed a PR timestamp with new Date(...).getTime() and divided by the day constant with no guard. A malformed/empty createdAt or lastUpdatedAt yields NaN, which flows into computePrivateBurdenReductionScore and then the analyzePRQueue sort comparator (b.score - a.score). An Array.sort comparator that returns NaN is implementation-defined, so the ranked PR list reordered run-to-run whenever a PR carried a bad timestamp. Mirror the issueAgeDays guard in signals/reward-risk.ts: when Date.parse is not finite, return 0 (treated as just-created, lowest burden priority) instead of NaN. The fallback is finite rather than Infinity because Infinity - Infinity is still NaN when two PRs share a bad timestamp, and the sort must stay deterministic for any input. Closes JSONbored#1541
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1543 +/- ##
=======================================
Coverage 95.37% 95.37%
=======================================
Files 199 199
Lines 21546 21547 +1
Branches 7791 7792 +1
=======================================
+ Hits 20550 20551 +1
Misses 416 416
Partials 580 580
🚀 New features to boost your workflow:
|
|
Caution 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 🛑 Gittensory review — blocked
🛑 Blocked Review summary CI checks failing
Nits — 2 non-blocking
Review context
Contributor next steps
Signal definitions
Review detailsGenerated from public PR metadata and the diff. Advisory only; deterministic signals remain authoritative. The change replaces `new Date(isoDateString).getTime()` with `Date.parse` guarded by `Number.isFinite`, returning 0 on parse failure instead of NaN. This is correct: NaN in `computePrivateBurdenReductionScore` flows into the `analyzePRQueue` sort comparator and makes `Array.sort` implementation-defined, so the guard must produce a finite value. Returning 0 rather than Infinity is the right call — `Infinity - Infinity` in the comparator would reintroduce NaN when two PRs share a bad timestamp. The three new tests cover the key cases (unparseable string, empty string, staleness path) and all pass. Nits (2)
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Summary
computeDaysSinceinsrc/queue-intelligence.tsparsed a PR timestamp withnew Date(isoDateString).getTime()and divided by the day constant with no guard against a non-finite result. A malformed or emptycreatedAt/lastUpdatedAtyieldsNaN, which flows intocomputePrivateBurdenReductionScore(daysSinceCreated * 2 + ...) and then into theanalyzePRQueueranking comparatorb.privateBurdenReductionScore - a.privateBurdenReductionScore. AnArray.sortcomparator that returnsNaNis implementation-defined, so the ranked PR list for a repo reorders run-to-run whenever one PR in the set carries a bad timestamp.The two directly-analogous helpers already guard this case --
daysSince(src/scoring/pending-pr-scenarios.ts) andissueAgeDays(src/signals/reward-risk.ts).computeDaysSincewas the lone sibling without the guard.Fix
Mirror the
issueAgeDaysguard: whenDate.parseis not finite, return0(treated as just-created, lowest burden-reduction priority) instead ofNaN. The fallback is finite rather thanInfinitybecauseInfinity - Infinityis stillNaNwhen two PRs share a bad timestamp -- the sort comparator must stay deterministic for any input, so a finite fallback is required.0is also conservative on the recommendation path: a badlastUpdatedAtis not flagged as pending-too-long. No behavior change for any well-formed input.Reachability note: live GitHub webhooks carry valid ISO timestamps, but a backfilled / partially-migrated / manually-inserted
pull_requestsrow can carry a malformed or empty value -- the same dirty-data state the rest of the codebase defends against.Scope
type(scope): short summaryConventional Commit format.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run typechecknpm run test:coverage--test/unit/queue-intelligence.test.ts33/33 pass; scoped coverage onsrc/queue-intelligence.tsis 100% statements / branches / functions / lines.createdAtkeeps the ranking deterministic; an emptycreatedAtdegrades to a finite age; a malformedlastUpdatedAtdoes not trigger the pending-too-long branch.Targeted run:
Safety
UI Evidence
Not applicable -- backend pure-function guard with no visible UI, frontend, docs, or extension surface.
Closes #1541