1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Mutates {
9 Nothing,
11 Forge,
13 Local,
15}
16
17#[derive(Debug)]
19pub struct StepSpec {
20 pub name: &'static str,
23 pub chapter: &'static str,
25 pub mutates: Mutates,
27 pub proves: &'static str,
29 pub destructive: bool,
32 pub optional: bool,
35 pub prereqs: &'static [&'static str],
37}
38
39pub const STEPS: [StepSpec; 15] = [
47 StepSpec {
48 name: "package-check",
49 chapter: "§0",
50 mutates: Mutates::Nothing,
51 proves: "the package is publishable with no credentials",
52 destructive: false,
53 optional: false,
54 prereqs: &[],
55 },
56 StepSpec {
57 name: "default-branch",
58 chapter: "§1",
59 mutates: Mutates::Forge,
60 proves: "the trunk is the default branch",
61 destructive: false,
62 optional: false,
63 prereqs: &[],
64 },
65 StepSpec {
66 name: "single-trunk",
67 chapter: "§1",
68 mutates: Mutates::Forge,
69 proves: "no long-lived branch besides the trunk remains",
70 destructive: true,
71 optional: false,
72 prereqs: &["default-branch"],
73 },
74 StepSpec {
75 name: "merge-cleanup",
76 chapter: "§1",
77 mutates: Mutates::Forge,
78 proves: "the forge deletes a branch when its merge lands",
79 destructive: false,
80 optional: false,
81 prereqs: &["default-branch"],
82 },
83 StepSpec {
84 name: "branch-reminder",
85 chapter: "§1",
86 mutates: Mutates::Local,
87 proves: "a pull reminds the operator when a merged branch lingers locally",
88 destructive: false,
89 optional: false,
90 prereqs: &[],
91 },
92 StepSpec {
93 name: "ci-permissions",
94 chapter: "§2",
95 mutates: Mutates::Forge,
96 proves: "CI may write and open requests",
97 destructive: false,
98 optional: false,
99 prereqs: &[],
100 },
101 StepSpec {
102 name: "install-bot",
103 chapter: "§2",
104 mutates: Mutates::Forge,
105 proves: "the bot identity can act on this project",
106 destructive: false,
107 optional: false,
108 prereqs: &[],
109 },
110 StepSpec {
111 name: "bot-secrets",
112 chapter: "§2",
113 mutates: Mutates::Forge,
114 proves: "the bot credentials are stored on the project",
115 destructive: false,
116 optional: false,
117 prereqs: &[],
118 },
119 StepSpec {
120 name: "forge-version",
121 chapter: "§3",
122 mutates: Mutates::Nothing,
123 proves: "the forge meets the convention's minimum version",
124 destructive: false,
125 optional: false,
126 prereqs: &[],
127 },
128 StepSpec {
129 name: "protect-trunk",
130 chapter: "§3",
131 mutates: Mutates::Forge,
132 proves: "the trunk takes no direct push, merges only by squash with the request's title and body as the message, and requires the named check",
133 destructive: false,
134 optional: false,
135 prereqs: &["default-branch", "forge-version"],
136 },
137 StepSpec {
138 name: "protect-tags",
139 chapter: "§3",
140 mutates: Mutates::Forge,
141 proves: "v* is protected as far as the forge allows",
142 destructive: false,
143 optional: false,
144 prereqs: &[],
145 },
146 StepSpec {
147 name: "protect-release-lines",
148 chapter: "§3",
149 mutates: Mutates::Forge,
150 proves: "release/* cannot be force-pushed or deleted",
151 destructive: false,
152 optional: true,
153 prereqs: &[],
154 },
155 StepSpec {
156 name: "auto-merge",
157 chapter: "§3",
158 mutates: Mutates::Forge,
159 proves: "a request may merge itself once its required checks pass",
160 destructive: false,
161 optional: false,
162 prereqs: &["default-branch"],
163 },
164 StepSpec {
165 name: "protections-check",
166 chapter: "§3",
167 mutates: Mutates::Nothing,
168 proves: "exactly the owned protections, with those rules",
169 destructive: false,
170 optional: false,
171 prereqs: &[],
172 },
173 StepSpec {
174 name: "private-vulnerability-reporting",
175 chapter: "§3",
176 mutates: Mutates::Forge,
177 proves: "a vulnerability report has a private intake path, with the forge's limits named",
178 destructive: false,
179 optional: false,
180 prereqs: &[],
181 },
182];
183
184#[must_use]
186pub fn spec(name: &str) -> Option<&'static StepSpec> {
187 STEPS.iter().find(|step| step.name == name)
188}
189
190#[cfg(test)]
191mod tests {
192 use super::{STEPS, spec};
193
194 #[test]
197 fn every_prereq_is_an_earlier_step() {
198 for (idx, step) in STEPS.iter().enumerate() {
199 for prereq in step.prereqs {
200 let position = STEPS
201 .iter()
202 .position(|other| other.name == *prereq)
203 .unwrap_or(usize::MAX);
204 assert!(
205 position < idx,
206 "{}: prereq {prereq} is not an earlier step",
207 step.name
208 );
209 }
210 }
211 let reporting = &STEPS[STEPS.len() - 1];
212 assert_eq!(reporting.name, "private-vulnerability-reporting");
213 assert_eq!(reporting.chapter, "§3");
214 assert_eq!(reporting.mutates, super::Mutates::Forge);
215 assert!(!reporting.optional && !reporting.destructive);
216 assert!(reporting.prereqs.is_empty());
217 assert_eq!(STEPS[STEPS.len() - 2].name, "protections-check");
218 assert!(spec("package-check").is_some());
219 assert!(spec("no-such-step").is_none());
220 }
221}