Skip to main content

qualia_core_db/inference/
agent.rs

1//! Agent-identifier model (#16): resolve an identifier's AGENT TYPE and check grounding.
2//!
3//! An agent's type is carried as a graph relation (`rdf:type` → a values agent class),
4//! the same modal-predicate pattern as [`crate::modal_kind`], resolved via zero-alloc
5//! `QuinIndex` point lookups (one identity space, #14). On top of resolution this
6//! activates the agency.n3 grounding guard at runtime: an `ArtificialAgent` /
7//! `PlatformAgent` acting with no Principal (`values:operatedBy`) is UngroundedAgency —
8//! the G1' guard that keeps an AI agent accountable to a human principal rather than
9//! free-floating. (Agent identity is still relational + never definitive — this resolves
10//! a declared type, it does not *fix* the agent; see principle-identifiers-not-identity.)
11
12use crate::indexing::QuinIndex;
13use crate::q_hash;
14
15pub const P_RDF_TYPE: u64 = q_hash("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
16/// The accountable principal behind an agent (agency.n3 `values:operatedBy`).
17pub const P_OPERATED_BY: u64 = q_hash("https://ns.webcivics.net/values/operatedBy");
18
19// The values agent lattice (agency.n3).
20pub const A_NATURAL_PERSON: u64 = q_hash("https://ns.webcivics.net/values/NaturalPerson");
21pub const A_LEGAL_PERSON: u64 = q_hash("https://ns.webcivics.net/values/LegalPerson");
22pub const A_PUBLIC_AUTHORITY: u64 = q_hash("https://ns.webcivics.net/values/PublicAuthority");
23pub const A_ARTIFICIAL_AGENT: u64 = q_hash("https://ns.webcivics.net/values/ArtificialAgent");
24pub const A_PLATFORM_AGENT: u64 = q_hash("https://ns.webcivics.net/values/PlatformAgent");
25
26/// The declared agent class of `agent` (its `rdf:type`), if any.
27pub fn agent_type(index: &QuinIndex, agent: u64) -> Option<u64> {
28    index.object_of(agent, P_RDF_TYPE)
29}
30
31/// The accountable principal behind `agent` (`values:operatedBy`), if declared.
32pub fn principal_of(index: &QuinIndex, agent: u64) -> Option<u64> {
33    index.object_of(agent, P_OPERATED_BY)
34}
35
36/// Whether an agent class is a non-personhood artificial agent (must be grounded).
37#[inline]
38pub fn is_artificial(agent_class: u64) -> bool {
39    agent_class == A_ARTIFICIAL_AGENT || agent_class == A_PLATFORM_AGENT
40}
41
42/// agency.n3 G1' grounding guard: an `ArtificialAgent` / `PlatformAgent` acting with no
43/// Principal is **UngroundedAgency**. Returns `true` when the agent trips the flag.
44pub fn is_ungrounded_agency(index: &QuinIndex, agent: u64) -> bool {
45    match agent_type(index, agent) {
46        Some(class) if is_artificial(class) => principal_of(index, agent).is_none(),
47        _ => false,
48    }
49}
50
51/// Readable name for a known agent class.
52pub fn agent_type_name(class: u64) -> Option<&'static str> {
53    match class {
54        A_NATURAL_PERSON => Some("NaturalPerson"),
55        A_LEGAL_PERSON => Some("LegalPerson"),
56        A_PUBLIC_AUTHORITY => Some("PublicAuthority"),
57        A_ARTIFICIAL_AGENT => Some("ArtificialAgent"),
58        A_PLATFORM_AGENT => Some("PlatformAgent"),
59        _ => None,
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::NQuin;
67
68    fn t(s: u64, p: u64, o: u64) -> NQuin {
69        NQuin {
70            subject: s,
71            predicate: p,
72            object: o,
73            context: 0,
74            metadata: 0,
75            parity: 0,
76        }
77    }
78
79    #[test]
80    fn resolves_agent_type() {
81        let alice = q_hash("https://example.org/alice");
82        let idx = QuinIndex::from_slice(&[t(alice, P_RDF_TYPE, A_NATURAL_PERSON)]);
83        assert_eq!(agent_type(&idx, alice), Some(A_NATURAL_PERSON));
84        assert_eq!(
85            agent_type_name(agent_type(&idx, alice).unwrap()),
86            Some("NaturalPerson")
87        );
88    }
89
90    #[test]
91    fn natural_person_is_never_ungrounded() {
92        let alice = q_hash("https://example.org/alice");
93        let idx = QuinIndex::from_slice(&[t(alice, P_RDF_TYPE, A_NATURAL_PERSON)]);
94        assert!(!is_ungrounded_agency(&idx, alice));
95    }
96
97    #[test]
98    fn artificial_agent_without_principal_is_ungrounded() {
99        let bot = q_hash("https://example.org/bot");
100        let idx = QuinIndex::from_slice(&[t(bot, P_RDF_TYPE, A_ARTIFICIAL_AGENT)]);
101        // No values:operatedBy → trips the agency.n3 G1' UngroundedAgency guard.
102        assert!(is_ungrounded_agency(&idx, bot));
103    }
104
105    #[test]
106    fn artificial_agent_with_principal_is_grounded() {
107        let bot = q_hash("https://example.org/bot");
108        let human = q_hash("https://example.org/alice");
109        let idx = QuinIndex::from_slice(&[
110            t(bot, P_RDF_TYPE, A_ARTIFICIAL_AGENT),
111            t(bot, P_OPERATED_BY, human), // a Principal stands behind it
112        ]);
113        assert!(!is_ungrounded_agency(&idx, bot));
114        assert_eq!(principal_of(&idx, bot), Some(human));
115    }
116}