You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
"Compute a [0.05, 1] freshness factor from open issue timestamps, mirroring opportunityFreshnessFactor in src/signals/reward-risk.ts with an injected clock so the miner
engine stays pure and testable."
Three things in that sentence are wrong or misleading:
The range is not [0.05, 1]. Two paths return 0, outside the stated interval: a non-finite nowMs, and an issue list with no open issues. The clamp on the main path really is [0.05, 1],
which makes 0 a meaningful sentinel — but a caller reading the JSDoc has no way to know a 0 is
possible, and 0 vs 0.05 is a 20x difference in the multiplicative ranker score
(potential x feasibility x laneFit x freshness x (1 - dupRisk)).
The named function is not at that path.src/signals/reward-risk.ts contains no opportunityFreshnessFactor (grep returns zero hits for freshness in that file). The real one is
at packages/loopover-engine/src/reward-risk.ts:919.
Separately, the non-finite-nowMs guard is suppressed from coverage with /* v8 ignore next -- Caller supplies a finite epoch; non-finite clocks degrade to zero freshness. */
(line 56). That claim does not hold: the guard is reachable from packages/loopover-engine/src/reward-risk.ts:289, which passes args.nowMs ?? Date.now() — a
caller-supplied value with no finiteness check of its own. The file is inside coverage.include, so
this is a live branch hidden from the gate.
Requirements
The JSDoc on computeOpportunityFreshness MUST state the real contract: 0 when there are no open
issues or the clock is non-finite, otherwise a value in [0.05, 1]. It MUST name what 0 means
(no measurable freshness signal), so a caller can distinguish it from 0.05 (measured, maximally
stale).
The /* v8 ignore next */ on the non-finite-nowMs guard MUST be deleted and the branch covered by
a real test.
No behaviour change: the two 0 returns, the clamp(exp(-age/20), 0.05, 1) formula, the round4
rounding, and the pickTimestamp order (updatedAt then createdAt, each only if parseable) MUST be
byte-for-byte unchanged.
⚠️ Required pattern: mirror the delegation-comment wording already used at packages/loopover-engine/src/reward-risk.ts:908-917 (opportunityCompetitionFactor, #7529) and :919-929 (opportunityFreshnessFactor, #8011). What does NOT satisfy this issue: changing the 0 returns to 0.05 (that silently reweights every ranked candidate); deleting the sentinel;
adding a new exported constant for it; or moving the function.
Deliverables
The JSDoc on computeOpportunityFreshness in packages/loopover-engine/src/opportunity-freshness.ts states the 0-sentinel cases and the [0.05, 1] range for the measured case, and cites packages/loopover-engine/src/reward-risk.ts with the delegation direction stated correctly.
The /* v8 ignore next */ above the !Number.isFinite(nowMs) guard is removed.
A new named test asserting computeOpportunityFreshness([{ state: "open" }], Number.NaN) === 0
and the same for Number.POSITIVE_INFINITY.
A new named test asserting computeOpportunityFreshness([], 1_700_000_000_000) === 0 and computeOpportunityFreshness([{ state: "closed", updatedAt: <now> }], 1_700_000_000_000) === 0
(no open issues).
A new named test asserting the measured floor: an issue whose updatedAt is ~5 years old returns
exactly 0.05, and a same-day issue returns 1.
git diff shows no change to any expression inside computeOpportunityFreshness, issueAgeDays, pickTimestamp, round4, or clamp.
All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example
correcting the JSDoc without removing the coverage suppression and testing the non-finite-clock branch
— does not resolve this issue.
Test Coverage Requirements
packages/loopover-engine/src/**/*.tsis inside coverage.include in vitest.config.ts and
carries its own engine Codecov flag; the 99%+ branch-counted codecov/patch gate applies to every
line this PR touches, including the guard whose suppression is being removed. Both arms of that guard
need a test (a finite clock and a non-finite one), as do both arms of the open-issue filter (at least
one open issue, and none). The non-finite-clock test is the required named regression test for the
removed suppression.
Expected Outcome
A caller reading computeOpportunityFreshness's JSDoc learns that 0 is a real return value and what
it means, is pointed at a function that actually exists and told which way the delegation runs, and the
non-finite-clock branch is graded by the coverage gate instead of suppressed from it.
packages/loopover-engine/src/reward-risk.ts:289 (the caller that can pass a non-finite clock), :908-917 and :919-929 (the delegation comments to mirror)
src/signals/reward-risk.ts (the file the stale reference points at — a different, unrelated shim)
Context
packages/loopover-engine/src/opportunity-freshness.tsdocumentscomputeOpportunityFreshnessas:Three things in that sentence are wrong or misleading:
[0.05, 1]. Two paths return0, outside the stated interval: a non-finitenowMs, and an issue list with no open issues. The clamp on the main path really is[0.05, 1],which makes
0a meaningful sentinel — but a caller reading the JSDoc has no way to know a0ispossible, and
0vs0.05is a 20x difference in the multiplicative ranker score(
potential x feasibility x laneFit x freshness x (1 - dupRisk)).src/signals/reward-risk.tscontains noopportunityFreshnessFactor(grep returns zero hits forfreshnessin that file). The real one isat
packages/loopover-engine/src/reward-risk.ts:919.reward-risk.ts'sopportunityFreshnessFactortodelegate to this module (
return computeOpportunityFreshness(issues, nowMs);, with a commentsaying so). This module is now the canonical implementation, not a mirror of one — the sibling
opportunityCompetitionFactorgot the identical treatment in fix(engine): opportunityCompetitionFactor doesn't fail closed on non-finite inputs like its own engine-mirror sibling does #7529 and its call site says soexplicitly.
Separately, the non-finite-
nowMsguard is suppressed from coverage with/* v8 ignore next -- Caller supplies a finite epoch; non-finite clocks degrade to zero freshness. */(line 56). That claim does not hold: the guard is reachable from
packages/loopover-engine/src/reward-risk.ts:289, which passesargs.nowMs ?? Date.now()— acaller-supplied value with no finiteness check of its own. The file is inside
coverage.include, sothis is a live branch hidden from the gate.
Requirements
computeOpportunityFreshnessMUST state the real contract:0when there are no openissues or the clock is non-finite, otherwise a value in
[0.05, 1]. It MUST name what0means(no measurable freshness signal), so a caller can distinguish it from
0.05(measured, maximallystale).
opportunityFreshnessFactorinpackages/loopover-engine/src/reward-risk.ts, and reworded to state that that function delegatesto this one (opportunityFreshnessFactor still hand-duplicates its arithmetic and reads live Date.now(), unlike opportunityCompetitionFactor which #7529 converted to delegate to the pure mirror #8011), not that this one mirrors it — matching the wording style of the
opportunityCompetitionFactordelegation comment atpackages/loopover-engine/src/reward-risk.ts:908-917./* v8 ignore next */on the non-finite-nowMsguard MUST be deleted and the branch covered bya real test.
0returns, theclamp(exp(-age/20), 0.05, 1)formula, theround4rounding, and the
pickTimestamporder (updatedAt then createdAt, each only if parseable) MUST bebyte-for-byte unchanged.
Deliverables
computeOpportunityFreshnessinpackages/loopover-engine/src/opportunity-freshness.tsstates the0-sentinel cases and the[0.05, 1]range for the measured case, and citespackages/loopover-engine/src/reward-risk.tswith the delegation direction stated correctly./* v8 ignore next */above the!Number.isFinite(nowMs)guard is removed.computeOpportunityFreshness([{ state: "open" }], Number.NaN) === 0and the same for
Number.POSITIVE_INFINITY.computeOpportunityFreshness([], 1_700_000_000_000) === 0andcomputeOpportunityFreshness([{ state: "closed", updatedAt: <now> }], 1_700_000_000_000) === 0(no open issues).
updatedAtis ~5 years old returnsexactly
0.05, and a same-day issue returns1.git diffshows no change to any expression insidecomputeOpportunityFreshness,issueAgeDays,pickTimestamp,round4, orclamp.All Deliverables above are required in a single PR. A PR that satisfies only some of them — for example
correcting the JSDoc without removing the coverage suppression and testing the non-finite-clock branch
— does not resolve this issue.
Test Coverage Requirements
packages/loopover-engine/src/**/*.tsis insidecoverage.includeinvitest.config.tsandcarries its own
engineCodecov flag; the 99%+ branch-countedcodecov/patchgate applies to everyline this PR touches, including the guard whose suppression is being removed. Both arms of that guard
need a test (a finite clock and a non-finite one), as do both arms of the open-issue filter (at least
one open issue, and none). The non-finite-clock test is the required named regression test for the
removed suppression.
Expected Outcome
A caller reading
computeOpportunityFreshness's JSDoc learns that0is a real return value and whatit means, is pointed at a function that actually exists and told which way the delegation runs, and the
non-finite-clock branch is graded by the coverage gate instead of suppressed from it.
Links & Resources
packages/loopover-engine/src/opportunity-freshness.ts(whole file; lines 47-58)packages/loopover-engine/src/reward-risk.ts:289(the caller that can pass a non-finite clock),:908-917and:919-929(the delegation comments to mirror)src/signals/reward-risk.ts(the file the stale reference points at — a different, unrelated shim)