copybook_governance_runtime/
lib.rs1#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
2pub mod feature_flags {
10 pub use copybook_governance_grid::feature_flags::*;
11}
12
13pub mod support_matrix {
15 pub use copybook_governance_grid::support_matrix::*;
16}
17
18use serde::Serialize;
19
20pub use copybook_governance_grid::{
21 GovernanceSummary, GovernedFeatureBinding, feature_flags_for_support_id, governance_bindings,
22 summarize_governance,
23};
24pub use feature_flags::{
25 Feature, FeatureCategory, FeatureFlags, FeatureFlagsBuilder, FeatureFlagsHandle,
26 FeatureLifecycle,
27};
28pub use support_matrix::{FeatureId, FeatureSupport, SupportStatus};
29
30#[derive(Debug, Clone, Serialize)]
32#[non_exhaustive]
33pub struct FeatureGovernanceState {
34 pub support_id: FeatureId,
36 pub support_name: &'static str,
38 pub support_description: &'static str,
40 pub support_status: SupportStatus,
42 pub doc_ref: Option<&'static str>,
44 pub rationale: &'static str,
46 pub required_feature_flags: &'static [Feature],
48 pub missing_feature_flags: Vec<Feature>,
50 pub runtime_enabled: bool,
52}
53
54impl FeatureGovernanceState {
55 #[inline]
57 #[must_use]
58 pub fn from_support(feature: &'static FeatureSupport) -> Self {
59 Self {
60 support_id: feature.id,
61 support_name: feature.name,
62 support_description: feature.description,
63 support_status: feature.status,
64 doc_ref: feature.doc_ref,
65 rationale: "No runtime governance mapping requested.",
66 required_feature_flags: &[],
67 missing_feature_flags: Vec::new(),
68 runtime_enabled: true,
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, Serialize)]
75#[non_exhaustive]
76pub struct FeatureGovernanceSummary {
77 pub total_support_features: usize,
79 pub mapped_support_features: usize,
81 pub total_linked_feature_flags: usize,
83 pub runtime_enabled_features: usize,
85 pub runtime_disabled_features: usize,
87}
88
89impl FeatureGovernanceSummary {
90 #[inline]
92 #[must_use]
93 pub const fn all_support_rows_present(&self) -> bool {
94 self.total_support_features == self.mapped_support_features
95 }
96
97 #[inline]
99 #[must_use]
100 pub const fn has_runtime_unavailable_features(&self) -> bool {
101 self.runtime_disabled_features > 0
102 }
103}
104
105#[inline]
107#[must_use]
108pub fn support_states() -> Vec<FeatureGovernanceState> {
109 support_matrix::all_features()
110 .iter()
111 .map(FeatureGovernanceState::from_support)
112 .collect()
113}
114
115#[inline]
117#[must_use]
118pub fn governance_states(feature_flags: &FeatureFlags) -> Vec<FeatureGovernanceState> {
119 governance_bindings()
120 .iter()
121 .filter_map(|binding| governance_state_for_support_id(binding.support_id, feature_flags))
122 .collect()
123}
124
125#[inline]
127#[must_use]
128pub fn governance_state_for_support_id(
129 support_id: FeatureId,
130 feature_flags: &FeatureFlags,
131) -> Option<FeatureGovernanceState> {
132 let binding = governance_bindings()
133 .iter()
134 .find(|entry| entry.support_id == support_id)?;
135 let support = support_matrix::find_feature_by_id(support_id)?;
136
137 let mut missing_feature_flags = Vec::new();
138 for flag in binding.feature_flags {
139 if !feature_flags.is_enabled(*flag) {
140 missing_feature_flags.push(*flag);
141 }
142 }
143
144 let runtime_enabled = missing_feature_flags.is_empty();
145 Some(FeatureGovernanceState {
146 support_id,
147 support_name: support.name,
148 support_description: support.description,
149 support_status: support.status,
150 doc_ref: support.doc_ref,
151 rationale: binding.rationale,
152 required_feature_flags: binding.feature_flags,
153 missing_feature_flags,
154 runtime_enabled,
155 })
156}
157
158#[inline]
160#[must_use]
161pub fn is_support_runtime_available(support_id: FeatureId, feature_flags: &FeatureFlags) -> bool {
162 governance_state_for_support_id(support_id, feature_flags)
163 .is_some_and(|state| state.runtime_enabled)
164}
165
166#[inline]
168#[must_use]
169pub fn runtime_summary(feature_flags: &FeatureFlags) -> FeatureGovernanceSummary {
170 let base = summarize_governance();
171 let states = governance_states(feature_flags);
172 let runtime_enabled_features = states.iter().filter(|state| state.runtime_enabled).count();
173
174 FeatureGovernanceSummary {
175 total_support_features: base.total_support_features,
176 mapped_support_features: base.mapped_support_features,
177 total_linked_feature_flags: base.total_linked_feature_flags,
178 runtime_enabled_features,
179 runtime_disabled_features: states.len().saturating_sub(runtime_enabled_features),
180 }
181}
182
183#[cfg(test)]
184#[allow(clippy::expect_used)]
185#[allow(clippy::unwrap_used)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_support_states_nonempty() {
191 let states = support_states();
192 assert!(!states.is_empty());
193 assert!(states.iter().all(|state| state.runtime_enabled));
194 }
195
196 #[test]
197 fn test_runtime_state_for_supported_feature() {
198 let flags = FeatureFlags::default();
199 let state = governance_state_for_support_id(FeatureId::Comp1Comp2, &flags)
200 .expect("state should exist for mapped feature");
201 assert_eq!(state.support_id, FeatureId::Comp1Comp2);
202 assert!(state.runtime_enabled);
203 assert_eq!(state.required_feature_flags.len(), 2);
204 }
205
206 #[test]
207 fn test_runtime_state_reports_missing_feature_flags() {
208 let flags = FeatureFlags::builder()
209 .disable(Feature::Comp1)
210 .disable(Feature::Comp2)
211 .build();
212 let state = governance_state_for_support_id(FeatureId::Comp1Comp2, &flags)
213 .expect("state should exist for mapped feature");
214 assert!(!state.runtime_enabled);
215 assert_eq!(state.missing_feature_flags.len(), 2);
216 }
217
218 #[test]
219 fn test_runtime_summary_counts() {
220 let flags = FeatureFlags::builder()
221 .disable(Feature::SignSeparate)
222 .build();
223 let summary = runtime_summary(&flags);
224 assert!(summary.total_support_features >= 1);
225 assert!(summary.mapped_support_features <= summary.total_support_features);
226 assert!(summary.runtime_enabled_features <= summary.total_support_features);
227 }
228
229 #[test]
230 fn test_is_support_runtime_available() {
231 let flags = FeatureFlags::builder()
232 .enable(Feature::SignSeparate)
233 .disable(Feature::Comp1)
234 .build();
235 assert!(is_support_runtime_available(
236 FeatureId::SignSeparate,
237 &flags
238 ));
239 assert!(!is_support_runtime_available(FeatureId::Comp1Comp2, &flags));
240 }
241
242 #[test]
243 fn test_support_id_lookup() {
244 assert!(support_matrix::find_feature_by_id(FeatureId::Level88Conditions).is_some());
245 assert!(support_matrix::find_feature_by_id(FeatureId::NestedOdo).is_some());
246 assert!(support_matrix::find_feature_by_id(FeatureId::SignSeparate).is_some());
247 }
248
249 #[test]
250 fn test_support_states_count_matches_all_features() {
251 let states = support_states();
252 assert_eq!(states.len(), support_matrix::all_features().len());
253 }
254
255 #[test]
256 fn test_support_states_all_have_empty_required_flags() {
257 for state in support_states() {
258 assert!(
259 state.required_feature_flags.is_empty(),
260 "support_states should have no required flags for {:?}",
261 state.support_id
262 );
263 assert!(state.missing_feature_flags.is_empty());
264 }
265 }
266
267 #[test]
268 fn test_governance_states_with_all_defaults() {
269 let flags = FeatureFlags::default();
270 let states = governance_states(&flags);
271 assert!(!states.is_empty());
272 let renames_state = states
274 .iter()
275 .find(|s| s.support_id == FeatureId::Level66Renames)
276 .expect("Level66Renames should be in governance states");
277 assert!(!renames_state.runtime_enabled);
278 }
279
280 #[test]
281 fn test_governance_states_with_all_features_enabled() {
282 let mut flags = FeatureFlags::default();
283 for feature in feature_flags::all_features() {
284 flags.enable(feature);
285 }
286 let states = governance_states(&flags);
287 for state in &states {
288 assert!(
289 state.runtime_enabled,
290 "{:?} should be runtime enabled when all flags on",
291 state.support_id
292 );
293 assert!(state.missing_feature_flags.is_empty());
294 }
295 }
296
297 #[test]
298 fn test_governance_states_with_all_features_disabled() {
299 let mut flags = FeatureFlags::default();
300 for feature in feature_flags::all_features() {
301 flags.disable(feature);
302 }
303 let states = governance_states(&flags);
304 for state in &states {
306 if !state.required_feature_flags.is_empty() {
307 assert!(
308 !state.runtime_enabled,
309 "{:?} should be disabled when all flags off",
310 state.support_id
311 );
312 }
313 }
314 }
315
316 #[test]
317 fn test_runtime_summary_with_all_enabled() {
318 let mut flags = FeatureFlags::default();
319 for feature in feature_flags::all_features() {
320 flags.enable(feature);
321 }
322 let summary = runtime_summary(&flags);
323 assert!(!summary.has_runtime_unavailable_features());
324 assert_eq!(summary.runtime_disabled_features, 0);
325 assert_eq!(
326 summary.runtime_enabled_features,
327 summary.mapped_support_features
328 );
329 }
330
331 #[test]
332 fn test_runtime_summary_all_support_rows_present() {
333 let flags = FeatureFlags::default();
334 let summary = runtime_summary(&flags);
335 assert!(summary.all_support_rows_present());
336 assert_eq!(summary.total_support_features, 7);
337 assert_eq!(summary.mapped_support_features, 7);
338 }
339
340 #[test]
341 fn test_from_support_sets_no_governance_rationale() {
342 let feature = support_matrix::find_feature_by_id(FeatureId::EditedPic).unwrap();
343 let state = FeatureGovernanceState::from_support(feature);
344 assert_eq!(state.rationale, "No runtime governance mapping requested.");
345 assert!(state.runtime_enabled);
346 assert!(state.required_feature_flags.is_empty());
347 assert!(state.missing_feature_flags.is_empty());
348 }
349
350 #[test]
351 fn test_governance_state_for_support_id_preserves_metadata() {
352 let flags = FeatureFlags::default();
353 let state = governance_state_for_support_id(FeatureId::SignSeparate, &flags).unwrap();
354 assert_eq!(state.support_id, FeatureId::SignSeparate);
355 assert!(!state.support_name.is_empty());
356 assert!(!state.support_description.is_empty());
357 assert!(state.doc_ref.is_some());
358 assert!(!state.rationale.is_empty());
359 }
360
361 #[test]
362 fn test_is_support_runtime_available_for_ungoverned_feature() {
363 let flags = FeatureFlags::default();
365 let state = governance_state_for_support_id(FeatureId::Level88Conditions, &flags).unwrap();
366 assert!(state.runtime_enabled);
367 assert!(state.required_feature_flags.is_empty());
368 }
369
370 #[test]
371 fn test_runtime_summary_disabled_count_matches_expectations() {
372 let flags = FeatureFlags::builder()
373 .disable(Feature::SignSeparate)
374 .disable(Feature::Comp1)
375 .disable(Feature::Comp2)
376 .build();
377 let summary = runtime_summary(&flags);
378 assert!(summary.runtime_disabled_features >= 3);
382 assert!(summary.has_runtime_unavailable_features());
383 }
384}