chore: add postgres schema migrations
This commit is contained in:
18
packages/db/package.json
Normal file
18
packages/db/package.json
Normal 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
18
packages/db/src/client.ts
Normal 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
2
packages/db/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { createDbClient, type DbClient } from "./client.js";
|
||||
export * from "./schema/index.js";
|
||||
223
packages/db/src/migrations/0000_robust_the_phantom.sql
Normal file
223
packages/db/src/migrations/0000_robust_the_phantom.sql
Normal 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");
|
||||
1597
packages/db/src/migrations/meta/0000_snapshot.json
Normal file
1597
packages/db/src/migrations/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
13
packages/db/src/migrations/meta/_journal.json
Normal file
13
packages/db/src/migrations/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1778333151192,
|
||||
"tag": "0000_robust_the_phantom",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
34
packages/db/src/migrations/migration.test.ts
Normal file
34
packages/db/src/migrations/migration.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
25
packages/db/src/schema/index.test.ts
Normal file
25
packages/db/src/schema/index.test.ts
Normal 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",
|
||||
]);
|
||||
});
|
||||
});
|
||||
321
packages/db/src/schema/index.ts
Normal file
321
packages/db/src/schema/index.ts
Normal 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;
|
||||
9
packages/db/tsconfig.json
Normal file
9
packages/db/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"types": ["node", "vitest"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user