diff --git a/src/brainy.ts b/src/brainy.ts index d0474e46..b3d482b8 100644 --- a/src/brainy.ts +++ b/src/brainy.ts @@ -2196,7 +2196,7 @@ export class Brainy implements BrainyInterface { return this.augmentationRegistry.execute('clear', {}, async () => { // Clear storage await this.storage.clear() - + // Reset index if ('clear' in this.index && typeof this.index.clear === 'function') { await this.index.clear() @@ -2204,10 +2204,15 @@ export class Brainy implements BrainyInterface { // Recreate index if no clear method this.index = this.setupIndex() } - + + // v5.10.4: Recreate metadata index to clear cached data + // Bug: Metadata index cache was not being cleared, causing find() with type filters to return stale data + this.metadataIndex = new MetadataIndexManager(this.storage) + await this.metadataIndex.init() + // Reset dimensions this.dimensions = undefined - + // Clear any cached sub-APIs this._neural = undefined this._nlp = undefined diff --git a/src/storage/adapters/azureBlobStorage.ts b/src/storage/adapters/azureBlobStorage.ts index 06a73a60..7fcc27ef 100644 --- a/src/storage/adapters/azureBlobStorage.ts +++ b/src/storage/adapters/azureBlobStorage.ts @@ -1153,6 +1153,12 @@ export class AzureBlobStorage extends BaseStorage { this.commitLog = undefined this.cowEnabled = false + // v5.10.4: Create persistent marker blob (CRITICAL FIX) + // Bug: cowEnabled = false only affects current instance, not future instances + // Fix: Create marker blob that persists across instance restarts + // When new instance calls initializeCOW(), it checks for this marker + await this.createClearMarker() + // Clear caches this.nounCacheManager.clear() this.verbCacheManager.clear() @@ -1204,6 +1210,47 @@ export class AzureBlobStorage extends BaseStorage { } } + /** + * Check if COW has been explicitly disabled via clear() + * v5.10.4: Fixes bug where clear() doesn't persist across instance restarts + * @returns true if marker blob exists, false otherwise + * @protected + */ + protected async checkClearMarker(): Promise { + await this.ensureInitialized() + + try { + const markerPath = `${this.systemPrefix}cow-disabled` + const blockBlobClient = this.containerClient!.getBlockBlobClient(markerPath) + const exists = await blockBlobClient.exists() + return exists + } catch (error) { + this.logger.warn('AzureBlobStorage.checkClearMarker: Error checking marker', error) + return false + } + } + + /** + * Create marker indicating COW has been explicitly disabled + * v5.10.4: Called by clear() to prevent COW reinitialization on new instances + * @protected + */ + protected async createClearMarker(): Promise { + await this.ensureInitialized() + + try { + const markerPath = `${this.systemPrefix}cow-disabled` + const blockBlobClient = this.containerClient!.getBlockBlobClient(markerPath) + // Create empty marker blob + await blockBlobClient.upload(Buffer.from(''), 0, { + blobHTTPHeaders: { blobContentType: 'text/plain' } + }) + } catch (error) { + this.logger.error('AzureBlobStorage.createClearMarker: Failed to create marker blob', error) + // Don't throw - marker creation failure shouldn't break clear() + } + } + /** * Save statistics data to storage */ diff --git a/src/storage/adapters/fileSystemStorage.ts b/src/storage/adapters/fileSystemStorage.ts index fbc5c61f..8b99f8d4 100644 --- a/src/storage/adapters/fileSystemStorage.ts +++ b/src/storage/adapters/fileSystemStorage.ts @@ -1001,20 +1001,13 @@ export class FileSystemStorage extends BaseStorage { } } - // Remove all files in the nouns directory - await removeDirectoryContents(this.nounsDir) - - // Remove all files in the verbs directory - await removeDirectoryContents(this.verbsDir) - - // Remove all files in the metadata directory - await removeDirectoryContents(this.metadataDir) - - // Remove all files in the noun metadata directory - await removeDirectoryContents(this.nounMetadataDir) - - // Remove all files in the verb metadata directory - await removeDirectoryContents(this.verbMetadataDir) + // v5.10.4: Clear the entire branches/ directory (branch-based storage) + // Bug fix: Data is stored in branches/main/entities/, not just entities/ + // The branch-based structure was introduced for COW support + const branchesDir = path.join(this.rootDir, 'branches') + if (await this.directoryExists(branchesDir)) { + await removeDirectoryContents(branchesDir) + } // Remove all files in both system directories await removeDirectoryContents(this.systemDir) @@ -1037,6 +1030,12 @@ export class FileSystemStorage extends BaseStorage { this.blobStorage = undefined this.commitLog = undefined this.cowEnabled = false + + // v5.10.4: Create persistent marker file (CRITICAL FIX) + // Bug: cowEnabled = false only affects current instance, not future instances + // Fix: Create marker file that persists across instance restarts + // When new instance calls initializeCOW(), it checks for this marker + await this.createClearMarker() } // Clear the statistics cache @@ -1075,6 +1074,49 @@ export class FileSystemStorage extends BaseStorage { return result } + /** + * Check if COW has been explicitly disabled via clear() + * v5.10.4: Fixes bug where clear() doesn't persist across instance restarts + * @returns true if marker file exists, false otherwise + * @protected + */ + protected async checkClearMarker(): Promise { + // Check if fs module is available + if (!fs || !fs.promises) { + return false + } + + try { + const markerPath = path.join(this.systemDir, 'cow-disabled') + await fs.promises.access(markerPath, fs.constants.F_OK) + return true // Marker exists + } catch (error) { + return false // Marker doesn't exist (ENOENT) or can't be accessed + } + } + + /** + * Create marker indicating COW has been explicitly disabled + * v5.10.4: Called by clear() to prevent COW reinitialization on new instances + * @protected + */ + protected async createClearMarker(): Promise { + // Check if fs module is available + if (!fs || !fs.promises) { + console.warn('FileSystemStorage.createClearMarker: fs module not available, skipping marker creation') + return + } + + try { + const markerPath = path.join(this.systemDir, 'cow-disabled') + // Create empty marker file + await fs.promises.writeFile(markerPath, '', 'utf8') + } catch (error) { + console.error('FileSystemStorage.createClearMarker: Failed to create marker file', error) + // Don't throw - marker creation failure shouldn't break clear() + } + } + /** * Get information about storage usage and capacity */ diff --git a/src/storage/adapters/gcsStorage.ts b/src/storage/adapters/gcsStorage.ts index 3179f280..6d915986 100644 --- a/src/storage/adapters/gcsStorage.ts +++ b/src/storage/adapters/gcsStorage.ts @@ -1015,6 +1015,12 @@ export class GcsStorage extends BaseStorage { this.commitLog = undefined this.cowEnabled = false + // v5.10.4: Create persistent marker object (CRITICAL FIX) + // Bug: cowEnabled = false only affects current instance, not future instances + // Fix: Create marker object that persists across instance restarts + // When new instance calls initializeCOW(), it checks for this marker + await this.createClearMarker() + // Clear caches this.nounCacheManager.clear() this.verbCacheManager.clear() @@ -1069,6 +1075,45 @@ export class GcsStorage extends BaseStorage { } } + /** + * Check if COW has been explicitly disabled via clear() + * v5.10.4: Fixes bug where clear() doesn't persist across instance restarts + * @returns true if marker object exists, false otherwise + * @protected + */ + protected async checkClearMarker(): Promise { + await this.ensureInitialized() + + try { + const markerPath = `${this.systemPrefix}cow-disabled` + const file = this.bucket!.file(markerPath) + const [exists] = await file.exists() + return exists + } catch (error) { + this.logger.warn('GCSStorage.checkClearMarker: Error checking marker', error) + return false + } + } + + /** + * Create marker indicating COW has been explicitly disabled + * v5.10.4: Called by clear() to prevent COW reinitialization on new instances + * @protected + */ + protected async createClearMarker(): Promise { + await this.ensureInitialized() + + try { + const markerPath = `${this.systemPrefix}cow-disabled` + const file = this.bucket!.file(markerPath) + // Create empty marker object + await file.save('', { contentType: 'text/plain' }) + } catch (error) { + this.logger.error('GCSStorage.createClearMarker: Failed to create marker object', error) + // Don't throw - marker creation failure shouldn't break clear() + } + } + /** * Save statistics data to storage */ diff --git a/src/storage/adapters/historicalStorageAdapter.ts b/src/storage/adapters/historicalStorageAdapter.ts index d515d250..3075f171 100644 --- a/src/storage/adapters/historicalStorageAdapter.ts +++ b/src/storage/adapters/historicalStorageAdapter.ts @@ -310,6 +310,25 @@ export class HistoricalStorageAdapter extends BaseStorage { } } + /** + * Check if COW has been explicitly disabled via clear() + * v5.10.4: No-op for HistoricalStorageAdapter (read-only, doesn't manage COW) + * @returns Always false (read-only adapter doesn't manage COW state) + * @protected + */ + protected async checkClearMarker(): Promise { + return false // Read-only adapter - COW state managed by underlying storage + } + + /** + * Create marker indicating COW has been explicitly disabled + * v5.10.4: No-op for HistoricalStorageAdapter (read-only) + * @protected + */ + protected async createClearMarker(): Promise { + // No-op: HistoricalStorageAdapter is read-only, doesn't create markers + } + // ============= Override Write Methods (Read-Only) ============= /** diff --git a/src/storage/adapters/memoryStorage.ts b/src/storage/adapters/memoryStorage.ts index 0b5e9459..fb895080 100644 --- a/src/storage/adapters/memoryStorage.ts +++ b/src/storage/adapters/memoryStorage.ts @@ -196,6 +196,26 @@ export class MemoryStorage extends BaseStorage { } } + /** + * Check if COW has been explicitly disabled via clear() + * v5.10.4: No-op for MemoryStorage (doesn't persist) + * @returns Always false (marker doesn't persist in memory) + * @protected + */ + protected async checkClearMarker(): Promise { + return false // MemoryStorage doesn't persist - marker doesn't survive restart + } + + /** + * Create marker indicating COW has been explicitly disabled + * v5.10.4: No-op for MemoryStorage (doesn't persist) + * @protected + */ + protected async createClearMarker(): Promise { + // No-op: MemoryStorage doesn't persist, so marker is not needed + // clear() in memory already resets all state, no marker survives restart + } + /** * Save statistics data to storage * @param statistics The statistics data to save diff --git a/src/storage/adapters/opfsStorage.ts b/src/storage/adapters/opfsStorage.ts index 2d054f46..9945ea5a 100644 --- a/src/storage/adapters/opfsStorage.ts +++ b/src/storage/adapters/opfsStorage.ts @@ -495,6 +495,12 @@ export class OPFSStorage extends BaseStorage { this.blobStorage = undefined this.commitLog = undefined this.cowEnabled = false + + // v5.10.4: Create persistent marker file (CRITICAL FIX) + // Bug: cowEnabled = false only affects current instance, not future instances + // Fix: Create marker file that persists across instance restarts + // When new instance calls initializeCOW(), it checks for this marker + await this.createClearMarker() } catch (error: any) { // Ignore if _cow directory doesn't exist (not all instances use COW) if (error.name !== 'NotFoundError') { @@ -516,6 +522,53 @@ export class OPFSStorage extends BaseStorage { } } + /** + * Check if COW has been explicitly disabled via clear() + * v5.10.4: Fixes bug where clear() doesn't persist across instance restarts + * @returns true if marker file exists, false otherwise + * @protected + */ + protected async checkClearMarker(): Promise { + await this.ensureInitialized() + + try { + // Get system directory (may not exist yet) + const systemDir = await this.rootDir!.getDirectoryHandle('system', { create: false }) + // Try to get the marker file + await systemDir.getFileHandle('cow-disabled', { create: false }) + return true // Marker exists + } catch (error: any) { + if (error.name === 'NotFoundError') { + return false // Marker doesn't exist (or system dir doesn't exist) + } + // Other errors (permissions, etc.) - treat as marker not existing + console.warn('OPFSStorage.checkClearMarker: Error checking marker', error) + return false + } + } + + /** + * Create marker indicating COW has been explicitly disabled + * v5.10.4: Called by clear() to prevent COW reinitialization on new instances + * @protected + */ + protected async createClearMarker(): Promise { + await this.ensureInitialized() + + try { + // Get or create system directory + const systemDir = await this.rootDir!.getDirectoryHandle('system', { create: true }) + // Create empty marker file + const fileHandle = await systemDir.getFileHandle('cow-disabled', { create: true }) + const writable = await fileHandle.createWritable() + await writable.write(new Uint8Array(0)) // Empty file + await writable.close() + } catch (error) { + console.error('OPFSStorage.createClearMarker: Failed to create marker file', error) + // Don't throw - marker creation failure shouldn't break clear() + } + } + // Quota monitoring configuration (v4.0.0) private quotaWarningThreshold = 0.8 // Warn at 80% usage private quotaCriticalThreshold = 0.95 // Critical at 95% usage diff --git a/src/storage/adapters/r2Storage.ts b/src/storage/adapters/r2Storage.ts index 572fcbf7..2579f411 100644 --- a/src/storage/adapters/r2Storage.ts +++ b/src/storage/adapters/r2Storage.ts @@ -1051,6 +1051,12 @@ export class R2Storage extends BaseStorage { this.commitLog = undefined this.cowEnabled = false + // v5.10.4: Create persistent marker object (CRITICAL FIX) + // Bug: cowEnabled = false only affects current instance, not future instances + // Fix: Create marker object that persists across instance restarts + // When new instance calls initializeCOW(), it checks for this marker + await this.createClearMarker() + this.nounCacheManager.clear() this.verbCacheManager.clear() @@ -1086,6 +1092,43 @@ export class R2Storage extends BaseStorage { } } + /** + * Check if COW has been explicitly disabled via clear() + * v5.10.4: Fixes bug where clear() doesn't persist across instance restarts + * @returns true if marker object exists, false otherwise + * @protected + */ + protected async checkClearMarker(): Promise { + await this.ensureInitialized() + + try { + const markerPath = `${this.systemPrefix}cow-disabled` + const data = await this.readObjectFromPath(markerPath) + return data !== null // Marker exists if we got any data + } catch (error) { + prodLog.warn('R2Storage.checkClearMarker: Error checking marker', error) + return false + } + } + + /** + * Create marker indicating COW has been explicitly disabled + * v5.10.4: Called by clear() to prevent COW reinitialization on new instances + * @protected + */ + protected async createClearMarker(): Promise { + await this.ensureInitialized() + + try { + const markerPath = `${this.systemPrefix}cow-disabled` + // Create empty marker object + await this.writeObjectToPath(markerPath, '') + } catch (error) { + prodLog.error('R2Storage.createClearMarker: Failed to create marker object', error) + // Don't throw - marker creation failure shouldn't break clear() + } + } + // v5.4.0: Removed getNounsWithPagination override - use BaseStorage's type-first implementation diff --git a/src/storage/adapters/s3CompatibleStorage.ts b/src/storage/adapters/s3CompatibleStorage.ts index f1d6f747..141f3a78 100644 --- a/src/storage/adapters/s3CompatibleStorage.ts +++ b/src/storage/adapters/s3CompatibleStorage.ts @@ -2137,6 +2137,12 @@ export class S3CompatibleStorage extends BaseStorage { this.commitLog = undefined this.cowEnabled = false + // v5.10.4: Create persistent marker object (CRITICAL FIX) + // Bug: cowEnabled = false only affects current instance, not future instances + // Fix: Create marker object that persists across instance restarts + // When new instance calls initializeCOW(), it checks for this marker + await this.createClearMarker() + // Clear the statistics cache this.statisticsCache = null this.statisticsModified = false @@ -2261,6 +2267,62 @@ export class S3CompatibleStorage extends BaseStorage { } } + /** + * Check if COW has been explicitly disabled via clear() + * v5.10.4: Fixes bug where clear() doesn't persist across instance restarts + * @returns true if marker object exists, false otherwise + * @protected + */ + protected async checkClearMarker(): Promise { + await this.ensureInitialized() + + try { + const { HeadObjectCommand } = await import('@aws-sdk/client-s3') + const markerKey = `${this.systemPrefix}cow-disabled` + + await this.s3Client!.send( + new HeadObjectCommand({ + Bucket: this.bucketName, + Key: markerKey + }) + ) + return true // Marker exists + } catch (error: any) { + if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) { + return false // Marker doesn't exist + } + prodLog.warn('S3CompatibleStorage.checkClearMarker: Error checking marker', error) + return false + } + } + + /** + * Create marker indicating COW has been explicitly disabled + * v5.10.4: Called by clear() to prevent COW reinitialization on new instances + * @protected + */ + protected async createClearMarker(): Promise { + await this.ensureInitialized() + + try { + const { PutObjectCommand } = await import('@aws-sdk/client-s3') + const markerKey = `${this.systemPrefix}cow-disabled` + + // Create empty marker object + await this.s3Client!.send( + new PutObjectCommand({ + Bucket: this.bucketName, + Key: markerKey, + Body: Buffer.from(''), + ContentType: 'text/plain' + }) + ) + } catch (error) { + prodLog.error('S3CompatibleStorage.createClearMarker: Failed to create marker object', error) + // Don't throw - marker creation failure shouldn't break clear() + } + } + // Batch update timer ID protected statisticsBatchUpdateTimerId: NodeJS.Timeout | null = null // Flag to indicate if statistics have been modified since last save diff --git a/src/storage/baseStorage.ts b/src/storage/baseStorage.ts index 19446eca..64f86235 100644 --- a/src/storage/baseStorage.ts +++ b/src/storage/baseStorage.ts @@ -300,6 +300,15 @@ export abstract class BaseStorage extends BaseStorageAdapter { branch?: string enableCompression?: boolean }): Promise { + // v5.10.4: Check for persistent marker file (CRITICAL FIX) + // Bug: Setting cowEnabled = false on OLD instance doesn't affect NEW instances + // Fix: Check storage for persistent marker created by clear() + // If marker exists, COW was explicitly disabled and should NOT reinitialize + const markerExists = await this.checkClearMarker() + if (markerExists) { + return // COW was disabled by clear() - don't recreate _cow/ directory + } + // v5.6.1: If COW was explicitly disabled (e.g., via clear()), don't reinitialize // This prevents automatic recreation of COW data after clear() operations if (this.cowEnabled === false) { @@ -1704,6 +1713,23 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public abstract clear(): Promise + /** + * Check if COW has been explicitly disabled via clear() + * v5.10.4: Fixes bug where clear() doesn't persist across instance restarts + * Each adapter checks for a marker file/object (e.g., "_system/cow-disabled") + * @returns true if COW was disabled by clear(), false otherwise + * @protected + */ + protected abstract checkClearMarker(): Promise + + /** + * Create marker indicating COW has been explicitly disabled + * v5.10.4: Called by clear() to prevent COW reinitialization on new instances + * Each adapter creates a marker file/object (e.g., "_system/cow-disabled") + * @protected + */ + protected abstract createClearMarker(): Promise + /** * Get information about storage usage and capacity * This method should be implemented by each specific adapter diff --git a/tests/integration/clear-persistence.test.ts b/tests/integration/clear-persistence.test.ts new file mode 100644 index 00000000..b794de19 --- /dev/null +++ b/tests/integration/clear-persistence.test.ts @@ -0,0 +1,234 @@ +/** + * Integration tests for clear() bug fix (v5.10.4) + * + * Bug report: Workshop team reported that brain.clear() doesn't fully delete persistent storage. + * After calling clear() and creating a new Brainy instance, all data was restored from _cow/ directory. + * + * Root cause: Setting cowEnabled = false on old instance doesn't affect new instances. + * Fix: Create persistent marker file that survives instance restarts. + * + * These tests verify: + * 1. clear() actually deletes all data + * 2. New instances don't restore data from _cow/ + * 3. Marker file persists across restarts + * 4. Works for all storage adapters + */ + +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { Brainy } from '../../src/brainy.js' +import * as fs from 'fs' +import * as path from 'path' + +describe('Clear Persistence Bug Fix (v5.10.4)', () => { + const testStoragePath = './test-clear-persistence-' + Date.now() + + afterEach(async () => { + // Cleanup test storage + try { + if (fs.existsSync(testStoragePath)) { + await fs.promises.rm(testStoragePath, { recursive: true, force: true }) + } + } catch (error) { + console.warn('Failed to cleanup test storage:', error) + } + }) + + it('should fully clear persistent storage (Workshop scenario)', async () => { + // Step 1: Create and populate instance + const brain1 = new Brainy({ + storage: { + type: 'filesystem', + path: testStoragePath, + enableCompression: true + } + }) + await brain1.init() + + // Add data (using only entities, not VFS) + const entityId = await brain1.add({ data: 'Test Entity', type: 'concept' }) + + // Verify data exists (filter by type to exclude VFS directories) + const entityCount1 = (await brain1.find({ type: 'concept' })).length + expect(entityCount1).toBe(1) + + // Step 2: Clear all data + await brain1.clear() + + // Verify cleared in same instance (filter by type to exclude VFS directories) + const entityCount2 = (await brain1.find({ type: 'concept' })).length + expect(entityCount2).toBe(0) + + // Step 3: Create NEW instance (simulate server restart) + const brain2 = new Brainy({ + storage: { + type: 'filesystem', + path: testStoragePath, + enableCompression: true + } + }) + await brain2.init() + + // CRITICAL: Verify data is NOT restored (THE BUG) + const entityCount3 = (await brain2.find({ type: 'concept' })).length + + expect(entityCount3).toBe(0) // Should be 0, not 1 + }) + + it('should create cow-disabled marker file', async () => { + const brain = new Brainy({ + storage: { + type: 'filesystem', + path: testStoragePath + } + }) + await brain.init() + + // Add some data to ensure COW is initialized + await brain.add({ data: 'Test', type: 'concept' }) + + // Clear + await brain.clear() + + // Check for marker file + const markerPath = path.join(testStoragePath, '_system', 'cow-disabled') + const markerExists = fs.existsSync(markerPath) + expect(markerExists).toBe(true) + }) + + it('should delete _cow/ directory', async () => { + const brain = new Brainy({ + storage: { + type: 'filesystem', + path: testStoragePath + } + }) + await brain.init() + + // Add data to create COW commits + await brain.add({ data:'Test', type: 'concept' }) + + // Verify _cow/ exists + const cowPath = path.join(testStoragePath, '_cow') + const cowExistsBefore = fs.existsSync(cowPath) + expect(cowExistsBefore).toBe(true) + + // Clear + await brain.clear() + + // Verify _cow/ is deleted + const cowExistsAfter = fs.existsSync(cowPath) + expect(cowExistsAfter).toBe(false) + }) + + it('should prevent COW reinitialization after clear()', async () => { + // Create and clear + const brain1 = new Brainy({ + storage: { type: 'filesystem', path: testStoragePath } + }) + await brain1.init() + await brain1.add({ data:'Test', type: 'concept' }) + await brain1.clear() + + // Create new instance + const brain2 = new Brainy({ + storage: { type: 'filesystem', path: testStoragePath } + }) + await brain2.init() + + // Add new data - should NOT recreate _cow/ + // (COW stays disabled because marker exists) + await brain2.add({ data:'New Data', type: 'concept' }) + + // Verify _cow/ still doesn't exist + const cowPath = path.join(testStoragePath, '_cow') + const cowExists = fs.existsSync(cowPath) + expect(cowExists).toBe(false) + }) + + it('should work across multiple clear() calls', async () => { + const storagePath = testStoragePath + '-multi' + + try { + // Iteration 1 + const brain1 = new Brainy({ storage: { type: 'filesystem', path: storagePath }}) + await brain1.init() + await brain1.add({ data:'Entity 1', type: 'concept' }) + expect((await brain1.find({ type: 'concept' })).length).toBe(1) + await brain1.clear() + + // Iteration 2 + const brain2 = new Brainy({ storage: { type: 'filesystem', path: storagePath }}) + await brain2.init() + expect((await brain2.find({ type: 'concept' })).length).toBe(0) + await brain2.add({ data:'Entity 2', type: 'concept' }) + expect((await brain2.find({ type: 'concept' })).length).toBe(1) + await brain2.clear() + + // Iteration 3 + const brain3 = new Brainy({ storage: { type: 'filesystem', path: storagePath }}) + await brain3.init() + expect((await brain3.find({ type: 'concept' })).length).toBe(0) + } finally { + // Cleanup + if (fs.existsSync(storagePath)) { + await fs.promises.rm(storagePath, { recursive: true, force: true }) + } + } + }) + + it('should clear both entities and relations', async () => { + const brain1 = new Brainy({ storage: { type: 'filesystem', path: testStoragePath }}) + await brain1.init() + + // Add graph data + const entity1Id = await brain1.add({ data:'Entity 1', type: 'person' }) + const entity2Id = await brain1.add({ data:'Entity 2', type: 'concept' }) + await brain1.relate({ from: entity1Id, to: entity2Id, type: 'relatedTo' }) + + // Verify data exists + expect((await brain1.find({ type: 'person' })).length).toBe(1) + expect((await brain1.find({ type: 'concept' })).length).toBe(1) + expect((await brain1.getRelations({})).length).toBe(1) + + // Clear + await brain1.clear() + + // Create new instance + const brain2 = new Brainy({ storage: { type: 'filesystem', path: testStoragePath }}) + await brain2.init() + + // Verify everything is cleared + expect((await brain2.find({ type: 'person' })).length).toBe(0) + expect((await brain2.find({ type: 'concept' })).length).toBe(0) + expect((await brain2.getRelations({})).length).toBe(0) + }) + + it('should handle clear() on empty storage', async () => { + const brain = new Brainy({ storage: { type: 'filesystem', path: testStoragePath }}) + await brain.init() + + // Clear without adding any data + await expect(brain.clear()).resolves.not.toThrow() + + // Should create marker even if no data was present + const markerPath = path.join(testStoragePath, '_system', 'cow-disabled') + const markerExists = fs.existsSync(markerPath) + expect(markerExists).toBe(true) + }) +}) + +describe('Clear() works for MemoryStorage', () => { + it('should clear memory storage completely', async () => { + const brain1 = new Brainy({ storage: { type: 'memory' }}) + await brain1.init() + + await brain1.add({ data:'Test', type: 'concept' }) + expect((await brain1.find({ type: 'concept' })).length).toBe(1) + + await brain1.clear() + expect((await brain1.find({ type: 'concept' })).length).toBe(0) + + // Note: MemoryStorage doesn't persist, so we can't test instance restart + // The marker methods are no-ops for MemoryStorage + }) +})