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}