Marking personal fields
On this page
A field that identifies or describes a person says so in its block, with
personal: and one category from a fixed list. The category decides how
carefully the compiler treats the value. Here a user's display name
identifies them, and their email address is contact data without saying so
(Contact details and names need no mark).
domains/accounts/entities/user.jadpo:
type DisplayName = Text { max_length: 80}entity User { id: Uuid email: Email { unique: true } display_name: DisplayName { personal: PersonalData.identifying } person: true}
Email and PersonalData are built in. DisplayName and User are defined
here. person: true says each User record is a person, so erasure can find
the marked field (The person).
Every marked field needs a category: there is no bare personal: true, and
you can't add categories of your own. These are the fourteen:
PersonalData. |
For | Sensitive |
|---|---|---|
identifying |
Names, usernames, account numbers | |
contact |
Email, phone, postal address | |
financial |
Bank and card details, income, purchases | |
online |
IP address, device and cookie identifiers, browsing | |
demographic |
Date of birth, gender, nationality | |
government_id |
Passport, national insurance, driving licence | yes |
location |
Precise location | yes |
health |
Physical or mental health, including disability | yes |
biometric |
Fingerprints, face data used to identify | yes |
genetic |
Genetic data | yes |
beliefs |
Religion, political opinions, trade union membership | yes |
ethnicity |
Racial or ethnic origin | yes |
sex_life |
Sex life or sexual orientation | yes |
criminal |
Criminal convictions and offences | yes |
"Sensitive" covers GDPR's special categories (Articles 9 and 10) and the extra ones CCPA/CPRA lists: government IDs and precise location.
personal: goes on a field of an entity, an event or an email, or of an
object, input or output a package declares, wherever Jadpo keeps or sends the
value. A provider package marks
what its API takes and gives, so an app sees what it is sending and
receiving without reading the provider's documentation. Here the package
says a customer's phone number is contact data, whatever the app fills it
with:
// packages/stripe/v2024_06_20/types/customer_request.jadpo/// A customer to create.public type CustomerRequest = Input { phone: Text { personal: PersonalData.contact }}
The category is checked the same way everywhere (type.setting_value). An
app's own object, input or output carries no mark
(type.unknown_setting): it holds what a marked field gave it, and
Jadpo follows the value back to that field.
Contact details and names need no mark
These standard types are personal data without a personal:, on a field of the
type or of one built on it, and so is a value of one wherever it goes:
| Type | Counts as |
|---|---|
Email, EmailWithName |
PersonalData.contact |
PhoneNumber |
PersonalData.contact |
PostalAddress |
PersonalData.contact |
PersonName, GivenName, FamilyName |
PersonalData.identifying |
- It is hashed in logs, and the report lists each connection it reaches, the address an email is sent to among them. A connection that can't erase it needs an acceptance.
- A model is sent one only where
ai: { personal_data: { allowed: } }lists its category (Personal data sent to a model), a public cache can't keep one, and an audited entity can't hold one yet. - Erasure clears it as it clears a marked field, so on a record erasure keeps
it is optional, or says
erase: Erasure.retainedon a person's own record (Erasure). - The first record that lets anyone read one, with no sign-in, says so to a person.
Here an order is kept when its buyer is erased, and what it was sent to goes:
entity User { id: Uuid email: Email person: true}entity Order { id: Uuid buyer_id: User.id? { on_delete: OnDelete.cleared } recipient: PersonName? ship_to: PostalAddress? delivery_phone: PhoneNumber?}
Written delivery_phone: PhoneNumber, it couldn't be cleared
(data.erase_field_required):
entity User { id: Uuid email: Email person: true}entity Order { id: Uuid buyer_id: User.id? { on_delete: OnDelete.cleared } delivery_phone: PhoneNumber}
error Erasing a User can't clear Order.delivery_phone
--> main.jadpo:13:5
|10 | buyer_id: User.id? { | -------- keeps the record without the person13 | delivery_phone: PhoneNumber | ^^^^^^^^^^^^^^ personal data that can't be `none`
- reason
buyer_idisOnDelete.cleared, which meansErasure.anonymised: erasing aUserkeeps each order that refers to them, clearing the reference and every marked field.delivery_phoneis a phone number, which counts asPersonalData.contact, and only an optional field (PhoneNumber?) can be cleared.- next
- Make
delivery_phonePhoneNumber?, or sayerase: Erasure.deletedonbuyer_idto delete the order instead (human) - rule
- data.
erase_ field_ required - help
- https://jadpo.
dev/docs/errors/data. erase_ field_ required
The other standard types that are personal data, such as a PostalCode or a
Username, are hashed, listed and checked the same way, and erasure clears
them only when marked.
A business's own address, phone number or manager's name is one of these too,
and there is no mark that says one isn't personal data. Nothing here stops one
being kept: an entity erasing a person never reaches may hold it, as a shop may
hold its own phone number
(What the compiler checks). It is listed as any
other is, a person reads the notice and goes on, and a connection it reaches
takes a one-line acceptance. On a record erasure keeps it is cleared like a
person's, so a shop's number belongs on the shop, not copied onto each order. A
category written on one, personal: PersonalData.contact or any other, is read
as on any field.
Marked fields in logs
A marked field is written to logs as a keyed hash, whatever its type, and so
is an Email or another standard type that is personal data with no mark. So a
marked date_of_birth: CalendarDate is hashed, though
logs otherwise show dates and numbers as
values, and so is a marked field in a failure's context. The marking, not
the type, decides.
The hash covers the UTF-8 bytes of the value's canonical text, as
text.from gives it: 2026-10-25 for a date, GBP 10.50 for an amount.
A type with no single text form, such as a PostalAddress, is hashed from
its JSON as json.write writes it. So one value hashes the same in every
log and on both executors, because every boundary goes through
the standard types' contract.
Sensitive fields
A sensitive field is never put in a cache, and every read of one is recorded in the access records.
Records a person owns
You don't mark every field of a record a person owns. A todo's title is the user's data because the todo belongs to them (see The person), so it is exported and erased with them. Mark only the fields whose value points at, or describes, a person.
Markdown for agents: /docs/marking-personal-fields.md.Built from Jadpo main at aee2e8f.