Skip to main content

agent_office/services/kb/
mod.rs

1use crate::domain::{Edge, Properties, PropertyValue, string_to_node_id, NodeId};
2use crate::services::kb::domain::{LinkType, LuhmannId, Note, NoteId, NoteLink, NoteCounter};
3use crate::storage::{GraphStorage, StorageError, SearchQuery, EdgeDirection};
4use async_trait::async_trait;
5use thiserror::Error;
6
7pub mod domain;
8
9#[derive(Error, Debug)]
10pub enum KbError {
11    #[error("Note not found: {0}")]
12    NoteNotFound(NoteId),
13    
14    #[error("Note already exists: {0}")]
15    NoteAlreadyExists(NoteId),
16    
17    #[error("Invalid Luhmann ID: {0}")]
18    InvalidLuhmannId(String),
19    
20    #[error("Storage error: {0}")]
21    Storage(#[from] StorageError),
22    
23    #[error("Cannot link note to itself")]
24    SelfLink,
25}
26
27pub type Result<T> = std::result::Result<T, KbError>;
28
29/// Simplified Knowledge Base Service - shared across all agents, uses only Luhmann IDs
30#[async_trait]
31pub trait KnowledgeBaseService: Send + Sync {
32    // Core note operations (all use LuhmannId)
33    async fn create_note(
34        &self,
35        title: impl Into<String> + Send,
36        content: impl Into<String> + Send,
37    ) -> Result<Note>;
38    
39    async fn create_note_with_id(
40        &self,
41        id: LuhmannId,
42        title: impl Into<String> + Send,
43        content: impl Into<String> + Send,
44    ) -> Result<Note>;
45    
46    async fn create_branch(
47        &self,
48        parent_id: &LuhmannId,
49        title: impl Into<String> + Send,
50        content: impl Into<String> + Send,
51    ) -> Result<Note>;
52    
53    async fn get_note(&self, note_id: &LuhmannId) -> Result<Note>;
54    async fn list_notes(&self) -> Result<Vec<Note>>;
55    async fn list_notes_by_prefix(&self, prefix: &LuhmannId) -> Result<Vec<Note>>;
56    async fn delete_note(&self, note_id: &LuhmannId) -> Result<()>;
57    
58    // Search
59    async fn search_notes(&self, query: &str) -> Result<Vec<Note>>;
60    
61    // Link operations
62    async fn link_notes(
63        &self,
64        from_id: &LuhmannId,
65        to_id: &LuhmannId,
66        context: Option<String>,
67    ) -> Result<()>;
68    
69    async fn get_links(&self, note_id: &LuhmannId) -> Result<Vec<NoteLink>>;
70    
71    // Note relationships
72    async fn mark_continuation(&self, from_id: &LuhmannId, to_id: &LuhmannId) -> Result<()>;
73    
74    // Index operations
75    async fn create_index(&self, parent_id: &LuhmannId) -> Result<Note>;
76    
77    // Get full context of a note
78    async fn get_context(&self, note_id: &LuhmannId) -> Result<NoteContext>;
79}
80
81/// Full context of a note including all relationships
82#[derive(Debug, Clone)]
83pub struct NoteContext {
84    pub note: Note,
85    pub parent: Option<Note>,
86    pub children: Vec<Note>,
87    pub links_to: Vec<Note>,
88    pub backlinks: Vec<Note>,
89    pub continues_to: Vec<Note>,
90    pub continued_from: Vec<Note>,
91}
92
93/// Convert a LuhmannId to a NodeId for storage
94fn luhmann_to_node_id(luhmann_id: &LuhmannId) -> NodeId {
95    string_to_node_id(&luhmann_id.to_string())
96}
97
98pub struct KnowledgeBaseServiceImpl<S: GraphStorage> {
99    storage: S,
100}
101
102impl<S: GraphStorage> KnowledgeBaseServiceImpl<S> {
103    pub fn new(storage: S) -> Self {
104        Self { storage }
105    }
106
107    /// Convert LuhmannId to storage NodeId
108    fn to_node_id(&self, luhmann_id: &LuhmannId) -> NodeId {
109        luhmann_to_node_id(luhmann_id)
110    }
111
112    /// Get or initialize the note counter
113    async fn get_or_init_counter(&self) -> Result<NoteCounter> {
114        let counter_id = string_to_node_id("__kb_counter__");
115        match self.storage.get_node(counter_id).await {
116            Ok(node) => {
117                NoteCounter::from_node(&node)
118                    .ok_or_else(|| KbError::Storage(StorageError::ConstraintViolation("Invalid counter node".to_string())))
119            }
120            Err(StorageError::NodeNotFound(_)) => {
121                // Create new counter
122                let counter = NoteCounter::new();
123                let node = counter.to_node();
124                self.storage.create_node(&node).await?;
125                Ok(counter)
126            }
127            Err(e) => Err(KbError::Storage(e)),
128        }
129    }
130
131    /// Update the counter
132    async fn update_counter(&self, counter: &NoteCounter) -> Result<()> {
133        let node = counter.to_node();
134        self.storage.update_node(&node).await?;
135        Ok(())
136    }
137
138    /// Generate next available top-level ID
139    async fn next_main_id(&self) -> Result<LuhmannId> {
140        let mut counter = self.get_or_init_counter().await?;
141        let id = counter.next_main_topic_id();
142        self.update_counter(&counter).await?;
143        Ok(id)
144    }
145
146    /// Find the next available child ID under a parent
147    async fn next_child_id(&self, parent_id: &LuhmannId) -> Result<LuhmannId> {
148        let all_notes = self.list_notes().await?;
149        
150        // Collect existing children
151        let mut children: Vec<LuhmannId> = all_notes
152            .into_iter()
153            .map(|n| n.id)
154            .filter(|id| id.parent().as_ref() == Some(parent_id))
155            .collect();
156        
157        if children.is_empty() {
158            // First child
159            Ok(parent_id.first_child())
160        } else {
161            // Find the next sibling after the last child
162            children.sort();
163            let last = children.last().unwrap();
164            Ok(last.next_sibling()
165                .unwrap_or_else(|| last.first_child()))
166        }
167    }
168}
169
170#[async_trait]
171impl<S: GraphStorage> KnowledgeBaseService for KnowledgeBaseServiceImpl<S> {
172    async fn create_note(
173        &self,
174        title: impl Into<String> + Send,
175        content: impl Into<String> + Send,
176    ) -> Result<Note> {
177        // Generate next available top-level Luhmann ID
178        let luhmann_id = self.next_main_id().await?;
179        
180        // Check if note already exists
181        let node_id = self.to_node_id(&luhmann_id);
182        match self.storage.get_node(node_id).await {
183            Ok(_) => return Err(KbError::NoteAlreadyExists(luhmann_id)),
184            Err(StorageError::NodeNotFound(_)) => (), // Good, doesn't exist
185            Err(e) => return Err(KbError::Storage(e)),
186        }
187        
188        let note = Note::new(luhmann_id, title, content);
189        let node = note.to_node();
190        self.storage.create_node(&node).await?;
191        
192        Ok(note)
193    }
194
195    async fn create_note_with_id(
196        &self,
197        id: LuhmannId,
198        title: impl Into<String> + Send,
199        content: impl Into<String> + Send,
200    ) -> Result<Note> {
201        // Check if note already exists
202        let node_id = self.to_node_id(&id);
203        match self.storage.get_node(node_id).await {
204            Ok(_) => return Err(KbError::NoteAlreadyExists(id)),
205            Err(StorageError::NodeNotFound(_)) => (), // Good, doesn't exist
206            Err(e) => return Err(KbError::Storage(e)),
207        }
208        
209        let note = Note::new(id, title, content);
210        let node = note.to_node();
211        self.storage.create_node(&node).await?;
212        
213        Ok(note)
214    }
215
216    async fn create_branch(
217        &self,
218        parent_id: &LuhmannId,
219        title: impl Into<String> + Send,
220        content: impl Into<String> + Send,
221    ) -> Result<Note> {
222        // Verify parent exists
223        let parent_node_id = self.to_node_id(parent_id);
224        self.storage.get_node(parent_node_id).await
225            .map_err(|e| match e {
226                StorageError::NodeNotFound(_) => KbError::NoteNotFound(parent_id.clone()),
227                _ => KbError::Storage(e),
228            })?;
229        
230        // Generate child ID
231        let child_id = self.next_child_id(parent_id).await?;
232        
233        // Check if child already exists
234        let child_node_id = self.to_node_id(&child_id);
235        match self.storage.get_node(child_node_id).await {
236            Ok(_) => return Err(KbError::NoteAlreadyExists(child_id)),
237            Err(StorageError::NodeNotFound(_)) => (), // Good, doesn't exist
238            Err(e) => return Err(KbError::Storage(e)),
239        }
240        
241        // Create the note
242        let note = Note::new(child_id.clone(), title, content);
243        let node = note.to_node();
244        self.storage.create_node(&node).await?;
245        
246        // Create link to parent
247        let mut props = Properties::new();
248        props.insert("context".to_string(), PropertyValue::String(format!("Branch of {}", parent_id)));
249        
250        let edge = Edge::new(
251            "references",
252            self.to_node_id(&child_id),
253            parent_node_id,
254            props,
255        );
256        self.storage.create_edge(&edge).await?;
257        
258        Ok(note)
259    }
260
261    async fn get_note(&self, note_id: &LuhmannId) -> Result<Note> {
262        let node_id = self.to_node_id(note_id);
263        let node = self.storage.get_node(node_id).await
264            .map_err(|e| match e {
265                StorageError::NodeNotFound(_) => KbError::NoteNotFound(note_id.clone()),
266                _ => KbError::Storage(e),
267            })?;
268        
269        Note::from_node(&node)
270            .ok_or_else(|| KbError::NoteNotFound(note_id.clone()))
271    }
272
273    async fn list_notes(&self) -> Result<Vec<Note>> {
274        // Query all nodes of type "note"
275        let query = SearchQuery {
276            node_types: vec!["note".to_string()],
277            limit: 10000, // Large limit to get all notes
278            ..SearchQuery::default()
279        };
280        
281        let results = self.storage.search_nodes(&query).await?;
282        
283        let mut notes: Vec<Note> = results.items
284            .into_iter()
285            .filter_map(|node| Note::from_node(&node))
286            .collect();
287        
288        // Sort by LuhmannId for consistent ordering
289        notes.sort_by(|a, b| a.id.cmp(&b.id));
290        
291        Ok(notes)
292    }
293
294    async fn list_notes_by_prefix(&self, prefix: &LuhmannId) -> Result<Vec<Note>> {
295        let all_notes = self.list_notes().await?;
296        
297        let filtered: Vec<Note> = all_notes
298            .into_iter()
299            .filter(|note| {
300                note.id == *prefix || note.id.is_descendant_of(prefix)
301            })
302            .collect();
303        
304        Ok(filtered)
305    }
306
307    async fn delete_note(&self, note_id: &LuhmannId) -> Result<()> {
308        // Verify note exists first
309        let _ = self.get_note(note_id).await?;
310        
311        // Delete the note node
312        let node_id = self.to_node_id(note_id);
313        self.storage.delete_node(node_id).await
314            .map_err(|e| match e {
315                StorageError::NodeNotFound(_) => KbError::NoteNotFound(note_id.clone()),
316                _ => KbError::Storage(e),
317            })?;
318        
319        Ok(())
320    }
321
322    async fn search_notes(&self, query: &str) -> Result<Vec<Note>> {
323        let all_notes = self.list_notes().await?;
324        let query_lower = query.to_lowercase();
325        
326        let filtered: Vec<Note> = all_notes.into_iter()
327            .filter(|note| {
328                note.title.to_lowercase().contains(&query_lower) ||
329                note.content.to_lowercase().contains(&query_lower) ||
330                note.tags.iter().any(|tag| tag.to_lowercase().contains(&query_lower))
331            })
332            .collect();
333        
334        Ok(filtered)
335    }
336
337    async fn link_notes(
338        &self,
339        from_id: &LuhmannId,
340        to_id: &LuhmannId,
341        context: Option<String>,
342    ) -> Result<()> {
343        if from_id == to_id {
344            return Err(KbError::SelfLink);
345        }
346        
347        // Verify both notes exist
348        self.get_note(from_id).await?;
349        self.get_note(to_id).await?;
350        
351        // Create link edge
352        let mut props = Properties::new();
353        if let Some(ctx) = context {
354            props.insert("context".to_string(), PropertyValue::String(ctx));
355        }
356        
357        let edge = Edge::new(
358            "references",
359            self.to_node_id(from_id),
360            self.to_node_id(to_id),
361            props,
362        );
363        
364        self.storage.create_edge(&edge).await?;
365        Ok(())
366    }
367
368    async fn get_links(&self, note_id: &LuhmannId) -> Result<Vec<NoteLink>> {
369        let node_id = self.to_node_id(note_id);
370        
371        // Get outgoing edges
372        let edges = self.storage.get_edges_from(node_id, Some("references")).await?;
373        
374        let mut links = Vec::new();
375        for edge in edges {
376            // Get the target note by looking up the node and converting it
377            match self.storage.get_node(edge.to_node_id).await {
378                Ok(target_node) => {
379                    if let Some(target_id) = target_node.properties.get("luhmann_id")
380                        .and_then(|v| v.as_str())
381                        .and_then(|s| LuhmannId::parse(s))
382                    {
383                        let context = edge.properties.get("context")
384                            .and_then(|v| v.as_str())
385                            .map(String::from);
386                        
387                        links.push(NoteLink::new(
388                            note_id.clone(),
389                            target_id,
390                            LinkType::References,
391                            context,
392                        ));
393                    }
394                }
395                Err(_) => continue, // Skip notes that can't be found
396            }
397        }
398        
399        Ok(links)
400    }
401
402    async fn mark_continuation(&self, from_id: &LuhmannId, to_id: &LuhmannId) -> Result<()> {
403        if from_id == to_id {
404            return Err(KbError::SelfLink);
405        }
406        
407        // Verify both notes exist
408        self.get_note(from_id).await?;
409        self.get_note(to_id).await?;
410        
411        // Create "continues" edge
412        let mut props = Properties::new();
413        props.insert("context".to_string(), PropertyValue::String("Continues on next note".to_string()));
414        
415        let edge = Edge::new(
416            "continues",
417            self.to_node_id(from_id),
418            self.to_node_id(to_id),
419            props,
420        );
421        
422        self.storage.create_edge(&edge).await?;
423        Ok(())
424    }
425
426    async fn create_index(&self, parent_id: &LuhmannId) -> Result<Note> {
427        // Verify parent exists
428        let parent_note = self.get_note(parent_id).await?;
429        
430        // Find all direct children (notes that are immediate descendants)
431        let all_notes = self.list_notes().await?;
432        
433        let children: Vec<&Note> = all_notes
434            .iter()
435            .filter(|note| {
436                // Check if note's parent is the parent_id
437                note.id.parent().as_ref() == Some(parent_id)
438            })
439            .collect();
440        
441        // Create index note ID: {parent_id}0 (e.g., 1a -> 1a0)
442        let index_id = LuhmannId::parse(&format!("{}0", parent_id))
443            .ok_or_else(|| KbError::InvalidLuhmannId(format!("{}0", parent_id)))?;
444        
445        // Check if index already exists
446        let index_node_id = self.to_node_id(&index_id);
447        match self.storage.get_node(index_node_id).await {
448            Ok(_) => return Err(KbError::NoteAlreadyExists(index_id)),
449            Err(StorageError::NodeNotFound(_)) => (), // Good, doesn't exist
450            Err(e) => return Err(KbError::Storage(e)),
451        }
452        
453        // Build index content
454        let mut content = format!("# Index: {}\n\n", parent_note.title);
455        content.push_str(&format!("Parent note: [[{}]]\n\n", parent_id));
456        content.push_str("Children:\n\n");
457        
458        if children.is_empty() {
459            content.push_str("(No children)\n");
460        } else {
461            for child in &children {
462                content.push_str(&format!("- [[{}]]: {}\n", child.id, child.title));
463            }
464        }
465        
466        // Create the index note
467        let index_note = Note::new(
468            index_id.clone(),
469            format!("Index: {}", parent_note.title),
470            content,
471        );
472        
473        let node = index_note.to_node();
474        self.storage.create_node(&node).await?;
475        
476        // Create "child_of" relationship to parent
477        let mut props = Properties::new();
478        props.insert("context".to_string(), PropertyValue::String("Index of children".to_string()));
479        
480        let edge = Edge::new(
481            "child_of",
482            index_node_id,
483            self.to_node_id(parent_id),
484            props,
485        );
486        self.storage.create_edge(&edge).await?;
487        
488        Ok(index_note)
489    }
490    
491    async fn get_context(&self, note_id: &LuhmannId) -> Result<NoteContext> {
492        // Get the note itself
493        let note = self.get_note(note_id).await?;
494        
495        // Get parent (if any)
496        let parent = if let Some(parent_id) = note.id.parent() {
497            self.get_note(&parent_id).await.ok()
498        } else {
499            None
500        };
501        
502        // Get all children (direct descendants)
503        let all_notes = self.list_notes().await?;
504        let children: Vec<Note> = all_notes
505            .into_iter()
506            .filter(|n| n.id.parent().as_ref() == Some(note_id))
507            .collect();
508        
509        // Get links (notes this note links TO)
510        let links = self.get_links(note_id).await?;
511        let mut links_to = Vec::new();
512        for link in &links {
513            if let Ok(target_note) = self.get_note(&link.to_note_id).await {
514                links_to.push(target_note);
515            }
516        }
517        
518        // Get backlinks (notes that link TO this note via "references" edges)
519        let node_id = self.to_node_id(note_id);
520        let edges = self.storage.get_edges_to(node_id, Some("references")).await?;
521        let mut backlinks = Vec::new();
522        for edge in edges {
523            if let Ok(source_node) = self.storage.get_node(edge.from_node_id).await {
524                if let Some(note) = Note::from_node(&source_node) {
525                    backlinks.push(note);
526                }
527            }
528        }
529        
530        // Get continuations (notes this note "continues" to)
531        // Need to query for "continues" edges
532        let note_node_id = self.to_node_id(note_id);
533        let neighbors = self.storage
534            .get_neighbors(note_node_id, Some("continues"), EdgeDirection::Outgoing)
535            .await?;
536        
537        let mut continues_to = Vec::new();
538        for node in neighbors {
539            if let Some(luhmann_str) = node.get_property("luhmann_id").and_then(|v| v.as_str()) {
540                if let Some(target_id) = LuhmannId::parse(luhmann_str) {
541                    if let Ok(target_note) = self.get_note(&target_id).await {
542                        continues_to.push(target_note);
543                    }
544                }
545            }
546        }
547        
548        // Get notes that continue FROM this note (reverse of continues)
549        let incoming_neighbors = self.storage
550            .get_neighbors(note_node_id, Some("continues"), EdgeDirection::Incoming)
551            .await?;
552        
553        let mut continued_from = Vec::new();
554        for node in incoming_neighbors {
555            if let Some(luhmann_str) = node.get_property("luhmann_id").and_then(|v| v.as_str()) {
556                if let Some(source_id) = LuhmannId::parse(luhmann_str) {
557                    if let Ok(source_note) = self.get_note(&source_id).await {
558                        continued_from.push(source_note);
559                    }
560                }
561            }
562        }
563        
564        Ok(NoteContext {
565            note,
566            parent,
567            children,
568            links_to,
569            backlinks,
570            continues_to,
571            continued_from,
572        })
573    }
574}
575
576#[cfg(test)]
577mod tests {
578    use super::*;
579    use crate::storage::memory::InMemoryStorage;
580
581    #[tokio::test]
582    async fn test_create_note_auto_id() {
583        let storage = InMemoryStorage::new();
584        let kb = KnowledgeBaseServiceImpl::new(storage);
585        
586        // First note should get ID "1"
587        let note1 = kb.create_note("First Note", "Content 1").await.unwrap();
588        assert_eq!(note1.id.to_string(), "1");
589        
590        // Second note should get ID "2"
591        let note2 = kb.create_note("Second Note", "Content 2").await.unwrap();
592        assert_eq!(note2.id.to_string(), "2");
593    }
594
595    #[tokio::test]
596    async fn test_create_note_with_specific_id() {
597        let storage = InMemoryStorage::new();
598        let kb = KnowledgeBaseServiceImpl::new(storage);
599        
600        let id = LuhmannId::parse("1a").unwrap();
601        let note = kb.create_note_with_id(id.clone(), "Note 1a", "Content").await.unwrap();
602        assert_eq!(note.id, id);
603    }
604
605    #[tokio::test]
606    async fn test_create_branch() {
607        let storage = InMemoryStorage::new();
608        let kb = KnowledgeBaseServiceImpl::new(storage);
609        
610        // Create parent note "1"
611        let parent_id = LuhmannId::parse("1").unwrap();
612        kb.create_note_with_id(parent_id.clone(), "Parent", "Parent content").await.unwrap();
613        
614        // Create branch - should get ID "1a"
615        let child = kb.create_branch(&parent_id, "Child", "Child content").await.unwrap();
616        assert_eq!(child.id.to_string(), "1a");
617        
618        // Create another branch - should get ID "1b"
619        let child2 = kb.create_branch(&parent_id, "Child 2", "Child content 2").await.unwrap();
620        assert_eq!(child2.id.to_string(), "1b");
621    }
622
623    #[tokio::test]
624    async fn test_duplicate_id_fails() {
625        let storage = InMemoryStorage::new();
626        let kb = KnowledgeBaseServiceImpl::new(storage);
627        
628        let id = LuhmannId::parse("1").unwrap();
629        kb.create_note_with_id(id.clone(), "First", "Content").await.unwrap();
630        
631        // Creating with same ID should fail
632        let result = kb.create_note_with_id(id, "Second", "Content").await;
633        assert!(matches!(result, Err(KbError::NoteAlreadyExists(_))));
634    }
635
636    #[tokio::test]
637    async fn test_get_note() {
638        let storage = InMemoryStorage::new();
639        let kb = KnowledgeBaseServiceImpl::new(storage);
640        
641        let note = kb.create_note("Test", "Content").await.unwrap();
642        let retrieved = kb.get_note(&note.id).await.unwrap();
643        
644        assert_eq!(retrieved.title, "Test");
645        assert_eq!(retrieved.content, "Content");
646    }
647
648    #[tokio::test]
649    async fn test_list_notes() {
650        let storage = InMemoryStorage::new();
651        let kb = KnowledgeBaseServiceImpl::new(storage);
652        
653        kb.create_note_with_id(LuhmannId::parse("2").unwrap(), "Second", "Content").await.unwrap();
654        kb.create_note_with_id(LuhmannId::parse("1").unwrap(), "First", "Content").await.unwrap();
655        kb.create_note_with_id(LuhmannId::parse("1a").unwrap(), "Child", "Content").await.unwrap();
656        
657        let notes = kb.list_notes().await.unwrap();
658        
659        // Should be sorted by Luhmann ID
660        assert_eq!(notes.len(), 3);
661        assert_eq!(notes[0].id.to_string(), "1");
662        assert_eq!(notes[1].id.to_string(), "1a");
663        assert_eq!(notes[2].id.to_string(), "2");
664    }
665
666    #[tokio::test]
667    async fn test_list_by_prefix() {
668        let storage = InMemoryStorage::new();
669        let kb = KnowledgeBaseServiceImpl::new(storage);
670        
671        kb.create_note_with_id(LuhmannId::parse("1").unwrap(), "One", "Content").await.unwrap();
672        kb.create_note_with_id(LuhmannId::parse("1a").unwrap(), "One-A", "Content").await.unwrap();
673        kb.create_note_with_id(LuhmannId::parse("1a1").unwrap(), "One-A-One", "Content").await.unwrap();
674        kb.create_note_with_id(LuhmannId::parse("1b").unwrap(), "One-B", "Content").await.unwrap();
675        kb.create_note_with_id(LuhmannId::parse("2").unwrap(), "Two", "Content").await.unwrap();
676        
677        let prefix = LuhmannId::parse("1a").unwrap();
678        let notes = kb.list_notes_by_prefix(&prefix).await.unwrap();
679        
680        assert_eq!(notes.len(), 2); // 1a and 1a1
681        assert!(notes.iter().any(|n| n.id.to_string() == "1a"));
682        assert!(notes.iter().any(|n| n.id.to_string() == "1a1"));
683    }
684
685    #[tokio::test]
686    async fn test_search_notes() {
687        let storage = InMemoryStorage::new();
688        let kb = KnowledgeBaseServiceImpl::new(storage);
689        
690        kb.create_note("Rust Programming", "A systems language").await.unwrap();
691        kb.create_note("Python Basics", "Easy to learn").await.unwrap();
692        kb.create_note("Rust vs Go", "Comparison").await.unwrap();
693        
694        let results = kb.search_notes("rust").await.unwrap();
695        assert_eq!(results.len(), 2);
696    }
697
698    #[tokio::test]
699    async fn test_link_notes() {
700        let storage = InMemoryStorage::new();
701        let kb = KnowledgeBaseServiceImpl::new(storage);
702        
703        let note1 = kb.create_note("First", "Content").await.unwrap();
704        let note2 = kb.create_note("Second", "Content").await.unwrap();
705        
706        kb.link_notes(&note1.id, &note2.id, Some("See also".to_string())).await.unwrap();
707        
708        let links = kb.get_links(&note1.id).await.unwrap();
709        assert_eq!(links.len(), 1);
710        assert_eq!(links[0].to_note_id, note2.id);
711    }
712
713    #[tokio::test]
714    async fn test_self_link_fails() {
715        let storage = InMemoryStorage::new();
716        let kb = KnowledgeBaseServiceImpl::new(storage);
717        
718        let note = kb.create_note("Note", "Content").await.unwrap();
719        
720        let result = kb.link_notes(&note.id, &note.id, None).await;
721        assert!(matches!(result, Err(KbError::SelfLink)));
722    }
723
724    #[tokio::test]
725    async fn test_mark_continuation() {
726        let storage = InMemoryStorage::new();
727        let kb = KnowledgeBaseServiceImpl::new(storage);
728        
729        let id1 = LuhmannId::parse("1").unwrap();
730        let id2 = LuhmannId::parse("2").unwrap();
731        
732        kb.create_note_with_id(id1.clone(), "First", "Content 1").await.unwrap();
733        kb.create_note_with_id(id2.clone(), "Second", "Content 2").await.unwrap();
734        
735        // Mark note 1 as continuing to note 2
736        kb.mark_continuation(&id1, &id2).await.unwrap();
737        
738        // Self-continuation should fail
739        let result = kb.mark_continuation(&id1, &id1).await;
740        assert!(matches!(result, Err(KbError::SelfLink)));
741    }
742
743    #[tokio::test]
744    async fn test_create_index() {
745        let storage = InMemoryStorage::new();
746        let kb = KnowledgeBaseServiceImpl::new(storage);
747        
748        // Create parent and children
749        let parent_id = LuhmannId::parse("1").unwrap();
750        kb.create_note_with_id(parent_id.clone(), "Parent Note", "Parent content").await.unwrap();
751        
752        let child1_id = LuhmannId::parse("1a").unwrap();
753        kb.create_note_with_id(child1_id.clone(), "First Child", "Child 1 content").await.unwrap();
754        
755        let child2_id = LuhmannId::parse("1b").unwrap();
756        kb.create_note_with_id(child2_id.clone(), "Second Child", "Child 2 content").await.unwrap();
757        
758        // Create grandchild (should not appear in index)
759        let grandchild_id = LuhmannId::parse("1a1").unwrap();
760        kb.create_note_with_id(grandchild_id.clone(), "Grandchild", "Grandchild content").await.unwrap();
761        
762        // Create index
763        let index = kb.create_index(&parent_id).await.unwrap();
764        
765        // Index ID should be {parent_id}0
766        assert_eq!(index.id.to_string(), "10");
767        // Should contain references to children
768        assert!(index.content.contains("1a"));
769        assert!(index.content.contains("1b"));
770        assert!(index.content.contains("First Child"));
771        assert!(index.content.contains("Second Child"));
772        // Should NOT contain grandchild
773        assert!(!index.content.contains("1a1"));
774    }
775
776    #[tokio::test]
777    async fn test_delete_note() {
778        let storage = InMemoryStorage::new();
779        let kb = KnowledgeBaseServiceImpl::new(storage);
780        
781        // Create a note
782        let id = LuhmannId::parse("1").unwrap();
783        kb.create_note_with_id(id.clone(), "Test Note", "Test content").await.unwrap();
784        
785        // Verify note exists
786        let note = kb.get_note(&id).await.unwrap();
787        assert_eq!(note.title, "Test Note");
788        
789        // Delete the note
790        kb.delete_note(&id).await.unwrap();
791        
792        // Verify note is gone
793        let result = kb.get_note(&id).await;
794        assert!(matches!(result, Err(KbError::NoteNotFound(_))));
795        
796        // Verify it's not in the list anymore
797        let notes = kb.list_notes().await.unwrap();
798        assert!(notes.is_empty());
799    }
800
801    #[tokio::test]
802    async fn test_delete_nonexistent_note_fails() {
803        let storage = InMemoryStorage::new();
804        let kb = KnowledgeBaseServiceImpl::new(storage);
805        
806        // Try to delete a note that doesn't exist
807        let id = LuhmannId::parse("999").unwrap();
808        let result = kb.delete_note(&id).await;
809        
810        assert!(matches!(result, Err(KbError::NoteNotFound(_))));
811    }
812}