--- phase: 21-chat-foundation plan: 01 type: execute wave: 1 depends_on: ["21-00"] files_modified: - packages/db/src/schema/chat_conversations.ts - packages/db/src/schema/chat_messages.ts - packages/db/src/schema/index.ts - packages/shared/src/types/chat.ts - packages/shared/src/validators/chat.ts - packages/shared/src/types/index.ts - packages/shared/src/validators/index.ts autonomous: true requirements: [HIST-01, HIST-06] must_haves: truths: - "Conversations and messages written to the database survive a server restart" - "Shared types and Zod validators are importable from @paperclipai/shared" - "A new migration SQL file exists that can create the chat tables on first server start" artifacts: - path: "packages/db/src/schema/chat_conversations.ts" provides: "chatConversations Drizzle table" exports: ["chatConversations"] - path: "packages/db/src/schema/chat_messages.ts" provides: "chatMessages Drizzle table" exports: ["chatMessages"] - path: "packages/shared/src/types/chat.ts" provides: "ChatConversation and ChatMessage TypeScript types" exports: ["ChatConversation", "ChatMessage", "ChatConversationListItem"] - path: "packages/shared/src/validators/chat.ts" provides: "Zod schemas for create/update operations" exports: ["createConversationSchema", "updateConversationSchema", "createMessageSchema"] key_links: - from: "packages/db/src/schema/chat_conversations.ts" to: "packages/db/src/schema/companies.ts" via: "FK companyId references companies.id" pattern: "references.*companies\\.id" - from: "packages/db/src/schema/chat_messages.ts" to: "packages/db/src/schema/chat_conversations.ts" via: "FK conversationId references chatConversations.id with onDelete cascade" pattern: "onDelete.*cascade" - from: "packages/db/src/schema/index.ts" to: "packages/db/src/schema/chat_conversations.ts" via: "re-export" pattern: "export.*chatConversations.*chat_conversations" --- Create the database schema and shared types for the chat system. Purpose: Establish the persistence layer that all subsequent plans depend on — two new Drizzle tables (chat_conversations, chat_messages), a migration, and shared TypeScript types + Zod validators. Output: Migration SQL applied, tables created, types and validators importable from @paperclipai/shared. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/21-chat-foundation/21-RESEARCH.md From packages/db/src/schema/documents.ts: ```typescript import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core"; ``` From packages/db/src/schema/companies.ts: ```typescript export const companies = pgTable("companies", { id: uuid("id").primaryKey().defaultRandom(), ... }); ``` From packages/db/src/schema/agents.ts: ```typescript export const agents = pgTable("agents", { id: uuid("id").primaryKey().defaultRandom(), ... }); ``` From packages/shared/src/types/index.ts — re-exports all type modules. From packages/shared/src/validators/index.ts — re-exports all validator modules. Task 1: Create Drizzle schema files and generate migration packages/db/src/schema/chat_conversations.ts, packages/db/src/schema/chat_messages.ts, packages/db/src/schema/index.ts - packages/db/src/schema/documents.ts (reference pattern for pgTable, timestamps, indexes) - packages/db/src/schema/companies.ts (FK target for companyId) - packages/db/src/schema/agents.ts (FK target for agentId) - packages/db/src/schema/index.ts (current re-exports to extend) Create `packages/db/src/schema/chat_conversations.ts`: ```typescript import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core"; import { companies } from "./companies.js"; import { agents } from "./agents.js"; export const chatConversations = pgTable( "chat_conversations", { id: uuid("id").primaryKey().defaultRandom(), companyId: uuid("company_id").notNull().references(() => companies.id), title: text("title"), agentId: uuid("agent_id").references(() => agents.id, { onDelete: "set null" }), pinnedAt: timestamp("pinned_at", { withTimezone: true }), archivedAt: timestamp("archived_at", { withTimezone: true }), deletedAt: timestamp("deleted_at", { withTimezone: true }), createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), }, (table) => [ index("chat_conversations_company_updated_idx").on(table.companyId, table.updatedAt), index("chat_conversations_company_deleted_idx").on(table.companyId, table.deletedAt), ], ); ``` Create `packages/db/src/schema/chat_messages.ts`: ```typescript import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core"; import { chatConversations } from "./chat_conversations.js"; export const chatMessages = pgTable( "chat_messages", { id: uuid("id").primaryKey().defaultRandom(), conversationId: uuid("conversation_id").notNull() .references(() => chatConversations.id, { onDelete: "cascade" }), role: text("role").notNull(), content: text("content").notNull(), agentId: uuid("agent_id"), createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), }, (table) => [ index("chat_messages_conversation_created_idx").on(table.conversationId, table.createdAt), ], ); ``` Add to `packages/db/src/schema/index.ts` at the end: ```typescript export { chatConversations } from "./chat_conversations.js"; export { chatMessages } from "./chat_messages.js"; ``` IMPORTANT: Check the index helper pattern in the existing schema files — Drizzle v0.38 may use the array syntax `(table) => [index(...)]` instead of the object syntax `(table) => ({...})`. Match whichever pattern the existing `documents.ts` or `issues.ts` uses. Then run: ```bash cd /opt/nexus && pnpm db:generate ``` This generates the migration SQL file. Inspect it to confirm it contains `CREATE TABLE chat_conversations`, `CREATE TABLE chat_messages`, `ON DELETE CASCADE`, and the two indexes. Do NOT run `pnpm db:migrate` — that happens at server start. cd /opt/nexus && grep -r "chat_conversations" packages/db/src/schema/index.ts && grep -r "chat_messages" packages/db/src/schema/index.ts && ls packages/db/src/migrations/*.sql | tail -1 | xargs grep -l "chat_conversations" - packages/db/src/schema/chat_conversations.ts contains `export const chatConversations = pgTable(` - packages/db/src/schema/chat_messages.ts contains `export const chatMessages = pgTable(` - packages/db/src/schema/chat_messages.ts contains `onDelete: "cascade"` - packages/db/src/schema/index.ts contains `export { chatConversations } from "./chat_conversations.js"` - packages/db/src/schema/index.ts contains `export { chatMessages } from "./chat_messages.js"` - A migration SQL file exists in packages/db/src/migrations/ containing `CREATE TABLE "chat_conversations"` - The migration SQL contains `ON DELETE CASCADE` - The migration SQL contains `chat_conversations_company_updated_idx` Both Drizzle schema files exist, are exported from index.ts, and a migration SQL has been generated containing the correct DDL with FK constraints and indexes. Task 2: Create shared types and Zod validators for chat packages/shared/src/types/chat.ts, packages/shared/src/validators/chat.ts, packages/shared/src/types/index.ts, packages/shared/src/validators/index.ts - packages/shared/src/types/company.ts (reference pattern for type definitions) - packages/shared/src/validators/company.ts (reference pattern for Zod schemas) - packages/shared/src/types/index.ts (current re-exports) - packages/shared/src/validators/index.ts (current re-exports) Create `packages/shared/src/types/chat.ts`: ```typescript export interface ChatConversation { id: string; companyId: string; title: string | null; agentId: string | null; pinnedAt: string | null; archivedAt: string | null; deletedAt: string | null; createdAt: string; updatedAt: string; } export interface ChatConversationListItem { id: string; companyId: string; title: string | null; agentId: string | null; pinnedAt: string | null; archivedAt: string | null; updatedAt: string; lastMessagePreview: string | null; } export interface ChatMessage { id: string; conversationId: string; role: "user" | "assistant" | "system"; content: string; agentId: string | null; createdAt: string; } export interface ChatConversationListResponse { items: ChatConversationListItem[]; hasMore: boolean; } export interface ChatMessageListResponse { items: ChatMessage[]; hasMore: boolean; } ``` Create `packages/shared/src/validators/chat.ts`: ```typescript import { z } from "zod"; export const createConversationSchema = z.object({ title: z.string().max(200).optional(), agentId: z.string().uuid().optional(), }); export const updateConversationSchema = z.object({ title: z.string().max(200).optional(), agentId: z.string().uuid().nullable().optional(), pinnedAt: z.string().datetime().nullable().optional(), archivedAt: z.string().datetime().nullable().optional(), }); export const createMessageSchema = z.object({ role: z.enum(["user", "assistant", "system"]), content: z.string().min(1).max(100_000), agentId: z.string().uuid().optional(), }); ``` Add to `packages/shared/src/types/index.ts`: ```typescript export * from "./chat.js"; ``` Add to `packages/shared/src/validators/index.ts`: ```typescript export * from "./chat.js"; ``` cd /opt/nexus && npx tsx -e "import { createConversationSchema, createMessageSchema } from '@paperclipai/shared'; console.log('validators OK');" 2>/dev/null || grep -q "createConversationSchema" packages/shared/src/validators/chat.ts && grep -q "ChatConversation" packages/shared/src/types/chat.ts && echo "files OK" - packages/shared/src/types/chat.ts contains `export interface ChatConversation` - packages/shared/src/types/chat.ts contains `export interface ChatMessage` - packages/shared/src/types/chat.ts contains `export interface ChatConversationListItem` - packages/shared/src/validators/chat.ts contains `export const createConversationSchema` - packages/shared/src/validators/chat.ts contains `export const updateConversationSchema` - packages/shared/src/validators/chat.ts contains `export const createMessageSchema` - packages/shared/src/types/index.ts contains `export * from "./chat.js"` - packages/shared/src/validators/index.ts contains `export * from "./chat.js"` Chat types (ChatConversation, ChatMessage, ChatConversationListItem) and Zod validators (createConversationSchema, updateConversationSchema, createMessageSchema) exist and are re-exported from the shared package barrel files. - Migration SQL file contains CREATE TABLE for both chat_conversations and chat_messages - Schema files follow existing Drizzle pattern (pgTable, uuid PKs, timestamp with timezone) - chat_messages FK has ON DELETE CASCADE - Types and validators importable from @paperclipai/shared - Two new Drizzle schema files created and exported - Migration SQL generated with correct DDL - Shared types and Zod validators created and re-exported - No modifications to existing tables After completion, create `.planning/phases/21-chat-foundation/21-01-SUMMARY.md`