@mergesignal/shared 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/cardObservationCatalog.d.ts +55 -0
- package/dist/cardObservationCatalog.d.ts.map +1 -0
- package/dist/cardObservationCatalog.js +320 -0
- package/dist/cardSummaryCopy.d.ts +12 -0
- package/dist/cardSummaryCopy.d.ts.map +1 -0
- package/dist/cardSummaryCopy.js +36 -0
- package/dist/deriveCardDisplaySummary.d.ts +16 -0
- package/dist/deriveCardDisplaySummary.d.ts.map +1 -0
- package/dist/deriveCardDisplaySummary.js +56 -0
- package/dist/deriveCardDisplaySummary.test.d.ts +2 -0
- package/dist/deriveCardDisplaySummary.test.d.ts.map +1 -0
- package/dist/deriveCardDisplaySummary.test.js +89 -0
- package/dist/deriveCardOperationalObservations.d.ts +19 -0
- package/dist/deriveCardOperationalObservations.d.ts.map +1 -0
- package/dist/deriveCardOperationalObservations.js +180 -0
- package/dist/deriveCardOperationalObservations.test.d.ts +2 -0
- package/dist/deriveCardOperationalObservations.test.d.ts.map +1 -0
- package/dist/deriveCardOperationalObservations.test.js +268 -0
- package/dist/deriveRiskScoreSummary.d.ts +33 -0
- package/dist/deriveRiskScoreSummary.d.ts.map +1 -0
- package/dist/deriveRiskScoreSummary.js +90 -0
- package/dist/deriveRiskScoreSummary.test.d.ts +2 -0
- package/dist/deriveRiskScoreSummary.test.d.ts.map +1 -0
- package/dist/deriveRiskScoreSummary.test.js +47 -0
- package/dist/deriveScanDetailRecommendations.d.ts +63 -0
- package/dist/deriveScanDetailRecommendations.d.ts.map +1 -0
- package/dist/deriveScanDetailRecommendations.js +725 -0
- package/dist/deriveScanDetailRecommendations.test.d.ts +2 -0
- package/dist/deriveScanDetailRecommendations.test.d.ts.map +1 -0
- package/dist/deriveScanDetailRecommendations.test.js +181 -0
- package/dist/formatCardAreaLabels.d.ts +8 -0
- package/dist/formatCardAreaLabels.d.ts.map +1 -0
- package/dist/formatCardAreaLabels.js +65 -0
- package/dist/formatCardEvidenceCounts.d.ts +7 -0
- package/dist/formatCardEvidenceCounts.d.ts.map +1 -0
- package/dist/formatCardEvidenceCounts.js +19 -0
- package/dist/formatCardExposureDisplay.d.ts +24 -0
- package/dist/formatCardExposureDisplay.d.ts.map +1 -0
- package/dist/formatCardExposureDisplay.js +71 -0
- package/dist/formatCardExposureDisplay.test.d.ts +2 -0
- package/dist/formatCardExposureDisplay.test.d.ts.map +1 -0
- package/dist/formatCardExposureDisplay.test.js +36 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/productMessaging.d.ts +85 -0
- package/dist/productMessaging.d.ts.map +1 -0
- package/dist/productMessaging.js +109 -0
- package/dist/productMessaging.test.d.ts +2 -0
- package/dist/productMessaging.test.d.ts.map +1 -0
- package/dist/productMessaging.test.js +97 -0
- package/dist/riskVocabulary.d.ts +8 -2
- package/dist/riskVocabulary.d.ts.map +1 -1
- package/dist/riskVocabulary.js +26 -9
- package/dist/scanCardSummary.d.ts +9 -0
- package/dist/scanCardSummary.d.ts.map +1 -1
- package/dist/scanCardSummary.js +19 -29
- package/dist/scanCardSummary.test.js +45 -8
- package/dist/scanDetailViewModel.d.ts +108 -0
- package/dist/scanDetailViewModel.d.ts.map +1 -0
- package/dist/scanDetailViewModel.js +420 -0
- package/dist/scanDetailViewModel.test.d.ts +2 -0
- package/dist/scanDetailViewModel.test.d.ts.map +1 -0
- package/dist/scanDetailViewModel.test.js +503 -0
- package/dist/scanSurfaceCopy.d.ts +187 -0
- package/dist/scanSurfaceCopy.d.ts.map +1 -1
- package/dist/scanSurfaceCopy.js +190 -0
- package/dist/truncateCardSummary.d.ts +5 -0
- package/dist/truncateCardSummary.d.ts.map +1 -0
- package/dist/truncateCardSummary.js +23 -0
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { type ScanDetailRecommendationCenter } from "./deriveScanDetailRecommendations.js";
|
|
2
|
+
import { type ScanDetailSignalSummary } from "./deriveRiskScoreSummary.js";
|
|
3
|
+
import { type MergePosture } from "./riskVocabulary.js";
|
|
4
|
+
import type { FindingSeverity, ScanResult } from "./types.js";
|
|
5
|
+
export declare const ACT2_MAX_THEMES = 4;
|
|
6
|
+
export declare const ACT2_MAX_THEME_CHARS = 80;
|
|
7
|
+
export declare const VERDICT_LINE_MAX_CHARS = 120;
|
|
8
|
+
export declare const TIER1_MAX_VISIBLE_IMPACTS = 3;
|
|
9
|
+
export declare const ACT3_EVIDENCE_COLLAPSE_THRESHOLD = 10;
|
|
10
|
+
export type ScanDetailOperationalImpactItem = {
|
|
11
|
+
message: string;
|
|
12
|
+
where?: string;
|
|
13
|
+
verify?: string;
|
|
14
|
+
affectedFiles?: string[];
|
|
15
|
+
};
|
|
16
|
+
export type ScanDetailOperationalImpact = {
|
|
17
|
+
status: "rich" | "fallback" | "hidden";
|
|
18
|
+
items: ScanDetailOperationalImpactItem[];
|
|
19
|
+
fallbackMessage?: string;
|
|
20
|
+
};
|
|
21
|
+
export type ScanDetailVerdict = {
|
|
22
|
+
posture: MergePosture | null;
|
|
23
|
+
scopeChip: string | null;
|
|
24
|
+
verdictLine: string;
|
|
25
|
+
prLabel?: string;
|
|
26
|
+
statusLabel?: string;
|
|
27
|
+
};
|
|
28
|
+
export type ScanDetailAttentionPackage = {
|
|
29
|
+
name: string;
|
|
30
|
+
version?: string;
|
|
31
|
+
direct: boolean;
|
|
32
|
+
severity?: FindingSeverity;
|
|
33
|
+
evidence?: string;
|
|
34
|
+
};
|
|
35
|
+
export type ScanDetailAttentionArea = {
|
|
36
|
+
problemLabel: string;
|
|
37
|
+
problemDescription: string;
|
|
38
|
+
packages: ScanDetailAttentionPackage[];
|
|
39
|
+
overflowCount: number;
|
|
40
|
+
};
|
|
41
|
+
export type ScanDetailFindingRow = {
|
|
42
|
+
id: string;
|
|
43
|
+
severity: FindingSeverity;
|
|
44
|
+
title: string;
|
|
45
|
+
description: string;
|
|
46
|
+
packageName: string;
|
|
47
|
+
recommendation?: string;
|
|
48
|
+
source: "dependency" | "code";
|
|
49
|
+
coveredByRecommendationRank?: number;
|
|
50
|
+
};
|
|
51
|
+
export type ScanDetailTopology = {
|
|
52
|
+
summaryLine: string;
|
|
53
|
+
deepest: Array<{
|
|
54
|
+
packageName: string;
|
|
55
|
+
depth: number;
|
|
56
|
+
direct: boolean;
|
|
57
|
+
via: string[];
|
|
58
|
+
}>;
|
|
59
|
+
};
|
|
60
|
+
export type ScanDetailViewModel = {
|
|
61
|
+
verdict: ScanDetailVerdict;
|
|
62
|
+
signalSummary: ScanDetailSignalSummary | null;
|
|
63
|
+
followUpBridgeNote: string | null;
|
|
64
|
+
recommendedActions: ScanDetailRecommendationCenter;
|
|
65
|
+
operationalImpact: ScanDetailOperationalImpact;
|
|
66
|
+
because: {
|
|
67
|
+
themes: string[];
|
|
68
|
+
confidenceCaveat?: string;
|
|
69
|
+
} | null;
|
|
70
|
+
evidence: {
|
|
71
|
+
attentionAreas: ScanDetailAttentionArea[];
|
|
72
|
+
findings: ScanDetailFindingRow[];
|
|
73
|
+
findingsOverflowCount: number;
|
|
74
|
+
topology: ScanDetailTopology | null;
|
|
75
|
+
} | null;
|
|
76
|
+
repoContext: {
|
|
77
|
+
status: "hidden";
|
|
78
|
+
} | {
|
|
79
|
+
status: "loaded";
|
|
80
|
+
comparisonLine: string;
|
|
81
|
+
};
|
|
82
|
+
metadata: {
|
|
83
|
+
scanId: string;
|
|
84
|
+
generatedAt?: string;
|
|
85
|
+
methodologyVersion?: string | null;
|
|
86
|
+
changedPackagesSummary?: string;
|
|
87
|
+
codeAnalysisTimedOut?: boolean;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
export type DeriveScanDetailOptions = {
|
|
91
|
+
scanId: string;
|
|
92
|
+
status: "queued" | "running" | "done" | "failed";
|
|
93
|
+
methodologyVersion?: string | null;
|
|
94
|
+
prNumber?: number | null;
|
|
95
|
+
repoContext?: ScanDetailViewModel["repoContext"];
|
|
96
|
+
};
|
|
97
|
+
/** Bridge copy from Signal summary to Recommended actions. */
|
|
98
|
+
export declare function deriveFollowUpBridgeNote(actionCount: number): string | null;
|
|
99
|
+
/** Act 2 theme sanitizer — strips package/CVE/version content. */
|
|
100
|
+
export declare function sanitizeAct2Theme(raw: string): string | null;
|
|
101
|
+
/** Verdict line from posture + reach band (scanSurfaceCopy.scanDetail.verdictLine). */
|
|
102
|
+
export declare function deriveVerdictLine(posture: MergePosture | null, totalScore: number | null | undefined): string;
|
|
103
|
+
export declare function deriveAct2Themes(result: ScanResult): string[];
|
|
104
|
+
export declare function deriveOperationalImpact(result: ScanResult, verifyLabels?: Set<string>): ScanDetailOperationalImpact;
|
|
105
|
+
export declare function deriveScanDetailViewModel(result: ScanResult | null | undefined, options: DeriveScanDetailOptions): ScanDetailViewModel | null;
|
|
106
|
+
/** Tier 1 recall test helper — true when message describes application-level impact. */
|
|
107
|
+
export declare function tier1PassesRecallTest(operationalImpact: ScanDetailOperationalImpact): boolean;
|
|
108
|
+
//# sourceMappingURL=scanDetailViewModel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanDetailViewModel.d.ts","sourceRoot":"","sources":["../src/scanDetailViewModel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { humanizeEngineSurfaceText, layerRiskBandLabel, sortPRInsightsForDisplay, truncateWithEllipsis, } from "./actionsStepSummary.js";
|
|
2
|
+
import { mapFindingToCatalogPhrase, mapGraphInsightsToCatalogPhrases, phraseForFamily, } from "./cardObservationCatalog.js";
|
|
3
|
+
import { deriveCardOperationalObservations } from "./deriveCardOperationalObservations.js";
|
|
4
|
+
import { deriveScanDetailRecommendations, recommendationTitlesForDedupe, } from "./deriveScanDetailRecommendations.js";
|
|
5
|
+
import { deriveSignalSummary, } from "./deriveRiskScoreSummary.js";
|
|
6
|
+
import { deriveCardExposureDisplay, deriveDetailReachChip, } from "./formatCardExposureDisplay.js";
|
|
7
|
+
import { formatInsight } from "./formatInsight.js";
|
|
8
|
+
import { mergePostureFromDecision, } from "./riskVocabulary.js";
|
|
9
|
+
import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
|
|
10
|
+
export const ACT2_MAX_THEMES = 4;
|
|
11
|
+
export const ACT2_MAX_THEME_CHARS = 80;
|
|
12
|
+
export const VERDICT_LINE_MAX_CHARS = 120;
|
|
13
|
+
export const TIER1_MAX_VISIBLE_IMPACTS = 3;
|
|
14
|
+
export const ACT3_EVIDENCE_COLLAPSE_THRESHOLD = 10;
|
|
15
|
+
const ACT2_FORBIDDEN = /CVE-\d|@\d|\d+\.\d+\.\d+|[a-z0-9@./+-]+\/[a-z0-9@./+-]+/i;
|
|
16
|
+
const SEVERITY_ORDER = {
|
|
17
|
+
critical: 4,
|
|
18
|
+
high: 3,
|
|
19
|
+
medium: 2,
|
|
20
|
+
low: 1,
|
|
21
|
+
};
|
|
22
|
+
const LAYER_ORDER = [
|
|
23
|
+
"security",
|
|
24
|
+
"maintainability",
|
|
25
|
+
"ecosystem",
|
|
26
|
+
"upgradeImpact",
|
|
27
|
+
];
|
|
28
|
+
/** Bridge copy from Signal summary to Recommended actions. */
|
|
29
|
+
export function deriveFollowUpBridgeNote(actionCount) {
|
|
30
|
+
if (actionCount <= 0)
|
|
31
|
+
return null;
|
|
32
|
+
const copy = scanSurfaceCopy.scanDetail.signalSummary;
|
|
33
|
+
if (actionCount === 1)
|
|
34
|
+
return copy.followUpImprovementIdentified;
|
|
35
|
+
return copy.followUpImprovementsIdentified.replace("{count}", String(actionCount));
|
|
36
|
+
}
|
|
37
|
+
/** Act 2 theme sanitizer — strips package/CVE/version content. */
|
|
38
|
+
export function sanitizeAct2Theme(raw) {
|
|
39
|
+
const trimmed = raw.trim();
|
|
40
|
+
if (!trimmed || ACT2_FORBIDDEN.test(trimmed))
|
|
41
|
+
return null;
|
|
42
|
+
return truncateWithEllipsis(trimmed, ACT2_MAX_THEME_CHARS);
|
|
43
|
+
}
|
|
44
|
+
function reachBand(category) {
|
|
45
|
+
if (!category || category === "minimal" || category === "limited")
|
|
46
|
+
return "narrow";
|
|
47
|
+
if (category === "moderate")
|
|
48
|
+
return "moderate";
|
|
49
|
+
return "wide";
|
|
50
|
+
}
|
|
51
|
+
/** Verdict line from posture + reach band (scanSurfaceCopy.scanDetail.verdictLine). */
|
|
52
|
+
export function deriveVerdictLine(posture, totalScore) {
|
|
53
|
+
const copy = scanSurfaceCopy.scanDetail.verdictLine;
|
|
54
|
+
const category = deriveCardExposureDisplay(totalScore)?.category;
|
|
55
|
+
const band = reachBand(category);
|
|
56
|
+
if (posture === "safe") {
|
|
57
|
+
if (band === "wide")
|
|
58
|
+
return copy.safeNoBlockersWide;
|
|
59
|
+
if (band === "moderate")
|
|
60
|
+
return copy.safeNoBlockersModerate;
|
|
61
|
+
return copy.safeNoBlockersNarrow;
|
|
62
|
+
}
|
|
63
|
+
if (posture === "risky") {
|
|
64
|
+
if (band === "wide")
|
|
65
|
+
return copy.riskyResolveWide;
|
|
66
|
+
if (band === "moderate")
|
|
67
|
+
return copy.riskyResolveModerate;
|
|
68
|
+
return copy.riskyResolveNarrow;
|
|
69
|
+
}
|
|
70
|
+
if (posture === "needs_review") {
|
|
71
|
+
if (band === "wide")
|
|
72
|
+
return copy.reviewBeforeMergeWide;
|
|
73
|
+
if (band === "moderate")
|
|
74
|
+
return copy.reviewBeforeMergeModerate;
|
|
75
|
+
return copy.reviewBeforeMergeNarrow;
|
|
76
|
+
}
|
|
77
|
+
return scanSurfaceCopy.pipeline.scanIncomplete;
|
|
78
|
+
}
|
|
79
|
+
function themeFromCatalogPhrase(phrase) {
|
|
80
|
+
const map = {
|
|
81
|
+
"Vulnerable transitive packages detected": "Known vulnerabilities in the transitive tree",
|
|
82
|
+
"Transitive dependency hotspots detected": "Shared packages concentrate dependency risk",
|
|
83
|
+
"Duplicate dependency versions detected": "Overlapping dependency paths detected",
|
|
84
|
+
"Large upgrade blast radius": "Large upgrade footprint across the graph",
|
|
85
|
+
"High transitive dependency volume": "High transitive dependency volume in the graph",
|
|
86
|
+
"Deep indirect dependency chains": "Deep indirect dependency chains",
|
|
87
|
+
"Broad package surface area": "Broad declared package footprint",
|
|
88
|
+
"Stale ecosystem dependencies detected": "Stale ecosystem dependencies detected",
|
|
89
|
+
"Overlapping dependency paths detected": "Overlapping dependency paths detected",
|
|
90
|
+
};
|
|
91
|
+
return map[phrase] ?? phrase;
|
|
92
|
+
}
|
|
93
|
+
function collectAct2ThemeCandidates(result) {
|
|
94
|
+
const candidates = [];
|
|
95
|
+
const posture = mergePostureFromDecision(result.decision?.recommendation);
|
|
96
|
+
const observations = deriveCardOperationalObservations(result, {
|
|
97
|
+
mergePosture: posture,
|
|
98
|
+
hasFullResult: true,
|
|
99
|
+
max: ACT2_MAX_THEMES + 2,
|
|
100
|
+
});
|
|
101
|
+
for (const phrase of observations.operationalObservations) {
|
|
102
|
+
candidates.push(themeFromCatalogPhrase(phrase));
|
|
103
|
+
}
|
|
104
|
+
for (const phrase of mapGraphInsightsToCatalogPhrases(result.graphInsights)) {
|
|
105
|
+
candidates.push(themeFromCatalogPhrase(phrase.phrase));
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(result.decision?.reasoning)) {
|
|
108
|
+
for (const reason of result.decision.reasoning) {
|
|
109
|
+
const humanized = humanizeEngineSurfaceText(reason);
|
|
110
|
+
if (humanized)
|
|
111
|
+
candidates.push(humanized);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const layer of LAYER_ORDER) {
|
|
115
|
+
const layerScore = result.layerScores?.[layer];
|
|
116
|
+
const band = layerRiskBandLabel(layerScore);
|
|
117
|
+
if (band !== "Moderate" && band !== "High")
|
|
118
|
+
continue;
|
|
119
|
+
const drivers = humanizeEngineSurfaceText(`${layer} ${band.toLowerCase()} contribution`);
|
|
120
|
+
if (drivers)
|
|
121
|
+
candidates.push(`${layer} dimension elevated`);
|
|
122
|
+
}
|
|
123
|
+
return candidates;
|
|
124
|
+
}
|
|
125
|
+
export function deriveAct2Themes(result) {
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
const themes = [];
|
|
128
|
+
for (const candidate of collectAct2ThemeCandidates(result)) {
|
|
129
|
+
const sanitized = sanitizeAct2Theme(candidate);
|
|
130
|
+
if (!sanitized)
|
|
131
|
+
continue;
|
|
132
|
+
const key = sanitized.toLowerCase();
|
|
133
|
+
if (seen.has(key))
|
|
134
|
+
continue;
|
|
135
|
+
seen.add(key);
|
|
136
|
+
themes.push(sanitized);
|
|
137
|
+
if (themes.length >= ACT2_MAX_THEMES)
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
return themes;
|
|
141
|
+
}
|
|
142
|
+
function insightToImpactItem(insight, verifyLabels) {
|
|
143
|
+
const formatted = formatInsight(insight);
|
|
144
|
+
const verify = formatted.action &&
|
|
145
|
+
!verifyLabels.has(formatted.action.toLowerCase().replace(/\s+/g, " ").trim())
|
|
146
|
+
? formatted.action
|
|
147
|
+
: undefined;
|
|
148
|
+
return {
|
|
149
|
+
message: formatted.message,
|
|
150
|
+
where: formatted.where ?? undefined,
|
|
151
|
+
verify,
|
|
152
|
+
affectedFiles: insight.affectedFiles?.length
|
|
153
|
+
? insight.affectedFiles
|
|
154
|
+
: undefined,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
export function deriveOperationalImpact(result, verifyLabels = new Set()) {
|
|
158
|
+
const insights = sortPRInsightsForDisplay(Array.isArray(result.insights) ? result.insights : []);
|
|
159
|
+
const copy = scanSurfaceCopy.scanDetail.tier1Fallback;
|
|
160
|
+
const posture = mergePostureFromDecision(result.decision?.recommendation);
|
|
161
|
+
const reachCategory = deriveCardExposureDisplay(result.totalScore)?.category;
|
|
162
|
+
if (insights.length > 0) {
|
|
163
|
+
return {
|
|
164
|
+
status: "rich",
|
|
165
|
+
items: insights.map((insight) => insightToImpactItem(insight, verifyLabels)),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const isQuietSafe = posture === "safe" &&
|
|
169
|
+
(reachCategory === "minimal" || reachCategory === "limited");
|
|
170
|
+
if (result.reportPresentation?.mode === "lightweight_pr_graph_baseline") {
|
|
171
|
+
return {
|
|
172
|
+
status: "fallback",
|
|
173
|
+
items: [],
|
|
174
|
+
fallbackMessage: copy.lightweightPr,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const hasGraphProof = (result.graphInsights?.vulnerable?.length ?? 0) > 0 ||
|
|
178
|
+
(result.graphInsights?.hotspots?.length ?? 0) > 0 ||
|
|
179
|
+
(result.findings?.length ?? 0) > 0;
|
|
180
|
+
if (hasGraphProof && posture !== "safe") {
|
|
181
|
+
return {
|
|
182
|
+
status: "fallback",
|
|
183
|
+
items: [],
|
|
184
|
+
fallbackMessage: copy.structuralProofBelow,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (isQuietSafe) {
|
|
188
|
+
return {
|
|
189
|
+
status: "fallback",
|
|
190
|
+
items: [],
|
|
191
|
+
fallbackMessage: copy.quietSafe,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
status: "fallback",
|
|
196
|
+
items: [],
|
|
197
|
+
fallbackMessage: copy.structuralComplete,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function graphInsightToPackage(item, severity, evidence) {
|
|
201
|
+
return {
|
|
202
|
+
name: item.packageName,
|
|
203
|
+
version: item.version,
|
|
204
|
+
direct: item.direct,
|
|
205
|
+
severity,
|
|
206
|
+
evidence,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function buildAttentionAreas(result) {
|
|
210
|
+
const areas = [];
|
|
211
|
+
const gi = result.graphInsights;
|
|
212
|
+
const vulnerable = Array.isArray(gi?.vulnerable) ? gi.vulnerable : [];
|
|
213
|
+
if (vulnerable.length > 0) {
|
|
214
|
+
const packages = vulnerable.map((v) => graphInsightToPackage(v));
|
|
215
|
+
areas.push({
|
|
216
|
+
problemLabel: phraseForFamily("vulnerable_transitive"),
|
|
217
|
+
problemDescription: "Packages with known security advisories in the dependency tree.",
|
|
218
|
+
packages: packages.slice(0, ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
219
|
+
overflowCount: Math.max(0, packages.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const hotspots = Array.isArray(gi?.hotspots) ? gi.hotspots : [];
|
|
223
|
+
if (hotspots.length > 0) {
|
|
224
|
+
const packages = hotspots.map((h) => graphInsightToPackage(h));
|
|
225
|
+
areas.push({
|
|
226
|
+
problemLabel: phraseForFamily("transitive_hotspots"),
|
|
227
|
+
problemDescription: "Packages many dependency paths converge on.",
|
|
228
|
+
packages: packages.slice(0, ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
229
|
+
overflowCount: Math.max(0, packages.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
const findings = Array.isArray(result.findings) ? result.findings : [];
|
|
233
|
+
const duplicateFindings = findings.filter((f) => {
|
|
234
|
+
const mapped = mapFindingToCatalogPhrase(f);
|
|
235
|
+
return mapped?.family === "duplicate_versions";
|
|
236
|
+
});
|
|
237
|
+
if (duplicateFindings.length > 0) {
|
|
238
|
+
const packages = duplicateFindings.map((f) => ({
|
|
239
|
+
name: f.packageName,
|
|
240
|
+
direct: true,
|
|
241
|
+
severity: f.severity,
|
|
242
|
+
}));
|
|
243
|
+
areas.push({
|
|
244
|
+
problemLabel: phraseForFamily("duplicate_versions"),
|
|
245
|
+
problemDescription: "Multiple resolved versions of the same dependency.",
|
|
246
|
+
packages: packages.slice(0, ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
247
|
+
overflowCount: Math.max(0, packages.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
const changed = Array.isArray(result.changedPackages)
|
|
251
|
+
? result.changedPackages
|
|
252
|
+
: [];
|
|
253
|
+
if (changed.length >= 8) {
|
|
254
|
+
const packages = changed
|
|
255
|
+
.slice(0, ACT3_EVIDENCE_COLLAPSE_THRESHOLD)
|
|
256
|
+
.map((name) => ({
|
|
257
|
+
name,
|
|
258
|
+
direct: true,
|
|
259
|
+
}));
|
|
260
|
+
areas.push({
|
|
261
|
+
problemLabel: phraseForFamily("blast_radius"),
|
|
262
|
+
problemDescription: "Many packages changed in this upgrade.",
|
|
263
|
+
packages,
|
|
264
|
+
overflowCount: Math.max(0, changed.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return areas;
|
|
268
|
+
}
|
|
269
|
+
function findingToRow(f, source) {
|
|
270
|
+
return {
|
|
271
|
+
id: f.id,
|
|
272
|
+
severity: f.severity,
|
|
273
|
+
title: f.title,
|
|
274
|
+
description: f.description,
|
|
275
|
+
packageName: f.packageName,
|
|
276
|
+
recommendation: f.recommendation,
|
|
277
|
+
source,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function sortFindings(findings) {
|
|
281
|
+
return [...findings].sort((a, b) => {
|
|
282
|
+
const sd = SEVERITY_ORDER[b.severity] - SEVERITY_ORDER[a.severity];
|
|
283
|
+
if (sd !== 0)
|
|
284
|
+
return sd;
|
|
285
|
+
return a.packageName.localeCompare(b.packageName);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function buildFindingRows(result) {
|
|
289
|
+
const rows = [];
|
|
290
|
+
for (const f of Array.isArray(result.findings) ? result.findings : []) {
|
|
291
|
+
rows.push(findingToRow(f, "dependency"));
|
|
292
|
+
}
|
|
293
|
+
return sortFindings(rows);
|
|
294
|
+
}
|
|
295
|
+
function buildTopology(result) {
|
|
296
|
+
const gi = result.graphInsights;
|
|
297
|
+
if (!gi)
|
|
298
|
+
return null;
|
|
299
|
+
const nodes = typeof gi.nodes === "number" ? gi.nodes : 0;
|
|
300
|
+
const edges = typeof gi.edges === "number" ? gi.edges : 0;
|
|
301
|
+
const maxDepth = typeof gi.maxDepth === "number" ? gi.maxDepth : 0;
|
|
302
|
+
if (nodes === 0 && edges === 0 && maxDepth === 0)
|
|
303
|
+
return null;
|
|
304
|
+
const deepest = Array.isArray(gi.deepest)
|
|
305
|
+
? gi.deepest.slice(0, 5).map((d) => ({
|
|
306
|
+
packageName: d.packageName,
|
|
307
|
+
depth: d.depth,
|
|
308
|
+
direct: d.direct,
|
|
309
|
+
via: Array.isArray(d.via) ? d.via : [],
|
|
310
|
+
}))
|
|
311
|
+
: [];
|
|
312
|
+
return {
|
|
313
|
+
summaryLine: `${nodes} packages · ${edges} edges · max depth ${maxDepth}`,
|
|
314
|
+
deepest,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function annotateFindingsWithCoverage(rows, center) {
|
|
318
|
+
const rankByFindingId = new Map();
|
|
319
|
+
for (const item of center.items) {
|
|
320
|
+
for (const findingId of item.proofRefs?.findingIds ?? []) {
|
|
321
|
+
if (!rankByFindingId.has(findingId)) {
|
|
322
|
+
rankByFindingId.set(findingId, item.rank);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return rows.map((row) => {
|
|
327
|
+
const rank = rankByFindingId.get(row.id);
|
|
328
|
+
if (!rank)
|
|
329
|
+
return row;
|
|
330
|
+
return {
|
|
331
|
+
...row,
|
|
332
|
+
recommendation: undefined,
|
|
333
|
+
coveredByRecommendationRank: rank,
|
|
334
|
+
};
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function deriveConfidenceCaveat(result) {
|
|
338
|
+
const confidence = result.decision?.confidence;
|
|
339
|
+
if (confidence === "medium") {
|
|
340
|
+
return scanSurfaceCopy.scanDetail.confidenceCaveat.medium;
|
|
341
|
+
}
|
|
342
|
+
if (confidence === "low") {
|
|
343
|
+
return scanSurfaceCopy.scanDetail.confidenceCaveat.low;
|
|
344
|
+
}
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
function changedPackagesSummary(changed) {
|
|
348
|
+
if (!changed?.length)
|
|
349
|
+
return undefined;
|
|
350
|
+
if (changed.length === 1)
|
|
351
|
+
return changed[0];
|
|
352
|
+
return `${changed[0]} +${changed.length - 1} changed`;
|
|
353
|
+
}
|
|
354
|
+
export function deriveScanDetailViewModel(result, options) {
|
|
355
|
+
if (!result || options.status !== "done")
|
|
356
|
+
return null;
|
|
357
|
+
const posture = mergePostureFromDecision(result.decision?.recommendation);
|
|
358
|
+
const scopeChip = deriveDetailReachChip(result.totalScore);
|
|
359
|
+
const verdictLine = truncateWithEllipsis(deriveVerdictLine(posture, result.totalScore), VERDICT_LINE_MAX_CHARS);
|
|
360
|
+
const recommendedActions = deriveScanDetailRecommendations(result);
|
|
361
|
+
const signalSummary = deriveSignalSummary(result);
|
|
362
|
+
const verifyLabels = recommendationTitlesForDedupe(recommendedActions);
|
|
363
|
+
const operationalImpact = deriveOperationalImpact(result, verifyLabels);
|
|
364
|
+
const themes = deriveAct2Themes(result);
|
|
365
|
+
const confidenceCaveat = deriveConfidenceCaveat(result);
|
|
366
|
+
const because = themes.length > 0 || confidenceCaveat
|
|
367
|
+
? {
|
|
368
|
+
themes,
|
|
369
|
+
confidenceCaveat,
|
|
370
|
+
}
|
|
371
|
+
: null;
|
|
372
|
+
const attentionAreas = buildAttentionAreas(result);
|
|
373
|
+
const allFindings = annotateFindingsWithCoverage(buildFindingRows(result), recommendedActions);
|
|
374
|
+
const findings = allFindings;
|
|
375
|
+
const topology = buildTopology(result);
|
|
376
|
+
const hasEvidence = attentionAreas.length > 0 || allFindings.length > 0 || topology !== null;
|
|
377
|
+
const evidence = hasEvidence
|
|
378
|
+
? {
|
|
379
|
+
attentionAreas,
|
|
380
|
+
findings,
|
|
381
|
+
findingsOverflowCount: Math.max(0, allFindings.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
382
|
+
topology,
|
|
383
|
+
}
|
|
384
|
+
: null;
|
|
385
|
+
return {
|
|
386
|
+
verdict: {
|
|
387
|
+
posture,
|
|
388
|
+
scopeChip,
|
|
389
|
+
verdictLine,
|
|
390
|
+
prLabel: options.prNumber != null ? `#${options.prNumber}` : undefined,
|
|
391
|
+
},
|
|
392
|
+
signalSummary,
|
|
393
|
+
followUpBridgeNote: deriveFollowUpBridgeNote(recommendedActions.items.length),
|
|
394
|
+
recommendedActions,
|
|
395
|
+
operationalImpact,
|
|
396
|
+
because,
|
|
397
|
+
evidence,
|
|
398
|
+
repoContext: options.repoContext ?? { status: "hidden" },
|
|
399
|
+
metadata: {
|
|
400
|
+
scanId: options.scanId,
|
|
401
|
+
generatedAt: result.generatedAt,
|
|
402
|
+
methodologyVersion: options.methodologyVersion,
|
|
403
|
+
changedPackagesSummary: changedPackagesSummary(result.changedPackages),
|
|
404
|
+
codeAnalysisTimedOut: result.codeAnalysisMetrics?.timedOut === true,
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
/** Tier 1 recall test helper — true when message describes application-level impact. */
|
|
409
|
+
export function tier1PassesRecallTest(operationalImpact) {
|
|
410
|
+
if (operationalImpact.status === "rich") {
|
|
411
|
+
return operationalImpact.items.some((item) => item.message.trim().length > 10);
|
|
412
|
+
}
|
|
413
|
+
if (operationalImpact.status === "fallback" &&
|
|
414
|
+
operationalImpact.fallbackMessage) {
|
|
415
|
+
return (operationalImpact.fallbackMessage.includes("Dependency") ||
|
|
416
|
+
operationalImpact.fallbackMessage.includes("runtime") ||
|
|
417
|
+
operationalImpact.fallbackMessage.includes("application"));
|
|
418
|
+
}
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanDetailViewModel.test.d.ts","sourceRoot":"","sources":["../src/scanDetailViewModel.test.ts"],"names":[],"mappings":""}
|