{"record":{"id":"d0649cb19af033b4","repo":"Hmbown/CodeWhale","slug":"invalid-signed-payload-metadata","errorCode":null,"errorMessage":"invalid signed payload metadata","messagePattern":"invalid signed payload metadata","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":290,"sourceCode":"/** Build the signed payload object (no signing) from a source file. */\nexport function buildPayload(source, { channel, factsVersion, publishedAt }) {\n  const errors = validateSource(source);\n  if (errors.length) throw new Error(`source invalid:\\n  - ${errors.join(\"\\n  - \")}`);\n  const payload = {\n    schema_version: SCHEMA_VERSION,\n    channel,\n    facts_version: factsVersion,\n    published_at: publishedAt,\n    applies_to: typeof source.applies_to === \"string\" ? source.applies_to.trim() : \"*\",\n    models: source.models ?? [],\n    provider_defaults: source.provider_defaults ?? {},\n    release: source.release ?? null,\n    announcements: source.announcements ?? [],\n  };\n  if (source.not_after) payload.not_after = source.not_after;\n  const payloadErrors = validateSource(payload);\n  if (payloadErrors.length || utcTime(publishedAt) === null || !Number.isSafeInteger(factsVersion) || factsVersion <= 0 || !CHANNEL_RE.test(channel)) {\n    throw new Error(\"invalid signed payload metadata\");\n  }\n  return payload;\n}\n\nexport function buildEnvelope({ privateKey, keyId, payload }) {\n  if (!KEY_ID_RE.test(keyId)) throw new Error(`key_id must match ${KEY_ID_RE}`);\n  const payloadBytes = Buffer.from(canonicalize(payload), \"utf8\");\n  if (payloadBytes.length > MAX_PAYLOAD_BYTES) throw new Error(`payload exceeds ${MAX_PAYLOAD_BYTES} bytes`);\n  const sig = signPayload(privateKey, keyId, payloadBytes);\n  const sha256 = createHash(\"sha256\").update(payloadBytes).digest(\"hex\");\n  const envelope = {\n    envelope: ENVELOPE_VERSION,\n    channel: payload.channel,\n    facts_version: payload.facts_version,\n    schema_version: payload.schema_version,\n    key_id: keyId,\n    alg: \"ed25519\",\n    applies_to: payload.applies_to,","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L272-L308","documentation":"After building the payload object, buildPayload re-validates it and additionally checks the envelope metadata: publishedAt must parse as a UTC ISO timestamp (utcTime), factsVersion must be a positive safe integer, and channel must match CHANNEL_RE. If any of these fail it throws this error, ensuring only well-formed metadata is ever signed.","triggerScenarios":"Passing publishedAt like '2024-01-01' (no time/Z) or a non-UTC offset, factsVersion as 0, negative, a float, or a string, or channel not matching the allowed pattern (e.g. 'Prod' vs lowercase channel names).","commonSituations":"Generating publishedAt with toISOString of an invalid date, reading factsVersion from config as a string, or typos/casing mistakes in the channel name.","solutions":["Set publishedAt to a UTC ISO string like new Date().toISOString() that matches the utcTime regex","Make factsVersion a positive safe integer (Number(...) and validate)","Use a channel value matching the script's CHANNEL_RE (check the regex in facts-publish.mjs and match its exact format)"],"exampleFix":"// before\nbuildPayload(source, { channel: 'Prod', factsVersion: '12', publishedAt: '2024-01-01' });\n// after\nbuildPayload(source, { channel: 'prod', factsVersion: 12, publishedAt: new Date().toISOString() });","handlingStrategy":"validation","validationCode":"const ok = utcTime(publishedAt) !== null && Number.isSafeInteger(factsVersion) && factsVersion > 0 && CHANNEL_RE.test(channel);\nif (!ok) throw new Error('bad payload metadata');","typeGuard":"const isPositiveSafeInt = (v) => Number.isSafeInteger(v) && v > 0;","tryCatchPattern":"try { buildPayload(source, meta); } catch (e) { if (e.message === 'invalid signed payload metadata') { console.error('check publishedAt (UTC ISO), factsVersion (positive int), channel format'); process.exitCode = 2; } else throw e; }","preventionTips":["Always derive publishedAt from new Date().toISOString()","Coerce and check factsVersion with Number.isSafeInteger before passing","Validate channel names against CHANNEL_RE in CI"],"tags":["validation","metadata","signing"],"backgroundTag":"invalid-config-value","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}