1use 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
23const IBM_MONTHLY_MINUTES: f64 = 10.0;
25const DWAVE_MONTHLY_MINUTES: f64 = 1.0;
26const IONQ_MONTHLY_MINUTES: f64 = 0.0; const RIGETTI_MONTHLY_MINUTES: f64 = 0.0; const AZURE_MONTHLY_MINUTES: f64 = 0.0; const BRAKET_MONTHLY_MINUTES: f64 = 0.0; const GOOGLE_MONTHLY_MINUTES: f64 = 0.0; const QUANTINUUM_MONTHLY_MINUTES: f64 = 0.0; fn 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48pub enum QpuProvider {
49 Ibm,
51 DWave,
53 IonQ,
55 Rigetti,
57 Azure,
59 Braket,
61 Google,
63 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, }
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 pub commitment_affirmed_at: Option<String>,
109
110 pub ibm_token_enc: String,
112 pub dwave_token_enc: String,
113 pub ionq_token_enc: String,
114 pub rigetti_token_enc: String,
115 pub azure_credentials_enc: String,
117 pub braket_credentials_enc: String,
119 pub google_token_enc: String,
120 pub quantinuum_token_enc: String,
121
122 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 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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct QpuOracleSettings {
185 pub feature_unlocked: bool,
186 pub commitment_affirmed: bool,
187 pub ibm_token_configured: bool,
189 pub dwave_token_configured: bool,
190 pub providers: Vec<QpuProviderStatus>,
192 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 pub ibm_quota_minutes_remaining: f64,
200 pub dwave_quota_minutes_remaining: f64,
201}
202
203#[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 pub ibm_token: Option<String>,
213 pub dwave_token: Option<String>,
214 pub ionq_token: Option<String>,
215 pub rigetti_token: Option<String>,
216 pub azure_credentials: Option<String>,
218 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
231fn 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
265fn 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
302pub fn verify_commitment(input: &str) -> bool {
307 let trimmed = input.trim();
308 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 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 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
357pub 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
375pub 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
548pub 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
582pub 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
602pub 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
637pub 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
654pub 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 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 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
693pub 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 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}