chore: align slurp config and scaffolding

This commit is contained in:
anthonyrawlins
2025-09-27 21:03:12 +10:00
parent acc4361463
commit 4a77862289
47 changed files with 5133 additions and 4274 deletions

View File

@@ -9,36 +9,36 @@ import (
"sync"
"time"
"chorus/pkg/ucxl"
slurpContext "chorus/pkg/slurp/context"
"chorus/pkg/slurp/storage"
"chorus/pkg/ucxl"
)
// temporalGraphImpl implements the TemporalGraph interface
type temporalGraphImpl struct {
mu sync.RWMutex
// Core storage
storage storage.ContextStore
// In-memory graph structures for fast access
nodes map[string]*TemporalNode // nodeID -> TemporalNode
addressToNodes map[string][]*TemporalNode // address -> list of temporal nodes
influences map[string][]string // nodeID -> list of influenced nodeIDs
influencedBy map[string][]string // nodeID -> list of influencer nodeIDs
nodes map[string]*TemporalNode // nodeID -> TemporalNode
addressToNodes map[string][]*TemporalNode // address -> list of temporal nodes
influences map[string][]string // nodeID -> list of influenced nodeIDs
influencedBy map[string][]string // nodeID -> list of influencer nodeIDs
// Decision tracking
decisions map[string]*DecisionMetadata // decisionID -> DecisionMetadata
decisionToNodes map[string][]*TemporalNode // decisionID -> list of affected nodes
decisions map[string]*DecisionMetadata // decisionID -> DecisionMetadata
decisionToNodes map[string][]*TemporalNode // decisionID -> list of affected nodes
// Performance optimization
pathCache map[string][]*DecisionStep // cache for decision paths
metricsCache map[string]interface{} // cache for expensive metrics
cacheTimeout time.Duration
lastCacheClean time.Time
pathCache map[string][]*DecisionStep // cache for decision paths
metricsCache map[string]interface{} // cache for expensive metrics
cacheTimeout time.Duration
lastCacheClean time.Time
// Configuration
maxDepth int // Maximum depth for path finding
maxDepth int // Maximum depth for path finding
stalenessWeight *StalenessWeights
}
@@ -69,113 +69,113 @@ func NewTemporalGraph(storage storage.ContextStore) TemporalGraph {
}
// CreateInitialContext creates the first temporal version of context
func (tg *temporalGraphImpl) CreateInitialContext(ctx context.Context, address ucxl.Address,
func (tg *temporalGraphImpl) CreateInitialContext(ctx context.Context, address ucxl.Address,
contextData *slurpContext.ContextNode, creator string) (*TemporalNode, error) {
tg.mu.Lock()
defer tg.mu.Unlock()
// Generate node ID
nodeID := tg.generateNodeID(address, 1)
// Create temporal node
temporalNode := &TemporalNode{
ID: nodeID,
UCXLAddress: address,
Version: 1,
Context: contextData,
Timestamp: time.Now(),
DecisionID: fmt.Sprintf("initial-%s", creator),
ChangeReason: ReasonInitialCreation,
ParentNode: nil,
ContextHash: tg.calculateContextHash(contextData),
Confidence: contextData.RAGConfidence,
Staleness: 0.0,
Influences: make([]ucxl.Address, 0),
InfluencedBy: make([]ucxl.Address, 0),
ValidatedBy: []string{creator},
ID: nodeID,
UCXLAddress: address,
Version: 1,
Context: contextData,
Timestamp: time.Now(),
DecisionID: fmt.Sprintf("initial-%s", creator),
ChangeReason: ReasonInitialCreation,
ParentNode: nil,
ContextHash: tg.calculateContextHash(contextData),
Confidence: contextData.RAGConfidence,
Staleness: 0.0,
Influences: make([]ucxl.Address, 0),
InfluencedBy: make([]ucxl.Address, 0),
ValidatedBy: []string{creator},
LastValidated: time.Now(),
ImpactScope: ImpactLocal,
PropagatedTo: make([]ucxl.Address, 0),
Metadata: make(map[string]interface{}),
ImpactScope: ImpactLocal,
PropagatedTo: make([]ucxl.Address, 0),
Metadata: make(map[string]interface{}),
}
// Store in memory structures
tg.nodes[nodeID] = temporalNode
addressKey := address.String()
tg.addressToNodes[addressKey] = []*TemporalNode{temporalNode}
// Initialize influence maps
tg.influences[nodeID] = make([]string, 0)
tg.influencedBy[nodeID] = make([]string, 0)
// Store decision metadata
decisionMeta := &DecisionMetadata{
ID: temporalNode.DecisionID,
Maker: creator,
Rationale: "Initial context creation",
Scope: ImpactLocal,
ConfidenceLevel: contextData.RAGConfidence,
ExternalRefs: make([]string, 0),
CreatedAt: time.Now(),
ID: temporalNode.DecisionID,
Maker: creator,
Rationale: "Initial context creation",
Scope: ImpactLocal,
ConfidenceLevel: contextData.RAGConfidence,
ExternalRefs: make([]string, 0),
CreatedAt: time.Now(),
ImplementationStatus: "complete",
Metadata: make(map[string]interface{}),
Metadata: make(map[string]interface{}),
}
tg.decisions[temporalNode.DecisionID] = decisionMeta
tg.decisionToNodes[temporalNode.DecisionID] = []*TemporalNode{temporalNode}
// Persist to storage
if err := tg.persistTemporalNode(ctx, temporalNode); err != nil {
return nil, fmt.Errorf("failed to persist initial temporal node: %w", err)
}
return temporalNode, nil
}
// EvolveContext creates a new temporal version due to a decision
func (tg *temporalGraphImpl) EvolveContext(ctx context.Context, address ucxl.Address,
newContext *slurpContext.ContextNode, reason ChangeReason,
func (tg *temporalGraphImpl) EvolveContext(ctx context.Context, address ucxl.Address,
newContext *slurpContext.ContextNode, reason ChangeReason,
decision *DecisionMetadata) (*TemporalNode, error) {
tg.mu.Lock()
defer tg.mu.Unlock()
// Get latest version
addressKey := address.String()
nodes, exists := tg.addressToNodes[addressKey]
if !exists || len(nodes) == 0 {
return nil, fmt.Errorf("no existing context found for address %s", address.String())
}
// Find latest version
latestNode := nodes[len(nodes)-1]
newVersion := latestNode.Version + 1
// Generate new node ID
nodeID := tg.generateNodeID(address, newVersion)
// Create new temporal node
temporalNode := &TemporalNode{
ID: nodeID,
UCXLAddress: address,
Version: newVersion,
Context: newContext,
Timestamp: time.Now(),
DecisionID: decision.ID,
ChangeReason: reason,
ParentNode: &latestNode.ID,
ContextHash: tg.calculateContextHash(newContext),
Confidence: newContext.RAGConfidence,
Staleness: 0.0, // New version, not stale
Influences: make([]ucxl.Address, 0),
InfluencedBy: make([]ucxl.Address, 0),
ValidatedBy: []string{decision.Maker},
ID: nodeID,
UCXLAddress: address,
Version: newVersion,
Context: newContext,
Timestamp: time.Now(),
DecisionID: decision.ID,
ChangeReason: reason,
ParentNode: &latestNode.ID,
ContextHash: tg.calculateContextHash(newContext),
Confidence: newContext.RAGConfidence,
Staleness: 0.0, // New version, not stale
Influences: make([]ucxl.Address, 0),
InfluencedBy: make([]ucxl.Address, 0),
ValidatedBy: []string{decision.Maker},
LastValidated: time.Now(),
ImpactScope: decision.Scope,
PropagatedTo: make([]ucxl.Address, 0),
Metadata: make(map[string]interface{}),
ImpactScope: decision.Scope,
PropagatedTo: make([]ucxl.Address, 0),
Metadata: make(map[string]interface{}),
}
// Copy influence relationships from parent
if latestNodeInfluences, exists := tg.influences[latestNode.ID]; exists {
tg.influences[nodeID] = make([]string, len(latestNodeInfluences))
@@ -183,18 +183,18 @@ func (tg *temporalGraphImpl) EvolveContext(ctx context.Context, address ucxl.Add
} else {
tg.influences[nodeID] = make([]string, 0)
}
if latestNodeInfluencedBy, exists := tg.influencedBy[latestNode.ID]; exists {
tg.influencedBy[nodeID] = make([]string, len(latestNodeInfluencedBy))
copy(tg.influencedBy[nodeID], latestNodeInfluencedBy)
} else {
tg.influencedBy[nodeID] = make([]string, 0)
}
// Store in memory structures
tg.nodes[nodeID] = temporalNode
tg.addressToNodes[addressKey] = append(tg.addressToNodes[addressKey], temporalNode)
// Store decision metadata
tg.decisions[decision.ID] = decision
if existing, exists := tg.decisionToNodes[decision.ID]; exists {
@@ -202,18 +202,18 @@ func (tg *temporalGraphImpl) EvolveContext(ctx context.Context, address ucxl.Add
} else {
tg.decisionToNodes[decision.ID] = []*TemporalNode{temporalNode}
}
// Update staleness for related contexts
tg.updateStalenessAfterChange(temporalNode)
// Clear relevant caches
tg.clearCacheForAddress(address)
// Persist to storage
if err := tg.persistTemporalNode(ctx, temporalNode); err != nil {
return nil, fmt.Errorf("failed to persist evolved temporal node: %w", err)
}
return temporalNode, nil
}
@@ -221,38 +221,38 @@ func (tg *temporalGraphImpl) EvolveContext(ctx context.Context, address ucxl.Add
func (tg *temporalGraphImpl) GetLatestVersion(ctx context.Context, address ucxl.Address) (*TemporalNode, error) {
tg.mu.RLock()
defer tg.mu.RUnlock()
addressKey := address.String()
nodes, exists := tg.addressToNodes[addressKey]
if !exists || len(nodes) == 0 {
return nil, fmt.Errorf("no temporal nodes found for address %s", address.String())
}
// Return the latest version (last in slice)
return nodes[len(nodes)-1], nil
}
// GetVersionAtDecision gets context as it was at a specific decision hop
func (tg *temporalGraphImpl) GetVersionAtDecision(ctx context.Context, address ucxl.Address,
func (tg *temporalGraphImpl) GetVersionAtDecision(ctx context.Context, address ucxl.Address,
decisionHop int) (*TemporalNode, error) {
tg.mu.RLock()
defer tg.mu.RUnlock()
addressKey := address.String()
nodes, exists := tg.addressToNodes[addressKey]
if !exists || len(nodes) == 0 {
return nil, fmt.Errorf("no temporal nodes found for address %s", address.String())
}
// Find node at specific decision hop (version)
for _, node := range nodes {
if node.Version == decisionHop {
return node, nil
}
}
return nil, fmt.Errorf("no temporal node found at decision hop %d for address %s",
return nil, fmt.Errorf("no temporal node found at decision hop %d for address %s",
decisionHop, address.String())
}
@@ -260,20 +260,20 @@ func (tg *temporalGraphImpl) GetVersionAtDecision(ctx context.Context, address u
func (tg *temporalGraphImpl) GetEvolutionHistory(ctx context.Context, address ucxl.Address) ([]*TemporalNode, error) {
tg.mu.RLock()
defer tg.mu.RUnlock()
addressKey := address.String()
nodes, exists := tg.addressToNodes[addressKey]
if !exists || len(nodes) == 0 {
return []*TemporalNode{}, nil
}
// Sort by version to ensure proper order
sortedNodes := make([]*TemporalNode, len(nodes))
copy(sortedNodes, nodes)
sort.Slice(sortedNodes, func(i, j int) bool {
return sortedNodes[i].Version < sortedNodes[j].Version
})
return sortedNodes, nil
}
@@ -281,22 +281,22 @@ func (tg *temporalGraphImpl) GetEvolutionHistory(ctx context.Context, address uc
func (tg *temporalGraphImpl) AddInfluenceRelationship(ctx context.Context, influencer, influenced ucxl.Address) error {
tg.mu.Lock()
defer tg.mu.Unlock()
// Get latest nodes for both addresses
influencerNode, err := tg.getLatestNodeUnsafe(influencer)
if err != nil {
return fmt.Errorf("influencer node not found: %w", err)
}
influencedNode, err := tg.getLatestNodeUnsafe(influenced)
if err != nil {
return fmt.Errorf("influenced node not found: %w", err)
}
// Add to influence mappings
influencerNodeID := influencerNode.ID
influencedNodeID := influencedNode.ID
// Add to influences map (influencer -> influenced)
if influences, exists := tg.influences[influencerNodeID]; exists {
// Check if relationship already exists
@@ -309,7 +309,7 @@ func (tg *temporalGraphImpl) AddInfluenceRelationship(ctx context.Context, influ
} else {
tg.influences[influencerNodeID] = []string{influencedNodeID}
}
// Add to influencedBy map (influenced <- influencer)
if influencedBy, exists := tg.influencedBy[influencedNodeID]; exists {
// Check if relationship already exists
@@ -322,14 +322,14 @@ func (tg *temporalGraphImpl) AddInfluenceRelationship(ctx context.Context, influ
} else {
tg.influencedBy[influencedNodeID] = []string{influencerNodeID}
}
// Update temporal nodes with the influence relationship
influencerNode.Influences = append(influencerNode.Influences, influenced)
influencedNode.InfluencedBy = append(influencedNode.InfluencedBy, influencer)
// Clear path cache as influence graph has changed
tg.pathCache = make(map[string][]*DecisionStep)
// Persist changes
if err := tg.persistTemporalNode(ctx, influencerNode); err != nil {
return fmt.Errorf("failed to persist influencer node: %w", err)
@@ -337,7 +337,7 @@ func (tg *temporalGraphImpl) AddInfluenceRelationship(ctx context.Context, influ
if err := tg.persistTemporalNode(ctx, influencedNode); err != nil {
return fmt.Errorf("failed to persist influenced node: %w", err)
}
return nil
}
@@ -345,39 +345,39 @@ func (tg *temporalGraphImpl) AddInfluenceRelationship(ctx context.Context, influ
func (tg *temporalGraphImpl) RemoveInfluenceRelationship(ctx context.Context, influencer, influenced ucxl.Address) error {
tg.mu.Lock()
defer tg.mu.Unlock()
// Get latest nodes for both addresses
influencerNode, err := tg.getLatestNodeUnsafe(influencer)
if err != nil {
return fmt.Errorf("influencer node not found: %w", err)
}
influencedNode, err := tg.getLatestNodeUnsafe(influenced)
if err != nil {
return fmt.Errorf("influenced node not found: %w", err)
}
// Remove from influence mappings
influencerNodeID := influencerNode.ID
influencedNodeID := influencedNode.ID
// Remove from influences map
if influences, exists := tg.influences[influencerNodeID]; exists {
tg.influences[influencerNodeID] = tg.removeFromSlice(influences, influencedNodeID)
}
// Remove from influencedBy map
if influencedBy, exists := tg.influencedBy[influencedNodeID]; exists {
tg.influencedBy[influencedNodeID] = tg.removeFromSlice(influencedBy, influencerNodeID)
}
// Update temporal nodes
influencerNode.Influences = tg.removeAddressFromSlice(influencerNode.Influences, influenced)
influencedNode.InfluencedBy = tg.removeAddressFromSlice(influencedNode.InfluencedBy, influencer)
// Clear path cache
tg.pathCache = make(map[string][]*DecisionStep)
// Persist changes
if err := tg.persistTemporalNode(ctx, influencerNode); err != nil {
return fmt.Errorf("failed to persist influencer node: %w", err)
@@ -385,7 +385,7 @@ func (tg *temporalGraphImpl) RemoveInfluenceRelationship(ctx context.Context, in
if err := tg.persistTemporalNode(ctx, influencedNode); err != nil {
return fmt.Errorf("failed to persist influenced node: %w", err)
}
return nil
}
@@ -393,28 +393,28 @@ func (tg *temporalGraphImpl) RemoveInfluenceRelationship(ctx context.Context, in
func (tg *temporalGraphImpl) GetInfluenceRelationships(ctx context.Context, address ucxl.Address) ([]ucxl.Address, []ucxl.Address, error) {
tg.mu.RLock()
defer tg.mu.RUnlock()
node, err := tg.getLatestNodeUnsafe(address)
if err != nil {
return nil, nil, fmt.Errorf("node not found: %w", err)
}
influences := make([]ucxl.Address, len(node.Influences))
copy(influences, node.Influences)
influencedBy := make([]ucxl.Address, len(node.InfluencedBy))
copy(influencedBy, node.InfluencedBy)
return influences, influencedBy, nil
}
// FindRelatedDecisions finds decisions within N decision hops
func (tg *temporalGraphImpl) FindRelatedDecisions(ctx context.Context, address ucxl.Address,
func (tg *temporalGraphImpl) FindRelatedDecisions(ctx context.Context, address ucxl.Address,
maxHops int) ([]*DecisionPath, error) {
tg.mu.RLock()
defer tg.mu.RUnlock()
// Check cache first
cacheKey := fmt.Sprintf("related-%s-%d", address.String(), maxHops)
if cached, exists := tg.pathCache[cacheKey]; exists {
@@ -430,27 +430,27 @@ func (tg *temporalGraphImpl) FindRelatedDecisions(ctx context.Context, address u
}
return paths, nil
}
startNode, err := tg.getLatestNodeUnsafe(address)
if err != nil {
return nil, fmt.Errorf("start node not found: %w", err)
}
// Use BFS to find all nodes within maxHops
visited := make(map[string]bool)
queue := []*bfsItem{{node: startNode, distance: 0, path: []*DecisionStep{}}}
relatedPaths := make([]*DecisionPath, 0)
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
nodeID := current.node.ID
if visited[nodeID] || current.distance > maxHops {
continue
}
visited[nodeID] = true
// If this is not the starting node, add it to results
if current.distance > 0 {
step := &DecisionStep{
@@ -459,7 +459,7 @@ func (tg *temporalGraphImpl) FindRelatedDecisions(ctx context.Context, address u
HopDistance: current.distance,
Relationship: "influence",
}
path := &DecisionPath{
From: address,
To: current.node.UCXLAddress,
@@ -469,7 +469,7 @@ func (tg *temporalGraphImpl) FindRelatedDecisions(ctx context.Context, address u
}
relatedPaths = append(relatedPaths, path)
}
// Add influenced nodes to queue
if influences, exists := tg.influences[nodeID]; exists {
for _, influencedID := range influences {
@@ -491,7 +491,7 @@ func (tg *temporalGraphImpl) FindRelatedDecisions(ctx context.Context, address u
}
}
}
// Add influencer nodes to queue
if influencedBy, exists := tg.influencedBy[nodeID]; exists {
for _, influencerID := range influencedBy {
@@ -514,7 +514,7 @@ func (tg *temporalGraphImpl) FindRelatedDecisions(ctx context.Context, address u
}
}
}
return relatedPaths, nil
}
@@ -522,44 +522,44 @@ func (tg *temporalGraphImpl) FindRelatedDecisions(ctx context.Context, address u
func (tg *temporalGraphImpl) FindDecisionPath(ctx context.Context, from, to ucxl.Address) ([]*DecisionStep, error) {
tg.mu.RLock()
defer tg.mu.RUnlock()
// Check cache first
cacheKey := fmt.Sprintf("path-%s-%s", from.String(), to.String())
if cached, exists := tg.pathCache[cacheKey]; exists {
return cached, nil
}
fromNode, err := tg.getLatestNodeUnsafe(from)
if err != nil {
return nil, fmt.Errorf("from node not found: %w", err)
}
toNode, err := tg.getLatestNodeUnsafe(to)
_, err := tg.getLatestNodeUnsafe(to)
if err != nil {
return nil, fmt.Errorf("to node not found: %w", err)
}
// Use BFS to find shortest path
visited := make(map[string]bool)
queue := []*pathItem{{node: fromNode, path: []*DecisionStep{}}}
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
nodeID := current.node.ID
if visited[nodeID] {
continue
}
visited[nodeID] = true
// Check if we reached the target
if current.node.UCXLAddress.String() == to.String() {
// Cache the result
tg.pathCache[cacheKey] = current.path
return current.path, nil
}
// Explore influenced nodes
if influences, exists := tg.influences[nodeID]; exists {
for _, influencedID := range influences {
@@ -580,7 +580,7 @@ func (tg *temporalGraphImpl) FindDecisionPath(ctx context.Context, from, to ucxl
}
}
}
// Explore influencer nodes
if influencedBy, exists := tg.influencedBy[nodeID]; exists {
for _, influencerID := range influencedBy {
@@ -602,7 +602,7 @@ func (tg *temporalGraphImpl) FindDecisionPath(ctx context.Context, from, to ucxl
}
}
}
return nil, fmt.Errorf("no path found from %s to %s", from.String(), to.String())
}
@@ -610,7 +610,7 @@ func (tg *temporalGraphImpl) FindDecisionPath(ctx context.Context, from, to ucxl
func (tg *temporalGraphImpl) AnalyzeDecisionPatterns(ctx context.Context) (*DecisionAnalysis, error) {
tg.mu.RLock()
defer tg.mu.RUnlock()
analysis := &DecisionAnalysis{
TimeRange: 24 * time.Hour, // Analyze last 24 hours by default
TotalDecisions: len(tg.decisions),
@@ -620,10 +620,10 @@ func (tg *temporalGraphImpl) AnalyzeDecisionPatterns(ctx context.Context) (*Deci
MostInfluentialDecisions: make([]*InfluentialDecision, 0),
DecisionClusters: make([]*DecisionCluster, 0),
Patterns: make([]*DecisionPattern, 0),
Anomalies: make([]*AnomalousDecision, 0),
AnalyzedAt: time.Now(),
Anomalies: make([]*AnomalousDecision, 0),
AnalyzedAt: time.Now(),
}
// Calculate decision velocity
cutoff := time.Now().Add(-analysis.TimeRange)
recentDecisions := 0
@@ -633,7 +633,7 @@ func (tg *temporalGraphImpl) AnalyzeDecisionPatterns(ctx context.Context) (*Deci
}
}
analysis.DecisionVelocity = float64(recentDecisions) / analysis.TimeRange.Hours()
// Calculate average influence distance
totalDistance := 0.0
connections := 0
@@ -648,37 +648,37 @@ func (tg *temporalGraphImpl) AnalyzeDecisionPatterns(ctx context.Context) (*Deci
if connections > 0 {
analysis.AverageInfluenceDistance = totalDistance / float64(connections)
}
// Find most influential decisions (simplified)
influenceScores := make(map[string]float64)
for nodeID, node := range tg.nodes {
score := float64(len(tg.influences[nodeID])) * 1.0 // Direct influences
score := float64(len(tg.influences[nodeID])) * 1.0 // Direct influences
score += float64(len(tg.influencedBy[nodeID])) * 0.5 // Being influenced
influenceScores[nodeID] = score
if score > 3.0 { // Threshold for "influential"
influential := &InfluentialDecision{
Address: node.UCXLAddress,
DecisionHop: node.Version,
InfluenceScore: score,
AffectedContexts: node.Influences,
DecisionMetadata: tg.decisions[node.DecisionID],
InfluenceReasons: []string{"high_connectivity", "multiple_influences"},
Address: node.UCXLAddress,
DecisionHop: node.Version,
InfluenceScore: score,
AffectedContexts: node.Influences,
DecisionMetadata: tg.decisions[node.DecisionID],
InfluenceReasons: []string{"high_connectivity", "multiple_influences"},
}
analysis.MostInfluentialDecisions = append(analysis.MostInfluentialDecisions, influential)
}
}
// Sort influential decisions by score
sort.Slice(analysis.MostInfluentialDecisions, func(i, j int) bool {
return analysis.MostInfluentialDecisions[i].InfluenceScore > analysis.MostInfluentialDecisions[j].InfluenceScore
})
// Limit to top 10
if len(analysis.MostInfluentialDecisions) > 10 {
analysis.MostInfluentialDecisions = analysis.MostInfluentialDecisions[:10]
}
return analysis, nil
}
@@ -686,19 +686,19 @@ func (tg *temporalGraphImpl) AnalyzeDecisionPatterns(ctx context.Context) (*Deci
func (tg *temporalGraphImpl) ValidateTemporalIntegrity(ctx context.Context) error {
tg.mu.RLock()
defer tg.mu.RUnlock()
errors := make([]string, 0)
// Check for orphaned nodes
for nodeID, node := range tg.nodes {
if node.ParentNode != nil {
if _, exists := tg.nodes[*node.ParentNode]; !exists {
errors = append(errors, fmt.Sprintf("orphaned node %s has non-existent parent %s",
errors = append(errors, fmt.Sprintf("orphaned node %s has non-existent parent %s",
nodeID, *node.ParentNode))
}
}
}
// Check influence consistency
for nodeID := range tg.influences {
if influences, exists := tg.influences[nodeID]; exists {
@@ -713,33 +713,33 @@ func (tg *temporalGraphImpl) ValidateTemporalIntegrity(ctx context.Context) erro
}
}
if !found {
errors = append(errors, fmt.Sprintf("influence inconsistency: %s -> %s not reflected in influencedBy",
errors = append(errors, fmt.Sprintf("influence inconsistency: %s -> %s not reflected in influencedBy",
nodeID, influencedID))
}
}
}
}
}
// Check version sequence integrity
for address, nodes := range tg.addressToNodes {
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].Version < nodes[j].Version
})
for i, node := range nodes {
expectedVersion := i + 1
if node.Version != expectedVersion {
errors = append(errors, fmt.Sprintf("version sequence error for address %s: expected %d, got %d",
errors = append(errors, fmt.Sprintf("version sequence error for address %s: expected %d, got %d",
address, expectedVersion, node.Version))
}
}
}
if len(errors) > 0 {
return fmt.Errorf("temporal integrity violations: %v", errors)
}
return nil
}
@@ -747,21 +747,21 @@ func (tg *temporalGraphImpl) ValidateTemporalIntegrity(ctx context.Context) erro
func (tg *temporalGraphImpl) CompactHistory(ctx context.Context, beforeTime time.Time) error {
tg.mu.Lock()
defer tg.mu.Unlock()
compacted := 0
// For each address, keep only the latest version and major milestones before the cutoff
for address, nodes := range tg.addressToNodes {
toKeep := make([]*TemporalNode, 0)
toRemove := make([]*TemporalNode, 0)
for _, node := range nodes {
// Always keep nodes after the cutoff time
if node.Timestamp.After(beforeTime) {
toKeep = append(toKeep, node)
continue
}
// Keep major changes and influential decisions
if tg.isMajorChange(node) || tg.isInfluentialDecision(node) {
toKeep = append(toKeep, node)
@@ -769,10 +769,10 @@ func (tg *temporalGraphImpl) CompactHistory(ctx context.Context, beforeTime time
toRemove = append(toRemove, node)
}
}
// Update the address mapping
tg.addressToNodes[address] = toKeep
// Remove old nodes from main maps
for _, node := range toRemove {
delete(tg.nodes, node.ID)
@@ -781,11 +781,11 @@ func (tg *temporalGraphImpl) CompactHistory(ctx context.Context, beforeTime time
compacted++
}
}
// Clear caches after compaction
tg.pathCache = make(map[string][]*DecisionStep)
tg.metricsCache = make(map[string]interface{})
return nil
}
@@ -847,13 +847,13 @@ func (tg *temporalGraphImpl) calculateStaleness(node *TemporalNode, changedNode
// Simple staleness calculation based on time since last update and influence strength
timeSinceUpdate := time.Since(node.Timestamp)
timeWeight := math.Min(timeSinceUpdate.Hours()/168.0, 1.0) // Max staleness from time: 1 week
// Influence weight based on connection strength
influenceWeight := 0.0
if len(node.InfluencedBy) > 0 {
influenceWeight = 1.0 / float64(len(node.InfluencedBy)) // Stronger if fewer influencers
}
// Impact scope weight
impactWeight := 0.0
switch changedNode.ImpactScope {
@@ -866,23 +866,23 @@ func (tg *temporalGraphImpl) calculateStaleness(node *TemporalNode, changedNode
case ImpactLocal:
impactWeight = 0.4
}
return math.Min(
tg.stalenessWeight.TimeWeight*timeWeight+
tg.stalenessWeight.InfluenceWeight*influenceWeight+
tg.stalenessWeight.ImportanceWeight*impactWeight, 1.0)
tg.stalenessWeight.InfluenceWeight*influenceWeight+
tg.stalenessWeight.ImportanceWeight*impactWeight, 1.0)
}
func (tg *temporalGraphImpl) clearCacheForAddress(address ucxl.Address) {
addressStr := address.String()
keysToDelete := make([]string, 0)
for key := range tg.pathCache {
if contains(key, addressStr) {
keysToDelete = append(keysToDelete, key)
}
}
for _, key := range keysToDelete {
delete(tg.pathCache, key)
}
@@ -908,7 +908,7 @@ func (tg *temporalGraphImpl) persistTemporalNode(ctx context.Context, node *Temp
}
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr ||
return len(s) >= len(substr) && (s == substr ||
(len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr)))
}
@@ -923,4 +923,4 @@ type bfsItem struct {
type pathItem struct {
node *TemporalNode
path []*DecisionStep
}
}