koala73/worldmonitor · error · TypeError

Physical divergence composite does not match its member read

Error message

Physical divergence composite does not match its member readings

What it means

validateStoredComposite() recomputes the expected composite (state, reason, index) from the snapshot's member readings and compares it with the stored composite fields. Any mismatch — wrong/missing reason string, a reason inconsistent with the actual state, or divergent state/reason/index values — throws this TypeError. It guarantees a stored composite is never presented unless it is exactly derivable from its readings.

Source

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

}

function validateStoredComposite(
  value: unknown,
  readings: PhysicalDivergenceRawReading[],
): void {
  const raw = object(value);
  methodology(raw.methodologyVersion);
  canonicalWeights(raw.weights);
  const expected = buildPhysicalStressComposite(readings);
  const actualState = state(raw.state);
  const actualIndex = nullableFinite(raw.index);
  if (
    !string(raw.reason)
    || !isPhysicalDivergenceStoredCompositeReason(actualState, raw.reason)
    || actualState !== expected.state
    || raw.reason !== expected.reason
    || actualIndex !== expected.index
  ) throw new TypeError('Physical divergence composite does not match its member readings');
}

function applyFreshness(
  readings: PhysicalDivergenceRawReading[],
  nowMs: number,
): PhysicalDivergenceRawReading[] {
  if (!Number.isFinite(nowMs)) throw new TypeError('Physical divergence evaluation clock is invalid');
  return readings.map((entry) => {
    if (entry.state !== 'ok' && entry.state !== 'insufficient_history') return entry;
    const staleReason = physicalDivergenceStaleReason({
      physicalAsOf: entry.physicalAsOf,
      paperAsOf: entry.paperAsOf,
      fxAsOf: entry.provenance.fxAsOf,
    }, nowMs);
    if (!staleReason) return entry;
    return {
      ...entry,
      state: physicalDivergenceStateForFreshnessReason(staleReason),

View on GitHub (pinned to 9361220cc0)

Solutions

  1. Purge the stale snapshot cache entry and let getPhysicalDivergenceIndex rebuild it from fresh readings so composite and readings are computed together atomically.
  2. Ensure the writer updates readings and composite in one atomic write (same Redis transaction/key version).
  3. If a methodology bump caused this, bump PHYSICAL_DIVERGENCE_METHODOLOGY_VERSION and invalidate all snapshots written under the old version.
  4. Verify serialization preserves the exact index value (no lossy rounding) between compute and store.

Example fix

// before (partial write)
cache.set(key, { ...snapshot, readings: newReadings }); // composite now stale
// after
cache.set(key, recomputeCompositeAndWrite(newReadings)); // composite + readings atomic
Defensive patterns

Strategy: validation

Validate before calling

const recomputed = computeComposite(readings); // same code path as the writer
if (recomputed.state !== stored.state || recomputed.reason !== stored.reason || recomputed.index !== stored.index) {
  throw new Error('stored composite diverges from member readings; regenerate before use');
}

Type guard

null

Try / catch

try {
  const snapshot = normalizePhysicalDivergenceSnapshot(stored, Date.now());
} catch (err) {
  if (err instanceof TypeError && err.message.includes('does not match its member readings')) {
    stored = await rebuildSnapshotFromReadings(); // discard stale composite
  } else throw err;
}

Prevention

When it happens

Trigger: Normalizing a stored physical-divergence snapshot where the persisted composite was computed under an older methodology, the readings were updated without recomputing the composite, or the stored index/reason/state drifted from what the readings imply (float/rounding drift included).

Common situations: Partial cache writes (readings updated, composite stale); methodology-version bump without cache invalidation; hand-patched cache entries; serialization rounding of the index value.

Related errors


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