summaryrefslogtreecommitdiff
path: root/makima/src/db
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/db')
-rw-r--r--makima/src/db/models.rs15
-rw-r--r--makima/src/db/repository.rs32
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(&copy_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(&copy_files_json)
.bind(&req.branched_from_task_id)
.bind(&req.conversation_history)
+ .bind(&depends_on_json)
.fetch_one(pool)
.await
}