@soulcraft/cor 3.0.21 → 3.0.22

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.
@@ -1145,28 +1145,52 @@ export class MetadataIndexManager {
1145
1145
  // Initialization
1146
1146
  // ==========================================================================
1147
1147
  async init() {
1148
+ // Init-phase narration (loop-termination doctrine): every phase of
1149
+ // open stamps its elapsed time on the ops channel, so a slow or
1150
+ // wedged boot names its own phase instead of presenting as a silent
1151
+ // hang after the last incidental log line (the 2026-07-18 incident
1152
+ // burned two gate-hours localizing a freeze that one of these
1153
+ // lines would have named instantly). Threshold-gated: a phase
1154
+ // faster than INIT_PHASE_NARRATE_MS stays silent — narration in
1155
+ // proportion to the work.
1156
+ const initStarted = Date.now();
1157
+ let phaseStarted = initStarted;
1158
+ const INIT_PHASE_NARRATE_MS = 1000;
1159
+ const phase = (name) => {
1160
+ const now = Date.now();
1161
+ if (now - phaseStarted >= INIT_PHASE_NARRATE_MS) {
1162
+ opsLine(`init phase '${name}' took ${((now - phaseStarted) / 1000).toFixed(1)}s ` +
1163
+ `(${((now - initStarted) / 1000).toFixed(1)}s total)`);
1164
+ }
1165
+ phaseStarted = now;
1166
+ };
1148
1167
  // **Piece 11 closeout.** Detect cortex 2.x legacy JSON metadata state on
1149
1168
  // disk. The cor 3.0 LSM engine is the only metadata engine, so an
1150
1169
  // un-migrated 2.x brain would have its on-disk JSON state shadowed and
1151
1170
  // surface as a mysteriously-empty brain on first query. Throw a clear
1152
1171
  // migration error instead so the operator runs the migration script.
1153
1172
  await this.detectLegacyJsonStateAndGate();
1173
+ phase('legacy-gate');
1154
1174
  // Load field registry to discover persisted indices
1155
1175
  await this.loadFieldRegistry();
1176
+ phase('field-registry');
1156
1177
  // #72 Phase C: build the shared mmap BinaryIdMapper from the brain's
1157
1178
  // `_id_mapper/*` paths and inject it as the core's canonical
1158
1179
  // UUID ↔ entity-int allocator. Must run before columnStore.init (which
1159
1180
  // resolves ids through getIdMapper()) and before any mutation.
1160
1181
  this.wireIdMapper();
1182
+ phase('id-mapper');
1161
1183
  // ADR-004 §7: declare this provider's protected families (feature-
1162
1184
  // detected on brainy ≥8.3.0; idempotent upsert; loud-but-non-blocking).
1163
1185
  await declareDerivedFamilies(this.storage, METADATA_FAMILIES);
1186
+ phase('family-declarations');
1164
1187
  // LSM durability (steps 3-4): open the native LSM engine here (not in the
1165
1188
  // constructor) so it can be seeded from the on-disk manifest and cold-load
1166
1189
  // every persisted SSTable into its live read set — the entire restore,
1167
1190
  // with NO O(N) rebuild. Runs after wireIdMapper (the engine's posting
1168
1191
  // lists reference the mapper's entity ints) and before any read/mutation.
1169
1192
  await this.openLsmEngine();
1193
+ phase('lsm-open');
1170
1194
  // LOST-MAPPER GUARD (MEMORY-COR-IDMAP-MIGRATION, root-caused
1171
1195
  // 2026-07-14): the ONE genuinely dangerous open-state — the mmap
1172
1196
  // mapper was just created EMPTY while durable int-keyed postings
@@ -1205,17 +1229,20 @@ export class MetadataIndexManager {
1205
1229
  // or an absent/corrupt marker) does any further work. See
1206
1230
  // {@link guardIndexEpoch}.
1207
1231
  await this.guardIndexEpoch();
1232
+ phase('epoch-guard');
1208
1233
  // Initialize the column store with the shared storage + id mapper façade.
1209
1234
  // Discovers any persisted `_column_index/` segments and re-applies tombstones.
1210
1235
  // Done before the rebuild branch below so that a rebuild repopulates a
1211
1236
  // freshly-initialized column store via addToIndex.
1212
1237
  await this.columnStore.init(this.storage, this.getIdMapper());
1238
+ phase('column-store');
1213
1239
  const hasFields = this.knownFields.size > 0;
1214
1240
  if (!hasFields) {
1215
1241
  // Don't trust "empty" — field registry may be missing due to interrupted flush.
1216
1242
  // Probe storage for actual entities before concluding the workspace is empty.
1217
1243
  try {
1218
1244
  const probe = await this.storage.getNouns({ pagination: { limit: 1, offset: 0 } });
1245
+ phase('entity-probe');
1219
1246
  const hasEntities = (probe.totalCount ?? 0) > 0 || probe.items.length > 0;
1220
1247
  if (hasEntities) {
1221
1248
  // **LSM durability (step 6) — rebuild demoted to disaster-recovery.**
@@ -1239,6 +1266,7 @@ export class MetadataIndexManager {
1239
1266
  // lines). The strand check is boot-path-invariant: it runs
1240
1267
  // here too, with the same verified-unpostable allowance.
1241
1268
  await this.detectDerivedStrand();
1269
+ phase('strand-detect');
1242
1270
  this.readinessInitialized = true;
1243
1271
  return;
1244
1272
  }
@@ -1266,8 +1294,10 @@ export class MetadataIndexManager {
1266
1294
  catch {
1267
1295
  // marker read failed — fall through to the normal rebuild path
1268
1296
  }
1297
+ phase('format-marker');
1269
1298
  console.warn(`[NativeMetadataIndex] Field registry missing but ${probe.totalCount ?? 'unknown'} entities exist — rebuilding index`);
1270
1299
  await this.rebuild();
1300
+ phase('rebuild');
1271
1301
  // A completed rebuild IS the readiness determination for this
1272
1302
  // open (sweep finding: these early exits previously left
1273
1303
  // readinessInitialized false — a fresh-rebuilt brain reported
@@ -1287,7 +1317,9 @@ export class MetadataIndexManager {
1287
1317
  return;
1288
1318
  }
1289
1319
  await this.warmCache();
1320
+ phase('warm-cache');
1290
1321
  await this.lazyLoadCounts();
1322
+ phase('lazy-counts');
1291
1323
  this.syncTypeCountsToFixed();
1292
1324
  // Strand detection (CORTEX-CONSOLIDATION-SILENT-DROP): the LSM can load
1293
1325
  // "durable state" that is silently MISSING postings — deleted shard logs
@@ -1296,6 +1328,7 @@ export class MetadataIndexManager {
1296
1328
  // contract reports ready, so brainy's gate heals via rebuild-from-
1297
1329
  // canonical instead of trusting a partial index.
1298
1330
  await this.detectDerivedStrand();
1331
+ phase('strand-detect');
1299
1332
  // **#18 migration kick — LAST, after the index is fully constructed.** If the
1300
1333
  // epoch guard flagged a large stale brain (outcome 4), start the coordinated
1301
1334
  // blocking migration NOW: `start()` flips the lock (`isMigrating()` → true)
@@ -1327,6 +1360,7 @@ export class MetadataIndexManager {
1327
1360
  this.lostMapperHeal = false;
1328
1361
  if (coordinator) {
1329
1362
  await coordinator.start();
1363
+ phase('lost-mapper-heal');
1330
1364
  }
1331
1365
  // No coordinator (raw/test usage): strandDetected already gates
1332
1366
  // the metadata leg; the loud LOST-MAPPER ops line stands.
@@ -1434,6 +1468,20 @@ export class MetadataIndexManager {
1434
1468
  async detectDerivedStrand() {
1435
1469
  if (!this.lsmEngine || !this.lsmMetaDir)
1436
1470
  return;
1471
+ // Stage narration (loop-termination doctrine, same contract as init's
1472
+ // phase stamps): every stage of this detector that touches storage or
1473
+ // the fact log stamps its elapsed time on the ops channel when it
1474
+ // exceeds 1s, so a slow open names its stage instead of presenting as
1475
+ // a silent hang after init's 'column-store' line. Threshold-gated —
1476
+ // narration in proportion to the work.
1477
+ let stageStarted = Date.now();
1478
+ const stage = (name) => {
1479
+ const now = Date.now();
1480
+ if (now - stageStarted >= 1000) {
1481
+ opsLine(`strand-detect stage '${name}' took ${((now - stageStarted) / 1000).toFixed(1)}s`);
1482
+ }
1483
+ stageStarted = now;
1484
+ };
1437
1485
  // M3 PD-4 (Stage 1): a coherent-but-BEHIND family stamp means the
1438
1486
  // postings simply predate the newest commits — replay EXACTLY the gap
1439
1487
  // from the generation fact log before any strand verdict, so the
@@ -1444,18 +1492,26 @@ export class MetadataIndexManager {
1444
1492
  try {
1445
1493
  const fns = await familyStampFns();
1446
1494
  const head = await readCommittedGeneration(this.storage);
1495
+ stage('replay-precheck');
1447
1496
  if (fns && head !== undefined) {
1448
1497
  const stamp = (await fns.readFamilyStamp(this.storage, METADATA_STAMP_PATH));
1449
1498
  const src = stamp?.family === METADATA_FAMILY ? stamp.sourceGeneration : undefined;
1499
+ stage('stamp-read');
1450
1500
  if (typeof src === 'number' && src < head) {
1451
- if (await this.replayFactGap(src + 1)) {
1501
+ const replayed = await this.replayFactGap(src + 1);
1502
+ stage('fact-replay');
1503
+ if (replayed) {
1452
1504
  await this.persistMetadataStamp();
1505
+ stage('stamp-persist');
1453
1506
  }
1454
1507
  }
1455
1508
  }
1456
1509
  }
1457
1510
  catch {
1458
- // Detector below stays authoritative.
1511
+ // Detector below stays authoritative. The stage marker still fires
1512
+ // so a slow-then-faulted stamp read/replay is attributed HERE, not
1513
+ // silently rolled into the next stage's elapsed time.
1514
+ stage('m3-replay-fault');
1459
1515
  }
1460
1516
  // 0. Replay-stats verdict (3.0.12). The native open now RECREATES a
1461
1517
  // missing live log and recovers what its archives hold, so the
@@ -1487,6 +1543,7 @@ export class MetadataIndexManager {
1487
1543
  catch {
1488
1544
  // Verdict probe is advisory — a stats read failure must not block open.
1489
1545
  }
1546
+ stage('replay-verdict');
1490
1547
  // 1. Archive-orphan census over the memtable shard logs.
1491
1548
  try {
1492
1549
  const memtableDir = join(this.lsmMetaDir, 'memtable');
@@ -1513,11 +1570,13 @@ export class MetadataIndexManager {
1513
1570
  catch {
1514
1571
  // Census is advisory — an unreadable dir must not block open.
1515
1572
  }
1573
+ stage('archive-census');
1516
1574
  // 2. |canonical| == |posted| invariant.
1517
1575
  try {
1518
1576
  const getNouns = this.storage.getNouns;
1519
1577
  if (typeof getNouns === 'function') {
1520
1578
  const probe = await getNouns.call(this.storage, { pagination: { limit: 1 } });
1579
+ stage('canonical-probe');
1521
1580
  const canonical = probe?.totalCount;
1522
1581
  if (typeof canonical === 'number' &&
1523
1582
  Number.isFinite(canonical) &&
@@ -1536,6 +1595,7 @@ export class MetadataIndexManager {
1536
1595
  for (const t of Object.values(NounType)) {
1537
1596
  posted += this.native.getEntityCountByType(t);
1538
1597
  }
1598
+ stage('posted-count');
1539
1599
  // LOWER-BOUND invariant, not equality: the adapter's totalCount
1540
1600
  // EXCLUDES system-routed entities (e.g. the VFS root) while the
1541
1601
  // index posts them, so healthy brains sit at posted ≥ canonical.
@@ -1555,6 +1615,7 @@ export class MetadataIndexManager {
1555
1615
  // count-only and the allowance carries a bounded, logged
1556
1616
  // staleness caveat.
1557
1617
  const unpostable = await this.verifiedUnpostableAllowance();
1618
+ stage('unpostable-allowance');
1558
1619
  if (posted + unpostable < canonical) {
1559
1620
  this.strandDetected = true;
1560
1621
  prodLog.error(`[NativeMetadataIndex] STRAND: canonical holds ${canonical} ` +
@@ -1575,6 +1636,7 @@ export class MetadataIndexManager {
1575
1636
  catch {
1576
1637
  // Invariant probe is advisory — a failed canonical read must not block open.
1577
1638
  }
1639
+ stage('count-invariant');
1578
1640
  }
1579
1641
  // ==========================================================================
1580
1642
  // Storage I/O helpers
@@ -1837,7 +1899,11 @@ export class MetadataIndexManager {
1837
1899
  // Cache warming
1838
1900
  // ==========================================================================
1839
1901
  async warmCache() {
1902
+ // Elapsed-time narration: each warm stage is a loop of per-field
1903
+ // storage reads — stamp it when it exceeds 1s so a slow warm names
1904
+ // itself instead of hiding inside init's 'warm-cache' phase total.
1840
1905
  const commonFields = ['noun', 'type', 'service', 'createdAt'];
1906
+ const t0 = Date.now();
1841
1907
  await Promise.all(commonFields.map(async (field) => {
1842
1908
  try {
1843
1909
  await this.ensureFieldLoaded(field);
@@ -1846,7 +1912,15 @@ export class MetadataIndexManager {
1846
1912
  prodLog.debug(`Cache warming: field '${field}' not yet indexed`);
1847
1913
  }
1848
1914
  }));
1915
+ if (Date.now() - t0 >= 1000) {
1916
+ opsLine(`warm-cache: common-field load (${commonFields.length} fields) took ` +
1917
+ `${((Date.now() - t0) / 1000).toFixed(1)}s`);
1918
+ }
1919
+ const t1 = Date.now();
1849
1920
  await this.warmCacheForTopTypes(3);
1921
+ if (Date.now() - t1 >= 1000) {
1922
+ opsLine(`warm-cache: top-type field load took ${((Date.now() - t1) / 1000).toFixed(1)}s`);
1923
+ }
1850
1924
  }
1851
1925
  async warmCacheForTopTypes(topN = 3) {
1852
1926
  const topTypes = this.getTopNounTypes(topN);
@@ -1881,12 +1955,17 @@ export class MetadataIndexManager {
1881
1955
  this.entityCountsByTypeFixed.fill(0);
1882
1956
  this.verbCountsByTypeFixed.fill(0);
1883
1957
  // Ensure 'noun' field is loaded into Rust
1958
+ const t0 = Date.now();
1884
1959
  await this.ensureFieldLoaded('noun');
1960
+ if (Date.now() - t0 >= 1000) {
1961
+ opsLine(`lazy-counts: 'noun' field load took ${((Date.now() - t0) / 1000).toFixed(1)}s`);
1962
+ }
1885
1963
  // Use Rust to get type counts via the noun field. Count natively
1886
1964
  // (#78 pt2) — getIdsCount returns the mapped-id count without
1887
1965
  // materializing one UUID string per entity. At 10B this is the
1888
1966
  // difference between O(1) per type and allocating billions of strings
1889
1967
  // just to read `.length`.
1968
+ const t1 = Date.now();
1890
1969
  const nounValues = this.native.getFilterValues('noun');
1891
1970
  for (const typeName of nounValues) {
1892
1971
  const count = this.native.getIdsCount('noun', JSON.stringify(typeName));
@@ -1894,6 +1973,10 @@ export class MetadataIndexManager {
1894
1973
  this.totalEntitiesByType.set(typeName, count);
1895
1974
  }
1896
1975
  }
1976
+ if (Date.now() - t1 >= 1000) {
1977
+ opsLine(`lazy-counts: type-count scan over ${nounValues.length} noun value(s) took ` +
1978
+ `${((Date.now() - t1) / 1000).toFixed(1)}s`);
1979
+ }
1897
1980
  prodLog.debug(`Loaded type counts: ${this.totalEntitiesByType.size} types`);
1898
1981
  }
1899
1982
  catch (error) {
@@ -2791,10 +2874,82 @@ export class MetadataIndexManager {
2791
2874
  return false;
2792
2875
  let upserts = 0;
2793
2876
  let removals = 0;
2877
+ let batches = 0;
2878
+ let lastNarrated = 0;
2879
+ const replayStarted = Date.now();
2880
+ // Entry marker + first-batch liveness budget. The replay is ADVISORY
2881
+ // (false = the walk heals, always authoritative), so the scan's FIRST
2882
+ // batch gets a hard arrival budget: on a generation-backlogged brain
2883
+ // the storage-side scan wedged indefinitely before its first batch,
2884
+ // and the every-200 line below never fired — init froze silently
2885
+ // inside this await (2026-07-19). An optional answer must never be a
2886
+ // mandatory wait.
2887
+ // Proportionality: the routine catch-up (a boot replaying its own
2888
+ // last write, gap of a few generations, milliseconds) stays SILENT —
2889
+ // the suite's boots-2+-are-clean contract treats new boot noise as
2890
+ // heal activity, and it is right to. The entry line is deferred to
2891
+ // the first liveness event ≥1s; the budget abort always speaks.
2892
+ const FIRST_BATCH_BUDGET_MS = 120_000;
2893
+ // TOTAL wall-clock budget: the replay is an OPTIMIZATION over the
2894
+ // census walk, never a requirement — the walk stays authoritative
2895
+ // and heals in minutes. The batch-arrival budget alone missed the
2896
+ // second failure shape (2026-07-19): the scan produced batches fine
2897
+ // but the per-op index work ground at ~7s/record on a mint-storm-
2898
+ // scarred mapper — 321 upserts in 39 minutes against a 7,531-
2899
+ // generation gap, a replay that could NEVER beat the walk it was
2900
+ // optimizing away. Checked between ops: past the budget, abort
2901
+ // loudly to the walk.
2902
+ const TOTAL_REPLAY_BUDGET_MS = 300_000;
2903
+ const iterator = scan.batches()[Symbol.asyncIterator]();
2794
2904
  try {
2795
- for await (const batch of scan.batches()) {
2905
+ for (;;) {
2906
+ let timer;
2907
+ const budget = new Promise((resolve) => {
2908
+ timer = setTimeout(() => resolve('BUDGET'), FIRST_BATCH_BUDGET_MS);
2909
+ timer.unref?.();
2910
+ });
2911
+ const next = await Promise.race([iterator.next(), budget]);
2912
+ if (timer !== undefined)
2913
+ clearTimeout(timer);
2914
+ if (next === 'BUDGET') {
2915
+ opsLine(`fact replay from generation ${fromGeneration} ABORTED — the scan ` +
2916
+ `produced no batch within ${FIRST_BATCH_BUDGET_MS / 1000}s ` +
2917
+ `(${batches} batches arrived before the stall); the storage ` +
2918
+ `fact-scan path is unhealthy on this brain — falling back to ` +
2919
+ `the walk-based heal, which is always authoritative`);
2920
+ return false;
2921
+ }
2922
+ if (next.done)
2923
+ break;
2924
+ const batch = next.value;
2925
+ // Liveness narration (loop-termination doctrine): a long replay
2926
+ // must narrate. If the loop wedges mid-batch, the LAST line's
2927
+ // counts localize the wedge; if scan.batches() itself stops
2928
+ // producing, this line's ABSENCE (after the caller's stage stamp)
2929
+ // localizes the producer instead.
2930
+ batches++;
2931
+ {
2932
+ // At most one liveness line per second, and none at all for a
2933
+ // sub-second replay — loud in proportion to the work.
2934
+ const now = Date.now();
2935
+ if (now - replayStarted >= 1000 && now - lastNarrated >= 1000) {
2936
+ lastNarrated = now;
2937
+ opsLine(`fact replay: ${batches} batch(es) from generation ${fromGeneration}, ` +
2938
+ `${upserts} upserts + ${removals} removals so far, ` +
2939
+ `${((now - replayStarted) / 1000).toFixed(1)}s elapsed`);
2940
+ }
2941
+ }
2796
2942
  for (const fact of batch.facts) {
2797
2943
  for (const op of fact.ops) {
2944
+ if (Date.now() - replayStarted > TOTAL_REPLAY_BUDGET_MS) {
2945
+ opsLine(`fact replay from generation ${fromGeneration} ABORTED — ` +
2946
+ `${upserts} upserts + ${removals} removals consumed the ` +
2947
+ `${TOTAL_REPLAY_BUDGET_MS / 1000}s total budget with the gap ` +
2948
+ `unfinished; this replay cannot beat the walk it optimizes ` +
2949
+ `away — falling back to the walk-based heal, which is ` +
2950
+ `always authoritative`);
2951
+ return false;
2952
+ }
2798
2953
  if (op.record === null) {
2799
2954
  await this.removeFromIndex(op.id);
2800
2955
  removals++;
@@ -10,6 +10,15 @@
10
10
  * means the stamp carries no sourceGeneration and verifiers skip the
11
11
  * comparison — never a blocked write, never a guessed value.
12
12
  */
13
+ /**
14
+ * Hard ceiling on the manifest-read fallback. The watermark is ADVISORY
15
+ * by contract (`undefined` = stamp carries no sourceGeneration, verifier
16
+ * skips) — so a storage layer that cannot answer promptly must cost a
17
+ * bounded wait and a loud line, never a frozen open (2026-07-19: a real
18
+ * brain state wedged this read indefinitely and init froze inside it,
19
+ * silent, until an external kill).
20
+ */
21
+ const MANIFEST_READ_BUDGET_MS = 30_000;
13
22
  /** Best-effort committed watermark; `undefined` when unknowable. */
14
23
  export async function readCommittedGeneration(storage) {
15
24
  try {
@@ -24,7 +33,22 @@ export async function readCommittedGeneration(storage) {
24
33
  }
25
34
  if (typeof s?.readRawObject !== 'function')
26
35
  return undefined;
27
- const manifest = (await s.readRawObject('_system/manifest.json'));
36
+ let timer;
37
+ const budget = new Promise((resolve) => {
38
+ timer = setTimeout(() => {
39
+ console.error(`[cor] watermark: manifest read exceeded its ${MANIFEST_READ_BUDGET_MS / 1000}s ` +
40
+ `budget — proceeding WITHOUT a sourceGeneration (advisory contract); ` +
41
+ `the storage layer's read path is unhealthy and should be investigated`);
42
+ resolve(undefined);
43
+ }, MANIFEST_READ_BUDGET_MS);
44
+ timer.unref?.();
45
+ });
46
+ const manifest = (await Promise.race([
47
+ s.readRawObject('_system/manifest.json'),
48
+ budget,
49
+ ]));
50
+ if (timer !== undefined)
51
+ clearTimeout(timer);
28
52
  const g = manifest?.generation;
29
53
  return typeof g === 'number' && Number.isFinite(g) && g >= 0 ? g : undefined;
30
54
  }
package/dist/version.d.ts CHANGED
@@ -11,5 +11,5 @@
11
11
  * `check:brainy`-style drift between the two fails the release.
12
12
  */
13
13
  /** The @soulcraft/cor version this build was cut from. */
14
- export declare const COR_VERSION = "3.0.21";
14
+ export declare const COR_VERSION = "3.0.22";
15
15
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -11,5 +11,5 @@
11
11
  * `check:brainy`-style drift between the two fails the release.
12
12
  */
13
13
  /** The @soulcraft/cor version this build was cut from. */
14
- export const COR_VERSION = '3.0.21';
14
+ export const COR_VERSION = '3.0.22';
15
15
  //# sourceMappingURL=version.js.map
Binary file
package/native/index.d.ts CHANGED
@@ -121,8 +121,10 @@ export declare class NativeBinaryIdMapper {
121
121
  */
122
122
  getOrAssign(uuid: Buffer): number
123
123
  /**
124
- * Lookup the int for `uuid`. Returns null if not present. Throws
125
- * in `IdSpace::U64` mode use [`get_int_big`].
124
+ * Lookup the int for `uuid`. Returns null only when the mapping
125
+ * is PROVEN absent; an unreadable store throws instead of
126
+ * masquerading as null. Throws in `IdSpace::U64` mode — use
127
+ * [`get_int_big`].
126
128
  */
127
129
  getInt(uuid: Buffer): number | null
128
130
  /**
@@ -156,7 +158,10 @@ export declare class NativeBinaryIdMapper {
156
158
  * a JS number).
157
159
  */
158
160
  getOrAssignBig(uuid: Buffer): bigint
159
- /** BigInt sibling of [`get_int`]. */
161
+ /**
162
+ * BigInt sibling of [`get_int`]. Same null contract: null =
163
+ * proven absent, unreadable store = throw.
164
+ */
160
165
  getIntBig(uuid: Buffer): bigint | null
161
166
  /** BigInt sibling of [`get_uuid`]. */
162
167
  getUuidBig(int: bigint): Buffer | null
@@ -1796,7 +1801,8 @@ export declare class NativeMetadataIndex {
1796
1801
  uuidToInt(uuid: string): number | null
1797
1802
  /**
1798
1803
  * BigInt sibling of [`uuid_to_int`]. Works for both U32 and U64
1799
- * brains; preferred by brainy 8.0 consumers.
1804
+ * brains; preferred by brainy 8.0 consumers. Null means PROVEN
1805
+ * unmapped; an unreadable mapper store throws.
1800
1806
  */
1801
1807
  uuidToIntBig(uuid: string): bigint | null
1802
1808
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/cor",
3
- "version": "3.0.21",
3
+ "version": "3.0.22",
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",
@@ -82,7 +82,7 @@
82
82
  },
83
83
  "devDependencies": {
84
84
  "@napi-rs/cli": "^3.0.0",
85
- "@soulcraft/brainy": "8.5.2",
85
+ "@soulcraft/brainy": "8.7.0",
86
86
  "@types/node": "^22.0.0",
87
87
  "pg": "^8.21.0",
88
88
  "tsx": "^4.21.0",