Files
bzzz/LICENSING_DEVELOPMENT_PLAN.md
anthonyrawlins c8c5e918d5 feat: Implement comprehensive license enforcement and revenue protection
CRITICAL REVENUE PROTECTION: Fix $0 recurring revenue by enforcing BZZZ licensing

This commit implements Phase 2A license enforcement, transforming BZZZ from having zero
license validation to comprehensive revenue protection integrated with KACHING license authority.

KEY BUSINESS IMPACT:
• PREVENTS unlimited free usage - BZZZ now requires valid licensing to operate
• ENABLES real-time license control - licenses can be suspended immediately via KACHING
• PROTECTS against license sharing - unique cluster IDs bind licenses to specific deployments
• ESTABLISHES recurring revenue foundation - licensing is now technically enforced

CRITICAL FIXES:
1. Setup Manager Revenue Protection (api/setup_manager.go):
   - FIXED: License data was being completely discarded during setup (line 2085)
   - NOW: License data is extracted, validated, and saved to configuration
   - IMPACT: Closes $0 recurring revenue loophole - licenses are now required for deployment

2. Configuration System Integration (pkg/config/config.go):
   - ADDED: Complete LicenseConfig struct with KACHING integration fields
   - ADDED: License validation in config validation pipeline
   - IMPACT: Makes licensing a core requirement, not optional

3. Runtime License Enforcement (main.go):
   - ADDED: License validation before P2P node initialization (line 175)
   - ADDED: Fail-closed design - BZZZ exits if license validation fails
   - ADDED: Grace period support for offline operations
   - IMPACT: Prevents unlicensed BZZZ instances from starting

4. KACHING License Authority Integration:
   - REPLACED: Mock license validation (hardcoded BZZZ-2025-DEMO-EVAL-001)
   - ADDED: Real-time KACHING API integration for license activation
   - ADDED: Cluster ID generation for license binding
   - IMPACT: Enables centralized license management and immediate suspension

5. Frontend License Validation Enhancement:
   - UPDATED: License validation UI to indicate KACHING integration
   - MAINTAINED: Existing UX while adding revenue protection backend
   - IMPACT: Users now see real license validation, not mock responses

TECHNICAL DETAILS:
• Version bump: 1.0.8 → 1.1.0 (significant license enforcement features)
• Fail-closed security design: System stops rather than degrading on license issues
• Unique cluster ID generation prevents license sharing across deployments
• Grace period support (24h default) for offline/network issue scenarios
• Comprehensive error handling and user guidance for license issues

TESTING REQUIREMENTS:
• Test that BZZZ refuses to start without valid license configuration
• Verify license data is properly saved during setup (no longer discarded)
• Test KACHING integration for license activation and validation
• Confirm cluster ID uniqueness and license binding

DEPLOYMENT IMPACT:
• Existing BZZZ deployments will require license configuration on next restart
• Setup process now enforces license validation before deployment
• Invalid/missing licenses will prevent BZZZ startup (revenue protection)

This implementation establishes the foundation for recurring revenue by making
valid licensing technically required for BZZZ operation.

🚀 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-01 10:20:33 +10:00

16 KiB
Raw 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.