Skip to main content

persona_wire_core/domain/graph/
node.rs

1//! Graph primitive — Node + Edge entities (open vocabulary type system).
2//!
3//! Identity model (v0.7):
4//! - `id` (= `NodeId` / `EdgeId`) is a ULID generated by the server — opaque,
5//!   immutable, primary key, the slot that `prev_id` chains follow.
6//! - `name` is a human-readable label (no uniqueness constraint). Optional on
7//!   `Edge` for backward compatibility with workflow id-as-name callers.
8//! - At MCP boundaries: `wire_*_create` accepts `name` only and server mints
9//!   the ULID; subsequent ops accept `id_or_name` and resolve internally
10//!   (ULID parse → fall back to name lookup → error on multiple hits).
11
12use serde::{Deserialize, Serialize};
13pub use ulid::Ulid;
14
15/// Derive a deterministic `Ulid` from a seed string. Used by tests, the
16/// bundle TOML loader, and the manual migration path that needs stable ids
17/// across runs (e.g. share an id between `Node.id` and `Edge.src_node`).
18///
19/// Not for production identity — server-side row creation uses `Ulid::new()`
20/// so the timestamp half stays monotonic.
21pub fn ulid_from_seed(seed: &str) -> Ulid {
22    // 128-bit FNV-1a over the bytes — splits into (timestamp64, random64)
23    // for the `Ulid::from_parts` constructor.
24    let mut h: u128 = 0x6c62_272e_07bb_0142_62b8_2175_6295_c58d;
25    for b in seed.bytes() {
26        h ^= b as u128;
27        h = h.wrapping_mul(0x0000_0000_0100_0000_0000_0000_0000_013b);
28    }
29    Ulid::from_parts((h >> 80) as u64, h & 0x_ffff_ffff_ffff_ffff_ffff)
30}
31
32pub type NodeId = Ulid;
33pub type EdgeId = Ulid;
34pub type TypeName = String;
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct Node {
38    pub id: NodeId,
39    pub name: String,
40    pub r#type: TypeName,
41    pub sot_ref: Option<String>,
42    pub confidence: Option<f64>,
43    pub applicability: Option<String>,
44    pub last_verified_at: Option<i64>,
45    pub review_due: Option<i64>,
46    pub version: u32,
47    pub prev_id: Option<NodeId>,
48    pub metadata: serde_json::Value,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct Edge {
53    pub id: EdgeId,
54    pub name: Option<String>,
55    pub src_node: NodeId,
56    pub tgt_node: NodeId,
57    pub kind: TypeName,
58    pub severity: Option<Severity>,
59    pub metadata: serde_json::Value,
60    pub version: u32,
61    pub prev_id: Option<EdgeId>,
62}
63
64#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
65#[serde(rename_all = "lowercase")]
66pub enum Severity {
67    Hard,
68    Soft,
69    Advisory,
70}