{"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":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"add-entry.mjs","lineNumber":147,"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":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/add-entry.mjs#L129-L165","documentation":"Top-level validation in the pure `applyAdd(payload, opts)` core of add-entry.mjs. It requires `payload` to be an object carrying at least one of `cv` or `articleDigest`; otherwise nothing could be inserted and the call is meaningless. Thrown before any I/O so callers (and tests) get a fast, deterministic failure.","triggerScenarios":"Calling `applyAdd(null)`, `applyAdd({})`, `applyAdd({ cv: null, articleDigest: undefined })`, or `applyAdd('some string')`. Any shape missing both `payload.cv` and `payload.articleDigest` trips the guard.","commonSituations":"Agent/LLM calls `add` with only metadata (e.g. `{ section: 'Experience' }` at the top level instead of nested under `cv`); misreading the schema and passing the section/entry directly; empty payload from a tool wiring bug; calling add-entry programmatically before the caller has assembled either target.","solutions":["Structure the payload as `{ cv: { section, dedupKey, entry } }` and/or `{ articleDigest: { dedupKey, entry } }` — at least one must be present.","If you only have a CV entry, still nest it: `applyAdd({ cv: { section, dedupKey, entry } })`.","Validate the payload shape in your caller before invoking `applyAdd`; surface a clearer upstream error.","Re-read the JSDoc above the function — it documents the exact `{ cv, articleDigest }` contract.","Add a unit test exercising the empty-payload path to lock the contract."],"exampleFix":"// before\napplyAdd({ section: 'Experience', entry: '- Role @ X' }); // throws\n// after\napplyAdd({\n  cv: { section: 'Experience', dedupKey: 'role-x-2024', entry: '- Role @ X (2024)' }\n});","handlingStrategy":"type-guard","validationCode":"function isValidPayload(p) {\n  return !!p && typeof p === 'object' &&\n    (!!p.cv && typeof p.cv === 'object' || !!p.articleDigest && typeof p.articleDigest === 'object');\n}\nif (!isValidPayload(payload)) throw new Error('Supply { cv: {...} } and/or { articleDigest: {...} }');","typeGuard":"function isAddPayload(p) {\n  if (!p || typeof p !== 'object') return false;\n  const hasCv = p.cv && typeof p.cv === 'object';\n  const hasDigest = p.articleDigest && typeof p.articleDigest === 'object';\n  return hasCv || hasDigest;\n}","tryCatchPattern":null,"preventionTips":["Always nest targets under `cv` or `articleDigest` — never at the top level.","Validate shape in the caller before calling applyAdd.","Use a builder helper that constructs the payload object to avoid typos.","Lock the contract with a unit test on the empty/missing-target case."],"tags":["validation","schema","add-entry","api-contract"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}