1use std::io::{self, Read};
25
26const MAGIC: [u8; 3] = [0xCE, 0x9B, 0x44]; const FORMAT_VERSION: u8 = 1;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
36pub enum Derivation {
37 Raw = 0,
38 Derived = 1,
39 Inferred = 2,
40 Aggregated = 3,
41 Transformed = 4,
42}
43
44impl Derivation {
45 pub fn from_str(s: &str) -> Option<Self> {
46 match s {
47 "raw" => Some(Derivation::Raw),
48 "derived" => Some(Derivation::Derived),
49 "inferred" => Some(Derivation::Inferred),
50 "aggregated" => Some(Derivation::Aggregated),
51 "transformed" => Some(Derivation::Transformed),
52 _ => None,
53 }
54 }
55
56 pub fn as_str(&self) -> &'static str {
57 match self {
58 Derivation::Raw => "raw",
59 Derivation::Derived => "derived",
60 Derivation::Inferred => "inferred",
61 Derivation::Aggregated => "aggregated",
62 Derivation::Transformed => "transformed",
63 }
64 }
65
66 fn from_byte(b: u8) -> Option<Self> {
67 match b {
68 0 => Some(Derivation::Raw),
69 1 => Some(Derivation::Derived),
70 2 => Some(Derivation::Inferred),
71 3 => Some(Derivation::Aggregated),
72 4 => Some(Derivation::Transformed),
73 _ => None,
74 }
75 }
76}
77
78#[derive(Debug, Clone)]
90pub struct LambdaData {
91 pub name: String,
92 pub ontology: String, pub value: Vec<u8>, pub certainty: f64, pub temporal_frame_start: String, pub temporal_frame_end: String, pub provenance: String, pub derivation: Derivation, }
100
101#[derive(Debug)]
104pub enum LdError {
105 InvariantViolation(String),
107 DecodeError(String),
109 Io(io::Error),
111}
112
113impl From<io::Error> for LdError {
114 fn from(e: io::Error) -> Self {
115 LdError::Io(e)
116 }
117}
118
119impl std::fmt::Display for LdError {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 match self {
122 LdError::InvariantViolation(msg) => write!(f, "ΛD invariant violation: {msg}"),
123 LdError::DecodeError(msg) => write!(f, "ΛD decode error: {msg}"),
124 LdError::Io(e) => write!(f, "ΛD I/O error: {e}"),
125 }
126 }
127}
128
129impl LambdaData {
132 pub fn validate(&self) -> Result<(), LdError> {
134 if self.ontology.is_empty() {
136 return Err(LdError::InvariantViolation(format!(
137 "Ontological Rigidity: '{}' has empty ontology (T = ⊥)",
138 self.name
139 )));
140 }
141
142 if self.certainty < 0.0 || self.certainty > 1.0 {
144 return Err(LdError::InvariantViolation(format!(
145 "Epistemic Bounding: certainty={} for '{}' (must be in [0, 1])",
146 self.certainty, self.name
147 )));
148 }
149
150 if self.certainty == 1.0 && self.derivation != Derivation::Raw {
152 return Err(LdError::InvariantViolation(format!(
153 "Epistemic Degradation: '{}' has c=1.0 with δ={}, only raw may carry absolute certainty",
154 self.name, self.derivation.as_str()
155 )));
156 }
157
158 Ok(())
159 }
160}
161
162pub fn encode(ld: &LambdaData) -> Result<Vec<u8>, LdError> {
179 ld.validate()?;
180
181 let mut buf: Vec<u8> = Vec::new();
182
183 buf.extend_from_slice(&MAGIC);
185 buf.push(FORMAT_VERSION);
186
187 write_str(&mut buf, &ld.name)?;
189 write_str(&mut buf, &ld.ontology)?;
190
191 buf.extend_from_slice(&ld.certainty.to_le_bytes());
193
194 write_str(&mut buf, &ld.temporal_frame_start)?;
196 write_str(&mut buf, &ld.temporal_frame_end)?;
197
198 write_str(&mut buf, &ld.provenance)?;
200
201 buf.push(ld.derivation as u8);
203
204 let vlen = ld.value.len() as u32;
206 buf.extend_from_slice(&vlen.to_le_bytes());
207 buf.extend_from_slice(&ld.value);
208
209 Ok(buf)
210}
211
212pub fn decode(data: &[u8]) -> Result<LambdaData, LdError> {
214 let mut cursor = io::Cursor::new(data);
215
216 let mut magic = [0u8; 3];
218 cursor
219 .read_exact(&mut magic)
220 .map_err(|_| LdError::DecodeError("truncated: missing magic bytes".into()))?;
221 if magic != MAGIC {
222 return Err(LdError::DecodeError(format!(
223 "invalid magic: expected [CE 9B 44], got [{:02X} {:02X} {:02X}]",
224 magic[0], magic[1], magic[2]
225 )));
226 }
227
228 let mut ver = [0u8; 1];
230 cursor
231 .read_exact(&mut ver)
232 .map_err(|_| LdError::DecodeError("truncated: missing version byte".into()))?;
233 if ver[0] != FORMAT_VERSION {
234 return Err(LdError::DecodeError(format!(
235 "unsupported version: {} (expected {})",
236 ver[0], FORMAT_VERSION
237 )));
238 }
239
240 let name = read_str(&mut cursor)?;
242 let ontology = read_str(&mut cursor)?;
243
244 let mut c_bytes = [0u8; 8];
245 cursor
246 .read_exact(&mut c_bytes)
247 .map_err(|_| LdError::DecodeError("truncated: missing certainty".into()))?;
248 let certainty = f64::from_le_bytes(c_bytes);
249
250 let temporal_frame_start = read_str(&mut cursor)?;
251 let temporal_frame_end = read_str(&mut cursor)?;
252 let provenance = read_str(&mut cursor)?;
253
254 let mut d_byte = [0u8; 1];
255 cursor
256 .read_exact(&mut d_byte)
257 .map_err(|_| LdError::DecodeError("truncated: missing derivation".into()))?;
258 let derivation = Derivation::from_byte(d_byte[0])
259 .ok_or_else(|| LdError::DecodeError(format!("invalid derivation tag: {}", d_byte[0])))?;
260
261 let mut vlen_bytes = [0u8; 4];
262 cursor
263 .read_exact(&mut vlen_bytes)
264 .map_err(|_| LdError::DecodeError("truncated: missing value length".into()))?;
265 let vlen = u32::from_le_bytes(vlen_bytes) as usize;
266 let mut value = vec![0u8; vlen];
267 cursor
268 .read_exact(&mut value)
269 .map_err(|_| LdError::DecodeError("truncated: value payload incomplete".into()))?;
270
271 let ld = LambdaData {
272 name,
273 ontology,
274 value,
275 certainty,
276 temporal_frame_start,
277 temporal_frame_end,
278 provenance,
279 derivation,
280 };
281
282 ld.validate()?;
284
285 Ok(ld)
286}
287
288pub fn compose(
298 a: &LambdaData,
299 b: &LambdaData,
300 result_name: &str,
301 result_ontology: &str,
302) -> Result<LambdaData, LdError> {
303 let c_out = a.certainty.min(b.certainty);
305
306 let d_out = if (a.derivation as u8) >= (b.derivation as u8) {
308 a.derivation
309 } else {
310 b.derivation
311 };
312
313 let tf_start = if a.temporal_frame_start >= b.temporal_frame_start {
315 &a.temporal_frame_start
316 } else {
317 &b.temporal_frame_start
318 };
319 let tf_end = if a.temporal_frame_end.is_empty() {
320 &b.temporal_frame_end
321 } else if b.temporal_frame_end.is_empty() {
322 &a.temporal_frame_end
323 } else if a.temporal_frame_end <= b.temporal_frame_end {
324 &a.temporal_frame_end
325 } else {
326 &b.temporal_frame_end
327 };
328
329 let prov = if a.provenance.is_empty() {
331 b.provenance.clone()
332 } else if b.provenance.is_empty() {
333 a.provenance.clone()
334 } else {
335 format!("{} \u{2218} {}", a.provenance, b.provenance)
336 };
337
338 let composed = LambdaData {
339 name: result_name.to_string(),
340 ontology: result_ontology.to_string(),
341 value: Vec::new(), certainty: c_out,
343 temporal_frame_start: tf_start.clone(),
344 temporal_frame_end: tf_end.clone(),
345 provenance: prov,
346 derivation: d_out,
347 };
348
349 composed.validate()?;
350 Ok(composed)
351}
352
353pub fn apply_provenance_ceiling(input_c: f64, ceiling: f64) -> f64 {
375 input_c.clamp(0.0, 1.0).min(ceiling.clamp(0.0, 1.0))
376}
377
378pub fn to_json(ld: &LambdaData) -> serde_json::Value {
385 serde_json::json!({
386 "_ld_version": FORMAT_VERSION,
387 "_ld_lossy": true,
388 "name": ld.name,
389 "ontology": ld.ontology,
390 "certainty": ld.certainty,
391 "temporal_frame_start": ld.temporal_frame_start,
392 "temporal_frame_end": ld.temporal_frame_end,
393 "provenance": ld.provenance,
394 "derivation": ld.derivation.as_str(),
395 "value_bytes": ld.value.len(),
396 })
397}
398
399pub fn from_ir(
401 name: &str,
402 ontology: &str,
403 certainty: f64,
404 temporal_frame_start: &str,
405 temporal_frame_end: &str,
406 provenance: &str,
407 derivation: &str,
408) -> Result<LambdaData, LdError> {
409 let d = Derivation::from_str(derivation)
410 .ok_or_else(|| LdError::InvariantViolation(format!("unknown derivation '{derivation}'")))?;
411
412 let ld = LambdaData {
413 name: name.to_string(),
414 ontology: ontology.to_string(),
415 value: Vec::new(),
416 certainty,
417 temporal_frame_start: temporal_frame_start.to_string(),
418 temporal_frame_end: temporal_frame_end.to_string(),
419 provenance: provenance.to_string(),
420 derivation: d,
421 };
422
423 ld.validate()?;
424 Ok(ld)
425}
426
427fn write_str(buf: &mut Vec<u8>, s: &str) -> Result<(), LdError> {
430 let bytes = s.as_bytes();
431 if bytes.len() > u16::MAX as usize {
432 return Err(LdError::InvariantViolation(format!(
433 "string too long for ΛD format: {} bytes (max {})",
434 bytes.len(),
435 u16::MAX
436 )));
437 }
438 buf.extend_from_slice(&(bytes.len() as u16).to_le_bytes());
439 buf.extend_from_slice(bytes);
440 Ok(())
441}
442
443fn read_str(cursor: &mut io::Cursor<&[u8]>) -> Result<String, LdError> {
444 let mut len_bytes = [0u8; 2];
445 cursor
446 .read_exact(&mut len_bytes)
447 .map_err(|_| LdError::DecodeError("truncated: missing string length".into()))?;
448 let len = u16::from_le_bytes(len_bytes) as usize;
449 let mut buf = vec![0u8; len];
450 cursor
451 .read_exact(&mut buf)
452 .map_err(|_| LdError::DecodeError("truncated: string payload incomplete".into()))?;
453 String::from_utf8(buf).map_err(|_| LdError::DecodeError("invalid UTF-8 in string field".into()))
454}
455
456pub fn run_ld(action: &str, file: &str) -> i32 {
460 match action {
461 "encode" => run_ld_encode(file),
462 "decode" | "inspect" => run_ld_inspect(file),
463 _ => {
464 eprintln!("axon ld: unknown action '{action}'. Use: encode, decode, inspect");
465 2
466 }
467 }
468}
469
470fn run_ld_encode(file: &str) -> i32 {
472 let source = match std::fs::read_to_string(file) {
473 Ok(s) => s,
474 Err(_) => {
475 eprintln!("X File not found: {file}");
476 return 2;
477 }
478 };
479
480 let tokens = match crate::lexer::Lexer::new(&source, file).tokenize() {
482 Ok(t) => t,
483 Err(e) => {
484 eprintln!("X Lexer error: {}", e.message);
485 return 1;
486 }
487 };
488 let mut parser = crate::parser::Parser::new(tokens);
489 let program = match parser.parse() {
490 Ok(p) => p,
491 Err(e) => {
492 eprintln!("X Parse error: {}", e.message);
493 return 1;
494 }
495 };
496
497 let mut count = 0;
499 for decl in &program.declarations {
500 if let crate::ast::Declaration::LambdaData(ld_def) = decl {
501 let derivation = if ld_def.derivation.is_empty() {
502 "raw"
503 } else {
504 &ld_def.derivation
505 };
506 let ld = match from_ir(
507 &ld_def.name,
508 &ld_def.ontology,
509 ld_def.certainty,
510 &ld_def.temporal_frame_start,
511 &ld_def.temporal_frame_end,
512 &ld_def.provenance,
513 derivation,
514 ) {
515 Ok(ld) => ld,
516 Err(e) => {
517 eprintln!("X {e}");
518 return 1;
519 }
520 };
521
522 let bytes = match encode(&ld) {
523 Ok(b) => b,
524 Err(e) => {
525 eprintln!("X {e}");
526 return 1;
527 }
528 };
529
530 let out_path = format!("{}.ld", ld_def.name);
531 if let Err(e) = std::fs::write(&out_path, &bytes) {
532 eprintln!("X Failed to write {out_path}: {e}");
533 return 1;
534 }
535 println!(
536 " \u{2713} {} \u{2192} {out_path} ({} bytes, c={}, \u{03B4}={})",
537 ld_def.name,
538 bytes.len(),
539 ld.certainty,
540 ld.derivation.as_str()
541 );
542 count += 1;
543 }
544 }
545
546 if count == 0 {
547 eprintln!("X No lambda data declarations found in {file}");
548 return 1;
549 }
550 println!("\n{count} \u{039B}D state vector(s) encoded.");
551 0
552}
553
554fn run_ld_inspect(file: &str) -> i32 {
556 let data = match std::fs::read(file) {
557 Ok(d) => d,
558 Err(_) => {
559 eprintln!("X File not found: {file}");
560 return 2;
561 }
562 };
563
564 let ld = match decode(&data) {
565 Ok(ld) => ld,
566 Err(e) => {
567 eprintln!("X {e}");
568 return 1;
569 }
570 };
571
572 println!("\u{03C8} = \u{27E8}T, V, E\u{27E9} where E = \u{27E8}c, \u{03C4}, \u{03C1}, \u{03B4}\u{27E9}\n");
573 println!(" name: {}", ld.name);
574 println!(" T (ontology): {}", ld.ontology);
575 println!(" V (payload): {} bytes", ld.value.len());
576 println!(" c (certainty): {}", ld.certainty);
577 if !ld.temporal_frame_start.is_empty() {
578 let tf = if ld.temporal_frame_end.is_empty() {
579 ld.temporal_frame_start.clone()
580 } else {
581 format!("[{}, {}]", ld.temporal_frame_start, ld.temporal_frame_end)
582 };
583 println!(" \u{03C4} (temporal): {tf}");
584 }
585 if !ld.provenance.is_empty() {
586 println!(" \u{03C1} (provenance): {}", ld.provenance);
587 }
588 println!(" \u{03B4} (derivation): {}", ld.derivation.as_str());
589 println!(
590 "\n format: \u{039B}D v{FORMAT_VERSION} ({} bytes)",
591 data.len()
592 );
593 0
594}
595
596#[cfg(test)]
597mod tests {
598 use super::apply_provenance_ceiling;
599
600 #[test]
602 fn provenance_ceiling_is_a_ceiling_not_a_floor() {
603 assert_eq!(apply_provenance_ceiling(0.40, 0.95), 0.40);
605 assert_eq!(apply_provenance_ceiling(0.99, 0.80), 0.80);
607 assert_eq!(apply_provenance_ceiling(0.80, 0.80), 0.80);
609 }
610
611 #[test]
613 fn provenance_ceiling_is_min() {
614 assert_eq!(
615 apply_provenance_ceiling(0.30, 0.70),
616 apply_provenance_ceiling(0.70, 0.30)
617 );
618 assert_eq!(apply_provenance_ceiling(0.30, 0.70), 0.30);
619 }
620
621 #[test]
623 fn provenance_ceiling_clamps_out_of_range() {
624 assert_eq!(apply_provenance_ceiling(1.5, 0.9), 0.9);
625 assert_eq!(apply_provenance_ceiling(0.5, 2.0), 0.5);
626 assert_eq!(apply_provenance_ceiling(-0.2, 0.9), 0.0);
627 }
628}