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.
This commit is contained in:
358
docs/modules/web/smart-diff-ui-architecture.md
Normal file
358
docs/modules/web/smart-diff-ui-architecture.md
Normal file
@@ -0,0 +1,358 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user