Web UI Architecture

Stella Ops Web UI (Angular Console; baseline 2025Q4). Consolidates Console requirements documented across the Policy, Graph, Vulnerability Explorer, Advisory AI, Orchestrator, and Notifications module guides and implementation plans.

Scope. Implementation-ready architecture for the Angular SPA that operators and developers use to drive Stella Ops. This document defines UX surfaces, module boundaries, data flows, auth, RBAC, real-time updates, performance targets, i18n/a11y, security headers, testing and deployment. The UI is a consumer of backend APIs (Scanner, Policy, Excititor, Concelier, Attestor, Authority) and never performs scanning, merging, or signing on its own.


0) Mission & non-goals

Mission. Provide a fast, explainable console for:

Non-goals. No client-side crypto signing; no Docker/CRI access; no direct registry access beyond fetching static assets or OCI referrer summaries exposed by backend.


1) Technology baseline


2) High-level module map

Conceptual map — not the literal directory tree. The implementation is organised as src/app/core/ (bootstrap, config, auth, http, i18n, api clients), src/app/shared/, src/app/features/<feature>/ (one folder per feature, each lazy-loaded), and src/app/routes/ (route aggregators that compose features into top-level shells: mission-control, releases, security-risk, triage, evidence, compliance, ops, setup, topology, platform-ops). The grouping below is the domain view; map it to features/ + routes/ when navigating source.

core/      # bootstrap, config, auth (PKCE/DPoP), http interceptors, i18n, api/ clients
shared/    # UI kit (tables, code-viewers, badges), pipes
features/  # one lazy-loaded folder per surface, e.g.:
  dashboard-v3/        # home dashboard (persona lens-card registry)
  scanner-ops/         # scanner operations
  security/ + findings/ + image-security/   # security & findings surfaces
  policy/ + policy-studio/ + policy-governance/ + policy-simulation/  # policy authoring/eval
  vex-hub/ + vex-timeline/ + console-vex     # VEX explorer/consensus
  release-orchestrator/ (dashboard/runs/approvals/deployments/...)    # release control
  evidence/ + evidence-export/ + evidence-thread/  # attestation/evidence
  feed-mirror/ + sbom-sources/               # source/feed health
  console-admin/ + settings/ + setup-wizard/ # admin/setup
  integration-hub/                           # integrations + wizard
routes/    # top-level shells that lazy-load and guard the feature folders

Each feature folder builds as a standalone route (lazy loaded via loadChildren / loadComponent). HTTP shapes live in core/api/*.client.ts + *.models.ts.


3) Navigation & key views

Navigation note. The live top-level shells (declared in app.routes.ts) are: home Dashboard (/dashboard-v3), Mission Control (/mission-control), Environments (/environments), Releases (/releases), Developer Services (/developer), Security (/security), Vulnerabilities (/triage), Evidence (/evidence), Compliance (/compliance, alias /assurance), Asset Inventory (/inventory), Operations (/ops), Setup (/setup), Settings (/settings), Console Admin (/console-admin), Notifications (/notifications), and Documentation (/docs). Several subsections below (Scans, Runtime/Zastava, Excititor, Concelier, Attest) describe domain capabilities by their original product names; in the current build these surface as routes under those shells (e.g. scanner operations under /opsscanner-ops, runtime evidence as a tab inside finding detail, VEX under vex-hub/console-vex, attestation/evidence under /evidence) rather than as the literal top-level routes named in the headings. Treat the per-domain bullets as capability specs.

Route shells ≠ sidebar sections. Those app.routes.ts shells are not what the operator sees in the rail. The sidebar renders five pillarshome · risk · releases · proof · operate (src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts:49 NAVIGATION_GROUPS, rendered as Home · Vulnerabilities · Deployment · Proof · Operate) — and the route shells are re-parented under them (Phase A, sprint 20260707_006). navigation.config.ts is the source of truth for the grouping; a second navSections array in app-sidebar.component.ts is a test/mirror artifact, not the rendered nav.

3.1 Dashboard

3.1a Developer Services

3.1b Registry Images

3.2 Scans

3.3 Runtime (Zastava)

3.4 Policy

3.5 Excititor

3.6 Concelier

3.7 Attest

3.8 Admin

3.9 Vulnerability triage (VEX-first)

3.10 Integration Hub (Sprint 011)

3.11 Integration Wizard (Sprint 014)

3.12 Advisor (Ask Stella)

3.13 Global Search and Assistant Bridge


4) Auth, sessions & RBAC

4.1 OIDC flow

4.2 RBAC

4.3 Session storage


5) HTTP layer & API clients

DTO examples (abbrev):

export type ImageDigest = `sha256:${string}`;
export interface ScanSummary {
  imageDigest: ImageDigest; createdAt: string;
  artifacts: { view: 'inventory'|'usage'; format: 'cdx-json'|'cdx-pb'|'spdx-json'; sha256: string; size: number }[];
  status: 'queued'|'running'|'completed'|'error';
  rekor?: { uuid: string; index?: number; url?: string };
}
export interface DiffEntry {
  key: string; change: 'added'|'removed'|'upgraded'|'downgraded';
  fromVersion?: string; toVersion?: string; layer: string; usedByEntrypoint?: boolean;
}
export interface VexConsensus {
  vulnId: string; productKey: string; rollupStatus: 'affected'|'not_affected'|'fixed'|'under_investigation';
  sources: { providerId: string; status: string; weight: number; accepted: boolean; reason: string }[];
}

Notifier delivery history: notify.client.ts reads delivery history from <notify-base>/deliveries and deserialises a NotifyDeliveriesResponse envelope. The illustrative shape below is representative; field names are normalized through the client and may differ from the wire DTO — treat this as a design sketch rather than the authoritative contract.

// Illustrative — see notify.client.ts / notify.models.ts for the live shape.
export interface NotifyDeliveryListResponse {
  items: NotifyDelivery[];
  count: number;
  continuationToken?: string;
}

export interface NotifyDelivery {
  deliveryId: string;
  ruleId: string;
  actionId: string;
  status: 'pending'|'sent'|'failed'|'throttled'|'digested'|'dropped';
  rendered: NotifyDeliveryRendered;
  metadata: Record<string, string>; // includes channelType, target, previewProvider, traceId, and provider-specific entries
  createdAt: string;
  sentAt?: string;
  completedAt?: string;
}

6) State, caching & real-time


7) SBOM viewing & diff UX


8) Policy editor & VEX integration


9) Accessibility, i18n & theming


10) Performance budgets

Techniques: route-level code splitting; ChangeDetectionStrategy.OnPush; Signals; server compression (zstd/gzip), immutable assets with long max-age (cache busting via hashes).


11) Security headers & CSP


12) Error handling & UX hygiene


13) Observability


14) Testing strategy


15) Deployment & ops


16) Plugin system (route plug-ins)

Status: roadmap / not implemented. The Console does not currently load front-end route plug-ins via signed remote JS bundles — there is no module-federation/remote-module loader, manifest verifier, or features/plugins/ folder in src/Web/StellaOps.Web. What exists today is read-only viewing of backend plugins (core/api/execution-plugin-registry.client.ts, features/release-orchestrator/plugins/installed-plugins.component.ts). The design below describes the intended future front-end plug-in model.


17) Wire sequences (representative)

A) View scan progress

sequenceDiagram
  autonumber
  participant UI
  participant Auth as Authority
  participant SW as Scanner.WebService

  UI->>Auth: /authorize (PKCE)
  Auth-->>UI: code → token (DPoP-bound)
  UI->>SW: GET /scans/{id} (Authorization+DPoP)
  SW-->>UI: { status: running }
  UI->>SW: (SSE) GET /scans/{id}/events
  SW-->>UI: progress events …
  SW-->>UI: terminal event { status: completed, artifacts[] }

B) Verify attestation

sequenceDiagram
  autonumber
  participant UI
  participant AT as Attestor
  UI->>AT: POST /rekor/verify { uuid }
  AT-->>UI: { ok:true, index, logURL }

C) Promote policy & preview

sequenceDiagram
  autonumber
  participant UI
  participant BE as Scanner.WebService (Policy endpoint)
  UI->>BE: POST /policy/stage { yaml, rego }
  BE-->>UI: { policyRevision, diagnostics }
  UI->>BE: POST /policy/preview { imageDigest, policyRevision }
  BE-->>UI: { verdict: pass|fail, reasons[] }
  UI->>BE: POST /policy/promote { policyRevision }
  BE-->>UI: { ok:true }

18) Security hard lines


19) Roadmap

20) Advisory Commitments (2026-02-26 Batch)

Runtime API Provider Contract

The Angular runtime must bind API injection tokens to HTTP clients from app.config.ts and route-level providers. Mock, stub, fake, and in-memory API clients are allowed only for specs, Storybook, and explicit local fixtures. Provider helpers in production source must default to HTTP clients; local/test callers must opt in to mocks. provideScoreApi() and provideReplayApi() follow this contract: calling them without arguments binds the HTTP client, while the named 'local-harness' mode is reserved for test-only/local fixture setup. Legacy truthy flags must not select fixture clients.

src/Web/StellaOps.Web/scripts/guard-runtime-mocks.mjs scans runtime config, all route files, and the score/replay provider helpers for mock provider bindings, local-harness provider bindings, or mock-default helper definitions. Run npm run guard:runtime-mocks whenever API provider wiring changes.

The same guard also checks routed release approval decision surfaces for hardcoded gate, security, evidence, replay, or placeholder decision-cockpit data. Production /releases/approvals/:id pages must render API-backed approval detail store truth and fail closed when required decision subresources are unavailable; deterministic fixtures belong in test-only files.

Frontend Truthfulness Contract

The Console must not create evidence-grade outcomes in the browser. UI components may format and explain backend results, but must not locally simulate DSSE verification, Rekor inclusion, policy verdict changes, export signing, or air-gap sealing state.

Current fail-closed requirements: