@soulcraft/cor 3.0.0-rc.1 → 3.0.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/README.md +130 -247
- package/dist/cli.js +82 -1
- package/dist/license/constants.d.ts +3 -0
- package/dist/license/constants.js +3 -0
- package/dist/license/onlineValidator.d.ts +10 -0
- package/dist/license/onlineValidator.js +45 -1
- package/dist/license/types.d.ts +1 -1
- package/dist/license.d.ts +1 -1
- package/dist/plugin.js +18 -2
- package/docs/scaling.md +22 -0
- package/native/brainy-native.node +0 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,328 +1,211 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/soulcraftlabs/brainy/main/brainy.png" alt="Cor" width="180">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Cor</h1>
|
|
2
6
|
|
|
3
7
|
<p align="center">
|
|
4
|
-
<
|
|
8
|
+
<b>One engine. Any scale. Your hardware.</b><br>
|
|
9
|
+
The native accelerator for <a href="https://www.npmjs.com/package/@soulcraft/brainy">@soulcraft/brainy</a> — billion-entity vector + graph + metadata<br>
|
|
10
|
+
search on a single machine, with zero code changes and zero configuration.
|
|
5
11
|
</p>
|
|
6
12
|
|
|
7
13
|
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@soulcraft/cor"><img src="https://
|
|
14
|
+
<a href="https://www.npmjs.com/package/@soulcraft/cor"><img src="https://img.shields.io/npm/v/@soulcraft/cor.svg" alt="npm version"></a>
|
|
9
15
|
<a href="https://www.npmjs.com/package/@soulcraft/cor"><img src="https://img.shields.io/npm/dm/@soulcraft/cor.svg" alt="npm downloads"></a>
|
|
16
|
+
<a href="https://soulcraft.com/docs"><img src="https://img.shields.io/badge/docs-soulcraft.com-blue.svg" alt="Documentation"></a>
|
|
10
17
|
<a href="https://soulcraft.com/pricing?focus=cor"><img src="https://img.shields.io/badge/license-commercial-blue.svg" alt="Commercial License"></a>
|
|
11
18
|
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg" alt="TypeScript"></a>
|
|
12
19
|
</p>
|
|
13
20
|
|
|
14
21
|
<p align="center">
|
|
15
|
-
<
|
|
16
|
-
</
|
|
17
|
-
|
|
18
|
-
<
|
|
19
|
-
|
|
22
|
+
<a href="#quick-start">Quick start</a> ·
|
|
23
|
+
<a href="#exact-filters-inside-the-vector-walk">Filter pushdown</a> ·
|
|
24
|
+
<a href="#feature-tour">Features</a> ·
|
|
25
|
+
<a href="#one-box-hundreds-of-brains--or-one-brain-at-billion-scale">Scale</a> ·
|
|
26
|
+
<a href="#measured-not-promised">Benchmarks</a> ·
|
|
27
|
+
<a href="#learn-more">Docs</a>
|
|
20
28
|
</p>
|
|
21
29
|
|
|
22
|
-
```bash
|
|
23
|
-
bun add @soulcraft/cor # or: npm install @soulcraft/cor
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
**[soulcraft.com/docs](https://soulcraft.com/docs)** · **[Pricing](https://soulcraft.com/pricing?focus=cor)** · **[Benchmarks](https://github.com/soulcraftlabs/cor/blob/main/docs/performance.md)** · **[Comparison](https://github.com/soulcraftlabs/cor/blob/main/docs/comparison.md)**
|
|
27
|
-
|
|
28
30
|
---
|
|
29
31
|
|
|
30
|
-
Brainy
|
|
31
|
-
|
|
32
|
-
When performance matters — at scale, in production, with large embedding models — Cor automatically swaps in compiled Rust implementations for distance calculations, vector index, metadata filtering, graph adjacency, and embeddings. No code changes. No configuration changes. Add the package and it activates.
|
|
33
|
-
|
|
34
|
-
**New in cor 3.0** *(measured numbers on bxl9000 — Ryzen 9 7950X3D / 184 GB / NVMe; reproducible from `scripts/verify-*.mjs`)*:
|
|
32
|
+
[Brainy](https://github.com/soulcraftlabs/brainy) is the open-source knowledge database — vectors, relationships, metadata, and full-text in one MIT-licensed engine. Cor swaps compiled Rust in under every hot path. **No code changes. No configuration.** Install it and brainy (≥ 8.0.9) auto-detects it — loudly: activation prints `Providers: 10/10 native`, a missing license warns by name, and a broken install makes `init()` throw rather than quietly run slow. **Remove it and everything keeps working** on the same files, at open-source speed. That's the contract in both directions.
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
| Brainy runs it as | Cor replaces it with | You get |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| HNSW vector index (in-RAM) | **Adaptive DiskANN** — mmap-native, self-tuning, streaming segments | billion-scale ANN on one box; recall@10 **0.96 at 100M** (measured) |
|
|
37
|
+
| `find()` composed in JS | one fused native query: metadata ∩ graph ∩ vector | exact filters **inside** the vector walk, not after it |
|
|
38
|
+
| JSON field index | LSM postings + columnar sort/aggregate, mmap-durable | cold open serves instantly — restart = warm (measured) |
|
|
39
|
+
| graph adjacency in JS maps | u64 LSM adjacency + native traversal/analytics | whole-graph reads as single cursor walks |
|
|
40
|
+
| JS distance loops | SIMD kernels (f32, SQ8, SQ4-packed) | the hot inner loop in vector search, compiled |
|
|
41
|
+
| WASM embedding model | Candle ML runtime, in-process | batch on-box embeddings — no API fees, no data egress |
|
|
42
|
+
| time-travel reads in JS | generation-pinned reads across all three substrates | `asOf()` at native speed |
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## Quick Start
|
|
44
|
+
## Quick start
|
|
46
45
|
|
|
47
46
|
```bash
|
|
48
|
-
|
|
47
|
+
npm install @soulcraft/cor # or: bun add @soulcraft/cor
|
|
49
48
|
```
|
|
50
49
|
|
|
51
50
|
```typescript
|
|
52
|
-
import { Brainy, NounType } from '@soulcraft/brainy'
|
|
53
|
-
|
|
54
|
-
const brain = new Brainy({
|
|
55
|
-
storage: { type: 'filesystem', rootDirectory: './data' },
|
|
56
|
-
plugins: ['@soulcraft/cor']
|
|
57
|
-
})
|
|
58
|
-
await brain.init()
|
|
59
|
-
// [brainy] Plugin activated: @soulcraft/cor
|
|
60
|
-
// All native providers registered — same API, Rust speed.
|
|
61
|
-
|
|
62
|
-
await brain.add({ data: 'Native Rust SIMD acceleration', type: NounType.Concept })
|
|
63
|
-
const results = await brain.find({ query: 'fast vector search' })
|
|
64
|
-
```
|
|
51
|
+
import { Brainy, NounType, VerbType } from '@soulcraft/brainy'
|
|
65
52
|
|
|
66
|
-
|
|
53
|
+
const brain = new Brainy({ storage: { type: 'filesystem', path: './data' } })
|
|
54
|
+
await brain.init() // cor auto-detected (brainy ≥ 8.0.9) — look for "Providers: 10/10 native"
|
|
67
55
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
56
|
+
const cor = await brain.add({
|
|
57
|
+
data: 'Cor swaps compiled Rust in under every Brainy hot path',
|
|
58
|
+
type: NounType.Concept,
|
|
59
|
+
subtype: 'library',
|
|
60
|
+
metadata: { layer: 'native', year: 2026 },
|
|
61
|
+
})
|
|
62
|
+
const brainy = await brain.add({
|
|
63
|
+
data: 'Brainy is the open-source knowledge database',
|
|
64
|
+
type: NounType.Concept,
|
|
65
|
+
subtype: 'library',
|
|
66
|
+
metadata: { layer: 'engine', year: 2026 },
|
|
67
|
+
})
|
|
68
|
+
await brain.relate({ from: cor, to: brainy, type: VerbType.DependsOn, subtype: 'runtime' })
|
|
72
69
|
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
const hits = await brain.find({
|
|
71
|
+
query: 'native acceleration', // vector — what it means
|
|
72
|
+
where: { layer: 'native' }, // metadata — pushed INTO the walk
|
|
73
|
+
connected: { to: brainy }, // graph — what it touches
|
|
74
|
+
}) // one call, three indexes, fused
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
**Activate** (paid tiers; free = just use brainy):
|
|
78
78
|
|
|
79
|
-
**Other CLI commands:**
|
|
80
79
|
```bash
|
|
81
|
-
npx cor
|
|
82
|
-
|
|
83
|
-
npx cor help # all commands
|
|
80
|
+
npx @soulcraft/cor login # browser sign-in, like `gh auth login`
|
|
81
|
+
# or, for servers / CI: export COR_LICENSE_KEY=sc_cor_...
|
|
84
82
|
```
|
|
85
83
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## What Gets Accelerated
|
|
91
|
-
|
|
92
|
-
Every provider listed here is a drop-in replacement — Brainy's plugin system wires them in automatically based on your license tier.
|
|
93
|
-
|
|
94
|
-
| Subsystem | What Cor Provides |
|
|
95
|
-
|-----------|---------------------|
|
|
96
|
-
| **Adaptive DiskANN** | 100% pure-Rust Vamana α-pruned graph + Product Quantization. Single-machine billion-vector ANN search; MEASURED p50 0.85 ms at 1 M on bxl9000. Auto-engages on local-storage installs |
|
|
97
|
-
| **Triple-intelligence find()** | Composes metadata filter + graph hop + vector ANN in one query. Substrate-composition p50 ≈ 0.230 ms at 1 M (per-stage measured on synthetic; end-to-end SIFT re-validation in progress) |
|
|
98
|
-
| **Time-travel reads** | `db.asOf(g)` / `db.with(...)` — pin a generation, read the brain as it existed at that moment. All 3 substrates participate in the pin lifecycle |
|
|
99
|
-
| **SIMD distance** | Float32 cosine; SQ8 uint8 cosine (81× faster); SQ4 nibble-packed |
|
|
100
|
-
| **Embeddings** | Candle ML runtime — in-process, no Python sidecar, no HTTP roundtrip. CPU and Apple Metal |
|
|
101
|
-
| **Batch embeddings** | Single Rust forward pass for bulk import and reindex |
|
|
102
|
-
| **Metadata index** | Rust LSM-tree query/mutation engine with CRoaring bitmap posting lists. MEASURED 525 K inserts/sec at 100 M |
|
|
103
|
-
| **Graph adjacency** | 4 LSM-trees + verb-endpoints shadow-page store with typed verb tracking |
|
|
104
|
-
| **Entity ID mapper** | Mmap'd UUID ↔ u64 with shadow-page snapshot safety; supports 2 B+ entities |
|
|
105
|
-
| **Vector quantization** | SQ8 (4× memory), SQ4 (8× memory), Product Quantization (16–32×) |
|
|
106
|
-
| **Graph compression** | Delta-varint encoded connection lists |
|
|
107
|
-
| **Roaring bitmaps** | CRoaring bindings — binary-compatible with `roaring-wasm` |
|
|
108
|
-
| **Msgpack** | Native encode/decode |
|
|
109
|
-
| **mmap storage** | Zero-copy SSTable reads via memory-mapped files; shadow-page LSM for snapshot safety |
|
|
110
|
-
| **Aggregation engine** | Incremental updates with Rayon parallel rebuild |
|
|
111
|
-
| **Compaction** | Pin-aware (respects `min_pinned_generation` for time-travel) + graph-aware BFS ordering for disk locality |
|
|
112
|
-
|
|
113
|
-
### Scale envelope at a glance
|
|
114
|
-
|
|
115
|
-
> **MEASURED at 1 M / 10 M / 100 M on bxl9000.** Source files cited in
|
|
116
|
-
> `docs/verification-report.md`. 1 B numbers are projected from SIFT1B
|
|
117
|
-
> trends + 100 M measured + Subramanya et al. (NeurIPS 2019) algorithm
|
|
118
|
-
> latency model.
|
|
119
|
-
|
|
120
|
-
| Vectors | RAM | DiskANN p50 | LSM filter p50 | Graph 2-hop p50 | **Composed find() p50** | Source |
|
|
121
|
-
|--------:|---:|---:|---:|---:|---:|:---|
|
|
122
|
-
| 1 M | 0.5–2 GB | **0.85 ms** | **23.8 µs** | **14.3 µs** | **0.230 ms** | bxl9000, measured |
|
|
123
|
-
| 10 M | 1–5 GB | _running_ | **537 µs** | _running_ | _projected_ | bxl9000, partial |
|
|
124
|
-
| 100 M | 5–20 GB | _projected_ | **5.85 ms** | _projected_ | _projected_ | bxl9000, LSM only |
|
|
125
|
-
| **1 B** | **20–70 GB** | **5–10 ms** (PROJECTED) | _projected_ | _projected_ | _projected_ | SIFT1B reference |
|
|
126
|
-
|
|
127
|
-
All numbers are for the search engine itself. End-to-end query latency adds entity hydration; see **[docs/scaling.md](https://github.com/soulcraftlabs/cor/blob/main/docs/scaling.md#operational-ceiling-at-1-b)** for the honest full-stack story.
|
|
128
|
-
|
|
129
|
-
**Running many brains per box (per-tenant pools, e.g. one brain per user)?** Cor auto-raises its own soft open-file limit on startup and warns you (with the exact fix) if the OS file/mmap limits would bottleneck a pool before RAM. The hard `ulimit -n` + Linux `vm.max_map_count` must be set by your deployment — copy-paste Docker / Compose / Kubernetes / host config in **[docs/deployment-limits.md](https://github.com/soulcraftlabs/cor/blob/main/docs/deployment-limits.md)**.
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## Performance
|
|
134
|
-
|
|
135
|
-
All numbers **measured** with `vitest bench` on Linux x64.
|
|
136
|
-
|
|
137
|
-
### Distance Calculations — 384-dimensional vectors (all-MiniLM-L6-v2)
|
|
84
|
+
Keys verify **offline** in Rust — no network call at startup, no license server to run.
|
|
138
85
|
|
|
139
|
-
|
|
140
|
-
|-----------|----------:|--------:|:-----------------:|
|
|
141
|
-
| `cosineDistance` (float32) | 45,965 ops/s | 21.8 μs | baseline |
|
|
142
|
-
| `cosineDistanceSq8` (quantized) | 3,715,855 ops/s | 0.27 μs | **81×** |
|
|
143
|
-
| `cosineDistanceSq8Batch` (1K vectors) | 2,142 ops/s | 467 μs | **28×** |
|
|
144
|
-
| `cosineDistanceBatch` (float32, 1K) | 77 ops/s | 13.0 ms | — |
|
|
145
|
-
| `quantizeSq8` (encode 384-dim) | 74,020 ops/s | 13.5 μs | — |
|
|
86
|
+
## Exact filters inside the vector walk
|
|
146
87
|
|
|
147
|
-
|
|
88
|
+
Pure vector databases post-filter — search first, discard non-matches — so selective filters return too few or lower-recall results. Cor computes the exact matching set first (roaring-bitmap intersection), searches only that provably-correct space, and switches to an exact scan when the set is small (recall 1.0).
|
|
148
89
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
### Serialization
|
|
159
|
-
|
|
160
|
-
| Operation | Throughput | Latency |
|
|
161
|
-
|-----------|----------:|--------:|
|
|
162
|
-
| `msgpackEncode` (1K entities) | 235 ops/s | 4.3 ms |
|
|
163
|
-
| `msgpackDecode` (1K entities) | 355 ops/s | 2.8 ms |
|
|
164
|
-
| `RoaringBitmap32.serialize` (10K elements) | 361,258 ops/s | 2.8 μs |
|
|
165
|
-
| `RoaringBitmap32.deserialize` (10K elements) | 548,714 ops/s | 1.8 μs |
|
|
166
|
-
| `encodeConnections` (1K lists) | 913 ops/s | 1.1 ms |
|
|
167
|
-
| `decodeConnections` (1K lists) | 1,078 ops/s | 928 μs |
|
|
168
|
-
|
|
169
|
-
Full breakdown: **[docs/performance.md](https://github.com/soulcraftlabs/cor/blob/main/docs/performance.md)** · Side-by-side comparison: **[docs/comparison.md](https://github.com/soulcraftlabs/cor/blob/main/docs/comparison.md)**
|
|
170
|
-
|
|
171
|
-
---
|
|
90
|
+
```typescript
|
|
91
|
+
await brain.find({
|
|
92
|
+
query: 'quarterly revenue anomalies',
|
|
93
|
+
where: { region: 'EMEA', year: 2026 }, // resolved to an exact bitmap FIRST
|
|
94
|
+
})
|
|
95
|
+
// the vector walk only ever visits entities that already match —
|
|
96
|
+
// selective filters get MORE accurate, not less
|
|
97
|
+
```
|
|
172
98
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
| Feature | Free | Pro / Enterprise |
|
|
176
|
-
|---------|:----:|:----------------:|
|
|
177
|
-
| mmap-enhanced filesystem storage | ✓ | ✓ |
|
|
178
|
-
| **Adaptive DiskANN billion-scale index** | — | ✓ |
|
|
179
|
-
| **Triple-intelligence find() composition** | — | ✓ |
|
|
180
|
-
| **Time-travel reads (`db.asOf(g)`)** | — | ✓ |
|
|
181
|
-
| SIMD distance (float32, SQ8, SQ4) | — | ✓ |
|
|
182
|
-
| Vector quantization (SQ8, SQ4, PQ) | — | ✓ |
|
|
183
|
-
| Candle ML embeddings (CPU / Metal) | — | ✓ |
|
|
184
|
-
| Native metadata index (LSM + CRoaring) | — | ✓ |
|
|
185
|
-
| Graph adjacency index (4 LSM-trees + verb endpoints) | — | ✓ |
|
|
186
|
-
| Entity ID mapper (mmap UUID ↔ u64) | — | ✓ |
|
|
187
|
-
| Snapshot-safe shadow-page LSM stores | — | ✓ |
|
|
188
|
-
| CRoaring bitmap bindings | — | ✓ |
|
|
189
|
-
| Native msgpack encode/decode | — | ✓ |
|
|
190
|
-
| Incremental aggregation engine | — | ✓ |
|
|
191
|
-
| Pin-aware + graph-aware compaction | — | ✓ |
|
|
192
|
-
|
|
193
|
-
Free tier activates automatically with no key. Pro/Enterprise require a license key — see [soulcraft.com/pricing](https://soulcraft.com/pricing?focus=cor).
|
|
99
|
+
*Cor pushes the exact filter into the vector search; everyone else filters after.*
|
|
194
100
|
|
|
195
|
-
|
|
101
|
+
## Feature tour
|
|
196
102
|
|
|
197
|
-
|
|
103
|
+
### Adaptive DiskANN — it tunes itself to your machine
|
|
198
104
|
|
|
199
|
-
|
|
105
|
+
A 100% pure-Rust implementation of the DiskANN algorithm (Microsoft Research, NeurIPS 2019: Vamana graphs + product quantization + an mmap-native format). Zero knobs, by design:
|
|
200
106
|
|
|
201
107
|
```typescript
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
// distance: { source: 'plugin' },
|
|
206
|
-
// vectorIndex: { source: 'plugin' }, // Adaptive DiskANN
|
|
207
|
-
// embeddings: { source: 'plugin' },
|
|
208
|
-
// metadataIndex: { source: 'plugin' },
|
|
209
|
-
// graphIndex: { source: 'plugin' },
|
|
210
|
-
// ...
|
|
211
|
-
// }
|
|
108
|
+
const brain = new Brainy({ storage: { type: 'filesystem', path: './data' } })
|
|
109
|
+
await brain.init()
|
|
110
|
+
// that's the entire configuration — on every machine, at every scale
|
|
212
111
|
```
|
|
213
112
|
|
|
214
|
-
|
|
113
|
+
It observes available RAM and picks in-memory / compressed / on-disk operation, widens its search with corpus size, shares the box fairly with sibling instances, and grows its id space to ~51B entities. Same binary, same files — from a laptop to a 128 GB server, it simply uses what it finds.
|
|
215
114
|
|
|
216
|
-
|
|
217
|
-
brain.requireProviders(['distance', 'vectorIndex', 'embeddings', 'metadataIndex', 'graphIndex'])
|
|
218
|
-
```
|
|
115
|
+
**[How Adaptive DiskANN works](docs/diskann.md)** · **[ADR-002: why 100% Rust](docs/ADR-002-diskann-100-percent-rust.md)**
|
|
219
116
|
|
|
220
|
-
|
|
117
|
+
### Streaming inserts that never freeze
|
|
221
118
|
|
|
222
|
-
|
|
119
|
+
Writes absorb instantly and flush to small immutable segments in **O(new-data)** — seconds at any corpus size, not hours of reindexing. Index maintenance runs off-thread; consolidation is rare, background, and **never blocks a read or a write**.
|
|
223
120
|
|
|
224
|
-
|
|
121
|
+
```typescript
|
|
122
|
+
// keep writing at any corpus size — reads stay live throughout
|
|
123
|
+
for (const doc of firehose) await brain.add(doc)
|
|
124
|
+
await brain.find({ query: 'still answering' }) // never waits on a rebuild
|
|
125
|
+
```
|
|
225
126
|
|
|
226
|
-
|
|
127
|
+
A brain that grows fast never hits a rebuild wall.
|
|
227
128
|
|
|
228
|
-
|
|
129
|
+
### Time travel
|
|
229
130
|
|
|
230
|
-
|
|
231
|
-
2. A **14-day grace period** begins
|
|
232
|
-
3. After the grace period, Cor silently degrades to free tier — storage keeps working, compute acceleration pauses
|
|
131
|
+
Point-in-time queries across vectors, graph, and metadata together — plus speculative transactions. No other vector database ships this.
|
|
233
132
|
|
|
234
|
-
```
|
|
235
|
-
|
|
133
|
+
```typescript
|
|
134
|
+
const lastWeek = await brain.asOf(Date.now() - 7 * 86_400_000)
|
|
135
|
+
await lastWeek.find({ query: 'what did we know then?' }) // full query surface, past state
|
|
236
136
|
```
|
|
237
137
|
|
|
238
|
-
|
|
138
|
+
**[ADR-003: semantic time travel](docs/ADR-003-semantic-time-travel.md)**
|
|
239
139
|
|
|
240
|
-
###
|
|
140
|
+
### Cold start = warm
|
|
241
141
|
|
|
242
|
-
|
|
142
|
+
Every index is mmap-durable — the files *are* the state. A restart serves identical results from the first query, with no rebuild and no warm-up phase (measured: cold-reopen recall matches warm exactly).
|
|
243
143
|
|
|
244
|
-
|
|
245
|
-
{ "cor": "CX-7F3A-9B2D" }
|
|
246
|
-
```
|
|
144
|
+
**[Snapshot safety](docs/snapshot-safety.md)**
|
|
247
145
|
|
|
248
|
-
|
|
146
|
+
### Local embeddings, embedded engine
|
|
249
147
|
|
|
250
|
-
|
|
148
|
+
An on-box ML runtime (Candle) — no per-document API fees, no data egress, fully offline-capable. And the whole thing is a library, not a database server: nothing to deploy, monitor, or operate.
|
|
251
149
|
|
|
252
|
-
|
|
150
|
+
### Automatic upgrades
|
|
253
151
|
|
|
254
|
-
|
|
152
|
+
Opening an older-format brain triggers a coordinated, observable migration: progress reporting, an automatic pre-upgrade backup, and a guarantee it never serves half-built state or loses a write.
|
|
255
153
|
|
|
256
|
-
|
|
154
|
+
**[Upgrading from 2.x / brainy 7.x](docs/migration-3.0.md)**
|
|
257
155
|
|
|
258
|
-
|
|
259
|
-
|----------|-------------|:---------------:|
|
|
260
|
-
| Linux | x64 (glibc 2.17+) | ✓ |
|
|
261
|
-
| Linux | arm64 (glibc) | ✓ |
|
|
262
|
-
| macOS | arm64 (Apple Silicon) | ✓ |
|
|
263
|
-
| macOS | x64 (Intel) | ✓ |
|
|
264
|
-
| Windows | x64 | ✓ |
|
|
156
|
+
## One box, hundreds of brains — or one brain at billion scale
|
|
265
157
|
|
|
266
|
-
|
|
158
|
+
The same zero-config design serves both extremes. **Per-user isolation:** run one brain per user or customer — each a physically separate database (own files, own indexes; no shared store, no cross-tenant query surface to secure). Every brain sizes itself from *observed RAM ÷ active brains*, a resource manager rebalances budgets at runtime, and mmap paging means idle tenants cost almost nothing — hundreds of isolated brains fit one commodity box. **Or scale one up:** the identical engine takes a single brain to billions of entities on that same box. No sharding tier, no "multi-tenant edition" — one design, both shapes.
|
|
267
159
|
|
|
268
|
-
|
|
160
|
+
**[Scaling: sizing, tenants, limits](docs/scaling.md)**
|
|
269
161
|
|
|
270
|
-
##
|
|
162
|
+
## Measured, not promised
|
|
271
163
|
|
|
272
|
-
|
|
273
|
-
|---------|-------------|-----|
|
|
274
|
-
| `[cor] missing sc_cor_ prefix` | `.soulcraft.json` contains wrong key or non-cor key | Re-run `npx @soulcraft/cor activate CX-XXXX-XXXX` |
|
|
275
|
-
| `[cor] license expired` | Key is past its expiry date | Renew at [soulcraft.com/account](https://soulcraft.com/account) |
|
|
276
|
-
| `[cor] invalid signature` | Key was truncated or corrupted | Re-activate: `npx @soulcraft/cor activate CX-XXXX-XXXX` |
|
|
277
|
-
| `[cor] invalid product: workshop` | Workshop key used for Cor | Keys are product-specific — get a Cor key at [soulcraft.com/account](https://soulcraft.com/account) |
|
|
278
|
-
| `[cor] native module unavailable` | Native binary not found for current platform | Check platform table above |
|
|
279
|
-
| Compute providers inactive, no warning | No key set — free tier is active | Run `npx @soulcraft/cor activate CX-XXXX-XXXX` |
|
|
280
|
-
| `[cor] License lapsed. Compute acceleration disabled.` | Grace period expired | Renew at [soulcraft.com/account](https://soulcraft.com/account) |
|
|
281
|
-
| `[cor] License suspended. Contact support.` | Account suspended | Open a ticket at [soulcraft.com/issues](https://soulcraft.com/issues) |
|
|
164
|
+
SIFT benchmarks on commodity-class hardware; reproducible via `scripts/verify-*.mjs`; methodology + honesty notes in [docs/comparison.md](docs/comparison.md).
|
|
282
165
|
|
|
283
|
-
|
|
166
|
+
| Scale | Recall@10 | Median latency |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| 1M | **0.9942** | 0.31 ms |
|
|
169
|
+
| 10M | **0.9647** | 1.33 ms |
|
|
170
|
+
| 100M | **0.9559** | ~7 ms — one box, at the published DiskANN billion-scale operating point |
|
|
171
|
+
| 1B | in validation | measured run in progress; labeled projected until it lands |
|
|
284
172
|
|
|
285
|
-
|
|
173
|
+
Cold-reopen recall matches warm exactly (measured). The write path holds flat RSS from 100k → 1M entities (measured). Billion-scale claims stay labeled until the 1B run completes — that's the house rule.
|
|
286
174
|
|
|
287
|
-
|
|
175
|
+
## Built for teams that can't send data anywhere
|
|
288
176
|
|
|
289
|
-
|
|
290
|
-
import { loadNativeModule, isNativeAvailable } from '@soulcraft/cor'
|
|
177
|
+
Everything runs on your hardware, in your process: search, storage, **and embedding generation** (the on-box ML runtime means documents never leave the machine, even to be vectorized). Air-gapped deployments work — license keys verify offline, and the daily license ping (key id, machine fingerprint, size bucket; never data) degrades gracefully when there's no route out. Point-in-time queries give you an audit dimension most databases can't: *what did the system know, and when?*
|
|
291
178
|
|
|
292
|
-
|
|
293
|
-
const native = loadNativeModule()
|
|
294
|
-
const dist = native.cosineDistance(vectorA, vectorB)
|
|
295
|
-
const encoded = native.msgpackEncode(data)
|
|
296
|
-
}
|
|
297
|
-
```
|
|
179
|
+
## Licensing that never breaks your app
|
|
298
180
|
|
|
299
|
-
|
|
181
|
+
Start free on brainy. When you outgrow it, `npm install @soulcraft/cor` — nothing else changes. And in the other direction: no key — or an expired one, or no network — means cor quietly steps aside and you're running open-source brainy on the same files. **There is no kill switch, and we will never build one.** Pricing follows the size of your brain, not seats or nodes: free under 1M entities (that's brainy), then simple tiers as you grow. Once a day cor reports its key id, a machine fingerprint, and a coarse size bucket — never data, queries, or content. See [soulcraft.com/pricing](https://soulcraft.com/pricing?focus=cor).
|
|
300
182
|
|
|
301
183
|
## Requirements
|
|
302
184
|
|
|
303
|
-
-
|
|
304
|
-
- `@soulcraft/brainy
|
|
185
|
+
- Node.js ≥ 22 (Bun ≥ 1.1 as a runtime) · TypeScript-native, ESM
|
|
186
|
+
- `@soulcraft/brainy` ≥ 8.0 · local filesystem storage (mmap requires real files)
|
|
187
|
+
- 32 GB RAM serves ~1B entities; 64 GB recommended — see [docs/scaling.md](docs/scaling.md)
|
|
305
188
|
|
|
306
|
-
|
|
307
|
-
bun add @soulcraft/brainy @soulcraft/cor # Bun — best performance
|
|
308
|
-
npm install @soulcraft/brainy @soulcraft/cor # Node.js — fully supported
|
|
309
|
-
```
|
|
189
|
+
## Learn more
|
|
310
190
|
|
|
311
|
-
|
|
191
|
+
| Topic | Doc |
|
|
192
|
+
|---|---|
|
|
193
|
+
| How Adaptive DiskANN works | [docs/diskann.md](docs/diskann.md) · [ADR-002: why 100% Rust](docs/ADR-002-diskann-100-percent-rust.md) |
|
|
194
|
+
| Benchmarks + methodology | [docs/performance.md](docs/performance.md) · [docs/comparison.md](docs/comparison.md) · [docs/verification-report.md](docs/verification-report.md) |
|
|
195
|
+
| Scaling: sizing, tenants, limits | [docs/scaling.md](docs/scaling.md) · [docs/billion-scale.md](docs/billion-scale.md) · [docs/deployment-limits.md](docs/deployment-limits.md) |
|
|
196
|
+
| Durability + snapshots | [docs/snapshot-safety.md](docs/snapshot-safety.md) |
|
|
197
|
+
| Time travel | [ADR-003: semantic time travel](docs/ADR-003-semantic-time-travel.md) |
|
|
198
|
+
| Upgrading from 2.x / brainy 7.x | [docs/migration-3.0.md](docs/migration-3.0.md) |
|
|
312
199
|
|
|
313
|
-
##
|
|
200
|
+
## The open-core promise
|
|
314
201
|
|
|
315
|
-
|
|
316
|
-
- **[Plugin system](https://github.com/soulcraftlabs/brainy/blob/main/docs/PLUGINS.md)** — How Brainy plugins work
|
|
317
|
-
- **[Performance benchmarks](https://github.com/soulcraftlabs/cor/blob/main/docs/performance.md)** — Full benchmark table with methodology
|
|
318
|
-
- **[Brainy vs Cor comparison](https://github.com/soulcraftlabs/cor/blob/main/docs/comparison.md)** — Side-by-side feature and speed comparison
|
|
319
|
-
- **[soulcraft.com/pricing](https://soulcraft.com/pricing?focus=cor)** — Pro and Enterprise plans
|
|
202
|
+
Every accelerated path has a working JavaScript default in brainy (MIT). You upgrade for speed, not capability — and you can downgrade at any time with zero migration, because cor works on brainy's files, not its own. Your data is never hostage to a subscription.
|
|
320
203
|
|
|
321
204
|
---
|
|
322
205
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
206
|
+
<p align="center">
|
|
207
|
+
<a href="https://soulcraft.com/docs">Documentation</a> ·
|
|
208
|
+
<a href="https://soulcraft.com/pricing?focus=cor">Pricing</a> ·
|
|
209
|
+
<a href="https://github.com/soulcraftlabs/brainy">Brainy (open source)</a> ·
|
|
210
|
+
<a href="https://soulcraft.com/support">Support</a>
|
|
211
|
+
</p>
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { readConfig, writeConfig, isShortCode, exchangeShortCode, readLicenseToken } from './license.js';
|
|
13
|
-
import { SHORT_CODE_PATTERN, CONFIG_FILE } from './license/constants.js';
|
|
13
|
+
import { SHORT_CODE_PATTERN, CONFIG_FILE, DEVICE_START_URL, DEVICE_TOKEN_URL } from './license/constants.js';
|
|
14
14
|
const JWT_PREFIX = 'sc_cor_';
|
|
15
15
|
// Legacy prefix still accepted during the cortex→cor rename transition.
|
|
16
16
|
const LEGACY_JWT_PREFIX = 'sc_cortex_';
|
|
@@ -37,6 +37,83 @@ function resolveLicenseSource(config) {
|
|
|
37
37
|
return { source: 'file', value: fileValue };
|
|
38
38
|
return { source: 'none', value: undefined };
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* `cor login` — RFC 8628 device-authorization flow (the `gh auth login` UX):
|
|
42
|
+
* request a device code, show the user code + verification URL, poll until the
|
|
43
|
+
* browser approval lands, write the issued JWT to `.soulcraft.json`. The env
|
|
44
|
+
* var (`COR_LICENSE_KEY`) remains the CI/headless path; `activate <code>`
|
|
45
|
+
* remains the offline fallback.
|
|
46
|
+
*/
|
|
47
|
+
async function cmdLogin() {
|
|
48
|
+
let start;
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(DEVICE_START_URL, { method: 'POST' });
|
|
51
|
+
if (!res.ok)
|
|
52
|
+
throw new Error(`HTTP ${res.status}`);
|
|
53
|
+
start = await res.json();
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error('[cor] Could not reach soulcraft.com to start login.');
|
|
57
|
+
console.error(`[cor] (${error instanceof Error ? error.message : String(error)})`);
|
|
58
|
+
console.error('[cor] Offline? Use: npx @soulcraft/cor activate <code-or-key>');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
// RFC 8628 field names, tolerating camelCase servers.
|
|
62
|
+
const deviceCode = start.device_code ?? start.deviceCode;
|
|
63
|
+
const userCode = start.user_code ?? start.userCode;
|
|
64
|
+
const uri = start.verification_uri ?? start.verificationUri ?? 'https://soulcraft.com/activate';
|
|
65
|
+
let intervalS = Number(start.interval ?? 5) || 5;
|
|
66
|
+
const expiresIn = Number(start.expires_in ?? start.expiresIn ?? 900) || 900;
|
|
67
|
+
if (!deviceCode || !userCode) {
|
|
68
|
+
console.error('[cor] Login service returned an unexpected response. Try again, or use `activate <code>`.');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
console.log('');
|
|
72
|
+
console.log(` Open: ${uri}`);
|
|
73
|
+
console.log(` Code: ${userCode}`);
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(' Waiting for approval in the browser…');
|
|
76
|
+
const deadline = Date.now() + expiresIn * 1000;
|
|
77
|
+
while (Date.now() < deadline) {
|
|
78
|
+
await new Promise((r) => setTimeout(r, intervalS * 1000));
|
|
79
|
+
let body;
|
|
80
|
+
try {
|
|
81
|
+
const res = await fetch(DEVICE_TOKEN_URL, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { 'Content-Type': 'application/json' },
|
|
84
|
+
body: JSON.stringify({ device_code: deviceCode }),
|
|
85
|
+
});
|
|
86
|
+
body = await res.json().catch(() => ({}));
|
|
87
|
+
if (res.ok && (body.token ?? body.access_token)) {
|
|
88
|
+
const token = body.token ?? body.access_token;
|
|
89
|
+
const config = await readConfig();
|
|
90
|
+
delete config.cortex; // retire the legacy key on a fresh login
|
|
91
|
+
config.cor = token;
|
|
92
|
+
await writeConfig(config);
|
|
93
|
+
console.log(` ✓ Logged in. License saved to ${CONFIG_FILE} — cor acceleration is active.`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
continue; // transient network error — keep polling until the deadline
|
|
99
|
+
}
|
|
100
|
+
const err = body?.error;
|
|
101
|
+
if (err === 'authorization_pending')
|
|
102
|
+
continue;
|
|
103
|
+
if (err === 'slow_down') {
|
|
104
|
+
intervalS += 5;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (err === 'expired_token')
|
|
108
|
+
break;
|
|
109
|
+
if (err === 'access_denied') {
|
|
110
|
+
console.error(' ✗ Login was denied in the browser.');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
console.error(' ✗ Login timed out. Run `npx @soulcraft/cor login` again.');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
40
117
|
function printHelp() {
|
|
41
118
|
console.log(`
|
|
42
119
|
cor — license management for @soulcraft/cor
|
|
@@ -45,6 +122,7 @@ USAGE
|
|
|
45
122
|
npx @soulcraft/cor <command> [args]
|
|
46
123
|
|
|
47
124
|
COMMANDS
|
|
125
|
+
login Sign in via the browser (device flow) — the easy path
|
|
48
126
|
activate <code-or-key> Activate with a short code (CO-XXXX-XXXX) or JWT
|
|
49
127
|
status Show current license status
|
|
50
128
|
deactivate Remove the cor license key
|
|
@@ -213,6 +291,9 @@ async function cmdDeactivate() {
|
|
|
213
291
|
}
|
|
214
292
|
const [, , command, ...args] = process.argv;
|
|
215
293
|
switch (command) {
|
|
294
|
+
case 'login':
|
|
295
|
+
await cmdLogin();
|
|
296
|
+
break;
|
|
216
297
|
case 'activate':
|
|
217
298
|
await cmdActivate(args[0]);
|
|
218
299
|
break;
|
|
@@ -13,6 +13,9 @@ export declare const ONLINE_VALIDATION_TIMEOUT_MS = 10000;
|
|
|
13
13
|
export declare const API_URL = "https://soulcraft.com/api/license/validate";
|
|
14
14
|
/** Short code exchange API URL */
|
|
15
15
|
export declare const EXCHANGE_URL = "https://soulcraft.com/api/license/exchange";
|
|
16
|
+
/** RFC 8628 device-authorization endpoints (`cor login`). */
|
|
17
|
+
export declare const DEVICE_START_URL = "https://soulcraft.com/api/license/device/start";
|
|
18
|
+
export declare const DEVICE_TOKEN_URL = "https://soulcraft.com/api/license/device/token";
|
|
16
19
|
/** Pricing URL for license purchase */
|
|
17
20
|
export declare const PRICING_URL = "https://soulcraft.com/pricing?focus=pro";
|
|
18
21
|
/**
|
|
@@ -13,6 +13,9 @@ export const ONLINE_VALIDATION_TIMEOUT_MS = 10_000;
|
|
|
13
13
|
export const API_URL = 'https://soulcraft.com/api/license/validate';
|
|
14
14
|
/** Short code exchange API URL */
|
|
15
15
|
export const EXCHANGE_URL = 'https://soulcraft.com/api/license/exchange';
|
|
16
|
+
/** RFC 8628 device-authorization endpoints (`cor login`). */
|
|
17
|
+
export const DEVICE_START_URL = 'https://soulcraft.com/api/license/device/start';
|
|
18
|
+
export const DEVICE_TOKEN_URL = 'https://soulcraft.com/api/license/device/token';
|
|
16
19
|
/** Pricing URL for license purchase */
|
|
17
20
|
export const PRICING_URL = 'https://soulcraft.com/pricing?focus=pro';
|
|
18
21
|
/**
|
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
* No signature verification here — that's in Rust.
|
|
10
10
|
* Default 24h heartbeat interval, 14-day grace period.
|
|
11
11
|
*/
|
|
12
|
+
export declare function setEntityCountSource(source: () => number | undefined): void;
|
|
13
|
+
/** Coarse size bucket for metering — the pricing ladder's rungs. */
|
|
14
|
+
export declare function entityBucketFor(count: number | undefined): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Stable, non-identifying machine fingerprint: a truncated hash of
|
|
17
|
+
* hostname|platform|arch. Lets Portal count concurrent instances per key
|
|
18
|
+
* (the anti-sharing backstop — soft: an email, never a block) without
|
|
19
|
+
* transmitting the hostname itself.
|
|
20
|
+
*/
|
|
21
|
+
export declare function machineFingerprint(): string;
|
|
12
22
|
interface HeartbeatCache {
|
|
13
23
|
lastCheck: string;
|
|
14
24
|
nextCheckAt?: string;
|
|
@@ -11,7 +11,46 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import { hostname, platform, arch } from 'node:os';
|
|
14
16
|
import { API_URL, CACHE_DIR, HEARTBEAT_FILE, GRACE_PERIOD_DAYS, DEFAULT_CHECK_INTERVAL_MS, ONLINE_VALIDATION_TIMEOUT_MS, } from './constants.js';
|
|
17
|
+
/**
|
|
18
|
+
* Lazy source of the brain's indexed entity count, wired by the plugin
|
|
19
|
+
* (`setEntityCountSource`). The heartbeat reports only a coarse SIZE BUCKET —
|
|
20
|
+
* never data — per the licensing transparency contract ("once a day: key id,
|
|
21
|
+
* machine fingerprint, size bucket"). Undefined (unwired / probe failure)
|
|
22
|
+
* reports no bucket.
|
|
23
|
+
*/
|
|
24
|
+
let entityCountSource = null;
|
|
25
|
+
export function setEntityCountSource(source) {
|
|
26
|
+
entityCountSource = source;
|
|
27
|
+
}
|
|
28
|
+
/** Coarse size bucket for metering — the pricing ladder's rungs. */
|
|
29
|
+
export function entityBucketFor(count) {
|
|
30
|
+
if (count === undefined || !Number.isFinite(count) || count < 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
if (count < 1_000_000)
|
|
33
|
+
return '<1M';
|
|
34
|
+
if (count < 10_000_000)
|
|
35
|
+
return '1M-10M';
|
|
36
|
+
if (count < 100_000_000)
|
|
37
|
+
return '10M-100M';
|
|
38
|
+
if (count < 1_000_000_000)
|
|
39
|
+
return '100M-1B';
|
|
40
|
+
return '1B+';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Stable, non-identifying machine fingerprint: a truncated hash of
|
|
44
|
+
* hostname|platform|arch. Lets Portal count concurrent instances per key
|
|
45
|
+
* (the anti-sharing backstop — soft: an email, never a block) without
|
|
46
|
+
* transmitting the hostname itself.
|
|
47
|
+
*/
|
|
48
|
+
export function machineFingerprint() {
|
|
49
|
+
return createHash('sha256')
|
|
50
|
+
.update(`${hostname()}|${platform()}|${arch()}`)
|
|
51
|
+
.digest('hex')
|
|
52
|
+
.slice(0, 16);
|
|
53
|
+
}
|
|
15
54
|
function getDefaultCacheDir() {
|
|
16
55
|
return join(process.cwd(), CACHE_DIR);
|
|
17
56
|
}
|
|
@@ -98,7 +137,12 @@ async function callApi(token) {
|
|
|
98
137
|
const response = await fetch(API_URL, {
|
|
99
138
|
method: 'POST',
|
|
100
139
|
headers: { 'Content-Type': 'application/json' },
|
|
101
|
-
body: JSON.stringify({
|
|
140
|
+
body: JSON.stringify({
|
|
141
|
+
licenseKey: token,
|
|
142
|
+
ver: 2,
|
|
143
|
+
sid: machineFingerprint(),
|
|
144
|
+
entityBucket: entityBucketFor(entityCountSource?.()),
|
|
145
|
+
}),
|
|
102
146
|
signal: controller.signal,
|
|
103
147
|
});
|
|
104
148
|
if (!response.ok) {
|
package/dist/license/types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/** Result of license validation */
|
|
5
5
|
export interface LicenseResult {
|
|
6
6
|
valid: boolean;
|
|
7
|
-
tier: 'free' | 'pro' | 'enterprise' | 'internal';
|
|
7
|
+
tier: 'free' | 'startup' | 'pro' | 'growth' | 'scale' | 'enterprise' | 'internal';
|
|
8
8
|
email: string;
|
|
9
9
|
expiresAt: number;
|
|
10
10
|
error?: string;
|
package/dist/license.d.ts
CHANGED
package/dist/plugin.js
CHANGED
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
import { loadNativeModule, isNativeAvailable } from './native/index.js';
|
|
46
46
|
import { validateLicense, readLicenseToken, readConfig, isShortCode } from './license.js';
|
|
47
47
|
import { MigrationCoordinator } from './migration/MigrationCoordinator.js';
|
|
48
|
+
import { prodLog } from '@soulcraft/brainy/internals';
|
|
49
|
+
import { setEntityCountSource } from './license/onlineValidator.js';
|
|
48
50
|
const PRICING_URL = 'https://soulcraft.com/pricing?focus=pro';
|
|
49
51
|
// `brainyRange` engages brainy 8.0's version-coupling gate (handoff LV.3): a
|
|
50
52
|
// brainy outside this range fails loud at init() instead of silently dropping to
|
|
@@ -162,7 +164,10 @@ const corPlugin = {
|
|
|
162
164
|
const { readHeartbeatCache, isGracePeriodExpired } = await import('./license/onlineValidator.js');
|
|
163
165
|
const cachedState = await readHeartbeatCache();
|
|
164
166
|
if (cachedState?.gracePeriodStart && isGracePeriodExpired(cachedState.gracePeriodStart)) {
|
|
165
|
-
|
|
167
|
+
// LOUD (prodLog stays on even in silent mode): grace exhausted is the
|
|
168
|
+
// one licensing state an operator must notice — acceleration is now
|
|
169
|
+
// OFF and queries run at open-source speed until the key renews.
|
|
170
|
+
prodLog.error('[cor] License grace period EXPIRED — native acceleration disabled; running at open-source (brainy) speed. Renew at soulcraft.com/account');
|
|
166
171
|
return true;
|
|
167
172
|
}
|
|
168
173
|
}
|
|
@@ -354,6 +359,16 @@ const corPlugin = {
|
|
|
354
359
|
// ResourceManager runs internally (adaptive cache sizing, wired above) — it is
|
|
355
360
|
// not registered as a provider since brainy has no consumer for it.
|
|
356
361
|
resourceManager.startRebalancing();
|
|
362
|
+
// Metering source for the heartbeat: the brain's indexed entity count
|
|
363
|
+
// (O(1) from the vector index), reported as a coarse size bucket only.
|
|
364
|
+
setEntityCountSource(() => {
|
|
365
|
+
try {
|
|
366
|
+
return vectorInstance?.size();
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return undefined;
|
|
370
|
+
}
|
|
371
|
+
});
|
|
357
372
|
// Background heartbeat with forceCheck — always checks Portal on startup.
|
|
358
373
|
// Updates the cache so the next startup sees current subscription state.
|
|
359
374
|
// Runs after providers are registered (non-blocking for the user).
|
|
@@ -366,7 +381,8 @@ const corPlugin = {
|
|
|
366
381
|
.then(result => {
|
|
367
382
|
if (result.degrade) {
|
|
368
383
|
const msg = result.warning ?? 'License lapsed. Compute acceleration disabled. Renew at soulcraft.com/account';
|
|
369
|
-
|
|
384
|
+
// Loud even in silent mode — see the grace-expiry note above.
|
|
385
|
+
prodLog.error(`[cor] ${msg}`);
|
|
370
386
|
}
|
|
371
387
|
else if (result.warning) {
|
|
372
388
|
console.warn(`[cor] ${result.warning}`);
|
package/docs/scaling.md
CHANGED
|
@@ -36,6 +36,28 @@ Moving up a tier is `rsync` plus restart at the single-machine side, and adding
|
|
|
36
36
|
a routing layer at the horizontal side. The Brainy API doesn't change — your
|
|
37
37
|
code looks the same whether you're running one instance or 256.
|
|
38
38
|
|
|
39
|
+
## Build time vs serve time — the number that matters is milliseconds
|
|
40
|
+
|
|
41
|
+
A common first worry: "if a billion-vector index takes days to build, will my users wait days?"
|
|
42
|
+
No user ever waits on a build. The two costs are completely different things:
|
|
43
|
+
|
|
44
|
+
| | What it is | How long | Who experiences it |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| **Serving** | answering queries | **milliseconds** (measured: ~1 ms @ 10M, ~7 ms @ 100M) | your users, constantly |
|
|
47
|
+
| **Inserting** | new data becoming searchable | **instant** (bounded buffer + O(new-data) segment flush) | your writers, constantly |
|
|
48
|
+
| **Building** | constructing the optimized graph | minutes at 1M · ~hours at 100M · days at 1B | **nobody waits** — it runs in the background while serving continues |
|
|
49
|
+
|
|
50
|
+
Why building is expensive: a query is *one* walk through the graph; building makes *every*
|
|
51
|
+
vector do that walk to wire itself in — a billion queries' worth of work, performed once.
|
|
52
|
+
This is the DiskANN design working as intended (build once, serve from disk forever), and
|
|
53
|
+
the same physics applies to every vector database at this scale.
|
|
54
|
+
|
|
55
|
+
Why nobody waits: brains grow incrementally — every insert is searchable in seconds via the
|
|
56
|
+
segment tier, and the big consolidation runs off-thread with live progress while reads and
|
|
57
|
+
writes flow uninterrupted. Even a one-time bulk migration of a pre-existing billion-vector
|
|
58
|
+
corpus serves queries the whole time; only *peak* performance arrives when the background
|
|
59
|
+
build completes.
|
|
60
|
+
|
|
39
61
|
## Single machine
|
|
40
62
|
|
|
41
63
|
The default unit of deployment is **one Brainy instance** (the data layer —
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/cor",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"description": "Native Rust acceleration for Brainy
|
|
3
|
+
"version": "3.0.1",
|
|
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",
|
|
7
7
|
"type": "module",
|
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
"LICENSE"
|
|
79
79
|
],
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@soulcraft/brainy": ">=8.0.0
|
|
81
|
+
"@soulcraft/brainy": ">=8.0.0 <9.0.0"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@napi-rs/cli": "^3.0.0",
|
|
85
|
-
"@soulcraft/brainy": "8.0.
|
|
85
|
+
"@soulcraft/brainy": "8.0.7",
|
|
86
86
|
"@types/node": "^22.0.0",
|
|
87
87
|
"pg": "^8.21.0",
|
|
88
88
|
"tsx": "^4.21.0",
|