#![allow(clippy::unwrap_used, clippy::unnecessary_debug_formatting)]
use chrono::Utc;
use ggen_graph::graph::serialize::serialize_to_string;
use oxigraph::io::{RdfFormat, RdfParser};
use oxigraph::model::{BlankNode, GraphName, Literal, NamedNode, NamedOrBlankNode, Quad, Term};
use oxigraph::store::Store;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
fn check_uri(uri: &str) -> Option<String> {
let lower = uri.to_lowercase();
if !uri.starts_with("file://") && lower.contains("gall") {
return Some(format!("Banned 'gall' string in RDF URI: {}", uri));
}
let vocabularies: &[(&str, &[&str])] = &[
(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#",
&[
"type",
"Property",
"Statement",
"subject",
"predicate",
"object",
"first",
"rest",
"nil",
"List",
"Alt",
"Bag",
"Seq",
"value",
],
),
(
"http://www.w3.org/2000/01/rdf-schema#",
&[
"label",
"comment",
"subClassOf",
"subPropertyOf",
"domain",
"range",
"Class",
"Resource",
"Datatype",
"Container",
"member",
"seeAlso",
"isDefinedBy",
],
),
(
"http://www.w3.org/2002/07/owl#",
&[
"Ontology",
"Class",
"ObjectProperty",
"DatatypeProperty",
"Restriction",
"onProperty",
"allValuesFrom",
"someValuesFrom",
"hasValue",
"minCardinality",
"maxCardinality",
"cardinality",
"equivalentClass",
"equivalentProperty",
"inverseOf",
"sameAs",
"differentFrom",
"Nothing",
"Thing",
],
),
(
"http://www.w3.org/2001/XMLSchema#",
&[
"string",
"boolean",
"decimal",
"float",
"double",
"duration",
"dateTime",
"time",
"date",
"gYearMonth",
"gYear",
"gMonthDay",
"gDay",
"gMonth",
"hexBinary",
"base64Binary",
"anyURI",
"QName",
"NOTATION",
"normalizedString",
"token",
"language",
"NMTOKEN",
"NMTOKENS",
"Name",
"NCName",
"ID",
"IDREF",
"IDREFS",
"ENTITY",
"ENTITIES",
"integer",
"nonPositiveInteger",
"negativeInteger",
"long",
"int",
"short",
"byte",
"nonNegativeInteger",
"unsignedLong",
"unsignedInt",
"unsignedShort",
"unsignedByte",
"positiveInteger",
],
),
(
"http://www.w3.org/ns/prov#",
&[
"Entity",
"Activity",
"Agent",
"wasGeneratedBy",
"wasDerivedFrom",
"wasAttributedTo",
"startedAtTime",
"endedAtTime",
"used",
"wasInformedBy",
"actedOnBehalfOf",
"wasAssociatedWith",
"Plan",
"Role",
"Person",
"Organization",
"SoftwareAgent",
"Location",
"atLocation",
"value",
],
),
(
"http://www.w3.org/ns/dcat#",
&[
"Dataset",
"Distribution",
"Catalog",
"CatalogRecord",
"DataService",
"contactPoint",
"keyword",
"publisher",
"theme",
"accessURL",
"downloadURL",
"mediaType",
"spatial",
"temporal",
],
),
(
"http://purl.org/dc/terms/",
&[
"identifier",
"title",
"description",
"created",
"modified",
"type",
"format",
"subject",
"publisher",
"creator",
"contributor",
"rights",
"language",
"source",
"relation",
"coverage",
"spatial",
"temporal",
"date",
"issued",
"alternative",
],
),
(
"http://purl.org/dc/elements/1.1/",
&[
"title",
"description",
"creator",
"publisher",
"date",
"type",
"format",
"subject",
"contributor",
"rights",
"language",
"source",
"relation",
"coverage",
],
),
(
"http://www.w3.org/2004/02/skos/core#",
&[
"Concept",
"ConceptScheme",
"prefLabel",
"altLabel",
"hiddenLabel",
"notation",
"note",
"changeNote",
"definition",
"editorialNote",
"example",
"historyNote",
"scopeNote",
"broader",
"narrower",
"related",
"inScheme",
"semanticRelation",
],
),
(
"http://www.w3.org/ns/shacl#",
&[
"ValidationReport",
"ValidationResult",
"conforms",
"result",
"resultSeverity",
"resultMessage",
"focusNode",
"resultPath",
"value",
"sourceConstraintComponent",
"sourceShape",
"NodeShape",
"PropertyShape",
"property",
"path",
"datatype",
"class",
"minCount",
"maxCount",
"severity",
"Violation",
"Info",
"Warning",
"SPARQLConstraint",
"select",
"ask",
"message",
],
),
(
"http://www.w3.org/2006/time#",
&[
"Instant",
"Interval",
"DateTimeInterval",
"TemporalEntity",
"inXSDDateTime",
"inXSDDateTimeStamp",
"hasBeginning",
"hasEnd",
"inside",
"hasTRS",
],
),
(
"http://spdx.org/rdf/terms#",
&[
"File",
"Checksum",
"checksum",
"algorithm",
"checksumValue",
"checksumAlgorithm_sha256",
"checksumAlgorithm_blake3",
"fileName",
"byteSize",
],
),
(
"http://www.ocel-standard.org/ns#",
&[
"Event",
"Object",
"log",
"events",
"objects",
"time",
"type",
"attributes",
],
),
(
"http://www.w3.org/ns/ocel#",
&[
"Event",
"Object",
"log",
"events",
"objects",
"time",
"type",
"attributes",
],
),
(
"http://purl.org/vocab/vann/",
&["preferredNamespacePrefix", "preferredNamespaceUri"],
),
(
"http://xmlns.com/foaf/0.1/",
&["Person", "name", "mbox", "depiction", "homepage"],
),
];
for (prefix_uri, whitelist) in vocabularies {
if let Some(local_name) = uri.strip_prefix(prefix_uri) {
if !whitelist.contains(&local_name) {
return Some(format!("Namespace laundering detected: URI {} is in namespace {} but local name '{}' is not whitelisted", uri, prefix_uri, local_name));
}
return None;
}
}
if uri.starts_with("http://example.org/")
|| uri.starts_with("file://")
|| uri.starts_with("https://schema.org/")
|| uri.starts_with("http://schema.org/")
{
return None;
}
Some(format!(
"Banned or unregistered namespace URI used: {}",
uri
))
}
fn scan_file_for_private_namespaces(
path: &Path,
) -> Result<Option<String>, Box<dyn std::error::Error>> {
let mut content = String::new();
File::open(path)?.read_to_string(&mut content)?;
let allowed_prefixes = [
"rdf", "rdfs", "owl", "xsd", "prov", "dcat", "dcterms", "dc", "skos", "sh", "time", "spdx",
"ocel", "schema", "mcp", "ex", "mp", "vann", "foaf",
];
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("@prefix") {
if let Some(col_idx) = trimmed.find(':') {
let prefix_part = &trimmed["@prefix".len()..col_idx];
let prefix_name = prefix_part.trim();
if !prefix_name.is_empty() && !allowed_prefixes.contains(&prefix_name) {
return Ok(Some(format!("Banned prefix declared: {}", prefix_name)));
}
if prefix_name.to_lowercase().contains("gall") {
return Ok(Some(format!(
"Banned string 'gall' in prefix name: {}",
prefix_name
)));
}
}
}
}
let store = Store::new()?;
let f = File::open(path)?;
if let Ok(()) = store.load_from_reader(RdfParser::from_format(RdfFormat::Turtle), f) {
for quad_res in store.quads_for_pattern(None, None, None, None) {
let quad = quad_res?;
let pred_uri = quad.predicate.as_str();
if let Some(err) = check_uri(pred_uri) {
return Ok(Some(err));
}
if let NamedOrBlankNode::NamedNode(n) = &quad.subject {
let s_uri = n.as_str();
if let Some(err) = check_uri(s_uri) {
return Ok(Some(err));
}
}
if let Term::NamedNode(n) = &quad.object {
let o_uri = n.as_str();
if let Some(err) = check_uri(o_uri) {
return Ok(Some(err));
}
}
}
}
Ok(None)
}
fn scan_dir_for_violations(
dir: &Path, violations: &mut Vec<(PathBuf, String)>,
) -> Result<(), Box<dyn std::error::Error>> {
if !dir.exists() {
return Ok(());
}
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
scan_dir_for_violations(&path, violations)?;
} else if path.extension().is_some_and(|ext| ext == "ttl") {
let filename = path.file_name().unwrap_or_default().to_string_lossy();
if filename != "gall_decision.delta.ttl"
&& filename != "gall_code_evaluation.receipt.ttl"
{
if let Some(detail) = scan_file_for_private_namespaces(&path)? {
violations.push((path, detail));
}
}
}
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let start_time = Utc::now();
let workspace_root = std::env::current_dir()?;
let audit_dir = workspace_root.join("crates/ggen-graph/audit");
let hook_pack_path = workspace_root.join("crates/ggen-graph/hooks/gall-code-evaluation.ttl");
println!("W7: Actuating Hook Evaluation Runtime...");
let store = Store::new()?;
if hook_pack_path.exists() {
let f = File::open(&hook_pack_path)?;
store.load_from_reader(RdfParser::from_format(RdfFormat::Turtle), f)?;
} else {
eprintln!("FAIL: Hook pack missing at {:?}", hook_pack_path);
std::process::exit(1);
}
let mut evidence_files = Vec::new();
if audit_dir.exists() {
for entry in std::fs::read_dir(&audit_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "ttl") {
let filename = path.file_name().unwrap_or_default().to_string_lossy();
if filename != "gall_decision.delta.ttl"
&& filename != "gall_code_evaluation.final.ttl"
&& filename != "gall_code_evaluation.receipt.ttl"
{
println!("Loading evidence: {:?}", path);
let f = File::open(&path)?;
store.load_from_reader(RdfParser::from_format(RdfFormat::Turtle), f)?;
evidence_files.push(path);
}
}
}
}
if evidence_files.is_empty() {
eprintln!("FAIL: No evidence graphs found in {:?}", audit_dir);
std::process::exit(1);
}
let find_hooks_query = "
PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX prov: <http://www.w3.org/ns/prov#>
SELECT ?name ?query WHERE {
?h a sh:NodeShape , prov:Plan ;
dcterms:identifier ?name ;
prov:value ?query .
}
";
let mut hooks = Vec::new();
#[allow(deprecated)]
let query_results = store.query(find_hooks_query)?;
if let oxigraph::sparql::QueryResults::Solutions(solutions) = query_results {
for sol in solutions {
let sol = sol?;
let name = match sol.get("name") {
Some(Term::Literal(lit)) => lit.value().to_string(),
_ => continue,
};
let query = match sol.get("query") {
Some(Term::Literal(lit)) => lit.value().to_string(),
_ => continue,
};
hooks.push((name, query));
}
}
println!("Found {} hooks to actuate.", hooks.len());
let mut delta_quads = Vec::new();
for (name, query) in hooks {
println!("Actuating hook: {}...", name);
#[allow(deprecated)]
let construct_results = store.query(&query)?;
if let oxigraph::sparql::QueryResults::Graph(triples) = construct_results {
for triple in triples {
let triple = triple?;
let quad = Quad::new(
triple.subject,
triple.predicate,
triple.object,
GraphName::DefaultGraph,
);
delta_quads.push(quad);
}
}
}
let mut scan_violations = Vec::new();
scan_dir_for_violations(
&workspace_root.join("crates/ggen-graph/hooks"),
&mut scan_violations,
)?;
scan_dir_for_violations(
&workspace_root.join("crates/ggen-graph/audit"),
&mut scan_violations,
)?;
scan_dir_for_violations(&workspace_root.join("docs"), &mut scan_violations)?;
scan_dir_for_violations(&workspace_root.join("schema"), &mut scan_violations)?;
let conforms = scan_violations.is_empty();
if !conforms {
println!("R7 scan failed with {} violations.", scan_violations.len());
for (p, detail) in &scan_violations {
println!(" Violation in {:?}: {}", p, detail);
}
} else {
println!("R7 scan passed.");
}
let r7_node = NamedNode::new("http://example.org/#R7")?;
delta_quads.push(Quad::new(
NamedOrBlankNode::NamedNode(r7_node.clone()),
NamedNode::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
Term::NamedNode(NamedNode::new(
"http://www.w3.org/ns/shacl#ValidationReport",
)?),
GraphName::DefaultGraph,
));
delta_quads.push(Quad::new(
NamedOrBlankNode::NamedNode(r7_node.clone()),
NamedNode::new("http://purl.org/dc/terms/identifier")?,
Term::Literal(Literal::new_simple_literal("R7")),
GraphName::DefaultGraph,
));
delta_quads.push(Quad::new(
NamedOrBlankNode::NamedNode(r7_node.clone()),
NamedNode::new("http://www.w3.org/ns/shacl#conforms")?,
Term::Literal(Literal::new_typed_literal(
if conforms { "true" } else { "false" },
NamedNode::new("http://www.w3.org/2001/XMLSchema#boolean")?,
)),
GraphName::DefaultGraph,
));
if !conforms {
let result_node = BlankNode::default();
delta_quads.push(Quad::new(
NamedOrBlankNode::NamedNode(r7_node.clone()),
NamedNode::new("http://www.w3.org/ns/shacl#result")?,
Term::BlankNode(result_node.clone()),
GraphName::DefaultGraph,
));
delta_quads.push(Quad::new(
NamedOrBlankNode::BlankNode(result_node.clone()),
NamedNode::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
Term::NamedNode(NamedNode::new(
"http://www.w3.org/ns/shacl#ValidationResult",
)?),
GraphName::DefaultGraph,
));
delta_quads.push(Quad::new(
NamedOrBlankNode::BlankNode(result_node.clone()),
NamedNode::new("http://www.w3.org/ns/shacl#resultSeverity")?,
Term::NamedNode(NamedNode::new("http://www.w3.org/ns/shacl#Violation")?),
GraphName::DefaultGraph,
));
let error_msg = format!(
"Project-private prefix/namespace violations: {:?}",
scan_violations
);
delta_quads.push(Quad::new(
NamedOrBlankNode::BlankNode(result_node.clone()),
NamedNode::new("http://www.w3.org/ns/shacl#resultMessage")?,
Term::Literal(Literal::new_simple_literal(error_msg)),
GraphName::DefaultGraph,
));
} else {
delta_quads.push(Quad::new(
NamedOrBlankNode::NamedNode(r7_node.clone()),
NamedNode::new("http://purl.org/dc/terms/description")?,
Term::Literal(Literal::new_simple_literal(
"R7 Public Vocabulary Gate passed.",
)),
GraphName::DefaultGraph,
));
}
let delta_turtle = serialize_to_string(&delta_quads, RdfFormat::Turtle)?;
std::fs::write(audit_dir.join("gall_decision.delta.ttl"), &delta_turtle)?;
println!("W7: gall_decision.delta.ttl written successfully.");
let mut failed = !conforms;
let mut hook_reports = std::collections::HashSet::new();
for quad in &delta_quads {
if quad.predicate.as_str() == "http://www.w3.org/ns/shacl#conforms" {
if let Term::Literal(lit) = &quad.object {
if lit.value() == "false" {
failed = true;
}
}
}
if quad.predicate.as_str() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" {
if let Term::NamedNode(nn) = &quad.object {
if nn.as_str() == "http://www.w3.org/ns/shacl#ValidationReport" {
hook_reports.insert(quad.subject.clone());
}
}
}
}
let final_store = Store::new()?;
for q in &delta_quads {
final_store.insert(q)?;
}
let final_status_node = NamedNode::new("http://example.org/#evaluation_final")?;
final_store.insert(&Quad::new(
NamedOrBlankNode::NamedNode(final_status_node.clone()),
NamedNode::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?,
Term::NamedNode(NamedNode::new(
"http://www.w3.org/ns/shacl#ValidationReport",
)?),
GraphName::DefaultGraph,
))?;
final_store.insert(&Quad::new(
NamedOrBlankNode::NamedNode(final_status_node.clone()),
NamedNode::new("http://www.w3.org/ns/shacl#conforms")?,
Term::Literal(Literal::new_typed_literal(
if failed { "false" } else { "true" },
NamedNode::new("http://www.w3.org/2001/XMLSchema#boolean")?,
)),
GraphName::DefaultGraph,
))?;
for report_node in hook_reports {
final_store.insert(&Quad::new(
NamedOrBlankNode::NamedNode(final_status_node.clone()),
NamedNode::new("http://www.w3.org/ns/shacl#result")?,
Term::from(report_node),
GraphName::DefaultGraph,
))?;
}
final_store.insert(&Quad::new(
NamedOrBlankNode::NamedNode(final_status_node.clone()),
NamedNode::new("http://purl.org/dc/terms/description")?,
Term::Literal(Literal::new_simple_literal(if failed {
"Evaluation Refused: Conformance failure or hook violations detected"
} else {
"Evaluation Passed: All hooks and R7 scan conform"
})),
GraphName::DefaultGraph,
))?;
let mut final_output_quads = Vec::new();
for q in final_store.quads_for_pattern(None, None, None, None) {
final_output_quads.push(q?);
}
let final_turtle = serialize_to_string(&final_output_quads, RdfFormat::Turtle)?;
std::fs::write(
audit_dir.join("gall_code_evaluation.final.ttl"),
&final_turtle,
)?;
println!("W7: gall_code_evaluation.final.ttl written successfully.");
let mut pv_ttl = String::new();
pv_ttl.push_str("@base <http://example.org/> .\n");
pv_ttl.push_str("@prefix sh: <http://www.w3.org/ns/shacl#> .\n");
pv_ttl.push_str("@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n");
pv_ttl.push_str("@prefix dcterms: <http://purl.org/dc/terms/> .\n\n");
pv_ttl.push_str("<#public_vocab_report> a sh:ValidationReport ;\n");
pv_ttl.push_str(&format!(
" sh:conforms \"{}\"^^xsd:boolean ;\n",
conforms
));
pv_ttl.push_str(" dcterms:identifier \"public_vocab.validation\" ;\n");
pv_ttl.push_str(" dcterms:description \"R7 Public Vocabulary Gate Report\" ;\n");
if !conforms {
pv_ttl.push_str(" sh:result [\n");
pv_ttl.push_str(" a sh:ValidationResult ;\n");
pv_ttl.push_str(" sh:resultSeverity sh:Violation ;\n");
let esc_msg = format!(
"Project-private prefix/namespace violations: {:?}",
scan_violations
)
.replace('"', "\\\"");
pv_ttl.push_str(&format!(" sh:resultMessage \"{}\"\n", esc_msg));
pv_ttl.push_str(" ] .\n");
} else {
pv_ttl.push_str(" dcterms:title \"All prefixes and vocabularies conform to public interop rules.\" .\n");
}
std::fs::write(audit_dir.join("public_vocab.validation.ttl"), &pv_ttl)?;
let mut ha_ttl = String::new();
ha_ttl.push_str("@base <http://example.org/> .\n");
ha_ttl.push_str("@prefix sh: <http://www.w3.org/ns/shacl#> .\n");
ha_ttl.push_str("@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n");
ha_ttl.push_str("@prefix dcterms: <http://purl.org/dc/terms/> .\n\n");
ha_ttl.push_str("<#hook_actuation_report> a sh:ValidationReport ;\n");
ha_ttl.push_str(&format!(" sh:conforms \"{}\"^^xsd:boolean ;\n", !failed));
ha_ttl.push_str(" dcterms:identifier \"hook_actuation.validation\" ;\n");
ha_ttl.push_str(
" dcterms:description \"Hook Actuation and Execution Conformance Report\" ;\n",
);
if failed {
ha_ttl.push_str(" sh:result [\n");
ha_ttl.push_str(" a sh:ValidationResult ;\n");
ha_ttl.push_str(" sh:resultSeverity sh:Violation ;\n");
ha_ttl.push_str(" sh:resultMessage \"One or more evaluation hooks failed or returned non-conformance.\"\n");
ha_ttl.push_str(" ] .\n");
} else {
ha_ttl.push_str(" dcterms:title \"All hooks executed successfully and conformed.\" .\n");
}
std::fs::write(audit_dir.join("hook_actuation.validation.ttl"), &ha_ttl)?;
let mut evidence_hashes = Vec::new();
for path in evidence_files {
let mut content = Vec::new();
File::open(&path)?.read_to_end(&mut content)?;
let hash = blake3::hash(&content).to_hex().to_string();
let name = path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
evidence_hashes.push((name, hash));
}
let delta_path = audit_dir.join("gall_decision.delta.ttl");
let mut delta_content = Vec::new();
File::open(&delta_path)?.read_to_end(&mut delta_content)?;
let delta_hash = blake3::hash(&delta_content).to_hex().to_string();
let final_path = audit_dir.join("gall_code_evaluation.final.ttl");
let mut final_content = Vec::new();
File::open(&final_path)?.read_to_end(&mut final_content)?;
let final_hash = blake3::hash(&final_content).to_hex().to_string();
let mut combined_hasher = blake3::Hasher::new();
for (_, h) in &evidence_hashes {
combined_hasher.update(h.as_bytes());
}
combined_hasher.update(delta_hash.as_bytes());
combined_hasher.update(final_hash.as_bytes());
let receipt_hash = combined_hasher.finalize().to_hex().to_string();
let mut requesting_activity = None;
let find_requester_query = "
PREFIX prov: <http://www.w3.org/ns/prov#>
PREFIX dcterms: <http://purl.org/dc/terms/>
SELECT ?activity WHERE {
?activity a prov:Activity .
OPTIONAL { ?activity dcterms:type ?type }
BIND(IF(BOUND(?type) && ?type = \"BoundaryExecutionRequest\", 1, 0) AS ?priority)
} ORDER BY DESC(?priority) ?activity LIMIT 1
";
#[allow(deprecated)]
let requester_results = store.query(find_requester_query)?;
if let oxigraph::sparql::QueryResults::Solutions(mut solutions) = requester_results {
if let Some(Ok(sol)) = solutions.next() {
if let Some(term) = sol.get("activity") {
requesting_activity = Some(match term {
Term::NamedNode(n) => format!("<{}>", n.as_str()),
Term::BlankNode(b) => format!("_:{}", b.as_str()),
_ => "".to_string(),
});
}
}
}
let receipt_id_lit = Literal::new_simple_literal(uuid::Uuid::new_v4().to_string());
let mut receipt_ttl = format!(
"@base <http://example.org/> .\n\
@prefix prov: <http://www.w3.org/ns/prov#> .\n\
@prefix dcat: <http://www.w3.org/ns/dcat#> .\n\
@prefix dcterms: <http://purl.org/dc/terms/> .\n\
@prefix spdx: <http://spdx.org/rdf/terms#> .\n\
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n\n\
<#evaluation_receipt> a prov:Entity, dcat:Dataset ;\n\
dcterms:identifier {} ;\n\
dcterms:created \"{}\"^^xsd:dateTime ;\n\
dcterms:description \"Witnessed Agent Truthfulness code evaluation receipt\" ;\n\
prov:wasGeneratedBy <#activity_hook_actuation> ;\n\
prov:used <#evaluation_final_report> ;\n\
spdx:checksum [\n\
a spdx:Checksum ;\n\
spdx:algorithm <http://spdx.org/rdf/terms#checksumAlgorithm_blake3> ;\n\
spdx:checksumValue \"{}\"\n\
] .\n\n\
<#activity_hook_actuation> a prov:Activity ;\n\
dcterms:title \"Hook Actuation Activity\" ;\n\
prov:startedAtTime \"{}\"^^xsd:dateTime ;\n\
prov:endedAtTime \"{}\"^^xsd:dateTime .\n\n",
receipt_id_lit,
Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
receipt_hash,
start_time.to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
);
if let Some(req_act) = requesting_activity {
receipt_ttl.push_str(&format!(
"<#activity_hook_actuation> prov:wasInformedBy {} .\n\n",
req_act
));
}
let final_report_title = Literal::new_simple_literal("Final Evaluation Report");
let title = Literal::new_simple_literal("Evaluation Decision Delta");
receipt_ttl.push_str(&format!(
"<#evaluation_final_report> a prov:Entity, dcat:Distribution ;\n\
dcterms:title {} ;\n\
dcat:downloadURL <file://witnessed_code_evaluation.final.ttl> ;\n\
spdx:checksum [\n\
a spdx:Checksum ;\n\
spdx:algorithm <http://spdx.org/rdf/terms#checksumAlgorithm_blake3> ;\n\
spdx:checksumValue \"{}\"\n\
] .\n\n\
<#evaluation_delta> a prov:Entity, dcat:Distribution ;\n\
dcterms:title {} ;\n\
dcat:downloadURL <file://witnessed_decision.delta.ttl> ;\n\
spdx:checksum [\n\
a spdx:Checksum ;\n\
spdx:algorithm <http://spdx.org/rdf/terms#checksumAlgorithm_blake3> ;\n\
spdx:checksumValue \"{}\"\n\
] .\n\n",
final_report_title, final_hash, title, delta_hash
));
for (i, (name, hash)) in evidence_hashes.iter().enumerate() {
let encoded_name = urlencoding::encode(name);
let sanitized_name = encoded_name
.replace("gall", "witnessed")
.replace("GALL", "witnessed");
let title_lit = Literal::new_simple_literal(format!("Evidence: {}", name));
receipt_ttl.push_str(&format!(
"<#evidence_{}> a prov:Entity, dcat:Distribution ;\n\
dcterms:title {} ;\n\
dcat:downloadURL <file://{}> ;\n\
spdx:checksum [\n\
a spdx:Checksum ;\n\
spdx:algorithm <http://spdx.org/rdf/terms#checksumAlgorithm_blake3> ;\n\
spdx:checksumValue \"{}\"\n\
] .\n\n",
i, title_lit, sanitized_name, hash
));
receipt_ttl.push_str(&format!(
"<#evaluation_receipt> prov:used <#evidence_{}> .\n\n",
i
));
}
std::fs::write(
audit_dir.join("gall_code_evaluation.receipt.ttl"),
receipt_ttl,
)?;
println!("W7: gall_code_evaluation.receipt.ttl written successfully.");
Ok(())
}