JuliusBrussee/caveman · error · Error
shares more units than either theme has
Error message
${where} shares more units than either theme has What it means
A relationship's shared_unit_count cannot exceed the number of analysis units either endpoint theme has: since shared units are by definition a subset of each theme's units, the validator throws when shared_unit_count > theme_a_unit_count or > theme_b_unit_count. This count-consistency check keeps relationship arithmetic (and the probabilities derived from it) re-derivable.
Solutions
- Recompute shared_unit_count as the actual size of the intersection of the two themes' analysis-unit sets.
- Update theme_a_unit_count / theme_b_unit_count to current theme unit-set sizes if those are the stale values.
- Regenerate the relationship from the canonical span fixture rather than hand-maintaining counts.
- Re-run the validator to confirm all count invariants (and the derived probabilities) hold.
Example fix
// before
{ "shared_unit_count": 9, "theme_a_unit_count": 5, "theme_b_unit_count": 12 }
// after
{ "shared_unit_count": 4, "theme_a_unit_count": 5, "theme_b_unit_count": 12 } Defensive patterns
Strategy: validation
Validate before calling
if (report.relationships.some((r) => r.shared_unit_count > Math.min(r.theme_a_unit_count, r.theme_b_unit_count))) {
throw new Error("shared_unit_count exceeds a theme's unit count");
} Type guard
const countsAreFeasible = (r) => r.shared_unit_count <= r.theme_a_unit_count && r.shared_unit_count <= r.theme_b_unit_count;
Try / catch
try {
await runValidator([reportPath, spansPath]);
} catch (err) {
if (String(err.message).includes("shares more units than either theme has")) {
console.error("Recompute shared_unit_count from the intersection of the two themes' unit sets.");
}
throw err;
} Prevention
- Compute counts from unit-set intersections, never store them by hand.
- Recompute relationship counts whenever theme unit sets change.
- Clamp or assert shared <= min(aCount, bCount) in the overlap detector itself.
When it happens
Trigger: Running the validator on a report where relationship.shared_unit_count is greater than relationship.theme_a_unit_count or relationship.theme_b_unit_count — e.g. unit counts recomputed after a theme's unit set shrank but the shared count did not.
Common situations: Theme unit counts recomputed after pruning analysis units while relationship counts stayed stale; off-by-one or wrong-denominator bugs in the overlap detector; hand-written fixtures with invented counts.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- references a theme that is not in this report
- theme ids are not in canonical order
- canonical span
- expected exactly 2 agent conformance fixtures, found
- ${fixtureFiles[index]}…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/3e9d388190fcf203.
Report an issue: GitHub.
Appendix: source
Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:88
if (opportunity.alternative_metrics.cost_per_outcome_usd !== null || opportunity.alternative_metrics.eligible_runs !== 0) {
throw new Error(`${where} is a safety finding carrying another workflow's metrics`);
}
}
}
// 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]));View on GitHub (pinned to 3ee70a1026)