@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
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @soulcraft/cor — Native Rust acceleration for Brainy
|
|
3
|
+
*
|
|
4
|
+
* When installed alongside @soulcraft/brainy, this package automatically
|
|
5
|
+
* provides native Rust implementations for:
|
|
6
|
+
* - SIMD distance calculations (cosine, euclidean, manhattan, dot product)
|
|
7
|
+
* - Metadata index (full query/mutation engine in Rust)
|
|
8
|
+
* - Graph adjacency index (4 LSM-trees + verb tracking)
|
|
9
|
+
* - Entity ID mapper (UUID ↔ integer bidirectional mapping)
|
|
10
|
+
* - Roaring bitmaps (CRoaring bindings)
|
|
11
|
+
* - Embedding engine (Candle ML — CPU, CUDA, Metal)
|
|
12
|
+
* - Msgpack encoding/decoding
|
|
13
|
+
*
|
|
14
|
+
* Storage adapter: cor uses brainy's `FileSystemStorage` directly
|
|
15
|
+
* (since brainy 7.25.0 it natively provides the binary-blob primitive
|
|
16
|
+
* and multi-process locking cor used to layer on top). Cor no
|
|
17
|
+
* longer ships a storage adapter — the boundary is now razor-sharp:
|
|
18
|
+
* **brainy = data + storage**, **cor = compute accelerators**.
|
|
19
|
+
*
|
|
20
|
+
* Usage: `npm install @soulcraft/cor` — auto-detected, zero config.
|
|
21
|
+
*/
|
|
22
|
+
export { default } from './plugin.js';
|
|
23
|
+
export { loadNativeModule, isNativeAvailable } from './native/index.js';
|
|
24
|
+
export type { NativeBindings } from './native/types.js';
|
|
25
|
+
export { NativeEmbeddingEngine, nativeEmbeddingEngine, cosineSimilarity } from './native/NativeEmbeddingEngine.js';
|
|
26
|
+
export { RoaringBitmap32, RoaringBitmap32Iterator, roaringLibraryInitialize, roaringLibraryIsReady, SerializationFormat, DeserializationFormat } from './native/NativeRoaringBitmap32.js';
|
|
27
|
+
export { NativeAggregationProvider } from './aggregation/NativeAggregationEngine.js';
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @soulcraft/cor — Native Rust acceleration for Brainy
|
|
3
|
+
*
|
|
4
|
+
* When installed alongside @soulcraft/brainy, this package automatically
|
|
5
|
+
* provides native Rust implementations for:
|
|
6
|
+
* - SIMD distance calculations (cosine, euclidean, manhattan, dot product)
|
|
7
|
+
* - Metadata index (full query/mutation engine in Rust)
|
|
8
|
+
* - Graph adjacency index (4 LSM-trees + verb tracking)
|
|
9
|
+
* - Entity ID mapper (UUID ↔ integer bidirectional mapping)
|
|
10
|
+
* - Roaring bitmaps (CRoaring bindings)
|
|
11
|
+
* - Embedding engine (Candle ML — CPU, CUDA, Metal)
|
|
12
|
+
* - Msgpack encoding/decoding
|
|
13
|
+
*
|
|
14
|
+
* Storage adapter: cor uses brainy's `FileSystemStorage` directly
|
|
15
|
+
* (since brainy 7.25.0 it natively provides the binary-blob primitive
|
|
16
|
+
* and multi-process locking cor used to layer on top). Cor no
|
|
17
|
+
* longer ships a storage adapter — the boundary is now razor-sharp:
|
|
18
|
+
* **brainy = data + storage**, **cor = compute accelerators**.
|
|
19
|
+
*
|
|
20
|
+
* Usage: `npm install @soulcraft/cor` — auto-detected, zero config.
|
|
21
|
+
*/
|
|
22
|
+
// Default export: BrainyPlugin (auto-detected by brainy init())
|
|
23
|
+
export { default } from './plugin.js';
|
|
24
|
+
export { loadNativeModule, isNativeAvailable } from './native/index.js';
|
|
25
|
+
// Re-export all native wrapper classes
|
|
26
|
+
export { NativeEmbeddingEngine, nativeEmbeddingEngine, cosineSimilarity } from './native/NativeEmbeddingEngine.js';
|
|
27
|
+
export { RoaringBitmap32, RoaringBitmap32Iterator, roaringLibraryInitialize, roaringLibraryIsReady, SerializationFormat, DeserializationFormat } from './native/NativeRoaringBitmap32.js';
|
|
28
|
+
export { NativeAggregationProvider } from './aggregation/NativeAggregationEngine.js';
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module legacyLayoutGuard
|
|
3
|
+
* @description Refuses to start when a cor 3.0 process is pointed
|
|
4
|
+
* at a directory that still holds cortex 2.x on-disk artifacts.
|
|
5
|
+
*
|
|
6
|
+
* The failure mode this prevents is the most dangerous in production:
|
|
7
|
+
* an operator deploys brainy 8.0 + cor 3.0 to a host whose data
|
|
8
|
+
* directory hasn't been migrated yet, the service starts,
|
|
9
|
+
* `brain.init()` fires, and either (a) crashes mid-init with a
|
|
10
|
+
* confusing low-level error from a shadow-page store, or (b) — worse —
|
|
11
|
+
* cortex's auto-engagement creates a new 3.0 file alongside the 2.x
|
|
12
|
+
* remnants, producing a half-migrated state that's harder to roll
|
|
13
|
+
* back than a clean stop.
|
|
14
|
+
*
|
|
15
|
+
* The guard runs at the top of `corPlugin.activate()`, before any
|
|
16
|
+
* provider registers. It scans the storage root for known cortex 2.x
|
|
17
|
+
* markers, and if any are found, throws a single error with the exact
|
|
18
|
+
* migration command the operator needs to run. **Throwing here is the
|
|
19
|
+
* point** — a service that won't start is recoverable; a service that
|
|
20
|
+
* starts on the wrong format isn't.
|
|
21
|
+
*
|
|
22
|
+
* The guard is no-op when:
|
|
23
|
+
* - The storage adapter isn't filesystem-backed (cortex 2.x markers
|
|
24
|
+
* are inherently filesystem-rooted; cloud adapters can't have
|
|
25
|
+
* them).
|
|
26
|
+
* - The storage root is empty (fresh install — no migration needed).
|
|
27
|
+
* - Only cor 3.0 markers are present (the migration completed,
|
|
28
|
+
* or this brain was created fresh on 3.0).
|
|
29
|
+
*
|
|
30
|
+
* The guard fires when:
|
|
31
|
+
* - `_id_mapper/uuid_to_int.mkv` is a FILE (cortex 2.x). In cortex
|
|
32
|
+
* 3.0 this is a DIRECTORY containing the shadow-page head pointer
|
|
33
|
+
* + base + delta.
|
|
34
|
+
* - `_id_mapper/int_to_uuid.bin` is a FILE (cortex 2.x). Same
|
|
35
|
+
* story — 3.0 makes this a directory.
|
|
36
|
+
* - `_metadata/` contains the cortex 2.x JSON chunk envelope shape
|
|
37
|
+
* without the 3.0 LSM shard subdirectory.
|
|
38
|
+
* - HNSW provider artifacts exist alongside no DiskANN directory.
|
|
39
|
+
*
|
|
40
|
+
* Bypass: setting `COR_LEGACY_LAYOUT_GUARD=skip` (legacy `CORTEX_LEGACY_LAYOUT_GUARD` still honored) in the environment
|
|
41
|
+
* disables the guard. This exists for the migration script itself
|
|
42
|
+
* (which needs to be able to read the legacy layout to take its
|
|
43
|
+
* snapshot) and for emergency debugging. Production code should never
|
|
44
|
+
* set this.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Storage shape we can inspect. Cortex plugin's
|
|
48
|
+
* `BrainyPluginContext.storage` exposes `getBinaryBlobPath` on
|
|
49
|
+
* filesystem-backed adapters; we use that to discover the root.
|
|
50
|
+
*/
|
|
51
|
+
interface StorageWithBlobPath {
|
|
52
|
+
getBinaryBlobPath?: (key: string) => string | null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Public guard. Call at the top of `corPlugin.activate()` with
|
|
56
|
+
* the brainy storage adapter. Throws if cortex 2.x markers are
|
|
57
|
+
* present.
|
|
58
|
+
*/
|
|
59
|
+
export declare function assertNoLegacyLayout(storage: StorageWithBlobPath | null | undefined): void;
|
|
60
|
+
/**
|
|
61
|
+
* @description Run {@link assertNoLegacyLayout} exactly once per storage
|
|
62
|
+
* adapter, from inside cor's storage-receiving provider factories.
|
|
63
|
+
*
|
|
64
|
+
* This is the load-bearing entry point under brainy 8.0. The plugin
|
|
65
|
+
* `activate(context)` hook receives a context that does **not** expose
|
|
66
|
+
* the brain's storage — it carries only `registerProvider` + `version` —
|
|
67
|
+
* so a guard call there scans `undefined` and silently passes, defeating
|
|
68
|
+
* the safety net. The storage adapter is only handed to the *provider
|
|
69
|
+
* factories* (`metadataIndex`, `graphIndex`, `entityIdMapper`), which
|
|
70
|
+
* brainy calls during `brain.init()`, before any of those native engines
|
|
71
|
+
* open files under the brain root. Calling this at the top of each such
|
|
72
|
+
* factory makes the cortex-2.x guard fire at the right moment, against
|
|
73
|
+
* the real storage, on whichever factory brainy constructs first.
|
|
74
|
+
*
|
|
75
|
+
* A loud "service won't start" failure is recoverable; letting cor 3.0
|
|
76
|
+
* auto-engagement run alongside 2.x remnants produces a half-migrated
|
|
77
|
+
* state that's harder to undo. Bypass via env `COR_LEGACY_LAYOUT_GUARD=skip`
|
|
78
|
+
* (legacy `CORTEX_LEGACY_LAYOUT_GUARD` still honored) for the migration
|
|
79
|
+
* script itself.
|
|
80
|
+
*
|
|
81
|
+
* @param storage - The brainy storage adapter passed to a provider
|
|
82
|
+
* factory. Non-object / nullish values no-op (defensive).
|
|
83
|
+
* @throws If cortex 2.x on-disk markers are found under the storage root
|
|
84
|
+
* (see {@link assertNoLegacyLayout}).
|
|
85
|
+
* @example
|
|
86
|
+
* context.registerProvider('graphIndex', (storage) => {
|
|
87
|
+
* guardLegacyLayoutOnce(storage)
|
|
88
|
+
* return new NativeGraphAdjacencyIndex(storage)
|
|
89
|
+
* })
|
|
90
|
+
*/
|
|
91
|
+
export declare function guardLegacyLayoutOnce(storage: StorageWithBlobPath | null | undefined): void;
|
|
92
|
+
export {};
|
|
93
|
+
//# sourceMappingURL=legacyLayoutGuard.d.ts.map
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module legacyLayoutGuard
|
|
3
|
+
* @description Refuses to start when a cor 3.0 process is pointed
|
|
4
|
+
* at a directory that still holds cortex 2.x on-disk artifacts.
|
|
5
|
+
*
|
|
6
|
+
* The failure mode this prevents is the most dangerous in production:
|
|
7
|
+
* an operator deploys brainy 8.0 + cor 3.0 to a host whose data
|
|
8
|
+
* directory hasn't been migrated yet, the service starts,
|
|
9
|
+
* `brain.init()` fires, and either (a) crashes mid-init with a
|
|
10
|
+
* confusing low-level error from a shadow-page store, or (b) — worse —
|
|
11
|
+
* cortex's auto-engagement creates a new 3.0 file alongside the 2.x
|
|
12
|
+
* remnants, producing a half-migrated state that's harder to roll
|
|
13
|
+
* back than a clean stop.
|
|
14
|
+
*
|
|
15
|
+
* The guard runs at the top of `corPlugin.activate()`, before any
|
|
16
|
+
* provider registers. It scans the storage root for known cortex 2.x
|
|
17
|
+
* markers, and if any are found, throws a single error with the exact
|
|
18
|
+
* migration command the operator needs to run. **Throwing here is the
|
|
19
|
+
* point** — a service that won't start is recoverable; a service that
|
|
20
|
+
* starts on the wrong format isn't.
|
|
21
|
+
*
|
|
22
|
+
* The guard is no-op when:
|
|
23
|
+
* - The storage adapter isn't filesystem-backed (cortex 2.x markers
|
|
24
|
+
* are inherently filesystem-rooted; cloud adapters can't have
|
|
25
|
+
* them).
|
|
26
|
+
* - The storage root is empty (fresh install — no migration needed).
|
|
27
|
+
* - Only cor 3.0 markers are present (the migration completed,
|
|
28
|
+
* or this brain was created fresh on 3.0).
|
|
29
|
+
*
|
|
30
|
+
* The guard fires when:
|
|
31
|
+
* - `_id_mapper/uuid_to_int.mkv` is a FILE (cortex 2.x). In cortex
|
|
32
|
+
* 3.0 this is a DIRECTORY containing the shadow-page head pointer
|
|
33
|
+
* + base + delta.
|
|
34
|
+
* - `_id_mapper/int_to_uuid.bin` is a FILE (cortex 2.x). Same
|
|
35
|
+
* story — 3.0 makes this a directory.
|
|
36
|
+
* - `_metadata/` contains the cortex 2.x JSON chunk envelope shape
|
|
37
|
+
* without the 3.0 LSM shard subdirectory.
|
|
38
|
+
* - HNSW provider artifacts exist alongside no DiskANN directory.
|
|
39
|
+
*
|
|
40
|
+
* Bypass: setting `COR_LEGACY_LAYOUT_GUARD=skip` (legacy `CORTEX_LEGACY_LAYOUT_GUARD` still honored) in the environment
|
|
41
|
+
* disables the guard. This exists for the migration script itself
|
|
42
|
+
* (which needs to be able to read the legacy layout to take its
|
|
43
|
+
* snapshot) and for emergency debugging. Production code should never
|
|
44
|
+
* set this.
|
|
45
|
+
*/
|
|
46
|
+
import { existsSync, statSync, readdirSync } from 'node:fs';
|
|
47
|
+
import { join } from 'node:path';
|
|
48
|
+
const CORTEX_SUBDIRS = ['_id_mapper', '_metadata', '_diskann', '_graph_adjacency'];
|
|
49
|
+
/**
|
|
50
|
+
* Heuristic: cortex 2.x flat-file layout markers under `_id_mapper`.
|
|
51
|
+
* Cor 3.0 makes both of these directories instead.
|
|
52
|
+
*/
|
|
53
|
+
const LEGACY_ID_MAPPER_FILES = [
|
|
54
|
+
'_id_mapper/uuid_to_int.mkv',
|
|
55
|
+
'_id_mapper/int_to_uuid.bin',
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* Discover the storage root directory from the brainy
|
|
59
|
+
* `StorageAdapter`. Returns null for non-filesystem adapters (no
|
|
60
|
+
* legacy markers possible).
|
|
61
|
+
*/
|
|
62
|
+
function resolveStorageRoot(storage) {
|
|
63
|
+
if (!storage || typeof storage.getBinaryBlobPath !== 'function')
|
|
64
|
+
return null;
|
|
65
|
+
// Ask for a sentinel path; the returned path's parent is the
|
|
66
|
+
// storage root for our purposes.
|
|
67
|
+
const probe = storage.getBinaryBlobPath('__cortex_legacy_layout_guard_probe__');
|
|
68
|
+
if (!probe)
|
|
69
|
+
return null;
|
|
70
|
+
// The probe path doesn't exist; we want its containing directory.
|
|
71
|
+
// brainy's FileSystemStorage returns something like
|
|
72
|
+
// `${rootDirectory}/__cortex_legacy_layout_guard_probe__` or
|
|
73
|
+
// `${rootDirectory}/binaryBlobs/__cortex_legacy_layout_guard_probe__`.
|
|
74
|
+
// Walk up to the directory that contains `_id_mapper` or is the
|
|
75
|
+
// brain root.
|
|
76
|
+
let dir = probe;
|
|
77
|
+
// Strip the probe filename component.
|
|
78
|
+
dir = dir.replace(/\/[^/]+$/, '');
|
|
79
|
+
// If this is `binaryBlobs/`, go one more up.
|
|
80
|
+
if (dir.endsWith('/binaryBlobs')) {
|
|
81
|
+
dir = dir.slice(0, -'/binaryBlobs'.length);
|
|
82
|
+
}
|
|
83
|
+
if (!existsSync(dir))
|
|
84
|
+
return null;
|
|
85
|
+
return dir;
|
|
86
|
+
}
|
|
87
|
+
function findLegacyMarkers(root) {
|
|
88
|
+
const markers = [];
|
|
89
|
+
// (1) cortex 2.x flat-file id mapper layout.
|
|
90
|
+
for (const rel of LEGACY_ID_MAPPER_FILES) {
|
|
91
|
+
const p = join(root, rel);
|
|
92
|
+
if (!existsSync(p))
|
|
93
|
+
continue;
|
|
94
|
+
const stat = statSync(p);
|
|
95
|
+
if (stat.isFile()) {
|
|
96
|
+
markers.push({
|
|
97
|
+
path: p,
|
|
98
|
+
reason: `${rel} is a file (cortex 2.x format). ` +
|
|
99
|
+
`Cor 3.0 expects this to be a directory (shadow-page LSM store).`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// (2) cortex 2.x metadata index JSON chunk files at the metadata
|
|
104
|
+
// root, with no 3.0 LSM shard subdirectory.
|
|
105
|
+
const metadataDir = join(root, '_metadata');
|
|
106
|
+
if (existsSync(metadataDir) && statSync(metadataDir).isDirectory()) {
|
|
107
|
+
const entries = readdirSync(metadataDir);
|
|
108
|
+
const hasLsmShard = entries.some((e) => e === 'memtable' || e.startsWith('lsm-'));
|
|
109
|
+
const hasJsonChunks = entries.some((e) => e.endsWith('.json') || e.endsWith('.cidx'));
|
|
110
|
+
if (hasJsonChunks && !hasLsmShard) {
|
|
111
|
+
markers.push({
|
|
112
|
+
path: metadataDir,
|
|
113
|
+
reason: `${metadataDir} contains cortex 2.x JSON chunk files (.json/.cidx) but no 3.0 LSM shard layout. ` +
|
|
114
|
+
`Cor 3.0's metadata index is LSM-tree-backed.`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// (3) HNSW provider artifacts in `_diskann/` or root without DiskANN
|
|
119
|
+
// directory. The cortex 2.x HNSW provider wrote a `.hnsw` file at
|
|
120
|
+
// the root or under a `_hnsw/` subdirectory. Cor 3.0 writes
|
|
121
|
+
// `_diskann/` instead.
|
|
122
|
+
const candidateHnsw = readdirSync(root, { withFileTypes: true })
|
|
123
|
+
.filter((e) => e.isFile())
|
|
124
|
+
.filter((e) => e.name.endsWith('.hnsw'));
|
|
125
|
+
if (candidateHnsw.length > 0 && !existsSync(join(root, '_diskann'))) {
|
|
126
|
+
markers.push({
|
|
127
|
+
path: join(root, candidateHnsw[0].name),
|
|
128
|
+
reason: `${root} contains cortex 2.x HNSW provider files (.hnsw) with no 3.0 DiskANN directory. ` +
|
|
129
|
+
`Cor 3.0 uses Adaptive DiskANN as the default vector index.`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return markers;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Public guard. Call at the top of `corPlugin.activate()` with
|
|
136
|
+
* the brainy storage adapter. Throws if cortex 2.x markers are
|
|
137
|
+
* present.
|
|
138
|
+
*/
|
|
139
|
+
export function assertNoLegacyLayout(storage) {
|
|
140
|
+
// Dual-read during the cortex→cor rename: COR_ preferred, legacy CORTEX_ honored.
|
|
141
|
+
if ((process.env.COR_LEGACY_LAYOUT_GUARD ?? process.env.CORTEX_LEGACY_LAYOUT_GUARD) === 'skip') {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const root = resolveStorageRoot(storage);
|
|
145
|
+
if (!root)
|
|
146
|
+
return; // non-filesystem adapter or unresolvable; nothing to check
|
|
147
|
+
if (!existsSync(root))
|
|
148
|
+
return; // fresh install
|
|
149
|
+
const markers = findLegacyMarkers(root);
|
|
150
|
+
if (markers.length === 0)
|
|
151
|
+
return;
|
|
152
|
+
// Build the error message. The point is to be SO clear that an
|
|
153
|
+
// operator can act on it without reading docs.
|
|
154
|
+
const lines = [
|
|
155
|
+
'',
|
|
156
|
+
'╔══════════════════════════════════════════════════════════════════════════╗',
|
|
157
|
+
'║ Cor 3.0 cannot start: this storage directory holds cortex 2.x data. ║',
|
|
158
|
+
'╚══════════════════════════════════════════════════════════════════════════╝',
|
|
159
|
+
'',
|
|
160
|
+
`Storage root: ${root}`,
|
|
161
|
+
'',
|
|
162
|
+
'Cortex 2.x markers found:',
|
|
163
|
+
...markers.map((m) => ` • ${m.reason}`),
|
|
164
|
+
'',
|
|
165
|
+
'Cor 3.0 broke the on-disk format intentionally to ship snapshot-safe',
|
|
166
|
+
'shadow-page LSM stores + Adaptive DiskANN. Reading 2.x files in 3.0 is not',
|
|
167
|
+
'supported by design.',
|
|
168
|
+
'',
|
|
169
|
+
'🚨 Do NOT delete this directory. Your data is intact in the 2.x format.',
|
|
170
|
+
'',
|
|
171
|
+
'To migrate (zero data loss, ~10 minutes for Venue-scale brains):',
|
|
172
|
+
'',
|
|
173
|
+
' node node_modules/@soulcraft/cor/scripts/migrate-cortex-2x-to-3x.mjs \\',
|
|
174
|
+
` --brain-dir ${root} \\`,
|
|
175
|
+
` --backup-dir ${root}/../backups/pre-3.0`,
|
|
176
|
+
'',
|
|
177
|
+
'Full migration guide: docs/migration-3.0.md',
|
|
178
|
+
'Venue-specific worked example in docs/migration-3.0.md § Worked example — Venue',
|
|
179
|
+
'',
|
|
180
|
+
'To rollback instead: downgrade @soulcraft/brainy + @soulcraft/cortex to',
|
|
181
|
+
'your previous versions (e.g. brainy@7.31.6 cortex@2.7.0) — no data',
|
|
182
|
+
'changes have happened yet because cor 3.0 refused to start.',
|
|
183
|
+
'',
|
|
184
|
+
];
|
|
185
|
+
throw new Error(lines.join('\n'));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Per-storage memo so the (filesystem-scanning) guard runs at most once
|
|
189
|
+
* per brain. brainy invokes each storage-receiving provider factory
|
|
190
|
+
* independently during `brain.init()`; without this, the same root would
|
|
191
|
+
* be re-scanned for every factory. A `WeakSet` keyed on the storage
|
|
192
|
+
* object also means distinct brains (distinct storage adapters) are each
|
|
193
|
+
* guarded independently, and the memo is collected with the brain.
|
|
194
|
+
*/
|
|
195
|
+
const guardedStorages = new WeakSet();
|
|
196
|
+
/**
|
|
197
|
+
* @description Run {@link assertNoLegacyLayout} exactly once per storage
|
|
198
|
+
* adapter, from inside cor's storage-receiving provider factories.
|
|
199
|
+
*
|
|
200
|
+
* This is the load-bearing entry point under brainy 8.0. The plugin
|
|
201
|
+
* `activate(context)` hook receives a context that does **not** expose
|
|
202
|
+
* the brain's storage — it carries only `registerProvider` + `version` —
|
|
203
|
+
* so a guard call there scans `undefined` and silently passes, defeating
|
|
204
|
+
* the safety net. The storage adapter is only handed to the *provider
|
|
205
|
+
* factories* (`metadataIndex`, `graphIndex`, `entityIdMapper`), which
|
|
206
|
+
* brainy calls during `brain.init()`, before any of those native engines
|
|
207
|
+
* open files under the brain root. Calling this at the top of each such
|
|
208
|
+
* factory makes the cortex-2.x guard fire at the right moment, against
|
|
209
|
+
* the real storage, on whichever factory brainy constructs first.
|
|
210
|
+
*
|
|
211
|
+
* A loud "service won't start" failure is recoverable; letting cor 3.0
|
|
212
|
+
* auto-engagement run alongside 2.x remnants produces a half-migrated
|
|
213
|
+
* state that's harder to undo. Bypass via env `COR_LEGACY_LAYOUT_GUARD=skip`
|
|
214
|
+
* (legacy `CORTEX_LEGACY_LAYOUT_GUARD` still honored) for the migration
|
|
215
|
+
* script itself.
|
|
216
|
+
*
|
|
217
|
+
* @param storage - The brainy storage adapter passed to a provider
|
|
218
|
+
* factory. Non-object / nullish values no-op (defensive).
|
|
219
|
+
* @throws If cortex 2.x on-disk markers are found under the storage root
|
|
220
|
+
* (see {@link assertNoLegacyLayout}).
|
|
221
|
+
* @example
|
|
222
|
+
* context.registerProvider('graphIndex', (storage) => {
|
|
223
|
+
* guardLegacyLayoutOnce(storage)
|
|
224
|
+
* return new NativeGraphAdjacencyIndex(storage)
|
|
225
|
+
* })
|
|
226
|
+
*/
|
|
227
|
+
export function guardLegacyLayoutOnce(storage) {
|
|
228
|
+
if (!storage || typeof storage !== 'object')
|
|
229
|
+
return;
|
|
230
|
+
if (guardedStorages.has(storage))
|
|
231
|
+
return;
|
|
232
|
+
assertNoLegacyLayout(storage);
|
|
233
|
+
// Only memoize a CLEAN result: a storage that threw is never marked,
|
|
234
|
+
// so a legacy layout keeps failing loudly on every entry point.
|
|
235
|
+
guardedStorages.add(storage);
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=legacyLayoutGuard.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License constants (v2)
|
|
3
|
+
*
|
|
4
|
+
* Crypto keys are in Rust. This file keeps paths and URLs only.
|
|
5
|
+
*/
|
|
6
|
+
/** Default heartbeat check interval (24h) — server may override via nextCheckSeconds */
|
|
7
|
+
export declare const DEFAULT_CHECK_INTERVAL_MS: number;
|
|
8
|
+
/** Grace period in days after subscription lapse detected */
|
|
9
|
+
export declare const GRACE_PERIOD_DAYS = 14;
|
|
10
|
+
/** Online validation timeout in milliseconds */
|
|
11
|
+
export declare const ONLINE_VALIDATION_TIMEOUT_MS = 10000;
|
|
12
|
+
/** Portal heartbeat validation API URL */
|
|
13
|
+
export declare const API_URL = "https://soulcraft.com/api/license/validate";
|
|
14
|
+
/** Short code exchange API URL */
|
|
15
|
+
export declare const EXCHANGE_URL = "https://soulcraft.com/api/license/exchange";
|
|
16
|
+
/** Pricing URL for license purchase */
|
|
17
|
+
export declare const PRICING_URL = "https://soulcraft.com/pricing?focus=pro";
|
|
18
|
+
/**
|
|
19
|
+
* Pattern matching activation short codes (CO-XXXX-XXXX). The legacy `CX-`
|
|
20
|
+
* prefix is also accepted during the cortex→cor rename so codes already issued
|
|
21
|
+
* by Portal keep working; Portal issues `CO-` going forward.
|
|
22
|
+
*/
|
|
23
|
+
export declare const SHORT_CODE_PATTERN: RegExp;
|
|
24
|
+
/** Project-local config file name */
|
|
25
|
+
export declare const CONFIG_FILE = ".soulcraft.json";
|
|
26
|
+
/** Project-local cache directory (gitignored) */
|
|
27
|
+
export declare const CACHE_DIR = ".soulcraft";
|
|
28
|
+
/** Heartbeat cache file */
|
|
29
|
+
export declare const HEARTBEAT_FILE = "heartbeat.json";
|
|
30
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License constants (v2)
|
|
3
|
+
*
|
|
4
|
+
* Crypto keys are in Rust. This file keeps paths and URLs only.
|
|
5
|
+
*/
|
|
6
|
+
/** Default heartbeat check interval (24h) — server may override via nextCheckSeconds */
|
|
7
|
+
export const DEFAULT_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
8
|
+
/** Grace period in days after subscription lapse detected */
|
|
9
|
+
export const GRACE_PERIOD_DAYS = 14;
|
|
10
|
+
/** Online validation timeout in milliseconds */
|
|
11
|
+
export const ONLINE_VALIDATION_TIMEOUT_MS = 10_000;
|
|
12
|
+
/** Portal heartbeat validation API URL */
|
|
13
|
+
export const API_URL = 'https://soulcraft.com/api/license/validate';
|
|
14
|
+
/** Short code exchange API URL */
|
|
15
|
+
export const EXCHANGE_URL = 'https://soulcraft.com/api/license/exchange';
|
|
16
|
+
/** Pricing URL for license purchase */
|
|
17
|
+
export const PRICING_URL = 'https://soulcraft.com/pricing?focus=pro';
|
|
18
|
+
/**
|
|
19
|
+
* Pattern matching activation short codes (CO-XXXX-XXXX). The legacy `CX-`
|
|
20
|
+
* prefix is also accepted during the cortex→cor rename so codes already issued
|
|
21
|
+
* by Portal keep working; Portal issues `CO-` going forward.
|
|
22
|
+
*/
|
|
23
|
+
export const SHORT_CODE_PATTERN = /^(?:CO|CX)-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
|
|
24
|
+
/** Project-local config file name */
|
|
25
|
+
export const CONFIG_FILE = '.soulcraft.json';
|
|
26
|
+
/** Project-local cache directory (gitignored) */
|
|
27
|
+
export const CACHE_DIR = '.soulcraft';
|
|
28
|
+
/** Heartbeat cache file */
|
|
29
|
+
export const HEARTBEAT_FILE = 'heartbeat.json';
|
|
30
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Online license heartbeat (v2)
|
|
3
|
+
*
|
|
4
|
+
* Background check against Portal API for:
|
|
5
|
+
* - Status-based subscription state (active, past_due, canceled, etc.)
|
|
6
|
+
* - Key renewal (subscription renewed, fresh JWT cached in memory)
|
|
7
|
+
* - Server-driven check intervals via nextCheckSeconds
|
|
8
|
+
*
|
|
9
|
+
* No signature verification here — that's in Rust.
|
|
10
|
+
* Default 24h heartbeat interval, 14-day grace period.
|
|
11
|
+
*/
|
|
12
|
+
interface HeartbeatCache {
|
|
13
|
+
lastCheck: string;
|
|
14
|
+
nextCheckAt?: string;
|
|
15
|
+
status?: string;
|
|
16
|
+
gracePeriodStart?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read the heartbeat cache from disk. Returns null if not found or unreadable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readHeartbeatCache(): Promise<HeartbeatCache | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Check if a grace period start timestamp is expired (> GRACE_PERIOD_DAYS old).
|
|
24
|
+
*/
|
|
25
|
+
export declare function isGracePeriodExpired(gracePeriodStart: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Perform background heartbeat check.
|
|
28
|
+
*
|
|
29
|
+
* Call this after successful Rust validation. Non-blocking (fire and forget).
|
|
30
|
+
* Returns a warning string if the user should be notified.
|
|
31
|
+
* Returns degrade: true if the license is suspended or the grace period has expired.
|
|
32
|
+
*
|
|
33
|
+
* @param token - License key token
|
|
34
|
+
* @param options.forceCheck - Skip interval check, always call API (use at process startup)
|
|
35
|
+
*/
|
|
36
|
+
export declare function performHeartbeat(token: string, options?: {
|
|
37
|
+
forceCheck?: boolean;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
warning?: string;
|
|
40
|
+
degrade?: boolean;
|
|
41
|
+
}>;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=onlineValidator.d.ts.map
|