JuliusBrussee/caveman · error · Error

${where} adjacency ${edge.from}->${edge.to} appears in both

Error message

${where} adjacency ${edge.from}->${edge.to} appears in both the hard chain and the unproven list

What it means

Thrown when the same (from, to, type) edge appears in both causal_slice.hard_edges and causal_slice.unproven_adjacencies. The two lists must be disjoint: an edge cannot simultaneously be a proven hard dependency and an unproven adjacency, because readers could not tell which claim the slice makes.

Source

Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:203

    if (slice.manifestation_node_id !== "" && !slice.node_chain.includes(slice.manifestation_node_id)) {
      throw new Error(`${where} causal slice omits its own manifestation node`);
    }
    const provenDependency = (edge) => edge.strength === "hard" && (edge.type === "data" || edge.type === "control") && edge.evidence_refs.length > 0;
    const variantEdges = new Map(sliceVariant.edges.map((edge) => [`${edge.from}${edge.to}${edge.type}`, edge]));
    for (const edge of slice.hard_edges) {
      if (!provenDependency(edge)) throw new Error(`${where} hard chain carries an unproven edge ${edge.from}->${edge.to}`);
      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}`);
      if (!slice.node_chain.includes(edge.from) || !slice.node_chain.includes(edge.to)) {
        throw new Error(`${where} hard chain edge ${edge.from}->${edge.to} is outside the slice's node chain`);
      }
      const source = variantEdges.get(`${edge.from}${edge.to}${edge.type}`);
      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}`);
    }
    for (const edge of slice.unproven_adjacencies) {
      // The whole point of the separate list: nothing in it may read as proven.
      if (provenDependency(edge)) throw new Error(`${where} unproven adjacency ${edge.from}->${edge.to} is in fact a proven hard dependency`);
      if (slice.hard_edges.some((hard) => hard.from === edge.from && hard.to === edge.to && hard.type === edge.type)) {
        throw new Error(`${where} adjacency ${edge.from}->${edge.to} appears in both the hard chain and the unproven list`);
      }
    }
    const upstream = new Set(item.diagnosis.root_cause_hypothesis.upstream_node_ids);
    for (const nodeID of upstream) {
      if (!slice.node_chain.includes(nodeID) || nodeID === slice.manifestation_node_id) {
        throw new Error(`${where} diagnosis upstream node ${nodeID} is not an upstream node of its causal slice`);
      }
    }

    // A case that proposes nothing must carry nothing: no operator, no eval
    // pack, no replay. Anything else would present an abstention as a proposal.
    if (item.status === "diagnosis_only") {
      if (item.change_set.operator_id !== "" || item.eval_pack.id !== "" || item.replay.status !== "not_run") {
        throw new Error(`${where} is diagnosis-only but carries a change set, eval pack or replay`);
      }
      continue;
    }
    if (item.change_set.case_id !== item.id || item.eval_pack.case_id !== item.id) {

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Decide the edge's status once and keep it in exactly one list: delete it from unproven_adjacencies if it belongs to hard_edges, or from hard_edges if it is a hypothesis.
  2. Audit the slice for other duplicate triples between the two lists (same from/to but different type will NOT be caught here but usually signals a modeling mistake).
  3. Regenerate the slice from the analysis tooling if it was hand-merged.

Example fix

// before
hard_edges: [{from:"n1",to:"n2",type:"data",...}]
unproven_adjacencies: [{from:"n1",to:"n2",type:"data",...}]

// after
hard_edges: [{from:"n1",to:"n2",type:"data",...}]
unproven_adjacencies: []
Defensive patterns

Strategy: validation

Validate before calling

const hardKey = (e) => `${e.from}|${e.to}|${e.type}`;
const hard = new Set(slice.hard_edges.map(hardKey));
const disjoint = slice.unproven_adjacencies.every((e) => !hard.has(hardKey(e)));

Prevention

When it happens

Trigger: Adding an edge to unproven_adjacencies while its proven twin remains in hard_edges (or vice versa) — the dedup check compares from, to and type exactly, so only fully identical triples trigger it.

Common situations: Hand-editing a slice during diagnosis iteration; merging slices from two drafts; promoting/demoting an edge without deleting the original entry.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/6efa04ffabbb2fce. Report an issue: GitHub.