18 KiB
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.golines 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.
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
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 structapi/setup_manager.go- Add license to SetupConfig, fix save processmain.go- Add license validation to startup sequence
New License Client Files
pkg/license/client.go- KACHING API clientpkg/license/heartbeat.go- Background heartbeat workerpkg/license/features.go- Feature gate implementationpkg/license/validation.go- Runtime license validation
UI Integration
- Update
install/config-ui/app/setup/components/LicenseValidation.tsxto 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
- License Key Protection: Store license keys securely, never log them
- Token Security: JWT tokens stored in memory only, never persisted
- Cluster ID Integrity: Generate cryptographically secure cluster IDs
- Audit Logging: All license operations logged for compliance
- 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
- Backward Compatibility: Existing BZZZ instances must upgrade gracefully
- Migration Path: Convert existing configs to include license requirements
- Rollback Plan: Ability to temporarily disable license enforcement if needed
- 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
- Pull Request: https://gitea.chorus.services/tony/KACHING/pulls/new/feature/license-authority-server
- Complete license authority API with Ed25519 security
- Professional admin CLI tools with testing frameworks
⚡ BZZZ: feature/licensing-enforcement
- Pull Request: https://gitea.chorus.services/tony/bzzz/pulls/new/feature/licensing-enforcement
- Critical fix: Setup now saves license data (was discarded!)
- Runtime license enforcement prevents unlicensed usage
🌐 WHOOSH: feature/license-gating-integration
- Pull Request: https://gitea.chorus.services/tony/hive/pulls/new/feature/license-gating-integration
- License-aware UI with feature gating and upgrade optimization
- Secure backend proxy pattern implementation
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!