{"record":{"id":"10fc61ba3847b474","repo":"pbakaus/impeccable","slug":"invalid-catalog-validation-errors-join","errorCode":null,"errorMessage":"invalid catalog: ${validation.errors.join('; ')}","messagePattern":"invalid catalog: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"skill/scripts/concept-seed.mjs","lineNumber":137,"sourceCode":"// network degrades after one timeout total, never one timeout per call.\nlet apiDeadline = null;\nfunction apiBudgetMs() {\n  if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS;\n  return Math.max(0, apiDeadline - Date.now());\n}\n\nconst localStates = new Map();\nfunction loadLocal(catalogDir = CATALOG_DIR) {\n  if (localStates.has(catalogDir)) return localStates.get(catalogDir);\n  let localState;\n  try {\n    const catalogState = readConceptCatalog(\n      join(catalogDir, 'concept-ingredients.json'),\n      join(catalogDir, 'concept-reviews.json')\n    );\n    const validation = validateConceptCatalog(catalogState.catalog, catalogState.reviewData);\n    if (validation.errors.length > 0) {\n      throw new Error(`invalid catalog: ${validation.errors.join('; ')}`);\n    }\n    const compositionState = readCompositionCatalog(\n      join(catalogDir, 'composition-ingredients.json'),\n      join(catalogDir, 'composition-reviews.json')\n    );\n    localState = {\n      concepts: catalogState.concepts,\n      compositions: compositionState.compositions,\n    };\n  } catch {\n    localState = null;\n  }\n  localStates.set(catalogDir, localState);\n  return localState;\n}\n\nfunction requireLocalConcepts() {\n  const local = loadLocal();","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/pbakaus/impeccable/blob/d14711ae3d1a1dd62dee61a358d27f107c51ccd0/skill/scripts/concept-seed.mjs#L119-L155","documentation":"Thrown (then swallowed into a null localState) when the local concept catalog at IMPECCABLE_CATALOG_DIR fails schema validation. validateConceptCatalog returns one error string per offending entry and they are joined. The catalog is the curated pool of challenger ingredients; a malformed JSON or a schema-drift between the catalog and this validator makes the whole local catalog unusable.","triggerScenarios":"Editing concept-ingredients.json / concept-reviews.json by hand and introducing a field the validator rejects (bad status, strength, grain, platform, or a duplicate id); updating the validator schema in roll-selection.mjs/concept-catalog.mjs without migrating existing catalog entries; a partial git checkout that left a catalog JSON truncated.","commonSituations":"A contributor adds a new composition with platforms:['desktop'] when only web/ios/android are allowed; a review entry references an allowedMode not in the persuade/operate/read/experience set; the catalog was regenerated by a newer tool than the running seeder.","solutions":["Set IMPECCABLE_CATALOG_DIR to a directory with valid catalog JSON, or unset it to fall through to the roll API / degraded seed.","Read the joined error messages — they name the offending entry id and the violated constraint (e.g. 'review r42 allowedModes may only contain ...').","Re-run the catalog validator on the files directly: `node -e \"import('./skill/scripts/lib/concept-catalog.mjs').then(...)\"` to see all errors at once.","If you edited the schema, migrate existing catalog rows to the new allowed-value sets before running the seeder."],"exampleFix":"// before — catalog has platforms: [\"desktop\"] which is not allowed\n\n// after — use one of COMPOSITION_PLATFORMS\n\"platforms\": [\"web\", \"ios\"]","handlingStrategy":"validation","validationCode":"import { readConceptCatalog, validateConceptCatalog } from './skill/scripts/lib/concept-catalog.mjs';\nconst state = readConceptCatalog(ingredientsPath, reviewsPath);\nconst v = validateConceptCatalog(state.catalog, state.reviewData);\nif (v.errors.length) console.error(v.errors); // surface before the seeder swallows them","typeGuard":"function isValidCatalog(state) {\n  return validateConceptCatalog(state.catalog, state.reviewData).errors.length === 0;\n}","tryCatchPattern":"try {\n  loadLocal(catalogDir);\n} catch (err) {\n  if (/invalid catalog/.test(err.message)) {\n    console.error('Catalog invalid; fix or unset IMPECCABLE_CATALOG_DIR:', err.message);\n  } else throw err;\n}","preventionTips":["Run validateConceptCatalog in CI on catalog JSON edits before commit.","Keep the catalog JSON and the validator schema versioned together; migrate rows when allowed-value sets change.","Surfacing validation errors directly (rather than the seeder swallowing them) shortens debugging."],"tags":["catalog","validation","concept-seed","schema","config"],"backgroundTag":null,"analyzedSha":"d14711ae3d1a1dd62dee61a358d27f107c51ccd0","analyzedAt":"2026-08-13T00:52:25.771Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}