fix: honest index readiness — no silently-empty queries on a cold index

Closes the "dishonest readiness proxy" anti-pattern (Pattern A): size()>0 /
isInitialized were treated as "this index serves queries", but a cold native
index can load its COUNT before its SERVING structure, so a query returned a
silent [] indistinguishable from "no such data". A shared assessIndexReadiness()
now reads only the provider's honest isReady() signal (never size()), applied at
every site:

- Vector: a one-shot verifyVectorLive() guard on the semantic/proximity search
  path (a pure semantic find({query}) has no filter, so nothing guarded it). It
  prefers isReady(), else a known-vector self-match probe; self-heals via rebuild
  or throws the new VectorIndexNotReadyError instead of a silent [].
- Graph: getVerbsBySource/ByTarget skip the fast path when the provider reports
  not-ready (falling to the canonical shard scan), plus a one-shot probe that
  self-heals a no-isReady provider whose adjacency did not cold-load.
- getIndexStatus(): folds in per-index honest `ready` (making `populated`
  honest) + rebuildFailed/rebuildError/degradedIds, so a readiness probe never
  200s a brain that is still warming up or degraded.

Unblocked by the native providers now reporting serving-truth (graph via
SSTable-residency readiness, vector via durableBaseLoadFailed). New export:
VectorIndexNotReadyError. getIndexStatus gains additive fields. No breaking API.
13 new tests; existing readiness guards green.
This commit is contained in:
David Snelling 2026-07-13 13:08:52 -07:00
parent 36d4e80ba2
commit d0f69c731f
9 changed files with 654 additions and 9 deletions

View file

@ -10,6 +10,39 @@ Full auto-generated changelog: `CHANGELOG.md` · Releases: https://github.com/so
---
## v8.2.8 — 2026-07-13 (honest index readiness — no more silently-empty queries on a cold index)
Closes the last of the three spine anti-patterns: the "dishonest readiness proxy," where `size() > 0`
was treated as "this index actually serves queries." On a cold open (fresh boot, restart, crash
recovery) a native index can load its **count** before its **serving structure** — so for a brief
window it has data but cannot answer, and a query returned a silent empty result indistinguishable
from "no such data." Every fix here asks the index whether it can *actually serve*, and if not,
self-heals or fails loudly instead of returning `[]`.
- **Semantic search no longer returns a silent `[]` on a cold vector index.** A pure semantic
`find({ query })` has no filter, so nothing previously guarded the vector index. A new one-shot
guard verifies the vector index serves a known persisted vector on the first semantic/proximity
search — preferring the provider's honest `isReady()` signal, else a known-vector self-match probe.
It rebuilds from canonical records if the serving structure did not load, and throws the new
**`VectorIndexNotReadyError`** only if a rebuild still cannot serve — never a silent empty result.
- **Relationship reads fall back to the canonical scan instead of an empty result on a cold graph
index.** `getVerbsBySource`/`getVerbsByTarget` (used by relationship queries and virtual-filesystem
traversal) skipped the fast path only on `isInitialized` — which reads true once the manifest loaded
even if the source→target adjacency did not. They now consult the honest readiness signal and, when
the adjacency is not serving, take the correct-but-slower canonical shard scan. A one-shot self-heal
probe covers providers that expose no readiness signal.
- **`getIndexStatus()` tells the truth for readiness probes.** It reported `populated: size>0` only, so
a Kubernetes readiness check could route traffic to a brain still warming up. It now folds in the
honest per-index `ready` signal (making `populated` honest), plus the degraded states already
surfaced by `checkHealth()`/`validateIndexConsistency()` (`rebuildFailed`/`rebuildError` and a
`degradedIds` count) — so a probe never reports 200-ready over a known-degraded index.
This completes the write/index-spine hardening end to end. New export: **`VectorIndexNotReadyError`**;
`getIndexStatus()` gains additive fields (`rebuildFailed`, `rebuildError?`, `degradedIds`, per-index
`ready?`). No breaking API change. Each fix ships with a dedicated regression test.
## v8.2.7 — 2026-07-13 (loadBinaryBlob fault-propagation — the lockstep completion of 8.2.6)
Completes the Pass-1 spine hardening. `loadBinaryBlob` (the raw-blob read a native accelerator mmaps