1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct CellstateAst {
13 pub version: String,
14 pub definitions: Vec<Definition>,
15}
16
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19pub enum Definition {
20 Adapter(AdapterDef),
21 Memory(MemoryDef),
22 Policy(PolicyDef),
23 Injection(InjectionDef),
24 Evolution(EvolutionDef),
26 SummarizationPolicy(SummarizationPolicyDef),
28 Trajectory(TrajectoryDef),
30 Agent(AgentDef),
31 Cache(CacheDef),
32 Provider(ProviderDef),
33 Intent(IntentDef),
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub struct AdapterDef {
40 pub name: String,
41 pub adapter_type: AdapterType,
42 pub connection: String,
43 pub options: Vec<(String, String)>,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48pub enum AdapterType {
49 Postgres,
50 Redis,
51 Memory,
52}
53
54impl fmt::Display for AdapterType {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 AdapterType::Postgres => f.write_str("postgres"),
58 AdapterType::Redis => f.write_str("redis"),
59 AdapterType::Memory => f.write_str("memory"),
60 }
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub struct MemoryDef {
67 pub name: String,
68 pub memory_type: MemoryType,
69 pub schema: Vec<FieldDef>,
70 pub retention: Retention,
71 pub lifecycle: Lifecycle,
72 pub parent: Option<String>,
73 pub indexes: Vec<IndexDef>,
74 pub inject_on: Vec<Trigger>,
75 pub artifacts: Vec<String>,
76 pub modifiers: Vec<ModifierDef>,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82pub enum MemoryType {
83 Ephemeral,
84 Working,
85 Episodic,
86 Semantic,
87 Procedural,
88 Meta,
89}
90
91impl fmt::Display for MemoryType {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 match self {
94 MemoryType::Ephemeral => f.write_str("ephemeral"),
95 MemoryType::Working => f.write_str("working"),
96 MemoryType::Episodic => f.write_str("episodic"),
97 MemoryType::Semantic => f.write_str("semantic"),
98 MemoryType::Procedural => f.write_str("procedural"),
99 MemoryType::Meta => f.write_str("meta"),
100 }
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106pub struct FieldDef {
107 pub name: String,
108 pub field_type: FieldType,
109 pub nullable: bool,
110 pub default: Option<String>,
111 pub security: Option<FieldSecurity>,
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
117pub enum FieldType {
118 Uuid,
119 Text,
120 Int,
121 Float,
122 Bool,
123 Timestamp,
124 Json,
125 Embedding(Option<usize>),
126 Enum(Vec<String>),
127 Array(Box<FieldType>),
128}
129
130impl fmt::Display for FieldType {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match self {
133 FieldType::Uuid => f.write_str("uuid"),
134 FieldType::Text => f.write_str("text"),
135 FieldType::Int => f.write_str("int"),
136 FieldType::Float => f.write_str("float"),
137 FieldType::Bool => f.write_str("bool"),
138 FieldType::Timestamp => f.write_str("timestamp"),
139 FieldType::Json => f.write_str("json"),
140 FieldType::Embedding(Some(d)) => write!(f, "embedding({})", d),
141 FieldType::Embedding(None) => f.write_str("embedding"),
142 FieldType::Enum(variants) => write!(f, "enum({})", variants.join(", ")),
143 FieldType::Array(inner) => write!(f, "array({})", inner),
144 }
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
150pub enum Retention {
151 Persistent,
152 Session,
153 Scope,
154 Duration(String),
155 Max(usize),
156}
157
158impl fmt::Display for Retention {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 match self {
161 Retention::Persistent => f.write_str("persistent"),
162 Retention::Session => f.write_str("session"),
163 Retention::Scope => f.write_str("scope"),
164 Retention::Duration(d) => write!(f, "duration({})", d),
165 Retention::Max(n) => write!(f, "max({})", n),
166 }
167 }
168}
169
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
172pub enum Lifecycle {
173 Explicit,
174 AutoClose(Trigger),
175}
176
177impl fmt::Display for Lifecycle {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 match self {
180 Lifecycle::Explicit => f.write_str("explicit"),
181 Lifecycle::AutoClose(trigger) => write!(f, "auto_close({})", trigger),
182 }
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
188pub enum Trigger {
189 TaskStart,
190 TaskEnd,
191 ScopeClose,
192 TurnEnd,
193 Manual,
194 Schedule(String),
195}
196
197impl fmt::Display for Trigger {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 match self {
200 Trigger::TaskStart => f.write_str("task_start"),
201 Trigger::TaskEnd => f.write_str("task_end"),
202 Trigger::ScopeClose => f.write_str("scope_close"),
203 Trigger::TurnEnd => f.write_str("turn_end"),
204 Trigger::Manual => f.write_str("manual"),
205 Trigger::Schedule(s) => write!(f, "schedule:{}", s),
206 }
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
212pub struct IndexDef {
213 pub field: String,
214 pub index_type: IndexType,
215 pub options: Vec<(String, String)>,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
220pub enum IndexType {
221 Btree,
222 Hash,
223 Gin,
224 Hnsw,
225 Ivfflat,
226}
227
228impl fmt::Display for IndexType {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 match self {
231 IndexType::Btree => f.write_str("btree"),
232 IndexType::Hash => f.write_str("hash"),
233 IndexType::Gin => f.write_str("gin"),
234 IndexType::Hnsw => f.write_str("hnsw"),
235 IndexType::Ivfflat => f.write_str("ivfflat"),
236 }
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
242pub struct PolicyDef {
243 pub name: String,
244 pub rules: Vec<PolicyRule>,
245}
246
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
249pub struct PolicyRule {
250 pub trigger: Trigger,
251 pub actions: Vec<Action>,
252}
253
254#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
256pub enum Action {
257 Summarize(String),
258 ExtractArtifacts(String),
259 Checkpoint(String),
260 Prune {
261 target: String,
262 criteria: FilterExpr,
263 },
264 Notify(String),
265 Inject {
266 target: String,
267 mode: InjectionMode,
268 },
269 AutoSummarize {
271 source_level: AbstractionLevelParsed,
272 target_level: AbstractionLevelParsed,
273 create_edges: bool,
274 },
275}
276
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279pub struct InjectionDef {
280 pub source: String,
281 pub target: String,
282 pub mode: InjectionMode,
283 pub priority: i32,
284 pub max_tokens: Option<i32>,
285 pub filter: Option<FilterExpr>,
286}
287
288#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
290pub enum InjectionMode {
291 Full,
292 Summary,
293 TopK(usize),
294 Relevant(f32),
295}
296
297impl fmt::Display for InjectionMode {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 match self {
300 InjectionMode::Full => f.write_str("full"),
301 InjectionMode::Summary => f.write_str("summary"),
302 InjectionMode::TopK(k) => write!(f, "topk:{}", k),
303 InjectionMode::Relevant(threshold) => write!(f, "relevant:{}", threshold),
304 }
305 }
306}
307
308#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
310pub enum FilterExpr {
311 Comparison {
312 field: String,
313 op: CompareOp,
314 value: FilterValue,
315 },
316 And(Vec<FilterExpr>),
317 Or(Vec<FilterExpr>),
318 Not(Box<FilterExpr>),
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
323pub enum CompareOp {
324 Eq,
325 Ne,
326 Gt,
327 Lt,
328 Ge,
329 Le,
330 Contains,
331 Regex,
332 In,
333}
334
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
337pub enum FilterValue {
338 String(String),
339 Number(f64),
340 Bool(bool),
341 Null,
342 CurrentTrajectory,
343 CurrentScope,
344 Now,
345 Array(Vec<FilterValue>),
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
354pub enum AbstractionLevelParsed {
355 Raw, Summary, Principle, }
359
360#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
376pub struct EvolutionDef {
377 pub name: String,
378 pub baseline: String,
380 pub candidates: Vec<String>,
382 pub benchmark_queries: i32,
384 pub metrics: Vec<String>,
386}
387
388#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
394pub enum SummarizationTriggerParsed {
395 DosageThreshold { percent: u8 },
397 ScopeClose,
399 TurnCount { count: i32 },
401 ArtifactCount { count: i32 },
403 Manual,
405}
406
407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
420pub struct SummarizationPolicyDef {
421 pub name: String,
422 pub triggers: Vec<SummarizationTriggerParsed>,
423 pub source_level: AbstractionLevelParsed,
424 pub target_level: AbstractionLevelParsed,
425 pub max_sources: i32,
426 pub create_edges: bool,
427}
428
429#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
445pub struct TrajectoryDef {
446 pub name: String,
447 pub description: Option<String>,
448 pub agent_type: String,
449 pub token_budget: i32,
450 pub memory_refs: Vec<String>,
451 pub metadata: Option<serde_json::Value>,
452}
453
454#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
476pub struct AgentDef {
477 pub name: String,
478 pub capabilities: Vec<String>,
479 pub constraints: AgentConstraints,
480 pub permissions: PermissionMatrix,
481}
482
483#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
485pub struct AgentConstraints {
486 pub max_concurrent: i32,
487 pub timeout_ms: i64,
488}
489
490impl Default for AgentConstraints {
491 fn default() -> Self {
492 Self {
493 max_concurrent: 1,
494 timeout_ms: 30000,
495 }
496 }
497}
498
499#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
501pub struct PermissionMatrix {
502 pub read: Vec<String>,
503 pub write: Vec<String>,
504 pub lock: Vec<String>,
505}
506
507#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
523pub struct CacheDef {
524 pub backend: CacheBackendType,
525 pub path: Option<String>,
526 pub size_mb: i32,
527 pub default_freshness: FreshnessDef,
528 pub max_entries: Option<i32>,
529 pub ttl: Option<String>,
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
534pub enum CacheBackendType {
535 Lmdb,
536 Memory,
537}
538
539impl fmt::Display for CacheBackendType {
540 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541 match self {
542 CacheBackendType::Lmdb => f.write_str("lmdb"),
543 CacheBackendType::Memory => f.write_str("memory"),
544 }
545 }
546}
547
548#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
550pub enum FreshnessDef {
551 BestEffort { max_staleness: String },
553 Strict,
555}
556
557impl fmt::Display for FreshnessDef {
558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
559 match self {
560 FreshnessDef::BestEffort { max_staleness } => {
561 write!(f, "best_effort(max_staleness: {})", max_staleness)
562 }
563 FreshnessDef::Strict => f.write_str("strict"),
564 }
565 }
566}
567
568impl Default for FreshnessDef {
569 fn default() -> Self {
570 FreshnessDef::BestEffort {
571 max_staleness: "60s".to_string(),
572 }
573 }
574}
575
576#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
591pub struct ProviderDef {
592 pub name: String,
593 pub provider_type: ProviderType,
594 pub api_key: EnvValue,
595 pub model: String,
596 pub options: Vec<(String, String)>,
597}
598
599#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
601pub enum ProviderType {
602 OpenAI,
603 Anthropic,
604 Local,
605 Custom,
606}
607
608impl fmt::Display for ProviderType {
609 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
610 match self {
611 ProviderType::OpenAI => f.write_str("openai"),
612 ProviderType::Anthropic => f.write_str("anthropic"),
613 ProviderType::Local => f.write_str("local"),
614 ProviderType::Custom => f.write_str("custom"),
615 }
616 }
617}
618
619#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
621pub enum EnvValue {
622 Env(String),
624 Literal(String),
626}
627
628pub use cellstate_core::intent::{
633 AlignmentSignal, AutonomyLevel, DelegationBoundary, DriftReport, IntentDef, ResolutionRule,
634 SignalScore, SignalTarget,
635};
636
637#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
653pub enum ModifierDef {
654 Embeddable { provider: String },
656 Summarizable {
658 style: SummaryStyle,
659 on_triggers: Vec<Trigger>,
660 },
661 Lockable { mode: LockMode },
663}
664
665impl fmt::Display for ModifierDef {
666 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
667 match self {
668 ModifierDef::Embeddable { provider } => {
669 write!(f, "embeddable(provider: {})", provider)
670 }
671 ModifierDef::Summarizable { style, on_triggers } => {
672 let triggers_str = on_triggers
673 .iter()
674 .map(|t| t.to_string())
675 .collect::<Vec<_>>()
676 .join(", ");
677 write!(f, "summarizable(style: {}, on: [{}])", style, triggers_str)
678 }
679 ModifierDef::Lockable { mode } => write!(f, "lockable(mode: {})", mode),
680 }
681 }
682}
683
684#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
686pub enum SummaryStyle {
687 Brief,
688 Detailed,
689}
690
691impl fmt::Display for SummaryStyle {
692 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
693 match self {
694 SummaryStyle::Brief => f.write_str("brief"),
695 SummaryStyle::Detailed => f.write_str("detailed"),
696 }
697 }
698}
699
700#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
702pub enum LockMode {
703 Exclusive,
704 Shared,
705}
706
707impl fmt::Display for LockMode {
708 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
709 match self {
710 LockMode::Exclusive => f.write_str("exclusive"),
711 LockMode::Shared => f.write_str("shared"),
712 }
713 }
714}
715
716#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
722pub enum PIIClassification {
723 #[default]
725 Public,
726 Internal,
728 Confidential,
730 Restricted,
732 Secret,
734}
735
736impl PIIClassification {
737 pub fn requires_redaction(&self) -> bool {
739 matches!(self, Self::Confidential | Self::Restricted | Self::Secret)
740 }
741
742 pub fn requires_encryption(&self) -> bool {
744 matches!(self, Self::Secret)
745 }
746}
747
748#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
752pub struct FieldSecurity {
753 pub classification: PIIClassification,
755 pub opaque: bool,
757 pub immutable: bool,
759 pub audited: bool,
761 pub redact_in_logs: bool,
763 pub env_source: Option<String>,
765}
766
767impl Default for FieldSecurity {
768 fn default() -> Self {
769 Self {
770 classification: PIIClassification::Public,
771 opaque: false,
772 immutable: false,
773 audited: false,
774 redact_in_logs: false,
775 env_source: None,
776 }
777 }
778}
779
780impl FieldSecurity {
781 pub fn public() -> Self {
783 Self::default()
784 }
785
786 pub fn secret() -> Self {
788 Self {
789 classification: PIIClassification::Secret,
790 opaque: true,
791 immutable: false,
792 audited: true,
793 redact_in_logs: true,
794 env_source: None,
795 }
796 }
797
798 pub fn sensitive() -> Self {
800 Self {
801 classification: PIIClassification::Confidential,
802 opaque: false,
803 immutable: false,
804 audited: false,
805 redact_in_logs: true,
806 env_source: None,
807 }
808 }
809
810 pub fn with_opaque(mut self) -> Self {
812 self.opaque = true;
813 self
814 }
815
816 pub fn with_immutable(mut self) -> Self {
818 self.immutable = true;
819 self
820 }
821
822 pub fn with_audited(mut self) -> Self {
824 self.audited = true;
825 self
826 }
827
828 pub fn with_env(mut self, var_name: impl Into<String>) -> Self {
830 self.env_source = Some(var_name.into());
831 self
832 }
833}
834
835#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
839pub struct SecureFieldDef {
840 pub name: String,
842 pub field_type: FieldType,
844 pub security: Option<FieldSecurity>,
846 pub default: Option<String>,
848}
849
850#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
856pub struct ParseError {
857 pub message: String,
858 pub line: usize,
859 pub column: usize,
860}
861
862impl std::fmt::Display for ParseError {
863 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
864 write!(
865 f,
866 "Parse error at line {}, column {}: {}",
867 self.line, self.column, self.message
868 )
869 }
870}
871
872impl std::error::Error for ParseError {}
873
874#[derive(Debug, Clone, Default)]
876pub struct ErrorCollector {
877 errors: Vec<ParseError>,
878}
879
880impl ErrorCollector {
881 pub fn new() -> Self {
882 Self { errors: vec![] }
883 }
884
885 pub fn add(&mut self, error: ParseError) {
886 self.errors.push(error);
887 }
888
889 pub fn has_errors(&self) -> bool {
890 !self.errors.is_empty()
891 }
892
893 pub fn into_errors(self) -> Vec<ParseError> {
894 self.errors
895 }
896
897 pub fn into_single_error(self) -> Option<ParseError> {
918 if self.errors.is_empty() {
919 return None;
920 }
921
922 if self.errors.len() == 1 {
923 return Some(
924 self.errors
925 .into_iter()
926 .next()
927 .expect("length verified as 1"),
928 );
929 }
930
931 let first = &self.errors[0];
933 let mut message = format!(
934 "{} (and {} more errors):\n",
935 first.message,
936 self.errors.len() - 1
937 );
938
939 for (i, err) in self.errors.iter().enumerate() {
940 message.push_str(&format!(
941 " {}. Line {}, col {}: {}\n",
942 i + 1,
943 err.line,
944 err.column,
945 err.message
946 ));
947 }
948
949 Some(ParseError {
950 message,
951 line: first.line,
952 column: first.column,
953 })
954 }
955}
956
957#[cfg(test)]
958mod tests {
959 use super::*;
960
961 #[test]
964 fn pii_public_no_redaction_no_encryption() {
965 assert!(!PIIClassification::Public.requires_redaction());
966 assert!(!PIIClassification::Public.requires_encryption());
967 }
968
969 #[test]
970 fn pii_internal_no_redaction_no_encryption() {
971 assert!(!PIIClassification::Internal.requires_redaction());
972 assert!(!PIIClassification::Internal.requires_encryption());
973 }
974
975 #[test]
976 fn pii_confidential_requires_redaction_not_encryption() {
977 assert!(PIIClassification::Confidential.requires_redaction());
978 assert!(!PIIClassification::Confidential.requires_encryption());
979 }
980
981 #[test]
982 fn pii_restricted_requires_redaction_not_encryption() {
983 assert!(PIIClassification::Restricted.requires_redaction());
984 assert!(!PIIClassification::Restricted.requires_encryption());
985 }
986
987 #[test]
988 fn pii_secret_requires_both() {
989 assert!(PIIClassification::Secret.requires_redaction());
990 assert!(PIIClassification::Secret.requires_encryption());
991 }
992
993 #[test]
994 fn pii_default_is_public() {
995 assert_eq!(PIIClassification::default(), PIIClassification::Public);
996 }
997
998 #[test]
1001 fn field_security_default_is_public() {
1002 let fs = FieldSecurity::default();
1003 assert_eq!(fs.classification, PIIClassification::Public);
1004 assert!(!fs.opaque);
1005 assert!(!fs.immutable);
1006 assert!(!fs.audited);
1007 assert!(!fs.redact_in_logs);
1008 assert!(fs.env_source.is_none());
1009 }
1010
1011 #[test]
1012 fn field_security_public_equals_default() {
1013 assert_eq!(FieldSecurity::public(), FieldSecurity::default());
1014 }
1015
1016 #[test]
1017 fn field_security_secret_has_full_protection() {
1018 let fs = FieldSecurity::secret();
1019 assert_eq!(fs.classification, PIIClassification::Secret);
1020 assert!(fs.opaque);
1021 assert!(fs.audited);
1022 assert!(fs.redact_in_logs);
1023 }
1024
1025 #[test]
1026 fn field_security_sensitive_redacts_logs() {
1027 let fs = FieldSecurity::sensitive();
1028 assert_eq!(fs.classification, PIIClassification::Confidential);
1029 assert!(fs.redact_in_logs);
1030 assert!(!fs.opaque);
1031 }
1032
1033 #[test]
1034 fn field_security_builder_chain() {
1035 let fs = FieldSecurity::public()
1036 .with_opaque()
1037 .with_immutable()
1038 .with_audited()
1039 .with_env("MY_SECRET");
1040 assert!(fs.opaque);
1041 assert!(fs.immutable);
1042 assert!(fs.audited);
1043 assert_eq!(fs.env_source, Some("MY_SECRET".to_string()));
1044 }
1045
1046 #[test]
1049 fn field_security_serde_roundtrip() {
1050 let fs = FieldSecurity::secret().with_env("API_KEY");
1051 let json = serde_json::to_string(&fs).unwrap();
1052 let d: FieldSecurity = serde_json::from_str(&json).unwrap();
1053 assert_eq!(d, fs);
1054 }
1055
1056 #[test]
1059 fn parse_error_display() {
1060 let err = ParseError {
1061 message: "unexpected token".into(),
1062 line: 10,
1063 column: 5,
1064 };
1065 let s = format!("{}", err);
1066 assert!(s.contains("line 10"));
1067 assert!(s.contains("column 5"));
1068 assert!(s.contains("unexpected token"));
1069 }
1070
1071 #[test]
1072 fn parse_error_is_std_error() {
1073 let err = ParseError {
1074 message: "test".into(),
1075 line: 1,
1076 column: 1,
1077 };
1078 let _: &dyn std::error::Error = &err;
1079 }
1080
1081 #[test]
1084 fn error_collector_empty() {
1085 let c = ErrorCollector::new();
1086 assert!(!c.has_errors());
1087 assert!(c.into_single_error().is_none());
1088 }
1089
1090 #[test]
1091 fn error_collector_single_error() {
1092 let mut c = ErrorCollector::new();
1093 c.add(ParseError {
1094 message: "oops".into(),
1095 line: 1,
1096 column: 2,
1097 });
1098 assert!(c.has_errors());
1099 let single = c.into_single_error().unwrap();
1100 assert_eq!(single.message, "oops");
1101 assert_eq!(single.line, 1);
1102 assert_eq!(single.column, 2);
1103 }
1104
1105 #[test]
1106 fn error_collector_multiple_errors_combined() {
1107 let mut c = ErrorCollector::new();
1108 c.add(ParseError {
1109 message: "first".into(),
1110 line: 1,
1111 column: 1,
1112 });
1113 c.add(ParseError {
1114 message: "second".into(),
1115 line: 5,
1116 column: 3,
1117 });
1118 let combined = c.into_single_error().unwrap();
1119 assert_eq!(combined.line, 1); assert_eq!(combined.column, 1);
1121 assert!(combined.message.contains("first"));
1122 assert!(combined.message.contains("second"));
1123 assert!(combined.message.contains("1 more errors"));
1124 }
1125
1126 #[test]
1127 fn error_collector_into_errors() {
1128 let mut c = ErrorCollector::new();
1129 c.add(ParseError {
1130 message: "a".into(),
1131 line: 1,
1132 column: 1,
1133 });
1134 c.add(ParseError {
1135 message: "b".into(),
1136 line: 2,
1137 column: 2,
1138 });
1139 let errors = c.into_errors();
1140 assert_eq!(errors.len(), 2);
1141 }
1142
1143 #[test]
1144 fn error_collector_default_is_empty() {
1145 let c = ErrorCollector::default();
1146 assert!(!c.has_errors());
1147 }
1148
1149 #[test]
1152 fn agent_constraints_default() {
1153 let ac = AgentConstraints::default();
1154 assert_eq!(ac.max_concurrent, 1);
1155 assert_eq!(ac.timeout_ms, 30000);
1156 }
1157
1158 #[test]
1161 fn freshness_def_default_is_best_effort() {
1162 let f = FreshnessDef::default();
1163 assert!(matches!(f, FreshnessDef::BestEffort { .. }));
1164 }
1165
1166 #[test]
1169 fn adapter_type_serde_roundtrip() {
1170 for variant in [
1171 AdapterType::Postgres,
1172 AdapterType::Redis,
1173 AdapterType::Memory,
1174 ] {
1175 let json = serde_json::to_string(&variant).unwrap();
1176 let d: AdapterType = serde_json::from_str(&json).unwrap();
1177 assert_eq!(d, variant);
1178 }
1179 }
1180
1181 #[test]
1182 fn memory_type_serde_roundtrip() {
1183 for variant in [
1184 MemoryType::Ephemeral,
1185 MemoryType::Working,
1186 MemoryType::Episodic,
1187 MemoryType::Semantic,
1188 MemoryType::Procedural,
1189 MemoryType::Meta,
1190 ] {
1191 let json = serde_json::to_string(&variant).unwrap();
1192 let d: MemoryType = serde_json::from_str(&json).unwrap();
1193 assert_eq!(d, variant);
1194 }
1195 }
1196
1197 #[test]
1198 fn field_type_serde_roundtrip() {
1199 for variant in [
1200 FieldType::Uuid,
1201 FieldType::Text,
1202 FieldType::Int,
1203 FieldType::Float,
1204 FieldType::Bool,
1205 FieldType::Timestamp,
1206 FieldType::Json,
1207 FieldType::Embedding(Some(1536)),
1208 FieldType::Embedding(None),
1209 ] {
1210 let json = serde_json::to_string(&variant).unwrap();
1211 let d: FieldType = serde_json::from_str(&json).unwrap();
1212 assert_eq!(d, variant);
1213 }
1214 }
1215
1216 #[test]
1217 fn retention_serde_roundtrip() {
1218 for variant in [
1219 Retention::Persistent,
1220 Retention::Session,
1221 Retention::Scope,
1222 Retention::Duration("3600s".into()),
1223 Retention::Max(100),
1224 ] {
1225 let json = serde_json::to_string(&variant).unwrap();
1226 let d: Retention = serde_json::from_str(&json).unwrap();
1227 assert_eq!(d, variant);
1228 }
1229 }
1230
1231 #[test]
1232 fn lifecycle_serde_roundtrip() {
1233 for variant in [
1234 Lifecycle::Explicit,
1235 Lifecycle::AutoClose(Trigger::ScopeClose),
1236 ] {
1237 let json = serde_json::to_string(&variant).unwrap();
1238 let d: Lifecycle = serde_json::from_str(&json).unwrap();
1239 assert_eq!(d, variant);
1240 }
1241 }
1242
1243 #[test]
1244 fn trigger_serde_roundtrip() {
1245 for variant in [
1246 Trigger::TaskStart,
1247 Trigger::TaskEnd,
1248 Trigger::ScopeClose,
1249 Trigger::TurnEnd,
1250 Trigger::Manual,
1251 Trigger::Schedule("0 * * * *".into()),
1252 ] {
1253 let json = serde_json::to_string(&variant).unwrap();
1254 let d: Trigger = serde_json::from_str(&json).unwrap();
1255 assert_eq!(d, variant);
1256 }
1257 }
1258
1259 #[test]
1260 fn index_type_serde_roundtrip() {
1261 for variant in [
1262 IndexType::Btree,
1263 IndexType::Hash,
1264 IndexType::Gin,
1265 IndexType::Hnsw,
1266 IndexType::Ivfflat,
1267 ] {
1268 let json = serde_json::to_string(&variant).unwrap();
1269 let d: IndexType = serde_json::from_str(&json).unwrap();
1270 assert_eq!(d, variant);
1271 }
1272 }
1273
1274 #[test]
1275 fn action_serde_roundtrip() {
1276 let actions = vec![
1277 Action::Summarize("scope".into()),
1278 Action::Checkpoint("scope".into()),
1279 Action::ExtractArtifacts("scope".into()),
1280 Action::Notify("admin".into()),
1281 Action::Inject {
1282 target: "memory".into(),
1283 mode: InjectionMode::Full,
1284 },
1285 ];
1286 for action in actions {
1287 let json = serde_json::to_string(&action).unwrap();
1288 let d: Action = serde_json::from_str(&json).unwrap();
1289 assert_eq!(d, action);
1290 }
1291 }
1292
1293 #[test]
1294 fn injection_mode_serde_roundtrip() {
1295 for variant in [
1296 InjectionMode::Full,
1297 InjectionMode::Summary,
1298 InjectionMode::TopK(5),
1299 InjectionMode::Relevant(0.8),
1300 ] {
1301 let json = serde_json::to_string(&variant).unwrap();
1302 let d: InjectionMode = serde_json::from_str(&json).unwrap();
1303 assert_eq!(d, variant);
1304 }
1305 }
1306
1307 #[test]
1308 fn lock_mode_serde_roundtrip() {
1309 for variant in [LockMode::Exclusive, LockMode::Shared] {
1310 let json = serde_json::to_string(&variant).unwrap();
1311 let d: LockMode = serde_json::from_str(&json).unwrap();
1312 assert_eq!(d, variant);
1313 }
1314 }
1315
1316 #[test]
1317 fn summary_style_serde_roundtrip() {
1318 for variant in [SummaryStyle::Brief, SummaryStyle::Detailed] {
1319 let json = serde_json::to_string(&variant).unwrap();
1320 let d: SummaryStyle = serde_json::from_str(&json).unwrap();
1321 assert_eq!(d, variant);
1322 }
1323 }
1324
1325 #[test]
1326 fn compare_op_serde_roundtrip() {
1327 for variant in [
1328 CompareOp::Eq,
1329 CompareOp::Ne,
1330 CompareOp::Gt,
1331 CompareOp::Lt,
1332 CompareOp::Ge,
1333 CompareOp::Le,
1334 CompareOp::Contains,
1335 CompareOp::In,
1336 CompareOp::Regex,
1337 ] {
1338 let json = serde_json::to_string(&variant).unwrap();
1339 let d: CompareOp = serde_json::from_str(&json).unwrap();
1340 assert_eq!(d, variant);
1341 }
1342 }
1343}