{"record":{"id":"145b54ca791cccf1","repo":"JuliusBrussee/caveman","slug":"catalog-s-w","errorCode":null,"errorMessage":"catalog %s: %w","messagePattern":"catalog (.+?): %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/catalog/catalog.go","lineNumber":294,"sourceCode":"\t\treturn verifiedAt\n\t}\n\treturn \"unknown\"\n}\n\nfunc load() {\n\tfor _, path := range catalogCandidates() {\n\t\traw, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tloadErr = err\n\t\t\tcontinue\n\t\t}\n\t\tdecoded, err := DecodeAndValidate(raw)\n\t\tif err == nil {\n\t\t\tentries = decoded\n\t\t\tloadErr = nil\n\t\t\treturn\n\t\t}\n\t\tloadErr = fmt.Errorf(\"catalog %s: %w\", path, err)\n\t}\n\tentries = []Entry{}\n}\n\n// DecodeAndValidate strictly parses a catalog. Unknown YAML fields, duplicate\n// provider/model/region rows, invalid URLs/timestamps, and unsafe prices reject\n// the entire file; runtime must never load a plausible partial catalog.\nfunc DecodeAndValidate(raw []byte) ([]Entry, error) {\n\tdec := yaml.NewDecoder(bytes.NewReader(raw))\n\tdec.KnownFields(true)\n\tvar decoded []Entry\n\tif err := dec.Decode(&decoded); err != nil {\n\t\treturn nil, err\n\t}\n\tvar trailing any\n\tif err := dec.Decode(&trailing); err != io.EOF {\n\t\tif err == nil {\n\t\t\treturn nil, fmt.Errorf(\"multiple YAML documents are not allowed\")","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/catalog/catalog.go#L276-L312","documentation":"load wraps any DecodeAndValidate failure from a candidate catalog file with the 'catalog %s: %w' prefix so callers know which path failed validation. It is a wrapping error; the root cause (unknown fields, duplicates, bad prices, etc.) is chained inside. On final failure the loader returns an empty entry list rather than partially trusted data.","triggerScenarios":"Calling load (and transitively loadCurrentCatalog / snapshotDrift) with a catalog YAML file that fails DecodeAndValidate for any reason — the path-specific wrapper is applied to the underlying error.","commonSituations":"A hand-edited or tool-generated catalog YAML with a typo in a field, a duplicate provider/model/region row, or an invalid URL/timestamp; pointing the loader at a stale schema file after a format version change.","solutions":["Read the wrapped cause after 'catalog <path>:' — it names the exact rule that failed.","Fix the offending entry in the YAML at that path per the cause (required fields, currency, prices, timestamps).","Regenerate the catalog from the canonical source rather than hand-editing if available.","Run DecodeAndValidate or the test suite on the file before deploying."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if _, err := catalog.DecodeAndValidate(raw); err != nil {\n\tlog.Printf(\"catalog %s invalid: %v\", path, err)\n}","typeGuard":null,"tryCatchPattern":"entries, err := load(path)\nif err != nil {\n\tvar wrapped *fmt.wrapError\n\tif errors.As(err, &wrapped) {\n\t\tlog.Printf(\"root cause for %s: %v\", path, wrapped.Unwrap())\n\t}\n\treturn fallbackEntries()\n}","preventionTips":["Always unwrap past the 'catalog <path>:' prefix to see the real cause","Validate catalogs in CI before deploy","Regenerate catalogs from a generator instead of hand edits","Never deploy when load returns the empty-entries fallback silently"],"tags":["yaml","catalog","validation","error-wrapping"],"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"}