/** * Stable public identity for a large-course module. * * The persisted classroom id is an operations-side source identifier. It must * never double as the learner-facing courseware id, otherwise a manifest leaks * a direct route to the mutable source classroom and bypasses its version/hash * pin. Keeping this derivation deterministic also means a course re-publish * creates the next immutable version of the same public module. */ export function courseModuleCoursewareId(courseId: string, moduleIndex: number): string { if (!/^[a-zA-Z0-9_-]+$/.test(courseId)) { throw new Error(`Invalid course id: ${courseId}`); } if (!Number.isInteger(moduleIndex) || moduleIndex < 1) { throw new Error(`Invalid module index: ${moduleIndex}`); } return `course_${courseId}_module_${moduleIndex}`; }