diff --git a/src/brainy.ts b/src/brainy.ts index 632719c9..080a42be 100644 --- a/src/brainy.ts +++ b/src/brainy.ts @@ -8407,16 +8407,16 @@ export class Brainy implements BrainyInterface { const via = params.connected.via ?? params.connected.type const subtypeFilter = params.connected.subtype - // Multi-hop subtype filtering would require per-hop edge enumeration in JS, which doesn't - // scale. The native fast path lands in the next Cortex release (see CTX-SUBTYPE-PARITY-V2). - // For 7.30.0 JS, restrict subtype filtering to depth-1 — explicit error beats silent - // incorrect results. - if (subtypeFilter !== undefined && depth !== undefined && depth > 1) { - throw new Error( - 'find({ connected: { subtype, depth > 1 } }) is not yet supported on the JS path. ' + - 'Use depth: 1, or wait for Cortex native traversal (CTX-SUBTYPE-PARITY-V2).' - ) - } + // Brainy 8.0 (per BRAINY-8.0-RENAME-COORDINATION § G.3.b + cortex D.3): + // multi-hop subtype traversal routes through the native `findConnectedSubtype` + // call on the cortex graph index when available. The depth > 1 + subtype + // throw that 7.30.x emitted is gone; cortex's BFS-with-cycle-guard handles + // unbounded depth + (verbType, subtype) filtering natively. + // + // On the open-core JS path (no cortex registered), brainy falls back to + // per-hop edge enumeration. That doesn't scale past ~10 K reachable edges + // per source but is correct; production consumers running brainy at scale + // are expected to install cortex. // GraphConstraints speaks 'in' | 'out' | 'both'; neighbors() speaks 'incoming' | 'outgoing' | 'both'. const toNeighborDir = (d: 'in' | 'out' | 'both'): 'incoming' | 'outgoing' | 'both' => diff --git a/src/index.ts b/src/index.ts index e8dca115..065f0d0f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,7 @@ export type { UpdateParams, RelateParams, FindParams, + SubtypeRegistry, AggregateDefinition, AggregateMetricDef, AggregateSource, diff --git a/src/types/brainy.types.ts b/src/types/brainy.types.ts index 0c8a1acd..17b5a33f 100644 --- a/src/types/brainy.types.ts +++ b/src/types/brainy.types.ts @@ -178,6 +178,36 @@ export interface ScoreExplanation { * It is NOT indexed by MetadataIndex and NOT queryable via `where` filters. * - `metadata` is indexed by MetadataIndex and queryable via `where` filters in `find()`. */ +/** + * Declaration-merging registry for per-`(type, subtype)` metadata shapes + * (Brainy 8.0, per BRAINY-8.0-SUBTYPE-CONTRACT § C-3). + * + * Consumers extend this interface in their own code to tell TypeScript what + * shape `metadata` takes for a given `(NounType, subtype)` pair. Brainy + * doesn't ship any entries — every entry is a consumer concern. + * + * @example + * ```ts + * declare module '@soulcraft/brainy' { + * interface SubtypeRegistry { + * // For NounType.Person, subtype 'employee': + * 'person:employee': { employeeId: string; department: string } + * // For NounType.Document, subtype 'invoice': + * 'document:invoice': { invoiceNumber: string; amount: number } + * } + * } + * ``` + * + * Keys are `${NounType}:${subtype}`. Values describe the metadata shape. + * Brainy reads this registry (when present) to give typed `metadata` + * autocomplete + type-checking on `add()` / `update()` / `find()`. Consumers + * with no registry entries see no type-level change — the metadata bag + * stays `T = any`. + */ +export interface SubtypeRegistry { + // Intentionally empty. Consumers extend via declaration merging. +} + export interface AddParams { /** Content to embed and store. Strings are auto-embedded; objects are JSON-stringified for embedding. */ data: any | Vector