{"record":{"id":"9e8190755ae63357","repo":"ruvnet/ruflo","slug":"at-least-one-candidate-is-required","errorCode":null,"errorMessage":"at least one candidate is required","messagePattern":"at least one candidate is required","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/agenticow-speculate-tools.ts","lineNumber":146,"sourceCode":"          description: 'ADR-171 fail-closed gate. When true, the top-scored winner is NOT promoted (base stays unchanged) and a provenance-tagged receipt is emitted — score alone cannot graduate work. Use when speculating over TASK outcomes rather than pure memory A/B. Default false (score-only promotion, tagged `unverified`).',\n          default: false,\n        },\n      },\n      required: ['basePath', 'candidates'],\n    },\n    handler: async (input) => {\n      const api = await loadAgenticow();\n      if (!api) return degradedResult('agenticow-not-found');\n\n      const basePath = resolveMemoryPath(String(input.basePath));\n      const dimension = input.dimension as number | undefined;\n      const scoreBy = (input.scoreBy as string) === 'count' ? 'count' : 'nearest';\n      const k = Number.isInteger(input.k) && (input.k as number) > 0 ? (input.k as number) : 1;\n      const probe = Array.isArray(input.probe) ? (input.probe as number[]) : null;\n\n      const rawCandidates = input.candidates as CandidateInput[];\n      if (!Array.isArray(rawCandidates) || rawCandidates.length === 0) {\n        throw new Error('at least one candidate is required');\n      }\n      if (scoreBy === 'nearest' && !probe) {\n        throw new Error(\"scoreBy='nearest' requires a probe vector\");\n      }\n\n      // Build the generic {label, fn} candidates. Each fn ingests into its own\n      // branch handle, then (for 'nearest') probes it so we can score.\n      // Map validated label → explicit branchPath so the branchPath() resolver\n      // below is O(1) instead of re-scanning + re-validating rawCandidates per\n      // candidate (explore() calls branchPath once per candidate → was O(n²)).\n      const explicitBranchPaths = new Map<string, string>();\n      const candidates: SpeculativeCandidate<CandidateOutcome>[] = rawCandidates.map((c) => {\n        const label = validateLabel(String(c.label));\n        if (!Array.isArray(c.ingest) || c.ingest.length === 0) {\n          throw new Error(`candidate ${label} must ingest at least one vector`);\n        }\n        if (typeof c.branchPath === 'string' && c.branchPath) {\n          explicitBranchPaths.set(label, c.branchPath);","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/mcp-tools/agenticow-speculate-tools.ts#L128-L164","documentation":"Thrown by the agenticow speculate tool handler when input.candidates is not an array or is empty. Speculation requires at least one candidate (each candidate ingests into its own branch and is scored against the probe); with zero candidates there is nothing to rank and the winner-promotion step is undefined, so the handler rejects early before touching the agenticow API.","triggerScenarios":"Calling speculate without candidates; passing candidates as an empty array; passing a single object instead of an array; a pipeline that filtered out all candidates before invoking.","commonSituations":"Integrator built the candidates list conditionally and hit the empty branch; a fixture placeholder `candidates: []`; a serializer that returned undefined for the field; all candidates were filtered out by an upstream rule.","solutions":["Pass a non-empty array of candidate objects, each with at least `label` and `ingest`.","If your pipeline legitimately produces zero candidates, skip the speculate call rather than invoking with an empty array.","Validate upstream: `Array.isArray(c) && c.length > 0` before calling the tool."],"exampleFix":"// before\nspeculate({ basePath, candidates: [], scoreBy: 'nearest', probe: [0.1] }); // throws\n// after\nspeculate({ basePath, candidates: [{ label: 'a', ingest: [{vector:[...]}] }], scoreBy: 'nearest', probe: [0.1] });","handlingStrategy":"validation","validationCode":"function requireCandidates(c: unknown): Array<{ label: string; ingest: unknown[] }> {\n  if (!Array.isArray(c) || c.length === 0) throw new Error('candidates must be a non-empty array');\n  return c as Array<{ label: string; ingest: unknown[] }>;\n}","typeGuard":"const isNonEmptyArray = (v: unknown): v is unknown[] => Array.isArray(v) && v.length > 0;","tryCatchPattern":"null","preventionTips":["Validate candidates is a non-empty array at your boundary before calling speculate.","Skip the speculate call entirely when your pipeline legitimately has zero candidates.","Add a unit test for the empty-array path so it surfaces in your layer."],"tags":["validation","agenticow","speculate","candidates","required-field"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}