summaryrefslogtreecommitdiff
path: root/makima/src/server/handlers/directives.rs
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-06 20:06:30 +0000
committersoryu <soryu@soryu.co>2026-02-06 20:15:27 +0000
commit1b692b8cde4a888c8a35af69231f181b57bf5619 (patch)
tree74ce25ce6ee5fb4536b53404e1a0ae923e85c30d /makima/src/server/handlers/directives.rs
parent139be135c2086d725e4f040e744bb25acd436549 (diff)
downloadsoryu-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.rs485
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()
+ }
+ }
+}