CapabilityMatcher
The ONE criterion for "does this `provides` satisfy this `requires`". It exists because the family had four, and they disagreed. Measured in `docs/library/settlement-q-p17.md`: | comparador | identidad | `oneOf` | `contractVersion` | `\` inicial | |-----------------------------|--------------------|---------|-------------------|-------------| | `GraphResolver` (motor) | `id` | sí | **sí** | no | | `CapabilityGraphChecker` | `id` + `interface` | sí | no | no | | `CapabilityGraphValidator` | **sólo** interface | **no** | no | sí | | `PluginInspection` | `id` + `interface` | **no** | no | no | The inspector was strictly weaker than the engine — not a second opinion, a worse one — and it was the surface offered as a diagnostic tool. Two components that decide whether an app boots cannot have two laws. ## Identity `php:` is an explicit, strippable annotation, NOT something inferred from how a string looks. Deducing the scheme from syntax (a `\` means a class) would turn spelling into meaning, and a capability id like `crm.oauth.google.v1` has no backslash and is not a class. So: - `php:Acme\Thing` and `Acme\Thing` are the SAME identity — that is the migration alias between the canonical form and the legacy bare FQCN it replaces, declared here on purpose rather than guessed per call site; - a leading `\` never distinguishes (`\Acme\Thing` ≡ `Acme\Thing`); - `env:DATABASE_URL` lives in its own namespace and never collides with a PHP identity; - anything else is an OPAQUE id, kept verbatim. No claim is made about what it names. A `provides` record offers TWO identities — its `id` and its `interface` — because {@see \Milpa\ValueObjects\Capability\CapabilityProvision} carries both and the family declares requirements against either. ## Contract compatibility, and the unknown version Identity and compatibility are separate questions, and this class keeps them separate: {@see identitiesOffered()} answers the first, {@see satisfies()} answers both. An absent `contractVersion` means UNKNOWN — never `0.0.0`. The legacy wrapper {@see \Milpa\ValueObjects\Capability\CapabilityProvision::fromInterface()} pins `0.0.0` so the record can be constructed, but `0.0.0` is a real version that a `^1.0` constraint rejects *for being too old*, which is a different statement from *nobody said*. Here an unknown version satisfies `*` and nothing else — same verdict the engine already produced, an honest reason.
CapabilityMatcher::identityMatches()
public function identityMatches(array|string $provision, array|string $requirement): boolWhether a `provides` entry NAMES the thing a `requires` entry asks for, saying nothing about versions. This is the pre-boot question — "does *some* plugin provide this at all" — and keeping it separate from {@see satisfies()} is deliberate: the layering between an identity check before boot and a range check in the resolver was never the defect. The defect was that each layer used a DIFFERENT identity law, so one accepted what the other rejected. Both layers now ask this one.
Parameters
| Name | Type | Description |
|---|---|---|
| $provision | (string | array<string, mixed>) | |
| $requirement | (string | array<string, mixed>) |
CapabilityMatcher::satisfies()
public function satisfies(array|string $provision, array|string $requirement): boolWhether a `provides` entry satisfies a `requires` entry: identities must match AND the declared contract version must satisfy the constraint. This is the resolver's question. A consumer that only needs to know whether the capability exists at all wants {@see identityMatches()}.
Parameters
| Name | Type | Description |
|---|---|---|
| $provision | (string | array<string, mixed>) | |
| $requirement | (string | array<string, mixed>) |
CapabilityMatcher::identitiesOffered()
public function identitiesOffered(array|string $entry): arrayThe canonical identities a `provides` entry offers: the bare string itself, or a record's `id` and `interface`.
Parameters
| Name | Type | Description |
|---|---|---|
| $entry | (string | array<string, mixed>) |
CapabilityMatcher::identitiesAccepted()
public function identitiesAccepted(array|string $entry): arrayEvery canonical identity that satisfies one `requires` entry: its own, plus any `oneOf` alternatives. `oneOf` is the reason the inspector and the validator used to report violations the engine never had: a requirement the engine resolves through an alternative must not fail anywhere else.
Parameters
| Name | Type | Description |
|---|---|---|
| $entry | (string | array<string, mixed>) |
CapabilityMatcher::constraintOf()
public function constraintOf(array|string $entry): stringThe semver constraint a `requires` entry imposes: its `constraint` field, or the range packed into the identity itself (`some.capability@^1.0` — the third syntax, which host profiles use), or `*`.
Parameters
| Name | Type | Description |
|---|---|---|
| $entry | (string | array<string, mixed>) |
CapabilityMatcher::contractVersionOf()
public function contractVersionOf(array|string $entry): ?stringThe contract version a `provides` entry declares, or NULL when nobody said. Null is not `0.0.0`: see the class docblock.
Parameters
| Name | Type | Description |
|---|---|---|
| $entry | (string | array<string, mixed>) |
CapabilityMatcher::contractIsCompatible()
public function contractIsCompatible(?string $contractVersion, string $constraint): boolWhether a declared contract version satisfies a constraint. An unknown version (null) satisfies only the unconstrained `*`.
Parameters
| Name | Type | Description |
|---|---|---|
| $contractVersion | ?string | |
| $constraint | string |