use std::path::Path;
use crate::ontology::rdf::Graph;
use crate::ontology::receipts;
pub mod apr_model;
pub mod binary;
pub mod claims;
pub mod cli_surface;
pub mod code;
pub mod covering;
pub mod csv;
pub mod example;
pub mod gguf;
pub mod json;
pub mod kernel;
pub mod kernel_cells;
pub mod lean;
pub mod llm_context;
pub mod parity_receipt;
pub mod pv_contract;
pub mod readme;
pub mod release_cells;
pub mod release_crux;
pub mod release_evidence;
pub mod release_inputs;
#[derive(Debug, Clone, Default)]
pub struct Extraction {
pub graph: Graph,
pub warnings: Vec<json::Warning>,
pub entities_extracted: Vec<String>,
pub gguf: gguf::GgufStats,
pub apr_model: apr_model::AprStats,
pub receipts: Vec<receipts::Receipt>,
pub resolve: receipts::ResolveStats,
pub code: code::CodeStats,
pub lean: lean::LeanStats,
pub example: example::ExampleStats,
pub binary: binary::BinaryStats,
pub kernel: kernel::KernelStats,
pub parity: parity_receipt::ParityStats,
pub github: json::github::GithubStats,
pub readme: claims::DocStats,
pub llm_context: claims::DocStats,
pub csv: claims::DocStats,
pub release: Option<release_evidence::ReleaseStats>,
pub type_closure_added: usize,
}
#[must_use]
pub fn sigma_of(contract_dir: &Path) -> Option<crate::ontology::sigma::Sigma> {
let text = std::fs::read_to_string(contract_dir.join("ontology.yaml")).ok()?;
let s = crate::ontology::sigma::Sigma::from_yaml(&text).ok()?;
s.check_integrity().ok()?;
Some(s)
}
pub fn materialize_type_closure(graph: &mut Graph, sigma: &crate::ontology::sigma::Sigma) -> usize {
use crate::ontology::rdf::{ont, Term, RDF_TYPE};
let mut adds: Vec<(String, String)> = Vec::new();
for c in sigma.concepts.keys() {
let supers = sigma.supers(c);
if supers.is_empty() {
continue;
}
for x in graph.instances_of(&ont(c)) {
adds.extend(supers.iter().map(|s| (x.to_string(), s.clone())));
}
}
let before = graph.len();
for (x, s) in adds {
graph.insert(x, RDF_TYPE, Term::iri(ont(&s)));
}
graph.len() - before
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExtractFailure {
Json(json::ExtractError),
Receipt(receipts::ReceiptError),
Release(release_inputs::ReleaseError),
ReservedEntityType { contract: String },
Kernel(kernel::KernelError),
}
impl std::fmt::Display for ExtractFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Json(e) => write!(f, "{e}"),
Self::Receipt(e) => write!(f, "receipt {e}"),
Self::Release(e) => write!(f, "{e}"),
Self::ReservedEntityType { contract } => write!(
f,
"contract `{contract}` declares entity.type `{}`, which is reserved — `<ONT_BASE>entity/` holds \
the entity classes, so its entity predicates would collide with them (PMAT-4160)",
crate::ontology::sigma::RESERVED_ENTITY_TYPE
),
Self::Kernel(e) => write!(f, "{e}"),
}
}
}
impl std::error::Error for ExtractFailure {}
pub fn all(contract_dir: &Path) -> Result<Extraction, ExtractFailure> {
all_with(contract_dir, None)
}
pub fn all_with(
contract_dir: &Path,
release: Option<&release_inputs::Subject>,
) -> Result<Extraction, ExtractFailure> {
let mut out = Extraction {
graph: pv_contract::extract(contract_dir),
..Extraction::default()
};
let root = contract_dir.parent().unwrap_or(contract_dir);
for (stem, _rel, doc) in pv_contract::documents(contract_dir) {
let entity_type = pv_contract::scalar(doc.get("entity").and_then(|e| e.get("type")));
if entity_type.as_deref() == Some(crate::ontology::sigma::RESERVED_ENTITY_TYPE) {
return Err(ExtractFailure::ReservedEntityType { contract: stem });
}
if !json::applies(&doc) {
continue;
}
let mut warnings =
json::extract_into(&mut out.graph, &stem, &doc, root).map_err(ExtractFailure::Json)?;
out.warnings.append(&mut warnings);
out.entities_extracted.push(stem);
}
out.gguf = gguf::extract(contract_dir, &mut out.graph);
out.apr_model = apr_model::extract(contract_dir, &mut out.graph);
out.receipts = receipts::read_all(root).map_err(ExtractFailure::Receipt)?;
out.resolve = receipts::resolve(&mut out.graph, &out.gguf.rungs, &out.receipts);
out.code = code::extract(contract_dir, &mut out.graph);
out.kernel = kernel::extract(&repo_root(contract_dir), &mut out.graph)
.map_err(ExtractFailure::Kernel)?;
out.lean = lean::extract(contract_dir, &mut out.graph);
out.example = example::extract(contract_dir, &mut out.graph);
out.binary = binary::extract(contract_dir, &mut out.graph);
out.parity = parity_receipt::extract(root, &mut out.graph);
let sigma = sigma_of(contract_dir);
let snapshot_types = sigma
.as_ref()
.map(json::github::snapshot_types)
.unwrap_or_default();
out.github = json::github::extract(&repo_root(contract_dir), &snapshot_types, &mut out.graph);
let ci = claims::ci_run_lines(&repo_root(contract_dir));
out.readme = readme::extract(contract_dir, &mut out.graph, &ci);
out.llm_context = llm_context::extract(contract_dir, &mut out.graph, &ci);
out.csv = csv::extract(contract_dir, &mut out.graph);
out.type_closure_added = sigma
.as_ref()
.map_or(0, |s| materialize_type_closure(&mut out.graph, s));
if let Some(subject) = release {
out.release = Some(
release_evidence::extract(&mut out.graph, contract_dir, subject)
.map_err(ExtractFailure::Release)?,
);
}
Ok(out)
}
#[must_use]
pub fn repo_root(contract_dir: &Path) -> std::path::PathBuf {
match contract_dir.parent() {
Some(p) if !p.as_os_str().is_empty() => p.to_path_buf(),
_ => std::path::PathBuf::from("."),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ontology::rdf::{ont, Term, RDF_TYPE};
fn sigma() -> crate::ontology::sigma::Sigma {
let text = std::fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../contracts/ontology.yaml"),
)
.expect("the repo's Σ is readable");
crate::ontology::sigma::Sigma::from_yaml(&text).expect("the repo's Σ parses")
}
#[test]
fn the_type_closure_counts_exactly_the_super_types_it_adds_and_a_second_pass_adds_none() {
let s = sigma();
let supers = s.supers("Kernel");
assert!(!supers.is_empty(), "Kernel has a super-concept");
let mut g = Graph::new();
for i in 0..5 {
g.insert(format!("urn:other:{i}"), "urn:p", Term::string("v"));
}
for k in ["urn:k", "urn:k2"] {
g.insert(k.to_string(), RDF_TYPE, Term::iri(ont("Kernel")));
}
let before = g.len();
let added = materialize_type_closure(&mut g, &s);
assert_eq!(added, 2 * supers.len());
assert_eq!(g.len(), before + added);
for sup in &supers {
let xs = g.instances_of(&ont(sup));
assert!(xs.contains(&"urn:k") && xs.contains(&"urn:k2"), "{sup}");
}
assert_eq!(materialize_type_closure(&mut g, &s), 0, "idempotent");
}
#[test]
fn an_extract_failure_displays_its_cause() {
let e = ExtractFailure::ReservedEntityType {
contract: "c.yaml".into(),
};
let msg = e.to_string();
assert!(
msg.starts_with("contract `c.yaml` declares entity.type `"),
"{msg}"
);
assert!(msg.ends_with("(PMAT-4160)"), "{msg}");
}
}