///|
/// Thresholds used to decide whether a rule set may pass a CI or release gate.
pub struct GatePolicy {
name : String
max_critical : Int
max_warning : Int
max_risk_score : Int
allow_parse_errors : Bool
} derive(Eq, Debug)
///|
pub fn GatePolicy::new(
name : String,
max_critical : Int,
max_warning : Int,
max_risk_score : Int,
allow_parse_errors? : Bool = false,
) -> Result[GatePolicy, String] {
guard trim_ascii(name) != "" else {
return Err("gate policy name must not be empty")
}
guard max_critical >= 0 else {
return Err("maximum critical finding count must not be negative")
}
guard max_warning >= 0 else {
return Err("maximum warning finding count must not be negative")
}
guard max_risk_score >= 0 else {
return Err("maximum risk score must not be negative")
}
Ok({ name, max_critical, max_warning, max_risk_score, allow_parse_errors })
}
///|
/// Strict release policy: no critical findings, warnings, or parse errors.
pub fn GatePolicy::release() -> GatePolicy {
{
name: "release",
max_critical: 0,
max_warning: 0,
max_risk_score: 9,
allow_parse_errors: false,
}
}
///|
/// Review policy: allows a small warning budget but no critical findings.
pub fn GatePolicy::review() -> GatePolicy {
{
name: "review",
max_critical: 0,
max_warning: 5,
max_risk_score: 29,
allow_parse_errors: false,
}
}
///|
/// Development policy: blocks only very risky reports and parse failures.
pub fn GatePolicy::development() -> GatePolicy {
{
name: "development",
max_critical: 2,
max_warning: 20,
max_risk_score: 49,
allow_parse_errors: false,
}
}
///|
pub fn GatePolicy::name(self : GatePolicy) -> String {
self.name
}
///|
pub fn GatePolicy::max_critical(self : GatePolicy) -> Int {
self.max_critical
}
///|
pub fn GatePolicy::max_warning(self : GatePolicy) -> Int {
self.max_warning
}
///|
pub fn GatePolicy::max_risk_score(self : GatePolicy) -> Int {
self.max_risk_score
}
///|
pub fn GatePolicy::allow_parse_errors(self : GatePolicy) -> Bool {
self.allow_parse_errors
}
///|
pub fn GatePolicy::summary(self : GatePolicy) -> String {
let parse_error_mode = if self.allow_parse_errors {
"allowed"
} else {
"blocked"
}
self.name +
": critical<=" +
self.max_critical.to_string() +
", warning<=" +
self.max_warning.to_string() +
", score<=" +
self.max_risk_score.to_string() +
", parse_errors=" +
parse_error_mode
}
///|
pub struct GateReason {
code : String
actual : Int
limit : Int
message : String
} derive(Eq, Debug)
///|
pub fn GateReason::new(
code : String,
actual : Int,
limit : Int,
message : String,
) -> GateReason {
{ code, actual, limit, message }
}
///|
pub fn GateReason::code(self : GateReason) -> String {
self.code
}
///|
pub fn GateReason::actual(self : GateReason) -> Int {
self.actual
}
///|
pub fn GateReason::limit(self : GateReason) -> Int {
self.limit
}
///|
pub fn GateReason::message(self : GateReason) -> String {
self.message
}
///|
pub fn GateReason::summary(self : GateReason) -> String {
self.code +
": actual=" +
self.actual.to_string() +
", limit=" +
self.limit.to_string() +
" (" +
self.message +
")"
}
///|
pub struct GateResult {
policy : GatePolicy
passed : Bool
risk_score : Int
reasons : Array[GateReason]
} derive(Debug)
///|
pub fn GateResult::policy(self : GateResult) -> GatePolicy {
self.policy
}
///|
pub fn GateResult::passed(self : GateResult) -> Bool {
self.passed
}
///|
pub fn GateResult::decision(self : GateResult) -> String {
if self.passed {
"pass"
} else {
"fail"
}
}
///|
pub fn GateResult::risk_score(self : GateResult) -> Int {
self.risk_score
}
///|
pub fn GateResult::reasons(self : GateResult) -> Array[GateReason] {
self.reasons
}
///|
pub fn GateResult::reason_count(self : GateResult) -> Int {
self.reasons.length()
}
///|
pub fn GateResult::has_reason(self : GateResult, code : String) -> Bool {
for reason in self.reasons {
if reason.code() == code {
return true
}
}
false
}
///|
pub fn GateResult::text_report(self : GateResult) -> String {
let mut output = "MoonCIDR deployment gate\n"
output = output + "policy: " + self.policy.name() + "\n"
output = output + "decision: " + self.decision() + "\n"
output = output + "risk_score: " + self.risk_score.to_string() + "\n"
if self.reasons.length() == 0 {
output = output + "\nNo gate thresholds were exceeded.\n"
} else {
output = output + "\nBlocking reasons:\n"
for reason in self.reasons {
output = output + "- " + reason.summary() + "\n"
}
}
output
}
///|
pub fn AuditReport::evaluate_gate(
self : AuditReport,
policy : GatePolicy,
) -> GateResult {
let reasons : Array[GateReason] = []
let critical = self.critical_count()
let warning = self.warning_count()
let score = self.risk_score()
let parse_errors = self.parse_errors().length()
if critical > policy.max_critical() {
reasons.push(
GateReason::new(
"critical_limit",
critical,
policy.max_critical(),
"critical finding count exceeds policy",
),
)
}
if warning > policy.max_warning() {
reasons.push(
GateReason::new(
"warning_limit",
warning,
policy.max_warning(),
"warning finding count exceeds policy",
),
)
}
if score > policy.max_risk_score() {
reasons.push(
GateReason::new(
"risk_score_limit",
score,
policy.max_risk_score(),
"risk score exceeds policy",
),
)
}
if parse_errors > 0 && !policy.allow_parse_errors() {
reasons.push(
GateReason::new(
"parse_errors", parse_errors, 0, "invalid rule input is not allowed",
),
)
}
{ policy, passed: reasons.length() == 0, risk_score: score, reasons }
}
///|
pub fn RuleSet::evaluate_gate(
self : RuleSet,
audit_policy : AuditPolicy,
gate_policy : GatePolicy,
) -> GateResult {
self.audit_with_policy(audit_policy).evaluate_gate(gate_policy)
}