# 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, Date, 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, IssueType, 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, ) # Parent issue for hierarchy (Epic -> Story -> Task) parent_id = Column( PGUUID(as_uuid=True), ForeignKey("issues.id", ondelete="CASCADE"), nullable=True, index=True, ) # Issue type (Epic, Story, Task, Bug) type: Column[IssueType] = Column( Enum(IssueType), default=IssueType.TASK, nullable=False, index=True, ) # Reporter (who created this issue - can be user or agent) reporter_id = Column( PGUUID(as_uuid=True), nullable=True, # System-generated issues may have no reporter 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) # Due date for the issue due_date = Column(Date, nullable=True, index=True) # External tracker integration external_tracker_type = Column( String(50), nullable=True, index=True, ) # 'gitea', 'github', 'gitlab' external_issue_id = Column(String(255), nullable=True) # External system's ID remote_url = Column(String(1000), nullable=True) # Link to external issue external_issue_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") parent = relationship("Issue", remote_side="Issue.id", backref="children") __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_type", "external_issue_id", ), Index("ix_issues_sync_status", "sync_status"), Index("ix_issues_project_agent", "project_id", "assigned_agent_id"), Index("ix_issues_project_type", "project_id", "type"), Index("ix_issues_project_status_priority", "project_id", "status", "priority"), ) def __repr__(self) -> str: return ( f"" )