# Complete Example: Detecting timeout=0 This example shows the complete flow from code violation → extractor → claim → conflict detection. --- ## Step 1: The Violation (Code) **File:** `src/config.rs` ```rust /// Message queue configuration pub struct Config { /// Connection timeout in seconds /// @aphoria:claim[safety] Timeout MUST be > 0 -- Zero timeout causes indefinite blocking pub timeout: Duration = Duration::from_secs(0); // ❌ VIOLATION pub max_retries: u32 = 3, pub backoff_ms: u64 = 1000, } ``` **Line 20:** `timeout: Duration = Duration::from_secs(0)` This violates the safety invariant that timeouts must be positive to prevent indefinite blocking. --- ## Step 2: The Claim (Authored) **File:** `.aphoria/claims.toml` ```toml [[claim]] id = "msgqueue-001" concept_path = "msgqueue/config/timeout" predicate = "zero" value = 0 comparison = "not_equals" # Timeout MUST NOT equal zero provenance = "Safety review 2024-12-20 by jml" invariant = "Connection timeout MUST be greater than zero seconds" consequence = "Zero timeout causes client to block indefinitely on connection attempts, leading to thread exhaustion under network failures" authority_tier = "expert" category = "safety" evidence = ["docs/sources/rabbitmq-best-practices.md"] status = "active" created_at = "2024-12-20T10:30:00Z" created_by = "jml" ``` **Key Fields:** - `concept_path = "msgqueue/config/timeout"` - Where this applies - `predicate = "zero"` - What we're checking (is it zero?) - `value = 0`, `comparison = "not_equals"` - Must NOT be zero - `consequence` - What breaks if violated --- ## Step 3: The Extractor (Declarative) **File:** `.aphoria/config.toml` ```toml [[extractors.declarative]] name = "timeout_zero_detector" description = "Detects Duration::from_secs(0) timeout values" pattern = 'timeout:\s*Duration::from_secs\(0\)' languages = ["rust"] [extractors.declarative.claim] subject = "msgqueue/config/timeout" # ← MUST match claim's concept_path EXACTLY predicate = "zero" # ← MUST match claim's predicate value = 0 # ← Value observed when pattern matches confidence = 0.95 ``` **How It Maps:** | Extractor Field | Claim Field | Purpose | |----------------|-------------|---------| | `subject` | `concept_path` | Where observation applies (MUST match) | | `predicate` | `predicate` | What attribute we're observing (MUST match) | | `value` | `value` | Value observed in code (violation value) | | `comparison` | (in claim) | How to compare observation vs claim | **Critical:** `subject` must EXACTLY match claim's `concept_path`. Partial paths won't work. --- ## Step 4: The Scan (Detection) ```bash aphoria scan --format json > scan-results.json ``` **What Happens:** ### 4.1. Extractor Runs 1. Aphoria loads `timeout_zero_detector` extractor 2. Scans `src/config.rs` for pattern `timeout:\s*Duration::from_secs\(0\)` 3. Finds match at line 20 ### 4.2. Observation Created ```json { "concept_path": "msgqueue/config/timeout", "predicate": "zero", "value": 0, "confidence": 0.95, "source": { "file": "src/config.rs", "line": 20, "extractor": "timeout_zero_detector" } } ``` ### 4.3. Claim Lookup Aphoria searches for claims with matching concept_path: - Tail-path matching: Last 2 segments of observation (`config/timeout`) vs last 2 of claim (`config/timeout`) - **Match found:** `msgqueue-001` ### 4.4. Comparison ``` Observation says: msgqueue/config/timeout :: zero = 0 Claim says: msgqueue/config/timeout :: zero NOT_EQUALS 0 Comparison: 0 NOT_EQUALS 0? Result: FALSE → CONFLICT ``` ### 4.5. Conflict Reported ```json { "claim_id": "msgqueue-001", "verdict": "CONFLICT", "observation": { "concept_path": "msgqueue/config/timeout", "predicate": "zero", "value": 0 }, "location": { "file": "src/config.rs", "line": 20 }, "consequence": "Zero timeout causes client to block indefinitely..." } ``` --- ## Step 5: The Report (Human-Readable) **Console Output:** ``` ❌ CONFLICT: msgqueue-001 at src/config.rs:20 Claim: Connection timeout MUST be greater than zero seconds Found: timeout = Duration::from_secs(0) Why this matters: Zero timeout causes client to block indefinitely on connection attempts, leading to thread exhaustion under network failures. Fix: Set timeout to positive value (recommended: 30s) ``` --- ## Step 6: The Fix (Remediation) **Before:** ```rust pub timeout: Duration = Duration::from_secs(0); // ❌ Violation ``` **After:** ```rust pub timeout: Duration = Duration::from_secs(30); // ✅ Compliant ``` **Re-scan:** ```bash aphoria scan --format json > scan-results-v2.json ``` **Result:** ```json { "claim_id": "msgqueue-001", "verdict": "PASS", "observation": { "concept_path": "msgqueue/config/timeout", "predicate": "zero", "value": 30 }, "location": { "file": "src/config.rs", "line": 20 } } ``` No conflict - claim is satisfied. --- ## Complete Flow Diagram ``` ┌─────────────────────────────────────────────────────────────────┐ │ 1. CODE (src/config.rs:20) │ │ timeout: Duration = Duration::from_secs(0) ← VIOLATION │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 2. EXTRACTOR RUNS (.aphoria/config.toml) │ │ Pattern: timeout:\s*Duration::from_secs\(0\) │ │ Languages: ["rust"] │ │ → MATCH FOUND at src/config.rs:20 │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 3. OBSERVATION CREATED │ │ concept_path: msgqueue/config/timeout │ │ predicate: zero │ │ value: 0 │ │ confidence: 0.95 │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 4. CLAIM LOOKUP (.aphoria/claims.toml) │ │ Search: concept_path ending with "config/timeout" │ │ → FOUND: msgqueue-001 │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 5. COMPARISON │ │ Observation: zero = 0 │ │ Claim: zero NOT_EQUALS 0 │ │ → 0 NOT_EQUALS 0? FALSE → CONFLICT │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 6. CONFLICT REPORTED │ │ ❌ src/config.rs:20: Connection timeout MUST be > 0 │ │ Consequence: Indefinite blocking, thread exhaustion │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## Key Takeaways ### 1. Path Alignment is Critical ```toml # Claim concept_path = "msgqueue/config/timeout" # Extractor MUST match exactly subject = "msgqueue/config/timeout" # ✅ Matches # Common mistakes: subject = "config/timeout" # ❌ Won't match (missing prefix) subject = "timeout" # ❌ Won't match (too short) subject = "myapp/config/timeout" # ❌ Won't match (wrong prefix) ``` **Rule:** Copy claim's `concept_path` EXACTLY into extractor's `subject`. --- ### 2. Predicate Must Match ```toml # Claim predicate = "zero" # Extractor MUST use same predicate predicate = "zero" # ✅ Matches # Common mistakes: predicate = "value" # ❌ Different predicate predicate = "timeout" # ❌ Different predicate ``` --- ### 3. Value Represents Observed State **In code:** `Duration::from_secs(0)` **Observation:** `value = 0` (what we SEE) **Claim:** `value = 0`, `comparison = "not_equals"` (what we REJECT) The observation reports what EXISTS, the claim defines what's ALLOWED. --- ### 4. Comparison Happens in Claim Extractor just creates observations. The claim's `comparison` field determines PASS/CONFLICT: - `comparison = "equals"` → Observation must equal claim value - `comparison = "not_equals"` → Observation must NOT equal claim value - `comparison = "greater_than"` → Observation must be > claim value - `comparison = "less_than"` → Observation must be < claim value --- ## Validation Checklist Before running scan, verify: - [ ] **Pattern matches code:** Test with `grep -rE 'timeout:\s*Duration::from_secs\(0\)' src/` - [ ] **Subject matches claim:** Compare `.aphoria/config.toml` subject vs `.aphoria/claims.toml` concept_path - [ ] **Predicate matches claim:** Both use `"zero"` - [ ] **Language correct:** Code is Rust, extractor has `languages = ["rust"]` - [ ] **TOML valid:** No syntax errors in config --- ## Troubleshooting ### Problem: Pattern doesn't match code ```bash # Test pattern manually grep -rE 'timeout:\s*Duration::from_secs\(0\)' src/ # If no results, pattern is wrong # Adjust pattern to match actual code syntax ``` --- ### Problem: Extractor runs but 0 conflicts **Check 1: Were observations created?** ```bash jq '.observations | length' scan-results.json # Expected: > 0 ``` If 0 observations → pattern doesn't match code (see above) **Check 2: Does subject match claim concept_path?** ```bash grep "subject =" .aphoria/config.toml # Output: subject = "msgqueue/config/timeout" grep "concept_path =" .aphoria/claims.toml # Output: concept_path = "msgqueue/config/timeout" # Must be EXACTLY the same ``` If different → fix subject to match claim **Check 3: Does predicate match?** ```bash # In extractor config: predicate = "zero" # In claim: predicate = "zero" # Must be identical ``` --- ### Problem: Observations match wrong claim **Symptom:** Conflict reported for different claim than expected **Cause:** Tail-path matching found a different claim with similar path **Example:** - Observation: `msgqueue/config/timeout` - Claim A: `msgqueue/config/timeout` (expected) - Claim B: `other/config/timeout` (matched by tail) **Solution:** Use more specific concept paths (3+ segments) to avoid ambiguity. --- ## Related Examples - **Unbounded Queue:** `examples/extractors/unbounded-queue-example.md` (detects `Option = None`) - **TLS Disabled:** `examples/extractors/tls-disabled-example.md` (detects `verify = false`) - **Blocking in Async:** `examples/extractors/blocking-async-example.md` (detects `std::thread::sleep` in async fn) --- ## Further Reading - **Declarative Extractor Reference:** `docs/extractors/declarative-extractors.md` - **Claims Authoring Guide:** `docs/claims-authoring.md` - **Scan Workflow:** `docs/scanning-workflow.md` - **Tail-Path Matching Explained:** `docs/concepts/tail-path-matching.md` --- **Last Updated:** 2026-02-10 **Related Gap:** VG-DAY3-002 (concept path alignment)