BREAKING CHANGE: Remove hard delete option from deleteVerb() for consistent API - Add complete metadata namespace architecture with O(1) soft delete performance - Implement periodic cleanup system for old soft-deleted items - Add restore methods for both nouns and verbs - Require metadata contracts for all augmentations - Eliminate namespace collisions with clean separation (_brainy, _augmentations, _audit) - Optimize index performance using flattened dot-notation for O(1) lookups - Add comprehensive augmentation safety system with type-safe access control - Maintain full backward compatibility for existing data - Add enterprise-grade cleanup with configurable age thresholds and batch processing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
7.7 KiB
7.7 KiB
Brainy 2.2 Implementation Plan: Metadata Architecture & Performance
Goal
Implement namespaced metadata with O(1) soft delete filtering while maintaining backward compatibility and adding augmentation safety.
Current Issues to Fix
- ❌
deleted: { notEquals: true }is O(n) - kills performance - ❌ Namespace collisions between user and internal metadata
- ❌ No safety for augmentation metadata modifications
- ❌ Progressive search bug when field filter returns 0 results
- ❌ Inconsistent metadata handling across CRUD operations
Architecture Decisions
1. Metadata Structure
{
// User data - untouched
name: "Django",
type: "framework",
// Internal namespace - indexed for O(1)
_brainy: {
deleted: false, // ALWAYS set, enables O(1) lookup
version: 1, // Schema version
created: Date.now(), // Creation timestamp
updated: Date.now(), // Last update
indexed: true // Whether in search index
},
// Augmentation data - isolated
_augmentations: {
[augName]: { ... }
},
// Audit trail - optional
_audit: []
}
2. Performance Strategy
Soft Delete: O(1) Hash Lookup
// Before: O(n) disaster
where: { deleted: { notEquals: true } } // Scans ALL items!
// After: O(1) perfection
where: { '_brainy.deleted': false } // Direct hash lookup!
Index Structure
MetadataIndex:
"_brainy.deleted" -> {
"false": Set<id1, id2, id3...>, // O(1) lookup
"true": Set<id4, id5...> // Small set
}
Implementation Steps
Phase 1: Core Infrastructure (CRITICAL)
1.1 Update Metadata Utilities
// src/utils/metadataNamespace.ts
export const BRAINY_NS = '_brainy'
export const AUG_NS = '_augmentations'
export const AUDIT_NS = '_audit'
export interface BrainyMetadata {
deleted: boolean
version: number
created: number
updated: number
indexed: boolean
}
export function ensureBrainyMetadata(metadata: any): any {
if (!metadata) metadata = {}
if (!metadata[BRAINY_NS]) {
metadata[BRAINY_NS] = {
deleted: false, // ALWAYS set
version: 1,
created: Date.now(),
updated: Date.now(),
indexed: true
}
} else {
// Ensure deleted is always boolean
if (metadata[BRAINY_NS].deleted === undefined) {
metadata[BRAINY_NS].deleted = false
}
}
return metadata
}
1.2 Fix MetadataIndex for Nested Fields
// Already supports dot notation!
// Just ensure we index '_brainy.deleted' as a key
Phase 2: CRUD Operations Update
2.1 AddNoun - Set metadata correctly
async addNoun(data: any, metadata?: any): Promise<string> {
// Ensure namespaced metadata
metadata = ensureBrainyMetadata(metadata)
// ... existing logic ...
// Save with guaranteed structure
await this.storage.saveMetadata(id, metadata)
// Index the nested field
await this.metadataIndex.addToIndex(id, metadata)
}
2.2 UpdateNoun - Preserve namespaces
async updateNoun(id: string, data?: any, metadata?: any): Promise<any> {
const existing = await this.getNoun(id)
// Merge carefully, preserving namespaces
const merged = {
...existing.metadata,
...metadata,
[BRAINY_NS]: {
...existing.metadata?.[BRAINY_NS],
...metadata?.[BRAINY_NS],
updated: Date.now()
}
}
// Ensure deleted field exists
merged[BRAINY_NS].deleted = merged[BRAINY_NS].deleted ?? false
await this.storage.saveMetadata(id, merged)
}
2.3 Soft Delete Operations
async softDelete(id: string): Promise<boolean> {
const noun = await this.getNoun(id)
if (!noun) return false
// Just flip the boolean - O(1) operation!
noun.metadata[BRAINY_NS].deleted = true
await this.storage.saveMetadata(id, noun.metadata)
// Update index - also O(1)
await this.metadataIndex.updateIndex(id,
'_brainy.deleted', false, true)
return true
}
async restore(id: string): Promise<boolean> {
const noun = await this.getNoun(id)
if (!noun) return false
noun.metadata[BRAINY_NS].deleted = false
await this.storage.saveMetadata(id, noun.metadata)
await this.metadataIndex.updateIndex(id,
'_brainy.deleted', true, false)
return true
}
Phase 3: Search & Query Updates
3.1 Find Method - Use correct field
async find(query: TripleQuery, options: FindOptions): Promise<any[]> {
const { excludeDeleted = true } = options
if (excludeDeleted) {
if (!query.where) query.where = {}
// Use indexed nested field for O(1) lookup
query.where['_brainy.deleted'] = false
}
return this._tripleEngine.find(query)
}
3.2 Progressive Search Fix (Already Done ✅)
// Fixed: Don't do fresh search when previous step returns 0
case 'vector':
if (candidates.length === 0 && plan.steps[0].type !== 'vector') {
// Keep empty candidates, don't search
break
}
Phase 4: Augmentation Safety Integration
4.1 Augmentation Pipeline Update
class AugmentationPipeline {
private enforcer = new MetadataSafetyEnforcer()
async execute(operation: string, params: any, fn: Function) {
// Get augmentation contract
const contract = this.getContract(operation)
if (contract && params.metadata) {
// Wrap metadata in safety proxy
params.metadata = this.enforcer.createSafeProxy(
params.metadata,
contract.name
)
}
const result = await fn()
// Audit trail
if (params.metadata?._audit) {
await this.saveAuditLog(params.metadata._audit)
}
return result
}
}
Phase 5: Migration & Compatibility
5.1 Lazy Migration
async getNoun(id: string): Promise<any> {
const noun = await this.storage.getNoun(id)
// Migrate on read if needed
if (noun && !noun.metadata?.[BRAINY_NS]) {
noun.metadata = ensureBrainyMetadata(noun.metadata)
await this.storage.saveMetadata(id, noun.metadata)
}
return noun
}
5.2 Batch Migration Utility
async migrateToBrainyNamespace(): Promise<void> {
const batch = 1000
let offset = 0
while (true) {
const nouns = await this.storage.getNouns({
limit: batch,
offset
})
if (nouns.length === 0) break
for (const noun of nouns) {
if (!noun.metadata?.[BRAINY_NS]) {
noun.metadata = ensureBrainyMetadata(noun.metadata)
await this.storage.saveMetadata(noun.id, noun.metadata)
}
}
offset += batch
}
}
Performance Guarantees
| Operation | Before | After | Method |
|---|---|---|---|
| Soft delete filter | O(n) ❌ | O(1) ✅ | Hash index on _brainy.deleted |
| Metadata exact match | O(1) ✅ | O(1) ✅ | Hash index |
| Range query | O(log n) ✅ | O(log n) ✅ | Sorted index |
| Nested field query | O(1) ✅ | O(1) ✅ | Dot notation indexing |
| Vector search | O(log n) ✅ | O(log n) ✅ | HNSW |
Testing Strategy
Unit Tests
- Namespace preservation in CRUD
- Soft delete O(1) performance
- Augmentation safety enforcement
- Migration utilities
Integration Tests
- Combined vector + metadata queries
- Multiple augmentation conflicts
- Backward compatibility
Performance Tests
- 1M items soft delete filter < 10ms
- Nested field queries < 5ms
- No performance regression
Rollout Plan
- v2.2.0-beta: Core namespace implementation
- v2.2.0-rc: Augmentation safety system
- v2.2.0: Full release with migration tools
Risk Mitigation
- Backward Compatibility: Support both old and new formats
- Data Loss: Comprehensive testing before release
- Performance Regression: Benchmark every change
- Migration Issues: Provide rollback mechanism