Files
git.stella-ops.org/src/Policy/__Libraries/StellaOps.Policy.Determinization/AGENTS.md
StellaOps Bot 37e11918e0 save progress
2026-01-06 09:42:20 +02:00

5.4 KiB

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<T>: 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<T> to wrap signal values:

// Good - explicit status
var epss = SignalState<EpssEvidence>.WithValue(evidence, queriedAt, "first.org");
var vex = SignalState<VexClaimSummary>.Absent(queriedAt, "vendor");
var reach = SignalState<ReachabilityEvidence>.NotQueried();
var failed = SignalState<CvssEvidence>.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):

// 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:

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<T> 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

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)
  • 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