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:
@@ -135,6 +135,37 @@ enum FormalityLevel {
|
||||
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
|
||||
// ============================================
|
||||
@@ -151,24 +182,24 @@ model User {
|
||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
||||
|
||||
// Relations
|
||||
ownedWorkspaces Workspace[] @relation("WorkspaceOwner")
|
||||
workspaceMemberships WorkspaceMember[]
|
||||
teamMemberships TeamMember[]
|
||||
assignedTasks Task[] @relation("TaskAssignee")
|
||||
createdTasks Task[] @relation("TaskCreator")
|
||||
createdEvents Event[] @relation("EventCreator")
|
||||
createdProjects Project[] @relation("ProjectCreator")
|
||||
activityLogs ActivityLog[]
|
||||
sessions Session[]
|
||||
accounts Account[]
|
||||
ideas Idea[] @relation("IdeaCreator")
|
||||
relationships Relationship[] @relation("RelationshipCreator")
|
||||
agentSessions AgentSession[]
|
||||
agentTasks AgentTask[] @relation("AgentTaskCreator")
|
||||
userLayouts UserLayout[]
|
||||
userPreference UserPreference?
|
||||
knowledgeEntryVersions KnowledgeEntryVersion[] @relation("EntryVersionAuthor")
|
||||
llmProviders LlmProviderInstance[] @relation("UserLlmProviders")
|
||||
ownedWorkspaces Workspace[] @relation("WorkspaceOwner")
|
||||
workspaceMemberships WorkspaceMember[]
|
||||
teamMemberships TeamMember[]
|
||||
assignedTasks Task[] @relation("TaskAssignee")
|
||||
createdTasks Task[] @relation("TaskCreator")
|
||||
createdEvents Event[] @relation("EventCreator")
|
||||
createdProjects Project[] @relation("ProjectCreator")
|
||||
activityLogs ActivityLog[]
|
||||
sessions Session[]
|
||||
accounts Account[]
|
||||
ideas Idea[] @relation("IdeaCreator")
|
||||
relationships Relationship[] @relation("RelationshipCreator")
|
||||
agentSessions AgentSession[]
|
||||
agentTasks AgentTask[] @relation("AgentTaskCreator")
|
||||
userLayouts UserLayout[]
|
||||
userPreference UserPreference?
|
||||
knowledgeEntryVersions KnowledgeEntryVersion[] @relation("EntryVersionAuthor")
|
||||
llmProviders LlmProviderInstance[] @relation("UserLlmProviders")
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
@@ -195,7 +226,7 @@ model Workspace {
|
||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
||||
|
||||
// Relations
|
||||
owner User @relation("WorkspaceOwner", fields: [ownerId], references: [id], onDelete: Cascade)
|
||||
owner User @relation("WorkspaceOwner", fields: [ownerId], references: [id], onDelete: Cascade)
|
||||
members WorkspaceMember[]
|
||||
teams Team[]
|
||||
tasks Task[]
|
||||
@@ -216,6 +247,7 @@ model Workspace {
|
||||
personalities Personality[]
|
||||
llmSettings WorkspaceLlmSettings?
|
||||
qualityGates QualityGate[]
|
||||
runnerJobs RunnerJob[]
|
||||
|
||||
@@index([ownerId])
|
||||
@@map("workspaces")
|
||||
@@ -565,8 +597,8 @@ model Agent {
|
||||
}
|
||||
|
||||
model AgentTask {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
workspaceId String @map("workspace_id") @db.Uuid
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
workspaceId String @map("workspace_id") @db.Uuid
|
||||
|
||||
// Task details
|
||||
title String
|
||||
@@ -575,23 +607,24 @@ model AgentTask {
|
||||
priority AgentTaskPriority @default(MEDIUM)
|
||||
|
||||
// Agent configuration
|
||||
agentType String @map("agent_type")
|
||||
agentConfig Json @default("{}") @map("agent_config")
|
||||
agentType String @map("agent_type")
|
||||
agentConfig Json @default("{}") @map("agent_config")
|
||||
|
||||
// Results
|
||||
result Json?
|
||||
error String? @db.Text
|
||||
result Json?
|
||||
error String? @db.Text
|
||||
|
||||
// Timing
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
||||
startedAt DateTime? @map("started_at") @db.Timestamptz
|
||||
completedAt DateTime? @map("completed_at") @db.Timestamptz
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||
updatedAt DateTime @updatedAt @map("updated_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)
|
||||
createdBy User @relation("AgentTaskCreator", fields: [createdById], references: [id], onDelete: Cascade)
|
||||
createdById String @map("created_by_id") @db.Uuid
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
createdBy User @relation("AgentTaskCreator", fields: [createdById], references: [id], onDelete: Cascade)
|
||||
createdById String @map("created_by_id") @db.Uuid
|
||||
runnerJobs RunnerJob[]
|
||||
|
||||
@@unique([id, workspaceId])
|
||||
@@index([workspaceId])
|
||||
@@ -890,18 +923,18 @@ model KnowledgeEmbedding {
|
||||
// ============================================
|
||||
|
||||
model CronSchedule {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
workspaceId String @map("workspace_id") @db.Uuid
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
workspaceId String @map("workspace_id") @db.Uuid
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
|
||||
// Cron configuration
|
||||
expression String // Standard cron: "0 9 * * *" = 9am daily
|
||||
command String // MoltBot command to trigger
|
||||
expression String // Standard cron: "0 9 * * *" = 9am daily
|
||||
command String // MoltBot command to trigger
|
||||
|
||||
// State
|
||||
enabled Boolean @default(true)
|
||||
lastRun DateTime? @map("last_run") @db.Timestamptz
|
||||
nextRun DateTime? @map("next_run") @db.Timestamptz
|
||||
enabled Boolean @default(true)
|
||||
lastRun DateTime? @map("last_run") @db.Timestamptz
|
||||
nextRun DateTime? @map("next_run") @db.Timestamptz
|
||||
|
||||
// Audit
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||
@@ -918,22 +951,22 @@ model CronSchedule {
|
||||
// ============================================
|
||||
|
||||
model Personality {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
workspaceId String @map("workspace_id") @db.Uuid
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
workspaceId String @map("workspace_id") @db.Uuid
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
|
||||
// Identity
|
||||
name String // unique identifier slug
|
||||
displayName String @map("display_name")
|
||||
description String? @db.Text
|
||||
name String // unique identifier slug
|
||||
displayName String @map("display_name")
|
||||
description String? @db.Text
|
||||
|
||||
// System prompt
|
||||
systemPrompt String @map("system_prompt") @db.Text
|
||||
|
||||
// LLM configuration
|
||||
temperature Float? // null = use provider default
|
||||
maxTokens Int? @map("max_tokens") // null = use provider default
|
||||
llmProviderInstanceId String? @map("llm_provider_instance_id") @db.Uuid
|
||||
temperature Float? // null = use provider default
|
||||
maxTokens Int? @map("max_tokens") // null = use provider default
|
||||
llmProviderInstanceId String? @map("llm_provider_instance_id") @db.Uuid
|
||||
|
||||
// Status
|
||||
isDefault Boolean @default(false) @map("is_default")
|
||||
@@ -961,20 +994,20 @@ model Personality {
|
||||
// ============================================
|
||||
|
||||
model LlmProviderInstance {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
providerType String @map("provider_type") // "ollama" | "claude" | "openai"
|
||||
displayName String @map("display_name")
|
||||
userId String? @map("user_id") @db.Uuid // NULL = system-level, UUID = user-level
|
||||
config Json // Provider-specific configuration
|
||||
isDefault Boolean @default(false) @map("is_default")
|
||||
isEnabled Boolean @default(true) @map("is_enabled")
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
providerType String @map("provider_type") // "ollama" | "claude" | "openai"
|
||||
displayName String @map("display_name")
|
||||
userId String? @map("user_id") @db.Uuid // NULL = system-level, UUID = user-level
|
||||
config Json // Provider-specific configuration
|
||||
isDefault Boolean @default(false) @map("is_default")
|
||||
isEnabled Boolean @default(true) @map("is_enabled")
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
||||
|
||||
// Relations
|
||||
user User? @relation("UserLlmProviders", fields: [userId], references: [id], onDelete: Cascade)
|
||||
personalities Personality[] @relation("PersonalityLlmProvider")
|
||||
workspaceLlmSettings WorkspaceLlmSettings[] @relation("WorkspaceLlmProvider")
|
||||
user User? @relation("UserLlmProviders", fields: [userId], references: [id], onDelete: Cascade)
|
||||
personalities Personality[] @relation("PersonalityLlmProvider")
|
||||
workspaceLlmSettings WorkspaceLlmSettings[] @relation("WorkspaceLlmProvider")
|
||||
|
||||
@@index([userId])
|
||||
@@index([providerType])
|
||||
@@ -1010,20 +1043,20 @@ model WorkspaceLlmSettings {
|
||||
// ============================================
|
||||
|
||||
model QualityGate {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
workspaceId String @map("workspace_id") @db.Uuid
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
workspaceId String @map("workspace_id") @db.Uuid
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
name String
|
||||
description String?
|
||||
type String // 'build' | 'lint' | 'test' | 'coverage' | 'custom'
|
||||
type String // 'build' | 'lint' | 'test' | 'coverage' | 'custom'
|
||||
command String?
|
||||
expectedOutput String? @map("expected_output")
|
||||
isRegex Boolean @default(false) @map("is_regex")
|
||||
required Boolean @default(true)
|
||||
order Int @default(0)
|
||||
isEnabled Boolean @default(true) @map("is_enabled")
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
||||
expectedOutput String? @map("expected_output")
|
||||
isRegex Boolean @default(false) @map("is_regex")
|
||||
required Boolean @default(true)
|
||||
order Int @default(0)
|
||||
isEnabled Boolean @default(true) @map("is_enabled")
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
|
||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
|
||||
|
||||
@@unique([workspaceId, name])
|
||||
@@index([workspaceId])
|
||||
@@ -1032,19 +1065,19 @@ model QualityGate {
|
||||
}
|
||||
|
||||
model TaskRejection {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
taskId String @map("task_id")
|
||||
workspaceId String @map("workspace_id")
|
||||
agentId String @map("agent_id")
|
||||
attemptCount Int @map("attempt_count")
|
||||
failures Json // FailureSummary[]
|
||||
originalTask String @map("original_task")
|
||||
startedAt DateTime @map("started_at") @db.Timestamptz
|
||||
rejectedAt DateTime @map("rejected_at") @db.Timestamptz
|
||||
escalated Boolean @default(false)
|
||||
manualReview Boolean @default(false) @map("manual_review")
|
||||
resolvedAt DateTime? @map("resolved_at") @db.Timestamptz
|
||||
resolution String?
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
taskId String @map("task_id")
|
||||
workspaceId String @map("workspace_id")
|
||||
agentId String @map("agent_id")
|
||||
attemptCount Int @map("attempt_count")
|
||||
failures Json // FailureSummary[]
|
||||
originalTask String @map("original_task")
|
||||
startedAt DateTime @map("started_at") @db.Timestamptz
|
||||
rejectedAt DateTime @map("rejected_at") @db.Timestamptz
|
||||
escalated Boolean @default(false)
|
||||
manualReview Boolean @default(false) @map("manual_review")
|
||||
resolvedAt DateTime? @map("resolved_at") @db.Timestamptz
|
||||
resolution String?
|
||||
|
||||
@@index([taskId])
|
||||
@@index([workspaceId])
|
||||
@@ -1055,22 +1088,22 @@ model TaskRejection {
|
||||
}
|
||||
|
||||
model TokenBudget {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
taskId String @unique @map("task_id") @db.Uuid
|
||||
workspaceId String @map("workspace_id") @db.Uuid
|
||||
agentId String @map("agent_id")
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
taskId String @unique @map("task_id") @db.Uuid
|
||||
workspaceId String @map("workspace_id") @db.Uuid
|
||||
agentId String @map("agent_id")
|
||||
|
||||
// Budget allocation
|
||||
allocatedTokens Int @map("allocated_tokens")
|
||||
allocatedTokens Int @map("allocated_tokens")
|
||||
estimatedComplexity String @map("estimated_complexity") // "low", "medium", "high", "critical"
|
||||
|
||||
// Usage tracking
|
||||
inputTokensUsed Int @default(0) @map("input_tokens_used")
|
||||
outputTokensUsed Int @default(0) @map("output_tokens_used")
|
||||
totalTokensUsed Int @default(0) @map("total_tokens_used")
|
||||
inputTokensUsed Int @default(0) @map("input_tokens_used")
|
||||
outputTokensUsed Int @default(0) @map("output_tokens_used")
|
||||
totalTokensUsed Int @default(0) @map("total_tokens_used")
|
||||
|
||||
// Cost tracking
|
||||
estimatedCost Decimal? @map("estimated_cost") @db.Decimal(10, 6)
|
||||
estimatedCost Decimal? @map("estimated_cost") @db.Decimal(10, 6)
|
||||
|
||||
// State
|
||||
startedAt DateTime @default(now()) @map("started_at") @db.Timestamptz
|
||||
@@ -1078,12 +1111,103 @@ model TokenBudget {
|
||||
completedAt DateTime? @map("completed_at") @db.Timestamptz
|
||||
|
||||
// Analysis
|
||||
budgetUtilization Float? @map("budget_utilization") // 0.0 - 1.0
|
||||
suspiciousPattern Boolean @default(false) @map("suspicious_pattern")
|
||||
suspiciousReason String? @map("suspicious_reason")
|
||||
budgetUtilization Float? @map("budget_utilization") // 0.0 - 1.0
|
||||
suspiciousPattern Boolean @default(false) @map("suspicious_pattern")
|
||||
suspiciousReason String? @map("suspicious_reason")
|
||||
|
||||
@@index([taskId])
|
||||
@@index([workspaceId])
|
||||
@@index([suspiciousPattern])
|
||||
@@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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user