{"record":{"id":"9be2cac3792393c7","repo":"ruvnet/ruflo","slug":"duplicate-declared-build-input","errorCode":null,"errorMessage":"duplicate declared build input","messagePattern":"duplicate declared build input","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/harness/build-evidence.ts","lineNumber":123,"sourceCode":"  toolchains: readonly DeclaredToolchain[],\n): BuildEvidence {\n  const inputs = buildInputs.map((input) => ({\n    name: requireText(input.name, 'build input name'),\n    path: normalizePath(input.path),\n    digest: requireDigest(input.digest, 'build input digest'),\n    bytes: input.bytes,\n  })).sort((left, right) => compare(left.path, right.path) || compare(left.name, right.name));\n  const tools = toolchains.map((toolchain) => ({\n    name: requireText(toolchain.name, 'toolchain name'),\n    version: requireText(toolchain.version, 'toolchain version'),\n    digest: requireDigest(toolchain.digest, 'toolchain digest'),\n  })).sort((left, right) => compare(left.name, right.name) || compare(left.version, right.version));\n\n  if (inputs.some(({ bytes }) => !Number.isSafeInteger(bytes) || bytes < 0)) {\n    throw new Error('build input bytes must be a non-negative safe integer');\n  }\n  const inputKeys = inputs.map(({ name, path }) => `${name}\\0${path}`);\n  if (new Set(inputKeys).size !== inputKeys.length) throw new Error('duplicate declared build input');\n  const foldedPaths = inputs.map(({ path }) => portableCaseFold(path));\n  if (new Set(foldedPaths).size !== foldedPaths.length) {\n    throw new Error('case-fold collision in declared build inputs');\n  }\n  const toolKeys = tools.map(({ name, version }) => `${name}\\0${version}`);\n  if (new Set(toolKeys).size !== toolKeys.length) throw new Error('duplicate declared toolchain');\n\n  const body = {\n    contractVersion: 1 as const,\n    assurance: 'declared-unsigned' as const,\n    sourceStateId: requireDigest(sourceState.sourceStateId, 'source state id'),\n    buildInputs: inputs,\n    toolchains: tools,\n  };\n  return { ...body, evidenceDigest: sha256(canonicalJson(body)) };\n}\n\n/**","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/harness/build-evidence.ts#L105-L141","documentation":"createBuildEvidence keys every declared build input by the pair name\\0path and throws if the same pair appears twice. The contract refuses to silently merge duplicate declarations because each (name, path) pair binds a distinct digest in the hashed evidence body; deduplication is the caller's responsibility.","triggerScenarios":"Two DeclaredBuildInput entries with identical name and path, typically from concatenating declaration lists (defaults + overrides) without merging, or from a generator that appends on each run instead of replacing.","commonSituations":"Layered configuration where preset inputs and user inputs are simply concatenated; re-running an initialization step that appends the same declaration again; merging evidence requests from multiple components.","solutions":["Deduplicate by `${name}\\0${path}` before calling createBuildEvidence","If the same path must be declared twice, give each entry a distinct logical name","When merging lists from multiple sources, build a Map keyed by the pair so later entries replace earlier ones deliberately"],"exampleFix":"// before\nconst buildInputs = [\n  { name: 'bundle', path: 'dist/app.js', digest: d, bytes: 1 },\n  { name: 'bundle', path: 'dist/app.js', digest: d, bytes: 1 }, // duplicate pair\n];\n\n// after\nconst buildInputs = [...new Map(\n  rawInputs.map((i) => [`${i.name}\\0${i.path}`, i]),\n).values()];","handlingStrategy":"validation","validationCode":"function dedupeInputs<T extends { name: string; path: string }>(inputs: T[]): T[] {\n  return [...new Map(inputs.map((i) => [`${i.name}\\0${i.path}`, i])).values()];\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Merge declaration lists through a keyed Map instead of array concat","Make re-runs idempotent: upsert by (name, path), never append","Unit-test the merge path with overlapping default and user inputs"],"tags":["build-evidence","duplicate","validation","harness"],"backgroundTag":"duplicate-config-entry","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}