fix(ambient): re-check active mode after the judge call (coexistence TOCTOU)
This commit is contained in:
@@ -236,6 +236,16 @@ func (s *Sentinel) evaluate(ctx context.Context) {
|
||||
cancel()
|
||||
|
||||
s.mu.Lock()
|
||||
// A mode may have activated during the (off-lock) brain call; re-check so a
|
||||
// result computed while idle is not surfaced over an now-active mode.
|
||||
if s.activeModeLocked() != "" {
|
||||
changed := s.clearLocked()
|
||||
s.mu.Unlock()
|
||||
if changed {
|
||||
s.fireOnChange()
|
||||
}
|
||||
return
|
||||
}
|
||||
s.lastEvalTitles = joined
|
||||
if err != nil {
|
||||
s.mu.Unlock()
|
||||
|
||||
@@ -228,6 +228,42 @@ func TestEvaluateCheapGateSkipsUnchangedOnTrack(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// flippingJudge simulates a harness mode activating DURING the brain call: it
|
||||
// returns a drift verdict but flips the active-mode flag as a side effect, so by
|
||||
// the time evaluate re-acquires the lock a mode is active.
|
||||
type flippingJudge struct{ active *string }
|
||||
|
||||
func (f *flippingJudge) AmbientDrift(_ context.Context, _ string, _ []string) (string, error) {
|
||||
*f.active = "focus"
|
||||
return "deep in YouTube", nil
|
||||
}
|
||||
|
||||
// A mode activating mid-evaluation must suppress the surface: no line, no toast,
|
||||
// no memory record (coexistence rule holds across the unlock-for-I/O window).
|
||||
func TestEvaluateModeActivatingDuringJudgeStaysSilent(t *testing.T) {
|
||||
active := ""
|
||||
n := &fakeNotifier{}
|
||||
mem := &memoryFake{}
|
||||
s := New(Deps{
|
||||
AI: &flippingJudge{active: &active},
|
||||
Notifier: n,
|
||||
Memory: mem,
|
||||
Clock: func() time.Time { return time.Unix(1_700_000_000, 0) },
|
||||
ActiveMode: func() string { return active },
|
||||
}, time.Minute, modeNotify)
|
||||
s.OnWindow(snap("YouTube - Brave", "Brave"))
|
||||
s.evaluate(context.Background())
|
||||
if s.Line() != "" {
|
||||
t.Fatalf("mode active after judge must leave line empty, got %q", s.Line())
|
||||
}
|
||||
if n.calls != 0 {
|
||||
t.Fatalf("mode active after judge must not toast, got %d", n.calls)
|
||||
}
|
||||
if len(mem.events) != 0 {
|
||||
t.Fatalf("mode active after judge must not record memory, got %d", len(mem.events))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnoozeMutesAndClears(t *testing.T) {
|
||||
j := &fakeJudge{out: []string{"drift", "drift"}}
|
||||
active := ""
|
||||
|
||||
Reference in New Issue
Block a user