Skip to content

Commit e6d86a8

Browse files
feat(evaluation): scan the whole dependency graph (#18)
* feat(evaluation): scan the whole dependency graph * feat(evaluation): improve acceptance test
1 parent 56dcf24 commit e6d86a8

File tree

9 files changed

+194
-47
lines changed

9 files changed

+194
-47
lines changed

Cargo.lock

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ cargo-lock = "=10.1.0"
2727
clap = "=4.5.48"
2828
console = "=0.16.1"
2929
env_logger = "=0.11.8"
30+
futures = "=0.3.31"
3031
hex = "=0.4.3"
3132
httpmock = "=0.7.0"
3233
human-panic = "=2.0.3"

crates/pollux/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ cargo-lock.workspace = true
1818
clap = { workspace = true, features = ["derive"] }
1919
console.workspace = true
2020
env_logger.workspace = true
21+
futures.workspace = true
2122
human-panic.workspace = true
2223
log.workspace = true
2324
reqwest.workspace = true
@@ -30,4 +31,5 @@ tokio.workspace = true
3031
assertor.workspace = true
3132
assert_cmd.workspace = true
3233
httpmock.workspace = true
34+
predicates.workspace = true
3335
temp-dir.workspace = true

crates/pollux/src/core.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
// Copyright 2025 Dotanuki Labs
22
// SPDX-License-Identifier: MIT
33

4+
use crate::core::CrateVeracityLevel::NotAvailable;
45
use crate::infra::{
56
CachedVeracityEvaluator, CrateBuildReproducibilityEvaluator, CrateProvenanceEvaluator, VeracityEvaluationStorage,
67
};
78
use std::fmt::Display;
9+
use std::time::Duration;
10+
use tokio::time::sleep;
811

912
#[derive(Clone, Debug, PartialEq)]
1013
pub struct CargoPackage {
@@ -95,8 +98,8 @@ impl CombinedVeracityEvaluator {
9598
}
9699

97100
async fn evaluate_two_veracity_factors(&self, crate_info: &CargoPackage) -> anyhow::Result<CrateVeracityLevel> {
98-
let has_provenance = self.provenance.evaluate(crate_info).await?;
99101
let has_reproduced_build = self.reproducibility.evaluate(crate_info).await?;
102+
let has_provenance = self.provenance.evaluate(crate_info).await?;
100103

101104
let veracity_level = CrateVeracityLevel::from_booleans(has_provenance, has_reproduced_build);
102105
self.cache.save(crate_info, veracity_level.clone())?;
@@ -108,6 +111,8 @@ impl CombinedVeracityEvaluator {
108111
existing_factor: VeracityFactor,
109112
crate_info: &CargoPackage,
110113
) -> anyhow::Result<CrateVeracityLevel> {
114+
sleep(Duration::from_millis(1000)).await;
115+
111116
let found_additional_factor = match existing_factor {
112117
VeracityFactor::ReproducibleBuilds => self.provenance.evaluate(crate_info).await?,
113118
VeracityFactor::ProvenanceAttested => self.reproducibility.evaluate(crate_info).await?,
@@ -126,14 +131,29 @@ impl CombinedVeracityEvaluator {
126131
}
127132

128133
impl CrateVeracityEvaluation for CombinedVeracityEvaluator {
129-
async fn evaluate(&self, crate_info: &CargoPackage) -> anyhow::Result<CrateVeracityLevel> {
130-
let cached_veracity = self.cache.read(crate_info).unwrap_or(CrateVeracityLevel::NotAvailable);
134+
async fn evaluate(&self, cargo_package: &CargoPackage) -> anyhow::Result<CrateVeracityLevel> {
135+
let cached_veracity = self.cache.read(cargo_package).unwrap_or(NotAvailable);
136+
137+
let new_evaluation = match &cached_veracity {
138+
CrateVeracityLevel::NotAvailable => self.evaluate_two_veracity_factors(cargo_package).await,
139+
CrateVeracityLevel::SingleFactor(factor) => {
140+
self.evaluate_missing_veracity_factor(factor.clone(), cargo_package)
141+
.await
142+
},
143+
CrateVeracityLevel::TwoFactors => Ok(cached_veracity.clone()),
144+
};
131145

132-
match cached_veracity {
133-
CrateVeracityLevel::NotAvailable => self.evaluate_two_veracity_factors(crate_info).await,
134-
CrateVeracityLevel::SingleFactor(factor) => self.evaluate_missing_veracity_factor(factor, crate_info).await,
135-
CrateVeracityLevel::TwoFactors => Ok(cached_veracity),
146+
if new_evaluation.is_ok() {
147+
return new_evaluation;
136148
}
149+
150+
log::info!(
151+
"[pollux.core] failed to evaluate {} | reason = {}; defaulting to cache",
152+
cargo_package,
153+
new_evaluation.unwrap_err()
154+
);
155+
log::info!("[pollux.core] using cached veracity evaluation for {}", cargo_package);
156+
Ok(cached_veracity)
137157
}
138158
}
139159

crates/pollux/src/infra.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,19 @@ pub mod factories {
9595
use std::env::home_dir;
9696
use std::path::PathBuf;
9797
use std::sync::{Arc, LazyLock};
98+
use std::time::Duration;
9899

99100
pub static HTTP_CLIENT: LazyLock<Arc<HTTPClient>> = LazyLock::new(|| {
100101
let user_agent = format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
101102

102103
let mut headers = header::HeaderMap::new();
103104
headers.insert(header::USER_AGENT, header::HeaderValue::from_str(&user_agent).unwrap());
104105

105-
let client = HTTPClient::builder().default_headers(headers).build().unwrap();
106+
let client = HTTPClient::builder()
107+
.default_headers(headers)
108+
.timeout(Duration::from_secs(15))
109+
.build()
110+
.unwrap();
106111
Arc::new(client)
107112
});
108113

crates/pollux/src/infra/cratesio.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use crate::infra::HTTPClient;
66
use serde::Deserialize;
77
use std::fmt::Display;
88
use std::sync::Arc;
9+
use std::time::Duration;
10+
use tokio::time::sleep;
911

1012
#[derive(Debug, Deserialize)]
1113
struct TrustPubData {
@@ -46,6 +48,8 @@ impl CratesIOEvaluator {
4648

4749
impl VeracityEvaluation for CratesIOEvaluator {
4850
async fn evaluate(&self, crate_info: &CargoPackage) -> anyhow::Result<bool> {
51+
sleep(Duration::from_millis(1000)).await;
52+
4953
let endpoint = format!(
5054
"{}/api/v1/crates/{}/{}",
5155
self.base_url, crate_info.name, crate_info.version

crates/pollux/src/infra/ossrebuild.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use crate::infra::HTTPClient;
66
use anyhow::bail;
77
use reqwest::StatusCode;
88
use std::sync::Arc;
9+
use std::time::Duration;
10+
use tokio::time::sleep;
911

1012
pub struct OssRebuildEvaluator {
1113
base_url: String,
@@ -20,12 +22,20 @@ impl OssRebuildEvaluator {
2022

2123
impl VeracityEvaluation for OssRebuildEvaluator {
2224
async fn evaluate(&self, crate_info: &CargoPackage) -> anyhow::Result<bool> {
25+
sleep(Duration::from_millis(1000)).await;
26+
2327
let endpoint = format!(
2428
"{}/{}/{}/{}-{}.crate/rebuild.intoto.jsonl",
2529
self.base_url, crate_info.name, crate_info.version, crate_info.name, crate_info.version
2630
);
2731

28-
let response = self.http_client.head(&endpoint).send().await?;
32+
let response = match self.http_client.head(&endpoint).send().await {
33+
Ok(inner) => inner,
34+
Err(incoming) => {
35+
log::info!("{}", incoming);
36+
bail!("{}", incoming);
37+
},
38+
};
2939

3040
if response.status() == StatusCode::OK {
3141
log::info!("[pollux.evaluator] found reproduced build for {}", crate_info);

crates/pollux/src/main.rs

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
mod core;
55
mod infra;
66

7-
use crate::core::CargoPackage;
87
use crate::core::CrateVeracityEvaluation;
8+
use crate::core::CrateVeracityLevel;
99
use crate::infra::cargo::RustProjectDependenciesResolver;
1010
use clap::Parser;
1111
use console::style;
@@ -18,14 +18,12 @@ static GLOBAL: Jemalloc = Jemalloc;
1818
#[derive(Parser, Debug)]
1919
#[command(version, about, long_about = None)]
2020
struct ProgramArguments {
21-
#[arg(short, long)]
22-
name: String,
23-
2421
#[arg(short, long, help = "Path pointing to project root")]
2522
pub path: PathBuf,
2623
}
2724

2825
#[tokio::main]
26+
2927
async fn main() -> anyhow::Result<()> {
3028
better_panic::install();
3129
human_panic::setup_panic!();
@@ -49,17 +47,62 @@ async fn main() -> anyhow::Result<()> {
4947
let dependencies_resolver = RustProjectDependenciesResolver::new(arguments.path);
5048

5149
let cargo_packages = dependencies_resolver.resolve_packages()?;
50+
let total_project_packages = cargo_packages.len();
5251

5352
println!();
54-
println!("Total cargo packages for this project: {}", cargo_packages.len());
53+
println!("Total cargo packages for this project: {}", total_project_packages);
54+
println!();
55+
println!("Evaluating veracity for packages. This operation may take some time ...");
56+
57+
let veracity_checks = cargo_packages
58+
.into_iter()
59+
.map(async |package| (package.clone(), veracity_evaluator.evaluate(&package).await))
60+
.collect::<Vec<_>>();
61+
62+
let evaluations = futures::future::join_all(veracity_checks).await;
5563

56-
let parts = arguments.name.split("@").collect::<Vec<_>>();
57-
let crates_info = CargoPackage::new(parts[0].to_string(), parts[1].to_string());
64+
let total_evaluated_packages = evaluations
65+
.iter()
66+
.filter(|(_, check)| check.is_ok())
67+
.collect::<Vec<_>>()
68+
.len();
5869

59-
let evaluation = veracity_evaluator.evaluate(&crates_info).await.unwrap();
70+
let total_packages_with_veracity_level = evaluations
71+
.iter()
72+
.filter_map(|(_, check)| {
73+
if let Ok(level) = check {
74+
Some(level.to_owned().clone())
75+
} else {
76+
None
77+
}
78+
})
79+
.filter(|veracity_level| *veracity_level != CrateVeracityLevel::NotAvailable)
80+
.collect::<Vec<_>>()
81+
.len();
6082

6183
println!();
62-
println!("For {} : truthfulness = {:?} ", crates_info, style(evaluation).cyan());
84+
println!("Packages evaluated : {}", total_evaluated_packages);
85+
println!(
86+
"Packages missing veracity checks : {}",
87+
total_project_packages - total_evaluated_packages
88+
);
89+
println!(
90+
"Packages with existing veracity checks : {}",
91+
total_packages_with_veracity_level
92+
);
93+
println!();
94+
95+
evaluations
96+
.iter()
97+
.for_each(|(package, veracity_check)| match veracity_check {
98+
Ok(level) => {
99+
println!("For {} : veracity = {:?} ", package, style(level).cyan());
100+
},
101+
Err(_) => {
102+
println!("For {} : {}", package, style("failed to evaluate").red());
103+
},
104+
});
105+
63106
println!();
64107
Ok(())
65108
}

0 commit comments

Comments
 (0)