## Writing Suppa entity code (`*.entity.ts`, `*.seed.ts`)

**Read this section before you edit any schema file in this repo.** The entity
class **is** the migration: on every application start the platform converges
every tenant's real tables to these classes. There is no separate migration file
to review, so a mistake here is a mistake applied to production data.

These are **house rules of the platform**. They override the platform's own
migrations doc, which contradicts two of them, and they override the style of the
entities already in this repo — an existing file that breaks them is the habit
these rules exist to stop, not a convention to match.

1. **Type every relation property to match its decorator.** `@ManyToOne` holds
   one row → `owner: Target;` (`owner?: Target` when the relation is nullable);
   `@ManyToMany` / `@OneToMany` / `@ManyToManyBackRef` hold many →
   `peers: Target[];`. Never `any`, and never a shape that names no class.
2. **Enum member keys are PascalCase** — `NotFilled = 'Not filled'`. The key is
   the identifier your code reads; the string on the right is what the platform
   stores, and the two need not match.
3. **Never write a field option at its default.** `canGroup`, `canSort`,
   `showInTable`, `editFromTable` and `canFilter` are already `true`; `readOnly`
   and `searchable` are already `false`. Declare one **only** where the field
   departs from that — usually just `searchable: true`. A field with all seven
   spelled out is wrong even though it works: it buries the one option that
   actually differs.
4. **`@Enum` / `@MultiEnum` take a global name only when the enum is global** —
   shared by another entity, or referenced from a seed record. Otherwise omit the
   first argument: `@Enum(StatusEnum, StatusTitles, { … })`, and the decorator
   derives `<ClassName>.<propertyName>`. A field's own `key: 'Entity.field'` is
   the rename key, not a reference, and does not make the enum global.
5. **A relation target that already exists on the tenant gets a stub**, and a
   stub is three things at once:

   ```ts
   // accounts.stub.entity.ts — one stub, one file
   import { Entity, SystemBaseEntity } from '@suppa/sdk';

   @Entity({ name: 'Accounts', key: 'Accounts' })
   export class Accounts extends SystemBaseEntity {}
   ```

   - `@Entity({ name, key })` + `extends SystemBaseEntity`, with an **empty
     body** — a stub names a table this application does not own, and every
     field it declares is a column the migration would create or alter there;
   - alone in a file named `<kebab-name>.stub.entity.ts` (still `.entity.ts`, so
     discovery and autoload are unchanged);
   - **registered** in `EntityModule.forFeature()` with the entities you create.

   Never a bare `export class Accounts {}`: it satisfies the relation thunk at
   compile time and registers nothing, so the platform cannot resolve the
   relation. Platform system entities need no stub — import `UsersEntity`,
   `FilesEntity`, `IconsEntity` from `@suppa/sdk`.

Two more that cost a table if you get them wrong: an entity class that never
reaches `EntityModule.forFeature()` **does not exist** to the platform (write the
class and register it in the same step), and `default` is a **SQL expression
string** — `'false'`, `"'draft'"` — never a JavaScript value.

### The gate is not optional

```
npm run schema:gate          # or: node <path-to>/entity-code-guard.mjs --ci .
```

Run it before you report any schema file done. `passed: false` means fix and
re-run — never "noted, moving on". It fails on every rule above and names
`file:line`. If you have the Suppa MCP server, `suppa_validate_entity_code(path=<app-root>)`
is the same checker as a tool call.

The full rules, the decision table for every decorator, tabular parts, seeds and
the v1 → v2 mapping are in `skills/suppa-entity-code/SKILL.md` inside the
installed `suppa-mcp-2` package. Read it when a case is not covered above —
guessing an option that does not exist is the other way this breaks.
