Skip to main content

lean_ctx/core/web/
citation.rs

1//! Evidence / citation metadata attached to every fetched document.
2//!
3//! Research agents must be able to attribute claims to a source. Every
4//! [`crate::core::web`] read carries a [`Citation`] (URL, title, site, fetch
5//! timestamp) that is rendered as a compact footer so downstream reasoning — and
6//! the human reading the answer — can trace each fact back to where it came
7//! from.
8
9use super::url_guard;
10
11/// Source attribution for a fetched document or quote.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Citation {
14    pub url: String,
15    pub title: Option<String>,
16    pub site: Option<String>,
17    /// RFC 3339 UTC timestamp of when the content was retrieved.
18    pub fetched_at: String,
19}
20
21impl Citation {
22    pub fn new(url: &str, title: Option<String>) -> Self {
23        Self {
24            url: url.to_string(),
25            title,
26            site: url_guard::validate(url).ok().map(|u| u.host),
27            fetched_at: chrono::Utc::now().to_rfc3339(),
28        }
29    }
30
31    /// Render a compact, machine-and-human readable citation footer.
32    pub fn footer(&self) -> String {
33        let headline = match &self.title {
34            Some(t) if !t.is_empty() => format!("{t} — {}", self.url),
35            _ => self.url.clone(),
36        };
37        let site = self.site.as_deref().unwrap_or("unknown");
38        format!(
39            "\n\n---\nSource: {headline}\nSite: {site} · Retrieved: {}",
40            self.fetched_at
41        )
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn derives_site_from_url() {
51        let c = Citation::new("https://en.wikipedia.org/wiki/Rust", Some("Rust".into()));
52        assert_eq!(c.site.as_deref(), Some("en.wikipedia.org"));
53    }
54
55    #[test]
56    fn footer_contains_source_and_retrieval() {
57        let c = Citation::new("https://x.com/a", Some("Title".into()));
58        let footer = c.footer();
59        assert!(footer.contains("Source: Title — https://x.com/a"));
60        assert!(footer.contains("Site: x.com"));
61        assert!(footer.contains("Retrieved:"));
62    }
63
64    #[test]
65    fn footer_without_title_uses_url() {
66        let c = Citation::new("https://x.com/a", None);
67        assert!(c.footer().contains("Source: https://x.com/a"));
68    }
69}