diff --git a/.planning/quick/260923-dhh-proxmox-modul-pve-pbs-und-pmg-anbinden-n/260923-dhh-PLAN.md b/.planning/quick/260923-dhh-proxmox-modul-pve-pbs-und-pmg-anbinden-n/260923-dhh-PLAN.md
new file mode 100644
index 0000000..15fc37c
--- /dev/null
+++ b/.planning/quick/260923-dhh-proxmox-modul-pve-pbs-und-pmg-anbinden-n/260923-dhh-PLAN.md
@@ -0,0 +1,910 @@
+---
+phase: quick-260923-dhh
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+autonomous: true
+requirements: [D-01, D-02, D-03, D-04, D-05, D-06, D-07, D-08, D-09, D-10, D-11]
+
+files_modified:
+ - apps/api/prisma/schema.prisma
+ - apps/api/prisma/migrations/20260923140000_proxmox_server/migration.sql
+ - apps/api/src/app.module.ts
+ - apps/api/src/proxmox/proxmox.types.ts
+ - apps/api/src/proxmox/proxmox-auth.ts
+ - apps/api/src/proxmox/proxmox-normalize.ts
+ - apps/api/src/proxmox/proxmox-client.service.ts
+ - apps/api/src/proxmox/proxmox.service.ts
+ - apps/api/src/proxmox/proxmox-scheduler.service.ts
+ - apps/api/src/proxmox/proxmox.controller.ts
+ - apps/api/src/proxmox/proxmox.module.ts
+ - apps/api/src/proxmox/proxmox.seed.ts
+ - apps/api/src/proxmox/dto/proxmox-server.dto.ts
+ - apps/api/src/proxmox/proxmox-client.service.spec.ts
+ - apps/api/src/proxmox/proxmox.service.spec.ts
+ - apps/api/src/proxmox/proxmox-normalize.spec.ts
+ - apps/api/src/proxmox/proxmox-scheduler.service.spec.ts
+ - apps/api/src/proxmox/proxmox-nur-lesen.spec.ts
+ - apps/api/src/prisma/rls-access-inventory.spec.ts
+ - apps/web/src/lib/proxmox-api.ts
+ - apps/web/src/lib/module-loader.ts
+ - apps/web/src/app/(portal)/modules/proxmox/layout.tsx
+ - apps/web/src/app/(portal)/modules/proxmox/page.tsx
+ - apps/web/src/app/(portal)/modules/proxmox/components/ServerCard.tsx
+ - apps/web/src/app/(portal)/modules/proxmox/components/ServerCard.test.tsx
+ - apps/web/src/app/(portal)/modules/proxmox/settings/page.tsx
+ - apps/web/src/app/(portal)/modules/proxmox/settings/components/ServerForm.tsx
+ - apps/web/src/app/(portal)/modules/proxmox/settings/components/ServerForm.test.tsx
+ - apps/web/src/messages/de.json
+ - apps/web/src/messages/en.json
+ - docs/mandantentrennung-zugriffsklassifikation.md
+ - docs/anleitung-entwicklung.md
+ - docs/anwenderhandbuch.md
+
+user_setup:
+ - service: proxmox
+ why: "Nur der Nutzer hat echte PVE-/PBS-/PMG-Server. Ohne sie bleibt die Feldnamen-Annahme A2/A3 der Recherche unbestaetigt."
+ dashboard_config:
+ - task: "Je Produkt einen NUR-LESE-Zugang anlegen: PVE-Rolle PVEAuditor, PBS-Rolle Audit bzw. DatastoreAudit, PMG-Rolle Auditor"
+ location: "Proxmox-Oberflaeche -> Datacenter/Configuration -> Permissions"
+
+estimate:
+ tokens: 320000
+ raw_tokens: 210000
+ tasks: 7
+ confidence: low
+
+must_haves:
+ truths:
+ - "Kein Weg im gesamten Modul veraendert etwas bei Proxmox: die einzige Nicht-GET-Anfrage im ganzen Modul ist die Ticket-Anmeldung, und sie wird maschinell nachgezaehlt (D-01)."
+ - "Ein Administrator legt in den Einstellungen Server an (Name, Typ pve|pbs|pmg, Adresse, Zugang) und sieht die Zugangsdaten nie wieder im Klartext (D-02)."
+ - "PVE und PBS bieten API-Token ODER Benutzer/Passwort; PMG bietet nur Benutzer/Passwort — die Token-Felder erscheinen bei PMG gar nicht und ein Token-Zugang fuer PMG wird serverseitig abgelehnt (D-03)."
+ - "Zertifikatsfehler werden nur fuer die Server geduldet, bei denen der Administrator es einzeln eingeschaltet hat; Voreinstellung ist pruefen (D-04)."
+ - "Die Modulseite und jede Anzeige lesen ausschliesslich aus dem Zwischenlager, nie live bei Proxmox (D-05)."
+ - "Der Knopf Verbindung testen nennt die Ursache in Alltagssprache: nicht erreichbar, Zugang abgelehnt, Rechte reichen nicht, Zertifikat, unerwartete Antwort (D-06)."
+ - "Ein fehlendes, anders benanntes oder falsch typisiertes Feld einer Proxmox-Antwort fuehrt zu unbekannt in der Anzeige, nie zu einem Absturz, einer leeren Seite oder einem stillen Falschwert."
+ - "Ohne angelegten Server ist die Modulseite ruhig und erklaert, dass noch keiner eingetragen ist."
+ - "Beide neuen Tabellen tragen tenantId mit RLS-Policy; rls-coverage.spec.ts und rls-access-inventory.spec.ts bleiben gruen (D-08)."
+ artifacts:
+ - apps/api/prisma/migrations/20260923140000_proxmox_server/migration.sql
+ - apps/api/src/proxmox/proxmox-auth.ts
+ - apps/api/src/proxmox/proxmox-client.service.ts
+ - apps/api/src/proxmox/proxmox-normalize.ts
+ - apps/api/src/proxmox/proxmox-scheduler.service.ts
+ - apps/api/src/proxmox/proxmox-nur-lesen.spec.ts
+ - "apps/web/src/app/(portal)/modules/proxmox/page.tsx"
+ - "apps/web/src/app/(portal)/modules/proxmox/settings/page.tsx"
+ key_links:
+ - "proxmox-auth.ts ist die EINZIGE Stelle, die Kopfzeilen und Anmelde-Cookies je Produkt baut — Klient, Verbindungstest und Planer rufen sie, keiner baut sie nach (D-03)."
+ - "proxmox-client.service.ts baut den undici-Dispatcher je Aufruf aus dem Feld tlsRejectUnauthorized genau dieser Serverzeile (D-04)."
+ - "proxmox-scheduler.service.ts haengt an onApplicationBootstrap und faechert je Mandant auf; der Controller zieht nach jedem Speichern nach (D-05)."
+ - "proxmox.controller.ts traegt @UseModule('proxmox'); die Schreibwege zusaetzlich @Roles(ADMIN, SUPER_ADMIN) (D-09)."
+ - "Jeder Datenbankzugriff laeuft ueber forTenant(); nur der Startpfad des Planers ueber forSystem() und steht in FORSYSTEM_ALLOWED_CALL_SITES (D-08)."
+---
+
+
+Das Proxmox-Modul anbinden: PVE, PBS und PMG **nur beobachten**. Der Administrator legt in
+den Moduleinstellungen beliebig viele Server an (Name, Typ, Adresse, Zugang — verschluesselt
+gespeichert). Ein Hintergrunddienst fragt sie periodisch ab und legt die Messwerte in einem
+Zwischenlager ab. Die Modulseite zeigt die Serverliste mit Auslastung und liest dabei
+ausschliesslich aus dem Zwischenlager.
+
+Purpose: Der Nutzer sieht den Zustand seiner Proxmox-Landschaft in Tessera, ohne die
+Proxmox-Oberflaechen einzeln zu oeffnen — und ohne dass Tessera je etwas an ihnen aendern kann.
+
+Output: Ein vollstaendiges Modul `proxmox` (Datenbank, Dienst, API, Hintergrundabfrage,
+Einstellungsseite, Modulseite, Dokumentation), sieben eigenstaendige Commits.
+
+## Herkunft der Entscheidungen (D-Nummern)
+
+Die D-Nummern in diesem Plan verweisen auf die elf bereits getroffenen Entscheidungen aus dem
+Auftrag (``), in derselben Reihenfolge:
+
+| ID | Entscheidung |
+|---|---|
+| D-01 | Nur beobachten — kein veraendernder Weg gegen Proxmox |
+| D-02 | Administrator legt Server an; Zugangsdaten verschluesselt, nie im Klartext zurueck |
+| D-03 | PVE/PBS: Token oder Benutzer/Passwort; PMG nur Benutzer/Passwort; Kopfzeilen aus EINER Stelle |
+| D-04 | Zertifikatsfehler nur je Server umschaltbar dulden, nie global |
+| D-05 | Zwischenlager statt Live-Abfrage; Planer nach TENDER-Muster (`onApplicationBootstrap`) |
+| D-06 | Knopf „Verbindung testen" mit Klartext-Ursache |
+| D-07 | Keine neue npm-Abhaengigkeit — `undici` ist bereits da |
+| D-08 | Mandantentrennung Pflicht: `tenantId` + RLS + Klassifikationsdoku + gruene Waechter-Tests |
+| D-09 | Zugriff ueber die normale Modulfreigabe |
+| D-10 | Oberflaechentexte Deutsch in der Sie-Form ueber next-intl; Kommentare Deutsch |
+| D-11 | Dashboard-Kachel ist NICHT in diesem Auftrag |
+
+## Ausgangswerte der Tore (gemessen 2026-09-23, vor Beginn)
+
+| Tor | Ausgangswert |
+|---|---|
+| `pnpm --filter @tessera/api test` | 77 Dateien, 1240 Tests, alle gruen |
+| `pnpm --filter @tessera/web test` | 82 Dateien, 693 Tests, alle gruen |
+| `pnpm type-check` | 4 von 4 erfolgreich |
+| `pnpm lint` | 5 von 5 erfolgreich |
+| Biome-Warnungen in `apps/web` | genau 53 (253 Dateien geprueft) |
+
+Zielwert nach jeder Aufgabe: Testzahlen **groesser oder gleich** dem Ausgangswert und gruen,
+type-check 4/4, lint 5/5, Biome-Warnungen in `apps/web` **exakt 53** — nicht mehr, nicht weniger.
+
+## Ausdruecklich NICHT im Umfang
+
+Dashboard-Kachel (D-11, kommt als eigener Auftrag ueber `WIDGET_TYPES` /
+`WIDGET_MODULE_SLUGS` / `registerWidget`), Zeitreihen und Verlaufsgrafiken (`/rrddata`),
+Eingriffe jeder Art (Start, Stopp, Sichern, Freigeben), Quarantaene-Verwaltung bei PMG.
+
+
+
+@~/.claude/gsd-core/workflows/execute-plan.md
+@~/.claude/gsd-core/templates/summary.md
+
+
+
+@.planning/quick/260923-dhh-proxmox-modul-pve-pbs-und-pmg-anbinden-n/260923-dhh-RESEARCH.md
+@.planning/STATE.md
+@CLAUDE.md
+
+Bestandsmuster, die dieser Plan wortgetreu wiederverwendet (vor der jeweiligen Aufgabe lesen,
+nicht raten):
+
+@apps/api/src/favorites/icon-discovery.service.ts
+@apps/api/src/ldap/ldap-config.service.ts
+@apps/api/src/dkv/dkv-scheduler.service.ts
+@apps/api/src/tenders/tender-scheduler.service.ts
+@apps/api/src/domaincheck/domaincheck.module.ts
+@apps/api/src/prisma/prisma-tenant.extension.ts
+
+
+
+Signaturen und Konstanten, auf die jede Aufgabe aufsetzt — so gemessen im Bestand, nicht erfunden:
+
+- `CryptoService` (`apps/api/src/crypto/crypto.service.ts`): `encrypt(plaintext: string): string`
+ und `decrypt(stored: string): string`. Format `iv:authTag:ciphertext`, alles hex,
+ Doppelpunkt-getrennt. `CryptoModule` steht bereits in `app.module.ts`.
+- Erkennungsform fuer „schon verschluesselt" (`ldap-config.service.ts:17`):
+ `/^[0-9a-f]+:[0-9a-f]+:[0-9a-f]*$/i`.
+- `forTenant(prisma, tenantId, userId?)` und `forSystem(prisma)` aus
+ `apps/api/src/prisma/prisma-tenant.extension.ts`. Konvention: lokale Konstante
+ `const tenantPrisma = forTenant(this.prisma, tenantId);` — keine andere Form, sonst schlaegt
+ `rls-access-inventory.spec.ts` fehl.
+- `undici`: `import { Agent, fetch as undiciFetch } from 'undici'`. Nodes globales `fetch`
+ ignoriert einen `Agent` aus dem npm-Paket (gemessen, `icon-discovery.service.ts:33-37`).
+- `@UseModule(slug)` aus `apps/api/src/module-registry/module.guard.ts`,
+ `@Roles(Role.ADMIN, Role.SUPER_ADMIN)` aus `apps/api/src/auth/decorators/roles.decorator.ts`.
+- `ModuleRegistryService.seedModule({ slug, name, version, category, description: {de, en}, isSystem })`
+ — Vorlage `apps/api/src/domaincheck/domaincheck.seed.ts`.
+- `CronJobClass` wird per `require('cron').CronJob` aufgeloest (pnpm-Isolation, Kommentar in
+ `dkv-scheduler.service.ts:6-16` woertlich uebernehmen).
+- RLS-Policy-Form ohne Benutzerdimension (`20260909140000`, DkvModuleConfig):
+ `CREATE POLICY tenant_isolation_policy ON "X" USING ("tenantId" = current_tenant_id());`
+- Systemlese-Form (`20260914120000`):
+ `CREATE POLICY system_read_policy ON "X" FOR SELECT USING (is_system_context());`
+- Frontend-Datenzugriff: ein Helfer `apps/web/src/lib/-api.ts` (Vorbild `dkv-api.ts`),
+ `API_URL` aus `process.env.NEXT_PUBLIC_API_URL`, `credentials: 'include'`.
+- Modulseiten liegen unter `apps/web/src/app/(portal)/modules//`, `layout.tsx` umschliesst
+ mit ``, Eintrag in `MODULE_REGISTRY`
+ (`apps/web/src/lib/module-loader.ts`).
+
+
+
+
+
+ Aufgabe 1: Ein PVE-Server per Token — von der Tabelle bis zur Modulseite
+
+apps/api/prisma/schema.prisma,
+apps/api/prisma/migrations/20260923140000_proxmox_server/migration.sql,
+apps/api/src/proxmox/proxmox.types.ts,
+apps/api/src/proxmox/proxmox-auth.ts,
+apps/api/src/proxmox/proxmox-client.service.ts,
+apps/api/src/proxmox/proxmox.service.ts,
+apps/api/src/proxmox/proxmox.controller.ts,
+apps/api/src/proxmox/proxmox.module.ts,
+apps/api/src/proxmox/proxmox.seed.ts,
+apps/api/src/proxmox/dto/proxmox-server.dto.ts,
+apps/api/src/proxmox/proxmox.service.spec.ts,
+apps/api/src/app.module.ts,
+apps/web/src/lib/proxmox-api.ts,
+apps/web/src/lib/module-loader.ts,
+apps/web/src/app/(portal)/modules/proxmox/layout.tsx,
+apps/web/src/app/(portal)/modules/proxmox/page.tsx,
+apps/web/src/messages/de.json,
+apps/web/src/messages/en.json,
+docs/mandantentrennung-zugriffsklassifikation.md
+
+
+`TESSERA_ENCRYPTION_KEY` ist gesetzt (64 Hex-Zeichen) und die Entwicklungsdatenbank ist vom
+Host erreichbar — sonst scheitert `prisma migrate dev`. Erreichbarkeit siehe
+`docs/anleitung-entwicklung.md`, Abschnitt „Datenbank vom Host erreichen"; die Datenbank hat
+keinen Host-Port, der Zugriff laeuft ueber die Container-IP mit `tessera:tessera_dev`.
+
+
+Duenner, aber durchgehender Schnitt durch ALLE Schichten, die dieses Modul anfasst — genau EIN
+Weg: ein PVE-Server, Zugang per API-Token, vom Anlegen ueber die Abfrage und das Zwischenlager
+bis zur Anzeige im Browser. Kein PBS, kein PMG, kein Benutzer/Passwort, kein Planer, keine
+Einstellungsoberflaeche, kein Bearbeiten oder Loeschen — das bauen die Aufgaben 2 bis 6 auf
+diesem bewiesenen Geruest auf. Was hier entsteht, ist Endstand, kein Wegwerfstueck: dieselbe
+Fehlerbehandlung, dieselbe Mandantenbindung, dieselben Tore wie jede spaetere Aufgabe.
+
+**Schema** (`schema.prisma`) — zwei Modelle nach dem Vorbild `CalendarSource` (mehrere
+verschluesselte Fremdzugaenge je Mandant), NICHT nach `DkvModuleConfig` (Singleton je Mandant):
+
+`ProxmoxServer`: `id` (uuid), `tenantId`, `name`, `productType` (Zeichenkette `pve|pbs|pmg`,
+kommentiert wie `CalendarSource.type`), `baseUrl`, `authMethod` (`token|password`), `tokenId`
+(nullable), `encryptedTokenSecret` (nullable, Kommentar „AES-256-GCM ciphertext
+(iv:authTag:ciphertext hex)" wie `CalendarSource.encryptedPassword`), `username` (nullable),
+`encryptedPassword` (nullable), `tlsRejectUnauthorized Boolean @default(true)` (Feldname
+woertlich von `LdapConfig`, D-04), `isActive Boolean @default(true)`,
+`pollIntervalMin Int @default(5)`, `position Int @default(0)`, `createdAt`, `updatedAt`,
+Relation `status ProxmoxServerStatus?`, `@@index([tenantId])`.
+
+`ProxmoxServerStatus`: `id`, `serverId String @unique` mit Relation auf `ProxmoxServer`
+(`onDelete: Cascade`), `tenantId`, `lastPolledAt DateTime?`, `lastOkAt DateTime?`,
+`reachable Boolean @default(false)`, `errorKind String?`, `errorDetail String?`,
+`metrics Json?` (normalisierte Messwerte), `rawSample Json?` (gekuerzte Rohantwort zur
+Fehlersuche beim Nutzer), `updatedAt`, `@@index([tenantId])`.
+
+**Migration** (`20260923140000_proxmox_server/migration.sql`) — von Hand geschrieben nach dem
+Vorbild `20260923120000_dashboard_tabs`: deutscher Kopfkommentar, der Zweck und die
+Entscheidungen benennt. Beide Tabellen mit `ENABLE`/`FORCE ROW LEVEL SECURITY` und
+`tenant_isolation_policy` in der Form OHNE Benutzerdimension
+(`USING ("tenantId" = current_tenant_id())`, Vorbild `DkvModuleConfig`) — Proxmox-Server sind
+Verwaltungsdaten des Mandanten, nicht persoenliche Daten eines Benutzers. Zusaetzlich auf
+`ProxmoxServer` (und NUR dort) `system_read_policy … FOR SELECT USING (is_system_context())`
+mit der Begruendung im Kommentar, dass der Planer aus Aufgabe 4 beim Start die aktiven Server
+ALLER Mandanten sehen muss; `ProxmoxServerStatus` bekommt sie bewusst nicht, weil dort nur je
+Mandant gebunden geschrieben wird. Indizes auf `tenantId` sowie `serverId` (unique). Rechte
+fuer `tessera_app` kommen automatisch ueber `ALTER DEFAULT PRIVILEGES` aus
+`20260909130000_rls_app_role` — im Kommentar erwaehnen, nichts tun.
+
+**`proxmox.types.ts`** — die gemeinsamen Typen: `ProxmoxProductType = 'pve' | 'pbs' | 'pmg'`,
+`ProxmoxAuthMethod = 'token' | 'password'`, `ProxmoxErrorKind =
+'netz' | 'zugang' | 'rechte' | 'zertifikat' | 'antwortform' | 'server' | 'unbekannt'` (diese
+sieben Werte landen so in der Datenbank und werden erst im Frontend uebersetzt — stabile
+Schluessel, uebersetzbarer Text), und die Ergebnisform `ProxmoxPollResult` mit
+`{ reachable, errorKind, errorDetail, metrics, rawSample }`.
+
+**`proxmox-auth.ts`** — die EINZIGE Stelle im ganzen Modul, die Anmeldeinformationen in
+Kopfzeilen uebersetzt (D-03, key_link). In dieser Aufgabe nur der Token-Zweig: eine reine
+Funktion `buildTokenAuthHeader(productType, tokenId, tokenSecret)`, die fuer `pve` das Schema
+`PVEAPIToken` mit Gleichheitszeichen vor dem Geheimnis und fuer `pbs` das Schema `PBSAPIToken`
+mit Doppelpunkt vor dem Geheimnis liefert (Recherche, Block 1) und fuer `pmg` einen Fehler
+wirft, weil PMG keine Token kennt. Die Funktion nimmt Klartext entgegen und gibt nur die
+Kopfzeile zurueck — sie protokolliert nie, sie wirft das Geheimnis nie in eine Fehlermeldung.
+
+**`proxmox-client.service.ts`** — der HTTP-Zugang, und ausschliesslich lesend (D-01).
+Genau EINE oeffentliche Datenabruf-Funktion `proxmoxGet(server, path)`, die das
+Anfrageverfahren fest auf Lesen setzt (kein Parameter dafuer, kein Durchreichen von aussen).
+Zwingend `undiciFetch` aus dem `undici`-Paket, nicht das globale `fetch` — sonst wird der
+Dispatcher stillschweigend ignoriert (gemessen, `icon-discovery.service.ts:33-37`); diesen
+Grund als deutschen Kommentar in die Datei schreiben. Der Dispatcher wird JE AUFRUF aus der
+gelesenen Serverzeile gebaut: ist `tlsRejectUnauthorized` wahr, wird kein Dispatcher
+uebergeben (Normalweg, echte Pruefung); ist es falsch, ein frischer
+`new Agent({ connect: { rejectUnauthorized: false } })` nur fuer diesen einen Aufruf (D-04).
+Ausdruecklich KEINE Modulkonstante wie in `icon-discovery.service.ts` und ausdruecklich keine
+Node-Umgebungsvariable — beides als Kommentar festhalten. Abbruch nach 8 Sekunden ueber
+`AbortController`. Keine SSRF-Adresspruefung wie `isPublicHttpUrl`: Proxmox-Server stehen
+per Definition im privaten Netz, eine solche Pruefung wuerde jede reale Adresse blockieren;
+die Absicherung ist stattdessen, dass nur ein Administrator Adressen eintragen darf (siehe
+Bedrohungsmodell T-DHH-02). Rueckgabe ist ein Ergebnisobjekt mit `ok`, `status`, `body` und
+`errorKind` — geworfen wird nichts nach aussen; Netzfehler und Zertifikatsfehler werden
+abgefangen und in `errorKind` uebersetzt.
+
+**`proxmox.service.ts`** — die Fachlogik, jeder Datenbankzugriff ueber
+`const tenantPrisma = forTenant(this.prisma, tenantId);` (Konvention woertlich, D-08):
+`createServer(tenantId, dto)` verschluesselt das Token-Geheimnis mit `this.crypto.encrypt(...)`
+und legt Server plus leere Zwischenlagerzeile an; `listWithStatus(tenantId)` liefert Server
+samt Zwischenlager OHNE jedes Geheimnisfeld (`select` ohne `encryptedTokenSecret` und
+`encryptedPassword`, nicht nachtraeglich maskiert — die Felder verlassen die Datenbank gar
+nicht erst); `pollServer(tenantId, serverId)` entschluesselt in genau EINER privaten Methode
+`decryptSecret(stored)` nach dem Vorbild `LdapConfigService.decryptBindPassword` (Form
+erkennen, unveraenderte Altwerte durchreichen), ruft fuer `pve` den Pfad
+`/api2/json/cluster/resources`, normalisiert das Ergebnis und schreibt es ins Zwischenlager.
+In dieser Aufgabe nur PVE und nur eine Grundauswertung: Anzahl Knoten, Anzahl laufender und
+gestoppter Gaeste, und je Knoten `cpu`/`maxcpu`/`mem`/`maxmem` — jeder Einzelwert nachsichtig
+gelesen (fehlt er, steht `null` im Zwischenlager und spaeter „unbekannt" in der Anzeige, nie
+ein Absturz und nie eine 0, die wie ein Messwert aussieht). Die Rohantwort wird auf hoechstens
+20 000 Zeichen gekuerzt in `rawSample` abgelegt, damit der Nutzer beim Testen an seinen echten
+Servern sieht, was tatsaechlich kam.
+
+**`proxmox.controller.ts`** — `@Controller('modules/proxmox')` und `@UseModule('proxmox')` auf
+Klassenebene (D-09, Vorbild `domaincheck.controller.ts`). Drei Wege: `GET servers` (Liste mit
+Zwischenlager, fuer jeden Benutzer mit Modulzugriff), `POST servers` und
+`POST servers/:id/poll` — beide Schreibwege zusaetzlich mit
+`@Roles(Role.ADMIN, Role.SUPER_ADMIN)`. `tenantId` kommt ausschliesslich aus `req.tenantId`,
+nie aus Body oder Query.
+
+**`dto/proxmox-server.dto.ts`** — `class-validator`: `name` nicht leer, `productType` per
+`@IsIn(['pve','pbs','pmg'])`, `baseUrl` per `@IsUrl({ protocols: ['http','https'], require_tld: false })`
+(ohne `require_tld`, weil interne Namen wie `pve.intern` sonst abgelehnt wuerden),
+`authMethod` per `@IsIn(['token','password'])`, `tlsRejectUnauthorized` optional boolesch,
+`pollIntervalMin` als Ganzzahl zwischen 1 und 1440.
+
+**`proxmox.module.ts` / `proxmox.seed.ts` / `app.module.ts`** — Vorbild Domaincheck:
+`seedProxmoxModule` mit `slug: 'proxmox'`, `name: 'Proxmox'`, `version: '1.0.0'`,
+`category: 'infrastructure'`, deutscher und englischer Beschreibung, `isSystem: true`;
+`ProxmoxModule` importiert `ModuleRegistryModule` und ruft den Seed in `onModuleInit`;
+Eintrag in `app.module.ts` unter `imports` hinter `BugReportsModule`.
+
+**Frontend** — `apps/web/src/lib/proxmox-api.ts` nach dem Vorbild `dkv-api.ts`
+(`listServers()`); `modules/proxmox/layout.tsx` mit
+``; `modules/proxmox/page.tsx` als Client-Komponente,
+die die Serverliste laedt und je Server Name, Typ, Adresse und die vorhandenen Messwerte
+anzeigt — fehlende Werte als „unbekannt", bei leerer Liste ein ruhiger Hinweis, dass noch kein
+Server eingetragen ist (kein Fehlergewitter, keine weisse Flaeche); Eintrag `proxmox` in
+`MODULE_REGISTRY` (`module-loader.ts`). Alle sichtbaren Texte ueber `useTranslations('proxmox')`
+mit neuen Schluesseln in `de.json` UND `en.json` — deutsche Texte in der Sie-Form (D-10).
+
+**Doku** — in `docs/mandantentrennung-zugriffsklassifikation.md` die neuen Fundstellen als
+Tabellenzeilen im Format `| Datei | Modell | Klasse | Stand | Begruendung |` eintragen
+(`apps/api/src/proxmox/proxmox.service.ts` / `proxmoxServer` und `proxmoxServerStatus`, Klasse
+`muss-mandantengebunden`, Stand `gebunden`), sonst schlaegt `rls-access-inventory.spec.ts` fehl.
+Die Bereichs- und Summenzeilen mit der Gate-Schleife NACHMESSEN, nicht abschreiben.
+
+Deutsche Kommentare im Code wie in den Nachbardateien (D-10). Keine neue npm-Abhaengigkeit
+(D-07) — `undici` steht bereits als direkte Abhaengigkeit in `apps/api/package.json`.
+
+
+ pnpm --filter @tessera/api exec vitest run src/proxmox src/prisma/rls-coverage.spec.ts src/prisma/rls-access-inventory.spec.ts
+ pnpm --filter @tessera/api test
+ pnpm --filter @tessera/web test
+ pnpm type-check
+
+
+`proxmox.service.spec.ts` fuehrt den ganzen Weg mit einer gefaelschten `undici`-Antwort durch
+(Vorbild der Attrappe: `icon-discovery.service.spec.ts`, `vi.mock('undici', …)`): Server
+anlegen, abfragen, Zwischenlager gelesen — und weist nach, dass (a) das Geheimnis
+verschluesselt in der Datenbank steht und in der Antwort von `listWithStatus` ueberhaupt nicht
+vorkommt, (b) bei `tlsRejectUnauthorized: true` KEIN Dispatcher uebergeben wird und bei
+`false` genau einer mit abgeschalteter Pruefung, (c) ein fehlendes Feld der Antwort zu `null`
+fuehrt und nicht zu einem Wurf. `pnpm --filter @tessera/api test` gruen mit mindestens 1240
+Tests, `pnpm --filter @tessera/web test` gruen mit mindestens 693 Tests, `pnpm type-check`
+4 von 4. Im Browser ist `/modules/infrastructure/proxmox` erreichbar und zeigt bei leerer
+Liste den ruhigen Hinweis.
+
+
+
+
+ Aufgabe 2: Zugang per Benutzer/Passwort, Fehler in Alltagssprache, Beobachtungs-Riegel
+
+apps/api/src/proxmox/proxmox-auth.ts,
+apps/api/src/proxmox/proxmox-client.service.ts,
+apps/api/src/proxmox/proxmox.service.ts,
+apps/api/src/proxmox/dto/proxmox-server.dto.ts,
+apps/api/src/proxmox/proxmox-client.service.spec.ts,
+apps/api/src/proxmox/proxmox-nur-lesen.spec.ts
+
+
+- Ticket-Anmeldung: `POST /api2/json/access/ticket` mit `username`/`password` als Formularfeldern liefert `data.ticket`; Folgeanfragen tragen das Ticket als Cookie mit produktabhaengigem Namen (`PVEAuthCookie`, `PBSAuthCookie`, `PMGAuthCookie`).
+- Kein `CSRFPreventionToken` wird jemals mitgesendet — dieses Modul liest nur, und fuer Leseanfragen verlangt Proxmox ihn laut offizieller Doku nicht.
+- Ein Server vom Typ `pmg` mit `authMethod: 'token'` wird beim Anlegen und beim Bearbeiten mit einer deutschen Klartextmeldung abgelehnt (400), nicht erst beim Abfragen.
+- Antwortstatus 401 wird zu `errorKind: 'zugang'`, 403 zu `'rechte'`, 404 zu `'antwortform'` mit dem Hinweis auf eine falsche Adresse, 5xx zu `'server'`.
+- Ein geworfener Netzfehler ohne Antwort (Verbindung verweigert, Zeitablauf, Name nicht aufloesbar) wird zu `errorKind: 'netz'`.
+- Ein Zertifikatsfehler (Meldungstext enthaelt eine der bekannten Zertifikatskennungen) wird zu `errorKind: 'zertifikat'` und NICHT zu `'netz'`.
+- Eine Antwort, die kein JSON ist (HTML-Anmeldeseite, leerer Rumpf), wird zu `errorKind: 'antwortform'` — kein geworfener Parserfehler, kein Absturz.
+- Laeuft ein Ticket ab (401 bei `authMethod: 'password'`), wird GENAU EINMAL neu angemeldet und die Abfrage wiederholt; erst ein zweites 401 wird zu `errorKind: 'zugang'`.
+- Keine Fehlermeldung, kein Protokolleintrag und kein `rawSample` enthaelt jemals Passwort, Token-Geheimnis oder das Ticket.
+- Im gesamten Verzeichnis `apps/api/src/proxmox` gibt es ausserhalb der Ticket-Anmeldung keine einzige Stelle, die ein anderes Anfrageverfahren als Lesen an Proxmox schickt.
+
+
+Zuerst die Tests aus `` in `proxmox-client.service.spec.ts` schreiben (rot), dann
+implementieren. Die `undici`-Attrappe wie in `icon-discovery.service.spec.ts`.
+
+`proxmox-auth.ts` waechst um den Ticket-Zweig und bleibt dabei die EINZIGE Stelle, die
+Kopfzeilen und Cookies baut (D-03, key_link): `buildTokenAuthHeader` wie in Aufgabe 1, neu
+`loginTicket(server, password)` und `buildTicketCookieHeader(productType, ticket)`. Die
+Cookie-Namen je Produkt stehen als benannte Konstante in dieser einen Datei, mit deutschem
+Kommentar, dass die Namen fuer PBS und PMG aus der Recherche nur abgeleitet sind (Annahme A2)
+und der Nutzer sie an seinen echten Servern bestaetigt — steht dort ein anderer Name, ist es
+genau diese eine Konstante, die angepasst wird.
+
+`loginTicket` ist die EINZIGE Stelle im Modul, die eine nicht-lesende Anfrage an Proxmox
+schickt, und sie aendert dort nichts — sie holt nur einen Nachweis ab (D-01). Diesen
+Sonderstatus als deutschen Kommentar in der Datei festhalten.
+
+`proxmox-client.service.ts` bekommt die Fehler-Uebersetzung: eine reine Funktion
+`classifyFailure(status, thrownError)`, die genau die sieben Werte aus `ProxmoxErrorKind`
+liefert, und eine Funktion `parseJsonLenient(text)`, die bei nicht-JSON kein Werfen zulaesst
+sondern das Scheitern meldet. Die Zertifikatserkennung laeuft ueber die bekannten
+Fehlerkennungen von Node/undici (selbstsigniert, abgelaufen, Name passt nicht, unbekannter
+Aussteller) — im Zweifel `'zertifikat'` nur bei eindeutigem Treffer, sonst `'netz'`.
+Zusaetzlich `errorDetail` als KURZE, deutsche Ergaenzung (Statuszahl, Fehlerkennung), aus der
+niemals ein Geheimnis hervorgeht; die Weiterverarbeitung des `errors`-Feldes der Proxmox-Antwort
+ist erlaubt, aber gekuerzt auf 500 Zeichen.
+
+Die Ticket-Erneuerung sitzt in `proxmox.service.ts` (nicht im Klienten): ein Zaehler, der genau
+einen zweiten Versuch erlaubt. Der Grund als Kommentar: bei Ticketdauer von zwei Stunden
+erzeugt ein normaler Ablauf sonst alle zwei Stunden einen Fehlalarm.
+
+`dto/proxmox-server.dto.ts` bekommt die produktabhaengige Pruefung (PMG plus Token ist
+ungueltig) — Pflichtfelder je nach `authMethod` mit `@ValidateIf`, damit ein Token-Zugang
+`tokenId` und Geheimnis verlangt und ein Passwort-Zugang `username` und Passwort.
+
+`proxmox-nur-lesen.spec.ts` ist der maschinelle Riegel zu D-01, gebaut nach dem Vorbild von
+`apps/api/src/prisma/rls-access-inventory.spec.ts` (Test liest den Quelltext, nicht das
+Laufzeitverhalten): er liest alle `.ts`-Dateien unter `apps/api/src/proxmox`, entfernt vor
+dem Zaehlen Kommentarzeilen und Zeichenkettenliterale aus Testdateien, und prueft zwei
+Aussagen — erstens, dass die Summe der Stellen, die ein Anfrageverfahren an `undiciFetch`
+uebergeben, genau EINS ist und in `proxmox-auth.ts` liegt; zweitens, dass jeder gegen einen
+Proxmox-Pfad gebaute Aufruf ausser dieser einen ueber `proxmoxGet` laeuft. Die erwartete Zahl
+steht als benannte Konstante mit ausgeschriebener Begruendung in der Testdatei, damit eine
+spaetere Erhoehung eine bewusste Entscheidung erzwingt und nicht unbemerkt durchrutscht.
+
+
+ pnpm --filter @tessera/api exec vitest run src/proxmox
+ pnpm --filter @tessera/api test
+ pnpm type-check
+
+
+Alle Punkte aus `` sind je durch mindestens einen Test belegt.
+`proxmox-nur-lesen.spec.ts` ist gruen und wuerde rot, wenn irgendwo im Modul eine zweite
+nicht-lesende Anfrage an Proxmox entstuende. `pnpm --filter @tessera/api test` gruen mit
+mindestens 1240 Tests, `pnpm type-check` 4 von 4.
+
+
+
+
+ Aufgabe 3: PBS und PMG auswerten — nachsichtig gegen jede Antwortform
+
+apps/api/src/proxmox/proxmox-normalize.ts,
+apps/api/src/proxmox/proxmox.service.ts,
+apps/api/src/proxmox/proxmox.types.ts,
+apps/api/src/proxmox/proxmox-normalize.spec.ts
+
+
+- PVE: aus `/api2/json/cluster/resources` entstehen Knotenzahl, Zahl laufender und gestoppter Gaeste, je Knoten Prozessorlast und Speicherbelegung, je Speicherort Belegung.
+- PBS: aus `/api2/json/status/datastore-usage` entsteht je Datenspeicher Gesamt, Belegt, Frei; aus `/api2/json/admin/datastore/{store}/snapshots` je Datenspeicher der Zeitpunkt der letzten Sicherung und das Ergebnis der letzten Pruefung.
+- PMG: aus `/api2/json/statistics/mail` entstehen die Tageszahlen eingehend, ausgehend, Spam, Viren.
+- Fehlt ein erwartetes Feld vollstaendig, ist der Einzelwert `null` — nie `0`, nie `NaN`, nie ein Wurf.
+- Kommt eine Zahl als Zeichenkette (`"42"`, `"0.37"`), wird sie als Zahl gelesen; kommt sie als nicht umwandelbarer Text, ist der Wert `null`.
+- Ist die gesamte Antwort eine Zeichenkette, ein Array statt eines Objekts, `null` oder leer, entsteht ein leeres Messwertobjekt mit `errorKind: 'antwortform'` — nie ein Wurf.
+- Heisst ein Feld anders als erwartet, bleibt der zugehoerige Einzelwert `null` und die gekuerzte Rohantwort bleibt in `rawSample` erhalten, damit der Nutzer am echten Server erkennt, wie das Feld wirklich heisst.
+- Ein Datenspeicher ohne Sicherungen ergibt „noch keine Sicherung" und keinen Fehler.
+- Ein PBS-Server mit vielen Datenspeichern erzeugt hoechstens 10 Folgeabfragen je Durchlauf.
+
+
+Zuerst `proxmox-normalize.spec.ts` schreiben (rot), mit ERFUNDENEN Antworten in der von der
+Recherche dokumentierten Form — es gibt in dieser Umgebung keinen echten Proxmox-Server, und
+es wird auch keiner angefragt. Je Punkt aus `` mindestens ein Fall, und zusaetzlich
+je Produkt ein Fall „Feld fehlt", „Zahl kommt als Zeichenkette" und „Antwort ist HTML statt
+JSON".
+
+`proxmox-normalize.ts` traegt die nachsichtigen Leser als reine Funktionen ohne
+Datenbankbezug: `readNumber(value)` (Zahl, umwandelbare Zeichenkette, sonst `null`),
+`readText(value)`, `readBool(value)` und `readList(value)` (liefert bei allem, was kein Array
+ist, eine leere Liste). Darauf setzen `normalizePve(body)`, `normalizePbs(usage, snapshots)`
+und `normalizePmg(body)` auf. Keine dieser Funktionen wirft jemals — der gesamte Umgang mit
+einer unerwarteten Form ist ein Rueckgabewert, nicht eine Ausnahme; als deutscher Kommentar
+festhalten, warum: der Nutzer prueft dieses Modul allein an seinen echten Servern, und ein Wurf
+wuerde ihm eine leere Seite statt eines Hinweises zeigen.
+
+Die Feldnamen von PBS und PMG sind aus der Recherche nur abgeleitet (Annahmen A2, A3, A5). In
+`proxmox-normalize.ts` je Produkt eine benannte Konstante mit den erwarteten Feldnamen und
+einem deutschen Kommentar, dass genau diese Liste anzupassen ist, falls der echte Server
+andere Namen liefert — dadurch gibt es EINE Stelle zum Nachziehen statt verstreuter
+Zeichenketten im Auswertungscode. Wo ein Feld unter mehreren plausiblen Namen auftreten kann,
+darf die Konstante mehrere Namen in Reihenfolge nennen, und der Leser nimmt den ersten
+vorhandenen.
+
+`proxmox.service.ts` waechst um die produktabhaengige Abfragefolge: `pve` eine Abfrage, `pbs`
+die Belegungsabfrage plus je Datenspeicher hoechstens zehn Folgeabfragen (Deckel als benannte
+Konstante mit Begruendung), `pmg` eine Abfrage. Jede dieser Abfragen laeuft ueber `proxmoxGet`
+— keine neue Aufrufform (Riegel aus Aufgabe 2 bleibt gruen). Das Zwischenlager bekommt je
+Produkt seine Messwertform; `ProxmoxMetrics` in `proxmox.types.ts` als unterscheidbare Union
+ueber `productType`, damit das Frontend typsicher verzweigen kann.
+
+
+ pnpm --filter @tessera/api exec vitest run src/proxmox
+ pnpm --filter @tessera/api test
+ pnpm type-check
+
+
+Alle Punkte aus `` sind je durch mindestens einen Test belegt, einschliesslich der
+vier ausdruecklich verlangten Fehlformen (Feld fehlt, Zahl als Zeichenkette, HTML statt JSON,
+Statuscodes 401/403/404/500 — Letztere aus Aufgabe 2 weiterhin gruen).
+`proxmox-nur-lesen.spec.ts` bleibt gruen. `pnpm --filter @tessera/api test` gruen mit
+mindestens 1240 Tests, `pnpm type-check` 4 von 4.
+
+
+
+
+ Aufgabe 4: Hintergrundabfrage je Mandant und der Knopf „Verbindung testen"
+
+apps/api/src/proxmox/proxmox-scheduler.service.ts,
+apps/api/src/proxmox/proxmox.service.ts,
+apps/api/src/proxmox/proxmox.controller.ts,
+apps/api/src/proxmox/proxmox.module.ts,
+apps/api/src/proxmox/proxmox-scheduler.service.spec.ts,
+apps/api/src/prisma/rls-access-inventory.spec.ts,
+docs/mandantentrennung-zugriffsklassifikation.md
+
+
+- Beim Start registriert der Planer je Mandant mit mindestens einem aktiven Server genau einen Auftrag unter dem Registry-Namen `proxmox-poll:`.
+- Der Tick eines Mandanten geht ueber dessen Server und fragt jeden einzeln ab; ein fehlgeschlagener Server bricht die Schleife nicht ab.
+- Ein zweiter Mandant verdraengt den Auftrag des ersten nicht — beide Auftraege bestehen nebeneinander.
+- Keine aktiven Server bedeutet: kein Auftrag, ein Protokolleintrag, kein Fehler, nichts geloescht.
+- Nach dem Speichern eines Servers zieht der Controller den Auftrag dieses Mandanten sofort nach — ohne Neustart.
+- Der Planer haengt an `onApplicationBootstrap`, nicht an `onModuleInit`.
+- Ein Fehler beim Start wird gefangen und protokolliert, nie weitergeworfen — die Anwendung startet trotzdem.
+- `POST servers/:id/test` liefert bei Erfolg eine Erfolgsmeldung und bei Misserfolg genau einen der sieben Fehlerschluessel samt kurzer Ergaenzung, ohne den Zwischenlagerstand zu ueberschreiben.
+- `POST servers/:id/poll` verweigert einen zweiten Durchlauf innerhalb von zehn Sekunden und liefert stattdessen den vorhandenen Zwischenlagerstand.
+
+
+Zuerst `proxmox-scheduler.service.spec.ts` schreiben (rot) — Vorbild
+`dkv-scheduler.service.spec.ts`, je Aussage aus `` ein Test.
+
+`proxmox-scheduler.service.ts` kombiniert die zwei Bestandsmuster (Recherche, Block 3): das
+Mandanten-Auffaechern von `DkvSchedulerService` (ein Auftrag je Mandant, Registry-Name mit
+Mandantenkennung als Suffix — die Vorgaengerform mit EINEM Auftragsfeld war genau der Fehler
+WINDOWS #21) und die Lebenszyklus-Stufe von `TenderSchedulerService`
+(`implements OnApplicationBootstrap`). Den Grund fuer `onApplicationBootstrap` als deutschen
+Kommentar uebernehmen: die Reihenfolge der `onModuleInit`-Haken zwischen Modulen ist nicht
+festgelegt, und die Erfahrung „frische Datenbank ingestiert nichts bis zum zweiten Neustart"
+steht bereits im Projektgedaechtnis. Die Aufloesung von `CronJob` ueber `require('cron')`
+samt Kommentar woertlich aus `dkv-scheduler.service.ts` uebernehmen (pnpm-Isolation).
+
+Anders als bei DKV ist ein Mandant NICHT gleich ein Server: der Tick eines Mandanten geht ueber
+dessen Serverzeilen. Das Abfrageintervall eines Mandanten ist das kleinste `pollIntervalMin`
+seiner aktiven Server. Ein fehlgeschlagener Server schreibt seinen Fehler ins Zwischenlager
+und die Schleife laeuft weiter — dieser Punkt ausdruecklich als Test.
+
+Der Startpfad `loadActiveServersForScheduler()` in `proxmox.service.ts` ist der EINZIGE
+Systemkontext-Aufruf des Moduls: `const systemPrisma = forSystem(this.prisma);`, nur lesend,
+ohne `include` auf das Zwischenlager (die Zwischenlagertabelle hat bewusst keine
+Systemlese-Regel — das Nachziehen laeuft je Zeile gebunden). Danach wird je Mandant und je
+Server ueber `forTenant(this.prisma, tenantId)` geschrieben, Muster
+`DkvSchedulerService`/`DashboardImagesService` (einmal lesen, viele bedienen). Diesen einen
+Aufruf in `FORSYSTEM_ALLOWED_CALL_SITES` in `apps/api/src/prisma/rls-access-inventory.spec.ts`
+eintragen (`apps/api/src/proxmox/proxmox.service.ts` mit Anzahl 1) und den Kopfkommentar
+derselben Datei um den neuen Fall ergaenzen, wie es die bestehenden sieben Faelle vormachen —
+sonst schlaegt der Waechter „ein Anfrageweg darf den Systemkontext nie rufen" fehl. In
+`docs/mandantentrennung-zugriffsklassifikation.md` den Stand der Zeile
+`proxmox.service.ts`/`proxmoxServer` von `gebunden` auf `system-gebunden` heben, mit derselben
+Begruendungsform wie bei `dashboard-images.service.ts`; Bereichs- und Summenzeilen mit der
+Gate-Schleife nachmessen.
+
+`proxmox.controller.ts` bekommt `POST servers/:id/test` (ADMIN/SUPER_ADMIN) — es benutzt
+denselben Klienten und dieselbe Fehleruebersetzung wie der Planer, schreibt aber NICHT ins
+Zwischenlager, damit ein Testklick den zuletzt gemessenen Stand nicht ueberschreibt (Vorbild
+`TenderEmailConfigService.testConnection` und der LDAP-Test). Zusaetzlich ruft der Controller
+nach jedem erfolgreichen Anlegen und Speichern `scheduler.setInterval(tenantId)` — Vorbild
+`DkvController`. `POST servers/:id/poll` bekommt die Zehn-Sekunden-Sperre als Schutz davor,
+dass ein Klick in der Oberflaeche zu ungebremsten Anfragen gegen die Fremd-API wird
+(Bedrohungsmodell T-DHH-06).
+
+`proxmox.module.ts` nimmt den Planer in `providers` auf; `ScheduleModule` ist bereits global
+in `app.module.ts` registriert — nichts zusaetzlich einzurichten.
+
+
+ pnpm --filter @tessera/api exec vitest run src/proxmox src/prisma/rls-access-inventory.spec.ts src/prisma/rls-coverage.spec.ts
+ pnpm --filter @tessera/api test
+ pnpm type-check
+
+
+Alle Punkte aus `` sind je durch mindestens einen Test belegt.
+`rls-access-inventory.spec.ts` und `rls-coverage.spec.ts` sind gruen, einschliesslich des
+neuen Erlaubnislisten-Eintrags und der nachgezogenen Dokumentationszeilen.
+`proxmox-nur-lesen.spec.ts` bleibt gruen. `pnpm --filter @tessera/api test` gruen mit
+mindestens 1240 Tests, `pnpm type-check` 4 von 4.
+
+
+
+
+ Aufgabe 5: Einstellungsseite — Server anlegen, bearbeiten, loeschen, testen
+
+apps/api/src/proxmox/proxmox.controller.ts,
+apps/api/src/proxmox/proxmox.service.ts,
+apps/api/src/proxmox/proxmox.service.spec.ts,
+apps/web/src/lib/proxmox-api.ts,
+apps/web/src/app/(portal)/modules/proxmox/settings/page.tsx,
+apps/web/src/app/(portal)/modules/proxmox/settings/components/ServerForm.tsx,
+apps/web/src/app/(portal)/modules/proxmox/settings/components/ServerForm.test.tsx,
+apps/web/src/messages/de.json,
+apps/web/src/messages/en.json
+
+
+- Bei Typ `pmg` erscheint die Auswahl „API-Token" im Formular gar nicht; nur Benutzer und Passwort sind zu sehen.
+- Bei Typ `pve` oder `pbs` und Auswahl „API-Token" erscheinen Token-Kennung und Token-Geheimnis; bei Auswahl „Benutzer/Passwort" stattdessen Benutzer und Passwort.
+- Ein gespeichertes Geheimnis wird beim Bearbeiten nie im Klartext angezeigt; das Feld ist leer und ein leer gelassenes Feld laesst das gespeicherte Geheimnis unveraendert.
+- Der Schalter fuer die Zertifikatspruefung steht beim Anlegen auf „pruefen" und traegt einen erklaerenden Hinweis, dass die Ausnahme nur fuer diesen einen Server gilt.
+- Der Knopf „Verbindung testen" zeigt bei Erfolg eine gruene Bestaetigung und bei Misserfolg den Klartext der Ursache in der Sie-Form.
+- Ein Benutzer ohne Verwaltungsrolle sieht die Einstellungsseite nicht, sondern einen Hinweis.
+- Loeschen verlangt eine Rueckfrage und entfernt Server samt Zwischenlagerzeile.
+
+
+Zuerst `ServerForm.test.tsx` schreiben (rot), Vorbild
+`modules/tender-radar/settings/components/EmailAlertConfigForm.test.tsx` und
+`modules/dkv-fleet/settings/components/InboxConfigForm.tsx`.
+
+Backend: `proxmox.controller.ts` und `proxmox.service.ts` um `PUT servers/:id` und
+`DELETE servers/:id` ergaenzen, beide mit `@Roles(Role.ADMIN, Role.SUPER_ADMIN)` und beide
+ueber `forTenant()`. Beim Aendern gilt dieselbe Regel wie bei
+`LdapConfigService.updateConfig`: ein NICHT gesendetes Geheimnisfeld laesst den gespeicherten
+Wert unveraendert, eine LEERE Zeichenkette bedeutet „loeschen" und ein gefuellter Wert wird
+neu verschluesselt. Die Ablehnung „PMG mit Token" gilt auch hier. Das Loeschen entfernt die
+Zwischenlagerzeile ueber die Fremdschluesselregel mit Loeschweitergabe und zieht anschliessend
+den Auftrag des Mandanten nach.
+
+Frontend: `settings/page.tsx` nach dem Muster von
+`modules/tender-radar/settings/page.tsx` — Rollenpruefung ausschliesslich zur Anzeige, mit
+Ladezustand solange die Rolle unbekannt ist, damit die Verwaltungsteile fuer einen normalen
+Benutzer nie kurz aufblitzen; der verbindliche Riegel bleibt serverseitig. Darin die
+Serverliste und das Formular `ServerForm.tsx`: Name, Typ (drei Knoepfe oder Auswahl),
+Adresse, Zugangsart, die typabhaengigen Zugangsfelder, Abfrageintervall, Schalter fuer die
+Zertifikatspruefung, aktiv/inaktiv. Der Knopf „Verbindung testen" ruft
+`POST servers/:id/test` und zeigt das Ergebnis direkt beim Formular. Die Uebersetzung der
+sieben Fehlerschluessel liegt im Frontend unter `proxmox.errors.*` — deutsche Texte in der
+Sie-Form (D-10), englische Entsprechungen in `en.json`; die Texte nennen die Ursache und den
+naechsten Schritt, ohne Fachbegriffe (Beispielform fuer `zugang`: „Der Zugang wurde
+abgelehnt. Bitte pruefen Sie Benutzername und Passwort beziehungsweise die Token-Angaben.").
+`proxmox-api.ts` bekommt `createServer`, `updateServer`, `deleteServer`, `testServer`,
+`pollServer`.
+
+Biome-Warnungen in `apps/web` muessen danach exakt 53 bleiben — neue Formulareingaben brauchen
+daher von Anfang an die im Bestand ueblichen Beschriftungsbezuege und Tastaturbedienbarkeit.
+
+
+ pnpm --filter @tessera/web exec vitest run proxmox
+ pnpm --filter @tessera/api test
+ pnpm --filter @tessera/web test
+ pnpm lint
+ pnpm --filter @tessera/web exec biome lint . 2>&1 | grep -c 'Found 53 warnings'
+
+
+Alle Punkte aus `` sind je durch mindestens einen Test belegt.
+`pnpm --filter @tessera/web test` gruen mit mindestens 693 Tests,
+`pnpm --filter @tessera/api test` gruen mit mindestens 1240 Tests, `pnpm lint` 5 von 5, und
+`biome lint` in `apps/web` meldet unveraendert 53 Warnungen.
+
+
+
+
+ Aufgabe 6: Modulseite — Serverliste mit Auslastung, Klartext bei Stoerungen
+
+apps/web/src/app/(portal)/modules/proxmox/page.tsx,
+apps/web/src/app/(portal)/modules/proxmox/components/ServerCard.tsx,
+apps/web/src/app/(portal)/modules/proxmox/components/ServerCard.test.tsx,
+apps/web/src/lib/proxmox-api.ts,
+apps/web/src/messages/de.json,
+apps/web/src/messages/en.json
+
+
+- Ohne eingetragenen Server zeigt die Seite einen ruhigen Hinweis mit dem Weg zu den Einstellungen — keine Fehlermeldung, keine leere Flaeche.
+- Ein PVE-Server zeigt Knotenzahl, laufende und gestoppte Gaeste sowie je Knoten Prozessorlast und Speicherbelegung.
+- Ein PBS-Server zeigt je Datenspeicher Belegung, letzte Sicherung und Ergebnis der letzten Pruefung.
+- Ein PMG-Server zeigt die Tageszahlen eingehend, ausgehend, Spam und Viren.
+- Ein Messwert, der `null` ist, erscheint als „unbekannt" — nie als `0`, nie als leeres Feld, nie als `NaN`.
+- Ein Server mit `reachable: false` zeigt den Klartext seiner Ursache und daneben den Zeitpunkt der letzten erfolgreichen Messung, falls es eine gab.
+- Der Zeitpunkt der letzten Abfrage steht bei jedem Server.
+- Zaehlerfelder sind ausdruecklich als „gesamt seit Start" beschriftet, nicht als aktueller Durchsatz.
+- Der Knopf „Jetzt aktualisieren" loest eine Abfrage aus und laedt danach die Liste neu; waehrend des Laufs ist er gesperrt.
+
+
+Zuerst `ServerCard.test.tsx` schreiben (rot) — je Punkt aus `` ein Fall, mit
+erfundenen Zwischenlagerstaenden je Produkttyp, einschliesslich eines Standes, in dem jeder
+Einzelwert `null` ist.
+
+`ServerCard.tsx` ist die Anzeige EINES Servers und verzweigt ueber `productType` auf der
+unterscheidbaren Union aus Aufgabe 3. Eine gemeinsame kleine Hilfe stellt jeden Einzelwert
+dar: ist er `null` oder `undefined`, erscheint der uebersetzte Text „unbekannt"; sonst der
+Wert mit seiner Einheit (Prozentwerte gerundet, Byte-Werte in lesbarer Form). Diese Hilfe ist
+die einzige Stelle, die einen Messwert in Text verwandelt — dadurch kann kein Zweig versehentlich
+eine `0` anzeigen, wo nichts gemessen wurde. Den Grund als deutschen Kommentar festhalten: die
+Feldnamen von PBS und PMG sind bis zur Pruefung am echten Server nur abgeleitet, und ein still
+falscher Wert waere schlimmer als ein ehrliches „unbekannt".
+
+Die Zaehlerfelder aus `cluster/resources` sind kumulative Werte seit dem Start eines Gastes,
+keine Rate (Recherche, Fallstricke) — die Beschriftung sagt das ausdruecklich, damit der
+Nutzer sie nicht als aktuellen Durchsatz liest.
+
+`page.tsx` zeigt die Serverliste, oben den Knopf „Jetzt aktualisieren", und fuer Benutzer mit
+Verwaltungsrolle einen Verweis auf die Einstellungsseite. Bei leerer Liste der ruhige Hinweis.
+Schlaegt der Listenabruf selbst fehl, erscheint eine einzelne verstaendliche Meldung, nicht
+mehrere. Alle Texte ueber `useTranslations('proxmox')` in `de.json` UND `en.json`, deutsch in
+der Sie-Form (D-10).
+
+Biome-Warnungen in `apps/web` bleiben exakt 53.
+
+
+ pnpm --filter @tessera/web exec vitest run proxmox
+ pnpm --filter @tessera/web test
+ pnpm type-check
+ pnpm --filter @tessera/web exec biome lint . 2>&1 | grep -c 'Found 53 warnings'
+
+
+Im Browser `/modules/infrastructure/proxmox` oeffnen: ohne Server steht dort der ruhige
+Hinweis; nach dem Anlegen eines Servers in den Einstellungen erscheint er in der Liste, und
+ein absichtlich falsch eingetragener Zugang zeigt Klartext statt einer leeren Flaeche.
+
+
+Alle Punkte aus `` sind je durch mindestens einen Test belegt.
+`pnpm --filter @tessera/web test` gruen mit mindestens 693 Tests, `pnpm type-check` 4 von 4,
+`biome lint` in `apps/web` unveraendert 53 Warnungen.
+
+
+
+
+ Aufgabe 7: Dokumentation und Nachmessung aller Tore
+
+docs/anleitung-entwicklung.md,
+docs/anwenderhandbuch.md,
+docs/mandantentrennung-zugriffsklassifikation.md
+
+
+`docs/anwenderhandbuch.md` bekommt einen Abschnitt zum Proxmox-Modul in Alltagssprache und in
+der Sie-Form (D-10): was das Modul zeigt, wie ein Server in den Einstellungen angelegt wird
+(Name, Typ, Adresse, Zugang), welche NUR-LESE-Rolle im jeweiligen Produkt zu vergeben ist
+(PVE `PVEAuditor`, PBS `Audit` beziehungsweise `DatastoreAudit`, PMG `Auditor`), dass bei PMG
+nur Benutzer und Passwort moeglich sind, wozu der Schalter fuer die Zertifikatspruefung da ist
+und dass er nur fuer genau diesen einen Server gilt, was der Knopf „Verbindung testen" sagt
+und was „unbekannt" bei einem Messwert bedeutet. Ausdruecklich festhalten: Tessera veraendert
+bei Proxmox nichts, es schaut nur zu (D-01).
+
+`docs/anleitung-entwicklung.md` bekommt im Abschnitt „So entsteht ein neues Modul" einen
+Hinweis auf `proxmox` als Vorlage fuer ein Modul mit Fremdsystem-Zugaengen und
+Hintergrundabfrage, und an geeigneter Stelle den Merksatz zur `undici`-Falle (globales `fetch`
+ignoriert einen Dispatcher aus dem npm-Paket), falls er dort noch nicht steht.
+
+`docs/mandantentrennung-zugriffsklassifikation.md` abschliessend nachziehen: den neuen Bereich
+`proxmox` als eigene Zeile in der Bereichsuebersicht und die Summenzeile — beides mit der
+Gate-Schleife NACHGEMESSEN, nicht abgeschrieben, und mit dem Auftragskuerzel `260923-dhh`
+versehen wie die bestehenden Eintraege.
+
+Danach alle Tore einmal vollstaendig durchlaufen und die Endzahlen in der Zusammenfassung
+gegen die Ausgangswerte aus dem `` stellen: api-Tests, web-Tests, type-check,
+lint, Biome-Warnungen in `apps/web`. Eine Verschlechterung an irgendeinem Tor ist ein
+Abbruchgrund, keine Randnotiz.
+
+
+ pnpm --filter @tessera/api test
+ pnpm --filter @tessera/web test
+ pnpm type-check
+ pnpm lint
+ pnpm --filter @tessera/web exec biome lint . 2>&1 | grep -c 'Found 53 warnings'
+
+
+Anwenderhandbuch und Entwicklungsanleitung beschreiben das Modul; die Klassifikationstabelle
+ist nachgemessen und `rls-access-inventory.spec.ts` gruen. Endzahlen dokumentiert:
+api-Tests gruen und mindestens 1240, web-Tests gruen und mindestens 693, type-check 4 von 4,
+lint 5 von 5, Biome-Warnungen in `apps/web` exakt 53.
+
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| Browser -> Tessera-API | Der Administrator sendet Serveradressen und Zugangsdaten; jeder Benutzer mit Modulfreigabe liest die Serverliste |
+| Tessera-API -> Proxmox (PVE/PBS/PMG) | Ausgehende Verbindung in das interne Netz mit einem Geheimnis im Gepaeck; Gegenstelle ist nicht von Tessera kontrolliert |
+| Tessera-API -> PostgreSQL | Verschluesselte Zugangsdaten und Messwerte; Mandantentrennung ueber RLS |
+| Mandant A -> Mandant B | Zwei Mandanten duerfen die Proxmox-Zugaenge des jeweils anderen nie sehen |
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
+|-----------|----------|-----------|----------|-------------|-----------------|
+| T-DHH-01 | Information Disclosure | Zugangsdaten in Antwort, Protokoll und Fehlermeldung | critical | mitigate | Aufgabe 1: `listWithStatus` waehlt `encryptedTokenSecret`/`encryptedPassword` per `select` gar nicht erst aus (nicht nachtraeglich maskiert). Aufgabe 2: `proxmox-auth.ts` protokolliert nie, `errorDetail` traegt nur Statuszahl und Fehlerkennung, `rawSample` ist auf 20 000 Zeichen gekuerzt und enthaelt nur Antwortdaten, nie die gesendete Kopfzeile. Test in Aufgabe 1/2: keine Geheimnisform in Antwort und Meldung |
+| T-DHH-02 | Spoofing / SSRF | Vom Administrator eingetragene Adresse | high | mitigate | Nur ADMIN/SUPER_ADMIN duerfen Adressen eintragen (`@Roles` auf allen Schreibwegen, Aufgabe 1/5) — damit ist jede Adresse eine bewusste Freigabe (D-02). Adressform per `@IsUrl` auf `http`/`https` begrenzt. BEWUSST KEINE Privat-IP-Sperre wie `isPublicHttpUrl`: Proxmox steht per Definition im privaten Netz, eine solche Sperre wuerde das Modul unbrauchbar machen; die Begruendung steht als Kommentar in `proxmox-client.service.ts`. Abbruch nach 8 Sekunden begrenzt den Missbrauch als Portscanner |
+| T-DHH-03 | Information Disclosure | Zertifikats-Ausnahme reicht weiter als gewollt | high | mitigate | Aufgabe 1: Dispatcher wird JE AUFRUF aus dem Feld `tlsRejectUnauthorized` genau dieser Serverzeile gebaut; Voreinstellung `true`. Keine Modulkonstante, keine Node-Umgebungsvariable. Test: bei `true` wird kein Dispatcher uebergeben, bei `false` genau einer mit abgeschalteter Pruefung — und die Ausnahme eines Servers wirkt nicht auf einen zweiten |
+| T-DHH-04 | Elevation of Privilege | Fremder Mandant liest Proxmox-Zugaenge | critical | mitigate | Aufgabe 1: `tenantId` auf beiden Tabellen, `tenant_isolation_policy` in der Migration, jeder Zugriff ueber `forTenant()`. Aufgabe 4: der einzige `forSystem()`-Aufruf ist der Startpfad des Planers, in `FORSYSTEM_ALLOWED_CALL_SITES` eingetragen und rein lesend; geschrieben wird je Zeile gebunden. Gates: `rls-coverage.spec.ts`, `rls-access-inventory.spec.ts` |
+| T-DHH-05 | Elevation of Privilege | Rechteausweitung ueber das Modul | high | mitigate | Aufgabe 1: `@UseModule('proxmox')` auf Klassenebene (Aktivierung UND Freigabe, D-09), zusaetzlich `@Roles(ADMIN, SUPER_ADMIN)` auf jedem Schreibweg. `tenantId` und Rolle kommen ausschliesslich aus dem geprueften Sitzungsnachweis, nie aus Body oder Query. Die Rollenpruefung im Frontend (Aufgabe 5) ist reine Anzeige und ersetzt nichts |
+| T-DHH-06 | Denial of Service | Ungebremster Nutzer-Auslöser gegen die Fremd-API | medium | mitigate | Aufgabe 4: `POST servers/:id/poll` sperrt einen zweiten Durchlauf innerhalb von zehn Sekunden und liefert stattdessen den Zwischenlagerstand. Regulaer fragt ausschliesslich der Planer mit begrenzter Frequenz ab; jede Anzeige liest aus dem Zwischenlager (D-05). Aufgabe 3: Deckel von zehn Folgeabfragen je PBS-Durchlauf |
+| T-DHH-07 | Tampering | Ein veraendernder Weg gegen Proxmox entsteht (heute oder spaeter) | high | mitigate | Aufgabe 1: nur eine Datenabruf-Funktion `proxmoxGet`, Verfahren fest verdrahtet. Aufgabe 2: `proxmox-nur-lesen.spec.ts` zaehlt maschinell nach, dass die einzige nicht-lesende Anfrage die Ticket-Anmeldung ist, mit benannter Erwartungszahl und ausgeschriebener Begruendung — eine spaetere Erhoehung erzwingt eine bewusste Entscheidung (D-01) |
+| T-DHH-08 | Tampering | Zwischenlager zeigt still einen Falschwert | medium | mitigate | Aufgabe 3: jeder Einzelwert wird nachsichtig gelesen und ist bei fehlendem oder unbrauchbarem Feld `null`; Aufgabe 6: `null` erscheint als „unbekannt", nie als `0`. Die gekuerzte Rohantwort bleibt erhalten, damit der Nutzer am echten Server erkennt, wie ein Feld wirklich heisst |
+| T-DHH-SC | Tampering | Paketinstallationen | low | accept | Dieser Auftrag installiert kein einziges Paket (D-07) — `undici` ist bereits direkte Abhaengigkeit von `apps/api`. Die Paket-Pruefliste der Recherche weist den Punkt ausdruecklich als nicht anwendbar aus. Entsteht wider Erwarten doch eine Installation, greift die Paket-Pruefung vor dem Einbau |
+
+
+
+## Mehrfachquellen-Abdeckung
+
+**GOAL** (Auftragsbeschreibung)
+
+| Punkt | Status | Abgedeckt durch |
+|---|---|---|
+| PVE, PBS und PMG anbinden | COVERED | Aufgabe 1 (PVE), Aufgabe 3 (PBS, PMG) |
+| Nur beobachten | COVERED | Aufgabe 1 (`proxmoxGet`), Aufgabe 2 (`proxmox-nur-lesen.spec.ts`) |
+| Server in den Einstellungen anlegen (Adresse + Zugang) | COVERED | Aufgabe 1 (Anlegen), Aufgabe 5 (Oberflaeche, Bearbeiten, Loeschen) |
+| Zugang Token oder Benutzer/Passwort, verschluesselt | COVERED | Aufgabe 1 (Token), Aufgabe 2 (Benutzer/Passwort), beide ueber `CryptoService` |
+| Abfrage im Hintergrund mit Zwischenlager | COVERED | Aufgabe 1 (Zwischenlagertabelle), Aufgabe 4 (Planer) |
+| Modulseite mit Serverliste und Auslastung | COVERED | Aufgabe 1 (duenne Liste), Aufgabe 6 (Auslastung je Produkt) |
+
+**RESEARCH** (`260923-dhh-RESEARCH.md`)
+
+| Punkt | Status | Abgedeckt durch |
+|---|---|---|
+| Token-Kopfzeilen je Produkt, PMG ohne Token (A1) | COVERED | Aufgabe 1 und 2 (`proxmox-auth.ts`), Aufgabe 5 (Formular bietet es bei PMG nicht an) |
+| Ticket-Anmeldung, Cookie-Namen je Produkt (A2) | COVERED | Aufgabe 2, Cookie-Namen als EINE benannte Konstante mit Annahme-Kommentar |
+| Kein CSRF noetig, weil nur gelesen wird | COVERED | Aufgabe 2 (``) |
+| `cluster/resources` als eine Abfrage fuer PVE | COVERED | Aufgabe 1 und 3 |
+| PBS-Belegung und Snapshot-Felder (A3) | COVERED | Aufgabe 3, Feldnamen als EINE benannte Konstante |
+| PMG-Tageszahlen (A5, keine Quarantaene) | COVERED | Aufgabe 3; Quarantaene bleibt ausserhalb des Umfangs |
+| Fehlerverhalten 401 breiter als ueblich (A4) | COVERED | Aufgabe 2 (`classifyFailure`) |
+| undici-Dispatcher-Falle unter Node 24 | COVERED | Aufgabe 1 (Kommentar und Test), Aufgabe 7 (Anleitung) |
+| Pro Zeile umschaltbarer Zertifikats-Bypass | COVERED | Aufgabe 1, T-DHH-03 |
+| `onApplicationBootstrap` statt `onModuleInit` | COVERED | Aufgabe 4 |
+| Mandanten-Auffaechern je Cron-Auftrag | COVERED | Aufgabe 4 |
+| Zwischenlager statt Live-Abfrage | COVERED | Aufgabe 1, 4, 6 |
+| RLS-Migration, Klassifikationsdoku, Erlaubnisliste | COVERED | Aufgabe 1 (Migration, Doku), Aufgabe 4 (Erlaubnisliste), Aufgabe 7 (Nachmessung) |
+| Nur-Lese-Rollen je Produkt als Hinweis an den Admin | COVERED | Aufgabe 7 (Anwenderhandbuch), `user_setup` im Frontmatter |
+| Zaehler sind kumulativ, keine Rate | COVERED | Aufgabe 6 (Beschriftung) |
+| Ticket-Erneuerung bei 401 | COVERED | Aufgabe 2 |
+| Keine neue npm-Abhaengigkeit | COVERED | Aufgabe 1 (D-07), Paket-Pruefliste nicht anwendbar |
+
+**CONTEXT** (getroffene Entscheidungen D-01 bis D-11)
+
+| ID | Status | Abgedeckt durch |
+|---|---|---|
+| D-01 | COVERED | Aufgabe 1 (`proxmoxGet`), Aufgabe 2 (`proxmox-nur-lesen.spec.ts`), `must_haves.truths`, T-DHH-07 |
+| D-02 | COVERED | Aufgabe 1 (Verschluesselung, `select` ohne Geheimnisse), Aufgabe 5 (Formular), T-DHH-01 |
+| D-03 | COVERED | Aufgabe 1 und 2 (`proxmox-auth.ts` als einzige Stelle), Aufgabe 5 (Formular ohne Token bei PMG) |
+| D-04 | COVERED | Aufgabe 1 (Dispatcher je Aufruf), Aufgabe 5 (Schalter), T-DHH-03 |
+| D-05 | COVERED | Aufgabe 1 (Zwischenlager), Aufgabe 4 (Planer nach TENDER-Muster), Aufgabe 6 (Seite liest nur den Cache) |
+| D-06 | COVERED | Aufgabe 2 (Fehlerklassen), Aufgabe 4 (Testendpunkt), Aufgabe 5 (Knopf und Klartext) |
+| D-07 | COVERED | Aufgabe 1 (nur `undici`), Paket-Pruefliste nicht anwendbar |
+| D-08 | COVERED | Aufgabe 1 (Migration, Doku), Aufgabe 4 (Erlaubnisliste, Standwechsel), Aufgabe 7 (Nachmessung), T-DHH-04 |
+| D-09 | COVERED | Aufgabe 1 (`@UseModule`, `ModuleAccessGate`), T-DHH-05 |
+| D-10 | COVERED | Aufgaben 1, 5, 6 (next-intl, Sie-Form), 7 (Anwenderhandbuch) |
+| D-11 | COVERED (als Ausschluss) | ``, Abschnitt „Ausdruecklich NICHT im Umfang" |
+
+**Keine Luecke.** Nicht abgedeckt sind ausschliesslich die vom Auftrag ausgeschlossenen Punkte
+(Dashboard-Kachel, `/rrddata`, Eingriffe, PMG-Quarantaene).
+
+
+
+Nach jeder Aufgabe (je Commit):
+
+- `pnpm --filter @tessera/api test` — gruen, mindestens 1240 Tests
+- `pnpm --filter @tessera/web test` — gruen, mindestens 693 Tests
+- `pnpm type-check` — 4 von 4 erfolgreich
+- `pnpm lint` — 5 von 5 erfolgreich
+- `pnpm --filter @tessera/web exec biome lint .` — exakt 53 Warnungen
+
+Zusaetzlich nach den Aufgaben 1 und 4:
+
+- `pnpm --filter @tessera/api exec vitest run src/prisma/rls-coverage.spec.ts src/prisma/rls-access-inventory.spec.ts` — gruen
+
+Ab Aufgabe 2 dauerhaft:
+
+- `pnpm --filter @tessera/api exec vitest run src/proxmox/proxmox-nur-lesen.spec.ts` — gruen
+
+**Was diese Tore NICHT beweisen:** die Feldnamen von PBS und PMG (Annahmen A2, A3, A5 der
+Recherche). Es gibt hier keinen echten PVE-/PBS-/PMG-Server; alle Tests laufen gegen erfundene
+Antworten in der dokumentierten Form. Der Nutzer prueft das Modul selbst auf `alpha` gegen
+seine echten Server. Genau dafuer sind die Feldnamen je Produkt als EINE benannte Konstante
+gebaut und bleibt die gekuerzte Rohantwort im Zwischenlager erhalten: weicht die Wirklichkeit
+ab, ist eine einzige Stelle nachzuziehen und der Nutzer sieht in der Oberflaeche „unbekannt"
+statt eines Absturzes.
+
+
+
+1. Kein Weg im gesamten Modul veraendert etwas bei Proxmox; der maschinelle Riegel
+ `proxmox-nur-lesen.spec.ts` weist nach, dass die einzige nicht-lesende Anfrage die
+ Ticket-Anmeldung ist.
+2. Ein Administrator legt in den Moduleinstellungen Server aller drei Typen an; bei PMG wird
+ die Token-Auswahl gar nicht erst angeboten und serverseitig abgelehnt.
+3. Zugangsdaten stehen verschluesselt in der Datenbank und verlassen sie auf keinem Weg im
+ Klartext — auch nicht in Fehlermeldungen, Protokollen oder der Rohprobe.
+4. Die Zertifikats-Ausnahme gilt nur fuer die Server, bei denen sie einzeln eingeschaltet
+ wurde; Voreinstellung ist pruefen.
+5. Der Hintergrunddienst haengt an `onApplicationBootstrap`, faechert je Mandant auf und
+ ueberschreibt den Auftrag eines zweiten Mandanten nicht.
+6. Die Modulseite liest ausschliesslich aus dem Zwischenlager, zeigt fehlende Werte als
+ „unbekannt" und ohne Server einen ruhigen Hinweis.
+7. Der Knopf „Verbindung testen" nennt die Ursache in Alltagssprache.
+8. Beide neuen Tabellen tragen `tenantId` mit RLS-Policy; `rls-coverage.spec.ts` und
+ `rls-access-inventory.spec.ts` sind gruen, die Klassifikationsdoku ist nachgemessen.
+9. Alle Tore mindestens auf Ausgangswert: api-Tests ab 1240, web-Tests ab 693, type-check 4/4,
+ lint 5/5, Biome-Warnungen in `apps/web` exakt 53.
+10. Anwenderhandbuch und Entwicklungsanleitung beschreiben das Modul, einschliesslich der
+ NUR-LESE-Rolle je Produkt.
+
+
+