Skip to main content

qualia_client_core/
qpu_oracle.rs

1//! Person-controlled QPU Oracle — BYOK remote quantum offload for NP-hard tasks.
2//!
3//! API keys are encrypted at rest via KeyVault-derived material. Only anonymized
4//! numeric matrices (QUBO / VQE parameter vectors) may egress; classified data
5//! is blocked by the Sentinel before any HTTP dispatch.
6//!
7//! # Activation
8//! The QPU Oracle requires the user to affirm the Universal Human Rights commitment
9//! before any remote QPU egress is permitted. The commitment code is:
10//! `SSBBZmZpcm0gTXkgQ29tbWl0bWVudCB0byBVbml2ZXJzYWwgSHVtYW4gUmlnaHRz`
11//! (base64 for "I Affirm My Commitment to Universal Human Rights")
12
13use crate::state::{app_meta_dir, APP_STATE};
14use serde::{Deserialize, Serialize};
15use std::path::PathBuf;
16use std::sync::Mutex;
17
18static QPU_CACHE: Mutex<Option<QpuOracleState>> = Mutex::new(None);
19
20const COMMITMENT_B64: &str = "SSBBZmZpcm0gTXkgQ29tbWl0bWVudCB0byBVbml2ZXJzYWwgSHVtYW4gUmlnaHRz";
21const COMMITMENT_TEXT: &str = "I Affirm My Commitment to Universal Human Rights";
22
23// Monthly free-tier quota estimates (minutes of QPU time)
24const IBM_MONTHLY_MINUTES: f64 = 10.0;
25const DWAVE_MONTHLY_MINUTES: f64 = 1.0;
26const IONQ_MONTHLY_MINUTES: f64 = 0.0; // pay-per-use
27const RIGETTI_MONTHLY_MINUTES: f64 = 0.0; // pay-per-use
28const AZURE_MONTHLY_MINUTES: f64 = 0.0; // credits-based
29const BRAKET_MONTHLY_MINUTES: f64 = 0.0; // pay-per-use
30const GOOGLE_MONTHLY_MINUTES: f64 = 0.0; // credits-based
31const QUANTINUUM_MONTHLY_MINUTES: f64 = 0.0; // pay-per-use
32
33fn qpu_config_path() -> PathBuf {
34    app_meta_dir().join("qpu_oracle.json")
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38pub enum QpuArchitecture {
39    GateModel,
40    Annealer,
41    TrappedIon,
42    PhotonicGate,
43    NeutralAtom,
44}
45
46/// All supported QPU providers.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48pub enum QpuProvider {
49    /// IBM Quantum Network / IBM Cloud (gate model, superconducting)
50    Ibm,
51    /// D-Wave Leap (quantum annealing)
52    DWave,
53    /// IonQ Cloud (gate model, trapped ion)
54    IonQ,
55    /// Rigetti Quantum Cloud Services (gate model, superconducting)
56    Rigetti,
57    /// Microsoft Azure Quantum (multi-hardware)
58    Azure,
59    /// Amazon Braket (multi-hardware)
60    Braket,
61    /// Google Quantum AI (gate model, superconducting)
62    Google,
63    /// Quantinuum (gate model, trapped ion)
64    Quantinuum,
65}
66
67impl QpuProvider {
68    pub fn name(self) -> &'static str {
69        match self {
70            Self::Ibm => "IBM Quantum",
71            Self::DWave => "D-Wave Leap",
72            Self::IonQ => "IonQ Cloud",
73            Self::Rigetti => "Rigetti QCS",
74            Self::Azure => "Azure Quantum",
75            Self::Braket => "Amazon Braket",
76            Self::Google => "Google Quantum AI",
77            Self::Quantinuum => "Quantinuum",
78        }
79    }
80
81    pub fn architecture(self) -> QpuArchitecture {
82        match self {
83            Self::DWave => QpuArchitecture::Annealer,
84            Self::IonQ | Self::Quantinuum => QpuArchitecture::TrappedIon,
85            Self::Ibm | Self::Rigetti | Self::Google => QpuArchitecture::GateModel,
86            Self::Azure | Self::Braket => QpuArchitecture::GateModel, // multi-hardware, default
87        }
88    }
89
90    pub fn docs_url(self) -> &'static str {
91        match self {
92            Self::Ibm => "https://quantum.cloud.ibm.com",
93            Self::DWave => "https://cloud.dwavesys.com/leap/",
94            Self::IonQ => "https://cloud.ionq.com",
95            Self::Rigetti => "https://qcs.rigetti.com",
96            Self::Azure => "https://azure.microsoft.com/products/quantum",
97            Self::Braket => "https://aws.amazon.com/braket/",
98            Self::Google => "https://quantumai.google",
99            Self::Quantinuum => "https://www.quantinuum.com/computingtechnology/nexus",
100        }
101    }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct QpuOracleState {
106    pub feature_unlocked: bool,
107    /// ISO-8601 timestamp of when the commitment was affirmed.
108    pub commitment_affirmed_at: Option<String>,
109
110    // ── Provider tokens (encrypted at rest) ────────────────────────────────
111    pub ibm_token_enc: String,
112    pub dwave_token_enc: String,
113    pub ionq_token_enc: String,
114    pub rigetti_token_enc: String,
115    /// Azure: "<subscription_id>/<resource_group>/<workspace>/<api_key>"
116    pub azure_credentials_enc: String,
117    /// Braket: "<aws_access_key_id>|<aws_secret_access_key>|<region>"
118    pub braket_credentials_enc: String,
119    pub google_token_enc: String,
120    pub quantinuum_token_enc: String,
121
122    // ── Usage accounting ────────────────────────────────────────────────────
123    pub ibm_minutes_used: f64,
124    pub dwave_minutes_used: f64,
125    pub ionq_minutes_used: f64,
126    pub rigetti_minutes_used: f64,
127    pub azure_minutes_used: f64,
128    pub braket_minutes_used: f64,
129    pub google_minutes_used: f64,
130    pub quantinuum_minutes_used: f64,
131
132    // ── Feature flags ────────────────────────────────────────────────────────
133    pub max_shots_per_task: u32,
134    pub fallback_to_classical: bool,
135    pub enable_qubo_routing: bool,
136    pub enable_dft_ground_state: bool,
137    pub enable_defeasible_resolution: bool,
138}
139
140impl Default for QpuOracleState {
141    fn default() -> Self {
142        Self {
143            feature_unlocked: false,
144            commitment_affirmed_at: None,
145            ibm_token_enc: String::new(),
146            dwave_token_enc: String::new(),
147            ionq_token_enc: String::new(),
148            rigetti_token_enc: String::new(),
149            azure_credentials_enc: String::new(),
150            braket_credentials_enc: String::new(),
151            google_token_enc: String::new(),
152            quantinuum_token_enc: String::new(),
153            ibm_minutes_used: 0.0,
154            dwave_minutes_used: 0.0,
155            ionq_minutes_used: 0.0,
156            rigetti_minutes_used: 0.0,
157            azure_minutes_used: 0.0,
158            braket_minutes_used: 0.0,
159            google_minutes_used: 0.0,
160            quantinuum_minutes_used: 0.0,
161            max_shots_per_task: 1000,
162            fallback_to_classical: true,
163            enable_qubo_routing: true,
164            enable_dft_ground_state: true,
165            enable_defeasible_resolution: false,
166        }
167    }
168}
169
170/// Per-provider status returned to the frontend — no raw tokens exposed.
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct QpuProviderStatus {
173    pub provider: String,
174    pub name: String,
175    pub architecture: String,
176    pub configured: bool,
177    pub docs_url: String,
178    pub minutes_used: f64,
179    pub monthly_quota: f64,
180}
181
182/// Public settings view returned to the desktop UI.
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct QpuOracleSettings {
185    pub feature_unlocked: bool,
186    pub commitment_affirmed: bool,
187    // Legacy fields (kept for UI compatibility)
188    pub ibm_token_configured: bool,
189    pub dwave_token_configured: bool,
190    // All providers
191    pub providers: Vec<QpuProviderStatus>,
192    // Feature flags
193    pub max_shots_per_task: u32,
194    pub fallback_to_classical: bool,
195    pub enable_qubo_routing: bool,
196    pub enable_dft_ground_state: bool,
197    pub enable_defeasible_resolution: bool,
198    // Legacy quota fields
199    pub ibm_quota_minutes_remaining: f64,
200    pub dwave_quota_minutes_remaining: f64,
201}
202
203/// Input from the UI for saving settings.
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct QpuOracleSettingsInput {
206    pub max_shots_per_task: u32,
207    pub fallback_to_classical: bool,
208    pub enable_qubo_routing: bool,
209    pub enable_dft_ground_state: bool,
210    pub enable_defeasible_resolution: bool,
211    /// `None` = leave unchanged; `Some("")` = clear token.
212    pub ibm_token: Option<String>,
213    pub dwave_token: Option<String>,
214    pub ionq_token: Option<String>,
215    pub rigetti_token: Option<String>,
216    /// Format: "<subscription_id>/<resource_group>/<workspace>/<api_key>"
217    pub azure_credentials: Option<String>,
218    /// Format: "<aws_access_key_id>|<aws_secret_access_key>|<region>"
219    pub braket_credentials: Option<String>,
220    pub google_token: Option<String>,
221    pub quantinuum_token: Option<String>,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct QpuChatCommandResult {
226    pub handled: bool,
227    pub response: String,
228    pub feature_unlocked: bool,
229}
230
231// ── Encryption helpers ────────────────────────────────────────────────────────
232
233fn encrypt_secret(plaintext: &str) -> Result<String, String> {
234    if plaintext.is_empty() {
235        return Ok(String::new());
236    }
237    let state = APP_STATE.get().ok_or("APP_STATE not initialized")?;
238    let vault = state.key_vault.lock().map_err(|e| e.to_string())?;
239    let key_bytes = vault.derive_key("qpu_oracle_secrets").to_bytes();
240    let encrypted: Vec<u8> = plaintext
241        .as_bytes()
242        .iter()
243        .enumerate()
244        .map(|(i, b)| b ^ key_bytes[i % 32])
245        .collect();
246    Ok(hex::encode(encrypted))
247}
248
249fn decrypt_secret(ciphertext_hex: &str) -> Result<String, String> {
250    if ciphertext_hex.is_empty() {
251        return Ok(String::new());
252    }
253    let state = APP_STATE.get().ok_or("APP_STATE not initialized")?;
254    let vault = state.key_vault.lock().map_err(|e| e.to_string())?;
255    let encrypted = hex::decode(ciphertext_hex).map_err(|_| "Invalid encrypted token")?;
256    let key_bytes = vault.derive_key("qpu_oracle_secrets").to_bytes();
257    let decrypted: Vec<u8> = encrypted
258        .iter()
259        .enumerate()
260        .map(|(i, b)| b ^ key_bytes[i % 32])
261        .collect();
262    String::from_utf8(decrypted).map_err(|_| "Token decryption failed".into())
263}
264
265// ── Persistence ───────────────────────────────────────────────────────────────
266
267fn load_state_from_disk() -> QpuOracleState {
268    std::fs::read_to_string(qpu_config_path())
269        .ok()
270        .and_then(|s| serde_json::from_str(&s).ok())
271        .unwrap_or_default()
272}
273
274fn persist_state(state: &QpuOracleState) -> Result<(), String> {
275    let meta = app_meta_dir();
276    std::fs::create_dir_all(&meta).map_err(|e| e.to_string())?;
277    let json = serde_json::to_string_pretty(state).map_err(|e| e.to_string())?;
278    std::fs::write(qpu_config_path(), json).map_err(|e| e.to_string())?;
279    Ok(())
280}
281
282fn cached_state() -> QpuOracleState {
283    let mut cache = QPU_CACHE.lock().unwrap();
284    if cache.is_none() {
285        *cache = Some(load_state_from_disk());
286    }
287    cache.clone().unwrap()
288}
289
290pub(crate) fn cached_state_internal() -> QpuOracleState {
291    cached_state()
292}
293
294fn update_state<F: FnOnce(&mut QpuOracleState)>(f: F) -> Result<QpuOracleState, String> {
295    let mut state = cached_state();
296    f(&mut state);
297    persist_state(&state)?;
298    *QPU_CACHE.lock().unwrap() = Some(state.clone());
299    Ok(state)
300}
301
302// ── Commitment verification ────────────────────────────────────────────────────
303
304/// Verifies that `input` matches the Universal Human Rights commitment text.
305/// Accepts both the plain English text and the base64 form.
306pub fn verify_commitment(input: &str) -> bool {
307    let trimmed = input.trim();
308    // An empty (or whitespace-only) input is never a valid affirmation — guard it explicitly so a
309    // buggy decode fallback can never treat "" as a match (a false-accept in a rights-affirmation gate).
310    if trimmed.is_empty() {
311        return false;
312    }
313    if trimmed.eq_ignore_ascii_case(COMMITMENT_TEXT) {
314        return true;
315    }
316    if trimmed == COMMITMENT_B64 {
317        return true;
318    }
319    // Also accept the decoded base64
320    if let Ok(decoded) = base64_decode(COMMITMENT_B64) {
321        if trimmed.eq_ignore_ascii_case(&decoded) {
322            return true;
323        }
324    }
325    false
326}
327
328fn base64_decode(s: &str) -> Result<String, ()> {
329    // Simple base64 decode using the alphabet without external crate
330    let alphabet = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
331    let mut out = Vec::new();
332    let bytes: Vec<u8> = s.bytes().filter(|b| *b != b'=').collect();
333    let mut i = 0;
334    while i + 3 < bytes.len() + 1 {
335        let chunk = &bytes[i..i.min(bytes.len()).min(i + 4)];
336        if chunk.is_empty() {
337            break;
338        }
339        let vals: Vec<u8> = chunk
340            .iter()
341            .map(|b| alphabet.iter().position(|a| a == b).unwrap_or(0) as u8)
342            .collect();
343        if vals.len() >= 2 {
344            out.push((vals[0] << 2) | (vals[1] >> 4));
345        }
346        if vals.len() >= 3 {
347            out.push((vals[1] << 4) | (vals[2] >> 2));
348        }
349        if vals.len() >= 4 {
350            out.push((vals[2] << 6) | vals[3]);
351        }
352        i += 4;
353    }
354    String::from_utf8(out).map_err(|_| ())
355}
356
357/// Activate advanced capabilities by affirming the Universal Human Rights commitment.
358///
359/// `commitment_text` must be "I Affirm My Commitment to Universal Human Rights"
360/// (or the base64 form). Returns the updated settings on success.
361pub fn activate_with_commitment(commitment_text: &str) -> Result<QpuOracleSettings, String> {
362    if !verify_commitment(commitment_text) {
363        return Err("Commitment not recognised. Please enter: \
364             \"I Affirm My Commitment to Universal Human Rights\""
365            .into());
366    }
367    let now = chrono::Utc::now().to_rfc3339();
368    update_state(|state| {
369        state.feature_unlocked = true;
370        state.commitment_affirmed_at = Some(now.clone());
371    })
372    .map(|s| to_public_settings(&s))
373}
374
375// ── Public API ────────────────────────────────────────────────────────────────
376
377pub fn to_public_settings(state: &QpuOracleState) -> QpuOracleSettings {
378    let providers = vec![
379        QpuProviderStatus {
380            provider: "ibm".into(),
381            name: QpuProvider::Ibm.name().into(),
382            architecture: "GateModel".into(),
383            configured: !state.ibm_token_enc.is_empty(),
384            docs_url: QpuProvider::Ibm.docs_url().into(),
385            minutes_used: state.ibm_minutes_used,
386            monthly_quota: IBM_MONTHLY_MINUTES,
387        },
388        QpuProviderStatus {
389            provider: "dwave".into(),
390            name: QpuProvider::DWave.name().into(),
391            architecture: "Annealer".into(),
392            configured: !state.dwave_token_enc.is_empty(),
393            docs_url: QpuProvider::DWave.docs_url().into(),
394            minutes_used: state.dwave_minutes_used,
395            monthly_quota: DWAVE_MONTHLY_MINUTES,
396        },
397        QpuProviderStatus {
398            provider: "ionq".into(),
399            name: QpuProvider::IonQ.name().into(),
400            architecture: "TrappedIon".into(),
401            configured: !state.ionq_token_enc.is_empty(),
402            docs_url: QpuProvider::IonQ.docs_url().into(),
403            minutes_used: state.ionq_minutes_used,
404            monthly_quota: IONQ_MONTHLY_MINUTES,
405        },
406        QpuProviderStatus {
407            provider: "rigetti".into(),
408            name: QpuProvider::Rigetti.name().into(),
409            architecture: "GateModel".into(),
410            configured: !state.rigetti_token_enc.is_empty(),
411            docs_url: QpuProvider::Rigetti.docs_url().into(),
412            minutes_used: state.rigetti_minutes_used,
413            monthly_quota: RIGETTI_MONTHLY_MINUTES,
414        },
415        QpuProviderStatus {
416            provider: "azure".into(),
417            name: QpuProvider::Azure.name().into(),
418            architecture: "GateModel".into(),
419            configured: !state.azure_credentials_enc.is_empty(),
420            docs_url: QpuProvider::Azure.docs_url().into(),
421            minutes_used: state.azure_minutes_used,
422            monthly_quota: AZURE_MONTHLY_MINUTES,
423        },
424        QpuProviderStatus {
425            provider: "braket".into(),
426            name: QpuProvider::Braket.name().into(),
427            architecture: "GateModel".into(),
428            configured: !state.braket_credentials_enc.is_empty(),
429            docs_url: QpuProvider::Braket.docs_url().into(),
430            minutes_used: state.braket_minutes_used,
431            monthly_quota: BRAKET_MONTHLY_MINUTES,
432        },
433        QpuProviderStatus {
434            provider: "google".into(),
435            name: QpuProvider::Google.name().into(),
436            architecture: "GateModel".into(),
437            configured: !state.google_token_enc.is_empty(),
438            docs_url: QpuProvider::Google.docs_url().into(),
439            minutes_used: state.google_minutes_used,
440            monthly_quota: GOOGLE_MONTHLY_MINUTES,
441        },
442        QpuProviderStatus {
443            provider: "quantinuum".into(),
444            name: QpuProvider::Quantinuum.name().into(),
445            architecture: "TrappedIon".into(),
446            configured: !state.quantinuum_token_enc.is_empty(),
447            docs_url: QpuProvider::Quantinuum.docs_url().into(),
448            minutes_used: state.quantinuum_minutes_used,
449            monthly_quota: QUANTINUUM_MONTHLY_MINUTES,
450        },
451    ];
452
453    QpuOracleSettings {
454        feature_unlocked: state.feature_unlocked,
455        commitment_affirmed: state.commitment_affirmed_at.is_some(),
456        ibm_token_configured: !state.ibm_token_enc.is_empty(),
457        dwave_token_configured: !state.dwave_token_enc.is_empty(),
458        providers,
459        max_shots_per_task: state.max_shots_per_task,
460        fallback_to_classical: state.fallback_to_classical,
461        enable_qubo_routing: state.enable_qubo_routing,
462        enable_dft_ground_state: state.enable_dft_ground_state,
463        enable_defeasible_resolution: state.enable_defeasible_resolution,
464        ibm_quota_minutes_remaining: (IBM_MONTHLY_MINUTES - state.ibm_minutes_used).max(0.0),
465        dwave_quota_minutes_remaining: (DWAVE_MONTHLY_MINUTES - state.dwave_minutes_used).max(0.0),
466    }
467}
468
469pub fn get_qpu_settings() -> QpuOracleSettings {
470    to_public_settings(&cached_state())
471}
472
473pub fn is_qpu_feature_unlocked() -> bool {
474    cached_state().feature_unlocked
475}
476
477pub fn save_qpu_settings(input: QpuOracleSettingsInput) -> Result<QpuOracleSettings, String> {
478    if input.max_shots_per_task == 0 || input.max_shots_per_task > 1000 {
479        return Err("max_shots_per_task must be between 1 and 1000 (SHACL bound)".into());
480    }
481    update_state(|state| {
482        state.max_shots_per_task = input.max_shots_per_task;
483        state.fallback_to_classical = input.fallback_to_classical;
484        state.enable_qubo_routing = input.enable_qubo_routing;
485        state.enable_dft_ground_state = input.enable_dft_ground_state;
486        state.enable_defeasible_resolution = input.enable_defeasible_resolution;
487        if let Some(ref tok) = input.ibm_token {
488            state.ibm_token_enc = encrypt_secret(tok).unwrap_or_default();
489        }
490        if let Some(ref tok) = input.dwave_token {
491            state.dwave_token_enc = encrypt_secret(tok).unwrap_or_default();
492        }
493        if let Some(ref tok) = input.ionq_token {
494            state.ionq_token_enc = encrypt_secret(tok).unwrap_or_default();
495        }
496        if let Some(ref tok) = input.rigetti_token {
497            state.rigetti_token_enc = encrypt_secret(tok).unwrap_or_default();
498        }
499        if let Some(ref creds) = input.azure_credentials {
500            state.azure_credentials_enc = encrypt_secret(creds).unwrap_or_default();
501        }
502        if let Some(ref creds) = input.braket_credentials {
503            state.braket_credentials_enc = encrypt_secret(creds).unwrap_or_default();
504        }
505        if let Some(ref tok) = input.google_token {
506            state.google_token_enc = encrypt_secret(tok).unwrap_or_default();
507        }
508        if let Some(ref tok) = input.quantinuum_token {
509            state.quantinuum_token_enc = encrypt_secret(tok).unwrap_or_default();
510        }
511    })
512    .map(|s| to_public_settings(&s))
513}
514
515pub fn enable_qpu_feature() -> Result<QpuOracleSettings, String> {
516    update_state(|state| state.feature_unlocked = true).map(|s| to_public_settings(&s))
517}
518
519pub fn disable_qpu_feature() -> Result<QpuOracleSettings, String> {
520    update_state(|state| state.feature_unlocked = false).map(|s| to_public_settings(&s))
521}
522
523pub fn record_usage(arch: QpuArchitecture, minutes: f64) -> Result<(), String> {
524    update_state(|state| match arch {
525        QpuArchitecture::GateModel => state.ibm_minutes_used += minutes,
526        QpuArchitecture::Annealer => state.dwave_minutes_used += minutes,
527        QpuArchitecture::TrappedIon => state.ionq_minutes_used += minutes,
528        QpuArchitecture::PhotonicGate => state.google_minutes_used += minutes,
529        QpuArchitecture::NeutralAtom => state.braket_minutes_used += minutes,
530    })
531    .map(|_| ())
532}
533
534pub fn record_provider_usage(provider: QpuProvider, minutes: f64) -> Result<(), String> {
535    update_state(|state| match provider {
536        QpuProvider::Ibm => state.ibm_minutes_used += minutes,
537        QpuProvider::DWave => state.dwave_minutes_used += minutes,
538        QpuProvider::IonQ => state.ionq_minutes_used += minutes,
539        QpuProvider::Rigetti => state.rigetti_minutes_used += minutes,
540        QpuProvider::Azure => state.azure_minutes_used += minutes,
541        QpuProvider::Braket => state.braket_minutes_used += minutes,
542        QpuProvider::Google => state.google_minutes_used += minutes,
543        QpuProvider::Quantinuum => state.quantinuum_minutes_used += minutes,
544    })
545    .map(|_| ())
546}
547
548// ── Token resolution ──────────────────────────────────────────────────────────
549
550pub fn resolve_ibm_token() -> Option<String> {
551    let state = cached_state();
552    if !state.feature_unlocked || state.ibm_token_enc.is_empty() {
553        return None;
554    }
555    decrypt_secret(&state.ibm_token_enc).ok()
556}
557
558pub fn resolve_dwave_token() -> Option<String> {
559    let state = cached_state();
560    if !state.feature_unlocked || state.dwave_token_enc.is_empty() {
561        return None;
562    }
563    decrypt_secret(&state.dwave_token_enc).ok()
564}
565
566pub fn resolve_ionq_token() -> Option<String> {
567    let state = cached_state();
568    if !state.feature_unlocked || state.ionq_token_enc.is_empty() {
569        return None;
570    }
571    decrypt_secret(&state.ionq_token_enc).ok()
572}
573
574pub fn resolve_rigetti_token() -> Option<String> {
575    let state = cached_state();
576    if !state.feature_unlocked || state.rigetti_token_enc.is_empty() {
577        return None;
578    }
579    decrypt_secret(&state.rigetti_token_enc).ok()
580}
581
582/// Returns `(subscription_id, resource_group, workspace, api_key)` if configured.
583pub fn resolve_azure_credentials() -> Option<(String, String, String, String)> {
584    let state = cached_state();
585    if !state.feature_unlocked || state.azure_credentials_enc.is_empty() {
586        return None;
587    }
588    let raw = decrypt_secret(&state.azure_credentials_enc).ok()?;
589    let parts: Vec<&str> = raw.splitn(4, '/').collect();
590    if parts.len() == 4 {
591        Some((
592            parts[0].to_string(),
593            parts[1].to_string(),
594            parts[2].to_string(),
595            parts[3].to_string(),
596        ))
597    } else {
598        None
599    }
600}
601
602/// Returns `(access_key_id, secret_access_key, region)` if configured.
603pub fn resolve_braket_credentials() -> Option<(String, String, String)> {
604    let state = cached_state();
605    if !state.feature_unlocked || state.braket_credentials_enc.is_empty() {
606        return None;
607    }
608    let raw = decrypt_secret(&state.braket_credentials_enc).ok()?;
609    let parts: Vec<&str> = raw.splitn(3, '|').collect();
610    if parts.len() == 3 {
611        Some((
612            parts[0].to_string(),
613            parts[1].to_string(),
614            parts[2].to_string(),
615        ))
616    } else {
617        None
618    }
619}
620
621pub fn resolve_google_token() -> Option<String> {
622    let state = cached_state();
623    if !state.feature_unlocked || state.google_token_enc.is_empty() {
624        return None;
625    }
626    decrypt_secret(&state.google_token_enc).ok()
627}
628
629pub fn resolve_quantinuum_token() -> Option<String> {
630    let state = cached_state();
631    if !state.feature_unlocked || state.quantinuum_token_enc.is_empty() {
632        return None;
633    }
634    decrypt_secret(&state.quantinuum_token_enc).ok()
635}
636
637// ── Architecture routing ──────────────────────────────────────────────────────
638
639pub fn target_architecture(task: &str) -> Option<QpuArchitecture> {
640    let state = cached_state();
641    if !state.feature_unlocked {
642        return None;
643    }
644    match task {
645        "qubo_routing" if state.enable_qubo_routing => Some(QpuArchitecture::Annealer),
646        "dft_ground_state" if state.enable_dft_ground_state => Some(QpuArchitecture::GateModel),
647        "defeasible_resolution" if state.enable_defeasible_resolution => {
648            Some(QpuArchitecture::GateModel)
649        }
650        _ => None,
651    }
652}
653
654/// Returns the best available provider for a given task, preferring remote QPU.
655pub fn select_provider(task: &str) -> Option<QpuProvider> {
656    let state = cached_state();
657    if !state.feature_unlocked {
658        return None;
659    }
660    match task {
661        "qubo_routing" if state.enable_qubo_routing => {
662            // Prefer D-Wave (annealer) for QUBO; fall back to IonQ or IBM
663            if !state.dwave_token_enc.is_empty() {
664                Some(QpuProvider::DWave)
665            } else if !state.ionq_token_enc.is_empty() {
666                Some(QpuProvider::IonQ)
667            } else if !state.ibm_token_enc.is_empty() {
668                Some(QpuProvider::Ibm)
669            } else {
670                None
671            }
672        }
673        "dft_ground_state" if state.enable_dft_ground_state => {
674            // Prefer trapped-ion for VQE accuracy; fall back to superconducting
675            if !state.quantinuum_token_enc.is_empty() {
676                Some(QpuProvider::Quantinuum)
677            } else if !state.ionq_token_enc.is_empty() {
678                Some(QpuProvider::IonQ)
679            } else if !state.ibm_token_enc.is_empty() {
680                Some(QpuProvider::Ibm)
681            } else if !state.rigetti_token_enc.is_empty() {
682                Some(QpuProvider::Rigetti)
683            } else if !state.google_token_enc.is_empty() {
684                Some(QpuProvider::Google)
685            } else {
686                None
687            }
688        }
689        _ => None,
690    }
691}
692
693// ── Chat command interception ─────────────────────────────────────────────────
694
695/// Intercept hidden chat commands before LLM inference.
696pub fn handle_qpu_chat_command(text: &str) -> QpuChatCommandResult {
697    let normalized = text.trim();
698
699    let enable_cmds = [
700        "[enable_QPU]",
701        "[enable_QPU}",
702        "[enable_qpu]",
703        "[enable_qpu}",
704    ];
705    let disable_cmds = [
706        "[disable_QPU]",
707        "[disable_QPU}",
708        "[disable_qpu]",
709        "[disable_qpu}",
710    ];
711
712    if enable_cmds
713        .iter()
714        .any(|c| normalized.eq_ignore_ascii_case(c))
715    {
716        // Require commitment before enabling via chat command
717        if !cached_state().commitment_affirmed_at.is_some() {
718            return QpuChatCommandResult {
719                handled: true,
720                feature_unlocked: false,
721                response: "⚛️ **QPU Oracle requires activation.**\n\n\
722                    To activate, open **Settings → Advanced Capabilities** and affirm:\n\n\
723                    > *\"I Affirm My Commitment to Universal Human Rights\"*\n\n\
724                    Or pass the activation code in the Settings panel."
725                    .to_string(),
726            };
727        }
728        match enable_qpu_feature() {
729            Ok(settings) => QpuChatCommandResult {
730                handled: true,
731                feature_unlocked: settings.feature_unlocked,
732                response: "⚛️ **QPU Oracle unlocked.**\n\n\
733                    Open **Settings → QPU Oracle** to configure provider API keys.\n\n\
734                    **Supported providers:** IBM Quantum, D-Wave Leap, IonQ, Rigetti QCS, \
735                    Azure Quantum, Amazon Braket, Google Quantum AI, Quantinuum\n\n\
736                    **Chat commands:**\n\
737                    - `[qpu:qubo]` or `$$\\min_{x} ...$$` → Annealing / QUBO routing\n\
738                    - `[qpu:dft]` or `$$\\hat{H}\\Psi = E\\Psi$$` → VQE ground states\n\
739                    - `[qpu:defeasible]` → probabilistic obligation resolution\n\n\
740                    Only anonymised numeric matrices egress; classified data is blocked by Sentinel."
741                    .to_string(),
742            },
743            Err(e) => QpuChatCommandResult {
744                handled: true,
745                feature_unlocked: false,
746                response: format!("🔴 QPU unlock failed: {e}"),
747            },
748        }
749    } else if disable_cmds
750        .iter()
751        .any(|c| normalized.eq_ignore_ascii_case(c))
752    {
753        match disable_qpu_feature() {
754            Ok(settings) => QpuChatCommandResult {
755                handled: true,
756                feature_unlocked: settings.feature_unlocked,
757                response: "⚛️ **QPU Oracle suspended.** Type `[enable_QPU]` to restore."
758                    .to_string(),
759            },
760            Err(e) => QpuChatCommandResult {
761                handled: true,
762                feature_unlocked: cached_state().feature_unlocked,
763                response: format!("🔴 QPU disable failed: {e}"),
764            },
765        }
766    } else {
767        QpuChatCommandResult {
768            handled: false,
769            response: String::new(),
770            feature_unlocked: cached_state().feature_unlocked,
771        }
772    }
773}
774
775#[cfg(test)]
776mod tests {
777    use super::*;
778
779    #[test]
780    fn commitment_text_verifies() {
781        assert!(verify_commitment(
782            "I Affirm My Commitment to Universal Human Rights"
783        ));
784        assert!(verify_commitment(
785            "SSBBZmZpcm0gTXkgQ29tbWl0bWVudCB0byBVbml2ZXJzYWwgSHVtYW4gUmlnaHRz"
786        ));
787        assert!(!verify_commitment("something else"));
788        assert!(!verify_commitment(""));
789    }
790
791    #[test]
792    fn chat_command_not_handled_for_normal_text() {
793        let r = handle_qpu_chat_command("hello world");
794        assert!(!r.handled);
795    }
796
797    #[test]
798    fn provider_names_non_empty() {
799        for p in [
800            QpuProvider::Ibm,
801            QpuProvider::DWave,
802            QpuProvider::IonQ,
803            QpuProvider::Rigetti,
804            QpuProvider::Azure,
805            QpuProvider::Braket,
806            QpuProvider::Google,
807            QpuProvider::Quantinuum,
808        ] {
809            assert!(!p.name().is_empty());
810            assert!(!p.docs_url().is_empty());
811        }
812    }
813}