fix: critical clear() data persistence regression (v5.10.4)

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 storage. This is a CRITICAL data integrity bug.

Root causes (3 bugs fixed):
1. **FileSystemStorage deleting wrong directory**: Data stored in branches/main/entities/
   but clear() was only deleting old pre-v5.4.0 structure (nouns/, verbs/, metadata/)
2. **COW reinitialization after clear()**: Setting cowEnabled=false on old instance
   doesn't affect new instances. Fixed with persistent marker file.
3. **Metadata index cache not cleared**: find() with type filters returned stale data
   after clear(). Fixed by recreating MetadataIndexManager.

Changes:
- FileSystemStorage: Clear branches/ directory (where data actually lives)
- All storage adapters: Add checkClearMarker()/createClearMarker() methods
- BaseStorage: Check for cow-disabled marker before initializing COW
- Brainy: Recreate metadataIndex after clear() to flush cached data
- Tests: Comprehensive regression suite (8 tests) to prevent recurrence

Fixes Workshop bug report: /media/dpsifr/storage/home/Projects/workshop/BRAINY_V5_10_2_CLEAR_BUG.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
David Snelling 2025-11-17 10:44:35 -08:00
parent 53420f6c9a
commit aba15638dc
11 changed files with 613 additions and 17 deletions

View file

@ -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<boolean> {
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<void> {
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