JuliusBrussee/caveman · error · Error
report fixture : theme transition disagrees with registry_id
Error message
report fixture ${reportPaths[index]}: theme ${theme.id} transition ${theme.lineage.transition} disagrees with registry_id "${theme.registry_id}" What it means
Beyond the JSON Schema, the validator enforces a semantic invariant: `lineage.transition` must agree with `registry_id` and `predecessor_ids`. A theme with transition "new" must have an empty registry_id and no predecessors; a continuing theme must carry a non-empty registry_id and at least one predecessor. When the transition value and the registry_id contradict each other, this error is thrown naming the theme and both values.
Solutions
- For a genuinely new theme: set `registry_id: ""`, `predecessor_ids: []`, and `transition: "new"`.
- For a continuing theme: set `registry_id` to its durable id, list that id in `predecessor_ids`, and use a non-new transition.
- Check the sibling invariants too: transition vs predecessor list, and registry_id membership in predecessor_ids, to fix all violations in one pass.
- Regenerate the report with the current pipeline rather than hand-editing lineage fields.
Example fix
// before
{ "id": "t3", "registry_id": "t2", "lineage": { "transition": "new", "predecessor_ids": [] } }
// after
{ "id": "t3", "registry_id": "", "lineage": { "transition": "new", "predecessor_ids": [] } } Defensive patterns
Strategy: validation
Validate before calling
function lineageConsistent(theme) {
const isNew = theme.lineage.transition === "new";
return isNew
? theme.registry_id === "" && theme.lineage.predecessor_ids.length === 0
: theme.registry_id !== "" && theme.lineage.predecessor_ids.includes(theme.registry_id);
}
// run on every theme before invoking the validator Try / catch
try {
await validateReports(reportPaths, spansPath);
} catch (e) {
if (String(e.message).includes("disagrees with registry_id")) {
const m = String(e.message).match(/theme (\S+) transition (\S+) disagrees with registry_id "(\S*)"/);
console.error(`Fix theme ${m?.[1]}: transition=${m?.[2]} registry_id="${m?.[3]}"`);
process.exit(1);
}
throw e;
} Prevention
- When adding a new theme to an updated report, always reset registry_id and predecessor_ids.
- When continuing a theme, copy the predecessor's registry_id into both registry_id and predecessor_ids.
- Add the lineageConsistent pre-check to fixture generators.
- Keep the semantic rules documented next to the schema.
When it happens
Trigger: Running the validator on a report where a theme has e.g. `transition: "new"` but a non-empty `registry_id`, or `transition: "extends"`/`"continues"` with `registry_id: ""`. Related sibling errors fire for mismatched predecessor lists and for inherited registry ids not present among predecessors.
Common situations: Copy-pasting a theme entry from an earlier report and forgetting to reset lineage fields for a genuinely new theme; manually bumping a transition to "new" while leaving the old registry_id; a generator bug emitting transition without clearing predecessors.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- history requires memory id
- report fixture : theme inherited a registry id that is not…
- report fixture : theme transition disagrees with its…
- apiKey, baseURL, and agent are required
- arm must be or
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/60c0fcc144647de8.
Report an issue: GitHub.
Appendix: source
Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:40
const ajv = new Ajv2020({ allErrors: true, strict: true });
const validateReport = ajv.compile(reportSchema);
const validateSpan = ajv.compile(spanSchema);
const contiguouslyContains = (haystack, needle) => {
for (let start = 0; start + needle.length <= haystack.length; start += 1) {
if (needle.every((operation, offset) => haystack[start + offset] === operation)) return true;
}
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])) {View on GitHub (pinned to 3ee70a1026)