feat: Add operations runbooks and UI API models for Sprint 3500.0004.x
Operations documentation: - docs/operations/reachability-runbook.md - Reachability troubleshooting guide - docs/operations/unknowns-queue-runbook.md - Unknowns queue management guide UI TypeScript models: - src/Web/StellaOps.Web/src/app/core/api/proof.models.ts - Proof ledger types - src/Web/StellaOps.Web/src/app/core/api/reachability.models.ts - Reachability types - src/Web/StellaOps.Web/src/app/core/api/unknowns.models.ts - Unknowns queue types Sprint: SPRINT_3500_0004_0002 (UI), SPRINT_3500_0004_0004 (Docs)
This commit is contained in:
180
src/Web/StellaOps.Web/src/app/core/api/proof.models.ts
Normal file
180
src/Web/StellaOps.Web/src/app/core/api/proof.models.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Proof and manifest models for Sprint 3500.0004.0002 - T6.
|
||||
* Supports proof ledger visualization and score replay operations.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Manifest Models
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Hash entry in the scan manifest.
|
||||
*/
|
||||
export interface ManifestHashEntry {
|
||||
readonly label: string;
|
||||
readonly algorithm: 'sha256' | 'sha384' | 'sha512';
|
||||
readonly value: string;
|
||||
readonly source: 'sbom' | 'layer' | 'config' | 'composition';
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan manifest with all input hashes for proof computation.
|
||||
*/
|
||||
export interface ScanManifest {
|
||||
readonly manifestId: string;
|
||||
readonly scanId: string;
|
||||
readonly imageDigest: string;
|
||||
readonly createdAt: string;
|
||||
readonly hashes: readonly ManifestHashEntry[];
|
||||
readonly merkleRoot: string;
|
||||
readonly compositionManifestUri?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Merkle Tree Models
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Node in a Merkle tree visualization.
|
||||
*/
|
||||
export interface MerkleTreeNode {
|
||||
readonly nodeId: string;
|
||||
readonly hash: string;
|
||||
readonly label?: string;
|
||||
readonly isLeaf: boolean;
|
||||
readonly isRoot: boolean;
|
||||
readonly children?: readonly MerkleTreeNode[];
|
||||
readonly level: number;
|
||||
readonly position: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete Merkle tree structure for visualization.
|
||||
*/
|
||||
export interface MerkleTree {
|
||||
readonly root: MerkleTreeNode;
|
||||
readonly leafCount: number;
|
||||
readonly depth: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Proof Bundle Models
|
||||
// ============================================================================
|
||||
|
||||
export type ProofVerificationStatus = 'verified' | 'failed' | 'pending' | 'unknown';
|
||||
export type SignatureStatus = 'valid' | 'invalid' | 'expired' | 'unknown';
|
||||
|
||||
/**
|
||||
* DSSE signature information.
|
||||
*/
|
||||
export interface DsseSignature {
|
||||
readonly keyId: string;
|
||||
readonly algorithm: string;
|
||||
readonly status: SignatureStatus;
|
||||
readonly signedAt?: string;
|
||||
readonly expiresAt?: string;
|
||||
readonly issuer?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rekor transparency log entry.
|
||||
*/
|
||||
export interface RekorLogEntry {
|
||||
readonly logId: string;
|
||||
readonly logIndex: number;
|
||||
readonly integratedTime: string;
|
||||
readonly logUrl: string;
|
||||
readonly bodyHash: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proof bundle containing all verification artifacts.
|
||||
*/
|
||||
export interface ProofBundle {
|
||||
readonly bundleId: string;
|
||||
readonly scanId: string;
|
||||
readonly createdAt: string;
|
||||
readonly merkleRoot: string;
|
||||
readonly dsseEnvelope: string; // Base64-encoded DSSE envelope
|
||||
readonly signatures: readonly DsseSignature[];
|
||||
readonly rekorEntry?: RekorLogEntry;
|
||||
readonly verificationStatus: ProofVerificationStatus;
|
||||
readonly downloadUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proof verification result from the backend.
|
||||
*/
|
||||
export interface ProofVerificationResult {
|
||||
readonly bundleId: string;
|
||||
readonly verified: boolean;
|
||||
readonly merkleRootValid: boolean;
|
||||
readonly signatureValid: boolean;
|
||||
readonly rekorInclusionValid?: boolean;
|
||||
readonly verifiedAt: string;
|
||||
readonly errors?: readonly string[];
|
||||
readonly warnings?: readonly string[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Score Replay Models
|
||||
// ============================================================================
|
||||
|
||||
export type ScoreReplayStatus = 'pending' | 'running' | 'completed' | 'failed';
|
||||
|
||||
/**
|
||||
* Score component with breakdown.
|
||||
*/
|
||||
export interface ScoreComponent {
|
||||
readonly name: string;
|
||||
readonly weight: number;
|
||||
readonly rawScore: number;
|
||||
readonly weightedScore: number;
|
||||
readonly details?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete score breakdown.
|
||||
*/
|
||||
export interface ScoreBreakdown {
|
||||
readonly totalScore: number;
|
||||
readonly components: readonly ScoreComponent[];
|
||||
readonly computedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Score comparison result showing drift between original and replayed.
|
||||
*/
|
||||
export interface ScoreDrift {
|
||||
readonly componentName: string;
|
||||
readonly originalScore: number;
|
||||
readonly replayedScore: number;
|
||||
readonly delta: number;
|
||||
readonly driftPercent: number;
|
||||
readonly significant: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Score replay request.
|
||||
*/
|
||||
export interface ScoreReplayRequest {
|
||||
readonly scanId: string;
|
||||
readonly useCurrentPolicy?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Score replay response with comparison.
|
||||
*/
|
||||
export interface ScoreReplayResult {
|
||||
readonly replayId: string;
|
||||
readonly scanId: string;
|
||||
readonly status: ScoreReplayStatus;
|
||||
readonly startedAt: string;
|
||||
readonly completedAt?: string;
|
||||
readonly originalScore: ScoreBreakdown;
|
||||
readonly replayedScore?: ScoreBreakdown;
|
||||
readonly drifts?: readonly ScoreDrift[];
|
||||
readonly hasDrift: boolean;
|
||||
readonly proofBundle?: ProofBundle;
|
||||
readonly error?: string;
|
||||
}
|
||||
192
src/Web/StellaOps.Web/src/app/core/api/reachability.models.ts
Normal file
192
src/Web/StellaOps.Web/src/app/core/api/reachability.models.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Reachability analysis models for Sprint 3500.0004.0002 - T6.
|
||||
* Supports the reachability explain widget with call graph visualization.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Call Graph Models
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Type of node in the call graph.
|
||||
*/
|
||||
export type CallGraphNodeType =
|
||||
| 'entrypoint'
|
||||
| 'method'
|
||||
| 'function'
|
||||
| 'class'
|
||||
| 'module'
|
||||
| 'vulnerable'
|
||||
| 'external';
|
||||
|
||||
/**
|
||||
* Node in the call graph.
|
||||
*/
|
||||
export interface CallGraphNode {
|
||||
readonly nodeId: string;
|
||||
readonly type: CallGraphNodeType;
|
||||
readonly name: string;
|
||||
readonly qualifiedName: string;
|
||||
readonly filePath?: string;
|
||||
readonly lineNumber?: number;
|
||||
readonly package?: string;
|
||||
readonly isVulnerable: boolean;
|
||||
readonly isEntrypoint: boolean;
|
||||
readonly metadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edge in the call graph representing a call relationship.
|
||||
*/
|
||||
export interface CallGraphEdge {
|
||||
readonly edgeId: string;
|
||||
readonly sourceId: string;
|
||||
readonly targetId: string;
|
||||
readonly callType: 'direct' | 'indirect' | 'dynamic' | 'virtual';
|
||||
readonly weight?: number;
|
||||
readonly confidence?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete call graph structure.
|
||||
*/
|
||||
export interface CallGraph {
|
||||
readonly graphId: string;
|
||||
readonly language: string;
|
||||
readonly nodes: readonly CallGraphNode[];
|
||||
readonly edges: readonly CallGraphEdge[];
|
||||
readonly nodeCount: number;
|
||||
readonly edgeCount: number;
|
||||
readonly createdAt: string;
|
||||
readonly digest: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Reachability Path Models
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Step in a reachability path.
|
||||
*/
|
||||
export interface ReachabilityPathStep {
|
||||
readonly stepIndex: number;
|
||||
readonly node: CallGraphNode;
|
||||
readonly callType?: 'direct' | 'indirect' | 'dynamic' | 'virtual';
|
||||
readonly confidence: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A reachability path from entrypoint to vulnerable function.
|
||||
*/
|
||||
export interface ReachabilityPath {
|
||||
readonly pathId: string;
|
||||
readonly entrypoint: CallGraphNode;
|
||||
readonly vulnerable: CallGraphNode;
|
||||
readonly steps: readonly ReachabilityPathStep[];
|
||||
readonly pathLength: number;
|
||||
readonly overallConfidence: number;
|
||||
readonly isShortestPath: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Confidence Scoring Models
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Factor contributing to reachability confidence.
|
||||
*/
|
||||
export interface ConfidenceFactor {
|
||||
readonly factorName: string;
|
||||
readonly weight: number;
|
||||
readonly score: number;
|
||||
readonly contribution: number;
|
||||
readonly description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete confidence breakdown.
|
||||
*/
|
||||
export interface ConfidenceBreakdown {
|
||||
readonly overallScore: number;
|
||||
readonly factors: readonly ConfidenceFactor[];
|
||||
readonly computedAt: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Reachability Explanation Models
|
||||
// ============================================================================
|
||||
|
||||
export type ReachabilityVerdict = 'reachable' | 'unreachable' | 'uncertain' | 'no_data';
|
||||
|
||||
/**
|
||||
* Reachability explanation for a CVE.
|
||||
*/
|
||||
export interface ReachabilityExplanation {
|
||||
readonly explanationId: string;
|
||||
readonly cveId: string;
|
||||
readonly vulnerablePackage: string;
|
||||
readonly vulnerableFunction?: string;
|
||||
readonly verdict: ReachabilityVerdict;
|
||||
readonly confidence: ConfidenceBreakdown;
|
||||
readonly paths: readonly ReachabilityPath[];
|
||||
readonly callGraph?: CallGraph;
|
||||
readonly entrypointsAnalyzed: number;
|
||||
readonly shortestPathLength?: number;
|
||||
readonly analysisTime: string;
|
||||
readonly createdAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request for reachability analysis.
|
||||
*/
|
||||
export interface ReachabilityAnalysisRequest {
|
||||
readonly scanId: string;
|
||||
readonly cveId: string;
|
||||
readonly includeCallGraph?: boolean;
|
||||
readonly maxPaths?: number;
|
||||
readonly maxDepth?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary of reachability analysis for a scan.
|
||||
*/
|
||||
export interface ReachabilitySummary {
|
||||
readonly scanId: string;
|
||||
readonly totalCves: number;
|
||||
readonly reachableCves: number;
|
||||
readonly unreachableCves: number;
|
||||
readonly uncertainCves: number;
|
||||
readonly noDataCves: number;
|
||||
readonly analysisCompletedAt?: string;
|
||||
readonly callGraphAvailable: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Visualization Export Models
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Export format for call graph visualization.
|
||||
*/
|
||||
export type ExportFormat = 'png' | 'svg' | 'json' | 'dot';
|
||||
|
||||
/**
|
||||
* Request to export call graph visualization.
|
||||
*/
|
||||
export interface ExportGraphRequest {
|
||||
readonly explanationId: string;
|
||||
readonly format: ExportFormat;
|
||||
readonly highlightPath?: string;
|
||||
readonly width?: number;
|
||||
readonly height?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export result.
|
||||
*/
|
||||
export interface ExportGraphResult {
|
||||
readonly format: ExportFormat;
|
||||
readonly dataUrl?: string; // For PNG/SVG
|
||||
readonly data?: string; // For JSON/DOT
|
||||
readonly filename: string;
|
||||
}
|
||||
163
src/Web/StellaOps.Web/src/app/core/api/unknowns.models.ts
Normal file
163
src/Web/StellaOps.Web/src/app/core/api/unknowns.models.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* Unknowns registry models for Sprint 3500.0004.0002 - T6.
|
||||
* Supports the unknowns queue component with band-based prioritization.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Band Classification
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Band classification for unknown packages.
|
||||
* HOT: Recently discovered, high occurrence, needs immediate attention
|
||||
* WARM: Moderate priority, stable occurrence
|
||||
* COLD: Low priority, rare or old unknowns
|
||||
*/
|
||||
export type UnknownBand = 'HOT' | 'WARM' | 'COLD';
|
||||
|
||||
/**
|
||||
* Status of an unknown package in the registry.
|
||||
*/
|
||||
export type UnknownStatus = 'pending' | 'escalated' | 'resolved' | 'ignored';
|
||||
|
||||
/**
|
||||
* Resolution action taken for an unknown.
|
||||
*/
|
||||
export type ResolutionAction =
|
||||
| 'mapped_to_cve'
|
||||
| 'marked_not_vulnerable'
|
||||
| 'added_to_allowlist'
|
||||
| 'false_positive'
|
||||
| 'vendor_confirmed'
|
||||
| 'other';
|
||||
|
||||
// ============================================================================
|
||||
// Unknown Entry Models
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Package information for an unknown.
|
||||
*/
|
||||
export interface UnknownPackage {
|
||||
readonly name: string;
|
||||
readonly version: string;
|
||||
readonly ecosystem: string; // npm, pypi, maven, nuget, etc.
|
||||
readonly purl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan occurrence information.
|
||||
*/
|
||||
export interface UnknownOccurrence {
|
||||
readonly scanId: string;
|
||||
readonly imageDigest: string;
|
||||
readonly imageName: string;
|
||||
readonly detectedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* An unknown entry in the registry.
|
||||
*/
|
||||
export interface UnknownEntry {
|
||||
readonly unknownId: string;
|
||||
readonly package: UnknownPackage;
|
||||
readonly band: UnknownBand;
|
||||
readonly status: UnknownStatus;
|
||||
readonly rank: number; // Priority rank within band
|
||||
readonly occurrenceCount: number;
|
||||
readonly firstSeenAt: string;
|
||||
readonly lastSeenAt: string;
|
||||
readonly ageInDays: number;
|
||||
readonly relatedCves?: readonly string[];
|
||||
readonly assignee?: string;
|
||||
readonly notes?: string;
|
||||
readonly recentOccurrences: readonly UnknownOccurrence[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// API Request/Response Models
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Filter options for listing unknowns.
|
||||
*/
|
||||
export interface UnknownsFilter {
|
||||
readonly band?: UnknownBand;
|
||||
readonly status?: UnknownStatus;
|
||||
readonly ecosystem?: string;
|
||||
readonly scanId?: string;
|
||||
readonly imageDigest?: string;
|
||||
readonly assignee?: string;
|
||||
readonly limit?: number;
|
||||
readonly offset?: number;
|
||||
readonly sortBy?: 'rank' | 'age' | 'occurrenceCount' | 'lastSeen';
|
||||
readonly sortOrder?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginated response for unknowns listing.
|
||||
*/
|
||||
export interface UnknownsListResponse {
|
||||
readonly items: readonly UnknownEntry[];
|
||||
readonly total: number;
|
||||
readonly limit: number;
|
||||
readonly offset: number;
|
||||
readonly hasMore: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary statistics for unknowns.
|
||||
*/
|
||||
export interface UnknownsSummary {
|
||||
readonly hotCount: number;
|
||||
readonly warmCount: number;
|
||||
readonly coldCount: number;
|
||||
readonly totalCount: number;
|
||||
readonly pendingCount: number;
|
||||
readonly escalatedCount: number;
|
||||
readonly resolvedToday: number;
|
||||
readonly oldestUnresolvedDays: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to escalate an unknown.
|
||||
*/
|
||||
export interface EscalateUnknownRequest {
|
||||
readonly unknownId: string;
|
||||
readonly reason: string;
|
||||
readonly assignTo?: string;
|
||||
readonly priority?: 'low' | 'medium' | 'high' | 'critical';
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to resolve an unknown.
|
||||
*/
|
||||
export interface ResolveUnknownRequest {
|
||||
readonly unknownId: string;
|
||||
readonly action: ResolutionAction;
|
||||
readonly mappedCve?: string;
|
||||
readonly notes?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk action request for multiple unknowns.
|
||||
*/
|
||||
export interface BulkUnknownsRequest {
|
||||
readonly unknownIds: readonly string[];
|
||||
readonly action: 'escalate' | 'resolve' | 'ignore' | 'assign';
|
||||
readonly resolutionAction?: ResolutionAction;
|
||||
readonly assignee?: string;
|
||||
readonly notes?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of a bulk action.
|
||||
*/
|
||||
export interface BulkUnknownsResult {
|
||||
readonly successCount: number;
|
||||
readonly failureCount: number;
|
||||
readonly failures?: readonly {
|
||||
readonly unknownId: string;
|
||||
readonly error: string;
|
||||
}[];
|
||||
}
|
||||
Reference in New Issue
Block a user