Smart-Diff UI Architecture

Version: 1.1 Status: Active 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