2025-10-24 11:42:47 -07:00
/ * *
2026-06-17 13:11:41 -07:00
* VFS - Knowledge Separation Test ( 8.0 )
2025-10-24 11:42:47 -07:00
*
2026-06-17 13:11:41 -07:00
* Exercises how VFS infrastructure entities coexist with knowledge - graph
* entities in the same brain , and how a query opts in / o u t o f t h e V F S l a y e r :
*
* - VFS files / directories are real entities , marked in metadata with
* ` isVFS: true ` / ` isVFSEntity: true ` and ` vfsType: 'file' | 'directory' ` .
* - ` brain.find() ` INCLUDES VFS entities by default ( 8.0 semantics ) .
* - ` brain.find({ excludeVFS: true }) ` drops the VFS infrastructure layer ,
* returning only knowledge entities — works on both the metadata - filter
* path and the vector / ` query ` path .
* - ` where: { vfsType: 'file' } ` selects VFS file entities explicitly .
* - Relationships can span VFS files and knowledge entities .
*
* Runs under the deterministic embedder ( tests / setup - integration . ts ) , so
* cross - text semantic ranking is not meaningful ; self - retrieval ( querying an
* entity by its own text ) and ` excludeVFS ` filtering are .
2025-10-24 11:42:47 -07:00
* /
import { describe , it , expect , beforeAll , afterAll } from 'vitest'
import { Brainy } from '../../src/brainy.js'
2026-06-17 13:11:41 -07:00
import { NounType , VerbType } from '../../src/types/graphTypes.js'
2025-10-24 11:42:47 -07:00
import * as fs from 'fs'
import * as path from 'path'
2026-06-17 13:11:41 -07:00
describe ( 'VFS-Knowledge Separation (8.0)' , ( ) = > {
2025-10-24 11:42:47 -07:00
const testDir = path . join ( process . cwd ( ) , 'test-vfs-knowledge-separation' )
let brain : Brainy
beforeAll ( async ( ) = > {
// Clean up
if ( fs . existsSync ( testDir ) ) {
fs . rmSync ( testDir , { recursive : true , force : true } )
}
fs . mkdirSync ( testDir , { recursive : true } )
2026-06-17 13:11:41 -07:00
brain = new Brainy ( {
requireSubtype : false ,
2025-10-24 11:42:47 -07:00
storage : {
type : 'filesystem' ,
feat(8.0): API simplification — remove neural()/Db.search, one storage `path` key, integration→0
8.0 RC cleanup toward "one place per thing, zero-config, no deprecation":
- Remove the `brain.neural()` clustering namespace (ImprovedNeuralAPI + the dead
legacy NeuralAPI + the neural CLI + neural-only types). Similarity is `find({vector})`
/ `similar({to})`; attribute grouping is the aggregation `GROUP BY` engine. The separate
entity-extraction / smart-import feature (NeuralImport, NeuralEntityExtractor, SmartExtractor,
NaturalLanguageProcessor, `brain.extract()`/`brain.nlp()`) is kept.
- Remove `Db.search()`; `find()` is the one query verb (accepts a bare string or FindParams).
Fix the bundled MCP client, which called a non-existent `brain.search(query, limit)` →
now `find({ query, limit })`.
- Storage config: collapse to one canonical top-level `path` key. The pre-8.0 aliases
(`rootDirectory`, `options.*`, `fileSystemStorage.*`) are removed and now THROW with the
exact rename instead of silently defaulting to `./brainy-data` on upgrade. A single resolver
feeds createStorage, the 7.x→8.0 migration probe, and the plugin-factory handoff, so a native
storage provider resolves the identical root (no split-brain).
- Fix `similar({ threshold })`: the min-similarity filter was silently dropped; it is now
applied as a post-filter on `result.score` (the documented way to bound semantic results).
- Fix `vfs.rename()` on a directory: child path updates spread the entity vector into `update()`
and failed dimension validation; they are metadata-only updates now.
- Fix `vfs.move()`: copy+delete orphaned the content-addressed content blob (the destination
shared the source hash, then unlink removed it). `move()` now delegates to `rename()` — an
in-place path change that preserves the blob and the entity id, for files and directories.
- Fix streaming import: the bulk fast path never flushed mid-import nor signalled queryability.
Entity writes are now chunked by a progressive flush interval (100 → 1000 → 5000); each chunk
flushes and emits `progress.queryable`, so imported data is queryable during the import.
- Sweep all docs, comments, and JSDoc for the removed/changed APIs.
Integration suite: 49 files / 588 passed / 0 failed. Unit: 80 files / 1456 passed, no type errors.
2026-06-20 13:31:11 -07:00
path : testDir
2025-10-24 11:42:47 -07:00
}
} )
await brain . init ( )
2026-06-17 13:11:41 -07:00
// Seed the VFS layer + one knowledge entity used across the suite.
2025-11-02 11:38:12 -08:00
const vfs = brain . vfs
2025-10-24 11:42:47 -07:00
await vfs . init ( )
await vfs . mkdir ( '/docs' , { recursive : true } )
await vfs . writeFile ( '/docs/readme.md' , '# Hello World' )
2026-06-17 13:11:41 -07:00
await brain . add ( {
2025-10-24 11:42:47 -07:00
data : 'This is a knowledge document about AI' ,
type : NounType . Document ,
metadata : {
title : 'AI Research Paper' ,
category : 'research'
}
} )
2026-06-17 13:11:41 -07:00
} )
afterAll ( async ( ) = > {
// Close releases the writer lock + flushes; prevents background-flush bleed.
await brain . close ( )
if ( fs . existsSync ( testDir ) ) {
fs . rmSync ( testDir , { recursive : true , force : true } )
}
} )
2025-10-24 11:42:47 -07:00
2026-06-17 13:11:41 -07:00
it ( 'includes VFS entities in brain.find() by default' , async ( ) = > {
// 8.0: VFS infrastructure entities are returned by default. The /docs/readme.md
// file is a Document-typed entity carrying isVFS/vfsType markers, so a plain
// type query surfaces BOTH it and the knowledge document.
2025-10-24 11:42:47 -07:00
const results = await brain . find ( {
type : NounType . Document ,
limit : 100
} )
2026-06-17 13:11:41 -07:00
const vfsResults = results . filter ( ( r ) = > r . metadata ? . isVFS === true )
const knowledgeResults = results . filter ( ( r ) = > r . metadata ? . isVFS !== true )
2025-10-24 11:42:47 -07:00
2026-06-17 13:11:41 -07:00
// Default find() shows the VFS file alongside knowledge.
expect ( vfsResults . length ) . toBeGreaterThan ( 0 )
2025-10-24 11:42:47 -07:00
expect ( knowledgeResults . length ) . toBeGreaterThan ( 0 )
2026-06-17 13:11:41 -07:00
expect ( results . some ( ( r ) = > r . metadata ? . title === 'AI Research Paper' ) ) . toBe ( true )
expect ( results . some ( ( r ) = > r . metadata ? . vfsType === 'file' ) ) . toBe ( true )
2025-10-24 11:42:47 -07:00
} )
2026-06-17 13:11:41 -07:00
it ( 'excludes VFS entities when excludeVFS: true' , async ( ) = > {
// 8.0: excludeVFS drops the VFS infrastructure layer. Only knowledge entities remain.
2025-10-24 11:42:47 -07:00
const results = await brain . find ( {
type : NounType . Document ,
2026-06-17 13:11:41 -07:00
excludeVFS : true ,
2025-10-24 11:42:47 -07:00
limit : 100
} )
2026-06-17 13:11:41 -07:00
const vfsResults = results . filter ( ( r ) = > r . metadata ? . isVFS === true )
const knowledgeResults = results . filter ( ( r ) = > r . metadata ? . isVFS !== true )
2025-10-24 11:42:47 -07:00
2026-06-17 13:11:41 -07:00
expect ( vfsResults . length ) . toBe ( 0 )
2025-10-24 11:42:47 -07:00
expect ( knowledgeResults . length ) . toBeGreaterThan ( 0 )
2026-06-17 13:11:41 -07:00
expect ( results . some ( ( r ) = > r . metadata ? . title === 'AI Research Paper' ) ) . toBe ( true )
2025-10-24 11:42:47 -07:00
} )
it ( 'should allow relationships between VFS files and knowledge entities' , async ( ) = > {
2026-06-17 13:11:41 -07:00
// Get VFS file entity. VFS files are identified by vfsType (the indexed,
// queryable marker); they are included in find() by default.
2025-10-24 11:42:47 -07:00
const vfsFile = await brain . find ( {
where : {
path : '/docs/readme.md'
} ,
limit : 1
} )
expect ( vfsFile . length ) . toBe ( 1 )
2026-06-17 13:11:41 -07:00
expect ( vfsFile [ 0 ] . metadata ? . vfsType ) . toBe ( 'file' )
2025-10-24 11:42:47 -07:00
// Get knowledge entity
const knowledgeEntity = await brain . find ( {
type : NounType . Document ,
where : {
title : 'AI Research Paper'
} ,
2026-06-17 13:11:41 -07:00
excludeVFS : true ,
2025-10-24 11:42:47 -07:00
limit : 1
} )
expect ( knowledgeEntity . length ) . toBe ( 1 )
2026-06-17 13:11:41 -07:00
// Create relationship: knowledge entity -> references -> VFS file.
// relate() returns the new relation's id (string).
const relationId = await brain . relate ( {
2025-10-24 11:42:47 -07:00
from : knowledgeEntity [ 0 ] . id ,
to : vfsFile [ 0 ] . id ,
2026-06-17 13:11:41 -07:00
type : VerbType . References
2025-10-24 11:42:47 -07:00
} )
2026-06-17 13:11:41 -07:00
expect ( typeof relationId ) . toBe ( 'string' )
expect ( relationId . length ) . toBeGreaterThan ( 0 )
2025-10-24 11:42:47 -07:00
// Verify relationship exists
2026-06-11 14:51:00 -07:00
const relations = await brain . related ( {
2025-10-24 11:42:47 -07:00
from : knowledgeEntity [ 0 ] . id ,
to : vfsFile [ 0 ] . id
} )
expect ( relations . length ) . toBe ( 1 )
2026-06-17 13:11:41 -07:00
expect ( relations [ 0 ] . type ) . toBe ( VerbType . References )
2025-10-24 11:42:47 -07:00
} )
it ( 'should filter VFS entities using where clause' , async ( ) = > {
2026-06-17 13:11:41 -07:00
// Query for VFS files explicitly via the vfsType marker (indexed + queryable).
2025-10-24 11:42:47 -07:00
const vfsFiles = await brain . find ( {
where : {
vfsType : 'file'
} ,
limit : 100
} )
expect ( vfsFiles . length ) . toBeGreaterThan ( 0 )
2026-06-17 13:11:41 -07:00
expect ( vfsFiles . every ( ( f ) = > f . metadata ? . vfsType === 'file' ) ) . toBe ( true )
// Every vfsType:'file' entity is a VFS infrastructure entity.
expect ( vfsFiles . every ( ( f ) = > f . metadata ? . isVFS === true ) ) . toBe ( true )
2025-10-24 11:42:47 -07:00
2026-06-17 13:11:41 -07:00
// Query for non-VFS knowledge entities explicitly via a user metadata field.
2025-10-24 11:42:47 -07:00
const nonVFS = await brain . find ( {
where : {
category : 'research'
} ,
limit : 100
} )
expect ( nonVFS . length ) . toBeGreaterThan ( 0 )
2026-06-17 13:11:41 -07:00
expect ( nonVFS . every ( ( e ) = > e . metadata ? . isVFS !== true ) ) . toBe ( true )
expect ( nonVFS . every ( ( e ) = > e . metadata ? . vfsType === undefined ) ) . toBe ( true )
2025-10-24 11:42:47 -07:00
} )
it ( 'should handle semantic search with VFS filtering' , async ( ) = > {
2026-06-17 13:11:41 -07:00
// Self-retrieval: querying with the knowledge doc's own text returns it
// (deterministic embedder ⇒ cosine 1.0). With excludeVFS the VFS layer is dropped.
const knowledgeOnly = await brain . find ( {
query : 'This is a knowledge document about AI' ,
excludeVFS : true ,
2025-10-24 11:42:47 -07:00
limit : 10
} )
2026-06-17 13:11:41 -07:00
expect ( knowledgeOnly . length ) . toBeGreaterThan ( 0 )
expect ( knowledgeOnly . some ( ( r ) = > r . metadata ? . isVFS === true ) ) . toBe ( false )
expect ( knowledgeOnly . some ( ( r ) = > r . metadata ? . title === 'AI Research Paper' ) ) . toBe ( true )
// Default (no excludeVFS): self-retrieval of the VFS file by its own content
// returns it — VFS entities participate in vector search by default.
const withVFS = await brain . find ( {
query : '# Hello World' ,
2025-10-24 11:42:47 -07:00
limit : 10
} )
2026-06-17 13:11:41 -07:00
expect ( withVFS . some ( ( r ) = > r . metadata ? . path === '/docs/readme.md' ) ) . toBe ( true )
2025-10-24 11:42:47 -07:00
2026-06-17 13:11:41 -07:00
// excludeVFS on the vector path removes the VFS file even when its content matches.
const withVFSExcluded = await brain . find ( {
query : '# Hello World' ,
excludeVFS : true ,
limit : 10
} )
expect ( withVFSExcluded . some ( ( r ) = > r . metadata ? . isVFS === true ) ) . toBe ( false )
2025-10-24 11:42:47 -07:00
} )
} )