diff --git a/src/Abilities/AbilityRegistrar.php b/src/Abilities/AbilityRegistrar.php index be1352e..37e7ca8 100644 --- a/src/Abilities/AbilityRegistrar.php +++ b/src/Abilities/AbilityRegistrar.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace LIT\MCP\Abilities; use LIT\MCP\Access; +use LIT\MCP\Integrations\Divi\Release\DiviLocked; +use LIT\MCP\Integrations\Divi\Release\DiviToolGate; use LIT\MCP\Integrations\IntegrationRegistry; use LIT\MCP\Log; use LIT\MCP\Server\RequestContext; @@ -14,15 +16,18 @@ use WP_Error; defined('ABSPATH') || exit; /** - * Registers all abilities of available integrations with the Abilities API and - * wraps them with the role gate and neutral error handling. + * Registers all abilities of available integrations with the Abilities API and wraps them + * with the role gate, the Divi release lock (DiviToolGate) and neutral error handling. */ final class AbilityRegistrar { public const CATEGORY = 'lit-mcp'; - public function __construct(private IntegrationRegistry $registry, private ?ExposurePolicy $policy = null) - { + public function __construct( + private IntegrationRegistry $registry, + private ?ExposurePolicy $policy = null, + private ?DiviToolGate $divi = null + ) { } public function register(): void @@ -50,6 +55,7 @@ final class AbilityRegistrar /** @return array */ public function argsFor(Ability $ability): array { + $divi = $this->divi ?? new DiviToolGate($this->registry->groupMap(...)); $args = [ 'label' => $ability->label(), 'description' => $ability->description(), @@ -71,15 +77,18 @@ final class AbilityRegistrar return Errors::internal(); } }, - 'execute_callback' => static function (mixed $input = null) use ($ability): mixed { + 'execute_callback' => static function (mixed $input = null) use ($ability, $divi): mixed { + $refusal = $divi->refusal($ability); + if ($refusal !== null) { + return $refusal; + } + try { - return $ability->execute(is_array($input) ? $input : []); - } catch (WriteFailed $e) { - Log::failure($ability->name(), $e); - return $e->toError(); + return $divi->annotate($ability, $ability->execute(is_array($input) ? $input : [])); } catch (Throwable $e) { Log::failure($ability->name(), $e); - return Errors::internal(); + + return self::errorFor($e); } }, 'meta' => [ @@ -95,11 +104,28 @@ final class AbilityRegistrar ], ]; - $output = $ability->outputSchema(); + $output = $divi->outputSchema($ability, $ability->outputSchema()); if ($output !== null) { $args['output_schema'] = $output; } return $args; } + + /** + * A partial write stays partial_write; otherwise a Divi release lock anywhere in the cause chain is its refusal + * (spec §5.5); a rolled-back write is write_failed; anything else internal_error. + */ + private static function errorFor(Throwable $e): WP_Error + { + if ($e instanceof WriteFailed && ! $e->rolledBack) { + return $e->toError(); + } + $locked = DiviLocked::in($e); + if ($locked !== null) { + return $locked->toError(); + } + + return $e instanceof WriteFailed ? $e->toError() : Errors::internal(); + } } diff --git a/src/Admin/LogListTable.php b/src/Admin/LogListTable.php index 54cc36a..707a375 100644 --- a/src/Admin/LogListTable.php +++ b/src/Admin/LogListTable.php @@ -126,8 +126,8 @@ final class LogListTable extends \WP_List_Table } /** - * Marks rows whose write did not complete (result "partial": failed after an - * irreversible step, spec §2 principle 7/8.2) so admins notice them while scrolling. + * Marks rows whose write did not complete (result "partial": failed after an irreversible step, + * spec §2 principle 7/8.2) or was refused (Divi release lock) so admins notice them while scrolling. * * @param array $item */ @@ -150,6 +150,7 @@ final class LogListTable extends \WP_List_Table return match ($result) { 'ok' => null, 'partial' => ['warning', __('teilweise', 'wp-lit-mcp')], + 'refused' => ['warning', __('abgelehnt', 'wp-lit-mcp')], 'failed', 'error' => ['error', __('fehlgeschlagen', 'wp-lit-mcp')], default => ['error', __('unbekannt', 'wp-lit-mcp')], }; diff --git a/src/Admin/LogTab.php b/src/Admin/LogTab.php index 6bae64a..82860fa 100644 --- a/src/Admin/LogTab.php +++ b/src/Admin/LogTab.php @@ -22,7 +22,7 @@ final class LogTab implements AdminTab /** Screen option "Einträge pro Seite" (user option; WordPress needs the "_per_page" end). */ public const PER_PAGE_OPTION = 'lit_mcp_log_per_page'; public const PER_PAGE_DEFAULT = 20; - private const RESULTS = ['ok', 'partial']; + private const RESULTS = ['ok', 'partial', 'refused']; private AdminPostAction $action; @@ -233,7 +233,11 @@ final class LogTab implements AdminTab self::filterLabel('result', __('Nach Ergebnis filtern', 'wp-lit-mcp')); echo '