{"record":{"id":"c11c8fa2a31580a3","repo":"ruvnet/ruflo","slug":"testindex-must-be-a-positive-integer","errorCode":null,"errorMessage":"testIndex must be a positive integer","messagePattern":"testIndex must be a positive integer","errorType":"validation","errorClass":"RangeError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/flywheel-sequential-evidence.ts","lineNumber":79,"sourceCode":"\nexport interface SequentialEvidenceVerdict {\n  significant: boolean;\n  eValue: number;\n  threshold: number;        // 1 / alphaAllocated\n  alphaAllocated: number;   // this test's share of the family-wise budget\n  testIndex: number;        // 1-based position in the lineage's test stream\n  informativePairs: number; // discordant pairs — the only ones carrying signal\n  totalPairs: number;\n  version: typeof SEQUENTIAL_EVIDENCE_VERSION;\n}\n\n/**\n * Alpha share for the k-th test in the stream: alphaTotal * 6/(pi^2 k^2).\n * Chosen over 2^-k because it decays polynomially — test 10 still gets a\n * workable ~0.6% of a 5% budget instead of ~0.005%.\n */\nexport function alphaForTest(testIndex: number, alphaTotal = DEFAULT_ALPHA_TOTAL): number {\n  if (!Number.isInteger(testIndex) || testIndex < 1) throw new RangeError('testIndex must be a positive integer');\n  if (!(alphaTotal > 0 && alphaTotal < 1)) throw new RangeError('alphaTotal must be in (0, 1)');\n  return (alphaTotal * 6) / (Math.PI * Math.PI * testIndex * testIndex);\n}\n\n/**\n * Fold paired outcomes into an anytime-valid e-value and judge it against the\n * k-th test's allocated alpha. Deterministic; order of outcomes does not\n * change the final e-value (the product commutes).\n */\nexport function sequentialEvidenceVerdict(\n  outcomes: PairedTaskOutcome[],\n  testIndex: number,\n  config: SequentialEvidenceConfig = {},\n): SequentialEvidenceVerdict {\n  const alphaTotal = config.alphaTotal ?? DEFAULT_ALPHA_TOTAL;\n  const lambda = config.lambda ?? DEFAULT_LAMBDA;\n  const epsilon = config.epsilon ?? DEFAULT_SCORE_EPSILON;\n  if (!(lambda > 0 && lambda < 1)) throw new RangeError('lambda must be in (0, 1)');","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/services/flywheel-sequential-evidence.ts#L61-L97","documentation":"Thrown by alphaForTest() as a RangeError when testIndex is not a positive integer. testIndex is the 1-based position of this candidate in the lineage's sequential test stream; it determines the alpha share (alphaTotal * 6/(pi^2 * k^2)). Zero, negative, or fractional indices are meaningless for the family-wise error budget. Called transitively by sequentialEvidenceVerdict, minInformativePairsToClear, and remainingAlphaBudget.","triggerScenarios":"Calling alphaForTest(0), alphaForTest(-1), alphaForTest(1.5), or sequentialEvidenceVerdict(outcomes, 0). Also if a caller computes testIndex from an empty/zero-based ledger without converting to 1-based.","commonSituations":"A transaction state with zero prior tests where the caller passes the raw length (0) instead of length+1; a 0-based array index used directly; an uninitialised sequentialTests ledger.","solutions":["Use a 1-based index: testIndex = Object.keys(state.sequentialTests).length + 1 (the transaction layer allocates this).","Validate before calling: if (!Number.isInteger(k) || k < 1) throw.","Let the flywheel-transaction promotion path allocate the index rather than computing it manually."],"exampleFix":"// before\nconst k = state.sequentialTests?.[receiptId] ?? 0; // 0-based, will throw\nsequentialEvidenceVerdict(outcomes, k);\n// after\nconst k = nextTestIndex(state); // returns 1-based positive integer\nsequentialEvidenceVerdict(outcomes, k);","handlingStrategy":"validation","validationCode":"function assertPositiveInt(x: number, name: string): void {\n  if (!Number.isInteger(x) || x < 1) throw new RangeError(`${name} must be a positive integer, got ${x}`);\n}\nassertPositiveInt(testIndex, 'testIndex');\nalphaForTest(testIndex);","typeGuard":"const isPositiveInt = (x: unknown): x is number => typeof x === 'number' && Number.isInteger(x) && x >= 1;","tryCatchPattern":null,"preventionTips":["Always allocate testIndex from the transaction ledger (1-based), never compute it ad hoc.","Convert 0-based array indices to 1-based before passing.","Unit-test the sequential evidence functions with k=1 as the minimum."],"tags":["validation","statistics","sequential-evidence","range-error"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}