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.
This commit is contained in:
David Snelling 2026-06-20 14:40:57 -07:00
parent 606445cd61
commit d02e522a3e
14 changed files with 943 additions and 100 deletions

View file

@ -9,6 +9,10 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'
import { Brainy } from '../../src/brainy'
import { NounType, VerbType } from '../../src/types/graphTypes'
// 8.0 id normalization: the test factory seeds natural-key ids ('alice', …).
// The engine maps each to a stable UUID (v5) and preserves the original under
// _originalId, so entity.id is the canonical UUID and lookups round-trip by key.
import { v5 } from '../../src/universal/uuid'
import {
createTestEntity,
createTestRelation,
@ -110,8 +114,9 @@ describe('Unified Find() Integration Tests', () => {
})
expect(results).toHaveLength(3)
// First result should be Alice herself with high similarity
expect(results[0].entity.id).toBe('alice')
// First result should be Alice herself with high similarity (her id is
// the canonical v5 mapping of the natural key 'alice').
expect(results[0].entity.id).toBe(v5('alice'))
expect(results[0].score).toBeGreaterThan(0.95)
})
@ -138,8 +143,8 @@ describe('Unified Find() Integration Tests', () => {
expect(results.length).toBeGreaterThan(0)
// Should find Bob and Charlie (Alice's friends)
const bobResult = results.find((r: any) => r.entity.id === 'bob')
const charlieResult = results.find((r: any) => r.entity.id === 'charlie')
const bobResult = results.find((r: any) => r.entity.id === v5('bob'))
const charlieResult = results.find((r: any) => r.entity.id === v5('charlie'))
expect(bobResult || charlieResult).toBeDefined()
})
@ -154,7 +159,7 @@ describe('Unified Find() Integration Tests', () => {
expect(results.length).toBeGreaterThan(0)
// Should find Alice (who is friends with Bob)
const aliceResult = results.find((r: any) => r.entity.id === 'alice')
const aliceResult = results.find((r: any) => r.entity.id === v5('alice'))
expect(aliceResult).toBeDefined()
})
@ -169,8 +174,8 @@ describe('Unified Find() Integration Tests', () => {
expect(results.length).toBeGreaterThan(0)
// Should find both Bob and Charlie
const bobResult = results.find((r: any) => r.entity.id === 'bob')
const charlieResult = results.find((r: any) => r.entity.id === 'charlie')
const bobResult = results.find((r: any) => r.entity.id === v5('bob'))
const charlieResult = results.find((r: any) => r.entity.id === v5('charlie'))
expect(bobResult).toBeDefined()
expect(charlieResult).toBeDefined()
})
@ -204,9 +209,10 @@ describe('Unified Find() Integration Tests', () => {
})
expect(results.length).toBeGreaterThan(0)
// Should find direct connections (Bob, Charlie) and second-degree (Diana)
const directConnections = ['bob', 'charlie']
const secondDegreeConnections = ['diana']
// Should find direct connections (Bob, Charlie) and second-degree (Diana).
// Compare against the canonical v5 ids the engine stored for each key.
const directConnections = [v5('bob'), v5('charlie')]
const secondDegreeConnections = [v5('diana')]
const hasDirect = results.some((r: any) => directConnections.includes(r.entity.id))
const hasSecondDegree = results.some((r: any) => secondDegreeConnections.includes(r.entity.id))
@ -326,7 +332,7 @@ describe('Unified Find() Integration Tests', () => {
expect(results.length).toBeGreaterThan(0)
// Charlie should be highly ranked due to direct connection
const charlieResult = results.find((r: any) => r.entity.id === 'charlie')
const charlieResult = results.find((r: any) => r.entity.id === v5('charlie'))
if (charlieResult) {
expect(charlieResult.score).toBeGreaterThan(0.5)
}