///|
pub(all) struct ChecksumEvidence {
manifest : String
algorithm : String
expected : String
observed : String?
status : String
} derive(ToJson)
///|
pub extend ChecksumEvidence with ToJson::{to_json}
///|
pub(all) struct FileAudit {
path : String
payload : Bool
present : Bool
size : Int64?
checksums : Array[ChecksumEvidence]
integrity_verified : Bool
} derive(ToJson)
///|
pub extend FileAudit with ToJson::{to_json}
///|
pub(all) struct AuditSummary {
manifest_count : Int32
expected_files : Int32
missing_files : Int32
mismatched_checksums : Int32
unavailable_checksums : Int32
unprotected_tags : Int32
pending_fetches : Int32
verified_algorithms : Array[String]
} derive(ToJson)
///|
pub extend AuditSummary with ToJson::{to_json}
///|
pub(all) struct BagAudit {
report : Report
summary : AuditSummary
files : Array[FileAudit]
located_diagnostics : Array[LocatedDiagnostic]
} derive(ToJson)
///|
pub extend BagAudit with ToJson::{to_json}
///|
fn[T] sorted_map_keys(values : Map[String, T]) -> Array[String] {
let keys = values.keys().collect()
keys.sort_by((a, b) => a.lexical_compare(b))
keys
}
///|
fn evidence_status(
expected : String,
observed : String?,
alg : Algorithm,
present : Bool,
) -> String {
if !present {
return "missing"
}
match observed {
Some(value) if valid_checksum(value, alg) =>
if value[:].to_lower().to_owned() == expected {
"verified"
} else {
"mismatch"
}
_ => "unavailable"
}
}
///|
/// A deterministic per-file explanation in addition to the authoritative report.
pub fn audit_bag(view : BagView) -> BagAudit {
let report = validate(view)
let inventory : Map[String, FileRecord] = Map([])
let paths : Map[String, Bool] = Map([])
let expected : Map[String, Array[ChecksumEvidence]] = Map([])
let located : Array[LocatedDiagnostic] = []
let algorithms : Map[String, Bool] = Map([])
let mut manifest_count = 0
for file in view.files {
inventory[file.path] = file
paths[file.path] = true
}
let names = view.files
.filter(f => manifest_name(f.path, false) || manifest_name(f.path, true))
.map(f => f.path)
names.sort_by((a, b) => a.lexical_compare(b))
for name in names {
if view.texts.get(name) is Some(text) {
let inspection = inspect_manifest(name, text)
located.append(inspection.diagnostics)
// Do not credit a partially parsed or duplicate-containing manifest.
if !inspection.diagnostics.is_empty() {
continue
}
let manifest = parse_manifest(name, text) catch { Invalid(_) => continue }
manifest_count = manifest_count + 1
for entry in manifest.entries {
paths[entry.path] = true
let record = inventory.get(entry.path)
let observed = match record {
Some(file) => file.digests.get(manifest.algorithm.name())
None => None
}
let status = evidence_status(
entry.checksum,
observed,
manifest.algorithm,
record is Some(_),
)
if status == "verified" {
algorithms[manifest.algorithm.name()] = true
}
let evidence : ChecksumEvidence = {
manifest: name,
algorithm: manifest.algorithm.name(),
expected: entry.checksum,
observed,
status,
}
match expected.get(entry.path) {
Some(items) => items.push(evidence)
None => expected[entry.path] = [evidence]
}
}
} else {
located.push({
path: name,
line: 0,
code: "manifest_text",
message: "manifest text is unavailable",
})
}
}
let files : Array[FileAudit] = []
let mut missing_files = 0
let mut mismatched_checksums = 0
let mut unavailable_checksums = 0
let mut unprotected_tags = 0
for path in sorted_map_keys(paths) {
let present = inventory.contains(path)
let payload = path.has_prefix("data/")
let checksums = expected.get(path).unwrap_or([])
let integrity_verified = !checksums.is_empty() &&
checksums.all(e => e.status == "verified")
if !present {
missing_files = missing_files + 1
}
for check in checksums {
if check.status == "mismatch" {
mismatched_checksums = mismatched_checksums + 1
}
if check.status == "unavailable" {
unavailable_checksums = unavailable_checksums + 1
}
}
if !payload && !manifest_name(path, true) && !integrity_verified {
unprotected_tags = unprotected_tags + 1
}
files.push({
path,
payload,
present,
size: inventory.get(path).map(f => f.size),
checksums,
integrity_verified,
})
}
let pending_fetches = match view.texts.get("fetch.txt") {
Some(text) => {
let fetch = parse_fetch(text) catch { Invalid(_) => [] }
fetch.filter(e => !inventory.contains(e.path)).length()
}
None => 0
}
{
report,
summary: {
manifest_count,
expected_files: expected.length(),
missing_files,
mismatched_checksums,
unavailable_checksums,
unprotected_tags,
pending_fetches,
verified_algorithms: sorted_map_keys(algorithms),
},
files,
located_diagnostics: located,
}
}
///|
pub(all) struct DiagnosticGroup {
code : String
errors : Int32
warnings : Int32
paths : Array[String]
} derive(ToJson)
///|
pub extend DiagnosticGroup with ToJson::{to_json}
///|
pub fn group_diagnostics(items : Array[Diagnostic]) -> Array[DiagnosticGroup] {
let groups : Map[String, Array[Diagnostic]] = Map([])
for item in items {
match groups.get(item.code) {
Some(group) => group.push(item)
None => groups[item.code] = [item]
}
}
let result : Array[DiagnosticGroup] = []
for code in sorted_map_keys(groups) {
let group = groups[code]
let paths : Map[String, Bool] = Map([])
for item in group {
paths[item.path] = true
}
result.push({
code,
errors: group.filter(d => d.severity == Error).length(),
warnings: group.filter(d => d.severity == Warning).length(),
paths: sorted_map_keys(paths),
})
}
result
}