import type { CourseRecord } from '@/lib/course-framework/types'; import { readCourseRecord } from '@/lib/course-framework/store'; import { readFrozenLearningPackageRecord } from '@/lib/makelore-course/package'; type CoursePublicationState = Pick; /** A frozen course id is a one-shot immutable product identity. */ export class FrozenLearningCourseMutationError extends Error { constructor(public readonly courseId: string) { super(`课程 ${courseId} 已冻结,不能继续生成或重新生成;如需修改,请新建课程`); this.name = 'FrozenLearningCourseMutationError'; } } /** * Enforce the single immutable-course rule at every source mutation boundary. * * The frozen record is authoritative: Works can successfully finalize an * archive and then fail before writing the later external-publication receipt. * Legacy publication receipts remain additional evidence for courses created * before aggregate finalization was introduced. */ export async function assertLearningCourseMutable( courseId: string, knownRecord?: CoursePublicationState | null, ): Promise { const [frozen, record] = await Promise.all([ readFrozenLearningPackageRecord(courseId), knownRecord === undefined ? readCourseRecord(courseId) : Promise.resolve(knownRecord), ]); if (frozen || record?.publication || record?.externalPublication) { throw new FrozenLearningCourseMutationError(courseId); } }