Skip to main content

release_kit/setup/
steps.rs

1//! The ordered step table: one step per executable item in
2//! `method/02-setup.md`, in the chapter's order, each defined by what it
3//! proves rather than by how a forge achieves it.
4
5/// What a step touches when it applies.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Mutates {
8    /// The step only reads and reports.
9    Nothing,
10    /// The step writes forge configuration.
11    Forge,
12    /// The step writes local repository state in the target.
13    Local,
14}
15
16/// One step of the setup, in canonical order.
17#[derive(Debug)]
18pub struct StepSpec {
19    /// The name, which is also the `rk setup step` argument and the script
20    /// file name in every forge tree.
21    pub name: &'static str,
22    /// The `method/02-setup.md` section the step executes.
23    pub chapter: &'static str,
24    /// What the step touches under apply.
25    pub mutates: Mutates,
26    /// What the step proves, from the chapter.
27    pub proves: &'static str,
28    /// Whether the step deletes anything; a destructive step carries its own
29    /// refusal beyond `--apply`.
30    pub destructive: bool,
31    /// Whether a full run skips the step: an optional step applies only
32    /// where its condition holds, by name, through `rk setup step`.
33    pub optional: bool,
34    /// Steps that must be observed satisfied before this one applies.
35    pub prereqs: &'static [&'static str],
36}
37
38/// The twelve steps, in the chapter's order.
39///
40/// `package-check` and `branch-reminder` belong to no forge tree — one
41/// reads its command from the technology binding, the other writes an
42/// embedded hook body into the target's own git directory — which makes
43/// them the two steps outside the parity rule.
44pub 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/// Look one step up by name.
156#[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    /// Every prerequisite names a step that exists and comes earlier, so the
166    /// full run can never be refused by its own table.
167    #[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}