Browse Source

feat(source-map-debug): Add improved source map debug endpoint (blue thunder edition) (#55909)

Luca Forstner 1 year ago
parent
commit
d4567ef60c

+ 2 - 1
.github/CODEOWNERS

@@ -418,7 +418,8 @@ yarn.lock                                                @getsentry/owners-js-de
 
 
 ## SDK
-/src/sentry/utils/sdk.py   @getsentry/team-web-sdk-backend
+/src/sentry/utils/sdk.py                                             @getsentry/team-web-sdk-backend
+/src/sentry/api/endpoints/source_map_debug_blue_thunder_edition.py   @getsentry/team-web-sdk-frontend
 ## End of SDK
 
 

+ 1 - 0
src/sentry/api/api_owners.py

@@ -20,5 +20,6 @@ class ApiOwner(Enum):
     OWNERS_INGEST = "owners-ingest"
     OWNERS_NATIVE = "owners-native"
     REPLAY = "replay-backend"
+    WEB_FRONTEND_SDKS = "team-web-sdk-frontend"
     FEEDBACK = "feedback-backend"
     UNOWNED = "unowned"

+ 494 - 0
src/sentry/api/endpoints/source_map_debug_blue_thunder_edition.py

@@ -0,0 +1,494 @@
+from typing import List, Literal, Optional
+
+import sentry_sdk
+from django.utils.encoding import force_bytes, force_str
+from drf_spectacular.utils import extend_schema
+from packaging.version import Version
+from rest_framework.exceptions import NotFound
+from rest_framework.request import Request
+from rest_framework.response import Response
+from typing_extensions import TypedDict
+
+from sentry import eventstore, features
+from sentry.api.api_owners import ApiOwner
+from sentry.api.api_publish_status import ApiPublishStatus
+from sentry.api.base import region_silo_endpoint
+from sentry.api.bases.project import ProjectEndpoint
+from sentry.apidocs.constants import RESPONSE_FORBIDDEN, RESPONSE_NOT_FOUND, RESPONSE_UNAUTHORIZED
+from sentry.apidocs.parameters import EventParams, GlobalParams
+from sentry.apidocs.utils import inline_sentry_response_serializer
+from sentry.models.artifactbundle import (
+    ArtifactBundle,
+    ArtifactBundleArchive,
+    DebugIdArtifactBundle,
+    ReleaseArtifactBundle,
+    SourceFileType,
+)
+from sentry.models.project import Project
+from sentry.models.release import Release
+from sentry.models.releasefile import ReleaseFile
+from sentry.sdk_updates import get_sdk_index
+from sentry.utils.javascript import find_sourcemap
+from sentry.utils.safe import get_path
+from sentry.utils.urls import non_standard_url_join
+
+MIN_JS_SDK_VERSION_FOR_DEBUG_IDS = "7.56.0"
+
+NO_DEBUG_ID_SDKS = {
+    "sentry.javascript.capacitor",
+    "sentry.javascript.react-native",
+    "sentry.javascript.wasm",
+    "sentry.javascript.cordova",
+    "sentry.javascript.nextjs",
+    "sentry.javascript.sveltekit",
+}
+
+
+class SourceMapDebugIdProcessResult(TypedDict):
+    debug_id: Optional[str]
+    uploaded_source_file_with_correct_debug_id: bool
+    uploaded_source_map_with_correct_debug_id: bool
+
+
+class SourceMapReleaseProcessResult(TypedDict):
+    matching_source_file_names: List[str]
+    matching_source_map_name: Optional[str]
+    source_map_reference: Optional[str]
+    source_file_lookup_result: Literal["found", "wrong-dist", "unsuccessful"]
+    source_map_lookup_result: Literal["found", "wrong-dist", "unsuccessful"]
+
+
+class SourceMapDebugFrame(TypedDict):
+    debug_id_process: SourceMapDebugIdProcessResult
+    release_process: Optional[SourceMapReleaseProcessResult]
+
+
+class SourceMapDebugException(TypedDict):
+    frames: List[SourceMapDebugFrame]
+
+
+class SourceMapDebugResponse(TypedDict):
+    dist: Optional[str]
+    release: Optional[str]
+    exceptions: List[SourceMapDebugException]
+    has_debug_ids: bool
+    sdk_version: Optional[str]
+    project_has_some_artifact_bundle: bool
+    release_has_some_artifact: bool
+    has_uploaded_some_artifact_with_a_debug_id: bool
+    sdk_debug_id_support: Literal["not-supported", "unofficial-sdk", "needs-upgrade", "full"]
+
+
+@region_silo_endpoint
+@extend_schema(tags=["Events"])
+class SourceMapDebugBlueThunderEditionEndpoint(ProjectEndpoint):
+    publish_status = {
+        "GET": ApiPublishStatus.PRIVATE,
+    }
+
+    owner = ApiOwner.WEB_FRONTEND_SDKS
+
+    @extend_schema(
+        operation_id="Get Debug Information Related to Source Maps for a Given Event",
+        parameters=[
+            GlobalParams.ORG_SLUG,
+            GlobalParams.PROJECT_SLUG,
+            EventParams.EVENT_ID,
+        ],
+        request=None,
+        responses={
+            200: inline_sentry_response_serializer("SourceMapDebug", SourceMapDebugResponse),
+            401: RESPONSE_UNAUTHORIZED,
+            403: RESPONSE_FORBIDDEN,
+            404: RESPONSE_NOT_FOUND,
+        },
+    )
+    def get(self, request: Request, project: Project, event_id: str) -> Response:
+        """
+        Return a list of source map errors for a given event.
+        """
+
+        if not features.has(
+            "organizations:source-maps-debugger-blue-thunder-edition",
+            project.organization,
+            actor=request.user,
+        ):
+            raise NotFound(
+                detail="Endpoint not available without 'organizations:source-maps-debugger-blue-thunder-edition' feature flag"
+            )
+
+        event = eventstore.backend.get_event_by_id(project.id, event_id)
+        if event is None:
+            raise NotFound(detail="Event not found")
+
+        event_data = event.data
+
+        release = None
+        if event.release is not None:
+            try:
+                release = Release.objects.get(
+                    organization=project.organization, version=event.release
+                )
+            except Release.DoesNotExist:
+                pass
+
+        # get general information about what has been uploaded
+        project_has_some_artifact_bundle = ArtifactBundle.objects.filter(
+            projectartifactbundle__project_id=project.id,
+        ).exists()
+        has_uploaded_release_bundle_with_release = False
+        has_uploaded_artifact_bundle_with_release = False
+        if release is not None:
+            has_uploaded_release_bundle_with_release = ReleaseFile.objects.filter(
+                release_id=release.id
+            ).exists()
+            has_uploaded_artifact_bundle_with_release = ReleaseArtifactBundle.objects.filter(
+                organization_id=project.organization_id, release_name=release.version
+            ).exists()
+        has_uploaded_some_artifact_with_a_debug_id = DebugIdArtifactBundle.objects.filter(
+            organization_id=project.organization_id,
+            artifact_bundle__projectartifactbundle__project_id=project.id,
+        ).exists()
+
+        debug_images = get_path(event_data, "debug_meta", "images")
+        debug_images = debug_images if debug_images is not None else []
+
+        # get information about which debug ids on the event have uploaded artifacts
+        debug_ids = [
+            debug_image["debug_id"]
+            for debug_image in debug_images
+            if debug_image["type"] == "sourcemap"
+        ][0:100]
+        debug_id_artifact_bundles = DebugIdArtifactBundle.objects.filter(
+            artifact_bundle__projectartifactbundle__project_id=project.id,
+            debug_id__in=debug_ids,
+        )
+        debug_ids_with_uploaded_source_file = set()
+        debug_ids_with_uploaded_source_map = set()
+        for debug_id_artifact_bundle in debug_id_artifact_bundles:
+            if (
+                SourceFileType(debug_id_artifact_bundle.source_file_type) == SourceFileType.SOURCE
+                or SourceFileType(debug_id_artifact_bundle.source_file_type)
+                == SourceFileType.MINIFIED_SOURCE
+            ):
+                debug_ids_with_uploaded_source_file.add(str(debug_id_artifact_bundle.debug_id))
+            elif (
+                SourceFileType(debug_id_artifact_bundle.source_file_type)
+                == SourceFileType.SOURCE_MAP
+            ):
+                debug_ids_with_uploaded_source_map.add(str(debug_id_artifact_bundle.debug_id))
+
+        # Get all abs paths and query for their existence so that we can match release artifacts
+        release_process_abs_path_data = {}
+        if release is not None:
+            abs_paths = get_abs_paths_in_event(event_data)
+            for abs_path in abs_paths:
+                path_data = get_source_file_data(abs_path, project, release, event)
+                release_process_abs_path_data[abs_path] = path_data
+
+        # build information about individual exceptions and their stack traces
+        processed_exceptions = []
+        exception_values = get_path(event_data, "exception", "values")
+        if exception_values is not None:
+            for exception_value in exception_values:
+                processed_frames = []
+                frames = get_path(exception_value, "raw_stacktrace", "frames")
+                frames = frames or get_path(exception_value, "stacktrace", "frames")
+                if frames is not None:
+                    for frame in frames:
+                        abs_path = get_path(frame, "abs_path")
+                        debug_id = next(
+                            (
+                                debug_image["debug_id"]
+                                for debug_image in debug_images
+                                if debug_image["type"] == "sourcemap"
+                                and abs_path == debug_image["code_file"]
+                            ),
+                            None,
+                        )
+                        processed_frames.append(
+                            {
+                                "debug_id_process": {
+                                    "debug_id": debug_id,
+                                    "uploaded_source_file_with_correct_debug_id": debug_id
+                                    in debug_ids_with_uploaded_source_file,
+                                    "uploaded_source_map_with_correct_debug_id": debug_id
+                                    in debug_ids_with_uploaded_source_map,
+                                },
+                                "release_process": release_process_abs_path_data.get(abs_path),
+                            }
+                        )
+                processed_exceptions.append({"frames": processed_frames})
+
+        return Response(
+            {
+                "dist": event.dist,
+                "release": event.release,
+                "exceptions": processed_exceptions,
+                "has_debug_ids": event_has_debug_ids(event_data),
+                "sdk_version": get_path(event_data, "sdk", "version"),
+                "project_has_some_artifact_bundle": project_has_some_artifact_bundle,
+                "release_has_some_artifact": has_uploaded_release_bundle_with_release
+                or has_uploaded_artifact_bundle_with_release,
+                "has_uploaded_some_artifact_with_a_debug_id": has_uploaded_some_artifact_with_a_debug_id,
+                "sdk_debug_id_support": get_sdk_debug_id_support(event_data),
+            }
+        )
+
+
+def get_source_file_data(abs_path, project, release, event):
+    filenme_choices = ReleaseFile.normalize(abs_path)
+
+    path_data = {
+        "matching_source_file_names": filenme_choices,
+        "matching_source_map_name": None,
+        "source_map_reference": None,
+        "source_file_lookup_result": "unsuccessful",
+        "source_map_lookup_result": "unsuccessful",
+    }
+
+    possible_release_files = (
+        ReleaseFile.objects.filter(
+            organization_id=project.organization_id,
+            release_id=release.id,
+            name__in=filenme_choices,
+        )
+        .exclude(artifact_count=0)
+        .select_related("file")
+    )
+    if len(possible_release_files) > 0:
+        path_data["source_file_lookup_result"] = "wrong-dist"
+    for possible_release_file in possible_release_files:
+        if possible_release_file.ident == ReleaseFile.get_ident(
+            possible_release_file.name, event.dist
+        ):
+            path_data["source_file_lookup_result"] = "found"
+            source_map_reference = None
+            sourcemap_header = None
+            if possible_release_file.file.headers:
+                headers = ArtifactBundleArchive.normalize_headers(
+                    possible_release_file.file.headers
+                )
+                sourcemap_header = headers.get("sourcemap", headers.get("x-sourcemap"))
+                sourcemap_header = (
+                    force_bytes(sourcemap_header) if sourcemap_header is not None else None
+                )
+
+            try:
+                source_map_reference = find_sourcemap(
+                    sourcemap_header, possible_release_file.file.getfile().read()
+                )
+                if source_map_reference is not None:
+                    source_map_reference = force_str(source_map_reference)
+            except AssertionError:
+                pass
+
+            matching_source_map_name = None
+            if source_map_reference is not None:
+                if source_map_reference.startswith("data:"):
+                    source_map_reference = "Inline Sourcemap"
+                    path_data["source_map_lookup_result"] = "found"
+                else:
+                    matching_source_map_name = get_matching_source_map_location(
+                        possible_release_file.name, source_map_reference
+                    )
+
+            if matching_source_map_name is not None:
+                path_data["source_map_lookup_result"] = get_release_files_status_by_url(
+                    event, project, release, [matching_source_map_name]
+                )
+
+            return {
+                "matching_source_file_names": filenme_choices,
+                "matching_source_map_name": matching_source_map_name,
+                "source_map_reference": source_map_reference,
+                "source_file_lookup_result": "found",
+                "source_map_lookup_result": path_data["source_map_lookup_result"],
+            }
+
+    possible_release_artifact_bundles = ReleaseArtifactBundle.objects.filter(
+        organization_id=project.organization.id,
+        release_name=release.version,
+        artifact_bundle__projectartifactbundle__project_id=project.id,
+        artifact_bundle__artifactbundleindex__organization_id=project.organization.id,
+        artifact_bundle__artifactbundleindex__url__in=filenme_choices,
+    )
+    if len(possible_release_artifact_bundles) > 0:
+        path_data["source_file_lookup_result"] = (
+            "wrong-dist"
+            if path_data["source_file_lookup_result"] == "unsuccessful"
+            else path_data["source_file_lookup_result"]
+        )
+    for possible_release_artifact_bundle in possible_release_artifact_bundles:
+        if possible_release_artifact_bundle.dist_name == (event.dist or ""):
+            found_source_file_path = None
+            source_map_reference = None
+            with ArtifactBundleArchive(
+                possible_release_artifact_bundle.artifact_bundle.file.getfile()
+            ) as archive:
+                matching_file = None
+                sourcemap_header = None
+                for filename_choice in filenme_choices:
+                    try:
+                        matching_file, headers = archive.get_file_by_url(filename_choice)
+                        sourcemap_header = headers.get("sourcemap", headers.get("x-sourcemap"))
+                        sourcemap_header = (
+                            force_bytes(sourcemap_header) if sourcemap_header is not None else None
+                        )
+                        found_source_file_path = filename_choice
+                        break
+                    except Exception:
+                        continue
+                if matching_file is not None:
+                    try:
+                        source_map_reference = find_sourcemap(
+                            sourcemap_header, matching_file.read()
+                        )
+                    except AssertionError:
+                        pass
+                    source_map_reference = (
+                        force_str(source_map_reference)
+                        if source_map_reference is not None
+                        else None
+                    )
+
+            matching_source_map_name = None
+            if source_map_reference is not None:
+                if source_map_reference.startswith("data:"):
+                    source_map_reference = "Inline Sourcemap"
+                    path_data["source_map_lookup_result"] = "found"
+                elif found_source_file_path is not None:
+                    matching_source_map_name = get_matching_source_map_location(
+                        found_source_file_path, source_map_reference
+                    )
+
+            if matching_source_map_name is not None:
+                path_data["source_map_lookup_result"] = get_artifact_bundle_file_status_by_url(
+                    event, project, release, [matching_source_map_name]
+                )
+
+            return {
+                "matching_source_file_names": filenme_choices,
+                "matching_source_map_name": matching_source_map_name,
+                "source_file_lookup_result": "found",
+                "source_map_reference": source_map_reference,
+                "source_map_lookup_result": path_data["source_map_lookup_result"],
+            }
+
+    return path_data
+
+
+def get_release_files_status_by_url(event, project, release, possible_urls):
+    result = "unsuccessful"
+    possible_release_files = ReleaseFile.objects.filter(
+        organization_id=project.organization_id,
+        release_id=release.id,
+        name__in=possible_urls,
+    ).exclude(artifact_count=0)
+    if len(possible_release_files) > 0:
+        result = "wrong-dist"
+    for possible_release_file in possible_release_files:
+        if possible_release_file.ident == ReleaseFile.get_ident(
+            possible_release_file.name, event.dist
+        ):
+            return "found"
+    return result
+
+
+def get_artifact_bundle_file_status_by_url(event, project, release, possible_urls):
+    result = "unsuccessful"
+    possible_release_artifact_bundles = ReleaseArtifactBundle.objects.filter(
+        organization_id=project.organization.id,
+        release_name=release.version,
+        artifact_bundle__projectartifactbundle__project_id=project.id,
+        artifact_bundle__artifactbundleindex__organization_id=project.organization.id,
+        artifact_bundle__artifactbundleindex__url__in=possible_urls,
+    )
+    if len(possible_release_artifact_bundles) > 0:
+        result = "wrong-dist"
+    for possible_release_artifact_bundle in possible_release_artifact_bundles:
+        if possible_release_artifact_bundle.dist_name == (event.dist or ""):
+            return "found"
+    return result
+
+
+def get_matching_source_map_location(source_file_path, source_map_reference):
+    return non_standard_url_join(force_str(source_file_path), force_str(source_map_reference))
+
+
+def event_has_debug_ids(event_data):
+    debug_images = get_path(event_data, "debug_meta", "images")
+    if debug_images is None:
+        return False
+    else:
+        for debug_image in debug_images:
+            if debug_image["type"] == "sourcemap":
+                return True
+        return False
+
+
+def get_sdk_debug_id_support(event_data):
+    sdk_name = get_path(event_data, "sdk", "name")
+
+    official_sdks = None
+    try:
+        sdk_release_registry = get_sdk_index()
+        official_sdks = [
+            sdk.startswith("sentry.javascript.") for sdk in sdk_release_registry.keys()
+        ]
+    except Exception as e:
+        sentry_sdk.capture_exception(e)
+        pass
+
+    if official_sdks is None or len(official_sdks) == 0:
+        # Fallback list if release registry is not available
+        official_sdks = [
+            "sentry.javascript.angular",
+            "sentry.javascript.angular-ivy",
+            "sentry.javascript.browser",
+            "sentry.javascript.capacitor",
+            "sentry.javascript.cordova",
+            "sentry.javascript.electron",
+            "sentry.javascript.gatsby",
+            "sentry.javascript.nextjs",
+            "sentry.javascript.node",
+            "sentry.javascript.opentelemetry-node",
+            "sentry.javascript.react",
+            "sentry.javascript.react-native",
+            "sentry.javascript.remix",
+            "sentry.javascript.svelte",
+            "sentry.javascript.sveltekit",
+            "sentry.javascript.vue",
+        ]
+
+    if sdk_name not in official_sdks or sdk_name is None:
+        return "unofficial-sdk"
+    elif sdk_name in NO_DEBUG_ID_SDKS:
+        return "not-supported"
+
+    sdk_version = get_path(event_data, "sdk", "version")
+    if sdk_version is None:
+        return "unofficial-sdk"
+
+    return (
+        "full"
+        if Version(sdk_version) >= Version(MIN_JS_SDK_VERSION_FOR_DEBUG_IDS)
+        else "needs-upgrade"
+    )
+
+
+def get_abs_paths_in_event(event_data):
+    abs_paths = set()
+    exception_values = get_path(event_data, "exception", "values")
+    if exception_values is not None:
+        for exception_value in exception_values:
+            stacktrace = get_path(exception_value, "raw_stacktrace") or get_path(
+                exception_value, "stacktrace"
+            )
+            frames = get_path(stacktrace, "frames")
+            if frames is not None:
+                for frame in frames:
+                    abs_path = get_path(frame, "abs_path")
+                    if abs_path:
+                        abs_paths.add(abs_path)
+    return abs_paths

+ 8 - 0
src/sentry/api/urls.py

@@ -22,6 +22,9 @@ from sentry.api.endpoints.organization_projects_experiment import (
     OrganizationProjectsExperimentEndpoint,
 )
 from sentry.api.endpoints.release_threshold import ReleaseThresholdEndpoint
+from sentry.api.endpoints.source_map_debug_blue_thunder_edition import (
+    SourceMapDebugBlueThunderEditionEndpoint,
+)
 from sentry.api.utils import method_dispatch
 from sentry.data_export.endpoints.data_export import DataExportEndpoint
 from sentry.data_export.endpoints.data_export_details import DataExportDetailsEndpoint
@@ -2012,6 +2015,11 @@ PROJECT_URLS: list[URLPattern | URLResolver] = [
         SourceMapDebugEndpoint.as_view(),
         name="sentry-api-0-event-source-map-debug",
     ),
+    re_path(
+        r"^(?P<organization_slug>[^\/]+)/(?P<project_slug>[^\/]+)/events/(?P<event_id>[\w-]+)/source-map-debug-blue-thunder-edition/$",
+        SourceMapDebugBlueThunderEditionEndpoint.as_view(),
+        name="sentry-api-0-event-source-map-debug-blue-thunder-edition",
+    ),
     re_path(
         r"^(?P<organization_slug>[^\/]+)/(?P<project_slug>[^\/]+)/events/(?P<event_id>[\w-]+)/actionable-items/$",
         ActionableItemsEndpoint.as_view(),

+ 2 - 0
src/sentry/conf/server.py

@@ -1777,6 +1777,8 @@ SENTRY_FEATURES = {
     "organizations:notifications-double-write": False,
     # Excludes measurement config from project config builds.
     "organizations:projconfig-exclude-measurements": False,
+    # Enable source maps debugger
+    "organizations:source-maps-debugger-blue-thunder-edition": False,
     # Enable data forwarding functionality for projects.
     "projects:data-forwarding": True,
     # Enable functionality to discard groups.

+ 1 - 0
src/sentry/features/__init__.py

@@ -287,6 +287,7 @@ default_manager.add("organizations:notifications-double-write", OrganizationFeat
 default_manager.add("organizations:custom-metrics", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
 default_manager.add("organizations:release-ui-v2", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
 default_manager.add("organizations:projconfig-exclude-measurements", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
+default_manager.add("organizations:source-maps-debugger-blue-thunder-edition", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
 
 # Project scoped features
 default_manager.add("projects:alert-filters", ProjectFeature, FeatureHandlerStrategy.INTERNAL)

+ 1405 - 0
tests/sentry/api/endpoints/test_source_map_debug_blue_thunder_edition.py

@@ -0,0 +1,1405 @@
+import zipfile
+from io import BytesIO
+
+from django.core.files.base import ContentFile
+from rest_framework import status
+
+from sentry.models.artifactbundle import (
+    ArtifactBundle,
+    ArtifactBundleIndex,
+    DebugIdArtifactBundle,
+    ProjectArtifactBundle,
+    ReleaseArtifactBundle,
+    SourceFileType,
+)
+from sentry.models.distribution import Distribution
+from sentry.models.file import File
+from sentry.models.release import Release
+from sentry.models.releasefile import ReleaseFile
+from sentry.testutils.cases import APITestCase
+from sentry.testutils.silo import region_silo_test
+from sentry.utils import json
+
+
+def create_exception_with_frame(frame):
+    return {
+        "type": "Error",
+        "stacktrace": {"frames": [frame]},
+    }
+
+
+def create_event(exceptions=None, debug_meta_images=None, sdk=None, release=None, dist=None):
+    exceptions = [] if exceptions is None else exceptions
+    return {
+        "event_id": "a" * 32,
+        "release": release,
+        "dist": dist,
+        "exception": {"values": exceptions},
+        "debug_meta": None if debug_meta_images is None else {"images": debug_meta_images},
+        "sdk": sdk,
+    }
+
+
+@region_silo_test  # TODO(hybrid-cloud): stable=True blocked on actors
+class SourceMapDebugBlueThunderEditionEndpointTestCase(APITestCase):
+    endpoint = "sentry-api-0-event-source-map-debug-blue-thunder-edition"
+
+    def setUp(self) -> None:
+        self.login_as(self.user)
+        return super().setUp()
+
+    def test_no_feature_flag(self):
+        event = self.store_event(data=create_event([]), project_id=self.project.id)
+        resp = self.get_error_response(
+            self.organization.slug,
+            self.project.slug,
+            event.event_id,
+            status_code=status.HTTP_404_NOT_FOUND,
+        )
+        assert (
+            resp.data["detail"]
+            == "Endpoint not available without 'organizations:source-maps-debugger-blue-thunder-edition' feature flag"
+        )
+
+    def test_missing_event(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            resp = self.get_error_response(
+                self.organization.slug,
+                self.project.slug,
+                "invalid_id",
+                frame_idx=0,
+                exception_idx=0,
+                status_code=status.HTTP_404_NOT_FOUND,
+            )
+            assert resp.data["detail"] == "Event not found"
+
+    def test_empty_exceptions_array(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(data=create_event([]), project_id=self.project.id)
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+            assert resp.data["exceptions"] == []
+
+    def test_has_debug_ids_true(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[create_exception_with_frame({"abs_path": "/some/path/to/file.js"})],
+                    debug_meta_images=[
+                        {
+                            "type": "sourcemap",
+                            "code_file": "/some/path/to/file.js",
+                            "debug_id": "8d65dbd3-bb6c-5632-9049-7751111284ed",
+                        }
+                    ],
+                ),
+                project_id=self.project.id,
+            )
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+            assert resp.data["has_debug_ids"]
+
+    def test_has_debug_ids_false(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[create_exception_with_frame({"abs_path": "/some/path/to/file.js"})],
+                    debug_meta_images=None,
+                ),
+                project_id=self.project.id,
+            )
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+            assert not resp.data["has_debug_ids"]
+
+    def test_sdk_version(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(sdk={"name": "sentry.javascript.react", "version": "7.66.0"}),
+                project_id=self.project.id,
+            )
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+            assert resp.data["sdk_version"] == "7.66.0"
+
+    def test_no_sdk_version(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(data=create_event(), project_id=self.project.id)
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+            assert resp.data["sdk_version"] is None
+
+    def test_sdk_debug_id_support_full(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(sdk={"name": "sentry.javascript.react", "version": "7.66.0"}),
+                project_id=self.project.id,
+            )
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+            assert resp.data["sdk_debug_id_support"] == "full"
+
+    def test_sdk_debug_id_support_needs_upgrade(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(sdk={"name": "sentry.javascript.react", "version": "7.47.0"}),
+                project_id=self.project.id,
+            )
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+            assert resp.data["sdk_debug_id_support"] == "needs-upgrade"
+
+    def test_sdk_debug_id_support_unsupported(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(sdk={"name": "sentry.javascript.cordova", "version": "7.47.0"}),
+                project_id=self.project.id,
+            )
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+            assert resp.data["sdk_debug_id_support"] == "not-supported"
+
+    def test_sdk_debug_id_support_community_sdk(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    sdk={"name": "sentry.javascript.some-custom-identifier", "version": "7.47.0"}
+                ),
+                project_id=self.project.id,
+            )
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+            assert resp.data["sdk_debug_id_support"] == "unofficial-sdk"
+
+    def test_release_has_some_artifact_positive(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(release="some-release"),
+                project_id=self.project.id,
+            )
+
+            release = Release.objects.get(organization=self.organization, version=event.release)
+
+            ReleaseFile.objects.create(
+                organization_id=self.organization.id,
+                release_id=release.id,
+                file=File.objects.create(name="bundle.js", type="release.file"),
+                name="~/bundle.js",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            assert resp.data["release_has_some_artifact"]
+
+    def test_release_has_some_artifact_negative(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(release="some-release"),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            assert not resp.data["release_has_some_artifact"]
+
+    def test_project_has_some_artifact_bundle_positive(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=File.objects.create(name="artifact-bundle.zip", type="dummy.file"),
+                artifact_count=1,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=artifact_bundle,
+            )
+
+            event = self.store_event(
+                data=create_event(),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            assert resp.data["project_has_some_artifact_bundle"]
+
+    def test_project_has_some_artifact_bundle_negative(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            assert not resp.data["project_has_some_artifact_bundle"]
+
+    def test_project_has_some_artifact_bundle_with_a_debug_id_positive(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=File.objects.create(name="artifact-bundle.zip", type="dummy.file"),
+                artifact_count=1,
+            )
+
+            DebugIdArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                debug_id="00000000-00000000-00000000-00000000",
+                artifact_bundle=artifact_bundle,
+                source_file_type=SourceFileType.SOURCE_MAP.value,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=artifact_bundle,
+            )
+
+            event = self.store_event(
+                data=create_event(),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            assert resp.data["has_uploaded_some_artifact_with_a_debug_id"]
+
+    def test_project_has_some_artifact_bundle_with_a_debug_id_negative(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            assert not resp.data["has_uploaded_some_artifact_with_a_debug_id"]
+
+    def test_multiple_exceptions(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame({"abs_path": "/some/path/to/file.js"}),
+                        create_exception_with_frame(
+                            {"abs_path": "/some/path/to/some/other/file.js"}
+                        ),
+                    ],
+                ),
+                project_id=self.project.id,
+            )
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+            assert len(resp.data["exceptions"]) == 2
+
+    def test_frame_debug_id_no_debug_id(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[create_exception_with_frame({"abs_path": "/some/path/to/file.js"})],
+                    debug_meta_images=[
+                        {
+                            "type": "sourcemap",
+                            "code_file": "/some/path/to/file/that/doesnt/match.js",
+                            "debug_id": "8d65dbd3-bb6c-5632-9049-7751111284ed",
+                        }
+                    ],
+                ),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            debug_id_process_result = resp.data["exceptions"][0]["frames"][0]["debug_id_process"]
+
+            assert debug_id_process_result["debug_id"] is None
+            assert not debug_id_process_result["uploaded_source_file_with_correct_debug_id"]
+            assert not debug_id_process_result["uploaded_source_map_with_correct_debug_id"]
+
+    def test_frame_debug_id_no_uploaded_source_no_uploaded_source_map(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[create_exception_with_frame({"abs_path": "/some/path/to/file.js"})],
+                    debug_meta_images=[
+                        {
+                            "type": "sourcemap",
+                            "code_file": "/some/path/to/file.js",
+                            "debug_id": "a5764857-ae35-34dc-8f25-a9c9e73aa898",
+                        }
+                    ],
+                ),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            debug_id_process_result = resp.data["exceptions"][0]["frames"][0]["debug_id_process"]
+
+            assert debug_id_process_result["debug_id"] == "a5764857-ae35-34dc-8f25-a9c9e73aa898"
+            assert not debug_id_process_result["uploaded_source_file_with_correct_debug_id"]
+            assert not debug_id_process_result["uploaded_source_map_with_correct_debug_id"]
+
+    def test_frame_debug_id_uploaded_source_no_uploaded_source_map(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=File.objects.create(name="artifact-bundle.zip", type="test.file"),
+                artifact_count=1,
+            )
+
+            DebugIdArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                debug_id="a5764857-ae35-34dc-8f25-a9c9e73aa898",
+                artifact_bundle=artifact_bundle,
+                source_file_type=SourceFileType.MINIFIED_SOURCE.value,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=artifact_bundle,
+            )
+
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[create_exception_with_frame({"abs_path": "/some/path/to/file.js"})],
+                    debug_meta_images=[
+                        {
+                            "type": "sourcemap",
+                            "code_file": "/some/path/to/file.js",
+                            "debug_id": "a5764857-ae35-34dc-8f25-a9c9e73aa898",
+                        }
+                    ],
+                ),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            debug_id_process_result = resp.data["exceptions"][0]["frames"][0]["debug_id_process"]
+
+            assert debug_id_process_result["debug_id"] == "a5764857-ae35-34dc-8f25-a9c9e73aa898"
+            assert debug_id_process_result["uploaded_source_file_with_correct_debug_id"]
+            assert not debug_id_process_result["uploaded_source_map_with_correct_debug_id"]
+
+    def test_frame_debug_id_no_uploaded_source_uploaded_source_map(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=File.objects.create(name="artifact-bundle.zip", type="test.file"),
+                artifact_count=1,
+            )
+
+            DebugIdArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                debug_id="a5764857-ae35-34dc-8f25-a9c9e73aa898",
+                artifact_bundle=artifact_bundle,
+                source_file_type=SourceFileType.SOURCE_MAP.value,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=artifact_bundle,
+            )
+
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[create_exception_with_frame({"abs_path": "/some/path/to/file.js"})],
+                    debug_meta_images=[
+                        {
+                            "type": "sourcemap",
+                            "code_file": "/some/path/to/file.js",
+                            "debug_id": "a5764857-ae35-34dc-8f25-a9c9e73aa898",
+                        }
+                    ],
+                ),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            debug_id_process_result = resp.data["exceptions"][0]["frames"][0]["debug_id_process"]
+
+            assert debug_id_process_result["debug_id"] == "a5764857-ae35-34dc-8f25-a9c9e73aa898"
+            assert not debug_id_process_result["uploaded_source_file_with_correct_debug_id"]
+            assert debug_id_process_result["uploaded_source_map_with_correct_debug_id"]
+
+    def test_frame_debug_id_uploaded_source_uploaded_source_map(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=File.objects.create(name="artifact-bundle.zip", type="test.file"),
+                artifact_count=1,
+            )
+
+            DebugIdArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                debug_id="a5764857-ae35-34dc-8f25-a9c9e73aa898",
+                artifact_bundle=artifact_bundle,
+                source_file_type=SourceFileType.SOURCE.value,
+            )
+
+            DebugIdArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                debug_id="a5764857-ae35-34dc-8f25-a9c9e73aa898",
+                artifact_bundle=artifact_bundle,
+                source_file_type=SourceFileType.SOURCE_MAP.value,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=artifact_bundle,
+            )
+
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[create_exception_with_frame({"abs_path": "/some/path/to/file.js"})],
+                    debug_meta_images=[
+                        {
+                            "type": "sourcemap",
+                            "code_file": "/some/path/to/file.js",
+                            "debug_id": "a5764857-ae35-34dc-8f25-a9c9e73aa898",
+                        }
+                    ],
+                ),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            debug_id_process_result = resp.data["exceptions"][0]["frames"][0]["debug_id_process"]
+
+            assert debug_id_process_result["debug_id"] == "a5764857-ae35-34dc-8f25-a9c9e73aa898"
+            assert debug_id_process_result["uploaded_source_file_with_correct_debug_id"]
+            assert debug_id_process_result["uploaded_source_map_with_correct_debug_id"]
+
+    def test_frame_release_process_release_file_matching_source_file_names(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame({"abs_path": "http://example.com/bundle.js"})
+                    ],
+                    release="some-release",
+                ),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["matching_source_file_names"] == [
+                "http://example.com/bundle.js",
+                "~/bundle.js",
+            ]
+
+    def test_frame_release_process_release_file_source_map_reference(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame({"abs_path": "http://example.com/bundle.js"})
+                    ],
+                    release="some-release",
+                ),
+                project_id=self.project.id,
+            )
+
+            release = Release.objects.get(organization=self.organization, version=event.release)
+
+            file = File.objects.create(name="bundle.js", type="release.file")
+            fileobj = ContentFile(
+                b'console.log("hello world");\n//# sourceMappingURL=bundle.js.map\n'
+            )
+            file.putfile(fileobj)
+
+            ReleaseFile.objects.create(
+                organization_id=self.organization.id,
+                release_id=release.id,
+                file=file,
+                name="~/bundle.js",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["matching_source_map_name"] == "~/bundle.js.map"
+            assert release_process_result["source_map_reference"] == "bundle.js.map"
+
+    def test_frame_release_process_release_file_data_protocol_source_map_reference(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame({"abs_path": "http://example.com/bundle.js"})
+                    ],
+                    release="some-release",
+                ),
+                project_id=self.project.id,
+            )
+
+            release = Release.objects.get(organization=self.organization, version=event.release)
+
+            file = File.objects.create(
+                name="bundle.js",
+                type="release.file",
+                headers={
+                    "Sourcemap": "data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcy"
+                },
+            )
+
+            ReleaseFile.objects.create(
+                organization_id=self.organization.id,
+                release_id=release.id,
+                file=file,
+                name="~/bundle.js",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_map_lookup_result"] == "found"
+            assert release_process_result["source_map_reference"] == "Inline Sourcemap"
+            assert release_process_result["matching_source_map_name"] is None
+
+    def test_frame_release_process_release_file_source_file_not_found(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame({"abs_path": "http://example.com/bundle.js"})
+                    ],
+                    release="some-release",
+                ),
+                project_id=self.project.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_file_lookup_result"] == "unsuccessful"
+            assert release_process_result["source_map_lookup_result"] == "unsuccessful"
+            assert release_process_result["source_map_reference"] is None
+            assert release_process_result["matching_source_map_name"] is None
+
+    def test_frame_release_process_release_file_source_file_wrong_dist(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame({"abs_path": "http://example.com/bundle.js"})
+                    ],
+                    release="some-release",
+                    dist="some-dist",
+                ),
+                project_id=self.project.id,
+            )
+
+            release = Release.objects.get(organization=self.organization, version=event.release)
+
+            file = File.objects.create(
+                name="bundle.js", type="release.file", headers={"Sourcemap": "bundle.js.map"}
+            )
+
+            ReleaseFile.objects.create(
+                organization_id=self.organization.id,
+                release_id=release.id,
+                file=file,
+                name="~/bundle.js",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_file_lookup_result"] == "wrong-dist"
+            assert release_process_result["source_map_lookup_result"] == "unsuccessful"
+            assert release_process_result["source_map_reference"] is None
+            assert release_process_result["matching_source_map_name"] is None
+
+    def test_frame_release_process_release_file_source_file_successful(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame({"abs_path": "http://example.com/bundle.js"})
+                    ],
+                    release="some-release",
+                ),
+                project_id=self.project.id,
+            )
+
+            release = Release.objects.get(organization=self.organization, version=event.release)
+
+            file = File.objects.create(
+                name="bundle.js", type="release.file", headers={"Sourcemap": "bundle.js.map"}
+            )
+
+            ReleaseFile.objects.create(
+                organization_id=self.organization.id,
+                release_id=release.id,
+                file=file,
+                name="~/bundle.js",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_file_lookup_result"] == "found"
+            assert release_process_result["source_map_lookup_result"] == "unsuccessful"
+            assert release_process_result["source_map_reference"] == "bundle.js.map"
+            assert release_process_result["matching_source_map_name"] == "~/bundle.js.map"
+
+    def test_frame_release_process_release_file_source_map_wrong_dist(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame({"abs_path": "http://example.com/bundle.js"})
+                    ],
+                    release="some-release",
+                    dist="some-dist",
+                ),
+                project_id=self.project.id,
+            )
+
+            release = Release.objects.get(organization=self.organization, version=event.release)
+
+            source_file = File.objects.create(
+                name="bundle.js", type="release.file", headers={"Sourcemap": "bundle.js.map"}
+            )
+
+            source_map_file = File.objects.create(
+                name="bundle.js.map",
+                type="release.file",
+            )
+
+            dist = Distribution.objects.get(name="some-dist", release=release)
+
+            ReleaseFile.objects.create(
+                organization_id=self.organization.id,
+                release_id=release.id,
+                file=source_file,
+                name="~/bundle.js",
+                ident=ReleaseFile.get_ident("~/bundle.js", dist.name),
+                dist_id=dist.id,
+            )
+
+            ReleaseFile.objects.create(
+                organization_id=self.organization.id,
+                release_id=release.id,
+                file=source_map_file,
+                name="~/bundle.js.map",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_file_lookup_result"] == "found"
+            assert release_process_result["source_map_lookup_result"] == "wrong-dist"
+            assert release_process_result["source_map_reference"] == "bundle.js.map"
+            assert release_process_result["matching_source_map_name"] == "~/bundle.js.map"
+
+    def test_frame_release_process_release_file_source_map_successful(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame(
+                            {"abs_path": "http://example.com/static/bundle.js"}
+                        )
+                    ],
+                    release="some-release",
+                    dist="some-dist",
+                ),
+                project_id=self.project.id,
+            )
+
+            release = Release.objects.get(organization=self.organization, version=event.release)
+
+            source_file = File.objects.create(
+                name="static/bundle.js",
+                type="release.file",
+                headers={"Sourcemap": "../bundle.js.map"},
+            )
+
+            source_map_file = File.objects.create(
+                name="bundle.js.map",
+                type="release.file",
+            )
+
+            dist = Distribution.objects.get(name="some-dist", release=release)
+
+            ReleaseFile.objects.create(
+                organization_id=self.organization.id,
+                release_id=release.id,
+                file=source_file,
+                name="~/static/bundle.js",
+                ident=ReleaseFile.get_ident("~/static/bundle.js", dist.name),
+                dist_id=dist.id,
+            )
+
+            ReleaseFile.objects.create(
+                organization_id=self.organization.id,
+                release_id=release.id,
+                file=source_map_file,
+                name="~/bundle.js.map",
+                ident=ReleaseFile.get_ident("~/bundle.js.map", dist.name),
+                dist_id=dist.id,
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_file_lookup_result"] == "found"
+            assert release_process_result["source_map_lookup_result"] == "found"
+            assert release_process_result["source_map_reference"] == "../bundle.js.map"
+            assert release_process_result["matching_source_map_name"] == "~/bundle.js.map"
+
+    def test_frame_release_process_artifact_bundle_data_protocol_source_map_reference(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            compressed = BytesIO(b"SYSB")
+            with zipfile.ZipFile(compressed, "a") as zip_file:
+                zip_file.writestr("files/_/_/bundle.min.js", b'console.log("hello world");')
+                zip_file.writestr(
+                    "manifest.json",
+                    json.dumps(
+                        {
+                            "files": {
+                                "files/_/_/bundle.min.js": {
+                                    "url": "~/bundle.min.js",
+                                    "type": "minified_source",
+                                    "headers": {
+                                        "content-type": "application/json",
+                                        "sourcemap": "data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcy",
+                                    },
+                                },
+                            },
+                        }
+                    ),
+                )
+            compressed.seek(0)
+
+            file_obj = File.objects.create(name="artifact_bundle.zip", type="artifact.bundle")
+            file_obj.putfile(compressed)
+
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame(
+                            {"abs_path": "http://example.com/bundle.min.js"}
+                        )
+                    ],
+                    release="some-release",
+                ),
+                project_id=self.project.id,
+            )
+
+            artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=file_obj,
+                artifact_count=1,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=artifact_bundle,
+            )
+
+            ReleaseArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                release_name="some-release",
+                artifact_bundle=artifact_bundle,
+            )
+
+            ArtifactBundleIndex.objects.create(
+                organization_id=self.organization.id,
+                artifact_bundle=artifact_bundle,
+                url="~/bundle.min.js",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_file_lookup_result"] == "found"
+            assert release_process_result["source_map_lookup_result"] == "found"
+            assert release_process_result["source_map_reference"] == "Inline Sourcemap"
+            assert release_process_result["matching_source_map_name"] is None
+
+    def test_frame_release_process_artifact_bundle_source_file_wrong_dist(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            compressed = BytesIO(b"SYSB")
+            with zipfile.ZipFile(compressed, "a") as zip_file:
+                zip_file.writestr(
+                    "files/_/_/bundle.min.js",
+                    b'console.log("hello world");\n//# sourceMappingURL=bundle.min.js.map\n',
+                )
+                zip_file.writestr(
+                    "manifest.json",
+                    json.dumps(
+                        {
+                            "files": {
+                                "files/_/_/bundle.min.js": {
+                                    "url": "~/bundle.min.js",
+                                    "type": "minified_source",
+                                    "headers": {
+                                        "content-type": "application/json",
+                                    },
+                                },
+                            },
+                        }
+                    ),
+                )
+            compressed.seek(0)
+
+            file_obj = File.objects.create(name="artifact_bundle.zip", type="artifact.bundle")
+            file_obj.putfile(compressed)
+
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame(
+                            {"abs_path": "http://example.com/bundle.min.js"}
+                        )
+                    ],
+                    release="some-release",
+                    dist="some-dist",
+                ),
+                project_id=self.project.id,
+            )
+
+            artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=file_obj,
+                artifact_count=1,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=artifact_bundle,
+            )
+
+            ReleaseArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                release_name="some-release",
+                artifact_bundle=artifact_bundle,
+            )
+
+            ArtifactBundleIndex.objects.create(
+                organization_id=self.organization.id,
+                artifact_bundle=artifact_bundle,
+                url="~/bundle.min.js",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_file_lookup_result"] == "wrong-dist"
+
+    def test_frame_release_process_artifact_bundle_source_file_successful(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            compressed = BytesIO(b"SYSB")
+            with zipfile.ZipFile(compressed, "a") as zip_file:
+                zip_file.writestr(
+                    "files/_/_/bundle.min.js",
+                    b'console.log("hello world");\n//# sourceMappingURL=bundle.min.js.map\n',
+                )
+                zip_file.writestr(
+                    "manifest.json",
+                    json.dumps(
+                        {
+                            "files": {
+                                "files/_/_/bundle.min.js": {
+                                    "url": "~/bundle.min.js",
+                                    "type": "minified_source",
+                                    "headers": {
+                                        "content-type": "application/json",
+                                    },
+                                },
+                            },
+                        }
+                    ),
+                )
+            compressed.seek(0)
+
+            file_obj = File.objects.create(name="artifact_bundle.zip", type="artifact.bundle")
+            file_obj.putfile(compressed)
+
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame(
+                            {"abs_path": "http://example.com/bundle.min.js"}
+                        )
+                    ],
+                    release="some-release",
+                ),
+                project_id=self.project.id,
+            )
+
+            artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=file_obj,
+                artifact_count=1,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=artifact_bundle,
+            )
+
+            ReleaseArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                release_name="some-release",
+                artifact_bundle=artifact_bundle,
+            )
+
+            ArtifactBundleIndex.objects.create(
+                organization_id=self.organization.id,
+                artifact_bundle=artifact_bundle,
+                url="~/bundle.min.js",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_file_lookup_result"] == "found"
+
+    def test_frame_release_process_artifact_bundle_source_map_not_found(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            compressed = BytesIO(b"SYSB")
+            with zipfile.ZipFile(compressed, "a") as zip_file:
+                zip_file.writestr(
+                    "files/_/_/bundle.min.js",
+                    b'console.log("hello world");\n//# sourceMappingURL=bundle.min.js.map\n',
+                )
+                zip_file.writestr("files/_/_/bundle.min.js.map", b"")
+                zip_file.writestr(
+                    "manifest.json",
+                    json.dumps(
+                        {
+                            "files": {
+                                "files/_/_/bundle.min.js": {
+                                    "url": "~/bundle.min.js",
+                                    "type": "minified_source",
+                                    "headers": {
+                                        "content-type": "application/json",
+                                    },
+                                },
+                                "files/_/_/wrong-bundle.min.js.map": {
+                                    "url": "~/wrong-bundle.min.js.map",
+                                    "type": "source_map",
+                                    "headers": {
+                                        "content-type": "application/json",
+                                    },
+                                },
+                            },
+                        }
+                    ),
+                )
+            compressed.seek(0)
+
+            file_obj = File.objects.create(name="artifact_bundle.zip", type="artifact.bundle")
+            file_obj.putfile(compressed)
+
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame(
+                            {"abs_path": "http://example.com/bundle.min.js"}
+                        )
+                    ],
+                    release="some-release",
+                ),
+                project_id=self.project.id,
+            )
+
+            artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=file_obj,
+                artifact_count=1,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=artifact_bundle,
+            )
+
+            ReleaseArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                release_name="some-release",
+                artifact_bundle=artifact_bundle,
+            )
+
+            ArtifactBundleIndex.objects.create(
+                organization_id=self.organization.id,
+                artifact_bundle=artifact_bundle,
+                url="~/bundle.min.js",
+            )
+
+            ArtifactBundleIndex.objects.create(
+                organization_id=self.organization.id,
+                artifact_bundle=artifact_bundle,
+                url="~/wrong-bundle.min.js.map",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_file_lookup_result"] == "found"
+            assert release_process_result["source_map_lookup_result"] == "unsuccessful"
+            assert release_process_result["source_map_reference"] == "bundle.min.js.map"
+            assert release_process_result["matching_source_map_name"] == "~/bundle.min.js.map"
+
+    def test_frame_release_process_artifact_bundle_source_map_wrong_dist(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            compressed = BytesIO(b"SYSB")
+            with zipfile.ZipFile(compressed, "a") as zip_file:
+                zip_file.writestr(
+                    "files/_/_/bundle.min.js",
+                    b'console.log("hello world");\n//# sourceMappingURL=bundle.min.js.map\n',
+                )
+                zip_file.writestr("files/_/_/bundle.min.js.map", b"")
+                zip_file.writestr(
+                    "manifest.json",
+                    json.dumps(
+                        {
+                            "files": {
+                                "files/_/_/bundle.min.js": {
+                                    "url": "~/bundle.min.js",
+                                    "type": "minified_source",
+                                    "headers": {
+                                        "content-type": "application/json",
+                                    },
+                                },
+                                "files/_/_/bundle.min.js.map": {
+                                    "url": "~/bundle.min.js.map",
+                                    "type": "source_map",
+                                    "headers": {
+                                        "content-type": "application/json",
+                                    },
+                                },
+                            },
+                        }
+                    ),
+                )
+            compressed.seek(0)
+
+            file_obj = File.objects.create(name="artifact_bundle.zip", type="artifact.bundle")
+            file_obj.putfile(compressed)
+
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame(
+                            {"abs_path": "http://example.com/bundle.min.js"}
+                        )
+                    ],
+                    release="some-release",
+                    dist="some-dist",
+                ),
+                project_id=self.project.id,
+            )
+
+            source_file_artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=file_obj,
+                artifact_count=1,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=source_file_artifact_bundle,
+            )
+
+            ReleaseArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                release_name="some-release",
+                dist_name="some-dist",
+                artifact_bundle=source_file_artifact_bundle,
+            )
+
+            ArtifactBundleIndex.objects.create(
+                organization_id=self.organization.id,
+                artifact_bundle=source_file_artifact_bundle,
+                url="~/bundle.min.js",
+            )
+
+            source_map_artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=file_obj,
+                artifact_count=1,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=source_map_artifact_bundle,
+            )
+
+            ReleaseArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                release_name="some-release",
+                dist_name="some-other-dist",
+                artifact_bundle=source_map_artifact_bundle,
+            )
+
+            ArtifactBundleIndex.objects.create(
+                organization_id=self.organization.id,
+                artifact_bundle=source_map_artifact_bundle,
+                url="~/bundle.min.js",
+            )
+
+            ArtifactBundleIndex.objects.create(
+                organization_id=self.organization.id,
+                artifact_bundle=source_map_artifact_bundle,
+                url="~/bundle.min.js.map",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_file_lookup_result"] == "found"
+            assert release_process_result["source_map_lookup_result"] == "wrong-dist"
+            assert release_process_result["source_map_reference"] == "bundle.min.js.map"
+            assert release_process_result["matching_source_map_name"] == "~/bundle.min.js.map"
+
+    def test_frame_release_process_artifact_bundle_source_map_successful(self):
+        with self.feature("organizations:source-maps-debugger-blue-thunder-edition"):
+            compressed = BytesIO(b"SYSB")
+            with zipfile.ZipFile(compressed, "a") as zip_file:
+                zip_file.writestr(
+                    "files/_/_/bundle.min.js",
+                    b'console.log("hello world");\n//# sourceMappingURL=bundle.min.js.map\n',
+                )
+                zip_file.writestr("files/_/_/bundle.min.js.map", b"")
+                zip_file.writestr(
+                    "manifest.json",
+                    json.dumps(
+                        {
+                            "files": {
+                                "files/_/_/bundle.min.js": {
+                                    "url": "~/bundle.min.js",
+                                    "type": "minified_source",
+                                    "headers": {
+                                        "content-type": "application/json",
+                                    },
+                                },
+                                "files/_/_/bundle.min.js.map": {
+                                    "url": "~/bundle.min.js.map",
+                                    "type": "source_map",
+                                    "headers": {
+                                        "content-type": "application/json",
+                                    },
+                                },
+                            },
+                        }
+                    ),
+                )
+            compressed.seek(0)
+
+            file_obj = File.objects.create(name="artifact_bundle.zip", type="artifact.bundle")
+            file_obj.putfile(compressed)
+
+            event = self.store_event(
+                data=create_event(
+                    exceptions=[
+                        create_exception_with_frame(
+                            {"abs_path": "http://example.com/bundle.min.js"}
+                        )
+                    ],
+                    release="some-release",
+                ),
+                project_id=self.project.id,
+            )
+
+            artifact_bundle = ArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                file=file_obj,
+                artifact_count=1,
+            )
+
+            ProjectArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                project_id=self.project.id,
+                artifact_bundle=artifact_bundle,
+            )
+
+            ReleaseArtifactBundle.objects.create(
+                organization_id=self.organization.id,
+                release_name="some-release",
+                artifact_bundle=artifact_bundle,
+            )
+
+            ArtifactBundleIndex.objects.create(
+                organization_id=self.organization.id,
+                artifact_bundle=artifact_bundle,
+                url="~/bundle.min.js",
+            )
+
+            ArtifactBundleIndex.objects.create(
+                organization_id=self.organization.id,
+                artifact_bundle=artifact_bundle,
+                url="~/bundle.min.js.map",
+            )
+
+            resp = self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                event.event_id,
+            )
+
+            release_process_result = resp.data["exceptions"][0]["frames"][0]["release_process"]
+
+            assert release_process_result["source_file_lookup_result"] == "found"
+            assert release_process_result["source_map_lookup_result"] == "found"
+            assert release_process_result["source_map_reference"] == "bundle.min.js.map"
+            assert release_process_result["matching_source_map_name"] == "~/bundle.min.js.map"