$features The locked features the refused call needs. * @throws LogicException When $features is empty – a lock always names what it locks. */ public function __construct(public readonly ReleaseStatus $status, public readonly array $features) { if ($features === []) { throw new LogicException('a Divi lock needs the locked features'); } parent::__construct(sprintf( '%s is %s for LIT MCP – %s refused', $status->versionLabel(), $status->state->value, implode(', ', array_map(static fn (DiviFeature $feature): string => $feature->value, $features)) )); } public function toError(): WP_Error { return $this->status->refusalFor($this->features); } /** The lock anywhere in $e's chain of previous exceptions (callers wrap failures in WriteFailed). */ public static function in(Throwable $e): ?self { for ($cause = $e; $cause !== null; $cause = $cause->getPrevious()) { if ($cause instanceof self) { return $cause; } } return null; } }