Skip to main content

buildfix_types/
receipt.rs

1use camino::Utf8PathBuf;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4
5/// A generic sensor receipt envelope.
6///
7/// buildfix tries hard to be *tolerant* when reading receipts:
8/// - Unknown fields are ignored.
9/// - Optional fields may be absent.
10///
11/// The director and sensors should enforce stricter schema compliance; buildfix's job is to be useful
12/// with receipts "as found".
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ReceiptEnvelope {
15    /// Schema identifier, e.g. "buildscan.report.v1".
16    pub schema: String,
17
18    pub tool: ToolInfo,
19
20    #[serde(default)]
21    pub run: RunInfo,
22
23    #[serde(default)]
24    pub verdict: Verdict,
25
26    #[serde(default)]
27    pub findings: Vec<Finding>,
28
29    /// Capabilities block for "No Green By Omission" pattern.
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub capabilities: Option<ReceiptCapabilities>,
32
33    /// Optional, tool-specific payload.
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub data: Option<serde_json::Value>,
36}
37
38/// Capabilities block describing what the sensor can/did check.
39#[derive(Debug, Clone, Default, Serialize, Deserialize)]
40pub struct ReceiptCapabilities {
41    /// List of check_ids this sensor can emit.
42    #[serde(default, skip_serializing_if = "Vec::is_empty")]
43    pub check_ids: Vec<String>,
44
45    /// Scopes this sensor covers (e.g., "workspace", "crate").
46    #[serde(default, skip_serializing_if = "Vec::is_empty")]
47    pub scopes: Vec<String>,
48
49    /// True if some inputs could not be processed.
50    #[serde(default)]
51    pub partial: bool,
52
53    /// Reason for partial results, if applicable.
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub reason: Option<String>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ToolInfo {
60    pub name: String,
61
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub version: Option<String>,
64
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub repo: Option<String>,
67
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub commit: Option<String>,
70}
71
72#[derive(Debug, Clone, Default, Serialize, Deserialize)]
73pub struct RunInfo {
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub started_at: Option<DateTime<Utc>>,
76
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub ended_at: Option<DateTime<Utc>>,
79
80    /// Git HEAD SHA at the time this run was created.
81    /// Used to verify the plan is applied to the same repo state it was generated from.
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub git_head_sha: Option<String>,
84}
85
86#[derive(Debug, Clone, Default, Serialize, Deserialize)]
87pub struct Verdict {
88    #[serde(default)]
89    pub status: VerdictStatus,
90
91    #[serde(default)]
92    pub counts: Counts,
93
94    #[serde(default, skip_serializing_if = "Vec::is_empty")]
95    pub reasons: Vec<String>,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
99#[serde(rename_all = "snake_case")]
100pub enum VerdictStatus {
101    Pass,
102    Warn,
103    Fail,
104    #[default]
105    Unknown,
106}
107
108#[derive(Debug, Clone, Default, Serialize, Deserialize)]
109pub struct Counts {
110    #[serde(default)]
111    pub findings: u64,
112
113    #[serde(default)]
114    pub errors: u64,
115
116    #[serde(default)]
117    pub warnings: u64,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct Finding {
122    #[serde(default)]
123    pub severity: Severity,
124
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub check_id: Option<String>,
127
128    #[serde(default, skip_serializing_if = "Option::is_none")]
129    pub code: Option<String>,
130
131    #[serde(default, skip_serializing_if = "Option::is_none")]
132    pub message: Option<String>,
133
134    #[serde(default, skip_serializing_if = "Option::is_none")]
135    pub location: Option<Location>,
136
137    /// A stable key (ideally) for deduplication across runs.
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub fingerprint: Option<String>,
140
141    /// Optional, tool-specific payload.
142    #[serde(default, skip_serializing_if = "Option::is_none")]
143    pub data: Option<serde_json::Value>,
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
147#[serde(rename_all = "snake_case")]
148pub enum Severity {
149    #[default]
150    Info,
151    Warn,
152    Error,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct Location {
157    pub path: Utf8PathBuf,
158
159    #[serde(default, skip_serializing_if = "Option::is_none")]
160    pub line: Option<u64>,
161
162    #[serde(default, skip_serializing_if = "Option::is_none")]
163    pub column: Option<u64>,
164}