{"record":{"id":"29ac0ba97573112b","repo":"atuinsh/atuin","slug":"fixed-layout-structure-cannot-fail-serialization","errorCode":null,"errorMessage":"fixed-layout structure cannot fail serialization","messagePattern":"fixed-layout structure cannot fail serialization","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"info","filePath":"crates/atuin-client/src/packfile/record.rs","lineNumber":261,"sourceCode":"\n/// An implicit assertion that matches this [`PackManifestRecordView`].\n///\n/// Do *not* modify this struct. Just like `atuin_domain::record::AdditionalData`, it gets\n/// serialized to JSON during encryption, and we rely on the serialization staying the same across\n/// versions. Field order, types, and even names all must stay the same!\n#[derive(Debug, Serialize)]\nstruct PackIA<'a> {\n    pub manifest_id: RecordId,\n    pub manifest_idx: RecordIdx,\n    pub manifest_version: &'a str,\n    pub host: HostId,\n    pub tag: &'a RecordTag,\n}\n\nimpl PackIA<'_> {\n    /// The JSON an [`paseto_v4::ImplicitAssertion`] is built from.\n    fn json(&self) -> String {\n        serde_json::to_string(self).expect(\"fixed-layout structure cannot fail serialization\")\n    }\n}\n\nimpl<'a> PackManifestRecordView<'a> {\n    /// Decided on `12` because that's what Claude's experiments showed to be the good trade-off\n    /// between compression size and compression speed and would be optimal for DSL/Fiber networks.\n    const ZSTD_ENCODING_LEVEL: NonZeroU8 = NonZeroU8::new(12).unwrap();\n\n    pub fn new(record: &'a Record<EncryptedData>) -> Result<Self, ParsingError> {\n        let manifest = PackManifestData::parse(record)?;\n        Ok(Self { record, manifest })\n    }\n\n    /// The range of history this manifest covers. Validated when the view was built.\n    #[must_use]\n    pub const fn range(&self) -> std::ops::Range<RecordIdx> {\n        self.manifest.range()\n    }","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/atuinsh/atuin/blob/15fe1318f1df51de604262eb50734c9883d48e7b/crates/atuin-client/src/packfile/record.rs#L243-L279","documentation":"Panic while serializing PackIA to JSON for a PASETO v4 implicit assertion. serde_json::to_string fails only for types that cannot be represented (non-string map keys, unhandled fallible Serialize impls); PackIA is a fixed layout of UUID-backed newtypes (RecordId, RecordIdx, HostId), a &str version, and a RecordTag — all infallible serializers with string-map-free output, hence the message 'fixed-layout structure cannot fail serialization'.","triggerScenarios":"Calling PackManifestRecordView assertion-building code during packfile encryption (building the implicit-assertion JSON for a manifest record). The only theoretical path to failure is a future field being added whose Serialize impl returns an error (e.g., a map with non-string keys or a custom fallible type).","commonSituations":"None on current code. It becomes reachable if someone extends PackIA with a type like a HashMap<NonStringKey, _> or a Serialize impl that can Err; refactors of the packfile format are the realistic way this invariant breaks.","solutions":["Treat a crash here as a code regression, not an environment problem: inspect recent changes to PackIA's fields","When adding fields to PackIA, keep them to strings/UUID newtypes/numbers, or switch this call to real error handling with map_err","If a fallible type is genuinely needed, replace expect with `.map_err(|e| eyre!(\"implicit assertion serialization failed: {e}\"))?` and propagate"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Packfile encryption paths: treat a serialization panic as a hard code bug\nlet packed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    view.ia_json() // example wrapper around PackIA::json\n}));\nlet json = packed.unwrap_or_else(|_| {\n    tracing::error!(\"PackIA serialization invariant broken — packfile format changed?\");\n    // abort the pack operation; do not write a malformed pack\n    std::process::abort();\n});","preventionTips":["Keep PackIA fields limited to infallible serde types (strings, UUID newtypes, numbers)","Revisit this expect whenever the packfile manifest layout is extended","Add a serde round-trip test for PackIA in the packfile test suite"],"tags":["rust","serde-json","panic","invariant","paseto","packfile","unreachable"],"backgroundTag":"json-serialize-panic","analyzedSha":"15fe1318f1df51de604262eb50734c9883d48e7b","analyzedAt":"2026-08-19T08:56:57.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}