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

  1. Ensure testsRun is a non-negative integer: Math.max(0, Math.floor(n)).
  2. Default to 0 when the ledger is empty/uninitialised.
  3. 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

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


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/02deebf5cabaae93. Report an issue: GitHub.