Conversation
…ring - Add HealthKeyAuthorizationHandler for private network and key-based auth - Add HealthEndpointAuthorizationMiddleware returning 404 for unauthorized access - Support CONDUIT_HEALTH_MONITORING_KEY env var with X-Conduit-Health-Key header - Allow full health access from private networks (10.x, 172.16-31.x, 192.168.x) - Secure SignalRHealthController endpoints via middleware - Simplify WebAdmin health response to minimal status - Add 34 unit tests for handler and middleware - Update docs with BetterStack configuration guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract shared useMediaInterface hook for model discovery and parameters - Unify RetryHistoryEntry type usage in ImageTask (import shared type) - Standardize error handling in video generation callbacks - Refactor ImageInterface and VideoInterface to use shared hook Closes #832 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…tion, caching, failover, and request pipeline.
- SignalR: 8.0.17 → 10.0.0 - @microsoft/signalr-protocol-msgpack: 8.0.17 → 10.0.0 - @types/node: 24.3.0 → 25.0.3 - TypeScript: 5.9.2 → 5.9.3 - tsup: 8.5.0 → 8.5.1
- SignalR: 8.0.17 → 10.0.0 - All dev dependencies updated to latest: - @types/node: 24.3.0 → 25.0.3 - TypeScript: 5.9.2 → 5.9.3 - ESLint/TypeScript ESLint tooling to latest - Jest: 30.0.4 → 30.2.0 - Prettier: 3.6.2 → 3.7.4
- SignalR: 8.0.17 → 10.0.0 - All dev dependencies updated to latest - Fixed ESLint issues: consolidated imports and optional chain preference
Major updates: - SignalR: 9.0.6 → 10.0.0 (aligned with SDKs) - Next.js: 16.0.10 → 16.1.1 - Mantine suite: 8.1.2 → 8.3.10 (all packages) - uuid: 11.1.0 → 13.0.0 (ESM-only) - react-syntax-highlighter: 15.6.1 → 16.1.0 - stylelint-config-standard: 36.0.0 → 39.0.1 - stylelint-order: 6.0.4 → 7.0.1 - TypeScript: 5.8.3 → 5.9.3 - zod: 4.0.5 → 4.3.4 - @clerk/nextjs: 6.36.3 → 6.36.5 - @tanstack/react-query: 5.0.0 → 5.90.16 - zustand: 5.0.6 → 5.0.9 - jest: 30.0.4 → 30.2.0 - @playwright/test: 1.54.1 → 1.57.0 - All other dependencies updated to latest versions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add AdminControllerBase with ExecuteAsync/ExecuteWithNotFoundAsync for consistent error handling across Admin API controllers - Add async versions of ILLMClientFactory methods (GetClientAsync, etc.) to eliminate blocking Task.Run().Result anti-pattern - Add GetTopEnabledAsync to VirtualKeyRepository for optimized queries - Configure FileRetrievalService with retry policy and proper HttpClient - Fix async void in SignalRMessageQueueService to use proper async Task - Add comprehensive unit tests for AdminControllerBase (22 tests)
…infrastructure Consolidate duplicated code across Admin and Gateway APIs to reduce maintenance burden and improve consistency. Phase 1 - SecurityOptions consolidation: - Create SecurityOptionsBase with shared IP filtering, rate limiting, failed auth, and security headers options - Create AdminSecurityOptions and GatewaySecurityOptions with API-specific extensions (ApiAuth for Admin, VirtualKey for Gateway) - Shared configuration loader with environment variable parsing Phase 2 - SecurityHeadersMiddleware consolidation: - Create generic SecurityHeadersMiddleware<TOptions> in shared library - Admin/Gateway middleware files now delegate to shared implementation Phase 3 - SecurityMiddleware base class: - Create SecurityMiddlewareBase with template method pattern - Admin/Gateway inherit base and add API-specific behavior - Gateway adds event monitoring via OnSecurityViolationAsync override Phase 4 - SignalRNotificationServiceBase: - Create generic base class with optional resilience policies - Provides SendToGroupAsync, SendToAllAsync helper methods - Refactor SystemNotificationService and VideoGenerationNotificationService Benefits: - ~750 lines of duplicated code eliminated - Single source of truth for security configuration - Consistent error handling patterns across services - Optional resilience policies for SignalR notifications
…actory Remove synchronous wrapper methods that used .GetAwaiter().GetResult() to eliminate thread blocking and potential deadlock risks. Changes: - Remove GetClient, GetClientByProviderId, GetClientByProviderType sync methods from ILLMClientFactory interface and implementations - Change IConduit.GetClient to async GetClientAsync - Change ISignalRMessageBatcher.GetStatistics to async GetStatisticsAsync - Update all callers to use async methods with await - Refactor SignalROpenTelemetryService timer callback to fire-and-forget async pattern - Update test mocks to use async method signatures
…cross repositories
…e files Delete facade middleware files in Gateway and Admin that simply delegated to the shared ConduitLLM.Security.Middleware implementation. Both services now use the shared extension methods directly (UseGatewaySecurityHeaders, UseAdminSecurityHeaders).
Replace individual Redis StringIncrementAsync calls with local buffering using thread-safe Interlocked operations. Statistics are flushed to Redis every 5 seconds via a batched pipeline, eliminating 2-3 Redis round-trips per cache operation. - Add StatisticsBuffer class with atomic GetAndReset - Implement IDisposable with final flush on shutdown - Update GetStatsAsync to include pending buffered values - Use Interlocked.Increment/Add for all stat updates
…entFactory Prevents socket exhaustion under high load by using proper connection pooling. - Add IImageDownloadService with ImageDownloadService implementation - Add HttpClient overloads to static methods in ContentParts and ImageUtility - Mark original static methods as [Obsolete] with migration guidance - Add warning logs when clients fall back to direct HttpClient creation - Register named HttpClients for ExternalImageFetch, Exa, and Tavily providers
…extension method Add AddProviderResiliencePolicies<TClient>() to consolidate timeout and retry policy configuration that was duplicated across OpenAI, Groq, and MiniMax client registrations, reducing ~102 lines to ~10 lines.
…sed processing - AlertBatchingService: Convert to Channel-based work queue processed in ExecuteAsync loop with proper error handling and PeriodicTimer - SignalRAcknowledgmentService: Replace per-message Task.Run with periodic timer scan to prevent memory leaks under high load - SignalRMessageBatcher: Add Channel-based signaling for batch operations with dedicated processing task and graceful shutdown These changes prevent silent exception swallowing, memory leaks from unbounded Task creation, and ensure proper graceful shutdown handling.
Implement hybrid local + distributed locking to prevent cache stampede when cache entries expire. When multiple requests hit a cache miss simultaneously, only one performs the database query while others wait. - Add IDistributedCachePopulator interface and DistributedCachePopulator implementation with triple-check pattern (cache -> local lock -> distributed lock) - Update RedisModelCostCache and RedisProviderCache to use stampede prevention for database fallback operations - Fix CacheManager.AcquireLockAsync bug that created new SemaphoreSlim per call (defeating locking purpose) - now uses ConcurrentDictionary for per-key semaphores with cleanup to prevent memory leaks Performance impact: 100 concurrent cache misses for same key now result in 1 database query instead of 100.
…tionToResponseMapper Extract duplicated exception handling logic from ControllerErrorExtensions and AdminControllerBase into a shared ExceptionToResponseMapper class. This provides a single source of truth for mapping exceptions to HTTP responses, adding support for custom Conduit exceptions (AuthorizationException, ModelNotFoundException, InvalidRequestException, RateLimitExceededException, ServiceUnavailableException, ConfigurationException).
…ase using template method pattern Extract common batch processing logic from BillingAuditService, PricingAuditService, FunctionCallAuditService, and RequestLogService into a shared abstract base class. This reduces code duplication (~400 lines) and ensures consistent behavior across all audit services including batched writes, timer-based flushing, and data retention. Key changes: - Add IAuditEvent interface for timestamp-based retention cleanup - Create BatchAuditServiceBase<TEvent> with template method pattern - Convert RequestLogService from direct DB writes to batch processing - Register RequestLogService as leader-elected hosted service
… consolidating common functionality
…on for stats reset time; mark synchronous methods in SignalRConnectionMonitor as obsolete; refactor S3MediaStorageService to defer bucket initialization; enhance TiktokenCounter with async encoding retrieval; add async method to IFunctionClientFactory; improve FunctionClientFactory to use async client retrieval; implement async signing in AwsSignatureV4
Replace inefficient LINQ patterns where Count() is used for existence checks with Any() which short-circuits on first element found. Changes: - Replace .Count() == 0 with !.Any() - Replace .Count() > 0 with .Any() - Replace .Count() != 0 with .Any() - Replace .Count() >= 1 with .Any() Also fixes logic bug in SecurityEventMonitoringService.Analysis.cs where condition Count() == 0 && Count() > threshold was impossible to satisfy. Affected areas: repositories, services, controllers, providers, caching, security monitoring, batch operations, and integration tests.
- Added GetPaginatedAsync methods to ModelProviderMappingRepository, NotificationRepository, ProviderKeyCredentialRepository, ProviderRepository, RequestLogRepository, VirtualKeyGroupRepository, and VirtualKeyRepository for efficient data retrieval. - Deprecated GetAllAsync methods in the above repositories with warnings to use the new paginated methods. - Introduced GetByProviderPaginatedAsync and GetByModelPaginatedAsync methods for specific filtering and pagination. - Updated tests to reflect changes in repository methods and ensure correct functionality. - Modified frontend components to handle paginated data responses. - Added VirtualKeyCountsDto for tracking virtual key counts by status.
Replace duplicated JsonSerializerOptions with inherited DefaultJsonOptions in Images and Videos, add shared CaseInsensitiveJsonOptions for Chat, and fix ConfigureHttpClient to call base class for auth/headers.
Extract shared RefreshAndInvalidateAsync from 3 identical Consume methods (Created/Updated/Deleted). Updated handler conditionally invalidates discovery cache only when IsEnabled/IsActive changed.
Add ValidateDateRange and ValidateTimeframe to ControllerErrorExtensions. Replace 7 inline date range checks and 3 timeframe checks across BillingAuditController, PricingController, and AnalyticsController.
The ConduitLLM.Tests.Http.TestBase was never inherited — all Gateway tests use the root ConduitLLM.Tests.TestBase. Remove dead code.
Move GlobalSettingsCacheService, ProviderService, ModelProviderMappingService (with caching decorator), and ProviderMetadataRegistry into a single AddSharedApplicationServices() in Core extensions. Both Admin and Gateway now call this instead of maintaining duplicate inline registrations.
Add LogAdminAuditBulk and LogAdminAuditStateChange helpers to AdminControllerBase. Fix sanitization gaps where user-provided strings (names, reasons) were passed to audit logs without LoggingSanitizer.S(). Add missing entity detail to bare Create/Delete/Update audit calls across 13 controllers so the audit trail identifies which entity was affected. Standardize bulk operation format using the new helper.
…ndings The shebang was removed in a59c821, breaking git push on Windows/WSL/Linux. Add .gitattributes to enforce LF line endings for hooks and shell scripts.
Introduces provider-agnostic helper that encapsulates the submit-poll-extract loop common to Replicate predictions and MiniMax video generation. Callers supply fetchStatus/classify/extractSuccess/extractFailure delegates plus PollingOptions (fixed vs exponential backoff, timeout, consecutive-error tolerance). Supports optional onProgress and onAbort hooks for SignalR progress emission and best-effort remote job cancellation. Covered by 17 unit tests including success paths, timeout, cancellation, rate limiting, transient-error thresholds, exponential backoff bounds, and progress-callback exception swallowing.
Replaces 155-line PollPredictionUntilCompletedAsync loop with a call to AsyncJobPoller.PollAsync plus three focused helpers (FetchPredictionStatusAsync, ClassifyPredictionStatus, ExtractPredictionFailure). Uses fixed backoff and fail-fast transient-error handling to match prior behavior. Best-effort remote cancellation is wired through onAbort. ClassifyReplicatePredictionError is preserved verbatim and invoked via extractFailure. Also removes unused yieldProgress parameter (dead code — declared but never referenced in the method body) and its one call site in ReplicateClient.Chat.
Replaces 275-line inline polling loop in CreateVideoAsync with AsyncJobPoller.PollAsync plus extracted helpers: FetchVideoStatusAsync, ClassifyVideoStatus, and ResolveVideoPollingTimeoutSeconds. Uses exponential backoff with jitter and tolerates up to 3 consecutive transient errors, matching prior behavior. HTTP 429 responses are synthesized as MiniMax error code 1013 so the classifier handles both transport-level and application-level rate limits uniformly as JobState.RateLimited. SignalR progress reporting is preserved via the onProgress callback, with the polling helper's exception swallowing replacing the manual try/catch around _progressCallback invocations. Side effect: JSON deserialization failures in video status responses now count as transient errors (previously logged and silently retried forever).
…ient CustomProviderClient was an abstract base with a single subclass (Replicate) that used only 3 of its 7 members. The XML doc described it as the template for "Anthropic, Cohere, Gemini" — none of which exist in this codebase. The other non-OpenAI-shape provider (MiniMax) already skipped it and inherits from BaseLLMClient directly. Relocates the 3 used pieces into ReplicateClient: - BaseUrl field (resolved in constructor via registry + provider fallback) - ValidateRequest<T> override for Messages/Input/Prompt null checks - CreateChatCompletionChunk helper (now private static, unused originalModelAlias parameter dropped) Deletes 4 dead members (DefaultSerializerOptions, ExtractErrorDetails, CreateChatCompletionResponse, and the ConfigureHttpClient override that Replicate already fully shadowed). Net: ~225 lines removed, hierarchy flattens to BaseLLMClient → ReplicateClient matching MiniMax's structure. No behavior change.
Provider client instrumentation was uneven: only Groq logged token counts,
none of the 13 providers emitted metrics or spans, no provider opened
Logger.BeginScope, and ProviderErrorInfo.RequestId was a serialized
headers dump instead of a real correlation ID.
This adds a shared ActivitySource + Meter ("ConduitLLM.Providers") and
wires it through BaseLLMClient + the streaming entry points so all
providers get spans, metrics (requests, errors, duration, tokens, stream
chunks, first-chunk latency), and BeginScope correlation tags uniformly.
IProviderErrorTrackingService is now resolved via GetRequiredService and
its RequestId is populated from HttpContext/Activity, making provider
failures traceable end-to-end through the W3C trace context.
There was a problem hiding this comment.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
The {{branch}} placeholder in `type=sha,prefix={{branch}}-` resolved to
empty for fork-origin pull_request events, producing an invalid tag like
`:-5176bfd`. Disable that rule on PR events and emit a `pr-<num>-<sha>`
tag instead so PR builds still get a sha-pinned reference.
| email: (value: string | undefined) => { | ||
| if (!value?.trim()) return null; | ||
| const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | ||
| return emailRegex.test(value) ? null : 'Must be a valid email address'; |
Comment on lines
+32
to
+33
| const regexPattern = pattern | ||
| .replace(/\./g, '\\.') |
| } else if (pattern.includes('gpt-4')) { | ||
| examples.push('openai/gpt-4', 'openai/gpt-4-turbo', 'openai/gpt-4-32k'); | ||
| } else { | ||
| examples.push(`${pattern.replace('*', 'model-1')}`, `${pattern.replace('*', 'model-2')}`); |
| } else if (pattern.includes('gpt-4')) { | ||
| examples.push('openai/gpt-4', 'openai/gpt-4-turbo', 'openai/gpt-4-32k'); | ||
| } else { | ||
| examples.push(`${pattern.replace('*', 'model-1')}`, `${pattern.replace('*', 'model-2')}`); |
| export function isValidEmail(value: unknown): value is string { | ||
| if (!isNonEmptyString(value)) return false; | ||
| const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | ||
| return emailRegex.test(value); |
Update GitHub repo URLs, Docker registry paths (ghcr.io/nickna/conduit-*), and NuGet/.NET package metadata to reflect the nickna/Conduit owner. The @knn_labs npm scope is preserved — npm publishing is unchanged. Also fix the WebAdmin footer LICENSE link to point at the master branch.
Adds a PollingScope primitive on top of the OTEL standardization landed in 0bc96a7, emitting provider.poll.{duration,attempts,transient_errors, outcomes} for long-running predictions on Replicate and MiniMax. Wires the scope through AsyncJobPoller so attempt count, transient errors, and terminal outcome (Succeeded/Failed/Timeout/Cancelled) are reported without each provider duplicating the bookkeeping. Also drops the hand-written try/catch in Replicate's CreateImage and CreateVideo in favor of ExecuteApiRequestAsync, matching the rest of the client.
Architectural pass over the Chat/Images/Videos controllers — they were
designed independently and accumulated mechanically incidental divergences
across virtual-key extraction, auth attributes, async-task orchestration,
and usage-tracking contracts. Plus activation of per-virtual-key rate
limiting that was wired but never enforced (three implementations existed,
none ran).
## Controller architecture
- Add CurrentVirtualKeyId / CurrentVirtualKey accessors on
GatewayControllerBase; migrate three different extraction patterns
(claim parse, claim+Items mix, full-entity fetch) to one. Replace
JSON-roundtrip ownership checks in ImagesController.Async with the
typed task.Metadata?.VirtualKeyId access VideosController already used.
- Standardize the auth attribute stack on Chat/Images to match Videos:
[Authorize(AuthenticationSchemes = "VirtualKey")] + [RequireBalance].
Pinning the scheme matches the established pattern used by
AuthController/CompletionsController/EmbeddingsController and prevents
silent pivots if the default scheme ever changes.
- Replace string-keyed HttpContext.Items entries for image/video usage
tracking (ImageRequestModel/Quality/Size/N, VideoRequestModel/Size/
Duration/Fps/Style/N/PricingParameters) with a typed IUsageContext —
ImageUsageContext / VideoUsageContext stored under an internal sentinel
key. Catches drift at compile time; surfaces the previously-silent gap
where the middleware read "ImageRequestStyle" but no controller wrote
it.
- Collapse VideoGenerationService (4 partial files, ~600 lines including
a dead reflection-based sync path with no production callers) into the
orchestrator pattern Images already used. The controller now calls
IAsyncTaskService.CreateTaskAsync + PublishEventFireAndForget directly;
VideoGenerationOrchestrator handles generation as the MassTransit
consumer. Removes the "pending:{taskId}" URL-encoding hack used to
shoehorn a task ID into a VideoGenerationResponse.
## Rate limit enforcement
- New VirtualKeyRateLimitMiddleware enforces per-key RPM/RPD via the
existing Redis-backed sliding-window IVirtualKeyRateLimitService.
Returns 429 + Retry-After + X-RateLimit-{Limit,Remaining,Reset,Scope}
on rejection. Fails open on exception (Redis unavailable, etc.) — rate
limiting is defense-in-depth, never tank the gateway.
Backend-scheme requests pass through without limits.
- VirtualKeyAuthenticationHandler stashes KeyHash/RateLimitRpm/
RateLimitRpd in HttpContext.Items so the middleware does zero DB
lookups. Null limits = unlimited (skips Redis round-trip entirely).
- Remove three dead implementations: VirtualKeyRateLimitPolicy (no-op
stub wired via [EnableRateLimiting] but always returned no-limit
partition), the unreachable VK rate-limit branch in
SecurityService.Core (gated on a "VirtualKeyEntity" Items key nothing
ever set), and the VK portion of SecurityService.RateLimiting.cs
(kept the alive IP/discovery rate-limit code).
- Drop app.UseRateLimiter() — SignalR has its own filter pattern via
VirtualKeySignalRRateLimitFilter, and the framework limiter only
routed through the deleted no-op policy.
## Behavior notes
- Existing virtual keys with configured RateLimitRpm/RateLimitRpd
(previously inert) are now enforced. Soft-launch / advance notice
recommended before announcing to tenants.
- Per-model permission check (ValidateVirtualKeyAsync(key, model))
removed from the Videos controller hot path — orchestrator validates
on event consume, matching Images. Permission errors surface via task
Failed state rather than sync 401/403.
- task.Metadata?.VirtualKeyId is the canonical ownership-check field
for async media tasks; JSON metadata roundtrips removed.
Verified: 462 controller / middleware / orchestrator / security tests
pass; 12 new rate-limit middleware tests pass; full suite 2768 pass with
29 pre-existing Redis-environment failures unchanged.
…va regression The reflection-based CreateStandardClient<T> in ClientCreatorRegistry could not bind to constructors with optional parameters, causing a runtime MissingMethodException for CerebrasClient and SambaNovaClient (both had a trailing string? providerName = null). Revert to explicit per-provider creator methods so constructor mismatches fail at build time instead of on the request path. Also drop the unused providerName parameter from both clients.
…NoTracking - SignalRHubContextResolver: memoize Type.GetType + MakeGenericType per hub name (process-invariant) so reflection only runs once instead of per message dispatch. - BatchWebhookSendOperation, AlertNotificationService, SlackNotificationChannel: wrap HttpResponseMessage (and HttpRequestMessage where caller-owned) in using to stop socket/handle leaks on every webhook and alert send. - BatchSpendUpdateService, AdminVirtualKeyService.Discovery: add AsNoTracking() to read-only queries on the batch flush hot path and the 4-level Include chain in the discovery preview.
Remove ~470 lines of dead code from OpenAICompatibleClient that were never called by the live request paths: - AddOptionalResponseProperties, TryGetProperty, HasProperty: zero callers. - MapFromOpenAIChunk + MapDynamicStreamingChoices + MapSingleStreamingChoice + MapToolCalls + MapSingleToolCall: only reachable from ProcessStreamingResponseAsync, which itself had zero callers. - MapDynamicChoices + MapSingleChoice + MapResponseToolCalls + MapSingleResponseToolCall: zero callers. - ProcessStreamingResponseAsync (Streaming.cs): zero callers. The live streaming path uses ProcessSseStreamAsync<JsonElement> + JsonSerializer.Deserialize<ChatCompletionChunk> directly. The non-streaming path uses MapFromOpenAIResponse with a typed OpenAIChatCompletionResponse and LINQ. Neither touches the deleted code. Kept ConfigureHttpClient and GetCapabilitiesAsync (the only live methods in Utilities.cs).
…sposed RetrieveFromUrlAsync returned a FileRetrievalResult holding a stream from HttpCompletionOption.ResponseHeadersRead, but the parent HttpResponseMessage was never disposed on the success path — leaking socket handles on every URL fetch. Add an internal Owner property on FileRetrievalResult that is disposed in Dispose() alongside the stream, and transfer ownership of the response to the result. Use a try/finally ownership-transfer pattern so the response is disposed on failure paths (non-success status, exceptions during metadata extraction) and only retained when ownership has been handed to the result.
…n-filter GetMappingByModelIdAsync and GetMappingsByModelIdAsync were calling GetAllViaPaginationAsync to materialize every model-provider mapping in the database, then filtering by modelId in memory. The repository already exposes a direct GetByModelIdAsync that pushes the predicate to SQL.
…redAsync ExportAnalyticsAsync was loading every request log in the date range, then filtering by model and virtualKeyId in memory with .Where().ToList(). For exports that scope to a single key or model, this materialized far more rows than necessary. Add IRequestLogRepository.GetByDateRangeFilteredAsync that applies both filters at the database level. The model filter uses EF.Functions.ILike for case-insensitive substring matching (matches the existing pattern in MediaRecordRepository), with %, _, and \ escaped so user input cannot leak LIKE wildcards.
PerformMaintenanceAsync was paginating every virtual key into memory,
foreach-iterating, and calling _virtualKeyRepository.UpdateAsync per
expired key — N round-trips for what is fundamentally one bulk update.
Replace with a SELECT for audit log fields + a single ExecuteUpdateAsync
that disables every expired enabled key in one query. The per-key
"Disabled expired virtual key {Id} ({Name})" Information log line is
preserved by capturing identifying fields up front.
Note: this preserves a pre-existing latent gap — the maintenance loop
calls the repository directly, bypassing VirtualKeyServiceBase.UpdateVirtualKeyAsync,
so VirtualKeyUpdated MassTransit events are not published and
OnVirtualKeyUpdatedAsync (which invalidates the per-node cache) is not
invoked. This bug exists today and is not addressed here; flag for a
follow-up that wires expiry-disables into the same notification path the
interactive update flow uses.
PreviewDiscoveryAsync was eager-loading the full Provider, ModelProviderTypeAssociation, Model, and Series object graphs (4-level Include chain) when the loop only reads ModelAlias plus a handful of capability flags and metadata strings on Model. Series in particular was loaded but never read. Replace with a flat anonymous-type projection that selects only the fields the loop touches. Provider's IsEnabled flag is still applied as a filter predicate so EF emits a JOIN without materializing the Provider entity. The "no model data" warning is preserved by allowing the inner Caps projection to be null when the Model navigation is missing. Net effect: fewer columns transferred per row, no Series JOIN, and no Provider/ModelProviderTypeAssociation/Model/Series entities materialized into the change tracker (already AsNoTracking, but the entity allocations are gone too).
CollectMetricsSnapshotAsync wrapped three synchronous collectors (CollectHttpMetrics, CollectInfrastructureMetrics, CollectSystemMetrics) in Task.Run to overlap them with the one async collector (CollectBusinessMetricsAsync). The sync collectors are fast in-memory metric reads — the Task.Run scheduling overhead exceeds the work itself. Replace with a kick-off-then-foreground pattern: start the I/O-bound business collector first, run the cheap sync collectors directly, then await the business task. Same wall-clock parallelism, no thread-pool churn.
StopAsync was declared as Task-returning but its body was synchronous, calling _processingLock.Wait(5s) and returning Task.CompletedTask. During graceful host shutdown, the host awaits StopAsync — the sync Wait blocked the shutdown thread for up to 5 seconds. Convert to async and use WaitAsync(timeout). Same 5-second cap, but the caller is no longer blocked on a thread-pool thread while waiting.
Update tests left stale by 5cd6bc9 (bulk virtual-key disable via ExecuteUpdateAsync) and 2c99063 (analytics export pushed into SQL): - AnalyticsService export tests: mock GetByDateRangeFilteredAsync. - AdminVirtualKeyService tests: back the IDbContextFactory with a real SQLite in-memory database (EF InMemory does not support ExecuteUpdateAsync) and rewrite the maintenance test to seed and verify against the DB instead of the bypassed repository.
- NU1903 high: pin System.Security.Cryptography.Xml 10.0.7 (was 10.0.3 transitively via Microsoft.Build.Tasks.Core 18.3.3 and Microsoft.AspNetCore.DataProtection 10.0.3) — fixes GHSA-37gx-xxp4-5rgx, GHSA-w3x6-4m5h-cxqf. - NU1904 critical: pin Microsoft.AspNetCore.DataProtection 10.0.7 — fixes GHSA-9mv3-2cwr-p262. - NU1902 moderate: bump OpenTelemetry / OpenTelemetry.Api / OpenTelemetry.Exporter.OpenTelemetryProtocol 1.15.0 -> 1.15.3 — fixes GHSA-g94r-2vxg-569j, GHSA-4625-4j76-fww9, GHSA-mr8r-92fq-pj8p, GHSA-q834-8qmm-v933. - NU1510: suppress in framework-pruned csprojs (the pins above are kept for SBOM clarity even though SDK pruning would otherwise remove them). - CS1591: suppress in Admin.csproj to match Gateway's policy (GenerateDocumentationFile + NoWarn 1591 already in use there). - CS8604: coalesce LoggingSanitizer.S result to string.Empty in RequestTrackingMiddlewareBase before passing to OnBeforeRequest. - Tests has no Microsoft.AspNetCore.App framework reference, so it needs explicit pins to clear NU1903/NU1904 (framework pruning does not apply there).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.