ruvnet/ruflo · error · RangeError
testsRun must be a non-negative integer
Error message
testsRun must be a non-negative integer
What it means
Thrown by remainingAlphaBudget() as a RangeError when testsRun is not a non-negative integer. remainingAlphaBudget computes how much of the family-wise alphaTotal budget remains after k allocated tests (alphaTotal - sum of alpha_k). Negative or fractional counts are meaningless for the spend ledger.
Source
Thrown at v3/@claude-flow/cli/src/services/flywheel-sequential-evidence.ts:142
/**
* Minimum number of INFORMATIVE (discordant) pairs a candidate must win —
* with zero losses — to clear the k-th test's threshold: the smallest n with
* (1+lambda)^n >= 1/alpha_k. The pre-flight power check (ADR-381 §4): an
* evaluation whose promotion holdout is smaller than this cannot promote even
* on a perfect sweep, so it should be refused BEFORE compute is spent and
* before a doomed receipt can be presented to the gate (spending alpha).
*/
export function minInformativePairsToClear(testIndex: number, config: SequentialEvidenceConfig = {}): number {
const alphaTotal = config.alphaTotal ?? DEFAULT_ALPHA_TOTAL;
const lambda = config.lambda ?? DEFAULT_LAMBDA;
if (!(lambda > 0 && lambda < 1)) throw new RangeError('lambda must be in (0, 1)');
const threshold = 1 / alphaForTest(testIndex, alphaTotal);
return Math.ceil(Math.log(threshold) / Math.log(1 + lambda));
}
/** Family-wise budget left after `testsRun` allocated tests: alphaTotal - Σ alpha_k. */
export function remainingAlphaBudget(testsRun: number, alphaTotal = DEFAULT_ALPHA_TOTAL): number {
if (!Number.isInteger(testsRun) || testsRun < 0) throw new RangeError('testsRun must be a non-negative integer');
let spent = 0;
for (let k = 1; k <= testsRun; k++) spent += alphaForTest(k, alphaTotal);
return Math.max(0, alphaTotal - spent);
}
export interface PairedEvidenceCheck {
ok: boolean;
reasons: string[];
}
/**
* Structural consistency between a receipt's paired outcomes and its
* aggregate heldOutDeltas: same length and order, unique non-empty task IDs,
* and each delta must equal candidateScore - baselineScore. This is what makes
* paired outcomes EVIDENCE rather than decoration — an aggregate that cannot
* be reproduced from its own per-task rows is refused.
*/
export function checkPairedOutcomesConsistency(View on GitHub (pinned to 6b01dc5a68)
Solutions
- Ensure testsRun is a non-negative integer: Math.max(0, Math.floor(n)).
- Default to 0 when the ledger is empty/uninitialised.
- Validate with Number.isInteger(testsRun) && testsRun >= 0 before calling.
Example fix
// before remainingAlphaBudget(state.sequentialTests ? Object.keys(state.sequentialTests).length : -1); // after remainingAlphaBudget(state.sequentialTests ? Object.keys(state.sequentialTests).length : 0);
Defensive patterns
Strategy: validation
Validate before calling
if (!Number.isInteger(testsRun) || testsRun < 0) {
throw new RangeError(`testsRun must be a non-negative integer, got ${testsRun}`);
} Type guard
const isNonNegativeInt = (x: unknown): x is number => typeof x === 'number' && Number.isInteger(x) && x >= 0;
Prevention
- Default testsRun to 0 when the ledger is empty rather than using -1 sentinels.
- Floor any computed counts before passing: Math.max(0, Math.floor(n)).
- Derive testsRun from Object.keys(state.sequentialTests ?? {}).length.
When it happens
Trigger: Calling remainingAlphaBudget(-1), remainingAlphaBudget(2.5), or passing a non-integer count derived from a float computation or an uninitialised field.
Common situations: A UI/CLI that passes the raw sequentialTests map size before validating it's a non-negative integer; a default value of -1 used as a sentinel that leaked into the call.
Related errors
- testIndex must be a positive integer
- lambda must be in (0, 1)
- metric must be finite
- bootstrap iterations must be >= 100
- unknown game "${key}". Known: ${Object.keys(GAMES).join(', '
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/02deebf5cabaae93.
Report an issue: GitHub.