Files
CHORUS/pkg/hmmm_adapter/adapter_stub.go
anthonyrawlins 543ab216f9 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>
2025-09-02 20:02:37 +10:00

236 lines
6.7 KiB
Go

package hmmm_adapter
import (
"context"
"fmt"
"sync"
"time"
)
// Joiner joins a pub/sub topic (ensure availability before publish).
type Joiner func(topic string) error
// Publisher publishes a raw JSON payload to a topic.
type Publisher func(topic string, payload []byte) error
// Adapter bridges BZZZ pub/sub to a RawPublisher-compatible interface.
// It does not impose any message envelope so HMMM can publish raw JSON frames.
// The adapter provides additional features like topic caching, metrics, and validation.
type Adapter struct {
join Joiner
publish Publisher
// Topic join cache to avoid redundant joins
joinedTopics map[string]bool
joinedTopicsMu sync.RWMutex
// Metrics tracking
publishCount int64
joinCount int64
errorCount int64
metricsLock sync.RWMutex
// Configuration
maxPayloadSize int
joinTimeout time.Duration
publishTimeout time.Duration
}
// AdapterConfig holds configuration options for the Adapter
type AdapterConfig struct {
MaxPayloadSize int `yaml:"max_payload_size"`
JoinTimeout time.Duration `yaml:"join_timeout"`
PublishTimeout time.Duration `yaml:"publish_timeout"`
}
// DefaultAdapterConfig returns sensible defaults for the adapter
func DefaultAdapterConfig() AdapterConfig {
return AdapterConfig{
MaxPayloadSize: 1024 * 1024, // 1MB max payload
JoinTimeout: 30 * time.Second,
PublishTimeout: 10 * time.Second,
}
}
// NewAdapter constructs a new adapter with explicit join/publish hooks.
// Wire these to BZZZ pubsub methods, e.g., JoinDynamicTopic and a thin PublishRaw helper.
func NewAdapter(join Joiner, publish Publisher) *Adapter {
return NewAdapterWithConfig(join, publish, DefaultAdapterConfig())
}
// NewAdapterWithConfig constructs a new adapter with custom configuration.
func NewAdapterWithConfig(join Joiner, publish Publisher, config AdapterConfig) *Adapter {
return &Adapter{
join: join,
publish: publish,
joinedTopics: make(map[string]bool),
maxPayloadSize: config.MaxPayloadSize,
joinTimeout: config.JoinTimeout,
publishTimeout: config.PublishTimeout,
}
}
// Publish ensures the topic is joined before sending a raw payload.
// Includes validation, caching, metrics, and timeout handling.
func (a *Adapter) Publish(ctx context.Context, topic string, payload []byte) error {
// Input validation
if topic == "" {
a.incrementErrorCount()
return fmt.Errorf("topic cannot be empty")
}
if len(payload) == 0 {
a.incrementErrorCount()
return fmt.Errorf("payload cannot be empty")
}
if len(payload) > a.maxPayloadSize {
a.incrementErrorCount()
return fmt.Errorf("payload size %d exceeds maximum %d bytes", len(payload), a.maxPayloadSize)
}
// Check if we need to join the topic (with caching)
if !a.isTopicJoined(topic) {
joinCtx, cancel := context.WithTimeout(ctx, a.joinTimeout)
defer cancel()
if err := a.joinTopic(joinCtx, topic); err != nil {
a.incrementErrorCount()
return fmt.Errorf("failed to join topic %s: %w", topic, err)
}
}
// Publish with timeout
publishCtx, cancel := context.WithTimeout(ctx, a.publishTimeout)
defer cancel()
done := make(chan error, 1)
go func() {
done <- a.publish(topic, payload)
}()
select {
case err := <-done:
if err != nil {
a.incrementErrorCount()
return fmt.Errorf("failed to publish to topic %s: %w", topic, err)
}
a.incrementPublishCount()
return nil
case <-publishCtx.Done():
a.incrementErrorCount()
return fmt.Errorf("publish to topic %s timed out after %v", topic, a.publishTimeout)
}
}
// isTopicJoined checks if a topic has already been joined (with caching)
func (a *Adapter) isTopicJoined(topic string) bool {
a.joinedTopicsMu.RLock()
defer a.joinedTopicsMu.RUnlock()
return a.joinedTopics[topic]
}
// joinTopic joins a topic and updates the cache
func (a *Adapter) joinTopic(ctx context.Context, topic string) error {
// Double-check locking pattern to avoid redundant joins
if a.isTopicJoined(topic) {
return nil
}
a.joinedTopicsMu.Lock()
defer a.joinedTopicsMu.Unlock()
// Check again after acquiring write lock
if a.joinedTopics[topic] {
return nil
}
// Execute join with context
done := make(chan error, 1)
go func() {
done <- a.join(topic)
}()
select {
case err := <-done:
if err == nil {
a.joinedTopics[topic] = true
a.incrementJoinCount()
}
return err
case <-ctx.Done():
return ctx.Err()
}
}
// GetMetrics returns current adapter metrics
func (a *Adapter) GetMetrics() AdapterMetrics {
a.metricsLock.RLock()
defer a.metricsLock.RUnlock()
return AdapterMetrics{
PublishCount: a.publishCount,
JoinCount: a.joinCount,
ErrorCount: a.errorCount,
JoinedTopics: len(a.joinedTopics),
}
}
// AdapterMetrics holds metrics data for the adapter
type AdapterMetrics struct {
PublishCount int64 `json:"publish_count"`
JoinCount int64 `json:"join_count"`
ErrorCount int64 `json:"error_count"`
JoinedTopics int `json:"joined_topics"`
}
// ResetMetrics resets all metrics counters (useful for testing)
func (a *Adapter) ResetMetrics() {
a.metricsLock.Lock()
defer a.metricsLock.Unlock()
a.publishCount = 0
a.joinCount = 0
a.errorCount = 0
}
// ClearTopicCache clears the joined topics cache (useful for testing or reconnections)
func (a *Adapter) ClearTopicCache() {
a.joinedTopicsMu.Lock()
defer a.joinedTopicsMu.Unlock()
a.joinedTopics = make(map[string]bool)
}
// GetJoinedTopics returns a list of currently joined topics
func (a *Adapter) GetJoinedTopics() []string {
a.joinedTopicsMu.RLock()
defer a.joinedTopicsMu.RUnlock()
topics := make([]string, 0, len(a.joinedTopics))
for topic := range a.joinedTopics {
topics = append(topics, topic)
}
return topics
}
// incrementPublishCount safely increments the publish counter
func (a *Adapter) incrementPublishCount() {
a.metricsLock.Lock()
a.publishCount++
a.metricsLock.Unlock()
}
// incrementJoinCount safely increments the join counter
func (a *Adapter) incrementJoinCount() {
a.metricsLock.Lock()
a.joinCount++
a.metricsLock.Unlock()
}
// incrementErrorCount safely increments the error counter
func (a *Adapter) incrementErrorCount() {
a.metricsLock.Lock()
a.errorCount++
a.metricsLock.Unlock()
}