fix(hc): Restructure OrganizationView base classes
diff --git a/src/sentry/web/frontend/base.py b/src/sentry/web/frontend/base.py
index d9ed43005b1eb..5ae4ffda6fbd4 100644
--- a/src/sentry/web/frontend/base.py
+++ b/src/sentry/web/frontend/base.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import abc
 import inspect
 import logging
 from typing import Any, Mapping, Protocol
@@ -418,12 +419,8 @@ def handle_disabled_member(self, organization: Organization) -> HttpResponse:
         return self.redirect(redirect_uri)
 
 
-class OrganizationView(BaseView):
+class AbstractOrganizationView(BaseView, abc.ABC):
     """
-    A deprecated view used by endpoints that act on behalf of an organization.
-    In the future, we should move endpoints to either of the subclasses, RegionSilo* or ControlSilo*, and
-    move out any ORM specific logic into the correct silo view.  This will likely become an ABC that shares some
-    common logic.
     The 'organization' keyword argument is automatically injected into the resulting dispatch, but currently the
     typing of 'organization' will vary based on the subclass.  It may either be an RpcOrganization or an orm
     Organization based on the subclass.  Be mindful during this transition of the typing.
@@ -543,58 +540,45 @@ def needs_sso(self, request: Request, organization: Organization | RpcOrganizati
             return True
         return False
 
-    def _lookup_orm_org(self) -> Organization | None:
-        """
-        Used by convert_args to convert the hybrid cloud safe active_organization object into an org ORM.
-        This should really only be used by the Region or Monolith silo modes -- calling this in a Control silo
-        endpoint or codepath will result in exceptions.
-        :return:
-        """
-        organization: Organization | None = None
-        if self.active_organization:
-            try:
-                organization = Organization.objects.get(id=self.active_organization.organization.id)
-            except Organization.DoesNotExist:
-                pass
-        return organization
+    @abc.abstractmethod
+    def _get_organization(self) -> Organization | RpcOrganization | None:
+        raise NotImplementedError
 
     def convert_args(
         self, request: Request, organization_slug: str | None = None, *args: Any, **kwargs: Any
     ) -> tuple[tuple[Any, ...], dict[str, Any]]:
         if "organization" not in kwargs:
-            kwargs["organization"] = self._lookup_orm_org()
+            kwargs["organization"] = self._get_organization()
 
-        return args, kwargs
+        return super().convert_args(request, *args, **kwargs)
 
 
-class RegionSiloOrganizationView(OrganizationView):
+class OrganizationView(AbstractOrganizationView):
     """
-    A view which has direct ORM access to organization objects.  In practice, **only endpoints that exist in the
-    region silo should use this class**.  When All endpoints have been convert / tested against region silo compliance,
-    the base class (OrganizationView) will likely disappear and only either ControlSilo* or RegionSilo* classes will
-    remain.
+    A view which has direct ORM access to organization objects.  Only endpoints that exist in the
+    region silo should use this class.
     """
 
-    def convert_args(
-        self, request: Any, organization_slug: str | None = None, *args: Any, **kwargs: Any
-    ) -> tuple[tuple[Any, ...], dict[str, Any]]:
-        if "organization" not in kwargs:
-            kwargs["organization"] = self._lookup_orm_org()
+    def _get_organization(self) -> Organization | None:
+        if not self.active_organization:
+            return None
+        try:
+            return Organization.objects.get(id=self.active_organization.organization.id)
+        except Organization.DoesNotExist:
+            return None
 
-        return args, kwargs
 
+class ControlSiloOrganizationView(AbstractOrganizationView):
+    """A view which accesses organization objects over RPC.
 
-class ControlSiloOrganizationView(OrganizationView):
-    def convert_args(
-        self, request: Any, *args: Any, **kwargs: Any
-    ) -> tuple[tuple[Any, ...], dict[str, Any]]:
-        kwargs["organization"] = (
-            self.active_organization.organization if self.active_organization else None
-        )
-        return super().convert_args(request, *args, **kwargs)
+    Only endpoints on the control silo should use this class (but it works anywhere).
+    """
+
+    def _get_organization(self) -> RpcOrganization | None:
+        return self.active_organization.organization if self.active_organization else None
 
 
-class ProjectView(RegionSiloOrganizationView):
+class ProjectView(OrganizationView):
     """
     Any view acting on behalf of a project should inherit from this base and the
     matching URL pattern must pass 'org_slug' as well as 'project_slug'.
@@ -640,7 +624,7 @@ def convert_args(self, request: Request, organization_slug: str, project_slug: s
         organization: Organization | None = None
         active_project: Project | None = None
         if self.active_organization:
-            organization = self._lookup_orm_org()
+            organization = self._get_organization()
 
             if organization:
                 active_project = self.get_active_project(
diff --git a/src/sentry/web/frontend/react_page.py b/src/sentry/web/frontend/react_page.py
index 0fb8e2244c9ed..04fa6f7d4e6e7 100644
--- a/src/sentry/web/frontend/react_page.py
+++ b/src/sentry/web/frontend/react_page.py
@@ -14,7 +14,7 @@
 from sentry.services.hybrid_cloud.organization import organization_service
 from sentry.signals import first_event_pending
 from sentry.utils.http import is_using_customer_domain, query_string
-from sentry.web.frontend.base import BaseView, OrganizationView
+from sentry.web.frontend.base import BaseView, ControlSiloOrganizationView
 from sentry.web.helpers import render_to_response
 
 # url names that should only be accessible from a non-customer domain hostname.
@@ -98,7 +98,7 @@ def handle_react(self, request: Request, **kwargs) -> Response:
 
 # TODO(dcramer): once we implement basic auth hooks in React we can make this
 # generic
-class ReactPageView(OrganizationView, ReactMixin):
+class ReactPageView(ControlSiloOrganizationView, ReactMixin):
     def handle_auth_required(self, request: Request, *args, **kwargs) -> Response:
         # If user is a superuser (but not active, because otherwise this method would never be called)
         # Then allow client to handle the route and respond to any API request errors

Make `OrganizationView` abstract and rename it to `AbstractOrganizationView`. Refactor `_lookup_orm_org` into an abstract method. Have `RegionSiloOrganizationView` replace `OrganizationView` as the typical case that loads an `Organization` ORM model. Have `ControlSiloOrganizationView` load an `RpcOrganization` instead.