diff options
| author | soryu <soryu@soryu.co> | 2026-02-16 17:59:38 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-16 17:59:38 +0000 |
| commit | b3de779d87450033f1e0361144c621a1d5f1dbf8 (patch) | |
| tree | 7cb84c2f953bf86f1dd3ec8ff305d70810ac55de /makima/src/orchestration | |
| parent | 7d2079d7c13804766405af8044574bfc93a86897 (diff) | |
| download | soryu-b3de779d87450033f1e0361144c621a1d5f1dbf8.tar.gz soryu-b3de779d87450033f1e0361144c621a1d5f1dbf8.zip | |
Fix contracts page overflow, remove contract link from orders, add directive name (#65)
* feat: soryu-co/soryu - makima: Add frontend pick-up-orders button and API integration
* WIP: heartbeat checkpoint
* feat: soryu-co/soryu - makima: Remove contract link from orders and add directive name to order metadata (frontend)
* fix: contracts page overflow - use contained scrolling layout
Changed the contracts page to use contained scrolling matching the
orders/directives pages, preventing the page from growing beyond
viewport height.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve completion_task_id FK violation and duplicate button
The completion_task_id column has an FK to tasks(id), but
claim_directive_for_completion was being called with a placeholder UUID
that did not exist in the tasks table, causing FK constraint violations.
Fix: Create the task FIRST via create_task_for_owner, then use the real
task.id when calling claim_directive_for_completion. Applied in all three
locations: phase_completion Part 1 (idle directives), Part 3 (verification
tasks), and trigger_completion_task (manual PR creation).
Also removes a duplicate "Pick Up Orders" button in DirectiveDetail.tsx.
* fix: restore Order type changes lost during rebase conflict resolution
Re-apply changes from the orders-refactor commit that were dropped when
resolving rebase conflicts with --ours:
- Replace contractId with directiveName in Order interface
- Make directiveId required in CreateOrderRequest
- Remove contractId from UpdateOrderRequest
- Change listOrders parameter from contractId to search
- Remove linkOrderToContract function
- Simplify convertOrderToStep to single argument
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'makima/src/orchestration')
| -rw-r--r-- | makima/src/orchestration/directive.rs | 94 |
1 files changed, 51 insertions, 43 deletions
diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs index 736715d..020c2e4 100644 --- a/makima/src/orchestration/directive.rs +++ b/makima/src/orchestration/directive.rs @@ -580,20 +580,11 @@ impl DirectiveOrchestrator { for directive in directives { if let Err(e) = async { - // Atomically claim this directive for completion using a placeholder. - // This prevents a concurrent tick from also spawning a completion task. - let placeholder_id = Uuid::new_v4(); - let claimed = repository::claim_directive_for_completion( - &self.pool, - directive.id, - placeholder_id, - ) - .await?; - - if !claimed { + // Skip if already claimed (completion_task_id is set) + if directive.completion_task_id.is_some() { tracing::debug!( directive_id = %directive.id, - "Directive already claimed for completion — skipping" + "Directive already has a completion task — skipping" ); return Ok::<(), anyhow::Error>(()); } @@ -606,7 +597,6 @@ impl DirectiveOrchestrator { let step_tasks = repository::get_completed_step_tasks(&self.pool, directive.id).await?; if step_tasks.is_empty() { - let _ = repository::clear_completion_task(&self.pool, directive.id).await; return Ok(()); } @@ -640,6 +630,7 @@ impl DirectiveOrchestrator { base_branch, ); + // Create the task FIRST so we have a real task ID for the FK match self .spawn_completion_task( directive.id, @@ -652,6 +643,22 @@ impl DirectiveOrchestrator { .await { Ok(task_id) => { + // Atomically claim with the REAL task ID (satisfies FK constraint) + let claimed = repository::claim_directive_for_completion( + &self.pool, + directive.id, + task_id, + ) + .await?; + + if !claimed { + tracing::debug!( + directive_id = %directive.id, + "Directive already claimed for completion — task will be orphaned" + ); + return Ok(()); + } + let update = crate::db::models::UpdateDirectiveRequest { pr_branch: Some(directive_branch.clone()), ..Default::default() @@ -663,15 +670,13 @@ impl DirectiveOrchestrator { update, ) .await; - repository::assign_completion_task(&self.pool, directive.id, task_id).await?; } Err(e) => { tracing::warn!( directive_id = %directive.id, error = %e, - "Failed to spawn completion task — releasing claim" + "Failed to spawn completion task" ); - let _ = repository::clear_completion_task(&self.pool, directive.id).await; } } Ok(()) @@ -773,15 +778,8 @@ impl DirectiveOrchestrator { for directive in verify_directives { if let Err(e) = async { - let placeholder_id = Uuid::new_v4(); - let claimed = repository::claim_directive_for_completion( - &self.pool, - directive.id, - placeholder_id, - ) - .await?; - - if !claimed { + // Skip if already claimed + if directive.completion_task_id.is_some() { return Ok::<(), anyhow::Error>(()); } @@ -795,6 +793,7 @@ impl DirectiveOrchestrator { let base_branch = directive.base_branch.as_deref().unwrap_or("main"); let prompt = build_verification_prompt(&directive, pr_branch, base_branch); + // Create the task FIRST so we have a real task ID for the FK match self .spawn_completion_task( directive.id, @@ -807,15 +806,27 @@ impl DirectiveOrchestrator { .await { Ok(task_id) => { - repository::assign_completion_task(&self.pool, directive.id, task_id).await?; + // Atomically claim with the REAL task ID (satisfies FK constraint) + let claimed = repository::claim_directive_for_completion( + &self.pool, + directive.id, + task_id, + ) + .await?; + + if !claimed { + tracing::debug!( + directive_id = %directive.id, + "Directive already claimed for verification — task will be orphaned" + ); + } } Err(e) => { tracing::warn!( directive_id = %directive.id, error = %e, - "Failed to spawn verification task — releasing claim" + "Failed to spawn verification task" ); - let _ = repository::clear_completion_task(&self.pool, directive.id).await; } } Ok(()) @@ -906,9 +917,9 @@ impl DirectiveOrchestrator { /// This is the public entry point used by both the orchestrator tick and the /// manual "create PR" API handler. It encapsulates the full flow: /// 1. Validate the directive has completed step tasks -/// 2. Claim the directive for completion (returns error if already claimed) -/// 3. Build branch names and prompt -/// 4. Spawn the completion task and assign it +/// 2. Create the completion task (so we have a real task ID) +/// 3. Atomically claim the directive for completion with the real task ID +/// 4. Dispatch the task to a daemon /// /// Returns the created task ID on success. pub async fn trigger_completion_task( @@ -931,14 +942,6 @@ pub async fn trigger_completion_task( anyhow::bail!("No completed steps with tasks found"); } - // Claim for completion - let placeholder_id = Uuid::new_v4(); - let claimed = - repository::claim_directive_for_completion(pool, directive_id, placeholder_id).await?; - if !claimed { - anyhow::bail!("Directive already claimed for completion"); - } - let base_branch = directive.base_branch.as_deref().unwrap_or("main"); let directive_branch = format!( @@ -970,7 +973,7 @@ pub async fn trigger_completion_task( format!("PR: {}", directive.title) }; - // Create the completion task + // Create the completion task FIRST so we have a real task ID for the FK let req = CreateTaskRequest { contract_id: None, name: task_name, @@ -997,6 +1000,14 @@ pub async fn trigger_completion_task( let task = repository::create_task_for_owner(pool, owner_id, req).await?; + // Atomically claim the directive with the REAL task ID (satisfies FK constraint). + // This prevents concurrent ticks from also spawning a completion task. + let claimed = + repository::claim_directive_for_completion(pool, directive_id, task.id).await?; + if !claimed { + anyhow::bail!("Directive already claimed for completion"); + } + // Update pr_branch on the directive let update = crate::db::models::UpdateDirectiveRequest { pr_branch: Some(directive_branch), @@ -1004,9 +1015,6 @@ pub async fn trigger_completion_task( }; let _ = repository::update_directive_for_owner(pool, owner_id, directive_id, update).await; - // Assign the real task as the completion task - repository::assign_completion_task(pool, directive_id, task.id).await?; - // Try to dispatch to a daemon if let Some(daemon_id) = state.find_alternative_daemon(owner_id, &[]) { let update_req = crate::db::models::UpdateTaskRequest { |
