Files
git.stella-ops.org/docs/modules/web/smart-diff-ui-architecture.md
StellaOps Bot df94136727 feat: Implement distro-native version comparison for RPM, Debian, and Alpine packages
- Add RpmVersionComparer for RPM version comparison with epoch, version, and release handling.
- Introduce DebianVersion for parsing Debian EVR (Epoch:Version-Release) strings.
- Create ApkVersion for parsing Alpine APK version strings with suffix support.
- Define IVersionComparator interface for version comparison with proof-line generation.
- Implement VersionComparisonResult struct to encapsulate comparison results and proof lines.
- Add tests for Debian and RPM version comparers to ensure correct functionality and edge case handling.
- Create project files for the version comparison library and its tests.
2025-12-22 09:49:53 +02:00

359 lines
16 KiB
Markdown

# Smart-Diff UI Architecture
**Version:** 1.0
**Status:** Draft
**Last Updated:** 2025-12-22
**Sprint Reference:** SPRINT_4200_0002_0003
## 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:
```typescript
// 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
```typescript
@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
```typescript
// 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
```typescript
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:
```html
<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
## Related Documentation
- [Sprint: Delta Compare View UI](../../implplan/SPRINT_4200_0002_0003_delta_compare_view.md)
- [Sprint: Delta Compare Backend API](../../implplan/SPRINT_4200_0002_0006_delta_compare_api.md)
- [Smart-Diff CLI Reference](../../cli/smart-diff-cli.md)
- [Advisory: Smart Diff - Reproducibility as a Feature](../../product-advisories/archived/22-Dec-2025/21-Dec-2025%20-%20Smart%20Diff%20-%20Reproducibility%20as%20a%20Feature.md)