🎭 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>
313 lines
9.7 KiB
Go
313 lines
9.7 KiB
Go
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")
|
|
} |