Skip to main content

amari_enumerative/
namespace.rs

1//! Namespace and Capability types for geometric access control
2//!
3//! Namespaces are points in Grassmannians. Capabilities are Schubert conditions.
4//! Intersection theory determines access control.
5//!
6//! This module answers questions like "how many valid namespace configurations
7//! satisfy these capability constraints?" using rigorous enumerative geometry.
8//!
9//! # Contracts
10//!
11//! Key invariants maintained:
12//!
13//! - **Dependency satisfaction**: A capability can only be granted if all its dependencies are present
14//! - **Conflict freedom**: Two conflicting capabilities cannot coexist in the same namespace
15//! - **Grassmannian consistency**: All capabilities in a namespace must be defined on the same Grassmannian
16//!
17//! # Phantom Types
18//!
19//! The module integrates with the phantom type system for compile-time verification
20//! of capability grant states (Granted, Pending, Revoked).
21//!
22//! # Rayon Parallelization
23//!
24//! When the `parallel` feature is enabled, batch operations on namespaces
25//! use parallel iterators for improved performance.
26
27use crate::geometric_algebra::quantum_k_theory::QuantumKClass;
28use crate::schubert::{IntersectionResult, SchubertCalculus, SchubertClass};
29use crate::EnumerativeResult;
30use num_rational::Rational64;
31use std::sync::Arc;
32
33#[cfg(feature = "parallel")]
34use rayon::prelude::*;
35
36/// Unique identifier for a capability
37///
38/// # Contract
39///
40/// ```text
41/// invariant: self.0.len() > 0
42/// ```
43#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
44pub struct CapabilityId(pub Arc<str>);
45
46impl CapabilityId {
47    /// Create a new capability ID
48    ///
49    /// # Contract
50    ///
51    /// ```text
52    /// requires: name.len() > 0
53    /// ensures: result.0 == name
54    /// ```
55    #[must_use]
56    pub fn new(name: impl Into<String>) -> Self {
57        Self(Arc::from(name.into()))
58    }
59
60    /// Get the underlying name
61    #[must_use]
62    pub fn as_str(&self) -> &str {
63        &self.0
64    }
65}
66
67impl std::fmt::Display for CapabilityId {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        write!(f, "{}", self.0)
70    }
71}
72
73impl From<&str> for CapabilityId {
74    fn from(s: &str) -> Self {
75        Self::new(s)
76    }
77}
78
79impl From<String> for CapabilityId {
80    fn from(s: String) -> Self {
81        Self::new(s)
82    }
83}
84
85/// A capability: an incidence condition on namespaces
86///
87/// Capabilities represent access rights that can be granted to namespaces.
88/// Each capability corresponds to a Schubert class, representing the geometric
89/// condition that a namespace must satisfy to have that capability.
90///
91/// # Contracts
92///
93/// - The Schubert class must be valid for the given Grassmannian
94/// - Dependencies form a DAG (no cycles)
95/// - Conflicts are symmetric (if A conflicts with B, B should conflict with A)
96#[derive(Debug, Clone)]
97pub struct Capability {
98    /// Unique identifier
99    pub id: CapabilityId,
100    /// Human-readable name
101    pub name: String,
102    /// Schubert class representing the incidence condition
103    /// "Namespaces with this capability" = Schubert variety σ_λ
104    pub schubert_class: SchubertClass,
105    /// Dependencies: must have these capabilities first
106    pub requires: Vec<CapabilityId>,
107    /// Conflicts: cannot coexist with these
108    pub conflicts: Vec<CapabilityId>,
109}
110
111impl Capability {
112    /// Create a new capability
113    ///
114    /// # Contract
115    ///
116    /// ```text
117    /// requires: partition fits in Grassmannian box
118    /// ensures: result.codimension() == partition.iter().sum()
119    /// ```
120    pub fn new(
121        id: impl Into<String>,
122        name: impl Into<String>,
123        partition: Vec<usize>,
124        grassmannian: (usize, usize),
125    ) -> EnumerativeResult<Self> {
126        let id_str = id.into();
127        Ok(Self {
128            id: CapabilityId::new(id_str),
129            name: name.into(),
130            schubert_class: SchubertClass::new(partition, grassmannian)?,
131            requires: Vec::new(),
132            conflicts: Vec::new(),
133        })
134    }
135
136    /// Add a dependency (builder pattern)
137    #[must_use]
138    pub fn requires(mut self, cap_id: CapabilityId) -> Self {
139        self.requires.push(cap_id);
140        self
141    }
142
143    /// Add multiple dependencies
144    #[must_use]
145    pub fn requires_all(mut self, cap_ids: impl IntoIterator<Item = CapabilityId>) -> Self {
146        self.requires.extend(cap_ids);
147        self
148    }
149
150    /// Add a conflict (builder pattern)
151    #[must_use]
152    pub fn conflicts_with(mut self, cap_id: CapabilityId) -> Self {
153        self.conflicts.push(cap_id);
154        self
155    }
156
157    /// Add multiple conflicts
158    #[must_use]
159    pub fn conflicts_with_all(mut self, cap_ids: impl IntoIterator<Item = CapabilityId>) -> Self {
160        self.conflicts.extend(cap_ids);
161        self
162    }
163
164    /// Codimension of this capability's Schubert class
165    ///
166    /// # Contract
167    ///
168    /// ```text
169    /// ensures: result == self.schubert_class.codimension()
170    /// ```
171    #[must_use]
172    pub fn codimension(&self) -> usize {
173        self.schubert_class.partition.iter().sum()
174    }
175
176    /// Check if this capability has any dependencies
177    #[must_use]
178    pub fn has_dependencies(&self) -> bool {
179        !self.requires.is_empty()
180    }
181
182    /// Check if this capability has any conflicts
183    #[must_use]
184    pub fn has_conflicts(&self) -> bool {
185        !self.conflicts.is_empty()
186    }
187}
188
189/// A namespace: a point in a Grassmannian with associated capabilities
190///
191/// Namespaces represent isolated execution contexts.
192/// Their position in the Grassmannian encodes their "geometric location"
193/// while their capabilities determine what operations they can perform.
194///
195/// # Contracts
196///
197/// - All capabilities must be compatible with the namespace's Grassmannian
198/// - Capabilities must satisfy dependency ordering
199/// - No two conflicting capabilities can coexist
200#[derive(Debug, Clone)]
201pub struct Namespace {
202    /// The Grassmannian this namespace lives in: Gr(k, n)
203    pub grassmannian: (usize, usize),
204    /// Schubert cell containing this namespace (its "position")
205    pub position: SchubertClass,
206    /// Capabilities granted to this namespace
207    pub capabilities: Vec<Capability>,
208    /// Human-readable name
209    pub name: String,
210}
211
212impl Namespace {
213    /// Create a new namespace at a given Schubert position
214    #[must_use]
215    pub fn new(name: impl Into<String>, position: SchubertClass) -> Self {
216        Self {
217            grassmannian: position.grassmannian_dim,
218            position,
219            capabilities: Vec::new(),
220            name: name.into(),
221        }
222    }
223
224    /// Create a namespace with full access (identity Schubert class)
225    ///
226    /// # Contract
227    ///
228    /// ```text
229    /// requires: k < n
230    /// ensures: result.position.partition.is_empty()
231    /// ensures: result.grassmannian == (k, n)
232    /// ```
233    pub fn full(name: impl Into<String>, k: usize, n: usize) -> EnumerativeResult<Self> {
234        let position = SchubertClass::new(vec![], (k, n))?;
235        Ok(Self::new(name, position))
236    }
237
238    /// Grant a capability to this namespace
239    ///
240    /// # Contract
241    ///
242    /// ```text
243    /// requires: forall dep in capability.requires. self.has_capability(dep)
244    /// requires: forall conf in capability.conflicts. !self.has_capability(conf)
245    /// requires: forall existing in self.capabilities. !existing.conflicts.contains(capability.id)
246    /// ensures: self.has_capability(capability.id)
247    /// ```
248    ///
249    /// # Errors
250    ///
251    /// Returns `NamespaceError::Conflict` if the capability conflicts with an existing one.
252    /// Returns `NamespaceError::MissingDependency` if a required capability is missing.
253    pub fn grant(&mut self, capability: Capability) -> Result<(), NamespaceError> {
254        // Check for conflicts
255        for existing in &self.capabilities {
256            if capability.conflicts.contains(&existing.id) {
257                return Err(NamespaceError::Conflict {
258                    new: capability.id,
259                    existing: existing.id.clone(),
260                });
261            }
262            if existing.conflicts.contains(&capability.id) {
263                return Err(NamespaceError::Conflict {
264                    new: capability.id,
265                    existing: existing.id.clone(),
266                });
267            }
268        }
269
270        // Check dependencies
271        for req in &capability.requires {
272            if !self.capabilities.iter().any(|c| &c.id == req) {
273                return Err(NamespaceError::MissingDependency {
274                    capability: capability.id,
275                    required: req.clone(),
276                });
277            }
278        }
279
280        self.capabilities.push(capability);
281        Ok(())
282    }
283
284    /// Try to grant multiple capabilities in dependency order
285    ///
286    /// This is a convenience method that attempts to grant capabilities
287    /// in the order that satisfies dependencies.
288    pub fn grant_all(&mut self, capabilities: Vec<Capability>) -> Result<(), NamespaceError> {
289        // Simple topological sort based on dependencies
290        let mut remaining = capabilities;
291        let mut progress = true;
292
293        while !remaining.is_empty() && progress {
294            progress = false;
295            let mut still_remaining = Vec::new();
296
297            for cap in remaining {
298                let deps_satisfied = cap.requires.iter().all(|dep| self.has_capability(dep));
299
300                if deps_satisfied {
301                    self.grant(cap)?;
302                    progress = true;
303                } else {
304                    still_remaining.push(cap);
305                }
306            }
307
308            remaining = still_remaining;
309        }
310
311        if !remaining.is_empty() {
312            let first = remaining.into_iter().next().unwrap();
313            return Err(NamespaceError::MissingDependency {
314                capability: first.id,
315                required: first
316                    .requires
317                    .into_iter()
318                    .next()
319                    .unwrap_or_else(|| CapabilityId::new("unknown")),
320            });
321        }
322
323        Ok(())
324    }
325
326    /// Revoke a capability from this namespace
327    ///
328    /// # Contract
329    ///
330    /// ```text
331    /// requires: no other capability depends on this one
332    /// ensures: !self.has_capability(id)
333    /// ```
334    ///
335    /// Returns `true` if the capability was revoked, `false` if it couldn't be
336    /// (either not present or has dependents).
337    pub fn revoke(&mut self, id: &CapabilityId) -> bool {
338        if let Some(pos) = self.capabilities.iter().position(|c| &c.id == id) {
339            // Check if any remaining capabilities depend on this one
340            let dependents: Vec<CapabilityId> = self
341                .capabilities
342                .iter()
343                .filter(|c| c.requires.contains(id))
344                .map(|c| c.id.clone())
345                .collect();
346
347            if dependents.is_empty() {
348                self.capabilities.remove(pos);
349                return true;
350            }
351        }
352        false
353    }
354
355    /// Check if this namespace has a specific capability
356    #[must_use]
357    pub fn has_capability(&self, id: &CapabilityId) -> bool {
358        self.capabilities.iter().any(|c| &c.id == id)
359    }
360
361    /// Get all capability IDs
362    #[must_use]
363    pub fn capability_ids(&self) -> Vec<CapabilityId> {
364        self.capabilities.iter().map(|c| c.id.clone()).collect()
365    }
366
367    /// Get the number of capabilities
368    #[must_use]
369    pub fn capability_count(&self) -> usize {
370        self.capabilities.len()
371    }
372
373    /// Count valid configurations satisfying all capability constraints
374    ///
375    /// This computes the intersection number of the position Schubert class
376    /// with all capability Schubert classes.
377    ///
378    /// # Contract
379    ///
380    /// ```text
381    /// ensures:
382    ///   - total_codim > dim(Gr) => Empty
383    ///   - total_codim == dim(Gr) => Finite(n) where n >= 0
384    ///   - total_codim < dim(Gr) => PositiveDimensional
385    /// ```
386    #[must_use]
387    pub fn count_configurations(&self) -> IntersectionResult {
388        let mut calc = SchubertCalculus::new(self.grassmannian);
389
390        let mut classes = vec![self.position.clone()];
391        for cap in &self.capabilities {
392            classes.push(cap.schubert_class.clone());
393        }
394
395        calc.multi_intersect(&classes)
396    }
397
398    /// Total codimension of position plus all capabilities
399    #[must_use]
400    pub fn total_codimension(&self) -> usize {
401        let position_codim: usize = self.position.partition.iter().sum();
402        let cap_codim: usize = self.capabilities.iter().map(|c| c.codimension()).sum();
403        position_codim + cap_codim
404    }
405
406    /// Check if adding a capability would make the system overdetermined
407    #[must_use]
408    pub fn would_overdetermine(&self, capability: &Capability) -> bool {
409        let (k, n) = self.grassmannian;
410        let grassmannian_dim = k * (n - k);
411        let new_total = self.total_codimension() + capability.codimension();
412        new_total > grassmannian_dim
413    }
414}
415
416/// Namespace-related errors
417#[derive(Debug, Clone, PartialEq, Eq)]
418pub enum NamespaceError {
419    /// Two capabilities conflict with each other
420    Conflict {
421        /// The new capability being added
422        new: CapabilityId,
423        /// The existing capability that conflicts
424        existing: CapabilityId,
425    },
426    /// A required dependency is missing
427    MissingDependency {
428        /// The capability being added
429        capability: CapabilityId,
430        /// The required capability that's missing
431        required: CapabilityId,
432    },
433    /// The configuration is invalid
434    InvalidConfiguration,
435}
436
437impl std::fmt::Display for NamespaceError {
438    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439        match self {
440            NamespaceError::Conflict { new, existing } => {
441                write!(f, "Capability {} conflicts with existing {}", new, existing)
442            }
443            NamespaceError::MissingDependency {
444                capability,
445                required,
446            } => {
447                write!(
448                    f,
449                    "Capability {} requires {} which is not present",
450                    capability, required
451                )
452            }
453            NamespaceError::InvalidConfiguration => {
454                write!(f, "Invalid namespace configuration")
455            }
456        }
457    }
458}
459
460impl std::error::Error for NamespaceError {}
461
462/// Compute the intersection of two namespaces
463///
464/// # Contract
465///
466/// ```text
467/// ensures:
468///   - ns1.grassmannian != ns2.grassmannian => Incompatible
469///   - ns1.grassmannian == ns2.grassmannian => result based on Schubert intersection
470/// ```
471pub fn namespace_intersection(
472    ns1: &Namespace,
473    ns2: &Namespace,
474) -> EnumerativeResult<NamespaceIntersection> {
475    if ns1.grassmannian != ns2.grassmannian {
476        return Ok(NamespaceIntersection::Incompatible);
477    }
478
479    let mut calc = SchubertCalculus::new(ns1.grassmannian);
480    let result = calc.multi_intersect(&[ns1.position.clone(), ns2.position.clone()]);
481
482    Ok(match result {
483        IntersectionResult::Empty => NamespaceIntersection::Disjoint,
484        IntersectionResult::Finite(1) => NamespaceIntersection::SinglePoint,
485        IntersectionResult::Finite(n) => NamespaceIntersection::FinitePoints(n),
486        IntersectionResult::PositiveDimensional { dimension, .. } => {
487            NamespaceIntersection::Subspace { dimension }
488        }
489    })
490}
491
492/// Result of namespace intersection
493#[derive(Debug, Clone, PartialEq, Eq, Default)]
494pub enum NamespaceIntersection {
495    /// Namespaces are in different Grassmannians
496    #[default]
497    Incompatible,
498    /// No overlap
499    Disjoint,
500    /// Single point intersection
501    SinglePoint,
502    /// Finite number of intersection points
503    FinitePoints(u64),
504    /// Positive-dimensional intersection
505    Subspace {
506        /// Dimension of the subspace
507        dimension: usize,
508    },
509}
510
511/// Check if a capability is accessible from a namespace
512///
513/// # Contract
514///
515/// ```text
516/// requires: namespace.grassmannian == capability.schubert_class.grassmannian_dim
517/// ensures: result == true iff intersection is non-empty
518/// ```
519pub fn capability_accessible(
520    namespace: &Namespace,
521    capability: &Capability,
522) -> EnumerativeResult<bool> {
523    if namespace.grassmannian != capability.schubert_class.grassmannian_dim {
524        return Ok(false);
525    }
526
527    let mut calc = SchubertCalculus::new(namespace.grassmannian);
528    let result = calc.multi_intersect(&[
529        namespace.position.clone(),
530        capability.schubert_class.clone(),
531    ]);
532
533    Ok(!matches!(result, IntersectionResult::Empty))
534}
535
536/// Builder for creating namespaces with a fluent API
537#[derive(Debug)]
538pub struct NamespaceBuilder {
539    name: String,
540    grassmannian: (usize, usize),
541    position: Vec<usize>,
542    capabilities: Vec<Capability>,
543}
544
545impl NamespaceBuilder {
546    /// Create a new namespace builder
547    #[must_use]
548    pub fn new(name: impl Into<String>, k: usize, n: usize) -> Self {
549        Self {
550            name: name.into(),
551            grassmannian: (k, n),
552            position: vec![],
553            capabilities: vec![],
554        }
555    }
556
557    /// Set the position partition
558    #[must_use]
559    pub fn position(mut self, partition: Vec<usize>) -> Self {
560        self.position = partition;
561        self
562    }
563
564    /// Add a capability
565    #[must_use]
566    pub fn with_capability(mut self, capability: Capability) -> Self {
567        self.capabilities.push(capability);
568        self
569    }
570
571    /// Add multiple capabilities
572    #[must_use]
573    pub fn with_capabilities(mut self, capabilities: impl IntoIterator<Item = Capability>) -> Self {
574        self.capabilities.extend(capabilities);
575        self
576    }
577
578    /// Build the namespace
579    pub fn build(self) -> EnumerativeResult<Namespace> {
580        let position = SchubertClass::new(self.position, self.grassmannian)?;
581        let mut ns = Namespace::new(self.name, position);
582
583        for cap in self.capabilities {
584            ns.grant(cap).map_err(|e| {
585                crate::EnumerativeError::SchubertError(format!("Failed to grant capability: {}", e))
586            })?;
587        }
588
589        Ok(ns)
590    }
591}
592
593// ============================================================================
594// Parallel Batch Operations
595// ============================================================================
596
597/// Count configurations for multiple namespaces in parallel
598#[cfg(feature = "parallel")]
599pub fn count_configurations_batch(namespaces: &[Namespace]) -> Vec<IntersectionResult> {
600    namespaces
601        .par_iter()
602        .map(|ns| ns.count_configurations())
603        .collect()
604}
605
606/// Check capability accessibility for multiple namespace-capability pairs in parallel
607#[cfg(feature = "parallel")]
608pub fn capability_accessible_batch(
609    pairs: &[(&Namespace, &Capability)],
610) -> EnumerativeResult<Vec<bool>> {
611    pairs
612        .par_iter()
613        .map(|(ns, cap)| capability_accessible(ns, cap))
614        .collect()
615}
616
617/// Compute intersections for multiple namespace pairs in parallel
618#[cfg(feature = "parallel")]
619pub fn namespace_intersection_batch(
620    pairs: &[(&Namespace, &Namespace)],
621) -> EnumerativeResult<Vec<NamespaceIntersection>> {
622    pairs
623        .par_iter()
624        .map(|(ns1, ns2)| namespace_intersection(ns1, ns2))
625        .collect()
626}
627
628// ============================================================================
629// Quantum K-Theoretic Capabilities
630// ============================================================================
631
632/// A capability enhanced with quantum K-theory data
633///
634/// While a standard `Capability` uses a Schubert class (cohomological),
635/// a `QuantumCapability` additionally carries a K-theory class representing
636/// the "sheaf of sections" of the capability bundle over the Grassmannian.
637///
638/// This enables:
639/// - **Quantum corrections**: Capabilities that interact through rational curve
640///   contributions (analogous to quantum entanglement in the namespace lattice)
641/// - **Euler characteristic counting**: chi(E) gives a refined count of valid
642///   configurations weighted by sheaf cohomology
643/// - **Adams operations**: psi^k acts on capabilities, modeling "k-fold
644///   amplification" of access rights
645///
646/// # Contract
647///
648/// ```text
649/// invariant: self.capability.schubert_class == classical limit of self.k_class
650/// ```
651#[derive(Debug, Clone)]
652pub struct QuantumCapability<const P: usize, const Q: usize, const R: usize> {
653    /// Classical capability (Schubert class)
654    pub capability: Capability,
655    /// Quantum K-theory class (sheaf data)
656    pub k_class: QuantumKClass<P, Q, R>,
657}
658
659impl<const P: usize, const Q: usize, const R: usize> QuantumCapability<P, Q, R> {
660    /// Create a quantum capability from a classical one
661    ///
662    /// The K-theory class defaults to the structure sheaf of the Schubert variety.
663    #[must_use]
664    pub fn from_classical(capability: Capability) -> Self {
665        let k_class = QuantumKClass::structure_sheaf_point();
666        Self {
667            capability,
668            k_class,
669        }
670    }
671
672    /// Create with explicit K-theory data
673    #[must_use]
674    pub fn new(capability: Capability, k_class: QuantumKClass<P, Q, R>) -> Self {
675        Self {
676            capability,
677            k_class,
678        }
679    }
680
681    /// Quantum product of two capabilities
682    ///
683    /// Returns the entangled capability with quantum corrections
684    /// from rational curves connecting the two Schubert varieties.
685    ///
686    /// # Contract
687    ///
688    /// ```text
689    /// ensures: result.q_power >= self.k_class.q_power + other.k_class.q_power
690    /// ```
691    pub fn quantum_entangle(&self, other: &Self) -> EnumerativeResult<QuantumKClass<P, Q, R>> {
692        self.k_class.quantum_product(&other.k_class)
693    }
694
695    /// Euler characteristic: refined configuration count
696    ///
697    /// While the classical `count_configurations` gives the intersection number,
698    /// this gives chi(E) = integral ch(E) * td(X), which accounts for higher cohomology.
699    ///
700    /// # Contract
701    ///
702    /// ```text
703    /// ensures: result == self.k_class.euler_characteristic(ambient_dimension)
704    /// ```
705    #[must_use]
706    pub fn euler_characteristic(&self, ambient_dimension: usize) -> Rational64 {
707        self.k_class.euler_characteristic(ambient_dimension)
708    }
709
710    /// Adams amplification: psi^k acts on the capability
711    ///
712    /// Models "k-fold amplification" of the access right.
713    /// psi^k preserves the underlying Schubert class but modifies
714    /// the K-theoretic refinement.
715    ///
716    /// # Contract
717    ///
718    /// ```text
719    /// ensures: result.capability == self.capability  (Schubert class unchanged)
720    /// ensures: result.k_class == psi^k(self.k_class)
721    /// ```
722    #[must_use]
723    pub fn amplify(&self, k: i32) -> Self {
724        Self {
725            capability: self.capability.clone(),
726            k_class: self.k_class.adams_operation(k),
727        }
728    }
729}
730
731impl Namespace {
732    /// Count configurations with quantum K-theoretic corrections
733    ///
734    /// This refines `count_configurations` by incorporating quantum
735    /// corrections from rational curves in the Grassmannian.
736    ///
737    /// Returns (classical_count, quantum_euler_characteristic).
738    ///
739    /// # Contract
740    ///
741    /// ```text
742    /// ensures: result.0 == self.count_configurations()
743    /// ```
744    #[must_use]
745    pub fn quantum_count_configurations<const P: usize, const Q: usize, const R: usize>(
746        &self,
747        quantum_caps: &[QuantumCapability<P, Q, R>],
748    ) -> (IntersectionResult, Rational64) {
749        let classical = self.count_configurations();
750
751        // Compute quantum Euler characteristic
752        let (k, n) = self.grassmannian;
753        let ambient_dim = k * (n - k);
754
755        let mut total_euler = Rational64::from(1);
756        for cap in quantum_caps {
757            total_euler *= cap.euler_characteristic(ambient_dim);
758        }
759
760        (classical, total_euler)
761    }
762}
763
764#[cfg(test)]
765mod tests {
766    use super::*;
767
768    #[test]
769    fn test_capability_creation() {
770        // Gr(2, 4): 2-planes in 4-space
771        let cap = Capability::new("read", "Read Access", vec![1], (2, 4)).unwrap();
772        assert_eq!(cap.codimension(), 1);
773        assert_eq!(cap.id, CapabilityId::new("read"));
774    }
775
776    #[test]
777    fn test_capability_id_from() {
778        let id1: CapabilityId = "read".into();
779        let id2: CapabilityId = String::from("write").into();
780        assert_eq!(id1.as_str(), "read");
781        assert_eq!(id2.as_str(), "write");
782    }
783
784    #[test]
785    fn test_namespace_full() {
786        let ns = Namespace::full("test", 2, 4).unwrap();
787        assert_eq!(ns.grassmannian, (2, 4));
788        assert!(ns.position.partition.is_empty());
789    }
790
791    #[test]
792    fn test_namespace_grant() {
793        let mut ns = Namespace::full("test", 2, 4).unwrap();
794        let cap = Capability::new("read", "Read", vec![1], (2, 4)).unwrap();
795
796        assert!(ns.grant(cap).is_ok());
797        assert!(ns.has_capability(&CapabilityId::new("read")));
798    }
799
800    #[test]
801    fn test_capability_conflict() {
802        let mut ns = Namespace::full("test", 2, 4).unwrap();
803
804        let read = Capability::new("read", "Read", vec![1], (2, 4))
805            .unwrap()
806            .conflicts_with(CapabilityId::new("write"));
807        let write = Capability::new("write", "Write", vec![1], (2, 4)).unwrap();
808
809        ns.grant(read).unwrap();
810
811        let result = ns.grant(write);
812        assert!(matches!(result, Err(NamespaceError::Conflict { .. })));
813    }
814
815    #[test]
816    fn test_capability_dependency() {
817        let mut ns = Namespace::full("test", 2, 4).unwrap();
818
819        let write = Capability::new("write", "Write", vec![1], (2, 4))
820            .unwrap()
821            .requires(CapabilityId::new("read"));
822
823        // Try to grant write without read - should fail
824        let result = ns.grant(write.clone());
825        assert!(matches!(
826            result,
827            Err(NamespaceError::MissingDependency { .. })
828        ));
829
830        // Grant read first, then write should succeed
831        let read = Capability::new("read", "Read", vec![1], (2, 4)).unwrap();
832        ns.grant(read).unwrap();
833        assert!(ns.grant(write).is_ok());
834    }
835
836    #[test]
837    fn test_namespace_intersection() {
838        let ns1 = Namespace::full("ns1", 2, 4).unwrap();
839        let ns2 = Namespace::full("ns2", 2, 4).unwrap();
840
841        let result = namespace_intersection(&ns1, &ns2).unwrap();
842        // Two full namespaces should have a positive-dimensional intersection
843        assert!(matches!(
844            result,
845            NamespaceIntersection::Subspace { dimension: 4 }
846        ));
847    }
848
849    #[test]
850    fn test_namespace_incompatible() {
851        let ns1 = Namespace::full("ns1", 2, 4).unwrap();
852        let ns2 = Namespace::full("ns2", 3, 6).unwrap();
853
854        let result = namespace_intersection(&ns1, &ns2).unwrap();
855        assert_eq!(result, NamespaceIntersection::Incompatible);
856    }
857
858    #[test]
859    fn test_count_configurations() {
860        let mut ns = Namespace::full("agent", 2, 4).unwrap();
861
862        // Add 4 capabilities each with codimension 1
863        // This should give us exactly 2 configurations (σ_1^4 in Gr(2,4))
864        for i in 0..4 {
865            let cap = Capability::new(format!("cap{}", i), format!("Cap {}", i), vec![1], (2, 4))
866                .unwrap();
867            ns.grant(cap).unwrap();
868        }
869
870        let count = ns.count_configurations();
871        assert_eq!(count, IntersectionResult::Finite(2));
872    }
873
874    #[test]
875    fn test_namespace_builder() {
876        let read = Capability::new("read", "Read", vec![1], (2, 4)).unwrap();
877
878        let ns = NamespaceBuilder::new("test", 2, 4)
879            .position(vec![])
880            .with_capability(read)
881            .build()
882            .unwrap();
883
884        assert!(ns.has_capability(&CapabilityId::new("read")));
885    }
886
887    #[test]
888    fn test_capability_accessible() {
889        let ns = Namespace::full("test", 2, 4).unwrap();
890        let cap = Capability::new("read", "Read", vec![1], (2, 4)).unwrap();
891
892        assert!(capability_accessible(&ns, &cap).unwrap());
893    }
894
895    #[test]
896    fn test_revoke_capability() {
897        let mut ns = Namespace::full("test", 2, 4).unwrap();
898        let cap = Capability::new("read", "Read", vec![1], (2, 4)).unwrap();
899
900        ns.grant(cap).unwrap();
901        assert!(ns.has_capability(&CapabilityId::new("read")));
902
903        assert!(ns.revoke(&CapabilityId::new("read")));
904        assert!(!ns.has_capability(&CapabilityId::new("read")));
905    }
906
907    #[test]
908    fn test_would_overdetermine() {
909        let mut ns = Namespace::full("test", 2, 4).unwrap();
910
911        // Add 4 capabilities of codimension 1 each
912        for i in 0..4 {
913            let cap = Capability::new(format!("cap{}", i), format!("Cap {}", i), vec![1], (2, 4))
914                .unwrap();
915            ns.grant(cap).unwrap();
916        }
917
918        // Adding another would overdetermine
919        let extra = Capability::new("extra", "Extra", vec![1], (2, 4)).unwrap();
920        assert!(ns.would_overdetermine(&extra));
921    }
922
923    #[test]
924    fn test_grant_all() {
925        let mut ns = Namespace::full("test", 2, 4).unwrap();
926
927        let read = Capability::new("read", "Read", vec![1], (2, 4)).unwrap();
928        let write = Capability::new("write", "Write", vec![1], (2, 4))
929            .unwrap()
930            .requires(CapabilityId::new("read"));
931
932        // Grant in reverse order - should still work
933        ns.grant_all(vec![write, read]).unwrap();
934
935        assert!(ns.has_capability(&CapabilityId::new("read")));
936        assert!(ns.has_capability(&CapabilityId::new("write")));
937    }
938
939    #[test]
940    fn test_namespace_intersection_default() {
941        let default = NamespaceIntersection::default();
942        assert_eq!(default, NamespaceIntersection::Incompatible);
943    }
944
945    // Quantum capability tests
946
947    #[test]
948    fn test_quantum_capability_creation() {
949        let cap = Capability::new("read", "Read", vec![1], (2, 4)).unwrap();
950        let qcap = QuantumCapability::<4, 0, 0>::from_classical(cap);
951        assert_eq!(qcap.capability.codimension(), 1);
952    }
953
954    #[test]
955    fn test_quantum_entanglement() {
956        let cap1 = Capability::new("read", "Read", vec![1], (2, 4)).unwrap();
957        let cap2 = Capability::new("write", "Write", vec![1], (2, 4)).unwrap();
958
959        let qcap1 = QuantumCapability::<4, 0, 0>::from_classical(cap1);
960        let qcap2 = QuantumCapability::<4, 0, 0>::from_classical(cap2);
961
962        let entangled = qcap1.quantum_entangle(&qcap2).unwrap();
963        // Quantum product should produce a valid K-class
964        assert!(entangled.q_power >= 0);
965    }
966
967    #[test]
968    fn test_adams_amplification() {
969        let cap = Capability::new("read", "Read", vec![1], (2, 4)).unwrap();
970        let mut qcap = QuantumCapability::<4, 0, 0>::from_classical(cap);
971        qcap.k_class = QuantumKClass::line_bundle(1);
972
973        let amplified = qcap.amplify(2);
974        // psi^2 on O(1) has c_1 = 2^1 * 1 = 2
975        assert_eq!(
976            *amplified.k_class.chern_character.get(&1).unwrap(),
977            Rational64::from(2)
978        );
979    }
980
981    #[test]
982    fn test_quantum_count_configurations() {
983        let mut ns = Namespace::full("agent", 2, 4).unwrap();
984
985        // Add 4 capabilities each with codimension 1
986        for i in 0..4 {
987            let cap = Capability::new(format!("cap{}", i), format!("Cap {}", i), vec![1], (2, 4))
988                .unwrap();
989            ns.grant(cap).unwrap();
990        }
991
992        // Create quantum capabilities from the granted ones
993        let quantum_caps: Vec<QuantumCapability<4, 0, 0>> = ns
994            .capabilities
995            .iter()
996            .map(|c| QuantumCapability::from_classical(c.clone()))
997            .collect();
998
999        let (classical, _euler) = ns.quantum_count_configurations(&quantum_caps);
1000        // Classical count should still be 2
1001        assert_eq!(classical, IntersectionResult::Finite(2));
1002    }
1003
1004    #[test]
1005    fn test_quantum_count_empty_caps() {
1006        let ns = Namespace::full("agent", 2, 4).unwrap();
1007        let empty_caps: Vec<QuantumCapability<4, 0, 0>> = vec![];
1008        let (classical, euler) = ns.quantum_count_configurations(&empty_caps);
1009        // No capabilities → positive-dimensional (full Grassmannian)
1010        assert!(matches!(
1011            classical,
1012            IntersectionResult::PositiveDimensional { .. }
1013        ));
1014        // Empty product of Euler chars → identity = 1
1015        assert_eq!(euler, Rational64::from(1));
1016    }
1017
1018    #[test]
1019    fn test_amplify_preserves_capability() {
1020        let cap = Capability::new("read", "Read", vec![1], (2, 4)).unwrap();
1021        let mut qcap = QuantumCapability::<4, 0, 0>::from_classical(cap);
1022        qcap.k_class = QuantumKClass::line_bundle(1);
1023
1024        let amplified = qcap.amplify(3);
1025        // Amplification preserves the underlying classical capability
1026        assert_eq!(amplified.capability.id, qcap.capability.id);
1027        assert_eq!(
1028            amplified.capability.codimension(),
1029            qcap.capability.codimension()
1030        );
1031        // psi^3 on O(1): c_1 → 3^1 * 1 = 3
1032        assert_eq!(
1033            *amplified.k_class.chern_character.get(&1).unwrap(),
1034            Rational64::from(3)
1035        );
1036    }
1037
1038    #[test]
1039    fn test_quantum_capability_with_explicit_k_class() {
1040        use crate::geometric_algebra::quantum_k_theory::QuantumKClass;
1041
1042        let cap = Capability::new("exec", "Execute", vec![1, 1], (2, 4)).unwrap();
1043        let k_class = QuantumKClass::<4, 0, 0>::line_bundle(2);
1044        let qcap = QuantumCapability::new(cap, k_class);
1045
1046        assert_eq!(qcap.capability.codimension(), 2);
1047        assert_eq!(
1048            *qcap.k_class.chern_character.get(&1).unwrap(),
1049            Rational64::from(2)
1050        );
1051    }
1052}
1053
1054// ============================================================================
1055// Parallel Batch Operation Tests
1056// ============================================================================
1057
1058#[cfg(all(test, feature = "parallel"))]
1059mod parallel_tests {
1060    use super::*;
1061
1062    #[test]
1063    fn test_count_configurations_batch() {
1064        // Create several namespaces with different capability configurations
1065        let mut ns1 = Namespace::full("ns1", 2, 4).unwrap();
1066        let mut ns2 = Namespace::full("ns2", 2, 4).unwrap();
1067        let ns3 = Namespace::full("ns3", 2, 4).unwrap();
1068
1069        // ns1 gets 4 capabilities -> should give 2 configurations
1070        for i in 0..4 {
1071            ns1.grant(
1072                Capability::new(format!("c{}", i), format!("Cap {}", i), vec![1], (2, 4)).unwrap(),
1073            )
1074            .unwrap();
1075        }
1076
1077        // ns2 gets 2 capabilities -> positive dimensional
1078        for i in 0..2 {
1079            ns2.grant(
1080                Capability::new(format!("d{}", i), format!("Cap {}", i), vec![1], (2, 4)).unwrap(),
1081            )
1082            .unwrap();
1083        }
1084
1085        // ns3 has no capabilities -> full Grassmannian
1086
1087        let namespaces = vec![ns1, ns2, ns3];
1088        let results = count_configurations_batch(&namespaces);
1089
1090        assert_eq!(results.len(), 3);
1091        assert_eq!(results[0], IntersectionResult::Finite(2));
1092        assert!(matches!(
1093            results[1],
1094            IntersectionResult::PositiveDimensional { dimension: 2, .. }
1095        ));
1096        assert!(matches!(
1097            results[2],
1098            IntersectionResult::PositiveDimensional { dimension: 4, .. }
1099        ));
1100    }
1101
1102    #[test]
1103    fn test_capability_accessible_batch() {
1104        let ns1 = Namespace::full("ns1", 2, 4).unwrap();
1105        let ns2 = Namespace::full("ns2", 3, 6).unwrap();
1106
1107        let cap_24 = Capability::new("read", "Read", vec![1], (2, 4)).unwrap();
1108        let cap_36 = Capability::new("write", "Write", vec![1], (3, 6)).unwrap();
1109
1110        let pairs: Vec<(&Namespace, &Capability)> = vec![
1111            (&ns1, &cap_24), // Compatible
1112            (&ns1, &cap_36), // Incompatible Grassmannians
1113            (&ns2, &cap_36), // Compatible
1114            (&ns2, &cap_24), // Incompatible Grassmannians
1115        ];
1116
1117        let results = capability_accessible_batch(&pairs).unwrap();
1118
1119        assert_eq!(results.len(), 4);
1120        assert!(results[0]); // ns1 can access cap_24
1121        assert!(!results[1]); // ns1 cannot access cap_36 (different Grassmannian)
1122        assert!(results[2]); // ns2 can access cap_36
1123        assert!(!results[3]); // ns2 cannot access cap_24 (different Grassmannian)
1124    }
1125
1126    #[test]
1127    fn test_namespace_intersection_batch() {
1128        let ns1 = Namespace::full("ns1", 2, 4).unwrap();
1129        let ns2 = Namespace::full("ns2", 2, 4).unwrap();
1130        let ns3 = Namespace::full("ns3", 3, 6).unwrap();
1131
1132        let pairs: Vec<(&Namespace, &Namespace)> = vec![
1133            (&ns1, &ns2), // Same Grassmannian
1134            (&ns1, &ns3), // Different Grassmannians
1135            (&ns2, &ns3), // Different Grassmannians
1136        ];
1137
1138        let results = namespace_intersection_batch(&pairs).unwrap();
1139
1140        assert_eq!(results.len(), 3);
1141        assert!(matches!(
1142            results[0],
1143            NamespaceIntersection::Subspace { dimension: 4 }
1144        ));
1145        assert_eq!(results[1], NamespaceIntersection::Incompatible);
1146        assert_eq!(results[2], NamespaceIntersection::Incompatible);
1147    }
1148}