termivar_scanner/decision_loop/
state.rs1use super::{
4 AdaptationLedger, DecisionLoopError, DecisionStopReason, Deserialize, Deserializer, EntityId,
5 Serialize, VerificationCase,
6};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(tag = "state", rename_all = "snake_case")]
11#[non_exhaustive]
12pub enum DecisionLoopState {
13 Ready,
15 AwaitingPassive {
17 case: VerificationCase,
19 },
20 AwaitingActive {
22 case: VerificationCase,
24 },
25 Completed,
27 Halted {
29 reason: DecisionStopReason,
31 },
32}
33
34impl DecisionLoopState {
35 pub(super) fn name(&self) -> &'static str {
36 match self {
37 Self::Ready => "ready",
38 Self::AwaitingPassive { .. } => "awaiting_passive",
39 Self::AwaitingActive { .. } => "awaiting_active",
40 Self::Completed => "completed",
41 Self::Halted { .. } => "halted",
42 }
43 }
44
45 pub(super) fn case(&self) -> Option<&VerificationCase> {
46 match self {
47 Self::AwaitingPassive { case } | Self::AwaitingActive { case } => Some(case),
48 Self::Ready | Self::Completed | Self::Halted { .. } => None,
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
55pub struct DecisionSession {
56 pub(super) subject: EntityId,
57 pub(super) action_cycles: u32,
58 pub(super) state: DecisionLoopState,
59 pub(super) adaptation: AdaptationLedger,
60}
61
62impl DecisionSession {
63 pub fn new(subject: EntityId) -> Self {
65 Self {
66 subject,
67 action_cycles: 0,
68 state: DecisionLoopState::Ready,
69 adaptation: AdaptationLedger::new(),
70 }
71 }
72
73 pub fn subject(&self) -> &EntityId {
75 &self.subject
76 }
77
78 pub fn action_cycles(&self) -> u32 {
80 self.action_cycles
81 }
82
83 pub fn state(&self) -> &DecisionLoopState {
85 &self.state
86 }
87
88 pub(crate) fn halt_for_runtime_budget(&mut self) {
90 self.state = DecisionLoopState::Halted {
91 reason: DecisionStopReason::RuntimeBudgetLimit,
92 };
93 }
94
95 pub(crate) fn halt_for_host_cancellation(&mut self) {
97 self.state = DecisionLoopState::Halted {
98 reason: DecisionStopReason::CancelledByHost,
99 };
100 }
101
102 pub(crate) fn replan_after_defense_suppression(&mut self) -> Result<(), DecisionLoopError> {
110 match &self.state {
111 DecisionLoopState::AwaitingPassive { .. }
112 | DecisionLoopState::AwaitingActive { .. } => {
113 self.state = DecisionLoopState::Ready;
114 Ok(())
115 },
116 _ => Err(DecisionLoopError::InvalidTransition {
117 operation: "replan after defense suppression",
118 state: self.state.name(),
119 }),
120 }
121 }
122
123 pub(crate) fn finalize_objective_complete(&mut self) {
127 self.state = DecisionLoopState::Completed;
128 }
129
130 pub(crate) fn finalize_human_review(&mut self) {
135 self.state = DecisionLoopState::Halted {
136 reason: DecisionStopReason::HumanReview,
137 };
138 }
139
140 pub fn adaptation(&self) -> &AdaptationLedger {
142 &self.adaptation
143 }
144
145 pub fn transition_summary(&self) -> DecisionSessionSummary {
151 DecisionSessionSummary {
152 state: self.state.clone(),
153 action_cycles: self.action_cycles,
154 adaptation_transitions: self.adaptation.transitions(),
155 }
156 }
157}
158
159impl<'de> Deserialize<'de> for DecisionSession {
160 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
161 where
162 D: Deserializer<'de>,
163 {
164 #[derive(Deserialize)]
165 struct WireSession {
166 subject: EntityId,
167 action_cycles: u32,
168 state: DecisionLoopState,
169 adaptation: AdaptationLedger,
170 }
171
172 let wire = WireSession::deserialize(deserializer)?;
173 if let Some(case) = wire.state.case() {
174 if case.subject() != &wire.subject {
175 return Err(serde::de::Error::custom(
176 DecisionLoopError::CaseSubjectMismatch {
177 expected: wire.subject,
178 actual: case.subject().clone(),
179 },
180 ));
181 }
182 if wire.action_cycles == 0 {
183 return Err(serde::de::Error::custom(
184 DecisionLoopError::AwaitingWithoutAction,
185 ));
186 }
187 }
188 Ok(Self {
189 subject: wire.subject,
190 action_cycles: wire.action_cycles,
191 state: wire.state,
192 adaptation: wire.adaptation,
193 })
194 }
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
202pub struct DecisionSessionSummary {
203 pub(super) state: DecisionLoopState,
204 pub(super) action_cycles: u32,
205 pub(super) adaptation_transitions: u32,
206}
207
208impl DecisionSessionSummary {
209 pub fn state(&self) -> &DecisionLoopState {
211 &self.state
212 }
213
214 pub fn action_cycles(&self) -> u32 {
216 self.action_cycles
217 }
218
219 pub fn adaptation_transitions(&self) -> u32 {
221 self.adaptation_transitions
222 }
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
227pub struct DecisionSessionTransition {
228 pub(super) before: DecisionSessionSummary,
229 pub(super) after: DecisionSessionSummary,
230}
231
232impl DecisionSessionTransition {
233 pub(super) fn new(before: DecisionSessionSummary, after: DecisionSessionSummary) -> Self {
234 Self { before, after }
235 }
236
237 pub fn before(&self) -> &DecisionSessionSummary {
239 &self.before
240 }
241
242 pub fn after(&self) -> &DecisionSessionSummary {
244 &self.after
245 }
246}