  # #region security_badge_service [C:4] [TYPE Module] [SEMANTICS profile,security,permissions,badges]
  # @defgroup Services Module group.
  # @BRIEF Builds security summary and permission badges for profile UI — role extraction,
  #        permission pair collection, and catalog formatting.
  # @LAYER Domain
  # @RELATION DEPENDS_ON -> [User]
  # @RELATION DEPENDS_ON -> [discover_declared_permissions]
  # @RELATION DEPENDS_ON -> [ProfileUtils]
  # @RATIONALE Extracted from ProfileService to satisfy INV_7. Security badge construction
  #            is a distinct concern with its own dependencies (discover_declared_permissions,
  #            plugin_loader) and can be tested independently.
  # @REJECTED Keeping security badge logic inside ProfileService was rejected — it adds
  #            plugin_loader coupling to preference operations that do not need it and
  #            inflates the class past 150 lines.
  # @PRE Plugin loader may be None; user must have roles/permissions graph.
  # @POST Returns deterministic security projection for profile UI with sorted permissions.
  # @SIDE_EFFECT Reads plugin permissions via discover_declared_permissions when plugin_loader is set.
  
> from typing import Any
  
> from ..core.logger import belief_scope, logger
> from ..models.auth import User
> from ..schemas.profile import ProfilePermissionState, ProfileSecuritySummary
> from .profile_utils import sanitize_text
> from .rbac_permission_catalog import discover_declared_permissions
  
  
  # #region SecurityBadgeService [C:4] [TYPE Class] [SEMANTICS security,permission,badge,profile]
  # @defgroup Services Module group.
  # @BRIEF Builds security summary with role names and permission badges for profile UI display.
  # @RELATION DEPENDS_ON -> [User]
  # @RELATION CALLS -> [discover_declared_permissions]
  # @PRE plugin_loader may be None for degraded permission discovery.
  # @POST build_security_summary returns ProfileSecuritySummary with deterministic sorted permissions.
> class SecurityBadgeService:
>     """Builds security summary with role names and permission badges."""
  
      # #region __init__ [TYPE Function]
      # @BRIEF Initialize service with optional plugin loader for permission discovery.
>     def __init__(self, plugin_loader: Any = None):
>         self.plugin_loader = plugin_loader
      # #endregion __init__
  
      # #region build_security_summary [TYPE Function]
  # @ingroup Services
      # @BRIEF Build read-only security snapshot with role and permission badges.
      # @PRE current_user is authenticated.
      # @POST Returns deterministic security projection for profile UI.
>     def build_security_summary(self, current_user: User) -> ProfileSecuritySummary:
>         with belief_scope("SecurityBadgeService.build_security_summary"):
>             role_names_set: set[str] = set()
>             roles = getattr(current_user, "roles", []) or []
>             for role in roles:
>                 normalized_role_name = sanitize_text(getattr(role, "name", None))
>                 if normalized_role_name:
>                     role_names_set.add(normalized_role_name)
>             role_names = sorted(role_names_set)
  
>             is_admin = any(str(role_name).lower() == "admin" for role_name in role_names)
>             user_permission_pairs = self._collect_user_permission_pairs(current_user)
  
>             declared_permission_pairs: set[tuple[str, str]] = set()
>             try:
>                 discovered_permissions = discover_declared_permissions(
>                     plugin_loader=self.plugin_loader
>                 )
>                 for resource, action in discovered_permissions:
>                     normalized_resource = sanitize_text(resource)
>                     normalized_action = str(action or "").strip().upper()
>                     if normalized_resource and normalized_action:
>                         declared_permission_pairs.add(
>                             (normalized_resource, normalized_action)
>                         )
>             except Exception as discovery_error:
>                 logger.explore(
>                     "Failed to build declared permission catalog",
>                     extra={"error": str(discovery_error), "src": "SecurityBadgeService.build_security_summary"},
>                 )
  
>             if not declared_permission_pairs:
>                 declared_permission_pairs = set(user_permission_pairs)
  
>             sorted_permission_pairs = sorted(
>                 declared_permission_pairs,
>                 key=lambda pair: (pair[0], pair[1]),
>             )
>             permission_states = [
>                 ProfilePermissionState(
>                     key=self._format_permission_key(resource, action),
>                     allowed=bool(is_admin or (resource, action) in user_permission_pairs),
>                 )
>                 for resource, action in sorted_permission_pairs
>             ]
  
>             auth_source = sanitize_text(getattr(current_user, "auth_source", None))
>             current_role = "Admin" if is_admin else (role_names[0] if role_names else None)
  
>             return ProfileSecuritySummary(
>                 read_only=True,
>                 auth_source=auth_source,
>                 current_role=current_role,
>                 role_source=auth_source,
>                 roles=role_names,
>                 permissions=permission_states,
>             )
      # #endregion build_security_summary
  
      # #region _collect_user_permission_pairs [TYPE Function]
      # @BRIEF Collect effective permission tuples from current user's roles.
      # @PRE current_user can include role/permission graph.
      # @POST Returns unique normalized (resource, ACTION) tuples.
>     def _collect_user_permission_pairs(
>         self, current_user: User
>     ) -> set[tuple[str, str]]:
>         collected: set[tuple[str, str]] = set()
>         roles = getattr(current_user, "roles", []) or []
>         for role in roles:
>             permissions = getattr(role, "permissions", []) or []
>             for permission in permissions:
>                 resource = sanitize_text(getattr(permission, "resource", None))
>                 action = str(getattr(permission, "action", "") or "").strip().upper()
>                 if resource and action:
>                     collected.add((resource, action))
>         return collected
      # #endregion _collect_user_permission_pairs
  
      # #region _format_permission_key [C:1] [TYPE Function]
      # @BRIEF Convert normalized permission pair to compact UI key.
>     def _format_permission_key(self, resource: str, action: str) -> str:
>         normalized_resource = sanitize_text(resource) or ""
>         normalized_action = str(action or "").strip().upper()
>         if normalized_action == "READ":
>             return normalized_resource
>         return f"{normalized_resource}:{normalized_action.lower()}"
      # #endregion _format_permission_key
  # #endregion SecurityBadgeService
  # #endregion security_badge_service
