@avantf/dsh-mem 0.6.4 → 0.7.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.
- package/README.md +15 -10
- package/lib/client.js +1146 -2300
- package/lib/dsh-build.json +5 -5
- package/lib/engine/mem/config/config_files.d.ts +1 -1
- package/lib/engine/mem/facts_io.d.ts +288 -0
- package/lib/engine/mem/index.d.ts +4 -0
- package/lib/engine/mem/maintenance_paths.d.ts +41 -0
- package/lib/engine/mem/runtime.d.ts +57 -3
- package/lib/engine/mem/store/facts_restore.d.ts +116 -0
- package/lib/engine/mem/store/memory.d.ts +16 -0
- package/lib/engine/mem-contract/tools.d.ts +55 -1
- package/lib/index.js +4301 -2614
- package/lib/types/command.d.ts +61 -0
- package/lib/types/command.d.ts.map +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/interface_gate.d.ts +9 -9
- package/lib/types/remote.d.ts +9 -2
- package/lib/types/remote.d.ts.map +1 -1
- package/lib/types/render.d.ts +3 -3
- package/package.json +6 -6
package/lib/dsh-build.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@deepseek-ai/dsh-system-prompt": "0.2.
|
|
3
|
-
"@deepseek-ai/dsh-tools": "0.2.
|
|
4
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.
|
|
5
|
-
"@deepseek-ai/dsh-typert-registry": "0.2.
|
|
6
|
-
"@deepseek-ai/dsh-util-values": "0.2.
|
|
2
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.1-rc.2",
|
|
3
|
+
"@deepseek-ai/dsh-tools": "0.2.1-rc.2",
|
|
4
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2",
|
|
5
|
+
"@deepseek-ai/dsh-typert-registry": "0.2.1-rc.2",
|
|
6
|
+
"@deepseek-ai/dsh-util-values": "0.2.1-rc.2"
|
|
7
7
|
}
|
|
@@ -15,7 +15,7 @@ import type { AvantfLogger } from '../../mem-contract/index.js';
|
|
|
15
15
|
* UNCOMMENTED line is warned about by the loader, so a wrong name here would be a trap rather than a
|
|
16
16
|
* hint. `config_files.spec.ts` pins the ones this template names.
|
|
17
17
|
*/
|
|
18
|
-
export declare const DEFAULT_COMMON_CONFIG = "# avantf-mem \u516C\u5171\u914D\u7F6E\uFF08\u5206\u5C42\u7B2C\u2461\u5C42\uFF09\u3002\u8FD9\u91CC\u5168\u662F\u6CE8\u91CA\uFF1A\u5220\u6389/\u7559\u7A7A = \u7528\u5185\u5EFA\u9ED8\u8BA4\uFF0C\u53D6\u6D88\u6CE8\u91CA\u5373\u751F\u6548\u3002\n# \u5206\u5C42\uFF08\u4F4E\u2192\u9AD8\uFF09\uFF1A\u2460 \u5185\u5EFA\u9ED8\u8BA4 \u2192 \u2461 \u672C\u6587\u4EF6 \u2192 \u2462 \u540C\u76EE\u5F55\u7684 memory.yaml / knowledge.yaml\uFF08\u7F3A\u5931=\u4E0D\u8986\u76D6\uFF09\n# \u2192 \u2463 \u73AF\u5883\u53D8\u91CF \u2192 \u2464 \u63D2\u4EF6/CLI \u663E\u5F0F\u4F20\u53C2\n# \u6240\u6709\u53EF\u7F16\u8F91\u6587\u672C\u90FD\u5728 ~/.avantf \u4E0B\uFF1Aconfigs/*.yaml\uFF08\u672C\u76EE\u5F55\uFF09\u4E0E prompts/*.md\uFF08\u6A21\u578B\u63D0\u793A\u8BCD\uFF09\u3002\n\n# \u6570\u636E\u6839\u4E0D\u5728\u8FD9\u91CC\u914D\uFF1A\u5B83\u7531 \u2463 AVANTF_HOME\uFF08\
|
|
18
|
+
export declare const DEFAULT_COMMON_CONFIG = "# avantf-mem \u516C\u5171\u914D\u7F6E\uFF08\u5206\u5C42\u7B2C\u2461\u5C42\uFF09\u3002\u8FD9\u91CC\u5168\u662F\u6CE8\u91CA\uFF1A\u5220\u6389/\u7559\u7A7A = \u7528\u5185\u5EFA\u9ED8\u8BA4\uFF0C\u53D6\u6D88\u6CE8\u91CA\u5373\u751F\u6548\u3002\n# \u5206\u5C42\uFF08\u4F4E\u2192\u9AD8\uFF09\uFF1A\u2460 \u5185\u5EFA\u9ED8\u8BA4 \u2192 \u2461 \u672C\u6587\u4EF6 \u2192 \u2462 \u540C\u76EE\u5F55\u7684 memory.yaml / knowledge.yaml\uFF08\u7F3A\u5931=\u4E0D\u8986\u76D6\uFF09\n# \u2192 \u2463 \u73AF\u5883\u53D8\u91CF \u2192 \u2464 \u63D2\u4EF6/CLI \u663E\u5F0F\u4F20\u53C2\n# \u6240\u6709\u53EF\u7F16\u8F91\u6587\u672C\u90FD\u5728 ~/.avantf \u4E0B\uFF1Aconfigs/*.yaml\uFF08\u672C\u76EE\u5F55\uFF09\u4E0E prompts/*.md\uFF08\u6A21\u578B\u63D0\u793A\u8BCD\uFF09\u3002\n\n# \u6570\u636E\u6839\u4E0D\u5728\u8FD9\u91CC\u914D\uFF1A\u5B83\u7531 \u2463 AVANTF_HOME\uFF08\u5BB6\u65CF\u6839\uFF1A\u540C\u65F6\u51B3\u5B9A\u6570\u636E\u6839\u4E0E models/tools\uFF09\u51B3\u5B9A \u2014\u2014\n# \u53EA\u60F3\u6362\u6570\u636E\u6839\u3001\u4E0D\u642C\u6A21\u578B\u7F13\u5B58\u65F6\u7528 CLI \u7684 \u2464 \u663E\u5F0F\u53C2\u6570 avantf-mem --data-home <path>\uFF08\u63D2\u4EF6\u5219\u662F profile \u7684\n# dataHome\uFF0C\u5C5E \u2461\uFF09\u3002\u672C\u6587\u4EF6\u5C31\u5728\u6570\u636E\u6839\u91CC\u9762\uFF0C\u89E3\u6790\u6839\u65F6\u8FD8\u6CA1\u8BFB\u5230\u5B83\uFF0C\u6240\u4EE5\u5199\u5728\u8FD9\u91CC\u7684 dataHome \u5BF9\u6570\u636E\u6839\u65E0\u6548\uFF08DESIGN \u00A73\uFF09\u3002\n\n# semantic: # \u5D4C\u5165\u540E\u7AEF\uFF08\u8BB0\u5FC6\u4E0E\u77E5\u8BC6\u5171\u7528\uFF0C\u8DE8\u5E93\u5206\u6570\u624D\u53EF\u6BD4\uFF09\n# backend: local_bge\n# local_model: Xenova/bge-base-zh-v1.5 # \u5FC5\u987B\u662F ONNX \u4ED3\u5E93\n# dim: 768\n# auto_download: true\n# auto_migrate: true # \u6362\u6A21\u578B/\u5BBD\u5EA6\u540E\u81EA\u52A8\u5206\u6279\u91CD\u7B97\u65E7\u7A7A\u95F4\u7684\u5411\u91CF\uFF08\u9ED8\u8BA4\u5F00\uFF09\uFF1B\u5173\u6389\u53EA\u5269\u542F\u52A8\u544A\u8B66 + vectors --fix\n# \u26A0\uFE0F cache_dir / mirror \u7531\u5BB6\u65CF\u5E95\u5EA7 @avantf/dsh-plugin-base \u7BA1\u7406\uFF1A\u5199\u5728\u8FD9\u91CC\u4F1A\u88AB\u5FFD\u7565\u5E76\u544A\u8B66\u3002\n# \u7F13\u5B58\u76EE\u5F55\u7528\u73AF\u5883\u53D8\u91CF AVANTF_MEM_MODEL_CACHE\uFF0C\u955C\u50CF\u7528 AVANTF_MEM_MODEL_MIRROR\uFF08\u6216 HF_ENDPOINT\uFF09\u3002\n\n# vectorStore:\n# backend: auto # auto = \u8D8A\u8FC7\u9608\u503C\u81EA\u52A8\u4ECE local_numpy \u8FC1\u5230 hnswlib\n# auto_thresholds:\n# hnswlib: 2000\n# hnswlib_ef_search: 256 # ANN \u7684\u53EC\u56DE/\u901F\u5EA6\u65CB\u94AE\uFF0C\u8BED\u6599\u53D8\u5927\u5148\u8C03\u5B83\n\n# retriever: # \u4E09\u6761\u68C0\u7D22\u817F\u5728\u878D\u5408\u91CC\u7684\u6743\u91CD\u4E0E\"\u76F8\u5173\u6027\u95E8\u69DB\"\n# weight_semantic: 0.55\n# weight_fts: 0.30\n# weight_jaccard: 0.15\n# # \u7EDD\u5BF9\u95E8\u69DB\u6253\u5728\u6BCF\u6761\u817F\u7684\u539F\u59CB\u5206\u4E0A\u3001fuse() \u4E4B\u524D\uFF08\u878D\u5408\u5206\u53EA\u5728\u4E00\u6B21\u67E5\u8BE2\u5185\u53EF\u6BD4\uFF0C\u4E0D\u80FD\u5F53\u95E8\u69DB\uFF09\u3002\n# # 0 = \u5173\u95ED\u8BE5\u95E8\u69DB\uFF1B\u5206\u6570\u7B49\u4E8E\u95E8\u69DB\u4FDD\u7559\u3002\u8BED\u4E49\u540E\u7AEF\u4E0D\u53EF\u7528\u65F6 min_fts_terms \u7684\u751F\u6548\u503C\u653E\u5BBD\u5230 1\u3002\n# min_semantic_similarity: 0.5 # \u8BED\u4E49\u817F\u4F59\u5F26\uFF1B0.5 \u53EA\u5BF9 bge-base-zh-v1.5/768\uFF08mean pooling\uFF09\u6807\u5B9A\u8FC7\uFF0C\u6362\u6A21\u578B\u8981\u91CD\u6807\n# min_fts_terms: 2 # FTS \u817F\uFF1A\u8FD9\u4E00\u884C\u547D\u4E2D\u51E0\u4E2A\u4E0D\u540C\u7684\u67E5\u8BE2\u8BCD\u5143\uFF08\u62C9\u4E01\u8BCD\u22655\u5B57\u7B26 + CJK 3-gram\uFF09\n# min_jaccard: 0.2 # \u5B9E\u4F53\u817F\uFF1A\u951A\u70B9\u5B9E\u4F53\u7684 Jaccard\uFF08\u4E8B\u5B9E\u5BBD\u5EA6\u9971\u548C\uFF09\uFF1B2 \u5B9E\u4F53\u547D\u4E2D 1 \u4E2A\u6B63\u597D 0.2\n# # P-11 \u4E2D\u6587\u65F6\u95F4\u7A97\u817F\uFF08\u9ED8\u8BA4\u5173\uFF09\uFF1A\u5F00\u542F\u540E\uFF0C\u5E26\u4E2D\u6587\u65F6\u95F4\u8868\u8FBE\u7684\u67E5\u8BE2\uFF08\u4E0A\u4E2A\u6708 / \u6700\u8FD1\u4E09\u5929 / 2026\u5E749\u6708 \u2026\uFF09\n# # \u4F1A\u989D\u5916\u628A\"\u4E8B\u4EF6\u65F6\u95F4 valid_from \u843D\u5728\u8BE5\u7A97\u53E3\"\u7684\u6D3B\u8DC3\u4E8B\u5B9E\u4F5C\u4E3A\u4E00\u6761\u817F\u53C2\u4E0E\u878D\u5408\u3002\u5B83\u662F**\u52A0\u4E00\u6761\u817F\u3001\u4E0D\u662F\u8FC7\u6EE4\u5668**\uFF1A\n# # \u53EA\u589E\u52A0\u5019\u9009\uFF0C\u7EDD\u4E0D\u5220\u6389\u8BED\u4E49 / FTS / \u5B9E\u4F53\u817F\u5DF2\u7ECF\u627E\u5230\u7684\u4E8B\u5B9E\uFF1B\u6CA1\u6709\u4E8B\u4EF6\u65F6\u95F4\u7684\u4E8B\u5B9E\u4E0D\u4F5C\u4E3A\u8BE5\u817F\u5019\u9009\uFF0C\n# # \u4E5F**\u4E0D\u56DE\u9000\u5230\u5199\u5165\u65F6\u95F4 created_at**\u3002\u5B83\u5171\u7528 jaccard \u6743\u91CD\u4F4D\uFF08\u5BF9\u5916\u7684\u4E09\u952E\u6743\u91CD\u4E0D\u53D8\uFF09\u3002\n# # \u4EC0\u4E48\u65F6\u5019\u8BE5\u5F00\uFF1A\u5148\u7528 mem_admin stats \u770B validity.coverage\uFF08\u6709\u591A\u5C11\u6D3B\u8DC3\u4E8B\u5B9E\u5E26\u4E8B\u4EF6\u65F6\u95F4\uFF09\uFF0C\n# # \u518D\u7528\u56DE\u5F52\u7F51\u786E\u8BA4\u5B83\u786E\u5B9E\u6539\u5584\u65F6\u95F4\u7C7B\u67E5\u8BE2\u2014\u2014\u8986\u76D6\u7387\u22480 \u65F6\u5B83\u7B49\u4E8E\u6CA1\u5F00\u3002\u5199\u5165\u4E8B\u4EF6\u65F6\u95F4\u7528 mem_remember \u7684 event_date\u3002\n# time_window: false\n\n# lifecycle:\n# purge_after_archived_days: 365 # \u5F52\u6863\u4E8B\u5B9E\u7684\u7269\u7406\u6E05\u7406\u7A97\u53E3\uFF08\u6D3B\u8DC3\u65E5\uFF09\n# contradiction_threshold: 0.6\n\n# trust:\n# decay_per_day: 0.0055\u2026\n# presence:\n# heartbeat_minutes: 60 # \u5E38\u9A7B\u8FDB\u7A0B\u7684\u5FC3\u8DF3\uFF08\u6D3B\u8DC3\u65E5\u65F6\u949F\u4E0E\u751F\u547D\u5468\u671F\u626B\u63CF\uFF09\n\n# tools: # \u5916\u90E8\u4E8C\u8FDB\u5236\uFF08pandoc\uFF09\u7684\u53D7\u7BA1\u76EE\u5F55\n# dir: '' # \u7559\u7A7A = \u5BB6\u65CF\u6839 tools\uFF08~/.avantf/env/tools\uFF09\uFF1BAVANTF_TOOLS_DIR \u53EF\u8986\u76D6\n# auto_install: true\n";
|
|
19
19
|
/**
|
|
20
20
|
* Ensure the common config exists with content, and hand back the path either way.
|
|
21
21
|
*
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { EXPORT_STATUSES, type ExportStatus, type RememberRequest } from '../mem-contract/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The runtime slice these flows actually touch.
|
|
4
|
+
*
|
|
5
|
+
* Narrow on purpose: export reads two config fields, import/merge also call the store's `add` path.
|
|
6
|
+
* Keeping the parameter structural (rather than the whole `AvantfRuntime`) is what lets the admin
|
|
7
|
+
* dispatcher — an object literal whose `closed` getter is attached only at return time — pass
|
|
8
|
+
* itself in without a cast, and it documents that these flows cannot reach any other store face.
|
|
9
|
+
*/
|
|
10
|
+
export interface FactsIoRuntime {
|
|
11
|
+
config: {
|
|
12
|
+
home: string;
|
|
13
|
+
memory: {
|
|
14
|
+
db: {
|
|
15
|
+
path: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
remember(req: RememberRequest): Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
/** The document's discriminator. A file without it is not an export of this tool. */
|
|
22
|
+
export declare const FACTS_EXPORT_FORMAT = "avantf-mem-facts";
|
|
23
|
+
/**
|
|
24
|
+
* The version THIS build writes: 2 (v1 + the facts columns v1 did not carry).
|
|
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.
|
|
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];
|
|
33
|
+
/** The version literal, so a parsed document can say which field set it came from. */
|
|
34
|
+
export type FactsExportVersion = (typeof FACTS_EXPORT_VERSIONS)[number];
|
|
35
|
+
/**
|
|
36
|
+
* `--status` values accepted by `export` (the whole DB, or one status).
|
|
37
|
+
*
|
|
38
|
+
* Re-exported from the contract, where the admin action and the CLI flag read the same set. The
|
|
39
|
+
* engine's public face keeps the name its callers already import.
|
|
40
|
+
*/
|
|
41
|
+
export { EXPORT_STATUSES, type ExportStatus };
|
|
42
|
+
/** One `fact_sources` row, copied verbatim. */
|
|
43
|
+
export interface ExportedFactSource {
|
|
44
|
+
kind: string;
|
|
45
|
+
ref: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* One fact, as it stands in the source database.
|
|
49
|
+
*
|
|
50
|
+
* Every field is part of the documented format even when v1 import ignores it — the point of the
|
|
51
|
+
* document is that a later restore does not have to invent what was never written down.
|
|
52
|
+
*/
|
|
53
|
+
export interface ExportedFact {
|
|
54
|
+
/** Source-local row id. For reports/debugging ONLY; content is the identity. */
|
|
55
|
+
source_id: number;
|
|
56
|
+
content: string;
|
|
57
|
+
category: string;
|
|
58
|
+
status: string;
|
|
59
|
+
created_at: string | null;
|
|
60
|
+
updated_at: string | null;
|
|
61
|
+
ttl_days: number;
|
|
62
|
+
pinned: boolean;
|
|
63
|
+
pinned_at: string | null;
|
|
64
|
+
trust_score: number;
|
|
65
|
+
assert_count: number;
|
|
66
|
+
helpful_count: number;
|
|
67
|
+
last_reinforced_at: string | null;
|
|
68
|
+
sources: ExportedFactSource[];
|
|
69
|
+
valid_from: string | null;
|
|
70
|
+
valid_to: string | null;
|
|
71
|
+
supersedes_source_id: number | null;
|
|
72
|
+
/** `null` for an active row; the archive bookkeeping for an archived one. */
|
|
73
|
+
archive: {
|
|
74
|
+
at: string | null;
|
|
75
|
+
reason: string | null;
|
|
76
|
+
} | null;
|
|
77
|
+
/** Active-day clock at the last trust settle — the decay baseline. v1 had no such column. */
|
|
78
|
+
settle_clock?: number | null;
|
|
79
|
+
/** Reinforcement events inside the current 24h window. */
|
|
80
|
+
bonus_count?: number | null;
|
|
81
|
+
/** Calendar start of that window. */
|
|
82
|
+
bonus_window_at?: string | null;
|
|
83
|
+
/** How many times this fact was RETURNED by a retrieval leg (local usage stat, still stored). */
|
|
84
|
+
retrieval_count?: number | null;
|
|
85
|
+
last_retrieved_at?: string | null;
|
|
86
|
+
/** Mirror bookkeeping (a fact copied between libraries); informational, restored verbatim. */
|
|
87
|
+
mirror_source?: string | null;
|
|
88
|
+
mirror_target?: string | null;
|
|
89
|
+
/**
|
|
90
|
+
* The DERIVED-STATE stamps. Exported for completeness/diagnostics, but restore deliberately does
|
|
91
|
+
* not trust them: it stamps `entities_version = 0` (so the entity sweep rebuilds) and
|
|
92
|
+
* `conflict_checked = 1` (no corpus-wide conflict re-scan after a rebuild).
|
|
93
|
+
*/
|
|
94
|
+
entities_version?: number | null;
|
|
95
|
+
conflict_checked?: number | null;
|
|
96
|
+
}
|
|
97
|
+
/** The single JSON document `export` writes (and `import`/`restore` read). */
|
|
98
|
+
export interface FactsExportDocument {
|
|
99
|
+
format: typeof FACTS_EXPORT_FORMAT;
|
|
100
|
+
/** The version the document was WRITTEN as (1 or 2); this build writes 2. */
|
|
101
|
+
version: FactsExportVersion;
|
|
102
|
+
exported_at: string;
|
|
103
|
+
source: {
|
|
104
|
+
data_home: string;
|
|
105
|
+
db: string;
|
|
106
|
+
/** The SOURCE database's whole-DB status counts, independent of `--status`. */
|
|
107
|
+
counts: {
|
|
108
|
+
active: number;
|
|
109
|
+
archived: number;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
facts: ExportedFact[];
|
|
113
|
+
}
|
|
114
|
+
/** How v1 import may treat the provenance embedded in the document. */
|
|
115
|
+
export interface ImportOptions {
|
|
116
|
+
/** Plan only: no write, and the report gets `would_import`. */
|
|
117
|
+
dryRun?: boolean;
|
|
118
|
+
/** Fallback provenance when the document has none (the CLI passes `import:<file>`). */
|
|
119
|
+
sourceRef?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Use `sources[0].ref` when present (the default, and the import contract).
|
|
122
|
+
*
|
|
123
|
+
* `merge` turns this OFF: its documented provenance is `merge:<absolute source path>` for every
|
|
124
|
+
* imported row, so a merge must not start attributing rows to whatever the other host recorded.
|
|
125
|
+
*/
|
|
126
|
+
preferEmbeddedSources?: boolean;
|
|
127
|
+
}
|
|
128
|
+
/** One row the store refused; the rest of the import continues. */
|
|
129
|
+
export interface ImportFailure {
|
|
130
|
+
/** The document's `source_id`, or `null` when the row was too malformed to have one. */
|
|
131
|
+
source_id: number | null;
|
|
132
|
+
error: string;
|
|
133
|
+
}
|
|
134
|
+
/** Source id → target id for one fact this invocation actually CREATED. */
|
|
135
|
+
export interface ImportNewId {
|
|
136
|
+
source_id: number | null;
|
|
137
|
+
new_fact_id: number;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Counts of values v1 could not carry, over the facts this invocation imported (or, for a dry run,
|
|
141
|
+
* planned to import). `1` per fact that actually carried the value; `0` when there was nothing to
|
|
142
|
+
* lose. See the module doc for why these are losses rather than silent successes.
|
|
143
|
+
*/
|
|
144
|
+
export interface MetadataDropped {
|
|
145
|
+
timestamps: number;
|
|
146
|
+
trust: number;
|
|
147
|
+
assert_count: number;
|
|
148
|
+
helpful_count: number;
|
|
149
|
+
pinned: number;
|
|
150
|
+
supersedes: number;
|
|
151
|
+
}
|
|
152
|
+
/** The single JSON object `import` prints (`file` is added by the CLI, which knows the argument). */
|
|
153
|
+
export interface ImportReport {
|
|
154
|
+
/** The input form this run detected: an export document. */
|
|
155
|
+
mode: 'json';
|
|
156
|
+
version: number;
|
|
157
|
+
total: number;
|
|
158
|
+
imported: number;
|
|
159
|
+
/** Present on `--dry-run` only; the number of facts that WOULD be written. */
|
|
160
|
+
would_import?: number;
|
|
161
|
+
skipped_existing: number;
|
|
162
|
+
skipped_archived: number;
|
|
163
|
+
failed: ImportFailure[];
|
|
164
|
+
new_ids: ImportNewId[];
|
|
165
|
+
metadata_dropped: MetadataDropped;
|
|
166
|
+
}
|
|
167
|
+
/** The db→db variant's report. Field names kept from the original `merge` output. */
|
|
168
|
+
export interface MergeReport {
|
|
169
|
+
/** The input form this run detected: a SQLite source database. */
|
|
170
|
+
mode: 'db';
|
|
171
|
+
source: string;
|
|
172
|
+
source_active: number;
|
|
173
|
+
added: number;
|
|
174
|
+
would_add: number;
|
|
175
|
+
skipped_existing: number;
|
|
176
|
+
skipped_archived: number;
|
|
177
|
+
failed: {
|
|
178
|
+
source_fact_id: number | null;
|
|
179
|
+
error: string;
|
|
180
|
+
}[];
|
|
181
|
+
new_ids: {
|
|
182
|
+
source_fact_id: number | null;
|
|
183
|
+
new_fact_id: number;
|
|
184
|
+
}[];
|
|
185
|
+
}
|
|
186
|
+
export interface MergeOptions {
|
|
187
|
+
/** Provenance for rows without an embedded source; default `merge:<absolute source path>`. */
|
|
188
|
+
sourceRef?: string;
|
|
189
|
+
/** Plan only: no model warmup, no write, `added` stays 0. */
|
|
190
|
+
dryRun?: boolean;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Export THIS runtime's memory facts. Read-only: the store's own connection is never asked to write
|
|
194
|
+
* (and this command needs no embedding model — the rows are already there).
|
|
195
|
+
*/
|
|
196
|
+
export declare function exportFacts(rt: FactsIoRuntime, options?: {
|
|
197
|
+
status?: ExportStatus;
|
|
198
|
+
}): FactsExportDocument;
|
|
199
|
+
/**
|
|
200
|
+
* The compression containers `export` can write. `none` is plain minified JSON (the format's
|
|
201
|
+
* canonical text, and what stdout always carries).
|
|
202
|
+
*/
|
|
203
|
+
export type FactsExportCodec = 'brotli' | 'gzip' | 'none';
|
|
204
|
+
/**
|
|
205
|
+
* The one serialization. MINIFIED by default: the document is a machine artifact that gets
|
|
206
|
+
* compressed, and indentation only makes the uncompressed fallback bigger. `pretty` exists for
|
|
207
|
+
* debugging (a human reading a diff), never as the default — and it is the ONLY mode that appends
|
|
208
|
+
* the trailing newline that used to be unconditional.
|
|
209
|
+
*/
|
|
210
|
+
export declare function serializeFactsExport(doc: FactsExportDocument, options?: {
|
|
211
|
+
pretty?: boolean;
|
|
212
|
+
}): string;
|
|
213
|
+
/**
|
|
214
|
+
* Serialize + compress in one step. gzip uses level 9; brotli uses quality 11 and tells the encoder
|
|
215
|
+
* the input size, which is the smallest output either library produces for a one-shot export.
|
|
216
|
+
*
|
|
217
|
+
* `codec` defaults to `none` so a caller that says nothing gets the canonical minified JSON.
|
|
218
|
+
*/
|
|
219
|
+
export declare function encodeFactsExport(doc: FactsExportDocument, options?: {
|
|
220
|
+
codec?: FactsExportCodec;
|
|
221
|
+
pretty?: boolean;
|
|
222
|
+
}): Buffer;
|
|
223
|
+
/** The document an encoded file carries, plus the container it actually turned out to be. */
|
|
224
|
+
export interface DecodedFactsExport {
|
|
225
|
+
doc: FactsExportDocument;
|
|
226
|
+
codec: FactsExportCodec;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Whether these bytes are a SQLite database, decided by the FILE HEADER.
|
|
230
|
+
*
|
|
231
|
+
* `mem_admin import` takes either an export document or another host's `memory.db`, and the only
|
|
232
|
+
* honest discriminator is the content: the export containers (plain / gzip / brotli) never start with
|
|
233
|
+
* the SQLite header, and a `.db` file renamed to `.json` still is one. Kept here, beside
|
|
234
|
+
* {@link decodeFactsExport}, so both content sniffers live together.
|
|
235
|
+
*/
|
|
236
|
+
export declare function isSqliteDatabase(bytes: Buffer): boolean;
|
|
237
|
+
/**
|
|
238
|
+
* The same check for a PATH, reading only the 16-byte header.
|
|
239
|
+
*
|
|
240
|
+
* A `memory.db` can be hundreds of MiB; deciding which flow to take must not read it into memory
|
|
241
|
+
* first. The JSON path still reads the whole document (it has to decode it), but the db path hands the
|
|
242
|
+
* path straight to the read-only opener.
|
|
243
|
+
*/
|
|
244
|
+
export declare function isSqliteDatabaseFile(path: string): boolean;
|
|
245
|
+
/**
|
|
246
|
+
* Decode one export file from its BYTES. The file name is deliberately not an input: gzip has a
|
|
247
|
+
* magic header, brotli does not, and a pre-compression export is plain JSON — so the only reliable
|
|
248
|
+
* discriminator is the content itself (a brotli file renamed to `.json` still imports, and a plain
|
|
249
|
+
* JSON file renamed to `.json.br` does too).
|
|
250
|
+
*
|
|
251
|
+
* Order, and why: gzip magic → gunzip (a corrupt gzip is refused as gzip, not silently "JSON");
|
|
252
|
+
* otherwise UTF-8 JSON (the backward-compatible path for every old pretty file); only when JSON
|
|
253
|
+
* parsing fails, brotli (its decoder rejects non-brotli input, so this cannot shadow a JSON
|
|
254
|
+
* document). A document that IS JSON but has the wrong format/version throws its own error right
|
|
255
|
+
* away — {@link parseFactsExport} is the format gate, not a fallback.
|
|
256
|
+
*/
|
|
257
|
+
export declare function decodeFactsExport(bytes: Buffer): DecodedFactsExport;
|
|
258
|
+
/**
|
|
259
|
+
* Resolve the container for a file export: an explicit flag wins, then the extension (`.br` /
|
|
260
|
+
* `.gz`), then plain minified JSON. Shared by the CLI and `/mem` so the two surfaces cannot
|
|
261
|
+
* disagree about what `--out backup.json.gz` means.
|
|
262
|
+
*/
|
|
263
|
+
export declare function codecForExportPath(path: string, explicit?: FactsExportCodec): FactsExportCodec;
|
|
264
|
+
/**
|
|
265
|
+
* Parse and validate an export document TEXT (the stdin entry point). Top-level format errors are
|
|
266
|
+
* FATAL (the caller must write nothing); a malformed individual fact is NOT — {@link importFacts}
|
|
267
|
+
* collects it in `failed` and keeps going. Restore is STRICTER by construction: it validates every
|
|
268
|
+
* row before opening its transaction, because skipping a row would produce a different history.
|
|
269
|
+
*/
|
|
270
|
+
export declare function parseFactsExport(text: string): FactsExportDocument;
|
|
271
|
+
/**
|
|
272
|
+
* Import one export document into `rt`'s memory store.
|
|
273
|
+
*
|
|
274
|
+
* Order of decisions per fact: archived → skipped_archived (v1 never revives); content already in
|
|
275
|
+
* the target (or planned by an earlier row of this same document) → skipped_existing; otherwise the
|
|
276
|
+
* store's add path runs. A row the store refuses is collected in `failed` and the loop continues.
|
|
277
|
+
*/
|
|
278
|
+
export declare function importFacts(rt: FactsIoRuntime, doc: FactsExportDocument, options?: ImportOptions): Promise<ImportReport>;
|
|
279
|
+
/**
|
|
280
|
+
* `merge` — the db→document→import form of the same union.
|
|
281
|
+
*
|
|
282
|
+
* The source is another host's file: opened read-only, never migrated, never written. The document is
|
|
283
|
+
* built in memory and handed to {@link importFacts}, so merge and `import` share one per-fact add
|
|
284
|
+
* path (and one failure-collection rule). Its provenance stays `merge:<absolute source path>` even
|
|
285
|
+
* when the source rows carry their own sources — that is the documented merge contract and the
|
|
286
|
+
* reason {@link ImportOptions.preferEmbeddedSources} can be turned off.
|
|
287
|
+
*/
|
|
288
|
+
export declare function mergeDb(rt: FactsIoRuntime, dbPath: string, options?: MergeOptions): Promise<MergeReport>;
|
|
@@ -21,6 +21,10 @@ export * from './trustHeartbeat.js';
|
|
|
21
21
|
export * from './runtime.js';
|
|
22
22
|
export * from './store/memory.js';
|
|
23
23
|
export * from './store/knowledge.js';
|
|
24
|
+
export { normalizeWrite } from './store/common.js';
|
|
25
|
+
export * from './facts_io.js';
|
|
26
|
+
export * from './maintenance_paths.js';
|
|
27
|
+
export * from './store/facts_restore.js';
|
|
24
28
|
export * from './store/vector_repair.js';
|
|
25
29
|
export * from './store/hybrid.js';
|
|
26
30
|
export * from './store/source_picker.js';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { FactsExportCodec } from './facts_io.js';
|
|
2
|
+
/** Hard cap on a file a maintenance action will read (import / merge / restore_snapshot). */
|
|
3
|
+
export declare const MAINTENANCE_MAX_FILE_BYTES: number;
|
|
4
|
+
/** Where an export lands when the caller does not name a file. */
|
|
5
|
+
export declare const MAINTENANCE_BACKUP_DIRNAME = "backups";
|
|
6
|
+
/**
|
|
7
|
+
* `<data home>/backups/memory-YYYYMMDD-HHMMSS.<ext>` — never inside `memory/`.
|
|
8
|
+
*
|
|
9
|
+
* Brotli is the default because it is the smallest (measured: 70.6 KiB vs 92.3 gzip vs 300.0 plain
|
|
10
|
+
* for 233 facts). A caller that passes another codec keeps the file name truthful by changing the
|
|
11
|
+
* suffix along with the bytes.
|
|
12
|
+
*/
|
|
13
|
+
export declare function defaultFactsExportPath(home: string, now?: Date, codec?: FactsExportCodec): string;
|
|
14
|
+
/** A resolved export destination, plus whether this call will overwrite an existing file. */
|
|
15
|
+
export interface MaintenanceExportTarget {
|
|
16
|
+
file: string;
|
|
17
|
+
existed: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve an export destination inside the data root.
|
|
21
|
+
*
|
|
22
|
+
* The TARGET may not exist yet (that is the normal case), so containment is established on the
|
|
23
|
+
* nearest existing ancestor: every component below it is one this call will create, and a
|
|
24
|
+
* not-yet-created component cannot be a symlink. When the target DOES exist, its realpath is
|
|
25
|
+
* checked — a symlink pointing outside is refused rather than followed.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveMaintenanceExportTarget(home: string, input?: string): MaintenanceExportTarget;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve an INPUT file (import / merge / restore_snapshot) inside the data root.
|
|
30
|
+
*
|
|
31
|
+
* The file must exist, be a regular file, and be no larger than {@link MAINTENANCE_MAX_FILE_BYTES}.
|
|
32
|
+
* A symlink to a regular file INSIDE the root is usable; a symlink that escapes it is refused before
|
|
33
|
+
* the size or existence of the outside target is examined.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveMaintenanceInputFile(home: string, input: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Write a file atomically: a sibling temp file, then one `rename` (same directory, so the rename
|
|
38
|
+
* cannot cross a filesystem). An overwrite is therefore either the old bytes or the new bytes, never
|
|
39
|
+
* a truncated export. The temp file is removed when anything fails.
|
|
40
|
+
*/
|
|
41
|
+
export declare function writeFileAtomic(path: string, bytes: Buffer | string): void;
|
|
@@ -6,6 +6,8 @@ import { type Db } from './db/conn.js';
|
|
|
6
6
|
import { type RelevanceHit } from './store/lexical.js';
|
|
7
7
|
import { MemoryStore } from './store/memory.js';
|
|
8
8
|
import { KnowledgeStore } from './store/knowledge.js';
|
|
9
|
+
import { type FactsExportCodec, type ImportReport, type MergeReport } from './facts_io.js';
|
|
10
|
+
import type { RestoreReport } from './store/facts_restore.js';
|
|
9
11
|
export interface RuntimeOptions {
|
|
10
12
|
dataHome?: string;
|
|
11
13
|
memoryDbPath?: string;
|
|
@@ -35,6 +37,22 @@ export interface RuntimeOptions {
|
|
|
35
37
|
readonly tools?: string;
|
|
36
38
|
readonly models?: string;
|
|
37
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Skip the adapters' eager constructor warm even when the caller passes no {@link managedRoots}:
|
|
42
|
+
* the caller owns the warm schedule and will warm explicitly.
|
|
43
|
+
*
|
|
44
|
+
* There are exactly TWO ways to get a deferred warm, and both are requests of the same thing:
|
|
45
|
+
*
|
|
46
|
+
* - {@link managedRoots} — the DSH plugin's path. The framework owns the model root and may still
|
|
47
|
+
* be installing into it, so the backends must not warm themselves (that would race the install
|
|
48
|
+
* and fetch a second copy).
|
|
49
|
+
* - `deferWarm: true` — the CLI's path. The flag exists because the CLI is one-shot and its
|
|
50
|
+
* READ-ONLY commands (`stats`, `list`, `export`, …) must not load or download the model at all;
|
|
51
|
+
* it warms explicitly, and only from `needsModels` (the command table in `cli/src/index.ts`).
|
|
52
|
+
*
|
|
53
|
+
* A first `encode()` still warms lazily, so deferring never costs the commands that do encode.
|
|
54
|
+
*/
|
|
55
|
+
deferWarm?: boolean;
|
|
38
56
|
}
|
|
39
57
|
/**
|
|
40
58
|
* The awaited result of a store method — what a dispatch overload promises.
|
|
@@ -65,6 +83,28 @@ export type DispatchError = {
|
|
|
65
83
|
* admin payload no store method produces, because the runtime composes it.
|
|
66
84
|
*/
|
|
67
85
|
export type AdminStatsResult = StatsSummary;
|
|
86
|
+
/**
|
|
87
|
+
* `admin export`: the file THIS call wrote and its container. Deliberately carries NO document body
|
|
88
|
+
* — the model gets a receipt, not the library (the file is the artifact; whoever needs the facts
|
|
89
|
+
* reads it with the CLI or imports it back).
|
|
90
|
+
*/
|
|
91
|
+
export interface AdminExportReport {
|
|
92
|
+
file: string;
|
|
93
|
+
codec: FactsExportCodec;
|
|
94
|
+
bytes: number;
|
|
95
|
+
plain_bytes: number;
|
|
96
|
+
counts: {
|
|
97
|
+
active: number;
|
|
98
|
+
archived: number;
|
|
99
|
+
};
|
|
100
|
+
/** True when an existing file was replaced (the atomic write is the same either way). */
|
|
101
|
+
overwritten: boolean;
|
|
102
|
+
}
|
|
103
|
+
/** `admin restore_snapshot`: the engine's restore report plus the container it came from. */
|
|
104
|
+
export type AdminRestoreSnapshotReport = RestoreReport & {
|
|
105
|
+
file: string;
|
|
106
|
+
codec: FactsExportCodec;
|
|
107
|
+
};
|
|
68
108
|
/**
|
|
69
109
|
* The payload of the four hits-returning recall actions, which one overload groups because a
|
|
70
110
|
* caller may hold them as a union (`action: 'chain' | 'reason'`).
|
|
@@ -119,9 +159,10 @@ export type RecallDispatch = {
|
|
|
119
159
|
(req: RecallRequest): Promise<unknown>;
|
|
120
160
|
};
|
|
121
161
|
/**
|
|
122
|
-
* `admin` is the widest dispatcher: counts, a fact page, a detail, flags,
|
|
123
|
-
* reports
|
|
124
|
-
*
|
|
162
|
+
* `admin` is the widest dispatcher: counts, a fact page, a detail, flags, five diagnostic
|
|
163
|
+
* reports, and the four data-root maintenance actions (export / import / merge / restore_snapshot).
|
|
164
|
+
* `ReturnType<…>` keeps each member tied to the store method that actually produces it, so a change
|
|
165
|
+
* there propagates instead of leaving callers with a cast they invented.
|
|
125
166
|
*/
|
|
126
167
|
export type AdminDispatch = {
|
|
127
168
|
(req: Extract<AdminRequest, {
|
|
@@ -163,6 +204,19 @@ export type AdminDispatch = {
|
|
|
163
204
|
(req: Extract<AdminRequest, {
|
|
164
205
|
action: 'maintenance';
|
|
165
206
|
}>): StoreResult<MemoryStore, 'maintenance'>;
|
|
207
|
+
(req: Extract<AdminRequest, {
|
|
208
|
+
action: 'export';
|
|
209
|
+
}>): AdminExportReport;
|
|
210
|
+
/** Content-detected: a SQLite source answers with the db report, an export document with the JSON one. */
|
|
211
|
+
(req: Extract<AdminRequest, {
|
|
212
|
+
action: 'import';
|
|
213
|
+
}>): Promise<ImportReport | MergeReport>;
|
|
214
|
+
(req: Extract<AdminRequest, {
|
|
215
|
+
action: 'merge';
|
|
216
|
+
}>): Promise<MergeReport>;
|
|
217
|
+
(req: Extract<AdminRequest, {
|
|
218
|
+
action: 'restore_snapshot';
|
|
219
|
+
}>): Promise<AdminRestoreSnapshotReport>;
|
|
166
220
|
(req: AdminRequest): unknown;
|
|
167
221
|
};
|
|
168
222
|
/**
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-fidelity REBUILD of a memory library from an export document — the ONE owner of
|
|
3
|
+
* `avantf-mem restore <file>`.
|
|
4
|
+
*
|
|
5
|
+
* RESTORE IS NOT IMPORT. `import` / `merge` (see `facts_io.ts`) are CONTENT-IDENTITY UNIONS: new
|
|
6
|
+
* rows get new ids, only ACTIVE facts travel, and every value the document carries beyond
|
|
7
|
+
* category/ttl/sources/validity is counted as dropped. Restore is the opposite promise — "put this
|
|
8
|
+
* library back exactly as it was" — which is only answerable if the target is EMPTY:
|
|
9
|
+
*
|
|
10
|
+
* - `facts` is written DIRECTLY, one transaction, keeping `fact_id`, status, timestamps,
|
|
11
|
+
* `trust_score`, `settle_clock`, counters, pin state, TTL, validity, the supersedes pointer and
|
|
12
|
+
* the mirror bookkeeping. A non-empty target is REFUSED (never merged into), because "restore"
|
|
13
|
+
* that silently unions two histories is a different, unreviewable operation.
|
|
14
|
+
* - Rows are inserted in `source_id` ASCENDING order, so the row a `supersedes` pointer names
|
|
15
|
+
* already exists when the pointer is written. v1 documents carry the same pointer as
|
|
16
|
+
* `supersedes_source_id`, so both versions resolve identically.
|
|
17
|
+
* - `fact_sources` is restored IN FULL (a fact may carry several rows); the identity `import`
|
|
18
|
+
* is forced to collapse into one `ref` string is not a limitation restore inherits.
|
|
19
|
+
* - FTS is NOT written by hand. `facts_fts` is an external-content FTS5 table with AFTER INSERT /
|
|
20
|
+
* DELETE / UPDATE triggers (`db/schema.ts`), so inserting into `facts` indexes the row. That is
|
|
21
|
+
* the whole reason this path can bypass `MemoryStore.add` without losing lexical recall.
|
|
22
|
+
* - The DERIVED state is re-derived, not restored. `entities_version = 0` marks every row for the
|
|
23
|
+
* entity sweep, which rebuilds `fact_entities`, `triples` and the HRR bundle in bounded passes
|
|
24
|
+
* after the commit; `conflict_checked = 1` keeps a restored corpus from queueing a whole-library
|
|
25
|
+
* conflict re-scan (the log is not exported — see below).
|
|
26
|
+
* - VECTORS ARE NOT RESTORED (`semantic_vector`, `embedding_model` stay NULL, `hrr_vector` stays
|
|
27
|
+
* NULL until the sweep writes it). They are machine-local encodings of a specific model revision;
|
|
28
|
+
* copying them between hosts would create a vector space the live index cannot serve. The report
|
|
29
|
+
* says so and points at `avantf-mem vectors --fix` / a host restart.
|
|
30
|
+
*
|
|
31
|
+
* WHAT IS DELIBERATELY NOT RESTORED: `contradiction_log`, the retrieval-stat meta table, `tags`
|
|
32
|
+
* (not exported), and `archived_clock`. The last one is a TRUST-CLOCK value ("active days at archive
|
|
33
|
+
* time"); the document only carries `archived_at` (a wall-clock timestamp), and converting one into
|
|
34
|
+
* the other would invent data. Leaving it NULL sends restored archived rows down
|
|
35
|
+
* `purgeArchived`'s CALENDAR branch, which reads the restored `archived_at` — the honest
|
|
36
|
+
* approximation, and the reason a restore does not restart every purge countdown at zero.
|
|
37
|
+
*
|
|
38
|
+
* V1 DOCUMENTS ARE READ. v2 only ADDED optional facts columns, so a v1 row simply lacks them; each
|
|
39
|
+
* absence is counted per field in `metadata_missing` and resolved with the documented fallback. The
|
|
40
|
+
* one fallback with a real decision behind it: `settle_clock` becomes the CURRENT active-day clock.
|
|
41
|
+
* Zero would make every restored fact decay by the host's whole clock in one settlement; the current
|
|
42
|
+
* clock is the "no decay has happened yet" baseline, which is what a fresh restore means.
|
|
43
|
+
*
|
|
44
|
+
* @module store/facts_restore
|
|
45
|
+
*/
|
|
46
|
+
import type { FactsExportDocument } from '../facts_io.js';
|
|
47
|
+
import type { Db } from '../db/conn.js';
|
|
48
|
+
import { type EntitySweepReport } from './entity_sweep.js';
|
|
49
|
+
/** The one operator hint about the data restore cannot carry. */
|
|
50
|
+
export declare const RESTORE_VECTOR_HINT: string;
|
|
51
|
+
/**
|
|
52
|
+
* Every metadata field the report accounts for.
|
|
53
|
+
*
|
|
54
|
+
* The list is the DOCUMENT's, not the table's: it includes the v2 additions (so a v1 file shows
|
|
55
|
+
* exactly which columns it could not carry) and the derived-state stamps (which restore re-derives
|
|
56
|
+
* rather than trust). A field counts as missing when the document does not carry the key at all —
|
|
57
|
+
* `null` is a value the document states ("unknown"), not an absence.
|
|
58
|
+
*/
|
|
59
|
+
export declare const RESTORE_METADATA_FIELDS: readonly ["category", "created_at", "updated_at", "trust_score", "pinned", "pinned_at", "assert_count", "helpful_count", "last_reinforced_at", "ttl_days", "valid_from", "valid_to", "supersedes_source_id", "archive", "sources", "settle_clock", "bonus_count", "bonus_window_at", "retrieval_count", "last_retrieved_at", "mirror_source", "mirror_target", "entities_version", "conflict_checked"];
|
|
60
|
+
export type RestoreMetadataField = (typeof RESTORE_METADATA_FIELDS)[number];
|
|
61
|
+
/** Per-field count of facts whose document did not carry that field. */
|
|
62
|
+
export type RestoreMetadataMissing = Record<RestoreMetadataField, number>;
|
|
63
|
+
export interface RestoreOptions {
|
|
64
|
+
/** Plan only: no write, no entity rebuild; the report carries `would_restore` + `target_facts`. */
|
|
65
|
+
dryRun?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The store's half of the contract, narrow on purpose: the ONE write connection, plus the bounded
|
|
69
|
+
* entity sweep this flow reuses after the commit. Nothing here knows about `MemoryStore`.
|
|
70
|
+
*/
|
|
71
|
+
export interface RestoreTarget {
|
|
72
|
+
db: Db;
|
|
73
|
+
reindexEntities(budget?: number): Promise<EntitySweepReport>;
|
|
74
|
+
}
|
|
75
|
+
/** How many facts a document would restore, split by status. */
|
|
76
|
+
export interface RestoreCounts {
|
|
77
|
+
total: number;
|
|
78
|
+
active: number;
|
|
79
|
+
archived: number;
|
|
80
|
+
}
|
|
81
|
+
/** The single JSON object the CLI prints (it adds `file` / `codec`, which are argv facts). */
|
|
82
|
+
export interface RestoreReport {
|
|
83
|
+
/** The DOCUMENT's version, echoed as-is (1 or 2). */
|
|
84
|
+
version: number;
|
|
85
|
+
/** Present on `--dry-run` only. */
|
|
86
|
+
dry_run?: true;
|
|
87
|
+
/** Present on `--dry-run` only: what a real run would write. */
|
|
88
|
+
would_restore?: RestoreCounts;
|
|
89
|
+
/**
|
|
90
|
+
* Present on `--dry-run` only: the target's CURRENT fact count. Non-zero means the real run would
|
|
91
|
+
* be refused (restore only rebuilds into an empty table).
|
|
92
|
+
*/
|
|
93
|
+
target_facts?: number;
|
|
94
|
+
/** Facts actually written (0 on a dry run). */
|
|
95
|
+
restored: number;
|
|
96
|
+
active: number;
|
|
97
|
+
archived: number;
|
|
98
|
+
metadata_missing: RestoreMetadataMissing;
|
|
99
|
+
/** FTS5 rows after the commit — the trigger's work, asserted equal to `restored` by the tests. */
|
|
100
|
+
fts_rows: number;
|
|
101
|
+
entities: EntitySweepReport;
|
|
102
|
+
vectors: {
|
|
103
|
+
restored: 0;
|
|
104
|
+
hint: string;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Rebuild the target library from `doc`.
|
|
109
|
+
*
|
|
110
|
+
* Order of guarantees: the document is validated COMPLETELY first (no writes on a bad file), then one
|
|
111
|
+
* `BEGIN IMMEDIATE` transaction re-checks emptiness and inserts facts + sources (a failure anywhere
|
|
112
|
+
* rolls the whole thing back), and only after that commit does the derived state get rebuilt. The
|
|
113
|
+
* entity sweep is deliberately OUTSIDE the transaction: it is bounded, resumable work, and holding
|
|
114
|
+
* the write lock for a whole-corpus re-extraction is exactly what its batch exists to avoid.
|
|
115
|
+
*/
|
|
116
|
+
export declare function restoreFactsDocument(target: RestoreTarget, doc: FactsExportDocument, options?: RestoreOptions): Promise<RestoreReport>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
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';
|
|
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';
|
|
5
|
+
import type { FactsExportDocument } from '../facts_io.js';
|
|
4
6
|
import { DEFAULT_VECTOR_MIGRATION_BATCH, type VectorRepairTarget } from './vector_repair.js';
|
|
5
7
|
import type { Db } from '../db/conn.js';
|
|
6
8
|
import { type LexicalProbe } from './lexical.js';
|
|
@@ -487,6 +489,20 @@ export declare class MemoryStore {
|
|
|
487
489
|
* selection (`facts.staleEntityRows`, ACTIVE facts only) and the per-fact write below.
|
|
488
490
|
*/
|
|
489
491
|
reindexEntities(budget?: number): Promise<EntitySweepReport>;
|
|
492
|
+
/**
|
|
493
|
+
* Rebuild this library from an export document — the engine half of `avantf-mem restore <file>`.
|
|
494
|
+
*
|
|
495
|
+
* A THIN adapter on purpose: the empty-table rule, the insert order, the SQL and the report all
|
|
496
|
+
* live in `store/facts_restore.ts`, because this class is a hub and a rebuild of the whole facts
|
|
497
|
+
* table must not become another reason to grow it. What it contributes is the two things only the
|
|
498
|
+
* store has: the ONE write connection (so the restored rows are immediately visible to every
|
|
499
|
+
* reader here) and the derived-state entity sweep that runs after the commit.
|
|
500
|
+
*
|
|
501
|
+
* Reachable from `mem_admin restore_snapshot` (and from the CLI): after a real restore this
|
|
502
|
+
* method refreshes the live vector index and resets the conflict-drain cursor, so a host that
|
|
503
|
+
* rebuilt its library from a tool call answers from the NEW rows without a restart.
|
|
504
|
+
*/
|
|
505
|
+
restoreFacts(doc: FactsExportDocument, options?: RestoreOptions): Promise<RestoreReport>;
|
|
490
506
|
/**
|
|
491
507
|
* A fact left the ACTIVE corpus: retire the open conflicts that name it, so
|
|
492
508
|
* `contradict` never reports a pair whose revision is archived and repeated
|