feat(compat): Profile, Gates und Pods über den Kompatibilitätskern

Neu: Tier, Profile, Compatibility, CapabilityLocked, CapabilityGate, CompatibilityComponent, ProvidesCompatibility, DiviProfile sowie PodsFeature, PodsCompatibility und PodsIntegration. Das CapabilityGate sperrt gesperrte und unbekannte Fähigkeits-IDs (übernimmt die Sperre unbekannter IDs vom DiviFeatureGate) und delegiert nur Divi ausdrücklich. Pods-Schema, -Lesen, -Schreiben und -Feldrechte laufen über usable(PodsFeature::…); Schreiben verlangt FieldRead und FieldWrite, damit ein Rollback keine Werte verliert. lit-site-status meldet Pods über die CompatibilityComponent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-11-06 12:26:54 +01:00
co-authored by Claude Opus 5.5
parent b511ef6348
commit c85131603b
38 changed files with 2259 additions and 135 deletions
@@ -36,10 +36,10 @@ final class CoreIntegrationTest extends TestCase
return ['id' => 'builder', 'active' => true, 'version' => '9', 'problems' => [], 'release' => null];
}
};
$core = new CoreIntegration(dirname(__DIR__, 3) . '/Fixtures/guides', [$component]);
$core = new CoreIntegration(dirname(__DIR__, 3) . '/Fixtures/guides', static fn (): array => [$component]);
$status = $core->abilities()[1]->execute([]);
self::assertSame(['builder', 'pods', 'the-events-calendar'], array_column($status['components'], 'id'));
self::assertSame(['builder', 'the-events-calendar'], array_column($status['components'], 'id'));
}
}
@@ -30,25 +30,38 @@ final class EnvironmentProbeTest extends TestCase
};
}
public function testGivenComponentsComeFirstInTheOrderGivenFollowedByPodsAndTheEventsCalendar(): void
public function testGivenComponentsComeFirstInTheOrderGivenFollowedByTheEventsCalendar(): void
{
$probe = new EnvironmentProbe([$this->component('builder', ['Etwas fehlt']), $this->component('shop')]);
$probe = new EnvironmentProbe(fn (): array => [$this->component('builder', ['Etwas fehlt']), $this->component('shop')]);
$components = $probe->components();
self::assertSame(['builder', 'shop', 'pods', 'the-events-calendar'], array_column($components, 'id'));
self::assertSame(['builder', 'shop', 'the-events-calendar'], array_column($components, 'id'));
self::assertSame(['Etwas fehlt'], $components[0]['problems']);
self::assertSame(['state' => 'released'], $components[1]['release']);
}
public function testWithoutComponentsOnlyPodsAndTheEventsCalendarAreReported(): void
public function testComponentsMayBeGivenLazilyAndAreAskedPerCall(): void
{
$asked = 0;
$probe = new EnvironmentProbe(function () use (&$asked): array {
$asked++;
return [$this->component('lazy')];
});
self::assertSame(0, $asked);
self::assertSame(['lazy', 'the-events-calendar'], array_column($probe->components(), 'id'));
$probe->components();
self::assertSame(2, $asked, 'asked per call, never cached: the state may change within a request');
}
public function testWithoutComponentsOnlyTheEventsCalendarIsReported(): void
{
$components = array_column((new EnvironmentProbe())->components(), null, 'id');
self::assertSame(['pods', 'the-events-calendar'], array_keys($components));
self::assertFalse($components['pods']['active'], 'no Pods in unit tests');
self::assertSame([], $components['pods']['problems']);
self::assertNull($components['pods']['release']);
self::assertSame(['the-events-calendar'], array_keys($components));
self::assertNull($components['the-events-calendar']['version']);
self::assertNull($components['the-events-calendar']['release']);
}
}