summaryrefslogtreecommitdiff
path: root/makima/src/db/models.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/db/models.rs')
-rw-r--r--makima/src/db/models.rs213
1 files changed, 193 insertions, 20 deletions
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index 9e624c9..2eeba87 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -1114,6 +1114,108 @@ pub struct MergeCompleteCheckResponse {
}
// =============================================================================
+// Contract Type Templates (User-defined)
+// =============================================================================
+
+/// A phase definition within a contract template
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct PhaseDefinition {
+ /// Phase identifier (e.g., "research", "plan", "execute")
+ pub id: String,
+ /// Display name for the phase
+ pub name: String,
+ /// Order in the workflow (0-indexed)
+ pub order: i32,
+}
+
+/// A deliverable definition within a phase
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DeliverableDefinition {
+ /// Deliverable identifier (e.g., "plan-document", "pull-request")
+ pub id: String,
+ /// Display name for the deliverable
+ pub name: String,
+ /// Priority: "required", "recommended", or "optional"
+ #[serde(default = "default_priority")]
+ pub priority: String,
+}
+
+fn default_priority() -> String {
+ "required".to_string()
+}
+
+/// Phase configuration stored on a contract (copied from template at creation)
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct PhaseConfig {
+ /// Ordered list of phases in the workflow
+ pub phases: Vec<PhaseDefinition>,
+ /// Default starting phase
+ pub default_phase: String,
+ /// Deliverables per phase: { "phase_id": [deliverables] }
+ #[serde(default)]
+ pub deliverables: std::collections::HashMap<String, Vec<DeliverableDefinition>>,
+}
+
+/// Contract type template record from the database
+#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractTypeTemplateRecord {
+ pub id: Uuid,
+ pub owner_id: Uuid,
+ pub name: String,
+ pub description: Option<String>,
+ #[sqlx(json)]
+ pub phases: Vec<PhaseDefinition>,
+ pub default_phase: String,
+ #[sqlx(json)]
+ pub deliverables: Option<std::collections::HashMap<String, Vec<DeliverableDefinition>>>,
+ pub version: i32,
+ pub created_at: DateTime<Utc>,
+ pub updated_at: DateTime<Utc>,
+}
+
+/// Request to create a new contract type template
+#[derive(Debug, Clone, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct CreateTemplateRequest {
+ pub name: String,
+ pub description: Option<String>,
+ pub phases: Vec<PhaseDefinition>,
+ pub default_phase: String,
+ pub deliverables: Option<std::collections::HashMap<String, Vec<DeliverableDefinition>>>,
+}
+
+/// Request to update a contract type template
+#[derive(Debug, Clone, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct UpdateTemplateRequest {
+ pub name: Option<String>,
+ pub description: Option<String>,
+ pub phases: Option<Vec<PhaseDefinition>>,
+ pub default_phase: Option<String>,
+ pub deliverables: Option<std::collections::HashMap<String, Vec<DeliverableDefinition>>>,
+ /// Version for optimistic locking
+ pub version: Option<i32>,
+}
+
+/// Summary of a contract type template for list views
+#[derive(Debug, Clone, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractTypeTemplateSummary {
+ pub id: Uuid,
+ pub name: String,
+ pub description: Option<String>,
+ pub phases: Vec<PhaseDefinition>,
+ pub default_phase: String,
+ pub is_builtin: bool,
+ pub version: i32,
+ pub created_at: DateTime<Utc>,
+}
+
+// =============================================================================
// Contract Types
// =============================================================================
@@ -1355,6 +1457,11 @@ pub struct Contract {
/// when evaluating task outputs.
#[serde(skip_serializing_if = "Option::is_none")]
pub red_team_prompt: Option<String>,
+ /// Phase configuration copied from template at contract creation.
+ /// When present, this overrides the built-in contract type phases.
+ #[sqlx(json)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub phase_config: Option<PhaseConfig>,
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
@@ -1376,37 +1483,96 @@ impl Contract {
self.status.parse()
}
- /// Get valid phases for this contract type
- pub fn valid_phases(&self) -> Vec<ContractPhase> {
+ /// Get valid phase IDs for this contract (as strings)
+ pub fn valid_phase_ids(&self) -> Vec<String> {
+ // Check phase_config first (for custom templates)
+ if let Some(ref config) = self.phase_config {
+ let mut phases: Vec<_> = config.phases.iter().collect();
+ phases.sort_by_key(|p| p.order);
+ return phases.iter().map(|p| p.id.clone()).collect();
+ }
+
+ // Fall back to built-in contract types
match self.contract_type.as_str() {
- "simple" => vec![ContractPhase::Plan, ContractPhase::Execute],
+ "simple" => vec!["plan".to_string(), "execute".to_string()],
"specification" => vec![
- ContractPhase::Research,
- ContractPhase::Specify,
- ContractPhase::Plan,
- ContractPhase::Execute,
- ContractPhase::Review,
+ "research".to_string(),
+ "specify".to_string(),
+ "plan".to_string(),
+ "execute".to_string(),
+ "review".to_string(),
],
- "execute" => vec![ContractPhase::Execute], // Execute-only, single phase
- _ => vec![ContractPhase::Plan, ContractPhase::Execute], // Default to simple
+ "execute" => vec!["execute".to_string()],
+ _ => vec!["plan".to_string(), "execute".to_string()],
+ }
+ }
+
+ /// Get valid phases for this contract type (as ContractPhase enums)
+ /// Note: For custom templates with non-standard phases, this only returns
+ /// phases that map to the ContractPhase enum.
+ pub fn valid_phases(&self) -> Vec<ContractPhase> {
+ self.valid_phase_ids()
+ .iter()
+ .filter_map(|id| id.parse::<ContractPhase>().ok())
+ .collect()
+ }
+
+ /// Get the initial phase ID for this contract type (as string)
+ pub fn initial_phase_id(&self) -> String {
+ // Check phase_config first (for custom templates)
+ if let Some(ref config) = self.phase_config {
+ return config.default_phase.clone();
+ }
+
+ // Fall back to built-in contract types
+ match self.contract_type.as_str() {
+ "specification" => "research".to_string(),
+ "execute" => "execute".to_string(),
+ _ => "plan".to_string(),
}
}
- /// Get the initial phase for this contract type
+ /// Get the initial phase for this contract type (as ContractPhase enum)
pub fn initial_phase(&self) -> ContractPhase {
+ self.initial_phase_id()
+ .parse()
+ .unwrap_or(ContractPhase::Plan)
+ }
+
+ /// Get the terminal phase ID for this contract type (as string)
+ pub fn terminal_phase_id(&self) -> String {
+ // Check phase_config first (for custom templates)
+ if let Some(ref config) = self.phase_config {
+ // Last phase in sorted order is the terminal phase
+ let mut phases: Vec<_> = config.phases.iter().collect();
+ phases.sort_by_key(|p| p.order);
+ if let Some(last) = phases.last() {
+ return last.id.clone();
+ }
+ }
+
+ // Fall back to built-in contract types
match self.contract_type.as_str() {
- "specification" => ContractPhase::Research,
- "execute" => ContractPhase::Execute,
- _ => ContractPhase::Plan, // simple and default
+ "specification" => "review".to_string(),
+ _ => "execute".to_string(),
}
}
/// Get the terminal phase for this contract type (phase where contract can be completed)
pub fn terminal_phase(&self) -> ContractPhase {
- match self.contract_type.as_str() {
- "specification" => ContractPhase::Review,
- _ => ContractPhase::Execute, // simple and execute both end at execute
- }
+ self.terminal_phase_id()
+ .parse()
+ .unwrap_or(ContractPhase::Execute)
+ }
+
+ /// Check if a phase ID is valid for this contract
+ pub fn is_valid_phase(&self, phase_id: &str) -> bool {
+ self.valid_phase_ids().contains(&phase_id.to_string())
+ }
+
+ /// Get the phase configuration for custom templates
+ pub fn get_phase_config(&self) -> Option<&PhaseConfig> {
+ self.phase_config.as_ref()
}
/// Get completed deliverable IDs for a specific phase
@@ -1507,12 +1673,19 @@ pub struct CreateContractRequest {
pub name: String,
/// Optional description
pub description: Option<String>,
- /// Contract type: "simple" (default) or "specification"
+ /// Contract type: "simple" (default), "specification", "execute", or a custom template name.
+ /// For built-in types:
/// - simple: Plan -> Execute workflow
/// - specification: Research -> Specify -> Plan -> Execute -> Review
+ /// - execute: Execute only
+ /// For custom templates, use the template name or provide template_id.
#[serde(default)]
pub contract_type: Option<String>,
- /// Initial phase to start in (defaults based on contract_type)
+ /// UUID of a custom template to use. If provided, this takes precedence over contract_type.
+ /// The template's phase configuration will be copied to the contract.
+ #[serde(default)]
+ pub template_id: Option<Uuid>,
+ /// Initial phase to start in (defaults based on contract_type or template)
/// - simple: defaults to "plan"
/// - specification: defaults to "research"
#[serde(default)]