Skip to main content

rvf_types/
agi_container.rs

1//! AGI Cognitive Container types (ADR-036).
2//!
3//! An AGI container is a single RVF file that packages the complete intelligence
4//! runtime: micro Linux kernel, Claude Code orchestrator config, Claude Flow
5//! swarm manager, RuVector world model, evaluation harness, witness chains,
6//! and tool adapters.
7//!
8//! Wire format: 64-byte `AgiContainerHeader` + TLV manifest sections.
9//! The header is stored as a META segment (SegmentType::Meta) in the RVF file,
10//! alongside the KERNEL_SEG, WASM_SEG, VEC_SEG, INDEX_SEG, WITNESS_SEG, and
11//! CRYPTO_SEG that hold the actual payload data.
12
13/// Magic bytes for AGI container manifest: "RVAG" (RuVector AGI).
14pub const AGI_MAGIC: u32 = 0x5256_4147;
15
16/// Size of the AGI container header in bytes.
17pub const AGI_HEADER_SIZE: usize = 64;
18
19/// Maximum container size: 16 GiB. Prevents unbounded resource consumption.
20pub const AGI_MAX_CONTAINER_SIZE: u64 = 16 * 1024 * 1024 * 1024;
21
22// --- Flags ---
23
24/// Container includes a KERNEL_SEG with micro Linux kernel.
25pub const AGI_HAS_KERNEL: u16 = 1 << 0;
26/// Container includes WASM_SEG modules.
27pub const AGI_HAS_WASM: u16 = 1 << 1;
28/// Container includes Claude Code + Claude Flow orchestrator config.
29pub const AGI_HAS_ORCHESTRATOR: u16 = 1 << 2;
30/// Container includes VEC_SEG + INDEX_SEG world model data.
31pub const AGI_HAS_WORLD_MODEL: u16 = 1 << 3;
32/// Container includes evaluation harness (task suite + graders).
33pub const AGI_HAS_EVAL: u16 = 1 << 4;
34/// Container includes promoted skill library.
35pub const AGI_HAS_SKILLS: u16 = 1 << 5;
36/// Container includes ADR-035 witness chain.
37pub const AGI_HAS_WITNESS: u16 = 1 << 6;
38/// Container is cryptographically signed (HMAC-SHA256 or Ed25519).
39pub const AGI_SIGNED: u16 = 1 << 7;
40/// All tool outputs stored — container supports replay mode.
41pub const AGI_REPLAY_CAPABLE: u16 = 1 << 8;
42/// Container can run without network (offline-first).
43pub const AGI_OFFLINE_CAPABLE: u16 = 1 << 9;
44/// Container includes MCP tool adapter registry.
45pub const AGI_HAS_TOOLS: u16 = 1 << 10;
46/// Container includes coherence gate configuration.
47pub const AGI_HAS_COHERENCE_GATES: u16 = 1 << 11;
48/// Container includes cross-domain transfer learning data.
49pub const AGI_HAS_DOMAIN_EXPANSION: u16 = 1 << 12;
50
51// --- TLV tags for the manifest payload ---
52
53/// Container UUID.
54pub const AGI_TAG_CONTAINER_ID: u16 = 0x0100;
55/// Build UUID.
56pub const AGI_TAG_BUILD_ID: u16 = 0x0101;
57/// Pinned model identifier (UTF-8 string, e.g. "claude-opus-4-6").
58pub const AGI_TAG_MODEL_ID: u16 = 0x0102;
59/// Serialized governance policy (binary, per ADR-035).
60pub const AGI_TAG_POLICY: u16 = 0x0103;
61/// Claude Code + Claude Flow orchestrator config (JSON or TOML).
62pub const AGI_TAG_ORCHESTRATOR: u16 = 0x0104;
63/// MCP tool adapter registry (JSON array of tool schemas).
64pub const AGI_TAG_TOOL_REGISTRY: u16 = 0x0105;
65/// Agent role prompts (one per agent type).
66pub const AGI_TAG_AGENT_PROMPTS: u16 = 0x0106;
67/// Evaluation task suite (JSON array of task specs).
68pub const AGI_TAG_EVAL_TASKS: u16 = 0x0107;
69/// Grading rules (JSON or binary grader config).
70pub const AGI_TAG_EVAL_GRADERS: u16 = 0x0108;
71/// Promoted skill library (serialized skill nodes).
72pub const AGI_TAG_SKILL_LIBRARY: u16 = 0x0109;
73/// Replay automation script.
74pub const AGI_TAG_REPLAY_SCRIPT: u16 = 0x010A;
75/// Kernel boot parameters (command line, initrd config).
76pub const AGI_TAG_KERNEL_CONFIG: u16 = 0x010B;
77/// Network configuration (ports, endpoints, TLS).
78pub const AGI_TAG_NETWORK_CONFIG: u16 = 0x010C;
79/// Coherence gate thresholds and rules.
80pub const AGI_TAG_COHERENCE_CONFIG: u16 = 0x010D;
81/// Claude.md project instructions.
82pub const AGI_TAG_PROJECT_INSTRUCTIONS: u16 = 0x010E;
83/// Dependency snapshot hashes (pinned repos, packages).
84pub const AGI_TAG_DEPENDENCY_SNAPSHOT: u16 = 0x010F;
85/// Authority level and resource budget configuration.
86pub const AGI_TAG_AUTHORITY_CONFIG: u16 = 0x0110;
87/// Target domain profile identifier.
88pub const AGI_TAG_DOMAIN_PROFILE: u16 = 0x0111;
89/// Cross-domain transfer prior (posterior summaries).
90pub const AGI_TAG_TRANSFER_PRIOR: u16 = 0x0112;
91/// Policy kernel configuration and performance history.
92pub const AGI_TAG_POLICY_KERNEL: u16 = 0x0113;
93/// Cost curve convergence and acceleration data.
94pub const AGI_TAG_COST_CURVE: u16 = 0x0114;
95/// Counterexample archive (failed solutions for future decisions).
96pub const AGI_TAG_COUNTEREXAMPLES: u16 = 0x0115;
97
98// --- Execution mode ---
99
100/// Container execution mode.
101#[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: no external tool calls, use stored receipts.
106    /// All graders must match exactly. Witness chain must match.
107    Replay = 0,
108    /// Verify: live tool calls, outputs stored and hashed.
109    /// Outputs must pass same tests. Costs within expected bounds.
110    Verify = 1,
111    /// Live: full autonomous operation with governance controls.
112    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// --- Authority level ---
129
130/// Authority level controlling what actions a container execution can perform.
131///
132/// Each action in the world model must reference a policy decision node
133/// that grants at least the required authority level.
134#[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    /// Read-only: query vectors, graphs, memories. No mutations.
139    ReadOnly = 0,
140    /// Write to internal memory: commit world model deltas behind
141    /// coherence gates. No external tool calls.
142    WriteMemory = 1,
143    /// Execute tools: run sandboxed tools (file read/write, tests,
144    /// code generation). External side effects gated by policy.
145    ExecuteTools = 2,
146    /// Write external: push code, create PRs, send messages, modify
147    /// infrastructure. Requires explicit policy grant per action class.
148    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    /// Default authority for the given execution mode.
167    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    /// Check if this authority level permits a given required level.
176    pub const fn permits(&self, required: AuthorityLevel) -> bool {
177        (*self as u8) >= (required as u8)
178    }
179}
180
181// --- Resource budgets ---
182
183/// Per-task resource budget with hard caps.
184///
185/// Budget exhaustion triggers graceful degradation: the task enters `Skipped`
186/// outcome with a `BudgetExhausted` postmortem in the witness bundle.
187#[derive(Clone, Copy, Debug, PartialEq, Eq)]
188pub struct ResourceBudget {
189    /// Maximum wall-clock time per task in seconds. Default: 300.
190    pub max_time_secs: u32,
191    /// Maximum total model tokens per task. Default: 200,000.
192    pub max_tokens: u32,
193    /// Maximum cost per task in microdollars. Default: 1,000,000 ($1.00).
194    pub max_cost_microdollars: u32,
195    /// Maximum tool calls per task. Default: 50.
196    pub max_tool_calls: u16,
197    /// Maximum external write actions per task. Default: 0.
198    pub max_external_writes: u16,
199}
200
201impl Default for ResourceBudget {
202    fn default() -> Self {
203        Self::DEFAULT
204    }
205}
206
207impl ResourceBudget {
208    /// Default resource budget for a single task.
209    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    /// Extended budget (4x default) for high-value tasks.
218    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    /// Maximum configurable budget (hard ceiling, not overridable).
227    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    /// Clamp this budget to not exceed the MAX limits.
236    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// --- Coherence thresholds ---
272
273/// Configurable coherence thresholds for structural health gating.
274///
275/// These map to ADR-033's quality framework: the coherence score is analogous
276/// to `ResponseQuality` -- it signals whether the system's internal state is
277/// trustworthy enough to act on.
278#[derive(Clone, Copy, Debug, PartialEq)]
279pub struct CoherenceThresholds {
280    /// Minimum coherence score (0.0 to 1.0). Below this, block all commits
281    /// and enter repair mode. Default: 0.70.
282    pub min_coherence_score: f32,
283    /// Maximum contradiction rate (contradictions per 100 events).
284    /// Above this, freeze skill promotion. Default: 5.0.
285    pub max_contradiction_rate: f32,
286    /// Maximum rollback ratio (fraction of tasks that required rollback).
287    /// Above this, halt Live execution; require human review. Default: 0.20.
288    pub max_rollback_ratio: f32,
289}
290
291impl Default for CoherenceThresholds {
292    fn default() -> Self {
293        Self::DEFAULT
294    }
295}
296
297impl CoherenceThresholds {
298    /// Default coherence thresholds.
299    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    /// Strict thresholds for production.
306    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    /// Validate that threshold values are within valid ranges.
313    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/// Wire-format AGI container header (exactly 64 bytes, `repr(C)`).
334///
335/// ```text
336/// Offset  Type        Field
337/// 0x00    u32         magic (0x52564147 "RVAG")
338/// 0x04    u16         version
339/// 0x06    u16         flags
340/// 0x08    [u8; 16]    container_id (UUID)
341/// 0x18    [u8; 16]    build_id (UUID)
342/// 0x28    u64         created_ns (UNIX epoch nanoseconds)
343/// 0x30    [u8; 8]     model_id_hash (SHA-256 truncated)
344/// 0x38    [u8; 8]     policy_hash (SHA-256 truncated)
345/// ```
346#[derive(Clone, Copy, Debug, PartialEq, Eq)]
347#[repr(C)]
348pub struct AgiContainerHeader {
349    /// Magic bytes: AGI_MAGIC.
350    pub magic: u32,
351    /// Format version (currently 1).
352    pub version: u16,
353    /// Bitfield flags indicating which segments are present.
354    pub flags: u16,
355    /// Unique container identifier (UUID).
356    pub container_id: [u8; 16],
357    /// Build identifier (UUID, changes on each repackaging).
358    pub build_id: [u8; 16],
359    /// Creation timestamp (nanoseconds since UNIX epoch).
360    pub created_ns: u64,
361    /// SHA-256 of the pinned model identifier, truncated to 8 bytes.
362    pub model_id_hash: [u8; 8],
363    /// SHA-256 of the governance policy, truncated to 8 bytes.
364    pub policy_hash: [u8; 8],
365}
366
367// Compile-time size assertion.
368const _: () = assert!(core::mem::size_of::<AgiContainerHeader>() == 64);
369
370impl AgiContainerHeader {
371    /// Check magic bytes.
372    pub const fn is_valid_magic(&self) -> bool {
373        self.magic == AGI_MAGIC
374    }
375
376    /// Check if the container is signed.
377    pub const fn is_signed(&self) -> bool {
378        self.flags & AGI_SIGNED != 0
379    }
380
381    /// Check if the container has a micro Linux kernel.
382    pub const fn has_kernel(&self) -> bool {
383        self.flags & AGI_HAS_KERNEL != 0
384    }
385
386    /// Check if the container has an orchestrator config.
387    pub const fn has_orchestrator(&self) -> bool {
388        self.flags & AGI_HAS_ORCHESTRATOR != 0
389    }
390
391    /// Check if the container supports replay mode.
392    pub const fn is_replay_capable(&self) -> bool {
393        self.flags & AGI_REPLAY_CAPABLE != 0
394    }
395
396    /// Check if the container can run offline.
397    pub const fn is_offline_capable(&self) -> bool {
398        self.flags & AGI_OFFLINE_CAPABLE != 0
399    }
400
401    /// Check if the container has a world model (VEC + INDEX segments).
402    pub const fn has_world_model(&self) -> bool {
403        self.flags & AGI_HAS_WORLD_MODEL != 0
404    }
405
406    /// Check if the container has coherence gate configuration.
407    pub const fn has_coherence_gates(&self) -> bool {
408        self.flags & AGI_HAS_COHERENCE_GATES != 0
409    }
410
411    /// Check if the container has domain expansion data.
412    pub const fn has_domain_expansion(&self) -> bool {
413        self.flags & AGI_HAS_DOMAIN_EXPANSION != 0
414    }
415
416    /// Serialize header to a 64-byte array.
417    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    /// Deserialize header from a byte slice (>= 64 bytes).
431    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/// Required segments for a valid AGI container.
471///
472/// Used by the container builder/validator to ensure completeness.
473#[derive(Clone, Debug, Default)]
474pub struct ContainerSegments {
475    /// KERNEL_SEG: micro Linux kernel (e.g. Firecracker-compatible vmlinux).
476    pub kernel_present: bool,
477    /// KERNEL_SEG size in bytes.
478    pub kernel_size: u64,
479    /// WASM_SEG: interpreter + microkernel modules.
480    pub wasm_count: u16,
481    /// Total WASM_SEG size in bytes.
482    pub wasm_total_size: u64,
483    /// VEC_SEG: world model vector count.
484    pub vec_segment_count: u16,
485    /// INDEX_SEG: HNSW index count.
486    pub index_segment_count: u16,
487    /// WITNESS_SEG: witness bundle count.
488    pub witness_count: u32,
489    /// CRYPTO_SEG: present.
490    pub crypto_present: bool,
491    /// META segment with AGI manifest: present.
492    pub manifest_present: bool,
493    /// Orchestrator configuration present.
494    pub orchestrator_present: bool,
495    /// World model data present (VEC + INDEX segments).
496    pub world_model_present: bool,
497    /// Domain expansion (transfer priors, policy kernels, cost curves) present.
498    pub domain_expansion_present: bool,
499    /// Total container size in bytes.
500    pub total_size: u64,
501}
502
503impl ContainerSegments {
504    /// Validate that the container has all required segments for a given
505    /// execution mode.
506    pub fn validate(&self, mode: ExecutionMode) -> Result<(), ContainerError> {
507        // All modes require the manifest.
508        if !self.manifest_present {
509            return Err(ContainerError::MissingSegment("AGI manifest"));
510        }
511
512        // Size check.
513        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                // Replay needs witness chains.
522                if self.witness_count == 0 {
523                    return Err(ContainerError::MissingSegment("witness chain"));
524                }
525            }
526            ExecutionMode::Verify | ExecutionMode::Live => {
527                // Verify/Live need at least kernel or WASM.
528                if !self.kernel_present && self.wasm_count == 0 {
529                    return Err(ContainerError::MissingSegment(
530                        "kernel or WASM runtime",
531                    ));
532                }
533                // Verify/Live need world model data for meaningful operation.
534                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    /// Compute the flags bitfield from present segments.
549    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/// Error type for AGI container operations.
580#[derive(Debug, PartialEq, Eq)]
581pub enum ContainerError {
582    /// A required segment is missing.
583    MissingSegment(&'static str),
584    /// Container exceeds size limit.
585    TooLarge { size: u64 },
586    /// Invalid segment configuration.
587    InvalidConfig(&'static str),
588    /// Signature verification failed.
589    SignatureInvalid,
590    /// Authority level insufficient for the requested action.
591    InsufficientAuthority {
592        required: u8,
593        granted: u8,
594    },
595    /// Resource budget exceeded.
596    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    // --- Authority level tests ---
822
823    #[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    // --- Resource budget tests ---
865
866    #[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    // --- Coherence threshold tests ---
907
908    #[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}