Files
orion/.architecture-rules.yaml
Samir Boulahtit e116be1a2c refactor(arch): revamp API-003 rule for exception handling pattern
Change API-003 from "must catch exceptions" to "must NOT raise HTTPException
directly". This aligns with the architecture where:
- Domain exceptions bubble up to global exception handler
- Endpoints should NOT catch and convert exceptions
- HTTPException should only be used by the global handler

Also adds:
- EXC-004: Domain exceptions must inherit from WizamartException
- EXC-005: Global exception handler must be registered

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 21:33:57 +01:00

719 lines
23 KiB
YAML

# Architecture Rules Configuration
# This file defines the key architectural decisions and patterns that must be followed
# across the application. The validator script uses these rules to check compliance.
version: "2.0"
project: "letzshop-product-import"
description: "Comprehensive architectural rules for multi-tenant e-commerce platform"
# ============================================================================
# CORE ARCHITECTURAL PRINCIPLES
# ============================================================================
principles:
- name: "Separation of Concerns"
description: "API endpoints should only handle HTTP concerns. Business logic belongs in services."
- name: "Layered Architecture"
description: "Routes → Services → Models. Each layer has specific responsibilities."
- name: "Type Safety"
description: "Use Pydantic models for request/response validation. Use SQLAlchemy models for database."
- name: "Proper Exception Handling"
description: "Services throw domain exceptions. Global handler converts to HTTP responses. Endpoints do NOT catch or raise HTTPException."
- name: "Multi-Tenancy"
description: "All queries must be scoped to vendor_id. No cross-vendor data access."
- name: "Consistent Naming"
description: "API files: plural, Services: singular+service, Models: singular"
# ============================================================================
# API ENDPOINT RULES (app/api/v1/**/*.py)
# ============================================================================
api_endpoint_rules:
- id: "API-001"
name: "Endpoint must use Pydantic models for request/response"
severity: "error"
description: |
All API endpoints must use Pydantic models (BaseModel) for request bodies
and response models. Never use raw dicts or SQLAlchemy models directly.
pattern:
file_pattern: "app/api/v1/**/*.py"
anti_patterns:
- "return dict"
- "-> dict"
- "return db_object"
example_good: |
@router.post("/vendors", response_model=VendorResponse)
async def create_vendor(vendor: VendorCreate):
return vendor_service.create_vendor(db, vendor)
- id: "API-002"
name: "Endpoint must NOT contain business logic"
severity: "error"
description: |
API endpoints should only handle HTTP concerns (validation, auth, response formatting).
All business logic must be delegated to service layer.
pattern:
file_pattern: "app/api/v1/**/*.py"
anti_patterns:
- "db.add("
- "db.commit()"
- "db.query("
- id: "API-003"
name: "Endpoint must NOT raise HTTPException directly"
severity: "error"
description: |
API endpoints should NOT raise HTTPException directly. Instead, let domain
exceptions bubble up to the global exception handler which converts them
to appropriate HTTP responses. This ensures consistent error formatting
and centralized error handling.
pattern:
file_pattern: "app/api/v1/**/*.py"
anti_patterns:
- "raise HTTPException"
exceptions:
- "app/exceptions/handler.py" # Handler is allowed to use HTTPException
- id: "API-004"
name: "Endpoint must have proper authentication/authorization"
severity: "warning"
description: |
Protected endpoints must use Depends() for authentication.
Use get_current_user, get_current_admin, etc.
pattern:
file_pattern: "app/api/v1/**/*.py"
required_if_not_public:
- "Depends(get_current_"
- id: "API-005"
name: "Multi-tenant endpoints must scope queries to vendor_id"
severity: "error"
description: |
All queries in vendor/shop contexts must filter by vendor_id.
Use request.state.vendor_id from middleware.
pattern:
file_pattern: "app/api/v1/vendor/**/*.py"
file_pattern: "app/api/v1/shop/**/*.py"
discouraged_patterns:
- "db.query(.*).all()" # Without vendor filter
# ============================================================================
# SERVICE LAYER RULES (app/services/**/*.py)
# ============================================================================
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"
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"
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"
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"
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"
check: "vendor_scoping"
# ============================================================================
# MODEL RULES (models/database/*.py, models/schema/*.py)
# ============================================================================
model_rules:
- id: "MDL-001"
name: "Database models must use SQLAlchemy Base"
severity: "error"
description: |
All database models must inherit from SQLAlchemy Base and use proper
column definitions with types and constraints.
pattern:
file_pattern: "models/database/**/*.py"
required_patterns:
- "class.*\\(Base\\):"
- id: "MDL-002"
name: "Use Pydantic models separately from SQLAlchemy models"
severity: "error"
description: |
Never mix SQLAlchemy and Pydantic in the same model.
SQLAlchemy = database schema, Pydantic = API validation/serialization.
pattern:
file_pattern: "models/**/*.py"
anti_patterns:
- "class.*\\(Base, BaseModel\\):"
- id: "MDL-003"
name: "Pydantic models must use from_attributes for ORM mode"
severity: "error"
description: |
Pydantic response models must enable from_attributes to work with SQLAlchemy models.
pattern:
file_pattern: "models/schema/**/*.py"
required_in_response_models:
- "from_attributes = True"
- id: "MDL-004"
name: "Database models use singular table names"
severity: "warning"
description: |
Database table names should be singular lowercase (e.g., 'vendor' not 'vendors').
pattern:
file_pattern: "models/database/**/*.py"
check: "table_naming"
# ============================================================================
# EXCEPTION HANDLING RULES
# ============================================================================
exception_rules:
- id: "EXC-001"
name: "Define custom exceptions in exceptions module"
severity: "warning"
description: |
Create domain-specific exceptions in app/exceptions/ for better
error handling and clarity.
pattern:
file_pattern: "app/exceptions/**/*.py"
encouraged_structure: |
class VendorError(Exception):
"""Base exception for vendor-related errors"""
pass
- id: "EXC-002"
name: "Never use bare except"
severity: "error"
description: |
Always specify exception types. Bare except catches everything including
KeyboardInterrupt and SystemExit.
pattern:
file_pattern: "**/*.py"
anti_patterns:
- "except:"
- "except\\s*:"
- id: "EXC-003"
name: "Log all exceptions with context"
severity: "warning"
description: |
When catching exceptions, log them with context and stack trace.
pattern:
file_pattern: "app/services/**/*.py"
encouraged_patterns:
- "logger.error"
- "exc_info=True"
- id: "EXC-004"
name: "Domain exceptions must inherit from WizamartException"
severity: "error"
description: |
All custom domain exceptions must inherit from WizamartException (or its
subclasses like ResourceNotFoundException, ValidationException, etc.).
This ensures the global exception handler catches and converts them properly.
pattern:
file_pattern: "app/exceptions/**/*.py"
required_base_class: "WizamartException"
example_good: |
class VendorNotFoundException(ResourceNotFoundException):
def __init__(self, vendor_code: str):
super().__init__(resource_type="Vendor", identifier=vendor_code)
- id: "EXC-005"
name: "Exception handler must be registered"
severity: "error"
description: |
The global exception handler must be set up in app initialization to
catch WizamartException and convert to HTTP responses.
pattern:
file_pattern: "app/main.py"
required_patterns:
- "setup_exception_handlers"
# ============================================================================
# NAMING CONVENTION RULES
# ============================================================================
naming_rules:
- id: "NAM-001"
name: "API files use PLURAL names"
severity: "error"
description: |
API endpoint files should use plural names (vendors.py, products.py)
pattern:
file_pattern: "app/api/v1/**/*.py"
check: "plural_naming"
exceptions:
- "__init__.py"
- "auth.py"
- "health.py"
- id: "NAM-002"
name: "Service files use SINGULAR + 'service' suffix"
severity: "error"
description: |
Service files should use singular name + _service (vendor_service.py)
pattern:
file_pattern: "app/services/**/*.py"
check: "service_naming"
- id: "NAM-003"
name: "Model files use SINGULAR names"
severity: "error"
description: |
Both database and schema model files use singular names (product.py)
pattern:
file_pattern: "models/**/*.py"
check: "singular_naming"
- id: "NAM-004"
name: "Use consistent terminology: vendor not shop"
severity: "warning"
description: |
Use 'vendor' consistently, not 'shop' (except for shop frontend)
pattern:
file_pattern: "app/**/*.py"
discouraged_terms:
- "shop_id" # Use vendor_id
- "shop_service" # Use vendor_service
- id: "NAM-005"
name: "Use consistent terminology: inventory not stock"
severity: "warning"
description: |
Use 'inventory' consistently, not 'stock'
pattern:
file_pattern: "app/**/*.py"
discouraged_terms:
- "stock_service" # Use inventory_service
# ============================================================================
# JAVASCRIPT ARCHITECTURE RULES (Frontend)
# ============================================================================
javascript_rules:
- id: "JS-001"
name: "Use centralized logger, not console"
severity: "error"
description: |
Use window.LogConfig.createLogger() for consistent logging.
Never use console.log, console.error, console.warn directly.
pattern:
file_pattern: "static/**/js/**/*.js"
anti_patterns:
- "console\\.log"
- "console\\.error"
- "console\\.warn"
exceptions:
- "// eslint-disable"
- "console.log('✅" # Bootstrap messages allowed
- id: "JS-002"
name: "Use lowercase apiClient for API calls"
severity: "error"
description: |
Use lowercase 'apiClient' consistently, not 'ApiClient' or 'API_CLIENT'
pattern:
file_pattern: "static/**/js/**/*.js"
anti_patterns:
- "ApiClient\\."
- "API_CLIENT\\."
required_pattern: "apiClient\\."
- id: "JS-003"
name: "Alpine components must spread ...data()"
severity: "error"
description: |
All Alpine.js components must inherit base layout data using spread operator
pattern:
file_pattern: "static/**/js/**/*.js"
required_in_alpine_components:
- "\\.\\.\\.data\\(\\)"
- id: "JS-004"
name: "Alpine components must set currentPage"
severity: "error"
description: |
All Alpine.js page components must set a currentPage identifier
pattern:
file_pattern: "static/**/js/**/*.js"
required_in_alpine_components:
- "currentPage:"
- id: "JS-005"
name: "Initialization methods must include guard"
severity: "error"
description: |
Init methods should prevent duplicate initialization with guard
pattern:
file_pattern: "static/**/js/**/*.js"
recommended_pattern: |
if (window._pageInitialized) return;
window._pageInitialized = true;
- id: "JS-006"
name: "All async operations must have try/catch with error logging"
severity: "error"
description: |
All API calls and async operations must have error handling
pattern:
file_pattern: "static/**/js/**/*.js"
check: "async_error_handling"
- id: "JS-007"
name: "Set loading state before async operations"
severity: "warning"
description: |
Loading state should be set before and cleared after async operations
pattern:
file_pattern: "static/**/js/**/*.js"
recommended_pattern: |
loading = true;
try {
// operation
} finally {
loading = false;
}
# ============================================================================
# TEMPLATE RULES (Jinja2)
# ============================================================================
template_rules:
- id: "TPL-001"
name: "Admin templates must extend admin/base.html"
severity: "error"
description: "All admin templates must extend the base template for consistency"
pattern:
file_pattern: "app/templates/admin/**/*.html"
required_patterns:
- "{% extends ['\"]admin/base\\.html['\"] %}"
exceptions:
- "base.html"
- "partials/"
- id: "TPL-002"
name: "Vendor templates must extend vendor/base.html"
severity: "error"
description: "All vendor templates must extend the base template"
pattern:
file_pattern: "app/templates/vendor/**/*.html"
required_patterns:
- "{% extends ['\"]vendor/base\\.html['\"] %}"
exceptions:
- "base.html"
- "partials/"
- id: "TPL-003"
name: "Shop templates must extend shop/base.html"
severity: "error"
description: "All shop templates must extend the base template"
pattern:
file_pattern: "app/templates/shop/**/*.html"
required_patterns:
- "{% extends ['\"]shop/base\\.html['\"] %}"
exceptions:
- "base.html"
- "partials/"
- id: "TPL-004"
name: "Use x-text for dynamic text content (prevents XSS)"
severity: "warning"
description: |
Use x-text directive for dynamic content to prevent XSS vulnerabilities
pattern:
file_pattern: "app/templates/**/*.html"
recommended_pattern: '<p x-text="item.name"></p>'
- id: "TPL-005"
name: "Use x-html ONLY for safe content"
severity: "error"
description: |
Use x-html only for trusted content like icons, never for user-generated content
pattern:
file_pattern: "app/templates/**/*.html"
safe_usage:
- 'x-html="\\$icon\\('
- id: "TPL-006"
name: "Implement loading state for data loads"
severity: "warning"
description: |
All templates that load data should show loading state
pattern:
file_pattern: "app/templates/**/*.html"
recommended_pattern: '<div x-show="loading">Loading...</div>'
- id: "TPL-007"
name: "Implement empty state when no data"
severity: "warning"
description: |
Show empty state when lists have no items
pattern:
file_pattern: "app/templates/**/*.html"
recommended_pattern: '<template x-if="items.length === 0">No items</template>'
# ============================================================================
# FRONTEND STYLING RULES
# ============================================================================
styling_rules:
- id: "CSS-001"
name: "Use Tailwind utility classes"
severity: "warning"
description: |
Prefer Tailwind utility classes over custom CSS
pattern:
file_pattern: "app/templates/**/*.html"
encouraged: true
- id: "CSS-002"
name: "Support dark mode with dark: prefix"
severity: "warning"
description: |
All color classes should include dark mode variants
pattern:
file_pattern: "app/templates/**/*.html"
recommended_pattern: 'class="bg-white dark:bg-gray-800"'
- id: "CSS-003"
name: "Shop templates use vendor theme CSS variables"
severity: "error"
description: |
Shop templates must use CSS variables for vendor-specific theming
pattern:
file_pattern: "app/templates/shop/**/*.html"
required_pattern: 'var\\(--color-primary\\)'
- id: "CSS-004"
name: "Mobile-first responsive design"
severity: "warning"
description: |
Use mobile-first responsive classes
pattern:
file_pattern: "app/templates/**/*.html"
recommended_pattern: 'class="grid-cols-1 md:grid-cols-2 lg:grid-cols-4"'
# ============================================================================
# MIDDLEWARE RULES
# ============================================================================
middleware_rules:
- id: "MDW-001"
name: "Middleware files use simple nouns without _middleware suffix"
severity: "warning"
description: |
Middleware files should be named with simple nouns (auth.py, not auth_middleware.py)
pattern:
file_pattern: "middleware/**/*.py"
check: "middleware_naming"
- id: "MDW-002"
name: "Vendor context must be injected for vendor/shop routes"
severity: "error"
description: |
Vendor context middleware must set request.state.vendor_id and request.state.vendor
pattern:
file_pattern: "middleware/vendor_context.py"
required: true
# ============================================================================
# MULTI-TENANCY RULES
# ============================================================================
multi_tenancy_rules:
- id: "MT-001"
name: "All queries must be scoped to vendor_id"
severity: "error"
description: |
In vendor/shop contexts, all database queries must filter by vendor_id
pattern:
file_pattern: "app/services/**/*.py"
context: "vendor_shop"
required_pattern: ".filter\\(.*vendor_id.*\\)"
- id: "MT-002"
name: "No cross-vendor data access"
severity: "error"
description: |
Queries must never access data from other vendors
pattern:
file_pattern: "app/services/**/*.py"
enforcement: "database_query_level"
# ============================================================================
# AUTHENTICATION & AUTHORIZATION RULES
# ============================================================================
auth_rules:
- id: "AUTH-001"
name: "Use JWT tokens in Authorization header"
severity: "error"
description: |
Authentication must use JWT tokens in Authorization: Bearer header
pattern:
file_pattern: "app/api/**/*.py"
enforcement: "middleware"
- id: "AUTH-002"
name: "Role-based access control with Depends"
severity: "error"
description: |
Use Depends(get_current_admin/vendor/customer) for role checks
pattern:
file_pattern: "app/api/v1/**/*.py"
required: "Depends\\(get_current_"
- id: "AUTH-003"
name: "Never store plain passwords"
severity: "error"
description: |
Always hash passwords with bcrypt before storing
pattern:
file_pattern: "app/services/auth_service.py"
required: "bcrypt"
# ============================================================================
# CODE QUALITY RULES
# ============================================================================
code_quality_rules:
- id: "QUAL-001"
name: "All code must be formatted with Ruff"
severity: "error"
description: |
Run 'make format' before committing
enforcement: "pre_commit"
- id: "QUAL-002"
name: "All code must pass Ruff linting"
severity: "error"
description: |
Run 'make lint' before committing
enforcement: "pre_commit"
- id: "QUAL-003"
name: "Type hints recommended for functions"
severity: "warning"
description: |
Add type hints to function parameters and return types
pattern:
file_pattern: "app/**/*.py"
encouraged: true
# ============================================================================
# VALIDATION SEVERITY LEVELS
# ============================================================================
severity_levels:
error:
description: "Critical architectural violation - must be fixed"
exit_code: 1
warning:
description: "Pattern deviation - should be fixed"
exit_code: 0
info:
description: "Suggestion for improvement"
exit_code: 0
# ============================================================================
# IGNORED PATTERNS
# ============================================================================
ignore:
files:
- "**/*_test.py"
- "**/test_*.py"
- "**/__pycache__/**"
- "**/migrations/**"
- "**/alembic/versions/**"
- "**/node_modules/**"
- "**/.venv/**"
- "**/venv/**"
- ".venv/**"
- "venv/**"
- "**/build/**"
- "**/dist/**"
patterns:
# Allow HTTPException in specific files
- file: "app/core/exceptions.py"
pattern: "HTTPException"
reason: "Exception handling utilities"
- file: "app/exceptions/handler.py"
pattern: "HTTPException"
reason: "Exception handler converts to HTTP"
# ============================================================================
# DOCUMENTATION
# ============================================================================
documentation:
architecture: "docs/architecture/overview.md"
backend: "docs/backend/overview.md"
frontend: "docs/frontend/overview.md"
contributing: "docs/development/contributing.md"
code_quality: "docs/development/code-quality.md"