Introduce a first-class binary-blob storage primitive on the StorageAdapter
contract and implement it across all storage backends. This stores opaque byte
payloads verbatim instead of base64-in-JSON, eliminating the ~33% inflation and
full-materialization cost of the JSON envelope. It unblocks zero-copy,
mmap-able column-store segments and batch vector I/O at billion scale.
New methods (declared abstract on BaseStorageAdapter, the class that implements
StorageAdapter, and added to the StorageAdapter interface):
saveBinaryBlob(key, data) raw write, atomic on real filesystems
loadBinaryBlob(key) exact bytes, or null if absent
deleteBinaryBlob(key) idempotent (missing is ignored)
getBinaryBlobPath(key) real local fs path where one exists, else null
Shared key -> location convention across every adapter: the key's
"/"-separated segments nest under a `_blobs/` prefix and are suffixed with
`.bin`, e.g. "graph-lsm/source/sstable-123" ->
"<root>/_blobs/graph-lsm/source/sstable-123.bin". Blobs are not branch-scoped
(COW): they are immutable producer-managed segments.
Per-adapter behavior:
- FileSystemStorage: writes under <rootDir>/_blobs via tmp+rename; returns the
real on-disk path so native code can mmap it directly. Path convention matches
the existing MmapFileSystemStorage subclass byte-for-byte.
- S3CompatibleStorage / R2Storage / GcsStorage / AzureBlobStorage: put/get/delete
raw octet-stream objects; getBinaryBlobPath returns null (remote stores have no
local path).
- MemoryStorage: defensive-copied Map<string, Buffer>; null path; cleared on
clear().
- OPFSStorage: stores raw bytes in the OPFS tree; null path.
- HistoricalStorageAdapter: read-only — save/delete throw; load resolves the
blob from the historical commit tree; null path.
Tests: tests/unit/storage/binaryBlob.test.ts exercises save/load round-trip
(byte-identical, incl. non-UTF8 bytes), overwrite, delete-then-load, load-missing,
and getBinaryBlobPath behavior for all eight adapters. Cloud adapters run against
in-memory client fakes that drive the real adapter code; OPFS runs against an
in-memory FileSystem Access API mock; the historical adapter commits a blob into
a real COW tree. 59 new tests; full unit suite (1398 tests) green.
The __words__ keyword index stores 50-5000 entries per entity (one per
word), which inflated avg entries/entity well above the corruption
threshold of 100. This caused:
1. validateConsistency() to falsely detect corruption on every startup,
triggering unnecessary clearAllIndexData() + rebuild() cycles
2. getStats() to log false "Metadata index may be corrupted" warnings
and report inflated totalEntries/totalIds stats
Both methods now skip __words__ when counting, so stats and health
checks reflect metadata fields only (noun, type, createdAt, etc.).
Keyword search is unaffected since the __words__ field index itself
is not modified.
HistoricalStorageAdapter was looking for underscore-prefixed properties
(_commitLog, _blobStorage, _treeObject) but BaseStorage sets them without
underscores (commitLog, blobStorage). This caused all asOf() calls to fail.
Changes:
- Fix property access: use commitLog/blobStorage instead of _commitLog/_blobStorage
- Remove unused treeObject property (TreeObject is dynamically imported)
- Remove unused TreeObject static import
Reported by: Soulcraft Workshop Team
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>