Storage-aware batching system prevents rate limiting issues on cloud storage (GCS, S3, R2, Azure). Replaces entity-by-entity creation with addMany()/relateMany() batch operations in ImportCoordinator. Separate read/write circuit breakers prevent read lockouts during write throttling. Each storage adapter auto-configures optimal batch sizes and delays. Fixes silent data loss and 30+ second lockouts on 1000+ row imports.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed critical bug where entity and relationship counts were not being tracked correctly
during add(), relate(), and import() operations. The root cause was a race condition where
count increment code tried to read metadata before it was saved to storage.
Core Fixes:
- Modified baseStorage.saveNounMetadata_internal to increment counts AFTER metadata is saved
- Modified baseStorage.saveVerbMetadata_internal to increment verb counts AFTER metadata is saved
- Added verb type to VerbMetadata to avoid circular dependency during count tracking
- Refactored verb count methods to prevent mutex deadlocks (synchronous base + async Safe wrapper)
Storage Adapter Cleanup:
- Removed broken count increment code from FileSystemStorage, GcsStorage, R2Storage, AzureBlobStorage
- Updated MemoryStorage comments to reflect centralized fix
- All count tracking now centralized in baseStorage (fixes ALL adapters automatically)
New Utilities:
- Added rebuildCounts utility to repair corrupted counts.json from actual storage data
- Added comprehensive integration tests for count synchronization across all operations
Verification:
- All 8 storage adapters verified (FileSystem, GCS, Memory, S3Compatible, R2, Azure, OPFS, TypeAware)
- All code paths verified (add, relate, import, batch, update, delete)
- 599 tests passing (no regressions)
- No deadlocks (tests complete in 6s vs 150s+)
Fixes#1 and #2 reported by Workshop team
Fixes critical bugs causing data loss after container restarts:
- Bug #1: GraphAdjacencyIndex rebuild now properly called
- Bug #2: Improved early return logic (checks actual storage data)
- Bug #4: HNSW index now has production-grade rebuild mechanism
New features:
- Production-grade HNSW rebuild() with O(N) restoration algorithm
- Unified IIndex interface for consistent lifecycle management
- Parallel index rebuilds (HNSW, Graph, Metadata in parallel)
- HNSW persistence methods across all 5 storage adapters
- Comprehensive integration tests with 9 test scenarios
Performance improvements:
- 20 entities: 8ms rebuild time
- Handles millions of entities via cursor-based pagination
- O(N) restoration vs O(N log N) rebuilding from scratch
All changes are production-ready with no mocks, stubs, or TODOs.
Fixes two critical production bugs in GCS storage adapter:
1. brain.find({ where: {...} }) returned empty array after restart
- Root cause: Counts only persisted every 10 operations
- If <10 entities added before restart, counts were lost
- After restart: totalNounCount = 0, causing empty results
2. brain.init() returned 0 entities after container restart
- Same root cause as bug #1
- Counts file never written for small datasets
- getStats() returned 0 despite data in GCS bucket
Changes:
- baseStorageAdapter.ts: Persist counts on EVERY operation (not every 10)
- incrementEntityCountSafe(): Now persists immediately
- decrementEntityCountSafe(): Now persists immediately
- incrementVerbCount(): Now persists immediately
- decrementVerbCount(): Now persists immediately
- gcsStorage.ts: Better error handling for count initialization
- initializeCounts(): Fail loudly on network/permission errors
- initializeCountsFromScan(): Throw on scan failures instead of silent fail
- Added recovery logic with bucket scan fallback
Impact: Critical for serverless/containerized deployments (Cloud Run, Fargate, Lambda)
where containers restart frequently. The basic write→restart→read scenario now works.
Fixed bug where getMetadataBatch() was reading from wrong directory:
- FileSystemStorage: Changed to use getNounMetadata() instead of getMetadata()
- OPFSStorage: Changed to use getNounMetadata() instead of getMetadata()
- MetadataIndex fallback: Fixed to use getNounMetadata()
- Added getNounMetadata() to StorageAdapter interface
This resolves 0% success rate during metadata index rebuild.
Also added comprehensive API documentation for return values and data field behavior.
- Removed countNouns() and countVerbs() from BaseStorageAdapter
- These methods would be dangerous with millions of entries
- We already have incremental statistics tracking via incrementStatistic()
- Statistics are maintained in cache and updated as items are added/removed
- Much more scalable than iterating through all items
The existing statistics system is the proper way to get counts:
- Uses incrementStatistic('noun'/'verb', service) on add
- Uses decrementStatistic() on delete
- Access via getStatistics() which returns cached counts
- No iteration through millions of items needed
- Add missing getVerbsWithPagination() method to FileSystemStorage
- Fixes verb retrieval returning empty arrays
- Add pagination method declarations to BaseStorageAdapter interface
- Support filtering by sourceId, targetId, verbType, and service
- Include metadata retrieval for each verb
Resolves issue where brain.getVerbs() returned empty array even after
successfully adding verbs with FileSystemStorage adapter.