feat(260923-dhh): Proxmox-Modul Aufgabe 3 - PBS und PMG auswerten

- proxmox-normalize.ts: nachsichtige Leser (readNumber/readText/readBool/
  readList) und normalizePve/normalizePbs/normalizePmg als reine
  Funktionen, nie ein Wurf bei unerwarteter Form
- PVE ergaenzt um je Speicherort Belegung (storages)
- PBS: Belegung je Datenspeicher plus letzte Sicherung/Pruefergebnis aus
  bis zu 10 Folgeabfragen je Durchlauf (Deckel in proxmox.service.ts)
- PMG: Tageszahlen eingehend/ausgehend/Spam/Viren
- Feldnamen je Produkt als benannte Konstante (Annahmen A3/A5 der
  Recherche), mehrere plausible Namen je Feld moeglich
- proxmox.service.ts: produktabhaengige Abfragefolge, Ticket-Erneuerung
  jetzt je Durchlauf statt je Aufruf (PBS-Mehrfachabfragen loggen nicht
  mehrfach neu ein)
- proxmox-nur-lesen.spec.ts: Riegel erkennt jetzt auch den Umschlag
  getWithRetry als zulaessige Aufrufform

Tore: api 1293/1293 (>=1240), type-check 4/4.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 10:28:25 +02:00
parent 4f8a368c9e
commit 998aba9ef3
6 changed files with 750 additions and 97 deletions
+14 -4
View File
@@ -120,21 +120,31 @@ function countMethodPassingCalls(text: string): number {
return count;
}
/** Fundstellen eines Proxmox-API-Pfads ausserhalb eines `proxmoxGet(...)`-Aufrufs. */
/**
* Aufrufformen, deren Argumentbereich einen Proxmox-Pfad tragen darf:
* `proxmoxGet` selbst, UND `getWithRetry` — der private Umschlag in
* `proxmox.service.ts` (Aufgabe 2/3, Ticket-Erneuerung), der seinerseits
* ausschliesslich `proxmoxGet` ruft (durch dieselbe erste Aussage dieses
* Riegels abgesichert: keine zweite `undiciFetch`-Methodenstelle in dieser
* Datei).
*/
const ALLOWED_PATH_CALLEES = ['proxmoxGet', 'getWithRetry'] as const;
/** Fundstellen eines Proxmox-API-Pfads ausserhalb einer erlaubten Aufrufform. */
function findApiPathViolations(fileName: string, text: string): string[] {
if (fileName === 'proxmox-auth.ts') {
// Die Ticket-Anmeldung ist die eine dokumentierte Ausnahme (D-01).
return [];
}
const proxmoxGetSpans = collectCallArgSpans(text, 'proxmoxGet');
const allowedSpans = ALLOWED_PATH_CALLEES.flatMap((callee) => collectCallArgSpans(text, callee));
const violations: string[] = [];
const pathRe = /\/api2\/json\/[A-Za-z0-9/{}_.-]*/g;
let m: RegExpExecArray | null;
// biome-ignore lint/suspicious/noAssignInExpressions: s.o.
while ((m = pathRe.exec(text))) {
const idx = m.index;
const insideProxmoxGetCall = proxmoxGetSpans.some((s) => idx >= s.start && idx <= s.end);
if (!insideProxmoxGetCall) {
const insideAllowedCall = allowedSpans.some((s) => idx >= s.start && idx <= s.end);
if (!insideAllowedCall) {
violations.push(`${fileName}@${idx}: ${m[0]}`);
}
}