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 protobuf;
68pub mod stream;
69pub mod unit;
70pub mod validate;
71pub mod view;
72
73pub use decode::{decode, decode_with};
74pub use encode::{encode, encode_with, hash_image};
75pub use error::{Error, Result};
76pub use ir::{
77 Annotation, AnnotationKind, BinOp, Constraint, InfinityModule, Inode, InterpPart,
78 ModuleBuilder, NodeId, PatchOp, ProvEntry, ProvKind, ProvValue, Provenance, SchemaFieldSpec,
79 SecretBytes, SemanticTag, SerializedKind, TypeSpec, UnOp,
80};
81pub use layout::{
82 dialect_hash, DecodeOpts, EncodeOpts, Limits, Meta, Section, FLAG_CANONICAL, FORMAT_MAJOR,
83 FORMAT_MINOR, HEADER_SIZE,
84};
85pub use protobuf::to_protobuf;
86pub use stream::{Frame, FrameKind, StreamDecoder, StreamEncoder};
87pub use unit::UnitTag;
88pub use validate::{validate, validate_canonical, validate_with, Validated};
89pub use view::{InfinityView, NodeView};
90
91/// Crate version.
92pub const VERSION: &str = env!("CARGO_PKG_VERSION");
93
94/// Magic bytes at the start of every Active Infinity module.
95pub const MAGIC: [u8; 4] = layout::MAGIC;
96
97/// Compute the Blake3-256 content hash of a validated image.
98///
99/// The hash covers the whole canonical payload with the stored hash field
100/// zeroed, so it changes when any canonical byte changes and is stable across
101/// runs and processes.
102pub fn content_hash(bytes: &[u8]) -> Result<[u8; 32]> {
103 let validated = Validated::new(bytes, DecodeOpts::default())?;
104 Ok(validated.header().content_hash)
105}
106
107/// Hex form of [`content_hash`].
108pub fn content_hash_hex(bytes: &[u8]) -> Result<String> {
109 content_hash(bytes).map(|hash| ir::hash_hex(&hash))
110}
111
112/// Encode a module and return `(bytes, hash)`.
113pub fn encode_hashed(module: &InfinityModule) -> Result<(Vec<u8>, [u8; 32])> {
114 let bytes = encode(module)?;
115 let hash = hash_image(&bytes)?;
116 Ok((bytes, hash))
117}