package executionpool import ( "errors" "math" "sort" "strings" "time" ) const ( defaultHealthMaxAge = 45 * time.Second defaultMinimumDwell = 2 * time.Minute defaultSwitchImprovement = 0.20 defaultBetterWindowCount = 3 ) var ErrNoEligiblePool = errors.New("no eligible execution pool") type SelectionCandidate struct { Pool ExecutionPool Health RouteHealth Capacity CapacitySnapshot CapabilityMatched bool BetterWindows int } type SelectionRequest struct { Candidates []SelectionCandidate CurrentPoolID string CurrentPoolSince time.Time Now time.Time } type Decision struct { PoolID string Score float64 Reason string Components map[string]float64 Rejected map[string]string } type Selector struct { HealthMaxAge time.Duration MinimumDwell time.Duration SwitchImprovement float64 BetterWindows int } func NewSelector() Selector { return Selector{ HealthMaxAge: defaultHealthMaxAge, MinimumDwell: defaultMinimumDwell, SwitchImprovement: defaultSwitchImprovement, BetterWindows: defaultBetterWindowCount, } } type scoredCandidate struct { candidate SelectionCandidate score float64 components map[string]float64 } func (s Selector) Select(request SelectionRequest) (Decision, error) { now := request.Now if now.IsZero() { now = time.Now() } if s.HealthMaxAge <= 0 { s.HealthMaxAge = defaultHealthMaxAge } if s.MinimumDwell <= 0 { s.MinimumDwell = defaultMinimumDwell } if s.SwitchImprovement <= 0 { s.SwitchImprovement = defaultSwitchImprovement } if s.BetterWindows <= 0 { s.BetterWindows = defaultBetterWindowCount } rejected := make(map[string]string) scored := make([]scoredCandidate, 0, len(request.Candidates)) for _, candidate := range request.Candidates { if reason := rejectionReason(candidate, now, s.HealthMaxAge); reason != "" { rejected[candidate.Pool.ID] = reason continue } network := routeQuality(candidate.Health) queue := queueQuality(candidate.Capacity) resources := clamp01(candidate.Capacity.ResourceHeadroom) stability := clamp01(candidate.Capacity.Stability) components := map[string]float64{ "network": network, "queue": queue, "resources": resources, "stability": stability, } scored = append(scored, scoredCandidate{ candidate: candidate, score: 0.55*network + 0.25*queue + 0.15*resources + 0.05*stability, components: components, }) } if len(scored) == 0 { return Decision{Rejected: rejected}, ErrNoEligiblePool } sort.SliceStable(scored, func(i, j int) bool { if math.Abs(scored[i].score-scored[j].score) > 0.000001 { return scored[i].score > scored[j].score } return scored[i].candidate.Pool.ID < scored[j].candidate.Pool.ID }) selected := scored[0] if currentIndex := indexOfPool(scored, strings.TrimSpace(request.CurrentPoolID)); currentIndex >= 0 && currentIndex != 0 { current := scored[currentIndex] withinDwell := !request.CurrentPoolSince.IsZero() && now.Sub(request.CurrentPoolSince) < s.MinimumDwell improvement := relativeImprovement(selected.score, current.score) if withinDwell || improvement < s.SwitchImprovement || selected.candidate.BetterWindows < s.BetterWindows { selected = current } } return Decision{ PoolID: selected.candidate.Pool.ID, Score: selected.score, Reason: "network_quality_then_safe_capacity", Components: selected.components, Rejected: rejected, }, nil } func rejectionReason(candidate SelectionCandidate, now time.Time, maxAge time.Duration) string { if strings.TrimSpace(candidate.Pool.ID) == "" { return "invalid_pool" } if candidate.Pool.State != PoolActive { return "pool_not_active" } if !candidate.CapabilityMatched { return "capability_mismatch" } if candidate.Health.State == RouteUnreachable { return "route_unreachable" } if candidate.Health.State == RouteUnknown || candidate.Health.State == "" { return "route_unknown" } if candidate.Health.SampledAt.IsZero() || now.Sub(candidate.Health.SampledAt) > maxAge || (!candidate.Health.ExpiresAt.IsZero() && !now.Before(candidate.Health.ExpiresAt)) { return "route_stale" } if candidate.Capacity.Critical { return "resource_critical" } if candidate.Capacity.WorkerCount < 1 { return "no_worker" } if candidate.Capacity.SafeCapacity-candidate.Capacity.ActiveTasks < 1 { return "capacity_exhausted" } return "" } func routeQuality(health RouteHealth) float64 { success := clamp01(health.SuccessRate) connect := latencyQuality(health.ConnectTLSP95, 100*time.Millisecond) firstByte := latencyQuality(health.FirstByteP95, 250*time.Millisecond) throughput := clamp01(health.UploadBytesPerSecond / (10 * 1024 * 1024)) jitter := latencyQuality(health.JitterP95, 100*time.Millisecond) return 0.35*success + 0.25*connect + 0.25*firstByte + 0.10*throughput + 0.05*jitter } func queueQuality(capacity CapacitySnapshot) float64 { available := capacity.SafeCapacity - capacity.ActiveTasks if available <= 0 { return 0 } headroom := float64(available) / float64(max(capacity.SafeCapacity, 1)) waitPenalty := 1 / (1 + capacity.EstimatedQueueWait.Seconds()) return clamp01(0.6*headroom + 0.4*waitPenalty) } func latencyQuality(value, target time.Duration) float64 { if value <= 0 { return 0 } return clamp01(1 / (1 + float64(value)/float64(target))) } func relativeImprovement(candidate, current float64) float64 { if current <= 0 { return 1 } return (candidate - current) / current } func indexOfPool(candidates []scoredCandidate, poolID string) int { for index := range candidates { if candidates[index].candidate.Pool.ID == poolID { return index } } return -1 } func clamp01(value float64) float64 { return math.Max(0, math.Min(1, value)) }