Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
18 KiB
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 |
|
v1:sha256:2ee19956636c68304958254f2e1d979a6763c3fae7dc11cd781fe24d56e498e8 | 0 | 0 |
|
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 |
Key Link Verification
| 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)