fix: clear the open bug backlog (#473-#481, #483, #485-#487) - #488
Merged
Conversation
Fixes every bug open on the tracker, plus the three found while doing so. Security / access control - #474 ProjectsController delegated to IProjectAccessGuard. Its inline CanAccessAsync never read ApiKeyAuthenticationHandler.ProjectIdItemKey, so a REST key minted for project A could read project B's detail and its members' email addresses whenever the key's owner (usually an admin) was a member. - #473 User.PrintMembers masks PasswordHash. ExternalSubject stays visible: the existing five redact credentials, not identifiers. - #479 HostEnvironmentName resolves the environment as the host does (DOTNET_ENVIRONMENT ahead of ASPNETCORE_ENVIRONMENT) and the module layers in appsettings.{Environment}.json. The reversed order made a Production host compute Development and drop Secure from the session cookie. Proxy - #475 ResponseHeadersRead is kept, but ProxyBufferedResponseAsync re-arms the bound at its copy loop from client.Timeout, so a stalled upstream is a 504 instead of a request held open until the client gives up. - #480 The SSE splitter treats a lone CR as a terminator, carrying the CR/LF seam across a chunk boundary so CRLF is never counted twice. Aggregates and scoping - #483 StatisticsFilter gained ProjectIds, applied in both the EF path and the raw SQL one (= ANY(@projectIDS), a single uuid[] parameter, never interpolated). The single-project path is preserved, so the web UI and every REST key keep their indexed equality predicate and its plan. EvaluatorsController's sparklines had the identical constraint and got the identical treatment. Runner and middleware - #476 The success-path optimizer/anomaly enqueues take CancellationToken.None, matching the failure path: a cancel landing as the group completed skipped both jobs silently. - #486 The generic catch only broadcasts and enqueues for a group it actually transitioned. The two enqueues also absorb their own failures, so losing one no longer costs the group the other. - #477 A fault after Response.HasStarted aborts the connection. Returning signalled success, framing a truncated body as complete. - #485 A client that hung up mid-stream is classified before the error capture, so a closed tab no longer persists an ApplicationError nobody can act on. Pricing - #478/#487 The LiteLLM catalog and the Frankfurter FX feed each arm a 30s negative cache. Priced per model, an outage previously cost one full fetch per discovered model, serialized behind the gate. Misc - #481 IsAzure trims one trailing DNS root dot before the exact suffix match, keeping the domain-boundary guarantee (my-azure.com.example.net still fails). #482 needed no change: PR #484 already added ResolveListScopeAsync and moved all eight list endpoints onto it. Verified: dotnet build Proxytrace.sln clean (24 projects, 0 warnings); scoped test runs green (Domain 398, Storage 815, Proxy 96, Infrastructure 73, Application 523, Api 543); manual VitePress build clean. Not verified: the two perf metrics added for #483 carry uncalibrated placeholder budgets. perf/run.sh --size 1000000 is still owed to set them from a real p95. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RTyMUobDLv1aEAqHwtUtQg
Comment on lines
+333
to
+336
| => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) | ||
| { | ||
| Content = new StreamContent(new StallingStream(cancelOnFirstRead)), | ||
| }); |
Comment on lines
+248
to
+251
| catch (Exception ex) | ||
| { | ||
| logger.LogError(ex, "Failed to enqueue {Job} for completed test run group {GroupId}", job, groupId); | ||
| } |
JabbaKadabra
added a commit
that referenced
this pull request
Jul 30, 2026
Resolves the conflicts between the cost-control feature and the bug-backlog and security work merged to master (#484, #488, 86f72d8). - AuditAction / NotificationKind: both sides appended members. Master's already-merged values keep their numbering; the cost actions shift to 72-76 and CostBudget moves after TraceQuotaReached. Neither shifted value has shipped, so no stored row changes meaning. - ResolvedApiKey: each side added a member for the same reason (attributing a proxied call to the Proxytrace-issued key). The record now carries both ApiKeyId (per-key spend and budget blocking) and Scopes (the pass-through capability check), both null on the upstream-key path. - OpenAiProxyController: keeps the ResolvedApiKey-shaped capture signatures and takes master's additions - the body-download timeout (#475) and capturedStatus, so a stalled upstream is still traced as a 504. - IApiKeyRepository / ApiKeyRepository: additive, GetByProjectAsync alongside GetKeyNamesByOwnerAsync. - CHANGELOG: merged section by section under [Unreleased]. Master's two separate ### Fixed headings are folded into one; entry order and text are unchanged. Verified: dotnet build (0 warnings), dotnet test Proxytrace.sln (2930 passed, 0 failed), frontend build/lint/test (1140 passed), manual docs:build, and `ef migrations has-pending-model-changes` (none). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9NmoPS88gGTwTKAuUcbNb
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.
Fixes every bug that was open on the tracker, plus the three found while doing so.
Closes #473, closes #474, closes #475, closes #476, closes #477, closes #478, closes #479, closes #480, closes #481, closes #483, closes #485, closes #486, closes #487.
#482 needs no change and can be closed as already fixed — PR #484 added
IProjectAccessGuard.ResolveListScopeAsync(Proxytrace.Api/Auth/ProjectListScope.cs) and moved all eight list endpoints onto it. Verified against the current tree; not closed here because this PR touches no code for it.Security / access control
ProjectsControllerwas the only controller doing role/membership checks inline. ItsCanAccessAsyncnever readApiKeyAuthenticationHandler.ProjectIdItemKey, so a REST key minted for project A could list every project its owner belonged to, open project B's detail, and read project B's member email addresses — and since keys are minted by an admin, the owner usually reached every project. Now delegates toIProjectAccessGuard/ResolveListScopeAsynclike the other ~15 controllers; the inline check is deleted.User.PrintMembersmasksPasswordHash.ExternalSubjectis deliberately left visible: the existing five overrides redact credentials, not identifiers (EmailSettingsmasksPassword, printsUsername).HostEnvironmentNameresolves the environment the wayWebApplicationBuilderdoes (DOTNET_ENVIRONMENTahead ofASPNETCORE_ENVIRONMENT), and the module layers inappsettings.{Environment}.json. The reversed order made a Production host computeDevelopmentand default the 7-day session cookie'sSecuretofalseon an HTTPS install.Proxy
ResponseHeadersReadis kept (the memory rationale stands), butProxyBufferedResponseAsyncre-arms the lost bound at its copy loop, sourced fromclient.Timeoutso the header and body phases cannot drift. A stalled upstream is now a 504 recorded on the trace instead of a request, socket and continuation held open until the client gives up.\ras a terminator again, carrying the CR/LF seam across a chunk boundary so CRLF is never counted twice.Aggregates and scoping
StatisticsFiltergainedProjectIds, mirroring whatAgentCallFiltergot in Unscoped list endpoints return an empty page instead of the caller's accessible rows for non-admins #482, applied in both the EF path and the raw-SQL one (= ANY(@projectIds)bound as a singleuuid[]parameter, never interpolated). The single-project path is deliberately preserved, so the web UI and every REST API key keep their indexed equality predicate and its plan; only a genuine multi-project scope takes the array path.EvaluatorsController's sparkline query had the identical constraint and got the identical treatment.Runner and middleware
CancellationToken.None, matching the failure path. A cancel landing in the window afterSetCompletedtripped the linked token and skipped both jobs silently, leaving a group reading Completed with no optimization and no anomaly detection.Response.HasStartedcallscontext.Abort(). Returning signalled success to the framework, framing a truncated body as complete.ApplicationErrorcarrying anerrorIdnobody can act on.Pricing
IClock,Interlockedticks, fast path outside the gate). Prices resolve per model, so an outage previously cost one full fetch per discovered model, serialized behind the gate — hundreds of doomed attempts for one refresh. A caller-cancelled fetch never arms it, so the recover-after-a-blip property is kept.Misc
IsAzuretrims exactly one trailing DNS root dot before the exact suffix match, keeping the domain-boundary guarantee (my-azure.com.example.netstill does not match).Verification
dotnet build Proxytrace.sln— 24 projects, 0 errors, 0 warningsmanual/VitePress build cleanfrontend/file changed, so no manual screenshots needed refreshingNot verified — needs a follow-up before merge is fully clean
The two perf metrics added for #483 (
statsAgentBreakdownByProjects,statsLatencyPercentilesByProjects) carry uncalibrated placeholder budgets derived from their single-project twins +25%.perf/run.sh --size 1000000is still owed to set them from a real p95; the budget file marks them as placeholders.One judgement call left open
StatisticsControllerrefuses an unscoped non-admin dashboard request with an explicit403rather than silently returning empty, so it has no #482/#483-class defect and was left alone. It is now inconsistent with the traces overview beside it, which aggregates across the caller's projects. Making it aggregate too is a behaviour change beyond these issues.🤖 Generated with Claude Code
https://claude.ai/code/session_01RTyMUobDLv1aEAqHwtUtQg