From 16c579e6d5b0869a50d59d1bcc661c131556e14f Mon Sep 17 00:00:00 2001 From: Christian Gick Date: Mon, 19 Jan 2026 16:05:19 +0200 Subject: [PATCH] Add deployment tracking schema (Migration 018) Creates deployments and deployment_logs tables for tracking all deployment types (Docker, MCP, iOS/macOS, services). Includes deployment status, timing, git integration, and structured logging. Also adds run-migration.mjs helper for running migrations against PostgreSQL database. Part of CF-290, completes CF-291. Co-Authored-By: Claude Sonnet 4.5 --- migrations/018_deployments.sql | 96 ++++++++++++++++++++++++++++++++++ run-migration.mjs | 62 ++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 migrations/018_deployments.sql create mode 100644 run-migration.mjs diff --git a/migrations/018_deployments.sql b/migrations/018_deployments.sql new file mode 100644 index 0000000..c111cb0 --- /dev/null +++ b/migrations/018_deployments.sql @@ -0,0 +1,96 @@ +-- Migration 018: Deployments tracking for deployment centralization +-- Purpose: Track all deployments (Docker, MCP, iOS/macOS, services) with logs +-- Dependencies: 001_base_schema.sql (tasks table), 010_sessions.sql (sessions table) + +-- Deployments table: Store deployment information linked to sessions and tasks +CREATE TABLE IF NOT EXISTS deployments ( + id SERIAL PRIMARY KEY, + session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL, + task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL, + + -- Project identification + project_name VARCHAR(255) NOT NULL, + project_path TEXT NOT NULL, + + -- Deployment type and method + deployment_type VARCHAR(50) NOT NULL CHECK (deployment_type IN ( + 'docker-compose', + 'mcp-server', + 'ios-macos-app', + 'python-service', + 'node-service' + )), + deployment_method VARCHAR(50) NOT NULL CHECK (deployment_method IN ( + 'doco-cd', + 'agiliton-build', + 'direct', + 'manual' + )), + + -- Status tracking + status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ( + 'pending', + 'running', + 'success', + 'failed', + 'cancelled' + )), + + -- Git integration + commit_sha VARCHAR(40), + git_branch TEXT, + + -- Timing + started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + completed_at TIMESTAMP WITH TIME ZONE, + duration_seconds INTEGER GENERATED ALWAYS AS + (EXTRACT(EPOCH FROM (completed_at - started_at))) STORED, + + -- Error tracking + error_message TEXT, + + -- Extra deployment-specific data (JSON) + metadata JSONB DEFAULT '{}'::jsonb, + + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Deployment logs table: Store deployment log messages +CREATE TABLE IF NOT EXISTS deployment_logs ( + id SERIAL PRIMARY KEY, + deployment_id INT NOT NULL REFERENCES deployments(id) ON DELETE CASCADE, + + timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + level VARCHAR(20) NOT NULL CHECK (level IN ('debug', 'info', 'warn', 'error')), + message TEXT NOT NULL, + + -- Optional structured metadata + metadata JSONB DEFAULT '{}'::jsonb +); + +-- Indexes for efficient querying +CREATE INDEX idx_deployments_project ON deployments(project_name); +CREATE INDEX idx_deployments_session ON deployments(session_id); +CREATE INDEX idx_deployments_task ON deployments(task_id); +CREATE INDEX idx_deployments_status ON deployments(status); +CREATE INDEX idx_deployments_started ON deployments(started_at DESC); +CREATE INDEX idx_deployments_type ON deployments(deployment_type); +CREATE INDEX idx_deployments_commit ON deployments(commit_sha); + +CREATE INDEX idx_deployment_logs_deployment ON deployment_logs(deployment_id); +CREATE INDEX idx_deployment_logs_timestamp ON deployment_logs(timestamp DESC); +CREATE INDEX idx_deployment_logs_level ON deployment_logs(level); + +-- Comments for documentation +COMMENT ON TABLE deployments IS 'Deployment tracking for all project types (Docker, MCP, iOS/macOS, services)'; +COMMENT ON COLUMN deployments.project_name IS 'Human-readable project name'; +COMMENT ON COLUMN deployments.project_path IS 'Absolute filesystem path to project'; +COMMENT ON COLUMN deployments.deployment_type IS 'Type of deployment (docker-compose, mcp-server, ios-macos-app, etc.)'; +COMMENT ON COLUMN deployments.deployment_method IS 'Method used for deployment (doco-cd, agiliton-build, direct, manual)'; +COMMENT ON COLUMN deployments.status IS 'Current deployment status'; +COMMENT ON COLUMN deployments.duration_seconds IS 'Auto-calculated deployment duration in seconds'; +COMMENT ON COLUMN deployments.metadata IS 'Extra deployment-specific data (runtime, host, build number, etc.)'; + +COMMENT ON TABLE deployment_logs IS 'Deployment log messages for debugging and audit trail'; +COMMENT ON COLUMN deployment_logs.level IS 'Log level (debug, info, warn, error)'; +COMMENT ON COLUMN deployment_logs.metadata IS 'Optional structured log metadata (source, context, etc.)'; diff --git a/run-migration.mjs b/run-migration.mjs new file mode 100644 index 0000000..5492e35 --- /dev/null +++ b/run-migration.mjs @@ -0,0 +1,62 @@ +#!/usr/bin/env node +/** + * Run a migration file against the task-mcp database + * Usage: node run-migration.mjs + */ + +import pg from 'pg'; +import { readFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const { Pool } = pg; + +// Database configuration +const pool = new Pool({ + host: process.env.POSTGRES_HOST || 'infra.agiliton.internal', + port: 5432, + database: 'agiliton', + user: 'agiliton', + password: 'QtqiwCOAUpQNF6pjzOMAREzUny2bY8V1', + max: 1, + connectionTimeoutMillis: 5000, +}); + +async function runMigration(migrationFile) { + const client = await pool.connect(); + + try { + console.log(`Running migration: ${migrationFile}`); + + // Read migration file + const migrationPath = join(__dirname, 'migrations', migrationFile); + const sql = readFileSync(migrationPath, 'utf-8'); + + // Execute migration + await client.query(sql); + + console.log(`Migration completed successfully: ${migrationFile}`); + } catch (error) { + console.error(`Migration failed: ${error.message}`); + throw error; + } finally { + client.release(); + await pool.end(); + } +} + +// Get migration file from command line +const migrationFile = process.argv[2]; + +if (!migrationFile) { + console.error('Usage: node run-migration.mjs '); + console.error('Example: node run-migration.mjs 018_deployments.sql'); + process.exit(1); +} + +runMigration(migrationFile) + .then(() => process.exit(0)) + .catch(() => process.exit(1));