{"record":{"id":"da1047bfd0963d83","repo":"JuliusBrussee/caveman","slug":"where-causal-slice-node-nodeid-is-not-a-node","errorCode":null,"errorMessage":"${where} causal slice node ${nodeID} is not a node of variant ${sliceVariant.id}","messagePattern":"(.+?) causal slice node (.+?) is not a node of variant (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/contracts/scripts/validate-continuous-improvement.mjs","lineNumber":183,"sourceCode":"      selectedPerArm.set(trace.arm, (selectedPerArm.get(trace.arm) ?? 0) + 1);\n    }\n    for (const [arm, count] of selectedPerArm) {\n      if (count > 3) throw new Error(`${where} arm ${arm} carries ${count} representative traces, more than three`);\n    }\n\n    const slice = item.causal_slice;\n    const sliceVariant = variantsByID.get(slice.variant_id);\n    if (!sliceVariant) throw new Error(`${where} causal slice references a workflow variant that is not in this report`);\n    if (sliceVariant.id !== armVariants.get(\"baseline\").id) throw new Error(`${where} causal slice is not taken from the baseline arm's variant`);\n    const sliceNodes = new Set(sliceVariant.nodes.map((node) => node.id));\n    if (slice.manifestation_node_id !== \"\" && !sliceNodes.has(slice.manifestation_node_id)) {\n      throw new Error(`${where} manifestation node ${slice.manifestation_node_id} is not a node of variant ${sliceVariant.id}`);\n    }\n    if (item.diagnosis.manifestation.node_id !== slice.manifestation_node_id) {\n      throw new Error(`${where} diagnosis manifestation node disagrees with its causal slice`);\n    }\n    for (const nodeID of slice.node_chain) {\n      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`);","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/shared/contracts/scripts/validate-continuous-improvement.mjs#L165-L201","documentation":"Every id in causal_slice.node_chain must be a node of the slice variant (validate-continuous-improvement.mjs:182-184). The chain is the backward slice through the baseline variant's own graph, so it may not route through nodes that variant does not have.","triggerScenarios":"The chain was built from the alternative variant's node set; a node was renamed in the variant without updating the chain; the chain was hand-extended with a plausible node id that the variant never defined.","commonSituations":"Reusing a chain across variants of the same family; refactors that rename nodes; LLM- or template-assisted fixture authoring inventing node ids.","solutions":["Remove or correct each offending id so the chain only contains nodes of the baseline variant.","After fixing, confirm the chain still ends at the manifestation node (error 618) and its hard edges still resolve (error 619).","Re-run the validator."],"exampleFix":"// before — variant has no node/review\n\"node_chain\": [\"node/parse\", \"node/review\", \"node/exec\"]\n// after\n\"node_chain\": [\"node/parse\", \"node/exec\"]","handlingStrategy":"validation","validationCode":"const chainNodesOutsideVariant = (report) => {\n  const byID = new Map(report.workflow_variants.map((v) => [v.id, v]));\n  return report.cases.flatMap((c) => {\n    const nodes = new Set(byID.get(c.causal_slice.variant_id)?.nodes.map((n) => n.id) ?? []);\n    return c.causal_slice.node_chain.filter((id) => !nodes.has(id)).map((id) => `${c.id}:${id}`);\n  });\n};","typeGuard":null,"tryCatchPattern":"try {\n  execFileSync(process.execPath, [VALIDATOR, reportPath, spansPath]);\n} catch (err) {\n  if (/causal slice node .* is not a node/.test(err.message)) {\n    failCI(`node_chain routes through a node the variant does not have: ${err.message}`);\n  } else throw err;\n}","preventionTips":["Build node chains from the baseline variant's own node set, programmatically.","After node renames, sweep node_chain, hard_edges and manifestation ids in one pass.","Never hand-extend a chain with an id you have not checked against variant.nodes."],"tags":["fixtures","cross-validation","causal-slice","node-chain","referential-integrity"],"backgroundTag":"dangling-node-reference","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}