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
|
|
@ -164,8 +164,15 @@ export class EntityIdMapper implements EntityIdMapperProvider {
|
|||
* would exceed that, throws {@link EntityIdSpaceExceeded} so the caller
|
||||
* loudly migrates to cor's binary mapper with `idSpace: 'u64'`
|
||||
* rather than silently truncating entity ids.
|
||||
*
|
||||
* @param generation - Brainy's commit generation current at mint time
|
||||
* (contract parity with the `EntityIdMapperProvider` surface). This JS
|
||||
* mapper keeps a snapshot file, not a per-record delta log, so there is
|
||||
* no natural slot to store it — accepted and ignored; a native mapper
|
||||
* stamps its assignment records with it.
|
||||
*/
|
||||
getOrAssign(uuid: string): number {
|
||||
getOrAssign(uuid: string, generation?: bigint): number {
|
||||
void generation // Contract parity — no per-record log in the JS mapper.
|
||||
const existing = this.uuidToInt.get(uuid)
|
||||
if (existing !== undefined) {
|
||||
return existing
|
||||
|
|
@ -226,8 +233,14 @@ export class EntityIdMapper implements EntityIdMapperProvider {
|
|||
|
||||
/**
|
||||
* Remove mapping for UUID
|
||||
*
|
||||
* @param generation - Brainy's commit generation for this removal (contract
|
||||
* parity with the `EntityIdMapperProvider` surface). Accepted and ignored —
|
||||
* this JS mapper removes immediately; a native mapper tombstones the
|
||||
* mapping at this generation in its version chain.
|
||||
*/
|
||||
remove(uuid: string): boolean {
|
||||
remove(uuid: string, generation?: bigint): boolean {
|
||||
void generation // Contract parity — no per-key version chain in the JS mapper.
|
||||
const intId = this.uuidToInt.get(uuid)
|
||||
if (intId === undefined) {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -1459,8 +1459,16 @@ export class MetadataIndexManager implements MetadataIndexProvider {
|
|||
* @param id - Entity ID
|
||||
* @param entityOrMetadata - Either full entity structure or plain metadata (backward compat)
|
||||
* @param skipFlush - Skip automatic flush (used during batch operations)
|
||||
* @param deferWrites - Batch mode: buffer postings for a later flush
|
||||
* @param generation - Brainy's commit generation for this write (see the
|
||||
* {@link import('../plugin.js').MetadataIndexProvider} contract). This JS
|
||||
* manager keeps a single live view with no per-record delta log, so it
|
||||
* has no slot to store it — the value is accepted for contract parity
|
||||
* and forwarded to the shared id mapper (an injected native mapper
|
||||
* stamps its assignment records with it; the JS mapper ignores it).
|
||||
* The JS twin adopts full per-write stamping with the watermark train.
|
||||
*/
|
||||
async addToIndex(id: string, entityOrMetadata: any, skipFlush: boolean = false, deferWrites: boolean = false): Promise<void> {
|
||||
async addToIndex(id: string, entityOrMetadata: any, skipFlush: boolean = false, deferWrites: boolean = false, generation?: bigint): Promise<void> {
|
||||
const fields = this.extractIndexableFields(entityOrMetadata)
|
||||
|
||||
// Sanity check for excessive indexed fields (indicates possible data issue)
|
||||
|
|
@ -1508,7 +1516,10 @@ export class MetadataIndexManager implements MetadataIndexProvider {
|
|||
// element, so a scalar overwrite (last-value-wins) would index only the final
|
||||
// element and `contains` would miss the rest.
|
||||
if (this.columnStore) {
|
||||
const entityIntId = this.idMapper.getOrAssign(id)
|
||||
// Thread the commit generation into the mint: an injected native mapper
|
||||
// stamps the assignment record's delta log with the real watermark
|
||||
// instead of a literal 0 (the JS mapper accepts and ignores it).
|
||||
const entityIntId = this.idMapper.getOrAssign(id, generation)
|
||||
const fieldsMap: Record<string, unknown> = {}
|
||||
for (const { field, value } of fields) {
|
||||
if (field === '__words__') {
|
||||
|
|
@ -1600,8 +1611,13 @@ export class MetadataIndexManager implements MetadataIndexProvider {
|
|||
*
|
||||
* @param id - Entity ID to remove
|
||||
* @param metadata - Optional entity or metadata structure (if not provided, requires scanning all fields - slow!)
|
||||
* @param generation - Brainy's commit generation for this removal (see the
|
||||
* {@link import('../plugin.js').MetadataIndexProvider} contract). Accepted
|
||||
* for contract parity — this JS manager removes immediately (no tombstone
|
||||
* chain) and forwards it to the shared id mapper's `remove`, where an
|
||||
* injected native mapper tombstones the mapping at this generation.
|
||||
*/
|
||||
async removeFromIndex(id: string, metadata?: any): Promise<void> {
|
||||
async removeFromIndex(id: string, metadata?: any, generation?: bigint): Promise<void> {
|
||||
if (metadata) {
|
||||
const fields = this.extractIndexableFields(metadata)
|
||||
|
||||
|
|
@ -1625,7 +1641,9 @@ export class MetadataIndexManager implements MetadataIndexProvider {
|
|||
// Clean up ID mapper — must happen AFTER column store removal since it uses
|
||||
// idMapper.getInt(id). Prevents deleted IDs from persisting in the mapper
|
||||
// universe, which would cause ne/exists:false queries to return deleted entities.
|
||||
this.idMapper.remove(id)
|
||||
// The generation rides along so a native mapper tombstones the mapping at
|
||||
// the real commit watermark (the JS mapper ignores it).
|
||||
this.idMapper.remove(id, generation)
|
||||
await this.idMapper.flush()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue