-- Reminders & follow-ups — a real tickler/task model tied to the fundraising grid. -- -- ADDITIVE + REVERSIBLE (CLAUDE.md guardrail #3): one new table + indexes; nothing -- existing is touched. Until now the only follow-up surfaces were the grid's binary -- `follow_up` checkbox (no date, owner, or status) and communications.next_action_date -- (tied to a single logged comm). This gives investors first-class reminders with a due -- date, status lifecycle, assignee, and provenance — the foundation for "who needs a -- follow-up?" queries, the daily digest's due/overdue section, and (later) bot-proposed -- reminders behind the Matrix approval gate. -- -- investor_id is a LOGICAL foreign key to fundraising_investors(id) — deliberately NOT a -- declared SQLite FOREIGN KEY, matching opportunities.fundraising_investor_id (migration -- 0005). fundraising_investors rows are upserted by source_row_id on every grid save with -- a STABLE id (so the link survives saves), but a row dropped from the grid is DELETEd — -- there is nothing to cascade, and reconcile_grid_reminders() cancels the orphans on the -- next save (the pipeline reconciler's twin). investor_name is denormalized so a reminder -- stays readable in history even after its grid row is gone. investor_id is nullable: a -- reminder can be a standalone team task not tied to any investor. -- -- contact_id is an optional logical FK to contacts(id) (the specific person). assignee_id -- is a logical ref to users(id) (NULL = team-wide). created_by holds a users.id OR a -- non-user sentinel ('bot'/'automation'), so it is plain TEXT with no FK. CREATE TABLE IF NOT EXISTS reminders ( id TEXT PRIMARY KEY, investor_id TEXT, -- logical FK -> fundraising_investors.id (NULL = standalone task) investor_name TEXT, -- denormalized; survives grid-row deletion contact_id TEXT, -- optional logical FK -> contacts.id title TEXT NOT NULL, details TEXT, due_date TEXT, -- ISO date 'YYYY-MM-DD' (or datetime) status TEXT NOT NULL DEFAULT 'open', -- open | done | snoozed | cancelled snoozed_until TEXT, assignee_id TEXT, -- logical ref -> users.id; NULL = team-wide created_by TEXT, -- users.id, or 'bot' / 'automation' source TEXT NOT NULL DEFAULT 'human', -- human | bot | automation completed_at TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')), deleted_at TEXT ); CREATE INDEX IF NOT EXISTS idx_reminders_investor ON reminders(investor_id) WHERE deleted_at IS NULL; CREATE INDEX IF NOT EXISTS idx_reminders_status ON reminders(status) WHERE deleted_at IS NULL; CREATE INDEX IF NOT EXISTS idx_reminders_due ON reminders(due_date) WHERE deleted_at IS NULL; CREATE INDEX IF NOT EXISTS idx_reminders_assignee ON reminders(assignee_id) WHERE deleted_at IS NULL;