---
phase: 25-file-system
plan: 00
type: execute
wave: 1
depends_on: []
files_modified:
  - packages/db/src/schema/chat_files.ts
  - packages/db/src/schema/chat_file_references.ts
  - packages/db/src/schema/index.ts
  - packages/db/src/migrations/0053_create_chat_files.sql
  - packages/db/src/migrations/0054_create_chat_file_references.sql
  - packages/shared/src/types/chat.ts
  - packages/shared/src/validators/chat.ts
  - packages/shared/src/index.ts
  - server/src/__tests__/chat-file-service.test.ts
  - server/src/__tests__/chat-file-routes.test.ts
autonomous: true
requirements:
  - FILE-01
  - FILE-02
  - FILE-03
must_haves:
  truths:
    - "chat_files table exists in Postgres with all required metadata columns"
    - "chat_file_references table exists enabling cross-conversation file references"
    - "Shared types and validators for file operations are exported from @paperclipai/shared"
  artifacts:
    - path: "packages/db/src/schema/chat_files.ts"
      provides: "chat_files Drizzle schema table"
      contains: "pgTable"
    - path: "packages/db/src/schema/chat_file_references.ts"
      provides: "chat_file_references Drizzle schema table"
      contains: "pgTable"
    - path: "packages/shared/src/types/chat.ts"
      provides: "ChatFile and ChatFileReference types"
      contains: "ChatFile"
  key_links:
    - from: "packages/db/src/schema/chat_files.ts"
      to: "packages/db/src/schema/chat_conversations.ts"
      via: "FK reference conversationId"
      pattern: "chatConversations"
    - from: "packages/db/src/schema/chat_file_references.ts"
      to: "packages/db/src/schema/chat_files.ts"
      via: "FK reference fileId"
      pattern: "chatFiles"
---

<objective>
Create the database schema, shared types, validators, and test stubs for the chat file system.

Purpose: Establish the data layer contracts that all subsequent plans (upload routes, UI previews) build against.
Output: Two new DB tables (chat_files, chat_file_references), shared TypeScript types, Zod validators, and test scaffolds.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/25-file-system/25-RESEARCH.md

@packages/db/src/schema/chat_messages.ts
@packages/db/src/schema/chat_conversations.ts
@packages/db/src/schema/assets.ts
@packages/db/src/schema/index.ts
@packages/shared/src/types/chat.ts
@packages/shared/src/validators/chat.ts
@server/src/__tests__/chat-routes.test.ts
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create chat_files and chat_file_references DB schema + migrations</name>
  <files>
    packages/db/src/schema/chat_files.ts,
    packages/db/src/schema/chat_file_references.ts,
    packages/db/src/schema/index.ts,
    packages/db/src/migrations/0053_create_chat_files.sql,
    packages/db/src/migrations/0054_create_chat_file_references.sql
  </files>
  <read_first>
    packages/db/src/schema/chat_messages.ts,
    packages/db/src/schema/chat_conversations.ts,
    packages/db/src/schema/assets.ts,
    packages/db/src/schema/index.ts,
    packages/db/src/migrations/meta/_journal.json
  </read_first>
  <action>
    Create `packages/db/src/schema/chat_files.ts` with a `chatFiles` pgTable containing:
    - id: uuid PK defaultRandom
    - companyId: uuid NOT NULL FK to companies.id
    - conversationId: uuid FK to chatConversations.id (nullable — file may exist without conversation)
    - messageId: uuid FK to chatMessages.id ON DELETE SET NULL (nullable — file may be uploaded before message is sent)
    - filename: text NOT NULL (display filename, may differ from original)
    - originalFilename: text NOT NULL (as uploaded)
    - mimeType: text NOT NULL
    - sizeBytes: integer NOT NULL
    - objectKey: text NOT NULL (StorageService object key)
    - sha256: text NOT NULL
    - source: text NOT NULL (values: 'user_upload', 'agent_generated')
    - category: text (values: 'image', 'document', 'code', 'other' — nullable, derived from mimeType)
    - projectId: uuid FK to projects.id ON DELETE SET NULL (nullable — for dual scoping FILE-04)
    - createdAt: timestamp with timezone NOT NULL defaultNow
    - updatedAt: timestamp with timezone NOT NULL defaultNow

    Indexes (use object-syntax callback like existing chat schemas):
    - chat_files_conversation_idx on (conversationId)
    - chat_files_message_idx on (messageId)
    - chat_files_company_created_idx on (companyId, createdAt)
    - chat_files_project_idx on (projectId)

    Create `packages/db/src/schema/chat_file_references.ts` with a `chatFileReferences` pgTable:
    - id: uuid PK defaultRandom
    - fileId: uuid NOT NULL FK to chatFiles.id ON DELETE CASCADE
    - conversationId: uuid NOT NULL FK to chatConversations.id ON DELETE CASCADE
    - messageId: uuid FK to chatMessages.id ON DELETE SET NULL (nullable)
    - createdAt: timestamp with timezone NOT NULL defaultNow

    Indexes:
    - chat_file_refs_file_idx on (fileId)
    - chat_file_refs_conversation_idx on (conversationId)
    - chat_file_refs_message_idx on (messageId)

    Add exports to `packages/db/src/schema/index.ts`:
    - export { chatFiles } from "./chat_files.js";
    - export { chatFileReferences } from "./chat_file_references.js";

    Create migration SQL files:
    - 0053_create_chat_files.sql: CREATE TABLE chat_files with all columns and indexes
    - 0054_create_chat_file_references.sql: CREATE TABLE chat_file_references with all columns and indexes

    Update `packages/db/src/migrations/meta/_journal.json` to add entries for idx 53 and 54.
    Copy the latest snapshot JSON file as a template for the new snapshots (0053_snapshot.json, 0054_snapshot.json) — but note that the migration system only strictly requires the SQL files and journal entries; skip snapshot creation if too complex.

    IMPORTANT: Follow existing index callback pattern from chat_messages.ts — use `(table) => ({})` object syntax, not array syntax.
  </action>
  <verify>
    <automated>cd /opt/nexus && grep -q "chatFiles" packages/db/src/schema/index.ts && grep -q "chatFileReferences" packages/db/src/schema/index.ts && grep -q "chat_files" packages/db/src/migrations/0053_create_chat_files.sql && grep -q "chat_file_references" packages/db/src/migrations/0054_create_chat_file_references.sql && echo "PASS"</automated>
  </verify>
  <acceptance_criteria>
    - grep "chatFiles" packages/db/src/schema/index.ts returns a match
    - grep "chatFileReferences" packages/db/src/schema/index.ts returns a match
    - grep "pgTable" packages/db/src/schema/chat_files.ts returns a match
    - grep "pgTable" packages/db/src/schema/chat_file_references.ts returns a match
    - grep "CREATE TABLE" packages/db/src/migrations/0053_create_chat_files.sql returns a match
    - grep "CREATE TABLE" packages/db/src/migrations/0054_create_chat_file_references.sql returns a match
    - grep "company_id" packages/db/src/schema/chat_files.ts returns a match (dual scoping support)
    - grep "project_id" packages/db/src/schema/chat_files.ts returns a match (FILE-04 support)
    - grep "source" packages/db/src/schema/chat_files.ts returns a match
  </acceptance_criteria>
  <done>
    chat_files and chat_file_references Drizzle schemas exist with all columns, indexes, FKs.
    Migration SQL files exist. Schema index re-exports both tables.
  </done>
</task>

<task type="auto">
  <name>Task 2: Add shared types, validators, and test stubs</name>
  <files>
    packages/shared/src/types/chat.ts,
    packages/shared/src/validators/chat.ts,
    packages/shared/src/index.ts,
    server/src/__tests__/chat-file-service.test.ts,
    server/src/__tests__/chat-file-routes.test.ts
  </files>
  <read_first>
    packages/shared/src/types/chat.ts,
    packages/shared/src/validators/chat.ts,
    packages/shared/src/index.ts,
    server/src/__tests__/chat-routes.test.ts
  </read_first>
  <action>
    Extend `packages/shared/src/types/chat.ts` — add these interfaces (append, do NOT modify existing interfaces):

    ```typescript
    export interface ChatFile {
      id: string;
      companyId: string;
      conversationId: string | null;
      messageId: string | null;
      filename: string;
      originalFilename: string;
      mimeType: string;
      sizeBytes: number;
      objectKey: string;
      sha256: string;
      source: "user_upload" | "agent_generated";
      category: "image" | "document" | "code" | "other" | null;
      projectId: string | null;
      createdAt: string;
      updatedAt: string;
    }

    export interface ChatFileReference {
      id: string;
      fileId: string;
      conversationId: string;
      messageId: string | null;
      createdAt: string;
    }

    export interface ChatFileUploadResponse {
      file: ChatFile;
      contentPath: string;
    }

    export interface ChatFileListResponse {
      items: ChatFile[];
    }
    ```

    Also extend `ChatMessage` by adding an optional `files` field:
    ```typescript
    // In ChatMessage interface, add:
    files?: ChatFile[];
    ```

    Extend `packages/shared/src/validators/chat.ts` — add Zod schemas:
    ```typescript
    export const uploadChatFileSchema = z.object({
      conversationId: z.string().uuid().optional(),
      messageId: z.string().uuid().optional(),
      source: z.enum(["user_upload", "agent_generated"]).default("user_upload"),
      projectId: z.string().uuid().optional(),
    });

    export const createFileReferenceSchema = z.object({
      fileId: z.string().uuid(),
      messageId: z.string().uuid().optional(),
    });
    ```

    Ensure new schemas and types are re-exported from `packages/shared/src/index.ts` — check existing export pattern in that file and add:
    - The new type exports (ChatFile, ChatFileReference, ChatFileUploadResponse, ChatFileListResponse)
    - The new validator exports (uploadChatFileSchema, createFileReferenceSchema)

    Create test stubs:

    `server/src/__tests__/chat-file-service.test.ts`:
    ```typescript
    import { describe, it } from "vitest";

    describe("chatFileService", () => {
      it.todo("creates a file record after upload");
      it.todo("lists files for a conversation");
      it.todo("lists files for a message");
      it.todo("creates a file reference in another conversation");
      it.todo("returns file with contentPath");
    });
    ```

    `server/src/__tests__/chat-file-routes.test.ts`:
    ```typescript
    import { describe, it } from "vitest";

    describe("chatFileRoutes", () => {
      it.todo("POST /conversations/:id/files uploads a file and returns 201");
      it.todo("GET /conversations/:id/files lists files for conversation");
      it.todo("GET /files/:fileId/content serves file content");
      it.todo("POST /files/:fileId/references creates a cross-conversation reference");
      it.todo("rejects upload when file exceeds size limit");
      it.todo("rejects upload when content type is not allowed");
    });
    ```
  </action>
  <verify>
    <automated>cd /opt/nexus && npx tsc --noEmit -p packages/shared/tsconfig.json 2>&1 | tail -5 && grep -q "ChatFile" packages/shared/src/types/chat.ts && grep -q "uploadChatFileSchema" packages/shared/src/validators/chat.ts && echo "PASS"</automated>
  </verify>
  <acceptance_criteria>
    - grep "ChatFile" packages/shared/src/types/chat.ts returns a match
    - grep "ChatFileReference" packages/shared/src/types/chat.ts returns a match
    - grep "ChatFileUploadResponse" packages/shared/src/types/chat.ts returns a match
    - grep "files?" packages/shared/src/types/chat.ts returns a match (optional files on ChatMessage)
    - grep "uploadChatFileSchema" packages/shared/src/validators/chat.ts returns a match
    - grep "createFileReferenceSchema" packages/shared/src/validators/chat.ts returns a match
    - grep "it.todo" server/src/__tests__/chat-file-service.test.ts returns matches
    - grep "it.todo" server/src/__tests__/chat-file-routes.test.ts returns matches
    - TypeScript compilation of shared package succeeds
  </acceptance_criteria>
  <done>
    ChatFile and related types exported from shared. Zod validators for file upload and reference creation exported.
    ChatMessage type has optional files array. Test stubs exist for service and routes.
  </done>
</task>

</tasks>

<verification>
- `packages/db/src/schema/chat_files.ts` exists with pgTable definition
- `packages/db/src/schema/chat_file_references.ts` exists with pgTable definition
- Both tables exported from schema index
- SQL migration files exist for both tables
- `ChatFile` type exported from shared
- `uploadChatFileSchema` validator exported from shared
- Test stubs have `.todo()` tests for service and routes
- TypeScript compiles without errors in shared package
</verification>

<success_criteria>
Database schema for file tracking is defined with all columns needed for FILE-01 (directory/storage), FILE-02 (metadata), FILE-03 (cross-references), and FILE-04 (dual scoping via projectId). Shared types provide the contract for Plans 01-03.
</success_criteria>

<output>
After completion, create `.planning/phases/25-file-system/25-00-SUMMARY.md`
</output>
