Skip to main content

vgi_forge/
hooks.rs

1//! Lifecycle hooks (§5.8, layer 2).
2//!
3//! The core calls a [`ForgeHooks`] implementation around each operation. A
4//! hook does not act on the forge itself: it returns a decision, and a
5//! [`HookDecision::Modify`] hands the core a changed plan that the core then
6//! runs through the [`crate::Forge`] methods. Keeping every forge write on
7//! that one path is what keeps it audited and retryable — which is also why
8//! the hooks are synchronous: they compute, they do not call out.
9
10use crate::bootstrap::{BootstrapStep, StepOutcome};
11use crate::event::{Drift, ForgeEvent};
12use crate::model::{RepoSpec, RepoState, RoleAssignment};
13use crate::resource::Resource;
14
15/// What a hook wants the core to do.
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum HookDecision<P> {
19    /// Proceed as planned.
20    Continue,
21    /// Proceed with this plan instead.
22    Modify(P),
23    /// Stop; the reason is shown and audited.
24    Abort(String),
25}
26
27/// Optional per-adapter hooks. Every method defaults to
28/// [`HookDecision::Continue`]; an adapter overrides only what its forge does
29/// differently.
30pub trait ForgeHooks: Send + Sync {
31    /// Before a repository is created. `Modify` replaces the spec.
32    fn before_create(&self, _spec: &RepoSpec) -> HookDecision<RepoSpec> {
33        HookDecision::Continue
34    }
35
36    /// After a repository is created. `Modify` adds steps for the core to
37    /// run before the bootstrap plan (Forgejo sets fast-forward-only merges
38    /// here).
39    fn after_create(&self, _state: &RepoState) -> HookDecision<Vec<BootstrapStep>> {
40        HookDecision::Continue
41    }
42
43    /// Before roles are converged. `Modify` replaces the desired set.
44    fn before_apply_roles(
45        &self,
46        _repo: &Resource,
47        _desired: &[RoleAssignment],
48    ) -> HookDecision<Vec<RoleAssignment>> {
49        HookDecision::Continue
50    }
51
52    /// After a bootstrap plan ran. `Modify` adds follow-up steps.
53    fn after_bootstrap(
54        &self,
55        _repo: &Resource,
56        _outcomes: &[(String, StepOutcome)],
57    ) -> HookDecision<Vec<BootstrapStep>> {
58        HookDecision::Continue
59    }
60
61    /// On a verified event. `Modify` replaces it; `Abort` drops it.
62    fn on_event(&self, _event: &ForgeEvent) -> HookDecision<ForgeEvent> {
63        HookDecision::Continue
64    }
65
66    /// On drift found for a repository. `Modify` replaces the list (to
67    /// suppress a forge's known false positives).
68    fn on_drift(&self, _repo: &Resource, _drift: &[Drift]) -> HookDecision<Vec<Drift>> {
69        HookDecision::Continue
70    }
71}
72
73/// Hooks that do nothing.
74#[derive(Debug, Clone, Copy, Default)]
75pub struct NoHooks;
76
77impl ForgeHooks for NoHooks {}