JuliusBrussee/caveman · error · Error
carries more shared unit ids than its shared unit count
Error message
${where} carries more shared unit ids than its shared unit count What it means
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.
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.
Example fix
// before
{ "shared_unit_count": 2, "shared_unit_ids": ["u1", "u2", "u3"] }
// after
{ "shared_unit_count": 3, "shared_unit_ids": ["u1", "u2", "u3"] } Defensive patterns
Strategy: validation
Validate before calling
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}`); Type guard
const hasConsistentSharedUnits = (rel) => Array.isArray(rel.shared_unit_ids) && rel.shared_unit_ids.length === rel.shared_unit_count;
Try / catch
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; } Prevention
- 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
When it happens
Trigger: A report fixture contains a relationship entry whose shared_unit_ids array has more elements than the relationship's shared_unit_count value.
Common situations: 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.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- support run count !=
- support variant count disagrees with its variant ids
- operations are not a contiguous subsequence of variant 's…
- probability_a_given_b !=
- probability_b_given_a !=
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/71e43d115262447c.
Report an issue: GitHub.
Appendix: source
Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:91
}
}
// A relationship is a count, so it must be recomputable from the counts it
// carries. Anything a reader cannot re-derive is a claim, not evidence.
const themeIds = new Set(report.themes.map((theme) => theme.id));
for (const relationship of report.relationships) {
const where = `report fixture ${reportPaths[index]}: relationship ${relationship.id}`;
if (!themeIds.has(relationship.theme_a_id) || !themeIds.has(relationship.theme_b_id)) {
throw new Error(`${where} references a theme that is not in this report`);
}
if (!(relationship.theme_a_id < relationship.theme_b_id)) {
throw new Error(`${where} theme ids are not in canonical order`);
}
if (relationship.shared_unit_count > relationship.theme_a_unit_count || relationship.shared_unit_count > relationship.theme_b_unit_count) {
throw new Error(`${where} shares more units than either theme has`);
}
if (relationship.shared_unit_ids.length > relationship.shared_unit_count) {
throw new Error(`${where} carries more shared unit ids than its shared unit count`);
}
const expectedBGivenA = relationship.shared_unit_count / relationship.theme_a_unit_count;
const expectedAGivenB = relationship.shared_unit_count / relationship.theme_b_unit_count;
if (Math.abs(relationship.probability_b_given_a - expectedBGivenA) > 1e-9) {
throw new Error(`${where} probability_b_given_a ${relationship.probability_b_given_a} != ${expectedBGivenA}`);
}
if (Math.abs(relationship.probability_a_given_b - expectedAGivenB) > 1e-9) {
throw new Error(`${where} probability_a_given_b ${relationship.probability_a_given_b} != ${expectedAGivenB}`);
}
}
relationshipCount += report.relationships.length;
// A motif is a structural count over variants this report carries, so every
// part of it must be re-derivable from those variants.
const variantsByID = new Map(report.workflow_variants.map((variant) => [variant.id, variant]));
const familyIDs = new Set(report.task_families.map((family) => family.id));
for (const motif of report.motifs) {
const where = `report fixture ${reportPaths[index]}: motif ${motif.id}`;View on GitHub (pinned to 3ee70a1026)