SIGN IN SIGN UP

fix: audit-driven hardening — cache scope, pool checkout, atomic writes (v2.0.42)

After v2.0.41 shipped, ran a 4-axis audit (file-write races, global locks,
ambiguous null returns, missing dashboard UX). Three P0/P1 fixes worth
shipping immediately:

1. cache.js cacheKey hashed only the request body. Two callers sending
   identical "hi" could read each other's cached response (cross-tenant
   leak). Now scopes by callerKey: hash(callerKey + '\0' + body).
   chat.js threads callerKey through.

2. conversation-pool.js checkout deleted the entry BEFORE validating
   callerKey/expected. A fingerprint collision from a different caller
   would discard the rightful owner's cached cascade. Now validates
   first; only deletes on actual hit (and on expiry, where deletion
   is correct).

3. runtime-config / proxy.json / model-access.json / stats.json all
   used bare writeFileSync(target, JSON.stringify(...)). A SIGTERM
   mid-write truncated the file; next start logged a warning, fell
   back to defaults — silent settings loss. Factor src/fs-atomic.js
   writeJsonAtomic helper (tmp + renameSync, unlink-on-failure)
   matching the pattern accounts.json already used. Wire into all 4
   call sites.

14 new regression tests; 457/457 pass (was 443).

Backlog items found by the audit but deferred: dashboard backup/restore,
runtime settings UI, first-run wizard, persistent log download, model
catalog refresh, per-account discoveredFreeModels, proxyKey+auth,
structured no-account reasons. See release notes for full list.
D
dwgx committed
99ccc850dbef1ceb9fb6bffdc92cec59daa01912
Parent: a252654