docs(quick-260924-m4n): Test-Flake und DashboardImage Stufe 2
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+2
-1
@@ -7,7 +7,7 @@ status: verified
|
||||
stopped_at: "22.09.2026: 1.3.0 freigegeben; danach quick-260922-hk4 — Bilderrahmen-Bilder liegen jetzt im Dateibereich (user-files) statt in der Datenbank, Umzug laeuft automatisch beim Start, Selbstheilung aus der alten data-Spalte eingebaut; im Browser nachgewiesen. NAECHSTER SCHRITT, vom Nutzer noch nicht bestaetigt: (1) einmaliges Aufraeumen, damit ein Modul seine Dashboard-Kachel selbst mitbringt (heute sieben Hartkodierungen je Kachel; Katalog zeigt auch Kacheln gesperrter Module; gesperrte Kachel bleibt leer statt zu erklaeren) — das Geruest WIDGET_MODULE_MAP existiert und ist leer; (2) danach das Proxmox-Modul (PVE/PBS/PMG) und seine Kachel. Offen beim Nutzer: Live-Server auf 1.3.0 ziehen, neuen Client per Browser installieren."
|
||||
last_updated: "2026-09-23T15:30:00.000Z"
|
||||
last_activity: 2026-09-23
|
||||
last_activity_desc: Quick 260924-i8v — Proxmox-Kachel fuers Dashboard; davor CI-Runner haengte (Neustart, Lauf 423 gruen)
|
||||
last_activity_desc: Quick 260924-m4n — flackernder Test entschaerft, alte DashboardImage-Spalte entfernt (mit Schutzklausel); 1.4.0 vom Nutzer ausdruecklich NICHT freigegeben
|
||||
state_head: 4d485432c003a6caf68f6d85aff7de0bd27794e2
|
||||
progress:
|
||||
total_phases: 18
|
||||
@@ -474,6 +474,7 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests.
|
||||
| 260923-lrr | **Favoriten: eigenes Symbol hochladen, Symbol sofort aktualisiert, Cloudflare-Meldung.** Versionszaehler `iconVersion` an der Symboladresse (`?v=`) statt 24-h-Zwischenspeicher mit fester Adresse (Ursache „neue Logo-Adresse, nichts passiert“); `Cache-Control: private`. Upload PNG/JPEG/GIF/WebP/ICO/SVG bis 512 KB nach Dateiinhalt, Ablage `user-files/favorite-icons/<userId>/<id>.<ext>`, Vorrang vor Logo-Adresse, Entfernen-Knopf. Neue Logo-Adresse wird beim Speichern einmal zur Probe abgerufen; scheitert es (Cloudflare-Pruefung, 403), bleibt das Formular offen mit deutscher Meldung und Hinweis aufs Hochladen. Aufraeumen der Dateien auch beim Loeschen einer Kachel/eines Reiters (T-LRR-07 geschlossen). Browser-Nachweis: rot hochgeladen -> sofort rot (v=1), blau -> sofort blau (v=2), Entfernen -> altes Logo (v=3), httpbin 403 -> Meldung, google favicon -> sofort (v=4). Nachtrag Orchestrator: Zeile `dashboard.service.ts`/`favoriteLink` in der Zugriffsklassifikation. api 1368, web 742 gruen. | 2026-09-23 | 7704372,61f95c8 | [260923-lrr-favoriten-eigenes-symbol-hochladen-und-s](./quick/260923-lrr-favoriten-eigenes-symbol-hochladen-und-s/) |
|
||||
| 260924-h7x | **Proxmox-Seite neu gestaltet (Status bestimmt das Bild) und Dashboard-Reiter in die Kopfzeile.** Nutzer hob am 24.09. die Umbausperre vom 23.09. selbst auf. Design-Plan aus dem frontend-design-Skill: Statusfarben als OKLCH-Tokens (`--status-ok/warn/down/idle/orphan`, dazu `-fg`-Textvarianten fuer 4,5:1), Gesundheitsbalken mit Legende, Karten mit Statusleiste links und im Statuston getoentem Schatten, eingelassene Messfelder, Knoten als Einschuebe mit Balken nach Schwellen (80/92 %, Sicherung > 26 h), PMG-Zahlfelder. **Deaktivierter Server = „Offline & verwaist“** (Vorrang vor allem, keine alten Werte, gestrichelt). Sortierung down/warn/ok/idle/orphan, Spaltenfluss statt Raster. Reiter als eingelassener Umschalter per Portal in der Kopfzeilenmitte (`header-center-slot`), eigene Zeile entfallen, Pfeiltasten, weiche Randausblendung bei Ueberlauf; unter 640 px Logo nur Bildmarke. Browser: hell/dunkel 1400 px, 390 px ohne Ueberlauf. web 789 gruen. | 2026-09-24 | 0fa7ce0,57c338f,7416a92,0b659d6,57a4196 | [260924-h7x-proxmox-seite-status-design-und-dashboar](./quick/260924-h7x-proxmox-seite-status-design-und-dashboar/) |
|
||||
| 260924-i8v | **Proxmox-Kachel fuers Dashboard.** Modul-Kachel ueber den Weg aus 260922-m1h (Typ `proxmox` in packages/shared + Modulbindung, API-Freigabeliste, Registry, Katalog), nur fuer Benutzer mit Modulzugriff. Kompakter Gesundheitsbalken + Zusammenfassung in Worten, Serverliste nach Dringlichkeit mit je einer Kennzahl (Gaeste/Auslastung, aelteste Sicherung, eingehende Mails, unbekannt nie 0), Links auf /modules/proxmox (nicht im Bearbeitungsmodus), liest jede Minute den Zwischenstand (pausiert bei verborgenem Tab, loest NIE eine Abfrage aus), Titel + Serverauswahl an der Kachel und unter Einstellungen > Dashboard, Groessenstufen per Container-Query. Gemeinsame Teile nach `components/proxmox/` verschoben. Browser: Katalog, Kachel hell/dunkel, schmale Stufe (nur Punkte+Namen). web 864, api 1370 gruen. | 2026-09-24 | a906c67,92bf130,a217d60,377b6e3,586da44,602a45c | [260924-i8v-proxmox-kachel-fuers-dashboard](./quick/260924-i8v-proxmox-kachel-fuers-dashboard/) |
|
||||
| 260924-m4n | **Flackernden Test entschaerft, alte Bildspalte entfernt.** (1) `tenant-selector.test.tsx`: Ursache war das Laden der Bausteine INNERHALB des ersten Tests (zaehlte in dessen 5-s-Grenze) -> Import vorab, Doppelfall getrennt, dasselbe in zwei weiteren Marktplatz-Tests; langsamster Web-Test jetzt < 2 s (mit 2 Kernen 1,3 s); act()-Warnungen der Proxmox-Kachel weg. (2) DashboardImage Stufe 2: Migration `20260924120000_dashboard_image_drop_data` mit Schutz (bricht ab, wenn noch Zeilen ohne `storagePath`; Zeilenschutz fuer die Pruefung abgeschaltet, sonst saehe sie still 0), `storagePath` NOT NULL, `data` weg, `system_read_policy` weg, Bootstrap-Umzug + `forSystem()` entfernt, Upload legt Zeile gleich mit Pfad an. Vorbedingung alpha geprueft (0 von 3 ohne Pfad); Live nicht pruefbar. Rueckweg bei Abbruch in `docs/anleitung-betrieb.md` Kap. 4. Browser/API: Bilder laden, Upload+Anzeige+Loeschen ok. api 1364, web 865 gruen. | 2026-09-24 | b10734f,dd54ec5 | [260924-m4n-flackernden-test-entschaerfen-und-dashbo](./quick/260924-m4n-flackernden-test-entschaerfen-und-dashbo/) |
|
||||
|
||||
## Deferred Items
|
||||
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
---
|
||||
quick_id: 260924-m4n
|
||||
type: quick
|
||||
wave: 1
|
||||
autonomous: true
|
||||
---
|
||||
|
||||
# Quick 260924-m4n — Flackernden Test entschaerfen; DashboardImage Stufe 2 (Spalte `data` entfernen)
|
||||
|
||||
Nutzerfreigabe 24.09.: beide offenen Punkte erledigen. Keine Freigabe/kein Tag.
|
||||
|
||||
## Task 1 — Flackernder Test `TenantContextSelector`
|
||||
|
||||
Todo: `.planning/todos/pending/2026-09-23-flackernder-test-tenant-selector-zeitueberschreitung.md`
|
||||
(lesen, dort steht die Analyse). Test: `apps/web/src/app/(portal)/marketplace/tenant-selector.test.tsx:100`.
|
||||
|
||||
- Ursache ansehen (warum nahe 5 s?). Den Doppelfall (SUPER_ADMIN + ADMIN in EINEM `it`) in zwei
|
||||
`it` auftrennen; langsame Stellen (unnoetige echte Wartezeiten, schwere Importe je Test)
|
||||
beseitigen. KEIN globales Hochsetzen von `testTimeout`.
|
||||
- Messen: `vitest run --reporter=verbose` fuer die ganze Web-Suite, die 10 langsamsten Tests
|
||||
auflisten (Dauer). Jeder Test ueber 2 s wird in der SUMMARY genannt; wenn eine Ursache offensichtlich
|
||||
und klein ist, gleich beheben, sonst nur auflisten.
|
||||
- Nebenbei (klein, gleiche Datei-Gruppe erlaubt): die `act(...)`-Warnungen aus
|
||||
`apps/web/src/components/dashboard/widgets/proxmox-widget.test.tsx` beseitigen (auf das Ende der
|
||||
Zustandsaenderung warten statt sie ins Leere laufen zu lassen) — sie blaehen das CI-Protokoll auf.
|
||||
- Todo-Datei nach `.planning/todos/done/` verschieben (git mv), mit kurzem Nachtrag „erledigt in 260924-m4n“.
|
||||
|
||||
## Task 2 — DashboardImage Stufe 2
|
||||
|
||||
Todo: `.planning/todos/pending/2026-09-22-dashboard-image-data-spalte-entfernen.md` — die dort
|
||||
genannten Schritte 2–4 umsetzen. Vorbedingung geprueft vom Orchestrator: alpha
|
||||
`count(storagePath IS NULL) = 0` (3 Zeilen). Live ist von hier nicht pruefbar (Live laeuft 1.3.1, die den
|
||||
Bootstrap-Umzug enthaelt).
|
||||
|
||||
- Neue Migration mit aktuellem Zeitstempel (NACH allen vorhandenen, `ls apps/api/prisma/migrations`),
|
||||
Name `..._dashboard_image_drop_data`. ZUERST ein Schutz, der den Datenverlust ausschliesst:
|
||||
|
||||
```sql
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM "DashboardImage" WHERE "storagePath" IS NULL) THEN
|
||||
RAISE EXCEPTION 'DashboardImage: es gibt noch Zeilen ohne storagePath — Umzug (quick-260922-hk4) zuerst mit einer Version >= 1.3.1 laufen lassen, dann erneut deployen';
|
||||
END IF;
|
||||
END $$;
|
||||
ALTER TABLE "DashboardImage" ALTER COLUMN "storagePath" SET NOT NULL;
|
||||
ALTER TABLE "DashboardImage" DROP COLUMN "data";
|
||||
```
|
||||
|
||||
Achtung RLS: die Migration laeuft als Eigentuemer; pruefen, dass der `EXISTS`-Check nicht von einer
|
||||
Zeilenregel auf 0 gefiltert wird (FORCE ROW LEVEL SECURITY?). Wenn ja, den Check so formulieren, dass
|
||||
er alle Zeilen sieht (z. B. `SET LOCAL row_security = off` falls als Eigentuemer erlaubt, oder ueber die
|
||||
vorhandene `system_read_policy`). Das Ergebnis der Pruefung in die SUMMARY.
|
||||
- Schema, Dienst (Bootstrap-Umzug + `forSystem()` raus), `FORSYSTEM_ALLOWED_CALL_SITES`, Tests
|
||||
18/21/22/23, Zugriffsklassifikation (Zahlen per Gate-Schleife neu messen, nicht abschreiben),
|
||||
`system_read_policy` auf `DashboardImage` per `DROP POLICY IF EXISTS` in derselben Migration entfernen
|
||||
und die Klassifikation/Aufzaehlung nachziehen.
|
||||
- Lokal anwenden (DB ohne Host-Port: Container-IP, `tessera:tessera_dev`), vorher/nachher
|
||||
`pg_total_relation_size` messen, danach `VACUUM FULL "DashboardImage";` lokal.
|
||||
- Negativtest der Schutzklausel lokal nachweisen: in einer Wegwerf-Datenbank oder Transaktion eine Zeile
|
||||
mit `storagePath NULL` anlegen → Migration bricht mit der Meldung ab (Protokoll in die SUMMARY).
|
||||
- CHANGELOG „Unveröffentlicht“ nur, wenn für Nutzer sichtbar (eher nicht) — sonst weglassen.
|
||||
- `docs/anleitung-betrieb.md`: kurzer Hinweis im Abschnitt Aktualisieren/Freigabe, dass die nächste
|
||||
Version die alte Bildspalte entfernt und bei Abbruch mit der Meldung zuerst 1.3.1 laufen muss.
|
||||
- Todo-Datei nach `.planning/todos/done/` verschieben.
|
||||
|
||||
## Tore
|
||||
|
||||
- API-Tests KOMPLETT (inkl. `src/prisma/rls-access-inventory.spec.ts`), Web-Tests komplett,
|
||||
`pnpm turbo run type-check lint` gruen, Biome-Warnungen web ≤ 53, api ≤ 82.
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
---
|
||||
quick_id: 260924-m4n
|
||||
type: quick
|
||||
status: complete
|
||||
subsystem: apps/web-tests, apps/api/dashboard, apps/api/prisma
|
||||
tags: [flake, vitest, act, prisma-migration, rls, bilderrahmen]
|
||||
requires: [quick-260922-hk4]
|
||||
provides:
|
||||
- Marktplatz-Tests ohne dynamischen Import im Test (Flake der Freigabe 1.3.1 entschärft)
|
||||
- Proxmox-Kachel-Tests ohne act-Warnungen
|
||||
- Migration 20260924120000_dashboard_image_drop_data (Stufe 2, mit Schutzprüfung)
|
||||
affects: [Freigabe der nächsten Version nach 1.3.1, docs/anleitung-betrieb.md Kap. 4]
|
||||
key-files:
|
||||
created:
|
||||
- apps/api/prisma/migrations/20260924120000_dashboard_image_drop_data/migration.sql
|
||||
modified:
|
||||
- apps/web/src/app/(portal)/marketplace/tenant-selector.test.tsx
|
||||
- apps/web/src/app/(portal)/marketplace/marketplace.test.tsx
|
||||
- apps/web/src/app/(portal)/marketplace/marketplace-filters.test.tsx
|
||||
- apps/web/src/components/dashboard/widgets/proxmox-widget.test.tsx
|
||||
- apps/api/prisma/schema.prisma
|
||||
- apps/api/src/dashboard/dashboard-images.service.ts
|
||||
- apps/api/src/dashboard/dashboard-images.service.spec.ts
|
||||
- apps/api/src/prisma/rls-access-inventory.spec.ts
|
||||
- apps/api/src/proxmox/proxmox.service.ts
|
||||
- docs/anleitung-betrieb.md
|
||||
- docs/mandantentrennung-zugriffsklassifikation.md
|
||||
decisions:
|
||||
- "Flake-Ursache: Komponenten wurden per await import() IM Test geladen, das Laden zählte in die 5-s-Frist; Lösung statische Importe + Doppelfall aufgetrennt, kein globales testTimeout"
|
||||
- "Migration heißt 20260924120000_dashboard_image_drop_data statt der im Todo vorgemerkten 20260922120100, weil sie hinter allen vorhandenen Migrationen liegen muss"
|
||||
- "Schutzprüfung der Migration schaltet row_security für die Transaktion ab: ein Eigentümer ohne BYPASSRLS würde sonst still 0 Zeilen sehen, jetzt scheitert er laut"
|
||||
- "Upload vergibt die UUID selbst (randomUUID) und legt die Zeile gleich mit storagePath an, weil storagePath Pflicht ist"
|
||||
- "system_read_policy auf DashboardImage in derselben Migration entfernt (kein Leser mehr)"
|
||||
metrics:
|
||||
duration: ~45 min
|
||||
completed: 2026-09-24
|
||||
actuals:
|
||||
tokens: 21900
|
||||
tasks: 2
|
||||
commits: 2
|
||||
plan_head_before: dd09c0831142b7068f060f09ece471eb1e27560b
|
||||
---
|
||||
|
||||
# Quick 260924-m4n: Flackernden Test entschärft, alte Bildspalte des Bilderrahmens entfernt – Zusammenfassung
|
||||
|
||||
Der Marktplatz-Test, der die Freigabe 1.3.1 blockiert hat, lädt seine Komponenten jetzt beim Einlesen der Testdatei statt im Test selbst und ist in zwei Einzelfälle aufgeteilt. Die Proxmox-Kachel-Tests erzeugen keine act-Warnungen mehr (81 weniger im CI-Protokoll). Außerdem ist Stufe 2 der Bilderrahmen-Umstellung umgesetzt: Die Spalte `data` ist weg, `storagePath` ist Pflicht. Eine Schutzprüfung bricht die Migration ab, bevor Daten verloren gehen könnten.
|
||||
|
||||
## Aufgabe 1 – Flackernder Test `TenantContextSelector` (Commit b10734f)
|
||||
|
||||
**Ursache:** `tenant-selector.test.tsx` hat `TenantContextSelector` und die Marktplatzseite per `await import(...)` **innerhalb** der Tests geladen. Der erste Test einer Datei hat damit das Laden und Umwandeln der Module in seiner 5-s-Frist mitbezahlt. Lokal waren das nur rund 115 ms. Bei jeder Freigabe laufen aber drei Pipelines gleichzeitig, und unter dieser Last reißt der Test die Frist. `waitFor` selbst war nicht der Grund: Es bricht schon nach 1 s mit einer eigenen Meldung ab, nicht erst nach 5 s.
|
||||
|
||||
**Behoben:**
|
||||
- Statische Importe: `vi.mock` wird über die Importe gehoben, das Laden fällt damit in die Einlesephase der Datei.
|
||||
- Der Doppelfall steckt jetzt in zwei `it`: „renders tenant options for SUPER_ADMIN“ sowie „renders nothing for ADMIN and does not load the tenant list“. Der ADMIN-Fall prüft zusätzlich, dass kein Abruf stattfindet.
|
||||
- Die gleiche Umstellung gilt für `marketplace.test.tsx` und `marketplace-filters.test.tsx`, die aus demselben Ordner stammen. In `marketplace-filters` ist `userEvent` jetzt per `userEvent.setup({ advanceTimers: vi.advanceTimersByTime })` an die simulierte Uhr gekoppelt. Vorher hat jede Eingabe auf das langsame Nachschieben in Echtzeit durch `shouldAdvanceTime` gewartet. Der Test „typing in search … after debounce“ läuft allein jetzt in 358 ms statt vorher rund 1,1 s in der Gesamtsuite.
|
||||
- `testTimeout` wurde nicht global erhöht.
|
||||
|
||||
**Proxmox-Kachel (`proxmox-widget.test.tsx`):** `renderWidget()` rendert jetzt innerhalb von `await act(async () => …)`. Dadurch laufen die drei Zustandsänderungen nach dem ersten Laden (`setServers`, `setLoadFailed`, `setNow`) innerhalb von act() und nicht mehr danach ins Leere. Den Test „Verlassen des Bearbeitungsmodus“ habe ich genauso umgestellt, und die Komponente wird ebenfalls statisch importiert.
|
||||
- act-Warnungen in der ganzen Web-Suite: **vorher 102, davon 81 von ProxmoxWidget. Nachher 21, davon 0 von ProxmoxWidget.** Die restlichen 21 stammen aus VehicleTable (8), WidgetSettingsPanel (8), DashboardPage (3), CalendarWidget (1) und Header (1). Sie lagen außerhalb des Auftrags und sind nicht angefasst.
|
||||
|
||||
**Messung:** `vitest run --reporter=verbose --reporter=json` über die ganze Web-Suite mit 91 Dateien und 865 Tests, alle grün.
|
||||
|
||||
Die 10 langsamsten Tests nach der Änderung im normalen Lauf mit 12 Kernen:
|
||||
|
||||
| # | Dauer | Datei | Test |
|
||||
|---|---|---|---|
|
||||
| 1 | 1136 ms | components/settings/smtp-settings-form.test.tsx | Test 2: PUT-Payload trägt den Wert; leeres Feld -> null |
|
||||
| 2 | 1044 ms | components/bug-report/bug-report-button.test.tsx | Test 1: Bild VOR dem Dialog … |
|
||||
| 3 | 882 ms | components/settings/proxmox-widget-config-form.test.tsx | lädt die Server einmal und zeigt die Auswahl … |
|
||||
| 4 | 839 ms | app/(portal)/marketplace/marketplace-filters.test.tsx | typing in search filters the grid … after debounce |
|
||||
| 5 | 799 ms | components/proxmox/proxmox-server-picker.test.tsx | zeigt je Server ein Kästchen … |
|
||||
| 6 | 798 ms | modules/tender-radar/settings/components/SourceConfigForm.test.tsx | editing the interval and clicking Speichern … |
|
||||
| 7 | 749 ms | modules/dkv-fleet/settings/components/VehicleTable.test.tsx | clicking trash icon opens confirm dialog … |
|
||||
| 8 | 746 ms | components/settings/picture-frame-config-form.test.tsx | Test 4: Pfeile … |
|
||||
| 9 | 742 ms | app/(portal)/admin/users/user-access-modal.test.tsx | rolls back the direct checkbox … |
|
||||
| 10 | 741 ms | app/(portal)/admin/users/users-page.test.tsx | zeigt den Servertext im offenen Löschdialog … |
|
||||
|
||||
Zusätzlich lief die ganze Suite gedrosselt auf 2 Kerne (`taskset -c 0-1`, 6 Worker), um die Last beim Freigeben nachzustellen. Alle 865 Tests waren grün, der langsamste brauchte 1302 ms (smtp-settings-form Test 2). Die Tests von `tenant-selector` lagen dort bei höchstens 950 ms.
|
||||
|
||||
**Kein Test lag über 2 s**, weder im normalen noch im gedrosselten Lauf. Damit gibt es nichts weiter zu beheben oder aufzulisten.
|
||||
|
||||
**Beobachtung, nicht behoben:** Das Muster „Komponente per `await import()` im Test laden“ steckt noch in 37 weiteren Web-Testdateien. Es macht jeweils den ersten Test einer Datei unter Last anfälliger. Keiner dieser Tests liegt heute nahe am Limit. Umgestellt sind nur der Marktplatz-Ordner und die Proxmox-Kachel.
|
||||
|
||||
## Aufgabe 2 – DashboardImage Stufe 2 (Commit dd54ec5)
|
||||
|
||||
**Migration `20260924120000_dashboard_image_drop_data`:**
|
||||
1. Eine Schutzprüfung im `DO`-Block: Existiert noch eine Zeile mit `storagePath IS NULL`, bricht die Migration mit `RAISE EXCEPTION` ab, und zwar mit der Meldung aus dem Plan.
|
||||
2. `ALTER COLUMN "storagePath" SET NOT NULL`, danach `DROP COLUMN "data"`.
|
||||
3. `DROP POLICY IF EXISTS system_read_policy ON "DashboardImage"`.
|
||||
|
||||
Die Migration musste einen anderen Namen bekommen als im Todo vorgemerkt (`20260922120100`), weil sie hinter allen vorhandenen Migrationen liegen muss (die letzte war `20260923160000`).
|
||||
|
||||
**Prüfung des Zeilenschutzes (RLS), Ergebnis:**
|
||||
- `DashboardImage` hat `FORCE ROW LEVEL SECURITY`, das gilt auch für den Eigentümer. Heute läuft `migrate deploy` als `tessera`. Die Rolle ist lokal gemessen Superuser mit BYPASSRLS und Eigentümerin der Tabelle, sieht also alle Zeilen.
|
||||
- **Die Falle ist real, lokal nachgewiesen:** Ein Eigentümer **ohne** BYPASSRLS bekommt bei `SELECT EXISTS (… storagePath IS NULL)` das Ergebnis `f`, obwohl eine solche Zeile existiert. Die Schutzprüfung wäre dann stumm wirkungslos. Getestet habe ich das mit einer Wegwerf-Rolle `m4n_owner` in einer Transaktion, die danach zurückgerollt wurde.
|
||||
- **Lösung:** Die Prüfung setzt `set_config('row_security', 'off', true)`. Sie sieht damit entweder alle Zeilen, oder PostgreSQL bricht laut ab. Die Meldung „ERROR: query would be affected by row-level security policy for table "DashboardImage"“ ist unter derselben Wegwerf-Rolle nachgewiesen. Als `tessera` greift die Schutzprüfung wie gewollt, auch das ist nachgewiesen. Die Datenbank sieht also nie „0 Zeilen, weiter“.
|
||||
|
||||
**Negativtest der Schutzprüfung** in einer Wegwerf-Datenbank `tessera_m4n_neg`, die danach gelöscht wurde. Alle Migrationen bis `20260923160000` wurden angewendet, dann eine Zeile mit Pfad und eine ohne Pfad angelegt:
|
||||
```
|
||||
Applying migration `20260924120000_dashboard_image_drop_data`
|
||||
Error: P3018
|
||||
Database error code: P0001
|
||||
ERROR: DashboardImage: es gibt noch Zeilen ohne storagePath — Umzug (quick-260922-hk4) zuerst mit einer Version >= 1.3.1 laufen lassen, dann erneut deployen
|
||||
```
|
||||
Danach war nichts geändert: `data` und `storagePath` waren weiterhin NULLbar, und `system_read_policy` stand noch.
|
||||
|
||||
**Nebenbefund, im Betriebshandbuch beschrieben:** Nach dem Abbruch verweigert auch 1.3.1 den Start mit `P3009` („failed migrations in the target database“). Den Ausweg habe ich vollständig durchgespielt. Zuerst wird der fehlgeschlagene Eintrag in `_prisma_migrations` als zurückgenommen markiert: `UPDATE … SET rolled_back_at = now() WHERE migration_name = '…' AND finished_at IS NULL`. Danach meldet der Stand von 1.3.1 „No pending migrations“. Anschließend habe ich den Umzug nachgestellt, und die neue Version lief sauber durch: NOT NULL gesetzt, Spalte entfernt, nur noch `tenant_isolation_policy` vorhanden. Der Ablauf steht in drei Schritten in `docs/anleitung-betrieb.md` Kapitel 4.
|
||||
|
||||
**Lokal angewendet** über die Container-IP 172.19.0.2 mit `tessera:tessera_dev`:
|
||||
|
||||
| Zeitpunkt | `pg_total_relation_size` | Zeilen |
|
||||
|---|---|---|
|
||||
| vorher (0 ohne Pfad, 1 Zeile mit 502 Bytes in `data`) | 65536 (64 kB) | 2 |
|
||||
| nach DROP | 65536 (64 kB) | 2 |
|
||||
| nach `VACUUM FULL "DashboardImage"` | 65536 (64 kB) | 2 |
|
||||
|
||||
Lokal ist keine Verkleinerung messbar. In `data` standen nur 502 Bytes, und die Tabelle belegt schon mit Tabelle, TOAST und zwei Indizes die Mindestgröße ganzer 8-kB-Seiten. Auf alpha und live mit echten Bildern ist der Gewinn größer. Dort gilt ebenfalls, dass PostgreSQL den Platz erst nach `VACUUM FULL` an das Dateisystem zurückgibt. Ob man das dort ausführt, entscheidest du; ich habe keinen Server angefasst.
|
||||
|
||||
**Dienst (`dashboard-images.service.ts`):**
|
||||
- `onApplicationBootstrap()` samt `forSystem()` ist entfernt, ebenso die Selbstheilung aus `data` in `getBytes`.
|
||||
- `upload` vergibt die UUID selbst mit `randomUUID()` und legt die Zeile gleich mit `storagePath` an, weil das Feld jetzt Pflicht ist. Das nachträgliche `update` entfällt. Das Verhalten bei halb fertigen Zuständen bleibt gleich: erst die Zeile, dann die Datei, und scheitert das Schreiben, wird die Zeile wieder gelöscht und 500 zurückgegeben.
|
||||
- Schema: `data Bytes?` ist gestrichen, `storagePath String?` wird zu `storagePath String`.
|
||||
|
||||
**Tests:** Die Fälle 18 und 21–23 entfallen wie geplant. Dazu fallen **10b und 10c** weg, weil sie die Selbstheilung aus `data` geprüft haben, die es nicht mehr gibt. Test 16 liest jetzt eine JPEG-Datei statt „Datei schlägt Zeilenbytes“, Test 13 prüft die UUID und den Pfad im `create`, und Test 12 prüft die neue Aufrufreihenfolge ohne `update` und ohne `forSystem`. Die Dienst-Spec hat damit 19 statt 25 Tests.
|
||||
|
||||
**Erlaubnisliste und Klassifikation:**
|
||||
- `FORSYSTEM_ALLOWED_CALL_SITES`: Der Eintrag `dashboard-images.service.ts` ist entfernt, jetzt 5 Dateien mit 6 Aufrufen.
|
||||
- `docs/mandantentrennung-zugriffsklassifikation.md`: Der Stand des Paars `dashboard-images.service.ts`/`dashboardImage` geht von `system-gebunden` zurück auf `gebunden`. Die Zahlen habe ich mit der Gate-Schleife (`for d in apps/api/src/*/`) **neu gemessen**:
|
||||
- Bereich `dashboard` geht auf **1/29/0**. Im Dokument stand 1/28/1, gemessen waren vor dieser Änderung aber schon 1/31/1, weil quick-260923-lrr zwei Treffer (favoriteLink) hinzugefügt hatte, ohne die Zeile nachzuziehen. Diese Änderung selbst zieht 2 gebundene Treffer und 1 System-Treffer ab.
|
||||
- Bereich `favorites` geht auf **0/12/0**. Im Dokument stand 0/8/0, auch hier war die Zeile nach quick-260923-lrr nicht nachgezogen worden.
|
||||
- Die Summe geht auf **61/213/6**, vorher 61/208/7.
|
||||
- Die Aufzählung der Tabellen mit `system_read_policy` ist korrigiert. Es sind jetzt sechs Tabellen (lokal gemessen), `ProxmoxServer` fehlte bisher in der Aufzählung, und `DashboardImage` ist nur noch als vorübergehender Fall erwähnt.
|
||||
- Gegenprobe: Den Stand habe ich absichtlich auf `system-gebunden` zurückgedreht. `rls-access-inventory.spec.ts` wurde rot („dokumentiert=system-gebunden, gemessen=gebunden“), danach habe ich die Datei zurückgesetzt.
|
||||
- Einen veralteten Kommentarverweis auf `DashboardImagesService` als Vorbild habe ich in `proxmox.service.ts` entfernt.
|
||||
|
||||
**CHANGELOG:** bewusst nicht ergänzt, weil die Änderung für Nutzer nicht sichtbar ist.
|
||||
**Betriebshandbuch:** Kapitel 4 hat einen Hinweis für die erste Version nach 1.3.1 bekommen: was die Meldung bedeutet und die drei Schritte zur Wiederherstellung.
|
||||
|
||||
## Tore
|
||||
|
||||
- API-Tests komplett: **84 Dateien, 1364 Tests, grün**, einschließlich `rls-access-inventory.spec.ts` mit 30 Tests.
|
||||
- Web-Tests komplett: **91 Dateien, 865 Tests, grün**, auch gedrosselt auf 2 Kerne.
|
||||
- `pnpm turbo run type-check lint`: **9/9 Aufgaben erfolgreich**.
|
||||
- Biome-Warnungen: **web 53** (Grenze 53), **api 82** (Grenze 82).
|
||||
|
||||
## Abweichungen vom Plan
|
||||
|
||||
1. **[Rule 3 – blockierend] Todo-Ordner:** Den Ordner `.planning/todos/done/` aus dem Plan gibt es nicht, die Ablage im Projekt heißt `.planning/todos/completed/`. Beide Todos liegen deshalb dort, jeweils mit einem Nachtrag „Erledigt in quick-260924-m4n“.
|
||||
2. **[Rule 1 – Bug] Weg zurück nach einem Abbruch:** Die Meldung der Schutzprüfung rät, zuerst 1.3.1 laufen zu lassen. Allein das hätte aber nicht funktioniert, weil 1.3.1 danach mit P3009 abbricht. Der fehlende Zwischenschritt steht jetzt im Betriebshandbuch und im Kopfkommentar der Migration, und ich habe ihn durchgespielt.
|
||||
3. **[Rule 1 – Bug] Upload-Ablauf:** Mit `storagePath` als Pflichtfeld hätte der bisherige Ablauf (Zeile ohne Pfad anlegen, Pfad nachtragen) an der Datenbank scheitern müssen. Deshalb vergibt der Dienst die UUID jetzt selbst.
|
||||
4. **Zusätzliche Tests entfallen:** Neben 18 und 21–23 sind auch 10b und 10c weg (siehe oben).
|
||||
5. **Mehr Marktplatz-Dateien umgestellt:** Neben `tenant-selector` sind auch `marketplace.test.tsx` und `marketplace-filters.test.tsx` umgestellt. Es ist dasselbe Muster im selben Ordner.
|
||||
6. **Drift in der Klassifikation nachgeholt:** Die Zeilen `favorites` und `dashboard` stimmten schon vorher nicht mit der Messung überein. Beides ist jetzt nachgeholt und im Dokument begründet.
|
||||
|
||||
## Offen / für dich
|
||||
|
||||
- Live konnte ich von hier aus nicht prüfen. Die nächste Freigabe nach 1.3.1 setzt voraus, dass live einmal auf 1.3.1 gelaufen ist. Wenn nicht, bricht die Migration mit einer klaren Meldung ab, der Weg zurück steht in Kapitel 4.
|
||||
- Optional nach dem Einspielen auf alpha und live: `VACUUM FULL "DashboardImage";`, damit PostgreSQL den Platz der alten Bilddaten tatsächlich an das Dateisystem zurückgibt.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- FOUND: apps/api/prisma/migrations/20260924120000_dashboard_image_drop_data/migration.sql
|
||||
- FOUND: .planning/todos/completed/2026-09-23-flackernder-test-tenant-selector-zeitueberschreitung.md
|
||||
- FOUND: .planning/todos/completed/2026-09-22-dashboard-image-data-spalte-entfernen.md
|
||||
- FOUND: b10734f, dd54ec5 (gemessen: `git rev-list --count dd09c08..HEAD` = 2)
|
||||
Reference in New Issue
Block a user