JuliusBrussee/caveman · error · Error
${where} hard chain edge ${edge.from}->${edge.to} is not a h
Error message
${where} hard chain edge ${edge.from}->${edge.to} is not a hard edge of variant ${sliceVariant.id} What it means
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.
Source
Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:197
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`);
}
}
// 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") {View on GitHub (pinned to 27d5a3981a)
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.
Example fix
// before (slice JSON)
{"from": "nodeA", "to": "nodeB", "type": "control", "strength": "hard", "evidence_refs": ["e1"]}
// variant has nodeA->nodeB as type "data"
// after — match the variant's edge type
{"from": "nodeA", "to": "nodeB", "type": "data", "strength": "hard", "evidence_refs": ["e1"]} Defensive patterns
Strategy: validation
Validate before calling
function hardEdgeBacked(slice, variant) {
const variantEdges = new Map(variant.edges.map((e) => [`${e.from} ${e.to} ${e.type}`, e]));
return slice.hard_edges.every((e) => {
const src = variantEdges.get(`${e.from} ${e.to} ${e.type}`);
return src !== undefined && src.strength === "hard";
});
} Type guard
function isBackedHardEdge(edge, variantEdges) {
const src = variantEdges.get(`${edge.from} ${edge.to} ${edge.type}`);
return src !== undefined && src.strength === "hard" && edge.strength === "hard";
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ${where} unproven adjacency ${edge.from}->${edge.to} is in f
- ${where} adjacency ${edge.from}->${edge.to} appears in both
- ${where} diagnosis upstream node ${nodeID} is not an upstrea
- ${where} is diagnosis-only but carries a change set, eval pa
- ${where} change set or eval pack is bound to another case
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/56ee83953e047c29.
Report an issue: GitHub.