-- Phase 5B §2 — programs: first-class learning products, distinct from curriculum JSON itself -- (a program is the commercial/academic unit a student enrolls in; curriculum_path is still -- just the content it points at, unchanged since Phase 1). program_id IS the slug — same -- convention as assessment_definitions/cohorts (a single TEXT primary key, no separate numeric -- id + slug pair). -- -- family is the init_/compile_/root_ youth-engineering ladder (see -- docs/PHASE5B_SELF_PACED_ENROLLMENT.md §1) — only 'root' has real curriculum content today; -- init/compile rows can be added later without a schema change, they just won't have a working -- curriculum_path yet. delivery_mode describes how the program is normally taken; an -- individual enrollment can still snapshot a different mode (see 020_enrollments.sql). -- -- version is a plain incrementing integer, matching assessment_definitions' existing -- versioning convention — enrollments snapshot the version+curriculum_path they were created -- against so a later edit to a program never silently changes what an already-enrolled -- student is being held to. CREATE TABLE IF NOT EXISTS programs ( program_id TEXT PRIMARY KEY, -- slug, e.g. 'root-kubernetes-foundations' name TEXT NOT NULL, description TEXT, family TEXT NOT NULL CHECK (family IN ('init', 'compile', 'root')), delivery_mode TEXT NOT NULL DEFAULT 'self_paced' CHECK (delivery_mode IN ('self_paced', 'cohort', 'hybrid')), curriculum_path TEXT NOT NULL DEFAULT 'rootnode-path', completion_assessment_id TEXT REFERENCES assessment_definitions(assessment_definition_id), estimated_duration_hours INTEGER, version INTEGER NOT NULL DEFAULT 1, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive')), created_by UUID REFERENCES instructors(instructor_id), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_programs_status ON programs(status); CREATE INDEX IF NOT EXISTS idx_programs_family ON programs(family); -- Seed: the one legitimate self-paced program this phase ships, wrapping the EXISTING -- RootNode/Kubernetes curriculum and capstone — no new curriculum content invented. init_/ -- compile_ programs are intentionally NOT seeded (no real curriculum for them yet, per this -- phase's explicit scope line). INSERT INTO programs (program_id, name, description, family, delivery_mode, curriculum_path, completion_assessment_id, estimated_duration_hours) VALUES ( 'root-kubernetes-foundations', 'ROOT — Kubernetes Foundations', 'Self-paced Linux, containers, and Kubernetes operations — the existing RootNode curriculum, from Foundations through the RootNode Capstone.', 'root', 'self_paced', 'rootnode-path', 'rootnode-capstone', 40 ) ON CONFLICT (program_id) DO NOTHING;