{"record":{"id":"96287c897b7de2d5","repo":"JuliusBrussee/caveman","slug":"canonical-span-index-ajv-errorstext-validate","errorCode":null,"errorMessage":"canonical span ${index}: ${ajv.errorsText(validateSpan.errors)}","messagePattern":"canonical span (.+?): (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/contracts/scripts/validate-continuous-improvement.mjs","lineNumber":336,"sourceCode":"    const perturbed = dataset.cases.some((entry) => entry.perturbation !== \"none\");\n    if (perturbed !== item.eval_pack.graders.includes(\"guard_respected\")) {\n      throw new Error(`${where} guard_respected grader ${perturbed ? \"missing for\" : \"declared without\"} perturbed dataset cases`);\n    }\n    for (const proof of item.replay.trial_proofs) {\n      if (!datasetCaseIDs.has(proof.dataset_case_id)) throw new Error(`${where} replay trial ${proof.id} replays a case outside the composed dataset`);\n      for (const arm of [proof.baseline, proof.candidate]) {\n        const graders = arm.grader_results.map((grader) => grader.grader).sort();\n        const required = [...item.eval_pack.graders].sort();\n        if (graders.join(\",\") !== required.join(\",\")) {\n          throw new Error(`${where} replay trial ${proof.id} graded ${graders.join(\",\")}, want exactly ${required.join(\",\")}`);\n        }\n      }\n    }\n  }\n}\nif (!Array.isArray(spans) || spans.length === 0) throw new Error(\"canonical span fixture is empty\");\nfor (const [index, span] of spans.entries()) {\n  if (!validateSpan(span)) throw new Error(`canonical span ${index}: ${ajv.errorsText(validateSpan.errors)}`);\n}\nif (relationshipCount === 0) throw new Error(\"no theme relationship reached the shared-analysis-unit threshold in any fixture\");\nif (motifCount === 0) throw new Error(\"no workflow motif recurred across two variants of one task family in any fixture\");\nconsole.log(`validated ${reports.length} generated improvement report(s), ${relationshipCount} theme relationship(s), ${motifCount} workflow motif(s) and ${spans.length} canonical spans`);\n","sourceCodeStart":318,"sourceCodeEnd":341,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/shared/contracts/scripts/validate-continuous-improvement.mjs#L318-L341","documentation":"This validation script for continuous-improvement contracts checks every canonical span fixture against an AJV-compiled span schema. When a span object does not conform to the schema, the script throws with the span's index in the fixture array and AJV's full error text (paths, keywords, expected types). It exists to catch malformed canonical span fixtures before they are used to validate generated improvement reports.","triggerScenarios":"Running scripts/validate-continuous-improvement.mjs when any element of the loaded spans fixture array fails validateSpan(span) — e.g. a span missing a required property, wrong primitive type, bad enum value, or violating additionalProperties/required constraints defined in the span JSON schema.","commonSituations":"Hand-editing a spans fixture JSON and dropping or renaming a required field; a code change altered the span shape (new required field, renamed key) without updating fixtures; a generator wrote a span with a numeric value where a string id was expected.","solutions":["Read the AJV errorsText in the message to find the exact dataPath and keyword violation for the reported span index","Open the spans fixture file and fix the span at that index to match the span schema (add/rename fields, correct types)","Regenerate the fixture from the canonical generator script rather than hand-editing","If the schema intentionally changed, update fixtures AND the span schema together so validateSpan compiles against the new contract"],"exampleFix":"// before (fixture)\n{ \"spanId\": 42, \"theme\": \"retry\" }\n// after\n{ \"spanId\": \"span-0042\", \"theme\": \"retry\", \"durationMs\": 120 }","handlingStrategy":"validation","validationCode":"import Ajv2020 from 'ajv/dist/2020.js';\nconst validateSpan = ajv.compile(spanSchema);\nconst bad = spans.map((s, i) => ({ i, errors: validateSpan(s) ? null : validateSpan.errors })).filter(x => x.errors);\nif (bad.length) console.error('invalid spans:', bad.map(b => `#${b.i}: ${JSON.stringify(b.errors)}`).join('\\n'));","typeGuard":"const isSpan = (s) => s !== null && typeof s === 'object' && typeof s.spanId === 'string' && typeof s.theme === 'string';","tryCatchPattern":"try { validateOrThrow(spans); } catch (e) { if (e.message.startsWith('canonical span')) { console.error('Fixture schema drift:', e.message); process.exitCode = 1; } else throw e; }","preventionTips":["Generate fixtures from the canonical generator instead of hand-editing","Validate fixtures in CI on every schema change","Keep schema and fixtures in the same PR/commit","Lint fixture JSONs against the schema locally before committing"],"tags":["schema","validation","fixtures","ajv"],"backgroundTag":"schema-validation-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}