> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cronozen.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Data Model

> Core entities and relationships in the Cronozen platform

# Data Model

Cronozen manages 200+ tables across its hub-and-spoke ecosystem. This page documents the core entities that define the platform's multi-tenant architecture.

## Entity Relationship

```
                    ┌──────────────────┐
                    │  unified_actors  │
                    │  (identity)      │
                    └────────┬─────────┘
                             │ 1:N
                    ┌────────▼─────────┐
                    │ center_memberships│
                    │ (access control)  │
                    └────────┬─────────┘
                             │ N:1
              ┌──────────────▼──────────────┐
              │           centers            │
              │  (tenant / organization)     │
              └──────┬──────────────┬───────┘
                     │              │
          ┌──────────▼───┐  ┌──────▼────────┐
          │   schedules  │  │  dpu_records   │
          │   sessions   │  │  (audit trail) │
          │   invoices   │  └───────────────┘
          └──────────────┘
```

***

## Core Entities

### unified\_actors

The identity layer. Every person in the system is a `unified_actor`.

| Field          | Type     | Description                                |
| -------------- | -------- | ------------------------------------------ |
| id             | UUID     | Primary key                                |
| email          | String   | Login email (unique within center context) |
| phone          | String?  | Phone number                               |
| name           | String   | Display name                               |
| password\_hash | String   | bcrypt hash                                |
| is\_verified   | Boolean  | Email/phone verified                       |
| created\_at    | DateTime | Account creation                           |

<Info>
  One person can have multiple actors (e.g., parent at Center A and instructor at Center B). These are linked via **actor family** — `findActorFamilyIds()` groups actors by shared email or phone.
</Info>

***

### centers

The tenant entity. Each center is an isolated organization.

| Field          | Type    | Description                                   |
| -------------- | ------- | --------------------------------------------- |
| id             | Integer | Primary key                                   |
| name           | String  | Center name                                   |
| center\_domain | String  | URL slug (unique)                             |
| path\_slug     | String  | Internal routing path                         |
| tenant\_type   | Enum    | CENTER, WORKSPACE, PROGRAM, WHITE\_LABEL      |
| vertical       | String  | rehabilitation, welfare, education, etc.      |
| status         | Enum    | ACTIVE, SUSPENDED, INACTIVE                   |
| partner\_id    | UUID?   | FK to whitelabel\_agreements (if white-label) |
| settings       | JSON    | Center-specific configuration                 |

**Tenant types:**

| Type         | Purpose                   | Example                               |
| ------------ | ------------------------- | ------------------------------------- |
| CENTER       | Physical facility         | Rehabilitation center, welfare center |
| WORKSPACE    | Personal actor space      | Instructor freelancer workspace       |
| PROGRAM      | Specific program instance | Summer therapy program                |
| WHITE\_LABEL | Partner-branded instance  | slowpace.co.kr                        |

***

### center\_memberships

The access control layer. Actors access centers through memberships.

| Field          | Type      | Description                                          |
| -------------- | --------- | ---------------------------------------------------- |
| id             | UUID      | Primary key                                          |
| actor\_id      | UUID      | FK to unified\_actors                                |
| center\_id     | Integer   | FK to centers                                        |
| role           | Enum      | ADMIN, INSTRUCTOR, PARENT, CHILD                     |
| status         | Enum      | INVITED, PENDING, ACTIVE, SUSPENDED, REJECTED, ENDED |
| invited\_at    | DateTime? | When invitation was sent                             |
| ended\_at      | DateTime? | When membership ended                                |
| status\_reason | String?   | Reason for status change                             |

**Lifecycle:**

```
INVITED ──▶ PENDING ──▶ ACTIVE ──▶ ENDED
                │          │
                ▼          ▼
            REJECTED   SUSPENDED
```

**Security guarantees:**

* No center access without active membership
* No center\_id fallback — always explicit membership check
* All transitions are audited

<Warning>
  Never query center data by center\_id directly. Always verify membership status first via `requireCenterScope()` or `getCenterScopeOrError()`.
</Warning>

***

### center\_tenant\_mapping

Cross-service linking between OPS hub and spoke services.

| Field                | Type    | Description                     |
| -------------------- | ------- | ------------------------------- |
| id                   | Integer | Primary key                     |
| center\_id           | Integer | FK to centers (unique)          |
| center\_domain       | String  | Domain slug                     |
| center\_name         | String  | Display name                    |
| cms\_tenant\_id      | UUID?   | FK to CMS tenant                |
| lms\_tenant\_id      | UUID?   | FK to LMS tenant                |
| cms\_enabled         | Boolean | CMS service active              |
| lms\_enabled         | Boolean | LMS service active              |
| provisioning\_status | Enum    | COMPLETED, FAILED, PROVISIONING |
| provisioning\_ref    | String  | Idempotency key (order ID)      |

***

### whitelabel\_agreements

Partner/white-label contract and branding configuration.

| Field                    | Type      | Description                                        |
| ------------------------ | --------- | -------------------------------------------------- |
| id                       | UUID      | Primary key                                        |
| partner\_domain          | String    | Unique brand slug                                  |
| partner\_name            | String    | Partner organization name                          |
| contract\_status         | Enum      | ACTIVE, INACTIVE                                   |
| business\_model          | Enum      | PUBLIC, PRIVATE                                    |
| revenue\_share\_rate     | Decimal   | Revenue share percentage (0-100)                   |
| branding\_logo           | String?   | Logo URL                                           |
| branding\_primary\_color | String?   | Brand color hex                                    |
| branding\_favicon        | String?   | Favicon URL                                        |
| landing\_hero\_title     | String?   | Landing page headline                              |
| seo\_title               | String?   | SEO page title                                     |
| seo\_description         | String?   | SEO meta description                               |
| features                 | JSON      | Enabled features (survey, voucher, matching, etc.) |
| enabled\_domains         | String\[] | Active verticals (rehab, edu, etc.)                |
| public\_signup\_enabled  | Boolean   | Allow public registration                          |

***

### partner\_memberships

Partner organization access control.

| Field       | Type | Description                         |
| ----------- | ---- | ----------------------------------- |
| id          | UUID | Primary key                         |
| actor\_id   | UUID | FK to unified\_actors               |
| partner\_id | UUID | FK to whitelabel\_agreements        |
| role        | Enum | PARTNER\_ADMIN, PARTNER\_OPERATOR   |
| status      | Enum | ACTIVE, INVITED, PENDING, SUSPENDED |

<Info>
  Partner Admin ≠ Tenant Admin. Partner admins manage centers (create, configure, monitor). Tenant admins manage data within a specific center.
</Info>

***

## DPU Entities

### dpu\_records

Decision Proof Unit — tamper-evident audit trail.

| Field           | Type      | Description                                     |
| --------------- | --------- | ----------------------------------------------- |
| id              | UUID      | Primary key                                     |
| content         | Text      | Decision content                                |
| evidence\_level | Enum      | DRAFT(0), DOCUMENTED(1), AUDIT\_READY(2)        |
| chain\_hash     | String    | SHA-256 hash                                    |
| chain\_index    | Integer   | Position in hash chain                          |
| previous\_hash  | String?   | Hash of previous record (null for genesis)      |
| reference\_type | String    | Source context (workflow, lms-attendance, etc.) |
| reference\_id   | String    | Source record ID                                |
| created\_by     | UUID      | Actor who created the decision                  |
| center\_id      | Integer   | Tenant scope                                    |
| tags            | String\[] | Classification tags                             |
| metadata        | JSON      | Additional context                              |
| created\_at     | DateTime  | Creation timestamp                              |

**Hash computation:**

```
chainHash = SHA-256(content | previousHash | timestamp)
```

**Evidence level transitions:**

```
DRAFT ──▶ DOCUMENTED ──▶ AUDIT_READY (LOCKED)
```

Once AUDIT\_READY, the record is sealed. Any modification breaks the chain.

***

## LMS Entities (learn.cronozen.com)

### Key Models

| Model             | Description                       | Key Fields                                 |
| ----------------- | --------------------------------- | ------------------------------------------ |
| Tenant            | LMS tenant (training institution) | subdomain, centerId, features, settings    |
| Product           | Course, workshop, webinar         | productType, title, price, hrdEnabled      |
| Chapter           | Course structure unit             | productId, title, order                    |
| Content           | Lesson (차시)                       | chapterId, title, duration, videoUrl       |
| Registration      | Enrollment                        | actorId, productId, expiresAt, status      |
| Progress          | Learning progress per content     | registrationId, contentId, progressPercent |
| Assessment        | Exam/assignment                   | hrdType, hrdWeight, timeLimitMinutes       |
| AttendanceSession | Workshop event instance           | date, location, capacity, qrTokenTTL       |
| AttendanceRecord  | Individual check-in               | sessionId, actorId, dpuChainHash, gps      |

### Tenant Configuration (JSON)

```json theme={null}
{
  "hrdEnabled": true,
  "hrdPassingScore": 60,
  "hrdMinProgressPercent": 50,
  "hrdMaxDailyLessons": 8,
  "emonEnabled": true,
  "emonApiUrl": "https://emon.hrd.go.kr/api/v1",
  "emonInstitionId": "INST-001"
}
```

***

## Data Isolation Pattern

### Scoped Prisma

All center-specific queries go through scoped Prisma, which automatically filters by `center_id`:

```typescript theme={null}
// Scoped: only returns data for the current center
const schedules = await scopedPrisma.schedule.findMany();

// Base: cross-center query (admin/audit only)
const allCenters = await basePrisma.centers.findMany({
  where: { partner_id: partnerId }
});
```

### Row-Level Isolation

Every data table includes `center_id` as a foreign key. The scoped Prisma middleware injects `WHERE center_id = ?` on every query automatically.

| Layer       | Mechanism                | Scope                 |
| ----------- | ------------------------ | --------------------- |
| Application | Scoped Prisma middleware | Automatic per-request |
| API         | `requireCenterScope()`   | Endpoint-level guard  |
| Database    | center\_id FK constraint | Schema-level          |

<Warning>
  `basePrisma` bypasses tenant isolation. It must only be used for:

  * Admin cross-center reporting
  * Actor family lookups
  * Partner platform aggregation
  * System-level operations (cron, migration)
</Warning>
