Prompt

**Issue-007에 따라 구현된 결과물에 대해 
아키텍처 및 스키마와 처리 구조를 비롯한 모든 단계의 시각적 다이어그램들을 작성해줘.**

Output

ISSUE-007 다이어그램 — Student / Course / Cohort 도메인 모델

ISSUE-007 결과물을 단계별(아키텍처 → 식별자 정합 → 데이터 모델 → 모듈 구조 → 운영 워크플로우 → 동작 시퀀스 → 라우트 합성)로 시각화한다. 모든 다이어그램은 Mermaid.


1. 레이어 아키텍처 (큰 그림)

콘텐츠(SoT)와 운영 DB가 분리되고, DB는 식별 레지스트리만 둔다. 표시 메타는 manifest에서 읽는다.

flowchart TB
  subgraph CP["콘텐츠 평면 (SoT — Git MDX)"]
    direction LR
    C1["content/<course>/course.mdx"]
    C2["content/<course>/<cohort>/cohort.mdx"]
    C3["content/<course>/<cohort>/*.mdx (lesson/assignment)"]
  end

  subgraph BUILD["빌드 (결정성)"]
    B1["build-manifest<br/>path-name(key) 검증 + cohortMeta 추출"]
    B2["lib/generated/content/index.json<br/>&lt;course&gt;/&lt;cohort&gt;.json"]
  end

  subgraph DBP["운영 DB 평면 (Prisma / Postgres)"]
    D1["Course / Cohort<br/>(식별 레지스트리 — key only)"]
    D2["Student / Enrollment / CohortLessonVisibility"]
  end

  subgraph DOMAIN["도메인 (lib/domain/enrollment.ts)"]
    M1["enrollStudents / unenroll"]
    M2["setLessonVisibility"]
    M3["resolveCohortId / findCohortByKeys"]
    M4["EnrollmentRepository + LessonRegistry"]
  end

  subgraph CONS["소비자"]
    U1["admin CLI<br/>--course/--cohort key"]
    U2["db:sync-from-content"]
    U3["라우트 /c/[course]/[cohort]/... (ISSUE-009~011)"]
  end

  CP --> B1 --> B2
  B2 -- "표시 메타(title/기간) 읽기" --> DOMAIN
  B2 -- "course/cohort key upsert" --> U2 --> D1
  D1 --- D2
  DBP --> DOMAIN
  DOMAIN --> U1
  DOMAIN --> U3
  style CP fill:#eef7ee,stroke:#4a7
  style BUILD fill:#eef7ee,stroke:#4a7
  style DBP fill:#eef2fb,stroke:#47a

2. 3중 식별자 정합 (key 한 문자열)

같은 key 문자열이 콘텐츠 폴더·DB·라우트 세 곳에서 동일하게 쓰인다. 빌드/CHECK/sync가 이 불변식을 강제한다.

flowchart LR
  subgraph GIT["Git 콘텐츠"]
    F["content/&lt;course&gt;/&lt;cohort&gt;/"]
  end
  subgraph DB["DB"]
    K["Course.key / Cohort.key<br/>@@unique([courseId, key])"]
  end
  subgraph RT["라우트"]
    R["/c/[course]/[cohort]"]
  end

  F == "동일 문자열" ==> K
  K == "동일 문자열" ==> R
  R == "동일 문자열" ==> F

  G1["build-manifest: 폴더명 path-name 검증"] -.강제.-> F
  G2["DB CHECK: key ~ '^[a-z0-9][a-z0-9-]*$'"] -.강제.-> K
  G3["db:sync-from-content: content→DB upsert"] -.정합.-> K

규칙: 소문자·숫자·하이픈만 허용. cohort key는 코스 내에서만 유일하므로 라우트는 [course]/[cohort] 두 세그먼트로 모호성 없이 해석된다.


3. 데이터 모델 (ERD)

erDiagram
  Course   ||--o{ Cohort                 : "contains (courseId FK)"
  Student  ||--o{ Enrollment             : enrolls
  Cohort   ||--o{ Enrollment             : includes
  Cohort   ||--o{ CohortLessonVisibility : controls
  Lesson_MDX ||..o{ CohortLessonVisibility : "lessonId (논리참조, FK 아님)"

  Course {
    string id PK
    string key UK "content 폴더 / [course] 라우트"
    datetime createdAt
  }
  Cohort {
    string id PK
    string courseId FK
    string key "UK(courseId, key)"
    datetime createdAt
    datetime updatedAt
  }
  Student {
    string id PK
    string email UK "lowercase+trim (DB 트리거)"
    string name
    datetime createdAt
    datetime updatedAt
  }
  Enrollment {
    string id PK
    string studentId FK
    string cohortId FK
    datetime createdAt
  }
  CohortLessonVisibility {
    string id PK
    string cohortId FK
    string lessonId "Git lsn_ 논리참조"
    boolean visible
    datetime createdAt
    datetime updatedAt
  }
  Lesson_MDX {
    string lessonId "front-matter id (lsn_), cohort마다 복제"
  }

인덱스: Enrollment @@unique([studentId, cohortId]) + @@index([cohortId]); CohortLessonVisibility @@unique([cohortId, lessonId]). 복합 unique의 leading prefix가 단독 질의를 커버하므로 courseId/studentId 단독 인덱스는 없고, cohort마다 복제되는 lessonId 단독 인덱스도 두지 않는다.


4. 도메인 모듈 구조