feat: guarded plugin auto-detection — installing @soulcraft/cor is the opt-in

With plugins unset (the default), init() probes for the first-party
accelerator: not installed means plain brainy with zero noise; installed and
healthy means it loads and announces itself; installed but broken (import
failure, invalid shape, failed activation, version mismatch) makes init()
THROW. An installed accelerator never silently vanishes behind the JS engines
- the anti-drift posture of the explicit list, applied to detection.

plugins: []/false stays a true opt-out (no probe); an explicit list keeps its
required-and-loud semantics. The import runs through an importPluginPackage
seam (variable specifier - bundlers cannot static-resolve the optional
package; tests simulate all outcomes without it installed).

Also: when a plugin activates but registers zero native providers (e.g. a
licensing gate declining to engage), the provider summary now warns loudly
instead of leaving every query silently on the JS engines.

Supersedes 8.0.8's explicit-opt-in wording; README/PLUGINS/types now document
the guarded contract. 7 new tests (tests/unit/plugin-autodetect.test.ts).
This commit is contained in:
David Snelling 2026-07-02 16:19:55 -07:00
parent b37359e097
commit 588267be7f
6 changed files with 265 additions and 26 deletions

View file

@ -181,14 +181,11 @@ npm install @soulcraft/cor
``` ```
```javascript ```javascript
const brain = new Brainy({ const brain = new Brainy({ storage: { type: 'filesystem', path: './data' } })
plugins: ['@soulcraft/cor'], // explicit opt-in — brainy never auto-loads a package await brain.init() // @soulcraft/cor detected — same code, native engines underneath
storage: { type: 'filesystem', path: './data' }
})
await brain.init() // same code — native engines underneath
``` ```
Plugins are explicit: an engine swap is always visible in your config, and a listed plugin that fails to load fails **loud** — never a silent fallback to the JS engines. [`@soulcraft/cor`](https://www.npmjs.com/package/@soulcraft/cor) (Brainy 8.x ↔ Cor 3.x, version-matched) registers Rust implementations behind every provider seam: SIMD distance kernels, memory-mapped storage, a disk-native vector index that doesn't need your dataset in RAM, durable LSM field/graph indexes that serve cold opens instantly, and native aggregation. Recall@10 measured **0.99 / 0.96 / 0.96 at 1M / 10M / 100M vectors** in Cor's release gate. Installing the package is the opt-in: if `@soulcraft/cor` is present, it loads and announces itself in the init log; if it's present but broken, `init()` **throws** — an installed accelerator never silently vanishes behind the JS engines. Opt out with `plugins: []`, or pin exactly what loads with `plugins: ['@soulcraft/cor']`. [`@soulcraft/cor`](https://www.npmjs.com/package/@soulcraft/cor) (Brainy 8.x ↔ Cor 3.x, version-matched) registers Rust implementations behind every provider seam: SIMD distance kernels, memory-mapped storage, a disk-native vector index that doesn't need your dataset in RAM, durable LSM field/graph indexes that serve cold opens instantly, and native aggregation. Recall@10 measured **0.99 / 0.96 / 0.96 at 1M / 10M / 100M vectors** in Cor's release gate.
Open core, commercial accelerator: Brainy is MIT and complete on its own; Cor is licensed and funds both. Open core, commercial accelerator: Brainy is MIT and complete on its own; Cor is licensed and funds both.

View file

@ -10,6 +10,27 @@ Full auto-generated changelog: `CHANGELOG.md` · Releases: https://github.com/so
--- ---
## v8.0.9 — 2026-07-02 (guarded plugin auto-detection — the "install it and it's on" contract)
With the default config (`plugins` unset), brainy now **auto-detects the first-party accelerator**:
installing `@soulcraft/cor` is the opt-in. The detection is guarded — everything except "not
installed" fails loud:
- Not installed → plain brainy, silently (the free path — zero noise, zero cost).
- Installed and healthy → it loads and announces itself (`[brainy] Plugin activated`).
- Installed but broken (unresolvable, invalid shape, failed activation, version mismatch) →
**`init()` throws.** An installed accelerator never silently vanishes behind the JS engines.
- `plugins: []` / `false` = explicit opt-out; `plugins: ['@soulcraft/cor']` pins the exact list
(unchanged semantics).
Also new: if a plugin activates but registers **zero** native providers (e.g. a licensing gate
declining to engage), brainy warns loudly instead of leaving you to discover every query is running
on the JS engines.
> This supersedes v8.0.8's "plugins are explicit opt-in" wording, which documented the pre-GA
> loader accurately but contradicted the published product contract ("add the package and it
> activates"). 8.0.9 makes the contract true — with the loud-failure guarantees intact.
## v8.0.8 — 2026-07-02 (docs-only patch on the GA) ## v8.0.8 — 2026-07-02 (docs-only patch on the GA)
Corrects the README's scale-up section: the native provider is **not** auto-detected — plugins are Corrects the README's scale-up section: the native provider is **not** auto-detected — plugins are

View file

@ -23,20 +23,19 @@ Brainy's plugin system uses **named providers** — string keys mapped to implem
3. The plugin calls `context.registerProvider(key, implementation)` for each subsystem it provides 3. The plugin calls `context.registerProvider(key, implementation)` for each subsystem it provides
4. Brainy checks each provider key and wires the implementation into its internal pipeline 4. Brainy checks each provider key and wires the implementation into its internal pipeline
Plugins are **opt-in** — brainy never auto-imports packages. You must explicitly list plugins in the config: Installing the first-party accelerator is the opt-in: with the default config, brainy probes for `@soulcraft/cor` and loads it when present. Everything except "not installed" fails **loud** — a present-but-broken accelerator makes `init()` throw rather than silently degrading to the JS engines.
```typescript ```typescript
const brain = new Brainy({ const brain = new Brainy() // @soulcraft/cor auto-detected when installed
plugins: ['@soulcraft/cor'] // explicitly load the native acceleration package const pinned = new Brainy({ plugins: ['@soulcraft/cor'] }) // or pin exactly what loads
}) const plain = new Brainy({ plugins: [] }) // or opt out of detection entirely
``` ```
| `plugins` value | Behavior | | `plugins` value | Behavior |
|---|---| |---|---|
| `undefined` (default) | No plugins loaded | | `undefined` (default) | Guarded auto-detection of `@soulcraft/cor`: not installed → no plugins, silently; installed → loads + announces; installed-but-broken → `init()` throws |
| `false` | No plugins loaded | | `false` / `[]` | No plugins, no detection (explicit opt-out) |
| `[]` | No plugins loaded | | `['@soulcraft/cor']` | Load only the listed packages; a listed plugin that fails to load throws |
| `['@soulcraft/cor']` | Load only the listed packages |
Plugins registered programmatically via `brain.use(plugin)` are always activated regardless of the `plugins` config. Plugins registered programmatically via `brain.use(plugin)` are always activated regardless of the `plugins` config.

View file

@ -331,6 +331,10 @@ export class Brainy<T = any> implements BrainyInterface<T> {
* event-driven `whenMigrationComplete()` signal. See {@link awaitMigrationLock}. */ * event-driven `whenMigrationComplete()` signal. See {@link awaitMigrationLock}. */
private static readonly MIGRATION_POLL_INTERVAL_MS = 250 private static readonly MIGRATION_POLL_INTERVAL_MS = 250
/** First-party accelerator packages probed by guarded auto-detection when
* `plugins` is undefined (installing one IS the opt-in). See {@link loadPlugins}. */
private static readonly AUTO_DETECT_PLUGIN_PACKAGES = ['@soulcraft/cor']
// Core components // Core components
private index!: JsHnswVectorIndex private index!: JsHnswVectorIndex
private storage!: BaseStorage private storage!: BaseStorage
@ -1041,6 +1045,17 @@ export class Brainy<T = any> implements BrainyInterface<T> {
} else { } else {
console.log(`[brainy] Providers: ${native.length}/${wellKnownKeys.length} native (${plugins}) | default: ${fallback.join(', ')}`) console.log(`[brainy] Providers: ${native.length}/${wellKnownKeys.length} native (${plugins}) | default: ${fallback.join(', ')}`)
} }
// An activated accelerator that registered NOTHING is running as pure
// decoration — every query silently serves from the JS engines. Most
// often a licensing gate declining to engage. Say so, loudly, so an
// evaluator never concludes the accelerator "does nothing".
if (native.length === 0) {
console.warn(
`[brainy] ⚠ ${plugins} activated but registered 0 native providers — all queries are ` +
`running on the default JS engines. Check the plugin's requirements (e.g. a license ` +
`key) and its logs; see docs/PLUGINS.md.`
)
}
} }
// Mark as initialized BEFORE VFS init // Mark as initialized BEFORE VFS init
@ -13870,23 +13885,59 @@ export class Brainy<T = any> implements BrainyInterface<T> {
* Auto-detect and activate plugins. * Auto-detect and activate plugins.
* Called internally during init(). * Called internally during init().
*/ */
/**
* Import a plugin package by name. Isolated as a seam so tests can simulate
* the three auto-detect outcomes (not installed / broken install / valid)
* without the package being present. The specifier is a variable, so
* bundlers cannot statically resolve and cannot force-include the
* optional accelerator.
*/
private async importPluginPackage(pkg: string): Promise<any> {
return import(pkg)
}
/**
* True when a dynamic-import failure means "the package itself is not
* installed" (the silent free path), as opposed to "the package is present
* but broken" (which must fail loud). Node and Bun both name the missing
* package in the resolution error; a failure naming anything else an
* internal file, a dependency of the plugin, a syntax error is a broken
* install, never a not-installed.
*/
private static isPackageNotInstalledError(error: unknown, pkg: string): boolean {
const code = (error as { code?: string })?.code
const message = error instanceof Error ? error.message : String(error)
const namesPackage =
message.includes(`'${pkg}'`) || message.includes(`"${pkg}"`) || message.includes(` ${pkg}`)
const isResolutionFailure =
code === 'ERR_MODULE_NOT_FOUND' ||
code === 'MODULE_NOT_FOUND' ||
/cannot find (package|module)/i.test(message) ||
/failed to resolve/i.test(message) // Bun's resolver phrasing
return isResolutionFailure && namesPackage
}
private async loadPlugins(): Promise<void> { private async loadPlugins(): Promise<void> {
// plugins config: // plugins config:
// undefined (default) → no auto-detection (safe default) // undefined (default) → guarded auto-detection of the first-party
// false → no auto-detection // accelerator: installing @soulcraft/cor IS the
// [] → no auto-detection // opt-in. Not installed → plain brainy, silently.
// Installed → it loads and announces itself; if it
// is installed but broken, init() THROWS — an
// installed accelerator never silently vanishes.
// false / [] → no plugins, no detection (explicit opt-out)
// ['@soulcraft/cor'] → load only these explicitly listed packages // ['@soulcraft/cor'] → load only these explicitly listed packages
// Note: plugins registered via brain.use() are always activated regardless of config // Note: plugins registered via brain.use() are always activated regardless of config
const pluginConfig = this.config.plugins const pluginConfig = this.config.plugins
if (Array.isArray(pluginConfig) && pluginConfig.length > 0) { if (Array.isArray(pluginConfig) && pluginConfig.length > 0) {
// Explicit list: import and register the specified packages. A package // Explicit list: import and register the specified packages. A package
// listed here is REQUIRED — brainy does no auto-detection, so a missing or // listed here is REQUIRED — a missing or invalid plugin must fail LOUD,
// invalid plugin must fail LOUD, never silently fall back to the default // never silently fall back to the default engine (the cross-repo drift
// engine (the cross-repo drift this whole guard exists to prevent). // this whole guard exists to prevent).
for (const pkg of pluginConfig) { for (const pkg of pluginConfig) {
let mod: any let mod: any
try { try {
mod = await import(pkg) mod = await this.importPluginPackage(pkg)
} catch (error) { } catch (error) {
throw new Error( throw new Error(
`[brainy] Plugin "${pkg}" is listed in config.plugins but could not be loaded: ` + `[brainy] Plugin "${pkg}" is listed in config.plugins but could not be loaded: ` +
@ -13904,6 +13955,38 @@ export class Brainy<T = any> implements BrainyInterface<T> {
) )
} }
} }
} else if (pluginConfig === undefined) {
// Guarded auto-detection (default). Installing the first-party
// accelerator is the opt-in — probe for it, and apply the SAME loud
// posture as the explicit branch to everything except "not installed":
// a present-but-broken accelerator must never silently degrade to JS.
for (const pkg of Brainy.AUTO_DETECT_PLUGIN_PACKAGES) {
let mod: any
try {
mod = await this.importPluginPackage(pkg)
} catch (error) {
if (Brainy.isPackageNotInstalledError(error, pkg)) {
continue // the free path: not installed, nothing to load, no noise
}
throw new Error(
`[brainy] The accelerator "${pkg}" is installed but failed to load: ` +
`${error instanceof Error ? error.message : String(error)}. ` +
`brainy will NOT silently run the default JS engines in its place — ` +
`fix the install (npm i ${pkg}) or disable detection with plugins: [].`
)
}
const plugin: BrainyPlugin = mod.default || mod
if (plugin && typeof plugin.activate === 'function' && plugin.name) {
this.pluginRegistry.register(plugin)
} else {
throw new Error(
`[brainy] The installed accelerator "${pkg}" is not a valid Brainy plugin ` +
`(missing { name, activate }) — a broken or incompatible install. ` +
`brainy will NOT silently run the default JS engines in its place — ` +
`fix the install (npm i ${pkg}) or disable detection with plugins: [].`
)
}
}
} }
// Create plugin context // Create plugin context

View file

@ -1804,10 +1804,13 @@ export interface BrainyConfig {
eagerEmbeddings?: boolean eagerEmbeddings?: boolean
// Plugin configuration // Plugin configuration
// Controls which plugins are loaded during init(). Plugins are explicit // Controls which plugins are loaded during init().
// opt-in — brainy NEVER auto-imports a package that isn't listed here. // - undefined (default): guarded auto-detection of the first-party
// - undefined (default): no plugins loaded (same as [] / false) // accelerator (@soulcraft/cor) — installing the package IS the opt-in.
// - false / []: no plugins loaded // Not installed → plain brainy, silently. Installed → it loads and
// announces itself. Installed but broken → init() THROWS (an installed
// accelerator never silently vanishes behind the JS engines).
// - false / []: no plugins, no detection (explicit opt-out)
// - ['@soulcraft/cor']: load exactly these packages; a listed plugin that // - ['@soulcraft/cor']: load exactly these packages; a listed plugin that
// fails to load or is invalid THROWS (loud, never a silent JS fallback) // fails to load or is invalid THROWS (loud, never a silent JS fallback)
plugins?: string[] | false plugins?: string[] | false

View file

@ -0,0 +1,136 @@
/**
* Guarded plugin auto-detection (8.0.9) the "install it and it's on" contract.
*
* With `plugins` unset, brainy probes for the first-party accelerator
* (@soulcraft/cor). The probe applies the same loud posture as the explicit
* list to everything except "not installed":
* - not installed plain brainy, silently (the free path)
* - installed + healthy registered and activated
* - installed + broken init() THROWS (import failure, invalid shape,
* or failed activation an installed
* accelerator never silently vanishes)
* - plugins: [] / false no probe at all (explicit opt-out)
* - explicit list unchanged: required, loud on any failure
*
* The import is isolated behind the private `importPluginPackage` seam so the
* three outcomes are simulated without the package being present.
*/
import { describe, it, expect, afterEach } from 'vitest'
import { Brainy, NounType } from '../../src/index.js'
const proto = Brainy.prototype as any
const origImport = proto.importPluginPackage
afterEach(() => {
proto.importPluginPackage = origImport
})
/** Simulate the probe outcome per package name. */
function stubImport(fn: (pkg: string) => Promise<any>): string[] {
const probed: string[] = []
proto.importPluginPackage = async function (pkg: string) {
probed.push(pkg)
return fn(pkg)
}
return probed
}
function notInstalledError(pkg: string): Error {
// Node's exact shape for a missing package in a dynamic ESM import.
const e = new Error(`Cannot find package '${pkg}' imported from /app/index.js`)
;(e as any).code = 'ERR_MODULE_NOT_FOUND'
return e
}
/** A minimal valid plugin that registers one well-known provider. */
function validPlugin(name: string) {
return {
name,
activate: async (context: any) => {
context.registerProvider('distance', { euclidean: (a: number[], b: number[]) => 0 })
return true
}
}
}
describe('Guarded plugin auto-detection (plugins: undefined)', () => {
it('not installed → plain brainy, no plugins, init succeeds silently', async () => {
const probed = stubImport(async (pkg) => {
throw notInstalledError(pkg)
})
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
await brain.init()
expect(probed).toContain('@soulcraft/cor') // the probe ran…
expect(brain.pluginRegistry.hasActivePlugins()).toBe(false) // …and found nothing, quietly
const id = await brain.add({ data: 'works without any plugin', type: NounType.Concept })
expect(id).toBeTruthy()
await brain.close()
})
it('installed + healthy → auto-registered and activated', async () => {
stubImport(async () => ({ default: validPlugin('@soulcraft/cor') }))
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
await brain.init()
expect(brain.pluginRegistry.getActivePlugins()).toContain('@soulcraft/cor')
expect(brain.pluginRegistry.hasProvider('distance')).toBe(true)
await brain.close()
})
it('installed but import fails (broken install) → init() throws, never a silent JS fallback', async () => {
stubImport(async () => {
// A resolution failure INSIDE the package (names a sub-path, not the
// package) — present but broken, must be loud.
const e = new Error(
"Cannot find module '/app/node_modules/@soulcraft/cor/dist/native.js' imported from " +
'/app/node_modules/@soulcraft/cor/dist/index.js'
)
;(e as any).code = 'ERR_MODULE_NOT_FOUND'
throw e
})
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
await expect(brain.init()).rejects.toThrow(/installed but failed to load/)
})
it('installed but not a valid plugin (missing activate) → init() throws', async () => {
stubImport(async () => ({ default: { name: '@soulcraft/cor' } })) // no activate()
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
await expect(brain.init()).rejects.toThrow(/not a valid Brainy plugin/)
})
it('installed but activation fails → init() throws (activateAll posture applies)', async () => {
stubImport(async () => ({
default: {
name: '@soulcraft/cor',
activate: async () => {
throw new Error('native binary missing for platform')
}
}
}))
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
await expect(brain.init()).rejects.toThrow(/failed to activate/)
})
it('plugins: [] and plugins: false → no probe at all (explicit opt-out)', async () => {
const probed = stubImport(async () => ({ default: validPlugin('@soulcraft/cor') }))
for (const plugins of [[], false] as const) {
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, plugins: plugins as any, silent: true })
await brain.init()
expect(brain.pluginRegistry.hasActivePlugins()).toBe(false)
await brain.close()
}
expect(probed).toEqual([]) // the seam was never touched
})
it('explicit list is unchanged: a listed-but-missing plugin throws with the config-pointing message', async () => {
stubImport(async (pkg) => {
throw notInstalledError(pkg)
})
const brain: any = new Brainy({
requireSubtype: false,
storage: { type: 'memory' },
plugins: ['@soulcraft/cor'],
silent: true
})
await expect(brain.init()).rejects.toThrow(/listed in config\.plugins but could not be loaded/)
})
})