Hmbown/CodeWhale · error · Error

invalid signed payload metadata

Error message

invalid signed payload metadata

What it means

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.

Solutions

  1. Set publishedAt to a UTC ISO string like new Date().toISOString() that matches the utcTime regex
  2. Make factsVersion a positive safe integer (Number(...) and validate)
  3. Use a channel value matching the script's CHANNEL_RE (check the regex in facts-publish.mjs and match its exact format)

Example fix

// before
buildPayload(source, { channel: 'Prod', factsVersion: '12', publishedAt: '2024-01-01' });
// after
buildPayload(source, { channel: 'prod', factsVersion: 12, publishedAt: new Date().toISOString() });
Defensive patterns

Strategy: validation

Validate before calling

const ok = utcTime(publishedAt) !== null && Number.isSafeInteger(factsVersion) && factsVersion > 0 && CHANNEL_RE.test(channel);
if (!ok) throw new Error('bad payload metadata');

Type guard

const isPositiveSafeInt = (v) => Number.isSafeInteger(v) && v > 0;

Try / catch

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; }

Prevention

When it happens

Trigger: 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).

Common situations: Generating publishedAt with toISOString of an invalid date, reading factsVersion from config as a string, or typos/casing mistakes in the channel name.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/d0649cb19af033b4. Report an issue: GitHub.

Appendix: source

Thrown at web/scripts/facts-publish.mjs:290

/** Build the signed payload object (no signing) from a source file. */
export function buildPayload(source, { channel, factsVersion, publishedAt }) {
  const errors = validateSource(source);
  if (errors.length) throw new Error(`source invalid:\n  - ${errors.join("\n  - ")}`);
  const payload = {
    schema_version: SCHEMA_VERSION,
    channel,
    facts_version: factsVersion,
    published_at: publishedAt,
    applies_to: typeof source.applies_to === "string" ? source.applies_to.trim() : "*",
    models: source.models ?? [],
    provider_defaults: source.provider_defaults ?? {},
    release: source.release ?? null,
    announcements: source.announcements ?? [],
  };
  if (source.not_after) payload.not_after = source.not_after;
  const payloadErrors = validateSource(payload);
  if (payloadErrors.length || utcTime(publishedAt) === null || !Number.isSafeInteger(factsVersion) || factsVersion <= 0 || !CHANNEL_RE.test(channel)) {
    throw new Error("invalid signed payload metadata");
  }
  return payload;
}

export function buildEnvelope({ privateKey, keyId, payload }) {
  if (!KEY_ID_RE.test(keyId)) throw new Error(`key_id must match ${KEY_ID_RE}`);
  const payloadBytes = Buffer.from(canonicalize(payload), "utf8");
  if (payloadBytes.length > MAX_PAYLOAD_BYTES) throw new Error(`payload exceeds ${MAX_PAYLOAD_BYTES} bytes`);
  const sig = signPayload(privateKey, keyId, payloadBytes);
  const sha256 = createHash("sha256").update(payloadBytes).digest("hex");
  const envelope = {
    envelope: ENVELOPE_VERSION,
    channel: payload.channel,
    facts_version: payload.facts_version,
    schema_version: payload.schema_version,
    key_id: keyId,
    alg: "ed25519",
    applies_to: payload.applies_to,

View on GitHub (pinned to 433685b202)