@soulcraft/brainy 5.10.0 β†’ 5.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,43 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [5.10.1](https://github.com/soulcraftlabs/brainy/compare/v5.10.0...v5.10.1) (2025-11-14)
6
+
7
+ ### 🚨 CRITICAL BUG FIX - Blob Integrity Regression
8
+
9
+ **v5.10.0 regressed the v5.7.2 blob integrity bug, causing 100% VFS file read failure. This hotfix restores functionality with defense-in-depth architecture.**
10
+
11
+ ### Bug Description
12
+ v5.10.0 reintroduced a critical bug where `BlobStorage.read()` was hashing wrapped binary data instead of unwrapped content, causing all blob integrity checks to fail:
13
+ - **Symptom**: `Blob integrity check failed: <hash>` errors on every VFS file read
14
+ - **Root Cause**: Missing defense-in-depth unwrap verification in `BlobStorage.read()`
15
+ - **Impact**: 100% failure rate for VFS file operations in Workshop application
16
+
17
+ ### The Fix (v5.10.1)
18
+ 1. **Defense-in-Depth Unwrapping**: Added unwrap verification in `BlobStorage.read()` before hash check
19
+ 2. **DRY Architecture**: Created `binaryDataCodec.ts` as single source of truth for wrap/unwrap logic
20
+ 3. **Metadata Unwrapping**: Fixed metadata parsing to handle wrapped format
21
+ 4. **Comprehensive Tests**: Added 3 regression tests using `TestWrappingAdapter`
22
+
23
+ ### Changes
24
+ - **NEW**: `src/storage/cow/binaryDataCodec.ts` - Single source of truth for binary data encoding/decoding
25
+ - **FIXED**: `src/storage/cow/BlobStorage.ts` - Unwraps data and metadata before verification (lines 314, 342)
26
+ - **REFACTORED**: `src/storage/baseStorage.ts` - Uses shared binaryDataCodec utilities (lines 332, 340)
27
+ - **ADDED**: `tests/helpers/TestWrappingAdapter.ts` - Real wrapping adapter for testing
28
+ - **ADDED**: 3 regression tests in `tests/unit/storage/cow/BlobStorage.test.ts`
29
+
30
+ ### Architecture Improvements
31
+ - βœ… **Defense-in-Depth**: Unwrap at BOTH adapter layer (v5.7.5) and blob layer (v5.10.1)
32
+ - βœ… **DRY Principle**: All wrap/unwrap operations use shared `binaryDataCodec.ts`
33
+ - βœ… **Works Across ALL 8 Storage Adapters**: FileSystem, Memory, S3, GCS, Azure, R2, OPFS, Historical
34
+ - βœ… **Prevents Future Regressions**: Real wrapping tests catch this bug class
35
+
36
+ ### Related Issues
37
+ - v5.7.2: Original blob integrity bug - hashed wrapper instead of content
38
+ - v5.7.5: First fix - added unwrap to COW adapter (necessary but insufficient)
39
+ - v5.10.0: Regression - missing defense-in-depth in BlobStorage layer
40
+ - v5.10.1: Complete fix - defense-in-depth + DRY architecture + comprehensive tests
41
+
5
42
  ### [5.9.0](https://github.com/soulcraftlabs/brainy/compare/v5.8.0...v5.9.0) (2025-11-14)
6
43
 
7
44
  - fix: resolve VFS tree corruption from blob errors (v5.8.0) (93d2d70)
@@ -10,6 +10,7 @@ import { getShardIdFromUuid } from './sharding.js';
10
10
  import { RefManager } from './cow/RefManager.js';
11
11
  import { BlobStorage } from './cow/BlobStorage.js';
12
12
  import { CommitLog } from './cow/CommitLog.js';
13
+ import { unwrapBinaryData, wrapBinaryData } from './cow/binaryDataCodec.js';
13
14
  import { prodLog } from '../utils/logger.js';
14
15
  // Clean directory structure (v4.7.2+)
15
16
  // All storage adapters use this consistent structure
@@ -234,32 +235,19 @@ export class BaseStorage extends BaseStorageAdapter {
234
235
  if (data === null) {
235
236
  return undefined;
236
237
  }
237
- // Convert to Buffer
238
- if (Buffer.isBuffer(data)) {
239
- return data;
240
- }
241
- // v5.7.5: Unwrap binary data stored as {_binary: true, data: "base64..."}
238
+ // v5.7.5/v5.10.1: Use shared binaryDataCodec utility (single source of truth)
239
+ // Unwraps binary data stored as {_binary: true, data: "base64..."}
242
240
  // Fixes "Blob integrity check failed" - hash must be calculated on original content
243
- if (data._binary && typeof data.data === 'string') {
244
- return Buffer.from(data.data, 'base64');
245
- }
246
- return Buffer.from(JSON.stringify(data));
241
+ return unwrapBinaryData(data);
247
242
  }
248
243
  catch (error) {
249
244
  return undefined;
250
245
  }
251
246
  },
252
247
  put: async (key, data) => {
253
- // Store as Buffer (for blob data) or parse JSON (for metadata)
254
- let obj;
255
- try {
256
- // Try to parse as JSON first (for metadata)
257
- obj = JSON.parse(data.toString());
258
- }
259
- catch {
260
- // Not JSON, store as binary (base64 encoded for JSON storage)
261
- obj = { _binary: true, data: data.toString('base64') };
262
- }
248
+ // v5.10.1: Use shared binaryDataCodec utility (single source of truth)
249
+ // Wraps binary data or parses JSON for storage
250
+ const obj = wrapBinaryData(data);
263
251
  await this.writeObjectToPath(`_cow/${key}`, obj);
264
252
  },
265
253
  delete: async (key) => {
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import { createHash } from 'crypto';
17
17
  import { NULL_HASH, isNullHash } from './constants.js';
18
+ import { unwrapBinaryData } from './binaryDataCodec.js';
18
19
  /**
19
20
  * State-of-the-art content-addressable blob storage
20
21
  *
@@ -207,7 +208,10 @@ export class BlobStorage {
207
208
  metadataBuffer = await this.adapter.get(`${tryPrefix}-meta:${hash}`);
208
209
  if (metadataBuffer) {
209
210
  prefix = tryPrefix;
210
- metadata = JSON.parse(metadataBuffer.toString());
211
+ // v5.10.1: Unwrap metadata before parsing (defense-in-depth)
212
+ // Metadata should be JSON, but adapter might return wrapped format
213
+ const unwrappedMetadata = unwrapBinaryData(metadataBuffer);
214
+ metadata = JSON.parse(unwrappedMetadata.toString());
211
215
  break;
212
216
  }
213
217
  }
@@ -227,15 +231,20 @@ export class BlobStorage {
227
231
  }
228
232
  finalData = await this.zstdDecompress(data);
229
233
  }
230
- // Verify hash (optional, expensive)
231
- if (!options.skipVerification && BlobStorage.hash(finalData) !== hash) {
234
+ // v5.10.1: Defense-in-depth unwrap (CRITICAL FIX for blob integrity regression)
235
+ // Even though COW adapter should unwrap (v5.7.5), verify it happened and re-unwrap if needed
236
+ // This prevents "Blob integrity check failed" errors if adapter returns wrapped data
237
+ // Uses shared binaryDataCodec utility (single source of truth for unwrap logic)
238
+ const unwrappedData = unwrapBinaryData(finalData);
239
+ // Verify hash (on unwrapped data)
240
+ if (!options.skipVerification && BlobStorage.hash(unwrappedData) !== hash) {
232
241
  throw new Error(`Blob integrity check failed: ${hash}`);
233
242
  }
234
243
  // Add to cache (only if not skipped)
235
244
  if (!options.skipCache) {
236
- this.addToCache(hash, finalData, metadata);
245
+ this.addToCache(hash, unwrappedData, metadata);
237
246
  }
238
- return finalData;
247
+ return unwrappedData;
239
248
  }
240
249
  /**
241
250
  * Check if blob exists
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Binary Data Codec: Single Source of Truth for Wrap/Unwrap Operations
3
+ *
4
+ * This module provides the ONLY implementation of binary data encoding/decoding
5
+ * used across all storage adapters and blob storage.
6
+ *
7
+ * Design Principles:
8
+ * - DRY: One implementation, used everywhere
9
+ * - Single Responsibility: Only handles binary ↔ JSON conversion
10
+ * - Type-Safe: Proper TypeScript types
11
+ * - Defensive: Handles all edge cases
12
+ *
13
+ * Used by:
14
+ * - BaseStorage COW adapter (write/read operations)
15
+ * - BlobStorage (defense-in-depth verification)
16
+ * - All storage adapters (via BaseStorage)
17
+ *
18
+ * @module storage/cow/binaryDataCodec
19
+ */
20
+ /**
21
+ * Wrapped binary data format
22
+ * Used when storing binary data in JSON-based storage
23
+ */
24
+ export interface WrappedBinaryData {
25
+ _binary: true;
26
+ data: string;
27
+ }
28
+ /**
29
+ * Check if data is wrapped binary format
30
+ */
31
+ export declare function isWrappedBinary(data: any): data is WrappedBinaryData;
32
+ /**
33
+ * Unwrap binary data from JSON wrapper
34
+ *
35
+ * This is the SINGLE SOURCE OF TRUTH for unwrapping binary data.
36
+ * All storage operations MUST use this function.
37
+ *
38
+ * Handles:
39
+ * - Buffer β†’ Buffer (pass-through)
40
+ * - {_binary: true, data: "base64..."} β†’ Buffer (unwrap)
41
+ * - Plain object β†’ Buffer (JSON stringify)
42
+ * - Other types β†’ Error
43
+ *
44
+ * @param data - Data to unwrap (may be Buffer, wrapped object, or plain object)
45
+ * @returns Unwrapped Buffer
46
+ * @throws Error if data type is invalid
47
+ */
48
+ export declare function unwrapBinaryData(data: any): Buffer;
49
+ /**
50
+ * Wrap binary data for JSON storage
51
+ *
52
+ * This is the SINGLE SOURCE OF TRUTH for wrapping binary data.
53
+ * All storage operations MUST use this function.
54
+ *
55
+ * @param data - Buffer to wrap
56
+ * @returns Wrapped object or parsed JSON object
57
+ */
58
+ export declare function wrapBinaryData(data: Buffer): any;
59
+ /**
60
+ * Ensure data is a Buffer
61
+ *
62
+ * Convenience function that combines type checking and unwrapping.
63
+ * Use this when you need to ensure you have a Buffer.
64
+ *
65
+ * @param data - Data that should be or can be converted to Buffer
66
+ * @returns Buffer
67
+ */
68
+ export declare function ensureBuffer(data: any): Buffer;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Binary Data Codec: Single Source of Truth for Wrap/Unwrap Operations
3
+ *
4
+ * This module provides the ONLY implementation of binary data encoding/decoding
5
+ * used across all storage adapters and blob storage.
6
+ *
7
+ * Design Principles:
8
+ * - DRY: One implementation, used everywhere
9
+ * - Single Responsibility: Only handles binary ↔ JSON conversion
10
+ * - Type-Safe: Proper TypeScript types
11
+ * - Defensive: Handles all edge cases
12
+ *
13
+ * Used by:
14
+ * - BaseStorage COW adapter (write/read operations)
15
+ * - BlobStorage (defense-in-depth verification)
16
+ * - All storage adapters (via BaseStorage)
17
+ *
18
+ * @module storage/cow/binaryDataCodec
19
+ */
20
+ /**
21
+ * Check if data is wrapped binary format
22
+ */
23
+ export function isWrappedBinary(data) {
24
+ return (typeof data === 'object' &&
25
+ data !== null &&
26
+ data._binary === true &&
27
+ typeof data.data === 'string');
28
+ }
29
+ /**
30
+ * Unwrap binary data from JSON wrapper
31
+ *
32
+ * This is the SINGLE SOURCE OF TRUTH for unwrapping binary data.
33
+ * All storage operations MUST use this function.
34
+ *
35
+ * Handles:
36
+ * - Buffer β†’ Buffer (pass-through)
37
+ * - {_binary: true, data: "base64..."} β†’ Buffer (unwrap)
38
+ * - Plain object β†’ Buffer (JSON stringify)
39
+ * - Other types β†’ Error
40
+ *
41
+ * @param data - Data to unwrap (may be Buffer, wrapped object, or plain object)
42
+ * @returns Unwrapped Buffer
43
+ * @throws Error if data type is invalid
44
+ */
45
+ export function unwrapBinaryData(data) {
46
+ // Case 1: Already a Buffer (no unwrapping needed)
47
+ if (Buffer.isBuffer(data)) {
48
+ return data;
49
+ }
50
+ // Case 2: Wrapped binary data {_binary: true, data: "base64..."}
51
+ if (isWrappedBinary(data)) {
52
+ return Buffer.from(data.data, 'base64');
53
+ }
54
+ // Case 3: Plain object (shouldn't happen for binary blobs, but handle gracefully)
55
+ if (typeof data === 'object' && data !== null) {
56
+ return Buffer.from(JSON.stringify(data));
57
+ }
58
+ // Case 4: String (convert to Buffer)
59
+ if (typeof data === 'string') {
60
+ return Buffer.from(data);
61
+ }
62
+ // Case 5: Invalid type
63
+ throw new Error(`Invalid data type for unwrap: ${typeof data}. ` +
64
+ `Expected Buffer or {_binary: true, data: "base64..."}`);
65
+ }
66
+ /**
67
+ * Wrap binary data for JSON storage
68
+ *
69
+ * This is the SINGLE SOURCE OF TRUTH for wrapping binary data.
70
+ * All storage operations MUST use this function.
71
+ *
72
+ * @param data - Buffer to wrap
73
+ * @returns Wrapped object or parsed JSON object
74
+ */
75
+ export function wrapBinaryData(data) {
76
+ // Try to parse as JSON first (for metadata, trees, commits)
77
+ try {
78
+ return JSON.parse(data.toString());
79
+ }
80
+ catch {
81
+ // Not JSON - wrap as binary data
82
+ return {
83
+ _binary: true,
84
+ data: data.toString('base64')
85
+ };
86
+ }
87
+ }
88
+ /**
89
+ * Ensure data is a Buffer
90
+ *
91
+ * Convenience function that combines type checking and unwrapping.
92
+ * Use this when you need to ensure you have a Buffer.
93
+ *
94
+ * @param data - Data that should be or can be converted to Buffer
95
+ * @returns Buffer
96
+ */
97
+ export function ensureBuffer(data) {
98
+ if (Buffer.isBuffer(data)) {
99
+ return data;
100
+ }
101
+ return unwrapBinaryData(data);
102
+ }
103
+ //# sourceMappingURL=binaryDataCodec.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "5.10.0",
3
+ "version": "5.10.1",
4
4
  "description": "Universal Knowledge Protocolβ„’ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. Stage 3 CANONICAL: 42 nouns Γ— 127 verbs covering 96-97% of all human knowledge.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",