{"record":{"id":"cbb2f7636e333b29","repo":"quickwit-oss/quickwit","slug":"recovery-time-range-must-contain-both-start-and-en","errorCode":null,"errorMessage":"recovery time range must contain both start and end","messagePattern":"recovery time range must contain both start and end","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"quickwit/quickwit-metastore/src/split_metadata.rs","lineNumber":216,"sourceCode":"            partition_id,\n            num_docs,\n            uncompressed_docs_size_bytes,\n            time_range_start_inclusive,\n            time_range_end_inclusive,\n            create_timestamp,\n            tags,\n            delete_opstamp,\n            num_merge_ops,\n            parent_split_ids,\n            maturation_period_millis,\n        } = recovery_metadata;\n        let time_range = match (time_range_start_inclusive, time_range_end_inclusive) {\n            (Some(start), Some(end)) if start <= end => Some(start..=end),\n            (None, None) => None,\n            (Some(start), Some(end)) => {\n                bail!(\"invalid recovery time range: start {start} is after end {end}\")\n            }\n            _ => bail!(\"recovery time range must contain both start and end\"),\n        };\n        ensure!(\n            !footer_offsets.is_empty(),\n            \"invalid recovery footer offsets\"\n        );\n        let maturity = match maturation_period_millis {\n            Some(maturation_period_millis) => SplitMaturity::Immature {\n                maturation_period: Duration::from_millis(maturation_period_millis),\n            },\n            None => SplitMaturity::Mature,\n        };\n        let split_metadata = Self {\n            split_id: split_id.into(),\n            index_uid: index_uid.ok_or_else(|| anyhow::anyhow!(\"missing recovery index UID\"))?,\n            partition_id,\n            source_id,\n            node_id,\n            num_docs: num_docs.try_into()?,","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/quickwit-oss/quickwit/blob/a39730c5cdcd1a4fe798403737ae293999ea21f8/quickwit/quickwit-metastore/src/split_metadata.rs#L198-L234","documentation":"Thrown by try_from_recovery_metadata when a recovery time range specifies only one of the two bounds: exactly one of time_range_start_inclusive / time_range_end_inclusive is present. The invariant is both-or-neither.","triggerScenarios":"Deserializing split metadata whose recovery record sets one timestamp bound but not the other, e.g. partial JSON written by an interrupted tool or hand-edit.","commonSituations":"Manually patching split metadata and filling only start; a metadata writer bug that omitted the paired field.","solutions":["Set both time_range_start_inclusive and time_range_end_inclusive, or remove both","Repair the metadata record with a script that enforces the paired-field invariant","Restore the affected metadata from backup"],"exampleFix":"// before\n{\"time_range_start_inclusive\": 1600, \"time_range_end_inclusive\": null}\n// after\n{\"time_range_start_inclusive\": 1600, \"time_range_end_inclusive\": 1700}","handlingStrategy":"validation","validationCode":"fn bounds_paired(start: Option<i64>, end: Option<i64>) -> bool {\n    matches!((start, end), (Some(_), Some(_)) | (None, None))\n}","typeGuard":"fn has_paired_bounds(m: &RecoveryMetadata) -> bool {\n    m.time_range_start_inclusive.is_some() == m.time_range_end_inclusive.is_some()\n}","tryCatchPattern":"if let Err(e) = SplitMetadata::try_from(meta) {\n    if e.to_string().contains(\"must contain both start and end\") {\n        error!(\"partial time range in metadata: {e}\");\n    } else { return Err(e); }\n}","preventionTips":["Always write both bounds together when constructing recovery metadata","Use the RecoveringSplitMetadata constructor rather than hand-built structs","Run metadata repair tools that enforce the paired-field invariant"],"tags":["metastore","split-metadata","time-range","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"a39730c5cdcd1a4fe798403737ae293999ea21f8","analyzedAt":"2026-09-08T13:19:37.784Z","contentChangedAt":"2026-09-08T13:19:37.784Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}