use std::collections::{BTreeMap, BTreeSet};
use crate::ontology::extract::gguf::{model, Rung};
use crate::ontology::rdf::{iri, Graph, Term};
use crate::ontology::receipts::{self, Receipt, Row};
use crate::ontology::verdict::Verdict;
#[must_use]
pub fn cell_id(rung: &str, host: &str) -> String {
format!("{rung}@{host}")
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CapabilityCells {
pub v_star: Option<String>,
pub domain: BTreeSet<String>,
pub cells: BTreeMap<String, Verdict>,
pub not_run: BTreeSet<String>,
pub refused_labels: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CellsError {
Version { file: String, version: String },
DuplicateRung { id: String, contracts: Vec<String> },
}
impl CellsError {
#[must_use]
pub fn file(&self) -> String {
match self {
Self::Version { file, .. } => file.clone(),
Self::DuplicateRung { contracts, .. } => contracts.join(", "),
}
}
}
impl std::fmt::Display for CellsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Version { file, version } => write!(
f,
"{file}: receipt version {version:?} is not dotted numerals, so the current release V* cannot be chosen — refused by name"
),
Self::DuplicateRung { id, contracts } => write!(
f,
"required rung id {id:?} is declared more than once ({}) — a capability cell is <rung>@<host>, so the two would merge; refused by name",
contracts.join(", ")
),
}
}
}
impl std::error::Error for CellsError {}
fn version_key(v: &str) -> Option<Vec<u64>> {
if v.is_empty() {
return None;
}
v.split('.')
.map(|p| {
if p.is_empty() || !p.bytes().all(|b| b.is_ascii_digit()) {
None
} else {
p.parse::<u64>().ok()
}
})
.collect()
}
fn v_star(receipts: &[Receipt]) -> Result<Option<String>, CellsError> {
let mut best: Option<(Vec<u64>, String)> = None;
for r in receipts {
let key = version_key(&r.version).ok_or_else(|| CellsError::Version {
file: r.file.clone(),
version: r.version.clone(),
})?;
if best.as_ref().is_none_or(|(k, _)| key > *k) {
best = Some((key, r.version.clone()));
}
}
Ok(best.map(|(_, v)| v))
}
fn row_verdict(rung: &Rung, row: &Row, file: &str, refused: &mut Vec<String>) -> Verdict {
let mut labelled: Option<Verdict> = None;
for label in &row.labels {
let v = match Verdict::from_label(label) {
Some(Verdict::Pass) => continue,
Some(v) => v,
None => {
refused.push(format!("{file}: {}: {label}", rung.id));
Verdict::Unknown(crate::ontology::verdict::Reason::NotRun)
}
};
labelled = Some(combine(labelled, v));
}
let measured = if receipts::row_is_green(rung, row) {
Verdict::Pass
} else {
Verdict::Fail
};
match labelled {
Some(l) => combine(Some(l), measured),
None => measured,
}
}
fn combine(acc: Option<Verdict>, v: Verdict) -> Verdict {
match (acc, v) {
(Some(Verdict::Unknown(r)), _) | (_, Verdict::Unknown(r)) => Verdict::Unknown(r),
(Some(Verdict::Fail), _) | (_, Verdict::Fail) => Verdict::Fail,
_ => Verdict::Pass,
}
}
fn unique_required_ids(rungs: &[Rung]) -> Result<(), CellsError> {
let mut seen: BTreeMap<&str, Vec<String>> = BTreeMap::new();
for r in rungs.iter().filter(|r| r.required) {
seen.entry(r.id.as_str())
.or_default()
.push(r.contract.clone());
}
match seen.into_iter().find(|(_, c)| c.len() > 1) {
Some((id, contracts)) => Err(CellsError::DuplicateRung {
id: id.to_string(),
contracts,
}),
None => Ok(()),
}
}
pub fn compute(rungs: &[Rung], all: &[Receipt]) -> Result<CapabilityCells, CellsError> {
unique_required_ids(rungs)?;
let v_star = v_star(all)?;
let hosts = receipts::receipt_hosts(all);
let mut out = CapabilityCells {
v_star: v_star.clone(),
..CapabilityCells::default()
};
for rung in rungs.iter().filter(|r| r.required) {
for host in receipts::expected_hosts(rung, &hosts) {
let id = cell_id(&rung.id, &host);
out.domain.insert(id.clone());
let mut cell: Option<Verdict> = None;
for rec in all
.iter()
.filter(|r| Some(&r.version) == v_star.as_ref() && r.host == host)
{
for row in rec.rows.iter().filter(|w| {
w.id == rung.id && w.sha256.as_deref() == Some(rung.sha256.as_str())
}) {
let v = row_verdict(rung, row, &rec.file, &mut out.refused_labels);
cell = Some(combine(cell, v));
}
}
match cell {
Some(v @ (Verdict::Pass | Verdict::Fail)) => {
out.cells.insert(id, v);
}
Some(v) => {
out.cells.insert(id.clone(), v);
out.not_run.insert(id);
}
None => {
out.not_run.insert(id);
}
}
}
}
out.refused_labels.sort();
out.refused_labels.dedup();
Ok(out)
}
pub fn apply(
g: &mut Graph,
rungs: &[Rung],
all: &[Receipt],
) -> Result<CapabilityCells, CellsError> {
let cc = compute(rungs, all)?;
for rung in rungs.iter().filter(|r| r.required) {
let s = iri("model", &rung.sha256);
let prefix = format!("{}@", rung.id);
for (id, v) in cc
.cells
.range(prefix.clone()..)
.take_while(|(k, _)| k.starts_with(&prefix))
{
let word = match v {
Verdict::Pass => "Pass",
Verdict::Fail => "Fail",
Verdict::Unknown(_) => "NotRun",
};
g.insert(
s.clone(),
model("capabilityCell"),
Term::string(format!("{id}={word}")),
);
}
for id in cc.not_run.iter().filter(|k| k.starts_with(&prefix)) {
g.insert(s.clone(), model("notRunCell"), Term::string(id));
}
for r in cc
.refused_labels
.iter()
.filter(|r| r.split(": ").nth(1) == Some(rung.id.as_str()))
{
g.insert(s.clone(), model("refusedLabel"), Term::string(r));
}
}
Ok(cc)
}
#[must_use]
pub fn not_run_edges(g: &Graph) -> usize {
let p = model("notRunCell");
g.iter().filter(|t| t.predicate == p).count()
}
#[cfg(test)]
#[path = "capability_cells_tests.rs"]
mod tests;