cozo-memory 1.1.4 → 1.1.6
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/README.md +161 -1159
- package/dist/adaptive-query-fusion.js +397 -0
- package/dist/dynamic-fusion.js +63 -8
- package/dist/explainable-retrieval.js +552 -0
- package/dist/hierarchical-memory.js +358 -0
- package/dist/proactive-suggestions.js +382 -0
- package/dist/temporal-conflict-resolution.js +386 -0
- package/dist/temporal-pattern-detection-backup.js +358 -0
- package/dist/temporal-pattern-detection.js +482 -0
- package/dist/test-adaptive-query-fusion.js +208 -0
- package/dist/test-explainable-retrieval.js +408 -0
- package/dist/test-hierarchical-and-patterns.js +17 -0
- package/dist/test-hierarchical-memory.js +205 -0
- package/dist/test-proactive-suggestions.js +240 -0
- package/dist/test-temporal-conflict-resolution.js +228 -0
- package/dist/test-temporal-patterns.js +317 -0
- package/package.json +1 -1
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Temporal Conflict Resolution Service
|
|
4
|
+
*
|
|
5
|
+
* Inspired by T-GRAG (Li et al., 2025) - Dynamic GraphRAG Framework
|
|
6
|
+
*
|
|
7
|
+
* Detects and resolves temporal conflicts in observations:
|
|
8
|
+
* - Semantic contradictions across time periods
|
|
9
|
+
* - Redundant information with temporal evolution
|
|
10
|
+
* - Outdated facts superseded by newer information
|
|
11
|
+
*
|
|
12
|
+
* Key Features:
|
|
13
|
+
* - Embedding-based semantic similarity detection
|
|
14
|
+
* - Keyword-based contradiction patterns
|
|
15
|
+
* - Automatic conflict resolution via Validity retraction
|
|
16
|
+
* - Audit trail preservation
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.TemporalConflictResolutionService = exports.ConflictConfidence = exports.ConflictType = void 0;
|
|
20
|
+
/**
|
|
21
|
+
* Conflict types
|
|
22
|
+
*/
|
|
23
|
+
var ConflictType;
|
|
24
|
+
(function (ConflictType) {
|
|
25
|
+
ConflictType["SEMANTIC_CONTRADICTION"] = "semantic_contradiction";
|
|
26
|
+
ConflictType["TEMPORAL_REDUNDANCY"] = "temporal_redundancy";
|
|
27
|
+
ConflictType["SUPERSEDED_FACT"] = "superseded_fact";
|
|
28
|
+
})(ConflictType || (exports.ConflictType = ConflictType = {}));
|
|
29
|
+
/**
|
|
30
|
+
* Confidence level for conflict detection
|
|
31
|
+
*/
|
|
32
|
+
var ConflictConfidence;
|
|
33
|
+
(function (ConflictConfidence) {
|
|
34
|
+
ConflictConfidence["HIGH"] = "high";
|
|
35
|
+
ConflictConfidence["MEDIUM"] = "medium";
|
|
36
|
+
ConflictConfidence["LOW"] = "low";
|
|
37
|
+
})(ConflictConfidence || (exports.ConflictConfidence = ConflictConfidence = {}));
|
|
38
|
+
/**
|
|
39
|
+
* Contradiction patterns for keyword-based detection
|
|
40
|
+
*/
|
|
41
|
+
const CONTRADICTION_PATTERNS = [
|
|
42
|
+
// Status contradictions
|
|
43
|
+
{ positive: ['active', 'running', 'ongoing', 'operational', 'continued'],
|
|
44
|
+
negative: ['inactive', 'discontinued', 'cancelled', 'stopped', 'shut down', 'closed', 'deprecated', 'archived', 'ended', 'abandoned'] },
|
|
45
|
+
// Boolean contradictions
|
|
46
|
+
{ positive: ['yes', 'true', 'confirmed', 'approved', 'accepted', 'enabled'],
|
|
47
|
+
negative: ['no', 'false', 'denied', 'rejected', 'refused', 'disabled'] },
|
|
48
|
+
// Existence contradictions
|
|
49
|
+
{ positive: ['exists', 'present', 'available', 'found', 'located'],
|
|
50
|
+
negative: ['missing', 'absent', 'unavailable', 'not found', 'removed', 'deleted'] },
|
|
51
|
+
// Quantity contradictions (detected via numeric comparison)
|
|
52
|
+
{ positive: ['increased', 'grew', 'rose', 'higher', 'more', 'expanded'],
|
|
53
|
+
negative: ['decreased', 'fell', 'dropped', 'lower', 'less', 'reduced', 'shrunk'] },
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* Temporal Conflict Resolution Service
|
|
57
|
+
*/
|
|
58
|
+
class TemporalConflictResolutionService {
|
|
59
|
+
db;
|
|
60
|
+
embeddings;
|
|
61
|
+
config;
|
|
62
|
+
constructor(db, embeddings, config = {}) {
|
|
63
|
+
this.db = db;
|
|
64
|
+
this.embeddings = embeddings;
|
|
65
|
+
this.config = {
|
|
66
|
+
similarityThreshold: config.similarityThreshold ?? 0.85,
|
|
67
|
+
contradictionThreshold: config.contradictionThreshold ?? 0.3,
|
|
68
|
+
timeWindowDays: config.timeWindowDays ?? 365,
|
|
69
|
+
autoResolve: config.autoResolve ?? false,
|
|
70
|
+
preserveAuditTrail: config.preserveAuditTrail ?? true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Detect temporal conflicts for an entity
|
|
75
|
+
*/
|
|
76
|
+
async detectConflicts(entityId) {
|
|
77
|
+
try {
|
|
78
|
+
// Get all observations for the entity, ordered by time
|
|
79
|
+
// Query WITHOUT @ "NOW" to access created_at, then filter manually for valid rows
|
|
80
|
+
const datalog = `
|
|
81
|
+
?[id, text, created_at_ms, embedding] :=
|
|
82
|
+
*observation{
|
|
83
|
+
id,
|
|
84
|
+
created_at,
|
|
85
|
+
entity_id,
|
|
86
|
+
text,
|
|
87
|
+
embedding
|
|
88
|
+
},
|
|
89
|
+
entity_id = $entity_id,
|
|
90
|
+
to_bool(created_at),
|
|
91
|
+
created_at_ms = to_int(created_at) / 1000
|
|
92
|
+
|
|
93
|
+
:order created_at_ms
|
|
94
|
+
`;
|
|
95
|
+
const result = await this.db.run(datalog, { entity_id: entityId });
|
|
96
|
+
if (result.rows.length < 2) {
|
|
97
|
+
return []; // Need at least 2 observations to have conflicts
|
|
98
|
+
}
|
|
99
|
+
const observations = result.rows.map((row) => ({
|
|
100
|
+
id: row[0],
|
|
101
|
+
text: row[1],
|
|
102
|
+
created_at: row[2], // Now in milliseconds
|
|
103
|
+
embedding: row[3],
|
|
104
|
+
}));
|
|
105
|
+
// Get entity name (query without @ "NOW" and filter manually)
|
|
106
|
+
const entityResult = await this.db.run(`?[name] := *entity{id, created_at, name}, id = $id, to_bool(created_at)`, { id: entityId });
|
|
107
|
+
const entityName = entityResult.rows[0]?.[0] || 'Unknown';
|
|
108
|
+
const conflicts = [];
|
|
109
|
+
// Compare each pair of observations
|
|
110
|
+
for (let i = 0; i < observations.length; i++) {
|
|
111
|
+
for (let j = i + 1; j < observations.length; j++) {
|
|
112
|
+
const older = observations[i];
|
|
113
|
+
const newer = observations[j];
|
|
114
|
+
// Check time window
|
|
115
|
+
const timeDiffDays = (newer.created_at - older.created_at) / (1000 * 60 * 60 * 24);
|
|
116
|
+
if (timeDiffDays > this.config.timeWindowDays) {
|
|
117
|
+
continue; // Outside time window
|
|
118
|
+
}
|
|
119
|
+
// Calculate semantic similarity
|
|
120
|
+
const similarity = this.cosineSimilarity(older.embedding, newer.embedding);
|
|
121
|
+
// Detect conflict type
|
|
122
|
+
const conflict = this.detectConflictType(older, newer, similarity, entityId, entityName);
|
|
123
|
+
if (conflict) {
|
|
124
|
+
conflicts.push(conflict);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return conflicts;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error('[TemporalConflict] Error detecting conflicts:', error);
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Detect conflict type between two observations
|
|
137
|
+
*/
|
|
138
|
+
detectConflictType(older, newer, similarity, entityId, entityName) {
|
|
139
|
+
// Type 1: Temporal Redundancy (very similar, likely duplicate)
|
|
140
|
+
// Check this FIRST to avoid false contradiction detection
|
|
141
|
+
if (similarity >= this.config.similarityThreshold) {
|
|
142
|
+
return {
|
|
143
|
+
older_observation_id: older.id,
|
|
144
|
+
newer_observation_id: newer.id,
|
|
145
|
+
older_text: older.text,
|
|
146
|
+
newer_text: newer.text,
|
|
147
|
+
older_time: older.created_at,
|
|
148
|
+
newer_time: newer.created_at,
|
|
149
|
+
conflict_type: ConflictType.TEMPORAL_REDUNDANCY,
|
|
150
|
+
confidence: similarity,
|
|
151
|
+
confidence_level: this.getConfidenceLevel(similarity),
|
|
152
|
+
reason: `Highly similar observations (${(similarity * 100).toFixed(1)}% similarity) - likely redundant`,
|
|
153
|
+
entity_id: entityId,
|
|
154
|
+
entity_name: entityName,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Type 2: Semantic Contradiction (contradiction keywords detected)
|
|
158
|
+
// Only check if NOT redundant (similarity < threshold)
|
|
159
|
+
const contradictionScore = this.detectContradictionKeywords(older.text, newer.text);
|
|
160
|
+
if (contradictionScore > 0.25) { // Lowered threshold from 0.5 to 0.25
|
|
161
|
+
return {
|
|
162
|
+
older_observation_id: older.id,
|
|
163
|
+
newer_observation_id: newer.id,
|
|
164
|
+
older_text: older.text,
|
|
165
|
+
newer_text: newer.text,
|
|
166
|
+
older_time: older.created_at,
|
|
167
|
+
newer_time: newer.created_at,
|
|
168
|
+
conflict_type: ConflictType.SEMANTIC_CONTRADICTION,
|
|
169
|
+
confidence: contradictionScore,
|
|
170
|
+
confidence_level: this.getConfidenceLevel(contradictionScore),
|
|
171
|
+
reason: `Contradictory statements detected via keyword analysis`,
|
|
172
|
+
entity_id: entityId,
|
|
173
|
+
entity_name: entityName,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
// Type 3: Superseded Fact (moderate similarity, newer info updates older)
|
|
177
|
+
if (similarity > 0.4 && similarity < 0.85) {
|
|
178
|
+
// Check if newer observation explicitly supersedes older
|
|
179
|
+
const supersessionScore = this.detectSupersession(older.text, newer.text);
|
|
180
|
+
if (supersessionScore > 0.6) {
|
|
181
|
+
return {
|
|
182
|
+
older_observation_id: older.id,
|
|
183
|
+
newer_observation_id: newer.id,
|
|
184
|
+
older_text: older.text,
|
|
185
|
+
newer_text: newer.text,
|
|
186
|
+
older_time: older.created_at,
|
|
187
|
+
newer_time: newer.created_at,
|
|
188
|
+
conflict_type: ConflictType.SUPERSEDED_FACT,
|
|
189
|
+
confidence: supersessionScore,
|
|
190
|
+
confidence_level: this.getConfidenceLevel(supersessionScore),
|
|
191
|
+
reason: `Newer observation updates or supersedes older information`,
|
|
192
|
+
entity_id: entityId,
|
|
193
|
+
entity_name: entityName,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Detect contradiction keywords
|
|
201
|
+
*/
|
|
202
|
+
detectContradictionKeywords(text1, text2) {
|
|
203
|
+
const lower1 = text1.toLowerCase();
|
|
204
|
+
const lower2 = text2.toLowerCase();
|
|
205
|
+
let contradictionCount = 0;
|
|
206
|
+
let totalChecks = 0;
|
|
207
|
+
for (const pattern of CONTRADICTION_PATTERNS) {
|
|
208
|
+
const hasPositive1 = pattern.positive.some(kw => lower1.includes(kw));
|
|
209
|
+
const hasNegative1 = pattern.negative.some(kw => lower1.includes(kw));
|
|
210
|
+
const hasPositive2 = pattern.positive.some(kw => lower2.includes(kw));
|
|
211
|
+
const hasNegative2 = pattern.negative.some(kw => lower2.includes(kw));
|
|
212
|
+
// Only count if at least one text has a keyword from this pattern
|
|
213
|
+
if (hasPositive1 || hasNegative1 || hasPositive2 || hasNegative2) {
|
|
214
|
+
totalChecks++;
|
|
215
|
+
// Contradiction: one has positive, other has negative
|
|
216
|
+
if ((hasPositive1 && hasNegative2) || (hasNegative1 && hasPositive2)) {
|
|
217
|
+
contradictionCount++;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Return 0 if no patterns were checked, otherwise return ratio
|
|
222
|
+
return totalChecks === 0 ? 0 : contradictionCount / totalChecks;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Detect if newer observation supersedes older
|
|
226
|
+
*/
|
|
227
|
+
detectSupersession(olderText, newerText) {
|
|
228
|
+
const supersessionKeywords = [
|
|
229
|
+
'updated', 'revised', 'changed', 'modified', 'corrected',
|
|
230
|
+
'now', 'currently', 'as of', 'latest', 'recent',
|
|
231
|
+
'replaced', 'superseded', 'obsolete', 'outdated'
|
|
232
|
+
];
|
|
233
|
+
const lowerNewer = newerText.toLowerCase();
|
|
234
|
+
const matchCount = supersessionKeywords.filter(kw => lowerNewer.includes(kw)).length;
|
|
235
|
+
return Math.min(matchCount / 3, 1.0); // Normalize to 0-1
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Resolve conflicts by invalidating older observations
|
|
239
|
+
*/
|
|
240
|
+
async resolveConflicts(entityId, conflicts) {
|
|
241
|
+
try {
|
|
242
|
+
// Detect conflicts if not provided
|
|
243
|
+
if (!conflicts) {
|
|
244
|
+
conflicts = await this.detectConflicts(entityId);
|
|
245
|
+
}
|
|
246
|
+
if (conflicts.length === 0) {
|
|
247
|
+
return {
|
|
248
|
+
resolved_conflicts: 0,
|
|
249
|
+
invalidated_observations: [],
|
|
250
|
+
audit_observations: [],
|
|
251
|
+
conflicts: [],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const invalidatedObservations = [];
|
|
255
|
+
const auditObservations = [];
|
|
256
|
+
// Resolve each conflict
|
|
257
|
+
for (const conflict of conflicts) {
|
|
258
|
+
// Invalidate older observation
|
|
259
|
+
await this.invalidateObservation(conflict.older_observation_id);
|
|
260
|
+
invalidatedObservations.push(conflict.older_observation_id);
|
|
261
|
+
// Create audit trail if enabled
|
|
262
|
+
if (this.config.preserveAuditTrail) {
|
|
263
|
+
const auditId = await this.createAuditObservation(conflict);
|
|
264
|
+
auditObservations.push(auditId);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
console.log(`[TemporalConflict] Resolved ${conflicts.length} conflicts for entity ${entityId}`);
|
|
268
|
+
return {
|
|
269
|
+
resolved_conflicts: conflicts.length,
|
|
270
|
+
invalidated_observations: invalidatedObservations,
|
|
271
|
+
audit_observations: auditObservations,
|
|
272
|
+
conflicts,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
console.error('[TemporalConflict] Error resolving conflicts:', error);
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Invalidate an observation using Validity retraction
|
|
282
|
+
*/
|
|
283
|
+
async invalidateObservation(observationId) {
|
|
284
|
+
// Delete the observation (query without @ "NOW" and filter manually)
|
|
285
|
+
const datalog = `
|
|
286
|
+
?[id, created_at, entity_id, text, embedding, metadata] :=
|
|
287
|
+
*observation{
|
|
288
|
+
id,
|
|
289
|
+
created_at,
|
|
290
|
+
entity_id,
|
|
291
|
+
text,
|
|
292
|
+
embedding,
|
|
293
|
+
metadata
|
|
294
|
+
},
|
|
295
|
+
id = $id,
|
|
296
|
+
to_bool(created_at)
|
|
297
|
+
|
|
298
|
+
:delete observation {
|
|
299
|
+
id, created_at, entity_id, text, embedding, metadata
|
|
300
|
+
}
|
|
301
|
+
`;
|
|
302
|
+
await this.db.run(datalog, { id: observationId });
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Create audit observation for conflict resolution
|
|
306
|
+
*/
|
|
307
|
+
async createAuditObservation(conflict) {
|
|
308
|
+
const { v4: uuidv4 } = await import('uuid');
|
|
309
|
+
const auditId = uuidv4();
|
|
310
|
+
const now = Date.now() * 1000; // Convert to microseconds
|
|
311
|
+
const auditText = `[CONFLICT RESOLVED] ${conflict.conflict_type}: Observation superseded by newer information (confidence: ${(conflict.confidence * 100).toFixed(1)}%)`;
|
|
312
|
+
const embedding = await this.embeddings.embed(auditText);
|
|
313
|
+
const datalog = `
|
|
314
|
+
?[id, created_at, entity_id, text, embedding, metadata] :=
|
|
315
|
+
id = $id,
|
|
316
|
+
created_at = $created_at,
|
|
317
|
+
entity_id = $entity_id,
|
|
318
|
+
text = $text,
|
|
319
|
+
embedding = $embedding,
|
|
320
|
+
metadata = $metadata
|
|
321
|
+
|
|
322
|
+
:put observation {
|
|
323
|
+
id, created_at => entity_id, text, embedding, metadata
|
|
324
|
+
}
|
|
325
|
+
`;
|
|
326
|
+
await this.db.run(datalog, {
|
|
327
|
+
id: auditId,
|
|
328
|
+
created_at: [now, true],
|
|
329
|
+
entity_id: conflict.entity_id,
|
|
330
|
+
text: auditText,
|
|
331
|
+
embedding,
|
|
332
|
+
metadata: {
|
|
333
|
+
conflict_resolution: true,
|
|
334
|
+
conflict_type: conflict.conflict_type,
|
|
335
|
+
superseded_by: conflict.newer_observation_id,
|
|
336
|
+
original_observation: conflict.older_observation_id,
|
|
337
|
+
original_time: conflict.older_time,
|
|
338
|
+
resolution_time: now / 1000, // Store as milliseconds in metadata
|
|
339
|
+
confidence: conflict.confidence,
|
|
340
|
+
reason: conflict.reason,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
return auditId;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Get confidence level from numeric confidence
|
|
347
|
+
*/
|
|
348
|
+
getConfidenceLevel(confidence) {
|
|
349
|
+
if (confidence >= 0.8)
|
|
350
|
+
return ConflictConfidence.HIGH;
|
|
351
|
+
if (confidence >= 0.5)
|
|
352
|
+
return ConflictConfidence.MEDIUM;
|
|
353
|
+
return ConflictConfidence.LOW;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Calculate cosine similarity between two embeddings
|
|
357
|
+
*/
|
|
358
|
+
cosineSimilarity(vec1, vec2) {
|
|
359
|
+
if (vec1.length !== vec2.length) {
|
|
360
|
+
throw new Error('Vectors must have same length');
|
|
361
|
+
}
|
|
362
|
+
let dotProduct = 0;
|
|
363
|
+
let norm1 = 0;
|
|
364
|
+
let norm2 = 0;
|
|
365
|
+
for (let i = 0; i < vec1.length; i++) {
|
|
366
|
+
dotProduct += vec1[i] * vec2[i];
|
|
367
|
+
norm1 += vec1[i] * vec1[i];
|
|
368
|
+
norm2 += vec2[i] * vec2[i];
|
|
369
|
+
}
|
|
370
|
+
const magnitude = Math.sqrt(norm1) * Math.sqrt(norm2);
|
|
371
|
+
return magnitude === 0 ? 0 : dotProduct / magnitude;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Update configuration
|
|
375
|
+
*/
|
|
376
|
+
updateConfig(config) {
|
|
377
|
+
this.config = { ...this.config, ...config };
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Get current configuration
|
|
381
|
+
*/
|
|
382
|
+
getConfig() {
|
|
383
|
+
return { ...this.config };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
exports.TemporalConflictResolutionService = TemporalConflictResolutionService;
|