#Privacy deletion (crypto-shredding + anonymization)
#Purpose
Honor data-subject erasure requests (e.g. PDPO, GDPR) without ever enabling DELETE: sensitive personal fields are crypto-shredded, other personal fields are anonymized, and records are marked erased and soft-deleted (Decision 42, §12.10). The erasure flow is executed by the platform, not by locks; your job in source is to classify personal fields correctly.
#Natural-language source example
models/customer/customer.md
# customer
Customer profiles, stored in the customers table of provider main.
Fields:
- id: uuid, primary key, auto-generated.
- user_id: uuid; identifies the data subject for privacy erasure.
- email: valid email, unique; standard personal data.
- name: at most 100 characters; standard personal data.
- phone: at most 20 characters; sensitive personal data.
- address: at most 300 characters; sensitive personal data.
- date_of_birth: timestamp; sensitive personal data.
- tier: string.
Provide get, list and update CRUD. list defaults to 20 per page, at most 100, sort by created_at.
Erasure is triggered outside source, by a tenant admin:
subject erase --subject <id> --reason "..."
#Compiled lock md example
models/customer/customer.lock.md (sections only; kind: model)
## Table
- provider: main
- table: customers
## Fields
| name | type | rules | system |
|---|---|---|---|
| id | uuid | primary, default: gen | yes |
| user_id | uuid | subject-key | no |
| email | string | format: email, unique, personal: standard | no |
| name | string | max: 100, personal: standard | no |
| phone | string | max: 20, sensitive, personal: sensitive | no |
| address | string | max: 300, sensitive, personal: sensitive | no |
| date_of_birth | timestamp | sensitive, personal: sensitive | no |
| tier | string | | no |
| created_at | timestamp | default: now | yes |
| updated_at | timestamp | default: now | yes |
| deleted_at | timestamp | nullable | yes |
| erased_at | timestamp | nullable | yes |
| tenant_id | string | platform | yes |
## CRUD
| method | options |
|---|---|
| get | sensitive-output: [] |
| list | pagination: offset, page-size: 20, max-page-size: 100, sort: [created_at], filter: [] |
| update | partial: true, optimistic-lock: if-match |
## Policies
- rls: enable, force
- policy: tenant_id = current_setting('app.tenant_id', false)
- grants: app_rw [SELECT, INSERT, UPDATE], public_reader [SELECT]
- subject-key: user_id
personal: standard | sensitive in the field rules follows the v1.7 user and email-outbox locks (§12.8, §12.11.2). subject-key follows the grammar of Decision 65 (§12.10): subject-key: {column} or subject-key: [{column}, ...] (any match counts), where {column} is a uuid column pointing to users.id (the users table uses its own id) or hash:{column}, a bytea column holding HMAC-SHA-256(pepper, normalize(email)) for rows without a user_id (e.g. email_outbox.recipient_hash). A model with personal fields but no subject-key is a validator error. erased_at is a platform system field in v1.7 (F5).
#Security considerations
- Encryption (
personal: sensitive): every sensitive field of every record has its own DEK (AES-256-GCM); DEKs are wrapped by the tenant KEK in platform KMS and kept in the platform key store, separate from business data and out of DB backups. Encryption/decryption is automatic; locks never see keys (§12.10). - Trigger: tenant admin via platform UI or CLI
subject erase; reason and request reference are mandatory; one admin submits, another confirms (single-admin tenants: 24h delay) (§12.10). - Flow (platform-executed): (1) find all subject records via the
subject-keyfield; (2) destroy DEKs of sensitive fields (crypto-shredding); (3) overwrite standard fields with irreversible placeholders (e.g.erased-<random>, random unique values for unique fields) using a platform erasure role — UPDATE only, no DELETE; (4) seterased_atand soft-delete, keeping rows and relations; (5) revoke all of the subject's refresh-token families and API keys (§12.10). - Deadlines: completed within 30 days of the request; platform target 24h after trigger.
- Audit: trigger, confirmer, reason, reference, affected tables/columns, record count, completion time — with pseudonymized subject id only (§12.10).
- Backups: shredded fields are unreadable in backups; standard fields disappear when the 35-day backup retention expires; restores replay the erasure ledger before reopening (§12.10).
- Email outbox (Decision 68):
email_outboxrows carryrecipient_hashso even rows withoutuser_id("already have an account" notices) are found and anonymized on erasure; 30 days aftersent_atthe mail worker masks the email and nulls the token anyway (§12.11.3). - Audit never stores plaintext email (HMAC hash or mask only) (Decision 68).
- Logs: personal data must never be logged; logs keep pseudonymized ids only, retained 30 days (§12.10).
- No rewrites: locks may not write personal fields again on erased records (§12.10).
- Mark sensitive fields also
sensitive: trueto keep them out oflistoutput, filter and sort (§12.9). DELETEremains forbidden everywhere (§2A.4).- The spec's §13 asks for legal review of the PDPO/GDPR aspects — this page is not legal advice.
#Spec gaps
- Whether
sensitive: true(§12.9, hides fields from list/filter/sort) andpersonal: sensitive(§12.10, crypto-shredding) are the same marker or independent is ambiguous; this page sets both. - The
subject-keyplacement differs between examples: in the migration## Datasection (§12.11.1), in the model## Policies(§12.11.2), and as a field rule (recipient_hashrow). Which one is authoritative is not stated. - How erased records interact with
uniqueconstraints for later re-use of the same email is not specified. - §14 now lists only a data-subject self-service submission entry as future work (Decision 50); the tenant-admin
subject eraseCLI flow in §12.10 is the defined path.