Skip to main content

stasis/application/runtime/
runtime_diagnostics_helpers.rs

1use serde_json::{Value as JsonValue, json};
2
3use crate::ports::outbound::memory::memory_models::{
4    MemoryNode, MemoryRecallResponse, MemoryStoreResponse,
5};
6
7pub fn memory_nodes_json(nodes: &[MemoryNode]) -> JsonValue {
8    JsonValue::Array(
9        nodes
10            .iter()
11            .map(|node| {
12                json!({
13                    "sync_key": node.sync_key,
14                    "session_id": node.session_id,
15                    "tier": node.tier,
16                    "raw": node.raw,
17                    "context_summary": node.context_summary,
18                    "semantic_tags": node.semantic_tags,
19                    "semantic_links": node.semantic_links,
20                    "psi": node.psi,
21                    "rho": node.rho,
22                    "kappa": node.kappa,
23                })
24            })
25            .collect(),
26    )
27}
28
29pub struct RuntimeMemoryDiagnosticsBundle {
30    pub retrieved_count: usize,
31    pub retrieval_path: Option<String>,
32    pub fallback_triggered: bool,
33    pub fallback_reason: Option<String>,
34    pub store_valid: bool,
35    pub store_node_id: Option<String>,
36    pub memory_recall: JsonValue,
37    pub memory_store: JsonValue,
38    pub identity_context: JsonValue,
39}
40
41pub struct RuntimeMemoryRecallDiagnosticsInput {
42    pub attempted: bool,
43    pub response: Option<MemoryRecallResponse>,
44    pub query_id: Option<String>,
45    pub query_fingerprint: Option<String>,
46    pub error: Option<String>,
47}
48
49pub struct RuntimeMemoryStoreDiagnosticsInput {
50    pub attempted: bool,
51    pub response: Option<MemoryStoreResponse>,
52    pub error: Option<String>,
53}
54
55pub struct RuntimeIdentityDiagnosticsInput {
56    pub attempted: bool,
57    pub summary: Option<String>,
58    pub error: Option<String>,
59}
60
61pub fn build_runtime_memory_diagnostics_bundle(
62    memory_recall: RuntimeMemoryRecallDiagnosticsInput,
63    memory_store: RuntimeMemoryStoreDiagnosticsInput,
64    identity: RuntimeIdentityDiagnosticsInput,
65) -> RuntimeMemoryDiagnosticsBundle {
66    let retrieved_count = memory_recall
67        .response
68        .as_ref()
69        .map(|value| value.retrieved)
70        .unwrap_or_default();
71    let retrieval_path = memory_recall
72        .response
73        .as_ref()
74        .and_then(|value| value.retrieval_path.clone());
75    let fallback_triggered = memory_recall
76        .response
77        .as_ref()
78        .map(|value| value.fallback_triggered)
79        .unwrap_or(false);
80    let fallback_reason = memory_recall
81        .response
82        .as_ref()
83        .and_then(|value| value.fallback_reason.clone());
84
85    let store_valid = memory_store
86        .response
87        .as_ref()
88        .map(|value| value.valid)
89        .unwrap_or(false);
90    let store_node_id = memory_store.response.as_ref().map(|value| value.node_id.clone());
91
92    let memory_recall_section = json!({
93        "attempted": memory_recall.attempted,
94        "query_id": memory_recall.query_id,
95        "query_fingerprint": memory_recall.query_fingerprint,
96        "retrieved": retrieved_count,
97        "retrieval_path": retrieval_path,
98        "fallback_triggered": fallback_triggered,
99        "fallback_reason": fallback_reason,
100        "error": memory_recall.error,
101    });
102
103    let memory_store_section = json!({
104        "attempted": memory_store.attempted,
105        "node_id": store_node_id,
106        "valid": store_valid,
107        "error": memory_store.error,
108    });
109
110    let identity_context_section = json!({
111        "attempted": identity.attempted,
112        "summary": identity.summary,
113        "error": identity.error,
114    });
115
116    RuntimeMemoryDiagnosticsBundle {
117        retrieved_count,
118        retrieval_path,
119        fallback_triggered,
120        fallback_reason,
121        store_valid,
122        store_node_id,
123        memory_recall: memory_recall_section,
124        memory_store: memory_store_section,
125        identity_context: identity_context_section,
126    }
127}
128
129pub fn build_runtime_failure_memory_recall_section(
130    attempted: bool,
131    error: Option<String>,
132) -> JsonValue {
133    json!({
134        "attempted": attempted,
135        "error": error,
136    })
137}
138
139pub fn build_runtime_failure_identity_context_section(
140    attempted: bool,
141    summary: Option<String>,
142    error: Option<String>,
143) -> JsonValue {
144    json!({
145        "attempted": attempted,
146        "summary": summary,
147        "error": error,
148    })
149}
150
151#[derive(Clone, Debug, Default)]
152pub struct RuntimeDiagnosticsEnvelope {
153    pub guardrail_code: Option<String>,
154    pub policy_reason: Option<String>,
155    pub duration_ms: Option<u64>,
156    pub input_memory_query_id: Option<String>,
157    pub input_memory_query_fingerprint: Option<String>,
158    pub output_memory_node_id: Option<String>,
159    pub retrieval_path: Option<String>,
160    pub thread_id: Option<String>,
161}
162
163pub fn extract_runtime_diagnostics_envelope(diagnostics: Option<&str>) -> RuntimeDiagnosticsEnvelope {
164    let Some(raw) = diagnostics else {
165        return RuntimeDiagnosticsEnvelope::default();
166    };
167
168    let Ok(json) = serde_json::from_str::<JsonValue>(raw) else {
169        return RuntimeDiagnosticsEnvelope::default();
170    };
171
172    RuntimeDiagnosticsEnvelope {
173        guardrail_code: json
174            .get("guardrail_code")
175            .and_then(|v| v.as_str())
176            .map(str::to_owned),
177        policy_reason: json
178            .get("policy_reason")
179            .and_then(|v| v.as_str())
180            .map(str::to_owned),
181        duration_ms: json.get("duration_ms").and_then(|v| v.as_u64()),
182        input_memory_query_id: json
183            .get("input_memory_query_id")
184            .and_then(|v| v.as_str())
185            .map(str::to_owned),
186        input_memory_query_fingerprint: json
187            .get("input_memory_query_fingerprint")
188            .and_then(|v| v.as_str())
189            .map(str::to_owned)
190            .or_else(|| {
191                json.get("memory_recall")
192                    .and_then(|v| v.get("query_fingerprint"))
193                    .and_then(|v| v.as_str())
194                    .map(str::to_owned)
195            }),
196        output_memory_node_id: json
197            .get("output_memory_node_id")
198            .and_then(|v| v.as_str())
199            .map(str::to_owned)
200            .or_else(|| {
201                json.get("memory_store_node_id")
202                    .and_then(|v| v.as_str())
203                    .map(str::to_owned)
204            }),
205        retrieval_path: json
206            .get("memory_retrieval_path")
207            .and_then(|v| v.as_str())
208            .map(str::to_owned),
209        thread_id: json
210            .get("thread_id")
211            .and_then(|v| v.as_str())
212            .map(str::to_owned),
213    }
214}
215
216pub fn extract_diagnostics_fields(
217    diagnostics: Option<&str>,
218) -> (Option<String>, Option<String>, Option<u64>) {
219    let envelope = extract_runtime_diagnostics_envelope(diagnostics);
220    (
221        envelope.guardrail_code,
222        envelope.policy_reason,
223        envelope.duration_ms,
224    )
225}
226
227pub fn extract_memory_lineage_fields(
228    diagnostics: Option<&str>,
229) -> (
230    Option<String>,
231    Option<String>,
232    Option<String>,
233    Option<String>,
234) {
235    let envelope = extract_runtime_diagnostics_envelope(diagnostics);
236
237    (
238        envelope.input_memory_query_id,
239        envelope.input_memory_query_fingerprint,
240        envelope.output_memory_node_id,
241        envelope.retrieval_path,
242    )
243}
244
245pub fn extract_thread_id(diagnostics: Option<&str>) -> Option<String> {
246    extract_runtime_diagnostics_envelope(diagnostics).thread_id
247}