Add web UI, sender profiles, purge rules, and infosec action

Extends the pipeline with infosec classification, sender profile tracking,
and configurable purge rules. Adds a web dashboard for managing rules and
monitoring email processing. Includes new migrations and seed script.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lorentz 2026-04-02 14:12:56 -04:00
parent 3bfda9e585
commit ecc6681432
48 changed files with 4394 additions and 59 deletions

74
SCHEMA.md Normal file
View file

@ -0,0 +1,74 @@
# Howl Database Schema — Seed Data Guide
This file describes what data to insert and how. The daemon populates `email_log` automatically — only the three sections below need to be seeded.
---
## `customers` + `customer_emails`
Insert the `customers` row first, then insert one or more `customer_emails` rows using the returned UUID.
### `customers`
| Column | Type | Required | Notes |
|--------|------|----------|-------|
| `name` | text | yes | Full name |
| `company` | text | no | Company name |
| `phone` | text | no | |
| `notes` | text | no | Free-form notes |
| `is_active` | bool | no | Defaults to `true` |
### `customer_emails`
| Column | Type | Required | Notes |
|--------|------|----------|-------|
| `customer_id` | UUID | yes | FK → `customers.id` |
| `email_address` | text | yes | Must be globally unique across this table |
| `label` | text | no | e.g. `"work"`, `"billing"` |
| `is_primary` | bool | no | Defaults to `false` — set `true` on the main address |
---
## `vendors` + `vendor_emails`
Same insert pattern as customers — parent row first, then email rows.
### `vendors`
| Column | Type | Required | Notes |
|--------|------|----------|-------|
| `name` | text | yes | |
| `company` | text | no | |
| `service_category` | text | no | e.g. `"logistics"`, `"IT"`, `"legal"` |
| `phone` | text | no | |
| `notes` | text | no | |
| `is_active` | bool | no | Defaults to `true` |
### `vendor_emails`
| Column | Type | Required | Notes |
|--------|------|----------|-------|
| `vendor_id` | UUID | yes | FK → `vendors.id` |
| `email_address` | text | yes | Must be globally unique across this table |
| `label` | text | no | |
| `is_primary` | bool | no | Defaults to `false` |
---
## `whitelist`
Standalone rows — no FK dependencies. At least one of `email_address` or `domain` must be set per row.
| Column | Type | Required | Notes |
|--------|------|----------|-------|
| `email_address` | text | one of these | Exact address match e.g. `"alice@example.com"` |
| `domain` | text | one of these | Entire domain match e.g. `"partnerco.com"` |
| `description` | text | no | Why this entry is whitelisted |
| `added_by` | text | no | Who added it |
| `expires_at` | timestamptz | no | Leave null for permanent entries |
| `is_active` | bool | no | Defaults to `true` |
---
## Notes
- **`email_log`** — do not seed this table. The daemon writes to it automatically as it processes emails.
- Email addresses are unique within their respective table (`customer_emails`, `vendor_emails`). There is no DB-level constraint preventing the same address appearing in both, but it should not happen logically.
- All `id` columns are UUID and auto-generated — do not supply them unless you have a specific reason to.
- `DATABASE_URL` is in `.env` — use the `postgresql+asyncpg://` form for async code, or `postgresql+psycopg://` for sync/migration code.