Lineage Smart-Diff UI Architecture
Sprint: SPRINT_20251229_001_FE_lineage_smartdiff_overview Last Updated: 2025-12-29
Paths verified against commit
942a18727c(2026-09-26): the components listed below live undersrc/Web/StellaOps.Web/src/app/features/lineage/components/. The per-component status and interaction detail were not re-verified. The Console data service (features/lineage/services/lineage-graph.service.ts) calls SbomService’s own routes (GET /api/v1/lineage/{artifactDigest},/api/v1/lineage/diff,/api/v1/lineage/compare,/api/v1/artifact-links/*), listed in …/architecture.md, and maps the graph and diff responses onto its view model (2026-09-27). The graph and diff responses carry no vulnerability summary, so the views show none. The diff and compare responses carry both versions’ component totals, which the component-diff footer and the deploy-diff headers show; when a response carries none, those elements are not rendered. No lineage response carries signature or attestation state, so the node views state none (the hover card’s attestation row and the graph’s attestation indicator are rendered only when an owner reports the state; SPRINT_20260928_004 PCD-151).
Console routes and states (SPRINT_20261007_031 CCP-024).
/security/sbomis the subject selection: it takes an exact artifact digest (sha256,sha384orsha512with the full hex length) and, optionally, a second digest, and opens/security/lineage/<digest>or/security/lineage/<digest>/compare?a=<digest>&b=<digest>. The lineage viewer without a digest asks for one and links back to that selection; it draws only the graph read for the digest in its URL. The compare page shows both digests in full, reads them from the URL on every navigation and refresh, and returns to the lineage of the subject it was opened from. Thetenantread assertion is the URL’s value, else the active session tenant, else none. A missing selection, an artifact without a lineage record (404), a denied read (401,403,400 tenant_mismatch) and an unreachable or failing service each render their own state, in the viewer and on the compare page; only an unreachable or failing service offers a retry. The viewer shows only the read state of the digest in its URL, so a failed or pending read of another digest (for example from a comparison) never appears under it. The compare page names the side for which SbomService answered404. A compare result naming another pair is refused. The compare panel marks truncated component, VEX and reachability lists as partial with the owner’stotalChanges, and states “not reported” for the vulnerability, VEX and reachability counts when the response carries no delta summary for them, rather than showing 0.
Unused routing family withdrawn (CCP-076, 2026-10-15). The former guard, resolver and URL-service family emitted unsupported bare
/lineageURLs and had no live caller. Its source and adjacent specs are frozen undersrc/__Obsoleted/Web/StellaOps.Web/src/app/features/lineage/routing/. The supported child table is mounted once at/security/lineage: the root asks for a subject,/<digest>selects the viewer and/<digest>/compare?a=&b=selectsLineageCompareComponent. The duplicate parent mounts and bare compare alias are withdrawn. Both parameterized paths require a full supported content digest before an owner read. Supplied compareaandbvalues must each be one digest; malformed or repeated values do not select a page or trigger an owner read. Missing selections retain the comparison’s selection-required state. Existing tenant assertions, owner-denied/missing/unavailable states and Back targets are unchanged. The static access inventory reports both handwritten digest guards as unresolved; it does not infer role acceptance from them. Routing source verified against commite61fd40f12222ade87f6e3e0a6ed129d6667996bwith 30 actual Router cases. Full source qualification is recorded in the CCP-076 receipt; deployment and real served navigation remain separate. Root query selection remains supported: missing/emptyartifactselects no subject; a supplied value must be one exact digest. Malformed or repeated values are refused before owner reads, as on the digest path routes. All three handwritten guard bindings remain unresolved in the static persona model; real scope refusal tests remain separate evidence. Re-verify withlineage.routes.spec.ts, the adjacent compare/viewer specs, and the production Console build.
1. Overview
The Lineage Smart-Diff feature provides a comprehensive UI for visualizing SBOM lineage graphs, comparing artifact versions, and explaining security state changes between builds.
1.1 Completion Status
| Area | Completion | Notes |
|---|---|---|
| Lineage Graph SVG | 95% | Full DAG visualization with lanes, pan/zoom, nodes; LineageGraphComponent draws each node inline (shape, selection ring, status and attestation indicators, label, digest); each node is a keyboard-focusable button with an accessible name (Enter or Space selects, focus opens the hover card, arrow keys move between nodes) |
| Hover Cards | 85% | Basic info displayed; needs CGS integration |
| SBOM Diff View | 90% | Added/removed/changed sections in the compare panel (LineageComponentDiffComponent); needs row expanders |
| VEX Diff View | 90% | Status change display in the compare panel (VexDiffViewComponent); needs reachability gates |
| Compare Mode | 85% | Three-pane layout exists; needs explainer timeline |
| Export Dialog | 80% | Audit pack export dialog on the compare page (ExportDialogComponent) |
| Proof Tree | 75% | Merkle tree viz; needs confidence breakdown |
| Reachability Diff | 60% | Basic view; needs gate visualization |
2. UI Data Contracts
2.1 CGS/Lineage API Integration
// Lineage Graph Response
interface LineageGraph {
artifact: string;
nodes: LineageNode[];
edges: LineageEdge[];
metadata: {
totalNodes: number;
maxDepth: number;
generatedAt: string;
};
}
interface LineageNode {
id: string;
artifactDigest: string;
artifactRef: string;
sequenceNumber: number;
createdAt: string;
source: string;
parentDigests: string[];
badges?: {
newVulns: number;
resolvedVulns: number;
componentCount: number;
};
replayHash: string;
cgsHash?: string;
}
interface LineageEdge {
fromDigest: string;
toDigest: string;
relationship: 'parent' | 'build' | 'base' | 'derived';
}
2.2 Diff Response Schema
interface LineageDiffResponse {
fromDigest: string;
toDigest: string;
sbomDiff: {
added: ComponentDiff[];
removed: ComponentDiff[];
versionChanged: VersionChange[];
licenseChanged: LicenseChange[];
};
vexDiff: VexDelta[];
reachabilityDiff: ReachabilityDelta[];
replayHash: string;
generatedAt: string;
}
interface ComponentDiff {
purl: string;
name: string;
version: string;
ecosystem: string;
license?: string;
scope: 'runtime' | 'development' | 'optional';
}
interface VersionChange {
purl: string;
name: string;
fromVersion: string;
toVersion: string;
changeType: 'upgrade' | 'downgrade' | 'patch';
}
interface VexDelta {
cveId: string;
purl: string;
fromStatus: VexStatus;
toStatus: VexStatus;
justification?: string;
effectiveAt: string;
}
interface ReachabilityDelta {
cveId: string;
purl: string;
fromReachable: boolean;
toReachable: boolean;
paths?: string[][];
}
3. Explainer Timeline Requirements
3.1 Engine Steps
The explainer timeline shows the sequence of analysis steps:
| Step | Description | Visual Cue |
|---|---|---|
| SBOM Parse | Initial SBOM ingestion | Document icon |
| Component Match | CVE-to-component matching | Link icon |
| VEX Lookup | VEX document resolution | Shield icon |
| Reachability | Call graph analysis | Graph icon |
| Policy Gate | Policy rule evaluation | Gate icon |
| Verdict | Final determination | Checkmark/X |
3.2 Data Binding
interface ExplainerStep {
stepId: string;
stepType: 'sbom_parse' | 'component_match' | 'vex_lookup' |
'reachability' | 'policy_gate' | 'verdict';
timestamp: string;
duration: number;
status: 'success' | 'warning' | 'error';
inputs: Record<string, unknown>;
outputs: Record<string, unknown>;
evidence?: {
type: string;
hash: string;
downloadUrl?: string;
};
}
4. Node Diff Table UX
4.1 Expander Pattern
Each diff row can expand to show:
- Full component details (license, scope, dependencies)
- CVE associations and status
- Reachability paths (if analyzed)
- VEX statements affecting the component
4.2 Visual Design
┌─────────────────────────────────────────────────────────────┐
│ Component │ Before │ After │ Status│
├─────────────────────────────────────────────────────────────┤
│ ▶ lodash │ 4.17.20 │ 4.17.21 │ ↑ │
│ └─ CVE-2021-23337 (fixed in 4.17.21) │
│ └─ License: MIT │
├─────────────────────────────────────────────────────────────┤
│ ▶ axios │ 0.21.1 │ 0.21.4 │ ↑ │
├─────────────────────────────────────────────────────────────┤
│ ▶ express │ - │ 4.18.2 │ + NEW │
└─────────────────────────────────────────────────────────────┘
5. Reachability Gate Diff UI
5.1 Visual Cues
| Gate Status | Icon | Color |
|---|---|---|
| Reachable | ● | Red |
| Not Reachable | ○ | Green |
| Unknown | ? | Gray |
| Changed | ↔ | Orange |
5.2 Path Display
When reachability changes, show the call path:
entrypoint.ts → handler.ts → vulnerable_fn.ts → lodash.get()
6. Audit Pack Export UI
6.1 Export Options
| Option | Description | Default |
|---|---|---|
| Include SBOMs | Both before/after SBOMs | ✓ |
| Include Diff | Component/VEX/reachability diff | ✓ |
| Include Attestations | DSSE envelopes | ✓ |
| Include Evidence | Supporting evidence files | ✗ |
| Sign Bundle | Sign with tenant key | ✓ |
6.2 Manifest Schema
{
"version": "1.0",
"generatedAt": "2025-12-29T10:00:00Z",
"artifactA": { "digest": "sha256:...", "name": "...", "createdAt": "..." },
"artifactB": { "digest": "sha256:...", "name": "...", "createdAt": "..." },
"contents": [
{ "type": "sbom", "filename": "before.cdx.json", "sha256": "..." },
{ "type": "sbom", "filename": "after.cdx.json", "sha256": "..." },
{ "type": "diff", "filename": "diff.json", "sha256": "..." },
{ "type": "attestation", "filename": "attestations.dsse.json", "sha256": "..." }
],
"merkleRoot": "sha256:...",
"summary": {
"componentsAdded": 5,
"componentsRemoved": 2,
"vexUpdates": 3,
"attestationCount": 2
}
}
7. Copy-Safe Workflow
7.1 Pinned Explanation UX
Operators can “pin” explanations for ticket export:
- Click pin icon on any verdict/finding
- Explanation captures current state with hash
- Export as Markdown/JSON for JIRA/ServiceNow
7.2 Ticket Export Format
## Security Finding Report
**Artifact**: myorg/myapp:v1.2.3 (sha256:abc123...)
**Generated**: 2025-12-29T10:00:00Z
**Replay Hash**: sha256:def456...
### Finding: CVE-2021-23337 in lodash
| Field | Value |
|-------|-------|
| Status | Not Affected |
| Reason | Not Reachable |
| Evidence | Call graph analysis |
| VEX ID | VEX-2025-001 |
### Verification
To reproduce this verdict:
stella verdict replay --hash sha256:def456…
8. Confidence Breakdown Charts
8.1 Metrics Sources
| Metric | Source | Weight |
|---|---|---|
| SBOM Completeness | Scanner analysis | 30% |
| VEX Coverage | VEX Hub aggregation | 25% |
| Reachability Depth | Call graph analysis | 25% |
| Attestation Count | Sigstore/local | 20% |
8.2 Visualization
- Donut chart showing confidence breakdown
- Hover for detailed explanations
- Color coding: Green (>80%), Yellow (50-80%), Red (<50%)
9. Component Inventory
9.1 Lineage Feature Components
| Component | Location | Status |
|---|---|---|
LineageGraphComponent | lineage-graph.component.ts | Complete; draws nodes and edges inline; nodes are keyboard and screen-reader reachable |
LineageHoverCardComponent | lineage-hover-card.component.ts | Needs CGS |
LineageMiniMapComponent | lineage-minimap.component.ts | Complete |
LineageControlsComponent | lineage-controls.component.ts | Complete |
LineageCompareComponent | lineage-compare.component.ts | Needs timeline |
