# app/models/syndarix/issue.py """ Issue model for Syndarix AI consulting platform. An Issue represents a unit of work that can be assigned to agents or humans, with optional synchronization to external issue trackers (Gitea, GitHub, GitLab). """ from sqlalchemy import Column, DateTime, Enum, ForeignKey, Index, Integer, String, Text from sqlalchemy.dialects.postgresql import ( JSONB, UUID as PGUUID, ) from sqlalchemy.orm import relationship from app.models.base import Base, TimestampMixin, UUIDMixin from .enums import IssuePriority, IssueStatus, SyncStatus class Issue(Base, UUIDMixin, TimestampMixin): """ Issue model representing a unit of work in a project. Features: - Standard issue fields (title, body, status, priority) - Assignment to agent instances or human assignees - Sprint association for backlog management - External tracker synchronization (Gitea, GitHub, GitLab) """ __tablename__ = "issues" # Foreign key to project project_id = Column( PGUUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True, ) # Issue content title = Column(String(500), nullable=False) body = Column(Text, nullable=False, default="") # Status and priority status: Column[IssueStatus] = Column( Enum(IssueStatus), default=IssueStatus.OPEN, nullable=False, index=True, ) priority: Column[IssuePriority] = Column( Enum(IssuePriority), default=IssuePriority.MEDIUM, nullable=False, index=True, ) # Labels for categorization (e.g., ["bug", "frontend", "urgent"]) labels = Column(JSONB, default=list, nullable=False) # Assignment - either to an agent or a human (mutually exclusive) assigned_agent_id = Column( PGUUID(as_uuid=True), ForeignKey("agent_instances.id", ondelete="SET NULL"), nullable=True, index=True, ) # Human assignee (username or email, not a FK to allow external users) human_assignee = Column(String(255), nullable=True, index=True) # Sprint association sprint_id = Column( PGUUID(as_uuid=True), ForeignKey("sprints.id", ondelete="SET NULL"), nullable=True, index=True, ) # Story points for estimation story_points = Column(Integer, nullable=True) # External tracker integration external_tracker = Column( String(50), nullable=True, index=True, ) # 'gitea', 'github', 'gitlab' external_id = Column(String(255), nullable=True) # External system's ID external_url = Column(String(1000), nullable=True) # Link to external issue external_number = Column(Integer, nullable=True) # Issue number (e.g., #123) # Sync status with external tracker sync_status: Column[SyncStatus] = Column( Enum(SyncStatus), default=SyncStatus.SYNCED, nullable=False, # Note: Index defined in __table_args__ as ix_issues_sync_status ) last_synced_at = Column(DateTime(timezone=True), nullable=True) external_updated_at = Column(DateTime(timezone=True), nullable=True) # Lifecycle timestamp closed_at = Column(DateTime(timezone=True), nullable=True, index=True) # Relationships project = relationship("Project", back_populates="issues") assigned_agent = relationship( "AgentInstance", back_populates="assigned_issues", foreign_keys=[assigned_agent_id], ) sprint = relationship("Sprint", back_populates="issues") __table_args__ = ( Index("ix_issues_project_status", "project_id", "status"), Index("ix_issues_project_priority", "project_id", "priority"), Index("ix_issues_project_sprint", "project_id", "sprint_id"), Index("ix_issues_external_tracker_id", "external_tracker", "external_id"), Index("ix_issues_sync_status", "sync_status"), Index("ix_issues_project_agent", "project_id", "assigned_agent_id"), ) def __repr__(self) -> str: return ( f"" )