JuliusBrussee/caveman · error · Error

probability_a_given_b !=

Error message

${where} probability_a_given_b ${relationship.probability_a_given_b} != ${expectedAGivenB}

What it means

Symmetric to probability_b_given_a: the validator requires probability_a_given_b to equal shared_unit_count / theme_b_unit_count within 1e-9. This keeps the relationship's reverse conditional probability derivable from the fixture's own counts.

Solutions

  1. Recompute probability_a_given_b as shared_unit_count / theme_b_unit_count and update the fixture.
  2. Fix theme_b_unit_count or shared_unit_count if the counts are the stale fields.
  3. Verify both directional probabilities together whenever any count changes.

Example fix

// before
{ "shared_unit_count": 2, "theme_b_unit_count": 5, "probability_a_given_b": 0.5 }
// after
{ "shared_unit_count": 2, "theme_b_unit_count": 5, "probability_a_given_b": 0.4 }
Defensive patterns

Strategy: validation

Validate before calling

const expected = rel.shared_unit_count / rel.theme_b_unit_count;
if (Math.abs(rel.probability_a_given_b - expected) > 1e-9) throw new Error(`probability_a_given_b must be ${expected}`);

Type guard

const probAGivenBValid = (rel) => Math.abs(rel.probability_a_given_b - rel.shared_unit_count / rel.theme_b_unit_count) <= 1e-9;

Try / catch

try { validateContinuousImprovement(report); } catch (e) { if (String(e.message).includes('probability_a_given_b')) { console.error('Recompute probability_a_given_b = shared_unit_count / theme_b_unit_count'); } else throw e; }

Prevention

When it happens

Trigger: A relationship entry where |probability_a_given_b - (shared_unit_count / theme_b_unit_count)| > 1e-9, typically after changing theme_b_unit_count or the shared count without updating the reverse probability.

Common situations: Only updating one direction's probability after editing counts; copy-pasting a relationship and swapping A/B fields inconsistently; hand-rounded probability values.

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


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/2903b2c34c659bb1. Report an issue: GitHub.

Appendix: source

Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:99

    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) {
      const variant = variantsByID.get(variantID);
      if (!variant) throw new Error(`${where} references workflow variant ${variantID} that is not in this report`);
      if (variant.task_family_id !== motif.task_family_id) throw new Error(`${where} supporting variant ${variantID} belongs to another task family`);

View on GitHub (pinned to 3ee70a1026)