feat(8.0): EntityIdMapper U32 ceiling + EntityIdSpaceExceeded error
The JS fallback EntityIdMapper caps at u32::MAX to match the metadata
index's Roaring32 bitmap width. Before this guard, a brain past 4.29 B
entities would silently widen `nextId` into JS's safe-integer range,
truncating ints inside the bitmaps and zeroing query results — the
same silent under-count cortex 3.0's Piece 10 just closed on the
native path. Brainy 8.0 surfaces the overflow loudly instead.
When `nextId` would exceed `U32_ENTITY_ID_MAX`, `getOrAssign` throws
`EntityIdSpaceExceeded` with a message pointing at the cortex 3.0
binary mapper's `idSpace: 'u64'` mode as the migration path (mmap-
backed extendible-hash KV, persists the full u64 range losslessly).
The existing-UUID lookup path bypasses the guard — only fresh
allocations can overflow.
Surfaces via `@soulcraft/brainy/internals`:
- `EntityIdMapper` (already exported, behavior gated)
- `EntityIdSpaceExceeded` (new)
- `U32_ENTITY_ID_MAX` (new constant, = 0xFFFF_FFFF)
This is the lockstep half of cortex 3.0 / Piece 10 / Step 15. Cortex's
`NativeBinaryEntityIdMapperWrapper` ships the U64 binary mapper that
takes over above this ceiling; brainy ships the bright-line failure
that points consumers at it.
New test: tests/unit/utils/entity-id-mapper-u32-ceiling.test.ts (6
assertions covering the constant, the error shape, normal
allocations, the boundary case at exactly u32::MAX, the overflow
throw, and the existing-uuid lookup bypass).
2026-06-02 10:20:01 -07:00
/ * *
* @description Brainy 8.0 IdSpace contract : the JS fallback
* ` EntityIdMapper ` caps at ` u32::MAX ` to match the metadata index ' s
* Roaring32 bitmap width . When ` nextId ` would exceed the ceiling ,
* ` getOrAssign ` throws ` EntityIdSpaceExceeded ` with a message pointing
* at the cortex 3.0 binary mapper 's `idSpace: ' u64 ' ` mode as the
* migration path .
*
* This is the lockstep counterpart to cortex 3.0 ' s Piece 10 / Step 15
* ( TS wrapper + napi BigInt siblings + Roaring32 - or - Treemap
* ` PostingList ` enum ) . Without this guard , a JS - only brainy install
* past 4.29 B entities would silently truncate entity ids into the
* low - 32 - bit range , zeroing query results and corrupting any
* persisted int - keyed structure that consumed the mapper .
* /
import { describe , it , expect } from 'vitest'
import {
EntityIdMapper ,
EntityIdSpaceExceeded ,
U32_ENTITY_ID_MAX ,
} from '../../../src/utils/entityIdMapper.js'
/ * *
* Construct a fresh in - memory mapper without going through brainy ' s
* full storage adapter — we only exercise the in - RAM ceiling guard
* here , so a minimal ` getMetadata ` - returning shim is enough .
* /
function makeMapper ( ) : EntityIdMapper {
const storage = {
getMetadata : async ( ) = > null ,
setMetadata : async ( ) = > { } ,
getNouns : async ( ) = > ( { items : [ ] , totalCount : 0 } ) ,
} as any
return new EntityIdMapper ( { storage } )
}
describe ( 'EntityIdMapper U32 IdSpace ceiling (Brainy 8.0)' , ( ) = > {
it ( 'U32_ENTITY_ID_MAX equals 0xFFFF_FFFF (u32 ceiling)' , ( ) = > {
expect ( U32_ENTITY_ID_MAX ) . toBe ( 0xffff _ffff )
} )
it ( 'EntityIdSpaceExceeded is an Error subclass with attempted + ceiling' , ( ) = > {
const err = new EntityIdSpaceExceeded ( 0xffff _ffff + 1 )
expect ( err ) . toBeInstanceOf ( Error )
expect ( err . name ) . toBe ( 'EntityIdSpaceExceeded' )
expect ( err . ceiling ) . toBe ( 0xffff _ffff )
expect ( err . attempted ) . toBe ( 0x1 _0000_0000 )
expect ( err . message ) . toMatch ( /u32::MAX/ )
feat(8.0): id-normalization (#18) + aggregation min/max delete-safety + RC-safe release
- id-normalization (#18): `brain.newId()` (UUID v7) + v7 default ids; non-UUID string ids are
transparently normalized to a stable UUID v5 on creation AND every lookup — get/update/remove,
relate(from,to), related, find({connected}), getMany, removeMany, the transact op-handler, and
db.with() overlays — with the caller's original key preserved under `_originalId`. The engine only
ever sees a UUID; real UUIDs pass through untouched. universal/uuid.ts gains v5/v7/isUUID +
BRAINY_ID_NAMESPACE; new utils/idNormalization.ts (coerceNewEntityId / resolveEntityId).
- aggregation min/max delete-safety: `queryAggregate` no longer leaves a stale min/max after
deleting the current extreme — min/max now track a value multiset and recompute in-memory (no
entity scan; that scan was the prior delete-then-hang a consumer reported). Other metrics were
already exact across deletes. Regression test proves resolve-after-delete + correct new min/max.
- release.sh RC-safe: explicit version arg, prerelease detection (npm `--tag rc` + GitHub
`--prerelease`), full `test:ci` gate (unit + integration), current-branch push, npm-access
verification after publish.
- native-engine references point at `@soulcraft/cor` (8.0's partner) in user-facing strings/docs.
Unit 1461/0 · integration 599/0 · tsc clean.
2026-06-20 14:40:57 -07:00
expect ( err . message ) . toMatch ( /@soulcraft\/cor/ )
feat(8.0): EntityIdMapper U32 ceiling + EntityIdSpaceExceeded error
The JS fallback EntityIdMapper caps at u32::MAX to match the metadata
index's Roaring32 bitmap width. Before this guard, a brain past 4.29 B
entities would silently widen `nextId` into JS's safe-integer range,
truncating ints inside the bitmaps and zeroing query results — the
same silent under-count cortex 3.0's Piece 10 just closed on the
native path. Brainy 8.0 surfaces the overflow loudly instead.
When `nextId` would exceed `U32_ENTITY_ID_MAX`, `getOrAssign` throws
`EntityIdSpaceExceeded` with a message pointing at the cortex 3.0
binary mapper's `idSpace: 'u64'` mode as the migration path (mmap-
backed extendible-hash KV, persists the full u64 range losslessly).
The existing-UUID lookup path bypasses the guard — only fresh
allocations can overflow.
Surfaces via `@soulcraft/brainy/internals`:
- `EntityIdMapper` (already exported, behavior gated)
- `EntityIdSpaceExceeded` (new)
- `U32_ENTITY_ID_MAX` (new constant, = 0xFFFF_FFFF)
This is the lockstep half of cortex 3.0 / Piece 10 / Step 15. Cortex's
`NativeBinaryEntityIdMapperWrapper` ships the U64 binary mapper that
takes over above this ceiling; brainy ships the bright-line failure
that points consumers at it.
New test: tests/unit/utils/entity-id-mapper-u32-ceiling.test.ts (6
assertions covering the constant, the error shape, normal
allocations, the boundary case at exactly u32::MAX, the overflow
throw, and the existing-uuid lookup bypass).
2026-06-02 10:20:01 -07:00
expect ( err . message ) . toMatch ( /idSpace: 'u64'/ )
} )
it ( 'normal allocations under the ceiling succeed' , async ( ) = > {
const m = makeMapper ( )
await m . init ( )
const a = m . getOrAssign ( 'uuid-a' )
const b = m . getOrAssign ( 'uuid-b' )
expect ( a ) . toBe ( 1 )
expect ( b ) . toBe ( 2 )
expect ( a ) . not . toBe ( b )
} )
it ( 'throws EntityIdSpaceExceeded when nextId would exceed u32::MAX' , async ( ) = > {
const m = makeMapper ( )
await m . init ( )
// Reach into the mapper to bump `nextId` past the ceiling without
// actually allocating 4.29 B entities at test time. This mirrors
// the runtime invariant the guard protects.
; ( m as any ) . nextId = U32_ENTITY_ID_MAX + 1
expect ( ( ) = > m . getOrAssign ( 'uuid-overflow' ) ) . toThrow ( EntityIdSpaceExceeded )
} )
it ( 'the last representable u32 int IS allocatable (boundary case)' , async ( ) = > {
const m = makeMapper ( )
await m . init ( )
; ( m as any ) . nextId = U32_ENTITY_ID_MAX
// `nextId === U32_ENTITY_ID_MAX` is still within range — the
// guard checks `> U32_ENTITY_ID_MAX`, so this last allocation
// succeeds.
const id = m . getOrAssign ( 'uuid-at-ceiling' )
expect ( id ) . toBe ( U32_ENTITY_ID_MAX )
// The NEXT allocation overflows.
expect ( ( ) = > m . getOrAssign ( 'uuid-after-ceiling' ) ) . toThrow (
EntityIdSpaceExceeded ,
)
} )
it ( 'existing uuid lookup never overflows even when nextId is past ceiling' , async ( ) = > {
const m = makeMapper ( )
await m . init ( )
const assigned = m . getOrAssign ( 'uuid-existing' )
; ( m as any ) . nextId = U32_ENTITY_ID_MAX + 1
// Looking up an already-assigned UUID does NOT allocate; the
// guard is on the assign path only.
expect ( m . getOrAssign ( 'uuid-existing' ) ) . toBe ( assigned )
} )
} )
feat(8.0): GraphAccelerationProvider contract — the native graph-engine seam
Defines the optional native graph-acceleration provider (cor 3.0) that
brainy feature-detects and routes brain.graph.* / related({node}) /
find({connected}) to, falling back to pure-TS adjacency when absent. This is
the published seam cor wires its native build against (mirrors the existing
VersionedIndexProvider pattern: defined in plugin.ts, exported from index.ts,
implemented externally, feature-detected at runtime).
Contract (locked cross-team in the design thread):
- GraphAccelerationProvider: traverse, edgesForNode, graphCursorOpen/Next/Close,
pageRank, connectedComponents, shortestPath, neighborhoodSample, topByDegree.
Every read op is generation-aware (optional trailing generation?: bigint) so
db.asOf(g).graph.* resolves historically; omitted = now.
- Subgraph: columnar wire format (BigInt64Array node/edge columns, Uint16Array
edgeTypes, optional Uint8Array nodeDepth, truncated flag) — parallel typed
arrays, never array-of-objects; brainy maps u64<->UUID lazily for rendered rows.
- OpaqueIdSet: a find() universe forwarded opaquely into traverse/search for the
zero-crossing query->expand fusion (brainy never inspects it; the provider
version-tags + validates the envelope).
- VectorIndexProvider.search gains allowedIds?: OpaqueIdSet | ReadonlySet<string>
(predicate pushdown — recovers filtered recall lost to post-filtering).
- EntityIdMapperProvider gains entityIntsToUuids(BigInt64Array) — the bigint batch
reverse resolver for Subgraph.nodes; implemented on the JS mapper (TS fallback).
All new public types exported from index.ts. Test: entityIntsToUuids round-trip
+ order-preservation + empty-int sentinel.
2026-06-21 08:12:22 -07:00
describe ( 'EntityIdMapper.entityIntsToUuids (bigint batch resolver — graph engine)' , ( ) = > {
it ( 'reverse-resolves a BigInt64Array of assigned ints to UUIDs, in order' , async ( ) = > {
const m = makeMapper ( )
await m . init ( )
const a = m . getOrAssign ( 'uuid-a' )
const b = m . getOrAssign ( 'uuid-b' )
const c = m . getOrAssign ( 'uuid-c' )
// Out-of-assignment order, to prove it's positional (not sorted).
const ints = BigInt64Array . from ( [ BigInt ( c ) , BigInt ( a ) , BigInt ( b ) ] )
expect ( m . entityIntsToUuids ( ints ) ) . toEqual ( [ 'uuid-c' , 'uuid-a' , 'uuid-b' ] )
} )
it ( 'yields "" for a never-assigned int (order-preserving, no drop)' , async ( ) = > {
const m = makeMapper ( )
await m . init ( )
const a = m . getOrAssign ( 'uuid-a' )
const ints = BigInt64Array . from ( [ BigInt ( a ) , 999999 n ] )
// Position is preserved — the unknown int does not collapse the array.
expect ( m . entityIntsToUuids ( ints ) ) . toEqual ( [ 'uuid-a' , '' ] )
} )
it ( 'returns an empty array for empty input' , async ( ) = > {
const m = makeMapper ( )
await m . init ( )
expect ( m . entityIntsToUuids ( new BigInt64Array ( 0 ) ) ) . toEqual ( [ ] )
} )
} )