docs: Divi safeguard design spec (version gate, Divi boundary, admin check)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-30 00:01:20 +02:00
co-authored by Claude Opus 5.5
parent 903246df74
commit 94b8c7b338
@@ -0,0 +1,210 @@
# Divi-Absicherung – Design-Spezifikation
- **Datum:** 2026-09-29
- **Status:** Entwurf zur Freigabe
- **Autor:** Jan-Niclas Loosen (Loosen-IT), erarbeitet mit Claude
- **Einordnung:** eigene Etappe vor Etappe 4 (Designsystem schreiben); Etappe 4 baut darauf auf.
## 1. Ziel und Kontext
### 1.1 Problem
Die Divi-Integration hängt an Divi-Interna, nicht an einer zugesicherten Schnittstelle (Divi 5 bietet zum Schreiben keine an). Stand 0.5.0:
- 19 Dateien rufen Divi direkt auf (`et_*`-Funktionen, `ET\…`-Klassen, `_et_*`-Meta, Option `et_divi`), auch außerhalb von `Integrations/Divi/` (`MediaUsageFinder`, `PostPresenter`, `ContentSchema`).
- Einige Stellen **bilden Divi-Speicherlogik nach** (Theme-Builder-Templateliste/Bedingungen/`_et_enabled` nach `et_theme_builder_api_save()`, Bibliothekseinträge nach `et_pb_submit_layout()`, Builder-Kennzeichen nach `SyncToServerController`). Ändert Divi diese Logik, schreibt das Plugin still falsche Daten.
- Welche Divi-Teile genutzt werden, steht an drei Orten (`DiviCompatibility`, `DiviThemeBuilder::FUNCTIONS`, Docblocks mit Zeilennummern aus Divi 5.2.1). `DiviCompatibility` prüft nur Existenz, nicht Verhalten.
- Eine Divi-Version wird nicht geprüft.
### 1.2 Ziele (Vorgabe Auftraggeber)
1. **Weniger Aufwand pro Divi-Update.**
2. **Ungetestete Divi-Version → nichts schreiben, klare Antwort.** Lesende Divi-Tools laufen weiter mit Hinweis „Achtung, ungetestet“; schreibende Divi-Tools sind gesperrt und sagen warum.
3. **Freigabe ohne Plugin-Update möglich:** getestete Versionen stehen fest im Code; zusätzlich kann ein Administrator auf einer Website im WP-Admin eine Prüfung laufen lassen und die Version danach freigeben (Eintrag in der Datenbank). Freigeben geht nur nach bestandener Prüfung.
4. **Wartbarkeit:** Divi-Zugriffe an einer Stelle bündeln (bestehender Code wird umgebaut, nicht nur neuer Code).
### 1.3 Erfolgskriterien
1. Außerhalb der fünf Divi-Adapter (§3) gibt es keinen direkten Divi-Zugriff; ein Unit-Test erzwingt das.
2. Jede genutzte Divi-Funktion/-Klasse/-Methode, jeder Post-Typ, jede Taxonomie, jeder Meta-Schlüssel und jede Option steht im Manifest ihres Adapters; nachgebaute Divi-Funktionen haben einen Fingerabdruck.
3. Mit einer nicht freigegebenen Divi-Version schreibt kein Divi-Tool; die Antwort trägt `divi_untested` bzw. `divi_incompatible` und einen deutschen Hinweis. Lesende Divi-Tools liefern ihr Ergebnis mit Hinweis in `warnings`.
4. Die Admin-Prüfung läuft auf einer Live-Website ohne sichtbare Spuren: die Wegwerfseite ist danach in jedem Fall gelöscht.
5. Alle bestehenden Unit-Tests und Smoke-Skripte sind nach dem Umbau unverändert grün (Umbau ohne Verhaltensänderung).
6. Eine neue Divi-Version lokal prüfen ist ein Befehl (`wp lit-mcp divi-check`); bei Rot zeigt `--diff` die geänderten Divi-Funktionen.
### 1.4 Nicht Teil dieser Etappe
- Sperren für Pods (nutzt nur die öffentliche Pods-API) oder den MCP-Adapter.
- Automatisierter Browsertest des Visual Builders.
- Schreiben ins Designsystem (Etappe 4 – dort gleich mit Manifest).
## 2. Begriffe
- **Adapter:** Klasse, die als einzige Divi direkt anspricht (§3).
- **Manifest:** Beschreibung der Divi-Abhängigkeiten eines Adapters (§4).
- **Nachbau (Mirror):** Plugin-Code, der Speicherlogik einer Divi-Funktion wiederholt, statt sie aufzurufen.
- **Fingerabdruck:** SHA-256 des normalisierten Quelltexts einer Divi-Funktion/-Methode (§4.2).
- **Live-Prüfung:** Abgleich aller Manifeste mit dem installierten Divi (Existenz + Fingerabdrücke), zur Laufzeit, zwischengespeichert (§5.4).
- **Admin-Prüfung:** vollständige Prüfung aus dem WP-Admin inkl. Schreibtest (§6).
## 3. Divi-Grenze
Nur fünf Adapter greifen auf Divi zu. Vorhandene Klassen werden wo möglich zu Adaptern bzw. in sie hineingezogen, damit der Umbau klein bleibt.
| Adapter | Übernimmt (Stand 0.5.0) |
|---|---|
| **Seiten** | `DiviSaver` (Divi-Teil), `DiviFlags`, CSS-Cache `ET_Core_PageResource`, `WpLinkTargets` (Divi-Teil) |
| **Bibliothek** | `DiviLibraryStore`, `LibraryItemSpec`, `NewLibraryEntry`, `DiviRoles`, `LibraryAccess` (Divi-Teil), `GlobalLayout` |
| **Theme Builder** | `DiviThemeBuilder`, `Template`, `TemplateConditions` (Divi-Teile) |
| **Designsystem** | `DesignSystemReader`, `DesignSystem` (Divi-Teil), Option `et_divi` inkl. `divi_logo` für `MediaUsageFinder` |
| **Katalog** | `DiviModules`, `DynamicContentMap`, Divi-Post-Typen/Flags für `PostPresenter` und `ContentSchema` |
Die genaue Klassenaufteilung legt der Plan fest; Regel: jede Divi-Berührung liegt in genau einem Adapter, der Rest des Plugins nutzt nur dessen Methoden.
Parser, Serializer und `Compose/` bleiben, wo sie sind: sie kennen das Divi-**Format** (Blockmarkup, Attributpfade), rufen aber keine Divi-**Funktionen** auf. Ihr Schutz ist die Katalog- und Formatprüfung (§6.2 Schritte 2–3).
**Architekturtest (Unit):** durchsucht `src/`; außerhalb der Adapter-Dateien sind `et_*(`-Aufrufe, `ET\…`- und `ET_…`-Klassen sowie `'_et_…'`/`'et_divi'`-Schlüssel ein Fehler. Innerhalb der Adapter ist jede solche Fundstelle ein Fehler, die nicht im Manifest des Adapters steht. Während des Umbaus gibt es eine Ausnahmeliste für Altbestand; am Ende der Etappe ist sie leer und wird entfernt.
## 4. Manifest
### 4.1 Inhalt
Jeder Adapter liefert ein Manifest (festes PHP-Wertobjekt, keine Konfigurationsdatei) mit:
- `functions`: Divi-Funktionen, die aufgerufen werden;
- `methods`: Klasse + Methode (+ erwartete Mindest-Parameterzahl), z. B. `ET\Builder\Packages\GlobalData\GlobalData::get_global_colors`;
- `classes`: Klassen, deren Existenz genügt;
- `post_types`, `taxonomies`, `block_types`;
- `meta_keys`, `options`: Divi-Schlüssel, die gelesen oder geschrieben werden (dokumentarisch; Architekturtest nutzt sie);
- `mirrors`: nachgebaute Divi-Funktionen/-Methoden mit Fingerabdruck und Kurzbeschreibung, was nachgebaut wird;
- `sources`: Quellenangaben (Divi-Datei, Funktion) – ersetzt die Zeilennummern in Docblocks. Zeilennummern entfallen.
### 4.2 Fingerabdruck
`sha256` über den Quelltext der Funktion bzw. Methode (per `ReflectionFunction`/`ReflectionMethod`: Datei, Start- und Endzeile), normalisiert mit `token_get_all()`: `T_COMMENT`, `T_DOC_COMMENT`, `T_WHITESPACE` entfernt, übrige Tokens aneinandergehängt. Kommentar- und Formatänderungen ändern den Fingerabdruck nicht; Codeänderungen schon.
Die Soll-Fingerabdrücke werden mit dem Plugin ausgeliefert (erzeugte Datei im Plugin, z. B. JSON neben den Manifesten). Die normalisierten Soll-Quelltexte liegen nur für die Entwicklung unter `tests/Fixtures/divi/reference/` (per `.gitattributes` nicht im Plugin-Paket) und dienen `--diff` (§7).
### 4.3 `DiviCompatibility`
Verliert ihre handgepflegte Liste und prüft alle Manifeste: Existenz von Funktionen/Klassen/Methoden (inkl. Parameterzahl), Post-Typen, Taxonomien, Block-Typen; Fingerabdrücke der Mirrors. Ergebnis: Liste roter Punkte mit Adapter, Divi-Symbol, Fundort in der installierten Version. Die bisherigen Sonderprüfungen (z. B. `divi/global-layout` registriert oder in den Core-Metadaten) werden Manifest-Einträge mit eigener Prüfregel.
## 5. Freigabe und Sperre zur Laufzeit
### 5.1 Divi-Version
Aus dem aktiven Theme wie bisher (`EnvironmentProbe`: Template `Divi`, bei Child-Theme Version des Eltern-Themes).
### 5.2 Zustände
| Zustand | Bedingung | Schreibende Divi-Tools | Lesende Divi-Tools |
|---|---|---|---|
| `released` | Version freigegeben (§5.3) **und** Live-Prüfung grün | laufen | laufen |
| `untested` | Live-Prüfung grün, Version nicht freigegeben | gesperrt, `divi_untested` | laufen, Hinweis |
| `incompatible` | Live-Prüfung rot | gesperrt, `divi_incompatible` | laufen, Hinweis |
Die Live-Prüfung zählt immer, auch für Versionen aus der Code-Liste (erkennt z. B. Hotfixes mit gleicher Versionsnummer). Ist Divi nicht aktiv, gibt es wie bisher keine Divi-Tools; der Zustand ist dann ohne Bedeutung.
### 5.3 Quellen der Freigabe
Eine Divi-Version ist freigegeben, wenn sie
1. **im Code** steht: feste Liste getesteter Versionen (PHP-Konstante), vom Entwickler gepflegt; **oder**
2. **in der Datenbank der Website** steht: Option `lit_mcp_divi_releases`, Liste von Einträgen `{divi_version, plugin_version, user_id, released_at, report_id}`. Ein Eintrag entsteht nur über die Admin-Prüfung nach grünem Bericht (§6.3) und gilt **nur für die Plugin-Version, mit der geprüft wurde**. Nach einem Plugin-Update muss eine nur per Datenbank freigegebene Divi-Version neu geprüft werden, außer sie steht inzwischen in der Code-Liste. Ein Administrator kann Einträge zurückziehen.
Multisite: pro Website (Optionen der jeweiligen Website).
### 5.4 Live-Prüfung und Zwischenspeicher
Die Live-Prüfung (Manifeste, §4.3) wird im Transient `lit_mcp_divi_state` zwischengespeichert, Schlüssel aus Divi-Version, Plugin-Version und `filemtime` der Dateien aller Mirrors und Methoden. Weicht der Schlüssel ab oder lief ein Theme-Update (`upgrader_process_complete` für das Divi-Theme), wird neu berechnet. Kosten ohne Cache: Reflection + Tokenisieren von rund zehn Funktionen.
### 5.5 Sperre
**Zentral (`AbilityRegistrar`):** Für jedes Tool der Divi-Integration wird vor `execute` der Zustand ermittelt.
- Schreibendes Tool (Annotation nicht `readonly`) und Zustand ≠ `released`: sofort `WP_Error` mit Code `divi_untested` bzw. `divi_incompatible`, keine Ausführung, Protokolleintrag als abgelehnter Aufruf.
- Lesendes Tool und Zustand ≠ `released`: normale Ausführung, Hinweis in `warnings` des Ergebnisses.
Welche Tools „Divi-Tools“ sind, ergibt sich aus der Integrations-Zugehörigkeit (`IntegrationRegistry::groupMap()`); Divi-lastige Tools anderer Integrationen deklarieren sich zusätzlich (Ability-Eigenschaft „nutzt Divi“), ein Unit-Test prüft die Zuordnung.
**In den Adaptern:** Jede schreibende Adapter-Methode prüft den Zustand selbst und wirft bei ≠ `released` eine eigene Ausnahme, die auf dieselben Fehlercodes abgebildet wird; bestehendes Rollback greift. Das deckt Wege ab, die Divi-Interna indirekt nutzen (z. B. Content-Update einer Divi-Seite über `DiviSaver`). Generisches Kopieren/Wiederherstellen von Posts (`PostCloner`, Revisionen) berührt keine Divi-Interna und bleibt ungesperrt.
**Prüfmodus:** Der Schreibtest der Admin-Prüfung (§6.2 Schritt 4) muss trotz `untested` schreiben. Er setzt dafür eine request-lokale Ausnahme, die ausschließlich für die ID der Wegwerfseite gilt; sie ist nicht von außen setzbar (kein Parameter, keine Option).
### 5.6 Hinweistexte
- `divi_untested`: „Achtung: Divi {version} ist auf dieser Website noch nicht für LIT MCP freigegeben. Schreibende Divi-Werkzeuge sind gesperrt, bis ein Administrator unter Einstellungen → LIT MCP → Divi die Prüfung ausführt und die Version freigibt. Es wurde nichts geändert.“
- `divi_incompatible`: „Divi {version} hat Stellen geändert, die LIT MCP nutzt. Schreibende Divi-Werkzeuge sind gesperrt, bis LIT MCP aktualisiert wurde. Es wurde nichts geändert.“
- Lesehinweis (`warnings`): „Achtung: Divi {version} ist für LIT MCP ungetestet (bzw. inkompatibel); Lesen funktioniert, Schreiben ist gesperrt.“
### 5.7 `lit-site-status`
Zeigt für Divi zusätzlich: Zustand, Quelle der Freigabe (`code`/`database` mit Datum und Benutzer), rote Punkte der Live-Prüfung.
## 6. Admin-Prüfung
### 6.1 Oberfläche und Rechte
Neuer Reiter **„Divi“** unter Einstellungen → LIT MCP: Zustand, Divi-Version, Quelle der Freigabe, letzter Prüfbericht, Knöpfe „Prüfung starten“, „Divi {version} freigeben“, „Freigabe zurückziehen“. Alle Aktionen: `manage_options`, Nonce, `admin-post`-Aktionen wie die übrigen Reiter (`AdminPostAction`). Eine Sperre (Transient) verhindert zwei gleichzeitige Prüfungen.
### 6.2 Prüfschritte
1. **Manifeste:** Live-Prüfung (§4.3), ohne Cache.
2. **Modulkatalog:** Alle Attributpfade aus `ShortformMap` existieren im Live-Katalog von Divi. Die Logik von `tests/Integration/divi-shortform-check.php` wandert dafür ins Plugin; das Skript nutzt danach die Plugin-Klasse.
3. **Format im Speicher:** Ein mit dem Plugin ausgelieferter Referenzinhalt (Divi-5-Blockmarkup, deckt die Alltagsmodule ab) wird mit dem Plugin-Parser gelesen und serialisiert und muss byte-identisch herauskommen; `parse_blocks()` muss dieselbe Blockstruktur (Namen, Verschachtelung) liefern.
4. **Schreibtest auf Wegwerfseite:** privater Entwurf „[LIT MCP Prüfung] {Datum}“ (Post-Typ `page`); über den normalen Schreibweg (Composer → Serializer → Seiten-Adapter) je ein Exemplar der Alltagsmodule schreiben; prüfen: Inhalt gespeichert wie serialisiert, Builder-Kennzeichen gesetzt, Rendern über Divi ohne PHP-Fehler/Warnung und mit den erwarteten Texten in der Ausgabe. Kein Eintrag im Änderungsprotokoll. Im `finally` immer `wp_delete_post($id, true)` (endgültig, mit Revisionen und Meta); schlägt das fehl, nennt der Bericht die ID zum Löschen von Hand und der Bericht ist rot.
Jeder Schritt meldet grün/rot mit Einzelpunkten. Ein Fehler in einem Schritt bricht die übrigen nicht ab (außer Schritt 4 bei rotem Schritt 1: dann kein Schreibtest).
### 6.3 Bericht und Freigabe
Der Bericht wird in der Option `lit_mcp_divi_check_report` gespeichert (letzter Bericht; ID, Zeit, Benutzer, Divi-Version, Plugin-Version, Cache-Schlüssel aus §5.4, Ergebnisse). „Freigeben“ ist nur aktiv – und wird serverseitig nur ausgeführt –, wenn der letzte Bericht vollständig grün ist und Divi-Version, Plugin-Version und Cache-Schlüssel zum aktuellen Stand passen. Freigabe und Zurückziehen werden im Änderungsprotokoll vermerkt.
Hinweis im Bericht (blockiert nichts): „Optional: eine Divi-Seite im Visual Builder öffnen und speichern.“
## 7. WP-CLI für die Entwicklung
`wp lit-mcp divi-check` (nur registriert, wenn `WP_CLI` definiert ist) führt die Prüfschritte aus §6.2 aus und gibt den Bericht aus; Rückgabecode ≠ 0 bei Rot. Freigaben schreibt der Befehl nicht.
- `--diff`: zeigt für jeden roten Mirror den Unterschied zwischen Soll-Quelltext (`tests/Fixtures/divi/reference/`) und installiertem Quelltext, mit Datei und Zeilen in der installierten Version.
- `--update-reference`: schreibt Soll-Fingerabdrücke und Referenz-Quelltexte neu – nur, wenn alle übrigen Prüfschritte grün sind. Gedacht nach dem Anpassen eines Nachbaus.
Ablauf bei neuer Divi-Version: lokal einspielen → `wp lit-mcp divi-check` → bei Grün Version in die Code-Liste eintragen; bei Rot `--diff`, Nachbau anpassen, `--update-reference`, Smoke-Skripte, Version eintragen → Plugin-Release.
## 8. Daten und Deinstallation
Neu: Optionen `lit_mcp_divi_releases`, `lit_mcp_divi_check_report`; Transients `lit_mcp_divi_state`, Prüfsperre. `Uninstaller` und `uninstall.php`-Doku werden ergänzt.
## 9. Fehlerbehandlung
- Reflection/Tokenisieren scheitert (Funktion fehlt, Datei nicht lesbar): Punkt rot → `incompatible`, nie Ausnahme nach außen.
- Zustand nicht ermittelbar (unerwartete Ausnahme): wie `incompatible` behandeln (Schreiben gesperrt), Protokoll-Fehlereintrag.
- Admin-Prüfung bricht ab (Timeout, Fatal im Rendern): Wegwerfseite im `finally` gelöscht; bei Fatal zusätzlich beim nächsten Aufruf des Reiters nach verwaisten „[LIT MCP Prüfung]“-Entwürfen des Plugins suchen (Marker-Meta) und löschen.
## 10. Tests
**Unit:**
- Zustandsberechnung: alle Kombinationen aus Code-Liste, DB-Eintrag (passende/andere Plugin-Version), Live-Prüfung grün/rot.
- Sperre in `AbilityRegistrar`: lesend/schreibend × drei Zustände; Fehlercodes und Texte; `warnings` bei lesenden Tools.
- Sperre in Adaptern inkl. Prüfmodus nur für die Wegwerfseiten-ID.
- Fingerabdruck: Kommentar/Leerraum ändert nichts, Codeänderung schon.
- Architekturtest (§3) und Test „jedes Divi-Tool ist als solches erkannt“.
- Freigabe nur bei frischem grünem Bericht (Version, Plugin-Version, Cache-Schlüssel); Rechte und Nonce.
- `Uninstaller` entfernt die neuen Optionen/Transients.
**Integration gegen echtes Divi (Smoke-Skript):**
- Admin-Prüfung komplett grün; Wegwerfseite danach nicht mehr vorhanden (auch keine Revisionen).
- Künstlich veränderter Soll-Fingerabdruck → `incompatible`, schreibendes Tool liefert `divi_incompatible`, lesendes Tool liefert Ergebnis + Hinweis.
- Version aus Code-Liste entfernt → `untested`; nach Admin-Prüfung + Freigabe → `released`.
- `wp lit-mcp divi-check` grün; `--diff` bei verändertem Soll zeigt den Unterschied.
**Regression:** alle bestehenden Unit-Tests und Smoke-Skripte unverändert grün.
## 11. Umsetzungsreihenfolge
1. Manifest-Format, Fingerabdruck, Architekturtest mit Ausnahmeliste für Altbestand.
2. Adapter nacheinander umbauen, bis die Ausnahmeliste leer ist; `DiviCompatibility` auf Manifeste umstellen; Docblock-Zeilennummern entfernen.
3. Zustand, Freigabe (Code-Liste, Datenbank), zentrale und Adapter-Sperre, `lit-site-status`.
4. Admin-Reiter „Divi“, Prüfschritte, Wegwerfseite, Bericht, Freigabe.
5. WP-CLI-Befehl mit `--diff` und `--update-reference`.
6. Security-Audit; danach Etappe 4.