@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,27 +1,27 @@
1
1
  /**
2
- * Entity + SPO triples extraction. Uses nodejieba (optional dependency) for POS;
3
- * degrades gracefully to a character/regex fallback when unavailable, exactly like
4
- * the `jieba_available()` guard.
2
+ * Entity extraction: POS tagging and the noun-ish names that become `entities` rows.
3
+ *
4
+ * Uses nodejieba (optional dependency) for POS; degrades gracefully to a character/regex fallback
5
+ * when unavailable, exactly like the `jieba_available()` guard.
5
6
  *
6
7
  * Note: nodejieba's `tag()` returns `{ word, tag }` objects, and its POS flag set
7
8
  * differs from Python jieba (e.g. proper nouns → `nr`, 名动词 → `vn`).
9
+ *
10
+ * WHAT THIS MODULE IS NOT. It owns what is an entity NAME — nothing about relations. The relation
11
+ * half (SVO candidate triples, explicit patterns, the name/statement/confidence gates and the
12
+ * predicate vocabulary) lives in `relations/`; the two halves share exactly one thing, the tagging
13
+ * pass ({@link tagText}), so a write pays for the dictionary once and drives both.
8
14
  */
9
15
  export interface ExtractedEntity {
10
16
  name: string;
11
17
  type: string;
12
18
  method: string;
13
19
  }
14
- export interface ExtractedTriple {
15
- subj: string;
16
- pred: string;
17
- obj: string;
18
- confidence: number;
19
- source: string;
20
- }
21
- type PosToken = {
20
+ /** One tagged token — the tokenizer's shape, shared with `relations/extract.ts`. */
21
+ export interface PosToken {
22
22
  word: string;
23
23
  tag: string;
24
- };
24
+ }
25
25
  /**
26
26
  * Attempts made so far — the mechanism, not the wall time.
27
27
  *
@@ -33,52 +33,49 @@ type PosToken = {
33
33
  export declare function jiebaLoadAttempts(): number;
34
34
  export declare function jiebaAvailable(): Promise<boolean>;
35
35
  /**
36
- * Version of the extraction RULES below (accepted tags, stop words, triple patterns).
36
+ * Version of the ENTITY extraction rules below (accepted tags, the regex fallback).
37
+ *
38
+ * Persisted per chunk so a reindex can skip rows whose text is unchanged while still rebuilding the
39
+ * ones produced by an older rule set. Bump this whenever a change here would yield different
40
+ * ENTITIES for the same text — it is the only thing that separates "already extracted" from
41
+ * "extracted by the previous version" (DESIGN §20).
42
+ *
43
+ * The relation rules have their OWN version (`db/graph.ts#EDGE_EXTRACTOR_VERSION` in `edges`):
44
+ * an entity-rule change is not an edge-policy change, and vice versa.
45
+ *
46
+ * 2 (Phase 5b, docs/RELATIONAL_RECALL.md §十七): punctuation is an ARGUMENT BOUNDARY instead of an
47
+ * argument in the relation half; `nrt`/`zg` count as arguments like they already counted as entities
48
+ * (`阿里巴巴`); an opening bracket at an argument's edge is skipped (`(架构评审)`); more negations
49
+ * are kept on the predicate (`不再/不会/不能/未能`); and triples are stamped `source: 'svo'` so the
50
+ * graph can require a STATED relation from this producer (`relations/statement.ts`).
51
+ *
52
+ * 3 (2026-10-25 relation review, docs/REVIEW-RELATION-EXTRACTION.md): the relation half's TRIPLES
53
+ * change, not the entity names — curated-predicate surface matching (`主管/隶属于/汇报/部署在/工作于/
54
+ * 属于/合作` are nouns or non-verbs to the tagger, so they were unreachable), `p`/`c` as a merge
55
+ * BOUNDARY instead of tokens to step over, strict + weak predicate candidates merged instead of
56
+ * fallbacks, and a triple-contiguity filter (`isContiguousTriple`). The bump is what makes
57
+ * `reindexEntities` re-run the extractor over existing facts — `reextractEdges` alone only
58
+ * re-derives edges from STORED triples and would keep the old, partial extraction.
37
59
  *
38
- * Persisted per chunk so a reindex can skip rows whose text is unchanged while still
39
- * rebuilding the ones produced by an older rule set. Bump this whenever a change here would
40
- * yield different entities/triples for the same text — it is the only thing that separates
41
- * "already extracted" from "extracted by the previous version" (DESIGN §20).
60
+ * The relation half's naming rules themselves are unchanged by 3; `triples` move because the
61
+ * identifier of "what the text extractor saw" is this stamp, and `MemoryStore.reindexEntities`
62
+ * rewrites entities, triples and edges together under it.
42
63
  */
43
- export declare const ENTITY_EXTRACTOR_VERSION = 1;
44
- export interface TriplePattern {
45
- subj?: string;
46
- pred?: string;
47
- obj?: string;
48
- }
64
+ export declare const ENTITY_EXTRACTOR_VERSION = 3;
49
65
  /**
50
66
  * POS-tag `text` ONCE, or `null` when nodejieba is unavailable (callers fall back to regex).
51
67
  *
52
- * Exists so one write can drive BOTH extractors from a single `jieba.tag()` call: `add`/`update`
53
- * used to tag the same content twice (once for entities, once for triples), which is pure repeated
54
- * mission — measured ~1.1 ms per 1000 characters per pass.
68
+ * Exists so one write can drive BOTH halves from a single `jieba.tag()` call: entity names and
69
+ * relation candidates used to tag the same content twice, which is pure repeated work — measured
70
+ * ~1.1 ms per 1000 characters per pass.
55
71
  */
56
72
  export declare function tagText(text: string): Promise<PosToken[] | null>;
57
73
  /**
58
74
  * Entities from ALREADY-TAGGED tokens (`null` = tagger unavailable → regex fallback on `text`).
59
75
  *
60
- * The token array is passed in rather than re-derived, so a caller that needs entities AND triples
61
- * pays for one tagging pass (see `tagText`).
76
+ * The token array is passed in rather than re-derived, so a caller that needs entities AND
77
+ * relations pays for one tagging pass (see `tagText`).
62
78
  */
63
79
  export declare function entitiesFromTokens(tokens: PosToken[] | null, text: string): ExtractedEntity[];
64
80
  /** Extract entity names using nodejieba POS when available, else a regex fallback. */
65
81
  export declare function extractEntities(text: string): Promise<ExtractedEntity[]>;
66
- /**
67
- * SPO triples from ALREADY-TAGGED tokens (`null` = tagger unavailable → no triples, as before).
68
- *
69
- * `pos` is exported alongside `entitiesFromTokens` so the write path can tag once and drive both.
70
- */
71
- export declare function triplesFromTokens(rawTokens: PosToken[] | null): ExtractedTriple[];
72
- /** Extract a few SPO triples via a simplified SVO heuristic. Returns [] when jieba unavailable. */
73
- export declare function extractTriples(text: string): Promise<ExtractedTriple[]>;
74
- /**
75
- * Parse a natural-language question into a direction-aware TriplePattern where
76
- * interrogative slots are wildcards (`undefined`).
77
- * `parse_query_pattern`: predicates anchor to the interrogative, and no literal
78
- * interrogative word fills a slot.
79
- *
80
- * 「李娜管理谁」 → { subj: '李娜', pred: '管理' }
81
- * 「谁管理李娜」 → { pred: '管理', obj: '李娜' }
82
- */
83
- export declare function parseQueryPattern(query: string): Promise<TriplePattern>;
84
- export {};
@@ -69,12 +69,17 @@ export interface DerivedRuntimePort {
69
69
  }): Promise<{
70
70
  fact_id: number;
71
71
  }>;
72
+ /**
73
+ * `search` as this harness needs it. `hits` is OPTIONAL because the answer has three modes since
74
+ * Phase 4 (a `mode:"relation"` answer carries paths + evidence instead); a relation answer ranks
75
+ * nothing here — this harness grades content retrieval.
76
+ */
72
77
  recall(req: {
73
78
  action: 'search';
74
79
  query: string;
75
80
  limit?: number;
76
81
  }): Promise<{
77
- hits: {
82
+ hits?: {
78
83
  ref_id: number;
79
84
  }[];
80
85
  }>;
@@ -0,0 +1,251 @@
1
+ /**
2
+ * The relation-recall gold set and its evaluator (docs/RELATIONAL_RECALL.md R6 / Phase 5a).
3
+ *
4
+ * WHAT THIS FILE IS. One deterministic corpus, one list of labelled queries, and a runner that
5
+ * seeds a THROWAWAY database through the production write path and measures the rule the relation
6
+ * stage promises (RELATIONAL_RECALL §6–7):
7
+ *
8
+ * - a COMPLETE relation question (one-hop / reverse / two-hop) is answered with structure;
9
+ * - a PURE multi-entity connection ("张伟和李娜") is answered at the FACT level (`mode:"facts"`,
10
+ * docs/GRAPH_RECALL_AND_SEMANTICS.md §7.9), including journal entries and notes without edges;
11
+ * - an incomplete or unanswerable one (no plan, no path/fact, archived evidence, filter mismatch)
12
+ * falls back to fuzzy and never returns an empty `mode:"relation"` / `mode:"facts"`;
13
+ * - exact content and `titles` still win / stay untouched;
14
+ * - aliases (entity + predicate), `kind` / `source` / `time` filters narrow the graph.
15
+ *
16
+ * WHY A GOLD SET AND NOT MORE SPECS. A spec asserts one behaviour per test; an eval fixture assigns
17
+ * every query an expected ROUTE and an expected ANSWER, then reports route accuracy, path
18
+ * precision/recall and the fallback rate over the whole set. The vitest spec
19
+ * (`test/relation_eval.spec.ts`) freezes the numbers, while `scripts/relation_gold_eval.mjs`
20
+ * prints the same report for the docs — both import THIS module, so the table cannot drift from
21
+ * the assertion.
22
+ *
23
+ * HOW THE FIXTURE IS SEEDED. Facts go through `rt.remember` (real FTS rows, entity links, triples,
24
+ * edges). The facts whose object surface the STRING EXTRACTOR's tokenizer would decide differently
25
+ * (`李娜负责支付网关` yields `网关`) get `writeEdgesForFact` afterwards: extraction quality is R3's
26
+ * subject, while the planner/walk is what this fixture evaluates. `writeEdgesForFact` is still the
27
+ * production edge writer (normalization, confidence policy, version stamp), so the seeded graph is
28
+ * the shape production builds. `archive` is the real admin action.
29
+ *
30
+ * WHAT IS DELIBERATELY NOT HERE. No timing assertions (those are `scripts/bench-relation.mjs`,
31
+ * because a CI runner's p95 is not a specification), no live database, no model, no new action.
32
+ *
33
+ * @module eval/relation_gold
34
+ */
35
+ import type { MemoryKind, TimeFilter } from '../../mem-contract/index.js';
36
+ import type { AvantfRuntime } from '../runtime.js';
37
+ /** One triple the fixture wants in the graph, as the production edge writer takes it. */
38
+ export interface RelationGoldEdge {
39
+ subj: string;
40
+ pred: string;
41
+ obj: string;
42
+ }
43
+ /** One fact of the synthetic corpus. `key` is the only handle expectations use. */
44
+ export interface RelationGoldFact {
45
+ key: string;
46
+ content: string;
47
+ kind?: MemoryKind;
48
+ title?: string;
49
+ eventDate?: string;
50
+ sourceRef?: string;
51
+ /**
52
+ * `facts.created_at` to force after the write — the time axis is a caller-visible filter, and
53
+ * "yesterday's fact" must be constructible without waiting a day.
54
+ */
55
+ createdAt?: string;
56
+ /** Edges to (re)write through `writeEdgesForFact`; omitted = whatever the text extractor produced. */
57
+ edges?: readonly RelationGoldEdge[];
58
+ /** Archive this fact after the corpus is seeded (the edge is written first, then hidden). */
59
+ archived?: boolean;
60
+ }
61
+ /** Which behaviour a query is labelled with. One primary label per query keeps the table readable. */
62
+ export type RelationGoldCategory = 'one-hop' | 'reverse' | 'two-hop' | 'facts' | 'alias' | 'archived' | 'kind' | 'source' | 'time' | 'negative' | 'exact';
63
+ /** One expected path, by entity names and the ordered fact keys behind its edges. */
64
+ export interface RelationGoldPath {
65
+ start: string;
66
+ end: string;
67
+ hops: number;
68
+ facts: readonly string[];
69
+ }
70
+ /** The four routes a gold query may be graded on ('time' is not one: every gold query has a `query`). */
71
+ export type RelationGoldMode = 'relation' | 'facts' | 'fuzzy' | 'exact';
72
+ /** What a query must produce. */
73
+ export interface RelationGoldExpectation {
74
+ mode: RelationGoldMode;
75
+ /** Relation answers: the COMPLETE path set (no extra, no missing). */
76
+ paths?: readonly RelationGoldPath[];
77
+ /**
78
+ * The facts the answer must cite. For `relation` this is the evidence set; for `facts` it is the
79
+ * WHOLE fact page (`facts` requires equality, in any order); for `exact`/`fuzzy` it is the hit set
80
+ * (`exact` requires equality, `fuzzy` requires inclusion in its top-5 page).
81
+ */
82
+ facts?: readonly string[];
83
+ }
84
+ /** One labelled query. */
85
+ export interface RelationGoldQuery {
86
+ id: string;
87
+ query: string;
88
+ category: RelationGoldCategory;
89
+ kind?: MemoryKind;
90
+ source?: string;
91
+ time?: TimeFilter;
92
+ expect: RelationGoldExpectation;
93
+ }
94
+ /** A `mem_recall.titles` regression check (the title search must not see the planner). */
95
+ export interface RelationGoldTitleCheck {
96
+ id: string;
97
+ query: string;
98
+ /** Title-holder fact keys, in the order the exact title page returns them. */
99
+ facts: readonly string[];
100
+ }
101
+ /**
102
+ * The corpus.
103
+ *
104
+ * The entity sets are DELIBERATELY disjoint enough that each query has one unambiguous answer:
105
+ *
106
+ * ```text
107
+ * 张伟 ──管理──▶ 李娜 ──管理(负责)──▶ 支付网关 note
108
+ * 张伟 ──管理──▶ 王强 ──管理(负责)──▶ 风控引擎 note (distilled from the journal below) / note
109
+ * 张伟 ──管理──▶ 赵敏 note, ARCHIVED
110
+ * 张伟, 李娜 ──参与(参加)──▶ 架构评审 note
111
+ * 赵敏 ──管理(负责)──▶ 数据平台 note, created_at 2020
112
+ * 周杰, 陈静 note, NO edge (fact-level AND only)
113
+ * 钱伟, 孙丽 note, NO edge, created_at 2020
114
+ * ```
115
+ *
116
+ * `zhang-zhao` is archived AFTER its edge is written, which is the state the reader must hide.
117
+ *
118
+ * `zhang-wang` is the corpus's JOURNAL, and it states the same relation the note `wang-note`
119
+ * distils — that pair is STRUCTURED_RELATIONS §一's promotion rule in fixture form, and it is what
120
+ * makes the `kind` dimension mean something: the journal keeps its `triples` and produces NO edge,
121
+ * so `kind: "journal"` cannot answer a relation question, while the note that carries the stable
122
+ * fact can. (Before Phase 5c both rows were one journal fact with an edge; the graph is note-only
123
+ * now, so the relation moved to the note it belongs to.)
124
+ *
125
+ * The fact-level AND stage (§7.9) reads `fact_entities`, not `edges`, and that is exactly why
126
+ * `zhang-wang` (the journal, no edge), `note-consensus` and `old-pair` (notes whose text produced
127
+ * no relation) are in the corpus: they are the rows a graph-only answer would silently miss.
128
+ */
129
+ export declare const RELATION_GOLD_FACTS: readonly RelationGoldFact[];
130
+ /** An entity alias registered through the production entry point (explicit, never inferred). */
131
+ export declare const RELATION_GOLD_ALIASES: readonly {
132
+ alias: string;
133
+ canonical: string;
134
+ }[];
135
+ /**
136
+ * The labelled queries.
137
+ *
138
+ * 17 relation questions, 9 fact-level AND questions, 16 negative controls, 1 exact-precedence probe
139
+ * (STRUCTURED_RELATIONS §四 moved the `kind: "journal"` relation question into the negative set: a
140
+ * journal produces no edge, so the layer filter can only answer with fuzzy). The breakdown is what
141
+ * makes the report diagnosable: a route-accuracy regression can be read per category, and the
142
+ * negative controls are the half that catches a planner that guesses.
143
+ */
144
+ export declare const RELATION_GOLD_QUERIES: readonly RelationGoldQuery[];
145
+ /** `titles` must keep answering the title index, never the planner. */
146
+ export declare const RELATION_GOLD_TITLE_CHECKS: readonly RelationGoldTitleCheck[];
147
+ /** The runtime surface the fixture needs (the real `AvantfRuntime` satisfies it). */
148
+ export type RelationGoldRuntime = Pick<AvantfRuntime, 'db' | 'remember' | 'recall' | 'admin' | 'memory'>;
149
+ /** Result of seeding: fact key → `fact_id`. */
150
+ export type RelationGoldIds = Readonly<Record<string, number>>;
151
+ /**
152
+ * Seed the corpus into an EMPTY store through the production write path.
153
+ *
154
+ * Order is load-bearing: every fact is written first (so text extraction has run and the FTS rows
155
+ * exist), then explicit edges replace what the extractor produced, then the clock/alias/archive
156
+ * adjustments are applied. Archiving last is what makes `zhang-zhao` a fact whose edge EXISTS but
157
+ * must not be walkable — the exact state the reader's active filter is for.
158
+ *
159
+ * @param rt - a runtime on a throwaway database.
160
+ * @returns fact key → id, for expectations that name facts.
161
+ */
162
+ export declare function seedRelationGold(rt: RelationGoldRuntime): Promise<RelationGoldIds>;
163
+ /** One query's measured route + answer. */
164
+ export interface RelationGoldRow {
165
+ id: string;
166
+ query: string;
167
+ category: RelationGoldCategory;
168
+ expected_mode: RelationGoldMode;
169
+ mode: RelationGoldMode;
170
+ /** What the planner decided BEFORE any read (`null` = neither planner formed a question). */
171
+ plan: 'one-hop' | 'reverse' | 'two-hop' | 'facts' | null;
172
+ /**
173
+ * For a fuzzy answer: why no structured stage answered (diagnosis, not a failure). `no-fact`
174
+ * means the fact-level plan formed but no active fact mentions every entity; `no-path` means the
175
+ * relation plan formed but the graph had no walk; `planner-refused` means neither did.
176
+ */
177
+ fallback: 'planner-refused' | 'no-fact' | 'no-path' | null;
178
+ expected_facts: string[];
179
+ returned_facts: string[];
180
+ missing_facts: string[];
181
+ extra_facts: string[];
182
+ fact_precision: number | null;
183
+ fact_recall: number | null;
184
+ paths_expected: number | null;
185
+ paths_returned: number | null;
186
+ path_matches: number | null;
187
+ path_precision: number | null;
188
+ path_recall: number | null;
189
+ pass: boolean;
190
+ failure: string | null;
191
+ }
192
+ /** Per-category roll-up. */
193
+ export interface RelationGoldCategoryRow {
194
+ total: number;
195
+ passed: number;
196
+ route_accuracy: number;
197
+ }
198
+ /** The whole report. `failures` is what a reviewer reads when `pass` is not 1. */
199
+ export interface RelationGoldReport {
200
+ rows: RelationGoldRow[];
201
+ title_checks: {
202
+ id: string;
203
+ query: string;
204
+ mode: 'exact' | 'fuzzy';
205
+ passed: boolean;
206
+ expected_facts: string[];
207
+ returned_facts: string[];
208
+ }[];
209
+ summary: {
210
+ total: number;
211
+ passed: number;
212
+ route_accuracy: number;
213
+ relation_queries: number;
214
+ relation_answered: number;
215
+ relation_route_accuracy: number;
216
+ facts_queries: number;
217
+ facts_answered: number;
218
+ facts_route_accuracy: number;
219
+ negative_queries: number;
220
+ negative_fallback_rate: number;
221
+ exact_queries: number;
222
+ exact_answered: number;
223
+ plan_formed: number;
224
+ plan_rate: number;
225
+ /** Relations only: `path_matches / paths_returned`, and `path_matches / paths_expected`. */
226
+ path_precision: number | null;
227
+ path_recall: number | null;
228
+ /** Relations only: fact-set precision/recall over evidence. */
229
+ fact_precision: number | null;
230
+ fact_recall: number | null;
231
+ /** Fraction of ALL queries the store answered with fuzzy (diagnosis of the middle layer). */
232
+ fuzzy_fallback_rate: number;
233
+ titles_total: number;
234
+ titles_passed: number;
235
+ by_category: Record<string, RelationGoldCategoryRow>;
236
+ failures: string[];
237
+ };
238
+ }
239
+ /**
240
+ * Evaluate the whole gold set against one seeded runtime.
241
+ *
242
+ * Both planners are invoked SEPARATELY from the store's answer — the relation planner through the
243
+ * same `memory.graphReader(filter)` the store uses in `relationPage`, the fact-level planner through
244
+ * `memory.factEntitiesIn` — so the report can distinguish "the planner refused" from "the planner
245
+ * planned and the read found nothing". That distinction is the point of a routing metric: both fall
246
+ * back to fuzzy, but only one of them is a coverage gap.
247
+ *
248
+ * @param rt - the runtime seeded by {@link seedRelationGold} (or an equivalent fixture).
249
+ * @param ids - fact key → id returned by the seeder.
250
+ */
251
+ export declare function evaluateRelationGold(rt: RelationGoldRuntime, ids: RelationGoldIds): Promise<RelationGoldReport>;
@@ -22,7 +22,7 @@
22
22
  * it in the text looks natural and is a trap: the token ends up inside the subject/predicate/
23
23
  * object the detector parses, and the case silently stops exercising the signal it exists for
24
24
  * (measured: an in-text token suppressed the polarity conflict in 4 of 5 placements). A label
25
- * the store never interprets — `category` — is stable across extractor changes.
25
+ * the store never interprets — the fact text's own label — is stable across extractor changes.
26
26
  */
27
27
  /** What a case is about. `expect_action` is structural only; `live` is content only. */
28
28
  export interface WriteCase {
@@ -1,4 +1,4 @@
1
- import { EXPORT_STATUSES, type ExportStatus, type RememberRequest } from '../mem-contract/index.js';
1
+ import { EXPORT_STATUSES, type ExportStatus, type MemoryKind, type RememberRequest } from '../mem-contract/index.js';
2
2
  /**
3
3
  * The runtime slice these flows actually touch.
4
4
  *
@@ -21,15 +21,18 @@ export interface FactsIoRuntime {
21
21
  /** The document's discriminator. A file without it is not an export of this tool. */
22
22
  export declare const FACTS_EXPORT_FORMAT = "avantf-mem-facts";
23
23
  /**
24
- * The version THIS build writes: 2 (v1 + the facts columns v1 did not carry).
24
+ * The version THIS build writes: 5 (v4's `title` plus the note-only `relations`).
25
25
  *
26
- * Versions are additive by design: v2 adds OPTIONAL fields, so a v1 document is still the same
27
- * shape minus the new keys. A version outside {@link FACTS_EXPORT_VERSIONS} must be refused, never
28
- * guessed at.
26
+ * Versions are additive by design: v2/v3/v4/v5 each add OPTIONAL fields, so an older document is the
27
+ * same shape minus the new keys. A version outside {@link FACTS_EXPORT_VERSIONS} must be refused,
28
+ * never guessed at.
29
29
  */
30
- export declare const FACTS_EXPORT_VERSION = 2;
31
- /** A version this build can READ. v1 documents are what users already have on disk. */
32
- export declare const FACTS_EXPORT_VERSIONS: readonly [1, 2];
30
+ export declare const FACTS_EXPORT_VERSION = 5;
31
+ /**
32
+ * A version this build can READ. v1–v4 documents are what users already have on disk, and a v5
33
+ * document adds only the OPTIONAL `relations` field — so all five parse into the same type.
34
+ */
35
+ export declare const FACTS_EXPORT_VERSIONS: readonly [1, 2, 3, 4, 5];
33
36
  /** The version literal, so a parsed document can say which field set it came from. */
34
37
  export type FactsExportVersion = (typeof FACTS_EXPORT_VERSIONS)[number];
35
38
  /**
@@ -54,7 +57,21 @@ export interface ExportedFact {
54
57
  /** Source-local row id. For reports/debugging ONLY; content is the identity. */
55
58
  source_id: number;
56
59
  content: string;
57
- category: string;
60
+ /** The memory layer (v3). ABSENT on a v1/v2 document, which carries `category` instead. */
61
+ kind?: MemoryKind;
62
+ /**
63
+ * v4: the journal title (JOURNAL_TITLE_SEARCH §7). ABSENT on a v1/v2/v3 document — those rows had
64
+ * no title column at all, and an import restores them as NULL rather than inventing one.
65
+ */
66
+ title?: string | null;
67
+ /** The old free-form label, kept for audit only; `null` when the row predates the migration. */
68
+ legacy_category?: string | null;
69
+ /**
70
+ * v1/v2 ONLY: the old free-form category. Read (and mapped) for backward compatibility, never
71
+ * written by this build — an export from a v10 source database still carries it, and a v1/v2
72
+ * document on disk is exactly that shape.
73
+ */
74
+ category?: string;
58
75
  status: string;
59
76
  created_at: string | null;
60
77
  updated_at: string | null;
@@ -93,11 +110,27 @@ export interface ExportedFact {
93
110
  */
94
111
  entities_version?: number | null;
95
112
  conflict_checked?: number | null;
113
+ /**
114
+ * v5: the relations the CALLER stated on this note (`edges.source = 'explicit'`), in insertion
115
+ * order. ABSENT on a v1–v4 document (those rows were written before the field existed) and on
116
+ * every `journal` row — a journal refuses relations by contract, so the key there would assert a
117
+ * relation set a journal cannot have.
118
+ *
119
+ * These are NOT the extracted (`svo`) edges: those are re-derived from `content`, while an
120
+ * explicit relation is the one graph fact the text does not contain. That is why it travels.
121
+ */
122
+ relations?: ExportedRelation[];
123
+ }
124
+ /** One caller-stated relation inside an export document (v5). */
125
+ export interface ExportedRelation {
126
+ subject: string;
127
+ predicate: string;
128
+ object: string;
96
129
  }
97
130
  /** The single JSON document `export` writes (and `import`/`restore` read). */
98
131
  export interface FactsExportDocument {
99
132
  format: typeof FACTS_EXPORT_FORMAT;
100
- /** The version the document was WRITTEN as (1 or 2); this build writes 2. */
133
+ /** The version the document was WRITTEN as (1, 2 or 3); this build writes 3. */
101
134
  version: FactsExportVersion;
102
135
  exported_at: string;
103
136
  source: {
@@ -274,6 +307,13 @@ export declare function parseFactsExport(text: string): FactsExportDocument;
274
307
  * Order of decisions per fact: archived → skipped_archived (v1 never revives); content already in
275
308
  * the target (or planned by an earlier row of this same document) → skipped_existing; otherwise the
276
309
  * store's add path runs. A row the store refuses is collected in `failed` and the loop continues.
310
+ *
311
+ * A v5 row's `relations` travel through that same add path, so the imported fact gets its declared
312
+ * relations as `source: 'explicit'` edges (normalized, name-gated, four-tuple deduped against the
313
+ * extracted ones). Two consequences worth knowing: a row that is SKIPPED as already-present keeps
314
+ * the target's own relation set (this is a content union, not a merge of graphs), and a `journal`
315
+ * row carrying relations is refused per row with {@link RELATIONS_NEED_NOTE} — that combination is
316
+ * unrepresentable, so it must be reported rather than silently stripped.
277
317
  */
278
318
  export declare function importFacts(rt: FactsIoRuntime, doc: FactsExportDocument, options?: ImportOptions): Promise<ImportReport>;
279
319
  /**
@@ -19,15 +19,19 @@ export * from './modelBootstrap.js';
19
19
  export * from './dispatch.js';
20
20
  export * from './trustHeartbeat.js';
21
21
  export * from './runtime.js';
22
+ export * from './storage/index.js';
23
+ export * from './interfaces/index.js';
24
+ export { isSqliteStorageBackend, sqliteDriver, SqliteStorageBackend, SQLITE_CAPABILITIES } from './dbdrivers/sqlite/index.js';
25
+ export { FAKE_CAPABILITIES, FakeStorageBackend, fakeDriver, isFakeStorageBackend, MockRemoteTransport, type MockRemoteTurnaround, type StorageTransport, type TransportRequestRecord, } from './dbdrivers/fake/index.js';
22
26
  export * from './store/memory.js';
23
- export * from './store/knowledge.js';
24
27
  export { normalizeWrite } from './store/common.js';
25
28
  export * from './facts_io.js';
26
29
  export * from './maintenance_paths.js';
27
30
  export * from './store/facts_restore.js';
28
31
  export * from './store/vector_repair.js';
29
32
  export * from './store/hybrid.js';
30
- export * from './store/source_picker.js';
31
33
  export * from './store/lexical.js';
32
34
  export * from './store/floors.js';
33
35
  export { registerSemanticBackend, registerVectorStore, type SemanticBackend, type VectorStore, } from '../mem-retrieval/index.js';
36
+ export * from './store/relation_backfill.js';
37
+ export { extractRelationCandidates, normalizeRelation, evaluateRelation, type RelationCandidate, type RelationEvaluation, type RelationExtractor, } from './relations/index.js';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * What a storage driver DECLARES it can do (docs/DB_DRIVER_LAYOUT.md §二).
3
+ *
4
+ * Capabilities are how the HAL stays honest about optional behaviour without a second interface per
5
+ * driver: a backend that cannot do full-text search says so once, here, instead of the store probing
6
+ * it with a query that fails. Adding a flag is a MINOR contract change (an old driver that does not
7
+ * know the flag simply does not declare it; the host must tolerate unknown flags).
8
+ *
9
+ * @module interfaces/capabilities
10
+ */
11
+ export interface StorageCapabilities {
12
+ /** Async unit-of-work transactions (`LifecycleService.transaction`). */
13
+ transactions: boolean;
14
+ /** Content full-text search (`SearchIndex.searchContent`). */
15
+ fullText: boolean;
16
+ /** Title search (`SearchIndex.searchTitles` + the exact-title face). */
17
+ titles: boolean;
18
+ /** The relation graph (entities / triples / edges / graph reader). */
19
+ relations: boolean;
20
+ /**
21
+ * Persisted vector BLOBS (`facts.semantic_vector` / `hrr_vector`) and their inventory reads. The
22
+ * live vector INDEX is a retrieval concern resolved above the storage layer; this flag is about
23
+ * storing and reading the rows a driver owns.
24
+ */
25
+ vectors: boolean;
26
+ /** The backend applies its own schema migrations on open (`MigrationRunner`). */
27
+ migrations: boolean;
28
+ /** The contract is async (always `true` for contract major 1 — see `interfaces/version.ts`). */
29
+ async: true;
30
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The storage HAL's async shape (DB-2).
3
+ *
4
+ * Every port method that touches stored state returns this: the in-process SQLite driver fulfils the
5
+ * contract by returning a plain value (it has no I/O to wait for), while a remote driver returns a
6
+ * promise. Callers ALWAYS `await`, so no store, no retrieval leg and no lifecycle caller has to know
7
+ * which kind of driver it holds — and a new driver never rewrites a call site.
8
+ *
9
+ * @module interfaces/common
10
+ */
11
+ export type Awaitable<T> = T | Promise<T>;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The storage error boundary (DB-3).
3
+ *
4
+ * SQLite reports failures as a driver error object with a numeric `errcode`; PostgreSQL has SQLSTATE,
5
+ * an HTTP backend has status codes, and none of those belong above the port. The store has exactly
6
+ * three things it can usefully do with a failed read/write: retry (the backend is temporarily
7
+ * unavailable), report a conflict (a uniqueness/constraint rule refused the write), or fail (anything
8
+ * else). This module names those three, and `db/sqlite.ts` — the one place that sees a driver error —
9
+ * maps into them.
10
+ *
11
+ * WHAT THIS IS NOT: a replacement for the domain refusals. `TITLE_IMMUTABLE`, `RELATIONS_NEED_NOTE`
12
+ * and the other `Error`s the store throws are decisions about the caller's request, not backend
13
+ * failures; they stay plain and unchanged. Only errors that come out of the database layer are
14
+ * classified here.
15
+ *
16
+ * The messages are preserved verbatim (the classification is metadata), so every existing caller and
17
+ * test that asserts on a driver message keeps working.
18
+ *
19
+ * @module interfaces/errors
20
+ */
21
+ /** What a caller can do about a failed storage operation. */
22
+ export type StorageFailureKind = 'unavailable' | 'constraint' | 'corrupt' | 'unknown';
23
+ /** A database-layer failure, classified so the boundary does not leak a driver's error shape. */
24
+ export declare class StorageError extends Error {
25
+ readonly kind: StorageFailureKind;
26
+ constructor(message: string, kind: StorageFailureKind, options?: {
27
+ code?: string | number;
28
+ cause?: unknown;
29
+ });
30
+ /** The driver's own code, kept for a log line — never branched on above the adapter. */
31
+ readonly code?: string | number;
32
+ /** Whether the same call may succeed if repeated later (a lock timeout, a restarting server). */
33
+ get retryable(): boolean;
34
+ }
35
+ /** The backend is temporarily unable to serve (SQLite: `SQLITE_BUSY`/`SQLITE_LOCKED`). */
36
+ export declare class StorageUnavailableError extends StorageError {
37
+ constructor(message: string, options?: {
38
+ code?: string | number;
39
+ cause?: unknown;
40
+ });
41
+ }
42
+ /** The backend refused the write because of one of its integrity rules. */
43
+ export declare class StorageConstraintError extends StorageError {
44
+ constructor(message: string, options?: {
45
+ code?: string | number;
46
+ cause?: unknown;
47
+ });
48
+ }
49
+ /** The stored data cannot be read (SQLite: `SQLITE_CORRUPT`/`SQLITE_NOTADB`). */
50
+ export declare class StorageCorruptError extends StorageError {
51
+ constructor(message: string, options?: {
52
+ code?: string | number;
53
+ cause?: unknown;
54
+ });
55
+ }
56
+ /** Whether a caught value is already one of ours (a second mapping must not wrap it again). */
57
+ export declare function isStorageError(error: unknown): error is StorageError;
58
+ /**
59
+ * Classify a driver failure into the storage vocabulary.
60
+ *
61
+ * Takes `unknown` because that is what a `catch` binds; anything already classified is returned
62
+ * untouched (mapping is idempotent), and a non-error value becomes a generic `StorageError` carrying
63
+ * its string form, so callers never have to re-check.
64
+ */
65
+ export declare function mapStorageError(error: unknown): StorageError;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The storage HAL contract (docs/DB_DRIVER_LAYOUT.md).
3
+ *
4
+ * Nothing in this directory may import `dbdrivers/`, `db/dao` or `node:sqlite`: these files are the
5
+ * vocabulary a driver implements and the store consumes, and they have to stay loadable in a host
6
+ * that has no SQLite at all.
7
+ *
8
+ * @module interfaces
9
+ */
10
+ export * from './common.js';
11
+ export * from './version.js';
12
+ export * from './capabilities.js';
13
+ export * from './migrations.js';
14
+ export * from './repositories.js';
15
+ export * from './search_index.js';
16
+ export * from './errors.js';
17
+ export * from './storage_backend.js';
18
+ export * from './storage_driver.js';