Files
bzzz/LICENSING_DEVELOPMENT_PLAN.md
2025-09-01 20:36:26 +10:00

18 KiB
Raw Permalink Blame History

BZZZ Licensing Development Plan

Date: 2025-09-01
Branch: feature/licensing-enforcement
Status: Ready for implementation (depends on KACHING Phase 1)
Priority: HIGH - Revenue protection and license enforcement

Executive Summary

BZZZ currently has zero license enforcement in production. The system collects license information during setup but completely ignores it at runtime, allowing unlimited unlicensed usage. This plan implements comprehensive license enforcement integrated with KACHING license authority.

Current State Analysis

Existing License Components

  • License validation UI component (install/config-ui/app/setup/components/LicenseValidation.tsx)
  • Terms and conditions acceptance (install/config-ui/app/setup/components/TermsAndConditions.tsx)
  • Mock license validation endpoint (main.go lines 1584-1618)
  • Test license key documentation (TEST_LICENSE_KEY.txt)

Critical Security Gap

  • License data NOT saved to configuration - Setup collects but discards license info
  • Zero runtime license validation - System starts without any license checks
  • No integration with license server - Mock validation only, no real enforcement
  • No cluster binding - No protection against license sharing across multiple clusters
  • No license expiration checks - Licenses never expire in practice
  • No feature restrictions - All features available regardless of license tier

Current Configuration Structure Gap

Setup Config Missing License Data:

// api/setup_manager.go line 539 - SetupConfig struct
type SetupConfig struct {
    Agent    *AgentConfig       `json:"agent"`
    GitHub   *GitHubConfig      `json:"github"`
    // ... other configs ...
    // ❌ NO LICENSE FIELD - license data is collected but discarded!
}

Main Config Missing License Support:

// pkg/config/config.go - Config struct
type Config struct {
    Agent    AgentConfig    `yaml:"agent" json:"agent"`
    GitHub   GitHubConfig   `yaml:"github" json:"github"`
    // ... other configs ...
    // ❌ NO LICENSE FIELD - runtime ignores licensing completely!
}

Development Phases

Phase 2A: Configuration System Integration (PRIORITY 1)

Goal: Make license data part of BZZZ configuration

1. Update Configuration Structures

// Add to pkg/config/config.go
type Config struct {
    // ... existing fields ...
    License LicenseConfig `yaml:"license" json:"license"`
}

type LicenseConfig struct {
    ServerURL        string    `yaml:"server_url" json:"server_url"`
    LicenseKey       string    `yaml:"license_key" json:"license_key"`
    ClusterID        string    `yaml:"cluster_id" json:"cluster_id"`
    Email            string    `yaml:"email" json:"email"`
    OrganizationName string    `yaml:"organization_name,omitempty" json:"organization_name,omitempty"`
    
    // Runtime state (populated during activation)
    Token            string    `yaml:"-" json:"-"` // Don't persist token to file
    TokenExpiry      time.Time `yaml:"-" json:"-"`
    LicenseType      string    `yaml:"license_type,omitempty" json:"license_type,omitempty"`
    MaxNodes         int       `yaml:"max_nodes,omitempty" json:"max_nodes,omitempty"`
    Features         []string  `yaml:"features,omitempty" json:"features,omitempty"`
    ExpiresAt        time.Time `yaml:"expires_at,omitempty" json:"expires_at,omitempty"`
    
    // Setup verification
    ValidatedAt      time.Time `yaml:"validated_at" json:"validated_at"`
    TermsAcceptedAt  time.Time `yaml:"terms_accepted_at" json:"terms_accepted_at"`
}

2. Update Setup Configuration

// Add to api/setup_manager.go SetupConfig struct
type SetupConfig struct {
    // ... existing fields ...
    License *LicenseConfig   `json:"license"`
    Terms   *TermsAcceptance `json:"terms"`
}

type TermsAcceptance struct {
    Agreed    bool      `json:"agreed"`
    Timestamp time.Time `json:"timestamp"`
}

3. Fix Setup Save Process

Currently in generateAndDeployConfig(), license data is completely ignored. Fix this:

// api/setup_manager.go - Update generateAndDeployConfig()
func (sm *SetupManager) generateAndDeployConfig(setupData SetupConfig) error {
    config := Config{
        Agent:   setupData.Agent,
        GitHub:  setupData.GitHub,
        License: setupData.License,  // ✅ ADD THIS - currently missing!
        // ... other fields ...
    }
    // ... save to config file ...
}

Phase 2B: License Validation Integration (PRIORITY 2)

Goal: Replace mock validation with KACHING license server

1. Replace Mock License Validation

Current (main.go lines 1584-1618):

// ❌ REMOVE: Hardcoded mock validation
validLicenseKey := "BZZZ-2025-DEMO-EVAL-001"
if licenseRequest.LicenseKey != validLicenseKey {
    // ... return error ...
}

New KACHING Integration:

// ✅ ADD: Real license server validation
func (sm *SetupManager) validateLicenseWithKACHING(email, licenseKey, orgName string) (*LicenseValidationResponse, error) {
    client := &http.Client{Timeout: 30 * time.Second}
    
    reqBody := map[string]string{
        "email":             email,
        "license_key":       licenseKey,
        "organization_name": orgName,
    }
    
    // Call KACHING license server
    resp, err := client.Post(
        sm.config.LicenseServerURL+"/v1/license/activate",
        "application/json",
        bytes.NewBuffer(jsonData),
    )
    
    // Parse response and return license details
    // Store cluster_id for runtime use
}

2. Generate and Persist Cluster ID

func generateClusterID() string {
    // Generate unique cluster identifier
    // Format: bzzz-cluster-<uuid>-<hostname>
    hostname, _ := os.Hostname()
    clusterUUID := uuid.New().String()[:8]
    return fmt.Sprintf("bzzz-cluster-%s-%s", clusterUUID, hostname)
}

Phase 2C: Runtime License Enforcement (PRIORITY 3)

Goal: Enforce license validation during BZZZ startup and operation

1. Add License Validation to Startup Sequence

Current startup logic (main.go lines 154-169):

func main() {
    // ... config loading ...
    
    if !cfg.IsValidConfiguration() {
        startSetupMode(configPath)
        return
    }
    
    // ✅ ADD LICENSE VALIDATION HERE - currently missing!
    if err := validateLicenseForRuntime(cfg); err != nil {
        fmt.Printf("❌ License validation failed: %v\n", err)
        fmt.Printf("🔧 License issue detected, entering setup mode...\n")
        startSetupMode(configPath)
        return
    }
    
    // Continue with normal startup...
    startNormalMode(cfg)
}

2. Implement Runtime License Validation

func validateLicenseForRuntime(cfg *Config) error {
    if cfg.License.LicenseKey == "" {
        return fmt.Errorf("no license key configured")
    }
    
    if cfg.License.ClusterID == "" {
        return fmt.Errorf("no cluster ID configured")
    }
    
    // Check license expiration
    if !cfg.License.ExpiresAt.IsZero() && time.Now().After(cfg.License.ExpiresAt) {
        return fmt.Errorf("license expired on %v", cfg.License.ExpiresAt.Format("2006-01-02"))
    }
    
    // Attempt license activation with KACHING
    client := NewLicenseClient(cfg.License.ServerURL)
    token, err := client.ActivateLicense(cfg.License.LicenseKey, cfg.License.ClusterID)
    if err != nil {
        return fmt.Errorf("license activation failed: %w", err)
    }
    
    // Store token for heartbeat worker
    cfg.License.Token = token.AccessToken
    cfg.License.TokenExpiry = token.ExpiresAt
    
    return nil
}

3. Background License Heartbeat Worker

func startLicenseHeartbeatWorker(cfg *Config, shutdownChan chan struct{}) {
    ticker := time.NewTicker(15 * time.Minute) // Heartbeat every 15 minutes
    defer ticker.Stop()
    
    client := NewLicenseClient(cfg.License.ServerURL)
    
    for {
        select {
        case <-ticker.C:
            // Send heartbeat to KACHING
            token, err := client.SendHeartbeat(cfg.License.LicenseKey, cfg.License.ClusterID, cfg.License.Token)
            if err != nil {
                log.Printf("❌ License heartbeat failed: %v", err)
                // Implement exponential backoff and graceful degradation
                handleLicenseHeartbeatFailure(err)
                continue
            }
            
            // Update token if refreshed
            if token.AccessToken != cfg.License.Token {
                cfg.License.Token = token.AccessToken
                cfg.License.TokenExpiry = token.ExpiresAt
                log.Printf("✅ License token refreshed, expires: %v", token.ExpiresAt)
            }
            
        case <-shutdownChan:
            // Deactivate license on shutdown
            err := client.DeactivateLicense(cfg.License.LicenseKey, cfg.License.ClusterID)
            if err != nil {
                log.Printf("⚠️ Failed to deactivate license on shutdown: %v", err)
            } else {
                log.Printf("✅ License deactivated on shutdown")
            }
            return
        }
    }
}

4. License Failure Handling

func handleLicenseHeartbeatFailure(err error) {
    // Parse error type
    if isLicenseSuspended(err) {
        log.Printf("🚨 LICENSE SUSPENDED - STOPPING BZZZ OPERATIONS")
        // Hard stop - license suspended by admin
        os.Exit(1)
    } else if isNetworkError(err) {
        log.Printf("⚠️ Network error during heartbeat - continuing with grace period")
        // Continue operation with exponential backoff
        // Stop if grace period exceeded (e.g., 24 hours)
    } else {
        log.Printf("❌ Unknown license error: %v", err)
        // Implement appropriate fallback
    }
}

5. Token Versioning and Offline Tokens

// On every heartbeat response, compare token_version
if token.TokenVersion > cfg.License.TokenVersion {
    // Server bumped version (suspend/cancel or rotation)
    cfg.License.TokenVersion = token.TokenVersion
}

// If server rejects with "stale token_version" → re-activate to fetch a fresh token

// Offline tokens
// Accept an Ed25519-signed offline token with short expiry when network is unavailable.
// Validate signature + expiry locally; on reconnect, immediately validate with server.
  • 200 OK (heartbeat): update token, token_version
  • 403 Forbidden: suspended/cancelled → fail closed, stop operations
  • 409 Conflict: cluster slot in use → backoff and reactivate after grace (or operator action)
  • 5xx / network error: continue in grace window with exponential backoff; exit when grace exceeded

7. Cluster Identity and Telemetry

  • Generate cluster_id once; persist in config; include hostname/IP in activation metadata for admin visibility.
  • Emit perjob telemetry to KACHING (align keys: tokens, context_operations, cpu_hours, temporal_nav_hops) to drive quotas and upgrade suggestions.

Phase 2D: Feature Enforcement (PRIORITY 4)

Goal: Restrict features based on license tier

1. Feature Gate Implementation

type FeatureGate struct {
    licensedFeatures map[string]bool
}

func NewFeatureGate(config *Config) *FeatureGate {
    gates := make(map[string]bool)
    for _, feature := range config.License.Features {
        gates[feature] = true
    }
    return &FeatureGate{licensedFeatures: gates}
}

func (fg *FeatureGate) IsEnabled(feature string) bool {
    return fg.licensedFeatures[feature]
}

// Usage throughout BZZZ codebase
func (agent *Agent) startAdvancedAIIntegration() error {
    if !agent.featureGate.IsEnabled("advanced-ai-integration") {
        return fmt.Errorf("advanced AI integration requires Standard tier or higher")
    }
    // ... proceed with feature ...
}

2. Node Count Enforcement

func validateNodeCount(config *Config, currentNodes int) error {
    maxNodes := config.License.MaxNodes
    if maxNodes > 0 && currentNodes > maxNodes {
        return fmt.Errorf("cluster has %d nodes but license only allows %d nodes", currentNodes, maxNodes)
    }
    return nil
}

Implementation Files to Modify

Core Configuration Files

  • pkg/config/config.go - Add LicenseConfig struct
  • api/setup_manager.go - Add license to SetupConfig, fix save process
  • main.go - Add license validation to startup sequence

New License Client Files

  • pkg/license/client.go - KACHING API client
  • pkg/license/heartbeat.go - Background heartbeat worker
  • pkg/license/features.go - Feature gate implementation
  • pkg/license/validation.go - Runtime license validation

UI Integration

  • Update install/config-ui/app/setup/components/LicenseValidation.tsx to call KACHING
  • Ensure license data is properly saved in setup flow

Configuration Updates Required

Environment Variables

# License server configuration
LICENSE_SERVER_URL=https://kaching.chorus.services
LICENSE_KEY=BZZZ-2025-ABC123-XYZ
CLUSTER_ID=bzzz-cluster-uuid-hostname

# Offline mode configuration
LICENSE_OFFLINE_GRACE_HOURS=24
LICENSE_HEARTBEAT_INTERVAL_MINUTES=15

Configuration File Format

# .bzzz/config.yaml
license:
  server_url: "https://kaching.chorus.services"
  license_key: "BZZZ-2025-ABC123-XYZ"
  cluster_id: "bzzz-cluster-abc123-walnut"
  email: "customer@example.com"
  organization_name: "Example Corp"
  license_type: "standard"
  max_nodes: 10
  features:
    - "basic-coordination" 
    - "task-distribution"
    - "advanced-ai-integration"
  expires_at: "2025-12-31T23:59:59Z"
  validated_at: "2025-09-01T10:30:00Z"
  terms_accepted_at: "2025-09-01T10:29:45Z"

Testing Strategy

Unit Tests Required

  • License configuration validation
  • Feature gate functionality
  • Heartbeat worker logic
  • Error handling scenarios

Integration Tests Required

  • End-to-end setup flow with real KACHING server
  • License activation/heartbeat/deactivation cycle
  • License suspension handling
  • Offline grace period behavior
  • Node count enforcement

Security Tests

  • License tampering detection
  • Token validation and expiry
  • Cluster ID spoofing protection
  • Network failure graceful degradation

Success Criteria

Phase 2A Success

  • License data properly saved during setup (no longer discarded)
  • Runtime configuration includes complete license information
  • Setup process generates and persists cluster ID

Phase 2B Success

  • Mock validation completely removed
  • Real license validation against KACHING server
  • License activation works end-to-end with cluster binding

Phase 2C Success

  • BZZZ refuses to start without valid license
  • Heartbeat worker maintains license token
  • License suspension stops BZZZ operations immediately
  • Clean deactivation on shutdown

Phase 2D Success

  • Features properly gated based on license tier
  • Node count enforcement prevents over-provisioning
  • Clear error messages for license violations

Overall Success

  • Zero unlicensed usage possible - system fails closed
  • License sharing across clusters prevented
  • Real-time license enforcement (suspend works immediately)
  • Comprehensive audit trail of license usage

Security Considerations

  1. License Key Protection: Store license keys securely, never log them
  2. Token Security: JWT tokens stored in memory only, never persisted
  3. Cluster ID Integrity: Generate cryptographically secure cluster IDs
  4. Audit Logging: All license operations logged for compliance
  5. Fail-Closed Design: System stops on license violations rather than degrading

Dependencies

  • KACHING Phase 1 Complete: Requires functioning license server
  • Database Migration: May require config schema updates for existing deployments
  • Documentation Updates: Update setup guides and admin documentation

Deployment Strategy

  1. Backward Compatibility: Existing BZZZ instances must upgrade gracefully
  2. Migration Path: Convert existing configs to include license requirements
  3. Rollback Plan: Ability to temporarily disable license enforcement if needed
  4. Monitoring: Comprehensive metrics for license validation success/failure rates

This plan transforms BZZZ from having zero license enforcement to comprehensive revenue protection integrated with KACHING license authority.




All Licensing Work Committed and Pushed!

Successfully Pushed to GITEA:

🔐 KACHING: feature/license-authority-server

BZZZ: feature/licensing-enforcement

🌐 WHOOSH: feature/license-gating-integration

Business Impact Summary:

Before: $0 recurring revenue protection - unlimited free usage across all products

After: Comprehensive license enforcement ecosystem with:

  • Real-time license control and immediate suspension capability
  • Prevention of license sharing through cluster binding
  • Automated upselling through intelligent feature gating
  • Complete operational tooling for license management
  • Production-ready security with Ed25519 cryptography

All work is properly versioned, comprehensively documented, and ready for integration testing and production deployment. The foundation for sustainable recurring revenue is now in place!