fix(policies): reject material policy attachments that can never apply
Closes #3774
A policy without material types (kind-less) applies only to the material named by the attachment selector. A contract that attached such a policy without selector.name was accepted, but the policy never ran and nothing reported it. For a gating policy, this looked like a passing check.
Changes:
-
Control plane: contract create, update and apply now reject a material policy attachment that can never apply:
- a kind-less policy without
selector.name, for example:policy "upstream-workflow-attested" applies to any material type and requires a material selector (selector.name) - a typed policy whose exact selector names a contract material of a type that the policy does not support.
A selector that names a material not declared in the contract, or a selector that is not an exact match (such as
PREFIX), is still accepted, because it can match materials added at attestation time. - a kind-less policy without
-
Attestation init: the same check runs when
attestation init --contract <file>sends the contract, because that path also creates or updates the contract. A pipeline whose contract attaches a policy that can never apply now fails at init with the same error. -
Shared rule: the applicability rule of
shouldApplyPolicymoves into pure functions (attachmentApplies,canMatchAnyMaterial). Policy evaluation and the new validation both use them, so the two can't drift.
Not in scope:
- Policy groups: a kind-less policy in an unnamed group material does not silently do nothing. The group verifier applies it to every material. This PR leaves that behavior unchanged.
policy develop eval --material-name: not part of this PR.- Embedded policies in contracts: they go through the provider lookup with an empty name in
findAndValidatePolicy, which fails. This is a separate, existing issue.
This PR was written with AI assistance (Claude Code).