2025-11-05 17:01:44 -08:00
/ * *
* Commit State Capture Integration Tests ( v5 . 4.0 Phase 1 )
*
* Tests the new captureState parameter on commit ( ) that enables
* historical time - travel by capturing entity snapshots in trees .
*
* Tests :
2026-03-23 15:45:46 -07:00
* 1 . Lightweight commit ( captureState : false uses NULL_HASH )
* 2 . Default behavior ( captureState omitted → captures state )
2025-11-05 17:01:44 -08:00
* 3 . Empty workspace handling
* 4 . Performance benchmarks ( 100 , 1 K , 10 K entities )
* 5 . BlobStorage deduplication
* 6 . Tree structure validity
* 7 . Storage adapter compatibility
* 8 . VFS entity capture
* /
import { describe , it , expect , beforeEach , afterEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import * as fs from 'fs'
import * as path from 'path'
const TEST_DATA_PATH = './test-commit-state-capture'
describe ( 'Commit State Capture (v5.4.0)' , ( ) = > {
let brain : Brainy
beforeEach ( async ( ) = > {
// Clean up test data
if ( fs . existsSync ( TEST_DATA_PATH ) ) {
fs . rmSync ( TEST_DATA_PATH , { recursive : true , force : true } )
}
fs . mkdirSync ( TEST_DATA_PATH , { recursive : true } )
brain = new Brainy ( {
storage : {
adapter : 'filesystem' ,
path : TEST_DATA_PATH
} ,
disableAutoRebuild : true ,
silent : true
} )
await brain . init ( )
} )
afterEach ( ( ) = > {
if ( fs . existsSync ( TEST_DATA_PATH ) ) {
fs . rmSync ( TEST_DATA_PATH , { recursive : true , force : true } )
}
} )
it ( 'should use NULL_HASH when captureState is false (backward compat)' , async ( ) = > {
console . log ( '\n📋 Test 1: Backward compatibility' )
// Add some entities
await brain . add ( { type : 'concept' , data : { title : 'Test' } } )
2026-03-23 15:45:46 -07:00
// Commit with explicit captureState: false (lightweight metadata-only commit)
2025-11-05 17:01:44 -08:00
const commitId = await brain . commit ( {
message : 'Test commit' ,
2026-03-23 15:45:46 -07:00
captureState : false // Explicit false — skips state capture
2025-11-05 17:01:44 -08:00
} )
console . log ( ` Commit ID: ${ commitId . slice ( 0 , 8 ) } ` )
// Read commit object
const blobStorage = ( brain . storage as any ) . blobStorage
const { CommitObject } = await import ( '../../src/storage/cow/CommitObject.js' )
const { NULL_HASH } = await import ( '../../src/storage/cow/constants.js' )
const commit = await CommitObject . read ( blobStorage , commitId )
// Verify tree is NULL_HASH (empty)
expect ( commit . tree ) . toBe ( NULL_HASH )
console . log ( ` ✅ Tree is NULL_HASH (backward compatible) ` )
} )
2026-03-23 15:45:46 -07:00
it ( 'should capture state when captureState is omitted (default: true)' , async ( ) = > {
console . log ( '\n📋 Test 2: Default behavior (captureState omitted → captures state)' )
2025-11-05 17:01:44 -08:00
await brain . add ( { type : 'concept' , data : { title : 'Test' } } )
2026-03-23 15:45:46 -07:00
// Commit WITHOUT captureState parameter (omitted = default true)
2025-11-05 17:01:44 -08:00
const commitId = await brain . commit ( {
message : 'Test commit'
2026-03-23 15:45:46 -07:00
// captureState not specified → defaults to true
2025-11-05 17:01:44 -08:00
} )
const blobStorage = ( brain . storage as any ) . blobStorage
const { CommitObject } = await import ( '../../src/storage/cow/CommitObject.js' )
const { NULL_HASH } = await import ( '../../src/storage/cow/constants.js' )
const commit = await CommitObject . read ( blobStorage , commitId )
2026-03-23 15:45:46 -07:00
// Default behavior now captures state (tree hash is NOT null)
expect ( commit . tree ) . not . toBe ( NULL_HASH )
console . log ( ` ✅ Tree is ${ commit . tree . slice ( 0 , 8 ) } ... (state captured by default) ` )
2025-11-05 17:01:44 -08:00
} )
it ( 'should capture entity state when captureState is true' , async ( ) = > {
console . log ( '\n📋 Test 3: State capture enabled' )
// Add entities (add() returns entity ID, not entity object)
const entity1Id = await brain . add ( {
type : 'person' ,
data : { name : 'Alice' , age : 30 }
} )
const entity2Id = await brain . add ( {
type : 'concept' ,
data : { title : 'AI' , description : 'Artificial Intelligence' }
} )
console . log ( ` Added 2 entities: ${ entity1Id . slice ( 0 , 8 ) } , ${ entity2Id . slice ( 0 , 8 ) } ` )
// Commit WITH captureState
const commitId = await brain . commit ( {
message : 'Snapshot with entities' ,
captureState : true // CAPTURE STATE!
} )
console . log ( ` Commit ID: ${ commitId . slice ( 0 , 8 ) } ` )
// Read commit object
const blobStorage = ( brain . storage as any ) . blobStorage
const { CommitObject } = await import ( '../../src/storage/cow/CommitObject.js' )
const { NULL_HASH , isNullHash } = await import ( '../../src/storage/cow/constants.js' )
const commit = await CommitObject . read ( blobStorage , commitId )
// Verify tree is NOT NULL_HASH
expect ( commit . tree ) . not . toBe ( NULL_HASH )
expect ( isNullHash ( commit . tree ) ) . toBe ( false )
console . log ( ` ✅ Tree is NOT NULL_HASH: ${ commit . tree . slice ( 0 , 8 ) } ` )
// Verify tree exists and can be read
const { TreeObject } = await import ( '../../src/storage/cow/TreeObject.js' )
const tree = await TreeObject . read ( blobStorage , commit . tree )
expect ( tree ) . toBeDefined ( )
expect ( tree . entries . length ) . toBeGreaterThan ( 0 )
console . log ( ` ✅ Tree has ${ tree . entries . length } entries ` )
// Verify entities are in tree
let foundEntities = 0
const entityNames : string [ ] = [ ]
for ( const entry of tree . entries ) {
if ( entry . name . startsWith ( 'entities/' ) ) {
foundEntities ++
entityNames . push ( entry . name )
}
}
console . log ( ` Found ${ foundEntities } entities: ${ entityNames . join ( ', ' ) } ` )
// At least 2 entities should be captured (the ones we added)
// There may be system entities (VFS root, metadata, etc.)
expect ( foundEntities ) . toBeGreaterThanOrEqual ( 2 )
console . log ( ` ✅ Found ${ foundEntities } entities in tree (at least 2 as expected) ` )
} )
it ( 'should handle empty workspace correctly' , async ( ) = > {
console . log ( '\n📋 Test 4: Empty workspace (no user entities)' )
// Check how many entities exist before commit
const entitiesBefore = await brain . find ( { excludeVFS : false } )
console . log ( ` Entities before commit: ${ entitiesBefore . length } ` )
// Commit with NO user-added entities
const commitId = await brain . commit ( {
message : 'Empty snapshot' ,
captureState : true
} )
const blobStorage = ( brain . storage as any ) . blobStorage
const { CommitObject } = await import ( '../../src/storage/cow/CommitObject.js' )
const { NULL_HASH , isNullHash } = await import ( '../../src/storage/cow/constants.js' )
const commit = await CommitObject . read ( blobStorage , commitId )
// If there were NO entities before commit, tree should be NULL_HASH
// If there WERE entities (system entities), tree should contain them
if ( entitiesBefore . length === 0 ) {
expect ( commit . tree ) . toBe ( NULL_HASH )
console . log ( ` ✅ No entities: tree is NULL_HASH (correct) ` )
} else {
expect ( isNullHash ( commit . tree ) ) . toBe ( false )
console . log ( ` ✅ ${ entitiesBefore . length } system entities captured in tree ` )
}
} )
it ( 'should perform well with small workspace (100 entities)' , async ( ) = > {
console . log ( '\n📋 Test 5: Performance - 100 entities' )
// Add 100 entities
for ( let i = 0 ; i < 100 ; i ++ ) {
await brain . add ( {
type : 'concept' ,
data : { id : i , title : ` Concept ${ i } ` }
} )
}
console . log ( ` Added 100 entities ` )
// Measure commit time
const start = Date . now ( )
const commitId = await brain . commit ( {
message : '100 entity snapshot' ,
captureState : true
} )
const duration = Date . now ( ) - start
console . log ( ` Commit completed in ${ duration } ms ` )
console . log ( ` Commit ID: ${ commitId . slice ( 0 , 8 ) } ` )
// Should complete in < 1s
expect ( duration ) . toBeLessThan ( 1000 )
console . log ( ` ✅ Performance: ${ duration } ms < 1000ms ` )
} )
it ( 'should deduplicate unchanged entities across commits' , async ( ) = > {
console . log ( '\n📋 Test 6: BlobStorage deduplication' )
// Add initial entities (add() returns entity IDs)
const entity1Id = await brain . add ( {
type : 'person' ,
data : { name : 'Alice' , age : 30 }
} )
const entity2Id = await brain . add ( {
type : 'person' ,
data : { name : 'Bob' , age : 25 }
} )
// First commit with captureState
const commit1Id = await brain . commit ( {
message : 'First snapshot' ,
captureState : true
} )
console . log ( ` Commit 1: ${ commit1Id . slice ( 0 , 8 ) } ` )
// Modify ONLY entity2
await brain . update ( {
id : entity2Id ,
data : { name : 'Bob' , age : 26 } // Changed age
} )
// Second commit with captureState
const commit2Id = await brain . commit ( {
message : 'Second snapshot' ,
captureState : true
} )
console . log ( ` Commit 2: ${ commit2Id . slice ( 0 , 8 ) } ` )
// Read both trees
const blobStorage = ( brain . storage as any ) . blobStorage
const { CommitObject } = await import ( '../../src/storage/cow/CommitObject.js' )
const { TreeObject } = await import ( '../../src/storage/cow/TreeObject.js' )
const commit1 = await CommitObject . read ( blobStorage , commit1Id )
const commit2 = await CommitObject . read ( blobStorage , commit2Id )
const tree1 = await TreeObject . read ( blobStorage , commit1 . tree )
const tree2 = await TreeObject . read ( blobStorage , commit2 . tree )
// Find entity1 blob hash in both trees
const entity1Entry1 = tree1 . entries . find ( e = > e . name === ` entities/ ${ entity1Id } ` )
const entity1Entry2 = tree2 . entries . find ( e = > e . name === ` entities/ ${ entity1Id } ` )
expect ( entity1Entry1 ) . toBeDefined ( )
expect ( entity1Entry2 ) . toBeDefined ( )
// entity1 unchanged, so blob hash should be SAME (deduplication!)
expect ( entity1Entry1 ! . hash ) . toBe ( entity1Entry2 ! . hash )
console . log ( ` ✅ Unchanged entity1 has same blob hash (deduplicated) ` )
// Find entity2 blob hash in both trees
const entity2Entry1 = tree1 . entries . find ( e = > e . name === ` entities/ ${ entity2Id } ` )
const entity2Entry2 = tree2 . entries . find ( e = > e . name === ` entities/ ${ entity2Id } ` )
expect ( entity2Entry1 ) . toBeDefined ( )
expect ( entity2Entry2 ) . toBeDefined ( )
// entity2 changed, so blob hash should be DIFFERENT
expect ( entity2Entry1 ! . hash ) . not . toBe ( entity2Entry2 ! . hash )
console . log ( ` ✅ Changed entity2 has different blob hash (new version) ` )
} )
it ( 'should create valid tree structure that can be read back' , async ( ) = > {
console . log ( '\n📋 Test 7: Tree structure validity' )
// Add entities (add() returns entity IDs)
const entity1Id = await brain . add ( {
type : 'person' ,
data : { name : 'Alice' }
} )
const entity2Id = await brain . add ( {
type : 'concept' ,
data : { title : 'AI' }
} )
// Commit with captureState
const commitId = await brain . commit ( {
message : 'Test snapshot' ,
captureState : true
} )
// Read tree
const blobStorage = ( brain . storage as any ) . blobStorage
const { CommitObject } = await import ( '../../src/storage/cow/CommitObject.js' )
const { TreeObject } = await import ( '../../src/storage/cow/TreeObject.js' )
const commit = await CommitObject . read ( blobStorage , commitId )
const tree = await TreeObject . read ( blobStorage , commit . tree )
// Verify tree structure
expect ( tree . entries ) . toBeDefined ( )
expect ( Array . isArray ( tree . entries ) ) . toBe ( true )
// Walk tree and deserialize entities
let foundEntities = 0
for await ( const entry of TreeObject . walk ( blobStorage , tree ) ) {
if ( entry . type !== 'blob' || ! entry . name . startsWith ( 'entities/' ) ) {
continue
}
// Read blob
const blob = await blobStorage . read ( entry . hash )
const entity = JSON . parse ( blob . toString ( ) )
// Verify entity structure
expect ( entity ) . toHaveProperty ( 'id' )
expect ( entity ) . toHaveProperty ( 'type' )
expect ( entity ) . toHaveProperty ( 'data' )
expect ( entity ) . toHaveProperty ( 'metadata' )
console . log ( ` Found entity: ${ entity . id . slice ( 0 , 8 ) } ( ${ entity . type } ) ` )
foundEntities ++
}
// At least 2 entities should be found (the ones we added)
expect ( foundEntities ) . toBeGreaterThanOrEqual ( 2 )
console . log ( ` ✅ Successfully walked tree and deserialized ${ foundEntities } entities ` )
} )
it ( 'should capture VFS entities along with regular entities' , async ( ) = > {
console . log ( '\n📋 Test 8: VFS entity capture' )
// Add regular entity (add() returns entity ID)
const conceptId = await brain . add ( {
type : 'concept' ,
data : { title : 'Test Concept' }
} )
// Add VFS file
await brain . vfs . init ( )
await brain . vfs . writeFile ( '/test.md' , 'Test content' )
console . log ( ` Added concept: ${ conceptId . slice ( 0 , 8 ) } ` )
console . log ( ` Added VFS file: /test.md ` )
// Commit with captureState
const commitId = await brain . commit ( {
message : 'Snapshot with VFS' ,
captureState : true
} )
// Read tree and verify both types of entities are captured
const blobStorage = ( brain . storage as any ) . blobStorage
const { CommitObject } = await import ( '../../src/storage/cow/CommitObject.js' )
const { TreeObject } = await import ( '../../src/storage/cow/TreeObject.js' )
const commit = await CommitObject . read ( blobStorage , commitId )
const tree = await TreeObject . read ( blobStorage , commit . tree )
let regularEntities = 0
let vfsEntities = 0
for await ( const entry of TreeObject . walk ( blobStorage , tree ) ) {
if ( entry . type !== 'blob' || ! entry . name . startsWith ( 'entities/' ) ) {
continue
}
const blob = await blobStorage . read ( entry . hash )
const entity = JSON . parse ( blob . toString ( ) )
if ( entity . metadata ? . isVFS || entity . metadata ? . vfsType ) {
vfsEntities ++
console . log ( ` Found VFS entity: ${ entity . metadata . path } ` )
} else {
regularEntities ++
console . log ( ` Found regular entity: ${ entity . id . slice ( 0 , 8 ) } ( ${ entity . type } ) ` )
}
}
expect ( regularEntities ) . toBeGreaterThanOrEqual ( 1 )
expect ( vfsEntities ) . toBeGreaterThanOrEqual ( 2 ) // File + root directory
console . log ( ` ✅ Captured ${ regularEntities } regular + ${ vfsEntities } VFS entities ` )
} )
it ( 'should work with Memory storage adapter' , async ( ) = > {
console . log ( '\n📋 Test 9: Memory storage adapter' )
const memBrain = new Brainy ( {
storage : { adapter : 'memory' } ,
silent : true
} )
await memBrain . init ( )
// Add entity
await memBrain . add ( { type : 'concept' , data : { title : 'Test' } } )
// Commit with captureState
const commitId = await memBrain . commit ( {
message : 'Memory storage test' ,
captureState : true
} )
const blobStorage = ( memBrain . storage as any ) . blobStorage
const { CommitObject } = await import ( '../../src/storage/cow/CommitObject.js' )
const { isNullHash } = await import ( '../../src/storage/cow/constants.js' )
const commit = await CommitObject . read ( blobStorage , commitId )
expect ( isNullHash ( commit . tree ) ) . toBe ( false )
console . log ( ` ✅ Memory storage: tree captured successfully ` )
} )
it ( 'should work with TypeAware storage adapter' , async ( ) = > {
console . log ( '\n📋 Test 10: TypeAware storage adapter' )
const testPath = './test-typeaware-commit-capture'
if ( fs . existsSync ( testPath ) ) {
fs . rmSync ( testPath , { recursive : true , force : true } )
}
const typeAwareBrain = new Brainy ( {
storage : {
adapter : 'typeaware' ,
path : testPath
} ,
silent : true
} )
await typeAwareBrain . init ( )
// Add entity
await typeAwareBrain . add ( { type : 'person' , data : { name : 'Alice' } } )
// Commit with captureState
const commitId = await typeAwareBrain . commit ( {
message : 'TypeAware storage test' ,
captureState : true
} )
const blobStorage = ( typeAwareBrain . storage as any ) . blobStorage
const { CommitObject } = await import ( '../../src/storage/cow/CommitObject.js' )
const { isNullHash } = await import ( '../../src/storage/cow/constants.js' )
const commit = await CommitObject . read ( blobStorage , commitId )
expect ( isNullHash ( commit . tree ) ) . toBe ( false )
console . log ( ` ✅ TypeAware storage: tree captured successfully ` )
// Cleanup
if ( fs . existsSync ( testPath ) ) {
fs . rmSync ( testPath , { recursive : true , force : true } )
}
} )
it ( 'should capture relationships along with entities' , async ( ) = > {
console . log ( '\n📋 Test 11: Relationship capture (CRITICAL FIX)' )
// Add entities
const aliceId = await brain . add ( {
type : 'person' ,
data : { name : 'Alice' , role : 'Developer' }
} )
const bobId = await brain . add ( {
type : 'person' ,
data : { name : 'Bob' , role : 'Designer' }
} )
const projectId = await brain . add ( {
type : 'concept' ,
data : { title : 'Project X' , description : 'AI Platform' }
} )
console . log ( ` Added 3 entities: Alice, Bob, Project X ` )
// Create relationships (using valid VerbTypes)
await brain . relate ( {
from : aliceId ,
to : projectId ,
type : 'relatedTo' // Alice works on Project X
} )
await brain . relate ( {
from : bobId ,
to : projectId ,
type : 'relatedTo' // Bob works on Project X
} )
await brain . relate ( {
from : aliceId ,
to : bobId ,
type : 'relatedTo' // Alice collaborates with Bob
} )
console . log ( ` Created 3 relationships ` )
// Commit with captureState
const commitId = await brain . commit ( {
message : 'Snapshot with entities + relationships' ,
captureState : true
} )
console . log ( ` Commit ID: ${ commitId . slice ( 0 , 8 ) } ` )
// Read tree and verify relationships are captured
const blobStorage = ( brain . storage as any ) . blobStorage
const { CommitObject } = await import ( '../../src/storage/cow/CommitObject.js' )
const { TreeObject } = await import ( '../../src/storage/cow/TreeObject.js' )
const commit = await CommitObject . read ( blobStorage , commitId )
const tree = await TreeObject . read ( blobStorage , commit . tree )
// Count entities and relationships in tree
let entityCount = 0
let relationCount = 0
for ( const entry of tree . entries ) {
if ( entry . name . startsWith ( 'entities/' ) ) {
entityCount ++
} else if ( entry . name . startsWith ( 'relations/' ) ) {
relationCount ++
}
}
console . log ( ` Tree contains: ${ entityCount } entities, ${ relationCount } relationships ` )
// Verify we have at least the entities and relationships we created
expect ( entityCount ) . toBeGreaterThanOrEqual ( 3 )
expect ( relationCount ) . toBe ( 3 )
console . log ( ` ✅ Relationships captured in tree! ` )
// Verify we can deserialize relationships from tree
let deserializedRelations = 0
for ( const entry of tree . entries ) {
if ( ! entry . name . startsWith ( 'relations/' ) ) continue
const blob = await blobStorage . read ( entry . hash )
const relation = JSON . parse ( blob . toString ( ) )
// Verify relationship structure
expect ( relation ) . toHaveProperty ( 'sourceId' )
expect ( relation ) . toHaveProperty ( 'targetId' )
expect ( relation ) . toHaveProperty ( 'verb' )
console . log ( ` Found relation: ${ relation . verb } ( ${ relation . sourceId . slice ( 0 , 8 ) } → ${ relation . targetId . slice ( 0 , 8 ) } ) ` )
deserializedRelations ++
}
expect ( deserializedRelations ) . toBe ( 3 )
console . log ( ` ✅ Successfully deserialized ${ deserializedRelations } relationships from tree ` )
} )
} )