Risk Score Dashboard Flow

Audience: developers integrating with the RiskEngine scoring API, and security leaders evaluating StellaOps risk posture. Read the reconciliation banner first — the per-subject scoring API is implemented; the org-wide risk dashboard is roadmap.

Reconciliation status (2026-05-30): This flow was reconciled against src/Findings (RiskEngine), src/Signals (Evidence-Weighted Score), and the Console risk client in src/Web. The implemented RiskEngine is a per-subject scoring service exposed under /risk-scores/* and /exploit-maturity/* (consolidated into the Findings module — code under src/Findings/StellaOps.RiskEngine.*). The org-wide / team / application risk dashboard with letter grades, executive rollups, 30-day trend lines, and remediation recommendations described below is NOT IMPLEMENTED as a single /api/v1/risk/summary endpoint — there is no such route in StellaOps.RiskEngine.WebService. Sections that describe that dashboard are retained as roadmap / aspirational and are flagged inline. The Console “risk” surface today maps to Policy governance risk-profiles (/api/v1/governance/risk-profiles) plus aggregated-status / severity-transition endpoints, not to an org risk-grade dashboard.

Overview

The implemented Risk Score flow describes how the StellaOps RiskEngine computes a single numeric risk score in [0,1] for an opaque subject (typically a CVE or an asset id) by running a named provider over a deterministic dictionary of numeric signals. Scoring is deterministic and side-effect free, and exploit-maturity assessment consolidates EPSS / KEV / in-the-wild evidence into a maturity level.

Roadmap (NOT IMPLEMENTED): Aggregating those per-subject scores across images, teams, applications, environments, and the whole organization, with trend analysis and executive reporting, is the forward-looking goal of this flow. No aggregation, grading, or trend-history logic exists in StellaOps.RiskEngine.* today.

Business Value: Quantified risk visibility enables resource prioritization, executive reporting, and measurable security improvement over time.

Actors

ActorTypeRoleStatus
Security LeaderHumanReviews risk posture
RiskEngineServiceComputes per-subject risk scores and exploit-maturity levels (part of the Findings module)Implemented
SignalsServiceComputes the Evidence-Weighted Score (EWS) that feeds reachability/exploit normalizationImplemented
ScannerServiceProvides vulnerability findingsImplemented
PolicyServiceOwns governance risk-profiles surfaced by the Console risk clientImplemented
ConsoleSystemDisplays risk-profile lists, aggregated status, and severity transitionsImplemented
PlatformServiceCross-tenant aggregation/rollupNOT IMPLEMENTED (roadmap)

Prerequisites

Risk Scoring Subject (implemented)

RiskEngine scores a single opaque subject per request. The aggregation dimensions below are roadmap; today the only implemented “aggregation” is the optional POST /risk-scores/simulations/summary endpoint, which returns averageScore, minScore, maxScore, and the top-3 highest-scoring subjects across a batch of ScoreRequests passed in a single call (no persisted grouping by team/app/org).

DimensionScopeAggregationStatus
SubjectSingle CVE / asset idDirect provider score [0,1]Implemented
Simulation batchCaller-supplied set of subjectsaverage/min/max + top-3 movers (in-request, not persisted)Implemented
ImageSingle containerWeighted/derived scoreNOT IMPLEMENTED (roadmap)
ApplicationGroup of imagesWeighted averageNOT IMPLEMENTED (roadmap)
TeamAll team assetsSum/averageNOT IMPLEMENTED (roadmap)
Environmentprod/staging/devEnvironment-weightedNOT IMPLEMENTED (roadmap)
OrganizationAll tenantsExecutive rollupNOT IMPLEMENTED (roadmap)

Risk Scoring Providers (implemented)

Corrected: There is no single fixed-weight 6-factor model (CVSS 25% / Exploitability 20% / Reachability 20% / Exposure 15% / Criticality 10% / Time-to-Remediate 10%). That table did not match any code. Instead, RiskEngine registers a set of named providers, each with its own deterministic formula over the Signals dictionary. The provider is chosen by the provider field of the request. Registered providers (StellaOps.RiskEngine.WebService/Program.cs):

Provider nameFormula (from source)Source file
default-transformsclamp each signal to [0,1], return the average (0 if no signals)DefaultTransformsProvider.cs
cvss-kevclamp01((cvss/10) + kevBonus), kevBonus = 0.2 if KEV else 0CvssKevProvider.cs
epssEPSS probability used directly (already [0,1]); 0 if unknownEpssProvider.cs
cvss-kev-epssclamp01((cvss/10) + kevBonus + epssBonus); KEV bonus 0.2; EPSS percentile bonus +0.10/+0.05/+0.02 at p99/p90/p50; falls back to raw EPSS if no CVSS/KEVEpssProvider.cs (CvssKevEpssProvider)
vex-gateshort-circuits to 0 when HasDenial >= 1; otherwise clamp01(max of remaining signals)VexGateProvider.cs
fix-exposureclamp01(0.5·FixAvailability + 0.3·Criticality + 0.2·Exposure)FixExposureProvider.cs

fixchain provider — NOT REGISTERED: FixChainRiskProvider (signals fixchain.confidence, fixchain.status) exists in StellaOps.RiskEngine.Core and emits a risk reduction from FixChain attestation verdicts, but it is not added to the RiskScoreProviderRegistryin Program.cs, so it cannot be invoked over HTTP today.

Reachability is not a RiskEngine signal weight. Reachability normalization lives in the Signals Evidence-Weighted Score (EWS) pipeline (ReachabilityNormalizer, dimension "RCH"), which maps a ReachabilityState + confidence into a [0,1] RCH score. See the Reachability Data note in “Data Collection” for the actual state enum.

Flow Diagram

Roadmap diagram. The sequence below depicts the aspirational Platform-aggregated dashboard (Platform fan-out to Scanner/Policy, RiskEngine per-image/per-team rollup). The implemented path is simpler: a caller posts a ScoreRequest to /risk-scores/jobs (or a batch to /risk-scores/simulations[/summary]), RiskEngine runs the named provider over the supplied signals and returns a [0,1] score. There is no Platform aggregator and no cross-service fan-out in RiskEngine.

┌─────────────────────────────────────────────────────────────────────────────────┐
│              Risk Score Dashboard Flow (roadmap — NOT IMPLEMENTED)               │
└─────────────────────────────────────────────────────────────────────────────────┘

┌──────────┐  ┌─────────┐  ┌────────────┐  ┌─────────┐  ┌────────┐  ┌─────────┐
│ Security │  │ Console │  │  Platform  │  │  Risk   │  │ Scanner│  │ Policy  │
│  Leader  │  │         │  │  Service   │  │ Engine  │  │        │  │         │
└────┬─────┘  └────┬────┘  └─────┬──────┘  └────┬────┘  └───┬────┘  └────┬────┘
     │             │             │              │           │            │
     │ View risk   │             │              │           │            │
     │ dashboard   │             │              │           │            │
     │────────────>│             │              │           │            │
     │             │             │              │           │            │
     │             │ GET /risk/  │              │           │            │
     │             │ summary     │              │           │            │
     │             │────────────>│              │           │            │
     │             │             │              │           │            │
     │             │             │ Get findings │           │            │
     │             │             │──────────────────────────>            │
     │             │             │              │           │            │
     │             │             │ Vuln data    │           │            │
     │             │             │<──────────────────────────            │
     │             │             │              │           │            │
     │             │             │ Get verdicts │           │            │
     │             │             │───────────────────────────────────────>
     │             │             │              │           │            │
     │             │             │ Policy data  │           │            │
     │             │             │<───────────────────────────────────────
     │             │             │              │           │            │
     │             │             │ Compute      │           │            │
     │             │             │ scores       │           │            │
     │             │             │─────────────>│           │            │
     │             │             │              │           │            │
     │             │             │              │ Calculate │            │
     │             │             │              │ per-image │            │
     │             │             │              │───┐       │            │
     │             │             │              │   │       │            │
     │             │             │              │<──┘       │            │
     │             │             │              │           │            │
     │             │             │              │ Aggregate │            │
     │             │             │              │ by team   │            │
     │             │             │              │───┐       │            │
     │             │             │              │   │       │            │
     │             │             │              │<──┘       │            │
     │             │             │              │           │            │
     │             │             │ Risk scores  │           │            │
     │             │             │<─────────────│           │            │
     │             │             │              │           │            │
     │             │ Dashboard   │              │           │            │
     │             │ data        │              │           │            │
     │             │<────────────│              │           │            │
     │             │             │              │           │            │
     │ Render      │             │              │           │            │
     │ dashboard   │             │              │           │            │
     │<────────────│             │              │           │            │
     │             │             │              │           │            │

Implemented RiskEngine API surface

These are the real routes exposed by StellaOps.RiskEngine.WebService. The service mounts them at the service root (the public path depends on the gateway route prefix for the riskengine service); there is no /api/v1/risk/*prefix in the code.

Method & pathAuth (scope)Purpose
GET /risk-scores/providersrisk-engine:readList registered provider names (sorted)
POST /risk-scores/jobsrisk-engine:operate (audited)Enqueue + synchronously run one scoring job; returns 202 with { jobId, result }
GET /risk-scores/jobs/{jobId:guid}risk-engine:readFetch a stored RiskScoreResult (404 if not found)
POST /risk-scores/simulationsrisk-engine:operate (audited)Evaluate a batch of ScoreRequests without persisting; returns { results }
POST /risk-scores/simulations/summaryrisk-engine:operate (audited)Same as simulations plus { summary: { averageScore, minScore, maxScore, topMovers } }
GET /exploit-maturity/{cveId}risk-engine:readAssess exploit maturity (ExploitMaturityResult)
GET /exploit-maturity/{cveId}/levelrisk-engine:readJust the ExploitMaturityLevel (404 if undetermined)
GET /exploit-maturity/{cveId}/historyrisk-engine:readMaturity history (always empty today — not persisted)
POST /exploit-maturity/batchrisk-engine:operateBatch assess { cveIds: [...] }; returns { results, errors }
GET /healthz, GET /readyzanonymousLiveness / readiness probes

When the CVSS/KEV/EPSS source bundles are not configured in a live host, source-backed providers raise NotSupportedException, which exploit-maturity endpoints surface as 501 Not Implemented (title: risk_sources_unavailable). Inline signals in the ScoreRequest bypass the source lookup.

Step-by-Step

1. Score Request (implemented)

A caller submits a scoring job for a single subject. The provider must be one of the registered names; signals is a deterministic string -> double map.

POST /risk-scores/jobs HTTP/1.1
Authorization: Bearer {jwt}
Content-Type: application/json

{
  "provider": "cvss-kev-epss",
  "subject": "CVE-2024-1234",
  "signals": {
    "Cvss": 9.8,
    "Kev": 1,
    "EpssScore": 0.94,
    "EpssPercentile": 0.99
  }
}

Response (202 Accepted, Location: /risk-scores/jobs/{jobId}):

{
  "jobId": "3f2a...-...",
  "result": {
    "jobId": "3f2a...-...",
    "provider": "cvss-kev-epss",
    "subject": "CVE-2024-1234",
    "score": 1.0,
    "success": true,
    "error": null,
    "signals": { "Cvss": 9.8, "Kev": 1, "EpssScore": 0.94, "EpssPercentile": 0.99 },
    "completedAtUtc": "2026-05-30T12:00:00Z"
  }
}

Roadmap (NOT IMPLEMENTED): The original GET /api/v1/risk/summary?scope=organization&period=30d&breakdown=team,severity,trend request below has no implementation. RiskEngine has no scope, period, or breakdown query parameters and no org/team summary endpoint.

2. Data Collection (roadmap)

NOT IMPLEMENTED as an aggregator. RiskEngine does not call Scanner/Policy/ReachGraph to assemble a tenant-wide summary. The JSON shapes below are illustrative of a future Platform aggregator and do not correspond to any current RiskEngine response. The one accurate grounding note is the reachability state enum, corrected at the end of this section.

Vulnerability Data (Scanner) — illustrative roadmap shape

{
  "vulnerability_summary": {
    "total_findings": 1847,
    "by_severity": {
      "critical": 23,
      "high": 189,
      "medium": 567,
      "low": 1068
    },
    "by_status": {
      "new": 145,
      "existing": 1502,
      "fixed": 200
    },
    "unique_cves": 423,
    "affected_images": 89,
    "affected_packages": 234
  }
}

Policy Data (Policy Engine) — illustrative roadmap shape

{
  "policy_summary": {
    "total_evaluations": 892,
    "by_verdict": {
      "pass": 743,
      "warn": 98,
      "fail": 51
    },
    "compliance_rate": 0.83,
    "by_policy_set": {
      "production": {"pass": 234, "fail": 12},
      "pci-dss": {"pass": 198, "fail": 8},
      "default": {"pass": 311, "fail": 31}
    }
  }
}

Reachability Data (Signals / EWS)

Corrected enum. The state names ConfirmedReachable, StaticallyReachable, StaticallyUnreachable, ConfirmedUnreachable do not exist. The Signals EWS ReachabilityState enum (src/Signals/.../EvidenceWeightedScore/ReachabilityInput.cs) is: Unknown, NotReachable, PotentiallyReachable, StaticReachable, DynamicReachable, LiveExploitPath. (The Scanner emit lattice in src/Scanner/.../Emit/Reachability/ReachabilityLattice.cs uses a separate, coarser enum: Unknown, Conditional, Reachable, Unreachable.)

{
  "reachability_summary": {
    "total_analyzed": 1847,
    "by_state": {
      "LiveExploitPath": 12,
      "DynamicReachable": 33,
      "StaticReachable": 234,
      "PotentiallyReachable": 180,
      "Unknown": 890,
      "NotReachable": 498
    }
  }
}

3. Risk Calculation (implemented)

Corrected. RiskEngine does not compute a fixed weighted sum of six factors. Each provider applies its own deterministic formula to the request signals. The example below uses the registered cvss-kev-epss provider.

Per-Subject Risk Score (cvss-kev-epss)

score = clamp01( cvss/10 + kevBonus + epssBonus )
  kevBonus  = 0.20 if KEV else 0.00
  epssBonus = 0.10 if EPSS percentile >= 0.99
            = 0.05 if >= 0.90
            = 0.02 if >= 0.50
            else 0.00
  (if CVSS<=0 and not KEV, falls back to raw EPSS score + epssBonus)

For CVE-2024-1234 with Cvss=9.8, Kev=1, EpssPercentile=0.99:
  base   = 9.8/10        = 0.98
  kev    = 0.20
  epss   = 0.10
  raw    = 1.28 -> clamped to 1.0

Subject Risk Score: 1.0

Exploit-Maturity Assessment (/exploit-maturity/{cveId})

ExploitMaturityService consolidates EPSS + KEV + in-the-wild signals.
Final level = max(level across present signals); confidence = mean confidence
of signals at that max level.

EPSS -> level mapping (EpssThresholds):
  score >= 0.80 -> Weaponized
  score >= 0.40 -> Active
  score >= 0.10 -> ProofOfConcept
  score >= 0.01 -> Theoretical
  else          -> Unknown
KEV present     -> Weaponized (confidence 0.95)

Roadmap (NOT IMPLEMENTED): Per-image rollup (max(finding_risks) + 0.1·avg(...)) and per-team weighted aggregation are not implemented. The only multi-subject computation that exists is the in-request /risk-scores/simulations/summary aggregate (averageScore, minScore, maxScore, top-3 topMovers).

4. Dashboard Response (roadmap — NOT IMPLEMENTED)

NOT IMPLEMENTED. No endpoint returns the risk_summary org/team/grade/trend/ recommendation shape below. The implemented responses are: the per-job RiskScoreResult (Step 1), the { results } / { summary, results } simulation payloads, and the ExploitMaturityResult. The block below is retained as a forward-looking target only.

{
  "risk_summary": {
    "organization": {
      "score": 0.58,
      "grade": "C",
      "trend": "-0.05",
      "trend_direction": "improving"
    },
    "breakdown": {
      "by_team": [
        {
          "team": "Platform Engineering",
          "score": 0.45,
          "grade": "B",
          "image_count": 12,
          "critical_findings": 2
        },
        {
          "team": "Product Development",
          "score": 0.72,
          "grade": "D",
          "image_count": 34,
          "critical_findings": 18
        }
      ],
      "by_severity": {
        "critical": {"count": 23, "risk_contribution": 0.35},
        "high": {"count": 189, "risk_contribution": 0.40},
        "medium": {"count": 567, "risk_contribution": 0.20},
        "low": {"count": 1068, "risk_contribution": 0.05}
      },
      "by_environment": {
        "production": {"score": 0.62, "image_count": 45},
        "staging": {"score": 0.55, "image_count": 23},
        "development": {"score": 0.48, "image_count": 67}
      }
    },
    "trends": {
      "period": "30d",
      "scores": [
        {"date": "2024-11-29", "score": 0.63},
        {"date": "2024-12-06", "score": 0.61},
        {"date": "2024-12-13", "score": 0.59},
        {"date": "2024-12-20", "score": 0.57},
        {"date": "2024-12-29", "score": 0.58}
      ],
      "change_30d": -0.05,
      "change_7d": +0.01
    },
    "top_risks": [
      {
        "cve": "CVE-2024-1234",
        "risk_score": 0.92,
        "affected_images": 8,
        "teams": ["Product Development"],
        "remediation": "Upgrade lodash to 4.17.21"
      }
    ],
    "recommendations": [
      {
        "priority": 1,
        "action": "Remediate CVE-2024-1234 in Product Development",
        "impact": "Reduces org risk by 0.08 points"
      }
    ]
  }
}

Implemented response shapes (for reference)

RiskScoreResult (StellaOps.RiskEngine.Core/Contracts/RiskScoreResult.cs):

{
  "jobId": "<guid>",
  "provider": "cvss-kev",
  "subject": "CVE-2024-1234",
  "score": 0.84,            // double in [0,1]
  "success": true,
  "error": null,
  "signals": { "Cvss": 8.4, "Kev": 0 },
  "completedAtUtc": "2026-05-30T12:00:00Z"
}

ExploitMaturityResult (StellaOps.RiskEngine.Core/Contracts/ExploitMaturityModels.cs):

{
  "cveId": "CVE-2024-1234",
  "level": "Weaponized",     // Unknown|Theoretical|ProofOfConcept|Active|Weaponized
  "confidence": 0.95,
  "signals": [ { "source": "Kev", "level": "Weaponized", "confidence": 0.95, "evidence": "...", "observedAt": "..." } ],
  "rationale": "Maturity level Weaponized determined by Kev (1 signal(s))",
  "assessedAt": "2026-05-30T12:00:00Z"
}

5. Dashboard Rendering (roadmap — NOT IMPLEMENTED)

NOT IMPLEMENTED. There is no org risk-grade dashboard view in the Console. The Console risk client (src/Web/.../core/api/risk-http.client.ts) renders Policy governance risk-profiles (GET /api/v1/governance/risk-profiles) plus an aggregated-status panel (/aggregated-status) and recent severity transitions (/transitions/recent). The ASCII mock-up below is an aspirational target.

┌─────────────────────────────────────────────────────────────────┐
│ Organization Risk Dashboard                                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Overall Risk Score: 58/100 (Grade: C)  ↓ 5% from last month    │
│  ████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
│                                                                  │
│  ┌─ Risk by Team ──────────────────────────────────────────────┐│
│  │ Platform Engineering  ████████░░░░░░░ 45 (B) ↓3%           ││
│  │ Product Development   ████████████████░░ 72 (D) ↑2%        ││
│  │ Security              ████░░░░░░░░░░░░ 28 (A) ↓1%          ││
│  └─────────────────────────────────────────────────────────────┘│
│                                                                  │
│  ┌─ 30-Day Trend ──────────────────────────────────────────────┐│
│  │       ╭───────────────────────────────────────────╮         ││
│  │   63  │    ╲                                       │         ││
│  │   61  │      ╲                                     │         ││
│  │   59  │        ╲                                   │         ││
│  │   57  │          ╲___________/                     │         ││
│  │       ╰─────────────────────────────────────────────╯       ││
│  │       Nov 29     Dec 13      Dec 22      Dec 29              ││
│  └─────────────────────────────────────────────────────────────┘│
│                                                                  │
│  ┌─ Top Actions ───────────────────────────────────────────────┐│
│  │ 1. Remediate CVE-2024-1234 (-0.08 risk)                    ││
│  │ 2. Update base images in Product team (-0.05 risk)         ││
│  │ 3. Enable runtime monitoring for api-service (-0.03 risk)  ││
│  └─────────────────────────────────────────────────────────────┘│
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Risk Grading Scale (roadmap — NOT IMPLEMENTED)

NOT IMPLEMENTED. RiskEngine returns a double score in [0,1]; it does not map scores to a 0-100 scale or to A-F letter grades. The exploit-maturity surface uses an ordinal level taxonomy (Unknown -> Theoretical -> ProofOfConcept -> Active -> Weaponized), not a grade. The table below is an aspirational presentation layer for a future dashboard.

Score RangeGradeDescription
0-20AExcellent - minimal risk
21-40BGood - well managed
41-60CFair - needs attention
61-80DPoor - significant risk
81-100FCritical - immediate action

Data Contracts

RiskEngine request / result (implemented)

ScoreRequest and RiskScoreResult (StellaOps.RiskEngine.Core/Contracts/):

public sealed record ScoreRequest(
    string Provider,
    string Subject,
    IReadOnlyDictionary<string, double> Signals);

public sealed record RiskScoreResult(
    Guid JobId,
    string Provider,
    string Subject,
    double Score,            // [0,1]
    bool Success,
    string? Error,
    IReadOnlyDictionary<string, double> Signals,
    DateTimeOffset CompletedAtUtc);

/risk-scores/simulations accepts a JSON array of ScoreRequest; the /summary variant adds { averageScore, minScore, maxScore, topMovers[] }.

Risk Summary Request Schema (roadmap — NOT IMPLEMENTED)

NOT IMPLEMENTED. The RiskSummaryRequest / RiskSummaryResponse interfaces below have no backing endpoint. scope, period, breakdown, grade, trend_direction, and the metadata.model_version field do not exist in the RiskEngine code. Retained as a roadmap contract sketch.

interface RiskSummaryRequest {
  scope: 'image' | 'application' | 'team' | 'environment' | 'organization';
  scope_id?: string;  // Required for image/application/team
  period?: string;    // ISO-8601 duration, default 30d
  breakdown?: Array<'team' | 'severity' | 'environment' | 'trend'>;
  compare_to?: string;  // Previous period for comparison
}

Risk Summary Response Schema

interface RiskSummaryResponse {
  risk_summary: {
    scope: string;
    score: number;  // 0-100
    grade: 'A' | 'B' | 'C' | 'D' | 'F';
    trend: number;
    trend_direction: 'improving' | 'stable' | 'degrading';
    breakdown?: {
      by_team?: TeamRisk[];
      by_severity?: SeverityBreakdown;
      by_environment?: EnvironmentRisk[];
    };
    trends?: {
      period: string;
      scores: Array<{date: string; score: number}>;
      change_30d: number;
      change_7d: number;
    };
    top_risks?: TopRisk[];
    recommendations?: Recommendation[];
  };
  metadata: {
    calculated_at: string;
    data_freshness: string;
    model_version: string;
  };
}

Risk Model Configuration (roadmap — NOT IMPLEMENTED)

NOT IMPLEMENTED. RiskEngine has no risk_model YAML config, no global factor weights, and no aggregation policy. Providers are hard-coded C# strategies registered in Program.cs; the only runtime config is the EPSS/CVSS/KEV source wiring (RiskEngine:Sources:Epss:BundlePath / DirectoryPath / SnapshotPath, Storage:Driver, and the Postgres connection string). The configurable FixChainRiskOptions (e.g. FixedReduction=0.90, PartialReduction=0.50, MinConfidenceThreshold=0.60) belong to the unregistered fixchain provider.

The reachability mapping keys below were also wrong (ConfirmedReachable, RuntimeObserved, StaticallyReachable, StaticallyUnreachable, ConfirmedUnreachable). The real Signals EWS states are Unknown / NotReachable / PotentiallyReachable / StaticReachable / DynamicReachable / LiveExploitPath, and their [0,1] mapping is computed by ReachabilityNormalizer (base score + confidence modifier + analysis bonus − hop penalty), not a static lookup table. The YAML below is retained as an aspirational design.

risk_model:
  version: "1.0.0"
  factors:
    cvss_score:
      weight: 0.25
      normalization: linear  # score/10
    exploitability:
      weight: 0.20
      components:
        kev: 0.50      # In KEV = 1.0
        epss: 0.30     # EPSS percentile
        poc: 0.20      # Public PoC exists
    reachability:
      weight: 0.20
      mapping:
        LiveExploitPath: 1.0
        DynamicReachable: 0.9
        StaticReachable: 0.7
        Unknown: 0.5
        PotentiallyReachable: 0.35
        NotReachable: 0.1
    exposure:
      weight: 0.15
      mapping:
        internet_facing: 1.0
        internal_network: 0.6
        isolated: 0.2
    asset_criticality:
      weight: 0.10
      source: business_metadata
    remediation_age:
      weight: 0.10
      decay_days: 90  # Linear decay over 90 days

  aggregation:
    image: max_plus_average
    team: weighted_average
    organization: weighted_sum

Error Handling

Implemented

ConditionBehavior (from source)
Provider not registeredRiskScoreResult returned with success=false, error="Provider not registered", score=0 (job and simulation paths)
Provider throws during scoringException captured into RiskScoreResult.error, success=false, score=0
CVSS/KEV/EPSS source unavailable in a live hostExploit-maturity endpoints return 501 Not Implemented (title: risk_sources_unavailable); inline signals bypass the source
Invalid CVE id / empty batch400 Bad Request (ArgumentException / “cve_ids_required”)
Maturity level undeterminedGET /exploit-maturity/{cveId}/level returns 404
inmemory storage outside TestingService throws on startup (postgres required in live hosts)

Roadmap (NOT IMPLEMENTED)

ErrorRecovery
Data staleShow warning, use cached data
Partial dataCalculate with available, note gaps
Model errorFall back to simplified model
Aggregation timeoutReturn partial results

Observability

Metrics (implemented)

Corrected. risk_score_current, risk_calculation_duration_ms, risk_grade_distribution, and risk_trend_change_30d do not exist. The actual metrics emitted by the RiskEngine code are below.

MetricTypeMeterLabelsSource
stellaops_exploit_maturity_assessment_duration_msHistogramStellaOps.RiskEnginecveExploitMaturityService.cs
stellaops_exploit_maturity_assessments_totalCounterStellaOps.RiskEnginelevelExploitMaturityService.cs
risk_fixchain_lookups_totalCounterStellaOps.RiskEngine.FixChainFixChainRiskMetrics.cs (provider unregistered)
risk_fixchain_hits_total / risk_fixchain_misses_totalCounterStellaOps.RiskEngine.FixChainverdictFixChainRiskMetrics.cs
risk_fixchain_cache_hits_totalCounterStellaOps.RiskEngine.FixChainFixChainRiskMetrics.cs
risk_fixchain_lookup_duration_secondsHistogramStellaOps.RiskEngine.FixChainFixChainRiskMetrics.cs
risk_fixchain_adjustments_totalCounterStellaOps.RiskEngine.FixChainverdict, confidence_tierFixChainRiskMetrics.cs
risk_fixchain_reduction_percentHistogramStellaOps.RiskEngine.FixChainFixChainRiskMetrics.cs
risk_fixchain_errors_totalCounterStellaOps.RiskEngine.FixChainerror_typeFixChainRiskMetrics.cs

The Findings Ledger module separately emits EWS / scoring metrics (e.g. Ews*, Scoring* families in StellaOps.Findings.Ledger/Observability/LedgerMetrics.cs).

Audit actions (implemented)

The /risk-scores/jobs, /risk-scores/simulations, and /risk-scores/simulations/summary endpoints are audited under module riskengine with actions create_score_job and create_simulation (StellaOps.Audit.Emission/AuditActions.cs -> RiskEngine).

Key Log Events

NOT IMPLEMENTED as named events. risk.calculated, risk.trend_alert, and risk.threshold_exceeded are not emitted. ExploitMaturityService logs an unstructured debug line per assessment (“Assessed exploit maturity for {CveId}: {Level} …”) and an error on failure. The structured-event table below is a roadmap target.

EventLevelFieldsStatus
risk.calculatedINFOscope, score, gradeNOT IMPLEMENTED (roadmap)
risk.trend_alertWARNscope, change, directionNOT IMPLEMENTED (roadmap)
risk.threshold_exceededWARNscope, threshold, scoreNOT IMPLEMENTED (roadmap)