1pub const AGI_MAGIC: u32 = 0x5256_4147;
15
16pub const AGI_HEADER_SIZE: usize = 64;
18
19pub const AGI_MAX_CONTAINER_SIZE: u64 = 16 * 1024 * 1024 * 1024;
21
22pub const AGI_HAS_KERNEL: u16 = 1 << 0;
26pub const AGI_HAS_WASM: u16 = 1 << 1;
28pub const AGI_HAS_ORCHESTRATOR: u16 = 1 << 2;
30pub const AGI_HAS_WORLD_MODEL: u16 = 1 << 3;
32pub const AGI_HAS_EVAL: u16 = 1 << 4;
34pub const AGI_HAS_SKILLS: u16 = 1 << 5;
36pub const AGI_HAS_WITNESS: u16 = 1 << 6;
38pub const AGI_SIGNED: u16 = 1 << 7;
40pub const AGI_REPLAY_CAPABLE: u16 = 1 << 8;
42pub const AGI_OFFLINE_CAPABLE: u16 = 1 << 9;
44pub const AGI_HAS_TOOLS: u16 = 1 << 10;
46pub const AGI_HAS_COHERENCE_GATES: u16 = 1 << 11;
48pub const AGI_HAS_DOMAIN_EXPANSION: u16 = 1 << 12;
50
51pub const AGI_TAG_CONTAINER_ID: u16 = 0x0100;
55pub const AGI_TAG_BUILD_ID: u16 = 0x0101;
57pub const AGI_TAG_MODEL_ID: u16 = 0x0102;
59pub const AGI_TAG_POLICY: u16 = 0x0103;
61pub const AGI_TAG_ORCHESTRATOR: u16 = 0x0104;
63pub const AGI_TAG_TOOL_REGISTRY: u16 = 0x0105;
65pub const AGI_TAG_AGENT_PROMPTS: u16 = 0x0106;
67pub const AGI_TAG_EVAL_TASKS: u16 = 0x0107;
69pub const AGI_TAG_EVAL_GRADERS: u16 = 0x0108;
71pub const AGI_TAG_SKILL_LIBRARY: u16 = 0x0109;
73pub const AGI_TAG_REPLAY_SCRIPT: u16 = 0x010A;
75pub const AGI_TAG_KERNEL_CONFIG: u16 = 0x010B;
77pub const AGI_TAG_NETWORK_CONFIG: u16 = 0x010C;
79pub const AGI_TAG_COHERENCE_CONFIG: u16 = 0x010D;
81pub const AGI_TAG_PROJECT_INSTRUCTIONS: u16 = 0x010E;
83pub const AGI_TAG_DEPENDENCY_SNAPSHOT: u16 = 0x010F;
85pub const AGI_TAG_AUTHORITY_CONFIG: u16 = 0x0110;
87pub const AGI_TAG_DOMAIN_PROFILE: u16 = 0x0111;
89pub const AGI_TAG_TRANSFER_PRIOR: u16 = 0x0112;
91pub const AGI_TAG_POLICY_KERNEL: u16 = 0x0113;
93pub const AGI_TAG_COST_CURVE: u16 = 0x0114;
95pub const AGI_TAG_COUNTEREXAMPLES: u16 = 0x0115;
97
98#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103#[repr(u8)]
104pub enum ExecutionMode {
105 Replay = 0,
108 Verify = 1,
111 Live = 2,
113}
114
115impl TryFrom<u8> for ExecutionMode {
116 type Error = u8;
117
118 fn try_from(value: u8) -> Result<Self, Self::Error> {
119 match value {
120 0 => Ok(Self::Replay),
121 1 => Ok(Self::Verify),
122 2 => Ok(Self::Live),
123 other => Err(other),
124 }
125 }
126}
127
128#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136#[repr(u8)]
137pub enum AuthorityLevel {
138 ReadOnly = 0,
140 WriteMemory = 1,
143 ExecuteTools = 2,
146 WriteExternal = 3,
149}
150
151impl TryFrom<u8> for AuthorityLevel {
152 type Error = u8;
153
154 fn try_from(value: u8) -> Result<Self, Self::Error> {
155 match value {
156 0 => Ok(Self::ReadOnly),
157 1 => Ok(Self::WriteMemory),
158 2 => Ok(Self::ExecuteTools),
159 3 => Ok(Self::WriteExternal),
160 other => Err(other),
161 }
162 }
163}
164
165impl AuthorityLevel {
166 pub const fn default_for_mode(mode: ExecutionMode) -> Self {
168 match mode {
169 ExecutionMode::Replay => Self::ReadOnly,
170 ExecutionMode::Verify => Self::ExecuteTools,
171 ExecutionMode::Live => Self::WriteMemory,
172 }
173 }
174
175 pub const fn permits(&self, required: AuthorityLevel) -> bool {
177 (*self as u8) >= (required as u8)
178 }
179}
180
181#[derive(Clone, Copy, Debug, PartialEq, Eq)]
188pub struct ResourceBudget {
189 pub max_time_secs: u32,
191 pub max_tokens: u32,
193 pub max_cost_microdollars: u32,
195 pub max_tool_calls: u16,
197 pub max_external_writes: u16,
199}
200
201impl Default for ResourceBudget {
202 fn default() -> Self {
203 Self::DEFAULT
204 }
205}
206
207impl ResourceBudget {
208 pub const DEFAULT: Self = Self {
210 max_time_secs: 300,
211 max_tokens: 200_000,
212 max_cost_microdollars: 1_000_000,
213 max_tool_calls: 50,
214 max_external_writes: 0,
215 };
216
217 pub const EXTENDED: Self = Self {
219 max_time_secs: 1200,
220 max_tokens: 800_000,
221 max_cost_microdollars: 4_000_000,
222 max_tool_calls: 200,
223 max_external_writes: 10,
224 };
225
226 pub const MAX: Self = Self {
228 max_time_secs: 3600,
229 max_tokens: 1_000_000,
230 max_cost_microdollars: 10_000_000,
231 max_tool_calls: 500,
232 max_external_writes: 50,
233 };
234
235 pub const fn clamped(self) -> Self {
237 Self {
238 max_time_secs: if self.max_time_secs > Self::MAX.max_time_secs {
239 Self::MAX.max_time_secs
240 } else {
241 self.max_time_secs
242 },
243 max_tokens: if self.max_tokens > Self::MAX.max_tokens {
244 Self::MAX.max_tokens
245 } else {
246 self.max_tokens
247 },
248 max_cost_microdollars: if self.max_cost_microdollars
249 > Self::MAX.max_cost_microdollars
250 {
251 Self::MAX.max_cost_microdollars
252 } else {
253 self.max_cost_microdollars
254 },
255 max_tool_calls: if self.max_tool_calls > Self::MAX.max_tool_calls {
256 Self::MAX.max_tool_calls
257 } else {
258 self.max_tool_calls
259 },
260 max_external_writes: if self.max_external_writes
261 > Self::MAX.max_external_writes
262 {
263 Self::MAX.max_external_writes
264 } else {
265 self.max_external_writes
266 },
267 }
268 }
269}
270
271#[derive(Clone, Copy, Debug, PartialEq)]
279pub struct CoherenceThresholds {
280 pub min_coherence_score: f32,
283 pub max_contradiction_rate: f32,
286 pub max_rollback_ratio: f32,
289}
290
291impl Default for CoherenceThresholds {
292 fn default() -> Self {
293 Self::DEFAULT
294 }
295}
296
297impl CoherenceThresholds {
298 pub const DEFAULT: Self = Self {
300 min_coherence_score: 0.70,
301 max_contradiction_rate: 5.0,
302 max_rollback_ratio: 0.20,
303 };
304
305 pub const STRICT: Self = Self {
307 min_coherence_score: 0.85,
308 max_contradiction_rate: 2.0,
309 max_rollback_ratio: 0.10,
310 };
311
312 pub fn validate(&self) -> Result<(), ContainerError> {
314 if self.min_coherence_score < 0.0 || self.min_coherence_score > 1.0 {
315 return Err(ContainerError::InvalidConfig(
316 "min_coherence_score must be in [0.0, 1.0]",
317 ));
318 }
319 if self.max_contradiction_rate < 0.0 {
320 return Err(ContainerError::InvalidConfig(
321 "max_contradiction_rate must be >= 0.0",
322 ));
323 }
324 if self.max_rollback_ratio < 0.0 || self.max_rollback_ratio > 1.0 {
325 return Err(ContainerError::InvalidConfig(
326 "max_rollback_ratio must be in [0.0, 1.0]",
327 ));
328 }
329 Ok(())
330 }
331}
332
333#[derive(Clone, Copy, Debug, PartialEq, Eq)]
347#[repr(C)]
348pub struct AgiContainerHeader {
349 pub magic: u32,
351 pub version: u16,
353 pub flags: u16,
355 pub container_id: [u8; 16],
357 pub build_id: [u8; 16],
359 pub created_ns: u64,
361 pub model_id_hash: [u8; 8],
363 pub policy_hash: [u8; 8],
365}
366
367const _: () = assert!(core::mem::size_of::<AgiContainerHeader>() == 64);
369
370impl AgiContainerHeader {
371 pub const fn is_valid_magic(&self) -> bool {
373 self.magic == AGI_MAGIC
374 }
375
376 pub const fn is_signed(&self) -> bool {
378 self.flags & AGI_SIGNED != 0
379 }
380
381 pub const fn has_kernel(&self) -> bool {
383 self.flags & AGI_HAS_KERNEL != 0
384 }
385
386 pub const fn has_orchestrator(&self) -> bool {
388 self.flags & AGI_HAS_ORCHESTRATOR != 0
389 }
390
391 pub const fn is_replay_capable(&self) -> bool {
393 self.flags & AGI_REPLAY_CAPABLE != 0
394 }
395
396 pub const fn is_offline_capable(&self) -> bool {
398 self.flags & AGI_OFFLINE_CAPABLE != 0
399 }
400
401 pub const fn has_world_model(&self) -> bool {
403 self.flags & AGI_HAS_WORLD_MODEL != 0
404 }
405
406 pub const fn has_coherence_gates(&self) -> bool {
408 self.flags & AGI_HAS_COHERENCE_GATES != 0
409 }
410
411 pub const fn has_domain_expansion(&self) -> bool {
413 self.flags & AGI_HAS_DOMAIN_EXPANSION != 0
414 }
415
416 pub fn to_bytes(&self) -> [u8; AGI_HEADER_SIZE] {
418 let mut buf = [0u8; AGI_HEADER_SIZE];
419 buf[0..4].copy_from_slice(&self.magic.to_le_bytes());
420 buf[4..6].copy_from_slice(&self.version.to_le_bytes());
421 buf[6..8].copy_from_slice(&self.flags.to_le_bytes());
422 buf[8..24].copy_from_slice(&self.container_id);
423 buf[24..40].copy_from_slice(&self.build_id);
424 buf[40..48].copy_from_slice(&self.created_ns.to_le_bytes());
425 buf[48..56].copy_from_slice(&self.model_id_hash);
426 buf[56..64].copy_from_slice(&self.policy_hash);
427 buf
428 }
429
430 pub fn from_bytes(data: &[u8]) -> Result<Self, crate::RvfError> {
432 if data.len() < AGI_HEADER_SIZE {
433 return Err(crate::RvfError::SizeMismatch {
434 expected: AGI_HEADER_SIZE,
435 got: data.len(),
436 });
437 }
438 let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
439 if magic != AGI_MAGIC {
440 return Err(crate::RvfError::BadMagic {
441 expected: AGI_MAGIC,
442 got: magic,
443 });
444 }
445 let mut container_id = [0u8; 16];
446 container_id.copy_from_slice(&data[8..24]);
447 let mut build_id = [0u8; 16];
448 build_id.copy_from_slice(&data[24..40]);
449 let mut model_id_hash = [0u8; 8];
450 model_id_hash.copy_from_slice(&data[48..56]);
451 let mut policy_hash = [0u8; 8];
452 policy_hash.copy_from_slice(&data[56..64]);
453
454 Ok(Self {
455 magic,
456 version: u16::from_le_bytes([data[4], data[5]]),
457 flags: u16::from_le_bytes([data[6], data[7]]),
458 container_id,
459 build_id,
460 created_ns: u64::from_le_bytes([
461 data[40], data[41], data[42], data[43],
462 data[44], data[45], data[46], data[47],
463 ]),
464 model_id_hash,
465 policy_hash,
466 })
467 }
468}
469
470#[derive(Clone, Debug, Default)]
474pub struct ContainerSegments {
475 pub kernel_present: bool,
477 pub kernel_size: u64,
479 pub wasm_count: u16,
481 pub wasm_total_size: u64,
483 pub vec_segment_count: u16,
485 pub index_segment_count: u16,
487 pub witness_count: u32,
489 pub crypto_present: bool,
491 pub manifest_present: bool,
493 pub orchestrator_present: bool,
495 pub world_model_present: bool,
497 pub domain_expansion_present: bool,
499 pub total_size: u64,
501}
502
503impl ContainerSegments {
504 pub fn validate(&self, mode: ExecutionMode) -> Result<(), ContainerError> {
507 if !self.manifest_present {
509 return Err(ContainerError::MissingSegment("AGI manifest"));
510 }
511
512 if self.total_size > AGI_MAX_CONTAINER_SIZE {
514 return Err(ContainerError::TooLarge {
515 size: self.total_size,
516 });
517 }
518
519 match mode {
520 ExecutionMode::Replay => {
521 if self.witness_count == 0 {
523 return Err(ContainerError::MissingSegment("witness chain"));
524 }
525 }
526 ExecutionMode::Verify | ExecutionMode::Live => {
527 if !self.kernel_present && self.wasm_count == 0 {
529 return Err(ContainerError::MissingSegment(
530 "kernel or WASM runtime",
531 ));
532 }
533 if !self.world_model_present
535 && self.vec_segment_count == 0
536 && self.index_segment_count == 0
537 {
538 return Err(ContainerError::MissingSegment(
539 "world model (VEC or INDEX segments)",
540 ));
541 }
542 }
543 }
544
545 Ok(())
546 }
547
548 pub fn to_flags(&self) -> u16 {
550 let mut flags: u16 = 0;
551 if self.kernel_present {
552 flags |= AGI_HAS_KERNEL;
553 }
554 if self.wasm_count > 0 {
555 flags |= AGI_HAS_WASM;
556 }
557 if self.witness_count > 0 {
558 flags |= AGI_HAS_WITNESS;
559 }
560 if self.crypto_present {
561 flags |= AGI_SIGNED;
562 }
563 if self.orchestrator_present {
564 flags |= AGI_HAS_ORCHESTRATOR;
565 }
566 if self.world_model_present
567 || self.vec_segment_count > 0
568 || self.index_segment_count > 0
569 {
570 flags |= AGI_HAS_WORLD_MODEL;
571 }
572 if self.domain_expansion_present {
573 flags |= AGI_HAS_DOMAIN_EXPANSION;
574 }
575 flags
576 }
577}
578
579#[derive(Debug, PartialEq, Eq)]
581pub enum ContainerError {
582 MissingSegment(&'static str),
584 TooLarge { size: u64 },
586 InvalidConfig(&'static str),
588 SignatureInvalid,
590 InsufficientAuthority {
592 required: u8,
593 granted: u8,
594 },
595 BudgetExhausted(&'static str),
597}
598
599impl core::fmt::Display for ContainerError {
600 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
601 match self {
602 ContainerError::MissingSegment(s) => write!(f, "missing segment: {s}"),
603 ContainerError::TooLarge { size } => {
604 write!(f, "container too large: {size} bytes")
605 }
606 ContainerError::InvalidConfig(s) => write!(f, "invalid config: {s}"),
607 ContainerError::SignatureInvalid => {
608 write!(f, "signature verification failed")
609 }
610 ContainerError::InsufficientAuthority { required, granted } => {
611 write!(
612 f,
613 "insufficient authority: required level {required}, granted {granted}"
614 )
615 }
616 ContainerError::BudgetExhausted(resource) => {
617 write!(f, "resource budget exhausted: {resource}")
618 }
619 }
620 }
621}
622
623#[cfg(test)]
624mod tests {
625 use super::*;
626 use alloc::format;
627
628 #[test]
629 fn agi_header_size() {
630 assert_eq!(core::mem::size_of::<AgiContainerHeader>(), 64);
631 }
632
633 #[test]
634 fn agi_header_round_trip() {
635 let hdr = AgiContainerHeader {
636 magic: AGI_MAGIC,
637 version: 1,
638 flags: AGI_HAS_KERNEL | AGI_HAS_ORCHESTRATOR | AGI_HAS_WORLD_MODEL
639 | AGI_HAS_EVAL | AGI_SIGNED | AGI_REPLAY_CAPABLE,
640 container_id: [0x42; 16],
641 build_id: [0x43; 16],
642 created_ns: 1_700_000_000_000_000_000,
643 model_id_hash: [0xAA; 8],
644 policy_hash: [0xBB; 8],
645 };
646 let bytes = hdr.to_bytes();
647 assert_eq!(bytes.len(), AGI_HEADER_SIZE);
648 let decoded = AgiContainerHeader::from_bytes(&bytes).unwrap();
649 assert_eq!(decoded, hdr);
650 }
651
652 #[test]
653 fn agi_header_bad_magic() {
654 let mut bytes = [0u8; 64];
655 bytes[0..4].copy_from_slice(&0xDEADBEEFu32.to_le_bytes());
656 assert!(AgiContainerHeader::from_bytes(&bytes).is_err());
657 }
658
659 #[test]
660 fn agi_header_too_short() {
661 assert!(AgiContainerHeader::from_bytes(&[0u8; 32]).is_err());
662 }
663
664 #[test]
665 fn agi_flags() {
666 let hdr = AgiContainerHeader {
667 magic: AGI_MAGIC,
668 version: 1,
669 flags: AGI_HAS_KERNEL | AGI_HAS_ORCHESTRATOR | AGI_SIGNED,
670 container_id: [0; 16],
671 build_id: [0; 16],
672 created_ns: 0,
673 model_id_hash: [0; 8],
674 policy_hash: [0; 8],
675 };
676 assert!(hdr.has_kernel());
677 assert!(hdr.has_orchestrator());
678 assert!(hdr.is_signed());
679 assert!(!hdr.is_replay_capable());
680 assert!(!hdr.is_offline_capable());
681 assert!(!hdr.has_world_model());
682 assert!(!hdr.has_coherence_gates());
683 }
684
685 #[test]
686 fn execution_mode_round_trip() {
687 for raw in 0..=2u8 {
688 let m = ExecutionMode::try_from(raw).unwrap();
689 assert_eq!(m as u8, raw);
690 }
691 assert!(ExecutionMode::try_from(3).is_err());
692 }
693
694 #[test]
695 fn segments_validate_replay_needs_witness() {
696 let segs = ContainerSegments {
697 manifest_present: true,
698 witness_count: 0,
699 ..Default::default()
700 };
701 assert_eq!(
702 segs.validate(ExecutionMode::Replay),
703 Err(ContainerError::MissingSegment("witness chain"))
704 );
705 }
706
707 #[test]
708 fn segments_validate_live_needs_runtime() {
709 let segs = ContainerSegments {
710 manifest_present: true,
711 kernel_present: false,
712 wasm_count: 0,
713 ..Default::default()
714 };
715 assert_eq!(
716 segs.validate(ExecutionMode::Live),
717 Err(ContainerError::MissingSegment("kernel or WASM runtime"))
718 );
719 }
720
721 #[test]
722 fn segments_validate_live_needs_world_model() {
723 let segs = ContainerSegments {
724 manifest_present: true,
725 kernel_present: true,
726 vec_segment_count: 0,
727 index_segment_count: 0,
728 world_model_present: false,
729 ..Default::default()
730 };
731 assert_eq!(
732 segs.validate(ExecutionMode::Live),
733 Err(ContainerError::MissingSegment(
734 "world model (VEC or INDEX segments)"
735 ))
736 );
737 }
738
739 #[test]
740 fn segments_validate_live_with_kernel_and_world_model() {
741 let segs = ContainerSegments {
742 manifest_present: true,
743 kernel_present: true,
744 world_model_present: true,
745 ..Default::default()
746 };
747 assert!(segs.validate(ExecutionMode::Live).is_ok());
748 }
749
750 #[test]
751 fn segments_validate_live_with_wasm_and_vec() {
752 let segs = ContainerSegments {
753 manifest_present: true,
754 wasm_count: 2,
755 vec_segment_count: 1,
756 ..Default::default()
757 };
758 assert!(segs.validate(ExecutionMode::Live).is_ok());
759 }
760
761 #[test]
762 fn segments_validate_replay_with_witness() {
763 let segs = ContainerSegments {
764 manifest_present: true,
765 witness_count: 10,
766 ..Default::default()
767 };
768 assert!(segs.validate(ExecutionMode::Replay).is_ok());
769 }
770
771 #[test]
772 fn segments_validate_too_large() {
773 let segs = ContainerSegments {
774 manifest_present: true,
775 total_size: AGI_MAX_CONTAINER_SIZE + 1,
776 ..Default::default()
777 };
778 assert_eq!(
779 segs.validate(ExecutionMode::Replay),
780 Err(ContainerError::TooLarge {
781 size: AGI_MAX_CONTAINER_SIZE + 1
782 })
783 );
784 }
785
786 #[test]
787 fn segments_to_flags() {
788 let segs = ContainerSegments {
789 kernel_present: true,
790 wasm_count: 1,
791 witness_count: 5,
792 crypto_present: true,
793 orchestrator_present: true,
794 vec_segment_count: 3,
795 ..Default::default()
796 };
797 let flags = segs.to_flags();
798 assert_ne!(flags & AGI_HAS_KERNEL, 0);
799 assert_ne!(flags & AGI_HAS_WASM, 0);
800 assert_ne!(flags & AGI_HAS_WITNESS, 0);
801 assert_ne!(flags & AGI_SIGNED, 0);
802 assert_ne!(flags & AGI_HAS_ORCHESTRATOR, 0);
803 assert_ne!(flags & AGI_HAS_WORLD_MODEL, 0);
804 }
805
806 #[test]
807 fn container_error_display() {
808 let e = ContainerError::MissingSegment("kernel");
809 assert!(format!("{e}").contains("kernel"));
810 let e2 = ContainerError::TooLarge { size: 999 };
811 assert!(format!("{e2}").contains("999"));
812 let e3 = ContainerError::InsufficientAuthority {
813 required: 3,
814 granted: 1,
815 };
816 assert!(format!("{e3}").contains("required level 3"));
817 let e4 = ContainerError::BudgetExhausted("tokens");
818 assert!(format!("{e4}").contains("tokens"));
819 }
820
821 #[test]
824 fn authority_level_round_trip() {
825 for raw in 0..=3u8 {
826 let a = AuthorityLevel::try_from(raw).unwrap();
827 assert_eq!(a as u8, raw);
828 }
829 assert!(AuthorityLevel::try_from(4).is_err());
830 }
831
832 #[test]
833 fn authority_level_ordering() {
834 assert!(AuthorityLevel::ReadOnly < AuthorityLevel::WriteMemory);
835 assert!(AuthorityLevel::WriteMemory < AuthorityLevel::ExecuteTools);
836 assert!(AuthorityLevel::ExecuteTools < AuthorityLevel::WriteExternal);
837 }
838
839 #[test]
840 fn authority_permits() {
841 assert!(AuthorityLevel::WriteExternal.permits(AuthorityLevel::ReadOnly));
842 assert!(AuthorityLevel::WriteExternal.permits(AuthorityLevel::WriteExternal));
843 assert!(AuthorityLevel::ExecuteTools.permits(AuthorityLevel::WriteMemory));
844 assert!(!AuthorityLevel::ReadOnly.permits(AuthorityLevel::WriteMemory));
845 assert!(!AuthorityLevel::WriteMemory.permits(AuthorityLevel::ExecuteTools));
846 }
847
848 #[test]
849 fn authority_default_for_mode() {
850 assert_eq!(
851 AuthorityLevel::default_for_mode(ExecutionMode::Replay),
852 AuthorityLevel::ReadOnly
853 );
854 assert_eq!(
855 AuthorityLevel::default_for_mode(ExecutionMode::Verify),
856 AuthorityLevel::ExecuteTools
857 );
858 assert_eq!(
859 AuthorityLevel::default_for_mode(ExecutionMode::Live),
860 AuthorityLevel::WriteMemory
861 );
862 }
863
864 #[test]
867 fn resource_budget_default() {
868 let b = ResourceBudget::default();
869 assert_eq!(b.max_time_secs, 300);
870 assert_eq!(b.max_tokens, 200_000);
871 assert_eq!(b.max_cost_microdollars, 1_000_000);
872 assert_eq!(b.max_tool_calls, 50);
873 assert_eq!(b.max_external_writes, 0);
874 }
875
876 #[test]
877 fn resource_budget_clamped() {
878 let over = ResourceBudget {
879 max_time_secs: 999_999,
880 max_tokens: 999_999_999,
881 max_cost_microdollars: 999_999_999,
882 max_tool_calls: 60_000,
883 max_external_writes: 60_000,
884 };
885 let clamped = over.clamped();
886 assert_eq!(clamped.max_time_secs, ResourceBudget::MAX.max_time_secs);
887 assert_eq!(clamped.max_tokens, ResourceBudget::MAX.max_tokens);
888 assert_eq!(
889 clamped.max_cost_microdollars,
890 ResourceBudget::MAX.max_cost_microdollars
891 );
892 assert_eq!(clamped.max_tool_calls, ResourceBudget::MAX.max_tool_calls);
893 assert_eq!(
894 clamped.max_external_writes,
895 ResourceBudget::MAX.max_external_writes
896 );
897 }
898
899 #[test]
900 fn resource_budget_within_max_unchanged() {
901 let within = ResourceBudget::DEFAULT;
902 let clamped = within.clamped();
903 assert_eq!(clamped, within);
904 }
905
906 #[test]
909 fn coherence_thresholds_default() {
910 let ct = CoherenceThresholds::default();
911 assert!((ct.min_coherence_score - 0.70).abs() < f32::EPSILON);
912 assert!((ct.max_contradiction_rate - 5.0).abs() < f32::EPSILON);
913 assert!((ct.max_rollback_ratio - 0.20).abs() < f32::EPSILON);
914 }
915
916 #[test]
917 fn coherence_thresholds_strict() {
918 let ct = CoherenceThresholds::STRICT;
919 assert!((ct.min_coherence_score - 0.85).abs() < f32::EPSILON);
920 assert!((ct.max_contradiction_rate - 2.0).abs() < f32::EPSILON);
921 assert!((ct.max_rollback_ratio - 0.10).abs() < f32::EPSILON);
922 }
923
924 #[test]
925 fn coherence_thresholds_validate_valid() {
926 assert!(CoherenceThresholds::DEFAULT.validate().is_ok());
927 assert!(CoherenceThresholds::STRICT.validate().is_ok());
928 }
929
930 #[test]
931 fn coherence_thresholds_validate_bad_score() {
932 let ct = CoherenceThresholds {
933 min_coherence_score: 1.5,
934 ..CoherenceThresholds::DEFAULT
935 };
936 assert_eq!(
937 ct.validate(),
938 Err(ContainerError::InvalidConfig(
939 "min_coherence_score must be in [0.0, 1.0]"
940 ))
941 );
942 }
943
944 #[test]
945 fn coherence_thresholds_validate_negative_rate() {
946 let ct = CoherenceThresholds {
947 max_contradiction_rate: -1.0,
948 ..CoherenceThresholds::DEFAULT
949 };
950 assert_eq!(
951 ct.validate(),
952 Err(ContainerError::InvalidConfig(
953 "max_contradiction_rate must be >= 0.0"
954 ))
955 );
956 }
957
958 #[test]
959 fn coherence_thresholds_validate_bad_ratio() {
960 let ct = CoherenceThresholds {
961 max_rollback_ratio: 2.0,
962 ..CoherenceThresholds::DEFAULT
963 };
964 assert_eq!(
965 ct.validate(),
966 Err(ContainerError::InvalidConfig(
967 "max_rollback_ratio must be in [0.0, 1.0]"
968 ))
969 );
970 }
971}