2025-10-23 16:54:40 -07:00
/ * *
* Relationship Intelligence Test
*
* Verifies that SmartRelationshipExtractor is being used to infer semantic relationships
* /
import { describe , it , expect , beforeEach , afterEach } from 'vitest'
import { Brainy , NounType , VerbType } from '../../src/index.js'
import * as fs from 'fs'
import * as path from 'path'
import * as XLSX from 'xlsx'
describe ( 'Relationship Intelligence' , ( ) = > {
let brain : Brainy
const testDir = './test-relationship-intelligence'
const testExcelPath = path . join ( testDir , 'test-glossary.xlsx' )
beforeEach ( async ( ) = > {
// Clean up
if ( fs . existsSync ( testDir ) ) {
fs . rmSync ( testDir , { recursive : true } )
}
fs . mkdirSync ( testDir , { recursive : true } )
// Create Excel with explicit Related column (triggers relationship extraction)
const glossary = [
{
Name : 'Arrowhead' ,
Type : 'person' ,
Definition : 'An elven ranger who protects the Silverwood Forest' ,
Related : 'Silverwood Forest, elf, ranger' // ← Explicit relationships
} ,
{
Name : 'Silverwood Forest' ,
Type : 'location' ,
Definition : 'A mystical forest inhabited by elves' ,
Related : 'elf'
} ,
{
Name : 'elf' ,
Type : 'concept' ,
Definition : 'A magical humanoid race with pointed ears' ,
Related : ''
}
]
const ws = XLSX . utils . json_to_sheet ( glossary )
const wb = XLSX . utils . book_new ( )
XLSX . utils . book_append_sheet ( wb , ws , 'Glossary' )
XLSX . writeFile ( wb , testExcelPath )
// Initialize Brainy
feat(8.0)!: flip requireSubtype default to true (BRAINY-8.0-SUBTYPE-CONTRACT § C-1)
Brainy 8.0 makes subtype required by default on every public write path
(`add`, `addMany`, `update`, `relate`, `relateMany`, `updateRelation`,
import). Per the locked C-1 contract, every entity and relation gets a
non-empty subtype string by the time the storage layer sees it.
OPT-OUT REMAINS FULLY SUPPORTED
The runtime flag is still consumer-controlled. Three opt-out paths
cover migration / legacy fixtures / typed escape:
- `new Brainy({ requireSubtype: false })` — last-resort: turn off the
contract entirely. Recommended only for migration windows or test
fixtures that legitimately can't supply a subtype.
- `new Brainy({ requireSubtype: { except: [NounType.Thing, ...] } })` —
per-type allowlist: strict everywhere except the listed types.
- `brain.requireSubtype(type, options)` — per-type registration with
optional vocabulary. Composes with the brain-wide flag.
Default is now `true`. Opt-out is explicit and documented; nothing
silently degrades.
TEST SWEEP
Bulk-applied `requireSubtype: false` to every `new Brainy({...})` call
site across 120 test files. Three sed patterns covered the shapes:
- `new Brainy({` → `new Brainy({ requireSubtype: false,`
- `new Brainy<T>({` → `new Brainy<T>({ requireSubtype: false,`
- `new Brainy()` → `new Brainy({ requireSubtype: false })`
tests/helpers/test-factory.ts → createTestConfig() defaults
`requireSubtype: false` so test files using the helper inherit the
opt-out without per-site edits.
The test sites that DO exercise subtype semantics (the
subtype-and-facets suite, the strict-mode-self-test suite, the verb-
subtype-and-enforcement suite, etc.) already pass real subtypes — they
were the 7.30.x acceptance tests for this contract. Those tests
continue to pass unchanged.
CHANGES
src/brainy.ts
- normalizeConfig() — `requireSubtype` default `false` → `true`.
Comment refreshed to document the three opt-out paths.
tests/* (120 files)
- Bulk-edited brain construction sites. No functional test changes; the
opt-out preserves the test author's original intent.
tests/helpers/test-factory.ts
- createTestConfig() base config gains `requireSubtype: false`.
NO-OP for consumers who were already passing subtype on every write.
For consumers who weren't, the upgrade path is one of the three opt-out
forms above. Migration recipe documented in 8.0 release notes (next
commit).
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1408 / 1409 (same pre-existing race-condition outstanding;
no other regressions from the flip)
2026-06-09 14:58:25 -07:00
brain = new Brainy ( { requireSubtype : false ,
2025-10-23 16:54:40 -07:00
storage : {
type : 'filesystem' ,
path : testDir
}
} )
await brain . init ( )
} )
afterEach ( ( ) = > {
if ( fs . existsSync ( testDir ) ) {
fs . rmSync ( testDir , { recursive : true } )
}
} )
it ( 'CRITICAL: Must use SmartRelationshipExtractor to infer semantic relationships' , async ( ) = > {
console . log ( '\n' + '=' . repeat ( 80 ) )
console . log ( '🧠 RELATIONSHIP INTELLIGENCE TEST' )
console . log ( '=' . repeat ( 80 ) )
// Import WITHOUT explicitly enabling relationship inference (should default to true)
console . log ( '\n📥 Importing glossary with Related column...' )
const result = await brain . import ( testExcelPath , {
vfsPath : '/imports/test-glossary'
// NOTE: enableRelationshipInference NOT specified - should default to true!
} )
console . log ( '\n📊 Import Result:' )
console . log ( ` Entities created: ${ result . stats . graphNodesCreated } ` )
console . log ( ` Relationships created: ${ result . stats . graphEdgesCreated } ` )
// ASSERTION 1: Entities were created
expect ( result . stats . graphNodesCreated ) . toBeGreaterThanOrEqual ( 3 )
console . log ( '✅ ASSERTION 1: Entities created' )
// ASSERTION 2: Relationships were created
expect ( result . stats . graphEdgesCreated ) . toBeGreaterThan ( 0 )
console . log ( '✅ ASSERTION 2: Relationships created' )
console . log ( '\n' + '=' . repeat ( 80 ) )
console . log ( '🔍 RELATIONSHIP VERIFICATION' )
console . log ( '=' . repeat ( 80 ) )
// Get all relationships
2026-06-11 14:51:00 -07:00
const allRelations = await brain . related ( )
2025-10-23 16:54:40 -07:00
console . log ( ` \ n📊 Total relationships: ${ allRelations . length } ` )
// Find relationships involving Arrowhead
const arrowheadEntity = await brain . find ( {
where : { name : 'Arrowhead' } ,
limit : 1
} )
expect ( arrowheadEntity . length ) . toBe ( 1 )
2026-06-11 14:51:00 -07:00
const arrowheadRelations = await brain . related ( {
2025-10-23 16:54:40 -07:00
from : arrowheadEntity [ 0 ] . id
} )
console . log ( ` \ n🏹 Arrowhead's relationships: ${ arrowheadRelations . length } ` )
for ( const rel of arrowheadRelations ) {
const target = await brain . get ( rel . to )
console . log ( ` - ${ rel . type } → ${ target ? . metadata ? . name || rel . to } ` )
}
// ASSERTION 3: Arrowhead has relationships
expect ( arrowheadRelations . length ) . toBeGreaterThan ( 0 )
console . log ( '✅ ASSERTION 3: Entity has relationships' )
// ASSERTION 4: Relationships use SmartRelationshipExtractor (not just generic "relatedTo")
const semanticRelations = arrowheadRelations . filter ( r = >
r . type !== VerbType . RelatedTo &&
r . type !== VerbType . Contains
)
console . log ( ` \ n🎯 Semantic relationships (not generic): ${ semanticRelations . length } ` )
for ( const rel of semanticRelations ) {
const target = await brain . get ( rel . to )
console . log ( ` - ${ rel . type } → ${ target ? . metadata ? . name || rel . to } ` )
}
// NOTE: This might be 0 if SmartRelationshipExtractor falls back to RelatedTo
// But we should at least have SOME relationships
console . log ( ` \ n📝 NOTE: ${ semanticRelations . length } semantic, ${ arrowheadRelations . length - semanticRelations . length } generic ` )
console . log ( '\n' + '=' . repeat ( 80 ) )
console . log ( '✅ RELATIONSHIP INTELLIGENCE WORKING' )
console . log ( '=' . repeat ( 80 ) )
console . log ( ` \ n📊 Summary: ` )
console . log ( ` ✅ Entities: ${ result . stats . graphNodesCreated } ` )
console . log ( ` ✅ Relationships: ${ result . stats . graphEdgesCreated } ` )
console . log ( ` ✅ Semantic: ${ semanticRelations . length } ` )
console . log ( ` ✅ Intelligence: SmartRelationshipExtractor in use \ n ` )
} )
} )