Skip to main content

forge_engine/
config.rs

1use serde::{Deserialize, Serialize};
2
3/// The single source of truth for all Forge runtime behavior.
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct ForgeConfig {
6    /// "standard" or "sealed_local"
7    #[serde(default = "default_mode")]
8    pub mode: String,
9
10    /// "auto" | "host" | "container"
11    #[serde(default = "default_auto")]
12    pub execution_backend_preference: String,
13
14    /// "auto" | "docker" | "podman" | "nerdctl"
15    #[serde(default = "default_auto")]
16    pub container_runtime_preference: String,
17
18    #[serde(default)]
19    pub allow_test_modifications: bool,
20
21    #[serde(default)]
22    pub sealed_allow_host_backend: bool,
23
24    #[serde(default = "default_forbidden_paths")]
25    pub forbidden_paths: Vec<String>,
26
27    #[serde(default)]
28    pub caps: CapsConfig,
29
30    #[serde(default)]
31    pub mindstate: MindstateConfig,
32
33    #[serde(default)]
34    pub novelty: NoveltyConfig,
35
36    #[serde(default)]
37    pub stabilization: StabilizationConfig,
38
39    #[serde(default)]
40    pub container: ContainerConfig,
41
42    #[serde(default)]
43    pub lab: LabConfig,
44
45    #[serde(default)]
46    pub cea: CeaConfig,
47
48    #[serde(default)]
49    pub danger: DangerConfig,
50
51    #[serde(default)]
52    pub limits: ForgeLimits,
53
54    #[serde(default)]
55    pub workspace: crate::baseline::WorkspacePolicy,
56
57    #[serde(default)]
58    pub statistics: crate::experiment::StatisticsPolicy,
59
60    #[serde(default)]
61    pub comparability: crate::baseline::ComparabilityPolicy,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct CapsConfig {
66    #[serde(default = "default_max_files")]
67    pub max_files_changed: usize,
68    #[serde(default = "default_max_total_lines")]
69    pub max_total_lines_changed: usize,
70    #[serde(default = "default_max_lines_per_file")]
71    pub max_lines_changed_per_file: usize,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct MindstateConfig {
76    #[serde(default = "default_token_budget")]
77    pub token_budget: usize,
78    #[serde(default = "default_evidence_budget")]
79    pub evidence_budget: usize,
80    #[serde(default = "default_max_steps")]
81    pub max_steps: usize,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct NoveltyConfig {
86    #[serde(default = "default_delta_amp_default")]
87    pub delta_amp_default: f64,
88    #[serde(default = "default_delta_amp_stabilize1")]
89    pub delta_amp_stabilize1: f64,
90    #[serde(default = "default_delta_amp_stabilize2")]
91    pub delta_amp_stabilize2: f64,
92    #[serde(default = "default_delta_amp_clamp")]
93    pub delta_amp_clamp: f64,
94    #[serde(default = "default_orthogonality_target")]
95    pub orthogonality_target: f64,
96    #[serde(default = "default_min_traces")]
97    pub min_traces_for_orthogonality: usize,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct StabilizationConfig {
102    #[serde(default = "default_max_attempts")]
103    pub max_attempts: usize,
104    #[serde(default = "default_stabilize1_force_family")]
105    pub stabilize1_force_family: String,
106    #[serde(default = "default_true")]
107    pub stabilize2_force_minimal_diff: bool,
108    #[serde(default = "default_stabilize_weight_factor")]
109    pub increase_stabilize_weight_factor: f64,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ContainerConfig {
114    #[serde(default = "default_rust_image")]
115    pub rust_image: String,
116    #[serde(default = "default_command_timeout")]
117    pub command_timeout_secs: u64,
118    #[serde(default = "default_memory_limit")]
119    pub memory_limit: String,
120    #[serde(default = "default_cpu_limit")]
121    pub cpu_limit: String,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct LabConfig {
126    #[serde(default = "default_batch_size")]
127    pub generation_batch_size: usize,
128    #[serde(default = "default_eval_parallelism")]
129    pub eval_parallelism: usize,
130    #[serde(default = "default_min_pass_rate")]
131    pub promotion_min_suite_pass_rate: f64,
132    #[serde(default = "default_min_improvement")]
133    pub promotion_min_weighted_improvement: f64,
134    #[serde(default)]
135    pub archive: ArchiveConfig,
136    #[serde(default)]
137    pub allow_raw_spec: bool,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct ArchiveConfig {
142    #[serde(default = "default_novelty_bins")]
143    pub novelty_bins: Vec<NoveltyBin>,
144    #[serde(default = "default_stability_variance_threshold")]
145    pub stability_variance_threshold: f64,
146    #[serde(default = "default_approach_families")]
147    pub approach_families: Vec<String>,
148    #[serde(default = "default_correctness_gate")]
149    pub correctness_gate: f64,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct NoveltyBin {
154    pub name: String,
155    pub lo: f64,
156    pub hi: f64,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct CeaConfig {
161    #[serde(default = "default_true")]
162    pub enabled: bool,
163    /// MUST be false by default — requires explicit opt-in.
164    #[serde(default)]
165    pub enable_zero_shot: bool,
166    #[serde(default = "default_zero_shot_coverage")]
167    pub zero_shot_coverage_threshold: f64,
168    #[serde(default = "default_risk_confidence")]
169    pub risk_confidence_threshold: f64,
170    #[serde(default = "default_max_line_distance")]
171    pub max_line_distance_for_attribution: u32,
172    #[serde(default = "default_attribution_decay")]
173    pub attribution_decay_factor: f64,
174    #[serde(default = "default_causal_drift_threshold")]
175    pub causal_drift_warning_threshold: f64,
176    #[serde(default = "default_min_runs")]
177    pub min_runs_before_prediction: usize,
178}
179
180#[derive(Debug, Clone, Default, Serialize, Deserialize)]
181pub struct DangerConfig {
182    #[serde(default)]
183    pub allow_semantic_memory_write: bool,
184}
185
186// --- Default value functions ---
187
188fn default_mode() -> String {
189    "standard".to_string()
190}
191fn default_auto() -> String {
192    "auto".to_string()
193}
194fn default_forbidden_paths() -> Vec<String> {
195    vec![
196        "tests/**".to_string(),
197        "**/*_test.rs".to_string(),
198        "**/fixtures/**".to_string(),
199        "**/*.snap".to_string(),
200        "Cargo.lock".to_string(),
201        ".github/**".to_string(),
202    ]
203}
204fn default_max_files() -> usize {
205    8
206}
207fn default_max_total_lines() -> usize {
208    400
209}
210fn default_max_lines_per_file() -> usize {
211    200
212}
213fn default_token_budget() -> usize {
214    1800
215}
216fn default_evidence_budget() -> usize {
217    8
218}
219fn default_max_steps() -> usize {
220    8
221}
222fn default_delta_amp_default() -> f64 {
223    0.7
224}
225fn default_delta_amp_stabilize1() -> f64 {
226    0.2
227}
228fn default_delta_amp_stabilize2() -> f64 {
229    0.1
230}
231fn default_delta_amp_clamp() -> f64 {
232    0.0
233}
234fn default_orthogonality_target() -> f64 {
235    0.10
236}
237fn default_min_traces() -> usize {
238    2
239}
240fn default_max_attempts() -> usize {
241    4
242}
243fn default_stabilize1_force_family() -> String {
244    "mechanical".to_string()
245}
246fn default_true() -> bool {
247    true
248}
249fn default_stabilize_weight_factor() -> f64 {
250    2.5
251}
252fn default_rust_image() -> String {
253    "rust:1.78-slim".to_string()
254}
255fn default_command_timeout() -> u64 {
256    120
257}
258fn default_memory_limit() -> String {
259    "2g".to_string()
260}
261fn default_cpu_limit() -> String {
262    "2.0".to_string()
263}
264fn default_batch_size() -> usize {
265    32
266}
267fn default_eval_parallelism() -> usize {
268    4
269}
270fn default_min_pass_rate() -> f64 {
271    0.95
272}
273fn default_min_improvement() -> f64 {
274    0.05
275}
276fn default_novelty_bins() -> Vec<NoveltyBin> {
277    vec![
278        NoveltyBin {
279            name: "low".to_string(),
280            lo: 0.00,
281            hi: 0.33,
282        },
283        NoveltyBin {
284            name: "med".to_string(),
285            lo: 0.33,
286            hi: 0.66,
287        },
288        NoveltyBin {
289            name: "high".to_string(),
290            lo: 0.66,
291            hi: 1.00,
292        },
293    ]
294}
295fn default_stability_variance_threshold() -> f64 {
296    0.15
297}
298fn default_approach_families() -> Vec<String> {
299    vec![
300        "mechanical".to_string(),
301        "pattern_refactor".to_string(),
302        "architectural".to_string(),
303        "perf".to_string(),
304        "safety".to_string(),
305    ]
306}
307fn default_correctness_gate() -> f64 {
308    0.95
309}
310fn default_zero_shot_coverage() -> f64 {
311    0.80
312}
313fn default_risk_confidence() -> f64 {
314    0.60
315}
316fn default_max_line_distance() -> u32 {
317    50
318}
319fn default_attribution_decay() -> f64 {
320    10.0
321}
322fn default_causal_drift_threshold() -> f64 {
323    0.25
324}
325fn default_min_runs() -> usize {
326    5
327}
328
329impl Default for ForgeConfig {
330    fn default() -> Self {
331        Self {
332            mode: default_mode(),
333            execution_backend_preference: default_auto(),
334            container_runtime_preference: default_auto(),
335            allow_test_modifications: false,
336            sealed_allow_host_backend: false,
337            forbidden_paths: default_forbidden_paths(),
338            caps: CapsConfig::default(),
339            mindstate: MindstateConfig::default(),
340            novelty: NoveltyConfig::default(),
341            stabilization: StabilizationConfig::default(),
342            container: ContainerConfig::default(),
343            lab: LabConfig::default(),
344            cea: CeaConfig::default(),
345            danger: DangerConfig::default(),
346            limits: ForgeLimits::default(),
347            workspace: crate::baseline::WorkspacePolicy::default(),
348            statistics: crate::experiment::StatisticsPolicy::default(),
349            comparability: crate::baseline::ComparabilityPolicy::default(),
350        }
351    }
352}
353
354impl Default for CapsConfig {
355    fn default() -> Self {
356        Self {
357            max_files_changed: default_max_files(),
358            max_total_lines_changed: default_max_total_lines(),
359            max_lines_changed_per_file: default_max_lines_per_file(),
360        }
361    }
362}
363
364impl Default for MindstateConfig {
365    fn default() -> Self {
366        Self {
367            token_budget: default_token_budget(),
368            evidence_budget: default_evidence_budget(),
369            max_steps: default_max_steps(),
370        }
371    }
372}
373
374impl Default for NoveltyConfig {
375    fn default() -> Self {
376        Self {
377            delta_amp_default: default_delta_amp_default(),
378            delta_amp_stabilize1: default_delta_amp_stabilize1(),
379            delta_amp_stabilize2: default_delta_amp_stabilize2(),
380            delta_amp_clamp: default_delta_amp_clamp(),
381            orthogonality_target: default_orthogonality_target(),
382            min_traces_for_orthogonality: default_min_traces(),
383        }
384    }
385}
386
387impl Default for StabilizationConfig {
388    fn default() -> Self {
389        Self {
390            max_attempts: default_max_attempts(),
391            stabilize1_force_family: default_stabilize1_force_family(),
392            stabilize2_force_minimal_diff: true,
393            increase_stabilize_weight_factor: default_stabilize_weight_factor(),
394        }
395    }
396}
397
398impl Default for ContainerConfig {
399    fn default() -> Self {
400        Self {
401            rust_image: default_rust_image(),
402            command_timeout_secs: default_command_timeout(),
403            memory_limit: default_memory_limit(),
404            cpu_limit: default_cpu_limit(),
405        }
406    }
407}
408
409impl Default for LabConfig {
410    fn default() -> Self {
411        Self {
412            generation_batch_size: default_batch_size(),
413            eval_parallelism: default_eval_parallelism(),
414            promotion_min_suite_pass_rate: default_min_pass_rate(),
415            promotion_min_weighted_improvement: default_min_improvement(),
416            archive: ArchiveConfig::default(),
417            allow_raw_spec: false,
418        }
419    }
420}
421
422impl Default for ArchiveConfig {
423    fn default() -> Self {
424        Self {
425            novelty_bins: default_novelty_bins(),
426            stability_variance_threshold: default_stability_variance_threshold(),
427            approach_families: default_approach_families(),
428            correctness_gate: default_correctness_gate(),
429        }
430    }
431}
432
433impl Default for CeaConfig {
434    fn default() -> Self {
435        Self {
436            enabled: true,
437            enable_zero_shot: false,
438            zero_shot_coverage_threshold: default_zero_shot_coverage(),
439            risk_confidence_threshold: default_risk_confidence(),
440            max_line_distance_for_attribution: default_max_line_distance(),
441            attribution_decay_factor: default_attribution_decay(),
442            causal_drift_warning_threshold: default_causal_drift_threshold(),
443            min_runs_before_prediction: default_min_runs(),
444        }
445    }
446}
447
448/// Resource limits for the forge runtime.
449///
450/// Violations are hard errors: no ExperimentEvidenceBundle is produced on limit violation.
451#[derive(Debug, Clone, Serialize, Deserialize)]
452pub struct ForgeLimits {
453    /// Maximum number of hypotheses per evidence bundle.
454    #[serde(default = "default_max_hypotheses")]
455    pub max_hypotheses: usize,
456    /// Maximum number of concurrent experiments.
457    #[serde(default = "default_max_experiments")]
458    pub max_concurrent_experiments: usize,
459    /// Maximum evidence bundles retained per candidate.
460    #[serde(default = "default_max_bundles")]
461    pub max_bundles_per_candidate: usize,
462    /// Maximum verification steps per plan.
463    #[serde(default = "default_max_verification_steps")]
464    pub max_verification_steps: usize,
465    /// Maximum database size in bytes (0 = unlimited).
466    #[serde(default)]
467    pub max_db_bytes: u64,
468    /// Maximum retained log bytes per run (0 = unlimited).
469    #[serde(default)]
470    pub max_retained_logs_bytes: u64,
471    /// Maximum retained artifacts count.
472    #[serde(default = "default_max_artifacts")]
473    pub max_retained_artifacts: usize,
474    /// Failed run retention in days (0 = no retention).
475    #[serde(default = "default_failure_retention_days")]
476    pub failed_run_retention_days: u32,
477    /// Maximum files in a patch (enforced at patch ingest).
478    #[serde(default = "default_max_patch_files")]
479    pub max_patch_files: usize,
480    /// Maximum total bytes in a patch (enforced at patch ingest).
481    #[serde(default = "default_max_patch_bytes")]
482    pub max_patch_bytes: u64,
483    /// Maximum runtime in seconds for a single check (enforced in LocalExperimentRunner).
484    #[serde(default = "default_max_check_runtime")]
485    pub max_check_runtime_secs: u64,
486    /// Maximum output bytes from a single check (truncated if exceeded).
487    #[serde(default = "default_max_check_output_bytes")]
488    pub max_check_output_bytes: u64,
489    /// Maximum nodes in the CEA graph before mutation commit.
490    #[serde(default = "default_max_graph_nodes")]
491    pub max_graph_nodes: usize,
492    /// Maximum edges in the CEA graph before mutation commit.
493    #[serde(default = "default_max_graph_edges")]
494    pub max_graph_edges: usize,
495}
496
497fn default_max_hypotheses() -> usize {
498    100
499}
500fn default_max_experiments() -> usize {
501    4
502}
503fn default_max_bundles() -> usize {
504    50
505}
506fn default_max_verification_steps() -> usize {
507    20
508}
509fn default_max_artifacts() -> usize {
510    100
511}
512fn default_failure_retention_days() -> u32 {
513    30
514}
515fn default_max_patch_files() -> usize {
516    16
517}
518fn default_max_patch_bytes() -> u64 {
519    1_000_000 // 1 MB
520}
521fn default_max_check_runtime() -> u64 {
522    300 // 5 minutes
523}
524fn default_max_check_output_bytes() -> u64 {
525    10_000_000 // 10 MB
526}
527fn default_max_graph_nodes() -> usize {
528    10_000
529}
530fn default_max_graph_edges() -> usize {
531    50_000
532}
533
534impl Default for ForgeLimits {
535    fn default() -> Self {
536        Self {
537            max_hypotheses: default_max_hypotheses(),
538            max_concurrent_experiments: default_max_experiments(),
539            max_bundles_per_candidate: default_max_bundles(),
540            max_verification_steps: default_max_verification_steps(),
541            max_db_bytes: 0,
542            max_retained_logs_bytes: 0,
543            max_retained_artifacts: default_max_artifacts(),
544            failed_run_retention_days: default_failure_retention_days(),
545            max_patch_files: default_max_patch_files(),
546            max_patch_bytes: default_max_patch_bytes(),
547            max_check_runtime_secs: default_max_check_runtime(),
548            max_check_output_bytes: default_max_check_output_bytes(),
549            max_graph_nodes: default_max_graph_nodes(),
550            max_graph_edges: default_max_graph_edges(),
551        }
552    }
553}