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/Webon 2026-05-30). The dashboard is composed client-side, not by a single server-side aggregation endpoint. The AngularDashboardV3Component(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 noGET /api/v1/dashboardendpoint and no Platform-side fan-out (/internal/stats,/internal/verdicts/summary, etc.) backing the live dashboard — those were never implemented. A separateGET /api/v1/dashboard/summaryPack-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
| Actor | Type | Role |
|---|---|---|
| User | Human | Views the persona-aware Mission Board, follows links to deep-dive surfaces |
| Console (Web UI) | System | Composes the dashboard client-side; calls multiple APIs in parallel and merges results |
| Gateway | Service | Routes, authenticates (JWT), and stamps identity headers/envelopes |
| Findings Security | Service | Provides the tenant/scoped production risk aggregate (GET /api/findings/v1/risk/aggregated-status) |
| Concelier / Excititor (Advisory Sources) | Service | Provides advisory & VEX source status (GET /api/v1/advisory-sources/status) |
| Platform Context | Service | Provides regions, environments, and scope preferences (/api/v2/context/*) |
| Notify / Notifier | Service | Provides the personal activity feed (developer persona) |
Prerequisites
- User authenticated via Authority (OAuth/OIDC); the auth interceptor attaches
Authorization: Bearer {jwt}. - Tenant context established. The Console sends the tenant via the
X-StellaOps-TenantIdheader (constantStellaOpsHeaders.Tenantinsrc/Web/StellaOps.Web/src/app/core/http/stella-ops-headers.ts). The legacyX-Tenant-Idform is not used. - The user’s issued scopes determine which dashboard views (personas) are offered and which lenses render. A session that matches no professional profile, or more than one, opens on the neutral “Overview” view.
- Backends are reachable for live data; otherwise lenses degrade gracefully (loaders, empty states, honest defaults) rather than showing fabricated values.
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
- The user navigates to the Console home route, which renders
DashboardV3Component(“Mission Board”). - The Angular SPA is served as static assets through the gateway.
2. Authentication & Persona Resolution
- Auth state is managed by
AuthService/AuthSessionStore; an HTTP interceptor attaches theAuthorization: Bearer {jwt}header. If the session is invalid, the app routes to the Authority login flow. - The dashboard derives the active view (persona) from the session’s issued scopes via
DashboardPersonaService(src/Web/StellaOps.Web/src/app/core/services/dashboard-persona.service.ts). A view selects presentation only; switching it never changes the session’s scopes or route access. Each professional profile’s view qualifies only when the issued scopes satisfy that profile’s explicit rule inPROFILE_HOME_VIEW_ACCESS(src/Web/StellaOps.Web/src/app/core/auth/route-access.ts);adminandui.adminnever substitute for a missing scope: it(“Compliance”, Executive Assurance Viewer) —release:readandpolicy:auditdevops(“Operations”, Release Operations Steward) —release:readandorch:operatesecurity(“Security”, Security Risk Manager) —findings:readandvuln:investigatedeveloper(“My work”, Service Release Contributor) —release:read,release:write,findings:readandvuln:viewoverview— the neutral view; it always qualifies- The default view is the single qualifying profile view. A session for which no profile view qualifies (for example
release:readalone) or several qualify (for example an administrator) opens on Overview; the result does not depend on scope order. - An explicit choice in the View selector is remembered per account and tenant (
localStoragekeystellaops.dashboard.view:<tenant>:<account>) and applies only while the current session still qualifies for that view; a view the session does not qualify for is never stored, and a choice made by another account or in another tenant never applies. Until the session identifies its account and tenant, only the default view is offered. The pre-change browser-wide keystellaops.dashboard.personais removed on load and never read. The rules and their tests are described in the Web UI information architecture. - The active view drives which lenses render and their grid spans (step 4).
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.
| Data | Source service | Endpoint (browser → gateway) | Client |
|---|---|---|---|
| Regions | Platform Context | GET /api/v2/context/regions | PlatformContextStore |
| Scope preferences | Platform Context | GET /api/v2/context/preferences (PUT to persist) | PlatformContextStore |
| Environments | Platform Context | GET /api/v2/context/environments?regions=… | PlatformContextStore |
| Vulnerability posture | Findings Security | GET /api/findings/v1/risk/aggregated-status | RiskHttpClient.getAggregatedStatus() |
| Advisory/VEX feed status | Advisory Sources | GET /api/v1/advisory-sources/status | SourceManagementApi.getStatus() |
| Personal activity feed | Notify | notifications store refresh (developer persona) | NotificationsStore |
Notes grounded in source:
RiskHttpClient.getAggregatedStatus()calls the Findings.Security aggregate under the exactfindings:readpolicy. Region/environment selections are forwarded as normalized scope parameters; tenant identity remains bound to the authenticated tenant and tenant header.- Scanner’s
/api/v1/vulnerabilities/statusbelongs only to its explicitly enabled non-production fixture. Production dashboard and Security Posture pages do not call it and do not allowlist its typedscanner.vulnerabilities.not_availableresponse. - On any error each loader clears the affected value and surfaces a partial/unavailable state with recovery instead of fabricating zeroes.
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):
- Risk funnel / vulnerability summary — from the Findings aggregate:
total,bySeverity, andcriticalOpen(critical findings not dispositioned asmitigated). - Feed status — derived from
SourceStatusResponse.sources: total, enabled, healthy (lastCheck.isHealthy === true), failed counts. - Environment health / SBOM health / env-risk table — from the context store’s environments, mapped to
EnvironmentCards. Honest defaults apply: until a real backend reports per-environment scan facts, each card defaults todeployStatus: 'unknown',sbomFreshness: 'missing', and zeroed counts (resolveStatusSeed). There is no fabricated fallback data. - Role queue (profile views) — built by
buildRoleQueue(src/Web/StellaOps.Web/src/app/features/dashboard-v3/role-queue.model.ts) from the reads above and makes no read of its own: Operations from the pending-approvals read, the failed cells of the header-scoped deployments page and the drift summary; Security from the approvals’ reachable-risk gate results and the critical findings of the Findings aggregate; Compliance from the chain-completeness projection (gaps first, then stages not verified) and the Policy active-exception count; My work from the owned-release currency probe. Each group carries its owner, scope and observation time; a denied read names its scope, a failed read is unavailable with Retry. The board verdict of a profile view states its queue. - Open items (Overview) — computed from pending approvals, critical findings, agent drift, tampered evidence and owned-release updates, each stating its scope. Tenant setup (the activation checklist) is shown only to an administrator session.
- Developer activity — newest notifications from
NotificationsStore. - Platform health bar — service health (context initialized & no error), feed health, and a “Security” dot driven by whether vuln stats loaded. The Evidence and DLQ dots are currently static placeholders.
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)
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer JWT (attached by the auth interceptor) |
X-StellaOps-TenantId | Yes | Tenant identifier (constant StellaOpsHeaders.Tenant) |
X-StellaOps-TraceId | No | Observability trace id (stamped by VulnerabilityHttpClient) |
X-StellaOps-RequestId | No | Per-request id (stamped by VulnerabilityHttpClient) |
Content-Type | On POST/PUT | application/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_vulnsandpolicy_statusdo not exist insrc/. The live dashboard has no trend series, notop_vulnsaggregation, and nopolicy_statusblock; 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.
| Condition | Observed behavior (per source) |
|---|---|
| Invalid/expired JWT | Auth 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 errors | Loader catches → empty source list; feed lens + health dot reflect “needs attention” |
| Context regions/preferences error | Store continues with empty regions/defaults so the app stays usable |
| Context environments error | Store records error; environment lenses show empty state |
| No environments for tenant | Dashboard 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, adashboard-requestserver span with per-module child spans, anddashboard.*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 byVulnerabilityHttpClient.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
| Metric | Type | Labels |
|---|---|---|
dashboard_request_total | Counter | tenant, status |
dashboard_latency_seconds | Histogram | tenant |
dashboard_module_latency_seconds | Histogram | module |
Related Flows
- Scan Submission Flow - How scans that feed the dashboard are created
- Policy Evaluation Flow - How policy verdicts are computed
- Risk Score Dashboard Flow - Detailed risk scoring
