Skip to main content
Glama
data-model.md12.6 kB
# Data Model: Web-App Graph Visualization and Production Hardening **Feature**: 001-webapp-graph-production-release **Date**: 2025-11-20 **Status**: Complete ## Overview This document defines the data models for graph visualization and production hardening features. All models use Pydantic for validation, aligning with the constitution's type safety requirements. ## Core Entities ### Memory (Existing - Extended) The existing `Memory` model in `src/cortexgraph/storage/models.py` contains all fields needed for metadata display (P3). No structural changes required. ```python class Memory(BaseModel): """A memory unit with temporal decay properties.""" # Identity id: str = Field(default_factory=lambda: str(uuid4())) # Content content: str tags: list[str] = Field(default_factory=list) entities: list[str] = Field(default_factory=list) # Metadata source: str | None = None context: str | None = None meta: dict[str, Any] = Field(default_factory=dict) # Temporal decay created_at: int = Field(default_factory=lambda: int(time.time())) last_used: int = Field(default_factory=lambda: int(time.time())) use_count: int = 1 strength: float = Field(default=1.0, ge=0, le=2.0) # Spaced repetition (v0.5.1+) review_priority: float = Field(default=0.0, ge=0, le=1) last_review_at: int | None = None review_count: int = 0 cross_domain_count: int = 0 # Status status: MemoryStatus = MemoryStatus.ACTIVE embedding: list[float] | None = None ``` **Display in Web-App** (P3 - Full Metadata): - All fields rendered in detail panel - Null/empty fields shown as "Not set" - Timestamps formatted as human-readable dates - Decay score calculated and displayed (not stored) --- ### Relation (Existing - No Changes) The existing `Relation` model handles all relationship types. ```python class Relation(BaseModel): """A directed relationship between two memories.""" id: str = Field(default_factory=lambda: str(uuid4())) from_memory_id: str to_memory_id: str relation_type: RelationType # related, causes, supports, contradicts, has_decision, consolidated_from strength: float = Field(default=1.0, ge=0, le=2.0) created_at: int = Field(default_factory=lambda: int(time.time())) metadata: dict[str, Any] = Field(default_factory=dict) ``` --- ### GraphNode (New - Visualization) Represents a memory as a visual node in the graph. ```python class GraphNode(BaseModel): """Visual representation of a memory in graph visualization.""" # Identity (from Memory) id: str # Display properties label: str # Truncated content for display tags: list[str] entities: list[str] # Visual encoding status: MemoryStatus # Determines color decay_score: float # Determines opacity (0.05-1.0) use_count: int # Determines size # Layout (optional, user-saved) x: float | None = None y: float | None = None fx: float | None = None # Fixed x (user pinned) fy: float | None = None # Fixed y (user pinned) # Timestamps for tooltips created_at: int last_used: int def memory_to_graph_node(memory: Memory, decay_score: float) -> GraphNode: """Convert a Memory to a GraphNode for visualization.""" return GraphNode( id=memory.id, label=memory.content[:100] + "..." if len(memory.content) > 100 else memory.content, tags=memory.tags, entities=memory.entities, status=memory.status, decay_score=decay_score, use_count=memory.use_count, created_at=memory.created_at, last_used=memory.last_used, ) ``` **Visual Encoding**: | Property | Visual Attribute | Mapping | |----------|-----------------|---------| | status | Color | active=#4CAF50, promoted=#2196F3, archived=#9E9E9E | | decay_score | Opacity | 0.05 → 0.3, 1.0 → 1.0 (linear) | | use_count | Size | 1 → 8px, 10+ → 20px (log scale) | | relation_count | Border | 0 → none, 5+ → thick | --- ### GraphEdge (New - Visualization) Represents a relation as a visual edge in the graph. ```python class GraphEdge(BaseModel): """Visual representation of a relation in graph visualization.""" # Identity (from Relation) id: str source: str # from_memory_id target: str # to_memory_id # Display properties relation_type: RelationType strength: float # Determines thickness # Computed for rendering directed: bool = True # All CortexGraph relations are directed def relation_to_graph_edge(relation: Relation) -> GraphEdge: """Convert a Relation to a GraphEdge for visualization.""" return GraphEdge( id=relation.id, source=relation.from_memory_id, target=relation.to_memory_id, relation_type=relation.relation_type, strength=relation.strength, ) ``` **Visual Encoding**: | Property | Visual Attribute | Mapping | |----------|-----------------|---------| | relation_type | Color | related=#666, causes=#FF9800, supports=#4CAF50, contradicts=#F44336 | | strength | Thickness | 0.0 → 1px, 2.0 → 4px | | directed | Arrow | Yes → arrowhead | --- ### GraphData (New - API Response) Container for full graph visualization data. ```python class GraphData(BaseModel): """Complete graph structure for visualization.""" nodes: list[GraphNode] edges: list[GraphEdge] # Statistics total_memories: int total_relations: int filtered_count: int | None = None # If filters applied # Performance query_time_ms: float class GraphResponse(BaseModel): """API response wrapper for graph data.""" success: bool = True data: GraphData ``` --- ### GraphFilter (New - Query Parameters) Filtering parameters for graph queries. ```python class GraphFilter(BaseModel): """Filters for graph visualization queries.""" # Content filters tags: list[str] | None = None entities: list[str] | None = None search_query: str | None = None # Score filters min_decay_score: float = Field(default=0.0, ge=0, le=1) # Time filters created_after: int | None = None created_before: int | None = None used_after: int | None = None # Status filter statuses: list[MemoryStatus] | None = None # Default: [ACTIVE] # Pagination (for large graphs) limit: int = Field(default=1000, ge=1, le=10000) offset: int = Field(default=0, ge=0) ``` --- ## Error Handling Models ### ErrorResponse (New - Production Hardening) Standardized error response format (FR-008). ```python class ErrorContext(BaseModel): """Additional context for error diagnosis.""" file: str | None = None line: int | None = None memory_id: str | None = None parameter: str | None = None value: Any | None = None class ErrorDetail(BaseModel): """Detailed error information with remediation.""" code: str # Machine-readable error code message: str # Human-readable description remediation: str # How to fix it context: ErrorContext | None = None class ErrorResponse(BaseModel): """API response wrapper for errors.""" success: bool = False error: ErrorDetail ``` **Error Code Enumeration**: ```python class ErrorCode(str, Enum): """Standardized error codes for CortexGraph.""" # Storage errors STORAGE_NOT_FOUND = "STORAGE_NOT_FOUND" STORAGE_CORRUPTION = "STORAGE_CORRUPTION" STORAGE_PERMISSION = "STORAGE_PERMISSION" # Validation errors INVALID_MEMORY_ID = "INVALID_MEMORY_ID" INVALID_RELATION_TYPE = "INVALID_RELATION_TYPE" VALIDATION_FAILED = "VALIDATION_FAILED" # Operation errors MEMORY_NOT_FOUND = "MEMORY_NOT_FOUND" RELATION_NOT_FOUND = "RELATION_NOT_FOUND" DUPLICATE_RELATION = "DUPLICATE_RELATION" # Security errors RATE_LIMITED = "RATE_LIMITED" INVALID_INPUT = "INVALID_INPUT" # System errors INTERNAL_ERROR = "INTERNAL_ERROR" SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE" ``` --- ### ValidationResult (New - Storage Validation) Result of storage file validation (FR-010). ```python class CorruptionDetail(BaseModel): """Details about a specific corruption issue.""" line: int error: str raw_content: str | None = None # Truncated for safety class ValidationResult(BaseModel): """Result of storage file validation.""" valid: bool file_path: str total_lines: int valid_lines: int errors: list[CorruptionDetail] = Field(default_factory=list) @property def error_rate(self) -> float: if self.total_lines == 0: return 0.0 return len(self.errors) / self.total_lines class StorageHealthReport(BaseModel): """Complete health report for storage system.""" memories_validation: ValidationResult relations_validation: ValidationResult index_status: str # "healthy", "stale", "missing" last_backup: int | None = None recommendations: list[str] = Field(default_factory=list) ``` --- ## Security Models ### RateLimitInfo (New - Rate Limiting) Information about rate limiting state. ```python class RateLimitInfo(BaseModel): """Rate limiting information for API responses.""" limit: int remaining: int reset_at: int # Unix timestamp retry_after: int | None = None # Seconds until reset (if limited) ``` --- ### SecurityEvent (New - Audit Logging) Security-relevant events for logging (FR-013). ```python class SecurityEventType(str, Enum): """Types of security-relevant events.""" RATE_LIMITED = "rate_limited" INVALID_INPUT = "invalid_input" STORAGE_ACCESS = "storage_access" VALIDATION_FAILURE = "validation_failure" RECOVERY_ATTEMPT = "recovery_attempt" class SecurityEvent(BaseModel): """A security-relevant event for audit logging.""" timestamp: int = Field(default_factory=lambda: int(time.time())) event_type: SecurityEventType source_ip: str | None = None endpoint: str details: dict[str, Any] = Field(default_factory=dict) # Never log sensitive data # No memory content, no full paths, no credentials ``` --- ## Relationships Between Models ``` ┌─────────────┐ 1:N ┌──────────────┐ │ Memory │◄───────────►│ Relation │ │ │ │ │ │ (storage) │ │ (storage) │ └──────┬──────┘ └──────┬───────┘ │ │ │ transform │ transform ▼ ▼ ┌─────────────┐ ┌──────────────┐ │ GraphNode │ │ GraphEdge │ │ │◄───────────►│ │ │ (view) │ source/ │ (view) │ └─────────────┘ target └──────────────┘ │ │ │ │ ▼ ▼ ┌─────────────────────────────────────────┐ │ GraphData │ │ │ │ nodes: list[GraphNode] │ │ edges: list[GraphEdge] │ │ stats: ... │ └─────────────────────────────────────────┘ ``` --- ## Model Location All new models will be added to `src/cortexgraph/storage/models.py` to maintain single source of truth for data structures. This follows the existing pattern and ensures: 1. Type hints available throughout codebase 2. Pydantic validation on all data 3. Easy import: `from cortexgraph.storage.models import GraphNode, GraphEdge` --- ## Backward Compatibility **No breaking changes**: - Existing Memory and Relation models unchanged - New models are additive - API responses wrapped in new structures (success/error pattern) - Old clients can ignore new fields **Migration path**: - v0.7.x → v1.0.0: No data migration required - API consumers should update to expect new response format - Old response format deprecated but available via header `Accept: application/json; version=0.7`

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prefrontalsys/mnemex'

If you have feedback or need assistance with the MCP directory API, please join our Discord server