-- Curriculum publication identity. Apply after taking a schema backup. -- This migration is additive: it does not rewrite `lessons` or local assets. CREATE TABLE IF NOT EXISTS `curriculum_lessons` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` CHAR(36) NOT NULL, `grade_key` VARCHAR(64) NOT NULL, `subject_key` VARCHAR(96) NOT NULL, `semester_key` VARCHAR(64) NOT NULL, `unit_key` VARCHAR(96) NOT NULL, `lesson_key` VARCHAR(128) NOT NULL, `curriculum_version` VARCHAR(64) NOT NULL, `title` VARCHAR(500) NOT NULL, `source_manifest_path` VARCHAR(500) NOT NULL, `source_status` ENUM('unverified','reviewed','approved','withdrawn') NOT NULL DEFAULT 'unverified', `reviewed_at` TIMESTAMP NULL DEFAULT NULL, `reviewed_by` BIGINT UNSIGNED NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uq_curriculum_lesson_uuid` (`uuid`), UNIQUE KEY `uq_curriculum_lesson_identity` (`grade_key`,`subject_key`,`semester_key`,`unit_key`,`lesson_key`,`curriculum_version`), KEY `idx_curriculum_lesson_manifest` (`source_manifest_path`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS `publication_bundles` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` CHAR(36) NOT NULL, `curriculum_lesson_id` BIGINT UNSIGNED NOT NULL, `bundle_version` VARCHAR(64) NOT NULL, `status` ENUM('draft','review','published','withdrawn') NOT NULL DEFAULT 'draft', `published_at` TIMESTAMP NULL DEFAULT NULL, `withdrawn_at` TIMESTAMP NULL DEFAULT NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uq_bundle_uuid` (`uuid`), UNIQUE KEY `uq_bundle_lesson_version` (`curriculum_lesson_id`,`bundle_version`), KEY `idx_bundle_publication` (`curriculum_lesson_id`,`status`,`published_at`), CONSTRAINT `fk_bundle_curriculum_lesson` FOREIGN KEY (`curriculum_lesson_id`) REFERENCES `curriculum_lessons` (`id`) ON DELETE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS `content_assets` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` CHAR(36) NOT NULL, `asset_type` ENUM('lesson_markdown','worksheet_markdown','textbook_pdf','lesson_mindmap_image','unit_mindmap_image','audio','transcript','other') NOT NULL, `storage_driver` ENUM('local','r2') NOT NULL DEFAULT 'local', `storage_key` VARCHAR(700) NOT NULL, `mime_type` VARCHAR(127) NOT NULL, `byte_size` BIGINT UNSIGNED NOT NULL, `sha256` CHAR(64) NOT NULL, `source_reference` VARCHAR(700) NULL, `rights_status` ENUM('unknown','review_required','cleared','restricted') NOT NULL DEFAULT 'unknown', `review_status` ENUM('draft','review','approved','withdrawn') NOT NULL DEFAULT 'draft', `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uq_content_asset_uuid` (`uuid`), UNIQUE KEY `uq_content_asset_integrity` (`storage_driver`,`storage_key`,`sha256`), KEY `idx_content_asset_status` (`review_status`,`asset_type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS `publication_bundle_assets` ( `publication_bundle_id` BIGINT UNSIGNED NOT NULL, `content_asset_id` BIGINT UNSIGNED NOT NULL, `role` ENUM('primary_lesson','worksheet','textbook','lesson_mindmap','unit_mindmap','audio','transcript','supporting') NOT NULL, `sort_order` SMALLINT UNSIGNED NOT NULL DEFAULT 0, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`publication_bundle_id`,`content_asset_id`,`role`), KEY `idx_bundle_assets_role` (`publication_bundle_id`,`role`,`sort_order`), CONSTRAINT `fk_bundle_asset_bundle` FOREIGN KEY (`publication_bundle_id`) REFERENCES `publication_bundles` (`id`) ON DELETE CASCADE, CONSTRAINT `fk_bundle_asset_asset` FOREIGN KEY (`content_asset_id`) REFERENCES `content_assets` (`id`) ON DELETE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Rollback: do not drop published records. Mark a bundle withdrawn and preserve -- its assets, then restore the preceding published bundle if one exists.