Skip to main content

buildfix_types/
plan.rs

1use crate::ops::{OpKind, OpPreview, OpTarget, SafetyClass};
2use crate::receipt::ToolInfo;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct BuildfixPlan {
7    pub schema: String,
8    pub tool: ToolInfo,
9    pub repo: RepoInfo,
10
11    #[serde(default)]
12    pub inputs: Vec<PlanInput>,
13
14    pub policy: PlanPolicy,
15
16    #[serde(default)]
17    pub preconditions: PlanPreconditions,
18
19    #[serde(default)]
20    pub ops: Vec<PlanOp>,
21
22    pub summary: PlanSummary,
23}
24
25impl BuildfixPlan {
26    pub fn new(tool: ToolInfo, repo: RepoInfo, policy: PlanPolicy) -> Self {
27        Self {
28            schema: crate::schema::BUILDFIX_PLAN_V1.to_string(),
29            tool,
30            repo,
31            inputs: vec![],
32            policy,
33            preconditions: PlanPreconditions::default(),
34            ops: vec![],
35            summary: PlanSummary::default(),
36        }
37    }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct RepoInfo {
42    pub root: String,
43
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub head_sha: Option<String>,
46
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub dirty: Option<bool>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct PlanInput {
53    pub path: String,
54
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub schema: Option<String>,
57
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub tool: Option<String>,
60}
61
62#[derive(Debug, Clone, Default, Serialize, Deserialize)]
63pub struct PlanPolicy {
64    #[serde(default)]
65    pub allow: Vec<String>,
66
67    #[serde(default)]
68    pub deny: Vec<String>,
69
70    #[serde(default)]
71    pub allow_guarded: bool,
72
73    #[serde(default)]
74    pub allow_unsafe: bool,
75
76    #[serde(default)]
77    pub allow_dirty: bool,
78
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub max_ops: Option<u64>,
81
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub max_files: Option<u64>,
84
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub max_patch_bytes: Option<u64>,
87}
88
89#[derive(Debug, Clone, Default, Serialize, Deserialize)]
90pub struct PlanPreconditions {
91    #[serde(default)]
92    pub files: Vec<FilePrecondition>,
93
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub head_sha: Option<String>,
96
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub dirty: Option<bool>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct FilePrecondition {
103    pub path: String,
104    pub sha256: String,
105}
106
107#[derive(Debug, Clone, Default, Serialize, Deserialize)]
108pub struct SafetyCounts {
109    pub safe: u64,
110    pub guarded: u64,
111    #[serde(rename = "unsafe")]
112    pub unsafe_count: u64,
113}
114
115#[derive(Debug, Clone, Default, Serialize, Deserialize)]
116pub struct PlanSummary {
117    pub ops_total: u64,
118    pub ops_blocked: u64,
119    pub files_touched: u64,
120
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub patch_bytes: Option<u64>,
123
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    pub safety_counts: Option<SafetyCounts>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct PlanOp {
130    pub id: String,
131    pub safety: SafetyClass,
132    pub blocked: bool,
133
134    #[serde(default, skip_serializing_if = "Option::is_none")]
135    pub blocked_reason: Option<String>,
136
137    #[serde(default, skip_serializing_if = "Option::is_none")]
138    pub blocked_reason_token: Option<String>,
139
140    pub target: OpTarget,
141    pub kind: OpKind,
142    pub rationale: Rationale,
143
144    #[serde(default, skip_serializing_if = "Vec::is_empty")]
145    pub params_required: Vec<String>,
146
147    #[serde(default, skip_serializing_if = "Option::is_none")]
148    pub preview: Option<OpPreview>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct Rationale {
153    pub fix_key: String,
154
155    #[serde(default, skip_serializing_if = "Option::is_none")]
156    pub description: Option<String>,
157
158    #[serde(default)]
159    pub findings: Vec<FindingRef>,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct FindingRef {
164    pub source: String,
165
166    #[serde(default, skip_serializing_if = "Option::is_none")]
167    pub check_id: Option<String>,
168
169    pub code: String,
170
171    #[serde(default, skip_serializing_if = "Option::is_none")]
172    pub path: Option<String>,
173
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub line: Option<u64>,
176
177    #[serde(default, skip_serializing_if = "Option::is_none")]
178    pub fingerprint: Option<String>,
179}
180
181pub mod blocked_tokens {
182    pub const MISSING_PARAMS: &str = "missing_params";
183    pub const DENYLIST: &str = "denylist";
184    pub const ALLOWLIST_MISSING: &str = "allowlist_missing";
185    pub const MAX_OPS: &str = "max_ops";
186    pub const MAX_FILES: &str = "max_files";
187    pub const MAX_PATCH_BYTES: &str = "max_patch_bytes";
188    pub const DIRTY_WORKING_TREE: &str = "dirty_working_tree";
189    pub const SAFETY_GUARDED_NOT_ALLOWED: &str = "safety_guarded_not_allowed";
190    pub const SAFETY_UNSAFE_NOT_ALLOWED: &str = "safety_unsafe_not_allowed";
191    pub const PRECONDITION_MISMATCH: &str = "precondition_mismatch";
192}