feat(v4.0.0): Complete metadata/vector separation architecture with Azure support

This commit completes the core v4.0.0 architecture changes for billion-scale
performance with metadata/vector separation. NO RELEASE YET - remaining optimizations
and testing required before production release.

## Core v4.0.0 Architecture Changes

### Type System Updates
- Fixed all TypeScript compilation errors (zero errors achieved)
- Updated HNSWNoun/HNSWVerb to separate core fields from metadata
- Implemented HNSWNounWithMetadata/HNSWVerbWithMetadata for API boundaries
- Added required 'noun' field to NounMetadata for semantic structure
- Renamed verb.type to verb.verb for consistency

### Storage Adapter Updates
**All adapters updated for v4.0.0 two-file storage pattern:**
- memoryStorage: Proper metadata/vector separation
- fileSystemStorage: Two-file pattern with sharding
- opfsStorage: Browser persistent storage updated
- s3CompatibleStorage: AWS/MinIO/DigitalOcean support
- r2Storage: Cloudflare R2 optimization
- gcsStorage: Google Cloud with ADC support
- **azureBlobStorage: NEW - Full Azure Blob Storage support**

### Storage Features
- BaseStorage: Internal vs public method separation (_getNoun vs getNoun)
- Two-file storage: Vectors in one file, metadata in another
- Change tracking: getChangesSince return type updated
- Pagination: getNounsWithPagination returns WithMetadata types

### Azure Blob Storage Integration (NEW)
- Native @azure/storage-blob SDK integration
- Four authentication methods:
  * DefaultAzureCredential (Managed Identity) - recommended
  * Connection String - simplest setup
  * Account Name + Key - traditional auth
  * SAS Token - delegated access
- High-volume mode with write buffering
- Adaptive backpressure for throttling
- UUID-based sharding for billion-scale
- Full HNSW support with graph persistence

### Utility Updates
- EmbeddingManager: Updated to accept Record<string, unknown>
- LSMTree: Wrapped data in NounMetadata structure with 'noun' field
- EntityIdMapper: Fixed nested metadata.data structure access
- MetadataIndex: Fixed field type inference integration
- PeriodicCleanup: Updated for new metadata structure

### Core API Updates
- Brainy: Updated verb property access from v.type to v.verb
- ConfigAPI: Fixed NounMetadata access patterns
- DataAPI: Updated metadata handling

### Documentation Updates
- CREATING-AUGMENTATIONS.md: v4.0.0 breaking changes guide
- DEVELOPER-GUIDE.md: Migration checklist and examples
- COMPLETE-REFERENCE.md: v4.0.0 architecture improvements
- **finite-type-system.md: NEW - Revolutionary type system benefits**

### Build & Dependencies
- Zero TypeScript compilation errors
- Added @azure/storage-blob and @azure/identity
- 591 tests passing (23 timeout in long-running neural tests)

## What's NOT in This Release
This is a work-in-progress commit. Before v4.0.0 release we need:
- Storage adapter optimizations (batch operations, compression)
- Azure blob tier management (Hot/Cool/Archive)
- Cost optimization implementations
- Additional performance testing at billion-scale
- Migration guides for v3.x users

## Testing
- Clean build: 
- Type checking:  (zero errors)
- Test suite:  (591/614 passing, timeouts in neural tests only)

🔐 Generated with Claude Code
https://claude.com/claude-code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
David Snelling 2025-10-17 12:29:27 -07:00
parent 8d6dd07e1d
commit 92c96246fb
35 changed files with 4524 additions and 1026 deletions

View file

@ -109,9 +109,10 @@ export class DistributedConfigManager {
const configData = await this.storage.getMetadata(LEGACY_CONFIG_KEY)
if (configData) {
// Migrate to new location
await this.migrateConfig(configData as SharedConfig)
this.lastConfigVersion = configData.version
return configData as SharedConfig
const config = configData as unknown as SharedConfig
await this.migrateConfig(config)
this.lastConfigVersion = config.version
return config
}
} catch (error) {
// Config doesn't exist yet
@ -214,21 +215,22 @@ export class DistributedConfigManager {
const legacyConfig = await this.storage.getMetadata(LEGACY_CONFIG_KEY)
if (legacyConfig) {
console.log('Migrating distributed config from legacy location to index folder...')
const config = legacyConfig as unknown as SharedConfig
// Save to new location
await this.migrateConfig(legacyConfig as SharedConfig)
await this.migrateConfig(config)
// Delete from old location (optional - we can keep it for rollback)
// await this.storage.deleteMetadata(LEGACY_CONFIG_KEY)
this.hasMigrated = true
this.lastConfigVersion = legacyConfig.version
return legacyConfig as SharedConfig
this.lastConfigVersion = config.version
return config
}
} catch (error) {
console.error('Error during config migration:', error)
}
this.hasMigrated = true
return null
}
@ -405,13 +407,13 @@ export class DistributedConfigManager {
if (stats && stats.distributedConfig) {
return stats.distributedConfig as SharedConfig
}
// Fallback to legacy location if not migrated yet
if (!this.hasMigrated) {
const configData = await this.storage.getMetadata(LEGACY_CONFIG_KEY)
if (configData) {
// Trigger migration on next save
return configData as SharedConfig
return configData as unknown as SharedConfig
}
}
} catch (error) {

View file

@ -304,6 +304,7 @@ export class ShardMigrationManager extends EventEmitter {
// Don't delete immediately in case of rollback
const cleanupKey = `cleanup:${shardId}:${Date.now()}`
await this.storage.saveMetadata(cleanupKey, {
noun: 'Document',
shardId,
scheduledFor: Date.now() + 3600000 // Delete after 1 hour
})
@ -330,12 +331,13 @@ export class ShardMigrationManager extends EventEmitter {
// Track progress
const progress = {
noun: 'Document',
migrationId: data.migrationId,
shardId: data.shardId,
received: data.offset + data.items.length,
total: data.total
}
await this.storage.saveMetadata(`migration:${data.migrationId}:progress`, progress)
}

View file

@ -238,7 +238,7 @@ export class StorageDiscovery extends EventEmitter {
// Remove ourselves from node registry
try {
// Mark as deleted rather than actually deleting
const deadNode = { ...this.nodeInfo, lastSeen: 0, status: 'inactive' as const }
const deadNode = { noun: 'Document', ...this.nodeInfo, lastSeen: 0, status: 'inactive' as const }
await this.storage.saveMetadata(`${this.CLUSTER_PATH}/nodes/${this.nodeId}.json`, deadNode)
} catch (err) {
// Ignore errors during shutdown
@ -258,8 +258,8 @@ export class StorageDiscovery extends EventEmitter {
*/
private async registerNode(): Promise<void> {
const path = `${this.CLUSTER_PATH}/nodes/${this.nodeId}.json`
await this.storage.saveMetadata(path, this.nodeInfo)
await this.storage.saveMetadata(path, { noun: 'Document', ...this.nodeInfo })
// Also update registry
await this.updateNodeRegistry(this.nodeId)
}
@ -318,10 +318,11 @@ export class StorageDiscovery extends EventEmitter {
if (nodeId === this.nodeId) continue
try {
const nodeInfo = await this.storage.getMetadata(
const nodeInfoData = await this.storage.getMetadata(
`${this.CLUSTER_PATH}/nodes/${nodeId}.json`
) as NodeInfo
)
const nodeInfo = nodeInfoData as unknown as NodeInfo
// Check if node is alive
if (now - nodeInfo.lastSeen < this.NODE_TIMEOUT) {
if (!this.clusterConfig!.nodes[nodeId]) {
@ -369,16 +370,17 @@ export class StorageDiscovery extends EventEmitter {
private async updateNodeRegistry(add?: string, remove?: string): Promise<void> {
try {
let registry = await this.loadNodeRegistry()
if (add && !registry.includes(add)) {
registry.push(add)
}
if (remove) {
registry = registry.filter(id => id !== remove)
}
await this.storage.saveMetadata(`${this.CLUSTER_PATH}/registry.json`, {
noun: 'Document',
nodes: registry,
updated: Date.now()
})
@ -428,7 +430,7 @@ export class StorageDiscovery extends EventEmitter {
private async loadClusterConfig(): Promise<ClusterConfig | null> {
try {
const config = await this.storage.getMetadata(`${this.CLUSTER_PATH}/config.json`)
return config as ClusterConfig
return config as unknown as ClusterConfig
} catch (err) {
// No cluster config exists yet
return null
@ -440,10 +442,10 @@ export class StorageDiscovery extends EventEmitter {
*/
private async saveClusterConfig(): Promise<void> {
if (!this.clusterConfig) return
await this.storage.saveMetadata(
`${this.CLUSTER_PATH}/config.json`,
this.clusterConfig
{ noun: 'Document', ...this.clusterConfig }
)
}