summaryrefslogtreecommitdiff
path: root/makima/migrations
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-11 05:52:14 +0000
committersoryu <soryu@soryu.co>2026-01-15 00:21:16 +0000
commit87044a747b47bd83249d61a45842c7f7b2eae56d (patch)
treeef2000ce79ffcc2723ef841acef5aa1deb1d5378 /makima/migrations
parent077820c4167c168072d217a1b01df840463a12a8 (diff)
downloadsoryu-87044a747b47bd83249d61a45842c7f7b2eae56d.tar.gz
soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.zip
Contract system
Diffstat (limited to 'makima/migrations')
-rw-r--r--makima/migrations/20250110100000_create_contracts.sql71
-rw-r--r--makima/migrations/20250112000000_cascade_delete_contract_children.sql15
-rw-r--r--makima/migrations/20250112100000_create_contract_chat_history.sql33
-rw-r--r--makima/migrations/20250113000000_add_repo_file_path.sql15
-rw-r--r--makima/migrations/20250114000000_task_tree_structure.sql41
-rw-r--r--makima/migrations/20250114000001_task_checkpoints.sql24
-rw-r--r--makima/migrations/20250114000002_daemon_capabilities.sql27
-rw-r--r--makima/migrations/20250114000003_supervisor_state.sql31
8 files changed, 257 insertions, 0 deletions
diff --git a/makima/migrations/20250110100000_create_contracts.sql b/makima/migrations/20250110100000_create_contracts.sql
new file mode 100644
index 0000000..3532b8e
--- /dev/null
+++ b/makima/migrations/20250110100000_create_contracts.sql
@@ -0,0 +1,71 @@
+-- Contracts table (hierarchical - contracts can contain sub-contracts)
+CREATE TABLE IF NOT EXISTS contracts (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ owner_id UUID NOT NULL REFERENCES owners(id) ON DELETE CASCADE,
+ parent_contract_id UUID REFERENCES contracts(id) ON DELETE CASCADE, -- NULL = root contract
+ name VARCHAR(255) NOT NULL,
+ description TEXT,
+ phase VARCHAR(32) NOT NULL DEFAULT 'research', -- research/specify/plan/execute/review
+ status VARCHAR(32) NOT NULL DEFAULT 'active', -- active/completed/archived
+ version INTEGER NOT NULL DEFAULT 1,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_contracts_owner_id ON contracts(owner_id);
+CREATE INDEX idx_contracts_parent_contract_id ON contracts(parent_contract_id);
+CREATE INDEX idx_contracts_status ON contracts(status);
+CREATE INDEX idx_contracts_phase ON contracts(phase);
+
+-- Contract repositories (1-to-many: contract has multiple repos)
+-- Supports: existing remote (GitHub, etc), existing local, or new repos created by daemon
+CREATE TABLE IF NOT EXISTS contract_repositories (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ contract_id UUID NOT NULL REFERENCES contracts(id) ON DELETE CASCADE,
+ name VARCHAR(255) NOT NULL, -- display name / repo name for creation
+ repository_url VARCHAR(512), -- NULL until created (for new repos)
+ local_path VARCHAR(512), -- local filesystem path (for local repos)
+ source_type VARCHAR(32) NOT NULL DEFAULT 'remote', -- remote/local/managed
+ status VARCHAR(32) NOT NULL DEFAULT 'ready', -- ready/pending/creating/failed
+ is_primary BOOLEAN NOT NULL DEFAULT false, -- primary repo for task defaults
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+-- source_type values:
+-- 'remote' = existing remote repo (GitHub, GitLab, etc) - has repository_url
+-- 'local' = existing local repo - has local_path
+-- 'managed' = new repo created/managed by Makima daemon - gets repository_url after creation
+
+-- status values:
+-- 'ready' = repo is usable (existing repos start here)
+-- 'pending' = waiting for daemon to create
+-- 'creating' = daemon is creating the repo
+-- 'failed' = creation failed
+
+CREATE INDEX idx_contract_repositories_contract_id ON contract_repositories(contract_id);
+CREATE INDEX idx_contract_repositories_status ON contract_repositories(status);
+-- Only one primary per contract
+CREATE UNIQUE INDEX idx_contract_repositories_primary ON contract_repositories(contract_id) WHERE is_primary = true;
+
+-- Add contract_id to files table (one-to-one)
+ALTER TABLE files ADD COLUMN IF NOT EXISTS contract_id UUID REFERENCES contracts(id) ON DELETE SET NULL;
+ALTER TABLE files ADD COLUMN IF NOT EXISTS contract_phase VARCHAR(32);
+CREATE INDEX IF NOT EXISTS idx_files_contract_id ON files(contract_id);
+
+-- Add contract_id to tasks table (already nullable for backward compat)
+ALTER TABLE tasks ADD COLUMN IF NOT EXISTS contract_id UUID REFERENCES contracts(id) ON DELETE SET NULL;
+CREATE INDEX IF NOT EXISTS idx_tasks_contract_id ON tasks(contract_id);
+
+-- Contract events for audit trail
+CREATE TABLE IF NOT EXISTS contract_events (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ contract_id UUID NOT NULL REFERENCES contracts(id) ON DELETE CASCADE,
+ event_type VARCHAR(64) NOT NULL, -- phase_change, file_added, task_added, status_change
+ previous_phase VARCHAR(32),
+ new_phase VARCHAR(32),
+ event_data JSONB,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_contract_events_contract_id ON contract_events(contract_id);
diff --git a/makima/migrations/20250112000000_cascade_delete_contract_children.sql b/makima/migrations/20250112000000_cascade_delete_contract_children.sql
new file mode 100644
index 0000000..6ef8a43
--- /dev/null
+++ b/makima/migrations/20250112000000_cascade_delete_contract_children.sql
@@ -0,0 +1,15 @@
+-- Change files and tasks to cascade delete when contract is deleted
+-- Previously these were ON DELETE SET NULL, which orphaned records
+
+-- Drop existing foreign key constraints
+ALTER TABLE files DROP CONSTRAINT IF EXISTS files_contract_id_fkey;
+ALTER TABLE tasks DROP CONSTRAINT IF EXISTS tasks_contract_id_fkey;
+
+-- Re-add with CASCADE
+ALTER TABLE files
+ ADD CONSTRAINT files_contract_id_fkey
+ FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE CASCADE;
+
+ALTER TABLE tasks
+ ADD CONSTRAINT tasks_contract_id_fkey
+ FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE CASCADE;
diff --git a/makima/migrations/20250112100000_create_contract_chat_history.sql b/makima/migrations/20250112100000_create_contract_chat_history.sql
new file mode 100644
index 0000000..c4d71d8
--- /dev/null
+++ b/makima/migrations/20250112100000_create_contract_chat_history.sql
@@ -0,0 +1,33 @@
+-- Create contract_chat_conversations table for storing conversation threads per contract
+CREATE TABLE IF NOT EXISTS contract_chat_conversations (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ contract_id UUID NOT NULL REFERENCES contracts(id) ON DELETE CASCADE,
+ owner_id UUID NOT NULL,
+ name VARCHAR(255),
+ is_active BOOLEAN NOT NULL DEFAULT true,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_contract_chat_conversations_contract ON contract_chat_conversations(contract_id);
+CREATE INDEX idx_contract_chat_conversations_owner ON contract_chat_conversations(owner_id);
+CREATE INDEX idx_contract_chat_conversations_active ON contract_chat_conversations(contract_id, is_active);
+
+CREATE TRIGGER update_contract_chat_conversations_updated_at
+ BEFORE UPDATE ON contract_chat_conversations
+ FOR EACH ROW
+ EXECUTE FUNCTION update_updated_at_column();
+
+-- Create contract_chat_messages table for individual messages
+CREATE TABLE IF NOT EXISTS contract_chat_messages (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ conversation_id UUID NOT NULL REFERENCES contract_chat_conversations(id) ON DELETE CASCADE,
+ role VARCHAR(16) NOT NULL CHECK (role IN ('user', 'assistant', 'error')),
+ content TEXT NOT NULL,
+ tool_calls JSONB,
+ pending_questions JSONB,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_contract_chat_messages_conversation ON contract_chat_messages(conversation_id);
+CREATE INDEX idx_contract_chat_messages_created ON contract_chat_messages(created_at);
diff --git a/makima/migrations/20250113000000_add_repo_file_path.sql b/makima/migrations/20250113000000_add_repo_file_path.sql
new file mode 100644
index 0000000..eb7cec2
--- /dev/null
+++ b/makima/migrations/20250113000000_add_repo_file_path.sql
@@ -0,0 +1,15 @@
+-- Add repository file path linking to files
+-- Files can now be linked to specific file paths in repositories
+
+ALTER TABLE files ADD COLUMN repo_file_path VARCHAR(500) NULL;
+ALTER TABLE files ADD COLUMN repo_synced_at TIMESTAMPTZ NULL;
+ALTER TABLE files ADD COLUMN repo_sync_status VARCHAR(50) DEFAULT 'none';
+-- Status: 'none' (not linked), 'synced' (up to date), 'modified' (local changes), 'conflict'
+
+-- Index for efficient lookup of files by repo path within a contract
+CREATE INDEX idx_files_repo_file_path ON files(contract_id, repo_file_path)
+WHERE repo_file_path IS NOT NULL;
+
+COMMENT ON COLUMN files.repo_file_path IS 'Path to the file in the repository (e.g., README.md, docs/design.md)';
+COMMENT ON COLUMN files.repo_synced_at IS 'When the file was last synced from the repository';
+COMMENT ON COLUMN files.repo_sync_status IS 'Sync status: none, synced, modified, conflict';
diff --git a/makima/migrations/20250114000000_task_tree_structure.sql b/makima/migrations/20250114000000_task_tree_structure.sql
new file mode 100644
index 0000000..489a702
--- /dev/null
+++ b/makima/migrations/20250114000000_task_tree_structure.sql
@@ -0,0 +1,41 @@
+-- Task tree structure changes for supervisor architecture
+-- - Remove depth constraint (supervisor controls task hierarchy)
+-- - Add checkpoint tracking
+-- - Add is_supervisor flag
+-- - Add supervisor_task_id to contracts
+
+-- Drop the depth constraint (supervisor handles task spawning rules at application level)
+ALTER TABLE tasks DROP CONSTRAINT IF EXISTS tasks_depth_check;
+
+-- Add is_supervisor flag to identify contract supervisor tasks
+ALTER TABLE tasks ADD COLUMN IF NOT EXISTS is_supervisor BOOLEAN NOT NULL DEFAULT false;
+
+-- Git checkpoint tracking
+ALTER TABLE tasks ADD COLUMN IF NOT EXISTS last_checkpoint_sha VARCHAR(40);
+ALTER TABLE tasks ADD COLUMN IF NOT EXISTS checkpoint_count INTEGER NOT NULL DEFAULT 0;
+ALTER TABLE tasks ADD COLUMN IF NOT EXISTS checkpoint_message TEXT;
+
+-- Conversation state preservation for task branches
+ALTER TABLE tasks ADD COLUMN IF NOT EXISTS conversation_state JSONB;
+
+-- Daemon migration tracking
+ALTER TABLE tasks ADD COLUMN IF NOT EXISTS migrated_from_daemon_id UUID;
+ALTER TABLE tasks ADD COLUMN IF NOT EXISTS last_active_daemon_id UUID;
+
+-- Add supervisor_task_id to contracts
+ALTER TABLE contracts ADD COLUMN IF NOT EXISTS supervisor_task_id UUID REFERENCES tasks(id) ON DELETE SET NULL;
+
+-- Index for tree queries
+CREATE INDEX IF NOT EXISTS idx_tasks_tree_path ON tasks(contract_id, parent_task_id, created_at);
+
+-- Index for supervisor lookup
+CREATE INDEX IF NOT EXISTS idx_tasks_is_supervisor ON tasks(contract_id, is_supervisor) WHERE is_supervisor = true;
+
+-- Index for checkpoint lookup
+CREATE INDEX IF NOT EXISTS idx_tasks_checkpoint_sha ON tasks(last_checkpoint_sha) WHERE last_checkpoint_sha IS NOT NULL;
+
+COMMENT ON COLUMN tasks.is_supervisor IS 'True for contract supervisor tasks. Only supervisors can spawn new tasks.';
+COMMENT ON COLUMN tasks.last_checkpoint_sha IS 'Git commit SHA of the most recent checkpoint';
+COMMENT ON COLUMN tasks.checkpoint_count IS 'Number of checkpoints created by this task';
+COMMENT ON COLUMN tasks.conversation_state IS 'Saved conversation context for task resumption';
+COMMENT ON COLUMN contracts.supervisor_task_id IS 'The long-running supervisor task that orchestrates this contract';
diff --git a/makima/migrations/20250114000001_task_checkpoints.sql b/makima/migrations/20250114000001_task_checkpoints.sql
new file mode 100644
index 0000000..8692466
--- /dev/null
+++ b/makima/migrations/20250114000001_task_checkpoints.sql
@@ -0,0 +1,24 @@
+-- Task checkpoints table for tracking git commit history per task
+-- Enables branching from any checkpoint
+
+CREATE TABLE IF NOT EXISTS task_checkpoints (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
+ checkpoint_number INTEGER NOT NULL,
+ commit_sha VARCHAR(40) NOT NULL,
+ branch_name VARCHAR(255) NOT NULL,
+ message TEXT NOT NULL,
+ files_changed JSONB, -- Array of {path, action: 'A'|'M'|'D'}
+ lines_added INTEGER DEFAULT 0,
+ lines_removed INTEGER DEFAULT 0,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+
+ UNIQUE(task_id, checkpoint_number)
+);
+
+CREATE INDEX idx_task_checkpoints_task_id ON task_checkpoints(task_id);
+CREATE INDEX idx_task_checkpoints_commit_sha ON task_checkpoints(commit_sha);
+
+COMMENT ON TABLE task_checkpoints IS 'Git commit history for tasks, enabling branching from any checkpoint';
+COMMENT ON COLUMN task_checkpoints.checkpoint_number IS 'Sequential checkpoint number within this task';
+COMMENT ON COLUMN task_checkpoints.files_changed IS 'JSON array of {path, action} for files modified in this commit';
diff --git a/makima/migrations/20250114000002_daemon_capabilities.sql b/makima/migrations/20250114000002_daemon_capabilities.sql
new file mode 100644
index 0000000..0c2e1c2
--- /dev/null
+++ b/makima/migrations/20250114000002_daemon_capabilities.sql
@@ -0,0 +1,27 @@
+-- Daemon capabilities for multi-daemon work distribution
+-- Adds selection scoring and task assignment tracking
+
+-- Extend daemons table for multi-daemon selection
+ALTER TABLE daemons ADD COLUMN IF NOT EXISTS capacity_score INTEGER DEFAULT 100;
+ALTER TABLE daemons ADD COLUMN IF NOT EXISTS task_queue_length INTEGER DEFAULT 0;
+ALTER TABLE daemons ADD COLUMN IF NOT EXISTS supports_migration BOOLEAN DEFAULT true;
+
+-- Track active task assignments per daemon
+-- This allows moving tasks between daemons
+CREATE TABLE IF NOT EXISTS daemon_task_assignments (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ daemon_id UUID NOT NULL REFERENCES daemons(id) ON DELETE CASCADE,
+ task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
+ assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ status VARCHAR(32) NOT NULL DEFAULT 'active', -- active, migrating, completed
+
+ UNIQUE(task_id) -- A task can only be assigned to one daemon at a time
+);
+
+CREATE INDEX idx_daemon_task_assignments_daemon_id ON daemon_task_assignments(daemon_id);
+CREATE INDEX idx_daemon_task_assignments_status ON daemon_task_assignments(status);
+
+COMMENT ON COLUMN daemons.capacity_score IS 'Relative capacity score for daemon selection (higher = more capacity)';
+COMMENT ON COLUMN daemons.task_queue_length IS 'Number of tasks queued/waiting on this daemon';
+COMMENT ON COLUMN daemons.supports_migration IS 'Whether this daemon supports task migration';
+COMMENT ON TABLE daemon_task_assignments IS 'Tracks task-to-daemon assignments for migration support';
diff --git a/makima/migrations/20250114000003_supervisor_state.sql b/makima/migrations/20250114000003_supervisor_state.sql
new file mode 100644
index 0000000..bcfe5e9
--- /dev/null
+++ b/makima/migrations/20250114000003_supervisor_state.sql
@@ -0,0 +1,31 @@
+-- Supervisor state persistence for resumability
+-- Stores conversation history and pending task state for supervisor tasks
+
+CREATE TABLE IF NOT EXISTS supervisor_states (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ contract_id UUID NOT NULL REFERENCES contracts(id) ON DELETE CASCADE,
+ task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
+ conversation_history JSONB NOT NULL DEFAULT '[]', -- Full Claude conversation for resumption
+ last_checkpoint_id UUID REFERENCES task_checkpoints(id) ON DELETE SET NULL,
+ pending_task_ids UUID[] DEFAULT ARRAY[]::UUID[], -- Tasks supervisor is waiting on
+ phase VARCHAR(50) NOT NULL DEFAULT 'research', -- Current contract phase
+ last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+
+ UNIQUE(contract_id) -- One supervisor state per contract
+);
+
+CREATE INDEX idx_supervisor_states_task_id ON supervisor_states(task_id);
+CREATE INDEX idx_supervisor_states_last_activity ON supervisor_states(last_activity);
+
+-- Trigger to update updated_at
+CREATE TRIGGER update_supervisor_states_updated_at
+ BEFORE UPDATE ON supervisor_states
+ FOR EACH ROW
+ EXECUTE FUNCTION update_updated_at_column();
+
+COMMENT ON TABLE supervisor_states IS 'Persisted state for contract supervisors, enabling resumption after interruption';
+COMMENT ON COLUMN supervisor_states.conversation_history IS 'Full Claude conversation history as JSON array';
+COMMENT ON COLUMN supervisor_states.pending_task_ids IS 'Array of task UUIDs the supervisor is waiting on';
+COMMENT ON COLUMN supervisor_states.phase IS 'Current contract phase when supervisor was last active';