JuliusBrussee/caveman · error · Error

report fixture : theme inherited a registry id that is not…

Error message

report fixture ${reportPaths[index]}: theme ${theme.id} inherited a registry id that is not one of its predecessors

What it means

A continuing theme must inherit its registry id from one of the themes it continues: the validator requires that for any theme whose transition is not "new", theme.lineage.predecessor_ids includes theme.registry_id. It throws when a theme claims a durable registry id that appears nowhere in its predecessor list, meaning the inherited identity cannot be traced to any ancestor. This prevents a fixture from silently rebinding a registry id to an unrelated lineage.

Solutions

  1. Open the fixture and add the theme's registry_id to its lineage.predecessor_ids array.
  2. Alternatively, correct registry_id to the actual id of one of the listed predecessors.
  3. If the id and predecessors are both stale, regenerate the report fixture from the canonical span fixture instead of hand-editing.
  4. Re-run the validator to confirm all three lineage invariants pass.

Example fix

// before
"registry_id": "theme-retry-v2", "lineage": { "transition": "continues", "predecessor_ids": ["theme-elsewhere-v1"] }
// after
"registry_id": "theme-retry-v2", "lineage": { "transition": "continues", "predecessor_ids": ["theme-retry-v1"] }
Defensive patterns

Strategy: validation

Validate before calling

function registryIdTracedToPredecessor(theme) {
  return theme.lineage.transition === "new" ||
    theme.lineage.predecessor_ids.includes(theme.registry_id);
}
if (!report.themes.every(registryIdTracedToPredecessor)) {
  throw new Error("inherited registry_id missing from predecessor_ids");
}

Type guard

const hasTracedLineage = (theme) => theme.lineage.transition === "new" || theme.lineage.predecessor_ids.includes(theme.registry_id);

Try / catch

try {
  await runValidator([reportPath, spansPath]);
} catch (err) {
  if (String(err.message).includes("inherited a registry id that is not one of its predecessors")) {
    console.error("Add the theme's registry_id to lineage.predecessor_ids or fix the id.");
  }
  throw err;
}

Prevention

When it happens

Trigger: Running the validator on a report fixture where theme.lineage.transition !== "new" (registry_id non-empty) but theme.lineage.predecessor_ids does not contain theme.registry_id — e.g. predecessors reference different theme ids than the inherited registry id.

Common situations: Renaming theme ids when regenerating fixtures without updating predecessor lists; copying a theme entry and changing registry_id but not its predecessors; merging reports where lineage chains were rewritten.

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


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

Appendix: source

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

  }
  return false;
};
let relationshipCount = 0;
let motifCount = 0;
for (const [index, report] of reports.entries()) {
  if (!validateReport(report)) throw new Error(`report fixture ${reportPaths[index]}: ${ajv.errorsText(validateReport.errors)}`);
  for (const theme of report.themes) {
    // A durable registry id and a predecessor are the same claim: a theme that
    // continues an earlier one must carry both, and a new theme neither.
    const isNew = theme.lineage.transition === "new";
    if (isNew !== (theme.registry_id === "")) {
      throw new Error(`report fixture ${reportPaths[index]}: theme ${theme.id} transition ${theme.lineage.transition} disagrees with registry_id "${theme.registry_id}"`);
    }
    if (isNew !== (theme.lineage.predecessor_ids.length === 0)) {
      throw new Error(`report fixture ${reportPaths[index]}: theme ${theme.id} transition ${theme.lineage.transition} disagrees with its predecessor list`);
    }
    if (!isNew && !theme.lineage.predecessor_ids.includes(theme.registry_id)) {
      throw new Error(`report fixture ${reportPaths[index]}: theme ${theme.id} inherited a registry id that is not one of its predecessors`);
    }
  }

  // An opportunity names the exact pair of workflow variants it was emitted
  // from, and a safety finding carries no borrowed dollar figure: copying the
  // efficiency finding's alternative metrics and expected value would let the
  // same money be counted twice under two detectors.
  const variantIds = new Set(report.workflow_variants.map((variant) => variant.id));
  for (const opportunity of report.opportunities) {
    const where = `report fixture ${reportPaths[index]}: opportunity ${opportunity.id}`;
    for (const key of ["current_variant_id", "alternative_variant_id"]) {
      if (opportunity[key] && !variantIds.has(opportunity[key])) {
        throw new Error(`${where} ${key} ${opportunity[key]} is not a workflow variant of this report`);
      }
    }
    if (opportunity.detector_id === "dominated-workflow") {
      if (!opportunity.current_variant_id || !opportunity.alternative_variant_id) {
        throw new Error(`${where} compares two workflows without naming both variants`);

View on GitHub (pinned to 3ee70a1026)