feat: vector provider identity is a required name field (hnsw-js), rendered [vector-index:<name>]

Reconciles the vector-index rename to the ruled three-layer naming: the
provider contract's identity field is now a REQUIRED readonly name (was
optional providerId), self-reported and truthful, rendered as
[vector-index:<name>] where the index identifies itself and stamped into
the op-name strings journals already parse (AddToVectorIndex(<name>)).
The built-in JS engine names itself hnsw-js. A runtime provider instance
compiled against the previous optional contract is tolerated - never
crashed on, never silently mislabeled: it stamps unknown-provider and
emits one loud warning naming the missing field. Graph index operations
keep their static names (they never interpolate provider identity), and
no public API exports a provider-routed hnsw-carrying name, so no
deprecation shim is required.
This commit is contained in:
David Snelling 2026-07-23 08:53:05 -07:00
parent 55b867c998
commit 3be4ba96c2
6 changed files with 92 additions and 38 deletions

View file

@ -54,8 +54,8 @@ export class HnswFlushError extends Error {
* acceleration provider).
*/
export class JsHnswVectorIndex implements VectorIndexProvider {
/** Self-identifies as the built-in JS fallback engine — see {@link VectorIndexProvider.providerId}. */
readonly providerId = 'js-hnsw'
/** Self-identifies as the built-in JS fallback engine — see {@link VectorIndexProvider.name}. */
readonly name = 'hnsw-js'
private nouns: Map<string, HNSWNoun> = new Map()
/**

View file

@ -962,20 +962,24 @@ export interface AtGenerationVectors {
*/
export interface VectorIndexProvider {
/**
* @description OPTIONAL backend identity, stamped into the transaction
* op-name strings that surface in journals/timings (e.g.
* `AddToVectorIndex(js-hnsw)` vs `AddToVectorIndex(<providerId>)`) see
* `src/transaction/operations/IndexOperations.ts`. Absent the op-name
* string reports `unknown-provider` rather than guessing a backend name
* (guessing would resurrect the exact bug this field exists to prevent:
* an operator hunting an index that isn't the one actually running). The
* built-in JS index always sets this to `'js-hnsw'`; a native provider
* sets its own identity here (e.g. its plugin/package name) so operators
* reading a journal see the backend that actually ran, never a
* backend-specific fossil name from whichever engine wrote the original
* op classes.
* @description REQUIRED self-reported implementation identity, rendered as
* `[vector-index:<name>]` in prose log lines and stamped into the
* transaction op-name strings that surface in journals/timings (e.g.
* `AddToVectorIndex(hnsw-js)`) see
* `src/transaction/operations/IndexOperations.ts`. A provider must name
* itself TRUTHFULLY (its own algorithm/engine, e.g. its plugin/package
* name) and must never inherit a default guessing a backend name would
* resurrect the exact bug this field exists to prevent: an operator
* hunting an index that isn't the one actually running. The built-in JS
* index always sets this to `'hnsw-js'`; a native provider picks its own
* string. At the TypeScript level this field is required; a runtime
* instance from an older provider compiled against the previous optional
* `providerId` contract is tolerated (never crashes, never silently
* mislabeled) see `resolveVectorProviderId` in
* `src/transaction/operations/IndexOperations.ts`, which stamps
* `'unknown-provider'` and emits one loud warning for that case.
*/
readonly providerId?: string
readonly name: string
addItem(item: VectorDocument): Promise<string>
removeItem(id: string): Promise<boolean>

View file

@ -16,19 +16,34 @@ import type { Operation, RollbackAction } from '../types.js'
/**
* Backend identity stamped into an operation's emitted `name` string (e.g.
* `AddToVectorIndex(js-hnsw)`), resolved from the provider's own
* {@link VectorIndexProvider.providerId} when it self-identifies.
* `AddToVectorIndex(hnsw-js)`), resolved from the provider's own
* {@link VectorIndexProvider.name} self-report.
*
* These operation classes are backend-neutral (the vector index they wrap may
* be Brainy's own JS HNSW fallback OR a native acceleration provider), but
* their names surface directly in consumer-visible transaction journals and
* timings. A provider that hasn't set `providerId` resolves to
* `'unknown-provider'` rather than guessing silently defaulting to the JS
* engine's name here is exactly the class of bug this stamping exists to
* prevent (an operator diagnosing a backend that isn't the one that ran).
* timings. `name` is REQUIRED at the TypeScript level but a native provider
* instance compiled against the previous (pre-required) contract can still
* reach this function at runtime without it. That legacy case is tolerated,
* never crashed on and never silently mislabeled: it resolves to
* `'unknown-provider'` and emits ONE loud warning naming the missing contract
* field, so the fix (implement `name`) is discoverable rather than a silent
* fossil label in every journal line thereafter.
*/
const warnedMissingName = new WeakSet<VectorIndexProvider>()
function resolveVectorProviderId(index: VectorIndexProvider): string {
return index.providerId ?? 'unknown-provider'
const name = (index as { name?: unknown }).name
if (typeof name === 'string') return name
if (!warnedMissingName.has(index)) {
warnedMissingName.add(index)
console.warn(
'[vector-index] provider is missing the required `name` field (VectorIndexProvider.name, ' +
'required since 8.10.0) — stamping "unknown-provider" in transaction op names until the ' +
'provider declares its own identity.'
)
}
return 'unknown-provider'
}
/**