DriftDetector
Detects manifest drift: the gap between what a package DECLARES in its `milpa.json` and what its code ACTUALLY carries in `#[PluginMetadata]`. It diffs the two {@see VersionManifest}s field by field — `name`, `version`, and the `provides`/`requires`/`suggests` capability sets (identity compared by interface/id, normalized with `ltrim('\\')` so a leading-backslash FQCN and its bare form are equal) — and {@see toLearnableErrors()} lifts a non-empty diff into the one `MILPA_MANIFEST_DRIFT` learnable error per package, built from the {@see ErrorCatalog} so the drift teaches (why + fixes + Academy lesson) instead of merely alarming. The engine never emits this code; the host's inspect surface calls the detector and attaches what it returns.
DriftDetector::diff()
public function diff(Milpa\Resolver\Manifest\VersionManifest $declared, Milpa\Resolver\Manifest\VersionManifest $actual): arrayDiff a declared manifest against the actual one.
Parameters
| Name | Type | Description |
|---|---|---|
| $declared | Milpa\Resolver\Manifest\VersionManifest | |
| $actual | Milpa\Resolver\Manifest\VersionManifest |
DriftDetector::toLearnableErrors()
public function toLearnableErrors(array $diff, string $package): arrayLift a diff into learnable errors: an empty diff yields none; a non-empty one yields exactly ONE `MILPA_MANIFEST_DRIFT` error for the package, with context `{package, fields: the diff rows}` — message, why, fixes, and learn links all templated by the {@see ErrorCatalog}.
Parameters
| Name | Type | Description |
|---|---|---|
| $diff | list<array{field: string, declared: (string | null), actual: (string | null)}> | |
| $package | string |