kmp-domain 0.18.1

Domain model of the Kernel Memory Protocol: aggregates, value objects, repositories and projections, with no IO
Documentation
use std::future::Future;
use std::sync::Arc;

use crate::{
    ContextPathNeighborhood, NeighborhoodRequest, NodeNeighborhood, NodeProjection, PortError,
};

pub trait GraphNeighborhoodReader {
    /// Optional reuse identity for this operation's pinned graph AND bodies.
    /// Live readers and adapters unable to certify every write return None.
    /// A reader identity must also distinguish stores and reader incarnations.
    fn graph_read_revision(
        &self,
    ) -> impl Future<Output = Result<Option<crate::GraphReadRevision>, PortError>> + Send {
        async { Ok(None) }
    }

    /// Read the requested nodes without requiring their neighborhoods. Results
    /// preserve input order, duplicates and missing slots. This operation does
    /// not discover neighbors or authorize a scope; callers select refs first.
    /// Backends may override the sequential fallback with a point-read batch.
    fn load_nodes_batch(
        &self,
        node_ids: Vec<String>,
    ) -> impl Future<Output = Result<Vec<Option<NodeProjection>>, PortError>> + Send
    where
        Self: Sync,
    {
        async {
            let mut nodes = Vec::with_capacity(node_ids.len());
            for id in node_ids {
                nodes.push(self.load_neighborhood(&id, 1).await?.map(|n| n.root));
            }
            Ok(nodes)
        }
    }

    /// One bounded search and all returned relation explanations share a snapshot.
    /// Unsupported adapters fail explicitly; never fall back to an unbounded read.
    fn load_bounded_trace(
        &self,
        _request: &crate::TraceSearchRequest,
    ) -> impl Future<Output = Result<crate::TraceSearchResult, PortError>> + Send {
        async {
            Err(PortError::Unavailable(
                "bounded trace search is not supported by this graph adapter".into(),
            ))
        }
    }

    /// Seed-based role discovery and joint obligations over one graph snapshot.
    /// This library capability is separate from MCP request/response projection.
    fn load_evidence_paths(
        &self,
        _request: &crate::EvidencePathRequest,
    ) -> impl Future<Output = Result<crate::EvidencePathResult, PortError>> + Send {
        async {
            Err(PortError::Unavailable(
                "evidence paths are not supported by this graph adapter".into(),
            ))
        }
    }

    /// Admission-only graph catalogue. Source prose and node metadata may be
    /// absent; consumers must point-read admitted nodes before returning them.
    /// Memberships, relation explanations, entry summaries and placeholder
    /// admission must be preserved. The default supplies complete nodes.
    fn load_neighborhood_headers(
        &self,
        request: &NeighborhoodRequest,
    ) -> impl Future<Output = Result<Option<NodeNeighborhood>, PortError>> + Send {
        self.load_scoped_neighborhood(request)
    }

    fn load_neighborhood(
        &self,
        root_node_id: &str,
        depth: u32,
    ) -> impl Future<Output = Result<Option<NodeNeighborhood>, PortError>> + Send;

    /// Loads a neighbourhood already narrowed by the axes the caller resolved.
    ///
    /// The application resolves the dimension axis into fully namespaced scope
    /// ids before it asks for anything, and until this existed it carried them
    /// as far as the query and then filtered the bundle after materialising it
    /// whole. Handing them down lets a store that can narrow do so.
    ///
    /// The narrowing is a hint and never a contract: the default ignores it,
    /// which leaves an adapter correct and only slow, and the application
    /// filters what comes back either way. That is what makes adopting this
    /// one adapter at a time a pure performance change.
    fn load_scoped_neighborhood(
        &self,
        request: &NeighborhoodRequest,
    ) -> impl Future<Output = Result<Option<NodeNeighborhood>, PortError>> + Send {
        self.load_neighborhood(request.root_node_id(), request.depth())
    }

    fn load_context_path(
        &self,
        root_node_id: &str,
        target_node_id: &str,
        subtree_depth: u32,
    ) -> impl Future<Output = Result<Option<ContextPathNeighborhood>, PortError>> + Send;
}

impl<T> GraphNeighborhoodReader for Arc<T>
where
    T: GraphNeighborhoodReader + Send + Sync + ?Sized,
{
    async fn graph_read_revision(&self) -> Result<Option<crate::GraphReadRevision>, PortError> {
        self.as_ref().graph_read_revision().await
    }

    async fn load_nodes_batch(
        &self,
        node_ids: Vec<String>,
    ) -> Result<Vec<Option<NodeProjection>>, PortError> {
        self.as_ref().load_nodes_batch(node_ids).await
    }

    async fn load_bounded_trace(
        &self,
        request: &crate::TraceSearchRequest,
    ) -> Result<crate::TraceSearchResult, PortError> {
        self.as_ref().load_bounded_trace(request).await
    }

    async fn load_evidence_paths(
        &self,
        request: &crate::EvidencePathRequest,
    ) -> Result<crate::EvidencePathResult, PortError> {
        self.as_ref().load_evidence_paths(request).await
    }

    async fn load_neighborhood_headers(
        &self,
        request: &NeighborhoodRequest,
    ) -> Result<Option<NodeNeighborhood>, PortError> {
        self.as_ref().load_neighborhood_headers(request).await
    }

    async fn load_neighborhood(
        &self,
        root_node_id: &str,
        depth: u32,
    ) -> Result<Option<NodeNeighborhood>, PortError> {
        self.as_ref().load_neighborhood(root_node_id, depth).await
    }

    async fn load_scoped_neighborhood(
        &self,
        request: &NeighborhoodRequest,
    ) -> Result<Option<NodeNeighborhood>, PortError> {
        self.as_ref().load_scoped_neighborhood(request).await
    }

    async fn load_context_path(
        &self,
        root_node_id: &str,
        target_node_id: &str,
        subtree_depth: u32,
    ) -> Result<Option<ContextPathNeighborhood>, PortError> {
        self.as_ref()
            .load_context_path(root_node_id, target_node_id, subtree_depth)
            .await
    }
}

impl<T> GraphNeighborhoodReader for &T
where
    T: GraphNeighborhoodReader + Send + Sync + ?Sized,
{
    async fn graph_read_revision(&self) -> Result<Option<crate::GraphReadRevision>, PortError> {
        (*self).graph_read_revision().await
    }

    fn load_nodes_batch(
        &self,
        node_ids: Vec<String>,
    ) -> impl Future<Output = Result<Vec<Option<NodeProjection>>, PortError>> + Send {
        (*self).load_nodes_batch(node_ids)
    }

    async fn load_bounded_trace(
        &self,
        request: &crate::TraceSearchRequest,
    ) -> Result<crate::TraceSearchResult, PortError> {
        (*self).load_bounded_trace(request).await
    }

    async fn load_evidence_paths(
        &self,
        request: &crate::EvidencePathRequest,
    ) -> Result<crate::EvidencePathResult, PortError> {
        (*self).load_evidence_paths(request).await
    }

    async fn load_neighborhood_headers(
        &self,
        request: &NeighborhoodRequest,
    ) -> Result<Option<NodeNeighborhood>, PortError> {
        (*self).load_neighborhood_headers(request).await
    }

    async fn load_neighborhood(
        &self,
        root_node_id: &str,
        depth: u32,
    ) -> Result<Option<NodeNeighborhood>, PortError> {
        (*self).load_neighborhood(root_node_id, depth).await
    }

    async fn load_context_path(
        &self,
        root_node_id: &str,
        target_node_id: &str,
        subtree_depth: u32,
    ) -> Result<Option<ContextPathNeighborhood>, PortError> {
        (*self)
            .load_context_path(root_node_id, target_node_id, subtree_depth)
            .await
    }
}