@avantf/dsh-mem 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +19 -1
  2. package/lib/client.js +40 -9
  3. package/lib/engine/mem/config/config_files.d.ts +1 -1
  4. package/lib/engine/mem/db/dao/chunks.d.ts +20 -1
  5. package/lib/engine/mem/db/dao/entities.d.ts +35 -11
  6. package/lib/engine/mem/db/dao/fact_sources.d.ts +53 -0
  7. package/lib/engine/mem/db/dao/facts.d.ts +127 -12
  8. package/lib/engine/mem/db/invariants.d.ts +26 -0
  9. package/lib/engine/mem/db/tokenizer.d.ts +23 -0
  10. package/lib/engine/mem/db/vectors.d.ts +40 -8
  11. package/lib/engine/mem/entities/extract.d.ts +1 -1
  12. package/lib/engine/mem/eval/derive.d.ts +120 -0
  13. package/lib/engine/mem/eval/derived_runner.d.ts +88 -0
  14. package/lib/engine/mem/eval/metrics.d.ts +24 -0
  15. package/lib/engine/mem/eval/runner.d.ts +8 -1
  16. package/lib/engine/mem/index.d.ts +1 -1
  17. package/lib/engine/mem/modelBootstrap.d.ts +2 -2
  18. package/lib/engine/mem/runtime.d.ts +1 -1
  19. package/lib/engine/mem/store/common.d.ts +33 -11
  20. package/lib/engine/mem/store/entity_leg.d.ts +95 -0
  21. package/lib/engine/mem/store/floors.d.ts +63 -31
  22. package/lib/engine/mem/store/hybrid.d.ts +21 -26
  23. package/lib/engine/mem/store/knowledge.d.ts +18 -9
  24. package/lib/engine/mem/store/lexical.d.ts +44 -0
  25. package/lib/engine/mem/store/memory.d.ts +151 -24
  26. package/lib/engine/mem/store/secret_guard.d.ts +34 -0
  27. package/lib/engine/mem/store/time_window.d.ts +74 -0
  28. package/lib/engine/mem-contract/config.d.ts +9 -16
  29. package/lib/engine/mem-contract/family.d.ts +1 -1
  30. package/lib/engine/mem-contract/tools.d.ts +10 -0
  31. package/lib/engine/mem-contract/types.d.ts +164 -9
  32. package/lib/engine/mem-retrieval/adapters/local_bge.d.ts +37 -7
  33. package/lib/engine/mem-retrieval/fusion.d.ts +14 -1
  34. package/lib/engine/mem-retrieval/index.d.ts +1 -3
  35. package/lib/engine/mem-retrieval/interfaces.d.ts +38 -14
  36. package/lib/engine/mem-retrieval/registry.d.ts +2 -4
  37. package/lib/engine/mem-retrieval/representation.d.ts +50 -0
  38. package/lib/engine/mem-retrieval/stats.d.ts +1 -3
  39. package/lib/index.js +2003 -565
  40. package/lib/types/client/index.d.ts.map +1 -1
  41. package/lib/types/index.d.ts +1 -1
  42. package/lib/types/index.d.ts.map +1 -1
  43. package/lib/types/prompt.d.ts +7 -0
  44. package/lib/types/prompt.d.ts.map +1 -1
  45. package/lib/types/remote.d.ts +12 -6
  46. package/lib/types/remote.d.ts.map +1 -1
  47. package/lib/types/vectorMigration.d.ts +44 -0
  48. package/lib/types/vectorMigration.d.ts.map +1 -0
  49. package/package.json +5 -5
  50. package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +0 -52
  51. package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +0 -10
  52. package/lib/engine/mem-retrieval/rerank.d.ts +0 -13
@@ -23,7 +23,6 @@ export interface FactSummary {
23
23
  /** Remaining ACTIVE days before `forgot`; null when pinned / non-active / trust disabled. */
24
24
  remaining_days: number | null;
25
25
  helpful_count: number;
26
- mirror_source: string | null;
27
26
  created_at: string;
28
27
  archived_at: string | null;
29
28
  archive_reason: string | null;
@@ -52,7 +51,6 @@ export declare const RETENTION_DIAGNOSTIC_FIELDS: readonly ["trust_score", "rema
52
51
  export declare function withoutRetentionDiagnostics<T>(value: T): T;
53
52
  export interface FactDetail extends FactSummary {
54
53
  retrieval_count: number;
55
- mirror_target: string | null;
56
54
  supersedes_id: number | null;
57
55
  entities: string[];
58
56
  triples: FactTriple[];
@@ -68,6 +66,33 @@ export interface FactDetail extends FactSummary {
68
66
  /** Effective reinforcement events in the current 24h window (recall + feedback). */
69
67
  bonus_count: number;
70
68
  bonus_window_at: string | null;
69
+ /**
70
+ * P-08: where this fact came from, as recorded rows. Empty when the writer gave no `source_ref`
71
+ * — the read surface must show "unknown" rather than invent one.
72
+ */
73
+ sources: FactSourceView[];
74
+ /**
75
+ * P-07: when the fact became true / stopped being true. `null` = unknown; `valid_to` set while
76
+ * the row is still `active` means "still valid, known to end at T" (the state
77
+ * `mem_remember valid_until` creates), while an archived row's `valid_to` is an audit stamp.
78
+ */
79
+ valid_from: string | null;
80
+ valid_to: string | null;
81
+ /**
82
+ * P-07: the fact that supersedes this one, DERIVED from the existing `supersedes_id` link
83
+ * (reverse lookup) — no redundant column. `null` when nothing supersedes it.
84
+ */
85
+ superseded_by: number | null;
86
+ /**
87
+ * P-10: how many times this exact content has been asserted. `1` for every row that predates the
88
+ * counter and for a first write; a verbatim duplicate `add` moves it. Never scored.
89
+ */
90
+ assert_count: number;
91
+ }
92
+ /** P-08: one provenance row on a fact (`fact_sources`), as `admin detail` renders it. */
93
+ export interface FactSourceView {
94
+ kind: 'session' | 'kb_doc' | 'tool' | 'manual';
95
+ ref: string;
71
96
  }
72
97
  /** A single hit from hybrid retrieval over the merged candidate pool. */
73
98
  export interface RecallHit {
@@ -107,6 +132,38 @@ export interface RecallHit {
107
132
  * `source_ref`, so the caller knows it exists and can fetch it explicitly.
108
133
  */
109
134
  truncated?: boolean;
135
+ /**
136
+ * P-01 per-leg evidence, present only when the caller asked (`include_scores`) — absent is the
137
+ * default and is what keeps the envelope byte-identical for every existing caller.
138
+ *
139
+ * Keys are the LEG NAMES actually fused for this hit's store (`semantic` / `fts` / `jaccard` /
140
+ * `hrr`; the HRR probe shares the jaccard weight, which is why it is named separately). A leg
141
+ * that did not recall this candidate is `null` — an explicit "this leg has nothing to say",
142
+ * which is information a missing key would lose.
143
+ */
144
+ scores?: Record<string, RecallLegScore | null>;
145
+ /**
146
+ * P-01: the FUSION output score for this hit, before any cross-store normalization.
147
+ *
148
+ * `score` is what the surface ranks by and can be re-scaled downstream (`crossQuery` min-max
149
+ * normalizes over the merged pool), so a caller reasoning about "which leg carried this" needs
150
+ * the fusion value itself rather than a number that means a different thing per surface.
151
+ */
152
+ final?: number;
153
+ /**
154
+ * P-07/P-13: the fact's event time / known end, serialized ONLY when non-null (the same
155
+ * convention as {@link RecallHit.truncated}). `null` means "unknown", and omitting the key keeps
156
+ * every pre-P-07 envelope byte-identical. Both are display/audit only — no leg filters on them.
157
+ */
158
+ valid_from?: string;
159
+ valid_to?: string;
160
+ }
161
+ /** P-01: one leg's contribution to a hit (see {@link RecallHit.scores}). */
162
+ export interface RecallLegScore {
163
+ /** The leg's own raw score, on its own scale. */
164
+ raw: number;
165
+ /** `raw / legMax` in `[0,1]` — the value fusion actually weighted. Not comparable across legs. */
166
+ normalized: number;
110
167
  }
111
168
  /**
112
169
  * The relevance floors in force for ONE retrieval (`0` = that leg is not gated).
@@ -121,7 +178,7 @@ export interface RetrievalFloors {
121
178
  semantic: number;
122
179
  /** Distinct-query-term floor for the FTS leg (`retriever.min_fts_terms`; 1 when degraded). */
123
180
  fts: number;
124
- /** Entity-overlap ratio floor (`retriever.min_jaccard`). */
181
+ /** Anchored query-coverage floor for the entity leg (`retriever.min_jaccard`, see `store/entity_leg.ts`). */
125
182
  jaccard: number;
126
183
  }
127
184
  /**
@@ -235,6 +292,12 @@ export interface RememberResult {
235
292
  is_new: boolean;
236
293
  revived: boolean;
237
294
  entities: string[];
295
+ /**
296
+ * P-10: how many times this content has been asserted. A first write is `1`; a verbatim
297
+ * duplicate `add` increments it. Reviving an archived row does NOT increment it — that is a
298
+ * resurrection, not a new assertion.
299
+ */
300
+ assert_count: number;
238
301
  /**
239
302
  * Contradictions detected against the existing corpus while writing this fact (DESIGN §11).
240
303
  * Present only when at least one was found, so the writer learns immediately instead of
@@ -258,11 +321,8 @@ export interface RetrievalHealth {
258
321
  /** Queries where the semantic leg was live / degraded (FTS+entity only). */
259
322
  semantic_live: number;
260
323
  semantic_degraded: number;
261
- rerank_used: number;
262
- rerank_fallback: number;
263
324
  /** Model-facing text that had to be bounded by a budget (see `retrieval-core/text_budget`). */
264
325
  embedding_truncated: number;
265
- rerank_truncated: number;
266
326
  output_truncated: number;
267
327
  /**
268
328
  * Retrieval legs that returned exactly their cap, i.e. whose tail was dropped (`retriever.leg_cap`).
@@ -287,10 +347,7 @@ export interface RetrievalHealthSummary {
287
347
  avg_latency_ms: number;
288
348
  max_latency_ms: number;
289
349
  semantic_live_rate: number;
290
- rerank_used: number;
291
- rerank_fallback: number;
292
350
  embedding_truncated: number;
293
- rerank_truncated: number;
294
351
  output_truncated: number;
295
352
  /** Retrieval legs that returned exactly their cap (see `RetrievalHealth`). */
296
353
  legs_capped: number;
@@ -299,11 +356,77 @@ export interface RetrievalHealthSummary {
299
356
  updated_at: string | null;
300
357
  by_kind: Record<string, KindHealth>;
301
358
  }
359
+ /**
360
+ * Persisted-vector health of the ACTIVE corpus, relative to the vector space the store writes into
361
+ * NOW — the DETECTION half of "changing the embedding space is a data migration" (DESIGN §20).
362
+ *
363
+ * Deliberately cheap: it compares the recorded width and space id only, never decoding the blobs, so
364
+ * the `/mem` status surface can read it on every poll without loading a whole corpus.
365
+ */
366
+ export interface VectorSpaceHealth {
367
+ /** Active facts whose persisted vector has a different width than `semantic.dim`. */
368
+ stale: number;
369
+ /** Active facts whose persisted vector is provably from another model space (same width). */
370
+ space_stale: number;
371
+ }
372
+ /**
373
+ * SQLite WAL sidecar health for the memory database (P-06).
374
+ *
375
+ * The `-wal` / `-shm` files are TRANSIENT: versioning or copying them next to `memory.db` can leave
376
+ * a database that will not recover. `admin stats` is offline and dependency-free by design, so it
377
+ * never shells out to git to ask whether they are tracked — it only reports the one fact a single
378
+ * `statSync` can establish. The "do not version these; `wal_checkpoint(TRUNCATE)` before a commit or
379
+ * a copy" rule lives in `docs/INSTALL.md`.
380
+ */
381
+ export interface WalHealth {
382
+ /** The `-wal` sidecar exists right now (a checkpoint has not removed it, or the store is live). */
383
+ present: boolean;
384
+ /** Size of `-wal` in bytes; 0 when it does not exist or has been truncated. */
385
+ bytes: number;
386
+ /**
387
+ * The operator-facing reminder, non-null only while `-wal` actually holds bytes. An EMPTY
388
+ * sidecar (the normal post-`wal_checkpoint(TRUNCATE)` state) and an absent one are both silent —
389
+ * this is a backup-discipline hint, never an error.
390
+ */
391
+ warning: string | null;
392
+ }
302
393
  /** `mem_admin stats`: store counts plus the retrieval-health counters. */
303
394
  export interface StatsSummary {
304
395
  active: number;
305
396
  archived: number;
306
397
  retrieval: RetrievalHealthSummary;
398
+ /**
399
+ * Vector-space health, so "retrieval silently degraded after a model upgrade" is visible from the
400
+ * status surface instead of only from a one-shot startup warning.
401
+ */
402
+ vectors: VectorSpaceHealth;
403
+ /**
404
+ * WAL sidecar health, so "there is an uncheckpointed `-wal` next to the database" is visible
405
+ * before someone versions or copies the data root.
406
+ */
407
+ wal: WalHealth;
408
+ /**
409
+ * P-08 source coverage. Reported from day one so the field cannot silently become empty: a
410
+ * `source_ref` that never lands (or a predicate that stops matching) shows up here as `0`.
411
+ */
412
+ sources: SourceCoverage;
413
+ /** P-13 `valid_from` coverage, the same "prevent an empty field" argument as {@link StatsSummary.sources}. */
414
+ validity: ValidityCoverage;
415
+ }
416
+ /** P-08: how much of the ACTIVE corpus carries provenance (`fact_sources`). */
417
+ export interface SourceCoverage {
418
+ active: number;
419
+ /** ACTIVE facts with at least one `fact_sources` row (a fact with three sources counts once). */
420
+ facts_with_source: number;
421
+ /** `facts_with_source / active`, `0` on an empty corpus. */
422
+ coverage: number;
423
+ }
424
+ /** P-13: how much of the ACTIVE corpus carries an event time (`valid_from`). */
425
+ export interface ValidityCoverage {
426
+ active: number;
427
+ facts_with_valid_from: number;
428
+ /** `facts_with_valid_from / active`, `0` on an empty corpus. */
429
+ coverage: number;
307
430
  }
308
431
  /** One page of facts (`mem_admin list`). */
309
432
  export interface FactPage {
@@ -347,10 +470,42 @@ export interface VectorsDiagnostic {
347
470
  space_stale: number;
348
471
  indexed: number;
349
472
  unindexed: number;
473
+ /**
474
+ * Distinct recorded spaces across the WHOLE table (archived rows included), while `stale` /
475
+ * `space_stale` count only ACTIVE rows — an archived row legitimately keeps its old vector, so the
476
+ * two must not be compared directly.
477
+ */
350
478
  models: Record<string, number>;
351
479
  /** Which backend is actually serving reads (`auto:local_numpy` → `auto:hnswlib`). */
352
480
  store: string;
353
481
  }
482
+ /**
483
+ * One BOUNDED slice of the vector-space migration (`MemoryStore.migrateVectorsBatch`).
484
+ *
485
+ * The migration is what turns "the default embedder changed" from a silent degradation into a
486
+ * resumable background repair: each call re-encodes at most `batchSize` rows, and the remaining
487
+ * count is what the next call (or the next process, after a restart) resumes from.
488
+ */
489
+ export interface VectorMigrationProgress {
490
+ /** Active rows still not usable by the semantic leg after this slice (old width/space, or no vector). */
491
+ remaining: number;
492
+ /** Rows re-encoded into the current space by this slice. */
493
+ migrated: number;
494
+ /** Rows whose old-space bytes were dropped so a re-encode could replace them. */
495
+ dropped: number;
496
+ /** Usable persisted vectors re-added to the live index (no model needed). */
497
+ reindexed: number;
498
+ /** `false` when the model was not loaded: nothing could be encoded this slice. */
499
+ semantic_available: boolean;
500
+ }
501
+ /**
502
+ * The outcome of a whole migration drive (`MemoryStore.migrateVectors`): the last slice plus
503
+ * cumulative counters, and whether the `semantic.auto_migrate` switch allowed any work at all.
504
+ */
505
+ export interface VectorMigrationOutcome extends VectorMigrationProgress {
506
+ /** `false` when `semantic.auto_migrate` is off — nothing was migrated on purpose. */
507
+ enabled: boolean;
508
+ }
354
509
  /**
355
510
  * `mem_admin vectors_fix`: what a repair did, or — with `dry_run` — what it WOULD do.
356
511
  *
@@ -1,4 +1,4 @@
1
- import type { SemanticBackend } from '../interfaces.js';
1
+ import type { SemanticBackend, SemanticRepresentation } from '../interfaces.js';
2
2
  /**
3
3
  * The injected pipeline surface: one text OR an array of texts in, a tensor out.
4
4
  *
@@ -31,6 +31,18 @@ export interface ModelEnv {
31
31
  * text first and logs it once (`text_budget.ts`).
32
32
  */
33
33
  maxInputTokens?: number;
34
+ /**
35
+ * Pooling strategy handed to the feature-extraction pipeline (`mean` by default). Part of the
36
+ * representation fingerprint (`representationKey`): `mean` and `cls` produce DIFFERENT
37
+ * coordinates from the same weights, so a change here is a data migration, not a tuning knob.
38
+ */
39
+ pooling?: string;
40
+ /**
41
+ * Whether the pipeline L2-normalizes (`true` by default). Also part of the representation:
42
+ * an un-normalized vector ranks differently under cosine, so switching it invalidates every
43
+ * persisted vector exactly like a model swap does.
44
+ */
45
+ normalize?: boolean;
34
46
  /**
35
47
  * Do NOT warm in the constructor: the first `encode()` / `encodeBatch()` warms lazily.
36
48
  *
@@ -61,13 +73,34 @@ export declare class LocalBgeBackend implements SemanticBackend {
61
73
  private _dimWarned;
62
74
  private _lengthWarned;
63
75
  private _window;
76
+ /** Cached so the fingerprint cannot move while the process runs (see {@link representation}). */
77
+ private _representation;
64
78
  private readonly configuredMax;
79
+ private readonly pooling;
80
+ private readonly normalize;
65
81
  private readonly model;
66
82
  private readonly env;
67
83
  private readonly pipeFactory;
68
84
  private readonly gate;
69
85
  constructor(model: string, dim: number, env?: Partial<ModelEnv>, pipeFactory?: PipeFactory);
70
86
  isAvailable(): boolean;
87
+ /**
88
+ * The representation knobs this adapter applies, resolved ONCE per instance.
89
+ *
90
+ * Resolved once on purpose: the model revision comes from disk, and a fingerprint that changed
91
+ * mid-process (install settles, sidecar appears) would make the store declare the rows it just
92
+ * wrote stale. The cost of reading it early is bounded and honest — a process that starts BEFORE
93
+ * the family provisioner lands the model writes a revision-less fingerprint for that session, and
94
+ * the next start re-encodes once into the revision-carrying one. The alternative (reading late)
95
+ * would re-encode on every restart instead.
96
+ *
97
+ * A missing sidecar is reported once, as a DEGRADATION: the fingerprint then cannot see a repo
98
+ * whose weights were re-pushed under the same name, which is a real (narrow) blind spot and must
99
+ * not be mistaken for a healthy state. It never throws, and it stays quiet while the weights are
100
+ * not on disk yet (a fresh install in flight has nothing to fingerprint; the sidecar arrives with
101
+ * the weights). The resolved representation is CACHED, so the warning is one line per process.
102
+ */
103
+ representation(): SemanticRepresentation;
71
104
  /**
72
105
  * Fire-and-forget retry for call sites that only observe availability. No-op
73
106
  * while the model is ready or an attempt is already in flight; otherwise
@@ -111,11 +144,8 @@ export declare class LocalBgeBackend implements SemanticBackend {
111
144
  /**
112
145
  * Apply this project's cache/mirror policy to transformers.js.
113
146
  *
114
- * `mod.env` is PROCESS-GLOBAL and last-writer-wins: the embedder and the reranker are two adapters
115
- * over ONE module, so whichever warms second decides what both see. Setting the keys from a single
116
- * place is what keeps the two from disagreeing — the reranker used to set `allowLocalModels` on its
117
- * own, which reads as a policy difference between the adapters and was really an accident of which
118
- * file got edited last. `allowLocalModels` is already the library default; it is stated here so the
119
- * policy is explicit rather than inherited, and identical for both adapters.
147
+ * `mod.env` is PROCESS-GLOBAL and last-writer-wins. Setting the keys from a single place is what
148
+ * keeps every future adapter over this module from disagreeing: `allowLocalModels` is already the
149
+ * library default, stated here so the policy is explicit rather than inherited.
120
150
  */
121
151
  export declare function applyModelEnv(mod: TransformersModule, env: ModelEnv): void;
@@ -29,6 +29,17 @@ export interface FusionPath {
29
29
  export interface Hit {
30
30
  id: number;
31
31
  score: number;
32
+ /**
33
+ * P-01 per-leg evidence, aligned to the `paths` array the caller passed, and only present when
34
+ * the caller asked for it (`opts.includeLegs`). `null` at an index means that leg did not
35
+ * recall this candidate — the explicit "no evidence", which a missing entry would hide.
36
+ */
37
+ legs?: (LegScore | null)[];
38
+ }
39
+ /** P-01: one leg's contribution to one candidate (raw on the leg's scale + the value fused). */
40
+ export interface LegScore {
41
+ raw: number;
42
+ normalized: number;
32
43
  }
33
44
  /**
34
45
  * Scale raw scores by the set's maximum, into [0,1].
@@ -50,4 +61,6 @@ export declare function scaleByMax(scores: Map<number, number>): Map<number, num
50
61
  * scaled scores collide far more often than min-max ones did (both legs of a pair can rank two
51
62
  * candidates identically), and the previous order was Map-insertion order.
52
63
  */
53
- export declare function fuse(paths: FusionPath[], topK: number): Hit[];
64
+ export declare function fuse(paths: FusionPath[], topK: number, opts?: {
65
+ includeLegs?: boolean;
66
+ }): Hit[];
@@ -1,14 +1,12 @@
1
1
  export * from './interfaces.js';
2
+ export * from './representation.js';
2
3
  export * from './log.js';
3
4
  export * from './stats.js';
4
5
  export * from './text_budget.js';
5
6
  export * from './budget.js';
6
7
  export * from './registry.js';
7
8
  export * from './fusion.js';
8
- export * from './rerank.js';
9
9
  export * from './adapters/local_numpy.js';
10
10
  export * from './adapters/hnswlib.js';
11
11
  export * from './adapters/auto_vstore.js';
12
- export * from './adapters/none_reranker.js';
13
12
  export * from './adapters/local_bge.js';
14
- export * from './adapters/local_reranker.js';
@@ -1,8 +1,38 @@
1
1
  /**
2
- * The three stable contracts the business flow depends on.
2
+ * The two stable contracts the business flow depends on.
3
3
  * The business flow (fusion pipeline, stores, tools, router, UI) must depend only on
4
4
  * these interfaces — never on a concrete backend. See DESIGN §5.
5
5
  */
6
+ /**
7
+ * HOW a backend turns text into a vector — the knobs that decide which coordinates a stored vector
8
+ * lives in, beyond `name`/`dim`.
9
+ *
10
+ * The vector-space identity used to record only `backend/model/dim`, so a change of pooling,
11
+ * normalization, input window or the weights behind the same repo name produced the SAME identity:
12
+ * new vectors were written into an index that still held the old ones and nothing detected the
13
+ * migration (see `db/vectors.ts` and DESIGN §20). A backend that can name these knobs declares them
14
+ * here and the space id carries them (see `representationKey`).
15
+ */
16
+ export interface SemanticRepresentation {
17
+ /** Pooling strategy handed to the feature-extraction pipeline (`mean`, `cls`, …). */
18
+ readonly pooling: string;
19
+ /** Whether vectors are L2-normalized by the pipeline (cosine relies on it). */
20
+ readonly normalize: boolean;
21
+ /**
22
+ * The CONFIGURED input-token cap (`0` = auto/declared window), i.e. the truncation window the
23
+ * adapter bounds text to before encoding. The CONFIGURED value, not the resolved one: the
24
+ * resolved window depends on the loaded tokenizer, so it is unknown before warmup — and the
25
+ * fingerprint must be identical before and after warmup, or a store would declare its own rows
26
+ * stale the moment the model finished loading.
27
+ */
28
+ readonly maxInputTokens: number;
29
+ /**
30
+ * Content identity of the model files (the family sidecar's revision sha), when one could be
31
+ * read. Absent = "could not be read" (not "known to be empty"): the fingerprint then omits it,
32
+ * which is the documented degradation, never an error.
33
+ */
34
+ readonly revision?: string;
35
+ }
6
36
  /** Encodes text into a normalized embedding vector. */
7
37
  export interface SemanticBackend {
8
38
  readonly name: string;
@@ -10,6 +40,13 @@ export interface SemanticBackend {
10
40
  encode(text: string): Promise<Float32Array>;
11
41
  encodeBatch(texts: string[]): Promise<Float32Array[]>;
12
42
  isAvailable(): boolean;
43
+ /**
44
+ * Optional declaration of the representation knobs this backend applies. The built-in
45
+ * `local_bge` declares them; a third-party backend that omits this is fingerprinted as
46
+ * `rep=undeclared` (the space id still changes with name/dim), which is honest rather than a
47
+ * silent claim to a representation nobody verified.
48
+ */
49
+ representation?(): SemanticRepresentation;
13
50
  /** Optional async bootstrap: configure env + download/load the model. */
14
51
  warmUp?(): Promise<void>;
15
52
  /**
@@ -20,19 +57,6 @@ export interface SemanticBackend {
20
57
  */
21
58
  ensureWarm?(): void;
22
59
  }
23
- /** Re-ranks candidate facts/chunks by query relevance. */
24
- export interface Reranker {
25
- readonly name: string;
26
- rerank(query: string, candidates: {
27
- id: number;
28
- text: string;
29
- }[]): Promise<number[]>;
30
- isAvailable(): boolean;
31
- /** Optional async bootstrap: configure env + download/load the model. */
32
- warmUp?(): Promise<void>;
33
- /** Optional non-blocking retry — see {@link SemanticBackend.ensureWarm}. */
34
- ensureWarm?(): void;
35
- }
36
60
  /** Distance/ann index over entry vectors (per-store instance). */
37
61
  export interface VectorStore {
38
62
  readonly name: string;
@@ -1,5 +1,5 @@
1
1
  import type { Config } from '../mem-contract/index.js';
2
- import type { Reranker, SemanticBackend, VectorStore } from './interfaces.js';
2
+ import type { SemanticBackend, VectorStore } from './interfaces.js';
3
3
  type Factory<T> = (cfg: Config, opts?: ResolveOptions) => T;
4
4
  /**
5
5
  * How a backend is resolved.
@@ -8,16 +8,14 @@ type Factory<T> = (cfg: Config, opts?: ResolveOptions) => T;
8
8
  * family framework installs the model into the managed root ASYNCHRONOUSLY, so a constructor-time
9
9
  * warm would race that install and fetch a second copy. The plugin passes `deferWarm: true` and
10
10
  * warms from the model item's `onSettled`; every other caller (CLI, MCP, tests) keeps the eager
11
- * constructor warm. `encode()` / `rerank()` still warm lazily, so deferring never loses the model.
11
+ * constructor warm. `encode()` still warms lazily, so deferring never loses the model.
12
12
  */
13
13
  export interface ResolveOptions {
14
14
  /** Skip the constructor warm; the caller warms explicitly (see `ModelEnv.deferWarm`). */
15
15
  readonly deferWarm?: boolean;
16
16
  }
17
17
  export declare function registerSemanticBackend(name: string, factory: Factory<SemanticBackend>): void;
18
- export declare function registerReranker(name: string, factory: Factory<Reranker>): void;
19
18
  export declare function registerVectorStore(name: string, factory: Factory<VectorStore>): void;
20
19
  export declare function resolveSemantic(cfg: Config, opts?: ResolveOptions): SemanticBackend;
21
- export declare function resolveReranker(cfg: Config, opts?: ResolveOptions): Reranker;
22
20
  export declare function resolveVStore(cfg: Config): VectorStore;
23
21
  export {};
@@ -0,0 +1,50 @@
1
+ import type { SemanticBackend, SemanticRepresentation } from './interfaces.js';
2
+ /** The declared representation of a backend that does not implement `representation()`. */
3
+ export declare const UNDECLARED_REPRESENTATION = "rep=undeclared";
4
+ /** What {@link readModelRevision} found (or why it found nothing). */
5
+ export interface ModelRevisionRead {
6
+ /** The model's content identity when the sidecar recorded one. */
7
+ readonly revision?: string;
8
+ /** Where it was looked for (the degradation warning names it). */
9
+ readonly path: string;
10
+ /** Why the revision is absent — short, for the one-line degradation warning. */
11
+ readonly detail: string;
12
+ }
13
+ /**
14
+ * Normalized, stable representation key embedded in a vector-space id.
15
+ *
16
+ * Field order is fixed and each value is normalized (lowercased pooling, 1/0 normalize, a
17
+ * truncated non-negative integer window), so two runs that describe the SAME representation always
18
+ * produce the SAME key — a fingerprint that changed spuriously would re-encode a whole corpus for
19
+ * nothing. A blank/absent revision is OMITTED rather than encoded as a placeholder: "unknown" then
20
+ * looks different from any known revision, which is the honest reading (the weights could have
21
+ * changed without this process being able to tell).
22
+ */
23
+ export declare function representationKey(rep: SemanticRepresentation): string;
24
+ /** {@link representationKey} for a live backend, or the explicit "undeclared" marker. */
25
+ export declare function representationKeyOf(backend: SemanticBackend): string;
26
+ /** The sidecar path for `model` under `cacheDir`, mirroring the base's flat-layout naming
27
+ * (`models--<owner>--<name>`), which is why a slash in the repo id becomes `--`.
28
+ */
29
+ export declare function modelRecordPath(cacheDir: string, model: string): string;
30
+ /**
31
+ * Whether the model's weights are actually on disk in the flat layout the adapter reads
32
+ * (`<cacheDir>/<repo>/config.json`).
33
+ *
34
+ * Only used to decide whether a missing revision is worth a WARNING: a machine that has never
35
+ * installed the model (a fresh install still in flight) has nothing to fingerprint yet, so the
36
+ * degradation is real but not yet reportable — warning there would be noise on every first run.
37
+ * Once the weights are present and the sidecar still is not, "no revision" is a genuine blind spot.
38
+ */
39
+ export declare function modelFilesPresent(cacheDir: string, model: string): boolean;
40
+ /**
41
+ * Read the family sidecar's revision for `model`, never throwing.
42
+ *
43
+ * Absence is a legitimate state (a model fetched by transformers.js directly, a machine that never
44
+ * ran the family provisioner, an install still in flight), not an error: the caller reports the
45
+ * degradation once and the fingerprint simply omits the revision.
46
+ *
47
+ * @param cacheDir - the model cache root the adapter actually reads (`semantic.cache_dir`).
48
+ * @param model - the repo id (`Xenova/bge-base-zh-v1.5`).
49
+ */
50
+ export declare function readModelRevision(cacheDir: string, model: string): ModelRevisionRead;
@@ -21,8 +21,6 @@ export interface RetrievalEvent {
21
21
  results: number;
22
22
  latencyMs: number;
23
23
  semanticLive: boolean;
24
- rerankUsed?: boolean;
25
- rerankFallback?: boolean;
26
24
  /**
27
25
  * Per-leg candidates removed by the relevance floors before fusion. The per-leg shape is what
28
26
  * makes an empty result readable ("the FTS floor cut 7 rows" vs "no leg had a candidate"); the
@@ -32,7 +30,7 @@ export interface RetrievalEvent {
32
30
  }
33
31
  /** Record one completed retrieval. */
34
32
  export declare function recordRetrieval(event: RetrievalEvent): void;
35
- export type TruncationKind = 'embedding' | 'rerank' | 'output';
33
+ export type TruncationKind = 'embedding' | 'output';
36
34
  /**
37
35
  * Record that a retrieval LEG returned exactly its cap, i.e. that its tail was dropped.
38
36
  *