from __future__ import annotations import enum from datetime import datetime, timezone from typing import List from sqlalchemy import ( String, Text, Integer, DateTime, ForeignKey, Enum, JSON, Boolean ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base # ─── Enums ───────────────────────────────────────────────────────────────────── class ProjectStatus(str, enum.Enum): PENDING = "PENDING" AWAITING_CONDUCTOR = "AWAITING_CONDUCTOR" CONDUCTOR_RUNNING = "CONDUCTOR_RUNNING" AWAITING_ARCHITECT = "AWAITING_ARCHITECT" ARCHITECT_RUNNING = "ARCHITECT_RUNNING" AWAITING_DEV = "AWAITING_DEV" DEV_RUNNING = "DEV_RUNNING" AWAITING_UIUX = "AWAITING_UIUX" UIUX_RUNNING = "UIUX_RUNNING" AWAITING_QA = "AWAITING_QA" QA_RUNNING = "QA_RUNNING" AWAITING_DEPLOY = "AWAITING_DEPLOY" DEPLOY_RUNNING = "DEPLOY_RUNNING" COMPLETED = "COMPLETED" FAILED = "FAILED" PAUSED = "PAUSED" class WorkflowType(str, enum.Enum): SOFTWARE_DESIGN = "SOFTWARE_DESIGN" SYSADMIN_DEBUG = "SYSADMIN_DEBUG" DEVOPS_SETUP = "DEVOPS_SETUP" SYSADMIN_ADJUST = "SYSADMIN_ADJUST" class ProjectType(str, enum.Enum): SYSTEMD_SERVICE = "SYSTEMD_SERVICE" DOCKER_WEBAPP = "DOCKER_WEBAPP" DOCKER_API = "DOCKER_API" DOCKER_COMPOSE = "DOCKER_COMPOSE" CLI_TOOL = "CLI_TOOL" CRON_JOB = "CRON_JOB" SHELL_SCRIPT = "SHELL_SCRIPT" WORKER_MQ = "WORKER_MQ" SOCKET_IPC = "SOCKET_IPC" WEBHOOK_EVENT = "WEBHOOK_EVENT" LIBRARY_PLUGIN = "LIBRARY_PLUGIN" AGENT_BOT = "AGENT_BOT" TUI_APP = "TUI_APP" MCP_SERVER = "MCP_SERVER" WASM_EDGE = "WASM_EDGE" GRPC_SERVICE = "GRPC_SERVICE" KERNEL_EBPF = "KERNEL_EBPF" SERVERLESS_FAAS = "SERVERLESS_FAAS" DESKTOP_GUI = "DESKTOP_GUI" STATIC_SITE = "STATIC_SITE" # Project type metadata (emoji, label, description) PROJECT_TYPE_INFO = { ProjectType.SYSTEMD_SERVICE: ("⚙️", "Service système (systemd)", "Démon long-running, démarrage automatique au boot, redémarrage sur erreur."), ProjectType.DOCKER_WEBAPP: ("🌐", "Docker — Web app", "Application frontend ou backend HTTP/HTTPS containerisée."), ProjectType.DOCKER_API: ("🔌", "Docker — API (REST / gRPC)", "Service HTTP headless containerisé, sans interface utilisateur."), ProjectType.DOCKER_COMPOSE: ("🐳", "Docker Compose", "Orchestration de plusieurs conteneurs avec réseaux et volumes partagés."), ProjectType.CLI_TOOL: ("💻", "Outil CLI", "Programme en ligne de commande avec sous-commandes, flags et stdin/stdout."), ProjectType.CRON_JOB: ("⏰", "Jobs planifiés (cron / timer)", "Exécution périodique ou batch, anacron pour machines non 24/7."), ProjectType.SHELL_SCRIPT: ("📜", "Script shell (Bash / Zsh)", "Automatisation légère, glue entre outils, pipelines de commandes."), ProjectType.WORKER_MQ: ("📨", "Worker + Message queue", "Traitement asynchrone via Redis, RabbitMQ ou Kafka."), ProjectType.SOCKET_IPC: ("🔗", "Socket / IPC local", "Communication inter-processus via Unix socket, FIFO ou D-Bus."), ProjectType.WEBHOOK_EVENT: ("🪝", "Webhook / Event-driven", "Réception d'événements HTTP entrants, architecture pub/sub."), ProjectType.LIBRARY_PLUGIN: ("📦", "Bibliothèque / Plugin", "Code réutilisable sous forme de .so, package Python ou module Go."), ProjectType.AGENT_BOT: ("🤖", "Agent / Bot autonome", "Service IA ou bot (Telegram, Discord) tournant en continu."), ProjectType.TUI_APP: ("🖥️", "TUI (interface texte)", "Interface terminal interactive via curses, Textual ou Bubble Tea."), ProjectType.MCP_SERVER: ("🧠", "Serveur MCP", "Exposition d'outils IA via Model Context Protocol (stdio ou HTTP SSE)."), ProjectType.WASM_EDGE: ("🧊", "WebAssembly / Edge", "Modules wasm sandboxés, portables, via wasi et wasmtime."), ProjectType.GRPC_SERVICE: ("⚡", "Microservice gRPC / Thrift", "RPC binaire performant avec contrat IDL (protobuf)."), ProjectType.KERNEL_EBPF: ("🔬", "Module kernel / eBPF", "Driver, hook réseau ou sonde d'observabilité au niveau noyau."), ProjectType.SERVERLESS_FAAS: ("☁️", "Serverless / FaaS", "Fonctions déclenchées à la demande via OpenFaaS ou Knative."), ProjectType.DESKTOP_GUI: ("🖼️", "Application desktop (GUI)", "Interface graphique native via GTK, Qt, ou Tauri/Electron."), ProjectType.STATIC_SITE: ("📄", "Site statique / SSG", "Génération de HTML pur au build time via Hugo, Astro ou Jekyll."), } class TaskStatus(str, enum.Enum): PENDING = "PENDING" IN_PROGRESS = "IN_PROGRESS" IN_REVIEW = "IN_REVIEW" REJECTED = "REJECTED" READY_FOR_DEPLOY = "READY_FOR_DEPLOY" DONE = "DONE" BLOCKED = "BLOCKED" class TaskType(str, enum.Enum): BACKEND = "BACKEND" FRONTEND = "FRONTEND" INFRA = "INFRA" TEST = "TEST" DESIGN = "DESIGN" class TaskPriority(str, enum.Enum): P1 = "P1" P2 = "P2" P3 = "P3" class AgentExecutionStatus(str, enum.Enum): RUNNING = "RUNNING" SUCCESS = "SUCCESS" FAILED = "FAILED" TIMEOUT = "TIMEOUT" def _utcnow() -> datetime: return datetime.now(timezone.utc) # ─── Infrastructure Models ───────────────────────────────────────────────────── class DeployServer(Base): __tablename__ = "deploy_servers" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(200), unique=True, nullable=False) host: Mapped[str] = mapped_column(String(500), nullable=False) user: Mapped[str] = mapped_column(String(100), default="deploy") password: Mapped[str] = mapped_column(String(500), nullable=True) ssh_port: Mapped[int] = mapped_column(Integer, default=22) description: Mapped[str] = mapped_column(Text, default="") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow) class GitServer(Base): __tablename__ = "git_servers" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(200), unique=True, nullable=False) url: Mapped[str] = mapped_column(String(500), nullable=False) token: Mapped[str] = mapped_column(String(500), nullable=True) org: Mapped[str] = mapped_column(String(200), default="openclaw") description: Mapped[str] = mapped_column(Text, default="") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow) # ─── Core Models ─────────────────────────────────────────────────────────────── class Project(Base): __tablename__ = "projects" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(200), nullable=False) slug: Mapped[str] = mapped_column(String(200), unique=True, nullable=False, index=True) description: Mapped[str] = mapped_column(Text, default="") status: Mapped[ProjectStatus] = mapped_column( Enum(ProjectStatus), default=ProjectStatus.PENDING, nullable=False ) workflow_type: Mapped[WorkflowType] = mapped_column( Enum(WorkflowType), default=WorkflowType.SOFTWARE_DESIGN, nullable=False ) project_type: Mapped[ProjectType] = mapped_column( Enum(ProjectType), default=ProjectType.DOCKER_WEBAPP, nullable=False ) test_mode: Mapped[bool] = mapped_column(default=False) install_path: Mapped[str] = mapped_column(String(500), nullable=True) gitea_repo: Mapped[str] = mapped_column(String(500), nullable=True) deployment_target: Mapped[str] = mapped_column(String(200), nullable=True) deploy_server_id: Mapped[int] = mapped_column(ForeignKey("deploy_servers.id"), nullable=True) git_server_id: Mapped[int] = mapped_column(ForeignKey("git_servers.id"), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=_utcnow, onupdate=_utcnow ) # Relationships tasks: Mapped[List["Task"]] = relationship( back_populates="project", cascade="all, delete-orphan", lazy="selectin" ) audit_logs: Mapped[List["AuditLog"]] = relationship( back_populates="project", cascade="all, delete-orphan", lazy="selectin" ) agent_executions: Mapped[List["AgentExecution"]] = relationship( back_populates="project", cascade="all, delete-orphan", lazy="selectin" ) deploy_server: Mapped["DeployServer"] = relationship(lazy="selectin") git_server: Mapped["GitServer"] = relationship(lazy="selectin") class Task(Base): __tablename__ = "tasks" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) project_id: Mapped[int] = mapped_column(ForeignKey("projects.id"), nullable=False) task_id: Mapped[str] = mapped_column(String(50), nullable=False) # e.g. TASK-001 type: Mapped[TaskType] = mapped_column(Enum(TaskType), default=TaskType.BACKEND) title: Mapped[str] = mapped_column(String(500), nullable=False) priority: Mapped[TaskPriority] = mapped_column(Enum(TaskPriority), default=TaskPriority.P3) assigned_to: Mapped[str] = mapped_column(String(100), nullable=True) status: Mapped[TaskStatus] = mapped_column( Enum(TaskStatus), default=TaskStatus.PENDING, nullable=False ) dependencies: Mapped[dict] = mapped_column(JSON, default=list) acceptance_criteria: Mapped[str] = mapped_column(Text, nullable=True) agent_payloads: Mapped[dict] = mapped_column(JSON, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=_utcnow, onupdate=_utcnow ) # Relationships project: Mapped["Project"] = relationship(back_populates="tasks") class AgentExecution(Base): __tablename__ = "agent_executions" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) project_id: Mapped[int] = mapped_column(ForeignKey("projects.id"), nullable=False) agent_name: Mapped[str] = mapped_column(String(100), nullable=False) status: Mapped[AgentExecutionStatus] = mapped_column( Enum(AgentExecutionStatus), default=AgentExecutionStatus.RUNNING ) pid: Mapped[int] = mapped_column(Integer, nullable=True) started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow) finished_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=True) exit_code: Mapped[int] = mapped_column(Integer, nullable=True) error_output: Mapped[str] = mapped_column(Text, nullable=True) # Relationships project: Mapped["Project"] = relationship(back_populates="agent_executions") class AuditLog(Base): __tablename__ = "audit_logs" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) project_id: Mapped[int] = mapped_column(ForeignKey("projects.id"), nullable=False) timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow) agent: Mapped[str] = mapped_column(String(100), nullable=False) action: Mapped[str] = mapped_column(String(100), nullable=False) target: Mapped[str] = mapped_column(String(100), nullable=True) message: Mapped[str] = mapped_column(Text, nullable=True) source: Mapped[str] = mapped_column(String(100), default="api") # Relationships project: Mapped["Project"] = relationship(back_populates="audit_logs")