JuliusBrussee/caveman · error · Error
${where} operations are not a contiguous subsequence of vari
Error message
${where} operations are not a contiguous subsequence of variant ${variantID}'s signature What it means
Each motif's operations must occur inside every supporting variant's signature as one consecutive run — contiguouslyContains does a sliding-window, element-wise comparison over the signature. This throw means the operations appear interleaved with other steps or in a different order, so the motif is not a structural subsequence of the variant and the structural_cost_share math built on it would be invalid.
Source
Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:119
}
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]));
for (const item of report.cases) {
const where = `report fixture ${reportPaths[index]}: case ${item.id}`;View on GitHub (pinned to 766dce6b13)
Solutions
- Regenerate motif.operations from the current variant signatures using contiguous-window extraction.
- If the motif genuinely spans non-adjacent steps, split it into multiple motifs whose operations are each contiguous.
- Align operation naming between the extractor and the signature builder (renames are the usual culprit).
- Re-run validate-continuous-improvement.mjs until the fixture passes.
Example fix
// before signature: ['a','x','b'] ; motif.operations: ['a','b'] // not contiguous -> throws // after signature: ['a','b','x'] ; motif.operations: ['a','b'] // contiguous run
Defensive patterns
Strategy: validation
Validate before calling
// Pre-check contiguity exactly the way the validator does.
const contiguouslyContains = (haystack, needle) => {
for (let start = 0; start + needle.length <= haystack.length; start += 1) {
if (needle.every((op, i) => haystack[start + i] === op)) return true;
}
return false;
};
function motifsAreSubsequences(report) {
const byID = new Map(report.workflow_variants.map((v) => [v.id, v]));
return report.motifs.every((m) =>
m.variant_ids.every((id) => contiguouslyContains(byID.get(id).signature, m.operations)),
);
}
// if (!motifsAreSubsequences(report)) regenerateMotifs(report); Type guard
function isContiguousMotif(variantSignature, motifOperations) {
if (motifOperations.length === 0 || motifOperations.length > variantSignature.length) return false;
for (let start = 0; start + motifOperations.length <= variantSignature.length; start += 1) {
if (motifOperations.every((op, i) => variantSignature[start + i] === op)) return true;
}
return false;
} Try / catch
try {
execFileSync('node', ['validate-continuous-improvement.mjs', reportPath, spansPath]);
} catch (e) {
if (/not a contiguous subsequence/.test(String(e?.stderr ?? e?.message))) {
// Re-extract motif.operations with a sliding window over the current signatures,
// or split the motif; then recompute support counts and rerun the validator.
}
throw e;
} Prevention
- Derive motif.operations by sliding-window extraction from signatures, never by set intersection.
- Regenerate motifs whenever signature operations are renamed, split, or reordered.
- Keep extractor and signature-builder versions pinned together so operation vocabularies match.
When it happens
Trigger: motif.operations built by set-intersection instead of contiguous-window extraction; the variant signature changed (operations inserted, renamed, or split) while motif.operations were kept from an older extraction; a hand-written motif listing steps that do exist in the variant but separated by other operations.
Common situations: Pipeline version changes renaming or splitting signature operations; fixtures authored against older signatures; a motif generalized across variants where it only matches some of them contiguously.
Related errors
- ${where} supporting variant ${variantID} belongs to another
- 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/fd8e562f499a53a5.
Report an issue: GitHub.