{"record":{"id":"448cce541191f41b","repo":"santifer/career-ops","slug":"payload-must-include-at-least-one-of-cv-articled","errorCode":null,"errorMessage":"payload must include at least one of: cv, articleDigest","messagePattern":"payload must include at least one of: cv, articleDigest","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"add-entry.mjs","lineNumber":171,"sourceCode":"  }\n  return false;\n}\n\nexport function appendArticleDigest(md, entry) {\n  const block = entry.replace(/\\s+$/, '');\n  const base = md.replace(/\\s+$/, '');\n  // Keep the existing `---`-separated block rhythm.\n  return `${base}\\n\\n---\\n\\n${block}\\n`;\n}\n\n/**\n * Pure core: given the current file contents and a payload, compute the new\n * contents and a per-target status. No I/O — this is what the tests exercise.\n * @returns {{ cv: string, articleDigest: string, result: object }}\n */\nexport function applyAdd(payload, { cvText = null, articleText = null } = {}) {\n  if (!payload || typeof payload !== 'object' || (!payload.cv && !payload.articleDigest)) {\n    throw new Error('payload must include at least one of: cv, articleDigest');\n  }\n\n  const result = {};\n  let cv = cvText;\n  let articleDigest = articleText;\n\n  if (payload.cv) {\n    const { section, dedupKey, entry } = payload.cv;\n    if (!section || !entry) throw new Error('payload.cv requires { section, entry }');\n    // dedupKey is what makes the insert idempotent — refuse to add without one\n    // rather than silently allowing duplicate re-runs.\n    if (!normalizeKey(dedupKey)) throw new Error('payload.cv requires a non-empty dedupKey (used for dedup/idempotency)');\n    if (cvText === null) throw new Error(`cv.md not found — cannot add to a CV that does not exist`);\n    if (cvHasEntry(cvText, section, dedupKey)) {\n      result.cv = { status: 'duplicate', section };\n    } else {\n      cv = insertIntoCvSection(cvText, section, entry);\n      result.cv = { status: 'added', section };","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/add-entry.mjs#L153-L189","documentation":"applyAdd() is the pure core of career-ops' `add` mode: given current cv.md / article-digest.md contents plus a payload, it computes the new contents. It refuses payloads that contain neither a `cv` nor an `articleDigest` key, since there would be nothing to insert into either target file. The throw is a fail-fast argument validation before any text transformation happens.","triggerScenarios":"Calling applyAdd(null), applyAdd('string'), applyAdd({}), or applyAdd({ cv: null, articleDigest: undefined }) — any call where payload is not a non-null object with a truthy cv or articleDigest property.","commonSituations":"Building the payload conditionally (e.g. `const payload = {}; if (opts.toCv) payload.cvSection = ...` using the wrong key name), a CLI flag parser producing an empty options object, or passing parsed JSON whose keys were renamed between versions of the add-entry API.","solutions":["Pass a payload object with at least one of the two keys set: applyAdd({ cv: {...} } or applyAdd({ articleDigest: {...} }).","Check the payload key spelling — `cv` and `articleDigest` are exact; nested CV content belongs under payload.cv as { section, dedupKey, entry }.","Verify the code path that constructs the payload actually assigns it (an object built conditionally may stay empty when a flag is absent)."],"exampleFix":"// before\nconst payload = {};\nif (addCv) payload.cvSection = { section: 'Projects', entry };\nawait applyAdd(payload, { cvText });\n\n// after\nconst payload = {};\nif (addCv) payload.cv = { section: 'Projects', dedupKey: 'proj:cv-santiago', entry };\nawait applyAdd(payload, { cvText });","handlingStrategy":"validation","validationCode":"function canCallApplyAdd(payload) {\n  return payload != null && typeof payload === 'object' && (Boolean(payload.cv) || Boolean(payload.articleDigest));\n}","typeGuard":"function isAddPayload(p) {\n  return typeof p === 'object' && p !== null && ('cv' in p || 'articleDigest' in p);\n}","tryCatchPattern":"try {\n  applyAdd(payload, { cvText, articleText });\n} catch (e) {\n  if (e.message.startsWith('payload must include')) {\n    console.error('Nothing to add: set payload.cv or payload.articleDigest');\n  } else throw e;\n}","preventionTips":["Construct payloads through a single builder function that validates keys at construction time.","Assert the payload is non-empty in tests before calling applyAdd.","Never rename the top-level keys cv/articleDigest when refactoring."],"tags":["validation","argument-validation","api-misuse"],"backgroundTag":"missing-required-argument","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"}