use std::collections::{BTreeMap, BTreeSet};
use std::sync::OnceLock;
use serde::Serialize;
use crate::address::ContentAddress;
use crate::archive::Archive;
use crate::codec::{self, CodecError};
use crate::definition::{Definition, EdgeTarget};
use crate::meta;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RecursiveAddressError {
Codec(CodecError),
DanglingLocalEdge {
from: String,
to: String,
},
AmbiguousCyclicIdentity {
names: Vec<String>,
},
ConceptNotFound {
name: String,
},
}
impl From<CodecError> for RecursiveAddressError {
fn from(e: CodecError) -> Self {
RecursiveAddressError::Codec(e)
}
}
#[derive(Serialize)]
enum ResolvedTarget {
Local([u8; 32]),
SelfIndex(u64),
Grounded { ontology: String, atom: [u8; 32] },
}
#[derive(Serialize)]
enum ResolvedKind {
Grounded([u8; 32]),
Free(String),
}
#[derive(Debug, Clone, Default)]
pub struct KindVocab(BTreeMap<String, ContentAddress>);
impl KindVocab {
pub fn empty() -> Self {
Self(BTreeMap::new())
}
pub fn from_archive(archive: &Archive) -> Result<Self, RecursiveAddressError> {
Ok(Self(recursive_addresses_grounded(archive, &Self::empty())?))
}
pub fn address_of(&self, name: &str) -> Option<ContentAddress> {
self.0.get(name).copied()
}
pub fn extend_overriding(&mut self, other: KindVocab) {
self.0.extend(other.0);
}
}
fn default_kind_vocab() -> &'static KindVocab {
static VOCAB: OnceLock<KindVocab> = OnceLock::new();
VOCAB.get_or_init(|| {
let mut vocab = KindVocab::from_archive(&meta::ontology())
.expect("the meta-ontology kind floor must address (referentially closed kernel)");
vocab.extend_overriding(
KindVocab::from_archive(&load_relation_kinds())
.expect("the loaded relation-kind vocab must address (closed projection)"),
);
vocab
})
}
fn load_relation_kinds() -> Archive {
const MORPHISM_KINDS_PRX: &[u8] = include_bytes!("morphism_kinds.prx");
const MORPHISM_KINDS_ROOT_HEX: &str =
"6c83ec88e28cd19b13f7762747162a0136f23e468267b3214bc7b9b30d5665a8";
let root = ContentAddress::from_hex(MORPHISM_KINDS_ROOT_HEX)
.expect("MORPHISM_KINDS_ROOT_HEX is valid 64-hex");
crate::load::load(MORPHISM_KINDS_PRX, root)
.expect("committed morphism_kinds.prx must load against its baked root")
}
#[derive(Serialize)]
struct NodeCanon<'a> {
kind: &'a str,
name: &'a str,
lexical: Option<&'a str>,
axioms: Vec<&'a str>,
edges: Vec<(ResolvedKind, ResolvedTarget)>,
}
impl Archive {
pub fn recursive_addresses(
&self,
) -> Result<BTreeMap<String, ContentAddress>, RecursiveAddressError> {
recursive_addresses_grounded(self, default_kind_vocab())
}
pub fn recursive_addresses_grounded(
&self,
vocab: &KindVocab,
) -> Result<BTreeMap<String, ContentAddress>, RecursiveAddressError> {
recursive_addresses_grounded(self, vocab)
}
pub fn recursive_root(&self) -> Result<ContentAddress, RecursiveAddressError> {
self.recursive_root_grounded(default_kind_vocab())
}
pub fn recursive_root_grounded(
&self,
vocab: &KindVocab,
) -> Result<ContentAddress, RecursiveAddressError> {
let by_name = self.recursive_addresses_grounded(vocab)?;
let mut addrs: Vec<[u8; 32]> = by_name.values().map(|a| *a.as_bytes()).collect();
for c in &self.connections {
addrs.push(*c.address()?.as_bytes());
}
addrs.sort_unstable();
addrs.dedup();
Ok(codec::address_of(&addrs)?)
}
pub fn extract_concept(&self, name: &str) -> Result<Archive, RecursiveAddressError> {
let index: BTreeMap<&str, usize> = self
.nodes
.iter()
.enumerate()
.map(|(i, d)| (d.name.as_str(), i))
.collect();
let &start = index
.get(name)
.ok_or_else(|| RecursiveAddressError::ConceptNotFound {
name: name.to_string(),
})?;
let mut keep: BTreeSet<usize> = BTreeSet::new();
let mut stack = vec![start];
while let Some(i) = stack.pop() {
if !keep.insert(i) {
continue;
}
for (_kind, target) in &self.nodes[i].edges {
if let EdgeTarget::Local(n) = target {
match index.get(n.as_str()) {
Some(&j) => stack.push(j),
None => {
return Err(RecursiveAddressError::DanglingLocalEdge {
from: self.nodes[i].name.clone(),
to: n.clone(),
});
}
}
}
}
}
let nodes: Vec<Definition> = keep.iter().map(|&i| self.nodes[i].clone()).collect();
Ok(Archive {
nodes,
connections: self.connections.clone(),
})
}
}
fn recursive_addresses_grounded(
archive: &Archive,
vocab: &KindVocab,
) -> Result<BTreeMap<String, ContentAddress>, RecursiveAddressError> {
let n = archive.nodes.len();
let mut index: BTreeMap<&str, usize> = BTreeMap::new();
for (i, node) in archive.nodes.iter().enumerate() {
index.insert(node.name.as_str(), i);
}
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for (i, node) in archive.nodes.iter().enumerate() {
for (_kind, target) in &node.edges {
if let EdgeTarget::Local(name) = target {
match index.get(name.as_str()) {
Some(&j) => adj[i].push(j),
None => {
return Err(RecursiveAddressError::DanglingLocalEdge {
from: node.name.clone(),
to: name.clone(),
});
}
}
}
}
}
let (scc_of, members, order) = tarjan_scc(n, &adj);
let mut rec: BTreeMap<String, ContentAddress> = BTreeMap::new();
let mut addr_of: Vec<Option<ContentAddress>> = vec![None; n];
for &scc_id in &order {
let scc = &members[scc_id];
let is_cycle = scc.len() > 1 || adj[scc[0]].contains(&scc[0]);
if !is_cycle {
let i = scc[0];
let canon = node_canon(archive, i, &index, &scc_of, &addr_of, None, vocab)?;
let a = codec::address_of(&canon)?;
addr_of[i] = Some(a);
rec.insert(archive.nodes[i].name.clone(), a);
continue;
}
let mut ordered: Vec<(usize, ContentAddress)> = Vec::with_capacity(scc.len());
for &i in scc {
ordered.push((i, archive.nodes[i].address()?));
}
ordered.sort_by(|a, b| a.1.as_bytes().cmp(b.1.as_bytes()));
for w in ordered.windows(2) {
if w[0].1 == w[1].1 {
return Err(RecursiveAddressError::AmbiguousCyclicIdentity {
names: scc.iter().map(|&i| archive.nodes[i].name.clone()).collect(),
});
}
}
let mut within: BTreeMap<usize, u64> = BTreeMap::new();
for (pos, (i, _)) in ordered.iter().enumerate() {
within.insert(*i, pos as u64);
}
let mut member_canons: Vec<NodeCanon> = Vec::with_capacity(ordered.len());
for (i, _) in &ordered {
member_canons.push(node_canon(
archive,
*i,
&index,
&scc_of,
&addr_of,
Some((scc_id, &within)),
vocab,
)?);
}
let x = codec::address_of(&member_canons)?;
for (i, _) in &ordered {
let idx = within[i];
let a = codec::address_of(&("#cycle", x.as_bytes(), idx))?;
addr_of[*i] = Some(a);
rec.insert(archive.nodes[*i].name.clone(), a);
}
}
Ok(rec)
}
fn node_canon<'a>(
archive: &'a Archive,
i: usize,
index: &BTreeMap<&str, usize>,
scc_of: &[usize],
addr_of: &[Option<ContentAddress>],
cycle: Option<(usize, &BTreeMap<usize, u64>)>,
vocab: &KindVocab,
) -> Result<NodeCanon<'a>, RecursiveAddressError> {
let node = &archive.nodes[i];
let mut edges: Vec<(ResolvedKind, ResolvedTarget)> = Vec::with_capacity(node.edges.len());
for (kind, target) in &node.edges {
let resolved_kind = match vocab.address_of(kind) {
Some(addr) => ResolvedKind::Grounded(*addr.as_bytes()),
None => ResolvedKind::Free(kind.clone()),
};
let resolved = match target {
EdgeTarget::Grounded { ontology, atom } => ResolvedTarget::Grounded {
ontology: ontology.clone(),
atom: *atom.as_bytes(),
},
EdgeTarget::Local(name) => {
let j = index[name.as_str()];
match cycle {
Some((scc, within)) if scc_of[j] == scc => {
ResolvedTarget::SelfIndex(within[&j])
}
_ => ResolvedTarget::Local(
*addr_of[j].expect("child addressed first").as_bytes(),
),
}
}
};
edges.push((resolved_kind, resolved));
}
edges.sort_by(|a, b| {
(kind_key(&a.0), target_key(&a.1)).cmp(&(kind_key(&b.0), target_key(&b.1)))
});
edges.dedup_by_key(|e| (kind_key(&e.0), target_key(&e.1)));
let mut axioms: Vec<&str> = node.axioms.iter().map(|s| s.as_str()).collect();
axioms.sort_unstable();
axioms.dedup();
Ok(NodeCanon {
kind: node.kind.as_str(),
name: node.name.as_str(),
lexical: node.lexical.as_deref(),
axioms,
edges,
})
}
fn kind_key(k: &ResolvedKind) -> (u8, Vec<u8>) {
match k {
ResolvedKind::Grounded(a) => (0, a.to_vec()),
ResolvedKind::Free(name) => (1, name.as_bytes().to_vec()),
}
}
fn target_key(t: &ResolvedTarget) -> (u8, Vec<u8>) {
match t {
ResolvedTarget::Local(a) => (0, a.to_vec()),
ResolvedTarget::SelfIndex(n) => (1, n.to_le_bytes().to_vec()),
ResolvedTarget::Grounded { ontology, atom } => {
let mut k = ontology.as_bytes().to_vec();
k.extend_from_slice(atom);
(2, k)
}
}
}
fn tarjan_scc(n: usize, adj: &[Vec<usize>]) -> (Vec<usize>, Vec<Vec<usize>>, Vec<usize>) {
const UNVISITED: usize = usize::MAX;
let mut idx = vec![UNVISITED; n];
let mut low = vec![0usize; n];
let mut on_stack = vec![false; n];
let mut stack: Vec<usize> = Vec::new();
let mut scc_of = vec![UNVISITED; n];
let mut members: Vec<Vec<usize>> = Vec::new();
let mut order: Vec<usize> = Vec::new();
let mut next_idx = 0usize;
for s in 0..n {
if idx[s] != UNVISITED {
continue;
}
let mut work: Vec<(usize, usize)> = Vec::new();
work.push((s, 0));
idx[s] = next_idx;
low[s] = next_idx;
next_idx += 1;
stack.push(s);
on_stack[s] = true;
while let Some(&(v, ci)) = work.last() {
if ci < adj[v].len() {
work.last_mut().unwrap().1 += 1;
let w = adj[v][ci];
if idx[w] == UNVISITED {
idx[w] = next_idx;
low[w] = next_idx;
next_idx += 1;
stack.push(w);
on_stack[w] = true;
work.push((w, 0));
} else if on_stack[w] {
low[v] = low[v].min(idx[w]);
}
} else {
if low[v] == idx[v] {
let scc_id = members.len();
let mut comp = Vec::new();
loop {
let w = stack.pop().unwrap();
on_stack[w] = false;
scc_of[w] = scc_id;
comp.push(w);
if w == v {
break;
}
}
members.push(comp);
order.push(scc_id);
}
work.pop();
if let Some(&(p, _)) = work.last() {
low[p] = low[p].min(low[v]);
}
}
}
}
(scc_of, members, order)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::definition::Definition;
fn node(name: &str, lexical: Option<&str>, edges: &[(&str, &str)]) -> Definition {
Definition {
kind: "Concept".into(),
name: name.into(),
edges: edges
.iter()
.map(|(k, t)| ((*k).to_string(), EdgeTarget::Local((*t).to_string())))
.collect(),
axioms: vec![],
lexical: lexical.map(|s| s.to_string()),
}
}
fn rec(a: &Archive, name: &str) -> ContentAddress {
a.recursive_addresses().unwrap()[name]
}
#[test]
fn recursive_address_fixes_a_deep_referent() {
let mk = |c_lex: &str| Archive {
nodes: vec![
node("A", Some("a"), &[("Subsumption", "B")]),
node("B", Some("b"), &[("Subsumption", "C")]),
node("C", Some(c_lex), &[]),
],
connections: vec![],
};
let a1 = mk("c");
let a2 = mk("c-changed"); assert_ne!(rec(&a1, "A"), rec(&a2, "A"), "deep change propagates to A");
assert_eq!(
a1.nodes[0].address().unwrap(),
a2.nodes[0].address().unwrap(),
"A's LOCAL address is unchanged — only the recursive one moves"
);
}
#[test]
fn recursive_root_is_order_independent_but_deep_sensitive() {
let a = Archive {
nodes: vec![
node("A", None, &[("Subsumption", "B")]),
node("B", Some("b"), &[]),
],
connections: vec![],
};
let shuffled = Archive {
nodes: vec![a.nodes[1].clone(), a.nodes[0].clone()],
connections: vec![],
};
assert_eq!(
a.recursive_root().unwrap(),
shuffled.recursive_root().unwrap(),
"assembly order must not change the recursive root"
);
let deep = Archive {
nodes: vec![
node("A", None, &[("Subsumption", "B")]),
node("B", Some("b-DIFFERENT"), &[]),
],
connections: vec![],
};
assert_ne!(a.recursive_root().unwrap(), deep.recursive_root().unwrap());
assert_ne!(rec(&a, "A"), rec(&deep, "A"));
}
#[test]
fn a_duplicate_edge_does_not_change_the_recursive_address() {
let plain = Archive {
nodes: vec![
node("A", None, &[("Subsumption", "B")]),
node("B", Some("b"), &[]),
],
connections: vec![],
};
let with_dup = Archive {
nodes: vec![
node("A", None, &[("Subsumption", "B"), ("Subsumption", "B")]),
node("B", Some("b"), &[]),
],
connections: vec![],
};
assert_eq!(
plain.nodes[0].address().unwrap(),
with_dup.nodes[0].address().unwrap(),
"the local address is dup-insensitive (the contract)"
);
assert_eq!(
rec(&plain, "A"),
rec(&with_dup, "A"),
"a duplicate edge must not change A's recursive address"
);
assert_eq!(
plain.recursive_root().unwrap(),
with_dup.recursive_root().unwrap(),
"the recursive root is dup-insensitive too"
);
}
#[test]
fn a_labeled_symmetric_cycle_addresses_deterministically() {
let hot = node("Hot", Some("hot"), &[("Equivalence", "Cold")]);
let cold = node("Cold", Some("cold"), &[("Equivalence", "Hot")]);
let a = Archive {
nodes: vec![hot.clone(), cold.clone()],
connections: vec![],
};
let r = a.recursive_addresses().unwrap(); assert_ne!(
r["Hot"], r["Cold"],
"distinct members get distinct addresses"
);
let reordered = Archive {
nodes: vec![cold, hot],
connections: vec![],
};
let r2 = reordered.recursive_addresses().unwrap();
assert_eq!(
r["Hot"], r2["Hot"],
"cycle addressing is node-order independent"
);
assert_eq!(r["Cold"], r2["Cold"]);
}
#[test]
fn a_dangling_local_edge_is_refused() {
let a = Archive {
nodes: vec![node("A", None, &[("Subsumption", "Ghost")])],
connections: vec![],
};
assert!(matches!(
a.recursive_addresses(),
Err(RecursiveAddressError::DanglingLocalEdge { .. })
));
}
#[test]
fn teach_a_peer_round_trip() {
let mut g = node("G", Some("g"), &[]);
g.edges.push((
"denotes".to_string(),
EdgeTarget::Grounded {
ontology: "english".to_string(),
atom: ContentAddress::of(b"the-foreign-atom"),
},
));
let full = Archive {
nodes: vec![
node("A", Some("a"), &[("Subsumption", "B")]),
node("B", Some("b"), &[("Subsumption", "C"), ("Denotes", "G")]),
node("C", Some("c"), &[]),
node("Unrelated", Some("u"), &[]), g,
],
connections: vec![],
};
let sender_addr = rec(&full, "A");
let payload = full.extract_concept("A").unwrap();
let have = |n: &str| payload.nodes.iter().any(|d| d.name == n);
assert!(
have("A") && have("B") && have("C") && have("G"),
"the closure travels"
);
assert!(
!have("Unrelated"),
"unrelated nodes are excluded — minimal payload"
);
let receiver_addr = rec(&payload, "A");
assert_eq!(
sender_addr, receiver_addr,
"the peer agrees on A's identity, transitive + grounded deps included, from the minimal payload"
);
}
#[test]
fn the_functor_rides_with_the_concept() {
use crate::connection::{Connection, GeneratorAction};
let functor = Connection {
kind: "FullyFaithful".to_string(),
source: "Wordnet".to_string(),
target: "Praxis".to_string(),
action: GeneratorAction::Functor {
map_object: vec![("Synset".to_string(), "Concept".to_string())],
map_morphism: vec![("hypernym".to_string(), "Subsumption".to_string())],
},
laws: vec![],
};
let full = Archive {
nodes: vec![
node("Dog", Some("dog"), &[("Subsumption", "Animal")]),
node("Animal", Some("animal"), &[]),
],
connections: vec![functor.clone()],
};
let payload = full.extract_concept("Dog").unwrap();
assert_eq!(
payload.connections,
vec![functor],
"the functor rides with the concept so the peer can interpret it"
);
assert_eq!(rec(&full, "Dog"), rec(&payload, "Dog"));
let without = Archive {
nodes: full.nodes.clone(),
connections: vec![],
};
assert_ne!(
full.recursive_root().unwrap(),
without.recursive_root().unwrap(),
"the functor contributes to the recursive root"
);
}
#[test]
fn the_default_kind_vocab_builds() {
let vocab =
KindVocab::from_archive(&meta::ontology()).expect("the meta kind floor must address");
for kind in [
"Subsumption",
"Contains",
"HasProperty",
"Roots",
"Constrains",
] {
assert!(
vocab.address_of(kind).is_some(),
"the meta floor must ground the format kind {kind:?}"
);
}
let a = Archive {
nodes: vec![
node("A", None, &[("Subsumption", "B")]),
node("B", None, &[]),
],
connections: vec![],
};
assert!(a.recursive_addresses().is_ok());
}
#[test]
fn a_kind_resolves_to_its_meaning_in_the_vocab() {
let vocab_with = KindVocab::from_archive(&Archive {
nodes: vec![
node("Transitive", None, &[]),
node("Rel", None, &[("HasProperty", "Transitive")]),
],
connections: vec![],
})
.unwrap();
let vocab_without = KindVocab::from_archive(&Archive {
nodes: vec![node("Rel", None, &[])],
connections: vec![],
})
.unwrap();
assert_ne!(
vocab_with.address_of("Rel"),
vocab_without.address_of("Rel"),
"a kind's vocab address folds in its declared properties"
);
let archive = Archive {
nodes: vec![node("A", None, &[("Rel", "B")]), node("B", None, &[])],
connections: vec![],
};
let with = archive.recursive_addresses_grounded(&vocab_with).unwrap();
let without = archive
.recursive_addresses_grounded(&vocab_without)
.unwrap();
assert_ne!(
with["A"], without["A"],
"A's recursive address depends on what the kind Rel MEANS in the vocab"
);
}
#[test]
fn an_ungrounded_kind_is_a_free_leaf() {
let mk = |k: &str| Archive {
nodes: vec![node("A", None, &[(k, "B")]), node("B", None, &[])],
connections: vec![],
};
let empty = KindVocab::empty();
assert_ne!(
mk("Foo").recursive_addresses_grounded(&empty).unwrap()["A"],
mk("Bar").recursive_addresses_grounded(&empty).unwrap()["A"],
"distinct ungrounded kinds address distinctly"
);
let other = KindVocab::from_archive(&Archive {
nodes: vec![node("Unrelated", None, &[])],
connections: vec![],
})
.unwrap();
assert_eq!(
mk("Foo").recursive_addresses_grounded(&empty).unwrap()["A"],
mk("Foo").recursive_addresses_grounded(&other).unwrap()["A"],
"a kind absent from the vocab is Free regardless of which vocab"
);
}
#[test]
fn the_loaded_relation_vocab_builds() {
let relations = load_relation_kinds();
assert!(
relations
.recursive_addresses_grounded(&KindVocab::empty())
.is_ok(),
"the committed Relations projection must address (closed; labeled cycles)"
);
}
#[test]
fn the_default_vocab_grounds_the_loaded_relation_kinds() {
let floor = KindVocab::from_archive(&meta::ontology()).unwrap();
let relations = KindVocab::from_archive(&load_relation_kinds()).unwrap();
for kind in [
"Parthood",
"Causation",
"Opposition",
"Equivalence",
"Specialisation",
] {
assert!(
floor.address_of(kind).is_none(),
"{kind} is a domain relation kind, not a format-floor kind"
);
assert!(
relations.address_of(kind).is_some(),
"the loaded Relations vocab must ground {kind}"
);
assert!(
default_kind_vocab().address_of(kind).is_some(),
"the default vocab must ground the loaded {kind}"
);
}
let archive = Archive {
nodes: vec![
node("Whole", None, &[("Parthood", "Part")]),
node("Part", None, &[]),
],
connections: vec![],
};
assert_ne!(
archive.recursive_addresses_grounded(&relations).unwrap()["Whole"],
archive
.recursive_addresses_grounded(&KindVocab::empty())
.unwrap()["Whole"],
"Parthood resolves to its loaded meaning, not its spelling"
);
}
#[test]
fn the_loaded_authority_overrides_the_bootstrap_floor() {
let floor = KindVocab::from_archive(&meta::ontology()).unwrap();
let relations = KindVocab::from_archive(&load_relation_kinds()).unwrap();
let f = floor
.address_of("Subsumption")
.expect("floor defines Subsumption");
let r = relations
.address_of("Subsumption")
.expect("Relations defines Subsumption");
assert_ne!(f, r, "the two tiers define Subsumption differently");
assert_eq!(
default_kind_vocab().address_of("Subsumption"),
Some(r),
"the loaded authority wins the collision"
);
}
#[test]
fn a_peer_agrees_on_kind_meaning_via_default_vocab() {
let full = Archive {
nodes: vec![
node("Engine", Some("engine"), &[("Parthood", "Car")]),
node("Car", Some("car"), &[]),
],
connections: vec![],
};
let sender = rec(&full, "Engine");
let payload = full.extract_concept("Engine").unwrap();
assert!(
!payload.nodes.iter().any(|n| n.name == "Parthood"),
"the kind's meaning is NOT shipped — both peers share the default vocab (B.3.i)"
);
assert_eq!(
rec(&payload, "Engine"),
sender,
"the peer agrees on Engine's identity, the loaded kind's meaning included"
);
let divergent = KindVocab::from_archive(&Archive {
nodes: vec![node("Parthood", Some("a different parthood"), &[])],
connections: vec![],
})
.unwrap();
assert_ne!(
payload.recursive_addresses_grounded(&divergent).unwrap()["Engine"],
sender,
"a peer that means a different Parthood does NOT agree — meaning, not spelling"
);
}
}