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>
318 lines
8.1 KiB
Rust
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()));
|
|
}
|
|
}
|