1use chrono::{DateTime, Utc};
2use serde::Serialize;
3use serde::{Deserialize, Serialize as SerdeSerialize};
4use serde_json::Value;
5use sha2::{Digest, Sha256};
6use std::fmt;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum DriftClassification {
10 Intentional,
11 Uncontrolled,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ValidationFailureReason {
16 None,
17 ParseFailure,
18 CoherenceFailure,
19 MissingLayer,
20 InvalidTier,
21 NestingDepth,
22 SchemaViolation,
23}
24
25impl fmt::Display for ValidationFailureReason {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 match self {
28 ValidationFailureReason::None => write!(f, "None"),
29 ValidationFailureReason::ParseFailure => write!(f, "ParseFailure"),
30 ValidationFailureReason::CoherenceFailure => write!(f, "CoherenceFailure"),
31 ValidationFailureReason::MissingLayer => write!(f, "MissingLayer"),
32 ValidationFailureReason::InvalidTier => write!(f, "InvalidTier"),
33 ValidationFailureReason::NestingDepth => write!(f, "NestingDepth"),
34 ValidationFailureReason::SchemaViolation => write!(f, "SchemaViolation"),
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
40pub struct AvecState {
41 pub stability: f32,
42 pub friction: f32,
43 pub logic: f32,
44 pub autonomy: f32,
45}
46
47impl AvecState {
48 pub fn psi(self) -> f32 {
49 self.stability + self.friction + self.logic + self.autonomy
50 }
51
52 pub fn drift_from(self, previous: Self) -> f32 {
53 self.psi() - previous.psi()
54 }
55
56 pub fn classify_drift(self, previous: Self) -> DriftClassification {
57 let delta = self.drift_from(previous).abs();
58 if delta > 0.3 {
59 DriftClassification::Uncontrolled
60 } else {
61 DriftClassification::Intentional
62 }
63 }
64
65 pub const fn zero() -> Self {
66 Self {
67 stability: 0.0,
68 friction: 0.0,
69 logic: 0.0,
70 autonomy: 0.0,
71 }
72 }
73
74 pub const fn focused() -> Self {
75 Self {
76 stability: 0.95,
77 friction: 0.10,
78 logic: 0.95,
79 autonomy: 0.90,
80 }
81 }
82
83 pub const fn creative() -> Self {
84 Self {
85 stability: 0.80,
86 friction: 0.15,
87 logic: 0.70,
88 autonomy: 0.95,
89 }
90 }
91
92 pub const fn analytical() -> Self {
93 Self {
94 stability: 0.90,
95 friction: 0.20,
96 logic: 0.98,
97 autonomy: 0.85,
98 }
99 }
100
101 pub const fn exploratory() -> Self {
102 Self {
103 stability: 0.75,
104 friction: 0.30,
105 logic: 0.65,
106 autonomy: 0.90,
107 }
108 }
109
110 pub const fn collaborative() -> Self {
111 Self {
112 stability: 0.85,
113 friction: 0.10,
114 logic: 0.80,
115 autonomy: 0.70,
116 }
117 }
118
119 pub const fn defensive() -> Self {
120 Self {
121 stability: 0.90,
122 friction: 0.40,
123 logic: 0.90,
124 autonomy: 0.60,
125 }
126 }
127
128 pub const fn passive() -> Self {
129 Self {
130 stability: 0.98,
131 friction: 0.05,
132 logic: 0.60,
133 autonomy: 0.40,
134 }
135 }
136}
137
138impl Default for AvecState {
139 fn default() -> Self {
140 Self::zero()
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq, Hash, SerdeSerialize, Deserialize)]
145#[serde(rename_all = "camelCase")]
146pub struct SemanticTagNodeRef {
147 pub tenant_id: String,
148 pub session_id: String,
149 pub node_id: String,
150 pub sync_key: String,
151}
152
153#[derive(Debug, Clone, PartialEq, SerdeSerialize, Deserialize)]
154#[serde(rename_all = "camelCase")]
155pub struct SemanticTagRecord {
156 pub tenant_id: String,
157 pub session_id: String,
158 pub node_id: String,
159 pub sync_key: String,
160 pub tag: String,
161 pub embedding: Option<Vec<f32>>,
162 pub embedding_model: Option<String>,
163 pub embedding_dimensions: Option<usize>,
164 pub embedded_at: Option<DateTime<Utc>>,
165 pub updated_at: DateTime<Utc>,
166}
167
168#[derive(Debug, Clone, Default)]
169pub struct SemanticTagQueryFilter {
170 pub tenant_id: Option<String>,
171 pub session_id: Option<String>,
172 pub tags: Option<Vec<String>>,
173 pub tag_prefix: Option<String>,
174 pub has_embedding: Option<bool>,
175 pub missing_embedding_only: bool,
176 pub limit: usize,
177}
178
179#[derive(Debug, Clone, PartialEq, SerdeSerialize, Deserialize)]
180#[serde(rename_all = "camelCase")]
181pub struct SemanticLink {
182 pub rel: String,
183 pub target: String,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub confidence: Option<f32>,
186}
187
188#[derive(Debug, Clone)]
189pub struct SttpNode {
190 pub raw: String,
191 pub session_id: String,
192 pub tier: String,
193 pub timestamp: DateTime<Utc>,
194 pub compression_depth: i32,
195 pub parent_node_id: Option<String>,
196 pub sync_key: String,
197 pub updated_at: DateTime<Utc>,
198 pub source_metadata: Option<ConnectorMetadata>,
199 pub context_summary: Option<String>,
200 pub semantic_tags: Option<Vec<String>>,
201 pub semantic_links: Option<Vec<SemanticLink>>,
202 pub embedding: Option<Vec<f32>>,
203 pub embedding_model: Option<String>,
204 pub embedding_dimensions: Option<usize>,
205 pub embedded_at: Option<DateTime<Utc>>,
206 pub user_avec: AvecState,
207 pub model_avec: AvecState,
208 pub compression_avec: Option<AvecState>,
209 pub rho: f32,
210 pub kappa: f32,
211 pub psi: f32,
212}
213
214#[derive(Debug, Clone, PartialEq, SerdeSerialize, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub struct ConnectorMetadata {
217 pub connector_id: String,
218 pub source_kind: String,
219 pub upstream_id: String,
220 pub revision: Option<String>,
221 pub observed_at_utc: DateTime<Utc>,
222 pub extra: Option<Value>,
223}
224
225impl SttpNode {
226 pub fn canonical_sync_key(&self) -> String {
227 #[derive(Serialize)]
228 struct SyncFingerprint<'a> {
229 session_id: &'a str,
230 tier: &'a str,
231 timestamp: String,
232 compression_depth: i32,
233 parent_node_id: &'a Option<String>,
234 raw: &'a str,
235 user_avec: AvecState,
236 model_avec: AvecState,
237 compression_avec: Option<AvecState>,
238 rho: f32,
239 kappa: f32,
240 psi: f32,
241 }
242
243 let fingerprint = SyncFingerprint {
244 session_id: &self.session_id,
245 tier: &self.tier,
246 timestamp: self.timestamp.to_rfc3339(),
247 compression_depth: self.compression_depth,
248 parent_node_id: &self.parent_node_id,
249 raw: &self.raw,
250 user_avec: self.user_avec,
251 model_avec: self.model_avec,
252 compression_avec: self.compression_avec,
253 rho: self.rho,
254 kappa: self.kappa,
255 psi: self.psi,
256 };
257
258 let encoded = serde_json::to_vec(&fingerprint).unwrap_or_default();
259 let mut hasher = Sha256::new();
260 hasher.update(encoded);
261 let digest = hasher.finalize();
262
263 digest.iter().map(|byte| format!("{byte:02x}")).collect()
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
268pub enum NodeUpsertStatus {
269 Created,
270 Updated,
271 Duplicate,
272 Skipped,
273}
274
275#[derive(Debug, Clone)]
276pub struct NodeUpsertResult {
277 pub node_id: String,
278 pub sync_key: String,
279 pub status: NodeUpsertStatus,
280 pub updated_at: DateTime<Utc>,
281}
282
283#[derive(Debug, Clone, PartialEq, Eq)]
284pub struct SyncCursor {
285 pub updated_at: DateTime<Utc>,
286 pub sync_key: String,
287}
288
289#[derive(Debug, Clone, Default)]
290pub struct ChangeQueryResult {
291 pub nodes: Vec<SttpNode>,
292 pub next_cursor: Option<SyncCursor>,
293 pub has_more: bool,
294}
295
296#[derive(Debug, Clone)]
297pub struct SyncCheckpoint {
298 pub session_id: String,
299 pub connector_id: String,
300 pub cursor: Option<SyncCursor>,
301 pub updated_at: DateTime<Utc>,
302 pub metadata: Option<ConnectorMetadata>,
303}
304
305#[derive(Debug, Clone)]
306pub struct SyncPullRequest {
307 pub session_id: String,
308 pub connector_id: String,
309 pub page_size: usize,
310 pub max_batches: Option<usize>,
311}
312
313#[derive(Debug, Clone, Default)]
314pub struct SyncPullResult {
315 pub fetched: usize,
316 pub created: usize,
317 pub updated: usize,
318 pub duplicate: usize,
319 pub skipped: usize,
320 pub filtered: usize,
321 pub batches: usize,
322 pub has_more: bool,
323 pub last_cursor: Option<SyncCursor>,
324 pub checkpoint: Option<SyncCheckpoint>,
325}
326
327#[derive(Debug, Clone)]
328pub struct CalibrationResult {
329 pub previous_avec: AvecState,
330 pub delta: f32,
331 pub drift_classification: DriftClassification,
332 pub trigger: String,
333 pub trigger_history: Vec<String>,
334 pub is_first_calibration: bool,
335}
336
337#[derive(Debug, Clone)]
338pub struct NodeQuery {
339 pub limit: usize,
340 pub session_id: Option<String>,
341 pub from_utc: Option<DateTime<Utc>>,
342 pub to_utc: Option<DateTime<Utc>>,
343 pub tiers: Option<Vec<String>>,
344}
345
346impl Default for NodeQuery {
347 fn default() -> Self {
348 Self {
349 limit: 500,
350 session_id: None,
351 from_utc: None,
352 to_utc: None,
353 tiers: None,
354 }
355 }
356}
357
358#[derive(Debug, Clone, Copy, PartialEq)]
359pub struct NumericRange {
360 pub min: f32,
361 pub max: f32,
362 pub average: f32,
363}
364
365impl Default for NumericRange {
366 fn default() -> Self {
367 Self {
368 min: 0.0,
369 max: 0.0,
370 average: 0.0,
371 }
372 }
373}
374
375#[derive(Debug, Clone, Copy, PartialEq)]
376pub struct PsiRange {
377 pub min: f32,
378 pub max: f32,
379 pub average: f32,
380}
381
382impl Default for PsiRange {
383 fn default() -> Self {
384 Self {
385 min: 0.0,
386 max: 0.0,
387 average: 0.0,
388 }
389 }
390}
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
393pub struct ConfidenceBandSummary {
394 pub low: usize,
395 pub medium: usize,
396 pub high: usize,
397}
398
399#[derive(Debug, Clone, Default)]
400pub struct RetrieveResult {
401 pub nodes: Vec<SttpNode>,
402 pub retrieved: usize,
403 pub psi_range: PsiRange,
404}
405
406#[derive(Debug, Clone, Default)]
407pub struct ListNodesResult {
408 pub nodes: Vec<SttpNode>,
409 pub retrieved: usize,
410}
411
412#[derive(Debug, Clone, Default)]
413pub struct ScopeRekeyResult {
414 pub source_tenant_id: String,
415 pub source_session_id: String,
416 pub target_tenant_id: String,
417 pub target_session_id: String,
418 pub temporal_nodes: usize,
419 pub calibrations: usize,
420 pub target_temporal_nodes: usize,
421 pub target_calibrations: usize,
422 pub applied: bool,
423 pub conflict: bool,
424 pub message: Option<String>,
425}
426
427#[derive(Debug, Clone, Default)]
428pub struct BatchRekeyResult {
429 pub dry_run: bool,
430 pub requested_node_ids: usize,
431 pub resolved_node_ids: usize,
432 pub missing_node_ids: Vec<String>,
433 pub scopes: Vec<ScopeRekeyResult>,
434 pub temporal_nodes_updated: usize,
435 pub calibrations_updated: usize,
436}
437
438#[derive(Debug, Clone, Copy, PartialEq, Eq)]
439pub enum NodeDeleteStatus {
440 Deleted,
441 NotFound,
442 Blocked,
443 Skipped,
444}
445
446#[derive(Debug, Clone, Default)]
447pub struct InboundNodeReferences {
448 pub child_parent_links: Vec<String>,
449 pub incoming_semantic_refs: Vec<String>,
450}
451
452#[derive(Debug, Clone)]
453pub struct NodeDeleteRecord {
454 pub node_id: String,
455 pub sync_key: String,
456 pub status: NodeDeleteStatus,
457 pub reason: Option<String>,
458}
459
460#[derive(Debug, Clone, Default)]
461pub struct NodeDeleteResult {
462 pub dry_run: bool,
463 pub deleted: usize,
464 pub blocked: usize,
465 pub not_found: usize,
466 pub skipped: usize,
467 pub calibrations_deleted: usize,
468 pub checkpoints_deleted: usize,
469 pub records: Vec<NodeDeleteRecord>,
470}
471
472#[derive(Debug, Clone, Default)]
473pub struct NodeDeleteRequest {
474 pub tenant_id: String,
475 pub session_id: String,
476 pub sync_keys: Vec<String>,
477 pub node_ids: Vec<String>,
478 pub dry_run: bool,
479}
480
481#[derive(Debug, Clone, Default)]
482pub struct SessionPurgeRequest {
483 pub tenant_id: String,
484 pub session_id: String,
485 pub tiers: Option<Vec<String>>,
486 pub dry_run: bool,
487 pub include_calibration: bool,
488 pub include_checkpoints: bool,
489}
490
491#[derive(Debug, Clone, Default)]
492pub struct StoreResult {
493 pub node_id: String,
494 pub psi: f32,
495 pub valid: bool,
496 pub validation_error: Option<String>,
497}
498
499#[derive(Debug, Clone)]
500pub struct ParseResult {
501 pub success: bool,
502 pub node: Option<SttpNode>,
503 pub error: Option<String>,
504 pub profile: ParseProfile,
505 pub strict_valid: bool,
506 pub diagnostics: Vec<ParseDiagnostic>,
507 pub canonical_ast: Option<CanonicalAst>,
508}
509
510impl ParseResult {
511 pub fn ok(node: SttpNode) -> Self {
512 Self {
513 success: true,
514 node: Some(node),
515 error: None,
516 profile: ParseProfile::Tolerant,
517 strict_valid: true,
518 diagnostics: Vec::new(),
519 canonical_ast: None,
520 }
521 }
522
523 pub fn ok_with_metadata(
524 node: SttpNode,
525 profile: ParseProfile,
526 strict_valid: bool,
527 diagnostics: Vec<ParseDiagnostic>,
528 canonical_ast: Option<CanonicalAst>,
529 ) -> Self {
530 Self {
531 success: true,
532 node: Some(node),
533 error: None,
534 profile,
535 strict_valid,
536 diagnostics,
537 canonical_ast,
538 }
539 }
540
541 pub fn fail(error: impl Into<String>) -> Self {
542 Self {
543 success: false,
544 node: None,
545 error: Some(error.into()),
546 profile: ParseProfile::Tolerant,
547 strict_valid: false,
548 diagnostics: Vec::new(),
549 canonical_ast: None,
550 }
551 }
552
553 pub fn fail_with_metadata(
554 error: impl Into<String>,
555 profile: ParseProfile,
556 diagnostics: Vec<ParseDiagnostic>,
557 canonical_ast: Option<CanonicalAst>,
558 ) -> Self {
559 Self {
560 success: false,
561 node: None,
562 error: Some(error.into()),
563 profile,
564 strict_valid: false,
565 diagnostics,
566 canonical_ast,
567 }
568 }
569}
570
571#[derive(Debug, Default,Clone, Copy, PartialEq, Eq)]
572pub enum ParseProfile {
573 Strict,
574 StrictTypedIr,
575 #[default]
576 Tolerant,
577}
578
579#[derive(Debug, Clone, Copy, PartialEq, Eq)]
580pub enum ParseDiagnosticSeverity {
581 Fatal,
582 Error,
583 Warning,
584 Info,
585}
586
587#[derive(Debug, Clone)]
588pub struct ParseDiagnostic {
589 pub code: String,
590 pub message: String,
591 pub severity: ParseDiagnosticSeverity,
592 pub strict_impact: bool,
593 pub span: Option<ParseSpan>,
594}
595
596#[derive(Debug, Clone, Copy, PartialEq, Eq)]
597pub struct ParseSpan {
598 pub start: usize,
599 pub end: usize,
600 pub line: usize,
601 pub column: usize,
602}
603
604#[derive(Debug, Clone)]
605pub struct CanonicalAstLayer {
606 pub source: String,
607 pub span: ParseSpan,
608}
609
610#[derive(Debug, Clone)]
611pub struct CanonicalAst {
612 pub provenance: Option<CanonicalAstLayer>,
613 pub envelope: Option<CanonicalAstLayer>,
614 pub content: Option<CanonicalAstLayer>,
615 pub metrics: Option<CanonicalAstLayer>,
616 pub strict_spine: bool,
617 pub profile: ParseProfile,
618}
619
620#[derive(Debug, Clone)]
621pub struct ValidationResult {
622 pub is_valid: bool,
623 pub error: Option<String>,
624 pub reason: ValidationFailureReason,
625}
626
627impl ValidationResult {
628 pub fn ok() -> Self {
629 Self {
630 is_valid: true,
631 error: None,
632 reason: ValidationFailureReason::None,
633 }
634 }
635
636 pub fn fail(error: impl Into<String>, reason: ValidationFailureReason) -> Self {
637 Self {
638 is_valid: false,
639 error: Some(error.into()),
640 reason,
641 }
642 }
643}
644
645#[derive(Debug, Clone)]
646pub struct MoodCatalogResult {
647 pub presets: Vec<MoodPreset>,
648 pub apply_guide: String,
649 pub swap_preview: Option<MoodSwapPreview>,
650}
651
652#[derive(Debug, Clone)]
653pub struct MoodPreset {
654 pub name: String,
655 pub description: String,
656 pub avec: AvecState,
657}
658
659#[derive(Debug, Clone)]
660pub struct MoodSwapPreview {
661 pub target_mood: String,
662 pub blend: f32,
663 pub current: AvecState,
664 pub target: AvecState,
665 pub blended: AvecState,
666}
667
668#[derive(Debug, Clone)]
669pub struct MonthlyRollupRequest {
670 pub session_id: String,
671 pub start_utc: DateTime<Utc>,
672 pub end_utc: DateTime<Utc>,
673 pub source_session_id: Option<String>,
674 pub parent_node_id: Option<String>,
675 pub limit: usize,
676 pub persist: bool,
677}
678
679impl MonthlyRollupRequest {
680 pub fn new(
681 session_id: impl Into<String>,
682 start_utc: DateTime<Utc>,
683 end_utc: DateTime<Utc>,
684 ) -> Self {
685 Self {
686 session_id: session_id.into(),
687 start_utc,
688 end_utc,
689 source_session_id: None,
690 parent_node_id: None,
691 limit: 5000,
692 persist: true,
693 }
694 }
695}
696
697#[derive(Debug, Clone)]
698pub struct MonthlyRollupResult {
699 pub success: bool,
700 pub node_id: String,
701 pub raw_node: String,
702 pub error: Option<String>,
703 pub source_nodes: usize,
704 pub parent_reference: Option<String>,
705 pub user_average: AvecState,
706 pub model_average: AvecState,
707 pub compression_average: AvecState,
708 pub rho_range: NumericRange,
709 pub kappa_range: NumericRange,
710 pub psi_range: NumericRange,
711 pub rho_bands: ConfidenceBandSummary,
712 pub kappa_bands: ConfidenceBandSummary,
713}
714
715impl Default for MonthlyRollupResult {
716 fn default() -> Self {
717 Self {
718 success: false,
719 node_id: String::new(),
720 raw_node: String::new(),
721 error: None,
722 source_nodes: 0,
723 parent_reference: None,
724 user_average: AvecState::zero(),
725 model_average: AvecState::zero(),
726 compression_average: AvecState::zero(),
727 rho_range: NumericRange::default(),
728 kappa_range: NumericRange::default(),
729 psi_range: NumericRange::default(),
730 rho_bands: ConfidenceBandSummary::default(),
731 kappa_bands: ConfidenceBandSummary::default(),
732 }
733 }
734}