CapabilityGraphChecker
The provides/requires capability graph check: fails BEFORE boot, with a readable message, when a plugin's declared `requires` has no matching `provides` among the given plugins. This is the "A provee / B requiere" edge of the `plugin → capability → tool → verification → event → result` loop — every host application that boots plugins needs it, so it lives here instead of being hand-rolled per consumer (it originated as exactly that: an application-level `CapabilityGraph` reflecting `#[PluginMetadata]` by hand). Only `requires` is enforced. A missing `suggests` MUST NOT fail the graph — that is the whole point of {@see \Milpa\ValueObjects\Capability\CapabilitySuggestion} being an optional dependency with a `fallback`, and this checker honors it by simply never looking at `suggests`. This check only reasons about IDENTITY (does *some* plugin provide the required thing at all), never about `contractVersion`/`constraint` ranges — range-checking is the architecture resolver's job. That layering is deliberate and survives; what did NOT survive is this class having its own idea of what "the same capability" means. Identity now comes from {@see CapabilityMatcher}, the single criterion the resolver, the manifest validator and the inspector all consume (`settlement-q-p17.md` measured four comparators disagreeing). A `#[PluginMetadata]` entry may be a bare interface FQCN (legacy) or a structured capability record (canonical — T087), and the matcher treats `php:Acme\Thing` and `Acme\Thing` as one identity so both forms coexist. A record offers BOTH its `id` and its `interface`, and a record requirement is satisfied by either or by any of its `oneOf` alternatives (a requirement the resolver would satisfy via `oneOf` must not fail pre-boot here). A record entry with no readable identity contributes nothing — teaching the malformed-record failure is the ingestion layer's job ({@see \Milpa\ValueObjects\Capability\CapabilityProvision::fromArray()}), not this check's.
CapabilityGraphChecker::__construct()
public function __construct(Milpa\Services\CapabilityMatcher $matcher = new CapabilityMatcher()):Parameters
| Name | Type | Description |
|---|---|---|
| $matcher | CapabilityMatcher | The single identity criterion. Injectable so a consumer can share one instance; the default is the same law, not a different one. |
CapabilityGraphChecker::check()
public function check(array $plugins): voidChecks that every `requires` entry across `$plugins` is matched by a `provides` entry somewhere in `$plugins` (including, if declared, the requiring plugin's own `provides` — a plugin may satisfy its own requirement).
Parameters
| Name | Type | Description |
|---|---|---|
| $plugins | list<object> | Plugin instances carrying `#[PluginMetadata]`, or `PluginMetadata` records passed directly (e.g. already extracted from a manifest — no reflection needed in that case). |
Throws
AttributeNotFoundException If a plugin instance carries no `#[PluginMetadata]` attribute.
PluginDependencyException If a `requires` entry has no matching `provides` among `$plugins`.