Skip to main content

Database

Hitler uses PostgreSQL with Drizzle ORM for type-safe database access.

Schema Overview

Entity Relationships

Foreign Key Constraints

All FK constraints are designed for safe deletion without leaving orphaned data.

CASCADE Delete (child deleted when parent deleted)

SET NULL on Delete (reference nullified, record kept)

Why This Matters

Without proper ON DELETE actions:
  • You can’t delete task drafts that have been confirmed (FK violation)
  • You can’t delete users who have sent inquiry messages
  • You can’t clean up old data without manual cascading

Automatic Data Cleanup

Scheduled jobs automatically clean up old data to prevent database bloat.

Task Drafts Cleanup (Daily at 2 AM)

Why keep confirmed drafts for 30 days?
  • Audit trail: see what user said vs what LLM parsed
  • Debugging: investigate if task was created incorrectly
  • After 30 days, the task itself is the source of truth

Channel Messages Cleanup (Daily at 2 AM)

Why 7 days?
  • Raw messages are processed into context_observations within minutes
  • Observations contain the extracted intelligence; raw text is no longer needed
  • Keeps storage costs manageable for high-volume channels

Security Audit Logs Cleanup (Monthly)

Why 90 days?
  • Sufficient for security investigations
  • Compliance with typical log retention policies
  • Prevents unlimited growth

pgvector Extension

The database uses the pgvector extension for vector similarity search. This powers the context memory system’s semantic search capabilities.

Setup

The Docker Postgres image is pgvector/pgvector:pg16 (not the standard postgres:16-alpine). The migration creates the extension:

Vector Columns

Two tables use vector(1536) columns for OpenAI text-embedding-3-small embeddings:
  • context_observations.embedding — per-observation semantic vector
  • context_summaries.embedding — per-entity-summary semantic vector

HNSW Indexes

HNSW (Hierarchical Navigable Small World) indexes enable fast approximate nearest neighbor search:
Queries use the cosine distance operator (<=>):
Lower distance = more similar. A threshold of 0.3 filters out irrelevant results.

Migrations

Drizzle generates migrations automatically when schema changes.

Migration Files

Indexes

All tables have performance indexes defined in their schema files.

Key Indexes

tasks:
  • user_id - Filter by user
  • organization_id - Filter by org
  • status - Filter by status
  • (user_id, status) - Composite for user’s pending tasks
  • due_date - Sort/filter by due date
  • created_at - Sort by creation
  • client_id - Filter by client
  • stage_id - Filter by pipeline stage
  • (organization_id, display_id) - Unique org-scoped display IDs
pipeline_stages:
  • organization_id - Filter by org
  • (organization_id, position) - Ordered stage listing
  • Unique (organization_id, slug) - Prevent duplicate slugs per org
task_drafts:
  • user_id - Filter by user
  • organization_id - Filter by org
  • expires_at - Cleanup job efficiency
mood_entries:
  • user_id - Filter by user
  • recorded_at - Time-based queries
  • (user_id, recorded_at) - User’s mood history
flags:
  • user_id, organization_id - Basic filtering
  • severity, status - Dashboard queries
  • (user_id, status) - User’s active flags
security_audit_logs:
  • organization_id - Org dashboard
  • threat_level - Severity filtering
  • created_at - Time-based cleanup
  • (organization_id, threat_level, created_at) - Dashboard queries

Best Practices

Adding New Tables

  1. Define schema in packages/db/src/schema/
  2. Export from packages/db/src/schema/index.ts
  3. Add appropriate FK constraints with ON DELETE actions
  4. Add indexes for common query patterns
  5. Generate and apply migration

FK Constraint Guidelines

When to Use Each

Manual Cleanup

If you need to manually clean up data:

Troubleshooting

”Cannot delete: FK constraint violation”

This means a child table references the record. Check:
  1. Is the FK set up with proper ON DELETE action?
  2. If not, delete children first, then parent
  3. Or update the schema and run migration

”Relation does not exist”

Run migrations:

Slow queries

Check if indexes exist for your query patterns:
If no index is used, add one to the schema.