cellstate_core/
enums.rs

1//! Enum types for CELLSTATE entities
2//!
3//! ## Database string convention (from_db_str / as_db_str)
4//!
5//! Reading from the database accepts both legacy (PascalCase or mixed-case) and
6//! new (snake_case) values for backward compatibility; **writing uses snake_case
7//! only** via `as_db_str()`. Do not remove or simplify `from_db_str()` normalization
8//! without a migration that updates existing enum columns to snake_case.
9
10use crate::DurationMs;
11use serde::{Deserialize, Serialize};
12use std::fmt;
13use std::str::FromStr;
14
15// ============================================================================
16// UNIFIED ENUM PARSE ERROR
17// ============================================================================
18
19/// Error returned when parsing a DB enum string fails.
20///
21/// Replaces the 17+ individual `*ParseError` structs that had identical
22/// `Display` and `Error` implementations. The `enum_name` field carries the
23/// name of the target enum so error messages remain specific.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct EnumParseError {
26    /// Name of the enum type that was being parsed (e.g. "AgentStatus").
27    pub enum_name: &'static str,
28    /// The raw input string that could not be parsed.
29    pub input: String,
30}
31
32impl fmt::Display for EnumParseError {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        write!(f, "Unknown {} value: '{}'", self.enum_name, self.input)
35    }
36}
37
38impl std::error::Error for EnumParseError {}
39
40// ============================================================================
41// CORE ENUMS
42// ============================================================================
43
44/// Time-to-live and retention configuration for memory entries.
45/// Supports both time-based expiration and count-based limits.
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
49pub enum TTL {
50    /// Never expires
51    Persistent,
52    /// Expires when session ends
53    Session,
54    /// Expires when scope closes
55    Scope,
56    /// Expires after specified duration in milliseconds
57    Duration(DurationMs),
58    /// Ephemeral - expires when scope closes (alias for Scope)
59    Ephemeral,
60    /// Short-term retention (~1 hour)
61    ShortTerm,
62    /// Medium-term retention (~24 hours)
63    MediumTerm,
64    /// Long-term retention (~7 days)
65    LongTerm,
66    /// Permanent - never expires (alias for Persistent)
67    Permanent,
68    /// Keep at most N entries (count-based retention from pack config)
69    Max(usize),
70}
71
72/// Entity type discriminator for polymorphic references.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
74#[serde(rename_all = "snake_case")]
75#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
76pub enum EntityType {
77    Trajectory,
78    Scope,
79    Artifact,
80    Note,
81    Turn,
82    Tenant,
83    AgentWorkingSet,
84    Link,
85    Belief,
86    Goal,
87    Plan,
88    Lock,
89    Message,
90    Agent,
91    Delegation,
92    Handoff,
93    Config,
94    Conflict,
95    Edge,
96    EvolutionSnapshot,
97    SummarizationPolicy,
98    SummarizationRequest,
99    ToolExecution,
100    /// Event (event DAG / storage context)
101    Event,
102}
103
104/// Memory category for hierarchical memory organization.
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
106#[serde(rename_all = "snake_case")]
107#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
108pub enum MemoryCategory {
109    Ephemeral,
110    Working,
111    Episodic,
112    Semantic,
113    Procedural,
114    Meta,
115}
116
117/// Principal type for identity and ownership scoping.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
119#[serde(rename_all = "snake_case")]
120#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
121pub enum PrincipalType {
122    User,
123    Agent,
124    System,
125}
126
127impl PrincipalType {
128    /// Convert to database string representation.
129    pub fn as_db_str(&self) -> &'static str {
130        match self {
131            PrincipalType::User => "user",
132            PrincipalType::Agent => "agent",
133            PrincipalType::System => "system",
134        }
135    }
136}
137
138impl fmt::Display for PrincipalType {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        f.write_str(self.as_db_str())
141    }
142}
143
144/// Agent type discriminator for registration and message routing.
145/// Extensible via Custom for user-defined agent kinds.
146#[derive(Debug, Clone, PartialEq, Eq, Hash)]
147#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
148pub enum AgentType {
149    Tester,
150    Monitor,
151    Planner,
152    Executor,
153    Reviewer,
154    Custom(String),
155}
156
157impl AgentType {
158    pub fn as_str(&self) -> &str {
159        match self {
160            AgentType::Tester => "tester",
161            AgentType::Monitor => "monitor",
162            AgentType::Planner => "planner",
163            AgentType::Executor => "executor",
164            AgentType::Reviewer => "reviewer",
165            AgentType::Custom(s) => s.as_str(),
166        }
167    }
168}
169
170impl Serialize for AgentType {
171    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
172    where
173        S: serde::Serializer,
174    {
175        serializer.serialize_str(self.as_str())
176    }
177}
178
179impl<'de> Deserialize<'de> for AgentType {
180    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
181    where
182        D: serde::Deserializer<'de>,
183    {
184        let s = String::deserialize(deserializer)?;
185        Ok(match s.to_lowercase().as_str() {
186            "tester" => AgentType::Tester,
187            "monitor" => AgentType::Monitor,
188            "planner" => AgentType::Planner,
189            "executor" => AgentType::Executor,
190            "reviewer" => AgentType::Reviewer,
191            _ => AgentType::Custom(s),
192        })
193    }
194}
195
196impl fmt::Display for AgentType {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        f.write_str(self.as_str())
199    }
200}
201
202impl FromStr for AgentType {
203    type Err = std::convert::Infallible;
204
205    fn from_str(s: &str) -> Result<Self, Self::Err> {
206        Ok(match s.to_lowercase().as_str() {
207            "tester" => AgentType::Tester,
208            "monitor" => AgentType::Monitor,
209            "planner" => AgentType::Planner,
210            "executor" => AgentType::Executor,
211            "reviewer" => AgentType::Reviewer,
212            _ => AgentType::Custom(s.to_string()),
213        })
214    }
215}
216
217impl From<&str> for AgentType {
218    fn from(s: &str) -> Self {
219        s.parse().unwrap()
220    }
221}
222
223/// Memory type for permission scoping (artifact, note, turn, working set).
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
225#[serde(rename_all = "snake_case")]
226#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
227pub enum MemoryType {
228    Artifact,
229    Note,
230    Turn,
231    WorkingSet,
232    /// Wildcard: all memory types (wire format "*" for backward compatibility).
233    #[serde(rename = "*")]
234    All,
235}
236
237impl MemoryType {
238    pub fn as_db_str(&self) -> &'static str {
239        match self {
240            MemoryType::Artifact => "artifact",
241            MemoryType::Note => "note",
242            MemoryType::Turn => "turn",
243            MemoryType::WorkingSet => "working_set",
244            MemoryType::All => "*",
245        }
246    }
247}
248
249/// Resource type for lock scoping.
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
251#[serde(rename_all = "snake_case")]
252#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
253pub enum ResourceType {
254    Trajectory,
255    Scope,
256    Artifact,
257    Note,
258    Agent,
259}
260
261impl ResourceType {
262    pub fn as_db_str(&self) -> &'static str {
263        match self {
264            ResourceType::Trajectory => "trajectory",
265            ResourceType::Scope => "scope",
266            ResourceType::Artifact => "artifact",
267            ResourceType::Note => "note",
268            ResourceType::Agent => "agent",
269        }
270    }
271}
272
273impl FromStr for ResourceType {
274    type Err = String;
275
276    fn from_str(s: &str) -> Result<Self, Self::Err> {
277        match normalize_token(s).as_str() {
278            "trajectory" => Ok(ResourceType::Trajectory),
279            "scope" => Ok(ResourceType::Scope),
280            "artifact" => Ok(ResourceType::Artifact),
281            "note" => Ok(ResourceType::Note),
282            "agent" => Ok(ResourceType::Agent),
283            _ => Err(format!("Invalid ResourceType: {}", s)),
284        }
285    }
286}
287
288/// Field types for schema definitions.
289/// Used by pack compiler and runtime validation.
290#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
291#[serde(rename_all = "snake_case")]
292#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
293pub enum FieldType {
294    /// UUID identifier
295    Uuid,
296    /// Text/string
297    Text,
298    /// Integer
299    Int,
300    /// Floating point
301    Float,
302    /// Boolean
303    Bool,
304    /// Timestamp
305    Timestamp,
306    /// JSON blob
307    Json,
308    /// Vector embedding with optional dimension hint
309    Embedding { dimensions: Option<usize> },
310    /// Enumeration with named variants
311    Enum { variants: Vec<String> },
312    /// Array of another field type
313    Array(Box<FieldType>),
314}
315
316/// Status of a trajectory (task container).
317#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
318#[serde(rename_all = "snake_case")]
319#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
320pub enum TrajectoryStatus {
321    Active,
322    Completed,
323    Failed,
324    Suspended,
325}
326
327/// Outcome status for completed trajectories.
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
329#[serde(rename_all = "snake_case")]
330#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
331pub enum OutcomeStatus {
332    Success,
333    Partial,
334    Failure,
335}
336
337/// Status of an agent in the system.
338#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
339#[serde(rename_all = "snake_case")]
340#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
341pub enum AgentStatus {
342    /// Agent is registered but not actively working
343    #[default]
344    Idle,
345    /// Agent is actively processing a task
346    Active,
347    /// Agent is blocked waiting on something (lock, delegation, etc.)
348    Blocked,
349    /// Agent has failed and requires attention
350    Failed,
351    /// Agent has been unregistered
352    Offline,
353}
354
355impl AgentStatus {
356    /// Convert to database string representation (lowercase to match DB CHECK constraints).
357    pub fn as_db_str(&self) -> &'static str {
358        match self {
359            AgentStatus::Idle => "idle",
360            AgentStatus::Active => "active",
361            AgentStatus::Blocked => "blocked",
362            AgentStatus::Failed => "failed",
363            AgentStatus::Offline => "offline",
364        }
365    }
366
367    /// Parse from database string representation.
368    pub fn from_db_str(s: &str) -> Result<Self, EnumParseError> {
369        match s.to_lowercase().as_str() {
370            "idle" => Ok(AgentStatus::Idle),
371            "active" => Ok(AgentStatus::Active),
372            "blocked" => Ok(AgentStatus::Blocked),
373            "failed" => Ok(AgentStatus::Failed),
374            "offline" => Ok(AgentStatus::Offline),
375            _ => Err(EnumParseError {
376                enum_name: "AgentStatus",
377                input: s.to_string(),
378            }),
379        }
380    }
381
382    /// Check if the agent can accept new work.
383    pub fn can_accept_work(&self) -> bool {
384        matches!(self, AgentStatus::Idle)
385    }
386}
387
388impl fmt::Display for AgentStatus {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        write!(f, "{}", self.as_db_str())
391    }
392}
393
394impl FromStr for AgentStatus {
395    type Err = EnumParseError;
396
397    fn from_str(s: &str) -> Result<Self, Self::Err> {
398        Self::from_db_str(s)
399    }
400}
401
402/// Status of a summarization request.
403#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
404#[serde(rename_all = "snake_case")]
405#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
406pub enum SummarizationRequestStatus {
407    Pending,
408    InProgress,
409    Completed,
410    Failed,
411}
412
413impl SummarizationRequestStatus {
414    pub fn as_db_str(&self) -> &'static str {
415        match self {
416            SummarizationRequestStatus::Pending => "pending",
417            SummarizationRequestStatus::InProgress => "in_progress",
418            SummarizationRequestStatus::Completed => "completed",
419            SummarizationRequestStatus::Failed => "failed",
420        }
421    }
422
423    pub fn from_db_str(s: &str) -> Result<Self, EnumParseError> {
424        match s.to_lowercase().as_str() {
425            "pending" => Ok(SummarizationRequestStatus::Pending),
426            "inprogress" | "in_progress" | "in-progress" => {
427                Ok(SummarizationRequestStatus::InProgress)
428            }
429            "completed" | "complete" => Ok(SummarizationRequestStatus::Completed),
430            "failed" | "failure" => Ok(SummarizationRequestStatus::Failed),
431            _ => Err(EnumParseError {
432                enum_name: "SummarizationRequestStatus",
433                input: s.to_string(),
434            }),
435        }
436    }
437}
438
439impl fmt::Display for SummarizationRequestStatus {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        write!(f, "{}", self.as_db_str())
442    }
443}
444
445impl FromStr for SummarizationRequestStatus {
446    type Err = EnumParseError;
447
448    fn from_str(s: &str) -> Result<Self, Self::Err> {
449        Self::from_db_str(s)
450    }
451}
452
453/// Status of a tool execution.
454#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
455#[serde(rename_all = "snake_case")]
456#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
457pub enum ToolExecutionStatus {
458    Started,
459    Succeeded,
460    Failed,
461}
462
463impl ToolExecutionStatus {
464    pub fn as_db_str(&self) -> &'static str {
465        match self {
466            ToolExecutionStatus::Started => "started",
467            ToolExecutionStatus::Succeeded => "succeeded",
468            ToolExecutionStatus::Failed => "failed",
469        }
470    }
471
472    pub fn from_db_str(s: &str) -> Result<Self, EnumParseError> {
473        match s.to_lowercase().as_str() {
474            "started" | "start" => Ok(ToolExecutionStatus::Started),
475            "succeeded" | "success" => Ok(ToolExecutionStatus::Succeeded),
476            "failed" | "failure" => Ok(ToolExecutionStatus::Failed),
477            _ => Err(EnumParseError {
478                enum_name: "ToolExecutionStatus",
479                input: s.to_string(),
480            }),
481        }
482    }
483}
484
485impl fmt::Display for ToolExecutionStatus {
486    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487        write!(f, "{}", self.as_db_str())
488    }
489}
490
491impl FromStr for ToolExecutionStatus {
492    type Err = EnumParseError;
493
494    fn from_str(s: &str) -> Result<Self, Self::Err> {
495        Self::from_db_str(s)
496    }
497}
498
499/// Role of a turn in conversation.
500#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
501#[serde(rename_all = "snake_case")]
502#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
503pub enum TurnRole {
504    User,
505    Assistant,
506    System,
507    Tool,
508}
509
510/// Type of artifact produced during a trajectory.
511#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
512#[serde(rename_all = "snake_case")]
513#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
514pub enum ArtifactType {
515    ErrorLog,
516    CodePatch,
517    DesignDecision,
518    UserPreference,
519    Fact,
520    Constraint,
521    ToolResult,
522    IntermediateOutput,
523    Custom,
524    Code,
525    Document,
526    Data,
527    Model,
528    Config,
529    Log,
530    Summary,
531    Decision,
532    Plan,
533    // Multimodal artifact types
534    Audio,
535    Image,
536    Video,
537    Transcript,
538    Screenshot,
539}
540
541/// Method used to extract an artifact or evidence.
542#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
543#[serde(rename_all = "snake_case")]
544#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
545pub enum ExtractionMethod {
546    /// Explicitly provided by user
547    Explicit,
548    /// Inferred from context
549    Inferred,
550    /// User provided directly
551    UserProvided,
552    /// Extracted by LLM
553    LlmExtraction,
554    /// Extracted by tool/function
555    ToolExtraction,
556    /// From memory recall
557    MemoryRecall,
558    /// From external API
559    ExternalApi,
560    /// Unknown or unspecified
561    #[default]
562    Unknown,
563}
564
565/// Type of note (cross-trajectory knowledge).
566#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
567#[serde(rename_all = "snake_case")]
568#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
569pub enum NoteType {
570    Convention,
571    Strategy,
572    Gotcha,
573    Fact,
574    Preference,
575    Relationship,
576    Procedure,
577    Meta,
578    Insight,
579    Correction,
580    Summary,
581}
582
583// ============================================================================
584// BATTLE INTEL ENUMS
585// ============================================================================
586
587/// LLM provider type for configuration.
588#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
589#[serde(rename_all = "snake_case")]
590#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
591pub enum ProviderType {
592    #[serde(rename = "openai")]
593    OpenAI,
594    Anthropic,
595    OpenRouter,
596    Local,
597    Custom,
598}
599
600impl ProviderType {
601    /// Convert to database string representation.
602    pub fn as_db_str(&self) -> &'static str {
603        match self {
604            ProviderType::OpenAI => "openai",
605            ProviderType::Anthropic => "anthropic",
606            ProviderType::OpenRouter => "openrouter",
607            ProviderType::Local => "local",
608            ProviderType::Custom => "custom",
609        }
610    }
611
612    /// Parse from database string representation.
613    pub fn from_db_str(s: &str) -> Result<Self, EnumParseError> {
614        match s.to_lowercase().as_str() {
615            "openai" => Ok(ProviderType::OpenAI),
616            "anthropic" => Ok(ProviderType::Anthropic),
617            "openrouter" => Ok(ProviderType::OpenRouter),
618            "local" => Ok(ProviderType::Local),
619            "custom" => Ok(ProviderType::Custom),
620            _ => Err(EnumParseError {
621                enum_name: "ProviderType",
622                input: s.to_string(),
623            }),
624        }
625    }
626}
627
628impl fmt::Display for ProviderType {
629    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
630        write!(f, "{}", self.as_db_str())
631    }
632}
633
634impl FromStr for ProviderType {
635    type Err = EnumParseError;
636
637    fn from_str(s: &str) -> Result<Self, Self::Err> {
638        Self::from_db_str(s)
639    }
640}
641
642/// Type of edge relationship between entities.
643#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
644#[serde(rename_all = "snake_case")]
645#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
646pub enum EdgeType {
647    Supports,
648    Contradicts,
649    Supersedes,
650    DerivedFrom,
651    RelatesTo,
652    Temporal,
653    Causal,
654    SynthesizedFrom,
655    Grouped,
656    Compared,
657}
658
659/// Semantic abstraction level for notes (L0 → L1 → L2 hierarchy).
660#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
661#[serde(rename_all = "snake_case")]
662#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
663pub enum AbstractionLevel {
664    #[default]
665    Raw,
666    Summary,
667    Principle,
668}
669
670/// Phase of pack config evolution cycle.
671#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
672#[serde(rename_all = "snake_case")]
673#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
674pub enum EvolutionPhase {
675    #[default]
676    Online,
677    Frozen,
678    Evolving,
679}
680
681/// Trigger condition for auto-summarization policy.
682#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
683#[serde(rename_all = "snake_case")]
684#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
685pub enum SummarizationTrigger {
686    DosageThreshold { percent: u8 },
687    ScopeClose,
688    TurnCount { count: i32 },
689    ArtifactCount { count: i32 },
690    Manual,
691}
692
693// ============================================================================
694// STRING CONVERSIONS
695// ============================================================================
696
697fn normalize_token(input: &str) -> String {
698    input
699        .chars()
700        .filter(|c| *c != '_')
701        .map(|c| c.to_ascii_lowercase())
702        .collect()
703}
704
705impl EntityType {
706    /// String form used in DB and wire (snake_case).
707    pub fn as_db_str(&self) -> &'static str {
708        match self {
709            EntityType::Trajectory => "trajectory",
710            EntityType::Scope => "scope",
711            EntityType::Artifact => "artifact",
712            EntityType::Note => "note",
713            EntityType::Turn => "turn",
714            EntityType::Tenant => "tenant",
715            EntityType::AgentWorkingSet => "agent_working_set",
716            EntityType::Link => "link",
717            EntityType::Belief => "belief",
718            EntityType::Goal => "goal",
719            EntityType::Plan => "plan",
720            EntityType::Lock => "lock",
721            EntityType::Message => "message",
722            EntityType::Agent => "agent",
723            EntityType::Delegation => "delegation",
724            EntityType::Handoff => "handoff",
725            EntityType::Config => "config",
726            EntityType::Conflict => "conflict",
727            EntityType::Edge => "edge",
728            EntityType::EvolutionSnapshot => "evolution_snapshot",
729            EntityType::SummarizationPolicy => "summarization_policy",
730            EntityType::SummarizationRequest => "summarization_request",
731            EntityType::ToolExecution => "tool_execution",
732            EntityType::Event => "event",
733        }
734    }
735}
736
737impl fmt::Display for EntityType {
738    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
739        let value = match self {
740            EntityType::Trajectory => "Trajectory",
741            EntityType::Scope => "Scope",
742            EntityType::Artifact => "Artifact",
743            EntityType::Note => "Note",
744            EntityType::Turn => "Turn",
745            EntityType::Tenant => "Tenant",
746            EntityType::AgentWorkingSet => "AgentWorkingSet",
747            EntityType::Link => "Link",
748            EntityType::Belief => "Belief",
749            EntityType::Goal => "Goal",
750            EntityType::Plan => "Plan",
751            EntityType::Lock => "Lock",
752            EntityType::Message => "Message",
753            EntityType::Agent => "Agent",
754            EntityType::Delegation => "Delegation",
755            EntityType::Handoff => "Handoff",
756            EntityType::Config => "Config",
757            EntityType::Conflict => "Conflict",
758            EntityType::Edge => "Edge",
759            EntityType::EvolutionSnapshot => "EvolutionSnapshot",
760            EntityType::SummarizationPolicy => "SummarizationPolicy",
761            EntityType::SummarizationRequest => "SummarizationRequest",
762            EntityType::ToolExecution => "ToolExecution",
763            EntityType::Event => "Event",
764        };
765        write!(f, "{}", value)
766    }
767}
768
769impl FromStr for EntityType {
770    type Err = String;
771
772    fn from_str(s: &str) -> Result<Self, Self::Err> {
773        let normalized = normalize_token(s);
774        match normalized.as_str() {
775            "trajectory" => Ok(EntityType::Trajectory),
776            "scope" => Ok(EntityType::Scope),
777            "artifact" => Ok(EntityType::Artifact),
778            "note" => Ok(EntityType::Note),
779            "turn" => Ok(EntityType::Turn),
780            "tenant" => Ok(EntityType::Tenant),
781            "agentworkingset" => Ok(EntityType::AgentWorkingSet),
782            "link" => Ok(EntityType::Link),
783            "belief" => Ok(EntityType::Belief),
784            "goal" => Ok(EntityType::Goal),
785            "plan" => Ok(EntityType::Plan),
786            "lock" => Ok(EntityType::Lock),
787            "message" => Ok(EntityType::Message),
788            "agent" => Ok(EntityType::Agent),
789            "delegation" => Ok(EntityType::Delegation),
790            "handoff" => Ok(EntityType::Handoff),
791            "config" => Ok(EntityType::Config),
792            "conflict" => Ok(EntityType::Conflict),
793            "edge" => Ok(EntityType::Edge),
794            "evolutionsnapshot" => Ok(EntityType::EvolutionSnapshot),
795            "summarizationpolicy" => Ok(EntityType::SummarizationPolicy),
796            "summarizationrequest" => Ok(EntityType::SummarizationRequest),
797            "toolexecution" => Ok(EntityType::ToolExecution),
798            "event" => Ok(EntityType::Event),
799            _ => Err(format!("Invalid EntityType: {}", s)),
800        }
801    }
802}
803
804impl fmt::Display for TrajectoryStatus {
805    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
806        let value = match self {
807            TrajectoryStatus::Active => "active",
808            TrajectoryStatus::Completed => "completed",
809            TrajectoryStatus::Failed => "failed",
810            TrajectoryStatus::Suspended => "suspended",
811        };
812        write!(f, "{}", value)
813    }
814}
815
816impl FromStr for TrajectoryStatus {
817    type Err = String;
818
819    fn from_str(s: &str) -> Result<Self, Self::Err> {
820        match normalize_token(s).as_str() {
821            "active" => Ok(TrajectoryStatus::Active),
822            "completed" | "complete" => Ok(TrajectoryStatus::Completed),
823            "failed" | "failure" => Ok(TrajectoryStatus::Failed),
824            "suspended" => Ok(TrajectoryStatus::Suspended),
825            _ => Err(format!("Invalid TrajectoryStatus: {}", s)),
826        }
827    }
828}
829
830impl fmt::Display for OutcomeStatus {
831    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
832        let value = match self {
833            OutcomeStatus::Success => "Success",
834            OutcomeStatus::Partial => "Partial",
835            OutcomeStatus::Failure => "Failure",
836        };
837        write!(f, "{}", value)
838    }
839}
840
841impl FromStr for OutcomeStatus {
842    type Err = String;
843
844    fn from_str(s: &str) -> Result<Self, Self::Err> {
845        match normalize_token(s).as_str() {
846            "success" => Ok(OutcomeStatus::Success),
847            "partial" => Ok(OutcomeStatus::Partial),
848            "failure" | "failed" => Ok(OutcomeStatus::Failure),
849            _ => Err(format!("Invalid OutcomeStatus: {}", s)),
850        }
851    }
852}
853
854impl fmt::Display for TurnRole {
855    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
856        let value = match self {
857            TurnRole::User => "user",
858            TurnRole::Assistant => "assistant",
859            TurnRole::System => "system",
860            TurnRole::Tool => "tool",
861        };
862        write!(f, "{}", value)
863    }
864}
865
866impl FromStr for TurnRole {
867    type Err = String;
868
869    fn from_str(s: &str) -> Result<Self, Self::Err> {
870        match normalize_token(s).as_str() {
871            "user" => Ok(TurnRole::User),
872            "assistant" => Ok(TurnRole::Assistant),
873            "system" => Ok(TurnRole::System),
874            "tool" => Ok(TurnRole::Tool),
875            _ => Err(format!("Invalid TurnRole: {}", s)),
876        }
877    }
878}
879
880impl fmt::Display for ArtifactType {
881    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
882        let value = match self {
883            ArtifactType::ErrorLog => "ErrorLog",
884            ArtifactType::CodePatch => "CodePatch",
885            ArtifactType::DesignDecision => "DesignDecision",
886            ArtifactType::UserPreference => "UserPreference",
887            ArtifactType::Fact => "Fact",
888            ArtifactType::Constraint => "Constraint",
889            ArtifactType::ToolResult => "ToolResult",
890            ArtifactType::IntermediateOutput => "IntermediateOutput",
891            ArtifactType::Custom => "Custom",
892            ArtifactType::Code => "Code",
893            ArtifactType::Document => "Document",
894            ArtifactType::Data => "Data",
895            ArtifactType::Model => "Model",
896            ArtifactType::Config => "Config",
897            ArtifactType::Log => "Log",
898            ArtifactType::Summary => "Summary",
899            ArtifactType::Decision => "Decision",
900            ArtifactType::Plan => "Plan",
901            ArtifactType::Audio => "Audio",
902            ArtifactType::Image => "Image",
903            ArtifactType::Video => "Video",
904            ArtifactType::Transcript => "Transcript",
905            ArtifactType::Screenshot => "Screenshot",
906        };
907        write!(f, "{}", value)
908    }
909}
910
911impl FromStr for ArtifactType {
912    type Err = String;
913
914    fn from_str(s: &str) -> Result<Self, Self::Err> {
915        match normalize_token(s).as_str() {
916            "errorlog" => Ok(ArtifactType::ErrorLog),
917            "codepatch" => Ok(ArtifactType::CodePatch),
918            "designdecision" => Ok(ArtifactType::DesignDecision),
919            "userpreference" => Ok(ArtifactType::UserPreference),
920            "fact" => Ok(ArtifactType::Fact),
921            "constraint" => Ok(ArtifactType::Constraint),
922            "toolresult" => Ok(ArtifactType::ToolResult),
923            "intermediateoutput" => Ok(ArtifactType::IntermediateOutput),
924            "custom" => Ok(ArtifactType::Custom),
925            "code" => Ok(ArtifactType::Code),
926            "document" => Ok(ArtifactType::Document),
927            "data" => Ok(ArtifactType::Data),
928            "model" => Ok(ArtifactType::Model),
929            "config" => Ok(ArtifactType::Config),
930            "log" => Ok(ArtifactType::Log),
931            "summary" => Ok(ArtifactType::Summary),
932            "decision" => Ok(ArtifactType::Decision),
933            "plan" => Ok(ArtifactType::Plan),
934            "audio" => Ok(ArtifactType::Audio),
935            "image" => Ok(ArtifactType::Image),
936            "video" => Ok(ArtifactType::Video),
937            "transcript" => Ok(ArtifactType::Transcript),
938            "screenshot" => Ok(ArtifactType::Screenshot),
939            _ => Err(format!("Invalid ArtifactType: {}", s)),
940        }
941    }
942}
943
944impl fmt::Display for ExtractionMethod {
945    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
946        let value = match self {
947            ExtractionMethod::Explicit => "explicit",
948            ExtractionMethod::Inferred => "inferred",
949            ExtractionMethod::UserProvided => "user_provided",
950            ExtractionMethod::LlmExtraction => "llm_extraction",
951            ExtractionMethod::ToolExtraction => "tool_extraction",
952            ExtractionMethod::MemoryRecall => "memory_recall",
953            ExtractionMethod::ExternalApi => "external_api",
954            ExtractionMethod::Unknown => "unknown",
955        };
956        write!(f, "{}", value)
957    }
958}
959
960impl FromStr for ExtractionMethod {
961    type Err = String;
962
963    fn from_str(s: &str) -> Result<Self, Self::Err> {
964        match normalize_token(s).as_str() {
965            "explicit" => Ok(ExtractionMethod::Explicit),
966            "inferred" => Ok(ExtractionMethod::Inferred),
967            "userprovided" => Ok(ExtractionMethod::UserProvided),
968            "llmextraction" => Ok(ExtractionMethod::LlmExtraction),
969            "toolextraction" => Ok(ExtractionMethod::ToolExtraction),
970            "memoryrecall" => Ok(ExtractionMethod::MemoryRecall),
971            "externalapi" => Ok(ExtractionMethod::ExternalApi),
972            "unknown" => Ok(ExtractionMethod::Unknown),
973            _ => Err(format!("Invalid ExtractionMethod: {}", s)),
974        }
975    }
976}
977
978impl fmt::Display for NoteType {
979    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
980        let value = match self {
981            NoteType::Convention => "Convention",
982            NoteType::Strategy => "Strategy",
983            NoteType::Gotcha => "Gotcha",
984            NoteType::Fact => "Fact",
985            NoteType::Preference => "Preference",
986            NoteType::Relationship => "Relationship",
987            NoteType::Procedure => "Procedure",
988            NoteType::Meta => "Meta",
989            NoteType::Insight => "Insight",
990            NoteType::Correction => "Correction",
991            NoteType::Summary => "Summary",
992        };
993        write!(f, "{}", value)
994    }
995}
996
997impl FromStr for NoteType {
998    type Err = String;
999
1000    fn from_str(s: &str) -> Result<Self, Self::Err> {
1001        match normalize_token(s).as_str() {
1002            "convention" => Ok(NoteType::Convention),
1003            "strategy" => Ok(NoteType::Strategy),
1004            "gotcha" => Ok(NoteType::Gotcha),
1005            "fact" => Ok(NoteType::Fact),
1006            "preference" => Ok(NoteType::Preference),
1007            "relationship" => Ok(NoteType::Relationship),
1008            "procedure" => Ok(NoteType::Procedure),
1009            "meta" => Ok(NoteType::Meta),
1010            "insight" => Ok(NoteType::Insight),
1011            "correction" => Ok(NoteType::Correction),
1012            "summary" => Ok(NoteType::Summary),
1013            _ => Err(format!("Invalid NoteType: {}", s)),
1014        }
1015    }
1016}
1017
1018impl fmt::Display for EdgeType {
1019    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1020        let value = match self {
1021            EdgeType::Supports => "supports",
1022            EdgeType::Contradicts => "contradicts",
1023            EdgeType::Supersedes => "supersedes",
1024            EdgeType::DerivedFrom => "derived_from",
1025            EdgeType::RelatesTo => "relates_to",
1026            EdgeType::Temporal => "temporal",
1027            EdgeType::Causal => "causal",
1028            EdgeType::SynthesizedFrom => "synthesized_from",
1029            EdgeType::Grouped => "grouped",
1030            EdgeType::Compared => "compared",
1031        };
1032        write!(f, "{}", value)
1033    }
1034}
1035
1036impl FromStr for EdgeType {
1037    type Err = String;
1038
1039    fn from_str(s: &str) -> Result<Self, Self::Err> {
1040        match normalize_token(s).as_str() {
1041            "supports" => Ok(EdgeType::Supports),
1042            "contradicts" => Ok(EdgeType::Contradicts),
1043            "supersedes" => Ok(EdgeType::Supersedes),
1044            "derivedfrom" => Ok(EdgeType::DerivedFrom),
1045            "relatesto" => Ok(EdgeType::RelatesTo),
1046            "temporal" => Ok(EdgeType::Temporal),
1047            "causal" => Ok(EdgeType::Causal),
1048            "synthesizedfrom" => Ok(EdgeType::SynthesizedFrom),
1049            "grouped" => Ok(EdgeType::Grouped),
1050            "compared" => Ok(EdgeType::Compared),
1051            _ => Err(format!("Invalid EdgeType: {}", s)),
1052        }
1053    }
1054}
1055
1056impl fmt::Display for AbstractionLevel {
1057    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1058        let value = match self {
1059            AbstractionLevel::Raw => "Raw",
1060            AbstractionLevel::Summary => "Summary",
1061            AbstractionLevel::Principle => "Principle",
1062        };
1063        write!(f, "{}", value)
1064    }
1065}
1066
1067impl FromStr for AbstractionLevel {
1068    type Err = String;
1069
1070    fn from_str(s: &str) -> Result<Self, Self::Err> {
1071        match normalize_token(s).as_str() {
1072            "raw" | "l0" => Ok(AbstractionLevel::Raw),
1073            "summary" | "l1" => Ok(AbstractionLevel::Summary),
1074            "principle" | "l2" => Ok(AbstractionLevel::Principle),
1075            _ => Err(format!("Invalid AbstractionLevel: {}", s)),
1076        }
1077    }
1078}
1079
1080impl fmt::Display for EvolutionPhase {
1081    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1082        let value = match self {
1083            EvolutionPhase::Online => "Online",
1084            EvolutionPhase::Frozen => "Frozen",
1085            EvolutionPhase::Evolving => "Evolving",
1086        };
1087        write!(f, "{}", value)
1088    }
1089}
1090
1091impl FromStr for EvolutionPhase {
1092    type Err = String;
1093
1094    fn from_str(s: &str) -> Result<Self, Self::Err> {
1095        match normalize_token(s).as_str() {
1096            "online" => Ok(EvolutionPhase::Online),
1097            "frozen" | "freeze" => Ok(EvolutionPhase::Frozen),
1098            "evolving" | "evolve" => Ok(EvolutionPhase::Evolving),
1099            _ => Err(format!("Invalid EvolutionPhase: {}", s)),
1100        }
1101    }
1102}
1103
1104impl fmt::Display for SummarizationTrigger {
1105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1106        match self {
1107            SummarizationTrigger::DosageThreshold { percent } => {
1108                write!(f, "DosageThreshold({}%)", percent)
1109            }
1110            SummarizationTrigger::ScopeClose => write!(f, "ScopeClose"),
1111            SummarizationTrigger::TurnCount { count } => write!(f, "TurnCount({})", count),
1112            SummarizationTrigger::ArtifactCount { count } => write!(f, "ArtifactCount({})", count),
1113            SummarizationTrigger::Manual => write!(f, "Manual"),
1114        }
1115    }
1116}
1117
1118// ============================================================================
1119// TESTS
1120// ============================================================================
1121
1122#[cfg(test)]
1123mod tests {
1124    use super::*;
1125
1126    // ========================================================================
1127    // Serde Roundtrip Tests - TTL
1128    // ========================================================================
1129
1130    #[test]
1131    fn test_ttl_persistent_serde_roundtrip() {
1132        let original = TTL::Persistent;
1133        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1134        let restored: TTL =
1135            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1136        assert_eq!(original, restored);
1137    }
1138
1139    #[test]
1140    fn test_ttl_session_serde_roundtrip() {
1141        let original = TTL::Session;
1142        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1143        let restored: TTL =
1144            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1145        assert_eq!(original, restored);
1146    }
1147
1148    #[test]
1149    fn test_ttl_scope_serde_roundtrip() {
1150        let original = TTL::Scope;
1151        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1152        let restored: TTL =
1153            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1154        assert_eq!(original, restored);
1155    }
1156
1157    #[test]
1158    fn test_ttl_duration_serde_roundtrip() {
1159        let original = TTL::Duration(DurationMs::new(3600000));
1160        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1161        let restored: TTL =
1162            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1163        assert_eq!(original, restored);
1164    }
1165
1166    #[test]
1167    fn test_ttl_ephemeral_serde_roundtrip() {
1168        let original = TTL::Ephemeral;
1169        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1170        let restored: TTL =
1171            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1172        assert_eq!(original, restored);
1173    }
1174
1175    #[test]
1176    fn test_ttl_max_serde_roundtrip() {
1177        let original = TTL::Max(1000);
1178        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1179        let restored: TTL =
1180            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1181        assert_eq!(original, restored);
1182    }
1183
1184    #[test]
1185    fn test_ttl_short_term_serde_roundtrip() {
1186        let original = TTL::ShortTerm;
1187        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1188        let restored: TTL =
1189            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1190        assert_eq!(original, restored);
1191    }
1192
1193    #[test]
1194    fn test_ttl_medium_term_serde_roundtrip() {
1195        let original = TTL::MediumTerm;
1196        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1197        let restored: TTL =
1198            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1199        assert_eq!(original, restored);
1200    }
1201
1202    #[test]
1203    fn test_ttl_long_term_serde_roundtrip() {
1204        let original = TTL::LongTerm;
1205        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1206        let restored: TTL =
1207            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1208        assert_eq!(original, restored);
1209    }
1210
1211    #[test]
1212    fn test_ttl_permanent_serde_roundtrip() {
1213        let original = TTL::Permanent;
1214        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1215        let restored: TTL =
1216            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1217        assert_eq!(original, restored);
1218    }
1219
1220    // ========================================================================
1221    // Serde Roundtrip Tests - EntityType
1222    // ========================================================================
1223
1224    #[test]
1225    fn test_entity_type_all_variants_serde_roundtrip() {
1226        let variants = [
1227            EntityType::Trajectory,
1228            EntityType::Scope,
1229            EntityType::Artifact,
1230            EntityType::Note,
1231            EntityType::Turn,
1232            EntityType::Tenant,
1233            EntityType::AgentWorkingSet,
1234            EntityType::Link,
1235            EntityType::Belief,
1236            EntityType::Goal,
1237            EntityType::Plan,
1238            EntityType::Lock,
1239            EntityType::Message,
1240            EntityType::Agent,
1241            EntityType::Delegation,
1242            EntityType::Handoff,
1243            EntityType::Config,
1244            EntityType::Conflict,
1245            EntityType::Edge,
1246            EntityType::EvolutionSnapshot,
1247            EntityType::SummarizationPolicy,
1248            EntityType::SummarizationRequest,
1249            EntityType::ToolExecution,
1250            EntityType::Event,
1251        ];
1252
1253        for original in variants {
1254            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1255            let restored: EntityType =
1256                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1257            assert_eq!(original, restored, "Failed for {:?}", original);
1258        }
1259    }
1260
1261    // ========================================================================
1262    // Serde Roundtrip Tests - TrajectoryStatus
1263    // ========================================================================
1264
1265    #[test]
1266    fn test_trajectory_status_all_variants_serde_roundtrip() {
1267        let variants = [
1268            TrajectoryStatus::Active,
1269            TrajectoryStatus::Completed,
1270            TrajectoryStatus::Failed,
1271            TrajectoryStatus::Suspended,
1272        ];
1273
1274        for original in variants {
1275            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1276            let restored: TrajectoryStatus =
1277                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1278            assert_eq!(original, restored);
1279        }
1280    }
1281
1282    // ========================================================================
1283    // Serde Roundtrip Tests - AgentStatus
1284    // ========================================================================
1285
1286    #[test]
1287    fn test_agent_status_all_variants_serde_roundtrip() {
1288        let variants = [
1289            AgentStatus::Idle,
1290            AgentStatus::Active,
1291            AgentStatus::Blocked,
1292            AgentStatus::Failed,
1293            AgentStatus::Offline,
1294        ];
1295
1296        for original in variants {
1297            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1298            let restored: AgentStatus =
1299                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1300            assert_eq!(original, restored);
1301        }
1302    }
1303
1304    // ========================================================================
1305    // Serde Roundtrip Tests - ArtifactType
1306    // ========================================================================
1307
1308    #[test]
1309    fn test_artifact_type_all_variants_serde_roundtrip() {
1310        let variants = [
1311            ArtifactType::ErrorLog,
1312            ArtifactType::CodePatch,
1313            ArtifactType::DesignDecision,
1314            ArtifactType::UserPreference,
1315            ArtifactType::Fact,
1316            ArtifactType::Constraint,
1317            ArtifactType::ToolResult,
1318            ArtifactType::IntermediateOutput,
1319            ArtifactType::Custom,
1320            ArtifactType::Code,
1321            ArtifactType::Document,
1322            ArtifactType::Data,
1323            ArtifactType::Model,
1324            ArtifactType::Config,
1325            ArtifactType::Log,
1326            ArtifactType::Summary,
1327            ArtifactType::Decision,
1328            ArtifactType::Plan,
1329        ];
1330
1331        for original in variants {
1332            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1333            let restored: ArtifactType =
1334                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1335            assert_eq!(original, restored);
1336        }
1337    }
1338
1339    // ========================================================================
1340    // Serde Roundtrip Tests - ExtractionMethod
1341    // ========================================================================
1342
1343    #[test]
1344    fn test_extraction_method_all_variants_serde_roundtrip() {
1345        let variants = [
1346            ExtractionMethod::Explicit,
1347            ExtractionMethod::Inferred,
1348            ExtractionMethod::UserProvided,
1349            ExtractionMethod::LlmExtraction,
1350            ExtractionMethod::ToolExtraction,
1351            ExtractionMethod::MemoryRecall,
1352            ExtractionMethod::ExternalApi,
1353            ExtractionMethod::Unknown,
1354        ];
1355
1356        for original in variants {
1357            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1358            let restored: ExtractionMethod =
1359                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1360            assert_eq!(original, restored);
1361        }
1362    }
1363
1364    // ========================================================================
1365    // Serde Roundtrip Tests - NoteType
1366    // ========================================================================
1367
1368    #[test]
1369    fn test_note_type_all_variants_serde_roundtrip() {
1370        let variants = [
1371            NoteType::Convention,
1372            NoteType::Strategy,
1373            NoteType::Gotcha,
1374            NoteType::Fact,
1375            NoteType::Preference,
1376            NoteType::Relationship,
1377            NoteType::Procedure,
1378            NoteType::Meta,
1379            NoteType::Insight,
1380            NoteType::Correction,
1381            NoteType::Summary,
1382        ];
1383
1384        for original in variants {
1385            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1386            let restored: NoteType =
1387                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1388            assert_eq!(original, restored);
1389        }
1390    }
1391
1392    // ========================================================================
1393    // Serde Roundtrip Tests - EdgeType
1394    // ========================================================================
1395
1396    #[test]
1397    fn test_edge_type_all_variants_serde_roundtrip() {
1398        let variants = [
1399            EdgeType::Supports,
1400            EdgeType::Contradicts,
1401            EdgeType::Supersedes,
1402            EdgeType::DerivedFrom,
1403            EdgeType::RelatesTo,
1404            EdgeType::Temporal,
1405            EdgeType::Causal,
1406            EdgeType::SynthesizedFrom,
1407            EdgeType::Grouped,
1408            EdgeType::Compared,
1409        ];
1410
1411        for original in variants {
1412            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1413            let restored: EdgeType =
1414                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1415            assert_eq!(original, restored);
1416        }
1417    }
1418
1419    // ========================================================================
1420    // Serde Roundtrip Tests - AbstractionLevel
1421    // ========================================================================
1422
1423    #[test]
1424    fn test_abstraction_level_all_variants_serde_roundtrip() {
1425        let variants = [
1426            AbstractionLevel::Raw,
1427            AbstractionLevel::Summary,
1428            AbstractionLevel::Principle,
1429        ];
1430
1431        for original in variants {
1432            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1433            let restored: AbstractionLevel =
1434                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1435            assert_eq!(original, restored);
1436        }
1437    }
1438
1439    // ========================================================================
1440    // Serde Roundtrip Tests - EvolutionPhase
1441    // ========================================================================
1442
1443    #[test]
1444    fn test_evolution_phase_all_variants_serde_roundtrip() {
1445        let variants = [
1446            EvolutionPhase::Online,
1447            EvolutionPhase::Frozen,
1448            EvolutionPhase::Evolving,
1449        ];
1450
1451        for original in variants {
1452            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1453            let restored: EvolutionPhase =
1454                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1455            assert_eq!(original, restored);
1456        }
1457    }
1458
1459    // ========================================================================
1460    // Serde Roundtrip Tests - SummarizationTrigger
1461    // ========================================================================
1462
1463    #[test]
1464    fn test_summarization_trigger_all_variants_serde_roundtrip() {
1465        let variants = [
1466            SummarizationTrigger::DosageThreshold { percent: 80 },
1467            SummarizationTrigger::ScopeClose,
1468            SummarizationTrigger::TurnCount { count: 10 },
1469            SummarizationTrigger::ArtifactCount { count: 5 },
1470            SummarizationTrigger::Manual,
1471        ];
1472
1473        for original in variants {
1474            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1475            let restored: SummarizationTrigger =
1476                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1477            assert_eq!(original, restored);
1478        }
1479    }
1480
1481    // ========================================================================
1482    // Serde Roundtrip Tests - MemoryCategory
1483    // ========================================================================
1484
1485    #[test]
1486    fn test_memory_category_all_variants_serde_roundtrip() {
1487        let variants = [
1488            MemoryCategory::Ephemeral,
1489            MemoryCategory::Working,
1490            MemoryCategory::Episodic,
1491            MemoryCategory::Semantic,
1492            MemoryCategory::Procedural,
1493            MemoryCategory::Meta,
1494        ];
1495
1496        for original in variants {
1497            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1498            let restored: MemoryCategory =
1499                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1500            assert_eq!(original, restored);
1501        }
1502    }
1503
1504    // ========================================================================
1505    // Serde Roundtrip Tests - TurnRole
1506    // ========================================================================
1507
1508    #[test]
1509    fn test_turn_role_all_variants_serde_roundtrip() {
1510        let variants = [
1511            TurnRole::User,
1512            TurnRole::Assistant,
1513            TurnRole::System,
1514            TurnRole::Tool,
1515        ];
1516
1517        for original in variants {
1518            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1519            let restored: TurnRole =
1520                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1521            assert_eq!(original, restored);
1522        }
1523    }
1524
1525    // ========================================================================
1526    // Serde Roundtrip Tests - OutcomeStatus
1527    // ========================================================================
1528
1529    #[test]
1530    fn test_outcome_status_all_variants_serde_roundtrip() {
1531        let variants = [
1532            OutcomeStatus::Success,
1533            OutcomeStatus::Partial,
1534            OutcomeStatus::Failure,
1535        ];
1536
1537        for original in variants {
1538            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1539            let restored: OutcomeStatus =
1540                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1541            assert_eq!(original, restored);
1542        }
1543    }
1544
1545    // ========================================================================
1546    // Serde Roundtrip Tests - FieldType
1547    // ========================================================================
1548
1549    #[test]
1550    fn test_field_type_simple_variants_serde_roundtrip() {
1551        let variants = [
1552            FieldType::Uuid,
1553            FieldType::Text,
1554            FieldType::Int,
1555            FieldType::Float,
1556            FieldType::Bool,
1557            FieldType::Timestamp,
1558            FieldType::Json,
1559        ];
1560
1561        for original in variants {
1562            let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1563            let restored: FieldType =
1564                serde_json::from_str(&json).expect("JSON deserialization should succeed");
1565            assert_eq!(original, restored);
1566        }
1567    }
1568
1569    #[test]
1570    fn test_field_type_embedding_serde_roundtrip() {
1571        let original = FieldType::Embedding {
1572            dimensions: Some(1536),
1573        };
1574        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1575        let restored: FieldType =
1576            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1577        assert_eq!(original, restored);
1578    }
1579
1580    #[test]
1581    fn test_field_type_enum_serde_roundtrip() {
1582        let original = FieldType::Enum {
1583            variants: vec!["A".to_string(), "B".to_string(), "C".to_string()],
1584        };
1585        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1586        let restored: FieldType =
1587            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1588        assert_eq!(original, restored);
1589    }
1590
1591    #[test]
1592    fn test_field_type_array_serde_roundtrip() {
1593        let original = FieldType::Array(Box::new(FieldType::Text));
1594        let json = serde_json::to_string(&original).expect("JSON serialization should succeed");
1595        let restored: FieldType =
1596            serde_json::from_str(&json).expect("JSON deserialization should succeed");
1597        assert_eq!(original, restored);
1598    }
1599
1600    // ========================================================================
1601    // Display/FromStr Roundtrip Tests
1602    // ========================================================================
1603
1604    #[test]
1605    fn test_entity_type_display_fromstr_roundtrip() {
1606        let variants = [
1607            EntityType::Trajectory,
1608            EntityType::Scope,
1609            EntityType::Artifact,
1610            EntityType::Note,
1611            EntityType::Turn,
1612            EntityType::Tenant,
1613            EntityType::AgentWorkingSet,
1614            EntityType::Link,
1615            EntityType::Belief,
1616            EntityType::Goal,
1617            EntityType::Plan,
1618            EntityType::Lock,
1619            EntityType::Message,
1620            EntityType::Agent,
1621            EntityType::Delegation,
1622            EntityType::Handoff,
1623            EntityType::Config,
1624            EntityType::Conflict,
1625            EntityType::Edge,
1626            EntityType::EvolutionSnapshot,
1627            EntityType::SummarizationPolicy,
1628            EntityType::SummarizationRequest,
1629            EntityType::ToolExecution,
1630            EntityType::Event,
1631        ];
1632
1633        for original in variants {
1634            let string = original.to_string();
1635            let restored: EntityType = string.parse().expect("EntityType roundtrip should succeed");
1636            assert_eq!(original, restored);
1637        }
1638    }
1639
1640    #[test]
1641    fn test_trajectory_status_display_fromstr_roundtrip() {
1642        let variants = [
1643            TrajectoryStatus::Active,
1644            TrajectoryStatus::Completed,
1645            TrajectoryStatus::Failed,
1646            TrajectoryStatus::Suspended,
1647        ];
1648
1649        for original in variants {
1650            let string = original.to_string();
1651            let restored: TrajectoryStatus = string
1652                .parse()
1653                .expect("TrajectoryStatus roundtrip should succeed");
1654            assert_eq!(original, restored);
1655        }
1656    }
1657
1658    #[test]
1659    fn test_agent_status_display_fromstr_roundtrip() {
1660        let variants = [
1661            AgentStatus::Idle,
1662            AgentStatus::Active,
1663            AgentStatus::Blocked,
1664            AgentStatus::Failed,
1665            AgentStatus::Offline,
1666        ];
1667
1668        for original in variants {
1669            let string = original.to_string();
1670            let restored: AgentStatus = string
1671                .parse()
1672                .expect("AgentStatus roundtrip should succeed");
1673            assert_eq!(original, restored);
1674        }
1675    }
1676
1677    #[test]
1678    fn test_artifact_type_display_fromstr_roundtrip() {
1679        let variants = [
1680            ArtifactType::ErrorLog,
1681            ArtifactType::CodePatch,
1682            ArtifactType::DesignDecision,
1683            ArtifactType::UserPreference,
1684            ArtifactType::Fact,
1685            ArtifactType::Constraint,
1686            ArtifactType::ToolResult,
1687            ArtifactType::IntermediateOutput,
1688            ArtifactType::Custom,
1689            ArtifactType::Code,
1690            ArtifactType::Document,
1691            ArtifactType::Data,
1692            ArtifactType::Model,
1693            ArtifactType::Config,
1694            ArtifactType::Log,
1695            ArtifactType::Summary,
1696            ArtifactType::Decision,
1697            ArtifactType::Plan,
1698        ];
1699
1700        for original in variants {
1701            let string = original.to_string();
1702            let restored: ArtifactType = string
1703                .parse()
1704                .expect("ArtifactType roundtrip should succeed");
1705            assert_eq!(original, restored);
1706        }
1707    }
1708
1709    #[test]
1710    fn test_extraction_method_display_fromstr_roundtrip() {
1711        let variants = [
1712            ExtractionMethod::Explicit,
1713            ExtractionMethod::Inferred,
1714            ExtractionMethod::UserProvided,
1715            ExtractionMethod::LlmExtraction,
1716            ExtractionMethod::ToolExtraction,
1717            ExtractionMethod::MemoryRecall,
1718            ExtractionMethod::ExternalApi,
1719            ExtractionMethod::Unknown,
1720        ];
1721
1722        for original in variants {
1723            let string = original.to_string();
1724            let restored: ExtractionMethod = string
1725                .parse()
1726                .expect("ExtractionMethod roundtrip should succeed");
1727            assert_eq!(original, restored);
1728        }
1729    }
1730
1731    #[test]
1732    fn test_note_type_display_fromstr_roundtrip() {
1733        let variants = [
1734            NoteType::Convention,
1735            NoteType::Strategy,
1736            NoteType::Gotcha,
1737            NoteType::Fact,
1738            NoteType::Preference,
1739            NoteType::Relationship,
1740            NoteType::Procedure,
1741            NoteType::Meta,
1742            NoteType::Insight,
1743            NoteType::Correction,
1744            NoteType::Summary,
1745        ];
1746
1747        for original in variants {
1748            let string = original.to_string();
1749            let restored: NoteType = string.parse().expect("NoteType roundtrip should succeed");
1750            assert_eq!(original, restored);
1751        }
1752    }
1753
1754    #[test]
1755    fn test_edge_type_display_fromstr_roundtrip() {
1756        let variants = [
1757            EdgeType::Supports,
1758            EdgeType::Contradicts,
1759            EdgeType::Supersedes,
1760            EdgeType::DerivedFrom,
1761            EdgeType::RelatesTo,
1762            EdgeType::Temporal,
1763            EdgeType::Causal,
1764            EdgeType::SynthesizedFrom,
1765            EdgeType::Grouped,
1766            EdgeType::Compared,
1767        ];
1768
1769        for original in variants {
1770            let string = original.to_string();
1771            let restored: EdgeType = string.parse().expect("EdgeType roundtrip should succeed");
1772            assert_eq!(original, restored);
1773        }
1774    }
1775
1776    #[test]
1777    fn test_abstraction_level_display_fromstr_roundtrip() {
1778        let variants = [
1779            AbstractionLevel::Raw,
1780            AbstractionLevel::Summary,
1781            AbstractionLevel::Principle,
1782        ];
1783
1784        for original in variants {
1785            let string = original.to_string();
1786            let restored: AbstractionLevel = string
1787                .parse()
1788                .expect("AbstractionLevel roundtrip should succeed");
1789            assert_eq!(original, restored);
1790        }
1791    }
1792
1793    #[test]
1794    fn test_evolution_phase_display_fromstr_roundtrip() {
1795        let variants = [
1796            EvolutionPhase::Online,
1797            EvolutionPhase::Frozen,
1798            EvolutionPhase::Evolving,
1799        ];
1800
1801        for original in variants {
1802            let string = original.to_string();
1803            let restored: EvolutionPhase = string
1804                .parse()
1805                .expect("EvolutionPhase roundtrip should succeed");
1806            assert_eq!(original, restored);
1807        }
1808    }
1809
1810    #[test]
1811    fn test_turn_role_display_fromstr_roundtrip() {
1812        let variants = [
1813            TurnRole::User,
1814            TurnRole::Assistant,
1815            TurnRole::System,
1816            TurnRole::Tool,
1817        ];
1818
1819        for original in variants {
1820            let string = original.to_string();
1821            let restored: TurnRole = string.parse().expect("TurnRole roundtrip should succeed");
1822            assert_eq!(original, restored);
1823        }
1824    }
1825
1826    #[test]
1827    fn test_outcome_status_display_fromstr_roundtrip() {
1828        let variants = [
1829            OutcomeStatus::Success,
1830            OutcomeStatus::Partial,
1831            OutcomeStatus::Failure,
1832        ];
1833
1834        for original in variants {
1835            let string = original.to_string();
1836            let restored: OutcomeStatus = string
1837                .parse()
1838                .expect("OutcomeStatus roundtrip should succeed");
1839            assert_eq!(original, restored);
1840        }
1841    }
1842
1843    // ========================================================================
1844    // FromStr with Aliases Tests
1845    // ========================================================================
1846
1847    #[test]
1848    fn test_abstraction_level_aliases() {
1849        // "raw" and "l0" should both parse to Raw
1850        assert_eq!(
1851            "raw"
1852                .parse::<AbstractionLevel>()
1853                .expect("parsing 'raw' should succeed"),
1854            AbstractionLevel::Raw
1855        );
1856        assert_eq!(
1857            "l0".parse::<AbstractionLevel>()
1858                .expect("parsing 'l0' should succeed"),
1859            AbstractionLevel::Raw
1860        );
1861
1862        // "summary" and "l1" should both parse to Summary
1863        assert_eq!(
1864            "summary"
1865                .parse::<AbstractionLevel>()
1866                .expect("parsing 'summary' should succeed"),
1867            AbstractionLevel::Summary
1868        );
1869        assert_eq!(
1870            "l1".parse::<AbstractionLevel>()
1871                .expect("parsing 'l1' should succeed"),
1872            AbstractionLevel::Summary
1873        );
1874
1875        // "principle" and "l2" should both parse to Principle
1876        assert_eq!(
1877            "principle"
1878                .parse::<AbstractionLevel>()
1879                .expect("parsing 'principle' should succeed"),
1880            AbstractionLevel::Principle
1881        );
1882        assert_eq!(
1883            "l2".parse::<AbstractionLevel>()
1884                .expect("parsing 'l2' should succeed"),
1885            AbstractionLevel::Principle
1886        );
1887    }
1888
1889    #[test]
1890    fn test_trajectory_status_aliases() {
1891        assert_eq!(
1892            "completed"
1893                .parse::<TrajectoryStatus>()
1894                .expect("parsing 'completed' should succeed"),
1895            TrajectoryStatus::Completed
1896        );
1897        assert_eq!(
1898            "complete"
1899                .parse::<TrajectoryStatus>()
1900                .expect("parsing 'complete' should succeed"),
1901            TrajectoryStatus::Completed
1902        );
1903        assert_eq!(
1904            "failed"
1905                .parse::<TrajectoryStatus>()
1906                .expect("parsing 'failed' should succeed"),
1907            TrajectoryStatus::Failed
1908        );
1909        assert_eq!(
1910            "failure"
1911                .parse::<TrajectoryStatus>()
1912                .expect("parsing 'failure' should succeed"),
1913            TrajectoryStatus::Failed
1914        );
1915    }
1916
1917    #[test]
1918    fn test_evolution_phase_aliases() {
1919        assert_eq!(
1920            "frozen"
1921                .parse::<EvolutionPhase>()
1922                .expect("parsing 'frozen' should succeed"),
1923            EvolutionPhase::Frozen
1924        );
1925        assert_eq!(
1926            "freeze"
1927                .parse::<EvolutionPhase>()
1928                .expect("parsing 'freeze' should succeed"),
1929            EvolutionPhase::Frozen
1930        );
1931        assert_eq!(
1932            "evolving"
1933                .parse::<EvolutionPhase>()
1934                .expect("parsing 'evolving' should succeed"),
1935            EvolutionPhase::Evolving
1936        );
1937        assert_eq!(
1938            "evolve"
1939                .parse::<EvolutionPhase>()
1940                .expect("parsing 'evolve' should succeed"),
1941            EvolutionPhase::Evolving
1942        );
1943    }
1944
1945    // ========================================================================
1946    // Default Implementation Tests
1947    // ========================================================================
1948
1949    #[test]
1950    fn test_agent_status_default() {
1951        assert_eq!(AgentStatus::default(), AgentStatus::Idle);
1952    }
1953
1954    #[test]
1955    fn test_extraction_method_default() {
1956        assert_eq!(ExtractionMethod::default(), ExtractionMethod::Unknown);
1957    }
1958
1959    #[test]
1960    fn test_abstraction_level_default() {
1961        assert_eq!(AbstractionLevel::default(), AbstractionLevel::Raw);
1962    }
1963
1964    #[test]
1965    fn test_evolution_phase_default() {
1966        assert_eq!(EvolutionPhase::default(), EvolutionPhase::Online);
1967    }
1968
1969    // ========================================================================
1970    // AgentStatus Business Logic Tests
1971    // ========================================================================
1972
1973    #[test]
1974    fn test_agent_status_can_accept_work() {
1975        assert!(AgentStatus::Idle.can_accept_work());
1976        assert!(!AgentStatus::Active.can_accept_work());
1977        assert!(!AgentStatus::Blocked.can_accept_work());
1978        assert!(!AgentStatus::Failed.can_accept_work());
1979        assert!(!AgentStatus::Offline.can_accept_work());
1980    }
1981
1982    #[test]
1983    fn test_agent_status_as_db_str() {
1984        assert_eq!(AgentStatus::Idle.as_db_str(), "idle");
1985        assert_eq!(AgentStatus::Active.as_db_str(), "active");
1986        assert_eq!(AgentStatus::Blocked.as_db_str(), "blocked");
1987        assert_eq!(AgentStatus::Failed.as_db_str(), "failed");
1988        assert_eq!(AgentStatus::Offline.as_db_str(), "offline");
1989    }
1990
1991    #[test]
1992    fn test_agent_status_from_db_str() {
1993        assert_eq!(
1994            AgentStatus::from_db_str("idle").expect("parsing 'idle' should succeed"),
1995            AgentStatus::Idle
1996        );
1997        assert_eq!(
1998            AgentStatus::from_db_str("ACTIVE").expect("parsing 'ACTIVE' should succeed"),
1999            AgentStatus::Active
2000        );
2001        assert_eq!(
2002            AgentStatus::from_db_str("Blocked").expect("parsing 'Blocked' should succeed"),
2003            AgentStatus::Blocked
2004        );
2005        assert!(AgentStatus::from_db_str("invalid").is_err());
2006    }
2007
2008    // ========================================================================
2009    // Invalid Parse Tests
2010    // ========================================================================
2011
2012    #[test]
2013    fn test_entity_type_invalid_parse() {
2014        assert!("invalid".parse::<EntityType>().is_err());
2015        assert!("".parse::<EntityType>().is_err());
2016    }
2017
2018    #[test]
2019    fn test_trajectory_status_invalid_parse() {
2020        assert!("invalid".parse::<TrajectoryStatus>().is_err());
2021    }
2022
2023    #[test]
2024    fn test_artifact_type_invalid_parse() {
2025        assert!("invalid".parse::<ArtifactType>().is_err());
2026    }
2027
2028    #[test]
2029    fn test_note_type_invalid_parse() {
2030        assert!("invalid".parse::<NoteType>().is_err());
2031    }
2032
2033    #[test]
2034    fn test_edge_type_invalid_parse() {
2035        assert!("invalid".parse::<EdgeType>().is_err());
2036    }
2037
2038    // ========================================================================
2039    // Case Insensitivity Tests
2040    // ========================================================================
2041
2042    #[test]
2043    fn test_parse_case_insensitive() {
2044        assert_eq!(
2045            "ACTIVE"
2046                .parse::<TrajectoryStatus>()
2047                .expect("parsing 'ACTIVE' should succeed"),
2048            TrajectoryStatus::Active
2049        );
2050        assert_eq!(
2051            "active"
2052                .parse::<TrajectoryStatus>()
2053                .expect("parsing 'active' should succeed"),
2054            TrajectoryStatus::Active
2055        );
2056        assert_eq!(
2057            "Active"
2058                .parse::<TrajectoryStatus>()
2059                .expect("parsing 'Active' should succeed"),
2060            TrajectoryStatus::Active
2061        );
2062        assert_eq!(
2063            "TRAJECTORY"
2064                .parse::<EntityType>()
2065                .expect("parsing 'TRAJECTORY' should succeed"),
2066            EntityType::Trajectory
2067        );
2068        assert_eq!(
2069            "trajectory"
2070                .parse::<EntityType>()
2071                .expect("parsing 'trajectory' should succeed"),
2072            EntityType::Trajectory
2073        );
2074    }
2075
2076    // ========================================================================
2077    // Error Type Tests
2078    // ========================================================================
2079
2080    #[test]
2081    fn test_enum_parse_error_display() {
2082        let err = EnumParseError {
2083            enum_name: "AgentStatus",
2084            input: "invalid_status".to_string(),
2085        };
2086        let msg = format!("{}", err);
2087        assert!(msg.contains("invalid_status"));
2088        assert!(msg.contains("AgentStatus"));
2089    }
2090
2091    // -----------------------------------------------------------------------
2092    // Moved from integration test: battle_intel_e2e.rs
2093    // -----------------------------------------------------------------------
2094
2095    #[test]
2096    fn battle_intel_edge_type_variants() {
2097        let all_edge_types = vec![
2098            EdgeType::Supports,
2099            EdgeType::Contradicts,
2100            EdgeType::Supersedes,
2101            EdgeType::DerivedFrom,
2102            EdgeType::RelatesTo,
2103            EdgeType::Temporal,
2104            EdgeType::Causal,
2105            EdgeType::SynthesizedFrom,
2106            EdgeType::Grouped,
2107            EdgeType::Compared,
2108        ];
2109
2110        for edge_type in &all_edge_types {
2111            let serialized =
2112                serde_json::to_string(edge_type).expect("EdgeType should serialize to JSON");
2113            let deserialized: EdgeType =
2114                serde_json::from_str(&serialized).expect("EdgeType should deserialize from JSON");
2115            assert_eq!(
2116                *edge_type, deserialized,
2117                "EdgeType {:?} round-trip failed. Serialized: {}",
2118                edge_type, serialized
2119            );
2120        }
2121
2122        for edge_type in &all_edge_types {
2123            match edge_type {
2124                EdgeType::Supports
2125                | EdgeType::Contradicts
2126                | EdgeType::Supersedes
2127                | EdgeType::DerivedFrom
2128                | EdgeType::RelatesTo
2129                | EdgeType::Temporal
2130                | EdgeType::Causal
2131                | EdgeType::SynthesizedFrom
2132                | EdgeType::Grouped
2133                | EdgeType::Compared => {}
2134            }
2135        }
2136
2137        let synth = EdgeType::SynthesizedFrom;
2138        let json = serde_json::to_value(synth).expect("should serialize");
2139        assert_eq!(json, serde_json::json!("synthesized_from"));
2140    }
2141
2142    #[test]
2143    fn battle_intel_summarization_request_status_transitions() {
2144        let statuses = vec![
2145            SummarizationRequestStatus::Pending,
2146            SummarizationRequestStatus::InProgress,
2147            SummarizationRequestStatus::Completed,
2148            SummarizationRequestStatus::Failed,
2149        ];
2150
2151        for status in &statuses {
2152            let json = serde_json::to_string(status).expect("status should serialize");
2153            let back: SummarizationRequestStatus =
2154                serde_json::from_str(&json).expect("status should deserialize");
2155            assert_eq!(*status, back, "Status round-trip failed for {:?}", status);
2156        }
2157
2158        assert_eq!(SummarizationRequestStatus::Pending.as_db_str(), "pending");
2159        assert_eq!(
2160            SummarizationRequestStatus::InProgress.as_db_str(),
2161            "in_progress"
2162        );
2163        assert_eq!(
2164            SummarizationRequestStatus::Completed.as_db_str(),
2165            "completed"
2166        );
2167        assert_eq!(SummarizationRequestStatus::Failed.as_db_str(), "failed");
2168    }
2169}