stasis/application/runtime/
runtime_diagnostics_helpers.rs1use 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}