fix(8.0): accept application-supplied entity ids, not just UUIDs

Sharding required a 32-hex UUID and threw "Invalid UUID format" on every
non-UUID id, even though add()'s own docs show `id: "user-12345"` and the u64
id-mapper happily assigns ints to any string. So custom ids mapped fine in
memory then threw on save — breaking documented usage and any 7.x consumer
using friendly ids (`'user-123'`, slugs, emails) on upgrade.

getShardId() (renamed from getShardIdFromUuid) now buckets UUID-format ids by
their first byte (on-disk layout unchanged, so existing data never moves) and
hashes any other id via FNV-1a into the same 256-bucket space. The hash is part
of the on-disk contract and must not change. Verbs stay Brainy-generated UUIDs
by contract; only custom noun ids take the hash path.

Adds getShardId unit coverage: dual scheme, determinism, even distribution
across buckets, and the empty-id guard.
This commit is contained in:
David Snelling 2026-06-16 10:40:40 -07:00
parent 5096f90fbc
commit 36b7216929
3 changed files with 111 additions and 37 deletions

View file

@ -25,7 +25,7 @@ import {
NOUN_TYPE_COUNT,
VERB_TYPE_COUNT
} from '../types/graphTypes.js'
import { getShardIdFromUuid } from './sharding.js'
import { getShardId } from './sharding.js'
import { BlobStorage, type BlobStoreAdapter } from './blobStorage.js'
import { unwrapBinaryData } from './binaryDataCodec.js'
import { prodLog } from '../utils/logger.js'
@ -161,7 +161,7 @@ function isSingletonSystemKey(key: string): boolean {
* No type parameter needed - direct O(1) lookup by ID
*/
function getNounVectorPath(id: string): string {
const shard = getShardIdFromUuid(id)
const shard = getShardId(id)
return `entities/nouns/${shard}/${id}/vectors.json`
}
@ -170,7 +170,7 @@ function getNounVectorPath(id: string): string {
* No type parameter needed - direct O(1) lookup by ID
*/
function getNounMetadataPath(id: string): string {
const shard = getShardIdFromUuid(id)
const shard = getShardId(id)
return `entities/nouns/${shard}/${id}/metadata.json`
}
@ -179,7 +179,7 @@ function getNounMetadataPath(id: string): string {
* No type parameter needed - direct O(1) lookup by ID
*/
function getVerbVectorPath(id: string): string {
const shard = getShardIdFromUuid(id)
const shard = getShardId(id)
return `entities/verbs/${shard}/${id}/vectors.json`
}
@ -188,7 +188,7 @@ function getVerbVectorPath(id: string): string {
* No type parameter needed - direct O(1) lookup by ID
*/
function getVerbMetadataPath(id: string): string {
const shard = getShardIdFromUuid(id)
const shard = getShardId(id)
return `entities/verbs/${shard}/${id}/metadata.json`
}
@ -392,7 +392,7 @@ export abstract class BaseStorage extends BaseStorageAdapter {
}
// Valid entity UUID - apply sharding
const shardId = getShardIdFromUuid(id)
const shardId = getShardId(id)
if (context === 'noun-metadata') {
return {