@avantf/dsh-mem 0.7.0 → 0.8.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 (146) hide show
  1. package/README.md +4 -0
  2. package/lib/client.js +548 -1195
  3. package/lib/dsh-build.json +1 -0
  4. package/lib/engine/graph-recall/in-memory.d.ts +93 -0
  5. package/lib/engine/graph-recall/index.d.ts +22 -0
  6. package/lib/engine/graph-recall/traverse.d.ts +119 -0
  7. package/lib/engine/graph-recall/types.d.ts +188 -0
  8. package/lib/engine/mem/config/config_files.d.ts +1 -1
  9. package/lib/engine/mem/config/loader.d.ts +2 -3
  10. package/lib/engine/mem/config/paths.d.ts +2 -13
  11. package/lib/engine/mem/db/dao/contradictions.d.ts +4 -22
  12. package/lib/engine/mem/db/dao/entities.d.ts +80 -7
  13. package/lib/engine/mem/db/dao/fact_sources.d.ts +3 -13
  14. package/lib/engine/mem/db/dao/facts.d.ts +311 -56
  15. package/lib/engine/mem/db/dao/triples.d.ts +1 -26
  16. package/lib/engine/mem/db/graph.d.ts +249 -0
  17. package/lib/engine/mem/db/journal_notes.d.ts +35 -0
  18. package/lib/engine/mem/db/migrations.d.ts +9 -0
  19. package/lib/engine/mem/db/port.d.ts +22 -3
  20. package/lib/engine/mem/db/schema.d.ts +119 -0
  21. package/lib/engine/mem/db/tokenizer.d.ts +9 -0
  22. package/lib/engine/mem/dbdrivers/fake/driver.d.ts +75 -0
  23. package/lib/engine/mem/dbdrivers/fake/index.d.ts +7 -0
  24. package/lib/engine/mem/dbdrivers/fake/surface.d.ts +34 -0
  25. package/lib/engine/mem/dbdrivers/fake/transport.d.ts +61 -0
  26. package/lib/engine/mem/dbdrivers/sqlite/connection.d.ts +14 -0
  27. package/lib/engine/mem/dbdrivers/sqlite/contradictions.d.ts +35 -0
  28. package/lib/engine/mem/dbdrivers/sqlite/dao/index.d.ts +16 -0
  29. package/lib/engine/mem/dbdrivers/sqlite/driver.d.ts +54 -0
  30. package/lib/engine/mem/dbdrivers/sqlite/facts.d.ts +117 -0
  31. package/lib/engine/mem/dbdrivers/sqlite/index.d.ts +6 -0
  32. package/lib/engine/mem/dbdrivers/sqlite/migrations.d.ts +15 -0
  33. package/lib/engine/mem/dbdrivers/sqlite/relations.d.ts +62 -0
  34. package/lib/engine/mem/dbdrivers/sqlite/search.d.ts +56 -0
  35. package/lib/engine/mem/dbdrivers/sqlite/sources.d.ts +37 -0
  36. package/lib/engine/mem/defaults.d.ts +3 -4
  37. package/lib/engine/mem/dispatch.d.ts +7 -32
  38. package/lib/engine/mem/entities/extract.d.ts +44 -47
  39. package/lib/engine/mem/eval/derived_runner.d.ts +6 -1
  40. package/lib/engine/mem/eval/relation_gold.d.ts +251 -0
  41. package/lib/engine/mem/eval/write_metrics.d.ts +1 -1
  42. package/lib/engine/mem/facts_io.d.ts +50 -10
  43. package/lib/engine/mem/index.d.ts +6 -2
  44. package/lib/engine/mem/interfaces/capabilities.d.ts +30 -0
  45. package/lib/engine/mem/interfaces/common.d.ts +11 -0
  46. package/lib/engine/mem/interfaces/errors.d.ts +65 -0
  47. package/lib/engine/mem/interfaces/index.d.ts +18 -0
  48. package/lib/engine/mem/interfaces/migrations.d.ts +21 -0
  49. package/lib/engine/mem/interfaces/repositories.d.ts +471 -0
  50. package/lib/engine/mem/interfaces/search_index.d.ts +113 -0
  51. package/lib/engine/mem/interfaces/storage_backend.d.ts +84 -0
  52. package/lib/engine/mem/interfaces/storage_driver.d.ts +43 -0
  53. package/lib/engine/mem/interfaces/version.d.ts +34 -0
  54. package/lib/engine/mem/lifecycle/contradiction.d.ts +2 -2
  55. package/lib/engine/mem/modelBootstrap.d.ts +1 -1
  56. package/lib/engine/mem/relations/confidence.d.ts +39 -0
  57. package/lib/engine/mem/relations/extract.d.ts +128 -0
  58. package/lib/engine/mem/relations/index.d.ts +32 -0
  59. package/lib/engine/mem/relations/name-quality.d.ts +78 -0
  60. package/lib/engine/mem/relations/normalize.d.ts +275 -0
  61. package/lib/engine/mem/relations/patterns.d.ts +67 -0
  62. package/lib/engine/mem/relations/statement.d.ts +88 -0
  63. package/lib/engine/mem/relations/types.d.ts +79 -0
  64. package/lib/engine/mem/runtime.d.ts +61 -88
  65. package/lib/engine/mem/storage/create.d.ts +42 -0
  66. package/lib/engine/mem/storage/errors.d.ts +65 -0
  67. package/lib/engine/mem/storage/index.d.ts +15 -0
  68. package/lib/engine/mem/storage/ports.d.ts +472 -0
  69. package/lib/engine/mem/storage/registry.d.ts +37 -0
  70. package/lib/engine/mem/storage/sqlite.d.ts +28 -0
  71. package/lib/engine/mem/store/common.d.ts +1 -1
  72. package/lib/engine/mem/store/entity_leg.d.ts +59 -2
  73. package/lib/engine/mem/store/entity_sweep.d.ts +4 -3
  74. package/lib/engine/mem/store/explicit_relations.d.ts +52 -0
  75. package/lib/engine/mem/store/facts_restore.d.ts +10 -2
  76. package/lib/engine/mem/store/floors.d.ts +4 -1
  77. package/lib/engine/mem/store/graph_reader.d.ts +146 -0
  78. package/lib/engine/mem/store/hybrid.d.ts +50 -13
  79. package/lib/engine/mem/store/kinds.d.ts +47 -0
  80. package/lib/engine/mem/store/legs.d.ts +24 -29
  81. package/lib/engine/mem/store/lexical.d.ts +36 -45
  82. package/lib/engine/mem/store/memory.d.ts +471 -83
  83. package/lib/engine/mem/store/recall_policy.d.ts +49 -0
  84. package/lib/engine/mem/store/relation_backfill.d.ts +210 -0
  85. package/lib/engine/mem/store/relation_planner.d.ts +191 -0
  86. package/lib/engine/mem/store/secret_guard.d.ts +0 -1
  87. package/lib/engine/mem/store/self_query.d.ts +4 -2
  88. package/lib/engine/mem/store/time_filter.d.ts +121 -0
  89. package/lib/engine/mem/store/titles.d.ts +52 -0
  90. package/lib/engine/mem/store/vector_repair.d.ts +44 -54
  91. package/lib/engine/mem-contract/config.d.ts +44 -38
  92. package/lib/engine/mem-contract/text.d.ts +1 -1
  93. package/lib/engine/mem-contract/tools.d.ts +145 -132
  94. package/lib/engine/mem-contract/types.d.ts +364 -288
  95. package/lib/index.js +12048 -11337
  96. package/lib/interface-version.json +1 -1
  97. package/lib/types/client/index.d.ts +2 -2
  98. package/lib/types/client/index.d.ts.map +1 -1
  99. package/lib/types/client/paging.d.ts +3 -12
  100. package/lib/types/client/paging.d.ts.map +1 -1
  101. package/lib/types/client/summarize.d.ts.map +1 -1
  102. package/lib/types/envinit-bootstrap.d.ts +1 -1
  103. package/lib/types/envinit.d.ts +2 -6
  104. package/lib/types/envinit.d.ts.map +1 -1
  105. package/lib/types/hints.d.ts +28 -24
  106. package/lib/types/hints.d.ts.map +1 -1
  107. package/lib/types/index.d.ts +21 -40
  108. package/lib/types/index.d.ts.map +1 -1
  109. package/lib/types/inject.d.ts +3 -2
  110. package/lib/types/inject.d.ts.map +1 -1
  111. package/lib/types/interface_gate.d.ts +1 -1
  112. package/lib/types/prompt.d.ts +17 -41
  113. package/lib/types/prompt.d.ts.map +1 -1
  114. package/lib/types/provision.d.ts +3 -0
  115. package/lib/types/provision.d.ts.map +1 -1
  116. package/lib/types/remote.d.ts +14 -7
  117. package/lib/types/remote.d.ts.map +1 -1
  118. package/lib/types/tool_schema.d.ts +4 -0
  119. package/lib/types/tool_schema.d.ts.map +1 -1
  120. package/lib/types/vectorMigration.d.ts +5 -7
  121. package/lib/types/vectorMigration.d.ts.map +1 -1
  122. package/package.json +15 -14
  123. package/lib/engine/mem/config/domains.d.ts +0 -20
  124. package/lib/engine/mem/db/dao/chunks.d.ts +0 -231
  125. package/lib/engine/mem/db/dao/documents.d.ts +0 -51
  126. package/lib/engine/mem/db/hash.d.ts +0 -2
  127. package/lib/engine/mem/db/knowledge.d.ts +0 -17
  128. package/lib/engine/mem/eval/index.d.ts +0 -4
  129. package/lib/engine/mem/git.d.ts +0 -73
  130. package/lib/engine/mem/router.d.ts +0 -51
  131. package/lib/engine/mem/store/doc_files.d.ts +0 -125
  132. package/lib/engine/mem/store/document_text.d.ts +0 -47
  133. package/lib/engine/mem/store/ingest_guard.d.ts +0 -25
  134. package/lib/engine/mem/store/knowledge.d.ts +0 -598
  135. package/lib/engine/mem/store/source_picker.d.ts +0 -43
  136. package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +0 -52
  137. package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +0 -10
  138. package/lib/engine/mem-retrieval/rerank.d.ts +0 -13
  139. package/lib/types/client/domains.d.ts +0 -53
  140. package/lib/types/client/domains.d.ts.map +0 -1
  141. package/lib/types/client/ingest.d.ts +0 -24
  142. package/lib/types/client/ingest.d.ts.map +0 -1
  143. package/lib/types/open.d.ts +0 -77
  144. package/lib/types/open.d.ts.map +0 -1
  145. package/lib/types/reconcile.d.ts +0 -64
  146. package/lib/types/reconcile.d.ts.map +0 -1
@@ -1,16 +1,18 @@
1
- import { type Config, type ContradictionRecord, type FactDetail, type FactPage, type FloorProfile, type RecallResult, type RememberResult, type SourceCoverage, type TrustDiagnostic, type ValidityCoverage, type VectorMigrationOutcome, type VectorMigrationProgress, type VectorSpaceHealth, type VectorsDiagnostic, type VectorsFixStoreReport } from '../../mem-contract/index.js';
1
+ import { type Config, type ContradictionRecord, type FactDetail, type FactPage, type MemoryKind, type FloorProfile, type RecallResult, type RememberResult, type SourceCoverage, type StatusFilter, type TimeFilter, type MemorySearchResult, type RelationInput, type TitlesResult, type TrustDiagnostic, type ValidityCoverage, type VectorMigrationOutcome, type VectorMigrationProgress, type VectorSpaceHealth, type VectorsDiagnostic, type VectorsFixStoreReport } from '../../mem-contract/index.js';
2
2
  import type { SemanticBackend, VectorStore } from '../../mem-retrieval/index.js';
3
3
  import { ENTITY_SWEEP_BATCH, type EntitySweepReport } from './entity_sweep.js';
4
- import { type RestoreOptions, type RestoreReport } from './facts_restore.js';
4
+ import type { RestoreOptions, RestoreReport } from './facts_restore.js';
5
5
  import type { FactsExportDocument } from '../facts_io.js';
6
6
  import { DEFAULT_VECTOR_MIGRATION_BATCH, type VectorRepairTarget } from './vector_repair.js';
7
- import type { Db } from '../db/conn.js';
8
7
  import { type LexicalProbe } from './lexical.js';
9
8
  import { type FloorLeg } from './floors.js';
10
- import { type TriplePattern } from '../entities/extract.js';
11
- import { type Sig } from '../lifecycle/contradiction.js';
12
- import { type MaintenanceResult } from '../lifecycle/maintenance.js';
13
- import { type TickResult } from '../lifecycle/tick.js';
9
+ import type { GraphReader } from '../../graph-recall/index.js';
10
+ import type { RelationVocabulary, SurfaceMatch } from './relation_planner.js';
11
+ import type { EdgeReextractReport, GraphCoverageReport } from '../interfaces/repositories.js';
12
+ import type { Sig } from '../lifecycle/contradiction.js';
13
+ import type { MaintenanceResult } from '../lifecycle/maintenance.js';
14
+ import type { TickResult } from '../lifecycle/tick.js';
15
+ import type { StorageBackend } from '../interfaces/storage_backend.js';
14
16
  /**
15
17
  * Result shapes live in `@avantf/mem-contract` (single source for tool/UI payloads); these
16
18
  * aliases keep the long-standing `@avantf/mem` export names working for consumers.
@@ -78,7 +80,7 @@ export interface ContradictionResolution {
78
80
  }
79
81
  export interface SearchInput {
80
82
  query: string;
81
- category?: string;
83
+ kind?: MemoryKind;
82
84
  /**
83
85
  * P-08: restrict every leg to facts whose `fact_sources.ref` equals this value. Applied INSIDE
84
86
  * each leg's SQL (an `EXISTS` predicate, before its `LIMIT cap`), never as a post-fusion filter:
@@ -134,6 +136,24 @@ export interface SearchInput {
134
136
  recordStats?: boolean;
135
137
  /** P-01: attach per-leg evidence (`scores` / `final`) to the returned hits. Default false. */
136
138
  includeScores?: boolean;
139
+ /**
140
+ * Override `retriever.min_score` (`0` = off). The unified cross-store entry passes `0` and applies
141
+ * the floor once on the NORMALIZED merged pool (DESIGN §7); a direct store search leaves it absent.
142
+ */
143
+ minScore?: number;
144
+ /** Read-only probe (`AvantfRuntime.wouldFind`): no reinforcement, stats or model warm. */
145
+ probe?: boolean;
146
+ /**
147
+ * The caller's EXPLICIT time filter (D1-D5), normalized by `store/time_filter.ts` and applied
148
+ * INSIDE every leg's SQL. A HARD filter: a fact outside the window is not a candidate at all, so
149
+ * the leg caps and the fusion pool are filled from the window only. No `COALESCE` fallback across
150
+ * axes — an `event` filter never reads `created_at`.
151
+ *
152
+ * When present it REPLACES the implicit `retriever.time_window` leg (the text-parsed one): the
153
+ * caller has said exactly which window it means, and stacking a second, phrase-derived window on
154
+ * top would silently widen an explicitly narrowed question.
155
+ */
156
+ time?: TimeFilter;
137
157
  }
138
158
  /**
139
159
  * P-08 / P-13 write-side extras on `add` / `update`.
@@ -149,25 +169,97 @@ export interface FactWriteOptions {
149
169
  eventDate?: string;
150
170
  /** P-13: known end → `valid_to`, WITHOUT archiving the row. */
151
171
  validUntil?: string;
172
+ /**
173
+ * The journal title (JOURNAL_TITLE_SEARCH §2.2/§11.1). `undefined` = not given, `null` = "no
174
+ * title", a string = the (already normalized) title. On a revision an omitted title INHERITS the
175
+ * replaced row's, and an explicitly DIFFERENT one is refused — except for a row whose title is
176
+ * still NULL, which may be backfilled once, and except under {@link retitle}.
177
+ */
178
+ title?: string | null;
179
+ /**
180
+ * v17 / R3: the CONTROLLED way to correct a title inside a revision chain.
181
+ *
182
+ * With this flag the immutability rule steps aside for ONE write, and the title it replaces is
183
+ * recorded as an alias of the revision (`title_aliases`), so searches for the old handle still find
184
+ * the transaction. It requires an explicit new `title` and an existing fact to rewrite — a first
185
+ * `add` has no chain to correct — and it changes nothing about the rule afterwards: the next update
186
+ * that names a different title WITHOUT this flag is refused exactly as before.
187
+ *
188
+ * Deliberately not a free rename: the caller must say "I am correcting a handle", which is the
189
+ * difference between fixing a typo and silently renaming a transaction the caller merely thinks it
190
+ * is continuing (the merge branch keeps refusing a conflicting title for the same reason).
191
+ */
192
+ retitle?: true;
193
+ /**
194
+ * Enforce the MODEL-facing rule "a journal needs a title" (JOURNAL_TITLE_SEARCH §2.3/§11.2).
195
+ *
196
+ * Set ONLY by the model-facing tool boundary (`@avantf/mem`'s `runToolSpec`). The CLI, an import,
197
+ * a merge and the UI leave it off: legacy journals, old exports and scripts must keep working, and
198
+ * a store-wide rule would break them.
199
+ */
200
+ requireJournalTitle?: boolean;
201
+ /**
202
+ * STRUCTURED_RELATIONS §2: relations the CALLER states, written as edges of this revision.
203
+ *
204
+ * `journal` refuses a non-empty list outright ({@link RELATIONS_NEED_NOTE}). The list is passed to
205
+ * `persistFact`, which writes the explicit edges BEFORE the extracted ones so a relation the caller
206
+ * stated wins the four-tuple dedupe against the extractor's guess at the same relation.
207
+ *
208
+ * OMITTED VERSUS EMPTY. Omitting the field means "this write says nothing about relations" — a
209
+ * write that lands on an existing row (duplicate `add`, revive, unchanged-content `update`) leaves
210
+ * that row's explicit edges untouched. An EMPTY ARRAY means "this write declares none" and clears
211
+ * them, which is the only way to remove the last relation from a fact whose content did not
212
+ * change (no new revision is created, so `supersede` cannot delete the edge).
213
+ */
214
+ relations?: readonly RelationInput[];
215
+ }
216
+ /**
217
+ * The `mem_recall.titles` request (JOURNAL_TITLE_SEARCH §4/§11.4, RECALL_INTERFACE §2.3).
218
+ *
219
+ * Defaults are the action's documented ones — `status: 'active'`, `kind: 'journal'` — applied HERE
220
+ * rather than in the contract, because the contract leaves an omitted field absent and the engine
221
+ * owns what absence means. `offset`/`limit` page the EXACT (title-equal) result only; the fuzzy
222
+ * fallback is the engine's fixed shortlist.
223
+ */
224
+ export interface TitleInput {
225
+ query: string;
226
+ kind?: MemoryKind;
227
+ status?: StatusFilter;
228
+ time?: TimeFilter;
229
+ offset?: number;
230
+ limit?: number;
231
+ includeExcerpt?: boolean;
232
+ }
233
+ /**
234
+ * The `mem_recall.search` request, after the engine's two-stage policy is applied to it
235
+ * (docs/RECALL_INTERFACE.md): the caller's `limit`/`offset` page the EXACT result, while the fuzzy
236
+ * fallback is fixed by the engine (`FUZZY_TOP_K` / `FUZZY_MIN_SCORE`), never by the caller.
237
+ *
238
+ * `query` is OPTIONAL (RECALL_INTERFACE §2.4): a call with only `time` is a PURE TIME window
239
+ * (`mode:"time"`), which never runs the literal/relation/fuzzy stages at all. An omitted `query`
240
+ * without a `time` is the caller's error, refused in {@link MemoryStore.searchMemory}.
241
+ */
242
+ export interface MemorySearchInput extends Omit<SearchInput, 'query'> {
243
+ /** The literal query. Omitted = a pure time window (then `time` is required). */
244
+ query?: string;
245
+ /** The exact page's first row (default 0). Only read when the literal pass matches. */
246
+ offset?: number;
152
247
  }
153
248
  export declare class MemoryStore {
154
- private readonly db;
155
249
  private readonly config;
156
250
  private readonly semantic;
157
251
  private readonly vstore;
158
252
  private readonly contradictions;
159
253
  private readonly facts;
254
+ private readonly relations;
160
255
  private readonly entities;
161
256
  private readonly triples;
162
257
  private readonly sources;
163
- /**
164
- * The tokenizer `facts_fts` was ACTUALLY built with, read from the database at open.
165
- *
166
- * Not `resolveFtsTokenizer()`: `CREATE VIRTUAL TABLE IF NOT EXISTS` never rebuilds an existing
167
- * table, so a database created under another SQLite build keeps the tokenizer it was born with,
168
- * and building MATCH expressions for the wrong one is silent zero recall (see `db/tokenizer.ts`).
169
- */
170
- private readonly ftsTokenizer;
258
+ private readonly searchIndex;
259
+ private readonly keyValue;
260
+ private readonly lifecycle;
261
+ /** The store's database path, used ONLY to name the vector index snapshot beside it. */
262
+ private readonly dbPath;
171
263
  /**
172
264
  * Identity object for the shared entity sweep's in-flight guard (`store/entity_sweep.ts`).
173
265
  *
@@ -181,9 +273,18 @@ export declare class MemoryStore {
181
273
  * derived state: nothing is lost when it resets, and the queue itself is in the database.
182
274
  */
183
275
  private conflictDrainCursor;
184
- constructor(db: Db, config: Config, semantic: SemanticBackend, vstore: VectorStore,
276
+ constructor(backend: StorageBackend, config: Config, semantic: SemanticBackend, vstore: VectorStore,
185
277
  /** The store's database path, used ONLY to name the vector index snapshot beside it. */
186
278
  dbPath?: string);
279
+ /**
280
+ * The open-time pass, now an explicit step (DB-2).
281
+ *
282
+ * It used to run inside the constructor. A constructor cannot `await` a repository call, and
283
+ * silently skipping the pass on a backend that answers asynchronously would leave the clock
284
+ * unrepaired and the semantic index empty — so the composition root wires the store first and
285
+ * starts it here, in the order the comments below explain.
286
+ */
287
+ start(): Promise<void>;
187
288
  /**
188
289
  * One automatic trust/lifecycle pass (spec §5): advance the active-day clock
189
290
  * (when trust is enabled), run the tick, and evict archived/purged vectors from
@@ -191,7 +292,7 @@ export declare class MemoryStore {
191
292
  */
192
293
  trustTick(opts?: {
193
294
  budget?: number | null;
194
- }): TickResult;
295
+ }): Promise<TickResult>;
195
296
  /**
196
297
  * Identity of the vector space this store currently writes into — the REPRESENTATION fingerprint
197
298
  * (`vectorSpaceId` + the backend's declared pooling/normalize/window/revision), not just the
@@ -223,14 +324,14 @@ export declare class MemoryStore {
223
324
  * The counts are the same two `VectorsDiagnostic` already separates (`stale` / `space_stale`); this
224
325
  * is the read model those surfaces use instead of the full diagnose pass.
225
326
  */
226
- vectorSpaceHealth(): VectorSpaceHealth;
327
+ vectorSpaceHealth(): Promise<VectorSpaceHealth>;
227
328
  /**
228
329
  * {@link vectorSpaceHealth} plus the internal `legacy` split (how many `space_stale` rows predate
229
330
  * the representation fingerprint). Kept off the contract type so the public detection surface —
230
331
  * and every existing assertion on it — is unchanged; only the warning's wording uses it.
231
332
  */
232
333
  private vectorSpaceCounts;
233
- add(content: string, category?: string, ttlDays?: number, opts?: FactWriteOptions): Promise<RememberResult>;
334
+ add(content: string, kind?: MemoryKind, ttlDays?: number, opts?: FactWriteOptions): Promise<RememberResult>;
234
335
  /**
235
336
  * Write-path contradiction detection (DESIGN §11): check the fact that was just
236
337
  * written against the corpus, then report its FULL open-conflict set.
@@ -257,18 +358,18 @@ export declare class MemoryStore {
257
358
  private warnIndexFailure;
258
359
  /** Best-effort semantic indexing: encode the fact and add it to the vstore. */
259
360
  private maybeIndexSemantic;
260
- update(fact_id: number, content: string, category?: string, ttlDays?: number, opts?: FactWriteOptions): Promise<RememberResult>;
261
- remove(fact_id: number, reason?: string): boolean;
262
- archive(fact_id: number, reason?: string): boolean;
361
+ update(fact_id: number, content: string, kind?: MemoryKind, ttlDays?: number, opts?: FactWriteOptions): Promise<RememberResult>;
362
+ remove(fact_id: number, reason?: string): Promise<boolean>;
363
+ archive(fact_id: number, reason?: string): Promise<boolean>;
263
364
  /**
264
365
  * Bring an archived fact back (spec §2.6): a `forgot` fact is lifted to
265
366
  * `recall_floor` (otherwise the very next tick would re-archive it, R4), the
266
367
  * idle clock is refreshed (otherwise an `idle`-archived fact dies again, R18),
267
368
  * and `settle_clock` moves to now so the downtime is not charged.
268
369
  */
269
- restore(fact_id: number): boolean;
270
- helpful(fact_id: number): number | null;
271
- unhelpful(fact_id: number): number | null;
370
+ restore(fact_id: number): Promise<boolean>;
371
+ helpful(fact_id: number): Promise<number | null>;
372
+ unhelpful(fact_id: number): Promise<number | null>;
272
373
  /**
273
374
  * Explicit feedback (spec §2.4). Pinned/archived rows only move `helpful_count`
274
375
  * (R7); otherwise the decay is settled first, then `±feedback_delta`, pin at the
@@ -276,40 +377,46 @@ export declare class MemoryStore {
276
377
  */
277
378
  private applyFeedback;
278
379
  /** Permanent-memory administration (spec §6). */
279
- pin(fact_id: number): boolean;
280
- unpin(fact_id: number): boolean;
380
+ pin(fact_id: number): Promise<boolean>;
381
+ unpin(fact_id: number): Promise<boolean>;
281
382
  /**
282
383
  * Trust/forgetting diagnostics (spec §6, R17/R24): the quota is PER FACT, so the
283
384
  * report exposes "facts that consumed quota today" and "total effective gains"
284
385
  * rather than a meaningless global remaining.
285
386
  */
286
- trustDiagnose(): TrustDiagnostic;
287
- get(fact_id: number): FactDetail | null;
387
+ trustDiagnose(): Promise<TrustDiagnostic>;
388
+ get(fact_id: number): Promise<FactDetail | null>;
288
389
  private triplesOf;
289
390
  /**
290
391
  * One page of facts, newest first.
291
- * @param category - optional category filter.
392
+ * @param kind - optional kind filter.
292
393
  * @param status - `active` (default) or `archived`.
293
394
  * @param limit - page size.
294
395
  * @param offset - page offset (0-based).
295
396
  * @returns the page, the page size (`count`), the matching total, and whether
296
397
  * more rows remain after this page.
297
398
  */
298
- list(category?: string, status?: string, limit?: number, offset?: number,
399
+ list(kind?: MemoryKind, status?: string, limit?: number, offset?: number,
299
400
  /** P-08: only facts whose `fact_sources.ref` equals this value (reverse provenance lookup). */
300
- source?: string): FactPage;
401
+ source?: string,
402
+ /**
403
+ * The caller's EXPLICIT time window (the same normalized filter `search` takes). It NARROWS the
404
+ * page: `total`/`truncated` count the window, and the page order stays `created_at DESC` (a list
405
+ * page is a listing, not a chronological walk along whichever axis was filtered).
406
+ */
407
+ time?: TimeFilter): Promise<FactPage>;
301
408
  /**
302
409
  * P-08 coverage over the ACTIVE corpus, for `admin stats`: the share of facts that can answer
303
410
  * "why do I remember this". Reported from day one so the field cannot quietly become a second
304
411
  * `mirror_source` (filled but meaningless).
305
412
  */
306
- sourceCoverage(): SourceCoverage;
413
+ sourceCoverage(): Promise<SourceCoverage>;
307
414
  /** P-13 coverage over the ACTIVE corpus: the share of facts carrying an event time. */
308
- validityCoverage(): ValidityCoverage;
309
- countByStatus(): {
415
+ validityCoverage(): Promise<ValidityCoverage>;
416
+ countByStatus(): Promise<{
310
417
  active: number;
311
418
  archived: number;
312
- };
419
+ }>;
313
420
  /** Run one incremental contradiction-detection pass; returns newly logged pairs. */
314
421
  /**
315
422
  * Drain the DURABLE pending queue, bounded by `budget`.
@@ -333,9 +440,9 @@ export declare class MemoryStore {
333
440
  *
334
441
  * A throw keeps the batch queued too — `checkMany` never returns, so nothing is stamped.
335
442
  */
336
- drainConflicts(budget?: number): ConflictDrainReport;
443
+ drainConflicts(budget?: number): Promise<ConflictDrainReport>;
337
444
  /** {@link drainConflicts}, reduced to the pairs it logged (the `contradict_check` payload). */
338
- checkContradictions(budget?: number): Sig[];
445
+ checkContradictions(budget?: number): Promise<Sig[]>;
339
446
  /**
340
447
  * One bounded conflict pass for {@link maintenance}, reduced to COUNTS.
341
448
  *
@@ -351,10 +458,10 @@ export declare class MemoryStore {
351
458
  /** List contradiction candidates (`resolved` 0 = open, 1 = closed with a recorded verdict). */
352
459
  listContradictions(opts?: {
353
460
  resolved?: number;
354
- category?: string;
461
+ kind?: MemoryKind;
355
462
  threshold?: number;
356
463
  limit?: number;
357
- }): ContradictionRecord[];
464
+ }): Promise<ContradictionRecord[]>;
358
465
  /**
359
466
  * Adjudicate one logged conflict — the outlet the detector never had.
360
467
  *
@@ -371,7 +478,7 @@ export declare class MemoryStore {
371
478
  * The verdict is recorded on this row either way: `archive()` short-circuits for a fact that
372
479
  * is already archived, and the row would otherwise stay open on a technicality.
373
480
  */
374
- resolveContradiction(id: number, resolution: 'true_positive' | 'false_positive', loserFactId?: number): ContradictionResolution;
481
+ resolveContradiction(id: number, resolution: 'true_positive' | 'false_positive', loserFactId?: number): Promise<ContradictionResolution>;
375
482
  /**
376
483
  * Semantic-index health for the ACTIVE corpus (the only thing the index serves):
377
484
  * - `missing` active facts with no persisted vector at all;
@@ -385,7 +492,7 @@ export declare class MemoryStore {
385
492
  * upgraded past the space id) — usable bytes, incomparable scores.
386
493
  * `total`/`with_semantic` still describe the whole table (including archived).
387
494
  */
388
- vectorsDiagnose(): VectorsDiagnostic;
495
+ vectorsDiagnose(): Promise<VectorsDiagnostic>;
389
496
  /**
390
497
  * ONE read+decode pass over the persisted vectors, shared by diagnose and fix
391
498
  * (the two methods used to re-scan and re-decode the same blobs 4-5× per call).
@@ -397,7 +504,7 @@ export declare class MemoryStore {
397
504
  * `semantic.auto_migrate`, the dry-run split and the drop-before-encode order — lives there once,
398
505
  * for both stores.
399
506
  */
400
- vectorRepairTarget(): VectorRepairTarget;
507
+ vectorRepairTarget(): Promise<VectorRepairTarget>;
401
508
  /**
402
509
  * {@link classifyVectors} reduced to what the repair flow steps on: the counts, the ids to drop
403
510
  * (bounded by `limit`) and the decoded vectors that can go back into a lost live index.
@@ -460,6 +567,7 @@ export declare class MemoryStore {
460
567
  maintenance(): Promise<MaintenanceResult & {
461
568
  entities: EntitySweepReport;
462
569
  conflicts: ConflictSweepCounts;
570
+ edges: EdgeReextractReport;
463
571
  }>;
464
572
  /**
465
573
  * Re-extract the entity/triple rows of facts written by OLDER extraction rules (bounded).
@@ -489,6 +597,42 @@ export declare class MemoryStore {
489
597
  * selection (`facts.staleEntityRows`, ACTIVE facts only) and the per-fact write below.
490
598
  */
491
599
  reindexEntities(budget?: number): Promise<EntitySweepReport>;
600
+ /**
601
+ * Phase 2 (RELATIONAL_RECALL §4.3/R2): re-derive `edges` for facts written by an older edge
602
+ * extractor — the graph's counterpart of {@link reindexEntities}.
603
+ *
604
+ * Why an explicit entry point instead of only the write path: every database upgraded to v15 has
605
+ * `edges.extraction_version = 0` rows (the Phase 1 backfill's), and the alias/canonical rules of
606
+ * this phase are functions of the STORED triples — so the corpus can be brought to the current
607
+ * version without re-tagging a single fact. `all` re-derives every active fact with triples, which
608
+ * is what an operator runs after the curated vocabulary or the confidence floor changed (those do
609
+ * not bump the extractor version by themselves).
610
+ *
611
+ * `maintenance()` runs one bounded pass of it, so the graph becomes current the same way the entity
612
+ * stamp and the conflict queue do; the CLI/UI surface is Phase 5 (R7) work.
613
+ *
614
+ * It is ALSO where STRUCTURED_RELATIONS §四/§七's one-time cleanup happens: `db/graph.ts`'s pass
615
+ * deletes the edges of non-note facts before re-deriving, because a database written by a pre-5c
616
+ * build still carries journal edges. The delete is idempotent (0 rows once clean) and touches
617
+ * `edges` only — the facts and their `triples`, which the contradiction detector reads, stay.
618
+ */
619
+ reextractEdges(options?: {
620
+ budget?: number;
621
+ all?: boolean;
622
+ }): Promise<EdgeReextractReport>;
623
+ /**
624
+ * Phase 2 (§R2/§R6): what the graph actually covers, and why the rest is missing.
625
+ *
626
+ * Two ratios rather than one, because they answer different questions: `tripleCoverage` is how
627
+ * much of the active corpus yields any relation at all (the string extractor's reach), while
628
+ * `relationCoverage` is how much of THAT survives normalization and the confidence floor (the
629
+ * graph's precision policy). `samples` shows the refused triples with their reason, which is the
630
+ * failure-sample half of §4.3's "记录抽取覆盖率/失败样本".
631
+ */
632
+ graphCoverage(options?: {
633
+ samples?: number;
634
+ sampleFacts?: number;
635
+ }): Promise<GraphCoverageReport>;
492
636
  /**
493
637
  * Rebuild this library from an export document — the engine half of `avantf-mem restore <file>`.
494
638
  *
@@ -511,20 +655,204 @@ export declare class MemoryStore {
511
655
  */
512
656
  private retireConflicts;
513
657
  /**
514
- * Entities that co-occur with `entity` across ACTIVE facts, ranked by occurrence
515
- * count. Archived (incl. superseded) facts are excluded — their entities are not
516
- * part of the live graph.
517
- */
518
- related(entity: string, limit?: number, category?: string): {
519
- entity: string;
520
- count: number;
521
- }[];
522
- /** Two-hop traversal: subj -pred-> mid, mid -second_pred-> obj. Returns the second-hop facts. */
523
- chain(subj: string, pred?: string, secondPred?: string, limit?: number): Promise<RecallResult>;
524
- /** Facts whose entity set contains ALL given entities (AND-join). */
525
- reason(entities: string[], limit?: number): Promise<RecallResult>;
526
- /** Hybrid search over the memory corpus (semantic + FTS + entity Jaccard [+ HRR probe] via fusion). */
658
+ * Hybrid search over the memory corpus (semantic + FTS + entity Jaccard [+ HRR probe] via fusion).
659
+ *
660
+ * This is the FUZZY half of `mem_recall.search` (see {@link searchMemory}) — the relevance-ranked
661
+ * retrieval with the floors, the fused-score floor and the output budget. It is also the
662
+ * conditional hint's probe (`AvantfRuntime.wouldFind`) and the cross-store path's memory leg, so it
663
+ * stays a public store method even though the public tool no longer exposes its knobs.
664
+ */
527
665
  search(input: SearchInput): Promise<RecallResult>;
666
+ /**
667
+ * `mem_recall.search` — the ONE content retrieval (docs/RECALL_INTERFACE.md §2.2/§4,
668
+ * docs/RELATIONAL_RECALL.md §6, docs/GRAPH_RECALL_AND_SEMANTICS.md §7.9).
669
+ *
670
+ * Four stages, in this order and with this precedence:
671
+ *
672
+ * 1. EXACT: active facts whose content literally CONTAINS the query. A literal match ends the
673
+ * pass — the answer is a page (`total`/`offset`/`limit`/`truncated`) of raw hits whose
674
+ * `score` is the constant 1 (a literal hit, not a relevance number; RECALL_INTERFACE §5 says
675
+ * the model must not read it as one). The pass runs only when the query is long enough to be
676
+ * a literal answer at all AND the literal set is small enough to be one
677
+ * (`store/recall_policy.ts`: `exactEligible` / `EXACT_MAX_TOTAL`).
678
+ * 2. FACTS, only when stage 1 matched nothing AND the query was a PURE multi-entity connection
679
+ * ("张伟和李娜", no other meaningful token — `planFactQuery`): the ACTIVE facts that mention
680
+ * EVERY named entity, paged at the FACT level. This is the fact-level AND over
681
+ * `fact_entities`, so journal entries and notes without any relation edge are covered. An
682
+ * empty page is NOT an answer — the pass returns `null` and the later stages run, which is
683
+ * the same no-empty-answer rule the relation stage follows.
684
+ * 3. RELATION, only when the earlier stages did not answer AND `store/relation_planner.ts` formed
685
+ * a complete plan: the graph walk whose paths answer the question. A plan that finds no path is
686
+ * NOT an answer — the pass returns `null` and fuzzy runs, which is the documented fallback
687
+ * ("无路径时不能返回空的 relation"). The caller's `kind`/`source`/`time` filters are handed
688
+ * to the graph reader, so the path is walked over exactly the corpus the caller asked about.
689
+ * 4. FUZZY, only when no earlier stage answered: the hybrid retrieval, unchanged, with the
690
+ * engine's fixed shortlist (`topK = 5`, `min_score = 0.5`). The caller's `limit`/`offset` page
691
+ * the exact and facts results (and the relation page is capped by the graph's own limit) and
692
+ * are NOT forwarded here: a five-item ranked shortlist has no page 2.
693
+ *
694
+ * A call with NO `query` skips all four stages and answers the pure time window instead
695
+ * ({@link timePage}, `mode:"time"`): the window is the whole question, so there is nothing to
696
+ * match, plan or rank.
697
+ *
698
+ * The exact, facts and relation passes reinforce the facts they return, exactly like the fuzzy
699
+ * pass does for its hits: all are recalls the caller actually reads. A caller that must stay
700
+ * read-only passes `track: false` (the conditional hint's probe does). Exact, facts, relation and
701
+ * time also honour the caller's output budget (their text is the caller's to bound).
702
+ *
703
+ * It does NOT emit a retrieval-health event. Those counters describe the retrieval PIPELINE
704
+ * (legs, fusion, semantic-live rate); none of an exact, facts, relation or time answer ran a leg,
705
+ * so counting them would report the semantic leg as degraded for a query that never asked it
706
+ * anything.
707
+ */
708
+ searchMemory(input: MemorySearchInput): Promise<MemorySearchResult>;
709
+ /**
710
+ * The pure time stage of {@link searchMemory} — `mem_recall.search` with no query (RECALL_INTERFACE
711
+ * §2.4). The window IS the question: no literal pass, no relation plan, no legs, nothing to rank.
712
+ *
713
+ * The order is the window's OWN axis, newest first (`db/dao/facts.ts#timeAxisColumn`), so "what
714
+ * happened that day" reads in the order it happened; `score` is the constant 1 exactly like the
715
+ * exact page's, because there is no relevance number here for the model to compare.
716
+ *
717
+ * It honours `kind`/`source`, pages with the same `offset`/`limit` (default 10, cap 50) and the
718
+ * same `max_tokens` budget as every other recall, and reinforces the rows it returns unless
719
+ * `track: false` — a window the caller reads is a recall like any other.
720
+ */
721
+ private timePage;
722
+ /**
723
+ * The literal stage of {@link searchMemory}, or `null` when it did not apply.
724
+ *
725
+ * `null` has four causes and they are deliberately not distinguished for the caller: the query is
726
+ * too short to be literal (`exactEligible`), it matched no active fact, the literal set is WIDER
727
+ * than a literal answer (`EXACT_MAX_TOTAL` — a common word's dozens of rows are a corpus scan, not
728
+ * a page), or it was blank. All four mean "the rest of the pipeline answers this".
729
+ */
730
+ private exactContentPage;
731
+ /**
732
+ * The FACT-level entity vocabulary for the fact-level AND stage
733
+ * (docs/GRAPH_RECALL_AND_SEMANTICS.md §7.9): entity surfaces linked to at least one ACTIVE fact
734
+ * through `fact_entities`, so journal entries and notes without a relation edge are visible.
735
+ *
736
+ * Public on the store but not on any tool/action (like {@link graphReader}): the gold evaluator
737
+ * plans the same question the stage answers, without going through `search`.
738
+ */
739
+ factEntitiesIn(text: string): Promise<SurfaceMatch[]>;
740
+ /**
741
+ * The FACTS stage of {@link searchMemory}, or `null` when it did not answer.
742
+ *
743
+ * `null` has three causes, deliberately not distinguished for the caller: the query is not a PURE
744
+ * multi-entity connection (`planFactQuery` refused it), the caller's `kind`/`source`/`time` filter
745
+ * leaves no fact mentioning every entity, or a named entity does not exist in the live corpus. All
746
+ * three mean "the next stage answers this": a facts result with zero rows is never returned,
747
+ * because `mode:"facts"` is a promise that a FACT page was found.
748
+ *
749
+ * Order inside the stage, and why:
750
+ * - the plan is checked BEFORE the repository read: no conjunction, fewer than two entities or a
751
+ * leftover meaningful token means no fact query at all;
752
+ * - the explicit `time` filter is normalized here (before the read, after the plan), so a
753
+ * malformed window is the caller's error exactly as it is in the exact page;
754
+ * - the page is FACT-level (`total`/`offset`/`limit`/`truncated`), and the caller's output budget
755
+ * only shortens `content` — dropping a row would make `total` disagree with the page;
756
+ * - the returned facts are reinforced unless `track: false`, like every other recall the caller
757
+ * actually reads.
758
+ */
759
+ private factPage;
760
+ /**
761
+ * The graph view over exactly the facts a search with these filters may read.
762
+ *
763
+ * This is the ONE way the store reaches `@avantf/graph-recall`: the planner's vocabulary
764
+ * (`entitiesIn` / `predicatesIn`), the walks, and — later, for evaluation — a gold-set harness all
765
+ * get the same reader, so none of them can accidentally walk the unfiltered corpus. The filter
766
+ * semantics live in `MemoryGraphReader` (active facts only; kind/source/time in SQL), not here.
767
+ *
768
+ * Public on the store but not on any tool/action: like `graphCoverage()`, it is an internal
769
+ * capability, not a public recall surface.
770
+ */
771
+ graphReader(filter?: {
772
+ kind?: MemoryKind;
773
+ source?: string;
774
+ time?: TimeFilter;
775
+ }): Promise<GraphReader & RelationVocabulary>;
776
+ /**
777
+ * The relation stage of {@link searchMemory}, or `null` when it did not answer.
778
+ *
779
+ * `null` has exactly two causes, and they are deliberately not distinguished for the caller
780
+ * (RELATIONAL_RECALL §6): the query is not a COMPLETE relation question (`planRelationQuery`
781
+ * refused it), or it is one and the graph has no path for it under the caller's filters. Both mean
782
+ * "fuzzy answers this" — a relation result with zero paths is never returned, because `mode:
783
+ * "relation"` is a promise that structure was found and every edge's fact is openable.
784
+ *
785
+ * ORDER INSIDE THIS STAGE, and why:
786
+ * - the reader is built FIRST (through {@link graphReader}) and carries the caller's
787
+ * `kind`/`source`/`time` filter, so both the vocabulary ("is this a known name") and the walks
788
+ * see one consistent view. An invalid `time` therefore throws here exactly as it would in
789
+ * `search` (the caller's error, not an empty answer);
790
+ * - the plan is checked BEFORE any walk: no plan means no graph read beyond the cheap vocabulary
791
+ * lookup;
792
+ * - evidence is loaded from the paths' own `fact_id`s (`rowsByIds`, one query) and the output
793
+ * budget is applied to it, never to the paths: shortening evidence text is allowed, dropping an
794
+ * evidence row would break the invariant that every path's edges point at a returned fact.
795
+ */
796
+ private relationPage;
797
+ /**
798
+ * The facts behind a relation answer, in path order and deduplicated.
799
+ *
800
+ * `RelationEvidence` is NOT a `RecallHit`: a relation answer is not a ranked content match, so no
801
+ * `score` is produced here (there is nothing to rank it against) and the handle is `fact_id`, which
802
+ * every path edge also carries. Only ACTIVE rows can reach this point — the graph reader filters
803
+ * on `facts.status` before any edge is walked.
804
+ */
805
+ private relationEvidence;
806
+ /**
807
+ * Apply the caller's output budget (`max_tokens`) to relation evidence.
808
+ *
809
+ * The shared `applyOutputBudget` cannot be reused directly: its input type carries a `score`, and a
810
+ * relation answer deliberately has none (nothing ranks it). `fitToTokenBudget` itself never reads
811
+ * `score` — it walks the entries in the order it is given and can only SHORTEN text — so the rank
812
+ * below is a placeholder that is dropped before the values leave this function. Order is preserved,
813
+ * `truncated` is set exactly where the text was cut, and no evidence row is ever removed.
814
+ */
815
+ private budgetRelationEvidence;
816
+ /**
817
+ * Apply the caller's output budget (`max_tokens`) to a FACT page.
818
+ *
819
+ * Same reason as {@link budgetRelationEvidence}: `applyOutputBudget` is typed for hits (`text` +
820
+ * `score`) and a `FactEvidence` carries `content` and no score. `fitToTokenBudget` never reads the
821
+ * score, so the entry rides through as `{ …entry, text: content, score: 0 }` and the budgeted text
822
+ * goes back into `content`; a shortened row keeps its `fact_id` and carries `truncated: true`.
823
+ */
824
+ private budgetFactEvidence;
825
+ /**
826
+ * `mem_recall.titles` — the ONE title search (JOURNAL_TITLE_SEARCH §4/§11.4, RECALL_INTERFACE §2.3).
827
+ *
828
+ * It answers a different question from `search`: "which journal entries have this title / titles
829
+ * like these keywords", so the caller can read the candidates and decide whether the thing it is
830
+ * about to write belongs to an existing transaction. Consequences, all deliberate:
831
+ *
832
+ * - EXACT first: `normalizeTitle(query)` compared with `=` (case-SENSITIVE: `DSH` and `dsh` are
833
+ * two titles, §11.1) pages like `search`'s exact stage. Any title-equal row ends the pass;
834
+ * - the FTS index it consults is `facts_title_fts`, never `facts_fts` — a title word that does not
835
+ * occur in any content is invisible to {@link searchMemory} by design, and vice versa;
836
+ * - the fuzzy ordering is computed on `facts.title` (exact > prefix > matched keywords > recency),
837
+ * with bm25 only as a tie-break inside the FTS path; no `score` is returned at all, because a
838
+ * title match is not a content-relevance number and the model must not compare the two;
839
+ * - it is READ-ONLY: no `reinforce`, no `touchUsage`, no `retrieval_count` / `last_retrieved_at` /
840
+ * trust change, in EITHER stage. Reading candidates is the common case (the caller may not write
841
+ * at all), so treating it as a recall would let a few searches rewrite the dormancy clock of a
842
+ * whole transaction;
843
+ * - defaults follow the documented ones: `status='active'` (the current revision), `kind='journal'`.
844
+ */
845
+ titles(input: TitleInput): Promise<TitlesResult>;
846
+ /**
847
+ * Resolve a caller's `title` for a write (JOURNAL_TITLE_SEARCH §2.4/§11.1).
848
+ *
849
+ * Three-way by design: `undefined` stays "not given" so an update can inherit, `null` is the store's
850
+ * "no title" (a blank NOTE title normalizes to it), and a string is normalized. A blank JOURNAL
851
+ * title is refused rather than silently stored as NULL — the caller tried to give one, it just had
852
+ * no content. The title passes the same secret scan as the content because it is rendered into the
853
+ * UI and into the model's context.
854
+ */
855
+ private resolveWriteTitle;
528
856
  /**
529
857
  * This store's half of the shared orchestration (`store/hybrid.ts`): the legs, the text load, the
530
858
  * fact→hit mapping, and what to do with the hits the caller actually receives.
@@ -570,11 +898,9 @@ export declare class MemoryStore {
570
898
  */
571
899
  private timeWindowPath;
572
900
  /**
573
- * The one fact→RecallHit mapper (kind/source_ref/entities shape lives here ONLY;
574
- * chain/reason/ask/search all used to carry byte-identical copies, three of them
575
- * with an N+1 entity load each). Facts have no knowledge-base taxonomy, so
576
- * `domain`/`source` are null — that is what keeps a `domain`-filtered cross query
577
- * from ever matching a memory hit.
901
+ * The one fact→RecallHit mapper (kind/source_ref/entities shape lives here ONLY; the exact page,
902
+ * the fuzzy search and the single-fact loader all go through it, so a hit cannot describe the same
903
+ * row two ways).
578
904
  */
579
905
  private factHits;
580
906
  /**
@@ -582,10 +908,10 @@ export declare class MemoryStore {
582
908
  * the trust bonus is quota-limited (per fact, calendar 24h), zero-gain recalls
583
909
  * cost nothing, and a recall can never push a fact to permanent (D11).
584
910
  *
585
- * Public so `runtime.query` can reinforce ONLY the merged hits it actually
586
- * returns, after the router's kind/domain/source filtering (R5/R21).
911
+ * Public so a caller that post-filters a search result can reinforce ONLY the facts it actually
912
+ * returns (R5/R21).
587
913
  */
588
- reinforce(ids: number[]): void;
914
+ reinforce(ids: number[]): Promise<void>;
589
915
  /**
590
916
  * Load the persisted retrieval-health counters, and write the current ones back.
591
917
  *
@@ -599,7 +925,7 @@ export declare class MemoryStore {
599
925
  * Called by `maintenance()` and by the runtime on shutdown — NOT from a `close()`, because the
600
926
  * runtime owns the database handle.
601
927
  */
602
- flushHealth(): void;
928
+ flushHealth(): Promise<void>;
603
929
  /** Retrieval bookkeeping: `retrieval_count` + the "last used" clock (spec §2.5). */
604
930
  private touchUsage;
605
931
  /**
@@ -608,7 +934,7 @@ export declare class MemoryStore {
608
934
  * `onVector` publishes the vector this leg actually used, so the relaxed retry pass can reuse it
609
935
  * instead of re-encoding (performance review §7.7 / P8). The flow is shared
610
936
  * (`store/legs.ts#semanticLeg`); the only memory-specific piece is the filter — the vstore has no
611
- * notion of status/category/source, so candidates go through the DB and archived/purged facts,
937
+ * notion of status/kind/source, so candidates go through the DB and archived/purged facts,
612
938
  * other categories and other sources never leak into the leg.
613
939
  */
614
940
  private semanticPath;
@@ -618,23 +944,17 @@ export declare class MemoryStore {
618
944
  * the probe even when surface text shares no FTS trigrams.
619
945
  */
620
946
  private hrrPath;
621
- /**
622
- * Direction-aware question answering over SPO triples.
623
- * Parse the query into a TriplePattern, match triples by the filled slots
624
- * (unfilled = wildcard), and rank the owning facts. Direction is preserved by
625
- * the triple's (subj, pred, obj) roles.
626
- */
627
- ask(query: string, limit?: number): Promise<RecallResult>;
628
- /** Direction-aware answer over SPO triples from an explicit pattern. */
629
- askByPattern(pattern: TriplePattern, limit?: number): Promise<RecallResult>;
630
947
  /** One-shot flag for the probe fallback warning (see `hrrPath`). */
631
948
  private hrrFallbackWarned;
632
949
  /**
633
- * Sync relevance probe for the plugin's conditional hint (DESIGN §12): how many of `text`'s
634
- * terms this store already holds. `store/lexical.ts` carries why it is lexical rather than
635
- * semantic, why it must be synchronous, and how the bar itself was measured.
950
+ * Diagnostic term-count probe: how many of `text`'s terms this store already holds.
951
+ *
952
+ * NOT the conditional hint's decision any more — that is `AvantfRuntime.wouldFind`, which runs the
953
+ * retrieval pipeline itself (DESIGN §12). Kept as the lexical oracle `test/retrieve.spec.ts` uses
954
+ * to show what the old short-circuit would have answered (a lexical hit can fail `min_score`).
955
+ * `store/lexical.ts` carries how the two-term bar was measured.
636
956
  */
637
- lexicalProbe(text: string, stopAt?: number): LexicalProbe;
957
+ lexicalProbe(text: string, stopAt?: number): Promise<LexicalProbe>;
638
958
  private ftsPath;
639
959
  /**
640
960
  * Entity leg over the precomputed candidate set (see `searchLegs`): the candidates are the facts
@@ -646,14 +966,18 @@ export declare class MemoryStore {
646
966
  * the generic-word filter keeps the wider denominator from dragging the corpus in. The fact set is
647
967
  * read per candidate and the score is recomputed here even though the DAO ordered by it, so one
648
968
  * formula stays in charge (the DAO's `total` tie-break only orders equal scores).
969
+ *
970
+ * The candidate also has to clear the rarity-weighted evidence bar (`entityEvidenceFloor`), which
971
+ * is per candidate because it depends on which anchors the candidate shares; that is why this
972
+ * method returns the drops instead of letting `runner.scored` count them.
649
973
  */
650
974
  private jaccardPath;
651
975
  /**
652
976
  * Insert (or resolve to) a fact row.
653
977
  *
654
- * `category` / `ttlDays` are `undefined` when the CALLER specified nothing — the
978
+ * `kind` / `ttlDays` are `undefined` when the CALLER specified nothing — the
655
979
  * distinction is load-bearing. A duplicate-content hit must not clobber the existing
656
- * row's values with the insert defaults: re-adding a fact without a category used to
980
+ * row's values with the insert defaults: re-adding a fact without a kind used to
657
981
  * reset it to `general`, and reviving an archived one silently cleared an explicit TTL.
658
982
  * The rule for all three branches is therefore: an explicitly provided value lands, an
659
983
  * omitted one keeps what the row already has, and a brand-new row falls back to the
@@ -689,6 +1013,70 @@ export declare class MemoryStore {
689
1013
  */
690
1014
  private normalizeTriples;
691
1015
  private insertTriples;
1016
+ /**
1017
+ * Phase 2 (RELATIONAL_RECALL §4.3/R2): the SAME extracted triples, resolved onto IDs and written to
1018
+ * `edges` — the write path's half of the graph. Called from every branch that writes triples (fresh
1019
+ * insert, duplicate/merge, revive, and the entity sweep), always inside the surrounding transaction,
1020
+ * so `triples` and `edges` describe one extraction and never two vintages.
1021
+ *
1022
+ * `kind` is the layer the ROW carries, and it is the STRUCTURED_RELATIONS §四 boundary: a `journal`
1023
+ * entry keeps its `triples` (the contradiction detector reads them) but produces NO edges. The
1024
+ * check uses the row's actual kind rather than the request's, because a duplicate-content write
1025
+ * lands on a DIFFERENT row — and the graph's rule is about that row, not about what was asked.
1026
+ *
1027
+ * Best-effort by contract: §4.3's "关系抽取失败不阻塞写入". A throw here is logged and swallowed —
1028
+ * the fact, its triples and its content index are already written, and the graph can be re-derived
1029
+ * from those triples at any time (`reextractEdges`). That is also why this is a `try` around a
1030
+ * single call rather than a per-triple loop: the policy already refuses bad triples individually.
1031
+ */
1032
+ private insertEdges;
1033
+ /**
1034
+ * Re-derive one fact's edges under the current policy — the graph half of "the row's layer/status
1035
+ * changed" (STRUCTURED_RELATIONS §五/§七; `db/graph.ts#rebuildEdgesForFact`).
1036
+ *
1037
+ * Called from the states where the incremental write cannot be right: an ARCHIVED row re-entering
1038
+ * the active set (`restore()` and the revive branch of {@link persistFact}) and a note whose
1039
+ * `keepKind` flips to journal on the duplicate path (a journal keeps no edges). Both call sites
1040
+ * are best-effort by the same contract `insertEdges` has (§4.3: a graph write never fails a fact
1041
+ * write), but the rebuild itself is one savepoint: a throw leaves the row's previous edges in
1042
+ * place instead of half-deleted ones.
1043
+ */
1044
+ private rebuildGraph;
1045
+ /**
1046
+ * STRUCTURED_RELATIONS §2: write the relations the CALLER stated, before the extracted ones.
1047
+ *
1048
+ * Order matters for exactly one reason. Both writers end in `INSERT OR IGNORE` on
1049
+ * `edges(fact_id, subject_id, predicate_id, object_id)`, so when the same relation is both stated
1050
+ * and extracted, whichever runs FIRST owns the row. The caller's statement is the stronger claim
1051
+ * (confidence 1, `source: 'explicit'`), so it goes first and the extractor's guess becomes the
1052
+ * duplicate — the edge keeps the explicit provenance the caller asked for.
1053
+ *
1054
+ * NOT best-effort, unlike {@link insertEdges}: an explicit relation is a request, not a guess. A
1055
+ * throw rolls the whole write back (`db.transaction`), which is also why the caller validated the
1056
+ * list before the transaction — so this path only fails when the DATABASE is broken, and then the
1057
+ * caller must hear about it rather than get a fact whose relations silently do not exist.
1058
+ *
1059
+ * `journal` is refused upstream (`store/explicit_relations.ts`); the guard here is for the merge
1060
+ * branch, where the surviving row's own kind decides and a note request can land on a journal row.
1061
+ *
1062
+ * `explicit === undefined` means "this write said nothing about relations" and leaves the row's
1063
+ * set alone; an EMPTY array means "this write declares none" and drops what is there. That second
1064
+ * case is the only way an unchanged-content update can remove the last relation (the row is not
1065
+ * superseded, so nothing else would delete that edge).
1066
+ */
1067
+ private insertExplicitEdges;
1068
+ /**
1069
+ * Phase 2 (§4.1): record the EXPLICIT alias statements in this write's text.
1070
+ *
1071
+ * Only cue-bearing forms (`A(又名 B)`, `A 简称 B`, …) and only when the left-hand name is one the
1072
+ * extractor recognised as an entity — see `relations/normalize.ts`. Two similar names, or two
1073
+ * frequent ones, never merge; that is the one rule §4.1 makes non-negotiable.
1074
+ *
1075
+ * Best-effort like the edge write: an alias is a refinement of the graph, never a precondition of
1076
+ * the fact. A learned alias is logged at INFO because an automatic merge is exactly the kind of
1077
+ * decision an operator must be able to see and undo (`DELETE FROM entity_aliases WHERE alias = ?`).
1078
+ */
1079
+ private recordAliases;
692
1080
  private getRow;
693
1081
  private entityNames;
694
1082
  private entityBags;