{"record":{"id":"abc72b516d1cf4fa","repo":"JuliusBrussee/caveman","slug":"where-cohort-references-a-task-family-that-is-n","errorCode":null,"errorMessage":"${where} cohort references a task family that is not in this report","messagePattern":"(.+?) cohort references a task family that is not in this report","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/contracts/scripts/validate-continuous-improvement.mjs","lineNumber":139,"sourceCode":"      runs += variant.eligible_runs;\n      weighted += (motif.operations.length / variant.signature.length) * variant.eligible_runs;\n    }\n    if (motif.support_run_count !== runs) throw new Error(`${where} support run count ${motif.support_run_count} != ${runs}`);\n    const expectedShare = runs > 0 ? weighted / runs : 0;\n    if (Math.abs(motif.structural_cost_share - expectedShare) > 1e-6) {\n      throw new Error(`${where} structural_cost_share ${motif.structural_cost_share} != ${expectedShare}`);\n    }\n  }\n  motifCount += report.motifs.length;\n\n  // The causal investigation of every case: the cohort's arms, the traces it\n  // selected from them, and the backward hard-dependency slice.\n  const unitsByID = new Map(report.analysis_units.map((unit) => [unit.id, unit]));\n  const familiesByID = new Map(report.task_families.map((family) => [family.id, family]));\n  for (const item of report.cases) {\n    const where = `report fixture ${reportPaths[index]}: case ${item.id}`;\n    const family = familiesByID.get(item.cohort.task_family_id);\n    if (!family) throw new Error(`${where} cohort references a task family that is not in this report`);\n    const familyUnits = new Set(family.analysis_unit_ids);\n    if (item.cohort.family_unit_count !== familyUnits.size) throw new Error(`${where} cohort family unit count disagrees with the task family`);\n    const roles = item.cohort.arms.map((arm) => arm.role);\n    if (roles.join(\",\") !== \"baseline,alternative\") throw new Error(`${where} cohort arms are not baseline then alternative`);\n    const armVariants = new Map();\n    let comparedUnits = 0;\n    for (const arm of item.cohort.arms) {\n      const variant = variantsByID.get(arm.variant_id);\n      if (!variant) throw new Error(`${where} cohort arm ${arm.role} references a workflow variant that is not in this report`);\n      if (variant.task_family_id !== family.id) throw new Error(`${where} cohort arm ${arm.role} uses a variant of another task family`);\n      armVariants.set(arm.role, variant);\n      comparedUnits += arm.unit_count;\n    }\n    const excluded = item.cohort.excluded_units.reduce((total, exclusion) => total + exclusion.unit_count, 0);\n    if (comparedUnits + excluded !== item.cohort.family_unit_count) {\n      throw new Error(`${where} cohort arms (${comparedUnits}) plus exclusions (${excluded}) do not account for its ${item.cohort.family_unit_count} family units`);\n    }\n","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/shared/contracts/scripts/validate-continuous-improvement.mjs#L121-L157","documentation":"Every case's cohort must reference a task family defined in the same report file. The validator builds familiesByID from report.task_families and throws when item.cohort.task_family_id is absent from it (validate-continuous-improvement.mjs:138-139). Reports are deliberately self-contained: a family id that lives only in another fixture, or nowhere, fails here.","triggerScenarios":"A case block whose cohort.task_family_id is a typo or renamed id (family-code-gen vs family-codegen); the task_families entry was deleted while its cases were kept; the case was copied from a different report fixture that defined the family.","commonSituations":"Copy-pasting a case between fixture files; renaming family ids in a refactor without sweeping case references; splitting one big fixture into per-topic fixtures and leaving cross-references behind.","solutions":["Fix cohort.task_family_id so it byte-matches an id in this report's task_families array.","If the family object was accidentally deleted, restore it to report.task_families (with its analysis_unit_ids).","If the family genuinely belongs to another report, move the case to that report instead of cross-referencing.","Re-run the validator."],"exampleFix":"// before — report defines task family \"family-codegen\"\n\"cohort\": { \"task_family_id\": \"family-code-gen\", ... }\n// after\n\"cohort\": { \"task_family_id\": \"family-codegen\", ... }","handlingStrategy":"validation","validationCode":"const danglingCohortFamilies = (report) => {\n  const ids = new Set(report.task_families.map((f) => f.id));\n  return report.cases.filter((c) => !ids.has(c.cohort.task_family_id)).map((c) => c.id);\n};","typeGuard":null,"tryCatchPattern":"try {\n  execFileSync(process.execPath, [VALIDATOR, reportPath, spansPath]);\n} catch (err) {\n  if (/cohort references a task family/.test(err.message)) {\n    failCI(`dangling family id — check task_families in the same file: ${err.message}`);\n  } else throw err;\n}","preventionTips":["Keep every case and the task family it cites in the same report fixture.","When renaming family ids, grep all fixtures for the old id before committing.","Validate fixtures locally before pushing; CI runs this validator on every build."],"tags":["fixtures","cross-validation","case","cohort","referential-integrity"],"backgroundTag":"dangling-id-reference","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}