@soulcraft/cor 3.0.14 → 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.
@@ -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 Brainy native module for ${key}:\n` +
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 = kind === 'noun'
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
- for (const id of ids) {
2798
- // Retry-once-then-throw (readRebuildMetadataOrThrow): a faulted
2799
- // read must fail the rebuild loudly, never silently exclude the
2800
- // entity — a swallowed fault here completed the rebuild
2801
- // under-posted (the strand bug re-created by its own healer).
2802
- const md = await this.readRebuildMetadataOrThrow(kind, id);
2803
- if (md)
2804
- m.set(id, md);
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 { existsSync } from 'node:fs';
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 = existsSync(uuidToIntPath);
71
- const intFileExists = existsSync(intToUuidPath);
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
@@ -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.14",
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",