@soulcraft/cor 3.0.2 → 3.0.4

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.
@@ -470,7 +470,7 @@ export class NativeDiskAnnWrapper {
470
470
  */
471
471
  async hydrateDeltaFromStorage(dim) {
472
472
  // Best-effort: only when the storage adapter supports bulk noun reads
473
- // (production FileSystemStorage/MmapFileSystemStorage do; some partial
473
+ // (production brainy FileSystemStorage does; some partial
474
474
  // adapters used in direct-construction tests do not). When absent, the
475
475
  // caller falls back to the in-memory rebuild.
476
476
  if (typeof this.storage?.getNouns !== 'function')
@@ -35,7 +35,8 @@ import type { ColumnStoreProvider, EntityIdMapperLike } from './columnStoreTypes
35
35
  import { ValueType } from './columnStoreTypes.js';
36
36
  import { RoaringBitmap32 } from '../native/NativeRoaringBitmap32.js';
37
37
  /**
38
- * @description The subset of cor's `MmapFileSystemStorage` that the column
38
+ * @description The subset of brainy's `FileSystemStorage` (whose index files cor
39
+ * memory-maps) that the column
39
40
  * store needs for billion-scale raw segment persistence. cor builds against
40
41
  * published `@soulcraft/brainy`, whose `StorageAdapter` type does not yet
41
42
  * declare these binary-blob methods, so we narrow the adapter to this local
@@ -130,7 +130,7 @@ export class NativeColumnStore {
130
130
  if (!isBinaryBlobStorage(storage)) {
131
131
  throw new Error('NativeColumnStore requires a storage adapter with binary-blob support ' +
132
132
  '(saveBinaryBlob/loadBinaryBlob/deleteBinaryBlob/getBinaryBlobPath). ' +
133
- 'Use cor MmapFileSystemStorage or another adapter implementing BinaryBlobStorage.');
133
+ 'Use brainy FileSystemStorage or another adapter implementing BinaryBlobStorage.');
134
134
  }
135
135
  this.storage = storage;
136
136
  // Discover fields by listing manifest files under the column-index prefix.
@@ -405,13 +405,31 @@ export class MetadataIndexManager {
405
405
  // Resolve the metadata directory for the persistent engine, mirroring
406
406
  // wireIdMapper's filesystem guard. `getBinaryBlobPath` returns null on
407
407
  // non-local adapters → fall back to the in-RAM engine.
408
- const metaDir = typeof blobStorage.getBinaryBlobPath === 'function'
409
- ? blobStorage.getBinaryBlobPath('_metadata')
410
- : null;
408
+ const getBlobPath = blobStorage.getBinaryBlobPath;
409
+ const hasBlobPath = typeof getBlobPath === 'function';
410
+ const metaDir = hasBlobPath ? getBlobPath.call(blobStorage, '_metadata') : null;
411
411
  if (metaDir === null) {
412
412
  // Fallback: non-persistent in-RAM engine (the legacy `new` path).
413
413
  // U64 IdSpace matches the cor 3.0 production default + the metadata
414
414
  // index core constructed above.
415
+ //
416
+ // Two very different situations land here — do NOT let the durable one
417
+ // pass silently (the invisible-degrade the whole GA bar forbids):
418
+ // - method ABSENT → a genuinely non-local adapter (cloud/in-memory);
419
+ // an in-RAM metadata engine is correct. Quiet.
420
+ // - method PRESENT but returned null for '_metadata' → a filesystem-
421
+ // class adapter that should have yielded a path but didn't. The
422
+ // metadata index will run WITHOUT on-disk durability — every restart
423
+ // rebuilds from canonical (the 48s-per-boot class). Announce it LOUD
424
+ // so an operator sees it instead of silently paying the cost forever.
425
+ if (hasBlobPath) {
426
+ prodLog.error('[NativeMetadataIndex] getBinaryBlobPath("_metadata") returned null on ' +
427
+ 'an adapter that implements it — the metadata LSM is running IN-RAM, ' +
428
+ 'NOT persisted. Every restart will rebuild the index from canonical ' +
429
+ '(slow cold opens). This is a durability misconfiguration, not normal ' +
430
+ 'operation — check the storage adapter/path. Reachable durable state is ' +
431
+ 'the "restart = warm" contract.');
432
+ }
415
433
  this.lsmEngine = new bindings.NativeLsmEngine({ idSpace: 'u64' });
416
434
  this.lsmManifest = null;
417
435
  this.lsmMetaDir = null;
@@ -1040,6 +1058,30 @@ export class MetadataIndexManager {
1040
1058
  'NO rebuild.');
1041
1059
  return;
1042
1060
  }
1061
+ // **Tripwire — a rebuild here on a PREVIOUSLY-HEALTHY brain is a bug,
1062
+ // not routine.** A rebuild is legitimate exactly twice: a fresh brain
1063
+ // (no marker) and a 7.x→8.0 upgrade (stale marker) — both have no
1064
+ // durable derived state yet. But if the on-disk brain-format marker is
1065
+ // present AND current, an 8.x/3.x already fully built and STAMPED this
1066
+ // brain's indexes; their absence now means the field registry + LSM
1067
+ // both failed to persist/reload (the "restart = milliseconds" contract
1068
+ // broken — every boot pays an O(N) rebuild). That must never be
1069
+ // silent: surface it at ERROR with the entity count so it's caught the
1070
+ // first time, not after a customer measures a 48s boot.
1071
+ try {
1072
+ const marker = await readOnDiskBrainFormat(this.storage);
1073
+ if (!isEpochStale(marker)) {
1074
+ prodLog.error(`[NativeMetadataIndex] DURABILITY TRIPWIRE: rebuilding ${probe.totalCount ?? 'unknown'} ` +
1075
+ 'entities on a brain whose format marker is present + current — the ' +
1076
+ 'derived indexes (field registry AND LSM) should already be on disk. ' +
1077
+ 'This is an unexpected full rebuild on a previously-healthy brain ' +
1078
+ '(the every-boot-rebuild class); please report it with the brain layout. ' +
1079
+ 'Data is safe (canonical entities intact); only startup time is affected.');
1080
+ }
1081
+ }
1082
+ catch {
1083
+ // marker read failed — fall through to the normal rebuild path
1084
+ }
1043
1085
  console.warn(`[NativeMetadataIndex] Field registry missing but ${probe.totalCount ?? 'unknown'} entities exist — rebuilding index`);
1044
1086
  await this.rebuild();
1045
1087
  return;
@@ -80,7 +80,7 @@ This means semantic time travel is a **brainy feature** that cor accelerates —
80
80
  **Guarantee (verified, 8.0.0-rc.2 + cor 3.0):** `db.asOf(g).find({ vector, where })` is set-correct, rank-correct (at-g embedding), and filter-correct (at-g metadata) — **for mutations committed via `brain.transact([...])`**.
81
81
 
82
82
  **Limits — all four are real; do not claim past them:**
83
- 1. **`transact()`-only in the shipped brainy rc.2.** Single-op `brain.add()`/`brain.remove()` writes bump the generation counter but do **not** retain a before-image or register the generation (`generationStore.js:178 noteSingleOpWrite`), so `asOf(g)` silently resolves to **current** state for them. Time-travel-correct history requires `transact()`. Brainy's rc.3 Model-B is slated to retain single-op writes; the guard test (`semanticAsOf.test.ts`) flips red when it lands. (Handoff: IMMUTABILITY-TIMETRAVEL thread.)
83
+ 1. ~~**`transact()`-only.**~~ **RESOLVED before GA** brainy's Model-B landed in 8.0.0-rc.3 (`5c3bb2c`): single-op `add()`/`remove()` writes ARE retained (per-write `persistSingleOp`/`commitSingleOp` generation-stamping), so `asOf(g)` is history-correct for BOTH `transact([...])` and single-op mutations. Regression-pinned in `src/native/semanticAsOf.test.ts` (runs green in the release gate vs the published 8.0.11). The rc.2-era gap this bullet used to describe no longer exists in any shipped 8.x.
84
84
  2. **The historical leg is brainy-JS-served this release.** `asOf(g)` builds an ephemeral reader brain and rebuilds a JS HNSW from the at-g vectors; cor's native vector index (`NativeDiskAnnWrapper`) is **not** on the historical path. cor accelerates only the live (`now`) leg in 3.0. (Open-core: brainy-alone is correct via JS rerank.)
85
85
  3. **O(n-at-g) rebuild per `asOf` Db — small/medium scale only.** One `addItem` per at-g vector into a fresh JS HNSW; this OOMs at billion-scale. Billion-scale historical semantic find is **Phase 1–2** (cor native rerank / overlay / tiered ANN; #35). Phase 0 is correctness, not scale.
86
86
  4. **No embedding-model-drift guard yet.** Comparing a query embedded with model vN against stored vectors from model vM is silently wrong across a model upgrade — detection lands in Phase 1, alignment in Phase 3.
@@ -355,7 +355,7 @@ Memory is the canonical first 3.0 consumer (production on Brainy 7.31.6 + Cortex
355
355
 
356
356
  **Memory-specific gotchas:**
357
357
 
358
- - **`mmap-filesystem` storage adapter** is filesystem-backed via Brainy's `MmapFileSystemStorage` — cor 3.0's DiskANN auto-engagement WILL fire here, which is what you want.
358
+ - **Storage** is Brainy's standard `filesystem` adapter (there is no separate "mmap" storage class to configure — cor memory-maps its own index files under whatever filesystem path you give Brainy). cor 3.0's DiskANN auto-engagement WILL fire here, which is what you want.
359
359
  - **Per-user brain cold start** was 8.3 s on Brainy 7.31.3 + Cortex 2.7.0 (per the BRAINY-COR-MMAP-VECTOR-NOT-WIRED thread). On 8.0 + 3.0 with DiskANN + the cor shadow-page LSM, cold start should drop substantially — Adaptive DiskANN auto-selects `in-memory` mode at the per-user scale (each user is currently ≤ 1 K memories), giving sub-millisecond reads with no warm-up. Re-measure after upgrade and update the production-impact section of that thread.
360
360
  - **`db.asOf(g)` / `db.with(...)`** time-travel reads are now available — if Memory wants to surface "what did this user remember last week", that's a feature you can build on top of brainy 8.0's historical query surface without any cor-side work.
361
361
  - **No external Memory user has 1 M+ memories yet**, so the migration window is operationally cheap; do it during the morning low-traffic window per current ops practice.
package/docs/scaling.md CHANGED
@@ -104,9 +104,11 @@ are evicted before small active ones).
104
104
 
105
105
  ### Memory-mapped storage
106
106
 
107
- Cor's `MmapFileSystemStorage` extends Brainy's filesystem storage with
108
- zero-copy binary I/O. The Rust native layer memory-maps data files via
109
- `memmap2`, letting the Linux kernel manage which pages stay in RAM.
107
+ You use Brainy's standard `filesystem` storage — there is no separate
108
+ storage class to choose. Cor's native engines memory-map their own index
109
+ files (vectors, graph adjacency, metadata LSM) via `memmap2`, letting the
110
+ Linux kernel manage which pages stay in RAM. Zero-copy binary I/O comes
111
+ from the native layer, not from a special adapter.
110
112
 
111
113
  What mmap provides:
112
114
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/cor",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "Native Rust acceleration for Brainy \u2014 SIMD distance, vector quantization, zero-copy mmap, native embeddings. Free tier for storage, Pro license for compute acceleration.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",