Enterprise Features: - Hosted mode with remote sync for team pattern aggregation - Community sharing with privacy-preserving anonymization - LLM-based semantic claim extraction with Gemini integration - Pattern learning with promotion to declarative extractors - High-entropy secrets extractor with configurable thresholds - Auth bypass and insecure cookies extractors Module Refactoring: - Split oversized files to comply with 500-line limit - Config split: types/core.rs, types/extractors.rs, types/hosted.rs, etc. - Handlers split: scan.rs, policy.rs, report.rs modules - Extractors split: declarative/, high_entropy_secrets/, insecure_cookies/ - Learning split: store modules with metrics and persistence SDK & Ontology: - stemedb-ontology SDK with fluent builders and StemeDB client - Pharma domain extractors for FDA Orange Book data - Consumer health UAT test infrastructure Code Quality: - Fixed clippy warnings (needless_borrows_for_generic_args) - Added KVStore trait imports where needed - Fixed utoipa path re-exports for OpenAPI docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
20 KiB
Policy Alias Implementation Guide
Related: Concept Matching Analysis Status: Implementation Ready Estimated Effort: 2-3 days
Implementation Phases
Phase 1: Schema Extension (Day 1, Morning)
Goal: Add PolicyAlias type and extend TrustPack.
1.1 Define PolicyAlias Type
File: applications/aphoria/src/policy.rs
/// Maps policy assertion paths to extractor output patterns.
///
/// Enables enterprise security teams to define standards using logical hierarchies
/// (e.g., "code://standards/tls/*") that match extractor output
/// (e.g., "code://rust/myapp/tls/*").
#[derive(Archive, Deserialize, Serialize, Debug, Clone)]
#[archive(check_bytes)]
pub struct PolicyAlias {
/// The policy path used in assertions (e.g., "code://standards/tls/cert_verification").
pub policy_path: String,
/// Glob patterns that should resolve to this policy path.
/// Supports '*' wildcard for single-segment match.
///
/// Examples:
/// - "code://rust/*/tls/cert_verification" (matches any project)
/// - "code://*/myapp/tls/cert_verification" (matches any language)
/// - "code://rust/myapp/*/cert_verification" (matches any module)
pub target_patterns: Vec<String>,
}
1.2 Extend TrustPack
File: applications/aphoria/src/policy.rs
#[derive(Archive, Deserialize, Serialize, Debug, Clone)]
#[archive(check_bytes)]
pub struct TrustPack {
pub header: PackHeader,
pub assertions: Vec<Assertion>,
pub aliases: Vec<ConceptAlias>,
/// Policy-level aliases for matching extractor output to policy paths.
/// Optional: Empty vec = no policy aliases (backward compatible).
pub policy_aliases: Vec<PolicyAlias>,
pub signature: [u8; 64],
}
1.3 Update TrustPack Constructor
impl TrustPack {
pub fn new(
name: String,
version: String,
assertions: Vec<Assertion>,
aliases: Vec<ConceptAlias>,
policy_aliases: Vec<PolicyAlias>, // NEW
signing_key: &SigningKey,
) -> Result<Self, AphoriaError> {
// ... existing timestamp/issuer logic
let temp_pack = TrustPack {
header: header.clone(),
assertions: assertions.clone(),
aliases: aliases.clone(),
policy_aliases: policy_aliases.clone(), // NEW
signature: [0u8; 64],
};
// ... existing signing logic
Ok(TrustPack {
header,
assertions,
aliases,
policy_aliases, // NEW
signature
})
}
}
Testing:
cargo test -p aphoria policy::tests::trust_pack_with_policy_aliases
Phase 2: Pattern Matching (Day 1, Afternoon)
Goal: Implement glob-based pattern matching for policy aliases.
2.1 Add Glob Matching Function
File: applications/aphoria/src/episteme/concept_index.rs
/// Check if a subject matches a glob pattern with '*' wildcard.
///
/// Supports single-segment wildcards only (not recursive `**`).
///
/// # Examples
/// ```
/// assert!(glob_match("code://rust/*/tls/cert_verification", "code://rust/myapp/tls/cert_verification"));
/// assert!(glob_match("code://*/myapp/tls/*", "code://python/myapp/tls/min_version"));
/// assert!(!glob_match("code://rust/*/tls", "code://go/myapp/tls/cert_verification"));
/// ```
fn glob_match(pattern: &str, subject: &str) -> bool {
let pattern_parts: Vec<&str> = pattern.split('/').collect();
let subject_parts: Vec<&str> = subject.split('/').collect();
if pattern_parts.len() != subject_parts.len() {
return false;
}
pattern_parts.iter().zip(subject_parts.iter()).all(|(p, s)| {
*p == "*" || *p == *s
})
}
2.2 Extend ConceptIndex Lookup
File: applications/aphoria/src/episteme/concept_index.rs
use crate::policy::PolicyAlias;
impl ConceptIndex {
/// Look up assertions with policy alias fallback.
///
/// Algorithm:
/// 1. Try direct tail-path match (existing behavior)
/// 2. If no match, try each policy alias pattern
/// 3. If pattern matches subject, lookup using policy_path
/// 4. Return first match (policy aliases processed in order)
pub fn lookup_with_policy_aliases(
&self,
subject: &str,
predicate: &str,
policy_aliases: &[PolicyAlias],
) -> Option<&Vec<Assertion>> {
// Try direct tail-path match first (fast path)
if let Some(result) = self.lookup(subject, predicate) {
return Some(result);
}
// Try policy alias patterns (fallback)
for alias in policy_aliases {
// Check if any pattern matches the subject
let pattern_matches = alias.target_patterns.iter().any(|pattern| {
glob_match(pattern, subject)
});
if pattern_matches {
// Look up using the policy path instead
if let Some(result) = self.lookup(&alias.policy_path, predicate) {
return Some(result);
}
}
}
None
}
}
Testing:
cargo test -p aphoria episteme::concept_index::tests::policy_alias_matching
Test Cases:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_glob_match_wildcard() {
assert!(glob_match("code://rust/*/tls", "code://rust/myapp/tls"));
assert!(glob_match("code://*/myapp/tls", "code://rust/myapp/tls"));
assert!(!glob_match("code://rust/*/tls", "code://go/myapp/tls"));
}
#[test]
fn test_policy_alias_lookup() {
// Policy assertion: "code://standards/tls/cert_verification"
let policy_assertion = Assertion {
subject: "code://standards/tls/cert_verification".to_string(),
predicate: "enabled".to_string(),
object: ObjectValue::Boolean(true),
// ... other fields
};
let index = ConceptIndex::build(&[policy_assertion]);
let alias = PolicyAlias {
policy_path: "code://standards/tls/cert_verification".to_string(),
target_patterns: vec![
"code://rust/*/tls/cert_verification".to_string(),
],
};
// Should match via alias
let result = index.lookup_with_policy_aliases(
"code://rust/myapp/tls/cert_verification",
"enabled",
&[alias],
);
assert!(result.is_some());
assert_eq!(result.unwrap().len(), 1);
}
}
Phase 3: Integration (Day 2, Morning)
Goal: Wire policy aliases into scan flow.
3.1 Pass Policy Aliases to ConceptIndex
File: applications/aphoria/src/scan.rs
async fn check_conflicts_persistent(
all_claims: &[ExtractedClaim],
project_root: &Path,
config: &AphoriaConfig,
sync: bool,
) -> Result<ConflictCheckResult, AphoriaError> {
// ... existing setup
// Load policies (Trust Packs)
let policy_manager = PolicyManager::new(&config.corpus.cache_dir);
let policies = policy_manager.load_policies(&config.policies)?;
// Extract policy aliases from all Trust Packs
let policy_aliases: Vec<PolicyAlias> = policies
.iter()
.flat_map(|pack| &pack.policy_aliases)
.cloned()
.collect();
info!(
policy_alias_count = policy_aliases.len(),
"Loaded policy aliases from Trust Packs"
);
// Build corpus and index
let mut corpus = create_authoritative_corpus(&signing_key);
let imported_assertions = episteme.fetch_authoritative_assertions().await?;
corpus.extend(imported_assertions);
let index = ConceptIndex::build(&corpus);
// Pass aliases to conflict checker
let conflicts = episteme
.check_conflicts_with_aliases(all_claims, config, &index, &policy_aliases)
.await?;
// ... rest of function
}
3.2 Extend LocalEpisteme::check_conflicts
File: applications/aphoria/src/episteme/local.rs
use crate::policy::PolicyAlias;
impl LocalEpisteme {
/// Check conflicts with policy alias support.
pub async fn check_conflicts_with_aliases(
&self,
claims: &[ExtractedClaim],
config: &AphoriaConfig,
index: &ConceptIndex,
policy_aliases: &[PolicyAlias],
) -> Result<Vec<ConflictResult>, AphoriaError> {
// ... existing setup (fetch acks, etc.)
for claim in claims {
// Use extended lookup with policy aliases
let auth_assertions = match index.lookup_with_policy_aliases(
&claim.concept_path,
&claim.predicate,
policy_aliases,
) {
Some(assertions) => assertions,
None => continue,
};
// ... rest of conflict detection logic (unchanged)
}
// ... return results
}
}
Backward Compatibility:
// Keep existing method for ephemeral mode
pub async fn check_conflicts(
&self,
claims: &[ExtractedClaim],
config: &AphoriaConfig,
index: &ConceptIndex,
) -> Result<Vec<ConflictResult>, AphoriaError> {
// Delegate to new method with empty aliases
self.check_conflicts_with_aliases(claims, config, index, &[]).await
}
3.3 Update EphemeralDetector
File: applications/aphoria/src/episteme/ephemeral.rs
pub struct EphemeralDetector {
// ... existing fields
policy_aliases: Vec<PolicyAlias>, // NEW
}
impl EphemeralDetector {
pub fn ingest_policies(&mut self, policies: &[TrustPack]) {
for pack in policies {
// Ingest assertions (existing)
self.ingest_authoritative(&pack.assertions);
// Ingest policy aliases (NEW)
self.policy_aliases.extend(pack.policy_aliases.clone());
}
}
pub fn check_conflicts(
&self,
claims: &[ExtractedClaim],
config: &AphoriaConfig,
) -> Vec<ConflictResult> {
let index = ConceptIndex::build(&self.corpus);
// Use policy aliases in lookup
for claim in claims {
let auth_assertions = index.lookup_with_policy_aliases(
&claim.concept_path,
&claim.predicate,
&self.policy_aliases,
);
// ... rest of conflict logic
}
}
}
Phase 4: CLI Tooling (Day 2, Afternoon)
Goal: Enable security teams to create policy aliases easily.
4.1 Add policy add-alias Command
File: applications/aphoria/src/types/command.rs
#[derive(Debug, Subcommand)]
pub enum PolicyCommand {
// ... existing commands (export, import, list)
/// Add a policy alias to a Trust Pack.
///
/// Allows mapping extractor output patterns to policy assertion paths.
#[command(name = "add-alias")]
AddAlias {
/// Path to the Trust Pack file.
#[arg(short, long)]
pack: PathBuf,
/// Policy path (e.g., "code://standards/tls/cert_verification").
#[arg(long)]
policy_path: String,
/// Target pattern (e.g., "code://rust/*/tls/cert_verification").
/// Can be specified multiple times.
#[arg(long = "target")]
target_patterns: Vec<String>,
/// Output path for updated pack (default: overwrite input).
#[arg(short, long)]
output: Option<PathBuf>,
},
}
4.2 Implement Handler
File: applications/aphoria/src/policy_ops.rs
use crate::policy::{PolicyAlias, TrustPack};
pub fn handle_policy_add_alias(
pack_path: &Path,
policy_path: String,
target_patterns: Vec<String>,
output_path: Option<&Path>,
signing_key: &SigningKey,
) -> Result<(), AphoriaError> {
// Load existing Trust Pack
let mut pack = TrustPack::load(pack_path)?;
info!(
pack = %pack.header.name,
version = %pack.header.version,
"Loaded Trust Pack"
);
// Validate patterns
for pattern in &target_patterns {
if !is_valid_glob_pattern(pattern) {
return Err(AphoriaError::Config(format!(
"Invalid glob pattern: {}",
pattern
)));
}
}
// Check if alias already exists (avoid duplicates)
let exists = pack.policy_aliases.iter().any(|a| {
a.policy_path == policy_path && a.target_patterns == target_patterns
});
if exists {
info!("Policy alias already exists, skipping");
return Ok(());
}
// Add new policy alias
let alias = PolicyAlias { policy_path: policy_path.clone(), target_patterns };
pack.policy_aliases.push(alias);
// Re-sign the pack (required because we modified it)
let new_pack = TrustPack::new(
pack.header.name,
pack.header.version,
pack.assertions,
pack.aliases,
pack.policy_aliases,
signing_key,
)?;
// Save to output path (or overwrite input)
let save_path = output_path.unwrap_or(pack_path);
new_pack.save(save_path)?;
info!(
policy_path,
output = %save_path.display(),
"Added policy alias to Trust Pack"
);
Ok(())
}
fn is_valid_glob_pattern(pattern: &str) -> bool {
// Check for balanced segments (no double slashes, etc.)
!pattern.is_empty()
&& !pattern.contains("//")
&& pattern.split('/').all(|seg| !seg.is_empty() || seg == "*")
}
4.3 Wire into CLI
File: applications/aphoria/src/handlers.rs
PolicyCommand::AddAlias { pack, policy_path, target_patterns, output } => {
let signing_key = load_or_generate_key(&project_root)?;
crate::policy_ops::handle_policy_add_alias(
&pack,
policy_path,
target_patterns,
output.as_deref(),
&signing_key,
)?;
}
Example Usage:
# Security team workflow
aphoria policy export security-standards-v1.0.pack
aphoria policy add-alias \
--pack security-standards-v1.0.pack \
--policy-path "code://standards/tls/cert_verification" \
--target "code://rust/*/tls/cert_verification" \
--target "code://go/*/tls/cert_verification" \
--target "code://python/*/tls/cert_verification"
# Dev team imports and scans
aphoria scan --mode persistent
Phase 5: Documentation & Testing (Day 3)
5.1 Update User Guide
File: applications/aphoria/docs/guides/federating-truth.md
Add section:
### Policy Aliases
When security teams create standards using logical hierarchies (e.g., `code://standards/*`),
these may not match extractor output (e.g., `code://rust/myapp/*`).
Policy aliases bridge this gap:
```bash
# Add alias to Trust Pack
aphoria policy add-alias \
--pack security.pack \
--policy-path "code://standards/tls/cert_verification" \
--target "code://rust/*/tls/cert_verification"
Now scans will match code extractors against the policy path.
#### 5.2 Write UAT Scenario
**File:** `applications/aphoria/uat/2026-02-05-policy-alias-uat.md`
```markdown
# UAT: Policy Alias Matching
## Scenario
Security team creates standard at `code://standards/tls/cert_verification`.
Dev team code has `code://rust/myapp/tls/cert_verification`.
## Setup
1. Create security-team project with blessed assertion
2. Export Trust Pack with policy alias
3. Create dev-team project with violating code
4. Import Trust Pack
5. Scan
## Expected Outcome
- Scan detects conflict via policy alias
- Report shows policy source
- Exit code = 1 (BLOCK)
5.3 Integration Tests
File: applications/aphoria/src/tests/policy_alias_integration.rs
#[tokio::test]
async fn test_policy_alias_matching_integration() {
// 1. Create policy assertion
let policy_assertion = create_test_assertion(
"code://standards/tls/cert_verification",
"enabled",
ObjectValue::Boolean(true),
SourceClass::Expert,
);
// 2. Create policy alias
let alias = PolicyAlias {
policy_path: "code://standards/tls/cert_verification".to_string(),
target_patterns: vec![
"code://rust/*/tls/cert_verification".to_string(),
],
};
// 3. Build Trust Pack
let key = SigningKey::generate(&mut rand::thread_rng());
let pack = TrustPack::new(
"Test Policy".to_string(),
"1.0.0".to_string(),
vec![policy_assertion],
vec![],
vec![alias],
&key,
).unwrap();
// 4. Simulate scan with code claim
let code_claim = ExtractedClaim {
concept_path: "code://rust/myapp/tls/cert_verification".to_string(),
predicate: "enabled".to_string(),
value: ObjectValue::Boolean(false), // CONFLICT
// ... other fields
};
// 5. Check conflicts
let corpus = vec![pack.assertions[0].clone()];
let index = ConceptIndex::build(&corpus);
let result = index.lookup_with_policy_aliases(
&code_claim.concept_path,
&code_claim.predicate,
&pack.policy_aliases,
);
// 6. Assert match
assert!(result.is_some(), "Policy alias should match");
assert_eq!(result.unwrap()[0].object, ObjectValue::Boolean(true));
}
Migration & Rollout
Backward Compatibility
✅ Existing Trust Packs:
policy_aliasesfield is optional (deserializes as empty vec)- No re-signing required unless adding aliases
✅ Existing Scans:
- Empty aliases vec = current behavior
- No performance impact (skips alias loop)
✅ Existing CLI:
- All existing commands work unchanged
policy add-aliasis additive
Rollout Plan
Week 1 (Dev):
- Implement Phases 1-3
- Write unit tests
- Manual testing with UAT scenario
Week 2 (Validation):
- Implement Phase 4 (CLI)
- Write integration tests
- Performance benchmarks
Week 3 (Docs & Release):
- Update user documentation
- Write migration guide
- Release 0.2.0 with feature flag
Week 4 (Enterprise Pilot):
- Deploy to 2-3 enterprise teams
- Collect feedback
- Iterate on pattern syntax if needed
Performance Considerations
Lookup Complexity
Direct tail-path: O(1) hash lookup Policy alias: O(P * A) where:
- P = patterns per alias
- A = number of aliases
Mitigation:
- Try direct lookup first (fast path)
- Only iterate aliases on miss
- Most scans will have < 10 aliases
- Pattern matching is simple string comparison
Benchmark Target: < 5% scan time increase
Memory Overhead
Per Trust Pack:
- PolicyAlias: ~100 bytes
- 10 aliases: ~1 KB
- Negligible compared to corpus (MBs)
Future Enhancements
1. Recursive Wildcards
Current: code://rust/*/tls (single segment)
Future: code://rust/**/tls (any depth)
Implementation: Use globset crate for full glob support.
2. Regex Patterns
Current: Glob wildcards Future: Full regex support
pub enum PatternSyntax {
Glob(String),
Regex(String),
}
3. Alias Auto-Discovery
During Scan: Suggest aliases when tail-path matches but full path differs.
// In conflict detection
if tail_match && !full_match {
warn!(
"Potential alias needed: {} -> {}",
claim.concept_path,
assertion.subject
);
}
4. Trust Pack Composition
Idea: Allow Trust Packs to "extend" other packs.
pub struct TrustPack {
pub header: PackHeader,
pub extends: Vec<String>, // URLs of parent packs
// ...
}
Success Criteria
Functional
- Security team can create policy at
code://standards/* - Dev team code at
code://rust/myapp/*matches - Conflicts detected and reported correctly
- Trust Pack signature verifies with aliases
Performance
- Scan time increase < 5%
- Memory overhead < 10 KB per pack
Usability
policy add-aliascommand works intuitively- Trust Pack import is automatic (no manual config)
- Error messages are clear (invalid patterns, etc.)
Quality
- 100% test coverage on pattern matching
- Integration test covers full workflow
- UAT scenario passes
Questions for Review
- Glob Syntax: Single wildcard (
*) sufficient, or support recursive (**)? - Alias Priority: First match wins, or most specific match?
- Validation: Fail Trust Pack creation if pattern is invalid?
- Caching: Cache pattern match results, or recompute each time?
Ready to implement. Feedback welcome before starting Phase 1.