diff --git a/apps/web/app/(app)/[workspaceSlug]/settings/runs/page.tsx b/apps/web/app/(app)/[workspaceSlug]/settings/runs/page.tsx new file mode 100644 index 0000000..a2e426e --- /dev/null +++ b/apps/web/app/(app)/[workspaceSlug]/settings/runs/page.tsx @@ -0,0 +1,399 @@ +"use client"; + +import * as React from "react"; +import { useParams } from "next/navigation"; +import { + Activity, + ChevronDown, + ChevronRight, + Loader2, + PlayCircle, +} from "lucide-react"; + +import { api } from "@/lib/trpc"; +import { Button } from "@/components/ui/button"; +import { Skeleton } from "@/components/ui/skeleton"; +import { cn } from "@/lib/utils"; + +/** + * Read-side dashboard for `agent_runs`. Mirrors the audit page's visual + * language but is open to all workspace members — runs aren't as + * sensitive as the audit log and the value is mostly debugging the + * orchestration layer once the MCP write paths land. + * + * The "Recent runs" section pages with keyset pagination on + * `started_at`. Click any row to expand and see the row's `notes` and + * `error` text — kept collapsed by default because most runs are + * uneventful and the table needs to stay scannable. + */ + +const FILTER_OPTIONS = ["all", "succeeded", "failed", "cancelled", "stalled", "open"] as const; +type FilterOption = (typeof FILTER_OPTIONS)[number]; + +const FILTER_LABEL: Record = { + all: "All", + succeeded: "Succeeded", + failed: "Failed", + cancelled: "Cancelled", + stalled: "Stalled", + open: "Open", +}; + +const OUTCOME_CHIP: Record = { + succeeded: + "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 ring-1 ring-emerald-500/20", + failed: + "bg-red-500/15 text-red-700 dark:text-red-300 ring-1 ring-red-500/20", + cancelled: + "bg-amber-500/15 text-amber-700 dark:text-amber-300 ring-1 ring-amber-500/20", + stalled: + "bg-orange-500/15 text-orange-700 dark:text-orange-300 ring-1 ring-orange-500/20", + open: "bg-sky-500/15 text-sky-700 dark:text-sky-300 ring-1 ring-sky-500/20", +}; + +function formatTimestamp(when: Date | string): string { + const d = when instanceof Date ? when : new Date(when); + return d.toLocaleString(undefined, { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +} + +/** + * Format a duration in milliseconds as a compact string. Returns "—" + * for in-flight runs (no `finishedAt`). Below a minute we show seconds + * with a single decimal so quick MCP calls don't all look like "0s". + */ +export function formatDuration( + startedAt: Date | string, + finishedAt: Date | string | null, +): string { + if (!finishedAt) return "—"; + const start = startedAt instanceof Date ? startedAt : new Date(startedAt); + const end = finishedAt instanceof Date ? finishedAt : new Date(finishedAt); + const ms = end.getTime() - start.getTime(); + if (!Number.isFinite(ms) || ms < 0) return "—"; + const seconds = ms / 1000; + if (seconds < 60) return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`; + const minutes = seconds / 60; + if (minutes < 60) return `${Math.round(minutes)}m`; + const hours = minutes / 60; + return `${hours.toFixed(hours < 10 ? 1 : 0)}h`; +} + +function formatTokens(n: number | null): string { + if (n === null || n === undefined) return "—"; + if (n < 1000) return String(n); + if (n < 1_000_000) return `${(n / 1000).toFixed(n < 10_000 ? 1 : 0)}k`; + return `${(n / 1_000_000).toFixed(1)}M`; +} + +function describeOutcome( + outcome: string | null, + finishedAt: Date | string | null, +): { label: string; key: keyof typeof OUTCOME_CHIP } { + if (!finishedAt) return { label: "open", key: "open" }; + const key = outcome && outcome in OUTCOME_CHIP ? outcome : "open"; + return { label: outcome ?? "open", key: key as keyof typeof OUTCOME_CHIP }; +} + +export default function RunsPage() { + const params = useParams(); + const workspaceSlug = params?.workspaceSlug as string | undefined; + + const [filter, setFilter] = React.useState("all"); + const [cursor, setCursor] = React.useState(undefined); + const [expanded, setExpanded] = React.useState(null); + + // Reset pagination + expansion state when the user switches filters so + // we never end up showing a cursor that was generated for a different + // outcome subset. + const onFilterChange = React.useCallback((next: FilterOption) => { + setFilter(next); + setCursor(undefined); + setExpanded(null); + }, []); + + const filterArg = filter === "all" ? undefined : filter; + + const summaryQuery = api.runs.summary.useQuery( + { workspace: workspaceSlug ?? "" }, + { enabled: Boolean(workspaceSlug) }, + ); + + const runsQuery = api.runs.listRecent.useQuery( + { + workspace: workspaceSlug ?? "", + limit: 25, + cursorStartedAt: cursor, + outcome: filterArg, + }, + { enabled: Boolean(workspaceSlug) }, + ); + + return ( +
+
+
+ +
+
+

Agent runs

+

+ Every agent session against a task. Read-only — write paths land + with the orchestrator MCP tools. +

+
+
+ + {/* Summary card */} + + + {/* Outcome filter pills */} +
+ {FILTER_OPTIONS.map((option) => ( + + ))} +
+ + {/* Recent runs table */} +
+ {runsQuery.isLoading ? ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ ) : runsQuery.error ? ( +
+ Couldn't load runs: {runsQuery.error.message} +
+ ) : !runsQuery.data || runsQuery.data.rows.length === 0 ? ( + + ) : ( + <> +
+ + + + + + + + + + + + + {runsQuery.data.rows.map((row) => { + const { label, key } = describeOutcome(row.outcome, row.finishedAt); + const isOpen = expanded === row.id; + const hasDetail = Boolean(row.notes || row.error); + return ( + + { + if (hasDetail) { + setExpanded(isOpen ? null : row.id); + } + }} + > + + + + + + + + + {isOpen && hasDetail ? ( + + + + ) : null} + + ); + })} + +
+ TaskStartedDurationActorOutcomeTokens
+ {hasDetail ? ( + isOpen ? ( + + ) : ( + + ) + ) : null} + + {row.taskTitle ?? ( + + {row.backlogItemId.slice(0, 8)} + + )} + + {formatTimestamp(row.startedAt)} + + {formatDuration(row.startedAt, row.finishedAt)} + + {row.actorUserId ? ( + + {row.actorName ?? row.actorEmail ?? row.actorUserId.slice(0, 8)} + + ) : ( + + )} + + + {label} + + + {formatTokens(row.tokensTotal)} +
+ + {row.notes ? ( +
+
+ Notes +
+

{row.notes}

+
+ ) : null} + {row.error ? ( +
+
+ Error +
+
+                                    {row.error}
+                                  
+
+ ) : null} +
+
+ + {runsQuery.data.nextCursor ? ( +
+ +
+ ) : null} + + )} +
+
+ ); +} + +function EmptyState({ filter }: { filter: FilterOption }) { + return ( +
+ +

+ {filter === "all" ? "No runs yet" : `No ${FILTER_LABEL[filter].toLowerCase()} runs`} +

+

+ Once an agent session runs against a task — via the orchestrator + or a manually-inserted row — it will appear here. +

+
+ ); +} + +type SummaryData = { + byOutcome: Record<"succeeded" | "failed" | "cancelled" | "stalled" | "open", number>; + tokensTotal: number; +}; + +function SummaryCard({ + loading, + data, + error, +}: { + loading: boolean; + data: SummaryData | undefined; + error: string | undefined; +}) { + if (loading) { + return ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ ); + } + + if (error) { + return ( +
+ Couldn't load summary: {error} +
+ ); + } + + if (!data) return null; + + const cells: Array<{ label: string; value: string; key: keyof typeof OUTCOME_CHIP | "tokens" }> = [ + { label: "Succeeded", value: String(data.byOutcome.succeeded), key: "succeeded" }, + { label: "Failed", value: String(data.byOutcome.failed), key: "failed" }, + { label: "Cancelled", value: String(data.byOutcome.cancelled), key: "cancelled" }, + { label: "Stalled", value: String(data.byOutcome.stalled), key: "stalled" }, + { label: "Open", value: String(data.byOutcome.open), key: "open" }, + { label: "Tokens (7d)", value: formatTokens(data.tokensTotal), key: "tokens" }, + ]; + + return ( +
+ {cells.map((cell) => ( +
+
+ {cell.label} +
+
{cell.value}
+
+ ))} +
+ ); +} diff --git a/apps/web/components/layout/top-header.tsx b/apps/web/components/layout/top-header.tsx index e21a20c..f6b4e81 100644 --- a/apps/web/components/layout/top-header.tsx +++ b/apps/web/components/layout/top-header.tsx @@ -3,6 +3,7 @@ import { usePathname, useRouter } from "next/navigation"; import { signOut } from "next-auth/react"; import { + Activity, AppWindow, Calendar, CheckSquare, @@ -140,6 +141,15 @@ export function TopHeader({ onOpenSearch, onQuickAction }: TopHeaderProps) { Audit log + + workspaceId && router.push(`/${workspaceId}/settings/runs`) + } + > + + Agent runs + {/* Create Workspace */}
diff --git a/apps/web/server/root.ts b/apps/web/server/root.ts index eb708f9..d82f038 100644 --- a/apps/web/server/root.ts +++ b/apps/web/server/root.ts @@ -13,6 +13,7 @@ import { favoritesRouter } from "@/server/routers/favorites"; import { identityRouter } from "@/server/routers/identity"; import { invitesRouter } from "@/server/routers/invites"; import { auditRouter } from "@/server/routers/audit"; +import { runsRouter } from "@/server/routers/runs"; export const appRouter = router({ health: healthRouter, @@ -29,6 +30,7 @@ export const appRouter = router({ identity: identityRouter, invites: invitesRouter, audit: auditRouter, + runs: runsRouter, }); export type AppRouter = typeof appRouter; diff --git a/apps/web/server/routers/runs.ts b/apps/web/server/routers/runs.ts new file mode 100644 index 0000000..9244e94 --- /dev/null +++ b/apps/web/server/routers/runs.ts @@ -0,0 +1,216 @@ +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import { and, desc, eq, gte, isNull, lt } from "drizzle-orm"; + +import { + agentRuns, + markdownBacklogItems, + users, +} from "@tasks/database/schema"; +import { router, workspaceProcedure } from "@/server/trpc"; + +const DEFAULT_LIMIT = 25; +const MAX_LIMIT = 100; + +/** + * Storage outcomes are a NULL column for in-flight runs and one of the four + * normalized terminal values otherwise. The settings UI also surfaces the + * synthetic "open" bucket which translates to `outcome IS NULL`. + */ +const TERMINAL_OUTCOMES = ["succeeded", "failed", "cancelled", "stalled"] as const; +const FILTER_OUTCOMES = [...TERMINAL_OUTCOMES, "open"] as const; +const outcomeFilterSchema = z.enum(FILTER_OUTCOMES).optional(); + +type FilterOutcome = (typeof FILTER_OUTCOMES)[number]; + +function outcomePredicate(outcome: FilterOutcome | undefined) { + if (!outcome) return undefined; + if (outcome === "open") return isNull(agentRuns.finishedAt); + return eq(agentRuns.outcome, outcome); +} + +/** + * Read-side surface for `agent_runs`. Write paths land in the next epic + * with `claim_task` / `complete_task` MCP tools — this router is + * intentionally read-only. Pagination uses keyset on `started_at` to + * stay scalable; we accept the rare-tie risk on identical timestamps + * (refresh fixes it) instead of paying for an `(started_at, id)` tuple + * comparison. + */ +export const runsRouter = router({ + listRecent: workspaceProcedure + .input( + z.object({ + limit: z.number().int().min(1).max(MAX_LIMIT).optional(), + cursorStartedAt: z.string().datetime().optional(), + outcome: outcomeFilterSchema, + }), + ) + .query(async ({ ctx, input }) => { + const limit = input.limit ?? DEFAULT_LIMIT; + const cursor = input.cursorStartedAt ? new Date(input.cursorStartedAt) : null; + + const filters = [eq(agentRuns.workspaceId, ctx.workspace.id)]; + if (cursor) filters.push(lt(agentRuns.startedAt, cursor)); + const outcomeWhere = outcomePredicate(input.outcome); + if (outcomeWhere) filters.push(outcomeWhere); + + const rows = await ctx.db + .select({ + id: agentRuns.id, + backlogItemId: agentRuns.backlogItemId, + taskTitle: markdownBacklogItems.title, + taskKind: markdownBacklogItems.kind, + actorUserId: agentRuns.actorUserId, + actorName: users.name, + actorEmail: users.email, + startedAt: agentRuns.startedAt, + finishedAt: agentRuns.finishedAt, + outcome: agentRuns.outcome, + error: agentRuns.error, + tokensInput: agentRuns.tokensInput, + tokensOutput: agentRuns.tokensOutput, + tokensTotal: agentRuns.tokensTotal, + notes: agentRuns.notes, + metadata: agentRuns.metadata, + }) + .from(agentRuns) + .leftJoin( + markdownBacklogItems, + eq(agentRuns.backlogItemId, markdownBacklogItems.id), + ) + .leftJoin(users, eq(agentRuns.actorUserId, users.id)) + .where(and(...filters)) + .orderBy(desc(agentRuns.startedAt)) + .limit(limit + 1); + + const hasMore = rows.length > limit; + const page = hasMore ? rows.slice(0, limit) : rows; + const nextCursor = hasMore + ? page[page.length - 1]!.startedAt.toISOString() + : null; + + return { rows: page, nextCursor }; + }), + + listForTask: workspaceProcedure + .input( + z.object({ + backlogItemId: z.string().uuid(), + outcome: outcomeFilterSchema, + }), + ) + .query(async ({ ctx, input }) => { + // Tenant boundary check before exposing run history. The backlog + // item must live in the caller's workspace; otherwise we 404 to + // avoid leaking existence across tenants. + const [item] = await ctx.db + .select({ id: markdownBacklogItems.id }) + .from(markdownBacklogItems) + .where( + and( + eq(markdownBacklogItems.id, input.backlogItemId), + eq(markdownBacklogItems.workspaceId, ctx.workspace.id), + ), + ) + .limit(1); + + if (!item) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Backlog item not found in this workspace.", + }); + } + + const filters = [ + eq(agentRuns.workspaceId, ctx.workspace.id), + eq(agentRuns.backlogItemId, input.backlogItemId), + ]; + const outcomeWhere = outcomePredicate(input.outcome); + if (outcomeWhere) filters.push(outcomeWhere); + + const rows = await ctx.db + .select({ + id: agentRuns.id, + backlogItemId: agentRuns.backlogItemId, + taskTitle: markdownBacklogItems.title, + taskKind: markdownBacklogItems.kind, + actorUserId: agentRuns.actorUserId, + actorName: users.name, + actorEmail: users.email, + startedAt: agentRuns.startedAt, + finishedAt: agentRuns.finishedAt, + outcome: agentRuns.outcome, + error: agentRuns.error, + tokensInput: agentRuns.tokensInput, + tokensOutput: agentRuns.tokensOutput, + tokensTotal: agentRuns.tokensTotal, + notes: agentRuns.notes, + metadata: agentRuns.metadata, + }) + .from(agentRuns) + .leftJoin( + markdownBacklogItems, + eq(agentRuns.backlogItemId, markdownBacklogItems.id), + ) + .leftJoin(users, eq(agentRuns.actorUserId, users.id)) + .where(and(...filters)) + .orderBy(desc(agentRuns.startedAt)) + .limit(10); + + return { rows }; + }), + + summary: workspaceProcedure.query(async ({ ctx }) => { + // 7-day rolling window. We compute the cutoff in JS to keep the + // query plan stable and the index on (workspace_id, started_at) + // usable; a `now() - interval` predicate would also work but + // cooperating with parameterized SQL is friendlier to drizzle. + const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); + + const rows = await ctx.db + .select({ + outcome: agentRuns.outcome, + finishedAt: agentRuns.finishedAt, + tokensTotal: agentRuns.tokensTotal, + }) + .from(agentRuns) + .where( + and( + eq(agentRuns.workspaceId, ctx.workspace.id), + gte(agentRuns.startedAt, cutoff), + ), + ); + + const byOutcome: Record< + "succeeded" | "failed" | "cancelled" | "stalled" | "open", + number + > = { + succeeded: 0, + failed: 0, + cancelled: 0, + stalled: 0, + open: 0, + }; + let tokensTotal = 0; + for (const row of rows) { + if (row.finishedAt === null) { + byOutcome.open += 1; + } else if ( + row.outcome === "succeeded" || + row.outcome === "failed" || + row.outcome === "cancelled" || + row.outcome === "stalled" + ) { + byOutcome[row.outcome] += 1; + } + if (typeof row.tokensTotal === "number") { + tokensTotal += row.tokensTotal; + } + } + + return { byOutcome, tokensTotal }; + }), +}); + +export type RunsRouter = typeof runsRouter; diff --git a/packages/database/migrations/0009_loving_rogue.sql b/packages/database/migrations/0009_loving_rogue.sql new file mode 100644 index 0000000..b175945 --- /dev/null +++ b/packages/database/migrations/0009_loving_rogue.sql @@ -0,0 +1,21 @@ +CREATE TABLE "agent_runs" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "workspace_id" uuid NOT NULL, + "backlog_item_id" uuid NOT NULL, + "actor_user_id" uuid, + "started_at" timestamp with time zone DEFAULT now() NOT NULL, + "finished_at" timestamp with time zone, + "outcome" varchar(20), + "error" text, + "tokens_input" integer, + "tokens_output" integer, + "tokens_total" integer, + "notes" text, + "metadata" jsonb +); +--> statement-breakpoint +ALTER TABLE "agent_runs" ADD CONSTRAINT "agent_runs_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "agent_runs" ADD CONSTRAINT "agent_runs_backlog_item_id_markdown_backlog_items_id_fk" FOREIGN KEY ("backlog_item_id") REFERENCES "public"."markdown_backlog_items"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "agent_runs" ADD CONSTRAINT "agent_runs_actor_user_id_users_id_fk" FOREIGN KEY ("actor_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "agent_runs_workspace_id_started_at_idx" ON "agent_runs" USING btree ("workspace_id","started_at" DESC NULLS LAST);--> statement-breakpoint +CREATE INDEX "agent_runs_backlog_item_id_started_at_idx" ON "agent_runs" USING btree ("backlog_item_id","started_at" DESC NULLS LAST); \ No newline at end of file diff --git a/packages/database/migrations/meta/0009_snapshot.json b/packages/database/migrations/meta/0009_snapshot.json new file mode 100644 index 0000000..953f05d --- /dev/null +++ b/packages/database/migrations/meta/0009_snapshot.json @@ -0,0 +1,3054 @@ +{ + "id": "8887562c-99dd-4b61-8b06-44dd39ddfc78", + "prevId": "74037ec5-ba7a-4d64-a0d5-272f81dad116", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.workspace_invites": { + "name": "workspace_invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now() + interval '14 days'" + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_invites_workspace_id_idx": { + "name": "workspace_invites_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_invites_token_unique": { + "name": "workspace_invites_token_unique", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_invites_open_email_unique": { + "name": "workspace_invites_open_email_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_invites\".\"accepted_at\" IS NULL AND \"workspace_invites\".\"revoked_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_invites_workspace_id_workspaces_id_fk": { + "name": "workspace_invites_workspace_id_workspaces_id_fk", + "tableFrom": "workspace_invites", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_invites_invited_by_user_id_users_id_fk": { + "name": "workspace_invites_invited_by_user_id_users_id_fk", + "tableFrom": "workspace_invites", + "tableTo": "users", + "columnsFrom": [ + "invited_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspaces": { + "name": "workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "varchar(60)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plan_tier": { + "name": "plan_tier", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspaces_slug_unique": { + "name": "workspaces_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspaces_owner_user_id_idx": { + "name": "workspaces_owner_user_id_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspaces_owner_user_id_users_id_fk": { + "name": "workspaces_owner_user_id_users_id_fk", + "tableFrom": "workspaces", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.object_assignees": { + "name": "object_assignees", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'assignee'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "object_assignees_object_id_user_id_unique": { + "name": "object_assignees_object_id_user_id_unique", + "columns": [ + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "object_assignees_object_id_idx": { + "name": "object_assignees_object_id_idx", + "columns": [ + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "object_assignees_user_id_idx": { + "name": "object_assignees_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "object_assignees_object_id_objects_id_fk": { + "name": "object_assignees_object_id_objects_id_fk", + "tableFrom": "object_assignees", + "tableTo": "objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "object_assignees_user_id_users_id_fk": { + "name": "object_assignees_user_id_users_id_fk", + "tableFrom": "object_assignees", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.objects": { + "name": "objects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "type": { + "name": "type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_image": { + "name": "cover_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "objects_parent_id_idx": { + "name": "objects_parent_id_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "objects_type_idx": { + "name": "objects_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "objects_workspace_id_idx": { + "name": "objects_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "objects_template_id_idx": { + "name": "objects_template_id_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "objects_created_by_idx": { + "name": "objects_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "objects_type_workspace_id_idx": { + "name": "objects_type_workspace_id_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "objects_workspace_id_workspaces_id_fk": { + "name": "objects_workspace_id_workspaces_id_fk", + "tableFrom": "objects", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "objects_created_by_users_id_fk": { + "name": "objects_created_by_users_id_fk", + "tableFrom": "objects", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "objects_parent_id_objects_id_fk": { + "name": "objects_parent_id_objects_id_fk", + "tableFrom": "objects", + "tableTo": "objects", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "objects_template_id_templates_id_fk": { + "name": "objects_template_id_templates_id_fk", + "tableFrom": "objects", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_members": { + "name": "workspace_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_members_workspace_id_user_id_unique": { + "name": "workspace_members_workspace_id_user_id_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_members_workspace_id_idx": { + "name": "workspace_members_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_members_user_id_idx": { + "name": "workspace_members_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_members_workspace_id_workspaces_id_fk": { + "name": "workspace_members_workspace_id_workspaces_id_fk", + "tableFrom": "workspace_members", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_members_user_id_users_id_fk": { + "name": "workspace_members_user_id_users_id_fk", + "tableFrom": "workspace_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.object_type_defs": { + "name": "object_type_defs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "layout": { + "name": "layout", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "default_properties": { + "name": "default_properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "object_type_defs_workspace_id_idx": { + "name": "object_type_defs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "object_type_defs_slug_idx": { + "name": "object_type_defs_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "object_type_defs_workspace_id_workspaces_id_fk": { + "name": "object_type_defs_workspace_id_workspaces_id_fk", + "tableFrom": "object_type_defs", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.property_definitions": { + "name": "property_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "property_definitions_workspace_id_idx": { + "name": "property_definitions_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "property_definitions_workspace_id_name_idx": { + "name": "property_definitions_workspace_id_name_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "property_definitions_workspace_id_workspaces_id_fk": { + "name": "property_definitions_workspace_id_workspaces_id_fk", + "tableFrom": "property_definitions", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.property_values": { + "name": "property_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "property_def_id": { + "name": "property_def_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "property_values_object_id_property_def_id_unique": { + "name": "property_values_object_id_property_def_id_unique", + "columns": [ + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_def_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "property_values_object_id_idx": { + "name": "property_values_object_id_idx", + "columns": [ + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "property_values_property_def_id_idx": { + "name": "property_values_property_def_id_idx", + "columns": [ + { + "expression": "property_def_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "property_values_object_id_objects_id_fk": { + "name": "property_values_object_id_objects_id_fk", + "tableFrom": "property_values", + "tableTo": "objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "property_values_property_def_id_property_definitions_id_fk": { + "name": "property_values_property_def_id_property_definitions_id_fk", + "tableFrom": "property_values", + "tableTo": "property_definitions", + "columnsFrom": [ + "property_def_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.views": { + "name": "views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "view_type": { + "name": "view_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "views_object_id_idx": { + "name": "views_object_id_idx", + "columns": [ + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "views_object_id_objects_id_fk": { + "name": "views_object_id_objects_id_fk", + "tableFrom": "views", + "tableTo": "objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "accounts_provider_provider_account_id_unique": { + "name": "accounts_provider_provider_account_id_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "accounts_user_id_idx": { + "name": "accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_token": { + "name": "session_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_session_token_unique": { + "name": "sessions_session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "session_token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_email_identities": { + "name": "user_email_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_identities_user_id_idx": { + "name": "user_email_identities_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_email_identities_email_idx": { + "name": "user_email_identities_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_email_identities_user_id_email_unique": { + "name": "user_email_identities_user_id_email_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_email_identities_verified_email_unique": { + "name": "user_email_identities_verified_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_email_identities\".\"verified_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_email_identities_user_id_users_id_fk": { + "name": "user_email_identities_user_id_users_id_fk", + "tableFrom": "user_email_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_email_lower_unique": { + "name": "users_email_lower_unique", + "columns": [ + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification_tokens": { + "name": "verification_tokens", + "schema": "", + "columns": { + "identifier": { + "name": "identifier", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verification_tokens_identifier_token_pk": { + "name": "verification_tokens_identifier_token_pk", + "columns": [ + "identifier", + "token" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.object_relations": { + "name": "object_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "relation_type": { + "name": "relation_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "object_relations_source_id_idx": { + "name": "object_relations_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "object_relations_target_id_idx": { + "name": "object_relations_target_id_idx", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "object_relations_relation_type_idx": { + "name": "object_relations_relation_type_idx", + "columns": [ + { + "expression": "relation_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "object_relations_source_target_type_unique": { + "name": "object_relations_source_target_type_unique", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "relation_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "object_relations_source_id_objects_id_fk": { + "name": "object_relations_source_id_objects_id_fk", + "tableFrom": "object_relations", + "tableTo": "objects", + "columnsFrom": [ + "source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "object_relations_target_id_objects_id_fk": { + "name": "object_relations_target_id_objects_id_fk", + "tableFrom": "object_relations", + "tableTo": "objects", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "templates_workspace_id_idx": { + "name": "templates_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "templates_workspace_id_workspaces_id_fk": { + "name": "templates_workspace_id_workspaces_id_fk", + "tableFrom": "templates", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.form_responses": { + "name": "form_responses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "form_id": { + "name": "form_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "respondent_id": { + "name": "respondent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_object_id": { + "name": "created_object_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "submitted_at": { + "name": "submitted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "form_responses_form_id_idx": { + "name": "form_responses_form_id_idx", + "columns": [ + { + "expression": "form_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_responses_respondent_id_idx": { + "name": "form_responses_respondent_id_idx", + "columns": [ + { + "expression": "respondent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "form_responses_form_id_forms_id_fk": { + "name": "form_responses_form_id_forms_id_fk", + "tableFrom": "form_responses", + "tableTo": "forms", + "columnsFrom": [ + "form_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "form_responses_respondent_id_users_id_fk": { + "name": "form_responses_respondent_id_users_id_fk", + "tableFrom": "form_responses", + "tableTo": "users", + "columnsFrom": [ + "respondent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "form_responses_created_object_id_objects_id_fk": { + "name": "form_responses_created_object_id_objects_id_fk", + "tableFrom": "form_responses", + "tableTo": "objects", + "columnsFrom": [ + "created_object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.forms": { + "name": "forms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_image": { + "name": "cover_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "forms_workspace_id_idx": { + "name": "forms_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "forms_object_id_idx": { + "name": "forms_object_id_idx", + "columns": [ + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "forms_workspace_id_workspaces_id_fk": { + "name": "forms_workspace_id_workspaces_id_fk", + "tableFrom": "forms", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "forms_object_id_objects_id_fk": { + "name": "forms_object_id_objects_id_fk", + "tableFrom": "forms", + "tableTo": "objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "forms_created_by_users_id_fk": { + "name": "forms_created_by_users_id_fk", + "tableFrom": "forms", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_favorites": { + "name": "user_favorites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_favorites_user_object_idx": { + "name": "user_favorites_user_object_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_favorites_user_id_users_id_fk": { + "name": "user_favorites_user_id_users_id_fk", + "tableFrom": "user_favorites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_favorites_object_id_objects_id_fk": { + "name": "user_favorites_object_id_objects_id_fk", + "tableFrom": "user_favorites", + "tableTo": "objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.markdown_backlog_items": { + "name": "markdown_backlog_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "plan_slug": { + "name": "plan_slug", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "epic_slug": { + "name": "epic_slug", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_path": { + "name": "repo_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "frontmatter": { + "name": "frontmatter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "body_markdown": { + "name": "body_markdown", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content_hash": { + "name": "content_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "workflow_prompt": { + "name": "workflow_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "markdown_backlog_workspace_repo_path_unique": { + "name": "markdown_backlog_workspace_repo_path_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "markdown_backlog_workspace_plan_idx": { + "name": "markdown_backlog_workspace_plan_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plan_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "markdown_backlog_parent_id_idx": { + "name": "markdown_backlog_parent_id_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "markdown_backlog_workspace_kind_idx": { + "name": "markdown_backlog_workspace_kind_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "markdown_backlog_items_workspace_id_workspaces_id_fk": { + "name": "markdown_backlog_items_workspace_id_workspaces_id_fk", + "tableFrom": "markdown_backlog_items", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "markdown_backlog_items_parent_id_markdown_backlog_items_id_fk": { + "name": "markdown_backlog_items_parent_id_markdown_backlog_items_id_fk", + "tableFrom": "markdown_backlog_items", + "tableTo": "markdown_backlog_items", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cursor_sync_mappings": { + "name": "cursor_sync_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "backlog_item_id": { + "name": "backlog_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cursor_plan_id": { + "name": "cursor_plan_id", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "cursor_item_id": { + "name": "cursor_item_id", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "last_pulled_at": { + "name": "last_pulled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_pushed_at": { + "name": "last_pushed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_content_hash": { + "name": "sync_content_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cursor_sync_mappings_backlog_item_id_unique": { + "name": "cursor_sync_mappings_backlog_item_id_unique", + "columns": [ + { + "expression": "backlog_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cursor_sync_mappings_workspace_id_idx": { + "name": "cursor_sync_mappings_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cursor_sync_mappings_workspace_id_workspaces_id_fk": { + "name": "cursor_sync_mappings_workspace_id_workspaces_id_fk", + "tableFrom": "cursor_sync_mappings", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cursor_sync_mappings_backlog_item_id_markdown_backlog_items_id_fk": { + "name": "cursor_sync_mappings_backlog_item_id_markdown_backlog_items_id_fk", + "tableFrom": "cursor_sync_mappings", + "tableTo": "markdown_backlog_items", + "columnsFrom": [ + "backlog_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_id_created_at_idx": { + "name": "audit_log_workspace_id_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_user_id_idx": { + "name": "audit_log_actor_user_id_idx", + "columns": [ + { + "expression": "actor_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspaces_id_fk": { + "name": "audit_log_workspace_id_workspaces_id_fk", + "tableFrom": "audit_log", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "audit_log_actor_user_id_users_id_fk": { + "name": "audit_log_actor_user_id_users_id_fk", + "tableFrom": "audit_log", + "tableTo": "users", + "columnsFrom": [ + "actor_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runs": { + "name": "agent_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "backlog_item_id": { + "name": "backlog_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_input": { + "name": "tokens_input", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_output": { + "name": "tokens_output", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_total": { + "name": "tokens_total", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_runs_workspace_id_started_at_idx": { + "name": "agent_runs_workspace_id_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runs_backlog_item_id_started_at_idx": { + "name": "agent_runs_backlog_item_id_started_at_idx", + "columns": [ + { + "expression": "backlog_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runs_workspace_id_workspaces_id_fk": { + "name": "agent_runs_workspace_id_workspaces_id_fk", + "tableFrom": "agent_runs", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_runs_backlog_item_id_markdown_backlog_items_id_fk": { + "name": "agent_runs_backlog_item_id_markdown_backlog_items_id_fk", + "tableFrom": "agent_runs", + "tableTo": "markdown_backlog_items", + "columnsFrom": [ + "backlog_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_runs_actor_user_id_users_id_fk": { + "name": "agent_runs_actor_user_id_users_id_fk", + "tableFrom": "agent_runs", + "tableTo": "users", + "columnsFrom": [ + "actor_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/database/migrations/meta/_journal.json b/packages/database/migrations/meta/_journal.json index 0015046..e8bd8b4 100644 --- a/packages/database/migrations/meta/_journal.json +++ b/packages/database/migrations/meta/_journal.json @@ -57,6 +57,20 @@ "when": 1780424597399, "tag": "0007_flaky_kinsey_walden", "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1780455565316, + "tag": "0008_curly_zzzax", + "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1780455865684, + "tag": "0009_loving_rogue", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/database/src/schema/agent_runs.ts b/packages/database/src/schema/agent_runs.ts new file mode 100644 index 0000000..c1049b8 --- /dev/null +++ b/packages/database/src/schema/agent_runs.ts @@ -0,0 +1,81 @@ +import { + pgTable, + uuid, + varchar, + text, + integer, + jsonb, + timestamp, + index, +} from "drizzle-orm/pg-core"; + +import { workspaces } from "./workspaces"; +import { users } from "./users"; +import { markdownBacklogItems } from "./markdown_backlog"; + +/** + * One row per agent session against a backlog item. The MCP tools + * (`claim_task`, `complete_task`, …) will be the write path in the next + * epic; this table is read-side + storage only at this stage. Rows can + * be inserted manually for development. + * + * Conventions captured here so future maintainers don't drift: + * + * - `outcome` is constrained at the application layer to one of + * {succeeded, failed, cancelled, stalled}. The column is nullable + * because in-flight runs (no `finished_at`) carry NULL outcomes — + * surfaced in the UI as the synthetic "open" bucket. + * + * - `actorUserId` is null for anonymous / dev sessions where no user + * is attached. The settings UI shows "—" for null actors. + * + * - Token totals follow the Symphony model: a single absolute + * `tokens_total` recorded at close-out, not incremental deltas. + * `tokens_input` / `tokens_output` are optional breakdowns. + * + * - `metadata` is JSONB but should stay small (roughly < 1 KB). + * Don't dump full transcripts here — that's what an external + * object store is for. + * + * - Indexes are tuned for the two read paths the UI exercises: + * `(workspace_id, started_at DESC)` for the recent-runs settings + * view and `(backlog_item_id, started_at DESC)` for per-task + * history on the task detail panel (deferred to a follow-up task + * to avoid stepping on the parent's concurrent work). + */ +export const agentRuns = pgTable( + "agent_runs", + { + id: uuid("id").primaryKey().defaultRandom(), + workspaceId: uuid("workspace_id") + .notNull() + .references(() => workspaces.id, { onDelete: "cascade" }), + backlogItemId: uuid("backlog_item_id") + .notNull() + .references(() => markdownBacklogItems.id, { onDelete: "cascade" }), + actorUserId: uuid("actor_user_id").references(() => users.id, { + onDelete: "set null", + }), + startedAt: timestamp("started_at", { withTimezone: true }) + .defaultNow() + .notNull(), + finishedAt: timestamp("finished_at", { withTimezone: true }), + outcome: varchar("outcome", { length: 20 }), + error: text("error"), + tokensInput: integer("tokens_input"), + tokensOutput: integer("tokens_output"), + tokensTotal: integer("tokens_total"), + notes: text("notes"), + metadata: jsonb("metadata").$type | null>(), + }, + (table) => ({ + workspaceStartedIdx: index("agent_runs_workspace_id_started_at_idx").on( + table.workspaceId, + table.startedAt.desc(), + ), + backlogStartedIdx: index("agent_runs_backlog_item_id_started_at_idx").on( + table.backlogItemId, + table.startedAt.desc(), + ), + }), +); diff --git a/packages/database/src/schema/index.ts b/packages/database/src/schema/index.ts index eeb74cd..364958e 100644 --- a/packages/database/src/schema/index.ts +++ b/packages/database/src/schema/index.ts @@ -12,3 +12,4 @@ export * from "./favorites"; export * from "./markdown_backlog"; export * from "./cursor_sync"; export * from "./audit"; +export * from "./agent_runs"; diff --git a/packages/database/src/schema/relations.ts b/packages/database/src/schema/relations.ts index 3c4ab98..279497b 100644 --- a/packages/database/src/schema/relations.ts +++ b/packages/database/src/schema/relations.ts @@ -18,6 +18,7 @@ import { markdownBacklogItems } from "./markdown_backlog"; import { cursorSyncMappings } from "./cursor_sync"; import { workspaces, workspaceInvites } from "./workspaces"; import { auditLog } from "./audit"; +import { agentRuns } from "./agent_runs"; export const objectRelations = pgTable( "object_relations", @@ -88,6 +89,21 @@ export const auditLogRelations = relations(auditLog, ({ one }) => ({ }), })); +export const agentRunsRelations = relations(agentRuns, ({ one }) => ({ + workspace: one(workspaces, { + fields: [agentRuns.workspaceId], + references: [workspaces.id], + }), + backlogItem: one(markdownBacklogItems, { + fields: [agentRuns.backlogItemId], + references: [markdownBacklogItems.id], + }), + actor: one(users, { + fields: [agentRuns.actorUserId], + references: [users.id], + }), +})); + export const workspacesRelations = relations(workspaces, ({ one, many }) => ({ owner: one(users, { fields: [workspaces.ownerUserId],