Skip to content

fix(engine): opportunityCompetitionFactor doesn't fail closed on non-finite inputs like its own engine-mirror sibling does #7529

Description

@JSONbored

Context

// packages/loopover-engine/src/reward-risk.ts:899-901
function opportunityCompetitionFactor(highRiskDuplicateClusters: number, openPullRequests: number): number {
  return round(clamp(highRiskDuplicateClusters / Math.max(1, openPullRequests), 0, 1));
}

This is the original, hosted implementation — packages/loopover-engine/src/opportunity-competition.ts's
computeOpportunityCompetition is explicitly documented as its pure, injectable-clock mirror for the
miner engine ("mirroring opportunityCompetitionFactor in src/signals/reward-risk.ts so the miner
engine can derive dupRisk inputs without importing hosted signal code"). But the mirror was hardened
in a way the original never was:

// packages/loopover-engine/src/opportunity-competition.ts
function finiteNonNegative(value: number): number {
  if (!Number.isFinite(value)) return 0;
  return Math.max(0, value);
}
function failClosedClusterPressure(value: number): number {
  // Non-finite input (NaN/±Infinity) means the duplicate-cluster signal is broken, not absent, so
  // treat it as maximal pressure instead of `finiteNonNegative`'s fail-open 0 — dividing by
  // `Math.max(1, openPrs)` and clamping below then yields the maximum competition factor of 1.
  if (!Number.isFinite(value)) return Number.POSITIVE_INFINITY;
  return Math.max(0, value);
}
export function computeOpportunityCompetition(highRiskDuplicateClusters: number, openPullRequests: number): number {
  const clusters = failClosedClusterPressure(highRiskDuplicateClusters);
  const openPrs = finiteNonNegative(openPullRequests);
  return round4(clamp(clusters / Math.max(1, openPrs), 0, 1));
}

The original opportunityCompetitionFactor has neither guard: it passes highRiskDuplicateClusters and
openPullRequests straight into the arithmetic. Concretely, if openPullRequests is NaN (e.g. an
upstream aggregation bug, or a caller propagating a bad reading), Math.max(1, NaN) evaluates to NaN
(one NaN operand makes Math.max/Math.min return NaN unconditionally), so the whole expression —
clamp(x / NaN, 0, 1) — becomes NaN (since Math.min(1, NaN) and Math.max(0, NaN) both propagate
NaN through clamp), and round(NaN) is NaN. That NaN then flows into
competitionFactor at reward-risk.ts:283 and downstream into the reward/risk score the maintainer
gate's own ranking depends on — silently, since nothing here throws or logs. The comment on the mirror's
failClosedClusterPressure makes clear this was a deliberate, reasoned hardening (fail closed to
maximum pressure, not fail open to zero) — it just never made it back into the original it mirrors.

Requirements

  • Add the same two guards to opportunityCompetitionFactor in reward-risk.ts: a non-finite
    highRiskDuplicateClusters must fail closed to maximal pressure (Number.POSITIVE_INFINITY before the
    Math.max(1, openPullRequests) division, exactly mirroring failClosedClusterPressure's documented
    rationale), and a non-finite/negative openPullRequests must normalize to a finite non-negative number
    (mirroring finiteNonNegative) before Math.max(1, ...).
  • Either import the two mirror's helper functions directly (opportunity-competition.ts is already a
    sibling module in the same package) or duplicate the identical two guard functions locally with a
    comment cross-referencing the mirror — do not silently diverge the two implementations again.
  • Do not change opportunityFreshnessFactor or any other function in this file — this issue is scoped to
    opportunityCompetitionFactor only (its opportunityFreshnessFactor sibling was checked and already
    matches its own mirror in opportunity-freshness.ts correctly).

Deliverables

  • opportunityCompetitionFactor in reward-risk.ts fails closed to maximal competition pressure on a non-finite highRiskDuplicateClusters, and normalizes a non-finite/negative openPullRequests to a safe finite value, matching computeOpportunityCompetition's documented behavior exactly.
  • A code comment cross-referencing opportunity-competition.ts as the sibling this must stay in sync with.

Test Coverage Requirements

99%+ Codecov patch gate, branch-counted, on src/**. Add regression tests asserting
opportunityCompetitionFactor(NaN, 5) returns 1 (maximal pressure, not NaN), and
opportunityCompetitionFactor(2, NaN) returns a finite, clamped value (not NaN) — mirroring
computeOpportunityCompetition's own existing test cases for the identical inputs.

Expected Outcome

A non-finite duplicate-cluster count or open-PR count fed into the hosted opportunityCompetitionFactor
can no longer silently poison the reward/risk score with NaN; it fails closed to maximal competition
pressure exactly like its already-hardened engine-mirror sibling does, so the two can never disagree on
this edge case again.

Links & Resources

  • packages/loopover-engine/src/opportunity-competition.ts — the already-hardened mirror to converge onto.
  • packages/loopover-engine/src/opportunity-freshness.ts — confirmed-clean sibling (checked, not part of this issue).

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions