# Architecture Rules - Service Layer Rules
# Rules for app/services/**/*.py and app/modules/*/services/**/*.py files

service_layer_rules:

  - id: "SVC-001"
    name: "Service must NOT raise HTTPException"
    severity: "error"
    description: |
      Services are business logic layer - they should NOT know about HTTP.
      Raise domain-specific exceptions instead (ValueError, custom exceptions).
    pattern:
      file_pattern:
        - "app/services/**/*.py"
        - "app/modules/*/services/**/*.py"
      anti_patterns:
        - "raise HTTPException"
        - "from fastapi import HTTPException"

  - id: "SVC-002"
    name: "Service must use proper exception handling"
    severity: "error"
    description: |
      Services should raise meaningful domain exceptions, not generic Exception.
      Create custom exception classes for business rule violations.
    pattern:
      file_pattern:
        - "app/services/**/*.py"
        - "app/modules/*/services/**/*.py"
      discouraged_patterns:
        - "raise Exception\\("

  - id: "SVC-003"
    name: "Service methods must accept db session as parameter"
    severity: "error"
    description: |
      Service methods should receive database session as a parameter for testability
      and transaction control. Never create session inside service.
    pattern:
      file_pattern:
        - "app/services/**/*.py"
        - "app/modules/*/services/**/*.py"
      required_in_method_signature:
        - "db: Session"
      anti_patterns:
        - "SessionLocal()"
        - "get_db()"

  - id: "SVC-004"
    name: "Service must use Pydantic models for input validation"
    severity: "warning"
    description: |
      Service methods should accept Pydantic models for complex inputs
      to ensure type safety and validation.
    pattern:
      file_pattern:
        - "app/services/**/*.py"
        - "app/modules/*/services/**/*.py"
      encouraged_patterns:
        - "BaseModel"

  - id: "SVC-005"
    name: "Service must scope queries to vendor_id in multi-tenant contexts"
    severity: "error"
    description: |
      All database queries must be scoped to vendor_id to prevent cross-tenant data access.
    pattern:
      file_pattern:
        - "app/services/**/*.py"
        - "app/modules/*/services/**/*.py"
      check: "vendor_scoping"

  - id: "SVC-006"
    name: "Service must NOT call db.commit()"
    severity: "warning"
    description: |
      Services should NOT commit transactions. Transaction control belongs at the
      API endpoint level where one request = one transaction.

      This allows:
      - Composing multiple service calls in a single transaction
      - Clean rollback on any failure
      - Easier testing of services in isolation

      The endpoint should call db.commit() after all service operations succeed.
    pattern:
      file_pattern:
        - "app/services/**/*.py"
        - "app/modules/*/services/**/*.py"
      anti_patterns:
        - "db.commit()"
      exceptions:
        - "log_service.py"
        - "card_service.py"
        - "wallet_service.py"
        - "program_service.py"
        - "points_service.py"
        - "apple_wallet_service.py"
        - "pin_service.py"
        - "stamp_service.py"
        - "google_wallet_service.py"
        - "theme_presets.py"

  - id: "SVC-007"
    name: "Service return types must match API response schemas"
    severity: "error"
    description: |
      When a service method's return value will be used as an API response,
      the returned dict keys MUST match the corresponding Pydantic schema fields.

      This prevents the common bug where:
      - Service returns {"total_imports": 5, "completed_imports": 3}
      - Schema expects {"total": 5, "completed": 3}
      - Frontend receives wrong/empty values

      RECOMMENDED PATTERNS:

      1. Return Pydantic model directly from service:
         def get_stats(self, db: Session) -> StatsResponse:
             return StatsResponse(total=count, completed=done)

      2. Return dict with schema-matching keys:
         def get_stats(self, db: Session) -> dict:
             return {"total": count, "completed": done}  # Matches StatsResponse

      3. Document the expected schema in service docstring:
         def get_stats(self, db: Session) -> dict:
             """
             Returns dict compatible with StatsResponse schema.
             Keys: total, pending, completed, failed
             """

      TESTING: Write tests that validate service output against schema:
         result = service.get_stats(db)
         StatsResponse(**result)  # Raises if keys don't match
    pattern:
      file_pattern:
        - "app/services/**/*.py"
        - "app/modules/*/services/**/*.py"
      check: "schema_compatibility"
