{"record":{"id":"c12eebeb1528311f","repo":"JuliusBrussee/caveman","slug":"where-is-a-safety-finding-with-an-alternative-variant","errorCode":null,"errorMessage":"${where} is a safety finding with an alternative variant","messagePattern":"(.+?) is a safety finding with an alternative variant","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/contracts/scripts/validate-continuous-improvement.mjs","lineNumber":68,"sourceCode":"  // An opportunity names the exact pair of workflow variants it was emitted\n  // from, and a safety finding carries no borrowed dollar figure: copying the\n  // efficiency finding's alternative metrics and expected value would let the\n  // same money be counted twice under two detectors.\n  const variantIds = new Set(report.workflow_variants.map((variant) => variant.id));\n  for (const opportunity of report.opportunities) {\n    const where = `report fixture ${reportPaths[index]}: opportunity ${opportunity.id}`;\n    for (const key of [\"current_variant_id\", \"alternative_variant_id\"]) {\n      if (opportunity[key] && !variantIds.has(opportunity[key])) {\n        throw new Error(`${where} ${key} ${opportunity[key]} is not a workflow variant of this report`);\n      }\n    }\n    if (opportunity.detector_id === \"dominated-workflow\") {\n      if (!opportunity.current_variant_id || !opportunity.alternative_variant_id) {\n        throw new Error(`${where} compares two workflows without naming both variants`);\n      }\n    }\n    if (opportunity.type === \"safety\") {\n      if (opportunity.alternative_variant_id) throw new Error(`${where} is a safety finding with an alternative variant`);\n      if (opportunity.expected_value !== 0) throw new Error(`${where} is a safety finding carrying expected value ${opportunity.expected_value}`);\n      if (opportunity.alternative_metrics.cost_per_outcome_usd !== null || opportunity.alternative_metrics.eligible_runs !== 0) {\n        throw new Error(`${where} is a safety finding carrying another workflow's metrics`);\n      }\n    }\n  }\n\n  // A relationship is a count, so it must be recomputable from the counts it\n  // carries. Anything a reader cannot re-derive is a claim, not evidence.\n  const themeIds = new Set(report.themes.map((theme) => theme.id));\n  for (const relationship of report.relationships) {\n    const where = `report fixture ${reportPaths[index]}: relationship ${relationship.id}`;\n    if (!themeIds.has(relationship.theme_a_id) || !themeIds.has(relationship.theme_b_id)) {\n      throw new Error(`${where} references a theme that is not in this report`);\n    }\n    if (!(relationship.theme_a_id < relationship.theme_b_id)) {\n      throw new Error(`${where} theme ids are not in canonical order`);\n    }","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/shared/contracts/scripts/validate-continuous-improvement.mjs#L50-L86","documentation":"A safety finding describes a hazard in the current workflow only — there is no alternative being recommended — so the validator forbids type === \"safety\" opportunities from carrying an alternative_variant_id. It throws when a safety finding sets that field, which would misleadingly suggest a swap is proposed. This keeps efficiency recommendations and hazard reports structurally distinguishable.","triggerScenarios":"Running the validator on a report where an opportunity with type === \"safety\" has a truthy alternative_variant_id.","commonSituations":"Generator code copying the efficiency-opportunity template (which sets both variant ids) for safety findings; a detector reclassified from efficiency to safety without clearing the alternative variant; hand-merged fixtures.","solutions":["Set alternative_variant_id to null (or remove the field) on the safety opportunity.","If an alternative workflow really is proposed, change opportunity.type to the efficiency type instead of \"safety\".","Check the detector that emitted the finding and fix it to not populate alternative_variant_id for safety findings.","Re-run the validator to confirm safety findings carry no alternative."],"exampleFix":"// before\n{ \"id\": \"opp-3\", \"type\": \"safety\", \"current_variant_id\": \"variant-a\", \"alternative_variant_id\": \"variant-b\" }\n// after\n{ \"id\": \"opp-3\", \"type\": \"safety\", \"current_variant_id\": \"variant-a\", \"alternative_variant_id\": null }","handlingStrategy":"validation","validationCode":"if (report.opportunities.some((o) => o.type === \"safety\" && o.alternative_variant_id)) {\n  throw new Error(\"safety finding carries an alternative variant\");\n}","typeGuard":"const isPureSafetyFinding = (o) => o.type !== \"safety\" || (o.alternative_variant_id ?? null) === null;","tryCatchPattern":"try {\n  await runValidator([reportPath, spansPath]);\n} catch (err) {\n  if (String(err.message).includes(\"is a safety finding with an alternative variant\")) {\n    console.error(\"Null out alternative_variant_id or reclassify the opportunity type.\");\n  }\n  throw err;\n}","preventionTips":["Build safety findings from a constructor that omits alternative_variant_id entirely.","Reclassify types before metrics/variants are attached, not after.","Add a CI check that no safety opportunity has recommendation-shaped fields populated."],"tags":["validation","fixtures","opportunities","safety"],"backgroundTag":"conflicting-config-options","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}