Artifex/control_plane/projects/models.py
2026-08-15 19:46:07 +07:00

556 lines
29 KiB
Python

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)