yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
policy_evaluator.cc
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <sstream>
6
7#include "absl/strings/numbers.h"
8#include "absl/strings/str_format.h"
9#include "absl/strings/str_split.h"
11#include "util/platform_paths.h"
12
13namespace yaze {
14namespace cli {
15
16// Internal policy configuration structures
18 std::string version;
19 bool enabled = true;
20
22 std::string name;
23 bool enabled = true;
25 // suite name → min pass rate
26 std::vector<std::pair<std::string, double>> test_suites;
27 std::string message;
28 };
29
41
43 std::string name;
44 bool enabled = true;
46 // start, end, reason
47 std::vector<std::tuple<int, int, std::string>> ranges;
48 std::string message;
49 };
50
52 std::string name;
53 bool enabled = true;
55 struct Condition {
56 std::string if_clause; // e.g., "bytes_changed > 1024"
57 std::string then_clause; // e.g., "require_diff_review"
58 std::string message;
59 };
60 std::vector<Condition> conditions;
61 std::string message;
62 };
63
64 std::vector<TestRequirement> test_requirements;
65 std::vector<ChangeConstraint> change_constraints;
66 std::vector<ForbiddenRange> forbidden_ranges;
67 std::vector<ReviewRequirement> review_requirements;
68};
69
70// Singleton instance
72 static PolicyEvaluator instance;
73 return instance;
74}
75
76absl::Status PolicyEvaluator::LoadPolicies(absl::string_view policy_dir) {
77 if (policy_dir.empty()) {
78 auto policies_dir = util::PlatformPaths::GetAppDataSubdirectory("policies");
79 if (policies_dir.ok()) {
80 policy_dir_ = policies_dir->string();
81 } else {
83 (std::filesystem::current_path() / ".yaze" / "policies").string();
84 }
85 } else {
86 policy_dir_ = std::string(policy_dir);
87 }
88 policy_path_ = absl::StrFormat("%s/agent.yaml", policy_dir_);
89
90 // Check if file exists
91 std::ifstream file(policy_path_);
92 if (!file.good()) {
93 // No policy file - policies disabled
94 enabled_ = false;
95 return absl::OkStatus();
96 }
97
98 // Read file content
99 std::stringstream buffer;
100 buffer << file.rdbuf();
101 std::string yaml_content = buffer.str();
102
103 return ParsePolicyFile(yaml_content);
104}
105
107 if (policy_dir_.empty()) {
108 return absl::FailedPreconditionError(
109 "No policy directory set. Call LoadPolicies first.");
110 }
112}
113
115 if (!enabled_) {
116 return "Policies disabled (no configuration file)";
117 }
118 if (!config_) {
119 return "Policies enabled but not loaded";
120 }
121
122 int total_policies =
123 config_->test_requirements.size() + config_->change_constraints.size() +
124 config_->forbidden_ranges.size() + config_->review_requirements.size();
125
126 return absl::StrFormat("Policies enabled (%d policies loaded from %s)",
127 total_policies, policy_path_);
128}
129
130absl::Status PolicyEvaluator::ParsePolicyFile(absl::string_view yaml_content) {
131 // For now, implement a simple key-value parser
132 // In production, we'd use yaml-cpp or similar library
133 // This stub implementation allows the system to work without YAML dependency
134
135 config_ = std::make_unique<PolicyConfig>();
136 config_->version = "1.0";
137 config_->enabled = true;
138
139 // Parse simple YAML-like format
140 std::vector<std::string> lines = absl::StrSplit(yaml_content, '\n');
141 bool in_policies = false;
142 std::string current_policy_type;
143 std::string current_policy_name;
144
145 for (const auto& line : lines) {
146 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
147
148 // Skip comments and empty lines
149 if (trimmed.empty() || trimmed[0] == '#')
150 continue;
151
152 // Check for main keys
153 if (absl::StartsWith(trimmed, "version:")) {
154 std::vector<std::string> parts = absl::StrSplit(trimmed, ':');
155 if (parts.size() >= 2) {
156 config_->version = std::string(absl::StripAsciiWhitespace(parts[1]));
157 }
158 } else if (absl::StartsWith(trimmed, "enabled:")) {
159 std::vector<std::string> parts = absl::StrSplit(trimmed, ':');
160 if (parts.size() >= 2) {
161 std::string value = std::string(absl::StripAsciiWhitespace(parts[1]));
162 config_->enabled = (value == "true");
163 }
164 } else if (trimmed == "policies:") {
165 in_policies = true;
166 } else if (in_policies && absl::StartsWith(trimmed, "- name:")) {
167 // Start of new policy
168 std::vector<std::string> parts = absl::StrSplit(trimmed, ':');
169 if (parts.size() >= 2) {
170 current_policy_name = std::string(absl::StripAsciiWhitespace(parts[1]));
171 }
172 } else if (in_policies && absl::StartsWith(trimmed, "type:")) {
173 std::vector<std::string> parts = absl::StrSplit(trimmed, ':');
174 if (parts.size() >= 2) {
175 current_policy_type = std::string(absl::StripAsciiWhitespace(parts[1]));
176
177 // Create appropriate policy structure
178 if (current_policy_type == "change_constraint") {
180 constraint.name = current_policy_name;
181 constraint.max_bytes_changed = 5120; // Default 5KB
182 constraint.max_commands_executed = 15;
183 constraint.message = "Change scope exceeded";
184 config_->change_constraints.push_back(constraint);
185 } else if (current_policy_type == "forbidden_range") {
187 range.name = current_policy_name;
188 range.ranges.push_back(std::make_tuple(0xFFB0, 0xFFFF, "ROM header"));
189 range.message = "Cannot modify protected region";
190 config_->forbidden_ranges.push_back(range);
191 } else if (current_policy_type == "test_requirement") {
193 test.name = current_policy_name;
194 test.test_suites.push_back(std::make_pair("smoke_test", 1.0));
195 test.message = "Required tests must pass";
196 config_->test_requirements.push_back(test);
197 } else if (current_policy_type == "review_requirement") {
199 review.name = current_policy_name;
200 review.message = "Manual review required";
201 config_->review_requirements.push_back(review);
202 }
203 }
204 }
205 }
206
207 if (!config_->enabled) {
208 enabled_ = false;
209 return absl::OkStatus();
210 }
211
212 enabled_ = true;
213 return absl::OkStatus();
214}
215
216absl::StatusOr<PolicyResult> PolicyEvaluator::EvaluateProposal(
217 absl::string_view proposal_id) {
218 PolicyResult result;
219 result.passed = true;
220
221 if (!enabled_ || !config_) {
222 // No policies - everything passes
223 return result;
224 }
225
226 // Evaluate each policy type
227 EvaluateTestRequirements(std::string(proposal_id), &result);
228 EvaluateChangeConstraints(std::string(proposal_id), &result);
229 EvaluateForbiddenRanges(std::string(proposal_id), &result);
230 EvaluateReviewRequirements(std::string(proposal_id), &result);
231
232 // Categorize violations by severity
233 CategorizeViolations(&result);
234
235 // Determine overall pass/fail
236 result.passed = !result.has_critical_violations();
237
238 return result;
239}
240
241void PolicyEvaluator::EvaluateTestRequirements(absl::string_view proposal_id,
242 PolicyResult* result) {
243 // TODO: Implement test requirement evaluation
244 // For now, all test requirements pass (no test framework yet)
245 std::string proposal_id_str(proposal_id);
246 for (const auto& policy : config_->test_requirements) {
247 if (!policy.enabled)
248 continue;
249
250 // Placeholder: would check actual test results here
251 // For now, we skip test validation
252 }
253}
254
255void PolicyEvaluator::EvaluateChangeConstraints(absl::string_view proposal_id,
256 PolicyResult* result) {
257 auto& registry = ProposalRegistry::Instance();
258 auto proposal_result = registry.GetProposal(std::string(proposal_id));
259
260 if (!proposal_result.ok()) {
261 return; // Can't evaluate non-existent proposal
262 }
263
264 const auto& proposal = proposal_result.value();
265
266 for (const auto& policy : config_->change_constraints) {
267 if (!policy.enabled)
268 continue;
269
270 // Check max bytes changed
271 if (policy.max_bytes_changed > 0 &&
272 proposal.bytes_changed > policy.max_bytes_changed) {
273 PolicyViolation violation;
274 violation.policy_name = policy.name;
275 violation.severity = policy.severity;
276 violation.message =
277 absl::StrFormat("%s: %d bytes changed (limit: %d)", policy.message,
278 proposal.bytes_changed, policy.max_bytes_changed);
279 violation.details =
280 absl::StrFormat("Proposal changed %d bytes", proposal.bytes_changed);
281 result->violations.push_back(violation);
282 }
283
284 // Check max commands executed
285 if (policy.max_commands_executed > 0 &&
286 proposal.commands_executed > policy.max_commands_executed) {
287 PolicyViolation violation;
288 violation.policy_name = policy.name;
289 violation.severity = policy.severity;
290 violation.message = absl::StrFormat(
291 "%s: %d commands executed (limit: %d)", policy.message,
292 proposal.commands_executed, policy.max_commands_executed);
293 violation.details = absl::StrFormat("Proposal executed %d commands",
294 proposal.commands_executed);
295 result->violations.push_back(violation);
296 }
297 }
298}
299
300void PolicyEvaluator::EvaluateForbiddenRanges(absl::string_view proposal_id,
301 PolicyResult* result) {
302 // TODO: Implement forbidden range checking
303 // Would need to parse diff or track ROM modifications
304 // For now, we assume no forbidden range violations
305 for (const auto& policy : config_->forbidden_ranges) {
306 if (!policy.enabled)
307 continue;
308
309 // Placeholder: would check ROM modification ranges here
310 }
311}
312
313void PolicyEvaluator::EvaluateReviewRequirements(absl::string_view proposal_id,
314 PolicyResult* result) {
315 auto& registry = ProposalRegistry::Instance();
316 auto proposal_result = registry.GetProposal(std::string(proposal_id));
317
318 if (!proposal_result.ok()) {
319 return;
320 }
321
322 const auto& proposal = proposal_result.value();
323
324 for (const auto& policy : config_->review_requirements) {
325 if (!policy.enabled)
326 continue;
327
328 // Evaluate conditions
329 for (const auto& condition : policy.conditions) {
330 bool condition_met = false;
331
332 // Simple condition evaluation
333 if (absl::StrContains(condition.if_clause, "bytes_changed")) {
334 // Extract threshold from condition like "bytes_changed > 1024"
335 if (absl::StrContains(condition.if_clause, ">")) {
336 std::vector<std::string> parts =
337 absl::StrSplit(condition.if_clause, '>');
338 if (parts.size() == 2) {
339 int threshold;
340 if (absl::SimpleAtoi(absl::StripAsciiWhitespace(parts[1]),
341 &threshold)) {
342 condition_met = (proposal.bytes_changed > threshold);
343 }
344 }
345 }
346 } else if (absl::StrContains(condition.if_clause, "commands_executed")) {
347 if (absl::StrContains(condition.if_clause, ">")) {
348 std::vector<std::string> parts =
349 absl::StrSplit(condition.if_clause, '>');
350 if (parts.size() == 2) {
351 int threshold;
352 if (absl::SimpleAtoi(absl::StripAsciiWhitespace(parts[1]),
353 &threshold)) {
354 condition_met = (proposal.commands_executed > threshold);
355 }
356 }
357 }
358 }
359
360 if (condition_met) {
361 PolicyViolation violation;
362 violation.policy_name = policy.name;
363 violation.severity = policy.severity;
364 violation.message =
365 condition.message.empty() ? policy.message : condition.message;
366 violation.details =
367 absl::StrFormat("Condition met: %s → %s", condition.if_clause,
368 condition.then_clause);
369 result->violations.push_back(violation);
370 }
371 }
372 }
373}
374
376 for (const auto& violation : result->violations) {
377 switch (violation.severity) {
379 result->critical_violations.push_back(violation);
380 break;
382 result->warnings.push_back(violation);
383 break;
385 result->info.push_back(violation);
386 break;
387 }
388 }
389}
390
391} // namespace cli
392} // namespace yaze
absl::Status ParsePolicyFile(absl::string_view yaml_content)
void EvaluateReviewRequirements(absl::string_view proposal_id, PolicyResult *result)
absl::StatusOr< PolicyResult > EvaluateProposal(absl::string_view proposal_id)
std::unique_ptr< PolicyConfig > config_
static PolicyEvaluator & GetInstance()
absl::Status LoadPolicies(absl::string_view policy_dir="")
void EvaluateTestRequirements(absl::string_view proposal_id, PolicyResult *result)
void CategorizeViolations(PolicyResult *result)
std::string GetStatusString() const
void EvaluateForbiddenRanges(absl::string_view proposal_id, PolicyResult *result)
void EvaluateChangeConstraints(absl::string_view proposal_id, PolicyResult *result)
static ProposalRegistry & Instance()
static absl::StatusOr< std::filesystem::path > GetAppDataSubdirectory(const std::string &subdir)
Get a subdirectory within the app data folder.
std::vector< std::tuple< int, int, std::string > > ranges
std::vector< std::pair< std::string, double > > test_suites
std::vector< TestRequirement > test_requirements
std::vector< ForbiddenRange > forbidden_ranges
std::vector< ReviewRequirement > review_requirements
std::vector< ChangeConstraint > change_constraints
std::vector< PolicyViolation > warnings
bool has_critical_violations() const
std::vector< PolicyViolation > info
std::vector< PolicyViolation > critical_violations
std::vector< PolicyViolation > violations