summaryrefslogtreecommitdiff
path: root/makima/src/db
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-05 01:42:59 +0000
committersoryu <soryu@soryu.co>2026-02-05 01:42:59 +0000
commit6a0c912a3fbd8e9b3e87ef40e960803d819d966d (patch)
treeb2c50c490811286d163e40f8d624ee8d43c0ce43 /makima/src/db
parent0302b4596e14210884df5d645df9a179d8f0c1c6 (diff)
downloadsoryu-6a0c912a3fbd8e9b3e87ef40e960803d819d966d.tar.gz
soryu-6a0c912a3fbd8e9b3e87ef40e960803d819d966d.zip
Add makima directives
Diffstat (limited to 'makima/src/db')
-rw-r--r--makima/src/db/models.rs351
-rw-r--r--makima/src/db/repository.rs511
2 files changed, 849 insertions, 13 deletions
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index 30e1603..392d019 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -1449,6 +1449,13 @@ pub struct Contract {
/// Chain ID if this contract is part of a chain (DAG of contracts)
#[serde(skip_serializing_if = "Option::is_none")]
pub chain_id: Option<Uuid>,
+ /// Reference to chain spawned by this directive contract
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub spawned_chain_id: Option<Uuid>,
+ /// Whether this contract is a chain directive orchestrator
+ #[serde(default)]
+ #[sqlx(default)]
+ pub is_chain_directive: bool,
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
@@ -2652,12 +2659,28 @@ pub struct Chain {
pub loop_current_iteration: Option<i32>,
/// Progress check prompt/criteria for evaluating loop completion
pub loop_progress_check: Option<String>,
+ /// Reference to the directive contract that created/orchestrates this chain
+ pub directive_contract_id: Option<Uuid>,
+ /// The directive document text (formal specification)
+ pub directive_document: Option<String>,
+ /// Whether LLM evaluation is enabled after contract completion
+ #[serde(default = "default_evaluation_enabled")]
+ #[sqlx(default)]
+ pub evaluation_enabled: bool,
+ /// Default pass threshold for evaluations (0.0-1.0)
+ pub default_pass_threshold: Option<f64>,
+ /// Default max retry attempts for evaluations
+ pub default_max_retries: Option<i32>,
/// Version for optimistic locking
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
+fn default_evaluation_enabled() -> bool {
+ true
+}
+
/// Chain repository record from the database
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
@@ -2709,9 +2732,37 @@ pub struct ChainContract {
pub editor_x: Option<f64>,
/// Y position for GUI editor
pub editor_y: Option<f64>,
+ /// Evaluation status: pending, evaluating, passed, failed, rework, escalated
+ #[serde(default = "default_evaluation_status")]
+ #[sqlx(default)]
+ pub evaluation_status: String,
+ /// Number of evaluation retry attempts
+ #[serde(default)]
+ #[sqlx(default)]
+ pub evaluation_retry_count: i32,
+ /// Maximum evaluation retry attempts (default: 3)
+ #[serde(default = "default_max_evaluation_retries")]
+ #[sqlx(default)]
+ pub max_evaluation_retries: i32,
+ /// Reference to the last evaluation result
+ pub last_evaluation_id: Option<Uuid>,
+ /// Rework feedback/instructions from failed evaluation
+ pub rework_feedback: Option<String>,
+ /// When rework was started
+ pub rework_started_at: Option<DateTime<Utc>>,
+ /// When contract originally completed (before rework)
+ pub original_completion_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
}
+fn default_evaluation_status() -> String {
+ "pending".to_string()
+}
+
+fn default_max_evaluation_retries() -> i32 {
+ 3
+}
+
/// Chain event for audit trail
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
@@ -2765,6 +2816,15 @@ pub struct ChainContractDetail {
pub order_index: i32,
pub editor_x: Option<f64>,
pub editor_y: Option<f64>,
+ /// Evaluation status: pending, passed, failed, rework
+ #[sqlx(default)]
+ pub evaluation_status: Option<String>,
+ /// Number of evaluation retries
+ #[sqlx(default)]
+ pub evaluation_retry_count: i32,
+ /// Maximum evaluation retry attempts
+ #[sqlx(default)]
+ pub max_evaluation_retries: i32,
}
/// DAG graph structure for visualization
@@ -3058,6 +3118,19 @@ pub struct ChainContractDefinition {
pub deliverables: Option<serde_json::Value>,
/// Validation configuration for checkpoint contracts (JSON)
pub validation: Option<serde_json::Value>,
+ /// Requirement IDs this contract addresses (for traceability)
+ #[sqlx(default)]
+ #[serde(default)]
+ pub requirement_ids: Vec<String>,
+ /// Acceptance criteria for this contract (JSON array)
+ #[serde(default)]
+ pub acceptance_criteria: Option<serde_json::Value>,
+ /// Whether LLM evaluation is enabled for this contract
+ #[serde(default = "default_evaluation_enabled")]
+ #[sqlx(default)]
+ pub evaluation_enabled: bool,
+ /// Pass threshold for evaluation (0.0-1.0)
+ pub pass_threshold: Option<f64>,
/// Position in GUI editor
pub editor_x: Option<f64>,
pub editor_y: Option<f64>,
@@ -3154,6 +3227,284 @@ pub struct ChainDefinitionGraphResponse {
}
// =============================================================================
+// Chain Directives (formal specification documents for directive-driven chains)
+// =============================================================================
+
+/// Chain directive - formal specification document that drives chain creation and evaluation
+#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ChainDirective {
+ pub id: Uuid,
+ pub chain_id: Uuid,
+ pub version: i32,
+ /// Requirements as JSON: [{ id, title, description, priority, category, parentId? }]
+ #[sqlx(json)]
+ pub requirements: serde_json::Value,
+ /// Acceptance criteria as JSON: [{ id, requirementIds[], description, testable, verificationMethod }]
+ #[sqlx(json)]
+ pub acceptance_criteria: serde_json::Value,
+ /// Constraints as JSON: [{ id, type, description, impact }]
+ #[sqlx(json)]
+ pub constraints: serde_json::Value,
+ /// External dependencies as JSON: [{ id, name, type, status, requiredBy[] }]
+ #[sqlx(json)]
+ pub external_dependencies: serde_json::Value,
+ /// Source type: 'manual', 'llm_generated', 'imported'
+ pub source_type: String,
+ pub created_at: DateTime<Utc>,
+ pub updated_at: DateTime<Utc>,
+}
+
+/// Requirement in a directive
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DirectiveRequirement {
+ pub id: String,
+ pub title: String,
+ pub description: String,
+ /// Priority: 'must', 'should', 'could', 'wont'
+ pub priority: String,
+ /// Category: 'feature', 'infrastructure', 'testing', etc.
+ pub category: Option<String>,
+ /// Parent requirement ID for hierarchical requirements
+ pub parent_id: Option<String>,
+}
+
+/// Acceptance criterion in a directive
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DirectiveAcceptanceCriterion {
+ pub id: String,
+ /// Requirement IDs this criterion validates
+ pub requirement_ids: Vec<String>,
+ pub description: String,
+ pub testable: bool,
+ /// Verification method: 'automated', 'manual', 'review', 'llm'
+ pub verification_method: String,
+}
+
+/// Constraint in a directive
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DirectiveConstraint {
+ pub id: String,
+ /// Type: 'technical', 'business', 'time', 'resource'
+ #[serde(rename = "type")]
+ pub constraint_type: String,
+ pub description: String,
+ /// Impact: 'high', 'medium', 'low'
+ pub impact: String,
+}
+
+/// External dependency in a directive
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DirectiveExternalDependency {
+ pub id: String,
+ pub name: String,
+ /// Type: 'api', 'service', 'library', 'data'
+ #[serde(rename = "type")]
+ pub dependency_type: String,
+ /// Status: 'available', 'pending', 'blocked'
+ pub status: String,
+ /// Requirement IDs that need this dependency
+ pub required_by: Vec<String>,
+}
+
+/// Request to create or update a chain directive
+#[derive(Debug, Clone, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct CreateChainDirectiveRequest {
+ pub requirements: Option<Vec<DirectiveRequirement>>,
+ pub acceptance_criteria: Option<Vec<DirectiveAcceptanceCriterion>>,
+ pub constraints: Option<Vec<DirectiveConstraint>>,
+ pub external_dependencies: Option<Vec<DirectiveExternalDependency>>,
+ pub source_type: Option<String>,
+}
+
+/// Request to initialize a directive-driven chain
+#[derive(Debug, Clone, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct InitChainRequest {
+ /// High-level goal/description for the directive contract
+ pub goal: String,
+ /// Repository URL for chain contracts
+ pub repository_url: Option<String>,
+ /// Local path for chain contracts
+ pub local_path: Option<String>,
+ /// Whether to enable phase guard (user approval between phases)
+ #[serde(default)]
+ pub phase_guard: bool,
+}
+
+/// Response from initializing a directive-driven chain
+#[derive(Debug, Clone, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct InitChainResponse {
+ pub chain_id: Uuid,
+ pub directive_contract_id: Uuid,
+ pub supervisor_task_id: Option<Uuid>,
+}
+
+// =============================================================================
+// Contract Evaluations (LLM evaluation results for completed contracts)
+// =============================================================================
+
+/// Evaluation status for chain contracts
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum EvaluationStatus {
+ /// Not yet evaluated
+ Pending,
+ /// Currently being evaluated
+ Evaluating,
+ /// Evaluation passed
+ Passed,
+ /// Evaluation failed
+ Failed,
+ /// Contract is being reworked after failed evaluation
+ Rework,
+ /// Max retries exceeded, escalated to user
+ Escalated,
+ /// User approved despite partial failure
+ ApprovedWithIssues,
+}
+
+impl std::fmt::Display for EvaluationStatus {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Pending => write!(f, "pending"),
+ Self::Evaluating => write!(f, "evaluating"),
+ Self::Passed => write!(f, "passed"),
+ Self::Failed => write!(f, "failed"),
+ Self::Rework => write!(f, "rework"),
+ Self::Escalated => write!(f, "escalated"),
+ Self::ApprovedWithIssues => write!(f, "approved_with_issues"),
+ }
+ }
+}
+
+impl std::str::FromStr for EvaluationStatus {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.to_lowercase().as_str() {
+ "pending" => Ok(Self::Pending),
+ "evaluating" => Ok(Self::Evaluating),
+ "passed" => Ok(Self::Passed),
+ "failed" => Ok(Self::Failed),
+ "rework" => Ok(Self::Rework),
+ "escalated" => Ok(Self::Escalated),
+ "approved_with_issues" => Ok(Self::ApprovedWithIssues),
+ _ => Err(format!("Unknown evaluation status: {}", s)),
+ }
+ }
+}
+
+/// Contract evaluation - LLM evaluation result after contract completion
+#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractEvaluation {
+ pub id: Uuid,
+ pub contract_id: Uuid,
+ pub chain_id: Option<Uuid>,
+ pub chain_contract_id: Option<Uuid>,
+ /// Evaluation attempt number (1-based)
+ pub evaluation_number: i32,
+ /// Model used for evaluation
+ pub evaluator_model: Option<String>,
+ /// Whether the evaluation passed
+ pub passed: bool,
+ /// Overall score (0.0-1.0)
+ pub overall_score: Option<f64>,
+ /// Per-criterion results as JSON
+ #[sqlx(json)]
+ pub criteria_results: serde_json::Value,
+ /// Summary feedback from the evaluator
+ pub summary_feedback: String,
+ /// Instructions for rework if evaluation failed
+ pub rework_instructions: Option<String>,
+ /// Snapshot of directive at evaluation time
+ pub directive_snapshot: Option<serde_json::Value>,
+ /// Snapshot of deliverables at evaluation time
+ pub deliverables_snapshot: Option<serde_json::Value>,
+ pub started_at: DateTime<Utc>,
+ pub completed_at: Option<DateTime<Utc>>,
+ pub created_at: DateTime<Utc>,
+}
+
+/// Per-criterion evaluation result
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct EvaluationCriterionResult {
+ pub criterion_id: String,
+ pub criterion_text: String,
+ pub passed: bool,
+ /// Score (0.0-1.0)
+ pub score: f64,
+ pub feedback: String,
+ /// Evidence supporting the evaluation
+ pub evidence: Vec<String>,
+}
+
+/// Request to create a contract evaluation
+#[derive(Debug, Clone, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct CreateContractEvaluationRequest {
+ pub contract_id: Uuid,
+ pub chain_id: Option<Uuid>,
+ pub chain_contract_id: Option<Uuid>,
+ pub evaluator_model: Option<String>,
+ pub passed: bool,
+ pub overall_score: Option<f64>,
+ pub criteria_results: Vec<EvaluationCriterionResult>,
+ pub summary_feedback: String,
+ pub rework_instructions: Option<String>,
+}
+
+/// Summary of contract evaluation for list views
+#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractEvaluationSummary {
+ pub id: Uuid,
+ pub contract_id: Uuid,
+ pub evaluation_number: i32,
+ pub passed: bool,
+ pub overall_score: Option<f64>,
+ pub summary_feedback: String,
+ pub created_at: DateTime<Utc>,
+}
+
+/// Response listing evaluations for a chain or contract
+#[derive(Debug, Clone, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractEvaluationsResponse {
+ pub evaluations: Vec<ContractEvaluationSummary>,
+ pub total: i64,
+}
+
+/// Traceability matrix entry - maps requirements to contracts
+#[derive(Debug, Clone, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct TraceabilityEntry {
+ pub requirement_id: String,
+ pub requirement_title: String,
+ pub contract_definition_ids: Vec<Uuid>,
+ pub contract_definition_names: Vec<String>,
+ pub acceptance_criteria_ids: Vec<String>,
+}
+
+/// Response for directive traceability
+#[derive(Debug, Clone, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DirectiveTraceabilityResponse {
+ pub chain_id: Uuid,
+ pub entries: Vec<TraceabilityEntry>,
+ /// Requirements not mapped to any contract
+ pub uncovered_requirements: Vec<String>,
+}
+
+// =============================================================================
// Unit Tests
// =============================================================================
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs
index 2b595b5..9cb653f 100644
--- a/makima/src/db/repository.rs
+++ b/makima/src/db/repository.rs
@@ -8,18 +8,21 @@ use uuid::Uuid;
use super::models::{
AddChainRepositoryRequest, AddContractDefinitionRequest, AddContractToChainRequest, Chain,
ChainContract, ChainContractDefinition, ChainContractDetail, ChainDefinitionGraphNode,
- ChainDefinitionGraphResponse, ChainEditorContract, ChainEditorData, ChainEditorDeliverable,
- ChainEditorEdge, ChainEditorNode, ChainEditorTask, ChainEvent, ChainGraphEdge, ChainGraphNode,
- ChainGraphResponse, ChainRepository, ChainSummary, ChainWithContracts, CheckpointPatch,
- CheckpointPatchInfo, Contract, ContractChatConversation, ContractChatMessageRecord,
- ContractEvent, ContractRepository, ContractSummary, ContractTypeTemplateRecord,
- ConversationMessage, ConversationSnapshot, CreateChainRequest, CreateContractRequest,
- CreateFileRequest, CreateTaskRequest, CreateTemplateRequest, Daemon, DaemonTaskAssignment,
- DaemonWithCapacity, DeliverableDefinition, File, FileSummary, FileVersion, HistoryEvent,
- HistoryQueryFilters, MeshChatConversation, MeshChatMessageRecord, PhaseChangeResult,
- PhaseConfig, PhaseDefinition, SupervisorHeartbeatRecord, SupervisorState, Task, TaskCheckpoint,
- TaskEvent, TaskSummary, UpdateChainRequest, UpdateContractDefinitionRequest,
- UpdateContractRequest, UpdateFileRequest, UpdateTaskRequest, UpdateTemplateRequest,
+ ChainDefinitionGraphResponse, ChainDirective, ChainEditorContract, ChainEditorData,
+ ChainEditorDeliverable, ChainEditorEdge, ChainEditorNode, ChainEditorTask, ChainEvent,
+ ChainGraphEdge, ChainGraphNode, ChainGraphResponse, ChainRepository, ChainSummary,
+ ChainWithContracts, CheckpointPatch, CheckpointPatchInfo, Contract, ContractChatConversation,
+ ContractChatMessageRecord, ContractEvaluation, ContractEvaluationSummary, ContractEvent,
+ ContractRepository, ContractSummary, ContractTypeTemplateRecord, ConversationMessage,
+ ConversationSnapshot, CreateChainDirectiveRequest, CreateChainRequest,
+ CreateContractEvaluationRequest, CreateContractRequest, CreateFileRequest, CreateTaskRequest,
+ CreateTemplateRequest, Daemon, DaemonTaskAssignment, DaemonWithCapacity, DeliverableDefinition,
+ DirectiveTraceabilityResponse, EvaluationCriterionResult, File, FileSummary, FileVersion,
+ HistoryEvent, HistoryQueryFilters, InitChainRequest, InitChainResponse, MeshChatConversation,
+ MeshChatMessageRecord, PhaseChangeResult, PhaseConfig, PhaseDefinition,
+ SupervisorHeartbeatRecord, SupervisorState, Task, TaskCheckpoint, TaskEvent, TaskSummary,
+ TraceabilityEntry, UpdateChainRequest, UpdateContractDefinitionRequest, UpdateContractRequest,
+ UpdateFileRequest, UpdateTaskRequest, UpdateTemplateRequest,
};
/// Repository error types.
@@ -5156,7 +5159,10 @@ pub async fn list_chain_contracts(
cc.depends_on,
cc.order_index,
cc.editor_x,
- cc.editor_y
+ cc.editor_y,
+ cc.evaluation_status,
+ cc.evaluation_retry_count,
+ cc.max_evaluation_retries
FROM chain_contracts cc
JOIN contracts c ON c.id = cc.contract_id
WHERE cc.chain_id = $1
@@ -6262,3 +6268,482 @@ async fn create_contract_from_definition(
Ok(contract.id)
}
+
+// =============================================================================
+// Chain Directives
+// =============================================================================
+
+/// Create a directive for a chain.
+pub async fn create_chain_directive(
+ pool: &PgPool,
+ chain_id: Uuid,
+ req: CreateChainDirectiveRequest,
+) -> Result<ChainDirective, sqlx::Error> {
+ let requirements = serde_json::to_value(&req.requirements.unwrap_or_default())
+ .unwrap_or(serde_json::json!([]));
+ let acceptance_criteria = serde_json::to_value(&req.acceptance_criteria.unwrap_or_default())
+ .unwrap_or(serde_json::json!([]));
+ let constraints =
+ serde_json::to_value(&req.constraints.unwrap_or_default()).unwrap_or(serde_json::json!([]));
+ let external_dependencies =
+ serde_json::to_value(&req.external_dependencies.unwrap_or_default())
+ .unwrap_or(serde_json::json!([]));
+ let source_type = req.source_type.unwrap_or_else(|| "llm_generated".to_string());
+
+ sqlx::query_as::<_, ChainDirective>(
+ r#"
+ INSERT INTO chain_directives (chain_id, requirements, acceptance_criteria, constraints, external_dependencies, source_type)
+ VALUES ($1, $2, $3, $4, $5, $6)
+ RETURNING *
+ "#,
+ )
+ .bind(chain_id)
+ .bind(&requirements)
+ .bind(&acceptance_criteria)
+ .bind(&constraints)
+ .bind(&external_dependencies)
+ .bind(&source_type)
+ .fetch_one(pool)
+ .await
+}
+
+/// Get the directive for a chain.
+pub async fn get_chain_directive(
+ pool: &PgPool,
+ chain_id: Uuid,
+) -> Result<Option<ChainDirective>, sqlx::Error> {
+ sqlx::query_as::<_, ChainDirective>(
+ r#"
+ SELECT *
+ FROM chain_directives
+ WHERE chain_id = $1
+ "#,
+ )
+ .bind(chain_id)
+ .fetch_optional(pool)
+ .await
+}
+
+/// Update a chain directive.
+pub async fn update_chain_directive(
+ pool: &PgPool,
+ chain_id: Uuid,
+ req: CreateChainDirectiveRequest,
+) -> Result<ChainDirective, sqlx::Error> {
+ let requirements = req
+ .requirements
+ .map(|r| serde_json::to_value(&r).unwrap_or(serde_json::json!([])));
+ let acceptance_criteria = req
+ .acceptance_criteria
+ .map(|ac| serde_json::to_value(&ac).unwrap_or(serde_json::json!([])));
+ let constraints = req
+ .constraints
+ .map(|c| serde_json::to_value(&c).unwrap_or(serde_json::json!([])));
+ let external_dependencies = req
+ .external_dependencies
+ .map(|ed| serde_json::to_value(&ed).unwrap_or(serde_json::json!([])));
+
+ sqlx::query_as::<_, ChainDirective>(
+ r#"
+ UPDATE chain_directives SET
+ requirements = COALESCE($2, requirements),
+ acceptance_criteria = COALESCE($3, acceptance_criteria),
+ constraints = COALESCE($4, constraints),
+ external_dependencies = COALESCE($5, external_dependencies),
+ source_type = COALESCE($6, source_type),
+ version = version + 1,
+ updated_at = NOW()
+ WHERE chain_id = $1
+ RETURNING *
+ "#,
+ )
+ .bind(chain_id)
+ .bind(&requirements)
+ .bind(&acceptance_criteria)
+ .bind(&constraints)
+ .bind(&external_dependencies)
+ .bind(&req.source_type)
+ .fetch_one(pool)
+ .await
+}
+
+/// Delete a chain directive.
+pub async fn delete_chain_directive(pool: &PgPool, chain_id: Uuid) -> Result<bool, sqlx::Error> {
+ let result = sqlx::query("DELETE FROM chain_directives WHERE chain_id = $1")
+ .bind(chain_id)
+ .execute(pool)
+ .await?;
+ Ok(result.rows_affected() > 0)
+}
+
+/// Get directive traceability (requirement -> contract mapping).
+pub async fn get_directive_traceability(
+ pool: &PgPool,
+ chain_id: Uuid,
+) -> Result<DirectiveTraceabilityResponse, sqlx::Error> {
+ // Get the directive
+ let directive = get_chain_directive(pool, chain_id).await?;
+
+ // Get all contract definitions with their requirement mappings
+ let definitions = list_chain_contract_definitions(pool, chain_id).await?;
+
+ // Parse requirements from directive
+ let requirements: Vec<super::models::DirectiveRequirement> = directive
+ .as_ref()
+ .and_then(|d| serde_json::from_value(d.requirements.clone()).ok())
+ .unwrap_or_default();
+
+ // Build traceability entries
+ let mut entries: Vec<TraceabilityEntry> = Vec::new();
+ let mut covered_requirements: std::collections::HashSet<String> =
+ std::collections::HashSet::new();
+
+ for req in &requirements {
+ let mut contract_def_ids: Vec<Uuid> = Vec::new();
+ let mut contract_def_names: Vec<String> = Vec::new();
+
+ for def in &definitions {
+ if def.requirement_ids.contains(&req.id) {
+ contract_def_ids.push(def.id);
+ contract_def_names.push(def.name.clone());
+ covered_requirements.insert(req.id.clone());
+ }
+ }
+
+ // Get acceptance criteria for this requirement
+ let acceptance_criteria: Vec<super::models::DirectiveAcceptanceCriterion> = directive
+ .as_ref()
+ .and_then(|d| serde_json::from_value(d.acceptance_criteria.clone()).ok())
+ .unwrap_or_default();
+
+ let ac_ids: Vec<String> = acceptance_criteria
+ .iter()
+ .filter(|ac| ac.requirement_ids.contains(&req.id))
+ .map(|ac| ac.id.clone())
+ .collect();
+
+ entries.push(TraceabilityEntry {
+ requirement_id: req.id.clone(),
+ requirement_title: req.title.clone(),
+ contract_definition_ids: contract_def_ids,
+ contract_definition_names: contract_def_names,
+ acceptance_criteria_ids: ac_ids,
+ });
+ }
+
+ // Find uncovered requirements
+ let uncovered: Vec<String> = requirements
+ .iter()
+ .filter(|r| !covered_requirements.contains(&r.id))
+ .map(|r| r.id.clone())
+ .collect();
+
+ Ok(DirectiveTraceabilityResponse {
+ chain_id,
+ entries,
+ uncovered_requirements: uncovered,
+ })
+}
+
+// =============================================================================
+// Contract Evaluations
+// =============================================================================
+
+/// Create a contract evaluation record.
+pub async fn create_contract_evaluation(
+ pool: &PgPool,
+ req: CreateContractEvaluationRequest,
+) -> Result<ContractEvaluation, sqlx::Error> {
+ let criteria_results = serde_json::to_value(&req.criteria_results).unwrap_or(serde_json::json!([]));
+
+ sqlx::query_as::<_, ContractEvaluation>(
+ r#"
+ INSERT INTO contract_evaluations (
+ contract_id, chain_id, chain_contract_id,
+ evaluator_model, passed, overall_score,
+ criteria_results, summary_feedback, rework_instructions,
+ completed_at
+ )
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW())
+ RETURNING *
+ "#,
+ )
+ .bind(req.contract_id)
+ .bind(req.chain_id)
+ .bind(req.chain_contract_id)
+ .bind(&req.evaluator_model)
+ .bind(req.passed)
+ .bind(req.overall_score)
+ .bind(&criteria_results)
+ .bind(&req.summary_feedback)
+ .bind(&req.rework_instructions)
+ .fetch_one(pool)
+ .await
+}
+
+/// Get a contract evaluation by ID.
+pub async fn get_contract_evaluation(
+ pool: &PgPool,
+ id: Uuid,
+) -> Result<Option<ContractEvaluation>, sqlx::Error> {
+ sqlx::query_as::<_, ContractEvaluation>(
+ r#"
+ SELECT *
+ FROM contract_evaluations
+ WHERE id = $1
+ "#,
+ )
+ .bind(id)
+ .fetch_optional(pool)
+ .await
+}
+
+/// List evaluations for a contract.
+pub async fn list_contract_evaluations(
+ pool: &PgPool,
+ contract_id: Uuid,
+) -> Result<Vec<ContractEvaluationSummary>, sqlx::Error> {
+ sqlx::query_as::<_, ContractEvaluationSummary>(
+ r#"
+ SELECT id, contract_id, evaluation_number, passed, overall_score, summary_feedback, created_at
+ FROM contract_evaluations
+ WHERE contract_id = $1
+ ORDER BY evaluation_number DESC
+ "#,
+ )
+ .bind(contract_id)
+ .fetch_all(pool)
+ .await
+}
+
+/// List evaluations for a chain.
+pub async fn list_chain_evaluations(
+ pool: &PgPool,
+ chain_id: Uuid,
+) -> Result<Vec<ContractEvaluationSummary>, sqlx::Error> {
+ sqlx::query_as::<_, ContractEvaluationSummary>(
+ r#"
+ SELECT id, contract_id, evaluation_number, passed, overall_score, summary_feedback, created_at
+ FROM contract_evaluations
+ WHERE chain_id = $1
+ ORDER BY created_at DESC
+ "#,
+ )
+ .bind(chain_id)
+ .fetch_all(pool)
+ .await
+}
+
+/// Get the latest evaluation for a chain contract.
+pub async fn get_latest_chain_contract_evaluation(
+ pool: &PgPool,
+ chain_contract_id: Uuid,
+) -> Result<Option<ContractEvaluation>, sqlx::Error> {
+ sqlx::query_as::<_, ContractEvaluation>(
+ r#"
+ SELECT *
+ FROM contract_evaluations
+ WHERE chain_contract_id = $1
+ ORDER BY evaluation_number DESC
+ LIMIT 1
+ "#,
+ )
+ .bind(chain_contract_id)
+ .fetch_optional(pool)
+ .await
+}
+
+/// Get the next evaluation number for a chain contract.
+pub async fn get_next_evaluation_number(
+ pool: &PgPool,
+ chain_contract_id: Uuid,
+) -> Result<i32, sqlx::Error> {
+ let result: Option<(i32,)> = sqlx::query_as(
+ r#"
+ SELECT COALESCE(MAX(evaluation_number), 0) + 1 as next_number
+ FROM contract_evaluations
+ WHERE chain_contract_id = $1
+ "#,
+ )
+ .bind(chain_contract_id)
+ .fetch_optional(pool)
+ .await?;
+
+ Ok(result.map(|(n,)| n).unwrap_or(1))
+}
+
+/// Update chain contract evaluation status.
+pub async fn update_chain_contract_evaluation_status(
+ pool: &PgPool,
+ chain_contract_id: Uuid,
+ status: &str,
+ evaluation_id: Option<Uuid>,
+ rework_feedback: Option<&str>,
+) -> Result<ChainContract, sqlx::Error> {
+ sqlx::query_as::<_, ChainContract>(
+ r#"
+ UPDATE chain_contracts SET
+ evaluation_status = $2,
+ last_evaluation_id = COALESCE($3, last_evaluation_id),
+ rework_feedback = COALESCE($4, rework_feedback),
+ evaluation_retry_count = CASE
+ WHEN $2 = 'rework' THEN evaluation_retry_count + 1
+ ELSE evaluation_retry_count
+ END,
+ rework_started_at = CASE
+ WHEN $2 = 'rework' THEN NOW()
+ ELSE rework_started_at
+ END
+ WHERE id = $1
+ RETURNING *
+ "#,
+ )
+ .bind(chain_contract_id)
+ .bind(status)
+ .bind(evaluation_id)
+ .bind(rework_feedback)
+ .fetch_one(pool)
+ .await
+}
+
+/// Mark a chain contract's original completion time (before rework).
+pub async fn mark_chain_contract_original_completion(
+ pool: &PgPool,
+ chain_contract_id: Uuid,
+) -> Result<(), sqlx::Error> {
+ sqlx::query(
+ r#"
+ UPDATE chain_contracts SET
+ original_completion_at = COALESCE(original_completion_at, NOW())
+ WHERE id = $1
+ "#,
+ )
+ .bind(chain_contract_id)
+ .execute(pool)
+ .await?;
+ Ok(())
+}
+
+/// Get chain contract by contract ID.
+pub async fn get_chain_contract_by_contract_id(
+ pool: &PgPool,
+ contract_id: Uuid,
+) -> Result<Option<ChainContract>, sqlx::Error> {
+ sqlx::query_as::<_, ChainContract>(
+ r#"
+ SELECT *
+ FROM chain_contracts
+ WHERE contract_id = $1
+ "#,
+ )
+ .bind(contract_id)
+ .fetch_optional(pool)
+ .await
+}
+
+// =============================================================================
+// Init Chain (Directive-Driven Chain Creation)
+// =============================================================================
+
+/// Initialize a directive-driven chain.
+/// Creates a directive contract and an empty chain linked to it.
+pub async fn init_chain_for_owner(
+ pool: &PgPool,
+ owner_id: Uuid,
+ req: InitChainRequest,
+) -> Result<InitChainResponse, sqlx::Error> {
+ // Create the directive contract
+ // Note: "directive" contract type uses the "specification" phases by default
+ let contract_req = CreateContractRequest {
+ name: format!("Directive: {}", truncate_string(&req.goal, 50)),
+ description: Some(req.goal.clone()),
+ contract_type: Some("specification".to_string()), // Directive uses spec workflow
+ template_id: None,
+ initial_phase: Some("research".to_string()),
+ phase_guard: Some(req.phase_guard),
+ autonomous_loop: Some(false),
+ local_only: Some(false),
+ auto_merge_local: Some(false),
+ };
+
+ let contract = create_contract_for_owner(pool, owner_id, contract_req).await?;
+
+ // Mark it as a chain directive
+ sqlx::query("UPDATE contracts SET is_chain_directive = true WHERE id = $1")
+ .bind(contract.id)
+ .execute(pool)
+ .await?;
+
+ // Build repositories list from request
+ let repositories = match (req.repository_url.as_ref(), req.local_path.as_ref()) {
+ (Some(url), _) => Some(vec![AddChainRepositoryRequest {
+ name: "Primary".to_string(),
+ repository_url: Some(url.clone()),
+ local_path: None,
+ source_type: "remote".to_string(),
+ is_primary: true,
+ }]),
+ (None, Some(path)) => Some(vec![AddChainRepositoryRequest {
+ name: "Primary".to_string(),
+ repository_url: None,
+ local_path: Some(path.clone()),
+ source_type: "local".to_string(),
+ is_primary: true,
+ }]),
+ (None, None) => None,
+ };
+
+ // Create the chain with directive contract reference
+ let chain_req = CreateChainRequest {
+ name: truncate_string(&req.goal, 100),
+ description: Some(req.goal),
+ repositories,
+ loop_enabled: Some(false),
+ loop_max_iterations: None,
+ loop_progress_check: None,
+ contracts: None,
+ };
+
+ let chain = create_chain_for_owner(pool, owner_id, chain_req).await?;
+
+ // Link directive contract to chain
+ sqlx::query(
+ r#"
+ UPDATE chains SET directive_contract_id = $2 WHERE id = $1;
+ UPDATE contracts SET spawned_chain_id = $1 WHERE id = $2;
+ "#,
+ )
+ .bind(chain.id)
+ .bind(contract.id)
+ .execute(pool)
+ .await?;
+
+ // Create empty directive document
+ create_chain_directive(
+ pool,
+ chain.id,
+ CreateChainDirectiveRequest {
+ requirements: Some(vec![]),
+ acceptance_criteria: Some(vec![]),
+ constraints: Some(vec![]),
+ external_dependencies: Some(vec![]),
+ source_type: Some("llm_generated".to_string()),
+ },
+ )
+ .await?;
+
+ Ok(InitChainResponse {
+ chain_id: chain.id,
+ directive_contract_id: contract.id,
+ supervisor_task_id: contract.supervisor_task_id,
+ })
+}
+
+/// Helper to truncate string to max length
+fn truncate_string(s: &str, max_len: usize) -> String {
+ if s.len() <= max_len {
+ s.to_string()
+ } else {
+ format!("{}...", &s[..max_len - 3])
+ }
+}