Risk Score Dashboard Flow

Audience: developers designing the future Findings-owned risk read surface, and security leaders evaluating StellaOps risk posture. Read the banner below first — the source surface this flow describes has been deleted.

Retirement EXECUTED (owner ruling Q-2, 2026-08-25; SPRINT_20260722_010 FND-10, 2026-08-27). /risk-scores/* and /exploit-maturity/* are gone, together with StellaOps.RiskEngine.WebService, .Worker and .Application. The route and provider detail below is a historical record of the deleted surface, kept because the future Findings-owned replacement is designed from it. RiskEngine.Core, its result store and the riskengine schema are KEPT and folded into the Findings family (stellaops_findings); any future read API is designed on /api/findings/v1/* for a real consumer. See ../modules/findings/architecture.md.

Reconciliation status (2026-05-30, superseded for RiskEngine by the banner above): This flow was reconciled against src/Findings (RiskEngine), src/Signals (Evidence-Weighted Score), and the Console risk client in src/Web. At that date the RiskEngine was a per-subject scoring service exposed under /risk-scores/* and /exploit-maturity/* (code under src/Findings/StellaOps.RiskEngine.*). That host was deleted on 2026-08-27 and no host serves those routes now. Sections marked “deleted surface” below record its behavior. 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 — the deleted StellaOps.RiskEngine.WebService had no such route, and no host has one now. 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 Risk Score flow describes how the StellaOps RiskEngine computed 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. The providers and ExploitMaturityService remain in the kept StellaOps.RiskEngine.Core library, but no deployable registers or calls them.

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—
RiskEngineLibrary (host deleted)Computed per-subject risk scores and exploit-maturity levels; the providers are kept in StellaOps.RiskEngine.Core (part of the Findings module)Host deleted 2026-08-27; library has no deployable caller
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 (deleted surface)

RiskEngine scored a single opaque subject per request. The aggregation dimensions below are roadmap; the only “aggregation” the deleted host offered was the optional POST /risk-scores/simulations/summary endpoint, which returned 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]Deleted surface (host deleted 2026-08-27)
Simulation batchCaller-supplied set of subjectsaverage/min/max + top-3 movers (in-request, not persisted)Deleted surface (host deleted 2026-08-27)
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 (kept library)

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, StellaOps.RiskEngine.Core defines 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. The deleted host registered the six below in StellaOps.RiskEngine.WebService/Program.cs; no deployable registers them now:

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. The deleted host did not add it to the RiskScoreProviderRegistryin its Program.cs, and no deployable registers any provider now.

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 deleted host’s path was simpler: a caller posted a ScoreRequest to /risk-scores/jobs (or a batch to /risk-scores/simulations[/summary]), RiskEngine ran the named provider over the supplied signals and returned a [0,1] score. No host serves that path now. 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   │             │              │           │            │
     │<────────────│             │              │           │            │
     │             │             │              │           │            │

Deleted RiskEngine API surface (historical record)

These routes were exposed by StellaOps.RiskEngine.WebService until the host was deleted on 2026-08-27. No host serves them now; a request returns 404. The host mounted them at the service root, with no /api/v1/risk/* prefix. The served risk-scoring surfaces are listed in the Risk Scoring Contract.

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 — 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, source-backed providers raise NotSupportedException; the deleted exploit-maturity endpoints surfaced it as 501 Not Implemented (title: risk_sources_unavailable). Inline signals in the ScoreRequest bypass the source lookup.

Step-by-Step

1. Score Request (deleted surface)

A caller submitted a scoring job for a single subject. The provider had to 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 (kept library)

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 cvss-kev-epss provider, which the deleted host registered.

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 (ExploitMaturityService; the deleted /exploit-maturity/{cveId} route)

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 the deleted host offered was 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 deleted host’s responses were: 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"
      }
    ]
  }
}

Response shapes of the deleted surface (types kept in StellaOps.RiskEngine.Core)

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 (kept library types)

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

The deleted /risk-scores/simulations route accepted a JSON array of ScoreRequest; the /summary variant added { 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. The deleted host registered them in its Program.cs and read only the EPSS/CVSS/KEV source wiring (RiskEngine:Sources:Epss:BundlePath / DirectoryPath / SnapshotPath, Storage:Driver, and the Postgres connection string); no deployable reads those keys now. 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

Deleted host behavior (historical record)

The rows below describe the host deleted on 2026-08-27. No host produces these responses now.

ConditionBehavior (from the deleted 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 returned 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 returned 404
inmemory storage outside TestingService threw 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 (defined in the kept library)

Corrected. risk_score_current, risk_calculation_duration_ms, risk_grade_distribution, and risk_trend_change_30d do not exist. The metrics the RiskEngine code defines are below. No deployable runs that code, so no host emits them now.

MetricTypeMeterLabelsSource
stellaops_exploit_maturity_assessment_duration_msHistogramStellaOps.RiskEnginecveExploitMaturityService.cs
stellaops_exploit_maturity_assessments_totalCounterStellaOps.RiskEnginelevelExploitMaturityService.cs
risk_fixchain_lookups_totalCounterStellaOps.RiskEngine.FixChain—FixChainRiskMetrics.cs (provider unregistered)
risk_fixchain_hits_total / risk_fixchain_misses_totalCounterStellaOps.RiskEngine.FixChainverdictFixChainRiskMetrics.cs
risk_fixchain_cache_hits_totalCounterStellaOps.RiskEngine.FixChain—FixChainRiskMetrics.cs
risk_fixchain_lookup_duration_secondsHistogramStellaOps.RiskEngine.FixChain—FixChainRiskMetrics.cs
risk_fixchain_adjustments_totalCounterStellaOps.RiskEngine.FixChainverdict, confidence_tierFixChainRiskMetrics.cs
risk_fixchain_reduction_percentHistogramStellaOps.RiskEngine.FixChain—FixChainRiskMetrics.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 (deleted surface)

The deleted /risk-scores/jobs, /risk-scores/simulations, and /risk-scores/simulations/summary endpoints were audited under module riskengine with actions create_score_job and create_simulation. Those constants (AuditActions.RiskEngine) and the AuditModules.RiskEngine module name were removed from StellaOps.Audit.Emission on 2026-10-02. The Timeline audit query still accepts riskengine as a module filter, so rows written before the deletion stay queryable.

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)