2727
2828from socketsecurity import USER_AGENT , __version__
2929from socketsecurity .core .classes import Alert , Diff , FullScan , Issue , Package , Purl
30+ from socketsecurity .core .diff_result import (
31+ DiffScanResult ,
32+ PackageComparisonResult ,
33+ create_diff_scan_result ,
34+ )
3035from socketsecurity .core .exceptions import APIResourceNotFound
3136
3237from .resource_utils import check_file_count_against_ulimit
@@ -1313,7 +1318,7 @@ def create_full_scan_with_report_url(
13131318 base_socket = "https://socket.dev/dashboard/org"
13141319 diff .report_url = f"{ base_socket } /{ self .config .org_slug } /sbom/{ new_full_scan .id } "
13151320 diff .diff_url = diff .report_url
1316- diff .id = new_full_scan .id
1321+ diff .set_scan_ids ( new_full_scan .id )
13171322 diff .is_full_scan = True
13181323
13191324 needs_alerts = (
@@ -1957,12 +1962,12 @@ def get_license_text_via_purl(self, packages: dict[str, Package], batch_size: in
19571962
19581963 return packages
19591964
1960- def get_diff_scan_artifacts (
1965+ def _get_diff_scan_result (
19611966 self ,
19621967 head_full_scan_id : str ,
19631968 new_full_scan_id : str ,
19641969 external_href : Optional [str ] = None
1965- ) -> DiffArtifacts :
1970+ ) -> DiffScanResult :
19661971 """Compare two full scans via the diff-scans endpoints, polling for the result.
19671972
19681973 Creates a diff-scan resource from the two full scan IDs, then polls
@@ -2110,10 +2115,19 @@ def get_diff_scan_artifacts(
21102115 last_interval = interval
21112116 interval = min (interval * DIFF_SCAN_POLL_BACKOFF_MULTIPLIER , DIFF_SCAN_POLL_MAX_INTERVAL_SECONDS )
21122117
2113- return DiffArtifacts .from_dict ({
2114- key : artifacts_dict .get (key ) or []
2115- for key in ("added" , "removed" , "unchanged" , "replaced" , "updated" )
2116- })
2118+ return create_diff_scan_result (artifacts_dict , diff_scan_id )
2119+
2120+ def get_diff_scan_artifacts (
2121+ self ,
2122+ head_full_scan_id : str ,
2123+ new_full_scan_id : str ,
2124+ external_href : Optional [str ] = None
2125+ ) -> DiffArtifacts :
2126+ return self ._get_diff_scan_result (
2127+ head_full_scan_id ,
2128+ new_full_scan_id ,
2129+ external_href = external_href ,
2130+ ).artifacts
21172131
21182132 def _requires_unchanged_artifacts (self ) -> bool :
21192133 """Whether any enabled output reads the unchanged half of a comparison.
@@ -2148,13 +2162,13 @@ def _requires_unchanged_artifacts(self) -> bool:
21482162 or getattr (config , "legal_format" , "socket" ) == "fossa"
21492163 )
21502164
2151- def get_added_and_removed_packages (
2165+ def _get_package_comparison (
21522166 self ,
21532167 head_full_scan_id : str ,
21542168 new_full_scan_id : str ,
21552169 include_license_details : bool = False ,
21562170 external_href : Optional [str ] = None
2157- ) -> Tuple [ Dict [ str , Package ], Dict [ str , Package ], Dict [ str , Package ]] :
2171+ ) -> PackageComparisonResult :
21582172 """
21592173 Get packages that were added and removed between scans.
21602174
@@ -2190,14 +2204,14 @@ def get_added_and_removed_packages(
21902204 with the primary diff-scan resource
21912205
21922206 Returns:
2193- Tuple of (added_packages, removed_packages) dictionaries
2207+ Added, removed, and all-package maps plus the completed diff ID
21942208 """
21952209
21962210 log .info (f"Comparing scans - Head scan ID: { head_full_scan_id } , New scan ID: { new_full_scan_id } " )
21972211 diff_start = time .time ()
2198- diff_artifacts = None
2212+ comparison = None
21992213 try :
2200- diff_artifacts = self .get_diff_scan_artifacts (
2214+ comparison = self ._get_diff_scan_result (
22012215 head_full_scan_id ,
22022216 new_full_scan_id ,
22032217 external_href = external_href ,
@@ -2215,7 +2229,7 @@ def get_added_and_removed_packages(
22152229 f"reason={ type (error ).__name__ } "
22162230 )
22172231
2218- if diff_artifacts is None :
2232+ if comparison is None :
22192233 try :
22202234 diff_artifacts = (
22212235 self .sdk .fullscans .stream_diff (
@@ -2226,6 +2240,10 @@ def get_added_and_removed_packages(
22262240 include_license_details = str (include_license_details ).lower ()
22272241 ).data .artifacts
22282242 )
2243+ comparison = DiffScanResult (
2244+ artifacts = diff_artifacts ,
2245+ diff_scan_id = None ,
2246+ )
22292247 except APIFailure as e :
22302248 log .error (f"API Error: { e } " )
22312249 # API failures are mapped to the configured infrastructure exit code by cli().
@@ -2245,7 +2263,7 @@ def get_added_and_removed_packages(
22452263 marker_found = False
22462264 buckets : Dict [str , List ] = {}
22472265 for name in ("added" , "removed" , "unchanged" , "replaced" , "updated" ):
2248- bucket = getattr (diff_artifacts , name )
2266+ bucket = getattr (comparison . artifacts , name )
22492267 buckets [name ] = [a for a in bucket if not Core .is_invalid_facts_marker (a )]
22502268 marker_found = marker_found or len (buckets [name ]) != len (bucket )
22512269 Core .warn_if_invalid_facts_marker (marker_found )
@@ -2304,7 +2322,27 @@ def get_added_and_removed_packages(
23042322 else :
23052323 log .debug ("Skipping PURL endpoint call (--generate-license not set)" )
23062324
2307- return added_packages , removed_packages , packages
2325+ return PackageComparisonResult (
2326+ added_packages = added_packages ,
2327+ removed_packages = removed_packages ,
2328+ packages = packages ,
2329+ diff_scan_id = comparison .diff_scan_id ,
2330+ )
2331+
2332+ def get_added_and_removed_packages (
2333+ self ,
2334+ head_full_scan_id : str ,
2335+ new_full_scan_id : str ,
2336+ include_license_details : bool = False ,
2337+ external_href : Optional [str ] = None
2338+ ) -> Tuple [Dict [str , Package ], Dict [str , Package ], Dict [str , Package ]]:
2339+ result = self ._get_package_comparison (
2340+ head_full_scan_id ,
2341+ new_full_scan_id ,
2342+ include_license_details = include_license_details ,
2343+ external_href = external_href ,
2344+ )
2345+ return result .added_packages , result .removed_packages , result .packages
23082346
23092347 def create_new_diff (
23102348 self ,
@@ -2456,16 +2494,15 @@ def create_new_diff(
24562494 # the response and risks the truncation crash on large repos. The
24572495 # user flag still controls the dashboard report URL below; it just no
24582496 # longer gates this internal diff payload.
2459- (
2460- added_packages ,
2461- removed_packages ,
2462- packages
2463- ) = self .get_added_and_removed_packages (
2497+ comparison = self ._get_package_comparison (
24642498 head_full_scan_id ,
24652499 new_full_scan .id ,
24662500 include_license_details = False ,
24672501 external_href = external_href ,
24682502 )
2503+ added_packages = comparison .added_packages
2504+ removed_packages = comparison .removed_packages
2505+ packages = comparison .packages
24692506
24702507 # Separate unchanged packages from added/removed for --strict-blocking support
24712508 unchanged_packages = {
@@ -2477,13 +2514,16 @@ def create_new_diff(
24772514 diff .packages = packages
24782515
24792516 base_socket = "https://socket.dev/dashboard/org"
2480- diff .id = new_full_scan .id
2517+ diff .set_scan_ids (
2518+ new_full_scan .id ,
2519+ old_scan_id = head_full_scan_id ,
2520+ diff_scan_id = comparison .diff_scan_id ,
2521+ )
24812522
24822523 report_url = f"{ base_socket } /{ self .config .org_slug } /sbom/{ diff .id } "
24832524 if not params .include_license_details :
24842525 report_url += "?include_license_details=false"
24852526 diff .report_url = report_url
2486- diff .new_scan_id = new_full_scan .id
24872527
24882528 if head_full_scan_id is not None :
24892529 diff .diff_url = f"{ base_socket } /{ self .config .org_slug } /diff/{ head_full_scan_id } /{ diff .id } "
0 commit comments