SIGN IN SIGN UP

fix: [ENG-2884] await in-flight build in refreshIndex (race fix on f5566acd)

Reviewer caught a real race in the refreshIndex() shipped in f5566acd.
The orphan-builder problem:

  T0: caller A invokes search() — acquireIndex starts buildA;
      state.buildingPromise = buildA. The IIFE body is still awaiting
      fileSystem.listDirectory and the eventual buildFreshIndex.
  T1: scanDreamCandidates calls refreshIndex(). Old code cleared
      state.cachedIndex AND state.buildingPromise immediately and
      returned.
  T2: caller C invokes search() — sees both fields cleared, kicks off
      a NEW build (buildB). state.buildingPromise = buildB.
  T3: buildA finally completes and writes state.cachedIndex =
      freshIndexA (line 1016 of acquireIndex).
  T4: buildB completes and writes state.cachedIndex = freshIndexB.

If buildA happens to settle AFTER buildB (e.g. slower disk read,
larger source set), the older freshIndexA wins — reintroducing the
exact TTL-stale-data scenario Fix #2 was meant to prevent. Realistic
on a daemon with concurrent brv search / brv query / dream-scan
tasks.

Fix per reviewer's accepted shape: refreshIndex() now AWAITS any
in-flight buildingPromise before clearing cached state. By the time
the await returns, the in-flight build has either published its
result (which is then cleared) or rejected (swallowed — callers don't
care about that build's outcome, they just want a clean slate). No
orphan publisher can write back after invalidation because the
publishing point is now in the past relative to the clear.

New test file search-knowledge-service-refresh.test.ts covers:
- the contract under test (refreshIndex MUST NOT resolve while a
  build is in flight)
- idempotent behavior on cold state (no in-flight build)
- graceful clear when the in-flight build rejects

Not a follow-up sub-agent regression replay: the race manifests only
under concurrent operations on a single SearchKnowledgeService
instance, which the 12-scenario test matrix (per-scenario daemons,
serial workflow) does not exercise. The unit test at the contract
boundary is the right pin.
N
Nguyễn Thuận Phát committed
8c7c1cc3e256ee53576a16c7044b801e6dceff39
Parent: 53004fd