-- cohorts + cohort_enrollments — replaces the old students.cohort TEXT column with a real -- many-to-many-capable model (a student can move between cohorts over time; the old model -- couldn't represent that at all). CREATE TABLE IF NOT EXISTS cohorts ( cohort_id TEXT PRIMARY KEY, -- slug, e.g. 'root-fall-2026' name TEXT NOT NULL, code TEXT UNIQUE, -- optional short join-style code, distinct from cohort_id tier TEXT, -- e.g. 'root' — free text, not an enum yet (YAGNI) start_date DATE, end_date DATE, status TEXT NOT NULL DEFAULT 'active', -- 'active' | 'archived' seat_target INTEGER, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_cohorts_status ON cohorts(status); CREATE TABLE IF NOT EXISTS cohort_enrollments ( cohort_id TEXT NOT NULL REFERENCES cohorts(cohort_id), student_id TEXT NOT NULL REFERENCES students(student_id), enrollment_status TEXT NOT NULL DEFAULT 'enrolled', -- 'enrolled' | 'removed' enrolled_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (cohort_id, student_id) ); CREATE INDEX IF NOT EXISTS idx_enrollments_student ON cohort_enrollments(student_id);