 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			999 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			999 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package temporal
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"chorus/pkg/ucxl"
 | |
| )
 | |
| 
 | |
| // querySystemImpl implements decision-hop based query operations
 | |
| type querySystemImpl struct {
 | |
| 	mu sync.RWMutex
 | |
| 	
 | |
| 	// Reference to the temporal graph
 | |
| 	graph *temporalGraphImpl
 | |
| 	navigator DecisionNavigator
 | |
| 	analyzer InfluenceAnalyzer
 | |
| 	detector StalenessDetector
 | |
| 	
 | |
| 	// Query optimization
 | |
| 	queryCache map[string]interface{}
 | |
| 	cacheTimeout time.Duration
 | |
| 	lastCacheClean time.Time
 | |
| 	
 | |
| 	// Query statistics
 | |
| 	queryStats map[string]*QueryStatistics
 | |
| }
 | |
| 
 | |
| // QueryStatistics represents statistics for different query types
 | |
| type QueryStatistics struct {
 | |
| 	QueryType       string        `json:"query_type"`
 | |
| 	TotalQueries    int64         `json:"total_queries"`
 | |
| 	AverageTime     time.Duration `json:"average_time"`
 | |
| 	CacheHits       int64         `json:"cache_hits"`
 | |
| 	CacheMisses     int64         `json:"cache_misses"`
 | |
| 	LastQuery       time.Time     `json:"last_query"`
 | |
| }
 | |
| 
 | |
| // HopQuery represents a decision-hop based query
 | |
| type HopQuery struct {
 | |
| 	StartAddress    ucxl.Address    `json:"start_address"`    // Starting point
 | |
| 	MaxHops         int             `json:"max_hops"`         // Maximum hops to traverse
 | |
| 	Direction       string          `json:"direction"`        // "forward", "backward", "both"
 | |
| 	FilterCriteria  *HopFilter      `json:"filter_criteria"`  // Filtering options
 | |
| 	SortCriteria    *HopSort        `json:"sort_criteria"`    // Sorting options
 | |
| 	Limit           int             `json:"limit"`            // Maximum results
 | |
| 	IncludeMetadata bool            `json:"include_metadata"` // Include detailed metadata
 | |
| }
 | |
| 
 | |
| // HopFilter represents filtering criteria for hop queries
 | |
| type HopFilter struct {
 | |
| 	ChangeReasons       []ChangeReason  `json:"change_reasons"`       // Filter by change reasons
 | |
| 	ImpactScopes        []ImpactScope   `json:"impact_scopes"`        // Filter by impact scopes  
 | |
| 	MinConfidence       float64         `json:"min_confidence"`       // Minimum confidence threshold
 | |
| 	MaxAge              time.Duration   `json:"max_age"`              // Maximum age of decisions
 | |
| 	DecisionMakers      []string        `json:"decision_makers"`      // Filter by decision makers
 | |
| 	Tags                []string        `json:"tags"`                 // Filter by context tags
 | |
| 	Technologies        []string        `json:"technologies"`         // Filter by technologies
 | |
| 	MinInfluenceCount   int             `json:"min_influence_count"`  // Minimum number of influences
 | |
| 	ExcludeStale        bool            `json:"exclude_stale"`        // Exclude stale contexts
 | |
| 	OnlyMajorDecisions  bool            `json:"only_major_decisions"` // Only major decisions
 | |
| }
 | |
| 
 | |
| // HopSort represents sorting criteria for hop queries  
 | |
| type HopSort struct {
 | |
| 	SortBy        string `json:"sort_by"`        // "hops", "time", "confidence", "influence"
 | |
| 	SortDirection string `json:"sort_direction"` // "asc", "desc"
 | |
| 	SecondarySort string `json:"secondary_sort"` // Secondary sort field
 | |
| }
 | |
| 
 | |
| // HopQueryResult represents the result of a hop-based query
 | |
| type HopQueryResult struct {
 | |
| 	Query           *HopQuery         `json:"query"`            // Original query
 | |
| 	Results         []*HopResult      `json:"results"`          // Query results
 | |
| 	TotalFound      int               `json:"total_found"`      // Total results found
 | |
| 	ExecutionTime   time.Duration     `json:"execution_time"`   // Query execution time
 | |
| 	FromCache       bool              `json:"from_cache"`       // Whether result came from cache
 | |
| 	QueryPath       []*QueryPathStep  `json:"query_path"`       // Path of query execution
 | |
| 	Statistics      *QueryExecution   `json:"statistics"`       // Execution statistics
 | |
| }
 | |
| 
 | |
| // HopResult represents a single result from a hop query
 | |
| type HopResult struct {
 | |
| 	Address         ucxl.Address      `json:"address"`          // Context address
 | |
| 	HopDistance     int               `json:"hop_distance"`     // Decision hops from start
 | |
| 	TemporalNode    *TemporalNode     `json:"temporal_node"`    // Temporal node data
 | |
| 	Path            []*DecisionStep   `json:"path"`             // Path from start to this result
 | |
| 	Relationship    string            `json:"relationship"`     // Relationship type
 | |
| 	RelevanceScore  float64           `json:"relevance_score"`  // Relevance to query
 | |
| 	MatchReasons    []string          `json:"match_reasons"`    // Why this matched
 | |
| 	Metadata        map[string]interface{} `json:"metadata"`     // Additional metadata
 | |
| }
 | |
| 
 | |
| // QueryPathStep represents a step in query execution path
 | |
| type QueryPathStep struct {
 | |
| 	Step            int               `json:"step"`             // Step number
 | |
| 	Operation       string            `json:"operation"`        // Operation performed
 | |
| 	NodesExamined   int               `json:"nodes_examined"`   // Nodes examined in this step
 | |
| 	NodesFiltered   int               `json:"nodes_filtered"`   // Nodes filtered out
 | |
| 	Duration        time.Duration     `json:"duration"`         // Step duration
 | |
| 	Description     string            `json:"description"`      // Step description
 | |
| }
 | |
| 
 | |
| // QueryExecution represents query execution statistics
 | |
| type QueryExecution struct {
 | |
| 	StartTime       time.Time         `json:"start_time"`       // Query start time
 | |
| 	EndTime         time.Time         `json:"end_time"`         // Query end time
 | |
| 	Duration        time.Duration     `json:"duration"`         // Total duration
 | |
| 	NodesVisited    int               `json:"nodes_visited"`    // Total nodes visited
 | |
| 	EdgesTraversed  int               `json:"edges_traversed"`  // Total edges traversed
 | |
| 	CacheAccesses   int               `json:"cache_accesses"`   // Cache access count
 | |
| 	FilterSteps     int               `json:"filter_steps"`     // Number of filter steps
 | |
| 	SortOperations  int               `json:"sort_operations"`  // Number of sort operations
 | |
| 	MemoryUsed      int64             `json:"memory_used"`      // Estimated memory used
 | |
| }
 | |
| 
 | |
| // NewQuerySystem creates a new decision-hop query system
 | |
| func NewQuerySystem(graph *temporalGraphImpl, navigator DecisionNavigator, 
 | |
| 	analyzer InfluenceAnalyzer, detector StalenessDetector) *querySystemImpl {
 | |
| 	return &querySystemImpl{
 | |
| 		graph:          graph,
 | |
| 		navigator:      navigator,
 | |
| 		analyzer:       analyzer,
 | |
| 		detector:       detector,
 | |
| 		queryCache:     make(map[string]interface{}),
 | |
| 		cacheTimeout:   time.Minute * 10,
 | |
| 		lastCacheClean: time.Now(),
 | |
| 		queryStats:     make(map[string]*QueryStatistics),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ExecuteHopQuery executes a decision-hop based query
 | |
| func (qs *querySystemImpl) ExecuteHopQuery(ctx context.Context, query *HopQuery) (*HopQueryResult, error) {
 | |
| 	startTime := time.Now()
 | |
| 	
 | |
| 	// Validate query
 | |
| 	if err := qs.validateQuery(query); err != nil {
 | |
| 		return nil, fmt.Errorf("invalid query: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	// Check cache
 | |
| 	cacheKey := qs.generateCacheKey(query)
 | |
| 	if cached, found := qs.getFromCache(cacheKey); found {
 | |
| 		if result, ok := cached.(*HopQueryResult); ok {
 | |
| 			result.FromCache = true
 | |
| 			qs.updateQueryStats("hop_query", time.Since(startTime), true)
 | |
| 			return result, nil
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	// Execute query
 | |
| 	result, err := qs.executeHopQueryInternal(ctx, query)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	
 | |
| 	// Set execution time and cache result
 | |
| 	result.ExecutionTime = time.Since(startTime)
 | |
| 	result.FromCache = false
 | |
| 	qs.setCache(cacheKey, result)
 | |
| 	qs.updateQueryStats("hop_query", result.ExecutionTime, false)
 | |
| 	
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| // FindDecisionsWithinHops finds all decisions within N hops of a given address
 | |
| func (qs *querySystemImpl) FindDecisionsWithinHops(ctx context.Context, address ucxl.Address, 
 | |
| 	maxHops int, filter *HopFilter) ([]*HopResult, error) {
 | |
| 	
 | |
| 	query := &HopQuery{
 | |
| 		StartAddress:    address,
 | |
| 		MaxHops:         maxHops,
 | |
| 		Direction:       "both",
 | |
| 		FilterCriteria:  filter,
 | |
| 		SortCriteria:    &HopSort{SortBy: "hops", SortDirection: "asc"},
 | |
| 		IncludeMetadata: false,
 | |
| 	}
 | |
| 	
 | |
| 	result, err := qs.ExecuteHopQuery(ctx, query)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	
 | |
| 	return result.Results, nil
 | |
| }
 | |
| 
 | |
| // FindInfluenceChain finds the chain of influence between two decisions
 | |
| func (qs *querySystemImpl) FindInfluenceChain(ctx context.Context, from, to ucxl.Address) ([]*DecisionStep, error) {
 | |
| 	// Use the temporal graph's path finding
 | |
| 	return qs.graph.FindDecisionPath(ctx, from, to)
 | |
| }
 | |
| 
 | |
| // AnalyzeDecisionGenealogy analyzes the genealogy of decisions for a context
 | |
| func (qs *querySystemImpl) AnalyzeDecisionGenealogy(ctx context.Context, address ucxl.Address) (*DecisionGenealogy, error) {
 | |
| 	qs.mu.RLock()
 | |
| 	defer qs.mu.RUnlock()
 | |
| 	
 | |
| 	// Get evolution history
 | |
| 	history, err := qs.graph.GetEvolutionHistory(ctx, address)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to get evolution history: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	// Get decision timeline
 | |
| 	timeline, err := qs.navigator.GetDecisionTimeline(ctx, address, true, 10)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to get decision timeline: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	// Analyze ancestry
 | |
| 	ancestry := qs.analyzeAncestry(history)
 | |
| 	
 | |
| 	// Analyze descendants  
 | |
| 	descendants := qs.analyzeDescendants(address, 5)
 | |
| 	
 | |
| 	// Find influential ancestors
 | |
| 	influentialAncestors := qs.findInfluentialAncestors(history)
 | |
| 	
 | |
| 	// Calculate genealogy metrics
 | |
| 	metrics := qs.calculateGenealogyMetrics(history, descendants)
 | |
| 	
 | |
| 	genealogy := &DecisionGenealogy{
 | |
| 		Address:              address,
 | |
| 		DirectAncestors:      ancestry.DirectAncestors,
 | |
| 		AllAncestors:         ancestry.AllAncestors,
 | |
| 		DirectDescendants:    descendants.DirectDescendants,
 | |
| 		AllDescendants:       descendants.AllDescendants,
 | |
| 		InfluentialAncestors: influentialAncestors,
 | |
| 		GenealogyDepth:       ancestry.MaxDepth,
 | |
| 		BranchingFactor:      descendants.BranchingFactor,
 | |
| 		DecisionTimeline:     timeline,
 | |
| 		Metrics:             metrics,
 | |
| 		AnalyzedAt:          time.Now(),
 | |
| 	}
 | |
| 	
 | |
| 	return genealogy, nil
 | |
| }
 | |
| 
 | |
| // FindSimilarDecisionPatterns finds decisions with similar patterns
 | |
| func (qs *querySystemImpl) FindSimilarDecisionPatterns(ctx context.Context, referenceAddress ucxl.Address, 
 | |
| 	maxResults int) ([]*SimilarDecisionMatch, error) {
 | |
| 	
 | |
| 	qs.mu.RLock()
 | |
| 	defer qs.mu.RUnlock()
 | |
| 	
 | |
| 	// Get reference node
 | |
| 	refNode, err := qs.graph.getLatestNodeUnsafe(referenceAddress)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("reference node not found: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	matches := make([]*SimilarDecisionMatch, 0)
 | |
| 	
 | |
| 	// Compare with all other nodes
 | |
| 	for _, node := range qs.graph.nodes {
 | |
| 		if node.UCXLAddress.String() == referenceAddress.String() {
 | |
| 			continue // Skip self
 | |
| 		}
 | |
| 		
 | |
| 		similarity := qs.calculateDecisionSimilarity(refNode, node)
 | |
| 		if similarity > 0.3 { // Threshold for meaningful similarity
 | |
| 			match := &SimilarDecisionMatch{
 | |
| 				Address:         node.UCXLAddress,
 | |
| 				TemporalNode:    node,
 | |
| 				SimilarityScore: similarity,
 | |
| 				SimilarityReasons: qs.getSimilarityReasons(refNode, node),
 | |
| 				PatternType:     qs.identifyPatternType(refNode, node),
 | |
| 				Confidence:      similarity * 0.9, // Slightly lower confidence
 | |
| 			}
 | |
| 			matches = append(matches, match)
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	// Sort by similarity score
 | |
| 	sort.Slice(matches, func(i, j int) bool {
 | |
| 		return matches[i].SimilarityScore > matches[j].SimilarityScore
 | |
| 	})
 | |
| 	
 | |
| 	// Limit results
 | |
| 	if maxResults > 0 && len(matches) > maxResults {
 | |
| 		matches = matches[:maxResults]
 | |
| 	}
 | |
| 	
 | |
| 	return matches, nil
 | |
| }
 | |
| 
 | |
| // DiscoverDecisionClusters discovers clusters of related decisions
 | |
| func (qs *querySystemImpl) DiscoverDecisionClusters(ctx context.Context, minClusterSize int) ([]*DecisionCluster, error) {
 | |
| 	qs.mu.RLock()
 | |
| 	defer qs.mu.RUnlock()
 | |
| 	
 | |
| 	// Use influence analyzer to get clusters
 | |
| 	analysis, err := qs.analyzer.AnalyzeInfluenceNetwork(ctx)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to analyze influence network: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	// Filter clusters by minimum size
 | |
| 	clusters := make([]*DecisionCluster, 0)
 | |
| 	for _, community := range analysis.Communities {
 | |
| 		if len(community.Nodes) >= minClusterSize {
 | |
| 			cluster := qs.convertCommunityToCluster(community)
 | |
| 			clusters = append(clusters, cluster)
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	return clusters, nil
 | |
| }
 | |
| 
 | |
| // Internal query execution
 | |
| 
 | |
| func (qs *querySystemImpl) executeHopQueryInternal(ctx context.Context, query *HopQuery) (*HopQueryResult, error) {
 | |
| 	execution := &QueryExecution{
 | |
| 		StartTime: time.Now(),
 | |
| 	}
 | |
| 	
 | |
| 	queryPath := make([]*QueryPathStep, 0)
 | |
| 	
 | |
| 	// Step 1: Get starting node
 | |
| 	step1Start := time.Now()
 | |
| 	startNode, err := qs.graph.getLatestNodeUnsafe(query.StartAddress)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("start node not found: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	queryPath = append(queryPath, &QueryPathStep{
 | |
| 		Step:          1,
 | |
| 		Operation:     "get_start_node",
 | |
| 		NodesExamined: 1,
 | |
| 		NodesFiltered: 0,
 | |
| 		Duration:      time.Since(step1Start),
 | |
| 		Description:   "Retrieved starting node",
 | |
| 	})
 | |
| 	
 | |
| 	// Step 2: Traverse decision graph
 | |
| 	step2Start := time.Now()
 | |
| 	candidates := qs.traverseDecisionGraph(startNode, query.MaxHops, query.Direction)
 | |
| 	execution.NodesVisited = len(candidates)
 | |
| 	
 | |
| 	queryPath = append(queryPath, &QueryPathStep{
 | |
| 		Step:          2,
 | |
| 		Operation:     "traverse_graph",
 | |
| 		NodesExamined: len(candidates),
 | |
| 		NodesFiltered: 0,
 | |
| 		Duration:      time.Since(step2Start),
 | |
| 		Description:   fmt.Sprintf("Traversed decision graph up to %d hops", query.MaxHops),
 | |
| 	})
 | |
| 	
 | |
| 	// Step 3: Apply filters
 | |
| 	step3Start := time.Now()
 | |
| 	filtered := qs.applyFilters(candidates, query.FilterCriteria)
 | |
| 	execution.FilterSteps = 1
 | |
| 	
 | |
| 	queryPath = append(queryPath, &QueryPathStep{
 | |
| 		Step:          3,
 | |
| 		Operation:     "apply_filters",
 | |
| 		NodesExamined: len(candidates),
 | |
| 		NodesFiltered: len(candidates) - len(filtered),
 | |
| 		Duration:      time.Since(step3Start),
 | |
| 		Description:   fmt.Sprintf("Applied filters, removed %d candidates", len(candidates)-len(filtered)),
 | |
| 	})
 | |
| 	
 | |
| 	// Step 4: Calculate relevance scores
 | |
| 	step4Start := time.Now()
 | |
| 	results := qs.calculateRelevanceScores(filtered, startNode, query)
 | |
| 	
 | |
| 	queryPath = append(queryPath, &QueryPathStep{
 | |
| 		Step:          4,
 | |
| 		Operation:     "calculate_relevance",
 | |
| 		NodesExamined: len(filtered),
 | |
| 		NodesFiltered: 0,
 | |
| 		Duration:      time.Since(step4Start),
 | |
| 		Description:   "Calculated relevance scores",
 | |
| 	})
 | |
| 	
 | |
| 	// Step 5: Sort results
 | |
| 	step5Start := time.Time{}
 | |
| 	if query.SortCriteria != nil {
 | |
| 		step5Start = time.Now()
 | |
| 		qs.sortResults(results, query.SortCriteria)
 | |
| 		execution.SortOperations = 1
 | |
| 		
 | |
| 		queryPath = append(queryPath, &QueryPathStep{
 | |
| 			Step:          5,
 | |
| 			Operation:     "sort_results",
 | |
| 			NodesExamined: len(results),
 | |
| 			NodesFiltered: 0,
 | |
| 			Duration:      time.Since(step5Start),
 | |
| 			Description:   fmt.Sprintf("Sorted by %s %s", query.SortCriteria.SortBy, query.SortCriteria.SortDirection),
 | |
| 		})
 | |
| 	}
 | |
| 	
 | |
| 	// Step 6: Apply limit
 | |
| 	totalFound := len(results)
 | |
| 	if query.Limit > 0 && len(results) > query.Limit {
 | |
| 		results = results[:query.Limit]
 | |
| 	}
 | |
| 	
 | |
| 	// Complete execution statistics
 | |
| 	execution.EndTime = time.Now()
 | |
| 	execution.Duration = execution.EndTime.Sub(execution.StartTime)
 | |
| 	
 | |
| 	result := &HopQueryResult{
 | |
| 		Query:         query,
 | |
| 		Results:       results,
 | |
| 		TotalFound:    totalFound,
 | |
| 		ExecutionTime: execution.Duration,
 | |
| 		FromCache:     false,
 | |
| 		QueryPath:     queryPath,
 | |
| 		Statistics:    execution,
 | |
| 	}
 | |
| 	
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) traverseDecisionGraph(startNode *TemporalNode, maxHops int, direction string) []*hopCandidate {
 | |
| 	candidates := make([]*hopCandidate, 0)
 | |
| 	visited := make(map[string]bool)
 | |
| 	
 | |
| 	// BFS traversal
 | |
| 	queue := []*hopCandidate{{
 | |
| 		node:     startNode,
 | |
| 		distance: 0,
 | |
| 		path:     []*DecisionStep{},
 | |
| 	}}
 | |
| 	
 | |
| 	for len(queue) > 0 {
 | |
| 		current := queue[0]
 | |
| 		queue = queue[1:]
 | |
| 		
 | |
| 		nodeID := current.node.ID
 | |
| 		if visited[nodeID] || current.distance > maxHops {
 | |
| 			continue
 | |
| 		}
 | |
| 		visited[nodeID] = true
 | |
| 		
 | |
| 		// Add to candidates (except start node)
 | |
| 		if current.distance > 0 {
 | |
| 			candidates = append(candidates, current)
 | |
| 		}
 | |
| 		
 | |
| 		// Add neighbors based on direction
 | |
| 		if direction == "forward" || direction == "both" {
 | |
| 			qs.addForwardNeighbors(current, &queue, maxHops)
 | |
| 		}
 | |
| 		
 | |
| 		if direction == "backward" || direction == "both" {
 | |
| 			qs.addBackwardNeighbors(current, &queue, maxHops)
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	return candidates
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) applyFilters(candidates []*hopCandidate, filter *HopFilter) []*hopCandidate {
 | |
| 	if filter == nil {
 | |
| 		return candidates
 | |
| 	}
 | |
| 	
 | |
| 	filtered := make([]*hopCandidate, 0)
 | |
| 	
 | |
| 	for _, candidate := range candidates {
 | |
| 		if qs.passesFilter(candidate, filter) {
 | |
| 			filtered = append(filtered, candidate)
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	return filtered
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) passesFilter(candidate *hopCandidate, filter *HopFilter) bool {
 | |
| 	node := candidate.node
 | |
| 	
 | |
| 	// Change reason filter
 | |
| 	if len(filter.ChangeReasons) > 0 {
 | |
| 		found := false
 | |
| 		for _, reason := range filter.ChangeReasons {
 | |
| 			if node.ChangeReason == reason {
 | |
| 				found = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	// Impact scope filter
 | |
| 	if len(filter.ImpactScopes) > 0 {
 | |
| 		found := false
 | |
| 		for _, scope := range filter.ImpactScopes {
 | |
| 			if node.ImpactScope == scope {
 | |
| 				found = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	// Confidence filter
 | |
| 	if filter.MinConfidence > 0 && node.Confidence < filter.MinConfidence {
 | |
| 		return false
 | |
| 	}
 | |
| 	
 | |
| 	// Age filter
 | |
| 	if filter.MaxAge > 0 && time.Since(node.Timestamp) > filter.MaxAge {
 | |
| 		return false
 | |
| 	}
 | |
| 	
 | |
| 	// Decision maker filter
 | |
| 	if len(filter.DecisionMakers) > 0 {
 | |
| 		if decision, exists := qs.graph.decisions[node.DecisionID]; exists {
 | |
| 			found := false
 | |
| 			for _, maker := range filter.DecisionMakers {
 | |
| 				if decision.Maker == maker {
 | |
| 					found = true
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			if !found {
 | |
| 				return false
 | |
| 			}
 | |
| 		} else {
 | |
| 			return false // No decision metadata
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	// Technology filter
 | |
| 	if len(filter.Technologies) > 0 && node.Context != nil {
 | |
| 		found := false
 | |
| 		for _, filterTech := range filter.Technologies {
 | |
| 			for _, nodeTech := range node.Context.Technologies {
 | |
| 				if nodeTech == filterTech {
 | |
| 					found = true
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			if found {
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	// Tag filter
 | |
| 	if len(filter.Tags) > 0 && node.Context != nil {
 | |
| 		found := false
 | |
| 		for _, filterTag := range filter.Tags {
 | |
| 			for _, nodeTag := range node.Context.Tags {
 | |
| 				if nodeTag == filterTag {
 | |
| 					found = true
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			if found {
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !found {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	// Influence count filter
 | |
| 	if filter.MinInfluenceCount > 0 && len(node.Influences) < filter.MinInfluenceCount {
 | |
| 		return false
 | |
| 	}
 | |
| 	
 | |
| 	// Staleness filter
 | |
| 	if filter.ExcludeStale && node.Staleness > 0.6 {
 | |
| 		return false
 | |
| 	}
 | |
| 	
 | |
| 	// Major decisions filter
 | |
| 	if filter.OnlyMajorDecisions && !qs.isMajorDecision(node) {
 | |
| 		return false
 | |
| 	}
 | |
| 	
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) calculateRelevanceScores(candidates []*hopCandidate, startNode *TemporalNode, query *HopQuery) []*HopResult {
 | |
| 	results := make([]*HopResult, len(candidates))
 | |
| 	
 | |
| 	for i, candidate := range candidates {
 | |
| 		relevanceScore := qs.calculateRelevance(candidate, startNode, query)
 | |
| 		matchReasons := qs.getMatchReasons(candidate, query.FilterCriteria)
 | |
| 		
 | |
| 		results[i] = &HopResult{
 | |
| 			Address:        candidate.node.UCXLAddress,
 | |
| 			HopDistance:    candidate.distance,
 | |
| 			TemporalNode:   candidate.node,
 | |
| 			Path:           candidate.path,
 | |
| 			Relationship:   qs.determineRelationship(candidate, startNode),
 | |
| 			RelevanceScore: relevanceScore,
 | |
| 			MatchReasons:   matchReasons,
 | |
| 			Metadata:       qs.buildMetadata(candidate, query.IncludeMetadata),
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	return results
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) calculateRelevance(candidate *hopCandidate, startNode *TemporalNode, query *HopQuery) float64 {
 | |
| 	score := 1.0
 | |
| 	
 | |
| 	// Distance-based relevance (closer = more relevant)
 | |
| 	distanceScore := 1.0 - (float64(candidate.distance-1) / float64(query.MaxHops))
 | |
| 	score *= distanceScore
 | |
| 	
 | |
| 	// Confidence-based relevance
 | |
| 	confidenceScore := candidate.node.Confidence
 | |
| 	score *= confidenceScore
 | |
| 	
 | |
| 	// Recency-based relevance
 | |
| 	age := time.Since(candidate.node.Timestamp)
 | |
| 	recencyScore := math.Max(0.1, 1.0-age.Hours()/(30*24)) // Decay over 30 days
 | |
| 	score *= recencyScore
 | |
| 	
 | |
| 	// Impact-based relevance
 | |
| 	var impactScore float64
 | |
| 	switch candidate.node.ImpactScope {
 | |
| 	case ImpactSystem:
 | |
| 		impactScore = 1.0
 | |
| 	case ImpactProject:
 | |
| 		impactScore = 0.8
 | |
| 	case ImpactModule:
 | |
| 		impactScore = 0.6
 | |
| 	case ImpactLocal:
 | |
| 		impactScore = 0.4
 | |
| 	}
 | |
| 	score *= impactScore
 | |
| 	
 | |
| 	return math.Min(1.0, score)
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) sortResults(results []*HopResult, sortCriteria *HopSort) {
 | |
| 	sort.Slice(results, func(i, j int) bool {
 | |
| 		var aVal, bVal float64
 | |
| 		
 | |
| 		switch sortCriteria.SortBy {
 | |
| 		case "hops":
 | |
| 			aVal, bVal = float64(results[i].HopDistance), float64(results[j].HopDistance)
 | |
| 		case "time":
 | |
| 			aVal, bVal = float64(results[i].TemporalNode.Timestamp.Unix()), float64(results[j].TemporalNode.Timestamp.Unix())
 | |
| 		case "confidence":
 | |
| 			aVal, bVal = results[i].TemporalNode.Confidence, results[j].TemporalNode.Confidence
 | |
| 		case "influence":
 | |
| 			aVal, bVal = float64(len(results[i].TemporalNode.Influences)), float64(len(results[j].TemporalNode.Influences))
 | |
| 		case "relevance":
 | |
| 			aVal, bVal = results[i].RelevanceScore, results[j].RelevanceScore
 | |
| 		default:
 | |
| 			aVal, bVal = results[i].RelevanceScore, results[j].RelevanceScore
 | |
| 		}
 | |
| 		
 | |
| 		if sortCriteria.SortDirection == "desc" {
 | |
| 			return aVal > bVal
 | |
| 		}
 | |
| 		return aVal < bVal
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Helper methods and types
 | |
| 
 | |
| type hopCandidate struct {
 | |
| 	node     *TemporalNode
 | |
| 	distance int
 | |
| 	path     []*DecisionStep
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) addForwardNeighbors(current *hopCandidate, queue *[]*hopCandidate, maxHops int) {
 | |
| 	if current.distance >= maxHops {
 | |
| 		return
 | |
| 	}
 | |
| 	
 | |
| 	nodeID := current.node.ID
 | |
| 	if influences, exists := qs.graph.influences[nodeID]; exists {
 | |
| 		for _, influencedID := range influences {
 | |
| 			if influencedNode, exists := qs.graph.nodes[influencedID]; exists {
 | |
| 				step := &DecisionStep{
 | |
| 					Address:      current.node.UCXLAddress,
 | |
| 					TemporalNode: current.node,
 | |
| 					HopDistance:  current.distance,
 | |
| 					Relationship: "influences",
 | |
| 				}
 | |
| 				newPath := append(current.path, step)
 | |
| 				
 | |
| 				*queue = append(*queue, &hopCandidate{
 | |
| 					node:     influencedNode,
 | |
| 					distance: current.distance + 1,
 | |
| 					path:     newPath,
 | |
| 				})
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) addBackwardNeighbors(current *hopCandidate, queue *[]*hopCandidate, maxHops int) {
 | |
| 	if current.distance >= maxHops {
 | |
| 		return
 | |
| 	}
 | |
| 	
 | |
| 	nodeID := current.node.ID
 | |
| 	if influencedBy, exists := qs.graph.influencedBy[nodeID]; exists {
 | |
| 		for _, influencerID := range influencedBy {
 | |
| 			if influencerNode, exists := qs.graph.nodes[influencerID]; exists {
 | |
| 				step := &DecisionStep{
 | |
| 					Address:      current.node.UCXLAddress,
 | |
| 					TemporalNode: current.node,
 | |
| 					HopDistance:  current.distance,
 | |
| 					Relationship: "influenced_by",
 | |
| 				}
 | |
| 				newPath := append(current.path, step)
 | |
| 				
 | |
| 				*queue = append(*queue, &hopCandidate{
 | |
| 					node:     influencerNode,
 | |
| 					distance: current.distance + 1,
 | |
| 					path:     newPath,
 | |
| 				})
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) isMajorDecision(node *TemporalNode) bool {
 | |
| 	return node.ChangeReason == ReasonArchitectureChange ||
 | |
| 		   node.ChangeReason == ReasonDesignDecision ||
 | |
| 		   node.ChangeReason == ReasonRequirementsChange ||
 | |
| 		   node.ImpactScope == ImpactSystem ||
 | |
| 		   node.ImpactScope == ImpactProject
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) getMatchReasons(candidate *hopCandidate, filter *HopFilter) []string {
 | |
| 	reasons := make([]string, 0)
 | |
| 	
 | |
| 	if filter == nil {
 | |
| 		reasons = append(reasons, "no_filters_applied")
 | |
| 		return reasons
 | |
| 	}
 | |
| 	
 | |
| 	node := candidate.node
 | |
| 	
 | |
| 	if len(filter.ChangeReasons) > 0 {
 | |
| 		for _, reason := range filter.ChangeReasons {
 | |
| 			if node.ChangeReason == reason {
 | |
| 				reasons = append(reasons, fmt.Sprintf("change_reason: %s", reason))
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	if len(filter.ImpactScopes) > 0 {
 | |
| 		for _, scope := range filter.ImpactScopes {
 | |
| 			if node.ImpactScope == scope {
 | |
| 				reasons = append(reasons, fmt.Sprintf("impact_scope: %s", scope))
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	if filter.MinConfidence > 0 && node.Confidence >= filter.MinConfidence {
 | |
| 		reasons = append(reasons, fmt.Sprintf("confidence: %.2f >= %.2f", node.Confidence, filter.MinConfidence))
 | |
| 	}
 | |
| 	
 | |
| 	if filter.MinInfluenceCount > 0 && len(node.Influences) >= filter.MinInfluenceCount {
 | |
| 		reasons = append(reasons, fmt.Sprintf("influence_count: %d >= %d", len(node.Influences), filter.MinInfluenceCount))
 | |
| 	}
 | |
| 	
 | |
| 	return reasons
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) determineRelationship(candidate *hopCandidate, startNode *TemporalNode) string {
 | |
| 	if len(candidate.path) == 0 {
 | |
| 		return "self"
 | |
| 	}
 | |
| 	
 | |
| 	// Look at the last step in the path
 | |
| 	lastStep := candidate.path[len(candidate.path)-1]
 | |
| 	return lastStep.Relationship
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) buildMetadata(candidate *hopCandidate, includeDetailed bool) map[string]interface{} {
 | |
| 	metadata := make(map[string]interface{})
 | |
| 	
 | |
| 	metadata["hop_distance"] = candidate.distance
 | |
| 	metadata["path_length"] = len(candidate.path)
 | |
| 	metadata["node_id"] = candidate.node.ID
 | |
| 	metadata["decision_id"] = candidate.node.DecisionID
 | |
| 	
 | |
| 	if includeDetailed {
 | |
| 		metadata["timestamp"] = candidate.node.Timestamp
 | |
| 		metadata["change_reason"] = candidate.node.ChangeReason
 | |
| 		metadata["impact_scope"] = candidate.node.ImpactScope
 | |
| 		metadata["confidence"] = candidate.node.Confidence
 | |
| 		metadata["staleness"] = candidate.node.Staleness
 | |
| 		metadata["influence_count"] = len(candidate.node.Influences)
 | |
| 		metadata["influenced_by_count"] = len(candidate.node.InfluencedBy)
 | |
| 		
 | |
| 		if candidate.node.Context != nil {
 | |
| 			metadata["context_summary"] = candidate.node.Context.Summary
 | |
| 			metadata["technologies"] = candidate.node.Context.Technologies
 | |
| 			metadata["tags"] = candidate.node.Context.Tags
 | |
| 		}
 | |
| 		
 | |
| 		if decision, exists := qs.graph.decisions[candidate.node.DecisionID]; exists {
 | |
| 			metadata["decision_maker"] = decision.Maker
 | |
| 			metadata["decision_rationale"] = decision.Rationale
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	return metadata
 | |
| }
 | |
| 
 | |
| // Query validation and caching
 | |
| 
 | |
| func (qs *querySystemImpl) validateQuery(query *HopQuery) error {
 | |
| 	if err := query.StartAddress.Validate(); err != nil {
 | |
| 		return fmt.Errorf("invalid start address: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	if query.MaxHops < 1 || query.MaxHops > 20 {
 | |
| 		return fmt.Errorf("max hops must be between 1 and 20")
 | |
| 	}
 | |
| 	
 | |
| 	if query.Direction != "" && query.Direction != "forward" && query.Direction != "backward" && query.Direction != "both" {
 | |
| 		return fmt.Errorf("direction must be 'forward', 'backward', or 'both'")
 | |
| 	}
 | |
| 	
 | |
| 	if query.Limit < 0 {
 | |
| 		return fmt.Errorf("limit cannot be negative")
 | |
| 	}
 | |
| 	
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) generateCacheKey(query *HopQuery) string {
 | |
| 	return fmt.Sprintf("hop_query_%s_%d_%s_%v", 
 | |
| 		query.StartAddress.String(), 
 | |
| 		query.MaxHops, 
 | |
| 		query.Direction,
 | |
| 		query.FilterCriteria != nil)
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) getFromCache(key string) (interface{}, bool) {
 | |
| 	qs.mu.RLock()
 | |
| 	defer qs.mu.RUnlock()
 | |
| 	
 | |
| 	value, exists := qs.queryCache[key]
 | |
| 	return value, exists
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) setCache(key string, value interface{}) {
 | |
| 	qs.mu.Lock()
 | |
| 	defer qs.mu.Unlock()
 | |
| 	
 | |
| 	// Clean cache if needed
 | |
| 	if time.Since(qs.lastCacheClean) > qs.cacheTimeout {
 | |
| 		qs.queryCache = make(map[string]interface{})
 | |
| 		qs.lastCacheClean = time.Now()
 | |
| 	}
 | |
| 	
 | |
| 	qs.queryCache[key] = value
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) updateQueryStats(queryType string, duration time.Duration, cacheHit bool) {
 | |
| 	qs.mu.Lock()
 | |
| 	defer qs.mu.Unlock()
 | |
| 	
 | |
| 	stats, exists := qs.queryStats[queryType]
 | |
| 	if !exists {
 | |
| 		stats = &QueryStatistics{QueryType: queryType}
 | |
| 		qs.queryStats[queryType] = stats
 | |
| 	}
 | |
| 	
 | |
| 	stats.TotalQueries++
 | |
| 	stats.LastQuery = time.Now()
 | |
| 	
 | |
| 	// Update average time
 | |
| 	if stats.AverageTime == 0 {
 | |
| 		stats.AverageTime = duration
 | |
| 	} else {
 | |
| 		stats.AverageTime = (stats.AverageTime + duration) / 2
 | |
| 	}
 | |
| 	
 | |
| 	if cacheHit {
 | |
| 		stats.CacheHits++
 | |
| 	} else {
 | |
| 		stats.CacheMisses++
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Additional analysis methods would continue...
 | |
| // This includes genealogy analysis, similarity matching, clustering, etc.
 | |
| // The implementation is getting quite long, so I'll include key supporting types:
 | |
| 
 | |
| // DecisionGenealogy represents the genealogy of decisions for a context
 | |
| type DecisionGenealogy struct {
 | |
| 	Address              ucxl.Address             `json:"address"`
 | |
| 	DirectAncestors      []ucxl.Address           `json:"direct_ancestors"`
 | |
| 	AllAncestors         []ucxl.Address           `json:"all_ancestors"`
 | |
| 	DirectDescendants    []ucxl.Address           `json:"direct_descendants"`
 | |
| 	AllDescendants       []ucxl.Address           `json:"all_descendants"`
 | |
| 	InfluentialAncestors []*InfluentialAncestor   `json:"influential_ancestors"`
 | |
| 	GenealogyDepth       int                      `json:"genealogy_depth"`
 | |
| 	BranchingFactor      float64                  `json:"branching_factor"`
 | |
| 	DecisionTimeline     *DecisionTimeline        `json:"decision_timeline"`
 | |
| 	Metrics              *GenealogyMetrics        `json:"metrics"`
 | |
| 	AnalyzedAt           time.Time                `json:"analyzed_at"`
 | |
| }
 | |
| 
 | |
| // Additional supporting types for genealogy and similarity analysis...
 | |
| type InfluentialAncestor struct {
 | |
| 	Address       ucxl.Address  `json:"address"`
 | |
| 	InfluenceScore float64      `json:"influence_score"`
 | |
| 	GenerationsBack int         `json:"generations_back"`
 | |
| 	InfluenceType string        `json:"influence_type"`
 | |
| }
 | |
| 
 | |
| type GenealogyMetrics struct {
 | |
| 	TotalAncestors    int     `json:"total_ancestors"`
 | |
| 	TotalDescendants  int     `json:"total_descendants"`
 | |
| 	MaxDepth          int     `json:"max_depth"`
 | |
| 	AverageBranching  float64 `json:"average_branching"`
 | |
| 	InfluenceSpread   float64 `json:"influence_spread"`
 | |
| }
 | |
| 
 | |
| type SimilarDecisionMatch struct {
 | |
| 	Address           ucxl.Address   `json:"address"`
 | |
| 	TemporalNode      *TemporalNode  `json:"temporal_node"`
 | |
| 	SimilarityScore   float64        `json:"similarity_score"`
 | |
| 	SimilarityReasons []string       `json:"similarity_reasons"`
 | |
| 	PatternType       string         `json:"pattern_type"`
 | |
| 	Confidence        float64        `json:"confidence"`
 | |
| }
 | |
| 
 | |
| // Placeholder implementations for the analysis methods
 | |
| func (qs *querySystemImpl) analyzeAncestry(history []*TemporalNode) *ancestryAnalysis {
 | |
| 	// Implementation would trace back through parent nodes
 | |
| 	return &ancestryAnalysis{}
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) analyzeDescendants(address ucxl.Address, maxDepth int) *descendantAnalysis {
 | |
| 	// Implementation would trace forward through influenced nodes
 | |
| 	return &descendantAnalysis{}
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) findInfluentialAncestors(history []*TemporalNode) []*InfluentialAncestor {
 | |
| 	// Implementation would identify most influential historical decisions
 | |
| 	return make([]*InfluentialAncestor, 0)
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) calculateGenealogyMetrics(history []*TemporalNode, descendants *descendantAnalysis) *GenealogyMetrics {
 | |
| 	// Implementation would calculate genealogy statistics
 | |
| 	return &GenealogyMetrics{}
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) calculateDecisionSimilarity(node1, node2 *TemporalNode) float64 {
 | |
| 	// Implementation would compare decision patterns, technologies, etc.
 | |
| 	return 0.0
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) getSimilarityReasons(node1, node2 *TemporalNode) []string {
 | |
| 	// Implementation would identify why decisions are similar
 | |
| 	return make([]string, 0)
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) identifyPatternType(node1, node2 *TemporalNode) string {
 | |
| 	// Implementation would classify the type of similarity pattern
 | |
| 	return "unknown"
 | |
| }
 | |
| 
 | |
| func (qs *querySystemImpl) convertCommunityToCluster(community Community) *DecisionCluster {
 | |
| 	// Implementation would convert community to decision cluster
 | |
| 	return &DecisionCluster{
 | |
| 		ID: community.ID,
 | |
| 		Decisions: community.Nodes,
 | |
| 		ClusterSize: len(community.Nodes),
 | |
| 		Cohesion: community.Modularity,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Supporting analysis types
 | |
| type ancestryAnalysis struct {
 | |
| 	DirectAncestors []ucxl.Address
 | |
| 	AllAncestors    []ucxl.Address
 | |
| 	MaxDepth        int
 | |
| }
 | |
| 
 | |
| type descendantAnalysis struct {
 | |
| 	DirectDescendants []ucxl.Address
 | |
| 	AllDescendants    []ucxl.Address
 | |
| 	BranchingFactor   float64
 | |
| } |