E0: Foundation — Epic Design Doc
Status: 🔄 In Refinement (Step 0) Authors: Dan Hannah & Clay Created: 2026-04-18 Parent: QuoteAI Project Design Doc
Overview
Goals & Non-Goals
Goals:
- Stand up a working local dev environment in Docker: Postgres 16 + pgvector + Next.js 14 shell
- Create the full database schema from the project design doc's data model
- Scaffold the repo layout so E1, E2, and E3 have clear homes for their code
- Establish env-var conventions, secrets handling, and local setup docs
- Provide a
make dev(or equivalent) one-command bring-up
Non-Goals:
- No ingestion logic (E1)
- No MCP servers (E2)
- No UI features beyond a shell page that proves the stack is wired (E3)
- No production deploy, hosting config, or CI
- No auth — explicitly deferred past Demo MVP
Problem Statement
We have a design doc and a revised MVP plan, but zero code. Every other epic is blocked until the local stack runs. Without a foundation, E1 can't target a schema, E2 can't talk to a database, and E3 can't render anything.
The goal is to hit "I can run docker compose up, open localhost, and see a working page backed by an empty but schema-complete Postgres+pgvector" in as few hours as possible — then stop. Nothing else.
What Is This Epic?
Foundation is the scaffolding epic. It creates the repo skeleton, the Docker environment, the database schema, and a minimal Next.js app that proves the connection layer works end-to-end. When E0 is done, every other epic has a concrete target to build into.
Context
Dependents
Every other MVP epic depends on E0:
- E1 (Ingestion + Vector DB) — needs the schema to exist before it can load data
- E2 (MCP Servers) — needs the DB connection layer and env conventions
- E3 (UI) — needs the Next.js shell to build pages into
Dependencies
- Node.js 20+ installed locally
- Docker Desktop running
- A directory to put the repo (likely
~/Documents/Code/quoteai)
No external APIs, no cloud accounts. Fully local.
Current State
No code. The design doc lives in Foundry at projects/quoteai/design.md. The Brehob source data lives in a shared Google Drive folder (QuoteAI - Brehob Quote History) — relevant to E1, not E0.
Affected Systems
| System / Layer | How It's Affected |
|---|---|
| Local dev environment | Created from scratch — Docker Compose, env files, Makefile |
| Repository structure | Initialized — monorepo pattern with app/, ingestion/, mcp-servers/, db/ |
| Database schema | Fully created from the project design doc's SQL |
| Next.js app | Shell only — routing structure, layout, dark-mode theme primitive, a /health page that queries the DB |
Design
Repo Structure
quoteai/
├── app/ # Next.js 14 (App Router)
│ ├── app/
│ │ ├── layout.tsx # root layout, dark-mode-first
│ │ ├── page.tsx # placeholder home
│ │ └── health/
│ │ └── page.tsx # "✓ DB connected" smoke test
│ ├── lib/
│ │ └── db.ts # Postgres client (pg or @neondatabase/serverless)
│ ├── package.json
│ └── tsconfig.json
├── db/
│ ├── schema.sql # full schema from design doc
│ ├── migrations/ # numbered SQL files, applied on container boot
│ │ └── 001_init.sql
│ └── seed/ # empty for E0; populated by E1
├── ingestion/ # E1 lives here (empty stub for E0)
│ └── .gitkeep
├── mcp-servers/ # E2 lives here (empty stub for E0)
│ ├── equipment/.gitkeep
│ └── quotes/.gitkeep
├── docker-compose.yml # postgres + pgvector + next dev container (optional)
├── .env.example # documented env contract
├── .env # local only, gitignored
├── Makefile # make dev, make db-reset, make test
├── README.md
└── CLAUDE.md # project-specific instructions for CC
Docker Compose Target
# docker-compose.yml — shape only, not final
services:
postgres:
image: pgvector/pgvector:pg16
environment:
POSTGRES_DB: quoteai
POSTGRES_USER: quoteai
POSTGRES_PASSWORD: quoteai
ports: ["5432:5432"]
volumes:
- pgdata:/var/lib/postgresql/data
- ./db/migrations:/docker-entrypoint-initdb.d
Next.js runs on the host (not in Docker) for fast HMR during dev. Docker only hosts Postgres for MVP.
Data Model Changes
Apply the full schema from the project design doc's Data Model section, plus three E1-driven additions folded in now (pre-E1-ingest, so no migration later).
Tables created in 001_init.sql:
products(catalog + vector)past_quotes(raw text + vector)quote_line_items(the description library — atomic retrieval units)vendor_quotes(reference)quote_log(empty for Demo MVP but schema exists)quote_log_items(empty for Demo MVP but schema exists)feedback- All
ivfflatvector indexes
E1-driven additions (per agreed decision 2026-04-18 in E1 Decisions Log). Added to products, past_quotes, and quote_line_items:
extractor_version TEXT, -- e.g., "haiku-4-5-v1" — enables A/B comparison when extraction prompts evolve
source_hash TEXT, -- hash of raw input text (pre-parse) — skip file entirely if unchanged
content_hash TEXT, -- hash of extracted content block (post-parse) — re-embed only changed blocks within a file
Why these three now: The E1 ingestion pipeline will write extractor_version per row for prompt-versioning; the re-embed detection logic needs both hash columns. Landing them in 001_init.sql is free right now (no data yet); migrating after E1 ingests would be painful.
Quote log tables are created even though they're not populated until Full MVP — saves a migration later.
Approach
git initthe repo at~/Documents/Code/quoteai- Scaffold Next.js 14 with TypeScript, Tailwind, App Router
- Add
shadcn/uiprimitives (button, form, input, card) — dark-mode-first since Dan prefers dark mode - Write
docker-compose.ymlwith pgvector/pgvector:pg16 - Write
db/migrations/001_init.sqlwith full schema - Write
app/lib/db.tswith a typed Postgres client - Add
/healthpage that runsSELECT 1and shows status - Write
README.mdandCLAUDE.md(CC operating instructions for this repo) - Write
Makefilewithdev,db-reset,testtargets
API / Interface Changes
No external APIs. Internal interface to establish:
app/lib/db.tsexports a singleton Postgres pool with typed query helper- Environment variables:
DATABASE_URL,ANTHROPIC_API_KEY(placeholder, unused until Full MVP),OPENAI_API_KEY(for embeddings — used by E1)
Edge Cases & Gotchas
| Scenario | Expected Behavior | Why It's Tricky |
|---|---|---|
pgvector extension not loading | Container fails to start with clear error | CREATE EXTENSION vector; must run before any table with VECTOR(...) columns. Ensure migration order. |
ivfflat index creation on empty tables | Succeeds but is useless | ivfflat needs data to build its lists. For E0 (empty DB), indexes are created but will rebuild automatically once E1 loads data. Fine. |
| Running on Apple Silicon vs Intel | Both should work | pgvector/pgvector:pg16 publishes multi-arch images. Verify on first boot. |
DATABASE_URL mismatch between app and docker-compose | App can't connect | .env.example documents the exact URL; README calls it out. |
Testing Strategy
QuoteAI doesn't have a full Validation Pipeline yet (that's Routr's level of rigor). For E0, testing is minimal and manual.
Test Layers
| Layer | Applies? | Notes |
|---|---|---|
| Unit tests | No | No real logic to test in E0 |
| Integration tests | Yes | One smoke test: /health page returns "DB connected" with all expected tables present |
| Visual tests | No | Deferred to E3 |
| E2E / manual | Yes | make dev → open browser → see shell page → see health page green |
Verification Checklist
make devbrings up Postgres and Next.js with zero manual steps beyond copying.env.exampleto.env/healthpage shows DB connection OK + a list of created tables matching the schemapsqlinto the container confirmspgvectorextension is installed and all tables existmake db-resetwipes and rebuilds the DB from migrations cleanly
Stories
| Story | Summary | Status | PR |
|---|---|---|---|
| S0 | Repo init + directory structure + .gitignore + CLAUDE.md | — | — |
| S1 | Next.js 14 scaffold with Tailwind + shadcn/ui + dark mode | — | — |
| S2 | Docker Compose with pgvector/pg16 + init migration | — | — |
| S3 | Full schema migration from design doc (db/migrations/001_init.sql) | — | — |
| S4 | app/lib/db.ts + /health smoke test page | — | — |
| S5 | Makefile (dev, db-reset, test) + README + .env.example | — | — |
Known Issues / Tech Debt
| Issue | Severity | Notes |
|---|---|---|
| No CI/CD | 🟢 Low | Intentional for Demo MVP. Add when production hardening begins. |
| No auth layer | 🟢 Low | Intentional — Brehob-single-tenant for Demo MVP. |
quote_log / feedback tables exist but unused | 🟢 Low | Accepted — saves a migration later. |
Open Questions
Open questions that need a human call before implementation.
| # | Question | Recommendation | Status |
|---|---|---|---|
| 1 | Package manager — pnpm vs npm vs yarn? | pnpm — monorepo-friendly via workspaces, fast, correct dependency resolution. Used extensively in the broader @claymore-dev ecosystem. | Needs confirmation |
| 2 | Monorepo tooling — pnpm workspaces only, or add Turborepo / Nx? | pnpm workspaces only for MVP. Turborepo is nice for caching but premature at 4 packages. Revisit when build time hurts. | Needs confirmation |
| 3 | Database client — raw pg, Drizzle, or Prisma? | Drizzle — type-safe, plays well with shadcn/ui crowd, lightweight, no codegen server. Raw pg fine if you want zero abstraction. Prisma feels heavy for this scope. | Needs decision |
| 4 | CLAUDE.md content — what goes in the project-specific CC instructions file? | Draft sections: (a) project overview + link to design doc, (b) repo layout reference, (c) common commands (pnpm ingest, make dev), (d) MCP server setup for CC in this repo, (e) coding conventions (e.g., Zod for all API boundaries), (f) pointers to next.md per-session handoffs. | Needs draft approval |
| 5 | Postgres credentials pattern — hardcoded quoteai/quoteai/quoteai in .env.example, or generate random on first make dev? | Recommend: hardcoded in .env.example for simplicity; .env is gitignored. For demo on localhost, not worth the ceremony. Full MVP deploy will use proper secrets. | Needs confirmation |
| 6 | Node version pinning — .nvmrc or Volta? | Recommend .nvmrc with Node 20 LTS. Simplest, works everywhere. | Needs confirmation |
| 7 | /health page scope — just DB ping, or also show MCP server reachability? | For E0: just DB ping. MCP server reachability is E2's concern. | Needs confirmation |
| 8 | Project-specific next.md — today next.md is workspace-scoped and can get crowded across projects. Should QuoteAI have its own projects/quoteai/next.md? | Recommend: yes, per-project next.md inside the project repo. Workspace next.md stays for cross-project context. This is a methodology improvement — worth a process.md PR. | Needs decision (methodology) |
Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| pgvector container behaves differently than expected on Apple Silicon | Low | Medium | Use official pgvector/pgvector:pg16 image which is multi-arch; test on Dan's laptop before locking the tag |
| shadcn/ui versioning causes install friction | Low | Low | Pin a known-good version in the initial setup |
| Schema drift between design doc and migration | Medium | Medium | Generate migration SQL directly from the design doc's Data Model section; diff against design doc on PR |
Decisions Log
| Date | Decision | Rationale | Alternatives Considered |
|---|---|---|---|
| 2026-04-18 | Next.js runs on host, not in Docker | Fast HMR during dev; one less networking complication | Full dockerized stack (rejected: slower dev loop) |
| 2026-04-18 | Single schema migration for E0 | No users yet — we can rewrite at will until E1 loads data | Incremental migrations (rejected: premature ceremony) |
| 2026-04-18 | Create quote_log / feedback tables now despite not using them | Saves a migration in Full MVP; schema is in the design doc anyway | Only create what Demo uses (rejected: creates migration debt) |
| 2026-04-18 | shadcn/ui + Tailwind | Dark-mode-first (user preference), easy to make polished quickly, composable | Chakra (rejected: heavier), plain Tailwind only (rejected: more scaffolding per component) |
| 2026-04-18 | Fold E1 schema additions (extractor_version, source_hash, content_hash) into 001_init.sql | Per E1 agreement. Added to products, past_quotes, quote_line_items in the initial migration. Cheaper to land now (no data yet) than to migrate after E1 ingests. Also resolves E1 Open Q #6 (hash strategy) by committing to both source and content hashes. | Post-E1 migration (rejected: more pain, more risk); only extractor_version (rejected: hash columns are cheap to add in the same touch) |
Once E0 is shipped, stories for E1 can begin. No other epic can start until make dev is green.