{"record":{"id":"98885814472e5640","repo":"koala73/worldmonitor","slug":"physical-divergence-composite-does-not-match-its-m","errorCode":null,"errorMessage":"Physical divergence composite does not match its member readings","messagePattern":"Physical divergence composite does not match its member readings","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"server/_shared/physical-divergence-snapshot.ts","lineNumber":374,"sourceCode":"}\n\nfunction validateStoredComposite(\n  value: unknown,\n  readings: PhysicalDivergenceRawReading[],\n): void {\n  const raw = object(value);\n  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),","sourceCodeStart":356,"sourceCodeEnd":392,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/server/_shared/physical-divergence-snapshot.ts#L356-L392","documentation":"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.","triggerScenarios":"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).","commonSituations":"Partial cache writes (readings updated, composite stale); methodology-version bump without cache invalidation; hand-patched cache entries; serialization rounding of the index value.","solutions":["Purge the stale snapshot cache entry and let getPhysicalDivergenceIndex rebuild it from fresh readings so composite and readings are computed together atomically.","Ensure the writer updates readings and composite in one atomic write (same Redis transaction/key version).","If a methodology bump caused this, bump PHYSICAL_DIVERGENCE_METHODOLOGY_VERSION and invalidate all snapshots written under the old version.","Verify serialization preserves the exact index value (no lossy rounding) between compute and store."],"exampleFix":"// before (partial write)\ncache.set(key, { ...snapshot, readings: newReadings }); // composite now stale\n// after\ncache.set(key, recomputeCompositeAndWrite(newReadings)); // composite + readings atomic","handlingStrategy":"validation","validationCode":"const recomputed = computeComposite(readings); // same code path as the writer\nif (recomputed.state !== stored.state || recomputed.reason !== stored.reason || recomputed.index !== stored.index) {\n  throw new Error('stored composite diverges from member readings; regenerate before use');\n}","typeGuard":"null","tryCatchPattern":"try {\n  const snapshot = normalizePhysicalDivergenceSnapshot(stored, Date.now());\n} catch (err) {\n  if (err instanceof TypeError && err.message.includes('does not match its member readings')) {\n    stored = await rebuildSnapshotFromReadings(); // discard stale composite\n  } else throw err;\n}","preventionTips":["Persist readings and their composite atomically in a single write.","Invalidate the whole snapshot key when any member reading changes.","Bump and check methodologyVersion on every composite write.","Avoid lossy rounding of index values during serialization."],"tags":["data-integrity","consistency","cache","snapshot","physical-divergence"],"backgroundTag":"snapshot-validation-failed","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"}