JuliusBrussee/caveman · error · Error
${where} hard chain carries an unproven edge ${edge.from}->$
Error message
${where} hard chain carries an unproven edge ${edge.from}->${edge.to} What it means
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.
Source
Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:191
if (!sliceVariant) throw new Error(`${where} causal slice references a workflow variant that is not in this report`);
if (sliceVariant.id !== armVariants.get("baseline").id) throw new Error(`${where} causal slice is not taken from the baseline arm's variant`);
const sliceNodes = new Set(sliceVariant.nodes.map((node) => node.id));
if (slice.manifestation_node_id !== "" && !sliceNodes.has(slice.manifestation_node_id)) {
throw new Error(`${where} manifestation node ${slice.manifestation_node_id} is not a node of variant ${sliceVariant.id}`);
}
if (item.diagnosis.manifestation.node_id !== slice.manifestation_node_id) {
throw new Error(`${where} diagnosis manifestation node disagrees with its causal slice`);
}
for (const nodeID of slice.node_chain) {
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`);View on GitHub (pinned to 766dce6b13)
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.
Example fix
// before
{ "from": "node/parse", "to": "node/exec", "type": "timing", "strength": "hard", "evidence_refs": [] }
// after — proven, or demote to unproven_adjacencies
{ "from": "node/parse", "to": "node/exec", "type": "data", "strength": "hard", "evidence_refs": ["span/abc123"] } Defensive patterns
Strategy: validation
Validate before calling
const unprovenHardEdges = (report) =>
report.cases.flatMap((c) =>
c.causal_slice.hard_edges
.filter((e) => !(e.strength === 'hard' && (e.type === 'data' || e.type === 'control') && e.evidence_refs.length > 0))
.map((e) => `${c.id}:${e.from}->${e.to}`)); Try / catch
try {
execFileSync(process.execPath, [VALIDATOR, reportPath, spansPath]);
} catch (err) {
if (/unproven edge/.test(err.message)) {
failCI(`hard_edges carries an edge without proof — demote it to unproven_adjacencies or cite evidence: ${err.message}`);
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ${where} causal slice references a workflow variant that is
- ${where} causal slice is not taken from the baseline arm's v
- ${where} manifestation node ${slice.manifestation_node_id} i
- ${where} diagnosis manifestation node disagrees with its cau
- ${where} causal slice node ${nodeID} is not a node of varian
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/73c29a671b61cea4.
Report an issue: GitHub.