feat: central Divi release lock for the Divi tools
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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<string, mixed> */
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user