1use crate::dcp::{DcpClient, DcpMessage, MessageType, ZctiBuilder};
32use crate::generator_integration::{GenerateParams, GeneratorBridge};
33use crate::hooks::{AgentHook, HookContext};
34use crate::{DrivenError, Result};
35use std::collections::HashMap;
36use std::path::{Path, PathBuf};
37
38#[derive(Debug)]
45pub struct DrivenDcpBridge {
46 tool_ids: HashMap<String, u32>,
48 initialized: bool,
50}
51
52#[derive(Debug, Clone)]
54pub struct DrivenTool {
55 pub name: String,
57 pub description: String,
59 pub category: DrivenToolCategory,
61 pub capabilities: u64,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum DrivenToolCategory {
68 Rules,
70 Specs,
72 Hooks,
74 Steering,
76 Templates,
78 Sync,
80}
81
82impl DrivenDcpBridge {
83 pub fn new() -> Self {
85 Self {
86 tool_ids: HashMap::new(),
87 initialized: false,
88 }
89 }
90
91 pub fn register_driven_tools(&mut self, client: &mut DcpClient) -> Result<()> {
93 let tools = self.get_driven_tools();
94
95 for tool in tools {
96 let schema_hash = self.compute_schema_hash(&tool);
97 let tool_id = client.register_tool(
98 &tool.name,
99 &tool.description,
100 schema_hash,
101 tool.capabilities,
102 );
103 self.tool_ids.insert(tool.name.clone(), tool_id);
104
105 client.register_tool_mcp(&tool.name, tool_id);
107 }
108
109 self.initialized = true;
110 Ok(())
111 }
112
113 fn get_driven_tools(&self) -> Vec<DrivenTool> {
115 vec![
116 DrivenTool {
118 name: "driven.rules.sync".to_string(),
119 description: "Sync rules to all configured editors".to_string(),
120 category: DrivenToolCategory::Rules,
121 capabilities: 0x0001,
122 },
123 DrivenTool {
124 name: "driven.rules.convert".to_string(),
125 description: "Convert rules between formats".to_string(),
126 category: DrivenToolCategory::Rules,
127 capabilities: 0x0001,
128 },
129 DrivenTool {
130 name: "driven.rules.validate".to_string(),
131 description: "Validate rule syntax and semantics".to_string(),
132 category: DrivenToolCategory::Rules,
133 capabilities: 0x0001,
134 },
135 DrivenTool {
137 name: "driven.spec.init".to_string(),
138 description: "Initialize a new specification".to_string(),
139 category: DrivenToolCategory::Specs,
140 capabilities: 0x0002,
141 },
142 DrivenTool {
143 name: "driven.spec.generate".to_string(),
144 description: "Generate spec artifacts from description".to_string(),
145 category: DrivenToolCategory::Specs,
146 capabilities: 0x0002,
147 },
148 DrivenTool {
149 name: "driven.spec.analyze".to_string(),
150 description: "Analyze spec for consistency".to_string(),
151 category: DrivenToolCategory::Specs,
152 capabilities: 0x0002,
153 },
154 DrivenTool {
156 name: "driven.hooks.list".to_string(),
157 description: "List all registered hooks".to_string(),
158 category: DrivenToolCategory::Hooks,
159 capabilities: 0x0004,
160 },
161 DrivenTool {
162 name: "driven.hooks.trigger".to_string(),
163 description: "Manually trigger a hook".to_string(),
164 category: DrivenToolCategory::Hooks,
165 capabilities: 0x0004,
166 },
167 DrivenTool {
168 name: "driven.hooks.enable".to_string(),
169 description: "Enable a hook".to_string(),
170 category: DrivenToolCategory::Hooks,
171 capabilities: 0x0004,
172 },
173 DrivenTool {
174 name: "driven.hooks.disable".to_string(),
175 description: "Disable a hook".to_string(),
176 category: DrivenToolCategory::Hooks,
177 capabilities: 0x0004,
178 },
179 DrivenTool {
181 name: "driven.steering.list".to_string(),
182 description: "List steering files".to_string(),
183 category: DrivenToolCategory::Steering,
184 capabilities: 0x0008,
185 },
186 DrivenTool {
187 name: "driven.steering.get".to_string(),
188 description: "Get steering content for context".to_string(),
189 category: DrivenToolCategory::Steering,
190 capabilities: 0x0008,
191 },
192 DrivenTool {
194 name: "driven.templates.list".to_string(),
195 description: "List available templates".to_string(),
196 category: DrivenToolCategory::Templates,
197 capabilities: 0x0010,
198 },
199 DrivenTool {
200 name: "driven.templates.apply".to_string(),
201 description: "Apply a template".to_string(),
202 category: DrivenToolCategory::Templates,
203 capabilities: 0x0010,
204 },
205 ]
206 }
207
208 fn compute_schema_hash(&self, tool: &DrivenTool) -> [u8; 32] {
210 use blake3::Hasher;
211 let mut hasher = Hasher::new();
212 hasher.update(tool.name.as_bytes());
213 hasher.update(tool.description.as_bytes());
214 hasher.update(&[tool.category as u8]);
215 *hasher.finalize().as_bytes()
216 }
217
218 pub fn get_tool_id(&self, name: &str) -> Option<u32> {
220 self.tool_ids.get(name).copied()
221 }
222
223 pub fn has_tool(&self, name: &str) -> bool {
225 self.tool_ids.contains_key(name)
226 }
227
228 pub fn list_tools(&self) -> Vec<&str> {
230 self.tool_ids.keys().map(|s| s.as_str()).collect()
231 }
232
233 pub fn create_invocation(&self, tool_name: &str, client: &DcpClient) -> Result<ZctiBuilder> {
235 let tool_id = self
236 .get_tool_id(tool_name)
237 .ok_or_else(|| DrivenError::Config(format!("Tool not found: {}", tool_name)))?;
238 client.create_zcti_builder(tool_id)
239 }
240}
241
242impl Default for DrivenDcpBridge {
243 fn default() -> Self {
244 Self::new()
245 }
246}
247
248#[derive(Debug)]
252pub struct SpecScaffolder {
253 generator: GeneratorBridge,
255 spec_dir: PathBuf,
257 next_spec_number: u32,
259}
260
261impl SpecScaffolder {
262 pub fn new() -> Result<Self> {
264 Ok(Self {
265 generator: GeneratorBridge::new()?,
266 spec_dir: PathBuf::from(".driven/specs"),
267 next_spec_number: 1,
268 })
269 }
270
271 pub fn with_spec_dir(spec_dir: impl AsRef<Path>) -> Result<Self> {
273 Ok(Self {
274 generator: GeneratorBridge::new()?,
275 spec_dir: spec_dir.as_ref().to_path_buf(),
276 next_spec_number: 1,
277 })
278 }
279
280 pub fn with_generator(mut self, generator: GeneratorBridge) -> Self {
282 self.generator = generator;
283 self
284 }
285
286 pub fn initialize(&mut self) -> Result<()> {
288 self.generator.initialize()?;
289
290 if self.spec_dir.exists() {
292 let mut max_num = 0u32;
293 for entry in std::fs::read_dir(&self.spec_dir).map_err(DrivenError::Io)? {
294 let entry = entry.map_err(DrivenError::Io)?;
295 if entry.path().is_dir() {
296 if let Some(name) = entry.file_name().to_str() {
297 if let Some(num_str) = name.split('-').next() {
299 if let Ok(num) = num_str.parse::<u32>() {
300 max_num = max_num.max(num);
301 }
302 }
303 }
304 }
305 }
306 self.next_spec_number = max_num + 1;
307 }
308
309 Ok(())
310 }
311
312 pub fn create_spec(
314 &mut self,
315 name: &str,
316 params: &GenerateParams,
317 ) -> Result<SpecScaffoldResult> {
318 let spec_id = format!("{:03}-{}", self.next_spec_number, name);
319 self.next_spec_number += 1;
320
321 self.create_spec_with_id(&spec_id, params)
322 }
323
324 pub fn create_spec_with_id(
326 &self,
327 spec_id: &str,
328 params: &GenerateParams,
329 ) -> Result<SpecScaffoldResult> {
330 let files = self.generator.generate_spec_scaffold(spec_id, params)?;
332
333 let written = self.generator.write_files(&files)?;
335 let written_count = written.len();
336 let files_skipped = files.len() - written_count;
337
338 Ok(SpecScaffoldResult {
339 spec_id: spec_id.to_string(),
340 spec_dir: self.spec_dir.join(spec_id),
341 files_created: written,
342 files_skipped,
343 })
344 }
345
346 pub fn next_spec_number(&self) -> u32 {
348 self.next_spec_number
349 }
350
351 pub fn spec_dir(&self) -> &Path {
353 &self.spec_dir
354 }
355}
356
357impl Default for SpecScaffolder {
358 fn default() -> Self {
359 Self::new().unwrap_or_else(|_| Self {
360 generator: GeneratorBridge::default(),
361 spec_dir: PathBuf::from(".driven/specs"),
362 next_spec_number: 1,
363 })
364 }
365}
366
367#[derive(Debug, Clone)]
369pub struct SpecScaffoldResult {
370 pub spec_id: String,
372 pub spec_dir: PathBuf,
374 pub files_created: Vec<PathBuf>,
376 pub files_skipped: usize,
378}
379
380#[derive(Debug)]
384pub struct HookMessenger {
385 message_queue: Vec<HookMessage>,
387 use_dcp: bool,
389}
390
391#[derive(Debug, Clone)]
393pub struct HookMessage {
394 pub hook_id: String,
396 pub content: String,
398 pub context: HashMap<String, String>,
400 pub priority: u8,
402 pub timestamp: u64,
404}
405
406impl HookMessenger {
407 pub fn new() -> Self {
409 Self {
410 message_queue: Vec::new(),
411 use_dcp: true,
412 }
413 }
414
415 pub fn with_dcp(mut self, use_dcp: bool) -> Self {
417 self.use_dcp = use_dcp;
418 self
419 }
420
421 pub fn create_message(&self, hook: &AgentHook, context: &HookContext) -> HookMessage {
423 let mut ctx_map = HashMap::new();
424
425 if let Some(file) = &context.file_path {
427 ctx_map.insert("file".to_string(), file.to_string_lossy().to_string());
428 }
429 if let Some(name) = &context.file_name {
430 ctx_map.insert("file_name".to_string(), name.clone());
431 }
432 if let Some(ext) = &context.file_ext {
433 ctx_map.insert("file_ext".to_string(), ext.clone());
434 }
435
436 for (k, v) in &context.variables {
438 ctx_map.insert(k.clone(), v.clone());
439 }
440
441 HookMessage {
442 hook_id: hook.id.clone(),
443 content: hook.action.message.clone(),
444 context: ctx_map,
445 priority: hook.priority,
446 timestamp: std::time::SystemTime::now()
447 .duration_since(std::time::UNIX_EPOCH)
448 .map(|d| d.as_secs())
449 .unwrap_or(0),
450 }
451 }
452
453 pub fn queue_message(&mut self, message: HookMessage) {
455 self.message_queue.push(message);
456 self.message_queue.sort_by_key(|m| m.priority);
458 }
459
460 pub fn send_message(&self, _client: &DcpClient, message: &HookMessage) -> Result<DcpMessage> {
462 if self.use_dcp {
463 self.send_dcp_message(message)
464 } else {
465 self.send_mcp_message(message)
466 }
467 }
468
469 fn send_dcp_message(&self, message: &HookMessage) -> Result<DcpMessage> {
471 let payload = self.serialize_message(message)?;
473
474 Ok(DcpMessage::new(MessageType::Prompt, 0, payload))
476 }
477
478 fn send_mcp_message(&self, message: &HookMessage) -> Result<DcpMessage> {
480 let json = serde_json::json!({
482 "hook_id": message.hook_id,
483 "content": message.content,
484 "context": message.context,
485 "priority": message.priority,
486 "timestamp": message.timestamp,
487 });
488
489 let payload = serde_json::to_vec(&json)
490 .map_err(|e| DrivenError::Format(format!("Failed to serialize: {}", e)))?;
491
492 Ok(DcpMessage::new(MessageType::Prompt, 0, payload))
493 }
494
495 fn serialize_message(&self, message: &HookMessage) -> Result<Vec<u8>> {
497 let mut buf = Vec::new();
512
513 buf.extend_from_slice(&(message.hook_id.len() as u32).to_le_bytes());
515 buf.extend_from_slice(message.hook_id.as_bytes());
516
517 buf.extend_from_slice(&(message.content.len() as u32).to_le_bytes());
519 buf.extend_from_slice(message.content.as_bytes());
520
521 buf.extend_from_slice(&(message.context.len() as u32).to_le_bytes());
523 for (k, v) in &message.context {
524 buf.extend_from_slice(&(k.len() as u32).to_le_bytes());
525 buf.extend_from_slice(k.as_bytes());
526 buf.extend_from_slice(&(v.len() as u32).to_le_bytes());
527 buf.extend_from_slice(v.as_bytes());
528 }
529
530 buf.push(message.priority);
532 buf.extend_from_slice(&message.timestamp.to_le_bytes());
533
534 Ok(buf)
535 }
536
537 pub fn deserialize_message(&self, data: &[u8]) -> Result<HookMessage> {
539 let mut offset = 0;
540
541 let read_u32 = |data: &[u8], offset: &mut usize| -> Result<u32> {
543 if *offset + 4 > data.len() {
544 return Err(DrivenError::InvalidBinary("Insufficient data".to_string()));
545 }
546 let bytes: [u8; 4] = data[*offset..*offset + 4].try_into().unwrap();
547 *offset += 4;
548 Ok(u32::from_le_bytes(bytes))
549 };
550
551 let read_string = |data: &[u8], offset: &mut usize| -> Result<String> {
553 let len = read_u32(data, offset)? as usize;
554 if *offset + len > data.len() {
555 return Err(DrivenError::InvalidBinary("Insufficient data".to_string()));
556 }
557 let s = std::str::from_utf8(&data[*offset..*offset + len])
558 .map_err(|e| DrivenError::InvalidBinary(format!("Invalid UTF-8: {}", e)))?
559 .to_string();
560 *offset += len;
561 Ok(s)
562 };
563
564 let hook_id = read_string(data, &mut offset)?;
566
567 let content = read_string(data, &mut offset)?;
569
570 let ctx_count = read_u32(data, &mut offset)? as usize;
572 let mut context = HashMap::new();
573 for _ in 0..ctx_count {
574 let key = read_string(data, &mut offset)?;
575 let value = read_string(data, &mut offset)?;
576 context.insert(key, value);
577 }
578
579 if offset >= data.len() {
581 return Err(DrivenError::InvalidBinary(
582 "Insufficient data for priority".to_string(),
583 ));
584 }
585 let priority = data[offset];
586 offset += 1;
587
588 if offset + 8 > data.len() {
590 return Err(DrivenError::InvalidBinary(
591 "Insufficient data for timestamp".to_string(),
592 ));
593 }
594 let timestamp_bytes: [u8; 8] = data[offset..offset + 8].try_into().unwrap();
595 let timestamp = u64::from_le_bytes(timestamp_bytes);
596
597 Ok(HookMessage {
598 hook_id,
599 content,
600 context,
601 priority,
602 timestamp,
603 })
604 }
605
606 pub fn flush(&mut self, client: &DcpClient) -> Result<Vec<DcpMessage>> {
608 let messages: Vec<_> = self.message_queue.drain(..).collect();
609 let mut results = Vec::new();
610
611 for message in messages {
612 results.push(self.send_message(client, &message)?);
613 }
614
615 Ok(results)
616 }
617
618 pub fn queue_len(&self) -> usize {
620 self.message_queue.len()
621 }
622
623 pub fn clear_queue(&mut self) {
625 self.message_queue.clear();
626 }
627}
628
629impl Default for HookMessenger {
630 fn default() -> Self {
631 Self::new()
632 }
633}
634
635#[derive(Debug)]
642pub struct BinaryFormatChecker {
643 supported_versions: Vec<u16>,
645}
646
647#[repr(C)]
649#[derive(Debug, Clone, Copy)]
650pub struct CrossCrateHeader {
651 pub magic: [u8; 4],
653 pub version: u16,
655 pub source_crate: u8,
657 pub flags: u8,
659 pub payload_len: u32,
661 pub checksum: [u8; 8],
663}
664
665#[repr(u8)]
667#[derive(Debug, Clone, Copy, PartialEq, Eq)]
668pub enum SourceCrate {
669 Driven = 1,
671 Generator = 2,
673 Dcp = 3,
675 Unknown = 0,
677}
678
679impl CrossCrateHeader {
680 pub const MAGIC: [u8; 4] = [0x44, 0x58, 0xE2, 0x43]; pub const SIZE: usize = std::mem::size_of::<Self>();
685
686 pub const CURRENT_VERSION: u16 = 1;
688
689 pub fn new(source: SourceCrate, payload_len: u32) -> Self {
691 Self {
692 magic: Self::MAGIC,
693 version: Self::CURRENT_VERSION,
694 source_crate: source as u8,
695 flags: 0,
696 payload_len,
697 checksum: [0; 8],
698 }
699 }
700
701 pub fn with_checksum(source: SourceCrate, payload: &[u8]) -> Self {
703 let mut header = Self::new(source, payload.len() as u32);
704 header.checksum = Self::compute_checksum(payload);
705 header
706 }
707
708 pub fn compute_checksum(payload: &[u8]) -> [u8; 8] {
710 let hash = blake3::hash(payload);
711 let mut checksum = [0u8; 8];
712 checksum.copy_from_slice(&hash.as_bytes()[..8]);
713 checksum
714 }
715
716 pub fn verify_checksum(&self, payload: &[u8]) -> bool {
718 let expected = Self::compute_checksum(payload);
719 self.checksum == expected
720 }
721
722 pub fn is_valid_magic(&self) -> bool {
724 self.magic == Self::MAGIC
725 }
726
727 pub fn source(&self) -> SourceCrate {
729 match self.source_crate {
730 1 => SourceCrate::Driven,
731 2 => SourceCrate::Generator,
732 3 => SourceCrate::Dcp,
733 _ => SourceCrate::Unknown,
734 }
735 }
736
737 pub fn as_bytes(&self) -> &[u8] {
739 unsafe { std::slice::from_raw_parts(self as *const Self as *const u8, Self::SIZE) }
740 }
741
742 pub fn from_bytes(bytes: &[u8]) -> Result<&Self> {
744 if bytes.len() < Self::SIZE {
745 return Err(DrivenError::InvalidBinary(format!(
746 "Insufficient data for header: {} < {}",
747 bytes.len(),
748 Self::SIZE
749 )));
750 }
751
752 let header = unsafe { &*(bytes.as_ptr() as *const Self) };
753
754 if !header.is_valid_magic() {
755 return Err(DrivenError::InvalidBinary(
756 "Invalid magic bytes".to_string(),
757 ));
758 }
759
760 Ok(header)
761 }
762}
763
764impl BinaryFormatChecker {
765 pub fn new() -> Self {
767 Self {
768 supported_versions: vec![1],
769 }
770 }
771
772 pub fn is_version_supported(&self, version: u16) -> bool {
774 self.supported_versions.contains(&version)
775 }
776
777 pub fn validate(&self, data: &[u8]) -> Result<ValidationResult> {
779 let header = CrossCrateHeader::from_bytes(data)?;
781
782 if !self.is_version_supported(header.version) {
784 return Ok(ValidationResult {
785 valid: false,
786 source: header.source(),
787 version: header.version,
788 payload_len: header.payload_len,
789 checksum_valid: false,
790 error: Some(format!("Unsupported version: {}", header.version)),
791 });
792 }
793
794 let expected_len = CrossCrateHeader::SIZE + header.payload_len as usize;
796 if data.len() < expected_len {
797 return Ok(ValidationResult {
798 valid: false,
799 source: header.source(),
800 version: header.version,
801 payload_len: header.payload_len,
802 checksum_valid: false,
803 error: Some(format!(
804 "Insufficient data: expected {}, got {}",
805 expected_len,
806 data.len()
807 )),
808 });
809 }
810
811 let payload = &data[CrossCrateHeader::SIZE..expected_len];
813 let checksum_valid = header.verify_checksum(payload);
814
815 Ok(ValidationResult {
816 valid: checksum_valid,
817 source: header.source(),
818 version: header.version,
819 payload_len: header.payload_len,
820 checksum_valid,
821 error: if checksum_valid {
822 None
823 } else {
824 Some("Checksum mismatch".to_string())
825 },
826 })
827 }
828
829 pub fn wrap(&self, source: SourceCrate, payload: &[u8]) -> Vec<u8> {
831 let header = CrossCrateHeader::with_checksum(source, payload);
832 let mut result = Vec::with_capacity(CrossCrateHeader::SIZE + payload.len());
833 result.extend_from_slice(header.as_bytes());
834 result.extend_from_slice(payload);
835 result
836 }
837
838 pub fn unwrap(&self, data: &[u8]) -> Result<(SourceCrate, Vec<u8>)> {
840 let result = self.validate(data)?;
841
842 if !result.valid {
843 return Err(DrivenError::InvalidBinary(
844 result.error.unwrap_or_else(|| "Unknown error".to_string()),
845 ));
846 }
847
848 let payload = data
849 [CrossCrateHeader::SIZE..CrossCrateHeader::SIZE + result.payload_len as usize]
850 .to_vec();
851 Ok((result.source, payload))
852 }
853}
854
855impl Default for BinaryFormatChecker {
856 fn default() -> Self {
857 Self::new()
858 }
859}
860
861#[derive(Debug, Clone)]
863pub struct ValidationResult {
864 pub valid: bool,
866 pub source: SourceCrate,
868 pub version: u16,
870 pub payload_len: u32,
872 pub checksum_valid: bool,
874 pub error: Option<String>,
876}
877
878#[cfg(test)]
881mod tests {
882 use super::*;
883
884 #[test]
887 fn test_driven_dcp_bridge_creation() {
888 let bridge = DrivenDcpBridge::new();
889 assert!(!bridge.initialized);
890 assert!(bridge.tool_ids.is_empty());
891 }
892
893 #[test]
894 fn test_driven_tools_list() {
895 let bridge = DrivenDcpBridge::new();
896 let tools = bridge.get_driven_tools();
897
898 assert!(
900 tools
901 .iter()
902 .any(|t| t.category == DrivenToolCategory::Rules)
903 );
904 assert!(
905 tools
906 .iter()
907 .any(|t| t.category == DrivenToolCategory::Specs)
908 );
909 assert!(
910 tools
911 .iter()
912 .any(|t| t.category == DrivenToolCategory::Hooks)
913 );
914 assert!(
915 tools
916 .iter()
917 .any(|t| t.category == DrivenToolCategory::Steering)
918 );
919 assert!(
920 tools
921 .iter()
922 .any(|t| t.category == DrivenToolCategory::Templates)
923 );
924 }
925
926 #[test]
927 fn test_schema_hash_determinism() {
928 let bridge = DrivenDcpBridge::new();
929 let tool = DrivenTool {
930 name: "test.tool".to_string(),
931 description: "A test tool".to_string(),
932 category: DrivenToolCategory::Rules,
933 capabilities: 0x1234,
934 };
935
936 let hash1 = bridge.compute_schema_hash(&tool);
937 let hash2 = bridge.compute_schema_hash(&tool);
938
939 assert_eq!(hash1, hash2);
940 }
941
942 #[test]
945 fn test_spec_scaffolder_creation() {
946 let scaffolder = SpecScaffolder::new().unwrap();
947 assert_eq!(scaffolder.next_spec_number(), 1);
948 }
949
950 #[test]
951 fn test_spec_scaffolder_custom_dir() {
952 let scaffolder = SpecScaffolder::with_spec_dir("custom/specs").unwrap();
953 assert_eq!(scaffolder.spec_dir(), Path::new("custom/specs"));
954 }
955
956 #[test]
959 fn test_hook_messenger_creation() {
960 let messenger = HookMessenger::new();
961 assert!(messenger.use_dcp);
962 assert_eq!(messenger.queue_len(), 0);
963 }
964
965 #[test]
966 fn test_hook_message_serialization_roundtrip() {
967 let messenger = HookMessenger::new();
968
969 let mut context = HashMap::new();
970 context.insert("file".to_string(), "test.rs".to_string());
971 context.insert("branch".to_string(), "main".to_string());
972
973 let message = HookMessage {
974 hook_id: "test-hook".to_string(),
975 content: "Test message content".to_string(),
976 context,
977 priority: 50,
978 timestamp: 1234567890,
979 };
980
981 let serialized = messenger.serialize_message(&message).unwrap();
982 let deserialized = messenger.deserialize_message(&serialized).unwrap();
983
984 assert_eq!(deserialized.hook_id, message.hook_id);
985 assert_eq!(deserialized.content, message.content);
986 assert_eq!(deserialized.priority, message.priority);
987 assert_eq!(deserialized.timestamp, message.timestamp);
988 assert_eq!(deserialized.context.len(), message.context.len());
989 }
990
991 #[test]
992 fn test_hook_message_queue() {
993 let mut messenger = HookMessenger::new();
994
995 messenger.queue_message(HookMessage {
997 hook_id: "low".to_string(),
998 content: "Low priority".to_string(),
999 context: HashMap::new(),
1000 priority: 100,
1001 timestamp: 0,
1002 });
1003
1004 messenger.queue_message(HookMessage {
1005 hook_id: "high".to_string(),
1006 content: "High priority".to_string(),
1007 context: HashMap::new(),
1008 priority: 10,
1009 timestamp: 0,
1010 });
1011
1012 messenger.queue_message(HookMessage {
1013 hook_id: "medium".to_string(),
1014 content: "Medium priority".to_string(),
1015 context: HashMap::new(),
1016 priority: 50,
1017 timestamp: 0,
1018 });
1019
1020 assert_eq!(messenger.queue_len(), 3);
1021
1022 assert_eq!(messenger.message_queue[0].hook_id, "high");
1024 assert_eq!(messenger.message_queue[1].hook_id, "medium");
1025 assert_eq!(messenger.message_queue[2].hook_id, "low");
1026 }
1027
1028 #[test]
1031 fn test_cross_crate_header_creation() {
1032 let header = CrossCrateHeader::new(SourceCrate::Driven, 100);
1033
1034 assert!(header.is_valid_magic());
1035 assert_eq!(header.version, CrossCrateHeader::CURRENT_VERSION);
1036 assert_eq!(header.source(), SourceCrate::Driven);
1037 assert_eq!(header.payload_len, 100);
1038 }
1039
1040 #[test]
1041 fn test_cross_crate_header_checksum() {
1042 let payload = b"test payload data";
1043 let header = CrossCrateHeader::with_checksum(SourceCrate::Generator, payload);
1044
1045 assert!(header.verify_checksum(payload));
1046 assert!(!header.verify_checksum(b"different data"));
1047 }
1048
1049 #[test]
1050 fn test_binary_format_wrap_unwrap() {
1051 let checker = BinaryFormatChecker::new();
1052 let payload = b"test payload for cross-crate transfer";
1053
1054 let wrapped = checker.wrap(SourceCrate::Dcp, payload);
1055 let (source, unwrapped) = checker.unwrap(&wrapped).unwrap();
1056
1057 assert_eq!(source, SourceCrate::Dcp);
1058 assert_eq!(unwrapped, payload);
1059 }
1060
1061 #[test]
1062 fn test_binary_format_validation() {
1063 let checker = BinaryFormatChecker::new();
1064 let payload = b"validation test payload";
1065
1066 let wrapped = checker.wrap(SourceCrate::Driven, payload);
1067 let result = checker.validate(&wrapped).unwrap();
1068
1069 assert!(result.valid);
1070 assert!(result.checksum_valid);
1071 assert_eq!(result.source, SourceCrate::Driven);
1072 assert_eq!(result.payload_len, payload.len() as u32);
1073 assert!(result.error.is_none());
1074 }
1075
1076 #[test]
1077 fn test_binary_format_invalid_magic() {
1078 let checker = BinaryFormatChecker::new();
1079 let invalid_data = vec![0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00];
1080
1081 let result = checker.validate(&invalid_data);
1082 assert!(result.is_err());
1083 }
1084
1085 #[test]
1086 fn test_binary_format_corrupted_checksum() {
1087 let checker = BinaryFormatChecker::new();
1088 let payload = b"test payload";
1089
1090 let mut wrapped = checker.wrap(SourceCrate::Driven, payload);
1091 if wrapped.len() > CrossCrateHeader::SIZE {
1093 wrapped[CrossCrateHeader::SIZE] ^= 0xFF;
1094 }
1095
1096 let result = checker.validate(&wrapped).unwrap();
1097 assert!(!result.valid);
1098 assert!(!result.checksum_valid);
1099 }
1100}
1101
1102#[cfg(test)]
1105mod property_tests {
1106 use super::*;
1107 use proptest::prelude::*;
1108
1109 proptest! {
1114 #[test]
1116 fn prop_hook_message_roundtrip(
1117 hook_id in "[a-z][a-z0-9-]{0,30}",
1118 content in ".{0,1000}",
1119 priority in 0u8..=255u8,
1120 timestamp in 0u64..=u64::MAX,
1121 ) {
1122 let messenger = HookMessenger::new();
1123
1124 let message = HookMessage {
1125 hook_id: hook_id.clone(),
1126 content: content.clone(),
1127 context: HashMap::new(),
1128 priority,
1129 timestamp,
1130 };
1131
1132 let serialized = messenger.serialize_message(&message).unwrap();
1133 let deserialized = messenger.deserialize_message(&serialized).unwrap();
1134
1135 prop_assert_eq!(deserialized.hook_id, hook_id);
1136 prop_assert_eq!(deserialized.content, content);
1137 prop_assert_eq!(deserialized.priority, priority);
1138 prop_assert_eq!(deserialized.timestamp, timestamp);
1139 }
1140
1141 #[test]
1143 fn prop_binary_format_roundtrip(
1144 payload in prop::collection::vec(any::<u8>(), 0..10000),
1145 source in prop_oneof![
1146 Just(SourceCrate::Driven),
1147 Just(SourceCrate::Generator),
1148 Just(SourceCrate::Dcp),
1149 ],
1150 ) {
1151 let checker = BinaryFormatChecker::new();
1152
1153 let wrapped = checker.wrap(source, &payload);
1154 let (unwrapped_source, unwrapped_payload) = checker.unwrap(&wrapped).unwrap();
1155
1156 prop_assert_eq!(unwrapped_source, source);
1157 prop_assert_eq!(unwrapped_payload, payload);
1158 }
1159
1160 #[test]
1162 fn prop_checksum_deterministic(
1163 payload in prop::collection::vec(any::<u8>(), 0..10000),
1164 ) {
1165 let checksum1 = CrossCrateHeader::compute_checksum(&payload);
1166 let checksum2 = CrossCrateHeader::compute_checksum(&payload);
1167
1168 prop_assert_eq!(checksum1, checksum2);
1169 }
1170
1171 #[test]
1173 fn prop_schema_hash_deterministic(
1174 name in "[a-z][a-z0-9.]{0,50}",
1175 description in ".{0,200}",
1176 capabilities in 0u64..=u64::MAX,
1177 ) {
1178 let bridge = DrivenDcpBridge::new();
1179
1180 let tool = DrivenTool {
1181 name: name.clone(),
1182 description: description.clone(),
1183 category: DrivenToolCategory::Rules,
1184 capabilities,
1185 };
1186
1187 let hash1 = bridge.compute_schema_hash(&tool);
1188 let hash2 = bridge.compute_schema_hash(&tool);
1189
1190 prop_assert_eq!(hash1, hash2);
1191 }
1192
1193 #[test]
1195 fn prop_message_queue_priority_order(
1196 priorities in prop::collection::vec(0u8..=255u8, 1..20),
1197 ) {
1198 let mut messenger = HookMessenger::new();
1199
1200 for (i, priority) in priorities.iter().enumerate() {
1201 messenger.queue_message(HookMessage {
1202 hook_id: format!("hook-{}", i),
1203 content: String::new(),
1204 context: HashMap::new(),
1205 priority: *priority,
1206 timestamp: 0,
1207 });
1208 }
1209
1210 for i in 1..messenger.message_queue.len() {
1212 prop_assert!(
1213 messenger.message_queue[i - 1].priority <= messenger.message_queue[i].priority,
1214 "Messages not sorted by priority"
1215 );
1216 }
1217 }
1218
1219 #[test]
1221 fn prop_corruption_detection(
1222 payload in prop::collection::vec(any::<u8>(), 1..1000),
1223 corrupt_offset in 0usize..1000usize,
1224 ) {
1225 let checker = BinaryFormatChecker::new();
1226 let mut wrapped = checker.wrap(SourceCrate::Driven, &payload);
1227
1228 let payload_start = CrossCrateHeader::SIZE;
1230 if corrupt_offset < payload.len() {
1231 let actual_offset = payload_start + corrupt_offset;
1232 if actual_offset < wrapped.len() {
1233 wrapped[actual_offset] ^= 0xFF;
1234
1235 let result = checker.validate(&wrapped).unwrap();
1236 prop_assert!(!result.checksum_valid, "Corruption not detected");
1237 }
1238 }
1239 }
1240 }
1241}