stemedb/applications/aphoria/src/config.rs
jordan d3a88585fe feat: Phase 6 UAT - Admission control, HLC recency, cluster coordination
This commit includes comprehensive work on Phase 6 features:

## Admission Control (Phase 6 admission middleware)
- AdmissionStore implementation backed by TrustRankStore
- PoW verification with tier-based difficulty computation
- Trust tier progression (Newcomer → Established → Trusted → Authority)
- API integration with admission status endpoints

## HLC Recency Lens (Phase 6C)
- HlcRecencyLens for distributed system ordering
- Hybrid logical clock integration with causality preservation

## Cluster Coordination (Phase 6C)
- Multi-node cluster tests (availability, partition tolerance)
- CRDT convergence tests for anti-entropy sync
- Gateway handler improvements

## Aphoria Code Linter (Phase 2A)
- RFC/OWASP corpus builders with network fetching and caching
- Concept hierarchy with auto-alias creation on conflict detection
- Multiple security extractors (TLS, JWT, CORS, secrets, rate limiting)

## Code Organization
- Split large files into modules to comply with 500-line limit
- Improved test organization with separate test modules
- Fixed rkyv serialization for EigenTrustState (AgentScore struct)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 00:43:37 -07:00

318 lines
8.1 KiB
Rust

//! Configuration parsing for Aphoria.
use std::path::{Path, PathBuf};
use serde::Deserialize;
use crate::AphoriaError;
/// Top-level Aphoria configuration.
///
/// Loaded from `aphoria.toml` at the project root.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct AphoriaConfig {
/// Project settings.
pub project: ProjectConfig,
/// Episteme instance settings.
pub episteme: EpistemeConfig,
/// Conflict threshold settings.
pub thresholds: ThresholdConfig,
/// Extractor settings.
pub extractors: ExtractorConfig,
/// Scan settings.
pub scan: ScanConfig,
/// Alias suggestion settings.
pub aliases: AliasConfig,
/// Corpus builder settings.
pub corpus: CorpusConfig,
}
impl AphoriaConfig {
/// Load configuration from a TOML file.
pub fn from_file(path: &Path) -> Result<Self, AphoriaError> {
if !path.exists() {
return Err(AphoriaError::ConfigNotFound(path.to_path_buf()));
}
let content = std::fs::read_to_string(path)?;
let config: AphoriaConfig = toml::from_str(&content)?;
Ok(config)
}
}
/// Project identification settings.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct ProjectConfig {
/// Project name (auto-detected if not specified).
pub name: Option<String>,
/// Primary language (auto-detected if not specified).
pub language: Option<String>,
}
/// Episteme instance configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct EpistemeConfig {
/// Path to local Episteme data directory.
pub data_dir: PathBuf,
/// Remote Episteme URL (future feature).
pub url: Option<String>,
}
impl Default for EpistemeConfig {
fn default() -> Self {
Self { data_dir: dirs_default_data_dir(), url: None }
}
}
/// Conflict threshold configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ThresholdConfig {
/// Conflict score at or above which to BLOCK.
pub block: f32,
/// Conflict score at or above which to FLAG.
pub flag: f32,
}
impl Default for ThresholdConfig {
fn default() -> Self {
Self { block: 0.7, flag: 0.4 }
}
}
/// Extractor configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ExtractorConfig {
/// Enabled extractors.
pub enabled: Vec<String>,
/// Disabled extractors (alternative to enabled list).
pub disabled: Vec<String>,
/// Timeout extractor settings.
pub timeout_config: TimeoutExtractorConfig,
/// Dependency version extractor settings.
pub dep_versions: DepVersionConfig,
}
impl Default for ExtractorConfig {
fn default() -> Self {
Self {
enabled: vec![
"tls_verify".to_string(),
"jwt_config".to_string(),
"hardcoded_secrets".to_string(),
"timeout_config".to_string(),
"dep_versions".to_string(),
"cors_config".to_string(),
"rate_limit".to_string(),
],
disabled: vec![],
timeout_config: TimeoutExtractorConfig::default(),
dep_versions: DepVersionConfig::default(),
}
}
}
/// Timeout extractor configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct TimeoutExtractorConfig {
/// Minimum reasonable timeout in milliseconds.
pub min_reasonable_ms: u64,
/// Maximum reasonable timeout in milliseconds.
pub max_reasonable_ms: u64,
}
impl Default for TimeoutExtractorConfig {
fn default() -> Self {
Self { min_reasonable_ms: 1000, max_reasonable_ms: 300_000 }
}
}
/// Dependency version extractor configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct DepVersionConfig {
/// Path to advisory database.
pub advisory_db: PathBuf,
}
impl Default for DepVersionConfig {
fn default() -> Self {
Self { advisory_db: dirs_default_advisory_db() }
}
}
/// Scan configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ScanConfig {
/// Directories to exclude from scanning.
pub exclude: Vec<String>,
/// Maximum file size to scan (bytes).
pub max_file_size: u64,
/// Whether to include test files.
pub include_tests: bool,
}
impl Default for ScanConfig {
fn default() -> Self {
Self {
exclude: vec![
"target/".to_string(),
"node_modules/".to_string(),
".git/".to_string(),
"vendor/".to_string(),
],
max_file_size: 1_048_576, // 1MB
include_tests: false,
}
}
}
/// Alias suggestion configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct AliasConfig {
/// Whether to auto-suggest aliases for shared concepts.
pub auto_suggest: bool,
/// Whether to auto-accept aliases to Tier 0 sources.
pub auto_accept_tier0: bool,
/// Whether to automatically create aliases when conflicts are detected.
///
/// When enabled, tail-path matching during conflict detection will
/// persist aliases (e.g., `code://rust/tls/cert_verification` →
/// `rfc://5246/tls/cert_verification`) for faster future queries.
pub auto_create_aliases: bool,
}
impl Default for AliasConfig {
fn default() -> Self {
Self { auto_suggest: true, auto_accept_tier0: true, auto_create_aliases: true }
}
}
/// Corpus builder configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct CorpusConfig {
/// Directory for caching downloaded RFCs and OWASP cheat sheets.
pub cache_dir: PathBuf,
/// Whether to include the hardcoded corpus (built-in assertions).
pub include_hardcoded: bool,
/// Whether to include RFC normative statements.
pub include_rfc: bool,
/// Whether to include OWASP cheat sheet recommendations.
pub include_owasp: bool,
/// Whether to include vendor documentation claims.
pub include_vendor: bool,
/// Override the default RFC list (if None, uses default list).
pub rfc_list: Option<Vec<u32>>,
}
impl Default for CorpusConfig {
fn default() -> Self {
Self {
cache_dir: dirs_default_cache_dir(),
include_hardcoded: true,
include_rfc: true,
include_owasp: true,
include_vendor: true,
rfc_list: None,
}
}
}
/// Get the default Aphoria data directory.
fn dirs_default_data_dir() -> PathBuf {
if let Some(home) = dirs::home_dir() {
home.join(".aphoria").join("db")
} else {
PathBuf::from(".aphoria/db")
}
}
/// Get the default advisory database directory.
fn dirs_default_advisory_db() -> PathBuf {
if let Some(home) = dirs::home_dir() {
home.join(".aphoria").join("advisory-db")
} else {
PathBuf::from(".aphoria/advisory-db")
}
}
/// Get the default cache directory for corpus downloads.
fn dirs_default_cache_dir() -> PathBuf {
if let Some(cache) = dirs::cache_dir() {
cache.join("aphoria")
} else if let Some(home) = dirs::home_dir() {
home.join(".cache").join("aphoria")
} else {
PathBuf::from(".aphoria/cache")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = AphoriaConfig::default();
assert_eq!(config.thresholds.block, 0.7);
assert_eq!(config.thresholds.flag, 0.4);
assert!(config.extractors.enabled.contains(&"tls_verify".to_string()));
assert!(config.scan.exclude.contains(&"target/".to_string()));
}
#[test]
fn test_config_parse() {
let toml = r#"
[project]
name = "testproject"
language = "rust"
[thresholds]
block = 0.8
flag = 0.5
[scan]
exclude = ["build/", "dist/"]
"#;
let config: AphoriaConfig = toml::from_str(toml).expect("should parse");
assert_eq!(config.project.name, Some("testproject".to_string()));
assert_eq!(config.project.language, Some("rust".to_string()));
assert_eq!(config.thresholds.block, 0.8);
assert_eq!(config.thresholds.flag, 0.5);
assert!(config.scan.exclude.contains(&"build/".to_string()));
}
}