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; 13] = [
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: "auto-merge",
146 chapter: "§3",
147 mutates: Mutates::Forge,
148 proves: "a request may merge itself once its required checks pass",
149 destructive: false,
150 optional: false,
151 prereqs: &["default-branch"],
152 },
153 StepSpec {
154 name: "protections-check",
155 chapter: "§3",
156 mutates: Mutates::Nothing,
157 proves: "exactly the owned protections, with those rules",
158 destructive: false,
159 optional: false,
160 prereqs: &[],
161 },
162];
163
164#[must_use]
166pub fn spec(name: &str) -> Option<&'static StepSpec> {
167 STEPS.iter().find(|step| step.name == name)
168}
169
170#[cfg(test)]
171mod tests {
172 use super::{STEPS, spec};
173
174 #[test]
177 fn every_prereq_is_an_earlier_step() {
178 for (idx, step) in STEPS.iter().enumerate() {
179 for prereq in step.prereqs {
180 let position = STEPS
181 .iter()
182 .position(|other| other.name == *prereq)
183 .unwrap_or(usize::MAX);
184 assert!(
185 position < idx,
186 "{}: prereq {prereq} is not an earlier step",
187 step.name
188 );
189 }
190 }
191 assert!(spec("package-check").is_some());
192 assert!(spec("no-such-step").is_none());
193 }
194}