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