1use ruvix_types::{ProofTier, RegionPolicy, WitTypeId};
11
12#[cfg(feature = "alloc")]
13use alloc::{string::String, vec::Vec};
14
15#[cfg(feature = "std")]
16use std::{string::String, vec::Vec};
17
18#[allow(dead_code)]
20pub const MAX_COMPONENT_NAME_LEN: usize = 256;
21
22#[allow(dead_code)]
24pub const MAX_ROLLBACK_HOOK_SIZE: usize = 64 * 1024; #[derive(Debug, Clone)]
31pub struct RvfManifest {
32 pub version: ManifestVersion,
34
35 pub content_hash: [u8; 32],
37
38 pub component_graph: ComponentGraph,
40
41 pub memory_schema: MemorySchema,
43
44 pub proof_policy: ProofPolicy,
46
47 pub rollback_hooks: RollbackHooks,
49
50 pub witness_log_policy: WitnessLogPolicy,
52
53 pub required_capabilities: RequiredCapabilities,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59#[repr(C)]
60pub struct ManifestVersion {
61 pub major: u16,
63 pub minor: u16,
65 pub patch: u16,
67}
68
69impl ManifestVersion {
70 pub const CURRENT: Self = Self {
72 major: 1,
73 minor: 0,
74 patch: 0,
75 };
76
77 #[inline]
79 #[must_use]
80 pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
81 Self { major, minor, patch }
82 }
83
84 #[inline]
86 #[must_use]
87 pub const fn is_compatible(&self) -> bool {
88 self.major == Self::CURRENT.major
89 }
90}
91
92impl Default for ManifestVersion {
93 fn default() -> Self {
94 Self::CURRENT
95 }
96}
97
98#[derive(Debug, Clone, Default)]
103pub struct ComponentGraph {
104 pub components: Vec<ComponentDecl>,
106
107 pub wirings: Vec<QueueWiring>,
109}
110
111impl ComponentGraph {
112 #[inline]
114 #[must_use]
115 pub fn new() -> Self {
116 Self::default()
117 }
118
119 #[inline]
121 #[must_use]
122 pub fn component_count(&self) -> usize {
123 self.components.len()
124 }
125
126 #[inline]
128 #[must_use]
129 pub fn wiring_count(&self) -> usize {
130 self.wirings.len()
131 }
132
133 #[must_use]
140 pub fn validate(&self) -> bool {
141 if self.components.is_empty() {
144 return true;
145 }
146
147 let count = self.component_count();
149 for wiring in &self.wirings {
150 if wiring.source_component as usize >= count
151 || wiring.target_component as usize >= count
152 {
153 return false;
154 }
155 }
156
157 true
158 }
159}
160
161#[derive(Debug, Clone)]
163pub struct ComponentDecl {
164 pub index: u32,
166
167 pub name: String,
169
170 pub code_hash: [u8; 32],
172
173 pub code_offset: u64,
175
176 pub code_size: u64,
178
179 pub wit_type: WitTypeId,
181
182 pub entry_point: String,
184
185 pub dependencies: Vec<u32>,
187}
188
189impl ComponentDecl {
190 #[must_use]
192 pub fn name_str(&self) -> &str {
193 &self.name
194 }
195
196 #[must_use]
198 pub fn entry_point_str(&self) -> &str {
199 &self.entry_point
200 }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205#[repr(C)]
206pub struct QueueWiring {
207 pub source_component: u32,
209
210 pub source_port_hash: u32,
212
213 pub target_component: u32,
215
216 pub target_port_hash: u32,
218
219 pub queue_capacity: u32,
221
222 pub max_message_size: u32,
224
225 pub message_type: WitTypeId,
227}
228
229#[derive(Debug, Clone, Default)]
231pub struct MemorySchema {
232 pub regions: Vec<RegionDecl>,
234
235 pub total_memory_required: u64,
237}
238
239impl MemorySchema {
240 #[inline]
242 #[must_use]
243 pub fn new() -> Self {
244 Self::default()
245 }
246
247 #[inline]
249 #[must_use]
250 pub fn region_count(&self) -> usize {
251 self.regions.len()
252 }
253
254 #[must_use]
256 pub fn validate(&self) -> bool {
257 let mut total: u64 = 0;
259 for region in &self.regions {
260 if let Some(size) = region.size_bytes() {
261 total = match total.checked_add(size) {
262 Some(t) => t,
263 None => return false,
264 };
265 }
266 }
267
268 total <= self.total_memory_required
269 }
270}
271
272#[derive(Debug, Clone)]
274pub struct RegionDecl {
275 pub index: u32,
277
278 pub name: String,
280
281 pub policy: RegionPolicy,
283
284 pub owner_component: u32,
286
287 pub read_access: Vec<u32>,
289
290 pub write_access: Vec<u32>,
292}
293
294impl RegionDecl {
295 #[must_use]
297 pub fn size_bytes(&self) -> Option<u64> {
298 match &self.policy {
299 RegionPolicy::Immutable => None, RegionPolicy::AppendOnly { max_size } => Some(*max_size as u64),
301 RegionPolicy::Slab { slot_size, slot_count } => {
302 Some((*slot_size as u64) * (*slot_count as u64))
303 }
304 }
305 }
306}
307
308#[derive(Debug, Clone, Default)]
310pub struct ProofPolicy {
311 pub component_tiers: Vec<ComponentProofTier>,
313
314 pub default_tier: ProofTier,
316
317 pub allow_tier_escalation: bool,
319}
320
321impl ProofPolicy {
322 #[inline]
324 #[must_use]
325 pub fn new(default_tier: ProofTier) -> Self {
326 Self {
327 component_tiers: Vec::new(),
328 default_tier,
329 allow_tier_escalation: false,
330 }
331 }
332
333 #[must_use]
335 pub fn tier_for_component(&self, component_index: u32) -> ProofTier {
336 for ct in &self.component_tiers {
337 if ct.component_index == component_index {
338 return ct.required_tier;
339 }
340 }
341 self.default_tier
342 }
343}
344
345#[derive(Debug, Clone, Copy, PartialEq, Eq)]
347#[repr(C)]
348pub struct ComponentProofTier {
349 pub component_index: u32,
351
352 pub required_tier: ProofTier,
354
355 pub proof_required_ops: ProofRequiredOps,
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
361#[repr(transparent)]
362pub struct ProofRequiredOps(pub u32);
363
364impl ProofRequiredOps {
365 pub const NONE: Self = Self(0);
367
368 pub const VECTOR_PUT: Self = Self(1 << 0);
370
371 pub const GRAPH_APPLY: Self = Self(1 << 1);
373
374 pub const REGION_WRITE: Self = Self(1 << 2);
376
377 pub const QUEUE_SEND: Self = Self(1 << 3);
379
380 pub const ALL: Self = Self(0b1111);
382
383 #[inline]
385 #[must_use]
386 pub const fn requires(&self, op: Self) -> bool {
387 (self.0 & op.0) != 0
388 }
389}
390
391#[derive(Debug, Clone, Default)]
393pub struct RollbackHooks {
394 pub hooks: Vec<RollbackHook>,
396}
397
398impl RollbackHooks {
399 #[inline]
401 #[must_use]
402 pub fn new() -> Self {
403 Self::default()
404 }
405
406 #[inline]
408 #[must_use]
409 pub fn count(&self) -> usize {
410 self.hooks.len()
411 }
412}
413
414#[derive(Debug, Clone)]
416pub struct RollbackHook {
417 pub index: u32,
419
420 pub component_index: u32,
422
423 pub function_name: String,
425
426 pub accessible_regions: Vec<u32>,
428
429 pub timeout_us: u64,
431}
432
433#[derive(Debug, Clone, Copy, PartialEq, Eq)]
435#[repr(C)]
436pub struct WitnessLogPolicy {
437 pub max_entries: u64,
439
440 pub max_size_bytes: u64,
442
443 pub retention_seconds: u64,
445
446 pub compression: WitnessCompression,
448
449 pub export_policy: WitnessExportPolicy,
451
452 pub hash_chain: bool,
454}
455
456impl Default for WitnessLogPolicy {
457 fn default() -> Self {
458 Self {
459 max_entries: 1_000_000,
460 max_size_bytes: 100 * 1024 * 1024, retention_seconds: 0, compression: WitnessCompression::None,
463 export_policy: WitnessExportPolicy::OnRotation,
464 hash_chain: true,
465 }
466 }
467}
468
469#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
471#[repr(u8)]
472pub enum WitnessCompression {
473 #[default]
475 None = 0,
476
477 Lz4 = 1,
479
480 Zstd = 2,
482}
483
484#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
486#[repr(u8)]
487pub enum WitnessExportPolicy {
488 #[default]
490 OnRotation = 0,
491
492 OnShutdown = 1,
494
495 Never = 2,
497
498 External = 3,
500}
501
502#[derive(Debug, Clone, Default)]
504pub struct RequiredCapabilities {
505 pub physical_memory: bool,
507
508 pub interrupt_queue: bool,
510
511 pub timer: bool,
513
514 pub vector_store: bool,
516
517 pub graph_store: bool,
519
520 pub min_memory_bytes: u64,
522}
523
524impl RvfManifest {
525 pub fn parse(data: &[u8]) -> Result<Self, ruvix_types::KernelError> {
531 if data.len() < 96 {
533 return Err(ruvix_types::KernelError::InvalidManifest);
534 }
535
536 if &data[0..4] != b"RVF1" {
538 return Err(ruvix_types::KernelError::InvalidManifest);
539 }
540
541 let major = u16::from_le_bytes([data[4], data[5]]);
543 let minor = u16::from_le_bytes([data[6], data[7]]);
544 let patch = u16::from_le_bytes([data[8], data[9]]);
545 let version = ManifestVersion::new(major, minor, patch);
546
547 if !version.is_compatible() {
548 return Err(ruvix_types::KernelError::InvalidManifest);
549 }
550
551 let mut content_hash = [0u8; 32];
553 content_hash.copy_from_slice(&data[10..42]);
554
555 let component_graph_offset = u32::from_le_bytes([data[42], data[43], data[44], data[45]]) as usize;
557 let memory_schema_offset = u32::from_le_bytes([data[46], data[47], data[48], data[49]]) as usize;
558 let proof_policy_offset = u32::from_le_bytes([data[50], data[51], data[52], data[53]]) as usize;
559 let rollback_hooks_offset = u32::from_le_bytes([data[54], data[55], data[56], data[57]]) as usize;
560 let witness_log_offset = u32::from_le_bytes([data[58], data[59], data[60], data[61]]) as usize;
561 let required_caps_offset = u32::from_le_bytes([data[62], data[63], data[64], data[65]]) as usize;
562
563 let component_graph = Self::parse_component_graph(data, component_graph_offset)?;
565 let memory_schema = Self::parse_memory_schema(data, memory_schema_offset)?;
566 let proof_policy = Self::parse_proof_policy(data, proof_policy_offset)?;
567 let rollback_hooks = Self::parse_rollback_hooks(data, rollback_hooks_offset)?;
568 let witness_log_policy = Self::parse_witness_log_policy(data, witness_log_offset)?;
569 let required_capabilities = Self::parse_required_capabilities(data, required_caps_offset)?;
570
571 Ok(Self {
572 version,
573 content_hash,
574 component_graph,
575 memory_schema,
576 proof_policy,
577 rollback_hooks,
578 witness_log_policy,
579 required_capabilities,
580 })
581 }
582
583 fn parse_component_graph(_data: &[u8], offset: usize) -> Result<ComponentGraph, ruvix_types::KernelError> {
584 if offset == 0 {
585 return Ok(ComponentGraph::new());
586 }
587
588 Ok(ComponentGraph::new())
590 }
591
592 fn parse_memory_schema(_data: &[u8], offset: usize) -> Result<MemorySchema, ruvix_types::KernelError> {
593 if offset == 0 {
594 return Ok(MemorySchema::new());
595 }
596
597 Ok(MemorySchema::new())
598 }
599
600 fn parse_proof_policy(_data: &[u8], offset: usize) -> Result<ProofPolicy, ruvix_types::KernelError> {
601 if offset == 0 {
602 return Ok(ProofPolicy::new(ProofTier::Standard));
603 }
604
605 Ok(ProofPolicy::new(ProofTier::Standard))
606 }
607
608 fn parse_rollback_hooks(_data: &[u8], offset: usize) -> Result<RollbackHooks, ruvix_types::KernelError> {
609 if offset == 0 {
610 return Ok(RollbackHooks::new());
611 }
612
613 Ok(RollbackHooks::new())
614 }
615
616 fn parse_witness_log_policy(data: &[u8], offset: usize) -> Result<WitnessLogPolicy, ruvix_types::KernelError> {
617 if offset == 0 || offset >= data.len() {
618 return Ok(WitnessLogPolicy::default());
619 }
620
621 if offset + 32 > data.len() {
623 return Ok(WitnessLogPolicy::default());
624 }
625
626 let max_entries = u64::from_le_bytes([
627 data[offset], data[offset + 1], data[offset + 2], data[offset + 3],
628 data[offset + 4], data[offset + 5], data[offset + 6], data[offset + 7],
629 ]);
630
631 let max_size_bytes = u64::from_le_bytes([
632 data[offset + 8], data[offset + 9], data[offset + 10], data[offset + 11],
633 data[offset + 12], data[offset + 13], data[offset + 14], data[offset + 15],
634 ]);
635
636 let retention_seconds = u64::from_le_bytes([
637 data[offset + 16], data[offset + 17], data[offset + 18], data[offset + 19],
638 data[offset + 20], data[offset + 21], data[offset + 22], data[offset + 23],
639 ]);
640
641 let compression = match data[offset + 24] {
642 1 => WitnessCompression::Lz4,
643 2 => WitnessCompression::Zstd,
644 _ => WitnessCompression::None,
645 };
646
647 let export_policy = match data[offset + 25] {
648 1 => WitnessExportPolicy::OnShutdown,
649 2 => WitnessExportPolicy::Never,
650 3 => WitnessExportPolicy::External,
651 _ => WitnessExportPolicy::OnRotation,
652 };
653
654 let hash_chain = data[offset + 26] != 0;
655
656 Ok(WitnessLogPolicy {
657 max_entries,
658 max_size_bytes,
659 retention_seconds,
660 compression,
661 export_policy,
662 hash_chain,
663 })
664 }
665
666 fn parse_required_capabilities(_data: &[u8], offset: usize) -> Result<RequiredCapabilities, ruvix_types::KernelError> {
667 if offset == 0 {
668 return Ok(RequiredCapabilities::default());
669 }
670
671 Ok(RequiredCapabilities::default())
672 }
673
674 #[must_use]
676 pub fn validate(&self) -> bool {
677 self.component_graph.validate() && self.memory_schema.validate()
678 }
679}
680
681#[cfg(test)]
682mod tests {
683 use super::*;
684
685 #[test]
686 fn test_manifest_version_compatibility() {
687 let v1_0_0 = ManifestVersion::new(1, 0, 0);
688 let v1_1_0 = ManifestVersion::new(1, 1, 0);
689 let v2_0_0 = ManifestVersion::new(2, 0, 0);
690
691 assert!(v1_0_0.is_compatible());
692 assert!(v1_1_0.is_compatible());
693 assert!(!v2_0_0.is_compatible());
694 }
695
696 #[test]
697 fn test_component_graph_empty() {
698 let graph = ComponentGraph::new();
699 assert_eq!(graph.component_count(), 0);
700 assert!(graph.validate());
702 }
703
704 #[test]
705 fn test_memory_schema_empty() {
706 let schema = MemorySchema::new();
707 assert_eq!(schema.region_count(), 0);
708 assert!(schema.validate()); }
710
711 #[test]
712 fn test_proof_policy_default_tier() {
713 let policy = ProofPolicy::new(ProofTier::Reflex);
714 assert_eq!(policy.tier_for_component(0), ProofTier::Reflex);
715 assert_eq!(policy.tier_for_component(999), ProofTier::Reflex);
716 }
717
718 #[test]
719 fn test_proof_required_ops() {
720 let ops = ProofRequiredOps::VECTOR_PUT;
721 assert!(ops.requires(ProofRequiredOps::VECTOR_PUT));
722 assert!(!ops.requires(ProofRequiredOps::GRAPH_APPLY));
723
724 let all = ProofRequiredOps::ALL;
725 assert!(all.requires(ProofRequiredOps::VECTOR_PUT));
726 assert!(all.requires(ProofRequiredOps::REGION_WRITE));
727 }
728
729 #[test]
730 fn test_witness_log_policy_default() {
731 let policy = WitnessLogPolicy::default();
732 assert_eq!(policy.max_entries, 1_000_000);
733 assert!(policy.hash_chain);
734 assert_eq!(policy.compression, WitnessCompression::None);
735 }
736
737 #[test]
738 fn test_manifest_parse_invalid_magic() {
739 let data = b"XXXX";
740 assert!(RvfManifest::parse(data).is_err());
741 }
742
743 #[test]
744 fn test_manifest_parse_too_short() {
745 let data = b"RVF1";
746 assert!(RvfManifest::parse(data).is_err());
747 }
748}