Skip to main content

agentic_reality/format/
file.rs

1//! .areal file reader and writer.
2
3use std::path::Path;
4
5use crate::engine::RealityEngine;
6use crate::types::error::{RealityError, RealityResult};
7use crate::types::{FOOTER_SIZE, HEADER_SIZE};
8
9use super::footer::ArealFooter;
10use super::header::ArealHeader;
11use super::sections::{SectionEntry, SectionType, SECTION_ENTRY_SIZE};
12
13/// Writer for .areal files.
14pub struct ArealWriter;
15
16impl ArealWriter {
17    /// Save a reality engine to a .areal file.
18    pub fn save(engine: &RealityEngine, path: &Path) -> RealityResult<()> {
19        let incarnation_bytes = engine
20            .incarnation_id()
21            .map(|id| *id.as_uuid().as_bytes())
22            .unwrap_or([0u8; 16]);
23
24        let mut header = ArealHeader::new(incarnation_bytes);
25        let mut sections_data: Vec<(SectionType, Vec<u8>)> = vec![];
26
27        // Serialize each domain store
28        let deployment = serde_json::to_vec(&engine.deployment_store)
29            .map_err(|e| RealityError::Serialization(e.to_string()))?;
30        sections_data.push((SectionType::Deployment, deployment));
31
32        let environment = serde_json::to_vec(&engine.environment_store)
33            .map_err(|e| RealityError::Serialization(e.to_string()))?;
34        sections_data.push((SectionType::Environment, environment));
35
36        let resource = serde_json::to_vec(&engine.resource_store)
37            .map_err(|e| RealityError::Serialization(e.to_string()))?;
38        sections_data.push((SectionType::Resource, resource));
39
40        let reality = serde_json::to_vec(&engine.reality_store)
41            .map_err(|e| RealityError::Serialization(e.to_string()))?;
42        sections_data.push((SectionType::Reality, reality));
43
44        let topology = serde_json::to_vec(&engine.topology_store)
45            .map_err(|e| RealityError::Serialization(e.to_string()))?;
46        sections_data.push((SectionType::Topology, topology));
47
48        let temporal = serde_json::to_vec(&engine.temporal_store)
49            .map_err(|e| RealityError::Serialization(e.to_string()))?;
50        sections_data.push((SectionType::Temporal, temporal));
51
52        let stakes = serde_json::to_vec(&engine.stakes_store)
53            .map_err(|e| RealityError::Serialization(e.to_string()))?;
54        sections_data.push((SectionType::Stakes, stakes));
55
56        let coherence = serde_json::to_vec(&engine.coherence_store)
57            .map_err(|e| RealityError::Serialization(e.to_string()))?;
58        sections_data.push((SectionType::Coherence, coherence));
59
60        header.section_count = sections_data.len() as u32;
61        header.section_table_offset = HEADER_SIZE as u64;
62
63        // Build output buffer
64        let mut buf = Vec::new();
65
66        // Write header placeholder
67        header.write_to(&mut buf)?;
68
69        // Section table offset = after header
70        let section_table_start = buf.len();
71
72        // Reserve space for section table
73        let table_size = sections_data.len() * SECTION_ENTRY_SIZE;
74        buf.resize(buf.len() + table_size, 0);
75
76        // Write section data and build entries
77        let mut entries = Vec::new();
78        for (section_type, data) in &sections_data {
79            let offset = buf.len() as u64;
80            let len = data.len() as u64;
81            let checksum_hash = blake3::hash(data);
82            let mut checksum = [0u8; 8];
83            checksum.copy_from_slice(&checksum_hash.as_bytes()[..8]);
84
85            entries.push(SectionEntry {
86                section_type: *section_type,
87                flags: 0,
88                offset,
89                length: len,
90                uncompressed_length: len,
91                checksum,
92            });
93
94            buf.extend_from_slice(data);
95        }
96
97        // Write section table entries
98        let mut table_buf = Vec::new();
99        for entry in &entries {
100            entry.write_to(&mut table_buf);
101        }
102        buf[section_table_start..section_table_start + table_size].copy_from_slice(&table_buf);
103
104        // Write footer
105        let global_hash = blake3::hash(&buf);
106        let footer = ArealFooter::new(*global_hash.as_bytes(), entries.len() as u8);
107        footer.write_to(&mut buf);
108
109        // Update header with final checksum
110        let header_hash = blake3::hash(&buf[..HEADER_SIZE - 8]);
111        let mut header_checksum = [0u8; 8];
112        header_checksum.copy_from_slice(&header_hash.as_bytes()[..8]);
113
114        // Write atomically using temp file
115        let tmp_path = path.with_extension("areal.tmp");
116        std::fs::write(&tmp_path, &buf)?;
117        std::fs::rename(&tmp_path, path)?;
118
119        Ok(())
120    }
121}
122
123/// Reader for .areal files.
124pub struct ArealReader;
125
126impl ArealReader {
127    /// Load a reality engine from a .areal file.
128    pub fn load(path: &Path) -> RealityResult<RealityEngine> {
129        let data = std::fs::read(path)?;
130
131        if data.len() < HEADER_SIZE + FOOTER_SIZE {
132            return Err(RealityError::InvalidFormat("file too small".into()));
133        }
134
135        // Validate header
136        let header = ArealHeader::read_from(&data)?;
137
138        // Validate footer
139        let _footer = ArealFooter::read_from(&data)?;
140
141        // Read section table
142        let table_start = header.section_table_offset as usize;
143        let section_count = header.section_count as usize;
144
145        let mut engine = RealityEngine::new();
146
147        for i in 0..section_count {
148            let entry_start = table_start + i * SECTION_ENTRY_SIZE;
149            if entry_start + SECTION_ENTRY_SIZE > data.len() {
150                return Err(RealityError::InvalidFormat("section table overflow".into()));
151            }
152            let entry = SectionEntry::read_from(&data[entry_start..])?;
153            let section_data = &data[entry.offset as usize..(entry.offset + entry.length) as usize];
154
155            match entry.section_type {
156                SectionType::Deployment => {
157                    engine.deployment_store = serde_json::from_slice(section_data)
158                        .map_err(|e| RealityError::Serialization(e.to_string()))?;
159                }
160                SectionType::Environment => {
161                    engine.environment_store = serde_json::from_slice(section_data)
162                        .map_err(|e| RealityError::Serialization(e.to_string()))?;
163                }
164                SectionType::Resource => {
165                    engine.resource_store = serde_json::from_slice(section_data)
166                        .map_err(|e| RealityError::Serialization(e.to_string()))?;
167                }
168                SectionType::Reality => {
169                    engine.reality_store = serde_json::from_slice(section_data)
170                        .map_err(|e| RealityError::Serialization(e.to_string()))?;
171                }
172                SectionType::Topology => {
173                    engine.topology_store = serde_json::from_slice(section_data)
174                        .map_err(|e| RealityError::Serialization(e.to_string()))?;
175                }
176                SectionType::Temporal => {
177                    engine.temporal_store = serde_json::from_slice(section_data)
178                        .map_err(|e| RealityError::Serialization(e.to_string()))?;
179                }
180                SectionType::Stakes => {
181                    engine.stakes_store = serde_json::from_slice(section_data)
182                        .map_err(|e| RealityError::Serialization(e.to_string()))?;
183                }
184                SectionType::Coherence => {
185                    engine.coherence_store = serde_json::from_slice(section_data)
186                        .map_err(|e| RealityError::Serialization(e.to_string()))?;
187                }
188                SectionType::Indexes => {
189                    engine.indexes = serde_json::from_slice(section_data)
190                        .map_err(|e| RealityError::Serialization(e.to_string()))?;
191                }
192            }
193        }
194
195        Ok(engine)
196    }
197}