persona-wire-core 0.14.0

persona-wire core: Domain (Graph + Specification + Compute + Constraint + AutoVersion + CRUD) + Application (NamedProjection registry, Use Case) + Infrastructure (SQLite storage, Rendering adapter). Transport-agnostic.
Documentation
//! Graph primitive — Node + Edge entities (open vocabulary type system).
//!
//! Identity model (v0.7):
//! - `id` (= `NodeId` / `EdgeId`) is a ULID generated by the server — opaque,
//!   immutable, primary key, the slot that `prev_id` chains follow.
//! - `name` is a human-readable label (no uniqueness constraint). Optional on
//!   `Edge` for backward compatibility with workflow id-as-name callers.
//! - At MCP boundaries: `wire_*_create` accepts `name` only and server mints
//!   the ULID; subsequent ops accept `id_or_name` and resolve internally
//!   (ULID parse → fall back to name lookup → error on multiple hits).

use serde::{Deserialize, Serialize};
pub use ulid::Ulid;

/// Derive a deterministic `Ulid` from a seed string. Used by tests, the
/// bundle TOML loader, and the manual migration path that needs stable ids
/// across runs (e.g. share an id between `Node.id` and `Edge.src_node`).
///
/// Not for production identity — server-side row creation uses `Ulid::new()`
/// so the timestamp half stays monotonic.
pub fn ulid_from_seed(seed: &str) -> Ulid {
    // 128-bit FNV-1a over the bytes — splits into (timestamp64, random64)
    // for the `Ulid::from_parts` constructor.
    let mut h: u128 = 0x6c62_272e_07bb_0142_62b8_2175_6295_c58d;
    for b in seed.bytes() {
        h ^= b as u128;
        h = h.wrapping_mul(0x0000_0000_0100_0000_0000_0000_0000_013b);
    }
    Ulid::from_parts((h >> 80) as u64, h & 0x_ffff_ffff_ffff_ffff_ffff)
}

pub type NodeId = Ulid;
pub type EdgeId = Ulid;
pub type TypeName = String;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Node {
    pub id: NodeId,
    pub name: String,
    pub r#type: TypeName,
    pub sot_ref: Option<String>,
    pub confidence: Option<f64>,
    pub applicability: Option<String>,
    pub last_verified_at: Option<i64>,
    pub review_due: Option<i64>,
    pub version: u32,
    pub prev_id: Option<NodeId>,
    pub metadata: serde_json::Value,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Edge {
    pub id: EdgeId,
    pub name: Option<String>,
    pub src_node: NodeId,
    pub tgt_node: NodeId,
    pub kind: TypeName,
    pub severity: Option<Severity>,
    pub metadata: serde_json::Value,
    pub version: u32,
    pub prev_id: Option<EdgeId>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
    Hard,
    Soft,
    Advisory,
}