{"record":{"id":"a1d47a1487553ee6","repo":"koala73/worldmonitor","slug":"physical-divergence-evaluation-clock-is-invalid","errorCode":null,"errorMessage":"Physical divergence evaluation clock is invalid","messagePattern":"Physical divergence evaluation clock is invalid","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"server/_shared/physical-divergence-snapshot.ts","lineNumber":381,"sourceCode":"  methodology(raw.methodologyVersion);\n  canonicalWeights(raw.weights);\n  const expected = buildPhysicalStressComposite(readings);\n  const actualState = state(raw.state);\n  const actualIndex = nullableFinite(raw.index);\n  if (\n    !string(raw.reason)\n    || !isPhysicalDivergenceStoredCompositeReason(actualState, raw.reason)\n    || actualState !== expected.state\n    || raw.reason !== expected.reason\n    || actualIndex !== expected.index\n  ) throw new TypeError('Physical divergence composite does not match its member readings');\n}\n\nfunction applyFreshness(\n  readings: PhysicalDivergenceRawReading[],\n  nowMs: number,\n): PhysicalDivergenceRawReading[] {\n  if (!Number.isFinite(nowMs)) throw new TypeError('Physical divergence evaluation clock is invalid');\n  return readings.map((entry) => {\n    if (entry.state !== 'ok' && entry.state !== 'insufficient_history') return entry;\n    const staleReason = physicalDivergenceStaleReason({\n      physicalAsOf: entry.physicalAsOf,\n      paperAsOf: entry.paperAsOf,\n      fxAsOf: entry.provenance.fxAsOf,\n    }, nowMs);\n    if (!staleReason) return entry;\n    return {\n      ...entry,\n      state: physicalDivergenceStateForFreshnessReason(staleReason),\n      reason: staleReason,\n      regime: null,\n      index: null,\n      percentile: null,\n      robustZ: null,\n      delta5d: null,\n      delta20d: null,","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/server/_shared/physical-divergence-snapshot.ts#L363-L399","documentation":"applyFreshness() stamps each reading with staleness computed against a wall-clock timestamp nowMs. If nowMs is not a finite number (NaN, Infinity, undefined coerced oddly, a string) it throws this TypeError immediately, because staleness decisions against an invalid clock would be meaningless.","triggerScenarios":"Calling applyFreshness (via the snapshot normalization pipeline: readings -> normalizePhysicalDivergenceSnapshot -> getPhysicalDivergenceIndex) with nowMs that is NaN, Infinity, undefined-as-NaN, or a non-numeric type — e.g. Date.now() replaced by an unparseable injected clock in tests or a corrupted config value.","commonSituations":"Tests injecting a fake clock that returns undefined; configuration supplying a timestamp string ('2026-09-01T...') instead of epoch ms; arithmetic on a missing field producing NaN; Date.now() monkey-patched incorrectly.","solutions":["Pass a finite epoch-milliseconds number: Number.isFinite(nowMs) must hold — use Date.now() by default.","If the clock comes from config/tests, coerce and validate it first: const t = Number(raw); if (!Number.isFinite(t)) throw ...","Fix test fakes to return a finite number from the clock function.","If a timestamp string is the source, parse it with Date.parse(...) and check for NaN before passing."],"exampleFix":"// before\napplyFreshness(readings, config.evaluationClock); // string\n// after\nconst nowMs = Number(config.evaluationClock);\nif (!Number.isFinite(nowMs)) nowMs = Date.now();\napplyFreshness(readings, nowMs);","handlingStrategy":"validation","validationCode":"const nowMs = clock?.() ?? Date.now();\nif (!Number.isFinite(nowMs)) throw new Error(`evaluation clock must be finite epoch ms, got ${nowMs}`);","typeGuard":"function isFiniteEpochMs(v: unknown): v is number {\n  return typeof v === 'number' && Number.isFinite(v) && v > 0;\n}","tryCatchPattern":"try {\n  applyFreshness(readings, nowMs);\n} catch (err) {\n  if (err instanceof TypeError && err.message.includes('evaluation clock is invalid')) {\n    applyFreshness(readings, Date.now()); // fall back to wall clock\n  } else throw err;\n}","preventionTips":["Validate injected test clocks return finite numbers.","Convert timestamp strings with Date.parse and check for NaN before use.","Default to Date.now() when no clock is supplied.","Never do arithmetic on possibly-missing timestamp fields without a Number.isFinite check."],"tags":["validation","time","snapshot","physical-divergence"],"backgroundTag":"invalid-timestamp","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}