""" Control, Evidence, and Risk models — extracted from compliance/db/models.py. Covers the control framework (ControlDB), requirement↔control mappings, evidence artifacts, and the risk register. Re-exported from ``compliance.db.models`` for backwards compatibility. DO NOT change __tablename__, column names, or relationship strings. """ import uuid import enum from datetime import datetime, date, timezone from sqlalchemy import ( Column, String, Text, Integer, Boolean, DateTime, Date, ForeignKey, Enum, JSON, Index, ) from sqlalchemy.orm import relationship from classroom_engine.database import Base # ============================================================================ # ENUMS # ============================================================================ class ControlTypeEnum(str, enum.Enum): """Type of security control.""" PREVENTIVE = "preventive" # Prevents incidents DETECTIVE = "detective" # Detects incidents CORRECTIVE = "corrective" # Corrects after incidents class ControlDomainEnum(str, enum.Enum): """Domain/category of control.""" GOVERNANCE = "gov" # Governance & Organization PRIVACY = "priv" # Privacy & Data Protection IAM = "iam" # Identity & Access Management CRYPTO = "crypto" # Cryptography & Key Management SDLC = "sdlc" # Secure Development Lifecycle OPS = "ops" # Operations & Monitoring AI = "ai" # AI-specific controls CRA = "cra" # CRA & Supply Chain AUDIT = "aud" # Audit & Traceability class ControlStatusEnum(str, enum.Enum): """Implementation status of a control.""" PASS = "pass" # Fully implemented & passing PARTIAL = "partial" # Partially implemented FAIL = "fail" # Not passing NOT_APPLICABLE = "n/a" # Not applicable PLANNED = "planned" # Planned for implementation class RiskLevelEnum(str, enum.Enum): """Risk severity level.""" LOW = "low" MEDIUM = "medium" HIGH = "high" CRITICAL = "critical" class EvidenceStatusEnum(str, enum.Enum): """Status of evidence artifact.""" VALID = "valid" # Currently valid EXPIRED = "expired" # Past validity date PENDING = "pending" # Awaiting validation FAILED = "failed" # Failed validation # ============================================================================ # MODELS # ============================================================================ class ControlDB(Base): """ Technical or organizational security control. Examples: PRIV-001 (Verarbeitungsverzeichnis), SDLC-001 (SAST Scanning) """ __tablename__ = 'compliance_controls' id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) control_id = Column(String(20), unique=True, nullable=False, index=True) # e.g., "PRIV-001" domain = Column(Enum(ControlDomainEnum), nullable=False, index=True) control_type = Column(Enum(ControlTypeEnum), nullable=False) title = Column(String(300), nullable=False) description = Column(Text) pass_criteria = Column(Text, nullable=False) # Measurable pass criteria implementation_guidance = Column(Text) # How to implement # Code/Evidence references code_reference = Column(String(500)) # e.g., "backend/middleware/pii_redactor.py:45" documentation_url = Column(String(500)) # Link to internal docs # Automation is_automated = Column(Boolean, default=False) automation_tool = Column(String(100)) # e.g., "Semgrep", "Trivy" automation_config = Column(JSON) # Tool-specific config # Status status = Column(Enum(ControlStatusEnum), default=ControlStatusEnum.PLANNED) status_notes = Column(Text) # Ownership & Review owner = Column(String(100)) # Responsible person/team review_frequency_days = Column(Integer, default=90) last_reviewed_at = Column(DateTime) next_review_at = Column(DateTime) # Timestamps created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) # Relationships mappings = relationship("ControlMappingDB", back_populates="control", cascade="all, delete-orphan") evidence = relationship("EvidenceDB", back_populates="control", cascade="all, delete-orphan") __table_args__ = ( Index('ix_control_domain_status', 'domain', 'status'), ) def __repr__(self): return f"" class ControlMappingDB(Base): """ Maps requirements to controls (many-to-many with metadata). """ __tablename__ = 'compliance_control_mappings' id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) requirement_id = Column(String(36), ForeignKey('compliance_requirements.id'), nullable=False, index=True) control_id = Column(String(36), ForeignKey('compliance_controls.id'), nullable=False, index=True) coverage_level = Column(String(20), default="full") # "full", "partial", "planned" notes = Column(Text) # Explanation of coverage # Timestamps created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) # Relationships requirement = relationship("RequirementDB", back_populates="control_mappings") control = relationship("ControlDB", back_populates="mappings") __table_args__ = ( Index('ix_mapping_req_ctrl', 'requirement_id', 'control_id', unique=True), ) class EvidenceDB(Base): """ Audit evidence for controls. Types: scan_report, policy_document, config_snapshot, test_result, manual_upload, screenshot, external_link """ __tablename__ = 'compliance_evidence' id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) control_id = Column(String(36), ForeignKey('compliance_controls.id'), nullable=False, index=True) evidence_type = Column(String(50), nullable=False) # Type of evidence title = Column(String(300), nullable=False) description = Column(Text) # File/Link storage artifact_path = Column(String(500)) # Local file path artifact_url = Column(String(500)) # External URL artifact_hash = Column(String(64)) # SHA-256 hash file_size_bytes = Column(Integer) mime_type = Column(String(100)) # Validity period valid_from = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) valid_until = Column(DateTime) # NULL = no expiry status = Column(Enum(EvidenceStatusEnum), default=EvidenceStatusEnum.VALID) # Source tracking source = Column(String(100)) # "ci_pipeline", "manual", "api" ci_job_id = Column(String(100)) # CI/CD job reference uploaded_by = Column(String(100)) # User who uploaded # Timestamps collected_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) # Relationships control = relationship("ControlDB", back_populates="evidence") __table_args__ = ( Index('ix_evidence_control_type', 'control_id', 'evidence_type'), Index('ix_evidence_status', 'status'), ) def __repr__(self): return f"" class RiskDB(Base): """ Risk register entry with likelihood x impact scoring. """ __tablename__ = 'compliance_risks' id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) risk_id = Column(String(20), unique=True, nullable=False, index=True) # e.g., "RISK-001" title = Column(String(300), nullable=False) description = Column(Text) category = Column(String(50), nullable=False) # "data_breach", "compliance_gap", etc. # Inherent risk (before controls) likelihood = Column(Integer, nullable=False) # 1-5 impact = Column(Integer, nullable=False) # 1-5 inherent_risk = Column(Enum(RiskLevelEnum), nullable=False) # Mitigating controls mitigating_controls = Column(JSON) # List of control_ids # Residual risk (after controls) residual_likelihood = Column(Integer) residual_impact = Column(Integer) residual_risk = Column(Enum(RiskLevelEnum)) # Management owner = Column(String(100)) status = Column(String(20), default="open") # "open", "mitigated", "accepted", "transferred" treatment_plan = Column(Text) # Review identified_date = Column(Date, default=date.today) review_date = Column(Date) last_assessed_at = Column(DateTime) # Timestamps created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) __table_args__ = ( Index('ix_risk_category_status', 'category', 'status'), Index('ix_risk_inherent', 'inherent_risk'), ) def __repr__(self): return f"" @staticmethod def calculate_risk_level(likelihood: int, impact: int) -> RiskLevelEnum: """Calculate risk level from likelihood x impact matrix.""" score = likelihood * impact if score >= 20: return RiskLevelEnum.CRITICAL elif score >= 12: return RiskLevelEnum.HIGH elif score >= 6: return RiskLevelEnum.MEDIUM else: return RiskLevelEnum.LOW __all__ = [ "ControlTypeEnum", "ControlDomainEnum", "ControlStatusEnum", "RiskLevelEnum", "EvidenceStatusEnum", "ControlDB", "ControlMappingDB", "EvidenceDB", "RiskDB", ]