agileplus-domain 0.3.0

Typed intent graphs for engineering work — convert prompts into validated DAGs of intents, features, tasks, tests, and edges, then store, query, and serve them from Rust, HTTP, or MCP.
Documentation
//! Metadata structures: per-record `Meta`, `CanonicalMap` and its supporting
//! `Direction` / `CanonicalLinkType` enums, and the graph-level `GraphMetadata`.

use serde::{Deserialize, Serialize};
use std::fmt;

// ---------------------------------------------------------------------------
// Meta
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct Meta {
    pub confidence: Option<f64>,
    pub source: String,
    pub timestamp: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub agent_id: Option<String>,
}

// ---------------------------------------------------------------------------
// Canonical Map
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct CanonicalMap {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub link_type: Option<CanonicalLinkType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub direction: Option<Direction>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Direction {
    Forward,
    Reverse,
}

impl fmt::Display for Direction {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Direction::Forward => write!(f, "forward"),
            Direction::Reverse => write!(f, "reverse"),
        }
    }
}

// ---------------------------------------------------------------------------
// Canonical Link Types
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CanonicalLinkType {
    ParentOf,
    ChildOf,
    DependsOn,
    Blocks,
    Implements,
    Verifies,
    References,
    Duplicates,
}

impl fmt::Display for CanonicalLinkType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            CanonicalLinkType::ParentOf => "parent_of",
            CanonicalLinkType::ChildOf => "child_of",
            CanonicalLinkType::DependsOn => "depends_on",
            CanonicalLinkType::Blocks => "blocks",
            CanonicalLinkType::Implements => "implements",
            CanonicalLinkType::Verifies => "verifies",
            CanonicalLinkType::References => "references",
            CanonicalLinkType::Duplicates => "duplicates",
        };
        write!(f, "{}", s)
    }
}

impl std::str::FromStr for CanonicalLinkType {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "parent_of" => Ok(CanonicalLinkType::ParentOf),
            "child_of" => Ok(CanonicalLinkType::ChildOf),
            "depends_on" => Ok(CanonicalLinkType::DependsOn),
            "blocks" => Ok(CanonicalLinkType::Blocks),
            "implements" => Ok(CanonicalLinkType::Implements),
            "verifies" => Ok(CanonicalLinkType::Verifies),
            "references" => Ok(CanonicalLinkType::References),
            "duplicates" => Ok(CanonicalLinkType::Duplicates),
            _ => Err(format!("Unknown CanonicalLinkType: {}", s)),
        }
    }
}

// ---------------------------------------------------------------------------
// Graph Metadata
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct GraphMetadata {
    pub version: String,
    pub schema_uri: String,
    pub created_at: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub updated_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub node_count: Option<usize>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub edge_count: Option<usize>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dag_valid: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source_system: Option<String>,
}