Skip to content

Integrate dev → main: inference routing (upstream router + remote chat) + OpenAPI describability + queued API work#8

Merged
Snider merged 38 commits into
mainfrom
dev
Jun 7, 2026
Merged

Integrate dev → main: inference routing (upstream router + remote chat) + OpenAPI describability + queued API work#8
Snider merged 38 commits into
mainfrom
dev

Conversation

@Snider
Copy link
Copy Markdown
Contributor

@Snider Snider commented Jun 6, 2026

Summary

Brings dev to main. Three features this round (each spec→plan→TDD-built with two-stage review per task, on top of the reused router transport), plus previously-queued dev work.

1. WithUpstreamRouter — selector-keyed reverse proxy (Go engine)

  • Load-balances each request across a runtime-mutable pool of HTTP upstreams, keyed by a pluggable selector (default JSON model). Weighted round-robin + passive failover (cooldown); hybrid streaming; decision hook; composes with TransformerIn/Out.
  • UpstreamRegistry (copy-on-write, lock-free reads); block-by-default SSRF validation at registration with AllowPrivateUpstreams(cidrs...) opt-in.

2. Chat-completions remote backend + format adapters (Go engine)

  • WithChatCompletionsRemote(reg, …) makes /v1/chat/completions serve remote models alongside the local in-process path — local-first dispatch (ModelResolver.Knows) → remote via the router transport.
  • Passthrough by default; per-model ChatFormatAdapter for non-OpenAI upstreams (Ollama-native + Anthropic, incl. streaming transcoders).
  • Off-loopback opt-in gated by a request-validated bearer (constant-time, fail-closed) — Cerberus-reviewed SECURE.

3. OpenAPI describability for the inference surface (Go engine)

  • The inference surface is now visible to /v1/openapi.json + SDK gen: chat-completions surfaces for local / remote / hybrid (honest x-chat-completions-enabled), and each WithUpstreamRouter mounted path appears as a minimal POST proxy item — deduped so real typed items (chat / spec / swagger / group) always win.
  • Proxy items declare bearerAuth for the non-public case so generated SDK clients authenticate correctly.

Also included (previously on dev)

  • PHP: webhooks, API-key hardening, MCP API + OpenAPI docs
  • Sonar sweep (string-literal dedup, long-param bundling)
  • core-family dependency bumps (core/go v0.10.3)
  • opt-in strict bind + bearer-on-public

Test Plan

  • GOWORK=off go test ./... green — api, cmd/gateway, pkg/provider, pkg/stream
  • -race clean (full root suite ~1693 tests); go vet ./ clean; gofmt -l clean
  • go build ./... + cmd/gateway build OK
  • gosec: no new findings (4 pre-existing in untouched files; the justified #nosec G107 upstream-dispatch annotation)
  • Per-task adversarial review (spec + code-quality), Cerberus security pass on the auth gate, holistic final reviews → MERGE-READY

Notes

  • Composition limitation (documented): post-c.Next() response-header middleware (e.g. ApiSunset) doesn't apply to streamed proxy responses; pre-handler middleware composes normally.
  • Follow-ups (non-blocking): bearerAuthMiddleware constant-time compare (pre-existing Low); generic streaming-transcoder registry + tool-calling translation; real per-path schemas for generic router paths via consumer RouteDescriptions; orphaned proxy tag when all router paths dedupe away.

🤖 Generated with Claude Code
Co-Authored-By: Virgil [email protected]

Snider and others added 19 commits May 31, 2026 08:29
…public+bearer

WithStrictBind / WithLoopbackOnly enable bind enforcement at Serve time;
default OFF so existing consumers (go-ml, go-ai, desktop, core-agent) keep
their current behaviour. Under strict mode Serve:
  - serves a loopback address unconditionally;
  - rejects a non-loopback bind with ErrNonLoopbackBind unless WithPublicBind;
  - rejects a public bind with ErrPublicBindNoBearer unless WithBearerAuth.

The check runs before the listener opens so a misconfigured strict engine
fails fast rather than exposing an unauthenticated public listener.
WithBearerAuth now records that a non-empty credential was supplied.

Good/Bad/Ugly tests cover the addr classifier, validateBind, and Serve
fail-fast. go build ./... and the new tests are green.

RFC.serve.md S2 / Mantis #1807

Co-authored-by: Hephaestus <[email protected]>
Co-Authored-By: Virgil <[email protected]>
…le v0.8 pins

The external/*/go submodules were pinned at the v0.8.0-alpha era. Under
go.work that stale code is what built — and stale go-log rendered
coreerr.E(op, msg, nil) as an empty string, which surfaced as a phantom
ExportSpec error-path test failure. Bump the core-family submodules:

  external/go            v0.8 -> v0.10.3
  external/go-log        v0.8 -> v0.10.0
  external/go-io         v0.8 -> v0.11.0
  external/go-inference  v0.8 -> v0.10.0

go.mod dappco.re/go -> v0.10.3. Full api suite green under go.work.

Co-Authored-By: Virgil <[email protected]>
…m lists

Addresses the go:S1192 finding set (duplicated string literals, 371×) by
extracting shared constants:
  - string_constants.go   — shared header/mime/error-op/message consts
  - test_constants_test.go — shared test format + fixture consts
and bundles operationResponses' long positional parameter list into an
operationRespParams struct. No behaviour change; full suite green under
go.work.

Co-Authored-By: Virgil <[email protected]>
core-api PHP package feature work:
  - webhook endpoints: signing, delivery, templates, secret management
  - API keys: IP whitelisting + rotation
  - MCP API controller + resource/server access
  - OpenAPI documentation builder + examples + SEO report service
plus their feature tests.

Co-Authored-By: Virgil <[email protected]>
GOAL.md is the Sonar findings tracker driving the dedup sweep (one rule
per commit). openapitools.json pins the openapi-generator-cli config.

Co-Authored-By: Virgil <[email protected]>
Selector-keyed reverse proxy over a pool of HTTP upstreams: pluggable
selector (default body `model`), weighted round-robin + passive failover,
runtime-mutable pool registry, decision hook, hybrid streaming, composes
with the existing TransformerIn/Out layer. SSRF guard bypassed for
operator-configured upstreams (validated at registration).

Co-Authored-By: Virgil <[email protected]>
…reams opt-in

Align registration validation with pkg/provider/proxy.go posture instead of
bare shape-check: reject loopback/private/reserved/metadata by default, widen
via an explicit AllowPrivateUpstreams(cidrs...) registry option. Allow-list
lives on the registry (it owns Set/Add validation; Option func(*Engine) can't
return errors).

Co-Authored-By: Virgil <[email protected]>
6 TDD tasks: UpstreamRegistry (COW + SSRF policy), upstreamBalancer (weighted
RR + cooldown), upstreamTransport (failover RoundTripper), router config +
options + engine mount, httptest integration suite, QA gate. Full no-placeholder
code per step; reuses ssrf_guard + transformer + response helpers.

Co-Authored-By: Virgil <[email protected]>
…t tests

Address code-review on 07d91d7:
- cloneUpstreams deep-copies each Upstream.Headers into a fresh map so a
  caller mutating their map post-write (or the transport iterating it) can
  no longer race the stored snapshot.
- clone() now clones the default-pool slice (cloneUpstreams(cur.deflt))
  instead of aliasing it across snapshot generations.
- Reword the empty-SetDefault error; split validateEach out of validateAll
  so Set/Add keep their pool-path message.
- Add resolve default-fallback, no-default, Remove-then-resolve, bad-CIDR,
  and a Headers deep-copy race test (verified to fail under a shallow copy).

Co-Authored-By: Virgil <[email protected]>
… GetBody guard + unit tests

Address code review on the failover transport:
- finalise() clones http.DefaultTransport so the router owns an isolated pool
- drainAndClose does a bounded drain before Close so keep-alive conns survive
  a flapping upstream (cap guards a hostile error body)
- RoundTrip bails to the 503 exhausted-path when GetBody replay errors
- doc caveats: unbounded per-key balancer state (v1), zero failover statuses
- add transport unit tests: 4xx passthrough, transport-error retry, body
  replay on retry, all-cooling 503, applyUpstream rewrite

Co-Authored-By: Virgil <[email protected]>
…, transforms, SSRF, composition

Adds upstream_router_test.go (9 black-box httptest tests) and
upstream_router_example_test.go (ExampleWithUpstreamRouter godoc test).

8/9 pass cleanly under -race:
  TestUpstreamRouter_RoutesByModel_Good
  TestUpstreamRouter_MissingModel_Bad
  TestUpstreamRouter_Failover_Good
  TestUpstreamRouter_AllDown_503_Ugly
  TestUpstreamRouter_StreamingPassthrough_Good
  TestUpstreamRouter_TransformInOut_Good
  TestUpstreamRouter_RouteHookOverride_Good
  TestUpstreamRouter_SSRFPosture_Bad
  ExampleWithUpstreamRouter

KNOWN DEFECT (Task 4): TestUpstreamRouter_Composition_Middleware_Good FAILS.
upstreamResponseWriter() unwraps gin's ResponseWriter to the raw
http.ResponseWriter so the reverse proxy can flush/stream. This breaks
post-Next() gin middleware header injection: when ApiSunset runs
c.Writer.Header().Add("Sunset", ...) after c.Next() returns, the raw
writer has already committed headers via proxy.ServeHTTP, so the Sunset
header is never sent and gin logs a superfluous WriteHeader. Test is NOT
weakened per task instructions — the composition guarantee must be fixed
in upstream_router.go (upstreamResponseWriter / proxy wiring).

Co-Authored-By: Virgil <[email protected]>
…dleware composition

upstreamResponseWriter() unwrapped gin's ResponseWriter to the raw
http.ResponseWriter before handing it to httputil.ReverseProxy. That was
unnecessary (gin.ResponseWriter already implements Flusher/Hijacker/
CloseNotifier — all the proxy needs to stream) and harmful: writing
straight to the raw writer bypassed gin's Written() tracking, producing a
"superfluous response.WriteHeader" warning and a split header map.

Now ServeHTTP writes through c.Writer and the helper is deleted. Streaming
(SSE flush-through) is unaffected — TestUpstreamRouter_StreamingPassthrough_Good
still passes.

Composition: post-c.Next() RESPONSE-header middleware (e.g. ApiSunset) still
cannot apply to proxied responses — the proxy commits the response during the
handler, before the post-Next phase runs. This is inherent to a streaming
reverse proxy, not a wiring bug. The Sunset-based composition test is replaced
by TestUpstreamRouter_Composition_PreNextMiddleware_Good, which proves engine
middleware wraps the router via WithRateLimit's pre-Next X-RateLimit-Limit
header on a successful proxied response. WithUpstreamRouter godoc now documents
that pre-Next middleware composes while post-Next response-header middleware
does not.

Full GOWORK=off go test ./ -run TestUpstream -race: 31 passed.

Co-Authored-By: Virgil <[email protected]>
…ationale

Adds TestUpstreamRouter_MultiPath_Good — WithRouterPaths with two paths
(/v1/chat/completions + /v1/embeddings) now has e2e coverage, asserting each
mounted path proxies through and the upstream receives the correct request path.

Also corrects the proxy.ServeHTTP rationale comment: gin.ResponseWriter satisfies
http.Flusher/http.Hijacker (all ReverseProxy needs on the Rewrite path); the prior
http.CloseNotifier claim only held on the deprecated Director path and is dropped.

GOWORK=off go test ./ -run TestUpstreamRouter -race -count=1: 10 passed.

Co-Authored-By: Virgil <[email protected]>
The non-streaming + out-transformer path read the upstream body with an
uncapped io.ReadAll — an asymmetry versus the request path, which is bounded
by maxToolRequestBodyBytes. Cap it with a test-overridable
maxUpstreamResponseBytes (10 MiB) via io.LimitReader, returning a 502
invalid_upstream_response when exceeded. Streaming and the no-out-transformer
fast path are untouched.

Adds internal tests (package api) that lower the cap via save/restore to
exercise the oversize 502 path and a small-body transform regression guard
without minting a real 10 MiB body.

Co-Authored-By: Virgil <[email protected]>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an upstream reverse-proxy router (registry, balancer, transport, Gin handler), introduces strict bind validation/options, standardises headers/messages/error keys, updates OpenAPI generation, and broadly refactors tests/docs across Go and PHP; bumps a Go dependency and several submodules.

Changes

Core API and Docs

Layer / File(s) Summary
Upstream router feature
go/upstream_*, go/upstream_router*.go, docs/superpowers/*
Implements selector-keyed reverse proxy with registry validation, weighted RR + cooldown, failover transport, in/out transforms, and Gin mounting; adds plans/specs and tests.
Strict bind enforcement
go/api.go, go/options.go, go/strict_bind_test.go
Adds validateBind with loopback/public bind rules, exposes options and errors, and tests behaviour including Serve short-circuit.
Constants and OpenAPI updates
go/string_constants.go, go/openapi*.go, go/swagger*.go
Standardises header/MIME/message constants and refactors OpenAPI response/request media keys and descriptions.
Tests refactor to shared constants
go/*_test.go
Rewrites many tests to use shared path/header/format constants and updated messages; behaviour unchanged.
PHP constants and controller/service tweaks
php/src/**
centralises strings/paths, minor signature changes (parameterless docs controller), SSRF/message constant reuse, regex and arg-name normalisation; tests updated accordingly.
Dependencies and submodules
go/go.mod, external/*
Bumps dappco.re/go and updates submodule SHAs.

Sequence Diagram(s)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
php/src/Api/Tests/Feature/RateLimitTest.php (1)

607-611: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Drop the unused foreach key variable.

$_tier is unused and triggers the PHPMD UnusedLocalVariable warning. Iterate values directly to keep the test clean.

Suggested patch
-        foreach ($tiers as $_tier => $tierConfig) {
+        foreach ($tiers as $tierConfig) {
             $this->assertArrayHasKey('limit', $tierConfig);
             $this->assertArrayHasKey('window', $tierConfig);
             $this->assertArrayHasKey('burst', $tierConfig);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@php/src/Api/Tests/Feature/RateLimitTest.php` around lines 607 - 611, The
foreach in RateLimitTest.php currently declares an unused key variable ($_tier)
causing a PHPMD UnusedLocalVariable warning; update the loop to iterate values
only (use foreach ($tiers as $tierConfig)) so you drop the unused $_tier while
keeping the existing assertions that check 'limit', 'window', and 'burst' on
$tierConfig.

Source: Linters/SAST tools

🧹 Nitpick comments (4)
GOAL.md (1)

780-782: 💤 Low value

Format regex patterns as inline code to resolve markdownlint warnings.

The regex pattern ^[a-z][a-zA-Z0-9]*$ contains square brackets that markdownlint interprets as undefined markdown reference links, triggering MD052 warnings. Wrap the regex in backticks to clarify it's a code pattern.

📝 Proposed fix to format regex as inline code
-- `src/php/src/Api/Tests/Feature/SeoReportServiceTest.php:6` — Rename function "dns_get_record" to match the regular expression ^[a-z][a-zA-Z0-9]*$.
+- `src/php/src/Api/Tests/Feature/SeoReportServiceTest.php:6` — Rename function "dns_get_record" to match the regular expression `^[a-z][a-zA-Z0-9]*$`.
-- `src/php/src/Api/Tests/Feature/WebhookDeliveryTest.php:6` — Rename function "dns_get_record" to match the regular expression ^[a-z][a-zA-Z0-9]*$.
+- `src/php/src/Api/Tests/Feature/WebhookDeliveryTest.php:6` — Rename function "dns_get_record" to match the regular expression `^[a-z][a-zA-Z0-9]*$`.
-- `src/php/src/Api/Tests/Feature/WebhookEndpointTest.php:6` — Rename function "dns_get_record" to match the regular expression ^[a-z][a-zA-Z0-9]*$.
+- `src/php/src/Api/Tests/Feature/WebhookEndpointTest.php:6` — Rename function "dns_get_record" to match the regular expression `^[a-z][a-zA-Z0-9]*$`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@GOAL.md` around lines 780 - 782, Update the three list items that reference
the function name dns_get_record in the GOAL.md entries for
SeoReportServiceTest, WebhookDeliveryTest, and WebhookEndpointTest so the regex
pattern ^[a-z][a-zA-Z0-9]*$ is wrapped in backticks (inline code) rather than
plain text; keep the function name dns_get_record unchanged but format the regex
as `^[a-z][a-zA-Z0-9]*$` so markdownlint no longer treats the square brackets as
reference links.

Source: Linters/SAST tools

php/src/Api/Controllers/Api/WebhookSecretController.php (1)

22-22: ⚡ Quick win

Complete the string constant extraction for both webhook types.

The refactor extracts 'Webhook endpoint' as RESOURCE_NAME for content webhook methods, but social webhook methods (lines 44, 130, 178, 232) still use the hardcoded literal 'Webhook'. For consistency and maintainability, extract both strings as constants.

♻️ Proposed fix to complete the refactor
-    private const RESOURCE_NAME = 'Webhook endpoint';
+    private const CONTENT_WEBHOOK_RESOURCE_NAME = 'Webhook endpoint';
+    private const SOCIAL_WEBHOOK_RESOURCE_NAME = 'Webhook';

Then update all usages:

  • Lines 44, 130, 178, 232: use self::SOCIAL_WEBHOOK_RESOURCE_NAME
  • Lines 87, 154, 205, 271: use self::CONTENT_WEBHOOK_RESOURCE_NAME

Also applies to: 87-87, 154-154, 205-205, 271-271

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@php/src/Api/Controllers/Api/WebhookSecretController.php` at line 22, Add two
class constants in WebhookSecretController: CONTENT_WEBHOOK_RESOURCE_NAME =
'Webhook endpoint' and SOCIAL_WEBHOOK_RESOURCE_NAME = 'Webhook'; then replace
all hardcoded literals: where the code currently uses 'Webhook endpoint' use
self::CONTENT_WEBHOOK_RESOURCE_NAME (e.g., in methods handling content webhooks
such as the ones referencing the existing RESOURCE_NAME), and where it uses
'Webhook' for social webhooks replace with self::SOCIAL_WEBHOOK_RESOURCE_NAME
(update all social webhook handlers that currently use the literal). Ensure you
remove or consolidate the old RESOURCE_NAME if redundant and update every usage
site in the class to reference the new constants.
php/tests/Feature/AuthenticationGuideTest.php (1)

8-8: 💤 Low value

Consider using a class constant instead of define() for test-scoped values.

Whilst define() works correctly, modern PHP test suites typically use class constants or local const declarations for better scoping and IDE support. For Pest tests, you can use a file-scoped const declaration instead.

♻️ Optional refactor to modern constant pattern
-define('API_KEYS_PREFIXED_WITH', 'API keys are prefixed with');
+const API_KEYS_PREFIXED_WITH = 'API keys are prefixed with';

This provides the same scoping behaviour with more modern PHP syntax.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@php/tests/Feature/AuthenticationGuideTest.php` at line 8, Replace the global
define('API_KEYS_PREFIXED_WITH', ...) with a scoped constant to avoid global
leakage: declare a class constant (e.g.,
AuthenticationGuideTest::API_KEYS_PREFIXED_WITH) or a file-scoped const in the
same test file and update any references to use that constant name
(API_KEYS_PREFIXED_WITH) from the AuthenticationGuideTest, ensuring the constant
is declared inside the test class (or file scope) rather than via define().
php/src/Api/Tests/Feature/RateLimitingTest.php (1)

362-362: ⚡ Quick win

Remove unnecessary API key creation.

This line creates an API key for $workspace2 but the result is neither stored nor used. The test only uses $apiKey1 in its requests (lines 365–367, 376), consistent with the comment "Use same API key ID to test shared limit". Creating this unused API key wastes test resources by inserting unnecessary database records.

♻️ Proposed fix
         $apiKey1 = createApiKeyForWorkspace($workspace1);
-        createApiKeyForWorkspace($workspace2);

         // Use same API key ID to test shared limit
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@php/src/Api/Tests/Feature/RateLimitingTest.php` at line 362, The test is
creating an unused API key via createApiKeyForWorkspace($workspace2) which
inserts unnecessary DB state; remove that call so only the relevant api key
($apiKey1) is created/used. Locate the createApiKeyForWorkspace invocation in
RateLimitingTest (near where $workspace2 and $apiKey1 are defined) and delete
the single-line call that creates an API key for $workspace2, ensuring
subsequent assertions still use $apiKey1 to test shared limits.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/specs/2026-06-06-upstream-router-design.md`:
- Line 168: Replace the bare triple-backtick fenced code blocks in the document
with language-tagged fences (e.g., ```text) so markdownlint MD040 is satisfied:
update the fence that wraps the block beginning "hits mounted path  (engine
auth/CORS/ratelimit/tracing already ran)" and the fence that wraps the block
starting "go/upstream_registry.go         + _test.go + _example_test.go" to use
```text (or another appropriate language) instead of plain ``` so both instances
are lint-compliant.

In `@go/openapitools.json`:
- Around line 1-7: The $schema path in go/openapitools.json is relative to the
go/ folder and currently points to
./node_modules/@openapitools/openapi-generator-cli/config.schema.json which will
only work if that package is installed under go/node_modules; update the $schema
to a reliable path such as
../node_modules/@openapitools/openapi-generator-cli/config.schema.json or a
stable URL (e.g., the package’s schema on npmjs or raw GitHub) so editor/schema
validation resolves correctly; keep the existing generator-cli.version entry
unchanged unless you want to intentionally bump it.

In `@go/strict_bind_test.go`:
- Around line 179-182: The test comment says it should verify default behavior
on a non-loopback bind but the code creates the engine with
New(WithAddr("127.0.0.1:0")) which is loopback; change the address passed to
WithAddr to a non-loopback ephemeral address (e.g. "0.0.0.0:0") so the Serve
test actually exercises public/non-loopback binding in non-strict mode while
keeping the rest of the test flow (error checks around New and subsequent
Serve/cancellation) unchanged.

In `@go/upstream_balancer.go`:
- Around line 41-45: The per-key state map b.current (variables key and cw) is
unbounded and can grow indefinitely; add an eviction strategy to limit growth:
introduce a maxEntries constant and track insertion order (e.g., an LRU list or
a simple FIFO ring) alongside b.current, and when inserting a new key in the cw
initialization path evict the oldest entries until size <= maxEntries; ensure
modifications to b.current and the order tracker are done under the existing
balancer lock/guard to keep it concurrency-safe and preserve the current
initialization logic for cw.

In `@go/upstream_registry_internal_test.go`:
- Line 70: The test function name TestUpstreamRegistry_Ugly_HeadersDeepCopy does
not end with the required `_Ugly` suffix; rename it to end with `_Ugly` (for
example TestUpstreamRegistry_HeadersDeepCopy_Ugly) and update the function
declaration and any references/imports that reference the test function name so
the test still compiles and runs; ensure only the function identifier is changed
(keep the body and behavior intact).

In `@go/upstream_registry_test.go`:
- Line 82: The test function name
TestUpstreamRegistry_Ugly_ConcurrentWriteSnapshot doesn't end with the required
_Ugly suffix; rename the function to end with _Ugly (for example
TestUpstreamRegistry_ConcurrentWriteSnapshot_Ugly) and update any references to
that symbol in the test file or other tests; ensure the function declaration
(TestUpstreamRegistry_Ugly_ConcurrentWriteSnapshot) is replaced with the new
name so the file conforms to the Go test naming convention.

In `@go/upstream_registry.go`:
- Line 113: The current append uses the original up (variable) so up.Headers can
alias caller memory and reintroduce map races; make a deep-copy of the upstream
before appending to next.pools. Modify the Add site where you call
next.pools[key] = append(cloneUpstreams(next.pools[key]), up) to create an
upstream copy (copy the struct and allocate a new map/slices for Headers) and
append that copy instead; you can add a helper like copyHeaders or cloneUpstream
that performs a deep copy of Headers and use it in place of up (referencing
next.pools, cloneUpstreams, and the up variable).

In `@go/upstream_router_example_test.go`:
- Around line 5-9: Replace the standard fmt usage with the Core wrapper: remove
the "fmt" import and import the Core package (dappco.re/go/core), then change
any fmt.Println calls in the example(s) to core.Println so the example prints
through Core; also prefer dappco.re/go wrapper functions for any formatting used
in this file (e.g., replace fmt.Printf/fmt.Sprintf similarly).

In `@go/upstream_router_test.go`:
- Line 59: Tests are ignoring errors from reg.SetDefault(api.Upstream{URL:
"https://example.com"}) which can hide setup failures; change each ignored call
(the occurrences of reg.SetDefault in this file) to check the returned error and
fail the test on error (e.g., capture err := reg.SetDefault(...); if err != nil
{ t.Fatalf("failed to set default upstream: %v", err) } or use
require.NoError(t, err)) so setup errors surface immediately.

In `@go/upstream_router.go`:
- Line 203: Guard the http.DefaultTransport cast by checking its dynamic type
before cloning: replace the direct cast at cfg.transport =
http.DefaultTransport.(*http.Transport).Clone() with a type assertion (if t, ok
:= http.DefaultTransport.(*http.Transport); ok { cfg.transport = t.Clone() }
else { cfg.transport = &http.Transport{} }) so we don’t panic if
DefaultTransport is a non-*http.Transport; also stop matching oversized-body by
string comparison and instead detect net/http’s MaxBytesError via errors.As
(e.g. var mbe *http.MaxBytesError; if errors.As(err, &mbe) { ... }) and add the
"errors" import if missing.
- Around line 347-348: Replace the fragile string comparison err.Error() ==
"http: request body too large" with a typed check for *http.MaxBytesError using
the project's error-as helper (e.g., core.As or errors.As): attempt to cast err
into a variable like var maxBytesErr *http.MaxBytesError and if the cast
succeeds, call c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge,
Fail(errCodeRequestTooLarge, "Request body exceeds the maximum allowed size"));
update the branch around the error handling in upstream_router.go accordingly so
wrapped or reformatted errors still produce HTTP 413.

In `@php/src/Api/Routes/api.php`:
- Around line 25-26: The route constants API_ROUTE_WORKSPACE and API_ROUTE_ID
are defined unguarded which can trigger "already defined" warnings if the file
is included multiple times; wrap each define call with a defined() check so you
only call define('API_ROUTE_WORKSPACE', '/{workspace}') and
define('API_ROUTE_ID', '/{id}') when the constant is not already defined (use
defined('API_ROUTE_WORKSPACE') and defined('API_ROUTE_ID') to guard them) to
avoid redefinition warnings at bootstrap.

In `@php/src/Api/Tests/Feature/DocumentationControllerTest.php`:
- Line 104: The test calls to DocumentationController::index are passing a
Request even though index() is now parameterless; update the three invocations
in DocumentationControllerTest (currently using
$controller->index(Request::create(...))) to call $controller->index() with no
arguments, and remove any now-unused Request construction/variables in those
test cases so they compile and run.

---

Outside diff comments:
In `@php/src/Api/Tests/Feature/RateLimitTest.php`:
- Around line 607-611: The foreach in RateLimitTest.php currently declares an
unused key variable ($_tier) causing a PHPMD UnusedLocalVariable warning; update
the loop to iterate values only (use foreach ($tiers as $tierConfig)) so you
drop the unused $_tier while keeping the existing assertions that check 'limit',
'window', and 'burst' on $tierConfig.

---

Nitpick comments:
In `@GOAL.md`:
- Around line 780-782: Update the three list items that reference the function
name dns_get_record in the GOAL.md entries for SeoReportServiceTest,
WebhookDeliveryTest, and WebhookEndpointTest so the regex pattern
^[a-z][a-zA-Z0-9]*$ is wrapped in backticks (inline code) rather than plain
text; keep the function name dns_get_record unchanged but format the regex as
`^[a-z][a-zA-Z0-9]*$` so markdownlint no longer treats the square brackets as
reference links.

In `@php/src/Api/Controllers/Api/WebhookSecretController.php`:
- Line 22: Add two class constants in WebhookSecretController:
CONTENT_WEBHOOK_RESOURCE_NAME = 'Webhook endpoint' and
SOCIAL_WEBHOOK_RESOURCE_NAME = 'Webhook'; then replace all hardcoded literals:
where the code currently uses 'Webhook endpoint' use
self::CONTENT_WEBHOOK_RESOURCE_NAME (e.g., in methods handling content webhooks
such as the ones referencing the existing RESOURCE_NAME), and where it uses
'Webhook' for social webhooks replace with self::SOCIAL_WEBHOOK_RESOURCE_NAME
(update all social webhook handlers that currently use the literal). Ensure you
remove or consolidate the old RESOURCE_NAME if redundant and update every usage
site in the class to reference the new constants.

In `@php/src/Api/Tests/Feature/RateLimitingTest.php`:
- Line 362: The test is creating an unused API key via
createApiKeyForWorkspace($workspace2) which inserts unnecessary DB state; remove
that call so only the relevant api key ($apiKey1) is created/used. Locate the
createApiKeyForWorkspace invocation in RateLimitingTest (near where $workspace2
and $apiKey1 are defined) and delete the single-line call that creates an API
key for $workspace2, ensuring subsequent assertions still use $apiKey1 to test
shared limits.

In `@php/tests/Feature/AuthenticationGuideTest.php`:
- Line 8: Replace the global define('API_KEYS_PREFIXED_WITH', ...) with a scoped
constant to avoid global leakage: declare a class constant (e.g.,
AuthenticationGuideTest::API_KEYS_PREFIXED_WITH) or a file-scoped const in the
same test file and update any references to use that constant name
(API_KEYS_PREFIXED_WITH) from the AuthenticationGuideTest, ensuring the constant
is declared inside the test class (or file scope) rather than via define().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d03c908d-3883-44f6-9d16-bc77ff824d69

📥 Commits

Reviewing files that changed from the base of the PR and between 6b25b9a and dd588cf.

⛔ Files ignored due to path filters (2)
  • go.work.sum is excluded by !**/*.sum
  • go/go.sum is excluded by !**/*.sum
📒 Files selected for processing (126)
  • GOAL.md
  • docs/superpowers/plans/2026-06-06-upstream-router.md
  • docs/superpowers/specs/2026-06-06-upstream-router-design.md
  • external/go
  • external/go-inference
  • external/go-io
  • external/go-log
  • go/api.go
  • go/api_describable_test.go
  • go/api_renderable_test.go
  • go/api_test.go
  • go/authentik_integration_test.go
  • go/authentik_test.go
  • go/authz_test.go
  • go/bridge.go
  • go/bridge_test.go
  • go/brotli.go
  • go/brotli_test.go
  • go/cache_test.go
  • go/chat_completions.go
  • go/chat_completions_internal_test.go
  • go/chat_completions_test.go
  • go/client.go
  • go/client_test.go
  • go/cmd/api/cmd_spec_test.go
  • go/cmd/api/spec_groups_iter.go
  • go/codegen.go
  • go/codegen_test.go
  • go/entitlements.go
  • go/entitlements_test.go
  • go/export_test.go
  • go/expvar_test.go
  • go/go.mod
  • go/graphql_config_test.go
  • go/graphql_test.go
  • go/group_test.go
  • go/gzip_test.go
  • go/httpsign_test.go
  • go/i18n_test.go
  • go/location_test.go
  • go/middleware.go
  • go/middleware_test.go
  • go/modernization_test.go
  • go/openapi.go
  • go/openapi_test.go
  • go/openapitools.json
  • go/options.go
  • go/pkg/provider/cache_control_test.go
  • go/pkg/provider/discovery_test.go
  • go/pkg/provider/proxy_test.go
  • go/pkg/provider/registry_test.go
  • go/pkg/stream/stream_group_test.go
  • go/pprof_test.go
  • go/ratelimit.go
  • go/ratelimit_test.go
  • go/response_meta.go
  • go/response_meta_test.go
  • go/response_test.go
  • go/runtime_config_test.go
  • go/secure_test.go
  • go/service.go
  • go/sessions_test.go
  • go/slog_test.go
  • go/spec_builder_helper_test.go
  • go/spec_registry_test.go
  • go/sse.go
  • go/sse_test.go
  • go/static_test.go
  • go/strict_bind_test.go
  • go/string_constants.go
  • go/sunset_test.go
  • go/swagger.go
  • go/swagger_test.go
  • go/test_constants_test.go
  • go/timeout_test.go
  • go/tracing_test.go
  • go/transformer_test.go
  • go/transport_client_test.go
  • go/upstream_balancer.go
  • go/upstream_balancer_internal_test.go
  • go/upstream_registry.go
  • go/upstream_registry_internal_test.go
  • go/upstream_registry_test.go
  • go/upstream_router.go
  • go/upstream_router_example_test.go
  • go/upstream_router_internal_test.go
  • go/upstream_router_test.go
  • go/upstream_transport.go
  • go/upstream_transport_internal_test.go
  • go/websocket_test.go
  • php/src/Api/Boot.php
  • php/src/Api/Controllers/Api/WebhookSecretController.php
  • php/src/Api/Controllers/McpApiController.php
  • php/src/Api/Database/Factories/ApiKeyFactory.php
  • php/src/Api/Documentation/DocumentationController.php
  • php/src/Api/Documentation/DocumentationServiceProvider.php
  • php/src/Api/Documentation/Examples/CommonExamples.php
  • php/src/Api/Documentation/OpenApiBuilder.php
  • php/src/Api/Models/WebhookEndpoint.php
  • php/src/Api/Routes/api.php
  • php/src/Api/Services/SeoReportService.php
  • php/src/Api/Services/WebhookSignature.php
  • php/src/Api/Services/WebhookTemplateService.php
  • php/src/Api/Tests/Feature/ApiKeyIpWhitelistTest.php
  • php/src/Api/Tests/Feature/ApiKeyRotationTest.php
  • php/src/Api/Tests/Feature/ApiKeyTest.php
  • php/src/Api/Tests/Feature/ApiScopeEnforcementTest.php
  • php/src/Api/Tests/Feature/ApiUsageTest.php
  • php/src/Api/Tests/Feature/AuthenticateApiKeyTest.php
  • php/src/Api/Tests/Feature/DocumentationControllerTest.php
  • php/src/Api/Tests/Feature/McpApiControllerTest.php
  • php/src/Api/Tests/Feature/McpResourceTest.php
  • php/src/Api/Tests/Feature/McpServerAccessTest.php
  • php/src/Api/Tests/Feature/McpServerDetailTest.php
  • php/src/Api/Tests/Feature/OpenApiDocumentationComprehensiveTest.php
  • php/src/Api/Tests/Feature/PixelEndpointTest.php
  • php/src/Api/Tests/Feature/PublicApiCorsTest.php
  • php/src/Api/Tests/Feature/RateLimitTest.php
  • php/src/Api/Tests/Feature/RateLimitingTest.php
  • php/src/Api/Tests/Feature/SeoReportServiceTest.php
  • php/src/Api/Tests/Feature/WebhookDeliveryTest.php
  • php/src/Api/Tests/Feature/WebhookEndpointTest.php
  • php/src/Website/Api/Services/OpenApiGenerator.php
  • php/tests/Feature/ApiSunsetTest.php
  • php/tests/Feature/ApiVersionServiceTest.php
  • php/tests/Feature/AuthenticationGuideTest.php
💤 Files with no reviewable changes (2)
  • php/src/Api/Services/WebhookSignature.php
  • go/codegen_test.go


## 6. Data Flow (one request)

```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Specify fenced code block languages to satisfy markdownlint.

Line 168 and Line 276 use bare triple-backtick fences, which triggers MD040 and can break docs lint gates.

🛠️ Proposed fix
-```
+```text
 hits mounted path  (engine auth/CORS/ratelimit/tracing already ran)
@@
-```
+```

-```
+```text
 go/upstream_registry.go         + _test.go + _example_test.go
@@
-```
+```

Also applies to: 276-276

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 168-168: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-06-06-upstream-router-design.md` at line 168,
Replace the bare triple-backtick fenced code blocks in the document with
language-tagged fences (e.g., ```text) so markdownlint MD040 is satisfied:
update the fence that wraps the block beginning "hits mounted path  (engine
auth/CORS/ratelimit/tracing already ran)" and the fence that wraps the block
starting "go/upstream_registry.go         + _test.go + _example_test.go" to use
```text (or another appropriate language) instead of plain ``` so both instances
are lint-compliant.

Source: Linters/SAST tools

Comment thread go/openapitools.json
Comment on lines +1 to +7
{
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "7.22.0"
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify schema path and OpenAPI generator version

# Check if schema exists at the configured path
if [ -f "go/node_modules/@openapitools/openapi-generator-cli/config.schema.json" ]; then
  echo "✓ Schema found at go/node_modules/..."
elif [ -f "node_modules/@openapitools/openapi-generator-cli/config.schema.json" ]; then
  echo "✗ Schema found at root node_modules/ - path in config should be ../node_modules/..."
else
  echo "✗ Schema not found - verify node_modules location"
fi

# Check npm for latest version of openapi-generator-cli
echo ""
echo "Checking npm registry for openapi-generator-cli versions..."
npm view `@openapitools/openapi-generator-cli` versions --json | jq -r '.[-5:]' 2>/dev/null || echo "npm query failed"

Repository: dAppCore/api

Length of output: 230


🌐 Web query:

OpenAPI Generator CLI latest version 2026 security advisories

💡 Result:

As of June 6, 2026, the latest version of the @openapitools/openapi-generator-cli npm package is 2.33.1, released on May 19, 2026 [1][2]. Regarding security advisories for 2026, there are no direct vulnerabilities in the OpenAPI Generator core codebase currently identified as active, high-impact security threats for the project itself [3][4]. However, users have reported issues concerning vulnerabilities in transitive dependencies during security scans [3][4][5][6]. Key points regarding 2026 security-related reports: 1. Transitive Dependency Alerts: Security scanners like Trivy and npm audit have flagged several vulnerabilities in dependencies (such as basic-ftp [3], path-to-regexp [4], and jackson-core [5][6]). In most cases, these are identified as transitive issues that do not directly affect the generator's functionality or are patched in subsequent releases [3][4][6]. 2. Resolution of Reported Issues: - A vulnerability involving path-to-regexp was resolved in version 2.31.1 following a dependency update [4]. - Concerns regarding jackson-core (GHSA-72hv-8253-57qq) were addressed in the core generator, with fixes integrated into newer releases [5][6]. - Users are advised to monitor the official repository's issues and security advisories page for real-time tracking of dependency-related alerts [2][7]. For the most secure implementation, it is recommended to keep the CLI updated to the latest version [1][8]. If your security compliance tools flag specific transitive dependencies that remain present in the current build, you can use package manager features such as npm overrides to force the use of patched versions of those specific libraries while waiting for upstream updates [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Files mentioning openapitools.json =="
rg -n "openapitools\.json" -S . || true

echo
echo "== package.json files =="
fd -t f "package.json" . || true

echo
echo "== npm/yarn install scripts (best-effort) =="
rg -n "(npm|yarn|pnpm).*install|openapi-generator|openapitools\.json|openapi-generator-cli" -S package.json **/package.json 2>/dev/null || true

Repository: dAppCore/api

Length of output: 169


🌐 Web query:

OpenAPI Generator 7.22.0 release

💡 Result:

OpenAPI Generator version 7.22.0 was officially released on April 28, 2026 [1][2]. This release included over 150 enhancements and bug fixes [3]. It is available through standard package repositories, including Maven Central [2] and the Gradle Plugin Portal [4].

Citations:


🌐 Web query:

OpenAPI Generator 7.22.0 security advisory

💡 Result:

As of June 6, 2026, there is no specific security advisory associated with OpenAPI Generator version 7.22.0 [1]. Version 7.22.0 was released on April 28, 2026, and is a stable release containing approximately 150 enhancements and bug fixes [1][2]. While there is no advisory for 7.22.0, recent security-related maintenance in the project includes: 1. Jackson-core vulnerability (GHSA-72hv-8253-57qq): In March 2026, a high-severity Denial of Service (DoS) vulnerability in the bundled jackson-core library (affecting version 2.18.2) was identified [3]. This issue was addressed in the project by upgrading to jackson-core 2.18.6 or higher [3]. 2. Historical Advisories: Users should be aware of older vulnerabilities, such as CVE-2024-35219 (a path traversal vulnerability affecting OpenAPI Generator Online versions prior to 7.6.0) [4][5][6]. Users are encouraged to review the official release notes and the project's GitHub security advisories page for the most up-to-date information regarding potential security issues [4][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Files mentioning openapitools.json =="
rg -n "openapitools\.json" -S . || true

echo
echo "== package.json files =="
fd -t f "package.json" . || true

echo
echo "== npm/yarn install scripts (best-effort) =="
rg -n "(npm|yarn|pnpm).*install|openapi-generator|openapitools\.json|openapi-generator-cli" -S package.json **/package.json 2>/dev/null || true

Repository: dAppCore/api

Length of output: 169


🌐 Web query:

OpenAPI Generator 7.22.0 release

💡 Result:

OpenAPI Generator version 7.22.0 was officially released on April 28, 2026 [1][2][3]. This release included over 150 enhancements and bug fixes [4]. It is available across standard repositories, including Maven Central [2] and the Gradle Plugin Portal [3]. Detailed release information and a full list of changes can be found in the official GitHub repository's release section [1][4].

Citations:


🌐 Web query:

OpenAPI Generator 7.22.0 security advisory

💡 Result:

As of June 6, 2026, there is no specific security advisory issued for OpenAPI Generator version 7.22.0 [1]. Version 7.22.0 was released on April 28, 2026, and includes various enhancements and bug fixes rather than serving as a security patch release [1][2]. Recent security-related actions for OpenAPI Generator include: - Dependency Updates: In March 2026, a high-severity vulnerability (GHSA-72hv-8253-57qq) in the bundled jackson-core library was identified [3]. This was addressed in the project by upgrading jackson-core to version 2.18.6 or higher, a fix incorporated into the development cycle leading up to recent releases [3]. - Distinctions from Other Projects: Users should be aware of vulnerabilities in related ecosystems, such as the quarkus-openapi-generator extension (e.g., CVE-2026-40180 and CVE-2026-42333), which are distinct from the main OpenAPITools/openapi-generator project [4][5][6]. It is recommended to monitor the official OpenAPITools GitHub security advisories page for any future disclosures [7][8].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find files related to openapi generator tooling =="
fd -t f "openapitools.json" . || true
fd -t f "*openapi*generator*" . || true
fd -t f "package.json" . || true
fd -t f "package-lock.json" . || true
fd -t f "yarn.lock" . || true
fd -t f "pnpm-lock.yaml" . || true

echo
echo "== Search for openapi-generator-cli usage =="
rg -n "`@openapitools/openapi-generator-cli`|openapi-generator-cli|openapitools\.json" -S . || true

echo
echo "== Search for config.schema.json usage =="
rg -n "config\.schema\.json|openapi-generator-cli/config\.schema\.json" -S . || true

Repository: dAppCore/api

Length of output: 4001


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== go/openapitools.json =="
cat -n go/openapitools.json

echo
echo "== scripts/generate-sdks.sh (around openapi-generator-cli call) =="
if [ -f scripts/generate-sdks.sh ]; then
  sed -n '1,120p' scripts/generate-sdks.sh | cat -n
else
  echo "scripts/generate-sdks.sh not found"
fi

echo
echo "== go/codegen.go (buildArgs / invocation) =="
sed -n '1,240p' go/codegen.go | cat -n

echo
echo "== go/cmd/api/cmd_sdk.go (install instructions / usage) =="
sed -n '1,120p' go/cmd/api/cmd_sdk.go | cat -n

echo
echo "== go/codegen_test.go (any mention of openapitools.json) =="
rg -n "openapitools\.json|openapi-generator-cli|config\.schema\.json|generator-cli\.version" go/codegen_test.go go -S || true

Repository: dAppCore/api

Length of output: 15243


Fix $schema path reliability in go/openapitools.json

  • go/openapitools.json sets $schema to ./node_modules/@openapitools/openapi-generator-cli/config.schema.json, which only resolves if @openapitools/openapi-generator-cli is installed under go/node_modules (often it’s under repo-root node_modules). Update the path (e.g., ../node_modules/...) or point to a stable URL if this is meant for editor/schema validation.
  • generator-cli.version: "7.22.0" corresponds to OpenAPI Generator 7.22.0 (released 28 Apr 2026) and there’s no specific security advisory for that version; this pin doesn’t look problematic.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/openapitools.json` around lines 1 - 7, The $schema path in
go/openapitools.json is relative to the go/ folder and currently points to
./node_modules/@openapitools/openapi-generator-cli/config.schema.json which will
only work if that package is installed under go/node_modules; update the $schema
to a reliable path such as
../node_modules/@openapitools/openapi-generator-cli/config.schema.json or a
stable URL (e.g., the package’s schema on npmjs or raw GitHub) so editor/schema
validation resolves correctly; keep the existing generator-cli.version entry
unchanged unless you want to intentionally bump it.

Comment thread go/strict_bind_test.go
Comment on lines +179 to +182
// Default engine on a non-loopback ephemeral port: must bind and exit
// cleanly on context cancellation — strict enforcement is opt-in only.
e, err = New(WithAddr("127.0.0.1:0"))
if err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Serve test does not exercise the documented non-loopback case.

The comment says this case verifies default behaviour on a non-loopback bind, but Line 181 uses 127.0.0.1:0 (loopback). This weakens the intended Serve coverage for public binds in non-strict mode.

Proposed fix
-	e, err = New(WithAddr("127.0.0.1:0"))
+	e, err = New(WithAddr("0.0.0.0:0"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Default engine on a non-loopback ephemeral port: must bind and exit
// cleanly on context cancellation — strict enforcement is opt-in only.
e, err = New(WithAddr("127.0.0.1:0"))
if err != nil {
// Default engine on a non-loopback ephemeral port: must bind and exit
// cleanly on context cancellation — strict enforcement is opt-in only.
e, err = New(WithAddr("0.0.0.0:0"))
if err != nil {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/strict_bind_test.go` around lines 179 - 182, The test comment says it
should verify default behavior on a non-loopback bind but the code creates the
engine with New(WithAddr("127.0.0.1:0")) which is loopback; change the address
passed to WithAddr to a non-loopback ephemeral address (e.g. "0.0.0.0:0") so the
Serve test actually exercises public/non-loopback binding in non-strict mode
while keeping the rest of the test flow (error checks around New and subsequent
Serve/cancellation) unchanged.

Comment thread go/upstream_balancer.go
Comment on lines +41 to +45
cw := b.current[key]
if cw == nil {
cw = map[string]int{}
b.current[key] = cw
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

current state can grow unbounded with high-cardinality selector keys.

Line 41 initialises per-key weight state for every observed key, but there is no eviction path. With default-pool fallback and user-controlled selector values, this map can grow indefinitely and degrade memory/GC behaviour.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/upstream_balancer.go` around lines 41 - 45, The per-key state map
b.current (variables key and cw) is unbounded and can grow indefinitely; add an
eviction strategy to limit growth: introduce a maxEntries constant and track
insertion order (e.g., an LRU list or a simple FIFO ring) alongside b.current,
and when inserting a new key in the cw initialization path evict the oldest
entries until size <= maxEntries; ensure modifications to b.current and the
order tracker are done under the existing balancer lock/guard to keep it
concurrency-safe and preserve the current initialization logic for cw.

// (cloned) map fetched via resolve. With a shallow copy both touch the same map
// and the race detector fires (and "concurrent map writes" panics); with the
// deep copy the stored map is independent, so neither happens.
func TestUpstreamRegistry_Ugly_HeadersDeepCopy(t *testing.T) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Rename this test to end with _Ugly for naming-rule compliance.

TestUpstreamRegistry_Ugly_HeadersDeepCopy does not satisfy the required suffix rule used across Go tests.

As per coding guidelines, "**/*_test.go: Use Go test naming convention with _Good, _Bad, or _Ugly suffixes".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/upstream_registry_internal_test.go` at line 70, The test function name
TestUpstreamRegistry_Ugly_HeadersDeepCopy does not end with the required `_Ugly`
suffix; rename it to end with `_Ugly` (for example
TestUpstreamRegistry_HeadersDeepCopy_Ugly) and update the function declaration
and any references/imports that reference the test function name so the test
still compiles and runs; ensure only the function identifier is changed (keep
the body and behavior intact).

Source: Coding guidelines


func TestUpstreamRouter_MissingModel_Bad(t *testing.T) {
reg := api.NewUpstreamRegistry()
_ = reg.SetDefault(api.Upstream{URL: "https://example.com"})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don’t discard setup errors in these tests.

At Line 59, Line 101, Line 133, Line 165, Line 192, Line 213, Line 244, and Line 251, setup errors are ignored. That can hide invalid test preconditions and make failures harder to diagnose.

Suggested fix
- _ = reg.Set("m", api.Upstream{URL: dead.URL})
+ if err := reg.Set("m", api.Upstream{URL: dead.URL}); err != nil {
+ 	t.Fatalf("Set: %v", err)
+ }

- e, _ := api.New(api.WithRateLimit(100), api.WithUpstreamRouter(reg))
+ e, err := api.New(api.WithRateLimit(100), api.WithUpstreamRouter(reg))
+ if err != nil {
+ 	t.Fatalf("New: %v", err)
+ }

Also applies to: 101-101, 133-133, 165-165, 192-192, 213-213, 244-244, 251-251

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/upstream_router_test.go` at line 59, Tests are ignoring errors from
reg.SetDefault(api.Upstream{URL: "https://example.com"}) which can hide setup
failures; change each ignored call (the occurrences of reg.SetDefault in this
file) to check the returned error and fail the test on error (e.g., capture err
:= reg.SetDefault(...); if err != nil { t.Fatalf("failed to set default
upstream: %v", err) } or use require.NoError(t, err)) so setup errors surface
immediately.

Comment thread go/upstream_router.go
if cfg.transport == nil {
// Clone so the router owns an isolated connection pool rather than
// mutating/sharing the process-wide http.DefaultTransport.
cfg.transport = http.DefaultTransport.(*http.Transport).Clone()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the target file with line numbers around the flagged areas.
FILE="go/upstream_router.go"

echo "=== File existence ==="
ls -la "$FILE"

echo "=== SPDX header / top of file (first 60 lines) ==="
sed -n '1,60p' "$FILE" | cat -n

echo "=== Around line 203 ==="
sed -n '180,230p' "$FILE" | cat -n

echo "=== Around line 347 ==="
sed -n '320,380p' "$FILE" | cat -n

echo "=== Search for other relevant compliance concerns in this file ==="
rg -n --hidden --no-ignore-vcs "http\.DefaultTransport|MaxBytes|MaxBytesError|DefaultTransport\.\(|Clone\(\)|strings\.|errors\.|fmt\.|buffer|os\.|process\.|env\." "$FILE" || true

# Check for wrapper usage guidance: ensure dappco.re/go wrappers appear where appropriate.
rg -n --hidden --no-ignore-vcs "dappco\.re/go/(json|errors|formatting|strings|buffers|filesystem|process|environment)" "$FILE" || true

Repository: dAppCore/api

Length of output: 7503


Guard the http.DefaultTransport cast to prevent init-time panics (go/upstream_router.go:203)

cfg.transport = http.DefaultTransport.(*http.Transport).Clone() will panic during initialisation if http.DefaultTransport has been replaced with a non-*http.Transport RoundTripper.

Suggested fix
- cfg.transport = http.DefaultTransport.(*http.Transport).Clone()
+ if base, ok := http.DefaultTransport.(*http.Transport); ok {
+ 	cfg.transport = base.Clone()
+ } else {
+ 	cfg.transport = http.DefaultTransport
+ }
  • Minor correctness: oversized-body detection relies on an exact string match (err.Error() == "http: request body too large"); match net/http’s MaxBytesError type instead to avoid misclassification across Go versions/configurations.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cfg.transport = http.DefaultTransport.(*http.Transport).Clone()
if base, ok := http.DefaultTransport.(*http.Transport); ok {
cfg.transport = base.Clone()
} else {
cfg.transport = http.DefaultTransport
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/upstream_router.go` at line 203, Guard the http.DefaultTransport cast by
checking its dynamic type before cloning: replace the direct cast at
cfg.transport = http.DefaultTransport.(*http.Transport).Clone() with a type
assertion (if t, ok := http.DefaultTransport.(*http.Transport); ok {
cfg.transport = t.Clone() } else { cfg.transport = &http.Transport{} }) so we
don’t panic if DefaultTransport is a non-*http.Transport; also stop matching
oversized-body by string comparison and instead detect net/http’s MaxBytesError
via errors.As (e.g. var mbe *http.MaxBytesError; if errors.As(err, &mbe) { ...
}) and add the "errors" import if missing.

Comment thread go/upstream_router.go
Comment on lines +347 to +348
if err.Error() == "http: request body too large" {
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, Fail(errCodeRequestTooLarge, "Request body exceeds the maximum allowed size"))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use typed http.MaxBytesError detection instead of err.Error() string comparison.

In go/upstream_router.go (lines 347-348), comparing err.Error() to "http: request body too large" is fragile; replace it with a type-based check for *http.MaxBytesError (via the project’s core.As/errors.As helper) so wrapped/changed error text still yields HTTP 413.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/upstream_router.go` around lines 347 - 348, Replace the fragile string
comparison err.Error() == "http: request body too large" with a typed check for
*http.MaxBytesError using the project's error-as helper (e.g., core.As or
errors.As): attempt to cast err into a variable like var maxBytesErr
*http.MaxBytesError and if the cast succeeds, call
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge,
Fail(errCodeRequestTooLarge, "Request body exceeds the maximum allowed size"));
update the branch around the error handling in upstream_router.go accordingly so
wrapped or reformatted errors still produce HTTP 413.

Comment on lines +25 to +26
define('API_ROUTE_WORKSPACE', '/{workspace}');
define('API_ROUTE_ID', '/{id}');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard route constants against redefinition warnings.

Using unguarded define() at bootstrap level can emit warnings if this routes file is loaded more than once in a process. Wrapping these with defined() checks makes this robust.

Suggested fix
-define('API_ROUTE_WORKSPACE', '/{workspace}');
-define('API_ROUTE_ID', '/{id}');
+if (! defined('API_ROUTE_WORKSPACE')) {
+    define('API_ROUTE_WORKSPACE', '/{workspace}');
+}
+if (! defined('API_ROUTE_ID')) {
+    define('API_ROUTE_ID', '/{id}');
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
define('API_ROUTE_WORKSPACE', '/{workspace}');
define('API_ROUTE_ID', '/{id}');
if (! defined('API_ROUTE_WORKSPACE')) {
define('API_ROUTE_WORKSPACE', '/{workspace}');
}
if (! defined('API_ROUTE_ID')) {
define('API_ROUTE_ID', '/{id}');
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@php/src/Api/Routes/api.php` around lines 25 - 26, The route constants
API_ROUTE_WORKSPACE and API_ROUTE_ID are defined unguarded which can trigger
"already defined" warnings if the file is included multiple times; wrap each
define call with a defined() check so you only call
define('API_ROUTE_WORKSPACE', '/{workspace}') and define('API_ROUTE_ID',
'/{id}') when the constant is not already defined (use
defined('API_ROUTE_WORKSPACE') and defined('API_ROUTE_ID') to guard them) to
avoid redefinition warnings at bootstrap.

config(['api-docs.ui.default' => $ui]);

$response = $controller->index(Request::create('/api/docs', 'GET'));
$response = $controller->index(Request::create(API_DOCS_PATH, 'GET'));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove the extra argument when calling index().

Line 104, Line 116, and Line 127 still call index() with a Request, but DocumentationController::index() is now parameterless. This will break these tests at runtime.

Suggested fix
-        $response = $controller->index(Request::create(API_DOCS_PATH, 'GET'));
+        $response = $controller->index();
...
-    $response = $controller->index(Request::create(API_DOCS_PATH, 'GET'));
+    $response = $controller->index();
...
-    $response = $controller->index(Request::create(API_DOCS_PATH, 'GET'));
+    $response = $controller->index();

Also applies to: 116-116, 127-127

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@php/src/Api/Tests/Feature/DocumentationControllerTest.php` at line 104, The
test calls to DocumentationController::index are passing a Request even though
index() is now parameterless; update the three invocations in
DocumentationControllerTest (currently using
$controller->index(Request::create(...))) to call $controller->index() with no
arguments, and remove any now-unused Request construction/variables in those
test cases so they compile and run.

Snider and others added 9 commits June 6, 2026 14:08
…dapters

Hybrid /v1/chat/completions: local-first (ModelResolver.Knows) → remote
(reuses UpstreamRegistry + balancer + transport from the upstream router),
passthrough by default, per-model ChatFormatAdapter for non-OpenAI upstreams
(Ollama-native + Anthropic, incl. streaming transcoders), bind opt-in gated by
bearer. Answers RFC.providers Open Question 4 (hybrid). One spec; units kept
crisp for task-splitting.

Co-Authored-By: Virgil <[email protected]>
5 TDD tasks: ModelResolver.Knows, remote backend core (config + options +
local-first dispatch + OpenAI passthrough + bind opt-in + integration),
OllamaAdapter, AnthropicAdapter, example+QA. Reuses the upstream router's
balancer/transport; full no-placeholder code per step.

Co-Authored-By: Virgil <[email protected]>
Knows used the raw name for cache/yaml/discovery lookups while
ResolveModel trims+lowercases. A mixed-case request (e.g. GPT-4) for a
known lowercased model (gpt-4) wrongly returned false, routing a local
model to the remote backend. Trim+lowercase to match ResolveModel
exactly. Adds TestModelResolver_Knows_CaseInsensitive_Good.

Co-Authored-By: Virgil <[email protected]>
…tants

writeChatCompletionError already owns the 503 Retry-After header, so the
cooldown-derived c.Header set in dispatchRemote was always clobbered (dead
code). Drop it (and the now-unused strconv import) and reuse hdrContentType /
mimeJSON for the passthrough header sets, matching the Sonar string-literal
dedup sweep.

Co-Authored-By: Virgil <[email protected]>
…ail-closed)

The off-loopback gate previously trusted that a bearer was *configured* and
that the separate bearer middleware had rejected unauthenticated requests — it
never verified the inbound request actually carried a valid bearer (fail-open,
brittle against middleware skip-lists). The handler now validates the request's
Authorization: Bearer header itself, in constant time, so the gate fails closed
regardless of middleware coverage.

- Engine gains bearerToken; WithBearerAuth records it alongside bearerConfigured.
- bearerValidator builds a request-validating closure (crypto/subtle compare),
  nil when no token is configured.
- chatCompletionsHandler holds validateBearer; the off-loopback branch rejects
  unless allowRemote AND a validator exists AND the request bearer matches.
- BindGuard_Ugly covers: no opt-in -> 403; opt-in + valid bearer -> allowed;
  opt-in + missing/wrong bearer -> 403; opt-in + no configured bearer -> 403.

Co-Authored-By: Virgil <[email protected]>
Snider and others added 5 commits June 6, 2026 15:04
… + empty-stream role priming

Mirror the hardened Ollama transcoder: return the scanner error (suppressing
[DONE]) on a truncated/errored stream so the client sees an incomplete response
rather than a clean termination, and prime delta.role:"assistant" on the finish
chunk when no content_block_delta arrived.

Co-Authored-By: Virgil <[email protected]>
@Snider Snider changed the title Integrate dev → main: WithUpstreamRouter (selector-keyed reverse proxy) + queued API work Integrate dev → main: WithUpstreamRouter + chat-completions remote backend + queued API work Jun 6, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/specs/2026-06-06-chat-completions-remote-backend-design.md`:
- Line 113: Two Markdown triple-backtick fences in the spec are missing language
labels; update those fences to include the appropriate language identifiers
(e.g., ```js, ```json, ```bash, or ```yaml) that match each block's content so
the docs linter (MD040) passes—locate the two unlabeled ``` blocks in the
document and add the correct fence languages.

In `@go/chat_adapter_anthropic.go`:
- Around line 71-74: Replace direct encoding/json usage with the repo's core
JSON wrappers: use core.JSONMarshal instead of json.Marshal when marshalling the
request body (the raw variable created from body) and use core.JSONUnmarshal
instead of json.Unmarshal when decoding responses (the places handling
respBody/resp bytes). Update imports to remove "encoding/json" and ensure the
core package is imported, and propagate errors using core.E as currently done
(e.g., "anthropic", "marshal request") while calling the core JSON functions for
consistent error handling.

In `@go/chat_adapter.go`:
- Around line 45-57: writeChatChunk (and sibling writeSSEDone) currently swallow
marshal and write errors which hides stream failures; change their signatures to
return error (e.g., func writeChatChunk(... ) error), check core.JSONMarshal for
errors and the type assertion before writing, check and return any
io.WriteString/w.Write errors instead of ignoring them, and keep calling flush()
only after successful writes; then update all callers to handle the returned
error and abort/propagate transcode work on failure so stream errors are not
silently dropped.

In `@go/chat_completions.go`:
- Around line 804-810: Normalize the adapter lookup key to a canonical form
(e.g., strings.ToLower) so adapter resolution and dispatch use the same key:
when retrieving the pool call h.remote.reg.resolve with req.Model as before but
use the canonicalized model string to index h.remote.adapters (replace
h.remote.adapters[req.Model] with h.remote.adapters[canonicalModel]); also
update the registration path in WithChatModelAdapter to store adapters under the
same canonical key (e.g., canonicalModel := strings.ToLower(model) and use that
for map writes) so reads and writes match and case-variants (like "CLAUDE-3" vs
"claude-3") do not bypass the adapter and hit the wrong upstream.
- Around line 771-803: When h.remote != nil the code currently uses
core.JSONUnmarshalString to populate req (lenient) which lets hybrid mode bypass
strict validation; fix by only using a lenient/unstructured unmarshal into a
small temp struct (e.g., struct{Model string} or similar) to inspect the model
for h.resolver.Knows, and always call decodeJSONBody (the strict decoder used
elsewhere) to populate the full chat request (req) before calling
validateChatRequest or serveLocal; update the branch that currently calls
core.JSONUnmarshalString to instead peek the model into a temp value and, if
serving locally, re-decode raw with decodeJSONBody into req so
validateChatRequest and serveLocal keep local behaviour intact.

In `@go/chat_remote_example_test.go`:
- Around line 6-9: The example is using fmt.Println instead of the Core println
helper; replace all fmt.Println calls in the example functions (the Example...
test(s) in this file) with core.Println and update imports: remove the "fmt"
import and add the Core package import (e.g., "dappco.re/go/core"). Ensure every
fmt.Println occurrence (including the ones around lines 24-25) is changed to
core.Println so the example prints through Core as per guidelines.

In `@go/chat_remote.go`:
- Around line 105-109: The passthrough branch currently overwrites headers
instead of forwarding the upstream response headers; update the adapter==nil
branch in the function containing the adapter nil check so you copy all headers
from the upstream http.Response (resp.Header) into the Gin context response (use
c.Writer.Header().Set/Add or c.Header for each header) before setting the status
and writing the body, preserving existing Content-Type behavior (e.g., only set
hdrContentType/mimeJSON if upstream did not provide one). Apply the same
header-forwarding change to the second passthrough block (the other adapter==nil
branch around the later response handling) so upstream metadata like
rate-limit/retry headers are forwarded to clients.
- Line 104: The code currently ignores errors from io.ReadAll when reading
resp.Body (body, _ := io.ReadAll(io.LimitReader(resp.Body,
maxUpstreamResponseBytes))) which can forward partial/invalid responses; update
the logic in the function that handles the upstream response to capture the
error (e.g., body, err := io.ReadAll(...)) and if err != nil return or write an
appropriate error response (and log the error) instead of streaming/forwarding
the (partial) body; apply the same change to the second occurrence using resp
and maxUpstreamResponseBytes so you verify read success before sending
status/headers/body downstream.
- Line 37: Guard the cast of http.DefaultTransport to *http.Transport before
calling Clone so we don't panic if DefaultTransport is a different RoundTripper:
in the code path that sets cfg.transport (where you currently do cfg.transport =
http.DefaultTransport.(*http.Transport).Clone()), check the type with a type
assertion or type switch (e.g., t, ok :=
http.DefaultTransport.(*http.Transport)); if ok use t.Clone() to set
cfg.transport, otherwise either clone a new http.Transport with reasonable
defaults or wrap/convert the non-*http.Transport RoundTripper into an
*http.Transport fallback and assign that to cfg.transport; update the assignment
in the same function where cfg.transport is initialized to handle both branches
safely.
- Line 126: The Transcode call currently discards errors; change the call to
capture its returned error from adapter.Transcoder().Transcode(c.Writer, flush,
resp.Body, meta) and handle it instead of assigning to blank identifier: check
if err != nil and then propagate/handle it (e.g., log the error with context
including meta/resp info via the existing logger, close/abort the response
stream or return the error up the call stack so the client is aware of a
truncated/malformed upstream stream) using the surrounding request/response
handling routines rather than ignoring it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f695a08e-6efc-468e-88fe-48e30de4e46e

📥 Commits

Reviewing files that changed from the base of the PR and between dd588cf and c28242d.

📒 Files selected for processing (15)
  • docs/superpowers/plans/2026-06-06-chat-completions-remote-backend.md
  • docs/superpowers/specs/2026-06-06-chat-completions-remote-backend-design.md
  • go/api.go
  • go/chat_adapter.go
  • go/chat_adapter_anthropic.go
  • go/chat_adapter_anthropic_test.go
  • go/chat_adapter_ollama.go
  • go/chat_adapter_ollama_test.go
  • go/chat_completions.go
  • go/chat_completions_internal_test.go
  • go/chat_remote.go
  • go/chat_remote_example_test.go
  • go/chat_remote_internal_test.go
  • go/chat_remote_test.go
  • go/options.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • go/chat_completions_internal_test.go


## 5. Dispatch Flow

```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add fence language identifiers to Markdown code blocks.

The code fences at Line 113 and Line 233 are missing language labels (MD040), which can fail docs lint in CI.

Also applies to: 233-233

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 113-113: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-06-06-chat-completions-remote-backend-design.md`
at line 113, Two Markdown triple-backtick fences in the spec are missing
language labels; update those fences to include the appropriate language
identifiers (e.g., ```js, ```json, ```bash, or ```yaml) that match each block's
content so the docs linter (MD040) passes—locate the two unlabeled ``` blocks in
the document and add the correct fence languages.

Source: Linters/SAST tools

Comment on lines +71 to +74
raw, err := json.Marshal(body)
if err != nil {
return nil, nil, core.E("anthropic", "marshal request", err)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file around the flagged lines
FILE="go/chat_adapter_anthropic.go"
echo "== File exists? =="
ls -l "$FILE" || true

echo "== SPDX header (first 5 lines) =="
sed -n '1,8p' "$FILE" | nl -ba

echo "== Imports =="
rg -n "^import" -n "$FILE" || true
# Print import block fully (if small)
sed -n '1,60p' "$FILE" | nl -ba

echo "== Flagged JSON marshal/unmarshal sections (lines ~60-110, ~120-160) =="
sed -n '60,115p' "$FILE" | nl -ba
sed -n '115,170p' "$FILE" | nl -ba

echo "== Search for dappco.re/go JSON wrapper usage patterns in repo =="
rg -n "dappco\.re/go/.+json|dappco\.re/.+json" -S . || true

echo "== Search for encoding/json usage in this file =="
rg -n "encoding/json" "$FILE" || true
rg -n "json\.(Marshal|Unmarshal)" "$FILE" || true

echo "== Find any dappco.re/go json wrapper package name by scanning go.mod or docs =="
if [ -f go.mod ]; then
  echo "== go.mod relevant lines =="
  rg -n "dappco\.re/go" go.mod || true
fi

echo "== Search for wrapper package in local module (if vendored) =="
# list candidate directories without assuming layout
fd -t d "dappco.re" . || true
fd -t f "json*.go" . | head -n 50 || true

Repository: dAppCore/api

Length of output: 249


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="go/chat_adapter_anthropic.go"

echo "== File exists? =="
ls -l "$FILE" || true

echo "== SPDX header (first 8 lines) =="
sed -n '1,8p' "$FILE" | awk '{printf "%4d:%s\n", NR, $0}'

echo "== Imports block =="
# Print import block by scanning from first import to closing paren/line
awk '
  $0 ~ /^import/ {in=1}
  in{
    print NR ":" $0
    if ($0 ~ /^\)/) {in=0}
    # if single-line import, stop
    if ($0 ~ /^import "/ && $0 !~ /\(/) {in=0}
  }
' "$FILE" | head -n 80

echo "== Flagged JSON usage regions =="
echo "-- Lines 60-120 --"
sed -n '60,120p' "$FILE" | awk '{printf "%4d:%s\n", (60+NR-1), $0}'
echo "-- Lines 120-170 --"
sed -n '120,170p' "$FILE" | awk '{printf "%4d:%s\n", (120+NR-1), $0}'

echo "== Search for encoding/json in this file =="
rg -n "encoding/json" "$FILE" || true
rg -n "json\.(Marshal|Unmarshal)" "$FILE" || true

echo "== Search for dappco.re/go JSON wrapper usage in repo =="
# Heuristic: wrapper paths typically include dappco.re/go/.../json
rg -n "dappco\.re/go/.*/json" . || true
rg -n "dappco\.re/go.*json" . || true

echo "== Search for any local package aliasing of json wrappers =="
rg -n "as json|jsonwrap|djson|core\.json" "$FILE" . || true

echo "== go.mod check for dappco.re/go =="
if [ -f go.mod ]; then
  rg -n "dappco\.re/go" go.mod || true
fi

Repository: dAppCore/api

Length of output: 450


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="go/chat_adapter_anthropic.go"

echo "== Header (first 25 lines) =="
sed -n '1,25p' "$FILE" | cat

echo "== Imports/encoding/json references =="
rg -n "encoding/json|json\.Marshal|json\.Unmarshal" "$FILE" || true

echo "== View around flagged lines (65-105) =="
sed -n '65,105p' "$FILE" | cat

echo "== View around flagged lines (88-105) =="
sed -n '88,105p' "$FILE" | cat

echo "== View around flagged lines (130-155) =="
sed -n '130,155p' "$FILE" | cat

echo "== Look for dappco.re/go JSON wrapper usage in repo =="
rg -n "dappco\.re/go/.+json" . || true
rg -n "dappco\.re/go/.*/(encoding|json)/" . || true
rg -n "dappco\.re/go/.+/(Marshal|Unmarshal)" . || true

echo "== Look for any import paths that wrap json encoding in this repo =="
rg -n "\"dappco\.re/go/.+\".*json|\"dappco\.re/go/.+json\"" -S . || true
rg -n "\"dappco\.re/go/.+\"$" -S go | head -n 200 || true

echo "== go.mod dappco.re/go presence (if any) =="
if [ -f go.mod ]; then
  rg -n "dappco\.re/go" go.mod || true
fi

Repository: dAppCore/api

Length of output: 8898


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for any dappco.re/go json wrapper packages in repo =="

# common guesses: .../json, .../encoding/json, .../jsonutil, .../corejson
rg -n "dappco\.re/go/.+json" -S . || true

echo "== Search for core json helper imports by alias =="

# look for imports that include json in the path, even if not caught by above
rg -n "import \\(.*\"dappco\\.re/go/.*json" -S . || true
rg -n "\"dappco\\.re/go/.*json\"" -S . || true

echo "== Search for usage of potential wrapper helper identifiers in Go code =="

# These are common wrapper function names; adjust based on discoveries.
rg -n "core\\.json|corejson|jsonwrap|Marshal\\(|Unmarshal\\(" go --glob='*.go' || true

echo "== Search for direct wrapper usage patterns in existing code (encoding/json avoided?) =="

# Find other files that import encoding/json and see whether they use core wrappers instead.
rg -n "import \\(.*encoding/json|\"encoding/json\"" -S go --glob='*.go' || true

echo "== Search for dappco.re/go wrappers for JSON marshal/unmarshal by identifier =="

# This checks for any exported wrapper functions with clear names (e.g., JSONMarshal/JSONUnmarshal).
rg -n "JSON(Marshal|Unmarshal|Decode|Encode)|MarshalJSON|UnmarshalJSON" -S . || true

Repository: dAppCore/api

Length of output: 45953


Replace direct encoding/json calls with core JSON wrappers in chat_adapter_anthropic
go/chat_adapter_anthropic.go imports encoding/json and uses json.Marshal/json.Unmarshal at lines 71, 92–94, and 138–140. The repo’s v0.9.0 Core compliance shape expects dappco.re/go JSON wrappers (e.g., core.JSONMarshal / core.JSONUnmarshal) wherever available.

raw, err := json.Marshal(body)
if err != nil {
	return nil, nil, core.E("anthropic", "marshal request", err)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/chat_adapter_anthropic.go` around lines 71 - 74, Replace direct
encoding/json usage with the repo's core JSON wrappers: use core.JSONMarshal
instead of json.Marshal when marshalling the request body (the raw variable
created from body) and use core.JSONUnmarshal instead of json.Unmarshal when
decoding responses (the places handling respBody/resp bytes). Update imports to
remove "encoding/json" and ensure the core package is imported, and propagate
errors using core.E as currently done (e.g., "anthropic", "marshal request")
while calling the core JSON functions for consistent error handling.

Source: Coding guidelines

Comment thread go/chat_adapter.go
Comment on lines +45 to +57
func writeChatChunk(w io.Writer, flush func(), chunk ChatCompletionChunk) {
data := core.JSONMarshal(chunk)
raw, ok := data.Value.([]byte)
if !data.OK || !ok {
return
}
_, _ = io.WriteString(w, "data: ")
_, _ = w.Write(raw)
_, _ = io.WriteString(w, "\n\n")
if flush != nil {
flush()
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

SSE helper writes fail silently and cannot be propagated.

writeChatChunk/writeSSEDone ignore marshal/write errors, so stream failures are swallowed instead of stopping transcode work.

Also applies to: 60-64

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/chat_adapter.go` around lines 45 - 57, writeChatChunk (and sibling
writeSSEDone) currently swallow marshal and write errors which hides stream
failures; change their signatures to return error (e.g., func writeChatChunk(...
) error), check core.JSONMarshal for errors and the type assertion before
writing, check and return any io.WriteString/w.Write errors instead of ignoring
them, and keep calling flush() only after successful writes; then update all
callers to handle the returned error and abort/propagate transcode work on
failure so stream errors are not silently dropped.

Comment thread go/chat_completions.go
Comment on lines +771 to +803
if h.remote != nil {
result := core.JSONUnmarshalString(string(raw), &req)
if !result.OK {
writeChatCompletionError(c, http.StatusBadRequest, "invalid_request_error", "body", "invalid request body", "")
return
}
} else {
if err := decodeJSONBody(bytes.NewReader(raw), &req); err != nil {
writeChatCompletionError(c, http.StatusBadRequest, "invalid_request_error", "body", "invalid request body", "")
return
}
}

if err := validateChatRequest(&req); err != nil {
chatErr, ok := err.(*chatCompletionRequestError)
if !ok {
chatErr, isChatErr := err.(*chatCompletionRequestError)
if !isChatErr {
writeChatCompletionError(c, http.StatusBadRequest, "invalid_request_error", "body", err.Error(), "")
return
}
writeChatCompletionError(c, chatErr.Status, chatErr.Type, chatErr.Param, chatErr.Message, chatErr.Code)
return
}

// PURE-LOCAL: unchanged current behaviour (no Knows gate).
if h.remote == nil {
h.serveLocal(c, req)
return
}
// HYBRID: local-first if the resolver knows the model; else remote.
if h.resolver != nil && h.resolver.Knows(req.Model) {
h.serveLocal(c, req)
return
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Hybrid mode weakens local request validation for known models.

When h.remote != nil, requests are always parsed via lenient unmarshal first, and known local models are served from that parsed payload. This bypasses local unknown-field rejection and changes local behaviour just by enabling remote routing.

Suggested patch
 	// HYBRID: local-first if the resolver knows the model; else remote.
 	if h.resolver != nil && h.resolver.Knows(req.Model) {
-		h.serveLocal(c, req)
+		var localReq ChatCompletionRequest
+		if err := decodeJSONBody(bytes.NewReader(raw), &localReq); err != nil {
+			writeChatCompletionError(c, http.StatusBadRequest, "invalid_request_error", "body", "invalid request body", "")
+			return
+		}
+		h.serveLocal(c, localReq)
 		return
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if h.remote != nil {
result := core.JSONUnmarshalString(string(raw), &req)
if !result.OK {
writeChatCompletionError(c, http.StatusBadRequest, "invalid_request_error", "body", "invalid request body", "")
return
}
} else {
if err := decodeJSONBody(bytes.NewReader(raw), &req); err != nil {
writeChatCompletionError(c, http.StatusBadRequest, "invalid_request_error", "body", "invalid request body", "")
return
}
}
if err := validateChatRequest(&req); err != nil {
chatErr, ok := err.(*chatCompletionRequestError)
if !ok {
chatErr, isChatErr := err.(*chatCompletionRequestError)
if !isChatErr {
writeChatCompletionError(c, http.StatusBadRequest, "invalid_request_error", "body", err.Error(), "")
return
}
writeChatCompletionError(c, chatErr.Status, chatErr.Type, chatErr.Param, chatErr.Message, chatErr.Code)
return
}
// PURE-LOCAL: unchanged current behaviour (no Knows gate).
if h.remote == nil {
h.serveLocal(c, req)
return
}
// HYBRID: local-first if the resolver knows the model; else remote.
if h.resolver != nil && h.resolver.Knows(req.Model) {
h.serveLocal(c, req)
return
}
if h.remote != nil {
result := core.JSONUnmarshalString(string(raw), &req)
if !result.OK {
writeChatCompletionError(c, http.StatusBadRequest, "invalid_request_error", "body", "invalid request body", "")
return
}
} else {
if err := decodeJSONBody(bytes.NewReader(raw), &req); err != nil {
writeChatCompletionError(c, http.StatusBadRequest, "invalid_request_error", "body", "invalid request body", "")
return
}
}
if err := validateChatRequest(&req); err != nil {
chatErr, isChatErr := err.(*chatCompletionRequestError)
if !isChatErr {
writeChatCompletionError(c, http.StatusBadRequest, "invalid_request_error", "body", err.Error(), "")
return
}
writeChatCompletionError(c, chatErr.Status, chatErr.Type, chatErr.Param, chatErr.Message, chatErr.Code)
return
}
// PURE-LOCAL: unchanged current behaviour (no Knows gate).
if h.remote == nil {
h.serveLocal(c, req)
return
}
// HYBRID: local-first if the resolver knows the model; else remote.
if h.resolver != nil && h.resolver.Knows(req.Model) {
var localReq ChatCompletionRequest
if err := decodeJSONBody(bytes.NewReader(raw), &localReq); err != nil {
writeChatCompletionError(c, http.StatusBadRequest, "invalid_request_error", "body", "invalid request body", "")
return
}
h.serveLocal(c, localReq)
return
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/chat_completions.go` around lines 771 - 803, When h.remote != nil the code
currently uses core.JSONUnmarshalString to populate req (lenient) which lets
hybrid mode bypass strict validation; fix by only using a lenient/unstructured
unmarshal into a small temp struct (e.g., struct{Model string} or similar) to
inspect the model for h.resolver.Knows, and always call decodeJSONBody (the
strict decoder used elsewhere) to populate the full chat request (req) before
calling validateChatRequest or serveLocal; update the branch that currently
calls core.JSONUnmarshalString to instead peek the model into a temp value and,
if serving locally, re-decode raw with decodeJSONBody into req so
validateChatRequest and serveLocal keep local behaviour intact.

Comment thread go/chat_completions.go
Comment on lines +804 to +810
pool, found := h.remote.reg.resolve(req.Model)
if !found {
writeChatCompletionError(c, http.StatusNotFound, "invalid_request_error", "model", "model not found: "+req.Model, "model_not_found")
return
}
h.dispatchRemote(c, req, raw, pool, h.remote.adapters[req.Model])
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalise adapter lookup key to avoid accidental passthrough.

Adapter resolution is keyed by exact model text (h.remote.adapters[req.Model]). A case variant (for example, registered as claude-3 but requested as CLAUDE-3) can skip the adapter and hit the wrong upstream contract.

Suggested patch
-	h.dispatchRemote(c, req, raw, pool, h.remote.adapters[req.Model])
+	adapter := h.remote.adapters[req.Model]
+	if adapter == nil {
+		adapter = h.remote.adapters[core.Lower(core.Trim(req.Model))]
+	}
+	h.dispatchRemote(c, req, raw, pool, adapter)

Also normalise the registration key in WithChatModelAdapter so write/read use the same canonical form.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pool, found := h.remote.reg.resolve(req.Model)
if !found {
writeChatCompletionError(c, http.StatusNotFound, "invalid_request_error", "model", "model not found: "+req.Model, "model_not_found")
return
}
h.dispatchRemote(c, req, raw, pool, h.remote.adapters[req.Model])
}
pool, found := h.remote.reg.resolve(req.Model)
if !found {
writeChatCompletionError(c, http.StatusNotFound, "invalid_request_error", "model", "model not found: "+req.Model, "model_not_found")
return
}
adapter := h.remote.adapters[req.Model]
if adapter == nil {
adapter = h.remote.adapters[core.Lower(core.Trim(req.Model))]
}
h.dispatchRemote(c, req, raw, pool, adapter)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/chat_completions.go` around lines 804 - 810, Normalize the adapter lookup
key to a canonical form (e.g., strings.ToLower) so adapter resolution and
dispatch use the same key: when retrieving the pool call h.remote.reg.resolve
with req.Model as before but use the canonicalized model string to index
h.remote.adapters (replace h.remote.adapters[req.Model] with
h.remote.adapters[canonicalModel]); also update the registration path in
WithChatModelAdapter to store adapters under the same canonical key (e.g.,
canonicalModel := strings.ToLower(model) and use that for map writes) so reads
and writes match and case-variants (like "CLAUDE-3" vs "claude-3") do not bypass
the adapter and hit the wrong upstream.

Comment on lines +6 to +9
"fmt"

api "dappco.re/go/api"
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use Core Println in the example output path.

This example currently prints through fmt.Println; the repo guideline asks examples to print through Core.

Suggested patch
 import (
-	"fmt"
+	core "dappco.re/go"
 
 	api "dappco.re/go/api"
 )
@@
-	fmt.Println(engine.Addr())
+	core.Println(engine.Addr())

As per coding guidelines, "Examples use Example<Symbol> or a valid lowercase suffix variant and print through Core Println."

Also applies to: 24-25

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/chat_remote_example_test.go` around lines 6 - 9, The example is using
fmt.Println instead of the Core println helper; replace all fmt.Println calls in
the example functions (the Example... test(s) in this file) with core.Println
and update imports: remove the "fmt" import and add the Core package import
(e.g., "dappco.re/go/core"). Ensure every fmt.Println occurrence (including the
ones around lines 24-25) is changed to core.Println so the example prints
through Core as per guidelines.

Source: Coding guidelines

Comment thread go/chat_remote.go
cfg.failover = defaultFailoverStatuses()
}
if cfg.transport == nil {
cfg.transport = http.DefaultTransport.(*http.Transport).Clone()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the http.DefaultTransport cast to avoid startup panics.

If http.DefaultTransport is replaced with a non-*http.Transport round-tripper, this assertion panics.

Proposed fix
 if cfg.transport == nil {
-	cfg.transport = http.DefaultTransport.(*http.Transport).Clone()
+	if t, ok := http.DefaultTransport.(*http.Transport); ok {
+		cfg.transport = t.Clone()
+	} else {
+		cfg.transport = http.DefaultTransport
+	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cfg.transport = http.DefaultTransport.(*http.Transport).Clone()
if cfg.transport == nil {
if t, ok := http.DefaultTransport.(*http.Transport); ok {
cfg.transport = t.Clone()
} else {
cfg.transport = http.DefaultTransport
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/chat_remote.go` at line 37, Guard the cast of http.DefaultTransport to
*http.Transport before calling Clone so we don't panic if DefaultTransport is a
different RoundTripper: in the code path that sets cfg.transport (where you
currently do cfg.transport = http.DefaultTransport.(*http.Transport).Clone()),
check the type with a type assertion or type switch (e.g., t, ok :=
http.DefaultTransport.(*http.Transport)); if ok use t.Clone() to set
cfg.transport, otherwise either clone a new http.Transport with reasonable
defaults or wrap/convert the non-*http.Transport RoundTripper into an
*http.Transport fallback and assign that to cfg.transport; update the assignment
in the same function where cfg.transport is initialized to handle both branches
safely.

Comment thread go/chat_remote.go
func (h *chatCompletionsHandler) deliverRemote(c *gin.Context, req ChatCompletionRequest, adapter ChatFormatAdapter, resp *http.Response) {
// Non-2xx: passthrough copies verbatim; adapter wraps in the OpenAI error shape.
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, maxUpstreamResponseBytes))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle body read failures before writing downstream responses.

At Line 104 and Line 131, read errors are ignored. A partial read can be forwarded as a successful/full response, which is incorrect and hard to diagnose.

Proposed fix
-body, _ := io.ReadAll(io.LimitReader(resp.Body, maxUpstreamResponseBytes))
+body, err := io.ReadAll(io.LimitReader(resp.Body, maxUpstreamResponseBytes))
+if err != nil {
+	writeChatCompletionError(c, http.StatusBadGateway, "invalid_request_error", "model", "could not read upstream response", "upstream_read_error")
+	return
+}

Also applies to: 131-131

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/chat_remote.go` at line 104, The code currently ignores errors from
io.ReadAll when reading resp.Body (body, _ :=
io.ReadAll(io.LimitReader(resp.Body, maxUpstreamResponseBytes))) which can
forward partial/invalid responses; update the logic in the function that handles
the upstream response to capture the error (e.g., body, err := io.ReadAll(...))
and if err != nil return or write an appropriate error response (and log the
error) instead of streaming/forwarding the (partial) body; apply the same change
to the second occurrence using resp and maxUpstreamResponseBytes so you verify
read success before sending status/headers/body downstream.

Comment thread go/chat_remote.go
Comment on lines +105 to +109
if adapter == nil {
c.Header(hdrContentType, mimeJSON)
c.Status(resp.StatusCode)
_, _ = c.Writer.Write(body)
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Passthrough response handling drops upstream headers.

The passthrough path currently rewrites headers instead of forwarding upstream response headers, so clients lose upstream metadata (for example rate-limit and retry hints), which breaks the advertised verbatim behaviour.

Proposed fix
+for k, vals := range resp.Header {
+	for _, v := range vals {
+		c.Writer.Header().Add(k, v)
+	}
+}
 if adapter == nil {
-	c.Header(hdrContentType, mimeJSON)
 	c.Status(resp.StatusCode)
 	_, _ = c.Writer.Write(body)
 	return
 }

Also applies to: 132-136

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/chat_remote.go` around lines 105 - 109, The passthrough branch currently
overwrites headers instead of forwarding the upstream response headers; update
the adapter==nil branch in the function containing the adapter nil check so you
copy all headers from the upstream http.Response (resp.Header) into the Gin
context response (use c.Writer.Header().Set/Add or c.Header for each header)
before setting the status and writing the body, preserving existing Content-Type
behavior (e.g., only set hdrContentType/mimeJSON if upstream did not provide
one). Apply the same header-forwarding change to the second passthrough block
(the other adapter==nil branch around the later response handling) so upstream
metadata like rate-limit/retry headers are forwarded to clients.

Comment thread go/chat_remote.go
return
}
meta := ChatStreamMeta{ID: newChatCompletionID(), Model: req.Model, Created: time.Now().Unix()}
_ = adapter.Transcoder().Transcode(c.Writer, flush, resp.Body, meta)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not discard stream transcoder errors.

At Line 126, Transcode errors are dropped. This hides truncated or malformed upstream-stream failures after a 200 has already started.

Proposed fix
-		_ = adapter.Transcoder().Transcode(c.Writer, flush, resp.Body, meta)
+		if err := adapter.Transcoder().Transcode(c.Writer, flush, resp.Body, meta); err != nil {
+			_ = c.Error(err)
+		}
 		return
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_ = adapter.Transcoder().Transcode(c.Writer, flush, resp.Body, meta)
if err := adapter.Transcoder().Transcode(c.Writer, flush, resp.Body, meta); err != nil {
_ = c.Error(err)
}
return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/chat_remote.go` at line 126, The Transcode call currently discards errors;
change the call to capture its returned error from
adapter.Transcoder().Transcode(c.Writer, flush, resp.Body, meta) and handle it
instead of assigning to blank identifier: check if err != nil and then
propagate/handle it (e.g., log the error with context including meta/resp info
via the existing logger, close/abort the response stream or return the error up
the call stack so the client is aware of a truncated/malformed upstream stream)
using the surrounding request/response handling routines rather than ignoring
it.

Snider and others added 5 commits June 6, 2026 15:37
…rface

Surface remote/hybrid chat-completions (flip ChatCompletionsEnabled for
chatRemote) and WithUpstreamRouter mounted paths (minimal honest POST proxy
items, deduped against real items) in /v1/openapi.json + SDK gen. Follows the
existing special-cased-path mechanism; no new abstraction.

Co-Authored-By: Virgil <[email protected]>
3 TDD tasks: chat-completions enabled for remote/hybrid, WithUpstreamRouter
path items (deduped proxy items), QA gate. Extends the existing special-cased
path mechanism; full no-placeholder code.

Co-Authored-By: Virgil <[email protected]>
…ackends

Widen ChatCompletionsEnabled in TransportConfig() to fire for a remote
backend (e.chatRemote) as well as a local resolver, so the top-level
x-chat-completions-enabled capability flag honestly reports a live remote
chat endpoint. The chat path item itself already surfaced via the default
path wiring; this corrects the flag a client/tool reads to discover chat.

Test asserts both the typed inference path and the x-chat-completions-enabled
flag; the flag assertion fails without the transport.go change.

Co-Authored-By: Virgil <[email protected]>
@Snider Snider changed the title Integrate dev → main: WithUpstreamRouter + chat-completions remote backend + queued API work Integrate dev → main: inference routing (upstream router + remote chat) + OpenAPI describability + queued API work Jun 6, 2026
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 6, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
10.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@Snider Snider merged commit aef4485 into main Jun 7, 2026
4 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant