lean_ctx_workgraph/outcome/
signals.rs1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum SignalType {
11 BuildSuccess,
12 TestsPassing,
13 LintClean,
14 TypecheckPassing,
15 AgentCompletion,
16 RetryCount,
17 HumanAcceptance,
18 PrMerge,
19 CiPassing,
20 Correction,
21 Rollback,
22}
23
24impl SignalType {
25 pub const fn as_str(self) -> &'static str {
27 match self {
28 Self::BuildSuccess => "build_success",
29 Self::TestsPassing => "tests_passing",
30 Self::LintClean => "lint_clean",
31 Self::TypecheckPassing => "typecheck_passing",
32 Self::AgentCompletion => "agent_completion",
33 Self::RetryCount => "retry_count",
34 Self::HumanAcceptance => "human_acceptance",
35 Self::PrMerge => "pr_merge",
36 Self::CiPassing => "ci_passing",
37 Self::Correction => "correction",
38 Self::Rollback => "rollback",
39 }
40 }
41}
42
43impl std::fmt::Display for SignalType {
44 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 formatter.write_str(self.as_str())
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51pub struct OutcomeSignal {
52 pub signal_type: SignalType,
53 pub value: SignalValue,
54 pub evidence_ref: Option<String>,
55 pub observed_at: Option<String>,
56}
57
58impl OutcomeSignal {
59 pub fn new(signal_type: SignalType, value: SignalValue) -> Self {
61 Self {
62 signal_type,
63 value,
64 evidence_ref: None,
65 observed_at: None,
66 }
67 }
68
69 pub fn with_metadata(
71 signal_type: SignalType,
72 value: SignalValue,
73 evidence_ref: Option<String>,
74 observed_at: Option<String>,
75 ) -> Self {
76 Self {
77 signal_type,
78 value,
79 evidence_ref,
80 observed_at,
81 }
82 }
83
84 pub fn with_evidence_ref(mut self, evidence_ref: impl Into<String>) -> Self {
86 self.evidence_ref = Some(evidence_ref.into());
87 self
88 }
89
90 pub fn with_observed_at(mut self, observed_at: impl Into<String>) -> Self {
92 self.observed_at = Some(observed_at.into());
93 self
94 }
95
96 pub fn unknown(signal_type: SignalType) -> Self {
98 Self::new(signal_type, SignalValue::Unknown)
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104#[serde(rename_all = "snake_case")]
105pub enum SignalValue {
106 Boolean(bool),
107 Count(u32),
108 Unknown,
109}
110
111pub struct LocalSignalAdapters;
113
114impl LocalSignalAdapters {
115 pub fn boolean(signal_type: SignalType, value: bool) -> OutcomeSignal {
117 OutcomeSignal::new(signal_type, SignalValue::Boolean(value))
118 }
119
120 pub fn count(signal_type: SignalType, value: u32) -> OutcomeSignal {
122 OutcomeSignal::new(signal_type, SignalValue::Count(value))
123 }
124
125 pub fn unknown(signal_type: SignalType) -> OutcomeSignal {
127 OutcomeSignal::unknown(signal_type)
128 }
129
130 pub fn build_success(value: bool) -> OutcomeSignal {
131 Self::boolean(SignalType::BuildSuccess, value)
132 }
133
134 pub fn tests_passing(value: bool) -> OutcomeSignal {
135 Self::boolean(SignalType::TestsPassing, value)
136 }
137
138 pub fn test_count_increased(delta: u32) -> OutcomeSignal {
140 Self::count(SignalType::TestsPassing, delta)
141 }
142
143 pub fn lint_clean(value: bool) -> OutcomeSignal {
144 Self::boolean(SignalType::LintClean, value)
145 }
146
147 pub fn typecheck_passing(value: bool) -> OutcomeSignal {
148 Self::boolean(SignalType::TypecheckPassing, value)
149 }
150
151 pub fn agent_completion(value: bool) -> OutcomeSignal {
152 Self::boolean(SignalType::AgentCompletion, value)
153 }
154
155 pub fn retry_count(value: u32) -> OutcomeSignal {
156 Self::count(SignalType::RetryCount, value)
157 }
158
159 pub fn human_acceptance(value: bool) -> OutcomeSignal {
160 Self::boolean(SignalType::HumanAcceptance, value)
161 }
162
163 pub fn pr_merge(value: bool) -> OutcomeSignal {
164 Self::boolean(SignalType::PrMerge, value)
165 }
166
167 pub fn ci_passing(value: bool) -> OutcomeSignal {
168 Self::boolean(SignalType::CiPassing, value)
169 }
170
171 pub fn correction(value: bool) -> OutcomeSignal {
172 Self::boolean(SignalType::Correction, value)
173 }
174
175 pub fn rollback(value: bool) -> OutcomeSignal {
176 Self::boolean(SignalType::Rollback, value)
177 }
178}
179
180pub fn adapt_boolean(signal_type: SignalType, value: bool) -> OutcomeSignal {
181 LocalSignalAdapters::boolean(signal_type, value)
182}
183
184pub fn adapt_count(signal_type: SignalType, value: u32) -> OutcomeSignal {
185 LocalSignalAdapters::count(signal_type, value)
186}
187
188pub fn adapt_build_success(value: bool) -> OutcomeSignal {
189 LocalSignalAdapters::build_success(value)
190}
191
192pub fn adapt_tests_passing(value: bool) -> OutcomeSignal {
193 LocalSignalAdapters::tests_passing(value)
194}
195
196pub fn adapt_test_count_increased(delta: u32) -> OutcomeSignal {
197 LocalSignalAdapters::test_count_increased(delta)
198}
199
200pub fn adapt_lint_clean(value: bool) -> OutcomeSignal {
201 LocalSignalAdapters::lint_clean(value)
202}
203
204pub fn adapt_typecheck_passing(value: bool) -> OutcomeSignal {
205 LocalSignalAdapters::typecheck_passing(value)
206}
207
208pub fn adapt_agent_completion(value: bool) -> OutcomeSignal {
209 LocalSignalAdapters::agent_completion(value)
210}
211
212pub fn adapt_retry_count(value: u32) -> OutcomeSignal {
213 LocalSignalAdapters::retry_count(value)
214}
215
216pub fn adapt_human_acceptance(value: bool) -> OutcomeSignal {
217 LocalSignalAdapters::human_acceptance(value)
218}
219
220pub fn adapt_pr_merge(value: bool) -> OutcomeSignal {
221 LocalSignalAdapters::pr_merge(value)
222}
223
224pub fn adapt_ci_passing(value: bool) -> OutcomeSignal {
225 LocalSignalAdapters::ci_passing(value)
226}
227
228pub fn adapt_correction(value: bool) -> OutcomeSignal {
229 LocalSignalAdapters::correction(value)
230}
231
232pub fn adapt_rollback(value: bool) -> OutcomeSignal {
233 LocalSignalAdapters::rollback(value)
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn adapters_preserve_signal_kind_and_value() {
242 assert_eq!(
243 LocalSignalAdapters::build_success(true),
244 OutcomeSignal::new(SignalType::BuildSuccess, SignalValue::Boolean(true))
245 );
246 assert_eq!(
247 LocalSignalAdapters::retry_count(2).value,
248 SignalValue::Count(2)
249 );
250 assert_eq!(
251 LocalSignalAdapters::test_count_increased(1).signal_type,
252 SignalType::TestsPassing
253 );
254 }
255}