{"record":{"id":"61d340b27a0eb99f","repo":"influxdata/influxdb","slug":"field-shape-of-catalog-record-ident-changed-declared-shape","errorCode":null,"errorMessage":"⚠ Field shape of catalog record ({ident}) changed ⚠\n\nDeclared shape:\n\n    {declared:#010x}\n\nActual shape:\n\n    {actual:#010x}\n\nThe fingerprint covers the field types, in order — adding, removing, reordering, or retyping a field moves it. Field renames do not, because bitcode does not encode field names.\n\nOnce a catalog record type has been shipped in a released version of the software, it cannot be modified. To introduce new functionality, add a new record type.\n\nIf this is the first time you're adding this record type, or are making modifications to it prior to releasing it, update the `shape` argument to the actual value above.\n","messagePattern":"⚠ Field shape of catalog record \\(\\{ident\\}\\) changed ⚠\n\nDeclared shape:\n\n    \\{declared:#010x\\}\n\nActual shape:\n\n    \\{actual:#010x\\}\n\nThe fingerprint covers the field types, in order — adding, removing, reordering, or retyping a field moves it\\. Field renames do not, because bitcode does not encode field names\\.\n\nOnce a catalog record type has been shipped in a released version of the software, it cannot be modified\\. To introduce new functionality, add a new record type\\.\n\nIf this is the first time you're adding this record type, or are making modifications to it prior to releasing it, update the `shape` argument to the actual value above\\.\n","errorType":"validation","errorClass":"syn::Error","httpStatus":null,"severity":"error","filePath":"influxdb3_catalog_macros/src/lib.rs","lineNumber":175,"sourceCode":"            \"catalog records cannot be generic — the persisted bytes must be one fixed shape\",\n        ));\n    }\n\n    let Fields::Named(ref fields) = item.fields else {\n        return Err(syn::Error::new(\n            item.fields.span(),\n            \"catalog records must be structs with named fields\",\n        ));\n    };\n\n    let field_types: Vec<&Type> = fields.named.iter().map(|f| &f.ty).collect();\n    for ty in &field_types {\n        types::check(ty)?;\n    }\n\n    let actual = shape::fingerprint(&field_types);\n    if actual != args.shape {\n        return Err(syn::Error::new(\n            args.shape_span,\n            shape_mismatch_message(&item.ident, args.shape, actual),\n        ));\n    }\n\n    let ident = &item.ident;\n    let id = &args.id;\n    let flags = args\n        .flags\n        .map(|f| quote!(#f))\n        .unwrap_or_else(|| quote!(crate::format::RecordFlags::none()));\n\n    Ok(quote! {\n        #[derive(\n            Debug,\n            Clone,\n            PartialEq,\n            Eq,","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/influxdata/influxdb/blob/06200ef96ba82c5f6727e5038a83af8e722c6875/influxdb3_catalog_macros/src/lib.rs#L157-L193","documentation":"A compile-time guard from the `#[catalog_record]` macro. The macro computes a fingerprint (via `shape::fingerprint`) over the record's field types in order and compares it with the `shape = 0x...` argument. A mismatch means the field layout (added/removed/reordered/retyped fields — renames don't count, bitcode doesn't encode names) differs from the declared persisted shape, which would silently corrupt previously written catalog bytes.","triggerScenarios":"Modifying the fields of a struct annotated with `#[catalog_record(shape = X)]` (adding, removing, reordering, or changing a field's type) without updating the `shape` argument. Raised in `expand` when `actual != args.shape`.","commonSituations":"A developer adds a new field to an already-released catalog record, reorders fields for tidiness, or changes `u32` to `u64` — all during a routine refactor of shipped code.","solutions":["If this is the first release of the record type (or changes are pre-release), update the `shape` argument to the actual fingerprint printed in the error message.","If the record type shipped in a released version, revert the field change; never modify it.","To add new functionality, define a NEW record type with its own shape instead of editing the existing one."],"exampleFix":"// before (added a field but kept old shape)\n#[catalog_record(shape = 0x12345678)]\nstruct Foo { id: u64, name: String }\n\n// after\n#[catalog_record(shape = 0x9abcdef0)]\nstruct Foo { id: u64, name: String }","handlingStrategy":"validation","validationCode":"// Before changing a shipped record's fields, check compatibility:\n// 1. Only append via a NEW record type if the old one was released.\n// 2. If pre-release, copy the 'Actual shape' hex from the error into `shape = 0x...`.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat released catalog record types as frozen; add new record types for new functionality.","Never reorder or retype existing fields; renames are safe but still re-verify.","After any field edit pre-release, update `shape` to the fingerprint printed in the error.","Include the compile check in CI so shape drift is caught on PRs."],"tags":["proc-macro","compile-time","schema-evolution","serialization","compatibility"],"backgroundTag":"shape-mismatch","analyzedSha":"06200ef96ba82c5f6727e5038a83af8e722c6875","analyzedAt":"2026-09-19T12:55:30.003Z","contentChangedAt":"2026-09-19T12:55:30.003Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}