{"record":{"id":"ca2aa695b6a706c5","repo":"JuliusBrussee/caveman","slug":"where-is-diagnosis-only-but-carries-a-change-se","errorCode":null,"errorMessage":"${where} is diagnosis-only but carries a change set, eval pack or replay","messagePattern":"(.+?) is diagnosis-only but carries a change set, eval pack or replay","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/contracts/scripts/validate-continuous-improvement.mjs","lineNumber":217,"sourceCode":"    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`);\n      if (slice.hard_edges.some((hard) => hard.from === edge.from && hard.to === edge.to && hard.type === edge.type)) {\n        throw new Error(`${where} adjacency ${edge.from}->${edge.to} appears in both the hard chain and the unproven list`);\n      }\n    }\n    const upstream = new Set(item.diagnosis.root_cause_hypothesis.upstream_node_ids);\n    for (const nodeID of upstream) {\n      if (!slice.node_chain.includes(nodeID) || nodeID === slice.manifestation_node_id) {\n        throw new Error(`${where} diagnosis upstream node ${nodeID} is not an upstream node of its causal slice`);\n      }\n    }\n\n    // A case that proposes nothing must carry nothing: no operator, no eval\n    // pack, no replay. Anything else would present an abstention as a proposal.\n    if (item.status === \"diagnosis_only\") {\n      if (item.change_set.operator_id !== \"\" || item.eval_pack.id !== \"\" || item.replay.status !== \"not_run\") {\n        throw new Error(`${where} is diagnosis-only but carries a change set, eval pack or replay`);\n      }\n      continue;\n    }\n    if (item.change_set.case_id !== item.id || item.eval_pack.case_id !== item.id) {\n      throw new Error(`${where} change set or eval pack is bound to another case`);\n    }\n\n    // The case must investigate the pair its own opportunity named. A cohort\n    // built from a differently-derived pair would describe one comparison and\n    // measure another.\n    const source = report.opportunities.find((opportunity) => opportunity.id === item.opportunity_id);\n    if (!source) throw new Error(`${where} names an opportunity that is not in this report`);\n    if (armVariants.get(\"baseline\").id !== source.current_variant_id || armVariants.get(\"alternative\").id !== source.alternative_variant_id) {\n      throw new Error(`${where} cohort arms (${armVariants.get(\"baseline\").id}/${armVariants.get(\"alternative\").id}) are not the variants opportunity ${source.id} named`);\n    }\n\n    // Every required grader is one the recorded interpreter actually computes.\n    // `json_schema` is deliberately absent: over a typed interpreter result it","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/shared/contracts/scripts/validate-continuous-improvement.mjs#L199-L235","documentation":"Thrown when a case has status \"diagnosis_only\" but still carries proposal artifacts: a non-empty change_set.operator_id, a non-empty eval_pack.id, or a replay.status other than \"not_run\". An abstaining case must carry nothing that reads as a proposed change; otherwise the report presents a non-proposal as a proposal.","triggerScenarios":"Setting status to \"diagnosis_only\" on a case that was originally a full proposal case, without clearing the change set, eval pack and replay fields back to their empty/not_run defaults.","commonSituations":"Downgrading a case after its proposal was rejected; templating new cases from full cases and only editing the status field.","solutions":["Reset the proposal fields: change_set.operator_id = \"\", eval_pack.id = \"\", replay.status = \"not_run\" (and empty the rest of those sub-objects per the contract).","If the case actually proposes a change, change status back to the proposal status instead of clearing fields.","Re-run the validator; this branch `continue`s, so the rest of the case is skipped once clean."],"exampleFix":"// before\nstatus: \"diagnosis_only\"\nchange_set: {operator_id: \"op-7\", ...}\neval_pack: {id: \"ep-7\", ...}\nreplay: {status: \"passed\"}\n\n// after\nstatus: \"diagnosis_only\"\nchange_set: {operator_id: \"\", ...}\neval_pack: {id: \"\", ...}\nreplay: {status: \"not_run\"}","handlingStrategy":"validation","validationCode":"const abstentionClean = item.status !== \"diagnosis_only\" || (\n  item.change_set.operator_id === \"\" && item.eval_pack.id === \"\" && item.replay.status === \"not_run\"\n);","typeGuard":"function isCleanDiagnosisOnly(item) {\n  return item.status !== \"diagnosis_only\" ||\n    (item.change_set.operator_id === \"\" && item.eval_pack.id === \"\" && item.replay.status === \"not_run\");\n}","tryCatchPattern":null,"preventionTips":["Provide a makeDiagnosisOnly(item) helper that clears proposal fields and use it whenever status changes.","Never template new cases from full proposal cases; start from a diagnosis-only skeleton."],"tags":["validation","state-machine","data-integrity"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}