Smart-Diff UI Architecture

This document retains the December 2025 Smart-Diff design. Its old component, route and API examples are historical design proposals; they are not a current backend or operator contract.

Source boundary verified 2026-10-07

At commit ed225f7338382c180774c2ebf582353e34472ed6, the compare feature is under src/Web/StellaOps.Web/src/app/features/compare/, with route declarations in src/Web/StellaOps.Web/src/app/routes/security-risk.routes.ts. services/compare.service.ts declares /api/compare as its base and getItemEvidence(itemId) requests /api/compare/evidence/${itemId}. services/delta-compute.service.ts uses the same caller base for session delta reads. These are verified client request targets, not proof of backend response behavior or current live acceptance.

The Evidence host composes its own /api/evidence/v1/* namespace. Its consolidated route inventory does not contain the design’s delta-item, witness-path or VEX-merge read family. Moving those proposed paths under the Evidence prefix would invent a contract. Verify a compare caller against its actual owning endpoint before using it as an implementation reference.

Re-verify the feature clients and src/Evidence/__Tests/StellaOps.Evidence.WebService.Tests/Fixtures/consolidated-route-inventory.tsv. The Console architecture is the current module entry point.

Historical December 2025 design withdrawn 2026-10-07

The original diagrams, code examples, component tree, API table and test strategy below are retained for design history. They have no current implementation or runtime acceptance authority. The source boundary above takes precedence.

Currency note (2026-09-26, DOC-2). Design reference from 2025-12, moved here from docs/modules/web/. The compare view is implemented under src/Web/StellaOps.Web/src/app/features/compare/ and routed from src/app/routes/security-risk.routes.ts. Component names, paths and endpoints below were not line-verified; src/ wins where they differ.

Version: 1.1 Status: Historical design; implementation details withdrawn 2026-10-07 Last Updated: 2025-12-26 Sprint Reference: SPRINT_20251226_012_FE_smart_diff_compare

Overview

The Smart-Diff UI provides a dedicated comparison experience for analyzing material risk changes between container image versions. It implements a “diff-first” approach to vulnerability triage, enabling users to focus on what changed rather than reviewing entire vulnerability lists.

Design Principles

1. Diff-First Triage

The primary question in any release is: “What changed that affects risk?” The UI defaults to showing delta information rather than full vulnerability lists.

2. Proof-Carrying Evidence

Every verdict and comparison includes cryptographic evidence. Users can verify determinism, trace decisions to policy rules, and replay computations.

3. Baseline Transparency

Comparisons require explicit baselines with auditor-friendly rationale. The system never uses “magic” to select baselines without explanation.

4. Role-Based Defaults

Different personas (Developer, Security, Audit) see different default views while retaining access to all information.

Component Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                         SMART-DIFF UI ARCHITECTURE                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                        COMPARE VIEW CONTAINER                        │   │
│  │  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐   │   │
│  │  │  Baseline        │  │  Trust           │  │  Export          │   │   │
│  │  │  Selector        │  │  Indicators      │  │  Actions         │   │   │
│  │  └──────────────────┘  └──────────────────┘  └──────────────────┘   │   │
│  │           │                     │                     │              │   │
│  │  ┌────────────────────────────────────────────────────────────────┐ │   │
│  │  │                    DELTA SUMMARY STRIP                         │ │   │
│  │  │  [+N added] [-N removed] [~N changed] [Policy: v1.2] [Feed: 2h]│ │   │
│  │  └────────────────────────────────────────────────────────────────┘ │   │
│  │                                                                      │   │
│  │  ┌───────────────────────────────────────────────────────────────┐  │   │
│  │  │                    THREE-PANE LAYOUT                          │  │   │
│  │  │  ┌──────────┐  ┌────────────────┐  ┌────────────────────────┐ │  │   │
│  │  │  │Categories│  │    Items       │  │      Proof Panel       │ │  │   │
│  │  │  │          │  │                │  │                        │ │  │   │
│  │  │  │ ● SBOM   │  │ CVE-2024-1234  │  │ ┌────────────────────┐ │ │  │   │
│  │  │  │ ● Reach  │  │ lodash@4.17.20 │  │ │ Witness Path       │ │ │  │   │
│  │  │  │ ● VEX    │  │ +reachable     │  │ │ main() → parse()   │ │ │  │   │
│  │  │  │ ● Policy │  │ Priority: 0.85 │  │ │ → vuln_func()      │ │ │  │   │
│  │  │  │ ● Unknwn │  │                │  │ └────────────────────┘ │ │  │   │
│  │  │  │          │  │ CVE-2024-5678  │  │ ┌────────────────────┐ │ │  │   │
│  │  │  │          │  │ requests@2.28  │  │ │ VEX Merge          │ │ │  │   │
│  │  │  │          │  │ +KEV           │  │ │ vendor: affected   │ │ │  │   │
│  │  │  │          │  │ Priority: 0.95 │  │ │ distro: not_aff    │ │ │  │   │
│  │  │  │          │  │                │  │ │ → Result: affected │ │ │  │   │
│  │  │  │          │  │                │  │ └────────────────────┘ │ │  │   │
│  │  │  └──────────┘  └────────────────┘  └────────────────────────┘ │  │   │
│  │  └───────────────────────────────────────────────────────────────┘  │   │
│  │                                                                      │   │
│  │  ┌────────────────────────────────────────────────────────────────┐ │   │
│  │  │                    ACTIONABLES PANEL                           │ │   │
│  │  │  ┌─────────────────────────────────────────────────────────┐   │ │   │
│  │  │  │ What to do next:                                        │   │ │   │
│  │  │  │ 1. [CRITICAL] Upgrade lodash → 4.17.21                  │   │ │   │
│  │  │  │ 2. [HIGH] Add VEX statement for urllib3 (not affected)  │   │ │   │
│  │  │  │ 3. [MEDIUM] Resolve unknown: missing SBOM for module A  │   │ │   │
│  │  │  └─────────────────────────────────────────────────────────┘   │ │   │
│  │  └────────────────────────────────────────────────────────────────┘ │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Component Hierarchy

CompareViewComponent
├── BaselineSelectorComponent
│   └── BaselineRationaleComponent
├── TrustIndicatorsComponent
│   ├── DeterminismHashDisplay
│   ├── PolicyVersionDisplay
│   ├── FeedSnapshotDisplay
│   ├── SignatureStatusDisplay
│   └── PolicyDriftIndicator
├── DeltaSummaryStripComponent
├── ThreePaneLayoutComponent
│   ├── CategoriesPaneComponent
│   ├── ItemsPaneComponent
│   └── ProofPaneComponent
│       ├── WitnessPathComponent
│       ├── VexMergeExplanationComponent
│       └── EnvelopeHashesComponent
├── ActionablesPanelComponent
└── ExportActionsComponent

State Management

Signals-Based State

The compare view uses Angular signals for reactive state management:

// Core state
currentTarget = signal<CompareTarget | null>(null);
baselineTarget = signal<CompareTarget | null>(null);
delta = signal<DeltaVerdictResponse | null>(null);

// UI state
selectedCategory = signal<string | null>(null);
selectedItem = signal<DeltaItem | null>(null);
viewMode = signal<'side-by-side' | 'unified'>('side-by-side');
userRole = signal<'developer' | 'security' | 'audit'>('developer');

// Computed state
filteredItems = computed(() => {
  const cat = this.selectedCategory();
  const items = this.delta()?.Items ?? [];
  return cat ? items.filter(i => i.category === cat) : items;
});

deltaSummary = computed(() => this.delta()?.Summary);
trustIndicators = computed(() => this.delta()?.TrustIndicators);

Data Flow

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   Route     │───►│  Component  │───►│   Service   │
│  Params     │    │   Init      │    │   Calls     │
└─────────────┘    └─────────────┘    └─────────────┘
                          │                   │
                          ▼                   ▼
                   ┌─────────────┐    ┌─────────────┐
                   │   Signals   │◄───│   Backend   │
                   │   Update    │    │   Response  │
                   └─────────────┘    └─────────────┘
                          │
                          ▼
                   ┌─────────────┐
                   │  Computed   │
                   │   Values    │
                   └─────────────┘
                          │
                          ▼
                   ┌─────────────┐
                   │  Template   │
                   │   Render    │
                   └─────────────┘

API Integration

Backend Endpoints

EndpointPurpose
GET /api/v1/baselines/recommendations/{digest}Get recommended baselines
GET /api/v1/baselines/rationale/{base}/{head}Get baseline selection rationale
POST /api/v1/delta/computeCompute delta (idempotent)
GET /api/v1/delta/{deltaId}Get delta results
GET /api/v1/delta/{deltaId}/trust-indicatorsGet trust indicators
GET /api/v1/actionables/delta/{deltaId}Get actionable recommendations
GET /api/v1/evidence/delta/{deltaId}/items/{itemId}Get item evidence
GET /api/v1/evidence/delta/{deltaId}/witness-pathsGet witness paths
GET /api/v1/evidence/delta/{deltaId}/vex-merge/{vulnId}Get VEX merge explanation

Service Layer

@Injectable({ providedIn: 'root' })
export class CompareService {
  constructor(private http: HttpClient) {}

  getRecommendedBaselines(digest: string): Observable<BaselineRecommendationsResponse> {
    return this.http.get<BaselineRecommendationsResponse>(
      `/api/v1/baselines/recommendations/${digest}`
    );
  }

  computeDelta(request: DeltaComputeRequest): Observable<DeltaVerdictResponse> {
    return this.http.post<DeltaVerdictResponse>('/api/v1/delta/compute', request);
  }

  getActionables(deltaId: string): Observable<ActionablesResponse> {
    return this.http.get<ActionablesResponse>(`/api/v1/actionables/delta/${deltaId}`);
  }

  getItemEvidence(deltaId: string, itemId: string): Observable<DeltaItemEvidenceResponse> {
    return this.http.get<DeltaItemEvidenceResponse>(
      `/api/v1/evidence/delta/${deltaId}/items/${itemId}`
    );
  }
}

Routing

// app.routes.ts additions
{
  path: 'releases/:releaseId',
  children: [
    { path: '', redirectTo: 'detail', pathMatch: 'full' },
    { path: 'detail', component: ReleaseFlowComponent },
    {
      path: 'compare',
      component: CompareViewComponent,
      data: { requireBaseline: false }
    },
    {
      path: 'compare/:baselineId',
      component: CompareViewComponent,
      data: { requireBaseline: true }
    }
  ]
},
{
  path: 'compare',
  children: [
    {
      path: ':currentDigest',
      component: CompareViewComponent
    },
    {
      path: ':currentDigest/:baselineDigest',
      component: CompareViewComponent
    }
  ]
}

Role-Based Views

Default Tab by Role

RoleDefault TabVisible Features
DeveloperActionablesActionables, Witness Paths, Upgrade Suggestions
SecurityClaimsVEX Merge, Policy Reasoning, Claim Sources, Actionables
AuditAttestationsSignatures, Replay, Evidence Pack, Envelope Hashes

Implementation

const ROLE_DEFAULTS: Record<UserRole, RoleConfig> = {
  developer: {
    defaultTab: 'actionables',
    showFeatures: ['actionables', 'witness-paths', 'upgrade-suggestions'],
    expandedPanels: ['actionables']
  },
  security: {
    defaultTab: 'claims',
    showFeatures: ['vex-merge', 'policy-reasoning', 'claim-sources', 'actionables'],
    expandedPanels: ['vex-merge', 'policy']
  },
  audit: {
    defaultTab: 'attestations',
    showFeatures: ['signatures', 'replay', 'evidence-pack', 'envelope-hashes'],
    expandedPanels: ['trust-indicators', 'signatures']
  }
};

Trust Indicators

Determinism Verification

The UI displays and enables verification of:

  1. Determinism Hash - SHA-256 of normalized delta output
  2. Policy Version/Hash - Active policy at scan time
  3. Feed Snapshot - Vulnerability feed timestamp and hash
  4. Signature Status - DSSE envelope verification result

Degraded Mode

When signature verification fails, the UI:

Accessibility

Keyboard Navigation

Screen Reader Support

Performance Considerations

Lazy Loading

Caching

Virtual Scrolling

For large deltas (> 100 items), the items pane uses virtual scrolling:

<cdk-virtual-scroll-viewport itemSize="56" class="items-viewport">
  <mat-list-item *cdkVirtualFor="let item of filteredItems()">
    <!-- item content -->
  </mat-list-item>
</cdk-virtual-scroll-viewport>

Testing Strategy

Unit Tests

Integration Tests

E2E Tests