package integration import ( "context" "crypto/sha256" "encoding/json" "fmt" "log" "time" "chorus.services/bzzz/pkg/dht" "chorus.services/bzzz/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") }