# Felhasználókezelés és a jogosultság-modell érvényesítése

**Dokumentum-típus:** feature-specifikáció (fejlesztői, Claude Code-barát)
**Modul:** Admin felület → Beállítások → Felhasználók (`/settings/users`) + meghívó-beváltó oldal (publikus SPA-route)
**Verzió:** v2.0
**Dátum:** 2026-08-03
**Státusz:** design-körben (v1)
**Cél olvasó:** senior fejlesztő + Claude Code
**Épít:** `00_domain_model.md`, `01_kozos_mintak.md`, `platform/spec_dontesnaplo.md`,
`00_terminologia.md`, `05_jogosultsag_es_authorization.md`, `05_mailer.md`,
`CLAUDE.md`, valamint a 0. szakasz `_bemenet/`-forrásai

> **Mire való ez a fájl.** Ez a feature-spec a városgazdálkodás belső
> felhasználóinak kezelését (`/settings/users`) és a **meghívó-flow-t**
> specifikálja fejlesztői mélységben. A v2.0 a 2026-08-02-i termékfunkció-kör
> (felhasználó-meghívás és -kezelés) és a fejlesztői kérdés-kör
> (`FK-felh-1..15`) nagy-verziós átvezetése: a spec **a `/v1/tenant-users`
> kód-valóságra épül** (a korábbi `/v1/users/*` meghívó-készlet törölve — a
> `platform/spec_kod_elteresek.md` #67 zárása), és a meghívó-flow-t a
> jelszó nélküli staff-belépés (magic-link + social) világára tervezi.
> A jogosultság-érvényesítés platform-szintű mintáját nem ez a fájl
> tartalmazza — az a `01_kozos_mintak.md` 8. szakaszában él; ez a spec oda
> hivatkozik. A `CLAUDE.md` hézag-kezelési konvencióit követi.
>
> **Implementált vs. új.** A lista/adatlap-réteg (list, get, PATCH basic,
> PATCH roles, deactivate, reactivate) a kódban **él** — ezekre a spec a
> kód-valóságot kanonizálja (a lezárt `apps/manager/spec_kod_elteresek.md`
> #77–#86 eltérések beemelésével), és csak a jelölt pontokon ír elő
> változást (SD-130 grant-sync, SD-134 e-mail-mező). A meghívó-flow (invite,
> resend, revoke, accept + beváltó-oldal) **új fejlesztés**.

---

## 0. Funkcionális alap

**Forrás-lista (a spec ezekből dolgozik):**

- `_bemenet/termekfunkcio/manager_felulet_felhasznalo_meghivas/manager_felulet_felhasznalo_meghivas_v1.md`
  — funkcionális terv **v1.1, 2026-08-02** (D-1–D-4 alapítói döntések,
  SZ-1–SZ-12 szabályok, E-1–E-3 e-mail-leltár, KJ-1–KJ-5 kanonikus jelöltek).
- `_bemenet/termekfunkcio/manager_felulet_felhasznalo_meghivas/manager_felulet_felhasznalo_meghivas_atadas.md`
  — átadás-memó, **2026-08-02**.
- `_bemenet/termekfunkcio/manager_felulet_felhasznalo_meghivas/FEJLESZTOI_KERDESEK.md`
  — kör-indító fejlesztői kérdés-fájl (`FK-felh-1..15`), a válaszokkal
  (**2026-08-03**). A blokkoló válaszok döntés-értékű tartalma SD-125 —
  SD-134-ként rögzült.

**Érintett kanonikus és platform-döntések.** K-007 (egységes Urbino-brand),
K-016 (durva szemcsés szerepkör-szint), K-024 (Urbino-admin külön felület),
K-025 (szerepkör-unió), K-027 (szerepkör-érzékeny landing), K-030/K-036
(törlés-tilalmi minta); KJ-1–KJ-5 (kanonikus jelöltek — K-számuk még nincs).
SD-34 – SD-39 (a meghívó-flow v1-alapdöntései), SD-120 – SD-123 (auth-keret,
`user` szerepkör, széles `TenantUser`-tükör), és az e körben született
**SD-125 – SD-134** (`platform/spec_dontesnaplo.md`).

**Mit döntött már el a funkcionális réteg.** A meghívás-alapú modell (nincs
nyílt regisztráció, nincs domain-join — KJ-1); egy ember = egy fiók (D-1); a
meghívás/kezelés admin-jog (D-2 — T0-leképezését az SD-126 adja); közös
meghívó-mechanizmus a leendő Urbino-adminnal (D-3); a Zitadel a tenant felé
láthatatlan, minden kimenő felület/levél Urbino-brandű és magyar (D-4); a
7 napos, egyszer használatos token; a Mind/Aktív/Meghívva/Letiltva
lista-modell; törlés helyett letiltás + utolsó-admin-védelem; az Urbino-hang
visszafogott tegeződése (KJ-5). A jogosultság-modell rétege (szerepkör-keret,
akció-mátrix, landing) a v1.0 óta változatlan forrásokból él
(`05_jogosultsagok_v2.md`, `00_architektura_v4.md` — l.
`_folyamat/kulso_forrasok.md`).

**Mit tölt ki EZ a spec.** A meghívó-flow teljes fejlesztői specifikációját a
`/v1/tenant-users` valóságra: entitás-delták (`UserInvitation.roles`, a
`TenantUser` meghívó-tükör-mezői), a három állapotgép, az API-szerződés
(invite/resend/revoke/preview/accept), a kétfázisú Core→Tenant írás
(SD-132), a beváltó-oldal folyamata, a Mailer-integráció, a letiltás
grant-hatálya (SD-130), a lista/adatlap v2-képe és az acceptance criteria.

---

## 1. Cél és hatókör

A feature lehetővé teszi, hogy a vezető (tenant-admin — SD-126 szerint a
`tenant_manager`) a saját tenantján önállóan hívja meg és kezelje a
városgazdálkodás munkatársait: meghívót küldjön, szerepkört és
csoport-tagságot rendeljen, letiltson és feloldjon — fejlesztői/Urbino-oldali
beavatkozás nélkül. Az üzleti tét: ma minden felhasználó-felvétel kézi
Zitadel-konzolos munka (`platform/spec_kod_elteresek.md` #67 — nincs
kontrollált onboarding-út); a Sub-csomag önkiszolgáló ígérete ezen a
funkción áll.

**Hatókörön kívül (ennél a feature-nél):**

- **Urbino-admin funkciók** — tenant-felvétel, billing (K-024). A leendő
  Urbino-admin felület ugyanezt a meghívó-mechanizmust használja majd (D-3) —
  a végpontok ezért `admin` szerepkörrel is hívhatók, de az Urbino-admin
  felület maga külön spec.
- **Polgári fiók-életút** — a polgári regisztráció/belépés a
  `00_polgari_webes_funnel.md` és a `06_polgari_api_szerzodes.md` világa. E
  spec csak ott érinti, ahol a meghívott e-mail már létező (akár polgári)
  Core `User` (SZ-4/SZ-5).
- **Jelszó-flow-k** — nincsenek (SD-128): a staff-belépés magic-linkes vagy
  social; jelszavas belépés, jelszó-reset, 2FA nem része a rendszernek.
- **Csoport-CRUD** — a `Group` kezelése a `/settings/groups` feature-é. E
  spec a csoport-tagságot a meghívó-dialógusban írja (SD-132) és az
  adatlapon olvasásra mutatja.
- **A `Tenant`-alapadatok** (`/settings/general`) — külön feature
  (`30_beallitasok.md`).

---

## 2. Domain-modell

A feature négy meglévő entitásra épül (`User`, `UserTenantRole`,
`TenantUser`, `UserInvitation`) — mindet a `00_domain_model.md` tartalmazza,
ez a spec **hivatkozza** és a v2-deltákat rögzíti; a domain-modell a deltákat
ugyanebben a körben átveszi (v2.10).

### 2.1 Áttekintés — mi új, mi módosul

| Elem | Státusz | DB-szint |
|---|---|---|
| `User` | meglévő — a `status` szemantika pontosítva (2.5) | Core DB |
| `UserTenantRole` | meglévő — a kötés-létrehozás időzítése változik (SD-129) | Core DB |
| `TenantUser` | meglévő — **+2 meghívó-tükör-mező** (`inviteExpiresAt`, `inviteRoles`); a `status` tenant-viszony-szemantikája (SD-130); bool→enum migráció (SD-37) | Tenant DB |
| `UserInvitation` | meglévő entitás, **+1 mező** (`roles`) | Core DB |
| `InvitationStatus` enum | meglévő — `Pending` / `Accepted` / `Expired` / `Revoked`, változatlan | — |
| `UserStatus` enum | meglévő — `Invited` / `Active` / `Disabled`, változatlan értékkészlet | — |
| `TenantRole` enum | meglévő — `manager` / `dispatcher` / `content_manager` / `field_worker` / `user` (SD-123); **nem bővül** (SD-126) | — |

### 2.2 Módosítás — `UserInvitation` (+`roles`)

A `UserInvitation` (Core DB, `AuditableEntity`, `00_domain_model.md` 3.5)
mezőkészlete egy mezővel bővül:

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `roles` | `TenantRole[]` | K | **ÚJ (SD-129).** A meghívással szánt szerepkör-készlet — legalább egy elem, csak munkatársi érték (`user` tiltott). A `UserTenantRole`-sorok a **beváltáskor** ebből jönnek létre. Tárolási forma (PG-tömb vagy join-tábla) fejlesztői döntés (NY-4 analóg) |

A token-mechanika a MagicAuth kód-mintáját követi (kriptográfiai véletlen,
legalább 32 byte; SHA-256 hash a `tokenHash`-ben; egyszer használatos;
`expiresAt = createdAt + 7 nap`, SD-35). **A nyers token kizárólag a
meghívó-e-mail linkjébe kerül** — DB-ben, logban, válasz-DTO-ban soha (a
MagicAuth mai INFO-szintű token-logolása itt tiltott anti-minta — AC-K3).

> **Tervezési döntés — a szerepkör-kötés a beváltáskor (SD-129).** A v1-spec
> a `UserTenantRole`-sorokat a meghíváskor hozta létre. Ez a jelszó nélküli,
> „a polgár is `TenantUser`" világban (SD-123) hibás lenne: egy **meglévő,
> aktív** fiók (pl. a település egy polgára) a meghívás pillanatában — az
> elfogadás előtt — megkapná a staff-szerepkör-grantet, és beléphetne vele
> (SZ-8-sértés). Ezért a szánt készletet a meghívó hordozza (`roles`), a
> kötés + Zitadel-grant a beváltás megerősítésekor jön létre. Az SD-38 (1)
> invariáns ehhez igazodik: *aktív* tag nem maradhat szerepkör nélkül; a
> meghívottnál az invariánst a `roles` kötelezősége adja.

> **Csoport-tagság nem az invitation-ön.** A meghíváskor megadott
> csoport-tagság azonnal `GroupMember`-sorként íródik a tenant DB-be
> (SD-132, 3.3) — a csoport-tagságnak nincs jogosultsági hatása, a „látszik,
> hogy jön valaki" élményhez viszont kell. A `UserInvitation` ezért nem
> tárol csoport-listát; visszavonáskor a `GroupMember`-sorok törlődnek.

### 2.3 Módosítás — `TenantUser` (+ meghívó-tükör, státusz-szemantika)

A `TenantUser` (Tenant DB, a Core `User` széles, csak-olvasható tükre —
`00_domain_model.md` 2.3) két tükör-mezővel bővül, és a `status`
szemantikája pontosul:

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `inviteExpiresAt` | `DateTime?` (UTC) | O | **ÚJ (SD-132).** Az e tenanton élő, még be nem váltott meghívó lejárati időbélyege; `null`, ha nincs függő meghívó. A kétfázisú írás tartja karban (invite/resend állítja, revoke/accept nullázza). A „Meghívva" lista-állapot, a „hamarosan lejár" (< 48 óra) és a „lejárt" (`< now`) jelvény **lokálisan, Core-lekérés nélkül** ebből számolódik |
| `inviteRoles` | `TenantRole[]` | O | **ÚJ (SD-132).** A függő meghívó szánt szerepkör-készletének replikája (a lista Szerepkörök-oszlopa a Meghívva-soron ezt mutatja — a `roles` replika beváltásig üres). Üres/`null`, ha nincs függő meghívó |

**A `status` szemantikája (SD-130 pontosítás):** a `TenantUser.status` a
**tenant-viszony** állapota, nem a Core-fiók tükre:

| Érték | Jelentése ezen a tenanton |
|---|---|
| `Invited` | A sor egy még sehol nem aktivált, meghívás-létrehozta fiókhoz tartozik (Core `User.status = Invited`) |
| `Active` | Élő fiók, ezen a tenanton nincs letiltva. **Meglévő aktív fiók függő staff-meghívója alatt is `Active`** — a „Meghívva" lista-állapotot az `inviteExpiresAt` adja, nem a `status` |
| `Disabled` | **Ebből a tenantból** kizárt felhasználó (SD-130 — a letiltás tenant-hatókörű; a Core `User.status`-t nem írja) |

A kódbeli `tenant_user.active` bool → `status` enum migráció az SD-37
végrehajtása (fejlesztési feladat, egyszeri migrációval: `active=true` →
`Active`, `active=false` → `Disabled`).

### 2.4 A meglévő `User` / `UserTenantRole` — hivatkozás és pontosítás

A `User` (`00_domain_model.md` 3.1) és a `UserTenantRole` (3.3) mezőkészlete
nem változik. Két szemantika-pontosítás:

- **`User.status`:** az `Invited → Active` átmenet a fiók-életút (beváltás);
  a **tenant-letiltás nem írja** (az a `TenantUser.status`-on él, SD-130). A
  `Disabled` Core-érték a globális fiók-tiltás tartaléka (Urbino-admin
  hatáskör, iteráció) — e feature nem állítja be.
- **`User.email`:** max **254** karakter (RFC 5321) — a v1.0 `VF-felh-1`
  jelzés lezárása, a domain-modell v2.10 átveszi. Egyedi a Core-ban;
  kisbetűsítve-trimmelve normalizálva tárolandó és hasonlítandó (SZ-12).
- **`User.lastActivityAt`** (kód-valóság, az enricher frissíti minden
  bejelentkezéskor — `apps/manager/spec_kod_elteresek.md` #77): a lista
  „Utoljára aktív" oszlopának forrása, a széles tükrön replikálódik.

### 2.5 Állapotgépek

A v1 egyetlen `User`-állapotgépe a v2-ben **három kis gépre** válik szét — ez
követi a kód valóságát (a letiltás ma is tenant-szintű) és a D-1 modellt (egy
fiók több tenanton él, a fiók-életút ≠ tenant-viszony).

**(a) A `User` fiók-életútja (Core):**

| Honnan | Hova | Kiváltó | Megjegyzés |
|---|---|---|---|
| — | `Invited` | Meghívás olyan e-mailre, amely még nem létezik a Core-ban | `User` létrejön (`status=Invited`, `userType=Management`, a megadott névvel); még nincs `externalAuthId`, nincs `UserTenantRole` |
| `Invited` | `Active` | A meghívó beváltása | Zitadel-fiók létrejön, `externalAuthId` kitöltődik, `emailVerified=true`; a `UserTenantRole`-sorok a meghívó `roles`-ából |
| `Invited` | *(fizikai törlés)* | A meghívó visszavonása, ha nincs más élő kötés/meghívó (SD-133) | Nem állapot-átmenet — a rekord és a tenant-lenyomatok törlődnek |

Nincs `Active → Invited` (aktivált fiók nem „hívható vissza meg"), és e
feature nem ír `Disabled` Core-értéket.

**(b) A tenant-viszony (`TenantUser.status`, tenant DB):**

| Honnan | Hova | Kiváltó | Jogosultság | Feltétel / mellékhatás |
|---|---|---|---|---|
| — | `Invited` / `Active` | Meghívás (kétfázisú írás, SD-132) | `admin`, `tenant_manager` | Új fióknál `Invited`; meglévő aktív fióknál `Active` (+ `inviteExpiresAt` mindkét esetben) |
| `Active` | `Disabled` | Letiltás | `admin`, `tenant_manager` | SD-38: nem az utolsó aktív `manager`, nem önmaga; admin-usert csak admin. Zitadel-grant-visszavonás e tenant szerepköreire (SD-130) |
| `Disabled` | `Active` | Feloldás | `admin`, `tenant_manager` | A grantek a meglévő `UserTenantRole`-sorokból helyreállnak (SD-130) — a szerepkörök a letiltás alatt is megmaradtak |
| `Invited` | *(sor törlődik)* | Visszavonás (SD-133) | `admin`, `tenant_manager` | Ha a sort a meghívás hozta létre; a `GroupMember`-sorok is törlődnek |

**(c) A `UserInvitation` életciklusa (változatlan v1-mag):** `Pending →
Accepted` (beváltás, `consumedAt`), `Pending → Expired` (lazy, a beváltási
kísérletkor — SD-35), `Pending/Expired → Revoked` (újraküldés vagy
visszavonás). `Accepted`/`Revoked` végállapot; `Expired`-ből újraküldéssel
új `Pending` meghívó születik (a régi `Revoked`-ra áll).

### 2.6 Lookup-ok

A szerepkör-választó a négy munkatársi `TenantRole`-érték (fix enum, nem
lookup-entitás); a `user` érték a staff-felületen sosem kínált és sosem
fogadott el. A csoport-választó a tenant `Group`-jainak lookupja (a meglévő
`LookupLoaderService` mintával). Default katalógus nem érintett.

---

## 3. Szerver — API és logika

A vezérelv a `01_kozos_mintak.md` 6.4 „csak az eltérést specifikáld" elve. A
teljes végpont-készlet a meglévő `TenantUserController`
(`/v1/tenant-users`) alá épül — dedikált controller, Core + tenant DbContext,
a `Tenant` headerre explicit szűréssel (SD-36 elve, SD-125 route-készlete).

### 3.1 Mi standard, mi tér el — a végpont-térkép

| Végpont | Státusz | Megjegyzés |
|---|---|---|
| `GET /v1/tenant-users/list` | meglévő — **bővül** | Meghívó-aggregátum + fül-szűrés (3.2) |
| `GET /v1/tenant-users/{id}` | meglévő | Adatlap; idegen tenant / csak-`user` sor → `404` |
| `PATCH /v1/tenant-users/{id}/basic` | meglévő — **szigorodik** | Az `email` csak `admin`-nak (SD-134); Meghívva-soron `422` (3.6) |
| `PATCH /v1/tenant-users/{id}/roles` | meglévő | SD-38 guardok + Zitadel-grant-sync (kód-valóság, #83); Meghívva-soron `422` |
| `POST /v1/tenant-users/{id}/deactivate` | meglévő — **bővül** | + Zitadel-grant-visszavonás (SD-130) |
| `POST /v1/tenant-users/{id}/reactivate` | meglévő — **bővül** | + Zitadel-grant-helyreállítás (SD-130) |
| `POST /v1/tenant-users/invite` | **ÚJ** | Meghívás-indítás — workflow, nem CRUD (3.3) |
| `POST /v1/tenant-users/{id}/resend-invite` | **ÚJ** | Újraküldés (3.5) |
| `POST /v1/tenant-users/{id}/revoke-invite` | **ÚJ** | Visszavonás + SD-133 törlés-kivétel (3.5) |
| `GET /v1/tenant-users/invitations/{token}` | **ÚJ, publikus** | Beváltó-oldali előnézet (3.4) |
| `POST /v1/tenant-users/invitations/accept` | **ÚJ, publikus** | Beváltás (3.4) |

> **Tenant-kontextus.** Az admin-oldali végpontok a `Tenant` headerből élnek
> (SD-36). A két `invitations/*` végpont **tenant-header-mentes**: a linkre
> érkező beváltó kliens nem ismer tenant-kódot — a tenant-kontextust a token
> által azonosított `UserInvitation.tenantId` adja.

> **`User.delete` továbbra sincs** (SD-39) — az SD-133 törlés-kivétel nem
> publikus delete-végpont, hanem a `revoke-invite` belső mellékhatása, szűk
> feltételekkel.

### 3.2 Lista és get — a v2-kép

**Standard `TableStateConfig`-lista a meglévő kódon**, az alábbi
eltérésekkel/bővítésekkel:

> **Eltérés a mintától — staff-szűrés + meghívott-sorok (SD-123 + v2).** A
> lista azon sorokat adja, amelyeknek (a) van legalább egy nem-`user`
> szerepkörük, **vagy** (b) függő meghívójuk van (`inviteExpiresAt` nem
> `null`). A csak-`user` (polgár) sor se listában, se `GET {id}`-n nem érhető
> el (`404`) — kivéve, ha épp függő staff-meghívója van.

> **Eltérés a mintától — származtatott lista-állapot.** A sor megjelenő
> állapota: `inviteExpiresAt != null` → **Meghívva** (aljelölés:
> `inviteExpiresAt - now < 48 óra` → „hamarosan lejár"; `< now` → „lejárt");
> egyébként a `TenantUser.status` leképezése (Aktív / Letiltva). Minden
> jelvény lokálisan számolódik — a lista-útvonal nem olvas Core DB-t.

> **Eltérés a mintától — a Szerepkörök-oszlop kettős forrása.** Aktív/letiltott
> soron a `TenantUser.roles` replika; Meghívva-soron az `inviteRoles`
> tükör-mező (a kötés még nem él — SD-129).

A fülek (Mind / Aktív / Meghívva / Letiltva) a származtatott lista-állapot
szerinti szerver-oldali szűrők (a `FilterQuery` mintában). A lejárt meghívó
a Meghívva fülben marad, „lejárt" jelvénnyel (SZ-jegyzék: a csendes eltűnés
support-hívást szül).

### 3.3 Meghívás-indítás — `POST /v1/tenant-users/invite`

> **Eltérés a mintától.** Workflow, nem CRUD: Core-írás + kétfázisú
> tenant-írás + e-mail. Dedikált service (munkanév:
> `UserInvitationService`); a konkrét osztály-struktúra fejlesztői döntés.

**Jogosultság:** `admin`, `tenant_manager`.

**Request — `InviteTenantUserRequest`:**

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `email` | `string` | K | A meghívott e-mail címe. Normalizálva (trim + kisbetű, SZ-12) hasonlítódik és tárolódik |
| `displayName` | `string` | K | Megjelenítendő név. **Csak új fiók létrehozásakor** íródik; meglévő fióknál a fiók neve marad érvényes |
| `roles` | `TenantRole[]` | K | Legalább egy munkatársi szerepkör; `user` érték tiltott; duplikátummentes |
| `groupIds` | `long[]` | O | A tenant `Group`-jainak id-i — a `GroupMember`-sorok azonnal létrejönnek (SD-132) |

**A workflow (sorrendben):**

1. **Normalizálás + ütközés-ellenőrzés az aktív tenanton:** ha az e-mail
   gazdájának van nem-`user` szerepköre → `409 already_member` (SZ-1); ha e
   tenanton `Disabled` → `409 member_disabled` (SZ-2, a felület a feloldás
   felé terel); ha függő meghívója van → `409 invite_pending` (SZ-3, a
   felület az újraküldésre terel).
2. **Sablon-kapu:** ha a szükséges `MailTemplate`-ident hiányzik, a végpont
   `422 invitation_mail_unavailable` hibával áll meg — **még a Core-írás
   előtt** (SD-131; csendes kimaradás tilos).
3. **Core-tranzakció:** e-mail-feloldás — ha nincs ilyen Core `User`, új jön
   létre (`status=Invited`, `userType=Management`, a megadott névvel); ha
   van, változatlan marad. Új `UserInvitation` (`Pending`,
   `expiresAt = now + 7 nap`, friss token, a `roles`-készlettel).
   `UserTenantRole` **nem** jön létre (SD-129).
4. **Tenant-fázis (SD-132):** `TenantUser`-projekció upsert (új fióknál
   `status=Invited`; meglévőnél a meglévő sor marad) + `inviteExpiresAt`/
   `inviteRoles` tükör + `GroupMember`-sorok a `groupIds`-ból. A projekció-
   és tükör-írás **konzisztencia-kritikus**: hibájánál a művelet egészében
   hibázik, fél-állapot nem maradhat (a mechanika — sorrend, kompenzáló
   törlés — fejlesztői döntés). A `GroupMember`-írás **best effort**: hibája
   nem dönti a meghívást — a válasz jelzi (`groupAssignmentFailed: true`), a
   tagság a Csoportok nézetben pótolható.
5. **E-mail:** E-1 (új fiók) vagy E-2 (meglévő fiók) a MailerX/Mandrill úton
   (3.7). Transzport-hiba nem görgeti vissza a tranzakciót — a meghívó a
   felületről újraküldhető (a „kiment" státusz amúgy is csak API-átadás,
   FK-felh-9).

**Válasz:** `201 Created`, a létrejött/érintett lista-sor
(`TenantUserListDto`) — **egységesen, akár új, akár meglévő fiók** (a válasz
nem árulja el, létezett-e már a Core-fiók; a különbség csak a kiküldött
e-mail-változatban él, amit a címzett lát).

**Hibakódok:**

| Kód | `reason` | Eset |
|---|---|---|
| `400` | — (`fieldErrors`) | Validáció: e-mail-formátum, üres név, üres/érvénytelen `roles`, `user`-szerepkör a kérésben |
| `403` | — | Nem `admin`/`tenant_manager` az aktív tenanton |
| `409` | `already_member` / `member_disabled` / `invite_pending` | SZ-1 / SZ-2 / SZ-3 |
| `422` | `invitation_mail_unavailable` | Hiányzó `MailTemplate`-ident (SD-131) |

### 3.4 Beváltás — előnézet és accept

A beváltó-oldal (4.5) két publikus végpontból él. Mindkettő
tenant-header-mentes; az `authorization.json`-ban explicit `"*"`-szabályt
kapnak (SD-122 szint — publikus, de JWT mellett dekorált viselkedés).

**`GET /v1/tenant-users/invitations/{token}` — előnézet:**

A nyers token alapján (hash-lookup) visszaadja, mit mutasson a beváltó-oldal.

**Válasz (`200`) — `InvitationPreviewDto`:** `tenantName` (a meghívó tenant
megjelenő neve), `displayName` (előtöltéshez), `roles` (magyar címkékhez a
kliens i18n-je fordít), `accountExists` (`bool` — van-e már aktivált fiók:
a meglévő-fiókos utat mutassa-e), `email` (a meghívott cím — a belépés-út
előtöltéséhez).

**Hibák:** `410 invitation_invalid` — nem létező, lejárt vagy visszavont
token, **egységesen** (SZ-6: a válasz nem fedi fel, létezik-e fiók a címen;
lejárt `Pending` meghívó ekkor áll `Expired`-re — lazy, SD-35);
`409 invitation_accepted` — már beváltott token (SZ-7: a kliens a
login-oldalra visz).

**`POST /v1/tenant-users/invitations/accept` — beváltás:**

**Request — `AcceptInvitationRequest`:** `token` (K); `displayName` (O —
név-pontosítás, csak új-fiókos úton értelmezett).

**Az új-fiókos út** (a meghívott `User.status=Invited`, nincs
`externalAuthId`) — JWT nélkül hívható, a token az azonosítás:

1. Token-validálás (mint az előnézetnél; lejáratnál `410`, beváltottnál `409`).
2. Ha `displayName` érkezett: a `User.displayName` frissül (a meghívott
   pontosíthatja a nevét — bemenet 5.3).
3. **Zitadel-fiók létrehozása** a Management API-n (a meglévő
   `RegisterHumanUserAsync`-minta): **véletlen jelszóval** (a Zitadel
   megköveteli, az app nem használja — SD-127/SD-128), `email.isVerified =
   true` (a link-kattintás maga a cím-birtoklás igazolása), username =
   e-mail (kód-konvenció, `apps/manager/spec_kod_elteresek.md` #85).
   `externalAuthId` kitöltődik.
4. `UserTenantRole`-sorok a `UserInvitation.roles`-ból (SD-129) +
   Zitadel-grant-sync (a meglévő `SyncProjectRolesAsync`).
5. `User.status → Active`, `emailVerified = true`; `UserInvitation →
   Accepted` + `consumedAt`; tenant-tükör frissül (`TenantUser.status →
   Active`, `roles`-replika, `inviteExpiresAt`/`inviteRoles` nullázva).
6. **Válasz `200`** — a kliens a **saját login-oldalra** irányít (SD-127): a
   meghívott ott magic-linkkel vagy social belépéssel lép be (e-mail
   előtöltve). **Az accept nem nyit sessiont** — nincs MagicAuth-os
   OIDC-finalizálás a beváltásban.

**A meglévő-fiókos út** (`accountExists=true` — SZ-4/SZ-5): az accept-hívás
**érvényes JWT-vel kötelező** (a kliens előbb friss belépésre viszi a
meghívottat a saját login-oldalon — SD-127; a friss belépés kikényszerítése
kliens-oldali folyamat-szabály, a szerver-oldali `auth_time`-kapu fejlesztői
döntés):

1. Token-validálás + **identitás-egyezés**: a JWT-hez tartozó `User.email`
   (normalizálva) egyezik a meghívott e-maillel — különben
   `403 invitation_identity_mismatch` (SZ-8; a kliens figyelmeztet és
   fiók-váltásra kényszerít — FK-felh-4c).
2. `UserTenantRole`-sorok + grant-sync + `UserInvitation → Accepted` +
   tenant-tükör frissítés (mint fent, a 3. lépés — Zitadel-fióklétrehozás —
   nélkül: a fiók már él).
3. Válasz `200` — a kliens megerősítő képernyőt mutat, majd a felületre visz.

> **Zitadel-hiba a beváltáskor.** Ha a fiók-létrehozás vagy a grant-sync
> hibázik, a tranzakció visszagördül (`User` `Invited` marad, a meghívó
> `Pending` marad), a válasz `502` `reason`-kóddal — a linkkel
> újrapróbálható (v1-elv, változatlan).

> **Melyik utat kényszeríti a szerver?** Nem a kliens jószándékán múlik: ha
> a meghívott fiók aktivált (`externalAuthId` kitöltött), a JWT-mentes
> accept-kérés `401 authentication_required` — az új-fiókos út csak
> tényleg új fióknál járható.

### 3.5 Újraküldés és visszavonás

**`POST /v1/tenant-users/{id}/resend-invite`** — jogosultság: `admin`,
`tenant_manager`. Feltétel: a sorhoz e tenanton `Pending` vagy `Expired`
meghívó tartozik (a lejárt sor is újraküldhető — bemenet 5.2). Hatás: a régi
meghívó `Revoked`; új `Pending` meghívó (friss token, `now + 7 nap`, a
**régi `roles`-készlet átemelve**); `inviteExpiresAt` tükör frissül; az
e-mail újra kimegy (sablon-kapu itt is — `422 invitation_mail_unavailable`).
Hibák: `422 no_pending_invite`, ha nincs újraküldhető meghívó; `403`.

**`POST /v1/tenant-users/{id}/revoke-invite`** — jogosultság: `admin`,
`tenant_manager`. Feltétel: mint fent. Hatás: a meghívó `Revoked`; a
`GroupMember`-sorok törlődnek; az `inviteExpiresAt`/`inviteRoles` tükör
nullázódik. **Törlés-kivétel (SD-133):** ha a `User`-t ez a meghívás hozta
létre és sosem aktivált (`status=Invited`, nincs `externalAuthId`), **és**
nincs más tenanton se szerepköre, se függő meghívója → a `User` a
meghívó-előzményeivel és a `TenantUser`-sorral együtt **fizikailag
törlődik** — a lista-sor nyomtalanul eltűnik, nincs örök maradvány (az
elgépelt e-mail útja: visszavonás + új meghívó). Ha van más élő kötése (más
tenant meghívta, vagy meglévő fiók volt): csak az e-tenant-lenyomatok
tűnnek el, a `User` marad. A meghívott a visszavonásról **nem kap
értesítést** (csendes visszavonás, bemenet 5.2).

### 3.6 A meglévő kezelő-végpontok — v2-eltérések

**`PATCH /v1/tenant-users/{id}/basic`** (kód-valóság: név/firstName/lastName/
e-mail szerkesztés Core+Zitadel+cross-tenant szinkronnal —
`apps/manager/spec_kod_elteresek.md` #82–#86, kanonizálva):

> **Eltérés — az `email`-mező `admin`-only (SD-134).** A `tenant_manager`
> hívásában az `email` kulcs nem elfogadott (a request-DTO-járól hiányzik —
> az implicit-eldobás mintája, `CLAUDE.md` 3.); az e-mail-módosítás az
> Urbino-support útja (`admin` hívóval a teljes mezőkészlet él, a meglévő
> Zitadel-username-szinkronnal). Admin-usert továbbra is csak `admin`
> módosíthat (kód-guard, marad).

> **Eltérés — Meghívva-soron nem értelmezett.** Függő meghívójú soron a
> `basic` és a `roles` PATCH egyaránt `422 invitee_pending` — az elgépelt
> adat útja a visszavonás + új meghívó (SZ-jegyzék 5.5; a szerepkör-készlet
> a meghívóban él, nem kötésként).

**`PATCH /v1/tenant-users/{id}/roles`:** változatlan kód-viselkedés
(SD-38 guardok: utolsó-`manager`-védelem, legalább egy szerepkör;
DB→Zitadel grant-sync — #83). A `user` érték e végponton nem adható és nem
vehető el (a polgári tagság a polgári flow-é, SD-123).

**`POST .../deactivate`:**

> **Eltérés — grant-hatály (SD-130).** A letiltás a `TenantUser.status →
> Disabled` írás mellett **visszavonja a Zitadel-graneket e tenant
> szerepköreire** (a meglévő grant-sync mechanizmussal). A
> `UserTenantRole`-sorok **megmaradnak** (a feloldás visszaadja a korábbi
> szerepköröket — bemenet 6.). Nincs app-oldali Active-kapu és nincs
> session-revokáció: a már kiadott access token a lejártáig (perces
> nagyságrend) él — a hatály a következő token-frissítésnél áll be
> (FK-felh-5; a `01_kozos_mintak.md` 3.5 eventual-consistency elve).
> Guardok (kód-valóság, marad): `last_manager`, `cannot_deactivate_self`,
> admin-usert csak admin.

**`POST .../reactivate`:** `TenantUser.status → Active` + a grantek
helyreállítása a meglévő `UserTenantRole`-sorokból (SD-130). A v1
„reaktiválás új meghívót igényel" ága okafogyott: sosem aktivált fiók nem
kerülhet `Disabled`-be (2.5 — a Meghívva-sor útja a visszavonás).

### 3.7 Meghívó-levelek — Mailer-integráció (SD-131)

A meghívó-levelek a meglévő **MailerX/Mandrill** úton mennek (`05_mailer.md`;
a `magic-auth` dispatch-minta: Transactional queue, Handlebars-változók,
Hangfire-retry 3 kísérlettel).

| Levél | `MailTemplate.ident` | Mikor | Változók |
|---|---|---|---|
| E-1 — meghívó új fióknak | `user-invite-new` | invite/resend, ha a fiók még nem aktivált | `inviterName`, `tenantName`, `roleNames`, `inviteeName`, `acceptUrl`, `expiresAtText` |
| E-2 — meghívó meglévő fióknak | `user-invite-existing` | invite/resend, ha a fiók aktivált | ugyanaz |

- **Hiányzó ident = végpont-hiba** (`422 invitation_mail_unavailable`) — a
  MagicAuth csendes-kimaradás mintája (warning-log + return) itt tiltott
  (AC-K1). A sablonok éles felvétele deploy-lépés (fejlesztői hatáskör,
  FK-felh-9-cel együtt).
- Az `acceptUrl` a manager SPA beváltó-oldalára mutat, a nyers tokennel
  (URL-forma fejlesztői döntés; a token log-védelme itt is áll).
- A levél-copy a bemenet 7.1–7.2 hangnem-szabályait követi (Urbino a feladó,
  a város a tartalom; „Kedves + teljes név"; tegező, marketing-zaj nélkül;
  a meghívó nevesíti a meghívót). Az E-1 illusztratív szövegéből a
  **jelszó-mondat kimarad** (SD-128 — VF-A); a végleges copy a fejlesztéssel
  párhuzamosan rögzül (átadás-memó). E-3 (jelszó-reset) **nincs** (SD-128).
- **Platform-tény (SD-131):** a Zitadel semmilyen körülmények között nem
  küld levelet végfelhasználónak (D-4). A fejlesztés első lépése ennek
  ellenőrzése/beállítása az instance-en (8.3).
- Amit tudatosan nem küldünk (bemenet 7.3): lejárat-emlékeztető,
  visszavonás-értesítés, üdvözlő e-mail, letiltás-értesítés.

### 3.8 Validáció — összefoglaló

**`InviteTenantUserRequest`:** `email` kötelező, e-mail-formátum, max 254,
normalizálva; `displayName` kötelező, nem üres/whitespace, max 200; `roles`
kötelező, legalább egy elem, érvényes munkatársi `TenantRole`-értékek,
duplikátummentes, `user` tiltott; `groupIds` opcionális, létező e-tenant
`Group`-id-k (ismeretlen id → `400 fieldErrors`).

**`AcceptInvitationRequest`:** `token` kötelező; `displayName` opcionális,
max 200 (csak új-fiókos úton értelmezett — meglévő fióknál figyelmen kívül
marad).

**Szerepkör-szerkesztés (`PATCH roles`):** legalább egy szerepkör marad; az
utolsó aktív `manager` szerepköre nem vehető el; a hívó önmagát nem
fokozhatja le kizáró módon (SD-38); `user` érték nem szerepelhet.

Az üzleti invariánsok (SD-38) igazsága a szerver (`422` + `reason`-kód); a
kliens proaktívan is tilthat (4.4), de nem ő a határ.

### 3.9 Jogosultság — `authorization.json`

| Route | Method | Engedélyezett szerepkör |
|---|---|---|
| `/v1/tenant-users/list`, `/v1/tenant-users/{id}` | `GET` | `admin`, `tenant_manager` |
| `/v1/tenant-users/invite` | `POST` | `admin`, `tenant_manager` |
| `/v1/tenant-users/{id}/basic`, `/{id}/roles` | `PATCH` | `admin`, `tenant_manager` |
| `/v1/tenant-users/{id}/deactivate`, `/{id}/reactivate` | `POST` | `admin`, `tenant_manager` |
| `/v1/tenant-users/{id}/resend-invite`, `/{id}/revoke-invite` | `POST` | `admin`, `tenant_manager` |
| `/v1/tenant-users/invitations/{token}` | `GET` | `"*"` — publikus (a token az azonosítás) |
| `/v1/tenant-users/invitations/accept` | `POST` | `"*"` — publikus; az aktivált-fiókos ágon a szerver JWT-t követel (3.4) |

A `tenant_manager` prefix futásidőben a `Tenant` header kódjával egészül ki
(`01_kozos_mintak.md` 3.3); az `admin` explicit felsorolása kötelező
(`platform/spec_kod_elteresek.md` #70). A `"*"`-szabály az SD-122
három-szintű készletének publikus szintje. Az e-mail-mező `admin`-only
korlátja (SD-134) nem route-szintű — DTO-/service-szinten érvényesül
(sor-szintű szabály, `CLAUDE.md` 6.). Route-szinkron: a
`05_jogosultsag_es_authorization.md` 3.2 D-blokkja e körrel frissült.

### 3.10 Audit és naplózás

T0-n az `AuditableEntity`-nyom + a mailer-oldali dispatch/mail/event
rekordok fedik az igényt (FK-felh-12): ki hívott meg
(`UserInvitation.createdBy`), ki vont vissza (`updatedBy` + `Revoked`), ki
tiltott le (a `TenantUser`-írás + a meglévő guard-logika), mikor ment ki
levél (MailerX). A strukturált `UserAuditLog` iteráció (8.2). Az SD-133
fizikai törlésnél a meghívás-nyom a Core-ból eltűnik — a mailer-rekordok
megmaradnak (tudatos kompromisszum, a döntés explicit erről szólt).

---

## 4. Admin felület

### 4.1 Érintett képernyők

| Nézet | URL | Szerep |
|---|---|---|
| Felhasználó-lista | `/settings/users` | Fülek: Mind / Aktív / Meghívva / Letiltva; a meghívás innen indul |
| Felhasználó-adatlap | `/settings/users/details/<id>` | Megtekintés + a kezelő-akciók |
| **Beváltó-oldal (ÚJ)** | publikus SPA-route (javaslat: `/invite?token=…` — fejlesztői döntés) | A meghívó elfogadása — autentikáció nélkül elérhető route a manager SPA-ban |

A SPA-váz (Meghívva-fül, jelvények, i18n-kulcsak, letiltott „Új felhasználó"
gomb) a kódban részben készen áll (`platform/spec_kod_elteresek.md` #67) —
a feature ezt élesíti, nem új képernyő-családot épít. **A beváltó-oldal új
felület — ez a spec design-inkremense** (a Státusz ezért `design-körben`).

### 4.2 Felhasználó-lista

A meglévő `TableStateConfig`-lista, a v2-bővítésekkel:

**Fülek:** Mind / Aktív / Meghívva / Letiltva — a származtatott
lista-állapot szerint (3.2).

**Oszlopok:**

| Oszlop | Forrás | Szűrhető | Rendezhető | Megjegyzés |
|---|---|---|---|---|
| Név | `displayName` | igen (szöveg) | igen | Saját soron „Te" jelölés |
| E-mail | `email` | igen (szöveg) | igen | — |
| Szerepkörök | `roles`, Meghívva-soron `inviteRoles` (3.2) | igen (szerepkör-választó) | nem | Chip-ek; a választó a 4 munkatársi érték |
| Csoportok | `GroupMember` (lokális join) | igen (csoport-választó) | nem | Alapból rejtett oszlop, a szűrő él |
| Állapot | származtatott (3.2) | igen (fülek + választó) | igen | `Aktív` / `Meghívva` / `Letiltva` |
| Meghívó | `inviteExpiresAt` | nem | nem | Csak Meghívva-soron: „Érvényes <dátum>-ig" / „Hamarosan lejár" (< 48 óra) / „Lejárt" jelvény |
| Utoljára aktív | `lastActivityAt` | nem | igen | Meghívva-soron „még nem" |

**Sor-akciók:** megnyitás (mindig); Meghívva-soron: meghívó újraküldése,
meghívó visszavonása; Aktív soron: letiltás; Letiltva soron: feloldás.

**Lista-szintű akció:** „Új felhasználó" gomb (a gomb-név marad — bemenet
4.; a dialógus címe „Felhasználó meghívása"). Bulk-művelet nincs (v1-döntés,
marad).

### 4.3 Meghívó-dialógus

A dialógus négy mezője: e-mail, név, szerepkörök, csoportok. Az e-mail és a
szerepkör-mező nem-triviális:

```
- Mező megjelenő neve: E-mail-cím
- i18n kulcs: users.invite.email
- Technikai név (API, camelCase): email
- Adatmodell-megfeleltetés: User.email
- Típus és méret: string, max 254 (RFC 5321)
- Kötelezőség: kötelező
- Validáció (kliens): e-mail-formátum; (szerver): formátum + a 3.3
  ütközés-esetek (409 already_member / member_disabled / invite_pending —
  a felület az adatlapra / feloldásra / újraküldésre terel)
- Default érték: nincs
- Láthatóság: mindig
- Szerkeszthetőség: csak a dialógusban; kiküldött meghívó e-mailje nem
  szerkeszthető — az elgépelés útja visszavonás + új meghívó (SZ 5.5)
- Interakció: mentéskor szerver-oldali ütközés-ellenőrzés
- Tenant-szinten konfigurálható: nem
- Eredet: funkcionális terv 5.1 (_bemenet, l. 0. szakasz)
- Megjegyzés: normalizálás (trim + kisbetű) a szerveren — SZ-12
```

```
- Mező megjelenő neve: Szerepkörök
- i18n kulcs: users.invite.roles
- Technikai név (API, camelCase): roles
- Adatmodell-megfeleltetés: UserInvitation.roles (a kötés a beváltáskor —
  SD-129)
- Típus és méret: TenantRole[] — a 4 munkatársi értékből, többes választás
  (K-025)
- Kötelezőség: kötelező — legalább egy
- Validáció (kliens): legalább egy bejelölt; (szerver): 3.8
- Default érték: nincs
- Láthatóság: mindig
- Szerkeszthetőség: csak a dialógusban; kiküldött meghívó szerepköre nem
  módosítható (visszavonás + új meghívó)
- Interakció: négy jelölőnégyzet (Vezető / Diszpécser / Tartalomkezelő /
  Terepi dolgozó); Vezető-érték is adható (admin hívhat meg admint —
  bemenet 5.1)
- Tenant-szinten konfigurálható: nem
- Eredet: funkcionális terv 5.1; SD-126 (a készlet a mai 4 érték)
- Megjegyzés: a bemenet régi szerepkör-listája (moderátor, polgármester)
  elavult készletet idéz — VF-C jelzés (8.5)
```

A **név** rövid formában: Név, `displayName`, szöveg, kötelező, max 200,
i18n `users.invite.displayName`; segédszöveg: meglévő fióknál a fiók neve
marad érvényes. A **csoportok** rövid formában: Csoportok, `groupIds`,
multiselect a tenant csoportjaiból, opcionális, i18n `users.invite.groups`;
a tagság utólag a Csoportok nézetben is rendezhető; ha az íráskor a
csoport-hozzárendelés hibázott (3.3), a felület toast-tal jelzi
(`users.invite.groupAssignmentFailed`).

### 4.4 Felhasználó-adatlap

A meglévő adatlap (Alapadatok szekció inline szerkesztéssel — kód-valóság
#82), a v2-eltérésekkel:

- **Alapadatok:** név/firstName/lastName szerkeszthető (`PATCH basic`). Az
  `email`-mező **`tenant_manager`-nek csak olvasható** — mellette
  info-szöveg: az e-mail-módosítás az Urbino-support útja
  (`users.field.emailReadonlyHint`). `admin` hívónak szerkeszthető (SD-134).
- **Szerepkörök:** a meglévő szerepkör-szerkesztő (`PATCH roles`), a SD-38
  guard-hibák `[validationForm]`-megjelenítésével. A kliens proaktívan
  tiltja a nyilvánvaló esetet (a saját utolsó-vezető jelölő nem vehető le),
  az igazság a szerveré.
- **Csoporttagság:** olvasásra (a csoport-adatlapról szerkeszthető —
  v1-döntés, marad). Üresnél „Nincs csoporttagság".
- **Állapot- és meghívó-szekció:** állapot-badge; Meghívva-állapotnál a
  lejárat + „Újraküldés" / „Visszavonás" akciók; Aktív/Letiltva állapotnál
  „Letiltás" / „Feloldás". Audit-sor (Létrehozva/Módosítva,
  tenant-időzónában).
- **Meghívva-soron** az Alapadatok és a Szerepkörök szekció nem
  szerkeszthető (3.6) — a felület a mezőket tiltva mutatja, magyarázó
  szöveggel (`users.detail.inviteePendingHint`).

### 4.5 A beváltó-oldal (új felület — design-inkremens)

Publikus route a manager SPA-ban; Urbino-brand, magyar, tegező. Betöltéskor
az előnézet-végpontot hívja (3.4), és az eredmény szerint ágazik:

| Előnézet-eredmény | Képernyő |
|---|---|
| Érvényes + `accountExists=false` | **Aktiváló-nézet:** a város neve, a szerepkör(ök), a név (előtöltve, pontosítható); „Fiók aktiválása" gomb → accept → **siker-nézet**: „A fiókod elkészült" + „Belépés" gomb a login-oldalra (e-mail előtöltve; ott magic-link vagy social — SD-127/128). Jelszó-mező **nincs** |
| Érvényes + `accountExists=true` | **Megerősítő-nézet:** „Már van Urbino-fiókod — lépj be a megerősítéshez" → friss belépés a saját login-oldalon → visszatérve a megerősítő képernyő („Mostantól hozzáférsz <város> manager felületéhez, <szerepkör> szerepkörrel") → accept → a felületre lép (K-027 landing) |
| Élő session **másik** fiókkal | Figyelmeztetés + kényszerített fiók-váltás (SZ-8, FK-felh-4c) — az elfogadás sosem köt a böngészőben épp belépett idegen fiókra; a szerver-oldali identitás-kapu (3.4) a határ |
| `410 invitation_invalid` | Barátságos hibaoldal: „Ez a meghívó már nem érvényes — kérj újat a hivatal adminisztrátorától." Nem fedi fel, létezik-e fiók (SZ-6) |
| `409 invitation_accepted` | Átirányítás a login-oldalra (SZ-7) |

### 4.6 Üres / betöltési / hibaállapot

A standard minták (v1, változatlan): üres lista / skeleton / lista-hiba /
adatlap-`404`. Akció-hibáknál a `reason`-kód magyar üzenete toast vagy
mezőszintű hiba a `[validationForm]`-minta szerint.

### 4.7 i18n kulcsok (`hu.json`)

A szerver kulcsot/kódot ad, nem kész szöveget (`01_kozos_mintak.md` 5.5); a
felület tegez. A meglévő kulcsok (`users.field.*`, `users.status.*`,
`users.role.*`, `users.action.*` — v1) élnek tovább; az új/változó készlet:

**Lista és fülek:**

- `users.tab.all` = „Mind"
- `users.tab.active` = „Aktív"
- `users.tab.invited` = „Meghívva"
- `users.tab.disabled` = „Letiltva"
- `users.column.groups` = „Csoportok"
- `users.column.lastActive` = „Utoljára aktív"
- `users.lastActive.never` = „még nem"
- `users.invitation.validUntil` = „Érvényes {date}-ig"
- `users.invitation.expiringSoon` = „Hamarosan lejár"
- `users.invitation.expired` = „Lejárt"
- `users.badge.you` = „Te"

**Meghívó-dialógus:**

- `users.invite.title` = „Felhasználó meghívása"
- `users.invite.button` = „Új felhasználó"
- `users.invite.subtitle` = „Munkatársat meghívóval lehet felvenni — e-mailben aktiválja a fiókját."
- `users.invite.email` = „E-mail-cím"
- `users.invite.displayName` = „Név"
- `users.invite.displayNameHint` = „Meglévő Urbino-fióknál a fiók neve marad érvényes."
- `users.invite.roles` = „Szerepkörök"
- `users.invite.groups` = „Csoportok"
- `users.invite.groupsHint` = „A csoport-tagságot később is beállíthatod a Csoportok nézetben."
- `users.invite.submit` = „Meghívó küldése"
- `users.invite.success` = „A meghívó elment {email} címre."
- `users.invite.groupAssignmentFailed` = „A meghívó elment, de a csoport-hozzárendelés nem sikerült — állítsd be a Csoportok nézetben."

**Akciók:**

- `users.action.resendInvite` = „Meghívó újraküldése"
- `users.action.revokeInvite` = „Meghívó visszavonása"
- `users.action.revokeConfirm` = „Biztosan visszavonod a meghívót? A link azonnal érvénytelenné válik."
- `users.action.resendSuccess` = „A meghívó újra elment."
- `users.action.revokeSuccess` = „A meghívó visszavonva."

**Adatlap:**

- `users.field.emailReadonlyHint` = „Az e-mail-cím módosítását az Urbino-support végzi."
- `users.detail.inviteePendingHint` = „A meghívott adatai a meghívó elfogadásáig nem szerkeszthetők — elgépelésnél vond vissza a meghívót, és küldj újat."

**Hibakód-kulcsok (`reason` → üzenet):**

- `users.error.alreadyMember` = „Ez a felhasználó már tagja a városnak."
- `users.error.memberDisabled` = „Ez a felhasználó letiltott tagja a városnak — a feloldással adhatod vissza a hozzáférését."
- `users.error.invitePending` = „Erre a címre már van függő meghívó — a sorból újraküldheted."
- `users.error.invitationMailUnavailable` = „A meghívó-levél sablonja hiányzik — jelezd az Urbino-supportnak."
- `users.error.noPendingInvite` = „Ehhez a felhasználóhoz nincs függő meghívó."
- `users.error.inviteePending` = „A meghívott adatai az elfogadásig nem szerkeszthetők."
- `users.error.lastManager` = „Nem tilthatod le az utolsó vezetőt — előbb nevezz ki másikat."
- `users.error.lastManagerRole` = „Legalább egy vezetőnek maradnia kell — ezt a szerepkört nem veheted el."
- `users.error.cannotDeactivateSelf` = „A saját fiókodat nem tilthatod le."
- `users.error.roleRequired` = „Legalább egy szerepkört ki kell választanod."

**Beváltó-oldal:**

- `invite.accept.title` = „Meghívó — {tenantName}"
- `invite.accept.lead` = „{tenantName} Urbino-rendszerébe kaptál meghívót, {roles} szerepkörrel."
- `invite.accept.nameLabel` = „Neved"
- `invite.accept.activate` = „Fiók aktiválása"
- `invite.accept.successTitle` = „A fiókod elkészült"
- `invite.accept.successLead` = „Lépj be az e-mail-címeddel — jelszó nem kell: belépő-linket kapsz, vagy Google/Apple-fiókkal is beléphetsz."
- `invite.accept.loginButton` = „Belépés"
- `invite.accept.existingLead` = „Már van Urbino-fiókod — új regisztráció nélkül, a meglévő fiókoddal léphetsz be."
- `invite.accept.existingConfirm` = „Mostantól hozzáférsz {tenantName} manager felületéhez, {roles} szerepkörrel."
- `invite.accept.confirmButton` = „Belépés és megerősítés"
- `invite.accept.wrongAccount` = „Ez a meghívó {email} címre szól — a megerősítéshez azzal a fiókkal lépj be."
- `invite.accept.switchAccount` = „Belépés a meghívott fiókkal"
- `invite.error.invalid` = „Ez a meghívó már nem érvényes — kérj újat a hivatal adminisztrátorától."
- `invite.error.identityMismatch` = „A belépett fiók nem egyezik a meghívott címmel."

---

## 5. Polgári mobilapp adatigénye

**Nem releváns, mert** a feature a staff-oldal kezelő-felülete. Az SD-123
óta a polgár is Core `User` + `TenantUser`-projekció, ezért az elhatárolás
v2-pontosítása: (a) a lista/get a csak-`user` sorokat kiszűri (3.2); (b) a
meghívó a `user` szerepkört sosem adja/veszi (a polgári tagság a
join/leave-flow-é); (c) ha a meghívott e-mail egy polgár fiókja, az SZ-4/SZ-5
meglévő-fiókos út érvényes — a polgári appot ez nem érinti, a polgár a
manager felülethez kap hozzáférést. A polgári kliens egyetlen végpontját sem
érinti a feature.

---

## 6. Acceptance criteria

### AC-A — Meghívás-indítás

- **AC-A1** — *Given* egy `tenant_manager` a saját tenantján; *When* érvényes
  `email` + `displayName` + legalább egy `roles` elemmel meghív; *Then*
  létrejön a `UserInvitation` (`Pending`, `expiresAt = now + 7 nap`, a
  `roles`-készlettel), új e-mailnél a `User` (`status=Invited`), a
  `TenantUser`-sor az `inviteExpiresAt`/`inviteRoles` tükörrel, és a válasz
  `201` a lista-sorral; `UserTenantRole`-sor **nem** jön létre.
- **AC-A2** — *Given* egy meghívás `groupIds`-szal; *When* a kérés lefut;
  *Then* a `GroupMember`-sorok a meghívott `TenantUser`-sorára létrejönnek, és
  a Csoportok nézet tag-listája mutatja a meghívottat.
- **AC-A3** — *Given* a csoport-írás meghiúsul; *When* a meghívás lefut;
  *Then* a meghívó és a projekció érvényesen létrejön, az e-mail kimegy, és a
  válasz `groupAssignmentFailed=true` jelzést hordoz.
- **AC-A4** — *Given* egy e-mail, amely e tenanton már munkatárs / letiltott
  tag / függő meghívott; *When* meghívják; *Then* a válasz `409` a megfelelő
  `reason`-kóddal (`already_member` / `member_disabled` / `invite_pending`).
- **AC-A5** — *Given* egy e-mail, amely más tenanton vagy polgárként már
  létező Core `User`; *When* e tenantra meghívják; *Then* nem jön létre új
  `User`, a meglévő `displayName` változatlan marad, E-2 sablonú levél megy
  ki, és a válasz megkülönböztethetetlen az új-fiókos `201`-től.
- **AC-A6** — *Given* a `user-invite-new` (vagy `-existing`) `MailTemplate`
  hiányzik; *When* a meghívást indítják; *Then* a válasz `422`
  `invitation_mail_unavailable`, és **semmilyen** rekord nem jön létre.
- **AC-A7** — *Given* az e-mail-transzport hibázik (a sablon létezik); *When*
  a meghívás lefut; *Then* a rekordok megmaradnak, és a meghívó a felületről
  újraküldhető.
- **AC-A8** — *Given* egy kérés `user` szerepkör-értékkel, üres `roles`-szal
  vagy rossz e-mail-formátummal; *When* feldolgozódik; *Then* `400`
  `fieldErrors`.
- **AC-A9** — *Given* egy `tenant_dispatcher` JWT; *When* a
  `POST /v1/tenant-users/invite`-ot hívja; *Then* `403`.
- **AC-A10** — *Given* a kiküldött meghívó; *Then* a `tokenHash` a token
  SHA-256 alakját tárolja, és a nyers token a DB-ben és a válaszban sehol nem
  szerepel.

### AC-B — Előnézet és beváltás (új fiók)

- **AC-B1** — *Given* érvényes, `Pending`, nem lejárt token; *When* az
  előnézetet hívják; *Then* a válasz a tenant-nevet, a szerepköröket, az
  előtöltendő nevet és `accountExists=false`-t adja.
- **AC-B2** — *Given* lejárt, visszavont vagy nem létező token; *When* az
  előnézetet vagy az acceptet hívják; *Then* a válasz egységesen `410`
  `invitation_invalid` (lejárt `Pending` ekkor áll `Expired`-re), és a válasz
  nem árulja el, létezik-e fiók a címen.
- **AC-B3** — *Given* már beváltott token; *When* az előnézetet hívják;
  *Then* `409 invitation_accepted` — a kliens a login-oldalra visz.
- **AC-B4** — *Given* érvényes token egy `Invited` fiókhoz; *When* az accept
  JWT nélkül, `displayName`-pontosítással fut; *Then* a Zitadel-fiók létrejön
  (véletlen jelszó, `email.isVerified=true`, username = e-mail), az
  `externalAuthId` kitöltődik, a `UserTenantRole`-sorok a meghívó
  `roles`-ából létrejönnek, a grant-sync lefut, a `User` `Active` +
  `emailVerified=true`, a meghívó `Accepted` + `consumedAt`, és a
  `TenantUser`-tükör (`status=Active`, `roles`, `inviteExpiresAt=null`)
  frissül.
- **AC-B5** — *Given* az accept sikeres; *Then* a válasz nem tartalmaz
  sessiont/tokent — a kliens a saját login-oldalra irányít, és a meghívott
  magic-linkkel vagy social úton lép be; jelszót a folyamat sehol nem kér.
- **AC-B6** — *Given* a Zitadel-hívás hibázik a beváltás közben; *When* az
  accept fut; *Then* a tranzakció visszagördül (`User` `Invited`, meghívó
  `Pending`), a válasz `502`, és a link újrapróbálható.
- **AC-B7** — *Given* ugyanaz a token másodszor; *When* az acceptet hívják;
  *Then* a beváltás nem fut le újra (`409 invitation_accepted`).

### AC-C — Beváltás meglévő fiókkal

- **AC-C1** — *Given* érvényes token egy aktivált (`externalAuthId`-s)
  fiókhoz; *When* az accept JWT nélkül érkezik; *Then* `401`
  `authentication_required` — az új-fiókos út aktivált fióknál nem járható.
- **AC-C2** — *Given* érvényes token + a meghívott fiók JWT-je; *When* az
  accept fut; *Then* a kötések + grant-sync létrejönnek Zitadel-fióklétrehozás
  nélkül, a meghívó `Accepted`, és a fiók `displayName`-je változatlan.
- **AC-C3** — *Given* érvényes token + **másik** fiók JWT-je; *When* az
  accept fut; *Then* `403 invitation_identity_mismatch`, és semmilyen kötés
  nem jön létre (SZ-8).
- **AC-C4** — *Given* egy polgári fiók (csak `user` szerepkör e tenanton)
  staff-meghívója; *When* a beváltás megerősítődik; *Then* a munkatársi
  szerepkör-kötések a meglévő `user` sor **mellé** jönnek létre, és a fiók
  megjelenik a Felhasználók-listában.

### AC-D — Újraküldés, visszavonás, törlés-kivétel

- **AC-D1** — *Given* egy Meghívva-sor (`Pending` vagy lejárt meghívóval);
  *When* a vezető újraküldi; *Then* a régi meghívó `Revoked`, új `Pending`
  jön létre (új token, friss 7 nap, a régi `roles`-készlettel), az
  `inviteExpiresAt` tükör frissül, és új e-mail megy ki.
- **AC-D2** — *Given* egy újraküldés utáni régi token; *When* beváltják;
  *Then* `410` — csak az új token érvényes.
- **AC-D3** — *Given* egy sor függő meghívó nélkül; *When* újraküldést vagy
  visszavonást hívnak rá; *Then* `422 no_pending_invite`.
- **AC-D4** — *Given* egy meghívás-létrehozta, sosem aktivált fiók, más
  tenant-kötés és meghívó nélkül; *When* a meghívót visszavonják; *Then* a
  `User`, a meghívó-előzmények, a `TenantUser`-sor és a `GroupMember`-sorok
  **fizikailag törlődnek**, és a sor eltűnik a listából (SD-133).
- **AC-D5** — *Given* egy meglévő (aktivált vagy más tenanton is meghívott)
  fiók függő meghívója; *When* visszavonják; *Then* csak a meghívó áll
  `Revoked`-ra és az e-tenant-lenyomatok (`GroupMember`, tükör-mezők)
  tűnnek el — a `User` és a más-tenant-adatok érintetlenek.
- **AC-D6** — *Given* egy visszavonás; *Then* a meghívott semmilyen
  értesítést nem kap (csendes visszavonás).

### AC-E — Letiltás és feloldás (grant-hatály)

- **AC-E1** — *Given* egy aktív munkatárs, aki nem az utolsó vezető és nem a
  hívó; *When* letiltják; *Then* a `TenantUser.status` `Disabled`, a
  Zitadel-grantek e tenant szerepköreire visszavonódnak, a
  `UserTenantRole`-sorok megmaradnak, és a Core `User.status` változatlan.
- **AC-E2** — *Given* egy letiltott munkatárs érvényes, még le nem járt
  access tokenje; *When* a token lejártáig API-t hív; *Then* a kérés a
  meglévő route-szabályok szerint kiszolgálódik (nincs app-oldali
  Active-kapu — SD-130); a token-frissítés után a staff-route-ok `403`-at
  adnak (a grant-claimek hiányoznak).
- **AC-E3** — *Given* egy letiltott munkatárs; *When* feloldják; *Then* a
  `TenantUser.status` `Active`, és a grantek a megőrzött
  `UserTenantRole`-sorokból helyreállnak — a korábbi szerepkörökkel.
- **AC-E4** — *Given* az utolsó aktív vezető, vagy a hívó saját fiókja;
  *When* letiltást kísérelnek meg; *Then* `422` a megfelelő `reason`-kóddal
  (`last_manager` / `cannot_deactivate_self`), és semmi nem változik.
- **AC-E5** — *Given* egy Meghívva-sor; *When* letiltást hívnak rá; *Then*
  `422` — a Meghívva-sor útjai az újraküldés/visszavonás.

### AC-F — Szerkesztés-guardok

- **AC-F1** — *Given* egy `tenant_manager` JWT; *When* a `PATCH basic`
  kérése `email` kulcsot tartalmaz; *Then* az e-mail változatlan marad (a
  kulcs a DTO-ról hiányzik, a modellkötés eldobja), a többi mező frissül.
- **AC-F2** — *Given* egy `admin` JWT; *When* a `PATCH basic` e-mailt
  módosít; *Then* a Core, a Zitadel (e-mail + username) és az érintett
  tenant-tükrök szinkronban frissülnek (kód-valóság #83–#85).
- **AC-F3** — *Given* egy Meghívva-sor; *When* `PATCH basic` vagy
  `PATCH roles` érkezik rá; *Then* `422 invitee_pending`.
- **AC-F4** — *Given* a szerepkör-szerkesztés az utolsó aktív vezető
  `manager` szerepkörét venné el, vagy nulla szerepkört hagyna; *When* a
  mentés fut; *Then* `422` (`last_manager_role` / `role_required`).

### AC-G — Lista, fülek, tenant-szigetelés

- **AC-G1** — *Given* munkatársak, meghívottak és letiltottak vegyesen;
  *When* a fülek váltakoznak; *Then* a Mind/Aktív/Meghívva/Letiltva fülek a
  származtatott lista-állapot szerint pontosan szűrnek.
- **AC-G2** — *Given* egy meghívó, amelynek lejáratáig kevesebb mint 48 óra
  van / amely lejárt; *When* a lista betölt; *Then* a sor „hamarosan lejár" /
  „lejárt" jelvényt mutat, és a lejárt sor a listában marad, újraküldés-
  akcióval.
- **AC-G3** — *Given* a lista-lekérdezés; *Then* kizárólag a tenant DB-ből
  fut (a meghívó-jelvények az `inviteExpiresAt`/`inviteRoles` tükörből) —
  Core-lekérdezés a lista-útvonalon nincs.
- **AC-G4** — *Given* egy csak-`user` (polgár) sor függő meghívó nélkül;
  *When* a listát vagy a `GET {id}`-t hívják; *Then* a sor nem szerepel /
  `404`; *és Given* ugyanez a fiók függő staff-meghívóval; *Then* a sor a
  Meghívva fülben megjelenik.
- **AC-G5** — *Given* egy idegen tenant sora; *When* bármely `{id}`-s
  végpontot hívják rá; *Then* `404`, és a kísérlet naplózódik.

### AC-H — Jogosultság-érvényesítés (a meglévő réteg)

- **AC-H1** — *Given* az `authorization.json`; *Then* a 3.9 tábla minden
  admin-route-ja pontosan az `admin,tenant_manager` listát hordozza, a két
  `invitations/*` route pedig explicit `"*"`-t.
- **AC-H2** — *Given* egy `tenant_dispatcher`/`content_manager`/
  `field_worker` JWT; *When* bármely kezelő-végpontot hív; *Then* `403`.
- **AC-H3** — *Given* a `vezető ⊇ diszpécser ÉS ⊇ tartalomkezelő` reláció;
  *Then* a diszpécser- és tartalomkezelő-route-ok `roles`-listái a
  `tenant_manager`-t is felsorolják (felsorolásos leképezés, öröklődés
  nélkül — v1-réteg, változatlan).
- **AC-H4** — *Given* egy szerepkör-visszavonás; *Then* a már kiadott token
  a lejártáig hordozhatja a régi claimeket (eventual consistency,
  `01_kozos_mintak.md` 3.5) — aktív session-revokáció nincs (8.2 iteráció).

### AC-I — Landing-redirect (v1-réteg, változatlan)

- **AC-I1** — *Given* egy felhasználó egyetlen szerepkörrel; *When* a `/`
  URL-re lép; *Then* a szerepköre landing-nézetére irányítódik (diszpécser →
  `/tickets`, vezető → `/dashboard`, tartalomkezelő → `/content/news`,
  terepi → `/tickets`).
- **AC-I2** — *Given* több szerepkör; *Then* a „magasabb" szerepkör landingje
  nyer (vezető jelenléte → `/dashboard`).

### AC-K — Biztonsági kritériumok

- **AC-K1** — *Given* hiányzó mail-sablon; *Then* soha nincs csendes
  kimaradás — minden érintett végpont hibával jelez (AC-A6, resend
  ugyanígy).
- **AC-K2** — *Given* a meghívó-küldés; *Then* az e-mail-cím normalizálva
  (trim + kisbetű) hasonlítódik, és a végponton működik védőkorlát
  (rate-limit — a mechanizmus fejlesztői döntés, FK-felh-11; SZ-12).
- **AC-K3** — *Given* a szerver-logok; *Then* a nyers meghívó-token
  semmilyen log-szinten nem jelenik meg (a MagicAuth INFO-logolása itt
  tiltott).

---

## 7. Keresztmetszeti

- **i18n** — minden szöveg kulcs-alapú (4.7); a szerver `reason`-kódot ad.
  **Érdemi.**
- **Időzóna** — a lejárati dátumok megjelenítése tenant-időzónában (SD-10);
  az `expiresAt`/`inviteExpiresAt` UTC-ben tárolt (SD-9). **Érdemi.**
- **Biztonság** — token-hash (SHA-256), egyszer-használat, 7 napos lejárat;
  a nyers token sosem logolható (AC-K3); az érvénytelen-token-válaszok
  egységesek (SZ-6, fiók-enumeráció ellen); e-mail-normalizálás + throttle
  (SZ-12, fejlesztői mechanika); a letiltás hatálya eventual (SD-130 —
  perces access-token-élettartam mellett vállalt); az accept-realm
  tenant-header-mentes, a token hordozza a kontextust. A felmérés
  biztonsági jelzései (plain-text kulcsok a dev-configban, nyílt
  `/mailer/click` redirect, hiányzó `mail_template.ident` unique index) a
  fejlesztés kísérő-teendői — a kérdés-fájl rögzíti őket. **Érdemi.**
- **Teljesítmény** — tenantonként egy-két számjegyű felhasználószám; a
  lista-útvonal tisztán tenant-DB (AC-G3). **Megjegyzés-szinten releváns.**

---

## 8. Lezárás

### 8.1 Első kiadás (T0)

- **Meghívó-flow** a `/v1/tenant-users` alatt: invite (szerepkör +
  opcionális csoport), resend, revoke (+ SD-133 törlés-kivétel), publikus
  előnézet + accept; kétfázisú Core→Tenant írás (SD-132) az
  `inviteExpiresAt`/`inviteRoles` tükörrel.
- **Beváltó-oldal** a manager SPA publikus route-ján: aktiváló- és
  megerősítő-út, jelszó nélkül (SD-127/128), a saját login-oldalra
  irányítással.
- **Mailer-integráció:** `user-invite-new` / `user-invite-existing` identek
  a MailerX/Mandrill úton; hiányzó sablon = végpont-hiba (SD-131).
- **Lista v2:** fülek (Mind/Aktív/Meghívva/Letiltva), meghívó-jelvények
  (hamarosan lejár / lejárt), csoport-szűrő, „Utoljára aktív" oszlop; a
  Meghívva-sor teljes értékű sor.
- **Letiltás-hatály:** Zitadel-grant-visszavonás letiltáskor,
  grant-helyreállítás feloldáskor (SD-130); a meglévő guardok.
- **E-mail-mező `admin`-only** a `PATCH basic`-en (SD-134).
- **Migráció:** `tenant_user.active` bool → `status` enum (SD-37);
  `User.email` max 254.

### 8.2 Következő iterációk (listázva, nem specifikálva)

- Aktív session-/refresh-token-revokáció letiltáskor (az FK-felh-5 „C"
  szintje) — SD-120 szerint csak explicit döntéssel.
- `UserAuditLog` — strukturált felhasználó-esemény-napló.
- Mandrill-webhook / bounce-kezelés — a „kiment" ma csak API-átadás.
- Meghívó-lejárat háttér-job (ma lazy) + lejárt `UserInvitation`-takarítás.
- Bulk-import / többes meghívás; meghívó-link másolása; automatikus
  emlékeztető — a bemenet tudatos elhagyásai (9. szakasz), itt csak
  nyilvántartva.
- E-mail-módosítás a `tenant_manager` felé nyitása.
- A polgárokat is mutató szűrő-nézet a listán (SD-123 iterációs jelölt).
- Új szerepkör (`tenant_admin` külön a vezetőtől — a D-2 teljes leképezése)
  és/vagy `manager` → `tenant_admin` átnevezés (FK-felh-1/14 elhalasztva).

### 8.3 Feltételezések (explicit lista)

1. **A Zitadel nem küld levelet végfelhasználónak** (SD-131 platform-tény) —
   a fejlesztés első lépése ennek instance-szintű ellenőrzése/beállítása
   (FK-felh-8).
2. **A social belépés** (Google/Apple) a login-oldalon a polgári körrel
   közös Zitadel IdP-federációra épül — konfigurációja fejlesztési
   előfeltétel; amíg nincs, a magic-link az egyetlen út (a flow ettől
   működőképes).
3. **Token-élettartamok** (access/refresh) a Zitadel-oldali konfigban élnek;
   az SD-130 vállalás perces access-élettartamra épül (fejlesztői közlés,
   FK-felh-5).
4. A `TenantRole`-készlet a 4 munkatársi érték + `user` (SD-123, SD-126);
   a seeder a 4 staff-rekordot minden tenant-DB-ben létrehozza (#81).
5. Az első tenant-admin a pilot alatt kézi (Zitadel-oldali) úton születik;
   az Urbino-admin felület később ugyanezt az invite-mechanizmust hívja
   (D-3) — a végpontok `admin`-jogosultsága ezt előkészíti.

### 8.4 Nyitott kérdések, fejlesztői döntések, megtartott hiányok

**Fejlesztői döntések (a spec keretet ad, a megoldás a fejlesztőé):**

| Kérdés | Keret |
|---|---|
| Feladó-infrastruktúra: éles feladó-domain, SPF/DKIM/DMARC, Reply-To (FK-felh-9) | A meghívó a mindenkori Urbino-feladóról megy; bounce-kezelés nincs (8.2) |
| Token-URL formája, hash-implementáció, rate-limit-mechanizmus (FK-felh-11) | MagicAuth-minta + SZ-12 követelmény (AC-K2) |
| A kétfázisú írás kompenzáció-mechanikája (SD-132) | A projekció-írás atomikus a meghívással; a mechanika (sorrend, kompenzáló törlés) szabad |
| Szerver-oldali `auth_time`-kapu a meglévő-fiókos megerősítésen | A friss belépés kliens-folyamat; a szerver-kapu opcionális szigorítás |

**Megtartott hiány:** nincs — a v1 „beváltó-képernyő UX-e" megtartott hiánya
ezzel a körrel lezárult (4.5 + a design-inkremens); a „felhasználó-esemény-
napló" iterációvá sorolódott át (8.2).

### 8.5 Visszacsorgó jelzések

| # | Jelzés | Cél |
|---|---|---|
| `VF-felh-2` (VF-A) | A staff-oldalon **nincs jelszó**: a beváltás nem kér jelszó-beállítást, jelszó-reset (E-3, adatlap-akció) nem épül — belépés magic-link + social (SD-127/128). A bemenet 5.3/5.4/7.2 (E-1 copy, E-3) és 9. („T0-n a klasszikus jelszó") pontjai ennek megfelelően korrigálandók | Termékfunkció-projekt |
| `VF-felh-3` (VF-B) | Az SZ-11 „minden felületre azonnali" letiltás helyett T0-n grant-visszavonás + perces token-kifutás (SD-130). A KJ-3 kanonizálásakor a szöveg pontosítandó („a hozzáférés perceken belül megszűnik") | Termékfunkció-projekt |
| `VF-felh-4` (VF-C) | A bemenet 5.1 szerepkör-választó listája (moderátor, polgármester stb.) törölt készletet idéz; a T0-készlet: vezető / diszpécser / tartalomkezelő / terepi (SD-126). A D-2 tenant-admin ≠ vezető szétválasztás iterációs jelölt | Termékfunkció-projekt |

*(A `VF-felh-1` — a `User.email` hosszkorlátja — a v1.0 jelzése, e körrel
lezárva: max 254, domain-modell v2.10.)*

### 8.6 Hivatkozott dokumentumok

- **Funkcionális (e kör):** a 0. szakasz három `_bemenet/`-forrása
- **Funkcionális (v1-örökség):** `05_jogosultsagok_v2.md`,
  `00_architektura_v4.md`, `50_konfig_v2.md`, `90_sitemap_v3.md`
- **Alapdokumentumok:** `00_domain_model.md` (v2.10), `01_kozos_mintak.md`
  (2.1, 3.2–3.5, 5.5, 6.3–6.4, 8.), `00_terminologia.md`, `05_mailer.md`,
  `05_jogosultsag_es_authorization.md` (3.2 D-blokk),
  `platform/spec_dontesnaplo.md` (SD-34 – SD-39, SD-120 – SD-123,
  SD-125 – SD-134)
- **Főkönyvek:** `platform/spec_kod_elteresek.md` (#67 — e körrel zárva,
  #70), `apps/manager/spec_kod_elteresek.md` (#77–#86 — kanonizálva)
- **Kanonikus:** `platform/kanonikus_dontesek.md` — K-007, K-016, K-024,
  K-025, K-027; KJ-1 – KJ-5 (jelölt-státuszban)
- **Meglévő minták:** `project_backend` CLAUDE.md,
  `project_backend_client_angular` CLAUDE.md (`BaseController`,
  `TableStateConfig`, `[validationForm]`, `authorization.json`,
  MagicAuth-token-minta, `ZitadelManagementClient`)

---

## Verziónapló

- **v1.0 (2026.05.19)** — Első kiadás: a felhasználókezelés és a
  jogosultság-modell érvényesítésének teljes specifikációja a `/v1/users/*`
  végpont-készletre; `UserInvitation` entitás, `User`-állapotgép, hét
  végpont, SD-34 – SD-39.
- **v1.1 (2026.05.21)** — SD-78: URL-szegmensek angolra
  (`/settings/users` stb.); a felületi terminológia magyar marad.
- **v1.2 (2026.07.31)** — SD-123 staff-szűrés: a lista/get a csak-`user`
  (polgár) sorokat kiszűri.
- **v2.0 (2026-08-03)** — **A felhasználó-meghívás termékfunkció-kör +
  fejlesztői kérdés-kör (FK-felh-1..15) nagy-verziós átvezetése (SD-125 –
  SD-134).** (1) A cél-API a `/v1/tenant-users` kód-valóság — a `/v1/users/*`
  meghívó-készlet törölve, a `platform/spec_kod_elteresek.md` #67 zárva; a
  lezárt #77–#86 eltérések (PATCH basic/roles, DB→Zitadel sync,
  username=email, staff-szűrés, seeder) kanonizálva. (2) Jelszó nélküli
  beváltás: Zitadel-fiók a beváltáskor véletlen jelszóval, redirect a saját
  login-oldalra (magic + social), jelszó-reset nincs (SD-127/128); a v1
  „beváltó-képernyő UX" megtartott hiánya lezárva (4.5 beváltó-oldal —
  design-inkremens). (3) A szerepkör-kötés a beváltáskor jön létre, a szánt
  készletet a `UserInvitation.roles` hordozza (SD-129); a meglévő-fiókos
  elfogadás friss belépés + identitás-kapuval (SZ-8). (4) Kétfázisú
  Core→Tenant írás a meghíváskor: `TenantUser`-projekció +
  `inviteExpiresAt`/`inviteRoles` tükör + opcionális `GroupMember`-írás
  (SD-132) — a lista-útvonal Core-mentes marad. (5) A letiltás
  tenant-hatókörű, hatálya Zitadel-grant-visszavonás, app-oldali Active-kapu
  nélkül (SD-130); a v1 egy-állapotgépe három kis gépre bontva (fiók-életút /
  tenant-viszony / meghívó). (6) Visszavonáskor a sosem aktivált,
  meghívás-létrehozta fiók fizikailag törlődik (SD-133 — SD-39-kivétel).
  (7) Mailer-fejezet: `user-invite-new`/`-existing` identek, hiányzó sablon =
  végpont-hiba, „a Zitadel nem küld levelet" platform-tény (SD-131). (8) Az
  e-mail-mező a `PATCH basic`-en `admin`-only (SD-134). (9) Lista v2: fülek,
  meghívó-jelvények, csoport-szűrő, „Utoljára aktív"; Meghívva-soron a
  szerkesztés tiltott. (10) AC-készlet a sablon csoport-betűs konvenciójára
  átírva (AC-A – AC-K); fejléc a sablon-metaadatokra igazítva, Státusz:
  `design-körben (v1)`. Három visszacsorgó jelzés (VF-felh-2/3/4).
