@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
|
@@ -4,12 +4,29 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import '@fgv/ts-utils-jest';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
ICycleGuardEdge,
|
|
9
|
+
IEdgeTarget,
|
|
10
|
+
LinkType,
|
|
11
|
+
MemoryId,
|
|
12
|
+
MemoryScopeKey,
|
|
13
|
+
assertNoCycles,
|
|
14
|
+
buildCycleKey
|
|
15
|
+
} from '../../../index';
|
|
8
16
|
|
|
9
17
|
const rel: LinkType = 'rel' as LinkType;
|
|
18
|
+
const DEFAULT_SCOPE: string = 'exp';
|
|
19
|
+
|
|
20
|
+
/** A scope-qualified node, `scope#id` or a bare id defaulting to {@link DEFAULT_SCOPE}. */
|
|
21
|
+
function node(spec: string): IEdgeTarget {
|
|
22
|
+
const hash: number = spec.indexOf('#');
|
|
23
|
+
const scope: string = hash >= 0 ? spec.slice(0, hash) : DEFAULT_SCOPE;
|
|
24
|
+
const id: string = hash >= 0 ? spec.slice(hash + 1) : spec;
|
|
25
|
+
return { scope: scope as MemoryScopeKey, id: id as MemoryId };
|
|
26
|
+
}
|
|
10
27
|
|
|
11
28
|
function edge(source: string, target: string, type: LinkType = rel): ICycleGuardEdge {
|
|
12
|
-
return { source: source
|
|
29
|
+
return { source: node(source), target: node(target), type };
|
|
13
30
|
}
|
|
14
31
|
|
|
15
32
|
describe('buildCycleKey', () => {
|
|
@@ -65,4 +82,29 @@ describe('assertNoCycles', () => {
|
|
|
65
82
|
// visited-node skip in the traversal.
|
|
66
83
|
expect(assertNoCycles([edge('a', 'c'), edge('a', 'b'), edge('b', 'c')], [edge('z', 'a')])).toSucceed();
|
|
67
84
|
});
|
|
85
|
+
|
|
86
|
+
describe('scope-qualified nodes', () => {
|
|
87
|
+
test('does NOT conflate a stem shared across scopes into one node', () => {
|
|
88
|
+
// ca#x -> cb#y and cb#x -> ca#z reuse the stem `x` across scopes ca/cb.
|
|
89
|
+
// If the guard keyed on bare id, the two `x` nodes would merge and a false
|
|
90
|
+
// cycle could appear; with scoped keys they are distinct and the graph is acyclic.
|
|
91
|
+
expect(assertNoCycles([], [edge('ca#x', 'cb#y'), edge('cb#x', 'ca#z')])).toSucceed();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('still rejects a genuine within-scope cycle across the same stems', () => {
|
|
95
|
+
expect(assertNoCycles([], [edge('ca#x', 'ca#y'), edge('ca#y', 'ca#x')])).toFailWith(
|
|
96
|
+
/would create a cycle/
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('a self-loop is detected on the full scoped identity', () => {
|
|
101
|
+
expect(assertNoCycles([], [edge('ca#x', 'ca#x')])).toFailWith(/self-loop/);
|
|
102
|
+
// Same stem, different scope is NOT a self-loop.
|
|
103
|
+
expect(assertNoCycles([], [edge('ca#x', 'cb#x')])).toSucceed();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('names the scope in the diagnostic', () => {
|
|
107
|
+
expect(assertNoCycles([], [edge('ca#x', 'ca#x')])).toFailWith(/ca\/x/);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
68
110
|
});
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
IBodyConverterRegistry,
|
|
18
18
|
ICandidateEdge,
|
|
19
19
|
ICandidateRecord,
|
|
20
|
+
IEdgeTarget,
|
|
20
21
|
IEntityResolutionCandidate,
|
|
21
22
|
IEntityResolver,
|
|
22
23
|
IFactExtractor,
|
|
@@ -28,8 +29,10 @@ import {
|
|
|
28
29
|
IMemoryClassifier,
|
|
29
30
|
IMemoryEnvelope,
|
|
30
31
|
IMemoryRecord,
|
|
32
|
+
IMemoryRecordSource,
|
|
31
33
|
IMemoryStore,
|
|
32
34
|
IProvenance,
|
|
35
|
+
IScopedMemoryRecord,
|
|
33
36
|
IRelationContext,
|
|
34
37
|
IRelationExtractor,
|
|
35
38
|
IVectorIndex,
|
|
@@ -40,6 +43,7 @@ import {
|
|
|
40
43
|
LinkType,
|
|
41
44
|
LtmIdentityCodec,
|
|
42
45
|
MemoryEmbedder,
|
|
46
|
+
MtmIdentityCodec,
|
|
43
47
|
MemoryId,
|
|
44
48
|
MemoryIngestOrchestrator,
|
|
45
49
|
MemoryScopeKey,
|
|
@@ -168,12 +172,36 @@ function candidate(
|
|
|
168
172
|
};
|
|
169
173
|
}
|
|
170
174
|
|
|
175
|
+
/**
|
|
176
|
+
* A scope-qualified target, authored as `scope#id` or a bare id defaulting to the
|
|
177
|
+
* knowledge scope (where the note/num fixtures live). `kt` is the assertion-side
|
|
178
|
+
* spelling of the same helper.
|
|
179
|
+
*/
|
|
180
|
+
function scopedTarget(spec: string): IEdgeTarget {
|
|
181
|
+
const hash: number = spec.indexOf('#');
|
|
182
|
+
const scope: string = hash >= 0 ? spec.slice(0, hash) : 'knowledge';
|
|
183
|
+
const id: string = hash >= 0 ? spec.slice(hash + 1) : spec;
|
|
184
|
+
return { scope: scope as MemoryScopeKey, id: id as MemoryId };
|
|
185
|
+
}
|
|
186
|
+
function kt(id: string, scope: string = 'knowledge'): IEdgeTarget {
|
|
187
|
+
return { scope: scope as MemoryScopeKey, id: id as MemoryId };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** The scope-qualified reference of a candidate about to be written, by its entityId. */
|
|
191
|
+
function srcOf(ctx: IRelationContext, entityId: string): IEdgeTarget {
|
|
192
|
+
const found = ctx.candidates.find((rc) => rc.candidate.envelope.entityId === (entityId as EntityId));
|
|
193
|
+
if (found === undefined) {
|
|
194
|
+
throw new Error(`test relater: no candidate with entityId '${entityId}'`);
|
|
195
|
+
}
|
|
196
|
+
return found.id;
|
|
197
|
+
}
|
|
198
|
+
|
|
171
199
|
function candEdge(source: string, type: string, target: string, confidence?: number): ICandidateEdge {
|
|
172
200
|
return {
|
|
173
|
-
source: source
|
|
201
|
+
source: scopedTarget(source),
|
|
174
202
|
edge: {
|
|
175
203
|
type: type as LinkType,
|
|
176
|
-
target: target
|
|
204
|
+
target: scopedTarget(target),
|
|
177
205
|
...(confidence !== undefined ? { confidence } : {})
|
|
178
206
|
}
|
|
179
207
|
};
|
|
@@ -280,7 +308,10 @@ describe('MemoryIngestOrchestrator', () => {
|
|
|
280
308
|
test('classifies, extracts, and writes one record end-to-end with host-ingest provenance + derivedFrom', async () => {
|
|
281
309
|
const store = buildStore();
|
|
282
310
|
const orch = buildOrchestrator({ store });
|
|
283
|
-
const sourceId:
|
|
311
|
+
const sourceId: IEdgeTarget = {
|
|
312
|
+
scope: 'conversations/c1' as MemoryScopeKey,
|
|
313
|
+
id: 'turn-3' as MemoryId
|
|
314
|
+
};
|
|
284
315
|
const result = await orch.ingestItem({ id: 'doc-1', content: 'the sky is blue', sourceId });
|
|
285
316
|
expect(result).toSucceedAndSatisfy((r: IIngestItemResult) => {
|
|
286
317
|
expect(r.item.id).toBe('doc-1');
|
|
@@ -290,7 +321,7 @@ describe('MemoryIngestOrchestrator', () => {
|
|
|
290
321
|
expect(rec.resolution.verdict).toBe('new');
|
|
291
322
|
expect(rec.id).toBe('doc-1');
|
|
292
323
|
expect(rec.record?.envelope.provenance.source).toBe(HOST_INGEST_PROVENANCE_SOURCE);
|
|
293
|
-
expect(rec.record?.envelope.provenance.derivedFrom).
|
|
324
|
+
expect(rec.record?.envelope.provenance.derivedFrom).toEqual(sourceId);
|
|
294
325
|
expect(rec.record?.body).toBe('the sky is blue');
|
|
295
326
|
expect(rec.interlock).toBeUndefined();
|
|
296
327
|
});
|
|
@@ -381,6 +412,28 @@ describe('MemoryIngestOrchestrator', () => {
|
|
|
381
412
|
});
|
|
382
413
|
expect(await orch.ingestItem({ id: 'o-1', content: 'x' })).toFailWith(/no identity codec/);
|
|
383
414
|
});
|
|
415
|
+
|
|
416
|
+
test('fails loudly when a pre-existing snapshot record cannot be scoped (no codec for its kind)', async () => {
|
|
417
|
+
// The store can persist a note (it has the note codec), but the orchestrator
|
|
418
|
+
// below is wired WITHOUT a note codec — so when it snapshots the store to run
|
|
419
|
+
// the edge path, it cannot resolve the seeded note's scope and fails loudly
|
|
420
|
+
// (never silently drops it from the cycle graph). This is the branch the
|
|
421
|
+
// scope-qualified edge change added.
|
|
422
|
+
const store = buildStore();
|
|
423
|
+
await putNote(store, 'legacy', 'anchor');
|
|
424
|
+
const orch = MemoryIngestOrchestrator.create({
|
|
425
|
+
store,
|
|
426
|
+
registry: registry(),
|
|
427
|
+
// Deliberately omit the note codec (and any default) that the seeded record needs.
|
|
428
|
+
codecs: new Map<Kind, IIdentityCodec>([[numKind, new KnowledgeIdentityCodec()]]),
|
|
429
|
+
classifier: classifier(() => succeed({ kind: numKind })),
|
|
430
|
+
extractor: extractor(() => succeed([candidate(numKind, 'n-1', 7)])),
|
|
431
|
+
relationExtractor: noEdges
|
|
432
|
+
}).orThrow();
|
|
433
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(
|
|
434
|
+
/cannot resolve scope for stored record 'legacy'.*no identity codec registered for kind 'note'/i
|
|
435
|
+
);
|
|
436
|
+
});
|
|
384
437
|
});
|
|
385
438
|
|
|
386
439
|
describe('stage 4 — exact dedup (layer 1)', () => {
|
|
@@ -392,7 +445,7 @@ describe('MemoryIngestOrchestrator', () => {
|
|
|
392
445
|
});
|
|
393
446
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r: IIngestItemResult) => {
|
|
394
447
|
expect(r.records[0].disposition).toBe('deduped');
|
|
395
|
-
expect(r.records[0].resolution).toEqual({ verdict: 'duplicate-of', target: 'doc-a' });
|
|
448
|
+
expect(r.records[0].resolution).toEqual({ verdict: 'duplicate-of', target: kt('doc-a') });
|
|
396
449
|
expect(r.records[0].id).toBe('doc-a');
|
|
397
450
|
});
|
|
398
451
|
// doc-b was never written.
|
|
@@ -464,6 +517,14 @@ function mockStore(overrides: Partial<IMemoryStore>): IMemoryStore {
|
|
|
464
517
|
list:
|
|
465
518
|
overrides.list ??
|
|
466
519
|
((): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>> => Promise.resolve(succeed([]))),
|
|
520
|
+
listScoped:
|
|
521
|
+
overrides.listScoped ??
|
|
522
|
+
((): Promise<Result<ReadonlyArray<IScopedMemoryRecord>>> => Promise.resolve(succeed([]))),
|
|
523
|
+
asRecordSource:
|
|
524
|
+
overrides.asRecordSource ??
|
|
525
|
+
((): IMemoryRecordSource => ({
|
|
526
|
+
list: (): Promise<Result<ReadonlyArray<IScopedMemoryRecord>>> => Promise.resolve(succeed([]))
|
|
527
|
+
})),
|
|
467
528
|
put: overrides.put ?? ((r): Promise<Result<IMemoryRecord<unknown>>> => Promise.resolve(succeed(r))),
|
|
468
529
|
delete: overrides.delete ?? ((): Promise<Result<MemoryId>> => Promise.resolve(fail('n/a')))
|
|
469
530
|
};
|
|
@@ -495,7 +556,9 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
495
556
|
store,
|
|
496
557
|
vectorIndex,
|
|
497
558
|
embed,
|
|
498
|
-
entityResolver: resolver((__c, similar) =>
|
|
559
|
+
entityResolver: resolver((__c, similar) =>
|
|
560
|
+
succeed({ verdict: 'duplicate-of', target: similar[0].target })
|
|
561
|
+
),
|
|
499
562
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
500
563
|
});
|
|
501
564
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r: IIngestItemResult) => {
|
|
@@ -513,12 +576,14 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
513
576
|
store,
|
|
514
577
|
vectorIndex,
|
|
515
578
|
embed,
|
|
516
|
-
entityResolver: resolver((__c, similar) =>
|
|
579
|
+
entityResolver: resolver((__c, similar) =>
|
|
580
|
+
succeed({ verdict: 'supersede', target: similar[0].target })
|
|
581
|
+
),
|
|
517
582
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
518
583
|
});
|
|
519
584
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r: IIngestItemResult) => {
|
|
520
585
|
expect(r.records[0].disposition).toBe('written');
|
|
521
|
-
expect(r.records[0].resolution).toEqual({ verdict: 'supersede', target: 'doc-a' });
|
|
586
|
+
expect(r.records[0].resolution).toEqual({ verdict: 'supersede', target: kt('doc-a') });
|
|
522
587
|
expect(r.records[0].id).toBe('doc-b');
|
|
523
588
|
});
|
|
524
589
|
expect(await store.get(noteKind, 'doc-b' as EntityId)).toSucceedAndSatisfy(
|
|
@@ -534,7 +599,9 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
534
599
|
store,
|
|
535
600
|
vectorIndex,
|
|
536
601
|
embed,
|
|
537
|
-
entityResolver: resolver((__c, similar) =>
|
|
602
|
+
entityResolver: resolver((__c, similar) =>
|
|
603
|
+
succeed({ verdict: 'merge-into', target: similar[0].target })
|
|
604
|
+
),
|
|
538
605
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
539
606
|
});
|
|
540
607
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r: IIngestItemResult) => {
|
|
@@ -556,11 +623,11 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
556
623
|
store,
|
|
557
624
|
vectorIndex,
|
|
558
625
|
embed,
|
|
559
|
-
entityResolver: resolver(() => succeed({ verdict: 'merge-into', target: 'ghost'
|
|
626
|
+
entityResolver: resolver(() => succeed({ verdict: 'merge-into', target: kt('ghost') })),
|
|
560
627
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
561
628
|
});
|
|
562
629
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(
|
|
563
|
-
/merge-into target 'ghost' does not exist/
|
|
630
|
+
/merge-into target 'knowledge\/ghost' does not exist/
|
|
564
631
|
);
|
|
565
632
|
});
|
|
566
633
|
|
|
@@ -601,10 +668,10 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
601
668
|
|
|
602
669
|
test('a surfaced hit with no backing record is skipped', async () => {
|
|
603
670
|
const ghostIndex: IVectorIndex = {
|
|
604
|
-
add: (
|
|
605
|
-
remove: (
|
|
671
|
+
add: (t) => Promise.resolve(succeed(t.id as string)),
|
|
672
|
+
remove: (t) => Promise.resolve(succeed(t)),
|
|
606
673
|
query: (): Promise<Result<ReadonlyArray<IVectorQueryHit>>> =>
|
|
607
|
-
Promise.resolve(succeed([{
|
|
674
|
+
Promise.resolve(succeed([{ target: kt('ghost'), score: 0.99 }]))
|
|
608
675
|
};
|
|
609
676
|
const orch = buildOrchestrator({
|
|
610
677
|
store: buildStore(),
|
|
@@ -632,8 +699,8 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
|
|
|
632
699
|
|
|
633
700
|
test('a similarity-query failure surfaces loudly', async () => {
|
|
634
701
|
const failingIndex: IVectorIndex = {
|
|
635
|
-
add: (
|
|
636
|
-
remove: (
|
|
702
|
+
add: (t) => Promise.resolve(succeed(t.id as string)),
|
|
703
|
+
remove: (t) => Promise.resolve(succeed(t)),
|
|
637
704
|
query: (): Promise<Result<ReadonlyArray<IVectorQueryHit>>> => Promise.resolve(fail('query kaput'))
|
|
638
705
|
};
|
|
639
706
|
const orch = buildOrchestrator({
|
|
@@ -716,7 +783,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
716
783
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r: IIngestItemResult) => {
|
|
717
784
|
expect(r.records[0].edges).toHaveLength(1);
|
|
718
785
|
expect(r.records[0].record?.envelope.links).toEqual([
|
|
719
|
-
{ type: 'mentions', target: 'doc-a', confidence: 0.7 }
|
|
786
|
+
{ type: 'mentions', target: kt('doc-a'), confidence: 0.7 }
|
|
720
787
|
]);
|
|
721
788
|
});
|
|
722
789
|
});
|
|
@@ -731,7 +798,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
731
798
|
});
|
|
732
799
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r: IIngestItemResult) => {
|
|
733
800
|
expect(r.records).toHaveLength(2);
|
|
734
|
-
expect(r.records[0].record?.envelope.links).toEqual([{ type: 'rel', target: 'doc-c' }]);
|
|
801
|
+
expect(r.records[0].record?.envelope.links).toEqual([{ type: 'rel', target: kt('doc-c') }]);
|
|
735
802
|
expect(r.records[1].edges).toEqual([]);
|
|
736
803
|
});
|
|
737
804
|
});
|
|
@@ -743,7 +810,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
743
810
|
relationExtractor: relater(() => succeed([candEdge('ghost', 'rel', 'doc-b')]))
|
|
744
811
|
});
|
|
745
812
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(
|
|
746
|
-
/edge source 'ghost' is not a candidate being written/
|
|
813
|
+
/edge source 'knowledge\/ghost' is not a candidate being written/
|
|
747
814
|
);
|
|
748
815
|
});
|
|
749
816
|
|
|
@@ -754,7 +821,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
754
821
|
relationExtractor: relater(() => succeed([candEdge('doc-b', 'rel', 'ghost')]))
|
|
755
822
|
});
|
|
756
823
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(
|
|
757
|
-
/edge target 'ghost' resolves to neither/
|
|
824
|
+
/edge target 'knowledge\/ghost' resolves to neither/
|
|
758
825
|
);
|
|
759
826
|
});
|
|
760
827
|
|
|
@@ -783,8 +850,8 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
783
850
|
)
|
|
784
851
|
});
|
|
785
852
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r: IIngestItemResult) => {
|
|
786
|
-
expect(r.records[0].record?.envelope.links).toEqual([{ type: 'rel', target: 'doc-c' }]);
|
|
787
|
-
expect(r.records[1].record?.envelope.links).toEqual([{ type: 'rel', target: 'doc-b' }]);
|
|
853
|
+
expect(r.records[0].record?.envelope.links).toEqual([{ type: 'rel', target: kt('doc-c') }]);
|
|
854
|
+
expect(r.records[1].record?.envelope.links).toEqual([{ type: 'rel', target: kt('doc-b') }]);
|
|
788
855
|
});
|
|
789
856
|
});
|
|
790
857
|
|
|
@@ -798,7 +865,111 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
|
|
|
798
865
|
});
|
|
799
866
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r: IIngestItemResult) => {
|
|
800
867
|
expect(r.records[0].interlock).toBeUndefined();
|
|
801
|
-
expect(r.records[0].record?.envelope.links).toEqual([{ type: 'contradicts', target: 'doc-a' }]);
|
|
868
|
+
expect(r.records[0].record?.envelope.links).toEqual([{ type: 'contradicts', target: kt('doc-a') }]);
|
|
869
|
+
});
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
describe('_validateEdges cross-scope target disambiguation', () => {
|
|
873
|
+
// Two EXISTING records share the idStem 'turn-3' across two conversations
|
|
874
|
+
// (the MTM codec mints per-scope stems). A stage-5 edge target must resolve
|
|
875
|
+
// to the CORRECT (scope, id) — with bare-id keying both would alias and a
|
|
876
|
+
// wrong-scope target would silently validate. Guards the ingest-validation seam.
|
|
877
|
+
const mtmKind: Kind = 'mtm' as Kind;
|
|
878
|
+
|
|
879
|
+
function mtmRegistry(): IBodyConverterRegistry {
|
|
880
|
+
const reg = registry();
|
|
881
|
+
reg.register(mtmKind, Converters.string);
|
|
882
|
+
return reg;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const mtmCodecs: ReadonlyMap<Kind, IIdentityCodec> = new Map<Kind, IIdentityCodec>([
|
|
886
|
+
[mtmKind, new MtmIdentityCodec()]
|
|
887
|
+
]);
|
|
888
|
+
|
|
889
|
+
async function seedTurn(store: FileTreeMemoryStore, entityId: string, body: string): Promise<void> {
|
|
890
|
+
const addr = new MtmIdentityCodec().encode(entityId as EntityId).orThrow();
|
|
891
|
+
const rec: IMemoryRecord<unknown> = {
|
|
892
|
+
envelope: {
|
|
893
|
+
id: addr.idStem as MemoryId,
|
|
894
|
+
entityId: entityId as EntityId,
|
|
895
|
+
kind: mtmKind,
|
|
896
|
+
tags: [],
|
|
897
|
+
links: [],
|
|
898
|
+
created: 0,
|
|
899
|
+
updated: 0,
|
|
900
|
+
seq: 0,
|
|
901
|
+
contentHash: '',
|
|
902
|
+
provenance: { source: 'agent' }
|
|
903
|
+
},
|
|
904
|
+
body
|
|
905
|
+
};
|
|
906
|
+
(await store.put(rec)).orThrow();
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function buildMtmOrchestrator(
|
|
910
|
+
store: FileTreeMemoryStore,
|
|
911
|
+
relationExtractor: IRelationExtractor
|
|
912
|
+
): MemoryIngestOrchestrator {
|
|
913
|
+
return MemoryIngestOrchestrator.create({
|
|
914
|
+
store,
|
|
915
|
+
registry: mtmRegistry(),
|
|
916
|
+
codecs: mtmCodecs,
|
|
917
|
+
classifier: classifier(() => succeed({ kind: mtmKind })),
|
|
918
|
+
// The candidate is a fresh turn-5 under conv-a (distinct stem from the seeds).
|
|
919
|
+
extractor: extractor(() => succeed([candidate(mtmKind, 'conv-a:5', 'a new turn')])),
|
|
920
|
+
relationExtractor
|
|
921
|
+
}).orThrow();
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
async function seededStore(): Promise<FileTreeMemoryStore> {
|
|
925
|
+
const store = FileTreeMemoryStore.create({
|
|
926
|
+
root: mutableRoot(),
|
|
927
|
+
registry: mtmRegistry(),
|
|
928
|
+
codecs: mtmCodecs,
|
|
929
|
+
clock
|
|
930
|
+
}).orThrow();
|
|
931
|
+
await seedTurn(store, 'conv-a:3', 'anchor in conversation a');
|
|
932
|
+
await seedTurn(store, 'conv-b:3', 'anchor in conversation b');
|
|
933
|
+
return store;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
test('accepts a same-stem edge target that resolves to the correct scope', async () => {
|
|
937
|
+
const store = await seededStore();
|
|
938
|
+
const orch = buildMtmOrchestrator(
|
|
939
|
+
store,
|
|
940
|
+
relater((ctx) =>
|
|
941
|
+
succeed([
|
|
942
|
+
{
|
|
943
|
+
source: srcOf(ctx, 'conv-a:5'),
|
|
944
|
+
edge: { type: 'rel' as LinkType, target: kt('turn-3', 'conversations/conv-a') }
|
|
945
|
+
}
|
|
946
|
+
])
|
|
947
|
+
)
|
|
948
|
+
);
|
|
949
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r: IIngestItemResult) => {
|
|
950
|
+
expect(r.records[0].record?.envelope.links).toEqual([
|
|
951
|
+
{ type: 'rel', target: kt('turn-3', 'conversations/conv-a') }
|
|
952
|
+
]);
|
|
953
|
+
});
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
test('rejects a same-stem edge target whose scope has no matching record', async () => {
|
|
957
|
+
const store = await seededStore();
|
|
958
|
+
const orch = buildMtmOrchestrator(
|
|
959
|
+
store,
|
|
960
|
+
relater((ctx) =>
|
|
961
|
+
succeed([
|
|
962
|
+
{
|
|
963
|
+
source: srcOf(ctx, 'conv-a:5'),
|
|
964
|
+
// turn-3 exists under conv-a and conv-b, but NOT conv-z.
|
|
965
|
+
edge: { type: 'rel' as LinkType, target: kt('turn-3', 'conversations/conv-z') }
|
|
966
|
+
}
|
|
967
|
+
])
|
|
968
|
+
)
|
|
969
|
+
);
|
|
970
|
+
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(
|
|
971
|
+
/edge target 'conversations\/conv-z\/turn-3' resolves to neither a sibling candidate nor an existing record/
|
|
972
|
+
);
|
|
802
973
|
});
|
|
803
974
|
});
|
|
804
975
|
});
|
|
@@ -821,13 +992,22 @@ describe('MemoryIngestOrchestrator — contradicts→temporal interlock', () =>
|
|
|
821
992
|
store,
|
|
822
993
|
classifier: classifyFact,
|
|
823
994
|
extractor: extractor(() => succeed([candidate(factKind, 'fact-1', 'the sky is grey')])),
|
|
824
|
-
relationExtractor: relater(() =>
|
|
995
|
+
relationExtractor: relater((ctx) =>
|
|
996
|
+
succeed([
|
|
997
|
+
{
|
|
998
|
+
source: srcOf(ctx, 'fact-1'),
|
|
999
|
+
edge: { type: CONTRADICTS_LINK_TYPE, target: kt(v1Id, 'facts/entities/fact-1') }
|
|
1000
|
+
}
|
|
1001
|
+
])
|
|
1002
|
+
)
|
|
825
1003
|
});
|
|
826
1004
|
expect(await secondOrch.ingestItem({ id: 'fact-1', content: 'the sky is grey' })).toSucceedAndSatisfy(
|
|
827
1005
|
(r: IIngestItemResult) => {
|
|
828
1006
|
expect(r.records[0].interlock).toBe('temporal-versioned');
|
|
829
1007
|
expect(r.records[0].disposition).toBe('written');
|
|
830
|
-
expect(r.records[0].record?.envelope.links).toEqual([
|
|
1008
|
+
expect(r.records[0].record?.envelope.links).toEqual([
|
|
1009
|
+
{ type: CONTRADICTS_LINK_TYPE, target: kt(v1Id, 'facts/entities/fact-1') }
|
|
1010
|
+
]);
|
|
831
1011
|
}
|
|
832
1012
|
);
|
|
833
1013
|
|
|
@@ -854,7 +1034,14 @@ describe('MemoryIngestOrchestrator — contradicts→temporal interlock', () =>
|
|
|
854
1034
|
store,
|
|
855
1035
|
classifier: classifyFact,
|
|
856
1036
|
extractor: extractor(() => succeed([candidate(factKind, 'fact-1', 'grey')])),
|
|
857
|
-
relationExtractor: relater(() =>
|
|
1037
|
+
relationExtractor: relater((ctx) =>
|
|
1038
|
+
succeed([
|
|
1039
|
+
{
|
|
1040
|
+
source: srcOf(ctx, 'fact-1'),
|
|
1041
|
+
edge: { type: CONTRADICTS_LINK_TYPE, target: kt(v1Id, 'facts/entities/fact-1') }
|
|
1042
|
+
}
|
|
1043
|
+
])
|
|
1044
|
+
)
|
|
858
1045
|
});
|
|
859
1046
|
(await o2.ingestItem({ id: 'fact-1', content: 'grey' })).orThrow();
|
|
860
1047
|
|
|
@@ -1012,7 +1199,7 @@ describe('MemoryIngestOrchestrator — review-punch-list fixes', () => {
|
|
|
1012
1199
|
)
|
|
1013
1200
|
});
|
|
1014
1201
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r: IIngestItemResult) => {
|
|
1015
|
-
expect(r.records[0].record?.envelope.links).toEqual([{ type: 'rel', target: 'doc-a' }]);
|
|
1202
|
+
expect(r.records[0].record?.envelope.links).toEqual([{ type: 'rel', target: kt('doc-a') }]);
|
|
1016
1203
|
});
|
|
1017
1204
|
});
|
|
1018
1205
|
|
|
@@ -1025,7 +1212,9 @@ describe('MemoryIngestOrchestrator — review-punch-list fixes', () => {
|
|
|
1025
1212
|
store,
|
|
1026
1213
|
vectorIndex,
|
|
1027
1214
|
embed,
|
|
1028
|
-
entityResolver: resolver((__c, similar) =>
|
|
1215
|
+
entityResolver: resolver((__c, similar) =>
|
|
1216
|
+
succeed({ verdict: 'merge-into', target: similar[0].target })
|
|
1217
|
+
),
|
|
1029
1218
|
extractor: extractor(() =>
|
|
1030
1219
|
succeed([candidate(noteKind, 'doc-b', 'apple tart'), candidate(noteKind, 'doc-c', 'apple tart')])
|
|
1031
1220
|
),
|
|
@@ -1034,7 +1223,7 @@ describe('MemoryIngestOrchestrator — review-punch-list fixes', () => {
|
|
|
1034
1223
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceed();
|
|
1035
1224
|
expect(await store.get(noteKind, 'doc-a' as EntityId)).toSucceedAndSatisfy(
|
|
1036
1225
|
(rec: IMemoryRecord<unknown> | undefined) => {
|
|
1037
|
-
expect(rec?.envelope.links).toEqual([{ type: 'rel', target: 'doc-x' }]);
|
|
1226
|
+
expect(rec?.envelope.links).toEqual([{ type: 'rel', target: kt('doc-x') }]);
|
|
1038
1227
|
}
|
|
1039
1228
|
);
|
|
1040
1229
|
});
|
|
@@ -1072,19 +1261,21 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
|
|
|
1072
1261
|
const store = buildStore({ vectorIndex, embed });
|
|
1073
1262
|
await putFull(store, noteKind, 'doc-a', 'apple pie', {
|
|
1074
1263
|
tags: ['old-tag'],
|
|
1075
|
-
links: [{ type: 'existing' as LinkType, target: 'doc-z'
|
|
1264
|
+
links: [{ type: 'existing' as LinkType, target: kt('doc-z') }]
|
|
1076
1265
|
});
|
|
1077
1266
|
await putFull(store, noteKind, 'doc-x', 'target of stage-5');
|
|
1078
1267
|
const orch = buildOrchestrator({
|
|
1079
1268
|
store,
|
|
1080
1269
|
vectorIndex,
|
|
1081
1270
|
embed,
|
|
1082
|
-
entityResolver: resolver((__c, similar) =>
|
|
1271
|
+
entityResolver: resolver((__c, similar) =>
|
|
1272
|
+
succeed({ verdict: 'merge-into', target: similar[0].target })
|
|
1273
|
+
),
|
|
1083
1274
|
extractor: extractor(() =>
|
|
1084
1275
|
succeed([
|
|
1085
1276
|
candidate(noteKind, 'doc-b', 'apple tart', {
|
|
1086
1277
|
tags: ['new-tag'],
|
|
1087
|
-
links: [{ type: 'candlink' as LinkType, target: 'doc-z'
|
|
1278
|
+
links: [{ type: 'candlink' as LinkType, target: kt('doc-z') }]
|
|
1088
1279
|
})
|
|
1089
1280
|
])
|
|
1090
1281
|
),
|
|
@@ -1099,9 +1290,9 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
|
|
|
1099
1290
|
expect([...(rec?.envelope.tags ?? [])].sort()).toEqual(['new-tag', 'old-tag']);
|
|
1100
1291
|
// Original link + candidate link + stage-5 edge, each exactly once.
|
|
1101
1292
|
expect(rec?.envelope.links).toEqual([
|
|
1102
|
-
{ type: 'existing', target: 'doc-z' },
|
|
1103
|
-
{ type: 'candlink', target: 'doc-z' },
|
|
1104
|
-
{ type: 'stage5', target: 'doc-x' }
|
|
1293
|
+
{ type: 'existing', target: kt('doc-z') },
|
|
1294
|
+
{ type: 'candlink', target: kt('doc-z') },
|
|
1295
|
+
{ type: 'stage5', target: kt('doc-x') }
|
|
1105
1296
|
]);
|
|
1106
1297
|
}
|
|
1107
1298
|
);
|
|
@@ -1116,11 +1307,13 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
|
|
|
1116
1307
|
store,
|
|
1117
1308
|
vectorIndex,
|
|
1118
1309
|
embed,
|
|
1119
|
-
entityResolver: resolver((__c, similar) =>
|
|
1310
|
+
entityResolver: resolver((__c, similar) =>
|
|
1311
|
+
succeed({ verdict: 'merge-into', target: similar[0].target })
|
|
1312
|
+
),
|
|
1120
1313
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
1121
1314
|
});
|
|
1122
1315
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(
|
|
1123
|
-
/merge-into target 'conv-1' is kind 'ltm' but the candidate is kind 'note'/
|
|
1316
|
+
/merge-into target 'conversations\/conv-1' is kind 'ltm' but the candidate is kind 'note'/
|
|
1124
1317
|
);
|
|
1125
1318
|
});
|
|
1126
1319
|
|
|
@@ -1132,11 +1325,11 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
|
|
|
1132
1325
|
store,
|
|
1133
1326
|
vectorIndex,
|
|
1134
1327
|
embed,
|
|
1135
|
-
entityResolver: resolver(() => succeed({ verdict: 'supersede', target: 'ghost'
|
|
1328
|
+
entityResolver: resolver(() => succeed({ verdict: 'supersede', target: kt('ghost') })),
|
|
1136
1329
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
1137
1330
|
});
|
|
1138
1331
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(
|
|
1139
|
-
/supersede target 'ghost' does not exist/
|
|
1332
|
+
/supersede target 'knowledge\/ghost' does not exist/
|
|
1140
1333
|
);
|
|
1141
1334
|
});
|
|
1142
1335
|
|
|
@@ -1148,11 +1341,11 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
|
|
|
1148
1341
|
store,
|
|
1149
1342
|
vectorIndex,
|
|
1150
1343
|
embed,
|
|
1151
|
-
entityResolver: resolver(() => succeed({ verdict: 'duplicate-of', target: 'ghost'
|
|
1344
|
+
entityResolver: resolver(() => succeed({ verdict: 'duplicate-of', target: kt('ghost') })),
|
|
1152
1345
|
extractor: extractor(() => succeed([candidate(noteKind, 'doc-b', 'apple tart')]))
|
|
1153
1346
|
});
|
|
1154
1347
|
expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(
|
|
1155
|
-
/duplicate-of target 'ghost' does not exist/
|
|
1348
|
+
/duplicate-of target 'knowledge\/ghost' does not exist/
|
|
1156
1349
|
);
|
|
1157
1350
|
});
|
|
1158
1351
|
});
|