Skip to main content

anodizer_core/context/
state.rs

1use super::*;
2
3/// Stage→stage handoff state produced by stages and consumed by later
4/// stages (as opposed to `config` / `options` which are pipeline inputs,
5/// or `artifacts` which has its own registry). The changelog stage
6/// writes here, the release stage reads here.
7#[derive(Debug, Default)]
8pub struct StageOutputs {
9    /// Set by the changelog stage when `use: github-native` is configured.
10    /// The release stage reads this to set `generate_release_notes(true)`
11    /// on the GitHub API.
12    pub github_native_changelog: bool,
13    /// Per-crate rendered changelog body, keyed by crate name.
14    pub changelogs: HashMap<String, String>,
15    /// Rendered `changelog.header` value, populated by the changelog stage.
16    /// The release stage uses it as a fallback when `release.header` is
17    /// unset so YAML-configured changelog headers reach the GitHub release
18    /// body (the release-header content-loading behaviour).
19    pub changelog_header: Option<String>,
20    /// Rendered `changelog.footer` value, populated by the changelog stage.
21    /// Same fallback semantics as `changelog_header`.
22    pub changelog_footer: Option<String>,
23    /// Per-publisher post-publish polling results, written by the publish
24    /// stage's chocolatey / winget polling fan-out and consumed by the
25    /// release-summary renderer. Stored as opaque JSON to keep core free
26    /// of stage-publish types (the `PostPublishResult` type lives in
27    /// `anodizer-stage-publish::post_publish::status` and serializes
28    /// stably). Empty when polling was disabled or no eligible
29    /// publishers ran.
30    pub post_publish_results: Vec<serde_json::Value>,
31}
32
33/// Callback that re-runs release-content verification against the already
34/// published reversible surface and reports whether it passed. Stored on
35/// [`Context`] so the publish dispatcher can gate one-way-door publishers on
36/// a fresh verify without `stage-publish` depending on `stage-verify-release`.
37/// `Arc` so it can be cheaply cloned out of `&mut Context` before invocation.
38pub type VerifyGate = std::sync::Arc<dyn Fn(&mut Context) -> anyhow::Result<bool> + Send + Sync>;
39
40impl Context {
41    /// Publisher-facing override: when `Publisher::run` returns `Ok`
42    /// but the terminal outcome is something other than `Succeeded`
43    /// (chocolatey moderation skip, winget/krew/homebrew
44    /// PR-already-exists skip, …) call this before returning so
45    /// dispatch records the correct `PublisherOutcome` on the report.
46    /// Without this, dispatch defaults to `Succeeded` on any Ok and
47    /// the summary table silently misreports the skip as success.
48    pub fn record_publisher_outcome(&mut self, outcome: crate::PublisherOutcome) {
49        self.pending_outcome = Some(outcome);
50    }
51
52    /// Dispatch-side consumer: take the pending outcome override (if
53    /// any) recorded by the publisher's `run`. Single-shot — the slot
54    /// is empty after this call.
55    pub fn take_pending_outcome(&mut self) -> Option<crate::PublisherOutcome> {
56        self.pending_outcome.take()
57    }
58
59    /// Publisher-side recorder: stash the partial evidence accumulated
60    /// before a failing `run` returns `Err`, so dispatch can attach it to
61    /// the failed report row and rollback has the authoritative record of
62    /// what went live. See [`Context::pending_evidence`].
63    pub fn record_pending_evidence(&mut self, evidence: crate::PublishEvidence) {
64        self.pending_evidence = Some(evidence);
65    }
66
67    /// Dispatch-side consumer: take the partial evidence (if any) a
68    /// publisher recorded before failing. Single-shot — empty after this
69    /// call.
70    pub fn take_pending_evidence(&mut self) -> Option<crate::PublishEvidence> {
71        self.pending_evidence.take()
72    }
73
74    /// Borrow the publisher dispatch report set by `PublishStage::run`,
75    /// or `None` if the publish stage hasn't run yet (or was skipped).
76    pub fn publish_report(&self) -> Option<&PublishReport> {
77        self.publish_report.as_ref()
78    }
79
80    /// Whether the publish stage entered its body this run (even if it
81    /// aborted before dispatching any publisher).
82    pub fn publish_attempted(&self) -> bool {
83        self.publish_attempted
84    }
85
86    /// Record that the publish stage entered its body. Called by
87    /// `PublishStage::run` ahead of its pre-dispatch guards so guard
88    /// aborts are distinguishable from a skipped stage.
89    pub fn set_publish_attempted(&mut self) {
90        self.publish_attempted = true;
91    }
92
93    /// Store the publisher dispatch report. Overwrites any prior value.
94    ///
95    /// Written by the publish stage during a normal release run; rehydrated by
96    /// `--announce-only` from the on-disk `<dist>/run-<id>/report.json` so the
97    /// announce stage sees an equivalent context without re-publishing.
98    pub fn set_publish_report(&mut self, r: PublishReport) {
99        self.publish_report = Some(r);
100    }
101
102    /// Borrow the set of crate names the build stage actually built, or
103    /// `None` if the build stage has not run in this pipeline (merge mode).
104    pub fn built_crate_names(&self) -> Option<&std::collections::HashSet<String>> {
105        self.built_crate_names.as_ref()
106    }
107
108    /// Record the distinct crate names that received at least one in-scope
109    /// build job. Called once by the build stage after job planning.
110    pub fn set_built_crate_names(&mut self, names: std::collections::HashSet<String>) {
111        self.built_crate_names = Some(names);
112    }
113
114    /// Record a working-tree path anodizer itself wrote during this run
115    /// (repo-relative, `/`-separated). Called by the writers of expected
116    /// in-run mutations (the binstall metadata emitter) so cleanliness
117    /// guards can distinguish the tool's own residue from operator drift.
118    pub fn record_tree_mutation(&mut self, rel_path: impl Into<String>) {
119        self.tree_mutations.insert(rel_path.into());
120    }
121
122    /// Repo-relative paths anodizer itself wrote this run (see
123    /// [`Context::record_tree_mutation`]).
124    pub fn tree_mutations(&self) -> &std::collections::BTreeSet<String> {
125        &self.tree_mutations
126    }
127
128    /// Record an intentional skip from a per-sub-config loop
129    /// (`signs`, `docker_signs`, `publishers`, …). `stage` identifies the
130    /// owning stage, `label` identifies the sub-config (id / name / index),
131    /// `reason` is short user-facing text. Duplicate (stage, label, reason)
132    /// tuples are dropped on insert so a per-artifact inner loop cannot emit
133    /// N copies of the same skip message.
134    pub fn remember_skip(&self, stage: &str, label: &str, reason: &str) {
135        self.skip_memento.remember(stage, label, reason);
136    }
137}