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
- Open the fixture and add the theme's registry_id to its lineage.predecessor_ids array.
- Alternatively, correct registry_id to the actual id of one of the listed predecessors.
- If the id and predecessors are both stale, regenerate the report fixture from the canonical span fixture instead of hand-editing.
- 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 assigning a registry_id to a continuing theme, always copy it from the chosen predecessor in the same code path.
- Keep a lineage chain test that walks predecessor_ids and asserts registry_id membership.
- Regenerate reports rather than editing registry_id/predecessors by hand.
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
- report fixture : theme transition disagrees with its…
- is not a workflow variant of this report
- references a theme that is not in this report
- canonical span
- expected exactly 2 agent conformance fixtures, found
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)