{"record":{"id":"c1a83a919bc777eb","repo":"JuliusBrussee/caveman","slug":"where-unproven-adjacency-edge-from-edge-t","errorCode":null,"errorMessage":"${where} unproven adjacency ${edge.from}->${edge.to} is in fact a proven hard dependency","messagePattern":"(.+?) unproven adjacency (.+?)->(.+?) is in fact a proven hard dependency","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/contracts/scripts/validate-continuous-improvement.mjs","lineNumber":201,"sourceCode":"      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\") {\n      if (item.change_set.operator_id !== \"\" || item.eval_pack.id !== \"\" || item.replay.status !== \"not_run\") {\n        throw new Error(`${where} is diagnosis-only but carries a change set, eval pack or replay`);\n      }\n      continue;","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/shared/contracts/scripts/validate-continuous-improvement.mjs#L183-L219","documentation":"Thrown when an entry in causal_slice.unproven_adjacencies satisfies provenDependency(): strength===\"hard\" AND type in {\"data\",\"control\"} AND evidence_refs.length > 0. The unproven list exists precisely to hold hypotheses that are NOT established dependencies; an entry that structurally reads as proven misrepresents speculation as evidence and fails validation.","triggerScenarios":"An unproven_adjacencies edge copied from a proven edge keeps strength \"hard\" and its evidence_refs array; or a hypothesis later gathered evidence (evidence_refs added, strength raised to \"hard\") but was left in the unproven list instead of being promoted to hard_edges.","commonSituations":"Promoting an adjacency to proven during analysis but forgetting to move it between lists; template/copy-paste from a hard_edges entry when drafting hypotheses.","solutions":["If the adjacency is genuinely proven, move it from unproven_adjacencies into hard_edges (it must then pass all hard-edge checks, including backing in the variant graph).","If it is not proven, strip the proof markers: set strength to something other than \"hard\" and/or empty its evidence_refs array.","Re-run the validator to confirm the lists are now disjoint and honest."],"exampleFix":"// before\nunproven_adjacencies: [{from:\"n1\",to:\"n2\",type:\"data\",strength:\"hard\",evidence_refs:[\"ev9\"]}]\n\n// after — either demote…\nunproven_adjacencies: [{from:\"n1\",to:\"n2\",type:\"data\",strength:\"speculative\",evidence_refs:[]}]\n// …or promote\nhard_edges: [{from:\"n1\",to:\"n2\",type:\"data\",strength:\"hard\",evidence_refs:[\"ev9\"]}]","handlingStrategy":"validation","validationCode":"const proven = (e) => e.strength === \"hard\" && (e.type === \"data\" || e.type === \"control\") && e.evidence_refs.length > 0;\nconst honest = slice.unproven_adjacencies.every((e) => !proven(e));","typeGuard":"function isHonestHypothesis(edge) {\n  return !(edge.strength === \"hard\" && (edge.type === \"data\" || edge.type === \"control\") && edge.evidence_refs.length > 0);\n}","tryCatchPattern":null,"preventionTips":["Use distinct builders/templates for hard_edges vs unproven_adjacencies so proof fields never leak into hypotheses.","When promoting an adjacency to proven, implement it as a move between lists, not an in-place field edit."],"tags":["validation","causal-graph","evidence-integrity"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}