{"record":{"id":"494ac136c9fe49c7","repo":"Hmbown/CodeWhale","slug":"source-invalid-n-errors-join-n","errorCode":null,"errorMessage":"source invalid:\\n  - ${errors.join(\"\\n  - \")}","messagePattern":"source invalid:\\\\n  - (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":275,"sourceCode":"      seen.add(a.id);\n      if (a.level !== undefined && !LEVELS.has(a.level)) errors.push(`${where}.level must be info|warn`);\n      if (typeof a.text !== \"string\" || !a.text.trim() || a.text.length > 200) errors.push(`${where}.text required (<= 200 chars)`);\n      optString(errors, where, a, \"url\", 300);\n      if (a.surfaces !== undefined && !(Array.isArray(a.surfaces) && a.surfaces.every((s) => SURFACES.has(s)))) errors.push(`${where}.surfaces invalid`);\n      optVersionReq(errors, where, a);\n      optString(errors, where, a, \"starts_at\", 40);\n      optString(errors, where, a, \"expires_at\", 40);\n    });\n  }\n  const allowed = new Set([\"$schema\", \"_meta\", \"schema_version\", \"channel\", \"facts_version\", \"published_at\", \"not_after\", \"applies_to\", \"models\", \"provider_defaults\", \"release\", \"announcements\"]);\n  for (const k of Object.keys(source)) if (!allowed.has(k)) errors.push(`unknown top-level field ${k}`);\n  return errors;\n}\n\n/** 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}","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L257-L293","documentation":"buildPayload validates the facts source file with validateSource before constructing the signed payload. If any validation errors are found, it throws one aggregated error listing every offending field, so publishers fix the whole source in one pass instead of one error per run.","triggerScenarios":"Calling buildPayload with a source missing required fields, having wrong-typed values (e.g. announcements not an array, bad release value), or otherwise failing validateSource's checks.","commonSituations":"Hand-editing the facts source JSON and dropping a required key, a schema drift after adding new fields, or generating the source programmatically with a bug.","solutions":["Read the '- ' lines in the thrown message; each names a specific validation failure in the source","Fix every listed field in the source file (types, required keys, allowed values)","Regenerate the source with the official build tooling instead of hand-editing"],"exampleFix":"// before\nconst payload = buildPayload({ version: '1.2.3' }, opts); // missing required fields\n// after\nconst payload = buildPayload({ version: '1.2.3', applies_to: '*', release: null, announcements: [] }, opts);","handlingStrategy":"try-catch","validationCode":"const errors = validateSource(source);\nif (errors.length) { console.error(errors.join('\\n')); process.exit(2); }","typeGuard":null,"tryCatchPattern":"try { buildPayload(source, opts); } catch (e) { if (e.message.startsWith('source invalid:')) { for (const line of e.message.split('\\n  - ').slice(1)) console.error('fix:', line); process.exitCode = 2; } else throw e; }","preventionTips":["Run validateSource as a pre-publish lint step","Generate the source with the official tooling, not hand edits","Pin the source schema version and validate on upgrade"],"tags":["validation","schema","signing"],"backgroundTag":"schema-validation-failed","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"}