stemedb/applications/aphoria/src/handlers/policy_ops.rs
jordan c65066fd1c feat(aphoria): implement ignore & exclusion system (Phase 16)
Reduces scan noise by 96% through proper exclusion of test fixtures,
demo apps, and intentional vulnerabilities.

Phase 16.1 - Glob Pattern Matching:
- Replace starts_with() with globset for ** and * patterns
- Backwards compatible with legacy prefix patterns
- Add walker/mod.rs tests for glob exclusions

Phase 16.2 - .aphoriaignore File:
- Create walker/ignore_file.rs for gitignore-style parsing
- Merge with aphoria.toml excludes
- Support # comments and whitespace trimming

Phase 16.3 - Inline Ignore Comments:
- Create extractors/ignore_comments.rs parser
- Support // aphoria:ignore, // aphoria:ignore-next-line
- Support // aphoria:ignore-block / // aphoria:end-ignore
- Multiple comment styles: //, #, /*, --, <!--
- Integrate with ExtractorRegistry.extract_all()

Phase 16.4 - Ack Export/Import:
- Create ack_file.rs for TOML serialization
- Add 'aphoria ack add' subcommand
- Add 'aphoria ack export' to .aphoria/acks.toml
- Add 'aphoria ack import' from .aphoria/acks.toml
- Preserve expiry and reason fields

Also configures stemedb with:
- aphoria.toml with glob excludes for vulnbank, extractors, fixtures
- .aphoriaignore for dashboard, community, latent, SDK examples

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-07 17:28:50 -07:00

169 lines
4.5 KiB
Rust

//! Policy operation handlers (ack, bless, update, baseline, diff, status, init)
use std::process::ExitCode;
use aphoria::{AcknowledgeArgs, AphoriaConfig, BlessArgs, UpdateArgs};
use crate::cli::AckCommands;
/// Handle the ack command and its subcommands.
pub async fn handle_ack_command(command: AckCommands, config: &AphoriaConfig) -> ExitCode {
match command {
AckCommands::Add { concept_path, reason, expires } => {
handle_ack_add(concept_path, reason, expires, config).await
}
AckCommands::Export { output } => handle_ack_export(output, config).await,
AckCommands::Import { input } => handle_ack_import(input, config).await,
}
}
async fn handle_ack_add(
concept_path: String,
reason: String,
expires: Option<String>,
config: &AphoriaConfig,
) -> ExitCode {
let args = AcknowledgeArgs { concept_path, reason, expires: expires.clone() };
match aphoria::acknowledge(args, config).await {
Ok(()) => {
if let Some(exp) = expires {
println!("Conflict acknowledged (expires {exp}).");
} else {
println!("Conflict acknowledged.");
}
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("Acknowledge error: {e}");
ExitCode::from(3)
}
}
}
async fn handle_ack_export(output: Option<std::path::PathBuf>, config: &AphoriaConfig) -> ExitCode {
match aphoria::export_acks(output, config).await {
Ok(stats) => {
println!(
"Exported {} acknowledgments to {}",
stats.exported,
stats.output_path.display()
);
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("Export error: {e}");
ExitCode::from(3)
}
}
}
async fn handle_ack_import(input: Option<std::path::PathBuf>, config: &AphoriaConfig) -> ExitCode {
match aphoria::import_acks(input, config).await {
Ok(stats) => {
println!(
"Imported {} acknowledgments ({} skipped as duplicates)",
stats.imported, stats.skipped
);
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("Import error: {e}");
ExitCode::from(3)
}
}
}
pub async fn handle_bless(
concept_path: String,
predicate: String,
value: String,
reason: String,
config: &AphoriaConfig,
) -> ExitCode {
let args = BlessArgs { concept_path, predicate, value, reason };
match aphoria::bless(args, config).await {
Ok(()) => {
println!("Pattern blessed as authoritative standard.");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("Bless error: {e}");
ExitCode::from(3)
}
}
}
pub async fn handle_update(
concept_path: String,
value: String,
reason: String,
config: &AphoriaConfig,
) -> ExitCode {
let args = UpdateArgs { concept_path, value, reason };
match aphoria::update(args, config).await {
Ok(()) => {
println!("Policy update recorded.");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("Update error: {e}");
ExitCode::from(3)
}
}
}
pub async fn handle_baseline(config: &AphoriaConfig) -> ExitCode {
match aphoria::set_baseline(config).await {
Ok(()) => {
println!("Baseline set.");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("Baseline error: {e}");
ExitCode::from(3)
}
}
}
pub async fn handle_diff(config: &AphoriaConfig) -> ExitCode {
match aphoria::show_diff(config).await {
Ok(output) => {
println!("{output}");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("Diff error: {e}");
ExitCode::from(3)
}
}
}
pub async fn handle_status(config: &AphoriaConfig) -> ExitCode {
match aphoria::show_status(config).await {
Ok(output) => {
println!("{output}");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("Status error: {e}");
ExitCode::from(3)
}
}
}
pub async fn handle_init(config: &AphoriaConfig) -> ExitCode {
match aphoria::initialize(config).await {
Ok(()) => {
println!("Aphoria initialized. Run `aphoria scan <project>` to begin.");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("Init error: {e}");
ExitCode::from(3)
}
}
}