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" ) extension_candidate = models.ForeignKey( "projects.ExtensionCandidate", on_delete=models.SET_NULL, null=True, blank=True, related_name="commits" ) evolution_candidate = models.ForeignKey( "projects.EvolutionCandidate", 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): PROPOSED = "PROPOSED" ACCEPTED = "ACCEPTED" PLANNING = "PLANNING" IN_PROGRESS = "IN_PROGRESS" COMPLETE = "COMPLETE" DEFERRED = "DEFERRED" REJECTED = "REJECTED" SUPERSEDED = "SUPERSEDED" class RoadmapHorizon(models.TextChoices): NOW = "NOW" NEXT = "NEXT" LATER = "LATER" EXPLORING = "EXPLORING" class RoadmapTargetAction(models.TextChoices): EXTEND = "EXTEND" EVOLVE = "EVOLVE" REPAIR = "REPAIR" INVESTIGATE = "INVESTIGATE" NONE = "NONE" 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") source_ref = models.JSONField(default=dict, blank=True) rationale = models.TextField(blank=True) evidence = models.JSONField(default=dict, blank=True) horizon = models.CharField(max_length=32, choices=RoadmapHorizon.choices, default=RoadmapHorizon.EXPLORING) category = models.CharField(max_length=80, blank=True) status = models.CharField(max_length=32, choices=RoadmapStatus.choices, default=RoadmapStatus.PROPOSED) target_action = models.CharField(max_length=32, choices=RoadmapTargetAction.choices, default=RoadmapTargetAction.NONE) priority = models.PositiveSmallIntegerField(default=50) value_score = models.FloatField(default=0.5) effort_score = models.FloatField(default=0.5) risk_score = models.FloatField(default=0.5) confidence = models.FloatField(default=0.5) strategic_fit = models.FloatField(default=0.5) technical_fit = models.FloatField(default=0.5) urgency = models.FloatField(default=0.5) composite_score = models.FloatField(default=0.5) grouping_key = models.CharField(max_length=240, blank=True) dependencies = models.ManyToManyField("self", symmetrical=False, blank=True, related_name="dependent_roadmap_items") related_items = models.ManyToManyField("self", symmetrical=True, blank=True) converted_extension = models.ForeignKey("projects.ExtensionCandidate", on_delete=models.SET_NULL, null=True, blank=True, related_name="converted_from_roadmap_items") converted_evolution = models.ForeignKey("projects.EvolutionCandidate", on_delete=models.SET_NULL, null=True, blank=True, related_name="converted_from_roadmap_items") converted_investigation = models.ForeignKey("agents.ProgenyInvestigation", on_delete=models.SET_NULL, null=True, blank=True, related_name="converted_from_roadmap_items") metadata = models.JSONField(default=dict, blank=True) 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 ScenarioSuite(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="scenario_suites") name = models.CharField(max_length=200) version = models.PositiveIntegerField(default=1) purpose = models.TextField(blank=True) status = models.CharField(max_length=32, default="DRAFT") frozen_at = models.DateTimeField(null=True, blank=True) execution_graph_version = models.ForeignKey("graph.ExecutionGraphVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="scenario_suites") metadata = models.JSONField(default=dict, blank=True) class Scenario(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="scenarios", null=True, blank=True) suite = models.ForeignKey(ScenarioSuite, on_delete=models.CASCADE, related_name="scenarios", null=True, blank=True) name = models.CharField(max_length=200) title = models.CharField(max_length=255, blank=True) description = models.TextField(blank=True) scenario_type = models.CharField(max_length=80, default="WORKFLOW") target_component = models.CharField(max_length=240, blank=True) target_type = models.CharField(max_length=80) target_id = models.CharField(max_length=120) preconditions = models.JSONField(default=list, blank=True) injected_condition = models.JSONField(default=dict, blank=True) expected_invariants = models.JSONField(default=list, blank=True) success_criteria = models.JSONField(default=list, blank=True) severity = models.CharField(max_length=32, default="MEDIUM") source = models.CharField(max_length=80, default="USER") definition = models.JSONField(default=dict, blank=True) status = models.CharField(max_length=32, default="DRAFT") validated_at = models.DateTimeField(null=True, blank=True) rejection_reason = models.TextField(blank=True) resource_budget = models.JSONField(default=dict, blank=True) metadata = models.JSONField(default=dict, blank=True) class ScenarioRun(TimestampedModel): scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE, related_name="runs") project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="scenario_runs") repository_baseline = models.CharField(max_length=255, blank=True) execution_graph_version = models.ForeignKey("graph.ExecutionGraphVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="scenario_runs") graph_run = models.ForeignKey("graph.GraphRun", on_delete=models.SET_NULL, null=True, blank=True, related_name="scenario_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) environment_metadata = models.JSONField(default=dict, blank=True) result = models.CharField(max_length=32, blank=True) failure_evidence = models.JSONField(default=dict, blank=True) telemetry = models.JSONField(default=dict, blank=True) class ScenarioFinding(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="scenario_findings") scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE, related_name="findings") scenario_run = models.ForeignKey(ScenarioRun, on_delete=models.SET_NULL, null=True, blank=True, related_name="findings") 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="MEDIUM") confidence = models.FloatField(default=0.5) affected_component = models.CharField(max_length=240, blank=True) failure_category = models.CharField(max_length=80, blank=True) 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) status = models.CharField(max_length=32, default="OPEN") roadmap_item = models.ForeignKey(RoadmapItem, on_delete=models.SET_NULL, null=True, blank=True, related_name="scenario_findings") steward_policy_metadata = models.JSONField(default=dict, blank=True) progeny_signal = models.ForeignKey("agents.ProgenySignal", on_delete=models.SET_NULL, null=True, blank=True, related_name="scenario_findings") metadata = models.JSONField(default=dict, blank=True) 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") extension_candidate = models.ForeignKey("projects.ExtensionCandidate", on_delete=models.SET_NULL, null=True, blank=True, related_name="steward_actions") evolution_candidate = models.ForeignKey("projects.EvolutionCandidate", 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) class ExtensionCandidate(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="extension_candidates") title = models.CharField(max_length=255) description = models.TextField(blank=True) rationale = models.TextField(blank=True) source = models.CharField(max_length=80, default="user") expected_value = models.TextField(blank=True) affected_areas = models.JSONField(default=list, blank=True) estimated_complexity = models.CharField(max_length=80, blank=True) risk = models.CharField(max_length=80, default="MEDIUM") confidence = models.FloatField(default=0.0) status = models.CharField(max_length=32, default="PROPOSED") evidence = models.JSONField(default=dict, blank=True) source_steward_finding = models.ForeignKey(StewardFinding, on_delete=models.SET_NULL, null=True, blank=True, related_name="extension_candidates") source_investigation = models.ForeignKey("agents.ProgenyInvestigation", on_delete=models.SET_NULL, null=True, blank=True, related_name="extension_candidates") source_roadmap_item = models.ForeignKey(RoadmapItem, on_delete=models.SET_NULL, null=True, blank=True, related_name="extension_candidates") source_opportunity = models.ForeignKey("projects.ExplorationOpportunity", on_delete=models.SET_NULL, null=True, blank=True, related_name="extension_candidates") metadata = models.JSONField(default=dict, blank=True) class ExtensionPlan(TimestampedModel): candidate = models.ForeignKey(ExtensionCandidate, on_delete=models.CASCADE, related_name="plans") project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="extension_plans") status = models.CharField(max_length=32, default="DRAFT") strategy = models.TextField(blank=True) plan = models.JSONField(default=dict, blank=True) acceptance_criteria = models.JSONField(default=list, blank=True) context_snapshot = models.JSONField(default=dict, blank=True) project_plan = models.ForeignKey(ProjectPlan, on_delete=models.SET_NULL, null=True, blank=True, related_name="extension_plans") verification = models.ForeignKey("verification.Verification", on_delete=models.SET_NULL, null=True, blank=True, related_name="extension_plans") approved_at = models.DateTimeField(null=True, blank=True) completed_at = models.DateTimeField(null=True, blank=True) metadata = models.JSONField(default=dict, blank=True) class EvolutionCandidate(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="evolution_candidates") target = models.CharField(max_length=240) objective = models.TextField() baseline_measurement = models.JSONField(default=dict, blank=True) desired_direction = models.CharField(max_length=32) target_measurement = models.JSONField(default=dict, blank=True) rationale = models.TextField(blank=True) source = models.CharField(max_length=80, default="user") evidence = models.JSONField(default=dict, blank=True) status = models.CharField(max_length=32, default="PROPOSED") risk = models.CharField(max_length=80, default="MEDIUM") confidence = models.FloatField(default=0.0) source_steward_finding = models.ForeignKey(StewardFinding, on_delete=models.SET_NULL, null=True, blank=True, related_name="evolution_candidates") source_investigation = models.ForeignKey("agents.ProgenyInvestigation", on_delete=models.SET_NULL, null=True, blank=True, related_name="evolution_candidates") source_roadmap_item = models.ForeignKey(RoadmapItem, on_delete=models.SET_NULL, null=True, blank=True, related_name="evolution_candidates") source_opportunity = models.ForeignKey("projects.ExplorationOpportunity", on_delete=models.SET_NULL, null=True, blank=True, related_name="evolution_candidates") metadata = models.JSONField(default=dict, blank=True) class EvolutionPlan(TimestampedModel): candidate = models.ForeignKey(EvolutionCandidate, on_delete=models.CASCADE, related_name="plans") project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="evolution_plans") status = models.CharField(max_length=32, default="DRAFT") baseline = models.JSONField(default=dict, blank=True) hypothesis = models.TextField(blank=True) intervention = models.TextField(blank=True) measurement_method = models.JSONField(default=dict, blank=True) success_threshold = models.JSONField(default=dict, blank=True) regression_constraints = models.JSONField(default=list, blank=True) affected_components = models.JSONField(default=list, blank=True) task_plan = models.JSONField(default=dict, blank=True) experiment_requirements = models.JSONField(default=dict, blank=True) candidate_measurement = models.JSONField(default=dict, blank=True) delta = models.JSONField(default=dict, blank=True) verdict = models.CharField(max_length=32, blank=True) context_snapshot = models.JSONField(default=dict, blank=True) project_plan = models.ForeignKey(ProjectPlan, on_delete=models.SET_NULL, null=True, blank=True, related_name="evolution_plans") verification = models.ForeignKey("verification.Verification", on_delete=models.SET_NULL, null=True, blank=True, related_name="evolution_plans") approved_at = models.DateTimeField(null=True, blank=True) completed_at = models.DateTimeField(null=True, blank=True) metadata = models.JSONField(default=dict, blank=True) class Exploration(TimestampedModel): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="explorations") source = models.CharField(max_length=80, default="Explorer") status = models.CharField(max_length=32, default="RUNNING") prompt = models.TextField(blank=True) context_snapshot = models.JSONField(default=dict, blank=True) execution_graph_version = models.ForeignKey("graph.ExecutionGraphVersion", on_delete=models.SET_NULL, null=True, blank=True, related_name="explorations") completed_at = models.DateTimeField(null=True, blank=True) metadata = models.JSONField(default=dict, blank=True) class ExplorationOpportunity(TimestampedModel): exploration = models.ForeignKey(Exploration, on_delete=models.CASCADE, related_name="opportunities") project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="exploration_opportunities") title = models.CharField(max_length=255) description = models.TextField(blank=True) opportunity_type = models.CharField(max_length=80) evidence = models.JSONField(default=dict, blank=True) rationale = models.TextField(blank=True) expected_value = models.TextField(blank=True) effort_estimate = models.CharField(max_length=80, blank=True) risk = models.CharField(max_length=80, default="MEDIUM") confidence = models.FloatField(default=0.0) technical_fit = models.FloatField(default=0.0) strategic_fit = models.FloatField(default=0.0) value_score = models.FloatField(default=0.0) effort_score = models.FloatField(default=0.0) risk_score = models.FloatField(default=0.0) composite_score = models.FloatField(default=0.0) recommended_action = models.CharField(max_length=32, default="DEFER") status = models.CharField(max_length=32, default="DISCOVERED") grouping_key = models.CharField(max_length=240) converted_extension = models.ForeignKey(ExtensionCandidate, on_delete=models.SET_NULL, null=True, blank=True, related_name="converted_from_opportunities") converted_evolution = models.ForeignKey(EvolutionCandidate, on_delete=models.SET_NULL, null=True, blank=True, related_name="converted_from_opportunities") metadata = models.JSONField(default=dict, blank=True)