feat(#164): Add database schema for job tracking
Add Prisma schema for runner jobs, job steps, and job events to support the autonomous runner infrastructure (M4.2). Enums added: - RunnerJobStatus: PENDING, QUEUED, RUNNING, COMPLETED, FAILED, CANCELLED - JobStepPhase: SETUP, EXECUTION, VALIDATION, CLEANUP - JobStepType: COMMAND, AI_ACTION, GATE, ARTIFACT - JobStepStatus: PENDING, RUNNING, COMPLETED, FAILED, SKIPPED Models added: - RunnerJob: Top-level job tracking linked to workspace and agent_tasks - JobStep: Granular step tracking within jobs with phase organization - JobEvent: Immutable event sourcing audit log for jobs and steps Foreign key relationships: - runner_jobs → workspaces (workspace_id, CASCADE) - runner_jobs → agent_tasks (agent_task_id, SET NULL) - job_steps → runner_jobs (job_id, CASCADE) - job_events → runner_jobs (job_id, CASCADE) - job_events → job_steps (step_id, CASCADE) Indexes added for performance on workspace_id, status, priority, timestamp. Migration: 20260201205935_add_job_tracking Quality gates passed: typecheck, lint, build Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "RunnerJobStatus" AS ENUM ('PENDING', 'QUEUED', 'RUNNING', 'COMPLETED', 'FAILED', 'CANCELLED');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "JobStepPhase" AS ENUM ('SETUP', 'EXECUTION', 'VALIDATION', 'CLEANUP');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "JobStepType" AS ENUM ('COMMAND', 'AI_ACTION', 'GATE', 'ARTIFACT');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "JobStepStatus" AS ENUM ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED', 'SKIPPED');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "runner_jobs" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"workspace_id" UUID NOT NULL,
|
||||||
|
"agent_task_id" UUID,
|
||||||
|
"type" TEXT NOT NULL,
|
||||||
|
"status" "RunnerJobStatus" NOT NULL DEFAULT 'PENDING',
|
||||||
|
"priority" INTEGER NOT NULL,
|
||||||
|
"progress_percent" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"result" JSONB,
|
||||||
|
"error" TEXT,
|
||||||
|
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"started_at" TIMESTAMPTZ,
|
||||||
|
"completed_at" TIMESTAMPTZ,
|
||||||
|
|
||||||
|
CONSTRAINT "runner_jobs_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "job_steps" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"job_id" UUID NOT NULL,
|
||||||
|
"ordinal" INTEGER NOT NULL,
|
||||||
|
"phase" "JobStepPhase" NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"type" "JobStepType" NOT NULL,
|
||||||
|
"status" "JobStepStatus" NOT NULL DEFAULT 'PENDING',
|
||||||
|
"output" TEXT,
|
||||||
|
"tokens_input" INTEGER,
|
||||||
|
"tokens_output" INTEGER,
|
||||||
|
"started_at" TIMESTAMPTZ,
|
||||||
|
"completed_at" TIMESTAMPTZ,
|
||||||
|
"duration_ms" INTEGER,
|
||||||
|
|
||||||
|
CONSTRAINT "job_steps_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "job_events" (
|
||||||
|
"id" UUID NOT NULL,
|
||||||
|
"job_id" UUID NOT NULL,
|
||||||
|
"step_id" UUID,
|
||||||
|
"type" TEXT NOT NULL,
|
||||||
|
"timestamp" TIMESTAMPTZ NOT NULL,
|
||||||
|
"actor" TEXT NOT NULL,
|
||||||
|
"payload" JSONB NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "job_events_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "runner_jobs_id_workspace_id_key" ON "runner_jobs"("id", "workspace_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "runner_jobs_workspace_id_idx" ON "runner_jobs"("workspace_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "runner_jobs_workspace_id_status_idx" ON "runner_jobs"("workspace_id", "status");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "runner_jobs_agent_task_id_idx" ON "runner_jobs"("agent_task_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "runner_jobs_priority_idx" ON "runner_jobs"("priority");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "job_steps_job_id_idx" ON "job_steps"("job_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "job_steps_job_id_ordinal_idx" ON "job_steps"("job_id", "ordinal");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "job_steps_status_idx" ON "job_steps"("status");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "job_events_job_id_idx" ON "job_events"("job_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "job_events_step_id_idx" ON "job_events"("step_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "job_events_timestamp_idx" ON "job_events"("timestamp");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "job_events_type_idx" ON "job_events"("type");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "runner_jobs" ADD CONSTRAINT "runner_jobs_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "runner_jobs" ADD CONSTRAINT "runner_jobs_agent_task_id_fkey" FOREIGN KEY ("agent_task_id") REFERENCES "agent_tasks"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "job_steps" ADD CONSTRAINT "job_steps_job_id_fkey" FOREIGN KEY ("job_id") REFERENCES "runner_jobs"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "job_events" ADD CONSTRAINT "job_events_job_id_fkey" FOREIGN KEY ("job_id") REFERENCES "runner_jobs"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "job_events" ADD CONSTRAINT "job_events_step_id_fkey" FOREIGN KEY ("step_id") REFERENCES "job_steps"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -135,6 +135,37 @@ enum FormalityLevel {
|
|||||||
VERY_FORMAL
|
VERY_FORMAL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RunnerJobStatus {
|
||||||
|
PENDING
|
||||||
|
QUEUED
|
||||||
|
RUNNING
|
||||||
|
COMPLETED
|
||||||
|
FAILED
|
||||||
|
CANCELLED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JobStepPhase {
|
||||||
|
SETUP
|
||||||
|
EXECUTION
|
||||||
|
VALIDATION
|
||||||
|
CLEANUP
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JobStepType {
|
||||||
|
COMMAND
|
||||||
|
AI_ACTION
|
||||||
|
GATE
|
||||||
|
ARTIFACT
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JobStepStatus {
|
||||||
|
PENDING
|
||||||
|
RUNNING
|
||||||
|
COMPLETED
|
||||||
|
FAILED
|
||||||
|
SKIPPED
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// MODELS
|
// MODELS
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -151,24 +182,24 @@ model User {
|
|||||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
ownedWorkspaces Workspace[] @relation("WorkspaceOwner")
|
ownedWorkspaces Workspace[] @relation("WorkspaceOwner")
|
||||||
workspaceMemberships WorkspaceMember[]
|
workspaceMemberships WorkspaceMember[]
|
||||||
teamMemberships TeamMember[]
|
teamMemberships TeamMember[]
|
||||||
assignedTasks Task[] @relation("TaskAssignee")
|
assignedTasks Task[] @relation("TaskAssignee")
|
||||||
createdTasks Task[] @relation("TaskCreator")
|
createdTasks Task[] @relation("TaskCreator")
|
||||||
createdEvents Event[] @relation("EventCreator")
|
createdEvents Event[] @relation("EventCreator")
|
||||||
createdProjects Project[] @relation("ProjectCreator")
|
createdProjects Project[] @relation("ProjectCreator")
|
||||||
activityLogs ActivityLog[]
|
activityLogs ActivityLog[]
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
accounts Account[]
|
accounts Account[]
|
||||||
ideas Idea[] @relation("IdeaCreator")
|
ideas Idea[] @relation("IdeaCreator")
|
||||||
relationships Relationship[] @relation("RelationshipCreator")
|
relationships Relationship[] @relation("RelationshipCreator")
|
||||||
agentSessions AgentSession[]
|
agentSessions AgentSession[]
|
||||||
agentTasks AgentTask[] @relation("AgentTaskCreator")
|
agentTasks AgentTask[] @relation("AgentTaskCreator")
|
||||||
userLayouts UserLayout[]
|
userLayouts UserLayout[]
|
||||||
userPreference UserPreference?
|
userPreference UserPreference?
|
||||||
knowledgeEntryVersions KnowledgeEntryVersion[] @relation("EntryVersionAuthor")
|
knowledgeEntryVersions KnowledgeEntryVersion[] @relation("EntryVersionAuthor")
|
||||||
llmProviders LlmProviderInstance[] @relation("UserLlmProviders")
|
llmProviders LlmProviderInstance[] @relation("UserLlmProviders")
|
||||||
|
|
||||||
@@map("users")
|
@@map("users")
|
||||||
}
|
}
|
||||||
@@ -195,7 +226,7 @@ model Workspace {
|
|||||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
owner User @relation("WorkspaceOwner", fields: [ownerId], references: [id], onDelete: Cascade)
|
owner User @relation("WorkspaceOwner", fields: [ownerId], references: [id], onDelete: Cascade)
|
||||||
members WorkspaceMember[]
|
members WorkspaceMember[]
|
||||||
teams Team[]
|
teams Team[]
|
||||||
tasks Task[]
|
tasks Task[]
|
||||||
@@ -216,6 +247,7 @@ model Workspace {
|
|||||||
personalities Personality[]
|
personalities Personality[]
|
||||||
llmSettings WorkspaceLlmSettings?
|
llmSettings WorkspaceLlmSettings?
|
||||||
qualityGates QualityGate[]
|
qualityGates QualityGate[]
|
||||||
|
runnerJobs RunnerJob[]
|
||||||
|
|
||||||
@@index([ownerId])
|
@@index([ownerId])
|
||||||
@@map("workspaces")
|
@@map("workspaces")
|
||||||
@@ -565,8 +597,8 @@ model Agent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model AgentTask {
|
model AgentTask {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
workspaceId String @map("workspace_id") @db.Uuid
|
workspaceId String @map("workspace_id") @db.Uuid
|
||||||
|
|
||||||
// Task details
|
// Task details
|
||||||
title String
|
title String
|
||||||
@@ -575,23 +607,24 @@ model AgentTask {
|
|||||||
priority AgentTaskPriority @default(MEDIUM)
|
priority AgentTaskPriority @default(MEDIUM)
|
||||||
|
|
||||||
// Agent configuration
|
// Agent configuration
|
||||||
agentType String @map("agent_type")
|
agentType String @map("agent_type")
|
||||||
agentConfig Json @default("{}") @map("agent_config")
|
agentConfig Json @default("{}") @map("agent_config")
|
||||||
|
|
||||||
// Results
|
// Results
|
||||||
result Json?
|
result Json?
|
||||||
error String? @db.Text
|
error String? @db.Text
|
||||||
|
|
||||||
// Timing
|
// Timing
|
||||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
||||||
startedAt DateTime? @map("started_at") @db.Timestamptz
|
startedAt DateTime? @map("started_at") @db.Timestamptz
|
||||||
completedAt DateTime? @map("completed_at") @db.Timestamptz
|
completedAt DateTime? @map("completed_at") @db.Timestamptz
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||||
createdBy User @relation("AgentTaskCreator", fields: [createdById], references: [id], onDelete: Cascade)
|
createdBy User @relation("AgentTaskCreator", fields: [createdById], references: [id], onDelete: Cascade)
|
||||||
createdById String @map("created_by_id") @db.Uuid
|
createdById String @map("created_by_id") @db.Uuid
|
||||||
|
runnerJobs RunnerJob[]
|
||||||
|
|
||||||
@@unique([id, workspaceId])
|
@@unique([id, workspaceId])
|
||||||
@@index([workspaceId])
|
@@index([workspaceId])
|
||||||
@@ -890,18 +923,18 @@ model KnowledgeEmbedding {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
model CronSchedule {
|
model CronSchedule {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
workspaceId String @map("workspace_id") @db.Uuid
|
workspaceId String @map("workspace_id") @db.Uuid
|
||||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
// Cron configuration
|
// Cron configuration
|
||||||
expression String // Standard cron: "0 9 * * *" = 9am daily
|
expression String // Standard cron: "0 9 * * *" = 9am daily
|
||||||
command String // MoltBot command to trigger
|
command String // MoltBot command to trigger
|
||||||
|
|
||||||
// State
|
// State
|
||||||
enabled Boolean @default(true)
|
enabled Boolean @default(true)
|
||||||
lastRun DateTime? @map("last_run") @db.Timestamptz
|
lastRun DateTime? @map("last_run") @db.Timestamptz
|
||||||
nextRun DateTime? @map("next_run") @db.Timestamptz
|
nextRun DateTime? @map("next_run") @db.Timestamptz
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||||
@@ -918,22 +951,22 @@ model CronSchedule {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
model Personality {
|
model Personality {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
workspaceId String @map("workspace_id") @db.Uuid
|
workspaceId String @map("workspace_id") @db.Uuid
|
||||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
// Identity
|
// Identity
|
||||||
name String // unique identifier slug
|
name String // unique identifier slug
|
||||||
displayName String @map("display_name")
|
displayName String @map("display_name")
|
||||||
description String? @db.Text
|
description String? @db.Text
|
||||||
|
|
||||||
// System prompt
|
// System prompt
|
||||||
systemPrompt String @map("system_prompt") @db.Text
|
systemPrompt String @map("system_prompt") @db.Text
|
||||||
|
|
||||||
// LLM configuration
|
// LLM configuration
|
||||||
temperature Float? // null = use provider default
|
temperature Float? // null = use provider default
|
||||||
maxTokens Int? @map("max_tokens") // null = use provider default
|
maxTokens Int? @map("max_tokens") // null = use provider default
|
||||||
llmProviderInstanceId String? @map("llm_provider_instance_id") @db.Uuid
|
llmProviderInstanceId String? @map("llm_provider_instance_id") @db.Uuid
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
isDefault Boolean @default(false) @map("is_default")
|
isDefault Boolean @default(false) @map("is_default")
|
||||||
@@ -961,20 +994,20 @@ model Personality {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
model LlmProviderInstance {
|
model LlmProviderInstance {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
providerType String @map("provider_type") // "ollama" | "claude" | "openai"
|
providerType String @map("provider_type") // "ollama" | "claude" | "openai"
|
||||||
displayName String @map("display_name")
|
displayName String @map("display_name")
|
||||||
userId String? @map("user_id") @db.Uuid // NULL = system-level, UUID = user-level
|
userId String? @map("user_id") @db.Uuid // NULL = system-level, UUID = user-level
|
||||||
config Json // Provider-specific configuration
|
config Json // Provider-specific configuration
|
||||||
isDefault Boolean @default(false) @map("is_default")
|
isDefault Boolean @default(false) @map("is_default")
|
||||||
isEnabled Boolean @default(true) @map("is_enabled")
|
isEnabled Boolean @default(true) @map("is_enabled")
|
||||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
user User? @relation("UserLlmProviders", fields: [userId], references: [id], onDelete: Cascade)
|
user User? @relation("UserLlmProviders", fields: [userId], references: [id], onDelete: Cascade)
|
||||||
personalities Personality[] @relation("PersonalityLlmProvider")
|
personalities Personality[] @relation("PersonalityLlmProvider")
|
||||||
workspaceLlmSettings WorkspaceLlmSettings[] @relation("WorkspaceLlmProvider")
|
workspaceLlmSettings WorkspaceLlmSettings[] @relation("WorkspaceLlmProvider")
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([providerType])
|
@@index([providerType])
|
||||||
@@ -1010,20 +1043,20 @@ model WorkspaceLlmSettings {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
model QualityGate {
|
model QualityGate {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
workspaceId String @map("workspace_id") @db.Uuid
|
workspaceId String @map("workspace_id") @db.Uuid
|
||||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||||
name String
|
name String
|
||||||
description String?
|
description String?
|
||||||
type String // 'build' | 'lint' | 'test' | 'coverage' | 'custom'
|
type String // 'build' | 'lint' | 'test' | 'coverage' | 'custom'
|
||||||
command String?
|
command String?
|
||||||
expectedOutput String? @map("expected_output")
|
expectedOutput String? @map("expected_output")
|
||||||
isRegex Boolean @default(false) @map("is_regex")
|
isRegex Boolean @default(false) @map("is_regex")
|
||||||
required Boolean @default(true)
|
required Boolean @default(true)
|
||||||
order Int @default(0)
|
order Int @default(0)
|
||||||
isEnabled Boolean @default(true) @map("is_enabled")
|
isEnabled Boolean @default(true) @map("is_enabled")
|
||||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
||||||
|
|
||||||
@@unique([workspaceId, name])
|
@@unique([workspaceId, name])
|
||||||
@@index([workspaceId])
|
@@index([workspaceId])
|
||||||
@@ -1032,19 +1065,19 @@ model QualityGate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model TaskRejection {
|
model TaskRejection {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
taskId String @map("task_id")
|
taskId String @map("task_id")
|
||||||
workspaceId String @map("workspace_id")
|
workspaceId String @map("workspace_id")
|
||||||
agentId String @map("agent_id")
|
agentId String @map("agent_id")
|
||||||
attemptCount Int @map("attempt_count")
|
attemptCount Int @map("attempt_count")
|
||||||
failures Json // FailureSummary[]
|
failures Json // FailureSummary[]
|
||||||
originalTask String @map("original_task")
|
originalTask String @map("original_task")
|
||||||
startedAt DateTime @map("started_at") @db.Timestamptz
|
startedAt DateTime @map("started_at") @db.Timestamptz
|
||||||
rejectedAt DateTime @map("rejected_at") @db.Timestamptz
|
rejectedAt DateTime @map("rejected_at") @db.Timestamptz
|
||||||
escalated Boolean @default(false)
|
escalated Boolean @default(false)
|
||||||
manualReview Boolean @default(false) @map("manual_review")
|
manualReview Boolean @default(false) @map("manual_review")
|
||||||
resolvedAt DateTime? @map("resolved_at") @db.Timestamptz
|
resolvedAt DateTime? @map("resolved_at") @db.Timestamptz
|
||||||
resolution String?
|
resolution String?
|
||||||
|
|
||||||
@@index([taskId])
|
@@index([taskId])
|
||||||
@@index([workspaceId])
|
@@index([workspaceId])
|
||||||
@@ -1055,22 +1088,22 @@ model TaskRejection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model TokenBudget {
|
model TokenBudget {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
taskId String @unique @map("task_id") @db.Uuid
|
taskId String @unique @map("task_id") @db.Uuid
|
||||||
workspaceId String @map("workspace_id") @db.Uuid
|
workspaceId String @map("workspace_id") @db.Uuid
|
||||||
agentId String @map("agent_id")
|
agentId String @map("agent_id")
|
||||||
|
|
||||||
// Budget allocation
|
// Budget allocation
|
||||||
allocatedTokens Int @map("allocated_tokens")
|
allocatedTokens Int @map("allocated_tokens")
|
||||||
estimatedComplexity String @map("estimated_complexity") // "low", "medium", "high", "critical"
|
estimatedComplexity String @map("estimated_complexity") // "low", "medium", "high", "critical"
|
||||||
|
|
||||||
// Usage tracking
|
// Usage tracking
|
||||||
inputTokensUsed Int @default(0) @map("input_tokens_used")
|
inputTokensUsed Int @default(0) @map("input_tokens_used")
|
||||||
outputTokensUsed Int @default(0) @map("output_tokens_used")
|
outputTokensUsed Int @default(0) @map("output_tokens_used")
|
||||||
totalTokensUsed Int @default(0) @map("total_tokens_used")
|
totalTokensUsed Int @default(0) @map("total_tokens_used")
|
||||||
|
|
||||||
// Cost tracking
|
// Cost tracking
|
||||||
estimatedCost Decimal? @map("estimated_cost") @db.Decimal(10, 6)
|
estimatedCost Decimal? @map("estimated_cost") @db.Decimal(10, 6)
|
||||||
|
|
||||||
// State
|
// State
|
||||||
startedAt DateTime @default(now()) @map("started_at") @db.Timestamptz
|
startedAt DateTime @default(now()) @map("started_at") @db.Timestamptz
|
||||||
@@ -1078,12 +1111,103 @@ model TokenBudget {
|
|||||||
completedAt DateTime? @map("completed_at") @db.Timestamptz
|
completedAt DateTime? @map("completed_at") @db.Timestamptz
|
||||||
|
|
||||||
// Analysis
|
// Analysis
|
||||||
budgetUtilization Float? @map("budget_utilization") // 0.0 - 1.0
|
budgetUtilization Float? @map("budget_utilization") // 0.0 - 1.0
|
||||||
suspiciousPattern Boolean @default(false) @map("suspicious_pattern")
|
suspiciousPattern Boolean @default(false) @map("suspicious_pattern")
|
||||||
suspiciousReason String? @map("suspicious_reason")
|
suspiciousReason String? @map("suspicious_reason")
|
||||||
|
|
||||||
@@index([taskId])
|
@@index([taskId])
|
||||||
@@index([workspaceId])
|
@@index([workspaceId])
|
||||||
@@index([suspiciousPattern])
|
@@index([suspiciousPattern])
|
||||||
@@map("token_budgets")
|
@@map("token_budgets")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// RUNNER JOB TRACKING MODULE
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
model RunnerJob {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
workspaceId String @map("workspace_id") @db.Uuid
|
||||||
|
agentTaskId String? @map("agent_task_id") @db.Uuid
|
||||||
|
|
||||||
|
// Job details
|
||||||
|
type String // 'git-status', 'code-task', 'priority-calc'
|
||||||
|
status RunnerJobStatus @default(PENDING)
|
||||||
|
priority Int
|
||||||
|
progressPercent Int @default(0) @map("progress_percent")
|
||||||
|
|
||||||
|
// Results
|
||||||
|
result Json?
|
||||||
|
error String? @db.Text
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||||
|
startedAt DateTime? @map("started_at") @db.Timestamptz
|
||||||
|
completedAt DateTime? @map("completed_at") @db.Timestamptz
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||||
|
agentTask AgentTask? @relation(fields: [agentTaskId], references: [id], onDelete: SetNull)
|
||||||
|
steps JobStep[]
|
||||||
|
events JobEvent[]
|
||||||
|
|
||||||
|
@@unique([id, workspaceId])
|
||||||
|
@@index([workspaceId])
|
||||||
|
@@index([workspaceId, status])
|
||||||
|
@@index([agentTaskId])
|
||||||
|
@@index([priority])
|
||||||
|
@@map("runner_jobs")
|
||||||
|
}
|
||||||
|
|
||||||
|
model JobStep {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
jobId String @map("job_id") @db.Uuid
|
||||||
|
|
||||||
|
// Step details
|
||||||
|
ordinal Int
|
||||||
|
phase JobStepPhase
|
||||||
|
name String
|
||||||
|
type JobStepType
|
||||||
|
status JobStepStatus @default(PENDING)
|
||||||
|
|
||||||
|
// Output and metrics
|
||||||
|
output String? @db.Text
|
||||||
|
tokensInput Int? @map("tokens_input")
|
||||||
|
tokensOutput Int? @map("tokens_output")
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
startedAt DateTime? @map("started_at") @db.Timestamptz
|
||||||
|
completedAt DateTime? @map("completed_at") @db.Timestamptz
|
||||||
|
durationMs Int? @map("duration_ms")
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
job RunnerJob @relation(fields: [jobId], references: [id], onDelete: Cascade)
|
||||||
|
events JobEvent[]
|
||||||
|
|
||||||
|
@@index([jobId])
|
||||||
|
@@index([jobId, ordinal])
|
||||||
|
@@index([status])
|
||||||
|
@@map("job_steps")
|
||||||
|
}
|
||||||
|
|
||||||
|
model JobEvent {
|
||||||
|
id String @id @default(uuid()) @db.Uuid
|
||||||
|
jobId String @map("job_id") @db.Uuid
|
||||||
|
stepId String? @map("step_id") @db.Uuid
|
||||||
|
|
||||||
|
// Event details
|
||||||
|
type String
|
||||||
|
timestamp DateTime @db.Timestamptz
|
||||||
|
actor String
|
||||||
|
payload Json
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
job RunnerJob @relation(fields: [jobId], references: [id], onDelete: Cascade)
|
||||||
|
step JobStep? @relation(fields: [stepId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@index([jobId])
|
||||||
|
@@index([stepId])
|
||||||
|
@@index([timestamp])
|
||||||
|
@@index([type])
|
||||||
|
@@map("job_events")
|
||||||
|
}
|
||||||
|
|||||||
109
docs/scratchpads/164-database-schema-jobs.md
Normal file
109
docs/scratchpads/164-database-schema-jobs.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# Issue #164: Database schema for job tracking
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Add Prisma schema for runner_jobs, job_steps, and job_events tables to support the autonomous runner infrastructure.
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
1. Read existing schema.prisma to understand current conventions
|
||||||
|
2. Add four enums: RunnerJobStatus, JobStepPhase, JobStepType, JobStepStatus
|
||||||
|
3. Add three models: RunnerJob, JobStep, JobEvent
|
||||||
|
4. Create and run migration
|
||||||
|
5. Verify migration succeeds
|
||||||
|
|
||||||
|
## Schema Design
|
||||||
|
|
||||||
|
### Enums
|
||||||
|
|
||||||
|
- **RunnerJobStatus**: PENDING, QUEUED, RUNNING, COMPLETED, FAILED, CANCELLED
|
||||||
|
- **JobStepPhase**: SETUP, EXECUTION, VALIDATION, CLEANUP
|
||||||
|
- **JobStepType**: COMMAND, AI_ACTION, GATE, ARTIFACT
|
||||||
|
- **JobStepStatus**: PENDING, RUNNING, COMPLETED, FAILED, SKIPPED
|
||||||
|
|
||||||
|
### Models
|
||||||
|
|
||||||
|
1. **RunnerJob** - Top-level job tracking
|
||||||
|
- Links to workspace and optionally to agent_task
|
||||||
|
- Tracks overall job status, progress, result
|
||||||
|
- Timestamps: created_at, started_at, completed_at
|
||||||
|
|
||||||
|
2. **JobStep** - Granular step tracking
|
||||||
|
- Child of RunnerJob
|
||||||
|
- Phase-based organization (SETUP, EXECUTION, etc.)
|
||||||
|
- Token tracking for AI operations
|
||||||
|
- Duration tracking
|
||||||
|
|
||||||
|
3. **JobEvent** - Event sourcing audit log
|
||||||
|
- Immutable event log for jobs and steps
|
||||||
|
- Links to both job and optionally step
|
||||||
|
- Actor tracking for accountability
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
|
||||||
|
- [x] Read existing schema.prisma
|
||||||
|
- [x] Read architecture document for schema requirements
|
||||||
|
- [x] Add enums (RunnerJobStatus, JobStepPhase, JobStepType, JobStepStatus)
|
||||||
|
- [x] Add RunnerJob model with workspace and agentTask relations
|
||||||
|
- [x] Add JobStep model with job relation
|
||||||
|
- [x] Add JobEvent model with job and step relations
|
||||||
|
- [x] Add RunnerJob[] to Workspace and AgentTask relations
|
||||||
|
- [x] Create migration (20260201205935_add_job_tracking)
|
||||||
|
- [x] Test migration - all tables created successfully
|
||||||
|
- [x] Run quality gates (typecheck, lint, build - all passed)
|
||||||
|
- [x] Generate Prisma client
|
||||||
|
- [ ] Commit changes
|
||||||
|
|
||||||
|
## Schema Observations from Existing Code
|
||||||
|
|
||||||
|
**Conventions Identified:**
|
||||||
|
|
||||||
|
- UUID primary keys with `@db.Uuid` annotation
|
||||||
|
- snake_case for database column names via `@map`
|
||||||
|
- snake_case for table names via `@@map`
|
||||||
|
- Timestamps use `@db.Timestamptz` for timezone awareness
|
||||||
|
- workspace_id on all workspace-scoped tables with cascading deletes
|
||||||
|
- Composite unique constraints with `@@unique([id, workspaceId])`
|
||||||
|
- Consistent indexing patterns: workspace_id, status, timestamps
|
||||||
|
- Json fields for flexible metadata with `@default("{}")`
|
||||||
|
- Optional foreign keys use `@db.Uuid` without NOT NULL
|
||||||
|
- Relations use descriptive names in both directions
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Since this is a schema-only change, testing will verify:
|
||||||
|
|
||||||
|
- Migration runs successfully ✅
|
||||||
|
- Foreign key constraints are valid ✅
|
||||||
|
- Schema matches architecture document ✅
|
||||||
|
|
||||||
|
Verification performed:
|
||||||
|
|
||||||
|
1. Database tables created: runner_jobs, job_steps, job_events
|
||||||
|
2. All enums created: RunnerJobStatus, JobStepPhase, JobStepType, JobStepStatus
|
||||||
|
3. Foreign key relationships verified:
|
||||||
|
- runner_jobs → workspaces (workspace_id)
|
||||||
|
- runner_jobs → agent_tasks (agent_task_id, optional)
|
||||||
|
- job_steps → runner_jobs (job_id)
|
||||||
|
- job_events → runner_jobs (job_id)
|
||||||
|
- job_events → job_steps (step_id, optional)
|
||||||
|
4. Indexes created for performance:
|
||||||
|
- workspace_id for workspace filtering
|
||||||
|
- status for job querying
|
||||||
|
- priority for job prioritization
|
||||||
|
- timestamp for event ordering
|
||||||
|
5. Quality gates passed:
|
||||||
|
- TypeScript compilation ✅
|
||||||
|
- ESLint checks ✅
|
||||||
|
- NestJS build ✅
|
||||||
|
- Prisma client generation ✅
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Following existing patterns from schema.prisma
|
||||||
|
- Using UUID for all primary keys (existing convention)
|
||||||
|
- Using snake_case for table names (Prisma convention)
|
||||||
|
- All workspace-scoped tables include workspace_id for RLS
|
||||||
|
- Migration file created: 20260201205935_add_job_tracking
|
||||||
|
- Database push successful, migration marked as applied
|
||||||
|
- Schema format validated successfully
|
||||||
Reference in New Issue
Block a user