Populate the session tables from a Sherpa provenance graph. Called during `save_provenance_graph` when the graph has `profile == "sherpa-trace/1.0.0"`. Extracts session data from the provenance nodes' `detail` fields and writes to the four session tables. Best-effort: parse errors on individual nodes are logged and skipped.
(
&mut self,
provenance_id: u64,
graph: &crate::change::ProvenanceGraph,
)
| 65 | /// |
| 66 | /// Best-effort: parse errors on individual nodes are logged and skipped. |
| 67 | pub fn populate_session_tables( |
| 68 | &mut self, |
| 69 | provenance_id: u64, |
| 70 | graph: &crate::change::ProvenanceGraph, |
| 71 | ) -> PristineResult<()> { |
| 72 | use crate::change::provenance_graph::ProvenanceNodeKind; |
| 73 | use crate::change::session::*; |
| 74 | |
| 75 | // Gate on profile — only populate for Sherpa graphs. |
| 76 | match graph.profile.as_deref() { |
| 77 | Some(crate::change::provenance_graph::SHERPA_PROFILE) => {} |
| 78 | _ => return Ok(()), |
| 79 | } |
| 80 | |
| 81 | // Open all four tables. |
| 82 | let mut events_table = self.txn.open_table(SESSION_EVENTS)?; |
| 83 | let mut todos_table = self.txn.open_table(SESSION_TODOS)?; |
| 84 | let mut phases_table = self.txn.open_table(SESSION_PHASES)?; |
| 85 | let mut intents_table = self.txn.open_table(SESSION_INTENTS)?; |
| 86 | |
| 87 | for (seq, node) in graph.nodes.iter().enumerate() { |
| 88 | let seq = seq as u64; |
| 89 | let detail: Option<serde_json::Value> = if let Some(s) = node.detail.as_deref() { |
| 90 | match serde_json::from_str(s) { |
| 91 | Ok(v) => Some(v), |
| 92 | Err(e) => { |
| 93 | log::warn!( |
| 94 | "Warning: failed to parse provenance node detail JSON (node id={}, kind={}): {}", |
| 95 | node.id, |
| 96 | node.kind.label(), |
| 97 | e |
| 98 | ); |
| 99 | None |
| 100 | } |
| 101 | } |
| 102 | } else { |
| 103 | None |
| 104 | }; |
| 105 | |
| 106 | // Write every node as a SessionEvent regardless of kind. |
| 107 | // |
| 108 | // `event_kind` and `record_type` are populated from the original |
| 109 | // Sherpa/agent-trace `record_type` field stored in the detail JSON |
| 110 | // (e.g. "intent", "todo_status", "phase_transition"). We fall back |
| 111 | // to `node.kind.label()` only when no such field is present so that |
| 112 | // non-Sherpa nodes still get a meaningful discriminant. |
| 113 | let sherpa_record_type = detail |
| 114 | .as_ref() |
| 115 | .and_then(|d| d["record_type"].as_str()) |
| 116 | .map(|s| s.to_string()); |
| 117 | let event_kind = sherpa_record_type |
| 118 | .clone() |
| 119 | .unwrap_or_else(|| node.kind.label().to_string()); |
| 120 | let event = SessionEvent { |
| 121 | seq, |
| 122 | timestamp: format_timestamp_ms(node.timestamp), |
| 123 | event_kind, |
| 124 | place: None, |