{"record":{"id":"cd7866b358bce97f","repo":"JuliusBrussee/caveman","slug":"where-is-a-safety-finding-carrying-expected-value","errorCode":null,"errorMessage":"${where} is a safety finding carrying expected value ${opportunity.expected_value}","messagePattern":"(.+?) is a safety finding carrying expected value (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/contracts/scripts/validate-continuous-improvement.mjs","lineNumber":69,"sourceCode":"  // 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    }\n    if (relationship.shared_unit_count > relationship.theme_a_unit_count || relationship.shared_unit_count > relationship.theme_b_unit_count) {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/shared/contracts/scripts/validate-continuous-improvement.mjs#L51-L87","documentation":"A safety finding must not attach a dollar-denominated expected value: the validator requires opportunity.expected_value === 0 when type === \"safety\" and throws with the offending value otherwise. The rationale (in the script's comments) is that copying an efficiency finding's expected value would let the same money be counted twice under two detectors, so hazard reports carry no financial claim.","triggerScenarios":"Running the validator on a report where an opportunity with type === \"safety\" has expected_value set to any non-zero number (e.g. a cost-saving estimate inherited from an efficiency finding).","commonSituations":"Detectors computing expected_value uniformly for all opportunity types; fixtures regenerated after a detector was reclassified from efficiency to safety; template reuse in report generators.","solutions":["Set expected_value to 0 on every opportunity whose type is \"safety\".","Fix the emitting detector so it only computes expected_value for efficiency-type opportunities.","If the finding truly quantifies savings, emit it as an efficiency opportunity instead of safety.","Re-run the validator to confirm safety findings carry zero expected value."],"exampleFix":"// before\n{ \"id\": \"opp-3\", \"type\": \"safety\", \"expected_value\": 142.5 }\n// after\n{ \"id\": \"opp-3\", \"type\": \"safety\", \"expected_value\": 0 }","handlingStrategy":"validation","validationCode":"if (report.opportunities.some((o) => o.type === \"safety\" && o.expected_value !== 0)) {\n  throw new Error(\"safety finding carries non-zero expected_value\");\n}","typeGuard":"const carriesNoMoney = (o) => o.type !== \"safety\" || o.expected_value === 0;","tryCatchPattern":"try {\n  await runValidator([reportPath, spansPath]);\n} catch (err) {\n  if (String(err.message).includes(\"carrying expected value\")) {\n    console.error(\"Zero expected_value on safety findings; money belongs to efficiency findings only.\");\n  }\n  throw err;\n}","preventionTips":["Compute expected_value only in the efficiency detector code path.","Default expected_value to 0 for every new opportunity type.","Review detector output for safety findings with financial fields before committing fixtures."],"tags":["validation","fixtures","opportunities","safety"],"backgroundTag":"invalid-argument-value","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"}