@fgv/ts-agent-memory 5.1.0-39 → 5.1.0-41
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/.rush/temp/{b82cf6bdece20481260e6bab946179eeec9d7b46.tar.log → cbbdbe09515171b4eba8f2592be2dace1e4e8142.tar.log} +14 -2
- package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +2 -2
- package/.rush/temp/operation/build/all.log +2 -2
- package/.rush/temp/operation/build/log-chunks.jsonl +2 -2
- package/.rush/temp/operation/build/state.json +1 -1
- package/dist/packlets/converters/envelopeConverter.js +17 -3
- package/dist/packlets/converters/envelopeConverter.js.map +1 -1
- package/dist/packlets/index/memoryIndex.js +58 -10
- package/dist/packlets/index/memoryIndex.js.map +1 -1
- package/dist/packlets/ingest/cycleGuard.js +13 -6
- package/dist/packlets/ingest/cycleGuard.js.map +1 -1
- package/dist/packlets/ingest/hostStages.js.map +1 -1
- package/dist/packlets/ingest/model.js.map +1 -1
- package/dist/packlets/ingest/orchestrator.js +94 -49
- package/dist/packlets/ingest/orchestrator.js.map +1 -1
- package/dist/packlets/retrieve/hybridRetriever.js +13 -2
- package/dist/packlets/retrieve/hybridRetriever.js.map +1 -1
- package/dist/packlets/retrieve/linkTraversalRetriever.js +46 -57
- package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/dist/packlets/retrieve/recencyRetriever.js +3 -3
- package/dist/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/dist/packlets/retrieve/retriever.js +50 -7
- package/dist/packlets/retrieve/retriever.js.map +1 -1
- package/dist/packlets/retrieve/semanticRetriever.js +9 -3
- package/dist/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/dist/packlets/retrieve/structuredFilterRetriever.js +3 -3
- package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/dist/packlets/retrieve/tagRetriever.js +3 -3
- package/dist/packlets/retrieve/tagRetriever.js.map +1 -1
- package/dist/packlets/retrieve/temporalRetrievers.js +3 -3
- package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/dist/packlets/store/fileTreeMemoryStore.js +72 -16
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/dist/packlets/tools/memoryTools.js +104 -21
- package/dist/packlets/tools/memoryTools.js.map +1 -1
- package/dist/packlets/types/envelope.js +13 -1
- package/dist/packlets/types/envelope.js.map +1 -1
- package/dist/packlets/vector/inMemoryCosineIndex.js +22 -17
- package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/dist/packlets/vector/vectorIndex.js.map +1 -1
- package/dist/test/unit/converters/antagonistRoundTrip.test.js +3 -3
- package/dist/test/unit/converters/antagonistRoundTrip.test.js.map +1 -1
- package/dist/test/unit/converters/envelopeConverter.test.js +125 -8
- package/dist/test/unit/converters/envelopeConverter.test.js.map +1 -1
- package/dist/test/unit/index/memoryIndex.test.js +87 -25
- package/dist/test/unit/index/memoryIndex.test.js.map +1 -1
- package/dist/test/unit/ingest/antagonistCycleAndParity.test.js +20 -16
- package/dist/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -1
- package/dist/test/unit/ingest/cycleGuard.test.js +28 -1
- package/dist/test/unit/ingest/cycleGuard.test.js.map +1 -1
- package/dist/test/unit/ingest/orchestrator.test.js +187 -45
- package/dist/test/unit/ingest/orchestrator.test.js.map +1 -1
- package/dist/test/unit/retrieve/linkTraversalRetriever.test.js +106 -31
- package/dist/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -1
- package/dist/test/unit/retrieve/retrievers.test.js +299 -37
- package/dist/test/unit/retrieve/retrievers.test.js.map +1 -1
- package/dist/test/unit/store/embedOnWrite.test.js +69 -12
- package/dist/test/unit/store/embedOnWrite.test.js.map +1 -1
- package/dist/test/unit/store/listScoped.test.js +109 -0
- package/dist/test/unit/store/listScoped.test.js.map +1 -0
- package/dist/test/unit/store/rankAxis.test.js +254 -0
- package/dist/test/unit/store/rankAxis.test.js.map +1 -0
- package/dist/test/unit/tools/memoryTools.test.js +280 -11
- package/dist/test/unit/tools/memoryTools.test.js.map +1 -1
- package/dist/test/unit/types/writePolicy.test.js +9 -2
- package/dist/test/unit/types/writePolicy.test.js.map +1 -1
- package/dist/test/unit/vector/inMemoryCosineIndex.test.js +95 -35
- package/dist/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -1
- package/dist/test/unit/vector/vectorIndex.test.js +24 -15
- package/dist/test/unit/vector/vectorIndex.test.js.map +1 -1
- package/dist/ts-agent-memory.d.ts +384 -97
- package/etc/ts-agent-memory.api.md +64 -22
- package/lib/packlets/converters/envelopeConverter.d.ts +8 -1
- package/lib/packlets/converters/envelopeConverter.d.ts.map +1 -1
- package/lib/packlets/converters/envelopeConverter.js +18 -4
- package/lib/packlets/converters/envelopeConverter.js.map +1 -1
- package/lib/packlets/index/memoryIndex.d.ts +42 -10
- package/lib/packlets/index/memoryIndex.d.ts.map +1 -1
- package/lib/packlets/index/memoryIndex.js +58 -10
- package/lib/packlets/index/memoryIndex.js.map +1 -1
- package/lib/packlets/ingest/cycleGuard.d.ts +5 -5
- package/lib/packlets/ingest/cycleGuard.d.ts.map +1 -1
- package/lib/packlets/ingest/cycleGuard.js +13 -6
- package/lib/packlets/ingest/cycleGuard.js.map +1 -1
- package/lib/packlets/ingest/hostStages.d.ts +3 -3
- package/lib/packlets/ingest/hostStages.d.ts.map +1 -1
- package/lib/packlets/ingest/hostStages.js.map +1 -1
- package/lib/packlets/ingest/model.d.ts +25 -14
- package/lib/packlets/ingest/model.d.ts.map +1 -1
- package/lib/packlets/ingest/model.js.map +1 -1
- package/lib/packlets/ingest/orchestrator.d.ts +18 -4
- package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
- package/lib/packlets/ingest/orchestrator.js +93 -48
- package/lib/packlets/ingest/orchestrator.js.map +1 -1
- package/lib/packlets/retrieve/hybridRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/hybridRetriever.js +12 -1
- package/lib/packlets/retrieve/hybridRetriever.js.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +18 -23
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.js +45 -56
- package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/lib/packlets/retrieve/recencyRetriever.js +2 -2
- package/lib/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/lib/packlets/retrieve/retriever.d.ts +70 -11
- package/lib/packlets/retrieve/retriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/retriever.js +52 -7
- package/lib/packlets/retrieve/retriever.js.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.js +9 -3
- package/lib/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/lib/packlets/retrieve/structuredFilterRetriever.js +2 -2
- package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/lib/packlets/retrieve/tagRetriever.js +2 -2
- package/lib/packlets/retrieve/tagRetriever.js.map +1 -1
- package/lib/packlets/retrieve/temporalRetrievers.js +3 -3
- package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +57 -3
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.js +72 -16
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/lib/packlets/tools/memoryTools.d.ts +24 -0
- package/lib/packlets/tools/memoryTools.d.ts.map +1 -1
- package/lib/packlets/tools/memoryTools.js +104 -21
- package/lib/packlets/tools/memoryTools.js.map +1 -1
- package/lib/packlets/types/envelope.d.ts +61 -8
- package/lib/packlets/types/envelope.d.ts.map +1 -1
- package/lib/packlets/types/envelope.js +14 -0
- package/lib/packlets/types/envelope.js.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts +9 -5
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.js +22 -17
- package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/lib/packlets/vector/vectorIndex.d.ts +46 -20
- package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
- package/lib/packlets/vector/vectorIndex.js.map +1 -1
- package/lib/test/unit/converters/antagonistRoundTrip.test.js +3 -3
- package/lib/test/unit/converters/antagonistRoundTrip.test.js.map +1 -1
- package/lib/test/unit/converters/envelopeConverter.test.js +124 -7
- package/lib/test/unit/converters/envelopeConverter.test.js.map +1 -1
- package/lib/test/unit/index/memoryIndex.test.js +86 -24
- package/lib/test/unit/index/memoryIndex.test.js.map +1 -1
- package/lib/test/unit/ingest/antagonistCycleAndParity.test.js +20 -16
- package/lib/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -1
- package/lib/test/unit/ingest/cycleGuard.test.js +28 -1
- package/lib/test/unit/ingest/cycleGuard.test.js.map +1 -1
- package/lib/test/unit/ingest/orchestrator.test.js +186 -44
- package/lib/test/unit/ingest/orchestrator.test.js.map +1 -1
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.js +106 -31
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -1
- package/lib/test/unit/retrieve/retrievers.test.js +298 -36
- package/lib/test/unit/retrieve/retrievers.test.js.map +1 -1
- package/lib/test/unit/store/embedOnWrite.test.js +68 -11
- package/lib/test/unit/store/embedOnWrite.test.js.map +1 -1
- package/lib/test/unit/store/listScoped.test.d.ts +2 -0
- package/lib/test/unit/store/listScoped.test.d.ts.map +1 -0
- package/lib/test/unit/store/listScoped.test.js +111 -0
- package/lib/test/unit/store/listScoped.test.js.map +1 -0
- package/lib/test/unit/store/rankAxis.test.d.ts +2 -0
- package/lib/test/unit/store/rankAxis.test.d.ts.map +1 -0
- package/lib/test/unit/store/rankAxis.test.js +256 -0
- package/lib/test/unit/store/rankAxis.test.js.map +1 -0
- package/lib/test/unit/tools/memoryTools.test.js +280 -11
- package/lib/test/unit/tools/memoryTools.test.js.map +1 -1
- package/lib/test/unit/types/writePolicy.test.js +9 -2
- package/lib/test/unit/types/writePolicy.test.js.map +1 -1
- package/lib/test/unit/vector/inMemoryCosineIndex.test.js +95 -35
- package/lib/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -1
- package/lib/test/unit/vector/vectorIndex.test.js +24 -15
- package/lib/test/unit/vector/vectorIndex.test.js.map +1 -1
- package/package.json +7 -7
- package/rush-logs/ts-agent-memory.build.cache.log +1 -1
- package/rush-logs/ts-agent-memory.build.log +2 -2
- package/src/packlets/converters/envelopeConverter.ts +27 -4
- package/src/packlets/index/memoryIndex.ts +86 -22
- package/src/packlets/ingest/cycleGuard.ts +22 -11
- package/src/packlets/ingest/hostStages.ts +3 -3
- package/src/packlets/ingest/model.ts +25 -14
- package/src/packlets/ingest/orchestrator.ts +143 -67
- package/src/packlets/retrieve/hybridRetriever.ts +14 -1
- package/src/packlets/retrieve/linkTraversalRetriever.ts +51 -62
- package/src/packlets/retrieve/recencyRetriever.ts +3 -3
- package/src/packlets/retrieve/retriever.ts +97 -13
- package/src/packlets/retrieve/semanticRetriever.ts +10 -5
- package/src/packlets/retrieve/structuredFilterRetriever.ts +3 -3
- package/src/packlets/retrieve/tagRetriever.ts +3 -3
- package/src/packlets/retrieve/temporalRetrievers.ts +3 -3
- package/src/packlets/store/fileTreeMemoryStore.ts +117 -12
- package/src/packlets/tools/memoryTools.ts +152 -25
- package/src/packlets/types/envelope.ts +66 -8
- package/src/packlets/vector/inMemoryCosineIndex.ts +45 -22
- package/src/packlets/vector/vectorIndex.ts +47 -20
- package/src/test/unit/converters/antagonistRoundTrip.test.ts +3 -3
- package/src/test/unit/converters/envelopeConverter.test.ts +168 -11
- package/src/test/unit/index/memoryIndex.test.ts +99 -14
- package/src/test/unit/ingest/antagonistCycleAndParity.test.ts +23 -18
- package/src/test/unit/ingest/cycleGuard.test.ts +44 -2
- package/src/test/unit/ingest/orchestrator.test.ts +234 -41
- package/src/test/unit/retrieve/linkTraversalRetriever.test.ts +134 -35
- package/src/test/unit/retrieve/retrievers.test.ts +381 -25
- package/src/test/unit/store/embedOnWrite.test.ts +83 -11
- package/src/test/unit/store/listScoped.test.ts +138 -0
- package/src/test/unit/store/rankAxis.test.ts +349 -0
- package/src/test/unit/tools/memoryTools.test.ts +362 -13
- package/src/test/unit/types/writePolicy.test.ts +11 -2
- package/src/test/unit/vector/inMemoryCosineIndex.test.ts +115 -39
- package/src/test/unit/vector/vectorIndex.test.ts +33 -17
- package/temp/build/lint/_eslint-5eVG3S6w.json +41 -33
- package/temp/build/typescript/ts_8nwakTlr.json +1 -1
- package/temp/ts-agent-memory.api.json +1080 -136
- package/temp/ts-agent-memory.api.md +64 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cycleGuard.test.js","sourceRoot":"","sources":["../../../../src/test/unit/ingest/cycleGuard.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,oBAAoB,CAAC;AAC5B,OAAO,
|
|
1
|
+
{"version":3,"file":"cycleGuard.test.js","sourceRoot":"","sources":["../../../../src/test/unit/ingest/cycleGuard.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport '@fgv/ts-utils-jest';\nimport {\n ICycleGuardEdge,\n IEdgeTarget,\n LinkType,\n MemoryId,\n MemoryScopeKey,\n assertNoCycles,\n buildCycleKey\n} from '../../../index';\n\nconst rel: LinkType = 'rel' as LinkType;\nconst DEFAULT_SCOPE: string = 'exp';\n\n/** A scope-qualified node, `scope#id` or a bare id defaulting to {@link DEFAULT_SCOPE}. */\nfunction node(spec: string): IEdgeTarget {\n const hash: number = spec.indexOf('#');\n const scope: string = hash >= 0 ? spec.slice(0, hash) : DEFAULT_SCOPE;\n const id: string = hash >= 0 ? spec.slice(hash + 1) : spec;\n return { scope: scope as MemoryScopeKey, id: id as MemoryId };\n}\n\nfunction edge(source: string, target: string, type: LinkType = rel): ICycleGuardEdge {\n return { source: node(source), target: node(target), type };\n}\n\ndescribe('buildCycleKey', () => {\n test('is deterministic for the same edge', () => {\n expect(buildCycleKey(edge('a', 'b'))).toSucceedAndSatisfy((key: string) => {\n expect(buildCycleKey(edge('a', 'b'))).toSucceedWith(key);\n });\n });\n\n test('differs by direction and type', () => {\n const forward = buildCycleKey(edge('a', 'b')).orThrow();\n const reverse = buildCycleKey(edge('b', 'a')).orThrow();\n const typed = buildCycleKey(edge('a', 'b', 'other' as LinkType)).orThrow();\n expect(forward).not.toBe(reverse);\n expect(forward).not.toBe(typed);\n });\n});\n\ndescribe('assertNoCycles', () => {\n test('accepts an acyclic graph', () => {\n expect(assertNoCycles([edge('a', 'b')], [edge('b', 'c'), edge('c', 'd')])).toSucceed();\n });\n\n test('rejects a proposed edge that closes a cycle against existing edges', () => {\n expect(assertNoCycles([edge('a', 'b'), edge('b', 'c')], [edge('c', 'a')])).toFailWith(\n /would create a cycle/\n );\n });\n\n test('rejects a cycle formed entirely within the proposed batch', () => {\n expect(assertNoCycles([], [edge('a', 'b'), edge('b', 'a')])).toFailWith(/would create a cycle/);\n });\n\n test('rejects a self-loop', () => {\n expect(assertNoCycles([], [edge('a', 'a')])).toFailWith(/self-loop/);\n });\n\n test('tolerates a duplicate proposed edge (deduped by canonical key)', () => {\n expect(assertNoCycles([], [edge('a', 'b'), edge('a', 'b')])).toSucceed();\n });\n\n test('tolerates a duplicate existing edge', () => {\n expect(assertNoCycles([edge('a', 'b'), edge('a', 'b')], [edge('b', 'c')])).toSucceed();\n });\n\n test('accepts a diamond (shared descendant is not a cycle)', () => {\n expect(assertNoCycles([], [edge('a', 'b'), edge('a', 'c'), edge('b', 'd'), edge('c', 'd')])).toSucceed();\n });\n\n test('reachability tolerates a frontier node reached by two paths (revisit guard)', () => {\n // Existing graph: a->c, a->b, b->c. A proposed edge z->a runs reachability\n // from `a`, whose DFS pushes `c` twice before it is visited — exercising the\n // visited-node skip in the traversal.\n expect(assertNoCycles([edge('a', 'c'), edge('a', 'b'), edge('b', 'c')], [edge('z', 'a')])).toSucceed();\n });\n\n describe('scope-qualified nodes', () => {\n test('does NOT conflate a stem shared across scopes into one node', () => {\n // ca#x -> cb#y and cb#x -> ca#z reuse the stem `x` across scopes ca/cb.\n // If the guard keyed on bare id, the two `x` nodes would merge and a false\n // cycle could appear; with scoped keys they are distinct and the graph is acyclic.\n expect(assertNoCycles([], [edge('ca#x', 'cb#y'), edge('cb#x', 'ca#z')])).toSucceed();\n });\n\n test('still rejects a genuine within-scope cycle across the same stems', () => {\n expect(assertNoCycles([], [edge('ca#x', 'ca#y'), edge('ca#y', 'ca#x')])).toFailWith(\n /would create a cycle/\n );\n });\n\n test('a self-loop is detected on the full scoped identity', () => {\n expect(assertNoCycles([], [edge('ca#x', 'ca#x')])).toFailWith(/self-loop/);\n // Same stem, different scope is NOT a self-loop.\n expect(assertNoCycles([], [edge('ca#x', 'cb#x')])).toSucceed();\n });\n\n test('names the scope in the diagnostic', () => {\n expect(assertNoCycles([], [edge('ca#x', 'ca#x')])).toFailWith(/ca\\/x/);\n });\n });\n});\n"]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import '@fgv/ts-utils-jest';
|
|
6
6
|
import { Converters, Logging, fail, succeed } from '@fgv/ts-utils';
|
|
7
7
|
import { FileTree } from '@fgv/ts-json-base';
|
|
8
|
-
import { BodyConverterRegistry, CONTRADICTS_LINK_TYPE, DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_SIMILARITY_TOP_K, FileTreeMemoryStore, HOST_INGEST_PROVENANCE_SOURCE, InMemoryCosineIndex, KnowledgeIdentityCodec, LtmIdentityCodec, MemoryIngestOrchestrator, TemporalIdentityCodec, TemporalVersionedPolicy, serializeMemoryFile } from '../../../index';
|
|
8
|
+
import { BodyConverterRegistry, CONTRADICTS_LINK_TYPE, DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_SIMILARITY_TOP_K, FileTreeMemoryStore, HOST_INGEST_PROVENANCE_SOURCE, InMemoryCosineIndex, KnowledgeIdentityCodec, LtmIdentityCodec, MtmIdentityCodec, MemoryIngestOrchestrator, TemporalIdentityCodec, TemporalVersionedPolicy, serializeMemoryFile } from '../../../index';
|
|
9
9
|
// --- kinds --------------------------------------------------------------------
|
|
10
10
|
const noteKind = 'note';
|
|
11
11
|
const factKind = 'fact';
|
|
@@ -89,10 +89,32 @@ function candidate(kind, entityId, body, opts = {}) {
|
|
|
89
89
|
body
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* A scope-qualified target, authored as `scope#id` or a bare id defaulting to the
|
|
94
|
+
* knowledge scope (where the note/num fixtures live). `kt` is the assertion-side
|
|
95
|
+
* spelling of the same helper.
|
|
96
|
+
*/
|
|
97
|
+
function scopedTarget(spec) {
|
|
98
|
+
const hash = spec.indexOf('#');
|
|
99
|
+
const scope = hash >= 0 ? spec.slice(0, hash) : 'knowledge';
|
|
100
|
+
const id = hash >= 0 ? spec.slice(hash + 1) : spec;
|
|
101
|
+
return { scope: scope, id: id };
|
|
102
|
+
}
|
|
103
|
+
function kt(id, scope = 'knowledge') {
|
|
104
|
+
return { scope: scope, id: id };
|
|
105
|
+
}
|
|
106
|
+
/** The scope-qualified reference of a candidate about to be written, by its entityId. */
|
|
107
|
+
function srcOf(ctx, entityId) {
|
|
108
|
+
const found = ctx.candidates.find((rc) => rc.candidate.envelope.entityId === entityId);
|
|
109
|
+
if (found === undefined) {
|
|
110
|
+
throw new Error(`test relater: no candidate with entityId '${entityId}'`);
|
|
111
|
+
}
|
|
112
|
+
return found.id;
|
|
113
|
+
}
|
|
92
114
|
function candEdge(source, type, target, confidence) {
|
|
93
115
|
return {
|
|
94
|
-
source: source,
|
|
95
|
-
edge: Object.assign({ type: type, target: target }, (confidence !== undefined ? { confidence } : {}))
|
|
116
|
+
source: scopedTarget(source),
|
|
117
|
+
edge: Object.assign({ type: type, target: scopedTarget(target) }, (confidence !== undefined ? { confidence } : {}))
|
|
96
118
|
};
|
|
97
119
|
}
|
|
98
120
|
function buildStore(params = {}) {
|
|
@@ -163,7 +185,10 @@ describe('MemoryIngestOrchestrator', () => {
|
|
|
163
185
|
test('classifies, extracts, and writes one record end-to-end with host-ingest provenance + derivedFrom', async () => {
|
|
164
186
|
const store = buildStore();
|
|
165
187
|
const orch = buildOrchestrator({ store });
|
|
166
|
-
const sourceId =
|
|
188
|
+
const sourceId = {
|
|
189
|
+
scope: 'conversations/c1',
|
|
190
|
+
id: 'turn-3'
|
|
191
|
+
};
|
|
167
192
|
const result = await orch.ingestItem({ id: 'doc-1', content: 'the sky is blue', sourceId });
|
|
168
193
|
expect(result).toSucceedAndSatisfy((r) => {
|
|
169
194
|
var _a, _b, _c;
|
|
@@ -174,7 +199,7 @@ describe('MemoryIngestOrchestrator', () => {
|
|
|
174
199
|
expect(rec.resolution.verdict).toBe('new');
|
|
175
200
|
expect(rec.id).toBe('doc-1');
|
|
176
201
|
expect((_a = rec.record) === null || _a === void 0 ? void 0 : _a.envelope.provenance.source).toBe(HOST_INGEST_PROVENANCE_SOURCE);
|
|
177
|
-
expect((_b = rec.record) === null || _b === void 0 ? void 0 : _b.envelope.provenance.derivedFrom).
|
|
202
|
+
expect((_b = rec.record) === null || _b === void 0 ? void 0 : _b.envelope.provenance.derivedFrom).toEqual(sourceId);
|
|
178
203
|
expect((_c = rec.record) === null || _c === void 0 ? void 0 : _c.body).toBe('the sky is blue');
|
|
179
204
|
expect(rec.interlock).toBeUndefined();
|
|
180
205
|
});
|
|
@@ -249,6 +274,25 @@ describe('MemoryIngestOrchestrator', () => {
|
|
|
249
274
|
});
|
|
250
275
|
expect(await orch.ingestItem({ id: 'o-1', content: 'x' })).toFailWith(/no identity codec/);
|
|
251
276
|
});
|
|
277
|
+
test('fails loudly when a pre-existing snapshot record cannot be scoped (no codec for its kind)', async () => {
|
|
278
|
+
// The store can persist a note (it has the note codec), but the orchestrator
|
|
279
|
+
// below is wired WITHOUT a note codec — so when it snapshots the store to run
|
|
280
|
+
// the edge path, it cannot resolve the seeded note's scope and fails loudly
|
|
281
|
+
// (never silently drops it from the cycle graph). This is the branch the
|
|
282
|
+
// scope-qualified edge change added.
|
|
283
|
+
const store = buildStore();
|
|
284
|
+
await putNote(store, 'legacy', 'anchor');
|
|
285
|
+
const orch = MemoryIngestOrchestrator.create({
|
|
286
|
+
store,
|
|
287
|
+
registry: registry(),
|
|
288
|
+
// Deliberately omit the note codec (and any default) that the seeded record needs.
|
|
289
|
+
codecs: new Map([[numKind, new KnowledgeIdentityCodec()]]),
|
|
290
|
+
classifier: classifier(() => succeed({ kind: numKind })),
|
|
291
|
+
extractor: extractor(() => succeed([candidate(numKind, 'n-1', 7)])),
|
|
292
|
+
relationExtractor: noEdges
|
|
293
|
+
}).orThrow();
|
|
294
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/cannot resolve scope for stored record 'legacy'.*no identity codec registered for kind 'note'/i);
|
|
295
|
+
});
|
|
252
296
|
});
|
|
253
297
|
describe('stage 4 — exact dedup (layer 1)', () => {
|
|
254
298
|
test('an exact {kind,body} match in scope is duplicate-of (no resolver)', async () => {
|
|
@@ -259,7 +303,7 @@ describe('MemoryIngestOrchestrator', () => {
|
|
|
259
303
|
});
|
|
260
304
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
|
|
261
305
|
expect(r.records[0].disposition).toBe('deduped');
|
|
262
|
-
expect(r.records[0].resolution).toEqual({ verdict: 'duplicate-of', target: 'doc-a' });
|
|
306
|
+
expect(r.records[0].resolution).toEqual({ verdict: 'duplicate-of', target: kt('doc-a') });
|
|
263
307
|
expect(r.records[0].id).toBe('doc-a');
|
|
264
308
|
});
|
|
265
309
|
// doc-b was never written.
|
|
@@ -316,13 +360,17 @@ async function putNote(store, id, body) {
|
|
|
316
360
|
(await store.put(rec)).orThrow();
|
|
317
361
|
}
|
|
318
362
|
function mockStore(overrides) {
|
|
319
|
-
var _a, _b, _c, _d, _e;
|
|
363
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
320
364
|
return {
|
|
321
365
|
get: (_a = overrides.get) !== null && _a !== void 0 ? _a : (() => Promise.resolve(succeed(undefined))),
|
|
322
366
|
getById: (_b = overrides.getById) !== null && _b !== void 0 ? _b : (() => Promise.resolve(succeed(undefined))),
|
|
323
367
|
list: (_c = overrides.list) !== null && _c !== void 0 ? _c : (() => Promise.resolve(succeed([]))),
|
|
324
|
-
|
|
325
|
-
|
|
368
|
+
listScoped: (_d = overrides.listScoped) !== null && _d !== void 0 ? _d : (() => Promise.resolve(succeed([]))),
|
|
369
|
+
asRecordSource: (_e = overrides.asRecordSource) !== null && _e !== void 0 ? _e : (() => ({
|
|
370
|
+
list: () => Promise.resolve(succeed([]))
|
|
371
|
+
})),
|
|
372
|
+
put: (_f = overrides.put) !== null && _f !== void 0 ? _f : ((r) => Promise.resolve(succeed(r))),
|
|
373
|
+
delete: (_g = overrides.delete) !== null && _g !== void 0 ? _g : (() => Promise.resolve(fail('n/a')))
|
|
326
374
|
};
|
|
327
375
|
}
|
|
328
376
|
describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
@@ -350,7 +398,7 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
350
398
|
store,
|
|
351
399
|
vectorIndex,
|
|
352
400
|
embed,
|
|
353
|
-
entityResolver: resolver((__c, similar) => succeed({ verdict: 'duplicate-of', target: similar[0].
|
|
401
|
+
entityResolver: resolver((__c, similar) => succeed({ verdict: 'duplicate-of', target: similar[0].target })),
|
|
354
402
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
355
403
|
});
|
|
356
404
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
|
|
@@ -367,12 +415,12 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
367
415
|
store,
|
|
368
416
|
vectorIndex,
|
|
369
417
|
embed,
|
|
370
|
-
entityResolver: resolver((__c, similar) => succeed({ verdict: 'supersede', target: similar[0].
|
|
418
|
+
entityResolver: resolver((__c, similar) => succeed({ verdict: 'supersede', target: similar[0].target })),
|
|
371
419
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
372
420
|
});
|
|
373
421
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
|
|
374
422
|
expect(r.records[0].disposition).toBe('written');
|
|
375
|
-
expect(r.records[0].resolution).toEqual({ verdict: 'supersede', target: 'doc-a' });
|
|
423
|
+
expect(r.records[0].resolution).toEqual({ verdict: 'supersede', target: kt('doc-a') });
|
|
376
424
|
expect(r.records[0].id).toBe('doc-b');
|
|
377
425
|
});
|
|
378
426
|
expect(await store.get(noteKind, 'doc-b')).toSucceedAndSatisfy((rec) => expect(rec === null || rec === void 0 ? void 0 : rec.body).toBe('apple tart'));
|
|
@@ -385,7 +433,7 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
385
433
|
store,
|
|
386
434
|
vectorIndex,
|
|
387
435
|
embed,
|
|
388
|
-
entityResolver: resolver((__c, similar) => succeed({ verdict: 'merge-into', target: similar[0].
|
|
436
|
+
entityResolver: resolver((__c, similar) => succeed({ verdict: 'merge-into', target: similar[0].target })),
|
|
389
437
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
390
438
|
});
|
|
391
439
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
|
|
@@ -404,10 +452,10 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
404
452
|
store,
|
|
405
453
|
vectorIndex,
|
|
406
454
|
embed,
|
|
407
|
-
entityResolver: resolver(() => succeed({ verdict: 'merge-into', target: 'ghost' })),
|
|
455
|
+
entityResolver: resolver(() => succeed({ verdict: 'merge-into', target: kt('ghost') })),
|
|
408
456
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
409
457
|
});
|
|
410
|
-
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/merge-into target 'ghost' does not exist/);
|
|
458
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/merge-into target 'knowledge\/ghost' does not exist/);
|
|
411
459
|
});
|
|
412
460
|
test('a below-threshold neighbor is not surfaced (resolver not called)', async () => {
|
|
413
461
|
const vectorIndex = InMemoryCosineIndex.create().orThrow();
|
|
@@ -444,9 +492,9 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
444
492
|
});
|
|
445
493
|
test('a surfaced hit with no backing record is skipped', async () => {
|
|
446
494
|
const ghostIndex = {
|
|
447
|
-
add: (
|
|
448
|
-
remove: (
|
|
449
|
-
query: () => Promise.resolve(succeed([{
|
|
495
|
+
add: (t) => Promise.resolve(succeed(t.id)),
|
|
496
|
+
remove: (t) => Promise.resolve(succeed(t)),
|
|
497
|
+
query: () => Promise.resolve(succeed([{ target: kt('ghost'), score: 0.99 }]))
|
|
450
498
|
};
|
|
451
499
|
const orch = buildOrchestrator({
|
|
452
500
|
store: buildStore(),
|
|
@@ -472,8 +520,8 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
472
520
|
});
|
|
473
521
|
test('a similarity-query failure surfaces loudly', async () => {
|
|
474
522
|
const failingIndex = {
|
|
475
|
-
add: (
|
|
476
|
-
remove: (
|
|
523
|
+
add: (t) => Promise.resolve(succeed(t.id)),
|
|
524
|
+
remove: (t) => Promise.resolve(succeed(t)),
|
|
477
525
|
query: () => Promise.resolve(fail('query kaput'))
|
|
478
526
|
};
|
|
479
527
|
const orch = buildOrchestrator({
|
|
@@ -545,7 +593,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
545
593
|
var _a;
|
|
546
594
|
expect(r.records[0].edges).toHaveLength(1);
|
|
547
595
|
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([
|
|
548
|
-
{ type: 'mentions', target: 'doc-a', confidence: 0.7 }
|
|
596
|
+
{ type: 'mentions', target: kt('doc-a'), confidence: 0.7 }
|
|
549
597
|
]);
|
|
550
598
|
});
|
|
551
599
|
});
|
|
@@ -558,7 +606,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
558
606
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
|
|
559
607
|
var _a;
|
|
560
608
|
expect(r.records).toHaveLength(2);
|
|
561
|
-
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: 'doc-c' }]);
|
|
609
|
+
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: kt('doc-c') }]);
|
|
562
610
|
expect(r.records[1].edges).toEqual([]);
|
|
563
611
|
});
|
|
564
612
|
});
|
|
@@ -568,7 +616,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
568
616
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'x')])),
|
|
569
617
|
relationExtractor: relater(() => succeed([candEdge('ghost', 'rel', 'doc-b')]))
|
|
570
618
|
});
|
|
571
|
-
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/edge source 'ghost' is not a candidate being written/);
|
|
619
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/edge source 'knowledge\/ghost' is not a candidate being written/);
|
|
572
620
|
});
|
|
573
621
|
test('rejects an edge whose target resolves to nothing', async () => {
|
|
574
622
|
const orch = buildOrchestrator({
|
|
@@ -576,7 +624,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
576
624
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'x')])),
|
|
577
625
|
relationExtractor: relater(() => succeed([candEdge('doc-b', 'rel', 'ghost')]))
|
|
578
626
|
});
|
|
579
|
-
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/edge target 'ghost' resolves to neither/);
|
|
627
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/edge target 'knowledge\/ghost' resolves to neither/);
|
|
580
628
|
});
|
|
581
629
|
test('rejects a cycle-inducing edge (default guard)', async () => {
|
|
582
630
|
const orch = buildOrchestrator({
|
|
@@ -595,8 +643,8 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
595
643
|
});
|
|
596
644
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
|
|
597
645
|
var _a, _b;
|
|
598
|
-
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: 'doc-c' }]);
|
|
599
|
-
expect((_b = r.records[1].record) === null || _b === void 0 ? void 0 : _b.envelope.links).toEqual([{ type: 'rel', target: 'doc-b' }]);
|
|
646
|
+
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: kt('doc-c') }]);
|
|
647
|
+
expect((_b = r.records[1].record) === null || _b === void 0 ? void 0 : _b.envelope.links).toEqual([{ type: 'rel', target: kt('doc-b') }]);
|
|
600
648
|
});
|
|
601
649
|
});
|
|
602
650
|
test('a contradicts edge on a NON-temporal kind persists as a plain link (no interlock)', async () => {
|
|
@@ -610,7 +658,89 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
610
658
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
|
|
611
659
|
var _a;
|
|
612
660
|
expect(r.records[0].interlock).toBeUndefined();
|
|
613
|
-
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'contradicts', target: 'doc-a' }]);
|
|
661
|
+
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'contradicts', target: kt('doc-a') }]);
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
describe('_validateEdges cross-scope target disambiguation', () => {
|
|
665
|
+
// Two EXISTING records share the idStem 'turn-3' across two conversations
|
|
666
|
+
// (the MTM codec mints per-scope stems). A stage-5 edge target must resolve
|
|
667
|
+
// to the CORRECT (scope, id) — with bare-id keying both would alias and a
|
|
668
|
+
// wrong-scope target would silently validate. Guards the ingest-validation seam.
|
|
669
|
+
const mtmKind = 'mtm';
|
|
670
|
+
function mtmRegistry() {
|
|
671
|
+
const reg = registry();
|
|
672
|
+
reg.register(mtmKind, Converters.string);
|
|
673
|
+
return reg;
|
|
674
|
+
}
|
|
675
|
+
const mtmCodecs = new Map([
|
|
676
|
+
[mtmKind, new MtmIdentityCodec()]
|
|
677
|
+
]);
|
|
678
|
+
async function seedTurn(store, entityId, body) {
|
|
679
|
+
const addr = new MtmIdentityCodec().encode(entityId).orThrow();
|
|
680
|
+
const rec = {
|
|
681
|
+
envelope: {
|
|
682
|
+
id: addr.idStem,
|
|
683
|
+
entityId: entityId,
|
|
684
|
+
kind: mtmKind,
|
|
685
|
+
tags: [],
|
|
686
|
+
links: [],
|
|
687
|
+
created: 0,
|
|
688
|
+
updated: 0,
|
|
689
|
+
seq: 0,
|
|
690
|
+
contentHash: '',
|
|
691
|
+
provenance: { source: 'agent' }
|
|
692
|
+
},
|
|
693
|
+
body
|
|
694
|
+
};
|
|
695
|
+
(await store.put(rec)).orThrow();
|
|
696
|
+
}
|
|
697
|
+
function buildMtmOrchestrator(store, relationExtractor) {
|
|
698
|
+
return MemoryIngestOrchestrator.create({
|
|
699
|
+
store,
|
|
700
|
+
registry: mtmRegistry(),
|
|
701
|
+
codecs: mtmCodecs,
|
|
702
|
+
classifier: classifier(() => succeed({ kind: mtmKind })),
|
|
703
|
+
// The candidate is a fresh turn-5 under conv-a (distinct stem from the seeds).
|
|
704
|
+
extractor: extractor(() => succeed([candidate(mtmKind, 'conv-a:5', 'a new turn')])),
|
|
705
|
+
relationExtractor
|
|
706
|
+
}).orThrow();
|
|
707
|
+
}
|
|
708
|
+
async function seededStore() {
|
|
709
|
+
const store = FileTreeMemoryStore.create({
|
|
710
|
+
root: mutableRoot(),
|
|
711
|
+
registry: mtmRegistry(),
|
|
712
|
+
codecs: mtmCodecs,
|
|
713
|
+
clock
|
|
714
|
+
}).orThrow();
|
|
715
|
+
await seedTurn(store, 'conv-a:3', 'anchor in conversation a');
|
|
716
|
+
await seedTurn(store, 'conv-b:3', 'anchor in conversation b');
|
|
717
|
+
return store;
|
|
718
|
+
}
|
|
719
|
+
test('accepts a same-stem edge target that resolves to the correct scope', async () => {
|
|
720
|
+
const store = await seededStore();
|
|
721
|
+
const orch = buildMtmOrchestrator(store, relater((ctx) => succeed([
|
|
722
|
+
{
|
|
723
|
+
source: srcOf(ctx, 'conv-a:5'),
|
|
724
|
+
edge: { type: 'rel', target: kt('turn-3', 'conversations/conv-a') }
|
|
725
|
+
}
|
|
726
|
+
])));
|
|
727
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
|
|
728
|
+
var _a;
|
|
729
|
+
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([
|
|
730
|
+
{ type: 'rel', target: kt('turn-3', 'conversations/conv-a') }
|
|
731
|
+
]);
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
test('rejects a same-stem edge target whose scope has no matching record', async () => {
|
|
735
|
+
const store = await seededStore();
|
|
736
|
+
const orch = buildMtmOrchestrator(store, relater((ctx) => succeed([
|
|
737
|
+
{
|
|
738
|
+
source: srcOf(ctx, 'conv-a:5'),
|
|
739
|
+
// turn-3 exists under conv-a and conv-b, but NOT conv-z.
|
|
740
|
+
edge: { type: 'rel', target: kt('turn-3', 'conversations/conv-z') }
|
|
741
|
+
}
|
|
742
|
+
])));
|
|
743
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/edge target 'conversations\/conv-z\/turn-3' resolves to neither a sibling candidate nor an existing record/);
|
|
614
744
|
});
|
|
615
745
|
});
|
|
616
746
|
});
|
|
@@ -631,13 +761,20 @@ describe('MemoryIngestOrchestrator — contradicts→temporal interlock', () =>
|
|
|
631
761
|
store,
|
|
632
762
|
classifier: classifyFact,
|
|
633
763
|
extractor: extractor(() => succeed([candidate(factKind, 'fact-1', 'the sky is grey')])),
|
|
634
|
-
relationExtractor: relater(() => succeed([
|
|
764
|
+
relationExtractor: relater((ctx) => succeed([
|
|
765
|
+
{
|
|
766
|
+
source: srcOf(ctx, 'fact-1'),
|
|
767
|
+
edge: { type: CONTRADICTS_LINK_TYPE, target: kt(v1Id, 'facts/entities/fact-1') }
|
|
768
|
+
}
|
|
769
|
+
]))
|
|
635
770
|
});
|
|
636
771
|
expect(await secondOrch.ingestItem({ id: 'fact-1', content: 'the sky is grey' })).toSucceedAndSatisfy((r) => {
|
|
637
772
|
var _a;
|
|
638
773
|
expect(r.records[0].interlock).toBe('temporal-versioned');
|
|
639
774
|
expect(r.records[0].disposition).toBe('written');
|
|
640
|
-
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([
|
|
775
|
+
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([
|
|
776
|
+
{ type: CONTRADICTS_LINK_TYPE, target: kt(v1Id, 'facts/entities/fact-1') }
|
|
777
|
+
]);
|
|
641
778
|
});
|
|
642
779
|
// The current version is the new (contradicting) one.
|
|
643
780
|
expect(await store.get(factKind, 'fact-1')).toSucceedAndSatisfy((rec) => expect(rec === null || rec === void 0 ? void 0 : rec.body).toBe('the sky is grey'));
|
|
@@ -659,7 +796,12 @@ describe('MemoryIngestOrchestrator — contradicts→temporal interlock', () =>
|
|
|
659
796
|
store,
|
|
660
797
|
classifier: classifyFact,
|
|
661
798
|
extractor: extractor(() => succeed([candidate(factKind, 'fact-1', 'grey')])),
|
|
662
|
-
relationExtractor: relater(() => succeed([
|
|
799
|
+
relationExtractor: relater((ctx) => succeed([
|
|
800
|
+
{
|
|
801
|
+
source: srcOf(ctx, 'fact-1'),
|
|
802
|
+
edge: { type: CONTRADICTS_LINK_TYPE, target: kt(v1Id, 'facts/entities/fact-1') }
|
|
803
|
+
}
|
|
804
|
+
]))
|
|
663
805
|
});
|
|
664
806
|
(await o2.ingestItem({ id: 'fact-1', content: 'grey' })).orThrow();
|
|
665
807
|
// Re-introduce the ORIGINAL body: the invalidated v1 must be skipped by exact
|
|
@@ -790,7 +932,7 @@ describe('MemoryIngestOrchestrator — review-punch-list fixes', () => {
|
|
|
790
932
|
});
|
|
791
933
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
|
|
792
934
|
var _a;
|
|
793
|
-
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: 'doc-a' }]);
|
|
935
|
+
expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: kt('doc-a') }]);
|
|
794
936
|
});
|
|
795
937
|
});
|
|
796
938
|
test('two candidates merging into the same target do not duplicate a shared edge', async () => {
|
|
@@ -802,13 +944,13 @@ describe('MemoryIngestOrchestrator — review-punch-list fixes', () => {
|
|
|
802
944
|
store,
|
|
803
945
|
vectorIndex,
|
|
804
946
|
embed,
|
|
805
|
-
entityResolver: resolver((__c, similar) => succeed({ verdict: 'merge-into', target: similar[0].
|
|
947
|
+
entityResolver: resolver((__c, similar) => succeed({ verdict: 'merge-into', target: similar[0].target })),
|
|
806
948
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart'), candidate(noteKind, 'doc-c', 'apple tart')])),
|
|
807
949
|
relationExtractor: relater(() => succeed([candEdge('doc-a', 'rel', 'doc-x')]))
|
|
808
950
|
});
|
|
809
951
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceed();
|
|
810
952
|
expect(await store.get(noteKind, 'doc-a')).toSucceedAndSatisfy((rec) => {
|
|
811
|
-
expect(rec === null || rec === void 0 ? void 0 : rec.envelope.links).toEqual([{ type: 'rel', target: 'doc-x' }]);
|
|
953
|
+
expect(rec === null || rec === void 0 ? void 0 : rec.envelope.links).toEqual([{ type: 'rel', target: kt('doc-x') }]);
|
|
812
954
|
});
|
|
813
955
|
});
|
|
814
956
|
});
|
|
@@ -838,18 +980,18 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
|
|
|
838
980
|
const store = buildStore({ vectorIndex, embed });
|
|
839
981
|
await putFull(store, noteKind, 'doc-a', 'apple pie', {
|
|
840
982
|
tags: ['old-tag'],
|
|
841
|
-
links: [{ type: 'existing', target: 'doc-z' }]
|
|
983
|
+
links: [{ type: 'existing', target: kt('doc-z') }]
|
|
842
984
|
});
|
|
843
985
|
await putFull(store, noteKind, 'doc-x', 'target of stage-5');
|
|
844
986
|
const orch = buildOrchestrator({
|
|
845
987
|
store,
|
|
846
988
|
vectorIndex,
|
|
847
989
|
embed,
|
|
848
|
-
entityResolver: resolver((__c, similar) => succeed({ verdict: 'merge-into', target: similar[0].
|
|
990
|
+
entityResolver: resolver((__c, similar) => succeed({ verdict: 'merge-into', target: similar[0].target })),
|
|
849
991
|
extractor: extractor(() => succeed([
|
|
850
992
|
candidate(noteKind, 'doc-b', 'apple tart', {
|
|
851
993
|
tags: ['new-tag'],
|
|
852
|
-
links: [{ type: 'candlink', target: 'doc-z' }]
|
|
994
|
+
links: [{ type: 'candlink', target: kt('doc-z') }]
|
|
853
995
|
})
|
|
854
996
|
])),
|
|
855
997
|
relationExtractor: relater(() => succeed([candEdge('doc-a', 'stage5', 'doc-x')]))
|
|
@@ -863,9 +1005,9 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
|
|
|
863
1005
|
expect([...((_a = rec === null || rec === void 0 ? void 0 : rec.envelope.tags) !== null && _a !== void 0 ? _a : [])].sort()).toEqual(['new-tag', 'old-tag']);
|
|
864
1006
|
// Original link + candidate link + stage-5 edge, each exactly once.
|
|
865
1007
|
expect(rec === null || rec === void 0 ? void 0 : rec.envelope.links).toEqual([
|
|
866
|
-
{ type: 'existing', target: 'doc-z' },
|
|
867
|
-
{ type: 'candlink', target: 'doc-z' },
|
|
868
|
-
{ type: 'stage5', target: 'doc-x' }
|
|
1008
|
+
{ type: 'existing', target: kt('doc-z') },
|
|
1009
|
+
{ type: 'candlink', target: kt('doc-z') },
|
|
1010
|
+
{ type: 'stage5', target: kt('doc-x') }
|
|
869
1011
|
]);
|
|
870
1012
|
});
|
|
871
1013
|
});
|
|
@@ -878,10 +1020,10 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
|
|
|
878
1020
|
store,
|
|
879
1021
|
vectorIndex,
|
|
880
1022
|
embed,
|
|
881
|
-
entityResolver: resolver((__c, similar) => succeed({ verdict: 'merge-into', target: similar[0].
|
|
1023
|
+
entityResolver: resolver((__c, similar) => succeed({ verdict: 'merge-into', target: similar[0].target })),
|
|
882
1024
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
883
1025
|
});
|
|
884
|
-
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/merge-into target 'conv-1' is kind 'ltm' but the candidate is kind 'note'/);
|
|
1026
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/merge-into target 'conversations\/conv-1' is kind 'ltm' but the candidate is kind 'note'/);
|
|
885
1027
|
});
|
|
886
1028
|
test('supersede with a non-existent target fails loudly', async () => {
|
|
887
1029
|
const vectorIndex = InMemoryCosineIndex.create().orThrow();
|
|
@@ -891,10 +1033,10 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
|
|
|
891
1033
|
store,
|
|
892
1034
|
vectorIndex,
|
|
893
1035
|
embed,
|
|
894
|
-
entityResolver: resolver(() => succeed({ verdict: 'supersede', target: 'ghost' })),
|
|
1036
|
+
entityResolver: resolver(() => succeed({ verdict: 'supersede', target: kt('ghost') })),
|
|
895
1037
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
896
1038
|
});
|
|
897
|
-
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/supersede target 'ghost' does not exist/);
|
|
1039
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/supersede target 'knowledge\/ghost' does not exist/);
|
|
898
1040
|
});
|
|
899
1041
|
test('duplicate-of with a non-existent target fails loudly', async () => {
|
|
900
1042
|
const vectorIndex = InMemoryCosineIndex.create().orThrow();
|
|
@@ -904,10 +1046,10 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
|
|
|
904
1046
|
store,
|
|
905
1047
|
vectorIndex,
|
|
906
1048
|
embed,
|
|
907
|
-
entityResolver: resolver(() => succeed({ verdict: 'duplicate-of', target: 'ghost' })),
|
|
1049
|
+
entityResolver: resolver(() => succeed({ verdict: 'duplicate-of', target: kt('ghost') })),
|
|
908
1050
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
909
1051
|
});
|
|
910
|
-
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/duplicate-of target 'ghost' does not exist/);
|
|
1052
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/duplicate-of target 'knowledge\/ghost' does not exist/);
|
|
911
1053
|
});
|
|
912
1054
|
});
|
|
913
1055
|
//# sourceMappingURL=orchestrator.test.js.map
|