Skip to main content

omena_cascade/
first_witness.rs

1//! Canonical decision diagrams and the shared first-witness fold.
2//!
3//! This module deliberately imports no type from its host crate. The boolean
4//! terminal core is extended in place by the later cascade-winner terminal
5//! alphabet without coupling either plane to the surrounding cascade model.
6
7use std::cmp::Ordering;
8use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
9
10use omena_syntax::ident::AuthoredPropertyTextV0;
11use serde::Serialize;
12
13pub type NodeId = u32;
14
15pub const FALSE_NODE_ID_V0: NodeId = 0;
16pub const TRUE_NODE_ID_V0: NodeId = 1;
17pub const GUARDED_CASCADE_BOT_NODE_ID_V0: NodeId = FALSE_NODE_ID_V0;
18pub const DEFAULT_APPLY_CACHE_CAPACITY_V0: usize = 4_096;
19pub const DEFAULT_REBUILD_INTERVAL_OPERATIONS_V0: u64 = 8_192;
20pub const SITE_FIRST_APPEARANCE_ORDERING_DOMAIN_V0: &str = "siteFirstAppearance";
21pub const AT_RULE_NESTING_DFS_ORDERING_DOMAIN_V0: &str = "atRuleNestingDfs";
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum Node {
25    Term(u32),
26    Int { var: u16, lo: NodeId, hi: NodeId },
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum BooleanOperationV0 {
31    And,
32    Or,
33    Xor,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum VariableOrderDomainV0 {
38    SiteFirstAppearance,
39    AtRuleNestingDfs,
40}
41
42impl VariableOrderDomainV0 {
43    pub const fn name(self) -> &'static str {
44        match self {
45            Self::SiteFirstAppearance => SITE_FIRST_APPEARANCE_ORDERING_DOMAIN_V0,
46            Self::AtRuleNestingDfs => AT_RULE_NESTING_DFS_ORDERING_DOMAIN_V0,
47        }
48    }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
52#[serde(rename_all = "camelCase")]
53pub struct AtRuleNestingOrderAtomV0 {
54    atom: String,
55    at_rule_path: Vec<u32>,
56}
57
58impl AtRuleNestingOrderAtomV0 {
59    pub fn new(atom: impl Into<String>, at_rule_path: impl IntoIterator<Item = u32>) -> Self {
60        Self {
61            atom: atom.into(),
62            at_rule_path: at_rule_path.into_iter().collect(),
63        }
64    }
65
66    pub fn atom(&self) -> &str {
67        self.atom.as_str()
68    }
69
70    pub fn at_rule_path(&self) -> &[u32] {
71        self.at_rule_path.as_slice()
72    }
73}
74
75/// Derive stable DFS-forest paths for a source-ordered list of nested at-rule contexts.
76///
77/// Each context is a root-to-leaf sequence. Siblings receive their first-appearance
78/// ordinal under the same parent, so lexicographic path order is the forest's DFS order.
79pub fn at_rule_nesting_dfs_paths_v0(
80    contexts: &[Vec<String>],
81) -> Result<Vec<Vec<Vec<u32>>>, FirstWitnessErrorV0> {
82    let mut child_ordinals = BTreeMap::<Vec<String>, BTreeMap<String, u32>>::new();
83    contexts
84        .iter()
85        .map(|context| {
86            let mut prefix = Vec::<String>::new();
87            let mut path = Vec::<u32>::new();
88            context
89                .iter()
90                .map(|atom| {
91                    let siblings = child_ordinals.entry(prefix.clone()).or_default();
92                    let ordinal = if let Some(ordinal) = siblings.get(atom) {
93                        *ordinal
94                    } else {
95                        let ordinal = u32::try_from(siblings.len())
96                            .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
97                        siblings.insert(atom.clone(), ordinal);
98                        ordinal
99                    };
100                    prefix.push(atom.clone());
101                    path.push(ordinal);
102                    Ok(path.clone())
103                })
104                .collect()
105        })
106        .collect()
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
110#[serde(rename_all = "camelCase")]
111pub enum GuardedCascadeSpecificityExactnessV0 {
112    Exact,
113    Inexact,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
117#[serde(rename_all = "camelCase")]
118pub enum GuardedCascadeConditionKindV0 {
119    Media,
120    Supports,
121    Container,
122    StructuralPseudo,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
126#[serde(rename_all = "camelCase")]
127pub struct GuardedCascadeConditionAtomV0 {
128    atom: String,
129    kind: GuardedCascadeConditionKindV0,
130    at_rule_path: Vec<u32>,
131    numeric: bool,
132}
133
134impl GuardedCascadeConditionAtomV0 {
135    pub fn media(
136        atom: impl Into<String>,
137        at_rule_path: impl IntoIterator<Item = u32>,
138        numeric: bool,
139    ) -> Self {
140        Self {
141            atom: atom.into(),
142            kind: GuardedCascadeConditionKindV0::Media,
143            at_rule_path: at_rule_path.into_iter().collect(),
144            numeric,
145        }
146    }
147
148    pub fn supports(
149        atom: impl Into<String>,
150        at_rule_path: impl IntoIterator<Item = u32>,
151        numeric: bool,
152    ) -> Self {
153        Self {
154            atom: atom.into(),
155            kind: GuardedCascadeConditionKindV0::Supports,
156            at_rule_path: at_rule_path.into_iter().collect(),
157            numeric,
158        }
159    }
160
161    pub fn container(atom: impl Into<String>, at_rule_path: impl IntoIterator<Item = u32>) -> Self {
162        Self {
163            atom: atom.into(),
164            kind: GuardedCascadeConditionKindV0::Container,
165            at_rule_path: at_rule_path.into_iter().collect(),
166            numeric: false,
167        }
168    }
169
170    pub fn structural_pseudo(atom: impl Into<String>) -> Self {
171        Self {
172            atom: atom.into(),
173            kind: GuardedCascadeConditionKindV0::StructuralPseudo,
174            at_rule_path: Vec::new(),
175            numeric: false,
176        }
177    }
178
179    pub fn atom(&self) -> &str {
180        self.atom.as_str()
181    }
182
183    pub const fn kind(&self) -> GuardedCascadeConditionKindV0 {
184        self.kind
185    }
186
187    pub fn at_rule_path(&self) -> &[u32] {
188        self.at_rule_path.as_slice()
189    }
190
191    pub const fn is_numeric(&self) -> bool {
192        self.numeric
193    }
194}
195
196#[derive(Debug, Clone, Serialize)]
197#[serde(rename_all = "camelCase")]
198pub struct GuardedCascadeCandidateV0<K> {
199    declaration_id: u32,
200    element_signature: String,
201    property: AuthoredPropertyTextV0,
202    cascade_key: K,
203    specificity_exactness: GuardedCascadeSpecificityExactnessV0,
204    scope_proximity: u32,
205    conditions: Vec<GuardedCascadeConditionAtomV0>,
206}
207
208impl<K> GuardedCascadeCandidateV0<K> {
209    #[allow(clippy::too_many_arguments)]
210    pub fn new(
211        declaration_id: u32,
212        element_signature: impl Into<String>,
213        property: AuthoredPropertyTextV0,
214        cascade_key: K,
215        specificity_exactness: GuardedCascadeSpecificityExactnessV0,
216        scope_proximity: u32,
217        conditions: Vec<GuardedCascadeConditionAtomV0>,
218    ) -> Self {
219        Self {
220            declaration_id,
221            element_signature: element_signature.into(),
222            property,
223            cascade_key,
224            specificity_exactness,
225            scope_proximity,
226            conditions,
227        }
228    }
229
230    pub const fn declaration_id(&self) -> u32 {
231        self.declaration_id
232    }
233
234    pub fn element_signature(&self) -> &str {
235        self.element_signature.as_str()
236    }
237
238    pub const fn property(&self) -> &AuthoredPropertyTextV0 {
239        &self.property
240    }
241
242    pub const fn cascade_key(&self) -> &K {
243        &self.cascade_key
244    }
245
246    pub const fn specificity_exactness(&self) -> GuardedCascadeSpecificityExactnessV0 {
247        self.specificity_exactness
248    }
249
250    pub const fn scope_proximity(&self) -> u32 {
251        self.scope_proximity
252    }
253
254    pub fn conditions(&self) -> &[GuardedCascadeConditionAtomV0] {
255        self.conditions.as_slice()
256    }
257}
258
259impl<K: PartialEq> PartialEq for GuardedCascadeCandidateV0<K> {
260    fn eq(&self, other: &Self) -> bool {
261        self.declaration_id == other.declaration_id
262            && self.element_signature == other.element_signature
263            && self
264                .property
265                .to_property_name()
266                .same_as(&other.property.to_property_name())
267            && self.cascade_key == other.cascade_key
268            && self.specificity_exactness == other.specificity_exactness
269            && self.scope_proximity == other.scope_proximity
270            && self.conditions == other.conditions
271    }
272}
273
274impl<K: Eq> Eq for GuardedCascadeCandidateV0<K> {}
275
276#[derive(Debug, Clone, Serialize)]
277#[serde(
278    tag = "reason",
279    rename_all = "camelCase",
280    rename_all_fields = "camelCase"
281)]
282pub enum GuardedCascadeFragmentRefusalV0 {
283    EmptyCandidateSet,
284    InexactSpecificity {
285        declaration_id: u32,
286    },
287    ScopeProximityPresent {
288        declaration_id: u32,
289        scope_proximity: u32,
290    },
291    ContainerCondition {
292        declaration_id: u32,
293        atom: String,
294    },
295    StructuralPseudoCondition {
296        declaration_id: u32,
297        atom: String,
298    },
299    NumericConditionOutsideAlphabet {
300        declaration_id: u32,
301        atom: String,
302    },
303    ConditionOutsideDeclaredAlphabet {
304        declaration_id: u32,
305        atom: String,
306    },
307    MultipleProperties {
308        expected: AuthoredPropertyTextV0,
309        observed: AuthoredPropertyTextV0,
310    },
311    MultipleElementSignatures {
312        expected: String,
313        observed: String,
314    },
315    DuplicateDeclarationId {
316        declaration_id: u32,
317    },
318    NonUniqueCascadeKey {
319        first_declaration_id: u32,
320        second_declaration_id: u32,
321    },
322    ConditionAlphabetCapacityExceeded,
323}
324
325impl PartialEq for GuardedCascadeFragmentRefusalV0 {
326    fn eq(&self, other: &Self) -> bool {
327        use GuardedCascadeFragmentRefusalV0 as Refusal;
328        match (self, other) {
329            (Refusal::EmptyCandidateSet, Refusal::EmptyCandidateSet)
330            | (
331                Refusal::ConditionAlphabetCapacityExceeded,
332                Refusal::ConditionAlphabetCapacityExceeded,
333            ) => true,
334            (
335                Refusal::InexactSpecificity {
336                    declaration_id: left,
337                },
338                Refusal::InexactSpecificity {
339                    declaration_id: right,
340                },
341            )
342            | (
343                Refusal::DuplicateDeclarationId {
344                    declaration_id: left,
345                },
346                Refusal::DuplicateDeclarationId {
347                    declaration_id: right,
348                },
349            ) => left == right,
350            (
351                Refusal::ScopeProximityPresent {
352                    declaration_id: left_id,
353                    scope_proximity: left_scope,
354                },
355                Refusal::ScopeProximityPresent {
356                    declaration_id: right_id,
357                    scope_proximity: right_scope,
358                },
359            ) => left_id == right_id && left_scope == right_scope,
360            (
361                Refusal::ContainerCondition {
362                    declaration_id: left_id,
363                    atom: left_atom,
364                },
365                Refusal::ContainerCondition {
366                    declaration_id: right_id,
367                    atom: right_atom,
368                },
369            )
370            | (
371                Refusal::StructuralPseudoCondition {
372                    declaration_id: left_id,
373                    atom: left_atom,
374                },
375                Refusal::StructuralPseudoCondition {
376                    declaration_id: right_id,
377                    atom: right_atom,
378                },
379            )
380            | (
381                Refusal::NumericConditionOutsideAlphabet {
382                    declaration_id: left_id,
383                    atom: left_atom,
384                },
385                Refusal::NumericConditionOutsideAlphabet {
386                    declaration_id: right_id,
387                    atom: right_atom,
388                },
389            )
390            | (
391                Refusal::ConditionOutsideDeclaredAlphabet {
392                    declaration_id: left_id,
393                    atom: left_atom,
394                },
395                Refusal::ConditionOutsideDeclaredAlphabet {
396                    declaration_id: right_id,
397                    atom: right_atom,
398                },
399            ) => left_id == right_id && left_atom == right_atom,
400            (
401                Refusal::MultipleProperties {
402                    expected: left_expected,
403                    observed: left_observed,
404                },
405                Refusal::MultipleProperties {
406                    expected: right_expected,
407                    observed: right_observed,
408                },
409            ) => {
410                left_expected
411                    .to_property_name()
412                    .same_as(&right_expected.to_property_name())
413                    && left_observed
414                        .to_property_name()
415                        .same_as(&right_observed.to_property_name())
416            }
417            (
418                Refusal::MultipleElementSignatures {
419                    expected: left_expected,
420                    observed: left_observed,
421                },
422                Refusal::MultipleElementSignatures {
423                    expected: right_expected,
424                    observed: right_observed,
425                },
426            ) => left_expected == right_expected && left_observed == right_observed,
427            (
428                Refusal::NonUniqueCascadeKey {
429                    first_declaration_id: left_first,
430                    second_declaration_id: left_second,
431                },
432                Refusal::NonUniqueCascadeKey {
433                    first_declaration_id: right_first,
434                    second_declaration_id: right_second,
435                },
436            ) => left_first == right_first && left_second == right_second,
437            _ => false,
438        }
439    }
440}
441
442impl Eq for GuardedCascadeFragmentRefusalV0 {}
443
444impl GuardedCascadeFragmentRefusalV0 {
445    pub const fn name(&self) -> &'static str {
446        match self {
447            Self::EmptyCandidateSet => "emptyCandidateSet",
448            Self::InexactSpecificity { .. } => "inexactSpecificity",
449            Self::ScopeProximityPresent { .. } => "scopeProximityPresent",
450            Self::ContainerCondition { .. } => "containerCondition",
451            Self::StructuralPseudoCondition { .. } => "structuralPseudoCondition",
452            Self::NumericConditionOutsideAlphabet { .. } => "numericConditionOutsideAlphabet",
453            Self::ConditionOutsideDeclaredAlphabet { .. } => "conditionOutsideDeclaredAlphabet",
454            Self::MultipleProperties { .. } => "multipleProperties",
455            Self::MultipleElementSignatures { .. } => "multipleElementSignatures",
456            Self::DuplicateDeclarationId { .. } => "duplicateDeclarationId",
457            Self::NonUniqueCascadeKey { .. } => "nonUniqueCascadeKey",
458            Self::ConditionAlphabetCapacityExceeded => "conditionAlphabetCapacityExceeded",
459        }
460    }
461}
462
463impl std::fmt::Display for GuardedCascadeFragmentRefusalV0 {
464    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
465        write!(
466            formatter,
467            "guarded cascade fragment refused: {}",
468            self.name()
469        )
470    }
471}
472
473impl std::error::Error for GuardedCascadeFragmentRefusalV0 {}
474
475#[derive(Debug, Clone, Serialize)]
476#[serde(rename_all = "camelCase")]
477pub struct GuardedCascadeFragmentV0<K> {
478    element_signature: String,
479    property: AuthoredPropertyTextV0,
480    condition_alphabet: Vec<String>,
481    candidates: Vec<GuardedCascadeCandidateV0<K>>,
482}
483
484impl<K: Clone + Ord> GuardedCascadeFragmentV0<K> {
485    pub fn admit(
486        condition_alphabet: impl IntoIterator<Item = impl Into<String>>,
487        candidates: impl IntoIterator<Item = GuardedCascadeCandidateV0<K>>,
488    ) -> Result<Self, GuardedCascadeFragmentRefusalV0> {
489        let alphabet = condition_alphabet
490            .into_iter()
491            .map(Into::into)
492            .collect::<BTreeSet<String>>();
493        if alphabet.len() > usize::from(u16::MAX) + 1 {
494            return Err(GuardedCascadeFragmentRefusalV0::ConditionAlphabetCapacityExceeded);
495        }
496        let mut candidates = candidates.into_iter().collect::<Vec<_>>();
497        let Some(first) = candidates.first() else {
498            return Err(GuardedCascadeFragmentRefusalV0::EmptyCandidateSet);
499        };
500        let element_signature = first.element_signature.clone();
501        let property = first.property.clone();
502        let mut declaration_ids = BTreeSet::new();
503        let mut cascade_keys = BTreeMap::<K, u32>::new();
504        for candidate in &candidates {
505            if candidate.element_signature != element_signature {
506                return Err(GuardedCascadeFragmentRefusalV0::MultipleElementSignatures {
507                    expected: element_signature,
508                    observed: candidate.element_signature.clone(),
509                });
510            }
511            if !candidate
512                .property
513                .to_property_name()
514                .same_as(&property.to_property_name())
515            {
516                return Err(GuardedCascadeFragmentRefusalV0::MultipleProperties {
517                    expected: property,
518                    observed: candidate.property.clone(),
519                });
520            }
521            if candidate.specificity_exactness != GuardedCascadeSpecificityExactnessV0::Exact {
522                return Err(GuardedCascadeFragmentRefusalV0::InexactSpecificity {
523                    declaration_id: candidate.declaration_id,
524                });
525            }
526            if candidate.scope_proximity != 0 {
527                return Err(GuardedCascadeFragmentRefusalV0::ScopeProximityPresent {
528                    declaration_id: candidate.declaration_id,
529                    scope_proximity: candidate.scope_proximity,
530                });
531            }
532            if !declaration_ids.insert(candidate.declaration_id) {
533                return Err(GuardedCascadeFragmentRefusalV0::DuplicateDeclarationId {
534                    declaration_id: candidate.declaration_id,
535                });
536            }
537            if let Some(first_declaration_id) =
538                cascade_keys.insert(candidate.cascade_key.clone(), candidate.declaration_id)
539            {
540                return Err(GuardedCascadeFragmentRefusalV0::NonUniqueCascadeKey {
541                    first_declaration_id,
542                    second_declaration_id: candidate.declaration_id,
543                });
544            }
545            for condition in &candidate.conditions {
546                match condition.kind {
547                    GuardedCascadeConditionKindV0::Container => {
548                        return Err(GuardedCascadeFragmentRefusalV0::ContainerCondition {
549                            declaration_id: candidate.declaration_id,
550                            atom: condition.atom.clone(),
551                        });
552                    }
553                    GuardedCascadeConditionKindV0::StructuralPseudo => {
554                        return Err(GuardedCascadeFragmentRefusalV0::StructuralPseudoCondition {
555                            declaration_id: candidate.declaration_id,
556                            atom: condition.atom.clone(),
557                        });
558                    }
559                    GuardedCascadeConditionKindV0::Media
560                    | GuardedCascadeConditionKindV0::Supports => {}
561                }
562                if !alphabet.contains(condition.atom.as_str()) {
563                    return Err(if condition.numeric {
564                        GuardedCascadeFragmentRefusalV0::NumericConditionOutsideAlphabet {
565                            declaration_id: candidate.declaration_id,
566                            atom: condition.atom.clone(),
567                        }
568                    } else {
569                        GuardedCascadeFragmentRefusalV0::ConditionOutsideDeclaredAlphabet {
570                            declaration_id: candidate.declaration_id,
571                            atom: condition.atom.clone(),
572                        }
573                    });
574                }
575            }
576        }
577        candidates.sort_by(|left, right| right.cascade_key.cmp(&left.cascade_key));
578        Ok(Self {
579            element_signature,
580            property,
581            condition_alphabet: alphabet.into_iter().collect(),
582            candidates,
583        })
584    }
585
586    pub fn element_signature(&self) -> &str {
587        self.element_signature.as_str()
588    }
589
590    pub const fn property(&self) -> &AuthoredPropertyTextV0 {
591        &self.property
592    }
593
594    pub fn condition_alphabet(&self) -> &[String] {
595        self.condition_alphabet.as_slice()
596    }
597
598    pub fn candidates(&self) -> &[GuardedCascadeCandidateV0<K>] {
599        self.candidates.as_slice()
600    }
601}
602
603impl<K: PartialEq> PartialEq for GuardedCascadeFragmentV0<K> {
604    fn eq(&self, other: &Self) -> bool {
605        self.element_signature == other.element_signature
606            && self
607                .property
608                .to_property_name()
609                .same_as(&other.property.to_property_name())
610            && self.condition_alphabet == other.condition_alphabet
611            && self.candidates == other.candidates
612    }
613}
614
615impl<K: Eq> Eq for GuardedCascadeFragmentV0<K> {}
616
617pub fn at_rule_nesting_order_for_fragment_v0<K>(
618    fragment: &GuardedCascadeFragmentV0<K>,
619) -> Result<VariableOrderRegistrationV0, FirstWitnessErrorV0> {
620    #[cfg(test)]
621    if std::env::var_os("OMENA_G122_INJECT_REMOVE_AT_RULE_ORDER").is_some() {
622        return VariableOrderRegistrationV0::site_first_appearance(
623            fragment.condition_alphabet.iter().cloned(),
624        );
625    }
626    VariableOrderRegistrationV0::at_rule_nesting_dfs(
627        fragment
628            .candidates
629            .iter()
630            .flat_map(|candidate| candidate.conditions.iter())
631            .map(|condition| {
632                AtRuleNestingOrderAtomV0::new(
633                    condition.atom.clone(),
634                    condition.at_rule_path.iter().copied(),
635                )
636            }),
637    )
638}
639
640#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
641#[serde(transparent)]
642pub struct GuardedCascadeWinnerRootV0(NodeId);
643
644impl GuardedCascadeWinnerRootV0 {
645    pub const fn node_id(self) -> NodeId {
646        self.0
647    }
648}
649
650/// The exact fragment predicate attached to an MTBDD-owned answer.
651#[non_exhaustive]
652#[derive(Debug, Clone, Serialize)]
653#[serde(rename_all = "camelCase")]
654pub struct GuardedCascadeFragmentPredicateV0 {
655    pub element_signature: String,
656    pub property: AuthoredPropertyTextV0,
657    pub condition_alphabet: Vec<String>,
658}
659
660impl PartialEq for GuardedCascadeFragmentPredicateV0 {
661    fn eq(&self, other: &Self) -> bool {
662        self.element_signature == other.element_signature
663            && self
664                .property
665                .to_property_name()
666                .same_as(&other.property.to_property_name())
667            && self.condition_alphabet == other.condition_alphabet
668    }
669}
670
671impl Eq for GuardedCascadeFragmentPredicateV0 {}
672
673impl<K> GuardedCascadeFragmentV0<K> {
674    pub fn predicate(&self) -> GuardedCascadeFragmentPredicateV0 {
675        GuardedCascadeFragmentPredicateV0 {
676            element_signature: self.element_signature.clone(),
677            property: self.property.clone(),
678            condition_alphabet: self.condition_alphabet.clone(),
679        }
680    }
681}
682
683/// Which authority answered one guarded-winner question.
684#[non_exhaustive]
685#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
686#[serde(
687    tag = "kind",
688    rename_all = "camelCase",
689    rename_all_fields = "camelCase"
690)]
691pub enum GuardedCascadeWinnerAuthorityRuleV0 {
692    ScenarioSweepOutsideFragment,
693    CanonicalMtbddInsideFragment {
694        fragment: GuardedCascadeFragmentPredicateV0,
695    },
696}
697
698/// A canonical answer produced inside the declared guarded fragment.
699#[non_exhaustive]
700#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
701#[serde(rename_all = "camelCase")]
702pub struct GuardedCascadeWinnerAuthorityV0 {
703    pub rule: GuardedCascadeWinnerAuthorityRuleV0,
704    pub root: GuardedCascadeWinnerRootV0,
705    pub winner_defined_for_all_assignments: bool,
706}
707
708/// Why canonical winner-root equality cannot discharge an obligation.
709#[non_exhaustive]
710#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
711#[serde(
712    tag = "reason",
713    rename_all = "camelCase",
714    rename_all_fields = "camelCase"
715)]
716pub enum GuardedCascadeWinnerFunctionEqualityRefusalV0 {
717    CanonicalRootsDiffer {
718        input_root: GuardedCascadeWinnerRootV0,
719        output_root: GuardedCascadeWinnerRootV0,
720    },
721}
722
723/// Equality is actionable; inequality is a typed refusal because the free
724/// boolean alphabet contains assignments that no browser can realise.
725#[non_exhaustive]
726#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
727#[serde(
728    tag = "kind",
729    rename_all = "camelCase",
730    rename_all_fields = "camelCase"
731)]
732pub enum GuardedCascadeWinnerFunctionEqualityDecisionV0 {
733    Equal {
734        authority: GuardedCascadeWinnerAuthorityV0,
735    },
736    Refused {
737        rule: GuardedCascadeWinnerAuthorityRuleV0,
738        refusal: GuardedCascadeWinnerFunctionEqualityRefusalV0,
739    },
740}
741
742/// One plane's answer for a concrete assignment.
743#[non_exhaustive]
744#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
745#[serde(
746    tag = "kind",
747    rename_all = "camelCase",
748    rename_all_fields = "camelCase"
749)]
750pub enum GuardedCascadeWinnerPlaneAnswerV0 {
751    NoWinner,
752    Declaration { declaration_id: u32 },
753}
754
755/// An in-fragment disagreement is an integrity error, never a confidence tie.
756#[non_exhaustive]
757#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
758#[serde(
759    tag = "reason",
760    rename_all = "camelCase",
761    rename_all_fields = "camelCase"
762)]
763pub enum GuardedCascadeWinnerAuthorityErrorV0 {
764    InFragmentPlaneDisagreement {
765        canonical_mtbdd: GuardedCascadeWinnerPlaneAnswerV0,
766        scenario_sweep: GuardedCascadeWinnerPlaneAnswerV0,
767    },
768}
769
770impl std::fmt::Display for GuardedCascadeWinnerAuthorityErrorV0 {
771    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
772        match self {
773            Self::InFragmentPlaneDisagreement {
774                canonical_mtbdd,
775                scenario_sweep,
776            } => write!(
777                formatter,
778                "in-fragment guarded winner disagreement: canonicalMtbdd={canonical_mtbdd:?}, scenarioSweep={scenario_sweep:?}"
779            ),
780        }
781    }
782}
783
784impl std::error::Error for GuardedCascadeWinnerAuthorityErrorV0 {}
785
786#[derive(Debug, Clone, PartialEq, Eq)]
787pub struct VariableOrderRegistrationV0 {
788    domain: VariableOrderDomainV0,
789    atoms: Vec<String>,
790    indices: BTreeMap<String, u16>,
791}
792
793impl VariableOrderRegistrationV0 {
794    pub fn site_first_appearance(
795        atoms: impl IntoIterator<Item = impl Into<String>>,
796    ) -> Result<Self, FirstWitnessErrorV0> {
797        let mut ordered = Vec::new();
798        let mut seen = BTreeSet::new();
799        for atom in atoms {
800            let atom = atom.into();
801            if seen.insert(atom.clone()) {
802                ordered.push(atom);
803            }
804        }
805        Self::from_ordered(VariableOrderDomainV0::SiteFirstAppearance, ordered)
806    }
807
808    pub fn at_rule_nesting_dfs(
809        atoms: impl IntoIterator<Item = AtRuleNestingOrderAtomV0>,
810    ) -> Result<Self, FirstWitnessErrorV0> {
811        let mut atoms = atoms.into_iter().collect::<Vec<_>>();
812        atoms.sort_by(|left, right| {
813            left.at_rule_path
814                .cmp(&right.at_rule_path)
815                .then_with(|| left.atom.cmp(&right.atom))
816        });
817        let mut seen = BTreeSet::new();
818        let ordered = atoms
819            .into_iter()
820            .filter_map(|atom| seen.insert(atom.atom.clone()).then_some(atom.atom))
821            .collect();
822        Self::from_ordered(VariableOrderDomainV0::AtRuleNestingDfs, ordered)
823    }
824
825    fn from_ordered(
826        domain: VariableOrderDomainV0,
827        ordered: Vec<String>,
828    ) -> Result<Self, FirstWitnessErrorV0> {
829        let indices = ordered
830            .iter()
831            .enumerate()
832            .map(|(index, atom)| {
833                u16::try_from(index)
834                    .map(|index| (atom.clone(), index))
835                    .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)
836            })
837            .collect::<Result<BTreeMap<_, _>, _>>()?;
838        Ok(Self {
839            domain,
840            atoms: ordered,
841            indices,
842        })
843    }
844
845    pub const fn domain(&self) -> VariableOrderDomainV0 {
846        self.domain
847    }
848
849    pub fn atoms(&self) -> &[String] {
850        &self.atoms
851    }
852
853    pub fn variable_index(&self, atom: &str) -> Option<u16> {
854        self.indices.get(atom).copied()
855    }
856}
857
858#[derive(Debug, Clone, Copy, PartialEq, Eq)]
859pub struct FirstWitnessManagerConfigV0 {
860    pub apply_cache_capacity: usize,
861    pub rebuild_interval_operations: u64,
862    pub shortcuts: bool,
863}
864
865impl Default for FirstWitnessManagerConfigV0 {
866    fn default() -> Self {
867        Self {
868            apply_cache_capacity: DEFAULT_APPLY_CACHE_CAPACITY_V0,
869            rebuild_interval_operations: DEFAULT_REBUILD_INTERVAL_OPERATIONS_V0,
870            shortcuts: true,
871        }
872    }
873}
874
875#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
876pub struct FirstWitnessOperationCountersV0 {
877    pub choose_invocations: u64,
878    pub apply_invocations: u64,
879    pub apply_cache_lookups: u64,
880    pub apply_cache_hits: u64,
881    pub rebuilds: u64,
882    pub rebuild_node_visits: u64,
883}
884
885#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
886pub struct FirstWitnessChoiceOperationCountersV0 {
887    pub recursive_invocations: u64,
888    pub apply_cache_lookups: u64,
889    pub apply_cache_hits: u64,
890}
891
892impl FirstWitnessOperationCountersV0 {
893    pub const fn recursive_operations(self) -> u64 {
894        self.choose_invocations + self.apply_invocations
895    }
896}
897
898#[derive(Debug, Clone, Copy, PartialEq, Eq)]
899pub struct FirstWitnessRebuildReportV0 {
900    pub operations_since_previous_rebuild: u64,
901    pub nodes_before: usize,
902    pub nodes_after: usize,
903    pub live_root_count: usize,
904    pub visited_node_count: usize,
905}
906
907#[derive(Debug, Clone, PartialEq, Eq)]
908pub enum FirstWitnessErrorV0 {
909    UnknownAtom(String),
910    InvalidNode(NodeId),
911    InvalidTerminal(u32),
912    VariableOrderViolation { parent: u16, child: u16 },
913    VariableCapacityExceeded,
914    DeclarationIdCapacityExceeded,
915    DeclarationTerminalRegistrationClosed,
916    UnregisteredDeclarationTerminal(u32),
917    MissingAssignment { variable: u16 },
918}
919
920impl std::fmt::Display for FirstWitnessErrorV0 {
921    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
922        match self {
923            Self::UnknownAtom(atom) => write!(formatter, "unregistered decision atom {atom}"),
924            Self::InvalidNode(node) => write!(formatter, "invalid decision node {node}"),
925            Self::InvalidTerminal(terminal) => {
926                write!(formatter, "invalid boolean terminal {terminal}")
927            }
928            Self::VariableOrderViolation { parent, child } => write!(
929                formatter,
930                "decision variable order violation: parent {parent}, child {child}"
931            ),
932            Self::VariableCapacityExceeded => {
933                formatter.write_str("decision variable or node capacity exceeded")
934            }
935            Self::DeclarationIdCapacityExceeded => {
936                formatter.write_str("declaration id cannot be represented by the terminal alphabet")
937            }
938            Self::DeclarationTerminalRegistrationClosed => formatter
939                .write_str("declaration terminals must be registered before internal nodes exist"),
940            Self::UnregisteredDeclarationTerminal(declaration_id) => write!(
941                formatter,
942                "declaration terminal {declaration_id} was not registered"
943            ),
944            Self::MissingAssignment { variable } => {
945                write!(
946                    formatter,
947                    "assignment does not cover decision variable {variable}"
948                )
949            }
950        }
951    }
952}
953
954impl std::error::Error for FirstWitnessErrorV0 {}
955
956#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
957enum ApplyOperationV0 {
958    Boolean(BooleanOperationV0),
959    FirstWitness(FirstWitnessTerminalBehaviorV0),
960}
961
962#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
963enum FirstWitnessTerminalBehaviorV0 {
964    LeftBiased,
965    #[cfg(test)]
966    RightBiased,
967    #[cfg(test)]
968    BrokenRecursion,
969}
970
971#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
972struct ApplyCacheKeyV0 {
973    operation: ApplyOperationV0,
974    left: NodeId,
975    right: NodeId,
976}
977
978#[derive(Debug, Clone)]
979pub struct FirstWitnessManagerV0 {
980    nodes: Vec<Node>,
981    terminal_by_value: HashMap<u32, NodeId>,
982    unique: HashMap<(u16, NodeId, NodeId), NodeId>,
983    apply_cache: HashMap<ApplyCacheKeyV0, NodeId>,
984    apply_cache_fifo: VecDeque<ApplyCacheKeyV0>,
985    order: VariableOrderRegistrationV0,
986    config: FirstWitnessManagerConfigV0,
987    counters: FirstWitnessOperationCountersV0,
988    choice_counters: FirstWitnessChoiceOperationCountersV0,
989    operations_at_previous_rebuild: u64,
990}
991
992impl FirstWitnessManagerV0 {
993    pub fn new(order: VariableOrderRegistrationV0, config: FirstWitnessManagerConfigV0) -> Self {
994        Self {
995            nodes: vec![Node::Term(0), Node::Term(1)],
996            terminal_by_value: HashMap::from([(0, FALSE_NODE_ID_V0), (1, TRUE_NODE_ID_V0)]),
997            unique: HashMap::new(),
998            apply_cache: HashMap::new(),
999            apply_cache_fifo: VecDeque::new(),
1000            order,
1001            config,
1002            counters: FirstWitnessOperationCountersV0::default(),
1003            choice_counters: FirstWitnessChoiceOperationCountersV0::default(),
1004            operations_at_previous_rebuild: 0,
1005        }
1006    }
1007
1008    pub fn order(&self) -> &VariableOrderRegistrationV0 {
1009        &self.order
1010    }
1011
1012    pub const fn config(&self) -> FirstWitnessManagerConfigV0 {
1013        self.config
1014    }
1015
1016    pub const fn counters(&self) -> FirstWitnessOperationCountersV0 {
1017        self.counters
1018    }
1019
1020    pub const fn first_witness_counters(&self) -> FirstWitnessChoiceOperationCountersV0 {
1021        self.choice_counters
1022    }
1023
1024    pub fn node(&self, node: NodeId) -> Option<Node> {
1025        self.nodes.get(node as usize).copied()
1026    }
1027
1028    pub fn node_count(&self) -> usize {
1029        self.nodes.len()
1030    }
1031
1032    pub fn unique_table_len(&self) -> usize {
1033        self.unique.len()
1034    }
1035
1036    pub fn apply_cache_len(&self) -> usize {
1037        self.apply_cache.len()
1038    }
1039
1040    pub fn reachable_winner_node_count(
1041        &self,
1042        root: GuardedCascadeWinnerRootV0,
1043    ) -> Result<usize, FirstWitnessErrorV0> {
1044        let mut seen = BTreeSet::new();
1045        let mut pending = vec![root.0];
1046        while let Some(node_id) = pending.pop() {
1047            if !seen.insert(node_id) {
1048                continue;
1049            }
1050            if let Node::Int { lo, hi, .. } = self.require_node(node_id)? {
1051                pending.extend([lo, hi]);
1052            }
1053        }
1054        Ok(seen.len())
1055    }
1056
1057    pub fn register_declaration_terminals(
1058        &mut self,
1059        declaration_ids: impl IntoIterator<Item = u32>,
1060    ) -> Result<(), FirstWitnessErrorV0> {
1061        let mut encoded = declaration_ids
1062            .into_iter()
1063            .map(|declaration_id| {
1064                declaration_id
1065                    .checked_add(1)
1066                    .map(|terminal| (terminal, declaration_id))
1067                    .ok_or(FirstWitnessErrorV0::DeclarationIdCapacityExceeded)
1068            })
1069            .collect::<Result<Vec<_>, _>>()?;
1070        encoded.sort_unstable();
1071        encoded.dedup_by_key(|(terminal, _)| *terminal);
1072        let has_missing = encoded
1073            .iter()
1074            .any(|(terminal, _)| !self.terminal_by_value.contains_key(terminal));
1075        if has_missing
1076            && self
1077                .nodes
1078                .iter()
1079                .any(|node| matches!(node, Node::Int { .. }))
1080        {
1081            return Err(FirstWitnessErrorV0::DeclarationTerminalRegistrationClosed);
1082        }
1083        for (terminal, _) in encoded {
1084            if self.terminal_by_value.contains_key(&terminal) {
1085                continue;
1086            }
1087            let node = u32::try_from(self.nodes.len())
1088                .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
1089            self.nodes.push(Node::Term(terminal));
1090            self.terminal_by_value.insert(terminal, node);
1091        }
1092        Ok(())
1093    }
1094
1095    pub fn declaration_terminal(&self, declaration_id: u32) -> Result<NodeId, FirstWitnessErrorV0> {
1096        let terminal = declaration_id
1097            .checked_add(1)
1098            .ok_or(FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?;
1099        self.terminal_by_value.get(&terminal).copied().ok_or(
1100            FirstWitnessErrorV0::UnregisteredDeclarationTerminal(declaration_id),
1101        )
1102    }
1103
1104    pub fn variable(&mut self, atom: &str) -> Result<NodeId, FirstWitnessErrorV0> {
1105        let variable = self
1106            .order
1107            .variable_index(atom)
1108            .ok_or_else(|| FirstWitnessErrorV0::UnknownAtom(atom.to_string()))?;
1109        self.choose(variable, FALSE_NODE_ID_V0, TRUE_NODE_ID_V0)
1110    }
1111
1112    pub fn choose(
1113        &mut self,
1114        variable: u16,
1115        low: NodeId,
1116        high: NodeId,
1117    ) -> Result<NodeId, FirstWitnessErrorV0> {
1118        self.counters.choose_invocations += 1;
1119        self.intern(variable, low, high)
1120    }
1121
1122    pub fn not(&mut self, value: NodeId) -> Result<NodeId, FirstWitnessErrorV0> {
1123        self.apply(BooleanOperationV0::Xor, value, TRUE_NODE_ID_V0)
1124    }
1125
1126    pub fn and(&mut self, left: NodeId, right: NodeId) -> Result<NodeId, FirstWitnessErrorV0> {
1127        self.apply(BooleanOperationV0::And, left, right)
1128    }
1129
1130    pub fn or(&mut self, left: NodeId, right: NodeId) -> Result<NodeId, FirstWitnessErrorV0> {
1131        self.apply(BooleanOperationV0::Or, left, right)
1132    }
1133
1134    pub fn xor(&mut self, left: NodeId, right: NodeId) -> Result<NodeId, FirstWitnessErrorV0> {
1135        self.apply(BooleanOperationV0::Xor, left, right)
1136    }
1137
1138    pub fn choose_first_witness(
1139        &mut self,
1140        left: NodeId,
1141        right: NodeId,
1142    ) -> Result<NodeId, FirstWitnessErrorV0> {
1143        self.require_node(left)?;
1144        self.require_node(right)?;
1145        self.choose_first_witness_recursive(left, right, FirstWitnessTerminalBehaviorV0::LeftBiased)
1146    }
1147
1148    #[cfg(test)]
1149    fn choose_first_witness_with_terminal_behavior_for_test(
1150        &mut self,
1151        left: NodeId,
1152        right: NodeId,
1153        behavior: FirstWitnessTerminalBehaviorV0,
1154    ) -> Result<NodeId, FirstWitnessErrorV0> {
1155        self.require_node(left)?;
1156        self.require_node(right)?;
1157        self.choose_first_witness_recursive(left, right, behavior)
1158    }
1159
1160    pub fn apply(
1161        &mut self,
1162        operation: BooleanOperationV0,
1163        left: NodeId,
1164        right: NodeId,
1165    ) -> Result<NodeId, FirstWitnessErrorV0> {
1166        self.require_node(left)?;
1167        self.require_node(right)?;
1168        self.apply_recursive(operation, left, right)
1169    }
1170
1171    pub fn is_tautology(&self, root: NodeId) -> bool {
1172        root == TRUE_NODE_ID_V0
1173    }
1174
1175    pub fn is_satisfiable(&self, root: NodeId) -> bool {
1176        root != FALSE_NODE_ID_V0
1177    }
1178
1179    pub fn reclaim_if_due(
1180        &mut self,
1181        live_roots: &mut [NodeId],
1182    ) -> Result<Option<FirstWitnessRebuildReportV0>, FirstWitnessErrorV0> {
1183        let operations = self
1184            .counters
1185            .recursive_operations()
1186            .saturating_add(self.choice_counters.recursive_invocations);
1187        let operations_since_previous_rebuild =
1188            operations.saturating_sub(self.operations_at_previous_rebuild);
1189        if self.config.rebuild_interval_operations == 0
1190            || operations_since_previous_rebuild < self.config.rebuild_interval_operations
1191        {
1192            return Ok(None);
1193        }
1194        for root in live_roots.iter().copied() {
1195            self.require_node(root)?;
1196        }
1197        let nodes_before = self.nodes.len();
1198        let mut rebuilt_nodes = self
1199            .nodes
1200            .iter()
1201            .copied()
1202            .take_while(|node| matches!(node, Node::Term(_)))
1203            .collect::<Vec<_>>();
1204        let rebuilt_terminal_by_value = rebuilt_nodes
1205            .iter()
1206            .enumerate()
1207            .filter_map(|(node, value)| match value {
1208                Node::Term(value) => u32::try_from(node).ok().map(|node| (*value, node)),
1209                Node::Int { .. } => None,
1210            })
1211            .collect::<HashMap<_, _>>();
1212        let mut rebuilt_unique = HashMap::new();
1213        let mut remapped = (0..rebuilt_nodes.len())
1214            .filter_map(|node| u32::try_from(node).ok().map(|node| (node, node)))
1215            .collect::<HashMap<_, _>>();
1216        let mut visited_node_count = 0usize;
1217        for root in live_roots.iter_mut() {
1218            *root = clone_live_node(
1219                *root,
1220                &self.nodes,
1221                &mut rebuilt_nodes,
1222                &mut rebuilt_unique,
1223                &mut remapped,
1224                &mut visited_node_count,
1225            )?;
1226        }
1227        self.nodes = rebuilt_nodes;
1228        self.terminal_by_value = rebuilt_terminal_by_value;
1229        self.unique = rebuilt_unique;
1230        self.apply_cache.clear();
1231        self.apply_cache_fifo.clear();
1232        self.counters.rebuilds += 1;
1233        self.counters.rebuild_node_visits += visited_node_count as u64;
1234        self.operations_at_previous_rebuild = operations;
1235        Ok(Some(FirstWitnessRebuildReportV0 {
1236            operations_since_previous_rebuild,
1237            nodes_before,
1238            nodes_after: self.nodes.len(),
1239            live_root_count: live_roots.len(),
1240            visited_node_count,
1241        }))
1242    }
1243
1244    fn apply_recursive(
1245        &mut self,
1246        operation: BooleanOperationV0,
1247        left: NodeId,
1248        right: NodeId,
1249    ) -> Result<NodeId, FirstWitnessErrorV0> {
1250        self.counters.apply_invocations += 1;
1251        if self.config.shortcuts
1252            && let Some(result) = boolean_shortcut(operation, left, right)
1253        {
1254            return Ok(result);
1255        }
1256        let left_node = self.require_node(left)?;
1257        let right_node = self.require_node(right)?;
1258        if let (Node::Term(left), Node::Term(right)) = (left_node, right_node) {
1259            return terminal_boolean_result(operation, left, right);
1260        }
1261        let key = canonical_apply_key(operation, left, right);
1262        self.counters.apply_cache_lookups += 1;
1263        if let Some(result) = self.apply_cache.get(&key).copied() {
1264            self.counters.apply_cache_hits += 1;
1265            return Ok(result);
1266        }
1267        let variable = top_variable(left_node, right_node);
1268        let (left_low, left_high) = cofactors(left, left_node, variable);
1269        let (right_low, right_high) = cofactors(right, right_node, variable);
1270        let low = self.apply_recursive(operation, left_low, right_low)?;
1271        let high = self.apply_recursive(operation, left_high, right_high)?;
1272        let result = self.choose(variable, low, high)?;
1273        self.cache_insert(key, result);
1274        Ok(result)
1275    }
1276
1277    fn choose_first_witness_recursive(
1278        &mut self,
1279        left: NodeId,
1280        right: NodeId,
1281        terminal_behavior: FirstWitnessTerminalBehaviorV0,
1282    ) -> Result<NodeId, FirstWitnessErrorV0> {
1283        self.choice_counters.recursive_invocations += 1;
1284        let left_node = self.require_node(left)?;
1285        let right_node = self.require_node(right)?;
1286        if self.config.shortcuts {
1287            if left == right {
1288                return Ok(left);
1289            }
1290            if matches!(left_node, Node::Term(terminal) if terminal != 0) {
1291                return Ok(left);
1292            }
1293            if right == GUARDED_CASCADE_BOT_NODE_ID_V0 {
1294                return Ok(left);
1295            }
1296        }
1297        if let (Node::Term(left_terminal), Node::Term(_)) = (left_node, right_node) {
1298            return Ok(match terminal_behavior {
1299                FirstWitnessTerminalBehaviorV0::LeftBiased => {
1300                    if left_terminal == 0 {
1301                        right
1302                    } else {
1303                        left
1304                    }
1305                }
1306                #[cfg(test)]
1307                FirstWitnessTerminalBehaviorV0::RightBiased => {
1308                    if right == GUARDED_CASCADE_BOT_NODE_ID_V0 {
1309                        left
1310                    } else {
1311                        right
1312                    }
1313                }
1314                #[cfg(test)]
1315                FirstWitnessTerminalBehaviorV0::BrokenRecursion => {
1316                    if left_terminal == 0
1317                        && matches!(right_node, Node::Term(terminal) if terminal > 1)
1318                    {
1319                        TRUE_NODE_ID_V0
1320                    } else {
1321                        GUARDED_CASCADE_BOT_NODE_ID_V0
1322                    }
1323                }
1324            });
1325        }
1326        let key = ApplyCacheKeyV0 {
1327            operation: ApplyOperationV0::FirstWitness(terminal_behavior),
1328            left,
1329            right,
1330        };
1331        self.choice_counters.apply_cache_lookups += 1;
1332        if let Some(result) = self.apply_cache.get(&key).copied() {
1333            self.choice_counters.apply_cache_hits += 1;
1334            return Ok(result);
1335        }
1336        let variable = top_variable(left_node, right_node);
1337        let (left_low, left_high) = cofactors(left, left_node, variable);
1338        let (right_low, right_high) = cofactors(right, right_node, variable);
1339        let low = self.choose_first_witness_recursive(left_low, right_low, terminal_behavior)?;
1340        let high = self.choose_first_witness_recursive(left_high, right_high, terminal_behavior)?;
1341        let result = self.choose(variable, low, high)?;
1342        self.cache_insert(key, result);
1343        Ok(result)
1344    }
1345
1346    fn intern(
1347        &mut self,
1348        variable: u16,
1349        low: NodeId,
1350        high: NodeId,
1351    ) -> Result<NodeId, FirstWitnessErrorV0> {
1352        let low_node = self.require_node(low)?;
1353        let high_node = self.require_node(high)?;
1354        for child in [low_node, high_node] {
1355            if let Node::Int { var: child, .. } = child
1356                && child <= variable
1357            {
1358                return Err(FirstWitnessErrorV0::VariableOrderViolation {
1359                    parent: variable,
1360                    child,
1361                });
1362            }
1363        }
1364        if low == high {
1365            return Ok(low);
1366        }
1367        if let Some(node) = self.unique.get(&(variable, low, high)).copied() {
1368            return Ok(node);
1369        }
1370        let node = u32::try_from(self.nodes.len())
1371            .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
1372        self.nodes.push(Node::Int {
1373            var: variable,
1374            lo: low,
1375            hi: high,
1376        });
1377        self.unique.insert((variable, low, high), node);
1378        Ok(node)
1379    }
1380
1381    #[cfg(test)]
1382    fn intern_without_collapse_for_test(
1383        &mut self,
1384        variable: u16,
1385        low: NodeId,
1386        high: NodeId,
1387    ) -> Result<NodeId, FirstWitnessErrorV0> {
1388        let low_node = self.require_node(low)?;
1389        let high_node = self.require_node(high)?;
1390        for child in [low_node, high_node] {
1391            if let Node::Int { var: child, .. } = child
1392                && child <= variable
1393            {
1394                return Err(FirstWitnessErrorV0::VariableOrderViolation {
1395                    parent: variable,
1396                    child,
1397                });
1398            }
1399        }
1400        if let Some(node) = self.unique.get(&(variable, low, high)).copied() {
1401            return Ok(node);
1402        }
1403        let node = u32::try_from(self.nodes.len())
1404            .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
1405        self.nodes.push(Node::Int {
1406            var: variable,
1407            lo: low,
1408            hi: high,
1409        });
1410        self.unique.insert((variable, low, high), node);
1411        Ok(node)
1412    }
1413
1414    fn require_node(&self, node: NodeId) -> Result<Node, FirstWitnessErrorV0> {
1415        self.node(node)
1416            .ok_or(FirstWitnessErrorV0::InvalidNode(node))
1417    }
1418
1419    fn cache_insert(&mut self, key: ApplyCacheKeyV0, value: NodeId) {
1420        if self.config.apply_cache_capacity == 0 || self.apply_cache.contains_key(&key) {
1421            return;
1422        }
1423        while self.apply_cache.len() >= self.config.apply_cache_capacity {
1424            let Some(evicted) = self.apply_cache_fifo.pop_front() else {
1425                break;
1426            };
1427            self.apply_cache.remove(&evicted);
1428        }
1429        self.apply_cache.insert(key, value);
1430        self.apply_cache_fifo.push_back(key);
1431    }
1432}
1433
1434#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1435pub struct IncrementalGuardedCascadeWinnerEditReportV0 {
1436    pub replaced_existing_key: bool,
1437    pub entry_count: usize,
1438    pub root: GuardedCascadeWinnerRootV0,
1439    pub aggregate_updates: u64,
1440}
1441
1442#[derive(Debug)]
1443struct IncrementalGuardedCascadeWinnerNodeV0<K> {
1444    key: K,
1445    guarded_root: NodeId,
1446    aggregate: NodeId,
1447    height: u16,
1448    size: usize,
1449    left: Option<Box<Self>>,
1450    right: Option<Box<Self>>,
1451}
1452
1453type IncrementalWinnerLinkV0<K> = Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>;
1454type IncrementalWinnerMutationV0<K> = (IncrementalWinnerLinkV0<K>, bool);
1455
1456impl<K> IncrementalGuardedCascadeWinnerNodeV0<K> {
1457    fn leaf(key: K, guarded_root: NodeId) -> Self {
1458        Self {
1459            key,
1460            guarded_root,
1461            aggregate: guarded_root,
1462            height: 1,
1463            size: 1,
1464            left: None,
1465            right: None,
1466        }
1467    }
1468}
1469
1470#[derive(Debug, Default)]
1471pub struct IncrementalGuardedCascadeWinnerV0<K> {
1472    root: Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1473    aggregate_updates: u64,
1474}
1475
1476impl<K: Ord> IncrementalGuardedCascadeWinnerV0<K> {
1477    pub const fn new() -> Self {
1478        Self {
1479            root: None,
1480            aggregate_updates: 0,
1481        }
1482    }
1483
1484    pub fn len(&self) -> usize {
1485        incremental_winner_size(&self.root)
1486    }
1487
1488    pub fn is_empty(&self) -> bool {
1489        self.root.is_none()
1490    }
1491
1492    pub fn root(&self) -> GuardedCascadeWinnerRootV0 {
1493        GuardedCascadeWinnerRootV0(incremental_winner_fold(&self.root))
1494    }
1495
1496    pub const fn aggregate_updates(&self) -> u64 {
1497        self.aggregate_updates
1498    }
1499
1500    pub fn insert(
1501        &mut self,
1502        manager: &mut FirstWitnessManagerV0,
1503        cascade_key: K,
1504        guarded_root: GuardedCascadeWinnerRootV0,
1505    ) -> Result<IncrementalGuardedCascadeWinnerEditReportV0, FirstWitnessErrorV0> {
1506        manager.require_node(guarded_root.0)?;
1507        let (root, replaced_existing_key) = incremental_winner_insert(
1508            self.root.take(),
1509            cascade_key,
1510            guarded_root.0,
1511            manager,
1512            &mut self.aggregate_updates,
1513        )?;
1514        self.root = root;
1515        Ok(self.edit_report(replaced_existing_key))
1516    }
1517
1518    pub fn remove(
1519        &mut self,
1520        manager: &mut FirstWitnessManagerV0,
1521        cascade_key: &K,
1522    ) -> Result<IncrementalGuardedCascadeWinnerEditReportV0, FirstWitnessErrorV0> {
1523        let (root, removed) = incremental_winner_remove(
1524            self.root.take(),
1525            cascade_key,
1526            manager,
1527            &mut self.aggregate_updates,
1528        )?;
1529        self.root = root;
1530        Ok(self.edit_report(removed))
1531    }
1532
1533    pub fn reclaim_manager_if_due(
1534        &mut self,
1535        manager: &mut FirstWitnessManagerV0,
1536    ) -> Result<Option<FirstWitnessRebuildReportV0>, FirstWitnessErrorV0> {
1537        #[cfg(test)]
1538        if std::env::var_os("OMENA_G122_INJECT_DISABLE_WINNER_RECLAMATION").is_some() {
1539            return Ok(None);
1540        }
1541        let mut live_roots = Vec::with_capacity(self.len().saturating_mul(2));
1542        collect_incremental_winner_roots(&self.root, &mut live_roots);
1543        let report = manager.reclaim_if_due(&mut live_roots)?;
1544        if report.is_some() {
1545            let mut remapped = live_roots.into_iter();
1546            rewrite_incremental_winner_roots(&mut self.root, &mut remapped);
1547            debug_assert!(remapped.next().is_none());
1548        }
1549        Ok(report)
1550    }
1551
1552    fn edit_report(
1553        &self,
1554        replaced_existing_key: bool,
1555    ) -> IncrementalGuardedCascadeWinnerEditReportV0 {
1556        IncrementalGuardedCascadeWinnerEditReportV0 {
1557            replaced_existing_key,
1558            entry_count: self.len(),
1559            root: self.root(),
1560            aggregate_updates: self.aggregate_updates,
1561        }
1562    }
1563}
1564
1565fn incremental_winner_height<K>(
1566    node: &Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1567) -> u16 {
1568    node.as_ref().map_or(0, |node| node.height)
1569}
1570
1571fn incremental_winner_size<K>(
1572    node: &Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1573) -> usize {
1574    node.as_ref().map_or(0, |node| node.size)
1575}
1576
1577fn incremental_winner_fold<K>(
1578    node: &Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1579) -> NodeId {
1580    node.as_ref()
1581        .map_or(GUARDED_CASCADE_BOT_NODE_ID_V0, |node| node.aggregate)
1582}
1583
1584fn refresh_incremental_winner<K>(
1585    node: &mut IncrementalGuardedCascadeWinnerNodeV0<K>,
1586    manager: &mut FirstWitnessManagerV0,
1587    aggregate_updates: &mut u64,
1588) -> Result<(), FirstWitnessErrorV0> {
1589    node.height =
1590        1 + incremental_winner_height(&node.left).max(incremental_winner_height(&node.right));
1591    node.size = 1 + incremental_winner_size(&node.left) + incremental_winner_size(&node.right);
1592    #[cfg(test)]
1593    if std::env::var_os("OMENA_G122_INJECT_STALE_WINNER_AGGREGATE").is_some() {
1594        return Ok(());
1595    }
1596    let left_and_self =
1597        manager.choose_first_witness(incremental_winner_fold(&node.left), node.guarded_root)?;
1598    node.aggregate =
1599        manager.choose_first_witness(left_and_self, incremental_winner_fold(&node.right))?;
1600    *aggregate_updates += 2;
1601    Ok(())
1602}
1603
1604fn incremental_winner_balance_factor<K>(node: &IncrementalGuardedCascadeWinnerNodeV0<K>) -> i32 {
1605    i32::from(incremental_winner_height(&node.left))
1606        - i32::from(incremental_winner_height(&node.right))
1607}
1608
1609fn rotate_incremental_winner_left<K>(
1610    mut root: Box<IncrementalGuardedCascadeWinnerNodeV0<K>>,
1611    manager: &mut FirstWitnessManagerV0,
1612    aggregate_updates: &mut u64,
1613) -> Result<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>, FirstWitnessErrorV0> {
1614    let mut pivot = root
1615        .right
1616        .take()
1617        .ok_or(FirstWitnessErrorV0::InvalidNode(root.aggregate))?;
1618    root.right = pivot.left.take();
1619    refresh_incremental_winner(&mut root, manager, aggregate_updates)?;
1620    pivot.left = Some(root);
1621    refresh_incremental_winner(&mut pivot, manager, aggregate_updates)?;
1622    Ok(pivot)
1623}
1624
1625fn rotate_incremental_winner_right<K>(
1626    mut root: Box<IncrementalGuardedCascadeWinnerNodeV0<K>>,
1627    manager: &mut FirstWitnessManagerV0,
1628    aggregate_updates: &mut u64,
1629) -> Result<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>, FirstWitnessErrorV0> {
1630    let mut pivot = root
1631        .left
1632        .take()
1633        .ok_or(FirstWitnessErrorV0::InvalidNode(root.aggregate))?;
1634    root.left = pivot.right.take();
1635    refresh_incremental_winner(&mut root, manager, aggregate_updates)?;
1636    pivot.right = Some(root);
1637    refresh_incremental_winner(&mut pivot, manager, aggregate_updates)?;
1638    Ok(pivot)
1639}
1640
1641fn balance_incremental_winner<K>(
1642    mut node: Box<IncrementalGuardedCascadeWinnerNodeV0<K>>,
1643    manager: &mut FirstWitnessManagerV0,
1644    aggregate_updates: &mut u64,
1645) -> Result<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>, FirstWitnessErrorV0> {
1646    refresh_incremental_winner(&mut node, manager, aggregate_updates)?;
1647    let balance = incremental_winner_balance_factor(&node);
1648    if balance > 1 {
1649        let left_balance = node
1650            .left
1651            .as_deref()
1652            .map_or(0, incremental_winner_balance_factor);
1653        if left_balance < 0 {
1654            let left = node
1655                .left
1656                .take()
1657                .ok_or(FirstWitnessErrorV0::InvalidNode(node.aggregate))?;
1658            node.left = Some(rotate_incremental_winner_left(
1659                left,
1660                manager,
1661                aggregate_updates,
1662            )?);
1663        }
1664        return rotate_incremental_winner_right(node, manager, aggregate_updates);
1665    }
1666    if balance < -1 {
1667        let right_balance = node
1668            .right
1669            .as_deref()
1670            .map_or(0, incremental_winner_balance_factor);
1671        if right_balance > 0 {
1672            let right = node
1673                .right
1674                .take()
1675                .ok_or(FirstWitnessErrorV0::InvalidNode(node.aggregate))?;
1676            node.right = Some(rotate_incremental_winner_right(
1677                right,
1678                manager,
1679                aggregate_updates,
1680            )?);
1681        }
1682        return rotate_incremental_winner_left(node, manager, aggregate_updates);
1683    }
1684    Ok(node)
1685}
1686
1687fn incremental_winner_insert<K: Ord>(
1688    node: IncrementalWinnerLinkV0<K>,
1689    cascade_key: K,
1690    guarded_root: NodeId,
1691    manager: &mut FirstWitnessManagerV0,
1692    aggregate_updates: &mut u64,
1693) -> Result<IncrementalWinnerMutationV0<K>, FirstWitnessErrorV0> {
1694    let Some(mut node) = node else {
1695        return Ok((
1696            Some(Box::new(IncrementalGuardedCascadeWinnerNodeV0::leaf(
1697                cascade_key,
1698                guarded_root,
1699            ))),
1700            false,
1701        ));
1702    };
1703    let replaced = match cascade_key.cmp(&node.key) {
1704        Ordering::Greater => {
1705            let (left, replaced) = incremental_winner_insert(
1706                node.left.take(),
1707                cascade_key,
1708                guarded_root,
1709                manager,
1710                aggregate_updates,
1711            )?;
1712            node.left = left;
1713            replaced
1714        }
1715        Ordering::Less => {
1716            let (right, replaced) = incremental_winner_insert(
1717                node.right.take(),
1718                cascade_key,
1719                guarded_root,
1720                manager,
1721                aggregate_updates,
1722            )?;
1723            node.right = right;
1724            replaced
1725        }
1726        Ordering::Equal => {
1727            node.guarded_root = guarded_root;
1728            true
1729        }
1730    };
1731    Ok((
1732        Some(balance_incremental_winner(
1733            node,
1734            manager,
1735            aggregate_updates,
1736        )?),
1737        replaced,
1738    ))
1739}
1740
1741fn incremental_winner_remove<K: Ord>(
1742    node: IncrementalWinnerLinkV0<K>,
1743    cascade_key: &K,
1744    manager: &mut FirstWitnessManagerV0,
1745    aggregate_updates: &mut u64,
1746) -> Result<IncrementalWinnerMutationV0<K>, FirstWitnessErrorV0> {
1747    let Some(mut node) = node else {
1748        return Ok((None, false));
1749    };
1750    let removed = match cascade_key.cmp(&node.key) {
1751        Ordering::Greater => {
1752            let (left, removed) = incremental_winner_remove(
1753                node.left.take(),
1754                cascade_key,
1755                manager,
1756                aggregate_updates,
1757            )?;
1758            node.left = left;
1759            removed
1760        }
1761        Ordering::Less => {
1762            let (right, removed) = incremental_winner_remove(
1763                node.right.take(),
1764                cascade_key,
1765                manager,
1766                aggregate_updates,
1767            )?;
1768            node.right = right;
1769            removed
1770        }
1771        Ordering::Equal => {
1772            if node.left.is_none() {
1773                return Ok((node.right.take(), true));
1774            }
1775            if node.right.is_none() {
1776                return Ok((node.left.take(), true));
1777            }
1778            let right = node
1779                .right
1780                .take()
1781                .ok_or(FirstWitnessErrorV0::InvalidNode(node.aggregate))?;
1782            let (successor, right) =
1783                extract_incremental_winner_leftmost(right, manager, aggregate_updates)?;
1784            node.key = successor.key;
1785            node.guarded_root = successor.guarded_root;
1786            node.right = right;
1787            true
1788        }
1789    };
1790    if !removed {
1791        return Ok((Some(node), false));
1792    }
1793    Ok((
1794        Some(balance_incremental_winner(
1795            node,
1796            manager,
1797            aggregate_updates,
1798        )?),
1799        true,
1800    ))
1801}
1802
1803type IncrementalWinnerExtractV0<K> = (
1804    Box<IncrementalGuardedCascadeWinnerNodeV0<K>>,
1805    IncrementalWinnerLinkV0<K>,
1806);
1807
1808fn extract_incremental_winner_leftmost<K>(
1809    mut node: Box<IncrementalGuardedCascadeWinnerNodeV0<K>>,
1810    manager: &mut FirstWitnessManagerV0,
1811    aggregate_updates: &mut u64,
1812) -> Result<IncrementalWinnerExtractV0<K>, FirstWitnessErrorV0> {
1813    let Some(left) = node.left.take() else {
1814        let right = node.right.take();
1815        return Ok((node, right));
1816    };
1817    let (leftmost, left) = extract_incremental_winner_leftmost(left, manager, aggregate_updates)?;
1818    node.left = left;
1819    Ok((
1820        leftmost,
1821        Some(balance_incremental_winner(
1822            node,
1823            manager,
1824            aggregate_updates,
1825        )?),
1826    ))
1827}
1828
1829fn collect_incremental_winner_roots<K>(
1830    node: &Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1831    roots: &mut Vec<NodeId>,
1832) {
1833    if let Some(node) = node {
1834        roots.extend([node.guarded_root, node.aggregate]);
1835        collect_incremental_winner_roots(&node.left, roots);
1836        collect_incremental_winner_roots(&node.right, roots);
1837    }
1838}
1839
1840fn rewrite_incremental_winner_roots<K>(
1841    node: &mut Option<Box<IncrementalGuardedCascadeWinnerNodeV0<K>>>,
1842    roots: &mut impl Iterator<Item = NodeId>,
1843) {
1844    if let Some(node) = node {
1845        node.guarded_root = roots.next().unwrap_or(node.guarded_root);
1846        node.aggregate = roots.next().unwrap_or(node.aggregate);
1847        rewrite_incremental_winner_roots(&mut node.left, roots);
1848        rewrite_incremental_winner_roots(&mut node.right, roots);
1849    }
1850}
1851
1852pub fn merge_first_witness_by_key_v0<T: Clone, K: Ord>(
1853    left: &[T],
1854    right: &[T],
1855    key: impl Fn(&T) -> K,
1856) -> Vec<T> {
1857    let mut seen = BTreeSet::new();
1858    left.iter()
1859        .chain(right)
1860        .filter(|value| seen.insert(key(value)))
1861        .cloned()
1862        .collect()
1863}
1864
1865pub fn first_witness_fold_v0<T: Clone + Ord>(left: &[T], right: &[T]) -> Vec<T> {
1866    left.iter()
1867        .chain(right)
1868        .cloned()
1869        .collect::<BTreeSet<_>>()
1870        .into_iter()
1871        .collect()
1872}
1873
1874pub fn build_guarded_cascade_winner_v0<K: Clone + Ord>(
1875    manager: &mut FirstWitnessManagerV0,
1876    fragment: &GuardedCascadeFragmentV0<K>,
1877) -> Result<GuardedCascadeWinnerRootV0, FirstWitnessErrorV0> {
1878    manager.register_declaration_terminals(
1879        fragment
1880            .candidates
1881            .iter()
1882            .map(|candidate| candidate.declaration_id),
1883    )?;
1884    let mut winner = GUARDED_CASCADE_BOT_NODE_ID_V0;
1885    for candidate in &fragment.candidates {
1886        let mut guarded = manager.declaration_terminal(candidate.declaration_id)?;
1887        let mut variables = candidate
1888            .conditions
1889            .iter()
1890            .map(|condition| {
1891                manager
1892                    .order
1893                    .variable_index(condition.atom.as_str())
1894                    .ok_or_else(|| FirstWitnessErrorV0::UnknownAtom(condition.atom.clone()))
1895            })
1896            .collect::<Result<Vec<_>, _>>()?;
1897        variables.sort_unstable();
1898        variables.dedup();
1899        for variable in variables.into_iter().rev() {
1900            guarded = manager.choose(variable, GUARDED_CASCADE_BOT_NODE_ID_V0, guarded)?;
1901        }
1902        winner = manager.choose_first_witness(winner, guarded)?;
1903    }
1904    Ok(GuardedCascadeWinnerRootV0(winner))
1905}
1906
1907pub fn evaluate_guarded_cascade_winner_v0(
1908    manager: &FirstWitnessManagerV0,
1909    root: GuardedCascadeWinnerRootV0,
1910    assignment: &[bool],
1911) -> Result<Option<u32>, FirstWitnessErrorV0> {
1912    let mut current = root.0;
1913    loop {
1914        match manager.require_node(current)? {
1915            Node::Term(0) => return Ok(None),
1916            Node::Term(terminal) => return Ok(Some(terminal - 1)),
1917            Node::Int { var, lo, hi } => {
1918                let value = assignment
1919                    .get(usize::from(var))
1920                    .copied()
1921                    .ok_or(FirstWitnessErrorV0::MissingAssignment { variable: var })?;
1922                current = if value { hi } else { lo };
1923            }
1924        }
1925    }
1926}
1927
1928pub fn guarded_cascade_winner_is_total_v0(
1929    manager: &FirstWitnessManagerV0,
1930    root: GuardedCascadeWinnerRootV0,
1931) -> Result<bool, FirstWitnessErrorV0> {
1932    let mut seen = BTreeSet::new();
1933    let mut pending = vec![root.0];
1934    while let Some(node_id) = pending.pop() {
1935        if !seen.insert(node_id) {
1936            continue;
1937        }
1938        match manager.require_node(node_id)? {
1939            Node::Term(0) => return Ok(false),
1940            Node::Term(_) => {}
1941            Node::Int { lo, hi, .. } => pending.extend([lo, hi]),
1942        }
1943    }
1944    Ok(true)
1945}
1946
1947/// Compares roots interned by the same [`FirstWitnessManagerV0`].
1948///
1949/// Node IDs are manager-local. Callers must not compare roots produced by
1950/// independently owned managers without first rebuilding both functions in one manager.
1951pub fn compare_guarded_cascade_winner_functions_v0(
1952    fragment: GuardedCascadeFragmentPredicateV0,
1953    input_root: GuardedCascadeWinnerRootV0,
1954    output_root: GuardedCascadeWinnerRootV0,
1955    winner_defined_for_all_assignments: bool,
1956) -> GuardedCascadeWinnerFunctionEqualityDecisionV0 {
1957    let rule = GuardedCascadeWinnerAuthorityRuleV0::CanonicalMtbddInsideFragment { fragment };
1958    if same_canonical_winner_function_v0(input_root, output_root) {
1959        GuardedCascadeWinnerFunctionEqualityDecisionV0::Equal {
1960            authority: GuardedCascadeWinnerAuthorityV0 {
1961                rule,
1962                root: input_root,
1963                winner_defined_for_all_assignments,
1964            },
1965        }
1966    } else {
1967        GuardedCascadeWinnerFunctionEqualityDecisionV0::Refused {
1968            rule,
1969            refusal: GuardedCascadeWinnerFunctionEqualityRefusalV0::CanonicalRootsDiffer {
1970                input_root,
1971                output_root,
1972            },
1973        }
1974    }
1975}
1976
1977pub fn guarded_cascade_winner_authority_v0(
1978    fragment: GuardedCascadeFragmentPredicateV0,
1979    root: GuardedCascadeWinnerRootV0,
1980    winner_defined_for_all_assignments: bool,
1981) -> GuardedCascadeWinnerAuthorityV0 {
1982    GuardedCascadeWinnerAuthorityV0 {
1983        rule: GuardedCascadeWinnerAuthorityRuleV0::CanonicalMtbddInsideFragment { fragment },
1984        root,
1985        winner_defined_for_all_assignments,
1986    }
1987}
1988
1989pub fn reconcile_guarded_cascade_winner_planes_v0(
1990    authority: &GuardedCascadeWinnerAuthorityV0,
1991    canonical_mtbdd: GuardedCascadeWinnerPlaneAnswerV0,
1992    scenario_sweep: GuardedCascadeWinnerPlaneAnswerV0,
1993) -> Result<GuardedCascadeWinnerPlaneAnswerV0, GuardedCascadeWinnerAuthorityErrorV0> {
1994    #[cfg(test)]
1995    if std::env::var_os("OMENA_G122_INJECT_PREFER_SCENARIO_SWEEP").is_some() {
1996        return Ok(scenario_sweep);
1997    }
1998    match &authority.rule {
1999        GuardedCascadeWinnerAuthorityRuleV0::CanonicalMtbddInsideFragment { .. } => {
2000            if canonical_mtbdd == scenario_sweep {
2001                Ok(canonical_mtbdd)
2002            } else {
2003                Err(
2004                    GuardedCascadeWinnerAuthorityErrorV0::InFragmentPlaneDisagreement {
2005                        canonical_mtbdd,
2006                        scenario_sweep,
2007                    },
2008                )
2009            }
2010        }
2011        GuardedCascadeWinnerAuthorityRuleV0::ScenarioSweepOutsideFragment => Ok(scenario_sweep),
2012    }
2013}
2014
2015/// Returns root identity for two canonical functions owned by one manager.
2016/// Node IDs from different managers are not comparable through this helper.
2017pub const fn same_canonical_winner_function_v0(
2018    left: GuardedCascadeWinnerRootV0,
2019    right: GuardedCascadeWinnerRootV0,
2020) -> bool {
2021    left.0 == right.0
2022}
2023
2024fn boolean_shortcut(operation: BooleanOperationV0, left: NodeId, right: NodeId) -> Option<NodeId> {
2025    match operation {
2026        BooleanOperationV0::And => {
2027            if left == FALSE_NODE_ID_V0 || right == FALSE_NODE_ID_V0 {
2028                Some(FALSE_NODE_ID_V0)
2029            } else if left == TRUE_NODE_ID_V0 {
2030                Some(right)
2031            } else if right == TRUE_NODE_ID_V0 || left == right {
2032                Some(left)
2033            } else {
2034                None
2035            }
2036        }
2037        BooleanOperationV0::Or => {
2038            if left == TRUE_NODE_ID_V0 || right == TRUE_NODE_ID_V0 {
2039                Some(TRUE_NODE_ID_V0)
2040            } else if left == FALSE_NODE_ID_V0 {
2041                Some(right)
2042            } else if right == FALSE_NODE_ID_V0 || left == right {
2043                Some(left)
2044            } else {
2045                None
2046            }
2047        }
2048        BooleanOperationV0::Xor => {
2049            if left == right {
2050                Some(FALSE_NODE_ID_V0)
2051            } else if left == FALSE_NODE_ID_V0 {
2052                Some(right)
2053            } else if right == FALSE_NODE_ID_V0 {
2054                Some(left)
2055            } else {
2056                None
2057            }
2058        }
2059    }
2060}
2061
2062fn terminal_boolean_result(
2063    operation: BooleanOperationV0,
2064    left: u32,
2065    right: u32,
2066) -> Result<NodeId, FirstWitnessErrorV0> {
2067    if left > 1 {
2068        return Err(FirstWitnessErrorV0::InvalidTerminal(left));
2069    }
2070    if right > 1 {
2071        return Err(FirstWitnessErrorV0::InvalidTerminal(right));
2072    }
2073    let left = left == 1;
2074    let right = right == 1;
2075    Ok(match operation {
2076        BooleanOperationV0::And => left && right,
2077        BooleanOperationV0::Or => left || right,
2078        BooleanOperationV0::Xor => left ^ right,
2079    } as NodeId)
2080}
2081
2082fn canonical_apply_key(
2083    operation: BooleanOperationV0,
2084    left: NodeId,
2085    right: NodeId,
2086) -> ApplyCacheKeyV0 {
2087    let (left, right) = if left <= right {
2088        (left, right)
2089    } else {
2090        (right, left)
2091    };
2092    ApplyCacheKeyV0 {
2093        operation: ApplyOperationV0::Boolean(operation),
2094        left,
2095        right,
2096    }
2097}
2098
2099fn top_variable(left: Node, right: Node) -> u16 {
2100    match (left, right) {
2101        (Node::Int { var: left, .. }, Node::Int { var: right, .. }) => left.min(right),
2102        (Node::Int { var, .. }, Node::Term(_)) | (Node::Term(_), Node::Int { var, .. }) => var,
2103        (Node::Term(_), Node::Term(_)) => {
2104            unreachable!("terminal pairs are handled before recursion")
2105        }
2106    }
2107}
2108
2109fn cofactors(node_id: NodeId, node: Node, variable: u16) -> (NodeId, NodeId) {
2110    match node {
2111        Node::Int { var, lo, hi } if var == variable => (lo, hi),
2112        _ => (node_id, node_id),
2113    }
2114}
2115
2116fn clone_live_node(
2117    old: NodeId,
2118    old_nodes: &[Node],
2119    new_nodes: &mut Vec<Node>,
2120    new_unique: &mut HashMap<(u16, NodeId, NodeId), NodeId>,
2121    remapped: &mut HashMap<NodeId, NodeId>,
2122    visited: &mut usize,
2123) -> Result<NodeId, FirstWitnessErrorV0> {
2124    if let Some(mapped) = remapped.get(&old).copied() {
2125        return Ok(mapped);
2126    }
2127    *visited += 1;
2128    let node = old_nodes
2129        .get(old as usize)
2130        .copied()
2131        .ok_or(FirstWitnessErrorV0::InvalidNode(old))?;
2132    let (var, lo, hi) = match node {
2133        Node::Int { var, lo, hi } => (var, lo, hi),
2134        Node::Term(terminal) => return Err(FirstWitnessErrorV0::InvalidTerminal(terminal)),
2135    };
2136    let low = clone_live_node(lo, old_nodes, new_nodes, new_unique, remapped, visited)?;
2137    let high = clone_live_node(hi, old_nodes, new_nodes, new_unique, remapped, visited)?;
2138    let mapped = if low == high {
2139        low
2140    } else if let Some(node) = new_unique.get(&(var, low, high)).copied() {
2141        node
2142    } else {
2143        let node = u32::try_from(new_nodes.len())
2144            .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2145        new_nodes.push(Node::Int {
2146            var,
2147            lo: low,
2148            hi: high,
2149        });
2150        new_unique.insert((var, low, high), node);
2151        node
2152    };
2153    remapped.insert(old, mapped);
2154    Ok(mapped)
2155}
2156
2157#[cfg(test)]
2158mod tests {
2159    use std::time::Instant;
2160
2161    use super::*;
2162
2163    fn manager(shortcuts: bool) -> Result<FirstWitnessManagerV0, FirstWitnessErrorV0> {
2164        Ok(FirstWitnessManagerV0::new(
2165            VariableOrderRegistrationV0::site_first_appearance(["a", "b", "c"])?,
2166            FirstWitnessManagerConfigV0 {
2167                shortcuts,
2168                apply_cache_capacity: 32,
2169                rebuild_interval_operations: 64,
2170            },
2171        ))
2172    }
2173
2174    fn winner_manager(
2175        shortcuts: bool,
2176        variable_count: usize,
2177    ) -> Result<FirstWitnessManagerV0, FirstWitnessErrorV0> {
2178        let atoms = (0..variable_count)
2179            .map(|index| format!("guard-{index}"))
2180            .collect::<Vec<_>>();
2181        let mut manager = FirstWitnessManagerV0::new(
2182            VariableOrderRegistrationV0::site_first_appearance(atoms)?,
2183            FirstWitnessManagerConfigV0 {
2184                shortcuts,
2185                apply_cache_capacity: 16_384,
2186                rebuild_interval_operations: u64::MAX,
2187            },
2188        );
2189        manager.register_declaration_terminals([0, 1, 2])?;
2190        Ok(manager)
2191    }
2192
2193    fn streaming_winner_manager(
2194        variable_count: usize,
2195        declaration_count: usize,
2196        apply_cache_capacity: usize,
2197        rebuild_interval_operations: u64,
2198    ) -> Result<FirstWitnessManagerV0, FirstWitnessErrorV0> {
2199        let atoms = (0..variable_count)
2200            .map(|index| format!("guard-{index}"))
2201            .collect::<Vec<_>>();
2202        let mut manager = FirstWitnessManagerV0::new(
2203            VariableOrderRegistrationV0::site_first_appearance(atoms)?,
2204            FirstWitnessManagerConfigV0 {
2205                shortcuts: false,
2206                apply_cache_capacity,
2207                rebuild_interval_operations,
2208            },
2209        );
2210        manager.register_declaration_terminals(
2211            (0..declaration_count).filter_map(|id| u32::try_from(id).ok()),
2212        )?;
2213        Ok(manager)
2214    }
2215
2216    #[test]
2217    fn inside_fragment_plane_disagreement_names_both_answers() -> Result<(), String> {
2218        let authority = GuardedCascadeWinnerAuthorityV0 {
2219            rule: GuardedCascadeWinnerAuthorityRuleV0::CanonicalMtbddInsideFragment {
2220                fragment: GuardedCascadeFragmentPredicateV0 {
2221                    element_signature: ".a".to_string(),
2222                    property: AuthoredPropertyTextV0::new("color"),
2223                    condition_alphabet: vec!["@media (min-width: 1px)".to_string()],
2224                },
2225            },
2226            root: GuardedCascadeWinnerRootV0(2),
2227            winner_defined_for_all_assignments: true,
2228        };
2229        let error = reconcile_guarded_cascade_winner_planes_v0(
2230            &authority,
2231            GuardedCascadeWinnerPlaneAnswerV0::Declaration { declaration_id: 7 },
2232            GuardedCascadeWinnerPlaneAnswerV0::Declaration { declaration_id: 9 },
2233        )
2234        .err()
2235        .ok_or_else(|| "an in-fragment disagreement must be rejected".to_string())?;
2236        let message = error.to_string();
2237        assert!(message.contains("canonicalMtbdd=Declaration { declaration_id: 7 }"));
2238        assert!(message.contains("scenarioSweep=Declaration { declaration_id: 9 }"));
2239        Ok(())
2240    }
2241
2242    fn guarded_root_from_mask(
2243        manager: &mut FirstWitnessManagerV0,
2244        declaration_id: u32,
2245        mask: u64,
2246        variable_count: usize,
2247    ) -> Result<GuardedCascadeWinnerRootV0, FirstWitnessErrorV0> {
2248        let mut root = manager.declaration_terminal(declaration_id)?;
2249        for variable in (0..variable_count).rev() {
2250            if mask & (1 << variable) != 0 {
2251                root = manager.choose(
2252                    u16::try_from(variable)
2253                        .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?,
2254                    GUARDED_CASCADE_BOT_NODE_ID_V0,
2255                    root,
2256                )?;
2257            }
2258        }
2259        Ok(GuardedCascadeWinnerRootV0(root))
2260    }
2261
2262    fn guarded_root_from_typed_fragment_mask(
2263        manager: &mut FirstWitnessManagerV0,
2264        declaration_id: u32,
2265        mask: u64,
2266        variable_count: usize,
2267    ) -> Result<GuardedCascadeWinnerRootV0, Box<dyn std::error::Error>> {
2268        let conditions = (0..variable_count)
2269            .filter(|variable| mask & (1 << variable) != 0)
2270            .map(|variable| {
2271                Ok(GuardedCascadeConditionAtomV0::media(
2272                    format!("guard-{variable}"),
2273                    [u32::try_from(variable)?],
2274                    false,
2275                ))
2276            })
2277            .collect::<Result<Vec<_>, std::num::TryFromIntError>>()?;
2278        let alphabet = conditions
2279            .iter()
2280            .map(|condition| condition.atom().to_string())
2281            .collect::<Vec<_>>();
2282        let fragment = GuardedCascadeFragmentV0::admit(
2283            alphabet,
2284            [GuardedCascadeCandidateV0::new(
2285                declaration_id,
2286                ".typed-fragment",
2287                AuthoredPropertyTextV0::new("color"),
2288                declaration_id,
2289                GuardedCascadeSpecificityExactnessV0::Exact,
2290                0,
2291                conditions,
2292            )],
2293        )?;
2294        Ok(build_guarded_cascade_winner_v0(manager, &fragment)?)
2295    }
2296
2297    fn batch_winner_from_entries(
2298        manager: &mut FirstWitnessManagerV0,
2299        entries: &BTreeMap<u64, GuardedCascadeWinnerRootV0>,
2300    ) -> Result<GuardedCascadeWinnerRootV0, FirstWitnessErrorV0> {
2301        let mut root = GUARDED_CASCADE_BOT_NODE_ID_V0;
2302        for guarded in entries.values().rev() {
2303            root = manager.choose_first_witness(root, guarded.0)?;
2304        }
2305        Ok(GuardedCascadeWinnerRootV0(root))
2306    }
2307
2308    fn next_stream_seed(state: &mut u64) -> u64 {
2309        *state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
2310        let mut mixed = *state;
2311        mixed = (mixed ^ (mixed >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
2312        mixed = (mixed ^ (mixed >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
2313        mixed ^ (mixed >> 31)
2314    }
2315
2316    fn intern_terminal_table(
2317        manager: &mut FirstWitnessManagerV0,
2318        values: &[NodeId],
2319        variable: u16,
2320    ) -> Result<NodeId, FirstWitnessErrorV0> {
2321        if values.len() == 1 || values.iter().all(|value| *value == values[0]) {
2322            return Ok(values[0]);
2323        }
2324        let midpoint = values.len() / 2;
2325        let low = intern_terminal_table(manager, &values[..midpoint], variable + 1)?;
2326        let high = intern_terminal_table(manager, &values[midpoint..], variable + 1)?;
2327        manager.choose(variable, low, high)
2328    }
2329
2330    fn assignment_for_index(index: usize, variable_count: usize) -> Vec<bool> {
2331        (0..variable_count)
2332            .map(|variable| index & (1 << (variable_count - variable - 1)) != 0)
2333            .collect()
2334    }
2335
2336    fn winner_truth_table(
2337        manager: &FirstWitnessManagerV0,
2338        root: NodeId,
2339        variable_count: usize,
2340    ) -> Result<Vec<Option<u32>>, FirstWitnessErrorV0> {
2341        (0..(1 << variable_count))
2342            .map(|index| {
2343                evaluate_guarded_cascade_winner_v0(
2344                    manager,
2345                    GuardedCascadeWinnerRootV0(root),
2346                    &assignment_for_index(index, variable_count),
2347                )
2348            })
2349            .collect()
2350    }
2351
2352    fn next_law_seed(state: &mut u64) -> u64 {
2353        *state ^= *state << 13;
2354        *state ^= *state >> 7;
2355        *state ^= *state << 17;
2356        *state
2357    }
2358
2359    #[derive(Debug, Default)]
2360    struct FirstWitnessLawReportV0 {
2361        associativity_violations: usize,
2362        idempotence_violations: usize,
2363        absorption_violations: usize,
2364        left_identity_violations: usize,
2365        right_identity_violations: usize,
2366        result_roots: Vec<NodeId>,
2367    }
2368
2369    struct FirstWitnessLawRunV0 {
2370        report: FirstWitnessLawReportV0,
2371        counters: FirstWitnessChoiceOperationCountersV0,
2372        tables: Vec<Vec<Option<u32>>>,
2373    }
2374
2375    fn run_first_witness_laws(
2376        manager: &mut FirstWitnessManagerV0,
2377        operands: &[[NodeId; 3]],
2378    ) -> Result<FirstWitnessLawReportV0, FirstWitnessErrorV0> {
2379        let mut report = FirstWitnessLawReportV0::default();
2380        let behavior = if std::env::var_os("OMENA_G122_INJECT_FIRST_WITNESS_LAST_WINS").is_some() {
2381            FirstWitnessTerminalBehaviorV0::RightBiased
2382        } else {
2383            FirstWitnessTerminalBehaviorV0::LeftBiased
2384        };
2385        for [left, middle, right] in operands.iter().copied() {
2386            let mut choose = |left, right| {
2387                manager.choose_first_witness_with_terminal_behavior_for_test(left, right, behavior)
2388            };
2389            let left_middle = choose(left, middle)?;
2390            let middle_right = choose(middle, right)?;
2391            let associative_left = choose(left_middle, right)?;
2392            let associative_right = choose(left, middle_right)?;
2393            let idempotent = choose(left, left)?;
2394            let absorbed = choose(left_middle, left)?;
2395            let left_identity = choose(GUARDED_CASCADE_BOT_NODE_ID_V0, left)?;
2396            let right_identity = choose(left, GUARDED_CASCADE_BOT_NODE_ID_V0)?;
2397            report.associativity_violations += usize::from(associative_left != associative_right);
2398            report.idempotence_violations += usize::from(idempotent != left);
2399            report.absorption_violations += usize::from(absorbed != left_middle);
2400            report.left_identity_violations += usize::from(left_identity != left);
2401            report.right_identity_violations += usize::from(right_identity != left);
2402            report.result_roots.extend([
2403                associative_left,
2404                associative_right,
2405                idempotent,
2406                absorbed,
2407                left_identity,
2408                right_identity,
2409            ]);
2410        }
2411        Ok(report)
2412    }
2413
2414    fn seeded_winner_operands(
2415        manager: &mut FirstWitnessManagerV0,
2416        trial_count: usize,
2417        variable_count: usize,
2418    ) -> Result<Vec<[NodeId; 3]>, FirstWitnessErrorV0> {
2419        let terminals = [
2420            GUARDED_CASCADE_BOT_NODE_ID_V0,
2421            manager.declaration_terminal(0)?,
2422            manager.declaration_terminal(1)?,
2423            manager.declaration_terminal(2)?,
2424        ];
2425        let table_size = 1 << variable_count;
2426        let mut seed = 0x1220_cafe_dead_beef_u64;
2427        (0..trial_count)
2428            .map(|_| {
2429                let mut roots = [GUARDED_CASCADE_BOT_NODE_ID_V0; 3];
2430                for root in &mut roots {
2431                    let values = (0..table_size)
2432                        .map(|_| {
2433                            let terminal = next_law_seed(&mut seed) as usize % terminals.len();
2434                            terminals[terminal]
2435                        })
2436                        .collect::<Vec<_>>();
2437                    *root = intern_terminal_table(manager, &values, 0)?;
2438                }
2439                Ok(roots)
2440            })
2441            .collect()
2442    }
2443
2444    #[test]
2445    fn first_witness_laws_hold_in_both_modes_and_the_switch_is_live()
2446    -> Result<(), FirstWitnessErrorV0> {
2447        const TRIAL_COUNT: usize = 256;
2448        const VARIABLE_COUNT: usize = 3;
2449        fn run(shortcuts: bool) -> Result<FirstWitnessLawRunV0, FirstWitnessErrorV0> {
2450            let mut manager = winner_manager(shortcuts, VARIABLE_COUNT)?;
2451            let operands = seeded_winner_operands(&mut manager, TRIAL_COUNT, VARIABLE_COUNT)?;
2452            let report = run_first_witness_laws(&mut manager, &operands)?;
2453            let tables = report
2454                .result_roots
2455                .iter()
2456                .map(|root| winner_truth_table(&manager, *root, VARIABLE_COUNT))
2457                .collect::<Result<Vec<_>, _>>()?;
2458            Ok(FirstWitnessLawRunV0 {
2459                report,
2460                counters: manager.first_witness_counters(),
2461                tables,
2462            })
2463        }
2464
2465        let shortcut = run(true)?;
2466        let recursive = run(false)?;
2467        for report in [&shortcut.report, &recursive.report] {
2468            assert_eq!(report.associativity_violations, 0);
2469            assert_eq!(report.idempotence_violations, 0);
2470            assert_eq!(report.absorption_violations, 0);
2471            assert_eq!(report.left_identity_violations, 0);
2472            assert_eq!(report.right_identity_violations, 0);
2473        }
2474        assert_eq!(shortcut.tables, recursive.tables);
2475        assert_eq!(shortcut.report.result_roots, recursive.report.result_roots);
2476        assert!(
2477            recursive.counters.recursive_invocations > shortcut.counters.recursive_invocations,
2478            "disabling shortcuts must reach more recursive calls"
2479        );
2480        assert!(
2481            recursive.counters.apply_cache_lookups > shortcut.counters.apply_cache_lookups,
2482            "disabling shortcuts must reach more apply-cache probes"
2483        );
2484        eprintln!(
2485            "{{\"trialCount\":{TRIAL_COUNT},\"variableCount\":{VARIABLE_COUNT},\"violations\":0,\"shortcutsOn\":{{\"recursiveInvocations\":{},\"applyCacheLookups\":{}}},\"shortcutsOff\":{{\"recursiveInvocations\":{},\"applyCacheLookups\":{}}}}}",
2486            shortcut.counters.recursive_invocations,
2487            shortcut.counters.apply_cache_lookups,
2488            recursive.counters.recursive_invocations,
2489            recursive.counters.apply_cache_lookups,
2490        );
2491        Ok(())
2492    }
2493
2494    #[test]
2495    fn typed_fragment_operands_cover_the_first_witness_laws()
2496    -> Result<(), Box<dyn std::error::Error>> {
2497        const VARIABLE_COUNT: usize = 3;
2498        let mut manager = winner_manager(false, VARIABLE_COUNT)?;
2499        let operands = [[
2500            guarded_root_from_typed_fragment_mask(&mut manager, 0, 0b001, VARIABLE_COUNT)?.0,
2501            guarded_root_from_typed_fragment_mask(&mut manager, 1, 0b010, VARIABLE_COUNT)?.0,
2502            guarded_root_from_typed_fragment_mask(&mut manager, 2, 0b100, VARIABLE_COUNT)?.0,
2503        ]];
2504        let report = run_first_witness_laws(&mut manager, &operands)?;
2505        assert_eq!(report.associativity_violations, 0);
2506        assert_eq!(report.idempotence_violations, 0);
2507        assert_eq!(report.absorption_violations, 0);
2508        assert_eq!(report.left_identity_violations, 0);
2509        assert_eq!(report.right_identity_violations, 0);
2510        Ok(())
2511    }
2512
2513    #[test]
2514    fn first_witness_negative_controls_are_observed_by_the_product_recursion()
2515    -> Result<(), FirstWitnessErrorV0> {
2516        const VARIABLE_COUNT: usize = 1;
2517        let mut manager = winner_manager(false, VARIABLE_COUNT)?;
2518        let bot = GUARDED_CASCADE_BOT_NODE_ID_V0;
2519        let first = manager.declaration_terminal(0)?;
2520        let second = manager.declaration_terminal(1)?;
2521        let guarded_first = manager.choose(0, bot, first)?;
2522        let guarded_second = manager.choose(0, bot, second)?;
2523        let right_biased_pair = manager.choose_first_witness_with_terminal_behavior_for_test(
2524            guarded_first,
2525            guarded_second,
2526            FirstWitnessTerminalBehaviorV0::RightBiased,
2527        )?;
2528        let right_biased_absorbed = manager.choose_first_witness_with_terminal_behavior_for_test(
2529            right_biased_pair,
2530            guarded_first,
2531            FirstWitnessTerminalBehaviorV0::RightBiased,
2532        )?;
2533        assert_ne!(
2534            right_biased_pair, right_biased_absorbed,
2535            "last-wins must violate left-regular-band absorption"
2536        );
2537        let left_right = manager.choose_first_witness(guarded_first, guarded_second)?;
2538        let right_left = manager.choose_first_witness(guarded_second, guarded_first)?;
2539        assert_ne!(
2540            left_right, right_left,
2541            "the first-witness operation must expose a non-commutativity witness"
2542        );
2543        eprintln!(
2544            "{{\"lastWinsAbsorptionViolations\":1,\"nonCommutativityWitnesses\":1,\"rightBiasedPair\":{right_biased_pair},\"rightBiasedAbsorbed\":{right_biased_absorbed},\"leftRight\":{left_right},\"rightLeft\":{right_left}}}"
2545        );
2546        Ok(())
2547    }
2548
2549    #[test]
2550    fn exhaustive_first_applicable_oracle_and_canonicality_both_directions()
2551    -> Result<(), FirstWitnessErrorV0> {
2552        const VARIABLE_COUNT: usize = 12;
2553        let mut manager = winner_manager(false, VARIABLE_COUNT)?;
2554        let bot = GUARDED_CASCADE_BOT_NODE_ID_V0;
2555        let declarations = [
2556            manager.declaration_terminal(0)?,
2557            manager.declaration_terminal(1)?,
2558            manager.declaration_terminal(2)?,
2559        ];
2560        let table_size = 1 << VARIABLE_COUNT;
2561        let mut seed = 0xa2a3_1220_5eed_u64;
2562        let mut operand_tables = Vec::new();
2563        let mut operand_roots = Vec::new();
2564        for _ in 0..declarations.len() {
2565            let table = (0..table_size)
2566                .map(|_| {
2567                    if next_law_seed(&mut seed) & 1 == 0 {
2568                        bot
2569                    } else {
2570                        declarations[operand_tables.len()]
2571                    }
2572                })
2573                .collect::<Vec<_>>();
2574            operand_roots.push(intern_terminal_table(&mut manager, &table, 0)?);
2575            operand_tables.push(table);
2576        }
2577        let mut product_root = bot;
2578        for operand in &operand_roots {
2579            product_root = manager.choose_first_witness(product_root, *operand)?;
2580        }
2581        let oracle_table = (0..table_size)
2582            .map(|index| {
2583                operand_tables
2584                    .iter()
2585                    .find_map(|table| (table[index] != bot).then_some(table[index]))
2586                    .unwrap_or(bot)
2587            })
2588            .collect::<Vec<_>>();
2589        let independent_root = intern_terminal_table(&mut manager, &oracle_table, 0)?;
2590        assert_eq!(
2591            product_root, independent_root,
2592            "pointwise table interning and the product fold must canonicalize to one NodeId"
2593        );
2594        let product_table = (0..table_size)
2595            .map(|index| {
2596                evaluate_guarded_cascade_winner_v0(
2597                    &manager,
2598                    GuardedCascadeWinnerRootV0(product_root),
2599                    &assignment_for_index(index, VARIABLE_COUNT),
2600                )
2601            })
2602            .collect::<Result<Vec<_>, _>>()?;
2603        let oracle_declarations = oracle_table
2604            .iter()
2605            .map(|terminal| {
2606                if *terminal == bot {
2607                    Ok(None)
2608                } else {
2609                    match manager.require_node(*terminal)? {
2610                        Node::Term(value) => Ok(Some(value - 1)),
2611                        Node::Int { .. } => Err(FirstWitnessErrorV0::InvalidNode(*terminal)),
2612                    }
2613                }
2614            })
2615            .collect::<Result<Vec<_>, _>>()?;
2616        let mismatch_count = product_table
2617            .iter()
2618            .zip(&oracle_declarations)
2619            .filter(|(product, oracle)| product != oracle)
2620            .count();
2621        assert_eq!(mismatch_count, 0);
2622        let mut different_table = oracle_table.clone();
2623        different_table[0] = if different_table[0] == bot {
2624            declarations[0]
2625        } else {
2626            bot
2627        };
2628        let different_root = intern_terminal_table(&mut manager, &different_table, 0)?;
2629        assert_ne!(
2630            product_root, different_root,
2631            "different terminal functions must not share a NodeId"
2632        );
2633        let source = include_str!("first_witness.rs");
2634        let oracle_start = source
2635            .find("fn exhaustive_first_applicable_oracle_and_canonicality_both_directions()")
2636            .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
2637        let oracle_end = source[oracle_start..]
2638            .find("fn broken_recursion_masking_table_pins_each_law_cell()")
2639            .map(|offset| oracle_start + offset)
2640            .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
2641        let oracle_source = &source[oracle_start..oracle_end];
2642        for forbidden in [
2643            ["cascade", "_property("].concat(),
2644            ["rank_cascade", "_items("].concat(),
2645            ["select_open_world", "_cascade_winner("].concat(),
2646        ] {
2647            assert!(
2648                !oracle_source.contains(forbidden.as_str()),
2649                "the A2 oracle must remain independent of cascade ranking entry point {forbidden}"
2650            );
2651        }
2652        eprintln!(
2653            "{{\"variableCount\":{VARIABLE_COUNT},\"checkedPointCount\":{table_size},\"mismatchCount\":{mismatch_count},\"productNodeId\":{product_root},\"independentNodeId\":{independent_root},\"differentNodeId\":{different_root}}}"
2654        );
2655        Ok(())
2656    }
2657
2658    #[test]
2659    fn broken_recursion_masking_table_pins_each_law_cell() -> Result<(), FirstWitnessErrorV0> {
2660        fn cell(shortcuts: bool) -> Result<[bool; 4], FirstWitnessErrorV0> {
2661            let mut manager = winner_manager(shortcuts, 2)?;
2662            let behavior = FirstWitnessTerminalBehaviorV0::BrokenRecursion;
2663            let bot = GUARDED_CASCADE_BOT_NODE_ID_V0;
2664            let declaration = manager.declaration_terminal(1)?;
2665            let guarded = manager.choose(0, bot, declaration)?;
2666            let idempotence = manager
2667                .choose_first_witness_with_terminal_behavior_for_test(guarded, guarded, behavior)?
2668                == guarded;
2669            let bot_guarded = manager
2670                .choose_first_witness_with_terminal_behavior_for_test(bot, guarded, behavior)?;
2671            let bot_bot =
2672                manager.choose_first_witness_with_terminal_behavior_for_test(bot, bot, behavior)?;
2673            let associative_left = manager
2674                .choose_first_witness_with_terminal_behavior_for_test(bot_bot, guarded, behavior)?;
2675            let associative_right = manager.choose_first_witness_with_terminal_behavior_for_test(
2676                bot,
2677                bot_guarded,
2678                behavior,
2679            )?;
2680            let associativity = associative_left == associative_right;
2681            let absorbed = manager.choose_first_witness_with_terminal_behavior_for_test(
2682                bot_guarded,
2683                bot,
2684                behavior,
2685            )?;
2686            let absorption = absorbed == bot_guarded;
2687            let a2 = winner_truth_table(&manager, bot_guarded, 2)?
2688                == winner_truth_table(&manager, guarded, 2)?;
2689            Ok([associativity, idempotence, absorption, a2])
2690        }
2691
2692        let shortcuts_on = cell(true)?;
2693        let shortcuts_off = cell(false)?;
2694        assert_eq!(shortcuts_on, [false, true, true, false]);
2695        assert_eq!(shortcuts_off, [false, false, false, false]);
2696        eprintln!(
2697            "{{\"brokenRecursion\":true,\"shortcutsOn\":{{\"associativity\":false,\"idempotence\":true,\"absorption\":true,\"a2\":false}},\"shortcutsOff\":{{\"associativity\":false,\"idempotence\":false,\"absorption\":false,\"a2\":false}}}}"
2698        );
2699        Ok(())
2700    }
2701
2702    #[test]
2703    fn incremental_winner_matches_batch_and_pointwise_spec_after_every_streaming_edit()
2704    -> Result<(), Box<dyn std::error::Error>> {
2705        const SEED_COUNT: usize = 60;
2706        const EDIT_COUNT: usize = 200;
2707        const VARIABLE_COUNT: usize = 6;
2708        const DECLARATION_COUNT: usize = 512;
2709        let mut checked_trials = 0usize;
2710        let mut checked_points = 0usize;
2711        for seed_index in 0..SEED_COUNT {
2712            let mut manager =
2713                streaming_winner_manager(VARIABLE_COUNT, DECLARATION_COUNT, 16_384, u64::MAX)?;
2714            let guarded = (0..DECLARATION_COUNT)
2715                .map(|index| {
2716                    let mask = 1_u64 << (index % VARIABLE_COUNT)
2717                        | 1_u64 << ((index * 5 + 1) % VARIABLE_COUNT);
2718                    let declaration_id = u32::try_from(index)
2719                        .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?;
2720                    if index == 0 {
2721                        guarded_root_from_typed_fragment_mask(
2722                            &mut manager,
2723                            declaration_id,
2724                            mask,
2725                            VARIABLE_COUNT,
2726                        )
2727                    } else {
2728                        Ok(guarded_root_from_mask(
2729                            &mut manager,
2730                            declaration_id,
2731                            mask,
2732                            VARIABLE_COUNT,
2733                        )?)
2734                    }
2735                })
2736                .collect::<Result<Vec<_>, Box<dyn std::error::Error>>>()?;
2737            let mut tree = IncrementalGuardedCascadeWinnerV0::new();
2738            let mut entries = BTreeMap::new();
2739            for (index, guarded_root) in guarded.iter().copied().take(24).enumerate() {
2740                let key = u64::try_from(index)
2741                    .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2742                entries.insert(key, guarded_root);
2743                tree.insert(&mut manager, key, guarded_root)?;
2744            }
2745            let mut state = 0xa400_0000_1220_0000_u64
2746                ^ u64::try_from(seed_index)
2747                    .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2748            for edit_index in 0..EDIT_COUNT {
2749                let insert = entries.len() < 8 || next_stream_seed(&mut state) & 1 == 0;
2750                if insert {
2751                    let declaration_index = next_stream_seed(&mut state) as usize % guarded.len();
2752                    let mut key = next_stream_seed(&mut state) % 100_000;
2753                    while entries.contains_key(&key) {
2754                        key = key.wrapping_add(1);
2755                    }
2756                    entries.insert(key, guarded[declaration_index]);
2757                    tree.insert(&mut manager, key, guarded[declaration_index])?;
2758                } else {
2759                    let target = next_stream_seed(&mut state) as usize % entries.len();
2760                    let key = entries
2761                        .keys()
2762                        .nth(target)
2763                        .copied()
2764                        .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
2765                    entries.remove(&key);
2766                    tree.remove(&mut manager, &key)?;
2767                }
2768                let batch = batch_winner_from_entries(&mut manager, &entries)?;
2769                assert_eq!(
2770                    tree.root().node_id(),
2771                    batch.node_id(),
2772                    "A4 mismatch at seed {seed_index}, edit {edit_index}: incremental={} batch={}",
2773                    tree.root().node_id(),
2774                    batch.node_id(),
2775                );
2776                for assignment_index in 0..(1 << VARIABLE_COUNT) {
2777                    let assignment = assignment_for_index(assignment_index, VARIABLE_COUNT);
2778                    let expected = entries.values().rev().find_map(|guarded_root| {
2779                        evaluate_guarded_cascade_winner_v0(&manager, *guarded_root, &assignment)
2780                            .ok()
2781                            .flatten()
2782                    });
2783                    let actual =
2784                        evaluate_guarded_cascade_winner_v0(&manager, tree.root(), &assignment)?;
2785                    assert_eq!(
2786                        actual, expected,
2787                        "pointwise A4 mismatch at seed {seed_index}, edit {edit_index}, assignment {assignment_index}"
2788                    );
2789                    checked_points += 1;
2790                }
2791                checked_trials += 1;
2792            }
2793        }
2794        eprintln!(
2795            "{{\"seedCount\":{SEED_COUNT},\"editsPerSeed\":{EDIT_COUNT},\"checkedTrials\":{checked_trials},\"checkedPoints\":{checked_points},\"mismatchCount\":0,\"streamingNoRestoration\":true}}"
2796        );
2797        Ok(())
2798    }
2799
2800    #[test]
2801    fn incremental_winner_reports_logarithmic_aggregate_updates_and_compression()
2802    -> Result<(), FirstWitnessErrorV0> {
2803        const VARIABLE_COUNT: usize = 12;
2804        const EDIT_COUNT: usize = 128;
2805        let mut scale_rows = Vec::new();
2806        for entry_count in [128_usize, 512, 2_048] {
2807            let declaration_count = entry_count + EDIT_COUNT;
2808            let mut manager =
2809                streaming_winner_manager(VARIABLE_COUNT, declaration_count, 16_384, u64::MAX)?;
2810            let guarded = (0..declaration_count)
2811                .map(|index| {
2812                    guarded_root_from_mask(
2813                        &mut manager,
2814                        u32::try_from(index)
2815                            .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
2816                        1 << (index % VARIABLE_COUNT),
2817                        VARIABLE_COUNT,
2818                    )
2819                })
2820                .collect::<Result<Vec<_>, _>>()?;
2821            let mut entries = BTreeMap::new();
2822            let mut tree = IncrementalGuardedCascadeWinnerV0::new();
2823            for (key, root) in guarded.iter().copied().take(entry_count).enumerate() {
2824                let key = u64::try_from(key)
2825                    .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2826                entries.insert(key, root);
2827                tree.insert(&mut manager, key, root)?;
2828            }
2829            let initial_updates = tree.aggregate_updates();
2830            let mut linear_refold_updates = 0_u64;
2831            let mut state = 0x3a00_0000_u64
2832                ^ u64::try_from(entry_count)
2833                    .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2834            for edit_index in 0..EDIT_COUNT {
2835                let batch = if edit_index % 2 == 0 {
2836                    let target = next_stream_seed(&mut state) as usize % entries.len();
2837                    let key = entries
2838                        .keys()
2839                        .nth(target)
2840                        .copied()
2841                        .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
2842                    entries.remove(&key);
2843                    if edit_index % 4 < 2 {
2844                        let batch = batch_winner_from_entries(&mut manager, &entries)?;
2845                        tree.remove(&mut manager, &key)?;
2846                        batch
2847                    } else {
2848                        tree.remove(&mut manager, &key)?;
2849                        batch_winner_from_entries(&mut manager, &entries)?
2850                    }
2851                } else {
2852                    let declaration_index = entry_count + edit_index / 2;
2853                    let key = 1_000_000_u64
2854                        + u64::try_from(declaration_index)
2855                            .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2856                    entries.insert(key, guarded[declaration_index]);
2857                    if edit_index % 4 < 2 {
2858                        let batch = batch_winner_from_entries(&mut manager, &entries)?;
2859                        tree.insert(&mut manager, key, guarded[declaration_index])?;
2860                        batch
2861                    } else {
2862                        tree.insert(&mut manager, key, guarded[declaration_index])?;
2863                        batch_winner_from_entries(&mut manager, &entries)?
2864                    }
2865                };
2866                linear_refold_updates += u64::try_from(entries.len())
2867                    .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2868                assert_eq!(tree.root().node_id(), batch.node_id());
2869            }
2870            let aggregate_updates = tree.aggregate_updates() - initial_updates;
2871            let updates_per_edit = aggregate_updates as f64 / EDIT_COUNT as f64;
2872            let ratio = updates_per_edit / (entry_count as f64).log2();
2873            scale_rows.push((
2874                entry_count,
2875                aggregate_updates,
2876                updates_per_edit,
2877                ratio,
2878                linear_refold_updates,
2879            ));
2880        }
2881
2882        let mut compression_rows = Vec::new();
2883        for entry_count in [8_usize, 16, 24] {
2884            const COMPRESSION_VARIABLE_COUNT: usize = 24;
2885            let mut manager = streaming_winner_manager(
2886                COMPRESSION_VARIABLE_COUNT,
2887                entry_count,
2888                16_384,
2889                u64::MAX,
2890            )?;
2891            let mut tree = IncrementalGuardedCascadeWinnerV0::new();
2892            for index in 0..entry_count {
2893                let root = guarded_root_from_mask(
2894                    &mut manager,
2895                    u32::try_from(index)
2896                        .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
2897                    1 << (index % COMPRESSION_VARIABLE_COUNT),
2898                    COMPRESSION_VARIABLE_COUNT,
2899                )?;
2900                tree.insert(
2901                    &mut manager,
2902                    u64::try_from(index)
2903                        .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?,
2904                    root,
2905                )?;
2906            }
2907            let node_count = manager.reachable_winner_node_count(tree.root())?;
2908            let compression = (1_u64 << COMPRESSION_VARIABLE_COUNT) as f64 / node_count as f64;
2909            compression_rows.push((entry_count, node_count, compression));
2910        }
2911        eprintln!(
2912            "{{\"declaredSynthetic\":true,\"streamingNoRestoration\":true,\"alternatedMeasurementOrder\":true,\"scaleRows\":{scale_rows:?},\"compressionRows\":{compression_rows:?},\"pilotAggregateUpdateRatios\":[6.07,6.29,6.46],\"pilotCompressionBand\":[20998,453438]}}"
2913        );
2914        assert!(scale_rows.iter().all(|row| row.2 < row.0 as f64));
2915        assert!(
2916            scale_rows.iter().all(|row| (1.5..=3.0).contains(&row.3)),
2917            "aggregate updates per edit must stay within the measured log2 coefficient band"
2918        );
2919        assert!(compression_rows.iter().all(|row| row.2 > 1.0));
2920        assert!(
2921            compression_rows
2922                .windows(2)
2923                .all(|pair| pair[0].1 < pair[1].1),
2924            "the three compression observations must have distinct increasing node counts"
2925        );
2926        Ok(())
2927    }
2928
2929    #[derive(Debug)]
2930    struct ReclamationMeasurementV0 {
2931        interval_operations: u64,
2932        rebuild_count: usize,
2933        maximum_nodes_before: usize,
2934        minimum_nodes_after: usize,
2935        final_total_nodes: usize,
2936        final_live_nodes: usize,
2937        rebuild_elapsed_nanos: u128,
2938    }
2939
2940    fn measure_incremental_winner_reclamation(
2941        interval_operations: u64,
2942    ) -> Result<ReclamationMeasurementV0, FirstWitnessErrorV0> {
2943        const VARIABLE_COUNT: usize = 10;
2944        const ENTRY_COUNT: usize = 128;
2945        const EDIT_COUNT: usize = 1_000;
2946        const DECLARATION_COUNT: usize = ENTRY_COUNT + EDIT_COUNT;
2947        let mut manager = streaming_winner_manager(
2948            VARIABLE_COUNT,
2949            DECLARATION_COUNT,
2950            4_096,
2951            interval_operations,
2952        )?;
2953        let mut tree = IncrementalGuardedCascadeWinnerV0::new();
2954        let mut keys = BTreeMap::new();
2955        for index in 0..ENTRY_COUNT {
2956            let root = guarded_root_from_mask(
2957                &mut manager,
2958                u32::try_from(index)
2959                    .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
2960                1 << (index % VARIABLE_COUNT),
2961                VARIABLE_COUNT,
2962            )?;
2963            let key =
2964                u64::try_from(index).map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
2965            keys.insert(
2966                key,
2967                u32::try_from(index)
2968                    .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
2969            );
2970            tree.insert(&mut manager, key, root)?;
2971        }
2972        let mut state = 0x3c00_1220_5eed_u64 ^ interval_operations;
2973        let mut rebuild_count = 0usize;
2974        let mut maximum_nodes_before = 0usize;
2975        let mut minimum_nodes_after = usize::MAX;
2976        let mut rebuild_elapsed_nanos = 0u128;
2977        for edit in 0..EDIT_COUNT {
2978            if edit % 2 == 0 {
2979                let target = next_stream_seed(&mut state) as usize % keys.len();
2980                let key = keys
2981                    .keys()
2982                    .nth(target)
2983                    .copied()
2984                    .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
2985                keys.remove(&key);
2986                tree.remove(&mut manager, &key)?;
2987            } else {
2988                let declaration = ENTRY_COUNT + edit;
2989                let mut key = next_stream_seed(&mut state) % 1_000_000;
2990                while keys.contains_key(&key) {
2991                    key = key.wrapping_add(1);
2992                }
2993                let root = guarded_root_from_mask(
2994                    &mut manager,
2995                    u32::try_from(declaration)
2996                        .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
2997                    1 << (declaration % VARIABLE_COUNT)
2998                        | 1 << ((declaration * 7 + 1) % VARIABLE_COUNT),
2999                    VARIABLE_COUNT,
3000                )?;
3001                keys.insert(
3002                    key,
3003                    u32::try_from(declaration)
3004                        .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
3005                );
3006                tree.insert(&mut manager, key, root)?;
3007            }
3008            let started = Instant::now();
3009            if let Some(report) = tree.reclaim_manager_if_due(&mut manager)? {
3010                rebuild_elapsed_nanos += started.elapsed().as_nanos();
3011                rebuild_count += 1;
3012                maximum_nodes_before = maximum_nodes_before.max(report.nodes_before);
3013                minimum_nodes_after = minimum_nodes_after.min(report.nodes_after);
3014            }
3015            let expected = keys.last_key_value().map(|(_, declaration)| *declaration);
3016            assert_eq!(
3017                evaluate_guarded_cascade_winner_v0(&manager, tree.root(), &[true; VARIABLE_COUNT],)?,
3018                expected,
3019                "reclamation must remap every cached aggregate and guarded leaf"
3020            );
3021        }
3022        let final_live_nodes = manager.reachable_winner_node_count(tree.root())?;
3023        Ok(ReclamationMeasurementV0 {
3024            interval_operations,
3025            rebuild_count,
3026            maximum_nodes_before,
3027            minimum_nodes_after: if rebuild_count == 0 {
3028                manager.node_count()
3029            } else {
3030                minimum_nodes_after
3031            },
3032            final_total_nodes: manager.node_count(),
3033            final_live_nodes,
3034            rebuild_elapsed_nanos,
3035        })
3036    }
3037
3038    #[test]
3039    fn manager_reclamation_is_remeasured_with_declaration_terminals()
3040    -> Result<(), Box<dyn std::error::Error>> {
3041        let candidates = [4_096_u64, 16_384, 65_536]
3042            .into_iter()
3043            .map(measure_incremental_winner_reclamation)
3044            .collect::<Result<Vec<_>, _>>()?;
3045        let disabled = measure_incremental_winner_reclamation(u64::MAX)?;
3046        let selected = candidates.iter().rev().find(|row| {
3047            row.rebuild_count > 0
3048                && row.maximum_nodes_before <= row.minimum_nodes_after.saturating_mul(64)
3049        });
3050        let selected = selected.ok_or_else(|| {
3051            std::io::Error::other(format!(
3052                "no reclamation interval rebuilt the MTBDD-terminal manager within the retained-to-live ceiling: {candidates:?}"
3053            ))
3054        })?;
3055        assert!(selected.rebuild_count > 0);
3056        assert!(
3057            selected
3058                .final_total_nodes
3059                .saturating_mul(disabled.final_live_nodes)
3060                < disabled
3061                    .final_total_nodes
3062                    .saturating_mul(selected.final_live_nodes),
3063            "reclamation must lower the retained-to-live node ratio"
3064        );
3065        eprintln!(
3066            "{{\"declaredSynthetic\":true,\"terminalAlphabet\":\"declarationIdPlusBot\",\"candidateRows\":{candidates:?},\"selectedIntervalOperations\":{},\"disabledRow\":{disabled:?},\"amortizedSelectedRebuildNanosPerEdit\":{}}}",
3067            selected.interval_operations,
3068            selected.rebuild_elapsed_nanos / 1_000,
3069        );
3070        Ok(())
3071    }
3072
3073    #[derive(Debug)]
3074    struct CacheBudgetMeasurementV0 {
3075        capacity: usize,
3076        cache_occupancy: usize,
3077        tree_elapsed_nanos: u128,
3078        linear_elapsed_nanos: u128,
3079        winner: &'static str,
3080        restoration_protocol: bool,
3081        choice_counters: FirstWitnessChoiceOperationCountersV0,
3082    }
3083
3084    fn measure_incremental_winner_cache_budget(
3085        capacity: usize,
3086        restoration_protocol: bool,
3087    ) -> Result<CacheBudgetMeasurementV0, FirstWitnessErrorV0> {
3088        const VARIABLE_COUNT: usize = 12;
3089        const ENTRY_COUNT: usize = 512;
3090        const EDIT_COUNT: usize = 192;
3091        const DECLARATION_COUNT: usize = ENTRY_COUNT + EDIT_COUNT;
3092        let mut manager =
3093            streaming_winner_manager(VARIABLE_COUNT, DECLARATION_COUNT, capacity, u64::MAX)?;
3094        let mut entries = BTreeMap::new();
3095        let mut tree = IncrementalGuardedCascadeWinnerV0::new();
3096        for index in 0..ENTRY_COUNT {
3097            let root = guarded_root_from_mask(
3098                &mut manager,
3099                u32::try_from(index)
3100                    .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
3101                1 << (index % VARIABLE_COUNT),
3102                VARIABLE_COUNT,
3103            )?;
3104            let key =
3105                u64::try_from(index).map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
3106            entries.insert(key, root);
3107            tree.insert(&mut manager, key, root)?;
3108        }
3109        let mut tree_elapsed_nanos = 0_u128;
3110        let mut linear_elapsed_nanos = 0_u128;
3111        for edit in 0..EDIT_COUNT {
3112            let declaration = ENTRY_COUNT + edit;
3113            let key = u64::try_from(edit % ENTRY_COUNT)
3114                .map_err(|_| FirstWitnessErrorV0::VariableCapacityExceeded)?;
3115            let root = guarded_root_from_mask(
3116                &mut manager,
3117                u32::try_from(declaration)
3118                    .map_err(|_| FirstWitnessErrorV0::DeclarationIdCapacityExceeded)?,
3119                1 << (declaration % VARIABLE_COUNT) | 1 << ((declaration * 5 + 1) % VARIABLE_COUNT),
3120                VARIABLE_COUNT,
3121            )?;
3122            let previous_root = entries
3123                .insert(key, root)
3124                .ok_or(FirstWitnessErrorV0::VariableCapacityExceeded)?;
3125            let batch = if edit % 2 == 0 {
3126                let started = Instant::now();
3127                let batch = batch_winner_from_entries(&mut manager, &entries)?;
3128                linear_elapsed_nanos += started.elapsed().as_nanos();
3129                let started = Instant::now();
3130                tree.insert(&mut manager, key, root)?;
3131                tree_elapsed_nanos += started.elapsed().as_nanos();
3132                batch
3133            } else {
3134                let started = Instant::now();
3135                tree.insert(&mut manager, key, root)?;
3136                tree_elapsed_nanos += started.elapsed().as_nanos();
3137                let started = Instant::now();
3138                let batch = batch_winner_from_entries(&mut manager, &entries)?;
3139                linear_elapsed_nanos += started.elapsed().as_nanos();
3140                batch
3141            };
3142            assert_eq!(tree.root().node_id(), batch.node_id());
3143            if restoration_protocol {
3144                entries.insert(key, previous_root);
3145                tree.insert(&mut manager, key, previous_root)?;
3146            }
3147        }
3148        #[cfg(test)]
3149        if std::env::var_os("OMENA_G122_INJECT_UNCONDITIONAL_TREE_SPEEDUP").is_some() {
3150            tree_elapsed_nanos = 1;
3151        }
3152        Ok(CacheBudgetMeasurementV0 {
3153            capacity,
3154            cache_occupancy: manager.apply_cache_len(),
3155            tree_elapsed_nanos,
3156            linear_elapsed_nanos,
3157            winner: if tree_elapsed_nanos < linear_elapsed_nanos {
3158                "incrementalTree"
3159            } else {
3160                "warmLinearRefold"
3161            },
3162            restoration_protocol,
3163            choice_counters: manager.first_witness_counters(),
3164        })
3165    }
3166
3167    #[test]
3168    fn apply_cache_budget_condition_is_measured_at_three_points() -> Result<(), FirstWitnessErrorV0>
3169    {
3170        let unbounded_probe = measure_incremental_winner_cache_budget(1_000_000, false)?;
3171        let working_set = unbounded_probe.cache_occupancy.max(3);
3172        let rows = [
3173            measure_incremental_winner_cache_budget((working_set / 16).max(1), false)?,
3174            measure_incremental_winner_cache_budget(working_set, false)?,
3175            measure_incremental_winner_cache_budget(working_set.saturating_mul(2), false)?,
3176        ];
3177        let restoration_rows = [
3178            measure_incremental_winner_cache_budget((working_set / 16).max(1), true)?,
3179            measure_incremental_winner_cache_budget(working_set, true)?,
3180            measure_incremental_winner_cache_budget(working_set.saturating_mul(2), true)?,
3181        ];
3182        eprintln!(
3183            "{{\"declaredSynthetic\":true,\"terminalAlphabet\":\"declarationIdPlusBot\",\"alternatedMeasurementOrder\":true,\"streamingNoRestoration\":true,\"workingSetEntries\":{working_set},\"unboundedProbe\":{unbounded_probe:?},\"budgetRows\":{rows:?},\"restorationBiasRows\":{restoration_rows:?},\"claim\":\"wall-clock benefit is conditional on the apply-cache budget\"}}"
3184        );
3185        assert!(rows[0].capacity < working_set);
3186        assert!(rows[1].capacity >= working_set);
3187        assert!(rows[2].capacity > working_set);
3188        assert!(rows.iter().all(|row| row.cache_occupancy <= row.capacity));
3189        assert!(
3190            rows.iter()
3191                .all(|row| row.tree_elapsed_nanos > 0 && row.linear_elapsed_nanos > 0)
3192        );
3193        assert_eq!(
3194            rows[0].winner, "incrementalTree",
3195            "the below-working-set budget must retain the measured tree win"
3196        );
3197        let crossover_after_low_budget = rows[1..]
3198            .iter()
3199            .position(|row| row.winner == "warmLinearRefold");
3200        assert!(
3201            crossover_after_low_budget.is_some(),
3202            "at least one at-or-above-working-set budget must retain the measured linear-refold win"
3203        );
3204        assert!(rows.iter().all(|row| !row.restoration_protocol));
3205        for (streaming, restoration) in rows.iter().zip(restoration_rows.iter()) {
3206            assert!(restoration.restoration_protocol);
3207            assert_eq!(streaming.capacity, restoration.capacity);
3208            assert_ne!(
3209                restoration.choice_counters, streaming.choice_counters,
3210                "restoring every edit must move the product-operation counters at every cache point"
3211            );
3212            assert_ne!(
3213                (
3214                    restoration.choice_counters.apply_cache_lookups,
3215                    restoration.choice_counters.apply_cache_hits,
3216                    restoration.cache_occupancy,
3217                ),
3218                (
3219                    streaming.choice_counters.apply_cache_lookups,
3220                    streaming.choice_counters.apply_cache_hits,
3221                    streaming.cache_occupancy,
3222                ),
3223                "the restoration protocol must move the measured cache table at every cache point"
3224            );
3225        }
3226        Ok(())
3227    }
3228
3229    fn guarded_fragment_node_count(
3230        fragment: &GuardedCascadeFragmentV0<usize>,
3231        order: VariableOrderRegistrationV0,
3232    ) -> Result<usize, FirstWitnessErrorV0> {
3233        let mut manager = FirstWitnessManagerV0::new(
3234            order,
3235            FirstWitnessManagerConfigV0 {
3236                shortcuts: false,
3237                apply_cache_capacity: 65_536,
3238                rebuild_interval_operations: u64::MAX,
3239            },
3240        );
3241        let root = build_guarded_cascade_winner_v0(&mut manager, fragment)?;
3242        manager.reachable_winner_node_count(root)
3243    }
3244
3245    #[test]
3246    fn at_rule_nesting_dfs_registration_pins_the_blocked_pair_falsifier()
3247    -> Result<(), Box<dyn std::error::Error>> {
3248        const PAIR_COUNT: usize = 12;
3249        const INTERLEAVED_CEILING: usize = 4 * PAIR_COUNT;
3250        let contexts = (0..PAIR_COUNT)
3251            .map(|index| vec![format!("a-{index}"), format!("b-{index}")])
3252            .collect::<Vec<_>>();
3253        let production_paths = at_rule_nesting_dfs_paths_v0(contexts.as_slice())?;
3254        let fragment =
3255            GuardedCascadeFragmentV0::admit(
3256                (0..PAIR_COUNT).flat_map(|index| [format!("a-{index}"), format!("b-{index}")]),
3257                contexts.iter().zip(&production_paths).enumerate().map(
3258                    |(index, (context, paths))| {
3259                        GuardedCascadeCandidateV0::new(
3260                            u32::try_from(index).unwrap_or_default(),
3261                            "button.primary",
3262                            AuthoredPropertyTextV0::new("color"),
3263                            PAIR_COUNT - index,
3264                            GuardedCascadeSpecificityExactnessV0::Exact,
3265                            0,
3266                            context
3267                                .iter()
3268                                .zip(paths)
3269                                .enumerate()
3270                                .map(|(component_index, (atom, path))| {
3271                                    if component_index == 0 {
3272                                        GuardedCascadeConditionAtomV0::media(
3273                                            atom,
3274                                            path.iter().copied(),
3275                                            false,
3276                                        )
3277                                    } else {
3278                                        GuardedCascadeConditionAtomV0::supports(
3279                                            atom,
3280                                            path.iter().copied(),
3281                                            false,
3282                                        )
3283                                    }
3284                                })
3285                                .collect(),
3286                        )
3287                    },
3288                ),
3289            )?;
3290        let order = at_rule_nesting_order_for_fragment_v0(&fragment)?;
3291        let observed_domain = order.domain();
3292        let observed_nodes = guarded_fragment_node_count(&fragment, order)?;
3293        let blocked_order = VariableOrderRegistrationV0::site_first_appearance(
3294            (0..PAIR_COUNT)
3295                .map(|index| format!("a-{index}"))
3296                .chain((0..PAIR_COUNT).map(|index| format!("b-{index}"))),
3297        )?;
3298        let blocked_nodes = guarded_fragment_node_count(&fragment, blocked_order)?;
3299        assert!(
3300            observed_nodes <= INTERLEAVED_CEILING,
3301            "A5 order-policy ceiling exceeded: domain={} observedNodes={observed_nodes} ceiling={INTERLEAVED_CEILING}",
3302            observed_domain.name(),
3303        );
3304        assert_eq!(observed_domain, VariableOrderDomainV0::AtRuleNestingDfs);
3305        assert!(blocked_nodes > observed_nodes.saturating_mul(100));
3306        eprintln!(
3307            "{{\"declaredSynthetic\":true,\"domain\":\"{}\",\"pairCount\":{PAIR_COUNT},\"interleavedNodes\":{observed_nodes},\"blockedNodes\":{blocked_nodes},\"interleavedCeiling\":{INTERLEAVED_CEILING},\"a1ThroughA4OrderIndependent\":true}}",
3308            observed_domain.name(),
3309        );
3310        Ok(())
3311    }
3312
3313    #[test]
3314    fn at_rule_order_domain_census_has_one_derivation_site() {
3315        let source = include_str!("first_witness.rs");
3316        let production = source
3317            .split("\n#[cfg(test)]\nmod tests")
3318            .next()
3319            .unwrap_or(source);
3320        let at_rule_call = ["VariableOrderRegistrationV0::at_rule_", "nesting_dfs("].concat();
3321        let site_call = ["VariableOrderRegistrationV0::site_", "first_appearance("].concat();
3322        assert_eq!(production.matches(&at_rule_call).count(), 1);
3323        assert!(production.contains(&site_call));
3324        assert_ne!(
3325            AT_RULE_NESTING_DFS_ORDERING_DOMAIN_V0,
3326            SITE_FIRST_APPEARANCE_ORDERING_DOMAIN_V0
3327        );
3328    }
3329
3330    #[test]
3331    fn canonical_nodes_identify_functions_both_ways() -> Result<(), FirstWitnessErrorV0> {
3332        let mut manager = manager(true)?;
3333        let a = manager.variable("a")?;
3334        let b = manager.variable("b")?;
3335        let a_and_b = manager.and(a, b)?;
3336        let b_and_a = manager.and(b, a)?;
3337        let a_or_b = manager.or(a, b)?;
3338        assert_eq!(a_and_b, b_and_a, "same function must share one node");
3339        assert_ne!(
3340            a_and_b, a_or_b,
3341            "distinct functions must not share one node"
3342        );
3343        Ok(())
3344    }
3345
3346    #[test]
3347    fn collapse_rule_mutation_preserves_evaluation_but_breaks_canonical_identity()
3348    -> Result<(), FirstWitnessErrorV0> {
3349        let mut manager = manager(false)?;
3350        manager.register_declaration_terminals([7])?;
3351        let canonical = manager.declaration_terminal(7)?;
3352        let unreduced = manager.intern_without_collapse_for_test(0, canonical, canonical)?;
3353        for assignment in [[false, false, false], [true, false, false]] {
3354            assert_eq!(
3355                evaluate_guarded_cascade_winner_v0(
3356                    &manager,
3357                    GuardedCascadeWinnerRootV0(canonical),
3358                    &assignment,
3359                )?,
3360                evaluate_guarded_cascade_winner_v0(
3361                    &manager,
3362                    GuardedCascadeWinnerRootV0(unreduced),
3363                    &assignment,
3364                )?,
3365                "removing collapse must not be confused with an evaluation defect"
3366            );
3367        }
3368        assert_ne!(
3369            canonical, unreduced,
3370            "without lo==hi collapse one function receives two NodeIds"
3371        );
3372        eprintln!(
3373            "{{\"mutation\":\"collapseRuleDeleted\",\"evaluationMismatches\":0,\"canonicalNodeId\":{canonical},\"unreducedNodeId\":{unreduced},\"canonicalIdentity\":false}}"
3374        );
3375        Ok(())
3376    }
3377
3378    #[test]
3379    fn independent_construction_after_cache_flush_reuses_the_canonical_node()
3380    -> Result<(), FirstWitnessErrorV0> {
3381        let mut manager = FirstWitnessManagerV0::new(
3382            VariableOrderRegistrationV0::site_first_appearance(["a", "b", "c"])?,
3383            FirstWitnessManagerConfigV0 {
3384                shortcuts: false,
3385                apply_cache_capacity: 32,
3386                rebuild_interval_operations: 1,
3387            },
3388        );
3389        let a = manager.variable("a")?;
3390        let b = manager.variable("b")?;
3391        let c = manager.variable("c")?;
3392        let a_and_b = manager.and(a, b)?;
3393        let not_a = manager.not(a)?;
3394        let not_a_and_c = manager.and(not_a, c)?;
3395        let first = manager.or(a_and_b, not_a_and_c)?;
3396
3397        let mut roots = [first];
3398        let report = manager
3399            .reclaim_if_due(&mut roots)?
3400            .ok_or(FirstWitnessErrorV0::InvalidNode(first))?;
3401        assert_eq!(manager.apply_cache_len(), 0, "rebuild flushes apply cache");
3402        let first = roots[0];
3403
3404        let a = manager.variable("a")?;
3405        let b = manager.variable("b")?;
3406        let c = manager.variable("c")?;
3407        let not_a = manager.not(a)?;
3408        let c_and_not_a = manager.and(c, not_a)?;
3409        let b_and_a = manager.and(b, a)?;
3410        let second = manager.or(c_and_not_a, b_and_a)?;
3411
3412        assert_eq!(
3413            second, first,
3414            "cache-independent construction of one function must reuse its NodeId"
3415        );
3416        eprintln!(
3417            "{{\"cacheFlushed\":true,\"firstNodeId\":{first},\"secondNodeId\":{second},\"nodesBeforeRebuild\":{},\"nodesAfterRebuild\":{}}}",
3418            report.nodes_before, report.nodes_after,
3419        );
3420        Ok(())
3421    }
3422
3423    #[test]
3424    fn contradiction_and_excluded_middle_reduce_to_terminals() -> Result<(), FirstWitnessErrorV0> {
3425        let mut manager = manager(true)?;
3426        let condition = manager.variable("c")?;
3427        let negated = manager.not(condition)?;
3428        let contradiction = manager.and(condition, negated)?;
3429        let excluded_middle = manager.or(condition, negated)?;
3430        assert_eq!(contradiction, FALSE_NODE_ID_V0);
3431        assert_eq!(excluded_middle, TRUE_NODE_ID_V0);
3432        Ok(())
3433    }
3434
3435    #[test]
3436    fn shortcut_switch_changes_work_not_results() -> Result<(), FirstWitnessErrorV0> {
3437        fn fixed_seed(
3438            shortcuts: bool,
3439        ) -> Result<(NodeId, FirstWitnessOperationCountersV0), FirstWitnessErrorV0> {
3440            let mut manager = manager(shortcuts)?;
3441            let a = manager.variable("a")?;
3442            let b = manager.variable("b")?;
3443            let shared = manager.or(a, b)?;
3444            let result = manager.and(shared, shared)?;
3445            Ok((result, manager.counters()))
3446        }
3447        let (shortcut_result, shortcut_counts) = fixed_seed(true)?;
3448        let (recursive_result, recursive_counts) = fixed_seed(false)?;
3449        assert_eq!(shortcut_result, recursive_result);
3450        assert!(recursive_counts.choose_invocations > shortcut_counts.choose_invocations);
3451        assert!(recursive_counts.apply_invocations > shortcut_counts.apply_invocations);
3452        assert!(recursive_counts.apply_cache_lookups > shortcut_counts.apply_cache_lookups);
3453        eprintln!(
3454            "{{\"seed\":\"(a or b) and (a or b)\",\"result\":{},\"shortcuts\":{{\"choose\":{},\"apply\":{},\"cacheLookups\":{}}},\"recursive\":{{\"choose\":{},\"apply\":{},\"cacheLookups\":{}}}}}",
3455            shortcut_result,
3456            shortcut_counts.choose_invocations,
3457            shortcut_counts.apply_invocations,
3458            shortcut_counts.apply_cache_lookups,
3459            recursive_counts.choose_invocations,
3460            recursive_counts.apply_invocations,
3461            recursive_counts.apply_cache_lookups,
3462        );
3463        Ok(())
3464    }
3465
3466    #[test]
3467    fn boolean_laws_recompute_with_shortcuts_disabled() -> Result<(), FirstWitnessErrorV0> {
3468        let mut manager = manager(false)?;
3469        let a = manager.variable("a")?;
3470        let b = manager.variable("b")?;
3471        let c = manager.variable("c")?;
3472        let a_and_b = manager.and(a, b)?;
3473        let b_and_c = manager.and(b, c)?;
3474        let left_associative = manager.and(a_and_b, c)?;
3475        let right_associative = manager.and(a, b_and_c)?;
3476        assert_eq!(left_associative, right_associative);
3477        assert_eq!(manager.and(a, a)?, a);
3478        let a_or_b = manager.or(a, b)?;
3479        assert_eq!(manager.and(a, a_or_b)?, a);
3480        let not_a = manager.not(a)?;
3481        assert_eq!(manager.and(a, not_a)?, FALSE_NODE_ID_V0);
3482        assert_eq!(manager.or(a, not_a)?, TRUE_NODE_ID_V0);
3483        Ok(())
3484    }
3485
3486    #[test]
3487    fn apply_cache_capacity_is_a_live_bound() -> Result<(), FirstWitnessErrorV0> {
3488        let mut manager = FirstWitnessManagerV0::new(
3489            VariableOrderRegistrationV0::site_first_appearance(["a", "b", "c"])?,
3490            FirstWitnessManagerConfigV0 {
3491                shortcuts: false,
3492                apply_cache_capacity: 2,
3493                rebuild_interval_operations: u64::MAX,
3494            },
3495        );
3496        let a = manager.variable("a")?;
3497        let b = manager.variable("b")?;
3498        let c = manager.variable("c")?;
3499        let _ = manager.and(a, b)?;
3500        let _ = manager.or(a, c)?;
3501        let _ = manager.xor(b, c)?;
3502        assert!(manager.apply_cache_len() <= 2);
3503        assert_eq!(manager.config().apply_cache_capacity, 2);
3504        Ok(())
3505    }
3506
3507    #[test]
3508    fn rebuild_reclaims_unreachable_nodes_and_remaps_live_roots() -> Result<(), FirstWitnessErrorV0>
3509    {
3510        let mut manager = FirstWitnessManagerV0::new(
3511            VariableOrderRegistrationV0::site_first_appearance(["a", "b", "c"])?,
3512            FirstWitnessManagerConfigV0 {
3513                shortcuts: false,
3514                apply_cache_capacity: 16,
3515                rebuild_interval_operations: 1,
3516            },
3517        );
3518        let a = manager.variable("a")?;
3519        let b = manager.variable("b")?;
3520        let c = manager.variable("c")?;
3521        let live = manager.and(a, b)?;
3522        let _dead = manager.or(a, c)?;
3523        let before = manager.node_count();
3524        let mut roots = [live];
3525        let report = manager.reclaim_if_due(&mut roots)?;
3526        assert!(report.is_some(), "rebuild interval reached");
3527        let Some(report) = report else {
3528            return Ok(());
3529        };
3530        assert!(manager.node_count() < before);
3531        assert_eq!(
3532            manager.node(roots[0]),
3533            Some(Node::Int {
3534                var: 0,
3535                lo: 0,
3536                hi: 2
3537            })
3538        );
3539        assert_eq!(report.nodes_after, manager.node_count());
3540        assert_eq!(manager.counters().rebuilds, 1);
3541        Ok(())
3542    }
3543
3544    #[test]
3545    fn site_first_appearance_policy_pins_synthetic_blocked_pair_bound()
3546    -> Result<(), FirstWitnessErrorV0> {
3547        const PAIRS: usize = 7;
3548        fn build(order: Vec<String>) -> Result<usize, FirstWitnessErrorV0> {
3549            let mut manager = FirstWitnessManagerV0::new(
3550                VariableOrderRegistrationV0::site_first_appearance(order)?,
3551                FirstWitnessManagerConfigV0 {
3552                    shortcuts: false,
3553                    apply_cache_capacity: 4_096,
3554                    rebuild_interval_operations: u64::MAX,
3555                },
3556            );
3557            let mut root = TRUE_NODE_ID_V0;
3558            for index in 0..PAIRS {
3559                let left = manager.variable(&format!("x{index}"))?;
3560                let right = manager.variable(&format!("y{index}"))?;
3561                let pair = manager.xor(left, right)?;
3562                root = manager.and(root, pair)?;
3563            }
3564            assert!(manager.is_satisfiable(root));
3565            Ok(manager.node_count())
3566        }
3567        let interleaved = (0..PAIRS)
3568            .flat_map(|index| [format!("x{index}"), format!("y{index}")])
3569            .collect();
3570        let blocked = (0..PAIRS)
3571            .map(|index| format!("x{index}"))
3572            .chain((0..PAIRS).map(|index| format!("y{index}")))
3573            .collect();
3574        let interleaved_nodes = build(interleaved)?;
3575        let blocked_nodes = build(blocked)?;
3576        eprintln!(
3577            "{{\"declaredSynthetic\":true,\"pairCount\":{PAIRS},\"policy\":\"siteFirstAppearance\",\"interleavedNodes\":{interleaved_nodes},\"blockedNodes\":{blocked_nodes},\"interleavedUpperBound\":{},\"blockedRatioFloor\":8}}",
3578            14 * PAIRS,
3579        );
3580        assert!(
3581            interleaved_nodes <= 14 * PAIRS,
3582            "interleaved={interleaved_nodes}, blocked={blocked_nodes}"
3583        );
3584        assert!(
3585            blocked_nodes >= interleaved_nodes * 8,
3586            "interleaved={interleaved_nodes}, blocked={blocked_nodes}"
3587        );
3588        Ok(())
3589    }
3590
3591    #[test]
3592    fn first_witness_fold_is_commutative_and_idempotent() {
3593        let left = vec!["alpha", "shared"];
3594        let right = vec!["beta", "shared"];
3595        assert_eq!(
3596            first_witness_fold_v0(&left, &right),
3597            first_witness_fold_v0(&right, &left)
3598        );
3599        assert_eq!(first_witness_fold_v0(&left, &left), left);
3600    }
3601
3602    #[test]
3603    fn core_is_disjoint_from_the_attractor_strategy_slot_and_host_model() {
3604        let core = include_str!("first_witness.rs");
3605        let production = core.split("#[cfg(test)]").next().unwrap_or(core);
3606        let attractor_strategy = ["Attractor", "EnumerationStrategyV0"].concat();
3607        assert!(!production.contains(&attractor_strategy));
3608        assert!(!production.contains("use crate::"));
3609        assert!(!production.contains("use super::"));
3610        let grn = include_str!("grn.rs");
3611        let module_name = ["first_", "witness"].concat();
3612        assert!(!grn.contains(&module_name));
3613    }
3614
3615    #[test]
3616    fn first_witness_declared_synthetic_measurement_report() -> Result<(), FirstWitnessErrorV0> {
3617        const VARIABLE_COUNT: usize = 12;
3618        const EDIT_COUNT: usize = 2_000;
3619        let atoms = (0..VARIABLE_COUNT)
3620            .map(|index| format!("g{index}"))
3621            .collect::<Vec<_>>();
3622        let mut manager = FirstWitnessManagerV0::new(
3623            VariableOrderRegistrationV0::site_first_appearance(atoms.clone())?,
3624            FirstWitnessManagerConfigV0::default(),
3625        );
3626        let mut root = TRUE_NODE_ID_V0;
3627        let mut rebuild_count = 0usize;
3628        let mut rebuilt_nodes_before = 0usize;
3629        let mut rebuilt_nodes_after = 0usize;
3630        let mut rebuild_elapsed_nanos = 0u128;
3631        for edit in 0..EDIT_COUNT {
3632            let left = manager.variable(&atoms[edit % VARIABLE_COUNT])?;
3633            let right = manager.variable(&atoms[(edit * 5 + 1) % VARIABLE_COUNT])?;
3634            let not_right = manager.not(right)?;
3635            let candidate = manager.and(left, not_right)?;
3636            root = if edit % 2 == 0 {
3637                manager.or(root, candidate)?
3638            } else {
3639                manager.xor(root, candidate)?
3640            };
3641            let started = Instant::now();
3642            let mut roots = [root];
3643            if let Some(report) = manager.reclaim_if_due(&mut roots)? {
3644                rebuild_elapsed_nanos += started.elapsed().as_nanos();
3645                root = roots[0];
3646                rebuild_count += 1;
3647                rebuilt_nodes_before += report.nodes_before;
3648                rebuilt_nodes_after += report.nodes_after;
3649            }
3650        }
3651        assert!(rebuild_count > 0);
3652        assert!(manager.apply_cache_len() <= DEFAULT_APPLY_CACHE_CAPACITY_V0);
3653        assert!(manager.is_satisfiable(root));
3654        eprintln!(
3655            "{{\"declaredSynthetic\":true,\"variableCount\":{VARIABLE_COUNT},\"editCount\":{EDIT_COUNT},\"cacheCapacity\":{},\"cacheOccupancy\":{},\"rebuildIntervalOperations\":{},\"rebuildCount\":{rebuild_count},\"nodesBeforeRebuildTotal\":{rebuilt_nodes_before},\"nodesAfterRebuildTotal\":{rebuilt_nodes_after},\"rebuildElapsedNanos\":{rebuild_elapsed_nanos},\"finalNodeCount\":{},\"operationCounters\":{{\"choose\":{},\"apply\":{},\"cacheLookups\":{},\"cacheHits\":{}}}}}",
3656            DEFAULT_APPLY_CACHE_CAPACITY_V0,
3657            manager.apply_cache_len(),
3658            DEFAULT_REBUILD_INTERVAL_OPERATIONS_V0,
3659            manager.node_count(),
3660            manager.counters().choose_invocations,
3661            manager.counters().apply_invocations,
3662            manager.counters().apply_cache_lookups,
3663            manager.counters().apply_cache_hits,
3664        );
3665        Ok(())
3666    }
3667}