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 {}