@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
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The storage DRIVER contract (docs/DB_DRIVER_LAYOUT.md §二/§八).
|
|
3
|
+
*
|
|
4
|
+
* "Adding a storage" = writing one driver directory that implements this interface, not editing a
|
|
5
|
+
* store. A driver:
|
|
6
|
+
*
|
|
7
|
+
* - declares the `name` its config section is chosen by (`storage.driver`, or the DB-1 alias
|
|
8
|
+
* `storage.backend`);
|
|
9
|
+
* - declares the CONTRACT MAJOR it was written against (`contractVersion`): the host refuses a
|
|
10
|
+
* mismatch at registration AND at open (never a silent fallback);
|
|
11
|
+
* - declares what it can do ({@link StorageCapabilities}), so optional behaviour is a flag rather
|
|
12
|
+
* than a probe that fails at query time;
|
|
13
|
+
* - parses ITS OWN config section (`storage.sqlite`, `storage.fake`, `storage.postgres`), owns its
|
|
14
|
+
* connection, schema/migrations, transactions and error mapping, and returns a
|
|
15
|
+
* {@link StorageBackend}.
|
|
16
|
+
*
|
|
17
|
+
* `open` is async for every driver, including the in-process one: the host must not have two opening
|
|
18
|
+
* paths, and a remote driver's open is I/O by nature.
|
|
19
|
+
*
|
|
20
|
+
* @module interfaces/storage_driver
|
|
21
|
+
*/
|
|
22
|
+
import type { StorageCapabilities } from './capabilities.js';
|
|
23
|
+
import type { StorageBackend } from './storage_backend.js';
|
|
24
|
+
/**
|
|
25
|
+
* What the host hands a driver at open.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately tiny: a driver owns its connection and its configuration. The one thing the host has
|
|
28
|
+
* to pass is a runtime knob that is NOT the driver's business (the contradiction threshold is a
|
|
29
|
+
* domain policy the detector is built with, and the detector lives above the storage layer).
|
|
30
|
+
*/
|
|
31
|
+
export interface DriverDeps {
|
|
32
|
+
/** `lifecycle.contradiction_threshold` — supplied to the contradiction façade at construction. */
|
|
33
|
+
readonly contradictionThreshold?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface StorageDriver<Config = unknown> {
|
|
36
|
+
/** The name `storage.driver` selects (also the config section key). */
|
|
37
|
+
readonly name: string;
|
|
38
|
+
/** The interface ABI this driver was written against (`interfaces/version.ts`). */
|
|
39
|
+
readonly contractVersion: number;
|
|
40
|
+
readonly capabilities: StorageCapabilities;
|
|
41
|
+
/** Open (or create) the store and return its backend. Rejecting = the host fails to mount. */
|
|
42
|
+
open(config: Config, deps: DriverDeps): Promise<StorageBackend>;
|
|
43
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The storage HAL's CONTRACT VERSION (docs/DB_DRIVER_LAYOUT.md §八).
|
|
3
|
+
*
|
|
4
|
+
* `contractVersion` is the interface ABI, NOT the package version. It exists because a driver and
|
|
5
|
+
* the host that opens it are independent upgrade units: without a number, a driver built for a
|
|
6
|
+
* different shape of `StorageBackend` would load, open and then misbehave silently.
|
|
7
|
+
*
|
|
8
|
+
* Rules (the doc's §8.2, restated where the code enforces them):
|
|
9
|
+
*
|
|
10
|
+
* - major = a breaking interface change (a method, a parameter or a return's meaning changed);
|
|
11
|
+
* - minor = an ADDITIVE change (an optional capability flag, an optional field): old drivers keep
|
|
12
|
+
* loading because they simply do not declare it;
|
|
13
|
+
* - a driver must implement the major it declares — no "partial implementation";
|
|
14
|
+
* - the host verifies the number at REGISTRATION and at OPEN, and refuses with
|
|
15
|
+
* {@link UnsupportedStorageContractError}. It never falls back to another driver.
|
|
16
|
+
*
|
|
17
|
+
* Bump {@link STORAGE_CONTRACT_VERSION} only with the interface change, and add the previous major to
|
|
18
|
+
* {@link SUPPORTED_STORAGE_CONTRACT_VERSIONS} when an adapter for it is actually written.
|
|
19
|
+
*
|
|
20
|
+
* @module interfaces/version
|
|
21
|
+
*/
|
|
22
|
+
/** The contract major this build implements (`storage/ports` shapes, async boundary). */
|
|
23
|
+
export declare const STORAGE_CONTRACT_VERSION = 2;
|
|
24
|
+
/** The contract majors this host can open. A driver outside this list is refused at registration. */
|
|
25
|
+
export declare const SUPPORTED_STORAGE_CONTRACT_VERSIONS: readonly [2];
|
|
26
|
+
/** The error a driver/host mismatch raises. Explicit refusal, never a silent fallback. */
|
|
27
|
+
export declare class UnsupportedStorageContractError extends Error {
|
|
28
|
+
readonly driver: string;
|
|
29
|
+
readonly contractVersion: number;
|
|
30
|
+
readonly supported: readonly number[];
|
|
31
|
+
constructor(driver: string, contractVersion: number, supported?: readonly number[]);
|
|
32
|
+
}
|
|
33
|
+
/** Whether this host can open a driver that declares `version`. */
|
|
34
|
+
export declare function supportsStorageContract(version: number): boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MAX_REPORTED_CONFLICTS, type ContradictionRecord } from '../../mem-contract/index.js';
|
|
1
|
+
import { MAX_REPORTED_CONFLICTS, type ContradictionRecord, type MemoryKind } from '../../mem-contract/index.js';
|
|
2
2
|
import type { Db } from '../db/conn.js';
|
|
3
3
|
import { type ConflictPair, type StoredConflict } from '../db/dao/contradictions.js';
|
|
4
4
|
export { polarityLookupSql, sameSubjPredLookupSql } from '../db/dao/triples.js';
|
|
@@ -97,7 +97,7 @@ export declare class ContradictDetector {
|
|
|
97
97
|
/** Record a verdict on a conflict WITHOUT touching the facts (see `resolveContradiction`). */
|
|
98
98
|
resolve(id: number, resolution: 'true_positive' | 'false_positive', loserFactId?: number): void;
|
|
99
99
|
/** List conflicts for the admin surface (`resolved` 0 = open). */
|
|
100
|
-
list(resolved?: number,
|
|
100
|
+
list(resolved?: number, kind?: MemoryKind, threshold?: number, limit?: number): ContradictionRecord[];
|
|
101
101
|
/** Drain the pending set: the incremental sweep (`admin contradict_check`). */
|
|
102
102
|
/**
|
|
103
103
|
* Check a LIST of facts — the store's durable drain supplies it (`contradict_check`).
|
|
@@ -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;
|
|
@@ -122,7 +122,7 @@ export interface ProvisionSweepResult {
|
|
|
122
122
|
* It replaced a one-shot model warm-up that sat beside a separate tools sweep, which meant two
|
|
123
123
|
* initialization paths, two logging vocabularies, and no single answer to "is this host fully
|
|
124
124
|
* provisioned?". Callers (the DSH plugin mount, the MCP entry) now make one call, and the per-artifact
|
|
125
|
-
* result lines in the host log come from the same mechanism that
|
|
125
|
+
* result lines in the host log come from the same mechanism that provisions each artifact.
|
|
126
126
|
*
|
|
127
127
|
* The FIRST step is the dsh compatibility gate: an incompatible host means NOTHING is resolved,
|
|
128
128
|
* installed or warmed — preparing an environment for an API we cannot drive is wasted mission.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NormalizedRelation } from './normalize.js';
|
|
2
|
+
import { type RelationEvaluation } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Edges below this confidence are NOT written (§4.3: "低置信度不写 edges,或写但 planner 不默认
|
|
5
|
+
* 使用" — this store takes the first option, because an unqueryable edge is still a lie a later
|
|
6
|
+
* reader can see).
|
|
7
|
+
*
|
|
8
|
+
* The number is a POLICY, kept in one place so a gold set (R6) can move it:
|
|
9
|
+
*
|
|
10
|
+
* - a triple whose predicate is CURATED (a described relation — see `PredicateRef.curated` in
|
|
11
|
+
* `normalize.ts`; every seeded family is, and so is one an operator registered with a
|
|
12
|
+
* description) scores `min(1, base + 0.25)` → 0.75 at the extractor's 0.5, well above the line;
|
|
13
|
+
* - an undescribed predicate scores `base × 0.6` → 0.3, below it, so the relation stays a
|
|
14
|
+
* legacy triple + content hit and never enters the graph. That is the §R3 stance: a wrong edge
|
|
15
|
+
* is worse than a missing one, and "计划/听说/可能" style verbs must not become facts.
|
|
16
|
+
* - a high-confidence non-heuristic triple (an `llm` row at 0.9 → 0.54) still qualifies, because
|
|
17
|
+
* the confidence came from the producer rather than from our vocabulary.
|
|
18
|
+
*/
|
|
19
|
+
export declare const EDGE_MIN_CONFIDENCE = 0.5;
|
|
20
|
+
/** An explicit relation's confidence: the caller asserted it, so there is no score to compute. */
|
|
21
|
+
export declare const EXPLICIT_EDGE_CONFIDENCE = 1;
|
|
22
|
+
/**
|
|
23
|
+
* The confidence an edge carries, as a pure function of the producer's score and one bit of
|
|
24
|
+
* vocabulary knowledge.
|
|
25
|
+
*/
|
|
26
|
+
export declare function edgeConfidence(tripleConfidence: number | null | undefined, curatedPredicate: boolean): number;
|
|
27
|
+
/**
|
|
28
|
+
* Decide whether one normalized relation may become an edge — and with what confidence.
|
|
29
|
+
*
|
|
30
|
+
* The result is a decision, not a mutation: the caller (`db/graph.ts#resolveForEdge`) keeps its own
|
|
31
|
+
* drop accounting (it adds the post-resolution `self-relation` case, which needs ids), and a
|
|
32
|
+
* refused relation is reported with the reason so a coverage report can say WHERE the relation was
|
|
33
|
+
* lost.
|
|
34
|
+
*
|
|
35
|
+
* `content` is the fact's text; it is what the statement test reads. A caller without text (a dry
|
|
36
|
+
* run over an `.svo` triple) gets `''`, which can only refuse an `svo` triple — the honest answer,
|
|
37
|
+
* since nothing is known to have stated it.
|
|
38
|
+
*/
|
|
39
|
+
export declare function evaluateRelation(relation: NormalizedRelation, content: string): RelationEvaluation;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relation CANDIDATE extraction — the two producers that read a fact's text, and the seam a third
|
|
3
|
+
* producer would implement (docs/RELATIONAL_RECALL.md §4.3/R3, §十七).
|
|
4
|
+
*
|
|
5
|
+
* WHAT A CANDIDATE IS. `(subj, pred, obj)` plus the producer's confidence and provenance. A
|
|
6
|
+
* candidate is NOT an edge: nothing here resolves names, applies the gates or touches a database.
|
|
7
|
+
* The pipeline is
|
|
8
|
+
*
|
|
9
|
+
* ```text
|
|
10
|
+
* extractRelationCandidates(text, tokens?) ← THIS module (SVO heuristic + explicit patterns)
|
|
11
|
+
* normalizeRelation(db, candidate) ← normalize.ts (aliases, predicate vocabulary)
|
|
12
|
+
* evaluateRelation(normalized, content) ← confidence.ts (name / statement / confidence gates)
|
|
13
|
+
* writeEdgesForFact(db, factId, candidates) ← db/graph.ts (ids, direction, rows)
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* THREE READINGS, ONE ENTRY POINT. The POS/SVO heuristic, the curated vocabulary's surface matching
|
|
17
|
+
* (`主管`/`隶属于`/`汇报`/`部署在` are nouns to the tagger, relations to the store) and the explicit
|
|
18
|
+
* patterns are three readings of the same text; they must not disagree about what counts as a
|
|
19
|
+
* candidate, so one function returns all of them (the write path, `update`, and the entity sweep all
|
|
20
|
+
* call {@link relationCandidatesFromText}). The pattern half needs the RAW TEXT, which is why the
|
|
21
|
+
* entry point takes both the content and its tokens — and why the token-based half is additionally
|
|
22
|
+
* filtered by `relations/statement.ts#isContiguousTriple`: its arguments come from proximity, so a
|
|
23
|
+
* merge that crossed a clause boundary must not reach `triples`.
|
|
24
|
+
*
|
|
25
|
+
* THE TOKENS COME FROM `entities/extract.ts`. Tagging is the expensive step (~1.1 ms per 1000
|
|
26
|
+
* characters) and the entity half needs it too, so the write path tags ONCE and hands the same
|
|
27
|
+
* array to both — `relationCandidatesFromText` is the sync form for exactly that caller.
|
|
28
|
+
* `extractRelationCandidates` is the standalone form: omit `tokens` and it tags; pass `null` to say
|
|
29
|
+
* "the tagger is unavailable" (patterns still run, the token-based halves produce nothing).
|
|
30
|
+
*
|
|
31
|
+
* @module relations/extract
|
|
32
|
+
*/
|
|
33
|
+
import { type PosToken } from '../entities/extract.js';
|
|
34
|
+
import { type RelationCandidate } from './types.js';
|
|
35
|
+
/**
|
|
36
|
+
* Predicates that are a verb in form only — the tokenizer's reading of them carries no relation.
|
|
37
|
+
*
|
|
38
|
+
* `属于` used to sit here while `relations/normalize.ts#PREDICATE_VOCABULARY` curates the 属于 family
|
|
39
|
+
* (`归属/隶属于/隶属`), so the canonical form was unreachable and only an alias could produce the
|
|
40
|
+
* `属于` edge (`项目归属平台组` worked, `项目属于平台组` produced nothing) — the 2026-10-25 review's
|
|
41
|
+
* §2.1. The invariant that prevents a repeat is `STOP_PREDICATES ∩ curated surfaces = ∅`, asserted
|
|
42
|
+
* in `test/relation_extraction.spec.ts`: a word this list refuses may not be a word the vocabulary
|
|
43
|
+
* promises to extract.
|
|
44
|
+
*
|
|
45
|
+
* EXPORTED for that test. It is a closed list of closed-class verbs (`是`, `有`, `可以`, `成为`…),
|
|
46
|
+
* not a tuning knob.
|
|
47
|
+
*/
|
|
48
|
+
export declare const STOP_PREDICATES: Set<string>;
|
|
49
|
+
/** A query's triple shape: an interrogative slot is a wildcard (`undefined`). */
|
|
50
|
+
export interface TriplePattern {
|
|
51
|
+
subj?: string;
|
|
52
|
+
pred?: string;
|
|
53
|
+
obj?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The POS/SVO half: triples from ALREADY-TAGGED tokens (`null` = tagger unavailable → no triples).
|
|
57
|
+
*
|
|
58
|
+
* The triples are stamped `source: 'svo'` (Phase 5b): they are the product of a PROXIMITY heuristic,
|
|
59
|
+
* so the graph holds them to the "was this actually stated?" test in `relations/statement.ts`.
|
|
60
|
+
*
|
|
61
|
+
* STRICT AND WEAK ARE MERGED, not fallbacks (2026-10-25 review §六). The old rule ran the weak
|
|
62
|
+
* reading (`vn`/preposition) only when the strict verbs produced NOTHING, so one strict candidate —
|
|
63
|
+
* which the gates might refuse a moment later — suppressed a weak candidate that would have become a
|
|
64
|
+
* real edge. Both readings now contribute, deduplicated, and the gates decide.
|
|
65
|
+
*/
|
|
66
|
+
export declare function svoCandidatesFromTokens(rawTokens: PosToken[] | null): RelationCandidate[];
|
|
67
|
+
/**
|
|
68
|
+
* The curated vocabulary's reading of a tagged text: one candidate per surface OCCURRENCE, with the
|
|
69
|
+
* same left/right argument merge the SVO half uses.
|
|
70
|
+
*
|
|
71
|
+
* A surface matches only as a whole TOKEN RUN (`工作于` = `工作` + `于`), which is the boundary rule:
|
|
72
|
+
* a tokenizer boundary is a word boundary, so `工作区` (`工作` + `区`) can never match `工作` and the
|
|
73
|
+
* `区` cannot be read as an object either. `pred` is the SURFACE as written (`汇报`, `部署在`), not
|
|
74
|
+
* the canonical name: `relations/normalize.ts` maps it, and `relations/statement.ts` has to test the
|
|
75
|
+
* word the fact actually contains.
|
|
76
|
+
*
|
|
77
|
+
* Stamped `source: 'svo'` on purpose. A curated word is evidence that a RELATION was written, not
|
|
78
|
+
* evidence of who did what to whom — the arguments still come from proximity, so the statement gate
|
|
79
|
+
* must run. (`relations/patterns.ts` is the half that carries a syntactic cue; those triples are
|
|
80
|
+
* `source: 'pattern'`.)
|
|
81
|
+
*/
|
|
82
|
+
export declare function vocabularyCandidatesFromTokens(rawTokens: PosToken[] | null): RelationCandidate[];
|
|
83
|
+
/**
|
|
84
|
+
* EVERYTHING the write path turns into `triples` + `edges`: the two token-based producers above plus
|
|
85
|
+
* the explicit patterns of `relations/patterns.ts`, deduplicated.
|
|
86
|
+
*
|
|
87
|
+
* The token-based half is filtered by {@link isContiguousTriple} first: a candidate whose three slots
|
|
88
|
+
* are not one unbroken run of the fact text came out of a merge that crossed a clause boundary, and
|
|
89
|
+
* it must not reach `triples` at all (the review's §五 `张伟阿里巴巴 — 工作 — 报告`). Pattern triples
|
|
90
|
+
* are exempt: their predicate is the CANONICAL name and their evidence is the cue, so the text does
|
|
91
|
+
* not contain the three slots literally — `relations/patterns.ts` enforces its own boundaries.
|
|
92
|
+
*
|
|
93
|
+
* Sync on purpose: the caller already tagged the text for the entity half, and re-tagging here would
|
|
94
|
+
* pay the dictionary cost twice for one write (see the module note).
|
|
95
|
+
*/
|
|
96
|
+
export declare function relationCandidatesFromText(text: string, tokens: PosToken[] | null): RelationCandidate[];
|
|
97
|
+
/**
|
|
98
|
+
* The module's candidate-extraction API: every relation this text yields, with one tagging pass.
|
|
99
|
+
*
|
|
100
|
+
* `tokens` is the SAME tagging pass a caller may already have (see `entities/extract.ts#tagText`);
|
|
101
|
+
* omit it to tag here, or pass `null` when the tagger is unavailable. The resolved value is
|
|
102
|
+
* candidates only — the caller still has to normalize and evaluate them before anything is stored.
|
|
103
|
+
*/
|
|
104
|
+
export declare function extractRelationCandidates(text: string, tokens?: PosToken[] | null): Promise<RelationCandidate[]>;
|
|
105
|
+
/**
|
|
106
|
+
* A relation PRODUCER — the seam for anything that is not the string extractor (a later LLM pass,
|
|
107
|
+
* the legacy backfill, a test fixture).
|
|
108
|
+
*
|
|
109
|
+
* `extract` may only produce CANDIDATES: it cannot normalize, evaluate or write anything, so
|
|
110
|
+
* implementing this interface is not a way around the name/statement/confidence gates. `id` is what
|
|
111
|
+
* a coverage report can attribute a candidate to.
|
|
112
|
+
*/
|
|
113
|
+
export interface RelationExtractor {
|
|
114
|
+
readonly id: string;
|
|
115
|
+
extract(text: string, tokens?: PosToken[] | null): Promise<RelationCandidate[]>;
|
|
116
|
+
}
|
|
117
|
+
/** The engine's own producer: the POS/SVO heuristic plus the explicit patterns. */
|
|
118
|
+
export declare const ruleBasedRelationExtractor: RelationExtractor;
|
|
119
|
+
/**
|
|
120
|
+
* Parse a natural-language question into a direction-aware TriplePattern where
|
|
121
|
+
* interrogative slots are wildcards (`undefined`).
|
|
122
|
+
* `parse_query_pattern`: predicates anchor to the interrogative, and no literal
|
|
123
|
+
* interrogative word fills a slot.
|
|
124
|
+
*
|
|
125
|
+
* 「李娜管理谁」 → { subj: '李娜', pred: '管理' }
|
|
126
|
+
* 「谁管理李娜」 → { pred: '管理', obj: '李娜' }
|
|
127
|
+
*/
|
|
128
|
+
export declare function parseQueryPattern(query: string): Promise<TriplePattern>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The relation module's front door.
|
|
3
|
+
*
|
|
4
|
+
* WHAT BELONGS HERE. One relation edge is decided by four steps, and this module owns the first
|
|
5
|
+
* three; the fourth (rows, ids, direction) is storage and lives in `db/graph.ts`:
|
|
6
|
+
*
|
|
7
|
+
* ```text
|
|
8
|
+
* extractRelationCandidates(text, tokens?) candidates from the text — no database
|
|
9
|
+
* normalizeRelation(db, candidate) alias/name + predicate vocabulary
|
|
10
|
+
* evaluateRelation(normalized, content) name / statement / confidence gates
|
|
11
|
+
* writeEdgesForFact(db, factId, candidates) ids, direction, rows, extraction_version
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Nothing else in the engine needs to know which rule produced a candidate: `db/graph.ts` reads the
|
|
15
|
+
* policy from here, `store/memory.ts` reads the extractor from here, and the caller-relation
|
|
16
|
+
* validator (`store/explicit_relations.ts`) reads the same name gate every extracted edge passes.
|
|
17
|
+
* A producer that is not the string extractor implements {@link RelationExtractor} and gets the same
|
|
18
|
+
* gates for free.
|
|
19
|
+
*
|
|
20
|
+
* The module is importable BY PATH (`relations/extract.js`, …) like every other internal module;
|
|
21
|
+
* the names below are the ones the rest of the engine (and a future LLM/backfill producer) should
|
|
22
|
+
* use, so a rule exists in exactly one place.
|
|
23
|
+
*
|
|
24
|
+
* @module relations
|
|
25
|
+
*/
|
|
26
|
+
export { STOP_PREDICATES, extractRelationCandidates, relationCandidatesFromText, svoCandidatesFromTokens, vocabularyCandidatesFromTokens, ruleBasedRelationExtractor, parseQueryPattern, type RelationExtractor, type TriplePattern, } from './extract.js';
|
|
27
|
+
export { ensureRelationalVocabulary, isSymmetricPredicate, normalizeRelation, parseAliasPairs, registerEntityAlias, registerEntityAliasesFromText, resolveEntity, resolvePredicate, stripAliasClause, PREDICATE_VOCABULARY, type AliasPair, type AliasRegistration, type AliasSkipReason, type EntityRef, type NormalizedRelation, type PredicateDirection, type PredicateRef, type PredicateSeed, type ResolveOptions, type ResolveVia, type VocabularySeedReport, } from './normalize.js';
|
|
28
|
+
export { EDGE_MIN_CONFIDENCE, EXPLICIT_EDGE_CONFIDENCE, edgeConfidence, evaluateRelation, } from './confidence.js';
|
|
29
|
+
export { GRAPH_NAME_MAX_CHARS, GRAPH_NAME_MAX_CJK_CHARS, graphNameIssue, isGraphName, stripParentheticalSuffix, type GraphNameIssue, } from './name-quality.js';
|
|
30
|
+
export { CLAUSE_BOUNDARY_CHARS, isContiguousTriple, isStatedRelation } from './statement.js';
|
|
31
|
+
export { PATTERN_TRIPLE_CONFIDENCE, patternTriples, patternTriplesWithReport, type PatternYield, } from './patterns.js';
|
|
32
|
+
export { EXPLICIT_EDGE_SOURCE, PATTERN_SOURCE, SVO_SOURCE, type RelationCandidate, type RelationDropReason, type RelationEvaluation, type RelationTripleInput, } from './types.js';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is a string a plausible NAME for a graph node? — the deterministic quality gate of Phase 5b
|
|
3
|
+
* (docs/RELATIONAL_RECALL.md §4.3/R3, §十七).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. The POS/SVO heuristic (`relations/extract.ts`) merges the noun-ish tokens on each
|
|
6
|
+
* side of a verb, and on real prose (long engineering notes) that rule happily produces slots like
|
|
7
|
+
* `)。**`, `,仓库…` or `包与CLI/MCP)。`. Phase 5a measured the result on a live snapshot: 69 of the
|
|
8
|
+
* 87 entity names in the active graph contained punctuation, and every edge v1 accepted was a clause
|
|
9
|
+
* fragment rather than a relation. A node whose name is a sentence fragment cannot be asked about,
|
|
10
|
+
* so it is worse than a missing node: it makes `relationCoverage` look healthy while the graph
|
|
11
|
+
* answers nothing (§R3's "错误边比缺失边更危险").
|
|
12
|
+
*
|
|
13
|
+
* The gate is a SHAPE test, not a semantics test. It cannot tell `阿里巴巴` (a company) from
|
|
14
|
+
* `发布节奏` (a compound noun) — that needs the statement test in `relations/statement.ts`. What it
|
|
15
|
+
* does guarantee is that every name the graph stores is a bounded, punctuation-free token, which is
|
|
16
|
+
* the precondition for the statement test to mean anything.
|
|
17
|
+
*
|
|
18
|
+
* Rules, each derived from the live failure samples (2026-10-25):
|
|
19
|
+
*
|
|
20
|
+
* - **identifier form is accepted as-is.** `@avantf/dsh-mem`, `v0.1.5`, `104.16.0.34`,
|
|
21
|
+
* `packages/core` are names in this store's corpus, and their punctuation (`.` `/` `-` `+` `@`
|
|
22
|
+
* `:`) is part of the name. They are matched by {@link IDENTIFIER_NAME}, so the punctuation rules
|
|
23
|
+
* below cannot reject them (the length bounds still apply).
|
|
24
|
+
* - **markup / quotes / brackets are refused.** A name that came out of Markdown emphasis, a code
|
|
25
|
+
* span, a quotation or a parenthesis is a fragment of formatting, not a name.
|
|
26
|
+
* - **sentence punctuation is refused.** Chinese commas/periods inside a slot mean the merge ran
|
|
27
|
+
* across a clause boundary.
|
|
28
|
+
* - **whitespace is refused.** The extractor joins tokens without spaces, so a space inside a
|
|
29
|
+
* slot means two tokens the tokenizer did not consider adjacent. (A multi-word ASCII name is the
|
|
30
|
+
* one case this loses; the live corpus has none, and accepting spaces would readmit clauses.)
|
|
31
|
+
* - **the name must carry a letter or digit.** Pure symbols (`——`, `**`, `()`) are not nodes.
|
|
32
|
+
* - **length is bounded**: {@link GRAPH_NAME_MAX_CHARS} overall, {@link GRAPH_NAME_MAX_CJK_CHARS}
|
|
33
|
+
* for a name with CJK in it. Chinese has no spaces, so a long CJK run is a clause; the shorter
|
|
34
|
+
* bound is what rejects it. Latin identifiers keep the longer one.
|
|
35
|
+
* - **a clause particle at the start is refused** (`的了着而并且或把被让`). A slot that OPENS with one
|
|
36
|
+
* starts mid-clause (`的那个工作区` passed for `当前会话所在的那个工作区`). The rule is deliberately
|
|
37
|
+
* starts-only: Chinese compounds routinely END in characters from that set, so a tail rule would
|
|
38
|
+
* reject `知识入库的规则`, `参与` and `行为` — measured against the live graph, the tail rule cost
|
|
39
|
+
* more real names than fragments.
|
|
40
|
+
*
|
|
41
|
+
* The gate is used by the EDGE writer (`db/graph.ts#resolveForEdge`) and by the caller-relation
|
|
42
|
+
* validator (`store/explicit_relations.ts`). `entities`/`fact_entities` keep the extractor's raw
|
|
43
|
+
* names: content recall searches what was written, and narrowing that is a different (and riskier)
|
|
44
|
+
* change.
|
|
45
|
+
*
|
|
46
|
+
* @module relations/name-quality
|
|
47
|
+
*/
|
|
48
|
+
/** Longest name the graph accepts, in characters (a package path or a version fits; a clause does not). */
|
|
49
|
+
export declare const GRAPH_NAME_MAX_CHARS = 40;
|
|
50
|
+
/**
|
|
51
|
+
* Longest name the graph accepts when it contains CJK, in characters.
|
|
52
|
+
*
|
|
53
|
+
* Chinese needs no spaces, so this is the only length signal that separates a name (`阿里巴巴`,
|
|
54
|
+
* `支付网关`) from a clause (`用户可以立即重发一个新版本`). 20 leaves room for names like
|
|
55
|
+
* `知识入库的规则` while rejecting sentence-shaped runs.
|
|
56
|
+
*/
|
|
57
|
+
export declare const GRAPH_NAME_MAX_CJK_CHARS = 20;
|
|
58
|
+
/** Why a string cannot be a graph node name. */
|
|
59
|
+
export type GraphNameIssue = 'blank' | 'too-long' | 'markup' | 'punctuation' | 'whitespace' | 'no-content' | 'long-clause' | 'function-word';
|
|
60
|
+
/**
|
|
61
|
+
* Classify `name` against the graph's name rules — `undefined` means "usable as a node".
|
|
62
|
+
*
|
|
63
|
+
* Pure and total: the edge writer calls it for both slots of every triple, so it must not throw
|
|
64
|
+
* on any input (the extractor can hand it anything the tokenizer produced).
|
|
65
|
+
*/
|
|
66
|
+
export declare function graphNameIssue(name: string): GraphNameIssue | undefined;
|
|
67
|
+
/** Whether {@link graphNameIssue} accepts `name` (the boolean form callers usually want). */
|
|
68
|
+
export declare function isGraphName(name: string): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Drop a parenthetical suffix when what remains is still a name: `阿里巴巴(阿里)` → `阿里巴巴`.
|
|
71
|
+
*
|
|
72
|
+
* The extractor merges brackets into the slot (`阿里巴巴(阿里)` is one argument), and the graph's
|
|
73
|
+
* own alias rule already strips a PARENTHESISED ALIAS STATEMENT (`(又名 老张)`). A bare parenthesis
|
|
74
|
+
* — the common "full name (short name)" form — is not an alias statement and has no canonical
|
|
75
|
+
* side, so the graph uses the head. Only a TRAILING group is dropped, and only when the head is a
|
|
76
|
+
* usable name; anything else is returned unchanged so a caller never receives an invented name.
|
|
77
|
+
*/
|
|
78
|
+
export declare function stripParentheticalSuffix(name: string): string;
|