JuliusBrussee/caveman · error · Error
${where} unproven adjacency ${edge.from}->${edge.to} is in f
Error message
${where} unproven adjacency ${edge.from}->${edge.to} is in fact a proven hard dependency What it means
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.
Source
Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:201
if (!sliceNodes.has(nodeID)) throw new Error(`${where} causal slice node ${nodeID} is not a node of variant ${sliceVariant.id}`);
}
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;View on GitHub (pinned to 27d5a3981a)
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.
Example fix
// before
unproven_adjacencies: [{from:"n1",to:"n2",type:"data",strength:"hard",evidence_refs:["ev9"]}]
// after — either demote…
unproven_adjacencies: [{from:"n1",to:"n2",type:"data",strength:"speculative",evidence_refs:[]}]
// …or promote
hard_edges: [{from:"n1",to:"n2",type:"data",strength:"hard",evidence_refs:["ev9"]}] Defensive patterns
Strategy: validation
Validate before calling
const proven = (e) => e.strength === "hard" && (e.type === "data" || e.type === "control") && e.evidence_refs.length > 0; const honest = slice.unproven_adjacencies.every((e) => !proven(e));
Type guard
function isHonestHypothesis(edge) {
return !(edge.strength === "hard" && (edge.type === "data" || edge.type === "control") && edge.evidence_refs.length > 0);
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ${where} hard chain edge ${edge.from}->${edge.to} is not a h
- ${where} adjacency ${edge.from}->${edge.to} appears in both
- ${where} diagnosis upstream node ${nodeID} is not an upstrea
- option not found
- cave_harness_adapter_version_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/c1a83a919bc777eb.
Report an issue: GitHub.