feat(plugin): every provider write surface carries the real committed generation
The provider contract (metadata addToIndex/removeFromIndex, vector addItem/removeItem, id-mapper getOrAssign/remove) gains an optional trailing generation — evaluated lazily at operation execute time (the graph surface's thunk pattern, generalized), threaded from all 17 construction sites: undefined during generation-0 bootstrap, the real committed generation everywhere else. Optional = additive: no existing provider or caller breaks; native delta logs that stamped literal zero start hearing truth. JS twins accept the parameter with parity notes. Pins: provider doubles capture and assert nonzero monotonic generations across add/update/remove on both surfaces.
This commit is contained in:
parent
3484107462
commit
2d532684b4
7 changed files with 583 additions and 63 deletions
|
|
@ -531,9 +531,24 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
* store has assigned the batch generation by then; for single-op writes it
|
||||
* reads the post-write watermark. The arrow body reads `generationStore`
|
||||
* lazily, so it is safe to define before `init()` assigns the store.
|
||||
* Metadata/vector index writes use the bootstrap-honest twin
|
||||
* {@link indexWriteGeneration} below.
|
||||
*/
|
||||
private readonly graphWriteGeneration = (): bigint =>
|
||||
BigInt(this.generationStore.generation())
|
||||
/**
|
||||
* The metadata/vector twin of {@link graphWriteGeneration}, honest about
|
||||
* bootstrap: while generation stamping is inactive (init-time
|
||||
* infrastructure writes, e.g. the VFS root, applied via
|
||||
* `runWithoutGeneration`) there IS no commit generation — this resolves to
|
||||
* `undefined` so a provider records "unstamped", never a fabricated 0.
|
||||
* The graph thunk keeps its non-optional `bigint` contract (no graph
|
||||
* writes occur during bootstrap).
|
||||
*/
|
||||
private readonly indexWriteGeneration = (): bigint | undefined =>
|
||||
this._generationStampingActive
|
||||
? BigInt(this.generationStore.generation())
|
||||
: undefined
|
||||
/** Lazily built host surface shared by every `Db` value of this brain. */
|
||||
private _dbHost?: DbHost<T>
|
||||
/**
|
||||
|
|
@ -1995,7 +2010,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
})
|
||||
)
|
||||
tx.addOperation(
|
||||
new ReplaceInVectorIndexOperation(this.index, id, oldVector, newVector)
|
||||
new ReplaceInVectorIndexOperation(this.index, id, oldVector, newVector, this.indexWriteGeneration)
|
||||
)
|
||||
})
|
||||
await this.clearPendingEmbed(id)
|
||||
|
|
@ -2479,13 +2494,13 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
// inserts the real vector.
|
||||
if (!deferringEmbed) {
|
||||
tx.addOperation(
|
||||
new AddToVectorIndexOperation(this.index, id, vector)
|
||||
new AddToVectorIndexOperation(this.index, id, vector, this.indexWriteGeneration)
|
||||
)
|
||||
}
|
||||
|
||||
// Operation 4: Add to metadata index
|
||||
tx.addOperation(
|
||||
new AddToMetadataIndexOperation(this.metadataIndex, id, entityForIndexing)
|
||||
new AddToMetadataIndexOperation(this.metadataIndex, id, entityForIndexing, this.indexWriteGeneration)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -3180,7 +3195,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
// flickered in production — is a pure no-op), else remove+add
|
||||
// adjacent within the single op.
|
||||
tx.addOperation(
|
||||
new ReplaceInVectorIndexOperation(this.index, params.id, existing.vector, vector)
|
||||
new ReplaceInVectorIndexOperation(this.index, params.id, existing.vector, vector, this.indexWriteGeneration)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -3210,10 +3225,10 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
metadata: existing.metadata // CRITICAL: keep as nested 'metadata' property!
|
||||
}
|
||||
tx.addOperation(
|
||||
new RemoveFromMetadataIndexOperation(this.metadataIndex, params.id, removalMetadata)
|
||||
new RemoveFromMetadataIndexOperation(this.metadataIndex, params.id, removalMetadata, this.indexWriteGeneration)
|
||||
)
|
||||
tx.addOperation(
|
||||
new AddToMetadataIndexOperation(this.metadataIndex, params.id, entityForIndexing)
|
||||
new AddToMetadataIndexOperation(this.metadataIndex, params.id, entityForIndexing, this.indexWriteGeneration)
|
||||
)
|
||||
}, casPrecommit, this._changeFeed.hasListeners
|
||||
? [
|
||||
|
|
@ -3298,14 +3313,14 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
// Operation 1: Remove from vector index
|
||||
if (noun) {
|
||||
tx.addOperation(
|
||||
new RemoveFromVectorIndexOperation(this.index, id, noun.vector)
|
||||
new RemoveFromVectorIndexOperation(this.index, id, noun.vector, this.indexWriteGeneration)
|
||||
)
|
||||
}
|
||||
|
||||
// Operation 2: Remove from metadata index
|
||||
if (metadata) {
|
||||
tx.addOperation(
|
||||
new RemoveFromMetadataIndexOperation(this.metadataIndex, id, metadata)
|
||||
new RemoveFromMetadataIndexOperation(this.metadataIndex, id, metadata, this.indexWriteGeneration)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -3409,8 +3424,14 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
verb: Pick<GraphVerb, 'sourceId' | 'targetId'> & { sourceInt?: bigint; targetInt?: bigint }
|
||||
): { sourceInt: bigint; targetInt: bigint } {
|
||||
const idMapper = this.metadataIndex.getIdMapper()
|
||||
const sourceInt = BigInt(idMapper.getOrAssign(verb.sourceId))
|
||||
const targetInt = BigInt(idMapper.getOrAssign(verb.targetId))
|
||||
// Thread the write generation into any mint: a native mapper stamps the
|
||||
// assignment record with the real watermark instead of a literal 0.
|
||||
// Evaluated HERE (mint time) — at execute time inside a batch this is the
|
||||
// in-flight commit generation; at plan time it is the pre-batch watermark
|
||||
// (truthful: the mint happened before the batch committed).
|
||||
const generation = this.indexWriteGeneration()
|
||||
const sourceInt = BigInt(idMapper.getOrAssign(verb.sourceId, generation))
|
||||
const targetInt = BigInt(idMapper.getOrAssign(verb.targetId, generation))
|
||||
verb.sourceInt = sourceInt
|
||||
verb.targetInt = targetInt
|
||||
return { sourceInt, targetInt }
|
||||
|
|
@ -7122,13 +7143,13 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
// Add delete operations to transaction
|
||||
if (noun) {
|
||||
tx.addOperation(
|
||||
new RemoveFromVectorIndexOperation(this.index, id, noun.vector)
|
||||
new RemoveFromVectorIndexOperation(this.index, id, noun.vector, this.indexWriteGeneration)
|
||||
)
|
||||
}
|
||||
|
||||
if (metadata) {
|
||||
tx.addOperation(
|
||||
new RemoveFromMetadataIndexOperation(this.metadataIndex, id, metadata)
|
||||
new RemoveFromMetadataIndexOperation(this.metadataIndex, id, metadata, this.indexWriteGeneration)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -9248,7 +9269,9 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
}
|
||||
// 'absent' / vectorless / wrong-dim → skip (not vector-rankable at this gen).
|
||||
if (Array.isArray(vec) && vec.length === dim) {
|
||||
ints.push(BigInt(idMapper.getInt(id) ?? idMapper.getOrAssign(id)))
|
||||
// Mint-now fallback stamps the CURRENT committed watermark (the mint
|
||||
// happens now, regardless of the historical G being materialized).
|
||||
ints.push(BigInt(idMapper.getInt(id) ?? idMapper.getOrAssign(id, this.indexWriteGeneration())))
|
||||
rows.push(vec)
|
||||
}
|
||||
}
|
||||
|
|
@ -9492,8 +9515,8 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
new SaveNounOperation(this.storage, { id, vector, connections: new Map(), level: 0 }, isNew),
|
||||
...(deferringEmbed
|
||||
? []
|
||||
: [new AddToVectorIndexOperation(this.index, id, vector)]),
|
||||
new AddToMetadataIndexOperation(this.metadataIndex, id, entityForIndexing)
|
||||
: [new AddToVectorIndexOperation(this.index, id, vector, this.indexWriteGeneration)]),
|
||||
new AddToMetadataIndexOperation(this.metadataIndex, id, entityForIndexing, this.indexWriteGeneration)
|
||||
)
|
||||
plan.touchedNouns.push(id)
|
||||
plan.postCommit.push(() => {
|
||||
|
|
@ -9670,12 +9693,12 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
// 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)
|
||||
new ReplaceInVectorIndexOperation(this.index, params.id, existing.vector, vector, this.indexWriteGeneration)
|
||||
)
|
||||
}
|
||||
plan.operations.push(
|
||||
new RemoveFromMetadataIndexOperation(this.metadataIndex, params.id, removalMetadata),
|
||||
new AddToMetadataIndexOperation(this.metadataIndex, params.id, entityForIndexing)
|
||||
new RemoveFromMetadataIndexOperation(this.metadataIndex, params.id, removalMetadata, this.indexWriteGeneration),
|
||||
new AddToMetadataIndexOperation(this.metadataIndex, params.id, entityForIndexing, this.indexWriteGeneration)
|
||||
)
|
||||
plan.touchedNouns.push(params.id)
|
||||
|
||||
|
|
@ -9755,10 +9778,10 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
}
|
||||
|
||||
if (noun) {
|
||||
plan.operations.push(new RemoveFromVectorIndexOperation(this.index, id, noun.vector))
|
||||
plan.operations.push(new RemoveFromVectorIndexOperation(this.index, id, noun.vector, this.indexWriteGeneration))
|
||||
}
|
||||
if (metadata) {
|
||||
plan.operations.push(new RemoveFromMetadataIndexOperation(this.metadataIndex, id, metadata))
|
||||
plan.operations.push(new RemoveFromMetadataIndexOperation(this.metadataIndex, id, metadata, this.indexWriteGeneration))
|
||||
}
|
||||
// Pre-read metadata rides along: the count decrement must not depend on
|
||||
// re-reading the record being removed (see remove()).
|
||||
|
|
|
|||
Reference in a new issue