# Policy Determinization Architecture
## Overview
The **Determinization** subsystem handles CVEs that arrive without complete evidence (EPSS, VEX, reachability). Rather than blocking pipelines or silently ignoring unknowns, it treats them as **probabilistic observations** that can mature as evidence arrives.
**Design Principles:**
1. **Uncertainty is first-class** - Missing signals contribute to entropy, not guesswork
2. **Graceful degradation** - Pipelines continue with guardrails, not hard blocks
3. **Automatic hardening** - Policies tighten as evidence accumulates
4. **Full auditability** - Every decision traces back to evidence state
## Problem Statement
When a CVE is discovered against a component, several scenarios create uncertainty:
| Scenario | Current Behavior | Desired Behavior |
|----------|------------------|------------------|
| EPSS not yet published | Treat as unknown severity | Explicit `SignalState.NotQueried` with default prior |
| VEX statement missing | Assume affected | Explicit uncertainty with configurable policy |
| Reachability indeterminate | Conservative block | Allow with guardrails in non-prod |
| Conflicting VEX sources | K4 Conflict state | Entropy penalty + human review trigger |
| Stale evidence (>14 days) | No special handling | Decay-adjusted confidence + auto-review |
## Architecture
### Component Diagram
```
+------------------------+
| Policy Engine |
| (Verdict Evaluation) |
+------------------------+
|
v
+----------------+ +-------------------+ +------------------------+
| Feedser |--->| Signal Aggregator |-->| Determinization Gate |
| (EPSS/VEX/KEV) | | (Null-aware) | | (Entropy Thresholds) |
+----------------+ +-------------------+ +------------------------+
| |
v v
+-------------------+ +-------------------+
| Uncertainty Score | | GuardRails Policy |
| Calculator | | (Allow/Quarantine)|
+-------------------+ +-------------------+
| |
v v
+-------------------+ +-------------------+
| Decay Calculator | | Observation State |
| (Half-life) | | (pending_determ) |
+-------------------+ +-------------------+
```
### Library Structure
```
src/Policy/__Libraries/StellaOps.Policy.Determinization/
├── Models/
│ ├── ObservationState.cs # CVE observation lifecycle states
│ ├── SignalState.cs # Null-aware signal wrapper
│ ├── SignalSnapshot.cs # Point-in-time signal collection
│ ├── UncertaintyScore.cs # Knowledge completeness entropy
│ ├── ObservationDecay.cs # Per-CVE decay configuration
│ ├── GuardRails.cs # Guardrail policy outcomes
│ └── DeterminizationContext.cs # Evaluation context container
├── Scoring/
│ ├── IUncertaintyScoreCalculator.cs
│ ├── UncertaintyScoreCalculator.cs # entropy = 1 - evidence_sum
│ ├── IDecayedConfidenceCalculator.cs
│ ├── DecayedConfidenceCalculator.cs # Half-life decay application
│ ├── SignalWeights.cs # Configurable signal weights
│ └── PriorDistribution.cs # Default priors for missing signals
├── Policies/
│ ├── IDeterminizationPolicy.cs
│ ├── DeterminizationPolicy.cs # Allow/quarantine/escalate rules
│ ├── GuardRailsPolicy.cs # Guardrails configuration
│ ├── DeterminizationRuleSet.cs # Rule definitions
│ └── EnvironmentThresholds.cs # Per-environment thresholds
├── Gates/
│ ├── IDeterminizationGate.cs
│ ├── DeterminizationGate.cs # Policy engine gate
│ └── DeterminizationGateOptions.cs
├── Subscriptions/
│ ├── ISignalUpdateSubscription.cs
│ ├── SignalUpdateHandler.cs # Re-evaluation on new signals
│ └── DeterminizationEventTypes.cs
├── DeterminizationOptions.cs # Global options
└── ServiceCollectionExtensions.cs # DI registration
```
## Data Models
### ObservationState
Represents the lifecycle state of a CVE observation, orthogonal to VEX status:
```csharp
///
/// Observation state for CVE tracking, independent of VEX status.
/// Allows a CVE to be "Affected" (VEX) but "PendingDeterminization" (observation).
///
public enum ObservationState
{
///
/// Initial state: CVE discovered but evidence incomplete.
/// Triggers guardrail-based policy evaluation.
///
PendingDeterminization = 0,
///
/// Evidence sufficient for confident determination.
/// Normal policy evaluation applies.
///
Determined = 1,
///
/// Multiple signals conflict (K4 Conflict state).
/// Requires human review regardless of confidence.
///
Disputed = 2,
///
/// Evidence decayed below threshold; needs refresh.
/// Auto-triggered when decay > threshold.
///
StaleRequiresRefresh = 3,
///
/// Manually flagged for review.
/// Bypasses automatic determinization.
///
ManualReviewRequired = 4,
///
/// CVE suppressed/ignored by policy exception.
/// Evidence tracking continues but decisions skip.
///
Suppressed = 5
}
```
### SignalState
Null-aware wrapper distinguishing "not queried" from "queried, value null":
```csharp
///
/// Wraps a signal value with query status metadata.
/// Distinguishes between: not queried, queried with value, queried but absent, query failed.
///
public sealed record SignalState
{
/// Status of the signal query.
public required SignalQueryStatus Status { get; init; }
/// Signal value if Status is Queried and value exists.
public T? Value { get; init; }
/// When the signal was last queried (UTC).
public DateTimeOffset? QueriedAt { get; init; }
/// Reason for failure if Status is Failed.
public string? FailureReason { get; init; }
/// Source that provided the value (feed ID, issuer, etc.).
public string? Source { get; init; }
/// Whether this signal contributes to uncertainty (true if not queried or failed).
public bool ContributesToUncertainty =>
Status is SignalQueryStatus.NotQueried or SignalQueryStatus.Failed;
/// Whether this signal has a usable value.
public bool HasValue => Status == SignalQueryStatus.Queried && Value is not null;
}
public enum SignalQueryStatus
{
/// Signal source not yet queried.
NotQueried = 0,
/// Signal source queried; value may be present or absent.
Queried = 1,
/// Signal query failed (timeout, network, parse error).
Failed = 2
}
```
### SignalSnapshot
Point-in-time collection of all signals for a CVE observation:
```csharp
///
/// Immutable snapshot of all signals for a CVE observation at a point in time.
///
public sealed record SignalSnapshot
{
/// CVE identifier (e.g., CVE-2026-12345).
public required string CveId { get; init; }
/// Subject component (PURL).
public required string SubjectPurl { get; init; }
/// Snapshot capture time (UTC).
public required DateTimeOffset CapturedAt { get; init; }
/// EPSS score signal.
public required SignalState Epss { get; init; }
/// VEX claim signal.
public required SignalState Vex { get; init; }
/// Reachability determination signal.
public required SignalState Reachability { get; init; }
/// Runtime observation signal (eBPF, dyld, ETW).
public required SignalState Runtime { get; init; }
/// Fix backport detection signal.
public required SignalState Backport { get; init; }
/// SBOM lineage signal.
public required SignalState SbomLineage { get; init; }
/// Known Exploited Vulnerability flag.
public required SignalState Kev { get; init; }
/// CVSS score signal.
public required SignalState Cvss { get; init; }
}
```
### UncertaintyScore
Knowledge completeness measurement (not code entropy):
```csharp
///
/// Measures knowledge completeness for a CVE observation.
/// High entropy (close to 1.0) means many signals are missing.
/// Low entropy (close to 0.0) means comprehensive evidence.
///
public sealed record UncertaintyScore
{
/// Entropy value [0.0-1.0]. Higher = more uncertain.
public required double Entropy { get; init; }
/// Completeness value [0.0-1.0]. Higher = more complete. (1 - Entropy)
public double Completeness => 1.0 - Entropy;
/// Signals that are missing or failed.
public required ImmutableArray MissingSignals { get; init; }
/// Weighted sum of present signals.
public required double WeightedEvidenceSum { get; init; }
/// Maximum possible weighted sum (all signals present).
public required double MaxPossibleWeight { get; init; }
/// Tier classification based on entropy.
public UncertaintyTier Tier => Entropy switch
{
<= 0.2 => UncertaintyTier.VeryLow, // Comprehensive evidence
<= 0.4 => UncertaintyTier.Low, // Good evidence coverage
<= 0.6 => UncertaintyTier.Medium, // Moderate gaps
<= 0.8 => UncertaintyTier.High, // Significant gaps
_ => UncertaintyTier.VeryHigh // Minimal evidence
};
}
public sealed record SignalGap(
string SignalName,
double Weight,
SignalQueryStatus Status,
string? Reason);
public enum UncertaintyTier
{
VeryLow = 0, // Entropy <= 0.2
Low = 1, // Entropy <= 0.4
Medium = 2, // Entropy <= 0.6
High = 3, // Entropy <= 0.8
VeryHigh = 4 // Entropy > 0.8
}
```
### ObservationDecay
Time-based confidence decay configuration:
```csharp
///
/// Tracks evidence freshness decay for a CVE observation.
///
public sealed record ObservationDecay
{
/// Half-life for confidence decay. Default: 14 days per advisory.
public required TimeSpan HalfLife { get; init; }
/// Minimum confidence floor (never decays below). Default: 0.35.
public required double Floor { get; init; }
/// Last time any signal was updated (UTC).
public required DateTimeOffset LastSignalUpdate { get; init; }
/// Current decayed confidence multiplier [Floor-1.0].
public required double DecayedMultiplier { get; init; }
/// When next auto-review is scheduled (UTC).
public DateTimeOffset? NextReviewAt { get; init; }
/// Whether decay has triggered stale state.
public bool IsStale { get; init; }
}
```
### GuardRails
Policy outcome with monitoring requirements:
```csharp
///
/// Guardrails applied when allowing uncertain observations.
///
public sealed record GuardRails
{
/// Enable runtime monitoring for this observation.
public required bool EnableRuntimeMonitoring { get; init; }
/// Interval for automatic re-review.
public required TimeSpan ReviewInterval { get; init; }
/// EPSS threshold that triggers automatic escalation.
public required double EpssEscalationThreshold { get; init; }
/// Reachability status that triggers escalation.
public required ImmutableArray EscalatingReachabilityStates { get; init; }
/// Maximum time in guarded state before forced review.
public required TimeSpan MaxGuardedDuration { get; init; }
/// Alert channels for this observation.
public ImmutableArray AlertChannels { get; init; } = ImmutableArray.Empty;
/// Additional context for audit trail.
public string? PolicyRationale { get; init; }
}
```
## Scoring Algorithms
### Uncertainty Score Calculation
```csharp
///
/// Calculates knowledge completeness entropy from signal snapshot.
/// Formula: entropy = 1 - (sum of weighted present signals / max possible weight)
///
public sealed class UncertaintyScoreCalculator : IUncertaintyScoreCalculator
{
private readonly SignalWeights _weights;
public UncertaintyScore Calculate(SignalSnapshot snapshot)
{
var gaps = new List();
var weightedSum = 0.0;
var maxWeight = _weights.TotalWeight;
// EPSS signal
if (snapshot.Epss.HasValue)
weightedSum += _weights.Epss;
else
gaps.Add(new SignalGap("EPSS", _weights.Epss, snapshot.Epss.Status, snapshot.Epss.FailureReason));
// VEX signal
if (snapshot.Vex.HasValue)
weightedSum += _weights.Vex;
else
gaps.Add(new SignalGap("VEX", _weights.Vex, snapshot.Vex.Status, snapshot.Vex.FailureReason));
// Reachability signal
if (snapshot.Reachability.HasValue)
weightedSum += _weights.Reachability;
else
gaps.Add(new SignalGap("Reachability", _weights.Reachability, snapshot.Reachability.Status, snapshot.Reachability.FailureReason));
// Runtime signal
if (snapshot.Runtime.HasValue)
weightedSum += _weights.Runtime;
else
gaps.Add(new SignalGap("Runtime", _weights.Runtime, snapshot.Runtime.Status, snapshot.Runtime.FailureReason));
// Backport signal
if (snapshot.Backport.HasValue)
weightedSum += _weights.Backport;
else
gaps.Add(new SignalGap("Backport", _weights.Backport, snapshot.Backport.Status, snapshot.Backport.FailureReason));
// SBOM Lineage signal
if (snapshot.SbomLineage.HasValue)
weightedSum += _weights.SbomLineage;
else
gaps.Add(new SignalGap("SBOMLineage", _weights.SbomLineage, snapshot.SbomLineage.Status, snapshot.SbomLineage.FailureReason));
var entropy = 1.0 - (weightedSum / maxWeight);
return new UncertaintyScore
{
Entropy = Math.Clamp(entropy, 0.0, 1.0),
MissingSignals = gaps.ToImmutableArray(),
WeightedEvidenceSum = weightedSum,
MaxPossibleWeight = maxWeight
};
}
}
```
### Signal Weights (Configurable)
```csharp
///
/// Configurable weights for signal contribution to completeness.
/// Weights should sum to 1.0 for normalized entropy.
///
public sealed record SignalWeights
{
public double Vex { get; init; } = 0.25;
public double Epss { get; init; } = 0.15;
public double Reachability { get; init; } = 0.25;
public double Runtime { get; init; } = 0.15;
public double Backport { get; init; } = 0.10;
public double SbomLineage { get; init; } = 0.10;
public double TotalWeight =>
Vex + Epss + Reachability + Runtime + Backport + SbomLineage;
public SignalWeights Normalize()
{
var total = TotalWeight;
return new SignalWeights
{
Vex = Vex / total,
Epss = Epss / total,
Reachability = Reachability / total,
Runtime = Runtime / total,
Backport = Backport / total,
SbomLineage = SbomLineage / total
};
}
}
```
### Decay Calculation
```csharp
///
/// Applies exponential decay to confidence based on evidence staleness.
/// Formula: decayed = max(floor, exp(-ln(2) * age_days / half_life_days))
///
public sealed class DecayedConfidenceCalculator : IDecayedConfidenceCalculator
{
private readonly TimeProvider _timeProvider;
public ObservationDecay Calculate(
DateTimeOffset lastSignalUpdate,
TimeSpan halfLife,
double floor = 0.35)
{
var now = _timeProvider.GetUtcNow();
var ageDays = (now - lastSignalUpdate).TotalDays;
double decayedMultiplier;
if (ageDays <= 0)
{
decayedMultiplier = 1.0;
}
else
{
var rawDecay = Math.Exp(-Math.Log(2) * ageDays / halfLife.TotalDays);
decayedMultiplier = Math.Max(rawDecay, floor);
}
// Calculate next review time (when decay crosses 50% threshold)
var daysTo50Percent = halfLife.TotalDays;
var nextReviewAt = lastSignalUpdate.AddDays(daysTo50Percent);
return new ObservationDecay
{
HalfLife = halfLife,
Floor = floor,
LastSignalUpdate = lastSignalUpdate,
DecayedMultiplier = decayedMultiplier,
NextReviewAt = nextReviewAt,
IsStale = decayedMultiplier <= 0.5
};
}
}
```
## Policy Rules
### Determinization Policy
```csharp
///
/// Implements allow/quarantine/escalate logic per advisory specification.
///
public sealed class DeterminizationPolicy : IDeterminizationPolicy
{
private readonly DeterminizationOptions _options;
private readonly ILogger _logger;
public DeterminizationResult Evaluate(DeterminizationContext ctx)
{
var snapshot = ctx.SignalSnapshot;
var uncertainty = ctx.UncertaintyScore;
var decay = ctx.Decay;
var env = ctx.Environment;
// Rule 1: Escalate if runtime evidence shows loaded
if (snapshot.Runtime.HasValue &&
snapshot.Runtime.Value!.ObservedLoaded)
{
return DeterminizationResult.Escalated(
"Runtime evidence shows vulnerable code loaded",
PolicyVerdictStatus.Escalated);
}
// Rule 2: Quarantine if EPSS >= threshold or proven reachable
if (snapshot.Epss.HasValue &&
snapshot.Epss.Value!.Score >= _options.EpssQuarantineThreshold)
{
return DeterminizationResult.Quarantined(
$"EPSS score {snapshot.Epss.Value.Score:P1} exceeds threshold {_options.EpssQuarantineThreshold:P1}",
PolicyVerdictStatus.Blocked);
}
if (snapshot.Reachability.HasValue &&
snapshot.Reachability.Value!.Status == ReachabilityStatus.Reachable)
{
return DeterminizationResult.Quarantined(
"Vulnerable code is reachable via call graph",
PolicyVerdictStatus.Blocked);
}
// Rule 3: Allow with guardrails if score < threshold AND entropy > threshold AND non-prod
var trustScore = ctx.TrustScore;
if (trustScore < _options.GuardedAllowScoreThreshold &&
uncertainty.Entropy > _options.GuardedAllowEntropyThreshold &&
env != DeploymentEnvironment.Production)
{
var guardrails = BuildGuardrails(ctx);
return DeterminizationResult.GuardedAllow(
$"Uncertain observation (entropy={uncertainty.Entropy:F2}) allowed with guardrails in {env}",
PolicyVerdictStatus.GuardedPass,
guardrails);
}
// Rule 4: Block in production with high entropy
if (env == DeploymentEnvironment.Production &&
uncertainty.Entropy > _options.ProductionBlockEntropyThreshold)
{
return DeterminizationResult.Quarantined(
$"High uncertainty (entropy={uncertainty.Entropy:F2}) not allowed in production",
PolicyVerdictStatus.Blocked);
}
// Rule 5: Defer if evidence is stale
if (decay.IsStale)
{
return DeterminizationResult.Deferred(
$"Evidence stale (last update: {decay.LastSignalUpdate:u}), requires refresh",
PolicyVerdictStatus.Deferred);
}
// Default: Allow (sufficient evidence or acceptable risk)
return DeterminizationResult.Allowed(
"Evidence sufficient for determination",
PolicyVerdictStatus.Pass);
}
private GuardRails BuildGuardrails(DeterminizationContext ctx) =>
new GuardRails
{
EnableRuntimeMonitoring = true,
ReviewInterval = TimeSpan.FromDays(_options.GuardedReviewIntervalDays),
EpssEscalationThreshold = _options.EpssQuarantineThreshold,
EscalatingReachabilityStates = ImmutableArray.Create("Reachable", "ObservedReachable"),
MaxGuardedDuration = TimeSpan.FromDays(_options.MaxGuardedDurationDays),
PolicyRationale = $"Auto-allowed with entropy={ctx.UncertaintyScore.Entropy:F2}, trust={ctx.TrustScore:F2}"
};
}
```
### Environment Thresholds
```csharp
///
/// Per-environment threshold configuration.
///
public sealed record EnvironmentThresholds
{
public DeploymentEnvironment Environment { get; init; }
public double MinConfidenceForNotAffected { get; init; }
public double MaxEntropyForAllow { get; init; }
public double EpssBlockThreshold { get; init; }
public bool RequireReachabilityForAllow { get; init; }
}
public static class DefaultEnvironmentThresholds
{
public static EnvironmentThresholds Production => new()
{
Environment = DeploymentEnvironment.Production,
MinConfidenceForNotAffected = 0.75,
MaxEntropyForAllow = 0.3,
EpssBlockThreshold = 0.3,
RequireReachabilityForAllow = true
};
public static EnvironmentThresholds Staging => new()
{
Environment = DeploymentEnvironment.Staging,
MinConfidenceForNotAffected = 0.60,
MaxEntropyForAllow = 0.5,
EpssBlockThreshold = 0.4,
RequireReachabilityForAllow = true
};
public static EnvironmentThresholds Development => new()
{
Environment = DeploymentEnvironment.Development,
MinConfidenceForNotAffected = 0.40,
MaxEntropyForAllow = 0.7,
EpssBlockThreshold = 0.6,
RequireReachabilityForAllow = false
};
}
```
## Integration Points
### Feedser Integration
Feedser attaches `SignalState` to CVE observations:
```csharp
// In Feedser: EpssSignalAttacher
public async Task> AttachEpssAsync(string cveId, CancellationToken ct)
{
try
{
var evidence = await _epssClient.GetScoreAsync(cveId, ct);
return new SignalState
{
Status = SignalQueryStatus.Queried,
Value = evidence,
QueriedAt = _timeProvider.GetUtcNow(),
Source = "first.org"
};
}
catch (EpssNotFoundException)
{
return new SignalState
{
Status = SignalQueryStatus.Queried,
Value = null,
QueriedAt = _timeProvider.GetUtcNow(),
Source = "first.org"
};
}
catch (Exception ex)
{
return new SignalState
{
Status = SignalQueryStatus.Failed,
Value = null,
FailureReason = ex.Message
};
}
}
```
### Policy Engine Gate
```csharp
// In Policy.Engine: DeterminizationGate
public sealed class DeterminizationGate : IPolicyGate
{
private readonly IDeterminizationPolicy _policy;
private readonly IUncertaintyScoreCalculator _uncertaintyCalculator;
private readonly IDecayedConfidenceCalculator _decayCalculator;
public async Task EvaluateAsync(PolicyEvaluationContext ctx, CancellationToken ct)
{
var snapshot = await BuildSignalSnapshotAsync(ctx, ct);
var uncertainty = _uncertaintyCalculator.Calculate(snapshot);
var decay = _decayCalculator.Calculate(snapshot.CapturedAt, ctx.Options.DecayHalfLife);
var determCtx = new DeterminizationContext
{
SignalSnapshot = snapshot,
UncertaintyScore = uncertainty,
Decay = decay,
TrustScore = ctx.TrustScore,
Environment = ctx.Environment
};
var result = _policy.Evaluate(determCtx);
return new GateResult
{
Passed = result.Status is PolicyVerdictStatus.Pass or PolicyVerdictStatus.GuardedPass,
Status = result.Status,
Reason = result.Reason,
GuardRails = result.GuardRails,
Metadata = new Dictionary
{
["uncertainty_entropy"] = uncertainty.Entropy,
["uncertainty_tier"] = uncertainty.Tier.ToString(),
["decay_multiplier"] = decay.DecayedMultiplier,
["missing_signals"] = uncertainty.MissingSignals.Select(g => g.SignalName).ToArray()
}
};
}
}
```
### Graph Integration
CVE nodes in the Graph module carry `ObservationState` and `UncertaintyScore`:
```csharp
// Extended CVE node for Graph module
public sealed record CveObservationNode
{
public required string CveId { get; init; }
public required string SubjectPurl { get; init; }
// VEX status (orthogonal to observation state)
public required VexClaimStatus? VexStatus { get; init; }
// Observation lifecycle state
public required ObservationState ObservationState { get; init; }
// Knowledge completeness
public required UncertaintyScore Uncertainty { get; init; }
// Evidence freshness
public required ObservationDecay Decay { get; init; }
// Trust score (from confidence aggregation)
public required double TrustScore { get; init; }
// Policy outcome
public required PolicyVerdictStatus PolicyHint { get; init; }
// Guardrails if GuardedPass
public GuardRails? GuardRails { get; init; }
}
```
## Event-Driven Re-evaluation
When new signals arrive, the system re-evaluates affected observations:
```csharp
public sealed class SignalUpdateHandler : ISignalUpdateSubscription
{
private readonly IObservationRepository _observations;
private readonly IDeterminizationPolicy _policy;
private readonly IEventPublisher _events;
public async Task HandleAsync(SignalUpdatedEvent evt, CancellationToken ct)
{
// Find observations affected by this signal
var affected = await _observations.FindByCveAndPurlAsync(evt.CveId, evt.Purl, ct);
foreach (var obs in affected)
{
// Rebuild signal snapshot
var snapshot = await BuildCurrentSnapshotAsync(obs, ct);
// Recalculate uncertainty
var uncertainty = _uncertaintyCalculator.Calculate(snapshot);
// Re-evaluate policy
var result = _policy.Evaluate(new DeterminizationContext
{
SignalSnapshot = snapshot,
UncertaintyScore = uncertainty,
// ... other context
});
// Transition state if needed
var newState = DetermineNewState(obs.ObservationState, result, uncertainty);
if (newState != obs.ObservationState)
{
await _observations.UpdateStateAsync(obs.Id, newState, ct);
await _events.PublishAsync(new ObservationStateChangedEvent(
obs.Id, obs.ObservationState, newState, result.Reason), ct);
}
}
}
private ObservationState DetermineNewState(
ObservationState current,
DeterminizationResult result,
UncertaintyScore uncertainty)
{
// Transition logic
if (result.Status == PolicyVerdictStatus.Escalated)
return ObservationState.ManualReviewRequired;
if (uncertainty.Tier == UncertaintyTier.VeryLow)
return ObservationState.Determined;
if (current == ObservationState.PendingDeterminization &&
uncertainty.Tier <= UncertaintyTier.Low)
return ObservationState.Determined;
return current;
}
}
```
## Configuration
```csharp
public sealed class DeterminizationOptions
{
/// EPSS score that triggers quarantine (block). Default: 0.4
public double EpssQuarantineThreshold { get; set; } = 0.4;
/// Trust score threshold for guarded allow. Default: 0.5
public double GuardedAllowScoreThreshold { get; set; } = 0.5;
/// Entropy threshold for guarded allow. Default: 0.4
public double GuardedAllowEntropyThreshold { get; set; } = 0.4;
/// Entropy threshold for production block. Default: 0.3
public double ProductionBlockEntropyThreshold { get; set; } = 0.3;
/// Half-life for evidence decay in days. Default: 14
public int DecayHalfLifeDays { get; set; } = 14;
/// Minimum confidence floor after decay. Default: 0.35
public double DecayFloor { get; set; } = 0.35;
/// Review interval for guarded observations in days. Default: 7
public int GuardedReviewIntervalDays { get; set; } = 7;
/// Maximum time in guarded state in days. Default: 30
public int MaxGuardedDurationDays { get; set; } = 30;
/// Signal weights for uncertainty calculation.
public SignalWeights SignalWeights { get; set; } = new();
/// Per-environment threshold overrides.
public Dictionary EnvironmentThresholds { get; set; } = new();
}
```
## Verdict Status Extension
Extended `PolicyVerdictStatus` enum:
```csharp
public enum PolicyVerdictStatus
{
Pass = 0, // Finding meets policy requirements
GuardedPass = 1, // NEW: Allow with runtime monitoring enabled
Blocked = 2, // Finding fails policy checks; must be remediated
Ignored = 3, // Finding deliberately ignored via exception
Warned = 4, // Finding passes but with warnings
Deferred = 5, // Decision deferred; needs additional evidence
Escalated = 6, // Decision escalated for human review
RequiresVex = 7 // VEX statement required to make decision
}
```
## Metrics & Observability
```csharp
public static class DeterminizationMetrics
{
// Counters
public static readonly Counter ObservationsCreated =
Meter.CreateCounter("stellaops_determinization_observations_created_total");
public static readonly Counter StateTransitions =
Meter.CreateCounter("stellaops_determinization_state_transitions_total");
public static readonly Counter PolicyEvaluations =
Meter.CreateCounter("stellaops_determinization_policy_evaluations_total");
// Histograms
public static readonly Histogram UncertaintyEntropy =
Meter.CreateHistogram("stellaops_determinization_uncertainty_entropy");
public static readonly Histogram DecayMultiplier =
Meter.CreateHistogram("stellaops_determinization_decay_multiplier");
// Gauges
public static readonly ObservableGauge PendingObservations =
Meter.CreateObservableGauge("stellaops_determinization_pending_observations",
() => /* query count */);
public static readonly ObservableGauge StaleObservations =
Meter.CreateObservableGauge("stellaops_determinization_stale_observations",
() => /* query count */);
}
```
## Testing Strategy
| Test Category | Focus Area | Example |
|---------------|------------|---------|
| Unit | Uncertainty calculation | Missing 2 signals = correct entropy |
| Unit | Decay calculation | 14 days = 50% multiplier |
| Unit | Policy rules | EPSS 0.5 + dev = guarded allow |
| Integration | Signal attachment | Feedser EPSS query → SignalState |
| Integration | State transitions | New VEX → PendingDeterminization → Determined |
| Determinism | Same input → same output | Canonical snapshot → reproducible entropy |
| Property | Entropy bounds | Always [0.0, 1.0] |
| Property | Decay monotonicity | Older → lower multiplier |
## Security Considerations
1. **No Guessing:** Missing signals use explicit priors, never random values
2. **Audit Trail:** Every state transition logged with evidence snapshot
3. **Conservative Defaults:** Production blocks high entropy; only non-prod allows guardrails
4. **Escalation Path:** Runtime evidence always escalates regardless of other signals
5. **Tamper Detection:** Signal snapshots hashed for integrity verification
## References
- Product Advisory: "Unknown CVEs: graceful placeholders, not blockers"
- Existing: `src/Policy/__Libraries/StellaOps.Policy.Unknowns/`
- Existing: `src/Policy/__Libraries/StellaOps.Policy/Confidence/`
- Existing: `src/Excititor/__Libraries/StellaOps.Excititor.Core/TrustVector/`
- OpenVEX Specification: https://openvex.dev/
- EPSS Model: https://www.first.org/epss/