from __future__ import annotations from django.db import models from control_plane.common import TimestampedModel class ProjectStatus(models.TextChoices): PLANNING = "PLANNING" READY = "READY" ARCHAEOLOGY = "ARCHAEOLOGY" BUILDING = "BUILDING" PAUSING = "PAUSING" PAUSED = "PAUSED" BLOCKED = "BLOCKED" VERIFYING = "VERIFYING" FINISHED = "FINISHED" FAILED = "FAILED" class Project(TimestampedModel): name = models.CharField(max_length=200) project_type = models.CharField(max_length=100, default="WEB_APP") goal = models.TextField() repository_url = models.TextField(blank=True) repository_path = models.TextField(blank=True) status = models.CharField(max_length=32, choices=ProjectStatus.choices, default=ProjectStatus.PLANNING) current_plan_version = models.PositiveIntegerField(default=0) architecture_summary = models.TextField(blank=True) constraints = models.JSONField(default=dict, blank=True) budget = models.JSONField(default=dict, blank=True) def __str__(self) -> str: return self.name class ProjectPlan(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="plans") version = models.PositiveIntegerField() goal = models.TextField() scope = models.TextField(blank=True) stack = models.JSONField(default=dict, blank=True) architecture = models.JSONField(default=dict, blank=True) constraints = models.JSONField(default=dict, blank=True) acceptance_criteria = models.JSONField(default=list, blank=True) permissions = models.JSONField(default=dict, blank=True) budget = models.JSONField(default=dict, blank=True) open_decisions = models.JSONField(default=list, blank=True) approved_at = models.DateTimeField(null=True, blank=True) class Meta: constraints = [models.UniqueConstraint(fields=["project", "version"], name="unique_plan_version")] class MilestoneStatus(models.TextChoices): QUEUED = "QUEUED" READY = "READY" RUNNING = "RUNNING" VERIFYING = "VERIFYING" COMPLETE = "COMPLETE" BLOCKED = "BLOCKED" FAILED = "FAILED" class Milestone(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="milestones") plan = models.ForeignKey(ProjectPlan, on_delete=models.PROTECT, related_name="milestones") key = models.CharField(max_length=50) title = models.CharField(max_length=200) goal = models.TextField() status = models.CharField(max_length=32, choices=MilestoneStatus.choices, default=MilestoneStatus.QUEUED) dependencies = models.ManyToManyField("self", symmetrical=False, blank=True) verification_contract = models.JSONField(default=dict, blank=True) order = models.PositiveIntegerField(default=0) class Meta: ordering = ["order", "created_at"] constraints = [models.UniqueConstraint(fields=["project", "key"], name="unique_milestone_key")] class Feature(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="features") milestone = models.ForeignKey(Milestone, on_delete=models.CASCADE, related_name="features") title = models.CharField(max_length=200) description = models.TextField(blank=True) acceptance_criteria = models.JSONField(default=list, blank=True) status = models.CharField(max_length=32, choices=MilestoneStatus.choices, default=MilestoneStatus.QUEUED) class TaskStatus(models.TextChoices): QUEUED = "QUEUED" READY = "READY" RUNNING = "RUNNING" WAITING_TOOL = "WAITING_TOOL" REVIEW = "REVIEW" BLOCKED = "BLOCKED" FAILED = "FAILED" COMPLETE = "COMPLETE" class TaskPriority(models.IntegerChoices): LOW = 10 NORMAL = 50 HIGH = 80 CRITICAL = 100 class Task(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="tasks") milestone = models.ForeignKey(Milestone, on_delete=models.PROTECT, related_name="tasks") feature = models.ForeignKey(Feature, on_delete=models.PROTECT, related_name="tasks", null=True, blank=True) task_type = models.CharField(max_length=80) status = models.CharField(max_length=32, choices=TaskStatus.choices, default=TaskStatus.QUEUED) priority = models.PositiveSmallIntegerField(choices=TaskPriority.choices, default=TaskPriority.NORMAL) goal = models.TextField() acceptance_criteria = models.JSONField(default=list, blank=True) assigned_agent_version = models.ForeignKey( "agents.AgentVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="tasks" ) retry_count = models.PositiveIntegerField(default=0) max_retries = models.PositiveIntegerField(default=2) class Meta: indexes = [models.Index(fields=["project", "status", "priority"])] class TaskDependency(TimestampedModel): task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name="dependency_edges") depends_on = models.ForeignKey(Task, on_delete=models.CASCADE, related_name="dependent_edges") class Meta: constraints = [models.UniqueConstraint(fields=["task", "depends_on"], name="unique_task_dependency")] class Action(TimestampedModel): task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name="actions") action_type = models.CharField(max_length=80) status = models.CharField(max_length=32, default="PENDING") contract = models.JSONField(default=dict, blank=True) result = models.JSONField(default=dict, blank=True) started_at = models.DateTimeField(null=True, blank=True) finished_at = models.DateTimeField(null=True, blank=True) class Worktree(TimestampedModel): task = models.OneToOneField(Task, on_delete=models.CASCADE, related_name="worktree") repository_path = models.TextField() worktree_path = models.TextField() branch_name = models.CharField(max_length=255) base_ref = models.CharField(max_length=255, default="main") status = models.CharField(max_length=32, default="ACTIVE") class CommitRecord(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="commits") task = models.ForeignKey(Task, on_delete=models.SET_NULL, null=True, blank=True, related_name="commits") worktree = models.ForeignKey(Worktree, on_delete=models.SET_NULL, null=True, blank=True, related_name="commits") coder = models.ForeignKey( "agents.AgentVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="coded_commits" ) reviewer = models.ForeignKey( "agents.AgentVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="reviewed_commits" ) judge = models.ForeignKey( "agents.AgentVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="judged_commits" ) test_run = models.ForeignKey( "verification.TestRun", on_delete=models.SET_NULL, null=True, blank=True, related_name="commits" ) review = models.ForeignKey( "verification.Review", on_delete=models.SET_NULL, null=True, blank=True, related_name="commits" ) verification = models.ForeignKey( "verification.Verification", on_delete=models.SET_NULL, null=True, blank=True, related_name="commits" ) graph_run = models.ForeignKey( "graph.GraphRun", on_delete=models.SET_NULL, null=True, blank=True, related_name="commits" ) steward_finding = models.ForeignKey( "projects.StewardFinding", on_delete=models.SET_NULL, null=True, blank=True, related_name="commits" ) sha = models.CharField(max_length=64) branch_name = models.CharField(max_length=255) message = models.TextField() class TaskAttempt(TimestampedModel): task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name="attempts") attempt_number = models.PositiveIntegerField() coder = models.ForeignKey("agents.AgentVersion", on_delete=models.PROTECT, related_name="task_attempts") status = models.CharField(max_length=32, default="RUNNING") context_snapshot = models.JSONField(default=dict, blank=True) coder_result = models.JSONField(default=dict, blank=True) review_findings = models.JSONField(default=list, blank=True) judge_findings = models.JSONField(default=list, blank=True) class Meta: constraints = [models.UniqueConstraint(fields=["task", "attempt_number"], name="unique_task_attempt")] class Decision(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="decisions") decision_type = models.CharField(max_length=80) decision = models.TextField() reason = models.TextField(blank=True) evidence = models.JSONField(default=list, blank=True) project_plan_version = models.PositiveIntegerField(default=0) actor = models.CharField(max_length=120) class Artifact(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="artifacts") task = models.ForeignKey(Task, on_delete=models.SET_NULL, null=True, blank=True, related_name="artifacts") artifact_type = models.CharField(max_length=80) name = models.CharField(max_length=255) uri = models.TextField(blank=True) content = models.JSONField(default=dict, blank=True) generated_by = models.CharField(max_length=120, blank=True) class RoadmapStatus(models.TextChoices): INBOX = "INBOX" NOW = "NOW" NEXT = "NEXT" LATER = "LATER" EXPLORING = "EXPLORING" DECLINED = "DECLINED" DONE = "DONE" class RoadmapItem(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="roadmap_items") title = models.CharField(max_length=200) description = models.TextField(blank=True) source = models.CharField(max_length=80, default="user") status = models.CharField(max_length=32, choices=RoadmapStatus.choices, default=RoadmapStatus.INBOX) priority = models.PositiveSmallIntegerField(default=50) class Finding(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="findings") source = models.CharField(max_length=80) finding_type = models.CharField(max_length=80) severity = models.CharField(max_length=32, default="INFO") title = models.CharField(max_length=255) description = models.TextField(blank=True) evidence = models.JSONField(default=list, blank=True) status = models.CharField(max_length=32, default="OPEN") class Scenario(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="scenarios", null=True, blank=True) name = models.CharField(max_length=200) target_type = models.CharField(max_length=80) target_id = models.CharField(max_length=120) definition = models.JSONField(default=dict, blank=True) status = models.CharField(max_length=32, default="DRAFT") class StewardPolicy(TimestampedModel): name = models.CharField(max_length=200) enabled_checks = models.JSONField(default=list, blank=True) severity_thresholds = models.JSONField(default=dict, blank=True) auto_route_thresholds = models.JSONField(default=dict, blank=True) run_cadence = models.JSONField(default=dict, blank=True) allowed_repair_scope = models.JSONField(default=dict, blank=True) approval_requirements = models.JSONField(default=dict, blank=True) ignored_paths = models.JSONField(default=list, blank=True) budget_limits = models.JSONField(default=dict, blank=True) metadata = models.JSONField(default=dict, blank=True) class StewardEnrollment(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="steward_enrollments") status = models.CharField(max_length=32, default="ACTIVE") policy = models.ForeignKey(StewardPolicy, on_delete=models.PROTECT, related_name="enrollments") enrolled_at = models.DateTimeField(auto_now_add=True) last_run_at = models.DateTimeField(null=True, blank=True) next_run_at = models.DateTimeField(null=True, blank=True) metadata = models.JSONField(default=dict, blank=True) class StewardRun(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="steward_runs") enrollment = models.ForeignKey(StewardEnrollment, on_delete=models.CASCADE, related_name="runs") execution_graph_version = models.ForeignKey("graph.ExecutionGraphVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="steward_runs") status = models.CharField(max_length=32, default="PENDING") started_at = models.DateTimeField(null=True, blank=True) completed_at = models.DateTimeField(null=True, blank=True) summary = models.TextField(blank=True) metadata = models.JSONField(default=dict, blank=True) class StewardCheck(TimestampedModel): steward_run = models.ForeignKey(StewardRun, on_delete=models.CASCADE, related_name="checks") check_type = models.CharField(max_length=80) status = models.CharField(max_length=32) evidence = models.JSONField(default=dict, blank=True) severity = models.CharField(max_length=32, default="INFO") metadata = models.JSONField(default=dict, blank=True) class StewardFinding(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="steward_findings") steward_run = models.ForeignKey(StewardRun, on_delete=models.SET_NULL, null=True, blank=True, related_name="findings") source_check = models.ForeignKey(StewardCheck, on_delete=models.SET_NULL, null=True, blank=True, related_name="findings") finding_type = models.CharField(max_length=80) title = models.CharField(max_length=255) summary = models.TextField(blank=True) evidence = models.JSONField(default=dict, blank=True) severity = models.CharField(max_length=32, default="INFO") confidence = models.FloatField(default=0.0) status = models.CharField(max_length=32, default="OPEN") recommended_action = models.CharField(max_length=32, default="INVESTIGATE") recommended_route = models.CharField(max_length=120, blank=True) grouping_key = models.CharField(max_length=240) first_seen = models.DateTimeField(null=True, blank=True) last_seen = models.DateTimeField(null=True, blank=True) occurrence_count = models.PositiveIntegerField(default=1) metadata = models.JSONField(default=dict, blank=True) class StewardAction(TimestampedModel): finding = models.ForeignKey(StewardFinding, on_delete=models.CASCADE, related_name="actions") action_type = models.CharField(max_length=32) status = models.CharField(max_length=32, default="PENDING") task = models.ForeignKey(Task, on_delete=models.SET_NULL, null=True, blank=True, related_name="steward_actions") roadmap_item = models.ForeignKey(RoadmapItem, on_delete=models.SET_NULL, null=True, blank=True, related_name="steward_actions") investigation = models.ForeignKey("agents.ProgenyInvestigation", on_delete=models.SET_NULL, null=True, blank=True, related_name="steward_actions") improvement_candidate = models.ForeignKey("agents.ImprovementCandidate", on_delete=models.SET_NULL, null=True, blank=True, related_name="steward_actions") requires_approval = models.BooleanField(default=False) approved_at = models.DateTimeField(null=True, blank=True) metadata = models.JSONField(default=dict, blank=True)