nexus/.planning/phases/25-file-system/25-00-PLAN.md
Nexus Dev d72c065fc7 docs(25-file-system): create phase plan — 4 plans in 3 waves
Plans cover FILE-01 through FILE-06: DB schema + shared types (wave 1),
server file service + routes and UI file upload (wave 2, parallel),
file preview components + full wiring (wave 3).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:55:48 +00:00

311 lines
13 KiB
Markdown

---
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>