Skip to main content

pr4xis_runtime/
connection.rs

1//! The connection — the uniform serialized morphism.
2//!
3//! One node subsumes functor / adjunction / lens / natural transformation: they
4//! differ only in `kind` (a name resolved against the meta-ontology), the
5//! [`GeneratorAction`] variant, and the `laws` they must satisfy — never in
6//! code. By the finite-presentation theorem (Lawvere functorial semantics), a
7//! structure-preserving map out of a finitely-presented category is fully
8//! determined by — and recoverable from — its finite action on generators; on
9//! load the action is replayed by folding generator-images through the target's
10//! composition (a `FreeExtension`), so no executable code is carried.
11//!
12//! The address is definition-bearing and cycle-safe: `source`/`target` are
13//! names bound by the ontology's Merkle root; the recursive form (where this
14//! address depends on the targets' own addresses) is the Merkle-DAG layer.
15
16use serde::{Deserialize, Serialize};
17
18use crate::address::ContentAddress;
19use crate::codec::{self, CodecError};
20
21/// The finite action-on-generators that *is* a connection. The variant names
22/// the categorical family; every table is `(source-generator name → target
23/// expression)`, which is why one representation serves all of them.
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub enum GeneratorAction {
26    /// A functor: where each source generating object and relation-kind goes.
27    Functor {
28        /// `(source object name, target expression)`, one row per generator.
29        map_object: Vec<(String, String)>,
30        /// `(source relation-kind name, target kind expression)`.
31        map_morphism: Vec<(String, String)>,
32    },
33    /// A natural transformation `η: F ⇒ G`: one component per object,
34    /// `η_x : F(x) → G(x)`.
35    NaturalTransformation {
36        /// `(object name, component-morphism expression)`.
37        components: Vec<(String, String)>,
38    },
39    /// A bidirectional lens (Foster et al. 2007): a `view` focus with `get`/
40    /// `put` morphisms. The `.prx` decompile round-trip is itself a lens.
41    Lens {
42        view: String,
43        get: String,
44        put: String,
45    },
46    /// An adjunction `F ⊣ G`: the two functors' object maps plus the unit `η`
47    /// and counit `ε` component families.
48    Adjunction {
49        left_map_object: Vec<(String, String)>,
50        right_map_object: Vec<(String, String)>,
51        unit: Vec<(String, String)>,
52        counit: Vec<(String, String)>,
53    },
54}
55
56impl GeneratorAction {
57    /// Sort every table so the encoding is order-independent.
58    fn canonicalize(&mut self) {
59        fn sort(v: &mut Vec<(String, String)>) {
60            v.sort();
61            v.dedup();
62        }
63        match self {
64            GeneratorAction::Functor {
65                map_object,
66                map_morphism,
67            } => {
68                sort(map_object);
69                sort(map_morphism);
70            }
71            GeneratorAction::NaturalTransformation { components } => sort(components),
72            GeneratorAction::Lens { .. } => {}
73            GeneratorAction::Adjunction {
74                left_map_object,
75                right_map_object,
76                unit,
77                counit,
78            } => {
79                sort(left_map_object);
80                sort(right_map_object);
81                sort(unit);
82                sort(counit);
83            }
84        }
85    }
86}
87
88/// The uniform serialized morphism.
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90pub struct Connection {
91    /// The specific kind, resolved against the meta-ontology (e.g.
92    /// `"FullyFaithful"`, `"FreeForgetful"`). The action variant names the
93    /// family; `kind` refines it.
94    pub kind: String,
95    /// The source category/ontology name.
96    pub source: String,
97    /// The target category/ontology name.
98    pub target: String,
99    /// The finite action-on-generators that IS this connection.
100    pub action: GeneratorAction,
101    /// Names of the laws this connection must satisfy (functor laws, the
102    /// triangle identities, the lens laws, …) — carried uniformly as data.
103    pub laws: Vec<String>,
104}
105
106impl Connection {
107    /// The content address of this connection — its definition-bearing identity,
108    /// over the canonical (sorted) action + laws.
109    pub fn address(&self) -> Result<ContentAddress, CodecError> {
110        let mut canon = self.clone();
111        canon.action.canonicalize();
112        canon.laws.sort();
113        canon.laws.dedup();
114        codec::address_of(&canon)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    fn functor() -> Connection {
123        Connection {
124            kind: "FullyFaithful".into(),
125            source: "OntologyArchive".into(),
126            target: "PraxisKnowledgeGraph".into(),
127            action: GeneratorAction::Functor {
128                map_object: vec![("ContentAddressableNode".into(), "ConceptNode".into())],
129                map_morphism: vec![("Subsumption".into(), "RelationEdge".into())],
130            },
131            laws: vec!["PreservesComposition".into(), "PreservesIdentity".into()],
132        }
133    }
134
135    #[test]
136    fn identical_connections_share_an_address() {
137        assert_eq!(functor().address().unwrap(), functor().address().unwrap());
138    }
139
140    #[test]
141    fn changing_the_action_changes_the_address() {
142        let mut b = functor();
143        if let GeneratorAction::Functor { map_object, .. } = &mut b.action {
144            map_object.push(("MerkleEdge".into(), "RelationEdge".into()));
145        }
146        assert_ne!(functor().address().unwrap(), b.address().unwrap());
147    }
148
149    #[test]
150    fn action_table_order_does_not_affect_address() {
151        let mut a = functor();
152        let mut b = functor();
153        if let GeneratorAction::Functor { map_object, .. } = &mut a.action {
154            map_object.push(("A".into(), "X".into()));
155            map_object.push(("B".into(), "Y".into()));
156        }
157        if let GeneratorAction::Functor { map_object, .. } = &mut b.action {
158            map_object.push(("B".into(), "Y".into())); // reversed
159            map_object.push(("A".into(), "X".into()));
160        }
161        assert_eq!(a.address().unwrap(), b.address().unwrap());
162    }
163
164    #[test]
165    fn distinct_families_get_distinct_addresses() {
166        let mut l = functor();
167        l.action = GeneratorAction::Lens {
168            view: "Source".into(),
169            get: "parse".into(),
170            put: "generate".into(),
171        };
172        assert_ne!(functor().address().unwrap(), l.address().unwrap());
173    }
174
175    #[test]
176    fn changing_the_kind_changes_the_address() {
177        let mut b = functor();
178        b.kind = "Faithful".into();
179        assert_ne!(functor().address().unwrap(), b.address().unwrap());
180    }
181
182    #[test]
183    fn every_family_addresses() {
184        for action in [
185            GeneratorAction::Functor {
186                map_object: vec![("a".into(), "b".into())],
187                map_morphism: vec![],
188            },
189            GeneratorAction::NaturalTransformation {
190                components: vec![("x".into(), "eta_x".into())],
191            },
192            GeneratorAction::Lens {
193                view: "v".into(),
194                get: "g".into(),
195                put: "p".into(),
196            },
197            GeneratorAction::Adjunction {
198                left_map_object: vec![],
199                right_map_object: vec![],
200                unit: vec![],
201                counit: vec![],
202            },
203        ] {
204            let mut c = functor();
205            c.action = action;
206            assert!(c.address().is_ok());
207        }
208    }
209}