{"record":{"id":"e4eee8f2aaf7744f","repo":"santifer/career-ops","slug":"malformed-states-file-at-statespath-expected-a","errorCode":null,"errorMessage":"Malformed states file at ${statesPath}: expected a top-level \"states\" list","messagePattern":"Malformed states file at (.+?): expected a top-level \"states\" list","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tracker-sync-check.mjs","lineNumber":114,"sourceCode":"// against every other status) instead of correctly ranking it as terminal.\n//\n// states.yml has no explicit ordering field, so LIFECYCLE_ORDER is taken from\n// the file's own array order among states NOT marked `terminal: true`; the\n// terminal set and the id -> display-label map are read directly off each\n// state's `terminal` and `label` fields. See the comment above `states:` in\n// templates/states.yml for the contract.\nconst STATES_FILE = join(CAREER_OPS, 'templates/states.yml');\n\n/**\n * Load the canonical lifecycle order, terminal-status set, and id -> label\n * map from templates/states.yml.\n * @param {string} statesPath - Path to templates/states.yml.\n * @returns {{ order: string[], terminal: Set<string>, labels: Record<string,string> }}\n */\nexport function loadLifecycle(statesPath) {\n  const doc = yaml.load(readFileSync(statesPath, 'utf-8'));\n  if (!doc || !Array.isArray(doc.states)) {\n    throw new Error(`Malformed states file at ${statesPath}: expected a top-level \"states\" list`);\n  }\n  const order = [];\n  const terminal = new Set();\n  const labels = {};\n  for (const s of doc.states) {\n    const id = String(s?.id ?? '').trim();\n    if (!id) continue;\n    labels[id] = String(s.label ?? id);\n    if (s.terminal) terminal.add(id);\n    else order.push(id);\n  }\n  return { order, terminal, labels };\n}\n\nconst { order: LIFECYCLE_ORDER, terminal: TERMINAL_STATUSES, labels: CANONICAL_LABELS } = loadLifecycle(STATES_FILE);\n\n// Mirrors the ALIASES map in analyze-patterns.mjs / verify-pipeline.mjs —\n// applications.md status cell normalization (bold markers, trailing dates,","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/tracker-sync-check.mjs#L96-L132","documentation":"loadLifecycle parses templates/states.yml and requires a top-level `states` key holding a list. It throws when the YAML is empty, parses to a non-object, or lacks a `states` array. This guards the lifecycle order/terminal-state tables the tracker sync check depends on.","triggerScenarios":"Calling loadLifecycle(statesPath) with a states.yml that is empty, contains only comments, has states nested under another key (e.g. `lifecycle: states:`), or was overwritten with a different schema (YAML that parses to null/string/list without a `states` array).","commonSituations":"Hand-editing states.yml and accidentally deleting or renaming the `states` key; truncating the file during an editor crash; copying a template from an older career-ops version with a different schema; pointing statesPath at the wrong file (e.g. another YAML config); YAML indentation errors collapsing the structure.","solutions":["Open templates/states.yml and ensure a top-level `states:` key exists with a list of state entries each having an `id`","Restore the canonical file: git checkout -- templates/states.yml (or re-run node update-system.mjs apply)","Validate the YAML parses as expected (node -e \"console.log(require('js-yaml').load(require('fs').readFileSync('templates/states.yml','utf8')))\") and inspect doc.states","Confirm the statesPath argument points to templates/states.yml, not another YAML file"],"exampleFix":"# before (states.yml)\nlifecycle:\n  - id: Applied\n# after\nstates:\n  - id: Evaluated\n  - id: Applied","handlingStrategy":"validation","validationCode":"const doc = yaml.load(readFileSync(statesPath, 'utf-8'));\nif (!doc || !Array.isArray(doc.states)) {\n  throw new Error(`${statesPath} must contain a top-level 'states' list`);\n}","typeGuard":"const hasStatesList = (doc) => !!doc && Array.isArray(doc.states);","tryCatchPattern":"try {\n  const lc = loadLifecycle('templates/states.yml');\n} catch (e) {\n  if (String(e.message).includes('Malformed states file')) {\n    // restore templates/states.yml from repo before continuing\n  }\n}","preventionTips":["Never hand-edit templates/states.yml without re-validating its schema","Keep the `states` top-level key intact; add entries, don't restructure","Run node verify-pipeline.mjs after editing template files","Restore from git when unsure: git checkout -- templates/states.yml"],"tags":["yaml","validation","config","schema"],"backgroundTag":"schema-validation-failed","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}