fix(index): the flicker window dies — atomic in-place vector update; lazy open honors every provider's not-ready report; the Path Registry twin table
DP6/DP8 of the Path Registry (BRAINY-PROD-LATENCY-TRIAD, the proven flicker mechanism): update paths staged RemoveFromVectorIndex then AddToVectorIndex as two separately-awaited transaction ops — between them a live row was in NEITHER index (dark to semantic recall, fine in metadata list). The native pair widened that window to seconds in production before their side's visibility-commit fix; the structural cure lands here: - hnswIndex.updateItem: absent → add; SAME vector → pure no-op (the production shape — a type-only update re-indexed an unchanged vector, remove+add did pure damage); changed vector → the node NEVER leaves the index: synchronous vector swap first (every query from that instant sees correct distances), then unlink/relink at the node's existing level via shared internals (linkNode/unlinkNodeEdges refactored out of add/remove; entry point and maxLevel provably unchanged). - ReplaceInVectorIndexOperation: ONE transaction leg; feature-detects provider updateItem (native seam flagged — their side ships updateItem, then the adjacent remove+add fallback is dead code). Both update staging sites swapped; delete sites untouched. - LAZY-OPEN GATE (fleet adoption find, SELF-ENGINE-PAIR-STANDARD): under disableAutoRebuild, ensureIndexesLoaded assessed ONLY the vector index — a not-ready native METADATA provider never blocked the completion latch and every find() silently returned [] on a populated store. All three providers now vote; any not-ready report falls through to the rebuild. - docs/path-registry.md: brainy's twin table for the 32 shared path IDs — service class, budgets, lifecycle, narration, and the cited pin per row; owed rows named (LC4 doors-open migration, MT4 yielding heals, LC7 downgrade contract) per the lifecycle-sprint choreography. Pins: update-item-atomic 9/9 (visibility-atomic swap, reverse-index parity vs fresh rebuild, entry-point invariants) · lazy-notready-honor 2/2. Gates: unit 1928/1928 (148 files) · integration 760 · conformance 27/27.
This commit is contained in:
parent
3236a01bef
commit
ebe06cdf33
7 changed files with 937 additions and 57 deletions
|
|
@ -97,6 +97,7 @@ import {
|
|||
SaveVerbOperation,
|
||||
AddToGraphIndexOperation,
|
||||
RemoveFromVectorIndexOperation,
|
||||
ReplaceInVectorIndexOperation,
|
||||
RemoveFromMetadataIndexOperation,
|
||||
RemoveFromGraphIndexOperation,
|
||||
UpdateNounMetadataOperation,
|
||||
|
|
@ -2949,11 +2950,16 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
level: 0
|
||||
})
|
||||
)
|
||||
// ONE atomic vector-index leg: the historical Remove→Add pair was
|
||||
// two separately-awaited operations — between them the row was in
|
||||
// NEITHER index (dark to semantic recall, visible to metadata
|
||||
// reads). ReplaceInVectorIndexOperation goes through the provider's
|
||||
// in-place updateItem when available (row never absent; an
|
||||
// element-wise UNCHANGED vector — the type-only-update shape that
|
||||
// flickered in production — is a pure no-op), else remove+add
|
||||
// adjacent within the single op.
|
||||
tx.addOperation(
|
||||
new RemoveFromVectorIndexOperation(this.index, params.id, existing.vector)
|
||||
)
|
||||
tx.addOperation(
|
||||
new AddToVectorIndexOperation(this.index, params.id, vector)
|
||||
new ReplaceInVectorIndexOperation(this.index, params.id, existing.vector, vector)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -9364,8 +9370,10 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
connections: new Map(),
|
||||
level: 0
|
||||
}),
|
||||
new RemoveFromVectorIndexOperation(this.index, params.id, existing.vector),
|
||||
new AddToVectorIndexOperation(this.index, params.id, vector)
|
||||
// ONE atomic vector-index leg — same law as update(): the row must
|
||||
// never be absent from vector search during an update (see
|
||||
// ReplaceInVectorIndexOperation).
|
||||
new ReplaceInVectorIndexOperation(this.index, params.id, existing.vector, vector)
|
||||
)
|
||||
}
|
||||
plan.operations.push(
|
||||
|
|
@ -14958,14 +14966,30 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
}
|
||||
|
||||
// If indexes already populated AND honestly serving, mark complete and skip.
|
||||
// Honest gate: when the provider exposes isReady(), that REPLACES the size()>0
|
||||
// Honest gate: when a provider exposes isReady(), that REPLACES the size()>0
|
||||
// proxy (a native index can report a non-zero size while its serving structure
|
||||
// is not loaded — the silent-empty cold-load class). A not-ready provider falls
|
||||
// through so the rebuild path can load it; verifyVectorLive() is the query-time
|
||||
// backstop either way. Providers without isReady() keep the size() heuristic
|
||||
// (the JS index's size()>0 genuinely means loaded).
|
||||
//
|
||||
// ALL THREE providers vote (fleet-adoption find, SELF-ENGINE-PAIR-STANDARD):
|
||||
// this gate used to assess ONLY the vector index, so a not-ready native
|
||||
// METADATA provider (its strand report) never blocked the completion latch
|
||||
// — under disableAutoRebuild the promised lazy first-query rebuild never
|
||||
// fired and every find() silently returned [] on a populated store. A
|
||||
// not-ready report from ANY provider now falls through to the rebuild.
|
||||
const vectorReadiness = assessIndexReadiness(this.index)
|
||||
if (vectorReadiness === 'ready' || (vectorReadiness === 'unknown' && this.index.size() > 0)) {
|
||||
const metadataReadiness = assessIndexReadiness(this.metadataIndex)
|
||||
const graphReadiness = assessIndexReadiness(this.graphIndex)
|
||||
const anyProviderNotReady =
|
||||
vectorReadiness === 'not-ready' ||
|
||||
metadataReadiness === 'not-ready' ||
|
||||
graphReadiness === 'not-ready'
|
||||
if (
|
||||
!anyProviderNotReady &&
|
||||
(vectorReadiness === 'ready' || (vectorReadiness === 'unknown' && this.index.size() > 0))
|
||||
) {
|
||||
this.lazyRebuildCompleted = true
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue