stemedb/applications/aphoria/docs/architecture/policy-alias-implementation.md
jml 9bfa626203 docs: reorganize documentation structure for clarity
Major documentation restructure to improve discoverability and reduce duplication.

## Changes

**Deleted (Archived/Consolidated)**:
- Removed duplicate getting started guides
- Archived outdated planning documents
- Consolidated corpus and configuration docs
- Removed obsolete vision/spec files (superseded by vision.md)
- Cleaned up scrapyard and old PDFs

**New Structure**:
- docs/about/ - Project overview and introduction
- docs/guides/ - User guides (moved from root)
- docs/specs/ - Technical specifications
- docs/sdk/ - SDK documentation (Go)
- docs/references/ - API references
- docs/archive/ - Archived historical docs
- applications/aphoria/docs/advanced/ - Advanced topics
- applications/aphoria/docs/reference/ - CLI reference
- applications/aphoria/docs/archive/ - Archived aphoria docs

**Updated**:
- README.md - New root README with clear navigation
- CONTRIBUTING.md - Contribution guidelines
- CLAUDE.md - Updated paths to new structure
- roadmap.md - Added recent completions

## Files Changed
- 57 files changed
- 1,977 insertions(+)
- 961 deletions(-)

**Net change**: +1,016 lines (added CONTRIBUTING.md, README.md, reorganized content)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 07:33:40 +00:00

788 lines
20 KiB
Markdown

# Policy Alias Implementation Guide
**Related:** [Concept Matching Analysis](./concept-matching-analysis.md)
**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`
```rust
/// 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`
```rust
#[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
```rust
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:**
```bash
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`
```rust
/// 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`
```rust
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:**
```bash
cargo test -p aphoria episteme::concept_index::tests::policy_alias_matching
```
**Test Cases:**
```rust
#[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`
```rust
async fn check_conflicts_persistent(
all_claims: &[Observation],
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`
```rust
use crate::policy::PolicyAlias;
impl LocalEpisteme {
/// Check conflicts with policy alias support.
pub async fn check_conflicts_with_aliases(
&self,
claims: &[Observation],
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:**
```rust
// Keep existing method for ephemeral mode
pub async fn check_conflicts(
&self,
claims: &[Observation],
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`
```rust
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: &[Observation],
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`
```rust
#[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`
```rust
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`
```rust
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:**
```bash
# 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:
```markdown
### 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`
```rust
#[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 = Observation {
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_aliases` field 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-alias` is 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
```rust
pub enum PatternSyntax {
Glob(String),
Regex(String),
}
```
### 3. Alias Auto-Discovery
**During Scan:** Suggest aliases when tail-path matches but full path differs.
```rust
// 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.
```rust
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-alias` command 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
1. **Glob Syntax:** Single wildcard (`*`) sufficient, or support recursive (`**`)?
2. **Alias Priority:** First match wins, or most specific match?
3. **Validation:** Fail Trust Pack creation if pattern is invalid?
4. **Caching:** Cache pattern match results, or recompute each time?
---
**Ready to implement.** Feedback welcome before starting Phase 1.