Files
homelab/packages/db/src/schema.ts
Julia McGhee eeb87018d7
Some checks failed
Deploy Production / deploy (push) Failing after 35s
CI / lint-and-test (push) Successful in 33s
CI / build (push) Has been cancelled
Add event-driven tasks via Gitea webhooks
Webhook endpoint at /api/webhooks/gitea receives Gitea status events,
matches them against configurable event triggers with conditions
(event type, repo glob, state, context), renders task templates with
{{variable}} substitution, and creates harness tasks automatically.

Includes circuit breaker: after N consecutive task failures from the
same trigger (default 3), the trigger auto-disables. Re-enable
manually via PATCH /api/event-triggers/:id.

New tables: harness_event_triggers (rules + circuit breaker state),
harness_event_log (audit trail + dedup via X-Gitea-Delivery).
2026-03-21 21:15:15 +00:00

188 lines
7.6 KiB
TypeScript

import {
pgTable,
serial,
text,
timestamp,
boolean,
integer,
real,
jsonb,
bigint,
} from "drizzle-orm/pg-core";
// ─── EXISTING ───────────────────────────────────────────────
export const users = pgTable("users", {
id: serial("id").primaryKey(),
email: text("email").notNull().unique(),
name: text("name"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// ─── HARNESS: CREDENTIALS ──────────────────────────────────
export const credentials = pgTable("harness_credentials", {
id: text("id").primaryKey(),
provider: text("provider").notNull(),
label: text("label").notNull(),
token: text("token").notNull(),
baseUrl: text("base_url"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// ─── HARNESS: CURATED MODELS ───────────────────────────────
export const curatedModels = pgTable("harness_curated_models", {
id: text("id").primaryKey(),
name: text("name").notNull(),
provider: text("provider").notNull(),
enabled: boolean("enabled").default(true).notNull(),
contextWindow: integer("context_window"),
costPer1kInput: real("cost_per_1k_input"),
costPer1kOutput: real("cost_per_1k_output"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// ─── HARNESS: MODEL USAGE ──────────────────────────────────
export const modelUsage = pgTable("harness_model_usage", {
id: serial("id").primaryKey(),
modelId: text("model_id").notNull(),
provider: text("provider").notNull(),
taskId: text("task_id").notNull(),
taskSlug: text("task_slug").notNull(),
iteration: integer("iteration").notNull(),
inputTokens: integer("input_tokens").notNull(),
outputTokens: integer("output_tokens").notNull(),
durationMs: integer("duration_ms").notNull(),
timestamp: bigint("timestamp", { mode: "number" }).notNull(),
});
// ─── HARNESS: AGENT CONFIGS ────────────────────────────────
export const agentConfigs = pgTable("harness_agent_configs", {
id: text("id").primaryKey(),
name: text("name").notNull(),
runtime: text("runtime").notNull(), // "claude-code" | "codex" | "opencode"
modelId: text("model_id").notNull(),
provider: text("provider").notNull(),
maxTokens: integer("max_tokens"),
env: jsonb("env").$type<Record<string, string>>(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// ─── HARNESS: ORCHESTRATOR STATE ──────────────────────────
export const orchestratorState = pgTable("harness_orchestrator", {
id: text("id").primaryKey().default("singleton"),
running: boolean("running").default(false).notNull(),
currentTaskId: text("current_task_id"),
heartbeat: bigint("heartbeat", { mode: "number" }),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// ─── HARNESS: TASKS ────────────────────────────────────────
export const tasks = pgTable("harness_tasks", {
id: text("id").primaryKey(),
slug: text("slug").notNull(),
goal: text("goal").notNull(),
status: text("status").notNull().default("pending"), // pending | running | completed | failed
iteration: integer("iteration").notNull().default(0),
maxIterations: integer("max_iterations").notNull().default(6),
startedAt: bigint("started_at", { mode: "number" }),
completedAt: bigint("completed_at", { mode: "number" }),
project: text("project").notNull().default("—"),
evals: jsonb("evals").$type<Record<string, unknown>>().default({}).notNull(),
pr: jsonb("pr").$type<{ number: number; title: string; status: string }>(),
cancelRequested: boolean("cancel_requested").default(false).notNull(),
spec: jsonb("spec").$type<{
slug: string;
goal: string;
project: string;
agentId: string;
maxIterations: number;
criteria: { label: string; target: string }[];
constraints: string[];
knowledgeRefs: string[];
}>().notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// ─── HARNESS: ITERATIONS ───────────────────────────────────
export const iterations = pgTable("harness_iterations", {
id: serial("id").primaryKey(),
taskId: text("task_id").notNull(),
n: integer("n").notNull(),
status: text("status").notNull().default("pending"), // pending | running | passed | failed
diagnosis: text("diagnosis"),
agentOutput: text("agent_output"),
evals: jsonb("evals").$type<Record<string, unknown>>(),
diffStats: text("diff_stats"),
startedAt: bigint("started_at", { mode: "number" }),
completedAt: bigint("completed_at", { mode: "number" }),
});
// ─── HARNESS: EVENT TRIGGERS ───────────────────────────────
export const eventTriggers = pgTable("harness_event_triggers", {
id: text("id").primaryKey(),
name: text("name").notNull(),
enabled: boolean("enabled").default(true).notNull(),
// Matching conditions (all AND'd, null = any)
eventType: text("event_type").notNull(), // "status" | "*"
repoFilter: text("repo_filter"), // glob: "lazorgurl/*" or exact "lazorgurl/homelab"
stateFilter: text("state_filter"), // "failure" | "error" | null
contextFilter: text("context_filter"), // substring match on CI context, null = any
// Task template with {{variable}} placeholders
taskTemplate: jsonb("task_template").$type<{
slug: string;
goal: string;
project: string;
gitProvider?: string;
gitBaseUrl?: string;
agentId: string;
maxIterations: number;
criteria: { label: string; target: string }[];
constraints: string[];
knowledgeRefs: string[];
}>().notNull(),
// Circuit breaker
consecutiveFailures: integer("consecutive_failures").default(0).notNull(),
maxConsecutiveFailures: integer("max_consecutive_failures").default(3).notNull(),
disabledReason: text("disabled_reason"),
// Webhook auth (per-trigger override, falls back to GITEA_WEBHOOK_SECRET env)
webhookSecret: text("webhook_secret"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// ─── HARNESS: EVENT LOG ────────────────────────────────────
export const eventLog = pgTable("harness_event_log", {
id: serial("id").primaryKey(),
triggerId: text("trigger_id").notNull(),
deliveryId: text("delivery_id").notNull().unique(), // X-Gitea-Delivery header (dedup)
eventType: text("event_type").notNull(),
repo: text("repo").notNull(),
commitSha: text("commit_sha"),
branch: text("branch"),
status: text("status").notNull().default("received"), // received | task_created | skipped | error
taskId: text("task_id"),
skipReason: text("skip_reason"),
error: text("error"),
payload: jsonb("payload").$type<Record<string, unknown>>().notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});