package licensing import ( "bytes" "context" "encoding/json" "fmt" "net/http" "time" ) const ( DefaultKachingURL = "http://localhost:8083" // For development testing LicenseTimeout = 30 * time.Second ) // LicenseConfig holds licensing information type LicenseConfig struct { LicenseID string ClusterID string KachingURL string } // Validator handles license validation with KACHING // Enhanced with license gate for burst-proof validation type Validator struct { config LicenseConfig kachingURL string client *http.Client gate *LicenseGate // New: License gate for scaling support } // NewValidator creates a new license validator with enhanced scaling support func NewValidator(config LicenseConfig) *Validator { kachingURL := config.KachingURL if kachingURL == "" { kachingURL = DefaultKachingURL } validator := &Validator{ config: config, kachingURL: kachingURL, client: &http.Client{ Timeout: LicenseTimeout, }, } // Initialize license gate for scaling support validator.gate = NewLicenseGate(config) return validator } // Validate performs license validation with KACHING license authority // Enhanced with caching, circuit breaker, and lease token support func (v *Validator) Validate() error { return v.ValidateWithContext(context.Background()) } // ValidateWithContext performs license validation with context and agent ID func (v *Validator) ValidateWithContext(ctx context.Context) error { if v.config.LicenseID == "" || v.config.ClusterID == "" { return fmt.Errorf("license ID and cluster ID are required") } // Use enhanced license gate for validation agentID := "default-agent" // TODO: Get from config/environment if err := v.gate.Validate(ctx, agentID); err != nil { // Fallback to legacy validation for backward compatibility fmt.Printf("⚠️ License gate validation failed, trying legacy validation: %v\n", err) return v.validateLegacy() } return nil } // validateLegacy performs the original license validation (for fallback) func (v *Validator) validateLegacy() error { // Prepare validation request request := map[string]interface{}{ "license_id": v.config.LicenseID, "cluster_id": v.config.ClusterID, "metadata": map[string]string{ "product": "CHORUS", "version": "0.1.0-dev", "container": "true", }, } requestBody, err := json.Marshal(request) if err != nil { return fmt.Errorf("failed to marshal license request: %w", err) } // Call KACHING license authority licenseURL := fmt.Sprintf("%s/v1/license/activate", v.kachingURL) resp, err := v.client.Post(licenseURL, "application/json", bytes.NewReader(requestBody)) if err != nil { // FAIL-CLOSED: No network = No license = No operation return fmt.Errorf("unable to contact license authority: %w", err) } defer resp.Body.Close() // Parse response var licenseResponse map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&licenseResponse); err != nil { return fmt.Errorf("invalid license authority response: %w", err) } // Check validation result if resp.StatusCode != http.StatusOK { message := "license validation failed" if msg, ok := licenseResponse["message"].(string); ok { message = msg } return fmt.Errorf("license validation failed: %s", message) } // License is valid return nil } // ValidateBackground performs background license validation (for runtime checks) // This is used for periodic license validation during operation func (v *Validator) ValidateBackground() error { // Similar to Validate() but with longer timeout and retry logic // Implementation would include retry logic and graceful degradation return v.Validate() }