Skip to main content

artifactize_core/monitor/
evidence.rs

1//! Saved non-Agent eval detail. Agent files belong to the incremental typed session reader.
2use crate::{
3    agent::session,
4    config::ProfileKind,
5    store::{self, RequestView},
6    types::RequestStatus,
7};
8
9use std::path::Path;
10
11/// The session facts of a request: when they change, its evidence (an Agent session) is
12/// resolved again; while they hold, an open reader keeps reading.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct EvidenceStamp {
15    status: RequestStatus,
16    completed_at: Option<crate::types::Timestamp>,
17    session: Option<session::SessionRef>,
18    session_id: Option<crate::types::SessionId>,
19    producer: Option<store::Producer>,
20    original: Option<crate::types::RequestId>,
21}
22impl EvidenceStamp {
23    pub fn new(view: &RequestView) -> Self {
24        Self {
25            status: view.request.status(),
26            completed_at: view.request.completed_at(),
27            session: view.request.session.clone(),
28            session_id: view.request.session_id.clone(),
29            producer: view.request.producer.clone(),
30            original: view
31                .execution
32                .as_ref()
33                .map(|execution| execution.provenance.request_id.clone())
34                .or_else(|| {
35                    view.request
36                        .provenance
37                        .as_ref()
38                        .map(|source| source.request_id.clone())
39                }),
40        }
41    }
42}
43
44#[derive(Debug, Clone, Default)]
45pub struct Evidence {
46    pub title: String,
47    pub text: String,
48}
49impl Evidence {
50    /// Human evals have no evidence beside their sections.
51    pub fn is_empty(&self) -> bool {
52        self.title.is_empty() && self.text.is_empty()
53    }
54}
55
56pub async fn original(state: &Path, view: &RequestView) -> Result<RequestView, String> {
57    store::read_original(state, view).await
58}
59
60pub fn evidence(_state: &Path, view: &RequestView) -> Evidence {
61    match view.request.profile.kind() {
62        ProfileKind::Agent => Evidence {
63            title: "Agent session".into(),
64            text: "Loading session…".into(),
65        },
66        ProfileKind::Runtime => {
67            let logs = view
68                .request
69                .result()
70                .cloned()
71                .and_then(|result| match result {
72                    store::ExecutionResult::Runtime(logs)
73                        if logs.stdout.value().is_some() && logs.stderr.value().is_some() =>
74                    {
75                        Some(logs)
76                    }
77                    _ => None,
78                });
79            let text = match logs {
80                Some(logs) => format!(
81                    "exit code: {}\ncapture truncated: {}\n\nstdout\n{}\n\nstderr\n{}",
82                    logs.exit_code.value()
83                        .map_or("unreported".into(), |code| code.to_string()),
84                    logs.truncated.value().copied().unwrap_or(false),
85                    logs.stdout.value().expect("filtered logs"),
86                    logs.stderr.value().expect("filtered logs")
87                ),
88                None if view.request.origin.is_some() => {
89                    "Logs unavailable: this remote result contains only a summary; stdout/stderr were not saved here."
90                        .into()
91                }
92                None if view.request.status() == RequestStatus::Running => {
93                    "Logs unavailable while running: only completed runtime output is saved; this view does not stream live pipes."
94                        .into()
95                }
96                None => {
97                    "Logs unavailable: stdout/stderr were not saved for this request (for example timeout, cancellation, operational failure, or a summary-only reused result)."
98                        .into()
99                }
100            };
101            Evidence {
102                title: "Saved runtime stdout / stderr".into(),
103                text,
104            }
105        }
106        ProfileKind::Human => Evidence::default(),
107    }
108}