"""Phase 5.11 — Compliance, Audit & Governance domain models.""" from __future__ import annotations import uuid from datetime import date, datetime from sqlalchemy import Boolean, Date, DateTime, Index, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from app.core.database import Base from app.models.base import TenantMixin, TimestampMixin, UUIDPrimaryKeyMixin from app.models.types import ApprovalStatus, GUID, PolicyViolationSeverity, RiskLevel class AuditRecord(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "audit_records" actor_user_id: Mapped[str] = mapped_column(String(100), nullable=False) action: Mapped[str] = mapped_column(String(100), nullable=False) resource_type: Mapped[str] = mapped_column(String(50), nullable=False) resource_id: Mapped[str] = mapped_column(String(100), nullable=False) source_module: Mapped[str | None] = mapped_column(String(50), nullable=True) before_value: Mapped[str | None] = mapped_column(Text, nullable=True) after_value: Mapped[str | None] = mapped_column(Text, nullable=True) reason: Mapped[str | None] = mapped_column(Text, nullable=True) correlation_id: Mapped[str | None] = mapped_column(String(100), nullable=True) request_id: Mapped[str | None] = mapped_column(String(100), nullable=True) ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True) user_agent: Mapped[str | None] = mapped_column(String(500), nullable=True) session_id: Mapped[str | None] = mapped_column(String(100), nullable=True) is_immutable: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = ( Index("ix_audit_records_tenant_created", "tenant_id", "created_at"), Index("ix_audit_records_resource", "tenant_id", "resource_type", "resource_id"), ) class AuditHistory(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "audit_history" audit_record_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) event_type: Mapped[str] = mapped_column(String(50), nullable=False) event_data: Mapped[str | None] = mapped_column(Text, nullable=True) class CompliancePolicy(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "compliance_policies" code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) policy_type: Mapped[str] = mapped_column(String(50), nullable=False) country_code: Mapped[str | None] = mapped_column(String(3), nullable=True) rules_config: Mapped[str] = mapped_column(Text, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) effective_from: Mapped[date | None] = mapped_column(Date, nullable=True) effective_to: Mapped[date | None] = mapped_column(Date, nullable=True) __table_args__ = (UniqueConstraint("tenant_id", "code", name="uq_compliance_policy_tenant_code"),) class GovernanceRule(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "governance_rules" code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) rule_type: Mapped[str] = mapped_column(String(50), nullable=False) condition_config: Mapped[str] = mapped_column(Text, nullable=False) action_config: Mapped[str | None] = mapped_column(Text, nullable=True) priority: Mapped[int] = mapped_column(Integer, default=0, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = (UniqueConstraint("tenant_id", "code", name="uq_governance_rule_tenant_code"),) class ApprovalWorkflow(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "approval_workflows" name: Mapped[str] = mapped_column(String(255), nullable=False) resource_type: Mapped[str] = mapped_column(String(50), nullable=False) min_amount: Mapped[str | None] = mapped_column(String(50), nullable=True) max_amount: Mapped[str | None] = mapped_column(String(50), nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = (UniqueConstraint("tenant_id", "name", name="uq_approval_workflow_tenant_name"),) class ApprovalStep(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "approval_steps" workflow_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) step_number: Mapped[int] = mapped_column(Integer, nullable=False) approver_role: Mapped[str] = mapped_column(String(50), nullable=False) is_required: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) class ApprovalRequest(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "approval_requests" workflow_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) resource_type: Mapped[str] = mapped_column(String(50), nullable=False) resource_id: Mapped[str] = mapped_column(String(100), nullable=False) requested_by: Mapped[str] = mapped_column(String(100), nullable=False) current_step: Mapped[int] = mapped_column(Integer, default=1, nullable=False) status: Mapped[ApprovalStatus] = mapped_column(default=ApprovalStatus.PENDING, nullable=False) approved_by: Mapped[str | None] = mapped_column(String(100), nullable=True) approved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) rejection_reason: Mapped[str | None] = mapped_column(Text, nullable=True) __table_args__ = ( Index("ix_approval_requests_resource", "tenant_id", "resource_type", "resource_id"), ) class Delegation(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "delegations" delegator_user_id: Mapped[str] = mapped_column(String(100), nullable=False) delegate_user_id: Mapped[str] = mapped_column(String(100), nullable=False) permission_scope: Mapped[str] = mapped_column(String(255), nullable=False) start_date: Mapped[date] = mapped_column(Date, nullable=False) end_date: Mapped[date | None] = mapped_column(Date, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) class RiskRecord(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "risk_records" code: Mapped[str] = mapped_column(String(50), nullable=False) title: Mapped[str] = mapped_column(String(255), nullable=False) risk_category: Mapped[str] = mapped_column(String(50), nullable=False) risk_level: Mapped[RiskLevel] = mapped_column(nullable=False) risk_score: Mapped[int] = mapped_column(Integer, default=0, nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) mitigation_plan: Mapped[str | None] = mapped_column(Text, nullable=True) is_resolved: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) __table_args__ = (UniqueConstraint("tenant_id", "code", name="uq_risk_record_tenant_code"),) class PolicyViolation(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "policy_violations" policy_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) resource_type: Mapped[str] = mapped_column(String(50), nullable=False) resource_id: Mapped[str] = mapped_column(String(100), nullable=False) severity: Mapped[PolicyViolationSeverity] = mapped_column(nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False) detected_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) is_resolved: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) class Evidence(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "evidence" resource_type: Mapped[str] = mapped_column(String(50), nullable=False) resource_id: Mapped[str] = mapped_column(String(100), nullable=False) file_name: Mapped[str] = mapped_column(String(255), nullable=False) file_path: Mapped[str | None] = mapped_column(String(500), nullable=True) mime_type: Mapped[str | None] = mapped_column(String(100), nullable=True) uploaded_by: Mapped[str] = mapped_column(String(100), nullable=False) version: Mapped[int] = mapped_column(Integer, default=1, nullable=False) class RetentionPolicy(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "retention_policies" name: Mapped[str] = mapped_column(String(255), nullable=False) resource_type: Mapped[str] = mapped_column(String(50), nullable=False) retention_days: Mapped[int] = mapped_column(Integer, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = (UniqueConstraint("tenant_id", "name", name="uq_retention_policy_tenant_name"),) class ControlDefinition(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "control_definitions" code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) control_type: Mapped[str] = mapped_column(String(50), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) validation_config: Mapped[str | None] = mapped_column(Text, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = (UniqueConstraint("tenant_id", "code", name="uq_control_def_tenant_code"),)