ci: trigga på master + formatfix inför Gitea Actions
CI / Typecheck, test & build (push) Failing after 2s

This commit is contained in:
Sven (AAMOS AI)
2026-08-13 17:25:14 +07:00
parent 61d60931ad
commit c32a7e33c7
141 changed files with 40555 additions and 45210 deletions
+137 -130
View File
@@ -5,6 +5,7 @@
> **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`)
@@ -28,14 +29,14 @@
### 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. |
| 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.
@@ -43,12 +44,12 @@
### 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. |
| 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.
@@ -56,13 +57,14 @@
### 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**. |
| 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.
@@ -73,24 +75,24 @@
### 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. |
| 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.
@@ -98,10 +100,10 @@
### 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. |
| 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.
@@ -110,17 +112,17 @@
### 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). |
| 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.
@@ -128,10 +130,10 @@
### 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). |
| 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.
@@ -139,14 +141,14 @@
### 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. |
| 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.
@@ -154,38 +156,38 @@
### 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. |
| 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. |
| 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. |
---
@@ -193,39 +195,39 @@
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` |
| # | 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).
@@ -235,12 +237,12 @@ Nedan är tabeller som **idag varken raderas, anonymiseras eller motiveras att b
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. |
| 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.
@@ -277,6 +279,7 @@ describe("DELETE /v1/me — GDPR residual test", () => {
### 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`)
@@ -291,6 +294,7 @@ describe("DELETE /v1/me — GDPR residual test", () => {
- `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.
@@ -347,6 +351,7 @@ Följande punkter behöver ditt (Johans) godkännande eller vägledning:
## 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`.
@@ -357,11 +362,13 @@ Följande punkter behöver ditt (Johans) godkännande eller vägledning:
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.
---