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)]
145pub struct SttpNode {
146 pub raw: String,
147 pub session_id: String,
148 pub tier: String,
149 pub timestamp: DateTime<Utc>,
150 pub compression_depth: i32,
151 pub parent_node_id: Option<String>,
152 pub sync_key: String,
153 pub updated_at: DateTime<Utc>,
154 pub source_metadata: Option<ConnectorMetadata>,
155 pub context_summary: Option<String>,
156 pub embedding: Option<Vec<f32>>,
157 pub embedding_model: Option<String>,
158 pub embedding_dimensions: Option<usize>,
159 pub embedded_at: Option<DateTime<Utc>>,
160 pub user_avec: AvecState,
161 pub model_avec: AvecState,
162 pub compression_avec: Option<AvecState>,
163 pub rho: f32,
164 pub kappa: f32,
165 pub psi: f32,
166}
167
168#[derive(Debug, Clone, PartialEq, SerdeSerialize, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct ConnectorMetadata {
171 pub connector_id: String,
172 pub source_kind: String,
173 pub upstream_id: String,
174 pub revision: Option<String>,
175 pub observed_at_utc: DateTime<Utc>,
176 pub extra: Option<Value>,
177}
178
179impl SttpNode {
180 pub fn canonical_sync_key(&self) -> String {
181 #[derive(Serialize)]
182 struct SyncFingerprint<'a> {
183 session_id: &'a str,
184 tier: &'a str,
185 timestamp: String,
186 compression_depth: i32,
187 parent_node_id: &'a Option<String>,
188 raw: &'a str,
189 user_avec: AvecState,
190 model_avec: AvecState,
191 compression_avec: Option<AvecState>,
192 rho: f32,
193 kappa: f32,
194 psi: f32,
195 }
196
197 let fingerprint = SyncFingerprint {
198 session_id: &self.session_id,
199 tier: &self.tier,
200 timestamp: self.timestamp.to_rfc3339(),
201 compression_depth: self.compression_depth,
202 parent_node_id: &self.parent_node_id,
203 raw: &self.raw,
204 user_avec: self.user_avec,
205 model_avec: self.model_avec,
206 compression_avec: self.compression_avec,
207 rho: self.rho,
208 kappa: self.kappa,
209 psi: self.psi,
210 };
211
212 let encoded = serde_json::to_vec(&fingerprint).unwrap_or_default();
213 let mut hasher = Sha256::new();
214 hasher.update(encoded);
215 let digest = hasher.finalize();
216
217 digest.iter().map(|byte| format!("{byte:02x}")).collect()
218 }
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub enum NodeUpsertStatus {
223 Created,
224 Updated,
225 Duplicate,
226 Skipped,
227}
228
229#[derive(Debug, Clone)]
230pub struct NodeUpsertResult {
231 pub node_id: String,
232 pub sync_key: String,
233 pub status: NodeUpsertStatus,
234 pub updated_at: DateTime<Utc>,
235}
236
237#[derive(Debug, Clone, PartialEq, Eq)]
238pub struct SyncCursor {
239 pub updated_at: DateTime<Utc>,
240 pub sync_key: String,
241}
242
243#[derive(Debug, Clone, Default)]
244pub struct ChangeQueryResult {
245 pub nodes: Vec<SttpNode>,
246 pub next_cursor: Option<SyncCursor>,
247 pub has_more: bool,
248}
249
250#[derive(Debug, Clone)]
251pub struct SyncCheckpoint {
252 pub session_id: String,
253 pub connector_id: String,
254 pub cursor: Option<SyncCursor>,
255 pub updated_at: DateTime<Utc>,
256 pub metadata: Option<ConnectorMetadata>,
257}
258
259#[derive(Debug, Clone)]
260pub struct SyncPullRequest {
261 pub session_id: String,
262 pub connector_id: String,
263 pub page_size: usize,
264 pub max_batches: Option<usize>,
265}
266
267#[derive(Debug, Clone, Default)]
268pub struct SyncPullResult {
269 pub fetched: usize,
270 pub created: usize,
271 pub updated: usize,
272 pub duplicate: usize,
273 pub skipped: usize,
274 pub filtered: usize,
275 pub batches: usize,
276 pub has_more: bool,
277 pub last_cursor: Option<SyncCursor>,
278 pub checkpoint: Option<SyncCheckpoint>,
279}
280
281#[derive(Debug, Clone)]
282pub struct CalibrationResult {
283 pub previous_avec: AvecState,
284 pub delta: f32,
285 pub drift_classification: DriftClassification,
286 pub trigger: String,
287 pub trigger_history: Vec<String>,
288 pub is_first_calibration: bool,
289}
290
291#[derive(Debug, Clone)]
292pub struct NodeQuery {
293 pub limit: usize,
294 pub session_id: Option<String>,
295 pub from_utc: Option<DateTime<Utc>>,
296 pub to_utc: Option<DateTime<Utc>>,
297 pub tiers: Option<Vec<String>>,
298}
299
300impl Default for NodeQuery {
301 fn default() -> Self {
302 Self {
303 limit: 500,
304 session_id: None,
305 from_utc: None,
306 to_utc: None,
307 tiers: None,
308 }
309 }
310}
311
312#[derive(Debug, Clone, Copy, PartialEq)]
313pub struct NumericRange {
314 pub min: f32,
315 pub max: f32,
316 pub average: f32,
317}
318
319impl Default for NumericRange {
320 fn default() -> Self {
321 Self {
322 min: 0.0,
323 max: 0.0,
324 average: 0.0,
325 }
326 }
327}
328
329#[derive(Debug, Clone, Copy, PartialEq)]
330pub struct PsiRange {
331 pub min: f32,
332 pub max: f32,
333 pub average: f32,
334}
335
336impl Default for PsiRange {
337 fn default() -> Self {
338 Self {
339 min: 0.0,
340 max: 0.0,
341 average: 0.0,
342 }
343 }
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
347pub struct ConfidenceBandSummary {
348 pub low: usize,
349 pub medium: usize,
350 pub high: usize,
351}
352
353#[derive(Debug, Clone, Default)]
354pub struct RetrieveResult {
355 pub nodes: Vec<SttpNode>,
356 pub retrieved: usize,
357 pub psi_range: PsiRange,
358}
359
360#[derive(Debug, Clone, Default)]
361pub struct ListNodesResult {
362 pub nodes: Vec<SttpNode>,
363 pub retrieved: usize,
364}
365
366#[derive(Debug, Clone, Default)]
367pub struct ScopeRekeyResult {
368 pub source_tenant_id: String,
369 pub source_session_id: String,
370 pub target_tenant_id: String,
371 pub target_session_id: String,
372 pub temporal_nodes: usize,
373 pub calibrations: usize,
374 pub target_temporal_nodes: usize,
375 pub target_calibrations: usize,
376 pub applied: bool,
377 pub conflict: bool,
378 pub message: Option<String>,
379}
380
381#[derive(Debug, Clone, Default)]
382pub struct BatchRekeyResult {
383 pub dry_run: bool,
384 pub requested_node_ids: usize,
385 pub resolved_node_ids: usize,
386 pub missing_node_ids: Vec<String>,
387 pub scopes: Vec<ScopeRekeyResult>,
388 pub temporal_nodes_updated: usize,
389 pub calibrations_updated: usize,
390}
391
392#[derive(Debug, Clone, Default)]
393pub struct StoreResult {
394 pub node_id: String,
395 pub psi: f32,
396 pub valid: bool,
397 pub validation_error: Option<String>,
398}
399
400#[derive(Debug, Clone)]
401pub struct ParseResult {
402 pub success: bool,
403 pub node: Option<SttpNode>,
404 pub error: Option<String>,
405 pub profile: ParseProfile,
406 pub strict_valid: bool,
407 pub diagnostics: Vec<ParseDiagnostic>,
408 pub canonical_ast: Option<CanonicalAst>,
409}
410
411impl ParseResult {
412 pub fn ok(node: SttpNode) -> Self {
413 Self {
414 success: true,
415 node: Some(node),
416 error: None,
417 profile: ParseProfile::Tolerant,
418 strict_valid: true,
419 diagnostics: Vec::new(),
420 canonical_ast: None,
421 }
422 }
423
424 pub fn ok_with_metadata(
425 node: SttpNode,
426 profile: ParseProfile,
427 strict_valid: bool,
428 diagnostics: Vec<ParseDiagnostic>,
429 canonical_ast: Option<CanonicalAst>,
430 ) -> Self {
431 Self {
432 success: true,
433 node: Some(node),
434 error: None,
435 profile,
436 strict_valid,
437 diagnostics,
438 canonical_ast,
439 }
440 }
441
442 pub fn fail(error: impl Into<String>) -> Self {
443 Self {
444 success: false,
445 node: None,
446 error: Some(error.into()),
447 profile: ParseProfile::Tolerant,
448 strict_valid: false,
449 diagnostics: Vec::new(),
450 canonical_ast: None,
451 }
452 }
453
454 pub fn fail_with_metadata(
455 error: impl Into<String>,
456 profile: ParseProfile,
457 diagnostics: Vec<ParseDiagnostic>,
458 canonical_ast: Option<CanonicalAst>,
459 ) -> Self {
460 Self {
461 success: false,
462 node: None,
463 error: Some(error.into()),
464 profile,
465 strict_valid: false,
466 diagnostics,
467 canonical_ast,
468 }
469 }
470}
471
472#[derive(Debug, Default,Clone, Copy, PartialEq, Eq)]
473pub enum ParseProfile {
474 Strict,
475 StrictTypedIr,
476 #[default]
477 Tolerant,
478}
479
480#[derive(Debug, Clone, Copy, PartialEq, Eq)]
481pub enum ParseDiagnosticSeverity {
482 Fatal,
483 Error,
484 Warning,
485 Info,
486}
487
488#[derive(Debug, Clone)]
489pub struct ParseDiagnostic {
490 pub code: String,
491 pub message: String,
492 pub severity: ParseDiagnosticSeverity,
493 pub strict_impact: bool,
494 pub span: Option<ParseSpan>,
495}
496
497#[derive(Debug, Clone, Copy, PartialEq, Eq)]
498pub struct ParseSpan {
499 pub start: usize,
500 pub end: usize,
501 pub line: usize,
502 pub column: usize,
503}
504
505#[derive(Debug, Clone)]
506pub struct CanonicalAstLayer {
507 pub source: String,
508 pub span: ParseSpan,
509}
510
511#[derive(Debug, Clone)]
512pub struct CanonicalAst {
513 pub provenance: Option<CanonicalAstLayer>,
514 pub envelope: Option<CanonicalAstLayer>,
515 pub content: Option<CanonicalAstLayer>,
516 pub metrics: Option<CanonicalAstLayer>,
517 pub strict_spine: bool,
518 pub profile: ParseProfile,
519}
520
521#[derive(Debug, Clone)]
522pub struct ValidationResult {
523 pub is_valid: bool,
524 pub error: Option<String>,
525 pub reason: ValidationFailureReason,
526}
527
528impl ValidationResult {
529 pub fn ok() -> Self {
530 Self {
531 is_valid: true,
532 error: None,
533 reason: ValidationFailureReason::None,
534 }
535 }
536
537 pub fn fail(error: impl Into<String>, reason: ValidationFailureReason) -> Self {
538 Self {
539 is_valid: false,
540 error: Some(error.into()),
541 reason,
542 }
543 }
544}
545
546#[derive(Debug, Clone)]
547pub struct MoodCatalogResult {
548 pub presets: Vec<MoodPreset>,
549 pub apply_guide: String,
550 pub swap_preview: Option<MoodSwapPreview>,
551}
552
553#[derive(Debug, Clone)]
554pub struct MoodPreset {
555 pub name: String,
556 pub description: String,
557 pub avec: AvecState,
558}
559
560#[derive(Debug, Clone)]
561pub struct MoodSwapPreview {
562 pub target_mood: String,
563 pub blend: f32,
564 pub current: AvecState,
565 pub target: AvecState,
566 pub blended: AvecState,
567}
568
569#[derive(Debug, Clone)]
570pub struct MonthlyRollupRequest {
571 pub session_id: String,
572 pub start_utc: DateTime<Utc>,
573 pub end_utc: DateTime<Utc>,
574 pub source_session_id: Option<String>,
575 pub parent_node_id: Option<String>,
576 pub limit: usize,
577 pub persist: bool,
578}
579
580impl MonthlyRollupRequest {
581 pub fn new(
582 session_id: impl Into<String>,
583 start_utc: DateTime<Utc>,
584 end_utc: DateTime<Utc>,
585 ) -> Self {
586 Self {
587 session_id: session_id.into(),
588 start_utc,
589 end_utc,
590 source_session_id: None,
591 parent_node_id: None,
592 limit: 5000,
593 persist: true,
594 }
595 }
596}
597
598#[derive(Debug, Clone)]
599pub struct MonthlyRollupResult {
600 pub success: bool,
601 pub node_id: String,
602 pub raw_node: String,
603 pub error: Option<String>,
604 pub source_nodes: usize,
605 pub parent_reference: Option<String>,
606 pub user_average: AvecState,
607 pub model_average: AvecState,
608 pub compression_average: AvecState,
609 pub rho_range: NumericRange,
610 pub kappa_range: NumericRange,
611 pub psi_range: NumericRange,
612 pub rho_bands: ConfidenceBandSummary,
613 pub kappa_bands: ConfidenceBandSummary,
614}
615
616impl Default for MonthlyRollupResult {
617 fn default() -> Self {
618 Self {
619 success: false,
620 node_id: String::new(),
621 raw_node: String::new(),
622 error: None,
623 source_nodes: 0,
624 parent_reference: None,
625 user_average: AvecState::zero(),
626 model_average: AvecState::zero(),
627 compression_average: AvecState::zero(),
628 rho_range: NumericRange::default(),
629 kappa_range: NumericRange::default(),
630 psi_range: NumericRange::default(),
631 rho_bands: ConfidenceBandSummary::default(),
632 kappa_bands: ConfidenceBandSummary::default(),
633 }
634 }
635}