{"record":{"id":"73c29a671b61cea4","repo":"JuliusBrussee/caveman","slug":"where-hard-chain-carries-an-unproven-edge-edg","errorCode":null,"errorMessage":"${where} hard chain carries an unproven edge ${edge.from}->${edge.to}","messagePattern":"(.+?) hard chain carries an unproven edge (.+?)->(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/contracts/scripts/validate-continuous-improvement.mjs","lineNumber":191,"sourceCode":"    if (!sliceVariant) throw new Error(`${where} causal slice references a workflow variant that is not in this report`);\n    if (sliceVariant.id !== armVariants.get(\"baseline\").id) throw new Error(`${where} causal slice is not taken from the baseline arm's variant`);\n    const sliceNodes = new Set(sliceVariant.nodes.map((node) => node.id));\n    if (slice.manifestation_node_id !== \"\" && !sliceNodes.has(slice.manifestation_node_id)) {\n      throw new Error(`${where} manifestation node ${slice.manifestation_node_id} is not a node of variant ${sliceVariant.id}`);\n    }\n    if (item.diagnosis.manifestation.node_id !== slice.manifestation_node_id) {\n      throw new Error(`${where} diagnosis manifestation node disagrees with its causal slice`);\n    }\n    for (const nodeID of slice.node_chain) {\n      if (!sliceNodes.has(nodeID)) throw new Error(`${where} causal slice node ${nodeID} is not a node of variant ${sliceVariant.id}`);\n    }\n    if (slice.manifestation_node_id !== \"\" && !slice.node_chain.includes(slice.manifestation_node_id)) {\n      throw new Error(`${where} causal slice omits its own manifestation node`);\n    }\n    const provenDependency = (edge) => edge.strength === \"hard\" && (edge.type === \"data\" || edge.type === \"control\") && edge.evidence_refs.length > 0;\n    const variantEdges = new Map(sliceVariant.edges.map((edge) => [`${edge.from}\u0000${edge.to}\u0000${edge.type}`, edge]));\n    for (const edge of slice.hard_edges) {\n      if (!provenDependency(edge)) throw new Error(`${where} hard chain carries an unproven edge ${edge.from}->${edge.to}`);\n      if (!sliceNodes.has(edge.from) || !sliceNodes.has(edge.to)) throw new Error(`${where} hard chain edge ${edge.from}->${edge.to} names a node outside variant ${sliceVariant.id}`);\n      if (!slice.node_chain.includes(edge.from) || !slice.node_chain.includes(edge.to)) {\n        throw new Error(`${where} hard chain edge ${edge.from}->${edge.to} is outside the slice's node chain`);\n      }\n      const source = variantEdges.get(`${edge.from}\u0000${edge.to}\u0000${edge.type}`);\n      if (!source || source.strength !== \"hard\") throw new Error(`${where} hard chain edge ${edge.from}->${edge.to} is not a hard edge of variant ${sliceVariant.id}`);\n    }\n    for (const edge of slice.unproven_adjacencies) {\n      // The whole point of the separate list: nothing in it may read as proven.\n      if (provenDependency(edge)) throw new Error(`${where} unproven adjacency ${edge.from}->${edge.to} is in fact a proven hard dependency`);\n      if (slice.hard_edges.some((hard) => hard.from === edge.from && hard.to === edge.to && hard.type === edge.type)) {\n        throw new Error(`${where} adjacency ${edge.from}->${edge.to} appears in both the hard chain and the unproven list`);\n      }\n    }\n    const upstream = new Set(item.diagnosis.root_cause_hypothesis.upstream_node_ids);\n    for (const nodeID of upstream) {\n      if (!slice.node_chain.includes(nodeID) || nodeID === slice.manifestation_node_id) {\n        throw new Error(`${where} diagnosis upstream node ${nodeID} is not an upstream node of its causal slice`);","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/shared/contracts/scripts/validate-continuous-improvement.mjs#L173-L209","documentation":"Every edge in causal_slice.hard_edges must be a proven dependency: strength \"hard\", type \"data\" or \"control\", and a non-empty evidence_refs list (validate-continuous-improvement.mjs:188-191). 'Hard' means the dependency is backed by recorded evidence; a soft edge, an unproven type such as \"timing\", or an edge whose evidence_refs were stripped fails here — it belongs in unproven_adjacencies instead, where a companion check (line 201) rejects exactly these proven-looking shapes.","triggerScenarios":"A soft or timing adjacency was promoted into hard_edges; an edge was copied between fixtures and its evidence_refs array came along empty; a generator marked plain control-flow order as hard without citing span evidence.","commonSituations":"Hand-upgrading adjacencies to 'hard' to make a slice look stronger; generators that emit all edges as hard by default; refactors that dropped the evidence_refs field while reshaping edges.","solutions":["Move edges that lack proof into causal_slice.unproven_adjacencies (they must not be hard/data|control/with-evidence there, and must not duplicate a hard_edges entry).","Or make the edge genuinely proven: strength \"hard\", type \"data\" or \"control\", and evidence_refs citing real span ids.","Re-run the validator — subsequent checks (lines 192-197) then verify the edge's nodes and its presence in the variant's own edge set."],"exampleFix":"// before\n{ \"from\": \"node/parse\", \"to\": \"node/exec\", \"type\": \"timing\", \"strength\": \"hard\", \"evidence_refs\": [] }\n// after — proven, or demote to unproven_adjacencies\n{ \"from\": \"node/parse\", \"to\": \"node/exec\", \"type\": \"data\", \"strength\": \"hard\", \"evidence_refs\": [\"span/abc123\"] }","handlingStrategy":"validation","validationCode":"const unprovenHardEdges = (report) =>\n  report.cases.flatMap((c) =>\n    c.causal_slice.hard_edges\n      .filter((e) => !(e.strength === 'hard' && (e.type === 'data' || e.type === 'control') && e.evidence_refs.length > 0))\n      .map((e) => `${c.id}:${e.from}->${e.to}`));","typeGuard":null,"tryCatchPattern":"try {\n  execFileSync(process.execPath, [VALIDATOR, reportPath, spansPath]);\n} catch (err) {\n  if (/unproven edge/.test(err.message)) {\n    failCI(`hard_edges carries an edge without proof — demote it to unproven_adjacencies or cite evidence: ${err.message}`);\n  } else throw err;\n}","preventionTips":["Only mark an edge hard when a recorded span id backs it (non-empty evidence_refs, type data or control).","Keep soft/timing adjacencies in unproven_adjacencies — never in both lists.","Treat 'hard' as an evidence claim, not a strength slider: no evidence, no hard edge."],"tags":["fixtures","cross-validation","causal-slice","hard-edges","evidence"],"backgroundTag":"missing-evidence-refs","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}