@avantf/dsh-mem 0.3.1 → 0.4.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 (144) hide show
  1. package/README.md +89 -28
  2. package/cordis.patch.yml +17 -0
  3. package/lib/client.js +155 -14
  4. package/lib/engine/mem/config/config_files.d.ts +26 -0
  5. package/lib/engine/mem/config/domains.d.ts +20 -0
  6. package/lib/engine/mem/config/loader.d.ts +35 -0
  7. package/lib/engine/mem/config/paths.d.ts +53 -0
  8. package/lib/engine/mem/db/chunk.d.ts +26 -0
  9. package/lib/engine/mem/db/conn.d.ts +13 -0
  10. package/lib/engine/mem/db/dao/chunks.d.ts +150 -0
  11. package/lib/engine/mem/db/dao/contradictions.d.ts +98 -0
  12. package/lib/engine/mem/db/dao/documents.d.ts +51 -0
  13. package/lib/engine/mem/db/dao/entities.d.ts +67 -0
  14. package/lib/engine/mem/db/dao/facts.d.ts +472 -0
  15. package/lib/engine/mem/db/dao/stats.d.ts +14 -0
  16. package/lib/engine/mem/db/dao/triples.d.ts +80 -0
  17. package/lib/engine/mem/db/hash.d.ts +2 -0
  18. package/lib/engine/mem/db/knowledge.d.ts +17 -0
  19. package/lib/engine/mem/db/migrations.d.ts +71 -0
  20. package/lib/engine/mem/db/port.d.ts +57 -0
  21. package/lib/engine/mem/db/schema.d.ts +26 -0
  22. package/lib/engine/mem/db/sqlite.d.ts +89 -0
  23. package/lib/engine/mem/db/store.d.ts +35 -0
  24. package/lib/engine/mem/db/tokenizer.d.ts +61 -0
  25. package/lib/engine/mem/db/vectors.d.ts +50 -0
  26. package/lib/engine/mem/dispatch.d.ts +51 -0
  27. package/lib/engine/mem/entities/extract.d.ts +84 -0
  28. package/lib/engine/mem/eval/loader.d.ts +19 -0
  29. package/lib/engine/mem/eval/metrics.d.ts +25 -0
  30. package/lib/engine/mem/eval/runner.d.ts +10 -0
  31. package/lib/engine/mem/eval/write_metrics.d.ts +88 -0
  32. package/lib/engine/mem/git.d.ts +73 -0
  33. package/lib/engine/mem/hrr/algebra.d.ts +30 -0
  34. package/lib/engine/mem/hrr/atoms.d.ts +22 -0
  35. package/lib/engine/mem/hrr/encode.d.ts +32 -0
  36. package/lib/engine/mem/hrr/index.d.ts +3 -0
  37. package/lib/engine/mem/index.d.ts +27 -0
  38. package/lib/engine/mem/lifecycle/contradiction.d.ts +156 -0
  39. package/lib/engine/mem/lifecycle/index.d.ts +5 -0
  40. package/lib/engine/mem/lifecycle/maintenance.d.ts +48 -0
  41. package/lib/engine/mem/lifecycle/presence.d.ts +29 -0
  42. package/lib/engine/mem/lifecycle/tick.d.ts +43 -0
  43. package/lib/engine/mem/lifecycle/trust.d.ts +90 -0
  44. package/lib/engine/mem/modelBootstrap.d.ts +207 -0
  45. package/lib/engine/mem/router.d.ts +51 -0
  46. package/lib/engine/mem/runtime.d.ts +232 -0
  47. package/lib/engine/mem/store/common.d.ts +82 -0
  48. package/lib/engine/mem/store/doc_files.d.ts +125 -0
  49. package/lib/engine/mem/store/document_text.d.ts +47 -0
  50. package/lib/engine/mem/store/floors.d.ts +191 -0
  51. package/lib/engine/mem/store/hybrid.d.ts +232 -0
  52. package/lib/engine/mem/store/ingest_guard.d.ts +25 -0
  53. package/lib/engine/mem/store/knowledge.d.ts +483 -0
  54. package/lib/engine/mem/store/lexical.d.ts +67 -0
  55. package/lib/engine/mem/store/memory.d.ts +543 -0
  56. package/lib/engine/mem/store/source_picker.d.ts +43 -0
  57. package/lib/engine/mem-contract/config.d.ts +129 -0
  58. package/lib/engine/mem-contract/env.d.ts +31 -0
  59. package/lib/engine/mem-contract/family.d.ts +25 -0
  60. package/lib/engine/mem-contract/index.d.ts +8 -0
  61. package/lib/engine/mem-contract/log.d.ts +29 -0
  62. package/lib/engine/mem-contract/remote.d.ts +80 -0
  63. package/lib/engine/mem-contract/text.d.ts +92 -0
  64. package/lib/engine/mem-contract/tools.d.ts +284 -0
  65. package/lib/engine/mem-contract/types.d.ts +615 -0
  66. package/lib/engine/mem-retrieval/adapters/auto_vstore.d.ts +47 -0
  67. package/lib/engine/mem-retrieval/adapters/hnswlib.d.ts +174 -0
  68. package/lib/engine/mem-retrieval/adapters/local_bge.d.ts +121 -0
  69. package/lib/engine/mem-retrieval/adapters/local_numpy.d.ts +28 -0
  70. package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +52 -0
  71. package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +10 -0
  72. package/lib/engine/mem-retrieval/adapters/warm_gate.d.ts +46 -0
  73. package/lib/engine/mem-retrieval/budget.d.ts +30 -0
  74. package/lib/engine/mem-retrieval/fusion.d.ts +53 -0
  75. package/lib/engine/mem-retrieval/index.d.ts +14 -0
  76. package/lib/engine/mem-retrieval/interfaces.d.ts +82 -0
  77. package/lib/engine/mem-retrieval/log.d.ts +11 -0
  78. package/lib/engine/mem-retrieval/registry.d.ts +23 -0
  79. package/lib/engine/mem-retrieval/rerank.d.ts +13 -0
  80. package/lib/engine/mem-retrieval/stats.d.ts +64 -0
  81. package/lib/engine/mem-retrieval/text_budget.d.ts +52 -0
  82. package/lib/index.js +1773 -473
  83. package/lib/interface-version.json +2 -2
  84. package/lib/types/client/domains.d.ts +5 -4
  85. package/lib/types/client/domains.d.ts.map +1 -1
  86. package/lib/types/client/index.d.ts.map +1 -1
  87. package/lib/types/client/ingest.d.ts +1 -1
  88. package/lib/types/client/wire.d.ts +36 -0
  89. package/lib/types/client/wire.d.ts.map +1 -0
  90. package/lib/types/envinit-bootstrap.d.ts +3 -2
  91. package/lib/types/envinit.d.ts +2 -19
  92. package/lib/types/envinit.d.ts.map +1 -1
  93. package/lib/types/hints.d.ts +1 -1
  94. package/lib/types/index.d.ts +8 -3
  95. package/lib/types/index.d.ts.map +1 -1
  96. package/lib/types/open.d.ts +61 -11
  97. package/lib/types/open.d.ts.map +1 -1
  98. package/lib/types/prompt.d.ts +19 -1
  99. package/lib/types/prompt.d.ts.map +1 -1
  100. package/lib/types/provision.d.ts +1 -1
  101. package/lib/types/provision.d.ts.map +1 -1
  102. package/lib/types/reconcile.d.ts +64 -0
  103. package/lib/types/reconcile.d.ts.map +1 -0
  104. package/lib/types/remote.d.ts +53 -0
  105. package/lib/types/remote.d.ts.map +1 -1
  106. package/lib/types/render.d.ts +35 -0
  107. package/lib/types/render.d.ts.map +1 -0
  108. package/lib/types/tool_schema.d.ts +3 -13
  109. package/lib/types/tool_schema.d.ts.map +1 -1
  110. package/package.json +24 -15
  111. package/lib/client.js.map +0 -1
  112. package/lib/types/client/domains.js +0 -25
  113. package/lib/types/client/domains.js.map +0 -1
  114. package/lib/types/client/index.js +0 -1371
  115. package/lib/types/client/index.js.map +0 -1
  116. package/lib/types/client/ingest.js +0 -30
  117. package/lib/types/client/ingest.js.map +0 -1
  118. package/lib/types/client/paging.js +0 -36
  119. package/lib/types/client/paging.js.map +0 -1
  120. package/lib/types/client/summarize.js +0 -79
  121. package/lib/types/client/summarize.js.map +0 -1
  122. package/lib/types/data_home.js +0 -26
  123. package/lib/types/data_home.js.map +0 -1
  124. package/lib/types/envinit-bootstrap.js +0 -333
  125. package/lib/types/envinit.js +0 -402
  126. package/lib/types/envinit.js.map +0 -1
  127. package/lib/types/hints.js +0 -53
  128. package/lib/types/hints.js.map +0 -1
  129. package/lib/types/index.js +0 -755
  130. package/lib/types/index.js.map +0 -1
  131. package/lib/types/inject.js +0 -14
  132. package/lib/types/inject.js.map +0 -1
  133. package/lib/types/interface_gate.js +0 -84
  134. package/lib/types/interface_gate.js.map +0 -1
  135. package/lib/types/open.js +0 -133
  136. package/lib/types/open.js.map +0 -1
  137. package/lib/types/prompt.js +0 -148
  138. package/lib/types/prompt.js.map +0 -1
  139. package/lib/types/provision.js +0 -177
  140. package/lib/types/provision.js.map +0 -1
  141. package/lib/types/remote.js +0 -247
  142. package/lib/types/remote.js.map +0 -1
  143. package/lib/types/tool_schema.js +0 -149
  144. package/lib/types/tool_schema.js.map +0 -1
@@ -0,0 +1,543 @@
1
+ import { type Config, type ContradictionRecord, type FactDetail, type FactPage, type FloorProfile, type RecallResult, type RememberResult, type TrustDiagnostic, type VectorsDiagnostic, type VectorsFixReport } from '../../mem-contract/index.js';
2
+ import type { Reranker, SemanticBackend, VectorStore } from '../../mem-retrieval/index.js';
3
+ import type { Db } from '../db/conn.js';
4
+ import { type LexicalProbe } from './lexical.js';
5
+ import { type FloorLeg } from './floors.js';
6
+ import { type TriplePattern } from '../entities/extract.js';
7
+ import { type Sig } from '../lifecycle/contradiction.js';
8
+ import { type MaintenanceResult } from '../lifecycle/maintenance.js';
9
+ import { type TickResult } from '../lifecycle/tick.js';
10
+ /**
11
+ * Result shapes live in `@avantf/mem-contract` (single source for tool/UI payloads); these
12
+ * aliases keep the long-standing `@avantf/mem` export names working for consumers.
13
+ */
14
+ export type { DetectedContradiction, RememberResult as AddResult } from '../../mem-contract/index.js';
15
+ /**
16
+ * Facts one derived-state sweep pass may rebuild (`reindexEntities`'s default budget).
17
+ *
18
+ * Bounds MEMORY, not only mission: the pass selects `content` for every row it takes, so an
19
+ * "unbounded" pass (the CLI used to ask for `Number.MAX_SAFE_INTEGER`) builds an array of every
20
+ * stale fact's text before rebuilding the first one. Callers that want the corpus drained loop
21
+ * until `deferred === 0` instead.
22
+ */
23
+ export declare const ENTITY_SWEEP_BATCH = 2000;
24
+ /** What one derived-state sweep pass did (see `MemoryStore.reindexEntities`). */
25
+ export interface EntitySweepReport {
26
+ /**
27
+ * Facts this pass RE-EXTRACTED, i.e. its batch size when it filled it. Counts rows VISITED and
28
+ * re-stamped, not rows whose extraction output differs — the stamp is what makes the corpus
29
+ * current, so a row re-extracted to the same names still counts (and is not rebuilt again).
30
+ */
31
+ rebuilt: number;
32
+ /** Facts still written by older rules after this pass. */
33
+ deferred: number;
34
+ /** Another pass was already running, so this one did nothing (`deferred` is still current). */
35
+ skipped: boolean;
36
+ }
37
+ /** What one bounded conflict-drain pass did (see `MemoryStore.drainConflicts`). */
38
+ export interface ConflictDrainReport {
39
+ /** Pairs newly logged by this pass. */
40
+ logged: Sig[];
41
+ /**
42
+ * Rows the pass COMPLETED — the embedding leg held their vector in the live index, so the stamp
43
+ * moved. Deliberately not the batch size: a row whose vector the index cannot serve stays queued
44
+ * (see `CheckResult`), which is why this can be 0 while `pending` is not.
45
+ */
46
+ checked: number;
47
+ /** Rows still queued after this pass (including any it could not complete). */
48
+ pending: number;
49
+ }
50
+ /**
51
+ * Counts-only view of one conflict pass, for the `maintenance` payload: the pairs themselves are
52
+ * the `contradict_check` payload, and a settings-page click has no use for a list of them.
53
+ */
54
+ export interface ConflictSweepCounts {
55
+ /** Rows the pass completed (the embedding leg held their vector). */
56
+ checked: number;
57
+ /** Pairs it logged. */
58
+ logged: number;
59
+ /** Rows still queued afterwards. */
60
+ pending: number;
61
+ }
62
+ /**
63
+ * Outcome of adjudicating a conflict (see `MemoryStore.resolveContradiction`). `reason` is
64
+ * present only on a refusal, so a caller can tell "the pair is closed" from "your request
65
+ * named a fact that is not in the pair".
66
+ */
67
+ export interface ContradictionResolution {
68
+ resolved: boolean;
69
+ contradiction_id: number;
70
+ /** The fact that was archived as the loser, when the verdict named one. */
71
+ archived_loser: number | null;
72
+ /**
73
+ * Why the verdict was refused. `already_resolved` means the pair carries a verdict already — see
74
+ * {@link MemoryStore.resolveContradiction} for why a second one is refused rather than applied.
75
+ */
76
+ reason?: 'not_found' | 'already_resolved' | 'loser_not_in_pair' | 'loser_requires_true_positive';
77
+ }
78
+ export interface SearchInput {
79
+ query: string;
80
+ category?: string;
81
+ limit?: number;
82
+ /** Also fuse the HRR entity-probe path (used by `recall.probe`). */
83
+ includeHrr?: boolean;
84
+ /**
85
+ * Record retrieval stats for the returned facts (default true). Cross-store
86
+ * `kb_query` sets this false and marks only the hits it actually returns, so
87
+ * router-side filtering cannot refresh the dormancy clock of unseen facts.
88
+ */
89
+ track?: boolean;
90
+ /**
91
+ * Pre-encoded query vector, when the caller already has one. `runtime.query` encodes a
92
+ * cross-store query ONCE and hands the same vector to both stores (they share the same
93
+ * backend and model), instead of each leg paying for its own encode. The store does not
94
+ * verify it, so it must come from the same backend.
95
+ */
96
+ queryVector?: Float32Array;
97
+ /** Per-call output token budget; `0` = unlimited, omitted = `retriever.max_output_tokens`. */
98
+ maxTokens?: number;
99
+ /**
100
+ * Relevance-floor profile for this query. Omitted = the default policy: the configured (strict)
101
+ * floors, and if they empty the result while having dropped candidates, ONE relaxed pass
102
+ * (`store/floors.ts`'s `LOOSE_FLOORS`) before answering. `'strict'` asserts the configured floors
103
+ * with no fallback — what the UI's 严格 mode asks for, so the panel can show the honest strict
104
+ * outcome and the drop count. `'loose'` applies the relaxed floors outright.
105
+ */
106
+ floors?: FloorProfile;
107
+ /**
108
+ * Which legs a `'loose'` pass may lower (see `store/floors.ts`). Internal: an omitted value lets
109
+ * the default policy derive it from the strict pass's own drop report, while the cross-store
110
+ * router pins the MERGED strict pass's dropping legs on its second call — only the merged result
111
+ * knows which legs were the problem for the user's one question.
112
+ */
113
+ relaxLegs?: readonly FloorLeg[];
114
+ /**
115
+ * Emit a `kind: 'memory'` health event for this search (default true). The cross-store router
116
+ * sets it false: it fuses this leg with the knowledge leg and records ONE `kind: 'cross'`
117
+ * event, so `queries` counts user questions instead of legs (DESIGN §20.5). Distinct from
118
+ * `track`, which is about the dormancy clock — a caller may want one without the other.
119
+ */
120
+ recordStats?: boolean;
121
+ }
122
+ export declare class MemoryStore {
123
+ private readonly db;
124
+ private readonly config;
125
+ private readonly semantic;
126
+ private readonly vstore;
127
+ private readonly reranker;
128
+ private readonly contradictions;
129
+ private readonly facts;
130
+ private readonly entities;
131
+ private readonly triples;
132
+ /**
133
+ * The tokenizer `facts_fts` was ACTUALLY built with, read from the database at open.
134
+ *
135
+ * Not `resolveFtsTokenizer()`: `CREATE VIRTUAL TABLE IF NOT EXISTS` never rebuilds an existing
136
+ * table, so a database created under another SQLite build keeps the tokenizer it was born with,
137
+ * and building MATCH expressions for the wrong one is silent zero recall (see `db/tokenizer.ts`).
138
+ */
139
+ private readonly ftsTokenizer;
140
+ /** In-flight guard for `reindexEntities` (see there: the periodic callers can overlap). */
141
+ private entitySweepInFlight;
142
+ /**
143
+ * Where the next conflict drain resumes (see `checkContradictions`). A scheduling cursor, not
144
+ * derived state: nothing is lost when it resets, and the queue itself is in the database.
145
+ */
146
+ private conflictDrainCursor;
147
+ constructor(db: Db, config: Config, semantic: SemanticBackend, vstore: VectorStore, reranker: Reranker,
148
+ /** The store's database path, used ONLY to name the vector index snapshot beside it. */
149
+ dbPath?: string);
150
+ /**
151
+ * One automatic trust/lifecycle pass (spec §5): advance the active-day clock
152
+ * (when trust is enabled), run the tick, and evict archived/purged vectors from
153
+ * the live index. Called at startup, from the heartbeat, and by `maintenance()`.
154
+ */
155
+ trustTick(opts?: {
156
+ budget?: number | null;
157
+ }): TickResult;
158
+ /** Identity of the vector space this store currently writes into (`vectorSpaceId`). */
159
+ private vectorSpace;
160
+ /**
161
+ * Rebuild the in-memory vstore from persisted `facts.semantic_vector` BLOBs.
162
+ * Without this the semantic path is silently empty after every process restart
163
+ * (the DB is the authoritative vector store; the vstore is a derived read model).
164
+ *
165
+ * Only ACTIVE facts are indexed: archived facts are filtered out of every
166
+ * retrieval path anyway, so loading them would only grow the index forever.
167
+ * `archive()` evicts and `restore()` re-adds, keeping the invariant.
168
+ */
169
+ private reloadIndex;
170
+ add(content: string, category?: string, ttlDays?: number): Promise<RememberResult>;
171
+ /**
172
+ * Write-path contradiction detection (DESIGN §11): check the fact that was just
173
+ * written against the corpus, then report its FULL open-conflict set.
174
+ *
175
+ * The check's own return value is not enough: `maybeLog` skips pairs that are already
176
+ * open, so what it returns is "what this pass inserted" — while the caller's question is
177
+ * "what does this write conflict with?". Reporting only the new rows made an update that
178
+ * merged into an already-conflicting fact look clean.
179
+ *
180
+ * Best-effort — a detection failure must never fail the write, and the fact stays PENDING for
181
+ * the next `contradict_check`. `complete` is what enforces that second half: it is true only
182
+ * when nothing went wrong AND the pass held this fact's vector, so a caught failure can never
183
+ * be mistaken for a finished check (an earlier version returned `[]` for both and the caller
184
+ * stamped the marker anyway — a permanent, silent exemption).
185
+ */
186
+ private detectContradictions;
187
+ /** Best-effort semantic indexing: encode the fact and add it to the vstore. */
188
+ private maybeIndexSemantic;
189
+ update(fact_id: number, content: string, category?: string, ttlDays?: number): Promise<RememberResult>;
190
+ remove(fact_id: number, reason?: string): boolean;
191
+ archive(fact_id: number, reason?: string): boolean;
192
+ /**
193
+ * Bring an archived fact back (spec §2.6): a `forgot` fact is lifted to
194
+ * `recall_floor` (otherwise the very next tick would re-archive it, R4), the
195
+ * idle clock is refreshed (otherwise an `idle`-archived fact dies again, R18),
196
+ * and `settle_clock` moves to now so the downtime is not charged.
197
+ */
198
+ restore(fact_id: number): boolean;
199
+ helpful(fact_id: number): number | null;
200
+ unhelpful(fact_id: number): number | null;
201
+ /**
202
+ * Explicit feedback (spec §2.4). Pinned/archived rows only move `helpful_count`
203
+ * (R7); otherwise the decay is settled first, then `±feedback_delta`, pin at the
204
+ * permanent threshold (snap to 1.0) or archive immediately at the forget line.
205
+ */
206
+ private applyFeedback;
207
+ /** Permanent-memory administration (spec §6). */
208
+ pin(fact_id: number): boolean;
209
+ unpin(fact_id: number): boolean;
210
+ /**
211
+ * Trust/forgetting diagnostics (spec §6, R17/R24): the quota is PER FACT, so the
212
+ * report exposes "facts that consumed quota today" and "total effective gains"
213
+ * rather than a meaningless global remaining.
214
+ */
215
+ trustDiagnose(): TrustDiagnostic;
216
+ get(fact_id: number): FactDetail | null;
217
+ private triplesOf;
218
+ /**
219
+ * One page of facts, newest first.
220
+ * @param category - optional category filter.
221
+ * @param status - `active` (default) or `archived`.
222
+ * @param limit - page size.
223
+ * @param offset - page offset (0-based).
224
+ * @returns the page, the page size (`count`), the matching total, and whether
225
+ * more rows remain after this page.
226
+ */
227
+ list(category?: string, status?: string, limit?: number, offset?: number): FactPage;
228
+ countByStatus(): {
229
+ active: number;
230
+ archived: number;
231
+ };
232
+ /** Run one incremental contradiction-detection pass; returns newly logged pairs. */
233
+ /**
234
+ * Drain the DURABLE pending queue, bounded by `budget`.
235
+ *
236
+ * These are facts whose embedding-leg check could not run when they were written (no vector —
237
+ * the embedder was unavailable). The queue is `facts.conflict_checked`, not an in-process Set, so
238
+ * it survives a restart and cannot grow without bound as a memory structure.
239
+ *
240
+ * The stamp follows the CHECK, not the selection: a row is marked checked only when the pass
241
+ * actually held its vector (see `CheckResult`). The queue predicate asks the DATABASE for
242
+ * `semantic_vector IS NOT NULL`, which is a different question from "the live index can serve
243
+ * it" — a vector written by another process, or dropped while this one rebuilt its index, is in
244
+ * the column and not in the index. Stamping those was a permanent exemption dressed up as a
245
+ * completed check; leaving them queued keeps `conflict_pending` honest, and `vectors_fix` (which
246
+ * resets the marker when it re-encodes) is what clears them.
247
+ *
248
+ * What the drain guarantees instead of "every selected row is stamped" is that it always MOVES:
249
+ * rows are taken above `conflictDrainCursor`, so an un-completable row cannot hold up the ones
250
+ * behind it. A restart resets the cursor, which costs one extra pass over the head and is why
251
+ * this is a scheduling cursor rather than derived state.
252
+ *
253
+ * A throw keeps the batch queued too — `checkMany` never returns, so nothing is stamped.
254
+ */
255
+ drainConflicts(budget?: number): ConflictDrainReport;
256
+ /** {@link drainConflicts}, reduced to the pairs it logged (the `contradict_check` payload). */
257
+ checkContradictions(budget?: number): Sig[];
258
+ /**
259
+ * One bounded conflict pass for {@link maintenance}, reduced to COUNTS.
260
+ *
261
+ * `maintenance` is the surfaces' "clean up now" entry (the settings page and the MCP tool call
262
+ * it), and the conflict queue is the other half of the derived state the entity sweep handles: a
263
+ * database upgraded from v5 has every vector-bearing row queued at once, and without this the
264
+ * only way to drain it was the explicit `contradict_check` action. Bounded like the entity sweep
265
+ * (one batch), so a settings-page click stays a click; the CLI is the surface that loops.
266
+ */
267
+ private sweepConflicts;
268
+ /** Async bootstrap the semantic backend (download/config model) without blocking. */
269
+ warmupSemantic(): Promise<boolean>;
270
+ /** List contradiction candidates (`resolved` 0 = open, 1 = closed with a recorded verdict). */
271
+ listContradictions(opts?: {
272
+ resolved?: number;
273
+ category?: string;
274
+ threshold?: number;
275
+ limit?: number;
276
+ }): ContradictionRecord[];
277
+ /**
278
+ * Adjudicate one logged conflict — the outlet the detector never had.
279
+ *
280
+ * Detection was complete but one-way: a pair was recorded and `resolveForFact` retired it when
281
+ * a fact left the ACTIVE corpus, yet nothing could say "this pair is decided" while both
282
+ * revisions stayed put. The DAO even had `resolve()` with no caller, so the open list could
283
+ * only grow. Two verdicts are meaningful:
284
+ *
285
+ * - `false_positive`: both statements are true, the pair is closed and both facts stay;
286
+ * - `true_positive` + `loser_fact_id`: the loser was wrong, so it is ARCHIVED — which is also
287
+ * what retires every other open conflict naming it, and why the loser must be one of the
288
+ * two facts in this pair.
289
+ *
290
+ * The verdict is recorded on this row either way: `archive()` short-circuits for a fact that
291
+ * is already archived, and the row would otherwise stay open on a technicality.
292
+ */
293
+ resolveContradiction(id: number, resolution: 'true_positive' | 'false_positive', loserFactId?: number): ContradictionResolution;
294
+ /**
295
+ * Semantic-index health for the ACTIVE corpus (the only thing the index serves):
296
+ * - `missing` active facts with no persisted vector at all;
297
+ * - `stale` active facts whose persisted vector has the wrong dim
298
+ * (semantic.dim changed) — needs a re-encode;
299
+ * - `indexed` vectors currently in the live index;
300
+ * - `unindexed` active facts with a usable persisted vector that the live
301
+ * index does not hold (restart/index loss);
302
+ * - `store` the backend actually serving reads (`auto` migrates at a threshold).
303
+ * - `space_stale` vectors written in a DIFFERENT vector space (another model, or a store
304
+ * upgraded past the space id) — usable bytes, incomparable scores.
305
+ * `total`/`with_semantic` still describe the whole table (including archived).
306
+ */
307
+ vectorsDiagnose(): VectorsDiagnostic;
308
+ /**
309
+ * ONE read+decode pass over the persisted vectors, shared by diagnose and fix
310
+ * (the two methods used to re-scan and re-decode the same blobs 4-5× per call).
311
+ */
312
+ private classifyVectors;
313
+ /**
314
+ * Repair the semantic read model:
315
+ * (a) reload persisted, dim-valid vectors missing from the live index
316
+ * (restart / index loss — needs no model),
317
+ * (b) drop persisted vectors whose dim no longer matches `semantic.dim` so a
318
+ * re-encode can replace them (never `add()` a stale vector — the store
319
+ * throws on dim mismatch),
320
+ * (c) encode active facts that have no vector yet (needs the semantic backend),
321
+ * (d) re-encode vectors written in ANOTHER vector space (a model swap, or the one-time
322
+ * adoption of a store predating the space id). Deliberately here and not at open: this is
323
+ * the only path that re-encodes the whole table, so it is an explicit, counted action.
324
+ * `dry_run` previews every count without writing — and without loading the model, which is why
325
+ * the preview also reports `would_warm` (see the contract's `VectorsFixReport`).
326
+ */
327
+ vectorsFix(dryRun?: boolean): Promise<VectorsFixReport>;
328
+ /**
329
+ * One lifecycle pass, owned by the store so the DB writes and the live-index
330
+ * invariant (ACTIVE vectors only) are maintained in ONE place — the runtime no
331
+ * longer has to patch the index behind the store's back.
332
+ *
333
+ * It also runs ONE bounded derived-state sweep pass, which is why it is async: `entities` is
334
+ * what the sweep rebuilt and what it still owes (`deferred`). One pass, not a loop — this is
335
+ * reachable from a settings-page button and an MCP call, so it must not block on a whole stale
336
+ * corpus; operators who want it drained loop on `reindexEntities` until `deferred === 0` (the
337
+ * CLI does). It deliberately does not sit behind `heartbeat_minutes`, which can be 0.
338
+ */
339
+ maintenance(): Promise<MaintenanceResult & {
340
+ entities: EntitySweepReport;
341
+ conflicts: ConflictSweepCounts;
342
+ }>;
343
+ /**
344
+ * Re-extract the entity/triple rows of facts written by OLDER extraction rules (bounded).
345
+ *
346
+ * The memory-side counterpart of `KnowledgeStore.reindex`'s entity leg, and the reason
347
+ * `facts.entities_version` exists: without it a change to the extraction rules could only reach
348
+ * facts written afterwards, so the corpus would carry two vintages indefinitely and retrieval
349
+ * quality would depend on when a fact happened to be written. The version covers the RULES, not
350
+ * which tagger happened to produce the tokens — the tagger is an optional dependency loaded
351
+ * asynchronously, and a process that fails to load it falls back to regex; folding that into the
352
+ * version would make a capability failure schedule a corpus-wide rebuild with the WORSE extractor.
353
+ *
354
+ * One transaction per fact: the entity rows, the triple rows, the HRR bundle (which is derived
355
+ * FROM the entity names) and the version stamp all move together or not at all. The semantic
356
+ * vector is untouched — it encodes the CONTENT, which is unchanged — but `conflict_checked` is
357
+ * RESET, because both legs of the conflict check read exactly what this rebuilt (see
358
+ * `requeueConflictCheck`).
359
+ *
360
+ * Awaited by `maintenance` (one batch), so the caller knows the batch is done when it returns;
361
+ * `deferred` is what the next pass still has to do. Callers that want the whole corpus drained
362
+ * loop until `deferred === 0` — `budget` bounds MEMORY as well as mission, since the pass reads
363
+ * every selected row's `content` before rebuilding the first one.
364
+ *
365
+ * The in-flight guard is here rather than at the callers because the callers CAN overlap: the
366
+ * plugin's heartbeat and `maintenance` are both periodic, and `staleEntityRows` is ORDERED, so
367
+ * two concurrent passes would select the same rows and re-tag them twice. Overlap is not
368
+ * corruption (each fact is stamped in its own transaction) — it is duplicated mission in a
369
+ * single-threaded process. A pass that finds one running reports `skipped` and does nothing.
370
+ *
371
+ * `budget` is clamped at 0: SQLite reads a negative LIMIT as "no limit" (`LIMIT -1`), so a
372
+ * caller asking for "-1 rows" would silently get the whole corpus — the opposite of bounded.
373
+ */
374
+ reindexEntities(budget?: number): Promise<EntitySweepReport>;
375
+ /**
376
+ * A fact left the ACTIVE corpus: retire the open conflicts that name it, so
377
+ * `contradict` never reports a pair whose revision is archived and repeated
378
+ * archive/restore cycles cannot stack duplicates (see
379
+ * `ContradictionsDao.resolveForFact`).
380
+ */
381
+ private retireConflicts;
382
+ /** Drop vectors of archived/purged facts from the live index (batched). */
383
+ private evictVectors;
384
+ /**
385
+ * Entities that co-occur with `entity` across ACTIVE facts, ranked by occurrence
386
+ * count. Archived (incl. superseded) facts are excluded — their entities are not
387
+ * part of the live graph.
388
+ */
389
+ related(entity: string, limit?: number, category?: string): {
390
+ entity: string;
391
+ count: number;
392
+ }[];
393
+ /** Two-hop traversal: subj -pred-> mid, mid -second_pred-> obj. Returns the second-hop facts. */
394
+ chain(subj: string, pred?: string, secondPred?: string, limit?: number): Promise<RecallResult>;
395
+ /** Facts whose entity set contains ALL given entities (AND-join). */
396
+ reason(entities: string[], limit?: number): Promise<RecallResult>;
397
+ /** Hybrid search over the memory corpus (semantic + FTS + entity Jaccard [+ HRR probe] via fusion). */
398
+ search(input: SearchInput): Promise<RecallResult>;
399
+ /**
400
+ * This store's half of the shared orchestration (`store/hybrid.ts`): the legs, the text load, the
401
+ * fact→hit mapping, and what to do with the hits the caller actually receives.
402
+ */
403
+ private hybridDeps;
404
+ /**
405
+ * The memory legs: semantic, FTS, entity Jaccard, and — for `recall.probe` — the HRR probe.
406
+ *
407
+ * Every non-semantic leg is CAPPED. Fusion only ever keeps `overFetch` entries, so a leg returning
408
+ * the whole matching corpus (measured: the FTS leg returned all 33000 facts for a common phrase,
409
+ * the entity leg 6600) made `fuse` normalize and sort the corpus per query. 4× the pool is
410
+ * deliberate headroom: each leg must be able to fill the pool on its own.
411
+ *
412
+ * The cap is score-transparent for a leg that hands its entries over in ITS OWN SCORE ORDER —
413
+ * `fuse` scales by each leg's maximum, an entry no cap can remove, so a trimmed tail cannot
414
+ * rescale the survivors. That covers the Jaccard and FTS legs. It does NOT cover the HRR leg, whose
415
+ * candidates arrive in the JACCARD leg's order (or by recency in its fallback), so its cap can
416
+ * remove its own highest scorer and `v / max` really does move. Accepted — the cap bounds per-query
417
+ * mission and the pool is reranked afterwards — but do not read these scores as comparable across
418
+ * different cap/over-fetch settings; see `docs/PROVENANCE_REVIEW.md` N5 for the missing differential.
419
+ */
420
+ private searchLegs;
421
+ /**
422
+ * Whether the configured reranker is actually in use — the two flags a retrieval event
423
+ * carries. Exposed so the cross-store router can report ONE event for a merged query with the
424
+ * same honesty as a single-store search (both stores share this adapter, and the derivation lives
425
+ * in `store/hybrid.ts` rather than being re-spelled per store).
426
+ */
427
+ rerankState(): {
428
+ used: boolean;
429
+ fallback: boolean;
430
+ };
431
+ /**
432
+ * The one fact→RecallHit mapper (kind/source_ref/entities shape lives here ONLY;
433
+ * chain/reason/ask/search all used to carry byte-identical copies, three of them
434
+ * with an N+1 entity load each). Facts have no knowledge-base taxonomy, so
435
+ * `domain`/`source` are null — that is what keeps a `domain`-filtered cross query
436
+ * from ever matching a memory hit.
437
+ */
438
+ private factHits;
439
+ /**
440
+ * Record a RECALL and reinforce the facts (spec §2.3): usage stats always move,
441
+ * the trust bonus is quota-limited (per fact, calendar 24h), zero-gain recalls
442
+ * cost nothing, and a recall can never push a fact to permanent (D11).
443
+ *
444
+ * Public so `runtime.query` can reinforce ONLY the merged hits it actually
445
+ * returns, after the router's kind/domain/source filtering (R5/R21).
446
+ */
447
+ reinforce(ids: number[]): void;
448
+ /**
449
+ * Load the persisted retrieval-health counters, and write the current ones back.
450
+ *
451
+ * A snapshot written by an older build (or a corrupt row) must not break startup, so a parse
452
+ * failure leaves the live counters alone — `restoreRetrievalHealth` validates the shape.
453
+ */
454
+ private restoreHealth;
455
+ /**
456
+ * Flush the counters so a restart does not reset the picture (see `RETRIEVAL_HEALTH_KEY`).
457
+ *
458
+ * Called by `maintenance()` and by the runtime on shutdown — NOT from a `close()`, because the
459
+ * runtime owns the database handle.
460
+ */
461
+ flushHealth(): void;
462
+ /** Retrieval bookkeeping: `retrieval_count` + the "last used" clock (spec §2.5). */
463
+ private touchUsage;
464
+ /**
465
+ * Semantic leg. `queryVector` short-circuits the encode when the caller already
466
+ * encoded this exact query (the cross-store router does, for both stores at once);
467
+ * `onVector` publishes the vector this leg actually used, so the relaxed retry pass can reuse it
468
+ * instead of re-encoding (performance review §7.7 / P8).
469
+ */
470
+ private semanticPath;
471
+ /**
472
+ * HRR entity-probe path: phase similarity between the query's entity bundle and
473
+ * each active fact's persisted HRR vector. Recalls facts whose entities relate to
474
+ * the probe even when surface text shares no FTS trigrams.
475
+ */
476
+ private hrrPath;
477
+ /**
478
+ * Direction-aware question answering over SPO triples.
479
+ * Parse the query into a TriplePattern, match triples by the filled slots
480
+ * (unfilled = wildcard), and rank the owning facts. Direction is preserved by
481
+ * the triple's (subj, pred, obj) roles.
482
+ */
483
+ ask(query: string, limit?: number): Promise<RecallResult>;
484
+ /** Direction-aware answer over SPO triples from an explicit pattern. */
485
+ askByPattern(pattern: TriplePattern, limit?: number): Promise<RecallResult>;
486
+ /** One-shot flag for the probe fallback warning (see `hrrPath`). */
487
+ private hrrFallbackWarned;
488
+ /**
489
+ * Sync relevance probe for the plugin's conditional hint (DESIGN §12): how many of `text`'s
490
+ * terms this store already holds. `store/lexical.ts` carries why it is lexical rather than
491
+ * semantic, why it must be synchronous, and how the bar itself was measured.
492
+ */
493
+ lexicalProbe(text: string, stopAt?: number): LexicalProbe;
494
+ private ftsPath;
495
+ /**
496
+ * Jaccard leg over the precomputed candidate set (see `search`): the candidates are the facts
497
+ * sharing at least one query entity, pre-ranked by how many they share and already capped.
498
+ */
499
+ private jaccardPath;
500
+ /**
501
+ * Insert (or resolve to) a fact row.
502
+ *
503
+ * `category` / `ttlDays` are `undefined` when the CALLER specified nothing — the
504
+ * distinction is load-bearing. A duplicate-content hit must not clobber the existing
505
+ * row's values with the insert defaults: re-adding a fact without a category used to
506
+ * reset it to `general`, and reviving an archived one silently cleared an explicit TTL.
507
+ * The rule for all three branches is therefore: an explicitly provided value lands, an
508
+ * omitted one keeps what the row already has, and a brand-new row falls back to the
509
+ * defaults (`general` / `0`).
510
+ */
511
+ private persistFact;
512
+ /**
513
+ * Link the revision chain and archive the superseded fact — on both the fresh-insert
514
+ * and duplicate-content branches.
515
+ *
516
+ * @returns the superseded fact id whose vector must leave the live index, or null.
517
+ * The caller evicts it AFTER the surrounding transaction commits: evicting inside
518
+ * would desync the index from the DB on a rollback.
519
+ */
520
+ private applySupersede;
521
+ private linkEntities;
522
+ /**
523
+ * Normalize the SPO slots of extracted triples through the same write entry as fact content.
524
+ *
525
+ * The triples are DERIVED (from an already-normalized content string, in `add`/`update`), but a
526
+ * tokenizer is free to hand back a half code unit, and a future truncation rule could split a
527
+ * surrogate pair — so the derived rows cross the same entry instead of trusting the producer.
528
+ */
529
+ private normalizeTriples;
530
+ private insertTriples;
531
+ private getRow;
532
+ private entityNames;
533
+ private entityBags;
534
+ private loadTexts;
535
+ /**
536
+ * Summary projection. `trust_score` follows the R11 display rule: the EFFECTIVE
537
+ * (decayed) value for active facts and the STORED one for non-active rows;
538
+ * `remaining_days` is null for pinned / non-active rows (spec §6) so it can never
539
+ * go negative.
540
+ */
541
+ private toSummary;
542
+ private toDetail;
543
+ }
@@ -0,0 +1,43 @@
1
+ import type { BrowseEntry, BrowseListing, SourceClassification, SourceKind } from '../../mem-contract/index.js';
2
+ import { type IngestLimits } from './ingest_guard.js';
3
+ /**
4
+ * The shapes come from the CONTRACT, and core re-exports them for its own callers.
5
+ *
6
+ * The engine owns the behaviour, but the browser half needs the types WITHOUT the engine, so the
7
+ * contract owns them (see `SourceClassification` / `BrowseListing` there). No hand-mirror left to
8
+ * drift, which is why the re-export below exists: `export type { X } from '…'` alone does NOT put `X`
9
+ * in this module's scope, so the separate `import type` above is what the rest of this file uses.
10
+ */
11
+ export type { BrowseEntry, BrowseListing, SourceClassification, SourceKind };
12
+ /** Split one source string the way `kb import` does: whitespace or comma separated. */
13
+ export declare function splitSourceInput(raw: string): string[];
14
+ /**
15
+ * Walk one directory for ingestable files: text formats plus PDF.
16
+ *
17
+ * Everything else is reported as SKIPPED rather than dropped silently — a directory of 200 files
18
+ * where 3 were ingested and 197 ignored used to look exactly like a directory of 3 files.
19
+ */
20
+ export declare function listTextFiles(dir: string): {
21
+ files: string[];
22
+ skipped: string[];
23
+ };
24
+ /** Would ingestion take this file name? */
25
+ export declare function isIngestableFile(name: string): boolean;
26
+ /**
27
+ * Classify one source input without ingesting anything.
28
+ *
29
+ * The order matters: a URL wins outright, then anything that resolves on disk (several paths, a
30
+ * directory, a file), then a path-SHAPED input that does not resolve is `missing` (a typo, or a
31
+ * path outside the boundary — the reason says which), and only then is it pasted text. Long or
32
+ * many-token inputs skip the filesystem probes entirely: prose is not a path list, and a paragraph
33
+ * should not cost thousands of `stat` calls.
34
+ */
35
+ export declare function classifySource(raw: string, limits: IngestLimits): SourceClassification;
36
+ /**
37
+ * List one directory for the picker.
38
+ *
39
+ * `requested` empty ⇒ the first allowed root. Every requested path goes through
40
+ * {@link resolveLocalSource}, so the picker can never show (or hand back) a directory that
41
+ * ingestion would refuse; `parent` stops at a root for the same reason.
42
+ */
43
+ export declare function browseDirectory(requested: string | undefined, limits: IngestLimits): BrowseListing;