{"record":{"id":"18b58a3d2d6f84d4","repo":"santifer/career-ops","slug":"usage","errorCode":"USAGE","errorMessage":"Invalid appNum: ${appNum}","messagePattern":"Invalid appNum: (.+?)","errorType":"validation","errorClass":"SeedError","httpStatus":null,"severity":"error","filePath":"followup-seed.mjs","lineNumber":415,"sourceCode":" * without touching the file, unless `options.force` is set.\n *\n * @param {number} appNum\n * @param {object} [options]\n * @param {string} [options.date] - Explicit apply date (YYYY-MM-DD), already validated.\n * @param {boolean} [options.force] - Bypass idempotency guard and the Applied-status guard.\n * @param {boolean} [options.dryRun] - Compute and report, but write nothing (no lock taken).\n * @param {string} [options.trackerPath]\n * @param {string} [options.followupsPath]\n * @param {string} [options.profilePath]\n * @param {string} [options.lockDir]\n * @param {number} [options.lockTimeoutMs]\n * @param {number} [options.lockRetryMs]\n * @param {number} [options.lockStaleMs]\n * @returns {Promise<object>}\n */\nexport async function seedFollowup(appNum, options = {}) {\n  if (!Number.isInteger(appNum) || appNum <= 0) {\n    throw new SeedError('USAGE', `Invalid appNum: ${appNum}`);\n  }\n  if (options.date != null && !isValidCalendarDate(options.date)) {\n    throw new SeedError('INVALID_DATE', `--date must be a real calendar date in YYYY-MM-DD form: ${options.date}`);\n  }\n\n  const trackerPath = resolveTrackerPath(options.trackerPath);\n  const followupsPath = resolveFollowupsPath(options.followupsPath);\n\n  if (!existsSync(trackerPath)) {\n    throw new SeedError('ROW_NOT_FOUND', `Tracker not found at ${trackerPath}`);\n  }\n  const rows = readTrackerRows(trackerPath);\n  const row = rows.find(r => r.num === appNum);\n  if (!row) {\n    throw new SeedError('ROW_NOT_FOUND', `Application #${appNum} not found in ${trackerPath}`);\n  }\n\n  const normalized = normalizeStatus(row.status);","sourceCodeStart":397,"sourceCodeEnd":433,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/followup-seed.mjs#L397-L433","documentation":"seedFollowup(appNum) is the programmatic API entry point and requires a positive integer for appNum. This USAGE guard fires when appNum is a float, zero, negative, NaN, a string, or undefined. The CLI parser does its own validation separately (error 52); this guard protects direct module importers.","triggerScenarios":"Calling seedFollowup(0), seedFollowup(-1), seedFollowup(3.5), seedFollowup('abc'), or seedFollowup(NaN) from a test or wrapper script; passing a raw unparsed CLI string into the API; passing a row.num that is undefined because the row lookup returned nothing.","commonSituations":"A wrapper script forwards user input as a string instead of a parsed integer; off-by-one produces 0; parseInt returned NaN and was forwarded unchecked.","solutions":["Parse and validate appNum as a positive integer before calling: const n = Number(input); if (!Number.isInteger(n) || n <= 0) throw.","If the value comes from tracker row.num, ensure the row was found before forwarding.","Use the exported isValidCalendarDate-style discipline: validate at the boundary, not inside the library."],"exampleFix":"// before\nawait seedFollowup(userInput);\n\n// after\nconst n = Number(userInput);\nif (!Number.isInteger(n) || n <= 0) throw new Error(`appNum must be a positive integer, got ${userInput}`);\nawait seedFollowup(n);","handlingStrategy":"validation","validationCode":"function isValidAppNum(n) {\n  return Number.isInteger(n) && n > 0;\n}\n\nif (!isValidAppNum(appNum)) {\n  throw new Error(`appNum must be a positive integer, got ${appNum}`);\n}\nawait seedFollowup(appNum);","typeGuard":"/** @param {unknown} v */\nfunction isValidAppNum(v) {\n  return typeof v === 'number' && Number.isInteger(v) && v > 0;\n}","tryCatchPattern":null,"preventionTips":["Always parse CLI input with parseInt and validate before forwarding to the API.","Use Number.isInteger rather than typeof === 'number' to reject floats and NaN.","Validate at the boundary (entry point), not inside every call site."],"tags":["validation","api","usage","programmatic"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}