@avantf/dsh-mem 0.4.2 → 0.6.1

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 (54) hide show
  1. package/README.md +19 -1
  2. package/lib/client.js +39 -9
  3. package/lib/engine/mem/config/config_files.d.ts +1 -1
  4. package/lib/engine/mem/db/dao/chunks.d.ts +80 -5
  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 +115 -12
  8. package/lib/engine/mem/db/dao/shared.d.ts +153 -0
  9. package/lib/engine/mem/db/invariants.d.ts +26 -0
  10. package/lib/engine/mem/db/tokenizer.d.ts +23 -0
  11. package/lib/engine/mem/db/vectors.d.ts +32 -6
  12. package/lib/engine/mem/entities/extract.d.ts +1 -1
  13. package/lib/engine/mem/eval/derive.d.ts +120 -0
  14. package/lib/engine/mem/eval/derived_runner.d.ts +88 -0
  15. package/lib/engine/mem/eval/index.d.ts +4 -0
  16. package/lib/engine/mem/eval/metrics.d.ts +24 -0
  17. package/lib/engine/mem/eval/runner.d.ts +8 -1
  18. package/lib/engine/mem/index.d.ts +2 -1
  19. package/lib/engine/mem/modelBootstrap.d.ts +2 -2
  20. package/lib/engine/mem/runtime.d.ts +3 -3
  21. package/lib/engine/mem/store/common.d.ts +18 -6
  22. package/lib/engine/mem/store/entity_leg.d.ts +95 -0
  23. package/lib/engine/mem/store/entity_sweep.d.ts +121 -0
  24. package/lib/engine/mem/store/floors.d.ts +51 -31
  25. package/lib/engine/mem/store/hybrid.d.ts +71 -26
  26. package/lib/engine/mem/store/knowledge.d.ts +134 -21
  27. package/lib/engine/mem/store/legs.d.ts +171 -0
  28. package/lib/engine/mem/store/lexical.d.ts +52 -0
  29. package/lib/engine/mem/store/memory.d.ts +177 -92
  30. package/lib/engine/mem/store/secret_guard.d.ts +34 -0
  31. package/lib/engine/mem/store/time_window.d.ts +74 -0
  32. package/lib/engine/mem/store/vector_repair.d.ts +248 -0
  33. package/lib/engine/mem-contract/config.d.ts +8 -16
  34. package/lib/engine/mem-contract/family.d.ts +1 -1
  35. package/lib/engine/mem-contract/tools.d.ts +14 -0
  36. package/lib/engine/mem-contract/types.d.ts +151 -15
  37. package/lib/engine/mem-retrieval/adapters/local_bge.d.ts +37 -7
  38. package/lib/engine/mem-retrieval/fusion.d.ts +14 -1
  39. package/lib/engine/mem-retrieval/index.d.ts +1 -3
  40. package/lib/engine/mem-retrieval/interfaces.d.ts +38 -14
  41. package/lib/engine/mem-retrieval/registry.d.ts +2 -4
  42. package/lib/engine/mem-retrieval/representation.d.ts +50 -0
  43. package/lib/engine/mem-retrieval/stats.d.ts +1 -3
  44. package/lib/index.js +3008 -1008
  45. package/lib/types/client/index.d.ts.map +1 -1
  46. package/lib/types/index.d.ts +1 -1
  47. package/lib/types/index.d.ts.map +1 -1
  48. package/lib/types/prompt.d.ts +7 -0
  49. package/lib/types/prompt.d.ts.map +1 -1
  50. package/lib/types/remote.d.ts +12 -6
  51. package/lib/types/remote.d.ts.map +1 -1
  52. package/lib/types/vectorMigration.d.ts +22 -18
  53. package/lib/types/vectorMigration.d.ts.map +1 -1
  54. package/package.json +5 -5
@@ -0,0 +1,248 @@
1
+ /**
2
+ * The ONE vector-space repair flow both stores run.
3
+ *
4
+ * Changing the embedding space (model, width, pooling, normalization, input window, weights) is a
5
+ * DATA MIGRATION: persisted vectors keep decoding but live in another model's coordinates, so the
6
+ * semantic leg must not serve them. `common.ts` owns the cheap DETECTION (`reportStaleVectors`); this
7
+ * module owns the REPAIR — a bounded, resumable, switchable loop that re-encodes the corpus in
8
+ * slices and yields the event loop between them.
9
+ *
10
+ * It is a template method implemented by COMPOSITION (AGENTS.md / the design note in
11
+ * `docs/vector-repair-shared-flow.md`): the skeleton lives here as free functions, each store
12
+ * supplies a {@link VectorRepairTarget} adapter. No common base class — the two stores are separate
13
+ * databases with separate DAOs, and a base class would be a structural rewrite of two hub files.
14
+ *
15
+ * What is shared, and therefore must never be copied back into `store/memory.ts` /
16
+ * `store/knowledge.ts`: the batch size, the `yieldToEventLoop` call, the stop / resume / no-progress
17
+ * rules, the `semantic.auto_migrate` decision, the `dry_run` split, the order invariant
18
+ * (unusable bytes leave first, then re-encode), the warmup policy and the four log kinds.
19
+ *
20
+ * What stays in the stores (correct differences, DESIGN §2.3): the DAO calls and row shapes, the
21
+ * `conflict_checked` re-queue on the memory side, the `content_hash` reuse and failure counting on
22
+ * the knowledge side, the HRR column, and which live index a row is added to.
23
+ *
24
+ * @module store/vector_repair
25
+ */
26
+ import type { AvantfLogger, VectorMigrationOutcome, VectorMigrationProgress, VectorsFixReport, VectorsFixStoreReport } from '../../mem-contract/index.js';
27
+ import type { VectorStore } from '../../mem-retrieval/index.js';
28
+ import { type StaleVectorCounts } from './common.js';
29
+ /** The two stores this flow can drive. */
30
+ export type VectorRepairStore = 'memory' | 'knowledge';
31
+ /**
32
+ * Rows one background vector-space migration slice may re-encode.
33
+ *
34
+ * Small on purpose: the point of batching is that a query arriving mid-migration waits for at most
35
+ * one ONNX forward pass batch, not for a whole corpus. At ~30 ms per bge-base-zh encode a batch of 16
36
+ * is well under a second of contested model time, and the event loop is yielded between batches
37
+ * (`yieldToEventLoop`). The value is a default, not a promise — `migrateVectorSlice` takes whatever
38
+ * the caller needs.
39
+ */
40
+ export declare const DEFAULT_VECTOR_MIGRATION_BATCH = 16;
41
+ /**
42
+ * What one store's persisted-vector scan found, before any write.
43
+ *
44
+ * `stale` and `space_stale` mirror the cheap detection counts (`StaleVectorCounts`); `missing` is
45
+ * "ACTIVE row with no persisted vector at all" — the third reason the semantic leg cannot use a row.
46
+ */
47
+ export interface VectorsClassification {
48
+ /** ACTIVE rows whose persisted vector has the wrong width (it cannot even be decoded). */
49
+ stale: number;
50
+ /** ACTIVE rows whose usable-width vector was recorded in another space. */
51
+ space_stale: number;
52
+ /** ACTIVE rows with no persisted vector at all. */
53
+ missing: number;
54
+ /**
55
+ * Ids whose unusable bytes must leave the store before a re-encode picks them up (the `limit`
56
+ * passed to {@link VectorRepairTarget.classifyVectors} is applied). Knowledge overwrites vectors
57
+ * in place, so it reports none.
58
+ */
59
+ dropIds: readonly number[];
60
+ /**
61
+ * Usable persisted vectors absent from the LIVE index, decoded and ready to re-add. Only stores
62
+ * whose diagnose surface tracks the live index report this (memory); knowledge rebuilds its index
63
+ * at open and keeps it in sync on every write, so it omits the property entirely.
64
+ */
65
+ unindexed?: readonly {
66
+ id: number;
67
+ vec: Float32Array;
68
+ }[];
69
+ }
70
+ /**
71
+ * One store's half of the repair flow — the "key methods" of the template.
72
+ *
73
+ * Every method is a PRIMITIVE the store can answer from its own DAO; none of them contains the
74
+ * loop, the batch size or the switch logic (those are in this module, once).
75
+ */
76
+ export interface VectorRepairTarget {
77
+ readonly store: VectorRepairStore;
78
+ /** The manual entry the stale-vector warning points at, as the operator would type it. */
79
+ readonly manualEntry: string;
80
+ /** The representation fingerprint this store writes into now. */
81
+ vectorSpace(): string;
82
+ /** Is the embedding model loaded RIGHT NOW? (Never warms — the flow owns the warmup policy.) */
83
+ semanticAvailable(): boolean;
84
+ /** The RAW `semantic.auto_migrate` value; the flow decides what it means (`!== false`). */
85
+ autoMigrate(): boolean | undefined;
86
+ /** Cheap `{ stale, space_stale, legacy? }` — blob LENGTHS only, never decodes. */
87
+ vectorSpaceHealth(): StaleVectorCounts;
88
+ /** Cheap count of ACTIVE rows with no persisted vector (part of `remaining`). */
89
+ missingVectorCount(): number;
90
+ /** One scan classifying what needs dropping and re-encoding; `limit` bounds `dropIds`. */
91
+ classifyVectors(limit: number): VectorsClassification;
92
+ /**
93
+ * Optional: put usable persisted vectors missing from the live index back into it, returning how
94
+ * many were added. Called with the slice of {@link VectorsClassification.unindexed} the current
95
+ * `limit` allows, so the flow does not re-scan the corpus for it.
96
+ */
97
+ reindexUsable?(rows: readonly {
98
+ id: number;
99
+ vec: Float32Array;
100
+ }[]): number;
101
+ /** Clear unusable bytes (table + live index); returns how many rows were actually cleared. */
102
+ dropVectors(ids: readonly number[]): number;
103
+ /** Encode + persist up to `limit` rows into the current space, one row isolated at a time. */
104
+ reencodeVectors(limit: number): Promise<{
105
+ encoded: number;
106
+ failed: number;
107
+ }>;
108
+ /** Optional: wait for the embedding model (explicit repair only; the background never waits). */
109
+ warmup?(): Promise<boolean>;
110
+ }
111
+ /** A persisted vector row as read back from a store, for the shared open-time load. */
112
+ export interface PersistedVectorRow {
113
+ id: number;
114
+ vec: Uint8Array | null;
115
+ embedding_model?: string | null;
116
+ }
117
+ /**
118
+ * Rebuild one store's live vstore from its persisted rows, count what is unusable in the current
119
+ * space, and say so once — the shared "open for business" step both stores used to write twice.
120
+ *
121
+ * The owning store's ONLY half is the row read (`facts.activeVectorRows()` /
122
+ * `chunks.vectorRows()`); the reload, the count and the loud warning are one implementation, so a
123
+ * change to what counts as stale cannot land on one store only (the incident this exists for: the
124
+ * memory store checked the SPACE while knowledge checked only the WIDTH, so a model swap mixed two
125
+ * spaces in one index until someone happened to run `kb_reindex`).
126
+ */
127
+ export declare function loadVectorIndex(opts: {
128
+ store: VectorRepairStore;
129
+ vstore: VectorStore;
130
+ rows: readonly PersistedVectorRow[];
131
+ space: string;
132
+ manualEntry: string;
133
+ }): StaleVectorCounts;
134
+ /** One row the shared encode flow should turn into a vector. */
135
+ export interface VectorEncodeRow {
136
+ id: number;
137
+ text: string;
138
+ }
139
+ /** One encoded row ready to be persisted. */
140
+ export interface EncodedVector {
141
+ id: number;
142
+ bytes: Buffer;
143
+ }
144
+ /**
145
+ * One store's half of {@link encodeAndPersist}: how to encode, where the live index is, and how a
146
+ * group of vectors is persisted.
147
+ */
148
+ export interface EncodeWriteTarget {
149
+ readonly store: VectorRepairStore;
150
+ /** The space recorded beside every persisted vector. */
151
+ readonly space: string;
152
+ /** One text → vector. Throwing skips that row (never the group). */
153
+ encode(text: string): Promise<Float32Array>;
154
+ /** Add a freshly encoded vector to THIS store's live index. */
155
+ add(id: number, vec: Float32Array): void;
156
+ /**
157
+ * Persist one group of encoded rows. Throwing fails the WHOLE group, which is what lets the store
158
+ * decide how to reconcile its live index (see {@link onWriteError}).
159
+ */
160
+ write(rows: readonly EncodedVector[]): void;
161
+ /** Rows per `write` call (default 1). Knowledge uses its measured `WRITE_BATCH`. */
162
+ readonly writeBatch?: number;
163
+ /** Called once per row whose encode (or live-index add) failed. */
164
+ onRowError?(row: VectorEncodeRow, error: unknown): void;
165
+ /** Called once per group whose `write` failed, with the rows that never reached the database. */
166
+ onWriteError?(rows: readonly EncodedVector[], error: unknown): void;
167
+ }
168
+ /**
169
+ * Encode rows one at a time, add each to the live index, and persist them in groups.
170
+ *
171
+ * The shared skeleton: serial encode (a MEASURED decision for chunks — a padded batched forward pass
172
+ * is 1.79x slower on real 53–550 character chunks), `add` right after a successful encode, one
173
+ * `write` call per `writeBatch` rows, per-row isolation of encode failures, per-group isolation of
174
+ * write failures, and the `{ encoded, failed }` count.
175
+ *
176
+ * What stays with the store: the backend call, the live index, the write statement, the replay of a
177
+ * failed group, and the summary log — the memory store logs per row and keeps a half-written index
178
+ * (documented as the benign direction for it), the knowledge store evicts the group so both sides
179
+ * agree that the vectors are missing.
180
+ */
181
+ export declare function encodeAndPersist(rows: readonly VectorEncodeRow[], target: EncodeWriteTarget): Promise<{
182
+ encoded: number;
183
+ failed: number;
184
+ }>;
185
+ /**
186
+ * ONE bounded slice of the repair: re-add unusable-but-readable vectors that the live index lost,
187
+ * drop the rows a re-encode has to replace, then encode up to `limit` missing/foreign rows.
188
+ *
189
+ * `limit` is what makes the SAME logic usable as a background migration: one call is a bounded unit
190
+ * of work the caller can yield between. The explicit fix passes no limit (the operator action drains
191
+ * everything); the background drive passes its batch size.
192
+ *
193
+ * The ORDER is an invariant: unusable bytes leave the table and the live index first, so a
194
+ * re-encode can pick them up and a process that dies mid-migration leaves at most one batch
195
+ * vector-less — exactly the `missing` rows the next run resumes on.
196
+ */
197
+ export declare function repairVectorSlice(target: VectorRepairTarget, opts: {
198
+ dryRun: boolean;
199
+ limit?: number;
200
+ }): Promise<{
201
+ report: VectorsFixStoreReport;
202
+ remaining: number;
203
+ }>;
204
+ /**
205
+ * ONE bounded slice of the background migration — the unit the drive (and the tests) step.
206
+ *
207
+ * Resumability is a property of the DATABASE, not of this process: every slice re-derives what is
208
+ * stale from the persisted width/space, so a restart simply continues where the last slice stopped.
209
+ */
210
+ export declare function migrateVectorSlice(target: VectorRepairTarget, batchSize?: number): Promise<VectorMigrationProgress>;
211
+ /** Options for the background drive; the four log kinds go to `log` (the host logger by default). */
212
+ export interface DriveVectorRepairOptions {
213
+ batchSize?: number;
214
+ /** Plugin unmount / runtime shutdown: stop at the next batch boundary. */
215
+ shouldStop?: () => boolean;
216
+ onProgress?: (progress: VectorMigrationOutcome) => void;
217
+ /** Log sink; `retrievalLogger()` (the host logger once the runtime installed it) when omitted. */
218
+ log?: AvantfLogger;
219
+ }
220
+ /**
221
+ * Drive the bounded background migration for one store until it is current, the model is
222
+ * unavailable, or `shouldStop()` turns true (plugin unmount / process shutdown). A batch FAILURE
223
+ * never rejects: it is logged and reported as `remaining > 0`, which is what makes the next
224
+ * heartbeat a retry. Never blocks the event loop for longer than one batch.
225
+ *
226
+ * Honours `semantic.auto_migrate` (default on) — the switch exists so a host that must not spend CPU
227
+ * on background re-encoding can leave the loud warning and the manual entry as the only path. The
228
+ * switch is read through the adapter so a new store cannot forget it.
229
+ */
230
+ export declare function driveVectorRepair(target: VectorRepairTarget, opts?: DriveVectorRepairOptions): Promise<VectorMigrationOutcome>;
231
+ /**
232
+ * Drive every store in order. A stop request keeps the drive from starting another store, but each
233
+ * store still answers with its snapshot (so the caller can report what is left everywhere).
234
+ */
235
+ export declare function driveVectorRepairs(targets: readonly VectorRepairTarget[], opts?: DriveVectorRepairOptions): Promise<VectorMigrationOutcome[]>;
236
+ /**
237
+ * The explicit, unbounded repair behind `mem_admin vectors_fix` / `avantf-mem vectors --fix`,
238
+ * covering one or both stores.
239
+ *
240
+ * `store` selects a single store (omitted = both); `dry_run` previews every count without writing —
241
+ * and without loading the model (so a preview also reports `would_warm`). The top-level
242
+ * `semantic_available` is AND over the selected stores: `false` means at least one store's repair
243
+ * could not encode anything.
244
+ */
245
+ export declare function vectorsFix(targets: Partial<Record<VectorRepairStore, VectorRepairTarget>>, opts: {
246
+ dryRun: boolean;
247
+ store?: VectorRepairStore;
248
+ }): Promise<VectorsFixReport>;
@@ -1,13 +1,12 @@
1
1
  import { z } from 'zod';
2
2
  /**
3
- * Input window of the SHIPPED models: `Xenova/bge-base-zh-v1.5` (embedder) and
4
- * `Xenova/bge-reranker-base` (reranker) both read 512 tokens. (The previous default embedder,
5
- * `Xenova/bge-small-zh-v1.5`, shares the same 512-token window — this constant did NOT move when
6
- * the embedder changed; only its vector width did, 512 → 768.)
3
+ * Input window of the SHIPPED model: `Xenova/bge-base-zh-v1.5` reads 512 tokens. (The previous
4
+ * default embedder, `Xenova/bge-small-zh-v1.5`, shares the same 512-token window — this constant
5
+ * did NOT move when the embedder changed; only its vector width did, 512 → 768.)
7
6
  *
8
- * Exported because more than one default derives from it: the embedder's fallback window, the
9
- * reranker's pair budget (retrieval-core), and `knowledge.chunk_size` below. transformers.js
10
- * truncates past the window SILENTLY, so every derivation has to agree on the number.
7
+ * Exported because more than one default derives from it: the embedder's fallback window and
8
+ * `knowledge.chunk_size` below. transformers.js truncates past the window SILENTLY, so every
9
+ * derivation has to agree on the number.
11
10
  */
12
11
  export declare const DEFAULT_MODEL_WINDOW_TOKENS = 512;
13
12
  /** The merged runtime config shared by memory and knowledge stores. */
@@ -23,14 +22,6 @@ export declare const ConfigSchema: z.ZodObject<{
23
22
  auto_download: z.ZodDefault<z.ZodBoolean>;
24
23
  auto_migrate: z.ZodDefault<z.ZodBoolean>;
25
24
  }, z.core.$strip>>;
26
- rerank: z.ZodPrefault<z.ZodObject<{
27
- backend: z.ZodDefault<z.ZodString>;
28
- local_model: z.ZodDefault<z.ZodString>;
29
- max_input_tokens: z.ZodDefault<z.ZodNumber>;
30
- mirror: z.ZodDefault<z.ZodString>;
31
- cache_dir: z.ZodDefault<z.ZodString>;
32
- auto_download: z.ZodDefault<z.ZodBoolean>;
33
- }, z.core.$strip>>;
34
25
  vectorStore: z.ZodPrefault<z.ZodObject<{
35
26
  backend: z.ZodDefault<z.ZodString>;
36
27
  auto_thresholds: z.ZodDefault<z.ZodObject<{
@@ -48,6 +39,7 @@ export declare const ConfigSchema: z.ZodObject<{
48
39
  min_fts_terms: z.ZodDefault<z.ZodNumber>;
49
40
  min_jaccard: z.ZodDefault<z.ZodNumber>;
50
41
  leg_cap: z.ZodDefault<z.ZodNumber>;
42
+ time_window: z.ZodDefault<z.ZodBoolean>;
51
43
  }, z.core.$strip>>;
52
44
  lifecycle: z.ZodPrefault<z.ZodObject<{
53
45
  purge_after_archived_days: z.ZodDefault<z.ZodNumber>;
@@ -120,8 +112,8 @@ export declare const KnowledgeConfigSchema: z.ZodObject<{
120
112
  chunk_size: z.ZodDefault<z.ZodNumber>;
121
113
  chunk_overlap: z.ZodDefault<z.ZodNumber>;
122
114
  source_priority: z.ZodDefault<z.ZodEnum<{
123
- none: "none";
124
115
  workspace: "workspace";
116
+ none: "none";
125
117
  }>>;
126
118
  ingest: z.ZodPrefault<z.ZodObject<{
127
119
  local_roots: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * `@avantf/dsh-plugin-base` (the family's environment framework) installs everything it manages under a
6
6
  * single root: `$AVANTF_HOME` when set, else `~/.avantf/env`. `tools` holds external binaries
7
- * (pandoc), `models` the downloaded embedding/rerank weights in the flat layout the transformers.js
7
+ * (pandoc), `models` the downloaded embedding weights in the flat layout the transformers.js
8
8
  * cache reads. The engine's built-in defaults point HERE, not at the pre-framework
9
9
  * `~/.avantf/{tools,models}`: those legacy directories are no longer a fallback anywhere, so a
10
10
  * machine that never creates them loses nothing, and no process resolves a path that only missions
@@ -54,12 +54,18 @@ export declare const RememberUnion: z.ZodDiscriminatedUnion<[z.ZodObject<{
54
54
  content: z.ZodString;
55
55
  category: z.ZodOptional<z.ZodString>;
56
56
  ttl_days: z.ZodOptional<z.ZodNumber>;
57
+ source_ref: z.ZodOptional<z.ZodString>;
58
+ event_date: z.ZodOptional<z.ZodString>;
59
+ valid_until: z.ZodOptional<z.ZodString>;
57
60
  }, z.core.$strip>, z.ZodObject<{
58
61
  action: z.ZodLiteral<"update">;
59
62
  fact_id: z.ZodNumber;
60
63
  content: z.ZodString;
61
64
  category: z.ZodOptional<z.ZodString>;
62
65
  ttl_days: z.ZodOptional<z.ZodNumber>;
66
+ source_ref: z.ZodOptional<z.ZodString>;
67
+ event_date: z.ZodOptional<z.ZodString>;
68
+ valid_until: z.ZodOptional<z.ZodString>;
63
69
  }, z.core.$strip>, z.ZodObject<{
64
70
  action: z.ZodLiteral<"remove">;
65
71
  fact_id: z.ZodNumber;
@@ -77,12 +83,14 @@ export declare const RecallUnion: z.ZodDiscriminatedUnion<[z.ZodObject<{
77
83
  action: z.ZodLiteral<"search">;
78
84
  query: z.ZodString;
79
85
  category: z.ZodOptional<z.ZodString>;
86
+ source: z.ZodOptional<z.ZodString>;
80
87
  limit: z.ZodOptional<z.ZodNumber>;
81
88
  max_tokens: z.ZodOptional<z.ZodNumber>;
82
89
  floors: z.ZodOptional<z.ZodEnum<{
83
90
  strict: "strict";
84
91
  loose: "loose";
85
92
  }>>;
93
+ include_scores: z.ZodOptional<z.ZodBoolean>;
86
94
  }, z.core.$strip>, z.ZodObject<{
87
95
  action: z.ZodLiteral<"ask">;
88
96
  subj: z.ZodOptional<z.ZodString>;
@@ -127,6 +135,7 @@ export declare const AdminUnion: z.ZodDiscriminatedUnion<[z.ZodObject<{
127
135
  active: "active";
128
136
  archived: "archived";
129
137
  }>>;
138
+ source: z.ZodOptional<z.ZodString>;
130
139
  limit: z.ZodOptional<z.ZodNumber>;
131
140
  offset: z.ZodOptional<z.ZodNumber>;
132
141
  }, z.core.$strip>, z.ZodObject<{
@@ -152,6 +161,10 @@ export declare const AdminUnion: z.ZodDiscriminatedUnion<[z.ZodObject<{
152
161
  }, z.core.$strip>, z.ZodObject<{
153
162
  action: z.ZodLiteral<"vectors_fix">;
154
163
  dry_run: z.ZodOptional<z.ZodBoolean>;
164
+ store: z.ZodOptional<z.ZodEnum<{
165
+ memory: "memory";
166
+ knowledge: "knowledge";
167
+ }>>;
155
168
  }, z.core.$strip>, z.ZodObject<{
156
169
  action: z.ZodLiteral<"contradict_check">;
157
170
  }, z.core.$strip>, z.ZodObject<{
@@ -220,6 +233,7 @@ export declare const QueryUnion: z.ZodObject<{
220
233
  strict: "strict";
221
234
  loose: "loose";
222
235
  }>>;
236
+ include_scores: z.ZodOptional<z.ZodBoolean>;
223
237
  }, z.core.$strip>;
224
238
  export type QueryRequest = z.infer<typeof QueryUnion>;
225
239
  /** Transport-agnostic tool spec: name / key / description / zod input. */
@@ -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;
@@ -312,6 +369,27 @@ export interface VectorSpaceHealth {
312
369
  /** Active facts whose persisted vector is provably from another model space (same width). */
313
370
  space_stale: number;
314
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
+ }
315
393
  /** `mem_admin stats`: store counts plus the retrieval-health counters. */
316
394
  export interface StatsSummary {
317
395
  active: number;
@@ -322,6 +400,33 @@ export interface StatsSummary {
322
400
  * status surface instead of only from a one-shot startup warning.
323
401
  */
324
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;
325
430
  }
326
431
  /** One page of facts (`mem_admin list`). */
327
432
  export interface FactPage {
@@ -402,22 +507,53 @@ export interface VectorMigrationOutcome extends VectorMigrationProgress {
402
507
  enabled: boolean;
403
508
  }
404
509
  /**
405
- * `mem_admin vectors_fix`: what a repair did, or — with `dry_run` — what it WOULD do.
510
+ * ONE store's part of a vector repair (`mem_admin vectors_fix`).
511
+ *
512
+ * The common subset (`stale` / `space_stale` / `dropped` / `encoded` / `failed` /
513
+ * `semantic_available`) is what both stores report; `missing` and `would_warm` are shared steps of
514
+ * the repair flow, and `unindexed` / `reindexed` are only meaningful to a store that tracks its live
515
+ * index (memory — knowledge rebuilds it at open and keeps it in sync on every write).
406
516
  *
407
517
  * `semantic_available` is "the model is loaded RIGHT NOW" (a dry run must not load it), so the
408
518
  * preview also carries `would_warm`: together they separate "unknown until you run it" from
409
519
  * "the warmup was attempted and failed".
410
520
  */
411
- export interface VectorsFixReport {
412
- missing: number;
521
+ export interface VectorsFixStoreReport {
522
+ /** ACTIVE rows whose persisted vector has the wrong width. */
413
523
  stale: number;
524
+ /** ACTIVE rows whose usable-width vector was recorded in another space. */
414
525
  space_stale: number;
415
- unindexed: number;
416
- reindexed: number;
526
+ /** ACTIVE rows with no persisted vector at all (what the repair has to encode). */
527
+ missing: number;
528
+ /** Rows whose unusable old-space bytes were cleared so a re-encode could replace them. */
417
529
  dropped: number;
418
- fixed: number;
530
+ /** Rows re-encoded into the current space by this repair. */
531
+ encoded: number;
532
+ /** Rows whose encode or write failed; they stay `missing` / `stale` for the next pass. */
533
+ failed: number;
534
+ /** The model is loaded right now. */
419
535
  semantic_available: boolean;
536
+ /** The model was NOT loaded at entry, so a real run would attempt a warmup. */
420
537
  would_warm: boolean;
538
+ /** Usable persisted vectors absent from the live index (memory only). */
539
+ unindexed?: number;
540
+ /** Usable persisted vectors put back into the live index by this repair (memory only). */
541
+ reindexed?: number;
542
+ }
543
+ /**
544
+ * `mem_admin vectors_fix`: what a repair did, or — with `dry_run` — what it WOULD do, per store.
545
+ *
546
+ * The report is per-store because one switch and one call now cover BOTH libraries (a model swap
547
+ * leaves memory and knowledge in the same state, and the repair used to exist for memory only).
548
+ * `store` on the request narrows the call to one library; the top-level `semantic_available` is the
549
+ * AND over the stores actually repaired.
550
+ */
551
+ export interface VectorsFixReport {
552
+ stores: {
553
+ memory?: VectorsFixStoreReport;
554
+ knowledge?: VectorsFixStoreReport;
555
+ };
556
+ semantic_available: boolean;
421
557
  dry_run: boolean;
422
558
  }
423
559
  /** `kb_manage ingest`: what one document produced. */