From 79fee98db4367b52857f745098e2a085c65ee888 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 24 May 2026 21:00:14 -0400 Subject: [PATCH] =?UTF-8?q?fc3i:=20TaskRun=20model=20=E2=80=94=20per-Celer?= =?UTF-8?q?y-task=20lifecycle=20audit=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/app/models/__init__.py | 2 ++ backend/app/models/task_run.py | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 backend/app/models/task_run.py diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 0697c07..f7a3c94 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -21,6 +21,7 @@ from .tag_alias import TagAlias from .tag_allowlist import TagAllowlist from .tag_reference_embedding import TagReferenceEmbedding from .tag_suggestion_rejection import TagSuggestionRejection +from .task_run import TaskRun __all__ = [ "Base", @@ -46,4 +47,5 @@ __all__ = [ "TagAllowlist", "TagReferenceEmbedding", "TagSuggestionRejection", + "TaskRun", ] diff --git a/backend/app/models/task_run.py b/backend/app/models/task_run.py new file mode 100644 index 0000000..e8eac05 --- /dev/null +++ b/backend/app/models/task_run.py @@ -0,0 +1,48 @@ +"""FC-3i: task_run — per-Celery-task lifecycle audit row. + +One row inserted by the task_prerun signal at task start, updated by +task_postrun / task_failure / task_retry. The shape supports the +SystemActivity dashboard's three panes: per-queue queue+worker summary, +recent failures (24h, grouped by error_type), and full paginated +activity history. + +Retention: ok rows pruned after 24h, error/timeout after 7d (see +backend.app.tasks.maintenance.prune_task_runs). + +Recovery: rows stuck in 'running' for >5 min flipped to 'error' by +backend.app.tasks.maintenance.recover_stalled_task_runs (Beat 5 min). +""" + +from datetime import datetime + +from sqlalchemy import DateTime, Integer, String, Text +from sqlalchemy.orm import Mapped, mapped_column + +from .base import Base + + +class TaskRun(Base): + __tablename__ = "task_run" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + celery_task_id: Mapped[str] = mapped_column( + String(64), nullable=False, index=True, + ) + queue: Mapped[str] = mapped_column(String(32), nullable=False, index=True) + task_name: Mapped[str] = mapped_column(String(128), nullable=False, index=True) + target_id: Mapped[int | None] = mapped_column(Integer, nullable=True) + started_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, index=True, + ) + finished_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True, index=True, + ) + duration_ms: Mapped[int | None] = mapped_column(Integer, nullable=True) + status: Mapped[str] = mapped_column( + String(16), nullable=False, default="running", index=True, + ) + error_type: Mapped[str | None] = mapped_column(String(128), nullable=True) + error_message: Mapped[str | None] = mapped_column(Text, nullable=True) + retry_count: Mapped[int | None] = mapped_column(Integer, nullable=True) + worker_hostname: Mapped[str | None] = mapped_column(String(128), nullable=True) + args_summary: Mapped[str | None] = mapped_column(String(255), nullable=True)