/** * Layer 3 (v1.7.5): metadata resolution chain. * * Pure orchestration — all dependencies are injected so the chain is * exhaustively testable in isolation. * * Resolution order (cache lives forever — see metadata-cache.ts header for * why; users explicitly invalidate via `obsidian-brain models refresh-cache`): * 1. embedder_capability cache hit → return immediately. * 2. Cache miss + seed lookup hit → write seed row to cache, return. * 3. Cache miss + seed miss + HF live fetch (with timeout/retry) → cache, return. * 4. HF unreachable + embedder loaded → use embedder.dimensions(); 512 / symmetric. * 5. All fail → safe defaults + stderr warning. Boot continues. */ import type { DatabaseHandle } from '../store/db.js'; import type { Embedder } from './types.js'; import { type CachedPrefixSource } from './metadata-cache.js'; import { type SeedEntry } from './seed-loader.js'; import { type HfMetadata, type HfMetadataOptions } from './hf-metadata.js'; import { type ModelOverride } from './overrides.js'; /** Resolved metadata returned by the chain. Always non-null — falls through to safe defaults. */ export interface ResolvedMetadata { modelId: string; dim: number | null; maxTokens: number; queryPrefix: string; documentPrefix: string; prefixSource: CachedPrefixSource; baseModel: string | null; sizeBytes: number | null; /** Which step of the chain produced this result. */ resolvedFrom: 'cache' | 'seed' | 'hf' | 'embedder-probe' | 'fallback'; /** True iff a user override (~/.config/obsidian-brain/model-overrides.json) * patched any field on top of the resolved value. Surfaced for diagnostics. */ overrideApplied: boolean; } export interface ResolverDeps { db: DatabaseHandle; /** Optional — when present, embedder.dimensions() is used as the * step-5 fallback. Null is fine (e.g. CLI calls before embedder init). */ embedder?: Embedder; /** Override the seed map (tests). Default: loadSeed(). */ seed?: Map; /** Override the user-overrides map (tests). Default: loadOverrides(). */ overrides?: Map; /** Override the HF fetcher (tests). Default: getEmbeddingMetadata. */ fetchHf?: (modelId: string, opts?: HfMetadataOptions) => Promise; /** Override env (tests). Default: process.env. */ env?: NodeJS.ProcessEnv; /** Per-fetch timeout. Forwarded to Layer 1. */ timeoutMs?: number; } /** * Async path — full chain. Used at bootstrap time after `embedder.init()`, * by `IndexPipeline.refreshCapacity()`, and by `models check`. * * Cache is permanent (no TTL). Users invalidate explicitly via * `obsidian-brain models refresh-cache`. */ export declare function resolveModelMetadata(modelId: string, deps: ResolverDeps): Promise; /** * Sync path — cache + seed only. Used by `bootstrap.ts` for the prefix-strategy * hash, which must be synchronous. Returns null when neither cache nor seed * has the model — callers treat null as "skip the optimisation, no reindex * triggered." */ export declare function resolveModelMetadataSync(modelId: string, deps: { db: DatabaseHandle; seed?: Map; overrides?: Map; }): ResolvedMetadata | null;