The digest needs to know whether a candidate is already pending before it pays
the phraser, and prose is not identity: phrasing varies, and State.Now advancing
does not turn the same unmet condition into a new event.
A rule eligible for the digest declares DigestIdentity beside its predicate.
DigestCandidateFingerprint frames the rule name and severity around it so two
rules cannot alias on a shared fact. BreakRule anchors on the last completed
break, not on desk_active, which the poller refreshes without the unmet need
changing. A rule that declares no identity does not enter the digest, since a
generic state hash would either change every tick or ignore an input the rule
reads.