{"record":{"id":"0115092ac2ce44e3","repo":"Hmbown/CodeWhale","slug":"facts-version-or-source-facts-version-must-be-a-positive","errorCode":null,"errorMessage":"--facts-version (or source.facts_version) must be a positive integer","messagePattern":"--facts-version \\(or source\\.facts_version\\) must be a positive integer","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":538,"sourceCode":"    const raw = rawPublicKeyFromKeyObject(publicKey);\n    console.log(JSON.stringify({\n      key_id: keyId,\n      algorithm: \"ed25519\",\n      public_key_b64: raw.toString(\"base64\"),\n      public_key_bytes: [...raw],\n      private_key_file: out,\n      note: \"Private key written with mode 0600. Move it into custody (password manager); never commit it.\",\n    }, null, 2));\n    return 0;\n  }\n  if (cmd === \"sign\") {\n    refuseUnderCi();\n    const sourcePath = resolve(String(flags.source ?? resolve(REPO_ROOT, \"docs/cloud-facts/stable.json\")));\n    const source = readJson(sourcePath);\n    const channel = String(flags.channel ?? source.channel ?? \"stable\");\n    if (!CHANNEL_RE.test(channel)) throw new Error(\"bad channel slug\");\n    const factsVersion = Number(flags[\"facts-version\"] ?? source.facts_version);\n    if (!Number.isSafeInteger(factsVersion) || factsVersion <= 0) throw new Error(\"--facts-version (or source.facts_version) must be a positive integer\");\n    const keyId = String(flags[\"key-id\"] ?? \"\");\n    const privateKey = loadPrivateKeyFromEnv();\n    const publishedAt = String(flags[\"published-at\"] ?? nowIso());\n    const payload = buildPayload(source, { channel, factsVersion, publishedAt });\n    const envelope = buildEnvelope({ privateKey, keyId, payload });\n    const text = `${JSON.stringify(envelope, null, 2)}\\n`;\n    if (flags.out) {\n      writeFileSync(resolve(String(flags.out)), text);\n      console.error(`wrote ${flags.out} (channel=${channel} facts_version=${factsVersion} key_id=${keyId} sha256=${envelope.sha256})`);\n    } else process.stdout.write(text);\n    return 0;\n  }\n  if (cmd === \"verify\") {\n    const envelope = readJson(resolve(String(positional[1] ?? \"\")));\n    let pub = flags[\"public-key\"];\n    if (!pub) {\n      const trusted = loadTrustedKeysFromRepo().get(envelope.key_id);\n      if (!trusted || trusted.status !== \"active\") throw new Error(\"key is not pinned and active; use --public-key only for explicit offline verification\");","sourceCodeStart":520,"sourceCodeEnd":556,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L520-L556","documentation":"The sign subcommand takes the facts version from --facts-version or source.facts_version, coerces it with Number(), and requires a safe positive integer. Non-numeric strings, floats, zero, negatives, NaN and unsafe integers all throw this error.","triggerScenarios":"`sign --facts-version abc` (NaN), --facts-version 1.5, --facts-version 0 or -1, or a source JSON with facts_version missing, null, or a non-integer like \"3\" only if Number coercion fails — note \"3\" passes, but an omitted flag plus missing source field yields NaN.","commonSituations":"A freshly created docs/cloud-facts/stable.json missing facts_version; a flag pasted with units or a trailing period (\"2.\"); automation writing the version as a non-integer or leaving it empty.","solutions":["Pass --facts-version <positive integer>, e.g. --facts-version 4","Add a numeric facts_version field to the source JSON before signing","Inspect the source JSON: Number(null) is 0 and Number(undefined) is NaN, both rejected","Ensure no shell quoting turns the value into text with stray characters"],"exampleFix":"// before\nnode facts-publish.mjs sign envelope.json --facts-version 2.5\n// after\nnode facts-publish.mjs sign envelope.json --facts-version 3","handlingStrategy":"validation","validationCode":"const v = Number(flags['facts-version'] ?? source.facts_version);\nif (!Number.isSafeInteger(v) || v <= 0) throw new Error(`facts_version must be a positive integer, got: ${v}`);","typeGuard":null,"tryCatchPattern":"try {\n  await run(['sign', srcPath, '--facts-version', String(v)]);\n} catch (e) {\n  if (e.message.includes('positive integer')) console.error('Pass an integer >= 1 via --facts-version or set facts_version in the source JSON');\n  throw e;\n}","preventionTips":["Always keep facts_version as a positive integer in the source JSON","Pass --facts-version explicitly in automation instead of relying on the source file","Validate the source JSON before signing"],"tags":["cli","validation","versioning"],"backgroundTag":"invalid-argument-value","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}