@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,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Result, succeed } from '@fgv/ts-utils';
|
|
7
|
-
import { IMemoryRecord, Kind, MemoryId, MemoryScopeKey, Tag } from '../types';
|
|
7
|
+
import { IEdgeTarget, IMemoryRecord, Kind, MemoryId, MemoryScopeKey, Tag, edgeTargetKey } from '../types';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The mutation a {@link IMemoryIndex.patch | patch} applies: a record was
|
|
@@ -72,10 +72,21 @@ export interface IMemoryIndex {
|
|
|
72
72
|
byRecency(): ReadonlyArray<IMemoryRecord<unknown>>;
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
75
|
+
* All records ordered by store-computed {@link IMemoryEnvelope.rank} descending,
|
|
76
|
+
* with recency (most-recently-updated, then `seq`) as a tiebreak. Records with
|
|
77
|
+
* an absent `rank` sort LAST (after every ranked record), then by recency among
|
|
78
|
+
* themselves. Serves a bounded top-M ({@link IMemoryEnvelope.rank}-ordered) page
|
|
79
|
+
* from the in-memory index with no full-vault (filesystem) scan.
|
|
77
80
|
*/
|
|
78
|
-
|
|
81
|
+
byRank(): ReadonlyArray<IMemoryRecord<unknown>>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The scope-qualified sources of records whose `links` point AT `target`
|
|
85
|
+
* (inbound edges), keyed on the target's `(scope, id)` address. The seed map
|
|
86
|
+
* for B2 link-traversal; results are {@link IEdgeTarget}s so a caller can feed
|
|
87
|
+
* them straight back in as further traversal seeds.
|
|
88
|
+
*/
|
|
89
|
+
backlinks(target: IEdgeTarget): ReadonlyArray<IEdgeTarget>;
|
|
79
90
|
}
|
|
80
91
|
|
|
81
92
|
/**
|
|
@@ -93,18 +104,21 @@ export class MemoryIndex implements IMemoryIndex {
|
|
|
93
104
|
/** tag → set of composite keys. */
|
|
94
105
|
private readonly _byTag: Map<Tag, Set<string>>;
|
|
95
106
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
107
|
+
* canonical target key (`edgeTargetKey`) → (source composite key → source
|
|
108
|
+
* {@link IEdgeTarget}). The OUTER map is keyed on the scope-qualified target's
|
|
109
|
+
* canonical `(scope, id)` string — NOT the target's bare id — so an edge to
|
|
110
|
+
* `turn-3` in one conversation is tracked separately from `turn-3` in another.
|
|
111
|
+
* The INNER map is keyed by the source's `(scope, id)` composite so two distinct
|
|
112
|
+
* source records that share an id across scopes are tracked independently and
|
|
113
|
+
* removing one never drops the other's edge.
|
|
100
114
|
*/
|
|
101
|
-
private readonly _backlinks: Map<
|
|
115
|
+
private readonly _backlinks: Map<string, Map<string, IEdgeTarget>>;
|
|
102
116
|
|
|
103
117
|
private constructor() {
|
|
104
118
|
this._byKey = new Map<string, IIndexedMemoryRecord>();
|
|
105
119
|
this._byKind = new Map<Kind, Set<string>>();
|
|
106
120
|
this._byTag = new Map<Tag, Set<string>>();
|
|
107
|
-
this._backlinks = new Map<
|
|
121
|
+
this._backlinks = new Map<string, Map<string, IEdgeTarget>>();
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
/** Family-convention factory. */
|
|
@@ -119,7 +133,7 @@ export class MemoryIndex implements IMemoryIndex {
|
|
|
119
133
|
* is a collision-proof separator across every scope/id pair the codecs produce.
|
|
120
134
|
*/
|
|
121
135
|
private static _keyOf(scope: MemoryScopeKey, id: MemoryId): string {
|
|
122
|
-
return
|
|
136
|
+
return edgeTargetKey({ scope, id });
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
/** {@inheritDoc IMemoryIndex.rebuild} */
|
|
@@ -166,9 +180,14 @@ export class MemoryIndex implements IMemoryIndex {
|
|
|
166
180
|
return this._recencyOrdered(this._byKey.keys());
|
|
167
181
|
}
|
|
168
182
|
|
|
183
|
+
/** {@inheritDoc IMemoryIndex.byRank} */
|
|
184
|
+
public byRank(): ReadonlyArray<IMemoryRecord<unknown>> {
|
|
185
|
+
return this._rankOrdered(this._byKey.keys());
|
|
186
|
+
}
|
|
187
|
+
|
|
169
188
|
/** {@inheritDoc IMemoryIndex.backlinks} */
|
|
170
|
-
public backlinks(target:
|
|
171
|
-
const sources: Map<string,
|
|
189
|
+
public backlinks(target: IEdgeTarget): ReadonlyArray<IEdgeTarget> {
|
|
190
|
+
const sources: Map<string, IEdgeTarget> | undefined = this._backlinks.get(edgeTargetKey(target));
|
|
172
191
|
return sources === undefined ? [] : Array.from(sources.values());
|
|
173
192
|
}
|
|
174
193
|
|
|
@@ -191,6 +210,49 @@ export class MemoryIndex implements IMemoryIndex {
|
|
|
191
210
|
});
|
|
192
211
|
}
|
|
193
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Resolve a set of composite keys to their records, ordered by
|
|
215
|
+
* {@link IMemoryEnvelope.rank} descending with recency (`updated`, then `seq`)
|
|
216
|
+
* as the tiebreak. Records with an absent `rank` sort LAST, then by recency
|
|
217
|
+
* among themselves. Computed on call (mirrors {@link MemoryIndex._recencyOrdered}) —
|
|
218
|
+
* no incremental rank-ordered view is maintained, matching the recency view's
|
|
219
|
+
* approach; the sort is over the in-memory index, never a filesystem walk.
|
|
220
|
+
*/
|
|
221
|
+
private _rankOrdered(keys: Iterable<string>): ReadonlyArray<IMemoryRecord<unknown>> {
|
|
222
|
+
const records: IMemoryRecord<unknown>[] = [];
|
|
223
|
+
for (const key of keys) {
|
|
224
|
+
const entry: IIndexedMemoryRecord | undefined = this._byKey.get(key);
|
|
225
|
+
if (entry !== undefined) {
|
|
226
|
+
records.push(entry.record);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return records.sort(MemoryIndex._compareByRank);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Rank-descending comparator with an absent-`rank`-last rule and a recency
|
|
234
|
+
* (`updated`, then `seq`) tiebreak. Duplicated from the retrieve packlet's
|
|
235
|
+
* `rankCompare` deliberately: the index must not depend on `retrieve` (that
|
|
236
|
+
* package depends on the index), mirroring how `_recencyOrdered` inlines the
|
|
237
|
+
* recency ordering rather than importing `recencyCompare`.
|
|
238
|
+
*/
|
|
239
|
+
private static _compareByRank(a: IMemoryRecord<unknown>, b: IMemoryRecord<unknown>): number {
|
|
240
|
+
const ra: number | undefined = a.envelope.rank;
|
|
241
|
+
const rb: number | undefined = b.envelope.rank;
|
|
242
|
+
// Absent rank sorts last; two absent ranks fall through to the recency tiebreak.
|
|
243
|
+
if (ra === undefined && rb !== undefined) {
|
|
244
|
+
return 1;
|
|
245
|
+
}
|
|
246
|
+
if (rb === undefined && ra !== undefined) {
|
|
247
|
+
return -1;
|
|
248
|
+
}
|
|
249
|
+
if (ra !== undefined && rb !== undefined && ra !== rb) {
|
|
250
|
+
return rb - ra;
|
|
251
|
+
}
|
|
252
|
+
const byUpdated: number = b.envelope.updated - a.envelope.updated;
|
|
253
|
+
return byUpdated !== 0 ? byUpdated : b.envelope.seq - a.envelope.seq;
|
|
254
|
+
}
|
|
255
|
+
|
|
194
256
|
/** Insert an entry and register all its derived associations. */
|
|
195
257
|
private _add(entry: IIndexedMemoryRecord): void {
|
|
196
258
|
const key: string = MemoryIndex._keyOf(entry.scope, entry.record.envelope.id);
|
|
@@ -201,7 +263,7 @@ export class MemoryIndex implements IMemoryIndex {
|
|
|
201
263
|
this._addToSetMap(this._byTag, tag, key);
|
|
202
264
|
}
|
|
203
265
|
for (const edge of envelope.links) {
|
|
204
|
-
this._addBacklink(edge.target, key, envelope.id);
|
|
266
|
+
this._addBacklink(edge.target, key, { scope: entry.scope, id: envelope.id });
|
|
205
267
|
}
|
|
206
268
|
}
|
|
207
269
|
|
|
@@ -222,25 +284,27 @@ export class MemoryIndex implements IMemoryIndex {
|
|
|
222
284
|
}
|
|
223
285
|
}
|
|
224
286
|
|
|
225
|
-
/** Register `
|
|
226
|
-
private _addBacklink(target:
|
|
227
|
-
const
|
|
287
|
+
/** Register `source` (keyed by its composite `sourceKey`) as linking at `target`. */
|
|
288
|
+
private _addBacklink(target: IEdgeTarget, sourceKey: string, source: IEdgeTarget): void {
|
|
289
|
+
const targetKey: string = edgeTargetKey(target);
|
|
290
|
+
const existing: Map<string, IEdgeTarget> | undefined = this._backlinks.get(targetKey);
|
|
228
291
|
if (existing === undefined) {
|
|
229
|
-
this._backlinks.set(
|
|
292
|
+
this._backlinks.set(targetKey, new Map<string, IEdgeTarget>([[sourceKey, source]]));
|
|
230
293
|
} else {
|
|
231
|
-
existing.set(sourceKey,
|
|
294
|
+
existing.set(sourceKey, source);
|
|
232
295
|
}
|
|
233
296
|
}
|
|
234
297
|
|
|
235
298
|
/** Drop the backlink from `sourceKey` to `target`, removing the target map when empty. */
|
|
236
|
-
private _removeBacklink(target:
|
|
237
|
-
const
|
|
299
|
+
private _removeBacklink(target: IEdgeTarget, sourceKey: string): void {
|
|
300
|
+
const targetKey: string = edgeTargetKey(target);
|
|
301
|
+
const existing: Map<string, IEdgeTarget> | undefined = this._backlinks.get(targetKey);
|
|
238
302
|
if (existing === undefined) {
|
|
239
303
|
return;
|
|
240
304
|
}
|
|
241
305
|
existing.delete(sourceKey);
|
|
242
306
|
if (existing.size === 0) {
|
|
243
|
-
this._backlinks.delete(
|
|
307
|
+
this._backlinks.delete(targetKey);
|
|
244
308
|
}
|
|
245
309
|
}
|
|
246
310
|
|
|
@@ -4,19 +4,24 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Hash, Result, fail, mapResults, succeed } from '@fgv/ts-utils';
|
|
7
|
-
import { LinkType,
|
|
7
|
+
import { IEdgeTarget, LinkType, edgeTargetKey } from '../types';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* A directed edge in the link graph the cycle guard reasons over:
|
|
11
|
-
* to `target` under relation `type`.
|
|
10
|
+
* A directed edge in the link graph the cycle guard reasons over: scope-qualified
|
|
11
|
+
* `source` links to scope-qualified `target` under relation `type`.
|
|
12
12
|
* @public
|
|
13
13
|
*/
|
|
14
14
|
export interface ICycleGuardEdge {
|
|
15
|
-
readonly source:
|
|
16
|
-
readonly target:
|
|
15
|
+
readonly source: IEdgeTarget;
|
|
16
|
+
readonly target: IEdgeTarget;
|
|
17
17
|
readonly type: LinkType;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** Human-readable `scope/id` rendering of a scoped node, for cycle-guard diagnostics. */
|
|
21
|
+
function formatNode(node: IEdgeTarget): string {
|
|
22
|
+
return `${node.scope}/${node.id}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
/**
|
|
21
26
|
* Build the design's `buildCycleKey` — a deterministic, canonical (RFC-8785,
|
|
22
27
|
* via `Crc32Normalizer` from `@fgv/ts-utils`) identity for a directed edge. Used
|
|
@@ -69,17 +74,23 @@ export function assertNoCycles(
|
|
|
69
74
|
}
|
|
70
75
|
for (const keyed of proposedKeyed) {
|
|
71
76
|
const edge: ICycleGuardEdge = keyed.edge;
|
|
77
|
+
const sourceKey: string = edgeTargetKey(edge.source);
|
|
78
|
+
const targetKey: string = edgeTargetKey(edge.target);
|
|
72
79
|
// A self-loop is the degenerate one-node cycle.
|
|
73
|
-
if (
|
|
80
|
+
if (sourceKey === targetKey) {
|
|
74
81
|
return fail(
|
|
75
|
-
`ingest cycle guard: edge '${edge.source}' -${edge.type}-> '${
|
|
82
|
+
`ingest cycle guard: edge '${formatNode(edge.source)}' -${edge.type}-> '${formatNode(
|
|
83
|
+
edge.target
|
|
84
|
+
)}' is a self-loop`
|
|
76
85
|
);
|
|
77
86
|
}
|
|
78
87
|
// Reachability: does `target` already reach `source`? If so, adding
|
|
79
88
|
// `source -> target` closes a directed cycle.
|
|
80
|
-
if (reaches(adjacency,
|
|
89
|
+
if (reaches(adjacency, targetKey, sourceKey)) {
|
|
81
90
|
return fail(
|
|
82
|
-
`ingest cycle guard: edge '${edge.source}' -${edge.type}-> '${
|
|
91
|
+
`ingest cycle guard: edge '${formatNode(edge.source)}' -${edge.type}-> '${formatNode(
|
|
92
|
+
edge.target
|
|
93
|
+
)}' would create a cycle`
|
|
83
94
|
);
|
|
84
95
|
}
|
|
85
96
|
addEdge(adjacency, seenKeys, keyed);
|
|
@@ -106,8 +117,8 @@ function addEdge(adjacency: Map<string, Set<string>>, seenKeys: Set<string>, key
|
|
|
106
117
|
return;
|
|
107
118
|
}
|
|
108
119
|
seenKeys.add(keyed.key);
|
|
109
|
-
const source: string = keyed.edge.source;
|
|
110
|
-
const target: string = keyed.edge.target;
|
|
120
|
+
const source: string = edgeTargetKey(keyed.edge.source);
|
|
121
|
+
const target: string = edgeTargetKey(keyed.edge.target);
|
|
111
122
|
const targets: Set<string> | undefined = adjacency.get(source);
|
|
112
123
|
if (targets === undefined) {
|
|
113
124
|
adjacency.set(source, new Set<string>([target]));
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Result } from '@fgv/ts-utils';
|
|
7
|
-
import {
|
|
7
|
+
import { IEdgeTarget } from '../types';
|
|
8
8
|
import {
|
|
9
9
|
ICandidateEdge,
|
|
10
10
|
ICandidateRecord,
|
|
@@ -90,8 +90,8 @@ export interface IRelationContext {
|
|
|
90
90
|
export interface IRelationCandidate {
|
|
91
91
|
/** The candidate about to be written. */
|
|
92
92
|
readonly candidate: ICandidateRecord;
|
|
93
|
-
/** Its resolved reference
|
|
94
|
-
readonly id:
|
|
93
|
+
/** Its resolved scope-qualified reference (codec `(scope, idStem)` — the stable entity address). */
|
|
94
|
+
readonly id: IEdgeTarget;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* SPDX-License-Identifier: MIT
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IEdge, IMemoryEnvelope, IMemoryRecord, Kind, MemoryId, Tag } from '../types';
|
|
6
|
+
import { IEdge, IEdgeTarget, IMemoryEnvelope, IMemoryRecord, Kind, MemoryId, Tag } from '../types';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* A single unit of raw source material handed to the ingest pipeline. The host
|
|
@@ -28,12 +28,15 @@ export interface IIngestItem {
|
|
|
28
28
|
*/
|
|
29
29
|
readonly content: unknown;
|
|
30
30
|
/**
|
|
31
|
-
* Optional back-link to the memory record this item was derived
|
|
32
|
-
* MTM turn an extracted fact came from). When present, fgv stamps
|
|
33
|
-
* {@link IProvenance.derivedFrom | provenance.derivedFrom} on every record
|
|
34
|
-
* ingested from this item (stage 6) — the cross-kind provenance spine.
|
|
31
|
+
* Optional scope-qualified back-link to the memory record this item was derived
|
|
32
|
+
* from (e.g. the MTM turn an extracted fact came from). When present, fgv stamps
|
|
33
|
+
* it as {@link IProvenance.derivedFrom | provenance.derivedFrom} on every record
|
|
34
|
+
* ingested from this item (stage 6) — the cross-kind provenance spine. A
|
|
35
|
+
* scope-qualified {@link IEdgeTarget} (not a bare {@link MemoryId}) because
|
|
36
|
+
* per-scope codecs legally reuse a stem across scopes, so a bare id would be
|
|
37
|
+
* ambiguous.
|
|
35
38
|
*/
|
|
36
|
-
readonly sourceId?:
|
|
39
|
+
readonly sourceId?: IEdgeTarget;
|
|
37
40
|
/** Optional opaque metadata carried alongside the item; never interpreted by fgv. */
|
|
38
41
|
readonly metadata?: Record<string, unknown>;
|
|
39
42
|
}
|
|
@@ -88,8 +91,14 @@ export interface ICandidateRecord {
|
|
|
88
91
|
* @public
|
|
89
92
|
*/
|
|
90
93
|
export interface IEntityResolutionCandidate {
|
|
91
|
-
/**
|
|
92
|
-
|
|
94
|
+
/**
|
|
95
|
+
* The existing record's scope-qualified `(scope, id)` address. Scope-qualified
|
|
96
|
+
* (not a bare {@link MemoryId}) because per-scope codecs legally mint the same
|
|
97
|
+
* stem under different scopes — the {@link ResolutionVerdict} target the resolver
|
|
98
|
+
* returns must round-trip this exact address so the write binds the intended
|
|
99
|
+
* record.
|
|
100
|
+
*/
|
|
101
|
+
readonly target: IEdgeTarget;
|
|
93
102
|
/** The existing record. */
|
|
94
103
|
readonly record: IMemoryRecord<unknown>;
|
|
95
104
|
/** The backend similarity score (higher = more similar). */
|
|
@@ -99,14 +108,16 @@ export interface IEntityResolutionCandidate {
|
|
|
99
108
|
/**
|
|
100
109
|
* The four dedup verdicts a {@link IEntityResolver} (or fgv's exact-match layer)
|
|
101
110
|
* returns for a candidate. See the design note §3 for the verdict → write
|
|
102
|
-
* disposition mapping.
|
|
111
|
+
* disposition mapping. Each target-bearing arm carries a scope-qualified
|
|
112
|
+
* {@link IEdgeTarget} (not a bare {@link MemoryId}) so the verdict resolves to a
|
|
113
|
+
* single record even when a filename stem is reused across scopes.
|
|
103
114
|
* @public
|
|
104
115
|
*/
|
|
105
116
|
export type ResolutionVerdict =
|
|
106
117
|
| { readonly verdict: 'new' }
|
|
107
|
-
| { readonly verdict: 'duplicate-of'; readonly target:
|
|
108
|
-
| { readonly verdict: 'supersede'; readonly target:
|
|
109
|
-
| { readonly verdict: 'merge-into'; readonly target:
|
|
118
|
+
| { readonly verdict: 'duplicate-of'; readonly target: IEdgeTarget }
|
|
119
|
+
| { readonly verdict: 'supersede'; readonly target: IEdgeTarget }
|
|
120
|
+
| { readonly verdict: 'merge-into'; readonly target: IEdgeTarget };
|
|
110
121
|
|
|
111
122
|
/**
|
|
112
123
|
* How a candidate was ultimately written (or not) after resolution.
|
|
@@ -129,8 +140,8 @@ export type IngestDisposition = 'written' | 'deduped' | 'merged';
|
|
|
129
140
|
* @public
|
|
130
141
|
*/
|
|
131
142
|
export interface ICandidateEdge {
|
|
132
|
-
/** The reference
|
|
133
|
-
readonly source:
|
|
143
|
+
/** The scope-qualified reference (codec `(scope, idStem)`) of the candidate the edge originates from. */
|
|
144
|
+
readonly source: IEdgeTarget;
|
|
134
145
|
/** The attributed edge (type / target / optional confidence / provenance). */
|
|
135
146
|
readonly edge: IEdge;
|
|
136
147
|
}
|