fix(8.0): restore() reloads a native entity-id mapper before graphIndex.rebuild()

A logical snapshot restore could silently lose graph edges on a brain backed by
a native provider: the graph adjacency keys on the shared id-mapper's interned
ints (sourceInt/targetInt), which are derived + never persisted, and restore()
never reloaded the mapper — so a native graph rebuild resolved verb endpoints
against a stale/empty mapper and dropped edges.

restore() now calls entityIdMapper.rebuild() — a new OPTIONAL method on the
EntityIdMapperProvider contract — BEFORE rebuilding the indexes, so a native
mapper reloads its int<->uuid from the restored binary KV first and the graph
resolves endpoints correctly. The JS mapper deliberately has NO rebuild() and
needs none: MetadataIndex.rebuild() re-derives it from the restored entities via
append-only getOrAssign, consistently with the bitmaps it builds — forcing a
reload there would blank a still-referenced mapping and break find() after a
same-instance restore.

Contract: graphIndex.rebuild() resolves sourceId/targetId -> ints through the
shared mapper itself (brainy does not re-feed resolved endpoints); brainy's only
job is to ensure the mapper is reloaded first.

Test: relationships survive a snapshot round-trip (db-mvcc.test.ts). 84
generation/temporal/visibility tests green; tsc clean.
This commit is contained in:
David Snelling 2026-06-23 12:02:17 -07:00
parent 3783e61b30
commit 4d0b64f455
4 changed files with 72 additions and 2 deletions

View file

@ -344,6 +344,17 @@ export class EntityIdMapper implements EntityIdMapperProvider {
await this.flush()
}
// No `rebuild()` on the JS mapper by design (CTX-BR-RESTORE-REBUILD): after a
// `brain.restore()`, `MetadataIndex.rebuild()` re-derives this mapper from the
// restored entities via append-only `getOrAssign` (the ints it picks are
// internally consistent with the bitmaps it builds), so the JS path needs no
// explicit post-restore reload — and forcing one (blanking + reloading) would
// drop a still-referenced mapping when the snapshot omits the mapper file. The
// `rebuild()` reload is the NATIVE mapper's concern: cor's binary KV holds the
// authoritative int↔uuid the native adjacency is keyed on, so it must reload
// from the restored KV before the native graph rebuild. `restore()` therefore
// calls `rebuild()` only when the provider implements it (see brainy.ts).
/**
* Get statistics about the mapper
*/