Skip to main content

qualia_core_db/entity_view/
entity_id.rs

1//! Entity-centric identity for mindware HID projections.
2//!
3//! Stable `u64` subjects in the same 60-bit FNV space as hypermedia assets
4//! (`crate::hypermedia::fnv60`) and `q_hash` IRIs.
5
6use crate::hypermedia::fnv60;
7use crate::q_hash;
8use serde::{Deserialize, Serialize};
9
10/// Stable entity id for selection, scene nodes, and attribution subjects.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
12#[repr(transparent)]
13pub struct EntityId(pub u64);
14
15impl EntityId {
16    pub const fn from_raw(id: u64) -> Self {
17        Self(id)
18    }
19
20    pub const fn raw(self) -> u64 {
21        self.0
22    }
23
24    pub fn is_unset(self) -> bool {
25        self.0 == 0
26    }
27
28    /// Content-addressed id from arbitrary bytes (URI, span, DID payload).
29    pub fn from_bytes(bytes: &[u8]) -> Self {
30        Self(fnv60(bytes))
31    }
32
33    /// IRI / URI string (http, model://, ontology://, asset uri).
34    pub fn from_uri(uri: &str) -> Self {
35        Self::from_bytes(uri.trim().as_bytes())
36    }
37
38    /// Compile-time style IRI hash via `q_hash` (full u64; not masked to 60 bits).
39    /// Prefer `from_uri` for hypermedia subject alignment with `fnv60`.
40    pub fn from_iri_q_hash(iri: &str) -> Self {
41        Self(q_hash(iri))
42    }
43
44    /// Agent / natural-person DID string.
45    pub fn from_did(did: &str) -> Self {
46        Self::from_uri(did)
47    }
48
49    /// Fragment / span element: parent uri + stable local key (offsets, claim id).
50    pub fn from_fragment(parent_uri: &str, fragment_key: &str) -> Self {
51        let mut buf = Vec::with_capacity(parent_uri.len() + fragment_key.len() + 1);
52        buf.extend_from_slice(parent_uri.as_bytes());
53        buf.push(0x1f);
54        buf.extend_from_slice(fragment_key.as_bytes());
55        Self::from_bytes(&buf)
56    }
57}
58
59/// Coarse kind for projection / layout (not a full ontology class).
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
61#[serde(rename_all = "snake_case")]
62#[repr(u8)]
63pub enum EntityKind {
64    #[default]
65    Unknown = 0,
66    /// Natural person principal or peer.
67    Agent = 1,
68    /// Software instrument / sub-agent.
69    Instrument = 2,
70    /// Lived-memory / hypermedia asset.
71    Asset = 3,
72    /// Web locus (https URL).
73    WebLocus = 4,
74    /// Fragment / span / claim element.
75    Fragment = 5,
76    /// Commitment / deontic norm handle.
77    Commitment = 6,
78    /// World layer / stratum.
79    Layer = 7,
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn uri_ids_are_stable_and_distinct() {
88        let a = EntityId::from_uri("https://example.org/a");
89        let b = EntityId::from_uri("https://example.org/b");
90        assert_eq!(a, EntityId::from_uri("https://example.org/a"));
91        assert_ne!(a, b);
92        assert!(!a.is_unset());
93    }
94
95    #[test]
96    fn fragment_differs_from_parent() {
97        let parent = EntityId::from_uri("urn:doc:lesson");
98        let frag = EntityId::from_fragment("urn:doc:lesson", "span:10-40");
99        assert_ne!(parent, frag);
100    }
101}