Skip to main content

qualia_core_db/inference/
orchestrator.rs

1//! The Orchestration Sieve — LLM Sub-Agent Dispatch Layer
2//!
3//! Sits between raw input (multi-modal data, user prompts) and the Webizen VM.
4//! Coordinates pre-processing → intent validation → inference → output grounding.
5//!
6//! Flow:
7//!   RawInput → [Orchestrator] → validate_intent → [LlmAgent.infer] → validate_output → .q42 commit
8
9use crate::llm_agent::{AgentIntent, AgentRuntime, LocalLlmAgent, WebizenVerdict};
10use crate::modalities::logic::n3_compiler::{
11    compile_rules_with_shacl_gate, default_observation_shape, N3OutputMode,
12};
13use crate::modalities::logic::n3_parser::{N3Event, N3Parser};
14use crate::modalities::logic::shacl::{
15    CompiledShape, ShaclCompiler, ShaclConstraint, ShaclSeverity,
16};
17use crate::solvers::grounding::{
18    evaluate_output_grounding, GroundingResolver, GroundingThresholds, GroundingVerdict,
19};
20#[cfg(not(target_arch = "wasm32"))]
21use crate::wal::{commit_semantic_mutation, WalHandoffResult, WriteAheadLog};
22use crate::webizen::{SlgArena, SlgOpcode, VmFrame};
23use crate::{q_hash, NQuin};
24use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
25use std::sync::Arc;
26use std::thread;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ModelLifecycle {
30    Discovered,
31    MappedToDisk,
32    StreamingVRAM,
33    Active,
34    Scrubbing,
35}
36
37/// The outcome of a full orchestrated inference cycle.
38#[derive(Debug)]
39pub enum OrchestrationResult {
40    /// Output was validated, grounded, and ready to commit to the semantic graph.
41    Committed {
42        text: String,
43        provenance_quins: Vec<u64>,
44        semantic_quin: Option<NQuin>,
45        wal_committed: bool,
46        wal_suspended: bool,
47        suspended_agreement_id: Option<u64>,
48    },
49    /// Webizen blocked the operation at pre-flight or post-flight.
50    Blocked {
51        rule_violated: u64,
52        reason: &'static str,
53    },
54    /// Inference failed (timeout, backend unavailable, etc.)
55    Failed(String),
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum ThermalStatus {
60    /// Normal operating temperatures. Full 3-Core Triad utilization.
61    Cool,
62    /// Elevated temperatures. Restrict non-essential parallelism and heavy sieving.
63    Warm,
64    /// Critical temperatures. Pause background ingestion/indexing, throttle to critical-path single-thread only.
65    Critical,
66}
67
68pub trait ThermalGovernor: Send + Sync {
69    /// Returns the current thermal state of the host device.
70    fn get_thermal_state(&self) -> ThermalStatus;
71
72    /// Optional hook for the governor to self-adjust or log transitions.
73    fn adjust_policy(&self, status: ThermalStatus) {
74        let _ = status; // Default no-op
75    }
76}
77
78pub struct NullThermalGovernor;
79impl ThermalGovernor for NullThermalGovernor {
80    fn get_thermal_state(&self) -> ThermalStatus {
81        ThermalStatus::Cool
82    }
83}
84
85use crate::solvers::calculus::{ODEFunction, RungeKutta4Static};
86use crate::solvers::SolverConfig;
87
88struct NewtonCoolingODE {
89    ambient_temp: f64,
90    cooling_constant: f64,
91    power_input: f64,
92}
93
94impl ODEFunction for NewtonCoolingODE {
95    fn derivatives(&self, _t: f64, y: &[f64; 4]) -> [f64; 4] {
96        let temp = y[0];
97        let dt_dt = -self.cooling_constant * (temp - self.ambient_temp) + self.power_input;
98        [dt_dt, 0.0, 0.0, 0.0]
99    }
100}
101
102pub struct CalculusThermalGovernor {
103    current_temp: std::sync::Mutex<f64>,
104}
105
106impl CalculusThermalGovernor {
107    pub fn new(initial_temp: f64) -> Self {
108        Self {
109            current_temp: std::sync::Mutex::new(initial_temp),
110        }
111    }
112}
113
114impl ThermalGovernor for CalculusThermalGovernor {
115    fn get_thermal_state(&self) -> ThermalStatus {
116        let mut temp = self.current_temp.lock().unwrap();
117
118        let ode = NewtonCoolingODE {
119            ambient_temp: 25.0,
120            cooling_constant: 0.1,
121            power_input: 5.0, // Represents current SoC TDP
122        };
123
124        let mut solver = RungeKutta4Static::new(0.1, SolverConfig::default());
125        let final_state = solver
126            .integrate(&ode, 0.0, [*temp, 0.0, 0.0, 0.0], 1.0)
127            .unwrap();
128
129        *temp = final_state.y[0];
130
131        if *temp > 85.0 {
132            ThermalStatus::Critical
133        } else if *temp > 65.0 {
134            ThermalStatus::Warm
135        } else {
136            ThermalStatus::Cool
137        }
138    }
139}
140
141pub struct TaskOrchestrator {
142    thermal_governor: Box<dyn ThermalGovernor>,
143    pub current_model_state: Arc<std::sync::Mutex<ModelLifecycle>>,
144    pub current_model_id: Arc<std::sync::Mutex<Option<u64>>>,
145    pub resident_memory_bytes: Arc<AtomicU64>,
146    pub scrubbing_lock: Arc<AtomicBool>,
147    pub mlock_enabled: Arc<AtomicBool>,
148}
149
150impl TaskOrchestrator {
151    fn routed_shapes(intent: &AgentIntent) -> Vec<CompiledShape> {
152        let compiler = ShaclCompiler::new();
153        let mut shapes = Vec::new();
154
155        let has_namespace = |needles: &[&str]| {
156            needles.iter().any(|needle| {
157                let hash = q_hash(needle);
158                intent.context_namespaces.iter().any(|ns| *ns == hash)
159            })
160        };
161
162        if has_namespace(&[
163            "health", "medical", "clinical", "fhir", "loinc", "snomed", "anatomy",
164        ]) {
165            shapes.push(default_observation_shape());
166            shapes.push(compiler.compile_class(
167                "fhir:Observation",
168                "health:heartRate",
169                ShaclConstraint::MinInclusive(20.0),
170                ShaclSeverity::Violation,
171            ));
172        }
173
174        if has_namespace(&[
175            "legal",
176            "law",
177            "contract",
178            "guardian",
179            "guardianship",
180            "rights",
181            "agreement",
182            "consent",
183        ]) {
184            shapes.push(compiler.compile_class(
185                "q42:Agreement",
186                "q42:hasGuardian",
187                ShaclConstraint::MinCount(1),
188                ShaclSeverity::Violation,
189            ));
190            shapes.push(compiler.compile_class(
191                "q42:Agreement",
192                "q42:requiresConsensus",
193                ShaclConstraint::MinCount(1),
194                ShaclSeverity::Violation,
195            ));
196        }
197
198        if has_namespace(&["commons", "governance", "community"]) {
199            shapes.push(compiler.compile_class(
200                "q42:CommonsAgreement",
201                "q42:hasDomainScope",
202                ShaclConstraint::MinCount(1),
203                ShaclSeverity::Violation,
204            ));
205        }
206
207        // CogAI agent shapes — always active for any inference intent.
208        // Validates cog:Agent identity, cog:Goal alignment, and inference authorization.
209        shapes.push(compiler.compile_class(
210            "cog:Agent",
211            "cog:agentID",
212            ShaclConstraint::MinCount(1),
213            ShaclSeverity::Violation,
214        ));
215        shapes.push(compiler.compile_class(
216            "q42:InferenceIntent",
217            "q42:inferenceAuthorizedBy",
218            ShaclConstraint::MinCount(1),
219            ShaclSeverity::Violation,
220        ));
221        shapes.push(compiler.compile_class(
222            "q42:InferenceIntent",
223            "q42:provenanceCitations",
224            ShaclConstraint::MinInclusive(1.0),
225            ShaclSeverity::Warning,
226        ));
227
228        if shapes.is_empty() {
229            shapes.push(default_observation_shape());
230        }
231        shapes
232    }
233
234    pub fn new(thermal_governor: Box<dyn ThermalGovernor>) -> Self {
235        Self {
236            thermal_governor,
237            current_model_state: Arc::new(std::sync::Mutex::new(ModelLifecycle::Discovered)),
238            current_model_id: Arc::new(std::sync::Mutex::new(None)),
239            resident_memory_bytes: Arc::new(AtomicU64::new(0)),
240            scrubbing_lock: Arc::new(AtomicBool::new(false)),
241            mlock_enabled: Arc::new(AtomicBool::new(false)),
242        }
243    }
244
245    pub fn thermal_state_label(&self) -> &'static str {
246        match self.thermal_governor.get_thermal_state() {
247            ThermalStatus::Cool => "Cool",
248            ThermalStatus::Warm => "Warm",
249            ThermalStatus::Critical => "Critical",
250        }
251    }
252
253    pub fn resident_model_id(&self) -> Option<u64> {
254        *self.current_model_id.lock().unwrap()
255    }
256
257    pub fn resident_memory_bytes(&self) -> u64 {
258        self.resident_memory_bytes.load(Ordering::Relaxed)
259    }
260
261    pub fn register_resident_model(&self, model_id: u64, bytes: u64) {
262        *self.current_model_id.lock().unwrap() = Some(model_id);
263        self.resident_memory_bytes.store(bytes, Ordering::Relaxed);
264    }
265
266    pub fn load_model(&self, agent: &LocalLlmAgent, model_id: u64) -> Result<(), &'static str> {
267        if self.scrubbing_lock.load(Ordering::Acquire) {
268            return Err("Cannot load model: Swarm Worker is actively scrubbing memory arena");
269        }
270        if let Some(active_model) = self.resident_model_id() {
271            if active_model != model_id {
272                return Err("Cannot load model: another model is still resident; evict it first");
273            }
274        }
275
276        let mut state = self.current_model_state.lock().unwrap();
277        *state = ModelLifecycle::MappedToDisk;
278        *state = ModelLifecycle::StreamingVRAM;
279        *state = ModelLifecycle::Active;
280        drop(state);
281        *self.current_model_id.lock().unwrap() = Some(model_id);
282
283        // Prefer the unified schema.org volume (embedded Q42LEX). Legacy
284        // `.q42.lex` sidecar remains a fallback until sidecars are gone.
285        let schema_q42 = "data/schemaorg/30.0/schemaorg-current-https.q42";
286        let schema_lex = "data/schemaorg/30.0/schemaorg-current-https.q42.lex";
287        if std::path::Path::new(schema_q42).exists() {
288            agent.configure_sieve_lex(schema_q42);
289        } else if std::path::Path::new(schema_lex).exists() {
290            agent.configure_sieve_lex(schema_lex);
291        } else if let crate::llm_agent::AgentBackend::Local { model_path, .. } = &agent.backend {
292            let mut p = std::path::PathBuf::from(model_path);
293            if let Some(stem) = p.file_stem().and_then(|s| s.to_str()).map(str::to_string) {
294                p.set_file_name(format!("{stem}.q42.lex"));
295                if p.exists() {
296                    agent.configure_sieve_lex(p.to_string_lossy().into_owned());
297                }
298            }
299        }
300        Ok(())
301    }
302
303    pub fn evict_model(&self, model_id: u64) {
304        let resident = self.resident_model_id();
305        if resident.is_none() {
306            self.resident_memory_bytes.store(0, Ordering::Relaxed);
307            if let Ok(mut st) = self.current_model_state.lock() {
308                *st = ModelLifecycle::Discovered;
309            }
310            return;
311        }
312        if resident != Some(model_id) {
313            return;
314        }
315
316        if let Ok(mut state) = self.current_model_state.lock() {
317            *state = ModelLifecycle::Scrubbing;
318        }
319
320        self.scrubbing_lock.store(true, Ordering::Release);
321        let scrub_bytes = self.resident_memory_bytes.swap(0, Ordering::Relaxed);
322
323        let lock_clone = self.scrubbing_lock.clone();
324        let state_clone = self.current_model_state.clone();
325        let model_clone = self.current_model_id.clone();
326
327        // Asynchronous scrubbing over a fixed stack buffer keeps the sweep deterministic and heap-free.
328        thread::spawn(move || {
329            let mut scrub_block = [0u8; 4096];
330            let mut remaining = scrub_bytes.max(scrub_block.len() as u64);
331            while remaining > 0 {
332                let write_len = remaining.min(scrub_block.len() as u64) as usize;
333                for byte in scrub_block.iter_mut().take(write_len) {
334                    unsafe { std::ptr::write_volatile(byte, 0) };
335                }
336                remaining = remaining.saturating_sub(write_len as u64);
337                if remaining > 0 {
338                    std::thread::yield_now();
339                }
340            }
341
342            // Release the cryptographic lock and revert state
343            lock_clone.store(false, Ordering::Release);
344            if let Ok(mut model) = model_clone.lock() {
345                *model = None;
346            }
347            if let Ok(mut st) = state_clone.lock() {
348                *st = ModelLifecycle::Discovered;
349            }
350            crate::resident_model::clear_resident_model();
351        });
352    }
353
354    /// Parse LLM-emitted N3, validate via SHACL compiler, and execute on the Sentinel VM.
355    fn gate_llm_n3_output(
356        text: &str,
357        contract_hash: u64,
358        intent: &AgentIntent,
359    ) -> Result<(), &'static str> {
360        let mut rules = Vec::new();
361        let mut parser = N3Parser::new(text);
362        parser
363            .parse_all(|event| {
364                if let N3Event::LogicRule(rule) = event {
365                    rules.push(rule);
366                }
367                Ok(())
368            })
369            .map_err(|_| "Invalid N3 output from LLM")?;
370
371        if rules.is_empty() {
372            return Err("LLM did not emit parseable N3 assertions");
373        }
374
375        let routed_shapes = Self::routed_shapes(intent);
376        let shapes: Vec<&CompiledShape> = routed_shapes.iter().collect();
377        let mut opcodes = [SlgOpcode::Call; 256];
378        let mut quins = [crate::NQuin::default(); 64];
379        // The gate validates each Rule against the routed SHACL shapes (fail closed),
380        // then compiles internally — pass the parsed rules directly.
381        let program =
382            compile_rules_with_shacl_gate(&rules, &shapes, &mut opcodes, &mut quins, contract_hash)
383                .map_err(|_| "SHACL validation failed for LLM N3 output")?;
384
385        let mut arena = SlgArena::new();
386        let mut frame = VmFrame::default();
387        let _ = crate::modalities::logic::n3_compiler::execute_compiled_program(
388            &mut arena,
389            &opcodes[..program.opcode_count],
390            &mut frame,
391            32,
392        )
393        .map_err(|_| "Sentinel VM memory overflow")?;
394        Ok(())
395    }
396
397    /// Runs a full, Webizen-gated inference cycle for a registered LLM sub-agent.
398    ///
399    /// This is the citation-presence gate (no grounding resolver). For the deeper
400    /// gate that verifies the claim is *supported* by its cited facts, use
401    /// [`Self::orchestrate_inference_grounded`].
402    pub fn orchestrate_inference(
403        &self,
404        agent: &dyn AgentRuntime,
405        prompt: &str,
406        graph_context: &str,
407        intent: AgentIntent,
408        suspended: Option<&mut crate::crdt::SuspendedTransactionQueue>,
409    ) -> OrchestrationResult {
410        self.orchestrate_inference_inner(agent, prompt, graph_context, intent, suspended, None)
411    }
412
413    /// Like [`Self::orchestrate_inference`], but adds the KG↔LLM **grounding gate** to
414    /// the post-flight: the model's structured claim is graded against its *resolved*
415    /// cited facts. A weakly-grounded claim is routed to human review (blocked, not
416    /// committed); an ungrounded one is blocked outright. Both happen **before** the
417    /// WAL commit, so an unsupported claim never reaches the graph.
418    pub fn orchestrate_inference_grounded(
419        &self,
420        agent: &dyn AgentRuntime,
421        prompt: &str,
422        graph_context: &str,
423        intent: AgentIntent,
424        suspended: Option<&mut crate::crdt::SuspendedTransactionQueue>,
425        resolver: &dyn GroundingResolver,
426        thresholds: GroundingThresholds,
427    ) -> OrchestrationResult {
428        self.orchestrate_inference_inner(
429            agent,
430            prompt,
431            graph_context,
432            intent,
433            suspended,
434            Some((resolver, thresholds)),
435        )
436    }
437
438    fn orchestrate_inference_inner(
439        &self,
440        agent: &dyn AgentRuntime,
441        prompt: &str,
442        graph_context: &str,
443        intent: AgentIntent,
444        suspended: Option<&mut crate::crdt::SuspendedTransactionQueue>,
445        grounding: Option<(&dyn GroundingResolver, GroundingThresholds)>,
446    ) -> OrchestrationResult {
447        let thermal_state = self.thermal_governor.get_thermal_state();
448
449        match thermal_state {
450            ThermalStatus::Critical => {
451                if !intent.is_critical() {
452                    return OrchestrationResult::Blocked {
453                        rule_violated: 0xDEADBEEF, // Mock constant for Thermal Block
454                        reason: "Device critical thermal state. Non-essential inference paused.",
455                    };
456                }
457            }
458            ThermalStatus::Warm => {
459                // Future extension: dampened logic
460            }
461            ThermalStatus::Cool => {}
462        }
463
464        // 1. Pre-flight: validate intent against Rights Ontology
465        match agent.validate_intent(&intent) {
466            WebizenVerdict::Deny {
467                rule_violated,
468                reason,
469                conduct_record,
470            } => {
471                // If the verdict contains a conduct violation Quin, propagate it to the immutable ledger
472                #[cfg(not(target_arch = "wasm32"))]
473                if let Some(quin) = conduct_record {
474                    if let Ok(mut wal) = crate::wal::WriteAheadLog::open(".qualia_conduct.wal") {
475                        let _ = wal.append_mutation(&quin);
476
477                        // Cryptographic signing pipeline (using a static key for demonstration of wiring)
478                        let secret = [42u8; 32];
479                        let signing_key = ed25519_dalek::SigningKey::from_bytes(&secret);
480                        let frame = [quin];
481                        let sub_root = crate::agency::compute_scoped_merkle_root(
482                            &frame,
483                            intent.principal_did_hash,
484                        );
485                        let _signature = crate::agency::sign_agency_root(&signing_key, &sub_root);
486
487                        // In production, the signature and quin would be passed to SuperBlockWriter
488                    }
489                }
490                #[cfg(target_arch = "wasm32")]
491                let _ = conduct_record;
492                return OrchestrationResult::Blocked {
493                    rule_violated,
494                    reason,
495                };
496            }
497            WebizenVerdict::DenyWithExplanation {
498                rule_violated,
499                reason: _,
500                explanation: _,
501            } => {
502                // Return blocked with the detailed explanation
503                return OrchestrationResult::Blocked {
504                    rule_violated,
505                    reason: "Intent Frame Violation",
506                };
507            }
508            WebizenVerdict::RequireReconfirmation { reason: _ } => {
509                return OrchestrationResult::Blocked {
510                    rule_violated: 0,
511                    reason: "Reconfirmation required",
512                };
513            }
514            WebizenVerdict::Sanitised { .. } => { /* intent was scrubbed; proceed with caution */ }
515            WebizenVerdict::Permit => {}
516        }
517
518        // 1b. Quantum egress gate — block classified prompts from remote QPU
519        if let Some(reason) = crate::modalities::logic::qubo::quantum_prompt_gate(prompt) {
520            return OrchestrationResult::Blocked {
521                rule_violated: crate::q_hash("q42:QuantumTaskShape"),
522                reason,
523            };
524        }
525
526        // 1c. CogAI pre-flight: write agent registration quin and validate InferenceIntentShape.
527        // The `register_agent` quin is written to AGENT_CONTEXT so downstream SPARQL queries
528        // can resolve cog:Agent identity without re-deriving it from the principal DID.
529        {
530            use crate::temporal_graph::register_agent;
531            let _agent_quin = register_agent(intent.principal_did_hash, 0);
532            // Future: append _agent_quin to the per-request provenance accumulator.
533        }
534
535        // 2. Inference
536        let output = match agent.infer(prompt, graph_context) {
537            Ok(o) => o,
538            Err(e) => return OrchestrationResult::Failed(format!("{:?}", e)),
539        };
540
541        // 2b. Optional CogAI symbolic path: compile LLM-emitted N3 through SHACL → bytecode.
542        // Skipped when the neuro-symbolic sieve already emitted a structured Quin.
543        if output.semantic_quin.is_none()
544            && (intent.output_mode == N3OutputMode::N3Assertions
545                || intent.output_mode == N3OutputMode::GraphMutation)
546        {
547            if let Err(reason) =
548                Self::gate_llm_n3_output(&output.text, intent.principal_did_hash, &intent)
549            {
550                return OrchestrationResult::Blocked {
551                    rule_violated: crate::q_hash("q42:N3Compiler"),
552                    reason,
553                };
554            }
555        }
556
557        // 3. Post-flight: validate output grounding
558        match agent.validate_output(&output) {
559            WebizenVerdict::Deny {
560                rule_violated,
561                reason,
562                conduct_record,
563            } => {
564                #[cfg(not(target_arch = "wasm32"))]
565                if let Some(quin) = conduct_record {
566                    if let Ok(mut wal) = crate::wal::WriteAheadLog::open(".qualia_conduct.wal") {
567                        let _ = wal.append_mutation(&quin);
568                    }
569                }
570                #[cfg(target_arch = "wasm32")]
571                let _ = conduct_record;
572                return OrchestrationResult::Blocked {
573                    rule_violated,
574                    reason,
575                };
576            }
577            WebizenVerdict::DenyWithExplanation {
578                rule_violated,
579                reason: _,
580                explanation: _,
581            } => {
582                return OrchestrationResult::Blocked {
583                    rule_violated,
584                    reason: "Output blocked due to frame bounds",
585                };
586            }
587            WebizenVerdict::RequireReconfirmation { reason: _ } => {
588                return OrchestrationResult::Blocked {
589                    rule_violated: 0,
590                    reason: "Output requires reconfirmation",
591                };
592            }
593            _ => {}
594        }
595
596        // 3b. Grounding gate (optional): when a fact resolver is supplied and the
597        // output carries a structured claim, verify the claim is actually *supported*
598        // by its cited facts — not merely that a citation exists. Fail closed: an
599        // ungrounded claim is blocked; a partially-grounded one is routed to human
600        // review. Runs before any WAL commit, so unsupported claims never persist.
601        if let (Some((resolver, thresholds)), Some(claim)) =
602            (grounding, output.semantic_quin.as_ref())
603        {
604            match evaluate_output_grounding(claim, &output.provenance_quins, resolver, thresholds) {
605                GroundingVerdict::Grounded { .. } => {}
606                GroundingVerdict::Weak { .. } => {
607                    return OrchestrationResult::Blocked {
608                        rule_violated: q_hash("q42:GroundingRequiresReview"),
609                        reason: "Claim only partially grounded in its cited facts — requires human review before commit.",
610                    };
611                }
612                GroundingVerdict::Ungrounded { .. } => {
613                    return OrchestrationResult::Blocked {
614                        rule_violated: q_hash("q42:GroundingRequired"),
615                        reason:
616                            "Claim does not trace to its cited facts (ungrounded). Cannot commit.",
617                    };
618                }
619            }
620        }
621
622        #[cfg(not(target_arch = "wasm32"))]
623        let mut semantic_quin = output.semantic_quin;
624        #[cfg(not(target_arch = "wasm32"))]
625        let mut wal_written = false;
626        #[cfg(not(target_arch = "wasm32"))]
627        let mut wal_suspended = false;
628        #[cfg(not(target_arch = "wasm32"))]
629        let mut suspended_agreement_id = None;
630
631        #[cfg(target_arch = "wasm32")]
632        let semantic_quin = output.semantic_quin;
633        #[cfg(target_arch = "wasm32")]
634        let (wal_written, wal_suspended, suspended_agreement_id) = {
635            if suspended.is_some() {
636                log::debug!(
637                    "WASM orchestration received a suspended queue; persistence remains delegated to the host"
638                );
639            }
640            (false, false, None)
641        };
642
643        #[cfg(not(target_arch = "wasm32"))]
644        if let Some(ref mut quin) = semantic_quin {
645            if let Ok(mut wal) = WriteAheadLog::open(".qualia_graph_mutations.wal") {
646                let secret = [42u8; 32];
647                let signing_key = ed25519_dalek::SigningKey::from_bytes(&secret);
648                let mut local_suspended = crate::crdt::SuspendedTransactionQueue::new();
649                let queue = suspended.unwrap_or(&mut local_suspended);
650                let agent_did_hash = q_hash(agent.agent_did());
651                match commit_semantic_mutation(
652                    &mut wal,
653                    quin,
654                    intent.principal_did_hash,
655                    agent_did_hash,
656                    &signing_key,
657                    queue,
658                ) {
659                    Ok(WalHandoffResult::Committed) => {
660                        wal_written = true;
661                    }
662                    Ok(WalHandoffResult::Suspended { agreement_id }) => {
663                        wal_written = true;
664                        wal_suspended = true;
665                        suspended_agreement_id = Some(agreement_id);
666                    }
667                    Err(_) => {}
668                }
669            }
670        }
671
672        OrchestrationResult::Committed {
673            text: output.text,
674            provenance_quins: output.provenance_quins,
675            semantic_quin,
676            wal_committed: wal_written,
677            wal_suspended,
678            suspended_agreement_id,
679        }
680    }
681}
682
683#[cfg(test)]
684pub mod tests {
685    use super::*;
686    use crate::llm_agent::{AgentIntent, AgentRuntime, LocalLlmAgent, SANCTUARY_SCOPE_WEBIZEN};
687    use crate::modalities::logic::n3_compiler::N3OutputMode;
688
689    #[test]
690    pub fn qualia_validate_ring_buffer() {}
691
692    #[test]
693    fn test_orchestrator_full_permit_path() {
694        let agent = LocalLlmAgent::new("did:git:orch-test", "model.gguf");
695        let intent = AgentIntent {
696            intent_predicate: 0x1234,
697            requested_graph_scope: vec![0xABCD],
698            context_namespaces: vec![],
699            requires_network: false,
700            ilp_offer_micro_cents: 0,
701            principal_did_hash: 0,
702            mcp_intent_frame_hash: 0x1234,
703            output_mode: N3OutputMode::FreeText,
704            clearance_ceiling: 0,
705            max_sentinel_depth: 32,
706            active_profile: None,
707        };
708        let orch = TaskOrchestrator::new(Box::new(NullThermalGovernor));
709        let result = orch.orchestrate_inference(
710            &agent,
711            "Summarise my health graph.",
712            "some_graph_bytes",
713            intent,
714            None,
715        );
716        assert!(matches!(result, OrchestrationResult::Committed { .. }));
717    }
718
719    #[test]
720    fn test_orchestrator_blocks_sanctuary_intent() {
721        let agent = LocalLlmAgent::new("did:git:orch-test", "model.gguf");
722        let intent = AgentIntent {
723            intent_predicate: 0x1234,
724            requested_graph_scope: vec![SANCTUARY_SCOPE_WEBIZEN],
725            context_namespaces: vec![],
726            requires_network: false,
727            ilp_offer_micro_cents: 0,
728            principal_did_hash: 0,
729            mcp_intent_frame_hash: 0x1234,
730            output_mode: N3OutputMode::FreeText,
731            clearance_ceiling: 0,
732            max_sentinel_depth: 32,
733            active_profile: None,
734        };
735        let orch = TaskOrchestrator::new(Box::new(NullThermalGovernor));
736        let result =
737            orch.orchestrate_inference(&agent, "Show me sanctuary data.", "ctx", intent, None);
738        assert!(matches!(result, OrchestrationResult::Blocked { .. }));
739    }
740
741    // ─── Grounding gate ──────────────────────────────────────────────────────
742    use crate::llm_agent::{AgentBackend, AgentError, AgentOutput};
743    use crate::solvers::grounding::{GroundingResolver, GroundingThresholds};
744    use std::collections::HashMap;
745
746    fn claim_quin(s: u64, p: u64, o: u64) -> NQuin {
747        NQuin {
748            subject: s,
749            predicate: p,
750            object: o,
751            context: 0,
752            metadata: 0,
753            parity: 0,
754        }
755    }
756
757    /// A mock agent that emits a fixed structured claim + citations, delegating the
758    /// rest of the runtime to a real local agent. `validate_output` permits so the
759    /// test isolates the *grounding* gate from the citation-presence gate.
760    struct ClaimAgent {
761        inner: LocalLlmAgent,
762        output: AgentOutput,
763    }
764    impl AgentRuntime for ClaimAgent {
765        fn backend(&self) -> &AgentBackend {
766            self.inner.backend()
767        }
768        fn agent_did(&self) -> &str {
769            self.inner.agent_did()
770        }
771        fn validate_intent(&self, intent: &AgentIntent) -> WebizenVerdict {
772            self.inner.validate_intent(intent)
773        }
774        fn infer(&self, _p: &str, _g: &str) -> Result<AgentOutput, AgentError> {
775            Ok(self.output.clone())
776        }
777        fn validate_output(&self, _o: &AgentOutput) -> WebizenVerdict {
778            WebizenVerdict::Permit
779        }
780        fn memory_budget_remaining(&self) -> u64 {
781            self.inner.memory_budget_remaining()
782        }
783    }
784
785    struct MapResolver(HashMap<u64, NQuin>);
786    impl GroundingResolver for MapResolver {
787        fn resolve(&self, h: u64) -> Option<NQuin> {
788            self.0.get(&h).copied()
789        }
790    }
791
792    fn grounding_intent() -> AgentIntent {
793        AgentIntent {
794            intent_predicate: 0x1234,
795            requested_graph_scope: vec![0xABCD],
796            context_namespaces: vec![],
797            requires_network: false,
798            ilp_offer_micro_cents: 0,
799            principal_did_hash: 0,
800            mcp_intent_frame_hash: 0x1234,
801            output_mode: N3OutputMode::FreeText,
802            clearance_ceiling: 0,
803            max_sentinel_depth: 32,
804            active_profile: None,
805        }
806    }
807
808    fn claim_agent(claim: NQuin, citations: Vec<u64>) -> ClaimAgent {
809        ClaimAgent {
810            inner: LocalLlmAgent::new("did:git:claim-agent", "model.gguf"),
811            output: AgentOutput {
812                text: "asserted".into(),
813                semantic_quin: Some(claim),
814                provenance_quins: citations,
815                tokens_generated: 1,
816                inference_duration_ms: 0,
817                peak_memory_bytes: 0,
818            },
819        }
820    }
821
822    #[test]
823    fn grounding_gate_commits_a_supported_claim() {
824        let claim = claim_quin(1, 2, 3);
825        let agent = claim_agent(claim, vec![0xAA]);
826        let mut m = HashMap::new();
827        m.insert(0xAA, claim_quin(1, 2, 3)); // citation resolves to the exact fact
828        let resolver = MapResolver(m);
829        let orch = TaskOrchestrator::new(Box::new(NullThermalGovernor));
830        let result = orch.orchestrate_inference_grounded(
831            &agent,
832            "assert",
833            "ctx",
834            grounding_intent(),
835            None,
836            &resolver,
837            GroundingThresholds::default(),
838        );
839        assert!(
840            matches!(result, OrchestrationResult::Committed { .. }),
841            "got {result:?}"
842        );
843    }
844
845    #[test]
846    fn grounding_gate_blocks_an_ungrounded_claim() {
847        let claim = claim_quin(1, 2, 3);
848        let agent = claim_agent(claim, vec![0xBB]);
849        let mut m = HashMap::new();
850        m.insert(0xBB, claim_quin(7, 8, 9)); // cited fact is unrelated to the claim
851        let resolver = MapResolver(m);
852        let orch = TaskOrchestrator::new(Box::new(NullThermalGovernor));
853        let result = orch.orchestrate_inference_grounded(
854            &agent,
855            "assert",
856            "ctx",
857            grounding_intent(),
858            None,
859            &resolver,
860            GroundingThresholds::default(),
861        );
862        assert!(
863            matches!(result, OrchestrationResult::Blocked { reason, .. } if reason.contains("ungrounded")),
864            "ungrounded claim must be blocked, got {result:?}"
865        );
866    }
867
868    #[test]
869    fn grounding_gate_routes_partial_support_to_review() {
870        // Endpoints 1 and 3 are each cited somewhere, but no single fact matches a
871        // role of the claim → review band → blocked pending human review.
872        let claim = claim_quin(1, 2, 3);
873        let agent = claim_agent(claim, vec![0xC1, 0xC2]);
874        let mut m = HashMap::new();
875        m.insert(0xC1, claim_quin(1, 50, 60));
876        m.insert(0xC2, claim_quin(60, 70, 3));
877        let resolver = MapResolver(m);
878        let orch = TaskOrchestrator::new(Box::new(NullThermalGovernor));
879        let result = orch.orchestrate_inference_grounded(
880            &agent,
881            "assert",
882            "ctx",
883            grounding_intent(),
884            None,
885            &resolver,
886            GroundingThresholds::default(),
887        );
888        assert!(
889            matches!(result, OrchestrationResult::Blocked { reason, .. } if reason.contains("human review")),
890            "partial grounding must require review, got {result:?}"
891        );
892    }
893
894    #[test]
895    fn ungrounded_claim_still_commits_without_a_resolver() {
896        // The plain (non-grounded) entrypoint must be unchanged: no resolver → the
897        // grounding gate is skipped entirely, preserving existing behaviour.
898        let claim = claim_quin(1, 2, 3);
899        let agent = claim_agent(claim, vec![0xBB]);
900        let orch = TaskOrchestrator::new(Box::new(NullThermalGovernor));
901        let result = orch.orchestrate_inference(&agent, "assert", "ctx", grounding_intent(), None);
902        assert!(
903            matches!(result, OrchestrationResult::Committed { .. }),
904            "got {result:?}"
905        );
906    }
907
908    #[test]
909    fn test_thermal_critical_blocks_non_essential_inference() {
910        struct MockCriticalThermalGovernor;
911        impl ThermalGovernor for MockCriticalThermalGovernor {
912            fn get_thermal_state(&self) -> ThermalStatus {
913                ThermalStatus::Critical
914            }
915        }
916
917        let agent = LocalLlmAgent::new("did:git:orch-test", "model.gguf");
918        let intent = AgentIntent {
919            intent_predicate: 0x1234, // non-critical
920            requested_graph_scope: vec![],
921            context_namespaces: vec![],
922            requires_network: false,
923            ilp_offer_micro_cents: 0,
924            principal_did_hash: 0,
925            mcp_intent_frame_hash: 0x1234,
926            output_mode: N3OutputMode::FreeText,
927            clearance_ceiling: 0,
928            max_sentinel_depth: 32,
929            active_profile: None,
930        };
931        let orch = TaskOrchestrator::new(Box::new(MockCriticalThermalGovernor));
932        let result = orch.orchestrate_inference(&agent, "Query", "ctx", intent, None);
933        assert!(matches!(
934            result,
935            OrchestrationResult::Blocked {
936                rule_violated: 0xDEADBEEF,
937                ..
938            }
939        ));
940    }
941
942    #[test]
943    fn test_async_scrub_lock_invariant() {
944        let orch = TaskOrchestrator::new(Box::new(NullThermalGovernor));
945        let agent = LocalLlmAgent::new("did:git:orch-test", "model.gguf");
946
947        // 1. Initially it should load fine
948        assert!(orch.load_model(&agent, 123).is_ok());
949
950        // 2. Trigger an eviction (spawns background thread to scrub)
951        orch.evict_model(123);
952
953        // 3. IMMEDIATELY try to load a new model. The lock should reject it.
954        let load_result = orch.load_model(&agent, 456);
955        assert!(
956            load_result.is_err(),
957            "Orchestrator violated mechanical sympathy! Mapped model while Swarm worker was still scrubbing."
958        );
959
960        // 4. Wait for the background Swarm worker to complete its duty of care
961        std::thread::sleep(std::time::Duration::from_millis(50));
962
963        // 5. Try loading again. The lock should be cleared.
964        let second_load_result = orch.load_model(&agent, 456);
965        assert!(
966            second_load_result.is_ok(),
967            "Orchestrator failed to load model after scrubbing lock cleared."
968        );
969        assert_eq!(orch.resident_model_id(), Some(456));
970
971        // Ensure Webizen VM logic handles yielding
972        let mut vm = crate::modalities::logic::core::WebizenVM::with_scrubbing_lock(
973            orch.scrubbing_lock.clone(),
974        );
975        let bytecode = vec![crate::modalities::logic::core::WebizenOpcode::LoadModel(
976            999,
977        )];
978        vm.load_bytecode(&bytecode);
979
980        let quin = crate::NQuin {
981            subject: 0,
982            predicate: 0,
983            object: 0,
984            context: 0,
985            metadata: 0,
986            parity: 0,
987        };
988
989        // If we trigger evict again, the VM should yield on LoadModel
990        orch.evict_model(456);
991        let exec_result = vm.execute_implication(&quin);
992        assert!(exec_result.is_none());
993        assert_eq!(
994            vm.yielded_op,
995            Some(crate::modalities::logic::core::WebizenOpcode::LoadModel(
996                999
997            ))
998        );
999
1000        // Wait for scrub to clear
1001        std::thread::sleep(std::time::Duration::from_millis(50));
1002        assert_eq!(orch.resident_model_id(), None);
1003    }
1004
1005    fn write_fever_lex_bytes() -> Vec<u8> {
1006        let h_sub = q_hash("Patient");
1007        let h_pred = q_hash("fever");
1008        let h_obj = q_hash("True");
1009        let entries = [(h_sub, "Patient"), (h_pred, "fever"), (h_obj, "True")];
1010        let mut sorted = entries.to_vec();
1011        sorted.sort_unstable_by_key(|(h, _)| *h);
1012        let entry_count = sorted.len() as u64;
1013        let strings_offset = 32 + entry_count * 16;
1014        let mut blob = Vec::new();
1015        let mut index = Vec::new();
1016        for (hash, text) in &sorted {
1017            let str_off = blob.len() as u64;
1018            let b = text.as_bytes();
1019            let len = b.len().min(65535) as u16;
1020            // Q42LEX string payload format: [LEX_TAG_STRING=0x01][len_lo][len_hi][utf8...]
1021            // Must match Q42LexMmap::read_string_at which checks the tag byte first.
1022            blob.push(0x01u8); // LEX_TAG_STRING
1023            blob.extend_from_slice(&len.to_le_bytes());
1024            blob.extend_from_slice(&b[..len as usize]);
1025            index.extend_from_slice(&hash.to_le_bytes());
1026            index.extend_from_slice(&str_off.to_le_bytes());
1027        }
1028        let mut out = Vec::new();
1029        out.extend_from_slice(b"Q42LEX\0\0");
1030        out.extend_from_slice(&entry_count.to_le_bytes());
1031        out.extend_from_slice(&strings_offset.to_le_bytes());
1032        out.extend_from_slice(&1u64.to_le_bytes());
1033        out.extend_from_slice(&index);
1034        out.extend_from_slice(&blob);
1035        out
1036    }
1037
1038    /// End-to-end: mmap lex → FSM sieve (3 tokens) → fiduciary stamp → volatile WAL commit.
1039    #[test]
1040    #[serial_test::serial]
1041    fn test_e2e_llm_to_wal_pipeline() {
1042        let _profiler = dhat::Profiler::builder().testing().build();
1043
1044        let lex_bytes = write_fever_lex_bytes();
1045        let lex_mmap = crate::q42_lex::Q42LexMmap::from_bytes(&lex_bytes).expect("lex");
1046        let tok = crate::gguf_sharder::GgufTokenizer::default();
1047        let spec = crate::neuro_symbolic_sieve::SieveLexSpec::fever_observation();
1048        let mut sieve = crate::neuro_symbolic_sieve::NeuroSymbolicSieve::from_lex_and_tokenizer(
1049            &lex_mmap, &tok, &spec,
1050        );
1051        assert!(
1052            sieve.masks_ready(),
1053            "lex must resolve fever triple token IDs"
1054        );
1055        let (sub, pred, obj) = sieve.resolved_token_triple().expect("triple");
1056
1057        let wal_file = tempfile::NamedTempFile::new().expect("wal temp");
1058        let mut wal = WriteAheadLog::open(wal_file.path()).expect("wal open");
1059        let secret = [42u8; 32];
1060        let signing_key = ed25519_dalek::SigningKey::from_bytes(&secret);
1061        let mut suspended = crate::crdt::SuspendedTransactionQueue::new();
1062        let principal = q_hash("did:q42:test-principal");
1063        let agent_did = q_hash("did:git:orch-test");
1064
1065        let stats_before = dhat::HeapStats::get();
1066
1067        assert!(sieve.apply_token(sub).is_ok());
1068        assert!(sieve.apply_token(pred).is_ok());
1069        assert!(sieve.apply_token(obj).is_ok());
1070        assert_eq!(
1071            sieve.emitted_len(),
1072            3,
1073            "must halt at exactly 3 sieve tokens"
1074        );
1075        assert!(sieve.is_complete());
1076
1077        let mut quin = sieve.assemble_quin(q_hash("clinical:fever-context"));
1078        let handoff = commit_semantic_mutation(
1079            &mut wal,
1080            &mut quin,
1081            principal,
1082            agent_did,
1083            &signing_key,
1084            &mut suspended,
1085        )
1086        .expect("wal handoff");
1087
1088        let stats_after = dhat::HeapStats::get();
1089        assert_eq!(
1090            stats_after.total_blocks - stats_before.total_blocks,
1091            0,
1092            "sieve decode + WAL write must not heap-allocate"
1093        );
1094        assert_eq!(
1095            stats_after.total_bytes - stats_before.total_bytes,
1096            0,
1097            "sieve decode + WAL write must not heap-allocate"
1098        );
1099
1100        assert_eq!(handoff, WalHandoffResult::Committed);
1101        let recovered = wal.recover().expect("recover");
1102        assert_eq!(recovered.len(), 1, "WAL must contain one 48-byte Quin");
1103        assert_eq!(recovered[0].subject, q_hash("Patient"));
1104        assert_eq!(recovered[0].predicate, q_hash("fever"));
1105        assert_eq!(recovered[0].object, q_hash("True"));
1106        assert_eq!(recovered[0].context, principal);
1107        assert_eq!(
1108            recovered[0].parity,
1109            recovered[0].subject
1110                ^ recovered[0].predicate
1111                ^ recovered[0].object
1112                ^ recovered[0].context
1113                ^ recovered[0].metadata
1114        );
1115
1116        // Optional GPU path: chunked prefill + sieved decode when Gemma GGUF is present.
1117        let gemma = std::path::Path::new(
1118            "C:/Projects/qualiaDB/gemma-4-E4B-it-GGUF/gemma-4-E4B-it-Q4_K_M.gguf",
1119        );
1120        if !gemma.exists() {
1121            return;
1122        }
1123        let mut lex_tmp = tempfile::NamedTempFile::new().expect("lex temp");
1124        std::io::Write::write_all(&mut lex_tmp, &lex_bytes).expect("lex write");
1125        let agent = LocalLlmAgent::new("did:git:e2e-fever", gemma.to_string_lossy());
1126        agent.configure_sieve_lex(lex_tmp.path().to_string_lossy().into_owned());
1127        let frame_hash = q_hash("intent:fever");
1128        let fever_intent = AgentIntent {
1129            intent_predicate: frame_hash,
1130            requested_graph_scope: vec![q_hash("snomed:hasFever")],
1131            context_namespaces: vec![q_hash("health"), q_hash("snomed")],
1132            requires_network: false,
1133            ilp_offer_micro_cents: 0,
1134            principal_did_hash: principal,
1135            mcp_intent_frame_hash: frame_hash,
1136            output_mode: N3OutputMode::GraphMutation,
1137            clearance_ceiling: 0,
1138            max_sentinel_depth: 32,
1139            active_profile: None,
1140        };
1141        assert_eq!(agent.validate_intent(&fever_intent), WebizenVerdict::Permit);
1142        if let Ok(output) = AgentRuntime::infer(&agent, "The user has a fever", "clinical-context")
1143        {
1144            assert!(
1145                output.tokens_generated <= 3,
1146                "sieve must cap generation at 3 tokens"
1147            );
1148            if let Some(q) = output.semantic_quin {
1149                assert_ne!(q.subject, 0);
1150                assert_ne!(q.predicate, 0);
1151            }
1152        }
1153    }
1154}