@avantf/dsh-mem 0.6.4 → 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 (150) hide show
  1. package/README.md +19 -10
  2. package/lib/client.js +1665 -3466
  3. package/lib/dsh-build.json +6 -5
  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 +328 -0
  43. package/lib/engine/mem/index.d.ts +10 -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/maintenance_paths.d.ts +41 -0
  56. package/lib/engine/mem/modelBootstrap.d.ts +1 -1
  57. package/lib/engine/mem/relations/confidence.d.ts +39 -0
  58. package/lib/engine/mem/relations/extract.d.ts +128 -0
  59. package/lib/engine/mem/relations/index.d.ts +32 -0
  60. package/lib/engine/mem/relations/name-quality.d.ts +78 -0
  61. package/lib/engine/mem/relations/normalize.d.ts +275 -0
  62. package/lib/engine/mem/relations/patterns.d.ts +67 -0
  63. package/lib/engine/mem/relations/statement.d.ts +88 -0
  64. package/lib/engine/mem/relations/types.d.ts +79 -0
  65. package/lib/engine/mem/runtime.d.ts +117 -90
  66. package/lib/engine/mem/storage/create.d.ts +42 -0
  67. package/lib/engine/mem/storage/errors.d.ts +65 -0
  68. package/lib/engine/mem/storage/index.d.ts +15 -0
  69. package/lib/engine/mem/storage/ports.d.ts +472 -0
  70. package/lib/engine/mem/storage/registry.d.ts +37 -0
  71. package/lib/engine/mem/storage/sqlite.d.ts +28 -0
  72. package/lib/engine/mem/store/common.d.ts +1 -1
  73. package/lib/engine/mem/store/entity_leg.d.ts +59 -2
  74. package/lib/engine/mem/store/entity_sweep.d.ts +4 -3
  75. package/lib/engine/mem/store/explicit_relations.d.ts +52 -0
  76. package/lib/engine/mem/store/facts_restore.d.ts +124 -0
  77. package/lib/engine/mem/store/floors.d.ts +4 -1
  78. package/lib/engine/mem/store/graph_reader.d.ts +146 -0
  79. package/lib/engine/mem/store/hybrid.d.ts +50 -13
  80. package/lib/engine/mem/store/kinds.d.ts +47 -0
  81. package/lib/engine/mem/store/legs.d.ts +24 -29
  82. package/lib/engine/mem/store/lexical.d.ts +36 -45
  83. package/lib/engine/mem/store/memory.d.ts +486 -82
  84. package/lib/engine/mem/store/recall_policy.d.ts +49 -0
  85. package/lib/engine/mem/store/relation_backfill.d.ts +210 -0
  86. package/lib/engine/mem/store/relation_planner.d.ts +191 -0
  87. package/lib/engine/mem/store/secret_guard.d.ts +0 -1
  88. package/lib/engine/mem/store/self_query.d.ts +4 -2
  89. package/lib/engine/mem/store/time_filter.d.ts +121 -0
  90. package/lib/engine/mem/store/titles.d.ts +52 -0
  91. package/lib/engine/mem/store/vector_repair.d.ts +44 -54
  92. package/lib/engine/mem-contract/config.d.ts +44 -38
  93. package/lib/engine/mem-contract/text.d.ts +1 -1
  94. package/lib/engine/mem-contract/tools.d.ts +184 -117
  95. package/lib/engine/mem-contract/types.d.ts +364 -288
  96. package/lib/index.js +13898 -11500
  97. package/lib/interface-version.json +1 -1
  98. package/lib/types/client/index.d.ts +2 -2
  99. package/lib/types/client/index.d.ts.map +1 -1
  100. package/lib/types/client/paging.d.ts +3 -12
  101. package/lib/types/client/paging.d.ts.map +1 -1
  102. package/lib/types/client/summarize.d.ts.map +1 -1
  103. package/lib/types/command.d.ts +61 -0
  104. package/lib/types/command.d.ts.map +1 -0
  105. package/lib/types/envinit-bootstrap.d.ts +1 -1
  106. package/lib/types/envinit.d.ts +2 -6
  107. package/lib/types/envinit.d.ts.map +1 -1
  108. package/lib/types/hints.d.ts +28 -24
  109. package/lib/types/hints.d.ts.map +1 -1
  110. package/lib/types/index.d.ts +21 -40
  111. package/lib/types/index.d.ts.map +1 -1
  112. package/lib/types/inject.d.ts +3 -2
  113. package/lib/types/inject.d.ts.map +1 -1
  114. package/lib/types/interface_gate.d.ts +9 -9
  115. package/lib/types/prompt.d.ts +17 -41
  116. package/lib/types/prompt.d.ts.map +1 -1
  117. package/lib/types/provision.d.ts +3 -0
  118. package/lib/types/provision.d.ts.map +1 -1
  119. package/lib/types/remote.d.ts +23 -9
  120. package/lib/types/remote.d.ts.map +1 -1
  121. package/lib/types/render.d.ts +3 -3
  122. package/lib/types/tool_schema.d.ts +4 -0
  123. package/lib/types/tool_schema.d.ts.map +1 -1
  124. package/lib/types/vectorMigration.d.ts +5 -7
  125. package/lib/types/vectorMigration.d.ts.map +1 -1
  126. package/package.json +16 -15
  127. package/lib/engine/mem/config/domains.d.ts +0 -20
  128. package/lib/engine/mem/db/dao/chunks.d.ts +0 -231
  129. package/lib/engine/mem/db/dao/documents.d.ts +0 -51
  130. package/lib/engine/mem/db/hash.d.ts +0 -2
  131. package/lib/engine/mem/db/knowledge.d.ts +0 -17
  132. package/lib/engine/mem/eval/index.d.ts +0 -4
  133. package/lib/engine/mem/git.d.ts +0 -73
  134. package/lib/engine/mem/router.d.ts +0 -51
  135. package/lib/engine/mem/store/doc_files.d.ts +0 -125
  136. package/lib/engine/mem/store/document_text.d.ts +0 -47
  137. package/lib/engine/mem/store/ingest_guard.d.ts +0 -25
  138. package/lib/engine/mem/store/knowledge.d.ts +0 -598
  139. package/lib/engine/mem/store/source_picker.d.ts +0 -43
  140. package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +0 -52
  141. package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +0 -10
  142. package/lib/engine/mem-retrieval/rerank.d.ts +0 -13
  143. package/lib/types/client/domains.d.ts +0 -53
  144. package/lib/types/client/domains.d.ts.map +0 -1
  145. package/lib/types/client/ingest.d.ts +0 -24
  146. package/lib/types/client/ingest.d.ts.map +0 -1
  147. package/lib/types/open.d.ts +0 -77
  148. package/lib/types/open.d.ts.map +0 -1
  149. package/lib/types/reconcile.d.ts +0 -64
  150. package/lib/types/reconcile.d.ts.map +0 -1
@@ -0,0 +1,472 @@
1
+ /**
2
+ * The storage boundary (DB-1, docs/DATABASE_PORTABILITY.md §四/§十二; async from DB-2).
3
+ *
4
+ * `MemoryStore` and the lifecycle code above it talk to these interfaces, never to a DAO, a
5
+ * `Db`, SQL, FTS5 or a driver. The only implementation is the SQLite adapter
6
+ * (`storage/sqlite.ts`), which wraps the existing DAOs; selecting it is the composition root's job
7
+ * (`storage/index.ts#createStorageBackend`).
8
+ *
9
+ * ASYNC BOUNDARY (DB-2). Every method that touches stored state returns {@link Awaitable}: the
10
+ * in-process SQLite adapter fulfils the contract by returning a plain value (it has no I/O to wait
11
+ * for), while a remote backend returns a promise. Callers ALWAYS `await`, so the store never has to
12
+ * know which kind of backend it holds, and no call site has to be rewritten again when a remote
13
+ * backend arrives. The lifetime of the boundary is the point: `Awaitable` is not a convenience for
14
+ * the local adapter, it is what makes the local adapter a legal implementation of a remote contract.
15
+ *
16
+ * WHAT DOES NOT CROSS THIS BOUNDARY: SQL text, `Db`, `DbStatement`, `TimeSql`, driver row classes,
17
+ * FTS5 query syntax, `bm25`'s sign convention, SQLite rowids or `AUTOINCREMENT` assumptions. Rows and
18
+ * result objects are the domain shapes the store already consumes; ranks are normalized to "higher is
19
+ * better" by the adapter that knows its own index; fact ids are handed back as ids, not rowids.
20
+ *
21
+ * @module storage/ports
22
+ */
23
+ import type { ContradictionRecord, FactSourceView, MemoryKind, RelationInput } from '../../mem-contract/index.js';
24
+ import type { GraphReader } from '../../graph-recall/index.js';
25
+ import type { RelationVocabulary } from '../store/relation_planner.js';
26
+ import type { ExtractedEntity } from '../entities/extract.js';
27
+ import type { TimeRange } from '../store/time_filter.js';
28
+ import type { EdgeReextractOptions, EdgeReextractReport, EdgeRebuildReport, EdgeSourceTriple, EdgeWriteReport, GraphCoverageReport } from '../db/graph.js';
29
+ import type { CheckResult } from '../lifecycle/contradiction.js';
30
+ import type { ConflictPair, StoredConflict } from '../db/dao/contradictions.js';
31
+ import type { MaintenanceResult } from '../lifecycle/maintenance.js';
32
+ import type { TickResult } from '../lifecycle/tick.js';
33
+ import type { FactsExportDocument } from '../facts_io.js';
34
+ import type { RestoreOptions, RestoreReport } from '../store/facts_restore.js';
35
+ import type { RelationCandidate } from '../relations/index.js';
36
+ import type { AliasRegistration } from '../relations/normalize.js';
37
+ /**
38
+ * A result the in-process adapter can produce synchronously and a remote adapter returns as a
39
+ * promise. Callers always `await`; see the module comment for why this is not a local shortcut.
40
+ */
41
+ export type Awaitable<T> = T | Promise<T>;
42
+ /** A facts row as the domain reads it. The SQL projection lives in the adapter. */
43
+ export type FactRow = Record<string, unknown>;
44
+ /** One fact's source provenance (P-08). */
45
+ export type FactSourceKind = FactSourceView['kind'];
46
+ export interface FactSource {
47
+ kind: FactSourceKind;
48
+ ref: string;
49
+ }
50
+ /** The scalar display columns of a hit (`factHits` maps these onto the contract envelope). */
51
+ export interface FactHitMeta {
52
+ created_at: string;
53
+ updated_at: string | null;
54
+ valid_from: string | null;
55
+ valid_to: string | null;
56
+ title: string | null;
57
+ }
58
+ /** One persisted semantic vector, as the reload/repair flows read it. */
59
+ export interface FactVectorRow {
60
+ fact_id: number;
61
+ semantic_vector: Uint8Array;
62
+ embedding_model: string | null;
63
+ }
64
+ /** The space-qualified vector inventory (`facts_wide` diagnostic). */
65
+ export interface FactVectorSpaceRow {
66
+ fact_id: number;
67
+ bytes: number;
68
+ embedding_model: string | null;
69
+ }
70
+ /** One persisted HRR bundle. */
71
+ export interface HrrVectorRow {
72
+ fact_id: number;
73
+ hrr_vector: Uint8Array;
74
+ }
75
+ /** One title-search row (`mem_recall.titles`). */
76
+ export interface TitleMatch {
77
+ fact_id: number;
78
+ title: string;
79
+ kind: string;
80
+ status: string;
81
+ created_at: string | null;
82
+ updated_at: string | null;
83
+ valid_from: string | null;
84
+ excerpt: string | null;
85
+ }
86
+ /** The filters every title statement applies. */
87
+ export interface TitleFilters {
88
+ kind?: MemoryKind;
89
+ status: 'active' | 'archived' | 'all';
90
+ time?: TimeRange;
91
+ }
92
+ /**
93
+ * One title search. The QUERY is the domain input: whether it is answered by a full-text index or by
94
+ * a substring scan is the adapter's decision (DB-3), exactly like the content search below.
95
+ */
96
+ export interface TitleQuery extends TitleFilters {
97
+ query: string;
98
+ limit: number;
99
+ }
100
+ export interface ExactTitleQuery extends TitleFilters {
101
+ query: string;
102
+ }
103
+ /** The exact-content filter (docs/RECALL_INTERFACE.md §2.2). */
104
+ export interface ExactContentQuery {
105
+ query: string;
106
+ kind?: MemoryKind;
107
+ source?: string;
108
+ time?: TimeRange;
109
+ }
110
+ /**
111
+ * A full-text hit: the fact id and the backend's own relevance rank.
112
+ *
113
+ * HIGHER IS ALWAYS BETTER. The adapter normalizes its index's convention (SQLite's `bm25` is
114
+ * negative; the substring predicate's match count is positive) so the store's floors and the fusion
115
+ * see one scale no matter which index answered (DB-3).
116
+ */
117
+ export interface SearchHit {
118
+ id: number;
119
+ rank: number;
120
+ }
121
+ /** The filters shared by the lexical legs. */
122
+ export interface SearchScope {
123
+ kind?: MemoryKind;
124
+ source?: string;
125
+ time?: TimeRange;
126
+ }
127
+ /**
128
+ * What one insert-or-ignore did.
129
+ *
130
+ * `inserted: false` means the content already exists and the backend resolved the write onto the
131
+ * EXISTING row; `factId` names that row. Ids are the backend's to assign (a distributed backend does
132
+ * not have SQLite's rowids), which is why the store never reads a driver's `lastInsertRowid`.
133
+ */
134
+ export interface InsertRevisionOutcome {
135
+ inserted: boolean;
136
+ factId: number;
137
+ }
138
+ /**
139
+ * The facts aggregate: reads, lifecycle writes and the derived-state stamps.
140
+ *
141
+ * Method names mirror the existing DAO because DB-1 was a boundary extraction, not a redesign of
142
+ * what the store asks for. The arguments/results are domain shapes — `TimeRange` instead of a SQL
143
+ * fragment, `FactRow` instead of a driver statement.
144
+ */
145
+ export interface MemoryRepository {
146
+ activeIds(ids: readonly number[]): Awaitable<number[]>;
147
+ getById(factId: number): Awaitable<FactRow | undefined>;
148
+ rowsByIds(ids: readonly number[]): Awaitable<FactRow[]>;
149
+ textsByIds(ids: readonly number[]): Awaitable<Map<number, string>>;
150
+ hitMetaByIds(ids: readonly number[]): Awaitable<Map<number, FactHitMeta>>;
151
+ minSettleClock(): Awaitable<number | null>;
152
+ maxSettleClock(): Awaitable<number | null>;
153
+ countByStatus(): Awaitable<{
154
+ active: number;
155
+ archived: number;
156
+ }>;
157
+ countActive(): Awaitable<number>;
158
+ countPinnedActive(): Awaitable<number>;
159
+ countInStatus(status: string, kind?: MemoryKind, source?: string): Awaitable<number>;
160
+ countActiveWithValidFrom(): Awaitable<number>;
161
+ countForgettingWithin(p: {
162
+ step: number;
163
+ clock: number;
164
+ withinDays: number;
165
+ }): Awaitable<number>;
166
+ countIdleCandidates(days: number): Awaitable<number>;
167
+ countArchiveBacklog(p: {
168
+ forgetThreshold: number;
169
+ idleCalendarDays: number;
170
+ }): Awaitable<number>;
171
+ countPendingConflicts(): Awaitable<number>;
172
+ countStaleEntities(version: number): Awaitable<number>;
173
+ vectorCounts(): Awaitable<{
174
+ total: number;
175
+ withSemantic: number;
176
+ missing: number;
177
+ }>;
178
+ activeVectorRows(): Awaitable<FactVectorRow[]>;
179
+ activeVectorSpaces(): Awaitable<FactVectorSpaceRow[]>;
180
+ archivedByReason(): Awaitable<{
181
+ reason: string;
182
+ n: number;
183
+ }[]>;
184
+ embeddingModels(): Awaitable<{
185
+ model: string | null;
186
+ n: number;
187
+ }[]>;
188
+ assertCount(factId: number): Awaitable<number>;
189
+ supersededBy(factId: number): Awaitable<number | null>;
190
+ statusOf(factId: number): Awaitable<string | undefined>;
191
+ bonusStatedToday(): Awaitable<{
192
+ count: number;
193
+ granted: number;
194
+ }>;
195
+ page(status: string, kind: MemoryKind | undefined, limit: number, offset: number, source?: string): Awaitable<FactRow[]>;
196
+ findByContent(content: string): Awaitable<FactRow | undefined>;
197
+ windowRows(from: string, to: string, kind: MemoryKind | undefined, cap: number, source?: string): Awaitable<number[]>;
198
+ staleEntityRows(version: number, limit: number): Awaitable<{
199
+ fact_id: number;
200
+ content: string;
201
+ kind: string;
202
+ }[]>;
203
+ pendingConflictRows(limit: number, after?: number): Awaitable<{
204
+ fact_id: number;
205
+ content: string;
206
+ }[]>;
207
+ missingVectorRows(): Awaitable<{
208
+ fact_id: number;
209
+ content: string;
210
+ }[]>;
211
+ activeHrrRows(kind?: MemoryKind, limit?: number, source?: string, time?: TimeRange): Awaitable<HrrVectorRow[]>;
212
+ hrrRowsForFacts(ids: readonly number[], kind?: MemoryKind, source?: string): Awaitable<HrrVectorRow[]>;
213
+ activeIdsIn(ids: readonly number[], kind?: MemoryKind, source?: string, time?: TimeRange): Awaitable<number[]>;
214
+ insertRevision(v: {
215
+ content: string;
216
+ kind: MemoryKind;
217
+ ttlDays: number;
218
+ supersedesId: number | null;
219
+ hrr: Uint8Array;
220
+ trust: number;
221
+ clock: number;
222
+ pinned: 0 | 1;
223
+ pinnedAt: string | null;
224
+ windowAt: string;
225
+ createdAt: string;
226
+ title?: string | null;
227
+ validFrom?: string | null;
228
+ validTo?: string | null;
229
+ validFromMs?: number | null;
230
+ validToMs?: number | null;
231
+ }): Awaitable<InsertRevisionOutcome>;
232
+ applyValidity(factId: number, validFrom: {
233
+ text: string;
234
+ ms: number;
235
+ } | undefined, validTo: {
236
+ text: string;
237
+ ms: number;
238
+ } | undefined): Awaitable<void>;
239
+ applyValues(factId: number, kind: MemoryKind, ttlDays: number): Awaitable<void>;
240
+ setTitle(factId: number, title: string): Awaitable<void>;
241
+ incrementAssertCount(factId: number): Awaitable<number>;
242
+ markValidTo(factId: number, at: string, atMs?: number | null): Awaitable<void>;
243
+ linkSupersede(factId: number, supersedesId: number): Awaitable<void>;
244
+ archiveSuperseded(factId: number, clock: number): Awaitable<number>;
245
+ revive(factId: number, kind: MemoryKind, ttlDays: number, trust: number, clock: number): Awaitable<number>;
246
+ archive(factId: number, clock: number, reason: string): Awaitable<number>;
247
+ restore(factId: number, trust: number, clock: number): Awaitable<number>;
248
+ bumpHelpful(factId: number, delta: number): Awaitable<void>;
249
+ pin(factId: number, clock: number): Awaitable<number>;
250
+ unpin(factId: number, clock: number): Awaitable<number>;
251
+ pinOutcome(factId: number, clock: number, bonusCount: number, windowAt: string): Awaitable<void>;
252
+ forgetOutcome(factId: number, trust: number, clock: number, bonusCount: number, windowAt: string): Awaitable<void>;
253
+ reinforceOutcome(factId: number, trust: number, clock: number, bonusCount: number, windowAt: string): Awaitable<void>;
254
+ touchUsage(ids: readonly number[]): Awaitable<void>;
255
+ settleTrust(factId: number, trust: number, clock: number): Awaitable<void>;
256
+ reinforce(factId: number, trust: number, clock: number, bonusCount: number, windowAt: string): Awaitable<void>;
257
+ setSemanticVector(factId: number, vector: Uint8Array, model: string, store: string): Awaitable<void>;
258
+ clearVectors(ids: readonly number[]): Awaitable<number>;
259
+ setHrrVector(factId: number, bytes: Uint8Array): Awaitable<void>;
260
+ setEntitiesVersion(ids: readonly number[], version: number): Awaitable<number>;
261
+ markConflictChecked(ids: readonly number[]): Awaitable<number>;
262
+ requeueConflictCheck(ids: readonly number[]): Awaitable<number>;
263
+ }
264
+ /** Entity writes/reads used by the write path, the entity leg and the contradiction detector. */
265
+ export interface EntityRepository {
266
+ linkFact(factId: number, entities: readonly ExtractedEntity[]): Awaitable<void>;
267
+ unlinkFact(factId: number): Awaitable<void>;
268
+ namesForFact(factId: number): Awaitable<string[]>;
269
+ bagsForFacts(ids: readonly number[]): Awaitable<Map<number, string[]>>;
270
+ activeDocFrequency(names: readonly string[]): Awaitable<Map<string, number>>;
271
+ candidateFactsForAnyEntity(names: readonly string[], kind: MemoryKind | undefined, limit: number, queryWidth: number, widthCap: number, source?: string, time?: TimeRange): Awaitable<number[]>;
272
+ }
273
+ /** One stored triple row (detail view). */
274
+ export interface StoredTriple {
275
+ subj: string;
276
+ pred: string;
277
+ obj: string;
278
+ confidence: number;
279
+ }
280
+ /** One triple as the extractor/producer writes it. */
281
+ export interface TripleInput {
282
+ subj: string;
283
+ pred: string;
284
+ obj: string;
285
+ confidence: number;
286
+ source: string;
287
+ }
288
+ export interface TripleRepository {
289
+ insertMany(factId: number, triples: readonly TripleInput[]): Awaitable<void>;
290
+ listForFact(factId: number): Awaitable<StoredTriple[]>;
291
+ deleteForFact(factId: number): Awaitable<void>;
292
+ }
293
+ /**
294
+ * The relation graph: caller-stated/explicit edges, the versioned extractor's derived edges and the
295
+ * read-only walkers. The SQL stays in `db/graph.ts`; this port carries the domain shapes.
296
+ */
297
+ export interface RelationRepository {
298
+ readonly entities: EntityRepository;
299
+ readonly triples: TripleRepository;
300
+ explicitRelationTriples(kind: MemoryKind, relations: readonly RelationInput[] | undefined): Awaitable<RelationCandidate[]>;
301
+ registerEntityAliasesFromText(text: string, knownNames: readonly string[]): Awaitable<AliasRegistration[]>;
302
+ explicitRelationsForFact(factId: number): Awaitable<{
303
+ subject: string;
304
+ predicate: string;
305
+ object: string;
306
+ }[]>;
307
+ writeEdgesForFact(factId: number, triples: readonly EdgeSourceTriple[], options?: {
308
+ dryRun?: boolean;
309
+ samples?: number;
310
+ }): Awaitable<EdgeWriteReport>;
311
+ writeExplicitEdgesForFact(factId: number, relations: readonly {
312
+ subject: string;
313
+ predicate: string;
314
+ object: string;
315
+ }[]): Awaitable<EdgeWriteReport>;
316
+ deleteEdgesForFact(factId: number, options?: {
317
+ keepExplicit?: boolean;
318
+ onlyExplicit?: boolean;
319
+ }): Awaitable<number>;
320
+ rebuildEdgesForFact(factId: number, kind: string): Awaitable<EdgeRebuildReport>;
321
+ reextractEdges(options?: EdgeReextractOptions): Awaitable<EdgeReextractReport>;
322
+ ensureVocabulary(): Awaitable<void>;
323
+ coverage(options?: {
324
+ samples?: number;
325
+ sampleFacts?: number;
326
+ }): Awaitable<GraphCoverageReport>;
327
+ /**
328
+ * The read-only walker. SYNCHRONOUS ON PURPOSE: `GraphReader` is the `@avantf/graph-recall`
329
+ * interface the relation planner and the UI call algorithmically (hundreds of `neighborsOf` hops
330
+ * per query), and it is a pure in-memory view over the rows the adapter loaded when it was built.
331
+ * A remote backend fills that view asynchronously in `graphReader()` itself; the walk never
332
+ * crosses the boundary.
333
+ */
334
+ graphReader(filter?: {
335
+ kind?: MemoryKind;
336
+ source?: string;
337
+ time?: import('../../mem-contract/index.js').TimeFilter;
338
+ }): Awaitable<GraphReader & RelationVocabulary>;
339
+ }
340
+ /** `fact_sources` — where a fact came from. */
341
+ export interface SourceRepository {
342
+ /** The `kind:` prefix rule, kept beside the rows it writes (pure; no SQL). */
343
+ classifyRef(sourceRef: string): Awaitable<{
344
+ kind: FactSourceKind;
345
+ ref: string;
346
+ }>;
347
+ insert(factId: number, kind: FactSourceKind, ref: string): Awaitable<void>;
348
+ listForFact(factId: number): Awaitable<FactSource[]>;
349
+ activeCoverage(): Awaitable<{
350
+ active: number;
351
+ facts_with_source: number;
352
+ }>;
353
+ }
354
+ /** The contradiction log + detector façade. */
355
+ export interface ContradictionRepository {
356
+ /**
357
+ * Supply the store's runtime knobs (the live vector index the embedding leg reads, plus the
358
+ * configured threshold). The detector is built lazily from these, so a direct store built on a
359
+ * backend cannot silently run with the schema default threshold.
360
+ */
361
+ configure(options: {
362
+ threshold?: number;
363
+ fetchVectors?: (ids: number[]) => Map<number, Float32Array>;
364
+ }): void;
365
+ checkOne(factId: number): Awaitable<CheckResult>;
366
+ checkMany(factIds: readonly number[]): Awaitable<CheckResult>;
367
+ openConflictsFor(factId: number): Awaitable<ConflictPair[]>;
368
+ resolveForFact(factId: number): Awaitable<number>;
369
+ resolveForFacts(factIds: readonly number[]): Awaitable<number>;
370
+ get(id: number): Awaitable<StoredConflict | undefined>;
371
+ resolve(id: number, resolution: 'true_positive' | 'false_positive', loserFactId?: number): Awaitable<void>;
372
+ list(resolved?: number, kind?: MemoryKind, threshold?: number, limit?: number): Awaitable<ContradictionRecord[]>;
373
+ }
374
+ /**
375
+ * Full-text / exact search (DB-3).
376
+ *
377
+ * The interface takes the CALLER'S QUERY, never a query-expression in the backend's syntax: the
378
+ * adapter owns its tokenizer, its full-text dialect and its rank sign. That is what lets a second
379
+ * backend answer the same contract with `tsvector`/`pg_trgm` without the store knowing, and it is why
380
+ * `searchContent` may internally fall back from its index to a substring scan (the short-query shape)
381
+ * without the caller branching on which path ran.
382
+ *
383
+ * The index is maintained by the backend itself (SQLite: FTS triggers); `rebuild` is the repair path
384
+ * a caller may run when the derived index must be re-derived from the source rows.
385
+ */
386
+ export interface SearchIndex {
387
+ /** Ranked content search. `rank` is normalized so HIGHER IS ALWAYS BETTER. */
388
+ searchContent(query: string, scope?: SearchScope, limit?: number): Awaitable<SearchHit[]>;
389
+ countContentContaining(query: ExactContentQuery): Awaitable<number>;
390
+ contentContainingRows(query: ExactContentQuery & {
391
+ limit: number;
392
+ offset: number;
393
+ }): Awaitable<number[]>;
394
+ countTitleEqual(query: ExactTitleQuery): Awaitable<number>;
395
+ titleEqualRows(query: ExactTitleQuery & {
396
+ limit: number;
397
+ offset: number;
398
+ }): Awaitable<TitleMatch[]>;
399
+ /** Title search: title-equal, else the adapter's own title index / substring fallback. */
400
+ searchTitles(query: TitleQuery): Awaitable<TitleMatch[]>;
401
+ /** Clear and re-derive the index from the source rows. */
402
+ rebuild(): Awaitable<void>;
403
+ }
404
+ /** The `avantf_stats` key/value side table (retrieval health; the lifecycle clock stays internal). */
405
+ export interface KeyValueRepository {
406
+ read(key: string): Awaitable<string | null>;
407
+ write(key: string, value: string): Awaitable<void>;
408
+ }
409
+ /**
410
+ * The backend's connection-scoped services: transactions, the lifecycle pass, the active-day clock
411
+ * and the restore transaction.
412
+ *
413
+ * TRANSACTIONS ARE ASYNC (DB-2). `work` may `await` repository calls — a remote backend has no other
414
+ * option, and the store's write paths are one unit of work whichever backend runs them. The callback
415
+ * form is deliberate: no backend can hand out a live transaction object that survives an `await`
416
+ * without leaking its dialect (docs/DATABASE_PORTABILITY.md §四).
417
+ */
418
+ export interface LifecycleService {
419
+ transaction<T>(work: () => T | Promise<T>): Promise<T>;
420
+ immediateTransaction<T>(work: () => T | Promise<T>): Promise<T>;
421
+ optimize(): Awaitable<void>;
422
+ initClock(): Awaitable<void>;
423
+ clock(): Awaitable<number>;
424
+ advancePresence(opts?: {
425
+ nowMs?: number;
426
+ gapCapDays?: number;
427
+ }): Awaitable<{
428
+ clock: number;
429
+ counted: number;
430
+ }>;
431
+ trustTick(config: import('../../mem-contract/index.js').Config, opts: {
432
+ clock: number;
433
+ budget?: number | null;
434
+ }): Awaitable<TickResult>;
435
+ maintenance(config: import('../../mem-contract/index.js').Config, opts?: {
436
+ clock?: number;
437
+ budget?: number | null;
438
+ }): Awaitable<MaintenanceResult>;
439
+ restore(doc: FactsExportDocument, reindexEntities: (budget?: number) => Promise<import('../store/entity_sweep.js').EntitySweepReport>, options?: RestoreOptions): Promise<RestoreReport>;
440
+ }
441
+ /**
442
+ * What one open applied (`PRAGMA user_version` on SQLite), exposed for the startup log and the tests.
443
+ *
444
+ * A backend keeps its own schema/migration state (a table, a version row, a service endpoint);
445
+ * `current` is how a caller re-reads it without applying anything — the hook DB-4's migration/import
446
+ * tooling needs, and the one thing the open-time summary cannot answer.
447
+ */
448
+ export interface MigrationRunner {
449
+ readonly from: number;
450
+ readonly to: number;
451
+ readonly applied: readonly number[];
452
+ current(): Awaitable<number>;
453
+ }
454
+ /**
455
+ * The composition-root result. `MemoryStore` receives this object, not a `Db`.
456
+ *
457
+ * `vectors` is deliberately absent: the vector index is a retrieval dependency resolved by the
458
+ * runtime (`resolveVStore`) before the store is built, and the storage boundary only owns the
459
+ * `facts` table's persisted blobs (docs/DATABASE_PORTABILITY.md §十二.2).
460
+ */
461
+ export interface StorageBackend {
462
+ readonly kind: string;
463
+ readonly memory: MemoryRepository;
464
+ readonly relations: RelationRepository;
465
+ readonly sources: SourceRepository;
466
+ readonly contradictions: ContradictionRepository;
467
+ readonly search: SearchIndex;
468
+ readonly keyValue: KeyValueRepository;
469
+ readonly lifecycle: LifecycleService;
470
+ readonly migrations: MigrationRunner;
471
+ close(): void | Promise<void>;
472
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The storage driver REGISTRY (docs/DB_DRIVER_LAYOUT.md §一/§八).
3
+ *
4
+ * `storage/` does nothing but select and hold drivers: no SQL, no repository, no dialect. A driver
5
+ * registers itself by importing the driver package (`dbdrivers/sqlite/index.ts` does so for the
6
+ * builtin), and the host resolves the configured name here.
7
+ *
8
+ * REGISTRATION VALIDATES THE CONTRACT MAJOR. A driver written against an incompatible interface is
9
+ * refused with {@link UnsupportedStorageContractError} at registration time — before any file is
10
+ * opened and before anything can half-work. There is no fallback to another driver.
11
+ *
12
+ * @module storage/registry
13
+ */
14
+ import type { StorageDriver } from '../interfaces/storage_driver.js';
15
+ import { UnsupportedStorageContractError } from '../interfaces/version.js';
16
+ /** Raised when a driver declares a contract major this host does not implement. Re-exported there. */
17
+ export { UnsupportedStorageContractError };
18
+ /**
19
+ * Register a driver under its `name`.
20
+ *
21
+ * @throws {UnsupportedStorageContractError} when `driver.contractVersion` is not a supported major.
22
+ * @throws {Error} when the name is already registered by a DIFFERENT driver object (a colliding
23
+ * external package must be an explicit choice, not a load-order accident).
24
+ */
25
+ export declare function registerStorageDriver<Config>(driver: StorageDriver<Config>): void;
26
+ /** The driver registered under `name`, or `undefined`. */
27
+ export declare function lookupStorageDriver(name: string): StorageDriver<never> | undefined;
28
+ /** The names this host can open (for an error message or a startup log). */
29
+ export declare function storageDriverNames(): string[];
30
+ /**
31
+ * Whether this host can open `driver`: the OPEN-time half of the contract check.
32
+ *
33
+ * Registration already refuses a mismatched driver, but a driver object can also arrive from
34
+ * `createStorageBackend`'s caller (a test harness, an embedder). Open re-checks rather than trusting
35
+ * the registry, and refuses the same way — never a fallback.
36
+ */
37
+ export declare function assertSupportedDriver(driver: Pick<StorageDriver, 'name' | 'contractVersion'>): void;
@@ -0,0 +1,28 @@
1
+ import { type Db } from '../db/conn.js';
2
+ import type { ContradictionRepository, KeyValueRepository, LifecycleService, MemoryRepository, MigrationRunner, RelationRepository, SearchIndex, SourceRepository, StorageBackend } from './ports.js';
3
+ /** The open SQLite database plus the adapter objects built over its one connection. */
4
+ export declare class SqliteStorageBackend implements StorageBackend {
5
+ readonly kind = "sqlite";
6
+ readonly memory: MemoryRepository;
7
+ readonly relations: RelationRepository;
8
+ readonly sources: SourceRepository;
9
+ readonly contradictions: ContradictionRepository;
10
+ readonly search: SearchIndex;
11
+ readonly keyValue: KeyValueRepository;
12
+ readonly lifecycle: LifecycleService;
13
+ readonly migrations: MigrationRunner;
14
+ /** The one connection. Kept private to the adapter; runtime-owned consumers use `leaseHandle`. */
15
+ private readonly db;
16
+ constructor(path: string, options?: {
17
+ contradictionThreshold?: number;
18
+ });
19
+ /**
20
+ * The runtime's transitional handle, for the CLI's relation-backfill commands and `AvantfRuntime.db`.
21
+ *
22
+ * DB-1's boundary is about the store and the DAOs it constructs; `Db` remains the relational port
23
+ * (not a driver), and two CLI commands still use it directly. DB-2 removes this lease once those
24
+ * commands go through a repository method.
25
+ */
26
+ leaseHandle(): Db;
27
+ close(): void;
28
+ }
@@ -18,7 +18,7 @@ import type { SemanticBackend, VectorStore } from '../../mem-retrieval/index.js'
18
18
  * `@avantf/mem-contract`'s `toWellFormedText` is the implementation (well-formed + NFC); this name
19
19
  * is the store-layer entry, so the write paths say WHAT they are doing ("normalize this for a
20
20
  * write") rather than repeating a Unicode recipe. Every text field a store persists — fact content,
21
- * category, archive reason, a document's domain/source/title/uri, entity names and the SPO slots of
21
+ * archive reason, a document's domain/source/title/uri, entity names and the SPO slots of
22
22
  * extracted triples — goes through here, which is what keeps a lone surrogate (and the strict-JSON
23
23
  * failure it causes downstream) out of the database in the first place.
24
24
  */
@@ -37,6 +37,16 @@
37
37
  * (silent beats noisy), and the FTS leg's 2-char substring fallback (§20.20⑦) serves the 2-char
38
38
  * shape.
39
39
  *
40
+ * WHY THE FLOOR IS NOT THE WHOLE BAR (2026-10-21). `min_jaccard` was calibrated on the OLD Jaccard
41
+ * unit, but the metric above has a different one: a maximally wide fact sharing ONE anchor scores
42
+ * `1/(W + cap)` — `0.25` for `W=1`, which clears a `0.2` floor for free. The floor alone therefore
43
+ * cannot tell "one generic anchor" from "one rare anchor", and the anchor filter only removes words
44
+ * ABOVE the ceiling; anchors just inside it kept dragging wide facts in. Each candidate now also
45
+ * clears {@link entityEvidenceFloor}, a bar expressed in the SAME unit and weighted by the shared
46
+ * anchors' {@link anchorCommonness}: a unique anchor leaves `min_jaccard` in charge (the rare-name
47
+ * reachability this metric exists for), an anchor at the ceiling costs two anchors. `min_jaccard`
48
+ * stays the operator's lower bound and the reported `floors.jaccard`.
49
+ *
40
50
  * WHAT `W` COUNTS, AND WHY IT IS NOT `|A|`. `W` is the query's FULL entity count — every entity the
41
51
  * extractor returned, generic or absent included. A Jaccard denominator is the union of what the
42
52
  * query asked for, and the old metric counted all of it; keeping `W` there is what makes a narrow
@@ -53,8 +63,18 @@
53
63
  *
54
64
  * @module store/entity_leg
55
65
  */
56
- /** An entity is an anchor at or below `max(ANCHOR_MIN, ceil(ANCHOR_RATE × activeFacts))`. */
57
- export declare const ANCHOR_RATE = 0.2;
66
+ /**
67
+ * An entity is an anchor at or below `max(ANCHOR_MIN, ceil(ANCHOR_RATE × activeFacts))`.
68
+ *
69
+ * 0.1, not 0.2 (2026-10-21). Measured on the live store: 165 active facts ⇒ the 0.2 ceiling was 33,
70
+ * and `用户` sat at df **32** — one fact inside the line — so every fact mentioning it became an
71
+ * entity-leg candidate at `1/(1+3) = 0.25` (>= the 0.2 floor) with no semantic and no FTS support:
72
+ * 7 of the 10 hits for 「我是谁?」 were that tail (`final` 0.1125). At 0.1 the ceiling is 17 and the
73
+ * word is correctly generic. The frozen eval (`eval_zh`, 41 queries + the self-query sentinel) is
74
+ * bit-identical under either rate — its fixtures are small enough that `ANCHOR_MIN` dominates — so
75
+ * this narrows noise on a real corpus without moving the measured quality numbers.
76
+ */
77
+ export declare const ANCHOR_RATE = 0.1;
58
78
  /**
59
79
  * Small-corpus forgiveness, and its bound.
60
80
  *
@@ -84,6 +104,43 @@ export declare function anchorCeiling(corpus: number): number;
84
104
  * set, and a stable order keeps the SQL/scoring deterministic).
85
105
  */
86
106
  export declare function selectAnchors(queryEntities: Iterable<string>, df: ReadonlyMap<string, number>, corpus: number): string[];
107
+ /**
108
+ * How many anchors the bar demands at the TOP of the anchor band, expressed in the metric's own
109
+ * unit: `2` = the score of a maximally wide fact that shares TWO anchors. This is the constant that
110
+ * ties the threshold to the metric instead of to a second, unrelated number (see
111
+ * {@link entityEvidenceFloor}).
112
+ */
113
+ export declare const ENTITY_RARITY_SPAN = 2;
114
+ /**
115
+ * An anchor's position in the anchor band, in `[0, 1]`: `0` = unique, `1` = at the ceiling.
116
+ *
117
+ * This is the IDF term of the bar, normalized against {@link anchorCeiling} rather than against the
118
+ * corpus size. The ceiling already IS this engine's "generic" line — `selectAnchors` drops anything
119
+ * above it — so normalizing by it puts the 1-anchor/2-anchor boundary at the metric's own midpoint
120
+ * (`commonness = 0.5` ⇒ the bar equals `1/(W + cap)`, the score of a maximally wide 1-anchor fact).
121
+ */
122
+ export declare function anchorCommonness(df: number, ceiling: number): number;
123
+ /**
124
+ * The LEAST rare shared anchor governs the bar: evidence is only as strong as its most generic
125
+ * anchor. Returns `0` when the candidate shares none (it is not entity evidence at all).
126
+ */
127
+ export declare function evidenceCommonness(anchors: readonly string[], factEntities: ReadonlySet<string>, df: ReadonlyMap<string, number>, ceiling: number): number;
128
+ /**
129
+ * The entity leg's evidence bar for one candidate, in the SAME unit as
130
+ * {@link anchoredOverlap}: `max(min_jaccard, ENTITY_RARITY_SPAN × commonness / (W + cap))`.
131
+ *
132
+ * WHY THIS SHAPE. `1/(W+cap)` is the score a maximally wide fact gets when it shares ONE anchor;
133
+ * `2/(W+cap)` is what it gets for TWO. Multiplying by `commonness` moves the bar between those two
134
+ * metric constants as the shared anchor moves across the anchor band: a unique anchor (`0`) leaves
135
+ * the operator's old `min_jaccard` in charge — a rare name on a wide fact is exactly the evidence
136
+ * the saturating metric exists to keep reachable — while an anchor at the ceiling (`1`) demands two
137
+ * anchors, because one is what a wide fact gets for free. The boundary is at `0.5`: below it one
138
+ * anchor is enough, above it the fact must share two.
139
+ *
140
+ * `min_jaccard` is still a lower bound and `0` still turns the gate off, so the knob keeps its
141
+ * meaning; the rarity term is what the old calibration was missing.
142
+ */
143
+ export declare function entityEvidenceFloor(minJaccard: number, queryWidth: number, commonness: number): number;
87
144
  /**
88
145
  * The leg's raw score, in [0, 1] — see the module doc for the formula and the constants.
89
146
  *
@@ -22,6 +22,7 @@
22
22
  *
23
23
  * @module store/entity_sweep
24
24
  */
25
+ import type { Awaitable } from '../interfaces/common.js';
25
26
  /**
26
27
  * What one bounded sweep pass did.
27
28
  *
@@ -73,10 +74,10 @@ export interface EntitySweepTarget<T extends EntitySweepRow> {
73
74
  * population — `total` is every stale row of the store's scope (memory: every ACTIVE fact;
74
75
  * knowledge: every chunk of the sweep's domain), `rows` is that population truncated.
75
76
  */
76
- staleEntityBatch(limit: number): {
77
+ staleEntityBatch(limit: number): Awaitable<{
77
78
  total: number;
78
79
  rows: readonly T[];
79
- };
80
+ }>;
80
81
  /**
81
82
  * Optional count-only read of the SAME population as {@link staleEntityBatch}'s `total`.
82
83
  *
@@ -84,7 +85,7 @@ export interface EntitySweepTarget<T extends EntitySweepRow> {
84
85
  * branch falls back to `staleEntityBatch(0).total`. The loop holds the guard for the whole pass and
85
86
  * each store is single-threaded, so the count cannot move between the two calls it makes.
86
87
  */
87
- staleEntityCount?(): number;
88
+ staleEntityCount?(): Awaitable<number>;
88
89
  /** Re-extract one row's text and write that store's derived rows. May throw; the loop isolates. */
89
90
  extractAndWriteEntities(row: T): Promise<void>;
90
91
  /**