Reconcile lifecycle metadata for a session record. Called from agent session start/end hooks. Creates the record if the session has no ledger entries yet; otherwise updates only the fields supplied, preserving ledger data (provenance links, turn count). `ended_at: None` on a re-entered (resumed) session clears a prior end.
(
&mut self,
session_id: &str,
json_path: &str,
view_name: Option<String>,
parent_view: Option<String>,
ended_at: Option<i64>,
)
| 782 | /// supplied, preserving ledger data (provenance links, turn count). |
| 783 | /// `ended_at: None` on a re-entered (resumed) session clears a prior end. |
| 784 | pub fn upsert_session_lifecycle( |
| 785 | &mut self, |
| 786 | session_id: &str, |
| 787 | json_path: &str, |
| 788 | view_name: Option<String>, |
| 789 | parent_view: Option<String>, |
| 790 | ended_at: Option<i64>, |
| 791 | ) -> PristineResult<()> { |
| 792 | use crate::change::session::SessionRecord; |
| 793 | |
| 794 | let mut sessions = self.txn.open_table(SESSIONS)?; |
| 795 | let mut record = match sessions.get(session_id)? { |
| 796 | Some(value) => SessionRecord::from_bytes(value.value()).map_err(|e| { |
| 797 | PristineError::Serialization { |
| 798 | message: format!("session record decode: {}", e), |
| 799 | } |
| 800 | })?, |
| 801 | None => SessionRecord { |
| 802 | session_id: session_id.to_string(), |
| 803 | json_path: json_path.to_string(), |
| 804 | view_name: None, |
| 805 | parent_view: None, |
| 806 | first_provenance: None, |
| 807 | latest_provenance: None, |
| 808 | turn_count: 0, |
| 809 | started_at: chrono::Utc::now().timestamp(), |
| 810 | ended_at: None, |
| 811 | }, |
| 812 | }; |
| 813 | |
| 814 | if record.json_path.is_empty() { |
| 815 | record.json_path = json_path.to_string(); |
| 816 | } |
| 817 | if view_name.is_some() { |
| 818 | record.view_name = view_name; |
| 819 | } |
| 820 | if parent_view.is_some() { |
| 821 | record.parent_view = parent_view; |
| 822 | } |
| 823 | // Explicit assignment: Some(ts) ends the session, None clears a |
| 824 | // stale end marker when a session is re-entered. |
| 825 | record.ended_at = ended_at; |
| 826 | |
| 827 | let bytes = record.to_bytes(); |
| 828 | sessions.insert(session_id, bytes.as_slice())?; |
| 829 | |
| 830 | // Refresh the session node's lifecycle metadata (ATOM-16). |
| 831 | drop(sessions); |
| 832 | self.emit_session_node(&record)?; |
| 833 | Ok(()) |
| 834 | } |
| 835 | |
| 836 | /// Store an immutable session manifest and advance its convenience head. |
| 837 | pub fn save_session_manifest( |