Files
tessera-ctl/.planning/quick/260923-dhh-proxmox-modul-pve-pbs-und-pmg-anbinden-n/260923-dhh-VERIFICATION.md
T

18 KiB
Raw Blame History

phase, verified, status, score, covered_files, covered_digest, behavior_unverified, overrides_applied, gaps
phase verified status score covered_files covered_digest behavior_unverified overrides_applied gaps
quick-260923-dhh 2026-09-23T14:58:00Z gaps_found 8/9 must-haves verified
.planning/quick/260923-dhh-proxmox-modul-pve-pbs-und-pmg-anbinden-n/260923-dhh-PLAN.md
.planning/quick/260923-dhh-proxmox-modul-pve-pbs-und-pmg-anbinden-n/260923-dhh-RESEARCH.md
.planning/quick/260923-dhh-proxmox-modul-pve-pbs-und-pmg-anbinden-n/260923-dhh-SUMMARY.md
apps/api/prisma/migrations/20260923140000_proxmox_server/migration.sql
apps/api/prisma/schema.prisma
apps/api/src/prisma/rls-access-inventory.spec.ts
apps/api/src/proxmox/dto/proxmox-server.dto.ts
apps/api/src/proxmox/proxmox-auth.ts
apps/api/src/proxmox/proxmox-client.service.spec.ts
apps/api/src/proxmox/proxmox-client.service.ts
apps/api/src/proxmox/proxmox-normalize.spec.ts
apps/api/src/proxmox/proxmox-normalize.ts
apps/api/src/proxmox/proxmox-nur-lesen.spec.ts
apps/api/src/proxmox/proxmox-scheduler.service.spec.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/proxmox.service.spec.ts
apps/api/src/proxmox/proxmox.service.ts
apps/api/src/proxmox/proxmox.types.ts
apps/web/src/app/(portal)/modules/proxmox/components/ServerCard.test.tsx
apps/web/src/app/(portal)/modules/proxmox/components/ServerCard.tsx
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/settings/components/ServerForm.test.tsx
apps/web/src/app/(portal)/modules/proxmox/settings/components/ServerForm.tsx
apps/web/src/app/(portal)/modules/proxmox/settings/page.tsx
apps/web/src/lib/module-loader.ts
apps/web/src/lib/proxmox-api.ts
apps/web/src/messages/de.json
apps/web/src/messages/en.json
apps/web/src/messages/umlaut-dictionary.ts
docs/anleitung-anwender.md
docs/anleitung-entwicklung.md
docs/mandantentrennung-zugriffsklassifikation.md
v1:sha256:2ee19956636c68304958254f2e1d979a6763c3fae7dc11cd781fe24d56e498e8 0 0
truth status reason artifacts missing
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. partial normalizePmg() kombiniert spamcount_in/spamcount_out (und viruscount_in/viruscount_out) ueber sumOrNull(a, b), das einen fehlenden Teilwert stillschweigend als 0 behandelt statt die Summe als unbekannt zu markieren. sumOrNull(10, null) liefert 10 — dieser Wert erscheint in der Modulseite als vollstaendige Tageszahl 'Spam: 10', obwohl eine der beiden Quellfelder (spamcount_out) fehlte oder anders heisst. Genau dieses Szenario ist der zentrale Risikofall des Moduls: PMG-Feldnamen sind Annahme A5 (Forenbeleg, unbestaetigt), und ein teilweise falscher, aber plausibel aussehender Wert ist laut eigenem Kommentar in proxmox-normalize.ts ('ein still falscher Wert waere schlimmer als ein ehrliches unbekannt') genau das, was das Modul verhindern soll. Alle uebrigen Einzelwerte (readNumber/readText/readBool je Feld, PBS readFirstPresent-Alternativnamen) sind korrekt nachsichtig und liefern bei fehlendem Feld null — nur diese eine Aggregation (zwei Teilwerte zu einer Summe) durchbricht das Muster.
path issue
apps/api/src/proxmox/proxmox-normalize.ts sumOrNull(a, b) (Zeile 240-243) gibt (a??0)+(b??0) zurueck, sobald mindestens einer von a/b nicht null ist — ein fehlender Halbwert wird als 0 addiert statt die Summe auf null zu setzen. Betrifft spamCount und virusCount in normalizePmg().
sumOrNull so aendern, dass die Summe null ist, sobald a ODER b null ist (nicht erst wenn beide null sind) — oder spamCount/virusCount nur berechnen, wenn beide Teilwerte vorhanden sind.
Test in proxmox-normalize.spec.ts ergaenzen: 'nur spamcount_in vorhanden, spamcount_out fehlt' -> spamCount muss null sein, nicht der Teilwert.

Quick 260923-dhh: Proxmox-Modul (PVE/PBS/PMG) — nur beobachten Verification Report

Goal: PVE/PBS/PMG per Modul beobachten (nicht veraendern): Server in den Einstellungen anlegen mit verschluesseltem Zugang, Zertifikatsfehler nur je Server dulden, Hintergrundabfrage mit Zwischenlager, Modulseite mit Serverliste und Auslastung, Verbindungstest mit Klartext-Ursache. Verified: 2026-09-23T14:58Z Status: gaps_found Re-verification: No — initial verification

Goal Achievement

Observable Truths

# Truth Status Evidence
1 Kein Weg im Modul veraendert etwas bei Proxmox; die einzige Nicht-GET-Anfrage ist die Ticket-Anmeldung, maschinell nachgezaehlt ✓ VERIFIED proxmox-nur-lesen.spec.ts liest den Quelltext (Klammertiefen-Bilanzierung), zaehlt genau 1 method:-Uebergabe an undiciFetch in proxmox-auth.ts, und verlangt, dass jeder API-Pfad ausserhalb der Ticket-Anmeldung durch proxmoxGet/getWithRetry laeuft. getWithRetry (proxmox.service.ts:275) ruft ausschliesslich proxmoxGet — keine verdeckte zweite Schreibstelle. Grep ueber apps/api/src/proxmox bestaetigt: kein bare fetch( ausserhalb undiciFetch. Test lief gruen (2/2).
2 Administrator legt Server (Name, Typ, Adresse, Zugang) an; Geheimnis nie im Klartext sichtbar ✓ VERIFIED createServer/updateServer verschluesseln via CryptoService; SAFE_SERVER_SELECT (proxmox.service.ts:26-42) waehlt encryptedTokenSecret/encryptedPassword nicht aus — die Felder verlassen die DB nie. proxmox.service.spec.ts bestaetigt 'encryptedTokenSecret' in list[0] ist false. Frontend ServerForm.tsx: Geheimnisfelder immer leer geladen (tokenSecret: '', password: ''), leer gelassen = unveraendert (Backend-Logik in updateServer).
3 PVE/PBS: Token ODER Passwort; PMG nur Passwort, Token-Feld verschwindet und wird serverseitig abgelehnt ✓ VERIFIED ServerForm.tsx: {form.productType !== 'pmg' && <option value="token">...} — Token-Option fehlt bei PMG. PmgOhneTokenConstraint im DTO UND zusaetzliche Pruefung in updateServer gegen den EFFEKTIVEN Stand (verhindert Umgehung ueber Teil-Updates). buildTokenAuthHeader('pmg', ...) wirft. Getestet in proxmox-client.service.spec.ts (DTO-Validierung PMG+Token).
4 Zertifikatsfehler nur je Server geduldet, Default "pruefen" ✓ VERIFIED proxmoxGet/loginTicket bauen den Agent-Dispatcher JE AUFRUF aus target.tlsRejectUnauthorized der jeweiligen Zeile — kein Modul-Singleton, keine Env-Variable. DTO-Default tlsRejectUnauthorized ?? true. Test bestaetigt: true → kein Dispatcher, false → genau ein Agent mit rejectUnauthorized: false.
5 Modulseite und jede Anzeige lesen ausschliesslich aus dem Zwischenlager ✓ VERIFIED GET servers → listWithStatus() liest nur aus der DB (kein proxmoxGet-Aufruf). page.tsx/ServerCard.tsx rendern nur server.status, das aus derselben Response stammt. Live-Abfrage findet nur ueber pollServer/testConnection statt, explizit durch Nutzerklick oder Scheduler ausgeloest.
6 Knopf "Verbindung testen" nennt Ursache in Alltagssprache ✓ VERIFIED Alle 7 ProxmoxErrorKind-Werte haben deutsche Klartexttexte in de.json/en.json (Sie-Form, mit Ursache und naechstem Schritt). testConnection() schreibt NICHT ins Zwischenlager (Vorbild LDAP-Test).
7 Fehlendes/anders benanntes/falsch typisiertes Feld → "unbekannt", nie Absturz/leere Seite/stiller Falschwert ✗ PARTIAL Siehe Gap unten: sumOrNull() in normalizePmg() liefert bei einem fehlenden Teilwert (z. B. spamcount_out fehlt) einen scheinbar vollstaendigen, tatsaechlich unvollstaendigen Zahlenwert statt null/"unbekannt". Alle uebrigen Einzelwerte (PVE/PBS, PMG countIn/countOut) sind korrekt nachsichtig — verifiziert in proxmox-normalize.spec.ts (20 Tests gruen) und live nachgerechnet (node -e).
8 Ohne Server: Modulseite ruhig, erklaert dass noch keiner eingetragen ist ✓ VERIFIED page.tsx: servers.length === 0 → t('emptyState') plus Link zu den Einstellungen fuer Admins, kein Fehlertext.
9 Beide Tabellen tragen tenantId mit RLS-Policy; rls-coverage/rls-access-inventory bleiben gruen ✓ VERIFIED Migration erstellt tenant_isolation_policy auf beiden Tabellen plus system_read_policy nur auf ProxmoxServer. Live in der Dev-DB bestaetigt (psql): relrowsecurity=t, relforcerowsecurity=t auf beiden Tabellen; pg_policies zeigt exakt die erwarteten drei Policies. rls-coverage.spec.ts (5/5) und rls-access-inventory.spec.ts (30/30) gruen, inkl. FORSYSTEM_ALLOWED_CALL_SITES-Eintrag fuer den einzigen forSystem()-Aufruf. Klassifikationsdoku nachgemessen (grep -c bestaetigt 11 gebundene + 1 System-Rohtreffer, Doku sagt dasselbe).

Score: 8/9 truths verified (0 present-but-behavior-unverified)

Required Artifacts

Artifact Expected Status Details
apps/api/prisma/migrations/20260923140000_proxmox_server/migration.sql RLS-Migration ✓ VERIFIED Existiert, angewendet (Tabellen + Policies live in der Dev-DB bestaetigt)
apps/api/src/proxmox/proxmox-auth.ts einzige Kopfzeilen-Stelle ✓ VERIFIED buildTokenAuthHeader, loginTicket, buildTicketCookieHeader; keine andere Datei im Repo baut PVEAPIToken/PBSAPIToken/Cookie-Header
apps/api/src/proxmox/proxmox-client.service.ts nur-lesender HTTP-Zugang ✓ VERIFIED proxmoxGet, classifyFailure, parseJsonLenient — kein method-Parameter
apps/api/src/proxmox/proxmox-normalize.ts nachsichtige Leser ⚠️ SUBSTANTIVE MIT LUECKE Grundfunktionen (readNumber/readText/readBool/readList) korrekt; normalizePmgs Aggregation (sumOrNull) durchbricht das Muster (siehe Gap)
apps/api/src/proxmox/proxmox-scheduler.service.ts Planer je Mandant ✓ VERIFIED onApplicationBootstrap, ein Cron-Auftrag je Mandant, Fan-out getestet (9/9 Tests)
apps/api/src/proxmox/proxmox-nur-lesen.spec.ts maschineller Riegel D-01 ✓ VERIFIED 2/2 Tests gruen, Klammertiefen-Analyse statt naiver Regex
apps/web/src/app/(portal)/modules/proxmox/page.tsx Modulseite ✓ VERIFIED Leerzustand, Serverliste, "Jetzt aktualisieren"
apps/web/src/app/(portal)/modules/proxmox/settings/page.tsx Einstellungsseite ✓ VERIFIED Rollen-Gate (Anzeige), CRUD, Loeschbestaetigung
From To Via Status Details
proxmox-auth.ts Klient/Planer/Verbindungstest einzige Kopfzeilen-Bau-Stelle (D-03) ✓ WIRED proxmox.service.ts importiert ausschliesslich buildTicketCookieHeader/buildTokenAuthHeader/loginTicket aus dieser Datei; kein Nachbau anderswo
proxmox-client.service.ts tlsRejectUnauthorized-Feld der Serverzeile Dispatcher je Aufruf (D-04) ✓ WIRED target.tlsRejectUnauthorized ? undefined : new Agent(...) in proxmoxGet und loginTicket, je aus der uebergebenen Serverzeile
proxmox-scheduler.service.ts proxmox.controller.ts onApplicationBootstrap + refreshTenant nach jedem Speichern ✓ WIRED Controller ruft scheduler.refreshTenant(tenantId) nach create/update/remove
proxmox.controller.ts @UseModule/@Roles Modulfreigabe + Rollenschutz (D-09) ✓ WIRED @UseModule('proxmox') auf Klassenebene, @Roles(ADMIN, SUPER_ADMIN) auf allen Schreibwegen
Jeder DB-Zugriff forTenant()/forSystem() Mandantenbindung (D-08) ✓ WIRED grep -c bestaetigt 11 tenantPrisma.(proxmoxServer|proxmoxServerStatus).-Treffer, 1 systemPrisma.proxmoxServer.-Treffer — deckungsgleich mit FORSYSTEM_ALLOWED_CALL_SITES und der Klassifikationsdoku

Data-Flow Trace

Artifact Data Variable Source Produces Real Data Status
ServerCard.tsx server.status.metrics GET modules/proxmox/servers → listWithStatus() → DB (ProxmoxServerStatus) Ja (mit Testdaten belegt, kein echter Proxmox verfuegbar — s. unten) ✓ FLOWING
ServerForm.tsx Testergebnis testResult POST servers/:id/test → testConnection() → pollOne() (kein DB-Schreiben) Ja ✓ FLOWING

Behavioral Spot-Checks

Behavior Command Result Status
Nur-Lesen-Riegel haelt (Klammertiefen-Analyse, nicht nur Praesenz) vitest run src/proxmox/proxmox-nur-lesen.spec.ts 2/2 gruen ✓ PASS
Ticket-Erneuerung: genau EIN zweiter Versuch, zweites 401 bleibt Fehler vitest run src/proxmox (enthaelt beide Faelle) gruen ✓ PASS
Scheduler: zwei Mandanten verdraengen sich nicht, leere Serverliste → kein Auftrag vitest run src/proxmox/proxmox-scheduler.service.spec.ts 9/9 gruen ✓ PASS
RLS tatsaechlich in der Dev-DB aktiv (nicht nur im SQL-Text) docker exec ... psql -c "SELECT relrowsecurity, relforcerowsecurity FROM pg_class WHERE relname IN (...)" t / t auf beiden Tabellen, 3 erwartete Policies vorhanden ✓ PASS
sumOrNull-Aggregationsluecke (eigener Nachbau, nicht Teil der Testsuite) node -e "sumOrNull(10, null)" 10 (haette bei ehrlichem Verhalten null sein muessen) ✗ FAIL — bestaetigt den Gap oben
Volle Testsuiten pnpm --filter @tessera/api test, pnpm --filter @tessera/web test 1311/1311 bzw. 708/708 gruen, identisch zu SUMMARY-Zahlen ✓ PASS
type-check / lint / Biome pnpm type-check, pnpm lint, pnpm --filter @tessera/web exec biome lint . 4/4, 5/5, "Found 53 warnings" ✓ PASS

Requirements Coverage

Kein separates REQUIREMENTS.md fuer Quick-Tasks; Abdeckung erfolgt ueber die elf D-Nummern im Plan-Frontmatter (<source_audit>), alle als COVERED gefuehrt und hier gegengeprueft — kein Widerspruch gefunden ausser dem oben genannten Gap zu D-08/T-DHH-08 (stiller Falschwert).

Anti-Patterns Found

File Line Pattern Severity Impact
apps/api/src/proxmox/proxmox-normalize.ts 240-243 Aggregation verschluckt fehlenden Teilwert (sumOrNull) 🛑 Blocker (verletzt explizites must-have) PMG "Spam"/"Viren"-Zahl kann eine unvollstaendige, aber vertrauenswuerdig aussehende Zahl zeigen statt "unbekannt"
— — Keine TBD/FIXME/XXX in den neuen Dateien gefunden ℹ️ Info —
apps/web/.../page.tsx 54 "Jetzt aktualisieren"-Knopf wird JEDEM Nutzer mit Modulzugriff gezeigt, POST servers/:id/poll ist aber @Roles(ADMIN, SUPER_ADMIN); Fehler wird mit .catch(() => undefined) still verschluckt ⚠️ Warning (UX, keine Sicherheitsluecke — Backend blockt korrekt) Normale Nutzer sehen einen Knopf, der bei ihnen wirkungslos bleibt, ohne Rueckmeldung

Human Verification Required

Diese Punkte kann kein automatisierter Check abschliessend pruefen — teils weil kein echter Proxmox-Server in dieser Umgebung erreichbar ist (vom Auftrag selbst so benannt), teils weil es sich um visuelles/Browser-Verhalten handelt.

1. Modulseite im Browser (vom Plan als <human-check> in Aufgabe 6 vorgesehen)

Test: /modules/proxmox oeffnen: ohne Server pruefen, dass der ruhige Hinweis erscheint; danach in den Einstellungen einen Server anlegen und pruefen, dass er in der Liste auftaucht; einen absichtlich falschen Zugang eintragen und pruefen, dass Klartext statt einer leeren Flaeche erscheint. Expected: Ruhiger Leerzustand, danach korrekte Anzeige, dann Klartext-Fehlermeldung. Why human: Erfordert echten Browser-Durchlauf; die laufenden Container wurden fuer diese Verifikation bewusst nicht neu gebaut (Auftragsvorgabe), ein visueller Check ist damit nicht ohne Weiteres moeglich.

2. Annahmen A2/A3/A5 gegen echte PVE-/PBS-/PMG-Server

Test: Cookie-Namen (PBSAuthCookie/PMGAuthCookie), PBS-Belegungs-/Snapshot-Feldnamen und PMG-Statistikfelder gegen einen echten Server pruefen. Expected: Die in TICKET_COOKIE_NAME/PBS_USAGE_FIELDS/PBS_SNAPSHOT_FIELDS/PMG_STATS_FIELDS hinterlegten Namen stimmen, oder werden an der jeweils benannten EINEN Stelle nachgezogen. Why human: Kein PVE/PBS/PMG-Server in dieser Umgebung erreichbar — vom Plan selbst so benannt und in user_setup dokumentiert, keine Verifikationsluecke dieser Pruefung.

Gaps Summary

Ein konkreter, durch Code und einen eigenen Nachrechenlauf bestaetigter Gap: normalizePmg()s sumOrNull()-Hilfsfunktion behandelt einen fehlenden Teilwert (spamcount_out/viruscount_out bzw. deren _in-Gegenstuecke) als 0 statt die kombinierte Summe als null/"unbekannt" zu markieren. Das widerspricht direkt dem im Plan-Frontmatter (must_haves.truths) UND im eigenen Code-Kommentar ("ein still falscher Wert waere schlimmer als ein ehrliches unbekannt") formulierten Anspruch. Da PMG-Feldnamen die am wenigsten abgesicherte Annahme des gesamten Auftrags sind (Annahme A5, reiner Forenbeleg), ist genau dieses Szenario — ein Teilfeld feuert, das andere heisst anders — nicht hypothetisch, sondern der wahrscheinlichste erste Fehlerfall beim echten Test durch den Nutzer. Kein Test in proxmox-normalize.spec.ts deckt den Fall "nur eine Haelfte des Paares vorhanden" ab; alle vorhandenen Tests pruefen entweder "beide vorhanden" oder "beide fehlen".

Alle uebrigen acht Wahrheiten aus dem Plan sind vollstaendig verifiziert, mehrfach durch automatisierte Tests UND durch eigene Stichproben (Live-RLS-Abfrage gegen die tatsaechliche Dev-Datenbank, Grep-Nachzaehlung der Mandantenbindung, direkte Pruefung des Nur-Lesen-Riegels, manuelles Nachrechnen der Klammertiefen-Logik). Alle sieben Commits, alle 24 im Frontmatter genannten Dateien und alle gemessenen Torzahlen (1311/1311 API-Tests, 708/708 Web-Tests, 4/4 type-check, 5/5 lint, exakt 53 Biome-Warnungen) wurden unabhaengig nachvollzogen und stimmen exakt mit der SUMMARY ueberein.


Verified: 2026-09-23T14:58Z Verifier: Claude (gsd-verifier)