Files
git.stella-ops.org/docs/modules/web/smart-diff-ui-architecture.md
StellaOps Bot 907783f625 Add property-based tests for SBOM/VEX document ordering and Unicode normalization determinism
- Implement `SbomVexOrderingDeterminismProperties` for testing component list and vulnerability metadata hash consistency.
- Create `UnicodeNormalizationDeterminismProperties` to validate NFC normalization and Unicode string handling.
- Add project file for `StellaOps.Testing.Determinism.Properties` with necessary dependencies.
- Introduce CI/CD template validation tests including YAML syntax checks and documentation content verification.
- Create validation script for CI/CD templates ensuring all required files and structures are present.
2025-12-26 15:17:58 +02:00

17 KiB

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

Endpoint Purpose
GET /api/v1/baselines/recommendations/{digest} Get recommended baselines
GET /api/v1/baselines/rationale/{base}/{head} Get baseline selection rationale
POST /api/v1/delta/compute Compute delta (idempotent)
GET /api/v1/delta/{deltaId} Get delta results
GET /api/v1/delta/{deltaId}/trust-indicators Get 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-paths Get 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

Role Default Tab Visible Features
Developer Actionables Actionables, Witness Paths, Upgrade Suggestions
Security Claims VEX Merge, Policy Reasoning, Claim Sources, Actionables
Audit Attestations Signatures, 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:

  • Displays a prominent warning banner
  • Disables "Approve" actions
  • Shows detailed verification failure reason
  • Provides replay command for local verification

Accessibility

Keyboard Navigation

  • Tab / Shift+Tab: Navigate between panes
  • Arrow Up/Down: Navigate items within pane
  • Enter: Select item / expand detail
  • Escape: Close expanded detail
  • C: Copy replay command (when focused on trust indicators)

Screen Reader Support

  • ARIA labels on all interactive elements
  • Live regions for delta summary updates
  • Semantic heading structure

Performance Considerations

Lazy Loading

  • Evidence panel loads on-demand when item selected
  • Witness paths collapse by default (expand on click)
  • VEX merge details in expansion panel

Caching

  • Delta computations cached by (base_hash, head_hash, policy_hash)
  • Baseline recommendations cached per session
  • Trust indicators cached with delta

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

  • Component behavior (selection, filtering, expansion)
  • Computed signal derivations
  • Role-based view switching

Integration Tests

  • API service calls and response handling
  • Navigation and routing
  • State persistence across route changes

E2E Tests

  • Full comparison workflow
  • Baseline selection and rationale display
  • Export functionality
  • Role-based default verification