{"record":{"id":"56ee83953e047c29","repo":"JuliusBrussee/caveman","slug":"where-hard-chain-edge-edge-from-edge-to","errorCode":null,"errorMessage":"${where} hard chain edge ${edge.from}->${edge.to} is not a hard edge of variant ${sliceVariant.id}","messagePattern":"(.+?) hard chain edge (.+?)->(.+?) is not a hard edge of variant (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/contracts/scripts/validate-continuous-improvement.mjs","lineNumber":197,"sourceCode":"    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`);\n      }\n    }\n\n    // A case that proposes nothing must carry nothing: no operator, no eval\n    // pack, no replay. Anything else would present an abstention as a proposal.\n    if (item.status === \"diagnosis_only\") {","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/shared/contracts/scripts/validate-continuous-improvement.mjs#L179-L215","documentation":"Thrown by the continuous-improvement report validator when a causal slice's hard_edges entry (from->to) does not correspond to an edge in the baseline workflow variant with the same from/to/type AND strength===\"hard\". The validator builds a Map of the variant's edges keyed \"from to type\" and requires every hard chain edge to be backed by a hard-strength edge in the variant graph, so a slice cannot claim proven causality the variant graph does not assert.","triggerScenarios":"A case's causal_slice.hard_edges contains an edge whose (from, to, type) triple matches no edge in variantsByID.get(slice.variant_id).edges, or matches one whose strength is \"soft\"/\"speculative\". Common when the slice was authored against an older revision of the variant graph, or when the type (data vs control) was mistyped.","commonSituations":"Regenerating a workflow variant (renaming/re-typing nodes or edges) without regenerating the causal slices that cite it; hand-editing slice JSON; copying a slice between reports whose variant graphs differ.","solutions":["Open the baseline variant referenced by slice.variant_id and copy the exact from, to, type and strength:\"hard\" values into the slice's hard_edges entry.","If the variant edge is genuinely soft, remove the edge from hard_edges (and optionally list it in unproven_adjacencies with strength != \"hard\").","If the variant edge should be hard (it has evidence), set its strength to \"hard\" and ensure evidence_refs is non-empty in the variant graph, then re-run the validator.","Regenerate the whole report from the current variant graphs instead of hand-patching the slice."],"exampleFix":"// before (slice JSON)\n{\"from\": \"nodeA\", \"to\": \"nodeB\", \"type\": \"control\", \"strength\": \"hard\", \"evidence_refs\": [\"e1\"]}\n// variant has nodeA->nodeB as type \"data\"\n\n// after — match the variant's edge type\n{\"from\": \"nodeA\", \"to\": \"nodeB\", \"type\": \"data\", \"strength\": \"hard\", \"evidence_refs\": [\"e1\"]}","handlingStrategy":"validation","validationCode":"function hardEdgeBacked(slice, variant) {\n  const variantEdges = new Map(variant.edges.map((e) => [`${e.from} ${e.to} ${e.type}`, e]));\n  return slice.hard_edges.every((e) => {\n    const src = variantEdges.get(`${e.from} ${e.to} ${e.type}`);\n    return src !== undefined && src.strength === \"hard\";\n  });\n}","typeGuard":"function isBackedHardEdge(edge, variantEdges) {\n  const src = variantEdges.get(`${edge.from} ${edge.to} ${edge.type}`);\n  return src !== undefined && src.strength === \"hard\" && edge.strength === \"hard\";\n}","tryCatchPattern":null,"preventionTips":["Generate causal slices programmatically from the variant graph so hard_edges are copied from real hard edges.","Treat variant regeneration as invalidating all slices that reference it; regenerate them in the same change.","Run the validator in CI on every report edit so graph/slice drift is caught at commit time."],"tags":["validation","causal-graph","data-integrity"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}