{"record":{"id":"71e43d115262447c","repo":"JuliusBrussee/caveman","slug":"where-carries-more-shared-unit-ids-than-its-shared-unit","errorCode":null,"errorMessage":"${where} carries more shared unit ids than its shared unit count","messagePattern":"(.+?) carries more shared unit ids than its shared unit count","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/contracts/scripts/validate-continuous-improvement.mjs","lineNumber":91,"sourceCode":"    }\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) {\n      throw new Error(`${where} shares more units than either theme has`);\n    }\n    if (relationship.shared_unit_ids.length > relationship.shared_unit_count) {\n      throw new Error(`${where} carries more shared unit ids than its shared unit count`);\n    }\n    const expectedBGivenA = relationship.shared_unit_count / relationship.theme_a_unit_count;\n    const expectedAGivenB = relationship.shared_unit_count / relationship.theme_b_unit_count;\n    if (Math.abs(relationship.probability_b_given_a - expectedBGivenA) > 1e-9) {\n      throw new Error(`${where} probability_b_given_a ${relationship.probability_b_given_a} != ${expectedBGivenA}`);\n    }\n    if (Math.abs(relationship.probability_a_given_b - expectedAGivenB) > 1e-9) {\n      throw new Error(`${where} probability_a_given_b ${relationship.probability_a_given_b} != ${expectedAGivenB}`);\n    }\n  }\n  relationshipCount += report.relationships.length;\n\n  // A motif is a structural count over variants this report carries, so every\n  // part of it must be re-derivable from those variants.\n  const variantsByID = new Map(report.workflow_variants.map((variant) => [variant.id, variant]));\n  const familyIDs = new Set(report.task_families.map((family) => family.id));\n  for (const motif of report.motifs) {\n    const where = `report fixture ${reportPaths[index]}: motif ${motif.id}`;","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/shared/contracts/scripts/validate-continuous-improvement.mjs#L73-L109","documentation":"This validator checks that continuous-improvement report fixtures are internally consistent. Each theme relationship declares a shared_unit_count plus an explicit list of shared unit ids; if the list is longer than the declared count, the fixture is self-contradictory and the validator throws. It catches fixtures that were hand-edited without updating the count.","triggerScenarios":"A report fixture contains a relationship entry whose shared_unit_ids array has more elements than the relationship's shared_unit_count value.","commonSituations":"Manually adding unit ids to shared_unit_ids during fixture authoring; a report generator bug that appends ids without recomputing the count; schema drift after adding units to a theme.","solutions":["Set shared_unit_count to equal shared_unit_ids.length in the fixture relationship.","Recompute the shared units from theme_a_units/theme_b_units intersection with the report's generator script and regenerate the fixture.","If the ids are wrong instead, trim shared_unit_ids to the true intersection and keep the count."],"exampleFix":"// before\n{ \"shared_unit_count\": 2, \"shared_unit_ids\": [\"u1\", \"u2\", \"u3\"] }\n// after\n{ \"shared_unit_count\": 3, \"shared_unit_ids\": [\"u1\", \"u2\", \"u3\"] }","handlingStrategy":"validation","validationCode":"if (rel.shared_unit_ids.length !== rel.shared_unit_count) throw new Error(`relationship ${rel.id}: shared_unit_ids length ${rel.shared_unit_ids.length} != shared_unit_count ${rel.shared_unit_count}`);","typeGuard":"const hasConsistentSharedUnits = (rel) => Array.isArray(rel.shared_unit_ids) && rel.shared_unit_ids.length === rel.shared_unit_count;","tryCatchPattern":"try { validateContinuousImprovement(report); } catch (e) { if (String(e.message).includes('more shared unit ids')) { console.error('Fix shared_unit_count or trim shared_unit_ids'); process.exitCode = 1; } else throw e; }","preventionTips":["Derive shared_unit_count from shared_unit_ids.length at generation time instead of storing both by hand","Regenerate fixtures with the report generator rather than editing JSON manually","Run the validator in CI on every fixture change"],"tags":["fixture","validation","count-mismatch"],"backgroundTag":"schema-validation-failed","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"}