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}