summaryrefslogtreecommitdiff
path: root/makima/src/db
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-24 04:31:20 +0000
committersoryu <soryu@soryu.co>2026-01-24 04:31:20 +0000
commit0cf6f6a4c5c75c736e6fe3b1c726ef80c0a6c802 (patch)
tree8f08fa5421e13381c5955f52a5197379feb44c58 /makima/src/db
parentf6f0790217d4098ffb6d2b3df08b0cf83ff61727 (diff)
downloadsoryu-0cf6f6a4c5c75c736e6fe3b1c726ef80c0a6c802.tar.gz
soryu-0cf6f6a4c5c75c736e6fe3b1c726ef80c0a6c802.zip
feat: implement dependency-ordered task execution
Add dependency tracking and validation for tasks to enforce execution order (schema changes → backend → UI) as specified in Section 1.3 of ralph-features-spec.md. Changes: - Add depends_on field to Task model (Vec<Uuid>) for explicit dependencies - Create database migration for depends_on column with GIN index - Add dependency_analysis.rs module with: - can_start_task() for checking if all dependencies are complete - Auto-detection of dependency patterns from file paths - Detection of schema/types/backend/UI categories - Warnings for potential dependency violations - Add DependencyOrderingConfig to daemon config with: - enabled: Enable/disable dependency checking - auto_detect: Auto-detect dependencies from file patterns - warn_on_violation: Warn on detected violations - Integrate dependency checks into task manager scheduling - Add depends_on to DaemonCommand::SpawnTask protocol The daemon performs dependency validation as a sanity check but defers to the server for authoritative scheduling decisions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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
}