Give a suppressed rule a durable semantic identity (V-687)
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.
This commit is contained in:
@@ -23,6 +23,13 @@ type Rule struct {
|
||||
Cooldown // base cooldown + bounded-duration envelope for the auto-tuner
|
||||
Predicate func(State) bool
|
||||
|
||||
// DigestIdentity names one semantic occurrence while this rule is held by
|
||||
// quiet hours, presence, or calendar restraint. The durable digest checks
|
||||
// its fingerprint before asking the phraser for prose, so it must change
|
||||
// when the candidate's meaning changes and remain stable while only Now
|
||||
// advances. Only rules eligible for that digest need to declare it.
|
||||
DigestIdentity DigestIdentity
|
||||
|
||||
// InertWhenNoData — most rules should be silent when their substrate key is
|
||||
// missing (since(key)==null → don't fire). If the predicate already encodes
|
||||
// that check itself, leave this empty. Otherwise set to the key(s) the rule
|
||||
@@ -109,6 +116,17 @@ func BreakRule() Rule {
|
||||
Severity: Sev2,
|
||||
Cooldown: Cooldown{Base: 45 * time.Minute, Min: 20 * time.Minute, Max: 4 * time.Hour},
|
||||
InertWhenNoData: []string{"desk_active", "break"},
|
||||
// The last completed break is the semantic anchor for this stretch.
|
||||
// desk_active is freshness evidence for the predicate and may be
|
||||
// refreshed every poll; treating each refresh as a new occurrence would
|
||||
// defeat durable dedupe even though the unmet need did not change.
|
||||
DigestIdentity: func(s State) []byte {
|
||||
lastBreak, ok := s.Fact("break")
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return factDigestIdentity(lastBreak)
|
||||
},
|
||||
Predicate: func(s State) bool {
|
||||
dDesk, ok1 := s.Since("desk_active")
|
||||
dBreak, ok2 := s.Since("break")
|
||||
|
||||
Reference in New Issue
Block a user