Skip to content

fix(engine): applyContributorCalibration can return NaN and bypasses its own cold-start guard on a NaN sampleSize #6627

Description

@JSONbored

Context

packages/loopover-engine/src/signals/contributor-calibration.ts's applyContributorCalibration(baselineReadinessScore, calibration) adjusts a baseline readiness score by a contributor's own predict-vs-real calibration history. Its own field-level doc comment on ContributorCalibrationSignal.agreementRate states: "Clamped to [0, 1] before use, so a malformed upstream aggregate can never push the adjustment past its own clamp" — i.e. the function's documented contract is that it degrades gracefully for any malformed calibration input and always returns a valid [0, 100] score (or the unchanged baseline).

The cold-start guard on line 56 is if (!calibration || calibration.sampleSize < MIN_CALIBRATION_SAMPLES) return baselineReadinessScore;. This relies on JavaScript's < comparison, which is always false for NaN. A calibration.sampleSize of NaN therefore bypasses the cold-start guard entirely instead of being treated as "insufficient history."

The local clamp(value, min, max) helper on line 18 (return Math.min(max, Math.max(min, value));) also has no Number.isFinite guard, unlike its sibling normalizers elsewhere in this package (e.g. governor/reputation-throttle.ts's clampFraction, which is Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : fallback, or governor/rate-limit.ts/governor/budget-cap.ts's finiteNonNegativeInt/finiteNonNegative, both of which explicitly fall back to a safe default for non-finite input). Math.max(min, NaN) and Math.min(max, NaN) both evaluate to NaN, so a NaN agreementRate (or a NaN produced anywhere downstream) propagates straight through clamp() unclamped.

Verified empirically (node -e, using the exact logic from the file):

  • applyContributorCalibration(70, { sampleSize: NaN, agreementRate: 0.9 }) returns 78 — the cold-start guard is bypassed and a full calibration adjustment is applied from a malformed sample count instead of returning the unchanged baseline of 70.
  • applyContributorCalibration(70, { sampleSize: 20, agreementRate: NaN }) returns NaN — a non-numeric readiness score reaches the caller, violating the documented [0, 100]-clamped contract.

The existing test suite (test/unit/predicted-gate.test.ts, describe("applyContributorCalibration (#2349)")) has explicit "CLAMP BOUNDARY" tests for out-of-range finite values (agreementRate: 1.7, agreementRate: -0.4) but no test exercises a non-finite (NaN) sampleSize or agreementRate.

Requirements

  • packages/loopover-engine/src/signals/contributor-calibration.ts's clamp(value, min, max) helper must treat a non-finite value (checked via Number.isFinite) as min (i.e. the lowest end of the clamp range) rather than propagating NaN, mirroring the fail-toward-the-safe-end pattern already used by this package's other numeric normalizers (governor/reputation-throttle.ts's clampFraction, governor/rate-limit.ts's finiteNonNegativeInt).
  • applyContributorCalibration's cold-start guard must treat a non-finite calibration.sampleSize as "insufficient history" (i.e. return baselineReadinessScore unchanged), not fall through to computing an adjustment. Change the guard's condition so calibration.sampleSize < MIN_CALIBRATION_SAMPLES is only trusted when Number.isFinite(calibration.sampleSize) is true; a non-finite sampleSize must take the same "cold start" branch.
  • After this fix, applyContributorCalibration(70, { sampleSize: NaN, agreementRate: 0.9 }) must return 70 (the unchanged baseline), and applyContributorCalibration(70, { sampleSize: 20, agreementRate: NaN }) must return a finite number in [0, 100] (not NaN).
  • Do not change the function's behavior for any already-tested finite input (the existing "CLAMP BOUNDARY" tests in test/unit/predicted-gate.test.ts must continue to pass unchanged).

Deliverables

  • clamp() in packages/loopover-engine/src/signals/contributor-calibration.ts guards against non-finite input.
  • applyContributorCalibration's cold-start guard treats a non-finite sampleSize as insufficient history.
  • New unit tests in test/unit/predicted-gate.test.ts (alongside the existing applyContributorCalibration (#2349) describe block) covering: NaN sampleSize with a finite agreementRate, and a finite sampleSize with a NaN agreementRate.

Test Coverage Requirements

This repo's Codecov patch gate is 99%+ for src/**/packages/**. Both new branches (non-finite sampleSize, non-finite agreementRate) must be covered by new assertions in test/unit/predicted-gate.test.ts.

Expected Outcome

applyContributorCalibration can never return NaN, and a malformed/non-finite sampleSize is treated as insufficient calibration history (baseline returned unchanged) rather than silently applying a calibration adjustment — matching the function's own documented "malformed upstream aggregate can never push the adjustment past its own clamp" contract.

Links & Resources

  • packages/loopover-engine/src/signals/contributor-calibration.ts
  • test/unit/predicted-gate.test.ts (describe("applyContributorCalibration (#2349)"), starting around line 556)
  • Precedent for the fail-toward-safe-end normalization pattern: packages/loopover-engine/src/governor/reputation-throttle.ts's clampFraction, packages/loopover-engine/src/governor/rate-limit.ts's finiteNonNegativeInt

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