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.
@@ -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;