Console (Web UI) Architecture

Verified against commit 942a18727c (2026-09-26, DOC-2 of SPRINT_20260926_001). Re-verify by reading the evidence paths listed in the Evidence section.

This is the canonical architecture dossier for the Stella Ops Console, the Angular single-page application in src/Web/StellaOps.Web/. The Console is a consumer of backend APIs: it never scans, merges advisories, evaluates policy, or signs on its own.

Companion documents:

Paths below are relative to src/Web/StellaOps.Web/ unless they start with src/, devops/ or docs/.


0) Mission and boundaries

The Console gives operators one place to:

Boundaries:

1) Technology baseline

Verified in package.json and angular.json:

ConcernChoice
FrameworkAngular 21 (@angular/core), standalone components, Signals
LanguageTypeScript 5.9
UI kitHouse components and SCSS design tokens (src/styles/, src/styles/tokens/); Angular Material and CDK 21 are dependencies but frozen for new surfaces (UXR catalog)
StateAngular Signals and hand-rolled stores (for example core/auth/auth-session.store.ts); no @ngrx/* dependency
TransportHttpClient + RxJS; Server-Sent Events via EventSource in selected clients (core/api/console-status.client.ts, export-center.client.ts, console-export.client.ts)
Visualizationd3, @viz-js/viz (Graphviz/WASM), mermaid
Editors / Markdownmonaco-editor (lazy-loaded), yaml, marked, ngx-markdown
Build@angular/build:application (esbuild); output dist/stellaops-web/ (browser/ sub-folder)
Unit testsVitest through @angular/build:unit-test; batched by scripts/run-unit-test-batches.mjs
E2E / a11yPlaywright (@playwright/test, @axe-core/playwright); several playwright.*.config.ts lanes
Component workshopStorybook (@storybook/angular)

2) Source layout

src/Web/StellaOps.Web/
  src/app/
    core/        cross-cutting singletons: api/ (typed clients + api-path-prefixes.ts),
                 auth/ (OIDC, PKCE, DPoP, route access, interceptors), config/ (runtime
                 config), context/ (platform context + URL sync), i18n/, navigation/,
                 http/, design/ (UX guard specs), ...
    shared/      reusable components, directives, pipes, services
    layout/      app shell, sidebar, topbar, breadcrumb, global search, overlays
    routes/      top-level lazy route aggregators (ops, security-risk, releases,
                 evidence, compliance, setup, topology, triage, mission-control, ...)
    features/    one folder per surface, lazy-loaded
    app.config.ts   ApplicationConfig: router, HTTP, interceptors, DI providers
    app.routes.ts   top-level route table
  src/i18n/      embedded offline translation bundles
  src/environments/  build-time flags only (no hostnames, no secrets)
  e2e/, tests/   Playwright specs
  scripts/       dev server, test batching, live harnesses, guard scripts

Features are wired through loadComponent / loadChildren, so each surface builds as its own lazy chunk. HTTP DTOs live beside their clients as core/api/*.client.ts and *.models.ts.

3) Navigation and route shells

Two layers must not be confused:

  1. Route shells — top-level paths in src/app/app.routes.ts: '' (Dashboard, DashboardV3Component), mission-control, environments, releases, developer (My services), security, triage (Vulnerabilities), evidence, compliance, inventory, ops, setup, settings, console-admin, notifications, docs, plus the unauthenticated signin, signout, logged-out, auth/callback, auth/silent-refresh. /assurance/* is a redirect to /compliance/*; the administration/* block is redirects except administration/profile, which loads the operator profile and decision-signing enrollment panel. console/profile is the “Insufficient Permissions” page that scope guards redirect to. The wildcard route renders NotFoundComponent.
  2. Sidebar groups — NAVIGATION_GROUPS in src/app/core/navigation/navigation.config.ts is the source of truth for what the operator sees: Home, Vulnerabilities (risk), Deployment (releases), Proof, Operate, plus User Preferences in the user menu. Operate is organised into four shelves (Operations, Connections, Access, Settings); a shelf link resolves to its first scope-visible child.

Older deep links are preserved as redirectTo entries (LEGACY_REDIRECT_ROUTES in src/app/routes/legacy-redirects.routes.ts and inline children in app.routes.ts). Treat these as the router’s current behavior, not as supported product routes.

Per-surface behavior and its guards are documented in information-architecture.mdand in the feature contracts in ../web/architecture.md.

4) Authentication, session and authorization

4.1 OIDC with PKCE and DPoP

4.2 Session storage

Session metadata is kept in sessionStorage by core/auth/auth-storage.service.ts, which sweeps entries older than 10 minutes. The config-source cookie stellaops_env_settings_url is the only cookie the config loader writes.

4.3 Scopes and route access

4.4 HTTP interceptors

Registered in src/app/app.config.ts, in this order: AuthHttpInterceptor (bearer + DPoP, refresh), OperatorMetadataInterceptor, TenantHttpInterceptor (canonical X-StellaOps-* tenant/project headers from core/http/stella-ops-headers), GlobalContextHttpInterceptor (region/environment context as comma-delimited sets).

Platform context (tenant, regions, environments) is mirrored into the URL by core/context/platform-context-url-sync.service.ts: once the session is authenticated and the initial navigation has settled, a context change performs one replaceUrl navigation so the router state, address bar and queryParamsHandling="merge" links agree. Setup-wizard, auth, sign-in/out and console routes are excluded. Regression coverage: src/tests/context/platform-context-url-sync.service.spec.ts.

5) Runtime configuration

The SPA is configured at runtime, so one build serves any deployment. AppConfigService.load() (core/config/app-config.service.ts, run by provideAppInitializer in app.config.ts) resolves the document in this order:

  1. ?envSettings=<url> query parameter (remembered in the stellaops_env_settings_url cookie);
  2. the URL in that cookie;
  3. /platform/envsettings.json — served anonymously by Platform (src/Platform/StellaOps.Platform.WebService/Endpoints/EnvironmentSettingsEndpoints.cs) and routed by the gateway;
  4. /config.json — deprecated fallback that logs a warning.

If none resolves, the status is missing and requireConfigGuard sends the operator to /setup. The shape is AppConfig in core/config/app-config.model.ts:

There is no STELLAOPS_BACKEND_URL variable. Feature visibility is decided by scope guards, not by configuration flags. The lab values live in devops/compose/envsettings-override.json (read by app-config.service.spec.ts).

6) API clients and the prefix registry

7) Internationalization, theming, accessibility

8) Build, serving and deployment

9) Runtime contracts enforced in source

10) Selected surfaces

These surfaces carry architecture-level rules. Other per-surface contracts are in ../web/architecture.md§3 and in the topic folders of this dossier.

10.1 My services (Developer Services)

10.2 Registry images

10.3 Global search and assistant

10.4 Crypto providers and compliance profile

11) Testing

12) Not implemented / design targets

Evidence

ClaimSource
Framework, dependency and script setsrc/Web/StellaOps.Web/package.json
Builders, output path, docs-content asset mappingsrc/Web/StellaOps.Web/angular.json
Route shells, guards, redirects, console/profile, administration/profilesrc/Web/StellaOps.Web/src/app/app.routes.ts, src/app/routes/legacy-redirects.routes.ts
Sidebar groups and Operate shelvessrc/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts
Route access model (standard vs explicit rules)src/Web/StellaOps.Web/src/app/core/auth/route-access.ts
UI scope names; UI-local scopessrc/Web/StellaOps.Web/src/app/core/auth/scopes.ts; canonical src/Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOpsScopes.cs
PKCE, DPoP key store, EdDSA rejectionsrc/app/core/auth/pkce.util.ts, authority-auth.service.ts, dpop/dpop-key-store.ts
Refresh thresholdsrc/app/core/auth/auth-session.model.ts
Session storage sweepsrc/app/core/auth/auth-storage.service.ts
Interceptor ordersrc/Web/StellaOps.Web/src/app/app.config.ts
Context URL syncsrc/app/core/context/platform-context-url-sync.service.ts
Tenant headerssrc/app/core/auth/tenant-http.interceptor.ts, src/app/core/http/stella-ops-headers.ts
Runtime config chain and shapesrc/app/core/config/app-config.service.ts, app-config.model.ts
envsettings endpointsrc/Platform/StellaOps.Platform.WebService/Endpoints/EnvironmentSettingsEndpoints.cs; gateway route in src/Router/StellaOps.Gateway.WebService/appsettings.json
Console Authority client, public-origin expansiondevops/etc/authority/plugins/standard.yaml (stella-ops-ui, STELLAOPS_CONSOLE_PUBLIC_ORIGINS)
Lab runtime configdevops/compose/envsettings-override.json
Prefix registrysrc/app/core/api/api-path-prefixes.ts
Prefix sweepstools/scripts/validate/check-console-dead-vuln-prefixes.sh, tools/scripts/validate/check-console-*-prefix-literals.sh
SSE clientssrc/app/core/api/console-status.client.ts, export-center.client.ts, console-export.client.ts
Policy interopsrc/app/core/api/policy-interop.service.ts
i18n bundles and Platform endpointssrc/app/core/i18n/i18n.service.ts, locale-catalog.service.ts, user-locale-preference.service.ts
Dev serving (console-builder, console-dist, gateway StaticFiles)devops/compose/docker-compose.stella-services.yml, devops/docker/Dockerfile.console, devops/compose/router-gateway-local.json
Release serving (dist baked into gateway image)devops/release/bundle/tools/build-and-publish.sh, devops/release/bundle/tools/generate-bundle.py, devops/release/bundle/docker-compose.yml
Runtime mock guardsrc/Web/StellaOps.Web/scripts/guard-runtime-mocks.mjs
Developer servicessrc/app/features/developer-services/
Registry image readinesssrc/app/features/registry-browser/registry-image-readiness.ts
Search context registrysrc/app/core/services/search-context.registry.ts
Crypto provider client and routesrc/app/core/api/crypto-provider.client.ts, src/app/routes/setup.routes.ts