# Kompatibilitätsschicht für Fremd-Plugins (Phase 0–2) Stand 2026-11-06, Branch `feat/compatibility-layer`. Gate 1: voller Architektur-Entwurf (Benutzerentscheidung), weil Pods künftig komplett über den Kern laufen soll und WP Super Cache direkt folgt. **Regeln für jeden Task:** zuerst ein fehlschlagender Test, dann die kleinste Änderung; nach jedem Task `composer test` grün und `composer lint` ohne neue Befunde (zwei Warnungen bestehen schon: `wp-lit-mcp.php:6`, `RegisteredMetaSourceTest.php:78`). `DiviBoundaryTest` und `CoreIndependenceTest` laufen immer mit. Ein roter Divi-Test heißt anhalten. Verschiebe-Tasks sind mechanisch (Namespace + `use`), keine Verhaltensänderung. Keine Commits durch Umsetzungs-Agents (Gate 2 je Commit-Block). ## Entscheidungen - Kern `src/Compatibility/` (Namespace `LIT\MCP\Compatibility`), importiert kein Divi (`CoreIndependenceTest`). - `Capability` (`id()`, `label()`, `isWrite()`); `DiviFeature implements Capability`, `id()` = `value`. - Kein eigenes `RequiresCapabilities`: Tools deklarieren Fähigkeits-IDs über das vorhandene `DeclaresRequirements`. - Tier: `ReportOnly` (nie sperren), `LockOnFailure` (hartes Problem sperrt, sonst frei; `signatures:false`), `ReleasePerVersion` (nur Divi; Matrix kommt per Closure aus `ReleaseGate::status()->matrix`). ReleaseStore, Resolver-Workflow, WriteProbe, CLI, FeatureSets bleiben Divi-eigen. - Gates: `CapabilityGate` überspringt IDs von `ReleasePerVersion`-Profilen, prüft IDs anderer Profile über `Compatibility::lockOf` und sperrt IDs, die kein Profil kennt. `DiviFeatureGate` ignoriert Nicht-Divi-IDs wieder. Umstellung atomar in 1.13. - Pods-IDs mit Präfix `pods-`, WPSC mit `wpsc-`; Divi-IDs bleiben unpräfixiert. Eindeutigkeitstest über alle Profile. - Pods und WPSC werden `Integration`en ohne Abilities, die `ProvidesCompatibility` implementieren; leere Gruppen dürfen weder im Tools-Tab noch in den Server-Anweisungen erscheinen. - Admin: ein gemeinsamer Reiter „Compatibility“ (Slug `compatibility`); Divi-Freigabe wird Sektion darin (`lit_mcp_compat_sections`). Kein Redirect vom alten Slug `divi`, `DiviTab` wird nicht umbenannt. - WPSC: Hook `lit_mcp_changed` in `ChangeLog::record` (nur `result = ok`, eigenes try/catch); Listener nutzt `wp_cache_post_edit` bzw. `wp_cache_clear_cache(blog)`; kein `lit/cache-clear`. ## Phase 0 – Sicherheitsfixes (erledigt: 8f3a4e7, 234fd1e, 691231f) ## Phase 1 – Kern, Pods, Reiter | # | Ziel und Dateien | Zuerst rot | Akzeptanz | Modell | |---|---|---|---|---| | 1.1 | `src/Compatibility/Capability.php`; `DiviFeature implements Capability` + `id()` | `DiviFeature::PageRead instanceof Capability`, `id()==='page-read'` | Divi-Tests grün | haiku | | 1.2 | `ReleaseState`, `SourceFingerprint`, `LineDiff` nach `src/Compatibility`; `'Compatibility'` in `CORE_PATHS` | `CoreIndependenceTest::testEveryScannedPathExists`; verschobene Tests unter neuem Namespace | kein `Integrations\Divi`-Import im Kern | haiku | | 1.3 | `Requirement` (`features(): list`), `Call` (+`accepts()` aus `ManifestCheck::accepts`, `signatureKey`), `ConstantRequirement`, `Mirror`, `SignatureFingerprint` verschieben; `ManifestCheck::accepts` delegiert | `CallTest` am neuen Ort; `ManifestCheckTest` Regression | Divi-Suite grün | sonnet | | 1.4 | `Problem`, `FeatureStatus` verschieben (Capability); `Problem::fromArray(mixed, Closure(string): ?Capability)`; `ReleaseResolver::cached` übergibt `DiviFeature::tryFrom(...)`; Transient-Format bleibt | `ProblemTest` unbekannte ID → null; `ReleaseResolverTest` | Transient-Rundlauf unverändert | sonnet | | 1.5 | `CompatibilityMatrix` generisch (`list`), `uniform`/`allIncompatible` mit Capabilities, `fromProblems(caps, problems, ?source, automatic)` aus `ReleaseResolver` (~89-129); `SOURCE_*` auf die Matrix, Aliase in `ReleaseStatus`; Aufrufer `ReleaseGate`, `ReleaseStatus`, `ReleaseResolver`, `DiviCheckCommand`, `tests/Support` | `CompatibilityMatrixTest` mit Fake-Capability `pods-x`; `fromProblems` gegen bisherigen Resolver-Fall | `ReleaseResolverTest`, `ReleaseStatusTest` unverändert grün | sonnet | | 1.6 | `DiviAreas` (PAGES, LIBRARY, THEME_BUILDER, DESIGN, CATALOG) aus `Manifest` | `DiviAreasTest`; `ManifestTest` | reine Umbenennung | haiku | | 1.7 | `Manifest` (Capabilities), `GlobalRequirement` (`isset($GLOBALS[name])`, hart), `ManifestCheck` nach `src/Compatibility`, ohne Divi-Defaults, Option `signatures`; `Divi\Release\DiviManifestCheck::create()`; Aufrufer `ReleaseResolver`, `CompatibilityCheck`, `DiviCompatibility`, Tests mit `new ManifestCheck()` | `ManifestCheckTest` mit `signatures:false` ohne reference-missing; `GlobalRequirement` fehlt = hartes Problem; `DiviManifestCheckTest` | Kern ohne Divi; Divi-Release-Tests unverändert | sonnet | | 1.8 | `Tier`, `Profile` (id, label, tier, capabilities, manifests, ?matrix-Closure, ?version-Closure, ?minVersion, aktiv-Prüfung), `Compatibility` (matrix memoisiert, `lockOf`, `assert`, `usable`, `problems`, `capability(id)`, `active`) | `CompatibilityTest` mit Fake-Profilen je Tier, Memoisierung, unbekannte ID → null, minVersion sperrt alles | rein bis auf `defined()`/`get_file_data` | sonnet | | 1.9 | `CapabilityLocked` (RuntimeException implements ToolRefusal, `compat_incompatible`, 423, details features/problems, Fabrik für unbekannte IDs) | `CapabilityLockedTest` | analog `DiviLockedTest` | sonnet | | 1.10 | `CapabilityGate implements AbilityGuard` (Vorlage `DiviFeatureGate`), Log `refused` nur bei Schreibtools; noch nicht verdrahtet | `CapabilityGateTest`: Pods-ID gesperrt/frei, Divi-ID ignoriert, unbekannt gesperrt | annotate/outputSchema reichen durch | sonnet | | 1.11 | `ProvidesCompatibility`, `IntegrationRegistry::compatibilities()` (auch nicht verfügbare), `DiviProfile::profile()`, `DiviIntegration implements ProvidesCompatibility` | `IntegrationRegistryTest`, `DiviIntegrationTest` | Divi neutral | sonnet | | 1.12 | `PodsFeature` (`pods-schema-read`, `pods-field-read`, `pods-field-write`, `pods-field-permissions`), `PodsCompatibility::profile()` (LockOnFailure, PODS_VERSION, min 2.8, Calls mit Argumentzahlen gegen Pods 3.3.9.2), `PodsIntegration` ohne Abilities | `PodsCompatibilityTest`; Registry-Test: leere Integration nicht in `grouped()`/`labels()`/Instructions | keine leere Gruppe sichtbar | sonnet | | 1.13 | `CompatibilityComponent` (3 Zustände, Zeile je Fähigkeit); `EnvironmentProbe` ohne Pods (TEC bleibt); `Plugin`: Guards `[DiviFeatureGate, CapabilityGate]`, Komponenten per Closure aus der Registry; 0.3-Umzug (`unknownIds` entfällt, Test zieht in `CapabilityGateTest`); ID-Eindeutigkeit | `EnvironmentProbeTest`; Zwei-Gate-Test über `AbilityRegistrar::argsFor`; `PluginTest` | Reihenfolge Divi, Pods, TEC | sonnet | | 1.14 | `PodsFieldSource` + Schema-Wahl über `usable(PodsFeature::…)`; `isAvailable()` entfällt; Feldrechte über `usable(FieldPermissions)`, sonst fail-closed; 0.2-Bedingung = `active && ! usable(SchemaRead)` | `PodsFieldSourceTest` je Fähigkeit; `PluginTest` | alle Pods-Tests grün | sonnet | **Erledigt 1.1–1.14:** 9beb55e, b511ef6, c851316. **Neu-Zuschnitt 1.15–1.25 (Gate 1 vom 2026-11-06):** Prüfen und Freigeben sind für alle Profile im Reiter „Compatibility“ gleich; die Wirkung regelt das Tier (ReleasePerVersion = Pflicht, LockOnFailure = dokumentiert, Nutzung automatisch solange grün, ReportOnly = nur Status). Optionen `lit_mcp__releases|_check_report|_check_lock` – für `divi` exakt die heutigen Namen. **Divi behält auch das Datenformat:** Store und Bericht bekommen den Versionsschlüssel als Parameter (Divi `divi_version`, neue Profile `version`) – keine Migration, Rollback-sicher. Profil-ID aus POST nur nach Allowlist (`Compatibility::profile()`) in Optionsnamen. | 1.15 | `Profile`: `testedVersions`, `stateKey`, `extraSteps` (Closure(bool $manifestClean): list), `manifestCheck`; `DiviProfile` + `active`, `testedVersions=DiviReleases::TESTED`, `stateKey=ReleaseResolver::stateKey`; Pods `testedVersions=['3.3.9.2']` | `ProfileTest`, `DiviProfileTest`, `PodsCompatibilityTest` | Gates grün | sonnet (mit 1.16–1.18) | | 1.16 | `CheckStep`, `CheckReport`, `CheckReportStore(profileId, versionKey)` nach `src/Compatibility`; Label mitgespeichert; Divi-Schrittkonstanten in `DiviCheckSteps` | `CheckReportTest` (Divi-Rundlauf unverändert, Option je ID) | Divi-Suite grün | sonnet | | 1.17 | `ReleaseStore(profileId, versionKey)` nach `src/Compatibility` | `ReleaseStoreTest` (Divi-Format unverändert, Option je ID) | `ReleaseResolverTest` grün | sonnet | | 1.18 | `CompatibilityCheck` generisch (Manifest, Fingerabdrücke nur mit Mirrors, dann `extraSteps`); `Compatibility::check(Profile)`; Divi liefert Katalog/Format/Schreibtest als `extraSteps` | `CompatibilityCheckTest`, `DiviCheckStepsTest` | `DiviCheckCommandTest` grün, Divi-Schrittfolge gleich | sonnet | | 1.18a | Review-Befunde (Architektur-Review 2026-11-06): roter gespeicherter Bericht für den aktuellen Stand sperrt LockOnFailure-Fähigkeiten bis zur nächsten grünen Prüfung; gefangene Ausnahmen in `Compatibility` per `Log::failure` protokollieren; `Profile` nur mit benannten Argumenten bauen | `CompatibilityTest` (roter/grüner/veralteter/kein Bericht) | Divi unverändert | sonnet | | 1.19 | `Admin\CheckRoute` je Profil (`/-check`, Lock je Profil, Redirect `tab=compatibility`, `do_action('lit_mcp_compat_changed', $id)`; Divi invalidiert darauf `ReleaseGate`) | `CheckRouteTest`, `DiviIntegrationTest` | Lock-Tests portiert | sonnet | | 1.20 | `Admin\CompatibilityTab` read-only (Sektion je Profil, Zustand Wort+Icon, Tier-Hinweis, Fähigkeiten, Versionen, Bericht, Hook `lit_mcp_compat_profile_extra`; inaktiv ohne `matrix()`) | `CompatibilityTabTest` inkl. Escaping | `CoreIndependenceTest` grün | sonnet | | 1.21 | Formulare + admin-post `lit_mcp_compat_release|withdraw` (guard, Allowlist, Nonce je Profil) | `CompatibilityTabTest` (frischer Bericht, unbekanntes Profil, Nonce/403, Tier-Sichtbarkeit) | Release/Withdraw-Fälle aus `DiviTabTest` portiert | sonnet | | 1.22 | Reiter in `Plugin::boot` (nach `LimitsTab`), Hilfe-Tab | `PluginTest`, `SettingsPageTest` | grün | sonnet | | 1.23 | `DiviTab` → `DiviAdminExtras` (Notice, Orphans, Zusatzhinweise); `TABS_FILTER`-Eintrag entfällt | `DiviAdminExtrasTest`, `DiviIntegrationTest` | Boundary/Core grün | sonnet | | 1.24 | Texte/CSS (`lit-mcp-divi-*` → `lit-mcp-compat-*`), `ReleaseStatus:24`, `docs/guides/overview.md`, `Uninstaller` (`lit_mcp_pods_*`), `divi-release-smoke.sh` | `ReleaseStatusTest`, `UninstallerTest` | DDEV Divi-Smoke grün | sonnet | | 1.25 | Pods-Smoke: `/pods-check`, Freigabe schreibt `lit_mcp_pods_releases` ohne Sperrwirkung, hohe `minVersion` sperrt | Smoke rot ohne 1.19–1.21 | DDEV Pods- und Divi-Smoke grün | sonnet | ## Phase 2 – WP Super Cache 3.1.5 | # | Ziel und Dateien | Zuerst rot | Akzeptanz | Modell | |---|---|---|---|---| | 2.1 | Hook `lit_mcp_changed(ability, objectType, objectId, logId)` in `ChangeLog::record`, nur bei `ok`, auch bei fehlgeschlagenem Insert; werfender Listener → `Log::failure` | `ChangeLogTest` | bestehende Fälle grün | sonnet | | 2.2 | `SuperCacheFeature` (`wpsc-purge-post`, `wpsc-purge-all`), `SuperCacheCompatibility::profile()` (LockOnFailure, `WPCACHEHOME`, Version per `get_file_data`, `GlobalRequirement('cache_path')`), `SuperCacheIntegration` ohne Abilities | `SuperCacheCompatibilityTest` | Eindeutigkeitstest umfasst `wpsc-` | sonnet | | 2.3 | `SuperCacheListener` an `lit_mcp_changed`: nur bei `usable()` (sonst Log-Zeile) und `$GLOBALS['cache_enabled']`; öffentlicher Posttyp außer attachment → `wp_cache_post_edit($id)`; sonst `wp_cache_clear_cache(is_multisite() ? get_current_blog_id() : 0)`; eigenes try/catch | `SuperCacheListenerTest` mit injizierten Closures | lint sauber | sonnet | | 2.4 | Verdrahtung + Absatz in `docs/guides/overview.md` | `PluginTest` | `lit-site-status` listet WPSC | haiku | | 2.5 | `tests/Integration/super-cache-smoke.sh` (Feld-Update leert Beitrag, Einstellung leert alles; `et_pb_layout` öffentlich?) | Smoke rot ohne 2.3 | DDEV grün | sonnet | ## Commit-Schnitt 4 `refactor(compat): Capability und DiviFeature` (1.1) · 5 `refactor(compat): reine Release-Klassen nach src/Compatibility` (1.2–1.4) · 6 `refactor(compat): CompatibilityMatrix generisch mit fromProblems` (1.5) · 7 `refactor(compat): Manifest und ManifestCheck generisch, DiviAreas, DiviManifestCheck` (1.6–1.7) · 8 `feat(compat): Tier, Profile, Compatibility, CapabilityLocked, CapabilityGate` (1.8–1.10) · 9 `feat(compat): Profile aus Integrationen, Divi- und Pods-Profil` (1.11–1.12) · 10 `feat(compat): Gates, Status und Pods über den Kern verdrahten` (1.13–1.14) · 11a `refactor(compat): Freigabe-Bausteine generisch` (1.15–1.18) · 11b `feat(compat): Prüfroute je Profil` (1.19) · 11c `feat(admin): Reiter „Compatibility“ für alle Profile` (1.20–1.22) · 11d `refactor(divi): DiviTab geht im Reiter auf` (1.23–1.24) · 11e `test(compat): Smokes` (1.25) · 12 `feat(changelog): Hook lit_mcp_changed` (2.1) · 13 `feat(cache): WP Super Cache über den Kern anbinden` (2.2–2.4) · 14 `test(cache): Integrations-Smoke` (2.5) ## Risiken Vergessene `use`-Zeilen in Verschiebe-Tasks (voller Testlauf je Task); Lücke bei unbekannten IDs nur, wenn `CapabilityGate` fehlt (PluginTest prüft beide Guards); leere Integrationen verändern Registry-Ausgaben (1.12 prüft zuerst); `signatures:false` prüft nur Existenz und Argumentzahl; `wp_cache_post_edit` ist keine dokumentierte API (Wegfall sperrt nur den Purge, sichtbar im Reiter und in `lit-site-status`).