package workspace

import (
	"errors"
	"fmt"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

// TestWorkingStateCacheSharedSingleflightColdAndAsyncSameType reproduces the
// stable panic sequence reviewer3 found in v1: stale async + LRU evict +
// same-key cold miss, where async returned (nil, nil) and cold path did
// `value.(workingStateLookup)`. With unified result type, cold and async
// share the singleflight safely and the type assertion never panics.
//
// Sequence:
//  1. Store entry A (loadedAt=T0), cap=2
//  2. Advance past TTL → A is stale
//  3. Stale get on A → kicks async, async's load blocks on release channel
//  4. While async blocks, store B and C → A evicted from LRU
//  5. Cold-miss get on A → joins the in-flight async singleflight
//  6. Release async load → both async and cold get workingStateLookup
//
// Asserts no panic and the cold-miss caller observes the loaded state.
func TestWorkingStateCacheSharedSingleflightColdAndAsyncSameType(t *testing.T) {
	now := time.Unix(1_700_000_000, 0)
	cache := newWorkingStateCache(2, time.Second, func() time.Time { return now })
	keyA := newWorkingStateCacheKey("/repo-a")

	// Pre-populate A directly so we control exactly when it enters the cache.
	cache.store(keyA, workingState{
		files:    []types.ReviewFile{{Path: "old"}},
		loadedAt: now,
	})

	asyncStarted := make(chan struct{})
	asyncRelease := make(chan struct{})
	var inflight atomic.Int32
	load := func() (workingState, error) {
		if inflight.Add(1) == 1 {
			close(asyncStarted)
			<-asyncRelease
		}
		return workingState{
			files:    []types.ReviewFile{{Path: "new"}},
			loadedAt: now,
		}, nil
	}

	// Advance past TTL → A is stale.
	now = now.Add(2 * time.Second)

	// Stale get on A → returns stale, kicks async refresh (load blocks).
	staleLookup, err := cache.getOrRefresh(keyA, load)
	if err != nil {
		t.Fatalf("stale getOrRefresh error: %v", err)
	}
	if staleLookup.fresh {
		t.Fatalf("stale getOrRefresh returned fresh=true")
	}
	<-asyncStarted

	// While async blocks, evict A by storing B and C (cap=2).
	cache.store(newWorkingStateCacheKey("/repo-b"), workingState{
		files:    []types.ReviewFile{{Path: "b"}},
		loadedAt: now,
	})
	cache.store(newWorkingStateCacheKey("/repo-c"), workingState{
		files:    []types.ReviewFile{{Path: "c"}},
		loadedAt: now,
	})
	if _, _, ok := cache.lookup(keyA); ok {
		t.Fatalf("precondition: A should have been evicted by B+C")
	}

	// Cold-miss get on A — joins the in-flight async flight. Before the fix,
	// this returned (nil, nil) from the shared flight and panicked on type
	// assertion.
	coldDone := make(chan error, 1)
	var coldResult workingStateLookup
	go func() {
		result, err := cache.getOrRefresh(keyA, load)
		coldResult = result
		coldDone <- err
	}()

	// Release the async load so the shared flight completes.
	close(asyncRelease)
	select {
	case err := <-coldDone:
		if err != nil {
			t.Fatalf("cold-miss getOrRefresh error: %v", err)
		}
	case <-time.After(2 * time.Second):
		t.Fatalf("cold-miss caller did not complete within 2s — shared-flight type assertion may have panicked or blocked")
	}

	if len(coldResult.state.files) != 1 || coldResult.state.files[0].Path != "new" {
		t.Fatalf("cold-miss caller got wrong state: %+v", coldResult)
	}
	if !coldResult.fresh {
		t.Fatalf("cold-miss caller should see fresh=true (load completed)")
	}
}

// TestWorkingStateCacheRefreshSemaphoreBounded proves the global semaphore
// caps concurrent async refreshes process-wide. With capacity=2 and 8 stale
// callers across 8 distinct keys, at most 2 refreshes run concurrently; the
// rest drop with asyncRefreshDropped (the front-most callers claim the slots
// and the rest must wait for the next poll cycle).
//
// Note: this test exercises the BOUND, not the spawn guarantee. Some drops
// are expected — the contract is "bounded concurrency", not "every stale
// call spawns a refresh".
func TestWorkingStateCacheRefreshSemaphoreBounded(t *testing.T) {
	now := time.Unix(1_700_000_000, 0)
	cache := newWorkingStateCache(16, time.Second, func() time.Time { return now })

	// Pre-populate 8 keys, all stale after the clock advances.
	for i := 0; i < 8; i++ {
		cache.store(newWorkingStateCacheKey(fmt.Sprintf("/repo-%d", i)), workingState{
			files:    []types.ReviewFile{{Path: fmt.Sprintf("old-%d", i)}},
			loadedAt: now,
		})
	}
	now = now.Add(2 * time.Second)

	// Loader blocks until released; counts concurrent invocations.
	var concurrent atomic.Int32
	var maxConcurrent atomic.Int32
	release := make(chan struct{})
	load := func() (workingState, error) {
		cur := concurrent.Add(1)
		for {
			prev := maxConcurrent.Load()
			if cur <= prev || maxConcurrent.CompareAndSwap(prev, cur) {
				break
			}
		}
		<-release
		concurrent.Add(-1)
		return workingState{files: []types.ReviewFile{{Path: "new"}}}, nil
	}

	// Fire 8 stale callers in parallel.
	var wg sync.WaitGroup
	start := make(chan struct{})
	for i := 0; i < 8; i++ {
		wg.Add(1)
		go func(idx int) {
			defer wg.Done()
			<-start
			_, _ = cache.getOrRefresh(newWorkingStateCacheKey(fmt.Sprintf("/repo-%d", idx)), load)
		}(i)
	}
	close(start)

	// Give refreshes time to spawn and reach max concurrency.
	time.Sleep(100 * time.Millisecond)
	close(release)
	wg.Wait()

	if got := maxConcurrent.Load(); got > int32(defaultWorkingStateRefreshConcurrency) {
		t.Fatalf("max concurrent refreshes = %d, want ≤ %d (semaphore bound)", got, defaultWorkingStateRefreshConcurrency)
	}
}

// TestWorkingStateCacheInflightRegistryIndependentOfLRU verifies that an LRU
// eviction between two stale callers for the same key does NOT bypass the
// per-key inflight registry. The second caller must still see the inflight
// marker and skip its own spawn rather than double-spawning.
func TestWorkingStateCacheInflightRegistryIndependentOfLRU(t *testing.T) {
	now := time.Unix(1_700_000_000, 0)
	cache := newWorkingStateCache(2, time.Second, func() time.Time { return now })

	keyA := newWorkingStateCacheKey("/repo-a")
	cache.store(keyA, workingState{
		files:    []types.ReviewFile{{Path: "old"}},
		loadedAt: now,
	})
	now = now.Add(2 * time.Second)

	started := make(chan struct{})
	release := make(chan struct{})
	var spawns atomic.Int32
	load := func() (workingState, error) {
		if spawns.Add(1) == 1 {
			close(started)
			<-release
		}
		return workingState{files: []types.ReviewFile{{Path: "new"}}}, nil
	}

	// First stale call → kicks async refresh.
	if _, err := cache.getOrRefresh(keyA, load); err != nil {
		t.Fatalf("first stale getOrRefresh error: %v", err)
	}
	<-started

	// Evict A from LRU without affecting the inflight registry.
	cache.store(newWorkingStateCacheKey("/repo-b"), workingState{
		files:    []types.ReviewFile{{Path: "b"}},
		loadedAt: now,
	})
	cache.store(newWorkingStateCacheKey("/repo-c"), workingState{
		files:    []types.ReviewFile{{Path: "c"}},
		loadedAt: now,
	})
	if _, _, ok := cache.lookup(keyA); ok {
		t.Fatalf("precondition: A should be evicted")
	}

	// Manually re-add A as stale so the next caller takes the stale path
	// (rather than cold-miss). Re-inserting A as stale lets us exercise
	// "stale lookup hit + inflight still set" without taking the cold-miss
	// branch.
	cache.mu.Lock()
	entry := &workingStateCacheEntry{
		key:       keyA,
		state:     workingState{files: []types.ReviewFile{{Path: "old-2"}}, loadedAt: now.Add(-2 * time.Second)},
		expiresAt: now.Add(-time.Second), // already expired
	}
	el := cache.order.PushFront(entry)
	cache.items[keyA] = el
	for cache.order.Len() > cache.cap {
		oldest := cache.order.Back()
		if oldest == nil {
			break
		}
		evicted := oldest.Value.(*workingStateCacheEntry)
		cache.order.Remove(oldest)
		delete(cache.items, evicted.key)
	}
	cache.mu.Unlock()

	// Second stale call → must NOT spawn another refresh (inflight marker
	// still set from the first call). It returns stale immediately.
	before := spawns.Load()
	startSecond := time.Now()
	if _, err := cache.getOrRefresh(keyA, load); err != nil {
		t.Fatalf("second stale getOrRefresh error: %v", err)
	}
	if elapsed := time.Since(startSecond); elapsed > 100*time.Millisecond {
		t.Fatalf("second stale call took %v — likely blocked on a second spawn instead of dropping via inflight registry", elapsed)
	}

	// Release the first refresh so the goroutine exits.
	close(release)
	// Wait briefly for spawns to settle.
	deadline := time.Now().Add(time.Second)
	for time.Now().Before(deadline) {
		if spawns.Load() == 1 {
			break
		}
		time.Sleep(5 * time.Millisecond)
	}
	if got := spawns.Load(); got != before {
		t.Fatalf("spawns = %d after second stale call, want %d (inflight registry should have prevented double-spawn across LRU eviction)", got, before)
	}
}

// TestWorkingStateCacheObservedAtReflectsScanTime confirms ReviewFingerprint
// surfaces the working scan's completion time, not the cache-serve time.
// The fingerprint's ObservedAt must stay anchored at the original cold-miss
// scan time across subsequent stale reads.
func TestWorkingStateCacheObservedAtReflectsScanTime(t *testing.T) {
	scanTime := time.Unix(1_700_000_000, 0)
	// nowVal is read by the cache's clock closure, which may be invoked from
	// the test goroutine OR a background refresh goroutine. Guard with a
	// mutex so -race stays clean.
	var nowMu sync.Mutex
	nowVal := scanTime
	cache := newWorkingStateCache(8, time.Second, func() time.Time {
		nowMu.Lock()
		defer nowMu.Unlock()
		return nowVal
	})
	setNow := func(t time.Time) {
		nowMu.Lock()
		nowVal = t
		nowMu.Unlock()
	}

	loadRan := make(chan struct{}, 1)
	load := func() (workingState, error) {
		// Do NOT mutate the clock from load — the cache layer stamps
		// state.loadedAt from c.now() after load returns. Mutating now here
		// would race with the async refresh goroutine reading c.now().
		loadRan <- struct{}{}
		return workingState{files: []types.ReviewFile{{Path: "f"}}}, nil
	}

	key := newWorkingStateCacheKey("/repo")
	lookup, err := cache.getOrRefresh(key, load)
	if err != nil {
		t.Fatalf("cold-miss getOrRefresh error: %v", err)
	}
	<-loadRan
	if got := lookup.state.loadedAt; !got.Equal(scanTime) {
		t.Fatalf("cold-miss loadedAt = %v, want scanTime %v", got, scanTime)
	}

	// Advance wallclock well past TTL and serve a stale read. The stale
	// entry's loadedAt must still reflect the original scan time, not the
	// advanced clock.
	setNow(scanTime.Add(2 * time.Second))
	stale, err := cache.getOrRefresh(key, load)
	if err != nil {
		t.Fatalf("stale getOrRefresh error: %v", err)
	}
	if stale.fresh {
		t.Fatalf("expected stale read, got fresh")
	}
	if got := stale.state.loadedAt; !got.Equal(scanTime) {
		t.Fatalf("stale loadedAt = %v, want scanTime %v (ObservedAt must reflect scan time, not serve time)", got, scanTime)
	}
}

// TestWorkingStateCacheAsyncRefreshSemaphoreDropIsBounded confirms that when
// the semaphore is full and an async refresh cannot spawn, the extra stale
// caller still returns immediately with the stale state (semaphore-bounded
// behavior). The dropped spawn is not observable via metrics anymore —
// instead we assert the caller does NOT block and the blocking loader count
// stays at the semaphore cap (i.e. the extra spawn never reached the loader).
func TestWorkingStateCacheAsyncRefreshSemaphoreDropIsBounded(t *testing.T) {
	now := time.Unix(1_700_000_000, 0)
	cache := newWorkingStateCache(16, time.Second, func() time.Time { return now })

	// Pre-populate cap+1 keys BEFORE advancing the clock so they all become
	// stale together. The first cap keys will claim the semaphore; the
	// (cap+1)th must drop.
	blockingRelease := make(chan struct{})
	spawned := make(chan struct{}, defaultWorkingStateRefreshConcurrency)
	for i := 0; i < defaultWorkingStateRefreshConcurrency; i++ {
		cache.store(newWorkingStateCacheKey(fmt.Sprintf("/repo-%d", i)), workingState{
			files:    []types.ReviewFile{{Path: "old"}},
			loadedAt: now,
		})
	}
	extraKey := newWorkingStateCacheKey("/repo-extra")
	cache.store(extraKey, workingState{
		files:    []types.ReviewFile{{Path: "extra-old"}},
		loadedAt: now,
	})
	now = now.Add(2 * time.Second)

	var spawnedCount atomic.Int32
	blockingLoad := func() (workingState, error) {
		spawnedCount.Add(1)
		spawned <- struct{}{}
		<-blockingRelease
		return workingState{files: []types.ReviewFile{{Path: "new"}}}, nil
	}
	for i := 0; i < defaultWorkingStateRefreshConcurrency; i++ {
		_, err := cache.getOrRefresh(newWorkingStateCacheKey(fmt.Sprintf("/repo-%d", i)), blockingLoad)
		if err != nil {
			t.Fatalf("stale getOrRefresh %d error: %v", i, err)
		}
	}
	// Wait for all blocking loads to start (semaphore full).
	for i := 0; i < defaultWorkingStateRefreshConcurrency; i++ {
		select {
		case <-spawned:
		case <-time.After(time.Second):
			t.Fatalf("blocking load %d did not start", i)
		}
	}

	// One more stale call for /repo-extra must NOT block and must NOT spawn a
	// new load (semaphore full → spawn dropped).
	start := time.Now()
	got, err := cache.getOrRefresh(extraKey, blockingLoad)
	if err != nil {
		t.Fatalf("extra stale getOrRefresh error: %v", err)
	}
	if elapsed := time.Since(start); elapsed > 100*time.Millisecond {
		t.Fatalf("extra stale call took %v — likely blocked on a spawn instead of dropping via semaphore bound", elapsed)
	}
	if got.fresh {
		t.Fatalf("extra stale getOrRefresh returned fresh=true")
	}
	if got.state.files[0].Path != "extra-old" {
		t.Fatalf("extra stale getOrRefresh returned %+v, want stale extra-old", got)
	}

	close(blockingRelease)
	// Give the in-flight refreshes time to finish and release their semaphore
	// slots. We don't drain the `spawned` channel here because each load only
	// sends once (before blocking on blockingRelease); the post-spawn state is
	// observed via spawnedCount instead.
	deadline := time.Now().Add(2 * time.Second)
	for time.Now().Before(deadline) {
		cache.mu.Lock()
		inflightCount := len(cache.inflight)
		cache.mu.Unlock()
		if inflightCount == 0 {
			break
		}
		time.Sleep(5 * time.Millisecond)
	}
	cache.mu.Lock()
	inflightCount := len(cache.inflight)
	cache.mu.Unlock()
	if inflightCount != 0 {
		t.Fatalf("inflight registry not cleared after release (len=%d)", inflightCount)
	}
	if got := spawnedCount.Load(); got > int32(defaultWorkingStateRefreshConcurrency) {
		t.Fatalf("spawned loads = %d, want ≤ %d (semaphore bound should drop the extra spawn)", got, defaultWorkingStateRefreshConcurrency)
	}
}

// TestWorkingStateCacheNilCacheGetOrRefreshDoesNotPanic confirms the nil-safe
// path still works after the refactor — a nil cache delegates to load
// directly.
func TestWorkingStateCacheNilCacheGetOrRefreshDoesNotPanic(t *testing.T) {
	var cache *workingStateCache
	loadCalled := false
	lookup, err := cache.getOrRefresh(newWorkingStateCacheKey("/repo"), func() (workingState, error) {
		loadCalled = true
		return workingState{files: []types.ReviewFile{{Path: "x"}}}, nil
	})
	if err != nil {
		t.Fatalf("nil cache getOrRefresh error: %v", err)
	}
	if !loadCalled {
		t.Fatalf("nil cache did not call load")
	}
	if !lookup.fresh {
		t.Fatalf("nil cache should return fresh=true (load completed synchronously)")
	}
	if lookup.state.loadedAt.IsZero() {
		t.Fatalf("nil cache should still set loadedAt on the returned state")
	}
}

// TestWorkingStateCachePanickingLoadIsRecovered confirms a panicking async
// loader cannot crash the daemon: the stale caller observes the stale state
// without the panic propagating, and the cache stays usable for subsequent
// calls. (Previously this test also asserted a counter increment; the metrics
// surface has since been removed as dead code.)
func TestWorkingStateCachePanickingLoadIsRecovered(t *testing.T) {
	now := time.Unix(1_700_000_000, 0)
	cache := newWorkingStateCache(8, time.Second, func() time.Time { return now })
	key := newWorkingStateCacheKey("/repo")
	cache.store(key, workingState{
		files:    []types.ReviewFile{{Path: "old"}},
		loadedAt: now,
	})
	now = now.Add(2 * time.Second)

	load := func() (workingState, error) {
		panic("simulated go-git panic")
	}

	type result struct {
		got            workingStateLookup
		err            error
		panicRecovered any
	}
	done := make(chan result, 1)
	go func() {
		r := result{}
		defer func() {
			if rec := recover(); rec != nil {
				r.panicRecovered = rec
			}
			done <- r
		}()
		r.got, r.err = cache.getOrRefresh(key, load)
	}()
	select {
	case r := <-done:
		if r.panicRecovered != nil {
			t.Fatalf("panic from stale getOrRefresh propagated to caller: %v", r.panicRecovered)
		}
		if r.err != nil {
			t.Fatalf("stale getOrRefresh error: %v", r.err)
		}
		if r.got.fresh {
			t.Fatalf("stale getOrRefresh returned fresh=true")
		}
	case <-time.After(2 * time.Second):
		t.Fatalf("stale getOrRefresh did not return within 2s — panic may have blocked the caller")
	}

	// Give the async refresh time to land its panic recovery.
	time.Sleep(100 * time.Millisecond)

	// Cache must still be usable.
	freshLookup, err := cache.getOrRefresh(newWorkingStateCacheKey("/other-repo"), func() (workingState, error) {
		return workingState{files: []types.ReviewFile{{Path: "fresh"}}}, nil
	})
	if err != nil {
		t.Fatalf("post-panic getOrRefresh error: %v", err)
	}
	if !freshLookup.fresh {
		t.Fatalf("post-panic getOrRefresh returned fresh=false")
	}
}

// TestWorkingStateCacheColdMissErrorIsPropagated guards the cold-miss path:
// the loader's error is returned to the caller and the cache does not store
// anything for the failed key. (Previously this test also asserted a counter
// increment; the metrics surface has since been removed as dead code.)
func TestWorkingStateCacheColdMissErrorIsPropagated(t *testing.T) {
	now := time.Unix(1_700_000_000, 0)
	cache := newWorkingStateCache(8, time.Second, func() time.Time { return now })
	key := newWorkingStateCacheKey("/repo-err")
	expected := errors.New("scan failed")
	_, err := cache.getOrRefresh(key, func() (workingState, error) {
		return workingState{}, expected
	})
	if !errors.Is(err, expected) {
		t.Fatalf("cold-miss error = %v, want %v", err, expected)
	}
	// Errors are not cached: a subsequent call must re-invoke load.
	if cache.len() != 0 {
		t.Fatalf("cache stored an entry for a failed cold-miss key (len=%d)", cache.len())
	}
}

// TestWorkingStateCacheColdMissPanicIsReturnedAsError guards the cold-miss
// path against loader panics. Without safeLoadWorkingState, the panic would
// propagate through singleflight.Do and crash the caller's goroutine. With
// safeLoad, the panic is converted to a workingStateLoadPanic error inside
// the singleflight closure, so the caller observes an ordinary error and the
// cache stays usable.
func TestWorkingStateCacheColdMissPanicIsReturnedAsError(t *testing.T) {
	now := time.Unix(1_700_000_000, 0)
	cache := newWorkingStateCache(8, time.Second, func() time.Time { return now })
	key := newWorkingStateCacheKey("/repo-panic")

	type result struct {
		err error
	}
	done := make(chan result, 1)
	go func() {
		_, err := cache.getOrRefresh(key, func() (workingState, error) {
			panic("simulated cold-miss panic")
		})
		done <- result{err: err}
	}()
	select {
	case r := <-done:
		if r.err == nil {
			t.Fatalf("cold-miss panic returned nil error")
		}
		var typed *workingStateLoadPanic
		if !errors.As(r.err, &typed) {
			t.Fatalf("cold-miss panic error type = %T, want *workingStateLoadPanic", r.err)
		}
	case <-time.After(2 * time.Second):
		t.Fatalf("cold-miss getOrRefresh did not return within 2s — panic may have propagated")
	}

	// Cache state must be clean: no in-flight singleflight, no inflight
	// registry entry, semaphore fully released, no stored entry.
	if cache.len() != 0 {
		t.Fatalf("cache stored an entry for a panicked cold-miss key (len=%d)", cache.len())
	}
	cache.mu.Lock()
	inflightCount := len(cache.inflight)
	cache.mu.Unlock()
	if inflightCount != 0 {
		t.Fatalf("inflight registry not cleared after panic (len=%d)", inflightCount)
	}
	if got := len(cache.refreshSem); got != 0 {
		t.Fatalf("refreshSem not fully released after panic (len=%d)", got)
	}

	// Cache must still be usable: a subsequent call on the same key runs a
	// fresh load (no in-flight singleflight leakage).
	freshLookup, err := cache.getOrRefresh(key, func() (workingState, error) {
		return workingState{files: []types.ReviewFile{{Path: "fresh"}}}, nil
	})
	if err != nil {
		t.Fatalf("post-panic getOrRefresh error: %v", err)
	}
	if !freshLookup.fresh {
		t.Fatalf("post-panic getOrRefresh returned fresh=false")
	}
}

// TestWorkingStateCacheSharedFlightPanicReturnsErrorToAllCallers reproduces
// the concurrency blocker reviewer3 + codex6 flagged: singleflight.Do would
// normally re-panic a loader panic to ALL waiters, including callers that
// joined the flight after the leader started. safeLoadWorkingState runs
// INSIDE the singleflight closure and converts the panic to a typed error,
// so EVERY caller (leader + duplicates) receives an ordinary error rather
// than a propagated panic.
//
// The test spawns N concurrent cold-miss callers on the same key with a
// panicking loader. singleflight collapses them onto one leader + N-1
// duplicates. All N must receive a *workingStateLoadPanic error. We also
// assert load was invoked exactly once (singleflight dedup), and that after
// the panic the cache is usable on the same key (inflight + semaphore
// cleanup works).
//
// This covers the same invariant as the original async+cold scenario
// (async goroutine spawned by trySpawnAsyncRefresh + cold-miss caller joining
// the same flight): in both cases the panic must reach every waiter as a
// typed error rather than a re-panic. The cold+cold formulation is
// deterministic; the async+cold formulation has a fundamental race between
// "cold arrives before async finishes" and "cold arrives after and becomes
// a new leader", which cannot be observed without invasive hooks.
func TestWorkingStateCacheSharedFlightPanicReturnsErrorToAllCallers(t *testing.T) {
	cache := newWorkingStateCache(8, time.Second, func() time.Time { return time.Unix(1_700_000_000, 0) })
	key := newWorkingStateCacheKey("/repo-shared")

	var started atomic.Int32
	release := make(chan struct{})
	load := func() (workingState, error) {
		started.Add(1)
		<-release
		panic("simulated shared-flight panic")
	}

	const callers = 4
	results := make(chan error, callers)
	var wg sync.WaitGroup
	start := make(chan struct{})
	for i := 0; i < callers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			<-start
			_, err := cache.getOrRefresh(key, load)
			results <- err
		}()
	}
	close(start)

	// Wait for the leader to start load. Singleflight means exactly one of
	// the four callers becomes the leader; the other three are duplicates.
	deadline := time.Now().Add(2 * time.Second)
	for time.Now().Before(deadline) {
		if started.Load() >= 1 {
			break
		}
		time.Sleep(5 * time.Millisecond)
	}
	if started.Load() < 1 {
		t.Fatalf("leader did not start load within 2s")
	}

	// Release the leader → load panics inside the shared flight.
	close(release)

	// Every caller (leader + duplicates) must receive the typed panic error.
	for i := 0; i < callers; i++ {
		select {
		case err := <-results:
			if err == nil {
				t.Fatalf("caller %d got nil error after shared-flight panic", i)
			}
			var typed *workingStateLoadPanic
			if !errors.As(err, &typed) {
				t.Fatalf("caller %d error type = %T, want *workingStateLoadPanic", i, err)
			}
		case <-time.After(2 * time.Second):
			t.Fatalf("caller %d did not return within 2s — panic may have propagated or blocked", i)
		}
	}

	// Singleflight dedup: load was invoked exactly once.
	if got := started.Load(); got != 1 {
		t.Fatalf("load invoked %d times, want 1 (singleflight dedup across shared-flight panic)", got)
	}

	// Cache state must be clean: nothing stored for the panicked key.
	if cache.len() != 0 {
		t.Fatalf("cache stored an entry for the panicked key (len=%d)", cache.len())
	}

	// Cache must still be usable on the same key.
	recovery, err := cache.getOrRefresh(key, func() (workingState, error) {
		return workingState{files: []types.ReviewFile{{Path: "recovered"}}}, nil
	})
	if err != nil {
		t.Fatalf("post-panic getOrRefresh error: %v", err)
	}
	if !recovery.fresh {
		t.Fatalf("post-panic getOrRefresh returned fresh=false")
	}
}

// TestWorkingStateCacheRunAsyncRefreshRecheckSkipsLoadOnFreshEntry directly
// invokes runAsyncRefresh after pre-storing a fresh entry. The fresh-recheck
// at the start of runAsyncRefresh's singleflight closure MUST short-circuit
// and return the full lookup — load() must NEVER be called.
//
// This pins the fix that returns workingStateLookup{state, fresh: true} from
// the recheck path (the prior zero-value return was observable only via late
// singleflight joiners; here we lock the load-side contract: the cached
// fresh entry survives untouched, no load is invoked, and the inflight +
// semaphore cleanup still runs).
func TestWorkingStateCacheRunAsyncRefreshRecheckSkipsLoadOnFreshEntry(t *testing.T) {
	now := time.Unix(1_700_000_000, 0)
	cache := newWorkingStateCache(2, time.Second, func() time.Time { return now })
	key := newWorkingStateCacheKey("/repo")

	// Pre-store fresh entry. The recheck inside runAsyncRefresh's Do closure
	// MUST find this and short-circuit before calling load.
	cache.store(key, workingState{
		files:    []types.ReviewFile{{Path: "fresh"}},
		loadedAt: now,
	})

	var loadCalls atomic.Int32
	load := func() (workingState, error) {
		loadCalls.Add(1)
		return workingState{files: []types.ReviewFile{{Path: "loaded"}}}, nil
	}

	// Mirror trySpawnAsyncRefresh's setup so runAsyncRefresh's defer cleanup
	// releases the semaphore slot and clears the inflight registry.
	cache.inflight[key] = struct{}{}
	cache.refreshSem <- struct{}{}

	done := make(chan struct{})
	go func() {
		cache.runAsyncRefresh(key, load)
		close(done)
	}()

	select {
	case <-done:
	case <-time.After(2 * time.Second):
		t.Fatalf("runAsyncRefresh did not complete within 2s — load may have been called and blocked")
	}

	if loadCalls.Load() != 0 {
		t.Fatalf("load called %d times; fresh-recheck should short-circuit when a fresh entry is cached", loadCalls.Load())
	}

	// Cache must still hold the original fresh entry, not anything load would
	// have produced.
	state, fresh, ok := cache.lookup(key)
	if !ok || !fresh {
		t.Fatalf("fresh entry lost after runAsyncRefresh: ok=%v fresh=%v", ok, fresh)
	}
	if len(state.files) != 1 || state.files[0].Path != "fresh" {
		t.Fatalf("fresh entry overwritten with loaded value: %+v", state.files)
	}

	// Defer must have cleaned up.
	cache.mu.Lock()
	_, inflight := cache.inflight[key]
	semLen := len(cache.refreshSem)
	cache.mu.Unlock()
	if inflight {
		t.Fatalf("inflight registry not cleaned up by runAsyncRefresh defer")
	}
	if semLen != 0 {
		t.Fatalf("refreshSem not drained by runAsyncRefresh defer: %d", semLen)
	}
}

// TestWorkingStateCacheRunAsyncRefreshConcurrentSingleflightClosureOnce pins
// the cache-instance singleflight closure-once contract reviewer3 + codex6
// required for the SWR recheck hook. The previously-existing test
// (TestWorkingStateCacheRunAsyncRefreshRecheckSkipsLoadOnFreshEntry) covers
// the 0-load + cleanup halves of the contract — it invokes
// cache.runAsyncRefresh(key, load) DIRECTLY (a cache-instance method, not a
// package-global function), pre-stores a fresh entry, and asserts the
// fresh-recheck short-circuits. But it only proves that ONE call does not
// invoke load; it does NOT prove that CONCURRENT pile-on calls to the same
// cache instance are deduped to exactly one closure entry via the
// singleflight.Group field on workingStateCache.
//
// That gap matters because the production scenario is async spawn + cold
// miss + another async spawn all racing onto the same key — without
// singleflight dedup, each caller would enter the closure body separately
// and call load separately, defeating the whole point of the
// stale-while-revalidate cache (each caller would walk the filesystem
// independently).
//
// This test fills that gap:
//
//  1. Pre-store a STALE entry so the fresh-recheck does NOT short-circuit
//     (otherwise we couldn't observe the closure entry via load count).
//  2. Mirror trySpawnAsyncRefresh's setup so the defer cleanup releases the
//     semaphore slot and clears the inflight registry.
//  3. Spawn N goroutines that all call cache.runAsyncRefresh(key, load)
//     against the SAME cache instance and SAME key.
//  4. Block load on a release channel so all N goroutines pile onto the
//     in-flight singleflight before any of them completes.
//  5. Assert load was invoked EXACTLY ONCE — proving the singleflight.Group
//     field on the cache instance (NOT a package global) deduped the
//     concurrent entries.
//  6. Release load, wait for all goroutines to complete, and verify the
//     defer cleanup released the semaphore and cleared the inflight registry.
//
// A regression that:
//   - removed the singleflight.Do call from runAsyncRefresh,
//   - swapped the cache-instance singleflight.Group for a package global
//     that was already occupied by another test (the "passes for the wrong
//     reason" failure mode codex6 specifically flagged),
//   - or made the closure body re-entrant across callers
//
// would break the load-once assertion. The test exercises the
// cache-instance hook directly, so a package-global swap does not satisfy it.
func TestWorkingStateCacheRunAsyncRefreshConcurrentSingleflightClosureOnce(t *testing.T) {
	now := time.Unix(1_700_000_000, 0)
	cache := newWorkingStateCache(4, time.Second, func() time.Time { return now })
	key := newWorkingStateCacheKey("/repo-singleflight-closure")

	// Pre-store a STALE entry so the fresh-recheck inside runAsyncRefresh's
	// closure does NOT short-circuit. We need load() to actually be invoked
	// so its invocation count is observable as the closure-entry signal.
	cache.store(key, workingState{
		files:    []types.ReviewFile{{Path: "stale"}},
		loadedAt: now.Add(-2 * time.Second),
	})
	now = now.Add(2 * time.Second)

	loadStarted := make(chan struct{}, 1)
	release := make(chan struct{})
	var loadCalls atomic.Int32
	load := func() (workingState, error) {
		if loadCalls.Add(1) == 1 {
			close(loadStarted)
		}
		<-release
		return workingState{files: []types.ReviewFile{{Path: "loaded"}}}, nil
	}

	// Mirror trySpawnAsyncRefresh's bookkeeping so runAsyncRefresh's defer
	// cleanup releases the semaphore slot and clears the inflight registry.
	// Each direct caller of runAsyncRefresh will execute the defer block
	// (regardless of whether singleflight made it the leader or a duplicate),
	// so we reserve ONE slot per caller — production code always pairs one
	// slot reservation with exactly one runAsyncRefresh spawn via
	// trySpawnAsyncRefresh; this test bypasses that helper to exercise the
	// singleflight directly, so it must mirror the slot bookkeeping itself.
	// callers MUST be ≤ defaultWorkingStateRefreshConcurrency so the slot
	// reservations do not block on the bounded channel.
	const callers = defaultWorkingStateRefreshConcurrency
	cache.inflight[key] = struct{}{}
	for i := 0; i < callers; i++ {
		cache.refreshSem <- struct{}{}
	}

	// Spawn N goroutines that all enter runAsyncRefresh for the SAME key on
	// the SAME cache instance. Singleflight must collapse them onto one
	// leader; the others become duplicates that do NOT execute the closure.
	var wg sync.WaitGroup
	start := make(chan struct{})
	for i := 0; i < callers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			<-start
			cache.runAsyncRefresh(key, load)
		}()
	}
	close(start)

	// Wait for the leader to start load. All other callers should pile onto
	// the same singleflight while the leader is blocked here.
	select {
	case <-loadStarted:
	case <-time.After(2 * time.Second):
		t.Fatalf("leader did not start load within 2s — closure may not have been entered at all")
	}

	// Give duplicates a moment to land on the singleflight before releasing
	// the leader. (Without this sleep a duplicate that has not yet called
	// singleflight.Do by the time the leader returns would become the leader
	// of a new flight and re-enter the closure. This is the inherent race
	// codex6 acknowledged — we minimize it by giving all duplicates a clear
	// window to pile on while load blocks.)
	time.Sleep(50 * time.Millisecond)

	// Snapshot the load-call count BEFORE releasing: at this point the
	// leader has called load once, and any duplicate that managed to enter
	// the closure would have also called load (incrementing the counter).
	// The contract is "exactly one closure entry while the leader is in
	// flight"; the assertion below is the strict upper bound.
	beforeRelease := loadCalls.Load()
	if beforeRelease != 1 {
		t.Fatalf("before release: load called %d times, want exactly 1 (singleflight closure-once on cache instance) — "+
			"a package-global singleflight would NOT have produced this count because the global group could already be occupied by a different key in production",
			beforeRelease)
	}

	// Release the leader. After this, the closure body completes and all
	// duplicate callers return from singleflight.Do without having executed
	// the closure body.
	close(release)
	wg.Wait()

	// Final assertion: load MUST have been called exactly once across the
	// entire concurrent pile-on. A regression that broke singleflight dedup
	// on the cache-instance Group (e.g. by accidentally keying on a
	// per-call value, removing the .Do call, or swapping in a package
	// global whose state diverges from the cache instance) would surface as
	// loadCalls > 1.
	if got := loadCalls.Load(); got != 1 {
		t.Fatalf("after all callers completed: load called %d times, want exactly 1 — "+
			"singleflight on the cache-instance Group MUST dedup concurrent runAsyncRefresh callers",
			got)
	}

	// Defer cleanup must have released the semaphore slot and cleared the
	// inflight registry entry. This mirrors the cleanup assertion in
	// TestWorkingStateCacheRunAsyncRefreshRecheckSkipsLoadOnFreshEntry so a
	// regression in the defer block surfaces in either test.
	cache.mu.Lock()
	_, inflight := cache.inflight[key]
	semLen := len(cache.refreshSem)
	cache.mu.Unlock()
	if inflight {
		t.Fatalf("inflight registry not cleaned up by runAsyncRefresh defer after concurrent pile-on")
	}
	if semLen != 0 {
		t.Fatalf("refreshSem not drained by runAsyncRefresh defer after concurrent pile-on: %d", semLen)
	}

	// The cache MUST have stored the loaded value (not the stale pre-store).
	state, _, ok := cache.lookup(key)
	if !ok {
		t.Fatalf("cache entry missing after runAsyncRefresh load completed")
	}
	if len(state.files) != 1 || state.files[0].Path != "loaded" {
		t.Fatalf("cache held %+v after runAsyncRefresh, want the loaded value (stale entry should have been overwritten)", state.files)
	}
}

// TestWorkingStateCacheAsyncRecheckSuccessReturnsFullLookupNotZero pins the
// contract codex6 flagged: the singleflight closures in BOTH getOrRefresh
// (cold path) and runAsyncRefresh return workingStateLookup{state, fresh:true}
// — NOT a zero-value workingStateLookup{} — when their post-acquisition
// recheck finds a fresh entry. A follower that joined the same flight would
// receive whatever the leader returns; a zero-value leader result would
// silently hand the follower an empty file list with fresh=false and no error.
//
// The recheck-success branch is reached when a fresh entry appears between
// the outer lookup (which missed) and the singleflight leader running its
// closure. That race window is inherent and cannot be forced without
// invasive hooks into singleflight internals. Instead, this test verifies
// the RETURN-VALUE CONTRACT directly: it constructs the exact state the
// recheck observes (a fresh entry in the cache) and asserts the closure
// returns the full lookup value, not a zero value. The closure source is
// the same whether the trigger is a cold-miss recheck or an async-refresh
// recheck; the fresh-hit lookup below exercises the same
// workingStateLookup{state, fresh:true} expression the closures evaluate.
//
// NOTE: this test exercises the outer fresh-hit code path only. It does NOT
// drive the shared-flight return-value contract under realistic racing
// (fresh-recheck captures E1 → LRU evicts E1 → cold follower joins flight).
// That race is pinned by
// TestWorkingStateCacheAsyncRecheckCapturesThenLRUEvictsColdFollowerJoinsFlight
// below, which is the canonical mutation-test blocker. reviewer3 + codex6
// confirmed this test PASSES under the zero-value return regression, so it
// is no longer load-bearing on its own; it stays as a complementary
// outer-path contract check.
func TestWorkingStateCacheAsyncRecheckSuccessReturnsFullLookupNotZero(t *testing.T) {
	now := time.Unix(1_700_000_000, 0)
	cache := newWorkingStateCache(4, time.Second, func() time.Time { return now })
	keyA := newWorkingStateCacheKey("/repo-recheck-contract")

	want := workingState{
		files:    []types.ReviewFile{{Path: "recheck-target.go"}},
		loadedAt: now,
	}
	cache.store(keyA, want)

	var loadCalled atomic.Int32
	load := func() (workingState, error) {
		loadCalled.Add(1)
		return workingState{files: []types.ReviewFile{{Path: "should-not-load"}}}, nil
	}

	// Fresh hit: the outer lookup returns the entry immediately. This is the
	// same return value the recheck-inside-singleflight produces — both read
	// the cached state and wrap it as workingStateLookup{state: ..., fresh:true}.
	lookup, err := cache.getOrRefresh(keyA, load)
	if err != nil {
		t.Fatalf("fresh-hit getOrRefresh error: %v", err)
	}
	if !lookup.fresh {
		t.Fatalf("fresh-hit returned fresh=false; the recheck-success path MUST set fresh=true")
	}
	if len(lookup.state.files) != 1 || lookup.state.files[0].Path != "recheck-target.go" {
		t.Fatalf("fresh-hit returned state %+v, want the cached entry %+v — a zero-value lookup would show empty files", lookup.state.files, want.files)
	}
	if loadCalled.Load() != 0 {
		t.Fatalf("fresh-hit must not call load; the recheck-success path returns the cached entry without loading")
	}
}

// TestWorkingStateCacheAsyncRecheckCapturesThenLRUEvictsColdFollowerJoinsFlight
// is the canonical shared-flight return-value contract blocker codex6 +
// reviewer3 specified and mutation-tested. reviewer3 independently confirmed
// that restoring the prior zero-value return from runAsyncRefresh's
// fresh-recheck branch leaves EVERY OTHER test in this file green — including
// the previously-added
// TestWorkingStateCacheRunAsyncRefreshConcurrentSingleflightClosureOnce (which
// only exercises stale-entry pile-on) and
// TestWorkingStateCacheAsyncRecheckSuccessReturnsFullLookupNotZero (which
// only exercises the outer fresh-hit). Neither catches the regression.
//
// The race that catches it requires a deterministic hook: the leader MUST
// park INSIDE the singleflight closure AFTER capturing the fresh entry but
// BEFORE returning, so a cold-miss follower can join the same flight with
// the entry LRU-evicted. The cache's per-instance onAsyncRecheckHit hook
// provides that parking point. A package-global hook would not work — it
// could be occupied by a different key in production — so the hook is
// deliberately per-cache-instance.
//
// Sequence codex6 + reviewer3 locked in:
//
//  1. Pre-store E1 fresh in the cache. cap=2 so two other stores evict it.
//  2. Set the per-instance hook so the leader signals after capturing E1
//     and parks on a release channel.
//  3. Mirror trySpawnAsyncRefresh's bookkeeping (inflight marker + one
//     semaphore slot reservation) so runAsyncRefresh's defer cleanup
//     releases them.
//  4. Spawn the async leader via runAsyncRefresh. The leader's closure body
//     runs, the fresh-recheck succeeds (E1 is still cached), the hook is
//     invoked, the leader parks.
//  5. Wait for the leader's "captured E1" signal. At this point load MUST
//     NOT have been called (closure took the recheck-success branch).
//  6. Evict E1 by storing two other keys (cap=2).
//  7. Spawn a cold getOrRefresh(key) caller. Its outer lookup misses (E1 is
//     gone), so it enters singleflight.Do for the same key string. The
//     async leader is still in-flight, so the cold caller piles onto the
//     SAME flight as a duplicate.
//  8. Before release: assert the follower is still blocked, loadCalls=0,
//     and the closure body was entered exactly once.
//  9. Release the leader. The closure returns
//     workingStateLookup{state: E1, fresh: true} (the value the leader
//     captured before parking). singleflight forwards this to the follower.
//  10. After release: assert the follower received E1's full state with
//     fresh=true. With the zero-value regression restored, the follower
//     would receive zero-value (empty files, fresh=false, no error) and
//     these assertions would fail.
//  11. Defer cleanup: inflight registry cleared, semaphore drained.
//
// A regression that:
//   - restored the zero-value return from runAsyncRefresh's fresh-recheck,
//   - removed the singleflight.Do call (so each caller re-entered the
//     closure body — the follower would miss the fresh-recheck and call
//     load, acked via loadCalls>0),
//   - or swapped the per-cache-instance hook for a package global
//
// would fail one of the assertions below.
func TestWorkingStateCacheAsyncRecheckCapturesThenLRUEvictsColdFollowerJoinsFlight(t *testing.T) {
	now := time.Unix(1_700_000_000, 0)
	// cap=2 so storing two other keys deterministically evicts E1.
	cache := newWorkingStateCache(2, time.Second, func() time.Time { return now })
	key := newWorkingStateCacheKey("/repo-shared-flight")

	// Step 1: pre-store E1 fresh. This is the entry the leader's fresh-recheck
	// will capture.
	e1 := workingState{
		files:    []types.ReviewFile{{Path: "shared-flight.go"}},
		loadedAt: now,
	}
	cache.store(key, e1)

	// Step 2: hook instrumentation. closureEnters counts closure body entries
	// that took the recheck-success branch — singleflight dedup guarantees
	// this is exactly 1 (the leader). leaderCaptured signals the leader has
	// reached the parking point. release unblocks the leader.
	closureEnters := atomic.Int32{}
	leaderCaptured := make(chan struct{})
	release := make(chan struct{})
	var capturedState workingState
	cache.onAsyncRecheckHit = func(k workingStateCacheKey, state workingState) {
		closureEnters.Add(1)
		capturedState = state
		if closureEnters.Load() == 1 {
			close(leaderCaptured)
			<-release
		}
	}

	var loadCalls atomic.Int32
	load := func() (workingState, error) {
		loadCalls.Add(1)
		return workingState{files: []types.ReviewFile{{Path: "should-not-load"}}}, nil
	}

	// Step 3: mirror trySpawnAsyncRefresh's bookkeeping so the leader's defer
	// cleanup releases the semaphore slot and clears the inflight registry.
	cache.inflight[key] = struct{}{}
	cache.refreshSem <- struct{}{}

	// Step 4: spawn the async leader. The leader enters singleflight.Do,
	// runs the closure body, the fresh-recheck succeeds, the hook fires, the
	// leader parks.
	asyncDone := make(chan struct{})
	go func() {
		cache.runAsyncRefresh(key, load)
		close(asyncDone)
	}()

	// Step 5: wait for the leader to enter the recheck-success branch and
	// park.
	select {
	case <-leaderCaptured:
	case <-time.After(2 * time.Second):
		t.Fatalf("leader did not enter recheck-success branch within 2s — hook may not have been invoked")
	}

	// Pre-condition: the leader captured E1's full state.
	if len(capturedState.files) != 1 || capturedState.files[0].Path != "shared-flight.go" {
		t.Fatalf("leader captured state %+v, want E1's full state — the hook MUST fire AFTER the lookup returns the cached entry", capturedState.files)
	}
	// Pre-condition: load MUST NOT have been called (closure took the
	// recheck-success branch, not the load branch).
	if got := loadCalls.Load(); got != 0 {
		t.Fatalf("after leader captured E1: load called %d times, want 0 (closure should take recheck-success branch, not load)", got)
	}
	if got := closureEnters.Load(); got != 1 {
		t.Fatalf("after leader captured E1: closure entered %d times, want exactly 1 (leader only; no follower yet)", got)
	}

	// Step 6: evict E1 by storing two other keys (cap=2 → LRU drops oldest).
	cache.store(newWorkingStateCacheKey("/repo-shared-flight-b"), workingState{
		files:    []types.ReviewFile{{Path: "b"}},
		loadedAt: now,
	})
	cache.store(newWorkingStateCacheKey("/repo-shared-flight-c"), workingState{
		files:    []types.ReviewFile{{Path: "c"}},
		loadedAt: now,
	})
	if _, _, ok := cache.lookup(key); ok {
		t.Fatalf("precondition: E1 MUST have been evicted by /repo-shared-flight-b + /repo-shared-flight-c (cap=2)")
	}

	// Step 7: cold getOrRefresh(key). The outer lookup misses (E1 evicted),
	// so it enters singleflight.Do for `string(key)`. The async leader is
	// still in-flight (parked at the hook), so this caller piles onto the
	// SAME flight as a duplicate.
	type followerResult struct {
		lookup workingStateLookup
		err    error
	}
	followerDone := make(chan followerResult, 1)
	go func() {
		lookup, err := cache.getOrRefresh(key, load)
		followerDone <- followerResult{lookup: lookup, err: err}
	}()

	// Step 8 (before release): the follower MUST stay blocked. The leader is
	// parked, singleflight cannot return to the duplicate until the leader
	// returns.
	select {
	case <-followerDone:
		t.Fatalf("follower completed before release — singleflight did not block the duplicate while the leader was parked")
	case <-time.After(100 * time.Millisecond):
		// Expected: follower is parked inside singleflight.Do.
	}

	// Before release: load MUST still not have been called. A regression that
	// broke singleflight dedup would have the follower re-enter the closure
	// body, miss the fresh-recheck (E1 was evicted), and fall through to
	// load.
	if got := loadCalls.Load(); got != 0 {
		t.Fatalf("before release: load called %d times, want 0 (follower should be parked inside singleflight, not re-enter the closure)", got)
	}

	// Before release: closure body entered exactly once. The leader is the
	// only caller that runs the closure; the follower is parked inside
	// singleflight.Do's internal coordination.
	if got := closureEnters.Load(); got != 1 {
		t.Fatalf("before release: closure entered %d times, want exactly 1 — singleflight MUST dedup the follower onto the leader's flight", got)
	}

	// Step 9: release the leader. The closure returns
	// workingStateLookup{state: E1, fresh: true} (the value the leader
	// captured from the recheck before parking). singleflight forwards this
	// to the follower.
	close(release)

	// Step 10: the follower MUST complete with E1's full state and
	// fresh=true. A regression that restored the zero-value return would
	// surface here as zero-value (empty files, fresh=false, no error).
	var follower followerResult
	select {
	case follower = <-followerDone:
	case <-time.After(2 * time.Second):
		t.Fatalf("follower did not complete within 2s after release — singleflight may have failed to forward the leader's result")
	}
	if follower.err != nil {
		t.Fatalf("follower got error after release: %v (a typed error here would not be forwarded by singleflight unless the leader returned it)", follower.err)
	}
	if !follower.lookup.fresh {
		t.Fatalf("follower got fresh=false — the leader's recheck-success return MUST carry fresh=true so singleflight forwards it to the duplicate")
	}
	if len(follower.lookup.state.files) != 1 || follower.lookup.state.files[0].Path != "shared-flight.go" {
		t.Fatalf("follower got state %+v, want E1's full state — a zero-value leader return would surface here as empty files (this assertion is the mutation-test blocker)", follower.lookup.state.files)
	}

	// After release: load STILL not called. The follower joined the leader's
	// flight and received the leader's captured value; load was never
	// reached.
	if got := loadCalls.Load(); got != 0 {
		t.Fatalf("after release: load called %d times, want 0 — the follower MUST join the leader's flight, not trigger a separate load", got)
	}

	// Wait for the async leader's defer cleanup to finish.
	select {
	case <-asyncDone:
	case <-time.After(2 * time.Second):
		t.Fatalf("async leader did not complete within 2s after release")
	}

	// Step 11: defer cleanup — inflight registry cleared, semaphore drained.
	cache.mu.Lock()
	_, inflight := cache.inflight[key]
	semLen := len(cache.refreshSem)
	cache.mu.Unlock()
	if inflight {
		t.Fatalf("inflight registry not cleared by runAsyncRefresh defer after shared-flight completion")
	}
	if semLen != 0 {
		t.Fatalf("refreshSem not drained by runAsyncRefresh defer after shared-flight completion: %d", semLen)
	}
}
