{"record":{"id":"79421732be2e3a22","repo":"santifer/career-ops","slug":"cv-md-not-found-cannot-add-to-a-cv-that-does-not","errorCode":null,"errorMessage":"cv.md not found — cannot add to a CV that does not exist","messagePattern":"cv\\.md not found — cannot add to a CV that does not exist","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"add-entry.mjs","lineNumber":181,"sourceCode":" * 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 };\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' };","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/add-entry.mjs#L163-L199","documentation":"Emitted by Check 11 of verify-pipeline.mjs. Each tracker row's Company cell is tested against CONFIDENTIAL_WORD_RE — confidential, vertraulich, confidentiel, confidencial, riservato, gizli, 機密, سري. A match means a locale-dependent confidentiality word was typed as the company name; the data contract requires the locale-invariant structural marker '?' instead, because such words can collide with a real firm name (a company actually called 'Confidential') and defeat exact string matching and dedup keys.","triggerScenarios":"Adding a tracker row by hand from an agency email that says 'Confidential client' and typing that into the Company cell; importing rows localized for the DACH ('Vertraulich'), Japanese ('機密'), or Arabic ('سري') market mode sets.","commonSituations":"Agency-mediated applications where the end employer is hidden; localizing the tracker into another market's vocabulary; copy-pasting the subject line of a blind recruiter email.","solutions":["Replace the Company cell with the structural marker '?'","Record the agency in the Via column — add it first with `node merge-tracker.mjs --migrate-via` if the tracker lacks it; '?' with no Via channel is the error-level sibling finding","Once the end employer becomes known, replace '?' with the real company name"],"exampleFix":"// before:\n| 17 | 2026-02-10 | Confidential | Backend Engineer | Applied | ... |\n\n// after:\n| 17 | 2026-02-10 | ? | Backend Engineer | Applied | ... | via=Hays recorded in Via |","handlingStrategy":"type-guard","validationCode":"// Run BEFORE writing a Company cell: reject locale-dependent placeholder words.\nconst CONFIDENTIAL_WORD_RE = /^(confidential|vertraulich|confidentiel|confidencial|riservato|gizli|機密|سري)$/i;\nexport function validCompanyCell(company) {\n  const c = String(company ?? '').trim();\n  return c !== '' && !CONFIDENTIAL_WORD_RE.test(c);\n}","typeGuard":"const CONFIDENTIAL_WORD_RE = /^(confidential|vertraulich|confidentiel|confidencial|riservato|gizli|機密|سري)$/i;\n// Structural unknown-employer marker: locale-invariant, cannot collide with a real firm.\nconst isStructuralUnknownEmployer = (company) => String(company ?? '').trim() === '?';\nconst isSafeCompanyCell = (company) => isStructuralUnknownEmployer(company) || !CONFIDENTIAL_WORD_RE.test(String(company ?? '').trim());","tryCatchPattern":null,"preventionTips":["Use '?' the moment an agency hides the end client, not the word the email uses","Pair every '?' with a Via channel so the row passes the error-level sibling check","When localizing the tracker for DACH/JP/AR markets, remember the regex covers those placeholders too — the marker stays '?' in every locale","Add the guard above to any script or workflow that appends tracker rows"],"tags":["confidential-placeholder","unknown-employer","tracker","i18n"],"backgroundTag":"placeholder-value","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}