Skip to main content

turnframe_test/explore/
limits.rs

1//! How far bounded exploration is allowed to go.
2
3use serde::{Deserialize, Serialize};
4
5/// Bounds on a breadth-first exploration (spec ยง8.5).
6///
7/// Exploration is *bounded* on purpose: a workflow with free-text fields has an
8/// infinite state space, so the model offers a finite menu of candidate
9/// commands and these limits cap the search on top of it. Hitting a limit is
10/// not a failure; it marks the report
11/// [`truncated`](crate::explore::ExplorationReport::truncated) so a test can
12/// tell "no violations found" from "no violations found so far".
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[non_exhaustive]
15pub struct ExplorationLimits {
16    /// Maximum number of distinct states to visit.
17    pub max_states: usize,
18    /// Maximum number of commands on the path from an initial state.
19    pub max_depth: usize,
20    /// Maximum number of candidate commands tried in one state.
21    pub max_commands_per_state: usize,
22}
23
24impl ExplorationLimits {
25    /// Builds explicit limits.
26    #[must_use]
27    pub const fn new(max_states: usize, max_depth: usize, max_commands_per_state: usize) -> Self {
28        Self {
29            max_states,
30            max_depth,
31            max_commands_per_state,
32        }
33    }
34
35    /// Limits for a fast check in a unit test: 128 states, depth 6, 8 commands.
36    #[must_use]
37    pub const fn smoke() -> Self {
38        Self::new(128, 6, 8)
39    }
40
41    /// The default: 1024 states, depth 16, 16 commands per state.
42    #[must_use]
43    pub const fn standard() -> Self {
44        Self::new(1024, 16, 16)
45    }
46
47    /// Limits for an exhaustive run in CI: 8192 states, depth 32, 32 commands.
48    #[must_use]
49    pub const fn generous() -> Self {
50        Self::new(8192, 32, 32)
51    }
52
53    /// Returns a copy with another state budget.
54    #[must_use]
55    pub const fn with_max_states(mut self, max_states: usize) -> Self {
56        self.max_states = max_states;
57        self
58    }
59
60    /// Returns a copy with another depth budget.
61    #[must_use]
62    pub const fn with_max_depth(mut self, max_depth: usize) -> Self {
63        self.max_depth = max_depth;
64        self
65    }
66
67    /// Returns a copy with another per-state command budget.
68    #[must_use]
69    pub const fn with_max_commands_per_state(mut self, max_commands_per_state: usize) -> Self {
70        self.max_commands_per_state = max_commands_per_state;
71        self
72    }
73}
74
75impl Default for ExplorationLimits {
76    fn default() -> Self {
77        Self::standard()
78    }
79}