{"record":{"id":"01c9193230dad3ac","repo":"santifer/career-ops","slug":"payload-articledigest-requires-entry","errorCode":null,"errorMessage":"payload.articleDigest requires { entry }","messagePattern":"payload\\.articleDigest requires (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"add-entry.mjs","lineNumber":195,"sourceCode":"\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 };\n    }\n  }\n\n  if (payload.articleDigest) {\n    const { dedupKey, entry } = payload.articleDigest;\n    if (!entry) throw new Error('payload.articleDigest requires { entry }');\n    if (!normalizeKey(dedupKey)) throw new Error('payload.articleDigest requires a non-empty dedupKey (used for dedup/idempotency)');\n    // article-digest.md is optional; create it from a header when missing.\n    const current = articleText === null\n      ? '# Article Digest -- Proof Points\\n\\nCompact proof points from portfolio projects. Read by career-ops at evaluation time.\\n'\n      : articleText;\n    if (articleDigestHasEntry(current, dedupKey)) {\n      result.articleDigest = { status: 'duplicate' };\n      articleDigest = articleText;\n    } else {\n      articleDigest = appendArticleDigest(current, entry);\n      result.articleDigest = { status: articleText === null ? 'created' : 'added' };\n    }\n  }\n\n  return { cv, articleDigest, result };\n}\n\nasync function readStdin() {","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/add-entry.mjs#L177-L213","documentation":"When payload.articleDigest is present, applyAdd requires an `entry` — the proof-point markdown to insert into article-digest.md. Unlike the cv branch, dedupKey is validated separately and article-digest.md is auto-created from a default header when missing; but without an entry there is nothing to insert, so the call is rejected.","triggerScenarios":"applyAdd({ articleDigest: {} }), applyAdd({ articleDigest: { dedupKey: 'x' } }) (dedupKey but no entry), or applyAdd({ articleDigest: { entry: '' } }) — a truthy articleDigest object with a falsy entry.","commonSituations":"Building both payload branches from one template and the articleDigest branch never receiving its entry field, destructuring a record where the proof-point text lives under a different property name (e.g. `text` or `point` instead of `entry`), or passing only dedupKey intending to reference an existing entry.","solutions":["Set entry: applyAdd({ articleDigest: { dedupKey: 'pp:rag-eval', entry: '- **RAG eval harness** — ...' } }).","Rename the source field to `entry` when constructing the payload (the API accepts only `entry`, not `text`/`point`).","If the goal was dedup-checking an existing entry, read article-digest.md and use articleDigestHasEntry directly instead of applyAdd."],"exampleFix":"// before\nawait applyAdd({ articleDigest: { dedupKey: point.slug, text: point.body } }, { articleText });\n\n// after\nawait applyAdd(\n  { articleDigest: { dedupKey: point.slug, entry: point.body } },\n  { articleText }\n);","handlingStrategy":"validation","validationCode":"function isValidDigestPayload(ad) {\n  return Boolean(ad && ad.entry);\n}\nif (payload.articleDigest && !isValidDigestPayload(payload.articleDigest)) throw new Error('articleDigest payload needs { entry }');","typeGuard":"function isDigestTarget(ad) {\n  return typeof ad === 'object' && ad !== null && typeof ad.entry === 'string' && ad.entry.length > 0;\n}","tryCatchPattern":"try {\n  applyAdd({ articleDigest: digestTarget }, { articleText });\n} catch (e) {\n  if (e.message.includes('payload.articleDigest requires')) {\n    console.error('articleDigest payload must carry the proof-point text as `entry`');\n  } else throw e;\n}","preventionTips":["Map source fields (text/point/body) to `entry` explicitly in the payload builder.","Share one typed factory for cv and articleDigest payloads so both always carry `entry`.","Add a test asserting applyAdd({ articleDigest: { dedupKey: 'x' } }) throws, locking the contract in."],"tags":["validation","schema","payload-shape"],"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"}