koala73/worldmonitor · error · TypeError

Physical divergence snapshot has an invalid envelope

Error message

Physical divergence snapshot has an invalid envelope

What it means

normalizePhysicalDivergenceSnapshot() first coerces the input to an object, validates the methodology version, then requires evaluatedAt to be a valid ISO instant and readings/transitions to be arrays. If any of these envelope-level checks fail it throws this TypeError, rejecting the snapshot before per-reading validation even begins. It is the top-level shape gate for the stored dataset.

Source

Thrown at server/_shared/physical-divergence-snapshot.ts:443

/**
 * A snapshot written under a methodology this build does not implement. Distinct from an
 * unknown state: this is ordinary producer/consumer deploy skew (the Railway seeder and the
 * Vercel API ship independently), so a read path should fail closed with a reason rather
 * than 500 for the length of the rollout window.
 */
export function isUnsupportedPhysicalDivergenceMethodology(error: unknown): boolean {
  return error instanceof Error
    && error.message.startsWith('Unsupported physical divergence methodology:');
}

export function normalizePhysicalDivergenceSnapshot(
  value: unknown,
  nowMs = Date.now(),
): PhysicalDivergenceRawSnapshot {
  const raw = object(value);
  methodology(raw.methodologyVersion);
  if (!isoInstant(raw.evaluatedAt) || !Array.isArray(raw.readings) || !Array.isArray(raw.transitions)) {
    throw new TypeError('Physical divergence snapshot has an invalid envelope');
  }
  const storedReadings = raw.readings.map(reading);
  if (
    storedReadings.length !== PHYSICAL_DIVERGENCE_METALS.length
    || new Set(storedReadings.map((entry) => entry.metal)).size !== PHYSICAL_DIVERGENCE_METALS.length
  ) throw new TypeError('Physical divergence snapshot must contain gold and silver readings');
  validateStoredComposite(raw.composite, storedReadings);
  const readings = applyFreshness(storedReadings, nowMs);
  return {
    readings,
    composite: buildPhysicalStressComposite(readings),
    evaluatedAt: raw.evaluatedAt,
    methodologyVersion: PHYSICAL_DIVERGENCE_METHODOLOGY_VERSION,
    transitions: raw.transitions.map(transition),
  };
}

View on GitHub (pinned to 9361220cc0)

Solutions

  1. Inspect the actual stored value at the snapshot cache key; purge the corrupt entry and let it regenerate from live data.
  2. Ensure the writer stores the parsed object (not JSON.stringify'd twice) with evaluatedAt as an ISO instant and readings/transitions arrays.
  3. If an error/empty response can be cached, guard the writer to only cache successfully normalized snapshots.
  4. Version the cache key with the methodology/snapshot schema version so old shapes are never re-read by new code.

Example fix

// before
cache.set(key, JSON.stringify(snapshot)); // later read as string
normalizePhysicalDivergenceSnapshot(raw); // throws: not an object
// after
const value = JSON.parse(raw); // parse once at the boundary
normalizePhysicalDivergenceSnapshot(value);
Defensive patterns

Strategy: type-guard

Validate before calling

function looksLikeDivergenceEnvelope(v: unknown): boolean {
  return !!v && typeof v === 'object'
    && typeof (v as any).evaluatedAt === 'string' && !Number.isNaN(Date.parse((v as any).evaluatedAt))
    && Array.isArray((v as any).readings) && Array.isArray((v as any).transitions);
}
if (!looksLikeDivergenceEnvelope(cached)) await refreshFromSource();

Type guard

function isDivergenceEnvelope(v: unknown): v is { evaluatedAt: string; readings: unknown[]; transitions: unknown[]; methodologyVersion: string } {
  return !!v && typeof v === 'object'
    && typeof (v as any).evaluatedAt === 'string' && !Number.isNaN(Date.parse((v as any).evaluatedAt))
    && Array.isArray((v as any).readings) && Array.isArray((v as any).transitions);
}

Try / catch

try {
  const snapshot = normalizePhysicalDivergenceSnapshot(raw, Date.now());
} catch (err) {
  if (err instanceof TypeError && err.message.includes('invalid envelope')) {
    await cache.delete(key);            // purge corrupt entry
    snapshot = await fetchAndStoreFresh();
  } else throw err;
}

Prevention

When it happens

Trigger: getPhysicalDivergenceIndex / normalizePhysicalDivergenceDataset receiving cached or fetched data where evaluatedAt is missing/malformed (not an ISO 8601 instant), readings or transitions is not an array, or value isn't even an object — e.g. a JSON string was stored instead of a parsed object, or an error payload was cached.

Common situations: A cache entry holding `{ body: "..." }` wrapper JSON; double-stringified Redis values; an upstream error page cached in the snapshot key; schema drift after a producer change; empty/null cache reads passed straight through.

Related errors


AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01). Data as JSON: /api/errors/9d7e0c264b14fc90. Report an issue: GitHub.