1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Mutates {
8 Nothing,
10 Forge,
12 Local,
14}
15
16#[derive(Debug)]
18pub struct StepSpec {
19 pub name: &'static str,
22 pub chapter: &'static str,
24 pub mutates: Mutates,
26 pub proves: &'static str,
28 pub destructive: bool,
31 pub optional: bool,
34 pub prereqs: &'static [&'static str],
36}
37
38pub const STEPS: [StepSpec; 12] = [
45 StepSpec {
46 name: "package-check",
47 chapter: "§0",
48 mutates: Mutates::Nothing,
49 proves: "the package is publishable with no credentials",
50 destructive: false,
51 optional: false,
52 prereqs: &[],
53 },
54 StepSpec {
55 name: "default-branch",
56 chapter: "§1",
57 mutates: Mutates::Forge,
58 proves: "the trunk is the default branch",
59 destructive: false,
60 optional: false,
61 prereqs: &[],
62 },
63 StepSpec {
64 name: "single-trunk",
65 chapter: "§1",
66 mutates: Mutates::Forge,
67 proves: "no long-lived branch besides the trunk remains",
68 destructive: true,
69 optional: false,
70 prereqs: &["default-branch"],
71 },
72 StepSpec {
73 name: "merge-cleanup",
74 chapter: "§1",
75 mutates: Mutates::Forge,
76 proves: "the forge deletes a branch when its merge lands",
77 destructive: false,
78 optional: false,
79 prereqs: &["default-branch"],
80 },
81 StepSpec {
82 name: "branch-reminder",
83 chapter: "§1",
84 mutates: Mutates::Local,
85 proves: "a pull reminds the operator when a merged branch lingers locally",
86 destructive: false,
87 optional: false,
88 prereqs: &[],
89 },
90 StepSpec {
91 name: "ci-permissions",
92 chapter: "§2",
93 mutates: Mutates::Forge,
94 proves: "CI may write and open requests",
95 destructive: false,
96 optional: false,
97 prereqs: &[],
98 },
99 StepSpec {
100 name: "install-bot",
101 chapter: "§2",
102 mutates: Mutates::Forge,
103 proves: "the bot identity can act on this project",
104 destructive: false,
105 optional: false,
106 prereqs: &[],
107 },
108 StepSpec {
109 name: "bot-secrets",
110 chapter: "§2",
111 mutates: Mutates::Forge,
112 proves: "the bot credentials are stored on the project",
113 destructive: false,
114 optional: false,
115 prereqs: &[],
116 },
117 StepSpec {
118 name: "protect-trunk",
119 chapter: "§3",
120 mutates: Mutates::Forge,
121 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",
122 destructive: false,
123 optional: false,
124 prereqs: &["default-branch"],
125 },
126 StepSpec {
127 name: "protect-tags",
128 chapter: "§3",
129 mutates: Mutates::Forge,
130 proves: "v* is protected as far as the forge allows",
131 destructive: false,
132 optional: false,
133 prereqs: &[],
134 },
135 StepSpec {
136 name: "protect-release-lines",
137 chapter: "§3",
138 mutates: Mutates::Forge,
139 proves: "release/* cannot be force-pushed or deleted",
140 destructive: false,
141 optional: true,
142 prereqs: &[],
143 },
144 StepSpec {
145 name: "protections-check",
146 chapter: "§3",
147 mutates: Mutates::Nothing,
148 proves: "exactly the owned protections, with those rules",
149 destructive: false,
150 optional: false,
151 prereqs: &[],
152 },
153];
154
155#[must_use]
157pub fn spec(name: &str) -> Option<&'static StepSpec> {
158 STEPS.iter().find(|step| step.name == name)
159}
160
161#[cfg(test)]
162mod tests {
163 use super::{STEPS, spec};
164
165 #[test]
168 fn every_prereq_is_an_earlier_step() {
169 for (idx, step) in STEPS.iter().enumerate() {
170 for prereq in step.prereqs {
171 let position = STEPS
172 .iter()
173 .position(|other| other.name == *prereq)
174 .unwrap_or(usize::MAX);
175 assert!(
176 position < idx,
177 "{}: prereq {prereq} is not an earlier step",
178 step.name
179 );
180 }
181 }
182 assert!(spec("package-check").is_some());
183 assert!(spec("no-such-step").is_none());
184 }
185}