# GDPR-radering — kompletthetsaudit (cibello, HEAD `c2cc387` → `28908d5`)

> **Syfte:** identifiera varje tabell som bär `user_id` eller annan persondata, klassa den enligt GDPR, hitta luckor mot nuvarande `DELETE /v1/me`, och designa ett permanent residual-test.
>
> **Status:** ✅ Implementerad i efterföljande commits. Residual-testet `apps/api/test/me.residual.test.ts` ingår permanent i `pnpm test` och failar automatiskt om nya `user_id`-liknande kolumner läggs till utan klassning.
>
> **Audit gjord mot:**
>
> - `packages/database/src/schema/*.ts`
> - `infrastructure/migrations/*.sql`
> - `apps/api/src/routes/me.ts` (nuvarande `DELETE /v1/me`)
> - `packages/database/src/analytics-gdpr.ts`

---

## 1. Metod

1. Enumrerade alla tabeller exporterade från `packages/database/src/schema/index.ts`.
2. Grep:ade `user_id`, `references(() => users.id)` och fria text/PGD-fält som kan bära PII (foton, hälsa, kvitton, betyg, kommentarer).
3. Klassade varje tabell i tre kategorier:
   - **RADERA** — direkt personlig, ingen laglig grund att behålla.
   - **ANONYMISERA** — behövs för referensintegritet/aggregat, men `user_id` och PII-fält ska skrubbas eller sättas `NULL`.
   - **BEHÅLL MED LAGLIG GRUND** — t.ex. bokföring, säkerhet, rättslig förpliktelse. Får inte raderas, men PII ska minimeras.
4. Jämförde klassningen mot vad `DELETE /v1/me` faktiskt gör idag.

---

## 2. Tabell-för-tabell-klassning

### 2.1 Användarkonto & autentisering — RADERA

| Tabell                      | Motivering                                                                                                                                              | Nuvarande `DELETE /v1/me`                                         |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `users`                     | Personliga grunduppgifter (e-post, namn). Soft-delete/anonymisering accepteras för att bevara referensintegritet, men övriga personfält måste skrubbas. | ✅ anonymiserar `email`, `displayName`, sätter `deletedAt`.       |
| `user_credentials`          | Lösenordshash. Ingen laglig grund efter radering.                                                                                                       | ❌ **saknas** — cascade på `users.id` fyrar inte vid soft-delete. |
| `admin_totp`                | TOTP-secret. Säkerhetsuppgift.                                                                                                                          | ❌ **saknas** — cascade fyrar inte.                               |
| `email_verification_tokens` | Engångstokens kopplade till användaren.                                                                                                                 | ❌ **saknas** — cascade fyrar inte.                               |
| `password_reset_tokens`     | Engångstokens kopplade till användaren.                                                                                                                 | ❌ **saknas** — cascade fyrar inte.                               |
| `refresh_tokens`            | Sessions-token-hash, IP, user-agent.                                                                                                                    | ✅ raderas explicit.                                              |

**Bedömning:** Fyra auth-tabeller är ohanterade eftersom `users` soft-deletas. Måste explicit raderas.

---

### 2.2 Hälsa, preferenser, samtycken & locale — RADERA

| Tabell                    | Motivering                                                              | Nuvarande `DELETE /v1/me`                                         |
| ------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `user_health_profiles`    | Hälsodata (längd, vikt, födelseår, kön, aktivitet). Känslig persondata. | ✅ raderas explicit.                                              |
| `user_preferences`        | Kost/mål/allergier/intoleranser. Personlig profilering.                 | ✅ raderas explicit.                                              |
| `user_consents`           | Samtyckeshistorik kopplad till person.                                  | ❌ **saknas** — cascade på `users.id` fyrar inte vid soft-delete. |
| `user_locale_preferences` | Språk, region, tidszon, valuta. Personlig preferens.                    | ❌ **saknas** — cascade fyrar inte.                               |

**Bedömning:** `user_consents` och `user_locale_preferences` är nya luckor.

---

### 2.3 Hushåll & medlemskap — BESLUT KRÄVS

| Tabell              | Motivering                                                                                                                                                                        | Nuvarande `DELETE /v1/me` |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| `household_members` | Länkar användare till hushåll. Ingen laglig grund att behålla kopplingen efter radering.                                                                                          | ❌ **saknas helt**.       |
| `households`        | Om användaren är **enda medlemmen/ägaren** innehåller hushållet personliga spår (namn, budget, aktiveringsdatum, storlek). Om flera medlemmar finns är hushållet en delad resurs. | ❌ **saknas helt**.       |
| `storage_locations` | Hushålls-specifika förvaringsplatser. Delad resurs om hushållet har flera medlemmar.                                                                                              | ❌ **saknas helt**.       |

**Bedömning:** Detta är den största strukturella luckan. Förslag (se öppna frågor nedan):

- Ta bort användarens `household_members`-rad(er).
- Om användaren är ensam ägare/medlem i ett hushåll: **radera hela hushållet och dess cascading data** (`storage_locations`, `inventory_items`, `inventory_transactions`, `shopping_lists`, `week_plans`, `meal_boxes`, `receipts`, `cooking_sessions`, etc.).
- Om andra medlemmar finns: låt hushållet leva, anonymisera användarreferenser i delade data.

**Öppen fråga till Johan:** Vill vi automatiskt radera ett enpersonshushåll vid kontoradering, eller ska användaren först tvingas överföra ägarskap? (GDPR-mässigt är automatisk radering av ett enpersonshushåll det säkraste.)

---

### 2.4 Lager, inköp, matplanering — hushållsdata (ANONYMISERA eller RADERA beroende på hushåll)

| Tabell                         | Klassning            | Motivering                                                                                                                                                                                                                             |
| ------------------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `inventory_items`              | ANONYMISERA / RADERA | Hushållsdata. Ingen personlig info i sig, men `source`, `verifiedByUser` etc. kan kopplas till användaren. Om hushållet överlever: sätt `verifiedByUser = false` där aktuell användare verifierat. Om hushållet raderas: cascade bort. |
| `inventory_transactions`       | ANONYMISERA          | `actor_user_id` är en personlig referens. Sätt `NULL`. Data behövs för hushållets saldo/historik.                                                                                                                                      |
| `inventory_item_decay_profile` | BEHÅLL / RADERA      | Teknisk koppling till `inventory_items`. Följer hushållets öde.                                                                                                                                                                        |
| `inventory_conflicts`          | ANONYMISERA          | `resolved_by_user_id` ska vara `NULL`. Övriga fält är hushållsdata.                                                                                                                                                                    |
| `shopping_lists`               | BEHÅLL               | Hushållsdata, ingen PII.                                                                                                                                                                                                               |
| `shopping_list_items`          | ANONYMISERA          | `added_by_user_id` ska vara `NULL`.                                                                                                                                                                                                    |
| `receipts`                     | RADERA / ANONYMISERA | Kvittobild (`image_url`) kan innehålla personuppgifter (butik, kortnummer, artiklar). Om hushållet överlever och kvittot är delat: radera bild-URL:en i objektlagring och anonymisera. Om hushållet raderas: cascade bort.             |
| `receipt_lines`                | RADERA / ANONYMISERA | `raw_text` är OCR-text från kvitto (potentiellt personligt). Samma logik som `receipts`.                                                                                                                                               |
| `price_observations`           | BEHÅLL               | Aggregatdata, anonymiserad prisinformation. `store_name` är inte personligt i GDPR-mening.                                                                                                                                             |
| `meal_boxes`                   | ANONYMISERA          | `reserved_for_user_id` ska vara `NULL`.                                                                                                                                                                                                |
| `meals`                        | RADERA               | Direkt personlig måltidslogg (`user_id`, `photo_url`). Raderas explicit idag.                                                                                                                                                          |
| `week_plans`                   | BEHÅLL               | Hushållsdata, ingen PII.                                                                                                                                                                                                               |
| `week_plan_entries`            | BEHÅLL               | Hushållsdata.                                                                                                                                                                                                                          |
| `cooking_sessions`             | ANONYMISERA          | `started_by_user_id` ska vara `NULL`. Sessionen är hushållshistorik.                                                                                                                                                                   |
| `cooking_assumption_profiles`  | BEHÅLL / RADERA      | Hushållsaggregat. Följer hushållets öde.                                                                                                                                                                                               |
| `waste_summaries`              | BEHÅLL               | Hushållsaggregat, ingen PII.                                                                                                                                                                                                           |

**Bedömning:** Dessa tabeller är i princip ohanterade idag. De flesta är hushållsdata och ska anonymiseras snarare än raderas, förutsatt att hushållet överlever.

---

### 2.5 Skanningar, AI-korrigeringar & träningsbank — RADERA

| Tabell             | Motivering                                                                   | Nuvarande `DELETE /v1/me`                |
| ------------------ | ---------------------------------------------------------------------------- | ---------------------------------------- |
| `scan_jobs`        | Innehåller uppladdade bilder (`s3_keys`) och användarspecifika resultat.     | ✅ raderas explicit.                     |
| `ai_corrections`   | Innehåller personliga korrigeringar och bildnyckel.                          | ✅ raderas explicit.                     |
| `ai_training_bank` | Innehåller träningsbild och korrigeringsdata. Cascadar via `ai_corrections`. | ✅ raderas via `ai_corrections` cascade. |

**Bedömning:** Godkänd i föregående fix. Ingen ytterligare åtgärd.

---

### 2.6 Recept & matminnen — RADERA eller ANONYMISERA

| Tabell             | Klassning            | Motivering                                                                                                                                                                      |
| ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `recipes`          | ANONYMISERA          | Publik/verifierad receptdata är inte personlig, men `creator_user_id` och `creator_display_name` är personliga. **Draft/private**-recept däremot är personliga och bör raderas. |
| `recipe_ratings`   | RADERA               | Användarbetyg och kommentar är personligt.                                                                                                                                      | ✅ raderas ej idag (lucka). |
| `recipe_favorites` | RADERA               | Användarens favoriter är personligt.                                                                                                                                            | ❌ **saknas** (lucka).      |
| `recipe_cooks`     | RADERA / ANONYMISERA | Logg över vilka recept användaren lagat. Personligt.                                                                                                                            | ❌ **saknas** (lucka).      |
| `food_memories`    | RADERA               | Matminne med eventuell bild (`photo_url`). Personligt.                                                                                                                          | ❌ **saknas** (lucka).      |
| `memory_items`     | RADERA               | AAMOS-minne — personligt.                                                                                                                                                       | ✅ raderas explicit.        |
| `taste_signals`    | RADERA               | Smakpreferenser kopplade till användaren.                                                                                                                                       | ✅ raderas explicit.        |
| `creator_stats`    | RADERA               | Creator-profil, följare, statistik. Personligt.                                                                                                                                 | ❌ **saknas** (lucka).      |
| `creator_follows`  | RADERA               | Följer/följare-relationer. Personliga.                                                                                                                                          | ❌ **saknas** (lucka).      |

**Bedömning:** Receptinteraktioner är i stort sett ohanterade.

---

### 2.7 Notiser & push — RADERA

| Tabell          | Motivering                                                          | Nuvarande `DELETE /v1/me`           |
| --------------- | ------------------------------------------------------------------- | ----------------------------------- |
| `notifications` | Push-/inapp-notiser till användaren.                                | ❌ **saknas** (cascade fyrar inte). |
| `push_tokens`   | Push-token för Expo. Teknisk identifierare kopplad till användaren. | ❌ **saknas** (cascade fyrar inte). |

**Bedömning:** Båda är luckor.

---

### 2.8 Betalningar & prenumerationer — BEHÅLL MED LAGLIG GRUND, MEN ANONYMISERA

| Tabell                | Klassning       | Motivering                                                                                                                                                                                                                                                                                                     |
| --------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `subscriptions`       | BEHÅLL          | Bokförings- och rättslig grund för att bevisa köp/åtkomst. `user_id` bör pseudonymiseras eller sättas `NULL` efter en viss period, men transaktionens existens måste kunna verifieras. Retention: minst 7 år (Bokföringslagen) eller tills rättslig tvist är avslutad.                                         |
| `subscription_events` | BEHÅLL          | Händelselogg för prenumeration. Samma grund. `user_id` kan anonymiseras.                                                                                                                                                                                                                                       |
| `store_transactions`  | BEHÅLL          | Råa store-transaktioner för revision. `raw_payload` kan innehålla PII (store-kvitto) — **ska skrubbas** så att endast transaktions-ID, produkt-ID och belopp återstår. `user_id` anonymiseras. Retention: 7 år.                                                                                                |
| `store_notifications` | BEHÅLL          | App/Play-servernotiser. Ingen direkt PII i sig, men kan innehålla transaktions-ID. Behålls för spårbarhet.                                                                                                                                                                                                     |
| `trials`              | RADERA / BEHÅLL | Trial-spårning. För rättvis kvot/fair-use kan vi behöva spara att denna person redan provat. Men efter kontoradering bör raden raderas eller pseudonymiseras (t.ex. hash av ursprungligt user_id för anti-fraud). **Förslag:** radera vid GDPR-radering; anti-fraud hanteras via store-transaktioner istället. | ❌ **saknas** idag. |
| `ai_usage_counters`   | RADERA          | Månatlig AI-användning. Ingen laglig grund efter kontoradering.                                                                                                                                                                                                                                                | ❌ **saknas** idag. |

**Bedömning:** Finansiella poster får inte raderas, men PII måste skalas bort. `subscriptions`, `subscription_events`, `store_transactions` behöver en anonymiseringsfunktion, inte deletion.

---

### 2.9 Analytics & audit — BEHÅLL MED LAGLIG GRUND, MEN ANONYMISERA

| Tabell                      | Klassning            | Motivering                                                                                                                                                                                                                    |
| --------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `product_analytics_events`  | RADERA               | Pseudonyma events, men `anonymous_id` kan länka till användaren. Hard-delete görs redan via `deleteUserAnalyticsEvents`.                                                                                                      | ✅ raderas explicit. |
| `analytics_daily_snapshots` | BEHÅLL               | Hushållsaggregat, ingen PII.                                                                                                                                                                                                  |
| `audit_logs`                | BEHÅLL               | Säkerhets-/rättslig grund. `actor_user_id`, `ip` och ev. PII i `metadata` ska anonymiseras (`actor_user_id` → `NULL`, `ip` → `NULL`, metadata skrubbas). Retention: minst 2 år, alternativt tills rättslig tvist är avslutad. | ❌ **saknas** idag.  |
| `domain_events`             | BEHÅLL / ANONYMISERA | Event-sourcing outbox. `user_id` och PII i `payload` ska anonymiseras för att inte bryta downstream-konsumenter.                                                                                                              | ❌ **saknas** idag.  |
| `idempotency_keys`          | RADERA               | Caching av API-svar kopplade till användaren. Ingen laglig grund.                                                                                                                                                             | ❌ **saknas** idag.  |

---

### 2.10 Globala system- och översättningstabeller — BEHÅLL (ingen PII)

| Tabell                       | Klassning | Motivering                              |
| ---------------------------- | --------- | --------------------------------------- |
| `canonical_ingredients`      | BEHÅLL    | Global produktdata.                     |
| `ingredient_translations`    | BEHÅLL    | Översättningar av globala ingredienser. |
| `unit_translations`          | BEHÅLL    | Översättningar av enheter.              |
| `recipe_translations`        | BEHÅLL    | Översättningar av recept.               |
| `recipe_step_translations`   | BEHÅLL    | Översättningar av receptsteg.           |
| `recipe_source_registry`     | BEHÅLL    | Juridisk källregister.                  |
| `recipe_ingredients`         | BEHÅLL    | Receptdata.                             |
| `recipe_steps`               | BEHÅLL    | Receptdata.                             |
| `recipe_similarities`        | BEHÅLL    | Systemdata.                             |
| `substitutions`              | BEHÅLL    | Systemdata.                             |
| `nutrition_display_profiles` | BEHÅLL    | Systemdata.                             |
| `allergen_market_rules`      | BEHÅLL    | Systemdata.                             |
| `season_events`              | BEHÅLL    | Systemdata.                             |
| `feature_flags`              | BEHÅLL    | Systemdata.                             |
| `release_gates`              | BEHÅLL    | Systemdata.                             |
| `inventory_decay_profiles`   | BEHÄLL    | Systemdata.                             |
| `ai_eval_cases`              | BEHÅLL    | Testdata.                               |
| `ai_eval_runs`               | BEHÅLL    | Testkörningsresultat.                   |

---

## 3. Gap-tabell mot nuvarande `DELETE /v1/me`

Nedan är tabeller som **idag varken raderas, anonymiseras eller motiveras att behållas** vid kontoradering.

| #   | Tabell                          | Kategori    | Risk                                             | Föreslagen åtgärd                                                                                               |
| --- | ------------------------------- | ----------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| 1   | `user_credentials`              | Auth        | Lösenordshash överlever radering.                | `DELETE FROM user_credentials WHERE user_id = $1`                                                               |
| 2   | `admin_totp`                    | Auth        | TOTP-secret överlever.                           | `DELETE`                                                                                                        |
| 3   | `email_verification_tokens`     | Auth        | Tokens överlever.                                | `DELETE`                                                                                                        |
| 4   | `password_reset_tokens`         | Auth        | Tokens överlever.                                | `DELETE`                                                                                                        |
| 5   | `user_consents`                 | Samtycken   | Samtyckeshistorik överlever.                     | `DELETE`                                                                                                        |
| 6   | `user_locale_preferences`       | Preferenser | Locale-preferenser överlever.                    | `DELETE`                                                                                                        |
| 7   | `household_members`             | Hushåll     | Användaren kvarstår som medlem.                  | `DELETE`                                                                                                        |
| 8   | `households` (+ cascading data) | Hushåll     | Enpersonshushåll läcker persondata.              | **Beslut krävs** (se §2.3)                                                                                      |
| 9   | `inventory_transactions`        | Hushåll     | `actor_user_id` kvarstår.                        | `SET actor_user_id = NULL`                                                                                      |
| 10  | `inventory_conflicts`           | Hushåll     | `resolved_by_user_id` kvarstår.                  | `SET resolved_by_user_id = NULL`                                                                                |
| 11  | `shopping_list_items`           | Hushåll     | `added_by_user_id` kvarstår.                     | `SET added_by_user_id = NULL`                                                                                   |
| 12  | `meal_boxes`                    | Hushåll     | `reserved_for_user_id` kvarstår.                 | `SET reserved_for_user_id = NULL`                                                                               |
| 13  | `cooking_sessions`              | Hushåll     | `started_by_user_id` kvarstår.                   | `SET started_by_user_id = NULL`                                                                                 |
| 14  | `receipts`                      | Hushåll     | Kvittobild (`image_url`) kan innehålla PII.      | Radera bild i lagring, sätt `image_url = NULL`                                                                  |
| 15  | `receipt_lines`                 | Hushåll     | OCR-text kan innehålla PII.                      | Skrubba `raw_text` eller radera rader om hushåll raderas.                                                       |
| 16  | `recipe_ratings`                | Recept      | Betyg/kommentar överlever.                       | `DELETE`                                                                                                        |
| 17  | `recipe_favorites`              | Recept      | Favoriter överlever.                             | `DELETE`                                                                                                        |
| 18  | `recipe_cooks`                  | Recept      | Lagningslogg överlever.                          | `DELETE`                                                                                                        |
| 19  | `food_memories`                 | Matminnen   | Matminnen + bild överlever.                      | `DELETE` (+ bild i lagring)                                                                                     |
| 20  | `creator_stats`                 | Creator     | Creator-profil överlever.                        | `DELETE`                                                                                                        |
| 21  | `creator_follows`               | Creator     | Följer/följare överlever.                        | `DELETE`                                                                                                        |
| 22  | `notifications`                 | Notiser     | Notiser överlever.                               | `DELETE`                                                                                                        |
| 23  | `push_tokens`                   | Push        | Push-token överlever.                            | `DELETE`                                                                                                        |
| 24  | `trials`                        | Trial       | Trial-info överlever.                            | `DELETE`                                                                                                        |
| 25  | `ai_usage_counters`             | Usage       | AI-användning överlever.                         | `DELETE`                                                                                                        |
| 26  | `audit_logs`                    | Audit       | `actor_user_id`, `ip`, metadata överlever.       | Anonymisera (`NULL` + skrubba metadata)                                                                         |
| 27  | `domain_events`                 | Events      | `user_id` + PII i payload överlever.             | Anonymisera (`NULL` + skrubba payload)                                                                          |
| 28  | `idempotency_keys`              | API         | Cachade svar kopplade till användaren överlever. | `DELETE`                                                                                                        |
| 29  | `subscriptions`                 | Betalning   | `user_id` kvarstår för evigt.                    | Pseudonymisera efter retention (eller direkt vid radering om ekonomiskt spår finns kvar i `store_transactions`) |
| 30  | `subscription_events`           | Betalning   | `user_id` + payload kvarstår.                    | Anonymisera (`user_id = NULL`, skrubba payload)                                                                 |
| 31  | `store_transactions`            | Betalning   | `raw_payload` kan innehålla PII.                 | Skrubba `raw_payload`, anonymisera `user_id`                                                                    |

**Totalt:** 31 tabell-förekomster som behöver åtgärdas (många är `SET NULL`, inte radering).

---

## 4. Bilder / objektlagring

Förutom redan hanterade `scan_jobs.s3_keys`, `ai_corrections.image_s3_key` och `ai_training_bank.image_s3_key` finns följande användarbilder:

| Källa                     | Kolumn             | Risk                                                                                                        | Åtgärd vid GDPR-radering                                                                                     |
| ------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `meals.photo_url`         | Foto på måltid     | Personlig bild.                                                                                             | Radera objektet i lagring; sätt `photo_url = NULL`.                                                          |
| `food_memories.photo_url` | Foto från matminne | Personlig bild.                                                                                             | Radera objektet i lagring; sätt `photo_url = NULL`.                                                          |
| `receipts.image_url`      | Kvittobild         | Potentiellt personlig (kortnummer, butik, köp).                                                             | Radera objektet i lagring; sätt `image_url = NULL`.                                                          |
| `recipes.image_urls`      | Receptbild         | Om receptet är **draft/private** och skapats av användaren: personlig. Om publikt/verifierat: delad resurs. | För draft/private: radera bilderna. För publika: behåll (om användaren samtyckt) eller anonymisera skaparen. |

**Notering:** Det finns ingen dedikerad profilbildskolumn i `users`. Om sådan läggs till i framtiden måste den inkluderas i residual-testet.

---

## 5. Design — RESIDUAL-TEST

Mål: en permanent test som skapar en användare med **en rad i varje personlig tabell**, kör `DELETE /v1/me`, och verifierar att inga personspår kvarstår (utom motiverat-behåll som måste vara PII-fritt/anonymiserat).

### 5.1 Teststruktur (förslag)

```ts
describe("DELETE /v1/me — GDPR residual test", () => {
  it("lämnar inga personspår efter kontoradering", async () => {
    const { token, userId } = await registerUser("residual@example.invalid");

    // 1. Skapa en rad i varje personlig tabell
    await seedAllPersonalTables(userId, token);

    // 2. Kör DELETE /v1/me
    const res = await app.inject({
      method: "DELETE",
      url: "/v1/me",
      headers: { authorization: `Bearer ${token}` },
    });
    expect(res.statusCode).toBe(200);

    // 3. Verifiera: varje tabell är antingen tom eller anonym/PII-fri
    await assertNoPersonalTraces(app.db, userId);
  });
});
```

### 5.2 Hjälpfunktioner

`seedAllPersonalTables(userId, token)` ska skapa exempeldata i:

- `user_credentials` (skapas vid registrering)
- `user_health_profiles`, `user_preferences`, `user_locale_preferences`, `user_consents`
- `households` + `household_members` (+ `storage_locations`)
- `scan_jobs` + `ai_corrections` + `ai_training_bank`
- `meals`, `memory_items`, `taste_signals`, `food_memories`
- `recipe_ratings`, `recipe_favorites`, `recipe_cooks`, `creator_stats`, `creator_follows`
- `notifications`, `push_tokens`
- `trials`, `ai_usage_counters`
- `audit_logs`, `domain_events`, `idempotency_keys`
- `subscriptions`, `subscription_events`, `store_transactions`
- `inventory_transactions`, `inventory_conflicts`, `shopping_list_items`, `meal_boxes`, `cooking_sessions`
- `receipts` + `receipt_lines`

`assertNoPersonalTraces(db, userId)` ska:

1. För **RADERA**-tabeller: `SELECT count(*) = 0 WHERE user_id = $userId`.
2. För **ANONYMISERA**-tabeller: `SELECT count(*) = 0 WHERE user_id = $userId` (eftersom `user_id` ska vara `NULL`).
3. För **BEHÅLL**-tabeller: tillåta rader med `user_id = $userId` **men** kontrollera att känsliga fält (`raw_payload`, `metadata`, `ip`) är skrubbade.
4. För bilder: mocka `storage.deleteObject` och verifiera att alla personliga bildnycklar raderats.
5. För `users`: kontrollera att `email`, `displayName` är anonymiserade och `deletedAt` är satt.

### 5.3 Mekanism för att upptäcka nya tabeller

För att residual-testet automatiskt ska upptäcka framtida tabeller som lägger till `user_id`:

```ts
// I testet: reflektera schemat och jämför med en allow-list
const userIdColumns = await db.execute(sql`
  SELECT table_name, column_name
  FROM information_schema.columns
  WHERE table_schema = 'public'
    AND column_name IN ('user_id', 'actor_user_id', 'creator_user_id',
                        'started_by_user_id', 'resolved_by_user_id',
                        'added_by_user_id', 'reserved_for_user_id')
`);
// Jämför med allow-list över tabeller som testet täcker.
// Om en ny tabell dyker upp: testet failar tills den klassas.
```

Denna "schema-diff"-assertion gör testet till en permanent vakt.

### 5.4 Förväntat testresultat idag (före implementation)

Testet kommer att **faila** på de ~31 luckor listade i §3. Det är avsiktligt — det blir den röda tråden som implementationen ska göra grön.

---

## 6. Öppna beslut innan implementation

Följande punkter behöver ditt (Johans) godkännande eller vägledning:

1. **Enpersonshushåll vid radering:** Ska vi automatiskt radera hela hushållet (inkl. lager, listor, kvitton, matplaner) när enda medlemmen begär GDPR-radering? Alternativet är att tvinga användaren överföra ägarskap först, vilket är mer komplext.
   - **Rekommendation:** Automatisk radering av enpersonshushåll.

2. **Publika recept:** Ska ett publikt/verifierat recept som användaren skapat anonymiseras (`creator_user_id = NULL`, `creator_display_name = NULL`) eller raderas helt?
   - **Rekommendation:** Anonymisera publika recept, radera draft/private.

3. **Finansiella poster:** Ska `subscriptions.user_id` pseudonymiseras direkt vid GDPR-radering, eller först efter 7 år? Bokföringslagen kräver att vi kan bevisa köpet, men inte nödvändigtvis vem som gjorde det.
   - **Rekommendation:** Sätt `subscriptions.user_id = NULL` direkt vid radering; ekonomisk spårbarhet finns kvar i `store_transactions` (som skrubbas men behåller transaktions-ID).

4. **Audit logs:** Ska `audit_logs` behållas med `actor_user_id = NULL` i 2+ år, eller raderas efter kortare tid?
   - **Rekommendation:** Behåll 2 år med anonymiserad aktör, för säkerhetsincident-utredning.

5. **Domain events:** Ska `domain_events` behållas för event-sourcing, men med `user_id = NULL` och skrubbad payload?
   - **Rekommendation:** Ja, behåll för downstream replay/konsumenter.

---

## 7. Sammanfattning & rekommenderad ordningsföljd

### 7.1 Snabbfix (högsta riskerna)

1. Radera auth-tabeller: `user_credentials`, `admin_totp`, `email_verification_tokens`, `password_reset_tokens`.
2. Radera preferenser/samtycken: `user_consents`, `user_locale_preferences`.
3. Radera notiser/push: `notifications`, `push_tokens`.
4. Radera trials/usage: `trials`, `ai_usage_counters`.
5. Radera idempotency: `idempotency_keys`.
6. Radera receptinteraktioner: `recipe_ratings`, `recipe_favorites`, `recipe_cooks`, `creator_stats`, `creator_follows`, `food_memories`.
7. Anonymisera hushållsreferenser: `inventory_transactions.actor_user_id`, `inventory_conflicts.resolved_by_user_id`, `shopping_list_items.added_by_user_id`, `meal_boxes.reserved_for_user_id`, `cooking_sessions.started_by_user_id`.
8. Hantera kvitton: radera `receipts.image_url` i lagring, skrubba `receipt_lines.raw_text`.

### 7.2 Större beslut

9. Hushållsstrategi: radera enpersonshushåll, anonymisera delade hushåll.
10. Finansiella poster: anonymisera `subscriptions`/`subscription_events`/`store_transactions` enligt retention.
11. Audit/domain events: anonymisera men behåll enligt retention.

### 7.3 Permanent vakt

12. Implementera residual-testet i `apps/api/test/me.residual.test.ts` med schema-diff-assertion.

---

## 8. Bilaga — fullständig tabellista från schema

(Används för att säkerställa att inga tabeller missats.)

```
adminTotp, aiCorrections, aiEvalCases, aiEvalRuns, aiTrainingBank,
aiUsageCounters, allergenMarketRules, analyticsDailySnapshots, auditLogs,
canonicalIngredients, cookingAssumptionProfiles, cookingSessions,
creatorFollows, creatorStats, domainEvents, emailVerificationTokens,
featureFlags, foodMemories, householdMembers, households, idempotencyKeys,
ingredientTranslations, inventoryConflicts, inventoryDecayProfiles,
inventoryItemDecayProfile, inventoryItems, inventoryTransactions,
mealBoxes, meals, memoryItems, notifications, nutritionDisplayProfiles,
passwordResetTokens, priceObservations, productAnalyticsEvents, products,
pushTokens, receiptLines, receipts, recipeCooks, recipeFavorites,
recipeIngredients, recipeRatings, recipeSimilarities, recipeSourceRegistry,
recipeStepTranslations, recipeSteps, recipeTranslations, recipes,
refreshTokens, releaseGates, scanJobs, seasonEvents, shoppingListItems,
shoppingLists, storageLocations, storeNotifications, storeTransactions,
subscriptionEvents, subscriptions, substitutions, tasteSignals, trials,
unitTranslations, userConsents, userCredentials, userHealthProfiles,
userLocalePreferences, userPreferences, users, wasteSummaries,
weekPlanEntries, weekPlans
```

Alla tabeller är granskade och klassade ovan.
