What's New
A long-running index batch could blow the ChromaDB worker's RPC deadline and latch vector search off permanently. Searches silently degraded to keyword-only behind an HTTP 200 and a healthy container, and stayed that way until someone restarted it. This release makes that state unreachable.
Fixed
- Permanent silent degradation. Replaced the breaker with a locked state machine (
CLOSED → OPEN → RECOVERING → CLOSED, plusSTOPPED) driven by a single permanent Event-based supervisor. A recovery epoch and worker generation are carried through every transition and health is published only by compare-and-set, so a stale probe cannot overwrite a newer failure. There is no reachable state where vector serving is off and nothing is scheduled to restore it. - A malformed query no longer degrades search for everyone. Worker errors are classified at the raise site; request/application errors (empty query text, bad collection) never open a backend-health breaker.
- Unbounded ready-wait.
_start_workerused a blockingreadline()whose deadline was only evaluated between reads, so a worker that never signalled ready blocked the supervisor indefinitely andREADY_TIMEOUThad no effect. - Recovery on the request path. The request path no longer starts or restarts a worker — only the supervisor owns process lifecycle — and query admission fails fast instead of waiting out a ~25s recovery probe.
- Health could be declared while a collection was unloaded. The recovery probe now asserts a per-collection sentinel instead of accepting "did not throw".
Added
- Embedding-model manifest. Collections are stamped with the model that produced their vectors and refused on mismatch. Two same-dimension indexes built by different models previously loaded without error and returned confidently-ranked noise.
scripts/stamp_manifest.pymigrates indexes created before this check existed. GET /health/vector— reports breakerstate,epoch,generation,consecutive_failures,next_probe_in_s,state_age_sandserved_collections, so a monitor can alert on vector death without the container healthcheck restarting mid-index. Alert onstate != CLOSED, and onstate_age_s > 300whileRECOVERING.- New environment variables, all with working defaults:
RPC_TIMEOUT_QUERY,RPC_TIMEOUT_INDEX,RPC_TIMEOUT_PING,VECTOR_QUERY_LOCK_TIMEOUT,VECTOR_LIVENESS_POLL_S,VECTOR_PROBE_REQUIRED_COLLECTIONS.
Verification
55 worker-protocol tests, up from 34. Fault-injected against a live container rather than only unit-tested:
SIGSTOPthe worker → search degrades tofts5with/health/vector503 → self-heals tohybrid/200 in ~20s,RestartCount=0- worker held stopped across repeated probes → backoff walks 120/300/900/1800 and still recovers once the fault clears
Upgrade Notes
No configuration change is required. Existing indexes carry no manifest and are served unverified with a warning; run scripts/stamp_manifest.py <persist_dir> <embedding_model> once (with the container stopped) to enable mismatch protection on them.