{"record":{"id":"5ebb86757569059b","repo":"Hmbown/CodeWhale","slug":"runtime-event-json-is-serializable","errorCode":null,"errorMessage":"runtime event JSON is serializable","messagePattern":"runtime event JSON is serializable","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/core/runtime_contract/ledger.rs","lineNumber":78,"sourceCode":"            checksum: String::new(),\n        };\n        event.checksum = event.expected_checksum();\n        event\n    }\n\n    #[must_use]\n    pub fn expected_checksum(&self) -> String {\n        let canonical = serde_json::json!({\n            \"schema_version\": self.schema_version,\n            \"sequence\": self.sequence,\n            \"event_id\": self.event_id,\n            \"kind\": self.kind,\n            \"parent_event_id\": self.parent_event_id,\n            \"causal_event_id\": self.causal_event_id,\n            \"recorded_at_ms\": self.recorded_at_ms,\n            \"payload\": self.payload,\n        });\n        let bytes = serde_json::to_vec(&canonical).expect(\"runtime event JSON is serializable\");\n        let digest = Sha256::digest(bytes);\n        digest.iter().map(|byte| format!(\"{byte:02x}\")).collect()\n    }\n\n    pub fn validate(&self) -> Result<(), String> {\n        if self.schema_version != RUNTIME_CONTRACT_SCHEMA_VERSION {\n            return Err(format!(\n                \"unsupported runtime event schema {}\",\n                self.schema_version\n            ));\n        }\n        if self.event_id.trim().is_empty() {\n            return Err(\"runtime event ID cannot be empty\".to_string());\n        }\n        let expected = self.expected_checksum();\n        if self.checksum != expected {\n            return Err(format!(\"runtime event {} checksum mismatch\", self.event_id));\n        }","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/core/runtime_contract/ledger.rs#L60-L96","documentation":"Panic while computing the SHA-256 checksum of a runtime-contract ledger event. `expected_checksum` builds a `serde_json::Value` from the event's fields and serializes it with `serde_json::to_vec`; the expect exists to unwrap the `Result`. A `serde_json::Value` is essentially always serializable to JSON bytes, so this fires only when the canonical value contains something JSON cannot represent — in practice a non-finite float (`NaN`/`Infinity`) reaching `payload` through a custom `Serialize` impl on the way into the `json!` macro.","triggerScenarios":"A `RuntimeEvent` whose `payload` was built from a type with a hand-written `Serialize` that emits a non-finite f64 (division by zero or missing data producing NaN) or otherwise emits a value `to_vec` rejects; the first `expected_checksum()` call on that event panics.","commonSituations":"New ledger event kinds embedding computed metrics (latency ratios, rates) where a denominator can be zero; downstream tooling comparing checksums after the canonical JSON representation changed between versions.","solutions":["Sanitize the payload before recording: replace non-finite floats with `null` or `0.0` (`f64::is_finite` check) and build the payload as a plain `serde_json::Value`.","Change `expected_checksum` to return `Result<String, serde_json::Error>` and let `record`/`validate` surface the error instead of panicking.","Pin the canonical JSON shape with a golden checksum test so schema edits are caught in CI.","If you are chasing a checksum mismatch rather than the panic itself, compare `validate()` output field-by-field between writer and reader."],"exampleFix":"// before\nlet bytes = serde_json::to_vec(&canonical).expect(\"runtime event JSON is serializable\");\n\n// after: surface the failure to the caller\nlet bytes = serde_json::to_vec(&canonical)\n    .map_err(|err| format!(\"event {} is not JSON-serializable: {err}\", self.event_id))?;","handlingStrategy":"validation","validationCode":"// Prove the payload is JSON-representable before recording the event\nif serde_json::to_vec(&event.payload).is_err() {\n    return Err(format!(\"payload of event {} is not JSON-representable\", event.event_id));\n}","typeGuard":"fn json_representable(v: &serde_json::Value) -> bool {\n    match v {\n        serde_json::Value::Number(n) => n.as_f64().map_or(true, f64::is_finite),\n        serde_json::Value::Array(a) => a.iter().all(json_representable),\n        serde_json::Value::Object(o) => o.values().all(json_representable),\n        _ => true,\n    }\n}","tryCatchPattern":null,"preventionTips":["Sanitize floats (`.filter(|f| f.is_finite())`) before building ledger payloads.","Keep a golden checksum test so changes to the canonical JSON shape are caught in CI."],"tags":["rust","serde-json","ledger","checksum","panic","expect"],"backgroundTag":"serde-serialization-failed","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}