summaryrefslogtreecommitdiff
path: root/docs/contract-management-spec.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/contract-management-spec.md')
-rw-r--r--docs/contract-management-spec.md1337
1 files changed, 1337 insertions, 0 deletions
diff --git a/docs/contract-management-spec.md b/docs/contract-management-spec.md
new file mode 100644
index 0000000..c7f948a
--- /dev/null
+++ b/docs/contract-management-spec.md
@@ -0,0 +1,1337 @@
+# Contract Management System Specification
+
+**Version**: 1.0.0
+**Status**: Draft
+**Author**: AI Assistant
+**Date**: 2025-01-31
+
+## Executive Summary
+
+This specification addresses critical issues in the current contract management system:
+
+1. **Manual Completion Required** - Contracts stay 'active' indefinitely
+2. **No Phase Readiness Validation** - No automatic checking before phase advancement
+3. **Supervisor State Restoration Broken** - Context lost after daemon crash
+4. **Version Conflicts Silent** - Phase changes can fail silently
+5. **No Deliverable Validation** - Can mark non-existent deliverables as complete
+6. **Phase Guard Supervisor Bypass** - Supervisors can bypass phase_guard setting
+
+---
+
+## 1. Contract Lifecycle State Machine
+
+### 1.1 Current State (ContractStatus)
+
+The current implementation uses three states:
+- `active` - Contract is being worked on
+- `completed` - Contract finished successfully
+- `archived` - Contract archived (soft delete)
+
+### 1.2 Proposed State Machine
+
+```
+ ┌─────────────────────────────────────────────┐
+ │ │
+ ▼ │
+┌────────┐ ┌─────────┐ ┌──────────────────┐ ┌────────────┴───┐
+│ created ├───►│ active ├───►│ waiting_for_input├───►│ completing │
+└────────┘ └────┬────┘ └────────┬─────────┘ └───────┬────────┘
+ │ │ │
+ │ │ ▼
+ │ │ ┌───────────────┐
+ │ │ │ completed │
+ │ │ └───────────────┘
+ │ │ │
+ ▼ ▼ ▼
+ ┌─────────┐ ┌──────────┐ ┌───────────────┐
+ │ paused │ │ blocked │ │ archived │
+ └────┬────┘ └────┬─────┘ └───────────────┘
+ │ │ ▲
+ └────────────────┴───────────────────────┤
+ │
+ ┌────────┐ │
+ │ failed ├───────────────────────────────┘
+ └────────┘
+```
+
+### 1.3 State Definitions
+
+```rust
+/// Contract lifecycle states
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum ContractStatus {
+ /// Contract created but not yet started
+ Created,
+ /// Contract is actively being worked on
+ Active,
+ /// Waiting for user input (phase confirmation, question, etc.)
+ WaitingForInput,
+ /// Contract is paused by user request
+ Paused,
+ /// Contract is blocked on external dependency
+ Blocked,
+ /// All phases complete, running final validation
+ Completing,
+ /// Contract completed successfully
+ Completed,
+ /// Contract failed with errors
+ Failed,
+ /// Contract archived (soft delete)
+ Archived,
+}
+```
+
+### 1.4 State Transitions
+
+| From State | To State | Guard Conditions | Trigger |
+|------------|----------|------------------|---------|
+| Created | Active | Has supervisor task | Supervisor starts |
+| Active | WaitingForInput | Pending question exists | Supervisor asks question |
+| Active | Paused | - | User requests pause |
+| Active | Blocked | Has external blocker | Blocker detected |
+| Active | Completing | Final phase, all deliverables met | Auto-completion check |
+| WaitingForInput | Active | Question answered | User responds |
+| WaitingForInput | Paused | - | Timeout or user pause |
+| Paused | Active | - | User resumes |
+| Blocked | Active | Blocker resolved | Blocker cleared |
+| Completing | Completed | All cleanup done | Completion confirmed |
+| Completing | Active | Completion rejected | User rejects |
+| Any | Failed | Unrecoverable error | Error detected |
+| Completed | Archived | - | User archives |
+| Failed | Archived | - | User archives |
+
+### 1.5 Timeout and Stale Detection
+
+```rust
+/// Configuration for contract timeout and stale detection
+pub struct ContractTimeoutConfig {
+ /// Time after last supervisor activity before contract is considered stale
+ pub stale_threshold: Duration, // Default: 30 minutes
+
+ /// Time to wait for user input before timing out
+ pub input_timeout: Duration, // Default: 24 hours
+
+ /// Time before completing contracts are auto-completed
+ pub completion_grace_period: Duration, // Default: 5 minutes
+
+ /// Time before archived contracts are deleted
+ pub archive_retention: Duration, // Default: 30 days
+}
+
+impl Default for ContractTimeoutConfig {
+ fn default() -> Self {
+ Self {
+ stale_threshold: Duration::from_secs(30 * 60),
+ input_timeout: Duration::from_secs(24 * 60 * 60),
+ completion_grace_period: Duration::from_secs(5 * 60),
+ archive_retention: Duration::from_secs(30 * 24 * 60 * 60),
+ }
+ }
+}
+```
+
+### 1.6 Database Schema Changes
+
+```sql
+-- Add new status column options and tracking fields
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS
+ status_changed_at TIMESTAMPTZ DEFAULT NOW();
+
+-- Track last activity for stale detection
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS
+ last_activity_at TIMESTAMPTZ DEFAULT NOW();
+
+-- Track pending input
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS
+ waiting_for TEXT; -- 'question', 'phase_confirmation', 'completion_confirmation'
+
+-- Track blockers
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS
+ blocked_reason TEXT;
+
+-- Track failure reason
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS
+ failure_reason TEXT;
+
+-- Index for status queries
+CREATE INDEX idx_contracts_status ON contracts(status);
+CREATE INDEX idx_contracts_last_activity ON contracts(last_activity_at);
+```
+
+### 1.7 API Changes
+
+```rust
+/// Request to change contract state
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ChangeStatusRequest {
+ pub target_status: ContractStatus,
+ /// Required for some transitions
+ pub reason: Option<String>,
+ /// For blocking states, what is blocking
+ pub blocker: Option<String>,
+}
+
+/// Response for status change
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ChangeStatusResponse {
+ pub success: bool,
+ pub previous_status: ContractStatus,
+ pub new_status: ContractStatus,
+ /// If transition failed, why
+ pub rejection_reason: Option<String>,
+}
+```
+
+---
+
+## 2. Automatic Completion Detection
+
+### 2.1 Current Problem
+
+Contracts currently require manual `supervisor_complete()` calls. Supervisors may exit without completing contracts, leaving them active indefinitely.
+
+### 2.2 Proposed Solution: Completion Gates
+
+#### 2.2.1 Phase Completion Gates
+
+```rust
+/// Gate that must be satisfied before advancing to next phase
+pub struct PhaseCompletionGate {
+ /// Required deliverables for this phase
+ pub required_deliverables: Vec<String>,
+ /// Required tasks to be completed
+ pub required_tasks: TaskRequirement,
+ /// Optional custom validation function
+ pub custom_validator: Option<Box<dyn Fn(&Contract) -> bool>>,
+ /// Whether to auto-advance when gate is satisfied
+ pub auto_advance: bool,
+}
+
+/// Task completion requirements
+pub enum TaskRequirement {
+ /// No task requirements
+ None,
+ /// All spawned tasks must complete
+ AllComplete,
+ /// At least N tasks must complete
+ MinComplete(usize),
+ /// Specific named tasks must complete
+ NamedTasks(Vec<String>),
+}
+```
+
+#### 2.2.2 Contract Completion Detection
+
+```rust
+/// Contract completion detector
+pub struct CompletionDetector {
+ /// Phase-specific gates
+ phase_gates: HashMap<String, PhaseCompletionGate>,
+}
+
+impl CompletionDetector {
+ /// Check if current phase is ready to advance
+ pub fn check_phase_readiness(
+ &self,
+ contract: &Contract,
+ tasks: &[TaskSummary],
+ ) -> PhaseReadinessResult {
+ let gate = match self.phase_gates.get(&contract.phase) {
+ Some(g) => g,
+ None => return PhaseReadinessResult::NoGate,
+ };
+
+ let mut missing = Vec::new();
+
+ // Check deliverables
+ let completed = contract.get_completed_deliverables(&contract.phase);
+ for req in &gate.required_deliverables {
+ if !completed.contains(req) {
+ missing.push(format!("Deliverable: {}", req));
+ }
+ }
+
+ // Check tasks
+ match &gate.required_tasks {
+ TaskRequirement::None => {},
+ TaskRequirement::AllComplete => {
+ let incomplete = tasks.iter()
+ .filter(|t| !t.is_supervisor && t.status != "done")
+ .count();
+ if incomplete > 0 {
+ missing.push(format!("{} tasks incomplete", incomplete));
+ }
+ },
+ TaskRequirement::MinComplete(n) => {
+ let complete = tasks.iter()
+ .filter(|t| !t.is_supervisor && t.status == "done")
+ .count();
+ if complete < *n {
+ missing.push(format!("Need {} tasks complete, have {}", n, complete));
+ }
+ },
+ TaskRequirement::NamedTasks(names) => {
+ for name in names {
+ let found = tasks.iter()
+ .find(|t| &t.name == name && t.status == "done");
+ if found.is_none() {
+ missing.push(format!("Task '{}' not complete", name));
+ }
+ }
+ }
+ }
+
+ if missing.is_empty() {
+ PhaseReadinessResult::Ready
+ } else {
+ PhaseReadinessResult::NotReady { missing }
+ }
+ }
+
+ /// Check if contract should auto-complete
+ pub fn check_contract_completion(&self, contract: &Contract) -> bool {
+ // Must be in terminal phase
+ if contract.phase != contract.terminal_phase_id() {
+ return false;
+ }
+
+ // Terminal phase gate must be satisfied
+ matches!(
+ self.check_phase_readiness(contract, &[]),
+ PhaseReadinessResult::Ready
+ )
+ }
+}
+
+/// Result of phase readiness check
+pub enum PhaseReadinessResult {
+ /// Phase is ready to advance
+ Ready,
+ /// Phase is not ready, with list of missing items
+ NotReady { missing: Vec<String> },
+ /// No gate defined for this phase
+ NoGate,
+}
+```
+
+#### 2.2.3 Auto-Completion Flow
+
+```
+┌─────────────────────────────────────────────────────────────────────┐
+│ AUTO-COMPLETION FLOW │
+└─────────────────────────────────────────────────────────────────────┘
+
+1. Task completes (status = "done")
+ │
+ ▼
+2. Check if any phase gate is now satisfied
+ │
+ ├─ NO ──► Return, wait for more tasks
+ │
+ ▼ YES
+3. Is auto_advance enabled for phase?
+ │
+ ├─ NO ──► Notify user, wait for manual advance
+ │
+ ▼ YES
+4. Is phase_guard enabled?
+ │
+ ├─ YES ─► Set status = WaitingForInput, ask for confirmation
+ │
+ ▼ NO
+5. Auto-advance to next phase
+ │
+ ▼
+6. Is this the terminal phase?
+ │
+ ├─ NO ──► Continue working
+ │
+ ▼ YES
+7. All terminal deliverables complete?
+ │
+ ├─ NO ──► Continue working
+ │
+ ▼ YES
+8. Set status = Completing
+ │
+ ▼
+9. Cleanup worktrees, stop supervisor
+ │
+ ▼
+10. Set status = Completed
+```
+
+### 2.3 Database Schema Changes
+
+```sql
+-- Track auto-completion state
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS
+ auto_complete_enabled BOOLEAN DEFAULT TRUE;
+
+-- Track when completion was detected
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS
+ completion_detected_at TIMESTAMPTZ;
+```
+
+### 2.4 API Endpoints
+
+```rust
+/// Check phase readiness
+/// GET /api/v1/contracts/{id}/phase-readiness
+pub async fn check_phase_readiness(
+ contract_id: Uuid,
+) -> PhaseReadinessResponse;
+
+/// Force completion check
+/// POST /api/v1/contracts/{id}/check-completion
+pub async fn check_completion(
+ contract_id: Uuid,
+) -> CompletionCheckResponse;
+
+/// Enable/disable auto-completion
+/// PUT /api/v1/contracts/{id}/auto-complete
+pub async fn set_auto_complete(
+ contract_id: Uuid,
+ enabled: bool,
+) -> ContractSummary;
+```
+
+---
+
+## 3. Supervisor Status Reporting
+
+### 3.1 Current Problem
+
+The `supervisor_states` table exists but:
+- State is not reliably persisted during daemon operations
+- Restoration after crash doesn't properly resume context
+- No clear indication of supervisor's current activity
+
+### 3.2 Proposed Supervisor States
+
+```rust
+/// Supervisor execution states
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum SupervisorState {
+ /// Supervisor is starting up
+ Initializing,
+ /// Supervisor is idle, no pending work
+ Idle,
+ /// Supervisor is actively working (LLM processing)
+ Working,
+ /// Supervisor is waiting for user input
+ WaitingForUser,
+ /// Supervisor is waiting for child tasks
+ WaitingForTasks,
+ /// Supervisor is blocked on external resource
+ Blocked,
+ /// Supervisor has completed its work
+ Completed,
+ /// Supervisor has failed
+ Failed,
+ /// Supervisor was interrupted
+ Interrupted,
+}
+```
+
+### 3.3 Heartbeat Mechanism
+
+```rust
+/// Heartbeat message from supervisor to server
+#[derive(Debug, Serialize, Deserialize)]
+pub struct SupervisorHeartbeat {
+ pub task_id: Uuid,
+ pub contract_id: Uuid,
+ pub state: SupervisorState,
+ pub phase: String,
+ /// What the supervisor is currently doing
+ pub current_activity: String,
+ /// Progress percentage (0-100)
+ pub progress: u8,
+ /// IDs of tasks supervisor is waiting on
+ pub pending_task_ids: Vec<Uuid>,
+ /// Timestamp
+ pub timestamp: DateTime<Utc>,
+}
+
+/// Heartbeat configuration
+pub struct HeartbeatConfig {
+ /// How often to send heartbeats
+ pub interval: Duration, // Default: 30 seconds
+ /// How long before a supervisor is considered dead
+ pub timeout: Duration, // Default: 2 minutes
+}
+```
+
+### 3.4 State Persistence
+
+```rust
+/// Enhanced supervisor state for persistence
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct SupervisorPersistentState {
+ /// Current supervisor state
+ pub state: SupervisorState,
+ /// Current contract phase
+ pub phase: String,
+ /// Conversation history for resumption
+ pub conversation_history: Vec<ConversationMessage>,
+ /// Currently pending questions
+ pub pending_questions: Vec<PendingQuestion>,
+ /// Tasks spawned by this supervisor
+ pub spawned_task_ids: Vec<Uuid>,
+ /// Tasks we're waiting on
+ pub waiting_on_task_ids: Vec<Uuid>,
+ /// Last checkpoint created
+ pub last_checkpoint: Option<CheckpointInfo>,
+ /// Current activity description
+ pub current_activity: String,
+ /// Error if in failed state
+ pub error: Option<String>,
+ /// Timestamps
+ pub created_at: DateTime<Utc>,
+ pub updated_at: DateTime<Utc>,
+}
+```
+
+### 3.5 Database Schema Changes
+
+```sql
+-- Enhance supervisor_states table
+ALTER TABLE supervisor_states ADD COLUMN IF NOT EXISTS
+ state VARCHAR(50) NOT NULL DEFAULT 'initializing';
+
+ALTER TABLE supervisor_states ADD COLUMN IF NOT EXISTS
+ current_activity TEXT;
+
+ALTER TABLE supervisor_states ADD COLUMN IF NOT EXISTS
+ progress INTEGER DEFAULT 0;
+
+ALTER TABLE supervisor_states ADD COLUMN IF NOT EXISTS
+ error_message TEXT;
+
+ALTER TABLE supervisor_states ADD COLUMN IF NOT EXISTS
+ spawned_task_ids UUID[] DEFAULT ARRAY[]::UUID[];
+
+-- Create heartbeat tracking table
+CREATE TABLE IF NOT EXISTS supervisor_heartbeats (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ supervisor_task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
+ contract_id UUID NOT NULL REFERENCES contracts(id) ON DELETE CASCADE,
+ state VARCHAR(50) NOT NULL,
+ phase VARCHAR(50) NOT NULL,
+ current_activity TEXT,
+ progress INTEGER DEFAULT 0,
+ pending_task_ids UUID[] DEFAULT ARRAY[]::UUID[],
+ timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+
+ -- Keep only recent heartbeats
+ CONSTRAINT heartbeat_ttl CHECK (timestamp > NOW() - INTERVAL '24 hours')
+);
+
+CREATE INDEX idx_heartbeats_supervisor ON supervisor_heartbeats(supervisor_task_id);
+CREATE INDEX idx_heartbeats_timestamp ON supervisor_heartbeats(timestamp);
+```
+
+### 3.6 Restoration Protocol
+
+```
+┌─────────────────────────────────────────────────────────────────────┐
+│ SUPERVISOR RESTORATION PROTOCOL │
+└─────────────────────────────────────────────────────────────────────┘
+
+1. Daemon restarts or task is assigned to new daemon
+ │
+ ▼
+2. Load supervisor state from supervisor_states table
+ │
+ ├─ NOT FOUND ──► Start fresh, log warning
+ │
+ ▼ FOUND
+3. Validate state consistency
+ │
+ ├─ INVALID ──► Start from last checkpoint
+ │
+ ▼ VALID
+4. Restore conversation history
+ │
+ ▼
+5. Check for pending questions
+ │
+ ├─ HAS PENDING ──► Re-deliver questions to user
+ │
+ ▼ NO PENDING
+6. Check for waiting tasks
+ │
+ ├─ HAS WAITING ──► Resume waiting state
+ │
+ ▼ NO WAITING
+7. Send restoration context to Claude
+ │
+ ▼
+8. Resume execution from last state
+```
+
+### 3.7 API Endpoints
+
+```rust
+/// Get supervisor status
+/// GET /api/v1/contracts/{id}/supervisor/status
+pub async fn get_supervisor_status(
+ contract_id: Uuid,
+) -> SupervisorStatusResponse;
+
+/// Get supervisor heartbeat history
+/// GET /api/v1/contracts/{id}/supervisor/heartbeats
+pub async fn get_heartbeats(
+ contract_id: Uuid,
+ limit: Option<i32>,
+) -> HeartbeatListResponse;
+
+/// Force supervisor state sync
+/// POST /api/v1/contracts/{id}/supervisor/sync
+pub async fn sync_supervisor_state(
+ contract_id: Uuid,
+) -> SyncResponse;
+```
+
+---
+
+## 4. Contract Monitoring Dashboard
+
+### 4.1 Real-Time Status Updates
+
+#### 4.1.1 WebSocket Events
+
+```rust
+/// Contract monitoring events
+#[derive(Debug, Serialize)]
+#[serde(tag = "type", rename_all = "snake_case")]
+pub enum ContractMonitorEvent {
+ /// Contract status changed
+ StatusChanged {
+ contract_id: Uuid,
+ old_status: ContractStatus,
+ new_status: ContractStatus,
+ reason: Option<String>,
+ },
+ /// Phase changed
+ PhaseChanged {
+ contract_id: Uuid,
+ old_phase: String,
+ new_phase: String,
+ },
+ /// Supervisor state changed
+ SupervisorStateChanged {
+ contract_id: Uuid,
+ supervisor_task_id: Uuid,
+ old_state: SupervisorState,
+ new_state: SupervisorState,
+ },
+ /// Supervisor heartbeat received
+ Heartbeat {
+ contract_id: Uuid,
+ state: SupervisorState,
+ activity: String,
+ progress: u8,
+ },
+ /// Contract became stale
+ StaleDetected {
+ contract_id: Uuid,
+ last_activity: DateTime<Utc>,
+ stale_duration: Duration,
+ },
+ /// Task completed
+ TaskCompleted {
+ contract_id: Uuid,
+ task_id: Uuid,
+ task_name: String,
+ success: bool,
+ },
+ /// Deliverable marked complete
+ DeliverableCompleted {
+ contract_id: Uuid,
+ phase: String,
+ deliverable_id: String,
+ },
+ /// Question asked (needs user attention)
+ QuestionAsked {
+ contract_id: Uuid,
+ question_id: Uuid,
+ question: String,
+ question_type: String,
+ },
+}
+```
+
+#### 4.1.2 Subscription API
+
+```rust
+/// Subscribe to contract monitoring events
+/// WS /api/v1/contracts/monitor
+pub async fn monitor_contracts(
+ ws: WebSocket,
+ filter: ContractMonitorFilter,
+) -> Result<(), Error>;
+
+#[derive(Debug, Deserialize)]
+pub struct ContractMonitorFilter {
+ /// Filter by specific contract IDs
+ pub contract_ids: Option<Vec<Uuid>>,
+ /// Filter by status
+ pub statuses: Option<Vec<ContractStatus>>,
+ /// Include stale detection events
+ pub include_stale: bool,
+ /// Include heartbeat events
+ pub include_heartbeats: bool,
+}
+```
+
+### 4.2 Stale Contract Detection
+
+```rust
+/// Stale contract detector service
+pub struct StaleContractDetector {
+ pool: PgPool,
+ config: ContractTimeoutConfig,
+}
+
+impl StaleContractDetector {
+ /// Run stale detection loop
+ pub async fn run(&self, event_tx: Sender<ContractMonitorEvent>) {
+ let mut interval = tokio::time::interval(Duration::from_secs(60));
+
+ loop {
+ interval.tick().await;
+
+ let stale = self.detect_stale_contracts().await;
+ for (contract_id, last_activity) in stale {
+ let _ = event_tx.send(ContractMonitorEvent::StaleDetected {
+ contract_id,
+ last_activity,
+ stale_duration: Utc::now() - last_activity,
+ }).await;
+ }
+ }
+ }
+
+ /// Detect stale contracts
+ async fn detect_stale_contracts(&self) -> Vec<(Uuid, DateTime<Utc>)> {
+ let threshold = Utc::now() - self.config.stale_threshold;
+
+ sqlx::query_as::<_, (Uuid, DateTime<Utc>)>(
+ r#"
+ SELECT id, last_activity_at
+ FROM contracts
+ WHERE status = 'active'
+ AND last_activity_at < $1
+ "#
+ )
+ .bind(threshold)
+ .fetch_all(&self.pool)
+ .await
+ .unwrap_or_default()
+ }
+}
+```
+
+### 4.3 Batch Operations
+
+```rust
+/// Batch operation types
+#[derive(Debug, Deserialize)]
+#[serde(tag = "operation", rename_all = "snake_case")]
+pub enum BatchOperation {
+ /// Archive completed contracts older than threshold
+ ArchiveOld {
+ older_than: Duration,
+ status_filter: Vec<ContractStatus>,
+ },
+ /// Pause all active contracts
+ PauseAll {
+ reason: String,
+ },
+ /// Resume all paused contracts
+ ResumeAll,
+ /// Delete archived contracts older than threshold
+ CleanupArchived {
+ older_than: Duration,
+ },
+ /// Restart stale supervisors
+ RestartStale {
+ stale_threshold: Duration,
+ },
+}
+
+/// Batch operation result
+#[derive(Debug, Serialize)]
+pub struct BatchOperationResult {
+ pub operation: String,
+ pub affected_count: usize,
+ pub affected_ids: Vec<Uuid>,
+ pub errors: Vec<BatchOperationError>,
+}
+
+#[derive(Debug, Serialize)]
+pub struct BatchOperationError {
+ pub contract_id: Uuid,
+ pub error: String,
+}
+```
+
+### 4.4 Dashboard API
+
+```rust
+/// Get dashboard summary
+/// GET /api/v1/contracts/dashboard
+pub async fn get_dashboard() -> DashboardResponse;
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DashboardResponse {
+ /// Count by status
+ pub status_counts: HashMap<ContractStatus, usize>,
+ /// Count by phase (for active contracts)
+ pub phase_counts: HashMap<String, usize>,
+ /// Stale contracts
+ pub stale_contracts: Vec<StaleContractInfo>,
+ /// Contracts waiting for input
+ pub waiting_for_input: Vec<WaitingContractInfo>,
+ /// Recent activity
+ pub recent_events: Vec<ContractMonitorEvent>,
+ /// Resource usage
+ pub resource_usage: ResourceUsage,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct StaleContractInfo {
+ pub id: Uuid,
+ pub name: String,
+ pub phase: String,
+ pub last_activity: DateTime<Utc>,
+ pub stale_duration_secs: i64,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct WaitingContractInfo {
+ pub id: Uuid,
+ pub name: String,
+ pub waiting_for: String, // 'question', 'phase_confirmation', etc.
+ pub waiting_since: DateTime<Utc>,
+ pub question: Option<String>,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ResourceUsage {
+ pub active_supervisors: usize,
+ pub running_tasks: usize,
+ pub pending_tasks: usize,
+ pub active_daemons: usize,
+ pub total_worktrees: usize,
+}
+```
+
+---
+
+## 5. Improved CLI Commands
+
+### 5.1 Contract Listing with Filters
+
+```bash
+# List all contracts
+makima contracts list
+
+# List with status filter
+makima contracts list --status active
+makima contracts list --status completed,failed
+
+# List stale contracts
+makima contracts list --stale
+makima contracts list --stale --threshold 30m
+
+# List contracts waiting for input
+makima contracts list --waiting
+
+# List by phase
+makima contracts list --phase execute
+
+# Combine filters
+makima contracts list --status active --phase plan --stale
+
+# Output formats
+makima contracts list --format json
+makima contracts list --format table
+makima contracts list --format compact
+```
+
+#### Implementation
+
+```rust
+#[derive(Debug, Args)]
+pub struct ListContractsArgs {
+ /// Filter by status (comma-separated)
+ #[arg(long)]
+ pub status: Option<String>,
+
+ /// Show only stale contracts
+ #[arg(long)]
+ pub stale: bool,
+
+ /// Stale threshold (e.g., "30m", "1h")
+ #[arg(long, default_value = "30m")]
+ pub threshold: String,
+
+ /// Show contracts waiting for input
+ #[arg(long)]
+ pub waiting: bool,
+
+ /// Filter by phase
+ #[arg(long)]
+ pub phase: Option<String>,
+
+ /// Output format
+ #[arg(long, default_value = "table")]
+ pub format: OutputFormat,
+
+ /// Limit results
+ #[arg(long, short = 'n')]
+ pub limit: Option<usize>,
+}
+```
+
+### 5.2 Cleanup Command
+
+```bash
+# Archive completed contracts older than 7 days
+makima contracts cleanup --archive --older-than 7d
+
+# Delete archived contracts older than 30 days
+makima contracts cleanup --delete-archived --older-than 30d
+
+# Dry run (show what would be affected)
+makima contracts cleanup --archive --older-than 7d --dry-run
+
+# Force cleanup without confirmation
+makima contracts cleanup --archive --older-than 7d --force
+
+# Cleanup stale worktrees
+makima contracts cleanup --worktrees
+
+# Full cleanup: archive old, delete archived, clean worktrees
+makima contracts cleanup --all --older-than 7d
+```
+
+#### Implementation
+
+```rust
+#[derive(Debug, Args)]
+pub struct CleanupContractsArgs {
+ /// Archive completed/failed contracts
+ #[arg(long)]
+ pub archive: bool,
+
+ /// Delete archived contracts
+ #[arg(long)]
+ pub delete_archived: bool,
+
+ /// Clean up orphaned worktrees
+ #[arg(long)]
+ pub worktrees: bool,
+
+ /// Run all cleanup operations
+ #[arg(long)]
+ pub all: bool,
+
+ /// Threshold for cleanup (e.g., "7d", "30d")
+ #[arg(long, default_value = "7d")]
+ pub older_than: String,
+
+ /// Dry run - show what would be affected
+ #[arg(long)]
+ pub dry_run: bool,
+
+ /// Skip confirmation prompts
+ #[arg(long)]
+ pub force: bool,
+}
+```
+
+### 5.3 Monitor Command
+
+```bash
+# Real-time monitoring dashboard
+makima contracts monitor
+
+# Monitor specific contracts
+makima contracts monitor <contract-id> <contract-id>
+
+# Monitor with filters
+makima contracts monitor --status active
+makima contracts monitor --stale
+
+# Quiet mode - only show important events
+makima contracts monitor --quiet
+
+# JSON output for scripting
+makima contracts monitor --format json
+```
+
+#### Implementation
+
+```rust
+#[derive(Debug, Args)]
+pub struct MonitorContractsArgs {
+ /// Contract IDs to monitor (empty = all)
+ pub contract_ids: Vec<Uuid>,
+
+ /// Filter by status
+ #[arg(long)]
+ pub status: Option<String>,
+
+ /// Only show stale contracts
+ #[arg(long)]
+ pub stale: bool,
+
+ /// Quiet mode - only important events
+ #[arg(long, short)]
+ pub quiet: bool,
+
+ /// Output format
+ #[arg(long, default_value = "tui")]
+ pub format: MonitorFormat,
+}
+
+#[derive(Debug, Clone, ValueEnum)]
+pub enum MonitorFormat {
+ /// Terminal UI dashboard
+ Tui,
+ /// Plain text output
+ Text,
+ /// JSON stream
+ Json,
+}
+```
+
+### 5.4 Additional Commands
+
+```bash
+# Resume a paused contract
+makima contracts resume <contract-id>
+
+# Pause an active contract
+makima contracts pause <contract-id> --reason "Waiting for external review"
+
+# Force advance phase
+makima contracts advance <contract-id> --phase execute --force
+
+# Restart stale supervisor
+makima contracts restart-supervisor <contract-id>
+
+# Show contract details
+makima contracts show <contract-id> --verbose
+
+# Check contract health
+makima contracts health <contract-id>
+
+# Export contract history
+makima contracts export <contract-id> --format json --output contract.json
+```
+
+---
+
+## 6. Bug Fixes
+
+### 6.1 Version Conflicts (Silent Failures)
+
+**Problem**: Phase changes can fail silently when version conflicts occur.
+
+**Solution**: Implement explicit version checking and conflict reporting.
+
+```rust
+/// Result type for phase changes with explicit conflict handling
+pub enum PhaseChangeResult {
+ Success(Contract),
+ VersionConflict {
+ expected: i32,
+ actual: i32,
+ current_phase: String,
+ },
+ ValidationFailed {
+ reason: String,
+ missing_requirements: Vec<String>,
+ },
+ Unauthorized,
+ NotFound,
+}
+
+/// Enhanced phase change handler
+pub async fn change_phase_with_validation(
+ pool: &PgPool,
+ contract_id: Uuid,
+ owner_id: Uuid,
+ new_phase: &str,
+ expected_version: Option<i32>,
+) -> Result<PhaseChangeResult, Error> {
+ // Start transaction
+ let mut tx = pool.begin().await?;
+
+ // Get current contract with lock
+ let contract = sqlx::query_as::<_, Contract>(
+ "SELECT * FROM contracts WHERE id = $1 AND owner_id = $2 FOR UPDATE"
+ )
+ .bind(contract_id)
+ .bind(owner_id)
+ .fetch_optional(&mut *tx)
+ .await?;
+
+ let contract = match contract {
+ Some(c) => c,
+ None => return Ok(PhaseChangeResult::NotFound),
+ };
+
+ // Check version if provided
+ if let Some(expected) = expected_version {
+ if contract.version != expected {
+ return Ok(PhaseChangeResult::VersionConflict {
+ expected,
+ actual: contract.version,
+ current_phase: contract.phase.clone(),
+ });
+ }
+ }
+
+ // Validate phase transition
+ let validation = validate_phase_transition(&contract, new_phase);
+ if !validation.valid {
+ return Ok(PhaseChangeResult::ValidationFailed {
+ reason: validation.reason,
+ missing_requirements: validation.missing,
+ });
+ }
+
+ // Update phase
+ let updated = sqlx::query_as::<_, Contract>(
+ r#"
+ UPDATE contracts
+ SET phase = $1, version = version + 1, updated_at = NOW()
+ WHERE id = $2
+ RETURNING *
+ "#
+ )
+ .bind(new_phase)
+ .bind(contract_id)
+ .fetch_one(&mut *tx)
+ .await?;
+
+ tx.commit().await?;
+
+ Ok(PhaseChangeResult::Success(updated))
+}
+```
+
+### 6.2 Deliverable Validation
+
+**Problem**: Can mark non-existent deliverables as complete.
+
+**Solution**: Validate deliverable IDs before marking complete.
+
+```rust
+/// Validate deliverable exists for contract type and phase
+pub fn validate_deliverable(
+ contract_type: &str,
+ phase: &str,
+ deliverable_id: &str,
+ phase_config: Option<&PhaseConfig>,
+) -> Result<(), DeliverableValidationError> {
+ let deliverables = if let Some(config) = phase_config {
+ get_phase_deliverables_from_config(phase, config)
+ } else {
+ get_phase_deliverables_for_type(phase, contract_type)
+ };
+
+ let valid_ids: Vec<&str> = deliverables
+ .deliverables
+ .iter()
+ .map(|d| d.id.as_str())
+ .collect();
+
+ if !valid_ids.contains(&deliverable_id) {
+ return Err(DeliverableValidationError::InvalidDeliverable {
+ deliverable_id: deliverable_id.to_string(),
+ phase: phase.to_string(),
+ valid_ids: valid_ids.into_iter().map(String::from).collect(),
+ });
+ }
+
+ Ok(())
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum DeliverableValidationError {
+ #[error("Invalid deliverable '{deliverable_id}' for {phase} phase. Valid IDs: {valid_ids:?}")]
+ InvalidDeliverable {
+ deliverable_id: String,
+ phase: String,
+ valid_ids: Vec<String>,
+ },
+}
+```
+
+### 6.3 Phase Guard Bypass
+
+**Problem**: Supervisors can bypass phase_guard setting.
+
+**Solution**: Enforce phase_guard at the API level, not just in supervisor logic.
+
+```rust
+/// Enhanced phase change with phase_guard enforcement
+pub async fn change_phase_enforced(
+ pool: &PgPool,
+ contract_id: Uuid,
+ owner_id: Uuid,
+ request: ChangePhaseRequest,
+ is_supervisor: bool,
+) -> Result<PhaseChangeResponse, Error> {
+ let contract = get_contract_for_owner(pool, contract_id, owner_id).await?
+ .ok_or_else(|| Error::NotFound)?;
+
+ // Phase guard is enforced for EVERYONE, including supervisors
+ if contract.phase_guard && !request.confirmed.unwrap_or(false) {
+ // Must return phase review info, regardless of caller
+ return Ok(PhaseChangeResponse::RequiresConfirmation {
+ current_phase: contract.phase,
+ next_phase: request.phase,
+ deliverables: get_phase_deliverables(&contract.phase),
+ message: "Phase guard is enabled. User confirmation required.".to_string(),
+ });
+ }
+
+ // Proceed with phase change
+ // ...
+}
+```
+
+---
+
+## 7. Migration Plan
+
+### 7.1 Phase 1: Database Schema (Week 1)
+
+1. Add new columns to `contracts` table
+2. Add new columns to `supervisor_states` table
+3. Create `supervisor_heartbeats` table
+4. Create indexes
+5. Backfill `last_activity_at` from existing data
+
+```sql
+-- Migration 001: Contract status enhancements
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS status_changed_at TIMESTAMPTZ DEFAULT NOW();
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS last_activity_at TIMESTAMPTZ DEFAULT NOW();
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS waiting_for TEXT;
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS blocked_reason TEXT;
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS failure_reason TEXT;
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS auto_complete_enabled BOOLEAN DEFAULT TRUE;
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS completion_detected_at TIMESTAMPTZ;
+
+CREATE INDEX IF NOT EXISTS idx_contracts_status ON contracts(status);
+CREATE INDEX IF NOT EXISTS idx_contracts_last_activity ON contracts(last_activity_at);
+
+-- Backfill last_activity_at from updated_at
+UPDATE contracts SET last_activity_at = updated_at WHERE last_activity_at IS NULL;
+```
+
+### 7.2 Phase 2: Core Logic (Week 2)
+
+1. Implement `ContractStatus` enum with new states
+2. Implement state transition validation
+3. Implement `CompletionDetector`
+4. Update phase change handlers with validation
+5. Implement deliverable validation
+
+### 7.3 Phase 3: Supervisor Enhancements (Week 3)
+
+1. Implement `SupervisorState` enum
+2. Implement heartbeat mechanism
+3. Implement state persistence
+4. Implement restoration protocol
+5. Update supervisor API endpoints
+
+### 7.4 Phase 4: Monitoring (Week 4)
+
+1. Implement WebSocket monitoring events
+2. Implement stale detection service
+3. Implement batch operations
+4. Implement dashboard API
+
+### 7.5 Phase 5: CLI (Week 5)
+
+1. Implement `contracts list` with filters
+2. Implement `contracts cleanup`
+3. Implement `contracts monitor`
+4. Implement additional helper commands
+
+### 7.6 Phase 6: Testing & Rollout (Week 6)
+
+1. Unit tests for all new components
+2. Integration tests for state machines
+3. Load testing for monitoring
+4. Staged rollout with feature flags
+5. Documentation updates
+
+---
+
+## 8. Appendix
+
+### 8.1 Configuration Options
+
+```toml
+[contracts]
+# Timeout configuration
+stale_threshold_minutes = 30
+input_timeout_hours = 24
+completion_grace_period_minutes = 5
+archive_retention_days = 30
+
+# Auto-completion
+auto_complete_enabled = true
+auto_advance_phases = true
+
+# Heartbeat
+heartbeat_interval_seconds = 30
+heartbeat_timeout_seconds = 120
+
+# Monitoring
+monitor_ws_buffer_size = 1000
+stale_detection_interval_seconds = 60
+```
+
+### 8.2 Error Codes
+
+| Code | Description |
+|------|-------------|
+| `CONTRACT_NOT_FOUND` | Contract does not exist |
+| `INVALID_TRANSITION` | State transition not allowed |
+| `VERSION_CONFLICT` | Optimistic locking conflict |
+| `PHASE_GUARD_REQUIRED` | Phase guard confirmation needed |
+| `INVALID_DELIVERABLE` | Deliverable ID not valid for phase |
+| `SUPERVISOR_NOT_FOUND` | No supervisor for contract |
+| `SUPERVISOR_DEAD` | Supervisor heartbeat timeout |
+| `VALIDATION_FAILED` | Phase requirements not met |
+
+### 8.3 Metrics
+
+The following metrics should be tracked:
+
+- `contracts_by_status` (gauge) - Count of contracts by status
+- `contracts_stale_count` (gauge) - Number of stale contracts
+- `phase_transitions_total` (counter) - Phase changes by from/to
+- `completion_detections_total` (counter) - Auto-completions detected
+- `supervisor_heartbeats_total` (counter) - Heartbeats received
+- `supervisor_restarts_total` (counter) - Supervisor restarts
+- `batch_operations_total` (counter) - Batch operations by type