Uncertainty States & Entropy Scoring

Status: Implemented for reachability facts in Signals (tier calculation and tier-aware risk score). Verified against commit 942a18727c (2026-09-26). Re-verify with: grep -n 'CalculateTier\|GetRiskModifier\|CalculateRiskScore' -A12 src/Signals/StellaOps.Signals/Lattice/UncertaintyTier.cs; grep -n 'class UncertaintyTierGateOptions' -A20 src/Policy/StellaOps.Policy.Engine/Gates/PolicyGateOptions.cs Ownership note: this guide describes Signals uncertainty states, not the Unknowns service. The UnknownsRegistry evidence type below is Signals’ own unresolved-symbol count, not a call to unknowns-web. Owners: Signals Guild · Policy Guild · UI Guild.

Audience: Signals, Policy, and Console engineers who consume or act on uncertainty data.

Stella Ops treats missing data and untrusted evidence as first-class uncertainty states, not silent false negatives. Signals persists uncertainty state entries alongside reachability facts and derives a deterministic riskScore that increases when entropy is high. Policy gates and the Console then reason over those states explicitly, so a release decision can never quietly rest on an unproven “not affected”.


1. Core states (extensible)

CodeNameMeaning
U1MissingSymbolResolutionUnresolved symbols/edges prevent a complete reachability proof.
U2MissingPurlPackage identity/version is ambiguous (lockfile absent, heuristics only).
U3UntrustedAdvisoryAdvisory source lacks provenance/corroboration.
U4UnknownNo analyzers have processed this subject; baseline uncertainty.

Each state records:


1.1 Uncertainty Tiers (v1 — Sprint 0401)

Uncertainty states are grouped into tiers that determine policy thresholds and UI treatment.

Tier Definitions

TierEntropy RangeStatesRisk ModifierPolicy Implication
T1 (High)0.7 - 1.0U1 (high), U4+50%Block “not_affected”, require human review
T2 (Medium)0.4 - 0.69U1 (medium), U2+25%Warn on “not_affected”, flag for review
T3 (Low)0.1 - 0.39U2 (low), U3+10%Allow “not_affected” with advisory note
T4 (Negligible)0.0 - 0.09U3 (low)+0%Normal processing, no special handling

Tier Assignment Rules

  1. U1 (MissingSymbolResolution):

    • entropy >= 0.7 → T1 (>30% unknowns in callgraph)
    • entropy >= 0.4 → T2 (15-30% unknowns)
    • entropy < 0.4 → T3 (<15% unknowns)
  2. U2 (MissingPurl):

    • entropy >= 0.5 → T2 (>50% packages unresolved)
    • entropy < 0.5 → T3 (<50% packages unresolved)
  3. U3 (UntrustedAdvisory):

    • entropy >= 0.6 → T3 (no corroboration)
    • entropy < 0.6 → T4 (partial corroboration)
  4. U4 (Unknown):

    • Always T1 (no analysis performed = maximum uncertainty)

Aggregate Tier Calculation

When multiple uncertainty states exist, the aggregate tier is the maximum (most severe):

aggregateTier = max(tier(state) for state in uncertainty.states)

2. JSON shape

{
  "uncertainty": {
    "states": [
      {
        "code": "U1",
        "name": "MissingSymbolResolution",
        "entropy": 0.72,
        "timestamp": "2025-11-12T14:12:00Z",
        "evidence": [
          {
            "type": "UnknownsRegistry",
            "sourceId": "signals.unknowns",
            "detail": "unknownsCount=12;unknownsPressure=0.375"
          }
        ]
      }
    ]
  }
}

3. Risk score math (Signals)

Signals computes a riskScore deterministically during reachability recompute:

meanEntropy  = avg(uncertainty.states[].entropy)              // 0 when no states
entropyBoost = clamp(meanEntropy * k, 0 .. boostCeiling)
riskScore    = clamp(baseScore * (1 + entropyBoost), 0 .. 1)

Where:

§5 adds the tier modifier; the implemented formula is the §5 one.


4. Policy guidance (high level)

Uncertainty should bias decisions away from “not affected” when evidence is missing:

See the reachability lattice guide for the current reachability score model and the Signals reachability contract for the Signals API contract.


5. Tier-Based Risk Score (v1 — Sprint 0401)

Risk Score Formula

Building on §3, the v1 risk score incorporates tier-based modifiers:

tierModifier = {
  T1: 0.50,
  T2: 0.25,
  T3: 0.10,
  T4: 0.00
}[aggregateTier]

riskScore = clamp(baseScore * (1 + tierModifier + entropyBoost), 0 .. 1)

Where:

Example Calculation

Given:
  - baseScore = 0.4 (moderate reachability)
  - uncertainty.states = [
      {code: "U1", entropy: 0.72},  // T1 tier
      {code: "U3", entropy: 0.45}   // T3 tier
    ]
  - aggregateTier = T1 (max of T1, T3)
  - tierModifier = 0.50

  meanEntropy = (0.72 + 0.45) / 2 = 0.585
  entropyBoost = clamp(0.585 * 0.5, 0 .. 0.5) = 0.2925

  riskScore = clamp(0.4 * (1 + 0.50 + 0.2925), 0 .. 1)
            = clamp(0.4 * 1.7925, 0 .. 1)
            = clamp(0.717, 0 .. 1)
            = 0.717

Policy gate behavior per tier

The Policy Engine uncertainty-tier gate is configured by PolicyGates:UncertaintyTier (UncertaintyTierGateOptions in src/Policy/StellaOps.Policy.Engine/Gates/PolicyGateOptions.cs):

OptionDefaultEffect
BlockT1ForNotAffectedtrueT1 uncertainty blocks a not_affected claim
WarnT2ForNotAffectedtrueT2 uncertainty warns on a not_affected claim
ReviewT1ForAffectedtrueT1 uncertainty on an affected claim requires an explicit override

Signals exposes the same per-tier semantics through UncertaintyTierExtensions.BlocksNotAffected (T1) and RequiresWarning (T1 and T2).


6. JSON Schema (v1)

Extended schema with tier information:

{
  "uncertainty": {
    "states": [
      {
        "code": "U1",
        "name": "MissingSymbolResolution",
        "entropy": 0.72,
        "tier": "T1",
        "timestamp": "2025-12-13T10:00:00Z",
        "evidence": [
          {
            "type": "UnknownsRegistry",
            "sourceId": "signals.unknowns",
            "detail": "unknownsCount=45;totalSymbols=125;unknownsPressure=0.36"
          }
        ]
      },
      {
        "code": "U4",
        "name": "Unknown",
        "entropy": 1.0,
        "tier": "T1",
        "timestamp": "2025-12-13T10:00:00Z",
        "evidence": [
          {
            "type": "NoAnalysis",
            "sourceId": "signals.bootstrap",
            "detail": "subject not yet analyzed"
          }
        ]
      }
    ],
    "aggregateTier": "T1",
    "riskScore": 0.717,
    "computedAt": "2025-12-13T10:00:00Z"
  }
}

7. Implementation Pointers


8. Policy Guidance (v1 — Sprint 0401)

Uncertainty gates enforce evidence-quality thresholds in the Policy Engine. When entropy is too high or evidence is missing, policies block or downgrade VEX decisions.

8.1 Gate Mapping

GateUncertainty StateTierPolicy Action
U1MissingSymbolResolutionT1/T2Block not_affected, require review
U2MissingPurlT2/T3Warn on not_affected, add review flag
U3UntrustedAdvisoryT3/T4Advisory caveat, no blocking

8.2 Policy rules and configuration

Gate configuration is the PolicyGates:UncertaintyTier section described in §5. For DSL rules that act on uncertainty, see Policy DSL §12 — Uncertainty Gates, which owns the rule syntax.


9. Remediation Actions

Each uncertainty state has recommended remediation steps:

StateCodeRemediation
MissingSymbolResolutionU1Supply debug symbols or resolve the unresolved call-graph nodes
MissingPurlU2Provide a lockfile or an SBOM with exact package coordinates
UntrustedAdvisoryU3Corroborate the advisory from a trusted source
UnknownU4Run the initial analysis (scan)

9.1 Automated Remediation Flow

1. Policy blocks decision with U1/U2 gate
   ↓
2. Console/CLI shows remediation hint
   ↓
3. User performs the remediation (§9 table)
   ↓
4. Signals recomputes uncertainty states
   ↓
5. Risk score updates, tier may drop
   ↓
6. Policy re-evaluates, decision may proceed

9.2 Remediation Priority

When multiple uncertainty states exist, prioritize by tier:

  1. T1 states first — Block all exclusions until resolved
  2. T2 states — May proceed with warnings if T1 cleared
  3. T3/T4 states — Normal flow with caveats

Content originates from Sprint 0401; re-verified against src/ on 2026-09-26.