@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.
- package/README.md +19 -10
- package/lib/client.js +1665 -3466
- package/lib/dsh-build.json +6 -5
- package/lib/engine/graph-recall/in-memory.d.ts +93 -0
- package/lib/engine/graph-recall/index.d.ts +22 -0
- package/lib/engine/graph-recall/traverse.d.ts +119 -0
- package/lib/engine/graph-recall/types.d.ts +188 -0
- package/lib/engine/mem/config/config_files.d.ts +1 -1
- package/lib/engine/mem/config/loader.d.ts +2 -3
- package/lib/engine/mem/config/paths.d.ts +2 -13
- package/lib/engine/mem/db/dao/contradictions.d.ts +4 -22
- package/lib/engine/mem/db/dao/entities.d.ts +80 -7
- package/lib/engine/mem/db/dao/fact_sources.d.ts +3 -13
- package/lib/engine/mem/db/dao/facts.d.ts +311 -56
- package/lib/engine/mem/db/dao/triples.d.ts +1 -26
- package/lib/engine/mem/db/graph.d.ts +249 -0
- package/lib/engine/mem/db/journal_notes.d.ts +35 -0
- package/lib/engine/mem/db/migrations.d.ts +9 -0
- package/lib/engine/mem/db/port.d.ts +22 -3
- package/lib/engine/mem/db/schema.d.ts +119 -0
- package/lib/engine/mem/db/tokenizer.d.ts +9 -0
- package/lib/engine/mem/dbdrivers/fake/driver.d.ts +75 -0
- package/lib/engine/mem/dbdrivers/fake/index.d.ts +7 -0
- package/lib/engine/mem/dbdrivers/fake/surface.d.ts +34 -0
- package/lib/engine/mem/dbdrivers/fake/transport.d.ts +61 -0
- package/lib/engine/mem/dbdrivers/sqlite/connection.d.ts +14 -0
- package/lib/engine/mem/dbdrivers/sqlite/contradictions.d.ts +35 -0
- package/lib/engine/mem/dbdrivers/sqlite/dao/index.d.ts +16 -0
- package/lib/engine/mem/dbdrivers/sqlite/driver.d.ts +54 -0
- package/lib/engine/mem/dbdrivers/sqlite/facts.d.ts +117 -0
- package/lib/engine/mem/dbdrivers/sqlite/index.d.ts +6 -0
- package/lib/engine/mem/dbdrivers/sqlite/migrations.d.ts +15 -0
- package/lib/engine/mem/dbdrivers/sqlite/relations.d.ts +62 -0
- package/lib/engine/mem/dbdrivers/sqlite/search.d.ts +56 -0
- package/lib/engine/mem/dbdrivers/sqlite/sources.d.ts +37 -0
- package/lib/engine/mem/defaults.d.ts +3 -4
- package/lib/engine/mem/dispatch.d.ts +7 -32
- package/lib/engine/mem/entities/extract.d.ts +44 -47
- package/lib/engine/mem/eval/derived_runner.d.ts +6 -1
- package/lib/engine/mem/eval/relation_gold.d.ts +251 -0
- package/lib/engine/mem/eval/write_metrics.d.ts +1 -1
- package/lib/engine/mem/facts_io.d.ts +328 -0
- package/lib/engine/mem/index.d.ts +10 -2
- package/lib/engine/mem/interfaces/capabilities.d.ts +30 -0
- package/lib/engine/mem/interfaces/common.d.ts +11 -0
- package/lib/engine/mem/interfaces/errors.d.ts +65 -0
- package/lib/engine/mem/interfaces/index.d.ts +18 -0
- package/lib/engine/mem/interfaces/migrations.d.ts +21 -0
- package/lib/engine/mem/interfaces/repositories.d.ts +471 -0
- package/lib/engine/mem/interfaces/search_index.d.ts +113 -0
- package/lib/engine/mem/interfaces/storage_backend.d.ts +84 -0
- package/lib/engine/mem/interfaces/storage_driver.d.ts +43 -0
- package/lib/engine/mem/interfaces/version.d.ts +34 -0
- package/lib/engine/mem/lifecycle/contradiction.d.ts +2 -2
- package/lib/engine/mem/maintenance_paths.d.ts +41 -0
- package/lib/engine/mem/modelBootstrap.d.ts +1 -1
- package/lib/engine/mem/relations/confidence.d.ts +39 -0
- package/lib/engine/mem/relations/extract.d.ts +128 -0
- package/lib/engine/mem/relations/index.d.ts +32 -0
- package/lib/engine/mem/relations/name-quality.d.ts +78 -0
- package/lib/engine/mem/relations/normalize.d.ts +275 -0
- package/lib/engine/mem/relations/patterns.d.ts +67 -0
- package/lib/engine/mem/relations/statement.d.ts +88 -0
- package/lib/engine/mem/relations/types.d.ts +79 -0
- package/lib/engine/mem/runtime.d.ts +117 -90
- package/lib/engine/mem/storage/create.d.ts +42 -0
- package/lib/engine/mem/storage/errors.d.ts +65 -0
- package/lib/engine/mem/storage/index.d.ts +15 -0
- package/lib/engine/mem/storage/ports.d.ts +472 -0
- package/lib/engine/mem/storage/registry.d.ts +37 -0
- package/lib/engine/mem/storage/sqlite.d.ts +28 -0
- package/lib/engine/mem/store/common.d.ts +1 -1
- package/lib/engine/mem/store/entity_leg.d.ts +59 -2
- package/lib/engine/mem/store/entity_sweep.d.ts +4 -3
- package/lib/engine/mem/store/explicit_relations.d.ts +52 -0
- package/lib/engine/mem/store/facts_restore.d.ts +124 -0
- package/lib/engine/mem/store/floors.d.ts +4 -1
- package/lib/engine/mem/store/graph_reader.d.ts +146 -0
- package/lib/engine/mem/store/hybrid.d.ts +50 -13
- package/lib/engine/mem/store/kinds.d.ts +47 -0
- package/lib/engine/mem/store/legs.d.ts +24 -29
- package/lib/engine/mem/store/lexical.d.ts +36 -45
- package/lib/engine/mem/store/memory.d.ts +486 -82
- package/lib/engine/mem/store/recall_policy.d.ts +49 -0
- package/lib/engine/mem/store/relation_backfill.d.ts +210 -0
- package/lib/engine/mem/store/relation_planner.d.ts +191 -0
- package/lib/engine/mem/store/secret_guard.d.ts +0 -1
- package/lib/engine/mem/store/self_query.d.ts +4 -2
- package/lib/engine/mem/store/time_filter.d.ts +121 -0
- package/lib/engine/mem/store/titles.d.ts +52 -0
- package/lib/engine/mem/store/vector_repair.d.ts +44 -54
- package/lib/engine/mem-contract/config.d.ts +44 -38
- package/lib/engine/mem-contract/text.d.ts +1 -1
- package/lib/engine/mem-contract/tools.d.ts +184 -117
- package/lib/engine/mem-contract/types.d.ts +364 -288
- package/lib/index.js +13898 -11500
- package/lib/interface-version.json +1 -1
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/paging.d.ts +3 -12
- package/lib/types/client/paging.d.ts.map +1 -1
- package/lib/types/client/summarize.d.ts.map +1 -1
- package/lib/types/command.d.ts +61 -0
- package/lib/types/command.d.ts.map +1 -0
- package/lib/types/envinit-bootstrap.d.ts +1 -1
- package/lib/types/envinit.d.ts +2 -6
- package/lib/types/envinit.d.ts.map +1 -1
- package/lib/types/hints.d.ts +28 -24
- package/lib/types/hints.d.ts.map +1 -1
- package/lib/types/index.d.ts +21 -40
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/inject.d.ts +3 -2
- package/lib/types/inject.d.ts.map +1 -1
- package/lib/types/interface_gate.d.ts +9 -9
- package/lib/types/prompt.d.ts +17 -41
- package/lib/types/prompt.d.ts.map +1 -1
- package/lib/types/provision.d.ts +3 -0
- package/lib/types/provision.d.ts.map +1 -1
- package/lib/types/remote.d.ts +23 -9
- package/lib/types/remote.d.ts.map +1 -1
- package/lib/types/render.d.ts +3 -3
- package/lib/types/tool_schema.d.ts +4 -0
- package/lib/types/tool_schema.d.ts.map +1 -1
- package/lib/types/vectorMigration.d.ts +5 -7
- package/lib/types/vectorMigration.d.ts.map +1 -1
- package/package.json +16 -15
- package/lib/engine/mem/config/domains.d.ts +0 -20
- package/lib/engine/mem/db/dao/chunks.d.ts +0 -231
- package/lib/engine/mem/db/dao/documents.d.ts +0 -51
- package/lib/engine/mem/db/hash.d.ts +0 -2
- package/lib/engine/mem/db/knowledge.d.ts +0 -17
- package/lib/engine/mem/eval/index.d.ts +0 -4
- package/lib/engine/mem/git.d.ts +0 -73
- package/lib/engine/mem/router.d.ts +0 -51
- package/lib/engine/mem/store/doc_files.d.ts +0 -125
- package/lib/engine/mem/store/document_text.d.ts +0 -47
- package/lib/engine/mem/store/ingest_guard.d.ts +0 -25
- package/lib/engine/mem/store/knowledge.d.ts +0 -598
- package/lib/engine/mem/store/source_picker.d.ts +0 -43
- package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +0 -52
- package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +0 -10
- package/lib/engine/mem-retrieval/rerank.d.ts +0 -13
- package/lib/types/client/domains.d.ts +0 -53
- package/lib/types/client/domains.d.ts.map +0 -1
- package/lib/types/client/ingest.d.ts +0 -24
- package/lib/types/client/ingest.d.ts.map +0 -1
- package/lib/types/open.d.ts +0 -77
- package/lib/types/open.d.ts.map +0 -1
- package/lib/types/reconcile.d.ts +0 -64
- package/lib/types/reconcile.d.ts.map +0 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `triples` — the SPO graph behind
|
|
3
|
-
* the supersede cleanup.
|
|
2
|
+
* `triples` — the SPO graph behind the structural contradiction signals and the supersede cleanup.
|
|
4
3
|
*
|
|
5
4
|
* The two structural lookups are exported as SQL builders so that the statement whose QUERY PLAN
|
|
6
5
|
* matters (they must seek a triples index and never scan) is the very one production runs, not a
|
|
@@ -25,12 +24,6 @@ interface TripleRow {
|
|
|
25
24
|
export declare function polarityLookupSql(predCount: number): string;
|
|
26
25
|
/** Structural lookup ②: same (subj, pred), different object. Same rewrite as above. */
|
|
27
26
|
export declare function sameSubjPredLookupSql(): string;
|
|
28
|
-
/** The optional `subj`/`pred`/`obj` slots an `ask` pattern may fill. */
|
|
29
|
-
interface TriplePatternParts {
|
|
30
|
-
subj?: string;
|
|
31
|
-
pred?: string;
|
|
32
|
-
obj?: string;
|
|
33
|
-
}
|
|
34
27
|
export declare class TriplesDao {
|
|
35
28
|
private readonly db;
|
|
36
29
|
constructor(db: Db);
|
|
@@ -58,23 +51,5 @@ export declare class TriplesDao {
|
|
|
58
51
|
findSameSubjPredOtherObj(subj: string, pred: string, obj: string, excludeFactId: number): {
|
|
59
52
|
fact_id: number;
|
|
60
53
|
}[];
|
|
61
|
-
/**
|
|
62
|
-
* First hop of `chain`: the objects reachable from `subj` (optionally via one predicate).
|
|
63
|
-
*
|
|
64
|
-
* JOIN, not `fact_id IN (SELECT … WHERE status='active')` — the same shape {@link polarityLookupSql}
|
|
65
|
-
* documents as a measured 202 ms on a 300k-triple corpus with zero matches, because the subquery
|
|
66
|
-
* materializes EVERY active fact id per call. The chain path paid that on every hop.
|
|
67
|
-
*/
|
|
68
|
-
objectsForSubject(subj: string, pred?: string): string[];
|
|
69
|
-
/**
|
|
70
|
-
* Second hop of `chain`: active facts whose subject is one of the mid names.
|
|
71
|
-
*
|
|
72
|
-
* JOIN for the same reason as {@link objectsForSubject}.
|
|
73
|
-
*/
|
|
74
|
-
activeFactsBySubject(subjs: readonly string[], secondPred?: string): number[];
|
|
75
|
-
/** Active facts whose triple matches every given slot exactly (`ask`, exact layer). */
|
|
76
|
-
activeFactsMatchingExact(parts: TriplePatternParts): number[];
|
|
77
|
-
/** Same, with `=` relaxed to `LIKE %term%` (`ask`, recall-recovery layer). */
|
|
78
|
-
activeFactsMatchingLike(parts: TriplePatternParts): number[];
|
|
79
54
|
}
|
|
80
55
|
export {};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { Db } from './port.js';
|
|
2
|
+
import type { EdgeReextractOptions, EdgeReextractReport, EdgeRebuildReport, EdgeSourceTriple, EdgeWriteReport, GraphCoverageReport } from '../interfaces/repositories.js';
|
|
3
|
+
export type { EdgeDropReason, EdgeDropSample, EdgeRebuildReport, EdgeReextractMode, EdgeReextractOptions, EdgeReextractReport, EdgeSourceTriple, EdgeVersionCount, EdgeWriteReport, GraphCoverageReport, } from '../interfaces/repositories.js';
|
|
4
|
+
/**
|
|
5
|
+
* `edges.extraction_version` for rows converted from the legacy string `triples`.
|
|
6
|
+
*
|
|
7
|
+
* 0 = "no versioned edge extractor produced this" — the same meaning the value has for
|
|
8
|
+
* `facts.entities_version` (a row from before the rules were versioned). Phase 2's extractor stamps
|
|
9
|
+
* 1 and up, so a re-extraction pass selects these rows with `extraction_version < :current` instead
|
|
10
|
+
* of needing to know which migration wrote them.
|
|
11
|
+
*/
|
|
12
|
+
export declare const LEGACY_EDGE_EXTRACTION_VERSION = 0;
|
|
13
|
+
/** One edge as the graph reads it: IDs for walkers, names for a result payload. */
|
|
14
|
+
export interface GraphEdgeRow {
|
|
15
|
+
edge_id: number;
|
|
16
|
+
fact_id: number;
|
|
17
|
+
subject_id: number;
|
|
18
|
+
predicate_id: number;
|
|
19
|
+
object_id: number;
|
|
20
|
+
subject: string;
|
|
21
|
+
predicate: string;
|
|
22
|
+
object: string;
|
|
23
|
+
confidence: number;
|
|
24
|
+
source: string;
|
|
25
|
+
extraction_version: number;
|
|
26
|
+
}
|
|
27
|
+
/** What one schema+backfill pass did (0 for every `*Created` counter on a repeated run). */
|
|
28
|
+
export interface RelationalGraphReport {
|
|
29
|
+
/** Legacy `triples` rows seen. */
|
|
30
|
+
triples: number;
|
|
31
|
+
/** Of those, rows skipped because a slot is blank (no node is invented for `''`). */
|
|
32
|
+
skipped: number;
|
|
33
|
+
/** `entities` rows this run created. */
|
|
34
|
+
entitiesCreated: number;
|
|
35
|
+
/** `predicates` rows this run created. */
|
|
36
|
+
predicatesCreated: number;
|
|
37
|
+
/** `edges` rows this run created. */
|
|
38
|
+
edgesCreated: number;
|
|
39
|
+
/** `edges` rows in the table after the run. */
|
|
40
|
+
edges: number;
|
|
41
|
+
}
|
|
42
|
+
/** `CREATE … IF NOT EXISTS` for the four tables and their indexes — then the backfill. */
|
|
43
|
+
export declare function applyRelationalGraphSchema(db: Db): RelationalGraphReport;
|
|
44
|
+
/**
|
|
45
|
+
* Convert `triples` → `edges` (idempotent; the tables must already exist).
|
|
46
|
+
*
|
|
47
|
+
* Kept separate from {@link applyRelationalGraphSchema} so a re-run can never be confused with a
|
|
48
|
+
* schema change: this function only inserts rows, and it reports how many it actually inserted.
|
|
49
|
+
*
|
|
50
|
+
* Only NOTE triples are converted (STRUCTURED_RELATIONS §四). The report's `triples`/`skipped`
|
|
51
|
+
* counters still describe every legacy row the conversion looked at, so "the conversion saw N rows
|
|
52
|
+
* but created fewer edges" stays explainable; `edgesCreated` is what a re-run can be compared against.
|
|
53
|
+
*/
|
|
54
|
+
export declare function backfillEdges(db: Db): RelationalGraphReport;
|
|
55
|
+
/**
|
|
56
|
+
* One fact's edges, resolved back to names — the evidence contract of R1/R4 ("every edge can be
|
|
57
|
+
* traced back to its `fact_id`").
|
|
58
|
+
*
|
|
59
|
+
* The IDs are returned next to the names because a path walker joins on them; the names are what a
|
|
60
|
+
* `RelationPath` payload shows. `ORDER BY edge_id` is insertion order, i.e. stable across reads and
|
|
61
|
+
* deterministic in tests.
|
|
62
|
+
*/
|
|
63
|
+
export declare function edgesForFact(db: Db, factId: number): GraphEdgeRow[];
|
|
64
|
+
/**
|
|
65
|
+
* One fact's EXPLICIT relations, resolved back to their three stored names
|
|
66
|
+
* (STRUCTURED_RELATIONS §2) — the read model behind `FactDetail.relations`, the v5 export and the
|
|
67
|
+
* restore write.
|
|
68
|
+
*
|
|
69
|
+
* Only `source = 'explicit'` rows are returned: an extracted edge is derivable from the fact text
|
|
70
|
+
* and is not something a caller stated, so it must not appear as a declared relation. `ORDER BY
|
|
71
|
+
* edge_id` is insertion order (the order the caller listed them, minus the four-tuple duplicates),
|
|
72
|
+
* which is what makes an export→restore round trip compare equal.
|
|
73
|
+
*
|
|
74
|
+
* A journal has no edges at all (§四), so this returns `[]` for one without a second query.
|
|
75
|
+
*/
|
|
76
|
+
export declare function explicitRelationsForFact(db: Db, factId: number): {
|
|
77
|
+
subject: string;
|
|
78
|
+
predicate: string;
|
|
79
|
+
object: string;
|
|
80
|
+
}[];
|
|
81
|
+
/**
|
|
82
|
+
* `edges.extraction_version` for rows THIS module's extractor wrote (Phase 2, R2/R3).
|
|
83
|
+
*
|
|
84
|
+
* 1 is the first versioned rule set: alias/canonical normalization of both entity slots and of the
|
|
85
|
+
* predicate, the confidence policy below, and the direction rule. `LEGACY_EDGE_EXTRACTION_VERSION`
|
|
86
|
+
* (0) stays what the migration wrote, so `reextractEdges` selects "older than me" without knowing
|
|
87
|
+
* which code path produced a row. Bump this whenever a change here would produce different edges for
|
|
88
|
+
* the same triples (the same contract `ENTITY_EXTRACTOR_VERSION` has).
|
|
89
|
+
*
|
|
90
|
+
* 2 (Phase 5b, docs/RELATIONAL_RECALL.md §十七) adds the two QUALITY gates every edge now passes:
|
|
91
|
+
* - `relations/name-quality.ts#graphNameIssue`: both slots must be bounded, punctuation-free names.
|
|
92
|
+
* This is what removes the fragments (`)。**`, `,仓库…`) that Phase 5a measured in 69 of 87 live
|
|
93
|
+
* node names;
|
|
94
|
+
* - `relations/statement.ts#isStatedRelation`: a triple the text extractor produced (`source: 'svo'`)
|
|
95
|
+
* must appear in the fact text as one clause. On the 2026-10-25 live snapshot NONE of the 48
|
|
96
|
+
* curated triples the v1 policy had accepted is a stated clause, so every edge v1 wrote was a
|
|
97
|
+
* fragment.
|
|
98
|
+
*
|
|
99
|
+
* 3 (2026-10-25 relation review, docs/REVIEW-RELATION-EXTRACTION.md) — what changes the EDGE SET for
|
|
100
|
+
* the same stored triples:
|
|
101
|
+
* - `合作` is a `symmetric` predicate (`relations/normalize.ts`), so a walk from either end of that
|
|
102
|
+
* family answers (a WRITE is unchanged: one edge, sentence order);
|
|
103
|
+
* - the curated vocabulary gained reachable surfaces, so the triples `reextractEdges` re-derives are
|
|
104
|
+
* scored against a fuller predicate table (a surface resolves to a curated family instead of
|
|
105
|
+
* creating an undescribed one, which is the difference between 0.3 and 0.75 for the same triple).
|
|
106
|
+
* Triples themselves are re-derived under `ENTITY_EXTRACTOR_VERSION` 3 — `reextractEdges` reads
|
|
107
|
+
* what `triples` holds, so both bumps are required (see `EDGE_REEXTRACT_BATCH`'s counterpart in
|
|
108
|
+
* `store/memory.ts#reindexEntities`).
|
|
109
|
+
*/
|
|
110
|
+
export declare const EDGE_EXTRACTOR_VERSION = 3;
|
|
111
|
+
/**
|
|
112
|
+
* Default facts one {@link reextractEdges} pass may rewrite.
|
|
113
|
+
*
|
|
114
|
+
* The same size as `ENTITY_SWEEP_BATCH` on purpose: both are "one bounded pass" of the derived state
|
|
115
|
+
* run from `maintenance()`, and a corpus should take the same number of passes through either half.
|
|
116
|
+
* This pass is the cheaper of the two — it reads stored triples instead of re-tagging content, and
|
|
117
|
+
* measured ~0.04 ms per fact (§14.5).
|
|
118
|
+
*/
|
|
119
|
+
export declare const EDGE_REEXTRACT_BATCH = 2000;
|
|
120
|
+
/**
|
|
121
|
+
* Write one fact's edges from its extracted triples — the call the WRITE PATH makes (add / update /
|
|
122
|
+
* revive / merge) right after it inserted the same triples, inside the same transaction.
|
|
123
|
+
*
|
|
124
|
+
* The legacy `triples` row is not replaced or read back: it stays the record of "what the string
|
|
125
|
+
* extractor saw" (the contradiction detector reads it), while `edges` is its normalized, versioned
|
|
126
|
+
* view. That double write is what lets Phase 2 land without a schema change or a re-extraction of the
|
|
127
|
+
* corpus, and it is also why this function can be a no-op without breaking anything.
|
|
128
|
+
*
|
|
129
|
+
* Never throws on a bad triple (it reports it); it CAN throw on a broken database, which is why the
|
|
130
|
+
* store calls it best-effort — a graph write must not fail a fact write (§4.3).
|
|
131
|
+
*
|
|
132
|
+
* Phase 5b reads the fact's `content` once when at least one triple came from the TEXT extractor
|
|
133
|
+
* (`source: 'svo'`), because the statement test (`relations/statement.ts`) is a test against what the
|
|
134
|
+
* fact actually says. One indexed row read per fact, and none for explicitly asserted triples.
|
|
135
|
+
*
|
|
136
|
+
* The gates themselves are `relations/confidence.ts#evaluateRelation`; this function reads the
|
|
137
|
+
* content the test needs and materializes what the policy accepted.
|
|
138
|
+
*
|
|
139
|
+
* @param options.dryRun resolve WITHOUT creating entities/predicates and report what would happen
|
|
140
|
+
* (the coverage report's "why is this fact missing from the graph" view).
|
|
141
|
+
*/
|
|
142
|
+
export declare function writeEdgesForFact(db: Db, factId: number, triples: readonly EdgeSourceTriple[], options?: {
|
|
143
|
+
dryRun?: boolean;
|
|
144
|
+
samples?: number;
|
|
145
|
+
}): EdgeWriteReport;
|
|
146
|
+
/**
|
|
147
|
+
* Write the relations a CALLER stated (`mem_remember.relations`, STRUCTURED_RELATIONS §2).
|
|
148
|
+
*
|
|
149
|
+
* A thin, named wrapper over {@link writeEdgesForFact}: the caller's `{subject, predicate, object}`
|
|
150
|
+
* are mapped onto the same triple shape the extractor produces, with `source: 'explicit'` and
|
|
151
|
+
* confidence 1 — that source is what makes the writer skip the confidence floor and the statement
|
|
152
|
+
* test, while still normalizing both entity slots and the predicate and still applying the name
|
|
153
|
+
* gate. Callers pass already-validated relations (`store/explicit_relations.ts`); this function
|
|
154
|
+
* reports what was written, dropped or already present, and the STORE decides what a drop means.
|
|
155
|
+
*/
|
|
156
|
+
export declare function writeExplicitEdgesForFact(db: Db, factId: number, relations: readonly {
|
|
157
|
+
subject: string;
|
|
158
|
+
predicate: string;
|
|
159
|
+
object: string;
|
|
160
|
+
}[]): EdgeWriteReport;
|
|
161
|
+
/**
|
|
162
|
+
* Drop one fact's edges (a superseded revision leaves the live graph, like its triples).
|
|
163
|
+
*
|
|
164
|
+
* Two narrowing flags, both about STRUCTURED_RELATIONS §2's `source: 'explicit'` rows — an explicit
|
|
165
|
+
* edge is the only one NOT derivable from the fact text, so the passes that re-derive edges must be
|
|
166
|
+
* able to leave them alone while a write that REPLACES the declared relations must be able to drop
|
|
167
|
+
* exactly them:
|
|
168
|
+
*
|
|
169
|
+
* - `keepExplicit` — re-derivation (`reextractEdges`, the entity sweep) deletes what it can
|
|
170
|
+
* recreate and keeps what it cannot;
|
|
171
|
+
* - `onlyExplicit` — an `update` that carries new relations rebuilds that revision's explicit set.
|
|
172
|
+
*
|
|
173
|
+
* With neither (the default) everything goes: the revision itself is gone (`supersede`).
|
|
174
|
+
*/
|
|
175
|
+
export declare function deleteEdgesForFact(db: Db, factId: number, options?: {
|
|
176
|
+
keepExplicit?: boolean;
|
|
177
|
+
onlyExplicit?: boolean;
|
|
178
|
+
}): number;
|
|
179
|
+
/**
|
|
180
|
+
* Delete every edge whose fact is NOT a note (STRUCTURED_RELATIONS §四/§七).
|
|
181
|
+
*
|
|
182
|
+
* The one-time cleanup of the pre-5c graph: before this phase a `journal` write produced edges, and
|
|
183
|
+
* an upgraded database still carries them. It is a DELETE on the derived table only — the facts and
|
|
184
|
+
* their `triples` are untouched (the contradiction detector still reads those) — and it is
|
|
185
|
+
* idempotent, so `maintenance` can call it on every pass and report 0.
|
|
186
|
+
*
|
|
187
|
+
* `kind <> 'note'` rather than `kind = 'journal'`: `journal` is the only other value the closed set
|
|
188
|
+
* has, and legacy values a pre-13 database might still hold all map onto `note`
|
|
189
|
+
* (`store/kinds.ts#mapLegacyKind`), so the negative form is the one that cannot under-clean.
|
|
190
|
+
*/
|
|
191
|
+
export declare function deleteNonNoteEdges(db: Db): number;
|
|
192
|
+
/**
|
|
193
|
+
* Re-derive one fact's edge set under the CURRENT policy (STRUCTURED_RELATIONS §五/§七).
|
|
194
|
+
*
|
|
195
|
+
* WHY THIS EXISTS. `reextractEdges` only selects ACTIVE facts, so an archived row keeps whatever
|
|
196
|
+
* edges it had — including the version-0 rows migration 15's backfill created from legacy triples.
|
|
197
|
+
* Those are exactly the rows Phase 5b's rules were written to refuse (fragments, unstated
|
|
198
|
+
* clauses), and the reader hides them only while the row is archived: a restore would put them
|
|
199
|
+
* back on the graph without ever re-deriving them. This is the one-fact rebuild both re-entry paths
|
|
200
|
+
* call (`store/memory.ts#restore` and the revive branch of `persistFact`), so an archived row comes
|
|
201
|
+
* back with the edges TODAY's rules would write and never with an older extractor's leftovers.
|
|
202
|
+
*
|
|
203
|
+
* Two properties that make it safe to call blindly:
|
|
204
|
+
* - it is IDEMPOTENT in edge SET (same triples + same policy → same rows; only `edge_id`s move);
|
|
205
|
+
* - it never touches `source: 'explicit'` rows. A declared relation cannot be re-derived from the
|
|
206
|
+
* fact text (`deleteEdgesForFact`'s `keepExplicit`), so losing one here would lose a caller's
|
|
207
|
+
* statement — the one thing a restore must preserve even when the extracted half is rebuilt.
|
|
208
|
+
*
|
|
209
|
+
* A JOURNAL gets its non-explicit edges dropped and nothing written (§四). The caller decides the
|
|
210
|
+
* failure policy: the write path and `restore` call it best-effort inside a savepoint, so a broken
|
|
211
|
+
* row keeps its previous edges and the fact write is never blocked (§4.3).
|
|
212
|
+
*/
|
|
213
|
+
export declare function rebuildEdgesForFact(db: Db, factId: number, kind: string): EdgeRebuildReport;
|
|
214
|
+
/**
|
|
215
|
+
* Re-derive edges from the STORED triples of facts whose edges predate the current extractor
|
|
216
|
+
* (§4.3 "支持按 extraction_version 重抽/回填").
|
|
217
|
+
*
|
|
218
|
+
* Why it re-derives from `triples` and not from the fact text: the rules that changed in Phase 2 are
|
|
219
|
+
* functions of the triples (alias/canonical resolution, confidence, direction). A change to the TEXT
|
|
220
|
+
* extraction is `ENTITY_EXTRACTOR_VERSION`'s business — `reindexEntities` re-tags the content, and
|
|
221
|
+
* it now rewrites the edges in the same transaction (`store/memory.ts`). Re-tagging here would mean
|
|
222
|
+
* a jieba round-trip per fact for a pass that is otherwise pure SQL.
|
|
223
|
+
*
|
|
224
|
+
* One transaction per fact, so a row that cannot be re-derived keeps its previous edges and stays
|
|
225
|
+
* selected for the next pass instead of leaving a half-rewritten graph.
|
|
226
|
+
*
|
|
227
|
+
* ONLY NOTES ARE SELECTED (STRUCTURED_RELATIONS §四): the graph expresses stable facts, so a
|
|
228
|
+
* `journal` row's triples are no longer turned into edges at all — its leftover edges from before
|
|
229
|
+
* this phase are removed by {@link deleteNonNoteEdges}, not rewritten here. Explicit relations
|
|
230
|
+
* (`source: 'explicit'`) survive the rewrite: they cannot be re-derived from `triples`.
|
|
231
|
+
*
|
|
232
|
+
* Idempotent in version mode: a second pass finds nothing older than the current version and
|
|
233
|
+
* reports `facts: 0`. In `all` mode it re-derives everything again by definition — the edge SET is
|
|
234
|
+
* identical, only `edge_id`s are new.
|
|
235
|
+
*/
|
|
236
|
+
export declare function reextractEdges(db: Db, options?: EdgeReextractOptions): EdgeReextractReport;
|
|
237
|
+
/**
|
|
238
|
+
* Read the coverage numbers — what Phase 2 actually produces on this corpus, and why the rest did
|
|
239
|
+
* not make it (§R6: "抽取覆盖率、错误边样本").
|
|
240
|
+
*
|
|
241
|
+
* `samples` is a bounded PREVIEW (it re-runs the resolver with `dryRun` over the first
|
|
242
|
+
* `sampleFacts` facts that have triples), not a persisted log: a drop is a decision, not a row, and
|
|
243
|
+
* the triples are what the decision was made from. Keeping it computed means it can never disagree
|
|
244
|
+
* with the write path — both call the same policy functions.
|
|
245
|
+
*/
|
|
246
|
+
export declare function graphCoverage(db: Db, options?: {
|
|
247
|
+
samples?: number;
|
|
248
|
+
sampleFacts?: number;
|
|
249
|
+
}): GraphCoverageReport;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one schema+data step that turns the old `category` vocabulary into the two memory layers
|
|
3
|
+
* (JOURNAL_NOTES_REDESIGN N1–N5).
|
|
4
|
+
*
|
|
5
|
+
* It is a FUNCTION, not an inline migration body, because two callers need the exact same work:
|
|
6
|
+
*
|
|
7
|
+
* - `MEMORY_SCHEMA` migration 13 (`journal-and-notes`) — the published upgrade path, which starts
|
|
8
|
+
* from the last released schema (`user_version = 10`, 16 free-form categories);
|
|
9
|
+
* - `scripts/migrate-local-kind.mjs` — the ONE-OFF manual conversion of a local database that was
|
|
10
|
+
* already put into the never-published eight-category shape by an earlier build of migration 13.
|
|
11
|
+
* That database sits at `user_version = 13` with a `categories` table, the `facts_category_*`
|
|
12
|
+
* triggers and an eight-value `category` column, so it needs the DATA half without a version bump.
|
|
13
|
+
*
|
|
14
|
+
* Both states are handled by the same predicates: the old `category` column is mapped when it exists,
|
|
15
|
+
* the lookup table and triggers are dropped when they exist, and every step is idempotent so a
|
|
16
|
+
* second run is a no-op. `worklog` (the one episodic value in the published set) becomes `journal`;
|
|
17
|
+
* every other value — the other 15 published ones and all eight experimental ones — becomes `note`,
|
|
18
|
+
* because the eight-category experiment already collapsed the episodic/stable distinction and only
|
|
19
|
+
* `worklog` was ever an episode.
|
|
20
|
+
*
|
|
21
|
+
* WHAT IT DOES NOT DO: rebuild FTS (`kind` is not indexed by it), touch `created_at`/`content`, or
|
|
22
|
+
* bump `user_version` (the caller does that — `migrate()` for the published path, nobody for the
|
|
23
|
+
* manual one).
|
|
24
|
+
*/
|
|
25
|
+
import type { Db } from './port.js';
|
|
26
|
+
/** What one run did, for the operator-facing script and the tests. */
|
|
27
|
+
export interface JournalNotesMigrationReport {
|
|
28
|
+
/** Rows the old `category` column labelled `worklog` (→ `journal`). */
|
|
29
|
+
journal: number;
|
|
30
|
+
/** Every other row (→ `note`). */
|
|
31
|
+
note: number;
|
|
32
|
+
/** Whether the old `category` column existed and was dropped by this run. */
|
|
33
|
+
droppedCategoryColumn: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare function applyJournalAndNotesSchema(db: Db): JournalNotesMigrationReport;
|
|
@@ -44,6 +44,15 @@ export interface MigrationResult {
|
|
|
44
44
|
* no-op, while an existing one is upgraded here.
|
|
45
45
|
*/
|
|
46
46
|
export declare function addColumnIfMissing(db: Db, table: string, column: string, columnDdl: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* Whether `table` already has `column`.
|
|
49
|
+
*
|
|
50
|
+
* Used by {@link addColumnIfMissing} and by a step whose data fix depends on a column an earlier
|
|
51
|
+
* step may or may not have created — a database from the never-published eight-category era has no
|
|
52
|
+
* `facts.kind` at all until the local conversion script adds it, and such a file must still be
|
|
53
|
+
* upgradable by the plugin (docs/JOURNAL_NOTES_REDESIGN.md §6.2).
|
|
54
|
+
*/
|
|
55
|
+
export declare function hasColumn(db: Db, table: string, column: string): boolean;
|
|
47
56
|
/** The schema version stored in the database header (0 = pre-versioning). */
|
|
48
57
|
export declare function readUserVersion(db: Db): number;
|
|
49
58
|
/**
|
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
* `pragma`, 4 `close` call sites, and none of the statement extras such as
|
|
14
14
|
* `pluck`/`raw`/`iterate`/`columns`/`function`/`loadExtension`).
|
|
15
15
|
*
|
|
16
|
-
* SYNCHRONOUS
|
|
17
|
-
* and the lifecycle tick
|
|
18
|
-
*
|
|
16
|
+
* SYNCHRONOUS CALLS, ONE ASYNC ENTRY POINT: a statement call is synchronous, because the SQLite
|
|
17
|
+
* driver is and the lifecycle tick/hot read paths are synchronous passes over it. What a REMOTE
|
|
18
|
+
* backend needs is not an async `prepare` but an async UNIT OF WORK, which is
|
|
19
|
+
* {@link Db.transactionAsync}: the store's write paths await repository calls inside a transaction,
|
|
20
|
+
* and that is the only place this port has to span an `await` (DB-2).
|
|
19
21
|
*/
|
|
20
22
|
export interface DbRunResult {
|
|
21
23
|
changes: number;
|
|
@@ -48,10 +50,27 @@ export interface DbTransaction<T> {
|
|
|
48
50
|
deferred(): T;
|
|
49
51
|
exclusive(): T;
|
|
50
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The transaction mode of {@link Db.transactionAsync}. `IMMEDIATE` takes the write lock up front,
|
|
55
|
+
* which is what a cross-process read-modify-write needs (see {@link DbTransaction.immediate}).
|
|
56
|
+
*/
|
|
57
|
+
export type DbTransactionMode = 'DEFERRED' | 'IMMEDIATE';
|
|
51
58
|
export interface Db {
|
|
52
59
|
prepare<Row = unknown>(sql: string): DbStatement<Row>;
|
|
53
60
|
exec(sql: string): void;
|
|
54
61
|
transaction<T>(fn: () => T): DbTransaction<T>;
|
|
62
|
+
/**
|
|
63
|
+
* An ASYNC body inside one transaction (DB-2). Added beside the synchronous runner because the
|
|
64
|
+
* store's write paths now await repository calls: a remote backend has no synchronous transaction
|
|
65
|
+
* to offer, and the connection-scoped "one unit of work" must survive those awaits.
|
|
66
|
+
*
|
|
67
|
+
* The two runners share the connection's nesting depth, so an async transaction opened inside a
|
|
68
|
+
* synchronous one (or vice versa) becomes a SAVEPOINT rather than a second `BEGIN`. Bodies that
|
|
69
|
+
* do not await are unaffected by this method; bodies that do must accept that a single SQLite
|
|
70
|
+
* connection cannot interleave two units of work — the adapter serializes async transactions so a
|
|
71
|
+
* second one waits for the first to commit or roll back.
|
|
72
|
+
*/
|
|
73
|
+
transactionAsync<T>(fn: () => T | Promise<T>, mode?: DbTransactionMode): Promise<T>;
|
|
55
74
|
pragma(source: string): unknown;
|
|
56
75
|
close(): void;
|
|
57
76
|
}
|
|
@@ -6,6 +6,101 @@
|
|
|
6
6
|
* so adopting them must be a no-op plus a version stamp (see `migrations.ts`). Later schema
|
|
7
7
|
* changes belong in a numbered migration — `ONE_OPEN_CONTRADICTION_ROW_SQL` below is v2.
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* The two columns that replace the eight-value `category` vocabulary
|
|
11
|
+
* (JOURNAL_NOTES_REDESIGN N1–N5).
|
|
12
|
+
*
|
|
13
|
+
* `kind` is the memory LAYER — `journal` (episodic, must carry an event date) or `note` (semantic,
|
|
14
|
+
* no event time required) — and it is the only classification any code or tool reads. The CHECK is
|
|
15
|
+
* the DB-level backstop for a non-cooperating writer (an older build sharing the file, a hand-run
|
|
16
|
+
* `sqlite3`); the application guard refuses an unknown value before it gets here.
|
|
17
|
+
*
|
|
18
|
+
* `legacy_category` is the AUDIT column: migration 13 copies the old free-form `category` into it
|
|
19
|
+
* (all 16 published values, `worklog` included) so the provenance of a migrated row is not lost. No
|
|
20
|
+
* tool schema, filter or query reads it.
|
|
21
|
+
*
|
|
22
|
+
* Both are exported as the exact column DDL because the base schema and migration 13 must produce
|
|
23
|
+
* BYTE-IDENTICAL column definitions (`test/db_upgrade_parity.spec.ts` compares sqlite_master).
|
|
24
|
+
*/
|
|
25
|
+
export declare const FACT_KIND_COLUMN_DDL = "TEXT NOT NULL DEFAULT 'note' CHECK (kind IN ('journal','note'))";
|
|
26
|
+
export declare const FACT_LEGACY_CATEGORY_COLUMN_DDL = "TEXT";
|
|
27
|
+
/**
|
|
28
|
+
* The journal title's column DDL (JOURNAL_TITLE_SEARCH §2.1/§11.1).
|
|
29
|
+
*
|
|
30
|
+
* NULLABLE on purpose: every journal written before this column existed has no title, and the
|
|
31
|
+
* model-facing "a journal needs a title" rule is enforced at the WRITE boundary, never by a CHECK
|
|
32
|
+
* (a `CHECK (kind='journal' → title IS NOT NULL)` could not be added to a corpus that is all NULL).
|
|
33
|
+
* Shared with migration 14 so the base schema and the upgrade produce byte-identical definitions.
|
|
34
|
+
*/
|
|
35
|
+
export declare const FACT_TITLE_COLUMN_DDL = "TEXT";
|
|
36
|
+
/**
|
|
37
|
+
* A title this fact USED to carry (v17; JOURNAL_TITLE_SEARCH §2.2 + R3's controlled retitle).
|
|
38
|
+
*
|
|
39
|
+
* A title is immutable inside a revision chain *by default*, but a wrong handle had no way out: the
|
|
40
|
+
* only accepted change was the one-time backfill of a NULL title, so a typo stayed searchable-by-
|
|
41
|
+
* nothing forever and the only repair was deleting the transaction. `retitle: true` on an update is
|
|
42
|
+
* that way out, and this table is what keeps it honest: the OLD title is recorded as an alias of the
|
|
43
|
+
* revision that replaced it, so every search for the old handle still finds the transaction (with
|
|
44
|
+
* `matched_title` naming what actually matched).
|
|
45
|
+
*
|
|
46
|
+
* Why `(fact_id, title)` rather than a chain id: aliases are derived search state of the revision
|
|
47
|
+
* that carries the title, exactly like `fact_sources`. The write path copies the predecessor's alias
|
|
48
|
+
* rows forward onto each new revision (see `copyTitleAliases`), so a correction never strands an old
|
|
49
|
+
* handle on an archived row. `ON DELETE CASCADE` keeps a purge from leaving orphans.
|
|
50
|
+
*
|
|
51
|
+
* No global UNIQUE on `title`: two unrelated transactions may have carried the same handle at
|
|
52
|
+
* different times, and refusing the second one would make the table a naming registry instead of a
|
|
53
|
+
* search aid.
|
|
54
|
+
*/
|
|
55
|
+
export declare const TITLE_ALIASES_TABLE_DDL = "CREATE TABLE IF NOT EXISTS title_aliases (\n fact_id INTEGER NOT NULL REFERENCES facts(fact_id) ON DELETE CASCADE,\n title TEXT NOT NULL,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (fact_id, title)\n)";
|
|
56
|
+
/**
|
|
57
|
+
* v17's alias index: both the EXACT title comparison and the fuzzy title statement look an alias up
|
|
58
|
+
* by its TEXT (`ta.title = ?` / `ta.title LIKE ?`), and `(fact_id, title)` cannot serve that
|
|
59
|
+
* direction. The table only holds corrected handles, so it stays small; the index is what keeps the
|
|
60
|
+
* per-search cost independent of how many corrections the corpus has seen.
|
|
61
|
+
*/
|
|
62
|
+
export declare const TITLE_ALIASES_INDEXES: string[];
|
|
63
|
+
/**
|
|
64
|
+
* v15's relation graph (RELATIONAL_RECALL §3): `entity_aliases`, `predicates`, `predicate_aliases`
|
|
65
|
+
* and `edges`.
|
|
66
|
+
*
|
|
67
|
+
* Why a second set of tables next to `entities` / `fact_entities` / `triples` instead of rewriting
|
|
68
|
+
* them: `triples` stores `subj` / `pred` / `obj` as TEXT, so every graph question is a string match
|
|
69
|
+
* that cannot tell two spellings of one entity apart and cannot keep a predicate's aliases in one
|
|
70
|
+
* place. `edges` keeps the same fact-level provenance (`fact_id`, `confidence`, `source`) but points
|
|
71
|
+
* at IDs, which is what a path query and a confidence policy need. The legacy `triples` table stays
|
|
72
|
+
* exactly as it is (the write path fills it, the contradiction detector reads it); `edges` is
|
|
73
|
+
* DERIVED from it by migration 15 until Phase 2 moves extraction onto IDs.
|
|
74
|
+
*
|
|
75
|
+
* Two shapes here are deliberate and easy to get wrong:
|
|
76
|
+
* - `extraction_version` is NOT NULL DEFAULT 0, the same convention as `facts.entities_version`:
|
|
77
|
+
* 0 means "before edge extraction was versioned", which is exactly what a row converted from
|
|
78
|
+
* `triples` is. A later re-extraction pass can therefore adopt it with
|
|
79
|
+
* `WHERE extraction_version < :current` instead of the row hiding behind a NULL forever.
|
|
80
|
+
* - `edges.created_at` carries the LEGACY triple's timestamp (`COALESCE(created_at, now)`), not the
|
|
81
|
+
* moment the backfill ran: this graph describes when a relation was recorded, and stamping a
|
|
82
|
+
* migration's wall clock onto a years-old relation would destroy that.
|
|
83
|
+
*
|
|
84
|
+
* `direction` has NO CHECK on purpose: the §4.2 vocabulary is Phase 2's to finish, and a CHECK on an
|
|
85
|
+
* enum that is still being designed costs a table rebuild to extend. The only value this phase
|
|
86
|
+
* writes is `'forward'`, documented as "subject → object is the stored direction; a reverse query
|
|
87
|
+
* reads the same edge backwards and is not a second edge".
|
|
88
|
+
*/
|
|
89
|
+
export declare const ENTITY_ALIASES_TABLE_DDL = "CREATE TABLE IF NOT EXISTS entity_aliases (\n alias TEXT NOT NULL UNIQUE,\n entity_id INTEGER NOT NULL REFERENCES entities(entity_id) ON DELETE CASCADE,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n)";
|
|
90
|
+
export declare const PREDICATES_TABLE_DDL = "CREATE TABLE IF NOT EXISTS predicates (\n predicate_id INTEGER PRIMARY KEY AUTOINCREMENT,\n canonical_name TEXT NOT NULL UNIQUE,\n direction TEXT NOT NULL DEFAULT 'forward',\n description TEXT,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n)";
|
|
91
|
+
export declare const PREDICATE_ALIASES_TABLE_DDL = "CREATE TABLE IF NOT EXISTS predicate_aliases (\n alias TEXT NOT NULL UNIQUE,\n predicate_id INTEGER NOT NULL REFERENCES predicates(predicate_id) ON DELETE CASCADE,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n)";
|
|
92
|
+
export declare const EDGES_TABLE_DDL = "CREATE TABLE IF NOT EXISTS edges (\n edge_id INTEGER PRIMARY KEY AUTOINCREMENT,\n fact_id INTEGER NOT NULL REFERENCES facts(fact_id) ON DELETE CASCADE,\n subject_id INTEGER NOT NULL REFERENCES entities(entity_id) ON DELETE CASCADE,\n predicate_id INTEGER NOT NULL REFERENCES predicates(predicate_id) ON DELETE CASCADE,\n object_id INTEGER NOT NULL REFERENCES entities(entity_id) ON DELETE CASCADE,\n confidence REAL NOT NULL DEFAULT 0.5,\n extraction_version INTEGER NOT NULL DEFAULT 0,\n source TEXT NOT NULL DEFAULT 'heuristic',\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n UNIQUE (fact_id, subject_id, predicate_id, object_id)\n)";
|
|
93
|
+
/**
|
|
94
|
+
* The graph's indexes, spelled once for the base DDL and migration 15.
|
|
95
|
+
*
|
|
96
|
+
* `UNIQUE (fact_id, subject_id, predicate_id, object_id)` already serves "this fact's edges" and the
|
|
97
|
+
* `ON DELETE CASCADE` check for `fact_id`. The other three columns each need their own leading index
|
|
98
|
+
* because they are ALSO cascade parents and because they are the two directions a graph query walks:
|
|
99
|
+
* subject → (predicate) → object is one-hop/two-hop, object → predicate → subject is the reverse
|
|
100
|
+
* query ("who manages 李娜"). Without them every entity/predicate delete scans `edges`, and the
|
|
101
|
+
* reverse walk has no index at all.
|
|
102
|
+
*/
|
|
103
|
+
export declare const RELATIONAL_GRAPH_INDEXES: string[];
|
|
9
104
|
export declare const DDL: string[];
|
|
10
105
|
/**
|
|
11
106
|
* v2 — at most one OPEN row per contradiction pair.
|
|
@@ -24,3 +119,27 @@ export declare const ONE_OPEN_CONTRADICTION_ROW_SQL: string[];
|
|
|
24
119
|
*/
|
|
25
120
|
export declare function factsFtsTableDdl(tokenizer: string): string;
|
|
26
121
|
export declare const FTS_TRIGGERS: string[];
|
|
122
|
+
/** The title-side FTS table's name — one spelling for the DDL, the triggers and the query builder. */
|
|
123
|
+
export declare const TITLE_FTS_TABLE = "facts_title_fts";
|
|
124
|
+
/**
|
|
125
|
+
* The title FTS table (JOURNAL_TITLE_SEARCH §3/§11.3): a SECOND, independent external-content index.
|
|
126
|
+
*
|
|
127
|
+
* `facts_fts` keeps indexing `content` only and is not touched by this at all — mixing titles into the
|
|
128
|
+
* content score is exactly what the separate action exists to avoid. The table is external-content
|
|
129
|
+
* (`content='facts'`) so the rows are the fact table itself and `rebuild` can re-derive the index;
|
|
130
|
+
* `COALESCE(title,'')` in the triggers is what makes a NULL title indexable (external-content FTS5
|
|
131
|
+
* cannot store a NULL).
|
|
132
|
+
*
|
|
133
|
+
* A function, like {@link factsFtsTableDdl}, because the tokenizer is the one the database was built
|
|
134
|
+
* with (read back from `sqlite_master`), not necessarily this process's preference.
|
|
135
|
+
*/
|
|
136
|
+
export declare function titleFtsTableDdl(tokenizer: string): string;
|
|
137
|
+
/**
|
|
138
|
+
* Keep {@link TITLE_FTS_TABLE} in step with `facts.title` — insert, delete, and `UPDATE OF title`.
|
|
139
|
+
*
|
|
140
|
+
* `AFTER UPDATE OF title` is deliberate and narrow: the content triggers fire for every content
|
|
141
|
+
* rewrite, while a title change is the one event this index cares about (a backfill, a merge copying
|
|
142
|
+
* a title onto an existing row). The `'delete'` command needs the OLD value, which is why the row is
|
|
143
|
+
* re-inserted rather than updated in place.
|
|
144
|
+
*/
|
|
145
|
+
export declare const TITLE_FTS_TRIGGERS: string[];
|
|
@@ -82,3 +82,12 @@ export declare function likeSubstring(column: string, terms: readonly string[]):
|
|
|
82
82
|
count: string;
|
|
83
83
|
params: string[];
|
|
84
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* The bind values {@link likeSubstring} uses, in its own order.
|
|
87
|
+
*
|
|
88
|
+
* Split out because a statement can compute the same substring predicate over TWO different columns
|
|
89
|
+
* (the title search ranks on the title that matched, which is either `facts.title` or an alias) while
|
|
90
|
+
* binding one pattern list. Empty terms are dropped here, so the caller never gets a `%%` that
|
|
91
|
+
* matches everything, and `%`/`_` stay literal through the same escaping.
|
|
92
|
+
*/
|
|
93
|
+
export declare function likePatterns(terms: readonly string[]): string[];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MOCK-REMOTE storage driver (DB-4, docs/DB_DRIVER_LAYOUT.md §五).
|
|
3
|
+
*
|
|
4
|
+
* What `storage.driver: fake` selects. It is a REAL driver — registered in the same registry as
|
|
5
|
+
* SQLite, declaring `contractVersion`, capabilities and an async `open()` — whose backend is the
|
|
6
|
+
* SQLite backend placed behind a simulated hop ({@link MockRemoteTransport}):
|
|
7
|
+
*
|
|
8
|
+
* store → StorageBackend (fake) → async surface (one hop per call) → SQLite core (in-memory)
|
|
9
|
+
*
|
|
10
|
+
* The purpose is narrow and stated: it proves the CONFIG SWITCH and the ASYNC BOUNDARY
|
|
11
|
+
* (`docs/DB_DRIVER_LAYOUT.md` §五, `docs/DATABASE_PORTABILITY.md` §十四). It is NOT a second storage
|
|
12
|
+
* engine and NOT the SQL independence proof: its core is this build's SQLite, so a query that only
|
|
13
|
+
* SQLite can answer would still "pass". A truly independent fake (or DB-5's `postgres`) plugs into
|
|
14
|
+
* the same registry and must pass the same `test/helpers/storage_contract.ts` suite unchanged — which
|
|
15
|
+
* is exactly the acceptance criterion, not this driver's implementation.
|
|
16
|
+
*
|
|
17
|
+
* Consequences of that choice, stated rather than hidden:
|
|
18
|
+
*
|
|
19
|
+
* - capabilities are the core's, because the mock has no capability of its own (no probing);
|
|
20
|
+
* - `relationalHandle()` returns the core's handle: the runtime's transitional SQL surfaces keep
|
|
21
|
+
* working on a fake-backed store (a genuinely remote driver would omit it and the runtime fails
|
|
22
|
+
* loudly, see `buildRuntime`);
|
|
23
|
+
* - the default core is `:memory:`, so opening a fake creates no file; `storage.fake.path` opts into
|
|
24
|
+
* a file-backed core for a test that wants one.
|
|
25
|
+
*
|
|
26
|
+
* @module dbdrivers/fake/driver
|
|
27
|
+
*/
|
|
28
|
+
import type { StorageConfig } from '../../../mem-contract/index.js';
|
|
29
|
+
import type { StorageCapabilities } from '../../interfaces/capabilities.js';
|
|
30
|
+
import type { MigrationRunner } from '../../interfaces/migrations.js';
|
|
31
|
+
import type { ContradictionRepository, KeyValueRepository, MemoryRepository, RelationRepository, SourceRepository } from '../../interfaces/repositories.js';
|
|
32
|
+
import type { SearchIndex } from '../../interfaces/search_index.js';
|
|
33
|
+
import type { LifecycleService, StorageBackend } from '../../interfaces/storage_backend.js';
|
|
34
|
+
import type { StorageDriver } from '../../interfaces/storage_driver.js';
|
|
35
|
+
import { SqliteStorageBackend } from '../sqlite/driver.js';
|
|
36
|
+
import { MockRemoteTransport } from './transport.js';
|
|
37
|
+
/** The in-memory core a mock remote serves when `storage.fake.path` is unset. */
|
|
38
|
+
export declare const FAKE_IN_MEMORY_PATH = ":memory:";
|
|
39
|
+
/** What this driver can do: the core's capabilities, declared once and never probed above. */
|
|
40
|
+
export declare const FAKE_CAPABILITIES: StorageCapabilities;
|
|
41
|
+
/**
|
|
42
|
+
* The mock-remote backend: the same repositories as the core, every call one transport hop.
|
|
43
|
+
*
|
|
44
|
+
* The surface tree is derived from the core ({@link asyncSurface}), so a method added to a port is
|
|
45
|
+
* routed through the transport automatically — there is no hand-written delegation list that can
|
|
46
|
+
* silently miss one.
|
|
47
|
+
*/
|
|
48
|
+
export declare class FakeStorageBackend implements StorageBackend {
|
|
49
|
+
readonly kind = "fake";
|
|
50
|
+
readonly capabilities: StorageCapabilities;
|
|
51
|
+
readonly facts: MemoryRepository;
|
|
52
|
+
readonly relations: RelationRepository;
|
|
53
|
+
readonly sources: SourceRepository;
|
|
54
|
+
readonly contradictions: ContradictionRepository;
|
|
55
|
+
readonly search: SearchIndex;
|
|
56
|
+
readonly keyValue: KeyValueRepository;
|
|
57
|
+
readonly lifecycle: LifecycleService;
|
|
58
|
+
readonly migrations: MigrationRunner;
|
|
59
|
+
/** The simulated hop every call above crosses (exposed for tests and diagnostics). */
|
|
60
|
+
readonly transport: MockRemoteTransport;
|
|
61
|
+
private readonly core;
|
|
62
|
+
constructor(core: SqliteStorageBackend, transport: MockRemoteTransport);
|
|
63
|
+
/**
|
|
64
|
+
* The transitional relational handle, delegated to the core (see this module's doc: a genuinely
|
|
65
|
+
* remote driver omits it, and `buildRuntime` refuses to start rather than inventing one).
|
|
66
|
+
*/
|
|
67
|
+
relationalHandle(): unknown;
|
|
68
|
+
/**
|
|
69
|
+
* Closing the backend is not a REQUEST: the core is released directly, so a simulated failure can
|
|
70
|
+
* never leave a test (or a process) holding a half-closed store.
|
|
71
|
+
*/
|
|
72
|
+
close(): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
/** The mock-remote driver: what `storage.driver: fake` (or the `storage.backend` alias) selects. */
|
|
75
|
+
export declare const fakeDriver: StorageDriver<StorageConfig>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StorageBackend } from '../../interfaces/storage_backend.js';
|
|
2
|
+
import { FakeStorageBackend } from './driver.js';
|
|
3
|
+
export { FAKE_CAPABILITIES, FAKE_IN_MEMORY_PATH, FakeStorageBackend, fakeDriver, } from './driver.js';
|
|
4
|
+
export { MOCK_REMOTE_FAILURE_CODE, MockRemoteTransport, type MockRemoteTurnaround, type StorageTransport, type TransportRequestRecord, } from './transport.js';
|
|
5
|
+
export { asyncSurface, type AsyncSurfaceOptions } from './surface.js';
|
|
6
|
+
/** Whether a backend was opened by THIS driver (a mock remote over this build's SQLite core). */
|
|
7
|
+
export declare function isFakeStorageBackend(backend: StorageBackend): backend is FakeStorageBackend;
|