Dashboard Data Flow

Overview

The Dashboard Data Flow describes how the Stella Ops Console (Web UI) assembles a security-posture overview and presents it to users. The dashboard provides visibility into vulnerability counts, advisory-feed health, environment topology, and personal activity across the tenant’s managed assets.

Reconciliation note (verified against src/Web on 2026-05-30). The dashboard is composed client-side, not by a single server-side aggregation endpoint. The Angular DashboardV3Component (src/Web/StellaOps.Web/src/app/features/dashboard-v3/dashboard-v3.component.ts) fans out to several independent backend APIs from the browser and merges the results into a persona-aware “Mission Board”. There is no GET /api/v1/dashboard endpoint and no Platform-side fan-out (/internal/stats, /internal/verdicts/summary, etc.) backing the live dashboard — those were never implemented. A separate GET /api/v1/dashboard/summary Pack-adapter endpoint does exist on the Platform service, but it returns hardcoded demo data (IsDemo: true) and the Console does not consume it (see Pack-Adapter Dashboard Summary).

Business Value: Operators gain immediate visibility into their security posture without querying multiple systems, with the layout and lenses tailored to the signed-in user’s role (persona).

Actors

ActorTypeRole
UserHumanViews the persona-aware Mission Board, follows links to deep-dive surfaces
Console (Web UI)SystemComposes the dashboard client-side; calls multiple APIs in parallel and merges results
GatewayServiceRoutes, authenticates (JWT), and stamps identity headers/envelopes
Findings SecurityServiceProvides the tenant/scoped production risk aggregate (GET /api/findings/v1/risk/aggregated-status)
Concelier / Excititor (Advisory Sources)ServiceProvides advisory & VEX source status (GET /api/v1/advisory-sources/status)
Platform ContextServiceProvides regions, environments, and scope preferences (/api/v2/context/*)
Notify / NotifierServiceProvides the personal activity feed (developer persona)

Prerequisites

Flow Diagram

┌─────────────────────────────────────────────────────────────────────────────────┐
│ Dashboard Data Flow (client-side composition) │
└─────────────────────────────────────────────────────────────────────────────────┘

┌────┐ ┌──────────────────────┐ ┌─────────┐ ┌──────────────────────────┐
│User│ │ Console (DashboardV3) │ │ Gateway │ │ Backend APIs │
└─┬──┘ └──────────┬───────────┘ └────┬────┘ └────────────┬─────────────┘
 │ Open Dashboard │ │ │
 │───────────────────>│ │ │
 │ │ Resolve active persona │ │
 │ │ from auth.scopes() │ │
 │ │───────┐ │ │
 │ │<──────┘ │ │
 │ │ │ │
 │ │ (parallel client calls; Bearer JWT + X-StellaOps-TenantId)
 │ │ │ │
 │ │ GET /api/v2/context/regions ─────────────────> │ Platform Context
 │ │ GET /api/v2/context/preferences ─────────────> │ Platform Context
 │ │ GET /api/v2/context/environments ────────────> │ Platform Context
 │ │ GET /api/findings/v1/risk/aggregated-status ─> │ Findings Security
 │ │ GET /api/v1/advisory-sources/status ─────────> │ Advisory Sources
 │ │ GET (notifications refresh) ─────────────────> │ Notify
 │ │ │ │
 │ │<──── responses (each lens loads independently) ─│
 │ │ │ │
 │ Render lens grid │ Merge into signals; │ │
 │ (persona-filtered) │ compute derived counts │ │
 │<───────────────────│ │ │

Step-by-Step

1. User Opens Dashboard

2. Authentication & Persona Resolution

3. Client-Side Data Fan-Out

On init (ngOnInit), the component issues parallel calls directly from the browser. Each lens loads and degrades independently — there is no single aggregate request.

DataSource serviceEndpoint (browser → gateway)Client
RegionsPlatform ContextGET /api/v2/context/regionsPlatformContextStore
Scope preferencesPlatform ContextGET /api/v2/context/preferences (PUT to persist)PlatformContextStore
EnvironmentsPlatform ContextGET /api/v2/context/environments?regions=…PlatformContextStore
Vulnerability postureFindings SecurityGET /api/findings/v1/risk/aggregated-statusRiskHttpClient.getAggregatedStatus()
Advisory/VEX feed statusAdvisory SourcesGET /api/v1/advisory-sources/statusSourceManagementApi.getStatus()
Personal activity feedNotifynotifications store refresh (developer persona)NotificationsStore

Notes grounded in source:

4. Lens Composition & Derived Values

The component holds the responses in signals and computes derived values for the persona-filtered lens grid (visibleLensCards() → selectLensCards(registry, persona, scopes)). A lens that loads its own data (promotion readiness, releases this week, release blockers, evidence coverage, assurance posture, your builds) carries its owner endpoint’s read scopes in requiredScopes and renders, in any view, only for a session holding one of them; the admin scope does not stand in for that read (src/Web/StellaOps.Web/src/app/features/dashboard-v3/lens-card.contract.ts):

When the tenant has no environments, the dashboard shows an empty-install “Bootstrap Release Control” setup guide instead of populated risk lenses.

5. Response Shapes (real contracts)

Vulnerability posture — GET /api/findings/v1/risk/aggregated-status (TypeScript models AggregatedRiskStatus / FindingsPostureStats in src/Web/StellaOps.Web/src/app/core/api/risk.models.ts):

interface FindingsPostureStats {
 total: number;
 bySeverity: Record<'critical' | 'high' | 'medium' | 'low' | 'unknown', number>;
 criticalOpen: number;
 computedAt: string; // aggregate generation time; nullable `asOf` carries source currency
 traceId: string;
}

Advisory/VEX feed status — GET /api/v1/advisory-sources/status (model SourceStatusResponse in source-management.api.ts):

interface SourceStatusResponse {
 sources: Array<{
 sourceId: string;
 enabled: boolean;
 lastCheck?: { isHealthy: boolean; status: string; checkedAt: string; /* … */ } | null;
 syncSupported?: boolean;
 syncState?: string;
 readyForSync?: boolean;
 }>;
}

Platform context — /api/v2/context/{regions,environments,preferences} (models in platform-context.store.ts): PlatformContextRegion[], PlatformContextEnvironment[], and PlatformContextPreferences.

Data Contracts

Request Headers (every lens call)

HeaderRequiredDescription
AuthorizationYesBearer JWT (attached by the auth interceptor)
X-StellaOps-TenantIdYesTenant identifier (constant StellaOpsHeaders.Tenant)
X-StellaOps-TraceIdNoObservability trace id (stamped by VulnerabilityHttpClient)
X-StellaOps-RequestIdNoPer-request id (stamped by VulnerabilityHttpClient)
Content-TypeOn POST/PUTapplication/json

Pack-Adapter Dashboard Summary (NOT the live source)

The Platform service exposes GET /api/v1/dashboard/summary (src/Platform/StellaOps.Platform.WebService/Endpoints/PackAdapterEndpoints.cs) guarded by the HealthRead policy. This is a Pack v2 demo projection: it returns a fixed snapshot wrapped with IsDemo: true and hardcoded values (CVE-2026-1234, us-prod, etc.). The live Console dashboard does not call it. Its DTO (for reference only) is:

record DashboardSummaryDto(DataConfidenceBadgeDto DataConfidence,
 int EnvironmentsWithCriticalReachable,
 int TotalCriticalReachable,
 decimal SbomCoveragePercent,
 decimal VexCoveragePercent,
 int BlockedApprovals,
 int ExceptionsExpiringSoon,
 IReadOnlyList<EnvironmentRiskSnapshotDto> EnvironmentRisk,
 IReadOnlyList<DashboardDriverDto> TopDrivers);

NOT IMPLEMENTED. GET /api/v1/dashboard, summary, total_images, images_scanned_24h, critical_vulns, policy_violations, trends, vuln_trend_7d, scan_volume_7d, top_vulns and policy_status do not exist in src/. The live dashboard has no trend series, no top_vulns aggregation, and no policy_status block; those fields were never built. Treat the old contract as roadmap/aspirational, not current behavior.

Error Handling

Because the dashboard is composed of independent client calls, failures are isolated per lens rather than failing the whole page.

ConditionObserved behavior (per source)
Invalid/expired JWTAuth interceptor / guard routes to the Authority login flow
getStats() errors (any status)Loader catches → vulnStats = null; risk/vuln lenses show an empty/loading state
Advisory source status errorsLoader catches → empty source list; feed lens + health dot reflect “needs attention”
Context regions/preferences errorStore continues with empty regions/defaults so the app stays usable
Context environments errorStore records error; environment lenses show empty state
No environments for tenantDashboard renders the “Bootstrap Release Control” setup guide

The previous HTTP-status recovery table (401 → login, 404 → tenant selection, 504 → partial dashboard with stale-data indicator, 429 → backoff) assumed a single aggregation endpoint and a stale-data indicator that do not exist. It has been replaced with the per-lens behavior above.

Observability

NOT VERIFIED IN SOURCE. The metrics, trace spans, and structured log events below (dashboard_request_total, dashboard_latency_seconds, dashboard_module_latency_seconds, a dashboard-request server span with per-module child spans, and dashboard.* log events) describe a server-side aggregation service that was never implemented. No such telemetry exists for the client-composed dashboard. The closest real signal is the client-side debug log emitted by VulnerabilityHttpClient.logRequest() (console.debug('[VulnHttpClient]', method, path, statusCode, durationMs)), which records each vuln API call. Keep this section as a forward-looking target only.

(Roadmap) Metrics

MetricTypeLabels
dashboard_request_totalCountertenant, status
dashboard_latency_secondsHistogramtenant
dashboard_module_latency_secondsHistogrammodule