Complete BZZZ functionality port to CHORUS

🎭 CHORUS now contains full BZZZ functionality adapted for containers

Core systems ported:
- P2P networking (libp2p with DHT and PubSub)
- Task coordination (COOEE protocol)
- HMMM collaborative reasoning
- SHHH encryption and security
- SLURP admin election system
- UCXL content addressing
- UCXI server integration
- Hypercore logging system
- Health monitoring and graceful shutdown
- License validation with KACHING

Container adaptations:
- Environment variable configuration (no YAML files)
- Container-optimized logging to stdout/stderr
- Auto-generated agent IDs for container deployments
- Docker-first architecture

All proven BZZZ P2P protocols, AI integration, and collaboration
features are now available in containerized form.

Next: Build and test container deployment.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-09-02 20:02:37 +10:00
parent 7c6cbd562a
commit 543ab216f9
224 changed files with 86331 additions and 186 deletions

View File

@@ -0,0 +1,569 @@
package temporal
import (
"context"
"fmt"
"sort"
"sync"
"time"
"chorus.services/bzzz/pkg/ucxl"
)
// decisionNavigatorImpl implements the DecisionNavigator interface
type decisionNavigatorImpl struct {
mu sync.RWMutex
// Reference to the temporal graph
graph *temporalGraphImpl
// Navigation state
navigationSessions map[string]*NavigationSession
bookmarks map[string]*DecisionBookmark
// Configuration
maxNavigationHistory int
}
// NavigationSession represents a navigation session
type NavigationSession struct {
ID string `json:"id"`
UserID string `json:"user_id"`
StartedAt time.Time `json:"started_at"`
LastActivity time.Time `json:"last_activity"`
CurrentPosition ucxl.Address `json:"current_position"`
History []*DecisionStep `json:"history"`
Bookmarks []string `json:"bookmarks"`
Preferences *NavPreferences `json:"preferences"`
}
// NavPreferences represents navigation preferences
type NavPreferences struct {
MaxHops int `json:"max_hops"`
PreferRecentDecisions bool `json:"prefer_recent_decisions"`
FilterByConfidence float64 `json:"filter_by_confidence"`
IncludeStaleContexts bool `json:"include_stale_contexts"`
}
// NewDecisionNavigator creates a new decision navigator
func NewDecisionNavigator(graph *temporalGraphImpl) DecisionNavigator {
return &decisionNavigatorImpl{
graph: graph,
navigationSessions: make(map[string]*NavigationSession),
bookmarks: make(map[string]*DecisionBookmark),
maxNavigationHistory: 100,
}
}
// NavigateDecisionHops navigates by decision distance, not time
func (dn *decisionNavigatorImpl) NavigateDecisionHops(ctx context.Context, address ucxl.Address,
hops int, direction NavigationDirection) (*TemporalNode, error) {
dn.mu.RLock()
defer dn.mu.RUnlock()
// Get starting node
startNode, err := dn.graph.getLatestNodeUnsafe(address)
if err != nil {
return nil, fmt.Errorf("failed to get starting node: %w", err)
}
// Navigate by hops
currentNode := startNode
for i := 0; i < hops; i++ {
nextNode, err := dn.navigateOneHop(currentNode, direction)
if err != nil {
return nil, fmt.Errorf("failed to navigate hop %d: %w", i+1, err)
}
currentNode = nextNode
}
return currentNode, nil
}
// GetDecisionTimeline gets timeline ordered by decision sequence
func (dn *decisionNavigatorImpl) GetDecisionTimeline(ctx context.Context, address ucxl.Address,
includeRelated bool, maxHops int) (*DecisionTimeline, error) {
dn.mu.RLock()
defer dn.mu.RUnlock()
// Get evolution history for the primary address
history, err := dn.graph.GetEvolutionHistory(ctx, address)
if err != nil {
return nil, fmt.Errorf("failed to get evolution history: %w", err)
}
// Build decision timeline entries
decisionSequence := make([]*DecisionTimelineEntry, len(history))
for i, node := range history {
entry := &DecisionTimelineEntry{
Version: node.Version,
DecisionHop: node.Version, // Version number as decision hop
ChangeReason: node.ChangeReason,
DecisionMaker: dn.getDecisionMaker(node),
DecisionRationale: dn.getDecisionRationale(node),
ConfidenceEvolution: node.Confidence,
Timestamp: node.Timestamp,
InfluencesCount: len(node.Influences),
InfluencedByCount: len(node.InfluencedBy),
ImpactScope: node.ImpactScope,
Metadata: make(map[string]interface{}),
}
decisionSequence[i] = entry
}
// Get related decisions if requested
relatedDecisions := make([]*RelatedDecision, 0)
if includeRelated && maxHops > 0 {
relatedPaths, err := dn.graph.FindRelatedDecisions(ctx, address, maxHops)
if err == nil {
for _, path := range relatedPaths {
if len(path.Steps) > 0 {
lastStep := path.Steps[len(path.Steps)-1]
related := &RelatedDecision{
Address: path.To,
DecisionHops: path.TotalHops,
LatestVersion: lastStep.TemporalNode.Version,
ChangeReason: lastStep.TemporalNode.ChangeReason,
DecisionMaker: dn.getDecisionMaker(lastStep.TemporalNode),
Confidence: lastStep.TemporalNode.Confidence,
LastDecisionTimestamp: lastStep.TemporalNode.Timestamp,
RelationshipType: lastStep.Relationship,
}
relatedDecisions = append(relatedDecisions, related)
}
}
}
}
// Calculate timeline analysis
analysis := dn.analyzeTimeline(decisionSequence, relatedDecisions)
// Calculate time span
var timeSpan time.Duration
if len(history) > 1 {
timeSpan = history[len(history)-1].Timestamp.Sub(history[0].Timestamp)
}
timeline := &DecisionTimeline{
PrimaryAddress: address,
DecisionSequence: decisionSequence,
RelatedDecisions: relatedDecisions,
TotalDecisions: len(decisionSequence),
TimeSpan: timeSpan,
AnalysisMetadata: analysis,
}
return timeline, nil
}
// FindStaleContexts finds contexts that may be outdated based on decisions
func (dn *decisionNavigatorImpl) FindStaleContexts(ctx context.Context, stalenessThreshold float64) ([]*StaleContext, error) {
dn.mu.RLock()
defer dn.mu.RUnlock()
staleContexts := make([]*StaleContext, 0)
// Check all nodes for staleness
for _, node := range dn.graph.nodes {
if node.Staleness >= stalenessThreshold {
staleness := &StaleContext{
UCXLAddress: node.UCXLAddress,
TemporalNode: node,
StalenessScore: node.Staleness,
LastUpdated: node.Timestamp,
Reasons: dn.getStalenessReasons(node),
SuggestedActions: dn.getSuggestedActions(node),
RelatedChanges: dn.getRelatedChanges(node),
Priority: dn.calculateStalePriority(node),
}
staleContexts = append(staleContexts, staleness)
}
}
// Sort by staleness score (highest first)
sort.Slice(staleContexts, func(i, j int) bool {
return staleContexts[i].StalenessScore > staleContexts[j].StalenessScore
})
return staleContexts, nil
}
// ValidateDecisionPath validates that a decision path is reachable
func (dn *decisionNavigatorImpl) ValidateDecisionPath(ctx context.Context, path []*DecisionStep) error {
if len(path) == 0 {
return fmt.Errorf("empty decision path")
}
dn.mu.RLock()
defer dn.mu.RUnlock()
// Validate each step in the path
for i, step := range path {
// Check if the temporal node exists
if step.TemporalNode == nil {
return fmt.Errorf("step %d has nil temporal node", i)
}
nodeID := step.TemporalNode.ID
if _, exists := dn.graph.nodes[nodeID]; !exists {
return fmt.Errorf("step %d references non-existent node %s", i, nodeID)
}
// Validate hop distance
if step.HopDistance != i {
return fmt.Errorf("step %d has incorrect hop distance: expected %d, got %d",
i, i, step.HopDistance)
}
// Validate relationship to next step
if i < len(path)-1 {
nextStep := path[i+1]
if !dn.validateStepRelationship(step, nextStep) {
return fmt.Errorf("invalid relationship between step %d and %d", i, i+1)
}
}
}
return nil
}
// GetNavigationHistory gets navigation history for a session
func (dn *decisionNavigatorImpl) GetNavigationHistory(ctx context.Context, sessionID string) ([]*DecisionStep, error) {
dn.mu.RLock()
defer dn.mu.RUnlock()
session, exists := dn.navigationSessions[sessionID]
if !exists {
return nil, fmt.Errorf("navigation session %s not found", sessionID)
}
// Return a copy of the history
history := make([]*DecisionStep, len(session.History))
copy(history, session.History)
return history, nil
}
// ResetNavigation resets navigation state to latest versions
func (dn *decisionNavigatorImpl) ResetNavigation(ctx context.Context, address ucxl.Address) error {
dn.mu.Lock()
defer dn.mu.Unlock()
// Clear any navigation sessions for this address
for sessionID, session := range dn.navigationSessions {
if session.CurrentPosition.String() == address.String() {
// Reset to latest version
latestNode, err := dn.graph.getLatestNodeUnsafe(address)
if err != nil {
return fmt.Errorf("failed to get latest node: %w", err)
}
session.CurrentPosition = address
session.History = []*DecisionStep{}
session.LastActivity = time.Now()
}
}
return nil
}
// BookmarkDecision creates a bookmark for a specific decision point
func (dn *decisionNavigatorImpl) BookmarkDecision(ctx context.Context, address ucxl.Address, hop int, name string) error {
dn.mu.Lock()
defer dn.mu.Unlock()
// Validate the decision point exists
node, err := dn.graph.GetVersionAtDecision(ctx, address, hop)
if err != nil {
return fmt.Errorf("decision point not found: %w", err)
}
// Create bookmark
bookmarkID := fmt.Sprintf("%s-%d-%d", address.String(), hop, time.Now().Unix())
bookmark := &DecisionBookmark{
ID: bookmarkID,
Name: name,
Description: fmt.Sprintf("Decision at hop %d for %s", hop, address.String()),
Address: address,
DecisionHop: hop,
CreatedBy: "system", // Could be passed as parameter
CreatedAt: time.Now(),
Tags: []string{},
Metadata: make(map[string]interface{}),
}
// Add context information to metadata
bookmark.Metadata["change_reason"] = node.ChangeReason
bookmark.Metadata["decision_id"] = node.DecisionID
bookmark.Metadata["confidence"] = node.Confidence
dn.bookmarks[bookmarkID] = bookmark
return nil
}
// ListBookmarks lists all bookmarks for navigation
func (dn *decisionNavigatorImpl) ListBookmarks(ctx context.Context) ([]*DecisionBookmark, error) {
dn.mu.RLock()
defer dn.mu.RUnlock()
bookmarks := make([]*DecisionBookmark, 0, len(dn.bookmarks))
for _, bookmark := range dn.bookmarks {
bookmarks = append(bookmarks, bookmark)
}
// Sort by creation time (newest first)
sort.Slice(bookmarks, func(i, j int) bool {
return bookmarks[i].CreatedAt.After(bookmarks[j].CreatedAt)
})
return bookmarks, nil
}
// Helper methods
func (dn *decisionNavigatorImpl) navigateOneHop(currentNode *TemporalNode, direction NavigationDirection) (*TemporalNode, error) {
switch direction {
case NavigationForward:
return dn.navigateForward(currentNode)
case NavigationBackward:
return dn.navigateBackward(currentNode)
default:
return nil, fmt.Errorf("invalid navigation direction: %s", direction)
}
}
func (dn *decisionNavigatorImpl) navigateForward(currentNode *TemporalNode) (*TemporalNode, error) {
// Forward navigation means going to a newer decision
addressKey := currentNode.UCXLAddress.String()
nodes, exists := dn.graph.addressToNodes[addressKey]
if !exists {
return nil, fmt.Errorf("no nodes found for address")
}
// Find current node in the list and get the next one
for i, node := range nodes {
if node.ID == currentNode.ID && i < len(nodes)-1 {
return nodes[i+1], nil
}
}
return nil, fmt.Errorf("no forward navigation possible")
}
func (dn *decisionNavigatorImpl) navigateBackward(currentNode *TemporalNode) (*TemporalNode, error) {
// Backward navigation means going to an older decision
if currentNode.ParentNode == nil {
return nil, fmt.Errorf("no backward navigation possible: no parent node")
}
parentNode, exists := dn.graph.nodes[*currentNode.ParentNode]
if !exists {
return nil, fmt.Errorf("parent node not found: %s", *currentNode.ParentNode)
}
return parentNode, nil
}
func (dn *decisionNavigatorImpl) getDecisionMaker(node *TemporalNode) string {
if decision, exists := dn.graph.decisions[node.DecisionID]; exists {
return decision.Maker
}
return "unknown"
}
func (dn *decisionNavigatorImpl) getDecisionRationale(node *TemporalNode) string {
if decision, exists := dn.graph.decisions[node.DecisionID]; exists {
return decision.Rationale
}
return ""
}
func (dn *decisionNavigatorImpl) analyzeTimeline(sequence []*DecisionTimelineEntry, related []*RelatedDecision) *TimelineAnalysis {
if len(sequence) == 0 {
return &TimelineAnalysis{
AnalyzedAt: time.Now(),
}
}
// Calculate change velocity
var changeVelocity float64
if len(sequence) > 1 {
firstTime := sequence[0].Timestamp
lastTime := sequence[len(sequence)-1].Timestamp
duration := lastTime.Sub(firstTime)
if duration > 0 {
changeVelocity = float64(len(sequence)-1) / duration.Hours()
}
}
// Analyze confidence trend
confidenceTrend := "stable"
if len(sequence) > 1 {
firstConfidence := sequence[0].ConfidenceEvolution
lastConfidence := sequence[len(sequence)-1].ConfidenceEvolution
diff := lastConfidence - firstConfidence
if diff > 0.1 {
confidenceTrend = "increasing"
} else if diff < -0.1 {
confidenceTrend = "decreasing"
}
}
// Count change reasons
reasonCounts := make(map[ChangeReason]int)
for _, entry := range sequence {
reasonCounts[entry.ChangeReason]++
}
// Find dominant reasons
dominantReasons := make([]ChangeReason, 0)
maxCount := 0
for reason, count := range reasonCounts {
if count > maxCount {
maxCount = count
dominantReasons = []ChangeReason{reason}
} else if count == maxCount {
dominantReasons = append(dominantReasons, reason)
}
}
// Count decision makers
makerCounts := make(map[string]int)
for _, entry := range sequence {
makerCounts[entry.DecisionMaker]++
}
// Count impact scope distribution
scopeCounts := make(map[ImpactScope]int)
for _, entry := range sequence {
scopeCounts[entry.ImpactScope]++
}
return &TimelineAnalysis{
ChangeVelocity: changeVelocity,
ConfidenceTrend: confidenceTrend,
DominantChangeReasons: dominantReasons,
DecisionMakers: makerCounts,
ImpactScopeDistribution: scopeCounts,
InfluenceNetworkSize: len(related),
AnalyzedAt: time.Now(),
}
}
func (dn *decisionNavigatorImpl) getStalenessReasons(node *TemporalNode) []string {
reasons := make([]string, 0)
// Time-based staleness
timeSinceUpdate := time.Since(node.Timestamp)
if timeSinceUpdate > 7*24*time.Hour {
reasons = append(reasons, "not updated in over a week")
}
// Influence-based staleness
if len(node.InfluencedBy) > 0 {
reasons = append(reasons, "influenced by other contexts that may have changed")
}
// Confidence-based staleness
if node.Confidence < 0.7 {
reasons = append(reasons, "low confidence score")
}
return reasons
}
func (dn *decisionNavigatorImpl) getSuggestedActions(node *TemporalNode) []string {
actions := make([]string, 0)
actions = append(actions, "review context for accuracy")
actions = append(actions, "check related decisions for impact")
if node.Confidence < 0.7 {
actions = append(actions, "improve context confidence through additional analysis")
}
if len(node.InfluencedBy) > 3 {
actions = append(actions, "validate dependencies are still accurate")
}
return actions
}
func (dn *decisionNavigatorImpl) getRelatedChanges(node *TemporalNode) []ucxl.Address {
// Find contexts that have changed recently and might affect this one
relatedChanges := make([]ucxl.Address, 0)
cutoff := time.Now().Add(-24 * time.Hour)
for _, otherNode := range dn.graph.nodes {
if otherNode.Timestamp.After(cutoff) && otherNode.ID != node.ID {
// Check if this node influences the stale node
for _, influenced := range otherNode.Influences {
if influenced.String() == node.UCXLAddress.String() {
relatedChanges = append(relatedChanges, otherNode.UCXLAddress)
break
}
}
}
}
return relatedChanges
}
func (dn *decisionNavigatorImpl) calculateStalePriority(node *TemporalNode) StalePriority {
score := node.Staleness
// Adjust based on influence
if len(node.Influences) > 5 {
score += 0.2 // Higher priority if it influences many others
}
// Adjust based on impact scope
switch node.ImpactScope {
case ImpactSystem:
score += 0.3
case ImpactProject:
score += 0.2
case ImpactModule:
score += 0.1
}
if score >= 0.9 {
return PriorityCritical
} else if score >= 0.7 {
return PriorityHigh
} else if score >= 0.5 {
return PriorityMedium
}
return PriorityLow
}
func (dn *decisionNavigatorImpl) validateStepRelationship(step, nextStep *DecisionStep) bool {
// Check if there's a valid relationship between the steps
currentNodeID := step.TemporalNode.ID
nextNodeID := nextStep.TemporalNode.ID
switch step.Relationship {
case "influences":
if influences, exists := dn.graph.influences[currentNodeID]; exists {
for _, influenced := range influences {
if influenced == nextNodeID {
return true
}
}
}
case "influenced_by":
if influencedBy, exists := dn.graph.influencedBy[currentNodeID]; exists {
for _, influencer := range influencedBy {
if influencer == nextNodeID {
return true
}
}
}
}
return false
}