| 27 | } |
| 28 | |
| 29 | func (e *BasicEngine) Evaluate(_ context.Context, req types.AIRequest) (types.PolicyDecision, error) { |
| 30 | tenant, ok := e.cfg.TenantByID(req.TenantID) |
| 31 | if !ok { |
| 32 | return types.PolicyDecision{ |
| 33 | Allowed: false, |
| 34 | Reason: "unknown tenant", |
| 35 | }, nil |
| 36 | } |
| 37 | |
| 38 | normalized := req |
| 39 | if normalized.Priority == "" { |
| 40 | normalized.Priority = tenant.Priority |
| 41 | } |
| 42 | normalized = types.NormalizeAIRequest(normalized) |
| 43 | |
| 44 | if strings.EqualFold(strings.TrimSpace(normalized.RequestType), types.RequestTypeAgent) && !e.cfg.Features.AgentEnabled { |
| 45 | return types.PolicyDecision{ |
| 46 | Allowed: false, |
| 47 | Reason: "agent requests are disabled (set features.agent_enabled=true to allow policy checks)", |
| 48 | Normalized: normalized, |
| 49 | }, nil |
| 50 | } |
| 51 | |
| 52 | if err := validateAgentTenantLimits(normalized, tenant); err != nil { |
| 53 | return types.PolicyDecision{ |
| 54 | Allowed: false, |
| 55 | Reason: err.Error(), |
| 56 | Normalized: normalized, |
| 57 | }, nil |
| 58 | } |
| 59 | |
| 60 | estimated := estimateBudgetConsumption(normalized) |
| 61 | if tenant.BudgetPerRequest > 0 && estimated > tenant.BudgetPerRequest { |
| 62 | return types.PolicyDecision{ |
| 63 | Allowed: false, |
| 64 | Reason: fmt.Sprintf("budget exceeded: estimated=%.2f budget=%.2f", estimated, tenant.BudgetPerRequest), |
| 65 | Normalized: normalized, |
| 66 | }, nil |
| 67 | } |
| 68 | |
| 69 | if !e.allowByRateLimit(tenant.ID, tenant.RateLimitRPS) { |
| 70 | return types.PolicyDecision{ |
| 71 | Allowed: false, |
| 72 | Reason: "rate limit exceeded", |
| 73 | Normalized: normalized, |
| 74 | }, nil |
| 75 | } |
| 76 | |
| 77 | return types.PolicyDecision{ |
| 78 | Allowed: true, |
| 79 | Reason: "allowed", |
| 80 | Normalized: normalized, |
| 81 | }, nil |
| 82 | } |
| 83 | |
| 84 | func (e *BasicEngine) allowByRateLimit(tenantID string, limit int) bool { |
| 85 | if limit <= 0 { |