Skip to main content

qualia_core_db/inference/
semantic_culler.rs

1//! Semantic Culler - Pre-GPU pipeline for agency-driven data filtering
2//!
3//! This module implements semantic and deontic culling before GPU rendering,
4//! ensuring that unauthorized Quins are mathematically dropped before they
5//! reach the WebGPU staging buffers. It integrates with zk_proofs and
6//! fiduciary_crypto for cryptographic verification of agency permissions.
7
8use crate::fiduciary_crypto::{CryptoContext, FiduciaryCrypto, MlDsaSignature};
9use crate::zk_proofs::{
10    FieldElement, MathematicalStatement, SemanticProof, StatementType, ZkProofSystem,
11};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15/// Semantic culler for agency-driven data filtering
16pub struct SemanticCuller {
17    zk_system: ZkProofSystem,
18    fiduciary_crypto: FiduciaryCrypto,
19    agency_policies: HashMap<String, AgencyPolicy>,
20    culling_stats: CullingStats,
21}
22
23/// Zero-heap verdict for hot-path filtering.
24#[derive(Debug, Clone, Copy, PartialEq)]
25pub struct CullingVerdict {
26    pub quin_hash: u64,
27    pub semantic_id: u64,
28    pub allowed: bool,
29    pub reason: CullingReason,
30    pub proof_valid: u8,
31    pub signature_valid: u8,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum CullingError {
36    OutputBufferFull,
37}
38
39/// Agency policy for data access control
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct AgencyPolicy {
42    pub agency_id: String,
43    pub access_level: AccessLevel,
44    pub semantic_filters: Vec<SemanticFilter>,
45    pub temporal_constraints: TemporalConstraints,
46    pub deontic_rules: Vec<DeonticRule>,
47}
48
49/// Access levels for agency permissions
50#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51pub enum AccessLevel {
52    Read,
53    Write,
54    Admin,
55    System,
56}
57
58/// Semantic filter for content-based filtering
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct SemanticFilter {
61    pub filter_id: String,
62    pub filter_type: FilterType,
63    pub criteria: String,
64    pub required: bool,
65}
66
67/// Filter types for semantic culling
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub enum FilterType {
70    Category,
71    IntensityThreshold,
72    EpistemicLevel,
73    TemporalRange,
74    Custom,
75}
76
77/// Temporal constraints for data access
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct TemporalConstraints {
80    pub valid_from: u64,
81    pub valid_until: u64,
82    pub max_age_seconds: Option<u64>,
83}
84
85/// Deontic rules for permission logic
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct DeonticRule {
88    pub rule_id: String,
89    pub rule_type: DeonticType,
90    pub condition: String,
91    pub action: DeonticAction,
92}
93
94/// Deontic logic types
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
96pub enum DeonticType {
97    Obligation,
98    Permission,
99    Prohibition,
100}
101
102/// Actions for deontic rules
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
104pub enum DeonticAction {
105    Allow,
106    Deny,
107    RequireProof,
108    RequireSignature,
109}
110
111/// Quin data structure for semantic filtering
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct Quin {
114    pub quin_id: String,
115    pub semantic_id: u64,
116    pub intensity: f64,
117    pub epistemic_level: f64,
118    pub timestamp: u64,
119    pub category: String,
120    pub agency_id: Option<String>,
121    pub proof: Option<SemanticProof>,
122    pub signature: Option<MlDsaSignature>,
123}
124
125/// Culling result for a Quin
126#[derive(Debug, Clone)]
127pub struct CullingResult {
128    pub quin_id: String,
129    pub allowed: bool,
130    pub reason: CullingReason,
131    pub verification_data: Option<VerificationData>,
132}
133
134/// Reasons for culling decisions
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum CullingReason {
137    SemanticFilterMatch,
138    TemporalConstraintViolation,
139    DeonticRuleViolation,
140    ProofVerificationFailed,
141    SignatureVerificationFailed,
142    MissingPermission,
143    Allowed,
144}
145
146/// Verification data for cryptographic checks
147#[derive(Debug, Clone)]
148pub struct VerificationData {
149    pub proof_valid: Option<bool>,
150    pub signature_valid: Option<bool>,
151    pub verification_time_ms: u64,
152}
153
154/// Statistics for culling operations
155#[derive(Debug, Clone)]
156pub struct CullingStats {
157    pub total_processed: u64,
158    pub total_allowed: u64,
159    pub total_denied: u64,
160    pub semantic_filtered: u64,
161    pub temporal_filtered: u64,
162    pub deontic_filtered: u64,
163    pub crypto_filtered: u64,
164}
165
166impl SemanticCuller {
167    /// Create new semantic culler
168    pub fn new() -> Self {
169        Self {
170            zk_system: ZkProofSystem::new(),
171            fiduciary_crypto: FiduciaryCrypto::new(),
172            agency_policies: HashMap::new(),
173            culling_stats: CullingStats::default(),
174        }
175    }
176
177    /// Add agency policy
178    pub fn add_policy(&mut self, policy: AgencyPolicy) {
179        self.agency_policies
180            .insert(policy.agency_id.clone(), policy);
181    }
182
183    /// Cull a batch of Quins based on agency policies
184    pub fn cull_quins(&mut self, agency_id: &str, quins: Vec<Quin>) -> Vec<CullingResult> {
185        let policy_ptr = self
186            .agency_policies
187            .get(agency_id)
188            .map(|policy| policy as *const AgencyPolicy);
189
190        let results: Vec<CullingResult> = quins
191            .iter()
192            .map(|quin| {
193                let policy = policy_ptr.map(|ptr| unsafe { &*ptr });
194                self.cull_single_quin(agency_id, quin, policy)
195            })
196            .collect();
197
198        // Update statistics
199        self.update_stats(&results);
200
201        results
202    }
203
204    /// Zero-heap batch culling API for hot-path callers.
205    pub fn cull_quins_into(
206        &mut self,
207        agency_id: &str,
208        quins: &[Quin],
209        out: &mut [CullingVerdict],
210    ) -> Result<usize, CullingError> {
211        let policy_ptr = self
212            .agency_policies
213            .get(agency_id)
214            .map(|policy| policy as *const AgencyPolicy);
215        if out.len() < quins.len() {
216            return Err(CullingError::OutputBufferFull);
217        }
218
219        let mut written = 0usize;
220        for quin in quins {
221            let policy = policy_ptr.map(|ptr| unsafe { &*ptr });
222            let result = self.cull_single_quin(agency_id, quin, policy);
223            out[written] = CullingVerdict {
224                quin_hash: crate::q_hash(&quin.quin_id),
225                semantic_id: quin.semantic_id,
226                allowed: result.allowed,
227                reason: result.reason.clone(),
228                proof_valid: result
229                    .verification_data
230                    .as_ref()
231                    .and_then(|data| data.proof_valid)
232                    .map(bool_to_flag)
233                    .unwrap_or(0xFF),
234                signature_valid: result
235                    .verification_data
236                    .as_ref()
237                    .and_then(|data| data.signature_valid)
238                    .map(bool_to_flag)
239                    .unwrap_or(0xFF),
240            };
241            written += 1;
242        }
243
244        self.update_stats_from_verdicts(&out[..written]);
245        Ok(written)
246    }
247
248    /// Cull a single Quin
249    fn cull_single_quin(
250        &mut self,
251        _agency_id: &str,
252        quin: &Quin,
253        policy: Option<&AgencyPolicy>,
254    ) -> CullingResult {
255        // If no policy exists, deny by default
256        let policy = match policy {
257            Some(p) => p,
258            None => {
259                return CullingResult {
260                    quin_id: quin.quin_id.clone(),
261                    allowed: false,
262                    reason: CullingReason::MissingPermission,
263                    verification_data: None,
264                }
265            }
266        };
267
268        // Check semantic filters
269        if let Some(reason) = self.check_semantic_filters(quin, policy) {
270            return CullingResult {
271                quin_id: quin.quin_id.clone(),
272                allowed: false,
273                reason,
274                verification_data: None,
275            };
276        }
277
278        // Check temporal constraints
279        if let Some(reason) = self.check_temporal_constraints(quin, policy) {
280            return CullingResult {
281                quin_id: quin.quin_id.clone(),
282                allowed: false,
283                reason,
284                verification_data: None,
285            };
286        }
287
288        // Check deontic rules
289        if let Some(reason) = self.check_deontic_rules(quin, policy) {
290            return CullingResult {
291                quin_id: quin.quin_id.clone(),
292                allowed: false,
293                reason,
294                verification_data: None,
295            };
296        }
297
298        // Check cryptographic verification if required
299        if let Some(verification_data) = self.check_cryptographic_verification(quin, policy) {
300            if !verification_data.is_allowed() {
301                let reason = if verification_data.proof_valid == Some(false) {
302                    CullingReason::ProofVerificationFailed
303                } else if verification_data.signature_valid == Some(false) {
304                    CullingReason::SignatureVerificationFailed
305                } else {
306                    CullingReason::DeonticRuleViolation
307                };
308
309                return CullingResult {
310                    quin_id: quin.quin_id.clone(),
311                    allowed: false,
312                    reason,
313                    verification_data: Some(verification_data),
314                };
315            }
316        }
317
318        // All checks passed
319        CullingResult {
320            quin_id: quin.quin_id.clone(),
321            allowed: true,
322            reason: CullingReason::Allowed,
323            verification_data: None,
324        }
325    }
326
327    /// Check semantic filters
328    fn check_semantic_filters(&self, quin: &Quin, policy: &AgencyPolicy) -> Option<CullingReason> {
329        for filter in &policy.semantic_filters {
330            if !filter.required {
331                continue;
332            }
333
334            let matches = match filter.filter_type {
335                FilterType::Category => quin.category == filter.criteria,
336                FilterType::IntensityThreshold => {
337                    let threshold: f64 = filter.criteria.parse().unwrap_or(0.0);
338                    quin.intensity < threshold
339                }
340                FilterType::EpistemicLevel => {
341                    let threshold: f64 = filter.criteria.parse().unwrap_or(0.0);
342                    quin.epistemic_level < threshold
343                }
344                FilterType::TemporalRange => {
345                    // Simplified temporal check
346                    true
347                }
348                FilterType::Custom => {
349                    // Custom filter logic would go here
350                    false
351                }
352            };
353
354            if matches {
355                return Some(CullingReason::SemanticFilterMatch);
356            }
357        }
358
359        None
360    }
361
362    /// Check temporal constraints
363    fn check_temporal_constraints(
364        &self,
365        quin: &Quin,
366        policy: &AgencyPolicy,
367    ) -> Option<CullingReason> {
368        let now = std::time::SystemTime::now()
369            .duration_since(std::time::UNIX_EPOCH)
370            .unwrap()
371            .as_secs();
372
373        // Check valid_from
374        if quin.timestamp < policy.temporal_constraints.valid_from {
375            return Some(CullingReason::TemporalConstraintViolation);
376        }
377
378        // Check valid_until
379        if quin.timestamp > policy.temporal_constraints.valid_until {
380            return Some(CullingReason::TemporalConstraintViolation);
381        }
382
383        // Check max_age
384        if let Some(max_age) = policy.temporal_constraints.max_age_seconds {
385            if now - quin.timestamp > max_age {
386                return Some(CullingReason::TemporalConstraintViolation);
387            }
388        }
389
390        None
391    }
392
393    /// Check deontic rules
394    fn check_deontic_rules(&self, quin: &Quin, policy: &AgencyPolicy) -> Option<CullingReason> {
395        for rule in &policy.deontic_rules {
396            match rule.rule_type {
397                DeonticType::Prohibition => {
398                    if rule.action == DeonticAction::Deny
399                        && self.evaluate_condition(&rule.condition, quin)
400                    {
401                        return Some(CullingReason::DeonticRuleViolation);
402                    }
403                }
404                DeonticType::Obligation => {
405                    if rule.action == DeonticAction::RequireProof && quin.proof.is_none() {
406                        return Some(CullingReason::DeonticRuleViolation);
407                    }
408                    if rule.action == DeonticAction::RequireSignature && quin.signature.is_none() {
409                        return Some(CullingReason::DeonticRuleViolation);
410                    }
411                }
412                DeonticType::Permission => {
413                    // Permission rules are checked in cryptographic verification
414                }
415            }
416        }
417
418        None
419    }
420
421    /// Evaluate deontic condition (simplified)
422    fn evaluate_condition(&self, condition: &str, quin: &Quin) -> bool {
423        // Simplified condition evaluation
424        // Real implementation would parse and evaluate logical expressions
425        condition.contains(&quin.category) || condition.contains(&quin.semantic_id.to_string())
426    }
427
428    /// Check cryptographic verification
429    fn check_cryptographic_verification(
430        &mut self,
431        quin: &Quin,
432        policy: &AgencyPolicy,
433    ) -> Option<VerificationData> {
434        let mut proof_valid = None;
435        let mut signature_valid = None;
436        let start_time = std::time::Instant::now();
437
438        // Check if any deontic rules require cryptographic verification
439        let requires_proof = policy.deontic_rules.iter().any(|r| {
440            r.rule_type == DeonticType::Obligation && r.action == DeonticAction::RequireProof
441        });
442        let requires_signature = policy.deontic_rules.iter().any(|r| {
443            r.rule_type == DeonticType::Obligation && r.action == DeonticAction::RequireSignature
444        });
445
446        if requires_proof {
447            if let Some(ref proof) = quin.proof {
448                let mut proof_copy = proof.clone();
449                match self.zk_system.verify_semantic_proof(&mut proof_copy) {
450                    Ok(_) => proof_valid = Some(true),
451                    Err(_) => proof_valid = Some(false),
452                }
453            } else {
454                proof_valid = Some(false);
455            }
456        }
457
458        if requires_signature {
459            if let Some(ref signature) = quin.signature {
460                let message = format!("{}:{}:{}", quin.quin_id, quin.semantic_id, quin.timestamp);
461                let context = CryptoContext {
462                    domain: "webizen_culling".to_string(),
463                    purpose: "agency_verification".to_string(),
464                    timestamp: 0,
465                    nonce: [0u8; 32],
466                };
467
468                // Use default key for verification
469                match self.fiduciary_crypto.verify(
470                    message.as_bytes(),
471                    signature,
472                    None,
473                    context.domain,
474                    context.purpose,
475                ) {
476                    Ok(valid) => signature_valid = Some(valid),
477                    Err(_) => signature_valid = Some(false),
478                }
479            } else {
480                signature_valid = Some(false);
481            }
482        }
483
484        let verification_time = start_time.elapsed().as_millis() as u64;
485
486        if proof_valid.is_some() || signature_valid.is_some() {
487            Some(VerificationData {
488                proof_valid,
489                signature_valid,
490                verification_time_ms: verification_time,
491            })
492        } else {
493            None
494        }
495    }
496
497    /// Update culling statistics
498    fn update_stats(&mut self, results: &[CullingResult]) {
499        self.culling_stats.total_processed += results.len() as u64;
500
501        for result in results {
502            if result.allowed {
503                self.culling_stats.total_allowed += 1;
504            } else {
505                self.culling_stats.total_denied += 1;
506
507                match result.reason {
508                    CullingReason::SemanticFilterMatch => {
509                        self.culling_stats.semantic_filtered += 1;
510                    }
511                    CullingReason::TemporalConstraintViolation => {
512                        self.culling_stats.temporal_filtered += 1;
513                    }
514                    CullingReason::DeonticRuleViolation => {
515                        self.culling_stats.deontic_filtered += 1;
516                    }
517                    CullingReason::ProofVerificationFailed
518                    | CullingReason::SignatureVerificationFailed => {
519                        self.culling_stats.crypto_filtered += 1;
520                    }
521                    _ => {}
522                }
523            }
524        }
525    }
526
527    fn update_stats_from_verdicts(&mut self, results: &[CullingVerdict]) {
528        self.culling_stats.total_processed += results.len() as u64;
529
530        for result in results {
531            if result.allowed {
532                self.culling_stats.total_allowed += 1;
533            } else {
534                self.culling_stats.total_denied += 1;
535
536                match result.reason {
537                    CullingReason::SemanticFilterMatch => self.culling_stats.semantic_filtered += 1,
538                    CullingReason::TemporalConstraintViolation => {
539                        self.culling_stats.temporal_filtered += 1
540                    }
541                    CullingReason::DeonticRuleViolation => self.culling_stats.deontic_filtered += 1,
542                    CullingReason::ProofVerificationFailed
543                    | CullingReason::SignatureVerificationFailed => {
544                        self.culling_stats.crypto_filtered += 1;
545                    }
546                    _ => {}
547                }
548            }
549        }
550    }
551
552    /// Get culling statistics
553    pub fn get_stats(&self) -> &CullingStats {
554        &self.culling_stats
555    }
556
557    /// Reset culling statistics
558    pub fn reset_stats(&mut self) {
559        self.culling_stats = CullingStats::default();
560    }
561
562    /// Generate a semantic proof for a Quin
563    pub fn generate_proof_for_quin(&mut self, quin: &Quin) -> Result<SemanticProof, String> {
564        let statement = MathematicalStatement {
565            statement_id: quin.quin_id.clone(),
566            statement_type: StatementType::Equality,
567            expression: format!("intensity == {}", quin.intensity),
568            variables: vec!["intensity".to_string()],
569            constraints: vec![],
570        };
571
572        let mut witness = HashMap::new();
573        witness.insert("intensity".to_string(), FieldElement { value: [0u8; 32] });
574
575        self.zk_system
576            .generate_semantic_proof(statement, witness)
577            .map_err(|e| format!("Proof generation failed: {:?}", e))
578    }
579
580    /// Sign a Quin with fiduciary crypto
581    pub fn sign_quin(
582        &mut self,
583        quin: &Quin,
584        key_id: Option<&str>,
585    ) -> Result<MlDsaSignature, String> {
586        let message = format!("{}:{}:{}", quin.quin_id, quin.semantic_id, quin.timestamp);
587
588        self.fiduciary_crypto
589            .sign(
590                message.as_bytes(),
591                key_id,
592                "webizen_quin".to_string(),
593                "agency_signature".to_string(),
594            )
595            .map_err(|e| format!("Signing failed: {:?}", e))
596    }
597}
598
599impl VerificationData {
600    /// Check if verification allows the Quin
601    fn is_allowed(&self) -> bool {
602        match (self.proof_valid, self.signature_valid) {
603            (Some(true), Some(true)) => true,
604            (Some(true), None) => true,
605            (None, Some(true)) => true,
606            (Some(false), _) => false,
607            (_, Some(false)) => false,
608            (None, None) => true,
609        }
610    }
611}
612
613fn bool_to_flag(value: bool) -> u8 {
614    if value {
615        1
616    } else {
617        0
618    }
619}
620
621impl Default for CullingStats {
622    fn default() -> Self {
623        Self {
624            total_processed: 0,
625            total_allowed: 0,
626            total_denied: 0,
627            semantic_filtered: 0,
628            temporal_filtered: 0,
629            deontic_filtered: 0,
630            crypto_filtered: 0,
631        }
632    }
633}
634
635#[cfg(test)]
636mod tests {
637    use super::*;
638
639    #[test]
640    fn test_semantic_culler_creation() {
641        let culler = SemanticCuller::new();
642        assert_eq!(culler.get_stats().total_processed, 0);
643    }
644
645    #[test]
646    fn test_agency_policy() {
647        let policy = AgencyPolicy {
648            agency_id: "test_agency".to_string(),
649            access_level: AccessLevel::Read,
650            semantic_filters: vec![],
651            temporal_constraints: TemporalConstraints {
652                valid_from: 0,
653                valid_until: u64::MAX,
654                max_age_seconds: None,
655            },
656            deontic_rules: vec![],
657        };
658
659        let mut culler = SemanticCuller::new();
660        culler.add_policy(policy);
661
662        assert_eq!(culler.agency_policies.len(), 1);
663    }
664
665    #[test]
666    fn test_quin_culling() {
667        let mut culler = SemanticCuller::new();
668
669        let policy = AgencyPolicy {
670            agency_id: "test_agency".to_string(),
671            access_level: AccessLevel::Read,
672            semantic_filters: vec![],
673            temporal_constraints: TemporalConstraints {
674                valid_from: 0,
675                valid_until: u64::MAX,
676                max_age_seconds: None,
677            },
678            deontic_rules: vec![],
679        };
680        culler.add_policy(policy);
681
682        let quin = Quin {
683            quin_id: "test_quin".to_string(),
684            semantic_id: 123,
685            intensity: 0.5,
686            epistemic_level: 0.9,
687            timestamp: std::time::SystemTime::now()
688                .duration_since(std::time::UNIX_EPOCH)
689                .unwrap()
690                .as_secs(),
691            category: "test".to_string(),
692            agency_id: None,
693            proof: None,
694            signature: None,
695        };
696
697        let results = culler.cull_quins("test_agency", vec![quin]);
698        assert_eq!(results.len(), 1);
699        assert!(results[0].allowed);
700    }
701}