Skip to content

fix: clear the open bug backlog (#473-#481, #483, #485-#487) - #488

Merged
JabbaKadabra merged 1 commit into
masterfrom
bugfixes_2
Jul 28, 2026
Merged

fix: clear the open bug backlog (#473-#481, #483, #485-#487)#488
JabbaKadabra merged 1 commit into
masterfrom
bugfixes_2

Conversation

@JabbaKadabra

Copy link
Copy Markdown
Collaborator

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

Proxy

Aggregates and scoping

Runner and middleware

Pricing

Misc

Verification

  • dotnet build Proxytrace.sln — 24 projects, 0 errors, 0 warnings
  • Scoped test runs, all green: Domain 398, Storage 815, Proxy 96, Infrastructure 73, Application 523, Api 543
  • manual/ VitePress build clean
  • No frontend/ file changed, so no manual screenshots needed refreshing

Not 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 1000000 is still owed to set them from a real p95; the budget file marks them as placeholders.

One judgement call left open

StatisticsController refuses an unscoped non-admin dashboard request with an explicit 403 rather 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

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
JabbaKadabra merged commit 6529981 into master Jul 28, 2026
12 checks passed
@JabbaKadabra
JabbaKadabra deleted the bugfixes_2 branch July 28, 2026 16:51
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment