@soulcraft/cor 3.0.13 → 3.0.15
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/dist/native/napi.js +21 -1
- package/dist/utils/NativeMetadataIndex.js +49 -13
- package/dist/utils/binaryIdMapperFactory.js +33 -3
- package/dist/version.d.ts +15 -0
- package/dist/version.js +15 -0
- package/package.json +1 -1
package/dist/native/napi.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { join, dirname } from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { COR_VERSION } from '../version.js';
|
|
14
15
|
let cachedBindings = null;
|
|
15
16
|
/** Platform keys matching napi-rs build output */
|
|
16
17
|
const PLATFORM_KEY_MAP = {
|
|
@@ -56,6 +57,7 @@ export function loadViaNapi() {
|
|
|
56
57
|
try {
|
|
57
58
|
const bindings = require(binaryPath);
|
|
58
59
|
cachedBindings = bindings;
|
|
60
|
+
logVersionBannerOnce(binaryPath);
|
|
59
61
|
return bindings;
|
|
60
62
|
}
|
|
61
63
|
catch (err) {
|
|
@@ -66,15 +68,33 @@ export function loadViaNapi() {
|
|
|
66
68
|
try {
|
|
67
69
|
const bindings = require(devPath);
|
|
68
70
|
cachedBindings = bindings;
|
|
71
|
+
logVersionBannerOnce(devPath);
|
|
69
72
|
return bindings;
|
|
70
73
|
}
|
|
71
74
|
catch (err) {
|
|
72
75
|
failures.push(` - ${devPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
73
76
|
}
|
|
74
|
-
throw new Error(`Failed to load
|
|
77
|
+
throw new Error(`[cor v${COR_VERSION}] Failed to load native module for ${key}:\n` +
|
|
75
78
|
`${failures.join('\n')}\n` +
|
|
76
79
|
`Build locally: cd native && npm run build`);
|
|
77
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* One-line version banner at first successful native load — the on-box
|
|
83
|
+
* version check (Gate Zero) as a journal one-liner. COR_VERSION is a CODE
|
|
84
|
+
* constant, so a bundled service (bun-compiled memory) logs the version its
|
|
85
|
+
* bundle actually CONTAINS — the 2026-07-13 phantom deploy (a fresh-looking
|
|
86
|
+
* bundle carrying a stale engine, no way to tell on-box) becomes a grep of
|
|
87
|
+
* the boot log. Once per process; console.error (not prodLog) so it appears
|
|
88
|
+
* even under silent/quiet logger configs — it is operational evidence, not
|
|
89
|
+
* chatter.
|
|
90
|
+
*/
|
|
91
|
+
let versionBannerLogged = false;
|
|
92
|
+
function logVersionBannerOnce(loadedFrom) {
|
|
93
|
+
if (versionBannerLogged)
|
|
94
|
+
return;
|
|
95
|
+
versionBannerLogged = true;
|
|
96
|
+
console.error(`[cor v${COR_VERSION}] native binary loaded (${loadedFrom})`);
|
|
97
|
+
}
|
|
78
98
|
/**
|
|
79
99
|
* Synchronously load a Node.js built-in module.
|
|
80
100
|
* Workaround for ESM dynamic import restrictions.
|
|
@@ -2710,13 +2710,27 @@ export class MetadataIndexManager {
|
|
|
2710
2710
|
let processed = 0;
|
|
2711
2711
|
let sinceFlush = 0;
|
|
2712
2712
|
let noMetadata = 0;
|
|
2713
|
+
// Heal observability (CORTEX-RESTART-STRAND, memory's ask): a rebuild
|
|
2714
|
+
// must never be silent for minutes — the operator cannot distinguish
|
|
2715
|
+
// converging from stuck. Every 500 posted entities logs progress with
|
|
2716
|
+
// the sustained rate, so a pathological per-item cost names itself in
|
|
2717
|
+
// the journal in real time.
|
|
2718
|
+
const startedAt = Date.now();
|
|
2719
|
+
let lastProgressAt = 0;
|
|
2720
|
+
const fetchPage = (c) => kind === 'noun'
|
|
2721
|
+
? this.storage.getNouns({ pagination: { limit: PAGE, cursor: c } })
|
|
2722
|
+
: this.storage.getVerbs({ pagination: { limit: PAGE, cursor: c } });
|
|
2723
|
+
// Prefetch pipeline: the NEXT canonical page is fetched while the
|
|
2724
|
+
// CURRENT page indexes, so adapter enumeration latency overlaps the
|
|
2725
|
+
// RAM-speed posting work instead of adding to it.
|
|
2726
|
+
let pending = fetchPage(cursor);
|
|
2713
2727
|
while (hasMore) {
|
|
2714
|
-
const page =
|
|
2715
|
-
? await this.storage.getNouns({ pagination: { limit: PAGE, cursor } })
|
|
2716
|
-
: await this.storage.getVerbs({ pagination: { limit: PAGE, cursor } });
|
|
2728
|
+
const page = await pending;
|
|
2717
2729
|
const items = page.items;
|
|
2718
2730
|
if (items.length === 0)
|
|
2719
2731
|
break; // empty page = done (defensive backstop)
|
|
2732
|
+
if (page.hasMore)
|
|
2733
|
+
pending = fetchPage(page.nextCursor);
|
|
2720
2734
|
const ids = items.map((it) => it.id);
|
|
2721
2735
|
const md = await this.loadRebuildMetadataBatch(kind, ids);
|
|
2722
2736
|
for (const it of items) {
|
|
@@ -2733,6 +2747,12 @@ export class MetadataIndexManager {
|
|
|
2733
2747
|
if (metadata) {
|
|
2734
2748
|
await this.addToIndex(it.id, metadata, true, true);
|
|
2735
2749
|
processed++;
|
|
2750
|
+
if (processed - lastProgressAt >= 500) {
|
|
2751
|
+
lastProgressAt = processed;
|
|
2752
|
+
const elapsedS = (Date.now() - startedAt) / 1000;
|
|
2753
|
+
prodLog.info(`[NativeMetadataIndex] rebuild(${kind}): ${processed} posted ` +
|
|
2754
|
+
`in ${elapsedS.toFixed(1)}s (${(processed / Math.max(elapsedS, 0.001)).toFixed(0)}/s)`);
|
|
2755
|
+
}
|
|
2736
2756
|
if (++sinceFlush >= 5000) {
|
|
2737
2757
|
await this.flushRebuildDirty();
|
|
2738
2758
|
sinceFlush = 0;
|
|
@@ -2743,9 +2763,12 @@ export class MetadataIndexManager {
|
|
|
2743
2763
|
}
|
|
2744
2764
|
}
|
|
2745
2765
|
hasMore = page.hasMore;
|
|
2746
|
-
cursor = page.nextCursor;
|
|
2747
2766
|
await this.yieldToEventLoop();
|
|
2748
2767
|
}
|
|
2768
|
+
const totalS = (Date.now() - startedAt) / 1000;
|
|
2769
|
+
prodLog.info(`[NativeMetadataIndex] rebuild(${kind}): COMPLETE — ${processed} posted ` +
|
|
2770
|
+
`in ${totalS.toFixed(1)}s (${(processed / Math.max(totalS, 0.001)).toFixed(0)}/s)` +
|
|
2771
|
+
(noMetadata > 0 ? `, ${noMetadata} without metadata` : ''));
|
|
2749
2772
|
if (noMetadata > 0) {
|
|
2750
2773
|
prodLog.warn(`[NativeMetadataIndex] rebuild(${kind}): ${noMetadata} enumerated ` +
|
|
2751
2774
|
`entit${noMetadata === 1 ? 'y' : 'ies'} carried no canonical ` +
|
|
@@ -2793,16 +2816,29 @@ export class MetadataIndexManager {
|
|
|
2793
2816
|
else if (this.storage.getVerbMetadataBatch) {
|
|
2794
2817
|
return this.storage.getVerbMetadataBatch(ids);
|
|
2795
2818
|
}
|
|
2819
|
+
// Bounded-concurrency reads (16-way). The reads are independent, and a
|
|
2820
|
+
// SERIAL per-id walk multiplies the adapter's per-op latency by N — on
|
|
2821
|
+
// a slow adapter (memory-vm measured ~1s/op shapes) that turned a
|
|
2822
|
+
// seconds-long heal into ~1s/entity (CORTEX-RESTART-STRAND, the
|
|
2823
|
+
// 52-minute heal). Concurrency divides heal time by ~16 on ANY
|
|
2824
|
+
// adapter; fail-loud is preserved (a faulted read rejects the whole
|
|
2825
|
+
// batch via Promise.all — readRebuildMetadataOrThrow retries once then
|
|
2826
|
+
// throws, never silently excludes).
|
|
2796
2827
|
const m = new Map();
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2828
|
+
const CONCURRENCY = 16;
|
|
2829
|
+
let next = 0;
|
|
2830
|
+
const workers = Array.from({ length: Math.min(CONCURRENCY, ids.length) }, async () => {
|
|
2831
|
+
for (;;) {
|
|
2832
|
+
const i = next++;
|
|
2833
|
+
if (i >= ids.length)
|
|
2834
|
+
break;
|
|
2835
|
+
const id = ids[i];
|
|
2836
|
+
const md = await this.readRebuildMetadataOrThrow(kind, id);
|
|
2837
|
+
if (md)
|
|
2838
|
+
m.set(id, md);
|
|
2839
|
+
}
|
|
2840
|
+
});
|
|
2841
|
+
await Promise.all(workers);
|
|
2806
2842
|
return m;
|
|
2807
2843
|
}
|
|
2808
2844
|
/**
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* brain pays only for what it uses. This is the constant-RAM,
|
|
16
16
|
* billion-scale store that replaces the resident JSON id-maps (#72).
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
18
|
+
import { statSync } from 'node:fs';
|
|
19
19
|
import { loadNativeModule } from '../native/index.js';
|
|
20
20
|
/** Default storage key for the extendible-hash UUID → int file. */
|
|
21
21
|
export const DEFAULT_UUID_TO_INT_KEY = '_id_mapper/uuid_to_int.mkv';
|
|
@@ -65,10 +65,17 @@ export function openOrCreateBinaryIdMapper(storage, opts = {}) {
|
|
|
65
65
|
// Both files must exist together (paired write semantics). A
|
|
66
66
|
// half-present state is crash corruption surfaced as an error,
|
|
67
67
|
// never silently recreated.
|
|
68
|
+
//
|
|
69
|
+
// Fault-vs-absent (spine ADR-004 §4): the probes below THROW on any stat
|
|
70
|
+
// fault other than a clean ENOENT. `existsSync` returned false on
|
|
71
|
+
// EACCES/EIO/EMFILE too — and a double false routed to `create()`, which
|
|
72
|
+
// TRUNCATES both stores: a transient permissions blip (backup tool,
|
|
73
|
+
// scanner, remount) could wipe the brain's entire UUID↔int allocator.
|
|
74
|
+
// Deciding open-vs-create is only legal on certain knowledge.
|
|
68
75
|
let mapper;
|
|
69
76
|
let created;
|
|
70
|
-
const uuidFileExists =
|
|
71
|
-
const intFileExists =
|
|
77
|
+
const uuidFileExists = durableFilePresent(uuidToIntPath, uuidToIntKey);
|
|
78
|
+
const intFileExists = durableFilePresent(intToUuidPath, intToUuidKey);
|
|
72
79
|
if (uuidFileExists && intFileExists) {
|
|
73
80
|
mapper = NativeBinaryIdMapper.openExisting(config);
|
|
74
81
|
created = false;
|
|
@@ -91,4 +98,27 @@ export function openOrCreateBinaryIdMapper(storage, opts = {}) {
|
|
|
91
98
|
created,
|
|
92
99
|
};
|
|
93
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Fault-discriminating existence probe for the id-mapper stores (spine
|
|
103
|
+
* ADR-004 §4). Returns `true`/`false` only on CERTAIN knowledge; any stat
|
|
104
|
+
* fault other than a clean ENOENT throws — because the caller's "neither
|
|
105
|
+
* file exists" branch invokes `create()`, which TRUNCATES, and a transient
|
|
106
|
+
* EACCES/EIO must never be allowed to masquerade as a fresh brain.
|
|
107
|
+
*/
|
|
108
|
+
function durableFilePresent(path, key) {
|
|
109
|
+
try {
|
|
110
|
+
statSync(path);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const code = error.code;
|
|
115
|
+
if (code === 'ENOENT')
|
|
116
|
+
return false;
|
|
117
|
+
throw new Error(`openOrCreateBinaryIdMapper: existence probe FAULTED for '${key}' ` +
|
|
118
|
+
`(${code ?? 'unknown'}) — refusing to decide open-vs-create on a ` +
|
|
119
|
+
`fault (create() truncates the brain's id allocator; a transient ` +
|
|
120
|
+
`IO/permissions fault must never wipe it). Restore storage health ` +
|
|
121
|
+
`and reopen. Cause: ${String(error)}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
94
124
|
//# sourceMappingURL=binaryIdMapperFactory.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module version
|
|
3
|
+
* @description The package version as a CODE constant — deliberately not a
|
|
4
|
+
* `package.json` read. Consumers that bundle cor (bun-compiled services like
|
|
5
|
+
* memory) inline this literal at build time, so the version a process LOGS
|
|
6
|
+
* is provably the version its bundle CONTAINS. A runtime `package.json`
|
|
7
|
+
* lookup would report whatever file happens to sit near the bundle — the
|
|
8
|
+
* exact ambiguity behind the 2026-07-13 phantom-deploy incident, where a
|
|
9
|
+
* "fresh" bundle silently carried a stale engine and no on-box check could
|
|
10
|
+
* tell. `scripts/release.sh` bumps this in lockstep with `package.json`;
|
|
11
|
+
* `check:brainy`-style drift between the two fails the release.
|
|
12
|
+
*/
|
|
13
|
+
/** The @soulcraft/cor version this build was cut from. */
|
|
14
|
+
export declare const COR_VERSION = "3.0.15";
|
|
15
|
+
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module version
|
|
3
|
+
* @description The package version as a CODE constant — deliberately not a
|
|
4
|
+
* `package.json` read. Consumers that bundle cor (bun-compiled services like
|
|
5
|
+
* memory) inline this literal at build time, so the version a process LOGS
|
|
6
|
+
* is provably the version its bundle CONTAINS. A runtime `package.json`
|
|
7
|
+
* lookup would report whatever file happens to sit near the bundle — the
|
|
8
|
+
* exact ambiguity behind the 2026-07-13 phantom-deploy incident, where a
|
|
9
|
+
* "fresh" bundle silently carried a stale engine and no on-box check could
|
|
10
|
+
* tell. `scripts/release.sh` bumps this in lockstep with `package.json`;
|
|
11
|
+
* `check:brainy`-style drift between the two fails the release.
|
|
12
|
+
*/
|
|
13
|
+
/** The @soulcraft/cor version this build was cut from. */
|
|
14
|
+
export const COR_VERSION = '3.0.15';
|
|
15
|
+
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/cor",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.15",
|
|
4
4
|
"description": "Native Rust acceleration for Brainy — 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",
|