///|
pub struct ReleaseGate {
name : String
required_checks : Int
passed_checks : Int
open_alerts : Int
critical_alerts : Int
evidence_count : Int
minimum_score : Double
} derive(Debug, ToJson)
///|
pub struct GateDecision {
released : Bool
score : Double
blockers : Array[String]
recommendation : String
} derive(Debug, ToJson)
///|
pub fn release_gate(
name~ : String,
required_checks~ : Int,
passed_checks~ : Int,
open_alerts~ : Int,
critical_alerts~ : Int,
evidence_count~ : Int,
minimum_score~ : Double,
) -> ReleaseGate {
{
name,
required_checks: required_checks.max(0),
passed_checks: passed_checks.max(0),
open_alerts: open_alerts.max(0),
critical_alerts: critical_alerts.max(0),
evidence_count: evidence_count.max(0),
minimum_score: minimum_score.max(0.0).min(100.0),
}
}
///|
pub fn release_check_rate(gate : ReleaseGate) -> Double {
if gate.required_checks <= 0 {
1.0
} else {
gate.passed_checks.to_double() / gate.required_checks.to_double()
}
}
///|
pub fn release_score(gate : ReleaseGate) -> Double {
let check = release_check_rate(gate) * 60.0
let evidence = gate.evidence_count.to_double().min(10.0) / 10.0 * 20.0
let alert = (1.0 - gate.open_alerts.to_double().min(10.0) / 10.0) * 20.0
check + evidence + alert
}
///|
pub fn release_blockers(gate : ReleaseGate) -> Array[String] {
let result : Array[String] = []
if gate.passed_checks < gate.required_checks {
result.push("checks")
}
if gate.critical_alerts > 0 {
result.push("critical_alerts")
}
if gate.evidence_count == 0 {
result.push("evidence")
}
if release_score(gate) < gate.minimum_score {
result.push("score")
}
result
}
///|
pub fn evaluate_release(gate : ReleaseGate) -> GateDecision {
let blockers = release_blockers(gate)
let released = blockers.length() == 0
{
released,
score: release_score(gate),
blockers,
recommendation: if released {
"release"
} else if gate.critical_alerts > 0 {
"hold_and_investigate"
} else {
"complete_open_actions"
},
}
}
///|
pub fn release_ready(gate : ReleaseGate) -> Bool {
evaluate_release(gate).released
}
///|
pub fn release_gap(gate : ReleaseGate) -> Double {
(gate.minimum_score - release_score(gate)).max(0.0)
}
///|
pub fn release_evidence_required(gate : ReleaseGate) -> Int {
if gate.evidence_count >= 10 {
0
} else {
10 - gate.evidence_count
}
}
///|
pub fn release_alert_burden(gate : ReleaseGate) -> Double {
gate.open_alerts.to_double() + gate.critical_alerts.to_double() * 3.0
}
///|
pub fn merge_release_gates(
left : ReleaseGate,
right : ReleaseGate,
) -> ReleaseGate {
release_gate(
name=left.name + "+" + right.name,
required_checks=left.required_checks + right.required_checks,
passed_checks=left.passed_checks + right.passed_checks,
open_alerts=left.open_alerts + right.open_alerts,
critical_alerts=left.critical_alerts + right.critical_alerts,
evidence_count=left.evidence_count + right.evidence_count,
minimum_score=left.minimum_score.max(right.minimum_score),
)
}
///|
pub fn release_audit_lines(gate : ReleaseGate) -> Array[String] {
let decision = evaluate_release(gate)
[
"name=\{gate.name}",
"score=\{decision.score}",
"released=\{decision.released}",
"blocker_count=\{decision.blockers.length()}",
]
}