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:
parent
40d2cd5419
commit
47e8031124
7 changed files with 291 additions and 133 deletions
134
src/brainy.ts
134
src/brainy.ts
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue