"""Phase 9: Ownership & Revalidation Queue models.""" import enum import uuid from datetime import datetime from sqlalchemy import Column, DateTime, Enum, ForeignKey, Index, String, Text from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.orm import relationship from app.database import Base class QueuePriority(str, enum.Enum): critical = "critical" high = "high" medium = "medium" low = "low" class QueueStatus(str, enum.Enum): pending = "pending" in_progress = "in_progress" completed = "completed" dismissed = "dismissed" class QueueReason(str, enum.Enum): validation_expired = "validation_expired" infra_change = "infra_change" osint_alert = "osint_alert" mitre_update = "mitre_update" rule_modified = "rule_modified" low_confidence = "low_confidence" manual = "manual" class TechniqueOwnership(Base): """Ownership assignment for a MITRE technique.""" __tablename__ = "technique_ownerships" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) technique_id = Column( UUID(as_uuid=True), ForeignKey("techniques.id", ondelete="CASCADE"), nullable=False, unique=True, ) owner_id = Column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, ) backup_owner_id = Column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, ) team = Column(String(200), nullable=True) notes = Column(Text, nullable=True) assigned_at = Column(DateTime, nullable=True) assigned_by = Column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, ) created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) technique = relationship("Technique", foreign_keys=[technique_id]) owner = relationship("User", foreign_keys=[owner_id]) backup_owner = relationship("User", foreign_keys=[backup_owner_id]) class RevalidationQueueItem(Base): """A prioritised work item for the analyst's daily queue.""" __tablename__ = "revalidation_queue_items" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) technique_id = Column( UUID(as_uuid=True), ForeignKey("techniques.id", ondelete="CASCADE"), nullable=True, ) detection_asset_id = Column( UUID(as_uuid=True), ForeignKey("detection_assets.id", ondelete="CASCADE"), nullable=True, ) priority = Column( Enum(QueuePriority, name="queue_priority"), nullable=False, default=QueuePriority.medium, ) reason = Column( Enum(QueueReason, name="queue_reason"), nullable=False, ) reason_detail = Column(Text, nullable=True) status = Column( Enum(QueueStatus, name="queue_status"), nullable=False, default=QueueStatus.pending, ) assigned_to = Column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, ) due_date = Column(DateTime, nullable=True) created_at = Column(DateTime, default=datetime.utcnow) completed_at = Column(DateTime, nullable=True) dismissed_at = Column(DateTime, nullable=True) completed_by = Column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, ) extra = Column(JSONB, nullable=True) # arbitrary metadata technique = relationship("Technique", foreign_keys=[technique_id]) detection_asset = relationship("DetectionAsset", foreign_keys=[detection_asset_id]) assignee = relationship("User", foreign_keys=[assigned_to]) # Indexes Index("ix_rqueue_status", RevalidationQueueItem.status) Index("ix_rqueue_priority", RevalidationQueueItem.priority) Index("ix_rqueue_assigned_to", RevalidationQueueItem.assigned_to) Index("ix_rqueue_technique_id", RevalidationQueueItem.technique_id) Index("ix_rqueue_asset_id", RevalidationQueueItem.detection_asset_id) Index("ix_techown_owner_id", TechniqueOwnership.owner_id)