refactor(compat): Capability-Interface, DiviFeature implementiert es
Grundlage der generischen Kompatibilitätsschicht für Fremd-Plugins mit inoffiziellen Schnittstellen (Plan docs/superpowers/plans/2026-11-06-kompatibilitaetsschicht.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
691231f776
commit
9beb55e228
@@ -0,0 +1,79 @@
|
||||
# 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<Capability>`), `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<Capability>`), `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 |
|
||||
| 1.15 | `CompatibilityTab` + `CompatibilitySection` (Filter `lit_mcp_compat_sections`, lazy); Zustand (Wort + Icon) und Probleme je Fähigkeit für Nicht-`ReleasePerVersion`-Profile | `CompatibilityTabTest`: drei Zustände, Sektion eingebettet, escaped | `CoreIndependenceTest` grün | sonnet |
|
||||
| 1.16 | `DiviTab implements CompatibilitySection` (`title()` „Divi“); Slug/Redirects/`requested()`/Notice auf `compatibility`; `DiviIntegration` hängt über `lit_mcp_compat_sections` ein | `DiviTabTest`, `DiviIntegrationTest` | Release/Withdraw/Orphans/Render unverändert | sonnet |
|
||||
| 1.17 | Links/Texte: `DiviCheckRoute`, `ReleaseStatus:24`, `docs/guides/overview.md:96`, `divi-release-smoke.sh`, Docblock `DiviCheckCommand` | `DiviCheckRouteTest` `tab=compatibility`; `ReleaseStatusTest` „→ Compatibility“ | DDEV-Smoke grün | haiku |
|
||||
| 1.18 | Reiter in `Plugin::boot` registrieren (nach `LogTab`); Pods-Smoke erweitern | `PluginTest` Tab-Liste | DDEV: `compatibility ok`, Pods ohne Probleme; hohe minVersion → Pods-Felder schreibgeschützt | 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) · 11 `feat(admin): Reiter „Compatibility“ mit
|
||||
Divi als Abschnitt` (1.15–1.18) · 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`).
|
||||
Reference in New Issue
Block a user