use crate::agent::CliError;
use crate::local_cmd::{base_result, reference};
use saccade_core::manifest;
use saccade_core::workflows::manifest as workflow;
use serde_json::json;
use std::path::PathBuf;
#[derive(clap::Args)]
pub(crate) struct Args {
#[command(subcommand)]
operation: Operation,
}
#[derive(clap::Subcommand)]
enum Operation {
Build {
dir: PathBuf,
#[arg(long)]
approved_anchor: Option<PathBuf>,
#[arg(long)]
last_good: Option<PathBuf>,
#[arg(long)]
cases: Option<PathBuf>,
#[arg(long)]
json: bool,
},
Views {
target: PathBuf,
#[arg(long)]
out: PathBuf,
#[arg(long, value_delimiter = ',')]
group_by: Vec<String>,
#[arg(long)]
now_unix: Option<u64>,
#[arg(long, default_value_t = 2_592_000)]
max_age_seconds: u64,
#[arg(long)]
json: bool,
},
Verify {
target: PathBuf,
#[arg(long)]
json: bool,
},
Link {
dir: PathBuf,
#[arg(long)]
report_id: String,
#[arg(long)]
out: PathBuf,
#[arg(long)]
json: bool,
},
Classify {
path: PathBuf,
#[arg(long)]
json: bool,
},
}
pub(crate) fn run(args: Args) -> Result<u8, CliError> {
match args.operation {
Operation::Build {
dir,
approved_anchor,
last_good,
cases,
json,
} => {
let output = workflow::build(&workflow::BuildOptions {
dir: &dir,
approved_anchor: approved_anchor.as_deref(),
last_good: last_good.as_deref(),
cases: cases.as_deref(),
policy: crate::signed_approval::policy(),
})?;
let path = output.path;
let value = output.document;
let mut result = base_result("manifest.build");
result["artifact"] = reference(&path)?;
result["counts"] = value["counts"].clone();
result["data"] = json!({"approval":value["approval"]["state"]});
if json {
crate::emit(&format!("{}\n", serde_json::to_string(&result)?))?;
} else {
crate::emit(&format!(
"wrote {} ({} artifacts, {} reports, {} duplicate files; approval: {})\n",
crate::escape_control(&path.display().to_string()),
value["counts"]["unique_artifacts"],
value["counts"]["reports"],
value["counts"]["duplicate_files"],
value["approval"]["state"].as_str().unwrap_or("none"),
))?;
}
Ok(0)
}
Operation::Views {
target,
out,
group_by,
now_unix,
max_age_seconds,
json,
} => {
let path = if target.is_dir() {
target.join(manifest::MANIFEST_FILE)
} else {
target
};
let now = match now_unix {
Some(now) => now,
None => std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| CliError::new("config", e.to_string()))?
.as_secs(),
};
let report = workflow::views(&workflow::ViewsOptions {
target: &path,
out: &out,
group_by: &group_by,
now_unix: now,
max_age_seconds,
policy: crate::signed_approval::policy(),
})?;
let artifact = out.join("coverage.json");
let mut result = base_result("manifest.views");
result["verdict"] = report.coverage.clone().into();
result["artifact"] = reference(&artifact)?;
result["counts"] = json!({"expected":report.counts.expected,"captured":report.counts.captured,"measured":report.counts.measured,"refused":report.counts.refused});
result["data"] = json!({"outcomes":report.counts.outcomes,"health_cases":report.rows.iter().filter(|r| !r.health.is_empty()).count(), "groups":report.groups.len(), "html":saccade_core::paths::portable(&out.join("index.html"))});
if json {
crate::emit(&format!("{}\n", serde_json::to_string(&result)?))?;
} else {
crate::emit(&format!(
"coverage: {}; {} expected, {} captured, {} measured, {} refused; {} health cases; {}\n",
report.coverage,
report.counts.expected,
report.counts.captured,
report.counts.measured,
report.counts.refused,
result["data"]["health_cases"],
crate::escape_control(&out.join("index.html").display().to_string())
))?;
}
Ok(u8::from(report.coverage != "complete"))
}
Operation::Verify { target, json } => {
workflow::verify(&target, crate::signed_approval::policy())?;
let mut result = base_result("manifest.verify");
result["data"] = json!({"verified":true});
if json {
crate::emit(&format!("{}\n", serde_json::to_string(&result)?))?;
} else {
crate::emit(
"verified: every recorded artifact is present with its recorded bytes\n",
)?;
}
Ok(0)
}
Operation::Link {
dir,
report_id,
out,
json,
} => {
workflow::link(&dir, &report_id, &out, crate::signed_approval::policy())?;
let mut result = base_result("manifest.link");
result["artifact"] = reference(&out)?;
if json {
crate::emit(&format!("{}\n", serde_json::to_string(&result)?))?;
} else {
crate::emit(&format!(
"wrote {}\n",
crate::escape_control(&out.display().to_string())
))?;
}
Ok(0)
}
Operation::Classify { path, json: _ } => {
let value = workflow::classify(&path, crate::signed_approval::policy())?.value;
crate::emit(&format!("{}\n", serde_json::to_string(&value)?))?;
Ok(0)
}
}
}