diff options
| author | soryu <soryu@soryu.co> | 2026-05-18 01:21:30 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-18 01:21:30 +0100 |
| commit | f240675da99bc7705e473b8f70a2628812aa4c10 (patch) | |
| tree | 3ee2d24b431ccb8cd1a3013c86b34a5782a3e224 /makima/src/db/models.rs | |
| parent | 0d996cf7590e3e52f424859c7d6f0e68640f119e (diff) | |
| download | soryu-master.tar.gz soryu-master.zip | |
The contracts table, supervisor task type, and all their backing
machinery have been inert for several PRs. The directives system reads
its own active contract body for spec text, and PR #135 removed the
last LLM surface that spawned supervisors.
This PR wipes the dead surface in one shot — the user authorised a DB
wipe, so the migration drops every legacy table with CASCADE rather
than carrying forward stub rows. Net change: −12k LOC across handlers,
repository, state, models, the TUI, and the listen module.
What's gone:
- contracts, contract_chat_*, contract_events, contract_repositories,
contract_type_templates tables.
- supervisor_states, supervisor_heartbeats tables.
- mesh_chat_conversations, mesh_chat_messages tables.
- tasks.contract_id/is_supervisor/supervisor_task_id/supervisor_worktree_task_id columns.
- directive_steps.contract_id/contract_type columns.
- files.contract_id/contract_phase columns.
- history_events.contract_id/phase columns.
- The Contract/Supervisor/MeshChat handler + model + repository
surface, plus the daemon TUI views that read them.
- The standalone listen.rs websocket handler (orphaned with the LLM).
What stays:
- mesh_supervisor handler: trimmed to just the questions + orders
backchannel used by `makima directive ask` / `create-order` (kept
the URL prefix for CLI client compat).
- directive_documents (the user-facing "contracts" surface).
- pending_questions in-memory state for the directive Ask flow.
cargo check, cargo test --lib (68 passed), tsc, and vite build all
clean.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'makima/src/db/models.rs')
| -rw-r--r-- | makima/src/db/models.rs | 905 |
1 files changed, 2 insertions, 903 deletions
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs index 3fb9667..bfb8bf3 100644 --- a/makima/src/db/models.rs +++ b/makima/src/db/models.rs @@ -111,10 +111,6 @@ pub enum BodyElement { pub struct File { pub id: Uuid, pub owner_id: Uuid, - /// Contract this file belongs to (optional) - pub contract_id: Option<Uuid>, - /// Phase of the contract when file was added (e.g., "research", "specify") - pub contract_phase: Option<String>, pub name: String, pub description: Option<String>, #[sqlx(json)] @@ -141,8 +137,6 @@ pub struct File { #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct CreateFileRequest { - /// Contract this file belongs to (required - files must belong to a contract) - pub contract_id: Uuid, /// Name of the file (auto-generated if not provided) pub name: Option<String>, /// Optional description @@ -157,8 +151,6 @@ pub struct CreateFileRequest { pub body: Vec<BodyElement>, /// Path to linked repository file (e.g., "README.md") pub repo_file_path: Option<String>, - /// Contract phase this file belongs to (for deliverable tracking) - pub contract_phase: Option<String>, } /// Request payload for updating an existing file. @@ -194,12 +186,6 @@ pub struct FileListResponse { #[serde(rename_all = "camelCase")] pub struct FileSummary { pub id: Uuid, - /// Contract this file belongs to - pub contract_id: Option<Uuid>, - /// Contract name (joined from contracts table) - pub contract_name: Option<String>, - /// Phase when file was added to contract - pub contract_phase: Option<String>, pub name: String, pub description: Option<String>, pub transcript_count: usize, @@ -224,9 +210,6 @@ impl From<File> for FileSummary { .fold(0.0_f32, f32::max); Self { id: file.id, - contract_id: file.contract_id, - contract_name: None, // Not available from File alone, requires JOIN - contract_phase: file.contract_phase, name: file.name, description: file.description, transcript_count: file.transcript.len(), @@ -425,8 +408,6 @@ impl std::str::FromStr for MergeMode { pub struct Task { pub id: Uuid, pub owner_id: Uuid, - /// Contract this task belongs to (required for new tasks) - pub contract_id: Option<Uuid>, pub parent_task_id: Option<Uuid>, /// Depth in task hierarchy (no longer constrained) pub depth: i32, @@ -436,11 +417,6 @@ pub struct Task { pub priority: i32, pub plan: String, - // Supervisor flag - /// True for contract supervisor tasks. Only supervisors can spawn new tasks. - #[serde(default)] - pub is_supervisor: bool, - // Daemon/container info pub daemon_id: Option<Uuid>, pub container_id: Option<String>, @@ -565,14 +541,6 @@ impl Task { #[serde(rename_all = "camelCase")] pub struct TaskSummary { pub id: Uuid, - /// Contract this task belongs to - pub contract_id: Option<Uuid>, - /// Contract name (joined from contracts table) - pub contract_name: Option<String>, - /// Contract phase (joined from contracts table) - pub contract_phase: Option<String>, - /// Contract status (joined from contracts table): 'active', 'completed', 'archived' - pub contract_status: Option<String>, pub parent_task_id: Option<Uuid>, /// Depth in task hierarchy: 0=orchestrator (top-level), 1=subtask (max) pub depth: i32, @@ -582,9 +550,6 @@ pub struct TaskSummary { pub progress_summary: Option<String>, pub subtask_count: i64, pub version: i32, - /// True for contract supervisor tasks - #[serde(default)] - pub is_supervisor: bool, /// Whether this task is hidden from the UI (user dismissed it) #[serde(default)] pub hidden: bool, @@ -597,10 +562,6 @@ impl From<Task> for TaskSummary { fn from(task: Task) -> Self { Self { id: task.id, - contract_id: task.contract_id, - contract_name: None, // Not available from Task directly - contract_phase: None, // Not available from Task directly - contract_status: None, // Not available from Task directly parent_task_id: task.parent_task_id, depth: task.depth, name: task.name, @@ -609,7 +570,6 @@ impl From<Task> for TaskSummary { progress_summary: task.progress_summary, subtask_count: 0, // Would need separate query version: task.version, - is_supervisor: task.is_supervisor, hidden: task.hidden, created_at: task.created_at, updated_at: task.updated_at, @@ -629,8 +589,6 @@ pub struct TaskListResponse { #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct CreateTaskRequest { - /// Contract this task belongs to (optional for branched/anonymous tasks) - pub contract_id: Option<Uuid>, /// Name of the task pub name: String, /// Optional description @@ -639,9 +597,6 @@ pub struct CreateTaskRequest { pub plan: String, /// Parent task ID (for subtasks) pub parent_task_id: Option<Uuid>, - /// True for contract supervisor tasks. Only supervisors can spawn new tasks. - #[serde(default)] - pub is_supervisor: bool, /// Priority (higher = more urgent) #[serde(default)] pub priority: i32, @@ -668,9 +623,6 @@ pub struct CreateTaskRequest { pub branched_from_task_id: Option<Uuid>, /// Conversation history to initialize the task with (JSON array of messages) pub conversation_history: Option<serde_json::Value>, - /// Task ID whose worktree this task shares. When set, this task reuses the supervisor's - /// worktree instead of creating its own, and should NOT have its worktree deleted during cleanup. - pub supervisor_worktree_task_id: Option<Uuid>, /// Directive this task belongs to (for directive-driven tasks) pub directive_id: Option<Uuid>, /// Directive step this task executes @@ -935,87 +887,8 @@ pub struct TaskOutputResponse { pub task_id: Uuid, } -// ============================================================================= -// Mesh Chat History Types -// ============================================================================= - -/// Mesh chat conversation for persisting history -#[derive(Debug, Clone, FromRow, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct MeshChatConversation { - pub id: Uuid, - pub owner_id: Uuid, - pub name: Option<String>, - pub is_active: bool, - pub created_at: DateTime<Utc>, - pub updated_at: DateTime<Utc>, -} - -/// Individual message in a mesh chat conversation -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct MeshChatMessageRecord { - pub id: Uuid, - pub conversation_id: Uuid, - pub role: String, - pub content: String, - pub context_type: String, - pub context_task_id: Option<Uuid>, - /// Tool calls made during this message (JSON, nullable) - pub tool_calls: Option<serde_json::Value>, - /// Pending questions requiring user response (JSON, nullable) - pub pending_questions: Option<serde_json::Value>, - pub created_at: DateTime<Utc>, -} - -/// Response for chat history endpoint -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct MeshChatHistoryResponse { - pub conversation_id: Uuid, - pub messages: Vec<MeshChatMessageRecord>, -} - -// ============================================================================= -// Contract Chat History Types -// ============================================================================= - -/// Conversation thread for contract chat (scoped to a specific contract) -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ContractChatConversation { - pub id: Uuid, - pub contract_id: Uuid, - pub owner_id: Uuid, - pub name: Option<String>, - pub is_active: bool, - pub created_at: DateTime<Utc>, - pub updated_at: DateTime<Utc>, -} - -/// Individual message in a contract chat conversation -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ContractChatMessageRecord { - pub id: Uuid, - pub conversation_id: Uuid, - pub role: String, - pub content: String, - /// Tool calls made during this message (JSON, nullable) - pub tool_calls: Option<serde_json::Value>, - /// Pending questions requiring user response (JSON, nullable) - pub pending_questions: Option<serde_json::Value>, - pub created_at: DateTime<Utc>, -} - -/// Response for contract chat history endpoint -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ContractChatHistoryResponse { - pub contract_id: Uuid, - pub conversation_id: Uuid, - pub messages: Vec<ContractChatMessageRecord>, -} +// (MeshChat* + ContractChat* types removed alongside their dead +// tables/handlers — see migration 20260517000000.) // ============================================================================= // Merge API Types @@ -1120,772 +993,6 @@ pub struct MergeCompleteCheckResponse { pub skipped_count: u32, } -// ============================================================================= -// Contract Type Templates (User-defined) -// ============================================================================= - -/// A phase definition within a contract template -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct PhaseDefinition { - /// Phase identifier (e.g., "research", "plan", "execute") - pub id: String, - /// Display name for the phase - pub name: String, - /// Order in the workflow (0-indexed) - pub order: i32, -} - -/// A deliverable definition within a phase -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DeliverableDefinition { - /// Deliverable identifier (e.g., "plan-document", "pull-request") - pub id: String, - /// Display name for the deliverable - pub name: String, - /// Priority: "required", "recommended", or "optional" - #[serde(default = "default_priority")] - pub priority: String, -} - -fn default_priority() -> String { - "required".to_string() -} - -/// Phase configuration stored on a contract (copied from template at creation) -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct PhaseConfig { - /// Ordered list of phases in the workflow - pub phases: Vec<PhaseDefinition>, - /// Default starting phase - pub default_phase: String, - /// Deliverables per phase: { "phase_id": [deliverables] } - #[serde(default)] - pub deliverables: std::collections::HashMap<String, Vec<DeliverableDefinition>>, -} - -/// Contract type template record from the database -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ContractTypeTemplateRecord { - pub id: Uuid, - pub owner_id: Uuid, - pub name: String, - pub description: Option<String>, - #[sqlx(json)] - pub phases: Vec<PhaseDefinition>, - pub default_phase: String, - #[sqlx(json)] - pub deliverables: Option<std::collections::HashMap<String, Vec<DeliverableDefinition>>>, - pub version: i32, - pub created_at: DateTime<Utc>, - pub updated_at: DateTime<Utc>, -} - -/// Request to create a new contract type template -#[derive(Debug, Clone, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct CreateTemplateRequest { - pub name: String, - pub description: Option<String>, - pub phases: Vec<PhaseDefinition>, - pub default_phase: String, - pub deliverables: Option<std::collections::HashMap<String, Vec<DeliverableDefinition>>>, -} - -/// Request to update a contract type template -#[derive(Debug, Clone, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct UpdateTemplateRequest { - pub name: Option<String>, - pub description: Option<String>, - pub phases: Option<Vec<PhaseDefinition>>, - pub default_phase: Option<String>, - pub deliverables: Option<std::collections::HashMap<String, Vec<DeliverableDefinition>>>, - /// Version for optimistic locking - pub version: Option<i32>, -} - -/// Summary of a contract type template for list views -#[derive(Debug, Clone, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ContractTypeTemplateSummary { - pub id: Uuid, - pub name: String, - pub description: Option<String>, - pub phases: Vec<PhaseDefinition>, - pub default_phase: String, - pub is_builtin: bool, - pub version: i32, - pub created_at: DateTime<Utc>, -} - -// ============================================================================= -// Contract Types -// ============================================================================= - -/// Contract type determines the workflow and required documents -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum ContractType { - /// Simple Plan -> Execute workflow (default) - /// - Plan phase: requires a "Plan" document - /// - Execute phase: requires a "PR" document - Simple, - /// Specification-based development with TDD - /// - Research: requires "Research Notes" document - /// - Specify: requires "Requirements Document" - /// - Plan: requires "Plan" document - /// - Execute: requires "PR" document - /// - Review: requires "Release Notes" document - Specification, - /// Execute-only workflow with no deliverables - /// - Only has "execute" phase - /// - NO deliverables at all - just execute tasks directly - Execute, -} - -impl Default for ContractType { - fn default() -> Self { - ContractType::Simple - } -} - -impl std::fmt::Display for ContractType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ContractType::Simple => write!(f, "simple"), - ContractType::Specification => write!(f, "specification"), - ContractType::Execute => write!(f, "execute"), - } - } -} - -impl std::str::FromStr for ContractType { - type Err = String; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s.to_lowercase().as_str() { - "simple" => Ok(ContractType::Simple), - "specification" => Ok(ContractType::Specification), - "execute" => Ok(ContractType::Execute), - _ => Err(format!("Unknown contract type: {}", s)), - } - } -} - -/// Contract phase for workflow progression -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum ContractPhase { - Research, - Specify, - Plan, - Execute, - Review, -} - -impl std::fmt::Display for ContractPhase { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ContractPhase::Research => write!(f, "research"), - ContractPhase::Specify => write!(f, "specify"), - ContractPhase::Plan => write!(f, "plan"), - ContractPhase::Execute => write!(f, "execute"), - ContractPhase::Review => write!(f, "review"), - } - } -} - -impl std::str::FromStr for ContractPhase { - type Err = String; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s.to_lowercase().as_str() { - "research" => Ok(ContractPhase::Research), - "specify" => Ok(ContractPhase::Specify), - "plan" => Ok(ContractPhase::Plan), - "execute" => Ok(ContractPhase::Execute), - "review" => Ok(ContractPhase::Review), - _ => Err(format!("Unknown contract phase: {}", s)), - } - } -} - -/// Contract status -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum ContractStatus { - Active, - Completed, - Archived, -} - -impl std::fmt::Display for ContractStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ContractStatus::Active => write!(f, "active"), - ContractStatus::Completed => write!(f, "completed"), - ContractStatus::Archived => write!(f, "archived"), - } - } -} - -impl std::str::FromStr for ContractStatus { - type Err = String; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s.to_lowercase().as_str() { - "active" => Ok(ContractStatus::Active), - "completed" => Ok(ContractStatus::Completed), - "archived" => Ok(ContractStatus::Archived), - _ => Err(format!("Unknown contract status: {}", s)), - } - } -} - -/// Repository source type -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum RepositorySourceType { - /// Existing remote repo (GitHub, GitLab, etc) - Remote, - /// Existing local repo - Local, - /// New repo created/managed by Makima daemon - Managed, -} - -impl std::fmt::Display for RepositorySourceType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - RepositorySourceType::Remote => write!(f, "remote"), - RepositorySourceType::Local => write!(f, "local"), - RepositorySourceType::Managed => write!(f, "managed"), - } - } -} - -impl std::str::FromStr for RepositorySourceType { - type Err = String; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s.to_lowercase().as_str() { - "remote" => Ok(RepositorySourceType::Remote), - "local" => Ok(RepositorySourceType::Local), - "managed" => Ok(RepositorySourceType::Managed), - _ => Err(format!("Unknown repository source type: {}", s)), - } - } -} - -/// Repository status (for managed repos) -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum RepositoryStatus { - /// Repo is usable - Ready, - /// Waiting for daemon to create - Pending, - /// Daemon is creating the repo - Creating, - /// Creation failed - Failed, -} - -impl std::fmt::Display for RepositoryStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - RepositoryStatus::Ready => write!(f, "ready"), - RepositoryStatus::Pending => write!(f, "pending"), - RepositoryStatus::Creating => write!(f, "creating"), - RepositoryStatus::Failed => write!(f, "failed"), - } - } -} - -impl std::str::FromStr for RepositoryStatus { - type Err = String; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s.to_lowercase().as_str() { - "ready" => Ok(RepositoryStatus::Ready), - "pending" => Ok(RepositoryStatus::Pending), - "creating" => Ok(RepositoryStatus::Creating), - "failed" => Ok(RepositoryStatus::Failed), - _ => Err(format!("Unknown repository status: {}", s)), - } - } -} - -/// Contract record from the database -#[derive(Debug, Clone, FromRow, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct Contract { - pub id: Uuid, - pub owner_id: Uuid, - pub name: String, - pub description: Option<String>, - /// Contract type: "simple" or "specification" - pub contract_type: String, - pub phase: String, - pub status: String, - /// The long-running supervisor task that orchestrates this contract - #[serde(skip_serializing_if = "Option::is_none")] - pub supervisor_task_id: Option<Uuid>, - /// Whether tasks for this contract should run in autonomous loop mode. - /// When enabled, tasks will automatically restart with --continue if they exit - /// without a COMPLETION_GATE indicating ready: true. - #[serde(default)] - pub autonomous_loop: bool, - /// Whether to wait for user confirmation before progressing to the next phase. - /// When enabled, the supervisor will pause and ask the user to review and approve - /// phase outputs (like plans, requirements, etc.) before continuing. - #[serde(default)] - pub phase_guard: bool, - /// Completed deliverables per phase. - /// Structure: { "plan": ["plan-document"], "execute": ["pull-request"] } - #[sqlx(json)] - #[serde(default)] - pub completed_deliverables: serde_json::Value, - /// Whether this contract operates in local-only mode. - /// When enabled, automatic completion actions (branch, merge, pr) are skipped, - /// allowing users to manually handle code changes via patch files or other means. - #[serde(default)] - pub local_only: bool, - /// Whether to auto-merge to target branch locally when local_only mode is enabled. - /// When both local_only and auto_merge_local are true, completed task changes will be - /// automatically merged to the master/main branch locally (without pushing or creating PRs). - #[serde(default)] - pub auto_merge_local: bool, - /// Phase configuration copied from template at contract creation (raw JSON). - /// When present, this overrides the built-in contract type phases. - /// Use `get_phase_config()` to get the parsed PhaseConfig. - #[serde(skip_serializing_if = "Option::is_none")] - pub phase_config: Option<serde_json::Value>, - pub version: i32, - pub created_at: DateTime<Utc>, - pub updated_at: DateTime<Utc>, -} - -impl Contract { - /// Parse contract_type string to ContractType enum - pub fn contract_type_enum(&self) -> Result<ContractType, String> { - self.contract_type.parse() - } - - /// Parse phase string to ContractPhase enum - pub fn phase_enum(&self) -> Result<ContractPhase, String> { - self.phase.parse() - } - - /// Parse status string to ContractStatus enum - pub fn status_enum(&self) -> Result<ContractStatus, String> { - self.status.parse() - } - - /// Get valid phase IDs for this contract (as strings) - pub fn valid_phase_ids(&self) -> Vec<String> { - // Check phase_config first (for custom templates) - if let Some(config) = self.get_phase_config() { - let mut phases: Vec<_> = config.phases.iter().collect(); - phases.sort_by_key(|p| p.order); - return phases.iter().map(|p| p.id.clone()).collect(); - } - - // Fall back to built-in contract types - match self.contract_type.as_str() { - "simple" => vec!["plan".to_string(), "execute".to_string()], - "specification" => vec![ - "research".to_string(), - "specify".to_string(), - "plan".to_string(), - "execute".to_string(), - "review".to_string(), - ], - "execute" => vec!["execute".to_string()], - _ => vec!["plan".to_string(), "execute".to_string()], - } - } - - /// Get valid phases for this contract type (as ContractPhase enums) - /// Note: For custom templates with non-standard phases, this only returns - /// phases that map to the ContractPhase enum. - pub fn valid_phases(&self) -> Vec<ContractPhase> { - self.valid_phase_ids() - .iter() - .filter_map(|id| id.parse::<ContractPhase>().ok()) - .collect() - } - - /// Get the initial phase ID for this contract type (as string) - pub fn initial_phase_id(&self) -> String { - // Check phase_config first (for custom templates) - if let Some(config) = self.get_phase_config() { - return config.default_phase.clone(); - } - - // Fall back to built-in contract types - match self.contract_type.as_str() { - "specification" => "research".to_string(), - "execute" => "execute".to_string(), - _ => "plan".to_string(), - } - } - - /// Get the initial phase for this contract type (as ContractPhase enum) - pub fn initial_phase(&self) -> ContractPhase { - self.initial_phase_id() - .parse() - .unwrap_or(ContractPhase::Plan) - } - - /// Get the terminal phase ID for this contract type (as string) - pub fn terminal_phase_id(&self) -> String { - // Check phase_config first (for custom templates) - if let Some(config) = self.get_phase_config() { - // Last phase in sorted order is the terminal phase - let mut phases: Vec<_> = config.phases.iter().collect(); - phases.sort_by_key(|p| p.order); - if let Some(last) = phases.last() { - return last.id.clone(); - } - } - - // Fall back to built-in contract types - match self.contract_type.as_str() { - "specification" => "review".to_string(), - _ => "execute".to_string(), - } - } - - /// Get the terminal phase for this contract type (phase where contract can be completed) - pub fn terminal_phase(&self) -> ContractPhase { - self.terminal_phase_id() - .parse() - .unwrap_or(ContractPhase::Execute) - } - - /// Check if a phase ID is valid for this contract - pub fn is_valid_phase(&self, phase_id: &str) -> bool { - self.valid_phase_ids().contains(&phase_id.to_string()) - } - - /// Get the phase configuration for custom templates - pub fn get_phase_config(&self) -> Option<PhaseConfig> { - self.phase_config - .as_ref() - .and_then(|v| serde_json::from_value(v.clone()).ok()) - } - - /// Get completed deliverable IDs for a specific phase - pub fn get_completed_deliverables(&self, phase: &str) -> Vec<String> { - self.completed_deliverables - .get(phase) - .and_then(|v| v.as_array()) - .map(|arr| { - arr.iter() - .filter_map(|v| v.as_str().map(String::from)) - .collect() - }) - .unwrap_or_default() - } - - /// Check if a specific deliverable is marked as complete for a phase - pub fn is_deliverable_complete(&self, phase: &str, deliverable_id: &str) -> bool { - self.get_completed_deliverables(phase) - .contains(&deliverable_id.to_string()) - } -} - -/// Contract repository record from the database -#[derive(Debug, Clone, FromRow, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ContractRepository { - pub id: Uuid, - pub contract_id: Uuid, - pub name: String, - pub repository_url: Option<String>, - pub local_path: Option<String>, - pub source_type: String, - pub status: String, - pub is_primary: bool, - pub created_at: DateTime<Utc>, - pub updated_at: DateTime<Utc>, -} - -impl ContractRepository { - /// Parse source_type string to RepositorySourceType enum - pub fn source_type_enum(&self) -> Result<RepositorySourceType, String> { - self.source_type.parse() - } - - /// Parse status string to RepositoryStatus enum - pub fn status_enum(&self) -> Result<RepositoryStatus, String> { - self.status.parse() - } -} - -/// Summary of a contract for list views -#[derive(Debug, Clone, FromRow, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ContractSummary { - pub id: Uuid, - pub name: String, - pub description: Option<String>, - /// Contract type: "simple" or "specification" - pub contract_type: String, - pub phase: String, - pub status: String, - /// Supervisor task ID for contract orchestration - pub supervisor_task_id: Option<Uuid>, - /// When true, tasks do not auto-execute completion actions and work stays in worktrees. - #[serde(default)] - pub local_only: bool, - /// When true with local_only, automatically merge completed tasks to target branch locally. - #[serde(default)] - pub auto_merge_local: bool, - pub file_count: i64, - pub task_count: i64, - pub repository_count: i64, - pub version: i32, - pub created_at: DateTime<Utc>, -} - -/// Contract with all relations for detail view -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ContractWithRelations { - #[serde(flatten)] - pub contract: Contract, - pub repositories: Vec<ContractRepository>, - pub files: Vec<FileSummary>, - pub tasks: Vec<TaskSummary>, -} - -/// Response for contract list endpoint -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ContractListResponse { - pub contracts: Vec<ContractSummary>, - pub total: i64, -} - -/// Request payload for creating a new contract -#[derive(Debug, Clone, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct CreateContractRequest { - /// Name of the contract - pub name: String, - /// Optional description - pub description: Option<String>, - /// Contract type: "simple" (default), "specification", "execute", or a custom template name. - /// For built-in types: - /// - simple: Plan -> Execute workflow - /// - specification: Research -> Specify -> Plan -> Execute -> Review - /// - execute: Execute only - /// For custom templates, use the template name or provide template_id. - #[serde(default)] - pub contract_type: Option<String>, - /// UUID of a custom template to use. If provided, this takes precedence over contract_type. - /// The template's phase configuration will be copied to the contract. - #[serde(default)] - pub template_id: Option<Uuid>, - /// Initial phase to start in (defaults based on contract_type or template) - /// - simple: defaults to "plan" - /// - specification: defaults to "research" - #[serde(default)] - pub initial_phase: Option<String>, - /// Enable autonomous loop mode for tasks in this contract. - /// When enabled, tasks automatically restart with --continue if they exit - /// without a COMPLETION_GATE indicating ready: true. - #[serde(default)] - pub autonomous_loop: Option<bool>, - /// Enable phase guard mode for this contract. - /// When enabled, the supervisor will pause and ask the user to review and approve - /// phase outputs before progressing to the next phase. - #[serde(default)] - pub phase_guard: Option<bool>, - /// Enable local-only mode for this contract. - /// When enabled, automatic completion actions (branch, merge, pr) are skipped, - /// allowing users to manually handle code changes via patch files or other means. - #[serde(default)] - pub local_only: Option<bool>, - /// Enable auto-merge to target branch locally when local_only mode is enabled. - /// When both local_only and auto_merge_local are true, completed task changes will be - /// automatically merged to the master/main branch locally (without pushing or creating PRs). - #[serde(default)] - pub auto_merge_local: Option<bool>, -} - -/// Request payload for updating a contract -#[derive(Debug, Default, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct UpdateContractRequest { - pub name: Option<String>, - pub description: Option<String>, - pub phase: Option<String>, - pub status: Option<String>, - /// Supervisor task ID for contract orchestration - #[serde(skip_serializing_if = "Option::is_none")] - pub supervisor_task_id: Option<Uuid>, - /// Enable or disable autonomous loop mode for tasks in this contract. - #[serde(default)] - pub autonomous_loop: Option<bool>, - /// Enable or disable phase guard mode for this contract. - /// When enabled, the supervisor will pause and ask the user to review and approve - /// phase outputs before progressing to the next phase. - #[serde(default)] - pub phase_guard: Option<bool>, - /// Enable or disable local-only mode for this contract. - /// When enabled, automatic completion actions (branch, merge, pr) are skipped, - /// allowing users to manually handle code changes via patch files or other means. - #[serde(default)] - pub local_only: Option<bool>, - /// Enable or disable auto-merge to target branch locally when local_only mode is enabled. - /// When both local_only and auto_merge_local are true, completed task changes will be - /// automatically merged to the master/main branch locally (without pushing or creating PRs). - #[serde(default)] - pub auto_merge_local: Option<bool>, - /// Version for optimistic locking - pub version: Option<i32>, -} - -/// Request to add a remote repository to a contract -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct AddRemoteRepositoryRequest { - pub name: String, - pub repository_url: String, - #[serde(default)] - pub is_primary: bool, -} - -/// Request to add a local repository to a contract -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct AddLocalRepositoryRequest { - pub name: String, - pub local_path: String, - #[serde(default)] - pub is_primary: bool, -} - -/// Request to create a managed repository (daemon will create it) -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct CreateManagedRepositoryRequest { - pub name: String, - #[serde(default)] - pub is_primary: bool, -} - -/// Request to change contract phase -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ChangePhaseRequest { - pub phase: String, - /// If phase_guard is enabled, this must be true to confirm the transition. - /// If not provided or false, returns phase deliverables for review. - #[serde(default)] - pub confirmed: Option<bool>, - /// User feedback for changes (used when not confirming) - #[serde(skip_serializing_if = "Option::is_none")] - pub feedback: Option<String>, - /// Expected version for optimistic locking. If provided, the phase change - /// will only succeed if the current contract version matches. - #[serde(skip_serializing_if = "Option::is_none")] - pub expected_version: Option<i32>, -} - -/// Result of a phase change operation, supporting explicit conflict detection. -#[derive(Debug, Clone)] -pub enum PhaseChangeResult { - /// Phase change succeeded, returning the updated contract - Success(Contract), - /// Version conflict: the contract was modified concurrently - VersionConflict { - /// The version the client expected - expected: i32, - /// The actual current version in the database - actual: i32, - /// The current phase of the contract - current_phase: String, - }, - /// Validation failed (e.g., invalid phase transition) - ValidationFailed { - /// Human-readable reason for the failure - reason: String, - /// List of missing requirements for the phase transition - missing_requirements: Vec<String>, - }, - /// The caller is not authorized to change this contract's phase - Unauthorized, - /// The contract was not found - NotFound, -} - -/// Response for phase transition when phase_guard is enabled -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct PhaseTransitionRequest { - /// Current contract phase - pub current_phase: String, - /// Requested next phase - pub next_phase: String, - /// Summary of phase deliverables/outputs - pub deliverables_summary: String, - /// List of files created in this phase - pub phase_files: Vec<PhaseFileInfo>, - /// List of completed tasks in this phase - pub phase_tasks: Vec<PhaseTaskInfo>, - /// Whether user confirmation is required - pub requires_confirmation: bool, - /// Unique ID for tracking this transition request - pub transition_id: String, -} - -/// File info for phase transition review -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct PhaseFileInfo { - pub id: Uuid, - pub name: String, - pub description: Option<String>, -} - -/// Task info for phase transition review -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct PhaseTaskInfo { - pub id: Uuid, - pub name: String, - pub status: String, -} - -/// Contract event record from the database -#[derive(Debug, Clone, FromRow, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ContractEvent { - pub id: Uuid, - pub contract_id: Uuid, - pub event_type: String, - pub previous_phase: Option<String>, - pub new_phase: Option<String>, - #[sqlx(json)] - pub event_data: Option<serde_json::Value>, - pub created_at: DateTime<Utc>, -} - -/// Response for contract events list endpoint -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ContractEventListResponse { - pub events: Vec<ContractEvent>, - pub total: i64, -} // ============================================================================ // Task Checkpoints (for git checkpoint tracking) @@ -2173,11 +1280,9 @@ pub struct ConversationSnapshot { pub struct HistoryEvent { pub id: Uuid, pub owner_id: Uuid, - pub contract_id: Option<Uuid>, pub task_id: Option<Uuid>, pub event_type: String, pub event_subtype: Option<String>, - pub phase: Option<String>, #[sqlx(json)] pub event_data: serde_json::Value, pub created_at: DateTime<Utc>, @@ -2221,7 +1326,6 @@ pub struct ToolCallInfo { #[derive(Debug, Deserialize, ToSchema, Default)] #[serde(rename_all = "camelCase")] pub struct HistoryQueryFilters { - pub phase: Option<String>, pub event_types: Option<Vec<String>>, #[serde(default, deserialize_with = "flexible_datetime::deserialize")] pub from: Option<DateTime<Utc>>, @@ -2766,11 +1870,6 @@ pub struct DirectiveStep { /// Status: pending, ready, running, completed, failed, skipped pub status: String, pub task_id: Option<Uuid>, - /// Optional contract ID for contract-backed execution. - pub contract_id: Option<Uuid>, - /// Optional contract type (e.g. "simple", "specification", "execute"). - /// When set, the orchestrator creates a contract instead of a standalone task. - pub contract_type: Option<String>, pub order_index: i32, pub generation: i32, pub started_at: Option<DateTime<Utc>>, |
