Files
Maven/internal/loop/digest_identity_test.go
T
claude 5c01fe338b 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.
2026-08-13 11:35:10 +04:00

55 lines
1.6 KiB
Go

package loop
import (
"testing"
"time"
"github.com/kami/maven/internal/store"
)
func TestBreakDigestFingerprintTracksOccurrenceNotTickTime(t *testing.T) {
now := refTime()
lastBreak := store.Fact{
ID: 41, Ts: now.Add(-2 * time.Hour), Kind: store.KindSelf,
Key: "break", Value: "done", Source: "tap:voice", Confidence: 1,
}
state := State{Now: now, Facts: map[string]store.Fact{"break": lastBreak}}
first, ok := DigestCandidateFingerprint(BreakRule(), state)
if !ok || first == "" {
t.Fatal("break rule did not produce a digest fingerprint")
}
state.Now = now.Add(20 * time.Minute)
second, ok := DigestCandidateFingerprint(BreakRule(), state)
if !ok || second != first {
t.Fatalf("the same break occurrence changed with tick time: %q then %q", first, second)
}
newBreak := lastBreak
newBreak.ID++
newBreak.Ts = now.Add(-100 * time.Minute)
state.Facts["break"] = newBreak
third, ok := DigestCandidateFingerprint(BreakRule(), state)
if !ok || third == first {
t.Fatal("a new last-break observation did not change candidate meaning")
}
}
func TestDigestCandidateFingerprintFailsClosedWithoutRuleIdentity(t *testing.T) {
r := Rule{Name: "future-care-rule", Severity: Sev2, Predicate: func(State) bool { return true }}
if got, ok := DigestCandidateFingerprint(r, State{}); ok || got != "" {
t.Fatalf("undeclared digest identity must fail closed, got %q, ok=%v", got, ok)
}
}
func TestDefaultDigestEligibleRulesDeclareIdentity(t *testing.T) {
for _, r := range DefaultRules() {
if r.Severity != Sev2 {
continue
}
if r.DigestIdentity == nil {
t.Errorf("digest-eligible default rule %q has no durable identity", r.Name)
}
}
}