///|
pub(all) enum ContractCheckKind {
RequiredNode
MinimumGraphSize
RequiredPolicy
RequiredControlPoint
Reachability
SeverityContract
} derive(Eq, Debug, ToJson)
///|
pub(all) struct ModelContract {
name : String
required_node_kinds : Array[String]
minimum_nodes : Int
minimum_edges : Int
required_policy_kinds : Array[String]
required_control_points : Array[String]
minimum_severity : String
} derive(Eq, Debug, ToJson)
///|
pub(all) struct ContractCheck {
kind : ContractCheckKind
name : String
passed : Bool
observed : String
expected : String
detail : String
} derive(Eq, Debug, ToJson)
///|
pub(all) struct ContractReport {
contract : String
passed : Bool
checks : Array[ContractCheck]
passed_count : Int
failed_count : Int
} derive(Eq, Debug, ToJson)
///|
pub fn default_model_contract() -> ModelContract {
{
name: "trusted-flow-production-model",
required_node_kinds: ["source", "sink"],
minimum_nodes: 2,
minimum_edges: 1,
required_policy_kinds: ["deny"],
required_control_points: [],
minimum_severity: "low",
}
}
///|
pub fn evaluate_contract(
model : Model,
contract : ModelContract,
) -> ContractReport {
let checks : Array[ContractCheck] = []
let mut passed_count = 0
let mut failed_count = 0
for required_kind in contract.required_node_kinds {
let observed = count_nodes_by_name(model, required_kind)
let passed = observed > 0
checks.push({
kind: RequiredNode,
name: required_kind,
passed,
observed: "\{observed}",
expected: "> 0",
detail: if passed {
"required node kind is present"
} else {
"required node kind is missing"
},
})
if passed {
passed_count += 1
} else {
failed_count += 1
}
}
let graph_size_passed = model.nodes.length() >= contract.minimum_nodes &&
model.edges.length() >= contract.minimum_edges
checks.push({
kind: MinimumGraphSize,
name: "graph-size",
passed: graph_size_passed,
observed: "nodes=\{model.nodes.length()}, edges=\{model.edges.length()}",
expected: "nodes >= \{contract.minimum_nodes}, edges >= \{contract.minimum_edges}",
detail: if graph_size_passed {
"graph has the minimum deployable shape"
} else {
"graph is too small for the declared contract"
},
})
if graph_size_passed {
passed_count += 1
} else {
failed_count += 1
}
for required_policy in contract.required_policy_kinds {
let observed = count_policies_by_name(model, required_policy)
let passed = observed > 0
checks.push({
kind: RequiredPolicy,
name: required_policy,
passed,
observed: "\{observed}",
expected: "> 0",
detail: if passed {
"required policy kind is present"
} else {
"required policy kind is missing"
},
})
if passed {
passed_count += 1
} else {
failed_count += 1
}
}
for control_point in contract.required_control_points {
let passed = has_named_node(model, control_point)
checks.push({
kind: RequiredControlPoint,
name: control_point,
passed,
observed: if passed {
"present"
} else {
"missing"
},
expected: "present",
detail: if passed {
"declared control point is available"
} else {
"declared control point is missing"
},
})
if passed {
passed_count += 1
} else {
failed_count += 1
}
}
let reachability_passed = all_sources_reach_sink(model)
checks.push({
kind: Reachability,
name: "source-to-sink-reachability",
passed: reachability_passed,
observed: if reachability_passed {
"reachable"
} else {
"not-reachable"
},
expected: "at least one source reaches a sink",
detail: if reachability_passed {
"the contract has an executable data-flow path"
} else {
"no source-to-sink path is executable"
},
})
if reachability_passed {
passed_count += 1
} else {
failed_count += 1
}
let severity_passed = minimum_severity_present(
model,
contract.minimum_severity,
)
checks.push({
kind: SeverityContract,
name: "minimum-severity",
passed: severity_passed,
observed: if severity_passed {
"satisfied"
} else {
"not-satisfied"
},
expected: contract.minimum_severity,
detail: if severity_passed {
"policy severity contract is satisfied"
} else {
"no policy meets the required severity"
},
})
if severity_passed {
passed_count += 1
} else {
failed_count += 1
}
{
contract: contract.name,
passed: failed_count == 0,
checks,
passed_count,
failed_count,
}
}
///|
pub fn contract_report_json(report : ContractReport) -> String {
report.to_json().stringify(indent=2)
}
///|
pub fn format_contract_report(report : ContractReport) -> String {
let out = StringBuilder()
out.write_string("contract=\{report.contract} passed=\{report.passed}")
out.write_string(" checks=\{report.passed_count}/\{report.checks.length()}")
for check in report.checks {
let status = if check.passed { "pass" } else { "fail" }
out.write_string("\n[\{status}] \{check.name}")
out.write_string(" observed=\{check.observed} expected=\{check.expected}")
out.write_string("\n \{check.detail}")
}
out.to_string()
}
///|
pub fn contract_is_release_ready(report : ContractReport) -> Bool {
report.passed
}
///|
fn count_nodes_by_name(model : Model, kind_name : String) -> Int {
let mut count = 0
for node in model.nodes {
if node_kind_name(node.kind) == kind_name {
count += 1
}
}
count
}
///|
fn count_policies_by_name(model : Model, policy_name : String) -> Int {
let mut count = 0
for policy in model.policies {
if rule_kind_name(policy.kind) == policy_name {
count += 1
}
}
count
}
///|
fn has_named_node(model : Model, name : String) -> Bool {
for node in model.nodes {
if node.name == name {
return true
}
}
false
}
///|
fn all_sources_reach_sink(model : Model) -> Bool {
let sources = graph_node_names_by_kind(model, Source)
if sources.length() == 0 {
return false
}
for source in sources {
if has_reachable_sink(model, source) {
return true
}
}
false
}
///|
fn minimum_severity_present(model : Model, minimum : String) -> Bool {
let target = severity_contract_rank(minimum)
for policy in model.policies {
if severity_contract_rank(policy.severity) >= target {
return true
}
}
false
}
///|
fn severity_contract_rank(severity : String) -> Int {
if severity == "high" {
3
} else if severity == "medium" {
2
} else if severity == "low" {
1
} else {
0
}
}