@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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.MemoryIndex = void 0;
|
|
8
8
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
9
|
+
const types_1 = require("../types");
|
|
9
10
|
/** Shared empty key set for `byKind` / `byTag` misses (avoids per-call allocation). */
|
|
10
11
|
const EMPTY_KEY_SET = new Set();
|
|
11
12
|
/**
|
|
@@ -33,7 +34,7 @@ class MemoryIndex {
|
|
|
33
34
|
* is a collision-proof separator across every scope/id pair the codecs produce.
|
|
34
35
|
*/
|
|
35
36
|
static _keyOf(scope, id) {
|
|
36
|
-
return
|
|
37
|
+
return (0, types_1.edgeTargetKey)({ scope, id });
|
|
37
38
|
}
|
|
38
39
|
/** {@inheritDoc IMemoryIndex.rebuild} */
|
|
39
40
|
rebuild(entries) {
|
|
@@ -75,9 +76,13 @@ class MemoryIndex {
|
|
|
75
76
|
byRecency() {
|
|
76
77
|
return this._recencyOrdered(this._byKey.keys());
|
|
77
78
|
}
|
|
79
|
+
/** {@inheritDoc IMemoryIndex.byRank} */
|
|
80
|
+
byRank() {
|
|
81
|
+
return this._rankOrdered(this._byKey.keys());
|
|
82
|
+
}
|
|
78
83
|
/** {@inheritDoc IMemoryIndex.backlinks} */
|
|
79
84
|
backlinks(target) {
|
|
80
|
-
const sources = this._backlinks.get(target);
|
|
85
|
+
const sources = this._backlinks.get((0, types_1.edgeTargetKey)(target));
|
|
81
86
|
return sources === undefined ? [] : Array.from(sources.values());
|
|
82
87
|
}
|
|
83
88
|
/**
|
|
@@ -98,6 +103,47 @@ class MemoryIndex {
|
|
|
98
103
|
return byUpdated !== 0 ? byUpdated : b.envelope.seq - a.envelope.seq;
|
|
99
104
|
});
|
|
100
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Resolve a set of composite keys to their records, ordered by
|
|
108
|
+
* {@link IMemoryEnvelope.rank} descending with recency (`updated`, then `seq`)
|
|
109
|
+
* as the tiebreak. Records with an absent `rank` sort LAST, then by recency
|
|
110
|
+
* among themselves. Computed on call (mirrors {@link MemoryIndex._recencyOrdered}) —
|
|
111
|
+
* no incremental rank-ordered view is maintained, matching the recency view's
|
|
112
|
+
* approach; the sort is over the in-memory index, never a filesystem walk.
|
|
113
|
+
*/
|
|
114
|
+
_rankOrdered(keys) {
|
|
115
|
+
const records = [];
|
|
116
|
+
for (const key of keys) {
|
|
117
|
+
const entry = this._byKey.get(key);
|
|
118
|
+
if (entry !== undefined) {
|
|
119
|
+
records.push(entry.record);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return records.sort(MemoryIndex._compareByRank);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Rank-descending comparator with an absent-`rank`-last rule and a recency
|
|
126
|
+
* (`updated`, then `seq`) tiebreak. Duplicated from the retrieve packlet's
|
|
127
|
+
* `rankCompare` deliberately: the index must not depend on `retrieve` (that
|
|
128
|
+
* package depends on the index), mirroring how `_recencyOrdered` inlines the
|
|
129
|
+
* recency ordering rather than importing `recencyCompare`.
|
|
130
|
+
*/
|
|
131
|
+
static _compareByRank(a, b) {
|
|
132
|
+
const ra = a.envelope.rank;
|
|
133
|
+
const rb = b.envelope.rank;
|
|
134
|
+
// Absent rank sorts last; two absent ranks fall through to the recency tiebreak.
|
|
135
|
+
if (ra === undefined && rb !== undefined) {
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
if (rb === undefined && ra !== undefined) {
|
|
139
|
+
return -1;
|
|
140
|
+
}
|
|
141
|
+
if (ra !== undefined && rb !== undefined && ra !== rb) {
|
|
142
|
+
return rb - ra;
|
|
143
|
+
}
|
|
144
|
+
const byUpdated = b.envelope.updated - a.envelope.updated;
|
|
145
|
+
return byUpdated !== 0 ? byUpdated : b.envelope.seq - a.envelope.seq;
|
|
146
|
+
}
|
|
101
147
|
/** Insert an entry and register all its derived associations. */
|
|
102
148
|
_add(entry) {
|
|
103
149
|
const key = MemoryIndex._keyOf(entry.scope, entry.record.envelope.id);
|
|
@@ -108,7 +154,7 @@ class MemoryIndex {
|
|
|
108
154
|
this._addToSetMap(this._byTag, tag, key);
|
|
109
155
|
}
|
|
110
156
|
for (const edge of envelope.links) {
|
|
111
|
-
this._addBacklink(edge.target, key, envelope.id);
|
|
157
|
+
this._addBacklink(edge.target, key, { scope: entry.scope, id: envelope.id });
|
|
112
158
|
}
|
|
113
159
|
}
|
|
114
160
|
/** Remove the entry at `key` (if present) and all its derived associations. */
|
|
@@ -127,25 +173,27 @@ class MemoryIndex {
|
|
|
127
173
|
this._removeBacklink(edge.target, key);
|
|
128
174
|
}
|
|
129
175
|
}
|
|
130
|
-
/** Register `
|
|
131
|
-
_addBacklink(target, sourceKey,
|
|
132
|
-
const
|
|
176
|
+
/** Register `source` (keyed by its composite `sourceKey`) as linking at `target`. */
|
|
177
|
+
_addBacklink(target, sourceKey, source) {
|
|
178
|
+
const targetKey = (0, types_1.edgeTargetKey)(target);
|
|
179
|
+
const existing = this._backlinks.get(targetKey);
|
|
133
180
|
if (existing === undefined) {
|
|
134
|
-
this._backlinks.set(
|
|
181
|
+
this._backlinks.set(targetKey, new Map([[sourceKey, source]]));
|
|
135
182
|
}
|
|
136
183
|
else {
|
|
137
|
-
existing.set(sourceKey,
|
|
184
|
+
existing.set(sourceKey, source);
|
|
138
185
|
}
|
|
139
186
|
}
|
|
140
187
|
/** Drop the backlink from `sourceKey` to `target`, removing the target map when empty. */
|
|
141
188
|
_removeBacklink(target, sourceKey) {
|
|
142
|
-
const
|
|
189
|
+
const targetKey = (0, types_1.edgeTargetKey)(target);
|
|
190
|
+
const existing = this._backlinks.get(targetKey);
|
|
143
191
|
if (existing === undefined) {
|
|
144
192
|
return;
|
|
145
193
|
}
|
|
146
194
|
existing.delete(sourceKey);
|
|
147
195
|
if (existing.size === 0) {
|
|
148
|
-
this._backlinks.delete(
|
|
196
|
+
this._backlinks.delete(targetKey);
|
|
149
197
|
}
|
|
150
198
|
}
|
|
151
199
|
/** Add `member` to the set at `mapKey`, creating the set on first use. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memoryIndex.js","sourceRoot":"","sources":["../../../src/packlets/index/memoryIndex.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, succeed } from '@fgv/ts-utils';\nimport { IMemoryRecord, Kind, MemoryId, MemoryScopeKey, Tag } from '../types';\n\n/**\n * The mutation a {@link IMemoryIndex.patch | patch} applies: a record was\n * written (`'put'`) or removed (`'delete'`).\n * @public\n */\nexport type MemoryIndexPatchOp = 'put' | 'delete';\n\n/** Shared empty key set for `byKind` / `byTag` misses (avoids per-call allocation). */\nconst EMPTY_KEY_SET: ReadonlySet<string> = new Set<string>();\n\n/**\n * A memory record paired with the {@link MemoryScopeKey | scope} it lives\n * under. The scope is not carried on the {@link IMemoryEnvelope} (it is derived\n * from the entity id by the codec), so the store threads it alongside the\n * record when patching or rebuilding the index. The `(scope, id)` pair is the\n * index's primary key, keeping records distinct across scopes that reuse a\n * filename stem (e.g. `turn-0` under different conversations in Phase C).\n * @public\n */\nexport interface IIndexedMemoryRecord {\n /** The scope the record is stored under. */\n readonly scope: MemoryScopeKey;\n /** The memory record itself. */\n readonly record: IMemoryRecord<unknown>;\n}\n\n/**\n * The derived, in-memory secondary indexes the store maintains over its\n * records. Never the source of truth — the FileTree is. The index is fully\n * rebuildable from a walk of the store ({@link IMemoryIndex.rebuild}) and is\n * patched incrementally on every write ({@link IMemoryIndex.patch}).\n *\n * @remarks\n * B1 builds the maps; link-traversal BFS over {@link IMemoryIndex.backlinks}\n * is B2. The accessors return records (not bare ids) so the B2 retrievers can\n * consume them directly.\n * @public\n */\nexport interface IMemoryIndex {\n /**\n * Replace the entire index from a full set of records (a store walk).\n * @returns The number of records indexed.\n */\n rebuild(entries: ReadonlyArray<IIndexedMemoryRecord>): Result<number>;\n\n /**\n * Apply a single incremental change. `'put'` inserts or replaces the entry\n * at its `(scope, id)` key (removing any prior associations first); `'delete'`\n * removes it.\n * @returns The entry that was applied.\n */\n patch(op: MemoryIndexPatchOp, entry: IIndexedMemoryRecord): Result<IIndexedMemoryRecord>;\n\n /** Every indexed entry (scope + record). Primary read surface for the store. */\n entries(): ReadonlyArray<IIndexedMemoryRecord>;\n\n /** Records of the given kind, in recency order (most-recently-updated first). */\n byKind(kind: Kind): ReadonlyArray<IMemoryRecord<unknown>>;\n\n /** Records carrying the given tag, in recency order. */\n byTag(tag: Tag): ReadonlyArray<IMemoryRecord<unknown>>;\n\n /** All records in recency order (most-recently-updated first). */\n byRecency(): ReadonlyArray<IMemoryRecord<unknown>>;\n\n /**\n * The ids of records whose `links` point AT `target` (inbound edges).\n * The seed map for B2 link-traversal.\n */\n backlinks(target: MemoryId): ReadonlyArray<MemoryId>;\n}\n\n/**\n * Default in-memory {@link IMemoryIndex}. Maintains the derived `byKind` /\n * `byTag` / `byRecency` / `backlinks` views incrementally; a `'put'` for an\n * already-indexed key first removes the prior entry's associations so a changed\n * kind / tag / link set never leaves a stale reference behind.\n * @public\n */\nexport class MemoryIndex implements IMemoryIndex {\n /** Primary store: `(scope, id)` composite key → indexed entry. */\n private readonly _byKey: Map<string, IIndexedMemoryRecord>;\n /** kind → set of composite keys. */\n private readonly _byKind: Map<Kind, Set<string>>;\n /** tag → set of composite keys. */\n private readonly _byTag: Map<Tag, Set<string>>;\n /**\n * link target id → (source composite key → source id). Keyed by the source's\n * `(scope, id)` composite — NOT its bare id — so two distinct source records\n * that share an id across scopes (e.g. `turn-0` in different conversations)\n * are tracked independently and removing one never drops the other's edge.\n */\n private readonly _backlinks: Map<MemoryId, Map<string, MemoryId>>;\n\n private constructor() {\n this._byKey = new Map<string, IIndexedMemoryRecord>();\n this._byKind = new Map<Kind, Set<string>>();\n this._byTag = new Map<Tag, Set<string>>();\n this._backlinks = new Map<MemoryId, Map<string, MemoryId>>();\n }\n\n /** Family-convention factory. */\n public static create(): Result<MemoryIndex> {\n return succeed(new MemoryIndex());\n }\n\n /**\n * The composite primary key for an entry: scope + id, NUL-separated. NUL\n * is excluded from both components (scope segments pass\n * `assertPortableFilenameStem`; `MemoryId` is portable-filename-safe), so it\n * is a collision-proof separator across every scope/id pair the codecs produce.\n */\n private static _keyOf(scope: MemoryScopeKey, id: MemoryId): string {\n return `${scope}\\0${id}`;\n }\n\n /** {@inheritDoc IMemoryIndex.rebuild} */\n public rebuild(entries: ReadonlyArray<IIndexedMemoryRecord>): Result<number> {\n this._byKey.clear();\n this._byKind.clear();\n this._byTag.clear();\n this._backlinks.clear();\n for (const entry of entries) {\n this._add(entry);\n }\n return succeed(this._byKey.size);\n }\n\n /** {@inheritDoc IMemoryIndex.patch} */\n public patch(op: MemoryIndexPatchOp, entry: IIndexedMemoryRecord): Result<IIndexedMemoryRecord> {\n const key: string = MemoryIndex._keyOf(entry.scope, entry.record.envelope.id);\n // Always drop any prior associations for this key first, so a 'put' that\n // changes kind/tags/links cannot strand a stale reference.\n this._remove(key);\n if (op === 'put') {\n this._add(entry);\n }\n return succeed(entry);\n }\n\n /** {@inheritDoc IMemoryIndex.entries} */\n public entries(): ReadonlyArray<IIndexedMemoryRecord> {\n return Array.from(this._byKey.values());\n }\n\n /** {@inheritDoc IMemoryIndex.byKind} */\n public byKind(kind: Kind): ReadonlyArray<IMemoryRecord<unknown>> {\n return this._recencyOrdered(this._byKind.get(kind) ?? EMPTY_KEY_SET);\n }\n\n /** {@inheritDoc IMemoryIndex.byTag} */\n public byTag(tag: Tag): ReadonlyArray<IMemoryRecord<unknown>> {\n return this._recencyOrdered(this._byTag.get(tag) ?? EMPTY_KEY_SET);\n }\n\n /** {@inheritDoc IMemoryIndex.byRecency} */\n public byRecency(): ReadonlyArray<IMemoryRecord<unknown>> {\n return this._recencyOrdered(this._byKey.keys());\n }\n\n /** {@inheritDoc IMemoryIndex.backlinks} */\n public backlinks(target: MemoryId): ReadonlyArray<MemoryId> {\n const sources: Map<string, MemoryId> | undefined = this._backlinks.get(target);\n return sources === undefined ? [] : Array.from(sources.values());\n }\n\n /**\n * Resolve a set of composite keys to their records, ordered\n * most-recently-updated first (with a `seq` tiebreak so equal-`updated`\n * records sort deterministically).\n */\n private _recencyOrdered(keys: Iterable<string>): ReadonlyArray<IMemoryRecord<unknown>> {\n const records: IMemoryRecord<unknown>[] = [];\n for (const key of keys) {\n const entry: IIndexedMemoryRecord | undefined = this._byKey.get(key);\n if (entry !== undefined) {\n records.push(entry.record);\n }\n }\n return records.sort((a, b) => {\n const byUpdated: number = b.envelope.updated - a.envelope.updated;\n return byUpdated !== 0 ? byUpdated : b.envelope.seq - a.envelope.seq;\n });\n }\n\n /** Insert an entry and register all its derived associations. */\n private _add(entry: IIndexedMemoryRecord): void {\n const key: string = MemoryIndex._keyOf(entry.scope, entry.record.envelope.id);\n const envelope: IMemoryRecord<unknown>['envelope'] = entry.record.envelope;\n this._byKey.set(key, entry);\n this._addToSetMap(this._byKind, envelope.kind, key);\n for (const tag of envelope.tags) {\n this._addToSetMap(this._byTag, tag, key);\n }\n for (const edge of envelope.links) {\n this._addBacklink(edge.target, key, envelope.id);\n }\n }\n\n /** Remove the entry at `key` (if present) and all its derived associations. */\n private _remove(key: string): void {\n const entry: IIndexedMemoryRecord | undefined = this._byKey.get(key);\n if (entry === undefined) {\n return;\n }\n const envelope: IMemoryRecord<unknown>['envelope'] = entry.record.envelope;\n this._byKey.delete(key);\n this._removeFromSetMap(this._byKind, envelope.kind, key);\n for (const tag of envelope.tags) {\n this._removeFromSetMap(this._byTag, tag, key);\n }\n for (const edge of envelope.links) {\n this._removeBacklink(edge.target, key);\n }\n }\n\n /** Register `sourceId` (keyed by its composite `sourceKey`) as linking at `target`. */\n private _addBacklink(target: MemoryId, sourceKey: string, sourceId: MemoryId): void {\n const existing: Map<string, MemoryId> | undefined = this._backlinks.get(target);\n if (existing === undefined) {\n this._backlinks.set(target, new Map<string, MemoryId>([[sourceKey, sourceId]]));\n } else {\n existing.set(sourceKey, sourceId);\n }\n }\n\n /** Drop the backlink from `sourceKey` to `target`, removing the target map when empty. */\n private _removeBacklink(target: MemoryId, sourceKey: string): void {\n const existing: Map<string, MemoryId> | undefined = this._backlinks.get(target);\n if (existing === undefined) {\n return;\n }\n existing.delete(sourceKey);\n if (existing.size === 0) {\n this._backlinks.delete(target);\n }\n }\n\n /** Add `member` to the set at `mapKey`, creating the set on first use. */\n private _addToSetMap<K, M>(map: Map<K, Set<M>>, mapKey: K, member: M): void {\n const existing: Set<M> | undefined = map.get(mapKey);\n if (existing === undefined) {\n map.set(mapKey, new Set<M>([member]));\n } else {\n existing.add(member);\n }\n }\n\n /** Remove `member` from the set at `mapKey`, dropping the set when empty. */\n private _removeFromSetMap<K, M>(map: Map<K, Set<M>>, mapKey: K, member: M): void {\n const existing: Set<M> | undefined = map.get(mapKey);\n if (existing === undefined) {\n return;\n }\n existing.delete(member);\n if (existing.size === 0) {\n map.delete(mapKey);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"memoryIndex.js","sourceRoot":"","sources":["../../../src/packlets/index/memoryIndex.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, succeed } from '@fgv/ts-utils';\nimport { IEdgeTarget, IMemoryRecord, Kind, MemoryId, MemoryScopeKey, Tag, edgeTargetKey } from '../types';\n\n/**\n * The mutation a {@link IMemoryIndex.patch | patch} applies: a record was\n * written (`'put'`) or removed (`'delete'`).\n * @public\n */\nexport type MemoryIndexPatchOp = 'put' | 'delete';\n\n/** Shared empty key set for `byKind` / `byTag` misses (avoids per-call allocation). */\nconst EMPTY_KEY_SET: ReadonlySet<string> = new Set<string>();\n\n/**\n * A memory record paired with the {@link MemoryScopeKey | scope} it lives\n * under. The scope is not carried on the {@link IMemoryEnvelope} (it is derived\n * from the entity id by the codec), so the store threads it alongside the\n * record when patching or rebuilding the index. The `(scope, id)` pair is the\n * index's primary key, keeping records distinct across scopes that reuse a\n * filename stem (e.g. `turn-0` under different conversations in Phase C).\n * @public\n */\nexport interface IIndexedMemoryRecord {\n /** The scope the record is stored under. */\n readonly scope: MemoryScopeKey;\n /** The memory record itself. */\n readonly record: IMemoryRecord<unknown>;\n}\n\n/**\n * The derived, in-memory secondary indexes the store maintains over its\n * records. Never the source of truth — the FileTree is. The index is fully\n * rebuildable from a walk of the store ({@link IMemoryIndex.rebuild}) and is\n * patched incrementally on every write ({@link IMemoryIndex.patch}).\n *\n * @remarks\n * B1 builds the maps; link-traversal BFS over {@link IMemoryIndex.backlinks}\n * is B2. The accessors return records (not bare ids) so the B2 retrievers can\n * consume them directly.\n * @public\n */\nexport interface IMemoryIndex {\n /**\n * Replace the entire index from a full set of records (a store walk).\n * @returns The number of records indexed.\n */\n rebuild(entries: ReadonlyArray<IIndexedMemoryRecord>): Result<number>;\n\n /**\n * Apply a single incremental change. `'put'` inserts or replaces the entry\n * at its `(scope, id)` key (removing any prior associations first); `'delete'`\n * removes it.\n * @returns The entry that was applied.\n */\n patch(op: MemoryIndexPatchOp, entry: IIndexedMemoryRecord): Result<IIndexedMemoryRecord>;\n\n /** Every indexed entry (scope + record). Primary read surface for the store. */\n entries(): ReadonlyArray<IIndexedMemoryRecord>;\n\n /** Records of the given kind, in recency order (most-recently-updated first). */\n byKind(kind: Kind): ReadonlyArray<IMemoryRecord<unknown>>;\n\n /** Records carrying the given tag, in recency order. */\n byTag(tag: Tag): ReadonlyArray<IMemoryRecord<unknown>>;\n\n /** All records in recency order (most-recently-updated first). */\n byRecency(): ReadonlyArray<IMemoryRecord<unknown>>;\n\n /**\n * All records ordered by store-computed {@link IMemoryEnvelope.rank} descending,\n * with recency (most-recently-updated, then `seq`) as a tiebreak. Records with\n * an absent `rank` sort LAST (after every ranked record), then by recency among\n * themselves. Serves a bounded top-M ({@link IMemoryEnvelope.rank}-ordered) page\n * from the in-memory index with no full-vault (filesystem) scan.\n */\n byRank(): ReadonlyArray<IMemoryRecord<unknown>>;\n\n /**\n * The scope-qualified sources of records whose `links` point AT `target`\n * (inbound edges), keyed on the target's `(scope, id)` address. The seed map\n * for B2 link-traversal; results are {@link IEdgeTarget}s so a caller can feed\n * them straight back in as further traversal seeds.\n */\n backlinks(target: IEdgeTarget): ReadonlyArray<IEdgeTarget>;\n}\n\n/**\n * Default in-memory {@link IMemoryIndex}. Maintains the derived `byKind` /\n * `byTag` / `byRecency` / `backlinks` views incrementally; a `'put'` for an\n * already-indexed key first removes the prior entry's associations so a changed\n * kind / tag / link set never leaves a stale reference behind.\n * @public\n */\nexport class MemoryIndex implements IMemoryIndex {\n /** Primary store: `(scope, id)` composite key → indexed entry. */\n private readonly _byKey: Map<string, IIndexedMemoryRecord>;\n /** kind → set of composite keys. */\n private readonly _byKind: Map<Kind, Set<string>>;\n /** tag → set of composite keys. */\n private readonly _byTag: Map<Tag, Set<string>>;\n /**\n * canonical target key (`edgeTargetKey`) → (source composite key → source\n * {@link IEdgeTarget}). The OUTER map is keyed on the scope-qualified target's\n * canonical `(scope, id)` string — NOT the target's bare id — so an edge to\n * `turn-3` in one conversation is tracked separately from `turn-3` in another.\n * The INNER map is keyed by the source's `(scope, id)` composite so two distinct\n * source records that share an id across scopes are tracked independently and\n * removing one never drops the other's edge.\n */\n private readonly _backlinks: Map<string, Map<string, IEdgeTarget>>;\n\n private constructor() {\n this._byKey = new Map<string, IIndexedMemoryRecord>();\n this._byKind = new Map<Kind, Set<string>>();\n this._byTag = new Map<Tag, Set<string>>();\n this._backlinks = new Map<string, Map<string, IEdgeTarget>>();\n }\n\n /** Family-convention factory. */\n public static create(): Result<MemoryIndex> {\n return succeed(new MemoryIndex());\n }\n\n /**\n * The composite primary key for an entry: scope + id, NUL-separated. NUL\n * is excluded from both components (scope segments pass\n * `assertPortableFilenameStem`; `MemoryId` is portable-filename-safe), so it\n * is a collision-proof separator across every scope/id pair the codecs produce.\n */\n private static _keyOf(scope: MemoryScopeKey, id: MemoryId): string {\n return edgeTargetKey({ scope, id });\n }\n\n /** {@inheritDoc IMemoryIndex.rebuild} */\n public rebuild(entries: ReadonlyArray<IIndexedMemoryRecord>): Result<number> {\n this._byKey.clear();\n this._byKind.clear();\n this._byTag.clear();\n this._backlinks.clear();\n for (const entry of entries) {\n this._add(entry);\n }\n return succeed(this._byKey.size);\n }\n\n /** {@inheritDoc IMemoryIndex.patch} */\n public patch(op: MemoryIndexPatchOp, entry: IIndexedMemoryRecord): Result<IIndexedMemoryRecord> {\n const key: string = MemoryIndex._keyOf(entry.scope, entry.record.envelope.id);\n // Always drop any prior associations for this key first, so a 'put' that\n // changes kind/tags/links cannot strand a stale reference.\n this._remove(key);\n if (op === 'put') {\n this._add(entry);\n }\n return succeed(entry);\n }\n\n /** {@inheritDoc IMemoryIndex.entries} */\n public entries(): ReadonlyArray<IIndexedMemoryRecord> {\n return Array.from(this._byKey.values());\n }\n\n /** {@inheritDoc IMemoryIndex.byKind} */\n public byKind(kind: Kind): ReadonlyArray<IMemoryRecord<unknown>> {\n return this._recencyOrdered(this._byKind.get(kind) ?? EMPTY_KEY_SET);\n }\n\n /** {@inheritDoc IMemoryIndex.byTag} */\n public byTag(tag: Tag): ReadonlyArray<IMemoryRecord<unknown>> {\n return this._recencyOrdered(this._byTag.get(tag) ?? EMPTY_KEY_SET);\n }\n\n /** {@inheritDoc IMemoryIndex.byRecency} */\n public byRecency(): ReadonlyArray<IMemoryRecord<unknown>> {\n return this._recencyOrdered(this._byKey.keys());\n }\n\n /** {@inheritDoc IMemoryIndex.byRank} */\n public byRank(): ReadonlyArray<IMemoryRecord<unknown>> {\n return this._rankOrdered(this._byKey.keys());\n }\n\n /** {@inheritDoc IMemoryIndex.backlinks} */\n public backlinks(target: IEdgeTarget): ReadonlyArray<IEdgeTarget> {\n const sources: Map<string, IEdgeTarget> | undefined = this._backlinks.get(edgeTargetKey(target));\n return sources === undefined ? [] : Array.from(sources.values());\n }\n\n /**\n * Resolve a set of composite keys to their records, ordered\n * most-recently-updated first (with a `seq` tiebreak so equal-`updated`\n * records sort deterministically).\n */\n private _recencyOrdered(keys: Iterable<string>): ReadonlyArray<IMemoryRecord<unknown>> {\n const records: IMemoryRecord<unknown>[] = [];\n for (const key of keys) {\n const entry: IIndexedMemoryRecord | undefined = this._byKey.get(key);\n if (entry !== undefined) {\n records.push(entry.record);\n }\n }\n return records.sort((a, b) => {\n const byUpdated: number = b.envelope.updated - a.envelope.updated;\n return byUpdated !== 0 ? byUpdated : b.envelope.seq - a.envelope.seq;\n });\n }\n\n /**\n * Resolve a set of composite keys to their records, ordered by\n * {@link IMemoryEnvelope.rank} descending with recency (`updated`, then `seq`)\n * as the tiebreak. Records with an absent `rank` sort LAST, then by recency\n * among themselves. Computed on call (mirrors {@link MemoryIndex._recencyOrdered}) —\n * no incremental rank-ordered view is maintained, matching the recency view's\n * approach; the sort is over the in-memory index, never a filesystem walk.\n */\n private _rankOrdered(keys: Iterable<string>): ReadonlyArray<IMemoryRecord<unknown>> {\n const records: IMemoryRecord<unknown>[] = [];\n for (const key of keys) {\n const entry: IIndexedMemoryRecord | undefined = this._byKey.get(key);\n if (entry !== undefined) {\n records.push(entry.record);\n }\n }\n return records.sort(MemoryIndex._compareByRank);\n }\n\n /**\n * Rank-descending comparator with an absent-`rank`-last rule and a recency\n * (`updated`, then `seq`) tiebreak. Duplicated from the retrieve packlet's\n * `rankCompare` deliberately: the index must not depend on `retrieve` (that\n * package depends on the index), mirroring how `_recencyOrdered` inlines the\n * recency ordering rather than importing `recencyCompare`.\n */\n private static _compareByRank(a: IMemoryRecord<unknown>, b: IMemoryRecord<unknown>): number {\n const ra: number | undefined = a.envelope.rank;\n const rb: number | undefined = b.envelope.rank;\n // Absent rank sorts last; two absent ranks fall through to the recency tiebreak.\n if (ra === undefined && rb !== undefined) {\n return 1;\n }\n if (rb === undefined && ra !== undefined) {\n return -1;\n }\n if (ra !== undefined && rb !== undefined && ra !== rb) {\n return rb - ra;\n }\n const byUpdated: number = b.envelope.updated - a.envelope.updated;\n return byUpdated !== 0 ? byUpdated : b.envelope.seq - a.envelope.seq;\n }\n\n /** Insert an entry and register all its derived associations. */\n private _add(entry: IIndexedMemoryRecord): void {\n const key: string = MemoryIndex._keyOf(entry.scope, entry.record.envelope.id);\n const envelope: IMemoryRecord<unknown>['envelope'] = entry.record.envelope;\n this._byKey.set(key, entry);\n this._addToSetMap(this._byKind, envelope.kind, key);\n for (const tag of envelope.tags) {\n this._addToSetMap(this._byTag, tag, key);\n }\n for (const edge of envelope.links) {\n this._addBacklink(edge.target, key, { scope: entry.scope, id: envelope.id });\n }\n }\n\n /** Remove the entry at `key` (if present) and all its derived associations. */\n private _remove(key: string): void {\n const entry: IIndexedMemoryRecord | undefined = this._byKey.get(key);\n if (entry === undefined) {\n return;\n }\n const envelope: IMemoryRecord<unknown>['envelope'] = entry.record.envelope;\n this._byKey.delete(key);\n this._removeFromSetMap(this._byKind, envelope.kind, key);\n for (const tag of envelope.tags) {\n this._removeFromSetMap(this._byTag, tag, key);\n }\n for (const edge of envelope.links) {\n this._removeBacklink(edge.target, key);\n }\n }\n\n /** Register `source` (keyed by its composite `sourceKey`) as linking at `target`. */\n private _addBacklink(target: IEdgeTarget, sourceKey: string, source: IEdgeTarget): void {\n const targetKey: string = edgeTargetKey(target);\n const existing: Map<string, IEdgeTarget> | undefined = this._backlinks.get(targetKey);\n if (existing === undefined) {\n this._backlinks.set(targetKey, new Map<string, IEdgeTarget>([[sourceKey, source]]));\n } else {\n existing.set(sourceKey, source);\n }\n }\n\n /** Drop the backlink from `sourceKey` to `target`, removing the target map when empty. */\n private _removeBacklink(target: IEdgeTarget, sourceKey: string): void {\n const targetKey: string = edgeTargetKey(target);\n const existing: Map<string, IEdgeTarget> | undefined = this._backlinks.get(targetKey);\n if (existing === undefined) {\n return;\n }\n existing.delete(sourceKey);\n if (existing.size === 0) {\n this._backlinks.delete(targetKey);\n }\n }\n\n /** Add `member` to the set at `mapKey`, creating the set on first use. */\n private _addToSetMap<K, M>(map: Map<K, Set<M>>, mapKey: K, member: M): void {\n const existing: Set<M> | undefined = map.get(mapKey);\n if (existing === undefined) {\n map.set(mapKey, new Set<M>([member]));\n } else {\n existing.add(member);\n }\n }\n\n /** Remove `member` from the set at `mapKey`, dropping the set when empty. */\n private _removeFromSetMap<K, M>(map: Map<K, Set<M>>, mapKey: K, member: M): void {\n const existing: Set<M> | undefined = map.get(mapKey);\n if (existing === undefined) {\n return;\n }\n existing.delete(member);\n if (existing.size === 0) {\n map.delete(mapKey);\n }\n }\n}\n"]}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Result } from '@fgv/ts-utils';
|
|
2
|
-
import {
|
|
2
|
+
import { IEdgeTarget, LinkType } from '../types';
|
|
3
3
|
/**
|
|
4
|
-
* A directed edge in the link graph the cycle guard reasons over:
|
|
5
|
-
* to `target` under relation `type`.
|
|
4
|
+
* A directed edge in the link graph the cycle guard reasons over: scope-qualified
|
|
5
|
+
* `source` links to scope-qualified `target` under relation `type`.
|
|
6
6
|
* @public
|
|
7
7
|
*/
|
|
8
8
|
export interface ICycleGuardEdge {
|
|
9
|
-
readonly source:
|
|
10
|
-
readonly target:
|
|
9
|
+
readonly source: IEdgeTarget;
|
|
10
|
+
readonly target: IEdgeTarget;
|
|
11
11
|
readonly type: LinkType;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cycleGuard.d.ts","sourceRoot":"","sources":["../../../src/packlets/ingest/cycleGuard.ts"],"names":[],"mappings":"AAKA,OAAO,EAAQ,MAAM,EAA6B,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cycleGuard.d.ts","sourceRoot":"","sources":["../../../src/packlets/ingest/cycleGuard.ts"],"names":[],"mappings":"AAKA,OAAO,EAAQ,MAAM,EAA6B,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAiB,MAAM,UAAU,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB;AAOD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,CAMnE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,aAAa,CAAC,eAAe,CAAC,EACxC,QAAQ,EAAE,aAAa,CAAC,eAAe,CAAC,GACvC,MAAM,CAAC,IAAI,CAAC,CAuCd"}
|
|
@@ -7,6 +7,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.buildCycleKey = buildCycleKey;
|
|
8
8
|
exports.assertNoCycles = assertNoCycles;
|
|
9
9
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
10
|
+
const types_1 = require("../types");
|
|
11
|
+
/** Human-readable `scope/id` rendering of a scoped node, for cycle-guard diagnostics. */
|
|
12
|
+
function formatNode(node) {
|
|
13
|
+
return `${node.scope}/${node.id}`;
|
|
14
|
+
}
|
|
10
15
|
/**
|
|
11
16
|
* Build the design's `buildCycleKey` — a deterministic, canonical (RFC-8785,
|
|
12
17
|
* via `Crc32Normalizer` from `@fgv/ts-utils`) identity for a directed edge. Used
|
|
@@ -54,14 +59,16 @@ function assertNoCycles(existing, proposed) {
|
|
|
54
59
|
}
|
|
55
60
|
for (const keyed of proposedKeyed) {
|
|
56
61
|
const edge = keyed.edge;
|
|
62
|
+
const sourceKey = (0, types_1.edgeTargetKey)(edge.source);
|
|
63
|
+
const targetKey = (0, types_1.edgeTargetKey)(edge.target);
|
|
57
64
|
// A self-loop is the degenerate one-node cycle.
|
|
58
|
-
if (
|
|
59
|
-
return (0, ts_utils_1.fail)(`ingest cycle guard: edge '${edge.source}' -${edge.type}-> '${edge.target}' is a self-loop`);
|
|
65
|
+
if (sourceKey === targetKey) {
|
|
66
|
+
return (0, ts_utils_1.fail)(`ingest cycle guard: edge '${formatNode(edge.source)}' -${edge.type}-> '${formatNode(edge.target)}' is a self-loop`);
|
|
60
67
|
}
|
|
61
68
|
// Reachability: does `target` already reach `source`? If so, adding
|
|
62
69
|
// `source -> target` closes a directed cycle.
|
|
63
|
-
if (reaches(adjacency,
|
|
64
|
-
return (0, ts_utils_1.fail)(`ingest cycle guard: edge '${edge.source}' -${edge.type}-> '${edge.target}' would create a cycle`);
|
|
70
|
+
if (reaches(adjacency, targetKey, sourceKey)) {
|
|
71
|
+
return (0, ts_utils_1.fail)(`ingest cycle guard: edge '${formatNode(edge.source)}' -${edge.type}-> '${formatNode(edge.target)}' would create a cycle`);
|
|
65
72
|
}
|
|
66
73
|
addEdge(adjacency, seenKeys, keyed);
|
|
67
74
|
}
|
|
@@ -78,8 +85,8 @@ function addEdge(adjacency, seenKeys, keyed) {
|
|
|
78
85
|
return;
|
|
79
86
|
}
|
|
80
87
|
seenKeys.add(keyed.key);
|
|
81
|
-
const source = keyed.edge.source;
|
|
82
|
-
const target = keyed.edge.target;
|
|
88
|
+
const source = (0, types_1.edgeTargetKey)(keyed.edge.source);
|
|
89
|
+
const target = (0, types_1.edgeTargetKey)(keyed.edge.target);
|
|
83
90
|
const targets = adjacency.get(source);
|
|
84
91
|
if (targets === undefined) {
|
|
85
92
|
adjacency.set(source, new Set([target]));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cycleGuard.js","sourceRoot":"","sources":["../../../src/packlets/ingest/cycleGuard.ts"],"names":[],"mappings":";AAAA;;;GAGG;;
|
|
1
|
+
{"version":3,"file":"cycleGuard.js","sourceRoot":"","sources":["../../../src/packlets/ingest/cycleGuard.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Hash, Result, fail, mapResults, succeed } from '@fgv/ts-utils';\nimport { IEdgeTarget, LinkType, edgeTargetKey } from '../types';\n\n/**\n * A directed edge in the link graph the cycle guard reasons over: scope-qualified\n * `source` links to scope-qualified `target` under relation `type`.\n * @public\n */\nexport interface ICycleGuardEdge {\n readonly source: IEdgeTarget;\n readonly target: IEdgeTarget;\n readonly type: LinkType;\n}\n\n/** Human-readable `scope/id` rendering of a scoped node, for cycle-guard diagnostics. */\nfunction formatNode(node: IEdgeTarget): string {\n return `${node.scope}/${node.id}`;\n}\n\n/**\n * Build the design's `buildCycleKey` — a deterministic, canonical (RFC-8785,\n * via `Crc32Normalizer` from `@fgv/ts-utils`) identity for a directed edge. Used\n * to de-duplicate proposed edges so a repeated `(source, target, type)` proposal\n * contributes a single graph edge (and never spuriously \"re-closes\" a cycle).\n * @public\n */\nexport function buildCycleKey(edge: ICycleGuardEdge): Result<string> {\n return new Hash.Crc32Normalizer().computeHash({\n source: edge.source,\n target: edge.target,\n type: edge.type\n });\n}\n\n/**\n * Write-time cycle guard. Given the graph's EXISTING directed edges plus a set of\n * PROPOSED edges, verify that adding the proposed edges keeps the union graph a\n * DAG. Fails loudly, naming the first proposed edge that would close a directed\n * cycle.\n *\n * @remarks\n * Distinct from the read-time BFS in `LinkTraversalRetriever` (which tolerates\n * cycles via a visited-set): this is a WRITE-time admission check. Cycle\n * detection is DFS reachability — a proposed `source -> target` edge closes a\n * cycle iff `target` already reaches `source` in the graph built so far. Proposed\n * edges are folded in one at a time (in order), so an intra-batch cycle\n * (`A->B`, `B->A` both proposed) is caught as well as a batch-vs-existing cycle.\n *\n * See the design note §4: this enforces GLOBAL directed-acyclicity over all link\n * types (conservative — a mutual associative pair is rejected). Callers that need\n * mutual links disable the guard.\n * @public\n */\nexport function assertNoCycles(\n existing: ReadonlyArray<ICycleGuardEdge>,\n proposed: ReadonlyArray<ICycleGuardEdge>\n): Result<true> {\n // Canonically key every edge up front (the only fallible step); the graph\n // building + cycle detection below is then pure.\n return keyEdges(existing).onSuccess((existingKeyed) =>\n keyEdges(proposed).onSuccess((proposedKeyed) => {\n // Adjacency map, de-duplicated by canonical edge key so a repeated edge is\n // one arc. Seed with the existing edges, then fold in each proposed edge,\n // checking reachability BEFORE inserting it.\n const adjacency: Map<string, Set<string>> = new Map<string, Set<string>>();\n const seenKeys: Set<string> = new Set<string>();\n for (const keyed of existingKeyed) {\n addEdge(adjacency, seenKeys, keyed);\n }\n for (const keyed of proposedKeyed) {\n const edge: ICycleGuardEdge = keyed.edge;\n const sourceKey: string = edgeTargetKey(edge.source);\n const targetKey: string = edgeTargetKey(edge.target);\n // A self-loop is the degenerate one-node cycle.\n if (sourceKey === targetKey) {\n return fail(\n `ingest cycle guard: edge '${formatNode(edge.source)}' -${edge.type}-> '${formatNode(\n edge.target\n )}' is a self-loop`\n );\n }\n // Reachability: does `target` already reach `source`? If so, adding\n // `source -> target` closes a directed cycle.\n if (reaches(adjacency, targetKey, sourceKey)) {\n return fail(\n `ingest cycle guard: edge '${formatNode(edge.source)}' -${edge.type}-> '${formatNode(\n edge.target\n )}' would create a cycle`\n );\n }\n addEdge(adjacency, seenKeys, keyed);\n }\n return succeed(true);\n })\n );\n}\n\n/** An edge paired with its canonical cycle key. */\ninterface IKeyedEdge {\n readonly edge: ICycleGuardEdge;\n readonly key: string;\n}\n\n/** Canonically key a set of edges (the guard's only fallible step). */\nfunction keyEdges(edges: ReadonlyArray<ICycleGuardEdge>): Result<ReadonlyArray<IKeyedEdge>> {\n return mapResults(edges.map((edge) => buildCycleKey(edge).onSuccess((key) => succeed({ edge, key }))));\n}\n\n/** Add an edge to the adjacency map, de-duplicated by its canonical key. */\nfunction addEdge(adjacency: Map<string, Set<string>>, seenKeys: Set<string>, keyed: IKeyedEdge): void {\n if (seenKeys.has(keyed.key)) {\n return;\n }\n seenKeys.add(keyed.key);\n const source: string = edgeTargetKey(keyed.edge.source);\n const target: string = edgeTargetKey(keyed.edge.target);\n const targets: Set<string> | undefined = adjacency.get(source);\n if (targets === undefined) {\n adjacency.set(source, new Set<string>([target]));\n } else {\n targets.add(target);\n }\n}\n\n/** DFS reachability: is `to` reachable from `from` over `adjacency`? */\nfunction reaches(adjacency: ReadonlyMap<string, Set<string>>, from: string, to: string): boolean {\n const visited: Set<string> = new Set<string>();\n const stack: string[] = [from];\n while (stack.length > 0) {\n const node: string = stack.pop() as string;\n if (node === to) {\n return true;\n }\n if (visited.has(node)) {\n continue;\n }\n visited.add(node);\n const neighbors: Set<string> | undefined = adjacency.get(node);\n if (neighbors !== undefined) {\n for (const neighbor of neighbors) {\n if (!visited.has(neighbor)) {\n stack.push(neighbor);\n }\n }\n }\n }\n return false;\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Result } from '@fgv/ts-utils';
|
|
2
|
-
import {
|
|
2
|
+
import { IEdgeTarget } from '../types';
|
|
3
3
|
import { ICandidateEdge, ICandidateRecord, IEntityResolutionCandidate, IIngestItem, IMemoryClassification, ResolutionVerdict } from './model';
|
|
4
4
|
/**
|
|
5
5
|
* Stage 2 — the host's classifier. Decides what {@link IIngestItem} maps to
|
|
@@ -67,8 +67,8 @@ export interface IRelationContext {
|
|
|
67
67
|
export interface IRelationCandidate {
|
|
68
68
|
/** The candidate about to be written. */
|
|
69
69
|
readonly candidate: ICandidateRecord;
|
|
70
|
-
/** Its resolved reference
|
|
71
|
-
readonly id:
|
|
70
|
+
/** Its resolved scope-qualified reference (codec `(scope, idStem)` — the stable entity address). */
|
|
71
|
+
readonly id: IEdgeTarget;
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* Stage 5 — the host's relation extractor. Proposes attributed edges among the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hostStages.d.ts","sourceRoot":"","sources":["../../../src/packlets/ingest/hostStages.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"hostStages.d.ts","sourceRoot":"","sources":["../../../src/packlets/ingest/hostStages.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,0BAA0B,EAC1B,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC;CACrE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,CACL,IAAI,EAAE,WAAW,EACjB,cAAc,EAAE,qBAAqB,GACpC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;CACrD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,aAAa,CAAC,0BAA0B,CAAC,GACjD,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC;CACvC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,iFAAiF;IACjF,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACxD;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,oGAAoG;IACpG,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;CACnF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hostStages.js","sourceRoot":"","sources":["../../../src/packlets/ingest/hostStages.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result } from '@fgv/ts-utils';\nimport {
|
|
1
|
+
{"version":3,"file":"hostStages.js","sourceRoot":"","sources":["../../../src/packlets/ingest/hostStages.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result } from '@fgv/ts-utils';\nimport { IEdgeTarget } from '../types';\nimport {\n ICandidateEdge,\n ICandidateRecord,\n IEntityResolutionCandidate,\n IIngestItem,\n IMemoryClassification,\n ResolutionVerdict\n} from './model';\n\n/**\n * Stage 2 — the host's classifier. Decides what {@link IIngestItem} maps to\n * which memory {@link Kind} (and optional tags / confidence). LOCKED as a\n * separate staged interface (OQ-10): the host plugs its existing classifier\n * machinery in here rather than surrendering to an opaque ingestor.\n * @public\n */\nexport interface IMemoryClassifier {\n /**\n * Classify one item. A `fail` aborts the item's ingest loudly (fgv never\n * guesses a kind).\n */\n classify(item: IIngestItem): Promise<Result<IMemoryClassification>>;\n}\n\n/**\n * Stage 3 — the host's fact extractor. Turns a classified item into zero or more\n * {@link ICandidateRecord}s. Each candidate's body is validated against the\n * kind's registered Converter by fgv before it can reach the store (the typed\n * validation boundary — no unchecked host data is persisted).\n * @public\n */\nexport interface IFactExtractor {\n /**\n * Extract candidate records from a classified item. An empty array is a valid\n * result (the item yielded nothing memorable).\n */\n extract(\n item: IIngestItem,\n classification: IMemoryClassification\n ): Promise<Result<ReadonlyArray<ICandidateRecord>>>;\n}\n\n/**\n * Stage 4 (optional) — the host's entity resolver (OQ-13, LOCKED OPTIONAL). When\n * supplied, fgv surfaces near-duplicate {@link IEntityResolutionCandidate}s (from\n * layer-2 similarity search) and the resolver returns a\n * {@link ResolutionVerdict}. When ABSENT, stage-4 dedup falls back to\n * exact-`{ kind, body }`-hash only — the deterministic-identity host path.\n * @public\n */\nexport interface IEntityResolver {\n /**\n * Decide whether `candidate` is new, a duplicate of / supersedes / merges into\n * one of the surfaced `similar` records. `similar` is non-empty and ordered by\n * descending score when the resolver is invoked (fgv only calls it when\n * layer-2 surfaces at least one over-threshold neighbor).\n */\n resolve(\n candidate: ICandidateRecord,\n similar: ReadonlyArray<IEntityResolutionCandidate>\n ): Promise<Result<ResolutionVerdict>>;\n}\n\n/**\n * The context fgv hands the host's relation extractor (stage 5): the source item\n * plus the candidates fgv is about to write, each paired with its resolved\n * reference id (the codec `idStem`). The extractor proposes attributed edges over\n * these ids and existing store records.\n * @public\n */\nexport interface IRelationContext {\n /** The item being ingested. */\n readonly item: IIngestItem;\n /** The candidates fgv is about to write, each with its resolved reference id. */\n readonly candidates: ReadonlyArray<IRelationCandidate>;\n}\n\n/**\n * A candidate paired with its resolved reference id, handed to the relation\n * extractor so it can source edges from it.\n * @public\n */\nexport interface IRelationCandidate {\n /** The candidate about to be written. */\n readonly candidate: ICandidateRecord;\n /** Its resolved scope-qualified reference (codec `(scope, idStem)` — the stable entity address). */\n readonly id: IEdgeTarget;\n}\n\n/**\n * Stage 5 — the host's relation extractor. Proposes attributed edges among the\n * candidates and existing records. fgv owns the validation, the write-time cycle\n * guard, and the actual persistence of the edges (the host brings only the\n * relationship judgment).\n * @public\n */\nexport interface IRelationExtractor {\n /**\n * Propose the edges to attach for this ingest. An empty array is valid (no\n * relations). Every proposed {@link ICandidateEdge.source | source} must be one\n * of the context's candidate reference ids.\n */\n relate(context: IRelationContext): Promise<Result<ReadonlyArray<ICandidateEdge>>>;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IEdge, IMemoryEnvelope, IMemoryRecord, Kind, MemoryId, Tag } from '../types';
|
|
1
|
+
import { IEdge, IEdgeTarget, IMemoryEnvelope, IMemoryRecord, Kind, MemoryId, Tag } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* A single unit of raw source material handed to the ingest pipeline. The host
|
|
4
4
|
* owns the shape of {@link IIngestItem.content | content} — fgv never interprets
|
|
@@ -22,12 +22,15 @@ export interface IIngestItem {
|
|
|
22
22
|
*/
|
|
23
23
|
readonly content: unknown;
|
|
24
24
|
/**
|
|
25
|
-
* Optional back-link to the memory record this item was derived
|
|
26
|
-
* MTM turn an extracted fact came from). When present, fgv stamps
|
|
27
|
-
* {@link IProvenance.derivedFrom | provenance.derivedFrom} on every record
|
|
28
|
-
* ingested from this item (stage 6) — the cross-kind provenance spine.
|
|
25
|
+
* Optional scope-qualified back-link to the memory record this item was derived
|
|
26
|
+
* from (e.g. the MTM turn an extracted fact came from). When present, fgv stamps
|
|
27
|
+
* it as {@link IProvenance.derivedFrom | provenance.derivedFrom} on every record
|
|
28
|
+
* ingested from this item (stage 6) — the cross-kind provenance spine. A
|
|
29
|
+
* scope-qualified {@link IEdgeTarget} (not a bare {@link MemoryId}) because
|
|
30
|
+
* per-scope codecs legally reuse a stem across scopes, so a bare id would be
|
|
31
|
+
* ambiguous.
|
|
29
32
|
*/
|
|
30
|
-
readonly sourceId?:
|
|
33
|
+
readonly sourceId?: IEdgeTarget;
|
|
31
34
|
/** Optional opaque metadata carried alongside the item; never interpreted by fgv. */
|
|
32
35
|
readonly metadata?: Record<string, unknown>;
|
|
33
36
|
}
|
|
@@ -78,8 +81,14 @@ export interface ICandidateRecord {
|
|
|
78
81
|
* @public
|
|
79
82
|
*/
|
|
80
83
|
export interface IEntityResolutionCandidate {
|
|
81
|
-
/**
|
|
82
|
-
|
|
84
|
+
/**
|
|
85
|
+
* The existing record's scope-qualified `(scope, id)` address. Scope-qualified
|
|
86
|
+
* (not a bare {@link MemoryId}) because per-scope codecs legally mint the same
|
|
87
|
+
* stem under different scopes — the {@link ResolutionVerdict} target the resolver
|
|
88
|
+
* returns must round-trip this exact address so the write binds the intended
|
|
89
|
+
* record.
|
|
90
|
+
*/
|
|
91
|
+
readonly target: IEdgeTarget;
|
|
83
92
|
/** The existing record. */
|
|
84
93
|
readonly record: IMemoryRecord<unknown>;
|
|
85
94
|
/** The backend similarity score (higher = more similar). */
|
|
@@ -88,20 +97,22 @@ export interface IEntityResolutionCandidate {
|
|
|
88
97
|
/**
|
|
89
98
|
* The four dedup verdicts a {@link IEntityResolver} (or fgv's exact-match layer)
|
|
90
99
|
* returns for a candidate. See the design note §3 for the verdict → write
|
|
91
|
-
* disposition mapping.
|
|
100
|
+
* disposition mapping. Each target-bearing arm carries a scope-qualified
|
|
101
|
+
* {@link IEdgeTarget} (not a bare {@link MemoryId}) so the verdict resolves to a
|
|
102
|
+
* single record even when a filename stem is reused across scopes.
|
|
92
103
|
* @public
|
|
93
104
|
*/
|
|
94
105
|
export type ResolutionVerdict = {
|
|
95
106
|
readonly verdict: 'new';
|
|
96
107
|
} | {
|
|
97
108
|
readonly verdict: 'duplicate-of';
|
|
98
|
-
readonly target:
|
|
109
|
+
readonly target: IEdgeTarget;
|
|
99
110
|
} | {
|
|
100
111
|
readonly verdict: 'supersede';
|
|
101
|
-
readonly target:
|
|
112
|
+
readonly target: IEdgeTarget;
|
|
102
113
|
} | {
|
|
103
114
|
readonly verdict: 'merge-into';
|
|
104
|
-
readonly target:
|
|
115
|
+
readonly target: IEdgeTarget;
|
|
105
116
|
};
|
|
106
117
|
/**
|
|
107
118
|
* How a candidate was ultimately written (or not) after resolution.
|
|
@@ -123,8 +134,8 @@ export type IngestDisposition = 'written' | 'deduped' | 'merged';
|
|
|
123
134
|
* @public
|
|
124
135
|
*/
|
|
125
136
|
export interface ICandidateEdge {
|
|
126
|
-
/** The reference
|
|
127
|
-
readonly source:
|
|
137
|
+
/** The scope-qualified reference (codec `(scope, idStem)`) of the candidate the edge originates from. */
|
|
138
|
+
readonly source: IEdgeTarget;
|
|
128
139
|
/** The attributed edge (type / target / optional confidence / provenance). */
|
|
129
140
|
readonly edge: IEdge;
|
|
130
141
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/packlets/ingest/model.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/packlets/ingest/model.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/ingest/model.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { IEdge, IMemoryEnvelope, IMemoryRecord, Kind, MemoryId, Tag } from '../types';\n\n/**\n * A single unit of raw source material handed to the ingest pipeline. The host\n * owns the shape of {@link IIngestItem.content | content} — fgv never interprets\n * it; it flows opaquely into the host's classifier and extractor (stages 2-3).\n *\n * Single-item ingest is FIRST-CLASS: the orchestrator's primary entry point\n * takes one `IIngestItem` (per-turn streaming), and the batch entry point is a\n * convenience loop over it.\n * @public\n */\nexport interface IIngestItem {\n /**\n * Host-owned identity for this source item. Opaque to fgv; used only in\n * diagnostics and echoed back on the {@link IIngestItemResult}.\n */\n readonly id: string;\n /**\n * The opaque source payload the host's classifier / extractor understand\n * (e.g. a raw turn, a document, a tool-call transcript). Never interpreted by\n * fgv.\n */\n readonly content: unknown;\n /**\n * Optional back-link to the memory record this item was derived from (e.g. the
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/ingest/model.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { IEdge, IEdgeTarget, IMemoryEnvelope, IMemoryRecord, Kind, MemoryId, Tag } from '../types';\n\n/**\n * A single unit of raw source material handed to the ingest pipeline. The host\n * owns the shape of {@link IIngestItem.content | content} — fgv never interprets\n * it; it flows opaquely into the host's classifier and extractor (stages 2-3).\n *\n * Single-item ingest is FIRST-CLASS: the orchestrator's primary entry point\n * takes one `IIngestItem` (per-turn streaming), and the batch entry point is a\n * convenience loop over it.\n * @public\n */\nexport interface IIngestItem {\n /**\n * Host-owned identity for this source item. Opaque to fgv; used only in\n * diagnostics and echoed back on the {@link IIngestItemResult}.\n */\n readonly id: string;\n /**\n * The opaque source payload the host's classifier / extractor understand\n * (e.g. a raw turn, a document, a tool-call transcript). Never interpreted by\n * fgv.\n */\n readonly content: unknown;\n /**\n * Optional scope-qualified back-link to the memory record this item was derived\n * from (e.g. the MTM turn an extracted fact came from). When present, fgv stamps\n * it as {@link IProvenance.derivedFrom | provenance.derivedFrom} on every record\n * ingested from this item (stage 6) — the cross-kind provenance spine. A\n * scope-qualified {@link IEdgeTarget} (not a bare {@link MemoryId}) because\n * per-scope codecs legally reuse a stem across scopes, so a bare id would be\n * ambiguous.\n */\n readonly sourceId?: IEdgeTarget;\n /** Optional opaque metadata carried alongside the item; never interpreted by fgv. */\n readonly metadata?: Record<string, unknown>;\n}\n\n/**\n * The host classifier's verdict for an {@link IIngestItem} (stage 2). Guides the\n * host's own extractor (stage 3) and supplies the default `kind` / `tags` /\n * `confidence` fgv stamps when the extractor does not override them.\n *\n * Extensible: the `[key: string]: unknown` arm lets the host attach an opaque\n * classification payload without changing this interface.\n * @public\n */\nexport interface IMemoryClassification {\n /** The record kind the item classifies as. */\n readonly kind: Kind;\n /** Optional tags the classifier assigns. */\n readonly tags?: ReadonlyArray<Tag>;\n /** Optional classifier confidence in `[0, 1]`; flows to `provenance.confidence`. */\n readonly confidence?: number;\n /** Opaque, host-owned extension payload — never interpreted by fgv. */\n readonly [key: string]: unknown;\n}\n\n/**\n * The store-owned envelope fields fgv derives or stamps: `id` (from the codec),\n * and the transaction-time metadata (`seq` / `contentHash` / `created` /\n * `updated`). A {@link ICandidateRecord} supplies everything EXCEPT these.\n * @public\n */\nexport type StoreStampedEnvelopeField = 'id' | 'seq' | 'contentHash' | 'created' | 'updated';\n\n/**\n * A host-extracted candidate record (stage 3 output). The host supplies the\n * full envelope MINUS the {@link StoreStampedEnvelopeField | store-stamped\n * fields} (fgv derives `id` from the codec; the store stamps the rest) plus the\n * typed body. Every candidate body is validated against the kind's registered\n * Converter before it can reach the store (the typed validation boundary).\n * @public\n */\nexport interface ICandidateRecord {\n /** The host-supplied envelope, minus the store-stamped fields. */\n readonly envelope: Omit<IMemoryEnvelope, StoreStampedEnvelopeField>;\n /** The per-kind body (a markdown string in v1), validated on ingest. */\n readonly body: unknown;\n}\n\n/**\n * A near-duplicate candidate surfaced to the {@link IEntityResolver} by stage-4\n * layer-2 similarity search: an existing record whose embedding is within the\n * similarity threshold of the incoming candidate.\n * @public\n */\nexport interface IEntityResolutionCandidate {\n /**\n * The existing record's scope-qualified `(scope, id)` address. Scope-qualified\n * (not a bare {@link MemoryId}) because per-scope codecs legally mint the same\n * stem under different scopes — the {@link ResolutionVerdict} target the resolver\n * returns must round-trip this exact address so the write binds the intended\n * record.\n */\n readonly target: IEdgeTarget;\n /** The existing record. */\n readonly record: IMemoryRecord<unknown>;\n /** The backend similarity score (higher = more similar). */\n readonly score: number;\n}\n\n/**\n * The four dedup verdicts a {@link IEntityResolver} (or fgv's exact-match layer)\n * returns for a candidate. See the design note §3 for the verdict → write\n * disposition mapping. Each target-bearing arm carries a scope-qualified\n * {@link IEdgeTarget} (not a bare {@link MemoryId}) so the verdict resolves to a\n * single record even when a filename stem is reused across scopes.\n * @public\n */\nexport type ResolutionVerdict =\n | { readonly verdict: 'new' }\n | { readonly verdict: 'duplicate-of'; readonly target: IEdgeTarget }\n | { readonly verdict: 'supersede'; readonly target: IEdgeTarget }\n | { readonly verdict: 'merge-into'; readonly target: IEdgeTarget };\n\n/**\n * How a candidate was ultimately written (or not) after resolution.\n *\n * - `written` — persisted as a fresh record (verdict `new`), or as a superseding\n * record (verdict `supersede`).\n * - `deduped` — not written; an existing record satisfied it (verdict\n * `duplicate-of`, incl. every layer-1 exact `{ kind, body }` match).\n * - `merged` — merged into an existing target entity (verdict `merge-into`).\n * @public\n */\nexport type IngestDisposition = 'written' | 'deduped' | 'merged';\n\n/**\n * A stage-5 attributed edge proposal: the {@link ICandidateEdge.edge | edge} to\n * add, sourced from {@link ICandidateEdge.source | source}. The source MUST be a\n * candidate being written in this ingest (edges land on the source record's\n * `envelope.links`); the edge's `target` must resolve to a sibling candidate or\n * an existing store record.\n * @public\n */\nexport interface ICandidateEdge {\n /** The scope-qualified reference (codec `(scope, idStem)`) of the candidate the edge originates from. */\n readonly source: IEdgeTarget;\n /** The attributed edge (type / target / optional confidence / provenance). */\n readonly edge: IEdge;\n}\n\n/**\n * Per-candidate outcome of an ingest run.\n * @public\n */\nexport interface IIngestedRecordResult {\n /** The candidate the outcome is for. */\n readonly candidate: ICandidateRecord;\n /** The resolution verdict fgv reached (or the resolver returned). */\n readonly resolution: ResolutionVerdict;\n /** What the write ultimately did. */\n readonly disposition: IngestDisposition;\n /**\n * The stored record's id: the newly-written id (`written` / `merged`), or the\n * existing target's id (`deduped`).\n */\n readonly id: MemoryId;\n /** The persisted record, when a write happened (`written` / `merged`). */\n readonly record?: IMemoryRecord<unknown>;\n /** The stage-5 edges attached to this candidate before the write. */\n readonly edges: ReadonlyArray<ICandidateEdge>;\n /**\n * Informational diagnostic, set to `'temporal-versioned'` when the\n * contradicts→temporal interlock is recognized: a `contradicts` edge was\n * attached to a candidate of a temporal kind.\n *\n * @remarks\n * A temporal kind ALWAYS writes through the store's versioned put path (that is\n * the codec's `isVersioned` behavior — the prior version is invalidated and a\n * new version written on every write, contradicts edge or not). This flag does\n * NOT cause that routing; it is a diagnostic marker that the contradicts-driven\n * scenario occurred, so callers can distinguish a contradiction-superseding\n * version from an ordinary revision.\n */\n readonly interlock?: 'temporal-versioned';\n}\n\n/**\n * The result of ingesting a single {@link IIngestItem}: the item plus the\n * per-candidate outcomes (one item can yield zero or many candidate records).\n * @public\n */\nexport interface IIngestItemResult {\n /** The item that was ingested. */\n readonly item: IIngestItem;\n /** The per-candidate outcomes, in extraction order. */\n readonly records: ReadonlyArray<IIngestedRecordResult>;\n}\n"]}
|
|
@@ -179,7 +179,8 @@ export declare class MemoryIngestOrchestrator implements IMemoryIngestOrchestrat
|
|
|
179
179
|
private _relate;
|
|
180
180
|
/**
|
|
181
181
|
* Validate stage-5 edges: each `source` must be a candidate being written; each
|
|
182
|
-
* `target` must resolve to a sibling candidate or an existing store record.
|
|
182
|
+
* `target` must resolve to a sibling candidate or an existing store record. All
|
|
183
|
+
* matching is on the canonical scope-qualified address.
|
|
183
184
|
*/
|
|
184
185
|
private _validateEdges;
|
|
185
186
|
/** Stage 6 — stamp provenance + edges, admit through the store, record the outcome. */
|
|
@@ -194,10 +195,23 @@ export declare class MemoryIngestOrchestrator implements IMemoryIngestOrchestrat
|
|
|
194
195
|
private _resolveAddress;
|
|
195
196
|
/** Run a host hook, normalizing a thrown/rejected hook into a Failure (never throws across the seam). */
|
|
196
197
|
private _capture;
|
|
197
|
-
/**
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Every existing outbound edge in the snapshot, as cycle-guard edges. The
|
|
200
|
+
* source is the record's own scope-qualified address (already resolved by
|
|
201
|
+
* {@link MemoryIngestOrchestrator._scopeRecords}); the target is the edge's
|
|
202
|
+
* own scoped target. Both ends are scoped so the guard never conflates a stem
|
|
203
|
+
* shared across scopes into one graph node.
|
|
204
|
+
*/
|
|
200
205
|
private static _existingEdges;
|
|
206
|
+
/**
|
|
207
|
+
* Resolve every snapshot record's scope-qualified `(scope, id)` address via its
|
|
208
|
+
* registered codec. Fails loudly if a record's kind has no resolvable codec —
|
|
209
|
+
* the edge path cannot place an un-scopeable record in the graph, and a missing
|
|
210
|
+
* codec for a stored kind is a real misconfiguration, not something to paper over.
|
|
211
|
+
*/
|
|
212
|
+
private _scopeRecords;
|
|
213
|
+
/** Human-readable `scope/id` rendering of a scoped target for edge-validation diagnostics. */
|
|
214
|
+
private static _formatTarget;
|
|
201
215
|
/** A provisional record for embedding a candidate (placeholder txn-time fields). */
|
|
202
216
|
private static _provisionalRecord;
|
|
203
217
|
/** Require a candidate body to be a string (the store persists only string bodies). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/packlets/ingest/orchestrator.ts"],"names":[],"mappings":"AAKA,OAAO,EAAQ,OAAO,EAAE,MAAM,EAA6B,MAAM,eAAe,CAAC;AACjF,OAAO,
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/packlets/ingest/orchestrator.ts"],"names":[],"mappings":"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"}
|