# Background Tasks Architecture Rules # ==================================== # Enforces consistent patterns for all background tasks in the application. # See docs/architecture/background-tasks.md for full specification. background_task_rules: # ───────────────────────────────────────────────────────────────── # Model Rules # ───────────────────────────────────────────────────────────────── - id: BG-001 name: Background task models must have status field description: | All database models for background tasks must include a 'status' field with proper indexing for efficient querying. severity: error patterns: # Models that should have status field - file: "models/database/marketplace_import_job.py" must_contain: "status = Column" - file: "models/database/letzshop.py" must_contain: "status = Column" - file: "models/database/test_run.py" must_contain: "status = Column" - file: "models/database/architecture_scan.py" must_contain: "status = Column" suggestion: | Add: status = Column(String(30), nullable=False, default="pending", index=True) - id: BG-002 name: Background task models must have timestamp fields description: | All background task models must include created_at, started_at, and completed_at timestamp fields for proper lifecycle tracking. severity: error patterns: - file: "models/database/*_job.py" must_contain: - "started_at" - "completed_at" - file: "models/database/test_run.py" must_contain: - "started_at" - "completed_at" suggestion: | Add timestamp fields: created_at = Column(DateTime, default=lambda: datetime.now(UTC)) started_at = Column(DateTime, nullable=True) completed_at = Column(DateTime, nullable=True) - id: BG-003 name: Background task models must have error_message field description: | All background task models must include an error_message field to store failure details. severity: warning patterns: - file: "models/database/marketplace_import_job.py" must_contain: "error_message" - file: "models/database/letzshop.py" must_contain: "error_message" - file: "models/database/test_run.py" must_contain: "error_message" suggestion: | Add: error_message = Column(Text, nullable=True) - id: BG-004 name: Background task models must have triggered_by field description: | All background task models must track who/what triggered the task for audit purposes. severity: warning patterns: - file: "models/database/*_job.py" must_contain: "triggered_by" - file: "models/database/test_run.py" must_contain: "triggered_by" suggestion: | Add: triggered_by = Column(String(100), nullable=True) Format: "manual:username", "scheduled", "api:client_id" # ───────────────────────────────────────────────────────────────── # Status Value Rules # ───────────────────────────────────────────────────────────────── - id: BG-010 name: Use standard status values description: | Background task status must use standard values: - pending: Task created, not started - running: Task actively executing - completed: Task finished successfully - failed: Task failed with error - completed_with_warnings: Task completed with non-fatal issues Legacy values to migrate: - "processing" -> "running" - "fetching" -> "running" - "passed" -> "completed" - "error" -> "failed" severity: warning anti_patterns: - pattern: "status.*=.*processing" message: "Use 'running' instead of 'processing'" - pattern: "status.*=.*fetching" message: "Use 'running' instead of 'fetching'" suggestion: | Use the standard status enum: class TaskStatus(str, Enum): PENDING = "pending" RUNNING = "running" COMPLETED = "completed" FAILED = "failed" COMPLETED_WITH_WARNINGS = "completed_with_warnings" # ───────────────────────────────────────────────────────────────── # API Rules # ───────────────────────────────────────────────────────────────── - id: BG-020 name: Task trigger endpoints must return job_id description: | Endpoints that trigger background tasks must return the job ID so the frontend can poll for status. severity: error patterns: - file: "app/api/v1/**/marketplace.py" endpoint_pattern: "@router.post.*import" must_return: "job_id" - file: "app/api/v1/**/tests.py" endpoint_pattern: "@router.post.*run" must_return: "run_id" suggestion: | Return a response like: { "job_id": job.id, "status": "pending", "message": "Task queued successfully" } - id: BG-021 name: Use FastAPI BackgroundTasks for async execution description: | All long-running tasks must use FastAPI's BackgroundTasks for async execution, not synchronous blocking calls. severity: error patterns: - file: "app/api/v1/**/*.py" must_contain: "BackgroundTasks" when_contains: ["import_", "export_", "run_scan", "execute_"] anti_patterns: - pattern: "subprocess\\.run.*wait.*True" message: "Don't wait synchronously for subprocesses in API handlers" - pattern: "time\\.sleep" file: "app/api/**/*.py" message: "Don't use time.sleep in API handlers" suggestion: | Use BackgroundTasks: async def trigger_task(background_tasks: BackgroundTasks): job = create_job_record(db) background_tasks.add_task(execute_task, job.id) return {"job_id": job.id} - id: BG-022 name: Tasks must be registered in BackgroundTasksService description: | All background task types must be registered in the BackgroundTasksService for unified monitoring. severity: warning patterns: - file: "app/services/background_tasks_service.py" must_reference: - "MarketplaceImportJob" - "LetzshopHistoricalImportJob" - "TestRun" - "ArchitectureScan" suggestion: | Add task model to TASK_MODELS in BackgroundTasksService: TASK_MODELS = { 'product_import': MarketplaceImportJob, 'order_import': LetzshopHistoricalImportJob, 'test_run': TestRun, 'code_quality_scan': ArchitectureScan, } # ───────────────────────────────────────────────────────────────── # Task Implementation Rules # ───────────────────────────────────────────────────────────────── - id: BG-030 name: Tasks must update status on start description: | Background task functions must set status to 'running' and record started_at timestamp at the beginning. severity: error patterns: - file: "app/tasks/*.py" must_contain: - 'status = "running"' - "started_at" suggestion: | At task start: job.status = "running" job.started_at = datetime.now(UTC) db.commit() - id: BG-031 name: Tasks must update status on completion description: | Background task functions must set appropriate final status and record completed_at timestamp. severity: error patterns: - file: "app/tasks/*.py" must_contain: - "completed_at" must_contain_one_of: - 'status = "completed"' - 'status = "failed"' suggestion: | On completion: job.status = "completed" # or "failed" job.completed_at = datetime.now(UTC) db.commit() - id: BG-032 name: Tasks must handle exceptions description: | Background tasks must catch exceptions, set status to 'failed', store error message, and optionally notify admins. severity: error patterns: - file: "app/tasks/*.py" must_contain: - "try:" - "except" - 'status = "failed"' - "error_message" suggestion: | Use try/except pattern: try: # Task logic job.status = "completed" except Exception as e: job.status = "failed" job.error_message = str(e) logger.error(f"Task failed: {e}") finally: job.completed_at = datetime.now(UTC) db.commit() - id: BG-033 name: Tasks must use separate database sessions description: | Background tasks must create their own database session since FastAPI request sessions are closed after response. severity: error patterns: - file: "app/tasks/*.py" must_contain: "SessionLocal()" anti_patterns: - pattern: "def.*task.*db.*Session" message: "Don't pass request db session to background tasks" suggestion: | Create session in task: async def my_task(job_id: int): db = SessionLocal() try: # Task logic finally: db.close() # ───────────────────────────────────────────────────────────────── # Frontend Rules # ───────────────────────────────────────────────────────────────── - id: BG-040 name: Pages with tasks must show status on return description: | Pages that trigger background tasks must check for active/recent tasks on load and display appropriate status banners. severity: info patterns: - file: "static/admin/js/*.js" when_contains: "BackgroundTasks" must_contain: - "init" - "activeTask" suggestion: | In Alpine component init(): async init() { // Check for active tasks for this page await this.checkActiveTask(); if (this.activeTask) { this.startPolling(); } } - id: BG-041 name: Use consistent polling interval description: | Polling for background task status should use 3-5 second intervals to balance responsiveness with server load. severity: info patterns: - file: "static/admin/js/*.js" when_contains: "setInterval" should_match: "setInterval.*[3-5]000" anti_patterns: - pattern: "setInterval.*1000" message: "1 second polling is too frequent" - pattern: "setInterval.*10000" message: "10 second polling may feel unresponsive" suggestion: | Use 3-5 second polling: this.pollInterval = setInterval(() => this.pollStatus(), 5000);