---
phase: dataModel
kind: level
level: relationships
---

# Aspect — RELATIONSHIPS

Connect the entities with foreign keys and cardinalities. For each relationship
between **two entities of the SAME module**, define: cardinality
(`1:1`/`1:N`/`N:1`/`N:M`), FK name (PascalCase, usually `{Target}Id`), required
(nullable?), and cascade (`restrict`/`cascade`/`set-null`/`no-action`). You
auto-deduce relationships from entity attributes, use cases and rules — never
from domain knowledge — and present the complete map for review.

## How to deduce (closed sources only)

- **Names/attributes** — `OrderLine` belongs to `Order` → N:1, FK `OrderId`; a
  "department" concept on Employee → N:1 to Department.
- **Use cases** — "the manager approves the leave request" → LeaveRequest N:1
  Employee (approver); "the order contains multiple lines" → Order 1:N OrderLine.
- **Business rules** — "cannot delete a department with employees" → `restrict`
  on Department→Employee; "when an order is cancelled, lines are soft-deleted" →
  `cascade` on Order→OrderLine; "if a ticket has no assignee, route to pool" →
  nullable assignee, `set-null`.

**NEVER from domain knowledge.** Hierarchical-tree, "manager-of", "category-of",
"type-of" reflexes are not sources: if no module UC/BR names the relationship,
defer to the upstream phase. Pick `restrict` only when a BR says deletion is
blocked; otherwise surface the gap.

## Cardinality

| Cardinality | Meaning | FK lives on |
|-------------|---------|-------------|
| `1:1` | each A ↔ exactly one B | the dependent side |
| `1:N` | each A has many B; each B one A | on B (`AId`) |
| `N:1` | each A has one B; each B many A | on A (`BId`) |
| `N:M` | each A many B and vice-versa | junction table (composite key) |

Declare the **owning side** (the one with the FK column); the inverse navigation
is auto-created. An `N:M` without payload (a pure tag cloud, e.g. Employee↔Skill)
is declared on one side; the junction table is generated automatically. If the
junction carries business meaning (e.g. `Enrollment` between Student and Course
with a grade and a completedAt), model it as a **separate entity** with two `N:1`
relationships instead of an `N:M`.

## Required & cascade

- **required** — `true` when a BR says "must exist" / "cannot be null"; else
  `false`. Self-references are usually `false` (root nodes exist).
- **cascade** — `cascade` when children can't exist without the parent
  (`Order`→`OrderLine`); `restrict` when a BR blocks deletion
  (`Department`→`Employee`); `set-null` to detach (requires nullable FK,
  `Ticket`→`Assignee`); `no-action` for self-references (SQL can't cascade a
  cycle). **Default `restrict`.** A self-referencing hierarchy (org chart,
  category tree) uses `N:1` self-ref, FK `{Role}Id` nullable, cascade
  `no-action`.

## Cross-module relationships — DESIGN RULE (load-bearing)

The MCD is **scoped to ONE module** (`entité.md` per module). The EF Core
generation pipeline does **not navigate across module boundaries** — cross-module
FKs are reconciled via `ICoreDataService`-style lookups, with no navigation
property. Therefore:

- **Same-module FK** → declared as a **relationship** (cardinality + cascade
  matter for code generation).
- **Another module of the same app, another app's module, or SmartStack Core**
  (`auth_Users`, `nav_*`, `tnt_*`, …) → model the FK as a **plain `Guid`
  attribute**, declare NO relationship.

| Target lives in… | Use |
|------------------|-----|
| Same module | declared `relationship` |
| Another module of the same app | **plain `Guid` attribute** — no relationship |
| Another app's module | **plain `Guid` attribute** — no relationship |
| SmartStack Core | **plain `Guid` attribute** — no relationship |

Naming for the plain FK attribute: `{TargetName}Id` (`ContactId`, `OwnerUserId`).
Always give it a description naming the foreign module/entity, e.g.
`ContactId (Guid) — FK cross-module vers CRM/CONTACTS.Contact`. In `entité.md`,
such a FK appears as a normal row in the attribute table (type `Guid`), NOT in
the `Relations` line.

## Quality checks (per entity)

- Every relationship targets an entity **of the same module**.
- No relationship targets another module or Core — those are plain `Guid`
  attributes.
- No duplicate relationship name within an entity.
- `required: true` is consistent with the FK being non-nullable (cannot pair
  `required: true` with `set-null`).
- Self-references use `no-action`.
- `N:M` is either payload-free or already promoted to a separate junction entity.

After relationships, Write `entité.md`.
