chore: add postgres schema migrations

This commit is contained in:
chungyeong
2026-05-09 22:30:49 +09:00
parent 6bd4c9382a
commit 38f3472d9c
18 changed files with 3399 additions and 1 deletions

18
packages/db/package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "@devflow/db",
"version": "0.0.0",
"private": true,
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts",
"typecheck": "tsc -b --noEmit",
"test": "vitest run"
},
"dependencies": {
"drizzle-orm": "0.45.2",
"pg": "8.20.0"
}
}

18
packages/db/src/client.ts Normal file
View File

@@ -0,0 +1,18 @@
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema/index.js";
export type DbClient = ReturnType<typeof createDbClient>;
export function createDbClient(databaseUrl: string) {
const pool = new Pool({ connectionString: databaseUrl });
return {
db: drizzle(pool, { schema }),
pool,
async close() {
await pool.end();
},
};
}

2
packages/db/src/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export { createDbClient, type DbClient } from "./client.js";
export * from "./schema/index.js";

View File

@@ -0,0 +1,223 @@
CREATE EXTENSION IF NOT EXISTS pgcrypto;
--> statement-breakpoint
CREATE TABLE "agent_personas" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"version" integer NOT NULL,
"hash" text NOT NULL,
"definition" jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "approval_decisions" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"approval_request_id" uuid NOT NULL,
"action" text NOT NULL,
"comment" text,
"decided_at" timestamp with time zone DEFAULT now() NOT NULL,
"idempotency_key" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "approval_requests" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"run_id" uuid NOT NULL,
"phase_id" uuid,
"gate_key" text NOT NULL,
"state" text NOT NULL,
"idempotency_key" text NOT NULL,
"payload" jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"resolved_at" timestamp with time zone
);
--> statement-breakpoint
CREATE TABLE "artifacts" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"run_id" uuid NOT NULL,
"phase_id" uuid,
"path" text NOT NULL,
"schema_id" text NOT NULL,
"hash" text NOT NULL,
"valid" boolean NOT NULL,
"validation_error" jsonb,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "backtest_iterations" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"run_id" uuid NOT NULL,
"payload" jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "backtest_metrics" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"run_id" uuid NOT NULL,
"payload" jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "commands" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"run_id" uuid NOT NULL,
"phase_id" uuid,
"kind" text NOT NULL,
"argv" text[] NOT NULL,
"cwd" text NOT NULL,
"exit_code" integer,
"stdout_path" text,
"stderr_path" text,
"started_at" timestamp with time zone,
"ended_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "review_findings" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"run_id" uuid NOT NULL,
"phase_id" uuid,
"reviewer_role" text NOT NULL,
"severity" text NOT NULL,
"category" text NOT NULL,
"file_path" text,
"line" integer,
"summary" text NOT NULL,
"evidence" text,
"verifier_status" text DEFAULT 'unverified' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "run_bindings" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"run_id" uuid NOT NULL,
"role_id" text NOT NULL,
"persona_id" uuid NOT NULL,
"persona_hash" text NOT NULL,
"backend" text NOT NULL,
"binding_hash" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "run_events" (
"id" bigserial PRIMARY KEY NOT NULL,
"run_id" uuid NOT NULL,
"phase_id" uuid,
"seq" bigint NOT NULL,
"type" text NOT NULL,
"payload" jsonb NOT NULL,
"idempotency_key" text NOT NULL,
"ts" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "run_inputs" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"run_id" uuid NOT NULL,
"requirements_md" text NOT NULL,
"objective" jsonb,
"extra" jsonb,
"input_hash" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "run_phases" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"run_id" uuid NOT NULL,
"phase_key" text NOT NULL,
"seq" integer NOT NULL,
"state" text NOT NULL,
"attempts" integer DEFAULT 0 NOT NULL,
"started_at" timestamp with time zone,
"ended_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "runs" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"template_id" uuid NOT NULL,
"template_hash" text NOT NULL,
"state" text NOT NULL,
"repo_path" text NOT NULL,
"base_branch" text NOT NULL,
"worktree_root" text NOT NULL,
"current_phase_id" uuid,
"started_at" timestamp with time zone,
"ended_at" timestamp with time zone,
"final_report_path" text,
"paused_from_state" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone
);
--> statement-breakpoint
CREATE TABLE "tui_sessions" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"run_id" uuid NOT NULL,
"role_id" text NOT NULL,
"backend" text NOT NULL,
"cwd" text NOT NULL,
"expected_artifact_path" text,
"expected_schema" text,
"last_prompt_hash" text,
"last_prompt_at" timestamp with time zone,
"last_capture_seq" bigint DEFAULT 0 NOT NULL,
"last_known_pane_pid" integer,
"tmux_session" text,
"tmux_window" text,
"state" text NOT NULL,
"recovery_attempts" integer DEFAULT 0 NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "tui_transcript_chunks" (
"id" bigserial PRIMARY KEY NOT NULL,
"session_id" uuid NOT NULL,
"seq" bigint NOT NULL,
"content" text NOT NULL,
"captured_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "workflow_templates" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"version" integer NOT NULL,
"hash" text NOT NULL,
"definition" jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "approval_decisions" ADD CONSTRAINT "approval_decisions_approval_request_id_approval_requests_id_fk" FOREIGN KEY ("approval_request_id") REFERENCES "public"."approval_requests"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "approval_requests" ADD CONSTRAINT "approval_requests_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "approval_requests" ADD CONSTRAINT "approval_requests_phase_id_run_phases_id_fk" FOREIGN KEY ("phase_id") REFERENCES "public"."run_phases"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "artifacts" ADD CONSTRAINT "artifacts_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "artifacts" ADD CONSTRAINT "artifacts_phase_id_run_phases_id_fk" FOREIGN KEY ("phase_id") REFERENCES "public"."run_phases"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "backtest_iterations" ADD CONSTRAINT "backtest_iterations_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "backtest_metrics" ADD CONSTRAINT "backtest_metrics_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "commands" ADD CONSTRAINT "commands_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "commands" ADD CONSTRAINT "commands_phase_id_run_phases_id_fk" FOREIGN KEY ("phase_id") REFERENCES "public"."run_phases"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "review_findings" ADD CONSTRAINT "review_findings_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "review_findings" ADD CONSTRAINT "review_findings_phase_id_run_phases_id_fk" FOREIGN KEY ("phase_id") REFERENCES "public"."run_phases"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "run_bindings" ADD CONSTRAINT "run_bindings_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "run_bindings" ADD CONSTRAINT "run_bindings_persona_id_agent_personas_id_fk" FOREIGN KEY ("persona_id") REFERENCES "public"."agent_personas"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "run_events" ADD CONSTRAINT "run_events_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "run_events" ADD CONSTRAINT "run_events_phase_id_run_phases_id_fk" FOREIGN KEY ("phase_id") REFERENCES "public"."run_phases"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "run_inputs" ADD CONSTRAINT "run_inputs_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "run_phases" ADD CONSTRAINT "run_phases_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "runs" ADD CONSTRAINT "runs_template_id_workflow_templates_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."workflow_templates"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "runs" ADD CONSTRAINT "runs_current_phase_id_run_phases_id_fk" FOREIGN KEY ("current_phase_id") REFERENCES "public"."run_phases"("id") ON DELETE no action ON UPDATE no action DEFERRABLE INITIALLY DEFERRED;--> statement-breakpoint
ALTER TABLE "tui_sessions" ADD CONSTRAINT "tui_sessions_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "tui_transcript_chunks" ADD CONSTRAINT "tui_transcript_chunks_session_id_tui_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."tui_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "agent_personas_hash_unique" ON "agent_personas" USING btree ("hash");--> statement-breakpoint
CREATE UNIQUE INDEX "agent_personas_name_version_unique" ON "agent_personas" USING btree ("name","version");--> statement-breakpoint
CREATE UNIQUE INDEX "approval_decisions_idempotency_key_unique" ON "approval_decisions" USING btree ("idempotency_key");--> statement-breakpoint
CREATE UNIQUE INDEX "approval_requests_idempotency_key_unique" ON "approval_requests" USING btree ("idempotency_key");--> statement-breakpoint
CREATE UNIQUE INDEX "artifacts_run_id_path_hash_unique" ON "artifacts" USING btree ("run_id","path","hash");--> statement-breakpoint
CREATE UNIQUE INDEX "run_bindings_run_id_role_id_unique" ON "run_bindings" USING btree ("run_id","role_id");--> statement-breakpoint
CREATE UNIQUE INDEX "run_events_run_id_seq_unique" ON "run_events" USING btree ("run_id","seq");--> statement-breakpoint
CREATE UNIQUE INDEX "run_events_run_id_idempotency_key_unique" ON "run_events" USING btree ("run_id","idempotency_key");--> statement-breakpoint
CREATE INDEX "run_events_run_id_ts_idx" ON "run_events" USING btree ("run_id","ts");--> statement-breakpoint
CREATE UNIQUE INDEX "run_inputs_run_id_unique" ON "run_inputs" USING btree ("run_id");--> statement-breakpoint
CREATE UNIQUE INDEX "run_phases_run_id_phase_key_unique" ON "run_phases" USING btree ("run_id","phase_key");--> statement-breakpoint
CREATE UNIQUE INDEX "ux_active_run_repo_base" ON "runs" USING btree ("repo_path","base_branch") WHERE "runs"."state" NOT IN ('completed', 'failed', 'aborted');--> statement-breakpoint
CREATE UNIQUE INDEX "tui_sessions_run_id_role_id_unique" ON "tui_sessions" USING btree ("run_id","role_id");--> statement-breakpoint
CREATE UNIQUE INDEX "tui_transcript_chunks_session_id_seq_unique" ON "tui_transcript_chunks" USING btree ("session_id","seq");--> statement-breakpoint
CREATE UNIQUE INDEX "workflow_templates_hash_unique" ON "workflow_templates" USING btree ("hash");--> statement-breakpoint
CREATE UNIQUE INDEX "workflow_templates_name_version_unique" ON "workflow_templates" USING btree ("name","version");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1778333151192,
"tag": "0000_robust_the_phantom",
"breakpoints": true
}
]
}

View File

@@ -0,0 +1,34 @@
import { readFileSync, readdirSync } from "node:fs";
import { describe, expect, it } from "vitest";
function readInitialMigration(): string {
const migrationDir = new URL(".", import.meta.url);
const [migrationFile] = readdirSync(migrationDir)
.filter((fileName) => fileName.endsWith(".sql"))
.sort();
if (!migrationFile) {
throw new Error("No SQL migration file found");
}
return readFileSync(new URL(migrationFile, migrationDir), "utf8");
}
describe("initial migration", () => {
it("contains the locked M1 database prelude and active-run index", () => {
const sql = readInitialMigration();
expect(sql).toContain("CREATE EXTENSION IF NOT EXISTS pgcrypto;");
expect(sql).toContain('CREATE UNIQUE INDEX "ux_active_run_repo_base"');
expect(sql).toContain("WHERE \"runs\".\"state\" NOT IN ('completed', 'failed', 'aborted')");
});
it("links runs.current_phase_id to run_phases with a deferrable foreign key", () => {
const sql = readInitialMigration();
expect(sql).toContain(
'ALTER TABLE "runs" ADD CONSTRAINT "runs_current_phase_id_run_phases_id_fk"',
);
expect(sql).toContain("DEFERRABLE INITIALLY DEFERRED");
});
});

View File

@@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest";
import { schemaTableNames } from "./index.js";
describe("database schema", () => {
it("exports every M1 table from the implementation plan", () => {
expect(schemaTableNames).toEqual([
"agent_personas",
"approval_decisions",
"approval_requests",
"artifacts",
"backtest_iterations",
"backtest_metrics",
"commands",
"review_findings",
"run_bindings",
"run_events",
"run_inputs",
"run_phases",
"runs",
"tui_sessions",
"tui_transcript_chunks",
"workflow_templates",
]);
});
});

View File

@@ -0,0 +1,321 @@
import { sql } from "drizzle-orm";
import {
bigint,
bigserial,
boolean,
index,
integer,
jsonb,
pgTable,
text,
timestamp,
uniqueIndex,
uuid,
} from "drizzle-orm/pg-core";
function createdAtColumn() {
return timestamp("created_at", { withTimezone: true }).notNull().defaultNow();
}
function updatedAtColumn() {
return timestamp("updated_at", { withTimezone: true });
}
export const workflowTemplates = pgTable(
"workflow_templates",
{
id: uuid("id").primaryKey().defaultRandom(),
name: text("name").notNull(),
version: integer("version").notNull(),
hash: text("hash").notNull(),
definition: jsonb("definition").notNull(),
createdAt: createdAtColumn(),
},
(table) => [
uniqueIndex("workflow_templates_hash_unique").on(table.hash),
uniqueIndex("workflow_templates_name_version_unique").on(table.name, table.version),
],
);
export const agentPersonas = pgTable(
"agent_personas",
{
id: uuid("id").primaryKey().defaultRandom(),
name: text("name").notNull(),
version: integer("version").notNull(),
hash: text("hash").notNull(),
definition: jsonb("definition").notNull(),
createdAt: createdAtColumn(),
},
(table) => [
uniqueIndex("agent_personas_hash_unique").on(table.hash),
uniqueIndex("agent_personas_name_version_unique").on(table.name, table.version),
],
);
export const runs = pgTable(
"runs",
{
id: uuid("id").primaryKey().defaultRandom(),
templateId: uuid("template_id")
.notNull()
.references(() => workflowTemplates.id),
templateHash: text("template_hash").notNull(),
state: text("state").notNull(),
repoPath: text("repo_path").notNull(),
baseBranch: text("base_branch").notNull(),
worktreeRoot: text("worktree_root").notNull(),
currentPhaseId: uuid("current_phase_id"),
startedAt: timestamp("started_at", { withTimezone: true }),
endedAt: timestamp("ended_at", { withTimezone: true }),
finalReportPath: text("final_report_path"),
pausedFromState: text("paused_from_state"),
createdAt: createdAtColumn(),
updatedAt: updatedAtColumn(),
},
(table) => [
uniqueIndex("ux_active_run_repo_base")
.on(table.repoPath, table.baseBranch)
.where(sql`${table.state} NOT IN ('completed', 'failed', 'aborted')`),
],
);
export const runInputs = pgTable(
"run_inputs",
{
id: uuid("id").primaryKey().defaultRandom(),
runId: uuid("run_id")
.notNull()
.references(() => runs.id, { onDelete: "cascade" }),
requirementsMd: text("requirements_md").notNull(),
objective: jsonb("objective"),
extra: jsonb("extra"),
inputHash: text("input_hash").notNull(),
createdAt: createdAtColumn(),
},
(table) => [uniqueIndex("run_inputs_run_id_unique").on(table.runId)],
);
export const runBindings = pgTable(
"run_bindings",
{
id: uuid("id").primaryKey().defaultRandom(),
runId: uuid("run_id")
.notNull()
.references(() => runs.id, { onDelete: "cascade" }),
roleId: text("role_id").notNull(),
personaId: uuid("persona_id")
.notNull()
.references(() => agentPersonas.id),
personaHash: text("persona_hash").notNull(),
backend: text("backend").notNull(),
bindingHash: text("binding_hash").notNull(),
createdAt: createdAtColumn(),
},
(table) => [uniqueIndex("run_bindings_run_id_role_id_unique").on(table.runId, table.roleId)],
);
export const runPhases = pgTable(
"run_phases",
{
id: uuid("id").primaryKey().defaultRandom(),
runId: uuid("run_id")
.notNull()
.references(() => runs.id, { onDelete: "cascade" }),
phaseKey: text("phase_key").notNull(),
seq: integer("seq").notNull(),
state: text("state").notNull(),
attempts: integer("attempts").notNull().default(0),
startedAt: timestamp("started_at", { withTimezone: true }),
endedAt: timestamp("ended_at", { withTimezone: true }),
createdAt: createdAtColumn(),
},
(table) => [uniqueIndex("run_phases_run_id_phase_key_unique").on(table.runId, table.phaseKey)],
);
export const runEvents = pgTable(
"run_events",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
runId: uuid("run_id")
.notNull()
.references(() => runs.id, { onDelete: "cascade" }),
phaseId: uuid("phase_id").references(() => runPhases.id),
seq: bigint("seq", { mode: "bigint" }).notNull(),
type: text("type").notNull(),
payload: jsonb("payload").notNull(),
idempotencyKey: text("idempotency_key").notNull(),
ts: timestamp("ts", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
uniqueIndex("run_events_run_id_seq_unique").on(table.runId, table.seq),
uniqueIndex("run_events_run_id_idempotency_key_unique").on(table.runId, table.idempotencyKey),
index("run_events_run_id_ts_idx").on(table.runId, table.ts),
],
);
export const approvalRequests = pgTable(
"approval_requests",
{
id: uuid("id").primaryKey().defaultRandom(),
runId: uuid("run_id")
.notNull()
.references(() => runs.id),
phaseId: uuid("phase_id").references(() => runPhases.id),
gateKey: text("gate_key").notNull(),
state: text("state").notNull(),
idempotencyKey: text("idempotency_key").notNull(),
payload: jsonb("payload").notNull(),
createdAt: createdAtColumn(),
resolvedAt: timestamp("resolved_at", { withTimezone: true }),
},
(table) => [uniqueIndex("approval_requests_idempotency_key_unique").on(table.idempotencyKey)],
);
export const approvalDecisions = pgTable(
"approval_decisions",
{
id: uuid("id").primaryKey().defaultRandom(),
approvalRequestId: uuid("approval_request_id")
.notNull()
.references(() => approvalRequests.id),
action: text("action").notNull(),
comment: text("comment"),
decidedAt: timestamp("decided_at", { withTimezone: true }).notNull().defaultNow(),
idempotencyKey: text("idempotency_key").notNull(),
createdAt: createdAtColumn(),
},
(table) => [uniqueIndex("approval_decisions_idempotency_key_unique").on(table.idempotencyKey)],
);
export const tuiSessions = pgTable(
"tui_sessions",
{
id: uuid("id").primaryKey().defaultRandom(),
runId: uuid("run_id")
.notNull()
.references(() => runs.id, { onDelete: "cascade" }),
roleId: text("role_id").notNull(),
backend: text("backend").notNull(),
cwd: text("cwd").notNull(),
expectedArtifactPath: text("expected_artifact_path"),
expectedSchema: text("expected_schema"),
lastPromptHash: text("last_prompt_hash"),
lastPromptAt: timestamp("last_prompt_at", { withTimezone: true }),
lastCaptureSeq: bigint("last_capture_seq", { mode: "bigint" }).notNull().default(sql`0`),
lastKnownPanePid: integer("last_known_pane_pid"),
tmuxSession: text("tmux_session"),
tmuxWindow: text("tmux_window"),
state: text("state").notNull(),
recoveryAttempts: integer("recovery_attempts").notNull().default(0),
createdAt: createdAtColumn(),
},
(table) => [uniqueIndex("tui_sessions_run_id_role_id_unique").on(table.runId, table.roleId)],
);
export const tuiTranscriptChunks = pgTable(
"tui_transcript_chunks",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
sessionId: uuid("session_id")
.notNull()
.references(() => tuiSessions.id, { onDelete: "cascade" }),
seq: bigint("seq", { mode: "bigint" }).notNull(),
content: text("content").notNull(),
capturedAt: timestamp("captured_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
uniqueIndex("tui_transcript_chunks_session_id_seq_unique").on(table.sessionId, table.seq),
],
);
export const artifacts = pgTable(
"artifacts",
{
id: uuid("id").primaryKey().defaultRandom(),
runId: uuid("run_id")
.notNull()
.references(() => runs.id, { onDelete: "cascade" }),
phaseId: uuid("phase_id").references(() => runPhases.id),
path: text("path").notNull(),
schemaId: text("schema_id").notNull(),
hash: text("hash").notNull(),
valid: boolean("valid").notNull(),
validationError: jsonb("validation_error"),
createdAt: createdAtColumn(),
},
(table) => [
uniqueIndex("artifacts_run_id_path_hash_unique").on(table.runId, table.path, table.hash),
],
);
export const commands = pgTable("commands", {
id: uuid("id").primaryKey().defaultRandom(),
runId: uuid("run_id")
.notNull()
.references(() => runs.id, { onDelete: "cascade" }),
phaseId: uuid("phase_id").references(() => runPhases.id),
kind: text("kind").notNull(),
argv: text("argv").array().notNull(),
cwd: text("cwd").notNull(),
exitCode: integer("exit_code"),
stdoutPath: text("stdout_path"),
stderrPath: text("stderr_path"),
startedAt: timestamp("started_at", { withTimezone: true }),
endedAt: timestamp("ended_at", { withTimezone: true }),
createdAt: createdAtColumn(),
});
export const reviewFindings = pgTable("review_findings", {
id: uuid("id").primaryKey().defaultRandom(),
runId: uuid("run_id")
.notNull()
.references(() => runs.id, { onDelete: "cascade" }),
phaseId: uuid("phase_id").references(() => runPhases.id),
reviewerRole: text("reviewer_role").notNull(),
severity: text("severity").notNull(),
category: text("category").notNull(),
filePath: text("file_path"),
line: integer("line"),
summary: text("summary").notNull(),
evidence: text("evidence"),
verifierStatus: text("verifier_status").notNull().default("unverified"),
createdAt: createdAtColumn(),
});
export const backtestIterations = pgTable("backtest_iterations", {
id: uuid("id").primaryKey().defaultRandom(),
runId: uuid("run_id")
.notNull()
.references(() => runs.id, { onDelete: "cascade" }),
payload: jsonb("payload").notNull(),
createdAt: createdAtColumn(),
});
export const backtestMetrics = pgTable("backtest_metrics", {
id: uuid("id").primaryKey().defaultRandom(),
runId: uuid("run_id")
.notNull()
.references(() => runs.id, { onDelete: "cascade" }),
payload: jsonb("payload").notNull(),
createdAt: createdAtColumn(),
});
export const schemaTableNames = [
"agent_personas",
"approval_decisions",
"approval_requests",
"artifacts",
"backtest_iterations",
"backtest_metrics",
"commands",
"review_findings",
"run_bindings",
"run_events",
"run_inputs",
"run_phases",
"runs",
"tui_sessions",
"tui_transcript_chunks",
"workflow_templates",
] as const;

View File

@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"types": ["node", "vitest"]
},
"include": ["src/**/*.ts"]
}