# personal data (Classification(Personal.X))

> Mark a field as personal data once, on the property, with a level of the core `Personal` enum. From then on the audit trail never records its value (only that it changed), and its entity can carry the restriction stamp that keeps a row for a stated purpose. Who may read the field is the ordinary `app.Classifications` mapping.

<!-- id: security-personal-data · area: security · stability: preview · html: https://osysharp.com/reference/security/personal-data/ -->

## Summary        {#summary}
A field that holds personal data says so **once**, on the property: `[Classification(Personal.Contact)] string
Email;`. `Personal` is a core enum, always in scope, with five levels — `Contact`, `Identity`, `Financial`, `Content`
and `Special` (GDPR Art. 9 data). A classification says **what** a field is; why it is kept and for how long is a
privacy category's job (the `Osysharp.Privacy` kit).

## Signature      {#signature}
```osy syntax
public enum Personal { Contact, Identity, Financial, Content, Special }   // core — no `using` needed

[Classification(Personal.Contact)] string Email;
```

## Description    {#description}
What the platform does with the declaration, with no further code:

- **The audit trail never records the value.** A change to a `Personal` field reaches `AuditEntry.Changes` as the
  redaction marker on both sides, whatever `app.Audit.Redact` lists — the trail records *that* the email changed,
  never to what. So erasing a person never has to rewrite the trail, which is append-only. Values recorded **before**
  a field was classified are rewritten once, by the app's next audit sweep, which leaves a `PersonalScrub` record in
  the trail saying how many rows it reached. The export `Values` a warehouse sink receives are not redacted by this:
  the sink is the tenant's own processor.
- **Its entity can be restricted.** Every type of a hierarchy holding a `Personal` field carries the restriction
  stamp, so `Restriction.Restrict(row, …)` can keep a row for a stated purpose (see [restricted rows and purpose reads (Restriction, [PurposeRead])](https://osysharp.com/reference/security/restriction/)). An
  entity holding no personal data cannot be restricted, and the compiler says so.
- **Who may read it** is the ordinary classification clearance: list the level in `app.Classifications` with the roles
  that may see it ([data classifications (app.Classifications)](https://osysharp.com/reference/config/classifications/)).

⚠ **A level YOUR app classifies its own field with and does not map is masked for everyone** — that is how every
classification works, fail-closed. Map the `Personal` levels your own fields use, or such a field reads as empty for
staff too (and memory derived through a secured read sees it empty as well).

### A kit's personal levels   {#kit-levels}

A kit classifies its own fields (`Osysharp.Conversations`' messages, `Osysharp.Mail`'s outbox addresses) and
**announces** the levels it uses in its `package.osy`, one line each — `personal Contact;`, `personal Content;` — so you
can see what personal data a kit brings before you read a line of it. A kit field whose level its manifest does not
announce is refused wherever the compile holds both (`PERSONAL_LEVEL_UNDECLARED`), so the announcement stays true.

**You need say nothing about a kit's levels.** A kit's personal level your app does not map is read through each row's
own `security {}` — exactly `Open` (below): a customer reads their own message, the desk the conversations it works.
It stays personal for privacy all the same. Where you do say something, it is one of two things:

- **Your mapping of the core level covers the kit's too.** `new Classification(Personal.Contact) { Roles = [Staff] }`
  narrows your own contact fields and every kit's.
- **A kit's level onto a classification of your own** — `Includes = [Osysharp.Conversations.Personal.Content]` on one of
  your classifications — so a kit's notion of "content" never has to be yours: in a clinic, a customer's messages may be
  your `Personal.Special`, read by clinicians alone. A package's level is included once; one the package does not
  announce, or a package the app does not use, is refused (`PERSONAL_LEVEL_MAPPING`).

⚠ **A role mapping that reaches a kit's field must include the role your sign-in runs as** (`app.AuthBootstrap`'s
`Role`). A kit's sign-in reads those fields as that role — Accounts reads the code it mailed through the Mail kit's
outbox — so a list that leaves it out would read them as empty and nobody could sign in. It is refused
(`CLASSIFICATION_SIGN_IN_ROLE`), naming the fields and the one word to add; the sign-in principal reads only the rows
your own grants already let it read.

### `Open` — personal for privacy, read by whoever may read the row   {#open}

Role clearance is the wrong answer for data a person reads about **themselves**: a customer reading their own message
in a chat widget holds no role, so a level mapped to the desk's roles masks their own words from them. Map such a level
`Open = true` — `new Classification(Personal.Content) { Open = true }` — and it stays personal for every PRIVACY purpose
(never recorded in the audit trail, its rows restrictable, memory following its text), while who may read it is simply
whoever the row's own `security {}` admits. `Open` widens a level, never a filter: it cannot make a row readable, and a
role-mapped level beside it stays masked. It is only for the core `Personal` levels, and it replaces `Roles` rather
than joining it — both are refused with the reason.

## Examples       {#examples}
```osy title="a customer whose email and notes are personal data" test app=security-personal-data
[Role] enum Role { Staff }
[Principal] entity User { [Unique, MaxLength(200)] string Email; }
[RoleGrant] entity RoleGrant { [Required] User Member; [Required] Role Level; }  // who holds which role

entity Customer {
  [Required, MaxLength(40)] string Number;
  [Classification(Personal.Contact), MaxLength(200)] string? Email;
  [Classification(Personal.Content), MaxLength(2000)] string? Notes;
  int Visits;
  security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}

app = new() {
  Name = "Desk",
  Classifications = [ new Classification(Personal.Contact) { Roles = [Role.Staff] } ],
};
```

```osy title="a customer reads their own message — personal for privacy, Open to whoever may read it" test app=security-personal-data-open
[Role] enum DeskRole { Agent }
[Principal] entity User { [Unique, MaxLength(200)] string Email; }
[RoleGrant] entity RoleGrant { [Required] User Member; [Required] DeskRole Role; }  // who holds which role
entity Message {
  [Classification(Personal.Content), MaxLength(2000)] string Text;
  [Classification(Personal.Contact), MaxLength(200)] string? ReplyTo;
  security { allow read, create when IsAuthenticated || IsAnonymous; }
}
app = new() {
  Name = "Chat",
  Classifications = [
    new Classification(Personal.Content) { Open = true },              // the writer reads their own words
    new Classification(Personal.Contact) { Roles = [DeskRole.Agent] }, // an address: the desk's alone
  ],
};
```

```osy title="a kit's message text is the app's own Special category, for clinicians alone" syntax
[Role] enum ClinicRole { Authenticator, Clinician, Reception }
app = new() {
  Classifications = [
    // The Conversations kit's `Personal.Content` (a patient's messages) is health data here.
    new Classification(Personal.Special) { Roles = [ClinicRole.Clinician, ClinicRole.Authenticator],
                                           Includes = [Osysharp.Conversations.Personal.Content] },
  ],
};
// Every other level the kits announce — addresses, names — is read through each row's own security.
```

## See also       {#see-also}
- [data classifications (app.Classifications)](https://osysharp.com/reference/config/classifications/) — mapping a level to the roles that may read it
- [Declared personal data — say whose a row is, and the platform answers for it](https://osysharp.com/reference/security/declared-personal-data/) — saying whose data a row is, and the platform writes its privacy holder
- [audit read access (app.Audit)](https://osysharp.com/reference/config/audit/) — the audit trail and its own redaction list
- [restricted rows and purpose reads (Restriction, [PurposeRead])](https://osysharp.com/reference/security/restriction/) — keeping a row only for a stated purpose
