Skip to main content

tokmd_types/
evidence_packet.rs

1//! Evidence packet manifest DTOs.
2//!
3//! These types model the `sensors/tokmd/manifest.json` contract used by
4//! high-risk review sensors. They intentionally index existing receipts rather
5//! than replacing them.
6
7use serde::{Deserialize, Serialize};
8
9/// Stable schema identifier for evidence packet manifests.
10pub const EVIDENCE_PACKET_SCHEMA: &str = "tokmd.evidence-packet/v1";
11
12/// Manifest for a scoped evidence packet directory.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct EvidencePacketManifest {
15    pub schema: String,
16    pub tokmd_version: String,
17    pub preset: String,
18    pub base: String,
19    pub head: String,
20    pub paths: Vec<String>,
21    pub status: EvidencePacketStatus,
22    pub artifacts: EvidencePacketArtifacts,
23    #[serde(default, skip_serializing_if = "Vec::is_empty")]
24    pub review_priority: Vec<EvidencePacketReviewPriorityItem>,
25    pub warnings: Vec<String>,
26    pub errors: Vec<String>,
27    pub non_claims: Vec<String>,
28    pub reproduce: Vec<String>,
29}
30
31/// Packet artifact paths.
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct EvidencePacketArtifacts {
34    pub analyze_md: String,
35    pub analyze_json: String,
36    pub context_md: String,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub syntax_json: Option<String>,
39}
40
41/// Evidence packet status.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "snake_case")]
44pub enum EvidencePacketStatus {
45    Complete,
46    Partial,
47    Failed,
48}
49
50/// Advisory first-read item derived from packet artifacts.
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52pub struct EvidencePacketReviewPriorityItem {
53    pub rank: u32,
54    pub path: String,
55    pub category: String,
56    pub severity: String,
57    pub score: u32,
58    pub kind: String,
59    pub reason: String,
60    pub evidence: String,
61    pub refs: Vec<String>,
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn evidence_packet_status_uses_contract_spelling() {
70        assert_eq!(
71            serde_json::to_string(&EvidencePacketStatus::Complete).unwrap(),
72            "\"complete\""
73        );
74        assert_eq!(
75            serde_json::to_string(&EvidencePacketStatus::Partial).unwrap(),
76            "\"partial\""
77        );
78        assert_eq!(
79            serde_json::to_string(&EvidencePacketStatus::Failed).unwrap(),
80            "\"failed\""
81        );
82    }
83
84    #[test]
85    fn evidence_packet_manifest_roundtrips() {
86        let manifest = EvidencePacketManifest {
87            schema: EVIDENCE_PACKET_SCHEMA.to_string(),
88            tokmd_version: "1.12.0".to_string(),
89            preset: "bun-ub".to_string(),
90            base: "origin/main".to_string(),
91            head: "HEAD".to_string(),
92            paths: vec!["src/runtime/api".to_string()],
93            status: EvidencePacketStatus::Complete,
94            artifacts: EvidencePacketArtifacts {
95                analyze_md: "sensors/tokmd/analyze.md".to_string(),
96                analyze_json: "sensors/tokmd/analyze.json".to_string(),
97                context_md: "sensors/tokmd/context.md".to_string(),
98                syntax_json: Some("sensors/tokmd/syntax.json".to_string()),
99            },
100            review_priority: vec![],
101            warnings: vec![],
102            errors: vec![],
103            non_claims: vec![
104                "bun-ub packages review evidence; it does not prove UB exists or is absent"
105                    .to_string(),
106            ],
107            reproduce: vec![
108                "tokmd analyze --preset bun-ub --format json --effort-base-ref origin/main --effort-head-ref HEAD --no-progress src/runtime/api > sensors/tokmd/analyze.json"
109                    .to_string(),
110            ],
111        };
112
113        let json = serde_json::to_string(&manifest).unwrap();
114        let roundtrip: EvidencePacketManifest = serde_json::from_str(&json).unwrap();
115        assert_eq!(roundtrip, manifest);
116    }
117
118    #[test]
119    fn evidence_packet_manifest_defaults_missing_review_priority() {
120        let json = serde_json::json!({
121            "schema": EVIDENCE_PACKET_SCHEMA,
122            "tokmd_version": "1.12.0",
123            "preset": "bun-ub",
124            "base": "origin/main",
125            "head": "HEAD",
126            "paths": ["src/runtime/api"],
127            "status": "complete",
128            "artifacts": {
129                "analyze_md": "sensors/tokmd/analyze.md",
130                "analyze_json": "sensors/tokmd/analyze.json",
131                "context_md": "sensors/tokmd/context.md"
132            },
133            "warnings": [],
134            "errors": [],
135            "non_claims": [],
136            "reproduce": []
137        });
138
139        let manifest: EvidencePacketManifest = serde_json::from_value(json).unwrap();
140        assert!(manifest.review_priority.is_empty());
141    }
142
143    #[test]
144    fn evidence_packet_canonical_layout_matches_spec() {
145        // Keep aligned with docs/specs/evidence-packet-workflow.md.
146        const PACKET_DIR: &str = "sensors/tokmd";
147        const MANIFEST: &str = "sensors/tokmd/manifest.json";
148        const REQUIRED: &[&str] = &["analyze.md", "analyze.json", "context.md", "manifest.json"];
149        const OPTIONAL_SYNTAX: &str = "syntax.json";
150
151        assert_eq!(MANIFEST, "sensors/tokmd/manifest.json");
152        assert!(REQUIRED.contains(&"analyze.md"));
153        assert!(REQUIRED.contains(&"analyze.json"));
154        assert!(REQUIRED.contains(&"context.md"));
155        assert!(REQUIRED.contains(&"manifest.json"));
156        assert_eq!(OPTIONAL_SYNTAX, "syntax.json");
157        assert!(
158            MANIFEST.starts_with(PACKET_DIR),
159            "manifest path should live under the canonical packet directory"
160        );
161    }
162
163    #[test]
164    fn evidence_packet_review_priority_item_serializes() {
165        let item = EvidencePacketReviewPriorityItem {
166            rank: 1,
167            path: "src/runtime/api/MarkdownObject.rs".to_string(),
168            category: "panic_seam".to_string(),
169            severity: "high".to_string(),
170            score: 95,
171            kind: "expect_call".to_string(),
172            reason: "panic-like seam".to_string(),
173            evidence: "expect".to_string(),
174            refs: vec!["sensors/tokmd/syntax.json#/receipts/0/review_signals/1".to_string()],
175        };
176
177        let json = serde_json::to_value(&item).unwrap();
178        assert_eq!(json["rank"], 1);
179        assert_eq!(json["category"], "panic_seam");
180        assert_eq!(
181            json["refs"][0],
182            "sensors/tokmd/syntax.json#/receipts/0/review_signals/1"
183        );
184    }
185}