@soulcraft/cor 3.0.0-rc.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/LICENSE +16 -0
- package/README.md +328 -0
- package/dist/aggregation/NativeAggregationEngine.d.ts +118 -0
- package/dist/aggregation/NativeAggregationEngine.js +186 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +236 -0
- package/dist/graph/GraphAccelerationAdapter.d.ts +153 -0
- package/dist/graph/GraphAccelerationAdapter.js +326 -0
- package/dist/graph/NativeGraphAdjacencyIndex.d.ts +286 -0
- package/dist/graph/NativeGraphAdjacencyIndex.js +944 -0
- package/dist/hnsw/AdaptiveDiskAnnModeSelector.d.ts +187 -0
- package/dist/hnsw/AdaptiveDiskAnnModeSelector.js +313 -0
- package/dist/hnsw/NativeDiskAnnWrapper.d.ts +489 -0
- package/dist/hnsw/NativeDiskAnnWrapper.js +1456 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +29 -0
- package/dist/legacyLayoutGuard.d.ts +93 -0
- package/dist/legacyLayoutGuard.js +237 -0
- package/dist/license/constants.d.ts +30 -0
- package/dist/license/constants.js +30 -0
- package/dist/license/onlineValidator.d.ts +43 -0
- package/dist/license/onlineValidator.js +225 -0
- package/dist/license/types.d.ts +39 -0
- package/dist/license/types.js +5 -0
- package/dist/license.d.ts +64 -0
- package/dist/license.js +165 -0
- package/dist/migration/MigrationCoordinator.d.ts +171 -0
- package/dist/migration/MigrationCoordinator.js +249 -0
- package/dist/migration/indexEpochGuard.d.ts +163 -0
- package/dist/migration/indexEpochGuard.js +202 -0
- package/dist/native/NativeEmbeddingEngine.d.ts +79 -0
- package/dist/native/NativeEmbeddingEngine.js +318 -0
- package/dist/native/NativeRoaringBitmap32.d.ts +114 -0
- package/dist/native/NativeRoaringBitmap32.js +221 -0
- package/dist/native/ffi.d.ts +20 -0
- package/dist/native/ffi.js +48 -0
- package/dist/native/idMapperTestSupport.d.ts +67 -0
- package/dist/native/idMapperTestSupport.js +112 -0
- package/dist/native/index.d.ts +49 -0
- package/dist/native/index.js +87 -0
- package/dist/native/napi.d.ts +21 -0
- package/dist/native/napi.js +88 -0
- package/dist/native/types.d.ts +1298 -0
- package/dist/native/types.js +16 -0
- package/dist/plugin.d.ts +50 -0
- package/dist/plugin.js +388 -0
- package/dist/providerContracts.d.ts +277 -0
- package/dist/providerContracts.js +38 -0
- package/dist/resource/OsMemoryProbe.d.ts +175 -0
- package/dist/resource/OsMemoryProbe.js +206 -0
- package/dist/resource/ResourceManager.d.ts +491 -0
- package/dist/resource/ResourceManager.js +960 -0
- package/dist/utils/ColumnManifest.d.ts +97 -0
- package/dist/utils/ColumnManifest.js +129 -0
- package/dist/utils/NativeColumnStore.d.ts +284 -0
- package/dist/utils/NativeColumnStore.js +685 -0
- package/dist/utils/NativeMetadataIndex.d.ts +882 -0
- package/dist/utils/NativeMetadataIndex.js +2631 -0
- package/dist/utils/NativeUnifiedCache.d.ts +87 -0
- package/dist/utils/NativeUnifiedCache.js +273 -0
- package/dist/utils/binaryIdMapperFactory.d.ts +59 -0
- package/dist/utils/binaryIdMapperFactory.js +94 -0
- package/dist/utils/collation.d.ts +30 -0
- package/dist/utils/collation.js +40 -0
- package/dist/utils/columnStoreTypes.d.ts +161 -0
- package/dist/utils/columnStoreTypes.js +29 -0
- package/dist/utils/nativeBinaryEntityIdMapper.d.ts +211 -0
- package/dist/utils/nativeBinaryEntityIdMapper.js +381 -0
- package/dist/utils/nativeEntityIdMapper.d.ts +111 -0
- package/dist/utils/nativeEntityIdMapper.js +170 -0
- package/docs/ADR-002-diskann-100-percent-rust.md +294 -0
- package/docs/ADR-003-semantic-time-travel.md +100 -0
- package/docs/aggregation.md +251 -0
- package/docs/comparison.md +162 -0
- package/docs/deployment-limits.md +87 -0
- package/docs/diskann.md +184 -0
- package/docs/migration-3.0.md +396 -0
- package/docs/performance-budget.md +274 -0
- package/docs/performance.md +117 -0
- package/docs/scaling.md +439 -0
- package/docs/snapshot-safety.md +246 -0
- package/docs/u64-id-space.md +214 -0
- package/docs/verification-report.md +670 -0
- package/native/brainy-native.node +0 -0
- package/native/index.d.ts +3916 -0
- package/package.json +92 -0
- package/scripts/migrate-cortex-2x-to-3x.mjs +970 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module hnsw/NativeDiskAnnWrapper
|
|
3
|
+
* @description TypeScript wrapper around cor's native **Adaptive
|
|
4
|
+
* DiskANN** engine. Satisfies brainy 8.0's `VectorIndexProvider`
|
|
5
|
+
* contract (see [`providerContracts.ts`](../providerContracts.ts))
|
|
6
|
+
* so brainy treats it as the standard vector index slot —
|
|
7
|
+
* `addItem` / `search` / `rebuild` / `flush` / ... Underneath,
|
|
8
|
+
* every operation routes through cor's pure-Rust DiskANN engine
|
|
9
|
+
* (`native/diskann/`).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { Brainy } from '@soulcraft/brainy'
|
|
14
|
+
*
|
|
15
|
+
* // Cor is auto-detected by brainy's plugin system on init.
|
|
16
|
+
* const brain = new Brainy({
|
|
17
|
+
* storage: { type: 'filesystem', path: '/data/idx' }
|
|
18
|
+
* })
|
|
19
|
+
* await brain.init() // [cor] Adaptive DiskANN engaged (mode auto)
|
|
20
|
+
*
|
|
21
|
+
* await brain.add({ data: 'native rust acceleration', type: 'concept' })
|
|
22
|
+
* const hits = await brain.search('billion scale ann', 10)
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* // Default zero-config — the adaptive selector picks Mode 1 / 2 / 3
|
|
28
|
+
* // from observed memory pressure. Most users only set `recall`.
|
|
29
|
+
* const brain = new Brainy({
|
|
30
|
+
* storage: { type: 'filesystem', path: '/data/idx' },
|
|
31
|
+
* vector: {
|
|
32
|
+
* recall: 'balanced', // 'fast' | 'balanced' | 'accurate'
|
|
33
|
+
* }
|
|
34
|
+
* })
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* ## Operating model
|
|
38
|
+
*
|
|
39
|
+
* Adaptive DiskANN is build-once, query-many: the on-disk file
|
|
40
|
+
* embeds the Vamana graph plus — depending on the build mode — an
|
|
41
|
+
* optional PQ codebook + codes section + the full vectors, all in
|
|
42
|
+
* a single contiguous mmap-able layout (see `native/diskann/src/format.rs`).
|
|
43
|
+
* Dynamic insertions go to a small in-memory **delta buffer** that
|
|
44
|
+
* brute-force-searches alongside the main index until the next
|
|
45
|
+
* `rebuild()` folds them in. This matches FreshDiskANN's published
|
|
46
|
+
* online-update model.
|
|
47
|
+
*
|
|
48
|
+
* The wrapper picks an operating mode (in-memory / hybrid /
|
|
49
|
+
* on-disk) at build time and open time via the
|
|
50
|
+
* {@link AdaptiveDiskAnnModeSelector}; see that module for the
|
|
51
|
+
* decision tree. Operators set nothing — observed memory drives
|
|
52
|
+
* the choice.
|
|
53
|
+
*
|
|
54
|
+
* ## Search path
|
|
55
|
+
*
|
|
56
|
+
* 1. Query the main index via the native DiskANN searcher:
|
|
57
|
+
* - **Mode 1**: exact-distance walk over full vectors (no PQ,
|
|
58
|
+
* no rerank — the walk's top-L is the exact top-L).
|
|
59
|
+
* - **Mode 2/3**: PQ-greedy walk in RAM (codes pinned or paged
|
|
60
|
+
* depending on mode), full-vector re-rank of the **whole walked
|
|
61
|
+
* candidate list** by default (`paddingFactor` is an optional cap
|
|
62
|
+
* for bounding cold-NVMe re-rank I/O — see {@link RECALL_PRESETS}).
|
|
63
|
+
* 2. Brute-force the delta buffer (typically <0.1% of total
|
|
64
|
+
* size after a recent rebuild).
|
|
65
|
+
* 3. Merge + sort + truncate to `k`.
|
|
66
|
+
*
|
|
67
|
+
* ## When this wrapper engages
|
|
68
|
+
*
|
|
69
|
+
* The cor plugin (`src/plugin.ts`) registers this wrapper under
|
|
70
|
+
* brainy 8.0's canonical `'vector'` provider key. Whenever cor is
|
|
71
|
+
* installed, brainy's vector index slot resolves to Adaptive DiskANN.
|
|
72
|
+
*/
|
|
73
|
+
import type { Vector, VectorDocument, DistanceFunction, StorageAdapter } from '@soulcraft/brainy';
|
|
74
|
+
import type { VectorIndexProvider } from '../providerContracts.js';
|
|
75
|
+
import type { EntityIdMapperLike } from '../utils/columnStoreTypes.js';
|
|
76
|
+
import type { MigrationCoordinator, MigrationStatus } from '../migration/MigrationCoordinator.js';
|
|
77
|
+
import { type AdaptiveDiskAnnMode, type ModeSelection } from './AdaptiveDiskAnnModeSelector.js';
|
|
78
|
+
/**
|
|
79
|
+
* **Public configuration surface** for the Adaptive DiskANN wrapper.
|
|
80
|
+
*
|
|
81
|
+
* The zero-config principle: the wrapper picks every algorithm-level
|
|
82
|
+
* tuning value (PQ subspaces, max degree, search list size, alpha
|
|
83
|
+
* pruning, mmap-vs-RAM adjacency) from observed runtime signals.
|
|
84
|
+
* Operators never tune the algorithm. The only knobs users touch are:
|
|
85
|
+
*
|
|
86
|
+
* 1. `dimensions` — required; can't be auto-detected
|
|
87
|
+
* 2. `recall` — the single quality/latency dial
|
|
88
|
+
* 3. `indexPath` — optional override; derived from storage by default
|
|
89
|
+
* 4. `mode` — explicit escape hatch for benchmarking / pinned-profile
|
|
90
|
+
* deployments. The adaptive selector picks correctly for ~99% of
|
|
91
|
+
* workloads.
|
|
92
|
+
*/
|
|
93
|
+
export interface DiskAnnIndexConfig {
|
|
94
|
+
/** Vector dimension (e.g. 384 for all-MiniLM-L6-v2). */
|
|
95
|
+
dimensions: number;
|
|
96
|
+
/**
|
|
97
|
+
* Output path for the on-disk DiskANN file. When omitted, the
|
|
98
|
+
* wrapper derives the path from `options.storage` using the
|
|
99
|
+
* canonical brainy 8.0 convention:
|
|
100
|
+
*
|
|
101
|
+
* `{storage-root}/_system/vector-index/{shard}.dkann`
|
|
102
|
+
*
|
|
103
|
+
* For a single-shard brain, `{shard}` is `main`. Living under
|
|
104
|
+
* `_system/` means brainy's `db.persist(path)` and operator
|
|
105
|
+
* backup tools (`gsutil` / `rclone`) sweep the DiskANN file for
|
|
106
|
+
* free — no special inclusion rule needed. Explicit `indexPath`
|
|
107
|
+
* always wins (sharded deployments that want custom paths).
|
|
108
|
+
*/
|
|
109
|
+
indexPath?: string;
|
|
110
|
+
/**
|
|
111
|
+
* **Recall preset** — the single quality/latency dial. Brainy 8.0
|
|
112
|
+
* exposes the same names on its JS HNSW path, so the user-facing
|
|
113
|
+
* trade-off is identical regardless of which vector index runs
|
|
114
|
+
* underneath.
|
|
115
|
+
*
|
|
116
|
+
* - `"fast"` — ~92-95% recall@10; lowest latency.
|
|
117
|
+
* - `"balanced"` (default) — ~96-98% recall@10.
|
|
118
|
+
* - `"accurate"` — ~99%+ recall@10; highest latency.
|
|
119
|
+
*/
|
|
120
|
+
recall?: 'fast' | 'balanced' | 'accurate';
|
|
121
|
+
/**
|
|
122
|
+
* **Adaptive DiskANN mode escape hatch** (Piece I). Defaults to
|
|
123
|
+
* `'auto'`, which lets the selector pick `in-memory` / `hybrid` /
|
|
124
|
+
* `on-disk` from observed memory pressure
|
|
125
|
+
* (`/proc/meminfo` + V8 heap + active instances). Set to an
|
|
126
|
+
* explicit mode only when you have a specific reason — benchmarking,
|
|
127
|
+
* a pinned-profile deployment, etc.
|
|
128
|
+
*/
|
|
129
|
+
mode?: 'auto' | AdaptiveDiskAnnMode;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* @description **Scale-aware `l_search` (E3 zero-config adaptation).** The walk
|
|
133
|
+
* width a fixed default serves degrades with corpus size: recall@10 at
|
|
134
|
+
* `l_search=100` MEASURED 0.9942 @ 1M → 0.9385 @ 10M → **0.8747 @ 100M**
|
|
135
|
+
* (SIFT sweeps, bxl9000 2026-07-01: sift10m-recall + sift100m-nvme runs;
|
|
136
|
+
* summaries in `docs/verification/`). Holding ≥0.95 needed `l_search≈150` at
|
|
137
|
+
* 10M (0.9647) and `≈256` at 100M (0.9559) — i.e. the required width grows
|
|
138
|
+
* ~×1.8 per decade of corpus size. This resolver widens the *default* walk to
|
|
139
|
+
* match: `base` up to 2M nodes (where the measured recall is ≥0.99 already),
|
|
140
|
+
* then `base × 1.8^log10(n/2e6)`, capped at 512 (the latency guard; the cap is
|
|
141
|
+
* also where the 1B PROJECTION ends — 1B ≈ 489 stays a projection until the
|
|
142
|
+
* SIFT1B run measures it).
|
|
143
|
+
*
|
|
144
|
+
* Zero-config contract: this scales the DEFAULT only. An explicit
|
|
145
|
+
* `defaultLSearch` in the construction config pins the width verbatim (the
|
|
146
|
+
* escape hatch), and a `recall` preset scales proportionally from its base
|
|
147
|
+
* (`fast`/`accurate` keep their relative latency/recall intent at every scale).
|
|
148
|
+
*
|
|
149
|
+
* @param base - The preset/base walk width (e.g. 100 for 'balanced').
|
|
150
|
+
* @param nodeCount - Current corpus size (main index + delta).
|
|
151
|
+
* @returns The scale-adjusted walk width, in `[base, 512]` (never below base).
|
|
152
|
+
* @example
|
|
153
|
+
* scaleAwareLSearch(100, 1e6) // 100 (measured 0.9942 — base is ample)
|
|
154
|
+
* scaleAwareLSearch(100, 1e7) // 151 (measured anchor: 150 → 0.9647)
|
|
155
|
+
* scaleAwareLSearch(100, 1e8) // 271 (measured anchor: 256 → 0.9559)
|
|
156
|
+
* scaleAwareLSearch(100, 1e9) // 489 (PROJECTED — SIFT1B measures it)
|
|
157
|
+
*/
|
|
158
|
+
export declare function scaleAwareLSearch(base: number, nodeCount: number): number;
|
|
159
|
+
export declare class NativeDiskAnnWrapper implements VectorIndexProvider {
|
|
160
|
+
private config;
|
|
161
|
+
private distanceFunction;
|
|
162
|
+
private storage;
|
|
163
|
+
private persistMode;
|
|
164
|
+
/** Live searcher instance — null until the first build. */
|
|
165
|
+
private native;
|
|
166
|
+
/** Newly added entries since the last build. Brute-force searched. */
|
|
167
|
+
private delta;
|
|
168
|
+
/** Removed entries — filtered out at search time. */
|
|
169
|
+
private tombstones;
|
|
170
|
+
/**
|
|
171
|
+
* **#72 Phase A+B — lazy resolver to the canonical entity-id mapper**
|
|
172
|
+
* (the metadata index's mapper, the SAME one brainy assigns entity
|
|
173
|
+
* ints with at write time). Wired by `src/plugin.ts` exactly as the
|
|
174
|
+
* graph index's `mapperResolver` (#68). The wrapper resolves
|
|
175
|
+
* `uuid → int` (slotmap writes, pushdown, membership) and `int → uuid`
|
|
176
|
+
* (search-result hydration) through it, which is why there is no
|
|
177
|
+
* longer a resident `slotByUuid` / `uuidBySlot` pair on the JS heap —
|
|
178
|
+
* the off-heap mmap slotmaps (`main.slotmap` / `main.slotrev`) plus
|
|
179
|
+
* this mapper replace them entirely (the 10B-RAM gate).
|
|
180
|
+
*/
|
|
181
|
+
private mapperResolver?;
|
|
182
|
+
/**
|
|
183
|
+
* Lazy resolver to the shared #18 {@link MigrationCoordinator} (the same getter
|
|
184
|
+
* the metadata + graph providers receive). Undefined when no migration path is
|
|
185
|
+
* wired (e.g. a raw-napi construction in tests).
|
|
186
|
+
*/
|
|
187
|
+
private readonly migrationCoordinatorGetter?;
|
|
188
|
+
/**
|
|
189
|
+
* True when the construction config carried an EXPLICIT `defaultLSearch` —
|
|
190
|
+
* the operator pinned the walk width, so {@link resolveLSearch} uses it
|
|
191
|
+
* verbatim instead of the scale-aware default (see {@link scaleAwareLSearch}).
|
|
192
|
+
* A `recall` preset does NOT pin: presets set the BASE and still scale with
|
|
193
|
+
* corpus size, preserving their relative recall/latency intent at every scale.
|
|
194
|
+
*/
|
|
195
|
+
private lSearchPinned;
|
|
196
|
+
/**
|
|
197
|
+
* The in-flight fold (async rebuild) promise, or `null`. Concurrent
|
|
198
|
+
* `rebuild()` calls coalesce onto it, `addItem()`'s hard-cap backpressure
|
|
199
|
+
* awaits it, and `removeItem()` consults it so a delta-only removal that
|
|
200
|
+
* may already be baked into the incoming index still gets a tombstone
|
|
201
|
+
* (no resurrection at swap).
|
|
202
|
+
*/
|
|
203
|
+
private rebuildInFlight;
|
|
204
|
+
/** In-flight L0 flush, or null (see {@link flushDeltaToL0}). */
|
|
205
|
+
private flushInFlight;
|
|
206
|
+
/** Live L0 segments, ascending by id (oldest first; search iterates newest→oldest). */
|
|
207
|
+
private l0Segments;
|
|
208
|
+
/** Next segment id (persisted in the manifest). */
|
|
209
|
+
private manifestNextId;
|
|
210
|
+
/** Segment-manifest cold-load memo (see {@link ensureSegments}). */
|
|
211
|
+
private segmentsLoaded;
|
|
212
|
+
private segmentsLoading;
|
|
213
|
+
/** Serializes manifest writes: the storage adapter has no per-key write
|
|
214
|
+
* ordering, so two in-flight saves (a flush racing a consolidation) could
|
|
215
|
+
* physically land out of order and persist a STALE segment list — losing a
|
|
216
|
+
* just-flushed segment's only reference after a restart. Each save chains
|
|
217
|
+
* behind the previous and builds its payload AT EXECUTION TIME. */
|
|
218
|
+
private manifestSaveChain;
|
|
219
|
+
/**
|
|
220
|
+
* **Piece I — Adaptive DiskANN.** The selector's most recent
|
|
221
|
+
* decision, exposed via {@link getLastModeSelection} for telemetry
|
|
222
|
+
* and tests. `null` until the first build or open.
|
|
223
|
+
*/
|
|
224
|
+
private lastSelection;
|
|
225
|
+
constructor(config: DiskAnnIndexConfig & {
|
|
226
|
+
distanceFunction?: DistanceFunction;
|
|
227
|
+
}, distanceFunction: DistanceFunction, options?: {
|
|
228
|
+
storage?: StorageAdapter | null;
|
|
229
|
+
persistMode?: 'immediate' | 'deferred';
|
|
230
|
+
/**
|
|
231
|
+
* **#72 Phase A+B** — lazy resolver to the canonical entity-id
|
|
232
|
+
* mapper (the metadata index's mapper). Required for any operation
|
|
233
|
+
* that touches the main index (rebuild / search / removeItem):
|
|
234
|
+
* the wrapper resolves `slot ↔ uuid` through the off-heap mmap
|
|
235
|
+
* slotmaps + this mapper instead of resident JS maps. `src/plugin.ts`
|
|
236
|
+
* passes `() => metadataIndexInstance?.getIdMapper()`, mirroring the
|
|
237
|
+
* graph index's #68 wiring; the resolver is lazy so factory-call
|
|
238
|
+
* order between the providers doesn't matter.
|
|
239
|
+
*/
|
|
240
|
+
mapperResolver?: () => EntityIdMapperLike | undefined;
|
|
241
|
+
/**
|
|
242
|
+
* **#18** — lazy resolver to the shared {@link MigrationCoordinator}. The
|
|
243
|
+
* plugin passes the SAME getter to all three derived-index providers so
|
|
244
|
+
* Brainy's feature-detected lock methods answer consistently across them.
|
|
245
|
+
*/
|
|
246
|
+
migrationCoordinator?: () => MigrationCoordinator | undefined;
|
|
247
|
+
});
|
|
248
|
+
/**
|
|
249
|
+
* Resolve the canonical entity-id mapper, or throw if it isn't wired.
|
|
250
|
+
* Every main-index operation (rebuild, search-result hydration,
|
|
251
|
+
* pushdown, membership) resolves `slot ↔ uuid` through this mapper +
|
|
252
|
+
* the off-heap slotmaps; without it the wrapper cannot translate the
|
|
253
|
+
* native engine's entity ints to UUIDs. In production `src/plugin.ts`
|
|
254
|
+
* always wires it; the throw surfaces a misconfiguration loudly rather
|
|
255
|
+
* than silently returning empty results.
|
|
256
|
+
*/
|
|
257
|
+
private mapper;
|
|
258
|
+
/**
|
|
259
|
+
* Append an entry to the delta buffer. Persisted by the next
|
|
260
|
+
* `rebuild()` call, which folds the delta into the main index.
|
|
261
|
+
*/
|
|
262
|
+
addItem(item: VectorDocument): Promise<string>;
|
|
263
|
+
/**
|
|
264
|
+
* Mark an entry as removed. Filtered out at search time; physically
|
|
265
|
+
* removed at the next `rebuild()`.
|
|
266
|
+
*/
|
|
267
|
+
removeItem(id: string): Promise<boolean>;
|
|
268
|
+
/**
|
|
269
|
+
* Hydrate the delta buffer from the canonical nouns persisted in
|
|
270
|
+
* storage — the cold-rebuild path used by {@link rebuild} after a
|
|
271
|
+
* `restore()` / migration, when the wrapper has no in-memory state to
|
|
272
|
+
* fold but storage holds the authoritative records. Streams via
|
|
273
|
+
* paginated `getNouns()` rather than fetching every noun at once.
|
|
274
|
+
*
|
|
275
|
+
* NOTE: like the existing delta path this materializes the vectors it
|
|
276
|
+
* loads into the JS delta. A billion-noun cold rebuild should instead
|
|
277
|
+
* stream straight from a storage-backed vector source (the native
|
|
278
|
+
* `build_from_file` path used by the SIFT billion-scale builder); that
|
|
279
|
+
* scale optimization is tracked separately. For the migration sizes
|
|
280
|
+
* this serves today the delta route is correct, and it logs the count
|
|
281
|
+
* it loaded so an operator can see the reconstruction happen.
|
|
282
|
+
*
|
|
283
|
+
* @param dim - Index dimension; nouns whose vector width differs are skipped.
|
|
284
|
+
*/
|
|
285
|
+
private hydrateDeltaFromStorage;
|
|
286
|
+
search(queryVector: Vector, k?: number, filter?: (id: string) => Promise<boolean>, options?: {
|
|
287
|
+
rerank?: {
|
|
288
|
+
multiplier: number;
|
|
289
|
+
};
|
|
290
|
+
candidateIds?: string[];
|
|
291
|
+
/**
|
|
292
|
+
* Metadata∩graph universe (entity UUIDs) from the `find()`
|
|
293
|
+
* composition. When supplied and selective, it is PUSHED INTO the
|
|
294
|
+
* native beam walk (predicate pushdown, #10) so the walk collects only
|
|
295
|
+
* allowed slots — far higher recall than post-filtering the unfiltered
|
|
296
|
+
* top-k. Above {@link PUSHDOWN_MAX_SELECTIVITY} it is folded into the
|
|
297
|
+
* post-filter predicate instead (cheaper, and post-filter suffices).
|
|
298
|
+
*/
|
|
299
|
+
allowedIds?: ReadonlySet<string>;
|
|
300
|
+
}): Promise<Array<[string, number]>>;
|
|
301
|
+
/**
|
|
302
|
+
* Predicate-pushdown search (#10). Translates the metadata∩graph entity
|
|
303
|
+
* universe to main-index slots, pushes them into the native beam walk
|
|
304
|
+
* (which traverses *through* all nodes but only collects allowed slots),
|
|
305
|
+
* and inflates `l_search` by ~1/selectivity so a selective predicate still
|
|
306
|
+
* surfaces k allowed neighbours. Delta-buffer entries (no slot yet) are
|
|
307
|
+
* brute-forced. This is the high-recall path for selective filtered
|
|
308
|
+
* `find()` — post-filtering the unfiltered top-k loses recall when few of
|
|
309
|
+
* those candidates pass the predicate.
|
|
310
|
+
*
|
|
311
|
+
* @param queryVector - The query embedding.
|
|
312
|
+
* @param k - Number of nearest allowed neighbours to return.
|
|
313
|
+
* @param allowedIds - Entity UUIDs permitted in the result (the find() universe).
|
|
314
|
+
* @param filter - Optional additional async predicate, composed with allowedIds.
|
|
315
|
+
* @param options - Optional rerank multiplier override.
|
|
316
|
+
* @returns Up to k `[uuid, distance]` pairs, ascending by distance.
|
|
317
|
+
*/
|
|
318
|
+
private searchPushdown;
|
|
319
|
+
/**
|
|
320
|
+
* @description **#18 migration lock — feature-detected by Brainy.** `true` while
|
|
321
|
+
* the brain is undergoing (or has failed) the coordinated 7.x → 8.0 rebuild, so
|
|
322
|
+
* Brainy holds reads+writes behind the lock (and skips its lazy first-query
|
|
323
|
+
* force-rebuild of the vector index). Delegates to the shared {@link
|
|
324
|
+
* MigrationCoordinator}; `false` when none is wired.
|
|
325
|
+
* @returns `true` when the brain must not be read from or written to.
|
|
326
|
+
*/
|
|
327
|
+
isMigrating(): boolean;
|
|
328
|
+
/**
|
|
329
|
+
* @description **#18 migration lock — structured progress** Brainy relays into
|
|
330
|
+
* `getIndexStatus().migration`. `null` unless a migration is in flight.
|
|
331
|
+
* @returns The current {@link MigrationStatus}, or `null`.
|
|
332
|
+
*/
|
|
333
|
+
migrationStatus(): MigrationStatus | null;
|
|
334
|
+
/**
|
|
335
|
+
* @description Resolve the search-time walk width for this query: the
|
|
336
|
+
* scale-aware default ({@link scaleAwareLSearch} over the current corpus size)
|
|
337
|
+
* unless the operator pinned `defaultLSearch` explicitly, floored at `k × 2`
|
|
338
|
+
* so a large `k` is never starved. This is the E3 fix for the measured
|
|
339
|
+
* fixed-default recall fade (0.9942 @ 1M → 0.8747 @ 100M at l_search=100).
|
|
340
|
+
* @param k - The requested result count.
|
|
341
|
+
* @returns The walk width to pass to the native searcher.
|
|
342
|
+
*/
|
|
343
|
+
/**
|
|
344
|
+
* **Adaptive DiskANN mode selection (Piece I)** — build the native config for
|
|
345
|
+
* an index of `totalCount` vectors: the selector picks "in-memory" for brains
|
|
346
|
+
* that fit in RAM (no PQ, sub-ms search), "hybrid" for medium scale, "on-disk"
|
|
347
|
+
* when memory is tight or many brains share the box. Shared by the full
|
|
348
|
+
* consolidation ({@link runRebuild}) and the O(delta) L0 segment flush
|
|
349
|
+
* ({@link runFlushToL0}); records the selection on {@link lastSelection}.
|
|
350
|
+
*/
|
|
351
|
+
private nativeBuildCfg;
|
|
352
|
+
private resolveLSearch;
|
|
353
|
+
/** Total entries across the base index + all L0 segments (no delta). */
|
|
354
|
+
private indexedSize;
|
|
355
|
+
size(): number;
|
|
356
|
+
clear(): void;
|
|
357
|
+
/**
|
|
358
|
+
* Rebuild the main index from scratch: concatenate (current main −
|
|
359
|
+
* tombstones) ∪ delta, run a full DiskANN build, swap the searcher
|
|
360
|
+
* atomically.
|
|
361
|
+
*
|
|
362
|
+
* At billion-scale this is the expensive operation (hours-to-days of
|
|
363
|
+
* build time) — but it is NON-BLOCKING: the build runs on a worker
|
|
364
|
+
* thread (`rebuildFromExistingAsync`), so the JS event loop keeps
|
|
365
|
+
* serving reads and accepting writes for the entire fold. Writes that
|
|
366
|
+
* land mid-fold stay in the live delta (reconciled against the fold's
|
|
367
|
+
* snapshot at swap) and roll into the next fold. Concurrent calls
|
|
368
|
+
* coalesce onto the in-flight fold. The delta is bounded: a soft
|
|
369
|
+
* threshold kicks a background fold automatically and a hard cap
|
|
370
|
+
* applies backpressure in `addItem` (see `DELTA_SOFT_FOLD_BYTES`).
|
|
371
|
+
*
|
|
372
|
+
* The optional `recall` argument overrides the wrapper's
|
|
373
|
+
* construction-time recall preset for this rebuild only — useful
|
|
374
|
+
* when an operator wants to ship a higher-quality index after a
|
|
375
|
+
* data-quality push but kept the brain on `'balanced'` originally.
|
|
376
|
+
* Omit to keep the wrapper's current preset.
|
|
377
|
+
*/
|
|
378
|
+
rebuild(options?: {
|
|
379
|
+
recall?: 'fast' | 'balanced' | 'accurate';
|
|
380
|
+
}): Promise<void>;
|
|
381
|
+
/** The fold body — see {@link rebuild} for the public contract. */
|
|
382
|
+
private runRebuild;
|
|
383
|
+
/**
|
|
384
|
+
* Flush the delta buffer to disk. For DiskANN the delta is in-memory
|
|
385
|
+
* by design (a few MB at most between rebuilds); returns the buffer
|
|
386
|
+
* size for parity with HNSW's flush contract.
|
|
387
|
+
*/
|
|
388
|
+
flush(): Promise<number>;
|
|
389
|
+
getPersistMode(): 'immediate' | 'deferred';
|
|
390
|
+
/** Absolute path of a segment file (lives next to the base indexPath). */
|
|
391
|
+
private segPath;
|
|
392
|
+
/** Membership across the base + every L0 segment (int → any slot). */
|
|
393
|
+
private hasIntAnywhere;
|
|
394
|
+
/**
|
|
395
|
+
* @description Provider init — brainy feature-detects and awaits it. Cold-loads
|
|
396
|
+
* the segment manifest so a reopened brain serves its L0 segments from the
|
|
397
|
+
* first query (the wrapper's own async paths also call {@link ensureSegments},
|
|
398
|
+
* so a caller that skips init() still gets the segments lazily).
|
|
399
|
+
*/
|
|
400
|
+
init(): Promise<void>;
|
|
401
|
+
/** Memoized segment-manifest cold-load. */
|
|
402
|
+
private ensureSegments;
|
|
403
|
+
/**
|
|
404
|
+
* Read `__vector_index_manifest__` and open every live L0 segment. A missing
|
|
405
|
+
* manifest is the legacy single-index layout (the base at `indexPath` IS the
|
|
406
|
+
* whole vector index) — existing brains upgrade seamlessly; the first flush
|
|
407
|
+
* writes the first manifest. A segment file the manifest references but that
|
|
408
|
+
* fails to open is SKIPPED loudly (crash-orphan tolerance; its entries remain
|
|
409
|
+
* safe in canonical storage and the next consolidation heals the set).
|
|
410
|
+
*/
|
|
411
|
+
private loadSegmentManifest;
|
|
412
|
+
/**
|
|
413
|
+
* Persist the live segment set (AFTER the referenced files exist on disk).
|
|
414
|
+
* Saves are SERIALIZED and each snapshot the segment list at write time, so
|
|
415
|
+
* concurrent flush/consolidation saves can never persist out of order.
|
|
416
|
+
*/
|
|
417
|
+
private saveSegmentManifest;
|
|
418
|
+
private writeSegmentManifestNow;
|
|
419
|
+
/**
|
|
420
|
+
* @description **#73 MVP-C — flush the delta into a new immutable L0 segment.**
|
|
421
|
+
* O(new-data): builds a small self-contained `.dkann` from the delta snapshot
|
|
422
|
+
* alone (existing index untouched), registers it in the manifest, and
|
|
423
|
+
* reconciles the live delta. Concurrent calls coalesce. Crash ordering: the
|
|
424
|
+
* segment file is fully written (with its slotmap sidecars) BEFORE the
|
|
425
|
+
* manifest references it.
|
|
426
|
+
*/
|
|
427
|
+
private flushDeltaToL0;
|
|
428
|
+
/** The flush body — see {@link flushDeltaToL0}. */
|
|
429
|
+
private runFlushToL0;
|
|
430
|
+
private tryOpenExisting;
|
|
431
|
+
/**
|
|
432
|
+
* Remove any leftover `main.slots.json` written by a pre-#72 RC build.
|
|
433
|
+
* The native `main.slotmap` is now authoritative; a stale JSON sidecar
|
|
434
|
+
* is never read, but we delete it (best-effort) so it can never shadow
|
|
435
|
+
* the off-heap slotmap and to reclaim its (potentially large) bytes.
|
|
436
|
+
*/
|
|
437
|
+
private removeLegacySlotsSidecar;
|
|
438
|
+
/**
|
|
439
|
+
* **Piece I.** Resolve the open-time mode for an existing file.
|
|
440
|
+
* Respects an explicit `config.mode` override; otherwise consults
|
|
441
|
+
* the adaptive selector.
|
|
442
|
+
*/
|
|
443
|
+
private resolveOpenMode;
|
|
444
|
+
/**
|
|
445
|
+
* **Piece I.** Resolve the build-time mode. Same override-respecting
|
|
446
|
+
* shape as {@link resolveOpenMode}; the build-time decision
|
|
447
|
+
* additionally lets the selector choose Mode 1 (no PQ) when the
|
|
448
|
+
* brain fits in RAM.
|
|
449
|
+
*/
|
|
450
|
+
private resolveBuildMode;
|
|
451
|
+
/**
|
|
452
|
+
* **Piece I.** The selector's most recent decision (either at the
|
|
453
|
+
* last `tryOpenExisting` or `rebuild`). Returns `null` if no
|
|
454
|
+
* brain has been opened or built yet. Surface for telemetry and
|
|
455
|
+
* tests.
|
|
456
|
+
*/
|
|
457
|
+
getLastModeSelection(): ModeSelection | null;
|
|
458
|
+
/**
|
|
459
|
+
* **Test-only hook** for exercising the build-adjacency resolver
|
|
460
|
+
* without spinning up a real build. Mirrors the
|
|
461
|
+
* `_testInjectOsMemoryProbe` pattern (Piece J); not part of the
|
|
462
|
+
* public contract.
|
|
463
|
+
* @internal
|
|
464
|
+
*/
|
|
465
|
+
_testResolveAdjacencyBackend(nodeCount: number): {
|
|
466
|
+
kind: 'mmap';
|
|
467
|
+
mmapPath: string;
|
|
468
|
+
} | {
|
|
469
|
+
kind: 'ram';
|
|
470
|
+
};
|
|
471
|
+
/**
|
|
472
|
+
* Resolve the build-time adjacency backend from the config plus
|
|
473
|
+
* the actual node count. `useMmapAdjacency: 'auto'` (the default)
|
|
474
|
+
* picks file-backed once `nodeCount > MMAP_ADJACENCY_AUTO_THRESHOLD`;
|
|
475
|
+
* explicit `true` / `false` always wins. Per the zero-config
|
|
476
|
+
* principle, operators don't have to know the 100 M threshold —
|
|
477
|
+
* the wrapper observes `totalCount` and flips automatically.
|
|
478
|
+
*/
|
|
479
|
+
private resolveAdjacencyBackend;
|
|
480
|
+
/**
|
|
481
|
+
* Count tombstoned entities that are present in the MAIN index (so
|
|
482
|
+
* `size()` can subtract them). Membership is the off-heap reverse
|
|
483
|
+
* slotmap: resolve uuid → int → `hasInt`. Returns 0 without touching
|
|
484
|
+
* the mapper when there are no tombstones or no main index, so the
|
|
485
|
+
* common `size()` call stays allocation- and FFI-free.
|
|
486
|
+
*/
|
|
487
|
+
private countMainTombstones;
|
|
488
|
+
}
|
|
489
|
+
//# sourceMappingURL=NativeDiskAnnWrapper.d.ts.map
|