diff --git a/.design-sync/NOTES.md b/.design-sync/NOTES.md
new file mode 100644
index 00000000..a80e57cf
--- /dev/null
+++ b/.design-sync/NOTES.md
@@ -0,0 +1,28 @@
+# design-sync notes — Equinet
+
+## Repo shape
+
+Equinet is a Next.js app, not a standalone component-library package — no `dist/`, no Storybook, no `main`/`module`/`exports` in `package.json`. This sync uses a hand-authored barrel entry (`.design-sync/synth-entry.ts`) + `cfg.componentSrcMap` to declare exactly which components are in scope, instead of relying on `.d.ts`-driven discovery. First sync scoped to 9 pure `src/components/ui` primitives (14 exports counting `Card`'s sub-parts): Button, Badge, Card/CardHeader/CardTitle/CardDescription/CardContent/CardFooter, Input, Label, Textarea, Checkbox, Switch, Skeleton.
+
+To add a component to the sync: add an export line to `.design-sync/synth-entry.ts` and an entry to `cfg.componentSrcMap`, then rebuild.
+
+## CSS
+
+No compiled Tailwind stylesheet exists anywhere in the repo (Tailwind v4, CSS-first config via `@tailwindcss/postcss`). `.design-sync/build-css.mjs` runs PostCSS + `@tailwindcss/postcss` directly against `src/app/globals.css` to produce `.design-sync/.cache/compiled.css`, which `cfg.cssEntry` points at. Tailwind v4's automatic content detection scans the whole project from `globals.css`'s location, so the compiled CSS includes utility classes used anywhere in the app — safe (superset), but means the file is larger (~146KB) than strictly needed for the 9 synced components. Re-run `node .design-sync/build-css.mjs` before every rebuild if any Tailwind class usage changed (wired as `cfg.buildCmd` for documentation — not auto-executed by resync.mjs).
+
+## Known, accepted gaps (non-blocking)
+
+- **`[TOKENS_MISSING]`: `--font-geist-sans` / `--font-geist-mono`** — dead/vestigial CSS variable references left in `globals.css`'s `@theme inline` block from the original `create-next-app` scaffold. The app actually uses `Inter` (body) and `DM_Serif_Display` (headings) via `next/font/google`, applied as a generated `className` directly on `
`, not through these vars. Real consequence: text in the Claude Design previews renders in the browser's fallback sans-serif, not Inter — `next/font` self-hosted fonts aren't portable to a standalone bundle (build-hashed filenames, no stable static path). Accepted as-is; not chased further for this sync. If this needs fixing later: either clean up the dead `--font-geist-*` refs in `globals.css`, or self-host an Inter woff2 in the repo and wire it via `cfg.extraFonts`.
+- **`[TOKENS_MISSING]`: `--radix-select-*` / `--radix-dropdown-menu-*`** — set at runtime by Radix's Select/DropdownMenu primitives via inline styles. Not used by any of the 9 synced components (no Select/DropdownMenu in scope yet) — irrelevant until those are added to a future sync.
+- **Badge `destructive` variant contrast** — `text-destructive-foreground` on `bg-destructive` looked low-contrast in the review screenshot. `--destructive-foreground` is not defined in `globals.css` (only `--destructive` is) — this reflects the real app's token set, not a sync artifact. Worth flagging to the app's design owner separately; out of scope to fix here.
+
+## Scope leak caught before upload
+
+First `resync.mjs` run swept **the entire `docs/` tree** (architecture reviews, iOS reviews, payment-domain notes, roadmap, decision log — 41 files) into `guidelines/` via the default `guidelinesGlob` (`docs/*.md`, `docs/guides/**/*.md`), because the hand-authored `--entry` override resolves `PKG_DIR` to the repo root. **Fixed** by setting `cfg.guidelinesGlob: []` — this repo has no actual design-guideline docs to sync yet. If real design guidelines get written later (e.g. `docs/design/*.md`), point `guidelinesGlob` at that specific path — never leave it at the default in a repo where `PKG_DIR` is the whole app.
+
+## Re-sync risks
+
+- `cfg.componentSrcMap` is the only source of truth for which components are in scope — no `.d.ts` discovery is happening (confirmed empty `exported PascalCase symbols: 0` in every build log). Adding a new `src/components/ui/*.tsx` file does **not** auto-appear in the sync; it must be added to both `synth-entry.ts` and `componentSrcMap` by hand.
+- `.design-sync/.cache/compiled.css` is gitignored and regenerated by `build-css.mjs` — if it's stale (Tailwind classes changed in the app but this wasn't re-run), the next build will ship outdated styling silently. Re-run it as part of every re-sync.
+- The 9 scoped components have zero React-context dependencies today. The moment a context-dependent component (Dialog, Select, DropdownMenu, ResponsiveDialog's `isMobile` context, etc.) is added to the scope, `cfg.provider` will very likely need to be set — none of that has been figured out yet.
+- Toolchain versions used for this sync: Node (system default, no `.nvmrc` pin followed), `@playwright/test` 1.56.1 pinned in repo (chromium build v1200 installed to match), esbuild + ts-morph installed fresh into `.ds-sync/` (not version-pinned beyond `npm i`'s resolution at sync time).
diff --git a/.design-sync/build-css.mjs b/.design-sync/build-css.mjs
new file mode 100644
index 00000000..2c961fa0
--- /dev/null
+++ b/.design-sync/build-css.mjs
@@ -0,0 +1,16 @@
+import postcss from "postcss"
+import tailwindcss from "@tailwindcss/postcss"
+import { readFileSync, writeFileSync } from "node:fs"
+
+const input = readFileSync("src/app/globals.css", "utf8")
+
+postcss([tailwindcss()])
+ .process(input, { from: "src/app/globals.css", to: ".design-sync/.cache/compiled.css" })
+ .then((result) => {
+ writeFileSync(".design-sync/.cache/compiled.css", result.css)
+ console.log(`Wrote ${result.css.length} bytes`)
+ })
+ .catch((err) => {
+ console.error(err)
+ process.exit(1)
+ })
diff --git a/.design-sync/config.json b/.design-sync/config.json
new file mode 100644
index 00000000..56680588
--- /dev/null
+++ b/.design-sync/config.json
@@ -0,0 +1,28 @@
+{
+ "projectId": "e77a0d60-26e5-4506-b7ed-b21f48a54424",
+ "pkg": "equinet-ui",
+ "globalName": "Equinet",
+ "shape": "package",
+ "entry": ".design-sync/synth-entry.ts",
+ "tsconfig": "tsconfig.json",
+ "buildCmd": "node .design-sync/build-css.mjs",
+ "cssEntry": ".design-sync/.cache/compiled.css",
+ "readmeHeader": ".design-sync/conventions.md",
+ "guidelinesGlob": [],
+ "componentSrcMap": {
+ "Button": "src/components/ui/button.tsx",
+ "Badge": "src/components/ui/badge.tsx",
+ "Card": "src/components/ui/card.tsx",
+ "CardHeader": "src/components/ui/card.tsx",
+ "CardTitle": "src/components/ui/card.tsx",
+ "CardDescription": "src/components/ui/card.tsx",
+ "CardContent": "src/components/ui/card.tsx",
+ "CardFooter": "src/components/ui/card.tsx",
+ "Input": "src/components/ui/input.tsx",
+ "Label": "src/components/ui/label.tsx",
+ "Textarea": "src/components/ui/textarea.tsx",
+ "Checkbox": "src/components/ui/checkbox.tsx",
+ "Switch": "src/components/ui/switch.tsx",
+ "Skeleton": "src/components/ui/skeleton.tsx"
+ }
+}
diff --git a/.design-sync/conventions.md b/.design-sync/conventions.md
new file mode 100644
index 00000000..dc9c6dbd
--- /dev/null
+++ b/.design-sync/conventions.md
@@ -0,0 +1,57 @@
+## Wrapping and setup
+
+No provider or root wrapper is required — none of the synced components read from React context. Design tokens are plain CSS custom properties on `:root` (and `.dark` for dark mode), already included in `styles.css`. To preview dark mode, add the `dark` class to an ancestor element; no extra setup needed.
+
+## Styling idiom
+
+This is a Tailwind CSS v4 + shadcn/ui system, styled entirely through utility classes — never inline styles or CSS-in-JS. Variant-bearing components (`Button`, `Badge`) use `class-variance-authority`: pass `variant`/`size` props, never hand-compose utility classes for variant state. Every component accepts a `className` prop that merges on top of its defaults (via `cn()` / `tailwind-merge`) — extend spacing/layout this way, don't override variant colors by className.
+
+Real token vocabulary (all defined in `styles.css`, both light and dark):
+
+| Token | Utility class examples |
+|---|---|
+| `--primary` / `--primary-foreground` | `bg-primary`, `text-primary-foreground` |
+| `--secondary` / `--secondary-foreground` | `bg-secondary`, `text-secondary-foreground` |
+| `--destructive` | `bg-destructive`, `text-destructive` |
+| `--muted` / `--muted-foreground` | `bg-muted`, `text-muted-foreground` |
+| `--card` / `--card-foreground` | `bg-card`, `text-card-foreground` |
+| `--border` / `--input` / `--ring` | `border`, `border-input`, `ring-ring` |
+| `--radius` (+ `-sm`/`-md`/`-lg`/`-xl` scale) | `rounded-md`, `rounded-lg`, `rounded-xl` |
+
+Brand primary is a dark green (`oklch(0.42 0.09 160)`), not the shadcn default — always use `bg-primary`/`variant="default"` rather than a hardcoded green.
+
+## Where the truth lives
+
+- `styles.css` — root token definitions (`:root`, `.dark`) plus the `@import` of `_ds_bundle.css`, which carries the compiled component CSS.
+- Each component's `.prompt.md` — usage reference for that component specifically.
+- `_ds_bundle.js` — the real compiled components (`Button`, `Badge`, `Card`/`CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`, `Input`, `Label`, `Textarea`, `Checkbox`, `Switch`, `Skeleton`).
+
+## Example composition
+
+```tsx
+import {
+ Card, CardHeader, CardTitle, CardDescription,
+ CardContent, CardFooter, Button, Badge,
+} from "equinet-ui"
+
+function BookingCard() {
+ return (
+
+
+ Helskoning
+ Lisa Andersson · Molly
+
+
+
+ )
+}
diff --git a/.design-sync/previews/Label.tsx b/.design-sync/previews/Label.tsx
new file mode 100644
index 00000000..9c42fea0
--- /dev/null
+++ b/.design-sync/previews/Label.tsx
@@ -0,0 +1,10 @@
+import { Label, Input } from "equinet-ui"
+
+export function Default() {
+ return (
+
+
+
+
+ )
+}
diff --git a/.design-sync/previews/Skeleton.tsx b/.design-sync/previews/Skeleton.tsx
new file mode 100644
index 00000000..b4baf473
--- /dev/null
+++ b/.design-sync/previews/Skeleton.tsx
@@ -0,0 +1,11 @@
+import { Skeleton } from "equinet-ui"
+
+export function CardLoading() {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/.design-sync/previews/Switch.tsx b/.design-sync/previews/Switch.tsx
new file mode 100644
index 00000000..64305f12
--- /dev/null
+++ b/.design-sync/previews/Switch.tsx
@@ -0,0 +1,20 @@
+import { Switch, Label } from "equinet-ui"
+
+export function States() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/.design-sync/previews/Textarea.tsx b/.design-sync/previews/Textarea.tsx
new file mode 100644
index 00000000..2fd1a754
--- /dev/null
+++ b/.design-sync/previews/Textarea.tsx
@@ -0,0 +1,25 @@
+import { Textarea, Label } from "equinet-ui"
+
+export function Default() {
+ return (
+
+
+
+
+ )
+}
+
+export function Filled() {
+ return (
+
+
+
+
+ )
+}
diff --git a/.design-sync/synth-entry.ts b/.design-sync/synth-entry.ts
new file mode 100644
index 00000000..ce3c3a8f
--- /dev/null
+++ b/.design-sync/synth-entry.ts
@@ -0,0 +1,20 @@
+// Hand-authored bundle entry for the design-sync converter.
+// Equinet has no standalone component-library build (it's a Next.js app,
+// not a published package), so this barrel stands in for a `dist/` entry --
+// it re-exports exactly the components scoped into this sync.
+export { Button } from "../src/components/ui/button"
+export { Badge } from "../src/components/ui/badge"
+export {
+ Card,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+ CardContent,
+ CardFooter,
+} from "../src/components/ui/card"
+export { Input } from "../src/components/ui/input"
+export { Label } from "../src/components/ui/label"
+export { Textarea } from "../src/components/ui/textarea"
+export { Checkbox } from "../src/components/ui/checkbox"
+export { Switch } from "../src/components/ui/switch"
+export { Skeleton } from "../src/components/ui/skeleton"
diff --git a/.gitignore b/.gitignore
index 7f7b612f..37875502 100644
--- a/.gitignore
+++ b/.gitignore
@@ -121,3 +121,10 @@ docs/metrics/e2e-visual/**/report/
docs/metrics/e2e-visual/**/traces/
.mcp.json
+
+# design-sync (claude.ai/design) — regenerated build output + machine state
+.ds-sync/
+ds-bundle/
+.design-sync/.cache/
+.design-sync/learnings/
+.design-sync/node_modules
diff --git a/docs/INDEX.md b/docs/INDEX.md
index 6ae2c52f..23fdc93f 100644
--- a/docs/INDEX.md
+++ b/docs/INDEX.md
@@ -3,7 +3,7 @@ title: "Equinet -- Dokumentationsindex"
description: "Centralt navigeringsdokument for all projektdokumentation"
category: root
status: active
-last_updated: 2026-06-07
+last_updated: 2026-08-09
sections:
- Arkitektur
- Operations
@@ -69,6 +69,9 @@ sections:
| [rls-findings.md](security/rls-findings.md) | Row Level Security-analys (2026-03, företrädd av augusti-auditen nedan) |
| [PENTEST-REPORT-2026-02-27.md](security/PENTEST-REPORT-2026-02-27.md) | Pentest-rapport (februari 2026, utokad) |
| [supabase-rls-security-audit-2026-08-06.md](security/supabase-rls-security-audit-2026-08-06.md) | Fullständig RLS/grants-audit (augusti 2026) -- åtgärdade fynd, kvarvarande Medium/Low, ingen High kvar |
+| [gdpr-records-of-processing.md](security/gdpr-records-of-processing.md) | GDPR Art. 30 -- datakarta över personuppgifter, ändamål, rättslig grund, lagringstid |
+| [gdpr-subprocessor-register.md](security/gdpr-subprocessor-register.md) | GDPR -- register över personuppgiftsbiträden (Stripe, Supabase, Sentry m.fl.) och DPA-status |
+| [gdpr-gap-register.md](security/gdpr-gap-register.md) | GDPR -- prioriterat gap-register med risknivå och rekommenderad åtgärd |
## Testning
@@ -168,4 +171,4 @@ Avslutade planer, ersatta dokument och 67 rå retrospectives finns i [archive/](
---
-*Senast uppdaterad: 2026-04-12*
+*Senast uppdaterad: 2026-08-09*
diff --git a/docs/security/gdpr-gap-register.md b/docs/security/gdpr-gap-register.md
new file mode 100644
index 00000000..7cc2c597
--- /dev/null
+++ b/docs/security/gdpr-gap-register.md
@@ -0,0 +1,293 @@
+---
+title: "GDPR -- Gap-register"
+description: "Prioriterad lista över identifierade GDPR-luckor i Equinet, med risknivå och rekommenderad åtgärd"
+category: security
+status: active
+last_updated: 2026-08-09
+tags: [gdpr, security, privacy, gap-analysis, dataskydd]
+related:
+ - docs/security/gdpr-records-of-processing.md
+ - docs/security/gdpr-subprocessor-register.md
+ - docs/security/data-retention-policy.md
+ - docs/security/supabase-rls-security-audit-2026-08-06.md
+ - docs/architecture/messaging-domain.md
+ - docs/product-audit/technical-risks.md
+sections:
+ - Syfte
+ - Hur luckorna hittades
+ - Sammanfattning
+ - Hög prioritet
+ - Medel prioritet
+ - Låg prioritet
+ - Redan medvetet uppskjutet
+ - Vad som INTE är en lucka
+ - Nästa steg
+---
+
+# GDPR -- Gap-register
+
+## Syfte
+
+Prioriterad lista över konkreta GDPR-luckor i Equinet, identifierade genom
+kodgranskning (inte antaganden). Varje rad har en risknivå och en rekommenderad
+åtgärd. **Inget i detta dokument är åtgärdat -- det är en inventering.**
+Prioritering och beslut om vilka luckor som ska stängas, och i vilken ordning,
+är ett produktbeslut för Johan.
+
+## Hur luckorna hittades
+
+1. Jämförde `src/app/api/export/my-data/route.ts` (dataexport, Art. 15/20) mot
+ samtliga personuppgiftsmodeller i `prisma/schema.prisma`.
+2. Jämförde `src/domain/account/AccountDeletionService.ts` (kontoradering, Art. 17)
+ -- särskilt `deletePersonalRecords()` -- mot samma modeller, inklusive att läsa
+ varje relations `onDelete`-beteende.
+3. Läste `sentry.server.config.ts` för att se vad `beforeSend` faktiskt scrubbar.
+4. Läste `CookieNotice.tsx` för att se om consent faktiskt hanteras.
+5. Sökte efter cron/cleanup-jobb för `AdminAuditLog`.
+6. Läste tidigare öppna frågor i `docs/product-audit/technical-risks.md` (R19) och
+ `docs/architecture/messaging-domain.md` för att bekräfta/komplettera kända gap.
+7. En andra, oberoende faktakontrollsomgång (separat agent, samma dag) verifierade
+ varje konkret påstående på nytt mot koden -- den hittade ytterligare fyra gap
+ (#3b, #5b, #5c, #5d, #7b nedan) som första genomgången missade, plus ett
+ felaktigt påstående om Apple/APNs-radens innehåll (korrigerat i
+ `gdpr-subprocessor-register.md`).
+
+## Sammanfattning
+
+| # | Lucka | Prioritet |
+|---|-------|-----------|
+| 1 | `AdminAuditLog` har ingen retention/radering | Hög |
+| 2 | Kontoradering missar `ProviderCustomerNote.content` | Medel |
+| 3 | Enhets-/inbjudningstokens raderas aldrig (cascade utlöses aldrig) | Medel |
+| 3b | `Stable`/`StableSpot`/`StableInviteToken`-adressdata raderas aldrig (samma cascade-brist som #3, men med full adress + kontaktuppgifter) | Medel-Hög |
+| 4 | Kontoradering anonymiserar inte `RouteOrder`/`RouteStop` | Medel |
+| 5 | Dataexport saknar tre datakällor (se detalj) | Medel |
+| 5b | `ProviderVerification` (certifieringsdata) rörs inte av kontoradering | Medel |
+| 5c | `BugReport` (fritext, kan innehålla PII) rörs inte av kontoradering | Medel |
+| 5d | `Booking.providerNotes`/`horseInfo` nollställs inte vid kontoradering (bara `customerNotes` gör det) | Medel |
+| 6 | Cookie-notice är ingen samtyckesmekanism | Låg |
+| 7 | Sentry scrubbar inte all PII | Låg |
+| 7b | Push-notiser till Apple/APNs innehåller avsändarnamn + meddelandeutdrag, inte bara enhetstoken | Låg-Medel |
+| 8 | `data_retention`-flaggan är avstängd i produktion | Låg-info |
+| 9 | Ingen DPIA genomförd | Info |
+| 10 | DPA-status overifierad för samtliga personuppgiftsbiträden | Info (se subprocessor-registret) |
+
+## Hög prioritet
+
+### 1. `AdminAuditLog` saknar retention -- sparas för evigt
+
+**Vad:** `AdminAuditLog` (rad 1009 i schema) loggar `userEmail`, `ipAddress`,
+`userAgent` för varje admin-åtgärd, automatiskt via `withApiHandler`
+(`src/lib/api-handler.ts:174-191`). Det finns ingen TTL, inget cron-jobb och
+ingen manuell rensningsrutin. `data-retention-policy.md` nämner generiskt
+"Loggar (server): 1 år" men specificerar inte denna tabell, och ingen kod
+verkställer det för `AdminAuditLog`.
+
+**Varför det är allvarligt:** Detta är inte en teoretisk risk --
+`docs/security/supabase-rls-security-audit-2026-08-06.md` bekräftar att exakt
+denna tabell **saknade RLS** fram till nyligen, och att 52 rader med IP-adresser
+var läsbara av `anon`-rollen i produktion innan det åtgärdades. RLS-hålet är
+stängt, men datan som låg exponerad hade inte behövt finnas kvar så länge om
+en retention-policy funnits från början. Lagringsminimering (Art. 5.1.e)
+uppfylls inte för denna tabell idag.
+
+**Rekommenderad åtgärd:** Lägg till `AdminAuditLog` i retention-policyn (t.ex.
+12 månader) och återanvänd samma cron-mönster som `DataRetentionService`
+redan implementerar. Litet, avgränsat arbete -- passar som egen slice.
+
+## Medel prioritet
+
+### 2. `ProviderCustomerNote` anonymiseras inte vid kundens kontoradering
+
+**Vad:** `ProviderCustomerNote.content` (fritext OM en kund, skriven av
+leverantören, se schema rad 723) rensas inte av
+`AccountDeletionService.deletePersonalRecords()`. Kundens övriga data
+anonymiseras, men leverantörens anteckningar om samma kund ligger kvar,
+kopplade till `customerId`.
+
+**Rekommenderad åtgärd:** Avgör om dessa anteckningar ska raderas eller
+anonymiseras vid kundens kontoradering (jämförbart med hur `HorseNote` redan
+hanteras). Kräver ett litet produktbeslut: har leverantören ett berättigat
+intresse att behålla anteckningen (t.ex. bokföringsskäl) även efter att
+kunden raderat sitt konto?
+
+### 3. Enhets- och inbjudningstokens raderas aldrig vid kontoradering
+
+**Vad:** `MobileToken`, `DeviceToken` och `CustomerInviteToken` har
+`onDelete: Cascade` mot `User` i schemat -- men `AccountDeletionService`
+**anonymiserar** `User`-raden (ett `UPDATE`), den raderas aldrig. Cascade
+utlöses därför aldrig, och dessa tre tabeller behåller sina poster kopplade
+till det anonymiserade kontots (oförändrade) `userId` på obestämd tid.
+
+**Rekommenderad åtgärd:** Lägg till explicit `deleteMany`-anrop för dessa tre
+modeller i `deletePersonalRecords()`, samma mönster som redan används för
+`PushSubscription`, `Follow` m.fl. i samma metod. Litet, mekaniskt arbete.
+
+### 3b. `Stable`/`StableSpot`/`StableInviteToken` raderas aldrig -- samma cascade-brist som #3, men mer känslig data
+
+**Vad:** Samma rotorsak som lucka #3: dessa tre modeller har `onDelete: Cascade`
+mot `User`, men `AccountDeletionService` anonymiserar `User`-raden (UPDATE)
+istället för att radera den, så cascaden utlöses aldrig. Skillnaden mot #3 är
+att `Stable` innehåller full adress, stad, postnummer, kommun, GPS-koordinater
+samt kontakt-e-post och kontakttelefon -- betydligt känsligare data än en
+enhetstoken.
+
+**Rekommenderad åtgärd:** Samma tekniska lösning som #3 (explicit `deleteMany`
+i `deletePersonalRecords()`), men prioritera denna högre än #3 på grund av
+datakänsligheten. Kan göras i samma slice som #3 -- det är samma buggmönster.
+
+### 4. `RouteOrder`/`RouteStop` anonymiseras inte vid kontoradering
+
+**Vad:** Kundinitierade ruttbeställningar (`RouteOrder.customerId`,
+adress + GPS-koordinater i `RouteStop`) rörs inte av kontoraderingsflödet.
+
+**Rekommenderad åtgärd:** Avgör om detta ska anonymiseras (adress/GPS null)
+på samma sätt som `Booking.customerNotes`, eller om ruttdata ska behandlas
+som leverantörens verksamhetsdata (berättigat intresse) på samma sätt som
+anonymiserad bokningshistorik. Kräver ett litet produktbeslut.
+
+### 5. Dataexporten (Art. 15/20) täcker inte all data som lagras om en användare
+
+**Vad:** `GET /api/export/my-data` inkluderar profil, hästar, bokningar,
+egna `HorseNote`, recensioner och leverantörsdata -- men **inte**:
+- `ProviderCustomerNote` (anteckningar OM kunden, skrivna av leverantören)
+- `Message`/`Conversation` (meddelandeinnehåll)
+- `RouteOrder`/`RouteStop` (kundinitierade ruttbeställningar)
+
+Detta besvarar den öppna frågan i `docs/product-audit/technical-risks.md`
+(R19: "Oklart om export/radering täcker all data korrekt") -- svaret är nej,
+inte fullständigt.
+
+**Rekommenderad åtgärd:** Lägg till dessa tre datakällor i exportroutens
+JSON/CSV-output. Meddelandeinnehåll kräver ett produktbeslut om huruvida hela
+konversationen (inkl. motpartens meddelanden) ska ingå eller bara den
+exporterande användarens egna meddelanden.
+
+### 5b. `ProviderVerification` rörs inte av kontoradering
+
+**Vad:** Leverantörens certifierings-/utbildningsuppgifter (titel, utfärdare,
+år) anonymiseras inte av `anonymizeProvider()` när leverantören raderar sitt
+konto.
+
+**Rekommenderad åtgärd:** Lägg till i `anonymizeProvider()`-flödet, eller
+avgör om detta ska raderas separat.
+
+### 5c. `BugReport` rörs inte av kontoradering
+
+**Vad:** `BugReport` innehåller fritext (`title`, `description`,
+`reproductionSteps`) och `userAgent`, med en valfri koppling till `userId`.
+Rensas eller anonymiseras aldrig vid kontoradering. Redan flaggad i
+`docs/security/supabase-rls-security-audit-2026-08-06.md` som en tabell som
+"kan innehålla PII i beskrivningar".
+
+**Rekommenderad åtgärd:** Avgör om `userId`-kopplingen ska nollställas vid
+kontoradering (innehållet i sig kan vara relevant att behålla för
+produktutveckling, men kopplingen till en specifik raderad användare bör
+sannolikt tas bort).
+
+### 5d. `Booking.providerNotes`/`horseInfo` nollställs inte vid kontoradering
+
+**Vad:** Endast `Booking.customerNotes` nollställs av `anonymizeBookings()`
+(`data: { customerNotes: null }`). `providerNotes` och `horseInfo` -- som
+båda kan innehålla kundrelaterad fritext -- rörs inte, trots att bokningen i
+övrigt behandlas som anonymiserad.
+
+**Rekommenderad åtgärd:** Avgör om `providerNotes`/`horseInfo` ska nollställas
+på samma sätt som `customerNotes`, eller om leverantören har ett berättigat
+intresse att behålla dem (t.ex. som del av sin egen verksamhetshistorik).
+
+## Låg prioritet
+
+### 6. Cookie-notice är en informationsbanner, inte en samtyckesmekanism
+
+**Vad:** `CookieNotice.tsx` visar ett meddelande med en "Stäng"-knapp. Det
+finns inget opt-in/opt-out per cookiekategori. Detta är primärt en
+ePrivacy-fråga (cookielagen) snarare än en ren GDPR-fråga, men de två
+regelverken hänger ihop i praktiken eftersom analytics-cookies räknas som
+personuppgiftsbehandling.
+
+**Rekommenderad åtgärd:** Bygg om till en faktisk samtyckesmekanism om
+tjänsten sätter några cookies som kräver samtycke (utöver strikt nödvändiga
+auth-cookies). Kräver UX-/produktbeslut om kategorier -- större arbete,
+föreslogs som separat slice i scope-valet för detta dokumentationspaket.
+
+### 7. Sentry scrubbar inte all PII
+
+**Vad:** `sentry.server.config.ts` tar bara bort `cookie`- och
+`authorization`-headers i `beforeSend`. Övrig PII i request-context,
+breadcrumbs eller felmeddelanden (t.ex. om en e-postadress råkar hamna i en
+exception-message) scrubbas inte.
+
+**Rekommenderad åtgärd:** Utöka `beforeSend` med ytterligare fältrensning
+(e-post-regex, IP-maskning om Sentry inte redan gör det på plattformsnivå).
+Litet, avgränsat arbete.
+
+### 7b. Push-notiser till Apple/APNs innehåller meddelandeinnehåll, inte bara enhetstoken
+
+**Vad:** `MessageNotifier.ts` skickar avsändarnamn och ett utdrag (80 tecken)
+av meddelandetexten i push-payloaden när ett nytt meddelande skickas
+(`PushDeliveryService.sendAPNs`). Det innebär att meddelandeinnehåll --
+personuppgifter enligt registret ovan -- överförs till Apples infrastruktur,
+inte bara en opersonlig enhetstoken. Redan delvis känt: `messaging-domain.md`
+listar "Privacy-note om push-preview" som en öppen punkt från en tidigare
+security-review, men det har inte tidigare kopplats till subprocessor-/
+tredjelandsöverföringsfrågan.
+
+**Rekommenderad åtgärd:** Avgör om push-notiser ska vara generiska ("Du har
+ett nytt meddelande") istället för att inkludera ett textutdrag, vilket
+skulle eliminera överföringen av meddelandeinnehåll till Apple helt. Litet
+UX-avvägningsbeslut (mindre informativ notis vs. mindre dataöverföring).
+
+### 8. `data_retention`-flaggan är byggd men avstängd
+
+**Vad:** Hela retention-/anonymiseringsflödet för inaktiva konton finns
+implementerat (`DataRetentionService`, cron, e-postmall) men är gated bakom
+en feature flag som är **av som default** i produktion, enligt
+`data-retention-policy.md`. Det innebär att lagringsminimering (Art. 5.1.e)
+för inaktiva konton inte verkställs i praktiken idag, trots att policyn är
+skriven och koden finns.
+
+**Rekommenderad åtgärd:** Detta är explicit **inte** något att slå på som en
+del av detta dokumentationsarbete -- att aktivera flaggan innebär att
+verklig användardata i produktion automatiskt anonymiseras, vilket är en
+irreversibel dataoperation och ett produktbeslut som kräver Johans
+uttryckliga godkännande (RED-beslut enligt `docs/agent-operations/`).
+Rekommenderas som en egen, tydligt avgränsad uppföljning.
+
+## Redan medvetet uppskjutet
+
+Dessa är inte nya fynd -- de är redan dokumenterade avvägningar i repot och
+tas med här för fullständighet:
+
+- **`Message.senderId` anonymiseras inte separat vid kontoradering.**
+ Dokumenterat i `docs/architecture/messaging-domain.md` (D7, rad 609):
+ Cascade från `Booking` räcker för MVP, partiell anonymisering av
+ `senderId` är planerad som separat PR "om Dataskyddsombudet kräver det".
+ Notera: detta gap överlappar delvis med lucka #5 (export) och #3
+ (kontoradering) ovan -- men är redan ett känt, medvetet beslut, inte ett
+ nytt fynd.
+
+## Vad som INTE är en lucka
+
+- **`Horse.specialNeeds` m.fl. hästfält räknas inte som Art. 9-särskild
+ kategori** eftersom hästen inte är en registrerad fysisk person. Se
+ resonemang i `gdpr-records-of-processing.md`.
+- **RLS-säkerheten** är i gott skick -- `supabase-rls-security-audit-2026-08-06.md`
+ visar noll kvarvarande High-fynd. Detta gap-register handlar om
+ lagringsminimering och fullständighet i DSR-flöden, inte om åtkomstkontroll.
+- **Export och radering (Art. 15/17/20) finns och fungerar** för
+ huvuddelen av datan -- de är inte "saknade" funktioner, bara ofullständiga
+ i periferin (se luckor #2--#5).
+
+## Nästa steg
+
+Detta gap-register är en inventering, inte en åtgärdsplan. Rekommenderat
+nästa steg: Johan prioriterar vilka luckor (om några) som ska bli egna,
+avgränsade slices, i fallande prioritetsordning (#1 → #5 → #6/#7 → #8 som
+separat produktbeslut). Ingen kod har ändrats som en del av detta dokument.
+
+## Ändringslogg
+
+| Datum | Ändring |
+|-------|---------|
+| 2026-08-09 | Första versionen, kompletterad efter oberoende faktakontroll samma dag med luckor #3b, #5b, #5c, #5d och #7b. Del av GDPR-dokumentationspaketet (se `docs/agent-operations/`). |
diff --git a/docs/security/gdpr-records-of-processing.md b/docs/security/gdpr-records-of-processing.md
new file mode 100644
index 00000000..5f58a60b
--- /dev/null
+++ b/docs/security/gdpr-records-of-processing.md
@@ -0,0 +1,137 @@
+---
+title: "GDPR -- Register över behandlingsaktiviteter (Art. 30)"
+description: "Datakarta över vilka personuppgifter Equinet behandlar, i vilket syfte, med vilken rättslig grund och hur länge"
+category: security
+status: active
+last_updated: 2026-08-09
+tags: [gdpr, security, privacy, records-of-processing, dataskydd]
+depends_on:
+ - prisma/schema.prisma
+related:
+ - docs/security/gdpr-subprocessor-register.md
+ - docs/security/gdpr-gap-register.md
+ - docs/security/data-retention-policy.md
+ - docs/security/supabase-rls-security-audit-2026-08-06.md
+ - docs/architecture/messaging-domain.md
+sections:
+ - Syfte
+ - Personuppgiftsansvarig
+ - Kategorier av registrerade
+ - Behandlingsaktiviteter
+ - Särskilda kategorier av personuppgifter
+ - Överföring till tredje land
+ - Källor och metod
+ - Ändringslogg
+---
+
+# GDPR -- Register över behandlingsaktiviteter (Art. 30)
+
+## Syfte
+
+Detta dokument uppfyller GDPR Art. 30 -- kravet att föra ett register över
+behandlingsaktiviteter. Det är en faktabaserad datakarta: vilka personuppgifter
+Equinet behandlar, i vilket syfte, med vilken rättslig grund och hur länge de sparas.
+Kartläggningen är gjord genom att läsa `prisma/schema.prisma` och tillhörande kod
+(inte genom antaganden) -- se [Källor och metod](#källor-och-metod).
+
+Detta dokument ersätter INTE en juridisk granskning. Rättslig grund per behandling
+är en teknisk bedömning baserad på hur systemet faktiskt fungerar -- bekräfta med
+juridisk rådgivning innan dokumentet används som formellt bevis vid en
+IMY-förfrågan (Integritetsskyddsmyndigheten).
+
+## Personuppgiftsansvarig
+
+**Att fylla i av Johan (bolagsuppgifter finns inte i kodbasen):**
+
+| Fält | Värde |
+|------|-------|
+| Bolagsnamn | *(saknas -- fyll i)* |
+| Organisationsnummer | *(saknas -- fyll i)* |
+| Adress | *(saknas -- fyll i)* |
+| Kontakt för dataskyddsfrågor | support@equinet.se (bekräftat i `data-retention-policy.md`) |
+| Dataskyddsombud (DPO) utsett? | *(saknas -- affärsbeslut, se gap-registret)* |
+
+## Kategorier av registrerade
+
+| Kategori | Beskrivning |
+|----------|-------------|
+| Kund | Privatperson som bokar tjänster (`User.userType = "customer"`) |
+| Leverantör | Privatperson/enskild firma som säljer tjänster (`User.userType = "provider"` + `Provider`) |
+| Admin | Equinet-anställd/operatör med adminrätt (`User.isAdmin`) |
+| Manuellt tillagd kund | Kund utan eget konto, skapad av en leverantör (`ProviderCustomer`) -- undantagen från automatisk radering per `data-retention-policy.md` |
+
+## Behandlingsaktiviteter
+
+Per datamodell i `prisma/schema.prisma`. "Lagringstid" hänvisar till
+[data-retention-policy.md](data-retention-policy.md) där en policy finns; annars
+markerad som ej explicit definierad.
+
+| Modell | Personuppgifter | Ändamål | Rättslig grund | Lagringstid |
+|--------|-----------------|---------|-----------------|-------------|
+| `User` | Namn, e-post, telefon, adress, stad/kommun, GPS-koordinater | Kontohantering, tjänsteleverans, platsbaserad matchning kund-leverantör | Avtal (Art. 6.1.b) | 2 års inaktivitet -> anonymisering (policy finns, se [gap-registret](gdpr-gap-register.md) för verkställighetsstatus) |
+| `Provider` | Företagsnamn, adress, GPS-koordinater, beskrivning | Publik leverantörsprofil, matchning | Avtal (Art. 6.1.b) | Anonymiseras vid kontoradering |
+| `Horse` | Namn, ras, födelseår, `specialNeeds` (medicinska behov/allergier/temperament), UELN-registreringsnummer, chipnummer | Tjänsteleverans (t.ex. hovslagare behöver veta om hästen är känslig) | Avtal (Art. 6.1.b) | Raderas vid ägarens kontoradering |
+| `Booking` | `customerNotes`, `providerNotes`, `horseInfo` (fritext) | Bokningshantering | Avtal (Art. 6.1.b) | Endast `customerNotes` nollställs vid kundens kontoradering (`anonymizeBookings()`). `providerNotes` och `horseInfo` rörs INTE -- se gap-registret |
+| `HorseNote` | Fritextanteckningar om häst (kan innehålla hälsoinfo) | Journalföring kring hästens skötsel | Avtal (Art. 6.1.b) | Raderas när författaren raderar sitt konto, ELLER när hästägaren raderar sitt konto (Cascade från `Horse`) -- den andra vägen tar även bort anteckningar skrivna av andra (t.ex. veterinär/hovslagare) vars eget konto inte rörs |
+| `Stable` / `StableSpot` / `StableInviteToken` | Stalladress, stad, postnummer, kommun, GPS-koordinater, kontakt-e-post, kontakttelefon | Stallprofil, platsannonsering | Avtal (Art. 6.1.b) | `onDelete: Cascade` från `User`, men samma cascade-brist som DeviceToken/MobileToken nedan -- adressdatan blir kvar på obestämd tid, se gap-registret |
+| `Upload` | Uppladdade filer (avatar, häst-, tjänste-, verifieringsbilder), kopplade till `userId` | Profil-/verifieringsbilder | Avtal (Art. 6.1.b) | Raderas korrekt vid kontoradering (`deleteUploads` + `deleteStorageFiles`) |
+| `ProviderVerification` | Titel, utfärdare, år för leverantörens certifiering/utbildning | Verifiering av leverantörens kompetens | Avtal (Art. 6.1.b) | Rörs INTE av `anonymizeProvider()` vid kontoradering -- se gap-registret |
+| `BugReport` | Fritext (`title`, `description`, `reproductionSteps`), `userAgent`, valfri koppling till `userId` | Felsökning/produktutveckling | Berättigat intresse (Art. 6.1.f) | Rörs INTE av kontoradering. Redan flaggad i `supabase-rls-security-audit-2026-08-06.md` som en tabell som "kan innehålla PII i beskrivningar" -- se gap-registret |
+| `ProviderCustomerNote` | Fritextanteckningar OM en kund, skrivna av leverantören | Kundrelationshantering för leverantören | Berättigat intresse (Art. 6.1.f) | **Ej definierad -- se gap-registret** (raderas inte vid kundens kontoradering idag) |
+| `Payment` | Belopp, `providerPaymentId`, status | Betalningshantering, bokföring | Avtal (Art. 6.1.b) + sannolikt rättslig förpliktelse (bokföringslagen) för den bokföringsrelevanta delen | "Enligt Stripe retention policy" (policy-dokument) -- bokföringslagens 7-årskrav ej explicit verifierat mot kod, bekräfta med redovisningsansvarig |
+| `FortnoxConnection` | Krypterade OAuth-tokens för bokföringsintegration | Automatiserad bokföring | Avtal (Art. 6.1.b) | Ej explicit definierad |
+| `Review` / `CustomerReview` | Betyg + fritextkommentar | Kvalitetstransparens för andra användare | Avtal/berättigat intresse (Art. 6.1.b/f) | Kommentar raderas vid kontoradering, betyg bevaras anonymiserat |
+| `Message` / `Conversation` | Meddelandeinnehåll (`content`, max 2000 tecken), ev. bildbilaga | Kommunikation kund <-> leverantör kring en bokning | Avtal (Art. 6.1.b) | Raderas via Cascade från `Booking`. `senderId` anonymiseras INTE separat vid kontoradering -- känt, medvetet dokumenterat i `docs/architecture/messaging-domain.md` som uppskjutet |
+| `AvailabilityException` | GPS-koordinater för leverantörens arbetsplats | Schemaläggning | Avtal (Art. 6.1.b) | Ej explicit definierad |
+| `RouteOrder` / `Route` / `RouteStop` | Adress + GPS-koordinater (kundinitierad ruttbeställning) | Ruttplanering för leverantören | Avtal (Art. 6.1.b) | **Ej anonymiserad vid kontoradering -- se gap-registret** |
+| `DeviceToken` / `MobileToken` | Enhets-/sessionidentifierare (APNs-token, JWT-hash) | Push-notiser, sessionhantering (native app) | Avtal (Art. 6.1.b) | `onDelete: Cascade` från `User`, men utlöses aldrig eftersom kontoradering anonymiserar (UPDATE) snarare än raderar `User`-raden -- se gap-registret |
+| `PushSubscription` | Push-prenumerationsidentifierare | Push-notiser (webb) | Avtal (Art. 6.1.b) | Raderas korrekt vid kontoradering (`deletePersonalRecords()` -- till skillnad från DeviceToken/MobileToken ovan) |
+| `CustomerInviteToken` | Inbjudningstoken, kopplad till `userId` + `invitedByProviderId` | Kundinbjudningsflöde | Avtal (Art. 6.1.b) | Samma cascade-brist som ovan |
+| `AdminAuditLog` | `userEmail`, `ipAddress`, `userAgent` (om admin-användaren) | Säkerhetslogg för admin-åtgärder | Berättigat intresse / rättslig förpliktelse (Art. 6.1.f/c) -- säkerhetslogg | **Ingen TTL/radering -- sparas på obestämd tid, se gap-registret (Hög prioritet)** |
+| `PasswordResetToken` / `EmailVerificationToken` | Token kopplad till `userId` | Kontosäkerhet | Avtal (Art. 6.1.b) | Raderas vid kontoradering (bekräftat i `AccountDeletionService`) |
+
+## Särskilda kategorier av personuppgifter
+
+Ingen modell innehåller Art. 9-kategorier (hälsodata, etnicitet, politisk åsikt etc.)
+**om en fysisk person**. `Horse.specialNeeds` innehåller "medicinska behov, allergier,
+temperament" men gäller djuret, inte en registrerad fysisk person, och saknar därför
+Art. 9-status enligt gängse GDPR-tolkning (bekräftat resonemang, se
+`docs/user-research/marknadsanalys.md:135`). Bedömningen bör ändå dokumenteras
+explicit här eftersom fritextfält (`specialNeeds`, `customerNotes`, `HorseNote.content`)
+teoretiskt skulle kunna innehålla personuppgifter om en tredje person (t.ex. "kunden har
+en synskada och behöver extra tid") -- det finns ingen teknisk spärr mot detta, bara
+ett UX-antagande om vad fältet används till.
+
+## Överföring till tredje land
+
+Se [gdpr-subprocessor-register.md](gdpr-subprocessor-register.md) för fullständig
+lista över personuppgiftsbiträden och deras lagringsregion. Supabase-projekten
+(prod: Zürich, staging: Frankfurt, enligt CLAUDE.md) ligger inom EU/EES. Statusen för
+övriga biträden (Stripe, Sentry, Vercel, Resend, Upstash, Anthropic, Apple) är INTE
+verifierad i denna genomgång -- flera av dessa är amerikanska bolag där överföring
+kan kräva SCC (Standard Contractual Clauses) eller motsvarande. Se gap-registret.
+
+## Källor och metod
+
+Denna kartläggning gjordes genom:
+1. Genomläsning av `prisma/schema.prisma` (44 modeller)
+2. Genomläsning av `src/app/api/export/my-data/route.ts` och
+ `src/domain/account/AccountDeletionService.ts` för att verifiera faktiskt
+ dataflöde (inte bara schema)
+3. Grep efter tredjepartsintegrationer i `package.json`, `.env.example`, `src/lib/`
+4. Genomläsning av befintlig säkerhets-/retention-dokumentation i `docs/security/`
+5. En andra, oberoende faktakontrollsomgång (separat agent) som verifierade varje
+ konkret påstående mot koden på nytt -- hittade och korrigerade tre sakfel i
+ den första versionen samt fyra modeller (`Stable`/`StableSpot`/
+ `StableInviteToken`, `Upload`, `ProviderVerification`, `BugReport`) som
+ saknades i den första genomgången. Se ändringsloggen.
+
+Ingen data i produktionsdatabasen har lästs eller exporterats för detta arbete.
+Trots två genomgångar kan dokumentet fortfarande innehålla luckor -- det är en
+ögonblicksbild från 2026-08-09, inte en garanti för fullständighet.
+
+## Ändringslogg
+
+| Datum | Ändring |
+|-------|---------|
+| 2026-08-09 | Första versionen, korrigerad efter oberoende faktakontroll samma dag: fixade felaktig `PushSubscription`-gruppering, korrigerade överdrivet `Booking`-anonymiseringspåstående, nyanserade `HorseNote`-raden, lade till `Stable`/`Upload`/`ProviderVerification`/`BugReport`. Del av GDPR-dokumentationspaketet (se `docs/agent-operations/`). |
diff --git a/docs/security/gdpr-subprocessor-register.md b/docs/security/gdpr-subprocessor-register.md
new file mode 100644
index 00000000..9f76288e
--- /dev/null
+++ b/docs/security/gdpr-subprocessor-register.md
@@ -0,0 +1,78 @@
+---
+title: "GDPR -- Register över personuppgiftsbiträden"
+description: "Alla tredjepartstjänster som behandlar personuppgifter för Equinets räkning, med ändamål, delad data och DPA-status"
+category: security
+status: active
+last_updated: 2026-08-09
+tags: [gdpr, security, privacy, subprocessors, dpa, dataskydd]
+depends_on:
+ - .env.example
+ - package.json
+related:
+ - docs/security/gdpr-records-of-processing.md
+ - docs/security/gdpr-gap-register.md
+sections:
+ - Syfte
+ - Register
+ - Hur registret hålls uppdaterat
+ - Källor och metod
+ - Ändringslogg
+---
+
+# GDPR -- Register över personuppgiftsbiträden
+
+## Syfte
+
+Enligt GDPR Art. 28 måste varje personuppgiftsbiträde (subprocessor) som behandlar
+personuppgifter för Equinets räkning ha ett skriftligt avtal (Data Processing
+Agreement, DPA) på plats. Detta register listar alla tredjepartstjänster som
+identifierats i kodbasen och vilken data som delas med dem.
+
+**Viktigt om DPA-status:** Detta dokument är en teknisk kartläggning av VILKA
+tjänster som används och VILKEN data som flödar till dem -- inte en bekräftelse
+på att avtal faktiskt är granskade eller accepterade. De flesta leverantörer
+nedan erbjuder ett standardiserat DPA/"Data Processing Addendum" som ingår i
+deras användarvillkor (ofta accepteras automatiskt vid kontoregistrering), men
+ingen bekräftelse på detta har hittats i kodbasen -- det är inte heller möjligt
+att verifiera från kod. **DPA-status per leverantör är en affärsuppgift som
+kräver manuell uppföljning, se [gap-registret](gdpr-gap-register.md).**
+
+## Register
+
+| Tjänst | Ändamål | Personuppgifter som delas | Lagringsregion | DPA-status |
+|--------|---------|----------------------------|-----------------|------------|
+| **Supabase** | Autentisering, databas (Postgres), filstorage | I princip all data i registret ovan (Supabase hostar hela databasen) | Prod: Zürich. Staging: Frankfurt (EU/EES, enligt CLAUDE.md) | Ej verifierat i repo -- kontrollera att Supabase DPA accepterats i organisationens inställningar |
+| **Stripe** | Betalningshantering (opt-in, `PAYMENT_PROVIDER="mock"` som default) | Namn, e-post, betalningsreferenser (inga kortnummer lagras lokalt) | Ej verifierat -- Stripe erbjuder EU-databehandling som tillval | Ej verifierat i repo |
+| **Sentry** | Felövervakning (error tracking) | Kan innehålla PII i stack traces/breadcrumbs/context -- `beforeSend` scrubbar idag bara `cookie`/`authorization`-headers, se gap-registret | Ej verifierat (beror på vald Sentry-region) | Ej verifierat i repo |
+| **Upstash Redis** | Rate limiting | IP-adresser | Ej verifierat | Ej verifierat i repo |
+| **Resend** | Transaktionsmail (bokningsbekräftelser, kvitton, kontoraderingsbekräftelse, retention-varningar) | Namn, e-postadress, bokningsinnehåll i mailtext | Ej verifierat | Ej verifierat i repo |
+| **Vercel** | Hosting, `@vercel/analytics/next` | Analytics beskrivs som cookielös i Vercels standardimplementation, men ingår inte i cookie-consent-flödet (se gap-registret). Hosting-lagret ser all trafik. | Fluid Compute-regioner konfigurerade mot `fra1` (Frankfurt) enligt CLAUDE.md | Ej verifierat i repo |
+| **Fortnox** | Bokföringsintegration (OAuth, opt-in per leverantör) | Fakturadata, ev. kundnamn/adress som skickas till bokföring | Sverige (svensk leverantör) | Ej verifierat i repo |
+| **Anthropic API** | Röstloggning/AI-tolkning av leverantörers diktat | Diktatinnehåll kan innehålla kundnamn, hästnamn, adresser i fri text | Ej verifierat -- redan flaggat som integritets-/GDPR-relevant i `docs/feature-flags/portfolio-audit.md:104` | Ej verifierat i repo |
+| **Apple (APNs)** | Push-notiser till iOS-appen | Enhetstoken (`DeviceToken`) **och innehåll**: `MessageNotifier.ts` skickar avsändarnamn och ett utdrag (80 tecken) av meddelandetext i push-payloaden för nya meddelanden (`PushDeliveryService.sendAPNs`) -- se gap-registret | Apple-infrastruktur (global) | Ej verifierat i repo -- Apple Developer-avtalet täcker normalt APNs som en del av standardvillkoren |
+
+**Ej en subprocessor (klargörande):** Ingen SMS-tjänst (t.ex. Twilio) hittades i
+kodbasen -- om en sådan läggs till i framtiden ska den läggas till i detta register
+innan den tas i drift.
+
+## Hur registret hålls uppdaterat
+
+Lägg till en rad i registret **innan** en ny extern tjänst som hanterar
+personuppgifter tas i produktionsbruk (nytt SDK, ny env-variabel för en
+tredjepartsnyckel, ny webhook-mottagare). Detta är i linje med Art. 25
+(inbyggt dataskydd) -- bedömningen ska göras i samband med implementation,
+inte efterhand.
+
+## Källor och metod
+
+Identifierat genom grep i `package.json`, `.env.example` och `src/lib/` efter
+kända integrationsmönster (API-nycklar, SDK-importer, webhook-routes). Ingen
+extern leverantörsdokumentation (t.ex. respektive bolags DPA-sida) har lästs
+som en del av detta arbete -- DPA-status är därför konsekvent markerad som
+"ej verifierat i repo" snarare än gissad.
+
+## Ändringslogg
+
+| Datum | Ändring |
+|-------|---------|
+| 2026-08-09 | Första versionen. Del av GDPR-dokumentationspaketet (se `docs/agent-operations/`). |