{"record":{"id":"63041df5503d7ba7","repo":"Hmbown/CodeWhale","slug":"publication-timestamp-is-future-or-expired","errorCode":null,"errorMessage":"publication timestamp is future or expired","messagePattern":"publication timestamp is future or expired","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":409,"sourceCode":"    keys.push({ keyId, publicKey, status });\n    return \"\";\n  });\n  if (remainder.replace(/[\\s,]/g, \"\")) throw new Error(\"unparsed TypeScript TRUSTED_KEYS entry\");\n  return validateTrustedKeys(keys);\n}\n\nfunction loadTrustedKeysFromRepo() {\n  const keys = parseTsKeys(readBoundedFile(resolve(WEB_ROOT, \"lib/cloud-facts/keys.ts\"), 64 * 1024).toString(\"utf8\"));\n  return new Map(keys.map((key) => [key.keyId, key]));\n}\n\nexport function activePublishingKey(envelope, keys, now = Date.now()) {\n  const key = validateTrustedKeys(keys).find((key) => key.keyId === envelope.key_id && key.status === \"active\");\n  if (!key) throw new Error(\"primary signing key is not pinned and active; refusing publication\");\n  const check = verifyEnvelope(envelope, key.publicKey);\n  if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join(\"; \")}`);\n  if (!Number.isFinite(now) || utcTime(check.payload.published_at) > now + 300_000 ||\n      (check.payload.not_after != null && utcTime(check.payload.not_after) <= now)) throw new Error(\"publication timestamp is future or expired\");\n  return { key, check };\n}\n\nfunction refuseUnderCi() {\n  for (const marker of CI_MARKERS) {\n    if (process.env[marker] && !/^(0|false|no|off)$/i.test(process.env[marker])) {\n      throw new Error(`refusing to run with a secret under CI (${marker} is set); publish from the founder's machine`);\n    }\n  }\n}\n\nfunction sqlLiteral(value) {\n  if (value === null || value === undefined) return \"null\";\n  return `'${String(value).replace(/'/g, \"''\")}'`;\n}\n\nexport function emitSql(envelope, { publishedBy = \"\", publicKeyB64, notes = \"\" }) {\n  if (!publicKeyB64) throw new Error(\"public key required to emit the facts_key row\");","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L391-L427","documentation":"activePublishingKey validates envelope timestamps: published_at may not be more than 5 minutes in the future, and not_after, when present, must still be in the future relative to `now`. A non-finite `now` also fails. This prevents publishing facts whose validity window is already expired or whose timestamps claim a future publish time.","triggerScenarios":"Calling activePublishingKey where utcTime(payload.published_at) > now + 300000, where payload.not_after <= now, or where the `now` argument is NaN/Infinity; also occurs when the system clock is wrong.","commonSituations":"The envelope was built earlier and its not_after expiry passed before the publish ran; the publishing machine's clock is skewed (drifted VM or wrong timezone handling); published_at was computed from a clock set minutes ahead.","solutions":["Rebuild the envelope with a fresh published_at (and a not_after comfortably in the future) and re-sign it, then publish immediately","Fix the local clock (NTP sync) if the machine's time is skewed and retry","If using the `now` parameter in tests, pass a finite epoch-milliseconds number aligned with the envelope's timestamps"],"exampleFix":"// before\nawait activePublishingKey(staleEnvelope, keys); // not_after already passed\n// after\nconst fresh = await signFacts(buildPayload({ published_at: new Date().toISOString(), not_after: futureDate }));\nawait activePublishingKey(fresh, keys);","handlingStrategy":"validation","validationCode":"const now = Date.now();\nconst pub = Date.parse(payload.published_at);\nconst exp = payload.not_after != null ? Date.parse(payload.not_after) : null;\nif (!Number.isFinite(pub) || pub > now + 300_000) throw new Error(\"published_at is missing/invalid or too far in the future\");\nif (exp != null && exp <= now) throw new Error(\"not_after already expired — rebuild and re-sign the envelope\");","typeGuard":null,"tryCatchPattern":"try {\n  await activePublishingKey(envelope, keys);\n} catch (err) {\n  if (err.message === \"publication timestamp is future or expired\") {\n    console.error(\"Rebuild the envelope with fresh timestamps (check clock skew via NTP) and re-sign before publishing\");\n    process.exit(1);\n  }\n  throw err;\n}","preventionTips":["Build and publish in one session so published_at/not_after stay within the validity window","Keep machine clocks NTP-synced; check `date` against a reference if the error appears","If passing an explicit `now`, ensure it is a finite epoch-milliseconds number consistent with the envelope"],"tags":["timestamps","publishing-gate","clock-skew"],"backgroundTag":"invalid-date-format","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"}