{"record":{"id":"01c9193230dad3ac","repo":"santifer/career-ops","slug":"payload-articledigest-requires-entry","errorCode":null,"errorMessage":"payload.articleDigest requires { entry }","messagePattern":"payload\\.articleDigest requires (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"add-entry.mjs","lineNumber":171,"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":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/add-entry.mjs#L153-L189","documentation":"Per-target validation in `applyAdd` for the `articleDigest` payload: when present, it must include a non-empty `entry` (the proof-point text to append). Unlike the cv branch, `dedupKey` is checked by a separate guard immediately after, but the entry itself is the first mandatory field because there's nothing to append without it.","triggerScenarios":"Calling `applyAdd({ articleDigest: { dedupKey: 'x' } })` (no entry); `applyAdd({ articleDigest: {} })`; `applyAdd({ articleDigest: { entry: '' } })`. Any truthy `payload.articleDigest` object without a non-empty `entry`.","commonSituations":"Caller wires up the digest dedupKey but forgets to attach the generated proof-point text; entry is built from a template that returned empty; agent passes the metadata shape but not the content; misreading the schema and putting the text under a different key (e.g. `text`, `body`).","solutions":["Include a non-empty `entry` string in `payload.articleDigest` (the actual proof-point line to append).","Use the exact key names `{ dedupKey, entry }` — do not substitute `text`/`body`/`content`.","Generate the entry eagerly upstream; if generation fails, do not call applyAdd at all.","Add a caller-side guard: `if (p.articleDigest && !p.articleDigest.entry) throw ...`.","Cover both missing-entry and missing-dedupKey paths in tests."],"exampleFix":"// before\napplyAdd({ articleDigest: { dedupKey: 'kpi-x' } }); // throws — no entry\n// after\napplyAdd({\n  articleDigest: {\n    dedupKey: 'kpi-x',\n    entry: '- KPI X: cut p95 latency 40% via caching (2024)'\n  }\n});","handlingStrategy":"validation","validationCode":"function validateDigestPayload(d) {\n  return !!d && typeof d === 'object' &&\n    typeof d.entry === 'string' && d.entry.trim() !== '';\n}\nif (payload.articleDigest && !validateDigestPayload(payload.articleDigest)) {\n  throw new Error('payload.articleDigest needs non-empty { entry }');\n}","typeGuard":"function isDigestEntry(d) {\n  return !!d && typeof d === 'object' &&\n    typeof d.entry === 'string' && d.entry.trim() !== '';\n}","tryCatchPattern":null,"preventionTips":["Use the exact key `entry` (not text/body/content).","Generate proof-point text eagerly; skip the call if generation returns empty.","Always pair entry with a dedupKey (checked by the next guard).","Cover both missing-entry and missing-dedupKey in tests."],"tags":["validation","schema","add-entry","article-digest"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}