#![allow(
clippy::print_stdout,
clippy::print_stderr,
reason = "this IS the CLI: stdout carries the run report and stderr the \
diagnostics; only library crates are restricted to `tracing`"
)]
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use clap::{Parser, Subcommand};
use veredictum::bench::client::AuthKind;
use veredictum::pipeline::assets::{
conformance_assets, performance_assets, schema_files, stress_overlay,
};
use veredictum::pipeline::bench::{BenchRequest, compare_bench, describe_packs, run_bench};
use veredictum::pipeline::catalogue::{coverage_report_path, validate_tree, write_coverage_report};
use veredictum::pipeline::conformance::{RunRequest, RunWarning, execute_run};
use veredictum::pipeline::judgement::{JudgementRequest, judge};
use veredictum::pipeline::measured::{
MeasuredEvent, MeasuredRequest, ProbeRequest, StressRequest, SustainedWindow, run_aql_probe,
run_measured, run_stress,
};
use veredictum::pipeline::replay::{ReplayRequest, divergences, replay_run};
use veredictum::pipeline::{RenderedFile, ensure_parent_dir, to_json_document, write_file};
use veredictum::record::{
DigestOutcome, HONESTY_LINE, MANIFEST_FILE, RecordedFile, SIGNATURE_FILE, SignatureOutcome,
seal, verify_bundle,
};
use veredictum::transcript::Recording;
#[derive(Parser)]
#[command(
name = "veredictum",
about = "The independent conformance instrument for openEHR clinical data repositories",
version
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
EmitSchemas {
#[arg(long)]
out: PathBuf,
},
Run {
#[arg(long)]
root: PathBuf,
#[arg(long)]
ixit: PathBuf,
#[arg(long)]
out: PathBuf,
#[arg(long, default_value = "ferroehr")]
sut_name: String,
#[arg(long, default_value = "dev")]
sut_version: String,
#[arg(long)]
filter: Option<String>,
#[arg(long)]
statement: Option<PathBuf>,
#[arg(long)]
sign_key: Option<PathBuf>,
#[arg(long, env = "VEREDICTUM_SIGN_PASSPHRASE", hide_env_values = true)]
sign_passphrase: Option<String>,
#[arg(long)]
record_exchanges: bool,
#[arg(long)]
progress: bool,
},
Validate {
#[arg(long)]
root: PathBuf,
#[arg(long)]
specs: Option<PathBuf>,
#[arg(long)]
write_report: bool,
},
Perf {
#[arg(long)]
root: PathBuf,
#[arg(long)]
ixit: PathBuf,
#[arg(long)]
results: PathBuf,
#[arg(long)]
class: String,
#[arg(long, default_value_t = 16)]
seed_workers: usize,
#[arg(long, default_value_t = 1)]
hours: u64,
},
Stress {
#[arg(long)]
root: PathBuf,
#[arg(long)]
ixit: PathBuf,
#[arg(long)]
out: PathBuf,
#[arg(long, default_value = "POC")]
corpus_class: String,
#[arg(long, default_value_t = 16)]
seed_workers: usize,
#[arg(long, default_value_t = 120)]
step_secs: u64,
#[arg(long, default_value_t = 3)]
bisections: u32,
#[arg(long, default_value_t = 4096.0)]
max_rate: f64,
},
Bench {
#[arg(long)]
base_url: String,
#[arg(long, default_value = "none")]
auth: String,
#[arg(long)]
user: Option<String>,
#[arg(long, default_value = "smoke")]
pack: String,
#[arg(long)]
posture: Option<String>,
#[arg(long, default_value_t = 3)]
repetitions: u32,
#[arg(long, default_value_t = 1.0)]
scale: f64,
#[arg(long)]
seed_workers: Option<usize>,
#[arg(long)]
with_baselines: bool,
#[arg(long)]
out: PathBuf,
#[arg(long)]
label: Option<String>,
},
BenchCompare {
#[arg(long = "result", required = true)]
results: Vec<PathBuf>,
#[arg(long)]
out: PathBuf,
},
BenchPacks {
#[arg(long)]
out: PathBuf,
},
AqlProbe {
#[arg(long)]
root: PathBuf,
#[arg(long)]
ixit: PathBuf,
#[arg(long)]
out: PathBuf,
#[arg(long, default_value = "POC")]
corpus_class: String,
#[arg(long, default_value_t = 16)]
seed_workers: usize,
#[arg(long, default_value_t = 20)]
requests: u32,
},
StressCompare {
#[arg(long)]
left: PathBuf,
#[arg(long)]
left_label: String,
#[arg(long)]
right: PathBuf,
#[arg(long)]
right_label: String,
#[arg(long)]
out: PathBuf,
},
PerfAssets {
#[arg(long)]
root: PathBuf,
#[arg(long)]
results: PathBuf,
#[arg(long)]
out: PathBuf,
#[arg(long)]
summary: Option<PathBuf>,
#[arg(long)]
stress: Option<PathBuf>,
},
ConformanceAssets {
#[arg(long)]
root: PathBuf,
#[arg(long)]
results: PathBuf,
#[arg(long)]
verdicts: PathBuf,
#[arg(long)]
out: PathBuf,
#[arg(long, default_value = "")]
suffix: String,
},
Verdicts {
#[arg(long)]
statement: PathBuf,
#[arg(long)]
results: PathBuf,
#[arg(long)]
root: PathBuf,
#[arg(long)]
out: PathBuf,
#[arg(long)]
sign_key: Option<PathBuf>,
#[arg(long, env = "VEREDICTUM_SIGN_PASSPHRASE", hide_env_values = true)]
sign_passphrase: Option<String>,
},
Replay {
#[arg(long)]
root: PathBuf,
#[arg(long)]
ixit: PathBuf,
#[arg(long)]
transcript: PathBuf,
#[arg(long)]
statement: Option<PathBuf>,
#[arg(long)]
filter: Option<String>,
#[arg(long)]
out: Option<PathBuf>,
#[arg(long)]
against: Option<PathBuf>,
#[arg(long)]
progress: bool,
},
VerifyRecord {
#[arg(long)]
record: PathBuf,
#[arg(long)]
key: PathBuf,
},
}
struct Signing {
key: Option<PathBuf>,
passphrase: Option<String>,
}
fn fail<E: std::fmt::Display>(e: &E) -> ExitCode {
eprintln!("{e}");
ExitCode::from(2)
}
fn emit(out: &Path, files: &[RenderedFile]) -> Result<(), ExitCode> {
if let Err(e) = std::fs::create_dir_all(out) {
eprintln!("cannot create {}: {e}", out.display());
return Err(ExitCode::from(2));
}
for file in files {
let path = out.join(&file.name);
if let Err(e) = std::fs::write(&path, &file.body) {
eprintln!("cannot write {}: {e}", path.display());
return Err(ExitCode::from(2));
}
println!("wrote {}", path.display());
}
Ok(())
}
fn emit_one(path: &Path, body: &str) -> Result<(), ExitCode> {
if let Err(e) = ensure_parent_dir(path) {
eprintln!("{e}");
return Err(ExitCode::from(2));
}
if let Err(e) = write_file(path, body) {
eprintln!("{e}");
return Err(ExitCode::from(2));
}
Ok(())
}
#[expect(
clippy::too_many_lines,
reason = "the dispatch table: one arm per subcommand, each binding its own flags"
)]
fn main() -> ExitCode {
match Cli::parse().command {
Command::EmitSchemas { out } => emit_schemas_command(&out),
Command::Run {
root,
ixit,
out,
sut_name,
sut_version,
filter,
statement,
sign_key,
sign_passphrase,
record_exchanges,
progress,
} => run_command(
&RunRequest {
root: &root,
ixit: &ixit,
out_dir: &out,
sut_name: &sut_name,
sut_version: &sut_version,
filter: filter.as_deref(),
statement: statement.as_deref(),
recording: Recording::from(record_exchanges),
},
&Signing {
key: sign_key,
passphrase: sign_passphrase,
},
progress,
),
Command::Validate {
root,
specs,
write_report,
} => validate_command(&root, specs.as_deref(), write_report),
Command::Replay {
root,
ixit,
transcript,
statement,
filter,
out,
against,
progress,
} => replay_command(
&ReplayRequest {
root: &root,
ixit: &ixit,
transcript: &transcript,
statement: statement.as_deref(),
filter: filter.as_deref(),
only: None,
},
out.as_deref(),
against.as_deref(),
progress,
),
Command::Perf {
root,
ixit,
results,
class,
seed_workers,
hours,
} => perf_command(&root, &ixit, &results, &class, seed_workers, hours),
Command::Stress {
root,
ixit,
out,
corpus_class,
seed_workers,
step_secs,
bisections,
max_rate,
} => stress_command(
&StressRequest {
root: &root,
ixit: &ixit,
corpus_class: &corpus_class,
seed_workers,
step_secs,
bisections,
max_rate,
},
&out,
),
Command::Bench {
base_url,
auth,
user,
pack,
posture,
repetitions,
scale,
seed_workers,
with_baselines,
out,
label,
} => bench_command(
&BenchInvocation {
base_url: &base_url,
auth_token: &auth,
user: user.as_deref(),
pack_token: &pack,
posture_token: posture.as_deref(),
repetitions,
scale,
seed_workers,
with_baselines,
label: label.as_deref(),
},
&out,
),
Command::BenchCompare { results, out } => bench_compare_command(&results, &out),
Command::BenchPacks { out } => bench_packs_command(&out),
Command::AqlProbe {
root,
ixit,
out,
corpus_class,
seed_workers,
requests,
} => probe_command(
&ProbeRequest {
root: &root,
ixit: &ixit,
corpus_class: &corpus_class,
seed_workers,
requests,
},
&out,
),
Command::StressCompare {
left,
left_label,
right,
right_label,
out,
} => stress_compare_command(&left, &left_label, &right, &right_label, &out),
Command::PerfAssets {
root,
results,
out,
summary,
stress,
} => perf_assets_command(&root, &results, &out, summary.as_deref(), stress.as_deref()),
Command::ConformanceAssets {
root,
results,
verdicts,
out,
suffix,
} => conformance_assets_command(&root, &results, &verdicts, &out, &suffix),
Command::Verdicts {
statement,
results,
root,
out,
sign_key,
sign_passphrase,
} => verdicts_command(
&JudgementRequest {
statement: &statement,
results: &results,
root: &root,
},
&out,
&Signing {
key: sign_key,
passphrase: sign_passphrase,
},
),
Command::VerifyRecord { record, key } => verify_record_command(&record, &key),
}
}
fn emit_schemas_command(out: &Path) -> ExitCode {
match emit(out, &schema_files()) {
Ok(()) => ExitCode::SUCCESS,
Err(code) => code,
}
}
fn validate_command(root: &Path, specs: Option<&Path>, write_report: bool) -> ExitCode {
let validation = match validate_tree(root, specs) {
Ok(validation) => validation,
Err(e) => return fail(&e),
};
for finding in &validation.findings {
println!("{finding}");
}
if write_report && let Some(specs) = specs {
let path = coverage_report_path(root);
match write_coverage_report(&validation.loaded.set, specs, &path) {
Ok(()) => println!("wrote {}", path.display()),
Err(e) => eprintln!("warning: {e}"),
}
}
println!(
"{} case(s), {} binding(s), {} party statement(s), {} finding(s)",
validation.loaded.set.cases.len(),
validation.loaded.set.bindings.len(),
validation.loaded.set.parties.len(),
validation.findings.len()
);
if validation.is_clean() {
ExitCode::SUCCESS
} else {
ExitCode::from(1)
}
}
fn seal_emitted(out: &Path, files: &[RecordedFile<'_>], signing: &Signing) -> Result<(), ExitCode> {
let Some(key_path) = signing.key.as_deref() else {
return Ok(());
};
let sealed = match seal(files, key_path, signing.passphrase.as_deref()) {
Ok(sealed) => sealed,
Err(e) => return Err(fail(&e)),
};
emit(
out,
&[
RenderedFile {
name: MANIFEST_FILE.to_owned(),
body: sealed.manifest,
},
RenderedFile {
name: SIGNATURE_FILE.to_owned(),
body: sealed.signature,
},
],
)
}
fn record_name(path: &Path) -> Result<&str, ExitCode> {
let Some(name) = path.file_name().and_then(std::ffi::OsStr::to_str) else {
eprintln!("cannot name {} in the record manifest", path.display());
return Err(ExitCode::from(2));
};
Ok(name)
}
fn verify_record_command(record: &Path, key: &Path) -> ExitCode {
let verification = match verify_bundle(record, key) {
Ok(verification) => verification,
Err(e) => return fail(&e),
};
match &verification.signature {
SignatureOutcome::Accepted(signed) => {
println!("signer fingerprint {}", signed.signer_fingerprint);
println!("signed at {}", signed.signed_at);
}
SignatureOutcome::Rejected => println!(
"signature REJECTED — no component of {} verified {MANIFEST_FILE}",
key.display()
),
}
println!(
"instrument {} {}",
verification.instrument.name, verification.instrument.version
);
for file in &verification.files {
match &file.outcome {
DigestOutcome::Matches => println!(" ok {} {}", file.digest, file.name),
DigestOutcome::Mismatch { recomputed } => println!(
" MISMATCH {} {} (recomputed {recomputed})",
file.digest, file.name
),
DigestOutcome::Missing => println!(" MISSING {} {}", file.digest, file.name),
DigestOutcome::Unreadable { message } => {
println!(" UNREADABLE {} {} ({message})", file.digest, file.name);
}
}
}
println!("{HONESTY_LINE}");
if verification.is_clean() {
ExitCode::SUCCESS
} else {
for finding in verification.findings() {
eprintln!("record: {finding}");
}
ExitCode::from(1)
}
}
fn verdicts_command(request: &JudgementRequest<'_>, out: &Path, signing: &Signing) -> ExitCode {
let judgement = match judge(request) {
Ok(judgement) => judgement,
Err(e) => return fail(&e),
};
if let Err(code) = emit(out, &judgement.documents) {
return code;
}
let sealed: Vec<RecordedFile<'_>> = judgement
.documents
.iter()
.map(|file| RecordedFile {
name: &file.name,
body: file.body.as_bytes(),
})
.collect();
if let Err(code) = seal_emitted(out, &sealed, signing) {
return code;
}
for finding in &judgement.report.review {
println!("static-review: {}", finding.message);
}
println!(
"{} capability verdict(s), {} of {} cases driven, {} review finding(s)",
judgement.report.capabilities.len(),
judgement.report.coverage.driven,
judgement.report.coverage.selected,
judgement.report.review.len(),
);
if judgement.is_clean() {
ExitCode::SUCCESS
} else {
ExitCode::from(1)
}
}
fn conformance_assets_command(
root: &Path,
results: &Path,
verdicts: &Path,
out: &Path,
suffix: &str,
) -> ExitCode {
let files = match conformance_assets(root, results, verdicts, suffix) {
Ok(files) => files,
Err(e) => return fail(&e),
};
match emit(out, &files) {
Ok(()) => ExitCode::SUCCESS,
Err(code) => code,
}
}
fn perf_assets_command(
root: &Path,
results: &Path,
out: &Path,
summary: Option<&Path>,
stress: Option<&Path>,
) -> ExitCode {
let assets = match performance_assets(root, results, stress) {
Ok(assets) => assets,
Err(e) => return fail(&e),
};
if let Err(code) = emit(out, &assets.files) {
return code;
}
if let Some(path) = summary {
let body = match assets.summary_markdown() {
Ok(body) => body,
Err(e) => return fail(&e),
};
if let Err(code) = emit_one(path, &body) {
return code;
}
println!("wrote {}", path.display());
}
ExitCode::SUCCESS
}
fn stress_compare_command(
left: &Path,
left_label: &str,
right: &Path,
right_label: &str,
out: &Path,
) -> ExitCode {
let svg = match stress_overlay((left_label, left), (right_label, right)) {
Ok(svg) => svg,
Err(e) => return fail(&e),
};
if let Err(code) = emit_one(out, &svg) {
return code;
}
println!("wrote {}", out.display());
ExitCode::SUCCESS
}
fn stress_command(request: &StressRequest<'_>, out: &Path) -> ExitCode {
let progress = |message: String| eprintln!("[stress] {message}");
let report = match run_stress(request, &progress) {
Ok(report) => report,
Err(e) => return fail(&e),
};
let document = match to_json_document(&report, "serialize") {
Ok(document) => document,
Err(e) => return fail(&e),
};
if let Err(code) = emit_one(out, &document) {
return code;
}
println!("{}", report.remark);
println!(
"wrote {} ({} steps, max sustainable {:.1}/s)",
out.display(),
report.steps.len(),
report.max_sustainable_throughput_per_s
);
ExitCode::SUCCESS
}
struct BenchInvocation<'a> {
base_url: &'a str,
auth_token: &'a str,
user: Option<&'a str>,
pack_token: &'a str,
posture_token: Option<&'a str>,
repetitions: u32,
scale: f64,
seed_workers: Option<usize>,
with_baselines: bool,
label: Option<&'a str>,
}
fn bench_command(invocation: &BenchInvocation<'_>, out: &Path) -> ExitCode {
let auth = match AuthKind::parse(invocation.auth_token) {
Ok(auth) => auth,
Err(e) => return fail(&e),
};
let pack = match veredictum::bench::pack::load(invocation.pack_token) {
Ok(pack) => pack,
Err(e) => return fail(&e),
};
let profile = match pack.resolve_profile(invocation.posture_token) {
Ok(profile) => profile,
Err(e) => return fail(&e),
};
let progress = |message: String| eprintln!("[bench] {message}");
let outcome = match run_bench(
&BenchRequest {
pack: &pack,
profile,
base_url: invocation.base_url,
auth,
user: invocation.user,
repetitions: invocation.repetitions,
label: invocation.label,
scale: invocation.scale,
seed_workers: invocation.seed_workers,
with_baselines: invocation.with_baselines,
docker: None,
},
&progress,
) {
Ok(outcome) => outcome,
Err(e) => return fail(&e),
};
if let Err(code) = emit(out, &outcome.documents) {
return code;
}
println!("{}", veredictum::bench::BOUNDARY_STATEMENT);
println!(
"machine: {}",
veredictum::bench::render::machine_line(&outcome.result.environment)
);
println!(
"{} repetition(s) over pack {}@{}; submittable: {}",
outcome.result.repetitions.len(),
outcome.result.pack.id,
outcome.result.pack.version,
outcome.result.submittable
);
println!("posture `{}`:", outcome.result.posture.profile);
for line in &outcome.result.posture.items {
println!(" {} = {} ({})", line.item, line.declared, line.assurance);
}
for requirement in &outcome.result.submittable_unmet {
println!(
"not submittable, unmet `{requirement}`: {}",
requirement.statement()
);
}
for index in &outcome.result.relative {
println!(
"vs {}: {} indexed operation(s), {} gap(s)",
index.display_name,
index
.phases
.values()
.map(|phase| phase.operations.len())
.sum::<usize>(),
index.gaps.len()
);
}
if !outcome.result.scale.reference_configuration {
println!(
"scale factor {:.3}: this run is off the pack's pinned configuration, so its numbers are not comparable with the reference figures the pack describes",
outcome.result.scale.factor
);
}
ExitCode::SUCCESS
}
fn bench_compare_command(results: &[PathBuf], out: &Path) -> ExitCode {
let outcome = match compare_bench(results) {
Ok(outcome) => outcome,
Err(e) => return fail(&e),
};
println!("{}", outcome.document.body);
if let Err(code) = emit(out, std::slice::from_ref(&outcome.document)) {
return code;
}
if outcome.comparison.warnings.is_empty() {
ExitCode::SUCCESS
} else {
for warning in &outcome.comparison.warnings {
eprintln!("bench-compare: {warning}");
}
ExitCode::from(1)
}
}
fn bench_packs_command(out: &Path) -> ExitCode {
let outcome = match describe_packs() {
Ok(outcome) => outcome,
Err(e) => return fail(&e),
};
if let Err(code) = emit(out, std::slice::from_ref(&outcome.document)) {
return code;
}
for pack in &outcome.manifest.packs {
println!(
"{}@{}: {} phase(s), {} fixture(s)",
pack.id,
pack.version,
pack.phases.len(),
pack.fixtures.len()
);
}
ExitCode::SUCCESS
}
fn probe_command(request: &ProbeRequest<'_>, out: &Path) -> ExitCode {
let progress = |message: String| eprintln!("[probe] {message}");
let report = match run_aql_probe(request, &progress) {
Ok(report) => report,
Err(e) => return fail(&e),
};
let document = match to_json_document(&report, "serialize") {
Ok(document) => document,
Err(e) => return fail(&e),
};
if let Err(code) = emit_one(out, &document) {
return code;
}
println!("wrote {} ({} probes)", out.display(), report.probes.len());
ExitCode::SUCCESS
}
fn perf_command(
root: &Path,
ixit: &Path,
results: &Path,
class_token: &str,
seed_workers: usize,
hours: u64,
) -> ExitCode {
let Some(window) = SustainedWindow::hours(hours) else {
eprintln!("--hours must be one of 1 | 2 | 4 | 6 | 8 | 12 (got {hours})");
return ExitCode::from(2);
};
let request = MeasuredRequest {
root,
ixit,
results,
class: class_token,
seed_workers,
window,
};
let observe = |event: MeasuredEvent<'_>| match event {
MeasuredEvent::Progress(message) => eprintln!("[perf] {message}"),
MeasuredEvent::CaseStarted { case, source } => println!(
"case {} (class {class_token}) from {}",
case.id,
source.display()
),
MeasuredEvent::Measured(measurement) => {
for op in &measurement.operations {
println!(
" {}: {} requests, {} errors, p50 {:.1}ms p90 {:.1}ms p99 {:.1}ms",
op.operation,
op.requests,
op.errors,
op.latency_ms_p50,
op.latency_ms_p90,
op.latency_ms_p99
);
}
println!(" {}", veredictum::perf::verdict_evidence(measurement));
}
MeasuredEvent::PrunedOrphan(case) => {
println!(" pruned orphaned measurement for retired case {case}");
}
MeasuredEvent::Merged(path) => {
println!(" measurement merged into {}", path.display());
}
};
match run_measured(&request, &observe) {
Ok(run) if run.earned_all => ExitCode::SUCCESS,
Ok(_) => ExitCode::from(1),
Err(e) => fail(&e),
}
}
fn report_transcript(outcome: &veredictum::pipeline::conformance::RunOutcome) {
let Some(path) = &outcome.transcript_path else {
return;
};
let exchanges: usize = outcome
.report
.transcripts
.iter()
.map(|case| case.exchanges.len())
.sum();
println!(
"wrote {} ({exchanges} recorded exchange(s)) — it can carry real patient data; store it as you store the record",
path.display()
);
}
fn emit_documents(
outcome: &veredictum::pipeline::conformance::RunOutcome,
) -> Result<Vec<(&str, String)>, ExitCode> {
let document = match outcome.results_document() {
Ok(document) => document,
Err(e) => return Err(fail(&e)),
};
if let Err(e) = write_file(&outcome.results_path, &document) {
return Err(fail(&e));
}
let exceptions = match outcome.exceptions_document() {
Ok(document) => document,
Err(e) => return Err(fail(&e)),
};
if let Err(e) = write_file(&outcome.exceptions_path, &exceptions) {
return Err(fail(&e));
}
let transcript = match outcome.transcript_document() {
Ok(transcript) => transcript,
Err(e) => return Err(fail(&e)),
};
if let (Some(body), Some(path)) = (transcript.as_ref(), outcome.transcript_path.as_ref())
&& let Err(e) = write_file(path, body)
{
return Err(fail(&e));
}
let names = match (
record_name(&outcome.results_path),
record_name(&outcome.exceptions_path),
) {
(Ok(results), Ok(exceptions)) => [results, exceptions],
(Err(code), _) | (_, Err(code)) => return Err(code),
};
let [results_name, exceptions_name] = names;
let mut emitted = vec![(results_name, document), (exceptions_name, exceptions)];
if let (Some(body), Some(path)) = (transcript, outcome.transcript_path.as_ref()) {
emitted.push((record_name(path)?, body));
}
Ok(emitted)
}
fn read_results(path: &Path) -> Result<veredictum::party::Results, String> {
let text = std::fs::read_to_string(path)
.map_err(|e| format!("cannot read {}: {e}", path.display()))?;
serde_json::from_str(&text)
.map_err(|e| format!("{} does not parse as results.json: {e}", path.display()))
}
fn replay_command(
request: &ReplayRequest<'_>,
out: Option<&Path>,
against: Option<&Path>,
progress: bool,
) -> ExitCode {
let mut report_progress = |event: veredictum::run::Progress<'_>| {
if progress {
use std::io::Write as _;
println!("{}", event.render_line());
let _flush = std::io::stdout().flush();
}
};
let submitted = match against.map(read_results) {
None => None,
Some(Ok(results)) => Some(results),
Some(Err(reason)) => {
eprintln!("{reason}");
return ExitCode::from(2);
}
};
let only: Option<Vec<String>> = submitted.as_ref().map(|results| {
let mut ids: Vec<String> = results
.outcomes
.iter()
.map(|outcome| outcome.case.to_string())
.collect();
ids.sort_unstable();
ids.dedup();
ids
});
let request = ReplayRequest {
only: only.as_deref(),
..*request
};
let outcome = match replay_run(&request, &mut report_progress) {
Ok(outcome) => outcome,
Err(e) => return fail(&e),
};
let document = match serde_json::to_string_pretty(&outcome.results) {
Ok(text) => format!(
"{text}
"
),
Err(e) => {
eprintln!("cannot serialize the re-judged results: {e}");
return ExitCode::from(2);
}
};
if let Some(path) = out {
if let Some(parent) = path.parent()
&& let Err(e) = std::fs::create_dir_all(parent)
{
eprintln!("cannot create {}: {e}", parent.display());
return ExitCode::from(2);
}
if let Err(e) = std::fs::write(path, &document) {
eprintln!("cannot write {}: {e}", path.display());
return ExitCode::from(2);
}
}
println!(
"re-judged {} case-record(s) from the recording: {} passed / {} failed / {} errored / {} n-a",
outcome.results.outcomes.len(),
outcome.counts.passed,
outcome.counts.failed,
outcome.counts.errored,
outcome.counts.not_applicable
);
let (Some(submitted_path), Some(submitted)) = (against, submitted) else {
return ExitCode::SUCCESS;
};
let found = divergences(&submitted, &outcome.results);
if found.is_empty() {
println!(
"every row of {} follows from the recorded exchanges",
submitted_path.display()
);
return ExitCode::SUCCESS;
}
eprintln!(
"{} row(s) of {} do not follow from the recorded exchanges:",
found.len(),
submitted_path.display()
);
for divergence in &found {
eprintln!(" {divergence}");
}
ExitCode::from(1)
}
fn run_command(request: &RunRequest<'_>, signing: &Signing, progress: bool) -> ExitCode {
let warn = |warning: RunWarning<'_>| match warning {
RunWarning::CarriedMeasurements {
count,
measured_at,
running_at,
} => eprintln!(
"warning: carrying {count} measurement record(s) taken at SUT version {measured_at} into a run at {running_at} — re-measure or attest the surface unchanged"
),
};
let mut report_progress = |event: veredictum::run::Progress<'_>| {
if progress {
use std::io::Write as _;
println!("{}", event.render_line());
let _flush = std::io::stdout().flush();
}
};
let outcome = match execute_run(request, &warn, &mut report_progress) {
Ok(outcome) => outcome,
Err(e) => return fail(&e),
};
if let Err(e) = std::fs::create_dir_all(request.out_dir) {
eprintln!("cannot create {}: {e}", request.out_dir.display());
return ExitCode::from(2);
}
let emitted = match emit_documents(&outcome) {
Ok(emitted) => emitted,
Err(code) => return code,
};
let sealed: Vec<RecordedFile<'_>> = emitted
.iter()
.map(|(name, body)| RecordedFile {
name,
body: body.as_bytes(),
})
.collect();
if let Err(code) = seal_emitted(request.out_dir, &sealed, signing) {
return code;
}
println!(
"{} case-records: {} passed / {} failed / {} errored / {} n-a; interpreter coverage {:.1}% ({} exceptions); wrote {}",
outcome.report.records.len(),
outcome.counts.passed,
outcome.counts.failed,
outcome.counts.errored,
outcome.counts.not_applicable,
outcome.report.interpreter_coverage() * 100.0,
outcome.report.exceptions.len(),
outcome.results_path.display()
);
for record in &outcome.report.records {
for advisory in &record.advisories {
println!("observed: {} {advisory}", record.case);
}
}
report_transcript(&outcome);
if outcome.is_clean() {
ExitCode::SUCCESS
} else {
ExitCode::from(1)
}
}