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>
30 KiB
RFC-001: Enterprise Policy Alias System
Date: 2026-02-05 Status: Proposed Author: Aphoria Team Target Audience: Enterprise security teams, CTOs, and development organizations
Executive Summary
Enterprise security teams need to enforce organization-wide security policies across diverse development teams using different programming languages and project structures. The current tail-path matching system works well for bundled RFC/OWASP corpus but fails when security teams create custom policies using logical hierarchies (e.g., code://standards/tls/*) that don't align with extractor output (e.g., code://rust/myapp/tls/*). This RFC proposes Policy Aliases—an explicit mapping layer in Trust Packs that bridges the gap between enterprise policy hierarchies and code extractor conventions, enabling seamless cross-language policy enforcement with full audit trails and cryptographic verification.
Problem Statement
Current State: Tail-Path Matching Works for Bundled Corpus
Aphoria uses a tail-path matching algorithm that extracts the last two segments of a subject path to create index keys. This enables cross-scheme concept matching:
RFC source: "rfc://5246/tls/cert_verification"
Code extractor: "code://rust/myapp/tls/cert_verification"
Both produce key: "tls/cert_verification::enabled"
This works because extractors are intentionally designed to align with RFC/OWASP hierarchies. The bundled authoritative corpus and code extractors share a common conceptual vocabulary.
The Enterprise Gap: Policy Hierarchies Don't Match Extractor Output
When enterprise security teams create their own policies, they naturally organize them using logical domains, not language-qualified paths:
# Security team's mental model
subject = "code://standards/tls/cert_verification" # Standards
subject = "code://internal/exceptions/md5_allowed" # Exceptions
subject = "code://vendor/aws/s3/public_access" # Cloud rules
But code extractors produce language-qualified paths:
// Rust extractor output
concept_path: "code://rust/myapp/tls/cert_verification"
// Go extractor output
concept_path: "code://go/myapp/tls/cert_verification"
Tail-path matching fails:
| Source | Path | Extracted Key |
|---|---|---|
| Policy | code://standards/tls/cert_verification |
tls/cert_verification::enabled |
| Code | code://rust/myapp/tls/cert_verification |
myapp/tls::enabled |
The keys don't match because they extract different segments: standards/tls vs rust/myapp/tls.
Business Impact
This mismatch has serious consequences for enterprises:
- Policy Enforcement Broken: Security standards don't match code violations
- Compliance Risk: Audits fail when policies exist but aren't enforced
- Adoption Blocker: Enterprises can't use Trust Packs for cross-team governance
- False Security: Teams think they're protected when policies aren't actually matching
Real-world scenario: A security team exports a signed Trust Pack with TLS requirements. Development teams import it and run scans. Zero conflicts are reported—not because the code is compliant, but because the matching algorithm never connects policy to code.
Design Goals
- Preserve Tail-Path Matching: The fast O(1) lookup for bundled corpus must remain the default path
- Explicit Over Implicit: Policy authors must explicitly define matches—no magic auto-aliasing
- Cryptographically Verified: Aliases are part of the signed Trust Pack, tamper-resistant
- Zero Dev Team Configuration: Import a Trust Pack URL, scanning works—no per-team setup
- Full Audit Trail: Every conflict traces back to specific policy, alias pattern, and issuer
- Backward Compatible: Existing Trust Packs without aliases continue working unchanged
Technical Architecture
Current: Tail-Path Matching
┌─────────────────────────────────────────────────────────────┐
│ Scan Flow │
│ │
│ 1. Extractor produces: │
│ concept_path: "code://rust/myapp/tls/cert_verification" │
│ predicate: "enabled" │
│ value: false │
│ │
│ 2. ConceptIndex::lookup() computes key: │
│ key = "myapp/tls::enabled" (last 2 segments) │
│ │
│ 3. Hash table lookup: O(1) │
│ result = index.get("myapp/tls::enabled") │
│ │
│ 4. If match found → conflict detection │
│ If no match → skip (no authoritative source) │
└─────────────────────────────────────────────────────────────┘
Strengths:
- O(1) constant-time lookups
- Zero configuration required
- Cross-language matching for RFC-aligned extractors
Limitation:
- Only works when policy paths and extractor paths share the same final segments
Extension: Policy Aliases
Policy Aliases add a fallback matching layer that activates only when tail-path matching fails:
┌─────────────────────────────────────────────────────────────┐
│ Extended Matching Flow │
│ │
│ 1. Try tail-path match (existing, fast path) │
│ key = make_key(subject, predicate) │
│ if index.contains(key) → return match │
│ │
│ 2. Try policy alias patterns (fallback path) │
│ for alias in trust_pack.policy_aliases: │
│ for pattern in alias.target_patterns: │
│ if glob_match(pattern, subject): │
│ key = make_key(alias.policy_path, predicate) │
│ if index.contains(key) → return match │
│ │
│ 3. No match → no authoritative source for this claim │
└─────────────────────────────────────────────────────────────┘
Matching Algorithm
Pseudocode:
fn lookup_with_policy_aliases(
index: &ConceptIndex,
subject: &str,
predicate: &str,
policy_aliases: &[PolicyAlias],
) -> Option<&Vec<Assertion>> {
// 1. Fast path: direct tail-path match (O(1))
if let Some(result) = index.lookup(subject, predicate) {
return Some(result);
}
// 2. Fallback path: policy alias expansion (O(P × A))
// P = patterns per alias, A = number of aliases
for alias in policy_aliases {
if subject_matches_any_pattern(subject, &alias.target_patterns) {
if let Some(result) = index.lookup(&alias.policy_path, predicate) {
return Some(result);
}
}
}
None
}
fn subject_matches_any_pattern(subject: &str, patterns: &[String]) -> bool {
patterns.iter().any(|pattern| glob_match(pattern, subject))
}
fn glob_match(pattern: &str, subject: &str) -> bool {
// Split both into segments
let pattern_parts: Vec<&str> = pattern.split('/').collect();
let subject_parts: Vec<&str> = subject.split('/').collect();
// Must have same segment count
if pattern_parts.len() != subject_parts.len() {
return false;
}
// Match segment-by-segment; "*" matches any single segment
pattern_parts.iter()
.zip(subject_parts.iter())
.all(|(p, s)| *p == "*" || *p == *s)
}
Step-by-step example:
Input:
subject = "code://rust/backend-api/tls/cert_verification"
predicate = "enabled"
alias = PolicyAlias {
policy_path: "code://standards/tls/cert_verification",
target_patterns: ["code://rust/*/tls/cert_verification"],
}
Step 1: Tail-path match attempt
key = "backend-api/tls::enabled"
index.lookup("backend-api/tls::enabled") → None
Step 2: Alias pattern matching
Pattern: "code://rust/*/tls/cert_verification"
Subject: "code://rust/backend-api/tls/cert_verification"
Segment comparison:
"code:" == "code:" ✓
"" == "" ✓ (after first slash)
"rust" == "rust" ✓
"*" matches "backend-api" ✓ (wildcard)
"tls" == "tls" ✓
"cert_verification" == "cert_verification" ✓
Pattern matches!
Step 3: Lookup using policy_path
key = "tls/cert_verification::enabled"
index.lookup("tls/cert_verification::enabled") → Some([Assertion])
Result: Match found via policy alias
Trust Pack Schema Extension
Current Schema
#[derive(Archive, Deserialize, Serialize, Debug, Clone)]
#[archive(check_bytes)]
pub struct TrustPack {
pub header: PackHeader,
pub assertions: Vec<Assertion>,
pub aliases: Vec<ConceptAlias>, // Existing: concept-level aliases
pub signature: [u8; 64], // Ed25519 signature
}
pub struct PackHeader {
pub name: String,
pub version: String,
pub issuer_id: [u8; 32], // Ed25519 public key
pub timestamp: u64, // Unix timestamp
}
Extended Schema
#[derive(Archive, Deserialize, Serialize, Debug, Clone)]
#[archive(check_bytes)]
pub struct TrustPack {
pub header: PackHeader,
pub assertions: Vec<Assertion>,
pub aliases: Vec<ConceptAlias>,
pub policy_aliases: Vec<PolicyAlias>, // NEW: Policy-level aliases
pub signature: [u8; 64],
}
/// 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.
/// Example: "code://standards/tls/cert_verification"
pub policy_path: String,
/// Glob patterns that should resolve to this policy path.
/// Supports single-segment wildcards (*) only.
///
/// Examples:
/// - "code://rust/*/tls/cert_verification" (any Rust project)
/// - "code://*/myapp/tls/cert_verification" (any language, specific project)
/// - "code://*/*/tls/cert_verification" (any language, any project)
pub target_patterns: Vec<String>,
}
Backward Compatibility:
// Deserialization with default for missing field
impl Default for TrustPack {
fn default() -> Self {
TrustPack {
header: PackHeader::default(),
assertions: vec![],
aliases: vec![],
policy_aliases: vec![], // Empty = no policy aliases = current behavior
signature: [0u8; 64],
}
}
}
Existing Trust Packs without policy_aliases field will deserialize with an empty vector, preserving current behavior.
CLI Interface
Adding Policy Aliases
# Add a single alias with multiple target patterns
aphoria policy add-alias \
--pack security-standards.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"
Options:
| Flag | Description |
|---|---|
--pack <path> |
Path to Trust Pack file |
--policy-path <uri> |
Policy assertion path to alias |
--target <pattern> |
Glob pattern (can be specified multiple times) |
--output <path> |
Output path (default: overwrite input) |
Listing Policy Aliases
aphoria policy list-aliases --pack security-standards.pack
Output:
Policy Aliases in "Acme Security Standards" v1.0.0:
1. code://standards/tls/cert_verification
Targets:
- code://rust/*/tls/cert_verification
- code://go/*/tls/cert_verification
- code://python/*/tls/cert_verification
2. code://standards/jwt/audience_validation
Targets:
- code://*/*/jwt/audience_validation
Total: 2 policy aliases
Validating Aliases
aphoria policy validate-aliases --pack security-standards.pack
Output:
Validating policy aliases...
✓ code://rust/*/tls/cert_verification - valid glob pattern
✓ code://go/*/tls/cert_verification - valid glob pattern
✓ code://python/*/tls/cert_verification - valid glob pattern
⚠ Warning: No assertions found for policy path "code://standards/jwt/audience_validation"
(alias exists but no corresponding assertion)
Validation complete: 3 valid, 0 invalid, 1 warning
Enterprise Workflow
Step 1: Security Team Creates Standards (Golden Repo)
cd security-standards/
# Create TLS certificate verification requirement
aphoria bless \
--subject "code://standards/tls/cert_verification" \
--predicate "enabled" \
--value true \
--reason "RFC 5246 compliance - TLS certificate verification required"
# Create JWT audience validation requirement
aphoria bless \
--subject "code://standards/jwt/audience_validation" \
--predicate "enabled" \
--value true \
--reason "RFC 7519 compliance - JWT audience claim must be validated"
Step 2: Security Team Adds Policy Aliases
# Export unsigned pack
aphoria policy export --name "Acme-Security-Standards" --output acme-v1.0.pack
# Add aliases for all supported languages
aphoria policy add-alias \
--pack acme-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" \
--target "code://java/*/tls/cert_verification"
aphoria policy add-alias \
--pack acme-v1.0.pack \
--policy-path "code://standards/jwt/audience_validation" \
--target "code://*/*/jwt/audience_validation"
Step 3: Security Team Publishes Pack
# Upload to internal artifact server
aws s3 cp acme-v1.0.pack s3://acme-policies/acme-v1.0.pack --acl private
# Or publish to internal registry
curl -X POST https://policy-registry.acme.com/packs \
-H "Authorization: Bearer $SECURITY_TEAM_TOKEN" \
--data-binary @acme-v1.0.pack
Step 4: Development Team Configures Project
File: aphoria.toml
[policies]
sources = [
"https://policy-registry.acme.com/packs/acme-v1.0.pack"
]
# Or using S3 presigned URL
# sources = [
# "s3://acme-policies/acme-v1.0.pack"
# ]
That's it. No other configuration needed.
Step 5: Development Team Scans Code
aphoria scan --mode persistent
Console Output:
┌─────────────────────────────────────────────────────────────────┐
│ Aphoria Security Scan Report │
├─────────────────────────────────────────────────────────────────┤
│ Project: backend-api │
│ Files: 42 │
│ Claims: 127 │
├─────────────────────────────────────────────────────────────────┤
│ BLOCK (1) │
├─────────────────────────────────────────────────────────────────┤
│ src/main.rs:42 │
│ │
│ code://rust/backend-api/tls/cert_verification │
│ │
│ Code asserts: enabled = false (confidence: 0.95) │
│ Authority: enabled = true (confidence: 1.00) │
│ │
│ Source: Acme-Security-Standards v1.0.0 │
│ Issuer: a1b2c3d4e5f6... │
│ Policy: code://standards/tls/cert_verification │
│ │
│ Matched via policy alias: │
│ Pattern: code://rust/*/tls/cert_verification │
│ │
│ Verdict: BLOCK │
│ Recommendation: Enable TLS certificate verification │
└─────────────────────────────────────────────────────────────────┘
Exit code: 1
Step 6: CI/CD Integration
GitHub Actions:
name: Security Scan
on: [push, pull_request]
jobs:
aphoria-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Aphoria
run: cargo install aphoria
- name: Run Security Scan
run: aphoria scan --mode persistent --exit-code
# Exit code 1 = BLOCK verdict found → build fails
Security Considerations
Signature Verification
Policy aliases are included in the signed Trust Pack:
fn sign_trust_pack(pack: &TrustPack, key: &SigningKey) -> [u8; 64] {
// Signature covers ALL fields including policy_aliases
let content = serialize(&TrustPackContent {
header: pack.header.clone(),
assertions: pack.assertions.clone(),
aliases: pack.aliases.clone(),
policy_aliases: pack.policy_aliases.clone(), // Included
});
key.sign(&content).to_bytes()
}
Guarantee: If anyone tampers with policy aliases, signature verification fails.
Audit Trail
Every conflict report includes:
- Pack Name & Version: Which Trust Pack triggered the conflict
- Issuer ID: Ed25519 public key of the signing authority
- Policy Path: The canonical policy assertion that was matched
- Matched Pattern: Which glob pattern caused the match
- Timestamp: When the Trust Pack was created
This enables forensic analysis: "Why did this build fail?" → "Because pattern X in pack Y matched code Z."
Tamper Resistance
Trust Pack Integrity Chain:
1. Security team creates assertions in local Episteme store
2. `aphoria policy export` serializes + signs with team's Ed25519 key
3. Pack distributed via any channel (S3, registry, etc.)
4. Dev team's `aphoria scan` verifies signature before using
5. If signature invalid → pack rejected, scan fails with clear error
Privilege Separation
| Actor | Can Do | Cannot Do |
|---|---|---|
| Security Team | Create assertions, add aliases, sign packs | Modify dev team code |
| Dev Team | Import packs, run scans, acknowledge conflicts | Modify pack contents, bypass policies |
| CI/CD | Run scans, fail builds | Override BLOCK verdicts |
Performance Analysis
Complexity Analysis
| Operation | Time Complexity | Space Complexity |
|---|---|---|
| Tail-path match (existing) | O(1) | O(N) where N = assertions |
| Policy alias fallback | O(P × A) | O(A) where A = aliases |
| Total lookup | O(1) + O(P × A) | O(N + A) |
Where:
- P = patterns per alias (typically 3-5)
- A = total number of aliases (typically 10-50)
Expected Performance:
- 95% of lookups hit fast path (tail-path match)
- 5% of lookups fall through to alias matching
- Alias matching adds ~0.1ms per lookup (simple string comparison)
Benchmark Targets
| Metric | Target | Rationale |
|---|---|---|
| Scan time increase | < 5% | Alias matching should be negligible |
| Memory overhead | < 10 KB per pack | Aliases are small strings |
| Policy download time | < 1s | Packs cached after first download |
Real-World Estimate
For a typical enterprise deployment:
- 50 policy assertions
- 20 policy aliases
- 5 patterns per alias (100 patterns total)
- 10,000 code claims per scan
Calculations:
- Fast path hits: ~9,500 claims (95%)
- Alias fallback: ~500 claims
- Alias comparisons: 500 × 100 = 50,000 string comparisons
- String comparison time: ~1 microsecond each
- Total alias overhead: ~50ms per scan
Conclusion: Negligible impact on scan time.
Backward Compatibility
Migration Path
No migration required. The change is purely additive:
- Existing Trust Packs: Deserialize with empty
policy_aliasesvector - Existing Scans: Empty aliases = skip alias fallback = current behavior
- Existing CI/CD: No changes to configuration or commands
- Existing Extractors: No changes to output format
Zero-Friction Adoption
| Scenario | Required Changes |
|---|---|
| New enterprise deployment | Add policy_aliases to Trust Pack |
| Existing small team | None (bundled corpus works) |
| Existing enterprise with custom pack | Add aliases, re-sign, redistribute |
Version Negotiation
Trust Packs include a version field in the header:
pub struct PackHeader {
pub schema_version: u8, // 1 = original, 2 = with policy_aliases
// ...
}
Older Aphoria versions encountering schema v2 packs will:
- Log a warning about unknown field
- Ignore
policy_aliases(graceful degradation) - Continue with tail-path matching only
Alternative Approaches Considered
Alternative 1: Normalize Extractor Output
Idea: Make extractors output "canonical" paths that match standards.
// Instead of: "code://rust/myapp/tls/cert_verification"
// Output: "code://standards/tls/cert_verification"
Why rejected:
- Loses language context (
rust/myapp) needed for file location - Breaks existing aliases and observations
- Forces extractors to know about ALL standards
- Violates separation of concerns
Alternative 2: Flexible Tail-Path Length
Idea: Try matching with N=1, N=2, N=3 segments.
// Try multiple keys
"cert_verification::enabled" // N=1
"tls/cert_verification::enabled" // N=2
"myapp/tls/cert_verification::enabled" // N=3
Why rejected:
- Ambiguous matches (which key wins?)
- Performance impact (3x lookups)
- Doesn't solve semantic differences
- No explicit control over matching behavior
Alternative 3: Auto-Discover Aliases
Idea: Automatically create aliases during scan when tail-path matches but full path differs.
// Scan detects potential match, auto-creates alias
if tail_match && !full_match {
auto_alias(claim.concept_path, assertion.subject);
}
Why rejected:
- Implicit behavior (hard to debug)
- No security team approval (bypasses policy control)
- May create false positives
- Violates "explicit over implicit" principle
Future consideration: Could be implemented as suggestions (not automatic), shown in scan output.
Implementation Roadmap
Phase 1: Schema Extension (Day 1)
- Add
PolicyAliastype topolicy.rs - Extend
TrustPackstruct withpolicy_aliasesfield - Update serialization/deserialization
- Update signature computation to include aliases
- Add unit tests for schema changes
Deliverable: Trust Packs can store and serialize policy aliases.
Phase 2: Pattern Matching (Day 1-2)
- Implement
glob_match()function - Add
lookup_with_policy_aliases()to ConceptIndex - Write comprehensive test suite for pattern matching
- Handle edge cases (empty patterns, invalid syntax)
Deliverable: Pattern matching algorithm works correctly.
Phase 3: Scan Integration (Day 2)
- Load policy aliases from imported Trust Packs
- Pass aliases to conflict detection
- Include alias info in conflict reports
- Update all output formats (JSON, table, markdown, SARIF)
Deliverable: Scans detect conflicts via policy aliases.
Phase 4: CLI Tooling (Day 3)
- Implement
policy add-aliascommand - Implement
policy list-aliasescommand - Implement
policy validate-aliasescommand - Update help text and documentation
Deliverable: Security teams can manage aliases via CLI.
Phase 5: Documentation & UAT (Day 3-4)
- Update user documentation
- Write enterprise deployment guide
- Create UAT scenario with real enterprise workflow
- Validate end-to-end functionality
Deliverable: Complete documentation and validated implementation.
Open Questions
Q1: Should we support recursive wildcards?
Current proposal: Single-segment wildcards only (*)
"code://rust/*/tls/*" ✓ Supported
"code://rust/**/tls" ✗ Not supported (yet)
Trade-off: Recursive wildcards (**) are more flexible but harder to reason about. Start simple, add if needed.
Decision requested: Defer recursive wildcards to future enhancement?
Q2: What happens when multiple aliases match?
Current proposal: First match wins (aliases processed in declaration order).
Alternative: Most specific match wins (fewer wildcards = higher priority).
Trade-off: First-match is simpler but requires security teams to order aliases carefully.
Decision requested: First-match or most-specific?
Q3: Should invalid patterns fail Trust Pack creation?
Current proposal: Yes—validate patterns at creation time, reject invalid glob syntax.
Alternative: Warn but allow (lenient), fail at scan time.
Trade-off: Fail-fast is better for security teams but may be inconvenient during iteration.
Decision requested: Strict validation at creation time?
Q4: Should we cache pattern match results?
Current proposal: Recompute on each lookup (simple, no cache invalidation issues).
Alternative: Cache subject → policy_path map per scan.
Trade-off: Caching improves performance but adds complexity.
Decision requested: Defer optimization until benchmarks show need?
Appendix: Example Scenarios
Scenario A: Multi-Language TLS Enforcement
Security team creates:
aphoria bless \
--subject "code://standards/tls/cert_verification" \
--predicate "enabled" \
--value true
aphoria policy add-alias \
--pack standards.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" \
--target "code://java/*/tls/cert_verification" \
--target "code://kotlin/*/tls/cert_verification"
Rust team's code:
// src/client.rs
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true) // Violation!
.build()?;
Extractor output:
concept_path: "code://rust/api-service/tls/cert_verification"
predicate: "enabled"
value: false
Scan result:
BLOCK: TLS cert verification disabled
Policy: code://standards/tls/cert_verification
Matched via: code://rust/*/tls/cert_verification
Source: Acme-Security-Standards v1.0.0
Scenario B: Cloud-Specific Rules
Security team creates:
aphoria bless \
--subject "code://vendor/aws/s3/public_access" \
--predicate "blocked" \
--value true \
--reason "S3 buckets must not allow public access"
aphoria policy add-alias \
--pack cloud-rules.pack \
--policy-path "code://vendor/aws/s3/public_access" \
--target "code://*/*/aws/s3/public_access" \
--target "code://*/*/cloud/storage/s3/public"
Dev team's Terraform:
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.example.id
block_public_acls = false # Violation!
}
Scan detects via alias matching.
Scenario C: Internal Policy Exceptions
Security team creates exception:
# Allow MD5 for specific legacy system
aphoria bless \
--subject "code://internal/exceptions/legacy-auth/md5" \
--predicate "allowed" \
--value true \
--reason "Legacy auth system requires MD5 until migration complete"
aphoria policy add-alias \
--pack exceptions.pack \
--policy-path "code://internal/exceptions/legacy-auth/md5" \
--target "code://rust/legacy-auth-service/crypto/md5" \
--target "code://go/legacy-auth-service/crypto/md5"
Result: Only the specific legacy service can use MD5; all other services still blocked.
Glossary
| Term | Definition |
|---|---|
| Assertion | A claim about a subject-predicate-object relationship |
| Trust Pack | Signed bundle of assertions and aliases for distribution |
| Policy Alias | Mapping from policy path to extractor output patterns |
| Tail-Path Matching | Algorithm using last 2 path segments for index lookup |
| Glob Pattern | String pattern with * wildcards for matching paths |
| Issuer ID | Ed25519 public key identifying Trust Pack signer |
| BLOCK Verdict | Scan result indicating policy violation that should halt build |