Hmbown/CodeWhale · error · Error

publication timestamp is future or expired

Error message

publication timestamp is future or expired

What it means

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.

Solutions

  1. Rebuild the envelope with a fresh published_at (and a not_after comfortably in the future) and re-sign it, then publish immediately
  2. Fix the local clock (NTP sync) if the machine's time is skewed and retry
  3. If using the `now` parameter in tests, pass a finite epoch-milliseconds number aligned with the envelope's timestamps

Example fix

// before
await activePublishingKey(staleEnvelope, keys); // not_after already passed
// after
const fresh = await signFacts(buildPayload({ published_at: new Date().toISOString(), not_after: futureDate }));
await activePublishingKey(fresh, keys);
Defensive patterns

Strategy: validation

Validate before calling

const now = Date.now();
const pub = Date.parse(payload.published_at);
const exp = payload.not_after != null ? Date.parse(payload.not_after) : null;
if (!Number.isFinite(pub) || pub > now + 300_000) throw new Error("published_at is missing/invalid or too far in the future");
if (exp != null && exp <= now) throw new Error("not_after already expired — rebuild and re-sign the envelope");

Try / catch

try {
  await activePublishingKey(envelope, keys);
} catch (err) {
  if (err.message === "publication timestamp is future or expired") {
    console.error("Rebuild the envelope with fresh timestamps (check clock skew via NTP) and re-sign before publishing");
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

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

Common situations: 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.

Related errors


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

Appendix: source

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

    keys.push({ keyId, publicKey, status });
    return "";
  });
  if (remainder.replace(/[\s,]/g, "")) throw new Error("unparsed TypeScript TRUSTED_KEYS entry");
  return validateTrustedKeys(keys);
}

function loadTrustedKeysFromRepo() {
  const keys = parseTsKeys(readBoundedFile(resolve(WEB_ROOT, "lib/cloud-facts/keys.ts"), 64 * 1024).toString("utf8"));
  return new Map(keys.map((key) => [key.keyId, key]));
}

export function activePublishingKey(envelope, keys, now = Date.now()) {
  const key = validateTrustedKeys(keys).find((key) => key.keyId === envelope.key_id && key.status === "active");
  if (!key) throw new Error("primary signing key is not pinned and active; refusing publication");
  const check = verifyEnvelope(envelope, key.publicKey);
  if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join("; ")}`);
  if (!Number.isFinite(now) || utcTime(check.payload.published_at) > now + 300_000 ||
      (check.payload.not_after != null && utcTime(check.payload.not_after) <= now)) throw new Error("publication timestamp is future or expired");
  return { key, check };
}

function refuseUnderCi() {
  for (const marker of CI_MARKERS) {
    if (process.env[marker] && !/^(0|false|no|off)$/i.test(process.env[marker])) {
      throw new Error(`refusing to run with a secret under CI (${marker} is set); publish from the founder's machine`);
    }
  }
}

function sqlLiteral(value) {
  if (value === null || value === undefined) return "null";
  return `'${String(value).replace(/'/g, "''")}'`;
}

export function emitSql(envelope, { publishedBy = "", publicKeyB64, notes = "" }) {
  if (!publicKeyB64) throw new Error("public key required to emit the facts_key row");

View on GitHub (pinned to 433685b202)