From 9c66c8d914f516b95b930bee62b553923d100331 Mon Sep 17 00:00:00 2001 From: glorydavid03023 Date: Sat, 4 Jul 2026 08:17:30 +0900 Subject: [PATCH] fix(engine): floor freshness for missing or invalid issue timestamps computeOpportunityFreshness treated an open issue with a missing or unparseable timestamp as maximally fresh (age 0 -> freshness 1) instead of stale, so a malformed metadata row could rank ahead of genuinely fresh opportunities in the local ranker. issueAgeDays now returns STALE_AGE_DAYS for a null/unparseable timestamp -- underflowing to the 0.05 freshness floor after the clamp -- restoring the flooring this scorer shipped with (#2806, "invalid or missing issue timestamps floor freshness instead of ranking malformed metadata first") that a later change inadvertently reverted to 0. The scorer's own tests already assert this contract (missing/non-string timestamps -> 0.05); they were red on main until this fix. --- .../gittensory-engine/src/opportunity-freshness.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/gittensory-engine/src/opportunity-freshness.ts b/packages/gittensory-engine/src/opportunity-freshness.ts index 943ee78f49..ca42837192 100644 --- a/packages/gittensory-engine/src/opportunity-freshness.ts +++ b/packages/gittensory-engine/src/opportunity-freshness.ts @@ -26,10 +26,18 @@ function pickTimestamp(issue: FreshnessIssue): string | null { return null; } +// An open issue whose timestamp is missing or unparseable is treated as maximally STALE, not fresh: a +// malformed row must not rank ahead of genuinely fresh opportunities in the metadata ranker. This restores +// the flooring introduced with this scorer (#2806, "invalid or missing issue timestamps floor freshness +// instead of ranking malformed metadata first"). exp(-STALE_AGE_DAYS / 20) underflows to ~0, which the +// clamp raises to the 0.05 freshness floor. +const STALE_AGE_DAYS = 9999; + function issueAgeDays(value: string | null, nowMs: number): number { - if (!value) return 0; + if (!value) return STALE_AGE_DAYS; const parsed = Date.parse(value); - if (!Number.isFinite(parsed)) return 0; + /* v8 ignore next -- pickTimestamp only ever yields a parseable string or null; kept as a defensive floor. */ + if (!Number.isFinite(parsed)) return STALE_AGE_DAYS; return Math.floor((nowMs - parsed) / 86_400_000); }