//! Database models for the files table.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use utoipa::ToSchema;
use uuid::Uuid;
/// Default max retries for task daemon failover (3 attempts)
#[allow(dead_code)]
fn default_max_retries() -> i32 {
3
}
/// Flexible datetime deserialization module.
/// Accepts both date-only ("2026-01-15") and full ISO 8601 datetime ("2026-01-15T00:00:00Z") formats.
pub mod flexible_datetime {
use chrono::{DateTime, NaiveDate, NaiveTime, TimeZone, Utc};
use serde::{self, Deserialize, Deserializer};
/// Deserializes a datetime from either date-only or full datetime format.
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: Deserializer<'de>,
{
let s: Option<String> = Option::deserialize(deserializer)?;
match s {
None => Ok(None),
Some(s) if s.is_empty() => Ok(None),
Some(s) => {
// Try full datetime first (RFC 3339 / ISO 8601)
if let Ok(dt) = DateTime::parse_from_rfc3339(&s) {
return Ok(Some(dt.with_timezone(&Utc)));
}
// Try date-only format (YYYY-MM-DD) and convert to start of day UTC
if let Ok(date) = NaiveDate::parse_from_str(&s, "%Y-%m-%d") {
let datetime = date.and_time(NaiveTime::MIN);
return Ok(Some(Utc.from_utc_datetime(&datetime)));
}
Err(serde::de::Error::custom(format!(
"Invalid datetime format '{}'. Expected ISO 8601 datetime (e.g., '2026-01-15T00:00:00Z') or date (e.g., '2026-01-15')",
s
)))
}
}
}
}
/// TranscriptEntry stored in JSONB - matches frontend TranscriptEntry
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TranscriptEntry {
pub id: String,
pub speaker: String,
pub start: f32,
pub end: f32,
pub text: String,
pub is_final: bool,
}
/// Chart type for visualization elements
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ChartType {
Line,
Bar,
Pie,
Area,
}
/// Body element types for structured file content
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum BodyElement {
/// Heading element (h1-h6)
Heading { level: u8, text: String },
/// Paragraph text
Paragraph { text: String },
/// Code block with optional language
Code {
language: Option<String>,
content: String,
},
/// List (ordered or unordered)
List {
ordered: bool,
items: Vec<String>,
},
/// Chart visualization
Chart {
#[serde(rename = "chartType")]
chart_type: ChartType,
title: Option<String>,
data: serde_json::Value,
config: Option<serde_json::Value>,
},
/// Image element (deferred for MVP)
Image {
src: String,
alt: Option<String>,
caption: Option<String>,
},
/// Raw markdown content - renders as formatted markdown, edits as raw text
Markdown { content: String },
}
/// File record from the database.
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct File {
pub id: Uuid,
pub owner_id: Uuid,
/// Contract this file belongs to (optional)
pub contract_id: Option<Uuid>,
/// Phase of the contract when file was added (e.g., "research", "specify")
pub contract_phase: Option<String>,
pub name: String,
pub description: Option<String>,
#[sqlx(json)]
pub transcript: Vec<TranscriptEntry>,
pub location: Option<String>,
/// AI-generated summary of the transcript
pub summary: Option<String>,
/// Structured body content (headings, paragraphs, charts)
#[sqlx(json)]
pub body: Vec<BodyElement>,
/// Version number for optimistic locking
pub version: i32,
/// Path to linked repository file (e.g., "README.md", "docs/design.md")
pub repo_file_path: Option<String>,
/// When the file was last synced from the repository
pub repo_synced_at: Option<DateTime<Utc>>,
/// Sync status: 'none', 'synced', 'modified', 'conflict'
pub repo_sync_status: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
/// Request payload for creating a new file.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateFileRequest {
/// Contract this file belongs to (required - files must belong to a contract)
pub contract_id: Uuid,
/// Name of the file (auto-generated if not provided)
pub name: Option<String>,
/// Optional description
pub description: Option<String>,
/// Transcript entries (default to empty)
#[serde(default)]
pub transcript: Vec<TranscriptEntry>,
/// Storage location (e.g., s3://bucket/path) - not used yet
pub location: Option<String>,
/// Initial body elements (e.g., from a template)
#[serde(default)]
pub body: Vec<BodyElement>,
/// Path to linked repository file (e.g., "README.md")
pub repo_file_path: Option<String>,
/// Contract phase this file belongs to (for deliverable tracking)
pub contract_phase: Option<String>,
}
/// Request payload for updating an existing file.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateFileRequest {
/// New name (optional)
pub name: Option<String>,
/// New description (optional)
pub description: Option<String>,
/// New transcript (optional)
pub transcript: Option<Vec<TranscriptEntry>>,
/// AI-generated summary (optional)
pub summary: Option<String>,
/// Structured body content (optional)
pub body: Option<Vec<BodyElement>>,
/// Version for optimistic locking (required for updates from frontend)
pub version: Option<i32>,
/// Path to linked repository file (e.g., "README.md")
pub repo_file_path: Option<String>,
}
/// Response for file list endpoint.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileListResponse {
pub files: Vec<FileSummary>,
pub total: i64,
}
/// Summary of a file for list views (excludes full transcript).
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileSummary {
pub id: Uuid,
/// Contract this file belongs to
pub contract_id: Option<Uuid>,
/// Contract name (joined from contracts table)
pub contract_name: Option<String>,
/// Phase when file was added to contract
pub contract_phase: Option<String>,
pub name: String,
pub description: Option<String>,
pub transcript_count: usize,
/// Duration derived from last transcript end time
pub duration: Option<f32>,
/// Version number for optimistic locking
pub version: i32,
/// Path to linked repository file
pub repo_file_path: Option<String>,
/// Sync status with repository
pub repo_sync_status: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl From<File> for FileSummary {
fn from(file: File) -> Self {
let duration = file
.transcript
.iter()
.map(|t| t.end)
.fold(0.0_f32, f32::max);
Self {
id: file.id,
contract_id: file.contract_id,
contract_name: None, // Not available from File alone, requires JOIN
contract_phase: file.contract_phase,
name: file.name,
description: file.description,
transcript_count: file.transcript.len(),
duration: if duration > 0.0 { Some(duration) } else { None },
version: file.version,
repo_file_path: file.repo_file_path,
repo_sync_status: file.repo_sync_status,
created_at: file.created_at,
updated_at: file.updated_at,
}
}
}
// =============================================================================
// Version History Types
// =============================================================================
/// Source of a version change
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, sqlx::Type)]
#[sqlx(type_name = "varchar")]
#[serde(rename_all = "lowercase")]
pub enum VersionSource {
#[sqlx(rename = "user")]
User,
#[sqlx(rename = "llm")]
Llm,
#[sqlx(rename = "system")]
System,
}
impl std::fmt::Display for VersionSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
VersionSource::User => write!(f, "user"),
VersionSource::Llm => write!(f, "llm"),
VersionSource::System => write!(f, "system"),
}
}
}
impl std::str::FromStr for VersionSource {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"user" => Ok(VersionSource::User),
"llm" => Ok(VersionSource::Llm),
"system" => Ok(VersionSource::System),
_ => Err(format!("Unknown version source: {}", s)),
}
}
}
/// Full version record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileVersion {
pub id: Uuid,
pub file_id: Uuid,
pub version: i32,
pub name: String,
pub description: Option<String>,
pub summary: Option<String>,
#[sqlx(json)]
pub body: Vec<BodyElement>,
pub source: String,
pub change_description: Option<String>,
pub created_at: DateTime<Utc>,
}
/// Summary of a version for list views
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileVersionSummary {
pub version: i32,
pub source: String,
pub created_at: DateTime<Utc>,
pub change_description: Option<String>,
}
impl From<FileVersion> for FileVersionSummary {
fn from(v: FileVersion) -> Self {
Self {
version: v.version,
source: v.source,
created_at: v.created_at,
change_description: v.change_description,
}
}
}
/// Response for version list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileVersionListResponse {
pub versions: Vec<FileVersionSummary>,
pub total: i64,
}
/// Request to restore a file to a previous version
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RestoreVersionRequest {
/// The version to restore to
pub target_version: i32,
/// The current version (for optimistic locking)
pub current_version: i32,
}
// =============================================================================
// Mesh/Task Types
// =============================================================================
/// Task status for orchestrating Claude Code instances
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum TaskStatus {
Pending,
Running,
Paused,
Blocked,
Done,
Failed,
Merged,
}
impl std::fmt::Display for TaskStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TaskStatus::Pending => write!(f, "pending"),
TaskStatus::Running => write!(f, "running"),
TaskStatus::Paused => write!(f, "paused"),
TaskStatus::Blocked => write!(f, "blocked"),
TaskStatus::Done => write!(f, "done"),
TaskStatus::Failed => write!(f, "failed"),
TaskStatus::Merged => write!(f, "merged"),
}
}
}
impl std::str::FromStr for TaskStatus {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"pending" => Ok(TaskStatus::Pending),
"running" => Ok(TaskStatus::Running),
"paused" => Ok(TaskStatus::Paused),
"blocked" => Ok(TaskStatus::Blocked),
"done" => Ok(TaskStatus::Done),
"failed" => Ok(TaskStatus::Failed),
"merged" => Ok(TaskStatus::Merged),
_ => Err(format!("Unknown task status: {}", s)),
}
}
}
/// Merge mode for task completion
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum MergeMode {
/// Create a PR for review
Pr,
/// Auto-merge to target branch
Auto,
/// Manual merge by user
Manual,
}
impl std::fmt::Display for MergeMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MergeMode::Pr => write!(f, "pr"),
MergeMode::Auto => write!(f, "auto"),
MergeMode::Manual => write!(f, "manual"),
}
}
}
impl std::str::FromStr for MergeMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"pr" => Ok(MergeMode::Pr),
"auto" => Ok(MergeMode::Auto),
"manual" => Ok(MergeMode::Manual),
_ => Err(format!("Unknown merge mode: {}", s)),
}
}
}
/// Task record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Task {
pub id: Uuid,
pub owner_id: Uuid,
/// Contract this task belongs to (required for new tasks)
pub contract_id: Option<Uuid>,
pub parent_task_id: Option<Uuid>,
/// Depth in task hierarchy (no longer constrained)
pub depth: i32,
pub name: String,
pub description: Option<String>,
pub status: String,
pub priority: i32,
pub plan: String,
// Supervisor flag
/// True for contract supervisor tasks. Only supervisors can spawn new tasks.
#[serde(default)]
pub is_supervisor: bool,
// Daemon/container info
pub daemon_id: Option<Uuid>,
pub container_id: Option<String>,
pub overlay_path: Option<String>,
// Repository info
pub repository_url: Option<String>,
pub base_branch: Option<String>,
pub target_branch: Option<String>,
// Merge settings
pub merge_mode: Option<String>,
pub pr_url: Option<String>,
// Completion action settings
/// Path to user's local repository (outside ~/.makima)
pub target_repo_path: Option<String>,
/// Action on completion: "none", "branch", "merge", "pr"
pub completion_action: Option<String>,
// Progress tracking
pub progress_summary: Option<String>,
pub last_output: Option<String>,
pub error_message: Option<String>,
// Git checkpoint tracking
/// Git commit SHA of the most recent checkpoint
#[serde(skip_serializing_if = "Option::is_none")]
pub last_checkpoint_sha: Option<String>,
/// Number of checkpoints created by this task
#[serde(default)]
pub checkpoint_count: i32,
/// Message from the most recent checkpoint
#[serde(skip_serializing_if = "Option::is_none")]
pub checkpoint_message: Option<String>,
// Conversation state for resumption
/// Saved conversation context for task/supervisor resumption
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_state: Option<serde_json::Value>,
// Daemon migration tracking
/// Previous daemon if task was migrated
#[serde(skip_serializing_if = "Option::is_none")]
pub migrated_from_daemon_id: Option<Uuid>,
/// Most recent daemon that worked on this task
#[serde(skip_serializing_if = "Option::is_none")]
pub last_active_daemon_id: Option<Uuid>,
// Timestamps
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>,
// Task continuation
/// Task ID to continue from (copy worktree from this task when starting).
/// Used for sequential subtask dependencies.
#[serde(skip_serializing_if = "Option::is_none")]
pub continue_from_task_id: Option<Uuid>,
/// Files to copy from parent task's worktree when starting.
#[serde(skip_serializing_if = "Option::is_none")]
pub copy_files: Option<serde_json::Value>,
// Retry tracking for daemon failover
/// Number of times this task has been retried after daemon failure
#[serde(default)]
pub retry_count: i32,
/// Maximum retry attempts before marking as permanently failed
#[serde(default = "default_max_retries")]
pub max_retries: i32,
/// Array of daemon IDs that have failed this task (excluded from retry)
#[serde(skip_serializing_if = "Option::is_none")]
pub failed_daemon_ids: Option<Vec<Uuid>>,
/// When the task was last interrupted due to daemon disconnect
#[serde(skip_serializing_if = "Option::is_none")]
pub interrupted_at: Option<DateTime<Utc>>,
// Task branching
/// Source task ID when this task was branched from another task's conversation.
/// Used to track the origin of "what if" explorations.
#[serde(skip_serializing_if = "Option::is_none")]
pub branched_from_task_id: Option<Uuid>,
// UI visibility
/// Whether this task is hidden from the UI (user dismissed it).
/// Standalone completed tasks can be dismissed by the user.
#[serde(default)]
pub hidden: bool,
// Directive association
/// Directive this task belongs to (for directive-driven tasks)
#[serde(skip_serializing_if = "Option::is_none")]
pub directive_id: Option<Uuid>,
/// Directive step this task executes
#[serde(skip_serializing_if = "Option::is_none")]
pub directive_step_id: Option<Uuid>,
}
impl Task {
/// Parse status string to TaskStatus enum
pub fn status_enum(&self) -> Result<TaskStatus, String> {
self.status.parse()
}
/// Parse merge_mode string to MergeMode enum
pub fn merge_mode_enum(&self) -> Option<Result<MergeMode, String>> {
self.merge_mode.as_ref().map(|s| s.parse())
}
}
/// Summary of a task for list views
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskSummary {
pub id: Uuid,
/// Contract this task belongs to
pub contract_id: Option<Uuid>,
/// Contract name (joined from contracts table)
pub contract_name: Option<String>,
/// Contract phase (joined from contracts table)
pub contract_phase: Option<String>,
/// Contract status (joined from contracts table): 'active', 'completed', 'archived'
pub contract_status: Option<String>,
pub parent_task_id: Option<Uuid>,
/// Depth in task hierarchy: 0=orchestrator (top-level), 1=subtask (max)
pub depth: i32,
pub name: String,
pub status: String,
pub priority: i32,
pub progress_summary: Option<String>,
pub subtask_count: i64,
pub version: i32,
/// True for contract supervisor tasks
#[serde(default)]
pub is_supervisor: bool,
/// Whether this task is hidden from the UI (user dismissed it)
#[serde(default)]
pub hidden: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
/// Convert a full Task to a TaskSummary
impl From<Task> for TaskSummary {
fn from(task: Task) -> Self {
Self {
id: task.id,
contract_id: task.contract_id,
contract_name: None, // Not available from Task directly
contract_phase: None, // Not available from Task directly
contract_status: None, // Not available from Task directly
parent_task_id: task.parent_task_id,
depth: task.depth,
name: task.name,
status: task.status,
priority: task.priority,
progress_summary: task.progress_summary,
subtask_count: 0, // Would need separate query
version: task.version,
is_supervisor: task.is_supervisor,
hidden: task.hidden,
created_at: task.created_at,
updated_at: task.updated_at,
}
}
}
/// Response for task list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskListResponse {
pub tasks: Vec<TaskSummary>,
pub total: i64,
}
/// Request payload for creating a new task
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateTaskRequest {
/// Contract this task belongs to (optional for branched/anonymous tasks)
pub contract_id: Option<Uuid>,
/// Name of the task
pub name: String,
/// Optional description
pub description: Option<String>,
/// The plan/instructions for Claude Code
pub plan: String,
/// Parent task ID (for subtasks)
pub parent_task_id: Option<Uuid>,
/// True for contract supervisor tasks. Only supervisors can spawn new tasks.
#[serde(default)]
pub is_supervisor: bool,
/// Priority (higher = more urgent)
#[serde(default)]
pub priority: i32,
/// Repository URL
pub repository_url: Option<String>,
/// Base branch for overlay
pub base_branch: Option<String>,
/// Target branch to merge into
pub target_branch: Option<String>,
/// Merge mode (pr, auto, manual)
pub merge_mode: Option<String>,
/// Path to user's local repository (outside ~/.makima)
pub target_repo_path: Option<String>,
/// Action on completion: "none", "branch", "merge", "pr"
pub completion_action: Option<String>,
/// Task ID to continue from (copy worktree from this task when starting)
pub continue_from_task_id: Option<Uuid>,
/// Files to copy from parent task's worktree when starting
#[serde(skip_serializing_if = "Option::is_none")]
pub copy_files: Option<Vec<String>>,
/// Checkpoint SHA to branch from (optional)
pub checkpoint_sha: Option<String>,
/// Source task ID when branching from another task's conversation
pub branched_from_task_id: Option<Uuid>,
/// Conversation history to initialize the task with (JSON array of messages)
pub conversation_history: Option<serde_json::Value>,
/// Task ID whose worktree this task shares. When set, this task reuses the supervisor's
/// worktree instead of creating its own, and should NOT have its worktree deleted during cleanup.
pub supervisor_worktree_task_id: Option<Uuid>,
/// Directive this task belongs to (for directive-driven tasks)
pub directive_id: Option<Uuid>,
/// Directive step this task executes
pub directive_step_id: Option<Uuid>,
}
/// Request payload for updating a task
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateTaskRequest {
pub name: Option<String>,
pub description: Option<String>,
pub plan: Option<String>,
pub status: Option<String>,
pub priority: Option<i32>,
pub progress_summary: Option<String>,
pub last_output: Option<String>,
pub error_message: Option<String>,
pub merge_mode: Option<String>,
pub pr_url: Option<String>,
/// Repository URL for the task (e.g., when updating supervisor with repo info)
pub repository_url: Option<String>,
/// Path to user's local repository (outside ~/.makima)
pub target_repo_path: Option<String>,
/// Action on completion: "none", "branch", "merge", "pr"
pub completion_action: Option<String>,
/// The daemon currently running this task
pub daemon_id: Option<Uuid>,
/// Explicitly clear daemon_id (set to NULL)
#[serde(default)]
pub clear_daemon_id: bool,
/// Whether this task is hidden from the UI (user dismissed it)
pub hidden: Option<bool>,
/// Version for optimistic locking
pub version: Option<i32>,
}
/// Task with its subtasks for detail view
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskWithSubtasks {
#[serde(flatten)]
pub task: Task,
pub subtasks: Vec<TaskSummary>,
}
/// Request to send a message to a running task's stdin.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SendMessageRequest {
/// The message to send to the task's stdin.
pub message: String,
}
/// Default for include_conversation field in BranchTaskRequest
fn default_include_conversation() -> bool {
true
}
/// Request to branch a task from an existing task's conversation.
/// Creates a new anonymous task that continues from the source task's state.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct BranchTaskRequest {
/// The initial message/instructions for the branched task
pub message: String,
/// Optional name for the branched task (auto-generated if not provided)
pub name: Option<String>,
/// Whether to include conversation history from the source task (default: true)
#[serde(default = "default_include_conversation")]
pub include_conversation: bool,
}
/// Response from branching a task.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct BranchTaskResponse {
/// The newly created branched task
pub task: Task,
/// Number of conversation messages included from source task
pub message_count: usize,
/// Daemon ID if the task was started (None if no daemon available)
pub daemon_id: Option<Uuid>,
}
// =============================================================================
// Daemon Types
// =============================================================================
/// Daemon status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum DaemonStatus {
Connected,
Disconnected,
Unhealthy,
}
impl std::fmt::Display for DaemonStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DaemonStatus::Connected => write!(f, "connected"),
DaemonStatus::Disconnected => write!(f, "disconnected"),
DaemonStatus::Unhealthy => write!(f, "unhealthy"),
}
}
}
impl std::str::FromStr for DaemonStatus {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"connected" => Ok(DaemonStatus::Connected),
"disconnected" => Ok(DaemonStatus::Disconnected),
"unhealthy" => Ok(DaemonStatus::Unhealthy),
_ => Err(format!("Unknown daemon status: {}", s)),
}
}
}
/// Connected daemon record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Daemon {
pub id: Uuid,
pub owner_id: Uuid,
pub connection_id: String,
pub hostname: Option<String>,
pub machine_id: Option<String>,
pub max_concurrent_tasks: i32,
pub current_task_count: i32,
pub status: String,
pub last_heartbeat_at: DateTime<Utc>,
pub connected_at: DateTime<Utc>,
pub disconnected_at: Option<DateTime<Utc>>,
}
impl Daemon {
/// Parse status string to DaemonStatus enum
pub fn status_enum(&self) -> Result<DaemonStatus, String> {
self.status.parse()
}
}
/// Response for daemon list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DaemonListResponse {
pub daemons: Vec<Daemon>,
pub total: i64,
}
/// Response for daemon directories endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DaemonDirectoriesResponse {
/// List of suggested directories from connected daemons
pub directories: Vec<DaemonDirectory>,
}
/// A suggested directory from a daemon
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DaemonDirectory {
/// Path to the directory
pub path: String,
/// Display label for the directory
pub label: String,
/// Type of directory: "working", "makima", "worktrees"
pub directory_type: String,
/// Daemon hostname this directory is from
pub hostname: Option<String>,
/// Whether the directory already exists (for validation)
#[serde(skip_serializing_if = "Option::is_none")]
pub exists: Option<bool>,
}
// =============================================================================
// Task Event Types
// =============================================================================
/// Task event record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskEvent {
pub id: Uuid,
pub task_id: Uuid,
pub event_type: String,
pub previous_status: Option<String>,
pub new_status: Option<String>,
#[sqlx(json)]
pub event_data: Option<serde_json::Value>,
pub created_at: DateTime<Utc>,
}
/// Response for task events list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskEventListResponse {
pub events: Vec<TaskEvent>,
pub total: i64,
}
/// A single output entry from a Claude Code task
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskOutputEntry {
pub id: Uuid,
pub task_id: Uuid,
/// Message type: "assistant", "tool_use", "tool_result", "result", "system", "error", "raw"
pub message_type: String,
/// Main text content
pub content: String,
/// Tool name if tool_use message
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_name: Option<String>,
/// Tool input JSON if tool_use message
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_input: Option<serde_json::Value>,
/// Whether tool result was an error
#[serde(skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
/// Cost in USD if result message
#[serde(skip_serializing_if = "Option::is_none")]
pub cost_usd: Option<f64>,
/// Duration in ms if result message
#[serde(skip_serializing_if = "Option::is_none")]
pub duration_ms: Option<u64>,
/// Timestamp when this output was recorded
pub created_at: DateTime<Utc>,
}
impl TaskOutputEntry {
/// Convert a TaskEvent with event_type='output' to a TaskOutputEntry
pub fn from_task_event(event: TaskEvent) -> Option<Self> {
if event.event_type != "output" {
return None;
}
let data = event.event_data?;
Some(Self {
id: event.id,
task_id: event.task_id,
message_type: data.get("messageType")?.as_str()?.to_string(),
content: data.get("content")?.as_str().unwrap_or("").to_string(),
tool_name: data.get("toolName").and_then(|v| v.as_str()).map(|s| s.to_string()),
tool_input: data.get("toolInput").cloned(),
is_error: data.get("isError").and_then(|v| v.as_bool()),
cost_usd: data.get("costUsd").and_then(|v| v.as_f64()),
duration_ms: data.get("durationMs").and_then(|v| v.as_u64()),
created_at: event.created_at,
})
}
}
/// Response for task output history endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskOutputResponse {
pub entries: Vec<TaskOutputEntry>,
pub total: usize,
pub task_id: Uuid,
}
// =============================================================================
// Mesh Chat History Types
// =============================================================================
/// Mesh chat conversation for persisting history
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MeshChatConversation {
pub id: Uuid,
pub owner_id: Uuid,
pub name: Option<String>,
pub is_active: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
/// Individual message in a mesh chat conversation
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MeshChatMessageRecord {
pub id: Uuid,
pub conversation_id: Uuid,
pub role: String,
pub content: String,
pub context_type: String,
pub context_task_id: Option<Uuid>,
/// Tool calls made during this message (JSON, nullable)
pub tool_calls: Option<serde_json::Value>,
/// Pending questions requiring user response (JSON, nullable)
pub pending_questions: Option<serde_json::Value>,
pub created_at: DateTime<Utc>,
}
/// Response for chat history endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MeshChatHistoryResponse {
pub conversation_id: Uuid,
pub messages: Vec<MeshChatMessageRecord>,
}
// =============================================================================
// Contract Chat History Types
// =============================================================================
/// Conversation thread for contract chat (scoped to a specific contract)
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractChatConversation {
pub id: Uuid,
pub contract_id: Uuid,
pub owner_id: Uuid,
pub name: Option<String>,
pub is_active: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
/// Individual message in a contract chat conversation
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractChatMessageRecord {
pub id: Uuid,
pub conversation_id: Uuid,
pub role: String,
pub content: String,
/// Tool calls made during this message (JSON, nullable)
pub tool_calls: Option<serde_json::Value>,
/// Pending questions requiring user response (JSON, nullable)
pub pending_questions: Option<serde_json::Value>,
pub created_at: DateTime<Utc>,
}
/// Response for contract chat history endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractChatHistoryResponse {
pub contract_id: Uuid,
pub conversation_id: Uuid,
pub messages: Vec<ContractChatMessageRecord>,
}
// =============================================================================
// Merge API Types
// =============================================================================
/// Information about a task branch
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct BranchInfo {
/// Full branch name
pub name: String,
/// Task ID extracted from branch name (if parseable)
pub task_id: Option<Uuid>,
/// Whether this branch has been merged
pub is_merged: bool,
/// Short SHA of the last commit
pub last_commit: String,
/// Subject line of the last commit
pub last_commit_message: String,
}
/// Response for branch list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct BranchListResponse {
pub branches: Vec<BranchInfo>,
}
/// Request to start a merge
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeStartRequest {
/// Branch name to merge
pub source_branch: String,
}
/// Current merge state
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeStatusResponse {
/// Whether a merge is in progress
pub in_progress: bool,
/// Branch being merged (if in progress)
pub source_branch: Option<String>,
/// Files with unresolved conflicts
pub conflicted_files: Vec<String>,
}
/// Request to resolve a conflict
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeResolveRequest {
/// File path to resolve
pub file: String,
/// Resolution strategy: "ours" or "theirs"
pub strategy: String,
}
/// Request to commit a merge
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeCommitRequest {
/// Commit message
pub message: String,
}
/// Request to skip a subtask branch
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeSkipRequest {
/// Subtask ID to skip
pub subtask_id: Uuid,
/// Reason for skipping
pub reason: String,
}
/// Result of a merge operation
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeResultResponse {
/// Whether the operation succeeded
pub success: bool,
/// Human-readable message
pub message: String,
/// Commit SHA (if a commit was created)
pub commit_sha: Option<String>,
/// Conflicted files (if conflicts occurred)
pub conflicts: Option<Vec<String>>,
}
/// Response to check if all branches are merged
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeCompleteCheckResponse {
/// Whether the orchestrator can mark itself as complete
pub can_complete: bool,
/// Branches not yet merged or skipped
pub unmerged_branches: Vec<String>,
/// Count of merged branches
pub merged_count: u32,
/// Count of skipped branches
pub skipped_count: u32,
}
// =============================================================================
// 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
// =============================================================================
/// Contract type determines the workflow and required documents
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ContractType {
/// Simple Plan -> Execute workflow (default)
/// - Plan phase: requires a "Plan" document
/// - Execute phase: requires a "PR" document
Simple,
/// Specification-based development with TDD
/// - Research: requires "Research Notes" document
/// - Specify: requires "Requirements Document"
/// - Plan: requires "Plan" document
/// - Execute: requires "PR" document
/// - Review: requires "Release Notes" document
Specification,
/// Execute-only workflow with no deliverables
/// - Only has "execute" phase
/// - NO deliverables at all - just execute tasks directly
Execute,
}
impl Default for ContractType {
fn default() -> Self {
ContractType::Simple
}
}
impl std::fmt::Display for ContractType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ContractType::Simple => write!(f, "simple"),
ContractType::Specification => write!(f, "specification"),
ContractType::Execute => write!(f, "execute"),
}
}
}
impl std::str::FromStr for ContractType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"simple" => Ok(ContractType::Simple),
"specification" => Ok(ContractType::Specification),
"execute" => Ok(ContractType::Execute),
_ => Err(format!("Unknown contract type: {}", s)),
}
}
}
/// Contract phase for workflow progression
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ContractPhase {
Research,
Specify,
Plan,
Execute,
Review,
}
impl std::fmt::Display for ContractPhase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ContractPhase::Research => write!(f, "research"),
ContractPhase::Specify => write!(f, "specify"),
ContractPhase::Plan => write!(f, "plan"),
ContractPhase::Execute => write!(f, "execute"),
ContractPhase::Review => write!(f, "review"),
}
}
}
impl std::str::FromStr for ContractPhase {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"research" => Ok(ContractPhase::Research),
"specify" => Ok(ContractPhase::Specify),
"plan" => Ok(ContractPhase::Plan),
"execute" => Ok(ContractPhase::Execute),
"review" => Ok(ContractPhase::Review),
_ => Err(format!("Unknown contract phase: {}", s)),
}
}
}
/// Contract status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ContractStatus {
Active,
Completed,
Archived,
}
impl std::fmt::Display for ContractStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ContractStatus::Active => write!(f, "active"),
ContractStatus::Completed => write!(f, "completed"),
ContractStatus::Archived => write!(f, "archived"),
}
}
}
impl std::str::FromStr for ContractStatus {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"active" => Ok(ContractStatus::Active),
"completed" => Ok(ContractStatus::Completed),
"archived" => Ok(ContractStatus::Archived),
_ => Err(format!("Unknown contract status: {}", s)),
}
}
}
/// Repository source type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum RepositorySourceType {
/// Existing remote repo (GitHub, GitLab, etc)
Remote,
/// Existing local repo
Local,
/// New repo created/managed by Makima daemon
Managed,
}
impl std::fmt::Display for RepositorySourceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RepositorySourceType::Remote => write!(f, "remote"),
RepositorySourceType::Local => write!(f, "local"),
RepositorySourceType::Managed => write!(f, "managed"),
}
}
}
impl std::str::FromStr for RepositorySourceType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"remote" => Ok(RepositorySourceType::Remote),
"local" => Ok(RepositorySourceType::Local),
"managed" => Ok(RepositorySourceType::Managed),
_ => Err(format!("Unknown repository source type: {}", s)),
}
}
}
/// Repository status (for managed repos)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum RepositoryStatus {
/// Repo is usable
Ready,
/// Waiting for daemon to create
Pending,
/// Daemon is creating the repo
Creating,
/// Creation failed
Failed,
}
impl std::fmt::Display for RepositoryStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RepositoryStatus::Ready => write!(f, "ready"),
RepositoryStatus::Pending => write!(f, "pending"),
RepositoryStatus::Creating => write!(f, "creating"),
RepositoryStatus::Failed => write!(f, "failed"),
}
}
}
impl std::str::FromStr for RepositoryStatus {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"ready" => Ok(RepositoryStatus::Ready),
"pending" => Ok(RepositoryStatus::Pending),
"creating" => Ok(RepositoryStatus::Creating),
"failed" => Ok(RepositoryStatus::Failed),
_ => Err(format!("Unknown repository status: {}", s)),
}
}
}
/// Contract record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Contract {
pub id: Uuid,
pub owner_id: Uuid,
pub name: String,
pub description: Option<String>,
/// Contract type: "simple" or "specification"
pub contract_type: String,
pub phase: String,
pub status: String,
/// The long-running supervisor task that orchestrates this contract
#[serde(skip_serializing_if = "Option::is_none")]
pub supervisor_task_id: Option<Uuid>,
/// Whether tasks for this contract should run in autonomous loop mode.
/// When enabled, tasks will automatically restart with --continue if they exit
/// without a COMPLETION_GATE indicating ready: true.
#[serde(default)]
pub autonomous_loop: bool,
/// Whether to wait for user confirmation before progressing to the next phase.
/// When enabled, the supervisor will pause and ask the user to review and approve
/// phase outputs (like plans, requirements, etc.) before continuing.
#[serde(default)]
pub phase_guard: bool,
/// Completed deliverables per phase.
/// Structure: { "plan": ["plan-document"], "execute": ["pull-request"] }
#[sqlx(json)]
#[serde(default)]
pub completed_deliverables: serde_json::Value,
/// Whether this contract operates in local-only mode.
/// When enabled, automatic completion actions (branch, merge, pr) are skipped,
/// allowing users to manually handle code changes via patch files or other means.
#[serde(default)]
pub local_only: bool,
/// Whether to auto-merge to target branch locally when local_only mode is enabled.
/// When both local_only and auto_merge_local are true, completed task changes will be
/// automatically merged to the master/main branch locally (without pushing or creating PRs).
#[serde(default)]
pub auto_merge_local: bool,
/// Phase configuration copied from template at contract creation (raw JSON).
/// When present, this overrides the built-in contract type phases.
/// Use `get_phase_config()` to get the parsed PhaseConfig.
#[serde(skip_serializing_if = "Option::is_none")]
pub phase_config: Option<serde_json::Value>,
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl Contract {
/// Parse contract_type string to ContractType enum
pub fn contract_type_enum(&self) -> Result<ContractType, String> {
self.contract_type.parse()
}
/// Parse phase string to ContractPhase enum
pub fn phase_enum(&self) -> Result<ContractPhase, String> {
self.phase.parse()
}
/// Parse status string to ContractStatus enum
pub fn status_enum(&self) -> Result<ContractStatus, String> {
self.status.parse()
}
/// 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(config) = self.get_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!["plan".to_string(), "execute".to_string()],
"specification" => vec![
"research".to_string(),
"specify".to_string(),
"plan".to_string(),
"execute".to_string(),
"review".to_string(),
],
"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(config) = self.get_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 (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(config) = self.get_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" => "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 {
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()
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
/// Get completed deliverable IDs for a specific phase
pub fn get_completed_deliverables(&self, phase: &str) -> Vec<String> {
self.completed_deliverables
.get(phase)
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default()
}
/// Check if a specific deliverable is marked as complete for a phase
pub fn is_deliverable_complete(&self, phase: &str, deliverable_id: &str) -> bool {
self.get_completed_deliverables(phase)
.contains(&deliverable_id.to_string())
}
}
/// Contract repository record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractRepository {
pub id: Uuid,
pub contract_id: Uuid,
pub name: String,
pub repository_url: Option<String>,
pub local_path: Option<String>,
pub source_type: String,
pub status: String,
pub is_primary: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl ContractRepository {
/// Parse source_type string to RepositorySourceType enum
pub fn source_type_enum(&self) -> Result<RepositorySourceType, String> {
self.source_type.parse()
}
/// Parse status string to RepositoryStatus enum
pub fn status_enum(&self) -> Result<RepositoryStatus, String> {
self.status.parse()
}
}
/// Summary of a contract for list views
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractSummary {
pub id: Uuid,
pub name: String,
pub description: Option<String>,
/// Contract type: "simple" or "specification"
pub contract_type: String,
pub phase: String,
pub status: String,
/// Supervisor task ID for contract orchestration
pub supervisor_task_id: Option<Uuid>,
/// When true, tasks do not auto-execute completion actions and work stays in worktrees.
#[serde(default)]
pub local_only: bool,
/// When true with local_only, automatically merge completed tasks to target branch locally.
#[serde(default)]
pub auto_merge_local: bool,
pub file_count: i64,
pub task_count: i64,
pub repository_count: i64,
pub version: i32,
pub created_at: DateTime<Utc>,
}
/// Contract with all relations for detail view
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractWithRelations {
#[serde(flatten)]
pub contract: Contract,
pub repositories: Vec<ContractRepository>,
pub files: Vec<FileSummary>,
pub tasks: Vec<TaskSummary>,
}
/// Response for contract list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractListResponse {
pub contracts: Vec<ContractSummary>,
pub total: i64,
}
/// Request payload for creating a new contract
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateContractRequest {
/// Name of the contract
pub name: String,
/// Optional description
pub description: Option<String>,
/// 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>,
/// 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)]
pub initial_phase: Option<String>,
/// Enable autonomous loop mode for tasks in this contract.
/// When enabled, tasks automatically restart with --continue if they exit
/// without a COMPLETION_GATE indicating ready: true.
#[serde(default)]
pub autonomous_loop: Option<bool>,
/// Enable phase guard mode for this contract.
/// When enabled, the supervisor will pause and ask the user to review and approve
/// phase outputs before progressing to the next phase.
#[serde(default)]
pub phase_guard: Option<bool>,
/// Enable local-only mode for this contract.
/// When enabled, automatic completion actions (branch, merge, pr) are skipped,
/// allowing users to manually handle code changes via patch files or other means.
#[serde(default)]
pub local_only: Option<bool>,
/// Enable auto-merge to target branch locally when local_only mode is enabled.
/// When both local_only and auto_merge_local are true, completed task changes will be
/// automatically merged to the master/main branch locally (without pushing or creating PRs).
#[serde(default)]
pub auto_merge_local: Option<bool>,
}
/// Request payload for updating a contract
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateContractRequest {
pub name: Option<String>,
pub description: Option<String>,
pub phase: Option<String>,
pub status: Option<String>,
/// Supervisor task ID for contract orchestration
#[serde(skip_serializing_if = "Option::is_none")]
pub supervisor_task_id: Option<Uuid>,
/// Enable or disable autonomous loop mode for tasks in this contract.
#[serde(default)]
pub autonomous_loop: Option<bool>,
/// Enable or disable phase guard mode for this contract.
/// When enabled, the supervisor will pause and ask the user to review and approve
/// phase outputs before progressing to the next phase.
#[serde(default)]
pub phase_guard: Option<bool>,
/// Enable or disable local-only mode for this contract.
/// When enabled, automatic completion actions (branch, merge, pr) are skipped,
/// allowing users to manually handle code changes via patch files or other means.
#[serde(default)]
pub local_only: Option<bool>,
/// Enable or disable auto-merge to target branch locally when local_only mode is enabled.
/// When both local_only and auto_merge_local are true, completed task changes will be
/// automatically merged to the master/main branch locally (without pushing or creating PRs).
#[serde(default)]
pub auto_merge_local: Option<bool>,
/// Version for optimistic locking
pub version: Option<i32>,
}
/// Request to add a remote repository to a contract
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct AddRemoteRepositoryRequest {
pub name: String,
pub repository_url: String,
#[serde(default)]
pub is_primary: bool,
}
/// Request to add a local repository to a contract
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct AddLocalRepositoryRequest {
pub name: String,
pub local_path: String,
#[serde(default)]
pub is_primary: bool,
}
/// Request to create a managed repository (daemon will create it)
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateManagedRepositoryRequest {
pub name: String,
#[serde(default)]
pub is_primary: bool,
}
/// Request to change contract phase
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ChangePhaseRequest {
pub phase: String,
/// If phase_guard is enabled, this must be true to confirm the transition.
/// If not provided or false, returns phase deliverables for review.
#[serde(default)]
pub confirmed: Option<bool>,
/// User feedback for changes (used when not confirming)
#[serde(skip_serializing_if = "Option::is_none")]
pub feedback: Option<String>,
/// Expected version for optimistic locking. If provided, the phase change
/// will only succeed if the current contract version matches.
#[serde(skip_serializing_if = "Option::is_none")]
pub expected_version: Option<i32>,
}
/// Result of a phase change operation, supporting explicit conflict detection.
#[derive(Debug, Clone)]
pub enum PhaseChangeResult {
/// Phase change succeeded, returning the updated contract
Success(Contract),
/// Version conflict: the contract was modified concurrently
VersionConflict {
/// The version the client expected
expected: i32,
/// The actual current version in the database
actual: i32,
/// The current phase of the contract
current_phase: String,
},
/// Validation failed (e.g., invalid phase transition)
ValidationFailed {
/// Human-readable reason for the failure
reason: String,
/// List of missing requirements for the phase transition
missing_requirements: Vec<String>,
},
/// The caller is not authorized to change this contract's phase
Unauthorized,
/// The contract was not found
NotFound,
}
/// Response for phase transition when phase_guard is enabled
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PhaseTransitionRequest {
/// Current contract phase
pub current_phase: String,
/// Requested next phase
pub next_phase: String,
/// Summary of phase deliverables/outputs
pub deliverables_summary: String,
/// List of files created in this phase
pub phase_files: Vec<PhaseFileInfo>,
/// List of completed tasks in this phase
pub phase_tasks: Vec<PhaseTaskInfo>,
/// Whether user confirmation is required
pub requires_confirmation: bool,
/// Unique ID for tracking this transition request
pub transition_id: String,
}
/// File info for phase transition review
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PhaseFileInfo {
pub id: Uuid,
pub name: String,
pub description: Option<String>,
}
/// Task info for phase transition review
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PhaseTaskInfo {
pub id: Uuid,
pub name: String,
pub status: String,
}
/// Contract event record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractEvent {
pub id: Uuid,
pub contract_id: Uuid,
pub event_type: String,
pub previous_phase: Option<String>,
pub new_phase: Option<String>,
#[sqlx(json)]
pub event_data: Option<serde_json::Value>,
pub created_at: DateTime<Utc>,
}
/// Response for contract events list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractEventListResponse {
pub events: Vec<ContractEvent>,
pub total: i64,
}
// ============================================================================
// Task Checkpoints (for git checkpoint tracking)
// ============================================================================
/// Task checkpoint record - represents a git commit checkpoint
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskCheckpoint {
pub id: Uuid,
pub task_id: Uuid,
/// Sequential checkpoint number within this task
pub checkpoint_number: i32,
/// Git commit SHA
pub commit_sha: String,
/// Git branch name
pub branch_name: String,
/// Commit message
pub message: String,
/// Files changed in this commit: [{path, action: 'A'|'M'|'D'}]
#[sqlx(json)]
pub files_changed: Option<serde_json::Value>,
/// Lines added in this commit
pub lines_added: Option<i32>,
/// Lines removed in this commit
pub lines_removed: Option<i32>,
pub created_at: DateTime<Utc>,
}
/// Request to create a checkpoint
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateCheckpointRequest {
/// Commit message
pub message: String,
}
/// Response for checkpoint list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CheckpointListResponse {
pub checkpoints: Vec<TaskCheckpoint>,
pub total: i64,
}
// ============================================================================
// Supervisor State (for supervisor resumability)
// ============================================================================
/// Supervisor state for contract supervisor tasks
/// Enables resumption after interruption
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorState {
pub id: Uuid,
pub contract_id: Uuid,
pub task_id: Uuid,
/// Full Claude conversation history for resumption
#[sqlx(json)]
pub conversation_history: serde_json::Value,
/// Last checkpoint this supervisor created
pub last_checkpoint_id: Option<Uuid>,
/// Tasks the supervisor is waiting on
#[sqlx(try_from = "Vec<Uuid>")]
pub pending_task_ids: Vec<Uuid>,
/// Current contract phase when supervisor was last active
pub phase: String,
/// When supervisor was last active
pub last_activity: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
/// Current supervisor state (initializing, idle, working, waiting_for_user, etc.)
pub state: String,
/// Human-readable description of current activity
pub current_activity: Option<String>,
/// Progress percentage (0-100)
pub progress: i32,
/// Error message when state is failed or blocked
pub error_message: Option<String>,
/// Tasks spawned by this supervisor
#[sqlx(try_from = "Vec<Uuid>")]
pub spawned_task_ids: Vec<Uuid>,
/// Pending questions awaiting user response
#[sqlx(json)]
pub pending_questions: serde_json::Value,
/// Number of times this supervisor has been restored
pub restoration_count: i32,
/// Timestamp of last restoration
pub last_restored_at: Option<DateTime<Utc>>,
/// Source of last restoration (daemon_restart, task_reassignment, manual)
pub restoration_source: Option<String>,
}
/// Pending question structure for supervisor state
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PendingQuestion {
/// Unique question ID
pub id: Uuid,
/// The question text
pub question: String,
/// Optional choices (empty for free-form)
#[serde(default)]
pub choices: Vec<String>,
/// Optional context
pub context: Option<String>,
/// Question type: general, phase_confirmation, contract_complete
#[serde(default = "default_question_type")]
pub question_type: String,
/// When the question was asked
pub asked_at: DateTime<Utc>,
}
fn default_question_type() -> String {
"general".to_string()
}
/// Request to update supervisor state
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateSupervisorStateRequest {
/// Updated conversation history
pub conversation_history: Option<serde_json::Value>,
/// Tasks the supervisor is waiting on
pub pending_task_ids: Option<Vec<Uuid>>,
/// Current contract phase
pub phase: Option<String>,
/// Current supervisor state
pub state: Option<String>,
/// Current activity description
pub current_activity: Option<String>,
/// Progress percentage
pub progress: Option<i32>,
/// Error message
pub error_message: Option<String>,
/// Spawned task IDs
pub spawned_task_ids: Option<Vec<Uuid>>,
/// Pending questions
pub pending_questions: Option<serde_json::Value>,
}
/// Restoration context returned when restoring a supervisor
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorRestorationContext {
/// Whether restoration was successful
pub success: bool,
/// Previous state before restoration
pub previous_state: SupervisorStateEnum,
/// Restored conversation history
pub conversation_history: serde_json::Value,
/// Pending questions that need re-delivery
pub pending_questions: Vec<PendingQuestion>,
/// Tasks still being waited on
pub waiting_task_ids: Vec<Uuid>,
/// Spawned tasks to check status of
pub spawned_task_ids: Vec<Uuid>,
/// Restoration count (incremented)
pub restoration_count: i32,
/// Context message for Claude
pub restoration_context_message: String,
/// Any warnings during restoration
pub warnings: Vec<String>,
}
/// Validation result for supervisor state consistency
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateValidationResult {
pub is_valid: bool,
pub issues: Vec<String>,
/// Suggested recovery action
pub recovery_action: StateRecoveryAction,
}
/// Action to take when state validation fails
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StateRecoveryAction {
/// State is valid, proceed with restoration
Proceed,
/// Start from last checkpoint
UseCheckpoint,
/// Start fresh
StartFresh,
/// Manual intervention required
ManualIntervention,
}
// ============================================================================
// Daemon Task Assignments (for multi-daemon support)
// ============================================================================
/// Daemon task assignment record
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DaemonTaskAssignment {
pub id: Uuid,
pub daemon_id: Uuid,
pub task_id: Uuid,
pub assigned_at: DateTime<Utc>,
/// Status: 'active', 'migrating', 'completed'
pub status: String,
}
/// Extended daemon info for selection
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DaemonWithCapacity {
pub id: Uuid,
pub owner_id: Uuid,
pub connection_id: String,
pub hostname: Option<String>,
pub machine_id: Option<String>,
pub max_concurrent_tasks: i32,
pub current_task_count: i32,
pub capacity_score: Option<i32>,
pub task_queue_length: Option<i32>,
pub supports_migration: Option<bool>,
pub status: String,
pub last_heartbeat_at: DateTime<Utc>,
pub connected_at: DateTime<Utc>,
}
// ============================================================================
// Repository History (for storing and suggesting previously used repositories)
// ============================================================================
/// Repository history entry - tracks previously used repositories for suggestions
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RepositoryHistoryEntry {
pub id: Uuid,
pub owner_id: Uuid,
pub name: String,
pub repository_url: Option<String>,
pub local_path: Option<String>,
pub source_type: String,
pub use_count: i32,
pub last_used_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
}
/// Response for repository history list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RepositoryHistoryListResponse {
pub entries: Vec<RepositoryHistoryEntry>,
pub total: i64,
}
/// Request for getting repository suggestions
#[derive(Debug, Deserialize, ToSchema)]
pub struct RepositorySuggestionsQuery {
/// Filter by source type: 'remote' or 'local'
pub source_type: Option<String>,
/// Optional search query to filter by name or URL/path
pub query: Option<String>,
/// Limit results (default: 10)
pub limit: Option<i32>,
}
// =============================================================================
// Resume and History System Types
// =============================================================================
/// Conversation snapshot for task resumption
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ConversationSnapshot {
pub id: Uuid,
pub task_id: Uuid,
pub checkpoint_id: Option<Uuid>,
/// Snapshot type: 'auto', 'manual', 'checkpoint'
pub snapshot_type: String,
pub message_count: i32,
#[sqlx(json)]
pub conversation_state: serde_json::Value,
#[sqlx(json)]
pub metadata: Option<serde_json::Value>,
pub created_at: DateTime<Utc>,
}
/// History event for contract/task history tracking
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct HistoryEvent {
pub id: Uuid,
pub owner_id: Uuid,
pub contract_id: Option<Uuid>,
pub task_id: Option<Uuid>,
pub event_type: String,
pub event_subtype: Option<String>,
pub phase: Option<String>,
#[sqlx(json)]
pub event_data: serde_json::Value,
pub created_at: DateTime<Utc>,
}
/// Unified conversation message for API responses
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ConversationMessage {
pub id: String,
/// Message role: 'user', 'assistant', 'system', 'tool'
pub role: String,
pub content: String,
pub timestamp: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCallInfo>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_input: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_result: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_count: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost_usd: Option<f64>,
}
/// Tool call information within a conversation message
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ToolCallInfo {
pub id: String,
pub name: String,
pub input: serde_json::Value,
}
/// Query filters for history endpoints
#[derive(Debug, Deserialize, ToSchema, Default)]
#[serde(rename_all = "camelCase")]
pub struct HistoryQueryFilters {
pub phase: Option<String>,
pub event_types: Option<Vec<String>>,
#[serde(default, deserialize_with = "flexible_datetime::deserialize")]
pub from: Option<DateTime<Utc>>,
#[serde(default, deserialize_with = "flexible_datetime::deserialize")]
pub to: Option<DateTime<Utc>>,
pub limit: Option<i32>,
pub cursor: Option<String>,
}
/// Request to resume a supervisor
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ResumeSupervisorRequest {
pub target_daemon_id: Option<Uuid>,
/// Resume mode: 'continue', 'restart_phase', 'from_checkpoint'
pub resume_mode: String,
pub checkpoint_id: Option<Uuid>,
pub additional_context: Option<String>,
}
/// Request to resume from a checkpoint
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ResumeFromCheckpointRequest {
pub task_name: Option<String>,
pub plan: String,
pub include_conversation: Option<bool>,
pub target_daemon_id: Option<Uuid>,
}
/// Request to rewind a task to a checkpoint
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RewindTaskRequest {
pub checkpoint_id: Option<Uuid>,
pub checkpoint_sha: Option<String>,
/// Preserve mode: 'discard', 'create_branch', 'stash'
pub preserve_mode: String,
pub branch_name: Option<String>,
}
/// Request to rewind a conversation
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RewindConversationRequest {
pub to_message_id: Option<String>,
pub to_timestamp: Option<DateTime<Utc>>,
pub by_message_count: Option<i32>,
pub rewind_code: Option<bool>,
}
/// Request to fork a task
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ForkTaskRequest {
/// Fork from type: 'checkpoint', 'timestamp', 'message_id'
pub fork_from_type: String,
pub fork_from_value: String,
pub new_task_name: String,
pub new_task_plan: String,
pub include_conversation: Option<bool>,
pub create_branch: Option<bool>,
pub branch_name: Option<String>,
}
/// Response for contract history endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractHistoryResponse {
pub contract_id: Uuid,
pub entries: Vec<HistoryEvent>,
pub total_count: i64,
pub cursor: Option<String>,
}
/// Response for task conversation endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskConversationResponse {
pub task_id: Uuid,
pub task_name: String,
pub status: String,
pub messages: Vec<ConversationMessage>,
pub total_tokens: Option<i32>,
pub total_cost: Option<f64>,
}
/// Response for supervisor conversation endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorConversationResponse {
pub contract_id: Uuid,
pub supervisor_task_id: Uuid,
pub phase: String,
pub last_activity: DateTime<Utc>,
pub pending_task_ids: Vec<Uuid>,
pub messages: Vec<ConversationMessage>,
pub spawned_tasks: Vec<TaskReference>,
}
/// Reference to a task for history/conversation responses
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskReference {
pub task_id: Uuid,
pub task_name: String,
pub status: String,
pub created_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
}
/// Response for task rewind operation
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RewindTaskResponse {
pub task_id: Uuid,
pub rewinded_to: CheckpointInfo,
pub preserved_as: Option<PreservedState>,
}
/// Checkpoint information in rewind response
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CheckpointInfo {
pub checkpoint_number: i32,
pub sha: String,
pub message: String,
}
/// Preserved state information in rewind response
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PreservedState {
/// State type: 'branch' or 'stash'
pub state_type: String,
pub reference: String,
}
/// Response for task fork operation
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ForkTaskResponse {
pub new_task_id: Uuid,
pub source_task_id: Uuid,
pub fork_point: ForkPoint,
pub branch_name: Option<String>,
pub conversation_included: bool,
pub message_count: Option<i32>,
}
/// Fork point information in fork response
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ForkPoint {
pub fork_type: String,
pub checkpoint: Option<TaskCheckpoint>,
pub timestamp: DateTime<Utc>,
}
// ============================================================================
// Checkpoint Patches (for task recovery when worktrees are lost)
// ============================================================================
/// A stored git patch for checkpoint recovery.
/// Enables task recovery when local worktrees are deleted or corrupted.
#[derive(Debug, Clone, FromRow, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CheckpointPatch {
pub id: Uuid,
pub task_id: Uuid,
/// Optional link to a task_checkpoint record
pub checkpoint_id: Option<Uuid>,
/// The commit SHA that the patch should be applied on top of
pub base_commit_sha: String,
/// Compressed git diff data (gzip)
#[sqlx(rename = "patch_data")]
#[serde(skip)] // Don't serialize binary data to JSON
pub patch_data: Vec<u8>,
/// Size of the compressed patch in bytes
pub patch_size_bytes: i32,
/// Number of files affected by this patch
pub files_count: i32,
pub created_at: DateTime<Utc>,
/// When this patch expires and will be automatically deleted
pub expires_at: DateTime<Utc>,
}
/// Response for checkpoint patch (without binary data)
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CheckpointPatchInfo {
pub id: Uuid,
pub task_id: Uuid,
pub checkpoint_id: Option<Uuid>,
pub base_commit_sha: String,
pub patch_size_bytes: i32,
pub files_count: i32,
pub created_at: DateTime<Utc>,
pub expires_at: DateTime<Utc>,
}
// ============================================================================
// Red Team Types
// ============================================================================
// =============================================================================
// Supervisor State and Heartbeat Types
// =============================================================================
/// Supervisor state for contract supervisor tasks.
/// Captures detailed activity state for monitoring and restoration.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum SupervisorStateEnum {
/// Supervisor is starting up
Initializing,
/// Supervisor is idle, waiting for work
Idle,
/// Supervisor is actively working
Working,
/// Supervisor is waiting for user input/confirmation
WaitingForUser,
/// Supervisor is waiting for spawned tasks to complete
WaitingForTasks,
/// Supervisor is blocked (external dependency, error, etc.)
Blocked,
/// Supervisor has completed its contract
Completed,
/// Supervisor has failed
Failed,
/// Supervisor was interrupted (daemon crash, etc.)
Interrupted,
}
impl Default for SupervisorStateEnum {
fn default() -> Self {
SupervisorStateEnum::Initializing
}
}
impl std::fmt::Display for SupervisorStateEnum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SupervisorStateEnum::Initializing => write!(f, "initializing"),
SupervisorStateEnum::Idle => write!(f, "idle"),
SupervisorStateEnum::Working => write!(f, "working"),
SupervisorStateEnum::WaitingForUser => write!(f, "waiting_for_user"),
SupervisorStateEnum::WaitingForTasks => write!(f, "waiting_for_tasks"),
SupervisorStateEnum::Blocked => write!(f, "blocked"),
SupervisorStateEnum::Completed => write!(f, "completed"),
SupervisorStateEnum::Failed => write!(f, "failed"),
SupervisorStateEnum::Interrupted => write!(f, "interrupted"),
}
}
}
impl std::str::FromStr for SupervisorStateEnum {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"initializing" => Ok(SupervisorStateEnum::Initializing),
"idle" => Ok(SupervisorStateEnum::Idle),
"working" => Ok(SupervisorStateEnum::Working),
"waiting_for_user" | "waitingforuser" => Ok(SupervisorStateEnum::WaitingForUser),
"waiting_for_tasks" | "waitingfortasks" => Ok(SupervisorStateEnum::WaitingForTasks),
"blocked" => Ok(SupervisorStateEnum::Blocked),
"completed" => Ok(SupervisorStateEnum::Completed),
"failed" => Ok(SupervisorStateEnum::Failed),
"interrupted" => Ok(SupervisorStateEnum::Interrupted),
_ => Err(format!("Unknown supervisor state: {}", s)),
}
}
}
/// Enhanced heartbeat record for supervisor task monitoring.
/// Stored in the database for historical analysis and dead supervisor detection.
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorHeartbeatRecord {
pub id: Uuid,
pub supervisor_task_id: Uuid,
pub contract_id: Uuid,
pub state: String,
pub phase: String,
pub current_activity: Option<String>,
pub progress: i32,
#[sqlx(try_from = "Vec<Uuid>")]
pub pending_task_ids: Vec<Uuid>,
pub timestamp: DateTime<Utc>,
}
/// Request payload for sending a supervisor heartbeat.
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorHeartbeatRequest {
pub task_id: Uuid,
pub contract_id: Uuid,
pub state: SupervisorStateEnum,
pub phase: String,
pub current_activity: Option<String>,
/// Progress percentage (0-100)
pub progress: u8,
pub pending_task_ids: Vec<Uuid>,
}
// ============================================================================
// Supervisor Status API Types
// ============================================================================
/// Response for supervisor status endpoint
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorStatusResponse {
/// The supervisor task ID
pub task_id: Uuid,
/// Current supervisor state (from supervisor_states table)
pub state: String,
/// Current contract phase
pub phase: String,
/// Description of current activity (from task progress_summary)
pub current_activity: Option<String>,
/// Progress percentage (0-100)
pub progress: Option<u8>,
/// When the supervisor last updated its state
pub last_heartbeat: DateTime<Utc>,
/// Task IDs the supervisor is currently waiting on
pub pending_task_ids: Vec<Uuid>,
/// Whether the supervisor is currently running
pub is_running: bool,
}
/// Individual heartbeat entry for history
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorHeartbeatEntry {
/// Timestamp of this heartbeat
pub timestamp: DateTime<Utc>,
/// Supervisor state at this time
pub state: String,
/// Activity description at this time
pub activity: Option<String>,
/// Progress at this time
pub progress: Option<u8>,
/// Contract phase at this time
pub phase: String,
/// Pending task IDs at this time
pub pending_task_ids: Vec<Uuid>,
}
/// Response for supervisor heartbeat history endpoint
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorHeartbeatHistoryResponse {
/// List of heartbeat entries
pub heartbeats: Vec<SupervisorHeartbeatEntry>,
/// Total count of heartbeats (for pagination)
pub total: i64,
}
/// Response for supervisor sync endpoint
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorSyncResponse {
/// Whether the sync was successful
pub synced: bool,
/// Current supervisor state after sync
pub state: String,
/// Optional message about the sync result
pub message: Option<String>,
}
/// Query parameters for heartbeat history endpoint
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct HeartbeatHistoryQuery {
/// Maximum number of heartbeats to return (default: 10)
pub limit: Option<i32>,
/// Offset for pagination (default: 0)
pub offset: Option<i32>,
}
// =============================================================================
// Unit Tests
// =============================================================================
#[cfg(test)]
mod tests {
use super::*;
use uuid::Uuid;
#[test]
fn test_supervisor_state_enum_display() {
assert_eq!(SupervisorStateEnum::Initializing.to_string(), "initializing");
assert_eq!(SupervisorStateEnum::Idle.to_string(), "idle");
assert_eq!(SupervisorStateEnum::Working.to_string(), "working");
assert_eq!(SupervisorStateEnum::WaitingForUser.to_string(), "waiting_for_user");
assert_eq!(SupervisorStateEnum::WaitingForTasks.to_string(), "waiting_for_tasks");
assert_eq!(SupervisorStateEnum::Blocked.to_string(), "blocked");
assert_eq!(SupervisorStateEnum::Completed.to_string(), "completed");
assert_eq!(SupervisorStateEnum::Failed.to_string(), "failed");
assert_eq!(SupervisorStateEnum::Interrupted.to_string(), "interrupted");
}
#[test]
fn test_supervisor_state_enum_from_str() {
// Standard lowercase
assert_eq!("initializing".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Initializing);
assert_eq!("idle".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Idle);
assert_eq!("working".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Working);
assert_eq!("waiting_for_user".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::WaitingForUser);
assert_eq!("waiting_for_tasks".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::WaitingForTasks);
assert_eq!("blocked".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Blocked);
assert_eq!("completed".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Completed);
assert_eq!("failed".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Failed);
assert_eq!("interrupted".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Interrupted);
// Case insensitive
assert_eq!("WORKING".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Working);
assert_eq!("Working".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Working);
// Alternative formats
assert_eq!("waitingforuser".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::WaitingForUser);
assert_eq!("waitingfortasks".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::WaitingForTasks);
// Invalid state
assert!("invalid_state".parse::<SupervisorStateEnum>().is_err());
}
#[test]
fn test_supervisor_state_enum_serialization() {
// Test JSON serialization
let state = SupervisorStateEnum::Working;
let json = serde_json::to_string(&state).unwrap();
assert_eq!(json, "\"working\"");
// Test JSON deserialization
let deserialized: SupervisorStateEnum = serde_json::from_str("\"working\"").unwrap();
assert_eq!(deserialized, SupervisorStateEnum::Working);
// Test underscore variants
let json = "\"waiting_for_user\"";
let deserialized: SupervisorStateEnum = serde_json::from_str(json).unwrap();
assert_eq!(deserialized, SupervisorStateEnum::WaitingForUser);
}
#[test]
fn test_supervisor_state_enum_default() {
let default_state = SupervisorStateEnum::default();
assert_eq!(default_state, SupervisorStateEnum::Initializing);
}
#[test]
fn test_supervisor_heartbeat_request_serialization() {
let request = SupervisorHeartbeatRequest {
task_id: Uuid::nil(),
contract_id: Uuid::nil(),
state: SupervisorStateEnum::Working,
phase: "execute".to_string(),
current_activity: Some("Implementing feature".to_string()),
progress: 50,
pending_task_ids: vec![Uuid::nil()],
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"state\":\"working\""));
assert!(json.contains("\"phase\":\"execute\""));
assert!(json.contains("\"progress\":50"));
assert!(json.contains("\"currentActivity\":\"Implementing feature\""));
// Test deserialization
let deserialized: SupervisorHeartbeatRequest = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.state, SupervisorStateEnum::Working);
assert_eq!(deserialized.phase, "execute");
assert_eq!(deserialized.progress, 50);
}
}
// =============================================================================
// Directive Types
// =============================================================================
/// A directive — a long-lived top-level entity for managing projects via a DAG of steps.
#[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,
/// Status: draft, active, idle, paused, archived
pub status: String,
pub repository_url: Option<String>,
pub local_path: Option<String>,
pub base_branch: Option<String>,
pub orchestrator_task_id: Option<Uuid>,
pub pr_url: Option<String>,
pub pr_branch: Option<String>,
pub completion_task_id: Option<Uuid>,
/// Question timeout mode: "auto" (30s timeout), "semi-auto" (block indefinitely), "manual" (block + ask many questions)
pub reconcile_mode: String,
pub goal_updated_at: DateTime<Utc>,
pub started_at: Option<DateTime<Utc>>,
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
/// A historical record of a directive goal change.
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveGoalHistory {
pub id: Uuid,
pub directive_id: Uuid,
pub goal: String,
pub created_at: DateTime<Utc>,
}
/// A step in a directive's DAG.
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveStep {
pub id: Uuid,
pub directive_id: Uuid,
pub name: String,
pub description: Option<String>,
pub task_plan: Option<String>,
pub depends_on: Vec<Uuid>,
/// Status: pending, ready, running, completed, failed, skipped
pub status: String,
pub task_id: Option<Uuid>,
/// Optional contract ID for contract-backed execution.
pub contract_id: Option<Uuid>,
/// Optional contract type (e.g. "simple", "specification", "execute").
/// When set, the orchestrator creates a contract instead of a standalone task.
pub contract_type: Option<String>,
pub order_index: i32,
pub generation: i32,
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
}
/// Directive with its steps for detail view.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveWithSteps {
#[serde(flatten)]
pub directive: Directive,
pub steps: Vec<DirectiveStep>,
}
/// Summary for directive list views.
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveSummary {
pub id: Uuid,
pub owner_id: Uuid,
pub title: String,
pub goal: String,
pub status: String,
pub repository_url: Option<String>,
pub orchestrator_task_id: Option<Uuid>,
pub pr_url: Option<String>,
pub completion_task_id: Option<Uuid>,
/// Question timeout mode: "auto" (30s timeout), "semi-auto" (block indefinitely), "manual" (block + ask many questions)
pub reconcile_mode: String,
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub total_steps: i64,
pub completed_steps: i64,
pub running_steps: i64,
pub failed_steps: i64,
}
/// List response for directives.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveListResponse {
pub directives: Vec<DirectiveSummary>,
pub total: i64,
}
/// Request to create a new directive.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateDirectiveRequest {
pub title: String,
pub goal: String,
pub repository_url: Option<String>,
pub local_path: Option<String>,
pub base_branch: Option<String>,
/// Question timeout mode: "auto", "semi-auto", or "manual"
pub reconcile_mode: Option<String>,
}
/// Request to update a directive.
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateDirectiveRequest {
pub title: Option<String>,
pub goal: Option<String>,
pub status: Option<String>,
pub repository_url: Option<String>,
pub local_path: Option<String>,
pub base_branch: Option<String>,
pub orchestrator_task_id: Option<Uuid>,
pub pr_url: Option<String>,
pub pr_branch: Option<String>,
/// Question timeout mode: "auto", "semi-auto", or "manual"
pub reconcile_mode: Option<String>,
pub version: Option<i32>,
}
/// Request to update a directive's goal (triggers re-planning).
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateGoalRequest {
pub goal: String,
}
/// Response for cleanup_directive_tasks (legacy).
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CleanupTasksResponse {
pub deleted: i64,
}
/// Response for cleanup_directive endpoint.
#[derive(Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CleanupResponse {
pub message: String,
pub task_id: Option<Uuid>,
}
/// Response for pick_up_orders endpoint.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PickUpOrdersResponse {
pub message: String,
pub order_count: i64,
pub task_id: Option<Uuid>,
}
/// Request to create a directive step.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateDirectiveStepRequest {
pub name: String,
pub description: Option<String>,
pub task_plan: Option<String>,
#[serde(default)]
pub depends_on: Vec<Uuid>,
#[serde(default)]
pub order_index: i32,
pub generation: Option<i32>,
/// Optional order ID to auto-link this step to an order.
#[serde(default)]
pub order_id: Option<Uuid>,
/// Optional: create a contract for this step instead of a standalone task.
/// Valid values: "simple", "specification", "execute"
#[serde(default)]
pub contract_type: Option<String>,
}
/// Request to update a directive step.
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateDirectiveStepRequest {
pub name: Option<String>,
pub description: Option<String>,
pub task_plan: Option<String>,
pub depends_on: Option<Vec<Uuid>>,
pub status: Option<String>,
pub task_id: Option<Uuid>,
pub order_index: Option<i32>,
}
// =============================================================================
// Order Types
// =============================================================================
/// An order — a card-based work item (feature, bug, spike, chore, improvement)
/// similar to GitHub Issues or Linear cards. Orders are linked to directives
/// for execution.
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Order {
pub id: Uuid,
pub owner_id: Uuid,
pub title: String,
pub description: Option<String>,
/// Priority: critical, high, medium, low, none
pub priority: String,
/// Status: open, in_progress, under_review, done, archived
pub status: String,
/// Type of work: feature, bug, spike, chore, improvement
pub order_type: String,
/// Flexible labels as JSON array of strings
pub labels: serde_json::Value,
/// Linked directive (required for new orders, nullable for legacy rows)
pub directive_id: Option<Uuid>,
/// Linked directive step (optional)
pub directive_step_id: Option<Uuid>,
/// Denormalized directive name for searchability (auto-populated by DB trigger)
pub directive_name: Option<String>,
/// Repository context
pub repository_url: Option<String>,
/// Optional DOG (Directive Order Group) this order belongs to
pub dog_id: Option<Uuid>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
/// Request to create a new order.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateOrderRequest {
pub title: String,
pub description: Option<String>,
pub priority: Option<String>,
pub status: Option<String>,
pub order_type: Option<String>,
#[serde(default = "default_empty_labels")]
pub labels: serde_json::Value,
/// Directive ID is required for new orders.
pub directive_id: Uuid,
pub repository_url: Option<String>,
/// Optional DOG (Directive Order Group) to assign this order to.
pub dog_id: Option<Uuid>,
}
/// Default empty JSON array for labels.
fn default_empty_labels() -> serde_json::Value {
serde_json::json!([])
}
/// Request to update an existing order.
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateOrderRequest {
pub title: Option<String>,
pub description: Option<String>,
pub priority: Option<String>,
pub status: Option<String>,
pub order_type: Option<String>,
pub labels: Option<serde_json::Value>,
pub directive_id: Option<Uuid>,
pub directive_step_id: Option<Uuid>,
pub repository_url: Option<String>,
/// Optional DOG (Directive Order Group) to assign/reassign this order to.
pub dog_id: Option<Uuid>,
}
/// Response for order list endpoint.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct OrderListResponse {
pub orders: Vec<Order>,
pub total: i64,
}
/// Query parameters for listing orders.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct OrderListQuery {
/// Filter by status (e.g., "open", "in_progress", "under_review", "done", "archived")
pub status: Option<String>,
/// Filter by order type (e.g., "feature", "bug", "spike", "chore", "improvement")
#[serde(rename = "type")]
pub order_type: Option<String>,
/// Filter by priority (e.g., "critical", "high", "medium", "low", "none")
pub priority: Option<String>,
/// Filter by linked directive ID
pub directive_id: Option<Uuid>,
/// Filter by DOG (Directive Order Group) ID
pub dog_id: Option<Uuid>,
/// Text search across title, description, and directive_name (case-insensitive)
pub search: Option<String>,
}
/// Request body for linking an order to a directive.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct LinkDirectiveRequest {
pub directive_id: Uuid,
}
// =============================================================================
// Directive Order Group (DOG) Types
// =============================================================================
/// A Directive Order Group (DOG) — an epic-like grouping of orders within a directive.
/// DOGs allow organizing related orders under a common theme or goal.
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveOrderGroup {
pub id: Uuid,
pub directive_id: Uuid,
pub owner_id: Uuid,
pub name: String,
pub description: Option<String>,
/// Status: open, in_progress, done, archived
pub status: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
/// Request to create a new Directive Order Group (DOG).
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateDirectiveOrderGroupRequest {
pub name: String,
pub description: Option<String>,
}
/// Request to update a Directive Order Group (DOG).
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateDirectiveOrderGroupRequest {
pub name: Option<String>,
pub description: Option<String>,
pub status: Option<String>,
}
/// Response for DOG list endpoint.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveOrderGroupListResponse {
pub dogs: Vec<DirectiveOrderGroup>,
pub total: i64,
}
/// User setting record from the database (key-value per owner).
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UserSetting {
pub id: Uuid,
pub owner_id: Uuid,
pub key: String,
pub value: serde_json::Value,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
/// Request body for upserting a user setting.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpsertUserSettingRequest {
pub key: String,
pub value: serde_json::Value,
}
/// Response containing a list of user settings.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UserSettingsResponse {
pub settings: Vec<UserSetting>,
}