# 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**: ```go // 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**: ```go // 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 ```go // 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 ```go // 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: ```go // 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)**: ```go // ❌ REMOVE: Hardcoded mock validation validLicenseKey := "BZZZ-2025-DEMO-EVAL-001" if licenseRequest.LicenseKey != validLicenseKey { // ... return error ... } ``` **New KACHING Integration**: ```go // ✅ 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 ```go func generateClusterID() string { // Generate unique cluster identifier // Format: bzzz-cluster-- 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)**: ```go 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 ```go 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 ```go 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 ```go 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 ```go // 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. ``` #### 6. Response Handling Map (recommended) - 200 OK (heartbeat): update token, token_version - 403 Forbidden: suspended/cancelled → fail closed, stop operations - 409 Conflict: cluster slot in use → backoff and re‑activate 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 per‑job 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 ```go 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 ```go 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 ```bash # 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 ```yaml # .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.