JuliusBrussee/caveman · error · Error
probability_b_given_a !=
Error message
${where} probability_b_given_a ${relationship.probability_b_given_a} != ${expectedBGivenA} What it means
The validator requires that conditional probabilities in a theme relationship equal the ratio shared_unit_count / theme_a_unit_count within 1e-9. probability_b_given_a must be the fraction of theme A's units shared with theme B. A mismatch means the fixture's probability was stale or hand-computed incorrectly.
Solutions
- Recompute probability_b_given_a as shared_unit_count / theme_a_unit_count and update the fixture.
- If the shared count is wrong, fix shared_unit_count (and shared_unit_ids) so the ratio matches the stored probability.
- Store enough precision (avoid heavy rounding) so the value is within 1e-9 of the exact ratio.
Example fix
// before
{ "shared_unit_count": 2, "theme_a_unit_count": 4, "probability_b_given_a": 0.4 }
// after
{ "shared_unit_count": 2, "theme_a_unit_count": 4, "probability_b_given_a": 0.5 } Defensive patterns
Strategy: validation
Validate before calling
const expected = rel.shared_unit_count / rel.theme_a_unit_count;
if (Math.abs(rel.probability_b_given_a - expected) > 1e-9) throw new Error(`probability_b_given_a must be ${expected}`); Type guard
const probBGivenAValid = (rel) => Math.abs(rel.probability_b_given_a - rel.shared_unit_count / rel.theme_a_unit_count) <= 1e-9;
Try / catch
try { validateContinuousImprovement(report); } catch (e) { if (String(e.message).includes('probability_b_given_a')) { console.error('Recompute probability_b_given_a = shared_unit_count / theme_a_unit_count'); } else throw e; } Prevention
- Compute conditional probabilities in code from counts, never hard-code them
- Keep full float precision (avoid rounding to 2 decimals)
- Re-validate after any change to shared_unit_count or theme_a_unit_count
When it happens
Trigger: A relationship entry where |probability_b_given_a - (shared_unit_count / theme_a_unit_count)| > 1e-9, e.g. after changing shared_unit_count or theme_a_unit_count without updating the probability.
Common situations: Editing shared unit counts in a fixture without recomputing probabilities; regenerating themes with different unit totals; floating-point values rounded too coarsely by hand (e.g. 0.33 instead of 1/3).
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- probability_a_given_b !=
- carries more shared unit ids than its shared unit count
- operations are not a contiguous subsequence of variant 's…
- references a task family that is not in this report
- references workflow variant that is not in this report
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/77f05f2974cb56aa.
Report an issue: GitHub.
Appendix: source
Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:96
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}`;
if (!familyIDs.has(motif.task_family_id)) throw new Error(`${where} references a task family that is not in this report`);
if (motif.support_variant_count !== motif.variant_ids.length) throw new Error(`${where} support variant count disagrees with its variant ids`);
let runs = 0;
let weighted = 0;
for (const variantID of motif.variant_ids) {View on GitHub (pinned to 3ee70a1026)