- 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.
359 lines
16 KiB
Markdown
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)
|