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 under src/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/sbom is the subject selection: it takes an exact artifact digest (sha256, sha384 or sha512 with 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. The tenant read 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 answered 404. A compare result naming another pair is refused. The compare panel marks truncated component, VEX and reachability lists as partial with the owner’s totalChanges, 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 /lineage URLs and had no live caller. Its source and adjacent specs are frozen under src/__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= selects LineageCompareComponent. The duplicate parent mounts and bare compare alias are withdrawn. Both parameterized paths require a full supported content digest before an owner read. Supplied compare a and b values 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 commit e61fd40f12222ade87f6e3e0a6ed129d6667996b with 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/empty artifact selects 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 with lineage.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

AreaCompletionNotes
Lineage Graph SVG95%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 Cards85%Basic info displayed; needs CGS integration
SBOM Diff View90%Added/removed/changed sections in the compare panel (LineageComponentDiffComponent); needs row expanders
VEX Diff View90%Status change display in the compare panel (VexDiffViewComponent); needs reachability gates
Compare Mode85%Three-pane layout exists; needs explainer timeline
Export Dialog80%Audit pack export dialog on the compare page (ExportDialogComponent)
Proof Tree75%Merkle tree viz; needs confidence breakdown
Reachability Diff60%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:

StepDescriptionVisual Cue
SBOM ParseInitial SBOM ingestionDocument icon
Component MatchCVE-to-component matchingLink icon
VEX LookupVEX document resolutionShield icon
ReachabilityCall graph analysisGraph icon
Policy GatePolicy rule evaluationGate icon
VerdictFinal determinationCheckmark/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:

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 StatusIconColor
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

OptionDescriptionDefault
Include SBOMsBoth before/after SBOMs✓
Include DiffComponent/VEX/reachability diff✓
Include AttestationsDSSE envelopes✓
Include EvidenceSupporting evidence files✗
Sign BundleSign 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:

  1. Click pin icon on any verdict/finding
  2. Explanation captures current state with hash
  3. 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

MetricSourceWeight
SBOM CompletenessScanner analysis30%
VEX CoverageVEX Hub aggregation25%
Reachability DepthCall graph analysis25%
Attestation CountSigstore/local20%

8.2 Visualization

9. Component Inventory

9.1 Lineage Feature Components

ComponentLocationStatus
LineageGraphComponentlineage-graph.component.tsComplete; draws nodes and edges inline; nodes are keyboard and screen-reader reachable
LineageHoverCardComponentlineage-hover-card.component.tsNeeds CGS
LineageMiniMapComponentlineage-minimap.component.tsComplete
LineageControlsComponentlineage-controls.component.tsComplete
LineageCompareComponentlineage-compare.component.tsNeeds timeline