sim_lib_doc_core/
evidence.rs1use crate::{DocId, ExternalRef};
4
5#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
7pub struct Evidence {
8 pub subject: DocId,
10 pub evidence: ExternalRef,
12 pub role: LinkRole,
14 pub captured_at_seq: u64,
16 pub immutable_hint: Option<String>,
18}
19
20impl Evidence {
21 #[must_use]
23 pub fn new(
24 subject: DocId,
25 evidence: ExternalRef,
26 role: LinkRole,
27 captured_at_seq: u64,
28 immutable_hint: Option<String>,
29 ) -> Self {
30 Self {
31 subject,
32 evidence,
33 role,
34 captured_at_seq,
35 immutable_hint,
36 }
37 }
38
39 #[must_use]
41 pub fn predicate(&self) -> &'static str {
42 self.role.predicate()
43 }
44}
45
46#[derive(
48 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
49)]
50pub enum LinkRole {
51 SourceDocument,
53 AccountingSupport,
55 ScheduleReference,
57 ProjectIssue,
59 PublishedTo,
61}
62
63impl LinkRole {
64 #[must_use]
66 pub fn predicate(self) -> &'static str {
67 match self {
68 Self::SourceDocument => "office/source-document",
69 Self::AccountingSupport => "office/accounting-support",
70 Self::ScheduleReference => "office/schedule-reference",
71 Self::ProjectIssue => "office/project-issue",
72 Self::PublishedTo => "office/published-to",
73 }
74 }
75
76 #[must_use]
78 pub fn from_predicate(predicate: &str) -> Option<Self> {
79 match predicate {
80 "office/source-document" => Some(Self::SourceDocument),
81 "office/accounting-support" => Some(Self::AccountingSupport),
82 "office/schedule-reference" => Some(Self::ScheduleReference),
83 "office/project-issue" => Some(Self::ProjectIssue),
84 "office/published-to" => Some(Self::PublishedTo),
85 _ => None,
86 }
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn evidence_json_round_trips_reference_only_fields() {
96 let evidence = Evidence::new(
97 DocId::new("task-17"),
98 ExternalRef::new(
99 "site/msgraph",
100 "messages/msg-1",
101 Some("etag-1".to_owned()),
102 Some("https://graph.example/messages/msg-1".to_owned()),
103 ),
104 LinkRole::SourceDocument,
105 9,
106 Some("sha256:abc".to_owned()),
107 );
108
109 let encoded = serde_json::to_string(&evidence).unwrap();
110 let decoded: Evidence = serde_json::from_str(&encoded).unwrap();
111
112 assert_eq!(decoded, evidence);
113 assert_eq!(decoded.predicate(), "office/source-document");
114 }
115
116 #[test]
117 fn ledger_voucher_uses_plain_external_ref() {
118 let evidence = Evidence::new(
119 DocId::new("annual-account-2026"),
120 ExternalRef::new("ledger", "voucher/2026/0007", None, None),
121 LinkRole::AccountingSupport,
122 42,
123 Some("voucher-digest".to_owned()),
124 );
125
126 assert_eq!(evidence.evidence.backend, "ledger");
127 assert_eq!(evidence.predicate(), "office/accounting-support");
128 }
129}