forge_engine/
invariants.rs1use std::path::Path;
2
3use crate::error::{ForgeError, ForgeResult};
4use crate::store::schema;
5
6pub fn refuse_to_open_db(path: &Path) -> ForgeResult<()> {
15 let spec = forge_policy::DbIdentitySpec {
16 min_user_version: schema::FORGE_MIN_USER_VERSION,
17 max_user_version: schema::FORGE_MAX_USER_VERSION,
18 required_tables: schema::REQUIRED_TABLES,
19 schema_table: "forge_meta",
20 schema_key: "schema_hash",
21 expected_schema_hash: schema::forge_schema_hash(),
22 };
23
24 forge_policy::verify_sqlite_db_identity(path, &spec).map_err(|error| match error {
25 forge_policy::PolicyError::RefuseToOpenDb { reason } => {
26 ForgeError::RefuseToOpenDb { reason }
27 }
28 other => ForgeError::RefuseToOpenDb {
29 reason: other.to_string(),
30 },
31 })
32}
33
34pub fn validate_forbidden_paths(
36 paths: &[&str],
37 forbidden_patterns: &[String],
38 allow_test_modifications: bool,
39) -> Vec<crate::error::Violation> {
40 let owned_paths: Vec<String> = paths.iter().map(|path| (*path).to_string()).collect();
41 forge_policy::validate_forbidden_paths(
42 &owned_paths,
43 forbidden_patterns,
44 allow_test_modifications,
45 )
46}
47
48pub fn validate_patch_caps(
50 files_changed: usize,
51 total_lines_changed: usize,
52 per_file_lines: &[usize],
53 max_files: usize,
54 max_total_lines: usize,
55 max_per_file: usize,
56) -> Vec<crate::error::Violation> {
57 forge_policy::validate_patch_caps(
58 files_changed,
59 total_lines_changed,
60 per_file_lines,
61 max_files,
62 max_total_lines,
63 max_per_file,
64 )
65}
66
67pub fn validate_cea_no_raw_source(sig_json: &str) -> ForgeResult<()> {
70 let value: serde_json::Value =
71 serde_json::from_str(sig_json).map_err(|_| ForgeError::CeaRawSourceDetected)?;
72 check_value_no_raw_source(&value)
73}
74
75fn check_value_no_raw_source(value: &serde_json::Value) -> ForgeResult<()> {
77 match value {
78 serde_json::Value::Object(obj) => {
79 for key in &["context_before", "context_after", "raw_source", "raw_lines"] {
80 if obj.contains_key(*key) {
81 return Err(ForgeError::CeaRawSourceDetected);
82 }
83 }
84
85 if let Some(hash) = obj.get("context_hash") {
87 if let Some(s) = hash.as_str() {
88 if s.len() != 64 || !s.chars().all(|c| c.is_ascii_hexdigit()) {
89 return Err(ForgeError::CeaRawSourceDetected);
90 }
91 }
92 }
93
94 if let Some(ext) = obj.get("file_extension") {
96 if let Some(s) = ext.as_str() {
97 if s.contains('/') || s.contains('\\') || s.len() > 10 {
98 return Err(ForgeError::CeaRawSourceDetected);
99 }
100 }
101 }
102
103 for v in obj.values() {
105 check_value_no_raw_source(v)?;
106 }
107 }
108 serde_json::Value::Array(arr) => {
109 for v in arr {
110 check_value_no_raw_source(v)?;
111 }
112 }
113 _ => {}
114 }
115 Ok(())
116}