feat(kernel): complete tree-first graph tasks 074-080
This commit is contained in:
@@ -0,0 +1,397 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum KernelNodeType {
|
||||
Workspace,
|
||||
Folder,
|
||||
Page,
|
||||
Section,
|
||||
Asset,
|
||||
Book,
|
||||
Pdf,
|
||||
Mindmap,
|
||||
MindmapNode,
|
||||
Summary,
|
||||
AiNote,
|
||||
ReferenceAnchor,
|
||||
ContentNode,
|
||||
IndexNode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum KernelEdgeType {
|
||||
ParentOf,
|
||||
ChildOf,
|
||||
Contains,
|
||||
References,
|
||||
BacklinksTo,
|
||||
SourceOf,
|
||||
DerivedFrom,
|
||||
Summarizes,
|
||||
Indexes,
|
||||
PointsTo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum KernelProjectionKind {
|
||||
SidebarTree,
|
||||
PageTree,
|
||||
FileTree,
|
||||
Mindmap,
|
||||
ReadView,
|
||||
SearchResults,
|
||||
RagIndex,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum KernelGraphDirection {
|
||||
Outgoing,
|
||||
Incoming,
|
||||
Both,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelSubtreeRef {
|
||||
pub root_node_id: String,
|
||||
#[serde(default)]
|
||||
pub path: Vec<String>,
|
||||
pub depth: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelNodeMetadata {
|
||||
pub title: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
#[serde(default)]
|
||||
pub extra: BTreeMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelContentPayload {
|
||||
pub format: String,
|
||||
pub body: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelRefsPayload {
|
||||
#[serde(default)]
|
||||
pub reference_node_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub evidence_node_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub source_node_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub extra: BTreeMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelAuditStamp {
|
||||
pub version: u64,
|
||||
pub revision: Option<u64>,
|
||||
pub request_id: Option<String>,
|
||||
pub trace_id: Option<String>,
|
||||
pub actor_id: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for KernelAuditStamp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: 1,
|
||||
revision: None,
|
||||
request_id: None,
|
||||
trace_id: None,
|
||||
actor_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelNode {
|
||||
pub id: String,
|
||||
pub node_type: KernelNodeType,
|
||||
pub workspace_id: Option<String>,
|
||||
pub parent_id: Option<String>,
|
||||
pub subtree: Option<KernelSubtreeRef>,
|
||||
pub metadata: KernelNodeMetadata,
|
||||
pub content: Option<KernelContentPayload>,
|
||||
pub refs: Option<KernelRefsPayload>,
|
||||
#[serde(default)]
|
||||
pub audit: KernelAuditStamp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelEdge {
|
||||
pub id: String,
|
||||
pub edge_type: KernelEdgeType,
|
||||
pub workspace_id: Option<String>,
|
||||
pub from_node_id: String,
|
||||
pub to_node_id: String,
|
||||
#[serde(default)]
|
||||
pub metadata: BTreeMap<String, Value>,
|
||||
#[serde(default)]
|
||||
pub audit: KernelAuditStamp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelProjectionFilter {
|
||||
#[serde(default)]
|
||||
pub node_types: Vec<KernelNodeType>,
|
||||
#[serde(default)]
|
||||
pub edge_types: Vec<KernelEdgeType>,
|
||||
#[serde(default)]
|
||||
pub include_deleted: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelGetNode {
|
||||
pub node_id: String,
|
||||
pub workspace_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelGetSubtree {
|
||||
pub subtree: KernelSubtreeRef,
|
||||
pub workspace_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub include_edges: bool,
|
||||
#[serde(default)]
|
||||
pub filters: KernelProjectionFilter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelListChildren {
|
||||
pub parent_node_id: String,
|
||||
pub workspace_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub node_types: Vec<KernelNodeType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelListEdges {
|
||||
pub node_id: String,
|
||||
pub workspace_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub edge_types: Vec<KernelEdgeType>,
|
||||
pub direction: Option<KernelGraphDirection>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelTraverseGraph {
|
||||
pub start_node_id: String,
|
||||
pub workspace_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub edge_types: Vec<KernelEdgeType>,
|
||||
pub max_depth: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelProjectionRequest {
|
||||
pub projection: KernelProjectionKind,
|
||||
pub workspace_id: Option<String>,
|
||||
pub root_node_id: Option<String>,
|
||||
pub subtree: Option<KernelSubtreeRef>,
|
||||
#[serde(default)]
|
||||
pub filters: KernelProjectionFilter,
|
||||
#[serde(default)]
|
||||
pub include_content: bool,
|
||||
#[serde(default)]
|
||||
pub include_edges: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelSubtreeResult {
|
||||
pub root_node_id: String,
|
||||
pub nodes: Vec<KernelNode>,
|
||||
pub edges: Vec<KernelEdge>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelEdgeListResult {
|
||||
pub node_id: String,
|
||||
pub edges: Vec<KernelEdge>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelGraphVisit {
|
||||
pub node_id: String,
|
||||
pub depth: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelGraphTraversalResult {
|
||||
pub start_node_id: String,
|
||||
pub visited: Vec<KernelGraphVisit>,
|
||||
pub edges: Vec<KernelEdge>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelProjectionItem {
|
||||
pub node_id: String,
|
||||
pub parent_node_id: Option<String>,
|
||||
pub node_type: KernelNodeType,
|
||||
pub title: Option<String>,
|
||||
pub depth: u32,
|
||||
pub position: Option<i64>,
|
||||
pub child_count: u32,
|
||||
pub expanded_by_default: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelProjectionResult {
|
||||
pub projection_id: String,
|
||||
pub projection: KernelProjectionKind,
|
||||
pub root_node_id: Option<String>,
|
||||
pub items: Vec<KernelProjectionItem>,
|
||||
pub edges: Vec<KernelEdge>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelCreateNode {
|
||||
pub node: KernelNode,
|
||||
pub position: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelUpdateNode {
|
||||
pub node_id: String,
|
||||
pub metadata: Option<KernelNodeMetadata>,
|
||||
pub content: Option<KernelContentPayload>,
|
||||
pub refs: Option<KernelRefsPayload>,
|
||||
pub audit: Option<KernelAuditStamp>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelMoveSubtree {
|
||||
pub subtree: KernelSubtreeRef,
|
||||
pub new_parent_node_id: Option<String>,
|
||||
pub sort_order: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelAttachEdge {
|
||||
pub edge: KernelEdge,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelDetachEdge {
|
||||
pub edge_id: Option<String>,
|
||||
pub from_node_id: Option<String>,
|
||||
pub to_node_id: Option<String>,
|
||||
pub edge_type: Option<KernelEdgeType>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn kernel_projection_request_roundtrip_keeps_subtree_and_filters() {
|
||||
let request = KernelProjectionRequest {
|
||||
projection: KernelProjectionKind::SidebarTree,
|
||||
workspace_id: Some("ws_1".into()),
|
||||
root_node_id: Some("page_root".into()),
|
||||
subtree: Some(KernelSubtreeRef {
|
||||
root_node_id: "page_root".into(),
|
||||
path: vec!["page_root".into(), "page_child".into()],
|
||||
depth: Some(2),
|
||||
}),
|
||||
filters: KernelProjectionFilter {
|
||||
node_types: vec![KernelNodeType::Page, KernelNodeType::Folder],
|
||||
edge_types: vec![KernelEdgeType::ParentOf],
|
||||
include_deleted: false,
|
||||
},
|
||||
include_content: false,
|
||||
include_edges: true,
|
||||
};
|
||||
|
||||
let value = serde_json::to_value(&request).expect("request 应可序列化");
|
||||
assert_eq!(value["projection"], json!("sidebar_tree"));
|
||||
assert_eq!(value["subtree"]["rootNodeId"], json!("page_root"));
|
||||
|
||||
let decoded: KernelProjectionRequest =
|
||||
serde_json::from_value(value).expect("request 应可反序列化");
|
||||
assert_eq!(decoded, request);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kernel_node_serializes_content_refs_and_audit_boundaries() {
|
||||
let node = KernelNode {
|
||||
id: "node_1".into(),
|
||||
node_type: KernelNodeType::Summary,
|
||||
workspace_id: Some("ws_1".into()),
|
||||
parent_id: Some("page_1".into()),
|
||||
subtree: Some(KernelSubtreeRef {
|
||||
root_node_id: "page_1".into(),
|
||||
path: vec!["page_1".into(), "node_1".into()],
|
||||
depth: Some(1),
|
||||
}),
|
||||
metadata: KernelNodeMetadata {
|
||||
title: Some("摘要节点".into()),
|
||||
icon: None,
|
||||
tags: vec!["summary".into()],
|
||||
created_at: Some("2026-04-16T00:00:00Z".into()),
|
||||
updated_at: Some("2026-04-16T00:00:00Z".into()),
|
||||
extra: BTreeMap::new(),
|
||||
},
|
||||
content: Some(KernelContentPayload {
|
||||
format: "markdown".into(),
|
||||
body: json!({"text": "摘要正文"}),
|
||||
}),
|
||||
refs: Some(KernelRefsPayload {
|
||||
reference_node_ids: vec!["page_1".into()],
|
||||
evidence_node_ids: vec!["asset_1".into()],
|
||||
source_node_ids: vec!["pdf_1".into()],
|
||||
extra: BTreeMap::new(),
|
||||
}),
|
||||
audit: KernelAuditStamp {
|
||||
version: 3,
|
||||
revision: Some(9),
|
||||
request_id: Some("req_1".into()),
|
||||
trace_id: Some("trace_1".into()),
|
||||
actor_id: Some("user_1".into()),
|
||||
},
|
||||
};
|
||||
|
||||
let value = serde_json::to_value(&node).expect("node 应可序列化");
|
||||
assert_eq!(value["nodeType"], json!("summary"));
|
||||
assert_eq!(value["content"]["format"], json!("markdown"));
|
||||
assert_eq!(value["refs"]["referenceNodeIds"], json!(["page_1"]));
|
||||
assert_eq!(value["audit"]["traceId"], json!("trace_1"));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod command;
|
||||
pub mod common;
|
||||
pub mod governance;
|
||||
pub mod kernel;
|
||||
pub mod mindmap;
|
||||
pub mod query;
|
||||
pub mod tool;
|
||||
@@ -16,6 +17,15 @@ pub use common::{
|
||||
ActorPayload, AffectedObject, ErrorDetail, ErrorPayload, OkPayload, RequestMeta, ResponseMeta,
|
||||
SourcePayload, TargetRef,
|
||||
};
|
||||
pub use kernel::{
|
||||
KernelAttachEdge, KernelAuditStamp, KernelContentPayload, KernelCreateNode,
|
||||
KernelDetachEdge, KernelEdge, KernelEdgeListResult, KernelEdgeType, KernelGetNode,
|
||||
KernelGetSubtree, KernelGraphDirection, KernelGraphTraversalResult, KernelGraphVisit,
|
||||
KernelListChildren, KernelListEdges, KernelMoveSubtree, KernelNode, KernelNodeMetadata,
|
||||
KernelNodeType, KernelProjectionFilter, KernelProjectionItem, KernelProjectionKind,
|
||||
KernelProjectionRequest, KernelProjectionResult, KernelRefsPayload, KernelSubtreeRef,
|
||||
KernelSubtreeResult, KernelTraverseGraph, KernelUpdateNode,
|
||||
};
|
||||
pub use mindmap::{
|
||||
MindmapNodeData, MindmapNodeInput, MindmapNodeRef, MindmapOp, MindmapTreeNode,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user