 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>
		
			
				
	
	
		
			313 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			313 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package integration
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/sha256"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"time"
 | |
| 
 | |
| 	"chorus/pkg/dht"
 | |
| 	"chorus/pkg/ucxl"
 | |
| )
 | |
| 
 | |
| // DecisionPublisher handles publishing decisions to encrypted DHT storage
 | |
| type DecisionPublisher struct {
 | |
| 	dhtStorage *dht.EncryptedDHTStorage
 | |
| 	enabled    bool
 | |
| }
 | |
| 
 | |
| // Decision represents a decision made from a HMMM discussion
 | |
| type Decision struct {
 | |
| 	Type            string                 `json:"type"`            // Event type (approval, warning, etc.)
 | |
| 	Content         string                 `json:"content"`         // Human-readable decision content
 | |
| 	Participants    []string               `json:"participants"`    // Who participated in the decision
 | |
| 	ConsensusLevel  float64                `json:"consensus_level"` // Strength of consensus (0.0-1.0)
 | |
| 	Timestamp       time.Time              `json:"timestamp"`       // When decision was made
 | |
| 	DiscussionID    string                 `json:"discussion_id"`   // Source discussion ID
 | |
| 	Confidence      float64                `json:"confidence"`      // AI confidence in decision extraction
 | |
| 	Metadata        map[string]interface{} `json:"metadata"`        // Additional decision metadata
 | |
| 	UCXLAddress     string                 `json:"ucxl_address"`    // Associated UCXL address
 | |
| 	ExpiresAt       *time.Time             `json:"expires_at,omitempty"` // Optional expiration
 | |
| 	Tags            []string               `json:"tags"`            // Decision tags
 | |
| 	RelatedDecisions []string              `json:"related_decisions,omitempty"` // Related decision hashes
 | |
| }
 | |
| 
 | |
| // PublishResult contains the result of publishing a decision
 | |
| type PublishResult struct {
 | |
| 	UCXLAddress string    `json:"ucxl_address"`
 | |
| 	DHTHash     string    `json:"dht_hash"`
 | |
| 	Success     bool      `json:"success"`
 | |
| 	PublishedAt time.Time `json:"published_at"`
 | |
| 	Error       string    `json:"error,omitempty"`
 | |
| }
 | |
| 
 | |
| // NewDecisionPublisher creates a new decision publisher
 | |
| func NewDecisionPublisher(dhtStorage *dht.EncryptedDHTStorage, enabled bool) *DecisionPublisher {
 | |
| 	return &DecisionPublisher{
 | |
| 		dhtStorage: dhtStorage,
 | |
| 		enabled:    enabled,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // PublishDecision publishes a decision to the encrypted DHT storage
 | |
| func (dp *DecisionPublisher) PublishDecision(ctx context.Context, ucxlAddr *ucxl.Address, decision *Decision) (*PublishResult, error) {
 | |
| 	result := &PublishResult{
 | |
| 		UCXLAddress: ucxlAddr.String(),
 | |
| 		PublishedAt: time.Now(),
 | |
| 	}
 | |
| 	
 | |
| 	if !dp.enabled {
 | |
| 		result.Error = "Decision publishing is disabled"
 | |
| 		log.Printf("📤 Decision publishing skipped (disabled): %s", ucxlAddr.String())
 | |
| 		return result, nil
 | |
| 	}
 | |
| 	
 | |
| 	// Enrich decision with UCXL address
 | |
| 	decision.UCXLAddress = ucxlAddr.String()
 | |
| 	
 | |
| 	// Serialize decision to JSON
 | |
| 	decisionJSON, err := json.Marshal(decision)
 | |
| 	if err != nil {
 | |
| 		result.Error = fmt.Sprintf("failed to serialize decision: %v", err)
 | |
| 		return result, fmt.Errorf("failed to serialize decision: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	// Determine creator role from UCXL address
 | |
| 	creatorRole := ucxlAddr.Role
 | |
| 	if creatorRole == "any" || creatorRole == "" {
 | |
| 		creatorRole = "contributor" // Default role for decisions
 | |
| 	}
 | |
| 	
 | |
| 	// Store in encrypted DHT
 | |
| 	err = dp.dhtStorage.StoreUCXLContent(
 | |
| 		ucxlAddr.String(),
 | |
| 		decisionJSON,
 | |
| 		creatorRole,
 | |
| 		"decision",
 | |
| 	)
 | |
| 	
 | |
| 	if err != nil {
 | |
| 		result.Error = err.Error()
 | |
| 		return result, fmt.Errorf("failed to store decision in DHT: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	// Generate content hash for reference
 | |
| 	result.DHTHash = fmt.Sprintf("sha256:%x", sha256.Sum256(decisionJSON))
 | |
| 	result.Success = true
 | |
| 	
 | |
| 	log.Printf("📤 Decision published to DHT: %s (hash: %s)", ucxlAddr.String(), result.DHTHash[:16]+"...")
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| // RetrieveDecision retrieves a decision from the encrypted DHT storage
 | |
| func (dp *DecisionPublisher) RetrieveDecision(ctx context.Context, ucxlAddr *ucxl.Address) (*Decision, error) {
 | |
| 	if !dp.enabled {
 | |
| 		return nil, fmt.Errorf("decision publishing is disabled")
 | |
| 	}
 | |
| 	
 | |
| 	// Retrieve from encrypted DHT
 | |
| 	content, metadata, err := dp.dhtStorage.RetrieveUCXLContent(ucxlAddr.String())
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to retrieve decision from DHT: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	// Verify content type
 | |
| 	if metadata.ContentType != "decision" {
 | |
| 		return nil, fmt.Errorf("content at address is not a decision (type: %s)", metadata.ContentType)
 | |
| 	}
 | |
| 	
 | |
| 	// Deserialize decision
 | |
| 	var decision Decision
 | |
| 	if err := json.Unmarshal(content, &decision); err != nil {
 | |
| 		return nil, fmt.Errorf("failed to deserialize decision: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	log.Printf("📥 Decision retrieved from DHT: %s", ucxlAddr.String())
 | |
| 	return &decision, nil
 | |
| }
 | |
| 
 | |
| // ListDecisionsByRole lists decisions accessible by a specific role
 | |
| func (dp *DecisionPublisher) ListDecisionsByRole(ctx context.Context, role string, limit int) ([]*Decision, error) {
 | |
| 	if !dp.enabled {
 | |
| 		return nil, fmt.Errorf("decision publishing is disabled")
 | |
| 	}
 | |
| 	
 | |
| 	// Get content metadata from DHT
 | |
| 	metadataList, err := dp.dhtStorage.ListContentByRole(role, limit)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to list content by role: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	decisions := make([]*Decision, 0)
 | |
| 	
 | |
| 	// Retrieve each decision
 | |
| 	for _, metadata := range metadataList {
 | |
| 		if metadata.ContentType != "decision" {
 | |
| 			continue // Skip non-decisions
 | |
| 		}
 | |
| 		
 | |
| 		// Parse UCXL address
 | |
| 		addr, err := ucxl.Parse(metadata.Address)
 | |
| 		if err != nil {
 | |
| 			log.Printf("⚠️ Invalid UCXL address in decision metadata: %s", metadata.Address)
 | |
| 			continue
 | |
| 		}
 | |
| 		
 | |
| 		// Retrieve decision content
 | |
| 		decision, err := dp.RetrieveDecision(ctx, addr)
 | |
| 		if err != nil {
 | |
| 			log.Printf("⚠️ Failed to retrieve decision %s: %v", metadata.Address, err)
 | |
| 			continue
 | |
| 		}
 | |
| 		
 | |
| 		decisions = append(decisions, decision)
 | |
| 		
 | |
| 		// Respect limit
 | |
| 		if len(decisions) >= limit {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	log.Printf("📋 Listed %d decisions for role: %s", len(decisions), role)
 | |
| 	return decisions, nil
 | |
| }
 | |
| 
 | |
| // UpdateDecision updates an existing decision or creates a new version
 | |
| func (dp *DecisionPublisher) UpdateDecision(ctx context.Context, ucxlAddr *ucxl.Address, decision *Decision) (*PublishResult, error) {
 | |
| 	if !dp.enabled {
 | |
| 		result := &PublishResult{
 | |
| 			UCXLAddress: ucxlAddr.String(),
 | |
| 			PublishedAt: time.Now(),
 | |
| 			Error:       "Decision publishing is disabled",
 | |
| 		}
 | |
| 		return result, nil
 | |
| 	}
 | |
| 	
 | |
| 	// Check if decision already exists
 | |
| 	existingDecision, err := dp.RetrieveDecision(ctx, ucxlAddr)
 | |
| 	if err == nil {
 | |
| 		// Decision exists, create related decision reference
 | |
| 		decision.RelatedDecisions = append(decision.RelatedDecisions, dp.generateDecisionHash(existingDecision))
 | |
| 		log.Printf("📝 Updating existing decision: %s", ucxlAddr.String())
 | |
| 	} else {
 | |
| 		log.Printf("📝 Creating new decision: %s", ucxlAddr.String())
 | |
| 	}
 | |
| 	
 | |
| 	// Publish the updated/new decision
 | |
| 	return dp.PublishDecision(ctx, ucxlAddr, decision)
 | |
| }
 | |
| 
 | |
| // SearchDecisions searches for decisions matching criteria
 | |
| func (dp *DecisionPublisher) SearchDecisions(ctx context.Context, searchCriteria map[string]string, limit int) ([]*Decision, error) {
 | |
| 	if !dp.enabled {
 | |
| 		return nil, fmt.Errorf("decision publishing is disabled")
 | |
| 	}
 | |
| 	
 | |
| 	// Convert search criteria to DHT search query
 | |
| 	query := &dht.SearchQuery{
 | |
| 		Agent:       searchCriteria["agent"],
 | |
| 		Role:        searchCriteria["role"],
 | |
| 		Project:     searchCriteria["project"],
 | |
| 		Task:        searchCriteria["task"],
 | |
| 		ContentType: "decision",
 | |
| 		Limit:       limit,
 | |
| 	}
 | |
| 	
 | |
| 	// Parse time filters if provided
 | |
| 	if createdAfter := searchCriteria["created_after"]; createdAfter != "" {
 | |
| 		if t, err := time.Parse(time.RFC3339, createdAfter); err == nil {
 | |
| 			query.CreatedAfter = t
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	if createdBefore := searchCriteria["created_before"]; createdBefore != "" {
 | |
| 		if t, err := time.Parse(time.RFC3339, createdBefore); err == nil {
 | |
| 			query.CreatedBefore = t
 | |
| 		}
 | |
| 	}
 | |
| 	
 | |
| 	// Search DHT for matching decisions
 | |
| 	searchResults, err := dp.dhtStorage.SearchContent(query)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to search decisions: %w", err)
 | |
| 	}
 | |
| 	
 | |
| 	decisions := make([]*Decision, 0, len(searchResults))
 | |
| 	
 | |
| 	// Retrieve each decision
 | |
| 	for _, metadata := range searchResults {
 | |
| 		// Parse UCXL address
 | |
| 		addr, err := ucxl.Parse(metadata.Address)
 | |
| 		if err != nil {
 | |
| 			log.Printf("⚠️ Invalid UCXL address in search results: %s", metadata.Address)
 | |
| 			continue
 | |
| 		}
 | |
| 		
 | |
| 		// Retrieve decision content
 | |
| 		decision, err := dp.RetrieveDecision(ctx, addr)
 | |
| 		if err != nil {
 | |
| 			log.Printf("⚠️ Failed to retrieve decision %s: %v", metadata.Address, err)
 | |
| 			continue
 | |
| 		}
 | |
| 		
 | |
| 		decisions = append(decisions, decision)
 | |
| 	}
 | |
| 	
 | |
| 	log.Printf("🔍 Search found %d decisions", len(decisions))
 | |
| 	return decisions, nil
 | |
| }
 | |
| 
 | |
| // GetDecisionMetrics returns metrics about decisions in the system
 | |
| func (dp *DecisionPublisher) GetDecisionMetrics(ctx context.Context) (map[string]interface{}, error) {
 | |
| 	if !dp.enabled {
 | |
| 		return map[string]interface{}{
 | |
| 			"enabled": false,
 | |
| 			"message": "Decision publishing is disabled",
 | |
| 		}, nil
 | |
| 	}
 | |
| 	
 | |
| 	// Get DHT storage metrics
 | |
| 	dhtMetrics := dp.dhtStorage.GetMetrics()
 | |
| 	
 | |
| 	// Add decision-specific metrics
 | |
| 	metrics := map[string]interface{}{
 | |
| 		"enabled":      true,
 | |
| 		"dht_storage":  dhtMetrics,
 | |
| 		"last_updated": time.Now(),
 | |
| 	}
 | |
| 	
 | |
| 	return metrics, nil
 | |
| }
 | |
| 
 | |
| // generateDecisionHash generates a hash for a decision to use in references
 | |
| func (dp *DecisionPublisher) generateDecisionHash(decision *Decision) string {
 | |
| 	// Create hash from key decision fields
 | |
| 	hashData := fmt.Sprintf("%s_%s_%s_%d",
 | |
| 		decision.Type,
 | |
| 		decision.UCXLAddress,
 | |
| 		decision.DiscussionID,
 | |
| 		decision.Timestamp.Unix(),
 | |
| 	)
 | |
| 	
 | |
| 	hash := sha256.Sum256([]byte(hashData))
 | |
| 	return fmt.Sprintf("decision_%x", hash[:8])
 | |
| }
 | |
| 
 | |
| // IsEnabled returns whether decision publishing is enabled
 | |
| func (dp *DecisionPublisher) IsEnabled() bool {
 | |
| 	return dp.enabled
 | |
| }
 | |
| 
 | |
| // Enable enables decision publishing
 | |
| func (dp *DecisionPublisher) Enable() {
 | |
| 	dp.enabled = true
 | |
| 	log.Printf("📤 Decision publishing enabled")
 | |
| }
 | |
| 
 | |
| // Disable disables decision publishing
 | |
| func (dp *DecisionPublisher) Disable() {
 | |
| 	dp.enabled = false
 | |
| 	log.Printf("🚫 Decision publishing disabled")
 | |
| } |