///|
pub(all) enum RiskLevel {
RiskLow
RiskMedium
RiskHigh
RiskCritical
} derive(Eq)
///|
pub(all) struct PolicyAdvice {
path : String
current : CachePolicy
recommended : CachePolicy
reason : String
} derive(Eq)
///|
pub(all) struct QualityReport {
score : Int
risk : RiskLevel
entries : Int
total_bytes : Int
errors : Int
warnings : Int
generated_assets : Int
runtime_assets : Int
no_store_assets : Int
immutable_assets : Int
missing_tag_assets : Int
oversized_assets : Int
duplicate_fingerprint_assets : Int
directory_count : Int
extension_count : Int
policy_advice : Array[PolicyAdvice]
findings : Array[Finding]
} derive(Eq)
///|
pub(all) struct DirectoryProfile {
directory : String
entries : Int
bytes : Int
generated_entries : Int
runtime_entries : Int
media_entries : Int
source_entries : Int
document_entries : Int
warnings : Int
} derive(Eq)
///|
pub fn RiskLevel::to_wire(self : RiskLevel) -> String {
match self {
RiskLow => "low"
RiskMedium => "medium"
RiskHigh => "high"
RiskCritical => "critical"
}
}
///|
pub fn parse_risk_level(raw : String) -> RiskLevel {
match raw.trim().to_owned().to_lower() {
"low" => RiskLow
"medium" => RiskMedium
"high" => RiskHigh
"critical" => RiskCritical
_ => RiskMedium
}
}
///|
pub fn assess_quality(manifest : Manifest) -> QualityReport {
let base_findings = validate_manifest(manifest)
let extra_findings = quality_findings(manifest)
let all_findings = merge_findings(base_findings, extra_findings)
let score = quality_score(manifest, all_findings)
let summary = summarize(manifest)
let policy_advice = policy_advice_for_manifest(manifest)
{
score,
risk: risk_from_score(score, all_findings),
entries: manifest.entries.length(),
total_bytes: summary.total_bytes,
errors: count_findings(all_findings, Error),
warnings: count_findings(all_findings, Warning),
generated_assets: assets_with_bucket(manifest, BucketGenerated).length(),
runtime_assets: assets_with_bucket(manifest, BucketRuntime).length(),
no_store_assets: assets_with_policy(manifest, NoStore).length(),
immutable_assets: assets_with_policy(manifest, Immutable).length(),
missing_tag_assets: assets_without_tags(manifest).length(),
oversized_assets: oversized_assets(manifest, 1048576).length(),
duplicate_fingerprint_assets: duplicate_asset_count(manifest),
directory_count: manifest_directories(manifest).length(),
extension_count: manifest_extensions(manifest).length(),
policy_advice,
findings: all_findings,
}
}
///|
pub fn quality_score(manifest : Manifest, findings : Array[Finding]) -> Int {
let mut score = 100
for finding in findings {
match finding.level {
Error => score = score - 20
Warning => score = score - 6
Info => score = score - 1
}
}
if manifest.entries.is_empty() {
score = score - 30
}
if manifest.entries.length() > 0 && manifest.entries.length() < 3 {
score = score - 8
}
if manifest_directories(manifest).length() == 1 &&
manifest.entries.length() > 8 {
score = score - 4
}
if assets_without_tags(manifest).length() > 0 {
score = score - 4
}
if policy_advice_for_manifest(manifest).length() > 0 {
score = score - 4
}
clamp_score(score)
}
///|
pub fn risk_from_score(score : Int, findings : Array[Finding]) -> RiskLevel {
if count_findings(findings, Error) > 0 {
return RiskCritical
}
if score >= 85 {
RiskLow
} else if score >= 70 {
RiskMedium
} else if score >= 50 {
RiskHigh
} else {
RiskCritical
}
}
///|
pub fn quality_findings(manifest : Manifest) -> Array[Finding] {
let findings : Array[Finding] = []
for entry in manifest.entries {
if entry.tags.is_empty() {
findings.push(
quality_warning(
"tag-missing",
Some(entry.path),
"asset has no tag and may be harder to audit",
),
)
}
if entry.bytes > 1048576 {
findings.push(
quality_warning(
"asset-large",
Some(entry.path),
"asset is larger than one mebibyte",
),
)
}
if entry.bytes == 0 {
findings.push(
quality_warning("asset-empty", Some(entry.path), "asset has zero bytes"),
)
}
if classify_asset(entry) == BucketGenerated && entry.policy == Immutable {
findings.push(
quality_info(
"generated-immutable",
Some(entry.path),
"generated asset is immutable; ensure rebuilds update the fingerprint",
),
)
}
if classify_asset(entry) == BucketRuntime && entry.policy == Immutable {
findings.push(
quality_warning(
"runtime-immutable",
Some(entry.path),
"runtime assets should usually revalidate or avoid storage",
),
)
}
if path_is_hidden(entry.path) is Ok(true) {
findings.push(
quality_warning(
"hidden-asset",
Some(entry.path),
"hidden files are easy to miss during release review",
),
)
}
}
for row in duplicate_fingerprint_rows(manifest) {
findings.push(
quality_info(
"fingerprint-reused",
Some(row.key),
"same fingerprint is shared by multiple assets",
),
)
}
findings
}
///|
pub fn policy_advice_for_manifest(manifest : Manifest) -> Array[PolicyAdvice] {
let advice : Array[PolicyAdvice] = []
for entry in manifest.entries {
match policy_advice_for_asset(entry) {
Some(item) => advice.push(item)
None => ()
}
}
advice.sort_by((a, b) => a.path.compare(b.path))
advice
}
///|
pub fn policy_advice_for_asset(entry : Asset) -> PolicyAdvice? {
let recommended = recommended_policy(entry)
if recommended == entry.policy {
return None
}
let reason = policy_reason(entry, recommended)
Some({ path: entry.path, current: entry.policy, recommended, reason })
}
///|
pub fn recommended_policy(entry : Asset) -> CachePolicy {
let bucket = classify_asset(entry)
match bucket {
BucketRuntime => Revalidate
BucketGenerated => Revalidate
BucketSource => Revalidate
BucketDocument => Revalidate
BucketConfig => Revalidate
BucketArchive => Immutable
BucketMedia => if entry.bytes == 0 { NoStore } else { Immutable }
BucketOther =>
match entry.kind {
Text | Data => Revalidate
Binary | Image | Audio | Font => Immutable
Other(_) => Revalidate
}
}
}
///|
pub fn cache_policy_weight(policy : CachePolicy) -> Int {
match policy {
Immutable => 4
Revalidate => 3
Runtime => 2
NoStore => 1
}
}
///|
pub fn cache_stability_score(manifest : Manifest) -> Int {
if manifest.entries.is_empty() {
return 0
}
let mut total = 0
let mut max = 0
for entry in manifest.entries {
total = total + cache_policy_weight(entry.policy)
max = max + 4
}
total * 100 / max
}
///|
pub fn assets_without_tags(manifest : Manifest) -> Array[Asset] {
let result : Array[Asset] = []
for entry in manifest.entries {
if entry.tags.is_empty() {
result.push(entry)
}
}
result
}
///|
pub fn oversized_assets(manifest : Manifest, threshold : Int) -> Array[Asset] {
let result : Array[Asset] = []
for entry in manifest.entries {
if entry.bytes > threshold {
result.push(entry)
}
}
result
}
///|
pub fn zero_byte_assets(manifest : Manifest) -> Array[Asset] {
let result : Array[Asset] = []
for entry in manifest.entries {
if entry.bytes == 0 {
result.push(entry)
}
}
result
}
///|
pub fn directory_profiles(manifest : Manifest) -> Array[DirectoryProfile] {
let profiles : Array[DirectoryProfile] = []
for row in count_by_directory(manifest) {
let entries = assets_under_directory(manifest, row.key)
profiles.push(profile_directory(row.key, entries))
}
profiles.sort_by((a, b) => {
if a.warnings == b.warnings {
b.bytes.compare(a.bytes)
} else {
b.warnings.compare(a.warnings)
}
})
profiles
}
///|
pub fn profile_directory(
directory : String,
entries : Array[Asset],
) -> DirectoryProfile {
let mut generated_entries = 0
let mut runtime_entries = 0
let mut media_entries = 0
let mut source_entries = 0
let mut document_entries = 0
let mut warnings = 0
for entry in entries {
match classify_asset(entry) {
BucketGenerated => generated_entries = generated_entries + 1
BucketRuntime => runtime_entries = runtime_entries + 1
BucketMedia => media_entries = media_entries + 1
BucketSource => source_entries = source_entries + 1
BucketDocument => document_entries = document_entries + 1
_ => ()
}
if entry.tags.is_empty() || entry.policy == NoStore || entry.bytes == 0 {
warnings = warnings + 1
}
}
{
directory,
entries: entries.length(),
bytes: profile_total_bytes(entries),
generated_entries,
runtime_entries,
media_entries,
source_entries,
document_entries,
warnings,
}
}
///|
pub fn directories_with_warnings(
manifest : Manifest,
) -> Array[DirectoryProfile] {
let result : Array[DirectoryProfile] = []
for profile in directory_profiles(manifest) {
if profile.warnings > 0 {
result.push(profile)
}
}
result
}
///|
pub fn render_quality_report(report : QualityReport) -> String {
let out = StringBuilder()
out.write_string("quality ")
out.write_string(report.score.to_string())
out.write_string(" ")
out.write_string(report.risk.to_wire())
out.write_string("\nentries ")
out.write_string(report.entries.to_string())
out.write_string("\nbytes ")
out.write_string(report.total_bytes.to_string())
out.write_string("\nerrors ")
out.write_string(report.errors.to_string())
out.write_string("\nwarnings ")
out.write_string(report.warnings.to_string())
out.write_string("\ngenerated ")
out.write_string(report.generated_assets.to_string())
out.write_string("\nruntime ")
out.write_string(report.runtime_assets.to_string())
out.write_string("\nmissing-tags ")
out.write_string(report.missing_tag_assets.to_string())
out.write_string("\npolicy-advice ")
out.write_string(report.policy_advice.length().to_string())
out.write_string("\n")
if !report.policy_advice.is_empty() {
out.write_string("\n[policy]\n")
for advice in report.policy_advice {
out.write_string(advice.path)
out.write_string(" ")
out.write_string(advice.current.to_wire())
out.write_string(" -> ")
out.write_string(advice.recommended.to_wire())
out.write_string(" ")
out.write_string(advice.reason)
out.write_string("\n")
}
}
if !report.findings.is_empty() {
out.write_string("\n[findings]\n")
out.write_string(render_findings(report.findings))
}
out.to_string()
}
///|
pub fn render_findings(findings : Array[Finding]) -> String {
if findings.is_empty() {
return "(none)\n"
}
let out = StringBuilder()
for finding in findings {
out.write_string(finding_level_wire(finding.level))
out.write_string(" ")
out.write_string(finding.code)
match finding.path {
Some(path) => {
out.write_string(" ")
out.write_string(path)
}
None => ()
}
out.write_string(" - ")
out.write_string(finding.message)
out.write_string("\n")
}
out.to_string()
}
///|
pub fn render_directory_profiles(profiles : Array[DirectoryProfile]) -> String {
if profiles.is_empty() {
return "(none)\n"
}
let out = StringBuilder()
for profile in profiles {
out.write_string(profile.directory)
out.write_string(" entries=")
out.write_string(profile.entries.to_string())
out.write_string(" bytes=")
out.write_string(profile.bytes.to_string())
out.write_string(" warnings=")
out.write_string(profile.warnings.to_string())
out.write_string(" generated=")
out.write_string(profile.generated_entries.to_string())
out.write_string(" media=")
out.write_string(profile.media_entries.to_string())
out.write_string(" source=")
out.write_string(profile.source_entries.to_string())
out.write_string("\n")
}
out.to_string()
}
///|
fn quality_warning(code : String, path : String?, message : String) -> Finding {
{ level: Warning, code, path, message }
}
///|
fn quality_info(code : String, path : String?, message : String) -> Finding {
{ level: Info, code, path, message }
}
///|
fn merge_findings(a : Array[Finding], b : Array[Finding]) -> Array[Finding] {
let result : Array[Finding] = []
for finding in a {
result.push(finding)
}
for finding in b {
result.push(finding)
}
result
}
///|
fn count_findings(findings : Array[Finding], level : FindingLevel) -> Int {
let mut count = 0
for finding in findings {
if finding.level == level {
count = count + 1
}
}
count
}
///|
fn clamp_score(score : Int) -> Int {
if score < 0 {
0
} else if score > 100 {
100
} else {
score
}
}
///|
fn policy_reason(entry : Asset, recommended : CachePolicy) -> String {
match recommended {
Immutable =>
match classify_asset(entry) {
BucketMedia => "media assets with fingerprints are stable cache targets"
BucketArchive => "archives are usually versioned release artifacts"
_ => "fingerprinted binary assets can be cached immutably"
}
Revalidate =>
match classify_asset(entry) {
BucketSource =>
"source files change during development and should revalidate"
BucketDocument => "documentation is usually edited between releases"
BucketConfig => "configuration changes should be visible quickly"
BucketGenerated =>
"generated outputs should refresh with the producing pipeline"
BucketRuntime => "runtime data should stay refreshable"
_ => "asset is safer with normal revalidation"
}
NoStore => "zero-byte or transient assets should not be stored"
Runtime => "runtime policy is reserved for caller-managed resources"
}
}
///|
fn finding_level_wire(level : FindingLevel) -> String {
match level {
Error => "error"
Warning => "warning"
Info => "info"
}
}
///|
fn profile_total_bytes(entries : Array[Asset]) -> Int {
let mut total = 0
for entry in entries {
total = total + entry.bytes
}
total
}