diff options
Diffstat (limited to 'makima/src/db')
| -rw-r--r-- | makima/src/db/models.rs | 15 | ||||
| -rw-r--r-- | makima/src/db/repository.rs | 32 |
2 files changed, 41 insertions, 6 deletions
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs index 58f4da1..f3977e0 100644 --- a/makima/src/db/models.rs +++ b/makima/src/db/models.rs @@ -531,6 +531,13 @@ pub struct Task { /// Standalone completed tasks can be dismissed by the user. #[serde(default)] pub hidden: bool, + + // Dependency tracking for dependency-ordered execution + /// Task IDs that must complete before this task can start. + /// Used for enforcing execution order: schema changes → backend → UI. + #[serde(default, skip_serializing_if = "Option::is_none")] + #[sqlx(json)] + pub depends_on: Option<Vec<Uuid>>, } impl Task { @@ -611,8 +618,8 @@ pub struct TaskListResponse { } /// Request payload for creating a new task -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Default, Deserialize, ToSchema)] +#[serde(rename_all = "camelCase", default)] pub struct CreateTaskRequest { /// Contract this task belongs to (optional for branched/anonymous tasks) pub contract_id: Option<Uuid>, @@ -653,6 +660,10 @@ 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 IDs that must complete before this task can start. + /// Used for enforcing execution order: schema changes → backend → UI. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub depends_on: Option<Vec<Uuid>>, } /// Request payload for updating a task diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index da44899..d9ba97d 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -684,6 +684,7 @@ pub async fn create_task(pool: &PgPool, req: CreateTaskRequest) -> Result<Task, }; let copy_files_json = req.copy_files.as_ref().map(|f| serde_json::to_value(f).unwrap_or_default()); + let depends_on_json = req.depends_on.as_ref().map(|d| serde_json::to_value(d).unwrap_or_default()); sqlx::query_as::<_, Task>( r#" @@ -691,9 +692,9 @@ pub async fn create_task(pool: &PgPool, req: CreateTaskRequest) -> Result<Task, contract_id, parent_task_id, depth, name, description, plan, priority, is_supervisor, repository_url, base_branch, target_branch, merge_mode, target_repo_path, completion_action, continue_from_task_id, copy_files, - branched_from_task_id, conversation_state + branched_from_task_id, conversation_state, depends_on ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) RETURNING * "#, ) @@ -715,6 +716,7 @@ pub async fn create_task(pool: &PgPool, req: CreateTaskRequest) -> Result<Task, .bind(©_files_json) .bind(&req.branched_from_task_id) .bind(&req.conversation_history) + .bind(&depends_on_json) .fetch_one(pool) .await } @@ -823,6 +825,26 @@ pub async fn get_pending_tasks_for_contract( .await } +/// Get all contracts that have pending tasks awaiting retry. +/// Returns tuples of (contract_id, owner_id) for contracts with retryable tasks. +pub async fn get_all_pending_task_contracts( + pool: &PgPool, +) -> Result<Vec<(Uuid, Uuid)>, sqlx::Error> { + sqlx::query_as::<_, (Uuid, Uuid)>( + r#" + SELECT DISTINCT contract_id, owner_id + FROM tasks + WHERE contract_id IS NOT NULL + AND status = 'pending' + AND is_supervisor = false + AND retry_count < max_retries + ORDER BY owner_id, contract_id + "#, + ) + .fetch_all(pool) + .await +} + /// Mark a task as pending for retry after daemon failure. /// Increments retry count and adds the failed daemon to exclusion list. pub async fn mark_task_for_retry( @@ -1075,6 +1097,7 @@ pub async fn create_task_for_owner( }; let copy_files_json = req.copy_files.as_ref().map(|f| serde_json::to_value(f).unwrap_or_default()); + let depends_on_json = req.depends_on.as_ref().map(|d| serde_json::to_value(d).unwrap_or_default()); sqlx::query_as::<_, Task>( r#" @@ -1082,9 +1105,9 @@ pub async fn create_task_for_owner( owner_id, contract_id, parent_task_id, depth, name, description, plan, priority, is_supervisor, repository_url, base_branch, target_branch, merge_mode, target_repo_path, completion_action, continue_from_task_id, copy_files, - branched_from_task_id, conversation_state + branched_from_task_id, conversation_state, depends_on ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) RETURNING * "#, ) @@ -1107,6 +1130,7 @@ pub async fn create_task_for_owner( .bind(©_files_json) .bind(&req.branched_from_task_id) .bind(&req.conversation_history) + .bind(&depends_on_json) .fetch_one(pool) .await } |
