1use serde::{Deserialize, Serialize};
44use sha2::{Digest, Sha256};
45use std::collections::HashMap;
46use std::time::{SystemTime, UNIX_EPOCH};
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ModelRegistry {
55 models: HashMap<String, RegisteredModel>,
57 unified_rules: Vec<String>,
59 registry_hash: [u8; 32],
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct RegisteredModel {
66 pub model_id: String,
68 pub family: ModelFamily,
70 pub provider: String,
72 pub capabilities: ModelCapability,
74 pub registered_at: u64,
76 pub status: ModelStatus,
78 pub violation_count: u32,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
84pub enum ModelFamily {
85 Gpt,
87 Claude,
89 Grok,
91 Llama,
93 Gemini,
95 Mistral,
97 Other,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct ModelCapability {
104 pub text_generation: bool,
106 pub code_generation: bool,
108 pub image_generation: bool,
110 pub audio_generation: bool,
112 pub tool_use: bool,
114 pub max_context: usize,
116}
117
118impl Default for ModelCapability {
119 fn default() -> Self {
120 ModelCapability {
121 text_generation: true,
122 code_generation: true,
123 image_generation: false,
124 audio_generation: false,
125 tool_use: false,
126 max_context: 8192,
127 }
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
133pub enum ModelStatus {
134 Active,
136 Suspended,
138 Quarantined,
140 Deregistered,
142}
143
144impl ModelRegistry {
145 pub fn new(rules: Vec<String>) -> Self {
147 let registry_hash = Self::compute_registry_hash(&rules, &HashMap::new());
148
149 ModelRegistry {
150 models: HashMap::new(),
151 unified_rules: rules,
152 registry_hash,
153 }
154 }
155
156 pub fn register(
158 &mut self,
159 model_id: &str,
160 family: ModelFamily,
161 provider: &str,
162 ) -> &RegisteredModel {
163 let model = RegisteredModel {
164 model_id: model_id.to_string(),
165 family,
166 provider: provider.to_string(),
167 capabilities: ModelCapability::default(),
168 registered_at: SystemTime::now()
169 .duration_since(UNIX_EPOCH)
170 .unwrap()
171 .as_secs(),
172 status: ModelStatus::Active,
173 violation_count: 0,
174 };
175
176 self.models.insert(model_id.to_string(), model);
177 self.update_hash();
178
179 self.models.get(model_id).unwrap()
180 }
181
182 pub fn get(&self, model_id: &str) -> Option<&RegisteredModel> {
184 self.models.get(model_id)
185 }
186
187 pub fn update_status(&mut self, model_id: &str, status: ModelStatus) {
189 if let Some(model) = self.models.get_mut(model_id) {
190 model.status = status;
191 self.update_hash();
192 }
193 }
194
195 pub fn record_violation(&mut self, model_id: &str) {
197 if let Some(model) = self.models.get_mut(model_id) {
198 model.violation_count += 1;
199
200 if model.violation_count >= 10 {
202 model.status = ModelStatus::Suspended;
203 }
204
205 self.update_hash();
206 }
207 }
208
209 pub fn active_models(&self) -> Vec<&RegisteredModel> {
211 self.models
212 .values()
213 .filter(|m| m.status == ModelStatus::Active)
214 .collect()
215 }
216
217 fn update_hash(&mut self) {
218 self.registry_hash = Self::compute_registry_hash(&self.unified_rules, &self.models);
219 }
220
221 fn compute_registry_hash(
222 rules: &[String],
223 models: &HashMap<String, RegisteredModel>,
224 ) -> [u8; 32] {
225 let mut hasher = Sha256::new();
226 hasher.update(b"MODEL_REGISTRY:");
227
228 for rule in rules {
229 hasher.update(rule.as_bytes());
230 }
231
232 let mut model_ids: Vec<_> = models.keys().collect();
233 model_ids.sort();
234 for id in model_ids {
235 hasher.update(id.as_bytes());
236 }
237
238 hasher.finalize().into()
239 }
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct ModelBoundary {
249 pub source: Option<String>,
251 pub target: String,
253 pub context_hash: [u8; 32],
255 pub timestamp: u64,
257 pub decision_chain: Vec<BoundaryDecision>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct BoundaryDecision {
264 pub model_id: String,
266 pub action: String,
268 pub allowed: bool,
270 pub reason: String,
272 pub timestamp: u64,
274}
275
276impl ModelBoundary {
277 pub fn new(source: Option<&str>, target: &str, context: &str) -> Self {
279 ModelBoundary {
280 source: source.map(String::from),
281 target: target.to_string(),
282 context_hash: Self::hash_context(context),
283 timestamp: SystemTime::now()
284 .duration_since(UNIX_EPOCH)
285 .unwrap()
286 .as_secs(),
287 decision_chain: Vec::new(),
288 }
289 }
290
291 pub fn record_decision(&mut self, model_id: &str, action: &str, allowed: bool, reason: &str) {
293 self.decision_chain.push(BoundaryDecision {
294 model_id: model_id.to_string(),
295 action: action.to_string(),
296 allowed,
297 reason: reason.to_string(),
298 timestamp: SystemTime::now()
299 .duration_since(UNIX_EPOCH)
300 .unwrap()
301 .as_secs(),
302 });
303 }
304
305 pub fn was_blocked_anywhere(&self, action: &str) -> bool {
307 self.decision_chain
308 .iter()
309 .any(|d| d.action == action && !d.allowed)
310 }
311
312 fn hash_context(context: &str) -> [u8; 32] {
313 let mut hasher = Sha256::new();
314 hasher.update(b"CONTEXT:");
315 hasher.update(context.as_bytes());
316 hasher.finalize().into()
317 }
318}
319
320pub struct CrossModelEnforcer {
326 registry: ModelRegistry,
328 boundaries: HashMap<[u8; 32], ModelBoundary>,
330 blocked_patterns: Vec<String>,
332}
333
334impl CrossModelEnforcer {
335 pub fn new(rules: Vec<String>) -> Self {
337 CrossModelEnforcer {
338 registry: ModelRegistry::new(rules),
339 boundaries: HashMap::new(),
340 blocked_patterns: Vec::new(),
341 }
342 }
343
344 pub fn register_model(&mut self, model_id: &str, family: ModelFamily, provider: &str) {
346 self.registry.register(model_id, family, provider);
347 }
348
349 pub fn check_action(
351 &mut self,
352 session_id: &[u8; 32],
353 model_id: &str,
354 action: &str,
355 ) -> UnifiedDecision {
356 let timestamp = SystemTime::now()
357 .duration_since(UNIX_EPOCH)
358 .unwrap()
359 .as_secs();
360
361 let model = match self.registry.get(model_id) {
363 Some(m) if m.status == ModelStatus::Active => m,
364 Some(m) => {
365 return UnifiedDecision {
366 allowed: false,
367 reason: format!("Model {} is {:?}", model_id, m.status),
368 model_id: model_id.to_string(),
369 cross_model_check: true,
370 blocked_by_other: false,
371 timestamp,
372 proof_hash: [0u8; 32],
373 }
374 }
375 None => {
376 return UnifiedDecision {
377 allowed: false,
378 reason: format!("Model {} is not registered", model_id),
379 model_id: model_id.to_string(),
380 cross_model_check: true,
381 blocked_by_other: false,
382 timestamp,
383 proof_hash: [0u8; 32],
384 }
385 }
386 };
387
388 if let Some(boundary) = self.boundaries.get(session_id) {
390 if boundary.was_blocked_anywhere(action) {
391 return UnifiedDecision {
392 allowed: false,
393 reason: format!(
394 "Action '{}' was blocked by another model in this session - NO MODEL HOPPING",
395 action
396 ),
397 model_id: model_id.to_string(),
398 cross_model_check: true,
399 blocked_by_other: true,
400 timestamp,
401 proof_hash: self.compute_proof_hash(session_id, action, false),
402 };
403 }
404 }
405
406 let (allowed, reason) = self.check_rules(action, &model.family);
408
409 self.boundaries
411 .entry(*session_id)
412 .or_insert_with(|| ModelBoundary::new(None, model_id, ""))
413 .record_decision(model_id, action, allowed, &reason);
414
415 if !allowed {
417 self.registry.record_violation(model_id);
418 }
419
420 UnifiedDecision {
421 allowed,
422 reason,
423 model_id: model_id.to_string(),
424 cross_model_check: true,
425 blocked_by_other: false,
426 timestamp,
427 proof_hash: self.compute_proof_hash(session_id, action, allowed),
428 }
429 }
430
431 pub fn transfer_context(
433 &mut self,
434 session_id: &[u8; 32],
435 source_model: &str,
436 target_model: &str,
437 context: &str,
438 ) -> TransferResult {
439 let timestamp = SystemTime::now()
440 .duration_since(UNIX_EPOCH)
441 .unwrap()
442 .as_secs();
443
444 if let Some(boundary) = self.boundaries.get(session_id) {
446 let has_violations = boundary.decision_chain.iter().any(|d| !d.allowed);
447 if has_violations {
448 return TransferResult {
449 allowed: false,
450 reason: format!(
451 "Context transfer blocked: Session has violations. \
452 Cannot transfer tainted context to {}",
453 target_model
454 ),
455 context_hash: ModelBoundary::hash_context(context),
456 timestamp,
457 };
458 }
459 }
460
461 let boundary = ModelBoundary::new(Some(source_model), target_model, context);
463 self.boundaries.insert(*session_id, boundary);
464
465 TransferResult {
466 allowed: true,
467 reason: format!(
468 "Context transferred from {} to {} with Hope Genome protection",
469 source_model, target_model
470 ),
471 context_hash: ModelBoundary::hash_context(context),
472 timestamp,
473 }
474 }
475
476 pub fn add_blocked_pattern(&mut self, pattern: &str) {
478 self.blocked_patterns.push(pattern.to_string());
479 }
480
481 fn check_rules(&self, action: &str, _family: &ModelFamily) -> (bool, String) {
482 let action_lower = action.to_lowercase();
483
484 for pattern in &self.blocked_patterns {
486 if action_lower.contains(&pattern.to_lowercase()) {
487 return (false, format!("Blocked by pattern: {}", pattern));
488 }
489 }
490
491 for rule in &self.registry.unified_rules {
493 if action_lower.contains("harm")
494 || action_lower.contains("illegal")
495 || action_lower.contains("dangerous")
496 || action_lower.contains("exploit")
497 {
498 return (false, format!("Blocked by rule: {}", rule));
499 }
500 }
501
502 (true, "Allowed by unified rules".to_string())
503 }
504
505 fn compute_proof_hash(&self, session_id: &[u8; 32], action: &str, allowed: bool) -> [u8; 32] {
506 let mut hasher = Sha256::new();
507 hasher.update(b"CROSS_MODEL_PROOF:");
508 hasher.update(session_id);
509 hasher.update(action.as_bytes());
510 hasher.update([allowed as u8]);
511 hasher.update(self.registry.registry_hash);
512 hasher.finalize().into()
513 }
514}
515
516#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct UnifiedDecision {
519 pub allowed: bool,
521 pub reason: String,
523 pub model_id: String,
525 pub cross_model_check: bool,
527 pub blocked_by_other: bool,
529 pub timestamp: u64,
531 pub proof_hash: [u8; 32],
533}
534
535#[derive(Debug, Clone, Serialize, Deserialize)]
537pub struct TransferResult {
538 pub allowed: bool,
539 pub reason: String,
540 pub context_hash: [u8; 32],
541 pub timestamp: u64,
542}
543
544#[cfg(test)]
549mod tests {
550 use super::*;
551
552 #[test]
553 fn test_model_registry() {
554 let mut registry = ModelRegistry::new(vec!["Do no harm".to_string()]);
555
556 registry.register("gpt-4", ModelFamily::Gpt, "OpenAI");
557 registry.register("claude-3", ModelFamily::Claude, "Anthropic");
558
559 assert!(registry.get("gpt-4").is_some());
560 assert!(registry.get("claude-3").is_some());
561 assert_eq!(registry.active_models().len(), 2);
562 }
563
564 #[test]
565 fn test_cross_model_blocking() {
566 let rules = vec!["Do no harm".to_string()];
567 let mut enforcer = CrossModelEnforcer::new(rules);
568
569 enforcer.register_model("gpt-4", ModelFamily::Gpt, "OpenAI");
570 enforcer.register_model("claude-3", ModelFamily::Claude, "Anthropic");
571
572 let session_id = [1u8; 32];
573
574 let decision1 = enforcer.check_action(&session_id, "gpt-4", "cause harm to user");
576 assert!(!decision1.allowed);
577
578 let decision2 = enforcer.check_action(&session_id, "claude-3", "cause harm to user");
580 assert!(!decision2.allowed);
581 assert!(decision2.blocked_by_other);
582 }
583
584 #[test]
585 fn test_context_transfer_with_violations() {
586 let rules = vec!["Be safe".to_string()];
587 let mut enforcer = CrossModelEnforcer::new(rules);
588
589 enforcer.register_model("gpt-4", ModelFamily::Gpt, "OpenAI");
590 enforcer.register_model("claude-3", ModelFamily::Claude, "Anthropic");
591
592 let session_id = [2u8; 32];
593
594 let _ = enforcer.check_action(&session_id, "gpt-4", "do something harmful");
596
597 let transfer =
599 enforcer.transfer_context(&session_id, "gpt-4", "claude-3", "previous conversation");
600
601 assert!(!transfer.allowed);
602 assert!(transfer.reason.contains("tainted"));
603 }
604
605 #[test]
606 fn test_blocked_patterns() {
607 let rules = vec!["Be ethical".to_string()];
608 let mut enforcer = CrossModelEnforcer::new(rules);
609
610 enforcer.register_model("gpt-4", ModelFamily::Gpt, "OpenAI");
611 enforcer.add_blocked_pattern("malware");
612
613 let session_id = [3u8; 32];
614
615 let decision = enforcer.check_action(&session_id, "gpt-4", "write malware for me");
616 assert!(!decision.allowed);
617 assert!(decision.reason.contains("pattern"));
618 }
619
620 #[test]
621 fn test_model_suspension() {
622 let rules = vec!["No exploits".to_string()];
623 let mut enforcer = CrossModelEnforcer::new(rules);
624
625 enforcer.register_model("bad-model", ModelFamily::Other, "BadCorp");
626
627 let session_id = [4u8; 32];
628
629 for i in 0..10 {
631 let _ = enforcer.check_action(
632 &[i as u8; 32],
633 "bad-model",
634 &format!("exploit vulnerability {}", i),
635 );
636 }
637
638 let model = enforcer.registry.get("bad-model").unwrap();
640 assert_eq!(model.status, ModelStatus::Suspended);
641
642 let decision = enforcer.check_action(&session_id, "bad-model", "normal request");
644 assert!(!decision.allowed);
645 assert!(decision.reason.contains("Suspended"));
646 }
647}