package validator import ( "fmt" "strings" "browser.local/platform/domain" ) func ValidateRunControlHello(hello domain.RunControlHello) error { var violations []string violations = appendRequired(violations, "registrationToken", hello.RegistrationToken) violations = appendRequired(violations, "runEndpointId", hello.RunEndpointID) violations = appendRequired(violations, "displayName", hello.DisplayName) violations = appendRequired(violations, "version", hello.Version) if hello.Architecture != "" { if !validDistributionTargetOS(hello.Platform) { violations = append(violations, "platform is invalid") } if !validDistributionTargetArch(hello.Architecture) { violations = append(violations, "architecture is invalid") } } if (hello.UpdateJobID == "") != (hello.UpdateOutcome == "") { violations = append(violations, "updateJobId and updateOutcome must be provided together") } if hello.UpdateOutcome != "" && hello.UpdateOutcome != "succeeded" && hello.UpdateOutcome != "rolled-back" { violations = append(violations, "updateOutcome is invalid") } violations = appendRequired(violations, "capabilityReport.fingerprint", hello.CapabilityReport.Fingerprint) if hello.ServerInstanceID != "" || hello.PluginID != "" || hello.ComponentKind != "" || hello.ComponentKey != "" || hello.KeyGeneration != 0 { violations = appendRequired(violations, "serverInstanceId", hello.ServerInstanceID) violations = appendRequired(violations, "pluginId", hello.PluginID) if hello.ComponentKind != domain.DistributionComponentRun && hello.ComponentKind != domain.DistributionComponentClientManager { violations = append(violations, "componentKind is invalid") } if hello.KeyGeneration <= 0 { violations = append(violations, "keyGeneration must be positive") } } if !validRunControlStatus(hello.Status) { violations = append(violations, "status is invalid") } violations = appendCapacityViolations(violations, hello.Capacity) violations = appendCapabilitiesViolations(violations, "capabilityReport.capabilities", hello.CapabilityReport.Capabilities) return finish(violations) } func ValidateRunControlHeartbeat(heartbeat domain.RunControlHeartbeat) error { var violations []string violations = appendRequired(violations, "runEndpointId", heartbeat.RunEndpointID) violations = appendRequired(violations, "sessionToken", heartbeat.SessionToken) violations = appendRequired(violations, "version", heartbeat.Version) violations = appendRequired(violations, "capabilityFingerprint", heartbeat.CapabilityFingerprint) if !validRunControlStatus(heartbeat.Status) { violations = append(violations, "status is invalid") } violations = appendCapacityViolations(violations, heartbeat.Capacity) return finish(violations) } func appendCapacityViolations(violations []string, capacity domain.RunCapacity) []string { if capacity.MaxJobs < 0 || capacity.RunningJobs < 0 || capacity.QueuedJobs < 0 || capacity.LogBacklogBatches < 0 || capacity.ArtifactBacklogChunks < 0 { violations = append(violations, "capacity counts must not be negative") } if capacity.MaxJobs > 0 && capacity.RunningJobs > capacity.MaxJobs { violations = append(violations, "runningJobs must not exceed maxJobs") } for i, code := range capacity.PressureCodes { if strings.TrimSpace(code) == "" || strings.ContainsAny(code, " \t\r\n") || containsUnsafeRuntimeSecret(code) || looksLikeRawHostPath(code) { violations = append(violations, fmt.Sprintf("pressureCodes[%d] is invalid", i)) } } return violations } func appendCapabilitiesViolations(violations []string, field string, capabilities []string) []string { if len(capabilities) == 0 { violations = append(violations, field+" must not be empty") } for i, capability := range capabilities { if strings.TrimSpace(capability) == "" { violations = append(violations, fmt.Sprintf("%s[%d] is required", field, i)) } } return violations } func validRunControlStatus(status domain.RunEndpointStatus) bool { switch status { case domain.RunEndpointStatusOnline, domain.RunEndpointStatusDegraded, domain.RunEndpointStatusOffline: return true default: return false } }