# StellaOps.Policy.Determinization - Agent Guide ## Module Overview The **Determinization** library handles CVEs that arrive without complete evidence (EPSS, VEX, reachability). It treats unknown observations as probabilistic with entropy-weighted trust that matures as evidence arrives. **Key Concepts:** - `ObservationState`: Lifecycle state for CVE observations (PendingDeterminization, Determined, Disputed, etc.) - `SignalState`: Null-aware wrapper distinguishing "not queried" from "queried but absent" - `UncertaintyScore`: Knowledge completeness measurement (high entropy = missing signals) - `ObservationDecay`: Time-based confidence decay with configurable half-life - `GuardRails`: Monitoring requirements when allowing uncertain observations ## Directory Structure ``` src/Policy/__Libraries/StellaOps.Policy.Determinization/ ├── Models/ # Core data models │ ├── ObservationState.cs │ ├── SignalState.cs │ ├── SignalSnapshot.cs │ ├── UncertaintyScore.cs │ ├── ObservationDecay.cs │ ├── GuardRails.cs │ └── DeterminizationContext.cs ├── Evidence/ # Signal evidence types │ ├── EpssEvidence.cs │ ├── VexClaimSummary.cs │ ├── ReachabilityEvidence.cs │ └── ... ├── Scoring/ # Calculation services │ ├── UncertaintyScoreCalculator.cs │ ├── DecayedConfidenceCalculator.cs │ ├── TrustScoreAggregator.cs │ └── SignalWeights.cs ├── Policies/ # Policy rules (in Policy.Engine) └── DeterminizationOptions.cs ``` ## Key Patterns ### 1. SignalState Usage Always use `SignalState` to wrap signal values: ```csharp // Good - explicit status var epss = SignalState.WithValue(evidence, queriedAt, "first.org"); var vex = SignalState.Absent(queriedAt, "vendor"); var reach = SignalState.NotQueried(); var failed = SignalState.Failed("Timeout"); // Bad - nullable without status EpssEvidence? epss = null; // Can't tell if not queried or absent ``` ### 2. Uncertainty Calculation Entropy = 1 - (weighted present signals / max weight): ```csharp // All signals present = 0.0 entropy (fully certain) // No signals present = 1.0 entropy (fully uncertain) // Formula uses configurable weights per signal type ``` ### 3. Decay Calculation Exponential decay with floor: ```csharp decayed = max(floor, exp(-ln(2) * age_days / half_life_days)) // Default: 14-day half-life, 0.35 floor // After 14 days: ~50% confidence // After 28 days: ~35% confidence (floor) ``` ### 4. Policy Rules Rules evaluate in priority order (lower = first): | Priority | Rule | Outcome | |----------|------|---------| | 10 | Runtime shows loaded | Escalated | | 20 | EPSS >= threshold | Blocked | | 25 | Proven reachable | Blocked | | 30 | High entropy in prod | Blocked | | 40 | Evidence stale | Deferred | | 50 | Uncertain + non-prod | GuardedPass | | 60 | Unreachable + confident | Pass | | 70 | Sufficient evidence | Pass | | 100 | Default | Deferred | ## Testing Guidelines ### Unit Tests Required 1. `SignalState` factory methods 2. `UncertaintyScoreCalculator` entropy bounds [0.0, 1.0] 3. `DecayedConfidenceCalculator` half-life formula 4. Policy rule priority ordering 5. State transition logic ### Property Tests - Entropy always in [0.0, 1.0] - Decay monotonically decreasing with age - Same snapshot produces same uncertainty ### Integration Tests - DI registration with configuration - Signal snapshot building - Policy gate evaluation ## Configuration ```yaml Determinization: EpssQuarantineThreshold: 0.4 GuardedAllowScoreThreshold: 0.5 GuardedAllowEntropyThreshold: 0.4 ProductionBlockEntropyThreshold: 0.3 DecayHalfLifeDays: 14 DecayFloor: 0.35 GuardedReviewIntervalDays: 7 MaxGuardedDurationDays: 30 SignalWeights: Vex: 0.25 Epss: 0.15 Reachability: 0.25 Runtime: 0.15 Backport: 0.10 SbomLineage: 0.10 ``` ## Common Pitfalls 1. **Don't confuse EntropySignal with UncertaintyScore**: `EntropySignal` measures code complexity; `UncertaintyScore` measures knowledge completeness. 2. **Always inject TimeProvider**: Never use `DateTime.UtcNow` directly for decay calculations. 3. **Normalize weights before calculation**: Call `SignalWeights.Normalize()` to ensure weights sum to 1.0. 4. **Check signal status before accessing value**: `signal.HasValue` must be true before using `signal.Value!`. 5. **Handle all ObservationStates**: Switch expressions must be exhaustive. ## Dependencies - `StellaOps.Policy` (PolicyVerdictStatus, existing confidence models) - `System.Collections.Immutable` (ImmutableArray for collections) - `Microsoft.Extensions.Options` (configuration) - `Microsoft.Extensions.Logging` (logging) ## Related Modules - **Policy.Engine**: DeterminizationGate integrates with policy pipeline - **Feedser**: Signal attachers emit SignalState - **VexLens**: VEX updates emit SignalUpdatedEvent - **Graph**: CVE nodes carry ObservationState and UncertaintyScore - **Findings**: Observation persistence and audit trail ## Sprint References - SPRINT_20260106_001_001_LB: Core models - SPRINT_20260106_001_002_LB: Scoring services - SPRINT_20260106_001_003_POLICY: Policy integration - SPRINT_20260106_001_004_BE: Backend integration - SPRINT_20260106_001_005_FE: Frontend UI