JuliusBrussee/caveman · error · Error
${where} supporting variant ${variantID} belongs to another
Error message
${where} supporting variant ${variantID} belongs to another task family What it means
The contracts fixture validator requires every motif to be re-derivable from the report's own workflow_variants: each variant a motif lists as supporting evidence must belong to the same task_family_id as the motif itself. This throw means a motif claims support from a variant in another family — the cross-reference is inconsistent, so the report's counts are not recomputable from the data it carries.
Source
Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:117
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`);
if (!contiguouslyContains(variant.signature, motif.operations)) {
throw new Error(`${where} operations are not a contiguous subsequence of variant ${variantID}'s signature`);
}
runs += variant.eligible_runs;
weighted += (motif.operations.length / variant.signature.length) * variant.eligible_runs;
}
if (motif.support_run_count !== runs) throw new Error(`${where} support run count ${motif.support_run_count} != ${runs}`);
const expectedShare = runs > 0 ? weighted / runs : 0;
if (Math.abs(motif.structural_cost_share - expectedShare) > 1e-6) {
throw new Error(`${where} structural_cost_share ${motif.structural_cost_share} != ${expectedShare}`);
}
}
motifCount += report.motifs.length;
// The causal investigation of every case: the cohort's arms, the traces it
// selected from them, and the backward hard-dependency slice.
const unitsByID = new Map(report.analysis_units.map((unit) => [unit.id, unit]));
const familiesByID = new Map(report.task_families.map((family) => [family.id, family]));View on GitHub (pinned to 766dce6b13)
Solutions
- Regenerate the report with the pipeline rather than editing by hand.
- Fix the motif's variant_ids to reference only variants whose task_family_id equals the motif's (or correct the motif's task_family_id).
- Check for typos/duplicates among family ids in report.task_families and report.workflow_variants.
- Re-run validate-continuous-improvement.mjs and confirm the fixture passes.
Example fix
// before
motif: { id: 'm1', task_family_id: 'family-a', variant_ids: ['v-a1', 'v-b2'] }
// v-b2 belongs to family-b -> throws
// after
motif: { id: 'm1', task_family_id: 'family-a', variant_ids: ['v-a1', 'v-a2'] } Defensive patterns
Strategy: validation
Validate before calling
// Pre-check the fixture cross-references the validator enforces.
function motifFamiliesConsistent(report) {
const variantsByID = new Map(report.workflow_variants.map((v) => [v.id, v]));
return report.motifs.every((motif) =>
motif.variant_ids.every((id) => variantsByID.get(id)?.task_family_id === motif.task_family_id),
);
}
// if (!motifFamiliesConsistent(report)) fixFixtureBeforeCommitting(report); Type guard
function isConsistentMotif(report, motif) {
const familyIDs = new Set(report.task_families.map((f) => f.id));
const variantsByID = new Map(report.workflow_variants.map((v) => [v.id, v]));
return familyIDs.has(motif.task_family_id) &&
motif.variant_ids.every((id) => variantsByID.get(id)?.task_family_id === motif.task_family_id);
} Try / catch
try {
execFileSync('node', ['validate-continuous-improvement.mjs', reportPath, spansPath]);
} catch (e) {
if (/belongs to another task family/.test(String(e?.stderr ?? e?.message))) {
// Regenerate the report from the pipeline; do not hand-patch variant_ids —
// support_run_count and structural_cost_share must be recomputed with it.
}
throw e;
} Prevention
- Generate report fixtures with the pipeline; never splice variant lists across motifs by hand.
- When renaming a task_family id, grep the whole report for the old id before committing.
- Run the validator in CI on every fixture change so inconsistencies fail the build, not review.
When it happens
Trigger: A hand-edited report fixture where variant_ids were copied from a motif of a different family; a motif-extraction pipeline bug assigning cross-family variants; family ids that differ by one character between the task_families and workflow_variants blocks.
Common situations: Authors assembling fixtures from several report drafts; a renamed family id not propagated into motifs; a generator that regenerates variants but reuses stale motif lists.
Related errors
- ${where} operations are not a contiguous subsequence of vari
- recordOutcome: values must be a non-empty plain object
- recordOutcome: evidence must be a plain object
- recordOutcome: evidence keys must be non-empty
- recordOutcome: evidence.${key} must be a string, number, or
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/f24b86b815ef93d0.
Report an issue: GitHub.