Repository navigation
fix(engine): applyContributorCalibration can return NaN and bypasses its own cold-start guard on a NaN sampleSize #6627
Description
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 })returns78— the cold-start guard is bypassed and a full calibration adjustment is applied from a malformed sample count instead of returning the unchanged baseline of70.applyContributorCalibration(70, { sampleSize: 20, agreementRate: NaN })returnsNaN— 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'sclamp(value, min, max)helper must treat a non-finitevalue(checked viaNumber.isFinite) asmin(i.e. the lowest end of the clamp range) rather than propagatingNaN, mirroring the fail-toward-the-safe-end pattern already used by this package's other numeric normalizers (governor/reputation-throttle.ts'sclampFraction,governor/rate-limit.ts'sfiniteNonNegativeInt).applyContributorCalibration's cold-start guard must treat a non-finitecalibration.sampleSizeas "insufficient history" (i.e. returnbaselineReadinessScoreunchanged), not fall through to computing an adjustment. Change the guard's condition socalibration.sampleSize < MIN_CALIBRATION_SAMPLESis only trusted whenNumber.isFinite(calibration.sampleSize)istrue; a non-finitesampleSizemust take the same "cold start" branch.- After this fix,
applyContributorCalibration(70, { sampleSize: NaN, agreementRate: 0.9 })must return70(the unchanged baseline), andapplyContributorCalibration(70, { sampleSize: 20, agreementRate: NaN })must return a finite number in[0, 100](notNaN). - Do not change the function's behavior for any already-tested finite input (the existing "CLAMP BOUNDARY" tests in
test/unit/predicted-gate.test.tsmust continue to pass unchanged).
Deliverables
-
clamp()inpackages/loopover-engine/src/signals/contributor-calibration.tsguards against non-finite input. -
applyContributorCalibration's cold-start guard treats a non-finitesampleSizeas insufficient history. - New unit tests in
test/unit/predicted-gate.test.ts(alongside the existingapplyContributorCalibration (#2349)describe block) covering:NaNsampleSizewith a finiteagreementRate, and a finitesampleSizewith aNaNagreementRate.
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.tstest/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'sclampFraction,packages/loopover-engine/src/governor/rate-limit.ts'sfiniteNonNegativeInt
Activity
linear-code commentedon Jul 16, 2026
JSO-4525
fix(engine): treat non-finite calibration inputs as cold-start / safe…