UNPKG

@clduab11/gemini-flow

Version:

Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.

1,185 lines (1,024 loc) 32.9 kB
/** * Conflict Resolution for A2A Memory Coordination * * Implements sophisticated conflict resolution strategies: * - Last-Writer-Wins (LWW) with vector clocks * - Semantic Conflict Resolution * - Multi-Value Resolution with user-defined merge functions * - Priority-based Resolution * - Operational Transform for collaborative editing * - Custom Application-Specific Resolvers * - Conflict Prevention through Locking */ import { EventEmitter } from "events"; import { Logger } from "../../../utils/logger.js"; import { VectorClock } from "./vector-clocks.js"; export type ConflictResolutionStrategy = | "lww" // Last Writer Wins | "mvr" // Multi-Value Register | "semantic" // Semantic merge | "priority" // Priority-based | "operational" // Operational Transform | "custom" // Custom resolver | "manual" // Manual resolution required | "union" // Union of values (for sets) | "intersection"; // Intersection of values export interface ConflictContext { key: string; namespace: string; conflictType: "concurrent_write" | "read_write" | "structural" | "semantic"; localValue: ConflictValue; remoteValue: ConflictValue; commonAncestor?: ConflictValue; metadata: { agents: string[]; timestamp: Date; priority: number; contentType?: string; schema?: any; }; } export interface ConflictValue { data: any; vectorClock: VectorClock; agentId: string; timestamp: Date; version: number; checksum?: string; metadata?: { priority?: number; contentType?: string; sourceOperation?: string; dependencies?: string[]; }; } export interface ConflictResolution { resolutionId: string; strategy: ConflictResolutionStrategy; resolvedValue: any; confidence: number; // 0-1 confidence in resolution reasoning: string; appliedTransforms?: OperationalTransform[]; alternativeValues?: any[]; requiresManualReview: boolean; timestamp: Date; resolverAgent: string; } export interface OperationalTransform { type: "insert" | "delete" | "retain" | "replace" | "move"; position: number; content?: any; length?: number; priority: number; agentId: string; timestamp: Date; } export interface ConflictRule { id: string; name: string; pattern: RegExp | string; strategy: ConflictResolutionStrategy; priority: number; conditions: ConflictCondition[]; customResolver?: (context: ConflictContext) => Promise<ConflictResolution>; transformers?: OperationalTransform[]; } export interface ConflictCondition { field: string; operator: "equals" | "contains" | "matches" | "greater" | "less"; value: any; negate?: boolean; } export interface ConflictStats { totalConflicts: number; resolvedConflicts: number; pendingConflicts: number; manualReviewRequired: number; resolutionsByStrategy: Map<ConflictResolutionStrategy, number>; averageResolutionTime: number; conflictRate: number; accuracyScore: number; } /** * Main Conflict Resolver */ export class ConflictResolver extends EventEmitter { private logger: Logger; private vectorClock: VectorClock; private rules: Map<string, ConflictRule> = new Map(); private pendingConflicts: Map<string, ConflictContext> = new Map(); private resolutionHistory: Map<string, ConflictResolution> = new Map(); private customResolvers: Map<string, Function> = new Map(); // Statistics private stats: ConflictStats = { totalConflicts: 0, resolvedConflicts: 0, pendingConflicts: 0, manualReviewRequired: 0, resolutionsByStrategy: new Map(), averageResolutionTime: 0, conflictRate: 0, accuracyScore: 0, }; constructor( vectorClock: VectorClock, defaultStrategy: ConflictResolutionStrategy = "lww", ) { super(); this.logger = new Logger("ConflictResolver"); this.vectorClock = vectorClock; this.initializeDefaultRules(defaultStrategy); this.initializeBuiltinResolvers(); this.logger.info("Conflict resolver initialized", { defaultStrategy, rulesCount: this.rules.size, }); } /** * Resolve a conflict between two values */ async resolve( localValue: ConflictValue, remoteValue: ConflictValue, key: string = "unknown", namespace: string = "default", ): Promise<ConflictResolution> { const startTime = Date.now(); try { // Create conflict context const context: ConflictContext = { key, namespace, conflictType: this.determineConflictType(localValue, remoteValue), localValue, remoteValue, commonAncestor: await this.findCommonAncestor(localValue, remoteValue), metadata: { agents: [localValue.agentId, remoteValue.agentId], timestamp: new Date(), priority: Math.max( localValue.metadata?.priority || 5, remoteValue.metadata?.priority || 5, ), }, }; this.stats.totalConflicts++; this.pendingConflicts.set(this.generateConflictId(context), context); // Find applicable rule const rule = this.findApplicableRule(context); // Execute resolution strategy const resolution = await this.executeResolutionStrategy(context, rule); // Record resolution this.recordResolution(context, resolution); const resolutionTime = Date.now() - startTime; this.updateStats(resolution, resolutionTime); this.logger.debug("Conflict resolved", { key, strategy: resolution.strategy, confidence: resolution.confidence, resolutionTime, }); this.emit("conflict_resolved", { context, resolution }); return resolution; } catch (error) { this.logger.error("Conflict resolution failed", { key, namespace, error: error.message, }); // Return fallback resolution return this.createFallbackResolution(localValue, remoteValue); } } /** * Add a custom conflict resolution rule */ addRule(rule: ConflictRule): void { this.rules.set(rule.id, rule); this.logger.info("Conflict rule added", { ruleId: rule.id, strategy: rule.strategy, priority: rule.priority, }); } /** * Remove a conflict resolution rule */ removeRule(ruleId: string): boolean { const removed = this.rules.delete(ruleId); if (removed) { this.logger.info("Conflict rule removed", { ruleId }); } return removed; } /** * Register a custom resolver function */ registerCustomResolver( name: string, resolver: (context: ConflictContext) => Promise<ConflictResolution>, ): void { this.customResolvers.set(name, resolver); this.logger.info("Custom resolver registered", { name }); } /** * Perform semantic merge for structured data */ async semanticMerge( localValue: any, remoteValue: any, schema?: any, ): Promise<any> { try { // Handle different data types if (Array.isArray(localValue) && Array.isArray(remoteValue)) { return this.mergeArrays(localValue, remoteValue); } if (this.isObject(localValue) && this.isObject(remoteValue)) { return this.mergeObjects(localValue, remoteValue, schema); } if (typeof localValue === "string" && typeof remoteValue === "string") { return this.mergeStrings(localValue, remoteValue); } if (typeof localValue === "number" && typeof remoteValue === "number") { return this.mergeNumbers(localValue, remoteValue); } // Fallback to LWW for other types return this.vectorClock.compare( new VectorClock("local"), new VectorClock("remote"), ) === "after" ? localValue : remoteValue; } catch (error) { this.logger.error("Semantic merge failed", { error: error.message }); throw error; } } /** * Apply operational transforms to resolve text conflicts */ async applyOperationalTransforms( baseText: string, transforms: OperationalTransform[], ): Promise<string> { // Sort transforms by priority and timestamp const sortedTransforms = transforms.sort((a, b) => { if (a.priority !== b.priority) { return b.priority - a.priority; // Higher priority first } return a.timestamp.getTime() - b.timestamp.getTime(); }); let result = baseText; let offset = 0; for (const transform of sortedTransforms) { try { result = this.applyTransform(result, transform, offset); offset = this.calculateOffset(transform, offset); } catch (error) { this.logger.warn("Failed to apply transform", { transform: transform.type, error: error.message, }); } } return result; } /** * Get conflict resolution statistics */ getStats(): ConflictStats { this.updateConflictRate(); return { ...this.stats }; } /** * Get pending conflicts */ getPendingConflicts(): ConflictContext[] { return Array.from(this.pendingConflicts.values()); } /** * Get resolution history */ getResolutionHistory(limit: number = 100): ConflictResolution[] { return Array.from(this.resolutionHistory.values()) .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, limit); } /** * Clear old resolution history */ cleanupHistory(olderThan: Date): number { let cleaned = 0; for (const [id, resolution] of this.resolutionHistory) { if (resolution.timestamp < olderThan) { this.resolutionHistory.delete(id); cleaned++; } } this.logger.debug("Resolution history cleaned", { cleaned }); return cleaned; } /** * Private methods */ private initializeDefaultRules( defaultStrategy: ConflictResolutionStrategy, ): void { // Default rule for all conflicts this.addRule({ id: "default", name: "Default Resolution Strategy", pattern: ".*", strategy: defaultStrategy, priority: 0, conditions: [], }); // High-priority rule for critical system keys this.addRule({ id: "system_critical", name: "System Critical Keys", pattern: /^system:|^config:|^security:/, strategy: "priority", priority: 10, conditions: [{ field: "priority", operator: "greater", value: 8 }], }); // Rule for collaborative documents this.addRule({ id: "collaborative_text", name: "Collaborative Text Documents", pattern: /^doc:|^text:|^content:/, strategy: "operational", priority: 8, conditions: [ { field: "contentType", operator: "equals", value: "text/plain" }, ], }); // Rule for configuration merging this.addRule({ id: "config_merge", name: "Configuration Objects", pattern: /^config\./, strategy: "semantic", priority: 7, conditions: [], }); // Rule for sets and arrays this.addRule({ id: "set_union", name: "Set Union Resolution", pattern: /^set:|^list:|^array:/, strategy: "union", priority: 6, conditions: [], }); } private initializeBuiltinResolvers(): void { // Last-Writer-Wins resolver this.customResolvers.set("lww", async (context) => { const comparison = context.localValue.vectorClock.compare( context.remoteValue.vectorClock, ); let resolvedValue: any; let confidence = 0.9; if (comparison === "after") { resolvedValue = context.localValue.data; } else if (comparison === "before") { resolvedValue = context.remoteValue.data; } else { // Concurrent - use timestamp as tiebreaker resolvedValue = context.localValue.timestamp > context.remoteValue.timestamp ? context.localValue.data : context.remoteValue.data; confidence = 0.7; // Lower confidence for concurrent updates } return { resolutionId: this.generateResolutionId(), strategy: "lww", resolvedValue, confidence, reasoning: `LWW resolution based on vector clock comparison: ${comparison}`, requiresManualReview: false, timestamp: new Date(), resolverAgent: this.vectorClock.getAgents()[0] || "system", }; }); // Multi-value resolver this.customResolvers.set("mvr", async (context) => { const values = [context.localValue.data, context.remoteValue.data]; return { resolutionId: this.generateResolutionId(), strategy: "mvr", resolvedValue: values, confidence: 1.0, reasoning: "Multi-value resolution - preserving all concurrent values", alternativeValues: values, requiresManualReview: true, timestamp: new Date(), resolverAgent: this.vectorClock.getAgents()[0] || "system", }; }); // Priority-based resolver this.customResolvers.set("priority", async (context) => { const localPriority = context.localValue.metadata?.priority || 5; const remotePriority = context.remoteValue.metadata?.priority || 5; const resolvedValue = localPriority >= remotePriority ? context.localValue.data : context.remoteValue.data; return { resolutionId: this.generateResolutionId(), strategy: "priority", resolvedValue, confidence: 0.95, reasoning: `Priority-based resolution: local=${localPriority}, remote=${remotePriority}`, requiresManualReview: localPriority === remotePriority, timestamp: new Date(), resolverAgent: this.vectorClock.getAgents()[0] || "system", }; }); // Union resolver for sets/arrays this.customResolvers.set("union", async (context) => { let resolvedValue: any; if ( Array.isArray(context.localValue.data) && Array.isArray(context.remoteValue.data) ) { resolvedValue = [ ...new Set([...context.localValue.data, ...context.remoteValue.data]), ]; } else if ( this.isSet(context.localValue.data) && this.isSet(context.remoteValue.data) ) { resolvedValue = new Set([ ...context.localValue.data, ...context.remoteValue.data, ]); } else { // Fallback to array union const local = Array.isArray(context.localValue.data) ? context.localValue.data : [context.localValue.data]; const remote = Array.isArray(context.remoteValue.data) ? context.remoteValue.data : [context.remoteValue.data]; resolvedValue = [...new Set([...local, ...remote])]; } return { resolutionId: this.generateResolutionId(), strategy: "union", resolvedValue, confidence: 0.95, reasoning: "Union of all values from conflicting updates", requiresManualReview: false, timestamp: new Date(), resolverAgent: this.vectorClock.getAgents()[0] || "system", }; }); } private determineConflictType( localValue: ConflictValue, remoteValue: ConflictValue, ): ConflictContext["conflictType"] { const comparison = localValue.vectorClock.compare(remoteValue.vectorClock); if (comparison === "concurrent") { return "concurrent_write"; } // Check for structural conflicts if (this.hasStructuralDifferences(localValue.data, remoteValue.data)) { return "structural"; } // Check for semantic conflicts if (this.hasSemanticConflicts(localValue.data, remoteValue.data)) { return "semantic"; } return "read_write"; } private async findCommonAncestor( localValue: ConflictValue, remoteValue: ConflictValue, ): Promise<ConflictValue | undefined> { // In a real implementation, this would traverse the version history // to find the last common version before divergence // For now, return undefined indicating no common ancestor found return undefined; } private findApplicableRule(context: ConflictContext): ConflictRule { const applicableRules = Array.from(this.rules.values()) .filter((rule) => this.ruleMatches(rule, context)) .sort((a, b) => b.priority - a.priority); if (applicableRules.length === 0) { // Return default rule return this.rules.get("default")!; } return applicableRules[0]; } private ruleMatches(rule: ConflictRule, context: ConflictContext): boolean { // Check pattern match const pattern = typeof rule.pattern === "string" ? new RegExp(rule.pattern) : rule.pattern; if (!pattern.test(context.key)) { return false; } // Check conditions for (const condition of rule.conditions) { if (!this.evaluateCondition(condition, context)) { return false; } } return true; } private evaluateCondition( condition: ConflictCondition, context: ConflictContext, ): boolean { let value: any; // Extract field value from context if (condition.field.startsWith("local.")) { value = this.getNestedValue( context.localValue, condition.field.substring(6), ); } else if (condition.field.startsWith("remote.")) { value = this.getNestedValue( context.remoteValue, condition.field.substring(7), ); } else { value = this.getNestedValue(context.metadata, condition.field); } let result = false; switch (condition.operator) { case "equals": result = value === condition.value; break; case "contains": result = Array.isArray(value) ? value.includes(condition.value) : String(value).includes(String(condition.value)); break; case "matches": result = new RegExp(condition.value).test(String(value)); break; case "greater": result = Number(value) > Number(condition.value); break; case "less": result = Number(value) < Number(condition.value); break; } return condition.negate ? !result : result; } private async executeResolutionStrategy( context: ConflictContext, rule: ConflictRule, ): Promise<ConflictResolution> { if (rule.customResolver) { return await rule.customResolver(context); } const resolver = this.customResolvers.get(rule.strategy); if (resolver) { return await resolver(context); } // Built-in strategy implementations switch (rule.strategy) { case "semantic": return await this.executeSemanticResolution(context); case "operational": return await this.executeOperationalResolution(context); case "intersection": return await this.executeIntersectionResolution(context); default: throw new Error(`Unknown resolution strategy: ${rule.strategy}`); } } private async executeSemanticResolution( context: ConflictContext, ): Promise<ConflictResolution> { try { const mergedValue = await this.semanticMerge( context.localValue.data, context.remoteValue.data, ); return { resolutionId: this.generateResolutionId(), strategy: "semantic", resolvedValue: mergedValue, confidence: 0.85, reasoning: "Semantic merge of structured data", requiresManualReview: false, timestamp: new Date(), resolverAgent: this.vectorClock.getAgents()[0] || "system", }; } catch (error) { // Fallback to LWW on semantic merge failure return await this.customResolvers.get("lww")!(context); } } private async executeOperationalResolution( context: ConflictContext, ): Promise<ConflictResolution> { if ( typeof context.localValue.data !== "string" || typeof context.remoteValue.data !== "string" ) { // OT only works with strings, fallback to semantic merge return await this.executeSemanticResolution(context); } // Generate operational transforms const transforms = this.generateOperationalTransforms( context.commonAncestor?.data || "", context.localValue.data, context.remoteValue.data, ); // Apply transforms to base text const baseText = context.commonAncestor?.data || context.localValue.data; const resolvedText = await this.applyOperationalTransforms( baseText, transforms, ); return { resolutionId: this.generateResolutionId(), strategy: "operational", resolvedValue: resolvedText, confidence: 0.8, reasoning: "Operational transform resolution for collaborative text", appliedTransforms: transforms, requiresManualReview: transforms.length > 10, // Complex merges need review timestamp: new Date(), resolverAgent: this.vectorClock.getAgents()[0] || "system", }; } private async executeIntersectionResolution( context: ConflictContext, ): Promise<ConflictResolution> { let resolvedValue: any; if ( Array.isArray(context.localValue.data) && Array.isArray(context.remoteValue.data) ) { resolvedValue = context.localValue.data.filter((item) => context.remoteValue.data.includes(item), ); } else if ( this.isSet(context.localValue.data) && this.isSet(context.remoteValue.data) ) { resolvedValue = new Set( [...context.localValue.data].filter((item) => context.remoteValue.data.has(item), ), ); } else { // For non-collection types, use LWW as fallback return await this.customResolvers.get("lww")!(context); } return { resolutionId: this.generateResolutionId(), strategy: "intersection", resolvedValue, confidence: 0.9, reasoning: "Intersection of values from conflicting updates", requiresManualReview: Array.isArray(resolvedValue) && resolvedValue.length === 0, timestamp: new Date(), resolverAgent: this.vectorClock.getAgents()[0] || "system", }; } private generateOperationalTransforms( baseText: string, localText: string, remoteText: string, ): OperationalTransform[] { const transforms: OperationalTransform[] = []; // Simple diff-based transform generation // In a real implementation, use a proper diff algorithm like Myers or patience diff const localDiff = this.simpleDiff(baseText, localText); const remoteDiff = this.simpleDiff(baseText, remoteText); // Convert diffs to operational transforms for (const diff of localDiff) { transforms.push({ type: diff.type as any, position: diff.position, content: diff.content, length: diff.length, priority: 5, agentId: "local", timestamp: new Date(), }); } for (const diff of remoteDiff) { transforms.push({ type: diff.type as any, position: diff.position, content: diff.content, length: diff.length, priority: 5, agentId: "remote", timestamp: new Date(), }); } return transforms; } private simpleDiff( oldText: string, newText: string, ): Array<{ type: string; position: number; content?: string; length?: number; }> { // Very simple diff implementation - replace with proper algorithm if (oldText === newText) return []; if (oldText.length === 0) { return [ { type: "insert", position: 0, content: newText, }, ]; } if (newText.length === 0) { return [ { type: "delete", position: 0, length: oldText.length, }, ]; } // For now, just replace the entire text return [ { type: "replace", position: 0, content: newText, length: oldText.length, }, ]; } private applyTransform( text: string, transform: OperationalTransform, offset: number, ): string { const position = transform.position + offset; switch (transform.type) { case "insert": return ( text.slice(0, position) + (transform.content || "") + text.slice(position) ); case "delete": return ( text.slice(0, position) + text.slice(position + (transform.length || 0)) ); case "replace": return ( text.slice(0, position) + (transform.content || "") + text.slice(position + (transform.length || 0)) ); case "retain": return text; // No change default: return text; } } private calculateOffset( transform: OperationalTransform, currentOffset: number, ): number { switch (transform.type) { case "insert": return currentOffset + (transform.content?.length || 0); case "delete": return currentOffset - (transform.length || 0); case "replace": const oldLength = transform.length || 0; const newLength = transform.content?.length || 0; return currentOffset + (newLength - oldLength); default: return currentOffset; } } private mergeArrays(local: any[], remote: any[]): any[] { // Union of arrays with deduplication const merged = [...local]; for (const item of remote) { if (!merged.some((existing) => this.deepEquals(existing, item))) { merged.push(item); } } return merged; } private mergeObjects(local: any, remote: any, schema?: any): any { const merged = { ...local }; for (const [key, value] of Object.entries(remote)) { if (!(key in merged)) { // New key in remote merged[key] = value; } else if (this.isObject(merged[key]) && this.isObject(value)) { // Recursively merge nested objects merged[key] = this.mergeObjects(merged[key], value, schema?.[key]); } else if (Array.isArray(merged[key]) && Array.isArray(value)) { // Merge arrays merged[key] = this.mergeArrays(merged[key], value); } else if (merged[key] !== value) { // Conflict - use schema or fallback to remote value merged[key] = this.resolveObjectConflict( key, merged[key], value, schema, ); } } return merged; } private mergeStrings(local: string, remote: string): string { // Simple string merge - in practice, use proper text merging algorithms if (local === remote) return local; // Try to find common prefix and suffix let commonPrefix = ""; let commonSuffix = ""; const minLength = Math.min(local.length, remote.length); // Find common prefix for (let i = 0; i < minLength; i++) { if (local[i] === remote[i]) { commonPrefix += local[i]; } else { break; } } // Find common suffix for (let i = 1; i <= minLength - commonPrefix.length; i++) { if (local[local.length - i] === remote[remote.length - i]) { commonSuffix = local[local.length - i] + commonSuffix; } else { break; } } const localMiddle = local.slice( commonPrefix.length, local.length - commonSuffix.length, ); const remoteMiddle = remote.slice( commonPrefix.length, remote.length - commonSuffix.length, ); // Combine different parts return commonPrefix + localMiddle + remoteMiddle + commonSuffix; } private mergeNumbers(local: number, remote: number): number { // Average of numbers (simple strategy) return (local + remote) / 2; } private resolveObjectConflict( key: string, localValue: any, remoteValue: any, schema?: any, ): any { // Use schema hints if available if (schema?.mergeStrategy) { switch (schema.mergeStrategy) { case "prefer_local": return localValue; case "prefer_remote": return remoteValue; case "sum": return Number(localValue) + Number(remoteValue); case "max": return Math.max(Number(localValue), Number(remoteValue)); case "min": return Math.min(Number(localValue), Number(remoteValue)); } } // Default to remote value return remoteValue; } private createFallbackResolution( localValue: ConflictValue, remoteValue: ConflictValue, ): ConflictResolution { return { resolutionId: this.generateResolutionId(), strategy: "lww", resolvedValue: localValue.data, // Default to local confidence: 0.5, reasoning: "Fallback resolution due to resolution error", requiresManualReview: true, timestamp: new Date(), resolverAgent: this.vectorClock.getAgents()[0] || "system", }; } private recordResolution( context: ConflictContext, resolution: ConflictResolution, ): void { const conflictId = this.generateConflictId(context); this.resolutionHistory.set(resolution.resolutionId, resolution); this.pendingConflicts.delete(conflictId); this.stats.resolvedConflicts++; this.stats.pendingConflicts = this.pendingConflicts.size; if (resolution.requiresManualReview) { this.stats.manualReviewRequired++; } } private updateStats( resolution: ConflictResolution, resolutionTime: number, ): void { // Update strategy statistics const strategyCount = this.stats.resolutionsByStrategy.get(resolution.strategy) || 0; this.stats.resolutionsByStrategy.set( resolution.strategy, strategyCount + 1, ); // Update average resolution time this.stats.averageResolutionTime = (this.stats.averageResolutionTime + resolutionTime) / 2; // Update accuracy score based on confidence this.stats.accuracyScore = (this.stats.accuracyScore + resolution.confidence) / 2; } private updateConflictRate(): void { // Calculate conflicts per time unit (simplified) const timeWindow = 60000; // 1 minute this.stats.conflictRate = this.stats.totalConflicts / (timeWindow / 1000); } // Utility methods private generateConflictId(context: ConflictContext): string { return `conflict_${context.key}_${context.metadata.timestamp.getTime()}`; } private generateResolutionId(): string { return `resolution_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private isObject(value: any): boolean { return value !== null && typeof value === "object" && !Array.isArray(value); } private isSet(value: any): boolean { return value instanceof Set; } private deepEquals(a: any, b: any): boolean { if (a === b) return true; if (a == null || b == null) return false; if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (!this.deepEquals(a[i], b[i])) return false; } return true; } if (this.isObject(a) && this.isObject(b)) { const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; for (const key of keysA) { if (!keysB.includes(key)) return false; if (!this.deepEquals(a[key], b[key])) return false; } return true; } return false; } private getNestedValue(obj: any, path: string): any { return path.split(".").reduce((current, key) => current?.[key], obj); } private hasStructuralDifferences(local: any, remote: any): boolean { // Check if the structure (keys, types) differs significantly if (typeof local !== typeof remote) return true; if (this.isObject(local) && this.isObject(remote)) { const localKeys = Object.keys(local); const remoteKeys = Object.keys(remote); // More than 50% different keys indicates structural difference const commonKeys = localKeys.filter((key) => remoteKeys.includes(key)); const totalKeys = new Set([...localKeys, ...remoteKeys]).size; return commonKeys.length / totalKeys < 0.5; } return false; } private hasSemanticConflicts(local: any, remote: any): boolean { // Detect semantic conflicts (values that shouldn't be automatically merged) if (this.isObject(local) && this.isObject(remote)) { // Check for conflicting business logic fields const criticalFields = ["id", "version", "status", "state"]; for (const field of criticalFields) { if (local[field] && remote[field] && local[field] !== remote[field]) { return true; } } } return false; } }