release_kit/plan/
evidence.rs1use serde::Serialize;
10
11use crate::digest::Digest;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
15#[serde(rename_all = "kebab-case")]
16pub enum EvidenceKind {
17 Record,
19 Configuration,
21 Destination,
23 Repository,
25 Bundle,
27 Host,
29 Pin,
31 Forge,
33}
34
35#[derive(Debug, Clone, Serialize)]
37pub struct EvidenceItem {
38 pub id: String,
40 pub kind: EvidenceKind,
42 pub producer: String,
45 pub observed_at: String,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub sha256: Option<Digest>,
50 pub method: String,
52}
53
54#[derive(Debug, Default)]
57pub struct Ledger {
58 items: Vec<EvidenceItem>,
59}
60
61impl Ledger {
62 #[must_use]
64 pub const fn new() -> Self {
65 Self { items: Vec::new() }
66 }
67
68 pub fn observe(
71 &mut self,
72 id: impl Into<String>,
73 kind: EvidenceKind,
74 producer: impl Into<String>,
75 observed_at: &str,
76 sha256: Option<Digest>,
77 method: impl Into<String>,
78 ) -> String {
79 let id = id.into();
80 debug_assert!(
81 !self.items.iter().any(|item| item.id == id),
82 "evidence id {id} is already in the ledger"
83 );
84 self.items.push(EvidenceItem {
85 id: id.clone(),
86 kind,
87 producer: producer.into(),
88 observed_at: observed_at.to_owned(),
89 sha256,
90 method: method.into(),
91 });
92 id
93 }
94
95 #[must_use]
97 pub fn into_items(self) -> Vec<EvidenceItem> {
98 self.items
99 }
100
101 #[must_use]
103 pub fn has(&self, id: &str) -> bool {
104 self.items.iter().any(|item| item.id == id)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::{EvidenceKind, Ledger};
111 use crate::digest::Digest;
112
113 #[test]
114 fn an_observation_answers_the_id_a_field_cites() {
115 let mut ledger = Ledger::new();
116 let id = ledger.observe(
117 "record",
118 EvidenceKind::Record,
119 "rk",
120 "2026-01-01T00:00:00Z",
121 Some(Digest::of(b"{}")),
122 "read .release-kit/manifest.json",
123 );
124 assert_eq!(id, "record");
125 assert!(ledger.has("record"));
126 let items = ledger.into_items();
127 assert_eq!(items.len(), 1);
128 assert_eq!(
129 serde_json::to_string(&items[0]).expect("serializes"),
130 format!(
131 r#"{{"id":"record","kind":"record","producer":"rk","observed_at":"2026-01-01T00:00:00Z","sha256":"{}","method":"read .release-kit/manifest.json"}}"#,
132 Digest::of(b"{}")
133 )
134 );
135 }
136}