fix(8.0): close GA-blocking correctness gaps from the readiness audit

- Version-coupling cold-init: getBrainyVersion() returned a stale build-time
  default ('3.14.0') on the FIRST synchronous call — the one loadPlugins() makes
  to build context.version — because the package.json read was async. A native
  provider declaring a realistic `>=8.0.0` range was therefore rejected on cold
  init. version.ts now reads package.json synchronously (8.0 targets Node-like
  runtimes only); added a cold-init coupling regression with a `^8.0.0` plugin.
- No-silent-failures on the highest-fan-in read path: getNouns()/getVerbs()
  converted a storage read failure into a success-shaped empty page, which the
  cold-start rebuild then read as "store empty" and skipped the rebuild — booting
  a permanently-empty index with no signal (the same silent-failure class as the
  phantom bug). Both now re-throw a named BrainyError; the rebuild path re-throws
  read failures (fail loud) and records a queryable degraded state, surfaced via
  checkHealth(), for non-fatal rebuild hiccups.
- LSM SSTable leak: graph compaction wrote a merged SSTable but only dropped the
  old ones from the manifest, orphaning their payloads forever ("In production
  we'd add a cleanup mechanism"). Added StorageAdapter.deleteMetadata() and now
  reclaim each compacted-away SSTable.
- Documented the two headline methods add() and find() (the only undocumented
  public methods on the class).

Full gate green: build, unit 1512, integration 607.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
David Snelling 2026-06-29 10:03:38 -07:00
parent 40d2cd5419
commit 47e8031124
7 changed files with 291 additions and 133 deletions

View file

@ -1,8 +1,10 @@
/**
* 🧠 Brainy 3.0 - The Future of Neural Databases
*
* Beautiful, Professional, Planet-Scale, Fun to Use
* NO STUBS, NO MOCKS, REAL IMPLEMENTATION
* @module brainy
* @description The `Brainy` class the Triple-Intelligence database that unifies
* vector similarity, graph traversal, and metadata filtering behind a single API
* (`add`/`find`/`relate`/`related`/`get`/`similar`/`graph.*`/`now`/`asOf`/`transact`/
* `export`/`import`). Native acceleration is feature-detected through the provider
* boundary; when no provider is registered, the built-in JS engines serve everything.
*/
import { v4 as uuidv4, v7 as uuidv7 } from './universal/uuid.js'
@ -159,6 +161,7 @@ import {
import { GenerationStore } from './db/generationStore.js'
import { isDeterministicEmbedMode } from './embeddings/deterministicEmbedMode.js'
import { GenerationConflictError } from './db/errors.js'
import { BrainyError } from './errors/brainyError.js'
import { MemoryStorage } from './storage/adapters/memoryStorage.js'
import type {
CompactHistoryOptions,
@ -390,6 +393,14 @@ export class Brainy<T = any> implements BrainyInterface<T> {
* (use the TS fallback); otherwise the provider instance.
*/
private _graphAccelProvider: GraphAccelerationProvider | null | undefined = undefined
/**
* Set when a non-fatal index rebuild fails at init() (the brain is allowed to
* start, but in a degraded state where queries may be incomplete). Surfaced
* through {@link checkHealth} so consumers can observe the failure
* programmatically rather than only via the console. A storage *read* failure
* during rebuild is NOT recorded here it re-throws and aborts init() loudly.
*/
private _indexRebuildFailed: Error | null = null
// Sub-APIs (lazy-loaded)
private _nlp?: NaturalLanguageProcessor
@ -1291,9 +1302,11 @@ export class Brainy<T = any> implements BrainyInterface<T> {
/**
* @description Persist one single-operation write (`add`/`update`/`remove`/
* `relate`/`updateRelation`/`unrelate` and the per-item calls of their `*Many`
* variants) as its OWN immutable generation the Model-B "every write
* versioned" contract. Wraps the write's existing `executeTransaction` batch in
* `relate`/`updateRelation`/`unrelate` and the per-item calls of `addMany`/
* `updateMany`/`relateMany`) as its OWN immutable generation the Model-B
* "every write versioned" contract. (`removeMany` is the one exception: it
* commits each delete *chunk* as a single generation for bulk-delete efficiency,
* not one generation per removed id.) Wraps the write's existing `executeTransaction` batch in
* {@link GenerationStore.commitSingleOp}, which captures byte-identical
* before-images of `touched`, runs the batch with the generation watermark
* pinned to the reserved generation (so this write's graph-index ops stamp cor
@ -1326,6 +1339,26 @@ export class Brainy<T = any> implements BrainyInterface<T> {
})
}
/**
* @description Add an entity (noun) to the brain. Embeds `data` into a vector and
* indexes the entity across all three intelligences vector similarity, graph
* adjacency, and metadata then returns its id. When no `id` is supplied a fresh
* time-ordered **UUID v7** is generated; a supplied natural-key string is
* normalized to a stable **UUID v5**. Under Model-B every add is its own immutable
* generation, so it is time-travelable via {@link asOf}.
* @param params - The entity to add. `data` (embedded for similarity) and `type`
* (a {@link NounType}) are the essentials; `subtype`, `metadata`, an explicit
* `id`, `visibility`, `confidence`, and `weight` are optional.
* @returns The entity's id the supplied/normalized id, or a freshly generated UUID v7.
* @throws If the brain is read-only (`mode: 'reader'`), or brain-wide `requireSubtype`
* is on and the entity's type has no subtype.
* @example
* const id = await brain.add({
* data: 'Ada Lovelace',
* type: NounType.Person,
* metadata: { role: 'mathematician' }
* })
*/
async add(params: AddParams<T>): Promise<string> {
this.assertWritable('add')
await this.ensureInitialized()
@ -4665,6 +4698,30 @@ export class Brainy<T = any> implements BrainyInterface<T> {
return new Set(ids)
}
/**
* @description The unified Triple-Intelligence query. Composes vector similarity
* (`query` / `vector` / `near`), metadata filtering (`type` / `subtype` / `where` /
* `service`), and graph traversal (`connected`) into one ranked, paginated result
* set pass a bare string for a quick semantic search, or a {@link FindParams}
* object to combine dimensions. Internal/system entities are hidden by default
* (opt in with `includeInternal` / `includeSystem`); `orderBy`/`order` and
* `limit`/`offset` apply across the composed result. Every returned row is
* re-validated against its own predicate, so a stale or cross-bucket index entry
* can never surface an entity that does not actually match.
* @param query - A semantic search string, or a {@link FindParams} object combining
* any of `query`, `vector`, `near`, `type`, `subtype`, `where`, `connected`,
* `orderBy`/`order`, `limit`, `offset`.
* @returns The matching {@link Result}s flattened (`id`, `score`, `type`,
* `metadata`, `data`, ) and ranked; an empty array when nothing matches.
* @example
* // Semantic + metadata + graph, composed in one call:
* const rows = await brain.find({
* query: 'climate policy',
* where: { year: { gte: 2020 } },
* connected: { from: orgId, via: VerbType.Authored },
* limit: 10
* })
*/
async find(query: string | FindParams<T>): Promise<Result<T>[]> {
await this.ensureInitialized()
@ -5152,11 +5209,19 @@ export class Brainy<T = any> implements BrainyInterface<T> {
// `entityMatchesFind` (db.ts). On a healthy index it is a no-op; on a
// corrupted one it drops the bad row instead of returning a phantom.
if (result.length > 0) {
result = result.filter(
(r) =>
r.entity != null &&
entityMatchesFind(r.entity as unknown as Entity, params as unknown as FindParams)
)
result = result.filter((r) => {
if (r.entity == null) return false
try {
return entityMatchesFind(r.entity as unknown as Entity, params as unknown as FindParams)
} catch {
// The JS matcher doesn't implement a where-operator the provider already
// matched on (e.g. a future native-only operator). The guard cannot
// DISPROVE a match the provider made, so keep the row rather than
// hard-failing a correct provider result. (The db.ts historical path
// keeps its throw, which legitimately reroutes to materialization.)
return true
}
})
}
// includeVectors — opt-in vector hydration. Default (false) keeps the perf
@ -10896,7 +10961,20 @@ export class Brainy<T = any> implements BrainyInterface<T> {
recommendation: string | null
}> {
await this.ensureInitialized()
return this.metadataIndex.validateConsistency()
const result = await this.metadataIndex.validateConsistency()
// Fold in a non-fatal index-rebuild failure recorded at init() so the degraded
// state is observable through the same health surface (not just the console).
if (this._indexRebuildFailed) {
return {
...result,
healthy: false,
recommendation:
`Index rebuild failed at init() (degraded — queries may be incomplete): ` +
`${this._indexRebuildFailed.message}. Rebuild the indexes and re-open.` +
(result.recommendation ? ` Also: ${result.recommendation}` : '')
}
}
return result
}
/**
@ -12785,8 +12863,19 @@ export class Brainy<T = any> implements BrainyInterface<T> {
}
} catch (error) {
console.warn('Warning: Could not rebuild indexes:', error)
// Don't throw - allow system to start even if rebuild fails
// A storage READ failure here is surfaced by getNouns/getVerbs as a named
// BrainyError — it means we could not even read the store to decide whether
// a rebuild is needed (or to perform it). That is a hard init failure, not a
// "start anyway" condition: booting a brain whose data we couldn't read would
// serve empty/incomplete results with no signal (the silent-failure class the
// 8.0 contract forbids). Re-throw it loud.
if (error instanceof BrainyError) throw error
// Any other rebuild hiccup is non-fatal — the brain may start on partially
// rebuilt indexes — but it is recorded as a queryable degraded state
// (surfaced via checkHealth) and escalated to error-level logging, rather
// than only emitting a console.warn that is trivially lost.
this._indexRebuildFailed = error instanceof Error ? error : new Error(String(error))
console.error('[Brainy] Index rebuild failed; starting in a DEGRADED state (queries may be incomplete):', error)
}
}
@ -12808,7 +12897,20 @@ export class Brainy<T = any> implements BrainyInterface<T> {
recommendation: string | null
}> {
await this.ensureInitialized()
return this.metadataIndex.validateConsistency()
const result = await this.metadataIndex.validateConsistency()
// Fold in a non-fatal index-rebuild failure recorded at init() so the degraded
// state is observable through the same health surface (not just the console).
if (this._indexRebuildFailed) {
return {
...result,
healthy: false,
recommendation:
`Index rebuild failed at init() (degraded — queries may be incomplete): ` +
`${this._indexRebuildFailed.message}. Rebuild the indexes and re-open.` +
(result.recommendation ? ` Also: ${result.recommendation}` : '')
}
}
return result
}
/**