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:
parent
36d4e80ba2
commit
d0f69c731f
9 changed files with 654 additions and 9 deletions
38
src/utils/indexReadiness.ts
Normal file
38
src/utils/indexReadiness.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* @module indexReadiness
|
||||
* @description The single honest-readiness classifier shared by the vector,
|
||||
* graph and metadata index sites. It exists to kill "Pattern A" — the dishonest
|
||||
* readiness proxy where `size() > 0` / `isInitialized` is treated as "this index
|
||||
* actually serves queries." A cold native index that loaded its COUNT but not its
|
||||
* SERVING structure passes those proxies and silently returns `[]`.
|
||||
*
|
||||
* This classifier reads ONLY the provider's OPTIONAL, honest `isReady()` signal
|
||||
* (see {@link import('../plugin.js').VectorIndexProvider.isReady},
|
||||
* {@link import('../plugin.js').GraphIndexProvider.isReady},
|
||||
* {@link import('../plugin.js').MetadataIndexProvider.isReady}). It NEVER inspects
|
||||
* `size()` or `isInitialized`. When `isReady()` is absent, callers must fall back
|
||||
* to a KNOWN-ITEM PROBE (a real search/lookup that must return a known-present
|
||||
* datum) before trusting an empty result — never a `size()` proxy.
|
||||
*/
|
||||
|
||||
/** A provider that MAY expose the honest cold-load readiness signal. */
|
||||
export interface MaybeReadyProvider {
|
||||
isReady?: () => boolean
|
||||
}
|
||||
|
||||
/** Three-valued honest-readiness verdict. */
|
||||
export type IndexReadiness = 'ready' | 'not-ready' | 'unknown'
|
||||
|
||||
/**
|
||||
* @description Classify an index provider's honest readiness.
|
||||
* @param provider - Any index provider (vector / graph / metadata) or `null`.
|
||||
* @returns
|
||||
* - `'ready'` when `isReady() === true` (serving structure loaded — trust it);
|
||||
* - `'not-ready'` when `isReady() === false` (count/manifest loaded, NOT serving — rebuild);
|
||||
* - `'unknown'` when the provider exposes no `isReady()` (caller must probe / keep the JS heuristic).
|
||||
*/
|
||||
export function assessIndexReadiness(provider: unknown): IndexReadiness {
|
||||
const p = provider as MaybeReadyProvider | null | undefined
|
||||
if (p == null || typeof p.isReady !== 'function') return 'unknown'
|
||||
return p.isReady() ? 'ready' : 'not-ready'
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue