active_infinity/lib.rs
1//! Active Infinity (Active ∞) — the canonical binary form of Active modules.
2//!
3//! Active Infinity is a graph-native, unit-aware, secret-safe, canonical,
4//! content-addressed binary data language for the Active ecosystem. It is the
5//! runtime, storage, network and agent-to-agent exchange format for Active
6//! modules: safer and faster than JSON for configuration and data, deterministic
7//! by construction, and designed so that an untrusted buffer can be validated
8//! in one bounded pass before any of it is trusted.
9//!
10//! # Quick start
11//!
12//! ```
13//! use active_infinity::{Inode, InfinityModule, encode, decode, content_hash};
14//!
15//! let mut module = InfinityModule::new();
16//! let hello = module.push(Inode::Str("hello".into()));
17//! let number = module.push(Inode::Int(42));
18//! let table = module.push(Inode::Table {
19//! fields: vec![("greeting".into(), hello), ("answer".into(), number)],
20//! });
21//! module.set_root(Some(table));
22//!
23//! let bytes = encode(&module).unwrap();
24//! let decoded = decode(&bytes).unwrap();
25//! assert_eq!(decoded, module);
26//! assert_eq!(encode(&decoded).unwrap(), bytes);
27//! assert_eq!(content_hash(&bytes).unwrap(), content_hash(&encode(&decoded).unwrap()).unwrap());
28//! ```
29//!
30//! # The four guarantees
31//!
32//! 1. **Canonical identity.** Logically equal modules encode to identical
33//! bytes, and the Blake3 content hash covers the whole canonical payload, so
34//! content addressing is a real identity (`canonical`, `encode`).
35//! 2. **Fail-closed validation.** Wrong magic/version, truncation, out-of-range
36//! offsets and counts, invalid UTF-8, unknown unit discriminants, cycles,
37//! unreachable nodes, non-canonical slack and size bombs are rejected with
38//! stable machine-readable codes before allocation (`validate`, `error`).
39//! 3. **Secrets stay secrets.** Secret material is a structural kind, never
40//! equals anything (no oracle), carries no `Debug`/`Display`/`Serialize`
41//! path, and is absent from every export path (`ir::SecretBytes`).
42//! 4. **Zero-copy and streaming.** A validated buffer can be read through
43//! [`view::InfinityView`] without copying strings or blobs, and a module can
44//! be shipped as ordered frames that reassemble to the identical canonical
45//! bytes and hash, including incremental deltas (`stream`).
46//!
47//! # Feature flags
48//!
49//! - `active-core` (default): interop with the Active text language IR
50//! (`interop::from_active_core`, `to_active_core`).
51//! - `json` (default): the redacted JSON projection
52//! (`interop::to_redacted_json`).
53//!
54//! Both can be disabled for a dependency-free wire codec.
55
56#![forbid(unsafe_code)]
57#![deny(missing_docs)]
58#![warn(clippy::all)]
59
60pub mod canonical;
61pub mod decode;
62pub mod encode;
63pub mod error;
64pub mod interop;
65pub mod ir;
66pub mod layout;
67pub mod stream;
68pub mod unit;
69pub mod validate;
70pub mod view;
71
72pub use decode::{decode, decode_with};
73pub use encode::{encode, encode_with, hash_image};
74pub use error::{Error, Result};
75pub use ir::{
76 Annotation, AnnotationKind, BinOp, Constraint, InfinityModule, Inode, InterpPart,
77 ModuleBuilder, NodeId, PatchOp, ProvEntry, ProvKind, ProvValue, Provenance, SchemaFieldSpec,
78 SecretBytes, SemanticTag, SerializedKind, TypeSpec, UnOp,
79};
80pub use layout::{
81 dialect_hash, DecodeOpts, EncodeOpts, Limits, Meta, Section, FLAG_CANONICAL, FORMAT_MAJOR,
82 FORMAT_MINOR, HEADER_SIZE,
83};
84pub use stream::{Frame, FrameKind, StreamDecoder, StreamEncoder};
85pub use unit::UnitTag;
86pub use validate::{validate, validate_canonical, validate_with, Validated};
87pub use view::{InfinityView, NodeView};
88
89/// Crate version.
90pub const VERSION: &str = env!("CARGO_PKG_VERSION");
91
92/// Magic bytes at the start of every Active Infinity module.
93pub const MAGIC: [u8; 4] = layout::MAGIC;
94
95/// Compute the Blake3-256 content hash of a validated image.
96///
97/// The hash covers the whole canonical payload with the stored hash field
98/// zeroed, so it changes when any canonical byte changes and is stable across
99/// runs and processes.
100pub fn content_hash(bytes: &[u8]) -> Result<[u8; 32]> {
101 let validated = Validated::new(bytes, DecodeOpts::default())?;
102 Ok(validated.header().content_hash)
103}
104
105/// Hex form of [`content_hash`].
106pub fn content_hash_hex(bytes: &[u8]) -> Result<String> {
107 content_hash(bytes).map(|hash| ir::hash_hex(&hash))
108}
109
110/// Encode a module and return `(bytes, hash)`.
111pub fn encode_hashed(module: &InfinityModule) -> Result<(Vec<u8>, [u8; 32])> {
112 let bytes = encode(module)?;
113 let hash = hash_image(&bytes)?;
114 Ok((bytes, hash))
115}