diff options
Diffstat (limited to 'makima/src/db')
| -rw-r--r-- | makima/src/db/models.rs | 666 | ||||
| -rw-r--r-- | makima/src/db/repository.rs | 1169 |
2 files changed, 0 insertions, 1835 deletions
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs index f951751..3b10cb5 100644 --- a/makima/src/db/models.rs +++ b/makima/src/db/models.rs @@ -2596,672 +2596,6 @@ pub struct HeartbeatHistoryQuery { } // ============================================================================= -// Directives (Goal-driven orchestration with chains of steps) -// ============================================================================= - -/// Directive status -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "snake_case")] -pub enum DirectiveStatus { - Draft, - Planning, - Active, - Paused, - Completed, - Archived, - Failed, -} - -impl Default for DirectiveStatus { - fn default() -> Self { - DirectiveStatus::Draft - } -} - -impl std::fmt::Display for DirectiveStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - DirectiveStatus::Draft => write!(f, "draft"), - DirectiveStatus::Planning => write!(f, "planning"), - DirectiveStatus::Active => write!(f, "active"), - DirectiveStatus::Paused => write!(f, "paused"), - DirectiveStatus::Completed => write!(f, "completed"), - DirectiveStatus::Archived => write!(f, "archived"), - DirectiveStatus::Failed => write!(f, "failed"), - } - } -} - -impl std::str::FromStr for DirectiveStatus { - type Err = String; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s.to_lowercase().as_str() { - "draft" => Ok(DirectiveStatus::Draft), - "planning" => Ok(DirectiveStatus::Planning), - "active" => Ok(DirectiveStatus::Active), - "paused" => Ok(DirectiveStatus::Paused), - "completed" => Ok(DirectiveStatus::Completed), - "archived" => Ok(DirectiveStatus::Archived), - "failed" => Ok(DirectiveStatus::Failed), - _ => Err(format!("Invalid directive status: {}", s)), - } - } -} - -/// Directive - the top-level goal-driven orchestration entity -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct Directive { - pub id: Uuid, - pub owner_id: Uuid, - pub title: String, - pub goal: String, - /// Structured requirements: [{ id, title, description, priority, category }] - #[sqlx(json)] - pub requirements: serde_json::Value, - /// Acceptance criteria: [{ id, requirementIds, description, testable, verificationMethod }] - #[sqlx(json)] - pub acceptance_criteria: serde_json::Value, - /// Constraints: [{ id, type, description, impact }] - #[sqlx(json)] - pub constraints: serde_json::Value, - /// External dependencies: [{ id, name, type, status, requiredBy }] - #[sqlx(json)] - pub external_dependencies: serde_json::Value, - pub status: String, - pub autonomy_level: String, - pub confidence_threshold_green: f64, - pub confidence_threshold_yellow: f64, - pub max_total_cost_usd: Option<f64>, - pub max_wall_time_minutes: Option<i32>, - pub max_rework_cycles: Option<i32>, - pub max_chain_regenerations: Option<i32>, - pub repository_url: Option<String>, - pub local_path: Option<String>, - pub base_branch: Option<String>, - pub orchestrator_contract_id: Option<Uuid>, - pub current_chain_id: Option<Uuid>, - pub chain_generation_count: i32, - pub total_cost_usd: f64, - pub started_at: Option<DateTime<Utc>>, - pub completed_at: Option<DateTime<Utc>>, - pub version: i32, - pub created_at: DateTime<Utc>, - pub updated_at: DateTime<Utc>, -} - -impl Directive { - /// Parse status string to DirectiveStatus enum - pub fn status_enum(&self) -> Result<DirectiveStatus, String> { - self.status.parse() - } -} - -/// Directive chain - a generated execution plan (DAG) for a directive -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveChain { - pub id: Uuid, - pub directive_id: Uuid, - pub generation: i32, - pub name: String, - pub description: Option<String>, - pub rationale: Option<String>, - pub planning_model: Option<String>, - pub status: String, - pub total_steps: i32, - pub completed_steps: i32, - pub failed_steps: i32, - pub current_confidence: Option<f64>, - pub started_at: Option<DateTime<Utc>>, - pub completed_at: Option<DateTime<Utc>>, - pub version: i32, - pub created_at: DateTime<Utc>, - pub updated_at: DateTime<Utc>, -} - -/// Chain step status -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "snake_case")] -pub enum StepStatus { - Pending, - Ready, - Running, - Evaluating, - Passed, - Failed, - Rework, - Skipped, - Blocked, -} - -impl Default for StepStatus { - fn default() -> Self { - StepStatus::Pending - } -} - -impl std::fmt::Display for StepStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - StepStatus::Pending => write!(f, "pending"), - StepStatus::Ready => write!(f, "ready"), - StepStatus::Running => write!(f, "running"), - StepStatus::Evaluating => write!(f, "evaluating"), - StepStatus::Passed => write!(f, "passed"), - StepStatus::Failed => write!(f, "failed"), - StepStatus::Rework => write!(f, "rework"), - StepStatus::Skipped => write!(f, "skipped"), - StepStatus::Blocked => write!(f, "blocked"), - } - } -} - -impl std::str::FromStr for StepStatus { - type Err = String; - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s.to_lowercase().as_str() { - "pending" => Ok(StepStatus::Pending), - "ready" => Ok(StepStatus::Ready), - "running" => Ok(StepStatus::Running), - "evaluating" => Ok(StepStatus::Evaluating), - "passed" => Ok(StepStatus::Passed), - "failed" => Ok(StepStatus::Failed), - "rework" => Ok(StepStatus::Rework), - "skipped" => Ok(StepStatus::Skipped), - "blocked" => Ok(StepStatus::Blocked), - _ => Err(format!("Invalid step status: {}", s)), - } - } -} - -/// Chain step - a node in the DAG execution plan -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ChainStep { - pub id: Uuid, - pub chain_id: Uuid, - pub name: String, - pub description: Option<String>, - pub step_type: String, - pub contract_type: String, - pub initial_phase: Option<String>, - pub task_plan: Option<String>, - #[sqlx(default)] - pub phases: Vec<String>, - #[sqlx(default)] - pub depends_on: Vec<Uuid>, - pub parallel_group: Option<String>, - #[sqlx(default)] - pub requirement_ids: Vec<String>, - #[sqlx(default)] - pub acceptance_criteria_ids: Vec<String>, - #[sqlx(json)] - #[serde(default)] - pub verifier_config: serde_json::Value, - pub status: String, - pub contract_id: Option<Uuid>, - pub supervisor_task_id: Option<Uuid>, - pub confidence_score: Option<f64>, - pub confidence_level: Option<String>, - pub evaluation_count: i32, - pub rework_count: i32, - pub last_evaluation_id: Option<Uuid>, - pub editor_x: Option<f64>, - pub editor_y: Option<f64>, - pub order_index: i32, - pub started_at: Option<DateTime<Utc>>, - pub completed_at: Option<DateTime<Utc>>, - pub created_at: DateTime<Utc>, -} - -impl ChainStep { - /// Parse status string to StepStatus enum - pub fn status_enum(&self) -> Result<StepStatus, String> { - self.status.parse() - } -} - -/// Confidence level (traffic light) -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "snake_case")] -pub enum ConfidenceLevel { - Green, - Yellow, - Red, -} - -impl ConfidenceLevel { - pub fn from_score(score: f64, green_threshold: f64, yellow_threshold: f64) -> Self { - if score >= green_threshold { - Self::Green - } else if score >= yellow_threshold { - Self::Yellow - } else { - Self::Red - } - } -} - -impl std::fmt::Display for ConfidenceLevel { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ConfidenceLevel::Green => write!(f, "green"), - ConfidenceLevel::Yellow => write!(f, "yellow"), - ConfidenceLevel::Red => write!(f, "red"), - } - } -} - -/// Directive evaluation - composite programmatic + LLM evaluation result -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveEvaluation { - pub id: Uuid, - pub directive_id: Uuid, - pub chain_id: Option<Uuid>, - pub step_id: Option<Uuid>, - pub contract_id: Option<Uuid>, - pub evaluation_type: String, - pub evaluation_number: i32, - pub evaluator: Option<String>, - pub passed: bool, - pub overall_score: Option<f64>, - pub confidence_level: Option<String>, - #[sqlx(json)] - #[serde(default)] - pub programmatic_results: serde_json::Value, - #[sqlx(json)] - #[serde(default)] - pub llm_results: serde_json::Value, - #[sqlx(json)] - #[serde(default)] - pub criteria_results: serde_json::Value, - pub summary_feedback: String, - pub rework_instructions: Option<String>, - #[sqlx(json)] - pub directive_snapshot: Option<serde_json::Value>, - #[sqlx(json)] - pub deliverables_snapshot: Option<serde_json::Value>, - pub started_at: DateTime<Utc>, - pub completed_at: Option<DateTime<Utc>>, - pub created_at: DateTime<Utc>, -} - -/// Directive event - audit stream entry -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveEvent { - pub id: Uuid, - pub directive_id: Uuid, - pub chain_id: Option<Uuid>, - pub step_id: Option<Uuid>, - pub event_type: String, - pub severity: String, - #[sqlx(json)] - pub event_data: Option<serde_json::Value>, - pub actor_type: String, - pub actor_id: Option<Uuid>, - pub created_at: DateTime<Utc>, -} - -/// Directive verifier - pluggable verification configuration -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveVerifier { - pub id: Uuid, - pub directive_id: Uuid, - pub name: String, - pub verifier_type: String, - pub command: Option<String>, - pub working_directory: Option<String>, - pub timeout_seconds: Option<i32>, - #[sqlx(json)] - #[serde(default)] - pub environment: serde_json::Value, - pub auto_detect: bool, - #[sqlx(default)] - pub detect_files: Vec<String>, - pub weight: f64, - pub required: bool, - pub enabled: bool, - pub last_run_at: Option<DateTime<Utc>>, - #[sqlx(json)] - pub last_result: Option<serde_json::Value>, - pub created_at: DateTime<Utc>, - pub updated_at: DateTime<Utc>, -} - -/// Directive approval - human-in-the-loop gate -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveApproval { - pub id: Uuid, - pub directive_id: Uuid, - pub step_id: Option<Uuid>, - pub approval_type: String, - pub description: String, - #[sqlx(json)] - pub context: Option<serde_json::Value>, - pub urgency: String, - pub status: String, - pub response: Option<String>, - pub responded_by: Option<Uuid>, - pub responded_at: Option<DateTime<Utc>>, - pub expires_at: Option<DateTime<Utc>>, - pub created_at: DateTime<Utc>, -} - -// ============================================================================= -// Directive Request/Response Types -// ============================================================================= - -/// Request to create a directive from a goal -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct CreateDirectiveRequest { - pub goal: String, - pub title: Option<String>, - pub repository_url: Option<String>, - pub local_path: Option<String>, - pub base_branch: Option<String>, - pub autonomy_level: Option<String>, - pub requirements: Option<serde_json::Value>, - pub acceptance_criteria: Option<serde_json::Value>, - pub confidence_threshold_green: Option<f64>, - pub confidence_threshold_yellow: Option<f64>, - pub max_total_cost_usd: Option<f64>, - pub max_wall_time_minutes: Option<i32>, -} - -/// Request to update a directive -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct UpdateDirectiveRequest { - pub title: Option<String>, - pub goal: Option<String>, - pub requirements: Option<serde_json::Value>, - pub acceptance_criteria: Option<serde_json::Value>, - pub constraints: Option<serde_json::Value>, - pub external_dependencies: Option<serde_json::Value>, - pub autonomy_level: Option<String>, - pub confidence_threshold_green: Option<f64>, - pub confidence_threshold_yellow: Option<f64>, - pub max_total_cost_usd: Option<f64>, - pub max_wall_time_minutes: Option<i32>, - pub max_rework_cycles: Option<i32>, - pub max_chain_regenerations: Option<i32>, - pub version: i32, -} - -/// Directive summary for list views -#[derive(Debug, Clone, Serialize, FromRow, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveSummary { - pub id: Uuid, - pub title: String, - pub goal: String, - pub status: String, - pub autonomy_level: String, - pub current_confidence: Option<f64>, - pub completed_steps: i32, - pub total_steps: i32, - pub chain_generation_count: i32, - pub started_at: Option<DateTime<Utc>>, - pub created_at: DateTime<Utc>, -} - -/// Directive with progress, chain, events, and approvals -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveWithProgress { - #[serde(flatten)] - pub directive: Directive, - pub chain: Option<DirectiveChain>, - pub steps: Vec<ChainStep>, - pub recent_events: Vec<DirectiveEvent>, - pub pending_approvals: Vec<DirectiveApproval>, -} - -/// Request to add a step to a chain -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct AddStepRequest { - pub name: String, - pub description: Option<String>, - pub step_type: Option<String>, - pub contract_type: Option<String>, - pub initial_phase: Option<String>, - pub task_plan: Option<String>, - pub phases: Option<Vec<String>>, - pub depends_on: Option<Vec<Uuid>>, - pub parallel_group: Option<String>, - pub requirement_ids: Option<Vec<String>>, - pub acceptance_criteria_ids: Option<Vec<String>>, - pub verifier_config: Option<serde_json::Value>, - pub editor_x: Option<f64>, - pub editor_y: Option<f64>, -} - -/// Request to update a step -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct UpdateStepRequest { - pub name: Option<String>, - pub description: Option<String>, - pub task_plan: Option<String>, - pub depends_on: Option<Vec<Uuid>>, - pub requirement_ids: Option<Vec<String>>, - pub acceptance_criteria_ids: Option<Vec<String>>, - pub verifier_config: Option<serde_json::Value>, - pub editor_x: Option<f64>, - pub editor_y: Option<f64>, -} - -/// Chain graph response for DAG visualization -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveChainGraphResponse { - pub chain_id: Uuid, - pub directive_id: Uuid, - pub nodes: Vec<DirectiveChainGraphNode>, - pub edges: Vec<DirectiveChainGraphEdge>, -} - -/// Node in directive chain graph -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveChainGraphNode { - pub id: Uuid, - pub name: String, - pub step_type: String, - pub status: String, - pub confidence_score: Option<f64>, - pub confidence_level: Option<String>, - pub contract_id: Option<Uuid>, - pub editor_x: Option<f64>, - pub editor_y: Option<f64>, -} - -/// Edge in directive chain graph -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveChainGraphEdge { - pub source: Uuid, - pub target: Uuid, -} - -/// Start directive response -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct StartDirectiveResponse { - pub directive_id: Uuid, - pub chain_id: Uuid, - pub chain_generation: i32, - pub steps: Vec<ChainStep>, - pub status: String, -} - -/// Request to create a verifier -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct CreateVerifierRequest { - pub name: String, - pub verifier_type: String, - pub command: Option<String>, - pub working_directory: Option<String>, - pub timeout_seconds: Option<i32>, - pub environment: Option<serde_json::Value>, - pub weight: Option<f64>, - pub required: Option<bool>, -} - -/// Request to update a verifier -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct UpdateVerifierRequest { - pub name: Option<String>, - pub command: Option<String>, - pub working_directory: Option<String>, - pub timeout_seconds: Option<i32>, - pub weight: Option<f64>, - pub required: Option<bool>, - pub enabled: Option<bool>, -} - -/// Approval action request -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ApprovalActionRequest { - pub response: Option<String>, -} - -/// Request to update directive requirements -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct UpdateRequirementsRequest { - pub requirements: Vec<DirectiveRequirement>, -} - -/// Request to update directive acceptance criteria -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct UpdateCriteriaRequest { - pub acceptance_criteria: Vec<DirectiveAcceptanceCriterion>, -} - -/// Request to trigger step rework -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ReworkStepRequest { - pub instructions: Option<String>, -} - -/// Directive requirement (shared type used in directive specification) -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveRequirement { - pub id: String, - pub title: String, - pub description: String, - pub priority: String, - pub category: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - pub parent_id: Option<String>, -} - -/// Directive acceptance criterion -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveAcceptanceCriterion { - pub id: String, - #[serde(default)] - pub requirement_ids: Vec<String>, - pub description: String, - #[serde(default = "default_true")] - pub testable: bool, - pub verification_method: Option<String>, -} - -fn default_true() -> bool { - true -} - -// Old chain types (Chain, ChainContract, ChainContractDefinition, ChainDirective, -// ContractEvaluation, ChainEvent, ChainRepository, etc.) have been replaced by -// the directive system above: Directive, DirectiveChain, ChainStep, -// DirectiveEvaluation, DirectiveEvent, DirectiveVerifier, DirectiveApproval. - -// Legacy types kept temporarily for chain runner/parser compatibility during migration. -// These will be removed once the chain daemon module is replaced. - -/// Request payload for creating a new chain (legacy - used by chain runner) -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CreateChainRequest { - pub name: String, - pub description: Option<String>, - pub repository_url: Option<String>, - pub repositories: Option<Vec<AddChainRepositoryRequest>>, - pub loop_enabled: Option<bool>, - pub loop_max_iterations: Option<i32>, - pub loop_progress_check: Option<String>, - pub contracts: Option<Vec<CreateChainContractRequest>>, -} - -/// Request to add a repository to a chain (legacy - used by chain runner) -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AddChainRepositoryRequest { - pub name: String, - pub repository_url: Option<String>, - pub local_path: Option<String>, - #[serde(default = "default_source_type")] - pub source_type: String, - #[serde(default)] - pub is_primary: bool, -} - -fn default_source_type() -> String { - "remote".to_string() -} - -/// Request to create a contract within a chain (legacy - used by chain runner) -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CreateChainContractRequest { - pub name: String, - pub description: Option<String>, - #[serde(default)] - pub contract_type: Option<String>, - pub initial_phase: Option<String>, - pub phases: Option<Vec<String>>, - pub depends_on: Option<Vec<String>>, - pub tasks: Option<Vec<CreateChainTaskRequest>>, - pub deliverables: Option<Vec<CreateChainDeliverableRequest>>, - pub editor_x: Option<f64>, - pub editor_y: Option<f64>, -} - -/// Task definition within a chain contract (legacy - used by chain runner) -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CreateChainTaskRequest { - pub name: String, - pub plan: String, -} - -/// Deliverable definition within a chain contract (legacy - used by chain runner) -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CreateChainDeliverableRequest { - pub id: String, - pub name: String, - pub priority: Option<String>, -} - -// ============================================================================= // Unit Tests // ============================================================================= diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index cd806f0..863d927 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -6,7 +6,6 @@ use sqlx::PgPool; use uuid::Uuid; use super::models::{ - // Core types CheckpointPatch, CheckpointPatchInfo, Contract, ContractChatConversation, ContractChatMessageRecord, ContractEvent, ContractRepository, ContractSummary, ContractTypeTemplateRecord, ConversationMessage, ConversationSnapshot, @@ -17,11 +16,6 @@ use super::models::{ PhaseDefinition, SupervisorHeartbeatRecord, SupervisorState, Task, TaskCheckpoint, TaskEvent, TaskSummary, UpdateContractRequest, UpdateFileRequest, UpdateTaskRequest, UpdateTemplateRequest, - // Directive types - AddStepRequest, ChainStep, CreateDirectiveRequest, Directive, DirectiveApproval, - DirectiveChain, DirectiveChainGraphEdge, DirectiveChainGraphNode, DirectiveChainGraphResponse, - DirectiveEvaluation, DirectiveEvent, DirectiveSummary, DirectiveVerifier, - DirectiveWithProgress, UpdateDirectiveRequest, UpdateStepRequest, }; /// Repository error types. @@ -4905,1169 +4899,6 @@ pub async fn sync_supervisor_state( } // ============================================================================= -// Directive Operations (top-level orchestration entity) -// ============================================================================= -// TODO: Implement directive CRUD functions -// - create_directive_for_owner -// - get_directive_for_owner -// - list_directives_for_owner -// - update_directive_for_owner -// - archive_directive_for_owner -// - update_directive_status - -// ============================================================================= -// Directive Chain Operations (generated execution plans) -// ============================================================================= -// TODO: Implement chain CRUD functions -// - create_directive_chain -// - get_current_chain -// - supersede_chain - -// ============================================================================= -// Chain Step Operations (nodes in the DAG) -// ============================================================================= -// TODO: Implement step CRUD functions -// - create_chain_step -// - update_chain_step -// - delete_chain_step -// - find_ready_steps -// - update_step_status -// - update_step_contract -// - update_step_confidence -// - increment_step_rework_count - -// ============================================================================= -// Directive Evaluation Operations -// ============================================================================= -// TODO: Implement evaluation functions -// - create_directive_evaluation -// - list_step_evaluations -// - list_directive_evaluations - -// ============================================================================= -// Directive Event Operations (audit stream) -// ============================================================================= -// TODO: Implement event functions -// - emit_directive_event -// - list_directive_events - -// ============================================================================= -// Directive Verifier Operations -// ============================================================================= -// TODO: Implement verifier CRUD functions -// - create_directive_verifier -// - list_directive_verifiers -// - update_directive_verifier - -// ============================================================================= -// Directive Approval Operations (human-in-the-loop) -// ============================================================================= -// TODO: Implement approval functions -// - create_approval_request -// - resolve_approval -// - list_pending_approvals - -// NOTE: Old chain functions removed. See git history for reference. -// Old functions included: create_chain_for_owner, get_chain_for_owner, -// list_chains_for_owner, update_chain_for_owner, delete_chain_for_owner, -// add_contract_to_chain, remove_contract_from_chain, list_chain_contracts, -// get_chain_with_contracts, list_chain_repositories, add_chain_repository, -// delete_chain_repository, set_chain_repository_primary, get_chain_graph, -// record_chain_event, list_chain_events, increment_chain_loop, complete_chain, -// get_ready_chain_contracts, is_chain_complete, get_chain_editor_data, -// create_chain_contract_definition, list_chain_contract_definitions, -// update_chain_contract_definition, delete_chain_contract_definition, -// get_chain_definition_graph, update_chain_status, progress_chain, -// create_chain_directive, get_chain_directive, update_chain_directive, -// delete_chain_directive, create_contract_evaluation, get_contract_evaluation, -// list_chain_evaluations, update_chain_contract_evaluation_status, -// mark_chain_contract_original_completion, get_chain_contract_by_contract_id, -// init_chain_for_owner. - -// ============================================================================= -// Directive Operations -// ============================================================================= - -/// Create a new directive for an owner. -pub async fn create_directive_for_owner( - pool: &PgPool, - owner_id: Uuid, - req: CreateDirectiveRequest, -) -> Result<Directive, sqlx::Error> { - let title = req.title.unwrap_or_else(|| truncate_string(&req.goal, 100)); - let autonomy_level = req.autonomy_level.unwrap_or_else(|| "guardrails".to_string()); - let green_threshold = req.confidence_threshold_green.unwrap_or(0.85); - let yellow_threshold = req.confidence_threshold_yellow.unwrap_or(0.60); - let requirements = req.requirements.unwrap_or(serde_json::json!([])); - let acceptance_criteria = req.acceptance_criteria.unwrap_or(serde_json::json!([])); - - sqlx::query_as::<_, Directive>( - r#" - INSERT INTO directives ( - owner_id, title, goal, requirements, acceptance_criteria, - autonomy_level, confidence_threshold_green, confidence_threshold_yellow, - repository_url, local_path, base_branch, - max_total_cost_usd, max_wall_time_minutes - ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) - RETURNING * - "#, - ) - .bind(owner_id) - .bind(&title) - .bind(&req.goal) - .bind(&requirements) - .bind(&acceptance_criteria) - .bind(&autonomy_level) - .bind(green_threshold) - .bind(yellow_threshold) - .bind(&req.repository_url) - .bind(&req.local_path) - .bind(&req.base_branch) - .bind(req.max_total_cost_usd) - .bind(req.max_wall_time_minutes) - .fetch_one(pool) - .await -} - -/// Get a directive by ID, scoped to owner. -pub async fn get_directive_for_owner( - pool: &PgPool, - id: Uuid, - owner_id: Uuid, -) -> Result<Option<Directive>, sqlx::Error> { - sqlx::query_as::<_, Directive>( - r#" - SELECT * FROM directives WHERE id = $1 AND owner_id = $2 - "#, - ) - .bind(id) - .bind(owner_id) - .fetch_optional(pool) - .await -} - -/// Get a directive by ID (no owner check - for internal use). -pub async fn get_directive(pool: &PgPool, id: Uuid) -> Result<Option<Directive>, sqlx::Error> { - sqlx::query_as::<_, Directive>( - r#"SELECT * FROM directives WHERE id = $1"#, - ) - .bind(id) - .fetch_optional(pool) - .await -} - -/// List directives for an owner. -pub async fn list_directives_for_owner( - pool: &PgPool, - owner_id: Uuid, - status_filter: Option<&str>, -) -> Result<Vec<DirectiveSummary>, sqlx::Error> { - let query = if let Some(status) = status_filter { - sqlx::query_as::<_, DirectiveSummary>( - r#" - SELECT - d.id, d.title, d.goal, d.status, d.autonomy_level, - dc.current_confidence, - COALESCE(dc.completed_steps, 0) as completed_steps, - COALESCE(dc.total_steps, 0) as total_steps, - d.chain_generation_count, d.started_at, d.created_at - FROM directives d - LEFT JOIN directive_chains dc ON dc.id = d.current_chain_id - WHERE d.owner_id = $1 AND d.status = $2 - ORDER BY d.created_at DESC - "#, - ) - .bind(owner_id) - .bind(status) - } else { - sqlx::query_as::<_, DirectiveSummary>( - r#" - SELECT - d.id, d.title, d.goal, d.status, d.autonomy_level, - dc.current_confidence, - COALESCE(dc.completed_steps, 0) as completed_steps, - COALESCE(dc.total_steps, 0) as total_steps, - d.chain_generation_count, d.started_at, d.created_at - FROM directives d - LEFT JOIN directive_chains dc ON dc.id = d.current_chain_id - WHERE d.owner_id = $1 - ORDER BY d.created_at DESC - "#, - ) - .bind(owner_id) - }; - query.fetch_all(pool).await -} - -/// Update a directive with optimistic locking. -pub async fn update_directive_for_owner( - pool: &PgPool, - id: Uuid, - owner_id: Uuid, - req: UpdateDirectiveRequest, -) -> Result<Directive, RepositoryError> { - // First get current version - let current = sqlx::query_scalar::<_, i32>( - "SELECT version FROM directives WHERE id = $1 AND owner_id = $2" - ) - .bind(id) - .bind(owner_id) - .fetch_optional(pool) - .await? - .ok_or_else(|| RepositoryError::Database(sqlx::Error::RowNotFound))?; - - if current != req.version { - return Err(RepositoryError::VersionConflict { - expected: req.version, - actual: current, - }); - } - - let directive = sqlx::query_as::<_, Directive>( - r#" - UPDATE directives SET - title = COALESCE($3, title), - goal = COALESCE($4, goal), - requirements = COALESCE($5, requirements), - acceptance_criteria = COALESCE($6, acceptance_criteria), - constraints = COALESCE($7, constraints), - external_dependencies = COALESCE($8, external_dependencies), - autonomy_level = COALESCE($9, autonomy_level), - confidence_threshold_green = COALESCE($10, confidence_threshold_green), - confidence_threshold_yellow = COALESCE($11, confidence_threshold_yellow), - max_total_cost_usd = COALESCE($12, max_total_cost_usd), - max_wall_time_minutes = COALESCE($13, max_wall_time_minutes), - max_rework_cycles = COALESCE($14, max_rework_cycles), - max_chain_regenerations = COALESCE($15, max_chain_regenerations), - version = version + 1, - updated_at = NOW() - WHERE id = $1 AND owner_id = $2 AND version = $16 - RETURNING * - "#, - ) - .bind(id) - .bind(owner_id) - .bind(&req.title) - .bind(&req.goal) - .bind(&req.requirements) - .bind(&req.acceptance_criteria) - .bind(&req.constraints) - .bind(&req.external_dependencies) - .bind(&req.autonomy_level) - .bind(req.confidence_threshold_green) - .bind(req.confidence_threshold_yellow) - .bind(req.max_total_cost_usd) - .bind(req.max_wall_time_minutes) - .bind(req.max_rework_cycles) - .bind(req.max_chain_regenerations) - .bind(req.version) - .fetch_one(pool) - .await?; - - Ok(directive) -} - -/// Update directive status. -pub async fn update_directive_status( - pool: &PgPool, - id: Uuid, - status: &str, -) -> Result<Directive, sqlx::Error> { - sqlx::query_as::<_, Directive>( - r#" - UPDATE directives SET - status = $2, - started_at = CASE WHEN $2 = 'active' AND started_at IS NULL THEN NOW() ELSE started_at END, - completed_at = CASE WHEN $2 IN ('completed', 'failed', 'archived') THEN NOW() ELSE completed_at END, - updated_at = NOW() - WHERE id = $1 - RETURNING * - "#, - ) - .bind(id) - .bind(status) - .fetch_one(pool) - .await -} - -/// Set the orchestrator contract ID for a directive. -pub async fn set_directive_orchestrator_contract( - pool: &PgPool, - directive_id: Uuid, - contract_id: Uuid, -) -> Result<(), sqlx::Error> { - sqlx::query( - r#" - UPDATE directives SET orchestrator_contract_id = $2, updated_at = NOW() - WHERE id = $1 - "#, - ) - .bind(directive_id) - .bind(contract_id) - .execute(pool) - .await?; - Ok(()) -} - -/// Find a directive by its orchestrator contract ID. -pub async fn get_directive_by_orchestrator_contract_id( - pool: &PgPool, - contract_id: Uuid, -) -> Result<Option<Directive>, sqlx::Error> { - sqlx::query_as::<_, Directive>( - r#" - SELECT * FROM directives WHERE orchestrator_contract_id = $1 - "#, - ) - .bind(contract_id) - .fetch_optional(pool) - .await -} - -/// Archive a directive (soft delete). -pub async fn archive_directive_for_owner( - pool: &PgPool, - id: Uuid, - owner_id: Uuid, -) -> Result<bool, sqlx::Error> { - let result = sqlx::query( - r#" - UPDATE directives SET status = 'archived', updated_at = NOW() - WHERE id = $1 AND owner_id = $2 - "#, - ) - .bind(id) - .bind(owner_id) - .execute(pool) - .await?; - Ok(result.rows_affected() > 0) -} - -/// Get directive with full progress info. -pub async fn get_directive_with_progress( - pool: &PgPool, - id: Uuid, - owner_id: Uuid, -) -> Result<Option<DirectiveWithProgress>, sqlx::Error> { - let directive = match get_directive_for_owner(pool, id, owner_id).await? { - Some(d) => d, - None => return Ok(None), - }; - - let chain = if let Some(chain_id) = directive.current_chain_id { - get_directive_chain(pool, chain_id).await? - } else { - None - }; - - let steps = if let Some(ref c) = chain { - list_chain_steps(pool, c.id).await? - } else { - vec![] - }; - - let recent_events = list_directive_events(pool, id, Some(20)).await?; - let pending_approvals = list_pending_approvals(pool, id).await?; - - Ok(Some(DirectiveWithProgress { - directive, - chain, - steps, - recent_events, - pending_approvals, - })) -} - -// ============================================================================= -// Directive Chain Operations -// ============================================================================= - -/// Create a new chain generation for a directive. -pub async fn create_directive_chain( - pool: &PgPool, - directive_id: Uuid, - name: &str, - description: Option<&str>, - rationale: Option<&str>, - planning_model: Option<&str>, -) -> Result<DirectiveChain, sqlx::Error> { - // Get next generation number - let generation = sqlx::query_scalar::<_, i32>( - "SELECT COALESCE(MAX(generation), 0) + 1 FROM directive_chains WHERE directive_id = $1" - ) - .bind(directive_id) - .fetch_one(pool) - .await?; - - let chain = sqlx::query_as::<_, DirectiveChain>( - r#" - INSERT INTO directive_chains (directive_id, generation, name, description, rationale, planning_model) - VALUES ($1, $2, $3, $4, $5, $6) - RETURNING * - "#, - ) - .bind(directive_id) - .bind(generation) - .bind(name) - .bind(description) - .bind(rationale) - .bind(planning_model) - .fetch_one(pool) - .await?; - - // Update directive to point to new chain and increment generation count - sqlx::query( - r#" - UPDATE directives SET - current_chain_id = $2, - chain_generation_count = chain_generation_count + 1, - updated_at = NOW() - WHERE id = $1 - "#, - ) - .bind(directive_id) - .bind(chain.id) - .execute(pool) - .await?; - - Ok(chain) -} - -/// Get a directive chain by ID. -pub async fn get_directive_chain(pool: &PgPool, id: Uuid) -> Result<Option<DirectiveChain>, sqlx::Error> { - sqlx::query_as::<_, DirectiveChain>( - "SELECT * FROM directive_chains WHERE id = $1" - ) - .bind(id) - .fetch_optional(pool) - .await -} - -/// Get the current chain for a directive. -pub async fn get_current_chain(pool: &PgPool, directive_id: Uuid) -> Result<Option<DirectiveChain>, sqlx::Error> { - sqlx::query_as::<_, DirectiveChain>( - r#" - SELECT dc.* FROM directive_chains dc - JOIN directives d ON d.current_chain_id = dc.id - WHERE d.id = $1 - "#, - ) - .bind(directive_id) - .fetch_optional(pool) - .await -} - -/// Update chain status. -pub async fn update_chain_status( - pool: &PgPool, - chain_id: Uuid, - status: &str, -) -> Result<DirectiveChain, sqlx::Error> { - sqlx::query_as::<_, DirectiveChain>( - r#" - UPDATE directive_chains SET - status = $2, - started_at = CASE WHEN $2 = 'active' AND started_at IS NULL THEN NOW() ELSE started_at END, - completed_at = CASE WHEN $2 IN ('completed', 'failed', 'superseded') THEN NOW() ELSE completed_at END, - updated_at = NOW() - WHERE id = $1 - RETURNING * - "#, - ) - .bind(chain_id) - .bind(status) - .fetch_one(pool) - .await -} - -/// Supersede a chain (mark as superseded and update directive). -pub async fn supersede_chain(pool: &PgPool, chain_id: Uuid) -> Result<(), sqlx::Error> { - sqlx::query( - r#" - UPDATE directive_chains SET status = 'superseded', completed_at = NOW(), updated_at = NOW() - WHERE id = $1 - "#, - ) - .bind(chain_id) - .execute(pool) - .await?; - Ok(()) -} - -// ============================================================================= -// Chain Step Operations -// ============================================================================= - -/// Create a new step in a chain. -pub async fn create_chain_step( - pool: &PgPool, - chain_id: Uuid, - req: AddStepRequest, -) -> Result<ChainStep, sqlx::Error> { - let step_type = req.step_type.unwrap_or_else(|| "execute".to_string()); - let contract_type = req.contract_type.unwrap_or_else(|| "simple".to_string()); - let phases = req.phases.unwrap_or_default(); - let depends_on = req.depends_on.unwrap_or_default(); - let requirement_ids = req.requirement_ids.unwrap_or_default(); - let acceptance_criteria_ids = req.acceptance_criteria_ids.unwrap_or_default(); - let verifier_config = req.verifier_config.unwrap_or(serde_json::json!({})); - - // Get next order index - let order_index = sqlx::query_scalar::<_, i32>( - "SELECT COALESCE(MAX(order_index), 0) + 1 FROM chain_steps WHERE chain_id = $1" - ) - .bind(chain_id) - .fetch_one(pool) - .await?; - - let step = sqlx::query_as::<_, ChainStep>( - r#" - INSERT INTO chain_steps ( - chain_id, name, description, step_type, contract_type, - initial_phase, task_plan, phases, depends_on, parallel_group, - requirement_ids, acceptance_criteria_ids, verifier_config, - editor_x, editor_y, order_index - ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) - RETURNING * - "#, - ) - .bind(chain_id) - .bind(&req.name) - .bind(&req.description) - .bind(&step_type) - .bind(&contract_type) - .bind(&req.initial_phase) - .bind(&req.task_plan) - .bind(&phases) - .bind(&depends_on) - .bind(&req.parallel_group) - .bind(&requirement_ids) - .bind(&acceptance_criteria_ids) - .bind(&verifier_config) - .bind(req.editor_x.unwrap_or(0.0)) - .bind(req.editor_y.unwrap_or(0.0)) - .bind(order_index) - .fetch_one(pool) - .await?; - - // Update chain total_steps count - sqlx::query( - "UPDATE directive_chains SET total_steps = total_steps + 1, updated_at = NOW() WHERE id = $1" - ) - .bind(chain_id) - .execute(pool) - .await?; - - Ok(step) -} - -/// Get a chain step by ID. -pub async fn get_chain_step(pool: &PgPool, id: Uuid) -> Result<Option<ChainStep>, sqlx::Error> { - sqlx::query_as::<_, ChainStep>( - "SELECT * FROM chain_steps WHERE id = $1" - ) - .bind(id) - .fetch_optional(pool) - .await -} - -/// List all steps in a chain. -pub async fn list_chain_steps(pool: &PgPool, chain_id: Uuid) -> Result<Vec<ChainStep>, sqlx::Error> { - sqlx::query_as::<_, ChainStep>( - "SELECT * FROM chain_steps WHERE chain_id = $1 ORDER BY order_index" - ) - .bind(chain_id) - .fetch_all(pool) - .await -} - -/// Update a chain step. -pub async fn update_chain_step( - pool: &PgPool, - step_id: Uuid, - req: UpdateStepRequest, -) -> Result<ChainStep, sqlx::Error> { - sqlx::query_as::<_, ChainStep>( - r#" - UPDATE chain_steps SET - name = COALESCE($2, name), - description = COALESCE($3, description), - task_plan = COALESCE($4, task_plan), - depends_on = COALESCE($5, depends_on), - requirement_ids = COALESCE($6, requirement_ids), - acceptance_criteria_ids = COALESCE($7, acceptance_criteria_ids), - verifier_config = COALESCE($8, verifier_config), - editor_x = COALESCE($9, editor_x), - editor_y = COALESCE($10, editor_y) - WHERE id = $1 - RETURNING * - "#, - ) - .bind(step_id) - .bind(&req.name) - .bind(&req.description) - .bind(&req.task_plan) - .bind(&req.depends_on) - .bind(&req.requirement_ids) - .bind(&req.acceptance_criteria_ids) - .bind(&req.verifier_config) - .bind(req.editor_x) - .bind(req.editor_y) - .fetch_one(pool) - .await -} - -/// Delete a chain step. -pub async fn delete_chain_step(pool: &PgPool, step_id: Uuid) -> Result<bool, sqlx::Error> { - // Get chain_id first for updating count - let chain_id = sqlx::query_scalar::<_, Uuid>( - "SELECT chain_id FROM chain_steps WHERE id = $1" - ) - .bind(step_id) - .fetch_optional(pool) - .await?; - - let result = sqlx::query("DELETE FROM chain_steps WHERE id = $1") - .bind(step_id) - .execute(pool) - .await?; - - // Update chain total_steps count - if let Some(cid) = chain_id { - sqlx::query( - "UPDATE directive_chains SET total_steps = total_steps - 1, updated_at = NOW() WHERE id = $1" - ) - .bind(cid) - .execute(pool) - .await?; - } - - Ok(result.rows_affected() > 0) -} - -/// Find steps that are ready to execute (all dependencies met, status=pending). -pub async fn find_ready_steps(pool: &PgPool, chain_id: Uuid) -> Result<Vec<ChainStep>, sqlx::Error> { - sqlx::query_as::<_, ChainStep>( - r#" - SELECT s.* FROM chain_steps s - WHERE s.chain_id = $1 - AND s.status = 'pending' - AND NOT EXISTS ( - SELECT 1 FROM chain_steps dep - WHERE dep.id = ANY(s.depends_on) - AND dep.status NOT IN ('passed', 'skipped') - ) - ORDER BY s.order_index - "#, - ) - .bind(chain_id) - .fetch_all(pool) - .await -} - -/// Update step status. -pub async fn update_step_status( - pool: &PgPool, - step_id: Uuid, - status: &str, -) -> Result<ChainStep, sqlx::Error> { - let step = sqlx::query_as::<_, ChainStep>( - r#" - UPDATE chain_steps SET - status = $2, - started_at = CASE WHEN $2 = 'running' AND started_at IS NULL THEN NOW() ELSE started_at END, - completed_at = CASE WHEN $2 IN ('passed', 'failed', 'skipped') THEN NOW() ELSE completed_at END - WHERE id = $1 - RETURNING * - "#, - ) - .bind(step_id) - .bind(status) - .fetch_one(pool) - .await?; - - // Update chain completed_steps and failed_steps counts - if status == "passed" || status == "skipped" { - sqlx::query( - "UPDATE directive_chains SET completed_steps = completed_steps + 1, updated_at = NOW() WHERE id = $1" - ) - .bind(step.chain_id) - .execute(pool) - .await?; - } else if status == "failed" { - sqlx::query( - "UPDATE directive_chains SET failed_steps = failed_steps + 1, updated_at = NOW() WHERE id = $1" - ) - .bind(step.chain_id) - .execute(pool) - .await?; - } - - Ok(step) -} - -/// Link a step to a contract. -pub async fn update_step_contract( - pool: &PgPool, - step_id: Uuid, - contract_id: Uuid, - supervisor_task_id: Option<Uuid>, -) -> Result<ChainStep, sqlx::Error> { - sqlx::query_as::<_, ChainStep>( - r#" - UPDATE chain_steps SET contract_id = $2, supervisor_task_id = $3 - WHERE id = $1 - RETURNING * - "#, - ) - .bind(step_id) - .bind(contract_id) - .bind(supervisor_task_id) - .fetch_one(pool) - .await -} - -/// Update step confidence score and level. -pub async fn update_step_confidence( - pool: &PgPool, - step_id: Uuid, - score: f64, - level: &str, - evaluation_id: Uuid, -) -> Result<ChainStep, sqlx::Error> { - sqlx::query_as::<_, ChainStep>( - r#" - UPDATE chain_steps SET - confidence_score = $2, - confidence_level = $3, - last_evaluation_id = $4, - evaluation_count = evaluation_count + 1 - WHERE id = $1 - RETURNING * - "#, - ) - .bind(step_id) - .bind(score) - .bind(level) - .bind(evaluation_id) - .fetch_one(pool) - .await -} - -/// Increment step rework count. -pub async fn increment_step_rework_count(pool: &PgPool, step_id: Uuid) -> Result<ChainStep, sqlx::Error> { - sqlx::query_as::<_, ChainStep>( - r#" - UPDATE chain_steps SET rework_count = rework_count + 1, status = 'rework' - WHERE id = $1 - RETURNING * - "#, - ) - .bind(step_id) - .fetch_one(pool) - .await -} - -/// Get chain graph for visualization. -pub async fn get_chain_graph( - pool: &PgPool, - chain_id: Uuid, -) -> Result<DirectiveChainGraphResponse, sqlx::Error> { - let chain = get_directive_chain(pool, chain_id).await? - .ok_or_else(|| sqlx::Error::RowNotFound)?; - - let steps = list_chain_steps(pool, chain_id).await?; - - let nodes: Vec<DirectiveChainGraphNode> = steps.iter().map(|s| { - DirectiveChainGraphNode { - id: s.id, - name: s.name.clone(), - step_type: s.step_type.clone(), - status: s.status.clone(), - confidence_score: s.confidence_score, - confidence_level: s.confidence_level.clone(), - contract_id: s.contract_id, - editor_x: s.editor_x, - editor_y: s.editor_y, - } - }).collect(); - - let mut edges = Vec::new(); - for step in &steps { - for dep_id in &step.depends_on { - edges.push(DirectiveChainGraphEdge { - source: *dep_id, - target: step.id, - }); - } - } - - Ok(DirectiveChainGraphResponse { - chain_id, - directive_id: chain.directive_id, - nodes, - edges, - }) -} - -// ============================================================================= -// Directive Evaluation Operations -// ============================================================================= - -/// Create a directive evaluation. -pub async fn create_directive_evaluation( - pool: &PgPool, - directive_id: Uuid, - chain_id: Option<Uuid>, - step_id: Option<Uuid>, - contract_id: Option<Uuid>, - evaluation_type: &str, - evaluator: Option<&str>, - passed: bool, - overall_score: Option<f64>, - confidence_level: Option<&str>, - programmatic_results: serde_json::Value, - llm_results: serde_json::Value, - criteria_results: serde_json::Value, - summary_feedback: &str, - rework_instructions: Option<&str>, -) -> Result<DirectiveEvaluation, sqlx::Error> { - // Get next evaluation number for this step/directive - let evaluation_number = if let Some(sid) = step_id { - sqlx::query_scalar::<_, i32>( - "SELECT COALESCE(MAX(evaluation_number), 0) + 1 FROM directive_evaluations WHERE step_id = $1" - ) - .bind(sid) - .fetch_one(pool) - .await? - } else { - sqlx::query_scalar::<_, i32>( - "SELECT COALESCE(MAX(evaluation_number), 0) + 1 FROM directive_evaluations WHERE directive_id = $1 AND step_id IS NULL" - ) - .bind(directive_id) - .fetch_one(pool) - .await? - }; - - sqlx::query_as::<_, DirectiveEvaluation>( - r#" - INSERT INTO directive_evaluations ( - directive_id, chain_id, step_id, contract_id, - evaluation_type, evaluation_number, evaluator, - passed, overall_score, confidence_level, - programmatic_results, llm_results, criteria_results, - summary_feedback, rework_instructions, - completed_at - ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, NOW()) - RETURNING * - "#, - ) - .bind(directive_id) - .bind(chain_id) - .bind(step_id) - .bind(contract_id) - .bind(evaluation_type) - .bind(evaluation_number) - .bind(evaluator) - .bind(passed) - .bind(overall_score) - .bind(confidence_level) - .bind(&programmatic_results) - .bind(&llm_results) - .bind(&criteria_results) - .bind(summary_feedback) - .bind(rework_instructions) - .fetch_one(pool) - .await -} - -/// List evaluations for a step. -pub async fn list_step_evaluations( - pool: &PgPool, - step_id: Uuid, -) -> Result<Vec<DirectiveEvaluation>, sqlx::Error> { - sqlx::query_as::<_, DirectiveEvaluation>( - "SELECT * FROM directive_evaluations WHERE step_id = $1 ORDER BY evaluation_number DESC" - ) - .bind(step_id) - .fetch_all(pool) - .await -} - -/// List evaluations for a directive. -pub async fn list_directive_evaluations( - pool: &PgPool, - directive_id: Uuid, - limit: Option<i64>, -) -> Result<Vec<DirectiveEvaluation>, sqlx::Error> { - let limit = limit.unwrap_or(100); - sqlx::query_as::<_, DirectiveEvaluation>( - "SELECT * FROM directive_evaluations WHERE directive_id = $1 ORDER BY created_at DESC LIMIT $2" - ) - .bind(directive_id) - .bind(limit) - .fetch_all(pool) - .await -} - -// ============================================================================= -// Directive Event Operations -// ============================================================================= - -/// Emit a directive event. -pub async fn emit_directive_event( - pool: &PgPool, - directive_id: Uuid, - chain_id: Option<Uuid>, - step_id: Option<Uuid>, - event_type: &str, - severity: &str, - event_data: Option<serde_json::Value>, - actor_type: &str, - actor_id: Option<Uuid>, -) -> Result<DirectiveEvent, sqlx::Error> { - sqlx::query_as::<_, DirectiveEvent>( - r#" - INSERT INTO directive_events ( - directive_id, chain_id, step_id, event_type, severity, event_data, actor_type, actor_id - ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8) - RETURNING * - "#, - ) - .bind(directive_id) - .bind(chain_id) - .bind(step_id) - .bind(event_type) - .bind(severity) - .bind(event_data) - .bind(actor_type) - .bind(actor_id) - .fetch_one(pool) - .await -} - -/// List directive events. -pub async fn list_directive_events( - pool: &PgPool, - directive_id: Uuid, - limit: Option<i64>, -) -> Result<Vec<DirectiveEvent>, sqlx::Error> { - let limit = limit.unwrap_or(100); - sqlx::query_as::<_, DirectiveEvent>( - "SELECT * FROM directive_events WHERE directive_id = $1 ORDER BY created_at DESC LIMIT $2" - ) - .bind(directive_id) - .bind(limit) - .fetch_all(pool) - .await -} - -// ============================================================================= -// Directive Verifier Operations -// ============================================================================= - -/// Create a directive verifier. -pub async fn create_directive_verifier( - pool: &PgPool, - directive_id: Uuid, - name: &str, - verifier_type: &str, - command: Option<&str>, - working_directory: Option<&str>, - auto_detect: bool, - detect_files: Vec<String>, - weight: f64, - required: bool, -) -> Result<DirectiveVerifier, sqlx::Error> { - sqlx::query_as::<_, DirectiveVerifier>( - r#" - INSERT INTO directive_verifiers ( - directive_id, name, verifier_type, command, working_directory, - auto_detect, detect_files, weight, required - ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) - RETURNING * - "#, - ) - .bind(directive_id) - .bind(name) - .bind(verifier_type) - .bind(command) - .bind(working_directory) - .bind(auto_detect) - .bind(&detect_files) - .bind(weight) - .bind(required) - .fetch_one(pool) - .await -} - -/// List verifiers for a directive. -pub async fn list_directive_verifiers( - pool: &PgPool, - directive_id: Uuid, -) -> Result<Vec<DirectiveVerifier>, sqlx::Error> { - sqlx::query_as::<_, DirectiveVerifier>( - "SELECT * FROM directive_verifiers WHERE directive_id = $1 ORDER BY name" - ) - .bind(directive_id) - .fetch_all(pool) - .await -} - -/// Update a directive verifier. -pub async fn update_directive_verifier( - pool: &PgPool, - verifier_id: Uuid, - enabled: Option<bool>, - command: Option<&str>, - weight: Option<f64>, - required: Option<bool>, -) -> Result<DirectiveVerifier, sqlx::Error> { - sqlx::query_as::<_, DirectiveVerifier>( - r#" - UPDATE directive_verifiers SET - enabled = COALESCE($2, enabled), - command = COALESCE($3, command), - weight = COALESCE($4, weight), - required = COALESCE($5, required), - updated_at = NOW() - WHERE id = $1 - RETURNING * - "#, - ) - .bind(verifier_id) - .bind(enabled) - .bind(command) - .bind(weight) - .bind(required) - .fetch_one(pool) - .await -} - -/// Update verifier last run result. -pub async fn update_verifier_result( - pool: &PgPool, - verifier_id: Uuid, - result: serde_json::Value, -) -> Result<DirectiveVerifier, sqlx::Error> { - sqlx::query_as::<_, DirectiveVerifier>( - r#" - UPDATE directive_verifiers SET last_run_at = NOW(), last_result = $2, updated_at = NOW() - WHERE id = $1 - RETURNING * - "#, - ) - .bind(verifier_id) - .bind(result) - .fetch_one(pool) - .await -} - -// ============================================================================= -// Directive Approval Operations -// ============================================================================= - -/// Create an approval request. -pub async fn create_approval_request( - pool: &PgPool, - directive_id: Uuid, - step_id: Option<Uuid>, - approval_type: &str, - description: &str, - context: Option<serde_json::Value>, - urgency: &str, - expires_at: Option<chrono::DateTime<Utc>>, -) -> Result<DirectiveApproval, sqlx::Error> { - sqlx::query_as::<_, DirectiveApproval>( - r#" - INSERT INTO directive_approvals ( - directive_id, step_id, approval_type, description, context, urgency, expires_at - ) - VALUES ($1, $2, $3, $4, $5, $6, $7) - RETURNING * - "#, - ) - .bind(directive_id) - .bind(step_id) - .bind(approval_type) - .bind(description) - .bind(context) - .bind(urgency) - .bind(expires_at) - .fetch_one(pool) - .await -} - -/// Resolve an approval request. -pub async fn resolve_approval( - pool: &PgPool, - approval_id: Uuid, - status: &str, - response: Option<&str>, - responded_by: Uuid, -) -> Result<DirectiveApproval, sqlx::Error> { - sqlx::query_as::<_, DirectiveApproval>( - r#" - UPDATE directive_approvals SET - status = $2, - response = $3, - responded_by = $4, - responded_at = NOW() - WHERE id = $1 - RETURNING * - "#, - ) - .bind(approval_id) - .bind(status) - .bind(response) - .bind(responded_by) - .fetch_one(pool) - .await -} - -/// List pending approvals for a directive. -pub async fn list_pending_approvals( - pool: &PgPool, - directive_id: Uuid, -) -> Result<Vec<DirectiveApproval>, sqlx::Error> { - sqlx::query_as::<_, DirectiveApproval>( - r#" - SELECT * FROM directive_approvals - WHERE directive_id = $1 AND status = 'pending' - ORDER BY - CASE urgency - WHEN 'critical' THEN 1 - WHEN 'high' THEN 2 - WHEN 'normal' THEN 3 - ELSE 4 - END, - created_at - "#, - ) - .bind(directive_id) - .fetch_all(pool) - .await -} - -/// Get step by contract ID. -pub async fn get_step_by_contract_id( - pool: &PgPool, - contract_id: Uuid, -) -> Result<Option<ChainStep>, sqlx::Error> { - sqlx::query_as::<_, ChainStep>( - "SELECT * FROM chain_steps WHERE contract_id = $1" - ) - .bind(contract_id) - .fetch_optional(pool) - .await -} - -// ============================================================================= // Helper Functions // ============================================================================= |
