diff --git a/docs/agents/plan/2026-03-04-us1-create-event.md b/docs/agents/plan/2026-03-04-us1-create-event.md
new file mode 100644
index 0000000..4542c41
--- /dev/null
+++ b/docs/agents/plan/2026-03-04-us1-create-event.md
@@ -0,0 +1,1152 @@
+---
+date: 2026-03-04T21:41:13+00:00
+git_commit: 91e566efea0cbf53ba06a29b63317b7435609bd8
+branch: master
+topic: "US-1: Create an Event"
+tags: [plan, us-1, event-creation, full-stack]
+status: approved
+---
+
+# US-1: Create an Event — Implementation Plan
+
+## Overview
+
+Implement the first user story end-to-end: an organizer creates an event with title, description, date/time, location, and expiry date. The server stores the event, returns event token + organizer token. The frontend stores tokens in localStorage and redirects to a stub event page. This is the first vertical slice through all layers of the hexagonal architecture.
+
+Additionally: remove the scaffolding health endpoint from the OpenAPI spec (replaced by Spring Actuator), establish RFC 9457 error handling, set the app's visual design foundation (mobile-first, app-native feel), and clean up Vue scaffold defaults.
+
+## Current State Analysis
+
+- All setup tasks (T-1 through T-5) complete
+- Hexagonal architecture skeleton with ArchUnit enforcement — all business packages empty
+- OpenAPI spec contains only a scaffolding `/health` endpoint (to be removed)
+- Liquibase with empty baseline migration
+- Testcontainers PostgreSQL for integration tests
+- Vue 3 SPA with placeholder routes and openapi-fetch client
+- Dockerfile HEALTHCHECK correctly uses `/actuator/health` (not the custom endpoint)
+- No domain code, no persistence, no controllers exist yet
+
+### Key Discoveries:
+- `WebConfig.java:19` — API prefix `/api` applied to all `@RestController` beans
+- `WebConfig.java:23-39` — SPA fallback routes non-API requests to `index.html`
+- `application.properties:4` — JPA `ddl-auto=validate` (Liquibase manages schema)
+- OpenAPI generator: `interfaceOnly=true`, `useBeanValidation=true`, packages `de.fete.adapter.in.web.api` / `.model`
+- Frontend types generated via `npm run generate:api` into `src/api/schema.d.ts`
+- Dockerfile HEALTHCHECK (`Dockerfile:25-26`) uses `/actuator/health` — safe to remove custom health endpoint
+
+## Desired End State
+
+After this plan is complete:
+
+1. `POST /api/events` accepts a JSON body with title, description, dateTime, location, expiryDate
+2. Server validates input (Bean Validation), creates the event with two UUIDs (event token, organizer token), persists to PostgreSQL, returns both tokens
+3. Error responses follow RFC 9457 Problem Details format with field-level validation errors
+4. Frontend shows a mobile-first event creation form at `/create`
+5. On success, localStorage stores organizer token + event metadata, browser redirects to `/events/:token` (stub page)
+6. Root page `/` shows a minimal landing with "Create Event" button
+7. The scaffolding health endpoint is removed from the OpenAPI spec
+8. Vue scaffold defaults are cleaned up
+9. All tests pass (backend unit + integration, frontend unit, ArchUnit, Checkstyle)
+
+### UI Mockups
+
+#### Root Page `/` (Mobile)
+```
+┌─────────────────────────────┐
+│ │
+│ ┌─────────────────────┐ │
+│ │ fete │ │
+│ └─────────────────────┘ │
+│ │
+│ │
+│ No events yet. │
+│ Create your first one! │
+│ │
+│ ┌─────────────────────┐ │
+│ │ + Create Event │ │
+│ └─────────────────────┘ │
+│ │
+│ │
+└─────────────────────────────┘
+```
+
+#### Create Event `/create` (Mobile)
+```
+┌─────────────────────────────┐
+│ ← Create │
+│ │
+│ ┌─────────────────────────┐│
+│ │ Title * ││
+│ │ ││
+│ └─────────────────────────┘│
+│ │
+│ ┌─────────────────────────┐│
+│ │ Description ││
+│ │ ││
+│ │ ││
+│ └─────────────────────────┘│
+│ │
+│ ┌─────────────────────────┐│
+│ │ 📅 Date & Time * ││
+│ └─────────────────────────┘│
+│ │
+│ ┌─────────────────────────┐│
+│ │ 📍 Location ││
+│ └─────────────────────────┘│
+│ │
+│ ┌─────────────────────────┐│
+│ │ 📆 Expiry Date * ││
+│ └─────────────────────────┘│
+│ │
+│ ┌─────────────────────────┐│
+│ │ Create Event ││
+│ └─────────────────────────┘│
+│ │
+└─────────────────────────────┘
+
+* = required
+Card-style inputs, rounded corners,
+generous padding, gradient background
+```
+
+#### Event Stub `/events/:token` (Mobile)
+```
+┌─────────────────────────────┐
+│ ← fete │
+│ │
+│ │
+│ ✓ Event created! │
+│ │
+│ Share this link: │
+│ ┌─────────────────────────┐│
+│ │ https://…/events/abc123 ││
+│ │ 📋 Copy ││
+│ └─────────────────────────┘│
+│ │
+│ │
+└─────────────────────────────┘
+```
+
+#### Desktop Layout
+```
+┌──────────────────────────────────────────────┐
+│ ┌──────────────────┐ │
+│ │ │ │
+│ │ (mobile view │ │
+│ │ centered, │ │
+│ │ max ~480px) │ │
+│ │ │ │
+│ └──────────────────┘ │
+│ │
+│ ← gradient background fills full width → │
+└──────────────────────────────────────────────┘
+```
+
+## What We're NOT Doing
+
+- Full event page (US-2) — only a stub with "Event created" confirmation
+- RSVP functionality (US-3)
+- Organizer view / event editing (US-4, US-5)
+- Dark/light mode toggle (US-17) — but we design CSS with future theming in mind
+- Concrete color palette selection — researched during implementation with browser tools
+- PWA manifest / service worker (US-14)
+- Event image or color theme (US-15, US-16)
+
+## Design Principles (Persistent — Applies to All Future Stories)
+
+These design decisions apply to US-1 and all subsequent frontend work:
+
+- **Mobile-first / App-native feel** — not a classic website. Think installed app, not browser page.
+- **Desktop:** centered narrow column (max ~480px), gradient background fills the rest
+- **Gradient backgrounds** as primary design language (subtle 2-3 color gradients)
+- **Card-style form fields** — rounded corners, generous padding, elevated look
+- **Typography: Sora** — contemporary geometric sans-serif with slightly rounded terminals. Self-hosted WOFF2, OFL 1.1 licensed. Source: github.com/sora-xor/sora-font. No Google Fonts CDN.
+- **Generous whitespace** — elements breathe, nothing cramped
+- **WCAG AA contrast** as baseline for all color choices
+
+### Color Palette: Electric Dusk
+
+Chosen for best balance of style, broad appeal, and accessibility (white text readable across almost the entire gradient).
+
+| Role | Hex | Description |
+|------|-----|-------------|
+| Gradient Start | `#F06292` | Pink |
+| Gradient Mid | `#AB47BC` | Purple |
+| Gradient End | `#5C6BC0` | Indigo blue |
+| Accent (CTAs/buttons) | `#FF7043` | Deep orange |
+| Text (light mode) | `#1C1C1E` | Near black |
+| Text (dark mode) | `#FFFFFF` | White |
+| Surface (light) | `#FFF5F8` | Pinkish white |
+| Surface (dark) | `#1B1730` | Deep indigo-black |
+| Card (light) | `#FFFFFF` | White |
+| Card (dark) | `#2A2545` | Muted indigo |
+
+**Primary gradient:**
+```css
+background: linear-gradient(135deg, #F06292 0%, #AB47BC 50%, #5C6BC0 100%);
+```
+
+**Usage rules:**
+- Gradient for hero/splash areas and page backgrounds — not as direct text background for body copy
+- Cards and content areas use solid surface colors with high-contrast text
+- Accent color (`#FF7043`) for primary action buttons with dark text (`#1C1C1E`)
+- White text on gradient mid/end passes WCAG AA (4.82:1 and 4.86:1)
+- White text on gradient start passes AA-large (3.06:1) — use for headings 18px+ only
+
+## Implementation Approach
+
+API-first, TDD, inside-out through the hexagonal layers. Seven phases:
+
+1. **OpenAPI Spec & Cleanup** — define the contract, remove scaffolding health endpoint
+2. **Database Migration** — create the `events` table
+3. **Domain & Ports** — `Event` model, `CreateEventUseCase`, `EventRepository`
+4. **Application Service** — `EventService` implementing the use case
+5. **Persistence Adapter** — JPA entity, Spring Data repository
+6. **Web Adapter & Error Handling** — Controller, `GlobalExceptionHandler`
+7. **Frontend** — design foundation, form, localStorage, routing, stub page
+
+---
+
+## Phase 1: OpenAPI Spec & Cleanup
+
+### Overview
+Define the `POST /events` contract in the OpenAPI spec. Remove the scaffolding `/health` endpoint and `HealthResponse` schema. Verify that nothing depends on the removed endpoint. Regenerate backend interfaces and frontend types.
+
+### Changes Required:
+
+#### [x] 1.1 Remove health endpoint from OpenAPI spec
+**File**: `backend/src/main/resources/openapi/api.yaml`
+**Changes**: Remove the `/health` path and `HealthResponse` schema. Add the `POST /events` path with request/response schemas and RFC 9457 error response schemas.
+
+```yaml
+openapi: 3.1.0
+info:
+ title: fete API
+ description: Privacy-focused event announcements and RSVPs
+ version: 0.1.0
+ license:
+ name: GPL-3.0-or-later
+ identifier: GPL-3.0-or-later
+
+servers:
+ - url: /api
+
+paths:
+ /events:
+ post:
+ operationId: createEvent
+ summary: Create a new event
+ tags:
+ - events
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreateEventRequest"
+ responses:
+ "201":
+ description: Event created successfully
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreateEventResponse"
+ "400":
+ description: Validation failed
+ content:
+ application/problem+json:
+ schema:
+ $ref: "#/components/schemas/ValidationProblemDetail"
+
+components:
+ schemas:
+ CreateEventRequest:
+ type: object
+ required:
+ - title
+ - dateTime
+ - expiryDate
+ properties:
+ title:
+ type: string
+ minLength: 1
+ maxLength: 200
+ description:
+ type: string
+ maxLength: 2000
+ dateTime:
+ type: string
+ format: date-time
+ description: Event date and time with UTC offset (ISO 8601)
+ example: "2026-03-15T20:00:00+01:00"
+ location:
+ type: string
+ maxLength: 500
+ expiryDate:
+ type: string
+ format: date
+ description: Date after which event data is deleted. Must be in the future.
+ example: "2026-06-15"
+
+ CreateEventResponse:
+ type: object
+ required:
+ - eventToken
+ - organizerToken
+ - title
+ - dateTime
+ - expiryDate
+ properties:
+ eventToken:
+ type: string
+ format: uuid
+ description: Public token for the event URL
+ organizerToken:
+ type: string
+ format: uuid
+ description: Secret token for organizer access
+ title:
+ type: string
+ dateTime:
+ type: string
+ format: date-time
+ expiryDate:
+ type: string
+ format: date
+
+ ProblemDetail:
+ type: object
+ properties:
+ type:
+ type: string
+ format: uri
+ default: "about:blank"
+ title:
+ type: string
+ status:
+ type: integer
+ detail:
+ type: string
+ instance:
+ type: string
+ format: uri
+ additionalProperties: true
+
+ ValidationProblemDetail:
+ allOf:
+ - $ref: "#/components/schemas/ProblemDetail"
+ - type: object
+ properties:
+ fieldErrors:
+ type: array
+ items:
+ type: object
+ required:
+ - field
+ - message
+ properties:
+ field:
+ type: string
+ message:
+ type: string
+```
+
+#### [x] 1.2 Remove health endpoint test reference
+**File**: `backend/src/test/java/de/fete/config/WebConfigTest.java`
+**Changes**: Verify that tests only reference `/actuator/health`, not `/api/health`. Remove or update any test that calls the generated `HealthApi` interface. The test `apiPrefixNotAccessibleWithoutIt` may need to be adapted to use the new `/events` endpoint instead.
+
+#### [x] 1.3 Regenerate backend and frontend types
+**Action**: Run `cd backend && ./mvnw compile` and `cd frontend && npm run generate:api` to verify generation succeeds with the new spec. The old `HealthApi.java` and `HealthResponse.java` in `target/` will be replaced by `EventsApi.java`, `CreateEventRequest.java`, `CreateEventResponse.java`, etc.
+
+### Success Criteria:
+
+#### Automated Verification:
+- [ ] `cd backend && ./mvnw compile` succeeds — generates `EventsApi` interface, request/response models
+- [ ] `cd frontend && npm run generate:api` succeeds — `schema.d.ts` contains event types
+- [ ] `cd backend && ./mvnw test` passes — no test references removed health endpoint
+- [ ] No `HealthApi.java` or `HealthResponse.java` in generated output
+
+#### Manual Verification:
+- [ ] OpenAPI spec is valid (no syntax errors)
+- [ ] Generated `EventsApi` interface has `createEvent` method with correct signature
+
+**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
+
+---
+
+## Phase 2: Database Migration
+
+### Overview
+Create the `events` table via a Liquibase migration. The table stores all event fields plus both UUID tokens and a `created_at` audit timestamp.
+
+### Changes Required:
+
+#### [x] 2.1 Create Liquibase migration
+**File**: `backend/src/main/resources/db/changelog/001-create-events-table.xml`
+**Changes**: New migration file.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### [x] 2.2 Include migration in master changelog
+**File**: `backend/src/main/resources/db/changelog/db.changelog-master.xml`
+**Changes**: Add include for the new migration file after the baseline.
+
+```xml
+
+```
+
+### Success Criteria:
+
+#### Automated Verification:
+- [ ] `cd backend && ./mvnw test` passes — Testcontainers spins up PostgreSQL, Liquibase runs migration, Hibernate validates schema
+- [ ] Index on `event_token` exists (primary lookup path for public access)
+- [ ] Index on `expiry_date` exists (cleanup job query path, US-12)
+
+#### Manual Verification:
+- [ ] Migration file uses correct PostgreSQL types (`timestamptz`, `date`, `uuid`, `bigserial`)
+- [ ] Column constraints match domain rules (title not null, description nullable, etc.)
+
+**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
+
+---
+
+## Phase 3: Domain Model & Ports
+
+### Overview
+Create the `Event` domain entity and the inbound/outbound port interfaces. The domain layer has zero dependencies on Spring or adapters (enforced by ArchUnit).
+
+### Changes Required:
+
+#### [x] 3.1 Event domain model
+**File**: `backend/src/main/java/de/fete/domain/model/Event.java`
+**Changes**: New file. Plain Java class — no JPA annotations, no Spring dependencies.
+
+```java
+package de.fete.domain.model;
+
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+import java.util.UUID;
+
+public class Event {
+
+ private Long id;
+ private UUID eventToken;
+ private UUID organizerToken;
+ private String title;
+ private String description;
+ private OffsetDateTime dateTime;
+ private String location;
+ private LocalDate expiryDate;
+ private OffsetDateTime createdAt;
+
+ // Constructor, getters, setters (or builder pattern if warranted)
+}
+```
+
+#### [x] 3.2 CreateEventUseCase inbound port
+**File**: `backend/src/main/java/de/fete/domain/port/in/CreateEventUseCase.java`
+**Changes**: New file. Interface with a command object.
+
+```java
+package de.fete.domain.port.in;
+
+import de.fete.domain.model.Event;
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+
+public interface CreateEventUseCase {
+
+ Event createEvent(CreateEventCommand command);
+
+ record CreateEventCommand(
+ String title,
+ String description,
+ OffsetDateTime dateTime,
+ String location,
+ LocalDate expiryDate
+ ) {}
+}
+```
+
+#### [x] 3.3 EventRepository outbound port
+**File**: `backend/src/main/java/de/fete/domain/port/out/EventRepository.java`
+**Changes**: New file. Interface — only the `save` method needed for US-1. `findByEventToken` added for the stub page route (minimal, but needed for redirect verification in later stories).
+
+```java
+package de.fete.domain.port.out;
+
+import de.fete.domain.model.Event;
+import java.util.Optional;
+import java.util.UUID;
+
+public interface EventRepository {
+
+ Event save(Event event);
+
+ Optional findByEventToken(UUID eventToken);
+}
+```
+
+### Success Criteria:
+
+#### Automated Verification:
+- [ ] `cd backend && ./mvnw test` passes — ArchUnit validates:
+ - `Event` in `domain.model` has no Spring/adapter dependencies
+ - `CreateEventUseCase` and `EventRepository` are interfaces
+ - No dependency violations between layers
+- [ ] `cd backend && ./mvnw checkstyle:check` passes
+
+#### Manual Verification:
+- [ ] Domain model fields match the database schema from Phase 2
+- [ ] `CreateEventCommand` record contains only the fields the organizer provides (no tokens, no id, no createdAt)
+
+**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
+
+---
+
+## Phase 4: Application Service
+
+### Overview
+Implement `EventService` — the use case implementation that validates the expiry date, generates UUID tokens, builds the domain model, and delegates to the repository.
+
+### Changes Required:
+
+#### [x] 4.1 EventService
+**File**: `backend/src/main/java/de/fete/application/service/EventService.java`
+**Changes**: New file. Implements `CreateEventUseCase`. Spring `@Service` annotation.
+
+```java
+package de.fete.application.service;
+
+import de.fete.domain.model.Event;
+import de.fete.domain.port.in.CreateEventUseCase;
+import de.fete.domain.port.out.EventRepository;
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+import java.util.UUID;
+import org.springframework.stereotype.Service;
+
+@Service
+public class EventService implements CreateEventUseCase {
+
+ private final EventRepository eventRepository;
+
+ public EventService(EventRepository eventRepository) {
+ this.eventRepository = eventRepository;
+ }
+
+ @Override
+ public Event createEvent(CreateEventCommand command) {
+ if (!command.expiryDate().isAfter(LocalDate.now())) {
+ throw new ExpiryDateInPastException(command.expiryDate());
+ }
+
+ var event = new Event();
+ event.setEventToken(UUID.randomUUID());
+ event.setOrganizerToken(UUID.randomUUID());
+ event.setTitle(command.title());
+ event.setDescription(command.description());
+ event.setDateTime(command.dateTime());
+ event.setLocation(command.location());
+ event.setExpiryDate(command.expiryDate());
+ event.setCreatedAt(OffsetDateTime.now());
+
+ return eventRepository.save(event);
+ }
+}
+```
+
+#### [x] 4.2 ExpiryDateInPastException
+**File**: `backend/src/main/java/de/fete/application/service/ExpiryDateInPastException.java`
+**Changes**: New file. Domain-level exception for the business rule "expiry date must be in the future".
+
+```java
+package de.fete.application.service;
+
+import java.time.LocalDate;
+
+public class ExpiryDateInPastException extends RuntimeException {
+
+ private final LocalDate expiryDate;
+
+ public ExpiryDateInPastException(LocalDate expiryDate) {
+ super("Expiry date must be in the future: " + expiryDate);
+ this.expiryDate = expiryDate;
+ }
+
+ public LocalDate getExpiryDate() {
+ return expiryDate;
+ }
+}
+```
+
+#### [x] 4.3 Unit tests for EventService
+**File**: `backend/src/test/java/de/fete/application/service/EventServiceTest.java`
+**Changes**: New file. Unit tests with a mocked `EventRepository`.
+
+Test cases:
+- Happy path: valid command → event saved with generated tokens, createdAt set
+- Expiry date today → `ExpiryDateInPastException`
+- Expiry date in the past → `ExpiryDateInPastException`
+- Expiry date tomorrow → succeeds
+- Event token and organizer token are different UUIDs
+- Repository `save` is called exactly once
+
+### Success Criteria:
+
+#### Automated Verification:
+- [ ] `cd backend && ./mvnw test` passes — all `EventServiceTest` tests green
+- [ ] `cd backend && ./mvnw checkstyle:check` passes
+- [ ] ArchUnit: `EventService` in `application.service` may depend on `domain.model` and `domain.port` but not on adapters
+
+#### Manual Verification:
+- [ ] Business rule enforced: expiry date must be strictly after today
+- [ ] UUID generation uses `UUID.randomUUID()` (v4, non-guessable)
+
+**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
+
+---
+
+## Phase 5: Persistence Adapter
+
+### Overview
+Implement the JPA entity and Spring Data repository that fulfill the `EventRepository` outbound port. The adapter translates between the domain model and the JPA entity.
+
+### Changes Required:
+
+#### [x] 5.1 JPA Entity
+**File**: `backend/src/main/java/de/fete/adapter/out/persistence/EventJpaEntity.java`
+**Changes**: New file. JPA entity mapping to the `events` table.
+
+```java
+package de.fete.adapter.out.persistence;
+
+import jakarta.persistence.*;
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+import java.util.UUID;
+
+@Entity
+@Table(name = "events")
+public class EventJpaEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "event_token", nullable = false, unique = true)
+ private UUID eventToken;
+
+ @Column(name = "organizer_token", nullable = false, unique = true)
+ private UUID organizerToken;
+
+ @Column(nullable = false, length = 200)
+ private String title;
+
+ @Column(length = 2000)
+ private String description;
+
+ @Column(name = "date_time", nullable = false)
+ private OffsetDateTime dateTime;
+
+ @Column(length = 500)
+ private String location;
+
+ @Column(name = "expiry_date", nullable = false)
+ private LocalDate expiryDate;
+
+ @Column(name = "created_at", nullable = false)
+ private OffsetDateTime createdAt;
+
+ // Getters and setters
+}
+```
+
+#### [x] 5.2 Spring Data Repository
+**File**: `backend/src/main/java/de/fete/adapter/out/persistence/EventJpaRepository.java`
+**Changes**: New file. Spring Data JPA interface.
+
+```java
+package de.fete.adapter.out.persistence;
+
+import java.util.Optional;
+import java.util.UUID;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface EventJpaRepository extends JpaRepository {
+
+ Optional findByEventToken(UUID eventToken);
+}
+```
+
+#### [x] 5.3 Persistence Adapter (Port Implementation)
+**File**: `backend/src/main/java/de/fete/adapter/out/persistence/EventPersistenceAdapter.java`
+**Changes**: New file. Implements `EventRepository` port, translates between domain model and JPA entity.
+
+```java
+package de.fete.adapter.out.persistence;
+
+import de.fete.domain.model.Event;
+import de.fete.domain.port.out.EventRepository;
+import java.util.Optional;
+import java.util.UUID;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class EventPersistenceAdapter implements EventRepository {
+
+ private final EventJpaRepository jpaRepository;
+
+ public EventPersistenceAdapter(EventJpaRepository jpaRepository) {
+ this.jpaRepository = jpaRepository;
+ }
+
+ @Override
+ public Event save(Event event) {
+ EventJpaEntity entity = toEntity(event);
+ EventJpaEntity saved = jpaRepository.save(entity);
+ return toDomain(saved);
+ }
+
+ @Override
+ public Optional findByEventToken(UUID eventToken) {
+ return jpaRepository.findByEventToken(eventToken).map(this::toDomain);
+ }
+
+ private EventJpaEntity toEntity(Event event) { /* field mapping */ }
+ private Event toDomain(EventJpaEntity entity) { /* field mapping */ }
+}
+```
+
+#### [x] 5.4 Integration test
+**File**: `backend/src/test/java/de/fete/adapter/out/persistence/EventPersistenceAdapterTest.java`
+**Changes**: New file. Integration test with Testcontainers.
+
+Test cases:
+- Save event → returns event with generated ID
+- Save event → `findByEventToken` returns same event
+- `findByEventToken` with unknown UUID → empty Optional
+- All fields round-trip correctly (especially `OffsetDateTime` ↔ `timestamptz` and `LocalDate` ↔ `date`)
+
+### Success Criteria:
+
+#### Automated Verification:
+- [ ] `cd backend && ./mvnw test` passes — persistence integration tests green
+- [ ] `cd backend && ./mvnw checkstyle:check` passes
+- [ ] ArchUnit: persistence adapter does not depend on web adapter
+- [ ] Hibernate schema validation passes (JPA entity matches Liquibase migration)
+
+#### Manual Verification:
+- [ ] JPA entity column mappings match Liquibase migration exactly
+- [ ] Domain ↔ JPA entity mapping is complete (no fields missed)
+
+**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
+
+---
+
+## Phase 6: Web Adapter & Error Handling
+
+### Overview
+Implement the REST controller (generated `EventsApi` interface) and the `GlobalExceptionHandler` for RFC 9457 Problem Details. Remove or update any remaining health endpoint references in tests.
+
+### Changes Required:
+
+#### [x] 6.1 GlobalExceptionHandler
+**File**: `backend/src/main/java/de/fete/adapter/in/web/GlobalExceptionHandler.java`
+**Changes**: New file. Handles all exceptions consistently as RFC 9457 Problem Details.
+
+```java
+package de.fete.adapter.in.web;
+
+import de.fete.application.service.ExpiryDateInPastException;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import org.springframework.http.*;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
+
+ @Override
+ protected ResponseEntity