## Vendor-in-Token Architecture (Complete Migration) - Migrate all vendor API endpoints from require_vendor_context() to token_vendor_id - Update permission dependencies to extract vendor from JWT token - Add vendor exceptions: VendorAccessDeniedException, VendorOwnerOnlyException, InsufficientVendorPermissionsException - Shop endpoints retain require_vendor_context() for URL-based detection - Add AUTH-004 architecture rule enforcing vendor context patterns - Fix marketplace router missing /marketplace prefix ## Exception Pattern Fixes (API-003/API-004) - Services raise domain exceptions, endpoints let them bubble up - Add code_quality and content_page exception modules - Move business logic from endpoints to services (admin, auth, content_page) - Fix exception handling in admin, shop, and vendor endpoints ## Tailwind CSS Consolidation - Consolidate CSS to per-area files (admin, vendor, shop, platform) - Remove shared/cdn-fallback.html and shared/css/tailwind.min.css - Update all templates to use area-specific Tailwind output files - Remove Node.js config (package.json, postcss.config.js, tailwind.config.js) ## Documentation & Cleanup - Update vendor-in-token-architecture.md with completed migration status - Update architecture-rules.md with new rules - Move migration docs to docs/development/migration/ - Remove duplicate/obsolete documentation files - Merge pytest.ini settings into pyproject.toml 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
21 KiB
Architecture Rules Reference
This document provides a comprehensive reference for all architectural rules enforced by the scripts/validate_architecture.py validator.
Overview
The architecture validator ensures consistent patterns, separation of concerns, and best practices across the entire codebase. Rules are organized by category and severity level.
Version: 2.0
Total Rules: 50+
Configuration File: .architecture-rules.yaml
Running the Validator
Using Make Commands (Recommended)
# Check all files
make arch-check
# Check a single file
make arch-check-file file="app/api/v1/admin/vendors.py"
# Check all files related to an entity (company, vendor, user, etc.)
make arch-check-object name="company"
make arch-check-object name="vendor"
# Full QA (includes arch-check)
make qa
Using Python Directly
# Check all files
python scripts/validate_architecture.py
# Check specific directory
python scripts/validate_architecture.py -d app/api/
# Check a single file
python scripts/validate_architecture.py -f app/api/v1/admin/vendors.py
# Check all files for an entity
python scripts/validate_architecture.py -o company
python scripts/validate_architecture.py -o vendor
# Verbose output
python scripts/validate_architecture.py --verbose
# JSON output (for CI/CD)
python scripts/validate_architecture.py --json
Output Format
The validator displays a summary table for validated files:
📋 FILE SUMMARY:
--------------------------------------------------------------------------------
File Status Errors Warnings
----------------------------- -------- ------- --------
app/api/v1/admin/companies.py ❌ FAILED 6 9
app/services/company_service.py ✅ PASSED 0 0
models/database/company.py ✅ PASSED 0 0
--------------------------------------------------------------------------------
Total: 3 files | ✅ 2 passed | ❌ 1 failed | 6 errors | 9 warnings
Severity Levels
| Severity | Description | Exit Code | Action Required |
|---|---|---|---|
| Error | Critical architectural violation | 1 | Must fix before committing |
| Warning | Pattern deviation | 0 | Should fix, doesn't block |
| Info | Suggestion for improvement | 0 | Optional improvement |
Core Architectural Principles
- Separation of Concerns - API endpoints handle HTTP, services handle business logic
- Layered Architecture - Routes → Services → Models
- Type Safety - Pydantic for API, SQLAlchemy for database
- Proper Exception Handling - Domain exceptions in services, HTTPException in routes
- Multi-Tenancy - All queries scoped to vendor_id
- Consistent Naming - API files: plural, Services: singular+service, Models: singular
Backend Rules
API Endpoint Rules (app/api/v1/**/*.py)
API-001: Use Pydantic Models for Request/Response
Severity: Error
All API endpoints must use Pydantic models (BaseModel) for request bodies and response models. Never use raw dicts or SQLAlchemy models directly.
# ✅ Good
@router.post("/vendors", response_model=VendorResponse)
async def create_vendor(vendor: VendorCreate):
return vendor_service.create_vendor(db, vendor)
# ❌ Bad
@router.post("/vendors")
async def create_vendor(data: dict):
return {"name": data["name"]} # No validation!
API-002: No Business Logic in Endpoints
Severity: Error
API endpoints should only handle HTTP concerns (validation, auth, response formatting). All business logic must be delegated to service layer.
# ✅ Good
@router.post("/vendors")
async def create_vendor(vendor: VendorCreate, db: Session = Depends(get_db)):
result = vendor_service.create_vendor(db, vendor)
return result
# ❌ Bad
@router.post("/vendors")
async def create_vendor(vendor: VendorCreate, db: Session = Depends(get_db)):
db_vendor = Vendor(name=vendor.name)
db.add(db_vendor) # Business logic in endpoint!
db.commit()
return db_vendor
Anti-patterns detected:
db.add(db.commit()db.query(
API-003: No HTTPException in Endpoints
Severity: Error
API endpoints must NOT raise HTTPException directly. Instead, let domain exceptions bubble up to the global exception handler which converts them to appropriate HTTP responses.
# ✅ Good - Let domain exceptions bubble up
@router.post("/vendors")
async def create_vendor(vendor: VendorCreate, db: Session = Depends(get_db)):
# Service raises VendorAlreadyExistsException if duplicate
# Global handler converts to 409 Conflict
return vendor_service.create_vendor(db, vendor)
# ❌ Bad - Don't raise HTTPException directly
@router.post("/vendors")
async def create_vendor(vendor: VendorCreate, db: Session = Depends(get_db)):
if vendor_service.exists(db, vendor.subdomain):
raise HTTPException(status_code=409, detail="Vendor exists") # BAD!
return vendor_service.create_vendor(db, vendor)
Pattern: Services raise domain exceptions → Global handler converts to HTTP responses
API-004: Proper Authentication
Severity: Warning
Protected endpoints must use Depends() for authentication.
# ✅ Good - Protected endpoint with authentication
@router.post("/vendors")
async def create_vendor(
vendor: VendorCreate,
current_user: User = Depends(get_current_admin),
db: Session = Depends(get_db)
):
pass
Auto-Excluded Files:
The validator automatically skips API-004 checks for authentication endpoint files (*/auth.py) since login, logout, and registration endpoints are intentionally public.
Marking Public Endpoints:
For other intentionally public endpoints (webhooks, health checks, etc.), use a comment marker:
# ✅ Good - Webhook endpoint marked as public
# public - Stripe webhook receives external callbacks
@router.post("/webhook/stripe")
def stripe_webhook(request: Request):
...
# ✅ Good - Using noqa style
@router.post("/health") # noqa: API-004
def health_check():
...
Recognized markers:
# public- descriptive marker for intentionally unauthenticated endpoints# noqa: API-004- standard noqa style to suppress the warning
API-005: Multi-Tenant Scoping
Severity: Error
All queries in vendor/shop contexts must filter by vendor_id.
Service Layer Rules (app/services/**/*.py)
SVC-001: No HTTPException in Services
Severity: Error
Services are business logic layer - they should NOT know about HTTP. Raise domain-specific exceptions instead.
# ✅ Good
class VendorService:
def create_vendor(self, db: Session, vendor_data):
if self._vendor_exists(db, vendor_data.subdomain):
raise VendorAlreadyExistsError(f"Vendor {vendor_data.subdomain} exists")
# ❌ Bad
class VendorService:
def create_vendor(self, db: Session, vendor_data):
if self._vendor_exists(db, vendor_data.subdomain):
raise HTTPException(status_code=409, detail="Vendor exists") # BAD!
SVC-002: Use Proper Exception Handling
Severity: Error
Services should raise meaningful domain exceptions, not generic Exception.
# ✅ Good
class VendorAlreadyExistsError(Exception):
pass
def create_vendor(self, db: Session, vendor_data):
if self._vendor_exists(db, vendor_data.subdomain):
raise VendorAlreadyExistsError(f"Subdomain {vendor_data.subdomain} taken")
# ❌ Bad
def create_vendor(self, db: Session, vendor_data):
if self._vendor_exists(db, vendor_data.subdomain):
raise Exception("Vendor exists") # Too generic!
SVC-003: Accept DB Session as Parameter
Severity: Error
Service methods should receive database session as a parameter for testability and transaction control.
# ✅ Good
def create_vendor(self, db: Session, vendor_data: VendorCreate):
vendor = Vendor(**vendor_data.dict())
db.add(vendor)
db.commit()
return vendor
# ❌ Bad
def create_vendor(self, vendor_data: VendorCreate):
db = SessionLocal() # Don't create session inside!
vendor = Vendor(**vendor_data.dict())
db.add(vendor)
db.commit()
SVC-004: Use Pydantic Models for Input
Severity: Warning
Service methods should accept Pydantic models for complex inputs to ensure type safety.
SVC-005: Scope Queries to vendor_id
Severity: Error
All database queries must be scoped to vendor_id to prevent cross-tenant data access.
Model Rules (models/**/*.py)
MDL-001: Database Models Use SQLAlchemy Base
Severity: Error
All database models must inherit from SQLAlchemy Base.
MDL-002: Never Mix SQLAlchemy and Pydantic
Severity: Error
# ❌ Bad
class Product(Base, BaseModel): # NEVER DO THIS!
pass
Keep them separate:
models/database/product.py- SQLAlchemy modelsmodels/schema/product.py- Pydantic models
MDL-003: Use from_attributes in Pydantic
Severity: Error
Pydantic response models must enable from_attributes to work with SQLAlchemy models.
# ✅ Good
class ProductResponse(BaseModel):
id: int
name: str
class Config:
from_attributes = True
MDL-004: Use Singular Table Names
Severity: Warning
Database table names should be singular lowercase (e.g., 'vendor' not 'vendors').
Exception Handling Rules
EXC-001: Define Custom Exceptions
Severity: Warning
Create domain-specific exceptions in app/exceptions/ for better error handling.
# ✅ Good - app/exceptions/vendor.py
class VendorError(Exception):
"""Base exception for vendor-related errors"""
pass
class VendorNotFoundError(VendorError):
pass
class VendorAlreadyExistsError(VendorError):
pass
EXC-002: Never Use Bare Except
Severity: Error
# ❌ Bad
try:
result = service.do_something()
except: # Too broad!
pass
# ✅ Good
try:
result = service.do_something()
except ValueError as e:
logger.error(f"Validation error: {e}")
except Exception as e:
logger.error(f"Unexpected error: {e}")
EXC-003: Log Exceptions with Context
Severity: Warning
When catching exceptions, log them with context and stack trace.
# ✅ Good
try:
result = service.process()
except Exception as e:
logger.error(f"Processing failed: {e}", exc_info=True)
Naming Convention Rules
NAM-001: API Files Use PLURAL Names
Severity: Error
✅ app/api/v1/admin/vendors.py
✅ app/api/v1/admin/products.py
❌ app/api/v1/admin/vendor.py
Exceptions: __init__.py, auth.py, health.py
NAM-002: Service Files Use SINGULAR + service
Severity: Error
✅ app/services/vendor_service.py
✅ app/services/product_service.py
❌ app/services/vendors_service.py
NAM-003: Model Files Use SINGULAR Names
Severity: Error
✅ models/database/product.py
✅ models/schema/vendor.py
❌ models/database/products.py
NAM-004: Use 'vendor' not 'shop'
Severity: Warning
Use consistent terminology: 'vendor' for shop owners, 'shop' only for customer-facing frontend.
# ✅ Good
vendor_id
vendor_service
# ❌ Bad
shop_id
shop_service
NAM-005: Use 'inventory' not 'stock'
Severity: Warning
# ✅ Good
inventory_service
inventory_level
# ❌ Bad
stock_service
stock_level
Frontend Rules
JavaScript Rules (static//js//*.js)
JS-001: Use Centralized Logger
Severity: Error
Never use console.log, console.error, console.warn directly. Use window.LogConfig.createLogger().
// ✅ Good
const pageLog = window.LogConfig.createLogger('dashboard');
pageLog.info('Dashboard loaded');
pageLog.error('Failed to load data', error);
// ❌ Bad
console.log('Dashboard loaded');
console.error('Failed to load data', error);
Exceptions: Bootstrap messages with console.log('✅' allowed.
JS-002: Use Lowercase apiClient
Severity: Error
// ✅ Good
await apiClient.get('/api/v1/vendors');
await apiClient.post('/api/v1/products', data);
// ❌ Bad
await ApiClient.get('/api/v1/vendors');
await API_CLIENT.post('/api/v1/products', data);
JS-003: Alpine Components Must Inherit ...data()
Severity: Error
All Alpine.js components must inherit base layout data using spread operator.
// ✅ Good
function adminDashboard() {
return {
...data(), // Inherit base layout data
currentPage: 'dashboard',
// ... component-specific data
};
}
// ❌ Bad
function adminDashboard() {
return {
currentPage: 'dashboard',
// Missing ...data()!
};
}
JS-004: Set currentPage in Components
Severity: Error
All Alpine.js page components must set a currentPage identifier.
// ✅ Good
return {
...data(),
currentPage: 'dashboard', // Required!
// ...
};
JS-005: Initialization Guards
Severity: Error
Init methods should prevent duplicate initialization.
// ✅ Good
init() {
if (window._pageInitialized) return;
window._pageInitialized = true;
// Initialization logic...
}
JS-006: Error Handling for Async Operations
Severity: Error
All API calls and async operations must have error handling.
// ✅ Good
async loadData() {
try {
const response = await apiClient.get('/api/v1/data');
this.items = response.data;
} catch (error) {
window.LogConfig.logError(error, 'Load Data');
Utils.showToast('Failed to load data', 'error');
}
}
JS-007: Set Loading State
Severity: Warning
Loading state should be set before and cleared after async operations.
// ✅ Good
async loadData() {
this.loading = true;
try {
const response = await apiClient.get('/api/v1/data');
this.items = response.data;
} catch (error) {
// Error handling
} finally {
this.loading = false;
}
}
Template Rules (app/templates/**/*.html)
TPL-001: Admin Templates Extend admin/base.html
Severity: Error
All admin templates must extend the base template for consistent layout (sidebar, navigation, etc.).
{# ✅ Good - Extends base template #}
{% extends "admin/base.html" %}
{% block content %}
...
{% endblock %}
Auto-Excluded Files:
The validator automatically skips TPL-001 checks for:
login.html- Standalone login page (no sidebar/navigation needed)errors/*.html- Error pages extenderrors/base.htmlinsteadtest-*.html- Test/development templatesbase.html- The base template itselfpartials/*.html- Partial templates included in other templates
Marking Standalone Templates:
For other templates that intentionally don't extend base.html, use a comment marker in the first 5 lines:
{# standalone - Minimal monitoring page without admin chrome #}
<!DOCTYPE html>
<html>
...
Recognized markers:
{# standalone #}- Jinja comment style{# noqa: TPL-001 #}- Standard noqa style<!-- standalone -->- HTML comment style
TPL-002: Vendor Templates Extend vendor/base.html
Severity: Error
✅ {% extends "vendor/base.html" %}
TPL-003: Shop Templates Extend shop/base.html
Severity: Error
✅ {% extends "shop/base.html" %}
TPL-004: Use x-text for Dynamic Content
Severity: Warning
Use x-text directive for dynamic content to prevent XSS vulnerabilities.
✅ <p x-text="item.name"></p>
❌ <p>{{ item.name }}</p>
TPL-005: Use x-html ONLY for Safe Content
Severity: Error
Use x-html only for trusted content like icons, never for user-generated content.
✅ <span x-html="$icon('home', 'w-5 h-5')"></span>
❌ <div x-html="userComment"></div> <!-- XSS vulnerability! -->
TPL-006: Implement Loading State
Severity: Warning
All templates that load data should show loading state.
✅ <div x-show="loading">Loading...</div>
TPL-007: Implement Empty State
Severity: Warning
Show empty state when lists have no items.
✅ <template x-if="items.length === 0">
<p>No items found</p>
</template>
Styling Rules (app/templates/**/*.html)
CSS-001: Use Tailwind Utility Classes
Severity: Warning
Prefer Tailwind utility classes over custom CSS.
CSS-002: Support Dark Mode
Severity: Warning
All color classes should include dark mode variants.
✅ class="bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
❌ class="bg-white text-gray-900"
CSS-003: Shop Templates Use CSS Variables
Severity: Error
Shop templates must use CSS variables for vendor-specific theming.
✅ <button style="background-color: var(--color-primary)">Buy Now</button>
❌ <button class="bg-blue-600">Buy Now</button>
CSS-004: Mobile-First Responsive Design
Severity: Warning
Use mobile-first responsive classes.
✅ class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
❌ class="grid grid-cols-4"
Security & Multi-Tenancy Rules
Multi-Tenancy Rules
MT-001: Scope All Queries to vendor_id
Severity: Error
In vendor/shop contexts, all database queries must filter by vendor_id.
# ✅ Good
def get_products(self, db: Session, vendor_id: int):
return db.query(Product).filter(Product.vendor_id == vendor_id).all()
# ❌ Bad
def get_products(self, db: Session):
return db.query(Product).all() # Cross-tenant data leak!
MT-002: No Cross-Vendor Data Access
Severity: Error
Queries must never access data from other vendors.
Authentication & Authorization Rules
AUTH-001: Use JWT Tokens
Severity: Error
Authentication must use JWT tokens in Authorization: Bearer header.
AUTH-002: Role-Based Access Control
Severity: Error
Use Depends(get_current_admin/vendor/customer) for role checks.
# ✅ Good
@router.get("/admin/users")
async def list_users(current_user: User = Depends(get_current_admin)):
pass
AUTH-003: Never Store Plain Passwords
Severity: Error
Always hash passwords with bcrypt before storing.
Middleware Rules
MDW-001: Middleware Naming
Severity: Warning
Middleware files should be named with simple nouns (auth.py, not auth_middleware.py).
✅ middleware/auth.py
✅ middleware/context.py
❌ middleware/auth_middleware.py
MDW-002: Vendor Context Injection
Severity: Error
Vendor context middleware must set request.state.vendor_id and request.state.vendor.
Code Quality Rules
QUAL-001: Format with Ruff
Severity: Error
All code must be formatted with Ruff before committing.
make format
QUAL-002: Pass Ruff Linting
Severity: Error
All code must pass Ruff linting before committing.
make lint
QUAL-003: Use Type Hints
Severity: Warning
Add type hints to function parameters and return types.
# ✅ Good
def create_product(self, db: Session, data: ProductCreate) -> Product:
pass
# Better
from typing import Optional
def get_product(self, product_id: int) -> Optional[Product]:
pass
Ignored Patterns
The validator ignores these files/patterns:
- Test files:
**/*_test.py,**/test_*.py - Cache:
**/__pycache__/** - Migrations:
**/alembic/versions/** - Dependencies:
**/node_modules/**,**/.venv/**,**/venv/** - Build artifacts:
**/build/**,**/dist/**
Special exceptions:
app/core/exceptions.py- Allowed to use HTTPExceptionapp/exceptions/handler.py- Converts to HTTPException
Quick Reference
Pre-Commit Checklist
Before committing code:
- Run
make format(Ruff formatting) - Run
make lint(Ruff + mypy) - Run
python scripts/validate_architecture.py - Fix all Error level violations
- Review Warning level violations
- Run relevant tests
Common Violations and Fixes
| Violation | Quick Fix |
|---|---|
| HTTPException in service | Create custom exception in app/exceptions/ |
| HTTPException in endpoint | Let domain exceptions bubble up to global handler |
| Business logic in endpoint | Move to service layer |
| console.log in JS | Use window.LogConfig.createLogger() |
| Missing ...data() | Add spread operator in component return |
| Bare except clause | Specify exception type |
| Raw dict return | Create Pydantic response model |
| Template not extending base | Add {% extends %} or {# standalone #} marker |
Configuration
All rules are defined in .architecture-rules.yaml. To modify rules:
- Edit
.architecture-rules.yaml - Update
scripts/validate_architecture.pyif implementing new checks - Run validator to test changes
- Update this documentation
Related Documentation
- Code Quality Guide
- Contributing Guide
- Architecture Overview
- Backend Development
- Frontend Development
Summary Statistics
| Category | Rules | Errors | Warnings |
|---|---|---|---|
| Backend | 20 | 15 | 5 |
| Frontend JS | 7 | 6 | 1 |
| Frontend Templates | 7 | 3 | 4 |
| Frontend Styling | 4 | 1 | 3 |
| Naming | 5 | 3 | 2 |
| Security | 5 | 5 | 0 |
| Quality | 3 | 2 | 1 |
| Total | 51 | 35 | 16 |
Last Updated: 2025-12-04 Version: 2.2