@fgv/ts-agent-memory 5.1.0-37 → 5.1.0-39
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/{6dfc331ddf74dc2db0d4483d0c96ea971f351c83.tar.log → b82cf6bdece20481260e6bab946179eeec9d7b46.tar.log} +116 -2
- package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +4 -4
- package/.rush/temp/operation/build/all.log +4 -4
- package/.rush/temp/operation/build/log-chunks.jsonl +4 -4
- package/.rush/temp/operation/build/state.json +1 -1
- package/.rush/temp/shrinkwrap-deps.json +222 -221
- package/config/jest.config.json +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/packlets/ingest/cycleGuard.js +111 -0
- package/dist/packlets/ingest/cycleGuard.js.map +1 -0
- package/dist/packlets/ingest/hostStages.js +6 -0
- package/dist/packlets/ingest/hostStages.js.map +1 -0
- package/dist/packlets/ingest/index.js +9 -0
- package/dist/packlets/ingest/index.js.map +1 -0
- package/dist/packlets/ingest/model.js +6 -0
- package/dist/packlets/ingest/model.js.map +1 -0
- package/dist/packlets/ingest/orchestrator.js +438 -0
- package/dist/packlets/ingest/orchestrator.js.map +1 -0
- package/dist/packlets/retrieve/index.js +1 -0
- package/dist/packlets/retrieve/index.js.map +1 -1
- package/dist/packlets/retrieve/temporalRetrievers.js +172 -0
- package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -0
- package/dist/packlets/store/fileTreeMemoryStore.js +323 -41
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/dist/packlets/tools/index.js +6 -0
- package/dist/packlets/tools/index.js.map +1 -0
- package/dist/packlets/tools/memoryTools.js +337 -0
- package/dist/packlets/tools/memoryTools.js.map +1 -0
- package/dist/packlets/types/identityCodec.js +115 -0
- package/dist/packlets/types/identityCodec.js.map +1 -1
- package/dist/packlets/types/index.js +1 -0
- package/dist/packlets/types/index.js.map +1 -1
- package/dist/packlets/types/temporal.js +85 -0
- package/dist/packlets/types/temporal.js.map +1 -0
- package/dist/packlets/types/writePolicy.js +96 -0
- package/dist/packlets/types/writePolicy.js.map +1 -1
- package/dist/test/unit/converters/antagonistRoundTrip.test.js +95 -0
- package/dist/test/unit/converters/antagonistRoundTrip.test.js.map +1 -0
- package/dist/test/unit/ingest/antagonistCycleAndParity.test.js +273 -0
- package/dist/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -0
- package/dist/test/unit/ingest/cycleGuard.test.js +54 -0
- package/dist/test/unit/ingest/cycleGuard.test.js.map +1 -0
- package/dist/test/unit/ingest/orchestrator.test.js +913 -0
- package/dist/test/unit/ingest/orchestrator.test.js.map +1 -0
- package/dist/test/unit/retrieve/temporalRetrievers.test.js +182 -0
- package/dist/test/unit/retrieve/temporalRetrievers.test.js.map +1 -0
- package/dist/test/unit/store/antagonistTemporalBoundary.test.js +120 -0
- package/dist/test/unit/store/antagonistTemporalBoundary.test.js.map +1 -0
- package/dist/test/unit/store/fileTreeMemoryStore.test.js +74 -7
- package/dist/test/unit/store/fileTreeMemoryStore.test.js.map +1 -1
- package/dist/test/unit/store/temporalStore.test.js +398 -0
- package/dist/test/unit/store/temporalStore.test.js.map +1 -0
- package/dist/test/unit/tools/memoryTools.test.js +572 -0
- package/dist/test/unit/tools/memoryTools.test.js.map +1 -0
- package/dist/test/unit/types/temporalCodec.test.js +203 -0
- package/dist/test/unit/types/temporalCodec.test.js.map +1 -0
- package/dist/test/unit/types/temporalPolicy.test.js +62 -0
- package/dist/test/unit/types/temporalPolicy.test.js.map +1 -0
- package/dist/ts-agent-memory.d.ts +1042 -10
- package/dist/tsdoc-metadata.json +1 -1
- package/etc/ts-agent-memory.api.md +284 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/packlets/ingest/cycleGuard.d.ts +41 -0
- package/lib/packlets/ingest/cycleGuard.d.ts.map +1 -0
- package/lib/packlets/ingest/cycleGuard.js +115 -0
- package/lib/packlets/ingest/cycleGuard.js.map +1 -0
- package/lib/packlets/ingest/hostStages.d.ts +88 -0
- package/lib/packlets/ingest/hostStages.d.ts.map +1 -0
- package/lib/packlets/ingest/hostStages.js +7 -0
- package/lib/packlets/ingest/hostStages.js.map +1 -0
- package/lib/packlets/ingest/index.d.ts +5 -0
- package/lib/packlets/ingest/index.d.ts.map +1 -0
- package/lib/packlets/ingest/index.js +25 -0
- package/lib/packlets/ingest/index.js.map +1 -0
- package/lib/packlets/ingest/model.d.ts +177 -0
- package/lib/packlets/ingest/model.d.ts.map +1 -0
- package/lib/packlets/ingest/model.js +7 -0
- package/lib/packlets/ingest/model.js.map +1 -0
- package/lib/packlets/ingest/orchestrator.d.ts +206 -0
- package/lib/packlets/ingest/orchestrator.d.ts.map +1 -0
- package/lib/packlets/ingest/orchestrator.js +442 -0
- package/lib/packlets/ingest/orchestrator.js.map +1 -0
- package/lib/packlets/retrieve/index.d.ts +1 -0
- package/lib/packlets/retrieve/index.d.ts.map +1 -1
- package/lib/packlets/retrieve/index.js +1 -0
- package/lib/packlets/retrieve/index.js.map +1 -1
- package/lib/packlets/retrieve/temporalRetrievers.d.ts +78 -0
- package/lib/packlets/retrieve/temporalRetrievers.d.ts.map +1 -0
- package/lib/packlets/retrieve/temporalRetrievers.js +178 -0
- package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -0
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +118 -9
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.js +322 -40
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/lib/packlets/tools/index.d.ts +2 -0
- package/lib/packlets/tools/index.d.ts.map +1 -0
- package/lib/packlets/tools/index.js +22 -0
- package/lib/packlets/tools/index.js.map +1 -0
- package/lib/packlets/tools/memoryTools.d.ts +139 -0
- package/lib/packlets/tools/memoryTools.d.ts.map +1 -0
- package/lib/packlets/tools/memoryTools.js +341 -0
- package/lib/packlets/tools/memoryTools.js.map +1 -0
- package/lib/packlets/types/identityCodec.d.ts +86 -0
- package/lib/packlets/types/identityCodec.d.ts.map +1 -1
- package/lib/packlets/types/identityCodec.js +118 -1
- package/lib/packlets/types/identityCodec.js.map +1 -1
- package/lib/packlets/types/index.d.ts +1 -0
- package/lib/packlets/types/index.d.ts.map +1 -1
- package/lib/packlets/types/index.js +1 -0
- package/lib/packlets/types/index.js.map +1 -1
- package/lib/packlets/types/temporal.d.ts +40 -0
- package/lib/packlets/types/temporal.d.ts.map +1 -0
- package/lib/packlets/types/temporal.js +92 -0
- package/lib/packlets/types/temporal.js.map +1 -0
- package/lib/packlets/types/writePolicy.d.ts +49 -0
- package/lib/packlets/types/writePolicy.d.ts.map +1 -1
- package/lib/packlets/types/writePolicy.js +98 -1
- package/lib/packlets/types/writePolicy.js.map +1 -1
- package/lib/test/unit/converters/antagonistRoundTrip.test.d.ts +10 -0
- package/lib/test/unit/converters/antagonistRoundTrip.test.d.ts.map +1 -0
- package/lib/test/unit/converters/antagonistRoundTrip.test.js +97 -0
- package/lib/test/unit/converters/antagonistRoundTrip.test.js.map +1 -0
- package/lib/test/unit/ingest/antagonistCycleAndParity.test.d.ts +9 -0
- package/lib/test/unit/ingest/antagonistCycleAndParity.test.d.ts.map +1 -0
- package/lib/test/unit/ingest/antagonistCycleAndParity.test.js +275 -0
- package/lib/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -0
- package/lib/test/unit/ingest/cycleGuard.test.d.ts +2 -0
- package/lib/test/unit/ingest/cycleGuard.test.d.ts.map +1 -0
- package/lib/test/unit/ingest/cycleGuard.test.js +56 -0
- package/lib/test/unit/ingest/cycleGuard.test.js.map +1 -0
- package/lib/test/unit/ingest/orchestrator.test.d.ts +2 -0
- package/lib/test/unit/ingest/orchestrator.test.d.ts.map +1 -0
- package/lib/test/unit/ingest/orchestrator.test.js +915 -0
- package/lib/test/unit/ingest/orchestrator.test.js.map +1 -0
- package/lib/test/unit/retrieve/temporalRetrievers.test.d.ts +2 -0
- package/lib/test/unit/retrieve/temporalRetrievers.test.d.ts.map +1 -0
- package/lib/test/unit/retrieve/temporalRetrievers.test.js +184 -0
- package/lib/test/unit/retrieve/temporalRetrievers.test.js.map +1 -0
- package/lib/test/unit/store/antagonistTemporalBoundary.test.d.ts +9 -0
- package/lib/test/unit/store/antagonistTemporalBoundary.test.d.ts.map +1 -0
- package/lib/test/unit/store/antagonistTemporalBoundary.test.js +122 -0
- package/lib/test/unit/store/antagonistTemporalBoundary.test.js.map +1 -0
- package/lib/test/unit/store/fileTreeMemoryStore.test.js +74 -7
- package/lib/test/unit/store/fileTreeMemoryStore.test.js.map +1 -1
- package/lib/test/unit/store/temporalStore.test.d.ts +2 -0
- package/lib/test/unit/store/temporalStore.test.d.ts.map +1 -0
- package/lib/test/unit/store/temporalStore.test.js +400 -0
- package/lib/test/unit/store/temporalStore.test.js.map +1 -0
- package/lib/test/unit/tools/memoryTools.test.d.ts +2 -0
- package/lib/test/unit/tools/memoryTools.test.d.ts.map +1 -0
- package/lib/test/unit/tools/memoryTools.test.js +574 -0
- package/lib/test/unit/tools/memoryTools.test.js.map +1 -0
- package/lib/test/unit/types/temporalCodec.test.d.ts +2 -0
- package/lib/test/unit/types/temporalCodec.test.d.ts.map +1 -0
- package/lib/test/unit/types/temporalCodec.test.js +205 -0
- package/lib/test/unit/types/temporalCodec.test.js.map +1 -0
- package/lib/test/unit/types/temporalPolicy.test.d.ts +2 -0
- package/lib/test/unit/types/temporalPolicy.test.d.ts.map +1 -0
- package/lib/test/unit/types/temporalPolicy.test.js +64 -0
- package/lib/test/unit/types/temporalPolicy.test.js.map +1 -0
- package/package.json +7 -7
- package/rush-logs/ts-agent-memory.build.cache.log +1 -1
- package/rush-logs/ts-agent-memory.build.log +4 -4
- package/src/index.ts +2 -0
- package/src/packlets/ingest/cycleGuard.ts +142 -0
- package/src/packlets/ingest/hostStages.ts +111 -0
- package/src/packlets/ingest/index.ts +9 -0
- package/src/packlets/ingest/model.ts +184 -0
- package/src/packlets/ingest/orchestrator.ts +797 -0
- package/src/packlets/retrieve/index.ts +1 -0
- package/src/packlets/retrieve/temporalRetrievers.ts +210 -0
- package/src/packlets/store/fileTreeMemoryStore.ts +460 -66
- package/src/packlets/tools/index.ts +6 -0
- package/src/packlets/tools/memoryTools.ts +579 -0
- package/src/packlets/types/identityCodec.ts +184 -0
- package/src/packlets/types/index.ts +1 -0
- package/src/packlets/types/temporal.ts +96 -0
- package/src/packlets/types/writePolicy.ts +127 -0
- package/src/test/unit/converters/antagonistRoundTrip.test.ts +110 -0
- package/src/test/unit/ingest/antagonistCycleAndParity.test.ts +362 -0
- package/src/test/unit/ingest/cycleGuard.test.ts +68 -0
- package/src/test/unit/ingest/orchestrator.test.ts +1158 -0
- package/src/test/unit/retrieve/temporalRetrievers.test.ts +226 -0
- package/src/test/unit/store/antagonistTemporalBoundary.test.ts +158 -0
- package/src/test/unit/store/fileTreeMemoryStore.test.ts +98 -7
- package/src/test/unit/store/temporalStore.test.ts +469 -0
- package/src/test/unit/tools/memoryTools.test.ts +771 -0
- package/src/test/unit/types/temporalCodec.test.ts +259 -0
- package/src/test/unit/types/temporalPolicy.test.ts +96 -0
- package/temp/build/lint/_eslint-5eVG3S6w.json +85 -9
- package/temp/build/typescript/ts_8nwakTlr.json +1 -1
- package/temp/ts-agent-memory.api.json +11984 -6314
- package/temp/ts-agent-memory.api.md +284 -0
package/dist/tsdoc-metadata.json
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
|
|
7
|
+
import { AiAssist } from '@fgv/ts-extras';
|
|
7
8
|
import { Brand } from '@fgv/ts-utils';
|
|
8
9
|
import { Converter } from '@fgv/ts-utils';
|
|
9
10
|
import { FileTree } from '@fgv/ts-json-base';
|
|
@@ -22,6 +23,16 @@ export type AdmissionDecision = {
|
|
|
22
23
|
readonly evict: ReadonlyArray<MemoryId>;
|
|
23
24
|
};
|
|
24
25
|
|
|
26
|
+
// @public
|
|
27
|
+
export class AsOfRetriever implements IMemoryRetriever {
|
|
28
|
+
get capabilities(): IMemoryRetrieverCapabilities;
|
|
29
|
+
static create(index: IMemoryIndex): Result<AsOfRetriever>;
|
|
30
|
+
retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// @public
|
|
34
|
+
export function assertNoCycles(existing: ReadonlyArray<ICycleGuardEdge>, proposed: ReadonlyArray<ICycleGuardEdge>): Result<true>;
|
|
35
|
+
|
|
25
36
|
// @public
|
|
26
37
|
export function assertPortableFilenameStem(stem: string): Result<string>;
|
|
27
38
|
|
|
@@ -35,6 +46,12 @@ export class BodyConverterRegistry implements IBodyConverterRegistry {
|
|
|
35
46
|
registerSchema<T>(kind: Kind, schema: JsonSchema.ISchemaValidator<T>): void;
|
|
36
47
|
}
|
|
37
48
|
|
|
49
|
+
// @public
|
|
50
|
+
export function buildCycleKey(edge: ICycleGuardEdge): Result<string>;
|
|
51
|
+
|
|
52
|
+
// @public
|
|
53
|
+
export const CONTRADICTS_LINK_TYPE: LinkType;
|
|
54
|
+
|
|
38
55
|
// @public
|
|
39
56
|
export const Convert: {
|
|
40
57
|
readonly memoryId: Converter<MemoryId>;
|
|
@@ -45,12 +62,34 @@ export const Convert: {
|
|
|
45
62
|
readonly linkType: Converter<LinkType>;
|
|
46
63
|
};
|
|
47
64
|
|
|
65
|
+
// @public
|
|
66
|
+
export function createMemoryTools(params: ICreateMemoryToolsParams): ReadonlyArray<AiAssist.IAiClientTool>;
|
|
67
|
+
|
|
68
|
+
// @public
|
|
69
|
+
export class CurrentValidRetriever implements IMemoryRetriever {
|
|
70
|
+
get capabilities(): IMemoryRetrieverCapabilities;
|
|
71
|
+
static create(index: IMemoryIndex): Result<CurrentValidRetriever>;
|
|
72
|
+
retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// @public
|
|
76
|
+
export type CycleGuardMode = 'reject' | 'off';
|
|
77
|
+
|
|
48
78
|
// @public
|
|
49
79
|
export type DedupScope = 'content' | 'entity';
|
|
50
80
|
|
|
51
81
|
// @public
|
|
52
82
|
export const DEFAULT_DEDUP_SCOPE: DedupScope;
|
|
53
83
|
|
|
84
|
+
// @public
|
|
85
|
+
export const DEFAULT_MEMORY_TOOLS: ReadonlyArray<MemoryToolName>;
|
|
86
|
+
|
|
87
|
+
// @public
|
|
88
|
+
export const DEFAULT_SIMILARITY_THRESHOLD: number;
|
|
89
|
+
|
|
90
|
+
// @public
|
|
91
|
+
export const DEFAULT_SIMILARITY_TOP_K: number;
|
|
92
|
+
|
|
54
93
|
// @public
|
|
55
94
|
export function defaultMemoryScopeEncoding(scope: MemoryScopeKey): Result<string>;
|
|
56
95
|
|
|
@@ -79,6 +118,16 @@ export class FileTreeMemoryStore implements IMemoryStore {
|
|
|
79
118
|
// @public
|
|
80
119
|
export function guardRetrieverCapabilities(query: IMemoryQuery, capabilities: IMemoryRetrieverCapabilities): Result<true>;
|
|
81
120
|
|
|
121
|
+
// @public
|
|
122
|
+
export class HistoryRetriever implements IMemoryRetriever {
|
|
123
|
+
get capabilities(): IMemoryRetrieverCapabilities;
|
|
124
|
+
static create(index: IMemoryIndex): Result<HistoryRetriever>;
|
|
125
|
+
retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// @public
|
|
129
|
+
export const HOST_INGEST_PROVENANCE_SOURCE: string;
|
|
130
|
+
|
|
82
131
|
// @public
|
|
83
132
|
export class HybridRetriever implements IMemoryRetriever {
|
|
84
133
|
get capabilities(): IMemoryRetrieverCapabilities;
|
|
@@ -95,6 +144,40 @@ export interface IBodyConverterRegistry {
|
|
|
95
144
|
registerSchema<T>(kind: Kind, schema: JsonSchema.ISchemaValidator<T>): void;
|
|
96
145
|
}
|
|
97
146
|
|
|
147
|
+
// @public
|
|
148
|
+
export interface ICandidateEdge {
|
|
149
|
+
readonly edge: IEdge;
|
|
150
|
+
readonly source: MemoryId;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// @public
|
|
154
|
+
export interface ICandidateRecord {
|
|
155
|
+
readonly body: unknown;
|
|
156
|
+
readonly envelope: Omit<IMemoryEnvelope, StoreStampedEnvelopeField>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// @public
|
|
160
|
+
export interface ICreateMemoryToolsParams {
|
|
161
|
+
readonly codecs?: ReadonlyMap<Kind, IIdentityCodec>;
|
|
162
|
+
readonly defaultCodec?: IIdentityCodec;
|
|
163
|
+
readonly handleFor?: (record: IMemoryRecord<unknown>) => string;
|
|
164
|
+
readonly kinds?: ReadonlyArray<Kind>;
|
|
165
|
+
readonly registry: IBodyConverterRegistry;
|
|
166
|
+
readonly retriever: IMemoryRetriever;
|
|
167
|
+
readonly store: IMemoryStore;
|
|
168
|
+
readonly tools?: ReadonlyArray<MemoryToolName>;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// @public
|
|
172
|
+
export interface ICycleGuardEdge {
|
|
173
|
+
// (undocumented)
|
|
174
|
+
readonly source: MemoryId;
|
|
175
|
+
// (undocumented)
|
|
176
|
+
readonly target: MemoryId;
|
|
177
|
+
// (undocumented)
|
|
178
|
+
readonly type: LinkType;
|
|
179
|
+
}
|
|
180
|
+
|
|
98
181
|
// @public
|
|
99
182
|
export interface IEdge {
|
|
100
183
|
readonly confidence?: number;
|
|
@@ -105,6 +188,23 @@ export interface IEdge {
|
|
|
105
188
|
readonly valid_at?: number;
|
|
106
189
|
}
|
|
107
190
|
|
|
191
|
+
// @public
|
|
192
|
+
export interface IEntityResolutionCandidate {
|
|
193
|
+
readonly id: MemoryId;
|
|
194
|
+
readonly record: IMemoryRecord<unknown>;
|
|
195
|
+
readonly score: number;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// @public
|
|
199
|
+
export interface IEntityResolver {
|
|
200
|
+
resolve(candidate: ICandidateRecord, similar: ReadonlyArray<IEntityResolutionCandidate>): Promise<Result<ResolutionVerdict>>;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// @public
|
|
204
|
+
export interface IFactExtractor {
|
|
205
|
+
extract(item: IIngestItem, classification: IMemoryClassification): Promise<Result<ReadonlyArray<ICandidateRecord>>>;
|
|
206
|
+
}
|
|
207
|
+
|
|
108
208
|
// @public
|
|
109
209
|
export interface IFileTreeMemoryStoreCreateParams {
|
|
110
210
|
readonly clock?: () => number;
|
|
@@ -140,12 +240,50 @@ export interface IIndexedMemoryRecord {
|
|
|
140
240
|
readonly scope: MemoryScopeKey;
|
|
141
241
|
}
|
|
142
242
|
|
|
243
|
+
// @public
|
|
244
|
+
export interface IIngestedRecordResult {
|
|
245
|
+
readonly candidate: ICandidateRecord;
|
|
246
|
+
readonly disposition: IngestDisposition;
|
|
247
|
+
readonly edges: ReadonlyArray<ICandidateEdge>;
|
|
248
|
+
readonly id: MemoryId;
|
|
249
|
+
readonly interlock?: 'temporal-versioned';
|
|
250
|
+
readonly record?: IMemoryRecord<unknown>;
|
|
251
|
+
readonly resolution: ResolutionVerdict;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// @public
|
|
255
|
+
export interface IIngestItem {
|
|
256
|
+
readonly content: unknown;
|
|
257
|
+
readonly id: string;
|
|
258
|
+
readonly metadata?: Record<string, unknown>;
|
|
259
|
+
readonly sourceId?: MemoryId;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// @public
|
|
263
|
+
export interface IIngestItemResult {
|
|
264
|
+
readonly item: IIngestItem;
|
|
265
|
+
readonly records: ReadonlyArray<IIngestedRecordResult>;
|
|
266
|
+
}
|
|
267
|
+
|
|
143
268
|
// @public
|
|
144
269
|
export interface IMemoryCapCullPolicyParams {
|
|
145
270
|
readonly maxRecords?: number;
|
|
146
271
|
readonly mutableFields: ReadonlyArray<string>;
|
|
147
272
|
}
|
|
148
273
|
|
|
274
|
+
// @public
|
|
275
|
+
export interface IMemoryClassification {
|
|
276
|
+
readonly [key: string]: unknown;
|
|
277
|
+
readonly confidence?: number;
|
|
278
|
+
readonly kind: Kind;
|
|
279
|
+
readonly tags?: ReadonlyArray<Tag>;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// @public
|
|
283
|
+
export interface IMemoryClassifier {
|
|
284
|
+
classify(item: IIngestItem): Promise<Result<IMemoryClassification>>;
|
|
285
|
+
}
|
|
286
|
+
|
|
149
287
|
// @public
|
|
150
288
|
export interface IMemoryEnvelope {
|
|
151
289
|
readonly contentHash: string;
|
|
@@ -179,6 +317,30 @@ export interface IMemoryIndex {
|
|
|
179
317
|
rebuild(entries: ReadonlyArray<IIndexedMemoryRecord>): Result<number>;
|
|
180
318
|
}
|
|
181
319
|
|
|
320
|
+
// @public
|
|
321
|
+
export interface IMemoryIngestOrchestrator {
|
|
322
|
+
ingestBatch(items: ReadonlyArray<IIngestItem>): Promise<Result<ReadonlyArray<IIngestItemResult>>>;
|
|
323
|
+
ingestItem(item: IIngestItem): Promise<Result<IIngestItemResult>>;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// @public
|
|
327
|
+
export interface IMemoryIngestOrchestratorCreateParams {
|
|
328
|
+
readonly classifier: IMemoryClassifier;
|
|
329
|
+
readonly codecs?: ReadonlyMap<Kind, IIdentityCodec>;
|
|
330
|
+
readonly cycleGuard?: CycleGuardMode;
|
|
331
|
+
readonly defaultCodec?: IIdentityCodec;
|
|
332
|
+
readonly embed?: MemoryEmbedder;
|
|
333
|
+
readonly entityResolver?: IEntityResolver;
|
|
334
|
+
readonly extractor: IFactExtractor;
|
|
335
|
+
readonly logger?: Logging.ILogger;
|
|
336
|
+
readonly registry: IBodyConverterRegistry;
|
|
337
|
+
readonly relationExtractor: IRelationExtractor;
|
|
338
|
+
readonly similarityThreshold?: number;
|
|
339
|
+
readonly similarityTopK?: number;
|
|
340
|
+
readonly store: IMemoryStore;
|
|
341
|
+
readonly vectorIndex?: IVectorIndex;
|
|
342
|
+
}
|
|
343
|
+
|
|
182
344
|
// @public
|
|
183
345
|
export interface IMemoryObservationQuery {
|
|
184
346
|
readonly kind?: Kind;
|
|
@@ -272,6 +434,23 @@ export interface IMemoryStoreListFilter {
|
|
|
272
434
|
readonly tag?: Tag;
|
|
273
435
|
}
|
|
274
436
|
|
|
437
|
+
// @public
|
|
438
|
+
export interface IMemoryToolResultItem {
|
|
439
|
+
readonly body: unknown;
|
|
440
|
+
readonly entityId: EntityId;
|
|
441
|
+
readonly handle: string;
|
|
442
|
+
readonly kind: Kind;
|
|
443
|
+
readonly tags: ReadonlyArray<string>;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// @public
|
|
447
|
+
export interface IMemoryWriteResult {
|
|
448
|
+
readonly entityId: EntityId;
|
|
449
|
+
readonly id: MemoryId;
|
|
450
|
+
readonly kind: Kind;
|
|
451
|
+
readonly outcome: MemoryWriteOutcome;
|
|
452
|
+
}
|
|
453
|
+
|
|
275
454
|
// @public
|
|
276
455
|
export interface IMergeStrategy {
|
|
277
456
|
merge(resultSets: ReadonlyArray<ReadonlyArray<IMemoryRecord<unknown>>>): Result<ReadonlyArray<IMemoryRecord<unknown>>>;
|
|
@@ -280,6 +459,9 @@ export interface IMergeStrategy {
|
|
|
280
459
|
// @public
|
|
281
460
|
export function indexedRecordMatchesQuery(entry: IIndexedMemoryRecord, query: IMemoryQuery): boolean;
|
|
282
461
|
|
|
462
|
+
// @public
|
|
463
|
+
export type IngestDisposition = 'written' | 'deduped' | 'merged';
|
|
464
|
+
|
|
283
465
|
// @public
|
|
284
466
|
export class InMemoryCosineIndex implements IVectorIndex {
|
|
285
467
|
add(id: MemoryId, vector: Float32Array): Promise<Result<string>>;
|
|
@@ -300,6 +482,23 @@ export interface IProvenance {
|
|
|
300
482
|
readonly source: ProvenanceSource;
|
|
301
483
|
}
|
|
302
484
|
|
|
485
|
+
// @public
|
|
486
|
+
export interface IRelationCandidate {
|
|
487
|
+
readonly candidate: ICandidateRecord;
|
|
488
|
+
readonly id: MemoryId;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// @public
|
|
492
|
+
export interface IRelationContext {
|
|
493
|
+
readonly candidates: ReadonlyArray<IRelationCandidate>;
|
|
494
|
+
readonly item: IIngestItem;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// @public
|
|
498
|
+
export interface IRelationExtractor {
|
|
499
|
+
relate(context: IRelationContext): Promise<Result<ReadonlyArray<ICandidateEdge>>>;
|
|
500
|
+
}
|
|
501
|
+
|
|
303
502
|
// @public
|
|
304
503
|
export interface ISemanticBackend {
|
|
305
504
|
readonly embedQuery: QueryEmbedder;
|
|
@@ -312,12 +511,36 @@ export interface ISemanticRetrieverCreateParams {
|
|
|
312
511
|
readonly index: IMemoryIndex;
|
|
313
512
|
}
|
|
314
513
|
|
|
514
|
+
// @public
|
|
515
|
+
export function isTemporalIdentityCodec(codec: IIdentityCodec): codec is ITemporalIdentityCodec;
|
|
516
|
+
|
|
517
|
+
// @public
|
|
518
|
+
export function isTemporalRecord(record: IMemoryRecord<unknown>): boolean;
|
|
519
|
+
|
|
520
|
+
// @public
|
|
521
|
+
export function isVersionCurrent(record: IMemoryRecord<unknown>): boolean;
|
|
522
|
+
|
|
523
|
+
// @public
|
|
524
|
+
export function isVersionValidAt(record: IMemoryRecord<unknown>, asOf: number): boolean;
|
|
525
|
+
|
|
315
526
|
// @public
|
|
316
527
|
export interface ITemporalBlock {
|
|
317
528
|
readonly invalid_at?: number | null;
|
|
318
529
|
readonly valid_at?: number;
|
|
319
530
|
}
|
|
320
531
|
|
|
532
|
+
// @public
|
|
533
|
+
export interface ITemporalIdentityCodec extends IIdentityCodec {
|
|
534
|
+
decodeVersion(scope: MemoryScopeKey, stem: string): Result<ITemporalVersionAddress>;
|
|
535
|
+
encodeVersion(entityId: EntityId, seq: number): Result<string>;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// @public
|
|
539
|
+
export interface ITemporalVersionAddress {
|
|
540
|
+
readonly entityId: EntityId;
|
|
541
|
+
readonly seq: number;
|
|
542
|
+
}
|
|
543
|
+
|
|
321
544
|
// @public
|
|
322
545
|
export interface IVectorIndex {
|
|
323
546
|
add(id: MemoryId, vector: Float32Array): Promise<Result<string>>;
|
|
@@ -419,6 +642,13 @@ export class MemoryIndex implements IMemoryIndex {
|
|
|
419
642
|
// @public
|
|
420
643
|
export type MemoryIndexPatchOp = 'put' | 'delete';
|
|
421
644
|
|
|
645
|
+
// @public
|
|
646
|
+
export class MemoryIngestOrchestrator implements IMemoryIngestOrchestrator {
|
|
647
|
+
static create(params: IMemoryIngestOrchestratorCreateParams): Result<MemoryIngestOrchestrator>;
|
|
648
|
+
ingestBatch(items: ReadonlyArray<IIngestItem>): Promise<Result<ReadonlyArray<IIngestItemResult>>>;
|
|
649
|
+
ingestItem(item: IIngestItem): Promise<Result<IIngestItemResult>>;
|
|
650
|
+
}
|
|
651
|
+
|
|
422
652
|
// @public
|
|
423
653
|
export type MemoryObservationOutcome = 'success' | 'failure';
|
|
424
654
|
|
|
@@ -438,6 +668,12 @@ export class MemoryObservationStore implements IMemoryObserver {
|
|
|
438
668
|
// @public
|
|
439
669
|
export type MemoryScopeKey = Brand<string, 'MemoryScopeKey'>;
|
|
440
670
|
|
|
671
|
+
// @public
|
|
672
|
+
export type MemoryToolName = 'memory_write' | 'memory_read' | 'memory_search' | 'memory_context' | 'memory_delete';
|
|
673
|
+
|
|
674
|
+
// @public
|
|
675
|
+
export type MemoryWriteOutcome = 'written' | 'deduped';
|
|
676
|
+
|
|
441
677
|
// @public
|
|
442
678
|
export class MtmIdentityCodec implements IIdentityCodec {
|
|
443
679
|
decode(scope: MemoryScopeKey, encodedStem: string): Result<EntityId>;
|
|
@@ -472,6 +708,20 @@ export class RecencyRetriever implements IMemoryRetriever {
|
|
|
472
708
|
retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>>;
|
|
473
709
|
}
|
|
474
710
|
|
|
711
|
+
// @public
|
|
712
|
+
export type ResolutionVerdict = {
|
|
713
|
+
readonly verdict: 'new';
|
|
714
|
+
} | {
|
|
715
|
+
readonly verdict: 'duplicate-of';
|
|
716
|
+
readonly target: MemoryId;
|
|
717
|
+
} | {
|
|
718
|
+
readonly verdict: 'supersede';
|
|
719
|
+
readonly target: MemoryId;
|
|
720
|
+
} | {
|
|
721
|
+
readonly verdict: 'merge-into';
|
|
722
|
+
readonly target: MemoryId;
|
|
723
|
+
};
|
|
724
|
+
|
|
475
725
|
// @public
|
|
476
726
|
export class ScoreUnionMergeStrategy implements IMergeStrategy {
|
|
477
727
|
static create(): Result<ScoreUnionMergeStrategy>;
|
|
@@ -481,6 +731,12 @@ export class ScoreUnionMergeStrategy implements IMergeStrategy {
|
|
|
481
731
|
// @public
|
|
482
732
|
export function selectByQuery(entries: ReadonlyArray<IIndexedMemoryRecord>, query: IMemoryQuery): IMemoryRecord<unknown>[];
|
|
483
733
|
|
|
734
|
+
// @public
|
|
735
|
+
export function selectCurrentVersion(versions: ReadonlyArray<IMemoryRecord<unknown>>): IMemoryRecord<unknown> | undefined;
|
|
736
|
+
|
|
737
|
+
// @public
|
|
738
|
+
export function selectVersionAsOf(versions: ReadonlyArray<IMemoryRecord<unknown>>, asOf: number): IMemoryRecord<unknown> | undefined;
|
|
739
|
+
|
|
484
740
|
// @public
|
|
485
741
|
export const SEMANTIC_UNWIRED_MESSAGE: string;
|
|
486
742
|
|
|
@@ -497,6 +753,9 @@ export function serializeMemoryFile(envelope: IMemoryEnvelope, body: string): Re
|
|
|
497
753
|
// @public
|
|
498
754
|
export function splitFrontmatter(raw: string): Result<IMemoryFileParts>;
|
|
499
755
|
|
|
756
|
+
// @public
|
|
757
|
+
export type StoreStampedEnvelopeField = 'id' | 'seq' | 'contentHash' | 'created' | 'updated';
|
|
758
|
+
|
|
500
759
|
// @public
|
|
501
760
|
export class StructuredFilterRetriever implements IMemoryRetriever {
|
|
502
761
|
get capabilities(): IMemoryRetrieverCapabilities;
|
|
@@ -514,12 +773,37 @@ export class TagRetriever implements IMemoryRetriever {
|
|
|
514
773
|
retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>>;
|
|
515
774
|
}
|
|
516
775
|
|
|
776
|
+
// @public
|
|
777
|
+
export const TEMPORAL_CAPABILITIES: IMemoryRetrieverCapabilities;
|
|
778
|
+
|
|
517
779
|
// @public
|
|
518
780
|
export const temporalConverter: Converter<ITemporalBlock>;
|
|
519
781
|
|
|
782
|
+
// @public
|
|
783
|
+
export class TemporalIdentityCodec implements ITemporalIdentityCodec {
|
|
784
|
+
readonly baseScope: string;
|
|
785
|
+
static create(baseScope: string): Result<TemporalIdentityCodec>;
|
|
786
|
+
decode(scope: MemoryScopeKey, encodedStem: string): Result<EntityId>;
|
|
787
|
+
decodeVersion(scope: MemoryScopeKey, stem: string): Result<ITemporalVersionAddress>;
|
|
788
|
+
encode(entityId: EntityId): Result<IIdentityCodecResult>;
|
|
789
|
+
encodeVersion(entityId: EntityId, seq: number): Result<string>;
|
|
790
|
+
static readonly entitiesSegment: string;
|
|
791
|
+
verifyRoundTrip(scope: MemoryScopeKey, stem: string): Result<true>;
|
|
792
|
+
static readonly versionInfix: string;
|
|
793
|
+
}
|
|
794
|
+
|
|
520
795
|
// @public
|
|
521
796
|
export function temporalUnwiredMessage(kind?: Kind): string;
|
|
522
797
|
|
|
798
|
+
// @public
|
|
799
|
+
export class TemporalVersionedPolicy implements IWritePolicy {
|
|
800
|
+
admit(__incoming: IMemoryRecord<unknown>, __existing: ReadonlyArray<IMemoryRecord<unknown>>): Result<AdmissionDecision>;
|
|
801
|
+
applyUpdate(existing: IMemoryRecord<unknown>, patch: Record<string, unknown>): Result<IMemoryRecord<unknown>>;
|
|
802
|
+
static create(): Result<TemporalVersionedPolicy>;
|
|
803
|
+
readonly dedupScope: DedupScope;
|
|
804
|
+
readonly mutableFields: ReadonlyArray<string>;
|
|
805
|
+
}
|
|
806
|
+
|
|
523
807
|
// (No @packageDocumentation comment for this package)
|
|
524
808
|
|
|
525
809
|
```
|
package/lib/index.d.ts
CHANGED
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -25,4 +25,6 @@ __exportStar(require("./packlets/index"), exports);
|
|
|
25
25
|
__exportStar(require("./packlets/retrieve"), exports);
|
|
26
26
|
__exportStar(require("./packlets/observe"), exports);
|
|
27
27
|
__exportStar(require("./packlets/vector"), exports);
|
|
28
|
+
__exportStar(require("./packlets/tools"), exports);
|
|
29
|
+
__exportStar(require("./packlets/ingest"), exports);
|
|
28
30
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;AAEH,mDAAiC;AACjC,wDAAsC;AACtC,mDAAiC;AACjC,mDAAiC;AACjC,sDAAoC;AACpC,qDAAmC;AACnC,oDAAkC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './packlets/types';\nexport * from './packlets/converters';\nexport * from './packlets/store';\nexport * from './packlets/index';\nexport * from './packlets/retrieve';\nexport * from './packlets/observe';\nexport * from './packlets/vector';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;AAEH,mDAAiC;AACjC,wDAAsC;AACtC,mDAAiC;AACjC,mDAAiC;AACjC,sDAAoC;AACpC,qDAAmC;AACnC,oDAAkC;AAClC,mDAAiC;AACjC,oDAAkC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './packlets/types';\nexport * from './packlets/converters';\nexport * from './packlets/store';\nexport * from './packlets/index';\nexport * from './packlets/retrieve';\nexport * from './packlets/observe';\nexport * from './packlets/vector';\nexport * from './packlets/tools';\nexport * from './packlets/ingest';\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Result } from '@fgv/ts-utils';
|
|
2
|
+
import { LinkType, MemoryId } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* A directed edge in the link graph the cycle guard reasons over: `source` links
|
|
5
|
+
* to `target` under relation `type`.
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export interface ICycleGuardEdge {
|
|
9
|
+
readonly source: MemoryId;
|
|
10
|
+
readonly target: MemoryId;
|
|
11
|
+
readonly type: LinkType;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build the design's `buildCycleKey` — a deterministic, canonical (RFC-8785,
|
|
15
|
+
* via `Crc32Normalizer` from `@fgv/ts-utils`) identity for a directed edge. Used
|
|
16
|
+
* to de-duplicate proposed edges so a repeated `(source, target, type)` proposal
|
|
17
|
+
* contributes a single graph edge (and never spuriously "re-closes" a cycle).
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildCycleKey(edge: ICycleGuardEdge): Result<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Write-time cycle guard. Given the graph's EXISTING directed edges plus a set of
|
|
23
|
+
* PROPOSED edges, verify that adding the proposed edges keeps the union graph a
|
|
24
|
+
* DAG. Fails loudly, naming the first proposed edge that would close a directed
|
|
25
|
+
* cycle.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* Distinct from the read-time BFS in `LinkTraversalRetriever` (which tolerates
|
|
29
|
+
* cycles via a visited-set): this is a WRITE-time admission check. Cycle
|
|
30
|
+
* detection is DFS reachability — a proposed `source -> target` edge closes a
|
|
31
|
+
* cycle iff `target` already reaches `source` in the graph built so far. Proposed
|
|
32
|
+
* edges are folded in one at a time (in order), so an intra-batch cycle
|
|
33
|
+
* (`A->B`, `B->A` both proposed) is caught as well as a batch-vs-existing cycle.
|
|
34
|
+
*
|
|
35
|
+
* See the design note §4: this enforces GLOBAL directed-acyclicity over all link
|
|
36
|
+
* types (conservative — a mutual associative pair is rejected). Callers that need
|
|
37
|
+
* mutual links disable the guard.
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
export declare function assertNoCycles(existing: ReadonlyArray<ICycleGuardEdge>, proposed: ReadonlyArray<ICycleGuardEdge>): Result<true>;
|
|
41
|
+
//# sourceMappingURL=cycleGuard.d.ts.map
|
|
@@ -0,0 +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,QAAQ,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB;AAED;;;;;;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,CAiCd"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.buildCycleKey = buildCycleKey;
|
|
8
|
+
exports.assertNoCycles = assertNoCycles;
|
|
9
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
10
|
+
/**
|
|
11
|
+
* Build the design's `buildCycleKey` — a deterministic, canonical (RFC-8785,
|
|
12
|
+
* via `Crc32Normalizer` from `@fgv/ts-utils`) identity for a directed edge. Used
|
|
13
|
+
* to de-duplicate proposed edges so a repeated `(source, target, type)` proposal
|
|
14
|
+
* contributes a single graph edge (and never spuriously "re-closes" a cycle).
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
function buildCycleKey(edge) {
|
|
18
|
+
return new ts_utils_1.Hash.Crc32Normalizer().computeHash({
|
|
19
|
+
source: edge.source,
|
|
20
|
+
target: edge.target,
|
|
21
|
+
type: edge.type
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Write-time cycle guard. Given the graph's EXISTING directed edges plus a set of
|
|
26
|
+
* PROPOSED edges, verify that adding the proposed edges keeps the union graph a
|
|
27
|
+
* DAG. Fails loudly, naming the first proposed edge that would close a directed
|
|
28
|
+
* cycle.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* Distinct from the read-time BFS in `LinkTraversalRetriever` (which tolerates
|
|
32
|
+
* cycles via a visited-set): this is a WRITE-time admission check. Cycle
|
|
33
|
+
* detection is DFS reachability — a proposed `source -> target` edge closes a
|
|
34
|
+
* cycle iff `target` already reaches `source` in the graph built so far. Proposed
|
|
35
|
+
* edges are folded in one at a time (in order), so an intra-batch cycle
|
|
36
|
+
* (`A->B`, `B->A` both proposed) is caught as well as a batch-vs-existing cycle.
|
|
37
|
+
*
|
|
38
|
+
* See the design note §4: this enforces GLOBAL directed-acyclicity over all link
|
|
39
|
+
* types (conservative — a mutual associative pair is rejected). Callers that need
|
|
40
|
+
* mutual links disable the guard.
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
function assertNoCycles(existing, proposed) {
|
|
44
|
+
// Canonically key every edge up front (the only fallible step); the graph
|
|
45
|
+
// building + cycle detection below is then pure.
|
|
46
|
+
return keyEdges(existing).onSuccess((existingKeyed) => keyEdges(proposed).onSuccess((proposedKeyed) => {
|
|
47
|
+
// Adjacency map, de-duplicated by canonical edge key so a repeated edge is
|
|
48
|
+
// one arc. Seed with the existing edges, then fold in each proposed edge,
|
|
49
|
+
// checking reachability BEFORE inserting it.
|
|
50
|
+
const adjacency = new Map();
|
|
51
|
+
const seenKeys = new Set();
|
|
52
|
+
for (const keyed of existingKeyed) {
|
|
53
|
+
addEdge(adjacency, seenKeys, keyed);
|
|
54
|
+
}
|
|
55
|
+
for (const keyed of proposedKeyed) {
|
|
56
|
+
const edge = keyed.edge;
|
|
57
|
+
// A self-loop is the degenerate one-node cycle.
|
|
58
|
+
if (edge.source === edge.target) {
|
|
59
|
+
return (0, ts_utils_1.fail)(`ingest cycle guard: edge '${edge.source}' -${edge.type}-> '${edge.target}' is a self-loop`);
|
|
60
|
+
}
|
|
61
|
+
// Reachability: does `target` already reach `source`? If so, adding
|
|
62
|
+
// `source -> target` closes a directed cycle.
|
|
63
|
+
if (reaches(adjacency, edge.target, edge.source)) {
|
|
64
|
+
return (0, ts_utils_1.fail)(`ingest cycle guard: edge '${edge.source}' -${edge.type}-> '${edge.target}' would create a cycle`);
|
|
65
|
+
}
|
|
66
|
+
addEdge(adjacency, seenKeys, keyed);
|
|
67
|
+
}
|
|
68
|
+
return (0, ts_utils_1.succeed)(true);
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
/** Canonically key a set of edges (the guard's only fallible step). */
|
|
72
|
+
function keyEdges(edges) {
|
|
73
|
+
return (0, ts_utils_1.mapResults)(edges.map((edge) => buildCycleKey(edge).onSuccess((key) => (0, ts_utils_1.succeed)({ edge, key }))));
|
|
74
|
+
}
|
|
75
|
+
/** Add an edge to the adjacency map, de-duplicated by its canonical key. */
|
|
76
|
+
function addEdge(adjacency, seenKeys, keyed) {
|
|
77
|
+
if (seenKeys.has(keyed.key)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
seenKeys.add(keyed.key);
|
|
81
|
+
const source = keyed.edge.source;
|
|
82
|
+
const target = keyed.edge.target;
|
|
83
|
+
const targets = adjacency.get(source);
|
|
84
|
+
if (targets === undefined) {
|
|
85
|
+
adjacency.set(source, new Set([target]));
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
targets.add(target);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** DFS reachability: is `to` reachable from `from` over `adjacency`? */
|
|
92
|
+
function reaches(adjacency, from, to) {
|
|
93
|
+
const visited = new Set();
|
|
94
|
+
const stack = [from];
|
|
95
|
+
while (stack.length > 0) {
|
|
96
|
+
const node = stack.pop();
|
|
97
|
+
if (node === to) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
if (visited.has(node)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
visited.add(node);
|
|
104
|
+
const neighbors = adjacency.get(node);
|
|
105
|
+
if (neighbors !== undefined) {
|
|
106
|
+
for (const neighbor of neighbors) {
|
|
107
|
+
if (!visited.has(neighbor)) {
|
|
108
|
+
stack.push(neighbor);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=cycleGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 { LinkType, MemoryId } from '../types';\n\n/**\n * A directed edge in the link graph the cycle guard reasons over: `source` links\n * to `target` under relation `type`.\n * @public\n */\nexport interface ICycleGuardEdge {\n readonly source: MemoryId;\n readonly target: MemoryId;\n readonly type: LinkType;\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 // A self-loop is the degenerate one-node cycle.\n if (edge.source === edge.target) {\n return fail(\n `ingest cycle guard: edge '${edge.source}' -${edge.type}-> '${edge.target}' 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, edge.target, edge.source)) {\n return fail(\n `ingest cycle guard: edge '${edge.source}' -${edge.type}-> '${edge.target}' 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 = keyed.edge.source;\n const target: string = 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"]}
|