diff options
| author | soryu <soryu@soryu.co> | 2026-02-06 20:06:30 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-06 20:15:27 +0000 |
| commit | 1b692b8cde4a888c8a35af69231f181b57bf5619 (patch) | |
| tree | 74ce25ce6ee5fb4536b53404e1a0ae923e85c30d /makima/src/server/handlers/directives.rs | |
| parent | 139be135c2086d725e4f040e744bb25acd436549 (diff) | |
| download | soryu-1b692b8cde4a888c8a35af69231f181b57bf5619.tar.gz soryu-1b692b8cde4a888c8a35af69231f181b57bf5619.zip | |
Fix: Cleanup old chain code
Diffstat (limited to 'makima/src/server/handlers/directives.rs')
| -rw-r--r-- | makima/src/server/handlers/directives.rs | 485 |
1 files changed, 483 insertions, 2 deletions
diff --git a/makima/src/server/handlers/directives.rs b/makima/src/server/handlers/directives.rs index 4a78ab5..52422cd 100644 --- a/makima/src/server/handlers/directives.rs +++ b/makima/src/server/handlers/directives.rs @@ -19,8 +19,9 @@ use std::time::Duration; use uuid::Uuid; use crate::db::models::{ - AddStepRequest, CreateDirectiveRequest, CreateVerifierRequest, UpdateDirectiveRequest, - UpdateStepRequest, UpdateVerifierRequest, + AddStepRequest, CreateDirectiveRequest, CreateVerifierRequest, ReworkStepRequest, + UpdateCriteriaRequest, UpdateDirectiveRequest, UpdateRequirementsRequest, UpdateStepRequest, + UpdateVerifierRequest, }; use crate::db::repository; use crate::server::auth::Authenticated; @@ -1567,3 +1568,483 @@ pub async fn deny_request( } } } + +// ============================================================================= +// Step Evaluation & Rework +// ============================================================================= + +/// Force re-evaluation of a step +/// POST /api/v1/directives/:id/steps/:step_id/evaluate +pub async fn evaluate_step( + State(state): State<SharedState>, + Authenticated(auth): Authenticated, + Path((id, step_id)): Path<(Uuid, Uuid)>, +) -> impl IntoResponse { + let Some(ref pool) = state.db_pool else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), + ) + .into_response(); + }; + + // Verify ownership + match repository::get_directive_for_owner(pool, id, auth.owner_id).await { + Ok(Some(_)) => {} + Ok(None) => { + return ( + StatusCode::NOT_FOUND, + Json(ApiError::new("NOT_FOUND", "Directive not found")), + ) + .into_response() + } + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response() + } + } + + // Set step to evaluating status + match repository::update_step_status(pool, step_id, "evaluating").await { + Ok(_) => {} + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response() + } + } + + // Trigger evaluation via engine + let engine = crate::orchestration::DirectiveEngine::new(pool.clone()); + match engine.on_contract_completed(step_id).await { + Ok(()) => { + // Return updated step + match repository::get_chain_step(pool, step_id).await { + Ok(Some(step)) => Json(step).into_response(), + Ok(None) => ( + StatusCode::NOT_FOUND, + Json(ApiError::new("NOT_FOUND", "Step not found")), + ) + .into_response(), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response(), + } + } + Err(e) => { + tracing::error!("Failed to evaluate step: {}", e); + ( + StatusCode::BAD_REQUEST, + Json(ApiError::new("EVALUATE_FAILED", &e.to_string())), + ) + .into_response() + } + } +} + +/// Trigger manual rework for a step +/// POST /api/v1/directives/:id/steps/:step_id/rework +pub async fn rework_step( + State(state): State<SharedState>, + Authenticated(auth): Authenticated, + Path((id, step_id)): Path<(Uuid, Uuid)>, + Json(req): Json<ReworkStepRequest>, +) -> impl IntoResponse { + let Some(ref pool) = state.db_pool else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), + ) + .into_response(); + }; + + // Verify ownership + match repository::get_directive_for_owner(pool, id, auth.owner_id).await { + Ok(Some(_)) => {} + Ok(None) => { + return ( + StatusCode::NOT_FOUND, + Json(ApiError::new("NOT_FOUND", "Directive not found")), + ) + .into_response() + } + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response() + } + } + + // Set step to rework status and increment rework count + match repository::update_step_status(pool, step_id, "rework").await { + Ok(_) => {} + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response() + } + } + + let _ = repository::increment_step_rework_count(pool, step_id).await; + + // Emit rework event + let _ = repository::emit_directive_event( + pool, + id, + None, + Some(step_id), + "step_rework", + "info", + Some(serde_json::json!({ + "step_id": step_id, + "instructions": req.instructions, + "initiated_by": "user", + })), + "user", + Some(auth.owner_id), + ) + .await; + + // Return updated step + match repository::get_chain_step(pool, step_id).await { + Ok(Some(step)) => Json(step).into_response(), + Ok(None) => ( + StatusCode::NOT_FOUND, + Json(ApiError::new("NOT_FOUND", "Step not found")), + ) + .into_response(), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response(), + } +} + +// ============================================================================= +// Auto-detect Verifiers +// ============================================================================= + +/// Auto-detect verifiers for a directive based on repository content +/// POST /api/v1/directives/:id/verifiers/auto-detect +pub async fn auto_detect_verifiers( + State(state): State<SharedState>, + Authenticated(auth): Authenticated, + Path(id): Path<Uuid>, +) -> impl IntoResponse { + let Some(ref pool) = state.db_pool else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), + ) + .into_response(); + }; + + // Get directive with ownership check + let directive = match repository::get_directive_for_owner(pool, id, auth.owner_id).await { + Ok(Some(d)) => d, + Ok(None) => { + return ( + StatusCode::NOT_FOUND, + Json(ApiError::new("NOT_FOUND", "Directive not found")), + ) + .into_response() + } + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response() + } + }; + + // Get repository path + let repo_path = directive + .local_path + .as_ref() + .map(std::path::PathBuf::from) + .unwrap_or_else(|| std::path::PathBuf::from(".")); + + // Auto-detect verifiers + let detected = crate::orchestration::auto_detect_verifiers(&repo_path).await; + + // Save detected verifiers to the database + let mut created = Vec::new(); + for verifier in &detected { + let info = verifier.info(); + match repository::create_directive_verifier( + pool, + id, + &info.name, + &info.verifier_type, + Some(&info.command), + info.working_directory.as_deref(), + true, // auto_detect + info.detect_files.clone(), + info.weight, + info.required, + ) + .await + { + Ok(v) => created.push(v), + Err(e) => { + tracing::warn!("Failed to create detected verifier '{}': {}", info.name, e); + } + } + } + + Json(serde_json::json!({ + "detected": created.len(), + "verifiers": created, + })) + .into_response() +} + +// ============================================================================= +// Requirements & Criteria +// ============================================================================= + +/// Update directive requirements +/// PUT /api/v1/directives/:id/requirements +pub async fn update_requirements( + State(state): State<SharedState>, + Authenticated(auth): Authenticated, + Path(id): Path<Uuid>, + Json(req): Json<UpdateRequirementsRequest>, +) -> impl IntoResponse { + let Some(ref pool) = state.db_pool else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), + ) + .into_response(); + }; + + // Get directive with ownership check to get current version + let directive = match repository::get_directive_for_owner(pool, id, auth.owner_id).await { + Ok(Some(d)) => d, + Ok(None) => { + return ( + StatusCode::NOT_FOUND, + Json(ApiError::new("NOT_FOUND", "Directive not found")), + ) + .into_response() + } + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response() + } + }; + + // Build update request with just requirements + let update = UpdateDirectiveRequest { + title: None, + goal: None, + requirements: Some(serde_json::to_value(&req.requirements).unwrap_or_default()), + acceptance_criteria: None, + constraints: None, + external_dependencies: None, + autonomy_level: None, + confidence_threshold_green: None, + confidence_threshold_yellow: None, + max_total_cost_usd: None, + max_wall_time_minutes: None, + max_rework_cycles: None, + max_chain_regenerations: None, + version: directive.version, + }; + + match repository::update_directive_for_owner(pool, id, auth.owner_id, update).await { + Ok(directive) => Json(directive).into_response(), + Err(repository::RepositoryError::VersionConflict { expected, actual }) => ( + StatusCode::CONFLICT, + Json(ApiError::new( + "VERSION_CONFLICT", + &format!("Version conflict: expected {}, got {}", expected, actual), + )), + ) + .into_response(), + Err(e) => { + tracing::error!("Failed to update requirements: {}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response() + } + } +} + +/// Update directive acceptance criteria +/// PUT /api/v1/directives/:id/criteria +pub async fn update_criteria( + State(state): State<SharedState>, + Authenticated(auth): Authenticated, + Path(id): Path<Uuid>, + Json(req): Json<UpdateCriteriaRequest>, +) -> impl IntoResponse { + let Some(ref pool) = state.db_pool else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), + ) + .into_response(); + }; + + // Get directive with ownership check to get current version + let directive = match repository::get_directive_for_owner(pool, id, auth.owner_id).await { + Ok(Some(d)) => d, + Ok(None) => { + return ( + StatusCode::NOT_FOUND, + Json(ApiError::new("NOT_FOUND", "Directive not found")), + ) + .into_response() + } + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response() + } + }; + + // Build update request with just acceptance criteria + let update = UpdateDirectiveRequest { + title: None, + goal: None, + requirements: None, + acceptance_criteria: Some( + serde_json::to_value(&req.acceptance_criteria).unwrap_or_default(), + ), + constraints: None, + external_dependencies: None, + autonomy_level: None, + confidence_threshold_green: None, + confidence_threshold_yellow: None, + max_total_cost_usd: None, + max_wall_time_minutes: None, + max_rework_cycles: None, + max_chain_regenerations: None, + version: directive.version, + }; + + match repository::update_directive_for_owner(pool, id, auth.owner_id, update).await { + Ok(directive) => Json(directive).into_response(), + Err(repository::RepositoryError::VersionConflict { expected, actual }) => ( + StatusCode::CONFLICT, + Json(ApiError::new( + "VERSION_CONFLICT", + &format!("Version conflict: expected {}, got {}", expected, actual), + )), + ) + .into_response(), + Err(e) => { + tracing::error!("Failed to update criteria: {}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response() + } + } +} + +// ============================================================================= +// Spec Generation +// ============================================================================= + +/// Generate a specification from the directive's goal using LLM +/// POST /api/v1/directives/:id/generate-spec +pub async fn generate_spec( + State(state): State<SharedState>, + Authenticated(auth): Authenticated, + Path(id): Path<Uuid>, +) -> impl IntoResponse { + let Some(ref pool) = state.db_pool else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), + ) + .into_response(); + }; + + // Get directive with ownership check + let directive = match repository::get_directive_for_owner(pool, id, auth.owner_id).await { + Ok(Some(d)) => d, + Ok(None) => { + return ( + StatusCode::NOT_FOUND, + Json(ApiError::new("NOT_FOUND", "Directive not found")), + ) + .into_response() + } + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response() + } + }; + + // Use the planner to generate a spec from the goal + let planner = crate::orchestration::ChainPlanner::new(pool.clone()); + match planner.generate_spec(&directive).await { + Ok(spec) => { + // Update the directive with the generated spec + let update = UpdateDirectiveRequest { + title: spec.title, + goal: None, + requirements: Some(spec.requirements), + acceptance_criteria: Some(spec.acceptance_criteria), + constraints: spec.constraints, + external_dependencies: None, + autonomy_level: None, + confidence_threshold_green: None, + confidence_threshold_yellow: None, + max_total_cost_usd: None, + max_wall_time_minutes: None, + max_rework_cycles: None, + max_chain_regenerations: None, + version: directive.version, + }; + + match repository::update_directive_for_owner(pool, id, auth.owner_id, update).await { + Ok(updated) => Json(updated).into_response(), + Err(e) => { + tracing::error!("Failed to save generated spec: {}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiError::new("DB_ERROR", &e.to_string())), + ) + .into_response() + } + } + } + Err(e) => { + tracing::error!("Failed to generate spec: {}", e); + ( + StatusCode::BAD_REQUEST, + Json(ApiError::new("SPEC_GENERATION_FAILED", &e.to_string())), + ) + .into_response() + } + } +} |
