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:
David Snelling 2026-08-10 09:29:06 -07:00
parent 3484107462
commit 2d532684b4
7 changed files with 583 additions and 63 deletions

View file

@ -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()).