Issue #3614604: A policy whose plugin is missing is silently skipped, so a limit fails open

An attachment stores a plugin id. If nothing defines that id any more, the row still says an operator configured a rule, and the rule most often configured is a cap. The manager kept a policy only when it could define its plugin id and said nothing when it could not, so a rename or an uninstall disarmed every attachment already stored against the old id: the rows were still there, the host's Policies tab still listed them, and nothing asked them. Silently.

An unresolvable attachment is now answered by a stand-in that refuses, through the plugin manager's own fallback (FallbackPluginManagerInterface), and the manager logs which host carries it and which id is missing. Refusing is the safe direction: a refusal is visible, reversible and reported, where an oversell is none of those.

The skip lived in two places, which is why removing the obvious one was not enough: runsAt() read the phases off the definition, so a missing plugin was dropped again a layer down. Both readers now resolve the definition through the fallback, so they agree with what createInstance() hands back.

The stand-in declares no hosts, so it is never offered as something to attach, and its refusal names no plugin id: the cause is a misconfiguration the visitor can neither see nor fix.

Coverage: BrokenPolicyTest asserts the refusal, that the message names no plugin id, that the host and id reach the log, that the published ceiling is zero, that the stand-in is unattachable, and that an intact rate still sells. Verified discriminating: with only the attachmentsFor() change reverted, four of the six fail and the intact-rate control still passes.

Docs and translations ride along.

Merge request reports

Loading