Skip to main content

krabka_metadata/
feature.rs

1//! KIP-584 feature framework. A [`Feature`] owns the *versioning facts* of one
2//! cluster feature: the supported range, the per-release default, the downgrade
3//! floor, the KIP-1022 dependencies, and an optional level name. The static
4//! [`feature_registry`] is the single source of truth for `ApiVersions`,
5//! `UpdateFeatures`, the `krabka format` bootstrap, and the Raft range guards.
6//! Behavioral *gating*, that is, the rejection of RPCs below a level, lives in
7//! the broker handlers that read the finalized level from the image, not here.
8
9use std::collections::BTreeMap;
10
11use crate::{MetadataImage, group_version, krabka_version, metadata_version, transaction_version};
12
13/// One versioned cluster feature (KIP-584).
14pub trait Feature: Sync {
15    /// KIP-584 feature name, e.g. `"metadata.version"`.
16    fn name(&self) -> &'static str;
17
18    /// Inclusive `[min, max]` supported level range. Advertised in
19    /// `ApiVersions.supported_features` and accepted by `UpdateFeatures`.
20    fn supported_range(&self) -> (i16, i16);
21
22    /// The level finalized at `krabka format` for the bootstrap
23    /// `metadata.version` level, which is the resolved `--release-version`.
24    /// Kafka derives the default of every feature from the release in this way.
25    fn default_level(&self, bootstrap_mv: i16) -> i16;
26
27    /// Lowest level that the live image permits for a finalize or a downgrade:
28    /// the "unsafe downgrade" floor. It defaults to the supported min, where no
29    /// live-state constraint applies.
30    fn min_required_floor(&self, _image: &MetadataImage) -> i16 {
31        self.supported_range().0
32    }
33
34    /// KIP-1022 dependencies: `(other_feature, min_level)` pairs that must be
35    /// finalized at `>= min_level` before THIS feature may be finalized at
36    /// `level`. Empty by default.
37    fn dependencies(&self, _level: i16) -> &'static [(&'static str, i16)] {
38        &[]
39    }
40
41    /// Optional Kafka level name (e.g. metadata.version's `"3.7-IV4"`).
42    /// `None` for plain integer features.
43    fn level_name(&self, _level: i16) -> Option<&'static str> {
44        None
45    }
46}
47
48/// `metadata.version` (KIP-584 / KIP-778). The range, the string table, and the
49/// SCRAM and delegation-token floor come from [`crate::metadata_version`].
50pub struct MetadataVersionFeature;
51
52impl Feature for MetadataVersionFeature {
53    fn name(&self) -> &'static str {
54        metadata_version::METADATA_VERSION_FEATURE
55    }
56    fn supported_range(&self) -> (i16, i16) {
57        (
58            metadata_version::METADATA_VERSION_MIN,
59            metadata_version::METADATA_VERSION_MAX,
60        )
61    }
62    fn default_level(&self, bootstrap_mv: i16) -> i16 {
63        // For metadata.version the release string IS the metadata.version, so
64        // the default is the bootstrap level itself, clamped into range.
65        bootstrap_mv.clamp(
66            metadata_version::METADATA_VERSION_MIN,
67            metadata_version::METADATA_VERSION_MAX,
68        )
69    }
70    fn min_required_floor(&self, image: &MetadataImage) -> i16 {
71        image.min_required_metadata_version()
72    }
73    fn level_name(&self, level: i16) -> Option<&'static str> {
74        metadata_version::from_feature_level(level).map(metadata_version::MetadataVersion::ivn)
75    }
76}
77
78/// `group.version` (KIP-848). A plain integer feature. The default rises to 1
79/// once the bootstrap metadata.version reaches the KIP-848 GA level.
80pub struct GroupVersionFeature;
81
82impl Feature for GroupVersionFeature {
83    fn name(&self) -> &'static str {
84        group_version::GROUP_VERSION_FEATURE
85    }
86    fn supported_range(&self) -> (i16, i16) {
87        (
88            group_version::GROUP_VERSION_MIN,
89            group_version::GROUP_VERSION_MAX,
90        )
91    }
92    fn default_level(&self, bootstrap_mv: i16) -> i16 {
93        if bootstrap_mv >= group_version::GROUP_VERSION_GA_METADATA_LEVEL {
94            group_version::GROUP_VERSION_MAX
95        } else {
96            group_version::GROUP_VERSION_MIN
97        }
98    }
99    // min_required_floor: inherits the default (supported min). Next-gen group
100    // state lives in the coordinator / __consumer_offsets, NOT the
101    // MetadataImage, so a live-state-aware downgrade floor can't be computed
102    // here. dependencies: inherits the empty default — Kafka 4.0
103    // declares no hard `UpdateFeatures` dependency for group.version.
104}
105
106/// `transaction.version` (KIP-890). The default jumps to 2 once the bootstrap
107/// metadata.version reaches 4.0-IV2, and 2 is the highest level Kafka defines.
108/// The downgrade floor is the supported min, because in-flight txn state lives
109/// in the `__transaction_state` log and not in the [`MetadataImage`], so this
110/// module computes no image-derived floor.
111pub struct TransactionVersionFeature;
112
113impl Feature for TransactionVersionFeature {
114    fn name(&self) -> &'static str {
115        transaction_version::TRANSACTION_VERSION_FEATURE
116    }
117    fn supported_range(&self) -> (i16, i16) {
118        (
119            transaction_version::TRANSACTION_VERSION_MIN,
120            transaction_version::TRANSACTION_VERSION_MAX,
121        )
122    }
123    // The TV_1 tier is retained as an explicit (currently coincident) threshold
124    // so a future Kafka release that splits TV_1's bootstrap level below TV_2's
125    // is a one-line constant change.
126    fn default_level(&self, bootstrap_mv: i16) -> i16 {
127        // Both TV_1 and TV_2 bootstrap at level 24 (4.0-IV2) → default jumps
128        // 0 -> 2 at >= 24. Empirically pinned.
129        use crate::transaction_version::{TV1_METADATA_LEVEL, TV2_METADATA_LEVEL};
130        match bootstrap_mv {
131            level if level >= TV2_METADATA_LEVEL => 2,
132            level if level >= TV1_METADATA_LEVEL => 1,
133            _ => 0,
134        }
135    }
136    // dependencies + min_required_floor: inherit the empty/supported-min defaults.
137}
138
139/// `share.version` (KIP-932). A plain integer feature that gates share-group
140/// membership. The default stays at the supported min, 0, which is disabled,
141/// until the bootstrap metadata.version reaches `4.2-IV0`, where Kafka's
142/// `ShareVersion.SV_1` bootstraps. Trunk's `SV_2` (KIP-1191 dead-letter
143/// queues) bootstraps at `4.4-IV0`. The registry lists it; a node supports
144/// level 2 only when it supports unstable feature versions, because Kafka
145/// 4.3.1's latest production level is 1.
146pub struct ShareVersionFeature;
147
148impl Feature for ShareVersionFeature {
149    fn name(&self) -> &'static str {
150        metadata_version::SHARE_VERSION_FEATURE
151    }
152    fn supported_range(&self) -> (i16, i16) {
153        (
154            metadata_version::SHARE_VERSION_MIN,
155            metadata_version::SHARE_VERSION_MAX,
156        )
157    }
158    fn default_level(&self, bootstrap_mv: i16) -> i16 {
159        use crate::metadata_version::{
160            SHARE_VERSION_2_DEFAULT_METADATA_LEVEL, SHARE_VERSION_DEFAULT_METADATA_LEVEL,
161        };
162        match bootstrap_mv {
163            level if level >= SHARE_VERSION_2_DEFAULT_METADATA_LEVEL => 2,
164            level if level >= SHARE_VERSION_DEFAULT_METADATA_LEVEL => 1,
165            _ => metadata_version::SHARE_VERSION_MIN,
166        }
167    }
168    // dependencies + min_required_floor: inherit the empty/supported-min defaults.
169}
170
171/// `streams.version` (KIP-1071). A plain integer feature that gates the
172/// broker-side Streams rebalance protocol. The default stays at the supported
173/// min, 0, which is disabled, until the bootstrap metadata.version reaches
174/// `4.2-IV1`, where Kafka's `StreamsVersion.SV_1` bootstraps. Below that an
175/// operator opts in through `UpdateFeatures`.
176pub struct StreamsVersionFeature;
177
178impl Feature for StreamsVersionFeature {
179    fn name(&self) -> &'static str {
180        metadata_version::STREAMS_VERSION_FEATURE
181    }
182    fn supported_range(&self) -> (i16, i16) {
183        (
184            metadata_version::STREAMS_VERSION_MIN,
185            metadata_version::STREAMS_VERSION_MAX,
186        )
187    }
188    fn default_level(&self, bootstrap_mv: i16) -> i16 {
189        if bootstrap_mv >= metadata_version::STREAMS_VERSION_DEFAULT_METADATA_LEVEL {
190            metadata_version::STREAMS_VERSION_MAX
191        } else {
192            metadata_version::STREAMS_VERSION_MIN
193        }
194    }
195    // dependencies + min_required_floor: inherit the empty/supported-min
196    // defaults. Streams group state lives in __consumer_offsets, not the
197    // MetadataImage, so a live-state-aware downgrade floor can't be computed
198    // here, mirroring group.version / share.version.
199}
200
201/// `eligible.leader.replicas.version` (KIP-966). A plain integer feature that
202/// gates the controller's ELR bookkeeping: at level 0 no partition gains an
203/// eligible-leader or last-known-leader set, at level 1 the controller
204/// maintains both.
205///
206/// `EligibleLeaderReplicasVersion` in apache/kafka bootstraps `ELRV_1` at
207/// `4.1-IV0` and declares one KIP-1022 dependency, `metadata.version` at
208/// `4.0-IV1`, the level whose `PartitionRecord` carries the ELR fields. The
209/// release default is therefore 1 from `4.1-IV0` and 0 below it, exactly as
210/// Kafka's `Feature.defaultLevel` computes it. Below `4.1-IV0` an operator
211/// turns the feature on with `kafka-features upgrade`.
212pub struct ElrVersionFeature;
213
214impl Feature for ElrVersionFeature {
215    fn name(&self) -> &'static str {
216        metadata_version::ELR_VERSION_FEATURE
217    }
218    fn supported_range(&self) -> (i16, i16) {
219        (
220            metadata_version::ELR_VERSION_MIN,
221            metadata_version::ELR_VERSION_MAX,
222        )
223    }
224    fn default_level(&self, bootstrap_mv: i16) -> i16 {
225        if bootstrap_mv >= metadata_version::ELR_DEFAULT_METADATA_LEVEL {
226            metadata_version::ELR_VERSION_MAX
227        } else {
228            metadata_version::ELR_VERSION_MIN
229        }
230    }
231    fn dependencies(&self, level: i16) -> &'static [(&'static str, i16)] {
232        if level >= 1 {
233            const ELRV_1_DEPENDENCIES: &[(&str, i16)] = &[(
234                metadata_version::METADATA_VERSION_FEATURE,
235                metadata_version::ELR_MIN_LEVEL,
236            )];
237            ELRV_1_DEPENDENCIES
238        } else {
239            &[]
240        }
241    }
242    // min_required_floor: inherits the supported min. ELR membership lives in
243    // the MetadataImage, but Kafka clears it with PartitionChangeRecords on a
244    // downgrade to 0 rather than refusing the downgrade, so no floor applies.
245}
246
247/// `kraft.version` is finalized by a `KRaft` control record, never by a
248/// KIP-584 `FeatureLevelRecord`.
249pub struct KRaftVersionFeature;
250
251impl Feature for KRaftVersionFeature {
252    fn name(&self) -> &'static str {
253        metadata_version::KRAFT_VERSION_FEATURE
254    }
255    fn supported_range(&self) -> (i16, i16) {
256        (0, 1)
257    }
258    fn default_level(&self, _bootstrap_mv: i16) -> i16 {
259        0
260    }
261}
262
263/// `krabka.version`, the krabka-owned feature that gates krabka-only on-disk
264/// and inter-node format changes (see [`crate::krabka_version`]). A fresh
265/// cluster bootstraps at level 0 whatever the bootstrap metadata.version is,
266/// so the bootstrap writes no record for it. An Apache Kafka node in the same
267/// cluster supports only level 0 of a feature it does not know.
268pub struct KrabkaVersionFeature;
269
270impl Feature for KrabkaVersionFeature {
271    fn name(&self) -> &'static str {
272        krabka_version::KRABKA_VERSION_FEATURE
273    }
274    fn supported_range(&self) -> (i16, i16) {
275        (
276            krabka_version::KRABKA_VERSION_MIN,
277            krabka_version::KRABKA_VERSION_MAX,
278        )
279    }
280    fn default_level(&self, _bootstrap_mv: i16) -> i16 {
281        krabka_version::KRABKA_VERSION_MIN
282    }
283    // dependencies: inherits the empty default. No krabka.version level needs a
284    // metadata.version yet. min_required_floor: inherits the supported min.
285    // Levels 0 and 1 are the same formats, and every build that supports level
286    // 2 or 3 reads that level's formats at every level, so lowering it strands
287    // nothing.
288}
289
290/// All features this broker supports finalizing. Single source of truth.
291#[must_use]
292pub fn feature_registry() -> &'static [&'static dyn Feature] {
293    const REGISTRY: &[&dyn Feature] = &[
294        &MetadataVersionFeature,
295        &GroupVersionFeature,
296        &TransactionVersionFeature,
297        &ShareVersionFeature,
298        &StreamsVersionFeature,
299        &ElrVersionFeature,
300        &KRaftVersionFeature,
301        &KrabkaVersionFeature,
302    ];
303    REGISTRY
304}
305
306/// Supported feature ranges advertised by a Krabka broker or controller when
307/// it registers with the `KRaft` controller.
308#[must_use]
309pub fn supported_feature_ranges() -> BTreeMap<String, (i16, i16)> {
310    let mut ranges: BTreeMap<_, _> = feature_registry()
311        .iter()
312        .map(|feature| (feature.name().to_owned(), feature.supported_range()))
313        .collect();
314    // Registration-only KIP-1155 capability signal. Keep it out of the
315    // finalizable feature registry: upstream has not assigned the KIP's
316    // promised metadata.version level, and inventing one would make
317    // ApiVersions claim support for an undefined metadata record format.
318    ranges.insert(
319        metadata_version::METADATA_DOWNGRADE_CAPABILITY_FEATURE.into(),
320        (
321            metadata_version::METADATA_DOWNGRADE_CAPABILITY_LEVEL,
322            metadata_version::METADATA_DOWNGRADE_CAPABILITY_LEVEL,
323        ),
324    );
325    ranges
326}
327
328/// Look up a registered feature by name.
329#[must_use]
330pub fn feature(name: &str) -> Option<&'static dyn Feature> {
331    feature_registry()
332        .iter()
333        .copied()
334        .find(|f| f.name() == name)
335}
336
337/// KIP-584/1022 bootstrap: one `V1FeatureLevel` record per registered feature
338/// at its per-release default, derived from `bootstrap_mv`, the bootstrap
339/// metadata.version level.
340///
341/// Both `krabka format` and the broker's standalone self-bootstrap use this, so
342/// a fresh cluster finalizes the release default of every feature.
343#[must_use]
344pub fn bootstrap_feature_records(bootstrap_mv: i16) -> Vec<crate::MetadataRecord> {
345    bootstrap_feature_records_with_overrides(bootstrap_mv, &BTreeMap::new())
346}
347
348/// KIP-1022 `krabka format` seeding: one `V1FeatureLevel` record per registered
349/// feature.
350///
351/// Each feature is at its explicit `--feature NAME=LEVEL` override if present
352/// in `overrides`, else at its per-release default for `bootstrap_mv`.
353///
354/// Mirrors `kafka-storage format`: this function **omits** a feature whose
355/// resolved level is `0`. Level 0 means absent and disabled, which is the
356/// default state. The seeded record set therefore matches what Kafka writes to
357/// its `bootstrap.checkpoint`.
358#[must_use]
359pub fn bootstrap_feature_records_with_overrides(
360    bootstrap_mv: i16,
361    overrides: &BTreeMap<String, i16>,
362) -> Vec<crate::MetadataRecord> {
363    feature_registry()
364        .iter()
365        .filter(|f| f.name() != metadata_version::KRAFT_VERSION_FEATURE)
366        .filter_map(|f| {
367            let level = overrides
368                .get(f.name())
369                .copied()
370                .unwrap_or_else(|| f.default_level(bootstrap_mv));
371            (level > 0).then(|| {
372                crate::MetadataRecord::V1FeatureLevel(crate::FeatureLevelRecord {
373                    name: f.name().to_owned(),
374                    level,
375                })
376            })
377        })
378        .collect()
379}
380
381/// KIP-1022 dependency validation for a fully-resolved feature→level map, as
382/// seeded by `krabka format`.
383///
384/// For every finalized feature, each of its `dependencies(level)` must be
385/// present in `resolved` at `>=` the required level. Returns `Err` with the
386/// name of the first unmet dependency, enforcing the rule at format time in the
387/// same way as the `UpdateFeatures` handler.
388/// # Errors
389/// Returns an error naming the first finalized feature whose required
390/// dependency is absent or finalized below the minimum level.
391pub fn validate_feature_dependencies(resolved: &BTreeMap<String, i16>) -> Result<(), String> {
392    check_deps(resolved, |name, level| {
393        feature(name).map_or(&[][..], |f| f.dependencies(level))
394    })
395}
396
397/// Core of [`validate_feature_dependencies`], parameterized over the dependency
398/// source so the rejection logic is unit-testable without a dependency-bearing
399/// feature in the real registry.
400fn check_deps(
401    resolved: &BTreeMap<String, i16>,
402    deps_of: impl Fn(&str, i16) -> &'static [(&'static str, i16)],
403) -> Result<(), String> {
404    for (name, &level) in resolved {
405        for &(dep, min) in deps_of(name, level) {
406            let have = resolved.get(dep).copied().unwrap_or(0);
407            if have < min {
408                return Err(format!(
409                    "feature {name}={level} requires {dep}>={min}, but {dep} is finalized at {have}"
410                ));
411            }
412        }
413    }
414    Ok(())
415}
416
417/// True if `level` is within the registered feature's supported range.
418/// `false` for an unknown feature (nothing supports that level).
419#[must_use]
420pub fn is_supported_level(name: &str, level: i16) -> bool {
421    feature(name).is_some_and(|f| {
422        let (min, max) = f.supported_range();
423        (min..=max).contains(&level)
424    })
425}
426
427#[cfg(test)]
428mod tests {
429    use std::collections::BTreeMap;
430
431    use assert2::check;
432
433    use super::*;
434
435    #[test]
436    fn registry_contains_metadata_version() {
437        let f = feature("metadata.version").expect("registered");
438        assert2::assert!(f.supported_range() == (7, 34));
439        assert2::assert!(feature("not.a.feature").is_none());
440    }
441
442    #[test]
443    fn metadata_version_default_is_the_bootstrap_level_clamped() {
444        let f = feature("metadata.version").unwrap();
445        for (_case, bootstrap, want) in [
446            ("maximum bootstrap", 34, 34),
447            ("minimum bootstrap", 7, 7),
448            ("above maximum", 99, 34),
449            ("below minimum", 1, 7),
450        ] {
451            assert2::assert!(f.default_level(bootstrap) == want);
452        }
453    }
454
455    #[test]
456    fn is_supported_level_checks_range() {
457        for (name, level, want) in [
458            ("metadata.version", 7, true),
459            ("metadata.version", 34, true),
460            ("metadata.version", 6, false),
461            ("metadata.version", 35, false),
462            // Kafka's TransactionVersion has TV_0 to TV_2 only (broker #784).
463            ("transaction.version", 2, true),
464            ("transaction.version", 3, false),
465            ("krabka.version", 0, true),
466            ("krabka.version", 1, true),
467            ("krabka.version", 2, true),
468            ("krabka.version", 3, true),
469            ("krabka.version", 4, false),
470            ("not.a.feature", 1, false),
471        ] {
472            assert2::assert!(is_supported_level(name, level) == want);
473        }
474    }
475
476    /// The whole map a node advertises at registration and in `ApiVersions`.
477    #[test]
478    fn supported_feature_ranges_are_pinned() {
479        let want: BTreeMap<String, (i16, i16)> = [
480            ("eligible.leader.replicas.version", (0, 1)),
481            ("group.version", (0, 1)),
482            ("krabka.metadata.downgrade", (1, 1)),
483            ("krabka.version", (0, 3)),
484            ("kraft.version", (0, 1)),
485            ("metadata.version", (7, 34)),
486            ("share.version", (0, 2)),
487            ("streams.version", (0, 1)),
488            ("transaction.version", (0, 2)),
489        ]
490        .into_iter()
491        .map(|(name, range)| (name.to_owned(), range))
492        .collect();
493        check!(supported_feature_ranges() == want);
494    }
495
496    /// `krabka.version` bootstraps at 0, so it earns no record, whatever the
497    /// bootstrap metadata.version. An explicit override still finalizes it.
498    #[test]
499    fn krabka_version_bootstraps_at_zero() {
500        use crate::metadata_version::{METADATA_VERSION_MAX, METADATA_VERSION_MIN};
501        for (case, bootstrap, overrides, want) in [
502            ("minimum release", METADATA_VERSION_MIN, vec![], None),
503            ("latest release", METADATA_VERSION_MAX, vec![], None),
504            (
505                "override to 1 earns a record",
506                METADATA_VERSION_MAX,
507                vec![("krabka.version".to_owned(), 1)],
508                Some(1),
509            ),
510            (
511                "override to 0 omits the record",
512                METADATA_VERSION_MAX,
513                vec![("krabka.version".to_owned(), 0)],
514                None,
515            ),
516        ] {
517            let overrides: BTreeMap<String, i16> = overrides.into_iter().collect();
518            let levels = levels_of(&bootstrap_feature_records_with_overrides(
519                bootstrap, &overrides,
520            ));
521            check!(levels.get("krabka.version").copied() == want, "{case}");
522        }
523    }
524
525    #[test]
526    fn krabka_version_declares_no_dependencies_and_no_level_name() {
527        let f = feature("krabka.version").expect("registered");
528        check!((f.dependencies(0), f.dependencies(1), f.level_name(1)) == (&[][..], &[][..], None));
529        let resolved = BTreeMap::from([("krabka.version".to_owned(), 1)]);
530        check!(validate_feature_dependencies(&resolved) == Ok(()));
531    }
532
533    #[test]
534    fn downgrade_capability_is_registration_only() {
535        let ranges = supported_feature_ranges();
536        check!(
537            ranges.get(metadata_version::METADATA_DOWNGRADE_CAPABILITY_FEATURE)
538                == Some(&(
539                    metadata_version::METADATA_DOWNGRADE_CAPABILITY_LEVEL,
540                    metadata_version::METADATA_DOWNGRADE_CAPABILITY_LEVEL,
541                ))
542        );
543        check!(feature(metadata_version::METADATA_DOWNGRADE_CAPABILITY_FEATURE).is_none());
544    }
545
546    #[test]
547    fn metadata_version_level_name() {
548        let f = feature("metadata.version").unwrap();
549        for (_case, level, want) in [
550            ("latest level", 34, Some("4.5-IV0")),
551            ("controller unregistration level", 33, Some("4.4-IV2")),
552            ("CIDR ACL level", 32, Some("4.4-IV1")),
553            ("cordoned log dirs level", 30, Some("4.3-IV0")),
554            ("earliest level", 7, Some("3.3-IV3")),
555            ("unknown level", 99, None),
556        ] {
557            assert2::assert!(f.level_name(level) == want);
558        }
559    }
560
561    #[test]
562    fn group_version_registered_with_range() {
563        let f = feature("group.version").expect("registered");
564        assert2::assert!(f.supported_range() == (0, 1));
565    }
566
567    #[test]
568    fn registry_feature_contracts_are_pinned() {
569        let image = MetadataImage::new(uuid::Uuid::nil());
570        let expected = [
571            ("metadata.version", (7, 34), 25, 7),
572            ("group.version", (0, 1), 1, 0),
573            ("transaction.version", (0, 2), 2, 0),
574            ("share.version", (0, 2), 0, 0),
575            ("streams.version", (0, 1), 0, 0),
576            ("eligible.leader.replicas.version", (0, 1), 0, 0),
577            // Registered like the rest, though it is finalized by a KRaft
578            // control record rather than by UpdateFeatures. Leaving it out of
579            // this table left its name, range and default entirely unasserted.
580            ("kraft.version", (0, 1), 0, 0),
581            ("krabka.version", (0, 3), 0, 0),
582        ];
583
584        for (name, range, default_at_25, floor) in expected {
585            let f = feature(name).expect("registered");
586            check!(
587                (
588                    f.supported_range(),
589                    f.default_level(25),
590                    f.min_required_floor(&image),
591                ) == (range, default_at_25, floor),
592                "feature {name}"
593            );
594        }
595    }
596
597    #[test]
598    fn group_version_default_follows_release() {
599        let f = feature("group.version").unwrap();
600        for (_case, bootstrap, want) in [
601            (
602                "before GA threshold",
603                group_version::GROUP_VERSION_GA_METADATA_LEVEL - 1,
604                0,
605            ),
606            (
607                "at GA threshold",
608                group_version::GROUP_VERSION_GA_METADATA_LEVEL,
609                1,
610            ),
611            ("after GA threshold", 25, 1),
612        ] {
613            assert2::assert!(f.default_level(bootstrap) == want);
614        }
615    }
616
617    #[test]
618    fn group_version_declares_no_hard_dependencies() {
619        let f = feature("group.version").unwrap();
620        assert2::assert!(
621            (f.dependencies(0).is_empty(), f.dependencies(1).is_empty()) == (true, true)
622        );
623    }
624
625    #[test]
626    fn transaction_version_registered() {
627        let f = feature("transaction.version").expect("registered");
628        assert2::assert!(f.supported_range() == (0, 2));
629    }
630
631    #[test]
632    fn streams_version_registered_with_no_dependencies() {
633        let f = feature("streams.version").expect("registered");
634        check!((f.supported_range(), f.dependencies(1).is_empty()) == ((0, 1), true));
635    }
636
637    /// Kafka's `Feature.defaultLevel`: the highest feature level whose
638    /// bootstrap metadata version is at or below the bootstrap level.
639    #[test]
640    fn opt_in_feature_defaults_follow_kafka_bootstrap_levels() {
641        use crate::metadata_version::{
642            ELR_DEFAULT_METADATA_LEVEL, METADATA_VERSION_MAX,
643            SHARE_VERSION_2_DEFAULT_METADATA_LEVEL, SHARE_VERSION_DEFAULT_METADATA_LEVEL,
644            STREAMS_VERSION_DEFAULT_METADATA_LEVEL,
645        };
646        for (name, bootstrap, want) in [
647            ("eligible.leader.replicas.version", 25, 0),
648            (
649                "eligible.leader.replicas.version",
650                ELR_DEFAULT_METADATA_LEVEL - 1,
651                0,
652            ),
653            (
654                "eligible.leader.replicas.version",
655                ELR_DEFAULT_METADATA_LEVEL,
656                1,
657            ),
658            ("eligible.leader.replicas.version", METADATA_VERSION_MAX, 1),
659            ("share.version", SHARE_VERSION_DEFAULT_METADATA_LEVEL - 1, 0),
660            ("share.version", SHARE_VERSION_DEFAULT_METADATA_LEVEL, 1),
661            (
662                "share.version",
663                SHARE_VERSION_2_DEFAULT_METADATA_LEVEL - 1,
664                1,
665            ),
666            ("share.version", SHARE_VERSION_2_DEFAULT_METADATA_LEVEL, 2),
667            ("share.version", METADATA_VERSION_MAX, 2),
668            (
669                "streams.version",
670                STREAMS_VERSION_DEFAULT_METADATA_LEVEL - 1,
671                0,
672            ),
673            ("streams.version", STREAMS_VERSION_DEFAULT_METADATA_LEVEL, 1),
674            ("streams.version", METADATA_VERSION_MAX, 1),
675        ] {
676            check!(
677                feature(name).unwrap().default_level(bootstrap) == want,
678                "feature {name} at bootstrap level {bootstrap}"
679            );
680        }
681    }
682
683    #[test]
684    fn elr_version_is_opt_in_and_depends_on_the_elr_metadata_level() {
685        let f = feature("eligible.leader.replicas.version").expect("registered");
686        // ELRV_1 bootstraps at 4.1-IV0, so the latest level enables it.
687        check!(
688            (
689                f.supported_range(),
690                f.default_level(metadata_version::METADATA_VERSION_MAX),
691                f.dependencies(0),
692                f.dependencies(1),
693            ) == (
694                (0, 1),
695                1,
696                &[][..],
697                &[(
698                    metadata_version::METADATA_VERSION_FEATURE,
699                    metadata_version::ELR_MIN_LEVEL,
700                )][..],
701            )
702        );
703    }
704
705    #[test]
706    fn elr_version_level_one_requires_the_elr_metadata_version() {
707        for (_case, metadata_level, want_err) in [
708            ("below 4.0-IV1", metadata_version::ELR_MIN_LEVEL - 1, true),
709            ("at 4.0-IV1", metadata_version::ELR_MIN_LEVEL, false),
710            (
711                "above 4.0-IV1",
712                metadata_version::METADATA_VERSION_MAX,
713                false,
714            ),
715        ] {
716            let resolved = BTreeMap::from([
717                (
718                    metadata_version::METADATA_VERSION_FEATURE.to_owned(),
719                    metadata_level,
720                ),
721                (metadata_version::ELR_VERSION_FEATURE.to_owned(), 1),
722            ]);
723            check!(validate_feature_dependencies(&resolved).is_err() == want_err);
724        }
725    }
726
727    #[test]
728    fn transaction_version_default_jumps_to_two_at_4_0_iv2() {
729        let f = feature("transaction.version").unwrap();
730        for (_case, bootstrap, want) in [
731            ("below activation", 23, 0),
732            ("at activation", 24, 2),
733            ("after activation", 25, 2),
734        ] {
735            assert2::assert!(f.default_level(bootstrap) == want);
736        }
737    }
738
739    #[test]
740    fn transaction_version_declares_no_hard_dependencies() {
741        let f = feature("transaction.version").unwrap();
742        for level in [0, 1, 2] {
743            assert2::assert!(f.dependencies(level).is_empty());
744        }
745    }
746
747    #[test]
748    fn transaction_version_ga_threshold_is_4_0_iv2() {
749        // Anchor the bare 24 to the metadata.version table (mirrors the
750        // group.version GA-threshold anchor test).
751        assert2::assert!(
752            metadata_version::from_feature_level(transaction_version::TV2_METADATA_LEVEL)
753                .unwrap()
754                .ivn()
755                == "4.0-IV2"
756        );
757    }
758
759    fn levels_of(recs: &[crate::MetadataRecord]) -> BTreeMap<String, i16> {
760        recs.iter()
761            .filter_map(|r| match r {
762                crate::MetadataRecord::V1FeatureLevel(f) => Some((f.name.clone(), f.level)),
763                _ => None,
764            })
765            .collect()
766    }
767
768    #[test]
769    fn bootstrap_omits_level_zero_features() {
770        // share.version / streams.version / ELR default to 0 at 4.0-IV3 → no
771        // record emitted (level 0 = absent = disabled, like Kafka's format).
772        let levels = levels_of(&bootstrap_feature_records(25));
773        assert2::assert!(
774            levels
775                == BTreeMap::from([
776                    ("metadata.version".to_owned(), 25),
777                    ("group.version".to_owned(), 1),
778                    ("transaction.version".to_owned(), 2),
779                ])
780        );
781    }
782
783    /// The whole record set, in registry order. `krabka.version` defaults to
784    /// 0, so it earns no record, as Kafka writes none for a level-0 feature.
785    #[test]
786    fn bootstrap_at_the_latest_level_enables_every_released_feature() {
787        let want: Vec<crate::MetadataRecord> = [
788            ("metadata.version", 34),
789            ("group.version", 1),
790            ("transaction.version", 2),
791            ("share.version", 2),
792            ("streams.version", 1),
793            ("eligible.leader.replicas.version", 1),
794        ]
795        .into_iter()
796        .map(|(name, level)| {
797            crate::MetadataRecord::V1FeatureLevel(crate::FeatureLevelRecord {
798                name: name.to_owned(),
799                level,
800            })
801        })
802        .collect();
803        check!(bootstrap_feature_records(metadata_version::METADATA_VERSION_MAX) == want);
804    }
805
806    #[test]
807    fn bootstrap_with_overrides_applies_explicit_levels() {
808        // group.version overridden down to 0 → omitted; the rest follow mv=25.
809        let mut ov = BTreeMap::new();
810        ov.insert("group.version".to_owned(), 0i16);
811        let levels = levels_of(&bootstrap_feature_records_with_overrides(25, &ov));
812        assert2::assert!(
813            levels
814                == BTreeMap::from([
815                    ("metadata.version".to_owned(), 25),
816                    ("transaction.version".to_owned(), 2),
817                ])
818        );
819    }
820
821    #[test]
822    fn bootstrap_override_can_enable_an_opt_in_feature() {
823        // streams.version is 0 by default below 4.2-IV1; an explicit override
824        // turns it on and earns a record.
825        let mut ov = BTreeMap::new();
826        ov.insert("streams.version".to_owned(), 1i16);
827        let levels = levels_of(&bootstrap_feature_records_with_overrides(25, &ov));
828        assert2::assert!(levels.get("streams.version") == Some(&1));
829    }
830
831    #[test]
832    fn bootstrap_unlisted_feature_follows_bootstrap_mv() {
833        // mv=23: at/above group GA (22) but below txn GA (24) → group=1,
834        // transaction omitted (default 0), metadata.version mirrors mv.
835        let levels = levels_of(&bootstrap_feature_records_with_overrides(
836            23,
837            &BTreeMap::new(),
838        ));
839        assert2::assert!(
840            levels
841                == BTreeMap::from([
842                    ("metadata.version".to_owned(), 23),
843                    ("group.version".to_owned(), 1),
844                ])
845        );
846    }
847
848    #[test]
849    fn validate_dependencies_ok_for_real_registry() {
850        // The real registry declares no dependencies, so any resolved set passes.
851        let mut resolved = BTreeMap::new();
852        resolved.insert("metadata.version".to_owned(), 25i16);
853        resolved.insert("group.version".to_owned(), 1i16);
854        resolved.insert("transaction.version".to_owned(), 2i16);
855        assert2::assert!(validate_feature_dependencies(&resolved).is_ok());
856    }
857
858    #[test]
859    fn check_deps_enforces_minimum_dependency_levels() {
860        // Synthetic dependency source: "b" at level 1 requires "a" >= 2.
861        fn deps_of(name: &str, level: i16) -> &'static [(&'static str, i16)] {
862            match (name, level) {
863                ("b", 1) => &[("a", 2)],
864                _ => &[],
865            }
866        }
867        let mut ok = BTreeMap::new();
868        ok.insert("a".to_owned(), 2i16);
869        ok.insert("b".to_owned(), 1i16);
870        assert2::assert!(check_deps(&ok, deps_of).is_ok());
871
872        let mut too_low = BTreeMap::new();
873        too_low.insert("a".to_owned(), 1i16);
874        too_low.insert("b".to_owned(), 1i16);
875        assert2::assert!(check_deps(&too_low, deps_of).is_err());
876
877        // dependency feature absent entirely (treated as level 0) → rejected.
878        let mut missing = BTreeMap::new();
879        missing.insert("b".to_owned(), 1i16);
880        assert2::assert!(check_deps(&missing, deps_of).is_err());
881    }
882
883    #[test]
884    fn group_version_ga_threshold_is_4_0_iv0() {
885        // Anchor the bare `22` to the metadata.version table so the
886        // bootstrap-default threshold can't silently drift from `4.0-IV0`
887        // (mirrors metadata_version's `gate_level_constants`).
888        assert2::assert!(
889            metadata_version::from_feature_level(group_version::GROUP_VERSION_GA_METADATA_LEVEL)
890                .unwrap()
891                .ivn()
892                == "4.0-IV0"
893        );
894    }
895
896    // --- mutation-coverage tests --------------------------------------------
897    //
898    // Exercise the per-feature accessors the suite above never reads directly.
899    // Some sub-mutants are equivalent and intentionally left: a feature whose
900    // supported min is 0 makes "replace floor with 0" a no-op; group/streams
901    // `supported_range` and `default_level` mutated to their real `(0,1)` / `0`;
902    // `dependencies` `&[]` vs a leaked empty slice; and `validate_feature_
903    // dependencies -> Ok(())` (the live registry declares no dependencies).
904
905    #[test]
906    fn default_min_required_floor_is_supported_min() {
907        // group.version uses the default min_required_floor -> supported min (0).
908        let img = MetadataImage::new(uuid::Uuid::nil());
909        assert2::assert!(feature("group.version").unwrap().min_required_floor(&img) == 0);
910    }
911
912    #[test]
913    fn metadata_version_min_required_floor_tracks_image() {
914        // The override returns the image's min_required_metadata_version.
915        let img = MetadataImage::new(uuid::Uuid::nil());
916        let mv = feature("metadata.version").unwrap();
917        // Empty image floor is METADATA_VERSION_MIN (7), distinct from 0/1/-1.
918        assert2::assert!(mv.min_required_floor(&img) == 7);
919        assert2::assert!(img.min_required_metadata_version() == 7);
920    }
921
922    #[test]
923    fn plain_features_have_no_level_name() {
924        // Integer features use the default level_name -> None.
925        assert2::assert!(feature("group.version").unwrap().level_name(1).is_none());
926        assert2::assert!(
927            feature("transaction.version")
928                .unwrap()
929                .level_name(2)
930                .is_none()
931        );
932    }
933
934    #[test]
935    fn share_version_accessors() {
936        let f = feature("share.version").expect("registered");
937        // Opt-in below 4.2-IV0, where Kafka's SV_1 bootstraps.
938        check!(
939            (f.name(), f.supported_range(), f.default_level(25)) == ("share.version", (0, 2), 0)
940        );
941    }
942}